'use client'; import React, { useState, useEffect, useRef, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { httpClient } from '@/app/infra/http/HttpClient'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; import { MessageChainComponent, Plain, At, Image, Quote, Voice, } from '@/app/infra/entities/message'; interface SessionInfo { session_id: string; bot_id: string; bot_name: string; pipeline_id: string; pipeline_name: string; message_count: number; start_time: string; last_activity: string; is_active: boolean; platform?: string | null; user_id?: string | null; } interface SessionMessage { id: string; timestamp: string; bot_id: string; bot_name: string; pipeline_id: string; pipeline_name: string; message_content: string; session_id: string; status: string; level: string; platform?: string | null; user_id?: string | null; runner_name?: string | null; variables?: string | null; role?: string | null; } interface BotSessionMonitorProps { botId: string; } export default function BotSessionMonitor({ botId }: BotSessionMonitorProps) { const { t } = useTranslation(); const [sessions, setSessions] = useState([]); const [selectedSessionId, setSelectedSessionId] = useState( null, ); const [messages, setMessages] = useState([]); const [loadingSessions, setLoadingSessions] = useState(false); const [loadingMessages, setLoadingMessages] = useState(false); const messagesContainerRef = useRef(null); const loadSessions = useCallback(async () => { setLoadingSessions(true); try { const response = await httpClient.getBotSessions(botId); setSessions(response.sessions ?? []); } catch (error) { console.error('Failed to load sessions:', error); } finally { setLoadingSessions(false); } }, [botId]); const loadMessages = useCallback(async (sessionId: string) => { setLoadingMessages(true); try { const response = await httpClient.getSessionMessages(sessionId); const sorted = (response.messages ?? []).sort( (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(), ); setMessages(sorted); } catch (error) { console.error('Failed to load session messages:', error); } finally { setLoadingMessages(false); } }, []); useEffect(() => { loadSessions(); }, [loadSessions]); useEffect(() => { if (selectedSessionId) { loadMessages(selectedSessionId); } else { setMessages([]); } }, [selectedSessionId, loadMessages]); useEffect(() => { const container = messagesContainerRef.current; if (container) { const viewport = container.querySelector( '[data-radix-scroll-area-viewport]', ); const scrollTarget = viewport || container; scrollTarget.scrollTop = scrollTarget.scrollHeight; } }, [messages]); const parseMessageChain = (content: string): MessageChainComponent[] => { try { const parsed = JSON.parse(content); if (Array.isArray(parsed)) { return parsed as MessageChainComponent[]; } } catch { // Not JSON, return as plain text } return [{ type: 'Plain', text: content } as Plain]; }; const isUserMessage = (msg: SessionMessage): boolean => { if (msg.role === 'assistant') return false; if (msg.role === 'user') return true; return !msg.runner_name; }; const renderMessageComponent = ( component: MessageChainComponent, index: number, ) => { switch (component.type) { case 'Plain': return {(component as Plain).text}; case 'At': { const atComponent = component as At; const displayName = atComponent.display || atComponent.target?.toString() || ''; return ( @{displayName} ); } case 'AtAll': return ( @All ); case 'Image': { const img = component as Image; const imageUrl = img.url || (img.base64 ? img.base64 : ''); if (!imageUrl) { return ( [Image] ); } return (
Image
); } case 'Voice': { const voice = component as Voice; const voiceUrl = voice.url || (voice.base64 ? voice.base64 : ''); if (!voiceUrl) { return ( ๐ŸŽ™ [Voice] ); } return (
); } case 'Quote': { const quote = component as Quote; return (
{quote.origin?.map((comp, idx) => renderMessageComponent(comp as MessageChainComponent, idx), )}
); } case 'Source': return null; case 'File': { const file = component as MessageChainComponent & { name?: string }; return ( ๐Ÿ“Ž {file.name || 'File'} ); } default: return ( [{component.type}] ); } }; const renderMessageContent = (msg: SessionMessage) => { const chain = parseMessageChain(msg.message_content); return (
{chain.map((component, index) => renderMessageComponent(component, index), )}
); }; const formatTime = (timestamp: string): string => { if (!timestamp) return ''; const date = new Date(timestamp); const hours = date.getHours().toString().padStart(2, '0'); const minutes = date.getMinutes().toString().padStart(2, '0'); return `${hours}:${minutes}`; }; const formatRelativeTime = (timestamp: string): string => { if (!timestamp) return ''; const date = new Date(timestamp); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); if (diffMins < 1) return '<1m'; if (diffMins < 60) return `${diffMins}m`; if (diffHours < 24) return `${diffHours}h`; return `${diffDays}d`; }; const selectedSession = sessions.find( (s) => s.session_id === selectedSessionId, ); return (
{/* Left Panel: Session List */}
{/* Refresh Button */}
{/* Session List */} {loadingSessions && sessions.length === 0 ? (
{t('bots.sessionMonitor.loading')}
) : sessions.length === 0 ? (
{t('bots.sessionMonitor.noSessions')}
) : (
{sessions.map((session) => { const isSelected = selectedSessionId === session.session_id; return ( ); })}
)}
{/* Right Panel: Messages */}
{!selectedSessionId ? (
{t('bots.sessionMonitor.selectSession')}
) : ( <> {/* Chat Header */}
{selectedSession?.user_id || selectedSessionId.slice(0, 20)}
{selectedSession?.platform && ( {selectedSession.platform} )} {selectedSession?.pipeline_name && ( <> {selectedSession?.platform && ยท} {selectedSession.pipeline_name} )} {selectedSession?.is_active && ( <> ยท Active )}
{/* Messages Area โ€” matches DebugDialog style */}
{loadingMessages ? (
{t('bots.sessionMonitor.loading')}
) : messages.length === 0 ? (
{t('bots.sessionMonitor.noMessages')}
) : ( messages.map((msg) => { const isUser = isUserMessage(msg); return (
{renderMessageContent(msg)} {/* Role label + timestamp inside bubble, matching DebugDialog */}
{isUser ? t('bots.sessionMonitor.userMessage', { defaultValue: 'User', }) : t('bots.sessionMonitor.botMessage', { defaultValue: 'Assistant', })} {formatTime(msg.timestamp)} {msg.status === 'error' && ( error )} {msg.runner_name && ( {msg.runner_name} )}
); }) )}
)}
); }