File size: 2,636 Bytes
e618a4f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

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>
  )
}