frontend
This commit is contained in:
112
src/components/ChatPanel.jsx
Normal file
112
src/components/ChatPanel.jsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { SendHorizontal, Sparkles } from 'lucide-react';
|
||||
import { ChatMessage } from './ChatMessage';
|
||||
import { Spinner } from './Atoms';
|
||||
import { api } from '../api/client';
|
||||
|
||||
const SUGGESTIONS = [
|
||||
'Recommend a low sugar biscuit',
|
||||
'What snacks come in family packs?',
|
||||
'Show me something under ₹20',
|
||||
'Any options with no added preservatives?',
|
||||
];
|
||||
|
||||
export function ChatPanel({ brand, onSelectSource }) {
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [input, setInput] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const scrollRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' });
|
||||
}, [messages, loading]);
|
||||
|
||||
async function send(text) {
|
||||
const query = (text ?? input).trim();
|
||||
if (!query || loading) return;
|
||||
|
||||
setInput('');
|
||||
setError(null);
|
||||
const history = messages.map((m) => ({ role: m.role, content: m.content }));
|
||||
setMessages((prev) => [...prev, { role: 'user', content: query }]);
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const res = await api.chat({ query, brand: brand || undefined, history });
|
||||
setMessages((prev) => [...prev, { role: 'assistant', content: res.answer, sources: res.sources, detectedCategory: res.detected_category }]);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<div ref={scrollRef} className="flex-1 space-y-4 overflow-y-auto px-6 py-5">
|
||||
{messages.length === 0 && (
|
||||
<div className="flex h-full flex-col items-center justify-center gap-4 text-center">
|
||||
<span className="flex h-12 w-12 items-center justify-center rounded-full bg-amber-100 text-amber-600">
|
||||
<Sparkles className="h-6 w-6" />
|
||||
</span>
|
||||
<div>
|
||||
<p className="font-display font-semibold text-ink-950">Ask about the catalog</p>
|
||||
<p className="mt-1 max-w-sm text-sm text-slate-500">
|
||||
I'll answer using only the products actually in the catalog
|
||||
{brand ? ` for ${brand}` : ''}, and show you exactly which ones I used.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap justify-center gap-2">
|
||||
{SUGGESTIONS.map((s) => (
|
||||
<button
|
||||
key={s}
|
||||
onClick={() => send(s)}
|
||||
className="rounded-full border border-ink-900/15 px-3 py-1.5 text-xs text-ink-700 transition hover:border-amber-500 hover:text-amber-600"
|
||||
>
|
||||
{s}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{messages.map((m, i) => (
|
||||
<ChatMessage key={i} role={m.role} content={m.content} sources={m.sources} detectedCategory={m.detectedCategory} onSelectSource={onSelectSource} />
|
||||
))}
|
||||
|
||||
{loading && (
|
||||
<div className="flex items-center gap-2 pl-9">
|
||||
<Spinner label="Thinking through the catalog…" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<p className="rounded-lg bg-maroon-100 px-3 py-2 text-sm text-maroon-600">{error}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
send();
|
||||
}}
|
||||
className="flex items-center gap-2 border-t border-ink-900/10 bg-paper-50 px-4 py-3"
|
||||
>
|
||||
<input
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
placeholder={brand ? `Ask about ${brand}'s products…` : 'Ask about any product in the catalog…'}
|
||||
className="flex-1 rounded-full border border-ink-900/10 bg-white px-4 py-2.5 text-sm focus:border-amber-500 focus:outline-none focus:ring-2 focus:ring-amber-500/20"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading || !input.trim()}
|
||||
className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-ink-900 text-paper-100 transition hover:bg-ink-800 disabled:opacity-40"
|
||||
>
|
||||
<SendHorizontal className="h-4 w-4" />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user