feat: HLS streaming, transcripts, agent course generation & landing-page course previews (#663)

New features:
- HLS video encoding/streaming (hls routes, hls-encoder) and a media
  transcripts pipeline (transcripts route + media-transcript queries)
- AI assistant chat vs. run model: agent-course-generation worker/queue,
  run-monitor pane, chat-changes card, content ask-ai bar
- Per-theme course-page previews across org landing-page themes (bold,
  classic, corporate, editorial, etc.) with a shared course token/component set
- View-as-student mode and login-link / link-invite invite flows

Refactor:
- Extract packages/core consolidating config (env, storage, dashboard-url),
  services and utils previously duplicated under apps/api/src/config
- Remove legacy agent service modules (chat-tools, chat-context,
  context-window, document, fetch-url, lesson-content)
- Relax Cross-Origin-Resource-Policy to allow cross-origin media embedding
- Add format script to tenant-router so turbo run format covers it
This commit is contained in:
Rotimi Best
2026-06-07 18:43:05 +01:00
committed by GitHub
parent a2e70779a0
commit d5b07d356e
496 changed files with 19050 additions and 8312 deletions
@@ -3,6 +3,7 @@
import { Badge } from '@cio/ui/base/badge';
import { Button } from '@cio/ui/base/button';
import { Progress } from '@cio/ui/base/progress';
import { Spinner } from '@cio/ui/base/spinner';
import { Empty } from '@cio/ui/custom/empty';
import { BlurFade } from '@cio/ui/custom/animation/blurfade';
import { ActivityCard } from '$features/ui';
@@ -259,7 +260,11 @@
</h2>
<div class="flex flex-1 flex-col space-y-3">
{#if highlightedCourses.length > 0}
{#if coursesApi.isLoading}
<div class="ui:text-muted-foreground flex flex-1 items-center justify-center py-16">
<Spinner class="size-6" />
</div>
{:else if highlightedCourses.length > 0}
{#each highlightedCourses as course}
{@const courseProgress = getStudentCourseProgressPercent(course)}
{@const totalItems = getCourseTotalItems(course)}
@@ -352,36 +357,42 @@
{$t('dashboard.compliance')}
</h2>
<article class="ui:bg-card ui:text-card-foreground flex flex-1 rounded border p-4">
<div class="flex items-center justify-between gap-4 lg:flex-col lg:items-start xl:flex-row xl:items-center">
<div class="flex min-w-0 items-center gap-4">
<div class="ui:bg-primary/10 flex size-12 shrink-0 items-center justify-center rounded">
<ClipboardCheckIcon class="ui:text-primary size-5" />
</div>
<div class="min-w-0">
<h3 class="font-semibold">{$t('dashboard.compliance_score')}</h3>
<p class="ui:text-muted-foreground mt-1 text-sm">
{#if complianceCourses.length > 0}
{$t('dashboard.required_courses_completed', {
completed: completedComplianceCourses.length,
total: complianceCourses.length
})}
{:else}
{$t('dashboard.compliance_score_empty')}
{/if}
</p>
</div>
</div>
<p class="ui:text-primary shrink-0 text-2xl font-semibold">
{#if complianceScore === null}
--
{:else}
{complianceScore}%
{/if}
</p>
{#if coursesApi.isLoading}
<div class="ui:text-muted-foreground flex flex-1 items-center justify-center rounded border py-16">
<Spinner class="size-6" />
</div>
</article>
{:else}
<article class="ui:bg-card ui:text-card-foreground flex flex-1 rounded border p-4">
<div class="flex items-center justify-between gap-4 lg:flex-col lg:items-start xl:flex-row xl:items-center">
<div class="flex min-w-0 items-center gap-4">
<div class="ui:bg-primary/10 flex size-12 shrink-0 items-center justify-center rounded">
<ClipboardCheckIcon class="ui:text-primary size-5" />
</div>
<div class="min-w-0">
<h3 class="font-semibold">{$t('dashboard.compliance_score')}</h3>
<p class="ui:text-muted-foreground mt-1 text-sm">
{#if complianceCourses.length > 0}
{$t('dashboard.required_courses_completed', {
completed: completedComplianceCourses.length,
total: complianceCourses.length
})}
{:else}
{$t('dashboard.compliance_score_empty')}
{/if}
</p>
</div>
</div>
<p class="ui:text-primary shrink-0 text-2xl font-semibold">
{#if complianceScore === null}
--
{:else}
{complianceScore}%
{/if}
</p>
</div>
</article>
{/if}
</section>
</div>
</div>