diff --git a/frontend/src/app/components/task-backends/page.tsx b/frontend/src/app/components/task-backends/page.tsx new file mode 100644 index 0000000..349750d --- /dev/null +++ b/frontend/src/app/components/task-backends/page.tsx @@ -0,0 +1,5 @@ +import { ComponentsTaskBackendsPage } from "@/components/pages/ComponentsTaskBackendsPage"; + +export default function Page() { + return ; +} diff --git a/frontend/src/components/assistant-editor/background-task-panel.tsx b/frontend/src/components/assistant-editor/background-task-panel.tsx index d07d031..b077b94 100644 --- a/frontend/src/components/assistant-editor/background-task-panel.tsx +++ b/frontend/src/components/assistant-editor/background-task-panel.tsx @@ -28,7 +28,7 @@ function TaskCard({ task, onChanged }: { task: BackgroundTask; onChanged: () => {task.instruction} {labels[task.status]} -

{task.summary}

+

{task.backendType === "reasoner" ? "内置 Reasoner" : "OpenCode"} · {task.summary}

{task.status === "waiting_input" && request.kind === "permission" &&

远端请求:{request.permission} {request.patterns?.join("、")}

@@ -45,7 +45,7 @@ function TaskCard({ task, onChanged }: { task: BackgroundTask; onChanged: () =>
} {task.result &&
查看结果
{task.result}
} - {task.status === "unknown" && } + {task.status === "unknown" && } {!terminal.has(task.status) && } {error &&

{error}

} ; diff --git a/frontend/src/components/assistant-editor/prompt-editor.tsx b/frontend/src/components/assistant-editor/prompt-editor.tsx index 2a70a90..d525c93 100644 --- a/frontend/src/components/assistant-editor/prompt-editor.tsx +++ b/frontend/src/components/assistant-editor/prompt-editor.tsx @@ -41,6 +41,7 @@ import { SectionAnchorTabs } from "@/components/editor/section-anchor-tabs"; import { SectionCard } from "@/components/editor/section-card"; import { VisionConfigSection } from "@/components/editor/vision-config-section"; import { BuiltInToolsEditor } from "@/components/assistant-editor/built-in-tools"; +import { TaskBackendSection } from "@/components/assistant-editor/task-backend-section"; import { ToolBindingEditor } from "@/components/tools/tool-binding-editor"; import { TurnConfigEditor } from "@/components/turn-config-editor"; import { Button } from "@/components/ui/button"; @@ -90,7 +91,6 @@ const EXTERNAL_TOOL_TYPE_LABELS: Record = { http: "HTTP", mcp: "MCP", client: "Client", - task: "后台任务", }; const OPENING_MODE_OPTIONS: Array<{ @@ -126,6 +126,7 @@ const ENTRY_MODE_OPTIONS: Array<{ const promptSections = [ { id: "conversation", label: "对话内容" }, { id: "models", label: "模型与语音" }, + { id: "tasks", label: "任务后端" }, { id: "capabilities", label: "知识与工具" }, { id: "interaction", label: "交互策略" }, { id: "variables", label: "动态变量" }, @@ -191,6 +192,7 @@ export function PromptEditor({ const sectionRefs = useRef>({ conversation: null, models: null, + tasks: null, capabilities: null, interaction: null, variables: null, @@ -640,6 +642,13 @@ export function PromptEditor({ { + if (runtimeMode === "realtime" && form.taskBackend) { + const confirmed = window.confirm( + "Realtime 暂不支持任务后端,切换会解除当前助手的后端绑定,后端资源仍会保留。是否继续?", + ); + if (!confirmed) return; + updateForm("taskBackend", null); + } updateForm("runtimeMode", runtimeMode); if ( runtimeMode === "realtime" && @@ -662,7 +671,7 @@ export function PromptEditor({ {form.runtimeMode === "pipeline" ? ( <> +
{ + sectionRefs.current.tasks = element; + }} + className="scroll-mt-3 space-y-3" + > + updateForm("taskBackend", value)} + pipeline={form.runtimeMode === "pipeline"} + variables={promptVariableSuggestions} + modelOptions={llmOptions} + /> +
+
{ sectionRefs.current.capabilities = element; @@ -882,7 +906,6 @@ export function PromptEditor({ description="从资源库添加工具,并配置当前助手的参数来源与结果写入" > = { + model: "Speaker 生成任务要求", literal: "固定任务要求", variable: "动态变量", template: "模板任务要求", +}; + +/** Task backends are configured independently from ordinary conversation tools. */ +export function TaskBackendSection({ value, onChange, pipeline, variables, modelOptions }: { + value: AssistantTaskBackend | null; + onChange: (value: AssistantTaskBackend | null) => void; + pipeline: boolean; + variables: Record; + modelOptions: Array<{ value: string; label: string }>; +}) { + const [resources, setResources] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(""); + const variableListId = useId(); + const load = useCallback(async () => { + setLoading(true); + setError(""); + try { setResources(await taskBackendsApi.list()); } + catch (error) { setError(error instanceof Error ? error.message : "任务后端加载失败"); } + finally { setLoading(false); } + }, []); + useEffect(() => { + // Synchronize resource choices with the API. + // eslint-disable-next-line react-hooks/set-state-in-effect + void load(); + }, [load]); + + const selected = resources.find(resource => resource.id === value?.backendId); + const source = value?.instructionSource ?? { source: "model" }; + function changeSource(kind: ValueSource["source"]) { + if (!value) return; + const instructionSource: ValueSource = kind === "model" ? { source: "model" } + : kind === "variable" ? { source: "variable", variableName: "" } : { source: kind, value: "" }; + onChange({ ...value, instructionSource }); + } + + return } title="任务后端" + description="Speaker 负责交流与澄清,任务后端在独立上下文中执行;任务期间可继续对话。" + action={管理资源}> + {!pipeline ?

任务后端仅适用于提示词 Pipeline;Realtime 当前不可用。

: <> + + {error &&
{error}
} + {!loading && !error && !resources.length &&

先在组件库创建一个任务后端,然后回到此处选择。

} + {value && selected && <> + {!selected.enabled &&

当前后端已停用,请启用资源或重新选择后端。

} +
+ {selected.type === "reasoner" ? <> +

推理模型:{modelOptions.find(model => model.value === selected.config.modelResourceId)?.label ?? selected.config.modelResourceId}

+

推理强度:{selected.config.reasoningEffort === "provider_default" ? "模型默认" : selected.config.reasoningEffort} · 服务端工具 {selected.config.toolBindings.length} 项 · 技能 {selected.config.skills.length} 项

+ : <> +

OpenCode:{selected.config.url} · {selected.config.directory}

+

Agent:{selected.config.agent || "远端默认"} · 权限、沙箱与技能由远端管理

+ {selected.config.skillsNote &&

技能说明:{selected.config.skillsNote}

} + } +

任务时限 {selected.config.timeoutSeconds} 秒 · 并发 1 · 断线后任务继续

+
+ } + {value &&
+