diff --git a/app/(admin)/settings/agent-workflow/agent-workflow-settings-client.tsx b/app/(admin)/settings/agent-workflow/agent-workflow-settings-client.tsx
index c6ebe646c..efc216c39 100644
--- a/app/(admin)/settings/agent-workflow/agent-workflow-settings-client.tsx
+++ b/app/(admin)/settings/agent-workflow/agent-workflow-settings-client.tsx
@@ -8,8 +8,10 @@ import {
ArrowLeft,
Image as ImageIcon,
Loader2,
+ Plus,
Save,
Sparkles,
+ Trash2,
Upload,
} from "lucide-react";
import {
@@ -34,11 +36,89 @@ import { Switch } from "@/components/ui/switch";
import type {
AgentWorkflowBrandModelConfig,
AgentWorkflowMaterialApiConfig,
+ AgentWorkflowOptionUiConfig,
+ AgentWorkflowStepUiConfig,
AgentWorkflowUiConfig,
} from "@/types/agent";
const fetcher = (url: string) => fetch(url, { cache: "no-store" }).then((res) => res.json());
+function normalizeConfigOrdering(config: AgentWorkflowUiConfig): AgentWorkflowUiConfig {
+ return {
+ ...config,
+ steps: config.steps.map((step, stepIndex) => ({
+ ...step,
+ sortOrder: stepIndex,
+ options: step.options.map((option, optionIndex) => ({
+ ...option,
+ sortOrder: optionIndex,
+ })),
+ })),
+ };
+}
+
+function createWorkflowSlug(value: string, fallback: string) {
+ const normalized = value
+ .trim()
+ .replace(/\s+/g, "_")
+ .replace(/[^\w-]/g, "_")
+ .replace(/_+/g, "_")
+ .replace(/^_+|_+$/g, "")
+ .toLowerCase();
+ return normalized || fallback;
+}
+
+function ensureUniqueWorkflowKey(baseKey: string, existingKeys: string[]) {
+ const normalizedBase = createWorkflowSlug(baseKey, "item");
+ const keySet = new Set(existingKeys);
+ if (!keySet.has(normalizedBase)) {
+ return normalizedBase;
+ }
+
+ let suffix = 2;
+ let candidate = `${normalizedBase}_${suffix}`;
+ while (keySet.has(candidate)) {
+ suffix += 1;
+ candidate = `${normalizedBase}_${suffix}`;
+ }
+ return candidate;
+}
+
+function createWorkflowOptionDraft(
+ stepKey: string,
+ optionIndex: number,
+ existingOptionKeys: string[]
+): AgentWorkflowOptionUiConfig {
+ const optionKey = ensureUniqueWorkflowKey(
+ `${stepKey}_option_${optionIndex + 1}`,
+ existingOptionKeys
+ );
+ return {
+ key: optionKey,
+ buttonName: `选项${optionIndex + 1}`,
+ description: "",
+ previewImageUrl: "",
+ enabled: true,
+ sortOrder: optionIndex,
+ promptFragment: `请按选项${optionIndex + 1}执行。`,
+ };
+}
+
+function createWorkflowStepDraft(
+ stepIndex: number,
+ existingStepKeys: string[]
+): AgentWorkflowStepUiConfig {
+ const stepKey = ensureUniqueWorkflowKey(`step_${stepIndex + 1}`, existingStepKeys);
+ return {
+ key: stepKey,
+ title: `新步骤${stepIndex + 1}`,
+ description: "",
+ enabled: true,
+ sortOrder: stepIndex,
+ options: [createWorkflowOptionDraft(stepKey, 0, [])],
+ };
+}
+
export function AgentWorkflowSettingsClient() {
const { data, isLoading, mutate } = useSWR<{
agentWorkflowUiConfig?: AgentWorkflowUiConfig;
@@ -89,10 +169,12 @@ export function AgentWorkflowSettingsClient() {
stepKey: string,
updater: (step: AgentWorkflowUiConfig["steps"][number]) => AgentWorkflowUiConfig["steps"][number]
) => {
- setConfig((prev) => ({
- ...prev,
- steps: prev.steps.map((step) => (step.key === stepKey ? updater(step) : step)),
- }));
+ setConfig((prev) =>
+ normalizeConfigOrdering({
+ ...prev,
+ steps: prev.steps.map((step) => (step.key === stepKey ? updater(step) : step)),
+ })
+ );
};
const updateOption = (
@@ -110,6 +192,44 @@ export function AgentWorkflowSettingsClient() {
}));
};
+ const handleAddStep = () => {
+ setConfig((prev) =>
+ normalizeConfigOrdering({
+ ...prev,
+ steps: [
+ ...prev.steps,
+ createWorkflowStepDraft(prev.steps.length, prev.steps.map((step) => step.key)),
+ ],
+ })
+ );
+ };
+
+ const handleRemoveStep = (stepKey: string) => {
+ setConfig((prev) =>
+ normalizeConfigOrdering({
+ ...prev,
+ steps: prev.steps.filter((step) => step.key !== stepKey),
+ })
+ );
+ };
+
+ const handleAddOption = (stepKey: string) => {
+ updateStep(stepKey, (step) => ({
+ ...step,
+ options: [
+ ...step.options,
+ createWorkflowOptionDraft(step.key, step.options.length, step.options.map((item) => item.key)),
+ ],
+ }));
+ };
+
+ const handleRemoveOption = (stepKey: string, optionKey: string) => {
+ updateStep(stepKey, (step) => ({
+ ...step,
+ options: step.options.filter((option) => option.key !== optionKey),
+ }));
+ };
+
const handleUploadPreview = async (stepKey: string, optionKey: string, file: File) => {
if (!file.type.startsWith("image/")) {
toast.error("请上传图片文件");
@@ -534,11 +654,27 @@ export function AgentWorkflowSettingsClient() {
- {sortedSteps.map((step) => (
+
+
+ {sortedSteps.length > 0 ? (
+ sortedSteps.map((step) => (
+
+
+ 步骤 Key:{step.key}
+
+
+ 新增步骤后系统会自动生成唯一 Key,用于前台保存和读取选择结果。
+
+
-
+
+
+
-
+ {step.options.length > 0 ? (
+
{step.options
.slice()
.sort((left, right) => left.sortOrder - right.sortOrder)
@@ -603,6 +754,16 @@ export function AgentWorkflowSettingsClient() {
+
+
+ ) : (
+
+ 当前步骤还没有选项,请先新增一个选项。
+
+ )}
- ))}
+ ))
+ ) : (
+
+
+ 还没有配置工作流步骤,点击上方“新增步骤”开始搭建第一步页面。
+
+
+ )}
)}
diff --git a/components/agents/AgentWorkflowPanel.tsx b/components/agents/AgentWorkflowPanel.tsx
index 3677f2736..fcfc0cdf3 100644
--- a/components/agents/AgentWorkflowPanel.tsx
+++ b/components/agents/AgentWorkflowPanel.tsx
@@ -89,7 +89,7 @@ export function AgentWorkflowPanel({
[config?.inputOptions?.stores]
);
const resolvedConfig = useMemo(() => {
- const source = config?.steps?.length ? config : defaultAgentWorkflowUiConfig;
+ const source = config || defaultAgentWorkflowUiConfig;
return {
steps: [...source.steps]
.filter((step) => step.enabled)
@@ -99,6 +99,7 @@ export function AgentWorkflowPanel({
.filter((option) => option.enabled)
.sort((left, right) => left.sortOrder - right.sortOrder),
}))
+ .filter((step) => step.options.length > 0)
.sort((left, right) => left.sortOrder - right.sortOrder),
};
}, [config]);
@@ -217,7 +218,8 @@ export function AgentWorkflowPanel({
- {resolvedConfig.steps.map((step) => (
+ {resolvedConfig.steps.length > 0 ? (
+ resolvedConfig.steps.map((step) => (
{step.title}
@@ -273,7 +275,12 @@ export function AgentWorkflowPanel({
})}
- ))}
+ ))
+ ) : (
+
+ 当前还没有启用的工作流步骤,请先到系统设置新增步骤或启用现有步骤。
+
+ )}
当前工作流摘要
@@ -367,7 +374,11 @@ export function AgentWorkflowPanel({
) : null}
-