Files
AIclinicalresearch/frontend-v2/src/modules/dc/pages/tool-c/components/DropnaDialog.tsx
HaHafeng 06028c6952 feat(pkb): implement complete batch processing workflow and frontend optimization
- Frontend V3 architecture migration to modules/pkb
- Implement three work modes: full-text reading, deep reading, batch processing
- Complete batch processing: template selection, progress display, result export (CSV)
- Integrate Ant Design X Chat component with streaming support
- Add document upload modal with drag-and-drop support
- Optimize UI: multi-line table display, citation formatting, auto-scroll
- Fix 10+ technical issues: API mapping, state sync, form clearing
- Update documentation: development records and module status

Performance: 3 docs batch processing ~17-28s
Status: PKB module now production-ready (90% complete)
2026-01-07 18:23:43 +08:00

342 lines
9.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from 'react';
import { Modal, Radio, Button, Slider, Alert, App, Statistic, Row, Col, Checkbox } from 'antd';
import { Trash2, AlertTriangle } from 'lucide-react';
interface Props {
visible: boolean;
onClose: () => void;
onApply: (newData: any[]) => void;
columns: Array<{ id: string; name: string }>;
data: any[];
sessionId: string | null;
}
const DropnaDialog: React.FC<Props> = ({
visible,
onClose,
onApply,
columns,
data,
sessionId,
}) => {
const { message } = App.useApp();
const [method, setMethod] = useState<'row' | 'column' | 'both'>('row');
const [threshold, setThreshold] = useState<number>(50); // 百分比
const [selectedColumns, setSelectedColumns] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
// 计算缺失值统计
const [missingStats, setMissingStats] = useState<{
totalMissing: number;
rowsWithMissing: number;
colsWithMissing: number;
}>({ totalMissing: 0, rowsWithMissing: 0, colsWithMissing: 0 });
useEffect(() => {
if (visible && data.length > 0) {
// 计算缺失值
let totalMissing = 0;
let rowsWithMissing = 0;
const colsMissing: Record<string, number> = {};
data.forEach(row => {
let rowHasMissing = false;
Object.keys(row).forEach(key => {
const value = row[key];
if (value === null || value === undefined || value === '') {
totalMissing++;
colsMissing[key] = (colsMissing[key] || 0) + 1;
rowHasMissing = true;
}
});
if (rowHasMissing) {
rowsWithMissing++;
}
});
setMissingStats({
totalMissing,
rowsWithMissing,
colsWithMissing: Object.keys(colsMissing).length,
});
}
}, [visible, data]);
// 执行
const handleApply = async () => {
if (!sessionId) {
message.error('Session ID不存在');
return;
}
// 验证
if (method === 'row' && selectedColumns.length === 0) {
// 删除所有行:不需要指定列
}
setLoading(true);
try {
const response = await fetch('/api/v1/dc/tool-c/quick-action', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sessionId,
action: 'dropna',
params: {
method,
threshold: threshold / 100, // 转换为0-1
subset: selectedColumns.length > 0 ? selectedColumns : undefined,
},
}),
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error || '删除缺失值失败');
}
message.success('删除缺失值成功!');
// 更新父组件数据
if (result.data?.newDataPreview) {
onApply(result.data.newDataPreview);
}
// 成功后关闭
onClose();
} catch (error: any) {
message.error(error.message || '执行失败');
} finally {
setLoading(false);
}
};
return (
<Modal
title={
<div className="flex items-center gap-2">
<Trash2 size={20} className="text-red-500" />
<span></span>
</div>
}
open={visible}
onCancel={onClose}
width={700}
footer={[
<Button key="cancel" onClick={onClose}>
</Button>,
<Button
key="apply"
type="primary"
danger
onClick={handleApply}
loading={loading}
icon={<Trash2 size={16} />}
>
</Button>,
]}
>
<div className="space-y-4">
{/* 缺失值统计 */}
<Alert
message="当前数据缺失值统计"
description={
<Row gutter={16} className="mt-3">
<Col span={8}>
<Statistic
title="总缺失值"
value={missingStats.totalMissing}
suffix={`/ ${data.length * columns.length} 单元格`}
/>
</Col>
<Col span={8}>
<Statistic
title="含缺失值的行"
value={missingStats.rowsWithMissing}
suffix={`/ ${data.length}`}
/>
</Col>
<Col span={8}>
<Statistic
title="含缺失值的列"
value={missingStats.colsWithMissing}
suffix={`/ ${columns.length}`}
/>
</Col>
</Row>
}
type="info"
showIcon
className="mb-4"
/>
{/* 删除方式 */}
<div>
<label className="text-sm font-medium text-slate-700 mb-2 block">
</label>
<Radio.Group
value={method}
onChange={(e) => setMethod(e.target.value)}
className="w-full"
>
<div className="space-y-3">
<div className="border rounded-lg p-3 hover:border-blue-400 transition-colors">
<Radio value="row">
<div className="ml-2">
<div className="font-medium"></div>
<div className="text-xs text-slate-500 mt-1">
</div>
</div>
</Radio>
</div>
<div className="border rounded-lg p-3 hover:border-blue-400 transition-colors">
<Radio value="column">
<div className="ml-2">
<div className="font-medium"></div>
<div className="text-xs text-slate-500 mt-1">
</div>
</div>
</Radio>
</div>
<div className="border rounded-lg p-3 hover:border-blue-400 transition-colors">
<Radio value="both">
<div className="ml-2">
<div className="font-medium"></div>
<div className="text-xs text-slate-500 mt-1">
</div>
</div>
</Radio>
</div>
</div>
</Radio.Group>
</div>
{/* 缺失率阈值(仅对按列删除和先删列再删行有效) */}
{(method === 'column' || method === 'both') && (
<div>
<label className="text-sm font-medium text-slate-700 mb-2 block">
{threshold}%
</label>
<Slider
min={0}
max={100}
value={threshold}
onChange={setThreshold}
marks={{
0: '0%',
30: '30%',
50: '50%',
70: '70%',
100: '100%',
}}
/>
<div className="text-xs text-slate-500 mt-2">
{threshold}%
</div>
</div>
)}
{/* 仅检查指定列(仅对按行删除有效) */}
{method === 'row' && (
<div>
<label className="text-sm font-medium text-slate-700 mb-2 block">
</label>
<div className="border rounded-lg p-3 max-h-48 overflow-y-auto">
<Checkbox.Group
value={selectedColumns}
onChange={setSelectedColumns}
className="flex flex-col gap-2"
>
{columns.map((col) => (
<Checkbox key={col.id} value={col.id}>
{col.name}
</Checkbox>
))}
</Checkbox.Group>
</div>
<div className="text-xs text-slate-500 mt-2">
</div>
</div>
)}
{/* 警告提示 */}
<Alert
message={
<div className="flex items-center gap-2">
<AlertTriangle size={16} />
<span className="font-semibold"></span>
</div>
}
description={
<div className="text-xs space-y-1 mt-1">
<div> </div>
<div> 使AI对话功能探索数据</div>
<div> </div>
<div> 使"多重插补"</div>
</div>
}
type="warning"
showIcon={false}
className="bg-orange-50 border-orange-200"
/>
</div>
</Modal>
);
};
export default DropnaDialog;