"use client"; import { BotMessageSquareIcon, GitBranchIcon, PowerIcon } from "lucide-react"; import { useMemo, useState } from "react"; import { DashboardCrudPage, type DashboardCrudActionState, createDashboardStatusColumn, createDashboardStatusToggleAction, type DashboardCrudColumn, type DashboardCrudFilter, } from "@/components/dashboard/crud"; import { ProjectDialog } from "@/components/project-dialog"; import { Badge } from "@/components/ui/badge"; import { createAIAgent, deleteAIAgent, fetchAIAgents, updateAIAgent, updateAIAgentSort, updateAIAgentStatus, type AIAgent, type CreateAIAgentPayload, } from "@/lib/api/admin"; import { IMConversationServiceMode, Status } from "@/lib/generated/enums"; import { useI18n } from "@/i18n/provider"; import { AIAgentConfigWorkbench } from "./_components/config-workbench"; type TFunction = (key: string, values?: Record) => string; function getStatusOptions(t: TFunction) { return [ { value: "all", label: t("aiAgent.allStatuses") }, { value: String(Status.Ok), label: t("aiAgent.enabled") }, { value: String(Status.Disabled), label: t("aiAgent.disabled") }, { value: String(Status.Deleted), label: t("status.deleted") }, ]; } function getStatusLabel(value: string, t: TFunction) { return ( getStatusOptions(t).find((item) => item.value === value)?.label ?? t("aiAgent.allStatuses") ); } function getServiceModeLabel(mode: number, t: TFunction) { switch (mode) { case IMConversationServiceMode.AIOnly: return t("aiAgent.serviceAiOnly"); case IMConversationServiceMode.HumanOnly: return t("aiAgent.serviceHumanOnly"); case IMConversationServiceMode.AIFirst: return t("aiAgent.serviceAiFirst"); default: return "-"; } } function getNextStatus(item: AIAgent) { return item.status === Status.Ok ? Status.Disabled : Status.Ok; } export default function DashboardAIAgentsPage() { const t = useI18n(); const statusOptions = useMemo(() => getStatusOptions(t), [t]); const [configAgentId, setConfigAgentId] = useState(null); const [configOpen, setConfigOpen] = useState(false); const [crudActions, setCrudActions] = useState(null); const filters = useMemo( () => [ { name: "name", label: t("aiAgent.filterName"), placeholder: t("aiAgent.filterName"), defaultValue: "", trim: true, className: "w-full sm:w-56", }, { name: "status", label: t("aiAgent.allStatuses"), type: "select", defaultValue: "all", allValue: "all", options: statusOptions, className: "w-full sm:w-52", }, ], [statusOptions, t], ); const columns = useMemo[]>( () => [ { key: "agent", label: "Agent", render: (item) => (
{item.name}
Agent ID: {item.id}
), }, { key: "aiConfig", label: t("aiAgent.columnAiConfig"), render: (item) => item.aiConfigName || "-", }, { key: "serviceMode", label: t("aiAgent.columnServiceMode"), render: (item) => getServiceModeLabel(item.serviceMode, t), }, { key: "publication", label: "发布状态", render: (item) => { const published = item.publishedRevisionId > 0; const workflowCount = item.workflowBindings?.length ?? 0; return (
{published ? "已发布" : "未发布"} {published ? ( Revision #{item.publishedRevisionId} ) : null}
{workflowCount > 0 ? `已配置 ${workflowCount} 个 Workflow` : "由 Agent 自主判断并直接回复"}
); }, }, { key: "skills", label: t("aiAgent.columnSkills"), render: (item) => { const skills = item.skills ?? []; return (
{skills.length === 0 ? ( {t("aiAgent.ragOnly")} ) : ( skills.map((skill) => ( {skill.name} )) )}
); }, }, { key: "capabilities", label: t("aiAgent.columnCapabilities"), render: (item) => { const skills = item.skills ?? []; const mcpTools = item.mcpTools ?? []; const mcpServerCodes = Array.from( new Set(mcpTools.map((tool) => tool.serverCode).filter(Boolean)), ); return (
{skills.length} Skills {mcpTools.length} MCP Tools
{mcpServerCodes.length === 0 ? ( {t("aiAgent.noMcpServer")} ) : ( mcpServerCodes.map((serverCode) => ( {serverCode} )) )}
); }, }, createDashboardStatusColumn({ label: t("aiAgent.columnStatus"), getStatus: (item) => item.status, getLabel: (status) => getStatusLabel(String(status), t), getBadgeVariant: (status) => status === Status.Ok ? "default" : "secondary", isEnabled: (status) => status === Status.Ok, toggle: { getNextStatus, updateStatus: (item, nextStatus) => updateAIAgentStatus(item.id, nextStatus), successMessage: (item, nextStatus) => t("aiAgent.statusChanged", { name: item.name, status: nextStatus === Status.Ok ? t("aiAgent.enabled") : t("aiAgent.stop"), }), errorMessage: t("aiAgent.statusUpdateFailed"), ariaLabel: (item) => t("aiAgent.toggleStatus", { name: item.name }), }, }), ], [t], ); return ( <> filters={filters} columns={columns} fetchList={(query) => fetchAIAgents({ name: typeof query.name === "string" ? query.name : undefined, status: typeof query.status === "string" ? query.status : undefined, page: Number(query.page), limit: Number(query.limit), }) } getItemId={(item) => item.id} createItem={createAIAgent} updateItem={(item, payload) => updateAIAgent({ id: item.id, ...payload })} onCreateItem={() => { setConfigAgentId(null); setConfigOpen(true); }} onEditItem={(item) => { setConfigAgentId(item.id); setConfigOpen(true); }} deleteItem={(item) => deleteAIAgent(item.id)} rowActions={[ { key: "workflow", icon: , label: t("aiAgent.configure"), run: ({ item }) => { setConfigAgentId(item.id); setConfigOpen(true); }, }, createDashboardStatusToggleAction({ icon: , label: (item) => item.status === Status.Ok ? t("aiAgent.stop") : t("aiAgent.enabled"), getNextStatus, updateStatus: (item, nextStatus) => updateAIAgentStatus(item.id, nextStatus), successMessage: (item, nextStatus) => t("aiAgent.statusChanged", { name: item.name, status: nextStatus === Status.Ok ? t("aiAgent.enabled") : t("aiAgent.stop"), }), errorMessage: t("aiAgent.statusUpdateFailed"), }), ]} sort={{ enabled: true, onReorder: (items) => updateAIAgentSort(items.map((item) => item.id)), successMessage: t("aiAgent.sortUpdated"), errorMessage: t("aiAgent.sortUpdateFailed"), handleLabel: t("aiAgent.dragSort", { name: "" }), }} onActionStateChange={setCrudActions} labels={{ refresh: t("aiAgent.refresh"), create: t("aiAgent.new"), query: t("aiAgent.query"), loading: t("aiAgent.loadingRows"), empty: t("aiAgent.emptyRows"), actions: t("aiAgent.columnActions"), edit: t("aiAgent.configure"), delete: t("aiAgent.delete"), processing: t("aiAgent.processing"), moreActions: (item) => t("aiAgent.moreActions", { name: item.name }), loadFailed: t("aiAgent.loadFailed"), saveFailed: t("aiAgent.saveFailed"), deleteFailed: t("aiAgent.deleteFailed"), created: (payload) => t("aiAgent.created", { name: payload.name }), updated: (_item, payload) => t("aiAgent.updated", { name: payload.name }), deleted: (item) => t("aiAgent.deleted", { name: item.name }), }} /> { setConfigOpen(open); if (!open) setConfigAgentId(null); }} title={t("aiAgent.configure")} size="xxl" allowFullscreen contentClassName="h-[800px] max-h-[calc(100vh-2rem)]" bodyScrollable={false} headerClassName="sr-only" closeOnEsc={false} > {configOpen ? ( setConfigAgentId(agent.id)} onAgentSaved={() => crudActions?.onRefresh()} onCancel={() => setConfigOpen(false)} /> ) : null} ); }