Vollständige Next.js 15 Portfolio-Website mit: - Blog-System mit 100+ Artikeln - Supabase-Integration - Responsive Design mit Tailwind CSS - TypeScript-Konfiguration - Testing-Setup mit Vitest und Playwright Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
263 lines
5.9 KiB
TypeScript
263 lines
5.9 KiB
TypeScript
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<string, unknown>;
|
|
}
|
|
|
|
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<string, unknown>;
|
|
}
|
|
|
|
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<ChatSession> {
|
|
const result = await queryOne<ChatSession>(
|
|
`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<ChatSession | null> {
|
|
return queryOne<ChatSession>(
|
|
'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<string, unknown>
|
|
): Promise<ChatSession> {
|
|
// 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<ChatSession>(
|
|
`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<string, unknown>
|
|
): Promise<ChatSession> {
|
|
const result = await queryOne<ChatSession>(
|
|
`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<ChatMessage> {
|
|
const result = await queryOne<ChatMessage>(
|
|
`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<ChatMessage[]> {
|
|
return query<ChatMessage>(
|
|
`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<ChatMessage[]> {
|
|
const messages = await query<ChatMessage>(
|
|
`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<void> {
|
|
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<void> {
|
|
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<Array<{ role: ChatRole; content: string }>> {
|
|
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,
|
|
};
|