Skip to content

Commit

Permalink
feat: show the participant chat history onclick in HostView
Browse files Browse the repository at this point in the history
  • Loading branch information
howard9199 committed Dec 21, 2024
1 parent 8e8ed7c commit ad38a6f
Show file tree
Hide file tree
Showing 2 changed files with 228 additions and 9 deletions.
152 changes: 145 additions & 7 deletions scripts/createTestConversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,109 @@ const groupStudents = {
f9U4yziIjCQ1R0jJYM7V: ['student25', 'student26', 'student27', 'student28', 'student29']
} as const;

// 添加這個新的函數來生成測試對話史
async function createTestHistory(conversationRef: FirebaseFirestore.DocumentReference) {
const sampleHistories = [
[
{
role: 'system',
content: '我是一個AI助手,可以幫助你完成任務。',
audio: null
},
{
role: 'user',
content: '你好,我需要幫助理解這篇文章。',
audio: null
},
{
role: 'assistant',
content: '當然可以,讓我們一步一步來分析這篇文章。首先,你覺得文章的主旨是什麼?',
audio: null
},
{
role: 'user',
content: '我覺得文章主要在討論環境保護的重要性。',
audio: null
},
{
role: 'assistant',
content: '很好的觀察!作者確實強調了環境保護的迫切性。你能指出文章中提到的具體環境問題嗎?',
audio: null
}
],
[
{
role: 'system',
content: '我是一個AI助手,可以幫助你完成任務。',
audio: null
},
{
role: 'user',
content: '這篇文章的寫作手法有什麼特別之處?',
audio: null
},
{
role: 'assistant',
content: '這篇文章運用了很多比喻和類比的修辭手法,讓抽象的概念更容易理解。',
audio: null
},
{
role: 'user',
content: '能舉個例子嗎?',
audio: null
},
{
role: 'assistant',
content:
'比如作者將地球比喻為一個生病的病人,環境污染就像是各種疾病,而我們人類就是醫生,需要及時治療。',
audio: null
}
],
[
{
role: 'system',
content: '我是一個AI助手,可以幫助你完成任務。',
audio: null
},
{
role: 'user',
content: '我想討論文章中的論點結構。',
audio: null
},
{
role: 'assistant',
content: '好的,這篇文章的論點結構很清晰。作者先提出問題,然後分析原因,最後給出解決方案。',
audio: null
},
{
role: 'user',
content: '你覺得哪個論點最有說服力?',
audio: null
},
{
role: 'assistant',
content:
'我認為作者用具體數據支持環境污染造成的經濟損失這個論點最有說服力,因為它提供了實際的證據。',
audio: null
}
]
];

// 隨機選擇一個對話歷史
const history = sampleHistories[Math.floor(Math.random() * sampleHistories.length)];

// 直接更新資料庫中的 conversation
await conversationRef.update({
history,
summary: '這是一個測試用的總結。',
keyPoints: ['關鍵點1', '關鍵點2', '關鍵點3']
});
}

