Add workflow support and enhance runtime configuration in models and services

- Introduce RuntimeModelResource and RuntimeKnowledgeBase classes to manage workflow resources.
- Update AssistantConfig to include workflow_model_resources and workflow_knowledge_bases for better integration.
- Refactor validation and processing logic in routes and services to accommodate workflow types.
- Implement dynamic variable support for workflow assistants and enhance graph normalization.
- Add ToolExecutor for reusable tool execution across different assistant types.
- Update various services to ensure compatibility with new workflow features and improve error handling.
This commit is contained in:
Xin Wang
2026-07-13 16:13:27 +08:00
parent 6108b00007
commit 32aef14ddb
27 changed files with 2563 additions and 910 deletions

View File

@@ -53,13 +53,6 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
} from "@/components/ui/sheet";
import {
DropdownMenu,
DropdownMenuContent,
@@ -478,10 +471,15 @@ export function AssistantPage(props: AssistantPageProps) {
defaultGraph(),
);
const [workflowSettings, setWorkflowSettings] = useState<WorkflowSettings>({
globalPrompt: defaultGraph().settings.globalPrompt,
allowInterrupt: true,
turnConfig: defaultTurnConfig(),
});
const [debugOpen, setDebugOpen] = useState(false);
const [workflowDynamicVariableDefinitions, setWorkflowDynamicVariableDefinitions] =
useState<Record<string, DynamicVariableDefinition>>({});
const [workflowDebugOpen, setWorkflowDebugOpen] = useState(false);
const [workflowEditingNodeId, setWorkflowEditingNodeId] = useState<string | null>(null);
const [workflowEditingEdgeId, setWorkflowEditingEdgeId] = useState<string | null>(null);
const [activeNodeId, setActiveNodeId] = useState<string | null>(null);
const [dynamicVariablesOpen, setDynamicVariablesOpen] = useState(false);
@@ -708,6 +706,7 @@ export function AssistantPage(props: AssistantPageProps) {
name: workflowName,
graph: workflowGraph,
settings: workflowSettings,
dynamicVariableDefinitions: workflowDynamicVariableDefinitions,
}),
);
}
@@ -849,19 +848,24 @@ export function AssistantPage(props: AssistantPageProps) {
: defaultGraph();
const wfSettings: WorkflowSettings = {
llm: assistant.modelResourceIds.LLM,
asr: assistant.modelResourceIds.ASR,
tts: assistant.modelResourceIds.TTS,
asr: graph.settings?.defaultAsrResourceId || assistant.modelResourceIds.ASR,
tts: graph.settings?.defaultTtsResourceId || assistant.modelResourceIds.TTS,
globalPrompt: graph.settings?.globalPrompt ?? "",
allowInterrupt: assistant.enableInterrupt,
turnConfig: assistant.turnConfig,
};
setWorkflowName(assistant.name);
setWorkflowGraph(graph);
setWorkflowSettings(wfSettings);
setWorkflowDynamicVariableDefinitions(
assistant.dynamicVariableDefinitions ?? {},
);
setSavedSnapshot(
JSON.stringify({
name: assistant.name,
graph,
settings: wfSettings,
dynamicVariableDefinitions: assistant.dynamicVariableDefinitions ?? {},
}),
);
}
@@ -913,6 +917,7 @@ export function AssistantPage(props: AssistantPageProps) {
...(workflowSettings.tts ? { TTS: workflowSettings.tts } : {}),
},
graph: workflowGraph as unknown as Record<string, unknown>,
dynamicVariableDefinitions: workflowDynamicVariableDefinitions,
}),
);
}
@@ -932,6 +937,7 @@ export function AssistantPage(props: AssistantPageProps) {
name: workflowName,
graph: workflowGraph,
settings: workflowSettings,
dynamicVariableDefinitions: workflowDynamicVariableDefinitions,
})
: null;
const dirty =
@@ -1377,7 +1383,11 @@ export function AssistantPage(props: AssistantPageProps) {
<div className="flex shrink-0 gap-2">
{saveError && (
<span className="self-center text-xs text-destructive">
<span
role="alert"
title={saveError}
className="line-clamp-2 max-w-[min(42vw,560px)] self-center text-right text-sm leading-5 text-destructive"
>
{saveError}
</span>
)}
@@ -1385,7 +1395,11 @@ export function AssistantPage(props: AssistantPageProps) {
variant="outline"
className="gap-2 border-hairline-strong text-foreground hover:bg-surface-strong"
disabled={!editingId}
onClick={() => setDebugOpen(true)}
onClick={() => {
setWorkflowEditingNodeId(null);
setWorkflowEditingEdgeId(null);
setWorkflowDebugOpen(true);
}}
>
<Bug size={16} />
@@ -1411,43 +1425,49 @@ export function AssistantPage(props: AssistantPageProps) {
onChange={setWorkflowGraph}
settings={workflowSettings}
onSettingsChange={setWorkflowSettings}
onOpenDynamicVariables={() => setDynamicVariablesOpen(true)}
editingNodeId={workflowEditingNodeId}
onEditingNodeIdChange={setWorkflowEditingNodeId}
editingEdgeId={workflowEditingEdgeId}
onEditingEdgeIdChange={setWorkflowEditingEdgeId}
debugOpen={workflowDebugOpen}
onDebugOpenChange={(open) => {
setWorkflowDebugOpen(open);
if (!open) setActiveNodeId(null);
}}
debugPanel={
<DebugDrawer
overlay
assistantId={editingId}
onClose={() => {
setWorkflowDebugOpen(false);
setActiveNodeId(null);
}}
hasUnsavedChanges={dirty}
onNodeActive={setActiveNodeId}
dynamicVariablesEnabled
dynamicVariableDefinitions={workflowDynamicVariableDefinitions}
/>
}
activeNodeId={activeNodeId}
modelOptions={{
llm: credOptions("LLM"),
asr: credOptions("ASR"),
tts: credOptions("TTS"),
}}
toolOptions={tools
.filter((tool) => tool.status === "active")
.map((tool) => ({ value: tool.id, label: tool.name }))}
knowledgeOptions={kbOptions}
/>
</div>
<Sheet
open={debugOpen}
onOpenChange={(open) => {
setDebugOpen(open);
if (!open) setActiveNodeId(null);
}}
modal={false}
>
<SheetContent
side="right"
showOverlay={false}
onInteractOutside={(e) => e.preventDefault()}
className="w-[440px] gap-0 border-l border-hairline bg-card p-0 sm:max-w-[440px]"
>
<SheetHeader className="sr-only">
<SheetTitle></SheetTitle>
<SheetDescription>
,
</SheetDescription>
</SheetHeader>
<DebugDrawer
assistantId={editingId}
asSheet
hasUnsavedChanges={dirty}
onNodeActive={setActiveNodeId}
/>
</SheetContent>
</Sheet>
<DynamicVariablesDialog
open={dynamicVariablesOpen}
onOpenChange={setDynamicVariablesOpen}
definitions={workflowDynamicVariableDefinitions}
onChange={setWorkflowDynamicVariableDefinitions}
/>
</div>
);
}
@@ -2132,7 +2152,8 @@ function SegmentedIconButton({
function DebugDrawer({
assistantId,
asSheet = false,
overlay = false,
onClose,
hasUnsavedChanges = false,
onNodeActive,
vision = false,
@@ -2140,7 +2161,8 @@ function DebugDrawer({
dynamicVariableDefinitions = {},
}: {
assistantId: string | null;
asSheet?: boolean;
overlay?: boolean;
onClose?: () => void;
hasUnsavedChanges?: boolean;
onNodeActive?: (nodeId: string | null) => void;
vision?: boolean;
@@ -2179,14 +2201,25 @@ function DebugDrawer({
[camera, preview],
);
const containerClass = asSheet
? "flex h-full min-w-0 flex-1 flex-col overflow-hidden"
: "hidden min-w-0 flex-1 flex-col overflow-hidden rounded-2xl border border-hairline bg-card shadow-sm lg:flex";
return (
<aside className={containerClass}>
<div className="flex shrink-0 items-center justify-between gap-3 border-b border-hairline px-5 py-3">
<aside
className={overlay
? "flex h-full min-w-0 flex-1 flex-col overflow-hidden bg-card"
: "hidden min-w-0 flex-1 flex-col overflow-hidden rounded-2xl border border-hairline bg-card shadow-sm lg:flex"}
>
<div className={`flex min-h-14 shrink-0 items-center justify-between gap-3 border-b border-hairline py-3 ${overlay ? "px-4" : "px-5"}`}>
<div className="flex min-w-0 items-center gap-2.5">
{overlay && onClose && (
<button
type="button"
aria-label="关闭调试预览"
title="关闭"
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full border border-hairline-strong bg-card text-muted-foreground shadow-sm transition-colors hover:text-foreground"
onClick={onClose}
>
<X size={16} />
</button>
)}
<div className="shrink-0 text-sm font-medium text-foreground">
</div>