Frontend start to use backend CRUD api

This commit is contained in:
Xin Wang
2026-02-08 15:08:18 +08:00
parent b9a315177a
commit 86744f0842
12 changed files with 768 additions and 154 deletions

View File

@@ -3,9 +3,56 @@ import React, { useState, useRef, useEffect } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { ArrowLeft, Play, Save, Rocket, Plus, Bot, UserCheck, Wrench, Ban, Zap, X, Copy, MousePointer2 } from 'lucide-react';
import { Button, Input, Badge } from '../components/UI';
import { mockAssistants, mockKnowledgeBases, mockWorkflows } from '../services/mockData';
import { WorkflowNode, WorkflowEdge, Workflow } from '../types';
import { Assistant, WorkflowNode, WorkflowEdge, Workflow } from '../types';
import { DebugDrawer } from './Assistants';
import { createWorkflow, fetchAssistants, fetchWorkflowById, updateWorkflow } from '../services/backendApi';
const getTemplateNodes = (templateType: string | null): WorkflowNode[] => {
if (templateType === 'lead') {
return [
{
name: 'introduction',
type: 'conversation',
isStart: true,
metadata: { position: { x: 100, y: 100 } },
prompt: "You are Morgan from GrowthPartners. Start with: 'Hello, this is Morgan from GrowthPartners. We help businesses improve their operational efficiency through custom software solutions. Do you have a few minutes to chat about how we might be able to help your business?'",
messagePlan: { firstMessage: "Hello, this is Morgan from GrowthPartners. Do you have a few minutes to chat?" }
},
{
name: 'need_discovery',
type: 'conversation',
metadata: { position: { x: 450, y: 250 } },
prompt: "Conduct need discovery by asking about business challenges...",
variableExtractionPlan: {
output: [
{ title: 'industry', type: 'string', description: 'user industry' },
{ title: 'pain_points', type: 'string', description: 'main challenges' }
]
}
},
{
name: 'hangup_node',
type: 'end',
metadata: { position: { x: 450, y: 550 } },
tool: {
type: 'endCall',
function: { name: 'hangup', parameters: {} },
messages: [{ type: 'request-start', content: 'Thank you for your time!', blocking: true }]
}
}
];
}
return [
{
name: 'start_node',
type: 'conversation',
isStart: true,
metadata: { position: { x: 200, y: 200 } },
prompt: '欢迎对话系统...',
messagePlan: { firstMessage: '你好!' }
}
];
};
export const WorkflowEditorPage: React.FC = () => {
const navigate = useNavigate();
@@ -15,58 +62,11 @@ export const WorkflowEditorPage: React.FC = () => {
// Template data for new workflows
const templateName = searchParams.get('name');
const templateType = searchParams.get('template');
// Find initial workflow or create a new one
const existingWf = mockWorkflows.find(w => w.id === id);
const [name, setName] = useState(templateName || existingWf?.name || '新工作流');
const [nodes, setNodes] = useState<WorkflowNode[]>(() => {
if (existingWf) return existingWf.nodes;
if (templateType === 'lead') {
return [
{
name: 'introduction',
type: 'conversation',
isStart: true,
metadata: { position: { x: 100, y: 100 } },
prompt: "You are Morgan from GrowthPartners. Start with: 'Hello, this is Morgan from GrowthPartners. We help businesses improve their operational efficiency through custom software solutions. Do you have a few minutes to chat about how we might be able to help your business?'",
messagePlan: { firstMessage: "Hello, this is Morgan from GrowthPartners. Do you have a few minutes to chat?" }
},
{
name: 'need_discovery',
type: 'conversation',
metadata: { position: { x: 450, y: 250 } },
prompt: "Conduct need discovery by asking about business challenges...",
variableExtractionPlan: {
output: [
{ title: 'industry', type: 'string', description: 'user industry' },
{ title: 'pain_points', type: 'string', description: 'main challenges' }
]
}
},
{
name: 'hangup_node',
type: 'end',
metadata: { position: { x: 450, y: 550 } },
tool: {
type: 'endCall',
function: { name: 'hangup', parameters: {} },
messages: [{ type: 'request-start', content: 'Thank you for your time!', blocking: true }]
}
}
];
}
return [
{
name: 'start_node',
type: 'conversation',
isStart: true,
metadata: { position: { x: 200, y: 200 } },
prompt: '欢迎对话系统...',
messagePlan: { firstMessage: '你好!' }
}
];
});
const [edges, setEdges] = useState<WorkflowEdge[]>(existingWf?.edges || []);
const [name, setName] = useState(templateName || '新工作流');
const [nodes, setNodes] = useState<WorkflowNode[]>(() => getTemplateNodes(templateType));
const [edges, setEdges] = useState<WorkflowEdge[]>([]);
const [createdAt, setCreatedAt] = useState('');
const [assistants, setAssistants] = useState<Assistant[]>([]);
const [selectedNodeName, setSelectedNodeName] = useState<string | null>(null);
const [isAddMenuOpen, setIsAddMenuOpen] = useState(false);
@@ -141,6 +141,36 @@ export const WorkflowEditorPage: React.FC = () => {
};
}, [draggingNodeName, isPanning, zoom]);
useEffect(() => {
const loadData = async () => {
try {
const assistantList = await fetchAssistants();
setAssistants(assistantList);
} catch (error) {
console.error(error);
}
};
loadData();
}, []);
useEffect(() => {
if (!id) return;
const loadWorkflow = async () => {
try {
const workflow = await fetchWorkflowById(id);
setName(workflow.name);
setNodes(workflow.nodes);
setEdges(workflow.edges);
setCreatedAt(workflow.createdAt);
} catch (error) {
console.error(error);
alert('加载工作流失败。');
}
};
loadWorkflow();
}, [id]);
const addNode = (type: WorkflowNode['type']) => {
const newNode: WorkflowNode = {
name: `${type}_${Date.now()}`,
@@ -158,26 +188,29 @@ export const WorkflowEditorPage: React.FC = () => {
setNodes(prev => prev.map(n => n.name === selectedNodeName ? { ...n, [field]: value } : n));
};
const handleSave = () => {
const handleSave = async () => {
const now = new Date().toISOString().replace('T', ' ').substring(0, 16);
const updatedWorkflow: Workflow = {
id: id || `wf_${Date.now()}`,
const workflowPayload: Partial<Workflow> = {
name,
nodeCount: nodes.length,
createdAt: existingWf?.createdAt || now,
createdAt: createdAt || now,
updatedAt: now,
nodes,
edges,
};
if (id) {
const idx = mockWorkflows.findIndex(w => w.id === id);
if (idx !== -1) mockWorkflows[idx] = updatedWorkflow;
} else {
mockWorkflows.push(updatedWorkflow);
try {
if (id) {
await updateWorkflow(id, workflowPayload);
} else {
await createWorkflow(workflowPayload);
}
alert('保存成功!工作流已同步至列表。');
navigate('/workflows');
} catch (error) {
console.error(error);
alert('保存失败,请稍后重试。');
}
alert('保存成功!工作流已同步至列表。');
navigate('/workflows');
};
return (
@@ -385,7 +418,18 @@ export const WorkflowEditorPage: React.FC = () => {
<DebugDrawer
isOpen={isDebugOpen}
onClose={() => setIsDebugOpen(false)}
assistant={mockAssistants[0]}
assistant={assistants[0] || {
id: 'debug',
name: 'Debug Assistant',
callCount: 0,
opener: 'Hello!',
prompt: '',
knowledgeBaseId: '',
language: 'zh',
voice: '',
speed: 1,
hotwords: [],
}}
/>
</div>
);