import { useEffect, useMemo, useRef, useState } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { IpcClient } from "@/ipc/ipc_client"; import { v4 as uuidv4 } from "uuid"; import { LoadingBlock, VanillaMarkdownParser } from "@/components/LoadingBlock"; interface HelpBotDialogProps { isOpen: boolean; onClose: () => void; } interface Message { role: "user" | "assistant"; content: string; reasoning?: string; } export function HelpBotDialog({ isOpen, onClose }: HelpBotDialogProps) { const [input, setInput] = useState(""); const [messages, setMessages] = useState([]); const [streaming, setStreaming] = useState(false); const [error, setError] = useState(null); const assistantBufferRef = useRef(""); const reasoningBufferRef = useRef(""); const flushTimerRef = useRef(null); const FLUSH_INTERVAL_MS = 100; const sessionId = useMemo(() => uuidv4(), [isOpen]); useEffect(() => { if (!isOpen) { // Clean up when dialog closes setMessages([]); setInput(""); setError(null); assistantBufferRef.current = ""; reasoningBufferRef.current = ""; // Clear the flush timer if (flushTimerRef.current) { window.clearInterval(flushTimerRef.current); flushTimerRef.current = null; } } }, [isOpen]); // Cleanup on component unmount useEffect(() => { return () => { // Clear the flush timer on unmount if (flushTimerRef.current) { window.clearInterval(flushTimerRef.current); flushTimerRef.current = null; } }; }, []); const handleSend = async () => { const trimmed = input.trim(); if (!trimmed || streaming) return; setError(null); // Clear any previous errors setMessages((prev) => [ ...prev, { role: "user", content: trimmed }, { role: "assistant", content: "", reasoning: "" }, ]); assistantBufferRef.current = ""; reasoningBufferRef.current = ""; setInput(""); setStreaming(true); IpcClient.getInstance().startHelpChat(sessionId, trimmed, { onChunk: (delta) => { // Buffer assistant content; UI will flush on interval for smoothness assistantBufferRef.current += delta; }, onEnd: () => { // Final flush then stop streaming setMessages((prev) => { const next = [...prev]; const lastIdx = next.length - 1; if (lastIdx >= 0 && next[lastIdx].role === "assistant") { next[lastIdx] = { ...next[lastIdx], content: assistantBufferRef.current, reasoning: reasoningBufferRef.current, }; } return next; }); setStreaming(false); if (flushTimerRef.current) { window.clearInterval(flushTimerRef.current); flushTimerRef.current = null; } }, onError: (errorMessage: string) => { setError(errorMessage); setStreaming(false); // Clear the flush timer if (flushTimerRef.current) { window.clearInterval(flushTimerRef.current); flushTimerRef.current = null; } // Clear the buffers assistantBufferRef.current = ""; reasoningBufferRef.current = ""; // Remove the empty assistant message that was added optimistically setMessages((prev) => { const next = [...prev]; if ( next.length > 0 && next[next.length - 1].role === "assistant" && !next[next.length - 1].content ) { next.pop(); } return next; }); }, }); // Start smooth flush interval if (flushTimerRef.current) { window.clearInterval(flushTimerRef.current); } flushTimerRef.current = window.setInterval(() => { setMessages((prev) => { const next = [...prev]; const lastIdx = next.length - 1; if (lastIdx >= 0 && next[lastIdx].role === "assistant") { const current = next[lastIdx]; // Only update if there's any new data to apply if ( current.content !== assistantBufferRef.current || current.reasoning !== reasoningBufferRef.current ) { next[lastIdx] = { ...current, content: assistantBufferRef.current, reasoning: reasoningBufferRef.current, }; } } return next; }); }, FLUSH_INTERVAL_MS); }; return ( MoreMinimore Help Bot
{error && (
Error:
{error}
)}
{messages.length === 0 ? (
Ask a question about using MoreMinimore.
This conversation may be logged and used to improve the product. Please do not put any sensitive information in here.
) : (
{messages.map((m, i) => (
{m.role === "user" ? (
{m.content}
) : (
{streaming && i === messages.length - 1 && ( )} {m.content && (
)}
)}
))}
)}
setInput(e.target.value)} placeholder="Type your question..." onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSend(); } }} />
); }