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:
@@ -2,8 +2,6 @@ const WIDGET_ATTR = 'data-cio-widget';
|
||||
const WIDGET_VALUE = 'course-widget';
|
||||
const WIDGET_KEY_ATTR = 'data-widget-key';
|
||||
const API_BASE_ATTR = 'data-api-base-url';
|
||||
const LOADING_CLASS = 'cio-embed-loading';
|
||||
const EMBED_MIN_HEIGHT_PX = 360;
|
||||
|
||||
export type CourseWidgetEmbedApi = {
|
||||
mount: (element: HTMLElement, options: { publicKey: string; apiBaseUrl?: string }) => { destroy: () => void };
|
||||
@@ -18,50 +16,6 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
let stylesInjected = false;
|
||||
|
||||
function injectSpinnerStyles(): void {
|
||||
if (stylesInjected) return;
|
||||
stylesInjected = true;
|
||||
|
||||
const css = `
|
||||
@keyframes cio-embed-spin { to { transform: rotate(360deg); } }
|
||||
.${LOADING_CLASS} {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
min-height: ${EMBED_MIN_HEIGHT_PX}px;
|
||||
padding: 1rem;
|
||||
color: oklch(0.556 0 0);
|
||||
}
|
||||
.${LOADING_CLASS} svg {
|
||||
animation: cio-embed-spin 0.8s linear infinite;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.setAttribute('data-cio-embed', 'course-widget-loading');
|
||||
styleEl.textContent = css;
|
||||
document.head.appendChild(styleEl);
|
||||
}
|
||||
|
||||
const LOADER_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 2v4"/><path d="m16.24 7.76 2.83-2.83"/><path d="M22 12h-4"/><path d="m16.24 16.24 2.83 2.83"/><path d="M12 22v-4"/><path d="m7.76 16.24-2.83 2.83"/><path d="M2 12h4"/><path d="m7.76 7.76-2.83-2.83"/></svg>`;
|
||||
|
||||
function renderLoading(container: HTMLElement): void {
|
||||
injectSpinnerStyles();
|
||||
|
||||
const root = document.createElement('div');
|
||||
root.className = LOADING_CLASS;
|
||||
root.setAttribute('role', 'status');
|
||||
root.setAttribute('aria-live', 'polite');
|
||||
root.setAttribute('aria-label', 'Loading');
|
||||
root.innerHTML = LOADER_SVG;
|
||||
|
||||
container.replaceChildren(root);
|
||||
}
|
||||
|
||||
function collectTargets(): Array<{ element: HTMLElement; publicKey: string; apiBaseUrl?: string }> {
|
||||
return [...document.querySelectorAll<HTMLElement>(`[${WIDGET_ATTR}="${WIDGET_VALUE}"]`)]
|
||||
.map((element) => ({
|
||||
@@ -86,8 +40,6 @@ function manualMount(
|
||||
element: HTMLElement,
|
||||
options: { publicKey: string; apiBaseUrl?: string }
|
||||
): { destroy: () => void } {
|
||||
renderLoading(element);
|
||||
|
||||
let cancelled = false;
|
||||
let destroyWidget: (() => void) | null = null;
|
||||
|
||||
@@ -108,10 +60,6 @@ function manualMount(
|
||||
function init(): void {
|
||||
const targets = collectTargets();
|
||||
|
||||
for (const target of targets) {
|
||||
renderLoading(target.element);
|
||||
}
|
||||
|
||||
window.CIO = window.CIO ?? {};
|
||||
window.CIO.courseWidget = {
|
||||
mount: manualMount
|
||||
|
||||
Reference in New Issue
Block a user