Deploy 2026-06-01T21:59:00.799Z
This commit is contained in:
98
src/App.jsx
Normal file
98
src/App.jsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import './App.css'
|
||||
|
||||
const API_URL = window.location.hostname === 'localhost'
|
||||
? 'http://localhost:4003'
|
||||
: 'https://diet-api.psbailey.uk'
|
||||
|
||||
export default function App() {
|
||||
const [messages, setMessages] = useState([])
|
||||
const [input, setInput] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const messagesEnd = useRef(null)
|
||||
const inputRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
messagesEnd.current?.scrollIntoView({ behavior: 'smooth' })
|
||||
}, [messages])
|
||||
|
||||
async function send(e) {
|
||||
e.preventDefault()
|
||||
const text = input.trim()
|
||||
if (!text || loading) return
|
||||
|
||||
setInput('')
|
||||
setMessages(prev => [...prev, { role: 'user', content: text }])
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/chat`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ message: text })
|
||||
})
|
||||
|
||||
if (!res.ok) throw new Error('Failed')
|
||||
|
||||
const reader = res.body.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
let assistantMsg = ''
|
||||
|
||||
setMessages(prev => [...prev, { role: 'assistant', content: '' }])
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
const chunk = decoder.decode(value, { stream: true })
|
||||
// Parse SSE lines
|
||||
for (const line of chunk.split('\n')) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.slice(6)
|
||||
if (data === '[DONE]') break
|
||||
assistantMsg += data
|
||||
setMessages(prev => {
|
||||
const updated = [...prev]
|
||||
updated[updated.length - 1] = { role: 'assistant', content: assistantMsg }
|
||||
return updated
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setMessages(prev => [...prev, { role: 'assistant', content: '⚠️ Connection error. Try again.' }])
|
||||
} finally {
|
||||
setLoading(false)
|
||||
inputRef.current?.focus()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<header>
|
||||
<h1>🥗 Diet Coach</h1>
|
||||
</header>
|
||||
<div className="messages">
|
||||
{messages.length === 0 && (
|
||||
<div className="empty">Ask me about your diet, log food, or get meal suggestions.</div>
|
||||
)}
|
||||
{messages.map((msg, i) => (
|
||||
<div key={i} className={`msg ${msg.role}`}>
|
||||
<div className="bubble">{msg.content || '...'}</div>
|
||||
</div>
|
||||
))}
|
||||
<div ref={messagesEnd} />
|
||||
</div>
|
||||
<form onSubmit={send} className="input-bar">
|
||||
<input
|
||||
ref={inputRef}
|
||||
value={input}
|
||||
onChange={e => setInput(e.target.value)}
|
||||
placeholder="What did you eat today?"
|
||||
disabled={loading}
|
||||
autoFocus
|
||||
/>
|
||||
<button type="submit" disabled={loading || !input.trim()}>Send</button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user