"use client"; import React, { useState, useRef, useEffect } from "react"; interface Message { role: "user" | "assistant" | "system"; content: string | any[]; reasoning_content?: string; files?: Array<{ name: string; type: string; dataUrl?: string }>; } export default function AetherisChatPage() { const [messages, setMessages] = useState([]); const [inputValue, setInputValue] = useState(""); const [selectedModel, setSelectedModel] = useState("auto/best-coding"); const [isGenerating, setIsGenerating] = useState(false); const [attachedFiles, setAttachedFiles] = useState< Array<{ name: string; type: string; dataUrl?: string; textContent?: string }> >([]); const [showModelsDropdown, setShowModelsDropdown] = useState(false); const messagesEndRef = useRef(null); const fileInputRef = useRef(null); const textareaRef = useRef(null); const models = [ { id: "auto/best-coding", name: "Best Coding", desc: "Top-tier coding, logic, and debugging" }, { id: "auto/best-reasoning", name: "Best Reasoning", desc: "Collapsible thinking, logic, math", }, { id: "auto/best-free", name: "Best Free", desc: "100% free, unlimited scaling models" }, { id: "auto/best-chat", name: "Best Conversational", desc: "Fast, natural dialogues and answers", }, ]; // Auto-scroll to bottom of chat useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages, isGenerating]); // Auto-resize textarea height useEffect(() => { if (textareaRef.current) { textareaRef.current.style.height = "auto"; textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 200)}px`; } }, [inputValue]); const handleFileUpload = (e: React.ChangeEvent) => { const files = e.target.files; if (!files) return; Array.from(files).forEach((file) => { const reader = new FileReader(); if (file.type.startsWith("image/")) { reader.onload = (event) => { setAttachedFiles((prev) => [ ...prev, { name: file.name, type: file.type, dataUrl: event.target?.result as string }, ]); }; reader.readAsDataURL(file); } else { // Read text files directly as text content reader.onload = (event) => { setAttachedFiles((prev) => [ ...prev, { name: file.name, type: file.type, textContent: event.target?.result as string }, ]); }; reader.readAsText(file); } }); if (fileInputRef.current) fileInputRef.current.value = ""; }; const removeAttachedFile = (index: number) => { setAttachedFiles((prev) => prev.filter((_, i) => i !== index)); }; const executeQuickAction = (promptText: string) => { if (isGenerating) return; sendMessage(promptText, []); }; const handleSend = () => { if (isGenerating) return; const text = inputValue.trim(); if (!text && attachedFiles.length === 0) return; sendMessage(text, attachedFiles); setInputValue(""); setAttachedFiles([]); }; const sendMessage = async (text: string, files: typeof attachedFiles) => { setIsGenerating(true); // Prepare message payload content let userMsgContent: string | any[] = text; // Check if there are attached images for multimodal OpenAI compatibility const attachedImages = files.filter((f) => f.type.startsWith("image/")); const attachedTexts = files.filter((f) => !f.type.startsWith("image/")); if (attachedImages.length > 0) { const parts: any[] = [{ type: "text", text }]; attachedImages.forEach((img) => { parts.push({ type: "image_url", image_url: { url: img.dataUrl }, }); }); userMsgContent = parts; } else if (attachedTexts.length > 0) { // Append text file content straight to the prompt let textFilesPrompt = ""; attachedTexts.forEach((file) => { textFilesPrompt += `[Attached File: ${file.name}]\n\`\`\`\n${file.textContent}\n\`\`\`\n\n`; }); userMsgContent = textFilesPrompt + text; } const newUserMessage: Message = { role: "user", content: userMsgContent, files: files.map((f) => ({ name: f.name, type: f.type, dataUrl: f.dataUrl })), }; const newMessages = [...messages, newUserMessage]; setMessages(newMessages); // Create a placeholder for the assistant response const assistantMessageIndex = newMessages.length; setMessages((prev) => [...prev, { role: "assistant", content: "", reasoning_content: "" }]); try { // Structure chat messages list for completions payload const chatHistory = newMessages.map((msg) => ({ role: msg.role, content: msg.content, })); const response = await fetch("/api/public-chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: selectedModel, messages: chatHistory, stream: true, }), }); if (!response.ok) { throw new Error(`Server returned error: ${response.status}`); } const reader = response.body?.getReader(); const decoder = new TextDecoder(); if (!reader) throw new Error("ReadableStream is not supported by the response."); let assistantText = ""; let reasoningText = ""; while (true) { const { value, done } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split("\n"); for (const line of lines) { if (line.startsWith("data: ")) { const dataStr = line.slice(6).trim(); if (dataStr === "[DONE]") continue; try { const dataObj = JSON.parse(dataStr); const delta = dataObj.choices?.[0]?.delta; if (delta) { if (delta.reasoning_content) { reasoningText += delta.reasoning_content; } if (delta.content) { assistantText += delta.content; } setMessages((prev) => { const updated = [...prev]; updated[assistantMessageIndex] = { role: "assistant", content: assistantText, reasoning_content: reasoningText, }; return updated; }); } } catch { // Ignore parse errors from malformed SSE chunks } } } } } catch (err: any) { console.error(err); setMessages((prev) => { const updated = [...prev]; updated[assistantMessageIndex] = { role: "assistant", content: `⚠️ Error: ${err.message || "Failed to generate response"}`, }; return updated; }); } finally { setIsGenerating(false); } }; // Helper to parse markdown-like code blocks and lists const renderMessageContent = (msg: Message) => { let text = ""; if (typeof msg.content === "string") { text = msg.content; } else if (Array.isArray(msg.content)) { const textPart = msg.content.find((p: any) => p.type === "text"); text = textPart ? textPart.text : ""; } if (!text) return null; // Split content by code blocks const parts = text.split(/(```[\s\S]*?```)/g); return parts.map((part, idx) => { if (part.startsWith("```") && part.endsWith("```")) { const lines = part.split("\n"); const header = lines[0].slice(3).trim(); const code = lines.slice(1, -1).join("\n"); return (
{header || "code"}
              {code}
            
); } // Handle simple formatting like lists and paragraphs return (
{part.split("\n").map((line, lIdx) => { if (line.startsWith("- ") || line.startsWith("* ")) { return (
  • {line.slice(2)}
); } if (/^\d+\.\s/.test(line)) { const numEnd = line.indexOf(" "); return (
  1. {line.slice(numEnd + 1)}
); } return (

{line}

); })}
); }); }; return (
{/* Premium Glassmorphic Header */}
auto_awesome

Aetheris AI

Unified Router

{/* Model Selection Dropdown */}
{showModelsDropdown && (
{models.map((model) => ( ))}
)}
{/* Main Chat Interface */}
{messages.length === 0 ? ( /* Empty / Landing Hero State */
auto_awesome

How can Aetheris help you?

Experience unified intelligence routing across multiple AI models dynamically. Upload code, text, or ask a question.

{/* Quick Action Suggestion Cards */}
) : ( /* Messages Feed */
{messages.map((msg, index) => (
{msg.role !== "user" && (
auto_awesome
)}
{/* Attached files preview in history */} {msg.files && msg.files.length > 0 && (
{msg.files.map((file, fIdx) => (
{file.type.startsWith("image/") ? ( uploaded ) : ( description )} {file.name}
))}
)} {/* Rendering thinking process block for assistant responses */} {msg.role === "assistant" && msg.reasoning_content && (
Thinking Process
{msg.reasoning_content}
)} {renderMessageContent(msg)}
{msg.role === "user" && (
person
)}
))}
)}
{/* Floating Input Panel */}