"use client"; import Link from "next/link"; import { useEffect, useMemo, useRef, useState } from "react"; import useSWR from "swr"; import { toast } from "sonner"; import { ArrowLeft, Image as ImageIcon, Loader2, Save, Sparkles, Upload, } from "lucide-react"; import { defaultAgentWorkflowUiConfig, normalizeAgentWorkflowUiConfig, } from "@/lib/agents/workflow-ui-config"; import { fetchUploadWithRetry } from "@/lib/client/upload-with-retry"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Switch } from "@/components/ui/switch"; import type { AgentWorkflowUiConfig } from "@/types/agent"; const fetcher = (url: string) => fetch(url, { cache: "no-store" }).then((res) => res.json()); export function AgentWorkflowSettingsClient() { const { data, isLoading, mutate } = useSWR<{ agentWorkflowUiConfig?: AgentWorkflowUiConfig; }>("/api/site-settings", fetcher); const [config, setConfig] = useState(defaultAgentWorkflowUiConfig); const [isSaving, setIsSaving] = useState(false); const [uploadingKey, setUploadingKey] = useState(null); const fileInputRefs = useRef>({}); useEffect(() => { if (!data) return; setConfig(normalizeAgentWorkflowUiConfig(data.agentWorkflowUiConfig)); }, [data]); const sortedSteps = useMemo( () => [...config.steps].sort((left, right) => left.sortOrder - right.sortOrder), [config.steps] ); const updateStep = ( stepKey: string, updater: (step: AgentWorkflowUiConfig["steps"][number]) => AgentWorkflowUiConfig["steps"][number] ) => { setConfig((prev) => ({ ...prev, steps: prev.steps.map((step) => (step.key === stepKey ? updater(step) : step)), })); }; const updateOption = ( stepKey: string, optionKey: string, updater: ( option: AgentWorkflowUiConfig["steps"][number]["options"][number] ) => AgentWorkflowUiConfig["steps"][number]["options"][number] ) => { updateStep(stepKey, (step) => ({ ...step, options: step.options.map((option) => option.key === optionKey ? updater(option) : option ), })); }; const handleUploadPreview = async (stepKey: string, optionKey: string, file: File) => { if (!file.type.startsWith("image/")) { toast.error("请上传图片文件"); return; } const refKey = `${stepKey}:${optionKey}`; try { setUploadingKey(refKey); const formData = new FormData(); formData.append("file", file); const response = await fetchUploadWithRetry(formData); const result = await response.json(); if (!response.ok || !result?.url) { throw new Error(result?.error || "上传失败"); } updateOption(stepKey, optionKey, (option) => ({ ...option, previewImageUrl: result.url, })); toast.success("预览图上传成功"); } catch (error) { toast.error(error instanceof Error ? error.message : "预览图上传失败"); } finally { setUploadingKey(null); } }; const handleSave = async () => { try { setIsSaving(true); const response = await fetch("/api/site-settings", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ agentWorkflowUiConfig: config, }), }); if (!response.ok) { throw new Error("保存失败"); } toast.success("智能体工作流配置已保存"); await mutate(); } catch (error) { toast.error(error instanceof Error ? error.message : "保存失败"); } finally { setIsSaving(false); } }; return (
智能体配置中心

智能体工作流配置

单独维护智能体页面左侧每个环节的功能按钮名称、说明、Prompt 片段和预览图。

功能说明 这里配置的是智能体工作流页面的功能卡片。网站设置页只保留入口,不再承载这些详细配置。 {isLoading ? ( ) : (
基础下拉配置 工作流左侧“品牌 / 店铺”采用后台配置的候选列表,前台支持输入关键字匹配下拉选择。