Files
auditly/pages/Chat.tsx

138 lines
7.1 KiB
TypeScript

import React, { useState, useMemo } from 'react';
import { Card, Button } from '../components/UiKit';
import { useOrg } from '../contexts/OrgContext';
import { CHAT_STARTERS } from '../constants';
const Chat: React.FC = () => {
const { employees, reports, generateEmployeeReport } = useOrg();
const [messages, setMessages] = useState<Array<{ id: string, role: 'user' | 'assistant', text: string }>>([]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [selectedEmployeeId, setSelectedEmployeeId] = useState<string>('');
const selectedReport = selectedEmployeeId ? reports[selectedEmployeeId] : undefined;
const dynamicStarters = useMemo(() => {
if (!selectedReport) return CHAT_STARTERS.slice(0, 4);
const strengths = selectedReport.insights.strengths?.slice(0, 2) || [];
const weaknesses = selectedReport.insights.weaknesses?.slice(0, 1) || [];
const risk = selectedReport.retentionRisk;
const starters: string[] = [];
if (strengths[0]) starters.push(`How can we further leverage ${strengths[0]} for cross-team impact?`);
if (weaknesses[0]) starters.push(`What is an actionable plan to address ${weaknesses[0]} this quarter?`);
if (risk) starters.push(`What factors contribute to ${selectedReport.employeeId}'s ${risk} retention risk?`);
starters.push(`Is ${selectedReport.employeeId} a candidate for expanded scope or leadership?`);
while (starters.length < 4) starters.push(CHAT_STARTERS[starters.length] || 'Provide an organizational insight.');
return starters.slice(0, 4);
}, [selectedReport]);
const handleSend = async (message?: string) => {
const textToSend = message || input;
if (!textToSend.trim()) return;
const userMessage = { id: Date.now().toString(), role: 'user' as const, text: textToSend };
setMessages(prev => [...prev, userMessage]);
setInput('');
setIsLoading(true);
// Simulate AI response (placeholder for server /api/chat usage)
setTimeout(() => {
const aiResponse = {
id: (Date.now() + 1).toString(),
role: 'assistant' as const,
text: `Based on ${selectedEmployeeId ? 'the selected employee\'s' : 'organizational'} data, here's an insight related to: "${textToSend}".`
};
setMessages(prev => [...prev, aiResponse]);
setIsLoading(false);
}, 1500);
};
return (
<div className="p-6 max-w-4xl mx-auto h-full flex flex-col">
<div className="mb-6">
<h1 className="text-3xl font-bold text-[--text-primary]">Chat with AI</h1>
<p className="text-[--text-secondary] mt-1">Ask questions about your employees and organization</p>
</div>
<div className="mb-4 flex flex-col md:flex-row md:items-center gap-3">
<div className="flex items-center gap-2">
<label className="text-sm text-[--text-secondary]">Focus Employee:</label>
<select
className="px-2 py-1 text-sm bg-[--background-secondary] border border-[--border-color] rounded"
value={selectedEmployeeId}
onChange={e => setSelectedEmployeeId(e.target.value)}
>
<option value="">(Organization)</option>
{employees.map(emp => <option key={emp.id} value={emp.id}>{emp.name}</option>)}
</select>
{selectedEmployeeId && !selectedReport && (
<Button size="sm" variant="secondary" onClick={() => generateEmployeeReport(employees.find(e => e.id === selectedEmployeeId)!)}>
Generate Report
</Button>
)}
</div>
</div>
{messages.length === 0 && (
<Card className="mb-6">
<h3 className="text-lg font-semibold text-[--text-primary] mb-4">Get started with these questions:</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{dynamicStarters.map((starter, idx) => (
<Button
key={idx}
variant="secondary"
size="sm"
className="text-left justify-start"
onClick={() => handleSend(starter)}
>
{starter}
</Button>
))}
</div>
</Card>
)}
<div className="flex-1 overflow-y-auto mb-4 space-y-4">
{messages.map(message => (
<div key={message.id} className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-[70%] p-4 rounded-lg ${message.role === 'user'
? 'bg-blue-500 text-white'
: 'bg-[--background-secondary] text-[--text-primary]'
}`}>
{message.text}
</div>
</div>
))}
{isLoading && (
<div className="flex justify-start">
<div className="bg-[--background-secondary] text-[--text-primary] p-4 rounded-lg">
<div className="flex space-x-1">
<div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce"></div>
<div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }}></div>
<div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
</div>
</div>
</div>
)}
</div>
<Card padding="sm">
<div className="flex space-x-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSend()}
placeholder="Ask about employees, reports, or company insights..."
className="flex-1 px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<Button onClick={() => handleSend()} disabled={isLoading || !input.trim()}>
Send
</Button>
</div>
</Card>
</div>
);
};
export default Chat;