import React, { useState, useEffect, useRef, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { httpClient } from '@/app/infra/http/HttpClient'; import { DialogContent } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { ScrollArea } from '@/components/ui/scroll-area'; import { cn } from '@/lib/utils'; import { Message } from '@/app/infra/entities/message'; import { toast } from 'sonner'; import AtBadge from './AtBadge'; import { Switch } from '@/components/ui/switch'; interface MessageComponent { type: 'At' | 'Plain'; target?: string; text?: string; } interface DebugDialogProps { open: boolean; pipelineId: string; isEmbedded?: boolean; } export default function DebugDialog({ open, pipelineId, isEmbedded = false, }: DebugDialogProps) { const { t } = useTranslation(); const [selectedPipelineId, setSelectedPipelineId] = useState(pipelineId); const [sessionType, setSessionType] = useState<'person' | 'group'>('person'); const [messages, setMessages] = useState([]); const [inputValue, setInputValue] = useState(''); const [showAtPopover, setShowAtPopover] = useState(false); const [hasAt, setHasAt] = useState(false); const [isHovering, setIsHovering] = useState(false); const [isStreaming, setIsStreaming] = useState(true); const messagesEndRef = useRef(null); const inputRef = useRef(null); const popoverRef = useRef(null); const scrollToBottom = useCallback(() => { // 使用setTimeout确保在DOM更新后执行滚动 setTimeout(() => { const scrollArea = document.querySelector('.scroll-area') as HTMLElement; if (scrollArea) { scrollArea.scrollTo({ top: scrollArea.scrollHeight, behavior: 'smooth', }); } // 同时确保messagesEndRef也滚动到视图 messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, 0); }, []); const loadMessages = useCallback( async (pipelineId: string) => { try { const response = await httpClient.getWebChatHistoryMessages( pipelineId, sessionType, ); setMessages(response.messages); } catch (error) { console.error('Failed to load messages:', error); } }, [sessionType], ); // 在useEffect中监听messages变化时滚动 useEffect(() => { scrollToBottom(); }, [messages, scrollToBottom]); useEffect(() => { if (open) { setSelectedPipelineId(pipelineId); loadMessages(pipelineId); } }, [open, pipelineId]); useEffect(() => { if (open) { loadMessages(selectedPipelineId); } }, [sessionType, selectedPipelineId, open, loadMessages]); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( popoverRef.current && !popoverRef.current.contains(event.target as Node) && !inputRef.current?.contains(event.target as Node) ) { setShowAtPopover(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, []); useEffect(() => { if (showAtPopover) { setIsHovering(true); } }, [showAtPopover]); const handleInputChange = (e: React.ChangeEvent) => { const value = e.target.value; if (sessionType === 'group') { if (value.endsWith('@')) { setShowAtPopover(true); } else if (showAtPopover && (!value.includes('@') || value.length > 1)) { setShowAtPopover(false); } } setInputValue(value); }; const handleAtSelect = () => { setHasAt(true); setShowAtPopover(false); setInputValue(inputValue.slice(0, -1)); }; const handleAtRemove = () => { setHasAt(false); }; const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); if (showAtPopover) { handleAtSelect(); } else { sendMessage(); } } else if (e.key === 'Backspace' && hasAt && inputValue === '') { handleAtRemove(); } }; const sendMessage = async () => { if (!inputValue.trim() && !hasAt) return; try { const messageChain = []; let text_content = inputValue.trim(); if (hasAt) { text_content = ' ' + text_content; } if (hasAt) { messageChain.push({ type: 'At', target: 'webchatbot', }); } messageChain.push({ type: 'Plain', text: text_content, }); if (hasAt) { // for showing text_content = '@webchatbot' + text_content; } const userMessage: Message = { id: -1, role: 'user', content: text_content, timestamp: new Date().toISOString(), message_chain: messageChain, }; // 根据isStreaming状态决定使用哪种传输方式 if (isStreaming) { // streaming // 创建初始bot消息 const placeholderRandomId = Math.floor(Math.random() * 1000000); const botMessagePlaceholder: Message = { id: placeholderRandomId, role: 'assistant', content: 'Generating...', timestamp: new Date().toISOString(), message_chain: [{ type: 'Plain', text: 'Generating...' }], }; // 添加用户消息和初始bot消息到状态 setMessages((prevMessages) => [ ...prevMessages, userMessage, botMessagePlaceholder, ]); setInputValue(''); setHasAt(false); try { await httpClient.sendStreamingWebChatMessage( sessionType, messageChain, selectedPipelineId, (data) => { // 处理流式响应数据 console.log('data', data); if (data.message) { // 更新完整内容 setMessages((prevMessages) => { const updatedMessages = [...prevMessages]; const botMessageIndex = updatedMessages.findIndex( (message) => message.id === placeholderRandomId, ); if (botMessageIndex !== -1) { updatedMessages[botMessageIndex] = { ...updatedMessages[botMessageIndex], content: data.message.content, message_chain: [ { type: 'Plain', text: data.message.content }, ], }; } return updatedMessages; }); } }, () => {}, (error) => { // 处理错误 console.error('Streaming error:', error); if (sessionType === 'person') { toast.error(t('pipelines.debugDialog.sendFailed')); } }, ); } catch (error) { console.error('Failed to send streaming message:', error); if (sessionType === 'person') { toast.error(t('pipelines.debugDialog.sendFailed')); } } } else { // non-streaming setMessages((prevMessages) => [...prevMessages, userMessage]); setInputValue(''); setHasAt(false); const response = await httpClient.sendWebChatMessage( sessionType, messageChain, selectedPipelineId, 180000, ); setMessages((prevMessages) => [...prevMessages, response.message]); } } catch ( // eslint-disable-next-line @typescript-eslint/no-explicit-any error: any ) { console.log(error, 'type of error', typeof error); console.error('Failed to send message:', error); if (!error.message.includes('timeout') && sessionType === 'person') { toast.error(t('pipelines.debugDialog.sendFailed')); } } finally { inputRef.current?.focus(); } }; const renderMessageContent = (message: Message) => { return ( {(message.message_chain as MessageComponent[]).map( (component, index) => { if (component.type === 'At') { return ( ); } else if (component.type === 'Plain') { return {component.text}; } return null; }, )} ); }; const renderContent = () => (
{messages.length === 0 ? (
{t('pipelines.debugDialog.noMessages')}
) : ( messages.map((message) => (
{renderMessageContent(message)}
{message.role === 'user' ? t('pipelines.debugDialog.userMessage') : t('pipelines.debugDialog.botMessage')}
)) )}
{t('pipelines.debugDialog.streaming')}
{hasAt && ( )}
{showAtPopover && (
setIsHovering(true)} onMouseLeave={() => setIsHovering(false)} > @webchatbot - {t('pipelines.debugDialog.atTips')}
)}
); // 如果是嵌入模式,直接返回内容 if (isEmbedded) { return (
{renderContent()}
); } // 原有的Dialog包装 return ( {renderContent()} ); }