openlearn-next 0.7.5 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +3 -3
  2. package/dist/assets/{ActivityWorkspaceWidget-Dq4kjBS2.js → ActivityWorkspaceWidget-0DUqiqFK.js} +1 -1
  3. package/dist/assets/{AdminDirectoryView-B6OAs5Rj.js → AdminDirectoryView-DL9zZi-_.js} +1 -1
  4. package/dist/assets/{AssessmentCenter-B3f1DxSG.js → AssessmentCenter-B2JzXBGe.js} +1 -1
  5. package/dist/assets/{AssignmentFilePreview-DQbvbgnd.js → AssignmentFilePreview-Bk751Blz.js} +1 -1
  6. package/dist/assets/{BatchPickerModal-CoOWloSS.js → BatchPickerModal-9ekXDUUj.js} +1 -1
  7. package/dist/assets/{ClassesView-Q56Kf4eL.js → ClassesView-PCt8I3MU.js} +1 -1
  8. package/dist/assets/{ClassroomCountdownWidget-C6luhy8P.js → ClassroomCountdownWidget-HxelcHUp.js} +1 -1
  9. package/dist/assets/{CloudDriveModal-DLf4fbKR.js → CloudDriveModal-Mp59sr-C.js} +1 -1
  10. package/dist/assets/{ComputerLabView-D9-Tk7yA.js → ComputerLabView-FStILvFC.js} +1 -1
  11. package/dist/assets/{CourseManagement-DNSfyre-.js → CourseManagement-BQw2idgZ.js} +1 -1
  12. package/dist/assets/{CourseWizardModal-DAueHXcl.js → CourseWizardModal-B7-e8otR.js} +1 -1
  13. package/dist/assets/{Dashboard-qMVadkd2.js → Dashboard-D3LXLQNC.js} +1 -1
  14. package/dist/assets/{ExportWeightModal-Dkh7OC8U.js → ExportWeightModal-vd7XgaJ0.js} +1 -1
  15. package/dist/assets/{HelpView-CTF6B9ml.js → HelpView-C4BHoNqR.js} +1 -1
  16. package/dist/assets/{ImportLessonsModal-Ra-OF_Vo.js → ImportLessonsModal-Y6uIIemO.js} +1 -1
  17. package/dist/assets/{InteractiveCoursewareViewer-BfHEDrCf.js → InteractiveCoursewareViewer-D9GzG28E.js} +1 -1
  18. package/dist/assets/{InteractiveWhiteboard-KBzmiNBG.js → InteractiveWhiteboard-DswIV-ES.js} +1 -1
  19. package/dist/assets/{LazyCourseware-BaZS8-i1.js → LazyCourseware-BEoTTg5h.js} +2 -2
  20. package/dist/assets/{LazyWhiteboard-DGyln3JC.js → LazyWhiteboard-CMBBxnFX.js} +2 -2
  21. package/dist/assets/{LessonEditorView-B5JXYyup.js → LessonEditorView-BgJtG8Ks.js} +1 -1
  22. package/dist/assets/{LiveClassroomView-DezAlve2.js → LiveClassroomView-Czwt5WW-.js} +1 -1
  23. package/dist/assets/{NotificationDetailModal-DbMMrIHQ.js → NotificationDetailModal-D42vEs3S.js} +1 -1
  24. package/dist/assets/{PluginView-CvIr-ANj.js → PluginView-BcOWIjLu.js} +1 -1
  25. package/dist/assets/{QuizGeneratorModal-CTj2moWM.js → QuizGeneratorModal-DiItok6z.js} +1 -1
  26. package/dist/assets/{StudentAssignmentEvalPanel-DrfZT8Lr.js → StudentAssignmentEvalPanel-DHoZc3g0.js} +1 -1
  27. package/dist/assets/{StudentAssignmentView-D_EhblVQ.js → StudentAssignmentView-UK_7_MF3.js} +1 -1
  28. package/dist/assets/{StudentLessonView-BHG50-4M.js → StudentLessonView-C11Qkdi_.js} +1 -1
  29. package/dist/assets/{StudentView-DKDhnJQx.js → StudentView-j0hx3IOi.js} +2 -2
  30. package/dist/assets/{SystemResourceLibraryModal-C5y8H5Pf.js → SystemResourceLibraryModal-dEdNKbk2.js} +1 -1
  31. package/dist/assets/{TeacherView-CWDK3DOe.js → TeacherView-COZw2tFl.js} +2 -2
  32. package/dist/assets/{TimetableView-C8kdJlf7.js → TimetableView-AkReskNG.js} +1 -1
  33. package/dist/assets/{index-BBznBaN5.js → index-DN3E-Fmw.js} +4 -4
  34. package/dist/assets/{pluginParsers-DkiO-BBu.js → pluginParsers-k90WUDrU.js} +1 -1
  35. package/dist/assets/{student-workspace-1yVTbWHC.js → student-workspace-BDw1i4Vl.js} +1 -1
  36. package/dist/index.html +1 -1
  37. package/dist/server.cjs +65 -27
  38. package/package.json +1 -1
