Spaces:
Sleeping
Sleeping
| import React, { useState, useEffect, useRef } from 'react'; | |
| const ChatInterface = () => { | |
| const [messages, setMessages] = useState([ | |
| { role: 'assistant', content: 'Hello! I am your custom LLM. How can I help you today?' } | |
| ]); | |
| const [input, setInput] = useState(''); | |
| const [isLoading, setIsLoading] = useState(false); | |
| const messagesEndRef = useRef(null); | |
| const scrollToBottom = () => { | |
| messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); | |
| }; | |
| useEffect(scrollToBottom, [messages]); | |
| const handleSubmit = async (e) => { | |
| e.preventDefault(); | |
| if (!input.trim()) return; | |
| const userMessage = { role: 'user', content: input }; | |
| setMessages(prev => [...prev, userMessage]); | |
| setInput(''); | |
| setIsLoading(true); | |
| try { | |
| const response = await fetch('/api/chat', { // Proxied to HF/FastAPI | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| messages: [...messages, userMessage].map(m => ({ | |
| role: m.role, | |
| content: m.content | |
| })) | |
| }), | |
| }); | |
| const data = await response.json(); | |
| setMessages(prev => [...prev, { | |
| role: 'assistant', | |
| content: data.choices[0].message.content | |
| }]); | |
| } catch (error) { | |
| console.error('Error:', error); | |
| setMessages(prev => [...prev, { | |
| role: 'assistant', | |
| content: 'Sorry, I encountered an error. Please check your backend connection.' | |
| }]); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| return ( | |
| <div className="flex flex-col h-screen max-w-4xl mx-auto p-4 font-sans text-gray-800"> | |
| <header className="py-6 border-b"> | |
| <h1 className="text-2xl font-bold text-indigo-600">Custom LLM Studio</h1> | |
| <p className="text-sm text-gray-500">Connected to HF Spaces / FastAPI Backend</p> | |
| </header> | |
| <div className="flex-1 overflow-y-auto py-6 space-y-4"> | |
| {messages.map((m, i) => ( | |
| <div key={i} className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}> | |
| <div className={`max-w-[80%] rounded-2xl px-4 py-2 ${ | |
| m.role === 'user' | |
| ? 'bg-indigo-600 text-white rounded-tr-none' | |
| : 'bg-gray-100 text-gray-800 rounded-tl-none' | |
| }`}> | |
| <p className="whitespace-pre-wrap">{m.content}</p> | |
| </div> | |
| </div> | |
| ))} | |
| {isLoading && ( | |
| <div className="flex justify-start"> | |
| <div className="bg-gray-100 rounded-2xl px-4 py-2 rounded-tl-none animate-pulse"> | |
| Typing... | |
| </div> | |
| </div> | |
| )} | |
| <div ref={messagesEndRef} /> | |
| </div> | |
| <form onSubmit={handleSubmit} className="py-6 border-t flex gap-2"> | |
| <input | |
| type="text" | |
| value={input} | |
| onChange={(e) => setInput(e.target.value)} | |
| placeholder="Type your message..." | |
| className="flex-1 border rounded-full px-6 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500" | |
| disabled={isLoading} | |
| /> | |
| <button | |
| type="submit" | |
| className="bg-indigo-600 text-white rounded-full px-6 py-2 font-semibold hover:bg-indigo-700 disabled:opacity-50 transition" | |
| disabled={isLoading} | |
| > | |
| Send | |
| </button> | |
| </form> | |
| </div> | |
| ); | |
| }; | |
| export default ChatInterface; | |