Frontend start to use backend CRUD api
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user