Compare commits

...

4 Commits

Author SHA1 Message Date
60f3029e54 update sht 2026-03-12 11:37:08 +08:00
fe7ff9a516 fix tool initialization failure 2026-03-12 11:36:37 +08:00
33faedc1b1 enable comma in tool list 2026-03-12 11:36:11 +08:00
c9b1c5cb32 support both docker and local dev 2026-03-11 22:31:37 +08:00
8 changed files with 142 additions and 29 deletions

View File

@@ -36,6 +36,7 @@ type EditableAgent = {
pipelineId: string; pipelineId: string;
promptSetId?: string; promptSetId?: string;
toolKeys: string[]; toolKeys: string[];
toolKeysInput: string;
prompts: Record<string, string>; prompts: Record<string, string>;
apiKey: string; apiKey: string;
llmName: string; llmName: string;
@@ -459,6 +460,7 @@ function toEditable(
pipelineId: config.pipeline_id, pipelineId: config.pipeline_id,
promptSetId: config.prompt_set_id, promptSetId: config.prompt_set_id,
toolKeys: config.tool_keys || [], toolKeys: config.tool_keys || [],
toolKeysInput: (config.tool_keys || []).join(", "),
prompts: config.prompt_dict || {}, prompts: config.prompt_dict || {},
apiKey: config.api_key || DEFAULT_API_KEY, apiKey: config.api_key || DEFAULT_API_KEY,
llmName: DEFAULT_LLM_NAME, llmName: DEFAULT_LLM_NAME,
@@ -750,6 +752,7 @@ export default function App() {
graphId, graphId,
prompts: { ...defaults.prompt_dict }, prompts: { ...defaults.prompt_dict },
toolKeys: defaults.tool_keys || [], toolKeys: defaults.tool_keys || [],
toolKeysInput: (defaults.tool_keys || []).join(", "),
actBackend: actBackend:
graphId === "deepagent" graphId === "deepagent"
? prev.actBackend || DEFAULT_DEEPAGENT_ACT_BACKEND ? prev.actBackend || DEFAULT_DEEPAGENT_ACT_BACKEND
@@ -1366,8 +1369,12 @@ export default function App() {
<label> <label>
tool_keys (comma separated) tool_keys (comma separated)
<input <input
value={editor.toolKeys.join(", ")} value={editor.toolKeysInput}
onChange={(e) => updateEditor("toolKeys", parseToolCsv(e.target.value))} onChange={(e) => {
const raw = e.target.value;
updateEditor("toolKeysInput", raw);
updateEditor("toolKeys", parseToolCsv(raw));
}}
placeholder="tool_a, tool_b" placeholder="tool_a, tool_b"
disabled={busy} disabled={busy}
/> />

View File

@@ -0,0 +1,22 @@
import { describe, expect, it } from "vitest";
import { joinApiUrl } from "./frontApis";
describe("joinApiUrl", () => {
it("keeps same-origin paths when base url is slash", () => {
expect(joinApiUrl("/", "/v1/pipelines")).toBe("/v1/pipelines");
});
it("joins absolute host and trims trailing slash", () => {
expect(joinApiUrl("http://127.0.0.1:8500/", "/v1/pipelines")).toBe(
"http://127.0.0.1:8500/v1/pipelines"
);
});
it("accepts path without leading slash", () => {
expect(joinApiUrl("http://127.0.0.1:8500", "v1/pipelines")).toBe(
"http://127.0.0.1:8500/v1/pipelines"
);
});
});

View File

@@ -22,6 +22,18 @@ import type {
const API_BASE_URL = const API_BASE_URL =
import.meta.env.VITE_FRONT_API_BASE_URL?.trim() || "http://127.0.0.1:8500"; import.meta.env.VITE_FRONT_API_BASE_URL?.trim() || "http://127.0.0.1:8500";
export function joinApiUrl(baseUrl: string, path: string): string {
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
const normalizedBase = baseUrl.trim();
// "/" is commonly used in Docker+nginx builds and should resolve as same-origin.
if (!normalizedBase || normalizedBase === "/") {
return normalizedPath;
}
return `${normalizedBase.replace(/\/+$/, "")}${normalizedPath}`;
}
// Log which backend the frontend is targeting on startup, with file + line hint. // Log which backend the frontend is targeting on startup, with file + line hint.
// This runs once when the module is loaded. // This runs once when the module is loaded.
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@@ -30,7 +42,8 @@ console.info(
); );
async function fetchJson<T>(path: string, init?: RequestInit): Promise<T> { async function fetchJson<T>(path: string, init?: RequestInit): Promise<T> {
const response = await fetch(`${API_BASE_URL}${path}`, { const url = joinApiUrl(API_BASE_URL, path);
const response = await fetch(url, {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
...(init?.headers || {}), ...(init?.headers || {}),
@@ -49,7 +62,24 @@ async function fetchJson<T>(path: string, init?: RequestInit): Promise<T> {
} }
throw new Error(message); throw new Error(message);
} }
return (await response.json()) as T;
if (response.status === 204) {
return undefined as T;
}
const bodyText = await response.text();
if (!bodyText.trim()) {
return undefined as T;
}
try {
return JSON.parse(bodyText) as T;
} catch {
const preview = bodyText.slice(0, 160).replace(/\s+/g, " ").trim();
throw new Error(
`Expected JSON response from ${url}, but received non-JSON content: ${preview || "<empty>"}`
);
}
} }
export function listAvailableGraphs(): Promise<AvailableGraphsResponse> { export function listAvailableGraphs(): Promise<AvailableGraphsResponse> {
@@ -189,7 +219,10 @@ export async function streamAgentChatResponse(
): Promise<string> { ): Promise<string> {
const { appId, sessionId, apiKey, message, onText, signal } = options; const { appId, sessionId, apiKey, message, onText, signal } = options;
const response = await fetch( const response = await fetch(
`${API_BASE_URL}/v1/apps/${encodeURIComponent(appId)}/sessions/${encodeURIComponent(sessionId)}/responses`, joinApiUrl(
API_BASE_URL,
`/v1/apps/${encodeURIComponent(appId)}/sessions/${encodeURIComponent(sessionId)}/responses`
),
{ {
method: "POST", method: "POST",
headers: { headers: {

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"root":["./src/App.tsx","./src/activeConfigSelection.test.ts","./src/activeConfigSelection.ts","./src/main.tsx","./src/types.ts","./src/vite-env.d.ts","./src/api/frontApis.ts"],"version":"5.9.3"} {"root":["./src/App.tsx","./src/activeConfigSelection.test.ts","./src/activeConfigSelection.ts","./src/main.tsx","./src/types.ts","./src/vite-env.d.ts","./src/api/frontApis.test.ts","./src/api/frontApis.ts"],"version":"5.9.3"}

View File

@@ -4,5 +4,15 @@ export default defineConfig({
plugins: [react()], plugins: [react()],
server: { server: {
port: 5173, port: 5173,
proxy: {
"/v1": {
target: "http://127.0.0.1:8500",
changeOrigin: true,
},
"/apps": {
target: "http://127.0.0.1:8500",
changeOrigin: true,
},
},
}, },
}); });

View File

@@ -5,6 +5,16 @@ export default defineConfig({
plugins: [react()], plugins: [react()],
server: { server: {
port: 5173, port: 5173,
proxy: {
"/v1": {
target: "http://127.0.0.1:8500",
changeOrigin: true,
},
"/apps": {
target: "http://127.0.0.1:8500",
changeOrigin: true,
},
},
}, },
}); });

View File

@@ -75,23 +75,40 @@ def build_route(
**_: Any, **_: Any,
): ):
cmd_opt = [ cmd_opt = [
"--pipeline.pipeline-id", pipeline_id, "--pipeline.pipeline-id",
pipeline_id,
"route", # ------------ "route", # ------------
"--llm-name", llm_name, "--llm-name",
"--api-key", api_key, llm_name,
"--pipeline-id", pipeline_id, "--api-key",
"--prompt-set-id", prompt_set, api_key,
"tool_node", # ------------ "--pipeline-id",
"--llm-name", llm_name, pipeline_id,
"--api-key", api_key, "--prompt-set-id",
"--pipeline-id", pipeline_id, prompt_set,
"--prompt-set-id", prompt_set,
] ]
if tool_keys: if tool_keys:
cmd_opt.extend( cmd_opt.extend(
["--tool-manager-config.client-tool-manager.tool-keys", *tool_keys] ["--tool-manager-config.client-tool-manager.tool-keys", *tool_keys]
) )
# Tyro parses list options greedily across positional subcommands; repeat a
# parent-level option to terminate list parsing before `tool_node`.
cmd_opt.extend(["--pipeline-id", pipeline_id])
cmd_opt.extend(
[
"tool_node", # ------------
"--llm-name",
llm_name,
"--api-key",
api_key,
"--pipeline-id",
pipeline_id,
"--prompt-set-id",
prompt_set,
]
)
return _build_and_load_pipeline_config(pipeline_id, pipeline_config_dir, cmd_opt) return _build_and_load_pipeline_config(pipeline_id, pipeline_config_dir, cmd_opt)
@@ -106,12 +123,17 @@ def build_react(
**_: Any, **_: Any,
): ):
cmd_opt = [ cmd_opt = [
"--pipeline.pipeline-id", pipeline_id, "--pipeline.pipeline-id",
pipeline_id,
"react", # ------------ "react", # ------------
"--llm-name", llm_name, "--llm-name",
"--api-key", api_key, llm_name,
"--pipeline-id", pipeline_id, "--api-key",
"--prompt-set-id", prompt_set, api_key,
"--pipeline-id",
pipeline_id,
"--prompt-set-id",
prompt_set,
] ]
if tool_keys: if tool_keys:
cmd_opt.extend( cmd_opt.extend(
@@ -146,22 +168,31 @@ def build_deep_agent(
) )
cmd_opt = [ cmd_opt = [
"--pipeline.pipeline-id", pipeline_id, "--pipeline.pipeline-id",
pipeline_id,
"deepagent", "deepagent",
"--llm-name", llm_name, "--llm-name",
"--api-key", api_key, llm_name,
"--pipeline-id", pipeline_id, "--api-key",
"--prompt-set-id", prompt_set, api_key,
backend_subcommand, "--pipeline-id",
pipeline_id,
"--prompt-set-id",
prompt_set,
] ]
if tool_keys: if tool_keys:
cmd_opt.extend( cmd_opt.extend(
["--tool-manager-config.client-tool-manager.tool-keys", *tool_keys] ["--tool-manager-config.client-tool-manager.tool-keys", *tool_keys]
) )
# Same greedy-list behavior as `build_route`; terminate before backend subcommand.
cmd_opt.extend(["--pipeline-id", pipeline_id])
cmd_opt.append(backend_subcommand)
return _build_and_load_pipeline_config(pipeline_id, pipeline_config_dir, cmd_opt) return _build_and_load_pipeline_config(pipeline_id, pipeline_config_dir, cmd_opt)
# {pipeline_id: build_function} # {pipeline_id: build_function}
GRAPH_BUILD_FNCS = { GRAPH_BUILD_FNCS = {
"routing": build_route, "routing": build_route,