Spaces:
Sleeping
Sleeping
Upload frontend/src/components/ChatWidget.jsx
Browse files
frontend/src/components/ChatWidget.jsx
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useRef, useEffect } from 'react'
|
| 2 |
+
|
| 3 |
+
const SUGGESTIONS = [
|
| 4 |
+
"What are the top fintech unicorns?",
|
| 5 |
+
"Tell me about SaaS startups in Bangalore",
|
| 6 |
+
"What is DPIIT recognition?",
|
| 7 |
+
"Which sectors are growing fastest?",
|
| 8 |
+
"Compare edtech vs healthtech",
|
| 9 |
+
]
|
| 10 |
+
|
| 11 |
+
export default function ChatWidget({ onClose }) {
|
| 12 |
+
const [messages, setMessages] = useState([
|
| 13 |
+
{ role: 'assistant', content: '👋 Hi! I\'m Bharat Tech Atlas AI. Ask me about Indian startups, sectors, funding trends, or any company!' }
|
| 14 |
+
])
|
| 15 |
+
const [input, setInput] = useState('')
|
| 16 |
+
const [loading, setLoading] = useState(false)
|
| 17 |
+
const scrollRef = useRef(null)
|
| 18 |
+
|
| 19 |
+
useEffect(() => {
|
| 20 |
+
scrollRef.current?.scrollIntoView({ behavior: 'smooth' })
|
| 21 |
+
}, [messages])
|
| 22 |
+
|
| 23 |
+
const sendMessage = async (text) => {
|
| 24 |
+
if (!text.trim() || loading) return
|
| 25 |
+
const userMsg = { role: 'user', content: text }
|
| 26 |
+
setMessages(prev => [...prev, userMsg])
|
| 27 |
+
setInput('')
|
| 28 |
+
setLoading(true)
|
| 29 |
+
|
| 30 |
+
try {
|
| 31 |
+
const resp = await fetch('/api/chat/completions', {
|
| 32 |
+
method: 'POST',
|
| 33 |
+
headers: { 'Content-Type': 'application/json' },
|
| 34 |
+
body: JSON.stringify({ messages: [...messages, userMsg].map(m => ({ role: m.role, content: m.content })) }),
|
| 35 |
+
})
|
| 36 |
+
const data = await resp.json()
|
| 37 |
+
setMessages(prev => [...prev, { role: 'assistant', content: data.content || 'Sorry, I had trouble responding.' }])
|
| 38 |
+
} catch (err) {
|
| 39 |
+
setMessages(prev => [...prev, { role: 'assistant', content: '⚠️ Network error. Please try again.' }])
|
| 40 |
+
} finally {
|
| 41 |
+
setLoading(false)
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
return (
|
| 46 |
+
<div className="fixed bottom-4 right-4 z-50 w-[380px] max-w-[calc(100vw-2rem)] h-[520px] max-h-[calc(100vh-2rem)] bg-atlas-bg border border-atlas-border rounded-2xl shadow-2xl flex flex-col overflow-hidden">
|
| 47 |
+
{/* Header */}
|
| 48 |
+
<div className="flex items-center justify-between px-4 py-3 border-b border-atlas-border bg-atlas-surface">
|
| 49 |
+
<div className="flex items-center gap-2">
|
| 50 |
+
<span className="text-lg">🤖</span>
|
| 51 |
+
<div>
|
| 52 |
+
<h3 className="text-sm font-semibold text-atlas-text">Bharat Tech Atlas AI</h3>
|
| 53 |
+
<p className="text-[10px] text-atlas-muted">Powered by Qwen2.5-0.5B</p>
|
| 54 |
+
</div>
|
| 55 |
+
</div>
|
| 56 |
+
<button onClick={onClose} className="text-atlas-muted hover:text-atlas-text text-lg">✕</button>
|
| 57 |
+
</div>
|
| 58 |
+
|
| 59 |
+
{/* Messages */}
|
| 60 |
+
<div className="flex-1 overflow-y-auto p-3 space-y-3">
|
| 61 |
+
{messages.map((m, i) => (
|
| 62 |
+
<div key={i} className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}>
|
| 63 |
+
<div className={`max-w-[85%] rounded-xl px-3 py-2 text-xs leading-relaxed ${
|
| 64 |
+
m.role === 'user'
|
| 65 |
+
? 'bg-brand-500/20 text-brand-300 rounded-br-none'
|
| 66 |
+
: 'bg-atlas-surface text-atlas-muted rounded-bl-none'
|
| 67 |
+
}`}>
|
| 68 |
+
{m.content}
|
| 69 |
+
</div>
|
| 70 |
+
</div>
|
| 71 |
+
))}
|
| 72 |
+
{loading && (
|
| 73 |
+
<div className="flex justify-start">
|
| 74 |
+
<div className="bg-atlas-surface rounded-xl rounded-bl-none px-3 py-2">
|
| 75 |
+
<div className="flex gap-1">
|
| 76 |
+
<span className="w-1.5 h-1.5 bg-atlas-muted rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
|
| 77 |
+
<span className="w-1.5 h-1.5 bg-atlas-muted rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
|
| 78 |
+
<span className="w-1.5 h-1.5 bg-atlas-muted rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
|
| 79 |
+
</div>
|
| 80 |
+
</div>
|
| 81 |
+
</div>
|
| 82 |
+
)}
|
| 83 |
+
<div ref={scrollRef} />
|
| 84 |
+
</div>
|
| 85 |
+
|
| 86 |
+
{/* Suggestions (only show when few messages) */}
|
| 87 |
+
{messages.length < 3 && (
|
| 88 |
+
<div className="px-3 pb-2 flex flex-wrap gap-1.5">
|
| 89 |
+
{SUGGESTIONS.map(s => (
|
| 90 |
+
<button key={s} onClick={() => sendMessage(s)}
|
| 91 |
+
className="text-[10px] px-2 py-1 rounded-full bg-atlas-surface border border-atlas-border text-atlas-muted hover:text-atlas-text hover:border-brand-500/30 transition-colors">
|
| 92 |
+
{s}
|
| 93 |
+
</button>
|
| 94 |
+
))}
|
| 95 |
+
</div>
|
| 96 |
+
)}
|
| 97 |
+
|
| 98 |
+
{/* Input */}
|
| 99 |
+
<div className="px-3 py-2 border-t border-atlas-border flex gap-2">
|
| 100 |
+
<input
|
| 101 |
+
value={input}
|
| 102 |
+
onChange={e => setInput(e.target.value)}
|
| 103 |
+
onKeyDown={e => e.key === 'Enter' && sendMessage(input)}
|
| 104 |
+
placeholder="Ask about startups, sectors, funding..."
|
| 105 |
+
className="flex-1 bg-atlas-surface border border-atlas-border rounded-lg px-3 py-2 text-xs text-atlas-text placeholder:text-atlas-muted/50 focus:outline-none focus:border-brand-500/50"
|
| 106 |
+
/>
|
| 107 |
+
<button onClick={() => sendMessage(input)} disabled={loading || !input.trim()}
|
| 108 |
+
className="px-3 py-2 bg-brand-500/20 text-brand-400 rounded-lg text-xs font-medium hover:bg-brand-500/30 disabled:opacity-30 transition-colors">
|
| 109 |
+
Send
|
| 110 |
+
</button>
|
| 111 |
+
</div>
|
| 112 |
+
</div>
|
| 113 |
+
)
|
| 114 |
+
}
|