HF_catalyst / frontend /components /WorkflowSender.tsx
SissiFeng's picture
Initial commit
cdb8847
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;