feat: fix the problem in the Operator Market frontend pages

This commit is contained in:
root
2025-12-29 11:38:47 +08:00
parent 29e4a333a9
commit 844add27ea
213 changed files with 45547 additions and 45537 deletions

View File

@@ -1,428 +1,428 @@
import React, { useState, useEffect } from 'react';
import { Button, Form, Input, Select, message, Modal, Row, Col, Table, Space } from 'antd';
import { EyeOutlined } from '@ant-design/icons';
import { queryDatasetsUsingGet } from "@/pages/DataManagement/dataset.api.ts";
import { mapDataset } from "@/pages/DataManagement/dataset.const.tsx";
import { queryModelListUsingGet } from "@/pages/SettingsPage/settings.apis.ts";
import { ModelI } from "@/pages/SettingsPage/ModelAccess.tsx";
import { createEvaluationTaskUsingPost } from "@/pages/DataEvaluation/evaluation.api.ts";
import { queryPromptTemplatesUsingGet } from "@/pages/DataEvaluation/evaluation.api.ts";
import PreviewPromptModal from "@/pages/DataEvaluation/Create/PreviewPrompt.tsx";
import { EVAL_METHODS, TASK_TYPES } from "@/pages/DataEvaluation/evaluation.const.tsx";
interface Dataset {
id: string;
name: string;
fileCount: number;
size: string;
}
interface Dimension {
key: string;
dimension: string;
description: string;
}
interface PromptTemplate {
evalType: string;
prompt: string;
defaultDimensions: Dimension[];
}
interface CreateTaskModalProps {
visible: boolean;
onCancel: () => void;
onSuccess: () => void;
}
const DEFAULT_EVAL_METHOD = 'AUTO';
const DEFAULT_TASK_TYPE = 'QA';
const CreateTaskModal: React.FC<CreateTaskModalProps> = ({ visible, onCancel, onSuccess }) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [datasets, setDatasets] = useState<Dataset[]>([]);
const [models, setModels] = useState<ModelI[]>([]);
const [dimensions, setDimensions] = useState<Dimension[]>([]);
const [newDimension, setNewDimension] = useState<Omit<Dimension, 'key'>>({
dimension: '',
description: ''
});
const [taskType, setTaskType] = useState<string>(DEFAULT_TASK_TYPE);
const [promptTemplates, setPromptTemplates] = useState<PromptTemplate[]>([]);
const [previewVisible, setPreviewVisible] = useState(false);
const [evaluationPrompt, setEvaluationPrompt] = useState('');
const handleAddDimension = () => {
if (!newDimension.dimension.trim()) {
message.warning('请输入维度名称');
return;
}
setDimensions([...dimensions, { ...newDimension, key: `dim-${Date.now()}` }]);
setNewDimension({ dimension: '', description: '' });
};
const handleDeleteDimension = (key: string) => {
if (dimensions.length <= 1) {
message.warning('至少需要保留一个评估维度');
return;
}
setDimensions(dimensions.filter(item => item.key !== key));
};
useEffect(() => {
if (visible) {
fetchDatasets().then();
fetchModels().then();
fetchPromptTemplates().then();
// sync form with local taskType default
form.setFieldsValue({ taskType: DEFAULT_TASK_TYPE });
}
}, [visible]);
// when promptTemplates or taskType change, switch dimensions to template defaults (COT/QA)
useEffect(() => {
if (!promptTemplates || promptTemplates.length === 0) return;
const template = promptTemplates.find(t => t.evalType === taskType);
if (template && template.defaultDimensions) {
setDimensions(template.defaultDimensions.map((dim: any, index: number) => ({
key: `dim-${index}`,
dimension: dim.dimension,
description: dim.description
})));
}
}, [taskType, promptTemplates]);
const fetchDatasets = async () => {
try {
const { data } = await queryDatasetsUsingGet({ page: 1, size: 1000 });
setDatasets(data.content.map(mapDataset) || []);
} catch (error) {
console.error('Error fetching datasets:', error);
message.error('获取数据集列表失败');
}
};
const fetchModels = async () => {
try {
const { data } = await queryModelListUsingGet({ page: 0, size: 1000 });
setModels(data.content || []);
} catch (error) {
console.error('Error fetching models:', error);
message.error('获取模型列表失败');
}
};
const formatDimensionsForPrompt = (dimensions: Dimension[]) => {
let result = "";
dimensions.forEach((dim, index) => {
if (index > 0) {
result += "\n";
}
result += `### ${index + 1}. ${dim.dimension}\n**评估标准:**\n${dim.description}`;
if (index < dimensions.length - 1) {
result += "\n";
}
});
return result;
};
const formatResultExample = (dimensions: Dimension[]) => {
let result = "";
dimensions.forEach((dim, index) => {
if (index > 0) {
result += "\n ";
}
result += `"${dim.dimension}": "Y"`;
if (index < dimensions.length - 1) {
result += ",";
}
});
return result;
};
const fetchPromptTemplates = async () => {
try {
const response = await queryPromptTemplatesUsingGet();
const templates: PromptTemplate[] = response.data?.templates || [];
setPromptTemplates(templates);
// if a template exists for current taskType, initialize dimensions (handled also by useEffect)
const template = templates.find(t => t.evalType === taskType);
if (template) {
setDimensions(template.defaultDimensions.map((dim: any, index: number) => ({
key: `dim-${index}`,
dimension: dim.dimension,
description: dim.description
})));
}
} catch (error) {
console.error('Error fetching prompt templates:', error);
message.error('获取评估维度失败');
}
};
const generateEvaluationPrompt = () => {
if (dimensions.length === 0) {
message.warning('请先添加评估维度');
return;
}
const template = promptTemplates.find(t => t.evalType === taskType);
const basePrompt = template?.prompt || '';
const filled = basePrompt
.replace('{dimensions}', formatDimensionsForPrompt(dimensions))
.replace('{result_example}', formatResultExample(dimensions));
setEvaluationPrompt(filled);
setPreviewVisible(true);
};
const chatModelOptions = models
.filter((model) => model.type === "CHAT")
.map((model) => ({
label: `${model.modelName} (${model.provider})`,
value: model.id,
}));
const handleSubmit = async (values: any) => {
if (dimensions.length === 0) {
message.warning('请至少添加一个评估维度');
return;
}
try {
setLoading(true);
const { datasetId, modelId, ...rest } = values;
const selectedDataset = datasets.find(d => d.id === datasetId);
const selectedModel = models.find(d => d.id === modelId);
const payload = {
...rest,
sourceType: 'DATASET',
sourceId: datasetId,
sourceName: selectedDataset?.name,
evalConfig: {
modelId: selectedModel?.id,
dimensions: dimensions.map(d => ({
dimension: d.dimension,
description: d.description
}))
}
};
await createEvaluationTaskUsingPost(payload);
message.success('评估任务创建成功');
onSuccess();
form.resetFields();
onCancel();
} catch (error: any) {
console.error('Error creating task:', error);
message.error(error.response?.data?.message || '创建评估任务失败');
} finally {
setLoading(false);
}
};
const columns = [
{
title: '维度',
dataIndex: 'dimension',
key: 'dimension',
width: '30%',
},
{
title: '描述',
dataIndex: 'description',
key: 'description',
width: '60%',
},
{
title: '操作',
key: 'action',
width: '10%',
render: (_: any, record: any) => (
<Space size="middle">
<a
onClick={() => handleDeleteDimension(record.key)}
style={{ color: dimensions.length <= 1 ? '#ccc' : '#ff4d4f' }}
className={dimensions.length <= 1 ? 'disabled-link' : ''}
>
</a>
</Space>
),
},
];
return (
<Modal
title="创建评估任务"
open={visible}
onCancel={onCancel}
footer={null}
width={800}
destroyOnClose
>
<Form
form={form}
layout="vertical"
onFinish={handleSubmit}
initialValues={{
evalMethod: DEFAULT_EVAL_METHOD,
taskType: DEFAULT_TASK_TYPE,
}}
onValuesChange={(changed) => {
if (changed.taskType) {
setTaskType(changed.taskType);
setEvaluationPrompt('');
setPreviewVisible(false);
}
}}
>
<Row gutter={16}>
<Col span={12}>
<Form.Item
label="任务名称"
name="name"
rules={[{ required: true, message: '请输入任务名称' }]}
>
<Input placeholder="输入任务名称" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="任务类型"
name="taskType"
rules={[{ required: true, message: '请选择任务类型' }]}
>
<Select options={TASK_TYPES} />
</Form.Item>
</Col>
</Row>
<Form.Item
label="任务描述"
name="description"
>
<Input.TextArea placeholder="输入任务描述(可选)" rows={3} />
</Form.Item>
<Row gutter={16}>
<Col span={12}>
<Form.Item
label="选择数据集"
name="datasetId"
rules={[{ required: true, message: '请选择数据集' }]}
>
<Select
placeholder="请选择要评估的数据集"
showSearch
optionFilterProp="label"
>
{datasets.map((dataset) => (
<Select.Option key={dataset.id} value={dataset.id} label={dataset.name}>
<div className="flex justify-between w-full">
<span>{dataset.name}</span>
<span className="text-gray-500">{dataset.size}</span>
</div>
</Select.Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="评估方式"
name="evalMethod"
initialValue={DEFAULT_EVAL_METHOD}
>
<Select options={EVAL_METHODS} />
</Form.Item>
</Col>
</Row>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) =>
prevValues.evalMethod !== currentValues.evalMethod
}
>
{({ getFieldValue }) => getFieldValue('evalMethod') === 'AUTO' && (
<>
<Form.Item
label="评估模型"
name="modelId"
rules={[{ required: true, message: '请选择评估模型' }]}
>
<Select
placeholder="请选择模型"
options={chatModelOptions}
showSearch
optionFilterProp="label"
/>
</Form.Item>
<Form.Item label="评估维度">
<Table
columns={columns}
dataSource={dimensions}
pagination={false}
size="small"
rowKey="key"
/>
<div style={{ display: 'flex', gap: 8, marginBottom: 16 }}>
<Input
placeholder="输入维度名称"
value={newDimension.dimension}
onChange={(e) => setNewDimension({...newDimension, dimension: e.target.value})}
style={{ flex: 1 }}
/>
<Input
placeholder="输入维度描述"
value={newDimension.description}
onChange={(e) => setNewDimension({...newDimension, description: e.target.value})}
style={{ flex: 2 }}
/>
<Button
type="primary"
onClick={handleAddDimension}
disabled={!newDimension.dimension.trim()}
>
</Button>
</div>
</Form.Item>
</>
)}
</Form.Item>
<Form.Item>
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '16px' }}>
<Button
type="link"
icon={<EyeOutlined />}
onClick={generateEvaluationPrompt}
>
</Button>
</div>
</Form.Item>
<Form.Item>
<div style={{ textAlign: 'right' }}>
<Button onClick={onCancel} style={{ marginRight: 8 }}>
</Button>
<Button type="primary" htmlType="submit" loading={loading}>
</Button>
</div>
</Form.Item>
</Form>
<PreviewPromptModal
previewVisible={previewVisible}
onCancel={() => setPreviewVisible(false)}
evaluationPrompt={evaluationPrompt}
/>
</Modal>
);
};
export default CreateTaskModal;
import React, { useState, useEffect } from 'react';
import { Button, Form, Input, Select, message, Modal, Row, Col, Table, Space } from 'antd';
import { EyeOutlined } from '@ant-design/icons';
import { queryDatasetsUsingGet } from "@/pages/DataManagement/dataset.api.ts";
import { mapDataset } from "@/pages/DataManagement/dataset.const.tsx";
import { queryModelListUsingGet } from "@/pages/SettingsPage/settings.apis.ts";
import { ModelI } from "@/pages/SettingsPage/ModelAccess.tsx";
import { createEvaluationTaskUsingPost } from "@/pages/DataEvaluation/evaluation.api.ts";
import { queryPromptTemplatesUsingGet } from "@/pages/DataEvaluation/evaluation.api.ts";
import PreviewPromptModal from "@/pages/DataEvaluation/Create/PreviewPrompt.tsx";
import { EVAL_METHODS, TASK_TYPES } from "@/pages/DataEvaluation/evaluation.const.tsx";
interface Dataset {
id: string;
name: string;
fileCount: number;
size: string;
}
interface Dimension {
key: string;
dimension: string;
description: string;
}
interface PromptTemplate {
evalType: string;
prompt: string;
defaultDimensions: Dimension[];
}
interface CreateTaskModalProps {
visible: boolean;
onCancel: () => void;
onSuccess: () => void;
}
const DEFAULT_EVAL_METHOD = 'AUTO';
const DEFAULT_TASK_TYPE = 'QA';
const CreateTaskModal: React.FC<CreateTaskModalProps> = ({ visible, onCancel, onSuccess }) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [datasets, setDatasets] = useState<Dataset[]>([]);
const [models, setModels] = useState<ModelI[]>([]);
const [dimensions, setDimensions] = useState<Dimension[]>([]);
const [newDimension, setNewDimension] = useState<Omit<Dimension, 'key'>>({
dimension: '',
description: ''
});
const [taskType, setTaskType] = useState<string>(DEFAULT_TASK_TYPE);
const [promptTemplates, setPromptTemplates] = useState<PromptTemplate[]>([]);
const [previewVisible, setPreviewVisible] = useState(false);
const [evaluationPrompt, setEvaluationPrompt] = useState('');
const handleAddDimension = () => {
if (!newDimension.dimension.trim()) {
message.warning('请输入维度名称');
return;
}
setDimensions([...dimensions, { ...newDimension, key: `dim-${Date.now()}` }]);
setNewDimension({ dimension: '', description: '' });
};
const handleDeleteDimension = (key: string) => {
if (dimensions.length <= 1) {
message.warning('至少需要保留一个评估维度');
return;
}
setDimensions(dimensions.filter(item => item.key !== key));
};
useEffect(() => {
if (visible) {
fetchDatasets().then();
fetchModels().then();
fetchPromptTemplates().then();
// sync form with local taskType default
form.setFieldsValue({ taskType: DEFAULT_TASK_TYPE });
}
}, [visible]);
// when promptTemplates or taskType change, switch dimensions to template defaults (COT/QA)
useEffect(() => {
if (!promptTemplates || promptTemplates.length === 0) return;
const template = promptTemplates.find(t => t.evalType === taskType);
if (template && template.defaultDimensions) {
setDimensions(template.defaultDimensions.map((dim: any, index: number) => ({
key: `dim-${index}`,
dimension: dim.dimension,
description: dim.description
})));
}
}, [taskType, promptTemplates]);
const fetchDatasets = async () => {
try {
const { data } = await queryDatasetsUsingGet({ page: 1, size: 1000 });
setDatasets(data.content.map(mapDataset) || []);
} catch (error) {
console.error('Error fetching datasets:', error);
message.error('获取数据集列表失败');
}
};
const fetchModels = async () => {
try {
const { data } = await queryModelListUsingGet({ page: 0, size: 1000 });
setModels(data.content || []);
} catch (error) {
console.error('Error fetching models:', error);
message.error('获取模型列表失败');
}
};
const formatDimensionsForPrompt = (dimensions: Dimension[]) => {
let result = "";
dimensions.forEach((dim, index) => {
if (index > 0) {
result += "\n";
}
result += `### ${index + 1}. ${dim.dimension}\n**评估标准:**\n${dim.description}`;
if (index < dimensions.length - 1) {
result += "\n";
}
});
return result;
};
const formatResultExample = (dimensions: Dimension[]) => {
let result = "";
dimensions.forEach((dim, index) => {
if (index > 0) {
result += "\n ";
}
result += `"${dim.dimension}": "Y"`;
if (index < dimensions.length - 1) {
result += ",";
}
});
return result;
};
const fetchPromptTemplates = async () => {
try {
const response = await queryPromptTemplatesUsingGet();
const templates: PromptTemplate[] = response.data?.templates || [];
setPromptTemplates(templates);
// if a template exists for current taskType, initialize dimensions (handled also by useEffect)
const template = templates.find(t => t.evalType === taskType);
if (template) {
setDimensions(template.defaultDimensions.map((dim: any, index: number) => ({
key: `dim-${index}`,
dimension: dim.dimension,
description: dim.description
})));
}
} catch (error) {
console.error('Error fetching prompt templates:', error);
message.error('获取评估维度失败');
}
};
const generateEvaluationPrompt = () => {
if (dimensions.length === 0) {
message.warning('请先添加评估维度');
return;
}
const template = promptTemplates.find(t => t.evalType === taskType);
const basePrompt = template?.prompt || '';
const filled = basePrompt
.replace('{dimensions}', formatDimensionsForPrompt(dimensions))
.replace('{result_example}', formatResultExample(dimensions));
setEvaluationPrompt(filled);
setPreviewVisible(true);
};
const chatModelOptions = models
.filter((model) => model.type === "CHAT")
.map((model) => ({
label: `${model.modelName} (${model.provider})`,
value: model.id,
}));
const handleSubmit = async (values: any) => {
if (dimensions.length === 0) {
message.warning('请至少添加一个评估维度');
return;
}
try {
setLoading(true);
const { datasetId, modelId, ...rest } = values;
const selectedDataset = datasets.find(d => d.id === datasetId);
const selectedModel = models.find(d => d.id === modelId);
const payload = {
...rest,
sourceType: 'DATASET',
sourceId: datasetId,
sourceName: selectedDataset?.name,
evalConfig: {
modelId: selectedModel?.id,
dimensions: dimensions.map(d => ({
dimension: d.dimension,
description: d.description
}))
}
};
await createEvaluationTaskUsingPost(payload);
message.success('评估任务创建成功');
onSuccess();
form.resetFields();
onCancel();
} catch (error: any) {
console.error('Error creating task:', error);
message.error(error.response?.data?.message || '创建评估任务失败');
} finally {
setLoading(false);
}
};
const columns = [
{
title: '维度',
dataIndex: 'dimension',
key: 'dimension',
width: '30%',
},
{
title: '描述',
dataIndex: 'description',
key: 'description',
width: '60%',
},
{
title: '操作',
key: 'action',
width: '10%',
render: (_: any, record: any) => (
<Space size="middle">
<a
onClick={() => handleDeleteDimension(record.key)}
style={{ color: dimensions.length <= 1 ? '#ccc' : '#ff4d4f' }}
className={dimensions.length <= 1 ? 'disabled-link' : ''}
>
</a>
</Space>
),
},
];
return (
<Modal
title="创建评估任务"
open={visible}
onCancel={onCancel}
footer={null}
width={800}
destroyOnClose
>
<Form
form={form}
layout="vertical"
onFinish={handleSubmit}
initialValues={{
evalMethod: DEFAULT_EVAL_METHOD,
taskType: DEFAULT_TASK_TYPE,
}}
onValuesChange={(changed) => {
if (changed.taskType) {
setTaskType(changed.taskType);
setEvaluationPrompt('');
setPreviewVisible(false);
}
}}
>
<Row gutter={16}>
<Col span={12}>
<Form.Item
label="任务名称"
name="name"
rules={[{ required: true, message: '请输入任务名称' }]}
>
<Input placeholder="输入任务名称" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="任务类型"
name="taskType"
rules={[{ required: true, message: '请选择任务类型' }]}
>
<Select options={TASK_TYPES} />
</Form.Item>
</Col>
</Row>
<Form.Item
label="任务描述"
name="description"
>
<Input.TextArea placeholder="输入任务描述(可选)" rows={3} />
</Form.Item>
<Row gutter={16}>
<Col span={12}>
<Form.Item
label="选择数据集"
name="datasetId"
rules={[{ required: true, message: '请选择数据集' }]}
>
<Select
placeholder="请选择要评估的数据集"
showSearch
optionFilterProp="label"
>
{datasets.map((dataset) => (
<Select.Option key={dataset.id} value={dataset.id} label={dataset.name}>
<div className="flex justify-between w-full">
<span>{dataset.name}</span>
<span className="text-gray-500">{dataset.size}</span>
</div>
</Select.Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="评估方式"
name="evalMethod"
initialValue={DEFAULT_EVAL_METHOD}
>
<Select options={EVAL_METHODS} />
</Form.Item>
</Col>
</Row>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) =>
prevValues.evalMethod !== currentValues.evalMethod
}
>
{({ getFieldValue }) => getFieldValue('evalMethod') === 'AUTO' && (
<>
<Form.Item
label="评估模型"
name="modelId"
rules={[{ required: true, message: '请选择评估模型' }]}
>
<Select
placeholder="请选择模型"
options={chatModelOptions}
showSearch
optionFilterProp="label"
/>
</Form.Item>
<Form.Item label="评估维度">
<Table
columns={columns}
dataSource={dimensions}
pagination={false}
size="small"
rowKey="key"
/>
<div style={{ display: 'flex', gap: 8, marginBottom: 16 }}>
<Input
placeholder="输入维度名称"
value={newDimension.dimension}
onChange={(e) => setNewDimension({...newDimension, dimension: e.target.value})}
style={{ flex: 1 }}
/>
<Input
placeholder="输入维度描述"
value={newDimension.description}
onChange={(e) => setNewDimension({...newDimension, description: e.target.value})}
style={{ flex: 2 }}
/>
<Button
type="primary"
onClick={handleAddDimension}
disabled={!newDimension.dimension.trim()}
>
</Button>
</div>
</Form.Item>
</>
)}
</Form.Item>
<Form.Item>
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '16px' }}>
<Button
type="link"
icon={<EyeOutlined />}
onClick={generateEvaluationPrompt}
>
</Button>
</div>
</Form.Item>
<Form.Item>
<div style={{ textAlign: 'right' }}>
<Button onClick={onCancel} style={{ marginRight: 8 }}>
</Button>
<Button type="primary" htmlType="submit" loading={loading}>
</Button>
</div>
</Form.Item>
</Form>
<PreviewPromptModal
previewVisible={previewVisible}
onCancel={() => setPreviewVisible(false)}
evaluationPrompt={evaluationPrompt}
/>
</Modal>
);
};
export default CreateTaskModal;

