diff --git a/web/app/dashboard/ai-agents/_components/config-workbench.tsx b/web/app/dashboard/ai-agents/_components/config-workbench.tsx index 9aabcf1..5d644d1 100644 --- a/web/app/dashboard/ai-agents/_components/config-workbench.tsx +++ b/web/app/dashboard/ai-agents/_components/config-workbench.tsx @@ -3,13 +3,15 @@ import { useCallback, useEffect, useMemo, useState, type ReactNode } from "react" import { BotMessageSquareIcon, + BookOpenIcon, GitBranchIcon, HistoryIcon, + MessageSquareTextIcon, PlugIcon, - RotateCcwIcon, SaveIcon, SettingsIcon, Trash2Icon, + UserRoundCheckIcon, } from "lucide-react" import { toast } from "sonner" @@ -37,34 +39,24 @@ import { Textarea } from "@/components/ui/textarea" import { createAIAgent, fetchAIAgent, - fetchAIAgentRevisions, + fetchAIAgentRevisions, fetchAIConfigsAll, - fetchKnowledgeBasesAll, - fetchAIWorkflowDefaultDefinition, - fetchAIWorkflowNodeSpecs, - fetchAIWorkflowTemplates, - fetchAIWorkflowVersions, - fetchAIWorkflows, + fetchAIWorkflows, fetchAgentTeamsAll, + fetchKnowledgeBasesAll, fetchMCPCatalog, fetchSkillDefinitionsAll, - publishAIAgent, - rollbackAIAgent, - rollbackAIAgentRollout, + publishAIAgent, + rollbackAIAgent, updateAIAgent, - validateAIWorkflow, type AIAgent, - type AIAgentWorkflowBindingInput, - type AgentRevision, + type AIAgentWorkflowBindingInput, + type AgentRevision, type AIConfig, - type AIWorkflowDefinition, - type AIWorkflow, - type AIWorkflowNodeSpec, - type AIWorkflowTemplate, - type AIWorkflowVersion, + type AIWorkflow, type AdminAgentTeam, type CreateAIAgentPayload, - type KnowledgeBase, + type KnowledgeBase, type MCPToolCatalogItem, type MCPToolSourceType, type SkillDefinition, @@ -76,8 +68,10 @@ import { IMConversationServiceMode, Status, } from "@/lib/generated/enums" -import { WorkflowEditor } from "../../ai-workflows/_components/workflow-editor" +import { cn } from "@/lib/utils" +type RuntimeMode = "workflow" | "autonomous" | "hybrid" +type SectionKey = "setup" | "persona" | "capability" | "service" type DirectToolItem = CreateAIAgentPayload["directTools"][number] type DirectToolOption = { @@ -88,29 +82,27 @@ type DirectToolOption = { groupLabel: string } -type SectionKey = - | "basic" - | "capabilities" - | "workflow" - -const fallbackDefinition: AIWorkflowDefinition = { - schemaVersion: 2, - nodes: [ - { - id: "start_1", - type: "start", - meta: { position: { x: 0, y: 80 } }, - data: { title: "开始", config: {}, inputsValues: {} }, - }, - { - id: "end_1", - type: "end", - meta: { position: { x: 260, y: 80 } }, - data: { title: "结束", config: {}, inputsValues: {} }, - }, - ], - edges: [{ sourceNodeID: "start_1", targetNodeID: "end_1", sourcePortID: "edge_start_end" }], -} +const runtimeModes: { + value: RuntimeMode + title: string + description: string +}[] = [ + { + value: "autonomous", + title: "自主接待", + description: "自主选择知识和工具处理请求,工作流可选。", + }, + { + value: "hybrid", + title: "自主接待 + 工作流", + description: "自主处理咨询,按需调用一个或多个工作流。", + }, + { + value: "workflow", + title: "仅工作流", + description: "严格按一个已发布工作流处理会话。", + }, +] function toText(value: string | number | undefined | null) { if (value === undefined || value === null || value === 0) return "" @@ -129,159 +121,142 @@ export function AIAgentConfigWorkbench({ agentId, onAgentSaved, onAgentCreated, + onCancel, }: { agentId?: number | null onAgentSaved?: () => void onAgentCreated?: (agent: AIAgent) => void + onCancel?: () => void }) { const [currentAgentId, setCurrentAgentId] = useState(agentId ?? null) - const [activeSection, setActiveSection] = useState("basic") + const [activeSection, setActiveSection] = useState("setup") const [agent, setAgent] = useState(null) - const [workflowVersions, setWorkflowVersions] = useState([]) - const [agentRevisions, setAgentRevisions] = useState([]) - const [nodeSpecs, setNodeSpecs] = useState([]) + const [agentRevisions, setAgentRevisions] = useState([]) const [loading, setLoading] = useState(true) - const [savingAgent, setSavingAgent] = useState(false) - const [savingWorkflow, setSavingWorkflow] = useState(false) + const [saving, setSaving] = useState(false) const [versionDialogOpen, setVersionDialogOpen] = useState(false) const [name, setName] = useState("") const [description, setDescription] = useState("") const [aiConfigId, setAIConfigId] = useState("") - const [runtimeMode, setRuntimeMode] = useState<"workflow" | "autonomous" | "hybrid">("autonomous") + const [runtimeMode, setRuntimeMode] = useState("autonomous") const [serviceMode, setServiceMode] = useState(String(IMConversationServiceMode.AIFirst)) const [systemPrompt, setSystemPrompt] = useState("") const [welcomeMessage, setWelcomeMessage] = useState("") const [replyTimeoutSeconds, setReplyTimeoutSeconds] = useState("180") - const [rolloutPercent, setRolloutPercent] = useState("5") const [handoffMode, setHandoffMode] = useState(String(AIAgentHandoffMode.WaitPool)) const [fallbackMode, setFallbackMode] = useState(String(AIAgentFallbackMode.NoAnswer)) const [fallbackMessage, setFallbackMessage] = useState("") const [selectedTeamIds, setSelectedTeamIds] = useState([]) const [selectedSkillIds, setSelectedSkillIds] = useState([]) - const [selectedKnowledgeBaseIds, setSelectedKnowledgeBaseIds] = useState([]) + const [selectedKnowledgeBaseIds, setSelectedKnowledgeBaseIds] = useState([]) const [directTools, setDirectTools] = useState([]) - const [workflowBindings, setWorkflowBindings] = useState([]) - const [publishedWorkflows, setPublishedWorkflows] = useState([]) - const [workflowToAdd, setWorkflowToAdd] = useState("") - - const [definition, setDefinition] = useState(fallbackDefinition) - const [workflowRevision, setWorkflowRevision] = useState(0) - const [workflowTemplates, setWorkflowTemplates] = useState([]) - const [selectedWorkflowTemplate, setSelectedWorkflowTemplate] = useState("") + const [workflowBindings, setWorkflowBindings] = useState([]) const [aiConfigs, setAIConfigs] = useState([]) const [agentTeams, setAgentTeams] = useState([]) const [skills, setSkills] = useState([]) - const [knowledgeBases, setKnowledgeBases] = useState([]) + const [knowledgeBases, setKnowledgeBases] = useState([]) const [toolCatalog, setToolCatalog] = useState([]) + const [publishedWorkflows, setPublishedWorkflows] = useState([]) + const [teamToAdd, setTeamToAdd] = useState("") const [skillToAdd, setSkillToAdd] = useState("") - const [knowledgeBaseToAdd, setKnowledgeBaseToAdd] = useState("") + const [knowledgeBaseToAdd, setKnowledgeBaseToAdd] = useState("") const [directToolGroupToAdd, setDirectToolGroupToAdd] = useState("") const [directToolToAdd, setDirectToolToAdd] = useState("") - const previousRolloutPercent = agent?.previousRolloutPercent ?? 0 + const [workflowToAdd, setWorkflowToAdd] = useState("") useEffect(() => { setCurrentAgentId(agentId ?? null) }, [agentId]) - const replaceWorkflowDefinition = useCallback((nextDefinition: AIWorkflowDefinition) => { - setDefinition(nextDefinition) - setWorkflowRevision((current) => current + 1) - }, []) - const loadData = useCallback(async () => { setLoading(true) try { - const [ - specs, - defaultDefinition, - templates, - configs, - teams, - skillList, - knowledgeBaseList, - catalog, - workflowPage, - ] = await Promise.all([ - fetchAIWorkflowNodeSpecs(), - fetchAIWorkflowDefaultDefinition().catch(() => fallbackDefinition), - fetchAIWorkflowTemplates(), - fetchAIConfigsAll({ modelType: AIModelType.LLM }), - fetchAgentTeamsAll(), - fetchSkillDefinitionsAll({ status: Status.Ok }), - fetchKnowledgeBasesAll({ status: Status.Ok }), - fetchMCPCatalog(), - fetchAIWorkflows({ limit: 100 }), - ]) + const [configs, teams, skillList, knowledgeBaseList, catalog, workflowPage] = + await Promise.all([ + fetchAIConfigsAll({ modelType: AIModelType.LLM }), + fetchAgentTeamsAll(), + fetchSkillDefinitionsAll({ status: Status.Ok }), + fetchKnowledgeBasesAll({ status: Status.Ok }), + fetchMCPCatalog(), + fetchAIWorkflows({ limit: 100 }), + ]) - setNodeSpecs(specs ?? []) - setWorkflowTemplates(templates ?? []) setAIConfigs(configs ?? []) setAgentTeams(teams ?? []) setSkills(skillList ?? []) - setKnowledgeBases(knowledgeBaseList ?? []) + setKnowledgeBases(knowledgeBaseList ?? []) setToolCatalog(catalog ?? []) - setPublishedWorkflows((workflowPage.results ?? []).filter((item) => item.publishedVersionId > 0)) + setPublishedWorkflows( + (workflowPage.results ?? []).filter((item) => item.publishedVersionId > 0), + ) if (!currentAgentId || currentAgentId <= 0) { setAgent(null) - setWorkflowVersions([]) - setAgentRevisions([]) + setAgentRevisions([]) setName("") setDescription("") - setAIConfigId("") - setRuntimeMode("autonomous") + setAIConfigId("") + setRuntimeMode("autonomous") setServiceMode(String(IMConversationServiceMode.AIFirst)) setSystemPrompt("") setWelcomeMessage("") setReplyTimeoutSeconds("180") - setRolloutPercent("5") setHandoffMode(String(AIAgentHandoffMode.WaitPool)) setFallbackMode(String(AIAgentFallbackMode.NoAnswer)) setFallbackMessage("") setSelectedTeamIds([]) setSelectedSkillIds([]) - setSelectedKnowledgeBaseIds([]) + setSelectedKnowledgeBaseIds([]) setDirectTools([]) - setWorkflowBindings([]) - replaceWorkflowDefinition(defaultDefinition ?? fallbackDefinition) + setWorkflowBindings([]) return } - const [agentDetail, revisionList] = await Promise.all([ + const [detail, revisions] = await Promise.all([ fetchAIAgent(currentAgentId), - fetchAIAgentRevisions(currentAgentId), + fetchAIAgentRevisions(currentAgentId), ]) - - setAgent(agentDetail) - setAgentRevisions(revisionList ?? []) - setWorkflowVersions([]) - setName(agentDetail.name) - setDescription(agentDetail.description || "") - setAIConfigId(toText(agentDetail.aiConfigId)) - setRuntimeMode(agentDetail.runtimeMode === "autonomous" || agentDetail.runtimeMode === "hybrid" ? agentDetail.runtimeMode : "workflow") - setServiceMode(String(agentDetail.serviceMode || IMConversationServiceMode.AIFirst)) - setSystemPrompt(agentDetail.systemPrompt || "") - setWelcomeMessage(agentDetail.welcomeMessage || "") - setReplyTimeoutSeconds(String(agentDetail.replyTimeoutSeconds ?? 180)) - setRolloutPercent(String(agentDetail.rolloutPercent || 100)) - setHandoffMode(String(agentDetail.handoffMode || AIAgentHandoffMode.WaitPool)) - setFallbackMode(String(agentDetail.fallbackMode || AIAgentFallbackMode.NoAnswer)) - setFallbackMessage(agentDetail.fallbackMessage || "") - setSelectedTeamIds((agentDetail.teams ?? []).map((team) => team.id)) - setSelectedSkillIds(agentDetail.skillIds ?? []) - setSelectedKnowledgeBaseIds(agentDetail.knowledgeBaseIds ?? []) - setDirectTools(agentDetail.directTools ?? []) - setWorkflowBindings((agentDetail.workflowBindings ?? []).map(({ workflowVersionId, toolName, triggerInstruction, priority, enabled }) => ({ workflowVersionId, toolName, triggerInstruction, priority, enabled }))) - replaceWorkflowDefinition(defaultDefinition ?? fallbackDefinition) + setAgent(detail) + setAgentRevisions(revisions ?? []) + setName(detail.name) + setDescription(detail.description || "") + setAIConfigId(toText(detail.aiConfigId)) + setRuntimeMode( + detail.runtimeMode === "autonomous" || detail.runtimeMode === "hybrid" + ? detail.runtimeMode + : "workflow", + ) + setServiceMode(String(detail.serviceMode || IMConversationServiceMode.AIFirst)) + setSystemPrompt(detail.systemPrompt || "") + setWelcomeMessage(detail.welcomeMessage || "") + setReplyTimeoutSeconds(String(detail.replyTimeoutSeconds ?? 180)) + setHandoffMode(String(detail.handoffMode || AIAgentHandoffMode.WaitPool)) + setFallbackMode(String(detail.fallbackMode || AIAgentFallbackMode.NoAnswer)) + setFallbackMessage(detail.fallbackMessage || "") + setSelectedTeamIds((detail.teams ?? []).map((team) => team.id)) + setSelectedSkillIds(detail.skillIds ?? []) + setSelectedKnowledgeBaseIds(detail.knowledgeBaseIds ?? []) + setDirectTools(detail.directTools ?? []) + setWorkflowBindings( + (detail.workflowBindings ?? []).map( + ({ workflowVersionId, toolName, triggerInstruction, priority, enabled }) => ({ + workflowVersionId, + toolName, + triggerInstruction, + priority, + enabled, + }), + ), + ) } catch (error) { - toast.error(error instanceof Error ? error.message : "Failed to load Agent config") + toast.error(error instanceof Error ? error.message : "加载 Agent 配置失败") } finally { setLoading(false) } - }, [currentAgentId, replaceWorkflowDefinition]) + }, [currentAgentId]) useEffect(() => { void loadData() @@ -293,55 +268,59 @@ export function AIAgentConfigWorkbench({ { value: String(IMConversationServiceMode.HumanOnly), label: "仅人工" }, { value: String(IMConversationServiceMode.AIFirst), label: "AI 优先" }, ], - [] + [], ) - const runtimeModeOptions = useMemo( - () => [ - { value: "autonomous", label: "自主接待" }, - { value: "hybrid", label: "自主接待 + 工作流" }, - { value: "workflow", label: "仅工作流" }, - ], - [] - ) const handoffModeOptions = useMemo( () => [ { value: String(AIAgentHandoffMode.WaitPool), label: "进入待接入池" }, - { value: String(AIAgentHandoffMode.DefaultTeamPool), label: "进入默认客服组待接入池" }, - { value: String(AIAgentHandoffMode.AIHoldAndNotify), label: "AI继续接待并提醒人工" }, + { + value: String(AIAgentHandoffMode.DefaultTeamPool), + label: "进入默认客服组待接入池", + }, + { + value: String(AIAgentHandoffMode.AIHoldAndNotify), + label: "AI继续接待并提醒人工", + }, ], - [] + [], ) const fallbackModeOptions = useMemo( () => [ { value: String(AIAgentFallbackMode.NoAnswer), label: "直接说明知识不足" }, { value: String(AIAgentFallbackMode.SuggestRetry), label: "引导用户补充信息" }, - { value: String(AIAgentFallbackMode.Handoff), label: "转人工客服" }, + { value: String(AIAgentFallbackMode.Handoff), label: "转人工客服" }, ], - [] + [], ) const aiConfigOptions = useMemo( - () => aiConfigs.map((item) => ({ value: String(item.id), label: `${item.name} · ${item.modelName}` })), - [aiConfigs] + () => + aiConfigs.map((item) => ({ + value: String(item.id), + label: `${item.name} · ${item.modelName}`, + })), + [aiConfigs], ) const teamOptions = useMemo( () => agentTeams.map((item) => ({ value: String(item.id), label: item.name })), - [agentTeams] + [agentTeams], ) const skillOptions = useMemo( () => skills.map((item) => ({ value: String(item.id), label: item.name })), - [skills] + [skills], + ) + const knowledgeBaseOptions = useMemo( + () => knowledgeBases.map((item) => ({ value: String(item.id), label: item.name })), + [knowledgeBases], ) - const knowledgeBaseOptions = useMemo( - () => knowledgeBases.map((item) => ({ value: String(item.id), label: item.name })), - [knowledgeBases] - ) const directToolOptions = useMemo( () => toolCatalog .filter( (tool) => !tool.autoInjected && - (tool.sourceType === "mcp" || tool.toolCode === "builtin/conversation_context" || tool.toolCode === "graph/prepare_ticket_draft") + (tool.sourceType === "mcp" || + tool.toolCode === "builtin/conversation_context" || + tool.toolCode === "graph/prepare_ticket_draft"), ) .map((tool) => ({ value: tool.toolCode, @@ -357,7 +336,7 @@ export function AIAgentConfigWorkbench({ arguments: undefined, }, })), - [toolCatalog] + [toolCatalog], ) const directToolGroupOptions = useMemo( () => @@ -366,25 +345,40 @@ export function AIAgentConfigWorkbench({ directToolOptions.map((option) => [ option.groupLabel, { value: option.groupLabel, label: option.groupLabel }, - ]) - ).values() + ]), + ).values(), ), - [directToolOptions] + [directToolOptions], ) const addableDirectToolOptions = useMemo( () => directToolOptions.filter( (option) => option.groupLabel === directToolGroupToAdd && - !directTools.some((tool) => tool.toolCode === option.value) + !directTools.some((tool) => tool.toolCode === option.value), ), - [directToolGroupToAdd, directToolOptions, directTools] + [directToolGroupToAdd, directToolOptions, directTools], + ) + const workflowOptions = useMemo( + () => + publishedWorkflows + .filter( + (workflow) => + !workflowBindings.some( + (binding) => binding.workflowVersionId === workflow.publishedVersionId, + ), + ) + .map((workflow) => ({ + value: String(workflow.publishedVersionId), + label: `${workflow.name} · 固定版本 #${workflow.publishedVersionId}`, + })), + [publishedWorkflows, workflowBindings], ) function selectedOptions(ids: number[], options: { value: string; label: string }[]) { return ids .map((id) => options.find((option) => Number(option.value) === id)) - .filter((option): option is { value: string; label: string } => !!option) + .filter((option): option is { value: string; label: string } => Boolean(option)) } function addSelected(value: string, current: number[], setNext: (ids: number[]) => void) { @@ -399,209 +393,241 @@ export function AIAgentConfigWorkbench({ setDirectTools((current) => current.some((tool) => tool.toolCode === option.meta.toolCode) ? current - : [...current, option.meta] + : [...current, option.meta], ) setDirectToolToAdd("") } - function addWorkflowBinding(value: string) { - const workflow = publishedWorkflows.find((item) => item.publishedVersionId === Number(value)) - if (!workflow || workflowBindings.some((item) => item.workflowVersionId === workflow.publishedVersionId)) return - setWorkflowBindings((current) => [...current, { workflowVersionId: workflow.publishedVersionId, toolName: workflow.name, triggerInstruction: "", priority: current.length + 1, enabled: true }]) - setWorkflowToAdd("") - } + function addWorkflowBinding(value: string) { + const workflow = publishedWorkflows.find( + (item) => item.publishedVersionId === Number(value), + ) + if (!workflow) return + if (runtimeMode === "workflow" && workflowBindings.length >= 1) { + toast.error("仅工作流模式只能关联一个已发布工作流") + return + } + setWorkflowBindings((current) => [ + ...current, + { + workflowVersionId: workflow.publishedVersionId, + toolName: workflow.name, + triggerInstruction: "", + priority: current.length + 1, + enabled: true, + }, + ]) + setWorkflowToAdd("") + } + + function validateForm() { + if (!name.trim()) { + setActiveSection("setup") + toast.error("请填写 Agent 名称") + return false + } + if (!Number(aiConfigId)) { + setActiveSection("setup") + toast.error("请选择 AI 配置") + return false + } + if (runtimeMode === "hybrid" && workflowBindings.length === 0) { + setActiveSection("capability") + toast.error("Hybrid 模式至少需要关联一个已发布工作流") + return false + } + if (runtimeMode === "workflow" && workflowBindings.length !== 1) { + setActiveSection("capability") + toast.error("仅工作流模式必须且只能关联一个已发布工作流") + return false + } + return true + } function buildPayload(): CreateAIAgentPayload { return { name: name.trim(), description: description.trim(), aiConfigId: Number(aiConfigId), - runtimeMode, + runtimeMode, serviceMode: Number(serviceMode), systemPrompt: systemPrompt.trim(), welcomeMessage: welcomeMessage.trim(), replyTimeoutSeconds: Number(replyTimeoutSeconds), - rolloutPercent: Number(rolloutPercent), + rolloutPercent: agent?.rolloutPercent || 100, teamIds: uniqueNumbers(selectedTeamIds), handoffMode: Number(handoffMode), fallbackMode: Number(fallbackMode), fallbackMessage: fallbackMessage.trim(), - knowledgeBaseIds: uniqueNumbers(selectedKnowledgeBaseIds), + knowledgeBaseIds: uniqueNumbers(selectedKnowledgeBaseIds), skillIds: uniqueNumbers(selectedSkillIds), - directTools, - workflowBindings, + directTools, + workflowBindings, } } async function saveAgentSettings() { - setSavingAgent(true) + if (!validateForm()) return + setSaving(true) try { const payload = buildPayload() if (agent) { await updateAIAgent({ id: agent.id, ...payload }) - toast.success("Agent config saved") + toast.success("Agent 配置已保存") await loadData() } else { const created = await createAIAgent(payload) setCurrentAgentId(created.id) setAgent(created) - toast.success("Agent created") + toast.success("Agent 已创建") onAgentCreated?.(created) } onAgentSaved?.() } catch (error) { - toast.error(error instanceof Error ? error.message : "Failed to save Agent config") + toast.error(error instanceof Error ? error.message : "保存 Agent 配置失败") } finally { - setSavingAgent(false) + setSaving(false) } } - async function publishAutonomousAgent() { - if (!agent || (runtimeMode !== "autonomous" && runtimeMode !== "hybrid")) return - setSavingAgent(true) - try { - await publishAIAgent(agent.id) - await loadData() - toast.success("Agent published") - } catch (error) { - toast.error(error instanceof Error ? error.message : "Failed to publish Autonomous Agent") - } finally { - setSavingAgent(false) - } - } + async function publishAgent() { + if (!agent || runtimeMode === "workflow") return + if (!validateForm()) return + setSaving(true) + try { + await publishAIAgent(agent.id) + await loadData() + toast.success("Agent 已发布") + onAgentSaved?.() + } catch (error) { + toast.error(error instanceof Error ? error.message : "发布 Agent 失败") + } finally { + setSaving(false) + } + } - async function rollbackAgentRevision(revisionId: number) { - if (!agent || revisionId <= 0 || revisionId === agent.publishedRevisionId) return - setSavingAgent(true) - try { - await rollbackAIAgent(agent.id, revisionId) - toast.success("已回滚到选中的 Agent 版本") - await loadData() - onAgentSaved?.() - } catch (error) { - toast.error(error instanceof Error ? error.message : "回滚 Agent 版本失败") - } finally { - setSavingAgent(false) - } - } + async function rollbackAgentRevision(revisionId: number) { + if (!agent || revisionId <= 0 || revisionId === agent.publishedRevisionId) return + setSaving(true) + try { + await rollbackAIAgent(agent.id, revisionId) + toast.success("已回滚到选中的 Agent 版本") + await loadData() + onAgentSaved?.() + } catch (error) { + toast.error(error instanceof Error ? error.message : "回滚 Agent 版本失败") + } finally { + setSaving(false) + } + } - async function rollbackAgentRollout() { - if (!agent || agent.previousRolloutPercent < 1) return - setSavingAgent(true) - try { - await rollbackAIAgentRollout(agent.id) - toast.success("已恢复上一次灰度比例") - await loadData() - onAgentSaved?.() - } catch (error) { - toast.error(error instanceof Error ? error.message : "恢复灰度比例失败") - } finally { - setSavingAgent(false) - } - } + const workflowPublished = isWorkflowPublished(agent) + const autonomousPublished = + runtimeMode === "autonomous" && (agent?.publishedRevisionId ?? 0) > 0 + const hybridPublished = + runtimeMode === "hybrid" && + workflowPublished && + (agent?.publishedRevisionId ?? 0) > 0 + const runtimePublished = + runtimeMode === "workflow" + ? workflowPublished + : runtimeMode === "hybrid" + ? hybridPublished + : autonomousPublished - const sections: { key: SectionKey; title: string; icon: ReactNode }[] = [ - { key: "basic", title: "基础信息", icon: }, - { key: "capabilities", title: "能力来源", icon: }, - { key: "workflow", title: "关联工作流", icon: }, + const sections: { + key: SectionKey + title: string + icon: ReactNode + }[] = [ + { key: "setup", title: "身份与运行", icon: }, + { key: "persona", title: "角色与回复", icon: }, + { key: "capability", title: "知识与能力", icon: }, + { key: "service", title: "服务与兜底", icon: }, ] - const selectedTeamOptions = selectedOptions(selectedTeamIds, teamOptions) - const selectedSkillOptions = selectedOptions(selectedSkillIds, skillOptions) - const workflowPublished = isWorkflowPublished(agent) - const autonomousPublished = runtimeMode === "autonomous" && (agent?.publishedRevisionId ?? 0) > 0 - const hybridPublished = runtimeMode === "hybrid" && workflowPublished && (agent?.publishedRevisionId ?? 0) > 0 - const runtimePublished = runtimeMode === "workflow" ? workflowPublished : runtimeMode === "hybrid" ? hybridPublished : autonomousPublished - const workflowStateText = - agent?.workflowStateText || (workflowPublished ? "已发布" : "未发布") + if (loading) { + return ( +
+ 加载中... +
+ ) + } return (
-
-
-
- +
+
+
+
-

{agent?.name ?? "新建 AI Agent"}

+

{agent?.name || "新建 AI Agent"}

{agent?.statusName ? {agent.statusName} : null} - {runtimeMode === "autonomous" ? (autonomousPublished ? "已发布" : "未发布") : runtimeMode === "hybrid" ? (hybridPublished ? "已发布" : "未发布") : workflowStateText} + {runtimePublished ? "已发布" : agent ? "未发布" : "尚未创建"} - {workflowPublished ? ( - 当前生效 #{agent?.workflowVersionId} - ) : null}
-
- {activeSection === "workflow" ? ( - null - ) : ( - <> - {agent && (runtimeMode === "autonomous" || runtimeMode === "hybrid") ? : null} - - - )} -
-
- -
- {agent && !runtimePublished ? ( -
- {runtimeMode === "autonomous" ? "未发布 Agent,AI 不会自动回复。保存配置后发布 Agent,再绑定渠道或启用自动回复。" : runtimeMode === "hybrid" ? "未发布 Hybrid Agent,AI 不会自动回复。保存配置后请关联已发布工作流,再发布 Agent。" : "未发布工作流,AI 不会自动回复。请先关联一个已发布工作流。"} -
+ {agent ? ( + ) : null} -
-
- {sections.map((section) => ( + + +
+
+ -
- {loading ? ( -
- 加载中... -
- ) : ( -
- {activeSection === "basic" ? ( - -
- +
+
+ {activeSection === "setup" ? ( +
+ +
+ setName(event.target.value)} /> @@ -612,14 +638,55 @@ export function AIAgentConfigWorkbench({ onChange={setServiceMode} /> + +