Spaces:
Runtime error
Runtime error
| import React, { useState, useRef, useCallback } from 'react'; | |
| import apiClient from '../api/apiClient'; | |
| import { Workflow, RunResult, ApiResponse } from '../types/types'; | |
| interface WorkflowSenderProps { | |
| onWorkflowRun: (result: ApiResponse<RunResult>) => void; | |
| onWorkflowChange: (workflow: Workflow | null) => void; | |
| } | |
| const WorkflowSender: React.FC<WorkflowSenderProps> = ({ | |
| onWorkflowRun, | |
| onWorkflowChange, | |
| }) => { | |
| const [workflow, setWorkflow] = useState<Workflow | null>(null); | |
| const [jsonText, setJsonText] = useState<string>(''); | |
| const [error, setError] = useState<string | null>(null); | |
| const [isLoading, setIsLoading] = useState<boolean>(false); | |
| const [isDragging, setIsDragging] = useState<boolean>(false); | |
| const fileInputRef = useRef<HTMLInputElement>(null); | |
| // 处理JSON文本变化 | |
| const handleJsonTextChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { | |
| setJsonText(e.target.value); | |
| try { | |
| const parsedWorkflow = JSON.parse(e.target.value) as Workflow; | |
| setWorkflow(parsedWorkflow); | |
| setError(null); | |
| onWorkflowChange(parsedWorkflow); | |
| } catch (err) { | |
| setWorkflow(null); | |
| setError('JSON格式无效'); | |
| onWorkflowChange(null); | |
| } | |
| }; | |
| // 处理文件选择 | |
| const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => { | |
| const file = e.target.files?.[0]; | |
| if (file) { | |
| readJsonFile(file); | |
| } | |
| }; | |
| // 读取JSON文件 | |
| const readJsonFile = (file: File) => { | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| try { | |
| const content = e.target?.result as string; | |
| setJsonText(content); | |
| const parsedWorkflow = JSON.parse(content) as Workflow; | |
| setWorkflow(parsedWorkflow); | |
| setError(null); | |
| onWorkflowChange(parsedWorkflow); | |
| } catch (err) { | |
| setWorkflow(null); | |
| setError('无法解析JSON文件'); | |
| onWorkflowChange(null); | |
| } | |
| }; | |
| reader.onerror = () => { | |
| setError('读取文件失败'); | |
| }; | |
| reader.readAsText(file); | |
| }; | |
| // 处理拖拽 | |
| const handleDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => { | |
| e.preventDefault(); | |
| setIsDragging(true); | |
| }, []); | |
| const handleDragLeave = useCallback((e: React.DragEvent<HTMLDivElement>) => { | |
| e.preventDefault(); | |
| setIsDragging(false); | |
| }, []); | |
| const handleDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => { | |
| e.preventDefault(); | |
| setIsDragging(false); | |
| if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { | |
| const file = e.dataTransfer.files[0]; | |
| if (file.type === 'application/json' || file.name.endsWith('.json')) { | |
| readJsonFile(file); | |
| } else { | |
| setError('请上传JSON文件'); | |
| } | |
| } | |
| }, []); | |
| // 发送工作流 | |
| const handleSendWorkflow = async () => { | |
| if (!workflow) { | |
| setError('没有有效的工作流可发送'); | |
| return; | |
| } | |
| setIsLoading(true); | |
| setError(null); | |
| try { | |
| const result = await apiClient.runExperiment(workflow); | |
| onWorkflowRun(result); | |
| if (!result.success) { | |
| setError(result.error || '发送工作流失败'); | |
| } | |
| } catch (err) { | |
| const errorMessage = err instanceof Error ? err.message : '未知错误'; | |
| setError(`发送工作流时出错: ${errorMessage}`); | |
| onWorkflowRun({ | |
| success: false, | |
| error: errorMessage, | |
| }); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| // 点击上传按钮 | |
| const handleClickUpload = () => { | |
| fileInputRef.current?.click(); | |
| }; | |
| // 重置表单 | |
| const handleReset = () => { | |
| setWorkflow(null); | |
| setJsonText(''); | |
| setError(null); | |
| onWorkflowChange(null); | |
| }; | |
| return ( | |
| <div className="workflow-sender"> | |
| <h2>工作流发送器</h2> | |
| {/* 拖放区域 */} | |
| <div | |
| className={`json-dropzone ${isDragging ? 'dragging' : ''} ${error ? 'error' : ''}`} | |
| onDragOver={handleDragOver} | |
| onDragLeave={handleDragLeave} | |
| onDrop={handleDrop} | |
| onClick={handleClickUpload} | |
| > | |
| <input | |
| type="file" | |
| ref={fileInputRef} | |
| className="file-input" | |
| accept=".json" | |
| onChange={handleFileSelect} | |
| style={{ display: 'none' }} | |
| /> | |
| {workflow ? ( | |
| <div className="file-info"> | |
| <span>已加载工作流: {workflow.name || 'Unnamed Workflow'}</span> | |
| <p>节点数量: {workflow.nodes.length}, 边数量: {workflow.edges.length}</p> | |
| </div> | |
| ) : ( | |
| <div className="upload-prompt"> | |
| <p>拖放JSON文件到这里或点击上传</p> | |
| <small>支持.json工作流文件</small> | |
| </div> | |
| )} | |
| </div> | |
| {/* JSON编辑器 */} | |
| <div className="json-editor"> | |
| <textarea | |
| value={jsonText} | |
| onChange={handleJsonTextChange} | |
| placeholder="在这里粘贴您的JSON工作流..." | |
| rows={10} | |
| className={error ? 'error' : ''} | |
| /> | |
| </div> | |
| {/* 错误提示 */} | |
| {error && <div className="error-message">{error}</div>} | |
| {/* 操作按钮 */} | |
| <div className="action-buttons"> | |
| <button | |
| onClick={handleReset} | |
| disabled={isLoading || !workflow} | |
| className="reset-button" | |
| > | |
| 重置 | |
| </button> | |
| <button | |
| onClick={handleSendWorkflow} | |
| disabled={isLoading || !workflow} | |
| className="send-button" | |
| > | |
| {isLoading ? '发送中...' : '发送工作流'} | |
| </button> | |
| </div> | |
| {/* CSS样式 */} | |
| <style jsx>{` | |
| .workflow-sender { | |
| padding: 20px; | |
| border-radius: 8px; | |
| background-color: #f8f9fa; | |
| box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); | |
| } | |
| .json-dropzone { | |
| padding: 30px; | |
| border: 2px dashed #ccc; | |
| border-radius: 6px; | |
| background-color: #fafafa; | |
| text-align: center; | |
| cursor: pointer; | |
| margin-bottom: 15px; | |
| transition: all 0.2s ease; | |
| } | |
| .json-dropzone.dragging { | |
| border-color: #007bff; | |
| background-color: rgba(0, 123, 255, 0.05); | |
| } | |
| .json-dropzone.error { | |
| border-color: #dc3545; | |
| } | |
| .upload-prompt p { | |
| margin: 0 0 5px; | |
| font-size: 16px; | |
| } | |
| .upload-prompt small { | |
| color: #6c757d; | |
| } | |
| .file-info { | |
| font-size: 14px; | |
| } | |
| .json-editor textarea { | |
| width: 100%; | |
| padding: 12px; | |
| border: 1px solid #ced4da; | |
| border-radius: 4px; | |
| font-family: monospace; | |
| font-size: 14px; | |
| line-height: 1.5; | |
| } | |
| .json-editor textarea.error { | |
| border-color: #dc3545; | |
| } | |
| .error-message { | |
| color: #dc3545; | |
| margin: 10px 0; | |
| font-size: 14px; | |
| } | |
| .action-buttons { | |
| display: flex; | |
| justify-content: flex-end; | |
| gap: 10px; | |
| margin-top: 15px; | |
| } | |
| button { | |
| padding: 8px 16px; | |
| border: none; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| font-weight: 500; | |
| transition: background-color 0.2s; | |
| } | |
| button:disabled { | |
| opacity: 0.6; | |
| cursor: not-allowed; | |
| } | |
| .reset-button { | |
| background-color: #6c757d; | |
| color: white; | |
| } | |
| .send-button { | |
| background-color: #007bff; | |
| color: white; | |
| } | |
| .reset-button:hover:not(:disabled) { | |
| background-color: #5a6268; | |
| } | |
| .send-button:hover:not(:disabled) { | |
| background-color: #0069d9; | |
| } | |
| `}</style> | |
| </div> | |
| ); | |
| }; | |
| export default WorkflowSender; | |