Files
AIclinicalresearch/frontend-v2/src/modules/rvw/components/AgentModal.tsx
HaHafeng c3554fd61d feat(rvw): harden json parsing and finalize 0316 rollout
Stabilize RVW editorial and methodology JSON parsing in production with layered repair and fallback handling, then publish the paired frontend task-level language selector updates. Also reset deployment checklist, record the 0316 deployment summary, and refresh the SAE runtime status with latest backend/frontend IPs.

Made-with: Cursor
2026-03-16 00:24:33 +08:00

221 lines
7.7 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 { useEffect, useState } from 'react';
import { PlayCircle, X } from 'lucide-react';
import type { AgentType, EditorialBaseStandard } from '../types';
interface AgentModalProps {
visible: boolean;
taskCount: number;
onClose: () => void;
onConfirm: (agents: AgentType[], editorialBaseStandard?: EditorialBaseStandard) => void;
defaultEditorialBaseStandard?: EditorialBaseStandard;
isSubmitting?: boolean; // 🔒 防止重复提交
}
export default function AgentModal({
visible,
taskCount,
onClose,
onConfirm,
defaultEditorialBaseStandard = 'zh',
isSubmitting = false,
}: AgentModalProps) {
const [selectedAgents, setSelectedAgents] = useState<AgentType[]>(['editorial']);
const [editorialBaseStandard, setEditorialBaseStandard] = useState<EditorialBaseStandard>(defaultEditorialBaseStandard);
useEffect(() => {
if (!visible) return;
setSelectedAgents(['editorial']);
setEditorialBaseStandard(defaultEditorialBaseStandard);
}, [visible, defaultEditorialBaseStandard]);
const toggleAgent = (agent: AgentType) => {
if (selectedAgents.includes(agent)) {
// 至少保留一个
if (selectedAgents.length > 1) {
setSelectedAgents(selectedAgents.filter(a => a !== agent));
}
} else {
setSelectedAgents([...selectedAgents, agent]);
}
};
const handleConfirm = () => {
// 只调用onConfirm让调用方控制关闭时机
onConfirm(
selectedAgents,
selectedAgents.includes('editorial') ? editorialBaseStandard : undefined
);
};
if (!visible) return null;
return (
<div className="fixed inset-0 bg-slate-900/50 z-50 flex items-center justify-center backdrop-blur-sm">
<div className="bg-white rounded-2xl shadow-2xl w-[560px] max-w-[92vw] overflow-hidden transform transition-all scale-100 fade-in">
{/* 头部 */}
<div className="bg-slate-900 p-5 text-white flex items-center justify-between">
<h3 className="font-bold text-lg flex items-center gap-2">
<PlayCircle className="w-5 h-5 text-indigo-400" />
稿
</h3>
<button onClick={onClose} className="text-slate-400 hover:text-white transition-colors">
<X className="w-5 h-5" />
</button>
</div>
{/* 内容 */}
<div className="p-6 space-y-4">
<p className="text-sm text-slate-600 mb-4">
{taskCount > 1 ? `已选择 ${taskCount} 个稿件,请选择审稿维度:` : '请选择审稿维度:'}
</p>
{/* 规范性智能体 */}
<label
className={`flex items-start gap-3 p-4 border rounded-xl cursor-pointer transition-all ${
selectedAgents.includes('editorial')
? 'border-indigo-500 bg-indigo-50'
: 'border-gray-200 hover:border-indigo-300'
}`}
>
<input
type="checkbox"
checked={selectedAgents.includes('editorial')}
onChange={() => toggleAgent('editorial')}
className="mt-1 w-4 h-4 text-indigo-600 rounded border-gray-300 focus:ring-indigo-500"
/>
<div className="flex-1">
<span className="block font-bold text-slate-800 text-sm">稿</span>
<span className="block text-xs text-slate-500 mt-0.5">
11
</span>
</div>
<span className="tag tag-blue"></span>
</label>
{selectedAgents.includes('editorial') && (
<div className="rounded-xl border border-sky-200 bg-sky-50 p-4 mt-2">
<div className="text-sm font-bold text-slate-800 mb-2">稿线</div>
<div className="flex items-center gap-8">
<label className="flex items-center gap-2 text-sm text-slate-700 cursor-pointer">
<input
type="radio"
name="editorialBaseStandard"
checked={editorialBaseStandard === 'zh'}
onChange={() => setEditorialBaseStandard('zh')}
className="w-4 h-4 text-indigo-600 border-gray-300 focus:ring-indigo-500"
/>
</label>
<label className="flex items-center gap-2 text-sm text-slate-700 cursor-pointer">
<input
type="radio"
name="editorialBaseStandard"
checked={editorialBaseStandard === 'en'}
onChange={() => setEditorialBaseStandard('en')}
className="w-4 h-4 text-indigo-600 border-gray-300 focus:ring-indigo-500"
/>
</label>
</div>
</div>
)}
{/* 方法学智能体 */}
<label
className={`flex items-start gap-3 p-4 border rounded-xl cursor-pointer transition-all ${
selectedAgents.includes('methodology')
? 'border-indigo-500 bg-indigo-50'
: 'border-gray-200 hover:border-indigo-300'
}`}
>
<input
type="checkbox"
checked={selectedAgents.includes('methodology')}
onChange={() => toggleAgent('methodology')}
className="mt-1 w-4 h-4 text-indigo-600 rounded border-gray-300 focus:ring-indigo-500"
/>
<div className="flex-1">
<span className="block font-bold text-slate-800 text-sm"></span>
<span className="block text-xs text-slate-500 mt-0.5">
20
</span>
</div>
<span className="tag tag-purple"></span>
</label>
{/* 临床专业评估智能体 */}
<label
className={`flex items-start gap-3 p-4 border rounded-xl cursor-pointer transition-all ${
selectedAgents.includes('clinical')
? 'border-indigo-500 bg-indigo-50'
: 'border-gray-200 hover:border-indigo-300'
}`}
>
<input
type="checkbox"
checked={selectedAgents.includes('clinical')}
onChange={() => toggleAgent('clinical')}
className="mt-1 w-4 h-4 text-indigo-600 rounded border-gray-300 focus:ring-indigo-500"
/>
<div className="flex-1">
<span className="block font-bold text-slate-800 text-sm"></span>
<span className="block text-xs text-slate-500 mt-0.5">
FINER
</span>
</div>
<span className="tag tag-pink"></span>
</label>
</div>
{/* 底部按钮 */}
<div className="p-4 bg-slate-50 flex justify-end gap-3 border-t border-gray-100">
<button
onClick={onClose}
className="px-4 py-2 text-sm text-slate-600 hover:bg-gray-200 rounded-lg transition-colors"
>
</button>
<button
onClick={handleConfirm}
disabled={selectedAgents.length === 0 || isSubmitting}
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-bold rounded-lg shadow-sm transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{isSubmitting ? '提交中...' : '立即运行'}
</button>
</div>
</div>
</div>
);
}