@@ -1 +1 @@
1
- import{a3 as _,a4 as l}from"./index-BBznBaN5.js";const L=s=>{const a=[];try{const i=s.match(/manifest\s*:\s*\{([\s\S]*?)\}(?:\s*,|\s*\})/),c=i?i[1]:s,m=c.match(/id\s*:\s*['"]([^'"]+)['"]/)||s.match(/id\s*:\s*['"]([^'"]+)['"]/),p=c.match(/name\s*:\s*['"]([^'"]+)['"]/)||s.match(/name\s*:\s*['"]([^'"]+)['"]/),o=c.match(/version\s*:\s*['"]([^'"]+)['"]/)||s.match(/version\s*:\s*['"]([^'"]+)['"]/),v=c.match(/description\s*:\s*['"]([^'"]+)['"]/)||s.match(/description\s*:\s*['"]([^'"]+)['"]/),h=c.match(/author\s*:\s*['"]([^'"]+)['"]/)||s.match(/author\s*:\s*['"]([^'"]+)['"]/);let r=[];const u=c.match(/capabilitiesProposed\s*:\s*\[([\s\S]*?)\]/)||s.match(/capabilitiesProposed\s*:\s*\[([\s\S]*?)\]/);u&&(r=u[1].split(",").map(t=>t.replace(/['"\s]/g,"")).filter(t=>t.length>0));const w={id:m?.[1]||void 0,name:p?.[1]||void 0,version:o?.[1]||void 0,description:v?.[1]||void 0,author:h?.[1]||void 0,capabilitiesProposed:r.length>0?r:void 0},x=/actionRegistry\.register\s*\(\s*\{([\s\S]*?)\}\s*\)/g;let f;const g=s;for(;(f=x.exec(g))!==null;){const t=f[1],n=t.match(/id\s*:\s*['"]([^'"]+)['"]/),e=t.match(/commandType\s*:\s*['"]([^'"]+)['"]/),d=t.match(/description\s*:\s*['"]([^'"]+)['"]/);(n||e)&&a.push({id:n?n[1]:"unknown",commandType:e?e[1]:"unknown",description:d?d[1]:""})}return{manifest:w,actions:a,error:null}}catch(i){return{manifest:null,actions:[],error:i.toString()}}},y=s=>{const a=s.split(/\r?\n/);if(a.length<2)return[];const i=a[0],c=[",",";"," "];let m=",",p=0;c.forEach(t=>{const n=i.split(t).length;n>p&&(p=n,m=t)});const o=_(i,m).map(t=>t.toLowerCase().replace(/["'\r]/g,"").trim()),v=l(o,["学生学号","student_number","studentnumber","student number","student_id","studentid","学号","编号"]),h=l(o,["学生姓名","student_name","studentname","student name","姓名","学生","name"]),r=l(o,["学生邮箱","student_email","studentemail","student email","邮箱","email","mail"]),u=l(o,["初始密码","initial password","password","passwd","pwd","密码"]),w=l(o,["学生备注","student notes","private_notes","备注","note","notes","remark"]),x=h>=0?h:0,f=r>=0?r:1,g=[];for(let t=1;t<a.length;t++){if(!a[t].trim())continue;const n=_(a[t],m),e=b=>b>=0&&n[b]?n[b].replace(/["'\r]/g,"").trim():"",d=e(x);if(!d)continue;const I=e(v),S=e(u),M=e(w);g.push({name:d,email:e(f),...I?{student_number:I}:{},...S?{password:S}:{},...M?{private_notes:M}:{}})}return g};export{y as a,L as p};
1
+ import{a3 as _,a4 as l}from"./index-DN3E-Fmw.js";const L=s=>{const a=[];try{const i=s.match(/manifest\s*:\s*\{([\s\S]*?)\}(?:\s*,|\s*\})/),c=i?i[1]:s,m=c.match(/id\s*:\s*['"]([^'"]+)['"]/)||s.match(/id\s*:\s*['"]([^'"]+)['"]/),p=c.match(/name\s*:\s*['"]([^'"]+)['"]/)||s.match(/name\s*:\s*['"]([^'"]+)['"]/),o=c.match(/version\s*:\s*['"]([^'"]+)['"]/)||s.match(/version\s*:\s*['"]([^'"]+)['"]/),v=c.match(/description\s*:\s*['"]([^'"]+)['"]/)||s.match(/description\s*:\s*['"]([^'"]+)['"]/),h=c.match(/author\s*:\s*['"]([^'"]+)['"]/)||s.match(/author\s*:\s*['"]([^'"]+)['"]/);let r=[];const u=c.match(/capabilitiesProposed\s*:\s*\[([\s\S]*?)\]/)||s.match(/capabilitiesProposed\s*:\s*\[([\s\S]*?)\]/);u&&(r=u[1].split(",").map(t=>t.replace(/['"\s]/g,"")).filter(t=>t.length>0));const w={id:m?.[1]||void 0,name:p?.[1]||void 0,version:o?.[1]||void 0,description:v?.[1]||void 0,author:h?.[1]||void 0,capabilitiesProposed:r.length>0?r:void 0},x=/actionRegistry\.register\s*\(\s*\{([\s\S]*?)\}\s*\)/g;let f;const g=s;for(;(f=x.exec(g))!==null;){const t=f[1],n=t.match(/id\s*:\s*['"]([^'"]+)['"]/),e=t.match(/commandType\s*:\s*['"]([^'"]+)['"]/),d=t.match(/description\s*:\s*['"]([^'"]+)['"]/);(n||e)&&a.push({id:n?n[1]:"unknown",commandType:e?e[1]:"unknown",description:d?d[1]:""})}return{manifest:w,actions:a,error:null}}catch(i){return{manifest:null,actions:[],error:i.toString()}}},y=s=>{const a=s.split(/\r?\n/);if(a.length<2)return[];const i=a[0],c=[",",";"," "];let m=",",p=0;c.forEach(t=>{const n=i.split(t).length;n>p&&(p=n,m=t)});const o=_(i,m).map(t=>t.toLowerCase().replace(/["'\r]/g,"").trim()),v=l(o,["学生学号","student_number","studentnumber","student number","student_id","studentid","学号","编号"]),h=l(o,["学生姓名","student_name","studentname","student name","姓名","学生","name"]),r=l(o,["学生邮箱","student_email","studentemail","student email","邮箱","email","mail"]),u=l(o,["初始密码","initial password","password","passwd","pwd","密码"]),w=l(o,["学生备注","student notes","private_notes","备注","note","notes","remark"]),x=h>=0?h:0,f=r>=0?r:1,g=[];for(let t=1;t<a.length;t++){if(!a[t].trim())continue;const n=_(a[t],m),e=b=>b>=0&&n[b]?n[b].replace(/["'\r]/g,"").trim():"",d=e(x);if(!d)continue;const I=e(v),S=e(u),M=e(w);g.push({name:d,email:e(f),...I?{student_number:I}:{},...S?{password:S}:{},...M?{private_notes:M}:{}})}return g};export{y as a,L as p};
@@ -1 +1 @@
1
- import{r as d,j as r,R as x}from"./vendor-react-Kv_JATwr.js";import{E as I,h as M}from"./index-BBznBaN5.js";import{L as A}from"./LazyWhiteboard-DGyln3JC.js";import{S as W}from"./StudentAssignmentEvalPanel-DrfZT8Lr.js";import{an as C,o as z}from"./vendor-icons-hc6MjYFv.js";import{ActivityWorkspaceWidget as P}from"./ActivityWorkspaceWidget-Dq4kjBS2.js";import"./vendor-utils-aM7aYvPP.js";import"./vendor-pdf-By0fofA0.js";import"./vendor-motion-BFAPOs-b.js";import"./vendor-charts-SrpBsvM3.js";import"./AssignmentFilePreview-DQbvbgnd.js";class w{constructor(){this.providers=new Map}register(e){if(!e||!e.id)throw new Error("WorkspaceSlotRegistry Error: WorkspaceSlotProvider must have a valid ID.");this.providers.set(e.id,e)}unregister(e){return this.providers.delete(e)}getProviders(e){const t=Array.from(this.providers.values()).filter(s=>s.slot===e);return t.sort((s,i)=>(i.priority??0)-(s.priority??0)),Object.freeze(t)}listAll(){return Object.freeze(Array.from(this.providers.values()))}clear(){this.providers.clear()}}const k=d.createContext(null),N=({registry:n,children:e})=>{const[t]=d.useState(()=>n??new w),[,s]=d.useState(0),i=d.useCallback(()=>{s(l=>l+1)},[]),a=d.useCallback(l=>{t.register(l),i()},[t,i]),c=d.useCallback(l=>{t.unregister(l)&&i()},[t,i]),o=d.useCallback(l=>t.getProviders(l),[t]),g=d.useMemo(()=>({registry:t,registerSlotProvider:a,unregisterSlotProvider:c,getSlotProviders:o}),[t,a,c,o]);return r.jsx(k.Provider,{value:g,children:e})},_=()=>{const n=d.useContext(k);if(!n)throw new Error("useWorkspace must be used within a WorkspaceProvider");return n},D=n=>{const{getSlotProviders:e}=_();return e(n)},f=({slot:n,className:e})=>{const t=D(n);return t.length===0?null:r.jsx("div",{className:`workspace-slot workspace-slot-${n.toLowerCase()} ${e??""}`,children:t.map(s=>r.jsx(x.Fragment,{children:s.render()},s.id))})},L=({className:n,children:e})=>r.jsxs("div",{className:`workspace-shell flex flex-col h-screen w-screen overflow-hidden bg-slate-900 text-slate-100 ${n??""}`,children:[r.jsx(f,{slot:"TopBar",className:"w-full flex-none z-30"}),r.jsxs("div",{className:"flex flex-1 overflow-hidden relative",children:[r.jsx(f,{slot:"LeftSidebar",className:"flex-none z-20"}),r.jsxs("main",{className:"flex-1 relative overflow-hidden flex flex-col",children:[r.jsx(f,{slot:"MainCanvas",className:"flex-1 w-full h-full relative"}),e,r.jsx(f,{slot:"BottomPanel",className:"flex-none z-20"})]}),r.jsx(f,{slot:"RightSidebar",className:"flex-none z-20"})]}),r.jsx(f,{slot:"StatusBar",className:"w-full flex-none z-30"}),r.jsx(f,{slot:"FloatingArea",className:"absolute inset-0 pointer-events-none z-40"}),r.jsx(f,{slot:"DialogArea",className:"absolute inset-0 pointer-events-none z-50"})]});var b=(n=>(n[n.Immediate=0]="Immediate",n[n.High=1]="High",n[n.Normal=2]="Normal",n[n.Low=3]="Low",n[n.Idle=4]="Idle",n))(b||{});class E{constructor(){this.subscribers=new Map}subscribe(e,t){const s=String(e);this.subscribers.has(s)||this.subscribers.set(s,new Set);const i=this.subscribers.get(s);return i.add(t),()=>{i.delete(t)}}async publish(e,t,s="runtime.system"){const i={id:`evt_${globalThis.crypto.randomUUID()}`,type:e,payload:t,source:s,timestamp:Date.now()},a=this.subscribers.get(String(e))||new Set,c=this.subscribers.get("*")||new Set,o=[...Array.from(a),...Array.from(c)];return await Promise.all(o.map(g=>Promise.resolve(g(i)).catch(l=>{console.error(`[RuntimeEventBus] Error in subscriber for ${String(e)}:`,l)}))),i}clear(){this.subscribers.clear()}}class O{constructor(e=`rt_${globalThis.crypto.randomUUID()}`){this.listeners=new Set,this.currentState={runtime:{id:e,lifecycle:"Create",startTime:Date.now(),elapsedTime:0},lesson:{},stage:{index:0},activity:{},whiteboard:{objectCount:0,isLocked:!1},teachingObjects:[],students:[],plugin:[],ai:{isGenerating:!1},analytics:{totalInteractions:0,activeStudentCount:0,averageScore:0}}}getState(){return this.currentState}setState(e){this.currentState=Object.freeze(e),this.notify()}updateState(e){this.currentState=Object.freeze({...this.currentState,...e,runtime:e.runtime?{...this.currentState.runtime,...e.runtime}:this.currentState.runtime,lesson:e.lesson?{...this.currentState.lesson,...e.lesson}:this.currentState.lesson,stage:e.stage?{...this.currentState.stage,...e.stage}:this.currentState.stage,activity:e.activity?{...this.currentState.activity,...e.activity}:this.currentState.activity,whiteboard:e.whiteboard?{...this.currentState.whiteboard,...e.whiteboard}:this.currentState.whiteboard,ai:e.ai?{...this.currentState.ai,...e.ai}:this.currentState.ai,analytics:e.analytics?{...this.currentState.analytics,...e.analytics}:this.currentState.analytics}),this.notify()}setLifecycle(e){this.updateState({runtime:{...this.currentState.runtime,lifecycle:e}})}addStudent(e){this.currentState.students.some(s=>s.id===e.id)||this.updateState({students:Object.freeze([...this.currentState.students,e])})}removeStudent(e){this.updateState({students:Object.freeze(this.currentState.students.filter(t=>t.id!==e))})}subscribe(e){return this.listeners.add(e),e(this.getState()),()=>{this.listeners.delete(e)}}notify(){const e=this.getState();for(const t of this.listeners)try{t(e)}catch(s){console.error("[RuntimeStateManager] Listener error:",s)}}}class ${constructor(){this.services=new Map}registerService(e){if(!e.serviceId)throw new Error("[RuntimeServiceRegistry] Service must provide a non-empty serviceId.");this.services.set(e.serviceId,e)}async unregisterService(e){const t=this.services.get(e);return t?(await t.dispose(),this.services.delete(e)):!1}getService(e){return this.services.get(e)}listServices(){return Object.freeze(Array.from(this.services.keys()))}async initializeAll(e){for(const t of this.services.values())await t.initialize(e)}async disposeAll(){for(const e of this.services.values())try{await e.dispose()}catch(t){console.error(`[RuntimeServiceRegistry] Error disposing service ${e.serviceId}:`,t)}this.services.clear()}}class B{constructor(){this.modules=new Map,this.activeModuleIds=new Set}registerModule(e){if(!e.id)throw new Error("[RuntimeModuleRegistry] Module must specify a valid id.");this.modules.set(e.id,e)}async unregisterModule(e,t){const s=this.modules.get(e);return s?(this.activeModuleIds.has(e)&&t&&(await s.stop(t),this.activeModuleIds.delete(e)),await s.dispose(),this.modules.delete(e)):!1}getModule(e){return this.modules.get(e)}listModules(){return Object.freeze(Array.from(this.modules.values()))}async startAll(e){for(const t of this.modules.values())await t.initialize(e),await t.start(e),this.activeModuleIds.add(t.id)}async stopAll(e){for(const t of this.activeModuleIds){const s=this.modules.get(t);if(s)try{await s.stop(e)}catch(i){console.error(`[RuntimeModuleRegistry] Error stopping module ${t}:`,i)}}this.activeModuleIds.clear()}}class U{constructor(){this.taskQueue=[],this.isProcessing=!1,this.timer=null}schedule(e,t,s=b.Normal,i=0,a=3){return new Promise((c,o)=>{const g={id:`tsk_${globalThis.crypto.randomUUID()}`,name:e,priority:s,taskFn:async()=>{i>0&&await new Promise(l=>setTimeout(l,i));try{const l=await t();return c(l),l}catch(l){throw o(l),l}},delayMs:i,maxRetries:a,createdAt:Date.now()};this.taskQueue.push(g),this.sortQueue(),queueMicrotask(()=>{this.processQueue()})})}start(){this.timer||(this.timer=setInterval(()=>{this.processQueue()},50))}stop(){this.timer&&(clearInterval(this.timer),this.timer=null)}getPendingTaskCount(){return this.taskQueue.length}sortQueue(){this.taskQueue.sort((e,t)=>e.priority-t.priority||e.createdAt-t.createdAt)}async processQueue(){if(this.isProcessing||this.taskQueue.length===0)return;this.isProcessing=!0;const e=this.taskQueue.shift();if(e){let t=0;const s=e.maxRetries??1;for(;t<s;)try{await e.taskFn();break}catch(i){t+=1,t>=s&&console.error(`[RuntimeScheduler] Task ${e.name} (${e.id}) failed after ${t} attempts:`,i)}}this.isProcessing=!1}}class T{constructor(){this.rolePermissions=new Map([["Teacher",new Set(["lesson:control","stage:navigate","whiteboard:draw","quiz:submit","plugin:execute","ai:invoke","session:manage"])],["Assistant",new Set(["stage:navigate","whiteboard:draw","quiz:submit","plugin:execute","ai:invoke"])],["Student",new Set(["whiteboard:draw","quiz:submit","ai:invoke","plugin:execute"])],["Observer",new Set([])],["Plugin",new Set(["whiteboard:draw","plugin:execute","ai:invoke"])],["AI",new Set(["stage:navigate","whiteboard:draw","ai:invoke"])]])}hasPermission(e,t){const s=this.rolePermissions.get(e);return s?s.has(t):!1}grantPermission(e,t){this.rolePermissions.has(e)||this.rolePermissions.set(e,new Set),this.rolePermissions.get(e).add(t)}revokePermission(e,t){const s=this.rolePermissions.get(e);s&&s.delete(t)}getRolePermissions(e){const t=this.rolePermissions.get(e);return Object.freeze(t?Array.from(t):[])}}class F{constructor(){this.resources=new Map}registerResource(e,t,s){const i=Array.from(this.resources.values()).find(c=>c.url===e);if(i)return i;const a={id:`res_${globalThis.crypto.randomUUID()}`,type:t,url:e,sizeBytes:s,status:"loaded",cachedAt:Date.now()};return this.resources.set(a.id,a),a}getResource(e){return this.resources.get(e)}listResources(e){const t=Array.from(this.resources.values());return Object.freeze(e?t.filter(s=>s.type===e):t)}removeResource(e){return this.resources.delete(e)}clear(){this.resources.clear()}}class H{constructor(){this.snapshots=new Map}takeSnapshot(e,t,s,i){const a={snapshotId:`snap_${globalThis.crypto.randomUUID()}`,timestamp:Date.now(),stateTree:JSON.parse(JSON.stringify(e)),activeServices:[...t],loadedModules:[...s],resources:[...i]};return this.snapshots.set(a.snapshotId,a),this.lastAutoSnapshot=a,a}getLatestSnapshot(){return this.lastAutoSnapshot}getSnapshot(e){return this.snapshots.get(e)}clear(){this.snapshots.clear(),this.lastAutoSnapshot=void 0}}class Q{constructor(){this.hooks=new Map}registerHook(e,t){this.hooks.has(e)||this.hooks.set(e,new Set);const s=this.hooks.get(e);return s.add(t),()=>{s.delete(t)}}async executeHook(e,t,s){const i=this.hooks.get(e);if(!(!i||i.size===0))for(const a of i)try{await a(t,s)}catch(c){console.error(`[RuntimeHooksManager] Error executing hook "${e}":`,c)}}clear(){this.hooks.clear()}}class J{constructor(e,t){this.activeSession=null,this.eventBus=e,this.stateManager=t}async createSession(e,t,s,i){this.activeSession&&this.activeSession.status==="active"&&await this.destroySession();const a={sessionId:e,courseId:s,lessonId:i,teacher:t,students:Object.freeze([]),createdAt:Date.now(),status:"active"};return this.activeSession=a,this.stateManager.updateState({lesson:{activeLessonId:i,status:"ready"}}),await this.eventBus.publish("SessionCreated",{sessionId:e,teacherId:t.id,timestamp:Date.now()}),a}async joinSession(e){if(!this.activeSession||this.activeSession.status!=="active")return!1;const t=[...this.activeSession.students.filter(s=>s.id!==e.id),{...e,isOnline:!0}];return this.activeSession={...this.activeSession,students:Object.freeze(t)},this.stateManager.addStudent(e),await this.eventBus.publish("StudentJoined",{student:e,timestamp:Date.now()}),!0}async leaveSession(e){if(!this.activeSession||this.activeSession.status!=="active")return!1;const t=this.activeSession.students.map(s=>s.id===e?{...s,isOnline:!1}:s);return this.activeSession={...this.activeSession,students:Object.freeze(t)},this.stateManager.removeStudent(e),await this.eventBus.publish("StudentLeft",{studentId:e,timestamp:Date.now()}),!0}async reconnectSession(e){if(!this.activeSession||this.activeSession.status!=="active")return!1;const t=this.activeSession.students.find(i=>i.id===e);if(!t)return!1;const s={...t,isOnline:!0};return this.joinSession(s)}async destroySession(){this.activeSession&&(this.activeSession={...this.activeSession,status:"destroyed"},this.activeSession=null)}getActiveSession(){return this.activeSession}}class q{constructor(){this.eventCount=0}incrementEventCount(){this.eventCount+=1}getDiagnostics(e,t,s,i){const a=process.memoryUsage?process.memoryUsage().heapUsed/1048576:0;return{runtimeId:e.runtime.id,lifecycleState:e.runtime.lifecycle,activeServicesCount:t,registeredModulesCount:s,pendingSchedulerTasksCount:i,activeStudentsCount:e.students.length,totalInteractionsCount:e.analytics.totalInteractions,memoryUsageMB:Math.round(a*100)/100,timestamp:Date.now()}}}class K{constructor(e=`rt_${globalThis.crypto.randomUUID()}`){this.currentRole="Teacher",this.runtimeId=e,this.eventBus=new E,this.stateManager=new O(e),this.serviceRegistry=new $,this.moduleRegistry=new B,this.scheduler=new U,this.permissionManager=new T,this.resourceManager=new F,this.snapshotManager=new H,this.hooksManager=new Q,this.sessionManager=new J(this.eventBus,this.stateManager),this.monitor=new q,this.stateManager.setLifecycle("Create")}getContext(){const e=this.sessionManager.getActiveSession();return{runtimeId:this.runtimeId,sessionId:e?.sessionId||"sess_default",courseId:e?.courseId,lessonId:e?.lessonId,teacher:e?.teacher,currentUser:this.currentUser||e?.teacher,role:this.currentRole,permissions:this.permissionManager.getRolePermissions(this.currentRole),lifecycleState:this.stateManager.getState().runtime.lifecycle}}async initialize(){this.transitionState("Initialize"),this.scheduler.start();const e=this.getContext();await this.serviceRegistry.initializeAll(e),this.transitionState("Prepare")}async start(){const e=this.getContext();await this.hooksManager.executeHook("beforeLessonStart",{},e),this.transitionState("Running"),await this.moduleRegistry.startAll(e),await this.hooksManager.executeHook("afterLessonStart",{},e),await this.eventBus.publish("LessonStarted",{lessonId:e.lessonId||"les_active",timestamp:Date.now()}),this.takeSnapshot()}async pause(){this.transitionState("Pause"),this.scheduler.stop(),await this.eventBus.publish("RuntimePaused",{elapsedTime:this.stateManager.getState().runtime.elapsedTime,timestamp:Date.now()})}async resume(){this.transitionState("Resume"),this.scheduler.start(),this.transitionState("Running")}async stop(){const e=this.getContext();this.transitionState("Stop"),await this.moduleRegistry.stopAll(e),this.scheduler.stop(),this.takeSnapshot()}async dispose(){await this.stop(),this.transitionState("Dispose"),await this.serviceRegistry.disposeAll(),this.hooksManager.clear(),this.resourceManager.clear(),this.eventBus.clear()}transitionState(e){const t=this.stateManager.getState().runtime.lifecycle;this.stateManager.setLifecycle(e),this.eventBus.publish("LifecycleChanged",{from:t,to:e,timestamp:Date.now()})}registerModule(e){this.moduleRegistry.registerModule(e)}registerService(e){this.serviceRegistry.registerService(e)}registerRuntimeHook(e,t){return this.hooksManager.registerHook(e,t)}scheduleTask(e,t,s=b.Normal,i=0){return this.scheduler.schedule(e,t,s,i)}setUser(e){this.currentUser=e,this.currentRole=e.role}hasPermission(e){return this.permissionManager.hasPermission(this.currentRole,e)}takeSnapshot(){return this.snapshotManager.takeSnapshot(this.stateManager.getState(),this.serviceRegistry.listServices(),this.moduleRegistry.listModules().map(e=>e.id),this.resourceManager.listResources())}restoreLatestSnapshot(){const e=this.snapshotManager.getLatestSnapshot();return e?(this.stateManager.setState(e.stateTree),!0):!1}getDiagnostics(){return this.monitor.getDiagnostics(this.stateManager.getState(),this.serviceRegistry.listServices().length,this.moduleRegistry.listModules().length,this.scheduler.getPendingTaskCount())}}class V{constructor(e){this.init=e,this.kernel=new K,this.kernel.setUser({id:e.student.id,name:e.student.name,role:"Student",avatar:e.student.avatar,isOnline:!0,joinedAt:Date.now()})}getView(){const e=this.kernel.getContext();return{studentId:this.init.student.id,studentName:this.init.student.name,role:"Student",lessonId:this.init.lessonId??e.lessonId,courseId:this.init.courseId??e.courseId,permissions:e.permissions}}get permissions(){return this.kernel.getContext().permissions}hasPermission(e){return this.kernel.hasPermission(e)}subscribe(e,t){return this.kernel.eventBus.subscribe(e,s=>t(s.payload))}takeSnapshot(){return this.kernel.takeSnapshot()}restoreSnapshot(){return this.kernel.restoreLatestSnapshot()}get runtimeKernel(){return this.kernel}dispose(){return this.kernel.dispose()}}function G({isOpen:n,lang:e,activeStudentId:t,studentNotifications:s,readNotifications:i,setReadNotifications:a,studentDashboardData:c,setIsNotificationsOpen:o,setSelectedNotificationForModal:g}){if(!n)return null;const l=s.filter(u=>!i.has(u.id));return r.jsxs("div",{className:"absolute right-0 mt-2 w-80 bg-white border border-gray-200 shadow-lg rounded-xl z-50 overflow-hidden",children:[r.jsxs("div",{className:"p-3 border-b border-gray-100 flex justify-between items-center bg-gray-50",children:[r.jsx("h3",{className:"font-semibold text-gray-800",children:"Notifications"}),l.length>0&&r.jsx("button",{onClick:async()=>{if(!t)return;try{const h=s.filter(m=>!i.has(m.id)).map(m=>fetch(`/api/students/${t}/read_notifications`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({notificationId:m.id})}));await Promise.all(h)}catch(h){console.error(h)}const u=new Set(i);s.forEach(h=>u.add(h.id)),a(u)},className:"text-xs text-indigo-600 hover:text-indigo-800 font-medium",children:"Mark all as read"})]}),r.jsx("div",{className:"max-h-96 overflow-y-auto",children:s.length===0?r.jsx("div",{className:"p-4 text-center text-sm text-gray-500 italic",children:"No notifications."}):r.jsx("div",{className:"divide-y divide-gray-100",children:s.map(u=>{const h=!i.has(u.id);return r.jsx("div",{className:`p-3 hover:bg-gray-50 cursor-pointer ${h?"bg-indigo-50/30":""}`,onClick:()=>{if(h){t&&fetch(`/api/students/${t}/read_notifications`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({notificationId:u.id})}).catch(console.error);const S=new Set(i);S.add(u.id),a(S)}const m=c?.assignments?.find(S=>S.id===u.relatedId);g({...u,assignment:m}),o(!1)},children:r.jsxs("div",{className:"flex gap-3",children:[r.jsx("div",{className:"mt-0.5",children:u.type==="new_assignment"?r.jsx(C,{size:16,className:"text-indigo-500"}):r.jsx(z,{size:16,className:"text-green-500"})}),r.jsxs("div",{className:"flex-1",children:[r.jsx("div",{className:`text-sm ${h?"font-semibold text-gray-900":"font-medium text-gray-700"}`,children:u.title}),r.jsx("div",{className:`text-xs mt-0.5 ${h?"text-gray-600":"text-gray-500"}`,children:u.message})]}),h&&r.jsx("div",{className:"w-2 h-2 rounded-full bg-indigo-500 mt-1"})]})},u.id)})})})]})}const X=({widgetId:n="widget_ai_teacher_workspace",initialSection:e="Lesson Assistant",onActionExecute:t})=>{const[s,i]=d.useState({widgetId:n,visible:!0,pinned:!0,collapsed:!1,fullscreen:!1,dockPosition:"right",activeSection:e});if(!s.visible)return null;const a=["Lesson Assistant","Whiteboard Assistant","Resource Assistant","Activity Assistant","Student Assistant","Assessment Assistant","Summary Assistant","Plugin Assistant"],c=o=>{t&&t(o,{section:s.activeSection})};return r.jsxs("div",{"data-testid":"ai-teacher-workspace-widget",className:`ai-teacher-workspace-panel ${s.dockPosition} ${s.collapsed?"collapsed":""} ${s.fullscreen?"fullscreen":""}`,style:{border:"1px solid #e2e8f0",borderRadius:"8px",background:"#ffffff",padding:"12px",boxShadow:"0 4px 6px -1px rgba(0, 0, 0, 0.1)"},children:[r.jsxs("div",{className:"ai-widget-header",style:{display:"flex",justifyContent:"space-between",alignItems:"center",borderBottom:"1px solid #f1f5f9",paddingBottom:"8px",marginBottom:"8px"},children:[r.jsx("span",{style:{fontWeight:600,fontSize:"14px",color:"#1e293b"},children:"🤖 AI Teacher Assistant"}),r.jsxs("div",{className:"ai-widget-controls",style:{display:"flex",gap:"4px"},children:[r.jsx("button",{"data-testid":"btn-pin",onClick:()=>i(o=>({...o,pinned:!o.pinned})),style:{fontSize:"12px",cursor:"pointer"},children:s.pinned?"📌 Unpin":"📍 Pin"}),r.jsx("button",{"data-testid":"btn-dock",onClick:()=>i(o=>({...o,dockPosition:o.dockPosition==="right"?"float":"right"})),style:{fontSize:"12px",cursor:"pointer"},children:s.dockPosition==="right"?"🌊 Float":"📥 Dock"}),r.jsx("button",{"data-testid":"btn-fullscreen",onClick:()=>i(o=>({...o,fullscreen:!o.fullscreen})),style:{fontSize:"12px",cursor:"pointer"},children:s.fullscreen?"🗗 Restore":"⛶ Fullscreen"}),r.jsx("button",{"data-testid":"btn-collapse",onClick:()=>i(o=>({...o,collapsed:!o.collapsed})),style:{fontSize:"12px",cursor:"pointer"},children:s.collapsed?"➕ Expand":"➖ Collapse"})]})]}),!s.collapsed&&r.jsxs(r.Fragment,{children:[r.jsx("div",{className:"ai-section-tabs",style:{display:"flex",flexWrap:"wrap",gap:"4px",marginBottom:"12px"},children:a.map(o=>r.jsx("button",{"data-testid":`tab-${o.toLowerCase().replace(/\s+/g,"-")}`,onClick:()=>i(g=>({...g,activeSection:o})),style:{padding:"4px 8px",fontSize:"12px",borderRadius:"4px",border:"1px solid #cbd5e1",background:s.activeSection===o?"#3b82f6":"#f8fafc",color:s.activeSection===o?"#ffffff":"#334155",cursor:"pointer"},children:o},o))}),r.jsxs("div",{className:"ai-section-body",style:{minHeight:"100px"},children:[r.jsxs("div",{style:{fontSize:"13px",color:"#475569",marginBottom:"8px"},children:["Active Assistant: ",r.jsx("strong",{children:s.activeSection})]}),r.jsxs("div",{style:{display:"flex",gap:"8px",flexWrap:"wrap"},children:[r.jsx("button",{"data-testid":"btn-summarize",onClick:()=>c("ai_summarize_lesson"),style:{padding:"6px 12px",fontSize:"12px",background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:"4px",cursor:"pointer"},children:"📝 Summarize Content"}),r.jsx("button",{"data-testid":"btn-explain",onClick:()=>c("ai_explain_whiteboard"),style:{padding:"6px 12px",fontSize:"12px",background:"#f0fdf4",border:"1px solid #bbf7d0",borderRadius:"4px",cursor:"pointer"},children:"🎨 Explain Visuals"}),r.jsx("button",{"data-testid":"btn-quiz",onClick:()=>c("ai_generate_quiz"),style:{padding:"6px 12px",fontSize:"12px",background:"#fefce8",border:"1px solid #fef08a",borderRadius:"4px",cursor:"pointer"},children:"⚡ Generate Quiz"})]})]})]})]})};class Y{constructor(){this.resources=new Map,this.providers=new Map}registerProvider(e){if(!e||!e.id||!e.type)throw new Error("ResourceRegistry Error: ResourceProvider must have a valid ID and Type.");this.providers.set(e.type,e)}unregisterProvider(e){for(const[t,s]of this.providers.entries())if(s.id===e)return this.providers.delete(t);return!1}getProvider(e){return this.providers.get(e)}registerResource(e){if(!e||!e.id)throw new Error("ResourceRegistry Error: ResourceDescriptor must have a valid ID.");this.resources.set(e.id,{...e})}unregisterResource(e){return this.resources.delete(e)}getResource(e){return this.resources.get(e)}listResources(e){const t=Array.from(this.resources.values());return Object.freeze(e?t.filter(s=>s.type===e):t)}executeAction(e,t,s){const i=this.resources.get(e);if(!i)throw new Error(`ResourceRegistry Error: Resource '${e}' not found.`);const a=this.providers.get(i.type);switch(t){case"preview":return a?.preview?a.preview(i):{previewUrl:i.url};case"open":return a?.open?a.open(i):{openUrl:i.url};case"pin":return i.pinned=!i.pinned,{pinned:i.pinned};case"favorite":return i.favorited=!i.favorited,{favorited:i.favorited};case"annotate":return{annotate:!0,resourceId:e,params:s};case"share":return{shareUrl:i.url??`resource://${i.id}`};case"fullscreen":return{fullscreen:!0,resourceId:e};default:throw new Error(`ResourceRegistry Error: Unknown action '${t}'.`)}}clear(){this.resources.clear(),this.providers.clear()}}const y={padding:"12px",color:"#e2e8f0",fontSize:"13px"},Z=()=>r.jsx("div",{style:{width:"100%",height:"100%"},children:r.jsx(A,{})}),ee=({context:n})=>{const e=n.getView();return r.jsxs("div",{style:y,children:[r.jsx("div",{style:{fontWeight:600,marginBottom:6},children:"📘 Lesson"}),r.jsxs("div",{children:["Lesson ID: ",e.lessonId??"—"]}),r.jsxs("div",{children:["Course ID: ",e.courseId??"—"]})]})},te=()=>{const[n]=d.useState(()=>new Y().listResources());return r.jsxs("div",{style:y,children:[r.jsx("div",{style:{fontWeight:600,marginBottom:6},children:"📂 Resources"}),n.length===0?r.jsx("div",{style:{opacity:.7},children:"No resources shared yet."}):r.jsx("ul",{children:n.map(e=>r.jsxs("li",{children:[e.type,": ",e.url]},e.id))})]})},se=({studentId:n,lang:e="en"})=>r.jsx(P,{role:"student",actorId:n,lang:e}),ie=({studentId:n,lessonId:e,lang:t="en"})=>e?r.jsx(W,{lessonId:e,studentId:n,lang:t,addToast:()=>{}}):r.jsx("div",{style:y,children:"No active lesson — assignments unavailable."}),re=({context:n})=>{const[e,t]=d.useState([]);return d.useEffect(()=>{const s=n.subscribe("LessonStarted",a=>{t(c=>[...c,{id:`ls_${a.timestamp}`,text:`Lesson started (${a.lessonId})`}])}),i=n.subscribe("StageChanged",a=>{t(c=>[...c,{id:`sc_${a.timestamp}`,text:`Stage changed → ${a.toStageId}`}])});return()=>{s(),i()}},[n]),r.jsxs("div",{style:y,children:[r.jsx("div",{style:{fontWeight:600,marginBottom:6},children:"📣 Teacher Broadcast"}),e.length===0?r.jsx("div",{style:{opacity:.7},children:"No announcements yet."}):r.jsx("ul",{children:e.map(s=>r.jsx("li",{children:s.text},s.id))})]})},ne=()=>r.jsx(X,{}),ae=({studentId:n,lang:e="en"})=>{const[t,s]=d.useState(!0),[i,a]=d.useState(new Set),[c]=d.useState([]);return r.jsx(G,{isOpen:t,lang:e,activeStudentId:n,studentNotifications:c,readNotifications:i,setReadNotifications:a,studentDashboardData:null,setIsNotificationsOpen:s,setSelectedNotificationForModal:()=>{}})},oe=()=>r.jsx(I,{slot:"student.view"});class ce{constructor(e){this.registeredWidgets=new Map,this.slotRegistry=e??new w}get slotRegistryRef(){return this.slotRegistry}registerWidget(e){if(!e||!e.id)throw new Error("StudentWidgetRegistry Error: StudentWidgetDescriptor must have a valid ID.");this.registeredWidgets.set(e.id,e);const t=e.component;this.slotRegistry.register({id:e.id,slot:e.slot,priority:e.priority??10,render:s=>x.createElement(t,s)})}unregisterWidget(e){const t=this.registeredWidgets.delete(e);return t&&this.slotRegistry.unregister(e),t}registerDefaultWidgets(e){const{studentId:t,lessonId:s,lang:i="en",context:a,pluginHost:c}=e;this.mountWidget("widget_student_whiteboard","Whiteboard","MainCanvas",Z,{},"official"),this.mountWidget("widget_student_lesson","Lesson","LeftSidebar",ee,{context:a},"official"),this.mountWidget("widget_student_resources","Resources","LeftSidebar",te,{},"official"),this.mountWidget("widget_student_activities","Activities","BottomPanel",se,{},"official"),this.mountWidget("widget_student_assignments","Assignments","RightSidebar",ie,{studentId:t,lessonId:s,lang:i},"official",20),this.mountWidget("widget_student_ai_assistant","AI Learning Assistant","RightSidebar",ne,{},"official",10),this.mountWidget("widget_student_broadcast","Teacher Broadcast","BottomPanel",re,{context:a},"official"),this.mountWidget("widget_student_notifications","Notifications","FloatingArea",ae,{studentId:t,lang:i},"official"),c&&this.mountWidget("widget_student_plugins","Plugin Widgets","FloatingArea",oe,{},"plugin")}getWidget(e){return this.registeredWidgets.get(e)}listWidgets(){return Object.freeze(Array.from(this.registeredWidgets.values()))}clear(){for(const e of this.registeredWidgets.keys())this.slotRegistry.unregister(e);this.registeredWidgets.clear()}mountWidget(e,t,s,i,a,c,o=10){this.registeredWidgets.set(e,{id:e,name:t,slot:s,component:i,provider:c,priority:o}),this.slotRegistry.register({id:e,slot:s,priority:o,render:()=>x.createElement(i,a)})}}const R="openlearn_student_workspace_";function le(n){return{studentId:n,openedWidgets:[],layout:{},selectedResourceIds:[],activityState:{},aiConversation:[],updatedAt:Date.now()}}class de{constructor(e){this.key=`${R}${e}`}load(){try{if(typeof localStorage>"u")return null;const e=localStorage.getItem(this.key);if(!e)return null;const t=JSON.parse(e);return t.studentId!==this.studentIdFromKey()?null:t}catch{return null}}save(e){try{if(typeof localStorage>"u")return;localStorage.setItem(this.key,JSON.stringify({...e,updatedAt:Date.now()}))}catch{}}update(e){const s={...this.load()??le(this.studentIdFromKey()),...e,updatedAt:Date.now()};return this.save(s),s}clear(){try{if(typeof localStorage>"u")return;localStorage.removeItem(this.key)}catch{}}studentIdFromKey(){return this.key.slice(R.length)}}const be=n=>{const{student:e,lessonId:t,courseId:s,teacherId:i,teacherName:a,lang:c="en",autoRestore:o=!0,pluginHost:g}=n,[l]=d.useState(()=>new V({student:e,lessonId:t,courseId:s,teacherId:i,teacherName:a,lang:c})),[u]=d.useState(()=>new w),[h]=d.useState(()=>{const p=new ce(u);return p.registerDefaultWidgets({studentId:e.id,lessonId:t,lang:c,context:l,pluginHost:g}),p}),[m]=d.useState(()=>new de(e.id));d.useEffect(()=>{if(!o)return;l.restoreSnapshot();const p=m.load();p?.currentLessonId&&p.currentLessonId},[o,l,m]),d.useEffect(()=>{const p=l.subscribe("LessonStarted",v=>{m.update({currentLessonId:v.lessonId})});return()=>{p()}},[l,m]),d.useEffect(()=>()=>{l.takeSnapshot();const p=h.listWidgets().map(j=>j.id),v=m.load();m.save({studentId:e.id,openedWidgets:p,layout:v?.layout??{},currentLessonId:v?.currentLessonId??t,selectedResourceIds:v?.selectedResourceIds??[],activityState:v?.activityState??{},aiConversation:v?.aiConversation??[],updatedAt:Date.now()}),l.dispose()},[l,m,h,e.id,t]);const S=r.jsx(N,{registry:u,children:r.jsx(L,{})});return g?r.jsx(M,{host:g,children:S}):S};export{be as StudentWorkspace,be as default};
1
+ import{r as d,j as r,R as x}from"./vendor-react-Kv_JATwr.js";import{E as I,h as M}from"./index-DN3E-Fmw.js";import{L as A}from"./LazyWhiteboard-CMBBxnFX.js";import{S as W}from"./StudentAssignmentEvalPanel-DHoZc3g0.js";import{an as C,o as z}from"./vendor-icons-hc6MjYFv.js";import{ActivityWorkspaceWidget as P}from"./ActivityWorkspaceWidget-0DUqiqFK.js";import"./vendor-utils-aM7aYvPP.js";import"./vendor-pdf-By0fofA0.js";import"./vendor-motion-BFAPOs-b.js";import"./vendor-charts-SrpBsvM3.js";import"./AssignmentFilePreview-Bk751Blz.js";class w{constructor(){this.providers=new Map}register(e){if(!e||!e.id)throw new Error("WorkspaceSlotRegistry Error: WorkspaceSlotProvider must have a valid ID.");this.providers.set(e.id,e)}unregister(e){return this.providers.delete(e)}getProviders(e){const t=Array.from(this.providers.values()).filter(s=>s.slot===e);return t.sort((s,i)=>(i.priority??0)-(s.priority??0)),Object.freeze(t)}listAll(){return Object.freeze(Array.from(this.providers.values()))}clear(){this.providers.clear()}}const k=d.createContext(null),N=({registry:n,children:e})=>{const[t]=d.useState(()=>n??new w),[,s]=d.useState(0),i=d.useCallback(()=>{s(l=>l+1)},[]),a=d.useCallback(l=>{t.register(l),i()},[t,i]),c=d.useCallback(l=>{t.unregister(l)&&i()},[t,i]),o=d.useCallback(l=>t.getProviders(l),[t]),g=d.useMemo(()=>({registry:t,registerSlotProvider:a,unregisterSlotProvider:c,getSlotProviders:o}),[t,a,c,o]);return r.jsx(k.Provider,{value:g,children:e})},_=()=>{const n=d.useContext(k);if(!n)throw new Error("useWorkspace must be used within a WorkspaceProvider");return n},D=n=>{const{getSlotProviders:e}=_();return e(n)},f=({slot:n,className:e})=>{const t=D(n);return t.length===0?null:r.jsx("div",{className:`workspace-slot workspace-slot-${n.toLowerCase()} ${e??""}`,children:t.map(s=>r.jsx(x.Fragment,{children:s.render()},s.id))})},L=({className:n,children:e})=>r.jsxs("div",{className:`workspace-shell flex flex-col h-screen w-screen overflow-hidden bg-slate-900 text-slate-100 ${n??""}`,children:[r.jsx(f,{slot:"TopBar",className:"w-full flex-none z-30"}),r.jsxs("div",{className:"flex flex-1 overflow-hidden relative",children:[r.jsx(f,{slot:"LeftSidebar",className:"flex-none z-20"}),r.jsxs("main",{className:"flex-1 relative overflow-hidden flex flex-col",children:[r.jsx(f,{slot:"MainCanvas",className:"flex-1 w-full h-full relative"}),e,r.jsx(f,{slot:"BottomPanel",className:"flex-none z-20"})]}),r.jsx(f,{slot:"RightSidebar",className:"flex-none z-20"})]}),r.jsx(f,{slot:"StatusBar",className:"w-full flex-none z-30"}),r.jsx(f,{slot:"FloatingArea",className:"absolute inset-0 pointer-events-none z-40"}),r.jsx(f,{slot:"DialogArea",className:"absolute inset-0 pointer-events-none z-50"})]});var b=(n=>(n[n.Immediate=0]="Immediate",n[n.High=1]="High",n[n.Normal=2]="Normal",n[n.Low=3]="Low",n[n.Idle=4]="Idle",n))(b||{});class E{constructor(){this.subscribers=new Map}subscribe(e,t){const s=String(e);this.subscribers.has(s)||this.subscribers.set(s,new Set);const i=this.subscribers.get(s);return i.add(t),()=>{i.delete(t)}}async publish(e,t,s="runtime.system"){const i={id:`evt_${globalThis.crypto.randomUUID()}`,type:e,payload:t,source:s,timestamp:Date.now()},a=this.subscribers.get(String(e))||new Set,c=this.subscribers.get("*")||new Set,o=[...Array.from(a),...Array.from(c)];return await Promise.all(o.map(g=>Promise.resolve(g(i)).catch(l=>{console.error(`[RuntimeEventBus] Error in subscriber for ${String(e)}:`,l)}))),i}clear(){this.subscribers.clear()}}class O{constructor(e=`rt_${globalThis.crypto.randomUUID()}`){this.listeners=new Set,this.currentState={runtime:{id:e,lifecycle:"Create",startTime:Date.now(),elapsedTime:0},lesson:{},stage:{index:0},activity:{},whiteboard:{objectCount:0,isLocked:!1},teachingObjects:[],students:[],plugin:[],ai:{isGenerating:!1},analytics:{totalInteractions:0,activeStudentCount:0,averageScore:0}}}getState(){return this.currentState}setState(e){this.currentState=Object.freeze(e),this.notify()}updateState(e){this.currentState=Object.freeze({...this.currentState,...e,runtime:e.runtime?{...this.currentState.runtime,...e.runtime}:this.currentState.runtime,lesson:e.lesson?{...this.currentState.lesson,...e.lesson}:this.currentState.lesson,stage:e.stage?{...this.currentState.stage,...e.stage}:this.currentState.stage,activity:e.activity?{...this.currentState.activity,...e.activity}:this.currentState.activity,whiteboard:e.whiteboard?{...this.currentState.whiteboard,...e.whiteboard}:this.currentState.whiteboard,ai:e.ai?{...this.currentState.ai,...e.ai}:this.currentState.ai,analytics:e.analytics?{...this.currentState.analytics,...e.analytics}:this.currentState.analytics}),this.notify()}setLifecycle(e){this.updateState({runtime:{...this.currentState.runtime,lifecycle:e}})}addStudent(e){this.currentState.students.some(s=>s.id===e.id)||this.updateState({students:Object.freeze([...this.currentState.students,e])})}removeStudent(e){this.updateState({students:Object.freeze(this.currentState.students.filter(t=>t.id!==e))})}subscribe(e){return this.listeners.add(e),e(this.getState()),()=>{this.listeners.delete(e)}}notify(){const e=this.getState();for(const t of this.listeners)try{t(e)}catch(s){console.error("[RuntimeStateManager] Listener error:",s)}}}class ${constructor(){this.services=new Map}registerService(e){if(!e.serviceId)throw new Error("[RuntimeServiceRegistry] Service must provide a non-empty serviceId.");this.services.set(e.serviceId,e)}async unregisterService(e){const t=this.services.get(e);return t?(await t.dispose(),this.services.delete(e)):!1}getService(e){return this.services.get(e)}listServices(){return Object.freeze(Array.from(this.services.keys()))}async initializeAll(e){for(const t of this.services.values())await t.initialize(e)}async disposeAll(){for(const e of this.services.values())try{await e.dispose()}catch(t){console.error(`[RuntimeServiceRegistry] Error disposing service ${e.serviceId}:`,t)}this.services.clear()}}class B{constructor(){this.modules=new Map,this.activeModuleIds=new Set}registerModule(e){if(!e.id)throw new Error("[RuntimeModuleRegistry] Module must specify a valid id.");this.modules.set(e.id,e)}async unregisterModule(e,t){const s=this.modules.get(e);return s?(this.activeModuleIds.has(e)&&t&&(await s.stop(t),this.activeModuleIds.delete(e)),await s.dispose(),this.modules.delete(e)):!1}getModule(e){return this.modules.get(e)}listModules(){return Object.freeze(Array.from(this.modules.values()))}async startAll(e){for(const t of this.modules.values())await t.initialize(e),await t.start(e),this.activeModuleIds.add(t.id)}async stopAll(e){for(const t of this.activeModuleIds){const s=this.modules.get(t);if(s)try{await s.stop(e)}catch(i){console.error(`[RuntimeModuleRegistry] Error stopping module ${t}:`,i)}}this.activeModuleIds.clear()}}class U{constructor(){this.taskQueue=[],this.isProcessing=!1,this.timer=null}schedule(e,t,s=b.Normal,i=0,a=3){return new Promise((c,o)=>{const g={id:`tsk_${globalThis.crypto.randomUUID()}`,name:e,priority:s,taskFn:async()=>{i>0&&await new Promise(l=>setTimeout(l,i));try{const l=await t();return c(l),l}catch(l){throw o(l),l}},delayMs:i,maxRetries:a,createdAt:Date.now()};this.taskQueue.push(g),this.sortQueue(),queueMicrotask(()=>{this.processQueue()})})}start(){this.timer||(this.timer=setInterval(()=>{this.processQueue()},50))}stop(){this.timer&&(clearInterval(this.timer),this.timer=null)}getPendingTaskCount(){return this.taskQueue.length}sortQueue(){this.taskQueue.sort((e,t)=>e.priority-t.priority||e.createdAt-t.createdAt)}async processQueue(){if(this.isProcessing||this.taskQueue.length===0)return;this.isProcessing=!0;const e=this.taskQueue.shift();if(e){let t=0;const s=e.maxRetries??1;for(;t<s;)try{await e.taskFn();break}catch(i){t+=1,t>=s&&console.error(`[RuntimeScheduler] Task ${e.name} (${e.id}) failed after ${t} attempts:`,i)}}this.isProcessing=!1}}class T{constructor(){this.rolePermissions=new Map([["Teacher",new Set(["lesson:control","stage:navigate","whiteboard:draw","quiz:submit","plugin:execute","ai:invoke","session:manage"])],["Assistant",new Set(["stage:navigate","whiteboard:draw","quiz:submit","plugin:execute","ai:invoke"])],["Student",new Set(["whiteboard:draw","quiz:submit","ai:invoke","plugin:execute"])],["Observer",new Set([])],["Plugin",new Set(["whiteboard:draw","plugin:execute","ai:invoke"])],["AI",new Set(["stage:navigate","whiteboard:draw","ai:invoke"])]])}hasPermission(e,t){const s=this.rolePermissions.get(e);return s?s.has(t):!1}grantPermission(e,t){this.rolePermissions.has(e)||this.rolePermissions.set(e,new Set),this.rolePermissions.get(e).add(t)}revokePermission(e,t){const s=this.rolePermissions.get(e);s&&s.delete(t)}getRolePermissions(e){const t=this.rolePermissions.get(e);return Object.freeze(t?Array.from(t):[])}}class F{constructor(){this.resources=new Map}registerResource(e,t,s){const i=Array.from(this.resources.values()).find(c=>c.url===e);if(i)return i;const a={id:`res_${globalThis.crypto.randomUUID()}`,type:t,url:e,sizeBytes:s,status:"loaded",cachedAt:Date.now()};return this.resources.set(a.id,a),a}getResource(e){return this.resources.get(e)}listResources(e){const t=Array.from(this.resources.values());return Object.freeze(e?t.filter(s=>s.type===e):t)}removeResource(e){return this.resources.delete(e)}clear(){this.resources.clear()}}class H{constructor(){this.snapshots=new Map}takeSnapshot(e,t,s,i){const a={snapshotId:`snap_${globalThis.crypto.randomUUID()}`,timestamp:Date.now(),stateTree:JSON.parse(JSON.stringify(e)),activeServices:[...t],loadedModules:[...s],resources:[...i]};return this.snapshots.set(a.snapshotId,a),this.lastAutoSnapshot=a,a}getLatestSnapshot(){return this.lastAutoSnapshot}getSnapshot(e){return this.snapshots.get(e)}clear(){this.snapshots.clear(),this.lastAutoSnapshot=void 0}}class Q{constructor(){this.hooks=new Map}registerHook(e,t){this.hooks.has(e)||this.hooks.set(e,new Set);const s=this.hooks.get(e);return s.add(t),()=>{s.delete(t)}}async executeHook(e,t,s){const i=this.hooks.get(e);if(!(!i||i.size===0))for(const a of i)try{await a(t,s)}catch(c){console.error(`[RuntimeHooksManager] Error executing hook "${e}":`,c)}}clear(){this.hooks.clear()}}class J{constructor(e,t){this.activeSession=null,this.eventBus=e,this.stateManager=t}async createSession(e,t,s,i){this.activeSession&&this.activeSession.status==="active"&&await this.destroySession();const a={sessionId:e,courseId:s,lessonId:i,teacher:t,students:Object.freeze([]),createdAt:Date.now(),status:"active"};return this.activeSession=a,this.stateManager.updateState({lesson:{activeLessonId:i,status:"ready"}}),await this.eventBus.publish("SessionCreated",{sessionId:e,teacherId:t.id,timestamp:Date.now()}),a}async joinSession(e){if(!this.activeSession||this.activeSession.status!=="active")return!1;const t=[...this.activeSession.students.filter(s=>s.id!==e.id),{...e,isOnline:!0}];return this.activeSession={...this.activeSession,students:Object.freeze(t)},this.stateManager.addStudent(e),await this.eventBus.publish("StudentJoined",{student:e,timestamp:Date.now()}),!0}async leaveSession(e){if(!this.activeSession||this.activeSession.status!=="active")return!1;const t=this.activeSession.students.map(s=>s.id===e?{...s,isOnline:!1}:s);return this.activeSession={...this.activeSession,students:Object.freeze(t)},this.stateManager.removeStudent(e),await this.eventBus.publish("StudentLeft",{studentId:e,timestamp:Date.now()}),!0}async reconnectSession(e){if(!this.activeSession||this.activeSession.status!=="active")return!1;const t=this.activeSession.students.find(i=>i.id===e);if(!t)return!1;const s={...t,isOnline:!0};return this.joinSession(s)}async destroySession(){this.activeSession&&(this.activeSession={...this.activeSession,status:"destroyed"},this.activeSession=null)}getActiveSession(){return this.activeSession}}class q{constructor(){this.eventCount=0}incrementEventCount(){this.eventCount+=1}getDiagnostics(e,t,s,i){const a=process.memoryUsage?process.memoryUsage().heapUsed/1048576:0;return{runtimeId:e.runtime.id,lifecycleState:e.runtime.lifecycle,activeServicesCount:t,registeredModulesCount:s,pendingSchedulerTasksCount:i,activeStudentsCount:e.students.length,totalInteractionsCount:e.analytics.totalInteractions,memoryUsageMB:Math.round(a*100)/100,timestamp:Date.now()}}}class K{constructor(e=`rt_${globalThis.crypto.randomUUID()}`){this.currentRole="Teacher",this.runtimeId=e,this.eventBus=new E,this.stateManager=new O(e),this.serviceRegistry=new $,this.moduleRegistry=new B,this.scheduler=new U,this.permissionManager=new T,this.resourceManager=new F,this.snapshotManager=new H,this.hooksManager=new Q,this.sessionManager=new J(this.eventBus,this.stateManager),this.monitor=new q,this.stateManager.setLifecycle("Create")}getContext(){const e=this.sessionManager.getActiveSession();return{runtimeId:this.runtimeId,sessionId:e?.sessionId||"sess_default",courseId:e?.courseId,lessonId:e?.lessonId,teacher:e?.teacher,currentUser:this.currentUser||e?.teacher,role:this.currentRole,permissions:this.permissionManager.getRolePermissions(this.currentRole),lifecycleState:this.stateManager.getState().runtime.lifecycle}}async initialize(){this.transitionState("Initialize"),this.scheduler.start();const e=this.getContext();await this.serviceRegistry.initializeAll(e),this.transitionState("Prepare")}async start(){const e=this.getContext();await this.hooksManager.executeHook("beforeLessonStart",{},e),this.transitionState("Running"),await this.moduleRegistry.startAll(e),await this.hooksManager.executeHook("afterLessonStart",{},e),await this.eventBus.publish("LessonStarted",{lessonId:e.lessonId||"les_active",timestamp:Date.now()}),this.takeSnapshot()}async pause(){this.transitionState("Pause"),this.scheduler.stop(),await this.eventBus.publish("RuntimePaused",{elapsedTime:this.stateManager.getState().runtime.elapsedTime,timestamp:Date.now()})}async resume(){this.transitionState("Resume"),this.scheduler.start(),this.transitionState("Running")}async stop(){const e=this.getContext();this.transitionState("Stop"),await this.moduleRegistry.stopAll(e),this.scheduler.stop(),this.takeSnapshot()}async dispose(){await this.stop(),this.transitionState("Dispose"),await this.serviceRegistry.disposeAll(),this.hooksManager.clear(),this.resourceManager.clear(),this.eventBus.clear()}transitionState(e){const t=this.stateManager.getState().runtime.lifecycle;this.stateManager.setLifecycle(e),this.eventBus.publish("LifecycleChanged",{from:t,to:e,timestamp:Date.now()})}registerModule(e){this.moduleRegistry.registerModule(e)}registerService(e){this.serviceRegistry.registerService(e)}registerRuntimeHook(e,t){return this.hooksManager.registerHook(e,t)}scheduleTask(e,t,s=b.Normal,i=0){return this.scheduler.schedule(e,t,s,i)}setUser(e){this.currentUser=e,this.currentRole=e.role}hasPermission(e){return this.permissionManager.hasPermission(this.currentRole,e)}takeSnapshot(){return this.snapshotManager.takeSnapshot(this.stateManager.getState(),this.serviceRegistry.listServices(),this.moduleRegistry.listModules().map(e=>e.id),this.resourceManager.listResources())}restoreLatestSnapshot(){const e=this.snapshotManager.getLatestSnapshot();return e?(this.stateManager.setState(e.stateTree),!0):!1}getDiagnostics(){return this.monitor.getDiagnostics(this.stateManager.getState(),this.serviceRegistry.listServices().length,this.moduleRegistry.listModules().length,this.scheduler.getPendingTaskCount())}}class V{constructor(e){this.init=e,this.kernel=new K,this.kernel.setUser({id:e.student.id,name:e.student.name,role:"Student",avatar:e.student.avatar,isOnline:!0,joinedAt:Date.now()})}getView(){const e=this.kernel.getContext();return{studentId:this.init.student.id,studentName:this.init.student.name,role:"Student",lessonId:this.init.lessonId??e.lessonId,courseId:this.init.courseId??e.courseId,permissions:e.permissions}}get permissions(){return this.kernel.getContext().permissions}hasPermission(e){return this.kernel.hasPermission(e)}subscribe(e,t){return this.kernel.eventBus.subscribe(e,s=>t(s.payload))}takeSnapshot(){return this.kernel.takeSnapshot()}restoreSnapshot(){return this.kernel.restoreLatestSnapshot()}get runtimeKernel(){return this.kernel}dispose(){return this.kernel.dispose()}}function G({isOpen:n,lang:e,activeStudentId:t,studentNotifications:s,readNotifications:i,setReadNotifications:a,studentDashboardData:c,setIsNotificationsOpen:o,setSelectedNotificationForModal:g}){if(!n)return null;const l=s.filter(u=>!i.has(u.id));return r.jsxs("div",{className:"absolute right-0 mt-2 w-80 bg-white border border-gray-200 shadow-lg rounded-xl z-50 overflow-hidden",children:[r.jsxs("div",{className:"p-3 border-b border-gray-100 flex justify-between items-center bg-gray-50",children:[r.jsx("h3",{className:"font-semibold text-gray-800",children:"Notifications"}),l.length>0&&r.jsx("button",{onClick:async()=>{if(!t)return;try{const h=s.filter(m=>!i.has(m.id)).map(m=>fetch(`/api/students/${t}/read_notifications`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({notificationId:m.id})}));await Promise.all(h)}catch(h){console.error(h)}const u=new Set(i);s.forEach(h=>u.add(h.id)),a(u)},className:"text-xs text-indigo-600 hover:text-indigo-800 font-medium",children:"Mark all as read"})]}),r.jsx("div",{className:"max-h-96 overflow-y-auto",children:s.length===0?r.jsx("div",{className:"p-4 text-center text-sm text-gray-500 italic",children:"No notifications."}):r.jsx("div",{className:"divide-y divide-gray-100",children:s.map(u=>{const h=!i.has(u.id);return r.jsx("div",{className:`p-3 hover:bg-gray-50 cursor-pointer ${h?"bg-indigo-50/30":""}`,onClick:()=>{if(h){t&&fetch(`/api/students/${t}/read_notifications`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({notificationId:u.id})}).catch(console.error);const S=new Set(i);S.add(u.id),a(S)}const m=c?.assignments?.find(S=>S.id===u.relatedId);g({...u,assignment:m}),o(!1)},children:r.jsxs("div",{className:"flex gap-3",children:[r.jsx("div",{className:"mt-0.5",children:u.type==="new_assignment"?r.jsx(C,{size:16,className:"text-indigo-500"}):r.jsx(z,{size:16,className:"text-green-500"})}),r.jsxs("div",{className:"flex-1",children:[r.jsx("div",{className:`text-sm ${h?"font-semibold text-gray-900":"font-medium text-gray-700"}`,children:u.title}),r.jsx("div",{className:`text-xs mt-0.5 ${h?"text-gray-600":"text-gray-500"}`,children:u.message})]}),h&&r.jsx("div",{className:"w-2 h-2 rounded-full bg-indigo-500 mt-1"})]})},u.id)})})})]})}const X=({widgetId:n="widget_ai_teacher_workspace",initialSection:e="Lesson Assistant",onActionExecute:t})=>{const[s,i]=d.useState({widgetId:n,visible:!0,pinned:!0,collapsed:!1,fullscreen:!1,dockPosition:"right",activeSection:e});if(!s.visible)return null;const a=["Lesson Assistant","Whiteboard Assistant","Resource Assistant","Activity Assistant","Student Assistant","Assessment Assistant","Summary Assistant","Plugin Assistant"],c=o=>{t&&t(o,{section:s.activeSection})};return r.jsxs("div",{"data-testid":"ai-teacher-workspace-widget",className:`ai-teacher-workspace-panel ${s.dockPosition} ${s.collapsed?"collapsed":""} ${s.fullscreen?"fullscreen":""}`,style:{border:"1px solid #e2e8f0",borderRadius:"8px",background:"#ffffff",padding:"12px",boxShadow:"0 4px 6px -1px rgba(0, 0, 0, 0.1)"},children:[r.jsxs("div",{className:"ai-widget-header",style:{display:"flex",justifyContent:"space-between",alignItems:"center",borderBottom:"1px solid #f1f5f9",paddingBottom:"8px",marginBottom:"8px"},children:[r.jsx("span",{style:{fontWeight:600,fontSize:"14px",color:"#1e293b"},children:"🤖 AI Teacher Assistant"}),r.jsxs("div",{className:"ai-widget-controls",style:{display:"flex",gap:"4px"},children:[r.jsx("button",{"data-testid":"btn-pin",onClick:()=>i(o=>({...o,pinned:!o.pinned})),style:{fontSize:"12px",cursor:"pointer"},children:s.pinned?"📌 Unpin":"📍 Pin"}),r.jsx("button",{"data-testid":"btn-dock",onClick:()=>i(o=>({...o,dockPosition:o.dockPosition==="right"?"float":"right"})),style:{fontSize:"12px",cursor:"pointer"},children:s.dockPosition==="right"?"🌊 Float":"📥 Dock"}),r.jsx("button",{"data-testid":"btn-fullscreen",onClick:()=>i(o=>({...o,fullscreen:!o.fullscreen})),style:{fontSize:"12px",cursor:"pointer"},children:s.fullscreen?"🗗 Restore":"⛶ Fullscreen"}),r.jsx("button",{"data-testid":"btn-collapse",onClick:()=>i(o=>({...o,collapsed:!o.collapsed})),style:{fontSize:"12px",cursor:"pointer"},children:s.collapsed?"➕ Expand":"➖ Collapse"})]})]}),!s.collapsed&&r.jsxs(r.Fragment,{children:[r.jsx("div",{className:"ai-section-tabs",style:{display:"flex",flexWrap:"wrap",gap:"4px",marginBottom:"12px"},children:a.map(o=>r.jsx("button",{"data-testid":`tab-${o.toLowerCase().replace(/\s+/g,"-")}`,onClick:()=>i(g=>({...g,activeSection:o})),style:{padding:"4px 8px",fontSize:"12px",borderRadius:"4px",border:"1px solid #cbd5e1",background:s.activeSection===o?"#3b82f6":"#f8fafc",color:s.activeSection===o?"#ffffff":"#334155",cursor:"pointer"},children:o},o))}),r.jsxs("div",{className:"ai-section-body",style:{minHeight:"100px"},children:[r.jsxs("div",{style:{fontSize:"13px",color:"#475569",marginBottom:"8px"},children:["Active Assistant: ",r.jsx("strong",{children:s.activeSection})]}),r.jsxs("div",{style:{display:"flex",gap:"8px",flexWrap:"wrap"},children:[r.jsx("button",{"data-testid":"btn-summarize",onClick:()=>c("ai_summarize_lesson"),style:{padding:"6px 12px",fontSize:"12px",background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:"4px",cursor:"pointer"},children:"📝 Summarize Content"}),r.jsx("button",{"data-testid":"btn-explain",onClick:()=>c("ai_explain_whiteboard"),style:{padding:"6px 12px",fontSize:"12px",background:"#f0fdf4",border:"1px solid #bbf7d0",borderRadius:"4px",cursor:"pointer"},children:"🎨 Explain Visuals"}),r.jsx("button",{"data-testid":"btn-quiz",onClick:()=>c("ai_generate_quiz"),style:{padding:"6px 12px",fontSize:"12px",background:"#fefce8",border:"1px solid #fef08a",borderRadius:"4px",cursor:"pointer"},children:"⚡ Generate Quiz"})]})]})]})]})};class Y{constructor(){this.resources=new Map,this.providers=new Map}registerProvider(e){if(!e||!e.id||!e.type)throw new Error("ResourceRegistry Error: ResourceProvider must have a valid ID and Type.");this.providers.set(e.type,e)}unregisterProvider(e){for(const[t,s]of this.providers.entries())if(s.id===e)return this.providers.delete(t);return!1}getProvider(e){return this.providers.get(e)}registerResource(e){if(!e||!e.id)throw new Error("ResourceRegistry Error: ResourceDescriptor must have a valid ID.");this.resources.set(e.id,{...e})}unregisterResource(e){return this.resources.delete(e)}getResource(e){return this.resources.get(e)}listResources(e){const t=Array.from(this.resources.values());return Object.freeze(e?t.filter(s=>s.type===e):t)}executeAction(e,t,s){const i=this.resources.get(e);if(!i)throw new Error(`ResourceRegistry Error: Resource '${e}' not found.`);const a=this.providers.get(i.type);switch(t){case"preview":return a?.preview?a.preview(i):{previewUrl:i.url};case"open":return a?.open?a.open(i):{openUrl:i.url};case"pin":return i.pinned=!i.pinned,{pinned:i.pinned};case"favorite":return i.favorited=!i.favorited,{favorited:i.favorited};case"annotate":return{annotate:!0,resourceId:e,params:s};case"share":return{shareUrl:i.url??`resource://${i.id}`};case"fullscreen":return{fullscreen:!0,resourceId:e};default:throw new Error(`ResourceRegistry Error: Unknown action '${t}'.`)}}clear(){this.resources.clear(),this.providers.clear()}}const y={padding:"12px",color:"#e2e8f0",fontSize:"13px"},Z=()=>r.jsx("div",{style:{width:"100%",height:"100%"},children:r.jsx(A,{})}),ee=({context:n})=>{const e=n.getView();return r.jsxs("div",{style:y,children:[r.jsx("div",{style:{fontWeight:600,marginBottom:6},children:"📘 Lesson"}),r.jsxs("div",{children:["Lesson ID: ",e.lessonId??"—"]}),r.jsxs("div",{children:["Course ID: ",e.courseId??"—"]})]})},te=()=>{const[n]=d.useState(()=>new Y().listResources());return r.jsxs("div",{style:y,children:[r.jsx("div",{style:{fontWeight:600,marginBottom:6},children:"📂 Resources"}),n.length===0?r.jsx("div",{style:{opacity:.7},children:"No resources shared yet."}):r.jsx("ul",{children:n.map(e=>r.jsxs("li",{children:[e.type,": ",e.url]},e.id))})]})},se=({studentId:n,lang:e="en"})=>r.jsx(P,{role:"student",actorId:n,lang:e}),ie=({studentId:n,lessonId:e,lang:t="en"})=>e?r.jsx(W,{lessonId:e,studentId:n,lang:t,addToast:()=>{}}):r.jsx("div",{style:y,children:"No active lesson — assignments unavailable."}),re=({context:n})=>{const[e,t]=d.useState([]);return d.useEffect(()=>{const s=n.subscribe("LessonStarted",a=>{t(c=>[...c,{id:`ls_${a.timestamp}`,text:`Lesson started (${a.lessonId})`}])}),i=n.subscribe("StageChanged",a=>{t(c=>[...c,{id:`sc_${a.timestamp}`,text:`Stage changed → ${a.toStageId}`}])});return()=>{s(),i()}},[n]),r.jsxs("div",{style:y,children:[r.jsx("div",{style:{fontWeight:600,marginBottom:6},children:"📣 Teacher Broadcast"}),e.length===0?r.jsx("div",{style:{opacity:.7},children:"No announcements yet."}):r.jsx("ul",{children:e.map(s=>r.jsx("li",{children:s.text},s.id))})]})},ne=()=>r.jsx(X,{}),ae=({studentId:n,lang:e="en"})=>{const[t,s]=d.useState(!0),[i,a]=d.useState(new Set),[c]=d.useState([]);return r.jsx(G,{isOpen:t,lang:e,activeStudentId:n,studentNotifications:c,readNotifications:i,setReadNotifications:a,studentDashboardData:null,setIsNotificationsOpen:s,setSelectedNotificationForModal:()=>{}})},oe=()=>r.jsx(I,{slot:"student.view"});class ce{constructor(e){this.registeredWidgets=new Map,this.slotRegistry=e??new w}get slotRegistryRef(){return this.slotRegistry}registerWidget(e){if(!e||!e.id)throw new Error("StudentWidgetRegistry Error: StudentWidgetDescriptor must have a valid ID.");this.registeredWidgets.set(e.id,e);const t=e.component;this.slotRegistry.register({id:e.id,slot:e.slot,priority:e.priority??10,render:s=>x.createElement(t,s)})}unregisterWidget(e){const t=this.registeredWidgets.delete(e);return t&&this.slotRegistry.unregister(e),t}registerDefaultWidgets(e){const{studentId:t,lessonId:s,lang:i="en",context:a,pluginHost:c}=e;this.mountWidget("widget_student_whiteboard","Whiteboard","MainCanvas",Z,{},"official"),this.mountWidget("widget_student_lesson","Lesson","LeftSidebar",ee,{context:a},"official"),this.mountWidget("widget_student_resources","Resources","LeftSidebar",te,{},"official"),this.mountWidget("widget_student_activities","Activities","BottomPanel",se,{},"official"),this.mountWidget("widget_student_assignments","Assignments","RightSidebar",ie,{studentId:t,lessonId:s,lang:i},"official",20),this.mountWidget("widget_student_ai_assistant","AI Learning Assistant","RightSidebar",ne,{},"official",10),this.mountWidget("widget_student_broadcast","Teacher Broadcast","BottomPanel",re,{context:a},"official"),this.mountWidget("widget_student_notifications","Notifications","FloatingArea",ae,{studentId:t,lang:i},"official"),c&&this.mountWidget("widget_student_plugins","Plugin Widgets","FloatingArea",oe,{},"plugin")}getWidget(e){return this.registeredWidgets.get(e)}listWidgets(){return Object.freeze(Array.from(this.registeredWidgets.values()))}clear(){for(const e of this.registeredWidgets.keys())this.slotRegistry.unregister(e);this.registeredWidgets.clear()}mountWidget(e,t,s,i,a,c,o=10){this.registeredWidgets.set(e,{id:e,name:t,slot:s,component:i,provider:c,priority:o}),this.slotRegistry.register({id:e,slot:s,priority:o,render:()=>x.createElement(i,a)})}}const R="openlearn_student_workspace_";function le(n){return{studentId:n,openedWidgets:[],layout:{},selectedResourceIds:[],activityState:{},aiConversation:[],updatedAt:Date.now()}}class de{constructor(e){this.key=`${R}${e}`}load(){try{if(typeof localStorage>"u")return null;const e=localStorage.getItem(this.key);if(!e)return null;const t=JSON.parse(e);return t.studentId!==this.studentIdFromKey()?null:t}catch{return null}}save(e){try{if(typeof localStorage>"u")return;localStorage.setItem(this.key,JSON.stringify({...e,updatedAt:Date.now()}))}catch{}}update(e){const s={...this.load()??le(this.studentIdFromKey()),...e,updatedAt:Date.now()};return this.save(s),s}clear(){try{if(typeof localStorage>"u")return;localStorage.removeItem(this.key)}catch{}}studentIdFromKey(){return this.key.slice(R.length)}}const be=n=>{const{student:e,lessonId:t,courseId:s,teacherId:i,teacherName:a,lang:c="en",autoRestore:o=!0,pluginHost:g}=n,[l]=d.useState(()=>new V({student:e,lessonId:t,courseId:s,teacherId:i,teacherName:a,lang:c})),[u]=d.useState(()=>new w),[h]=d.useState(()=>{const p=new ce(u);return p.registerDefaultWidgets({studentId:e.id,lessonId:t,lang:c,context:l,pluginHost:g}),p}),[m]=d.useState(()=>new de(e.id));d.useEffect(()=>{if(!o)return;l.restoreSnapshot();const p=m.load();p?.currentLessonId&&p.currentLessonId},[o,l,m]),d.useEffect(()=>{const p=l.subscribe("LessonStarted",v=>{m.update({currentLessonId:v.lessonId})});return()=>{p()}},[l,m]),d.useEffect(()=>()=>{l.takeSnapshot();const p=h.listWidgets().map(j=>j.id),v=m.load();m.save({studentId:e.id,openedWidgets:p,layout:v?.layout??{},currentLessonId:v?.currentLessonId??t,selectedResourceIds:v?.selectedResourceIds??[],activityState:v?.activityState??{},aiConversation:v?.aiConversation??[],updatedAt:Date.now()}),l.dispose()},[l,m,h,e.id,t]);const S=r.jsx(N,{registry:u,children:r.jsx(L,{})});return g?r.jsx(M,{host:g,children:S}):S};export{be as StudentWorkspace,be as default};
package/dist/index.html CHANGED
@@ -13,7 +13,7 @@
13
13
  property="og:description"
14
14
  content="An AI-powered Learning Management System featuring interactive classrooms, stage management, real-time student response systems, lesson editor, auto-grading, and an extensible third-party plugin ecosystem."
15
15
  />
16
- <script type="module" crossorigin src="/assets/index-BBznBaN5.js"></script>
16
+ <script type="module" crossorigin src="/assets/index-DN3E-Fmw.js"></script>
17
17
  <link rel="modulepreload" crossorigin href="/assets/vendor-utils-aM7aYvPP.js">
18
18
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-Kv_JATwr.js">
19
19
  <link rel="modulepreload" crossorigin href="/assets/vendor-icons-hc6MjYFv.js">
package/dist/server.cjs CHANGED
@@ -3578,6 +3578,19 @@ var init_errors2 = __esm({
3578
3578
  function scopedId(scopeType, scopeId, key) {
3579
3579
  return `${scopeType}\0${scopeId}\0${key}`;
3580
3580
  }
3581
+ function getSharedState(db2, manifestId) {
3582
+ let byManifest = sharedStates.get(db2);
3583
+ if (!byManifest) {
3584
+ byManifest = /* @__PURE__ */ new Map();
3585
+ sharedStates.set(db2, byManifest);
3586
+ }
3587
+ let state = byManifest.get(manifestId);
3588
+ if (!state) {
3589
+ state = { values: /* @__PURE__ */ new Map(), instances: /* @__PURE__ */ new Set() };
3590
+ byManifest.set(manifestId, state);
3591
+ }
3592
+ return state;
3593
+ }
3581
3594
  function normalizeWriteScope(scope) {
3582
3595
  const scopeType = scope?.scopeType ?? "platform";
3583
3596
  const scopeId = scope?.scopeId?.trim() ?? "";
@@ -3586,21 +3599,29 @@ function normalizeWriteScope(scope) {
3586
3599
  }
3587
3600
  return { scopeType, scopeId: scopeType === "platform" ? "" : scopeId };
3588
3601
  }
3589
- var CONFIG_SCOPE_TYPES, ConfigService;
3602
+ var CONFIG_SCOPE_TYPES, sharedStates, instanceCleanup, ConfigService;
3590
3603
  var init_config_service = __esm({
3591
3604
  "packages/core/plugin-host/config-service.ts"() {
3592
3605
  "use strict";
3593
3606
  CONFIG_SCOPE_TYPES = ["platform", "school", "class"];
3607
+ sharedStates = /* @__PURE__ */ new WeakMap();
3608
+ instanceCleanup = new FinalizationRegistry(
3609
+ ({ state, ref }) => {
3610
+ state.instances.delete(ref);
3611
+ }
3612
+ );
3594
3613
  ConfigService = class _ConfigService {
3595
3614
  constructor(db2, manifest) {
3596
- /** In-memory cache (scoped key → value). `null` 表示「明确没有值」,避免反复查询。 */
3597
- this.values = /* @__PURE__ */ new Map();
3598
- /** Change listeners. */
3615
+ /** Change listeners(每个实例独立,广播时按实例分发)。 */
3599
3616
  this.listeners = /* @__PURE__ */ new Set();
3600
3617
  this.tableEnsured = false;
3601
3618
  this.db = db2;
3602
3619
  this.manifestId = manifest.id;
3603
3620
  this.schema = manifest.configuration?.properties ?? {};
3621
+ this.state = getSharedState(db2, manifest.id);
3622
+ const ref = new WeakRef(this);
3623
+ this.state.instances.add(ref);
3624
+ instanceCleanup.register(this, { state: this.state, ref });
3604
3625
  }
3605
3626
  static {
3606
3627
  this.KEY_PREFIX = "config:";
@@ -3663,21 +3684,21 @@ var init_config_service = __esm({
3663
3684
  }
3664
3685
  readScoped(key, scopeType, scopeId) {
3665
3686
  const cacheKey2 = scopedId(scopeType, scopeId, key);
3666
- if (this.values.has(cacheKey2)) {
3667
- const cached3 = this.values.get(cacheKey2);
3687
+ if (this.state.values.has(cacheKey2)) {
3688
+ const cached3 = this.state.values.get(cacheKey2);
3668
3689
  return cached3 === null ? { found: false } : { found: true, value: cached3 };
3669
3690
  }
3670
3691
  const row = this.queryScoped(key, scopeType, scopeId);
3671
3692
  if (!row) {
3672
- this.values.set(cacheKey2, null);
3693
+ this.state.values.set(cacheKey2, null);
3673
3694
  return { found: false };
3674
3695
  }
3675
3696
  try {
3676
3697
  const parsed = JSON.parse(row.value);
3677
- this.values.set(cacheKey2, parsed);
3698
+ this.state.values.set(cacheKey2, parsed);
3678
3699
  return { found: true, value: parsed };
3679
3700
  } catch {
3680
- this.values.set(cacheKey2, row.value);
3701
+ this.state.values.set(cacheKey2, row.value);
3681
3702
  return { found: true, value: row.value };
3682
3703
  }
3683
3704
  }
@@ -3693,21 +3714,21 @@ var init_config_service = __esm({
3693
3714
  }
3694
3715
  readLegacyPlatformValue(key) {
3695
3716
  const cacheKey2 = scopedId("platform", "", `legacy:${key}`);
3696
- if (this.values.has(cacheKey2)) {
3697
- const cached3 = this.values.get(cacheKey2);
3717
+ if (this.state.values.has(cacheKey2)) {
3718
+ const cached3 = this.state.values.get(cacheKey2);
3698
3719
  return cached3 === null ? { found: false } : { found: true, value: cached3 };
3699
3720
  }
3700
3721
  try {
3701
3722
  const row = this.db.prepare("SELECT value FROM plugin_storage WHERE plugin_id = ? AND key = ?").get(this.manifestId, _ConfigService.KEY_PREFIX + key);
3702
3723
  if (!row) {
3703
- this.values.set(cacheKey2, null);
3724
+ this.state.values.set(cacheKey2, null);
3704
3725
  return { found: false };
3705
3726
  }
3706
3727
  const parsed = JSON.parse(row.value);
3707
- this.values.set(cacheKey2, parsed);
3728
+ this.state.values.set(cacheKey2, parsed);
3708
3729
  return { found: true, value: parsed };
3709
3730
  } catch {
3710
- this.values.set(cacheKey2, null);
3731
+ this.state.values.set(cacheKey2, null);
3711
3732
  return { found: false };
3712
3733
  }
3713
3734
  }
@@ -3763,8 +3784,8 @@ var init_config_service = __esm({
3763
3784
  const { scopeType, scopeId } = normalizeWriteScope(scope);
3764
3785
  this.assertScopeAllowed(key, scopeType);
3765
3786
  const cacheKey2 = scopedId(scopeType, scopeId, key);
3766
- const previous = this.values.get(cacheKey2);
3767
- this.values.set(cacheKey2, value);
3787
+ const previous = this.state.values.get(cacheKey2);
3788
+ this.state.values.set(cacheKey2, value);
3768
3789
  try {
3769
3790
  this.ensureTable();
3770
3791
  this.db.prepare(
@@ -3775,16 +3796,32 @@ var init_config_service = __esm({
3775
3796
  ).run(this.manifestId, scopeType, scopeId, key, JSON.stringify(value), Date.now());
3776
3797
  } catch (e) {
3777
3798
  if (previous === void 0) {
3778
- this.values.delete(cacheKey2);
3799
+ this.state.values.delete(cacheKey2);
3779
3800
  } else {
3780
- this.values.set(cacheKey2, previous);
3801
+ this.state.values.set(cacheKey2, previous);
3781
3802
  }
3782
3803
  throw e;
3783
3804
  }
3784
- for (const listener of this.listeners) {
3785
- try {
3786
- listener(key, value, previous === null ? void 0 : previous);
3787
- } catch {
3805
+ this.broadcast(key, value, previous ?? void 0);
3806
+ }
3807
+ /**
3808
+ * 通知所有共享同一 db + manifest.id 的活跃实例(含自身)。
3809
+ *
3810
+ * 管理端通过临时实例写入时,运行中插件的 `onChange` 也因此能收到。
3811
+ * 已回收的实例在遍历时顺手清理。
3812
+ */
3813
+ broadcast(key, newValue, oldValue) {
3814
+ for (const ref of this.state.instances) {
3815
+ const instance = ref.deref();
3816
+ if (!instance) {
3817
+ this.state.instances.delete(ref);
3818
+ continue;
3819
+ }
3820
+ for (const listener of instance.listeners) {
3821
+ try {
3822
+ listener(key, newValue, oldValue);
3823
+ } catch {
3824
+ }
3788
3825
  }
3789
3826
  }
3790
3827
  }
@@ -3817,11 +3854,12 @@ var init_config_service = __esm({
3817
3854
  * 插件在 `activate()` 里通常会马上读配置,提前填充避免每条配置一次查询。
3818
3855
  */
3819
3856
  loadFromDB() {
3857
+ this.state.values.clear();
3820
3858
  try {
3821
3859
  const rows = this.db.prepare(`SELECT scope_type, scope_id, key, value FROM ${_ConfigService.TABLE} WHERE plugin_id = ?`).all(this.manifestId);
3822
3860
  for (const row of rows) {
3823
3861
  const scopeType = CONFIG_SCOPE_TYPES.includes(row.scope_type) ? row.scope_type : "platform";
3824
- this.values.set(scopedId(scopeType, row.scope_id ?? "", row.key), this.parseStored(row.value));
3862
+ this.state.values.set(scopedId(scopeType, row.scope_id ?? "", row.key), this.parseStored(row.value));
3825
3863
  }
3826
3864
  } catch {
3827
3865
  }
@@ -3830,7 +3868,7 @@ var init_config_service = __esm({
3830
3868
  const rows = this.db.prepare(`SELECT key, value FROM plugin_storage WHERE plugin_id = ? AND key LIKE ?`).all(this.manifestId, prefix + "%");
3831
3869
  for (const row of rows) {
3832
3870
  const key = row.key.slice(prefix.length);
3833
- this.values.set(scopedId("platform", "", `legacy:${key}`), this.parseStored(row.value));
3871
+ this.state.values.set(scopedId("platform", "", `legacy:${key}`), this.parseStored(row.value));
3834
3872
  }
3835
3873
  } catch {
3836
3874
  }
@@ -3842,9 +3880,9 @@ var init_config_service = __esm({
3842
3880
  return raw;
3843
3881
  }
3844
3882
  }
3845
- /** 测试/诊断:当前缓存里的作用域条目数。 */
3883
+ /** 测试/诊断:当前共享缓存里的作用域条目数。 */
3846
3884
  cachedEntryCount() {
3847
- return this.values.size;
3885
+ return this.state.values.size;
3848
3886
  }
3849
3887
  };
3850
3888
  }
@@ -20999,7 +21037,7 @@ var import_node_fs11 = __toESM(require("node:fs"), 1);
20999
21037
  var import_node_path13 = __toESM(require("node:path"), 1);
21000
21038
  var import_node_url5 = require("node:url");
21001
21039
  var import_meta7 = {};
21002
- var FALLBACK_VERSION = "0.7.5";
21040
+ var FALLBACK_VERSION = "0.7.6";
21003
21041
  function readPlatformVersion() {
21004
21042
  const bases = [];
21005
21043
  if (typeof __dirname !== "undefined") bases.push(__dirname);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openlearn-next",
3
- "version": "0.7.5",
3
+ "version": "0.7.6",
4
4
  "private": false,
5
5
  "description": "A plugin-driven Educational OS / LMS platform with AI agent teaching assistant",
6
6
  "keywords": [