feat(frontend): Day 7 - frontend complete layout finished
This commit is contained in:
@@ -1,14 +1,18 @@
|
||||
import { Routes, Route, Navigate } from 'react-router-dom'
|
||||
import MainLayout from './layouts/MainLayout'
|
||||
import HomePage from './pages/HomePage'
|
||||
import AgentPage from './pages/AgentPage'
|
||||
import AgentChatPage from './pages/AgentChatPage'
|
||||
import KnowledgePage from './pages/KnowledgePage'
|
||||
import HistoryPage from './pages/HistoryPage'
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<MainLayout />}>
|
||||
<Route index element={<HomePage />} />
|
||||
<Route path="agent/:agentId" element={<AgentPage />} />
|
||||
<Route path="agent/:agentId" element={<AgentChatPage />} />
|
||||
<Route path="knowledge" element={<KnowledgePage />} />
|
||||
<Route path="history" element={<HistoryPage />} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
|
||||
96
frontend/src/components/CreateProjectDialog.tsx
Normal file
96
frontend/src/components/CreateProjectDialog.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import { Modal, Form, Input, Radio, message } from 'antd';
|
||||
import { useProjectStore, Project } from '../stores/useProjectStore';
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
export const CreateProjectDialog = () => {
|
||||
const [form] = Form.useForm();
|
||||
const { showCreateDialog, setShowCreateDialog, addProject } = useProjectStore();
|
||||
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
|
||||
// 模拟创建项目(后续会调用真实API)
|
||||
const newProject: Project = {
|
||||
id: `proj-${Date.now()}`,
|
||||
name: values.name,
|
||||
background: values.background || '',
|
||||
researchType: values.researchType,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
addProject(newProject);
|
||||
message.success('项目创建成功');
|
||||
form.resetFields();
|
||||
setShowCreateDialog(false);
|
||||
} catch (error) {
|
||||
console.error('表单验证失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
form.resetFields();
|
||||
setShowCreateDialog(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="创建新项目"
|
||||
open={showCreateDialog}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
width={600}
|
||||
okText="创建"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{ researchType: 'observational' }}
|
||||
>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="项目名称"
|
||||
rules={[
|
||||
{ required: true, message: '请输入项目名称' },
|
||||
{ max: 100, message: '项目名称不能超过100个字符' },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="例如:心血管疾病与生活方式关系研究" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="researchType"
|
||||
label="研究类型"
|
||||
rules={[{ required: true, message: '请选择研究类型' }]}
|
||||
>
|
||||
<Radio.Group>
|
||||
<Radio value="observational">观察性研究</Radio>
|
||||
<Radio value="interventional">干预性研究</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="background"
|
||||
label="项目背景"
|
||||
tooltip="可以包括:研究背景、目标人群、研究目的等"
|
||||
>
|
||||
<TextArea
|
||||
rows={6}
|
||||
placeholder="请详细描述项目背景信息,AI将基于这些信息提供更精准的建议...
|
||||
|
||||
例如:
|
||||
研究背景:心血管疾病是全球主要死因之一...
|
||||
目标人群:18-65岁成年人
|
||||
研究目的:探索生活方式与心血管疾病的关系"
|
||||
maxLength={2000}
|
||||
showCount
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
105
frontend/src/components/EditProjectDialog.tsx
Normal file
105
frontend/src/components/EditProjectDialog.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import { Modal, Form, Input, Radio, message } from 'antd';
|
||||
import { useEffect } from 'react';
|
||||
import { useProjectStore } from '../stores/useProjectStore';
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
export const EditProjectDialog = () => {
|
||||
const [form] = Form.useForm();
|
||||
const {
|
||||
currentProject,
|
||||
showEditDialog,
|
||||
setShowEditDialog,
|
||||
updateProject,
|
||||
} = useProjectStore();
|
||||
|
||||
// 当对话框打开时,填充表单
|
||||
useEffect(() => {
|
||||
if (showEditDialog && currentProject) {
|
||||
form.setFieldsValue({
|
||||
name: currentProject.name,
|
||||
researchType: currentProject.researchType,
|
||||
background: currentProject.background,
|
||||
});
|
||||
}
|
||||
}, [showEditDialog, currentProject, form]);
|
||||
|
||||
const handleOk = async () => {
|
||||
if (!currentProject) return;
|
||||
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
|
||||
// 模拟更新项目(后续会调用真实API)
|
||||
updateProject(currentProject.id, {
|
||||
name: values.name,
|
||||
background: values.background || '',
|
||||
researchType: values.researchType,
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
message.success('项目更新成功');
|
||||
setShowEditDialog(false);
|
||||
} catch (error) {
|
||||
console.error('表单验证失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
form.resetFields();
|
||||
setShowEditDialog(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="编辑项目"
|
||||
open={showEditDialog}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
width={600}
|
||||
okText="保存"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="项目名称"
|
||||
rules={[
|
||||
{ required: true, message: '请输入项目名称' },
|
||||
{ max: 100, message: '项目名称不能超过100个字符' },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="例如:心血管疾病与生活方式关系研究" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="researchType"
|
||||
label="研究类型"
|
||||
rules={[{ required: true, message: '请选择研究类型' }]}
|
||||
>
|
||||
<Radio.Group>
|
||||
<Radio value="observational">观察性研究</Radio>
|
||||
<Radio value="interventional">干预性研究</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="background"
|
||||
label="项目背景"
|
||||
tooltip="可以包括:研究背景、目标人群、研究目的等"
|
||||
>
|
||||
<TextArea
|
||||
rows={6}
|
||||
placeholder="请详细描述项目背景信息,AI将基于这些信息提供更精准的建议..."
|
||||
maxLength={2000}
|
||||
showCount
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
75
frontend/src/components/ProjectSelector.tsx
Normal file
75
frontend/src/components/ProjectSelector.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Select, Button, Space, Tooltip } from 'antd';
|
||||
import { PlusOutlined, EditOutlined, FolderOpenOutlined } from '@ant-design/icons';
|
||||
import { useProjectStore } from '../stores/useProjectStore';
|
||||
|
||||
export const ProjectSelector = () => {
|
||||
const {
|
||||
currentProject,
|
||||
projects,
|
||||
setCurrentProject,
|
||||
setShowCreateDialog,
|
||||
setShowEditDialog,
|
||||
} = useProjectStore();
|
||||
|
||||
const handleProjectChange = (projectId: string) => {
|
||||
if (projectId === 'global') {
|
||||
setCurrentProject(null);
|
||||
} else {
|
||||
const project = projects.find((p) => p.id === projectId);
|
||||
if (project) {
|
||||
setCurrentProject(project);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4 border-b border-gray-200">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<FolderOpenOutlined className="text-gray-400" />
|
||||
<span className="text-sm font-medium text-gray-700">当前项目</span>
|
||||
</div>
|
||||
|
||||
<Space.Compact block>
|
||||
<Select
|
||||
value={currentProject?.id || 'global'}
|
||||
onChange={handleProjectChange}
|
||||
style={{ width: '100%' }}
|
||||
placeholder="选择项目"
|
||||
>
|
||||
<Select.Option value="global">
|
||||
<span className="text-blue-600">全局快速问答</span>
|
||||
</Select.Option>
|
||||
|
||||
{projects.map((project) => (
|
||||
<Select.Option key={project.id} value={project.id}>
|
||||
{project.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
<Tooltip title="创建新项目">
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => setShowCreateDialog(true)}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
{currentProject && (
|
||||
<Tooltip title="编辑项目">
|
||||
<Button
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => setShowEditDialog(true)}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space.Compact>
|
||||
|
||||
{currentProject && (
|
||||
<div className="mt-2 text-xs text-gray-500 truncate">
|
||||
{currentProject.background || '未设置项目背景'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,28 +6,38 @@ import {
|
||||
MenuUnfoldOutlined,
|
||||
HomeOutlined,
|
||||
ExperimentOutlined,
|
||||
FolderOpenOutlined,
|
||||
HistoryOutlined,
|
||||
UserOutlined,
|
||||
SettingOutlined,
|
||||
LogoutOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import type { MenuProps } from 'antd'
|
||||
import { ProjectSelector } from '../components/ProjectSelector'
|
||||
import { CreateProjectDialog } from '../components/CreateProjectDialog'
|
||||
import { EditProjectDialog } from '../components/EditProjectDialog'
|
||||
|
||||
const { Header, Sider, Content } = Layout
|
||||
|
||||
// 12个智能体配置
|
||||
const AGENTS = [
|
||||
{ id: 'topic-evaluation', name: '选题评价智能体', icon: '🎯' },
|
||||
{ id: 'scientific-question', name: '科学问题梳理智能体', icon: '🔬' },
|
||||
{ id: 'picos-construction', name: 'PICOS构建智能体', icon: '📋' },
|
||||
{ id: 'observation-design', name: '观察指标设计智能体', icon: '📊' },
|
||||
{ id: 'crf-development', name: 'CRF制定智能体', icon: '📝' },
|
||||
{ id: 'sample-size', name: '样本量计算智能体', icon: '🔢' },
|
||||
{ id: 'protocol-writing', name: '临床研究方案撰写智能体', icon: '📄' },
|
||||
{ id: 'paper-polishing', name: '论文润色智能体', icon: '✨' },
|
||||
{ id: 'paper-translation', name: '论文翻译智能体', icon: '🌐' },
|
||||
{ id: 'methodology-review', name: '方法学评审智能体', icon: '🔍' },
|
||||
{ id: 'journal-methodology-review', name: '期刊方法学评审智能体', icon: '📑' },
|
||||
{ id: 'journal-guidelines-review', name: '期刊稿约评审智能体', icon: '✅' },
|
||||
// 12个智能体配置(已按PRD更正)
|
||||
export const AGENTS = [
|
||||
// 选题阶段
|
||||
{ id: 'topic-evaluation', name: '选题评价智能体', icon: '🎯', enabled: true },
|
||||
{ id: 'scientific-question', name: '科学问题梳理智能体', icon: '🔬', enabled: false },
|
||||
{ id: 'picos-construction', name: 'PICOS构建智能体', icon: '📋', enabled: false },
|
||||
|
||||
// 研究设计阶段
|
||||
{ id: 'observation-design', name: '观察指标设计智能体', icon: '📊', enabled: false },
|
||||
{ id: 'crf-development', name: 'CRF制定智能体', icon: '📝', enabled: false },
|
||||
{ id: 'sample-size', name: '样本量计算智能体', icon: '🔢', enabled: false },
|
||||
{ id: 'protocol-writing', name: '临床研究方案撰写智能体', icon: '📄', enabled: false },
|
||||
|
||||
// 论文撰写阶段
|
||||
{ id: 'paper-polishing', name: '论文润色智能体', icon: '✨', enabled: false },
|
||||
{ id: 'paper-translation', name: '论文翻译智能体', icon: '🌐', enabled: false },
|
||||
{ id: 'methodology-review', name: '方法学评审智能体', icon: '🔍', enabled: false },
|
||||
{ id: 'journal-methodology-review', name: '期刊方法学评审智能体', icon: '📑', enabled: false },
|
||||
{ id: 'journal-guidelines-review', name: '期刊稿约评审智能体', icon: '✅', enabled: false },
|
||||
]
|
||||
|
||||
const MainLayout = () => {
|
||||
@@ -49,8 +59,19 @@ const MainLayout = () => {
|
||||
children: AGENTS.map((agent) => ({
|
||||
key: `/agent/${agent.id}`,
|
||||
label: `${agent.icon} ${agent.name}`,
|
||||
disabled: !agent.enabled,
|
||||
})),
|
||||
},
|
||||
{
|
||||
key: '/knowledge',
|
||||
icon: <FolderOpenOutlined />,
|
||||
label: '知识库管理',
|
||||
},
|
||||
{
|
||||
key: '/history',
|
||||
icon: <HistoryOutlined />,
|
||||
label: '历史记录',
|
||||
},
|
||||
]
|
||||
|
||||
// 用户下拉菜单
|
||||
@@ -90,7 +111,15 @@ const MainLayout = () => {
|
||||
return (
|
||||
<Layout style={{ height: '100vh' }}>
|
||||
{/* 侧边栏 */}
|
||||
<Sider trigger={null} collapsible collapsed={collapsed} theme="light">
|
||||
<Sider
|
||||
trigger={null}
|
||||
collapsible
|
||||
collapsed={collapsed}
|
||||
theme="light"
|
||||
width={280}
|
||||
style={{ overflow: 'auto' }}
|
||||
>
|
||||
{/* Logo区域 */}
|
||||
<div
|
||||
style={{
|
||||
height: 64,
|
||||
@@ -105,6 +134,11 @@ const MainLayout = () => {
|
||||
>
|
||||
{collapsed ? '🏥' : '🏥 AI临床研究'}
|
||||
</div>
|
||||
|
||||
{/* 项目选择器 */}
|
||||
{!collapsed && <ProjectSelector />}
|
||||
|
||||
{/* 导航菜单 */}
|
||||
<Menu
|
||||
mode="inline"
|
||||
selectedKeys={[location.pathname]}
|
||||
@@ -160,9 +194,12 @@ const MainLayout = () => {
|
||||
<Outlet />
|
||||
</Content>
|
||||
</Layout>
|
||||
|
||||
{/* 对话框组件 */}
|
||||
<CreateProjectDialog />
|
||||
<EditProjectDialog />
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default MainLayout
|
||||
|
||||
|
||||
167
frontend/src/pages/AgentChatPage.tsx
Normal file
167
frontend/src/pages/AgentChatPage.tsx
Normal file
@@ -0,0 +1,167 @@
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { Card, Typography, Input, Button, Space, Select, Upload, Tag, Alert, Divider } from 'antd'
|
||||
import {
|
||||
SendOutlined,
|
||||
PaperClipOutlined,
|
||||
RobotOutlined,
|
||||
FolderOpenOutlined,
|
||||
SyncOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { AGENTS } from '../layouts/MainLayout'
|
||||
|
||||
const { Title, Paragraph } = Typography
|
||||
const { TextArea } = Input
|
||||
|
||||
const AgentChatPage = () => {
|
||||
const { agentId } = useParams()
|
||||
const agent = AGENTS.find((a) => a.id === agentId)
|
||||
|
||||
if (!agent) {
|
||||
return (
|
||||
<Alert
|
||||
message="智能体不存在"
|
||||
description="请从首页选择一个智能体"
|
||||
type="error"
|
||||
showIcon
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (!agent.enabled) {
|
||||
return (
|
||||
<Alert
|
||||
message="该智能体正在开发中"
|
||||
description="敬请期待后续版本..."
|
||||
type="warning"
|
||||
showIcon
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||
{/* 标题栏 */}
|
||||
<Card style={{ marginBottom: 16 }}>
|
||||
<Space align="center">
|
||||
<span style={{ fontSize: 32 }}>{agent.icon}</span>
|
||||
<div>
|
||||
<Title level={3} style={{ marginBottom: 4 }}>
|
||||
{agent.name}
|
||||
</Title>
|
||||
<Paragraph type="secondary" style={{ marginBottom: 0 }}>
|
||||
当前模型:DeepSeek-V3
|
||||
</Paragraph>
|
||||
</div>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
{/* 聊天区域 */}
|
||||
<Card
|
||||
style={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
bodyStyle={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
{/* 消息列表区域(占位符) */}
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: 24,
|
||||
overflowY: 'auto',
|
||||
background: '#fafafa',
|
||||
}}
|
||||
>
|
||||
<div style={{ textAlign: 'center', padding: '60px 0', color: '#999' }}>
|
||||
<RobotOutlined style={{ fontSize: 64, marginBottom: 16 }} />
|
||||
<div>开始对话,我将为您提供专业的研究建议</div>
|
||||
</div>
|
||||
|
||||
{/* 消息示例(占位符) */}
|
||||
<div style={{ maxWidth: 800, margin: '0 auto' }}>
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Tag color="blue" style={{ marginBottom: 8 }}>
|
||||
用户
|
||||
</Tag>
|
||||
<Card size="small">
|
||||
<Paragraph style={{ marginBottom: 0 }}>
|
||||
这是一个示例消息...(开发中)
|
||||
</Paragraph>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Tag color="green" style={{ marginBottom: 8 }}>
|
||||
AI助手
|
||||
</Tag>
|
||||
<Card size="small">
|
||||
<Paragraph style={{ marginBottom: 0 }}>
|
||||
这是AI的回复示例...(开发中)
|
||||
</Paragraph>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider style={{ margin: 0 }} />
|
||||
|
||||
{/* 输入区域 */}
|
||||
<div style={{ padding: 16, background: '#fff' }}>
|
||||
{/* 工具栏 */}
|
||||
<Space style={{ marginBottom: 12, width: '100%', justifyContent: 'space-between' }}>
|
||||
<Space>
|
||||
<Upload showUploadList={false}>
|
||||
<Button icon={<PaperClipOutlined />}>上传文件</Button>
|
||||
</Upload>
|
||||
|
||||
<Button icon={<FolderOpenOutlined />}>
|
||||
@ 知识库
|
||||
</Button>
|
||||
|
||||
<Select
|
||||
defaultValue="deepseek-v3"
|
||||
style={{ width: 180 }}
|
||||
options={[
|
||||
{ label: 'DeepSeek-V3', value: 'deepseek-v3' },
|
||||
{ label: 'Qwen3-72B', value: 'qwen3-72b' },
|
||||
{ label: 'Gemini Pro', value: 'gemini-pro' },
|
||||
]}
|
||||
/>
|
||||
</Space>
|
||||
|
||||
<Tag color="orange" icon={<SyncOutlined spin />}>
|
||||
功能开发中...
|
||||
</Tag>
|
||||
</Space>
|
||||
|
||||
{/* 输入框 */}
|
||||
<Space.Compact style={{ width: '100%' }}>
|
||||
<TextArea
|
||||
placeholder="输入您的问题... (功能开发中)"
|
||||
autoSize={{ minRows: 2, maxRows: 6 }}
|
||||
disabled
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SendOutlined />}
|
||||
style={{ height: 'auto' }}
|
||||
disabled
|
||||
>
|
||||
发送
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AgentChatPage
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { Card, Typography, Tag, Button, Space } from 'antd'
|
||||
import { ArrowLeftOutlined } from '@ant-design/icons'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
const { Title, Paragraph } = Typography
|
||||
|
||||
const AGENTS_MAP: Record<string, { name: string; icon: string; desc: string }> = {
|
||||
'topic-evaluation': { name: '选题评价智能体', icon: '🎯', desc: '从创新性、临床价值、科学性和可行性等维度评估研究选题' },
|
||||
'scientific-question': { name: '科学问题梳理智能体', icon: '🔬', desc: '将模糊的研究想法提炼成清晰、具体、可验证的科学问题' },
|
||||
'picos-construction': { name: 'PICOS构建智能体', icon: '📋', desc: '按照PICOS原则结构化定义临床研究的核心要素' },
|
||||
'observation-design': { name: '观察指标设计智能体', icon: '📊', desc: '推荐合适的主要、次要及安全性观察指标集' },
|
||||
'crf-development': { name: 'CRF制定智能体', icon: '📝', desc: '自动生成结构化、符合规范的病例报告表(CRF)框架' },
|
||||
'sample-size': { name: '样本量计算智能体', icon: '🔢', desc: '根据研究参数提供科学合理的样本量估算结果' },
|
||||
'protocol-writing': { name: '临床研究方案撰写智能体', icon: '📄', desc: '自动生成结构完整的临床研究设计方案' },
|
||||
'paper-polishing': { name: '论文润色智能体', icon: '✨', desc: '提供专业级的语言润色,修正语法、拼写和表达方式' },
|
||||
'paper-translation': { name: '论文翻译智能体', icon: '🌐', desc: '提供专业、精准的中英互译服务' },
|
||||
'methodology-review': { name: '方法学评审智能体', icon: '🔍', desc: '对研究方案或论文进行全面的方法学评审' },
|
||||
'journal-methodology-review': { name: '期刊方法学评审智能体', icon: '📑', desc: '模拟期刊审稿人视角,进行方法学挑战' },
|
||||
'journal-guidelines-review': { name: '期刊稿约评审智能体', icon: '✅', desc: '检查文章格式、字数、参考文献规范等是否符合投稿要求' },
|
||||
}
|
||||
|
||||
const AgentPage = () => {
|
||||
const { agentId } = useParams<{ agentId: string }>()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const agent = agentId ? AGENTS_MAP[agentId] : null
|
||||
|
||||
if (!agent) {
|
||||
return (
|
||||
<Card>
|
||||
<Paragraph>智能体不存在</Paragraph>
|
||||
<Button onClick={() => navigate('/')}>返回首页</Button>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* 头部 */}
|
||||
<Space direction="vertical" size="large" style={{ width: '100%' }}>
|
||||
<Button icon={<ArrowLeftOutlined />} onClick={() => navigate('/')}>
|
||||
返回首页
|
||||
</Button>
|
||||
|
||||
<Card>
|
||||
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
||||
<div>
|
||||
<span style={{ fontSize: 48, marginRight: 16 }}>{agent.icon}</span>
|
||||
<Title level={2} style={{ display: 'inline', margin: 0 }}>
|
||||
{agent.name}
|
||||
</Title>
|
||||
<Tag color="blue" style={{ marginLeft: 16 }}>
|
||||
AI智能体
|
||||
</Tag>
|
||||
</div>
|
||||
<Paragraph type="secondary" style={{ fontSize: 16 }}>
|
||||
{agent.desc}
|
||||
</Paragraph>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
{/* 功能区域 - 占位符 */}
|
||||
<Card title="智能对话区域" extra={<Tag color="orange">待开发</Tag>}>
|
||||
<div
|
||||
style={{
|
||||
height: 400,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: '#f5f5f5',
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<Space direction="vertical" align="center">
|
||||
<div style={{ fontSize: 64 }}>💬</div>
|
||||
<Title level={4} type="secondary">
|
||||
对话系统开发中...
|
||||
</Title>
|
||||
<Paragraph type="secondary">
|
||||
Day 9-10 将实现完整的智能对话功能
|
||||
</Paragraph>
|
||||
</Space>
|
||||
</div>
|
||||
</Card>
|
||||
</Space>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AgentPage
|
||||
|
||||
36
frontend/src/pages/HistoryPage.tsx
Normal file
36
frontend/src/pages/HistoryPage.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Card, Empty, Alert } from 'antd'
|
||||
import { HistoryOutlined, SyncOutlined } from '@ant-design/icons'
|
||||
|
||||
const HistoryPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<Alert
|
||||
message="功能开发中"
|
||||
description="历史记录功能正在开发中,您将能够查看和管理所有对话历史..."
|
||||
type="warning"
|
||||
showIcon
|
||||
icon={<SyncOutlined spin />}
|
||||
style={{ marginBottom: 24 }}
|
||||
/>
|
||||
|
||||
<Card>
|
||||
<Empty
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
imageStyle={{ height: 120 }}
|
||||
description={
|
||||
<div>
|
||||
<HistoryOutlined style={{ fontSize: 48, color: '#d9d9d9', marginBottom: 16 }} />
|
||||
<div style={{ color: '#999' }}>历史记录功能即将上线</div>
|
||||
<div style={{ color: '#ccc', fontSize: 12, marginTop: 8 }}>
|
||||
您将能够查看所有对话记录、按项目筛选、导出对话等
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default HistoryPage
|
||||
|
||||
@@ -1,27 +1,35 @@
|
||||
import { Card, Row, Col, Typography, Button } from 'antd'
|
||||
import { Card, Row, Col, Typography, Button, Tag, message } from 'antd'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { ExperimentOutlined, RocketOutlined } from '@ant-design/icons'
|
||||
import { ExperimentOutlined, RocketOutlined, LockOutlined } from '@ant-design/icons'
|
||||
|
||||
const { Title, Paragraph } = Typography
|
||||
|
||||
const AGENTS = [
|
||||
{ id: 'topic-evaluation', name: '选题评价智能体', icon: '🎯', desc: '从创新性、临床价值、科学性和可行性等维度评估研究选题' },
|
||||
{ id: 'scientific-question', name: '科学问题梳理智能体', icon: '🔬', desc: '将模糊的研究想法提炼成清晰、具体、可验证的科学问题' },
|
||||
{ id: 'picos-construction', name: 'PICOS构建智能体', icon: '📋', desc: '按照PICOS原则结构化定义临床研究的核心要素' },
|
||||
{ id: 'observation-design', name: '观察指标设计智能体', icon: '📊', desc: '推荐合适的主要、次要及安全性观察指标集' },
|
||||
{ id: 'crf-development', name: 'CRF制定智能体', icon: '📝', desc: '自动生成结构化、符合规范的病例报告表(CRF)框架' },
|
||||
{ id: 'sample-size', name: '样本量计算智能体', icon: '🔢', desc: '根据研究参数提供科学合理的样本量估算结果' },
|
||||
{ id: 'protocol-writing', name: '临床研究方案撰写智能体', icon: '📄', desc: '自动生成结构完整的临床研究设计方案' },
|
||||
{ id: 'paper-polishing', name: '论文润色智能体', icon: '✨', desc: '提供专业级的语言润色,修正语法、拼写和表达方式' },
|
||||
{ id: 'paper-translation', name: '论文翻译智能体', icon: '🌐', desc: '提供专业、精准的中英互译服务' },
|
||||
{ id: 'methodology-review', name: '方法学评审智能体', icon: '🔍', desc: '对研究方案或论文进行全面的方法学评审' },
|
||||
{ id: 'journal-methodology-review', name: '期刊方法学评审智能体', icon: '📑', desc: '模拟期刊审稿人视角,进行方法学挑战' },
|
||||
{ id: 'journal-guidelines-review', name: '期刊稿约评审智能体', icon: '✅', desc: '检查文章格式、字数、参考文献规范等是否符合投稿要求' },
|
||||
{ id: 'topic-evaluation', name: '选题评价智能体', icon: '🎯', desc: '从创新性、临床价值、科学性和可行性等维度评估研究选题', enabled: true },
|
||||
{ id: 'scientific-question', name: '科学问题梳理智能体', icon: '🔬', desc: '将模糊的研究想法提炼成清晰、具体、可验证的科学问题', enabled: false },
|
||||
{ id: 'picos-construction', name: 'PICOS构建智能体', icon: '📋', desc: '按照PICOS原则结构化定义临床研究的核心要素', enabled: false },
|
||||
{ id: 'observation-design', name: '观察指标设计智能体', icon: '📊', desc: '推荐合适的主要、次要及安全性观察指标集', enabled: false },
|
||||
{ id: 'crf-development', name: 'CRF制定智能体', icon: '📝', desc: '自动生成结构化、符合规范的病例报告表(CRF)框架', enabled: false },
|
||||
{ id: 'sample-size', name: '样本量计算智能体', icon: '🔢', desc: '根据研究参数提供科学合理的样本量估算结果', enabled: false },
|
||||
{ id: 'protocol-writing', name: '临床研究方案撰写智能体', icon: '📄', desc: '自动生成结构完整的临床研究设计方案', enabled: false },
|
||||
{ id: 'paper-polishing', name: '论文润色智能体', icon: '✨', desc: '提供专业级的语言润色,修正语法、拼写和表达方式', enabled: false },
|
||||
{ id: 'paper-translation', name: '论文翻译智能体', icon: '🌐', desc: '提供专业、精准的中英互译服务', enabled: false },
|
||||
{ id: 'methodology-review', name: '方法学评审智能体', icon: '🔍', desc: '对研究方案或论文进行全面的方法学评审', enabled: false },
|
||||
{ id: 'journal-methodology-review', name: '期刊方法学评审智能体', icon: '📑', desc: '模拟期刊审稿人视角,进行方法学挑战', enabled: false },
|
||||
{ id: 'journal-guidelines-review', name: '期刊稿约评审智能体', icon: '✅', desc: '检查文章格式、字数、参考文献规范等是否符合投稿要求', enabled: false },
|
||||
]
|
||||
|
||||
const HomePage = () => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const handleAgentClick = (agent: typeof AGENTS[0]) => {
|
||||
if (!agent.enabled) {
|
||||
message.info('该智能体正在开发中,敬请期待!')
|
||||
return
|
||||
}
|
||||
navigate(`/agent/${agent.id}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* 欢迎区域 */}
|
||||
@@ -44,10 +52,23 @@ const HomePage = () => {
|
||||
{AGENTS.map((agent) => (
|
||||
<Col xs={24} sm={12} md={8} lg={6} key={agent.id}>
|
||||
<Card
|
||||
hoverable
|
||||
style={{ height: '100%' }}
|
||||
onClick={() => navigate(`/agent/${agent.id}`)}
|
||||
hoverable={agent.enabled}
|
||||
style={{
|
||||
height: '100%',
|
||||
opacity: agent.enabled ? 1 : 0.7,
|
||||
position: 'relative',
|
||||
}}
|
||||
onClick={() => handleAgentClick(agent)}
|
||||
>
|
||||
{!agent.enabled && (
|
||||
<Tag
|
||||
color="orange"
|
||||
icon={<LockOutlined />}
|
||||
style={{ position: 'absolute', top: 8, right: 8 }}
|
||||
>
|
||||
即将上线
|
||||
</Tag>
|
||||
)}
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{ fontSize: 48, marginBottom: 12 }}>{agent.icon}</div>
|
||||
<Title level={4} style={{ marginBottom: 8 }}>
|
||||
@@ -56,8 +77,8 @@ const HomePage = () => {
|
||||
<Paragraph type="secondary" style={{ marginBottom: 16, minHeight: 44 }}>
|
||||
{agent.desc}
|
||||
</Paragraph>
|
||||
<Button type="primary" block>
|
||||
开始使用
|
||||
<Button type={agent.enabled ? 'primary' : 'default'} block disabled={!agent.enabled}>
|
||||
{agent.enabled ? '开始使用' : '敬请期待'}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
225
frontend/src/pages/KnowledgePage.tsx
Normal file
225
frontend/src/pages/KnowledgePage.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
import {
|
||||
Card,
|
||||
Button,
|
||||
Space,
|
||||
Table,
|
||||
Tag,
|
||||
Progress,
|
||||
Alert,
|
||||
} from 'antd'
|
||||
import {
|
||||
PlusOutlined,
|
||||
FolderOutlined,
|
||||
FileTextOutlined,
|
||||
UploadOutlined,
|
||||
DeleteOutlined,
|
||||
SyncOutlined,
|
||||
} from '@ant-design/icons'
|
||||
|
||||
const KnowledgePage = () => {
|
||||
// 模拟知识库数据
|
||||
const mockKnowledgeBases = [
|
||||
{
|
||||
id: '1',
|
||||
name: '心血管疾病研究文献库',
|
||||
fileCount: 15,
|
||||
totalSize: '45MB',
|
||||
createdAt: '2025-10-05',
|
||||
status: 'ready',
|
||||
},
|
||||
]
|
||||
|
||||
// 模拟文档数据
|
||||
const mockDocuments = [
|
||||
{
|
||||
id: '1',
|
||||
name: '高血压治疗指南2024.pdf',
|
||||
size: '3.2MB',
|
||||
uploadedAt: '2025-10-05 14:30',
|
||||
status: 'processed',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '心血管疾病流行病学研究.docx',
|
||||
size: '1.8MB',
|
||||
uploadedAt: '2025-10-05 15:20',
|
||||
status: 'processing',
|
||||
},
|
||||
]
|
||||
|
||||
const kbColumns = [
|
||||
{
|
||||
title: '知识库名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: (text: string) => (
|
||||
<Space>
|
||||
<FolderOutlined style={{ color: '#1890ff' }} />
|
||||
<span>{text}</span>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '文档数量',
|
||||
dataIndex: 'fileCount',
|
||||
key: 'fileCount',
|
||||
render: (count: number) => `${count} / 50`,
|
||||
},
|
||||
{
|
||||
title: '总大小',
|
||||
dataIndex: 'totalSize',
|
||||
key: 'totalSize',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createdAt',
|
||||
key: 'createdAt',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
render: (status: string) => (
|
||||
<Tag color={status === 'ready' ? 'green' : 'orange'}>
|
||||
{status === 'ready' ? '就绪' : '处理中'}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
render: () => (
|
||||
<Space>
|
||||
<Button size="small" icon={<UploadOutlined />} disabled>
|
||||
上传
|
||||
</Button>
|
||||
<Button size="small" danger icon={<DeleteOutlined />} disabled>
|
||||
删除
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
const docColumns = [
|
||||
{
|
||||
title: '文件名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: (text: string) => (
|
||||
<Space>
|
||||
<FileTextOutlined />
|
||||
<span>{text}</span>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '大小',
|
||||
dataIndex: 'size',
|
||||
key: 'size',
|
||||
},
|
||||
{
|
||||
title: '上传时间',
|
||||
dataIndex: 'uploadedAt',
|
||||
key: 'uploadedAt',
|
||||
},
|
||||
{
|
||||
title: '处理状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
render: (status: string) => {
|
||||
if (status === 'processed') {
|
||||
return <Tag color="green">已完成</Tag>
|
||||
}
|
||||
return (
|
||||
<Space>
|
||||
<Tag color="processing" icon={<SyncOutlined spin />}>
|
||||
处理中
|
||||
</Tag>
|
||||
<Progress percent={65} size="small" style={{ width: 100 }} />
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
render: () => (
|
||||
<Button size="small" danger icon={<DeleteOutlined />} disabled>
|
||||
删除
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* 提示信息 */}
|
||||
<Alert
|
||||
message="知识库功能说明"
|
||||
description="每个用户最多可创建3个知识库,每个知识库最多上传50个文件(支持PDF、DOCX格式)。在对话时可以@知识库,让AI基于您的文献进行回答。"
|
||||
type="info"
|
||||
showIcon
|
||||
closable
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
|
||||
<Alert
|
||||
message="功能开发中"
|
||||
description="知识库管理功能正在开发中,敬请期待..."
|
||||
type="warning"
|
||||
showIcon
|
||||
icon={<SyncOutlined spin />}
|
||||
style={{ marginBottom: 24 }}
|
||||
/>
|
||||
|
||||
{/* 知识库列表 */}
|
||||
<Card
|
||||
title={
|
||||
<Space>
|
||||
<FolderOutlined />
|
||||
<span>我的知识库 (1/3)</span>
|
||||
</Space>
|
||||
}
|
||||
extra={
|
||||
<Button type="primary" icon={<PlusOutlined />} disabled>
|
||||
创建知识库
|
||||
</Button>
|
||||
}
|
||||
style={{ marginBottom: 24 }}
|
||||
>
|
||||
<Table
|
||||
columns={kbColumns}
|
||||
dataSource={mockKnowledgeBases}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* 文档列表 */}
|
||||
<Card
|
||||
title={
|
||||
<Space>
|
||||
<FileTextOutlined />
|
||||
<span>文档列表:心血管疾病研究文献库</span>
|
||||
</Space>
|
||||
}
|
||||
extra={
|
||||
<Button icon={<UploadOutlined />} disabled>
|
||||
上传文件
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
columns={docColumns}
|
||||
dataSource={mockDocuments}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default KnowledgePage
|
||||
|
||||
69
frontend/src/stores/useProjectStore.ts
Normal file
69
frontend/src/stores/useProjectStore.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
export interface Project {
|
||||
id: string;
|
||||
name: string;
|
||||
background: string;
|
||||
researchType: 'observational' | 'interventional';
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface ProjectState {
|
||||
// 当前选中的项目
|
||||
currentProject: Project | null;
|
||||
|
||||
// 所有项目列表
|
||||
projects: Project[];
|
||||
|
||||
// 是否显示创建项目对话框
|
||||
showCreateDialog: boolean;
|
||||
|
||||
// 是否显示编辑项目对话框
|
||||
showEditDialog: boolean;
|
||||
|
||||
// Actions
|
||||
setCurrentProject: (project: Project | null) => void;
|
||||
setProjects: (projects: Project[]) => void;
|
||||
addProject: (project: Project) => void;
|
||||
updateProject: (id: string, updates: Partial<Project>) => void;
|
||||
deleteProject: (id: string) => void;
|
||||
setShowCreateDialog: (show: boolean) => void;
|
||||
setShowEditDialog: (show: boolean) => void;
|
||||
}
|
||||
|
||||
export const useProjectStore = create<ProjectState>((set) => ({
|
||||
currentProject: null,
|
||||
projects: [],
|
||||
showCreateDialog: false,
|
||||
showEditDialog: false,
|
||||
|
||||
setCurrentProject: (project) => set({ currentProject: project }),
|
||||
|
||||
setProjects: (projects) => set({ projects }),
|
||||
|
||||
addProject: (project) => set((state) => ({
|
||||
projects: [...state.projects, project],
|
||||
})),
|
||||
|
||||
updateProject: (id, updates) => set((state) => ({
|
||||
projects: state.projects.map((p) =>
|
||||
p.id === id ? { ...p, ...updates } : p
|
||||
),
|
||||
currentProject:
|
||||
state.currentProject?.id === id
|
||||
? { ...state.currentProject, ...updates }
|
||||
: state.currentProject,
|
||||
})),
|
||||
|
||||
deleteProject: (id) => set((state) => ({
|
||||
projects: state.projects.filter((p) => p.id !== id),
|
||||
currentProject:
|
||||
state.currentProject?.id === id ? null : state.currentProject,
|
||||
})),
|
||||
|
||||
setShowCreateDialog: (show) => set({ showCreateDialog: show }),
|
||||
|
||||
setShowEditDialog: (show) => set({ showEditDialog: show }),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user