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
@@ -1,26 +1,29 @@
<script module lang="ts">
// Module-level singleton: one Promise shared across all mounted instances.
// Dynamic imports are already cached by the JS engine after the first load,
// but reusing the same Promise avoids redundant microtask chains on remounts.
let pending: Promise<typeof import('@cio/ui/custom/editor')> | null = null;
function loadEditor() {
pending ??= import('@cio/ui/custom/editor');
return pending;
}
</script>
<script lang="ts">
import { Editor } from '@cio/ui/custom/editor';
// Type-only imports are erased at build time — no static TipTap dependency.
import type { HTMLContent, TiptapEditor } from '@cio/ui/custom/editor';
interface Props {
placeholder?: string | ((node: any) => string);
// Content of the editor
content?: HTMLContent;
// Whether the toolbar should be visible
showToolBar?: boolean;
// Whether the editor is editable
editable?: boolean;
// Whether to enable localStorage persistence
enablePersistence?: boolean;
// localStorage key for content persistence
contentStorageKey?: string;
// localStorage key for editable state persistence
editableStorageKey?: string;
// CSS class for the editor wrapper
class?: string;
// CSS class for the editor itself
editorClass?: string;
// Callback functions
onChange?: (content: HTMLContent) => void;
onReady?: (editor: TiptapEditor) => void;
onEditorDestroy?: () => void;
@@ -42,17 +45,21 @@
}: Props = $props();
</script>
<Editor
{content}
{showToolBar}
{editable}
{enablePersistence}
{contentStorageKey}
{editableStorageKey}
class={className}
{editorClass}
{placeholder}
onContentChange={onChange}
onEditorReady={onReady}
{onEditorDestroy}
/>
{#await loadEditor()}
<div class="ui:animate-pulse ui:rounded-md ui:bg-muted ui:h-32 ui:w-full" />
{:then { Editor }}
<Editor
{content}
{showToolBar}
{editable}
{enablePersistence}
{contentStorageKey}
{editableStorageKey}
class={className}
{editorClass}
{placeholder}
onContentChange={onChange}
onEditorReady={onReady}
{onEditorDestroy}
/>
{/await}