import { query, queryOne } from './postgres'; // Type definitions for chat tables export type ChatRole = 'user' | 'assistant' | 'system'; export interface ChatSession { id: string; created_at: string; updated_at: string; visitor_id: string; metadata: Record; } export interface ChatMessage { id: string; session_id: string; role: ChatRole; content: string; created_at: string; } // Input types for creating records export interface CreateChatSessionInput { visitor_id: string; metadata?: Record; } export interface CreateChatMessageInput { session_id: string; role: ChatRole; content: string; } // ============================================ // Chat Session Operations // ============================================ /** * Create a new chat session for a visitor */ export async function createChatSession( input: CreateChatSessionInput ): Promise { const result = await queryOne( `INSERT INTO chat_sessions (visitor_id, metadata) VALUES ($1, $2) RETURNING *`, [input.visitor_id, JSON.stringify(input.metadata ?? {})] ); if (!result) { throw new Error('Failed to create chat session'); } return result; } /** * Get a chat session by ID */ export async function getChatSession( sessionId: string ): Promise { return queryOne( 'SELECT * FROM chat_sessions WHERE id = $1', [sessionId] ); } /** * Get or create a chat session for a visitor * Useful for maintaining session continuity */ export async function getOrCreateChatSession( visitorId: string, metadata?: Record ): Promise { // First, try to find an existing active session for this visitor // Get the most recent session from the last 24 hours const existingSession = await queryOne( `SELECT * FROM chat_sessions WHERE visitor_id = $1 AND created_at >= NOW() - INTERVAL '24 hours' ORDER BY created_at DESC LIMIT 1`, [visitorId] ); if (existingSession) { return existingSession; } // No existing session found, create a new one return createChatSession({ visitor_id: visitorId, metadata, }); } /** * Update session metadata */ export async function updateSessionMetadata( sessionId: string, metadata: Record ): Promise { const result = await queryOne( `UPDATE chat_sessions SET metadata = $1 WHERE id = $2 RETURNING *`, [JSON.stringify(metadata), sessionId] ); if (!result) { throw new Error('Failed to update session metadata'); } return result; } // ============================================ // Chat Message Operations // ============================================ /** * Add a message to a chat session */ export async function addChatMessage( input: CreateChatMessageInput ): Promise { const result = await queryOne( `INSERT INTO chat_messages (session_id, role, content) VALUES ($1, $2, $3) RETURNING *`, [input.session_id, input.role, input.content] ); if (!result) { throw new Error('Failed to add chat message'); } return result; } /** * Get all messages for a chat session * Returns messages in chronological order */ export async function getChatMessages( sessionId: string ): Promise { return query( `SELECT * FROM chat_messages WHERE session_id = $1 ORDER BY created_at ASC`, [sessionId] ); } /** * Get the last N messages for a session * Useful for maintaining context window limits */ export async function getRecentMessages( sessionId: string, limit: number = 20 ): Promise { const messages = await query( `SELECT * FROM chat_messages WHERE session_id = $1 ORDER BY created_at DESC LIMIT $2`, [sessionId, limit] ); // Reverse to get chronological order return messages.reverse(); } /** * Delete all messages for a session (clear history) */ export async function clearChatMessages(sessionId: string): Promise { await query( 'DELETE FROM chat_messages WHERE session_id = $1', [sessionId] ); } /** * Delete a chat session and all its messages * Messages are deleted automatically via ON DELETE CASCADE */ export async function deleteChatSession(sessionId: string): Promise { await query( 'DELETE FROM chat_sessions WHERE id = $1', [sessionId] ); } // ============================================ // Utility Functions // ============================================ /** * Get conversation history formatted for AI context * Converts database messages to the format expected by chat completion APIs */ export async function getConversationHistory( sessionId: string, maxMessages: number = 20 ): Promise> { const messages = await getRecentMessages(sessionId, maxMessages); return messages.map((msg) => ({ role: msg.role, content: msg.content, })); } /** * Save a conversation turn (user message + assistant response) * Convenience function for typical chat flow */ export async function saveConversationTurn( sessionId: string, userMessage: string, assistantResponse: string ): Promise<{ userMsg: ChatMessage; assistantMsg: ChatMessage }> { const userMsg = await addChatMessage({ session_id: sessionId, role: 'user', content: userMessage, }); const assistantMsg = await addChatMessage({ session_id: sessionId, role: 'assistant', content: assistantResponse, }); return { userMsg, assistantMsg }; } // Default export for convenience export default { // Session operations createChatSession, getChatSession, getOrCreateChatSession, updateSessionMetadata, deleteChatSession, // Message operations addChatMessage, getChatMessages, getRecentMessages, clearChatMessages, // Utility functions getConversationHistory, saveConversationTurn, };