import { useState, useRef, useEffect, useMemo } from 'react'; import { ArrowUp, Bot, Trash2, Copy, Check, ImageUp, X, Brain, Pencil, Globe, Download, Wand2 } from 'lucide-react'; import { Sun, Moon } from 'lucide-react'; import { GROQ_API_KEYS, GROQ_API_URL, GROQ_MODEL_REASONER, POLLINATIONS_API_URL, POLLINATIONS_MODEL_TEXT, POLLINATIONS_MODEL_VISION, POLLINATIONS_MODEL_FALLBACK, POLLINATIONS_MODEL_SEARCH, TACTIQ_API_URL } from './config/api'; import ReactMarkdown, { type Components } from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; import 'katex/dist/katex.min.css'; import { InlineMath, BlockMath } from 'react-katex'; import type { Message, EditingMessage } from './types/msg'; import { SYSTEM_PROMPT, STORAGE_KEY, MAX_RETRIES, RETRY_DELAY, REQUEST_TIMEOUT } from './config/const'; const CodeBlock = ({ children, inline, className }: { children: string | string[]; inline?: boolean; className?: string }) => { const codeContent = Array.isArray(children) ? children.join('') : children?.toString() || ''; if (inline || !className) { return {codeContent}; } const [copied, setCopied] = useState(false); const language = className?.replace(/language-/, '') || 'plaintext'; const handleCopy = async () => { await navigator.clipboard.writeText(codeContent); setCopied(true); setTimeout(() => setCopied(false), 2000); }; const customStyle = { ...materialDark, 'pre[class*="language-"]': { ...materialDark['pre[class*="language-"]'], background: 'transparent' } }; return (
{codeContent}
); }; interface ThinkingBlockProps { id: string; children: React.ReactNode; isExpanded: boolean; onToggle: (id: string) => void; } const ThinkingProcess: React.FC = ({ id, children, isExpanded, onToggle }) => { return (
{isExpanded && (
{children}
)}
); }; const MathBlock = ({ math, isInParagraph }: { math: string; isInParagraph: boolean }) => { try { if (isInParagraph) { return ( {`\\displaystyle ${math}`} ); } return (
{math}
); } catch (error) { console.error('Math rendering error:', error); return Error rendering equation: {math}; } }; const ChatMessage = ({ message, onEdit, isEditing, editContent, onEditChange, onEditSave, onEditCancel, setFullScreenImage }: { message: Message; onEdit?: (message: Message) => void; isEditing?: boolean; editContent?: string; onEditChange?: (content: string) => void; onEditSave?: () => void; onEditCancel?: () => void; setFullScreenImage: (image: string | null) => void; }) => { const [expandedThinkBlocks, setExpandedThinkBlocks] = useState>(new Set()); const toggleThinkBlock = (id: string) => { setExpandedThinkBlocks(prev => { const newSet = new Set(prev); if (newSet.has(id)) { newSet.delete(id); } else { newSet.add(id); } return newSet; }); }; const TextBlock = ({ children, isInParagraph = false }: { children: React.ReactNode; isInParagraph?: boolean }) => { if (Array.isArray(children)) { return <>{children.map((child, i) => {child})}; } if (!children || typeof children !== 'string') { return <>{children}; } if (!children.includes('$')) { return <>{children}; } const segments: string[] = []; let currentText = ''; let pos = 0; let inMath = false; let mathStart = 0; while (pos < children.length) { if (children[pos] === '$') { if (pos + 1 < children.length && children[pos + 1] === '$') { if (!inMath) { if (currentText) segments.push(currentText); currentText = '$$'; mathStart = pos; inMath = true; pos += 2; } else if (pos > mathStart + 2) { segments.push(currentText + '$$'); currentText = ''; inMath = false; pos += 2; } else { currentText += '$$'; pos += 2; } } else { if (!inMath) { if (currentText) segments.push(currentText); currentText = '$'; mathStart = pos; inMath = true; pos++; } else if (pos > mathStart + 1) { segments.push(currentText + '$'); currentText = ''; inMath = false; pos++; } else { currentText += '$'; pos++; } } } else { currentText += children[pos]; pos++; } } if (currentText) { segments.push(currentText); } return ( <> {segments.map((segment, i) => { try { if (segment.startsWith('$$') && segment.endsWith('$$')) { const math = segment.slice(2, -2); return ; } else if (segment.startsWith('$') && segment.endsWith('$')) { const math = segment.slice(1, -1); return {math}; } return {segment}; } catch (error) { console.error('LaTeX rendering error:', error); return {segment}; } })} ); }; const components = { code: ({ children, inline, className }: { children: string; inline?: boolean; className?: string }) => ( {children as string} ), think: ({ children }: { children: React.ReactNode }) => { const thinkId = useMemo(() => { const content = children?.toString() || ''; return `think-${message.id}-${content.slice(0, 32)}`; }, [children, message.id]); return ( {children} ); }, p: ({ children }: { children: React.ReactNode }) => (

{children}

), strong: ({ children }: { children: React.ReactNode }) => ( {children} ), em: ({ children }: { children: React.ReactNode }) => ( {children} ), li: ({ children }: { children: React.ReactNode }) => (
  • {children}
  • ), h1: ({ children }: { children: React.ReactNode }) => (

    {children}

    ), h2: ({ children }: { children: React.ReactNode }) => (

    {children}

    ), h3: ({ children }: { children: React.ReactNode }) => (

    {children}

    ) }; const processMessageContent = (content: string) => { if (content.includes('') && !content.includes('')) { content = content.replace(//g, ''); } const thinkMatch = content.match(/([\s\S]*?)<\/think>/); if (thinkMatch) { const thinking = thinkMatch[1].trim(); const restOfContent = content.replace(/[\s\S]*?<\/think>/, '').trim(); return ( <> {thinking} } > {restOfContent} ); } return ( } > {content} ); }; const downloadImage = async (imageUrl: string) => { try { const response = await fetch(imageUrl); const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `image-${Date.now()}.png`; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(url); } catch (error) { console.error('Error downloading image:', error); } }; return (
    {isEditing ? (