Add @xyflow/react dependency and integrate workflow features in AssistantPage
Updated package.json and package-lock.json to include the @xyflow/react library. Enhanced the AssistantPage with a new AssistantWorkflowPage component for creating and managing workflows, allowing users to visually connect nodes. Removed the deprecated WorkflowPage component to streamline the codebase. Updated global styles to incorporate @xyflow/react styles for consistent UI.
This commit is contained in:
231
package-lock.json
generated
231
package-lock.json
generated
@@ -8,6 +8,7 @@
|
||||
"name": "ai-video-admin-frontend",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@xyflow/react": "^12.11.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^1.17.0",
|
||||
@@ -3848,6 +3849,55 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-color": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-drag": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
|
||||
"integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-interpolate": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
|
||||
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-color": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-selection": {
|
||||
"version": "3.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
|
||||
"integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-transition": {
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
|
||||
"integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-zoom": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
|
||||
"integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-interpolate": "*",
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
||||
@@ -4557,6 +4607,48 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@xyflow/react": {
|
||||
"version": "12.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.11.0.tgz",
|
||||
"integrity": "sha512-na4IO33FSs2OS72hASgZDmTYwFAkef7Z74uBUVrong3ARmQQHfnRUVaCFn1kTt5LbS6pK03TbYjCPGLjLFfziA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@xyflow/system": "0.0.77",
|
||||
"classcat": "^5.0.3",
|
||||
"zustand": "^4.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=17",
|
||||
"@types/react-dom": ">=17",
|
||||
"react": ">=17",
|
||||
"react-dom": ">=17"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@xyflow/system": {
|
||||
"version": "0.0.77",
|
||||
"resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.77.tgz",
|
||||
"integrity": "sha512-qCDCMCQAAgUu8yHnhloHG9F5mwPX5E+Wl8McpYIOPSSXfzFJJoZcwOcsDiAjitVKIg2de1WmJbCHfpcvxprsgg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-drag": "^3.0.7",
|
||||
"@types/d3-interpolate": "^3.0.4",
|
||||
"@types/d3-selection": "^3.0.10",
|
||||
"@types/d3-transition": "^3.0.8",
|
||||
"@types/d3-zoom": "^3.0.8",
|
||||
"d3-drag": "^3.0.0",
|
||||
"d3-interpolate": "^3.0.1",
|
||||
"d3-selection": "^3.0.0",
|
||||
"d3-zoom": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
||||
@@ -5173,6 +5265,12 @@
|
||||
"url": "https://polar.sh/cva"
|
||||
}
|
||||
},
|
||||
"node_modules/classcat": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
|
||||
"integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cli-cursor": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
|
||||
@@ -5420,6 +5518,111 @@
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-dispatch": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
|
||||
"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-drag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
|
||||
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-selection": "3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-ease": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
|
||||
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-interpolate": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-selection": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-timer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
|
||||
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-transition": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
|
||||
"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3",
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-ease": "1 - 3",
|
||||
"d3-interpolate": "1 - 3",
|
||||
"d3-timer": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"d3-selection": "2 - 3"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-zoom": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
|
||||
"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-drag": "2 - 3",
|
||||
"d3-interpolate": "1 - 3",
|
||||
"d3-selection": "2 - 3",
|
||||
"d3-transition": "2 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/damerau-levenshtein": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
||||
@@ -11646,6 +11849,34 @@
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/zustand": {
|
||||
"version": "4.5.7",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
|
||||
"integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"use-sync-external-store": "^1.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.7.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=16.8",
|
||||
"immer": ">=9.0.6",
|
||||
"react": ">=16.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"immer": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@xyflow/react": "^12.11.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^1.17.0",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
@import "shadcn/tailwind.css";
|
||||
@import "@xyflow/react/dist/style.css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import { ComponentsToolsPage } from "@/components/pages/ComponentsToolsPage";
|
||||
import { HistoryPage } from "@/components/pages/HistoryPage";
|
||||
import { DashboardPage } from "@/components/pages/DashboardPage";
|
||||
import { TestPage } from "@/components/pages/TestPage";
|
||||
import { WorkflowPage } from "@/components/pages/WorkflowPage";
|
||||
import { ProfilePage } from "@/components/pages/ProfilePage";
|
||||
|
||||
export type NavKey =
|
||||
@@ -24,7 +23,6 @@ export type NavKey =
|
||||
| "history"
|
||||
| "dashboard"
|
||||
| "test"
|
||||
| "workflow"
|
||||
| "profile";
|
||||
|
||||
|
||||
@@ -53,10 +51,9 @@ export function AppShell() {
|
||||
{active === "history" && <HistoryPage />}
|
||||
{active === "dashboard" && <DashboardPage />}
|
||||
{active === "test" && <TestPage />}
|
||||
{active === "workflow" && <WorkflowPage />}
|
||||
{active === "profile" && <ProfilePage />}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
Home,
|
||||
PlayCircle,
|
||||
Video,
|
||||
Workflow,
|
||||
} from "lucide-react";
|
||||
import type { NavKey } from "./AppShell";
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ import {
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { VoiceVisualizer } from "@/components/ui/voice-visualizer";
|
||||
import { AssistantWorkflowPage } from "@/components/pages/AssistantWorkflowPage";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -120,7 +121,7 @@ type AssistantTypeOption = {
|
||||
label: string;
|
||||
description: string;
|
||||
icon: React.ReactNode;
|
||||
/** 提示词、Dify、FastGPT 类型已落地,工作流暂时显示占位页 */
|
||||
/** 当前构建方式是否已经有可用的构建页面 */
|
||||
available: boolean;
|
||||
};
|
||||
|
||||
@@ -137,7 +138,7 @@ const assistantTypeOptions: AssistantTypeOption[] = [
|
||||
label: "使用工作流构建",
|
||||
description: "用可视化编排串联多个节点,适合多步骤、带分支的复杂流程。",
|
||||
icon: <Workflow size={20} />,
|
||||
available: false,
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
type: "Dify",
|
||||
@@ -303,6 +304,7 @@ export function AssistantPage() {
|
||||
| "list"
|
||||
| "choose"
|
||||
| "create"
|
||||
| "create-workflow"
|
||||
| "create-dify"
|
||||
| "create-fastgpt"
|
||||
| "create-opencode"
|
||||
@@ -338,10 +340,9 @@ export function AssistantPage() {
|
||||
updateOpenCodeForm("name", assistant.name);
|
||||
setView("create-opencode");
|
||||
} else {
|
||||
// 工作流:暂时显示占位页
|
||||
setDraftName(assistant.name);
|
||||
setDraftType(assistant.type);
|
||||
setView("placeholder");
|
||||
setView("create-workflow");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,8 +367,9 @@ export function AssistantPage() {
|
||||
// OpenCode 类型:进入 OpenCode 构建表单,并把已填的名称带过去
|
||||
updateOpenCodeForm("name", draftName.trim());
|
||||
setView("create-opencode");
|
||||
} else if (draftType === "工作流") {
|
||||
setView("create-workflow");
|
||||
} else {
|
||||
// 工作流:暂时显示占位页
|
||||
setView("placeholder");
|
||||
}
|
||||
}
|
||||
@@ -863,6 +865,15 @@ export function AssistantPage() {
|
||||
);
|
||||
}
|
||||
|
||||
if (view === "create-workflow") {
|
||||
return (
|
||||
<AssistantWorkflowPage
|
||||
workflowName={draftName.trim() || "未命名工作流助手"}
|
||||
onBack={() => setView("list")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (view === "create-dify") {
|
||||
return (
|
||||
<div className="-mt-6 flex h-full flex-col gap-4">
|
||||
|
||||
@@ -1,137 +1,144 @@
|
||||
import { ArrowRight, Boxes, GitBranch, Plus, Workflow } from "lucide-react";
|
||||
"use client";
|
||||
|
||||
import { useCallback } from "react";
|
||||
import {
|
||||
addEdge,
|
||||
Background,
|
||||
Connection,
|
||||
Controls,
|
||||
Edge,
|
||||
MiniMap,
|
||||
Node,
|
||||
ReactFlow,
|
||||
useEdgesState,
|
||||
useNodesState,
|
||||
} from "@xyflow/react";
|
||||
import { ChevronLeft, Plus, Save } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export function AssistantWorkflowPage() {
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
id: "start",
|
||||
type: "input",
|
||||
position: { x: 80, y: 180 },
|
||||
data: { label: "开始" },
|
||||
},
|
||||
{
|
||||
id: "answer",
|
||||
position: { x: 340, y: 180 },
|
||||
data: { label: "模型回答" },
|
||||
},
|
||||
{
|
||||
id: "end",
|
||||
type: "output",
|
||||
position: { x: 600, y: 180 },
|
||||
data: { label: "结束" },
|
||||
},
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
{
|
||||
id: "start-answer",
|
||||
source: "start",
|
||||
target: "answer",
|
||||
},
|
||||
{
|
||||
id: "answer-end",
|
||||
source: "answer",
|
||||
target: "end",
|
||||
},
|
||||
];
|
||||
|
||||
type AssistantWorkflowPageProps = {
|
||||
workflowName?: string;
|
||||
onBack?: () => void;
|
||||
};
|
||||
|
||||
export function AssistantWorkflowPage({
|
||||
workflowName = "工作流编辑器",
|
||||
onBack,
|
||||
}: AssistantWorkflowPageProps = {}) {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
|
||||
const handleConnect = useCallback(
|
||||
(connection: Connection) => {
|
||||
setEdges((currentEdges) => addEdge(connection, currentEdges));
|
||||
},
|
||||
[setEdges],
|
||||
);
|
||||
|
||||
const handleAddNode = () => {
|
||||
const id = crypto.randomUUID();
|
||||
|
||||
setNodes((currentNodes) => [
|
||||
...currentNodes,
|
||||
{
|
||||
id,
|
||||
position: {
|
||||
x: 200 + currentNodes.length * 40,
|
||||
y: 100 + currentNodes.length * 40,
|
||||
},
|
||||
data: {
|
||||
label: `新节点 ${currentNodes.length + 1}`,
|
||||
},
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
const workflow = { nodes, edges };
|
||||
|
||||
localStorage.setItem("assistant-workflow", JSON.stringify(workflow));
|
||||
|
||||
console.log("Saved workflow:", workflow);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-[1180px] flex-col gap-8">
|
||||
<div>
|
||||
<h1 className="font-display display-lg text-ink">创建助手</h1>
|
||||
<p className="mt-3 max-w-2xl text-[15px] leading-7 text-muted-foreground">
|
||||
通过节点编排方式创建复杂 AI
|
||||
视频助手流程,适合多轮任务、工具调用、知识库检索和人工协同场景。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section className="relative overflow-hidden rounded-3xl border border-hairline bg-canvas-soft p-10">
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute -right-20 -top-24 h-72 w-72 rounded-full opacity-55 blur-3xl"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"radial-gradient(circle, color-mix(in srgb, var(--gradient-mint) 55%, transparent), transparent 70%)",
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="relative flex items-start gap-5">
|
||||
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-surface-strong text-foreground">
|
||||
<Workflow size={30} />
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<div className="caption-label inline-flex rounded-full bg-surface-strong px-3 py-1 text-muted-foreground">
|
||||
即将开放
|
||||
</div>
|
||||
|
||||
<h2 className="font-display display-sm mt-5 text-ink">
|
||||
工作流画布正在设计中
|
||||
</h2>
|
||||
|
||||
<p className="mt-3 max-w-2xl text-[15px] leading-7 text-body">
|
||||
后续这里会提供可视化节点画布,你可以拖拽配置开始节点、意图识别节点、知识库检索节点、
|
||||
大模型回答节点、工具调用节点、人工接管节点和结束节点。
|
||||
</p>
|
||||
|
||||
<div className="mt-7 flex gap-3">
|
||||
<Button size="lg" className="gap-2">
|
||||
<Plus size={16} />
|
||||
新建工作流
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="gap-2 border-hairline-strong text-foreground hover:bg-surface-strong"
|
||||
>
|
||||
查看模板
|
||||
<ArrowRight size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="grid grid-cols-3 gap-4">
|
||||
<FeatureCard
|
||||
icon={<GitBranch size={20} />}
|
||||
title="节点编排"
|
||||
description="通过拖拽节点组织多轮对话、判断分支和任务流转。"
|
||||
/>
|
||||
|
||||
<FeatureCard
|
||||
icon={<Boxes size={20} />}
|
||||
title="组件复用"
|
||||
description="复用模型、知识库、语音识别、声音资源和工具插件。"
|
||||
/>
|
||||
|
||||
<FeatureCard
|
||||
icon={<Workflow size={20} />}
|
||||
title="流程调试"
|
||||
description="支持逐节点测试、查看输入输出和定位失败原因。"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section className="rounded-2xl border border-hairline bg-card p-6 shadow-sm">
|
||||
<div className="mb-5">
|
||||
<h2 className="font-display display-sm text-ink">未来画布结构预览</h2>
|
||||
<div className="flex h-[calc(100vh-160px)] min-h-[600px] flex-col gap-4">
|
||||
<header className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="font-display display-lg text-ink">{workflowName}</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
当前是静态占位,后续可替换为 React Flow 或自研画布组件。
|
||||
拖动节点,并从节点连接点拖出连线。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 overflow-x-auto rounded-2xl border border-hairline bg-canvas-soft p-5">
|
||||
{["开始", "意图识别", "知识库检索", "模型回答", "工具调用", "结束"].map(
|
||||
(item, index) => (
|
||||
<div key={item} className="flex items-center gap-3">
|
||||
<div className="min-w-[128px] rounded-xl border border-hairline bg-card p-4 text-center shadow-sm">
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
{item}
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-muted-soft">
|
||||
Node {index + 1}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
{onBack ? (
|
||||
<Button variant="outline" onClick={onBack}>
|
||||
<ChevronLeft size={16} />
|
||||
返回列表
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{index < 5 && (
|
||||
<ArrowRight size={18} className="shrink-0 text-muted-soft" />
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
<Button variant="outline" onClick={handleAddNode}>
|
||||
<Plus size={16} />
|
||||
添加节点
|
||||
</Button>
|
||||
|
||||
<Button onClick={handleSave}>
|
||||
<Save size={16} />
|
||||
保存工作流
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</header>
|
||||
|
||||
function FeatureCard({
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
}: {
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="rounded-2xl border border-hairline bg-card p-6 shadow-sm transition-shadow hover:shadow-md">
|
||||
<div className="mb-4 flex h-10 w-10 items-center justify-center rounded-full bg-surface-strong text-foreground">
|
||||
{icon}
|
||||
<div className="min-h-0 flex-1 overflow-hidden rounded-2xl border border-hairline bg-card">
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={handleConnect}
|
||||
fitView
|
||||
>
|
||||
<Background />
|
||||
<Controls />
|
||||
<MiniMap />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
|
||||
<div className="font-medium text-foreground">{title}</div>
|
||||
<p className="mt-2 text-sm leading-6 text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { PlaceholderPage } from "./PlaceholderPage";
|
||||
|
||||
export function WorkflowPage() {
|
||||
return (
|
||||
<PlaceholderPage
|
||||
title="工作流"
|
||||
description="管理与编排可复用的助手工作流,支持多轮任务与工具调用。"
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user