import React, { useState, useRef, useEffect } from 'react'; import { Send, Bot, User, Sparkles, Trash2, FileText, TableProperties, ArrowRight, Search, MessageSquare } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Markdown } from '@/components/ui/markdown'; import { backendApi } from '@/db/backend-api'; import { toast } from 'sonner'; import { cn } from '@/lib/utils'; type ChatMessage = { id: string; role: 'user' | 'assistant'; content: string; created_at: string; intent?: string; result?: any; }; const InstructionChat: React.FC = () => { const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [loading, setLoading] = useState(false); const [currentDocIds, setCurrentDocIds] = useState([]); const [conversationId, setConversationId] = useState(''); const scrollAreaRef = useRef(null); // 初始化会话ID useEffect(() => { const storedId = localStorage.getItem('chat_conversation_id'); if (storedId) { setConversationId(storedId); } else { const newId = `conv_${Date.now()}_${Math.random().toString(36).substring(7)}`; setConversationId(newId); localStorage.setItem('chat_conversation_id', newId); } }, []); useEffect(() => { // Initial welcome message if (messages.length === 0) { setMessages([ { id: 'welcome', role: 'assistant', content: `您好!我是智联文档 AI 助手。 **📄 文档智能操作** - "提取文档中的医院数量和床位数" - "帮我找出所有机构的名称" **📊 数据填表** - "根据这些数据填表" - "将提取的信息填写到Excel模板" **📝 内容处理** - "总结一下这份文档" - "对比这两个文档的差异" **🔍 智能问答** - "文档里说了些什么?" - "有多少家医院?" 请告诉我您想做什么?`, created_at: new Date().toISOString() } ]); // 获取已上传的文档ID列表 loadDocuments(); } }, []); const loadDocuments = async () => { try { const result = await backendApi.getDocuments(undefined, 50); if (result.success && result.documents) { const docIds = result.documents.map((d: any) => d.doc_id); setCurrentDocIds(docIds); if (docIds.length > 0) { console.log(`已加载 ${docIds.length} 个文档`); } } } catch (err) { console.error('获取文档列表失败:', err); } }; useEffect(() => { // Scroll to bottom if (scrollAreaRef.current) { const scrollElement = scrollAreaRef.current.querySelector('[data-radix-scroll-area-viewport]'); if (scrollElement) { scrollElement.scrollTop = scrollElement.scrollHeight; } } }, [messages]); const handleSend = async () => { if (!input.trim()) return; const userMessage: ChatMessage = { id: Math.random().toString(36).substring(7), role: 'user', content: input.trim(), created_at: new Date().toISOString() }; setMessages(prev => [...prev, userMessage]); setInput(''); setLoading(true); try { // 使用真实的智能指令 API const response = await backendApi.instructionChat( input.trim(), currentDocIds.length > 0 ? currentDocIds : undefined, { conversation_id: conversationId } ); // 根据意图类型生成友好响应 let responseContent = ''; const resultData = response.result; switch (response.intent) { case 'extract': // 信息提取结果 const extracted = resultData?.extracted_data || {}; const keys = Object.keys(extracted); if (keys.length > 0) { responseContent = `✅ 已提取到 ${keys.length} 个字段的数据:\n\n`; for (const [key, value] of Object.entries(extracted)) { const values = Array.isArray(value) ? value : [value]; const displayValues = values.length > 10 ? values.slice(0, 10).join(', ') + ` ...(共${values.length}条)` : values.join(', '); responseContent += `**${key}**: ${displayValues}\n`; } responseContent += `\n💡 可直接使用以上数据,或说"填入表格"继续填表操作。`; } else { responseContent = resultData?.message || '未能从文档中提取到相关数据。请尝试更明确的字段名称。'; } break; case 'fill_table': // 填表结果 const filled = resultData?.result?.filled_data || {}; const filledKeys = Object.keys(filled); if (filledKeys.length > 0) { responseContent = `✅ 填表完成!成功填写 ${filledKeys.length} 个字段:\n\n`; for (const [key, value] of Object.entries(filled)) { const values = Array.isArray(value) ? value : [value]; const displayValues = values.length > 10 ? values.slice(0, 10).join(', ') + ` ...(共${values.length}条)` : values.join(', '); responseContent += `**${key}**: ${displayValues}\n`; } responseContent += `\n📋 请到【智能填表】页面查看或导出结果。`; } else { responseContent = resultData?.message || '填表未能提取到数据。请检查模板表头和数据源内容。'; } break; case 'summarize': // 摘要结果 if (resultData?.action_needed === 'provide_document' || resultData?.action_needed === 'upload_document') { responseContent = `📋 ${resultData.message}\n\n${resultData.suggestion || ''}`; } else if (resultData?.ai_summary) { // AI 生成的摘要 responseContent = `📄 **${resultData.filename}** 摘要分析:\n\n${resultData.ai_summary}`; } else { responseContent = resultData?.message || '未能生成摘要。请确保已上传文档。'; } break; case 'question': // 问答结果 if (resultData?.answer) { responseContent = `**问题**: ${resultData.question}\n\n**答案**: ${resultData.answer}`; } else if (resultData?.context_preview) { responseContent = `**问题**: ${resultData.question}\n\n**相关上下文**:\n${resultData.context_preview}`; } else { responseContent = resultData?.message || '请先上传文档,我才能回答您的问题。'; } break; case 'search': // 搜索结果 const searchResults = resultData?.results || []; if (searchResults.length > 0) { responseContent = `🔍 找到 ${searchResults.length} 条相关内容:\n\n`; searchResults.slice(0, 5).forEach((r: any, idx: number) => { responseContent += `**${idx + 1}.** ${r.content?.substring(0, 100)}...\n\n`; }); } else { responseContent = '未找到相关内容。请尝试其他关键词。'; } break; case 'compare': // 对比结果 const comparison = resultData?.comparison || []; if (comparison.length > 0) { responseContent = `📊 对比了 ${comparison.length} 个文档:\n\n`; comparison.forEach((c: any) => { responseContent += `- **${c.filename}**: ${c.doc_type}, ${c.content_length} 字\n`; }); } else { responseContent = '需要至少2个文档才能进行对比。'; } break; case 'edit': // 文档编辑结果 if (resultData?.edited_content) { responseContent = `✏️ **${resultData.original_filename}** 编辑完成:\n\n${resultData.edited_content.substring(0, 500)}${resultData.edited_content.length > 500 ? '\n\n...(内容已截断)' : ''}`; } else { responseContent = resultData?.message || '编辑完成。'; } break; case 'transform': // 格式转换结果 if (resultData?.excel_data) { responseContent = `🔄 格式转换完成!\n\n已转换为 **Excel** 格式,共 **${resultData.excel_data.length}** 行数据。\n\n${resultData.message || ''}`; } else if (resultData?.content) { responseContent = `🔄 格式转换完成!\n\n目标格式: **${resultData.target_format?.toUpperCase()}**\n\n${resultData.message || ''}`; } else { responseContent = resultData?.message || '格式转换完成。'; } break; case 'unknown': // 检查是否需要用户上传文档 if (resultData?.suggestion) { responseContent = resultData.suggestion; } else if (resultData?.message && resultData.message !== '无法理解该指令,请尝试更明确的描述') { responseContent = resultData.message; } else { responseContent = `我理解您想要: "${input.trim()}"\n\n请尝试以下操作:\n\n1. **提取数据**: "提取医院数量和床位数"\n2. **填表**: "根据这些数据填表"\n3. **总结**: "总结这份文档"\n4. **问答**: "文档里说了什么?"\n5. **搜索**: "搜索相关内容"`; } break; default: responseContent = response.message || resultData?.message || '已完成您的请求。'; } const assistantMessage: ChatMessage = { id: Math.random().toString(36).substring(7), role: 'assistant', content: responseContent, created_at: new Date().toISOString(), intent: response.intent, result: resultData }; setMessages(prev => [...prev, assistantMessage]); } catch (err: any) { console.error('指令执行失败:', err); toast.error(err.message || '请求失败,请重试'); const errorMessage: ChatMessage = { id: Math.random().toString(36).substring(7), role: 'assistant', content: `抱歉,处理您的请求时遇到了问题:${err.message}\n\n请稍后重试,或尝试更简单的指令。`, created_at: new Date().toISOString() }; setMessages(prev => [...prev, errorMessage]); } finally { setLoading(false); } }; const clearChat = () => { setMessages([messages[0]]); toast.success('对话已清空'); }; const quickActions = [ { label: '提取医院数量', icon: Search, action: () => setInput('提取文档中的医院数量和床位数') }, { label: '智能填表', icon: TableProperties, action: () => setInput('根据这些数据填表') }, { label: '总结文档', icon: MessageSquare, action: () => setInput('总结一下这份文档') }, { label: '智能问答', icon: Bot, action: () => setInput('文档里说了些什么?') } ]; return (

智能助手

通过自然语言指令,极速操控您的整个文档数据库。

{/* Chat Area */}
{messages.map((m) => (
{m.role === 'user' ? : }
{m.role === 'assistant' ? ( ) : (

{m.content}

)} {new Date(m.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
))} {loading && (
)}
{ e.preventDefault(); handleSend(); }} className="w-full flex gap-3 bg-muted/30 p-2 rounded-2xl border border-border/50 focus-within:border-primary/50 transition-all shadow-inner" > setInput(e.target.value)} disabled={loading} />
{/* Quick Actions Panel */}
); }; export default InstructionChat;