diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx index d8394a10c..4fc1c830c 100644 --- a/app/(admin)/settings/admin-settings-client.tsx +++ b/app/(admin)/settings/admin-settings-client.tsx @@ -95,11 +95,16 @@ import { normalizeCanvasSkillTags, type CanvasSkillTag, } from "@/lib/canvas-skill-tags"; +import { + defaultAgentWorkflowUiConfig, + normalizeAgentWorkflowUiConfig, +} from "@/lib/agents/workflow-ui-config"; import { CANVAS_SKILL_ICON_OPTIONS, getCanvasSkillIconComponent, } from "@/components/canvas/canvas-skill-icons"; import { fetchUploadWithRetry } from "@/lib/client/upload-with-retry"; +import type { AgentWorkflowUiConfig } from "@/types/agent"; const fetcher = (url: string) => fetch(url).then((res) => res.json()); const DEFAULT_BFL_REALTIME_MODEL = "flux-2-klein-4b"; @@ -241,6 +246,7 @@ type SiteSettings = { video: string[]; text: string[]; }; + agentWorkflowUiConfig: AgentWorkflowUiConfig; realtimeCanvasEnabled: boolean; aiDetailGenerationEnabled: boolean; canvasDefaultModelId: string | null; @@ -642,6 +648,7 @@ export function AdminSettingsClient() { video: [], text: [], }, + agentWorkflowUiConfig: defaultAgentWorkflowUiConfig, realtimeCanvasEnabled: true, aiDetailGenerationEnabled: false, canvasDefaultModelId: null, @@ -739,6 +746,9 @@ export function AdminSettingsClient() { ? (siteSettingsData as any).agentSecondaryTypeConfig.text : [], }, + agentWorkflowUiConfig: normalizeAgentWorkflowUiConfig( + (siteSettingsData as any).agentWorkflowUiConfig + ), realtimeCanvasEnabled: (siteSettingsData as any).realtimeCanvasEnabled ?? true, aiDetailGenerationEnabled: siteSettingsData.aiDetailGenerationEnabled ?? false, canvasDefaultModelId: siteSettingsData.canvasDefaultModelId || null, @@ -902,6 +912,9 @@ export function AdminSettingsClient() { maxBuffering: 2, maxInputResolution: 1024, jpegQuality: 0.55, + previewMegapixels: 0.4, + detailMegapixels: 1.0, + outputJpegQuality: 62, steps: 3, scheduleMu: 2.3, feedback: 0.95, @@ -1070,6 +1083,9 @@ export function AdminSettingsClient() { maxBuffering?: number; maxInputResolution?: number; jpegQuality?: number; + previewMegapixels?: number; + detailMegapixels?: number; + outputJpegQuality?: number; defaults?: { steps?: number; scheduleMu?: number; @@ -1099,6 +1115,9 @@ export function AdminSettingsClient() { maxBuffering: Number(config.maxBuffering || 2), maxInputResolution: Number(config.maxInputResolution || 1024), jpegQuality: Number(config.jpegQuality || 0.55), + previewMegapixels: Number(config.previewMegapixels || 0.4), + detailMegapixels: Number(config.detailMegapixels || 1.0), + outputJpegQuality: Number(config.outputJpegQuality || 62), steps: Number(config.defaults?.steps || 3), scheduleMu: Number(config.defaults?.scheduleMu || 2.3), feedback: Number(config.defaults?.feedback || 0.95), @@ -1474,6 +1493,7 @@ export function AdminSettingsClient() { const [editingModel, setEditingModel] = useState(null); const [modelAvatarUploading, setModelAvatarUploading] = useState(false); const [contactQrUploading, setContactQrUploading] = useState(false); + const [workflowOptionUploadingKey, setWorkflowOptionUploadingKey] = useState(null); // 🆕 火山引擎视频多模型表单(动态从API加载) const [volcanoVideoModels, setVolcanoVideoModels] = useState(null); + const updateWorkflowStep = ( + stepKey: string, + updater: (step: AgentWorkflowUiConfig["steps"][number]) => AgentWorkflowUiConfig["steps"][number] + ) => { + setSiteSettings((prev) => ({ + ...prev, + agentWorkflowUiConfig: { + steps: prev.agentWorkflowUiConfig.steps.map((step) => + step.key === stepKey ? updater(step) : step + ), + }, + })); + }; + + const updateWorkflowOption = ( + stepKey: string, + optionKey: string, + updater: ( + option: AgentWorkflowUiConfig["steps"][number]["options"][number] + ) => AgentWorkflowUiConfig["steps"][number]["options"][number] + ) => { + updateWorkflowStep(stepKey, (step) => ({ + ...step, + options: step.options.map((option) => + option.key === optionKey ? updater(option) : option + ), + })); + }; + + const handleWorkflowOptionPreviewUpload = async ( + stepKey: string, + optionKey: string, + file: File + ) => { + if (!file.type.startsWith("image/")) { + toast.error("请上传图片文件"); + return; + } + + const uploadKey = `${stepKey}:${optionKey}`; + try { + setWorkflowOptionUploadingKey(uploadKey); + 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 || "上传失败"); + } + + updateWorkflowOption(stepKey, optionKey, (option) => ({ + ...option, + previewImageUrl: result.url, + })); + toast.success("功能卡片预览图上传成功"); + } catch (error: any) { + toast.error(error?.message || "功能卡片预览图上传失败"); + } finally { + setWorkflowOptionUploadingKey(null); + } + }; + // --- 用户管理逻辑 --- const handleUserSearch = (search: string) => { setUserSearch(search); @@ -2203,6 +2287,18 @@ export function AdminSettingsClient() { Math.max(512, Number(realtimeCanvasConfig.maxInputResolution) || 1024) ), jpegQuality: Math.min(0.82, Math.max(0.35, Number(realtimeCanvasConfig.jpegQuality) || 0.55)), + previewMegapixels: Math.min( + 0.8, + Math.max(0.2, Number(realtimeCanvasConfig.previewMegapixels) || 0.4) + ), + detailMegapixels: Math.min( + 1.2, + Math.max(0.6, Number(realtimeCanvasConfig.detailMegapixels) || 1.0) + ), + outputJpegQuality: Math.min( + 90, + Math.max(35, Number(realtimeCanvasConfig.outputJpegQuality) || 62) + ), defaults: { steps: Math.min(6, Math.max(1, Number(realtimeCanvasConfig.steps) || 3)), scheduleMu: Math.min(3.5, Math.max(1.5, Number(realtimeCanvasConfig.scheduleMu) || 2.3)), @@ -5528,6 +5624,215 @@ export function AdminSettingsClient() { )} + {siteSettings.agentEnabled && ( +
+
+
工作流功能卡片配置
+
+ 智能体工作流左侧每个环节的按钮名称、说明文案和预览图都从这里读取,前端不再写死。 +
+
+ +
+ {siteSettings.agentWorkflowUiConfig.steps.map((step) => ( +
+
+
+ + + updateWorkflowStep(step.key, (current) => ({ + ...current, + title: event.target.value, + })) + } + placeholder="步骤标题" + /> +