import { useState, useRef, FormEvent } from 'react'; import { StreamingText } from './StreamingText'; interface Message { role: 'user' | 'assistant'; content: string; } export default function App() { const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [isStreaming, setIsStreaming] = useState(false); const abortControllerRef = useRef(null); const handleSubmit = async (e: FormEvent) => { e.preventDefault(); if (!input.trim() || isStreaming) return; const userMessage: Message = { role: 'user', content: input }; const newMessages = [...messages, userMessage]; setMessages(newMessages); setInput(''); setIsStreaming(true); setMessages((prev) => [...prev, { role: 'assistant', content: '' }]); abortControllerRef.current = new AbortController(); try { const response = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: newMessages }), signal: abortControllerRef.current.signal, }); if (!response.body) throw new Error('No body in response'); const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); // Robust SSE parser: handles chunks split across network packets const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.startsWith('data:')) { const jsonStr = line.replace('data:', '').trim(); if (jsonStr === '[DONE]') continue; try { const parsed = JSON.parse(jsonStr); // OpenAI-compatible SSE: choices[0].delta.content const token: string = parsed.choices?.[0]?.delta?.content ?? ''; if (token) { setMessages((prev) => { const updated = [...prev]; const lastMsg = updated[updated.length - 1]; if (lastMsg.role === 'assistant') { lastMsg.content += token; } return updated; }); } } catch (err) { console.error('JSON parse error on line:', line, err); } } } } } catch (error) { if ((error as Error).name === 'AbortError') { console.log('Stream aborted by user'); } else { console.error('Chat error:', error); } } finally { setIsStreaming(false); abortControllerRef.current = null; } }; const handleStop = () => { abortControllerRef.current?.abort(); }; return (

Qwen 2.5 7B Chat

{messages.map((msg, idx) => (
{msg.role === 'assistant' && idx === messages.length - 1 && isStreaming ? ( ) : (
{msg.content}
)}
))}
); }