refactor: use config workbench for AI agent creation

This commit is contained in:
mlogclub
2026-06-22 12:03:48 +08:00
parent 32bb0dbc30
commit f0eb694803
4 changed files with 84 additions and 1337 deletions
@@ -26,6 +26,7 @@ import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea" import { Textarea } from "@/components/ui/textarea"
import { import {
createAIAgent,
fetchAIAgent, fetchAIAgent,
fetchAIAgentWorkflow, fetchAIAgentWorkflow,
fetchAIConfigsAll, fetchAIConfigsAll,
@@ -114,10 +115,13 @@ function uniqueNumbers(input: number[]) {
export function AIAgentConfigWorkbench({ export function AIAgentConfigWorkbench({
agentId, agentId,
onAgentSaved, onAgentSaved,
onAgentCreated,
}: { }: {
agentId: number agentId?: number | null
onAgentSaved?: () => void onAgentSaved?: () => void
onAgentCreated?: (agent: AIAgent) => void
}) { }) {
const [currentAgentId, setCurrentAgentId] = useState(agentId ?? null)
const [activeSection, setActiveSection] = useState<SectionKey>("basic") const [activeSection, setActiveSection] = useState<SectionKey>("basic")
const [agent, setAgent] = useState<AIAgent | null>(null) const [agent, setAgent] = useState<AIAgent | null>(null)
const [workflow, setWorkflow] = useState<AIWorkflow | null>(null) const [workflow, setWorkflow] = useState<AIWorkflow | null>(null)
@@ -162,16 +166,14 @@ export function AIAgentConfigWorkbench({
[workflow?.id, workflow?.updatedAt] [workflow?.id, workflow?.updatedAt]
) )
useEffect(() => {
setCurrentAgentId(agentId ?? null)
}, [agentId])
const loadData = useCallback(async () => { const loadData = useCallback(async () => {
if (!Number.isFinite(agentId) || agentId <= 0) {
setLoading(false)
return
}
setLoading(true) setLoading(true)
try { try {
const [ const [
agentDetail,
workflowDetail,
specs, specs,
configs, configs,
bases, bases,
@@ -179,8 +181,6 @@ export function AIAgentConfigWorkbench({
skillList, skillList,
catalog, catalog,
] = await Promise.all([ ] = await Promise.all([
fetchAIAgent(agentId),
fetchAIAgentWorkflow(agentId),
fetchAIWorkflowNodeSpecs(), fetchAIWorkflowNodeSpecs(),
fetchAIConfigsAll({ modelType: AIModelType.LLM }), fetchAIConfigsAll({ modelType: AIModelType.LLM }),
fetchKnowledgeBasesAll({ status: Status.Ok }), fetchKnowledgeBasesAll({ status: Status.Ok }),
@@ -189,14 +189,44 @@ export function AIAgentConfigWorkbench({
fetchMCPCatalog(), fetchMCPCatalog(),
]) ])
setAgent(agentDetail)
setWorkflow(workflowDetail)
setNodeSpecs(specs ?? []) setNodeSpecs(specs ?? [])
setAIConfigs(configs ?? []) setAIConfigs(configs ?? [])
setKnowledgeBases(bases ?? []) setKnowledgeBases(bases ?? [])
setAgentTeams(teams ?? []) setAgentTeams(teams ?? [])
setSkills(skillList ?? []) setSkills(skillList ?? [])
setToolCatalog(catalog ?? []) setToolCatalog(catalog ?? [])
if (!currentAgentId || currentAgentId <= 0) {
setAgent(null)
setWorkflow(null)
setName("")
setDescription("")
setAIConfigId("")
setServiceMode(String(IMConversationServiceMode.AIFirst))
setSystemPrompt("")
setWelcomeMessage("")
setReplyTimeoutSeconds("180")
setHandoffMode(String(AIAgentHandoffMode.WaitPool))
setFallbackMode(String(AIAgentFallbackMode.NoAnswer))
setFallbackMessage("")
setSelectedKnowledgeIds([])
setSelectedTeamIds([])
setSelectedSkillIds([])
setDirectTools([])
setWorkflowName("新建 Agent 会话流程")
setWorkflowDescription("")
setDefinition(emptyDefinition)
setValidation(null)
return
}
const [agentDetail, workflowDetail] = await Promise.all([
fetchAIAgent(currentAgentId),
fetchAIAgentWorkflow(currentAgentId),
])
setAgent(agentDetail)
setWorkflow(workflowDetail)
setName(agentDetail.name) setName(agentDetail.name)
setDescription(agentDetail.description || "") setDescription(agentDetail.description || "")
setAIConfigId(toText(agentDetail.aiConfigId)) setAIConfigId(toText(agentDetail.aiConfigId))
@@ -220,7 +250,7 @@ export function AIAgentConfigWorkbench({
} finally { } finally {
setLoading(false) setLoading(false)
} }
}, [agentId]) }, [currentAgentId])
useEffect(() => { useEffect(() => {
void loadData() void loadData()
@@ -361,13 +391,20 @@ export function AIAgentConfigWorkbench({
} }
async function saveAgentSettings() { async function saveAgentSettings() {
if (!agent) return
setSavingAgent(true) setSavingAgent(true)
try { try {
const payload = buildPayload() const payload = buildPayload()
await updateAIAgent({ id: agent.id, ...payload }) if (agent) {
toast.success("Agent config saved") await updateAIAgent({ id: agent.id, ...payload })
await loadData() toast.success("Agent config saved")
await loadData()
} else {
const created = await createAIAgent(payload)
setCurrentAgentId(created.id)
setAgent(created)
toast.success("Agent created")
onAgentCreated?.(created)
}
onAgentSaved?.() onAgentSaved?.()
} catch (error) { } catch (error) {
toast.error(error instanceof Error ? error.message : "Failed to save Agent config") toast.error(error instanceof Error ? error.message : "Failed to save Agent config")
@@ -377,10 +414,11 @@ export function AIAgentConfigWorkbench({
} }
async function saveWorkflowDraft() { async function saveWorkflowDraft() {
if (!currentAgentId) return
setSavingWorkflow(true) setSavingWorkflow(true)
try { try {
const saved = await saveAIAgentWorkflow({ const saved = await saveAIAgentWorkflow({
agentId, agentId: currentAgentId,
name: workflowName, name: workflowName,
description: workflowDescription, description: workflowDescription,
definition, definition,
@@ -410,15 +448,16 @@ export function AIAgentConfigWorkbench({
} }
async function publishWorkflow() { async function publishWorkflow() {
if (!currentAgentId) return
setSavingWorkflow(true) setSavingWorkflow(true)
try { try {
await saveAIAgentWorkflow({ await saveAIAgentWorkflow({
agentId, agentId: currentAgentId,
name: workflowName, name: workflowName,
description: workflowDescription, description: workflowDescription,
definition, definition,
}) })
const version = await publishAIAgentWorkflow(agentId, definition) const version = await publishAIAgentWorkflow(currentAgentId, definition)
toast.success(`Published version ${version.version}`) toast.success(`Published version ${version.version}`)
await loadData() await loadData()
onAgentSaved?.() onAgentSaved?.()
@@ -452,7 +491,7 @@ export function AIAgentConfigWorkbench({
<BotMessageSquareIcon className="size-5" /> <BotMessageSquareIcon className="size-5" />
</div> </div>
<div className="min-w-0"> <div className="min-w-0">
<h1 className="truncate text-base font-semibold">{agent?.name ?? "AI Agent 配置"}</h1> <h1 className="truncate text-base font-semibold">{agent?.name ?? "新建 AI Agent"}</h1>
<div className="mt-1 flex items-center gap-2 text-sm text-muted-foreground"> <div className="mt-1 flex items-center gap-2 text-sm text-muted-foreground">
{agent?.statusName ? <Badge variant="secondary">{agent.statusName}</Badge> : null} {agent?.statusName ? <Badge variant="secondary">{agent.statusName}</Badge> : null}
{agent?.workflowVersionId ? <Badge></Badge> : <Badge variant="outline">稿</Badge>} {agent?.workflowVersionId ? <Badge></Badge> : <Badge variant="outline">稿</Badge>}
@@ -464,7 +503,7 @@ export function AIAgentConfigWorkbench({
<SaveIcon className="size-4" /> <SaveIcon className="size-4" />
</Button> </Button>
<Button disabled={savingWorkflow || loading} onClick={publishWorkflow}> <Button disabled={savingWorkflow || loading || !currentAgentId} onClick={publishWorkflow}>
<SendIcon className="size-4" /> <SendIcon className="size-4" />
</Button> </Button>
@@ -690,15 +729,15 @@ export function AIAgentConfigWorkbench({
{validation.valid ? "校验通过" : `${validation.errors.length} 个问题`} {validation.valid ? "校验通过" : `${validation.errors.length} 个问题`}
</Badge> </Badge>
) : null} ) : null}
<Button variant="outline" disabled={savingWorkflow} onClick={validateWorkflowDraft}> <Button variant="outline" disabled={savingWorkflow || !currentAgentId} onClick={validateWorkflowDraft}>
<CheckCircle2Icon className="size-4" /> <CheckCircle2Icon className="size-4" />
</Button> </Button>
<Button variant="outline" disabled={savingWorkflow} onClick={saveWorkflowDraft}> <Button variant="outline" disabled={savingWorkflow || !currentAgentId} onClick={saveWorkflowDraft}>
<SaveIcon className="size-4" /> <SaveIcon className="size-4" />
稿 稿
</Button> </Button>
<Button disabled={savingWorkflow} onClick={publishWorkflow}> <Button disabled={savingWorkflow || !currentAgentId} onClick={publishWorkflow}>
<SendIcon className="size-4" /> <SendIcon className="size-4" />
</Button> </Button>
File diff suppressed because it is too large Load Diff
+14 -13
View File
@@ -26,7 +26,6 @@ import {
import { IMConversationServiceMode, Status } from "@/lib/generated/enums"; import { IMConversationServiceMode, Status } from "@/lib/generated/enums";
import { useI18n } from "@/i18n/provider"; import { useI18n } from "@/i18n/provider";
import { AIAgentConfigWorkbench } from "./_components/config-workbench"; import { AIAgentConfigWorkbench } from "./_components/config-workbench";
import { EditDialog } from "./_components/edit";
type TFunction = (key: string, values?: Record<string, string | number>) => string; type TFunction = (key: string, values?: Record<string, string | number>) => string;
@@ -67,6 +66,7 @@ export default function DashboardAIAgentsPage() {
const t = useI18n(); const t = useI18n();
const statusOptions = useMemo(() => getStatusOptions(t), [t]); const statusOptions = useMemo(() => getStatusOptions(t), [t]);
const [configAgentId, setConfigAgentId] = useState<number | null>(null); const [configAgentId, setConfigAgentId] = useState<number | null>(null);
const [configOpen, setConfigOpen] = useState(false);
const [crudActions, setCrudActions] = useState<DashboardCrudActionState | null>(null); const [crudActions, setCrudActions] = useState<DashboardCrudActionState | null>(null);
const filters = useMemo<DashboardCrudFilter[]>( const filters = useMemo<DashboardCrudFilter[]>(
@@ -239,7 +239,14 @@ export default function DashboardAIAgentsPage() {
getItemId={(item) => item.id} getItemId={(item) => item.id}
createItem={createAIAgent} createItem={createAIAgent}
updateItem={(item, payload) => updateAIAgent({ id: item.id, ...payload })} updateItem={(item, payload) => updateAIAgent({ id: item.id, ...payload })}
onEditItem={(item) => setConfigAgentId(item.id)} onCreateItem={() => {
setConfigAgentId(null);
setConfigOpen(true);
}}
onEditItem={(item) => {
setConfigAgentId(item.id);
setConfigOpen(true);
}}
deleteItem={(item) => deleteAIAgent(item.id)} deleteItem={(item) => deleteAIAgent(item.id)}
rowActions={[ rowActions={[
{ {
@@ -248,6 +255,7 @@ export default function DashboardAIAgentsPage() {
label: t("aiAgent.configure"), label: t("aiAgent.configure"),
run: ({ item }) => { run: ({ item }) => {
setConfigAgentId(item.id); setConfigAgentId(item.id);
setConfigOpen(true);
}, },
}, },
createDashboardStatusToggleAction<AIAgent, number>({ createDashboardStatusToggleAction<AIAgent, number>({
@@ -275,15 +283,6 @@ export default function DashboardAIAgentsPage() {
errorMessage: t("aiAgent.sortUpdateFailed"), errorMessage: t("aiAgent.sortUpdateFailed"),
handleLabel: t("aiAgent.dragSort", { name: "" }), handleLabel: t("aiAgent.dragSort", { name: "" }),
}} }}
renderEditDialog={({ open, saving, itemId, onOpenChange, onSubmit }) => (
<EditDialog
open={open}
saving={saving}
itemId={itemId}
onOpenChange={onOpenChange}
onSubmit={onSubmit}
/>
)}
onActionStateChange={setCrudActions} onActionStateChange={setCrudActions}
labels={{ labels={{
refresh: t("aiAgent.refresh"), refresh: t("aiAgent.refresh"),
@@ -305,8 +304,9 @@ export default function DashboardAIAgentsPage() {
}} }}
/> />
<ProjectDialog <ProjectDialog
open={configAgentId !== null} open={configOpen}
onOpenChange={(open) => { onOpenChange={(open) => {
setConfigOpen(open);
if (!open) setConfigAgentId(null); if (!open) setConfigAgentId(null);
}} }}
title={t("aiAgent.configure")} title={t("aiAgent.configure")}
@@ -315,9 +315,10 @@ export default function DashboardAIAgentsPage() {
contentClassName="top-5 left-5 h-[calc(100vh-40px)] max-h-[calc(100vh-40px)] w-[calc(100vw-40px)] max-w-[calc(100vw-40px)] translate-x-0 translate-y-0 sm:max-w-[calc(100vw-40px)]" contentClassName="top-5 left-5 h-[calc(100vh-40px)] max-h-[calc(100vh-40px)] w-[calc(100vw-40px)] max-w-[calc(100vw-40px)] translate-x-0 translate-y-0 sm:max-w-[calc(100vw-40px)]"
headerClassName="sr-only" headerClassName="sr-only"
> >
{configAgentId ? ( {configOpen ? (
<AIAgentConfigWorkbench <AIAgentConfigWorkbench
agentId={configAgentId} agentId={configAgentId}
onAgentCreated={(agent) => setConfigAgentId(agent.id)}
onAgentSaved={() => crudActions?.onRefresh()} onAgentSaved={() => crudActions?.onRefresh()}
/> />
) : null} ) : null}
@@ -148,6 +148,7 @@ export type DashboardCrudPageProps<TItem, TPayload> = {
getItemId: (item: TItem) => number getItemId: (item: TItem) => number
createItem: (payload: TPayload) => Promise<unknown> createItem: (payload: TPayload) => Promise<unknown>
updateItem: (item: TItem, payload: TPayload) => Promise<unknown> updateItem: (item: TItem, payload: TPayload) => Promise<unknown>
onCreateItem?: () => void
canEdit?: (item: TItem) => boolean canEdit?: (item: TItem) => boolean
onEditItem?: (item: TItem) => void onEditItem?: (item: TItem) => void
deleteItem?: (item: TItem) => Promise<unknown> deleteItem?: (item: TItem) => Promise<unknown>
@@ -199,6 +200,7 @@ export function DashboardCrudPage<TItem, TPayload>({
getItemId, getItemId,
createItem, createItem,
updateItem, updateItem,
onCreateItem,
canEdit, canEdit,
onEditItem, onEditItem,
deleteItem, deleteItem,
@@ -255,9 +257,13 @@ export function DashboardCrudPage<TItem, TPayload>({
} }
const openCreateDialog = useCallback(() => { const openCreateDialog = useCallback(() => {
if (onCreateItem) {
onCreateItem()
return
}
setEditingItem(null) setEditingItem(null)
setDialogOpen(true) setDialogOpen(true)
}, []) }, [onCreateItem])
function openEditDialog(item: TItem) { function openEditDialog(item: TItem) {
setEditingItem(item) setEditingItem(item)