View File

@@ -1,42 +1,42 @@
import React from 'react';
import { Button, message, Modal } from 'antd';
interface PreviewPromptModalProps {
previewVisible: boolean;
onCancel: () => void;
evaluationPrompt: string;
}
const PreviewPromptModal: React.FC<PreviewPromptModalProps> = ({ previewVisible, onCancel, evaluationPrompt }) => {
return (
<Modal
title="评估提示词预览"
open={previewVisible}
onCancel={onCancel}
footer={[
<Button key="copy" onClick={() => {
navigator.clipboard.writeText(evaluationPrompt).then();
message.success('已复制到剪贴板');
}}>
</Button>,
<Button key="close" type="primary" onClick={onCancel}>
</Button>
]}
width={800}
>
<div style={{
background: '#f5f5f5',
padding: '16px',
borderRadius: '4px',
whiteSpace: 'pre-wrap',
fontFamily: 'monospace'
}}>
{evaluationPrompt}
</div>
</Modal>
)
}
export default PreviewPromptModal;
import React from 'react';
import { Button, message, Modal } from 'antd';
interface PreviewPromptModalProps {
previewVisible: boolean;
onCancel: () => void;
evaluationPrompt: string;
}
const PreviewPromptModal: React.FC<PreviewPromptModalProps> = ({ previewVisible, onCancel, evaluationPrompt }) => {
return (
<Modal
title="评估提示词预览"
open={previewVisible}
onCancel={onCancel}
footer={[
<Button key="copy" onClick={() => {
navigator.clipboard.writeText(evaluationPrompt).then();
message.success('已复制到剪贴板');
}}>
</Button>,
<Button key="close" type="primary" onClick={onCancel}>
</Button>
]}
width={800}
>
<div style={{
background: '#f5f5f5',
padding: '16px',
borderRadius: '4px',
whiteSpace: 'pre-wrap',
fontFamily: 'monospace'
}}>
{evaluationPrompt}
</div>
</Modal>
)
}
export default PreviewPromptModal;

View File

@@ -1,152 +1,152 @@
import { Link, useParams } from "react-router";
import { Tabs, Spin, message, Breadcrumb } from 'antd';
import { LayoutList, Clock } from "lucide-react";
import { useEffect, useState } from 'react';
import { getEvaluationTaskByIdUsingGet, queryEvaluationItemsUsingGet } from '../evaluation.api';
import { EvaluationTask, EvaluationStatus } from '../evaluation.model';
import DetailHeader from "@/components/DetailHeader.tsx";
import {TaskStatusMap} from "@/pages/DataCleansing/cleansing.const.tsx";
import EvaluationItems from "@/pages/DataEvaluation/Detail/components/EvaluationItems.tsx";
import Overview from "@/pages/DataEvaluation/Detail/components/Overview.tsx";
const tabList = [
{
key: "overview",
label: "概览",
},
{
key: "evaluationItems",
label: "评估详情",
}
];
interface EvaluationItem {
id: string;
content: string;
status: EvaluationStatus;
score?: number;
dimensions: {
id: string;
name: string;
score: number;
}[];
createdAt: string;
}
const EvaluationDetailPage: React.FC = () => {
const { id } = useParams<{ id: string }>();
const [loading, setLoading] = useState<boolean>(true);
const [task, setTask] = useState<EvaluationTask | null>(null);
const [items, setItems] = useState<EvaluationItem[]>([]);
const [activeTab, setActiveTab] = useState("overview");
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
total: 0,
});
const fetchTaskDetail = async () => {
try {
const response = await getEvaluationTaskByIdUsingGet(id);
setTask(response.data);
} catch (error) {
message.error('Failed to fetch task details');
console.error('Error fetching task detail:', error);
}
};
const fetchEvaluationItems = async (page = 1, pageSize = 10) => {
try {
const response = await queryEvaluationItemsUsingGet({
taskId: id,
page: page,
size: pageSize,
});
setItems(response.data.content || []);
setPagination({
...pagination,
current: page,
total: response.data.totalElements || 0,
});
} catch (error) {
message.error('Failed to fetch evaluation items');
console.error('Error fetching evaluation items:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
if (id) {
setLoading(true);
Promise.all([
fetchTaskDetail(),
fetchEvaluationItems(1, pagination.pageSize),
]).finally(() => setLoading(false));
}
}, [id]);
if (loading && !task) {
return (
<div style={{ textAlign: 'center', padding: '100px 0' }}>
<Spin size="large" />
</div>
);
}
if (!task) {
return <div>Task not found</div>;
}
const breadItems = [
{
title: <Link to="/data/evaluation"></Link>,
},
{
title: "数据评估详情",
},
];
const headerData = {
...task,
icon: <LayoutList className="w-8 h-8" />,
status: TaskStatusMap[task?.status],
createdAt: task?.createdAt,
lastUpdated: task?.updatedAt,
};
// 基本信息描述项
const statistics = [
{
icon: <Clock className="text-blue-400 w-4 h-4" />,
key: "time",
value: task?.updatedAt,
},
];
const operations = []
return (
<>
<Breadcrumb items={breadItems} />
<div className="mb-4 mt-4">
<div className="mb-4 mt-4">
<DetailHeader
data={headerData}
statistics={statistics}
operations={operations}
/>
</div>
</div>
<div className="flex-overflow-auto p-6 pt-2 bg-white rounded-md shadow">
<Tabs activeKey={activeTab} items={tabList} onChange={setActiveTab} />
<div className="h-full overflow-auto">
{activeTab === "overview" && <Overview task={task} />}
{activeTab === "evaluationItems" && <EvaluationItems task={task} />}
</div>
</div>
</>
);
};
export default EvaluationDetailPage;
import { Link, useParams } from "react-router";
import { Tabs, Spin, message, Breadcrumb } from 'antd';
import { LayoutList, Clock } from "lucide-react";
import { useEffect, useState } from 'react';
import { getEvaluationTaskByIdUsingGet, queryEvaluationItemsUsingGet } from '../evaluation.api';
import { EvaluationTask, EvaluationStatus } from '../evaluation.model';
import DetailHeader from "@/components/DetailHeader.tsx";
import {TaskStatusMap} from "@/pages/DataCleansing/cleansing.const.tsx";
import EvaluationItems from "@/pages/DataEvaluation/Detail/components/EvaluationItems.tsx";
import Overview from "@/pages/DataEvaluation/Detail/components/Overview.tsx";
const tabList = [
{
key: "overview",
label: "概览",
},
{
key: "evaluationItems",
label: "评估详情",
}
];
interface EvaluationItem {
id: string;
content: string;
status: EvaluationStatus;
score?: number;
dimensions: {
id: string;
name: string;
score: number;
}[];
createdAt: string;
}
const EvaluationDetailPage: React.FC = () => {
const { id } = useParams<{ id: string }>();
const [loading, setLoading] = useState<boolean>(true);
const [task, setTask] = useState<EvaluationTask | null>(null);
const [items, setItems] = useState<EvaluationItem[]>([]);
const [activeTab, setActiveTab] = useState("overview");
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
total: 0,
});
const fetchTaskDetail = async () => {
try {
const response = await getEvaluationTaskByIdUsingGet(id);
setTask(response.data);
} catch (error) {
message.error('Failed to fetch task details');
console.error('Error fetching task detail:', error);
}
};
const fetchEvaluationItems = async (page = 1, pageSize = 10) => {
try {
const response = await queryEvaluationItemsUsingGet({
taskId: id,
page: page,
size: pageSize,
});
setItems(response.data.content || []);
setPagination({
...pagination,
current: page,
total: response.data.totalElements || 0,
});
} catch (error) {
message.error('Failed to fetch evaluation items');
console.error('Error fetching evaluation items:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
if (id) {
setLoading(true);
Promise.all([
fetchTaskDetail(),
fetchEvaluationItems(1, pagination.pageSize),
]).finally(() => setLoading(false));
}
}, [id]);
if (loading && !task) {
return (
<div style={{ textAlign: 'center', padding: '100px 0' }}>
<Spin size="large" />
</div>
);
}
if (!task) {
return <div>Task not found</div>;
}
const breadItems = [
{
title: <Link to="/data/evaluation"></Link>,
},
{
title: "数据评估详情",
},
];
const headerData = {
...task,
icon: <LayoutList className="w-8 h-8" />,
status: TaskStatusMap[task?.status],
createdAt: task?.createdAt,
lastUpdated: task?.updatedAt,
};
// 基本信息描述项
const statistics = [
{
icon: <Clock className="text-blue-400 w-4 h-4" />,
key: "time",
value: task?.updatedAt,
},
];
const operations = []
return (
<>
<Breadcrumb items={breadItems} />
<div className="mb-4 mt-4">
<div className="mb-4 mt-4">
<DetailHeader
data={headerData}
statistics={statistics}
operations={operations}
/>
</div>
</div>
<div className="flex-overflow-auto p-6 pt-2 bg-white rounded-md shadow">
<Tabs activeKey={activeTab} items={tabList} onChange={setActiveTab} />
<div className="h-full overflow-auto">
{activeTab === "overview" && <Overview task={task} />}
{activeTab === "evaluationItems" && <EvaluationItems task={task} />}
</div>
</div>
</>
);
};
export default EvaluationDetailPage;

View File

