| |
| |
| |
| |
| |
| 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); |
| const [aiGenerating, setAiGenerating] = useState(false); |
| 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(); |
|
|
| |
| 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.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); |
| } |
| }; |
|
|
| |
| 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', |
| }, |
| ]; |
|
|
| |
| 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] |
| ); |
| }; |
|
|
| |
| 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 || '', |
| instructor: values.instructor || '', |
| type: values.type, |
| }); |
| } |
|
|
| setCurrentStep(currentStep + 1); |
| } catch (error) { |
| message.error('请填写完整信息'); |
| } |
| }; |
|
|
| |
| const handlePrev = () => { |
| setCurrentStep(currentStep - 1); |
| |
| if (currentStep === 1) { |
| setTimeout(() => { |
| form.setFieldsValue(formData); |
| }, 0); |
| } |
| }; |
|
|
| |
| 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) { |
| |
| res = await agentService.updateAgent(editingAgentId, agentData); |
| if (res.success) { |
| message.success('Agent更新成功!'); |
| |
| window.location.href = '/teacher/agents'; |
| } |
| } else { |
| |
| 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: |
| |
| 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: |
| |
| 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: |
| |
| 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: |
| |
| 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; |
|
|