# AIA V2.1 前端组件设计 > **版本**:V2.1 > **创建日期**:2026-01-11 > **技术栈**:React 19 + TypeScript 5 + Ant Design 6 + Ant Design X 2.1 --- ## 📁 模块结构 ``` frontend-v2/src/modules/aia/ ├── pages/ │ ├── Dashboard.tsx # 智能体大厅(首页) │ └── Workspace.tsx # 对话工作台 ├── components/ │ ├── AgentPipeline/ │ │ ├── index.tsx # 5阶段流水线容器 │ │ ├── StageColumn.tsx # 单阶段列 │ │ └── AgentCard.tsx # 智能体卡片 │ ├── IntentSearch/ │ │ ├── index.tsx # 意图搜索框 │ │ └── SuggestionDropdown.tsx # 建议下拉框 │ ├── ConversationList/ │ │ ├── index.tsx # 历史会话列表 │ │ └── ConversationItem.tsx # 会话项 │ ├── MessageList/ │ │ ├── index.tsx # 消息列表 │ │ ├── UserMessage.tsx # 用户消息 │ │ ├── AssistantMessage.tsx # AI回复 │ │ └── ThinkingBlock.tsx # 深度思考折叠块 │ ├── Attachment/ │ │ ├── AttachmentUpload.tsx # 附件上传 │ │ ├── AttachmentCard.tsx # 附件卡片 │ │ └── AttachmentPreview.tsx # 附件预览 │ ├── SlashCommands/ │ │ └── index.tsx # 快捷指令菜单 │ └── ActionBar/ │ └── index.tsx # 结果操作栏 ├── hooks/ │ ├── useConversation.ts # 对话管理 │ ├── useAgents.ts # 智能体数据 │ ├── useIntentRouter.ts # 意图路由 │ ├── useStreamMessage.ts # 流式消息 │ └── useAttachment.ts # 附件上传 ├── api/ │ └── index.ts # API 封装 ├── types/ │ └── index.ts # TypeScript 类型 ├── styles/ │ ├── dashboard.module.css # Dashboard 样式 │ └── workspace.module.css # Workspace 样式 └── index.tsx # 模块入口 + 路由 ``` --- ## 🎨 设计规范 ### 色彩系统 ```css :root { /* 5阶段流水线主题色 */ --stage-design: #3B82F6; /* 蓝色 - 研究设计 */ --stage-data: #8B5CF6; /* 紫色 - 数据采集 */ --stage-analysis: #10B981; /* 绿色 - 统计分析 */ --stage-write: #F59E0B; /* 橙色 - 论文撰写 */ --stage-publish: #EF4444; /* 红色 - 成果发布 */ /* 功能色 */ --ai-assistant: #6366F1; /* AI助手主色 */ --thinking-bg: #F3F4F6; /* 思考块背景 */ --thinking-border: #E5E7EB; /* 思考块边框 */ /* Gemini 风格 */ --bg-primary: #FFFFFF; --bg-secondary: #F9FAFB; --text-primary: #111827; --text-secondary: #6B7280; --border-light: #E5E7EB; } ``` ### 间距系统 ```css /* 遵循 8px 网格 */ --spacing-xs: 4px; --spacing-sm: 8px; --spacing-md: 16px; --spacing-lg: 24px; --spacing-xl: 32px; --spacing-2xl: 48px; ``` ### 断点 ```css /* 移动优先 */ --breakpoint-sm: 640px; /* 手机 */ --breakpoint-md: 768px; /* 平板 */ --breakpoint-lg: 1024px; /* 桌面 */ --breakpoint-xl: 1280px; /* 大屏 */ ``` --- ## 📄 页面设计 ### 1. Dashboard(智能体大厅) ```tsx // pages/Dashboard.tsx import { IntentSearch } from '../components/IntentSearch'; import { AgentPipeline } from '../components/AgentPipeline'; export const Dashboard: React.FC = () => { return (
{/* 顶部意图搜索框 */}

AI 智能助手

有什么可以帮助您的?

