From fdc19aac41572b273b579eef7f0b2467f58c8914 Mon Sep 17 00:00:00 2001 From: Rotimi Best Date: Mon, 13 Jul 2026 11:18:28 +0100 Subject: [PATCH] feat(dashboard): add Show transcript and Summarize lesson buttons in lesson view (#748) * feat(dashboard): add lesson transcript and summarize actions in view mode Add Show transcript and Summarize lesson outline buttons beneath lesson videos in view mode. Show transcript opens the existing transcript side panel when an uploaded video has a transcript. Summarize lesson appears on every lesson and sends a contextual prompt to the AI assistant. Co-authored-by: Rotimi Best * fix(dashboard): localize summarize lesson AI prompts Move hardcoded summarize prompt strings into translation keys so the prompt sent to the AI assistant follows the user's app language. Co-authored-by: Rotimi Best --------- Co-authored-by: Cursor Agent Co-authored-by: Rotimi Best --- .../course/components/lesson/index.ts | 1 + .../lesson/lesson-summarize-button.svelte | 21 +++++++++++ .../lesson/utils/lesson-summarize-prompt.ts | 37 +++++++++++++++++++ .../lesson/video/lesson-video-player.svelte | 29 ++++++++++++--- .../components/lesson/video/video.svelte | 8 ++-- .../lib/features/course/pages/lesson.svelte | 10 ++++- .../src/lib/utils/translations/da.json | 7 ++++ .../src/lib/utils/translations/de.json | 7 ++++ .../src/lib/utils/translations/en.json | 7 ++++ .../src/lib/utils/translations/es.json | 7 ++++ .../src/lib/utils/translations/fr.json | 7 ++++ .../src/lib/utils/translations/hi.json | 7 ++++ .../src/lib/utils/translations/pl.json | 7 ++++ .../src/lib/utils/translations/pt.json | 7 ++++ .../src/lib/utils/translations/ru.json | 7 ++++ .../src/lib/utils/translations/vi.json | 7 ++++ 16 files changed, 166 insertions(+), 10 deletions(-) create mode 100644 apps/dashboard/src/lib/features/course/components/lesson/lesson-summarize-button.svelte create mode 100644 apps/dashboard/src/lib/features/course/components/lesson/utils/lesson-summarize-prompt.ts diff --git a/apps/dashboard/src/lib/features/course/components/lesson/index.ts b/apps/dashboard/src/lib/features/course/components/lesson/index.ts index 889ffabd..47b99059 100644 --- a/apps/dashboard/src/lib/features/course/components/lesson/index.ts +++ b/apps/dashboard/src/lib/features/course/components/lesson/index.ts @@ -10,3 +10,4 @@ export { default as Document } from './document/document.svelte'; export { default as AddVideoModal } from './video/add-video-modal.svelte'; export { default as AddDocumentModal } from './document/add-document-modal.svelte'; export { default as LessonSettingsTab } from './lesson-settings-tab.svelte'; +export { default as LessonSummarizeButton } from './lesson-summarize-button.svelte'; diff --git a/apps/dashboard/src/lib/features/course/components/lesson/lesson-summarize-button.svelte b/apps/dashboard/src/lib/features/course/components/lesson/lesson-summarize-button.svelte new file mode 100644 index 00000000..c9265ade --- /dev/null +++ b/apps/dashboard/src/lib/features/course/components/lesson/lesson-summarize-button.svelte @@ -0,0 +1,21 @@ + + + diff --git a/apps/dashboard/src/lib/features/course/components/lesson/utils/lesson-summarize-prompt.ts b/apps/dashboard/src/lib/features/course/components/lesson/utils/lesson-summarize-prompt.ts new file mode 100644 index 00000000..6ced455d --- /dev/null +++ b/apps/dashboard/src/lib/features/course/components/lesson/utils/lesson-summarize-prompt.ts @@ -0,0 +1,37 @@ +import { lessonApi } from '$features/course/api'; +import { isHtmlValueEmpty } from '$lib/utils/functions/toHtml'; +import { t } from '$lib/utils/functions/translations'; +import { isEnforceableLessonVideo, type LessonVideo } from '../video/video-card-utils'; + +export function hasUploadedLessonVideo(videos: LessonVideo[] | null | undefined): boolean { + return (videos ?? []).some((video) => isEnforceableLessonVideo(video)); +} + +export function hasLessonNoteContentForLesson(lessonId: string): boolean { + const rawNote = lessonApi.lesson?.note; + const hasLegacyNote = typeof rawNote === 'string' && rawNote.trim().length > 0; + + if (hasLegacyNote) { + return true; + } + + const translations = Object.values(lessonApi.translations[lessonId] || {}); + + return translations.some((content) => typeof content === 'string' && !isHtmlValueEmpty(content)); +} + +export function buildLessonSummarizePrompt(lessonId: string): string { + const videos = lessonApi.lesson?.videos ?? []; + const hasUploadedVideo = hasUploadedLessonVideo(videos); + const hasLessonNotes = hasLessonNoteContentForLesson(lessonId); + + if (!hasUploadedVideo) { + return t.get('course.navItem.lessons.materials.summarize_lesson_prompt.lesson_only'); + } + + if (hasLessonNotes) { + return t.get('course.navItem.lessons.materials.summarize_lesson_prompt.transcript_and_notes'); + } + + return t.get('course.navItem.lessons.materials.summarize_lesson_prompt.transcript_only'); +} diff --git a/apps/dashboard/src/lib/features/course/components/lesson/video/lesson-video-player.svelte b/apps/dashboard/src/lib/features/course/components/lesson/video/lesson-video-player.svelte index 4aa91fe8..5733cce2 100644 --- a/apps/dashboard/src/lib/features/course/components/lesson/video/lesson-video-player.svelte +++ b/apps/dashboard/src/lib/features/course/components/lesson/video/lesson-video-player.svelte @@ -1,5 +1,6 @@ -{#snippet content(video)} +{#snippet content(video, index)} {#key video.type === 'upload' ? ((video as typeof video & { assetId?: string }).assetId ?? video.link) : video.link} - + {/key} {/snippet} @@ -81,9 +81,9 @@ {#if videos.length}
- {#each videos as video} + {#each videos as video, index}
- {@render content(video)} + {@render content(video, index)}
{/each}
diff --git a/apps/dashboard/src/lib/features/course/pages/lesson.svelte b/apps/dashboard/src/lib/features/course/pages/lesson.svelte index 6a970e60..7ed8fcf8 100644 --- a/apps/dashboard/src/lib/features/course/pages/lesson.svelte +++ b/apps/dashboard/src/lib/features/course/pages/lesson.svelte @@ -46,7 +46,8 @@ Document, AddVideoModal, AddDocumentModal, - LessonSettingsTab + LessonSettingsTab, + LessonSummarizeButton } from '$features/course/components/lesson'; import type { TLocale } from '@cio/db/types'; @@ -179,6 +180,7 @@ const viewModeComponents = $derived(getViewModeComponents(tabs)); const isMaterialsEmpty = $derived(tabs.every((tab) => tab.badgeValue === 0)); + const hasLessonVideos = $derived((lessonApi.lesson?.videos?.length ?? 0) > 0); let timeoutId: NodeJS.Timeout | undefined; @@ -524,6 +526,12 @@ {:else if lessonApi.lesson && !isMaterialsEmpty} {#key lessonId}
+ {#if !hasLessonVideos} +
+ +
+ {/if} + {#each viewModeComponents as Component, index (index)} {/each} diff --git a/apps/dashboard/src/lib/utils/translations/da.json b/apps/dashboard/src/lib/utils/translations/da.json index 0885451e..235c4686 100644 --- a/apps/dashboard/src/lib/utils/translations/da.json +++ b/apps/dashboard/src/lib/utils/translations/da.json @@ -1889,6 +1889,13 @@ "no_content": "Intet indhold", "no_translation": "Ingen oversættelse for det valgte sprog", "button": "knappen", + "show_transcript": "Vis transskript", + "summarize_lesson": "Opsummer lektion", + "summarize_lesson_prompt": { + "lesson_only": "Opsummer, hvad der er blevet undervist i denne lektion.", + "transcript_only": "Se transskriptet af den uploadede video, og opsummer, hvad der nævnes i transskriptet.", + "transcript_and_notes": "Se transskriptet af den uploadede video, og opsummer, hvad der nævnes i transskriptet. Opsummer også, hvad der dækkes i lektionsnoterne." + }, "button_done": "Færdig", "tabs": { "settings": { diff --git a/apps/dashboard/src/lib/utils/translations/de.json b/apps/dashboard/src/lib/utils/translations/de.json index 4dcb7390..48214228 100644 --- a/apps/dashboard/src/lib/utils/translations/de.json +++ b/apps/dashboard/src/lib/utils/translations/de.json @@ -1889,6 +1889,13 @@ "no_content": "Kein Inhalt", "no_translation": "Keine Übersetzung für die ausgewählte Sprache", "button": "Taste", + "show_transcript": "Transkript anzeigen", + "summarize_lesson": "Lektion zusammenfassen", + "summarize_lesson_prompt": { + "lesson_only": "Fassen Sie zusammen, was in dieser Lektion vermittelt wurde.", + "transcript_only": "Sehen Sie sich das Transkript des hochgeladenen Videos an und fassen Sie zusammen, was im Transkript erwähnt wird.", + "transcript_and_notes": "Sehen Sie sich das Transkript des hochgeladenen Videos an und fassen Sie zusammen, was im Transkript erwähnt wird. Fassen Sie auch zusammen, was in den Lektionsnotizen behandelt wird." + }, "button_done": "Fertig", "tabs": { "settings": { diff --git a/apps/dashboard/src/lib/utils/translations/en.json b/apps/dashboard/src/lib/utils/translations/en.json index 233a7c2f..f0e34829 100644 --- a/apps/dashboard/src/lib/utils/translations/en.json +++ b/apps/dashboard/src/lib/utils/translations/en.json @@ -2265,6 +2265,13 @@ "no_content": "No Content", "no_translation": "No translation for the selected language", "button": "button", + "show_transcript": "Show transcript", + "summarize_lesson": "Summarize lesson", + "summarize_lesson_prompt": { + "lesson_only": "Summarize what has been taught in this lesson.", + "transcript_only": "Look at the transcript of the uploaded video and summarize what is mentioned in the transcript.", + "transcript_and_notes": "Look at the transcript of the uploaded video and summarize what is mentioned in the transcript. Also summarize what is covered in the lesson notes." + }, "tabs": { "note": { "title": "Note", diff --git a/apps/dashboard/src/lib/utils/translations/es.json b/apps/dashboard/src/lib/utils/translations/es.json index ddaaf2c3..5b9f3757 100644 --- a/apps/dashboard/src/lib/utils/translations/es.json +++ b/apps/dashboard/src/lib/utils/translations/es.json @@ -1889,6 +1889,13 @@ "no_content": "Sin contenido", "no_translation": "No hay traducción para el idioma seleccionado.", "button": "botón", + "show_transcript": "Mostrar transcripción", + "summarize_lesson": "Resumir lección", + "summarize_lesson_prompt": { + "lesson_only": "Resume lo que se ha enseñado en esta lección.", + "transcript_only": "Consulta la transcripción del video subido y resume lo que se menciona en la transcripción.", + "transcript_and_notes": "Consulta la transcripción del video subido y resume lo que se menciona en la transcripción. También resume lo que se cubre en las notas de la lección." + }, "button_done": "hecho", "tabs": { "settings": { diff --git a/apps/dashboard/src/lib/utils/translations/fr.json b/apps/dashboard/src/lib/utils/translations/fr.json index f859fca9..88a1133b 100644 --- a/apps/dashboard/src/lib/utils/translations/fr.json +++ b/apps/dashboard/src/lib/utils/translations/fr.json @@ -1889,6 +1889,13 @@ "no_content": "Aucun contenu", "no_translation": "Aucune traduction pour la langue sélectionnée", "button": "bouton", + "show_transcript": "Afficher la transcription", + "summarize_lesson": "Résumer la leçon", + "summarize_lesson_prompt": { + "lesson_only": "Résumez ce qui a été enseigné dans cette leçon.", + "transcript_only": "Consultez la transcription de la vidéo téléchargée et résumez ce qui y est mentionné.", + "transcript_and_notes": "Consultez la transcription de la vidéo téléchargée et résumez ce qui y est mentionné. Résumez également ce qui est couvert dans les notes de cours." + }, "button_done": "Terminé", "tabs": { "settings": { diff --git a/apps/dashboard/src/lib/utils/translations/hi.json b/apps/dashboard/src/lib/utils/translations/hi.json index d6b20714..f3657331 100644 --- a/apps/dashboard/src/lib/utils/translations/hi.json +++ b/apps/dashboard/src/lib/utils/translations/hi.json @@ -1889,6 +1889,13 @@ "no_content": "कोई सामग्री नहीं", "no_translation": "चयनित भाषा के लिए कोई अनुवाद नहीं", "button": "बटन", + "show_transcript": "प्रतिलेख दिखाएं", + "summarize_lesson": "पाठ का सारांश", + "summarize_lesson_prompt": { + "lesson_only": "इस पाठ में क्या सिखाया गया है, उसका सारांश दें।", + "transcript_only": "अपलोड किए गए वीडियो का प्रतिलेख देखें और प्रतिलेख में जो कुछ उल्लेखित है उसका सारांश दें।", + "transcript_and_notes": "अपलोड किए गए वीडियो का प्रतिलेख देखें और प्रतिलेख में जो कुछ उल्लेखित है उसका सारांश दें। साथ ही पाठ नोट्स में जो कवर किया गया है उसका भी सारांश दें।" + }, "button_done": "हो गया", "tabs": { "settings": { diff --git a/apps/dashboard/src/lib/utils/translations/pl.json b/apps/dashboard/src/lib/utils/translations/pl.json index df68c6c5..7a78cb05 100644 --- a/apps/dashboard/src/lib/utils/translations/pl.json +++ b/apps/dashboard/src/lib/utils/translations/pl.json @@ -1889,6 +1889,13 @@ "no_content": "Brak treści", "no_translation": "Brak tłumaczenia dla wybranego języka", "button": "przycisk", + "show_transcript": "Pokaż transkrypt", + "summarize_lesson": "Podsumuj lekcję", + "summarize_lesson_prompt": { + "lesson_only": "Podsumuj, czego nauczono się w tej lekcji.", + "transcript_only": "Przejrzyj transkrypcję przesłanego wideo i podsumuj, co zostało w niej wspomniane.", + "transcript_and_notes": "Przejrzyj transkrypcję przesłanego wideo i podsumuj, co zostało w niej wspomniane. Podsumuj także to, co zostało omówione w notatkach do lekcji." + }, "button_done": "Gotowe", "tabs": { "settings": { diff --git a/apps/dashboard/src/lib/utils/translations/pt.json b/apps/dashboard/src/lib/utils/translations/pt.json index f03a3d0c..568ac495 100644 --- a/apps/dashboard/src/lib/utils/translations/pt.json +++ b/apps/dashboard/src/lib/utils/translations/pt.json @@ -1889,6 +1889,13 @@ "no_content": "Sem conteúdo", "no_translation": "Nenhuma tradução para o idioma selecionado", "button": "botão", + "show_transcript": "Mostrar transcrição", + "summarize_lesson": "Resumir lição", + "summarize_lesson_prompt": { + "lesson_only": "Resuma o que foi ensinado nesta lição.", + "transcript_only": "Consulte a transcrição do vídeo enviado e resuma o que é mencionado na transcrição.", + "transcript_and_notes": "Consulte a transcrição do vídeo enviado e resuma o que é mencionado na transcrição. Resuma também o que é abordado nas notas da lição." + }, "button_done": "Concluído", "tabs": { "settings": { diff --git a/apps/dashboard/src/lib/utils/translations/ru.json b/apps/dashboard/src/lib/utils/translations/ru.json index 6ac2fbb0..f46ca451 100644 --- a/apps/dashboard/src/lib/utils/translations/ru.json +++ b/apps/dashboard/src/lib/utils/translations/ru.json @@ -1889,6 +1889,13 @@ "no_content": "Нет контента", "no_translation": "Нет перевода на выбранный язык", "button": "кнопка", + "show_transcript": "Показать транскрипт", + "summarize_lesson": "Суммировать урок", + "summarize_lesson_prompt": { + "lesson_only": "Кратко изложите, чему учили в этом уроке.", + "transcript_only": "Просмотрите транскрипт загруженного видео и кратко изложите, что в нём упоминается.", + "transcript_and_notes": "Просмотрите транскрипт загруженного видео и кратко изложите, что в нём упоминается. Также кратко изложите, что рассматривается в заметках к уроку." + }, "button_done": "Готово", "tabs": { "settings": { diff --git a/apps/dashboard/src/lib/utils/translations/vi.json b/apps/dashboard/src/lib/utils/translations/vi.json index fa8d9f43..f13ef016 100644 --- a/apps/dashboard/src/lib/utils/translations/vi.json +++ b/apps/dashboard/src/lib/utils/translations/vi.json @@ -1889,6 +1889,13 @@ "no_content": "Không có nội dung", "no_translation": "Không có bản dịch cho ngôn ngữ đã chọn", "button": "nút", + "show_transcript": "Hiển thị bản ghi", + "summarize_lesson": "Tóm tắt bài học", + "summarize_lesson_prompt": { + "lesson_only": "Tóm tắt những gì đã được dạy trong bài học này.", + "transcript_only": "Xem bản ghi của video đã tải lên và tóm tắt những gì được đề cập trong bản ghi.", + "transcript_and_notes": "Xem bản ghi của video đã tải lên và tóm tắt những gì được đề cập trong bản ghi. Đồng thời tóm tắt nội dung trong ghi chú bài học." + }, "button_done": "Xong", "tabs": { "settings": {