feat: productionize learning engine and classroom

This commit is contained in:
inman
2026-08-16 21:44:52 +08:00
parent 2d04197f3f
commit ba35adfbfa
124 changed files with 9071 additions and 796 deletions

View File

@@ -18,6 +18,7 @@ import { Button } from '@/components/ui/button';
import { MediaPopover, type MediaCapabilityOverride } from '@/components/generation/media-popover';
import type { SettingsSection } from '@/lib/types/settings';
import { cn } from '@/lib/utils';
import { opsApiFetch } from '@/lib/ops/api-fetch';
const WEB_SEARCH_STORAGE_KEY = 'webSearchEnabled';
@@ -64,16 +65,13 @@ export function LargeCourseWorkbench({
const [webSearch, setWebSearch] = useState(false);
const [enableImageGeneration, setEnableImageGeneration] = useState(false);
const [enableVideoGeneration, setEnableVideoGeneration] = useState(false);
const [enableTTS, setEnableTTS] = useState(true);
const [creating, setCreating] = useState(false);
const [createError, setCreateError] = useState<string | null>(null);
const [items, setItems] = useState<CourseListItem[]>([]);
const [loading, setLoading] = useState(true);
const [loadError, setLoadError] = useState<string | null>(null);
// Large courses always keep narration enabled because publishing requires
// persisted speech audio for every module.
const enableTTS = true;
useEffect(() => {
try {
if (localStorage.getItem(WEB_SEARCH_STORAGE_KEY) === 'true') setWebSearch(true);
@@ -95,7 +93,7 @@ export function LargeCourseWorkbench({
setLoading(true);
setLoadError(null);
try {
const response = await fetch('/api/courses');
const response = await opsApiFetch('/api/courses');
const body = (await response.json()) as { items?: CourseListItem[] };
if (!response.ok || !Array.isArray(body.items)) {
throw new Error(`HTTP ${response.status}`);
@@ -119,7 +117,7 @@ export function LargeCourseWorkbench({
setCreating(true);
setCreateError(null);
try {
const response = await fetch('/api/courses', {
const response = await opsApiFetch('/api/courses', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -127,7 +125,7 @@ export function LargeCourseWorkbench({
enableWebSearch: webSearch || undefined,
enableImageGeneration: enableImageGeneration || undefined,
enableVideoGeneration: enableVideoGeneration || undefined,
enableTTS: enableTTS || undefined,
enableTTS,
}),
});
const body = (await response.json()) as {
@@ -151,9 +149,7 @@ export function LargeCourseWorkbench({
> = {
image: { enabled: enableImageGeneration, onToggle: setEnableImageGeneration },
video: { enabled: enableVideoGeneration, onToggle: setEnableVideoGeneration },
// Narration is a large-course publishing requirement, so keep it visible
// in the shared settings bar without allowing it to be switched off.
tts: { enabled: enableTTS, disabled: true },
tts: { enabled: enableTTS, onToggle: setEnableTTS },
asr: { enabled: false, disabled: true },
};

View File

@@ -28,7 +28,7 @@ import { useI18n } from '@/lib/hooks/use-i18n';
import { cn } from '@/lib/utils';
import type { SceneOutline } from '@/lib/types/generation';
import type { WidgetType } from '@/lib/types/widgets';
import { changeOutlineType } from '@openmaic/generation';
import { changeOutlineType } from '@openmaic/generation/outline-type';
import { countBlockingOutlines, validateOutline } from '@/lib/edit/content-validation';
type SceneType = SceneOutline['type'];