import React, { useState, useEffect, useRef } from 'react'; import { Button } from '../ui/button'; import { Send, StopCircle, Copy, Check, ChevronDown, ChevronUp, X, CornerUpLeft, Loader2 } from 'lucide-react'; import ReactMarkdown from 'react-markdown'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; import LoadingDots from '../ui/LoadingDots'; interface Message { role: 'user' | 'assistant'; content: string; context?: string; } export default function AIChat() { const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [isGenerating, setIsGenerating] = useState(false); const chatContainerRef = useRef(null); const abortControllerRef = useRef(null); const [copiedIndex, setCopiedIndex] = useState(null); const [context, setContext] = useState(null); const [isContextExpanded, setIsContextExpanded] = useState(false); const [expandedMessageIndex, setExpandedMessageIndex] = useState(null); const [isLoading, setIsLoading] = useState(false); useEffect(() => { scrollToBottom(); }, [messages]); const scrollToBottom = () => { if (chatContainerRef.current) { setTimeout(() => { chatContainerRef.current?.scrollTo({ top: chatContainerRef.current.scrollHeight, behavior: 'smooth' }); }, 100); } }; const handleSend = async () => { if (input.trim() === '' && !context) return; const newMessage: Message = { role: 'user', content: input, context: context || undefined }; setMessages(prev => [...prev, newMessage]); setInput(''); setIsContextExpanded(false); setIsGenerating(true); setIsLoading(true); // Set loading state to true abortControllerRef.current = new AbortController(); try { const queryParams = new URLSearchParams({ instructions: input, ...(context && { context }) // Include context only if it exists }); const response = await fetch(`http://127.0.0.1:8787/api?${queryParams}`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, signal: abortControllerRef.current.signal, }); if (!response.ok) { throw new Error('Failed to get AI response'); } const data = await response.json(); const assistantMessage: Message = { role: 'assistant', content: '' }; setMessages(prev => [...prev, assistantMessage]); setIsLoading(false); // Set loading state to false once we start receiving the response // Simulate text generation for (let i = 0; i <= data.response.length; i++) { if (abortControllerRef.current.signal.aborted) { break; } setMessages(prev => { const updatedMessages = [...prev]; updatedMessages[updatedMessages.length - 1].content = data.response.slice(0, i); return updatedMessages; }); await new Promise(resolve => setTimeout(resolve, 20)); } } catch (error: any) { if (error.name === 'AbortError') { console.log('Generation aborted'); } else { console.error('Error fetching AI response:', error); const errorMessage: Message = { role: 'assistant', content: 'Sorry, I encountered an error. Please try again.' }; setMessages(prev => [...prev, errorMessage]); } } finally { setIsGenerating(false); setIsLoading(false); // Ensure loading state is set to false abortControllerRef.current = null; } }; const handleStopGeneration = () => { if (abortControllerRef.current) { abortControllerRef.current.abort(); } }; const copyToClipboard = (text: string, index: number) => { navigator.clipboard.writeText(text).then(() => { setCopiedIndex(index); setTimeout(() => setCopiedIndex(null), 1000); // Reset after 1 seconds }); }; const askAboutCode = (code: string) => { setContext(`Regarding this code:\n${code}`); setIsContextExpanded(false); }; const removeContext = () => { setContext(null); setIsContextExpanded(false); }; return (
CHAT
{messages.map((message, messageIndex) => (
{message.context && (
setExpandedMessageIndex(expandedMessageIndex === messageIndex ? null : messageIndex)} > Context {expandedMessageIndex === messageIndex ? ( ) : ( )}
{expandedMessageIndex === messageIndex && (
{(() => { // need to fix the language detection const code = message.context!.replace(/^Regarding this code:\n/, ''); const match = /language-(\w+)/.exec(code); const language = match ? match[1] : 'typescript'; return (
{code}
); })()}
)}
)} {message.role === 'assistant' ? (
{language}
{String(children).replace(/\n$/, '')}
) : ( {children} ); }, p({children}) { return

{children}

; }, ul({children}) { return
    {children}
; }, ol({children}) { return
    {children}
; }, li({children}) { return
  • {children}
  • ; }, }} > {message.content} ) : (
    {message.content}
    )}
    ))} {isLoading && ( )}
    {context && (
    setIsContextExpanded(!isContextExpanded)} > Context
    {isContextExpanded ? ( setIsContextExpanded(false)} /> ) : ( setIsContextExpanded(true)} /> )}
    {isContextExpanded && (