mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 19:21:10 +02:00
feat(chat): integrate chat project into monorepo with full app structure
- Restructure chat as apps/mobile, apps/web, apps/landing, backend - Add NestJS backend for secure Azure OpenAI API calls - Remove exposed API key from mobile app (security fix) - Add shared chat-types package - Create SvelteKit web app scaffold - Create Astro landing page scaffold - Update pnpm workspace configuration - Add project-level CLAUDE.md documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
fcf3a344b1
commit
c638a7ffee
155 changed files with 22622 additions and 348 deletions
692
chat/apps/mobile/services/conversation.ts
Normal file
692
chat/apps/mobile/services/conversation.ts
Normal file
|
|
@ -0,0 +1,692 @@
|
|||
import { supabase } from '../utils/supabase';
|
||||
import { sendChatRequest, ChatMessage, logTokenUsage, ChatRequestResult } from './openai';
|
||||
|
||||
// Typdefinitionen für Konversationen und Nachrichten
|
||||
export type Conversation = {
|
||||
id: string; // UUID
|
||||
user_id: string; // UUID des Benutzers (auth.uid)
|
||||
model_id: string; // UUID
|
||||
template_id?: string; // UUID, optional
|
||||
conversation_mode: 'free' | 'guided' | 'template';
|
||||
document_mode: boolean; // Gibt an, ob der Dokumentmodus aktiviert ist
|
||||
title?: string; // Titel der Konversation
|
||||
is_archived: boolean; // Gibt an, ob die Konversation archiviert wurde
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type Message = {
|
||||
id: string;
|
||||
conversation_id: string;
|
||||
sender: 'user' | 'assistant' | 'system';
|
||||
message_text: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type TokenUsageType = {
|
||||
id: string;
|
||||
conversation_id: string;
|
||||
message_id: string;
|
||||
user_id: string;
|
||||
model_id: string;
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
total_tokens: number;
|
||||
estimated_cost: number;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Erstellt eine neue Konversation in der Datenbank
|
||||
*/
|
||||
export async function createConversation(
|
||||
userId: string,
|
||||
modelId: string,
|
||||
mode: 'free' | 'guided' | 'template' = 'free',
|
||||
templateId?: string,
|
||||
documentMode: boolean = false,
|
||||
spaceId?: string
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
console.log("🔵 Erstelle Konversation mit Space ID:", spaceId || "keine");
|
||||
|
||||
// Erstelle einen neuen Eintrag in der Conversations-Tabelle
|
||||
const { data, error } = await supabase
|
||||
.from('conversations')
|
||||
.insert({
|
||||
user_id: userId,
|
||||
model_id: modelId,
|
||||
template_id: templateId,
|
||||
conversation_mode: mode,
|
||||
document_mode: documentMode,
|
||||
space_id: spaceId,
|
||||
})
|
||||
.select('id')
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
console.error('Fehler beim Erstellen der Konversation:', error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return data.id;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen der Konversation:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fügt eine neue Nachricht zur Konversation hinzu
|
||||
*/
|
||||
export async function addMessage(
|
||||
conversationId: string,
|
||||
sender: 'user' | 'assistant' | 'system',
|
||||
messageText: string
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
// Führe eine Prüfung und Validierung des Senders durch
|
||||
let validSender = sender;
|
||||
|
||||
// Stelle sicher, dass der Sender den zulässigen Werten entspricht
|
||||
// Das scheint das Problem zu sein - die Datenbank akzeptiert nur bestimmte Werte
|
||||
if (!['user', 'assistant', 'system'].includes(validSender)) {
|
||||
console.error('Ungültiger Sender-Wert:', sender);
|
||||
validSender = 'user'; // Fallback auf 'user'
|
||||
}
|
||||
|
||||
console.log('Füge Nachricht hinzu mit Sender:', validSender);
|
||||
|
||||
// Füge eine neue Nachricht in die Messages-Tabelle ein
|
||||
const { data, error } = await supabase
|
||||
.from('messages')
|
||||
.insert({
|
||||
conversation_id: conversationId,
|
||||
sender: validSender,
|
||||
message_text: messageText,
|
||||
})
|
||||
.select('id')
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
console.error('Fehler beim Hinzufügen der Nachricht:', error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return data.id;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Hinzufügen der Nachricht:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt alle Nachrichten einer Konversation
|
||||
*/
|
||||
export async function getMessages(conversationId: string): Promise<Message[]> {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('messages')
|
||||
.select('*')
|
||||
.eq('conversation_id', conversationId)
|
||||
.order('created_at', { ascending: true });
|
||||
|
||||
if (error) {
|
||||
console.error('Fehler beim Laden der Nachrichten:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
return data as Message[];
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Nachrichten:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert einen Titel für die Konversation basierend auf der ersten Benutzeranfrage
|
||||
* @param userQuestion Die erste Frage des Benutzers
|
||||
* @returns Generierter Titel
|
||||
*/
|
||||
export async function generateConversationTitle(userQuestion: string): Promise<string> {
|
||||
try {
|
||||
console.log('Generiere Titel für Konversation basierend auf:',
|
||||
userQuestion.substring(0, 50) + (userQuestion.length > 50 ? '...' : ''));
|
||||
|
||||
// Verwende speziell GPT-4o-Mini für die Titelerstellung
|
||||
const titlePrompt = `Schreibe eine kurze, prägnante Überschrift (maximal 5 Wörter) für unseren Chat mit dieser Frage: "${userQuestion}"`;
|
||||
|
||||
// Manuell 4o-mini-Modell festlegen
|
||||
const chatMessages: ChatMessage[] = [
|
||||
{
|
||||
role: 'system',
|
||||
content: 'MODEL:gpt-4o-mini-se'
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: titlePrompt
|
||||
}
|
||||
];
|
||||
|
||||
// Sende die Anfrage mit niedrigerer Temperatur für zuverlässigere Ergebnisse
|
||||
const titleResponse = await sendChatRequest(chatMessages, 0.3, 50);
|
||||
|
||||
// Extrahiere den Text aus der Antwort
|
||||
const responseText = typeof titleResponse === 'string'
|
||||
? titleResponse
|
||||
: titleResponse.content;
|
||||
|
||||
// Entferne Anführungszeichen und Punkt am Ende, falls vorhanden
|
||||
let cleanTitle = responseText.trim()
|
||||
.replace(/^["']|["']$/g, '') // Entferne Anführungszeichen am Anfang und Ende
|
||||
.replace(/\.$/g, ''); // Entferne Punkt am Ende
|
||||
|
||||
// Begrenze die Länge auf 100 Zeichen (für Datenbank)
|
||||
if (cleanTitle.length > 100) {
|
||||
cleanTitle = cleanTitle.substring(0, 97) + '...';
|
||||
}
|
||||
|
||||
console.log('Generierter Titel:', cleanTitle);
|
||||
return cleanTitle;
|
||||
} catch (error) {
|
||||
console.error('Fehler bei der Titelgenerierung:', error);
|
||||
// Fallback-Titel bei Fehler
|
||||
return 'Neue Konversation';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert den Titel einer Konversation
|
||||
*/
|
||||
export async function updateConversationTitle(conversationId: string, title: string): Promise<boolean> {
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('conversations')
|
||||
.update({ title })
|
||||
.eq('id', conversationId);
|
||||
|
||||
if (error) {
|
||||
console.error('Fehler beim Aktualisieren des Konversationstitels:', error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren des Konversationstitels:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt einen System-Prompt aus einer Vorlage
|
||||
* @param templateId Die ID der Vorlage
|
||||
* @returns Der System-Prompt der Vorlage oder null
|
||||
*/
|
||||
export async function getSystemPromptFromTemplate(templateId: string): Promise<string | null> {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('templates')
|
||||
.select('system_prompt')
|
||||
.eq('id', templateId)
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
console.error('Fehler beim Laden der Vorlage:', error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return data.system_prompt;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Vorlage:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sendet eine Benutzeranfrage an das LLM-Modell und speichert die Antwort
|
||||
*/
|
||||
export async function sendMessageAndGetResponse(
|
||||
conversationId: string,
|
||||
userMessage: string,
|
||||
modelId: string,
|
||||
templateId?: string,
|
||||
documentMode: boolean = false
|
||||
): Promise<{ userMessageId: string | null; assistantMessageId: string | null; assistantResponse: string; title?: string; documentContent?: string }> {
|
||||
try {
|
||||
console.log('Starte sendMessageAndGetResponse mit:', {
|
||||
conversationId,
|
||||
userMessage: userMessage.substring(0, 50) + (userMessage.length > 50 ? '...' : ''),
|
||||
modelId
|
||||
});
|
||||
|
||||
// Lade das Modell aus der Datenbank oder verwende Fallback
|
||||
const { data: modelData, error: modelError } = await supabase
|
||||
.from('models')
|
||||
.select('*')
|
||||
.eq('id', modelId)
|
||||
.single();
|
||||
|
||||
if (modelError) {
|
||||
console.error('Fehler beim Laden des Modells:', modelError);
|
||||
console.log('Verwende Standard-Parameter, da Modell nicht geladen werden konnte');
|
||||
} else {
|
||||
console.log('Modell geladen:', {
|
||||
id: modelData.id,
|
||||
name: modelData.name,
|
||||
parameters: modelData.parameters,
|
||||
deployment: modelData.parameters?.deployment
|
||||
});
|
||||
}
|
||||
|
||||
// Variable für die Benutzer-Nachricht-ID
|
||||
let userMessageId: string | null = null;
|
||||
|
||||
// Überprüfe, ob die Nachricht bereits in der Datenbank existiert
|
||||
const { data: existingMessages } = await supabase
|
||||
.from('messages')
|
||||
.select('id')
|
||||
.eq('conversation_id', conversationId)
|
||||
.eq('sender', 'user')
|
||||
.eq('message_text', userMessage)
|
||||
.limit(1);
|
||||
|
||||
if (existingMessages && existingMessages.length > 0) {
|
||||
// Wenn die Nachricht bereits existiert, verwende diese ID
|
||||
userMessageId = existingMessages[0].id;
|
||||
console.log('Bestehende Benutzernachricht gefunden mit ID:', userMessageId);
|
||||
} else {
|
||||
// Speichere die Benutzernachricht nur, wenn sie nicht bereits existiert
|
||||
userMessageId = await addMessage(conversationId, 'user', userMessage);
|
||||
console.log('Neue Benutzernachricht gespeichert mit ID:', userMessageId);
|
||||
}
|
||||
|
||||
// Lade alle bisherigen Nachrichten für Kontext
|
||||
const messages = await getMessages(conversationId);
|
||||
console.log(`${messages.length} Nachrichten für Kontext geladen`);
|
||||
|
||||
// Konvertiere die Nachrichten in das Format für die OpenAI-API
|
||||
const chatMessages: ChatMessage[] = [];
|
||||
|
||||
// Lade den System-Prompt aus der Vorlage, falls template_id vorhanden
|
||||
let systemPrompt: string | null = null;
|
||||
if (templateId) {
|
||||
systemPrompt = await getSystemPromptFromTemplate(templateId);
|
||||
if (systemPrompt) {
|
||||
console.log('System-Prompt aus Vorlage geladen');
|
||||
|
||||
// Wenn Dokumentmodus aktiv ist, füge spezielle Anweisungen hinzu
|
||||
if (documentMode) {
|
||||
const documentModePrompt = `
|
||||
${systemPrompt}
|
||||
|
||||
WICHTIG: Du befindest dich im Dokumentmodus. Deine Aufgabe ist es, dem Benutzer zu helfen, ein Dokument zu erstellen und zu verbessern.
|
||||
|
||||
1. Das Dokument wird in einem separaten Bereich neben dem Chat angezeigt.
|
||||
2. Wenn der Benutzer Feedback zu dem Dokument gibt, sollst du eine Überarbeitung des Dokuments vorschlagen.
|
||||
3. Formatiere deine Vorschläge für das Dokument in gut strukturiertem Markdown-Format.
|
||||
4. Verwende bei längeren Dokumenten Überschriften, Listen und andere Markdown-Elemente zur besseren Gliederung.
|
||||
5. Antworte IMMER in diesem Format:
|
||||
|
||||
CHAT: Hier antwortest du auf die Frage oder das Feedback des Nutzers.
|
||||
|
||||
DOKUMENT:
|
||||
Hier steht dein Vorschlag für das Dokument in Markdown-Format, ohne Codeblock-Markierungen.
|
||||
`;
|
||||
// Ersetze den Original-Prompt durch den Dokumentmodus-Prompt
|
||||
chatMessages.push({
|
||||
role: 'system',
|
||||
content: documentModePrompt
|
||||
});
|
||||
|
||||
console.log('Dokumentmodus-Prompt hinzugefügt');
|
||||
} else {
|
||||
// Standard-Prompt ohne Dokumentmodus
|
||||
chatMessages.push({
|
||||
role: 'system',
|
||||
content: systemPrompt
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (documentMode) {
|
||||
// Wenn kein Template, aber Dokumentmodus aktiv ist
|
||||
const documentModePrompt = `
|
||||
Du befindest dich im Dokumentmodus. Deine Aufgabe ist es, dem Benutzer zu helfen, ein Dokument zu erstellen und zu verbessern.
|
||||
|
||||
1. Das Dokument wird in einem separaten Bereich neben dem Chat angezeigt.
|
||||
2. Wenn der Benutzer Feedback zu dem Dokument gibt, sollst du eine Überarbeitung des Dokuments vorschlagen.
|
||||
3. Formatiere deine Vorschläge für das Dokument in gut strukturiertem Markdown-Format.
|
||||
4. Verwende bei längeren Dokumenten Überschriften, Listen und andere Markdown-Elemente zur besseren Gliederung.
|
||||
5. Antworte IMMER in diesem Format:
|
||||
|
||||
CHAT: Hier antwortest du auf die Frage oder das Feedback des Nutzers.
|
||||
|
||||
DOKUMENT:
|
||||
Hier steht dein Vorschlag für das Dokument in Markdown-Format, ohne Codeblock-Markierungen.
|
||||
`;
|
||||
chatMessages.push({
|
||||
role: 'system',
|
||||
content: documentModePrompt
|
||||
});
|
||||
|
||||
console.log('Standard-Dokumentmodus-Prompt hinzugefügt');
|
||||
}
|
||||
|
||||
// Füge eine System-Nachricht mit der Modell-ID hinzu, falls ein Modell geladen wurde
|
||||
if (modelData && modelData.parameters && modelData.parameters.deployment) {
|
||||
console.log(`Nutze deployment '${modelData.parameters.deployment}' für Modell ${modelData.name}`);
|
||||
// Stelle die Modell-Identifikation ganz am Anfang ein
|
||||
chatMessages.unshift({
|
||||
role: 'system',
|
||||
content: `MODEL:${modelData.parameters.deployment}`
|
||||
});
|
||||
} else {
|
||||
// Versuche, das Deployment über die Modell-ID zu finden
|
||||
console.warn('Kein Modell-Deployment in Modell-Daten gefunden, suche in verfügbaren Modellen');
|
||||
// Lade dynamisch die Modelle
|
||||
try {
|
||||
const { data: availableModels } = await supabase
|
||||
.from('models')
|
||||
.select('id, parameters, name');
|
||||
|
||||
const matchingModel = availableModels?.find(m => m.id === modelId);
|
||||
|
||||
if (matchingModel && matchingModel.parameters && matchingModel.parameters.deployment) {
|
||||
console.log(`Nutze deployment '${matchingModel.parameters.deployment}' für Modell ${matchingModel.name}`);
|
||||
chatMessages.unshift({
|
||||
role: 'system',
|
||||
content: `MODEL:${matchingModel.parameters.deployment}`
|
||||
});
|
||||
} else {
|
||||
console.warn('Kein passendes Modell-Deployment gefunden, verwende Standard-Deployment');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der verfügbaren Modelle:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Füge alle normalen Nachrichten hinzu
|
||||
chatMessages.push(...messages.map(msg => ({
|
||||
role: msg.sender === 'user' ? 'user' : msg.sender === 'assistant' ? 'assistant' : 'system',
|
||||
content: msg.message_text
|
||||
})));
|
||||
|
||||
console.log('Nachrichten für OpenAI konvertiert:', chatMessages.length, 'Nachrichten');
|
||||
|
||||
// Sende die Anfrage an das LLM-Modell
|
||||
console.log('Sende Anfrage an LLM-Modell mit Parametern:', {
|
||||
temperature: modelData?.parameters?.temperature || 0.7,
|
||||
maxTokens: modelData?.parameters?.max_tokens || 800
|
||||
});
|
||||
|
||||
const result = await sendChatRequest(
|
||||
chatMessages,
|
||||
modelData?.parameters?.temperature || 0.7,
|
||||
modelData?.parameters?.max_tokens || 800
|
||||
);
|
||||
|
||||
// Extrahiere die Antwort und Token-Nutzung aus dem Ergebnis
|
||||
let assistantResponse: string;
|
||||
let tokenUsage;
|
||||
|
||||
if (typeof result === 'string') {
|
||||
// Falls nur ein String zurückgegeben wurde (Fehlerfall)
|
||||
assistantResponse = result;
|
||||
console.log('Einfache String-Antwort vom LLM-Modell erhalten (kein Tokennutzungs-Tracking):', {
|
||||
length: assistantResponse.length,
|
||||
preview: assistantResponse.substring(0, 50) + (assistantResponse.length > 50 ? '...' : '')
|
||||
});
|
||||
} else {
|
||||
// Bei vollständigem Ergebnis mit Token-Nutzung
|
||||
assistantResponse = result.content;
|
||||
tokenUsage = result.usage;
|
||||
|
||||
console.log('Antwort vom LLM-Modell erhalten:', {
|
||||
length: assistantResponse.length,
|
||||
preview: assistantResponse.substring(0, 50) + (assistantResponse.length > 50 ? '...' : ''),
|
||||
tokenUsage
|
||||
});
|
||||
}
|
||||
|
||||
// Dokumentinhalt extrahieren, wenn im Dokumentmodus
|
||||
let documentContent: string | undefined;
|
||||
let chatResponse = assistantResponse;
|
||||
|
||||
if (documentMode) {
|
||||
// Nach dem Format "CHAT: ... DOKUMENT: ```markdown ... ```" suchen
|
||||
console.log("Analysiere LLM-Antwort für Dokumentextraktion:", assistantResponse.substring(0, 200) + "...");
|
||||
|
||||
const chatMatch = assistantResponse.match(/CHAT:(.*?)(?=DOKUMENT:|$)/s);
|
||||
const documentMatch = assistantResponse.match(/DOKUMENT:[\s\n]*(```markdown|```|`markdown)?([^`].*?)(?:```|`+)?$/s);
|
||||
|
||||
console.log("Dokument-Regex Match:", documentMatch ? "Ja" : "Nein");
|
||||
|
||||
if (chatMatch && chatMatch[1]) {
|
||||
chatResponse = chatMatch[1].trim();
|
||||
console.log('Chat-Antwort extrahiert:', chatResponse.substring(0, 50) + '...');
|
||||
}
|
||||
|
||||
if (documentMatch && documentMatch[2]) {
|
||||
documentContent = documentMatch[2].trim();
|
||||
console.log('Dokument-Inhalt extrahiert:', (documentContent?.substring(0, 50) || '') + '...');
|
||||
} else if (documentMatch) {
|
||||
console.log('Dokument-Match gefunden, aber kein Inhalt in Gruppe 2');
|
||||
console.log('Dokument-Match-Gruppen:', documentMatch.length);
|
||||
for (let i = 0; i < documentMatch.length; i++) {
|
||||
console.log(`Gruppe ${i}:`, documentMatch[i]?.substring(0, 30));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Speichere die Antwort des Assistenten
|
||||
const assistantMessageId = await addMessage(conversationId, 'assistant', chatResponse);
|
||||
console.log('Assistentenantwort gespeichert mit ID:', assistantMessageId);
|
||||
|
||||
// Token-Nutzung loggen, falls verfügbar
|
||||
if (tokenUsage && assistantMessageId && userMessageId && modelData) {
|
||||
try {
|
||||
// Lade die Konversation, um die user_id zu erhalten
|
||||
const { data: conversationData, error: convError } = await supabase
|
||||
.from('conversations')
|
||||
.select('user_id')
|
||||
.eq('id', conversationId)
|
||||
.single();
|
||||
|
||||
if (convError || !conversationData) {
|
||||
console.error('Fehler beim Laden der Konversation für Token-Logging:', convError);
|
||||
} else {
|
||||
const userId = conversationData.user_id;
|
||||
|
||||
// Logge die Token-Nutzung
|
||||
await logTokenUsage(
|
||||
tokenUsage,
|
||||
conversationId,
|
||||
assistantMessageId,
|
||||
userId,
|
||||
modelId
|
||||
);
|
||||
console.log('Token-Nutzung erfolgreich geloggt');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Loggen der Token-Nutzung:', error);
|
||||
// Wir werfen keinen Fehler, da das Token-Logging nicht kritisch ist
|
||||
}
|
||||
}
|
||||
|
||||
// Prüfe, ob dies die erste Nachricht in der Konversation ist
|
||||
// Wenn ja, generiere einen Titel und aktualisiere die Konversation
|
||||
const { count } = await supabase
|
||||
.from('messages')
|
||||
.select('id', { count: 'exact', head: true })
|
||||
.eq('conversation_id', conversationId);
|
||||
|
||||
let title;
|
||||
// Nur für die erste oder zweite Nachricht (die erste könnte eine System-Nachricht sein)
|
||||
if (count === 1 || count === 2) {
|
||||
// Generiere einen Titel basierend auf der Benutzernachricht
|
||||
title = await generateConversationTitle(userMessage);
|
||||
|
||||
// Aktualisiere den Titel in der Datenbank
|
||||
const success = await updateConversationTitle(conversationId, title);
|
||||
console.log('Konversationstitel aktualisiert:', success ? 'erfolgreich' : 'fehlgeschlagen');
|
||||
}
|
||||
|
||||
return {
|
||||
userMessageId,
|
||||
assistantMessageId,
|
||||
assistantResponse: chatResponse,
|
||||
title,
|
||||
documentContent
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Senden der Nachricht:', error);
|
||||
|
||||
// Detaillierte Fehlerinformationen ausgeben
|
||||
if (error instanceof Error) {
|
||||
console.error('Fehlerdetails:', {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
userMessageId: null,
|
||||
assistantMessageId: null,
|
||||
assistantResponse: `Es ist ein Fehler aufgetreten: ${error instanceof Error ? error.message : 'Unbekannter Fehler'}. Bitte versuche es später erneut.`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt alle aktiven (nicht archivierten) Konversationen eines Benutzers
|
||||
* Optional: Mit spaceId werden nur Konversationen aus diesem Space geladen
|
||||
*/
|
||||
export async function getConversations(userId: string, spaceId?: string): Promise<Conversation[]> {
|
||||
try {
|
||||
let query = supabase
|
||||
.from('conversations')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.eq('is_archived', false);
|
||||
|
||||
// Wenn eine Space-ID angegeben wurde, filtere nach diesem Space
|
||||
if (spaceId) {
|
||||
query = query.eq('space_id', spaceId);
|
||||
}
|
||||
|
||||
const { data, error } = await query.order('updated_at', { ascending: false });
|
||||
|
||||
if (error) {
|
||||
console.error('Fehler beim Laden der Konversationen:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
return data as Conversation[];
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Konversationen:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt alle archivierten Konversationen eines Benutzers
|
||||
*/
|
||||
export async function getArchivedConversations(userId: string): Promise<Conversation[]> {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('conversations')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.eq('is_archived', true)
|
||||
.order('updated_at', { ascending: false });
|
||||
|
||||
if (error) {
|
||||
console.error('Fehler beim Laden der archivierten Konversationen:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
return data as Conversation[];
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der archivierten Konversationen:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Archiviert eine Konversation
|
||||
*/
|
||||
export async function archiveConversation(conversationId: string): Promise<boolean> {
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('conversations')
|
||||
.update({ is_archived: true })
|
||||
.eq('id', conversationId);
|
||||
|
||||
if (error) {
|
||||
console.error('Fehler beim Archivieren der Konversation:', error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Archivieren der Konversation:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stellt eine archivierte Konversation wieder her
|
||||
*/
|
||||
export async function unarchiveConversation(conversationId: string): Promise<boolean> {
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('conversations')
|
||||
.update({ is_archived: false })
|
||||
.eq('id', conversationId);
|
||||
|
||||
if (error) {
|
||||
console.error('Fehler beim Wiederherstellen der Konversation:', error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Wiederherstellen der Konversation:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht eine Konversation dauerhaft
|
||||
*/
|
||||
export async function deleteConversation(conversationId: string): Promise<boolean> {
|
||||
try {
|
||||
// Lösche zuerst alle zugehörigen Nachrichten
|
||||
const { error: messagesError } = await supabase
|
||||
.from('messages')
|
||||
.delete()
|
||||
.eq('conversation_id', conversationId);
|
||||
|
||||
if (messagesError) {
|
||||
console.error('Fehler beim Löschen der Nachrichten:', messagesError);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lösche dann die Konversation selbst
|
||||
const { error: conversationError } = await supabase
|
||||
.from('conversations')
|
||||
.delete()
|
||||
.eq('id', conversationId);
|
||||
|
||||
if (conversationError) {
|
||||
console.error('Fehler beim Löschen der Konversation:', conversationError);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Löschen der Konversation:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue