You've already forked DataMate
init datamate
This commit is contained in:
1250
frontend/src/pages/SynthesisTask/CreateTask.tsx
Normal file
1250
frontend/src/pages/SynthesisTask/CreateTask.tsx
Normal file
File diff suppressed because it is too large
Load Diff
302
frontend/src/pages/SynthesisTask/CreateTemplate.tsx
Normal file
302
frontend/src/pages/SynthesisTask/CreateTemplate.tsx
Normal file
@@ -0,0 +1,302 @@
|
||||
import { useState, useRef } from "react";
|
||||
import { Card, Select, Input, Button, Badge, Divider, Form, message } from "antd";
|
||||
import {
|
||||
Plus,
|
||||
ArrowLeft,
|
||||
Play,
|
||||
Save,
|
||||
RefreshCw,
|
||||
FileText,
|
||||
Code,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { useNavigate } from "react-router";
|
||||
import { mockTemplates } from "@/mock/annotation";
|
||||
import DevelopmentInProgress from "@/components/DevelopmentInProgress";
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
export default function InstructionTemplateCreate() {
|
||||
return <DevelopmentInProgress />;
|
||||
const navigate = useNavigate();
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(null);
|
||||
const [isTestingTemplate, setIsTestingTemplate] = useState(false);
|
||||
const [templates, setTemplates] = useState<Template[]>(mockTemplates);
|
||||
const [variables, setVariables] = useState<string[]>([]);
|
||||
const variableInputRef = useRef<Input | null>(null);
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
// 初始化表单数据
|
||||
const initialValues = selectedTemplate
|
||||
? {
|
||||
name: selectedTemplate.name,
|
||||
category: selectedTemplate.category,
|
||||
prompt: selectedTemplate.prompt,
|
||||
description: selectedTemplate.description,
|
||||
testInput: "",
|
||||
testOutput: "",
|
||||
}
|
||||
: {
|
||||
name: "",
|
||||
category: "",
|
||||
prompt: "",
|
||||
description: "",
|
||||
testInput: "",
|
||||
testOutput: "",
|
||||
};
|
||||
|
||||
// 变量同步
|
||||
const handlePromptChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const value = e.target.value;
|
||||
form.setFieldValue("prompt", value);
|
||||
// 自动提取变量
|
||||
const matches = Array.from(value.matchAll(/\{(\w+)\}/g)).map((m) => m[1]);
|
||||
setVariables(Array.from(new Set(matches)));
|
||||
};
|
||||
|
||||
// 添加变量(手动)
|
||||
const handleAddVariable = () => {
|
||||
const input = variableInputRef.current?.input;
|
||||
const value = input?.value.trim();
|
||||
if (value && !variables.includes(value)) {
|
||||
setVariables([...variables, value]);
|
||||
input.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
// 删除变量
|
||||
const handleRemoveVariable = (index: number) => {
|
||||
setVariables(variables.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
// 测试模板
|
||||
const handleTestTemplate = async () => {
|
||||
const values = form.getFieldsValue();
|
||||
if (!values.prompt || !values.testInput) return;
|
||||
setIsTestingTemplate(true);
|
||||
setTimeout(() => {
|
||||
form.setFieldValue(
|
||||
"testOutput",
|
||||
`基于输入"${values.testInput}"生成的测试结果:
|
||||
|
||||
这是一个模拟的输出结果,展示了模板的工作效果。在实际使用中,这里会显示AI模型根据您的模板和输入生成的真实结果。
|
||||
|
||||
模板变量已正确替换,输出格式符合预期。`
|
||||
);
|
||||
setIsTestingTemplate(false);
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
// 保存模板
|
||||
const handleSaveTemplate = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
if (!values.name || !values.prompt || !values.category) return;
|
||||
if (selectedTemplate) {
|
||||
setTemplates((prev) =>
|
||||
prev.map((t) =>
|
||||
t.id === selectedTemplate.id
|
||||
? {
|
||||
...t,
|
||||
...values,
|
||||
variables,
|
||||
type: "custom" as const,
|
||||
usageCount: t.usageCount,
|
||||
lastUsed: new Date().toISOString().split("T")[0],
|
||||
}
|
||||
: t
|
||||
)
|
||||
);
|
||||
} else {
|
||||
const newTemplate: Template = {
|
||||
id: Date.now(),
|
||||
...values,
|
||||
variables,
|
||||
type: "custom",
|
||||
usageCount: 0,
|
||||
quality: 85,
|
||||
};
|
||||
setTemplates([newTemplate, ...templates]);
|
||||
}
|
||||
message.success("模板已保存");
|
||||
navigate("/data/synthesis/task");
|
||||
} catch {
|
||||
// 校验失败
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
onClick={() => navigate("/data/synthesis/task")}
|
||||
type="text"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
</Button>
|
||||
<h1 className="text-xl font-bold bg-clip-text">
|
||||
{selectedTemplate ? "编辑模板" : "创建新模板"}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<Card className="overflow-y-auto p-2">
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={initialValues}
|
||||
autoComplete="off"
|
||||
>
|
||||
<h2 className="font-medium text-gray-900 text-lg mb-2">基本信息</h2>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Form.Item
|
||||
label="模板名称"
|
||||
name="name"
|
||||
rules={[{ required: true, message: "请输入模板名称" }]}
|
||||
>
|
||||
<Input placeholder="输入模板名称" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="分类"
|
||||
name="category"
|
||||
rules={[{ required: true, message: "请选择分类" }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="选择分类"
|
||||
options={[
|
||||
{ label: "问答对生成", value: "问答对生成" },
|
||||
{ label: "蒸馏数据集", value: "蒸馏数据集" },
|
||||
{ label: "文本生成", value: "文本生成" },
|
||||
{ label: "多模态生成", value: "多模态生成" },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item label="模板描述" name="description">
|
||||
<Input placeholder="简要描述模板的用途和特点" />
|
||||
</Form.Item>
|
||||
<h2 className="font-medium text-gray-900 text-lg mt-6 mb-2">Prompt内容</h2>
|
||||
<Form.Item
|
||||
label="Prompt内容"
|
||||
name="prompt"
|
||||
rules={[{ required: true, message: "请输入Prompt内容" }]}
|
||||
>
|
||||
<TextArea
|
||||
placeholder="输入prompt内容,使用 {变量名} 格式定义变量"
|
||||
rows={10}
|
||||
className="font-mono text-xs resize-none"
|
||||
onChange={handlePromptChange}
|
||||
/>
|
||||
</Form.Item>
|
||||
<p className="text-xs text-gray-500 mb-2">
|
||||
提示:使用 {"{变量名}"} 格式定义变量,例如 {"{text}"} 或 {"{input}"}
|
||||
</p>
|
||||
<div className="mb-4">
|
||||
<span className="text-sm font-semibold text-gray-700">变量管理</span>
|
||||
<div className="flex flex-wrap gap-2 min-h-[50px] p-3 border rounded-xl bg-gray-50 mt-2">
|
||||
{variables.map((variable, index) => (
|
||||
<Badge
|
||||
key={index}
|
||||
count={
|
||||
<X
|
||||
className="w-3 h-3 cursor-pointer"
|
||||
onClick={() => handleRemoveVariable(index)}
|
||||
/>
|
||||
}
|
||||
style={{ backgroundColor: "#fff" }}
|
||||
>
|
||||
<span className="flex items-center gap-1 px-2 py-1 text-xs">
|
||||
<Code className="w-3 h-3" />
|
||||
{variable}
|
||||
</span>
|
||||
</Badge>
|
||||
))}
|
||||
{variables.length === 0 && (
|
||||
<span className="text-xs text-gray-400">
|
||||
暂无变量,在Prompt中使用 {"{变量名}"} 格式定义
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2 mt-2">
|
||||
<Input
|
||||
ref={variableInputRef}
|
||||
placeholder="添加变量名(如:text, input, question)"
|
||||
className="h-8 text-sm"
|
||||
onPressEnter={handleAddVariable}
|
||||
/>
|
||||
<Button onClick={handleAddVariable} type="default" className="px-4 text-sm">
|
||||
<Plus className="w-3 h-3 mr-1" />
|
||||
添加
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="font-medium text-gray-900 text-lg mb-2 pt-2">模板测试</h2>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Form.Item label="测试输入" name="testInput">
|
||||
<TextArea
|
||||
placeholder="输入测试数据"
|
||||
rows={5}
|
||||
className="resize-none text-sm"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="测试输出" name="testOutput">
|
||||
<TextArea
|
||||
readOnly
|
||||
placeholder="点击测试按钮查看输出结果"
|
||||
rows={5}
|
||||
className="resize-none bg-gray-50 text-sm"
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleTestTemplate}
|
||||
disabled={
|
||||
!form.getFieldValue("prompt") ||
|
||||
!form.getFieldValue("testInput") ||
|
||||
isTestingTemplate
|
||||
}
|
||||
type="default"
|
||||
className="px-4 py-2 text-sm"
|
||||
>
|
||||
{isTestingTemplate ? (
|
||||
<>
|
||||
<RefreshCw className="w-3 h-3 mr-1 animate-spin" />
|
||||
测试中...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Play className="w-3 h-3 mr-1" />
|
||||
测试模板
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Divider />
|
||||
<div className="flex gap-2 justify-end">
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleSaveTemplate}
|
||||
disabled={
|
||||
!form.getFieldValue("name") ||
|
||||
!form.getFieldValue("prompt") ||
|
||||
!form.getFieldValue("category")
|
||||
}
|
||||
className="px-6 py-2 text-sm font-semibold bg-purple-600 hover:bg-purple-700 shadow-lg"
|
||||
>
|
||||
<Save className="w-3 h-3 mr-1" />
|
||||
保存模板
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => navigate("/data/synthesis/task")}
|
||||
type="default"
|
||||
className="px-4 py-2 text-sm"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
84
frontend/src/pages/SynthesisTask/DataSynthesis.tsx
Normal file
84
frontend/src/pages/SynthesisTask/DataSynthesis.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { useState } from "react";
|
||||
import { Tabs, Button, Card } from "antd";
|
||||
import { Plus, ArrowRight } from "lucide-react";
|
||||
import DataAnnotation from "../DataAnnotation/Annotate/components/TextAnnotation";
|
||||
import { useNavigate } from "react-router";
|
||||
import InstructionTemplateTab from "./components/InstructionTemplateTab";
|
||||
import SynthesisTaskTab from "./components/SynthesisTaskTab";
|
||||
import DevelopmentInProgress from "@/components/DevelopmentInProgress";
|
||||
|
||||
export default function DataSynthesisPage() {
|
||||
return <DevelopmentInProgress />;
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [activeTab, setActiveTab] = useState("tasks");
|
||||
const [showAnnotatePage, setShowAnnotatePage] = useState(false);
|
||||
|
||||
if (showAnnotatePage) {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex">
|
||||
<Button
|
||||
onClick={() => setShowAnnotatePage(false)}
|
||||
className="hover:bg-white/70"
|
||||
>
|
||||
<ArrowRight className="w-4 h-4 rotate-180 mr-2" />
|
||||
</Button>
|
||||
</div>
|
||||
<DataAnnotation
|
||||
task={undefined}
|
||||
currentFileIndex={0}
|
||||
onSaveAndNext={function (): void {
|
||||
throw new Error("Function not implemented.");
|
||||
}}
|
||||
onSkipAndNext={function (): void {
|
||||
throw new Error("Function not implemented.");
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className=" p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-xl font-bold text-gray-900">数据合成</h2>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
navigate("/data/synthesis/task/create-template");
|
||||
}}
|
||||
>
|
||||
<Plus className="w-3 h-3 mr-1" />
|
||||
创建模板
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => navigate("/data/synthesis/task/create")}
|
||||
>
|
||||
<Plus className="w-3 h-3 mr-1" />
|
||||
创建合成任务
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tabs
|
||||
items={[
|
||||
{ key: "tasks", label: "合成任务", children: <SynthesisTaskTab /> },
|
||||
{
|
||||
key: "templates",
|
||||
label: "指令模板",
|
||||
children: <InstructionTemplateTab />,
|
||||
},
|
||||
]}
|
||||
activeKey={activeTab}
|
||||
onChange={setActiveTab}
|
||||
></Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
import { useState } from "react";
|
||||
import { Card, Table, Badge, Button } from "antd";
|
||||
import {
|
||||
Plus,
|
||||
FileText,
|
||||
Search,
|
||||
Edit,
|
||||
Copy,
|
||||
Trash2,
|
||||
MoreHorizontal,
|
||||
} from "lucide-react";
|
||||
import type { Template } from "@/pages/SynthesisTask/synthesis";
|
||||
import { useNavigate } from "react-router";
|
||||
import { mockTemplates } from "@/mock/synthesis";
|
||||
import { SearchControls } from "@/components/SearchControls";
|
||||
|
||||
export default function InstructionTemplateTab() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
const [templates, setTemplates] = useState<Template[]>(mockTemplates);
|
||||
const [filterTemplateType, setFilterTemplateType] = useState("all");
|
||||
|
||||
// 过滤模板
|
||||
const filteredTemplates = templates.filter((template) => {
|
||||
const matchesSearch =
|
||||
template.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
template.description.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
const matchesType =
|
||||
filterTemplateType === "all" || template.type === filterTemplateType;
|
||||
return matchesSearch && matchesType;
|
||||
});
|
||||
|
||||
const getQualityColor = (quality: number) => {
|
||||
if (quality >= 90) return "text-green-600 bg-green-50 border-green-200";
|
||||
if (quality >= 80) return "text-blue-600 bg-blue-50 border-blue-200";
|
||||
if (quality >= 70) return "text-yellow-600 bg-yellow-50 border-yellow-200";
|
||||
return "text-red-600 bg-red-50 border-red-200";
|
||||
};
|
||||
|
||||
// 模板表格列
|
||||
const templateColumns = [
|
||||
{
|
||||
title: "模板名称",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
render: (text: string, template: Template) => (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-purple-500 rounded-lg flex items-center justify-center shadow-sm">
|
||||
<FileText className="w-4 h-4 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium text-gray-900 text-sm">
|
||||
{template.name}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 line-clamp-1">
|
||||
{template.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "类型",
|
||||
dataIndex: "type",
|
||||
key: "type",
|
||||
render: (type: string) => (
|
||||
<Badge className="text-xs">
|
||||
{type === "preset" ? "预置" : "自定义"}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "分类",
|
||||
dataIndex: "category",
|
||||
key: "category",
|
||||
render: (category: string) => (
|
||||
<Badge className="bg-purple-50 text-purple-700 border-purple-200 text-xs">
|
||||
{category}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "变量数量",
|
||||
dataIndex: "variables",
|
||||
key: "variables",
|
||||
render: (variables: string[]) => (
|
||||
<div className="text-sm font-medium text-gray-900">
|
||||
{variables.length}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "使用次数",
|
||||
dataIndex: "usageCount",
|
||||
key: "usageCount",
|
||||
render: (usageCount: number) => (
|
||||
<div className="text-sm font-medium text-gray-900">{usageCount}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "质量评分",
|
||||
dataIndex: "quality",
|
||||
key: "quality",
|
||||
render: (quality: number) =>
|
||||
quality ? (
|
||||
<Badge className={`font-medium text-xs ${getQualityColor(quality)}`}>
|
||||
{quality}%
|
||||
</Badge>
|
||||
) : (
|
||||
<span className="text-sm text-gray-400">-</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "最后使用",
|
||||
dataIndex: "lastUsed",
|
||||
key: "lastUsed",
|
||||
render: (lastUsed: string) => (
|
||||
<div className="text-sm text-gray-600">{lastUsed || "-"}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "actions",
|
||||
align: "center" as const,
|
||||
render: (_: any, template: Template) => (
|
||||
<div className="flex items-center justify-center gap-1">
|
||||
<Button
|
||||
onClick={() =>
|
||||
navigate(`/data/synthesis/task/create-template/${template.id}`)
|
||||
}
|
||||
type="text"
|
||||
>
|
||||
<Edit className="w-3 h-3" />
|
||||
</Button>
|
||||
<Button type="text">
|
||||
<Copy className="w-3 h-3" />
|
||||
</Button>
|
||||
<Button type="text">
|
||||
<Trash2 className="w-3 h-3" />
|
||||
</Button>
|
||||
<Button type="text">
|
||||
<MoreHorizontal className="w-3 h-3" />
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<SearchControls
|
||||
searchTerm={searchQuery}
|
||||
onSearchChange={setSearchQuery}
|
||||
searchPlaceholder="搜索模板名称或描述..."
|
||||
filters={[
|
||||
{
|
||||
key: "type",
|
||||
label: "类型",
|
||||
options: [
|
||||
{ label: "全部类型", value: "all" },
|
||||
{ label: "预置模板", value: "preset" },
|
||||
{ label: "自定义模板", value: "custom" },
|
||||
],
|
||||
},
|
||||
]}
|
||||
selectedFilters={{ type: [filterTemplateType] }}
|
||||
onFiltersChange={(filters) => {
|
||||
setFilterTemplateType(filters.type?.[0] || "all");
|
||||
}}
|
||||
showFilters
|
||||
showViewToggle={false}
|
||||
/>
|
||||
|
||||
{/* 模板表格 */}
|
||||
<Card className="shadow-sm border-0 bg-white">
|
||||
<Table
|
||||
scroll={{ x: "max-content" }}
|
||||
columns={templateColumns}
|
||||
dataSource={filteredTemplates}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
locale={{
|
||||
emptyText: (
|
||||
<div className="text-center py-12">
|
||||
<FileText className="w-12 h-12 text-gray-400 mx-auto mb-3" />
|
||||
<h3 className="text-base font-semibold text-gray-900 mb-2">
|
||||
暂无指令模板
|
||||
</h3>
|
||||
<p className="text-gray-500 mb-4 text-sm">
|
||||
{searchQuery
|
||||
? "没有找到匹配的模板"
|
||||
: "开始创建您的第一个指令模板"}
|
||||
</p>
|
||||
{!searchQuery && (
|
||||
<Button
|
||||
onClick={() =>
|
||||
navigate("/data/synthesis/task/create-template")
|
||||
}
|
||||
className="px-6 py-2 text-sm font-semibold bg-purple-600 hover:bg-purple-700 shadow-lg"
|
||||
>
|
||||
<Plus className="w-3 h-3 mr-1" />
|
||||
创建模板
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
361
frontend/src/pages/SynthesisTask/components/SynthesisTaskTab.tsx
Normal file
361
frontend/src/pages/SynthesisTask/components/SynthesisTaskTab.tsx
Normal file
@@ -0,0 +1,361 @@
|
||||
import { useState } from "react";
|
||||
import { Card, Button, Badge, Table, Progress } from "antd";
|
||||
import {
|
||||
Plus,
|
||||
Sparkles,
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
Pause,
|
||||
Play,
|
||||
DownloadIcon,
|
||||
CheckCircle,
|
||||
} from "lucide-react";
|
||||
import type { SynthesisTask } from "@/pages/SynthesisTask/synthesis";
|
||||
import { mockSynthesisTasks } from "@/mock/synthesis";
|
||||
import { useNavigate } from "react-router";
|
||||
import { SearchControls } from "@/components/SearchControls";
|
||||
|
||||
export default function SynthesisTaskTab() {
|
||||
const navigate = useNavigate();
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [tasks, setTasks] = useState<SynthesisTask[]>(mockSynthesisTasks);
|
||||
const [filterStatus, setFilterStatus] = useState("all");
|
||||
const [sortBy, setSortBy] = useState<"createdAt" | "name">("createdAt");
|
||||
const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc");
|
||||
|
||||
// 过滤任务
|
||||
const filteredTasks = tasks.filter((task) => {
|
||||
const matchesSearch =
|
||||
task.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
task.template.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
const matchesStatus =
|
||||
filterStatus === "all" || task.status === filterStatus;
|
||||
return matchesSearch && matchesStatus;
|
||||
});
|
||||
|
||||
// 排序任务
|
||||
const sortedTasks = [...filteredTasks].sort((a, b) => {
|
||||
if (sortBy === "createdAt") {
|
||||
const dateA = new Date(a.createdAt).getTime();
|
||||
const dateB = new Date(b.createdAt).getTime();
|
||||
return sortOrder === "asc" ? dateA - dateB : dateB - dateA;
|
||||
} else if (sortBy === "name") {
|
||||
return sortOrder === "asc"
|
||||
? a.name.localeCompare(b.name)
|
||||
: b.name.localeCompare(a.name);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
const handleTaskAction = (taskId: number, action: string) => {
|
||||
setTasks((prev) =>
|
||||
prev.map((task) => {
|
||||
if (task.id === taskId) {
|
||||
switch (action) {
|
||||
case "pause":
|
||||
return { ...task, status: "paused" as const };
|
||||
case "resume":
|
||||
return { ...task, status: "running" as const };
|
||||
case "stop":
|
||||
return {
|
||||
...task,
|
||||
status: "failed" as const,
|
||||
progress: task.progress,
|
||||
};
|
||||
default:
|
||||
return task;
|
||||
}
|
||||
}
|
||||
return task;
|
||||
})
|
||||
);
|
||||
};
|
||||
// 状态徽章
|
||||
const getStatusBadge = (status: string) => {
|
||||
const statusConfig = {
|
||||
pending: {
|
||||
span: "等待中",
|
||||
color: "bg-yellow-50 text-yellow-700 border-yellow-200",
|
||||
icon: Pause,
|
||||
},
|
||||
running: {
|
||||
span: "运行中",
|
||||
color: "bg-blue-50 text-blue-700 border-blue-200",
|
||||
icon: Play,
|
||||
},
|
||||
completed: {
|
||||
span: "已完成",
|
||||
color: "bg-green-50 text-green-700 border-green-200",
|
||||
icon: CheckCircle,
|
||||
},
|
||||
failed: {
|
||||
span: "失败",
|
||||
color: "bg-red-50 text-red-700 border-red-200",
|
||||
icon: Pause,
|
||||
},
|
||||
paused: {
|
||||
span: "已暂停",
|
||||
color: "bg-gray-50 text-gray-700 border-gray-200",
|
||||
icon: Pause,
|
||||
},
|
||||
};
|
||||
return (
|
||||
statusConfig[status as keyof typeof statusConfig] || statusConfig.pending
|
||||
);
|
||||
};
|
||||
|
||||
// 任务表格列
|
||||
const taskColumns = [
|
||||
{
|
||||
title: (
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => {
|
||||
if (sortBy === "name") {
|
||||
setSortOrder(sortOrder === "asc" ? "desc" : "asc");
|
||||
} else {
|
||||
setSortBy("name");
|
||||
setSortOrder("desc");
|
||||
}
|
||||
}}
|
||||
className="h-auto p-0 font-semibold text-gray-700 hover:bg-transparent"
|
||||
>
|
||||
任务名称
|
||||
{sortBy === "name" &&
|
||||
(sortOrder === "asc" ? (
|
||||
<ArrowUp className="w-3 h-3 ml-1" />
|
||||
) : (
|
||||
<ArrowDown className="w-3 h-3 ml-1" />
|
||||
))}
|
||||
</Button>
|
||||
),
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
render: (text: string, task: SynthesisTask) => (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-blue-500 rounded-lg flex items-center justify-center shadow-sm">
|
||||
{/* 可根据 type 渲染不同图标 */}
|
||||
<span className="text-white font-bold text-base">
|
||||
{task.type?.toUpperCase()?.slice(0, 1) || "T"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium text-gray-900 text-sm">{task.name}</div>
|
||||
<div className="text-xs text-gray-500">{task.template}</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "类型",
|
||||
dataIndex: "type",
|
||||
key: "type",
|
||||
render: (type: string) => (
|
||||
<Badge className="bg-blue-50 text-blue-700 border-blue-200 text-xs">
|
||||
{type?.toUpperCase()}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "状态",
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
render: (status: string) => {
|
||||
const statusConfig = getStatusBadge(status);
|
||||
const StatusIcon = statusConfig.icon;
|
||||
return (
|
||||
<Badge
|
||||
className={`${statusConfig.color} flex items-center gap-1 w-fit text-xs`}
|
||||
>
|
||||
<StatusIcon className="w-3 h-3" />
|
||||
{statusConfig.span}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "进度",
|
||||
dataIndex: "progress",
|
||||
key: "progress",
|
||||
render: (_: any, task: SynthesisTask) =>
|
||||
task.status === "running" ? (
|
||||
<div className="space-y-1">
|
||||
<Progress percent={task.progress} size="small" showInfo={false} />
|
||||
<div className="text-xs text-gray-500">
|
||||
{Math.round(task.progress)}%
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-gray-600">
|
||||
{task.status === "completed"
|
||||
? "100%"
|
||||
: task.status === "failed"
|
||||
? `${Math.round(task.progress)}%`
|
||||
: "-"}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "源数据集",
|
||||
dataIndex: "sourceDataset",
|
||||
key: "sourceDataset",
|
||||
render: (text: string) => (
|
||||
<div className="text-sm text-gray-900">{text}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "生成数量",
|
||||
dataIndex: "generatedCount",
|
||||
key: "generatedCount",
|
||||
render: (_: any, task: SynthesisTask) => (
|
||||
<div className="text-sm font-medium text-gray-900">
|
||||
{task.generatedCount?.toLocaleString?.()} /{" "}
|
||||
{task.targetCount?.toLocaleString?.()}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "质量评分",
|
||||
dataIndex: "quality",
|
||||
key: "quality",
|
||||
render: (quality: number) =>
|
||||
quality ? (
|
||||
<Badge className="font-medium text-xs text-green-600 bg-green-50 border-green-200">
|
||||
{quality}%
|
||||
</Badge>
|
||||
) : (
|
||||
<span className="text-sm text-gray-400">-</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => {
|
||||
if (sortBy === "createdAt") {
|
||||
setSortOrder(sortOrder === "asc" ? "desc" : "asc");
|
||||
} else {
|
||||
setSortBy("createdAt");
|
||||
setSortOrder("desc");
|
||||
}
|
||||
}}
|
||||
className="h-auto p-0 font-semibold text-gray-700 hover:bg-transparent"
|
||||
>
|
||||
创建时间
|
||||
{sortBy === "createdAt" &&
|
||||
(sortOrder === "asc" ? (
|
||||
<ArrowUp className="w-3 h-3 ml-1" />
|
||||
) : (
|
||||
<ArrowDown className="w-3 h-3 ml-1" />
|
||||
))}
|
||||
</Button>
|
||||
),
|
||||
dataIndex: "createdAt",
|
||||
key: "createdAt",
|
||||
render: (createdAt: string) => (
|
||||
<div className="text-sm text-gray-600">{createdAt}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "actions",
|
||||
align: "center" as const,
|
||||
render: (_: any, task: SynthesisTask) => (
|
||||
<div className="flex items-center justify-center gap-1">
|
||||
{task.status === "running" && (
|
||||
<Button
|
||||
onClick={() => handleTaskAction(task.id, "pause")}
|
||||
className="hover:bg-orange-50 p-1 h-7 w-7"
|
||||
type="text"
|
||||
>
|
||||
<Pause className="w-3 h-3" />
|
||||
</Button>
|
||||
)}
|
||||
{task.status === "paused" && (
|
||||
<Button
|
||||
onClick={() => handleTaskAction(task.id, "resume")}
|
||||
className="hover:bg-green-50 p-1 h-7 w-7"
|
||||
type="text"
|
||||
>
|
||||
<Play className="w-3 h-3" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
className="hover:bg-blue-50 p-2 h-7 w-7"
|
||||
type="text"
|
||||
onClick={() => navigate(`/data/synthesis/task/${task.id}`)}
|
||||
>
|
||||
审核
|
||||
</Button>
|
||||
<Button className="hover:bg-green-50 p-1 h-7 w-7" type="text">
|
||||
<DownloadIcon className="w-3 h-3" />
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
{/* 搜索和筛选 */}
|
||||
<SearchControls
|
||||
searchTerm={searchQuery}
|
||||
onSearchChange={setSearchQuery}
|
||||
searchPlaceholder="搜索任务名称或模板..."
|
||||
filters={[
|
||||
{
|
||||
key: "status",
|
||||
label: "状态",
|
||||
options: [
|
||||
{ label: "全部状态", value: "all" },
|
||||
{ label: "等待中", value: "pending" },
|
||||
{ label: "运行中", value: "running" },
|
||||
{ label: "已完成", value: "completed" },
|
||||
{ label: "失败", value: "failed" },
|
||||
{ label: "已暂停", value: "paused" },
|
||||
],
|
||||
},
|
||||
]}
|
||||
selectedFilters={{ status: [filterStatus] }}
|
||||
onFiltersChange={(filters) => {
|
||||
setFilterStatus(filters.status?.[0] || "all");
|
||||
}}
|
||||
showFilters
|
||||
showViewToggle={false}
|
||||
/>
|
||||
|
||||
{/* 任务表格 */}
|
||||
<Card>
|
||||
<Table
|
||||
columns={taskColumns}
|
||||
dataSource={sortedTasks}
|
||||
rowKey="id"
|
||||
scroll={{ x: "max-content" }}
|
||||
locale={{
|
||||
emptyText: (
|
||||
<div className="text-center py-12">
|
||||
<Sparkles className="w-12 h-12 text-gray-400 mx-auto mb-3" />
|
||||
<h3 className="text-base font-semibold text-gray-900 mb-2">
|
||||
暂无合成任务
|
||||
</h3>
|
||||
<p className="text-gray-500 mb-4 text-sm">
|
||||
{searchQuery
|
||||
? "没有找到匹配的任务"
|
||||
: "开始创建您的第一个合成任务"}
|
||||
</p>
|
||||
{!searchQuery && filterStatus === "all" && (
|
||||
<Button
|
||||
onClick={() => navigate("/data/synthesis/task/create")}
|
||||
className="bg-gradient-to-r from-blue-500 to-indigo-500 hover:from-blue-600 hover:to-indigo-600 shadow-lg"
|
||||
>
|
||||
<Plus className="w-3 h-3 mr-1" />
|
||||
创建合成任务
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
35
frontend/src/pages/SynthesisTask/synthesis.d.ts
vendored
Normal file
35
frontend/src/pages/SynthesisTask/synthesis.d.ts
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
export interface SynthesisTask {
|
||||
id: number;
|
||||
name: string;
|
||||
type: "qa" | "distillation" | "text" | "multimodal";
|
||||
status: "pending" | "running" | "completed" | "failed" | "paused";
|
||||
progress: number;
|
||||
sourceDataset: string;
|
||||
targetCount: number;
|
||||
generatedCount: number;
|
||||
createdAt: string;
|
||||
template: string;
|
||||
estimatedTime?: string;
|
||||
quality?: number;
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export interface Template {
|
||||
id: number;
|
||||
name: string;
|
||||
type: "preset" | "custom";
|
||||
category: string;
|
||||
prompt: string;
|
||||
variables: string[];
|
||||
description: string;
|
||||
usageCount: number;
|
||||
lastUsed?: string;
|
||||
quality?: number;
|
||||
}
|
||||
|
||||
interface File {
|
||||
id: string;
|
||||
name: string;
|
||||
size: string;
|
||||
type: string;
|
||||
}
|
||||
Reference in New Issue
Block a user