agent / frontend /src /pages /teacher /CreateAgent.jsx
samlax12's picture
Upload 139 files
ad74240 verified
/**
* 教师端 - 创建Agent页面
* 对应原始 index.html 的 create-agent section (4步向导)
* 复用原始HTML的4步表单逻辑
*/
import { useState, useEffect } from 'react';
import { Steps, Card, Form, Input, Select, Button, Space, Checkbox, message, Row, Col, Modal } from 'antd';
import {
InfoCircleOutlined,
AppstoreOutlined,
DatabaseOutlined,
BranchesOutlined,
ArrowLeftOutlined,
ArrowRightOutlined,
CheckOutlined,
} from '@ant-design/icons';
import { motion } from 'framer-motion';
import knowledgeService from '../../services/knowledgeService';
import agentService from '../../services/agentService';
import WorkflowEditor from '../../components/WorkflowEditor';
const { TextArea } = Input;
const CreateAgent = () => {
const [form] = Form.useForm();
const [currentStep, setCurrentStep] = useState(0);
const [submitting, setSubmitting] = useState(false); // 创建Agent的loading
const [aiGenerating, setAiGenerating] = useState(false); // AI生成工作流的loading
const [selectedPlugins, setSelectedPlugins] = useState([]);
const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState([]);
const [knowledgeBases, setKnowledgeBases] = useState([]);
const [workflow, setWorkflow] = useState(null);
const [showWorkflowEditor, setShowWorkflowEditor] = useState(false);
// 检查是否是编辑模式
const urlParams = new URLSearchParams(window.location.search);
const isEditMode = urlParams.get('mode') === 'edit';
const [editingAgentId, setEditingAgentId] = useState(null);
// 保存每个步骤的数据
const [formData, setFormData] = useState({
name: '',
description: '',
subject: '',
instructor: '',
type: 'general',
});
// 加载知识库列表和编辑数据
useEffect(() => {
loadKnowledgeBases();
// 如果是编辑模式,加载agent数据
if (isEditMode) {
const editingAgentStr = localStorage.getItem('editingAgent');
if (editingAgentStr) {
try {
const editingAgent = JSON.parse(editingAgentStr);
setEditingAgentId(editingAgent.id);
// 设置表单数据
setFormData({
name: editingAgent.name || '',
description: editingAgent.description || '',
subject: editingAgent.subject || '',
instructor: editingAgent.instructor || '',
type: editingAgent.type || 'general',
});
// 设置表单初始值
form.setFieldsValue({
name: editingAgent.name || '',
description: editingAgent.description || '',
subject: editingAgent.subject || '',
instructor: editingAgent.instructor || '',
type: editingAgent.type || 'general',
});
// 设置其他数据
setSelectedPlugins(editingAgent.plugins || []);
setSelectedKnowledgeBases(editingAgent.knowledgeBases || []);
setWorkflow(editingAgent.workflow || null);
// 清除localStorage
localStorage.removeItem('editingAgent');
} catch (error) {
console.error('加载编辑数据失败:', error);
}
}
}
}, [isEditMode]);
const loadKnowledgeBases = async () => {
try {
const res = await knowledgeService.getKnowledgeBases();
setKnowledgeBases(res.data || []);
} catch (error) {
console.error('加载知识库失败:', error);
}
};
// 可用插件列表(对应原始HTML的插件选项)
const availablePlugins = [
{
id: 'code',
name: '代码执行',
icon: '💻',
description: '允许学生编写和执行Python代码,实时获取结果',
color: '#10b981',
},
{
id: 'visualization',
name: '3D可视化',
icon: '📊',
description: '生成交互式3D图形,帮助学生理解数学概念',
color: '#f97316',
},
{
id: 'mindmap',
name: '思维导图',
icon: '🗺️',
description: '创建结构化的思维导图,帮助学生梳理知识点',
color: '#8b5cf6',
},
];
// Agent类型选项
const agentTypes = [
{ value: 'educational', label: '教育辅导' },
{ value: 'programming', label: '编程辅助' },
{ value: 'math', label: '数学辅导' },
{ value: 'general', label: '通用助手' },
];
// 步骤配置
const steps = [
{
title: '基本信息',
icon: <InfoCircleOutlined />,
},
{
title: '插件选择',
icon: <AppstoreOutlined />,
},
{
title: '知识库',
icon: <DatabaseOutlined />,
},
{
title: '工作流',
icon: <BranchesOutlined />,
},
];
// 切换插件选择
const togglePlugin = (pluginId) => {
setSelectedPlugins((prev) =>
prev.includes(pluginId)
? prev.filter((id) => id !== pluginId)
: [...prev, pluginId]
);
};
// 切换知识库选择
const toggleKnowledgeBase = (kbId) => {
setSelectedKnowledgeBases((prev) =>
prev.includes(kbId)
? prev.filter((id) => id !== kbId)
: [...prev, kbId]
);
};
// 生成AI工作流
const handleGenerateAIWorkflow = async () => {
try {
setAiGenerating(true);
const values = form.getFieldsValue();
const res = await agentService.generateAIWorkflow({
name: values.name,
description: values.description,
type: values.type,
plugins: selectedPlugins,
knowledgeBases: selectedKnowledgeBases,
});
if (res.success) {
setWorkflow(res.data.workflow);
message.success('AI工作流生成成功');
}
} catch (error) {
message.error('生成工作流失败');
} finally {
setAiGenerating(false);
}
};
// 下一步
const handleNext = async () => {
try {
// 验证当前步骤的表单
if (currentStep === 0) {
await form.validateFields(['name', 'description', 'type']);
// 保存第一步的表单数据
const values = form.getFieldsValue();
setFormData({
name: values.name,
description: values.description,
subject: values.subject || '', // subject 可能为空
instructor: values.instructor || '', // instructor 可能为空
type: values.type,
});
}
setCurrentStep(currentStep + 1);
} catch (error) {
message.error('请填写完整信息');
}
};
// 上一步
const handlePrev = () => {
setCurrentStep(currentStep - 1);
// 返回第一步时,恢复表单数据
if (currentStep === 1) {
setTimeout(() => {
form.setFieldsValue(formData);
}, 0);
}
};
// 提交创建或更新Agent
const handleSubmit = async () => {
try {
setSubmitting(true);
const agentData = {
name: formData.name,
description: formData.description,
subject: formData.subject,
instructor: formData.instructor,
type: formData.type,
plugins: selectedPlugins,
knowledgeBases: selectedKnowledgeBases,
workflow: workflow || {
nodes: [],
edges: [],
},
};
console.log('发送Agent数据:', agentData);
let res;
if (isEditMode && editingAgentId) {
// 更新Agent
res = await agentService.updateAgent(editingAgentId, agentData);
if (res.success) {
message.success('Agent更新成功!');
// 跳转回列表页
window.location.href = '/teacher/agents';
}
} else {
// 创建新Agent
res = await agentService.createAgent(agentData);
if (res.success) {
message.success('Agent创建成功!');
// 重置表单
form.resetFields();
setCurrentStep(0);
setSelectedPlugins([]);
setSelectedKnowledgeBases([]);
setWorkflow(null);
setFormData({
name: '',
description: '',
subject: '',
instructor: '',
type: 'general',
});
}
}
if (!res.success) {
message.error(res.message || '操作失败');
}
} catch (error) {
console.error('操作失败:', error);
message.error(error.response?.data?.message || '操作失败');
} finally {
setSubmitting(false);
}
};
// 渲染步骤内容
const renderStepContent = () => {
switch (currentStep) {
case 0:
// 步骤1: 基本信息
return (
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3 }}
>
<Form form={form} layout="vertical" size="large">
<Form.Item
label="Agent名称"
name="name"
rules={[{ required: true, message: '请输入Agent名称' }]}
>
<Input placeholder="例如:Python编程助手" />
</Form.Item>
<Form.Item
label="描述"
name="description"
rules={[{ required: true, message: '请输入描述' }]}
>
<TextArea
rows={4}
placeholder="简要描述这个Agent的功能和用途"
/>
</Form.Item>
<Row gutter={16}>
<Col span={12}>
<Form.Item label="学科/课程名称" name="subject">
<Input placeholder="例如:计算机科学" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="指导教师" name="instructor">
<Input placeholder="例如:张老师" />
</Form.Item>
</Col>
</Row>
<Form.Item
label="Agent类型"
name="type"
rules={[{ required: true, message: '请选择Agent类型' }]}
>
<Select placeholder="选择Agent类型" options={agentTypes} />
</Form.Item>
</Form>
</motion.div>
);
case 1:
// 步骤2: 插件选择
return (
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3 }}
>
<div style={{ marginBottom: '16px' }}>
<h3 style={{ fontSize: '16px', fontWeight: 600, color: '#1f2937' }}>
选择Agent可以使用的插件
</h3>
<p style={{ fontSize: '14px', color: '#6b7280' }}>
这些插件将增强Agent的能力,帮助学生更好地学习
</p>
</div>
<Row gutter={[16, 16]}>
{availablePlugins.map((plugin) => (
<Col span={24} key={plugin.id}>
<motion.div
whileHover={{ x: 4 }}
style={{
padding: '20px',
background: selectedPlugins.includes(plugin.id)
? `${plugin.color}10`
: '#f8fafc',
border: selectedPlugins.includes(plugin.id)
? `2px solid ${plugin.color}`
: '2px solid #e5e7eb',
borderRadius: '12px',
cursor: 'pointer',
transition: 'all 0.2s',
}}
onClick={() => togglePlugin(plugin.id)}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
<div
style={{
fontSize: '32px',
width: '56px',
height: '56px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: `${plugin.color}15`,
borderRadius: '12px',
}}
>
{plugin.icon}
</div>
<div style={{ flex: 1 }}>
<div style={{ fontSize: '18px', fontWeight: 600, color: '#1f2937', marginBottom: '4px' }}>
{plugin.name}
</div>
<div style={{ fontSize: '14px', color: '#6b7280' }}>
{plugin.description}
</div>
</div>
<Checkbox checked={selectedPlugins.includes(plugin.id)} />
</div>
</motion.div>
</Col>
))}
</Row>
</motion.div>
);
case 2:
// 步骤3: 知识库关联
return (
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3 }}
>
<div style={{ marginBottom: '16px' }}>
<h3 style={{ fontSize: '16px', fontWeight: 600, color: '#1f2937' }}>
选择关联的知识库
</h3>
<p style={{ fontSize: '14px', color: '#6b7280' }}>
选择与此Agent相关的知识库,Agent将基于这些知识回答问题
</p>
</div>
{knowledgeBases.length > 0 ? (
<Row gutter={[16, 16]}>
{knowledgeBases.map((kb) => (
<Col span={12} key={kb.id}>
<motion.div
whileHover={{ y: -4 }}
style={{
padding: '16px',
background: selectedKnowledgeBases.includes(kb.id)
? 'rgba(16, 185, 129, 0.1)'
: 'white',
border: selectedKnowledgeBases.includes(kb.id)
? '2px solid #10b981'
: '1px solid #e5e7eb',
borderRadius: '12px',
cursor: 'pointer',
transition: 'all 0.2s',
}}
onClick={() => toggleKnowledgeBase(kb.id)}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<div style={{ fontSize: '16px', fontWeight: 600, color: '#1f2937' }}>
{kb.name}
</div>
<div style={{ fontSize: '14px', color: '#6b7280', marginTop: '4px' }}>
{kb.fileCount || 0} 个文件
</div>
</div>
<Checkbox checked={selectedKnowledgeBases.includes(kb.id)} />
</div>
</motion.div>
</Col>
))}
</Row>
) : (
<div style={{ textAlign: 'center', padding: '40px', color: '#9ca3af' }}>
<DatabaseOutlined style={{ fontSize: '48px', marginBottom: '16px' }} />
<div>还没有创建任何知识库</div>
<Button type="link" style={{ marginTop: '8px' }}>
去创建知识库
</Button>
</div>
)}
</motion.div>
);
case 3:
// 步骤4: 工作流设计
return (
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3 }}
>
<div style={{ marginBottom: '24px' }}>
<h3 style={{ fontSize: '16px', fontWeight: 600, color: '#1f2937' }}>
工作流设计
</h3>
<p style={{ fontSize: '14px', color: '#6b7280' }}>
定义Agent如何处理学生的问题和请求
</p>
</div>
<Card
style={{
border: '1px solid rgba(139, 92, 246, 0.2)',
borderRadius: '12px',
marginBottom: '16px',
}}
>
<Space direction="vertical" size="large" style={{ width: '100%' }}>
<div>
<h4 style={{ fontSize: '16px', fontWeight: 600, marginBottom: '8px' }}>
AI自动设计(推荐)
</h4>
<p style={{ fontSize: '14px', color: '#6b7280', marginBottom: '16px' }}>
让AI根据您选择的插件和知识库自动生成最优工作流
</p>
<Button
type="primary"
icon={<BranchesOutlined />}
loading={aiGenerating}
onClick={(e) => {
e.stopPropagation();
handleGenerateAIWorkflow();
}}
style={{ backgroundColor: '#8b5cf6' }}
>
AI自动生成工作流
</Button>
</div>
{workflow && (
<div
style={{
padding: '16px',
background: '#f8fafc',
borderRadius: '8px',
border: '1px solid #e5e7eb',
}}
>
<div style={{ fontSize: '14px', fontWeight: 500, marginBottom: '8px' }}>
工作流已生成
</div>
<div style={{ fontSize: '12px', color: '#6b7280' }}>
包含 {workflow.nodes?.length || 0} 个节点,
{workflow.edges?.length || 0} 条连接
</div>
</div>
)}
<div style={{ paddingTop: '16px', borderTop: '1px solid #e5e7eb' }}>
<h4 style={{ fontSize: '16px', fontWeight: 600, marginBottom: '8px' }}>
跳过工作流配置
</h4>
<p style={{ fontSize: '14px', color: '#6b7280', marginBottom: '16px' }}>
暂时不配置工作流,后续可以在Agent详情中进行配置
</p>
<Button
type="default"
icon={<CheckOutlined />}
onClick={(e) => {
e.stopPropagation();
setWorkflow({ nodes: [], edges: [] });
message.success('已设置为空工作流,可以直接创建Agent');
}}
style={{
borderColor: '#10b981',
color: '#10b981',
}}
>
跳过工作流配置
</Button>
</div>
<div style={{ paddingTop: '16px', borderTop: '1px solid #e5e7eb' }}>
<h4 style={{ fontSize: '16px', fontWeight: 600, marginBottom: '8px' }}>
手动设计(高级)
</h4>
<p style={{ fontSize: '14px', color: '#6b7280' }}>
使用可视化编辑器自定义工作流
</p>
<Button onClick={() => setShowWorkflowEditor(true)}>
打开工作流编辑器
</Button>
</div>
</Space>
</Card>
{/* 提示:可以直接创建 */}
<div
style={{
marginTop: '16px',
padding: '16px',
background: '#f0f9ff',
borderRadius: '8px',
border: '1px solid #bae6fd',
}}
>
<div style={{ fontSize: '14px', color: '#0369a1', marginBottom: '4px', fontWeight: 500 }}>
💡 提示
</div>
<div style={{ fontSize: '14px', color: '#075985' }}>
工作流设计是可选的。您可以直接点击下方的"创建Agent"按钮,先创建一个基础Agent,后续再配置工作流。
</div>
</div>
</motion.div>
);
default:
return null;
}
};
return (
<div>
<div style={{ marginBottom: '32px' }}>
<h1 style={{ fontSize: '28px', fontWeight: 600, color: '#1f2937', margin: 0 }}>
{isEditMode ? '编辑Agent' : '创建新的Agent'}
</h1>
<p style={{ fontSize: '14px', color: '#6b7280', marginTop: '8px' }}>
{isEditMode ? '修改您的AI教学助手' : '按照步骤创建您的AI教学助手'}
</p>
</div>
{/* 步骤指示器 */}
<Card style={{ marginBottom: '24px', border: '1px solid #e5e7eb', borderRadius: '12px' }}>
<Steps current={currentStep} items={steps} />
</Card>
{/* 步骤内容 */}
<Card style={{ border: '1px solid #e5e7eb', borderRadius: '12px', minHeight: '400px' }}>
{renderStepContent()}
{/* 导航按钮 */}
<div
style={{
marginTop: '32px',
paddingTop: '24px',
borderTop: '1px solid #e5e7eb',
display: 'flex',
justifyContent: 'space-between',
}}
>
<Button
size="large"
icon={<ArrowLeftOutlined />}
onClick={handlePrev}
disabled={currentStep === 0}
>
上一步
</Button>
{currentStep < steps.length - 1 ? (
<Button
type="primary"
size="large"
icon={<ArrowRightOutlined />}
onClick={handleNext}
style={{ backgroundColor: '#10b981' }}
>
下一步
</Button>
) : (
<Button
type="primary"
size="large"
icon={<CheckOutlined />}
onClick={handleSubmit}
loading={submitting}
style={{ backgroundColor: '#10b981' }}
>
{isEditMode ? '保存修改' : '创建Agent'}
</Button>
)}
</div>
</Card>
{/* 工作流编辑器Modal */}
<Modal
title="工作流编辑器"
open={showWorkflowEditor}
onCancel={() => setShowWorkflowEditor(false)}
width="90%"
style={{ top: 20 }}
footer={null}
destroyOnClose
>
<WorkflowEditor
value={workflow}
onChange={(newWorkflow) => setWorkflow(newWorkflow)}
onSave={(newWorkflow) => {
setWorkflow(newWorkflow);
setShowWorkflowEditor(false);
message.success('工作流已保存');
}}
/>
</Modal>
</div>
);
};
export default CreateAgent;