'use client'; import { useCallback, useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import { ArrowLeft, ArrowUp, CheckCircle2, ChevronRight, Layers, Loader2, RefreshCw, XCircle, } from 'lucide-react'; import { useI18n } from '@/lib/hooks/use-i18n'; import { Textarea } from '@/components/ui/textarea'; 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'; export interface CourseListItem { courseId: string; status: | 'queued' | 'framework_generating' | 'framework_ready' | 'generating' | 'completed' | 'failed' | 'cancelled'; title: string; moduleCount: number; succeededCount: number; error?: string; createdAt: string; updatedAt: string; } const STATUS_STYLES: Record = { queued: 'bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300', framework_generating: 'bg-blue-100 text-blue-700 dark:bg-blue-950/60 dark:text-blue-300', framework_ready: 'bg-amber-100 text-amber-700 dark:bg-amber-950/60 dark:text-amber-300', generating: 'bg-blue-100 text-blue-700 dark:bg-blue-950/60 dark:text-blue-300', completed: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-950/60 dark:text-emerald-300', failed: 'bg-red-100 text-red-700 dark:bg-red-950/60 dark:text-red-300', cancelled: 'bg-zinc-100 text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400', }; interface LargeCourseWorkbenchProps { embedded?: boolean; onSettingsOpen?: (section: SettingsSection) => void; } export function LargeCourseWorkbench({ embedded = false, onSettingsOpen, }: LargeCourseWorkbenchProps) { const { t } = useI18n(); const router = useRouter(); const [requirement, setRequirement] = useState(''); 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(null); const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [loadError, setLoadError] = useState(null); useEffect(() => { try { if (localStorage.getItem(WEB_SEARCH_STORAGE_KEY) === 'true') setWebSearch(true); } catch { /* localStorage unavailable */ } }, []); const updateWebSearch = (enabled: boolean) => { setWebSearch(enabled); try { localStorage.setItem(WEB_SEARCH_STORAGE_KEY, String(enabled)); } catch { /* localStorage unavailable */ } }; const load = useCallback(async () => { setLoading(true); setLoadError(null); try { 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}`); } setItems(body.items); } catch (err) { setLoadError(err instanceof Error ? err.message : String(err)); } finally { setLoading(false); } }, []); useEffect(() => { void load(); const timer = setInterval(() => void load(), 8000); return () => clearInterval(timer); }, [load]); const createCourse = async () => { if (!requirement.trim()) return; setCreating(true); setCreateError(null); try { const response = await opsApiFetch('/api/courses', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ requirement: requirement.trim(), enableWebSearch: webSearch || undefined, enableImageGeneration: enableImageGeneration || undefined, enableVideoGeneration: enableVideoGeneration || undefined, enableTTS, }), }); const body = (await response.json()) as { courseId?: string; error?: string; details?: string; }; if (!response.ok || !body.courseId) { throw new Error(body.details ?? body.error ?? `HTTP ${response.status}`); } router.push(`/courses/${body.courseId}`); } catch (err) { setCreateError(err instanceof Error ? err.message : String(err)); } finally { setCreating(false); } }; const capabilityOverrides: Partial< Record<'image' | 'video' | 'tts' | 'asr', MediaCapabilityOverride> > = { image: { enabled: enableImageGeneration, onToggle: setEnableImageGeneration }, video: { enabled: enableVideoGeneration, onToggle: setEnableVideoGeneration }, tts: { enabled: enableTTS, onToggle: setEnableTTS }, asr: { enabled: false, disabled: true }, }; const formatDate = (iso: string) => new Date(iso).toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' }); const content = ( <>

{t('course.createTitle')}

{t('course.createPlaceholder').split('\n')[1]}