diff --git a/scripts/deleteConversation.ts b/scripts/deleteConversation.ts new file mode 100644 index 0000000..c4a1496 --- /dev/null +++ b/scripts/deleteConversation.ts @@ -0,0 +1,39 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +import { adminDb } from '../src/lib/server/firebase'; + +async function deleteAllConversationsInSession(sessionId: string) { + try { + // 1. 獲取所有群組 + const groupsSnapshot = await adminDb + .collection('sessions') + .doc(sessionId) + .collection('groups') + .get(); + + // 2. 遍歷每個群組 + for (const groupDoc of groupsSnapshot.docs) { + // 3. 獲取該群組下所有的對話 + const conversationsSnapshot = await groupDoc.ref.collection('conversations').get(); + + // 4. 刪除每個對話 + const deletePromises = conversationsSnapshot.docs.map(async (doc) => { + await doc.ref.delete(); + console.log(`已刪除對話: ${doc.id} (群組 ${groupDoc.id})`); + }); + + // 5. 等待該群組的所有刪除操作完成 + await Promise.all(deletePromises); + console.log(`群組 ${groupDoc.id} 的所有對話已刪除`); + } + + console.log(`Session ${sessionId} 的所有對話刪除成功`); + } catch (error) { + console.error('刪除對話時發生錯誤:', error); + } +} + +// 使用方式:傳入要刪除的 session ID +const sessionId = 'VJgvzmbRuWwqR8kH4MMz'; // 替換成要刪除的 session ID +deleteAllConversationsInSession(sessionId).catch(console.error); diff --git a/src/lib/components/session/HostView.svelte b/src/lib/components/session/HostView.svelte index 6c428ff..c9c4b75 100644 --- a/src/lib/components/session/HostView.svelte +++ b/src/lib/components/session/HostView.svelte @@ -14,10 +14,10 @@ import { writable } from 'svelte/store'; import { getUser } from '$lib/utils/getUser'; import type { Conversation } from '$lib/schema/conversation'; - import { getUserProgress } from '$lib/utils/getUserProgress'; import { Modal } from 'flowbite-svelte'; import Chatroom from '$lib/components/Chatroom.svelte'; import { X } from 'lucide-svelte'; + import { SvelteMap } from 'svelte/reactivity'; let { session }: { session: Readable } = $props(); let code = $state('Code generate error'); @@ -29,7 +29,7 @@ progress: number; completedTasks: boolean[]; }; - let participantProgress = $state(new Map()); + let participantProgress = $state(new SvelteMap()); let showChatHistory = $state(false); let selectedParticipant = $state<{ displayName: string; @@ -43,12 +43,14 @@ } | null>(null); onMount(() => { + const unsubscribes: (() => void)[] = []; const initializeSession = async () => { try { const codeCollection = doc(db, 'temp_codes', $page.params.id); const codeDoc = await getDoc(codeCollection); code = codeDoc.data()?.code; const groupsCollection = collection(db, `sessions/${$page.params.id}/groups`); + const groupChecked = new Set(); const unsubscribe = onSnapshot(groupsCollection, (snapshot) => { const groupsData: GroupWithId[] = snapshot.docs.map( (doc) => ({ id: doc.id, ...doc.data() }) as GroupWithId @@ -65,9 +67,34 @@ participantNames.set(participant, '未知使用者'); } } + + if (!groupChecked.has(group.id)) { + groupChecked.add(group.id); + + const conversationsRef = collection( + db, + `sessions/${$page.params.id}/groups/${group.id}/conversations` + ); + const unsubscribe = onSnapshot(conversationsRef, async (snapshot) => { + const conversations = snapshot.docs.map((doc) => doc.data() as Conversation); + for (const conv of conversations) { + const userData = await getUser(conv.userId); + const totalTasks = conv.subtasks.length; + const completedCount = conv.subtaskCompleted.filter(Boolean).length; + const progress = totalTasks > 0 ? (completedCount / totalTasks) * 100 : 0; + + participantProgress.set(conv.userId, { + displayName: userData.displayName, + progress, + completedTasks: conv.subtaskCompleted + }); + } + }); + unsubscribes.push(unsubscribe); + } }); }); - return unsubscribe; + unsubscribes.push(unsubscribe); } catch (error) { console.error('無法加載群組資料:', error); } @@ -75,47 +102,60 @@ initializeSession(); - for (const group of $groups) { - for (const participant of group.participants) { - const conversationsRef = collection( - db, - `sessions/${$page.params.id}/groups/${group.id}/conversations` - ); - onSnapshot(conversationsRef, async (snapshot) => { - const conversations = snapshot.docs - .map((doc) => doc.data() as Conversation) - .filter((conv) => conv.userId === participant); - - if (conversations.length > 0) { - const conv = conversations[0]; - const userData = await getUser(participant); - const totalTasks = conv.subtasks.length; - const completedCount = conv.subtaskCompleted.filter(Boolean).length; - const progress = totalTasks > 0 ? (completedCount / totalTasks) * 100 : 0; - - participantProgress.set(participant, { - displayName: userData.displayName, - progress, - completedTasks: conv.subtaskCompleted - }); - } - }); - } - } - return () => { - initializeSession().then((unsubscribe) => unsubscribe?.()); + unsubscribes.forEach((unsubscribe) => unsubscribe()); }; }); async function handleStartSession() { - const response = await fetch(`/api/session/${$page.params.id}/action/start-individual`, { - method: 'POST' - }); + try { + // 為每個群組的每個參與者創建對話 + for (const group of $groups) { + for (const participant of group.participants) { + const response = await fetch( + `/api/session/${$page.params.id}/group/${group.id}/conversations`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + task: $session?.task || '', + subtasks: $session?.subtasks || [], + resources: $session?.resources.map((r) => r.content) || [], + participant: participant + }) + } + ); - if (!response.ok) { - const data = await response.json(); - console.error('Failed to start session:', data.error); + if (!response.ok) { + const data = await response.json(); + notifications.error( + data.error || `無法為參與者 ${participantNames.get(participant)} 創建對話` + ); + return; + } + } + } + + // 更新 session 狀態 + const statusResponse = await fetch( + `/api/session/${$page.params.id}/action/start-individual`, + { + method: 'POST' + } + ); + + if (!statusResponse.ok) { + const data = await statusResponse.json(); + notifications.error(data.error || '無法開始個人階段'); + return; + } + + notifications.success('成功開始個人階段', 3000); + } catch (error) { + console.error('無法開始個人階段:', error); + notifications.error('無法開始個人階段'); } } @@ -275,11 +315,13 @@ {#if $session?.status === 'preparing'}

Session QR Code

- +
+ +

Session Code

-

{code}

+

{code}

{/if} @@ -292,7 +334,7 @@ >

Groups

{#if $groups.length === 0} - Loading groups... + Waiting for participants to join groups... {:else}
{#each [...$groups].sort((a, b) => a.number - b.number) as group} @@ -304,24 +346,22 @@
    {#each group.participants as participant}
  • - {#await getUserProgress($page.params.id, group.id, participant)} -
    - Loading... -
    - {:then progress} -
    - handleParticipantClick(group.id, participant)} - onkeydown={(e) => - e.key === 'Enter' && handleParticipantClick(group.id, participant)} - role="button" - tabindex="0" - > - {progress.displayName} - +
    + handleParticipantClick(group.id, participant)} + onkeydown={(e) => + e.key === 'Enter' && handleParticipantClick(group.id, participant)} + role="button" + tabindex="0" + > + {#await getUser(participant) then userData} + {userData.displayName} + {/await} + + {#if participantProgress.has(participant)}
    - {#each progress.completedTasks as completed, i} + {#each participantProgress.get(participant)?.completedTasks || [] as completed, i}
    handleRemoveParticipant(group.id, participant)} - title="移除參與者" - > - - -
    - {:catch} -
    Error loading progress
    - {/await} + {/if} + +
  • {/each}
diff --git a/src/lib/components/session/ParticipantView.svelte b/src/lib/components/session/ParticipantView.svelte index 4762670..b6692cd 100644 --- a/src/lib/components/session/ParticipantView.svelte +++ b/src/lib/components/session/ParticipantView.svelte @@ -1,16 +1,26 @@ -
-
-
-

{$session?.title}

-
-
+ async function sendAudioToSTT(audio: Float32Array) { + const wavBuffer: ArrayBuffer = utils.encodeWAV(audio); + const file = new File([wavBuffer], 'audio.wav', { type: 'audio/wav' }); + const formData = new FormData(); + formData.append('file', file); + console.log('Sending audio to STT...', formData); -
- -
-

Session Status

-
- - - {$session?.status} -
-
+ try { + const response = await fetch('/api/stt', { + method: 'POST', + body: formData + }); + console.log('STT response:', response); + if (!response.ok) { + throw new Error('Failed to transcribe audio'); + } + const data = await response.json(); + if (data.status === 'success') { + return { transcription: data.transcription, url: data.url }; + } + throw new Error(data.message || 'Failed to transcribe audio'); + } catch (error) { + notifications.error('Failed to transcribe audio'); + console.error(error); + return null; + } + } + + async function handleRecord() { + if (!conversationDoc || !groupDoc) { + notifications.error('No group or conversation found'); + return async () => {}; + } + + const vad = await MicVAD.new({ + model: 'v5', + minSpeechFrames: 16, // 0.5s + redemptionFrames: 32, // 1s + onSpeechEnd: async (audio: Float32Array) => { + if (!conversationDoc || !groupDoc) { + notifications.error('No group or conversation found'); + return; + } - -
-

Group Information

- {#if $group?.[0]} + const result = await sendAudioToSTT(audio); + + if (result) { + try { + const response = await fetch( + `/api/session/${$page.params.id}/group/${groupDoc.id}/conversations/${conversationDoc.id}/chat`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + content: result.transcription, + audio: result.url + }) + } + ); + + if (!response.ok) { + throw new Error('Failed to send message'); + } + } catch (error) { + console.error('Error sending message:', error); + notifications.error('Failed to send message'); + } + } + } + }); + vad.start(); + + return async () => { + vad.pause(); + vad.destroy(); + }; + } + + async function handleSend(text: string) { + if (!conversationDoc || !groupDoc) { + notifications.error('No group or conversation found'); + return; + } + + try { + const response = await fetch( + `/api/session/${$page.params.id}/group/${groupDoc.id}/conversations/${conversationDoc.id}/chat`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + content: text, + audio: null + }) + } + ); + + if (!response.ok) { + throw new Error('Failed to send message'); + } + notifications.success('Message sent!'); + } catch (error) { + console.error('Error sending message:', error); + notifications.error('Failed to send message'); + } + } + + +
+

{$session?.title}

+ +
+
+ +
+

Session Status

- Group - #{$group[0][1].number} -
-
-

Members:

-
    - {#each $group[0][1].participants as participant} -
  • - - - {#if participant === user.uid} - You - {:else} - {#await getUser(participant)} - 載入中... - {:then profile} - {profile.displayName} - {:catch} - 未知使用者 - {/await} - {/if} - -
  • - {/each} -
+ + + {$session?.status}
+ {#if $session?.status === 'individual'} +

Work on your individual contributions.

+ {/if}
- {:else if $session?.status === 'preparing'} -
-

Group Management

- {#if creating} -
-
- - +
+ + +
+

Group Information

+ {#if groupDoc} +
+
+ Group + #{groupDoc.data.number} +
+
+

Members:

+
    + {#each groupDoc.data.participants as participant} +
  • + + + {#if participant === user.uid} + You + {:else} + {#await getUser(participant)} + 載入中... + {:then profile} + {profile.displayName} + {:catch} + 未知使用者 + {/await} + {/if} + +
  • + {/each} +
+
+
+ {:else if $session?.status === 'preparing'} +
+

Group Management

+ {#if creating} +
+
+ + +
+
- -
- {:else} - - {/if} - -
- {:else} -

You are not in a group yet.

- {/if} +
+ {:else} +

You are not in a group yet.

+ {/if} +
-
+
{#if $session?.status === 'preparing'}

Waiting for session to start...

The host will begin the session shortly.

{:else if $session?.status === 'individual'} -
-

Individual Work Phase

-

Work on your individual contributions.

-
+ {:else if $session?.status === 'before-group'}

Preparing for Group Discussion

diff --git a/src/lib/server/llm.ts b/src/lib/server/llm.ts index 1eda5ba..5200d2c 100644 --- a/src/lib/server/llm.ts +++ b/src/lib/server/llm.ts @@ -18,20 +18,24 @@ const openai = new OpenAI({ }); async function isHarmfulContent(content: string): Promise { + console.log('Checking content for harmful content:', { contentLength: content.length }); const moderation = await openai.moderations.create({ model: 'omni-moderation-latest', input: content }); - + console.log('Moderation result:', moderation.results[0]); return moderation.results[0].flagged; } export async function checkFileContent( filePath: string ): Promise<{ success: boolean; message: string; error?: string }> { + console.log('Checking file content:', { filePath }); try { const content = await fs.readFile(filePath, 'utf-8'); + console.log('File content read successfully:', { contentLength: content.length }); if (await isHarmfulContentFile(content)) { + console.warn('Harmful content detected in file'); return { success: false, message: '', @@ -61,13 +65,26 @@ export async function isHarmfulContentFile(message: string) { return moderation.results[0].flagged; } -async function requestChatLLM(system_prompt: string, history: LLMChatMessage[], temperature = 0.7) { +export async function requestChatLLM( + system_prompt: string, + history: LLMChatMessage[], + temperature = 0.7 +) { + console.log('Requesting chat LLM:', { + systemPromptLength: system_prompt.length, + historyLength: history.length, + temperature + }); try { const response = await openai.chat.completions.create({ model: 'gpt-4o-mini', messages: [{ role: 'system', content: system_prompt }, ...history], temperature: temperature }); + console.log('Chat LLM response received:', { + responseLength: response.choices[0].message.content?.length, + usage: response.usage + }); const result = response.choices[0].message.content; if (!result) { @@ -86,35 +103,31 @@ async function requestChatLLM(system_prompt: string, history: LLMChatMessage[], }; } } -async function requestZodLLM( +async function requestZodLLM>( system_prompt: string, - zod_scheme: z.ZodSchema, + zod_scheme: z.ZodSchema, temperature = 0.7 ) { - try { - const response = await openai.beta.chat.completions.parse({ - model: 'gpt-4o-mini', - messages: [{ role: 'user', content: system_prompt }], - temperature: temperature, - response_format: zodResponseFormat(zod_scheme, 'response') - }); - - const result = response.choices[0].message.parsed; - if (!result) { - throw new Error('Failed to parse response'); - } + const response = await openai.beta.chat.completions.parse({ + model: 'gpt-4o-mini', + messages: [{ role: 'user', content: system_prompt }], + temperature: temperature, + response_format: zodResponseFormat(zod_scheme, 'response') + }); + console.log('Received response from LLM', { + response, + response_format: zodResponseFormat(zod_scheme, 'response') + }); - return { - success: true, - message: result - }; - } catch (error) { - return { - success: false, - message: '', - error: error - }; + const result = response.choices[0].message.parsed; + if (!result) { + throw new Error('Failed to parse response'); } + + return { + success: true, + message: result + }; } export async function chatWithLLMByDocs( @@ -127,6 +140,12 @@ export async function chatWithLLMByDocs( }[], temperature = 0.7 ): Promise<{ success: boolean; message: string; subtask_completed: boolean[]; error?: string }> { + console.log('Starting chatWithLLMByDocs:', { + historyLength: history.length, + task, + subtasksCount: subtasks.length, + resourcesCount: resources.length + }); try { const last_message_content = history[history.length - 1]?.content; if (last_message_content && (await isHarmfulContent(last_message_content))) { @@ -149,7 +168,16 @@ export async function chatWithLLMByDocs( .replace('{resources}', formatted_docs); const subtask_completed = await checkSubtaskCompleted(history, subtasks); + console.log('Formatted system prompt:', { + promptLength: system_prompt.length, + subtaskCompletedCount: subtask_completed.completed.length + }); + const response = await requestChatLLM(system_prompt, history, temperature); + console.log('Chat response received:', { + success: response.success, + messageLength: response.message.length + }); if (!response.success) { throw new Error('Failed to parse response'); @@ -180,7 +208,9 @@ async function checkSubtaskCompleted( '{chatHistory}', formatted_history ).replace('{subtasks}', subtasks.join('\n')); - const completed_schema = z.array(z.boolean()).length(subtasks.length); + const completed_schema = z.object({ + satisfied: z.object(Object.fromEntries(subtasks.map((subtask) => [subtask, z.boolean()]))) + }); const response = await requestZodLLM(system_prompt, completed_schema); @@ -188,10 +218,11 @@ async function checkSubtaskCompleted( throw new Error('Failed to parse response'); } - const completed = response.message as z.infer; + const completed = subtasks.map((subtask) => response.message.satisfied[subtask]); + return { success: true, - completed: completed + completed }; } catch (error) { console.error('Error in checkSubtaskCompleted:', error); @@ -209,6 +240,7 @@ export async function summarizeStudentChat(history: LLMChatMessage[]): Promise<{ key_points: string[]; error?: string; }> { + console.log('Summarizing student chat:', { historyLength: history.length }); try { const formatted_history = history.map((msg) => `${msg.role}: ${msg.content}`).join('\n'); const system_prompt = CHAT_SUMMARY_PROMPT.replace('{chatHistory}', formatted_history); @@ -218,6 +250,10 @@ export async function summarizeStudentChat(history: LLMChatMessage[]): Promise<{ }); const response = await requestZodLLM(system_prompt, summary_student_opinion_schema); + console.log('Summary response:', { + success: response.success, + messageType: typeof response.message + }); if (!response.success) { throw new Error('Failed to parse response'); diff --git a/src/lib/utils/firestore.ts b/src/lib/utils/firestore.ts index bd86df1..30b6d96 100644 --- a/src/lib/utils/firestore.ts +++ b/src/lib/utils/firestore.ts @@ -24,7 +24,8 @@ export async function createConversation( task: task, subtasks: subtasks, resources: resources, - history: [] + history: [], + subtaskCompleted: new Array(subtasks.length).fill(false) }); return conversationRef.id; diff --git a/src/lib/utils/getUserProgress.ts b/src/lib/utils/getUserProgress.ts index 01ce555..8ecf8ce 100644 --- a/src/lib/utils/getUserProgress.ts +++ b/src/lib/utils/getUserProgress.ts @@ -14,36 +14,63 @@ export async function getUserProgress( groupId: string, userId: string ): Promise { - console.log(`Fetching progress for user ${userId} in group ${groupId}`); + try { + console.log(`Fetching progress for user ${userId} in group ${groupId}`); - const conversationsRef = collection(db, `sessions/${sessionId}/groups/${groupId}/conversations`); + const conversationsRef = collection( + db, + 'sessions', + sessionId, + 'groups', + groupId, + 'conversations' + ); - const snapshot = await getDocs(conversationsRef); - const conversations = snapshot.docs - .map((doc) => doc.data() as Conversation) - .filter((conv) => conv.userId === userId); + const snapshot = await getDocs(conversationsRef); - if (conversations.length === 0) { + if (snapshot.empty) { + console.log(`No conversations found for user ${userId}`); + const userData = await getUser(userId); + return { + displayName: userData.displayName, + progress: 0, + completedTasks: [] + }; + } + + const conversations = snapshot.docs + .map((doc) => doc.data() as Conversation) + .filter((conv) => conv.userId === userId); + + if (conversations.length === 0) { + console.log(`No matching conversations found for user ${userId}`); + const userData = await getUser(userId); + return { + displayName: userData.displayName, + progress: 0, + completedTasks: [] + }; + } + + const conv = conversations[0]; const userData = await getUser(userId); - return { - displayName: userData.displayName, - progress: 0, - completedTasks: [] - }; - } - const conv = conversations[0]; - const userData = await getUser(userId); - const totalTasks = conv.subtaskCompleted.length; - const completedCount = conv.subtaskCompleted.filter(Boolean).length; - const progress = totalTasks > 0 ? (completedCount / totalTasks) * 100 : 0; + const subtaskCompleted = conv.subtaskCompleted || []; + const totalTasks = subtaskCompleted.length; + const completedCount = subtaskCompleted.filter(Boolean).length; + const progress = totalTasks > 0 ? (completedCount / totalTasks) * 100 : 0; - const result = { - displayName: userData.displayName, - progress, - completedTasks: conv.subtaskCompleted - }; + const result = { + displayName: userData.displayName, + progress, + completedTasks: subtaskCompleted + }; - console.log(`Progress result for ${userId}:`, result); - return result; + console.log(`Progress result for ${userId}:`, result); + return result; + } catch (error: unknown) { + console.error('Error fetching user progress:', error); + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + throw new Error(`Failed to fetch progress for user ${userId}: ${errorMessage}`); + } } diff --git a/src/routes/api/session/[id]/group/[group_number]/conversations/+server.ts b/src/routes/api/session/[id]/group/[group_number]/conversations/+server.ts index d1caaac..fecc8bb 100644 --- a/src/routes/api/session/[id]/group/[group_number]/conversations/+server.ts +++ b/src/routes/api/session/[id]/group/[group_number]/conversations/+server.ts @@ -3,13 +3,12 @@ import type { RequestHandler } from '@sveltejs/kit'; import { error, json, redirect } from '@sveltejs/kit'; import { z } from 'zod'; -// Endpoint for creating a new conversation in a group by teacher -// POST /api/session/[id]/group/[group_number]/conversations/+server -// Request data format +// 更新請求數據格式 const requestDataFormat = z.object({ task: z.string(), subtasks: z.array(z.string()), - resources: z.array(z.string()) + resources: z.array(z.string()), + participant: z.string() }); export const POST: RequestHandler = async ({ request, params, locals }) => { @@ -22,12 +21,12 @@ export const POST: RequestHandler = async ({ request, params, locals }) => { throw error(400, 'Missing parameters'); } - const { task, subtasks, resources } = await getRequestData(request); + const { task, subtasks, resources, participant } = await getRequestData(request); const conv_id = await createConversation( id, group_number, - locals.user.uid, + participant, task, subtasks, resources @@ -46,10 +45,12 @@ export const POST: RequestHandler = async ({ request, params, locals }) => { async function getRequestData(request: Request): Promise> { const data = await request.json(); const result = requestDataFormat.parse(data); - if (!result.task || !result.subtasks || !result.resources) { + if (!result.task || !result.subtasks || !result.resources || !result.participant) { throw error( 400, - `Missing parameters: ${!result.task ? 'task ' : ''}${!result.subtasks ? 'subtasks ' : ''}${!result.resources ? 'resources' : ''}`.trim() + `Missing parameters: ${!result.task ? 'task ' : ''}${!result.subtasks ? 'subtasks ' : ''}${ + !result.resources ? 'resources ' : '' + }${!result.participant ? 'participant' : ''}`.trim() ); } return result; diff --git a/src/routes/api/session/[id]/group/[group_number]/conversations/[conv_id]/chat/+server.ts b/src/routes/api/session/[id]/group/[group_number]/conversations/[conv_id]/chat/+server.ts index 3cdb593..4654c05 100644 --- a/src/routes/api/session/[id]/group/[group_number]/conversations/[conv_id]/chat/+server.ts +++ b/src/routes/api/session/[id]/group/[group_number]/conversations/[conv_id]/chat/+server.ts @@ -10,29 +10,49 @@ import { z } from 'zod'; // Request data format const requestDataFormat = z.object({ content: z.string(), - audio: z.string() + audio: z.string().nullable() }); export const POST: RequestHandler = async ({ request, params, locals }) => { + console.log('POST request received', { params }); try { if (!locals.user) { + console.log('User not authenticated'); redirect(303, '/login'); } const { id, group_number, conv_id } = params; if (!id || !group_number || !conv_id) { + console.log('Missing parameters:', { id, group_number, conv_id }); throw error(400, 'Missing parameters'); } const conversation_ref = await getConversationRef(id, group_number, conv_id); + console.log('Retrieved conversation reference'); const { userId, task, subtasks, resources, history } = await getConversationData(conversation_ref); + console.log('Retrieved conversation data', { userId, task, subtasksCount: subtasks.length }); if (userId !== locals.user.uid) { + console.log('User unauthorized', { userId, requestingUser: locals.user.uid }); throw error(403, 'Forbidden'); } const { content, audio } = await getRequestData(request); + console.log('Parsed request data', { contentLength: content.length, hasAudio: !!audio }); + + await conversation_ref.update({ + history: [ + ...history, + { + role: 'user', + content: content, + audio: audio + } + ] + }); + console.log('Updated conversation with user message'); const chat_history = history.map(DBChatMessage2LLMChatMessage); + console.log('Prepared chat history for LLM', { historyLength: chat_history.length }); const response = await chatWithLLMByDocs( [...chat_history, { role: 'user', content: content }], @@ -40,7 +60,13 @@ export const POST: RequestHandler = async ({ request, params, locals }) => { subtasks, resources ); + console.log('Received LLM response', { + success: response.success, + subtaskCompleted: response.subtask_completed + }); + if (!response.success) { + console.error('LLM response failed:', response.error); throw error(500, response.error); } @@ -48,9 +74,13 @@ export const POST: RequestHandler = async ({ request, params, locals }) => { history: [ ...history, { - role: 'assistant', - content: response.message, + role: 'user', + content: content, audio: audio + }, + { + role: 'assistant', + content: response.message } ], subtaskCompleted: response.subtask_completed @@ -64,15 +94,15 @@ export const POST: RequestHandler = async ({ request, params, locals }) => { }; async function getRequestData(request: Request): Promise> { + console.log('Processing request data'); const data = await request.json(); - const parsedData = requestDataFormat.parse(data); - if (!parsedData.content && !parsedData.audio) { - throw error(400, 'Missing content or audio parameter'); - } - if (typeof parsedData.content !== 'string' || typeof parsedData.audio !== 'string') { - throw error(400, 'Invalid parameters'); + console.log('Parsed JSON data'); + const parsed = requestDataFormat.safeParse(data); + if (!parsed.success) { + console.error('Invalid request data:', parsed.error); + throw error(400, `Invalid request data: ${parsed.error}`); } - return parsedData; + return parsed.data; } function DBChatMessage2LLMChatMessage(message: DBChatMessage): LLMChatMessage { diff --git a/src/routes/api/session/[id]/group/[group_number]/join/+server.ts b/src/routes/api/session/[id]/group/[group_number]/join/+server.ts index abe7685..fc5db63 100644 --- a/src/routes/api/session/[id]/group/[group_number]/join/+server.ts +++ b/src/routes/api/session/[id]/group/[group_number]/join/+server.ts @@ -39,7 +39,7 @@ export const POST: RequestHandler = async ({ params, locals }) => { participants: [...groupData.participants, locals.user.uid] }); - return json({ success: true }); + return json({ success: true, groupId: groupDoc.id }); } catch (e) { console.error('Error joining group:', e); throw error(500, 'Failed to join group'); diff --git a/src/routes/test/chatroom/+page.svelte b/src/routes/test/chatroom/+page.svelte index 84d0bd4..1c5d0aa 100644 --- a/src/routes/test/chatroom/+page.svelte +++ b/src/routes/test/chatroom/+page.svelte @@ -51,8 +51,11 @@ } async function handleRecord() { + // 16kHz, frame size 512 const vad = await MicVAD.new({ model: 'v5', + minSpeechFrames: 16, // 0.5s + redemptionFrames: 32, // 1s onSpeechEnd: async (audio: Float32Array) => { const result = await sendAudioToSTT(audio); if (result) {