Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,13 @@ VITE_SUPABASE_ANON_KEY=

# Geheimer Server-Schlüssel mit Schreibrechten
# Nur für Backend-Skripte oder Edge Functions verwenden!
SUPABASE_SERVICE_ROLE_KEY=
SUPABASE_SERVICE_ROLE_KEY=

# Chat-Integration

# Endpunkt für Chat-Completions
OPENAI_ENDPOINT=http://host.docker.internal:1234/api/v0/chat/completions
OPENAI_API_KEY=some-token
OPENAI_MODEL=google/gemma-3-1b
OPENAI_SYSTEM_PROMPT="Du bist ein Baum, dessen einziger Zweck während der Interaktion ist, die Informationen über dich auf spielerische Art und Weise wiederzugeben. Gib kurze, präzise Antworten. Wenn du die Antwort nicht kennst, dann antworte mit \"Ich weiß es nicht.\" Antworte immer auf Deutsch."
OPENAI_FIRST_USER_PROMPT="Stell dich vor."
6 changes: 3 additions & 3 deletions frontend/src/components/chat/BotMessage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@
let lastMessageText = '';
let selectedLabel: string | null = null;

$: if (message?.text && message.text !== lastMessageText) {
const current = message.text;
$: if (message?.content && message.content !== lastMessageText) {
const current = message.content;
lastMessageText = current;

parseMarkdown(current).then((result) => {
if (message.text === current) htmlText = result;
if (message.content === current) htmlText = result;
});
}

Expand Down
48 changes: 17 additions & 31 deletions frontend/src/components/chat/Chat.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
import { onMount } from 'svelte';
import { supabase } from '$lib/supabase';
import Message from './Message.svelte';
import type { Message as MessageType, RawMessage } from '$types/chat';
import type { Message as MessageType } from '$types/chat';

// === Props ===
export let treeId: string = '';
$: treeId;
console.log('Chat got Tree ID: ', treeId);

// === State ===
let sessionId: string = '';
let messages: MessageType[] = [];
let newMessage: string = '';
let chatAvailable: boolean = true;
Expand Down Expand Up @@ -47,55 +46,44 @@
}

const { data, error } = response as { data: any; error: any };
const jsonData = JSON.parse(data);

if (error !== null) {
console.error('Error fetching chat messages:', error);
return;
}

sessionId = data.sessionId;
messages = [
...messages,
...data.messages
.filter((msg: RawMessage) => !['no-reply', 'path'].includes(msg.type))
.map((msg: RawMessage): MessageType => {
const buttons = Array.isArray(msg.payload?.buttons)
? msg.payload!.buttons!.map((btn: { name: string; request: any }) => ({
label: btn.name,
request: btn.request
}))
: [];

return {
text: msg.payload?.message ?? '',
label: '',
type: msg.payload?.type ?? msg.type,
sender: 'bot',
buttons,
ai: msg.payload?.ai === true
};
})
...jsonData.messages.map((msg: MessageType): MessageType => {
return {
content: msg.content,
type: 'text',
role: 'assistant',
ai: true
};
})
];
};

function sendMessage(text: string) {
if (text === '') {
function sendMessage(content: string) {
if (content === '') {
return;
}
const newUserMessage: MessageType = {
text,
label: '',
content,
type: 'text',
sender: 'user'
role: 'user',
ai: false
};

messages = [...messages, newUserMessage];

supabase.functions
.invoke('chat', {
body: {
sessionId,
text
treeId,
messages
}
})
.then(handleNewChatMessages);
Expand All @@ -116,8 +104,6 @@
});
}
}

$: console.log('↪ newMessage:', JSON.stringify(newMessage));
</script>

<!-- Chat innerhalb der Card -->
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/chat/Message.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
</script>

<div class="message flex flex-row w-full">
{#if message.sender === 'bot'}
<BotMessage {message} {sendMessage}/>
{:else if message.sender === 'user'}
{#if message.role === 'assistant'}
<BotMessage {message} {sendMessage} />
{:else if message.role === 'user'}
<UserMessage {message} />
{/if}
</div>
6 changes: 3 additions & 3 deletions frontend/src/components/chat/UserMessage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
<div class="flex gap-1.5 flex-row w-full flex-end">
<div class="flex w-full justify-end">
<div class="p-3 text-black rounded-xl bg-message-user box-border max-w-[80%] md:max-w-[70%]">
{message.text}
{message.content}
</div>
</div>
<div class="pt-2">
<img src="/chat/user.svg" alt="Bot" class="min-w-8 min-h-8" />
<img src="/chat/user.svg" alt="Bot" class="min-w-8 min-h-8" />
</div>
</div>
</div>
41 changes: 12 additions & 29 deletions frontend/src/types/chat.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,16 @@
// src/lib/types.ts

export interface ChatButton {
label: string;
request: {
type: string;
payload?: any;
};
}

export interface Message {
text: string;
label: string;
content: string;
type: 'text' | 'choice';
sender: 'bot' | 'user';
role: 'assistant' | 'user' | 'system';
ai?: boolean;
buttons?: ChatButton[];
}

export interface RawMessage {
time: number;
type: string;
payload: any;
}

// /**
// * RawMessage entspricht exakt dem Nachrichtenformat,
// * das von der Voiceflow Runtime API zurückgegeben wird.
// *
// *
// * Dieses Format enthält alle Typen (text, choice, no-reply, etc.)
// * sowie optionale Zusatzinfos wie ai, delay, slate usw.
// */
Expand All @@ -35,12 +19,12 @@ export interface RawMessage {
// * Der Typ der Nachricht (z. B. "text", "choice", "visual", "no-reply")
// */
// type: string;

// /**
// * Zeitstempel der Nachricht (in ms seit Unix-Epoch)
// */
// time: number;

// /**
// * Nutzdaten der Nachricht – Struktur hängt vom Typ ab
// */
Expand All @@ -49,23 +33,23 @@ export interface RawMessage {
// * Der eigentliche Nachrichtentext (bei Typ "text")
// */
// message?: string;

// /**
// * Ob die Nachricht als KI-generiert markiert ist
// */
// ai?: boolean;

// /**
// * Optional: Zeitverzögerung vor Anzeige der Nachricht
// */
// delay?: number;

// /**
// * Strukturierte Textrepräsentation (Slate.js-kompatibel),
// * meist nur bei Typ "text" enthalten
// */
// slate?: unknown;

// /**
// * Nur bei "choice": eine Liste auswählbarer Buttons
// */
Expand All @@ -81,21 +65,20 @@ export interface RawMessage {
// };
// };
// }[];

// /**
// * Nur bei "visual": URL eines Bildes (z. B. zur Anzeige im Chat)
// */
// image?: string;

// /**
// * Nur bei "no-reply": Zeit bis Timeout
// */
// timeout?: number;

// /**
// * Nur bei "path": enthält den nächsten internen Pfadnamen
// */
// path?: string;
// };
// }

Loading