diff --git a/frontend/src/documents/CandidateChatSystem.tsx b/frontend/src/documents/CandidateChatSystem.tsx new file mode 100644 index 0000000..a5f3e8b --- /dev/null +++ b/frontend/src/documents/CandidateChatSystem.tsx @@ -0,0 +1,486 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { + Box, + Paper, + Typography, + TextField, + Button, + List, + ListItem, + ListItemText, + Chip, + IconButton, + CircularProgress, + Divider, + Card, + CardContent, + Avatar, + Grid +} from '@mui/material'; + +// Mock API client (replace with your actual implementation) +const mockApiClient = { + async createCandidateChatSession(username: string, chatType: any, aiParameters: any, title: any) { + return { + id: `session-${Date.now()}`, + title: title || `Chat about ${username}`, + userId: 'current-user-id', + createdAt: new Date().toISOString(), + lastActivity: new Date().toISOString(), + context: { + type: chatType, + relatedEntityId: `candidate-${username}`, + relatedEntityType: 'candidate', + aiParameters, + additionalContext: { + candidateInfo: { + id: `candidate-${username}`, + name: `${username} Candidate`, + email: `${username}@example.com`, + username: username, + skills: ['JavaScript', 'React', 'Python'], + experience: 3, + location: 'San Francisco' + } + } + } + }; + }, + + async getCandidateChatSessions(username: string) { + return { + candidate: { + id: `candidate-${username}`, + username: username, + fullName: `${username} Candidate`, + email: `${username}@example.com` + }, + sessions: { + data: [ + { + id: 'session-1', + title: `Previous chat about ${username}`, + lastActivity: new Date(Date.now() - 86400000).toISOString(), + context: { type: 'candidate_screening' } + } + ], + total: 1, + page: 1, + limit: 20, + totalPages: 1, + hasMore: false + } + }; + }, + + async getChatMessages(sessionId: any) { + return { + data: [ + { + id: 'msg-1', + sessionId, + sender: 'user', + content: 'Tell me about this candidate', + timestamp: new Date(Date.now() - 60000).toISOString(), + status: 'done' + }, + { + id: 'msg-2', + sessionId, + sender: 'ai', + content: 'This candidate has strong technical skills in JavaScript and React...', + timestamp: new Date(Date.now() - 30000).toISOString(), + status: 'done' + } + ], + total: 2, + page: 1, + limit: 50, + totalPages: 1, + hasMore: false + }; + }, + + async sendMessageStream(sessionId: any, query: any, onMessage: any, onError: any, onComplete: any) { + // Simulate user message + const userMessage = { + id: `msg-user-${Date.now()}`, + sessionId, + sender: 'user', + content: typeof query === 'string' ? query : query.prompt, + timestamp: new Date().toISOString(), + status: 'done' + }; + onMessage(userMessage); + + // Simulate AI response with streaming + setTimeout(() => { + const aiMessage = { + id: `msg-ai-${Date.now()}`, + sessionId, + sender: 'ai', + content: 'This is a simulated AI response to your question...', + timestamp: new Date().toISOString(), + status: 'done' + }; + onMessage(aiMessage); + onComplete(); + }, 1000); + } +}; + +const CandidateChatSystem = () => { + const [username, setUsername] = useState('johndoe'); + const [sessions, setSessions] = useState(null); + const [currentSessionId, setCurrentSessionId] = useState(null); + const [messages, setMessages] = useState([]); + const [newMessage, setNewMessage] = useState(''); + const [loading, setLoading] = useState(false); + const [streaming, setStreaming] = useState(false); + const messagesEndRef = useRef(null); + + const aiParameters = { + name: 'Chat Assistant', + model: 'gpt-4', + temperature: 0.7, + maxTokens: 2000, + topP: 0.95, + frequencyPenalty: 0.0, + presencePenalty: 0.0, + isDefault: false, + createdAt: new Date(), + updatedAt: new Date() + }; + + // Load sessions for the candidate + const loadSessions = async () => { + if (!username) return; + + try { + setLoading(true); + const result = await mockApiClient.getCandidateChatSessions(username); + setSessions(result as any); + } catch (error) { + console.error('Failed to load sessions:', error); + } finally { + setLoading(false); + } + }; + + // Load messages for current session + const loadMessages = async () => { + if (!currentSessionId) return; + + try { + const result = await mockApiClient.getChatMessages(currentSessionId); + setMessages(result.data as any); + } catch (error) { + console.error('Failed to load messages:', error); + } + }; + + // Create new session + const createNewSession = async () => { + try { + setLoading(true); + const newSession = await mockApiClient.createCandidateChatSession( + username, + 'candidate_screening', + aiParameters, + `Interview Discussion - ${username}` + ); + + setCurrentSessionId(newSession.id as any); + setMessages([]); + await loadSessions(); // Refresh sessions list + } catch (error) { + console.error('Failed to create session:', error); + } finally { + setLoading(false); + } + }; + + // Send message + const sendMessage = async () => { + if (!newMessage.trim() || !currentSessionId || streaming) return; + + const messageContent = newMessage; + setNewMessage(''); + setStreaming(true); + + try { + await mockApiClient.sendMessageStream( + currentSessionId, + { prompt: messageContent }, + (message : any) => { + setMessages(prev => { + const filtered = prev.filter((m : any)=> m.id !== message.id); + return [...filtered, message].sort((a, b) => + new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime() + ) as any; + }); + }, + (error: any) => { + console.error('Streaming error:', error); + setStreaming(false); + }, + () => { + setStreaming(false); + } + ); + } catch (error) { + console.error('Failed to send message:', error); + setStreaming(false); + } + }; + + // Auto-scroll to bottom when new messages arrive + useEffect(() => { + (messagesEndRef.current as any)?.scrollIntoView({ behavior: 'smooth' }); + }, [messages]); + + // Load sessions when username changes + useEffect(() => { + loadSessions(); + }, [username]); + + // Load messages when session changes + useEffect(() => { + if (currentSessionId) { + loadMessages(); + } + }, [currentSessionId]); + + return ( + + + Candidate Chat System + + + {/* Username Input */} + + + + setUsername(e.target.value)} + onKeyPress={(e) => { + if (e.key === 'Enter') { + loadSessions(); + } + }} + /> + + + + + + + + + {/* Sessions Sidebar */} + + + + Chat Sessions + {sessions && ( + + )} + + + + + + {sessions ? ( + + {sessions.sessions.data.map((session : any) => ( + setCurrentSessionId(session.id)} + sx={{ + mb: 1, + borderRadius: 1, + border: '1px solid', + borderColor: currentSessionId === session.id ? 'primary.main' : 'divider', + cursor: 'pointer', + '&:hover': { + backgroundColor: 'action.hover' + } + }} + > + + + ))} + + ) : ( + + Enter a username and click "Load Sessions" + + )} + + + {sessions && ( + + + Candidate Info + + + Name: {sessions.candidate.fullName} + + + Email: {sessions.candidate.email} + + + )} + + + + {/* Chat Interface */} + + + {currentSessionId ? ( + <> + {/* Messages Area */} + + {messages.map((message: any) => ( + + {message.sender === 'ai' && ( + + 🤖 + + )} + + + + + {message.content} + + + {new Date(message.timestamp).toLocaleTimeString()} + + + + + {message.sender === 'user' && ( + + 👤 + + )} + + ))} + + {streaming && ( + + + 🤖 + + + + + AI is typing... + + + + )} + +
+ + + + + {/* Message Input */} + + setNewMessage(e.target.value)} + onKeyPress={(e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + sendMessage(); + } + }} + disabled={streaming} + multiline + maxRows={4} + /> + + + + ) : ( + + + 🤖 + + + Select a session to start chatting + + + Create a new session or choose from existing ones to begin discussing the candidate + + + )} + + + + + ); +}; + +export { CandidateChatSystem }; \ No newline at end of file diff --git a/frontend/src/pages/DocsPage.tsx b/frontend/src/pages/DocsPage.tsx index 08a987d..6f6c92d 100644 --- a/frontend/src/pages/DocsPage.tsx +++ b/frontend/src/pages/DocsPage.tsx @@ -39,6 +39,7 @@ import { BackstoryAppAnalysisPage } from 'documents/BackstoryAppAnalysisPage'; import { BackstoryThemeVisualizerPage } from 'documents/BackstoryThemeVisualizerPage'; import { UserManagement } from 'documents/UserManagement'; import { MockupPage } from 'documents/MockupPage'; +import { CandidateChatSystem } from 'documents/CandidateChatSystem'; // Sidebar navigation component using MUI components const Sidebar: React.FC<{ @@ -144,6 +145,7 @@ const documents : DocType[] = [ { title: "Application Architecture", route: "about-app", description: "System design and technical stack information", icon: }, { title: "UI Overview", route: "ui-overview", description: "Guide to the user interface components and interactions", icon: }, { title: "UI Mockup", route: "ui-mockup", description: "Visual previews of interfaces and layout concepts", icon: }, + { title: "Chat Mockup", route: "mockup-chat-system", description: "Mockup of chat system", icon: }, { title: "Theme Visualizer", route: "theme-visualizer", description: "Explore and customize application themes and visual styles", icon: }, { title: "App Analysis", route: "app-analysis", description: "Statistics and performance metrics of the application", icon: }, { title: 'Text Mockups', route: "backstory-ui-mockups", description: "Early text mockups of many of the interaction points." }, @@ -255,6 +257,8 @@ const DocsPage = (props: BackstoryPageProps) => { // Render the appropriate content based on current page function renderContent() { switch (page) { + case 'mockup-chat-system': + return (); case 'ui-overview': return (); case 'theme-visualizer':