Initial commit: Portfolio Website
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>
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
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,
|
||||
};
|
||||
Reference in New Issue
Block a user