This commit is contained in:
sriram
2026-08-11 19:14:03 +05:30
commit 3765e666bc
35 changed files with 7024 additions and 0 deletions

View 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>
);
}