@@ -1,257 +1,257 @@
import { useEffect, useState } from 'react';
import { Table, Typography, Button, Space, Empty, Tooltip } from 'antd';
import { FolderOpen, FileText, ArrowLeft } from 'lucide-react';
import { queryEvaluationFilesUsingGet, queryEvaluationItemsUsingGet } from '../../evaluation.api';
import useFetchData from '@/hooks/useFetchData';
const { Text } = Typography;
const COLUMN_WIDTH = 520;
const MONO_FONT = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
const codeBlockStyle = {
fontFamily: MONO_FONT,
fontSize: 12,
lineHeight: '20px',
color: '#334155',
backgroundColor: '#f8fafc',
border: '1px solid #f0f0f0',
borderRadius: 6,
padding: 8,
} as const;
type EvalFile = {
taskId: string;
fileId: string;
fileName: string;
totalCount: number;
evaluatedCount: number;
pendingCount: number;
};
type EvalItem = {
id: string;
taskId: string;
itemId: string;
fileId: string;
evalContent: any;
evalScore?: number | null;
evalResult: any;
status?: string;
};
export default function EvaluationItems({ task }: { task: any }) {
const [selectedFile, setSelectedFile] = useState<{ fileId: string; fileName: string } | null>(null);
// 文件列表数据(使用 useFetchData),pageOffset=0 表示后端分页为 1 基
const {
loading: loadingFiles,
tableData: files,
pagination: filePagination,
setSearchParams: setFileSearchParams,
} = useFetchData<EvalFile>(
(params) => queryEvaluationFilesUsingGet({ taskId: task?.id, ...params }),
(d) => d as unknown as EvalFile,
30000,
false,
[],
0
);
// 评估条目数据(使用 useFetchData),依赖选中文件
const {
loading: loadingItems,
tableData: items,
pagination: itemPagination,
setSearchParams: setItemSearchParams,
fetchData: fetchItems,
} = useFetchData<EvalItem>(
(params) => {
if (!task?.id || !selectedFile?.fileId) {
return Promise.resolve({ data: { content: [], totalElements: 0 } });
}
return queryEvaluationItemsUsingGet({ taskId: task.id, file_id: selectedFile.fileId, ...params });
},
(d) => d as unknown as EvalItem,
30000,
false,
[],
0
);
// 当选择文件变化时,主动触发一次条目查询,避免仅依赖 searchParams 变更导致未触发
useEffect(() => {
if (task?.id && selectedFile?.fileId) {
setItemSearchParams((prev: any) => ({ ...prev, current: 1 }));
// 立即拉取一次,保证点击后立刻出现数据
fetchItems();
}
}, [task?.id, selectedFile?.fileId]);
const fileColumns = [
{
title: '文件名',
dataIndex: 'fileName',
key: 'fileName',
render: (_: any, record: EvalFile) => (
<Space onClick={(e) => { e.stopPropagation(); setSelectedFile({ fileId: record.fileId, fileName: record.fileName }); }} style={{ cursor: 'pointer' }}>
<FolderOpen size={16} />
<Button type="link" style={{ padding: 0 }}>{record.fileName}</Button>
</Space>
),
},
{
title: '总条目',
dataIndex: 'totalCount',
key: 'totalCount',
width: 120,
},
{
title: '已评估',
dataIndex: 'evaluatedCount',
key: 'evaluatedCount',
width: 120,
},
{
title: '待评估',
dataIndex: 'pendingCount',
key: 'pendingCount',
width: 120,
},
];
const renderEvalObject = (rec: EvalItem) => {
const c = rec.evalContent;
let jsonString = '';
try {
if (typeof c === 'string') {
// 尝试将字符串解析为 JSON,失败则按原字符串显示
try {
jsonString = JSON.stringify(JSON.parse(c), null, 2);
} catch {
jsonString = JSON.stringify({ value: c }, null, 2);
}
} else {
jsonString = JSON.stringify(c, null, 2);
}
} catch {
jsonString = 'null';
}
return (
<Tooltip
color="#fff"
title={<pre style={{ ...codeBlockStyle, margin: 0, maxWidth: COLUMN_WIDTH, whiteSpace: 'pre-wrap' }}>{jsonString}</pre>}
overlayInnerStyle={{ maxHeight: 600, overflow: 'auto', width: COLUMN_WIDTH }}
>
<Typography.Paragraph
style={{ margin: 0, whiteSpace: 'pre-wrap', fontFamily: MONO_FONT, fontSize: 12, lineHeight: '20px', color: '#334155' }}
ellipsis={{ rows: 6 }}
>
<pre style={{ ...codeBlockStyle, whiteSpace: 'pre-wrap', margin: 0 }}>{jsonString}</pre>
</Typography.Paragraph>
</Tooltip>
);
};
const renderEvalResult = (rec: EvalItem) => {
const r = rec.evalResult;
let jsonString = '';
try {
if (typeof r === 'string') {
try {
jsonString = JSON.stringify(JSON.parse(r), null, 2);
} catch {
jsonString = JSON.stringify({ value: r, score: rec.evalScore ?? undefined }, null, 2);
}
} else {
const withScore = rec.evalScore !== undefined && rec.evalScore !== null ? { ...r, evalScore: rec.evalScore } : r;
jsonString = JSON.stringify(withScore, null, 2);
}
} catch {
jsonString = 'null';
}
// 判空展示未评估
const isEmpty = !r || (typeof r === 'string' && r.trim() === '') || (typeof r === 'object' && r !== null && Object.keys(r).length === 0);
if (isEmpty) {
return <Text type="secondary"></Text>;
}
return (
<Tooltip
color="#fff"
title={<pre style={{ ...codeBlockStyle, margin: 0, maxWidth: 800, whiteSpace: 'pre-wrap' }}>{jsonString}</pre>}
overlayInnerStyle={{ maxHeight: 600, overflow: 'auto' }}
>
<Typography.Paragraph
style={{ margin: 0, whiteSpace: 'pre-wrap', fontFamily: MONO_FONT, fontSize: 12, lineHeight: '20px', color: '#334155' }}
ellipsis={{ rows: 6 }}
>
<pre style={{ ...codeBlockStyle, whiteSpace: 'pre-wrap', margin: 0 }}>{jsonString}</pre>
</Typography.Paragraph>
</Tooltip>
);
};
const itemColumns = [
{
title: '评估对象',
dataIndex: 'evalContent',
key: 'evalContent',
render: (_: any, record: EvalItem) => renderEvalObject(record),
width: COLUMN_WIDTH,
},
{
title: '评估结果',
dataIndex: 'evalResult',
key: 'evalResult',
render: (_: any, record: EvalItem) => renderEvalResult(record),
width: COLUMN_WIDTH,
},
];
if (!task?.id) return <Empty description="任务不存在" />;
return (
<div className="flex flex-col gap-4">
{!selectedFile ? (
<Table
rowKey={(r: EvalFile) => r.fileId}
columns={fileColumns}
dataSource={files}
loading={loadingFiles}
size="middle"
onRow={(record) => ({
onClick: () => {
setSelectedFile({ fileId: record.fileId, fileName: record.fileName });
// 切换文件时,重置条目表到第一页
setItemSearchParams((prev: any) => ({ ...prev, current: 1 }));
},
})}
pagination={filePagination}
/>
) : (
<div className="flex flex-col gap-3">
<div className="sticky top-0 z-10 bg-white py-2" style={{ borderBottom: '1px solid #f0f0f0' }}>
<Space wrap>
<Button icon={<ArrowLeft size={16} />} onClick={() => { setSelectedFile(null); }}>
</Button>
<Space>
<FileText size={16} />
<Text strong>{selectedFile.fileName}</Text>
<Text type="secondary">ID{selectedFile.fileId}</Text>
<Text type="secondary"> {itemPagination.total} </Text>
</Space>
</Space>
</div>
<Table
rowKey={(r: EvalItem) => r.id}
columns={itemColumns}
dataSource={items}
loading={loadingItems}
size="middle"
pagination={itemPagination}
/>
</div>
)}
</div>
);
}
import { useEffect, useState } from 'react';
import { Table, Typography, Button, Space, Empty, Tooltip } from 'antd';
import { FolderOpen, FileText, ArrowLeft } from 'lucide-react';
import { queryEvaluationFilesUsingGet, queryEvaluationItemsUsingGet } from '../../evaluation.api';
import useFetchData from '@/hooks/useFetchData';
const { Text } = Typography;
const COLUMN_WIDTH = 520;
const MONO_FONT = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
const codeBlockStyle = {
fontFamily: MONO_FONT,
fontSize: 12,
lineHeight: '20px',
color: '#334155',
backgroundColor: '#f8fafc',
border: '1px solid #f0f0f0',
borderRadius: 6,
padding: 8,
} as const;
type EvalFile = {
taskId: string;
fileId: string;
fileName: string;
totalCount: number;
evaluatedCount: number;
pendingCount: number;
};
type EvalItem = {
id: string;
taskId: string;
itemId: string;
fileId: string;
evalContent: any;
evalScore?: number | null;
evalResult: any;
status?: string;
};
export default function EvaluationItems({ task }: { task: any }) {
const [selectedFile, setSelectedFile] = useState<{ fileId: string; fileName: string } | null>(null);
// 文件列表数据(使用 useFetchData),pageOffset=0 表示后端分页为 1 基
const {
loading: loadingFiles,
tableData: files,
pagination: filePagination,
setSearchParams: setFileSearchParams,
} = useFetchData<EvalFile>(
(params) => queryEvaluationFilesUsingGet({ taskId: task?.id, ...params }),
(d) => d as unknown as EvalFile,
30000,
false,
[],
0
);
// 评估条目数据(使用 useFetchData),依赖选中文件
const {
loading: loadingItems,
tableData: items,
pagination: itemPagination,
setSearchParams: setItemSearchParams,
fetchData: fetchItems,
} = useFetchData<EvalItem>(
(params) => {
if (!task?.id || !selectedFile?.fileId) {
return Promise.resolve({ data: { content: [], totalElements: 0 } });
}
return queryEvaluationItemsUsingGet({ taskId: task.id, file_id: selectedFile.fileId, ...params });
},
(d) => d as unknown as EvalItem,
30000,
false,
[],
0
);
// 当选择文件变化时,主动触发一次条目查询,避免仅依赖 searchParams 变更导致未触发
useEffect(() => {
if (task?.id && selectedFile?.fileId) {
setItemSearchParams((prev: any) => ({ ...prev, current: 1 }));
// 立即拉取一次,保证点击后立刻出现数据
fetchItems();
}
}, [task?.id, selectedFile?.fileId]);
const fileColumns = [
{
title: '文件名',
dataIndex: 'fileName',
key: 'fileName',
render: (_: any, record: EvalFile) => (
<Space onClick={(e) => { e.stopPropagation(); setSelectedFile({ fileId: record.fileId, fileName: record.fileName }); }} style={{ cursor: 'pointer' }}>
<FolderOpen size={16} />
<Button type="link" style={{ padding: 0 }}>{record.fileName}</Button>
</Space>
),
},
{
title: '总条目',
dataIndex: 'totalCount',
key: 'totalCount',
width: 120,
},
{
title: '已评估',
dataIndex: 'evaluatedCount',
key: 'evaluatedCount',
width: 120,
},
{
title: '待评估',
dataIndex: 'pendingCount',
key: 'pendingCount',
width: 120,
},
];
const renderEvalObject = (rec: EvalItem) => {
const c = rec.evalContent;
let jsonString = '';
try {
if (typeof c === 'string') {
// 尝试将字符串解析为 JSON,失败则按原字符串显示
try {
jsonString = JSON.stringify(JSON.parse(c), null, 2);
} catch {
jsonString = JSON.stringify({ value: c }, null, 2);
}
} else {
jsonString = JSON.stringify(c, null, 2);
}
} catch {
jsonString = 'null';
}
return (
<Tooltip
color="#fff"
title={<pre style={{ ...codeBlockStyle, margin: 0, maxWidth: COLUMN_WIDTH, whiteSpace: 'pre-wrap' }}>{jsonString}</pre>}
overlayInnerStyle={{ maxHeight: 600, overflow: 'auto', width: COLUMN_WIDTH }}
>
<Typography.Paragraph
style={{ margin: 0, whiteSpace: 'pre-wrap', fontFamily: MONO_FONT, fontSize: 12, lineHeight: '20px', color: '#334155' }}
ellipsis={{ rows: 6 }}
>
<pre style={{ ...codeBlockStyle, whiteSpace: 'pre-wrap', margin: 0 }}>{jsonString}</pre>
</Typography.Paragraph>
</Tooltip>
);
};
const renderEvalResult = (rec: EvalItem) => {
const r = rec.evalResult;
let jsonString = '';
try {
if (typeof r === 'string') {
try {
jsonString = JSON.stringify(JSON.parse(r), null, 2);
} catch {
jsonString = JSON.stringify({ value: r, score: rec.evalScore ?? undefined }, null, 2);
}
} else {
const withScore = rec.evalScore !== undefined && rec.evalScore !== null ? { ...r, evalScore: rec.evalScore } : r;
jsonString = JSON.stringify(withScore, null, 2);
}
} catch {
jsonString = 'null';
}
// 判空展示未评估
const isEmpty = !r || (typeof r === 'string' && r.trim() === '') || (typeof r === 'object' && r !== null && Object.keys(r).length === 0);
if (isEmpty) {
return <Text type="secondary"></Text>;
}
return (
<Tooltip
color="#fff"
title={<pre style={{ ...codeBlockStyle, margin: 0, maxWidth: 800, whiteSpace: 'pre-wrap' }}>{jsonString}</pre>}
overlayInnerStyle={{ maxHeight: 600, overflow: 'auto' }}
>
<Typography.Paragraph
style={{ margin: 0, whiteSpace: 'pre-wrap', fontFamily: MONO_FONT, fontSize: 12, lineHeight: '20px', color: '#334155' }}
ellipsis={{ rows: 6 }}
>
<pre style={{ ...codeBlockStyle, whiteSpace: 'pre-wrap', margin: 0 }}>{jsonString}</pre>
</Typography.Paragraph>
</Tooltip>
);
};
const itemColumns = [
{
title: '评估对象',
dataIndex: 'evalContent',
key: 'evalContent',
render: (_: any, record: EvalItem) => renderEvalObject(record),
width: COLUMN_WIDTH,
},
{
title: '评估结果',
dataIndex: 'evalResult',
key: 'evalResult',
render: (_: any, record: EvalItem) => renderEvalResult(record),
width: COLUMN_WIDTH,
},
];
if (!task?.id) return <Empty description="任务不存在" />;
return (
<div className="flex flex-col gap-4">
{!selectedFile ? (
<Table
rowKey={(r: EvalFile) => r.fileId}
columns={fileColumns}
dataSource={files}
loading={loadingFiles}
size="middle"
onRow={(record) => ({
onClick: () => {
setSelectedFile({ fileId: record.fileId, fileName: record.fileName });
// 切换文件时,重置条目表到第一页
setItemSearchParams((prev: any) => ({ ...prev, current: 1 }));
},
})}
pagination={filePagination}
/>
) : (
<div className="flex flex-col gap-3">
<div className="sticky top-0 z-10 bg-white py-2" style={{ borderBottom: '1px solid #f0f0f0' }}>
<Space wrap>
<Button icon={<ArrowLeft size={16} />} onClick={() => { setSelectedFile(null); }}>
</Button>
<Space>
<FileText size={16} />
<Text strong>{selectedFile.fileName}</Text>
<Text type="secondary">ID{selectedFile.fileId}</Text>
<Text type="secondary"> {itemPagination.total} </Text>
</Space>
</Space>
</div>
<Table
rowKey={(r: EvalItem) => r.id}
columns={itemColumns}
dataSource={items}
loading={loadingItems}
size="middle"
pagination={itemPagination}
/>
</div>
)}
</div>
);
}

