'use client'; import { FormEvent, KeyboardEvent, MouseEvent, useMemo, useState } from 'react'; import type { AnalysisResponse } from '@ftb/shared'; import { BotMessageSquare, Image as ImageIcon, Maximize2, MessageCircleQuestionMark, Mic, Search, SendHorizontal, Sparkles, SquarePen, Trash2, X, } from 'lucide-react'; import { AnalysisResultBlock } from '@/components/analysis/AnalysisResultBlock'; import { requestAnalysis } from '@/lib/analysis-api'; import { WENFAN_HELP_ARTICLES, type HelpArticle } from '@/lib/wenfan-help-articles'; import { createWenfanConversationRecord, deleteWenfanConversationRecord, updateWenfanConversationRecord, type WenfanConversationRecord, } from '@/lib/wenfan-conversation-history'; import { getFallbackHelpMessage, getFallbackHelpSuggestions, searchHelpArticles, shouldShowGenericHelpSuggestions, type HelpSearchResult, } from '@/lib/wenfan-help-search'; import { useAuthStore } from '@/stores/useAuthStore'; import { useMemberStore } from '@/stores/useMemberStore'; type ChatMessage = | { id: string; role: 'assistant'; type: 'intro'; content: string; } | { id: string; role: 'user'; type: 'text'; content: string; } | { id: string; role: 'assistant'; type: 'article'; result: HelpSearchResult; } | { id: string; role: 'assistant'; type: 'fallback'; content: string; suggestions: string[]; } | { id: string; role: 'assistant'; type: 'analysis'; response: AnalysisResponse; } | { id: string; role: 'assistant'; type: 'analysis_error'; content: string; }; const INITIAL_MESSAGES: ChatMessage[] = [ { id: 'intro', role: 'assistant', type: 'intro', content: '你可以问系统怎么用,也可以问业务数据,例如:哪个部门最忙、哪些版本风险最高、需求完成趋势怎么样。业务分析只读取你已有权限的数据。', }, ]; const STARTER_QUESTIONS = getFallbackHelpSuggestions(); type WenfanConversation = WenfanConversationRecord; export default function WenfanXiaobaoPage() { const [messages, setMessages] = useState(INITIAL_MESSAGES); const [input, setInput] = useState(''); const [conversations, setConversations] = useState([]); const [activeConversationId, setActiveConversationId] = useState(null); const user = useAuthStore((state) => state.user); const roles = useMemberStore((state) => state.roles); const visibleHistory = useMemo(() => conversations.slice(0, 12), [conversations]); const currentPermissions = useMemo( () => roles.find((role) => role.id === user?.roleId)?.permissions ?? [], [roles, user?.roleId], ); const userQuestionCount = useMemo( () => messages.filter((message) => message.role === 'user').length, [messages], ); const showGenericSuggestions = shouldShowGenericHelpSuggestions(userQuestionCount); const showStarterQuestions = showGenericSuggestions && userQuestionCount === 0; function handleNewConversation() { const id = `conversation-${Date.now()}`; const record = createWenfanConversationRecord(id, INITIAL_MESSAGES); setConversations((current) => [record, ...current]); setActiveConversationId(id); setMessages(INITIAL_MESSAGES); setInput(''); } function openConversation(conversation: WenfanConversation) { setActiveConversationId(conversation.id); setMessages(conversation.messages); setInput(''); } function deleteConversation(event: MouseEvent, id: string) { event.stopPropagation(); setConversations((current) => deleteWenfanConversationRecord(current, id)); if (activeConversationId === id) { setActiveConversationId(null); setMessages(INITIAL_MESSAGES); setInput(''); } } async function askQuestion(rawQuestion: string) { const question = rawQuestion.trim(); if (!question) return; const userMessage: ChatMessage = { id: `user-${Date.now()}`, role: 'user', type: 'text', content: question, }; const optimisticMessages = [...messages, userMessage]; setMessages(optimisticMessages); setInput(''); try { const analysis = await requestAnalysis( { question, context: { surface: 'ai_assistant' } }, currentPermissions, ); const assistantMessage: ChatMessage = { id: `analysis-${Date.now()}`, role: 'assistant', type: 'analysis', response: analysis, }; const nextMessages = [...optimisticMessages, assistantMessage]; setMessages(nextMessages); setConversations((current) => updateWenfanConversationRecord(current, activeConversationId, nextMessages)); return; } catch { const results = searchHelpArticles(question, WENFAN_HELP_ARTICLES); const nextUserQuestionCount = userQuestionCount + 1; const showFallbackSuggestions = shouldShowGenericHelpSuggestions(nextUserQuestionCount); const analysisErrorMessage: ChatMessage = { id: `analysis-error-${Date.now()}`, role: 'assistant', type: 'analysis_error', content: '业务分析暂不可用,我先用内置帮助继续回答。', }; const assistantMessage: ChatMessage = results.length > 0 ? { id: `article-${Date.now()}`, role: 'assistant', type: 'article', result: results[0], } : { id: `fallback-${Date.now()}`, role: 'assistant', type: 'fallback', content: getFallbackHelpMessage(showFallbackSuggestions), suggestions: showFallbackSuggestions ? STARTER_QUESTIONS : [], }; const nextMessages = [...optimisticMessages, analysisErrorMessage, assistantMessage]; setMessages(nextMessages); setConversations((current) => updateWenfanConversationRecord(current, activeConversationId, nextMessages)); } } function handleSubmit(event: FormEvent) { event.preventDefault(); void askQuestion(input); } function handleTextareaKeyDown(event: KeyboardEvent) { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); void askQuestion(input); } } return (

AI 助手

只读分析 · 内置帮助
{messages.map((message) => ( ))} {showStarterQuestions && (
{STARTER_QUESTIONS.map((question) => ( ))}
)}