wrd_drishti / src /pages /Chatbot.tsx
devarshia5's picture
Upload 29 files
c7b2d65 verified
Raw
History Blame Contribute Delete
33.3 kB
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { Input } from '@/src/components/ui/Input';
import { Button } from '@/src/components/ui/Button';
import { Badge } from '@/src/components/ui/Badge';
import {
Send, Bot, User, Sparkles, Loader2, X, MessageCircle,
ScrollText, BookOpen, Scale, FileText, AlertTriangle,
Droplets, MapPin, TrendingUp, Clock,
Shield, Waves, Database, Building2, FileSearch,
Search, ChevronRight, Star, ArrowRight, HelpCircle
} from 'lucide-react';
import { cn } from '@/src/utils/cn';
import { getPopularGRs, mockGRs } from '@/src/data/mockGRs';
type DocumentType = 'GR' | 'Act' | 'Circular' | 'Manual' | 'SOP' | 'Report';
interface Document {
id: string;
title: string;
type: DocumentType;
date: string;
department: string;
relevance: number;
summary?: string;
}
interface Insight {
title: string;
value: string;
trend?: string;
trendUp?: boolean;
icon: React.ElementType;
color: string;
}
type Message = {
id: string;
role: 'user' | 'assistant';
content: string;
sources?: Document[];
insights?: Insight[];
timestamp?: Date;
};
// Extended mock document database
const mockDocuments: Document[] = [
{ id: 'GR-2024-WRD-089', title: 'Flood Relief Compensation Guidelines 2024', type: 'GR', date: '2024-07-15', department: 'Relief & Rehabilitation', relevance: 95, summary: 'Compensation rates: ₹5,000/acre for crop loss, ₹95,000 for house damage' },
{ id: 'GR-2024-WRD-056', title: 'Emergency Dam Safety Protocol', type: 'GR', date: '2024-06-20', department: 'Dam Safety', relevance: 92, summary: 'Mandatory 2-hour advance notification for downstream districts' },
{ id: 'ACT-1976-012', title: 'Maharashtra Irrigation Act', type: 'Act', date: '1976-05-15', department: 'Legislative', relevance: 88, summary: 'Governs water distribution, irrigation rights, and canal maintenance' },
{ id: 'CIRC-2024-WRD-023', title: 'Monsoon Preparedness Circular', type: 'Circular', date: '2024-05-01', department: 'Operations', relevance: 85, summary: 'Pre-monsoon checklist for all dam control rooms' },
{ id: 'GR-2023-WRD-124', title: 'Dam Gate Operations SOP', type: 'SOP', date: '2023-08-10', department: 'Operations', relevance: 90, summary: 'Standard procedures for gate opening/closing with safety checks' },
{ id: 'MANUAL-2024-001', title: 'Flood Management Field Manual', type: 'Manual', date: '2024-03-15', department: 'Training', relevance: 82, summary: 'Field guide for emergency response teams' },
{ id: 'GR-2024-WRD-042', title: 'Water Resource Allocation Policy', type: 'GR', date: '2024-04-20', department: 'Planning', relevance: 78, summary: 'Priority: Drinking > Agriculture > Industrial use' },
{ id: 'ACT-2005-WRD-003', title: 'MWRRA Act', type: 'Act', date: '2005-08-12', department: 'Legislative', relevance: 75, summary: 'Regulatory authority for water pricing and distribution' },
{ id: 'CIRC-2024-WRD-015', title: 'Real-time Monitoring Guidelines', type: 'Circular', date: '2024-04-10', department: 'IT & Operations', relevance: 80, summary: 'IoT sensor deployment for live dam monitoring' },
{ id: 'RPT-2024-001', title: 'Koyna Dam Safety Audit', type: 'Report', date: '2024-06-01', department: 'Dam Safety', relevance: 94, summary: 'Structural integrity: GOOD | Seismic risk: MODERATE' },
];
// GR Helper Quick Actions
const grQuickActions = [
{ text: 'Find latest flood relief GR', icon: Search, color: 'blue' },
{ text: 'Explain GR-2024-WRD-089', icon: FileText, color: 'indigo' },
{ text: 'Compare compensation GRs', icon: Scale, color: 'purple' },
{ text: 'Dam safety protocols', icon: Shield, color: 'red' },
];
// Query suggestions with categories - Focused on rainfall regions and GR rules
const queryCategories = [
{
title: 'Rainfall & Weather',
icon: Droplets,
color: 'blue',
queries: [
{ text: 'Konkan region rainfall data', icon: Droplets },
{ text: 'Vidarbha drought relief GR', icon: FileText },
]
},
{
title: 'GR Rules & Acts',
icon: Scale,
color: 'purple',
queries: [
{ text: 'Water distribution priority rules', icon: Shield },
{ text: 'Canal maintenance responsibility', icon: ScrollText },
]
},
{
title: 'Relief & Compensation',
icon: Waves,
color: 'red',
queries: [
{ text: 'Flood affected area compensation', icon: FileText },
{ text: 'Crop damage claim procedure', icon: BookOpen },
]
},
{
title: 'Administrative',
icon: Building2,
color: 'emerald',
queries: [
{ text: 'Tahsildar water rights powers', icon: Scale },
{ text: 'WUA formation guidelines', icon: MapPin },
]
},
];
// Generate insights based on query
const generateInsights = (query: string): Insight[] => {
const q = query.toLowerCase();
if (q.includes('koyna')) {
return [
{ title: 'Current Level', value: '92.4%', trend: '+2.1%', trendUp: true, icon: Droplets, color: 'text-blue-600' },
{ title: 'Storage', value: '2.8 TMC', trend: 'of 3.0 TMC', icon: Database, color: 'text-cyan-600' },
{ title: 'Inflow', value: '45,000', trend: 'cusecs', icon: Waves, color: 'text-indigo-600' },
{ title: 'Outflow', value: '32,000', trend: 'cusecs', icon: Waves, color: 'text-orange-600' },
];
}
if (q.includes('flood') && q.includes('compensation')) {
return [
{ title: 'Crop Loss', value: '₹5,000', trend: 'per acre', icon: FileText, color: 'text-emerald-600' },
{ title: 'House Damage', value: '₹95,000', trend: 'full damage', icon: Building2, color: 'text-blue-600' },
{ title: 'Processing Time', value: '30', trend: 'days', icon: Clock, color: 'text-orange-600' },
{ title: 'Claims 2024', value: '12,450', trend: '+23%', trendUp: true, icon: TrendingUp, color: 'text-purple-600' },
];
}
if (q.includes('dam') && q.includes('gate')) {
return [
{ title: 'Active Releases', value: '12', trend: '+3 today', trendUp: true, icon: Waves, color: 'text-blue-600' },
{ title: 'Critical Dams', value: '3', trend: 'High alert', icon: AlertTriangle, color: 'text-red-600' },
{ title: 'Total Outflow', value: '1.2M', trend: 'cusecs', icon: Droplets, color: 'text-cyan-600' },
{ title: 'Downstream Alert', value: '18', trend: 'districts', icon: Shield, color: 'text-orange-600' },
];
}
return [
{ title: 'Total Dams', value: '287', trend: 'monitored', icon: Database, color: 'text-blue-600' },
{ title: 'GRs 2024', value: '142', trend: 'indexed', icon: FileText, color: 'text-emerald-600' },
{ title: 'Active Alerts', value: '7', trend: 'critical', icon: AlertTriangle, color: 'text-red-600' },
{ title: 'Avg Storage', value: '68%', trend: '+5%', trendUp: true, icon: Droplets, color: 'text-cyan-600' },
];
};
// Generate response based on query
const generateResponse = (query: string): { content: string; docs: Document[] } => {
const q = query.toLowerCase();
if (q.includes('koyna')) {
return {
content: `**Koyna Dam - Current Status (as of ${new Date().toLocaleTimeString('en-IN')})**
• **Storage Level:** 92.4% (2.8 TMC of 3.0 TMC capacity)
• **Inflow:** 45,000 cusecs | **Outflow:** 32,000 cusecs
• **Status:** ⚠️ CRITICAL - Emergency discharge initiated
• **Districts Alerted:** Satara, Sangli, Kolhapur collectors notified
**Recent Actions:**
- 4 radial gates opened at 50% capacity
- Downstream evacuation advisory issued
- Hourly monitoring activated
Refer to **GR-2024-WRD-056** for emergency protocols.`,
docs: mockDocuments.filter(d => d.id.includes('056') || d.id.includes('RPT-2024-001'))
};
}
if (q.includes('flood') && q.includes('compensation')) {
return {
content: `**Flood Relief Compensation - 2024 Guidelines (GR-2024-WRD-089)**
**Eligibility Criteria:**
• Residence in notified flood-affected area
• Damage between July 1 - October 31, 2024
• Registration with local Tahsildar office
**Compensation Rates:**
• Crop Loss: ₹5,000 per acre (max 10 acres)
• House Damage: ₹95,000 (full) / ₹50,000 (partial)
• Livestock: ₹50,000 per animal (max 5)
**Application Process:**
1. Submit Form WRD-FLOOD-01 to Tahsildar
2. Attach damage assessment report
3. Processing time: 30 days
4. Direct bank transfer
Contact: District Relief Office for assistance.`,
docs: mockDocuments.filter(d => d.id.includes('089') || d.id.includes('001'))
};
}
if (q.includes('gate') || q.includes('sop')) {
return {
content: `**Dam Gate Operations SOP (GR-2023-WRD-124)**
**Pre-Opening Checklist:**
✓ Seismic sensors calibrated
✓ Downstream flow sensors active
✓ Alert systems tested
✓ Control room staffed (min 2 engineers)
**Opening Procedure:**
1. **Notification** (T-2 hours): Alert downstream districts
2. **Gradual Opening**: 10% increments every 15 minutes
3. **Flow Monitoring**: Continuous discharge measurement
4. **Public Announcement**: Local media + sirens
**Emergency Override:**
Chief Engineer can authorize immediate release if dam level exceeds 95%.
**Documentation:** All gate operations logged in DAMS system within 1 hour.`,
docs: mockDocuments.filter(d => d.id.includes('124') || d.id.includes('056'))
};
}
if (q.includes('irrigation') || q.includes('act')) {
return {
content: `**Maharashtra Irrigation Act, 1976 - Key Provisions**
**Section 33 - Water Rights:**
• Priority order: Drinking water > Agriculture > Industry
• Canal water distribution by WRD, not local bodies
• Penalty for unauthorized diversion: Up to 3 years imprisonment
**Section 45 - Maintenance:**
• WRD responsible for major repairs (>₹1 lakh)
• Water User Associations (WUAs) for minor maintenance
• 5% water cess collected for maintenance fund
**Recent Amendment (2024):**
• IoT-based usage monitoring mandated
• Real-time allocation adjustments during scarcity
• Digital grievance portal: wrd.maharashtra.gov.in/grievance`,
docs: mockDocuments.filter(d => d.type === 'Act')
};
}
const relevantDocs = mockDocuments
.filter(d => q.includes(d.type.toLowerCase()) ||
d.title.toLowerCase().includes(q.split(' ')[0]))
.slice(0, 3);
return {
content: `I found **${relevantDocs.length} relevant document(s)** for your query on "${query}".
${relevantDocs.map(d => `**${d.id}** - ${d.title}\n• ${d.summary || 'No summary available'}`).join('\n\n')}
Would you like me to provide detailed information about any of these documents, or search for something more specific?`,
docs: relevantDocs
};
};
export function Chatbot() {
const location = useLocation();
const navigate = useNavigate();
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [isMobileOpen, setIsMobileOpen] = useState(false);
const [showWelcome, setShowWelcome] = useState(true);
const [showSidebar, setShowSidebar] = useState(true);
const messagesEndRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const popularGRs = getPopularGRs();
useEffect(() => {
const searchParams = new URLSearchParams(location.search);
const query = searchParams.get('q');
if (query) {
handleSend(query);
setShowWelcome(false);
}
}, [location]);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
// Generate unique ID to avoid React key duplicates
const generateId = useCallback(() => {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}, []);
const handleSend = async (text: string) => {
if (!text.trim()) return;
setShowWelcome(false);
const userMsgId = generateId();
const userMsg: Message = {
id: userMsgId,
role: 'user',
content: text,
timestamp: new Date()
};
setMessages(prev => [...prev, userMsg]);
setInput('');
setIsLoading(true);
await new Promise(r => setTimeout(r, 800));
const { content, docs } = generateResponse(text);
const insights = generateInsights(text);
const aiMsg: Message = {
id: generateId(),
role: 'assistant',
content,
sources: docs,
insights,
timestamp: new Date()
};
setMessages(prev => [...prev, aiMsg]);
setIsLoading(false);
};
const getTypeIcon = (type: DocumentType) => {
switch (type) {
case 'GR': return ScrollText;
case 'Act': return Scale;
case 'Circular': return FileText;
case 'Manual': return BookOpen;
case 'SOP': return Shield;
case 'Report': return FileSearch;
}
};
const formatTime = (date?: Date) => {
if (!date) return '';
return date.toLocaleTimeString('en-IN', { hour: '2-digit', minute: '2-digit' });
};
const WelcomeScreen = () => (
<div className="flex flex-col justify-center gap-4 h-full w-full max-w-2xl mx-auto py-2">
{/* Top Section - Title */}
<div className="text-center">
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-xl flex items-center justify-center mx-auto mb-2 shadow-md">
<Sparkles className="h-6 w-6 text-white" />
</div>
<h2 className="text-lg font-semibold text-slate-800">GR Helper</h2>
<p className="text-xs text-slate-500 mt-1">AI Assistant for Government Resolutions & Acts</p>
</div>
{/* GR Quick Actions */}
<div className="px-2 mt-2">
<p className="text-xs font-medium text-slate-500 mb-2 px-1">Quick Actions</p>
<div className="grid grid-cols-2 gap-2">
{grQuickActions.map((action, idx) => {
const Icon = action.icon;
return (
<button
key={idx}
onClick={() => handleSend(action.text)}
className="text-left p-3 bg-white rounded-lg border border-slate-200 hover:border-indigo-300 hover:shadow-sm active:bg-slate-50 transition-all"
>
<div className="flex items-center gap-2">
<div className={cn("p-1 rounded", `bg-${action.color}-100`)}>
<Icon className={cn("h-3.5 w-3.5", `text-${action.color}-600`)} />
</div>
<span className="text-xs font-medium text-slate-700 line-clamp-1">{action.text}</span>
</div>
</button>
);
})}
</div>
</div>
{/* Middle Section - Category Cards */}
<div className="px-2 mt-2">
<p className="text-xs font-medium text-slate-500 mb-2 px-1">Browse by Category</p>
<div className="grid grid-cols-2 gap-2">
{queryCategories.map((cat, idx) => {
const Icon = cat.icon;
return (
<button
key={idx}
onClick={() => handleSend(cat.queries[0].text)}
className="text-left p-3 bg-white rounded-lg border border-slate-200 hover:border-blue-300 active:bg-slate-50 transition-all shadow-sm"
>
<div className="flex items-center gap-2 mb-1">
<div className={cn("p-1 rounded", `bg-${cat.color}-100`)}>
<Icon className={cn("h-3.5 w-3.5", `text-${cat.color}-600`)} />
</div>
<span className="text-xs font-semibold text-slate-700">{cat.title}</span>
</div>
<p className="text-[10px] text-slate-500 truncate">{cat.queries[0].text}</p>
</button>
);
})}
</div>
</div>
{/* Bottom Section - Quick Links */}
<div className="flex flex-wrap justify-center gap-1.5 px-2 mt-2">
{['Konkan rainfall', 'Vidarbha GR', 'Water rights', 'Compensation'].map((s, i) => (
<button
key={i}
onClick={() => handleSend(s)}
className="text-[10px] px-2.5 py-1 bg-slate-100 text-slate-600 rounded-full hover:bg-slate-200 transition-colors"
>
{s}
</button>
))}
</div>
</div>
);
const RecentGRsSidebar = () => (
<div className={cn(
"w-64 border-l border-slate-200 bg-white flex-col shrink-0 hidden lg:flex",
showSidebar ? "lg:flex" : "lg:hidden"
)}>
<div className="p-3 border-b border-slate-100">
<div className="flex items-center justify-between">
<h3 className="text-xs font-semibold text-slate-700">Popular GRs</h3>
<Star className="h-3 w-3 text-yellow-500" />
</div>
</div>
<div className="flex-1 overflow-y-auto p-2 space-y-1.5">
{popularGRs.map(gr => (
<button
key={gr.id}
onClick={() => handleSend(`Explain ${gr.id}: ${gr.title}`)}
className="w-full text-left p-2 rounded-md hover:bg-slate-50 transition-colors group"
>
<div className="flex items-start justify-between gap-1">
<p className="text-[10px] font-mono text-indigo-600">{gr.id}</p>
<ChevronRight className="h-3 w-3 text-slate-300 group-hover:text-indigo-500 shrink-0 mt-0.5" />
</div>
<p className="text-xs text-slate-700 line-clamp-2 mt-0.5">{gr.title}</p>
<div className="flex items-center gap-1 mt-1">
<Badge className="text-[8px] px-1 py-0 h-auto bg-slate-100 text-slate-600">
{gr.category}
</Badge>
</div>
</button>
))}
</div>
<div className="p-2 border-t border-slate-100">
<Button
variant="ghost"
size="sm"
className="w-full text-xs h-8"
onClick={() => navigate('/gr-finder')}
>
<Search className="h-3 w-3 mr-1" />
Browse All GRs
</Button>
</div>
</div>
);
const MessageBubble = ({ msg }: { msg: Message }) => (
<div className={cn(
"flex w-full mb-4",
msg.role === 'user' ? "justify-end" : "justify-start"
)}>
<div className={cn(
"flex gap-3 max-w-[90%] md:max-w-[80%]",
msg.role === 'user' ? "flex-row-reverse" : "flex-row"
)}>
<div className={cn(
"flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center",
msg.role === 'user'
? "bg-blue-600 text-white"
: "bg-gradient-to-br from-blue-500 to-indigo-600 text-white"
)}>
{msg.role === 'user' ? <User className="h-4 w-4" /> : <Bot className="h-4 w-4" />}
</div>
<div className="flex flex-col gap-2">
<div className={cn(
"px-4 py-3 rounded-2xl text-sm leading-relaxed",
msg.role === 'user'
? "bg-blue-600 text-white rounded-tr-sm"
: "bg-white border border-slate-200 text-slate-700 rounded-tl-sm shadow-sm"
)}>
<div className="whitespace-pre-wrap">
{msg.content.split('**').map((part, i) =>
i % 2 === 0 ? part : <strong key={i} className="font-semibold">{part}</strong>
)}
</div>
</div>
{msg.insights && msg.insights.length > 0 && (
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 mt-1">
{msg.insights.map((insight, idx) => {
const Icon = insight.icon;
return (
<div key={idx} className="bg-white border border-slate-200 rounded-lg p-2.5 shadow-sm">
<div className="flex items-center gap-1.5 mb-1">
<Icon className={cn("h-3.5 w-3.5", insight.color)} />
<span className="text-[10px] text-slate-500 uppercase tracking-wider">{insight.title}</span>
</div>
<div className="flex items-baseline gap-1">
<span className="text-lg font-bold text-slate-800">{insight.value}</span>
{insight.trend && (
<span className={cn(
"text-[10px]",
insight.trendUp ? "text-emerald-600" : "text-slate-400"
)}>
{insight.trend}
</span>
)}
</div>
</div>
);
})}
</div>
)}
{msg.sources && msg.sources.length > 0 && (
<div className="flex flex-wrap gap-1.5 mt-1">
{msg.sources.map((doc, idx) => {
const TypeIcon = getTypeIcon(doc.type);
return (
<div
key={idx}
className="flex items-center gap-1.5 text-[11px] px-2.5 py-1.5 bg-slate-100 text-slate-600 rounded-md border border-slate-200"
>
<TypeIcon className="h-3 w-3" />
<span className="font-medium">{doc.id}</span>
<span className="text-slate-400">|</span>
<span className="truncate max-w-[120px]">{doc.title}</span>
</div>
);
})}
</div>
)}
<span className="text-[10px] text-slate-400 ml-1">
{formatTime(msg.timestamp)}
</span>
</div>
</div>
</div>
);
return (
<>
{/* Desktop */}
<div className="hidden md:flex h-full w-full flex-row bg-slate-50 overflow-hidden rounded-lg min-h-0">
{/* Main Chat Area */}
<div className="flex-1 flex flex-col min-w-0">
<div className="bg-white border-b border-slate-200 px-4 py-3 shrink-0">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-lg flex items-center justify-center shadow-sm">
<Sparkles className="h-4 w-4 text-white" />
</div>
<div>
<h1 className="text-base font-semibold text-slate-900">GR Helper</h1>
<p className="text-[11px] text-slate-500">AI Assistant for Government Resolutions</p>
</div>
</div>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="sm"
className="text-xs h-8"
onClick={() => navigate('/faq')}
>
<HelpCircle className="h-3.5 w-3.5 mr-1" />
FAQ
</Button>
<Button
variant="ghost"
size="sm"
className="text-xs h-8"
onClick={() => navigate('/gr-finder')}
>
<Search className="h-3.5 w-3.5 mr-1" />
GR Finder
</Button>
</div>
</div>
</div>
<div className="flex-1 overflow-hidden px-4 py-4 min-h-0">
{showWelcome && messages.length === 0 ? (
<WelcomeScreen />
) : (
<div className="max-w-3xl mx-auto h-full overflow-y-auto pr-2 min-h-0">
{messages.map((msg, i) => (
<React.Fragment key={msg.id || String(i)}>
<MessageBubble msg={msg} />
</React.Fragment>
))}
{isLoading && (
<div className="flex items-center gap-3 text-slate-500">
<div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-full flex items-center justify-center">
<Bot className="h-4 w-4 text-white" />
</div>
<div className="flex items-center gap-2 bg-white border border-slate-200 px-4 py-2.5 rounded-2xl rounded-tl-sm shadow-sm">
<Loader2 className="h-4 w-4 animate-spin text-blue-500" />
<span className="text-sm">Searching GR database...</span>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
)}
</div>
<div className="bg-white border-t border-slate-200 px-4 py-3 shrink-0">
<div className="max-w-3xl mx-auto">
<form onSubmit={(e) => { e.preventDefault(); handleSend(input); }} className="flex gap-3">
<div className="flex-1 relative">
<Input
ref={inputRef}
type="text"
placeholder="Ask about GRs, Acts, compensation, flood relief..."
value={input}
onChange={(e) => setInput(e.target.value)}
className="h-11 pl-4 pr-10 bg-slate-50 border-slate-200 text-sm focus:bg-white focus:border-blue-500 transition-all rounded-xl"
disabled={isLoading}
/>
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400">
<FileSearch className="h-4 w-4" />
</div>
</div>
<Button
type="submit"
disabled={!input.trim() || isLoading}
className="h-11 px-5 bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 text-white text-sm font-medium rounded-xl shadow-sm"
>
<Send className="h-4 w-4 mr-1.5" />
Send
</Button>
</form>
</div>
</div>
</div>
{/* Sidebar with Popular GRs */}
<RecentGRsSidebar />
</div>
{/* Mobile */}
<>
{!isMobileOpen && (
<button
onClick={() => setIsMobileOpen(true)}
className="fixed bottom-20 right-4 z-50 w-14 h-14 bg-gradient-to-br from-blue-600 to-indigo-600 text-white rounded-full shadow-lg flex items-center justify-center md:hidden"
>
<MessageCircle className="h-6 w-6" />
</button>
)}
{isMobileOpen && (
<div className="fixed inset-0 z-[60] bg-slate-50 flex flex-col md:hidden">
<div className="bg-white border-b border-slate-200 px-4 py-3 flex items-center justify-between">
<div className="flex items-center gap-2.5">
<div className="w-9 h-9 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-xl flex items-center justify-center">
<Sparkles className="h-4 w-4 text-white" />
</div>
<div>
<h1 className="font-semibold text-slate-900 text-sm">GR Helper</h1>
<p className="text-[10px] text-slate-500">AI Assistant for Government GRs</p>
</div>
</div>
<button
onClick={() => setIsMobileOpen(false)}
className="w-8 h-8 flex items-center justify-center rounded-full hover:bg-slate-100"
>
<X className="h-5 w-5 text-slate-500" />
</button>
</div>
<div className="flex-1 overflow-hidden px-3 py-1 min-h-0">
{showWelcome && messages.length === 0 ? (
<div className="flex flex-col justify-center gap-4 h-full w-full max-w-sm mx-auto">
{/* Top */}
<div className="text-center">
<div className="w-10 h-10 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-xl flex items-center justify-center mx-auto mb-2 shadow-md">
<Sparkles className="h-5 w-5 text-white" />
</div>
<h2 className="text-base font-semibold text-slate-800">GR Helper</h2>
<p className="text-[11px] text-slate-500 mt-1">AI Assistant for Government GRs</p>
</div>
{/* Quick Actions */}
<div className="px-2">
<p className="text-[10px] font-medium text-slate-500 mb-1.5 px-1">Quick Actions</p>
<div className="grid grid-cols-2 gap-1.5">
{grQuickActions.slice(0, 4).map((action, idx) => (
<button
key={idx}
onClick={() => handleSend(action.text)}
className="text-left p-2 bg-white rounded-lg border border-slate-200 active:bg-slate-50"
>
<div className="flex items-center gap-1.5">
<action.icon className={cn("h-3 w-3", `text-${action.color}-600`)} />
<span className="text-[10px] font-medium text-slate-700 line-clamp-1">{action.text}</span>
</div>
</button>
))}
</div>
</div>
{/* Middle - Cards */}
<div className="px-2">
<p className="text-[10px] font-medium text-slate-500 mb-1.5 px-1">Browse by Category</p>
<div className="grid grid-cols-2 gap-1.5">
{queryCategories.map((cat, idx) => (
<button
key={idx}
onClick={() => handleSend(cat.queries[0].text)}
className="text-left p-2 bg-white rounded-lg border border-slate-200 active:bg-slate-50"
>
<div className="flex items-center gap-1.5 mb-0.5">
<div className={cn("p-0.5 rounded", `bg-${cat.color}-100`)}>
<cat.icon className={cn("h-3 w-3", `text-${cat.color}-600`)} />
</div>
<span className="text-[10px] font-medium text-slate-700">{cat.title}</span>
</div>
<p className="text-[9px] text-slate-500 truncate">{cat.queries[0].text}</p>
</button>
))}
</div>
</div>
{/* Bottom - Quick Links */}
<div className="flex flex-wrap justify-center gap-1.5 px-2">
{['Konkan rainfall', 'Vidarbha GR', 'Water rights', 'Compensation'].map((s, i) => (
<button
key={i}
onClick={() => handleSend(s)}
className="text-[10px] px-2.5 py-1 bg-slate-100 text-slate-600 rounded-full"
>
{s}
</button>
))}
</div>
</div>
) : (
<div className="w-full h-full overflow-y-auto space-y-3 min-h-0">
{messages.map((msg, i) => (
<React.Fragment key={msg.id || String(i)}>
<MessageBubble msg={msg} />
</React.Fragment>
))}
{isLoading && (
<div className="flex items-center gap-2 text-slate-500">
<div className="w-7 h-7 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-full flex items-center justify-center">
<Bot className="h-3.5 w-3.5 text-white" />
</div>
<div className="flex items-center gap-2 bg-white border border-slate-200 px-3 py-2 rounded-xl rounded-tl-sm">
<Loader2 className="h-3 w-3 animate-spin text-blue-500" />
<span className="text-xs">Searching...</span>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
)}
</div>
<div className="bg-white border-t border-slate-200 px-4 py-2 pb-4 shrink-0 safe-area-bottom">
<form onSubmit={(e) => { e.preventDefault(); handleSend(input); }} className="flex gap-2">
<Input
type="text"
placeholder="Ask about GRs, Acts, compensation..."
value={input}
onChange={(e) => setInput(e.target.value)}
className="flex-1 h-10 bg-slate-50 text-sm rounded-xl"
disabled={isLoading}
/>
<Button
type="submit"
size="icon"
disabled={!input.trim() || isLoading}
className="h-10 w-10 bg-gradient-to-r from-blue-600 to-indigo-600 rounded-xl"
>
<Send className="h-4 w-4" />
</Button>
</form>
</div>
</div>
)}
</>
</>
);
}