View File

@@ -1,132 +1,132 @@
import { useState } from 'react';
import { Descriptions, Empty, DescriptionsProps, Table, Button } from 'antd';
import { EyeOutlined } from '@ant-design/icons';
import PreviewPromptModal from "@/pages/DataEvaluation/Create/PreviewPrompt.tsx";
import { formatDateTime } from "@/utils/unit.ts";
import { evalTaskStatusMap, getEvalMethod, getEvalType, getSource } from "@/pages/DataEvaluation/evaluation.const.tsx";
const Overview = ({ task }) => {
const [previewVisible, setPreviewVisible] = useState(false);
if (!task) {
return <Empty description="未找到评估任务信息" />;
}
const generateEvaluationPrompt = () => {
setPreviewVisible(true);
};
// 基本信息
const items: DescriptionsProps["items"] = [
{
key: "id",
label: "ID",
children: task.id,
},
{
key: "name",
label: "名称",
children: task.name,
},
{
key: "evalType",
label: "评估类型",
children: getEvalType(task.taskType),
},
{
key: "evalMethod",
label: "评估方式",
children: getEvalMethod(task.evalMethod),
},
{
key: "status",
label: "状态",
children: evalTaskStatusMap[task.status]?.label || "未知",
},
{
key: "source",
label: "评估数据",
children: getSource(task.sourceType) + task.sourceName,
},
{
key: "evalConfig.modelName",
label: "模型",
children: task.evalConfig?.modelName || task.evalConfig?.modelId,
},
{
key: "createdBy",
label: "创建者",
children: task.createdBy || "未知",
},
{
key: "createdAt",
label: "创建时间",
children: formatDateTime(task.createdAt),
},
{
key: "updatedAt",
label: "更新时间",
children: formatDateTime(task.updatedAt),
},
{
key: "description",
label: "描述",
children: task.description || "无",
},
];
const columns = [
{
title: '维度',
dataIndex: 'dimension',
key: 'dimension',
width: '30%',
},
{
title: '描述',
dataIndex: 'description',
key: 'description',
width: '60%',
},
];
return (
<>
<div className=" flex flex-col gap-4">
{/* 基本信息 */}
<Descriptions
title="基本信息"
layout="vertical"
size="small"
items={items}
column={5}
/>
<h2 className="text-base font-semibold mt-8"></h2>
<div className="overflow-x-auto">
<Table
size="middle"
rowKey="id"
columns={columns}
dataSource={task?.evalConfig?.dimensions}
scroll={{ x: "max-content", y: 600 }}
/>
</div>
<div className="flex items-center gap-2 p-3 bg-blue-50 rounded-lg border border-blue-200">
<Button
type="link"
icon={<EyeOutlined />}
onClick={generateEvaluationPrompt}
>
</Button>
</div>
<PreviewPromptModal
previewVisible={previewVisible}
onCancel={() => setPreviewVisible(false)}
evaluationPrompt={task?.evalPrompt}
/>
</div>
</>
);
};
export default Overview;
import { useState } from 'react';
import { Descriptions, Empty, DescriptionsProps, Table, Button } from 'antd';
import { EyeOutlined } from '@ant-design/icons';
import PreviewPromptModal from "@/pages/DataEvaluation/Create/PreviewPrompt.tsx";
import { formatDateTime } from "@/utils/unit.ts";
import { evalTaskStatusMap, getEvalMethod, getEvalType, getSource } from "@/pages/DataEvaluation/evaluation.const.tsx";
const Overview = ({ task }) => {
const [previewVisible, setPreviewVisible] = useState(false);
if (!task) {
return <Empty description="未找到评估任务信息" />;
}
const generateEvaluationPrompt = () => {
setPreviewVisible(true);
};
// 基本信息
const items: DescriptionsProps["items"] = [
{
key: "id",
label: "ID",
children: task.id,
},
{
key: "name",
label: "名称",
children: task.name,
},
{
key: "evalType",
label: "评估类型",
children: getEvalType(task.taskType),
},
{
key: "evalMethod",
label: "评估方式",
children: getEvalMethod(task.evalMethod),
},
{
key: "status",
label: "状态",
children: evalTaskStatusMap[task.status]?.label || "未知",
},
{
key: "source",
label: "评估数据",
children: getSource(task.sourceType) + task.sourceName,
},
{
key: "evalConfig.modelName",
label: "模型",
children: task.evalConfig?.modelName || task.evalConfig?.modelId,
},
{
key: "createdBy",
label: "创建者",
children: task.createdBy || "未知",
},
{
key: "createdAt",
label: "创建时间",
children: formatDateTime(task.createdAt),
},
{
key: "updatedAt",
label: "更新时间",
children: formatDateTime(task.updatedAt),
},
{
key: "description",
label: "描述",
children: task.description || "无",
},
];
const columns = [
{
title: '维度',
dataIndex: 'dimension',
key: 'dimension',
width: '30%',
},
{
title: '描述',
dataIndex: 'description',
key: 'description',
width: '60%',
},
];
return (
<>
<div className=" flex flex-col gap-4">
{/* 基本信息 */}
<Descriptions
title="基本信息"
layout="vertical"
size="small"
items={items}
column={5}
/>
<h2 className="text-base font-semibold mt-8"></h2>
<div className="overflow-x-auto">
<Table
size="middle"
rowKey="id"
columns={columns}
dataSource={task?.evalConfig?.dimensions}
scroll={{ x: "max-content", y: 600 }}
/>
</div>
<div className="flex items-center gap-2 p-3 bg-blue-50 rounded-lg border border-blue-200">
<Button
type="link"
icon={<EyeOutlined />}
onClick={generateEvaluationPrompt}
>
</Button>
</div>
<PreviewPromptModal
previewVisible={previewVisible}
onCancel={() => setPreviewVisible(false)}
evaluationPrompt={task?.evalPrompt}
/>
</div>
</>
);
};
export default Overview;

View File

