Files
AIclinicalresearch/frontend-v2/src/modules/dc/pages/tool-c/components/DropnaDialog.tsx
HaHafeng 57fdc6ef00 feat(aia): Integrate PromptService for 10 AI agents
Features:
- Migrate 10 agent prompts from hardcoded to database
- Add grayscale preview support (DRAFT/ACTIVE distribution)
- Implement 3-tier fallback (DB -> Cache -> Hardcoded)
- Add version management and rollback capability

Files changed:
- backend/scripts/migrate-aia-prompts.ts (new migration script)
- backend/src/common/prompt/prompt.fallbacks.ts (add AIA fallbacks)
- backend/src/modules/aia/services/agentService.ts (integrate PromptService)
- backend/src/modules/aia/services/conversationService.ts (pass userId)
- backend/src/modules/aia/types/index.ts (fix AgentStage type)

Documentation:
- docs/03-业务模块/AIA-AI智能问答/06-开发记录/2026-01-18-Prompt管理系统集成.md
- docs/02-通用能力层/00-通用能力层清单.md (add FileCard, Prompt management)
- docs/00-系统总体设计/00-系统当前状态与开发指南.md (update to v3.6)

Prompt codes:
- AIA_SCIENTIFIC_QUESTION, AIA_PICO_ANALYSIS, AIA_TOPIC_EVALUATION
- AIA_OUTCOME_DESIGN, AIA_CRF_DESIGN, AIA_SAMPLE_SIZE
- AIA_PROTOCOL_WRITING, AIA_METHODOLOGY_REVIEW
- AIA_PAPER_POLISH, AIA_PAPER_TRANSLATE

Tested: Migration script executed, all 10 prompts inserted successfully
2026-01-18 15:48:53 +08:00

357 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;