| import React, { useEffect, useRef, useState } from 'react' | |
| export default function App() { | |
| const [messages, setMessages] = useState([ | |
| { role: 'system', content: 'You are a helpful assistant that explains your reasoning clearly and concisely.' } | |
| ]) | |
| const [input, setInput] = useState('') | |
| const [loading, setLoading] = useState(false) | |
| const [model, setModel] = useState('') | |
| const endRef = useRef(null) | |
| useEffect(() => { | |
| endRef.current?.scrollIntoView({ behavior: 'smooth' }) | |
| }, [messages, loading]) | |
| async function sendChat(nextMessages) { | |
| const res = await fetch('/api/chat', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| messages: nextMessages, | |
| max_new_tokens: 512, | |
| temperature: 0.7, | |
| top_p: 0.95 | |
| }) | |
| }) | |
| if (!res.ok) { | |
| const t = await res.text() | |
| throw new Error(`API ${res.status}: ${t}`) | |
| } | |
| return res.json() | |
| } | |
| const onSend = async () => { | |
| const text = input.trim() | |
| if (!text || loading) return | |
| const next = [...messages, { role: 'user', content: text }] | |
| setMessages(next) | |
| setInput('') | |
| setLoading(true) | |
| try { | |
| const { reply, model } = await sendChat(next) | |
| setModel(model) | |
| setMessages([...next, { role: 'assistant', content: reply }]) | |
| } catch (e) { | |
| setMessages([...next, { role: 'assistant', content: `(Error) ${e.message}` }]) | |
| } finally { | |
| setLoading(false) | |
| } | |
| } | |
| const onKeyDown = (e) => { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault() | |
| onSend() | |
| } | |
| } | |
| return ( | |
| <div className="app"> | |
| <header className="header"> | |
| <div className="brand">Fathom R1 Chat</div> | |
| {model && <div className="model">{model}</div>} | |
| </header> | |
| <main className="chat"> | |
| {messages.filter(m => m.role !== 'system').map((m, i) => ( | |
| <div key={i} className={`bubble ${m.role}`}> | |
| <div className="sender">{m.role === 'user' ? 'You' : 'Assistant'}</div> | |
| <div className="content">{m.content}</div> | |
| </div> | |
| ))} | |
| {loading && <div className="bubble assistant"><div className="content">Thinking…</div></div>} | |
| <div ref={endRef} /> | |
| </main> | |
| <footer className="composer"> | |
| <textarea | |
| value={input} | |
| onChange={(e) => setInput(e.target.value)} | |
| onKeyDown={onKeyDown} | |
| placeholder="Ask a question…" | |
| rows={2} | |
| /> | |
| <button onClick={onSend} disabled={loading || !input.trim()}>Send</button> | |
| </footer> | |
| </div> | |
| ) | |
| } | |