{/* 5阶段智能体流水线 */}
); }; ``` **布局特点**: - 顶部居中大搜索框 - 5阶段流水线横向平铺(桌面)/ 纵向滚动(移动) - Gemini 风格大留白 --- ### 2. Workspace(对话工作台) ```tsx // pages/Workspace.tsx import { ChatContainer } from '@/shared/components/Chat'; import { ConversationList } from '../components/ConversationList'; import { ThinkingBlock } from '../components/MessageList/ThinkingBlock'; import { AttachmentUpload } from '../components/Attachment/AttachmentUpload'; export const Workspace: React.FC = () => { const { conversationId } = useParams(); const { conversation, messages, sendMessage } = useConversation(conversationId); return (
{/* 左侧边栏 - 历史会话 */} {/* 主对话区 */}
{/* 对话头部 */}

{conversation?.agent?.name}

{/* 消息列表 - 复用通用 Chat 组件 */} (
{/* 深度思考块 */} {msg.thinkingContent && ( )} {/* 消息内容 */} {/* 附件卡片 */} {msg.attachments?.map(att => ( ))}
)} inputFooter={} />
); }; ``` --- ## 🧩 组件详细设计 ### 1. AgentPipeline(5阶段流水线) ```tsx // components/AgentPipeline/index.tsx interface AgentPipelineProps { onAgentClick: (agentId: string) => void; } export const AgentPipeline: React.FC = ({ onAgentClick }) => { const { agents } = useAgents(); const stages = [ { key: 'design', title: '研究设计', color: 'var(--stage-design)' }, { key: 'data', title: '数据采集', color: 'var(--stage-data)' }, { key: 'analysis', title: '统计分析', color: 'var(--stage-analysis)' }, { key: 'write', title: '论文撰写', color: 'var(--stage-write)' }, { key: 'publish', title: '成果发布', color: 'var(--stage-publish)' }, ]; return (
{stages.map((stage, index) => ( a.stage === stage.key)} onAgentClick={onAgentClick} showConnector={index < stages.length - 1} /> ))}
); }; ``` **样式特点**: ```css .pipeline { display: flex; gap: var(--spacing-lg); overflow-x: auto; padding: var(--spacing-xl) 0; } /* 移动端纵向布局 */ @media (max-width: 768px) { .pipeline { flex-direction: column; overflow-x: visible; } } ``` --- ### 2. IntentSearch(意图搜索框) ```tsx // components/IntentSearch/index.tsx export const IntentSearch: React.FC = () => { const [query, setQuery] = useState(''); const [suggestions, setSuggestions] = useState([]); const { routeIntent, isLoading } = useIntentRouter(); const navigate = useNavigate(); // 500ms 防抖 const debouncedQuery = useDebouncedValue(query, 500); useEffect(() => { if (debouncedQuery.length >= 2) { routeIntent(debouncedQuery).then(setSuggestions); } }, [debouncedQuery]); const handleSelect = (suggestion: IntentSuggestion) => { // 跳转到对应智能体 navigate(`/aia/workspace?agent=${suggestion.agentId}&prompt=${encodeURIComponent(suggestion.prefillPrompt)}`); }; return (
setQuery(e.target.value)} loading={isLoading} className={styles.searchInput} /> {suggestions.length > 0 && ( )}
); }; ``` --- ### 3. ThinkingBlock(深度思考折叠块) ```tsx // components/MessageList/ThinkingBlock.tsx interface ThinkingBlockProps { content: string; duration?: number; // 思考耗时(秒) isStreaming?: boolean; // 是否正在生成 } export const ThinkingBlock: React.FC = ({ content, duration, isStreaming = false, }) => { // 生成中展开,完成后自动收起 const [expanded, setExpanded] = useState(isStreaming); useEffect(() => { if (!isStreaming && expanded) { // 完成后 1.5s 自动收起 const timer = setTimeout(() => setExpanded(false), 1500); return () => clearTimeout(timer); } }, [isStreaming]); return (
setExpanded(!expanded)} > {isStreaming ? : } {isStreaming ? '正在深度思考...' : `已深度思考 (耗时 ${duration?.toFixed(1)}s)`} {expanded ? : }
{expanded && (
{content}
)}
); }; ``` **样式**: ```css .thinkingBlock { background: var(--thinking-bg); border: 1px solid var(--thinking-border); border-radius: 8px; margin-bottom: var(--spacing-md); } .header { display: flex; align-items: center; padding: var(--spacing-sm) var(--spacing-md); cursor: pointer; user-select: none; } .content { padding: var(--spacing-md); padding-top: 0; font-size: 13px; line-height: 1.6; color: var(--text-secondary); } ``` --- ### 4. AttachmentUpload(附件上传) ```tsx // components/Attachment/AttachmentUpload.tsx interface AttachmentUploadProps { conversationId: string; onUploadComplete: (attachment: Attachment) => void; maxCount?: number; // 默认 5 } export const AttachmentUpload: React.FC = ({ conversationId, onUploadComplete, maxCount = 5, }) => { const { uploadFile, uploading, progress } = useAttachment(); const [attachments, setAttachments] = useState([]); const handleUpload = async (file: File) => { if (attachments.length >= maxCount) { message.error(`最多上传 ${maxCount} 个附件`); return false; } // 文件类型校验 const allowedTypes = ['application/pdf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'text/plain', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']; if (!allowedTypes.includes(file.type)) { message.error('仅支持 PDF、Word、TXT、Excel 文件'); return false; } // 文件大小校验(20MB) if (file.size > 20 * 1024 * 1024) { message.error('文件大小不能超过 20MB'); return false; } const attachment = await uploadFile(conversationId, file); setAttachments([...attachments, attachment]); onUploadComplete(attachment); return false; // 阻止默认上传 }; return (
{/* 已上传附件列表 */}
{attachments.map(att => ( { setAttachments(attachments.filter(a => a.id !== att.id)); }} /> ))}
{/* 上传进度 */} {uploading && ( )}
); }; ``` --- ### 5. SlashCommands(快捷指令) ```tsx // components/SlashCommands/index.tsx interface SlashCommandsProps { onSelect: (command: SlashCommand) => void; onClose: () => void; } const commands: SlashCommand[] = [ { key: 'polish', icon: '✨', label: '润色', description: '优化文本表达' }, { key: 'expand', icon: '📝', label: '扩写', description: '扩展内容细节' }, { key: 'translate', icon: '🌐', label: '翻译', description: '中英互译' }, { key: 'export', icon: '📄', label: '导出Word', description: '导出为 Word 文档' }, ]; export const SlashCommands: React.FC = ({ onSelect, onClose, }) => { const [selectedIndex, setSelectedIndex] = useState(0); // 键盘导航 useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { switch (e.key) { case 'ArrowUp': e.preventDefault(); setSelectedIndex(i => Math.max(0, i - 1)); break; case 'ArrowDown': e.preventDefault(); setSelectedIndex(i => Math.min(commands.length - 1, i + 1)); break; case 'Enter': e.preventDefault(); onSelect(commands[selectedIndex]); break; case 'Escape': onClose(); break; } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [selectedIndex]); return (
{commands.map((cmd, index) => (
onSelect(cmd)} onMouseEnter={() => setSelectedIndex(index)} > {cmd.icon}
{cmd.label}
{cmd.description}
))}
); }; ``` --- ### 6. ActionBar(结果操作栏) ```tsx // components/ActionBar/index.tsx interface ActionBarProps { message: Message; onCopy: () => void; onRegenerate: () => void; onExport: () => void; } export const ActionBar: React.FC = ({ message, onCopy, onRegenerate, onExport, }) => { return (
); }; ``` --- ## 🎣 Hooks 设计 ### useConversation ```typescript // hooks/useConversation.ts interface UseConversationReturn { conversation: Conversation | null; messages: Message[]; isLoading: boolean; sendMessage: (content: string, attachmentIds?: string[]) => Promise; regenerate: (messageId: string) => Promise; deleteConversation: () => Promise; } export function useConversation(conversationId?: string): UseConversationReturn { const [conversation, setConversation] = useState(null); const [messages, setMessages] = useState([]); const [isLoading, setIsLoading] = useState(false); // 加载对话 useEffect(() => { if (conversationId) { api.getConversation(conversationId).then(data => { setConversation(data); setMessages(data.messages); }); } }, [conversationId]); // 发送消息(流式) const sendMessage = async (content: string, attachmentIds?: string[]) => { setIsLoading(true); // 添加用户消息 const userMessage: Message = { id: `temp-${Date.now()}`, role: 'user', content, attachments: [], createdAt: new Date().toISOString(), }; setMessages(prev => [...prev, userMessage]); // 初始化 AI 消息 const aiMessage: Message = { id: `temp-ai-${Date.now()}`, role: 'assistant', content: '', thinkingContent: '', createdAt: new Date().toISOString(), }; setMessages(prev => [...prev, aiMessage]); // 流式接收 await api.sendMessageStream(conversationId!, content, attachmentIds, { onThinkingDelta: (delta) => { setMessages(prev => { const last = prev[prev.length - 1]; return [...prev.slice(0, -1), { ...last, thinkingContent: (last.thinkingContent || '') + delta, }]; }); }, onDelta: (delta) => { setMessages(prev => { const last = prev[prev.length - 1]; return [...prev.slice(0, -1), { ...last, content: last.content + delta, }]; }); }, onComplete: (finalMessage) => { setMessages(prev => { return [...prev.slice(0, -1), finalMessage]; }); setIsLoading(false); }, onError: (error) => { message.error(error.message); setIsLoading(false); }, }); }; return { conversation, messages, isLoading, sendMessage, regenerate: async () => {}, deleteConversation: async () => {}, }; } ``` ### useStreamMessage ```typescript // hooks/useStreamMessage.ts interface StreamCallbacks { onThinkingStart?: () => void; onThinkingDelta?: (content: string) => void; onThinkingEnd?: (duration: number) => void; onMessageStart?: (id: string) => void; onDelta?: (content: string) => void; onMessageEnd?: (message: Message) => void; onComplete?: (message: Message) => void; onError?: (error: Error) => void; } export function useStreamMessage() { const streamMessage = async ( url: string, body: object, callbacks: StreamCallbacks ) => { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream', }, body: JSON.stringify(body), }); const reader = response.body?.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader!.read(); if (done) break; const chunk = decoder.decode(value); const events = parseSSE(chunk); for (const event of events) { switch (event.type) { case 'thinking_start': callbacks.onThinkingStart?.(); break; case 'thinking_delta': callbacks.onThinkingDelta?.(event.data.content); break; case 'thinking_end': callbacks.onThinkingEnd?.(event.data.duration); break; case 'message_start': callbacks.onMessageStart?.(event.data.id); break; case 'delta': callbacks.onDelta?.(event.data.content); break; case 'message_end': callbacks.onMessageEnd?.(event.data); break; case 'done': callbacks.onComplete?.(event.data); break; case 'error': callbacks.onError?.(new Error(event.data.message)); break; } } } }; return { streamMessage }; } ``` --- ## 📱 响应式设计 ### 断点策略 ```typescript // 断点定义 const breakpoints = { sm: 640, // 手机 md: 768, // 平板(主要断点) lg: 1024, // 桌面 xl: 1280, // 大屏 }; ``` ### Dashboard 响应式 | 断点 | 布局 | |------|------| | `< 768px` | 流水线纵向滚动,卡片单列 | | `≥ 768px` | 流水线横向 5 列 | ### Workspace 响应式 | 断点 | 布局 | |------|------| | `< 768px` | 侧边栏隐藏(抽屉滑出),输入框键盘适配 | | `≥ 768px` | 侧边栏固定显示 240px | --- ## 📝 类型定义 ```typescript // types/index.ts export interface Agent { id: string; name: string; description: string; icon: string; stage: 'design' | 'data' | 'analysis' | 'write' | 'publish'; color: string; knowledgeBaseId?: string; isTool?: boolean; targetModule?: string; welcomeMessage?: string; suggestedQuestions?: string[]; } export interface Conversation { id: string; title: string; agentId: string; agent?: Agent; projectId?: string; messageCount: number; lastMessage?: string; createdAt: string; updatedAt: string; } export interface Message { id: string; conversationId?: string; role: 'user' | 'assistant'; content: string; thinkingContent?: string; attachments?: Attachment[]; model?: string; tokens?: number; isPinned?: boolean; createdAt: string; } export interface Attachment { id: string; filename: string; mimeType: string; size: number; ossUrl: string; textExtracted: boolean; tokenCount: number; truncated: boolean; createdAt: string; } export interface IntentSuggestion { agentId: string; agentName: string; confidence: number; prefillPrompt: string; } export interface SlashCommand { key: string; icon: string; label: string; description: string; } ``` --- ## 📝 更新日志 | 日期 | 版本 | 内容 | |------|------|------| | 2026-01-11 | V1.0 | 创建前端组件设计文档 |