Skip to content

Commit

Permalink
Feat/create individual (#52)
Browse files Browse the repository at this point in the history
* feat: enhance ParticipantView with chatroom integration and improved layout

* feat: implement API endpoint to start individual session and manage group conversations

* feat: sync the subtaskCompleted, create conversation when hitting start button

Co-authored-by: JacobLinCool <jacob@csie.cool>

* feat: handle the neccery doc, integrate the chatroom, fix some LLM bugs

- Integrated audio recording functionality using MicVAD.
- Added support for sending audio to a speech-to-text API for transcription.
- Updated conversation handling to include audio messages.
- Improved group and conversation data fetching with Firestore.
- Enhanced UI to reflect real-time updates in group information and chat history.

Co-authored-by: JacobLinCool <jacob@csie.cool>

* refactor: edit some console log

* fix: update initializeSession function to be asynchronous for codeDoc

* style: center QR code and enhance session code display in HostView.svelte

* refactor: update participant loading message and improve participant display in HostView.svelte

* Update src/routes/api/session/[id]/group/[group_number]/conversations/[conv_id]/chat/+server.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: JacobLinCool <jacob@csie.cool>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
  • Loading branch information
3 people authored Dec 22, 2024
1 parent fcf13d5 commit 1ffa745
Show file tree
Hide file tree
Showing 10 changed files with 597 additions and 243 deletions.
39 changes: 39 additions & 0 deletions scripts/deleteConversation.ts
Original file line number Diff line number Diff line change
@@ -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);
176 changes: 107 additions & 69 deletions src/lib/components/session/HostView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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<Session> } = $props();
let code = $state('Code generate error');
Expand All @@ -29,7 +29,7 @@
progress: number;
completedTasks: boolean[];
};
let participantProgress = $state(new Map<string, ParticipantProgress>());
let participantProgress = $state(new SvelteMap<string, ParticipantProgress>());
let showChatHistory = $state(false);
let selectedParticipant = $state<{
displayName: string;
Expand All @@ -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<string>();
const unsubscribe = onSnapshot(groupsCollection, (snapshot) => {
const groupsData: GroupWithId[] = snapshot.docs.map(
(doc) => ({ id: doc.id, ...doc.data() }) as GroupWithId
Expand All @@ -65,57 +67,95 @@
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);
}
};
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('無法開始個人階段');
}
}
Expand Down Expand Up @@ -275,11 +315,13 @@
{#if $session?.status === 'preparing'}
<div class="mt-4">
<h3 class="mb-2 font-medium">Session QR Code</h3>
<QRCode value={`${$page.url.origin}/session/${$page.params.id}`} />
<div class="flex justify-center">
<QRCode value={`${$page.url.origin}/session/${$page.params.id}`} />
</div>
</div>
<div class="mt-4">
<h3 class="mb-2 font-medium">Session Code</h3>
<p class="text-blue-700">{code}</p>
<p class="text-center text-5xl font-bold text-orange-600">{code}</p>
</div>
{/if}
</div>
Expand All @@ -292,7 +334,7 @@
>
<h2 class="mb-4 text-xl font-semibold">Groups</h2>
{#if $groups.length === 0}
<Alert color="blue">Loading groups...</Alert>
<Alert>Waiting for participants to join groups...</Alert>
{:else}
<div class="grid grid-cols-3 gap-4">
{#each [...$groups].sort((a, b) => a.number - b.number) as group}
Expand All @@ -304,24 +346,22 @@
<ul class="space-y-2">
{#each group.participants as participant}
<li class="space-y-1">
{#await getUserProgress($page.params.id, group.id, participant)}
<div class="flex items-center justify-between text-sm">
<span class="text-xs">Loading...</span>
</div>
{:then progress}
<div class="flex items-center gap-2">
<span
class="min-w-[60px] cursor-pointer text-xs hover:text-primary-600"
onclick={() => handleParticipantClick(group.id, participant)}
onkeydown={(e) =>
e.key === 'Enter' && handleParticipantClick(group.id, participant)}
role="button"
tabindex="0"
>
{progress.displayName}
</span>
<div class="flex items-center gap-2">
<span
class="min-w-[60px] cursor-pointer text-xs hover:text-primary-600"
onclick={() => 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}
</span>
{#if participantProgress.has(participant)}
<div class="flex h-2">
{#each progress.completedTasks as completed, i}
{#each participantProgress.get(participant)?.completedTasks || [] as completed, i}
<div
class="h-full w-8 border-r border-white first:rounded-l last:rounded-r last:border-r-0 {completed
? 'bg-green-500'
Expand All @@ -330,17 +370,15 @@
></div>
{/each}
</div>
<button
class="ml-auto rounded p-1 text-gray-500 hover:bg-gray-100 hover:text-red-500"
onclick={() => handleRemoveParticipant(group.id, participant)}
title="移除參與者"
>
<X class="h-4 w-4" />
</button>
</div>
{:catch}
<div class="text-xs text-red-500">Error loading progress</div>
{/await}
{/if}
<button
class="ml-auto rounded p-1 text-gray-500 hover:bg-gray-100 hover:text-red-500"
onclick={() => handleRemoveParticipant(group.id, participant)}
title="移除參與者"
>
<X class="h-4 w-4" />
</button>
</div>
</li>
{/each}
</ul>
Expand Down
Loading

0 comments on commit 1ffa745

Please sign in to comment.