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:
@@ -23,10 +23,10 @@
|
||||
];
|
||||
|
||||
const agents = [
|
||||
{ name: 'Cursor', logo: '/cursor-logo.png' },
|
||||
{ name: 'Claude Code', logo: '/claude-logo.svg' },
|
||||
{ name: 'Codex', logo: '/codex-logo.png' },
|
||||
{ name: 'OpenCode', logo: '/opencode-logo.svg' }
|
||||
{ name: 'Cursor', logo: 'https://assets.cdn.clsrio.com/www/home/cursor-logo.png' },
|
||||
{ name: 'Claude Code', logo: 'https://assets.cdn.clsrio.com/www/home/claude-logo.svg' },
|
||||
{ name: 'Codex', logo: 'https://assets.cdn.clsrio.com/www/home/codex-logo.png' },
|
||||
{ name: 'OpenCode', logo: 'https://assets.cdn.clsrio.com/www/home/opencode-logo.svg' }
|
||||
];
|
||||
</script>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<div class="mx-auto mt-10 w-full max-w-[1100px]">
|
||||
<div class="overflow-hidden rounded-2xl bg-white shadow-sm ring-1 ring-gray-200">
|
||||
<img
|
||||
src="/ai-course-builder.jpeg"
|
||||
src="https://assets.cdn.clsrio.com/www/home/ai-course-builder.jpeg"
|
||||
alt="Course builder — plan, approve, implement. Templates, in-editor chat, and MCP."
|
||||
class="block h-auto w-full"
|
||||
loading="lazy"
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
<div class="mx-auto mt-10 w-full max-w-[960px] overflow-hidden rounded-lg">
|
||||
<div style="padding:{videoAspect} 0 0 0;position:relative;">
|
||||
<iframe
|
||||
src="https://player.vimeo.com/video/{vimeoVideoId}?badge=0&autopause=0&player_id=0&app_id=58479&autoplay=1&muted=1&loop=1"
|
||||
src="https://player.vimeo.com/video/{vimeoVideoId}?badge=0&autopause=0&player_id=0&app_id=58479&loop=1"
|
||||
frameborder="0"
|
||||
allow="autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media; web-share"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
|
||||
@@ -145,6 +145,12 @@
|
||||
>Docs vs Company Academy</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="/public-courses"
|
||||
class="ui:text-muted-foreground ui:hover:text-foreground text-sm transition-colors">Public Courses</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/teach" class="ui:text-muted-foreground ui:hover:text-foreground text-sm transition-colors"
|
||||
>Teach with us</a
|
||||
@@ -177,7 +183,7 @@
|
||||
<ul class="space-y-3">
|
||||
<li>
|
||||
<a
|
||||
href="https://feedback.classroomio.com"
|
||||
href="https://feedback.classroomio.com/roadmap"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="ui:text-muted-foreground ui:hover:text-foreground text-sm transition-colors">Roadmap</a
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
<div class="relative z-30 mx-auto mt-14 w-full max-w-[1100px]">
|
||||
<div class="overflow-hidden rounded-2xl bg-white shadow-sm ring-1 ring-gray-200">
|
||||
<img
|
||||
src="/hero-academy.jpeg"
|
||||
src="https://assets.cdn.clsrio.com/www/home/hero-academy.jpeg"
|
||||
alt="Branded customer academy on a custom domain — catalog, programs, AI tutor"
|
||||
width="2560"
|
||||
height="1440"
|
||||
|
||||
@@ -27,7 +27,6 @@ export { default as ImagePlaceholder } from './image-placeholder.svelte';
|
||||
export { default as LanguageReel } from './language-reel.svelte';
|
||||
export { default as LearnerSection } from './learner-section.svelte';
|
||||
export { default as Modal } from './modal.svelte';
|
||||
export { default as MoreFeatures } from './more-features.svelte';
|
||||
export { default as McpServerSection } from './mcp-server-section.svelte';
|
||||
export { default as MultiAcademySection } from './multi-academy-section.svelte';
|
||||
export { default as Navigation } from './navigation.svelte';
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { fly } from 'svelte/transition';
|
||||
import { folder, globe, team, transcription } from '$lib/emojis';
|
||||
import { tools } from '$lib/utils/constants/tools';
|
||||
|
||||
let selected = $state(0);
|
||||
let interval: NodeJS.Timer = $state();
|
||||
|
||||
const moreFeatures = [
|
||||
{
|
||||
icon: globe,
|
||||
title: 'Customizable Landing Page',
|
||||
description: 'A minimalistic landing page template out of the box to share your courses with the world',
|
||||
image: '/landing-page-builder.webp'
|
||||
},
|
||||
{
|
||||
icon: team,
|
||||
title: 'Team management',
|
||||
description: 'Extend invitations and manage your teaching institution seamlessly through ClassroomIO.',
|
||||
image: '/team-management.png'
|
||||
},
|
||||
// {
|
||||
// icon: transcription,
|
||||
// title: 'Auto Video Transcription',
|
||||
// description:
|
||||
// 'AI automatically transcribes videos, making content searchable for seamless student access.',
|
||||
// image: '/video-transcription.png'
|
||||
// },
|
||||
{
|
||||
icon: folder,
|
||||
title: 'Downloadable Lesson PDF',
|
||||
description: 'Students can download lessons automatically for offline access.',
|
||||
image: '/download-pdf.png'
|
||||
}
|
||||
];
|
||||
|
||||
const freeTools = tools.filter((item) => item.showFeature);
|
||||
|
||||
onMount(() => {
|
||||
if (window.innerWidth > 768) {
|
||||
interval = setInterval(() => {
|
||||
selected = moreFeatures[selected + 1] ? selected + 1 : 0;
|
||||
}, 3000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- More Features -->
|
||||
<section id="morefeatures" class="bg-gray-50 px-3 py-[10%] lg:px-0">
|
||||
<div class="mx-0 lg:mx-[12%]">
|
||||
<div class="mx-4 mb-14 w-full lg:mx-0 lg:w-4/5">
|
||||
<h2 class="mt-2 text-3xl tracking-tight">There is More...</h2>
|
||||
<p class="mt-4 w-[94%] text-lg text-gray-500 lg:w-4/5">
|
||||
ClassroomIO is packed with useful features while we try to make it easy to use.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col-reverse gap-3 md:gap-10 lg:flex-row" id="image-container">
|
||||
<div class="mt-5 flex w-[95%] overflow-auto p-3 lg:mt-0 lg:block lg:w-[45%] lg:overflow-hidden">
|
||||
{#each moreFeatures as moreFeature, i}
|
||||
<button
|
||||
class="rounded-md p-4 {i === selected &&
|
||||
'selected'} mb-3 min-w-[75vw] cursor-pointer text-start lg:min-w-[unset]"
|
||||
onclick={() => {
|
||||
selected = i;
|
||||
clearInterval(interval);
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center gap-1">
|
||||
<img loading="lazy" width="32" height="32" src={moreFeature.icon} alt="" class="w-8" />
|
||||
<p class="text-lg">
|
||||
{moreFeature.title}
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-sm">{moreFeature.description}</p>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- Video -->
|
||||
<div class="h-full w-full p-3 lg:w-2/3 lg:p-0">
|
||||
{#key selected}
|
||||
<img
|
||||
width="100%"
|
||||
height="100%"
|
||||
loading="lazy"
|
||||
id="image"
|
||||
in:fly={{ x: 100, duration: 800 }}
|
||||
class="flex w-full flex-col items-center"
|
||||
src={moreFeatures[selected].image}
|
||||
alt={moreFeatures[selected].title}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
#morefeatures .selected {
|
||||
background-image: linear-gradient(45deg, rgb(253, 239, 132), rgb(247, 198, 169), rgb(21, 186, 196));
|
||||
}
|
||||
</style>
|
||||
@@ -105,10 +105,10 @@
|
||||
href: '/mcp-recipes'
|
||||
},
|
||||
{
|
||||
key: 'github',
|
||||
title: 'GitHub',
|
||||
subtitle: 'Star, read the source, fork.',
|
||||
href: '/github'
|
||||
key: 'storybook',
|
||||
title: 'Storybook',
|
||||
subtitle: 'Browse and reuse UI components.',
|
||||
href: 'https://ui.classroomio.dev'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -305,7 +305,12 @@
|
||||
|
||||
<div class="hidden! flex-row items-center justify-between gap-3 lg:flex!">
|
||||
<a href="/discord" target="_blank" class="flex items-center transition-opacity duration-200 hover:opacity-80">
|
||||
<img loading="lazy" alt="discord logo" src="/discord-blue.png" class="h-5 w-6 cursor-pointer" />
|
||||
<img
|
||||
loading="lazy"
|
||||
alt="discord logo"
|
||||
src="https://assets.cdn.clsrio.com/www/home/discord-blue.png"
|
||||
class="h-5 w-6 cursor-pointer"
|
||||
/>
|
||||
</a>
|
||||
<div class="flex items-center">
|
||||
<a
|
||||
@@ -316,7 +321,7 @@
|
||||
<img
|
||||
loading="lazy"
|
||||
alt="github logo"
|
||||
src="/github-mark.png"
|
||||
src="https://assets.cdn.clsrio.com/www/home/github-mark.png"
|
||||
class="h-5 w-5 cursor-pointer transition-transform duration-200 group-hover:scale-110"
|
||||
/>
|
||||
<span
|
||||
@@ -402,7 +407,12 @@
|
||||
target="_blank"
|
||||
class="flex w-full items-center rounded-md px-4 py-4 text-left text-sm transition-all duration-200 hover:bg-gray-100 md:text-lg"
|
||||
>
|
||||
<img loading="lazy" alt="discord logo" src="/discord-blue.png" class="mr-2 h-5 w-6 cursor-pointer" />
|
||||
<img
|
||||
loading="lazy"
|
||||
alt="discord logo"
|
||||
src="https://assets.cdn.clsrio.com/www/home/discord-blue.png"
|
||||
class="mr-2 h-5 w-6 cursor-pointer"
|
||||
/>
|
||||
<span>Discord</span>
|
||||
</a>
|
||||
<a
|
||||
@@ -413,7 +423,7 @@
|
||||
<img
|
||||
loading="lazy"
|
||||
alt="github logo"
|
||||
src="/github-mark.png"
|
||||
src="https://assets.cdn.clsrio.com/www/home/github-mark.png"
|
||||
class="h-5 w-5 cursor-pointer transition-transform duration-200 group-hover:scale-110"
|
||||
/>
|
||||
<span class="ml-3 transition-colors duration-200 group-hover:text-black">Github</span>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { dev } from '$app/environment';
|
||||
|
||||
const USERJOT_APP_ID = 'cm4a6vcmp00jpmdb5n66rmkzz';
|
||||
|
||||
let isInitialized = false;
|
||||
|
||||
function isWidgetAllowed(): boolean {
|
||||
return !dev;
|
||||
}
|
||||
|
||||
function ensureSdkLoaded(): void {
|
||||
if (window.uj) return;
|
||||
|
||||
window.$ujq = window.$ujq || [];
|
||||
window.uj =
|
||||
window.uj ||
|
||||
(new Proxy({} as Window['uj'], {
|
||||
get:
|
||||
(_, prop) =>
|
||||
(...args: unknown[]) =>
|
||||
window.$ujq.push([prop, ...args])
|
||||
}) as Window['uj']);
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.type = 'module';
|
||||
script.async = true;
|
||||
script.src = 'https://cdn.userjot.com/sdk/v2/uj.js';
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
export function initUserJot(): void {
|
||||
if (isInitialized) return;
|
||||
if (!isWidgetAllowed()) return;
|
||||
|
||||
ensureSdkLoaded();
|
||||
|
||||
window.uj.init(USERJOT_APP_ID, {
|
||||
trigger: 'custom',
|
||||
position: 'right',
|
||||
theme: 'auto'
|
||||
});
|
||||
|
||||
isInitialized = true;
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
$ujq: unknown[];
|
||||
uj: {
|
||||
init: (
|
||||
appId: string,
|
||||
options?: {
|
||||
widget?: boolean;
|
||||
position?: 'left' | 'right';
|
||||
theme?: 'auto' | 'light' | 'dark';
|
||||
}
|
||||
) => void;
|
||||
identify: (user: { id: string; email?: string; firstName?: string; lastName?: string; avatar?: string }) => void;
|
||||
[method: string]: (...args: unknown[]) => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
Reference in New Issue
Block a user