82 lines
2.7 KiB
React
82 lines
2.7 KiB
React
import React, { useState, useRef, useEffect } from 'react';
|
|
import ReactMarkdown from 'react-markdown';
|
|
import './App.css';
|
|
|
|
function App() {
|
|
const [messages, setMessages] = useState([]);
|
|
const [input, setInput] = useState("");
|
|
const messagesEndRef = useRef(null);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const sendMessage = async () => {
|
|
if (!input.trim()) return;
|
|
|
|
const userMessage = { role: "user", content: input };
|
|
setMessages(prev => [...prev, userMessage]);
|
|
setInput("");
|
|
setLoading(true); // 🚀 show loader
|
|
|
|
try {
|
|
const res = await fetch("/chat", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ user_id: "user1", message: input })
|
|
});
|
|
|
|
const data = await res.json();
|
|
const botMessage = { role: "assistant", content: data.response };
|
|
setMessages(prev => [...prev, botMessage]);
|
|
} finally {
|
|
setLoading(false); // ✅ hide loader
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
}, [messages]);
|
|
|
|
return (
|
|
<div className="chat-container">
|
|
<header className="navbar navbar-dark bg-primary sticky-top">
|
|
<div className="container-fluid">
|
|
<span className="navbar-brand mb-0 h1">Egalware's LM Studio Chat</span>
|
|
</div>
|
|
</header>
|
|
<div className="chat-box">
|
|
{loading && (
|
|
<div className="d-flex justify-content-start p-2">
|
|
<div className="spinner-border text-secondary" role="status" style={{width: "1.5rem", height: "1.5rem"}}>
|
|
<span className="visually-hidden">Thinking...</span>
|
|
</div>
|
|
<span className="ms-2 text-muted">The model is processing...</span>
|
|
</div>
|
|
)}
|
|
{messages.map((msg, i) => (
|
|
<div key={i} className={`d-flex mb-2 ${msg.role === "user" ? "justify-content-end" : "justify-content-start"}`}>
|
|
<div className={`p-2 rounded shadow-sm ${msg.role === "user" ? "bg-primary text-white" : "bg-light text-dark border" }`} style={{ maxWidth: "75%" }}>
|
|
<ReactMarkdown>{msg.content}</ReactMarkdown>
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
<div ref={messagesEndRef} />
|
|
</div>
|
|
<div className="input-bar d-flex justify-content-center p-3 bg-light border-top">
|
|
<div className="w-100 w-md-75 w-lg-50 d-flex">
|
|
<input className="form-control me-2"
|
|
value={input}
|
|
onChange={e => setInput(e.target.value)}
|
|
onKeyDown={e => e.key === "Enter" && sendMessage()}
|
|
placeholder="Type your message..."
|
|
autoFocus
|
|
/>
|
|
<button className="btn btn-primary" onClick={sendMessage}>Send</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default App;
|
|
|