async function createTestConversations() {
try {
// 為每個組別建立對話
for (const [groupNumber, students] of Object.entries(groupStudents)) {
// 為每個學生建立一個對話
for (const userId of students) {
// 隨機生成 5 個 true/false 值
const subtaskCompleted = Array.from({ length: 5 }, () => Math.random() > 0.5);

const conversation = {
Expand Down Expand Up @@ -58,16 +154,19 @@ async function createTestConversations() {
// 驗證 conversation 是否符合 schema
ConversationSchema.parse(conversation);

// 將 conversation 儲存到資料庫
await adminDb
// 將 conversation 儲存到資料庫並獲取參考
const conversationRef = await adminDb
.collection('sessions')
.doc(sessionId)
.collection('groups')
.doc(groupNumber)
.collection('conversations')
.add(conversation);

console.log(`已為 ${userId} (組別 ${groupNumber}) 建立測試對話`);
// 為新建立的 conversation 添加歷史記錄
await createTestHistory(conversationRef);

console.log(`已為 ${userId} (組別 ${groupNumber}) 建立測試對話和歷史記錄`);
}
}

Expand All @@ -77,4 +176,43 @@ async function createTestConversations() {
}
}

createTestConversations().catch(console.error);
async function deleteAllConversations() {
try {
// 遍歷每個組別
for (const groupNumber of Object.keys(groupStudents)) {
// 獲取該組別下所有的對話
const conversationsSnapshot = await adminDb
.collection('sessions')
.doc(sessionId)
.collection('groups')
.doc(groupNumber)
.collection('conversations')
.get();

// 刪除每個對話
const deletePromises = conversationsSnapshot.docs.map(async (doc) => {
await doc.ref.delete();
console.log(`已刪除對話: ${doc.id} (組別 ${groupNumber})`);
});

// 等待該組別的所有刪除操作完成
await Promise.all(deletePromises);
console.log(`組別 ${groupNumber} 的所有對話已刪除`);
}

console.log('所有對話刪除成功');
} catch (error) {
console.error('刪除對話時發生錯誤:', error);
}
}

// 如果你想同時執行刪除和創建,可以這樣寫:
async function resetConversations() {
await deleteAllConversations();
await createTestConversations();
}

//deleteAllConversations().catch(console.error);
resetConversations().catch(console.error);

// createTestConversations().catch(console.error);
85 changes: 83 additions & 2 deletions src/lib/components/session/HostView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import { getUser } from '$lib/utils/getUser';
import type { Conversation } from '$lib/schema/conversation';
import { getUserProgress } from '$lib/utils/getUserProgress';
import { Modal } from 'flowbite-svelte';
let { session }: { session: Readable<Session> } = $props();
type GroupWithId = Group & { id: string };
Expand All @@ -26,6 +27,15 @@
completedTasks: boolean[];
};
let participantProgress = $state(new Map<string, ParticipantProgress>());
let showChatHistory = $state(false);
let selectedParticipant = $state<{
displayName: string;
history: Array<{
role: string;
content: string;
audio?: string | null;
}>;
} | null>(null);
onMount(async () => {
try {
Expand Down Expand Up @@ -153,6 +163,31 @@
show: false
}
});
async function handleParticipantClick(groupId: string, participant: string) {
try {
const conversationsRef = collection(
db,
`sessions/${$page.params.id}/groups/${groupId}/conversations`
);
const snapshot = await getDocs(conversationsRef);
const conversations = snapshot.docs
.map((doc) => doc.data() as Conversation)
.filter((conv) => conv.userId === participant);
if (conversations.length > 0) {
const userData = await getUser(participant);
selectedParticipant = {
displayName: userData.displayName,
history: conversations[0].history
};
showChatHistory = true;
}
} catch (error) {
console.error('無法加載對話歷史:', error);
notifications.error('無法加載對話歷史');
}
}
</script>

<main class="mx-auto max-w-7xl px-4 py-16">
Expand Down Expand Up @@ -220,14 +255,23 @@
</div>
{:then progress}
<div class="flex items-center gap-2">
<span class="min-w-[60px] text-xs">{progress.displayName}</span>
<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 h-2">
{#each progress.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'
: 'bg-gray-300'}"
title="Subtask {i + 1}"
title={$session?.subtasks[i] || `Subtask ${i + 1}`}
></div>
{/each}
</div>
Expand Down Expand Up @@ -279,4 +323,41 @@
{/if}
</div>
</div>

{#if showChatHistory && selectedParticipant}
<Modal bind:open={showChatHistory} size="xl" autoclose outsideclose class="w-full">
<div class="mb-4">
<h3 class="text-xl font-semibold">
{selectedParticipant.displayName} 的對話歷史
</h3>
</div>
<div class="messages h-[400px] overflow-y-auto rounded-lg border border-gray-200 p-4">
{#each selectedParticipant.history as message}
<div class="flex py-2 {message.role === 'user' ? 'justify-end' : 'items-start'}">
{#if message.role !== 'user'}
<img src="/DefaultUser.jpg" alt="AI Profile" class="h-12 w-12 rounded-full" />
{/if}
<div
class="leading-1.5 flex max-w-2xl flex-col rounded-xl border-gray-500 bg-gray-200 p-4 dark:bg-gray-900"
>
<div>
<h2 class="text-lg font-bold {message.role === 'user' ? 'text-right' : ''}">
{message.role === 'user' ? selectedParticipant.displayName : 'AI Assistant'}
</h2>
<p class="text-base text-gray-900">{message.content}</p>
</div>
{#if message.audio}
<audio controls>
<source src={message.audio} type="audio/ogg; codecs=opus" />
</audio>
{/if}
</div>
{#if message.role === 'user'}
<img src="/DefaultUser.jpg" alt="User Profile" class="h-12 w-12 rounded-full" />
{/if}
</div>
{/each}
</div>
</Modal>
{/if}
</main>

0 comments on commit ad38a6f

Please sign in to comment.