@@ -1,407 +1,407 @@
import { useState, useEffect } from "react";
import { Button, Card, Badge, Input, Typography, Breadcrumb } from "antd";
import {
LeftOutlined,
RightOutlined,
SaveOutlined,
ScissorOutlined,
AimOutlined,
CalendarOutlined,
FileTextOutlined,
StarFilled,
DatabaseOutlined,
} from "@ant-design/icons";
import { mockTasks, presetEvaluationDimensions } from "@/mock/evaluation";
import { useNavigate } from "react-router";
import DetailHeader from "@/components/DetailHeader";
const { TextArea } = Input;
const { Title } = Typography;
// 生成切片内容
const generateSliceContent = (index: number) => {
const contents = [
"用户咨询产品退换货政策的相关问题,希望了解具体的退货流程和时间限制。客服详细解释了7天无理由退货政策,包括商品需要保持原包装完整的要求。这个回答涵盖了用户关心的主要问题,提供了明确的时间限制和条件说明。",
"客服回复关于质量问题商品的处理方式,说明15天内免费换货服务,并承诺承担相关物流费用。用户对此表示满意,认为这个政策很合理。回答中明确区分了质量问题和非质量问题的不同处理方式。",
"用户询问特殊商品的退换货政策,客服解释个人定制商品不支持退货的规定,并建议用户在购买前仔细确认商品信息。这个回答帮助用户理解了特殊商品的限制条件。",
"关于退货流程的详细说明,客服介绍了在线申请退货的步骤,包括订单页面操作和快递上门取件服务。整个流程描述清晰,用户可以轻松按照步骤操作。",
"用户对物流费用承担问题提出疑问,客服明确说明质量问题导致的退换货由公司承担物流费用,非质量问题由用户承担。这个回答消除了用户的疑虑。",
];
return contents[index % contents.length];
};
const slices: EvaluationSlice[] = Array.from(
{ length: mockTasks[0].sliceConfig?.sampleCount || 50 },
(_, index) => ({
id: `slice_${index + 1}`,
content: generateSliceContent(index),
sourceFile: `file_${Math.floor(index / 5) + 1}.txt`,
sliceIndex: index % 5,
sliceType: ["paragraph", "sentence", "semantic"][index % 3],
metadata: {
startPosition: index * 200,
endPosition: (index + 1) * 200,
pageNumber: Math.floor(index / 10) + 1,
section: `Section ${Math.floor(index / 5) + 1}`,
processingMethod: mockTasks[0].sliceConfig?.method || "语义分割",
},
})
);
const ManualEvaluatePage = () => {
const navigate = useNavigate();
const taskId = mockTasks[0].id;
// 人工评估状态
const [currentEvaluationTask, setCurrentEvaluationTask] =
useState<EvaluationTask | null>(mockTasks[0]);
const [evaluationSlices, setEvaluationSlices] =
useState<EvaluationSlice[]>(slices);
const [currentSliceIndex, setCurrentSliceIndex] = useState(0);
const [sliceScores, setSliceScores] = useState<{
[key: string]: { [dimensionId: string]: number };
}>({});
const [sliceComments, setSliceComments] = useState<{ [key: string]: string }>(
{}
);
const currentSlice = evaluationSlices[currentSliceIndex];
const currentScores = sliceScores[currentSlice?.id] || {};
const progress =
evaluationSlices.length > 0
? ((currentSliceIndex + 1) / evaluationSlices.length) * 100
: 0;
// 获取任务的所有维度
const getTaskAllDimensions = (task: EvaluationTask) => {
const presetDimensions = presetEvaluationDimensions.filter((d) =>
task.dimensions.includes(d.id)
);
return [...presetDimensions, ...(task.customDimensions || [])];
};
const allDimensions = getTaskAllDimensions(mockTasks[0]);
// 更新切片评分
const updateSliceScore = (
sliceId: string,
dimensionId: string,
score: number
) => {
setSliceScores((prev) => ({
...prev,
[sliceId]: {
...prev[sliceId],
[dimensionId]: score,
},
}));
};
// 保存当前切片评分并进入下一个
const handleSaveAndNext = () => {
const currentSlice = evaluationSlices[currentSliceIndex];
if (!currentSlice) return;
// 检查是否所有维度都已评分
const allDimensions = getTaskAllDimensions(currentEvaluationTask!);
const currentScores = sliceScores[currentSlice.id] || {};
const hasAllScores = allDimensions.every(
(dim) => currentScores[dim.id] > 0
);
if (!hasAllScores) {
window.alert("请为所有维度评分后再保存");
return;
}
// 如果是最后一个切片,完成评估
if (currentSliceIndex === evaluationSlices.length - 1) {
handleCompleteEvaluation();
} else {
setCurrentSliceIndex(currentSliceIndex + 1);
}
};
// 完成评估
const handleCompleteEvaluation = () => {
navigate(`/data/evaluation/task-report/${mockTasks[0].id}`);
};
// 星星评分组件
const StarRating = ({
value,
onChange,
dimension,
}: {
value: number;
onChange: (value: number) => void;
dimension: EvaluationDimension;
}) => {
return (
<div style={{ marginBottom: 8 }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<span style={{ fontWeight: 500 }}>{dimension.name}</span>
<span style={{ fontSize: 13, color: "#888" }}>{value}/5</span>
</div>
<div style={{ fontSize: 12, color: "#888", marginBottom: 4 }}>
{dimension.description}
</div>
<div>
{[1, 2, 3, 4, 5].map((star) => (
<Button
key={star}
type="text"
icon={
<StarFilled
style={{
color: star <= value ? "#fadb14" : "#d9d9d9",
fontSize: 22,
transition: "color 0.2s",
}}
/>
}
onClick={() => onChange(star)}
style={{ padding: 0, marginRight: 2 }}
/>
))}
</div>
</div>
);
};
// 头部统计信息
const statistics = [
{
icon: <DatabaseOutlined className="text-gray-500" />,
label: "数据集",
value: currentEvaluationTask?.datasetName || "",
},
{
icon: <ScissorOutlined className="text-gray-500" />,
label: "切片方法",
value: currentEvaluationTask?.sliceConfig?.method || "",
},
{
icon: <AimOutlined className="text-gray-500" />,
label: "样本数量",
value: evaluationSlices.length,
},
{
icon: <CalendarOutlined className="text-gray-500" />,
label: "创建时间",
value: currentEvaluationTask?.createdAt || "",
},
];
return (
<div className="space-y-4">
<Breadcrumb
items={[
{
title: (
<span onClick={() => navigate("/data/evaluation")}></span>
),
},
{ title: "人工评估", key: "manual-evaluate" },
]}
/>
{/* 头部信息 */}
<DetailHeader
data={{
name: currentEvaluationTask?.name || "",
description: "人工评估任务",
icon: <FileTextOutlined />,
createdAt: currentEvaluationTask?.createdAt,
lastUpdated: currentEvaluationTask?.createdAt,
}}
statistics={statistics}
operations={[]}
/>
{/* 进度条 */}
<div className="flex justify-between items-center mt-4 mb-6">
<div className="text-xs text-gray-500">
: {currentSliceIndex + 1} / {evaluationSlices.length}
</div>
<div className="flex items-center gap-4">
<span className="text-xs text-gray-500">
{Math.round(progress)}%
</span>
<div className="w-48 bg-gray-200 rounded h-2">
<div
className="bg-blue-600 h-2 rounded transition-all"
style={{ width: `${progress}%` }}
/>
</div>
<span className="text-2xl font-bold text-blue-600">
{Math.round(progress)}%
</span>
</div>
</div>
<div className="grid grid-cols-2 gap-2">
{/* 左侧:切片内容 */}
<Card>
<div className="border-b border-gray-100 pb-4 mb-4 flex justify-between items-center">
<span className="text-base font-semibold flex items-center gap-2">
<FileTextOutlined />
</span>
<Badge
count={`切片 ${currentSliceIndex + 1}`}
style={{ background: "#fafafa", color: "#333" }}
/>
</div>
<div className="flex flex-col gap-2">
{currentSlice && (
<>
{/* 切片元信息 */}
<div className="bg-gray-50 rounded p-4 text-sm">
<div className="grid grid-cols-2 gap-3">
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{currentSlice.sourceFile}
</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{currentSlice.metadata.processingMethod}
</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{currentSlice.metadata.startPosition}-
{currentSlice.metadata.endPosition}
</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{currentSlice.metadata.section}
</span>
</div>
</div>
</div>
{/* 切片内容 */}
<div className="border border-gray-100 rounded p-4 min-h-[180px]">
<div className="text-xs text-gray-500 mb-2"></div>
<div className="text-gray-900 leading-relaxed">
{currentSlice.content}
</div>
</div>
{/* 导航按钮 */}
<div className="flex items-center justify-between border-t border-gray-100 pt-4 mt-2">
<Button
type="default"
icon={<LeftOutlined />}
onClick={() =>
setCurrentSliceIndex(Math.max(0, currentSliceIndex - 1))
}
disabled={currentSliceIndex === 0}
>
</Button>
<span className="text-xs text-gray-500">
{currentSliceIndex + 1} / {evaluationSlices.length}
</span>
<Button
type="default"
icon={<RightOutlined />}
onClick={() =>
setCurrentSliceIndex(
Math.min(
evaluationSlices.length - 1,
currentSliceIndex + 1
)
)
}
disabled={currentSliceIndex === evaluationSlices.length - 1}
>
</Button>
</div>
</>
)}
</div>
</Card>
{/* 右侧:评估维度 */}
<Card>
<div className="border-b border-gray-100 pb-4 mb-4">
<span className="text-base font-semibold flex items-center gap-2">
<StarFilled className="text-yellow-400" />
</span>
<div className="text-xs text-gray-500 mt-1">
1-5
</div>
</div>
<div className="flex flex-col gap-4">
{allDimensions.map((dimension) => (
<div
key={dimension.id}
className="border border-gray-100 rounded p-4"
>
<StarRating
value={currentScores[dimension.id] || 0}
onChange={(score) =>
updateSliceScore(
currentSlice?.id || "",
dimension.id,
score
)
}
dimension={dimension}
/>
</div>
))}
{/* 评论区域 */}
<div className="border border-gray-100 rounded p-4">
<span className="font-medium mb-2 block"></span>
<TextArea
placeholder="请输入对该切片的评估备注和建议..."
value={sliceComments[currentSlice?.id || ""] || ""}
onChange={(e) =>
setSliceComments((prev) => ({
...prev,
[currentSlice?.id || ""]: e.target.value,
}))
}
rows={3}
/>
</div>
{/* 保存按钮 */}
<div className="border-t border-gray-100 pt-4">
<Button
type="primary"
icon={<SaveOutlined />}
onClick={handleSaveAndNext}
block
size="large"
>
{currentSliceIndex === evaluationSlices.length - 1
? "完成评估"
: "保存并下一个"}
</Button>
</div>
</div>
</Card>
</div>
</div>
);
};
export default ManualEvaluatePage;
import { useState, useEffect } from "react";
import { Button, Card, Badge, Input, Typography, Breadcrumb } from "antd";
import {
LeftOutlined,
RightOutlined,
SaveOutlined,
ScissorOutlined,
AimOutlined,
CalendarOutlined,
FileTextOutlined,
StarFilled,
DatabaseOutlined,
} from "@ant-design/icons";
import { mockTasks, presetEvaluationDimensions } from "@/mock/evaluation";
import { useNavigate } from "react-router";
import DetailHeader from "@/components/DetailHeader";
const { TextArea } = Input;
const { Title } = Typography;
// 生成切片内容
const generateSliceContent = (index: number) => {
const contents = [
"用户咨询产品退换货政策的相关问题,希望了解具体的退货流程和时间限制。客服详细解释了7天无理由退货政策,包括商品需要保持原包装完整的要求。这个回答涵盖了用户关心的主要问题,提供了明确的时间限制和条件说明。",
"客服回复关于质量问题商品的处理方式,说明15天内免费换货服务,并承诺承担相关物流费用。用户对此表示满意,认为这个政策很合理。回答中明确区分了质量问题和非质量问题的不同处理方式。",
"用户询问特殊商品的退换货政策,客服解释个人定制商品不支持退货的规定,并建议用户在购买前仔细确认商品信息。这个回答帮助用户理解了特殊商品的限制条件。",
"关于退货流程的详细说明,客服介绍了在线申请退货的步骤,包括订单页面操作和快递上门取件服务。整个流程描述清晰,用户可以轻松按照步骤操作。",
"用户对物流费用承担问题提出疑问,客服明确说明质量问题导致的退换货由公司承担物流费用,非质量问题由用户承担。这个回答消除了用户的疑虑。",
];
return contents[index % contents.length];
};
const slices: EvaluationSlice[] = Array.from(
{ length: mockTasks[0].sliceConfig?.sampleCount || 50 },
(_, index) => ({
id: `slice_${index + 1}`,
content: generateSliceContent(index),
sourceFile: `file_${Math.floor(index / 5) + 1}.txt`,
sliceIndex: index % 5,
sliceType: ["paragraph", "sentence", "semantic"][index % 3],
metadata: {
startPosition: index * 200,
endPosition: (index + 1) * 200,
pageNumber: Math.floor(index / 10) + 1,
section: `Section ${Math.floor(index / 5) + 1}`,
processingMethod: mockTasks[0].sliceConfig?.method || "语义分割",
},
})
);
const ManualEvaluatePage = () => {
const navigate = useNavigate();
const taskId = mockTasks[0].id;
// 人工评估状态
const [currentEvaluationTask, setCurrentEvaluationTask] =
useState<EvaluationTask | null>(mockTasks[0]);
const [evaluationSlices, setEvaluationSlices] =
useState<EvaluationSlice[]>(slices);
const [currentSliceIndex, setCurrentSliceIndex] = useState(0);
const [sliceScores, setSliceScores] = useState<{
[key: string]: { [dimensionId: string]: number };
}>({});
const [sliceComments, setSliceComments] = useState<{ [key: string]: string }>(
{}
);
const currentSlice = evaluationSlices[currentSliceIndex];
const currentScores = sliceScores[currentSlice?.id] || {};
const progress =
evaluationSlices.length > 0
? ((currentSliceIndex + 1) / evaluationSlices.length) * 100
: 0;
// 获取任务的所有维度
const getTaskAllDimensions = (task: EvaluationTask) => {
const presetDimensions = presetEvaluationDimensions.filter((d) =>
task.dimensions.includes(d.id)
);
return [...presetDimensions, ...(task.customDimensions || [])];
};
const allDimensions = getTaskAllDimensions(mockTasks[0]);
// 更新切片评分
const updateSliceScore = (
sliceId: string,
dimensionId: string,
score: number
) => {
setSliceScores((prev) => ({
...prev,
[sliceId]: {
...prev[sliceId],
[dimensionId]: score,
},
}));
};
// 保存当前切片评分并进入下一个
const handleSaveAndNext = () => {
const currentSlice = evaluationSlices[currentSliceIndex];
if (!currentSlice) return;
// 检查是否所有维度都已评分
const allDimensions = getTaskAllDimensions(currentEvaluationTask!);
const currentScores = sliceScores[currentSlice.id] || {};
const hasAllScores = allDimensions.every(
(dim) => currentScores[dim.id] > 0
);
if (!hasAllScores) {
window.alert("请为所有维度评分后再保存");
return;
}
// 如果是最后一个切片,完成评估
if (currentSliceIndex === evaluationSlices.length - 1) {
handleCompleteEvaluation();
} else {
setCurrentSliceIndex(currentSliceIndex + 1);
}
};
// 完成评估
const handleCompleteEvaluation = () => {
navigate(`/data/evaluation/task-report/${mockTasks[0].id}`);
};
// 星星评分组件
const StarRating = ({
value,
onChange,
dimension,
}: {
value: number;
onChange: (value: number) => void;
dimension: EvaluationDimension;
}) => {
return (
<div style={{ marginBottom: 8 }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<span style={{ fontWeight: 500 }}>{dimension.name}</span>
<span style={{ fontSize: 13, color: "#888" }}>{value}/5</span>
</div>
<div style={{ fontSize: 12, color: "#888", marginBottom: 4 }}>
{dimension.description}
</div>
<div>
{[1, 2, 3, 4, 5].map((star) => (
<Button
key={star}
type="text"
icon={
<StarFilled
style={{
color: star <= value ? "#fadb14" : "#d9d9d9",
fontSize: 22,
transition: "color 0.2s",
}}
/>
}
onClick={() => onChange(star)}
style={{ padding: 0, marginRight: 2 }}
/>
))}
</div>
</div>
);
};
// 头部统计信息
const statistics = [
{
icon: <DatabaseOutlined className="text-gray-500" />,
label: "数据集",
value: currentEvaluationTask?.datasetName || "",
},
{
icon: <ScissorOutlined className="text-gray-500" />,
label: "切片方法",
value: currentEvaluationTask?.sliceConfig?.method || "",
},
{
icon: <AimOutlined className="text-gray-500" />,
label: "样本数量",
value: evaluationSlices.length,
},
{
icon: <CalendarOutlined className="text-gray-500" />,
label: "创建时间",
value: currentEvaluationTask?.createdAt || "",
},
];
return (
<div className="space-y-4">
<Breadcrumb
items={[
{
title: (
<span onClick={() => navigate("/data/evaluation")}></span>
),
},
{ title: "人工评估", key: "manual-evaluate" },
]}
/>
{/* 头部信息 */}
<DetailHeader
data={{
name: currentEvaluationTask?.name || "",
description: "人工评估任务",
icon: <FileTextOutlined />,
createdAt: currentEvaluationTask?.createdAt,
lastUpdated: currentEvaluationTask?.createdAt,
}}
statistics={statistics}
operations={[]}
/>
{/* 进度条 */}
<div className="flex justify-between items-center mt-4 mb-6">
<div className="text-xs text-gray-500">
: {currentSliceIndex + 1} / {evaluationSlices.length}
</div>
<div className="flex items-center gap-4">
<span className="text-xs text-gray-500">
{Math.round(progress)}%
</span>
<div className="w-48 bg-gray-200 rounded h-2">
<div
className="bg-blue-600 h-2 rounded transition-all"
style={{ width: `${progress}%` }}
/>
</div>
<span className="text-2xl font-bold text-blue-600">
{Math.round(progress)}%
</span>
</div>
</div>
<div className="grid grid-cols-2 gap-2">
{/* 左侧:切片内容 */}
<Card>
<div className="border-b border-gray-100 pb-4 mb-4 flex justify-between items-center">
<span className="text-base font-semibold flex items-center gap-2">
<FileTextOutlined />
</span>
<Badge
count={`切片 ${currentSliceIndex + 1}`}
style={{ background: "#fafafa", color: "#333" }}
/>
</div>
<div className="flex flex-col gap-2">
{currentSlice && (
<>
{/* 切片元信息 */}
<div className="bg-gray-50 rounded p-4 text-sm">
<div className="grid grid-cols-2 gap-3">
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{currentSlice.sourceFile}
</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{currentSlice.metadata.processingMethod}
</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{currentSlice.metadata.startPosition}-
{currentSlice.metadata.endPosition}
</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{currentSlice.metadata.section}
</span>
</div>
</div>
</div>
{/* 切片内容 */}
<div className="border border-gray-100 rounded p-4 min-h-[180px]">
<div className="text-xs text-gray-500 mb-2"></div>
<div className="text-gray-900 leading-relaxed">
{currentSlice.content}
</div>
</div>
{/* 导航按钮 */}
<div className="flex items-center justify-between border-t border-gray-100 pt-4 mt-2">
<Button
type="default"
icon={<LeftOutlined />}
onClick={() =>
setCurrentSliceIndex(Math.max(0, currentSliceIndex - 1))
}
disabled={currentSliceIndex === 0}
>
</Button>
<span className="text-xs text-gray-500">
{currentSliceIndex + 1} / {evaluationSlices.length}
</span>
<Button
type="default"
icon={<RightOutlined />}
onClick={() =>
setCurrentSliceIndex(
Math.min(
evaluationSlices.length - 1,
currentSliceIndex + 1
)
)
}
disabled={currentSliceIndex === evaluationSlices.length - 1}
>
</Button>
</div>
</>
)}
</div>
</Card>
{/* 右侧:评估维度 */}
<Card>
<div className="border-b border-gray-100 pb-4 mb-4">
<span className="text-base font-semibold flex items-center gap-2">
<StarFilled className="text-yellow-400" />
</span>
<div className="text-xs text-gray-500 mt-1">
1-5
</div>
</div>
<div className="flex flex-col gap-4">
{allDimensions.map((dimension) => (
<div
key={dimension.id}
className="border border-gray-100 rounded p-4"
>
<StarRating
value={currentScores[dimension.id] || 0}
onChange={(score) =>
updateSliceScore(
currentSlice?.id || "",
dimension.id,
score
)
}
dimension={dimension}
/>
</div>
))}
{/* 评论区域 */}
<div className="border border-gray-100 rounded p-4">
<span className="font-medium mb-2 block"></span>
<TextArea
placeholder="请输入对该切片的评估备注和建议..."
value={sliceComments[currentSlice?.id || ""] || ""}
onChange={(e) =>
setSliceComments((prev) => ({
...prev,
[currentSlice?.id || ""]: e.target.value,
}))
}
rows={3}
/>
</div>
{/* 保存按钮 */}
<div className="border-t border-gray-100 pt-4">
<Button
type="primary"
icon={<SaveOutlined />}
onClick={handleSaveAndNext}
block
size="large"
>
{currentSliceIndex === evaluationSlices.length - 1
? "完成评估"
: "保存并下一个"}
</Button>
</div>
</div>
</Card>
</div>
</div>
);
};
export default ManualEvaluatePage;

View File

@@ -1,267 +1,267 @@
import { useState } from "react";
import {
Button,
Card,
Table,
Tag,
Space,
Typography,
Progress,
Popconfirm,
App,
} from "antd";
import {
PlusOutlined,
DeleteOutlined
} from "@ant-design/icons";
import { SearchControls } from "@/components/SearchControls";
import { useNavigate } from "react-router";
import { deleteEvaluationTaskUsingGet, getPagedEvaluationTaskUsingGet } from "@/pages/DataEvaluation/evaluation.api";
import CardView from "@/components/CardView";
import CreateTaskModal from "@/pages/DataEvaluation/Create/CreateTask.tsx";
import useFetchData from "@/hooks/useFetchData.ts";
import { EvaluationTask } from "@/pages/DataEvaluation/evaluation.model.ts";
import { mapEvaluationTask } from "@/pages/DataEvaluation/evaluation.const.tsx";
const { Text, Title } = Typography;
const statusMap = {
PENDING: { text: '等待中', color: 'warning'},
RUNNING: { text: '运行中', color: 'processing'},
COMPLETED: { text: '已完成', color: 'success'},
STOPPED: { text: '已停止', color: 'default'},
FAILED: { text: '失败', color: 'error'},
};
export default function DataEvaluationPage() {
const { message } = App.useApp();
const navigate = useNavigate();
const [viewMode, setViewMode] = useState<"card" | "list">("list");
const {
loading,
tableData,
pagination,
searchParams,
setSearchParams,
handleFiltersChange,
fetchData,
} = useFetchData<EvaluationTask>(
getPagedEvaluationTaskUsingGet,
mapEvaluationTask,
30000,
true,
[],
0
);
const [isModalVisible, setIsModalVisible] = useState(false);
const handleDeleteTask = async (task: EvaluationTask) => {
try {
// 调用删除接口
await deleteEvaluationTaskUsingGet(task.id);
message.success("任务删除成功");
// 重新加载数据
fetchData().then();
} catch (error) {
message.error("任务删除失败,请稍后重试");
}
};
const filterOptions = [
{
key: 'status',
label: '状态',
options: Object.entries(statusMap).map(([value, { text }]) => ({
value,
label: text,
})),
},
{
key: 'taskType',
label: '任务类型',
options: [
{ value: 'QA', label: 'QA评估' },
{ value: 'COT', label: 'COPT评估' },
],
},
{
key: 'evalMethod',
label: '评估方式',
options: [
{ value: 'AUTO', label: '自动评估' },
],
},
];
const columns = [
{
title: '任务名称',
dataIndex: 'name',
key: 'name',
render: (name, record) => (
<Button
type="link"
onClick={() => navigate(`/data/evaluation/detail/${record.id}`)}
>
{name}
</Button>
),
},
{
title: '任务类型',
dataIndex: 'taskType',
key: 'taskType',
render: (text: string) => (
<Tag color={text === 'QA' ? 'blue' : 'default'}>
{text === 'QA' ? 'QA评估' : text}
</Tag>
),
},
{
title: '评估方式',
dataIndex: 'evalMethod',
key: 'evalMethod',
render: (text: string) => (
<Tag color={text === 'AUTO' ? 'geekblue' : 'orange'}>
{text === 'AUTO' ? '自动评估' : '人工评估'}
</Tag>
),
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: any) => {
return (<Tag color={status.color}> {status.label} </Tag>);
},
},
{
title: '进度',
dataIndex: 'evalProcess',
key: 'evalProcess',
render: (progress: number, record: EvaluationTask) => (
<Progress
percent={Math.round(progress * 100)}
size="small"
status={record.status === 'FAILED' ? 'exception' : 'active'}
/>
),
},
{
title: "创建时间",
dataIndex: "updatedAt",
key: "updatedAt",
width: 180,
},
{
title: '操作',
key: 'action',
render: (_: any, task: EvaluationTask) => (
<div className="flex items-center gap-2">
{operations.map((op) => {
if (op.confirm) {
<Popconfirm
title={op.confirm.title}
description={op.confirm.description}
onConfirm={() => op.onClick(task)}
>
<Button type="text" icon={op.icon} />
</Popconfirm>;
}
return (
<Button
key={op.key}
type="text"
icon={op.icon}
danger={op.danger}
onClick={() => op.onClick(task)}
/>
);
})}
</div>
),
},
];
const operations = [
{
key: "delete",
label: "删除",
danger: true,
confirm: {
title: "确认删除该任务?",
description: "删除后该任务将无法恢复,请谨慎操作。",
okText: "删除",
cancelText: "取消",
okType: "danger",
},
icon: <DeleteOutlined />,
onClick: handleDeleteTask,
}
];
return (
<div className="p-4">
<div className="flex items-center justify-between mb-4">
<Title level={4} style={{ margin: 0 }}></Title>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => setIsModalVisible(true)}
>
</Button>
</div>
<>
{/* 搜索、筛选和视图控制 */}
<SearchControls
searchTerm={searchParams.keyword}
onSearchChange={(keyword) =>
setSearchParams({ ...searchParams, keyword })
}
searchPlaceholder="搜索任务名称..."
filters={filterOptions}
onFiltersChange={handleFiltersChange}
onClearFilters={() =>
setSearchParams({ ...searchParams, filter: {} })
}
viewMode={viewMode}
onViewModeChange={setViewMode}
showViewToggle
onReload={fetchData}
/>
{/* 任务列表 */}
{viewMode === "list" ? (
<Card>
<Table
columns={columns}
dataSource={tableData}
pagination={pagination}
rowKey="id"
scroll={{ x: "max-content", y: "calc(100vh - 30rem)" }}
/>
</Card>
) : (
<CardView
loading={loading}
data={tableData}
operations={operations}
pagination={pagination}
onView={(task) => {
navigate(`/data/evaluation/detail/${task.id}`);
}}
/>
)}
</>
<CreateTaskModal
visible={isModalVisible}
onCancel={() => setIsModalVisible(false)}
onSuccess={() => {
setIsModalVisible(false);
fetchData();
}}
/>
</div>
);
}
import { useState } from "react";
import {
Button,
Card,
Table,
Tag,
Space,
Typography,
Progress,
Popconfirm,
App,
} from "antd";
import {
PlusOutlined,
DeleteOutlined
} from "@ant-design/icons";
import { SearchControls } from "@/components/SearchControls";
import { useNavigate } from "react-router";
import { deleteEvaluationTaskUsingGet, getPagedEvaluationTaskUsingGet } from "@/pages/DataEvaluation/evaluation.api";
import CardView from "@/components/CardView";
import CreateTaskModal from "@/pages/DataEvaluation/Create/CreateTask.tsx";
import useFetchData from "@/hooks/useFetchData.ts";
import { EvaluationTask } from "@/pages/DataEvaluation/evaluation.model.ts";
import { mapEvaluationTask } from "@/pages/DataEvaluation/evaluation.const.tsx";
const { Text, Title } = Typography;
const statusMap = {
PENDING: { text: '等待中', color: 'warning'},
RUNNING: { text: '运行中', color: 'processing'},
COMPLETED: { text: '已完成', color: 'success'},
STOPPED: { text: '已停止', color: 'default'},
FAILED: { text: '失败', color: 'error'},
};
export default function DataEvaluationPage() {
const { message } = App.useApp();
const navigate = useNavigate();
const [viewMode, setViewMode] = useState<"card" | "list">("list");
const {
loading,
tableData,
pagination,
searchParams,
setSearchParams,
handleFiltersChange,
fetchData,
} = useFetchData<EvaluationTask>(
getPagedEvaluationTaskUsingGet,
mapEvaluationTask,
30000,
true,
[],
0
);
const [isModalVisible, setIsModalVisible] = useState(false);
const handleDeleteTask = async (task: EvaluationTask) => {
try {
// 调用删除接口
await deleteEvaluationTaskUsingGet(task.id);
message.success("任务删除成功");
// 重新加载数据
fetchData().then();
} catch (error) {
message.error("任务删除失败,请稍后重试");
}
};
const filterOptions = [
{
key: 'status',
label: '状态',
options: Object.entries(statusMap).map(([value, { text }]) => ({
value,
label: text,
})),
},
{
key: 'taskType',
label: '任务类型',
options: [
{ value: 'QA', label: 'QA评估' },
{ value: 'COT', label: 'COPT评估' },
],
},
{
key: 'evalMethod',
label: '评估方式',
options: [
{ value: 'AUTO', label: '自动评估' },
],
},
];
const columns = [
{
title: '任务名称',
dataIndex: 'name',
key: 'name',
render: (name, record) => (
<Button
type="link"
onClick={() => navigate(`/data/evaluation/detail/${record.id}`)}
>
{name}
</Button>
),
},
{
title: '任务类型',
dataIndex: 'taskType',
key: 'taskType',
render: (text: string) => (
<Tag color={text === 'QA' ? 'blue' : 'default'}>
{text === 'QA' ? 'QA评估' : text}
</Tag>
),
},
{
title: '评估方式',
dataIndex: 'evalMethod',
key: 'evalMethod',
render: (text: string) => (
<Tag color={text === 'AUTO' ? 'geekblue' : 'orange'}>
{text === 'AUTO' ? '自动评估' : '人工评估'}
</Tag>
),
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: any) => {
return (<Tag color={status.color}> {status.label} </Tag>);
},
},
{
title: '进度',
dataIndex: 'evalProcess',
key: 'evalProcess',
render: (progress: number, record: EvaluationTask) => (
<Progress
percent={Math.round(progress * 100)}
size="small"
status={record.status === 'FAILED' ? 'exception' : 'active'}
/>
),
},
{
title: "创建时间",
dataIndex: "updatedAt",
key: "updatedAt",
width: 180,
},
{
title: '操作',
key: 'action',
render: (_: any, task: EvaluationTask) => (
<div className="flex items-center gap-2">
{operations.map((op) => {
if (op.confirm) {
<Popconfirm
title={op.confirm.title}
description={op.confirm.description}
onConfirm={() => op.onClick(task)}
>
<Button type="text" icon={op.icon} />
</Popconfirm>;
}
return (
<Button
key={op.key}
type="text"
icon={op.icon}
danger={op.danger}
onClick={() => op.onClick(task)}
/>
);
})}
</div>
),
},
];
const operations = [
{
key: "delete",
label: "删除",
danger: true,
confirm: {
title: "确认删除该任务?",
description: "删除后该任务将无法恢复,请谨慎操作。",
okText: "删除",
cancelText: "取消",
okType: "danger",
},
icon: <DeleteOutlined />,
onClick: handleDeleteTask,
}
];
return (
<div className="p-4">
<div className="flex items-center justify-between mb-4">
<Title level={4} style={{ margin: 0 }}></Title>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => setIsModalVisible(true)}
>
</Button>
</div>
<>
{/* 搜索、筛选和视图控制 */}
<SearchControls
searchTerm={searchParams.keyword}
onSearchChange={(keyword) =>
setSearchParams({ ...searchParams, keyword })
}
searchPlaceholder="搜索任务名称..."
filters={filterOptions}
onFiltersChange={handleFiltersChange}
onClearFilters={() =>
setSearchParams({ ...searchParams, filter: {} })
}
viewMode={viewMode}
onViewModeChange={setViewMode}
showViewToggle
onReload={fetchData}
/>
{/* 任务列表 */}
{viewMode === "list" ? (
<Card>
<Table
columns={columns}
dataSource={tableData}
pagination={pagination}
rowKey="id"
scroll={{ x: "max-content", y: "calc(100vh - 30rem)" }}
/>
</Card>
) : (
<CardView
loading={loading}
data={tableData}
operations={operations}
pagination={pagination}
onView={(task) => {
navigate(`/data/evaluation/detail/${task.id}`);
}}
/>
)}
</>
<CreateTaskModal
visible={isModalVisible}
onCancel={() => setIsModalVisible(false)}
onSuccess={() => {
setIsModalVisible(false);
fetchData();
}}
/>
</div>
);
}

