You've already forked DataMate
feat: fix the problem in the Operator Market frontend pages
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user