View File

@@ -1,308 +1,308 @@
import { Button, Card, Badge, Breadcrumb } from "antd";
import {
Download,
Users,
Scissors,
BarChart3,
Target,
Calendar,
TrendingUp,
MessageSquare,
Star,
} from "lucide-react";
import {
mockQAPairs,
mockTasks,
presetEvaluationDimensions,
} from "@/mock/evaluation";
import { Link } from "react-router";
const EvaluationTaskReport = () => {
// const navigate = useNavigate();
const selectedTask = mockTasks[0]; // 假设我们只展示第一个任务的报告
// 获取任务的所有维度
const getTaskAllDimensions = (task: EvaluationTask) => {
const presetDimensions = presetEvaluationDimensions.filter((d) =>
task.dimensions.includes(d.id)
);
return [...presetDimensions, ...(task.customDimensions || [])];
};
const allDimensions = getTaskAllDimensions(selectedTask);
return (
<div className="min-h-screen">
<div className="mx-auto space-y-2">
{/* 头部 */}
<div className="flex items-center justify-between">
<Breadcrumb
items={[
{
title: <Link to="/data/evaluation"></Link>,
},
{ title: "评估报告", key: "report" },
]}
></Breadcrumb>
<div className="flex items-center gap-2">
<Button
className="flex items-center gap-2"
icon={<Download className="w-4 h-4" />}
>
</Button>
</div>
</div>
{/* 基本信息卡片 */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<Card>
<div className="p-4">
<div className="flex items-center gap-3">
<div className="p-2 bg-blue-100 rounded-lg">
<BarChart3 className="w-5 h-5 text-blue-600" />
</div>
<div>
<div className="text-2xl font-bold text-gray-900">
{selectedTask.score || 0}
</div>
<div className="text-sm text-gray-500"></div>
</div>
</div>
</div>
</Card>
<Card>
<div className="p-4">
<div className="flex items-center gap-3">
<div className="p-2 bg-green-100 rounded-lg">
<Target className="w-5 h-5 text-green-600" />
</div>
<div>
<div className="text-2xl font-bold text-gray-900">
{selectedTask.sliceConfig?.sampleCount}
</div>
<div className="text-sm text-gray-500"></div>
</div>
</div>
</div>
</Card>
<Card>
<div className="p-4">
<div className="flex items-center gap-3">
<div className="p-2 bg-purple-100 rounded-lg">
<Users className="w-5 h-5 text-purple-600" />
</div>
<div>
<div className="text-2xl font-bold text-gray-900">
{selectedTask.evaluationType === "manual" ? "人工" : "模型"}
</div>
<div className="text-sm text-gray-500"></div>
</div>
</div>
</div>
</Card>
<Card>
<div className="p-4">
<div className="flex items-center gap-3">
<div className="p-2 bg-orange-100 rounded-lg">
<Calendar className="w-5 h-5 text-orange-600" />
</div>
<div>
<div className="text-2xl font-bold text-gray-900">
{selectedTask.progress}%
</div>
<div className="text-sm text-gray-500"></div>
</div>
</div>
</div>
</Card>
</div>
{/* 详细信息 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* 评估结果 */}
<Card
title={
<span className="flex items-center gap-2">
<TrendingUp className="w-5 h-5" />
</span>
}
styles={{ body: { paddingTop: 0 } }}
>
{/* 维度评分 */}
<div className="mt-4">
<h4 className="font-medium mb-3"></h4>
<div className="space-y-3">
{allDimensions.map((dimension) => {
const score = 75 + Math.floor(Math.random() * 20); // 模拟评分
return (
<div
key={dimension.id}
className="flex items-center justify-between"
>
<div className="flex-1">
<div className="flex items-center justify-between mb-1">
<span className="text-sm font-medium">
{dimension.name}
</span>
<span className="text-sm font-bold text-blue-600">
{score}
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-500 h-2 rounded-full"
style={{ width: `${score}%` }}
/>
</div>
</div>
</div>
);
})}
</div>
</div>
{/* 质量分数解读 */}
<div className="border-t pt-4 mt-4">
<h4 className="font-medium mb-3"></h4>
<div className="space-y-2 text-sm">
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-green-500 rounded-full" />
<span>90-100分: 优秀</span>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-blue-500 rounded-full" />
<span>80-89分: 良好</span>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-yellow-500 rounded-full" />
<span>70-79分: 一般</span>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-red-500 rounded-full" />
<span>60-69分: 较差</span>
</div>
</div>
</div>
</Card>
{/* 切片信息 */}
<Card
title={
<span className="flex items-center gap-2">
<Scissors className="w-5 h-5" />
</span>
}
styles={{ body: { paddingTop: 0 } }}
>
<div className="space-y-4 mt-4">
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{selectedTask.sliceConfig?.threshold}
</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{selectedTask.sliceConfig?.sampleCount}
</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{selectedTask.sliceConfig?.method}
</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{selectedTask.completedAt || selectedTask.createdAt}
</span>
</div>
</div>
<div className="border-t pt-4">
<h4 className="font-medium mb-3"></h4>
<div className="flex flex-wrap gap-2">
{allDimensions.map((dimension) => (
<Badge
key={dimension.id}
style={{
border: "1px solid #d9d9d9",
background: "#fafafa",
padding: "0 8px",
}}
>
{dimension.name}
</Badge>
))}
</div>
</div>
</div>
</Card>
</div>
{/* QA对详情 */}
<Card
title={
<span className="flex items-center gap-2">
<MessageSquare className="w-5 h-5" />
QA对详情
</span>
}
styles={{ body: { paddingTop: 0 } }}
>
<div className="space-y-4 mt-4">
{mockQAPairs.map((qa) => (
<div key={qa.id} className="border rounded-lg p-4">
<div className="space-y-3">
<div>
<span className="text-sm font-medium text-gray-700 mb-1">
:
</span>
<span className="text-gray-900">{qa.question}</span>
</div>
<div>
<span className="text-sm font-medium text-gray-700 mb-1">
:
</span>
<span className="text-gray-900">{qa.answer}</span>
</div>
<div className="flex items-center justify-between pt-2 border-t">
<div className="flex items-center gap-2">
<span className="text-sm text-gray-500">:</span>
<div className="flex items-center gap-1">
{[1, 2, 3, 4, 5].map((star) => (
<Star
key={star}
className={`w-4 h-4 ${star <= qa.score
? "text-yellow-400"
: "text-gray-300"
}`}
style={star <= qa.score ? { fill: "#facc15" } : {}}
/>
))}
</div>
<span className="text-sm font-medium">{qa.score}/5</span>
</div>
{qa.feedback && (
<div className="text-sm text-gray-600">{qa.feedback}</div>
)}
</div>
</div>
</div>
))}
</div>
</Card>
</div>
</div>
);
};
export default EvaluationTaskReport;
import { Button, Card, Badge, Breadcrumb } from "antd";
import {
Download,
Users,
Scissors,
BarChart3,
Target,
Calendar,
TrendingUp,
MessageSquare,
Star,
} from "lucide-react";
import {
mockQAPairs,
mockTasks,
presetEvaluationDimensions,
} from "@/mock/evaluation";
import { Link } from "react-router";
const EvaluationTaskReport = () => {
// const navigate = useNavigate();
const selectedTask = mockTasks[0]; // 假设我们只展示第一个任务的报告
// 获取任务的所有维度
const getTaskAllDimensions = (task: EvaluationTask) => {
const presetDimensions = presetEvaluationDimensions.filter((d) =>
task.dimensions.includes(d.id)
);
return [...presetDimensions, ...(task.customDimensions || [])];
};
const allDimensions = getTaskAllDimensions(selectedTask);
return (
<div className="min-h-screen">
<div className="mx-auto space-y-2">
{/* 头部 */}
<div className="flex items-center justify-between">
<Breadcrumb
items={[
{
title: <Link to="/data/evaluation"></Link>,
},
{ title: "评估报告", key: "report" },
]}
></Breadcrumb>
<div className="flex items-center gap-2">
<Button
className="flex items-center gap-2"
icon={<Download className="w-4 h-4" />}
>
</Button>
</div>
</div>
{/* 基本信息卡片 */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<Card>
<div className="p-4">
<div className="flex items-center gap-3">
<div className="p-2 bg-blue-100 rounded-lg">
<BarChart3 className="w-5 h-5 text-blue-600" />
</div>
<div>
<div className="text-2xl font-bold text-gray-900">
{selectedTask.score || 0}
</div>
<div className="text-sm text-gray-500"></div>
</div>
</div>
</div>
</Card>
<Card>
<div className="p-4">
<div className="flex items-center gap-3">
<div className="p-2 bg-green-100 rounded-lg">
<Target className="w-5 h-5 text-green-600" />
</div>
<div>
<div className="text-2xl font-bold text-gray-900">
{selectedTask.sliceConfig?.sampleCount}
</div>
<div className="text-sm text-gray-500"></div>
</div>
</div>
</div>
</Card>
<Card>
<div className="p-4">
<div className="flex items-center gap-3">
<div className="p-2 bg-purple-100 rounded-lg">
<Users className="w-5 h-5 text-purple-600" />
</div>
<div>
<div className="text-2xl font-bold text-gray-900">
{selectedTask.evaluationType === "manual" ? "人工" : "模型"}
</div>
<div className="text-sm text-gray-500"></div>
</div>
</div>
</div>
</Card>
<Card>
<div className="p-4">
<div className="flex items-center gap-3">
<div className="p-2 bg-orange-100 rounded-lg">
<Calendar className="w-5 h-5 text-orange-600" />
</div>
<div>
<div className="text-2xl font-bold text-gray-900">
{selectedTask.progress}%
</div>
<div className="text-sm text-gray-500"></div>
</div>
</div>
</div>
</Card>
</div>
{/* 详细信息 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* 评估结果 */}
<Card
title={
<span className="flex items-center gap-2">
<TrendingUp className="w-5 h-5" />
</span>
}
styles={{ body: { paddingTop: 0 } }}
>
{/* 维度评分 */}
<div className="mt-4">
<h4 className="font-medium mb-3"></h4>
<div className="space-y-3">
{allDimensions.map((dimension) => {
const score = 75 + Math.floor(Math.random() * 20); // 模拟评分
return (
<div
key={dimension.id}
className="flex items-center justify-between"
>
<div className="flex-1">
<div className="flex items-center justify-between mb-1">
<span className="text-sm font-medium">
{dimension.name}
</span>
<span className="text-sm font-bold text-blue-600">
{score}
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-500 h-2 rounded-full"
style={{ width: `${score}%` }}
/>
</div>
</div>
</div>
);
})}
</div>
</div>
{/* 质量分数解读 */}
<div className="border-t pt-4 mt-4">
<h4 className="font-medium mb-3"></h4>
<div className="space-y-2 text-sm">
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-green-500 rounded-full" />
<span>90-100分: 优秀</span>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-blue-500 rounded-full" />
<span>80-89分: 良好</span>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-yellow-500 rounded-full" />
<span>70-79分: 一般</span>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-red-500 rounded-full" />
<span>60-69分: 较差</span>
</div>
</div>
</div>
</Card>
{/* 切片信息 */}
<Card
title={
<span className="flex items-center gap-2">
<Scissors className="w-5 h-5" />
</span>
}
styles={{ body: { paddingTop: 0 } }}
>
<div className="space-y-4 mt-4">
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{selectedTask.sliceConfig?.threshold}
</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{selectedTask.sliceConfig?.sampleCount}
</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{selectedTask.sliceConfig?.method}
</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{selectedTask.completedAt || selectedTask.createdAt}
</span>
</div>
</div>
<div className="border-t pt-4">
<h4 className="font-medium mb-3"></h4>
<div className="flex flex-wrap gap-2">
{allDimensions.map((dimension) => (
<Badge
key={dimension.id}
style={{
border: "1px solid #d9d9d9",
background: "#fafafa",
padding: "0 8px",
}}
>
{dimension.name}
</Badge>
))}
</div>
</div>
</div>
</Card>
</div>
{/* QA对详情 */}
<Card
title={
<span className="flex items-center gap-2">
<MessageSquare className="w-5 h-5" />
QA对详情
</span>
}
styles={{ body: { paddingTop: 0 } }}
>
<div className="space-y-4 mt-4">
{mockQAPairs.map((qa) => (
<div key={qa.id} className="border rounded-lg p-4">
<div className="space-y-3">
<div>
<span className="text-sm font-medium text-gray-700 mb-1">
:
</span>
<span className="text-gray-900">{qa.question}</span>
</div>
<div>
<span className="text-sm font-medium text-gray-700 mb-1">
:
</span>
<span className="text-gray-900">{qa.answer}</span>
</div>
<div className="flex items-center justify-between pt-2 border-t">
<div className="flex items-center gap-2">
<span className="text-sm text-gray-500">:</span>
<div className="flex items-center gap-1">
{[1, 2, 3, 4, 5].map((star) => (
<Star
key={star}
className={`w-4 h-4 ${star <= qa.score
? "text-yellow-400"
: "text-gray-300"
}`}
style={star <= qa.score ? { fill: "#facc15" } : {}}
/>
))}
</div>
<span className="text-sm font-medium">{qa.score}/5</span>
</div>
{qa.feedback && (
<div className="text-sm text-gray-600">{qa.feedback}</div>
)}
</div>
</div>
</div>
))}
</div>
</Card>
</div>
</div>
);
};
export default EvaluationTaskReport;

View File

@@ -1,276 +1,276 @@
import { get, post, put, del, download } from "@/utils/request";
export function createEvaluationTaskUsingPost(data: any) {
return post("/api/evaluation/tasks", data);
}
export function getPagedEvaluationTaskUsingGet(params?: any) {
return get("/api/evaluation/tasks", params);
}
export function deleteEvaluationTaskUsingGet(id: string) {
const url = `/api/evaluation/tasks?ids=${id}`;
return del(url);
}
export function queryPromptTemplatesUsingGet() {
return get("/api/evaluation/prompt-templates");
}
export function getEvaluationTaskByIdUsingGet(taskId: string | number) {
return get(`/api/evaluation/tasks/${taskId}`);
}
export function queryEvaluationFilesUsingGet(params: {
taskId: string;
page?: number;
size?: number;
}) {
const { taskId, ...rest } = params;
return get(`/api/evaluation/tasks/${taskId}/files`, rest);
}
export function queryEvaluationItemsUsingGet(params: {
taskId: string;
page?: number;
size?: number;
status?: string;
file_id?: string;
}) {
const { taskId, ...rest } = params;
return get(`/api/evaluation/tasks/${taskId}/items`, rest);
}
// 数据质量评估相关接口
export function evaluateDataQualityUsingPost(data: any) {
return post("/api/v1/evaluation/quality", data);
}
export function getQualityEvaluationByIdUsingGet(evaluationId: string | number) {
return get(`/api/v1/evaluation/quality/${evaluationId}`);
}
// 适配性评估相关接口
export function evaluateCompatibilityUsingPost(data: any) {
return post("/api/v1/evaluation/compatibility", data);
}
// 价值评估相关接口
export function evaluateValueUsingPost(data: any) {
return post("/api/v1/evaluation/value", data);
}
// 评估报告管理接口
export function queryEvaluationReportsUsingGet(params?: any) {
return get("/api/v1/evaluation/reports", params);
}
export function getEvaluationReportByIdUsingGet(reportId: string | number) {
return get(`/api/v1/evaluation/reports/${reportId}`);
}
export function exportEvaluationReportUsingGet(reportId: string | number, format = "PDF", filename?: string) {
return download(`/api/v1/evaluation/reports/${reportId}/export`, { format }, filename);
}
// 批量评估接口
export function batchEvaluationUsingPost(data: any) {
return post("/api/v1/evaluation/batch", data);
}
// 扩展功能接口(基于常见需求添加)
// 评估模板管理
export function queryEvaluationTemplatesUsingGet(params?: any) {
return get("/api/v1/evaluation/templates", params);
}
export function createEvaluationTemplateUsingPost(data: any) {
return post("/api/v1/evaluation/templates", data);
}
export function getEvaluationTemplateByIdUsingGet(templateId: string | number) {
return get(`/api/v1/evaluation/templates/${templateId}`);
}
export function updateEvaluationTemplateByIdUsingPut(templateId: string | number, data: any) {
return put(`/api/v1/evaluation/templates/${templateId}`, data);
}
export function deleteEvaluationTemplateByIdUsingDelete(templateId: string | number) {
return del(`/api/v1/evaluation/templates/${templateId}`);
}
// 评估历史记录
export function queryEvaluationHistoryUsingGet(params?: any) {
return get("/api/v1/evaluation/history", params);
}
export function getEvaluationHistoryByDatasetUsingGet(datasetId: string | number, params?: any) {
return get(`/api/v1/evaluation/history/dataset/${datasetId}`, params);
}
// 评估指标配置
export function queryQualityMetricsUsingGet() {
return get("/api/v1/evaluation/metrics/quality");
}
export function queryCompatibilityMetricsUsingGet() {
return get("/api/v1/evaluation/metrics/compatibility");
}
export function queryValueMetricsUsingGet() {
return get("/api/v1/evaluation/metrics/value");
}
// 评估规则管理
export function queryEvaluationRulesUsingGet(params?: any) {
return get("/api/v1/evaluation/rules", params);
}
export function createEvaluationRuleUsingPost(data: any) {
return post("/api/v1/evaluation/rules", data);
}
export function updateEvaluationRuleByIdUsingPut(ruleId: string | number, data: any) {
return put(`/api/v1/evaluation/rules/${ruleId}`, data);
}
export function deleteEvaluationRuleByIdUsingDelete(ruleId: string | number) {
return del(`/api/v1/evaluation/rules/${ruleId}`);
}
// 评估统计信息
export function getEvaluationStatisticsUsingGet(params?: any) {
return get("/api/v1/evaluation/statistics", params);
}
export function getDatasetEvaluationSummaryUsingGet(datasetId: string | number) {
return get(`/api/v1/evaluation/datasets/${datasetId}/summary`);
}
// 评估任务管理
export function queryEvaluationTasksUsingGet(params?: any) {
return get("/api/v1/evaluation/tasks", params);
}
export function cancelEvaluationTaskUsingPost(taskId: string | number) {
return post(`/api/v1/evaluation/tasks/${taskId}/cancel`);
}
export function retryEvaluationTaskUsingPost(taskId: string | number) {
return post(`/api/v1/evaluation/tasks/${taskId}/retry`);
}
// 评估结果比较
export function compareEvaluationResultsUsingPost(data: any) {
return post("/api/v1/evaluation/compare", data);
}
// 评估配置管理
export function getEvaluationConfigUsingGet() {
return get("/api/v1/evaluation/config");
}
export function updateEvaluationConfigUsingPut(data: any) {
return put("/api/v1/evaluation/config", data);
}
// 数据质量监控
export function createQualityMonitorUsingPost(data: any) {
return post("/api/v1/evaluation/quality/monitors", data);
}
export function queryQualityMonitorsUsingGet(params?: any) {
return get("/api/v1/evaluation/quality/monitors", params);
}
export function updateQualityMonitorByIdUsingPut(monitorId: string | number, data: any) {
return put(`/api/v1/evaluation/quality/monitors/${monitorId}`, data);
}
export function deleteQualityMonitorByIdUsingDelete(monitorId: string | number) {
return del(`/api/v1/evaluation/quality/monitors/${monitorId}`);
}
// 评估基准管理
export function queryEvaluationBenchmarksUsingGet(params?: any) {
return get("/api/v1/evaluation/benchmarks", params);
}
export function createEvaluationBenchmarkUsingPost(data: any) {
return post("/api/v1/evaluation/benchmarks", data);
}
export function updateEvaluationBenchmarkByIdUsingPut(benchmarkId: string | number, data: any) {
return put(`/api/v1/evaluation/benchmarks/${benchmarkId}`, data);
}
export function deleteEvaluationBenchmarkByIdUsingDelete(benchmarkId: string | number) {
return del(`/api/v1/evaluation/benchmarks/${benchmarkId}`);
}
// 评估算法管理
export function queryEvaluationAlgorithmsUsingGet(params?: any) {
return get("/api/v1/evaluation/algorithms", params);
}
export function runCustomEvaluationUsingPost(data: any) {
return post("/api/v1/evaluation/custom", data);
}
// 评估可视化数据
export function getEvaluationVisualizationUsingGet(evaluationId: string | number, chartType?: string) {
return get(`/api/v1/evaluation/${evaluationId}/visualization`, { chartType });
}
// 评估通知和警报
export function queryEvaluationAlertsUsingGet(params?: any) {
return get("/api/v1/evaluation/alerts", params);
}
export function createEvaluationAlertUsingPost(data: any) {
return post("/api/v1/evaluation/alerts", data);
}
export function updateEvaluationAlertByIdUsingPut(alertId: string | number, data: any) {
return put(`/api/v1/evaluation/alerts/${alertId}`, data);
}
export function deleteEvaluationAlertByIdUsingDelete(alertId: string | number) {
return del(`/api/v1/evaluation/alerts/${alertId}`);
}
// 批量操作扩展
export function batchDeleteEvaluationReportsUsingPost(data: { reportIds: string[] }) {
return post("/api/v1/evaluation/reports/batch-delete", data);
}
export function batchExportEvaluationReportsUsingPost(data: any) {
return post("/api/v1/evaluation/reports/batch-export", data);
}
// 评估调度管理
export function queryEvaluationSchedulesUsingGet(params?: any) {
return get("/api/v1/evaluation/schedules", params);
}
export function createEvaluationScheduleUsingPost(data: any) {
return post("/api/v1/evaluation/schedules", data);
}
export function updateEvaluationScheduleByIdUsingPut(scheduleId: string | number, data: any) {
return put(`/api/v1/evaluation/schedules/${scheduleId}`, data);
}
export function deleteEvaluationScheduleByIdUsingDelete(scheduleId: string | number) {
return del(`/api/v1/evaluation/schedules/${scheduleId}`);
}
export function enableEvaluationScheduleUsingPost(scheduleId: string | number) {
return post(`/api/v1/evaluation/schedules/${scheduleId}/enable`);
}
export function disableEvaluationScheduleUsingPost(scheduleId: string | number) {
return post(`/api/v1/evaluation/schedules/${scheduleId}/disable`);
}
import { get, post, put, del, download } from "@/utils/request";
export function createEvaluationTaskUsingPost(data: any) {
return post("/api/evaluation/tasks", data);
}
export function getPagedEvaluationTaskUsingGet(params?: any) {
return get("/api/evaluation/tasks", params);
}
export function deleteEvaluationTaskUsingGet(id: string) {
const url = `/api/evaluation/tasks?ids=${id}`;
return del(url);
}
export function queryPromptTemplatesUsingGet() {
return get("/api/evaluation/prompt-templates");
}
export function getEvaluationTaskByIdUsingGet(taskId: string | number) {
return get(`/api/evaluation/tasks/${taskId}`);
}
export function queryEvaluationFilesUsingGet(params: {
taskId: string;
page?: number;
size?: number;
}) {
const { taskId, ...rest } = params;
return get(`/api/evaluation/tasks/${taskId}/files`, rest);
}
export function queryEvaluationItemsUsingGet(params: {
taskId: string;
page?: number;
size?: number;
status?: string;
file_id?: string;
}) {
const { taskId, ...rest } = params;
return get(`/api/evaluation/tasks/${taskId}/items`, rest);
}
// 数据质量评估相关接口
export function evaluateDataQualityUsingPost(data: any) {
return post("/api/v1/evaluation/quality", data);
}
export function getQualityEvaluationByIdUsingGet(evaluationId: string | number) {
return get(`/api/v1/evaluation/quality/${evaluationId}`);
}
// 适配性评估相关接口
export function evaluateCompatibilityUsingPost(data: any) {
return post("/api/v1/evaluation/compatibility", data);
}
// 价值评估相关接口
export function evaluateValueUsingPost(data: any) {
return post("/api/v1/evaluation/value", data);
}
// 评估报告管理接口
export function queryEvaluationReportsUsingGet(params?: any) {
return get("/api/v1/evaluation/reports", params);
}
export function getEvaluationReportByIdUsingGet(reportId: string | number) {
return get(`/api/v1/evaluation/reports/${reportId}`);
}
export function exportEvaluationReportUsingGet(reportId: string | number, format = "PDF", filename?: string) {
return download(`/api/v1/evaluation/reports/${reportId}/export`, { format }, filename);
}
// 批量评估接口
export function batchEvaluationUsingPost(data: any) {
return post("/api/v1/evaluation/batch", data);
}
// 扩展功能接口(基于常见需求添加)
// 评估模板管理
export function queryEvaluationTemplatesUsingGet(params?: any) {
return get("/api/v1/evaluation/templates", params);
}
export function createEvaluationTemplateUsingPost(data: any) {
return post("/api/v1/evaluation/templates", data);
}
export function getEvaluationTemplateByIdUsingGet(templateId: string | number) {
return get(`/api/v1/evaluation/templates/${templateId}`);
}
export function updateEvaluationTemplateByIdUsingPut(templateId: string | number, data: any) {
return put(`/api/v1/evaluation/templates/${templateId}`, data);
}
export function deleteEvaluationTemplateByIdUsingDelete(templateId: string | number) {
return del(`/api/v1/evaluation/templates/${templateId}`);
}
// 评估历史记录
export function queryEvaluationHistoryUsingGet(params?: any) {
return get("/api/v1/evaluation/history", params);
}
export function getEvaluationHistoryByDatasetUsingGet(datasetId: string | number, params?: any) {
return get(`/api/v1/evaluation/history/dataset/${datasetId}`, params);
}
// 评估指标配置
export function queryQualityMetricsUsingGet() {
return get("/api/v1/evaluation/metrics/quality");
}
export function queryCompatibilityMetricsUsingGet() {
return get("/api/v1/evaluation/metrics/compatibility");
}
export function queryValueMetricsUsingGet() {
return get("/api/v1/evaluation/metrics/value");
}
// 评估规则管理
export function queryEvaluationRulesUsingGet(params?: any) {
return get("/api/v1/evaluation/rules", params);
}
export function createEvaluationRuleUsingPost(data: any) {
return post("/api/v1/evaluation/rules", data);
}
export function updateEvaluationRuleByIdUsingPut(ruleId: string | number, data: any) {
return put(`/api/v1/evaluation/rules/${ruleId}`, data);
}
export function deleteEvaluationRuleByIdUsingDelete(ruleId: string | number) {
return del(`/api/v1/evaluation/rules/${ruleId}`);
}
// 评估统计信息
export function getEvaluationStatisticsUsingGet(params?: any) {
return get("/api/v1/evaluation/statistics", params);
}
export function getDatasetEvaluationSummaryUsingGet(datasetId: string | number) {
return get(`/api/v1/evaluation/datasets/${datasetId}/summary`);
}
// 评估任务管理
export function queryEvaluationTasksUsingGet(params?: any) {
return get("/api/v1/evaluation/tasks", params);
}
export function cancelEvaluationTaskUsingPost(taskId: string | number) {
return post(`/api/v1/evaluation/tasks/${taskId}/cancel`);
}
export function retryEvaluationTaskUsingPost(taskId: string | number) {
return post(`/api/v1/evaluation/tasks/${taskId}/retry`);
}
// 评估结果比较
export function compareEvaluationResultsUsingPost(data: any) {
return post("/api/v1/evaluation/compare", data);
}
// 评估配置管理
export function getEvaluationConfigUsingGet() {
return get("/api/v1/evaluation/config");
}
export function updateEvaluationConfigUsingPut(data: any) {
return put("/api/v1/evaluation/config", data);
}
// 数据质量监控
export function createQualityMonitorUsingPost(data: any) {
return post("/api/v1/evaluation/quality/monitors", data);
}
export function queryQualityMonitorsUsingGet(params?: any) {
return get("/api/v1/evaluation/quality/monitors", params);
}
export function updateQualityMonitorByIdUsingPut(monitorId: string | number, data: any) {
return put(`/api/v1/evaluation/quality/monitors/${monitorId}`, data);
}
export function deleteQualityMonitorByIdUsingDelete(monitorId: string | number) {
return del(`/api/v1/evaluation/quality/monitors/${monitorId}`);
}
// 评估基准管理
export function queryEvaluationBenchmarksUsingGet(params?: any) {
return get("/api/v1/evaluation/benchmarks", params);
}
export function createEvaluationBenchmarkUsingPost(data: any) {
return post("/api/v1/evaluation/benchmarks", data);
}
export function updateEvaluationBenchmarkByIdUsingPut(benchmarkId: string | number, data: any) {
return put(`/api/v1/evaluation/benchmarks/${benchmarkId}`, data);
}
export function deleteEvaluationBenchmarkByIdUsingDelete(benchmarkId: string | number) {
return del(`/api/v1/evaluation/benchmarks/${benchmarkId}`);
}
// 评估算法管理
export function queryEvaluationAlgorithmsUsingGet(params?: any) {
return get("/api/v1/evaluation/algorithms", params);
}
export function runCustomEvaluationUsingPost(data: any) {
return post("/api/v1/evaluation/custom", data);
}
// 评估可视化数据
export function getEvaluationVisualizationUsingGet(evaluationId: string | number, chartType?: string) {
return get(`/api/v1/evaluation/${evaluationId}/visualization`, { chartType });
}
// 评估通知和警报
export function queryEvaluationAlertsUsingGet(params?: any) {
return get("/api/v1/evaluation/alerts", params);
}
export function createEvaluationAlertUsingPost(data: any) {
return post("/api/v1/evaluation/alerts", data);
}
export function updateEvaluationAlertByIdUsingPut(alertId: string | number, data: any) {
return put(`/api/v1/evaluation/alerts/${alertId}`, data);
}
export function deleteEvaluationAlertByIdUsingDelete(alertId: string | number) {
return del(`/api/v1/evaluation/alerts/${alertId}`);
}
// 批量操作扩展
export function batchDeleteEvaluationReportsUsingPost(data: { reportIds: string[] }) {
return post("/api/v1/evaluation/reports/batch-delete", data);
}
export function batchExportEvaluationReportsUsingPost(data: any) {
return post("/api/v1/evaluation/reports/batch-export", data);
}
// 评估调度管理
export function queryEvaluationSchedulesUsingGet(params?: any) {
return get("/api/v1/evaluation/schedules", params);
}
export function createEvaluationScheduleUsingPost(data: any) {
return post("/api/v1/evaluation/schedules", data);
}
export function updateEvaluationScheduleByIdUsingPut(scheduleId: string | number, data: any) {
return put(`/api/v1/evaluation/schedules/${scheduleId}`, data);
}
export function deleteEvaluationScheduleByIdUsingDelete(scheduleId: string | number) {
return del(`/api/v1/evaluation/schedules/${scheduleId}`);
}
export function enableEvaluationScheduleUsingPost(scheduleId: string | number) {
return post(`/api/v1/evaluation/schedules/${scheduleId}/enable`);
}
export function disableEvaluationScheduleUsingPost(scheduleId: string | number) {
return post(`/api/v1/evaluation/schedules/${scheduleId}/disable`);
}

View File

@@ -1,95 +1,95 @@
import { formatDateTime } from "@/utils/unit";
import { BarChart3 } from "lucide-react";
import { EvaluationStatus, EvaluationTask } from "@/pages/DataEvaluation/evaluation.model.ts";
export const TASK_TYPES = [
{ label: 'QA评估', value: 'QA' },
{ label: 'COT评估', value: 'COT' },
];
export const EVAL_METHODS = [
{ label: '模型自动评估', value: 'AUTO' },
];
export const getEvalType = (type: string) => {
return TASK_TYPES.find((item) => item.value === type)?.label;
};
export const getEvalMethod = (type: string) => {
return EVAL_METHODS.find((item) => item.value === type)?.label;
};
export const getSource = (type: string) => {
switch (type) {
case "DATASET":
return "数据集 - ";
case "SYNTHESIS":
return "合成任务 - ";
default:
return "-";
}
};
export const evalTaskStatusMap: Record<
string,
{
value: EvaluationStatus;
label: string;
color: string;
}
> = {
[EvaluationStatus.PENDING]: {
value: EvaluationStatus.PENDING,
label: "等待中",
color: "gray",
},
[EvaluationStatus.RUNNING]: {
value: EvaluationStatus.RUNNING,
label: "运行中",
color: "blue",
},
[EvaluationStatus.COMPLETED]: {
value: EvaluationStatus.COMPLETED,
label: "已完成",
color: "green",
},
[EvaluationStatus.FAILED]: {
value: EvaluationStatus.FAILED,
label: "失败",
color: "red",
},
[EvaluationStatus.PAUSED]: {
value: EvaluationStatus.PAUSED,
label: "已暂停",
color: "orange",
},
};
export function mapEvaluationTask(task: Partial<EvaluationTask>): EvaluationTask {
return {
...task,
status: evalTaskStatusMap[task.status || EvaluationStatus.PENDING],
createdAt: formatDateTime(task.createdAt),
updatedAt: formatDateTime(task.updatedAt),
description: task.description,
icon: <BarChart3 />,
iconColor: task.ratio_method === "DATASET" ? "bg-blue-100" : "bg-green-100",
statistics: [
{
label: "任务类型",
icon: <BarChart3 className="w-4 h-4 text-gray-500" />,
value: (task.taskType ?? 0).toLocaleString(),
},
{
label: "评估方式",
icon: <BarChart3 className="w-4 h-4 text-gray-500" />,
value: (task.evalMethod ?? 0).toLocaleString(),
},
{
label: "数据源",
icon: <BarChart3 className="w-4 h-4 text-gray-500" />,
value: (task.sourceName ?? 0).toLocaleString(),
},
],
};
}
import { formatDateTime } from "@/utils/unit";
import { BarChart3 } from "lucide-react";
import { EvaluationStatus, EvaluationTask } from "@/pages/DataEvaluation/evaluation.model.ts";
export const TASK_TYPES = [
{ label: 'QA评估', value: 'QA' },
{ label: 'COT评估', value: 'COT' },
];
export const EVAL_METHODS = [
{ label: '模型自动评估', value: 'AUTO' },
];
export const getEvalType = (type: string) => {
return TASK_TYPES.find((item) => item.value === type)?.label;
};
export const getEvalMethod = (type: string) => {
return EVAL_METHODS.find((item) => item.value === type)?.label;
};
export const getSource = (type: string) => {
switch (type) {
case "DATASET":
return "数据集 - ";
case "SYNTHESIS":
return "合成任务 - ";
default:
return "-";
}
};
export const evalTaskStatusMap: Record<
string,
{
value: EvaluationStatus;
label: string;
color: string;
}
> = {
[EvaluationStatus.PENDING]: {
value: EvaluationStatus.PENDING,
label: "等待中",
color: "gray",
},
[EvaluationStatus.RUNNING]: {
value: EvaluationStatus.RUNNING,
label: "运行中",
color: "blue",
},
[EvaluationStatus.COMPLETED]: {
value: EvaluationStatus.COMPLETED,
label: "已完成",
color: "green",
},
[EvaluationStatus.FAILED]: {
value: EvaluationStatus.FAILED,
label: "失败",
color: "red",
},
[EvaluationStatus.PAUSED]: {
value: EvaluationStatus.PAUSED,
label: "已暂停",
color: "orange",
},
};
export function mapEvaluationTask(task: Partial<EvaluationTask>): EvaluationTask {
return {
...task,
status: evalTaskStatusMap[task.status || EvaluationStatus.PENDING],
createdAt: formatDateTime(task.createdAt),
updatedAt: formatDateTime(task.updatedAt),
description: task.description,
icon: <BarChart3 />,
iconColor: task.ratio_method === "DATASET" ? "bg-blue-100" : "bg-green-100",
statistics: [
{
label: "任务类型",
icon: <BarChart3 className="w-4 h-4 text-gray-500" />,
value: (task.taskType ?? 0).toLocaleString(),
},
{
label: "评估方式",
icon: <BarChart3 className="w-4 h-4 text-gray-500" />,
value: (task.evalMethod ?? 0).toLocaleString(),
},
{
label: "数据源",
icon: <BarChart3 className="w-4 h-4 text-gray-500" />,
value: (task.sourceName ?? 0).toLocaleString(),
},
],
};
}

View File

@@ -1,57 +1,57 @@
export enum EvaluationStatus {
PENDING = "PENDING",
RUNNING = "RUNNING",
COMPLETED = "COMPLETED",
FAILED = "FAILED",
PAUSED = "PAUSED",
}
export interface EvaluationTask {
id: string;
name: string;
description?: string;
taskType: string;
sourceType: string;
sourceId: string;
sourceName: string;
status: 'PENDING' | 'RUNNING' | 'COMPLETED' | 'STOPPED' | 'FAILED';
evalProcess: number;
evalMethod: 'AUTO' | 'MANUAL';
createdAt: string;
updatedAt: string;
}
interface EvaluationDimension {
id: string
name: string
description: string
category: "quality" | "accuracy" | "completeness" | "consistency" | "bias" | "custom"
isCustom?: boolean
isEnabled?: boolean
}
interface EvaluationSlice {
id: string
content: string
sourceFile: string
sliceIndex: number
sliceType: string
metadata: {
startPosition?: number
endPosition?: number
pageNumber?: number
section?: string
processingMethod: string
}
scores?: { [dimensionId: string]: number }
comment?: string
}
interface QAPair {
id: string
question: string
answer: string
sliceId: string
score: number
feedback?: string
}
export enum EvaluationStatus {
PENDING = "PENDING",
RUNNING = "RUNNING",
COMPLETED = "COMPLETED",
FAILED = "FAILED",
PAUSED = "PAUSED",
}
export interface EvaluationTask {
id: string;
name: string;
description?: string;
taskType: string;
sourceType: string;
sourceId: string;
sourceName: string;
status: 'PENDING' | 'RUNNING' | 'COMPLETED' | 'STOPPED' | 'FAILED';
evalProcess: number;
evalMethod: 'AUTO' | 'MANUAL';
createdAt: string;
updatedAt: string;
}
interface EvaluationDimension {
id: string
name: string
description: string
category: "quality" | "accuracy" | "completeness" | "consistency" | "bias" | "custom"
isCustom?: boolean
isEnabled?: boolean
}
interface EvaluationSlice {
id: string
content: string
sourceFile: string
sliceIndex: number
sliceType: string
metadata: {
startPosition?: number
endPosition?: number
pageNumber?: number
section?: string
processingMethod: string
}
scores?: { [dimensionId: string]: number }
comment?: string
}
interface QAPair {
id: string
question: string
answer: string
sliceId: string
score: number
feedback?: string
}