ronin-skillforce 0.0.0-stage → 0.1.0
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.
- package/CHANGELOG.md +26 -0
- package/LICENSE +21 -0
- package/README.md +225 -2
- package/bin/cli.js +811 -0
- package/bin/commands/check.js +298 -0
- package/bin/commands/create.js +169 -0
- package/bin/commands/doctor.js +392 -0
- package/bin/commands/import.js +557 -0
- package/bin/commands/next.js +125 -0
- package/bin/commands/review.js +207 -0
- package/bin/commands/status.js +114 -0
- package/bin/commands/studio.js +497 -0
- package/bin/commands/trail.js +211 -0
- package/bin/commands/web.js +350 -0
- package/bin/course-reader.js +611 -0
- package/bin/node-version.js +60 -0
- package/bin/platforms/_shared.js +211 -0
- package/bin/platforms/claude.js +104 -0
- package/bin/platforms/cursor.js +49 -0
- package/bin/platforms/gemini.js +65 -0
- package/bin/platforms/opencode.js +95 -0
- package/bin/registry-helpers.js +504 -0
- package/bin/static-server.js +153 -0
- package/bin/studio-store.js +94 -0
- package/bin/sync-shared.sh +5 -0
- package/package.json +91 -4
- package/plugin.json +139 -0
- package/shared/SKILL.md +568 -0
- package/shared/agents/coach.md +225 -0
- package/shared/agents/navigator.md +215 -0
- package/shared/agents/professor.md +372 -0
- package/shared/agents/researcher.md +181 -0
- package/shared/agents/spotter.md +129 -0
- package/shared/commands/professor/archive.md +103 -0
- package/shared/commands/professor/capstone-review.md +79 -0
- package/shared/commands/professor/capstone.md +34 -0
- package/shared/commands/professor/discuss.md +18 -0
- package/shared/commands/professor/done.md +44 -0
- package/shared/commands/professor/export.md +69 -0
- package/shared/commands/professor/hint.md +108 -0
- package/shared/commands/professor/navigator.md +67 -0
- package/shared/commands/professor/new-topic.md +73 -0
- package/shared/commands/professor/next.md +174 -0
- package/shared/commands/professor/note.md +63 -0
- package/shared/commands/professor/onboard.md +95 -0
- package/shared/commands/professor/progress.md +46 -0
- package/shared/commands/professor/publish.md +93 -0
- package/shared/commands/professor/quiz.md +27 -0
- package/shared/commands/professor/recall.md +74 -0
- package/shared/commands/professor/review.md +43 -0
- package/shared/commands/professor/schedule.md +75 -0
- package/shared/commands/professor/skill-export.md +87 -0
- package/shared/commands/professor/spotter.md +76 -0
- package/shared/commands/professor/stuck.md +27 -0
- package/shared/commands/professor/switch.md +50 -0
- package/shared/commands/professor/syllabus.md +9 -0
- package/shared/commands/professor/template-export.md +91 -0
- package/shared/commands/professor/template-import.md +111 -0
- package/shared/commands/professor/worktrees.md +58 -0
- package/shared/hooks/pre-compact.js +232 -0
- package/studio/README.md +313 -0
- package/studio/dist/assets/BetaReportPanel-8NrDLiPm.js +1 -0
- package/studio/dist/assets/Capstone-Cv9kH6LY.js +6 -0
- package/studio/dist/assets/CodeWorkspace-BwCEINWo.js +2 -0
- package/studio/dist/assets/Course-BJ_hafUP.js +1 -0
- package/studio/dist/assets/Editor-CV0mNhlz.js +53 -0
- package/studio/dist/assets/Home-C1ry89ak.js +1 -0
- package/studio/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/studio/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/studio/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/studio/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/studio/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/studio/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/studio/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/studio/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/studio/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/studio/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/studio/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/studio/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/studio/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/studio/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/studio/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/studio/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/studio/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/studio/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/studio/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/studio/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/studio/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/studio/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/studio/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/studio/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/studio/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/studio/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/studio/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/studio/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/studio/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/studio/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/studio/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/studio/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/studio/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/studio/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/studio/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/studio/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/studio/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/studio/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/studio/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/studio/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/studio/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/studio/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/studio/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/studio/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/studio/dist/assets/Lesson-2e9TD9yL.js +2 -0
- package/studio/dist/assets/NewCourse-BZeOg2xn.js +952 -0
- package/studio/dist/assets/Progress-CRc_xEWC.js +1 -0
- package/studio/dist/assets/Review-6QEbHEfk.js +1 -0
- package/studio/dist/assets/Settings-NFnqVQui.js +1 -0
- package/studio/dist/assets/TutorPanel-DXQ6Z3BY.js +123 -0
- package/studio/dist/assets/abnfDiagram-WEYOIA3C-qeSjKlGM.js +1 -0
- package/studio/dist/assets/anthropic-D7NiQCUH.js +15 -0
- package/studio/dist/assets/arc-Y1ARJVZ4.js +1 -0
- package/studio/dist/assets/architecture-WOLXFQ4H-COG-m35l.js +1 -0
- package/studio/dist/assets/architectureDiagram-ZHVYKT53-VO7uKoeD.js +36 -0
- package/studio/dist/assets/array-BifhSqXX.js +1 -0
- package/studio/dist/assets/blockDiagram-RJAE7QA3-BGJF_qiL.js +140 -0
- package/studio/dist/assets/bridge-BTh0AeNX.js +1 -0
- package/studio/dist/assets/c4Diagram-XDPPPE54-C-S10RgU.js +38 -0
- package/studio/dist/assets/capstone-Amn6Hztq.js +135 -0
- package/studio/dist/assets/card-store-BrbrtX9w.js +1 -0
- package/studio/dist/assets/channel-SJiUQu_N.js +1 -0
- package/studio/dist/assets/chunk-2E7WHVMD-fv-iqTyC.js +88 -0
- package/studio/dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/studio/dist/assets/chunk-4EMNKR2T-CY9IZrgv.js +1 -0
- package/studio/dist/assets/chunk-55XCU76K-DyTZz7v_.js +1 -0
- package/studio/dist/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/studio/dist/assets/chunk-7S6XSTFO-BNMzTLRW.js +2 -0
- package/studio/dist/assets/chunk-AQDTSKZB-CEYANohH.js +72 -0
- package/studio/dist/assets/chunk-DSNLSHJZ-CDk5EXjH.js +129 -0
- package/studio/dist/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
- package/studio/dist/assets/chunk-HAGFOTED-hNAi1zh_.js +62 -0
- package/studio/dist/assets/chunk-HNE4Q36P-COJnSYoZ.js +1 -0
- package/studio/dist/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
- package/studio/dist/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/studio/dist/assets/chunk-NGNAAXSQ-C2Wy9Xmz.js +136 -0
- package/studio/dist/assets/chunk-NQOKRVIF-BU6KVxRr.js +213 -0
- package/studio/dist/assets/chunk-UHW7PFBP-DYvxL9JG.js +1 -0
- package/studio/dist/assets/chunk-VQ5W62QI-CuRfk65j.js +1 -0
- package/studio/dist/assets/chunk-WQQL4CDK-KaG7x-Mb.js +10 -0
- package/studio/dist/assets/chunk-XXDRQBXY-CnbRkih_.js +1 -0
- package/studio/dist/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/studio/dist/assets/chunk-YZO67HFG-DbuqIZmk.js +1 -0
- package/studio/dist/assets/chunk-ZAUX2LTX-bk4ivUmc.js +1 -0
- package/studio/dist/assets/chunk-ZCHMQSOR-B_LElfwh.js +2 -0
- package/studio/dist/assets/classDiagram-v2-4MYWR3UA-DtCJ6H_X.js +217 -0
- package/studio/dist/assets/cose-bilkent-JH36ORCC-DMBAZFnj.js +1 -0
- package/studio/dist/assets/course-export-DpaOTT3a.js +1 -0
- package/studio/dist/assets/cynefin-EF2NZ3EQ-D9btHJvU.js +1 -0
- package/studio/dist/assets/cynefinDiagram-WF24UDRP-CsFJuv27.js +62 -0
- package/studio/dist/assets/cytoscape.esm-CHBj4VkZ.js +321 -0
- package/studio/dist/assets/dagre-NK4GIZVX-Cg1Duec7.js +4 -0
- package/studio/dist/assets/defaultLocale-BFoDCU3G.js +1 -0
- package/studio/dist/assets/diagram-CBK3MWTC-_KU2m5gd.js +24 -0
- package/studio/dist/assets/diagram-I5XIIV2D-5RaBrwUA.js +24 -0
- package/studio/dist/assets/diagram-ODIL2UCW-C0VKxoD3.js +30 -0
- package/studio/dist/assets/diagram-PFBEJUXE-BaHcZEP2.js +3 -0
- package/studio/dist/assets/diagram-RJA3I4VV-cwSAZ90p.js +41 -0
- package/studio/dist/assets/diagram-TYUNFSNR-BJImxCEN.js +200 -0
- package/studio/dist/assets/dist-CumnGVmJ.js +1 -0
- package/studio/dist/assets/ebnfDiagram-V4P3TTPT-OQQLRXcm.js +1 -0
- package/studio/dist/assets/elk-QONTBCTQ-BJGhQHxQ.js +27 -0
- package/studio/dist/assets/erDiagram-KYQIGQAA-D91EKSep.js +99 -0
- package/studio/dist/assets/error-BTdmRKoB.js +1 -0
- package/studio/dist/assets/errors-BZa_CNHL.js +1 -0
- package/studio/dist/assets/eventmodeling-K75KTNOO-DgdTRgnw.js +1 -0
- package/studio/dist/assets/fetch-local-DG3O3hmQ.js +4 -0
- package/studio/dist/assets/flowDiagram-IUGLLWBU-CVnJYMum.js +1 -0
- package/studio/dist/assets/ganttDiagram-R7GPNTJY-CSYLPWEI.js +292 -0
- package/studio/dist/assets/gitGraph-VSP46ZUC-D6GWaV9y.js +1 -0
- package/studio/dist/assets/gitGraphDiagram-PDYL7XHY-CaV31bvT.js +106 -0
- package/studio/dist/assets/graphlib-DS17s2tU.js +1 -0
- package/studio/dist/assets/index-CVenTTOY.css +2 -0
- package/studio/dist/assets/index-DioXTZVc.js +313 -0
- package/studio/dist/assets/info-OHQRW6UA-BN-x8GoN.js +1 -0
- package/studio/dist/assets/infoDiagram-MVDRKAX5-CIose_cy.js +2 -0
- package/studio/dist/assets/init-C-OQMol4.js +1 -0
- package/studio/dist/assets/ishikawaDiagram-HPEHV2GE-DCihSNBw.js +70 -0
- package/studio/dist/assets/journeyDiagram-BOSHW7O7-Bz3o3RIX.js +139 -0
- package/studio/dist/assets/js.worker-AD7DjksO.js +4 -0
- package/studio/dist/assets/kanban-definition-FNSA4HC5-BUyqvlFE.js +89 -0
- package/studio/dist/assets/katex-B_JcIwqj.js +257 -0
- package/studio/dist/assets/katex-BgsA2lO1.css +1 -0
- package/studio/dist/assets/line-B08T8KlK.js +1 -0
- package/studio/dist/assets/linear-C9iwaMOi.js +1 -0
- package/studio/dist/assets/mermaid-parser.core-BYwreSUy.js +7 -0
- package/studio/dist/assets/mermaid.core-DTO1oDmq.js +44 -0
- package/studio/dist/assets/mindmap-definition-6OY57UG3-BGLwKheY.js +98 -0
- package/studio/dist/assets/node.browser-BISniYn8.js +1 -0
- package/studio/dist/assets/node.browser-HQ5SKBOf.js +1 -0
- package/studio/dist/assets/ollama-De5w84tG.js +1 -0
- package/studio/dist/assets/openai-compatible-Joy1oK3L.js +1 -0
- package/studio/dist/assets/ordinal-BDEzSJ7C.js +1 -0
- package/studio/dist/assets/packet-JDAUHWVQ-CDIMjHS_.js +1 -0
- package/studio/dist/assets/path-fybaL0A-.js +1 -0
- package/studio/dist/assets/pegDiagram-ECIUJNBX-DWXm4mwS.js +1 -0
- package/studio/dist/assets/pie-XZMESJXO-CY3QQiKZ.js +1 -0
- package/studio/dist/assets/pieDiagram-GNMPY74T-M1aCnI7W.js +39 -0
- package/studio/dist/assets/python.worker-CzTrFdd9.js +24 -0
- package/studio/dist/assets/quadrantDiagram-D57K4MC3-BFFtO7U5.js +7 -0
- package/studio/dist/assets/radar-ABXABTNO-DT0W4_Xn.js +1 -0
- package/studio/dist/assets/railroad-I3PHUGI6-_VCpgZR5.js +1 -0
- package/studio/dist/assets/railroad-abnf-LNEFI6M7-DkSpQ5YY.js +1 -0
- package/studio/dist/assets/railroad-ebnf-DWYD2UWJ-BqWILdGb.js +1 -0
- package/studio/dist/assets/railroad-peg-JR7BVNR7-BB0bQL-Z.js +1 -0
- package/studio/dist/assets/railroadDiagram-Y72NRBL7-CjLKjIdJ.js +1 -0
- package/studio/dist/assets/react-B8SKf8RM.js +1 -0
- package/studio/dist/assets/rehype-katex-C-sKddsH.js +259 -0
- package/studio/dist/assets/remark-math-C-ox67hE.js +5 -0
- package/studio/dist/assets/requirementDiagram-EQYSW5RA-CnwzZqR8.js +84 -0
- package/studio/dist/assets/rolldown-runtime-COiEfhsA.js +1 -0
- package/studio/dist/assets/rough.esm-Dy-Kn_BL.js +1 -0
- package/studio/dist/assets/sankeyDiagram-SY24JHO5-CPDugTUl.js +40 -0
- package/studio/dist/assets/schemas-BOc2YsOP.js +55 -0
- package/studio/dist/assets/sequenceDiagram-BLEHNHQU-1dLfeU9Y.js +169 -0
- package/studio/dist/assets/settings-DhwuyNPU.js +1 -0
- package/studio/dist/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
- package/studio/dist/assets/spec-nAnsSMkw.js +1 -0
- package/studio/dist/assets/sql-wasm-C1D_RJuy.wasm +0 -0
- package/studio/dist/assets/sql.worker-D7pBHOrq.js +4 -0
- package/studio/dist/assets/src-BWF7jMDv.js +1 -0
- package/studio/dist/assets/stateDiagram-v2-A6IESKWZ-DR7CmEjj.js +291 -0
- package/studio/dist/assets/swimlanes-B3ENRKFQ-BAWB9omQ.js +1 -0
- package/studio/dist/assets/swimlanesDiagram-AYQRV4AK-qeuHru16.js +8 -0
- package/studio/dist/assets/timeline-definition-JXYKGLGB-DNkj8z77.js +120 -0
- package/studio/dist/assets/treeView-D4JQ5SDB-BA-ouhMb.js +1 -0
- package/studio/dist/assets/treemap-SAJKECNS-BrrGxsAb.js +1 -0
- package/studio/dist/assets/useStream-rdsR6A7P.js +1 -0
- package/studio/dist/assets/usecaseDiagram-WPOLBNNX-BB3D0Dt_.js +328 -0
- package/studio/dist/assets/vennDiagram-Q3Y3IDCW-BrtqtKTE.js +34 -0
- package/studio/dist/assets/wardley-7MLQ67FV-BKkemDql.js +1 -0
- package/studio/dist/assets/wardleyDiagram-IVAIDKWA-DFnI3P5z.js +78 -0
- package/studio/dist/assets/xychartDiagram-4URNHDU4-Da0DZG_q.js +7 -0
- package/studio/dist/index.html +20 -0
- package/studio/dist/preview.html +22 -0
- package/studio/migrations/0001_init.sql +188 -0
- package/studio/migrations/0002_external_ids.sql +20 -0
- package/studio/migrations/0003_course_language.sql +12 -0
- package/studio/migrations/0004_capstone.sql +51 -0
- package/studio/migrations/0005_beta_loop.sql +73 -0
- package/studio/migrations/0006_interactive_exercises.sql +49 -0
- package/studio/src/lib/activity.js +92 -0
- package/studio/src/lib/ai/anthropic.js +109 -0
- package/studio/src/lib/ai/bridge.js +126 -0
- package/studio/src/lib/ai/catalog.js +160 -0
- package/studio/src/lib/ai/config.js +70 -0
- package/studio/src/lib/ai/errors.js +95 -0
- package/studio/src/lib/ai/fetch-local.js +104 -0
- package/studio/src/lib/ai/index.js +194 -0
- package/studio/src/lib/ai/ollama.js +136 -0
- package/studio/src/lib/ai/openai-compatible.js +101 -0
- package/studio/src/lib/ai/provider-utils.js +107 -0
- package/studio/src/lib/ai/sse.js +99 -0
- package/studio/src/lib/ai/status.js +92 -0
- package/studio/src/lib/ai/useStream.js +109 -0
- package/studio/src/lib/beta/metrics.js +147 -0
- package/studio/src/lib/beta/report.js +134 -0
- package/studio/src/lib/capstone-evidence.js +758 -0
- package/studio/src/lib/capstone.js +663 -0
- package/studio/src/lib/chat-store.js +128 -0
- package/studio/src/lib/course-export.js +38 -0
- package/studio/src/lib/course.js +187 -0
- package/studio/src/lib/exercises/journal-entry.js +259 -0
- package/studio/src/lib/exercises/placement.js +31 -0
- package/studio/src/lib/exercises/registry.js +19 -0
- package/studio/src/lib/exercises/spec.js +129 -0
- package/studio/src/lib/i18n/detect.js +162 -0
- package/studio/src/lib/i18n/en/app.json +23 -0
- package/studio/src/lib/i18n/en/betaReport.json +55 -0
- package/studio/src/lib/i18n/en/capstone.json +57 -0
- package/studio/src/lib/i18n/en/common.json +25 -0
- package/studio/src/lib/i18n/en/course.json +27 -0
- package/studio/src/lib/i18n/en/data.json +35 -0
- package/studio/src/lib/i18n/en/durability.json +5 -0
- package/studio/src/lib/i18n/en/errors.json +20 -0
- package/studio/src/lib/i18n/en/exercises.json +55 -0
- package/studio/src/lib/i18n/en/externalEvidence.json +76 -0
- package/studio/src/lib/i18n/en/home.json +18 -0
- package/studio/src/lib/i18n/en/lesson.json +33 -0
- package/studio/src/lib/i18n/en/nav.json +8 -0
- package/studio/src/lib/i18n/en/newCourse.json +51 -0
- package/studio/src/lib/i18n/en/onboarding.json +35 -0
- package/studio/src/lib/i18n/en/playwrightTemplate.json +20 -0
- package/studio/src/lib/i18n/en/practice.json +79 -0
- package/studio/src/lib/i18n/en/provider.json +27 -0
- package/studio/src/lib/i18n/en/providerMeta.json +26 -0
- package/studio/src/lib/i18n/en/reflection.json +17 -0
- package/studio/src/lib/i18n/en/revision.json +52 -0
- package/studio/src/lib/i18n/en/runnerNotice.json +14 -0
- package/studio/src/lib/i18n/en/settings.json +12 -0
- package/studio/src/lib/i18n/en/storage.json +49 -0
- package/studio/src/lib/i18n/en/tutor.json +39 -0
- package/studio/src/lib/i18n/en/workspace.json +50 -0
- package/studio/src/lib/i18n/index.js +144 -0
- package/studio/src/lib/i18n/languages.js +93 -0
- package/studio/src/lib/i18n/vi/app.json +23 -0
- package/studio/src/lib/i18n/vi/betaReport.json +55 -0
- package/studio/src/lib/i18n/vi/capstone.json +57 -0
- package/studio/src/lib/i18n/vi/common.json +25 -0
- package/studio/src/lib/i18n/vi/course.json +27 -0
- package/studio/src/lib/i18n/vi/data.json +35 -0
- package/studio/src/lib/i18n/vi/durability.json +5 -0
- package/studio/src/lib/i18n/vi/errors.json +20 -0
- package/studio/src/lib/i18n/vi/exercises.json +55 -0
- package/studio/src/lib/i18n/vi/externalEvidence.json +76 -0
- package/studio/src/lib/i18n/vi/home.json +18 -0
- package/studio/src/lib/i18n/vi/lesson.json +33 -0
- package/studio/src/lib/i18n/vi/nav.json +8 -0
- package/studio/src/lib/i18n/vi/newCourse.json +51 -0
- package/studio/src/lib/i18n/vi/onboarding.json +35 -0
- package/studio/src/lib/i18n/vi/playwrightTemplate.json +20 -0
- package/studio/src/lib/i18n/vi/practice.json +79 -0
- package/studio/src/lib/i18n/vi/provider.json +27 -0
- package/studio/src/lib/i18n/vi/providerMeta.json +26 -0
- package/studio/src/lib/i18n/vi/reflection.json +17 -0
- package/studio/src/lib/i18n/vi/revision.json +52 -0
- package/studio/src/lib/i18n/vi/runnerNotice.json +14 -0
- package/studio/src/lib/i18n/vi/settings.json +12 -0
- package/studio/src/lib/i18n/vi/storage.json +49 -0
- package/studio/src/lib/i18n/vi/tutor.json +39 -0
- package/studio/src/lib/i18n/vi/workspace.json +50 -0
- package/studio/src/lib/ids.js +10 -0
- package/studio/src/lib/json.js +26 -0
- package/studio/src/lib/markdown.js +74 -0
- package/studio/src/lib/prompts/base.js +139 -0
- package/studio/src/lib/prompts/capstone.js +777 -0
- package/studio/src/lib/prompts/course.js +150 -0
- package/studio/src/lib/prompts/lesson.js +275 -0
- package/studio/src/lib/prompts/practice.js +511 -0
- package/studio/src/lib/prompts/tutor.js +509 -0
- package/studio/src/lib/repo/blocked-adapter.js +69 -0
- package/studio/src/lib/repo/events.js +239 -0
- package/studio/src/lib/repo/idb-adapter.js +226 -0
- package/studio/src/lib/repo/index.js +1315 -0
- package/studio/src/lib/repo/memory-adapter.js +115 -0
- package/studio/src/lib/repo/remote-adapter.js +158 -0
- package/studio/src/lib/repo/schema.js +143 -0
- package/studio/src/lib/repo/select-adapter.js +345 -0
- package/studio/src/lib/router.js +71 -0
- package/studio/src/lib/runner/browser.js +244 -0
- package/studio/src/lib/runner/checks.js +248 -0
- package/studio/src/lib/runner/current-code.js +98 -0
- package/studio/src/lib/runner/esm-cjs.js +109 -0
- package/studio/src/lib/runner/hash.js +34 -0
- package/studio/src/lib/runner/history.js +48 -0
- package/studio/src/lib/runner/index.js +168 -0
- package/studio/src/lib/runner/languages.js +258 -0
- package/studio/src/lib/runner/local.js +139 -0
- package/studio/src/lib/runner/path.js +104 -0
- package/studio/src/lib/runner/skeleton.js +315 -0
- package/studio/src/lib/runner/strip-types.js +134 -0
- package/studio/src/lib/settings.js +399 -0
- package/studio/src/lib/srs.js +39 -0
- package/templates/agent/settings.json +6 -0
- package/templates/claude/plugin.json +139 -0
- package/templates/claude/settings.json +15 -0
- package/templates/cursor/professor.mdc +4 -0
- package/templates/cursor/settings.json +18 -0
- package/templates/gemini/GEMINI.md +28 -0
- package/templates/gemini/settings.json +5 -0
- package/templates/opencode/opencode.json +3 -0
- package/templates/researcher/README.md +61 -0
- package/templates/researcher/settings.json +18 -0
- package/web/client/components.json +15 -0
- package/web/client/index.html +12 -0
- package/web/client/package-lock.json +8732 -0
- package/web/client/package.json +39 -0
- package/web/client/postcss.config.js +6 -0
- package/web/client/src/App.jsx +140 -0
- package/web/client/src/api/client.js +357 -0
- package/web/client/src/components/ChatPanel.jsx +417 -0
- package/web/client/src/components/CommandPills.jsx +50 -0
- package/web/client/src/components/DeprecationBanner.jsx +21 -0
- package/web/client/src/components/ExerciseEditor.jsx +164 -0
- package/web/client/src/components/Header.jsx +119 -0
- package/web/client/src/components/LecturePanel.jsx +342 -0
- package/web/client/src/components/MobileTabSwitcher.jsx +51 -0
- package/web/client/src/components/SplitPane.jsx +42 -0
- package/web/client/src/components/ui/alert.jsx +38 -0
- package/web/client/src/components/ui/badge.jsx +24 -0
- package/web/client/src/components/ui/button.jsx +23 -0
- package/web/client/src/components/ui/select.jsx +74 -0
- package/web/client/src/hooks/useSSE.js +152 -0
- package/web/client/src/hooks/useWebSocket.js +98 -0
- package/web/client/src/index.css +23 -0
- package/web/client/src/lib/utils.js +6 -0
- package/web/client/src/main.jsx +12 -0
- package/web/client/src/utils/markdown.js +50 -0
- package/web/client/tailwind.config.js +8 -0
- package/web/client/vite.config.js +21 -0
- package/web/dist/assets/index-C1W0eXIQ.js +294 -0
- package/web/dist/assets/index-C3OUx70T.css +1 -0
- package/web/dist/index.html +13 -0
- package/web/lecture-archive.js +172 -0
- package/web/legacy-server.js +1277 -0
- package/web/package-lock.json +855 -0
- package/web/package.json +10 -0
- package/web/security.js +1016 -0
- package/web/server.js +390 -0
- package/web/studio-api/agents.js +580 -0
- package/web/studio-api/bridge.js +199 -0
- package/web/studio-api/db.js +66 -0
- package/web/studio-api/guards.js +201 -0
- package/web/studio-api/health.js +538 -0
- package/web/studio-api.js +214 -0
- package/web/studio-db.js +380 -0
- package/web/studio-runner.js +568 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Header.jsx - Course selector dropdown + progress indicator
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useState, useEffect } from 'react';
|
|
6
|
+
import { fetchCourses } from '../api/client.js';
|
|
7
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select.jsx';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Header component with course selector and progress indicator
|
|
11
|
+
* @param {Object} props
|
|
12
|
+
* @param {Object|null} props.selectedCourse - Currently selected course
|
|
13
|
+
* @param {Function} props.onCourseSelect - Callback when course is selected
|
|
14
|
+
*/
|
|
15
|
+
export default function Header({ selectedCourse, onCourseSelect }) {
|
|
16
|
+
const [courses, setCourses] = useState([]);
|
|
17
|
+
const [loading, setLoading] = useState(true);
|
|
18
|
+
const [error, setError] = useState(null);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
loadCourses();
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
async function loadCourses() {
|
|
25
|
+
setLoading(true);
|
|
26
|
+
setError(null);
|
|
27
|
+
try {
|
|
28
|
+
const data = await fetchCourses();
|
|
29
|
+
setCourses(data || []);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error('Failed to load courses:', err);
|
|
32
|
+
setError('Failed to load courses');
|
|
33
|
+
setCourses([]);
|
|
34
|
+
} finally {
|
|
35
|
+
setLoading(false);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function handleCourseChange(value) {
|
|
40
|
+
const slug = value === '__none__' ? '' : value;
|
|
41
|
+
const course = slug ? courses.find((c) => c.slug === slug) : null;
|
|
42
|
+
onCourseSelect?.(course);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Format last active timestamp
|
|
46
|
+
function formatLastActive(timestamp) {
|
|
47
|
+
if (!timestamp || timestamp === 'Unknown') return '';
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const date = new Date(timestamp);
|
|
51
|
+
if (isNaN(date.getTime())) return timestamp;
|
|
52
|
+
|
|
53
|
+
const now = new Date();
|
|
54
|
+
const diffMs = now - date;
|
|
55
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
56
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
57
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
58
|
+
|
|
59
|
+
if (diffMins < 1) return 'Just now';
|
|
60
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
61
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
62
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
63
|
+
|
|
64
|
+
return date.toLocaleDateString();
|
|
65
|
+
} catch {
|
|
66
|
+
return timestamp;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (loading) {
|
|
71
|
+
return (
|
|
72
|
+
<header className="flex shrink-0 items-center border-b border-slate-800 bg-slate-900 px-4 py-3 md:px-5">
|
|
73
|
+
<div className="text-sm text-slate-400">Loading courses...</div>
|
|
74
|
+
</header>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<header className="flex shrink-0 flex-wrap items-center gap-3 border-b border-slate-800 bg-slate-900 px-3 py-3 md:px-5">
|
|
80
|
+
<div className="flex items-center gap-2">
|
|
81
|
+
<span className="text-lg">📚</span>
|
|
82
|
+
<span className="text-lg font-semibold">Professor</span>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div className="w-full md:w-auto">
|
|
86
|
+
<Select
|
|
87
|
+
value={selectedCourse?.slug || '__none__'}
|
|
88
|
+
onValueChange={handleCourseChange}
|
|
89
|
+
disabled={loading}
|
|
90
|
+
>
|
|
91
|
+
<SelectTrigger className="h-9 w-full min-w-[180px] md:w-[220px]">
|
|
92
|
+
<SelectValue placeholder="Select course..." />
|
|
93
|
+
</SelectTrigger>
|
|
94
|
+
<SelectContent>
|
|
95
|
+
<SelectItem value="__none__">Select course...</SelectItem>
|
|
96
|
+
{courses.map((course) => (
|
|
97
|
+
<SelectItem key={course.slug} value={course.slug}>
|
|
98
|
+
{course.name}
|
|
99
|
+
</SelectItem>
|
|
100
|
+
))}
|
|
101
|
+
</SelectContent>
|
|
102
|
+
</Select>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
{selectedCourse && (
|
|
106
|
+
<div className="ml-0 flex w-full items-center gap-3 text-sm md:ml-auto md:w-auto">
|
|
107
|
+
<span className="font-medium text-slate-100">{selectedCourse.name}</span>
|
|
108
|
+
{selectedCourse.lastActive && (
|
|
109
|
+
<span className="text-xs text-slate-400">
|
|
110
|
+
{formatLastActive(selectedCourse.lastActive)}
|
|
111
|
+
</span>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
{error && <div className="text-xs text-red-400">{error}</div>}
|
|
117
|
+
</header>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LecturePanel.jsx - Markdown rendering with syntax highlighting
|
|
3
|
+
* Supports tabs: Lecture (LECTURE.md), Syllabus (COURSE.md), Exercises
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
7
|
+
import ReactMarkdown from 'react-markdown';
|
|
8
|
+
import remarkGfm from 'remark-gfm';
|
|
9
|
+
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
10
|
+
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
|
11
|
+
import { fetchCourseFile, fetchExercises, fetchExercise, saveExercise } from '../api/client.js';
|
|
12
|
+
import { Button } from './ui/button.jsx';
|
|
13
|
+
import ExerciseEditor from './ExerciseEditor.jsx';
|
|
14
|
+
|
|
15
|
+
const CODE_COMPONENTS = {
|
|
16
|
+
code({ node, inline, className, children, ...props }) {
|
|
17
|
+
const match = /language-(\w+)/.exec(className || '');
|
|
18
|
+
return !inline && match ? (
|
|
19
|
+
<SyntaxHighlighter
|
|
20
|
+
style={oneDark}
|
|
21
|
+
language={match[1]}
|
|
22
|
+
PreTag="div"
|
|
23
|
+
{...props}
|
|
24
|
+
>
|
|
25
|
+
{String(children).replace(/\n$/, '')}
|
|
26
|
+
</SyntaxHighlighter>
|
|
27
|
+
) : (
|
|
28
|
+
<code className={className} {...props}>
|
|
29
|
+
{children}
|
|
30
|
+
</code>
|
|
31
|
+
);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* LecturePanel component - displays markdown lecture content
|
|
37
|
+
* @param {Object} props
|
|
38
|
+
* @param {string|null} props.courseSlug - Current course slug
|
|
39
|
+
* @param {string} [props.content] - Optional pre-loaded content
|
|
40
|
+
* @param {Function} [props.onLectureUpdate] - Callback when lecture updates
|
|
41
|
+
*/
|
|
42
|
+
export default function LecturePanel({ courseSlug, content: initialContent, onLectureUpdate }) {
|
|
43
|
+
const [activeTab, setActiveTab] = useState('lecture'); // 'lecture' | 'syllabus' | 'exercises'
|
|
44
|
+
const [lectureContent, setLectureContent] = useState(initialContent || '');
|
|
45
|
+
const [syllabusContent, setSyllabusContent] = useState('');
|
|
46
|
+
const [exercises, setExercises] = useState([]);
|
|
47
|
+
const [activeExercise, setActiveExercise] = useState(null);
|
|
48
|
+
const [exerciseContent, setExerciseContent] = useState('');
|
|
49
|
+
const [selectedExercise, setSelectedExercise] = useState(null);
|
|
50
|
+
const [loading, setLoading] = useState(false);
|
|
51
|
+
const [error, setError] = useState(null);
|
|
52
|
+
const [saving, setSaving] = useState(false);
|
|
53
|
+
|
|
54
|
+
const content = activeTab === 'lecture' ? lectureContent :
|
|
55
|
+
activeTab === 'syllabus' ? syllabusContent : '';
|
|
56
|
+
|
|
57
|
+
const loadFile = useCallback(async (tab) => {
|
|
58
|
+
if (!courseSlug) return;
|
|
59
|
+
|
|
60
|
+
const file = tab === 'lecture' ? 'LECTURE.md' : 'COURSE.md';
|
|
61
|
+
setLoading(true);
|
|
62
|
+
setError(null);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const data = await fetchCourseFile(courseSlug, file);
|
|
66
|
+
const text = data?.content || '';
|
|
67
|
+
if (tab === 'lecture') {
|
|
68
|
+
setLectureContent(text);
|
|
69
|
+
} else {
|
|
70
|
+
setSyllabusContent(text);
|
|
71
|
+
}
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.error(`Failed to load ${file}:`, err);
|
|
74
|
+
setError(`Failed to load ${tab}`);
|
|
75
|
+
} finally {
|
|
76
|
+
setLoading(false);
|
|
77
|
+
}
|
|
78
|
+
}, [courseSlug]);
|
|
79
|
+
|
|
80
|
+
// Load exercises
|
|
81
|
+
const loadExercises = useCallback(async () => {
|
|
82
|
+
if (!courseSlug) return;
|
|
83
|
+
|
|
84
|
+
setLoading(true);
|
|
85
|
+
setError(null);
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const data = await fetchExercises(courseSlug);
|
|
89
|
+
setExercises(data.exercises || []);
|
|
90
|
+
setActiveExercise(data.activeExercise);
|
|
91
|
+
|
|
92
|
+
// Auto-select active exercise or first one
|
|
93
|
+
const toSelect = data.activeExercise || (data.exercises?.[0]?.filename);
|
|
94
|
+
if (toSelect) {
|
|
95
|
+
setSelectedExercise(toSelect);
|
|
96
|
+
const exerciseData = await fetchExercise(courseSlug, toSelect);
|
|
97
|
+
setExerciseContent(exerciseData.content);
|
|
98
|
+
}
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.error('Failed to load exercises:', err);
|
|
101
|
+
setError('Failed to load exercises');
|
|
102
|
+
} finally {
|
|
103
|
+
setLoading(false);
|
|
104
|
+
}
|
|
105
|
+
}, [courseSlug]);
|
|
106
|
+
|
|
107
|
+
// Handle exercise selection
|
|
108
|
+
const handleExerciseSelect = async (filename) => {
|
|
109
|
+
if (!courseSlug) return;
|
|
110
|
+
|
|
111
|
+
setSelectedExercise(filename);
|
|
112
|
+
try {
|
|
113
|
+
const data = await fetchExercise(courseSlug, filename);
|
|
114
|
+
setExerciseContent(data.content);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.error('Failed to load exercise:', err);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Handle exercise save
|
|
121
|
+
const handleExerciseSave = async () => {
|
|
122
|
+
if (!courseSlug || !selectedExercise) return;
|
|
123
|
+
|
|
124
|
+
setSaving(true);
|
|
125
|
+
try {
|
|
126
|
+
await saveExercise(courseSlug, selectedExercise, exerciseContent);
|
|
127
|
+
alert('Exercise saved!');
|
|
128
|
+
} catch (err) {
|
|
129
|
+
console.error('Failed to save exercise:', err);
|
|
130
|
+
alert('Failed to save exercise');
|
|
131
|
+
} finally {
|
|
132
|
+
setSaving(false);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Load both files on course change
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (!courseSlug) {
|
|
139
|
+
setLectureContent('');
|
|
140
|
+
setSyllabusContent('');
|
|
141
|
+
setExercises([]);
|
|
142
|
+
setSelectedExercise(null);
|
|
143
|
+
setExerciseContent('');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
loadFile('lecture');
|
|
147
|
+
loadFile('syllabus');
|
|
148
|
+
if (activeTab === 'exercises') {
|
|
149
|
+
loadExercises();
|
|
150
|
+
}
|
|
151
|
+
}, [courseSlug, loadFile, activeTab, loadExercises]);
|
|
152
|
+
|
|
153
|
+
// Load exercises when switching to exercises tab
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (activeTab === 'exercises' && courseSlug && exercises.length === 0) {
|
|
156
|
+
loadExercises();
|
|
157
|
+
}
|
|
158
|
+
}, [activeTab, courseSlug, exercises.length, loadExercises]);
|
|
159
|
+
|
|
160
|
+
// Refresh via custom events (fired when server saves course files)
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
const handleLecture = () => loadFile('lecture');
|
|
163
|
+
const handleSyllabus = () => loadFile('syllabus');
|
|
164
|
+
window.addEventListener('refresh-lecture', handleLecture);
|
|
165
|
+
window.addEventListener('refresh-syllabus', handleSyllabus);
|
|
166
|
+
return () => {
|
|
167
|
+
window.removeEventListener('refresh-lecture', handleLecture);
|
|
168
|
+
window.removeEventListener('refresh-syllabus', handleSyllabus);
|
|
169
|
+
};
|
|
170
|
+
}, [loadFile]);
|
|
171
|
+
|
|
172
|
+
// Notify parent of lecture content changes
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
if (onLectureUpdate) {
|
|
175
|
+
onLectureUpdate(lectureContent);
|
|
176
|
+
}
|
|
177
|
+
}, [lectureContent, onLectureUpdate]);
|
|
178
|
+
|
|
179
|
+
if (!courseSlug) {
|
|
180
|
+
return (
|
|
181
|
+
<div className="flex h-full flex-col items-center justify-center gap-3 px-6 text-center text-slate-400">
|
|
182
|
+
<div className="flex flex-col items-center gap-2">
|
|
183
|
+
<span className="text-5xl">📖</span>
|
|
184
|
+
<p>Select a course to see the current lecture.</p>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<div className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
192
|
+
<div className="flex shrink-0 gap-2 border-b border-slate-800 bg-slate-900/70 px-3 pt-2 md:px-4">
|
|
193
|
+
<Button
|
|
194
|
+
variant="ghost"
|
|
195
|
+
className={`h-9 rounded-b-none border-b-2 px-3 ${
|
|
196
|
+
activeTab === 'lecture'
|
|
197
|
+
? 'border-indigo-400 text-indigo-300'
|
|
198
|
+
: 'border-transparent text-slate-400'
|
|
199
|
+
}`}
|
|
200
|
+
onClick={() => setActiveTab('lecture')}
|
|
201
|
+
>
|
|
202
|
+
📝 Lecture
|
|
203
|
+
</Button>
|
|
204
|
+
<Button
|
|
205
|
+
variant="ghost"
|
|
206
|
+
className={`h-9 rounded-b-none border-b-2 px-3 ${
|
|
207
|
+
activeTab === 'syllabus'
|
|
208
|
+
? 'border-indigo-400 text-indigo-300'
|
|
209
|
+
: 'border-transparent text-slate-400'
|
|
210
|
+
}`}
|
|
211
|
+
onClick={() => setActiveTab('syllabus')}
|
|
212
|
+
>
|
|
213
|
+
📋 Syllabus
|
|
214
|
+
</Button>
|
|
215
|
+
<Button
|
|
216
|
+
variant="ghost"
|
|
217
|
+
className={`h-9 rounded-b-none border-b-2 px-3 ${
|
|
218
|
+
activeTab === 'exercises'
|
|
219
|
+
? 'border-indigo-400 text-indigo-300'
|
|
220
|
+
: 'border-transparent text-slate-400'
|
|
221
|
+
}`}
|
|
222
|
+
onClick={() => setActiveTab('exercises')}
|
|
223
|
+
>
|
|
224
|
+
💪 Exercises
|
|
225
|
+
{activeExercise && <span className="ml-1 text-[10px] text-emerald-500">●</span>}
|
|
226
|
+
</Button>
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
{loading ? (
|
|
230
|
+
<div className="flex h-full flex-col items-center justify-center gap-3 px-6 text-center text-slate-400">
|
|
231
|
+
<div className="h-8 w-8 animate-spin rounded-full border-2 border-slate-700 border-t-indigo-400" />
|
|
232
|
+
<p>Loading...</p>
|
|
233
|
+
</div>
|
|
234
|
+
) : error ? (
|
|
235
|
+
<div className="flex h-full flex-col items-center justify-center gap-3 px-6 text-center text-slate-400">
|
|
236
|
+
<p>{error}</p>
|
|
237
|
+
<Button onClick={() => loadFile(activeTab)}>Retry</Button>
|
|
238
|
+
</div>
|
|
239
|
+
) : activeTab === 'exercises' ? (
|
|
240
|
+
<div className="flex min-h-0 h-full overflow-hidden">
|
|
241
|
+
{exercises.length === 0 ? (
|
|
242
|
+
<div className="flex h-full flex-1 flex-col items-center justify-center gap-3 px-6 text-center text-slate-400">
|
|
243
|
+
<div className="flex flex-col items-center gap-2">
|
|
244
|
+
<span className="text-5xl">💪</span>
|
|
245
|
+
<p>No exercises yet.</p>
|
|
246
|
+
<p className="text-sm">
|
|
247
|
+
Type <code className="rounded bg-slate-800 px-1.5 py-0.5 text-slate-200">professor:next</code> in chat to
|
|
248
|
+
create exercises.
|
|
249
|
+
</p>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
) : (
|
|
253
|
+
<>
|
|
254
|
+
<div className="hidden w-48 shrink-0 border-r border-slate-800 bg-slate-900/40 md:block">
|
|
255
|
+
<div className="p-3">
|
|
256
|
+
<h4 className="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-400">
|
|
257
|
+
Exercises
|
|
258
|
+
</h4>
|
|
259
|
+
{exercises.map((ex) => (
|
|
260
|
+
<Button
|
|
261
|
+
key={ex.filename}
|
|
262
|
+
variant={selectedExercise === ex.filename ? 'secondary' : 'ghost'}
|
|
263
|
+
className="mb-1 h-auto w-full justify-start px-2 py-2 text-left text-xs"
|
|
264
|
+
onClick={() => handleExerciseSelect(ex.filename)}
|
|
265
|
+
>
|
|
266
|
+
{ex.filename.replace('.md', '')}
|
|
267
|
+
{activeExercise === ex.filename && (
|
|
268
|
+
<span className="ml-1 text-[10px] text-emerald-500">●</span>
|
|
269
|
+
)}
|
|
270
|
+
</Button>
|
|
271
|
+
))}
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">
|
|
275
|
+
<div className="flex items-center justify-between border-b border-slate-800 bg-slate-900/40 px-3 py-2 md:px-4">
|
|
276
|
+
<h4 className="text-sm font-medium">{selectedExercise}</h4>
|
|
277
|
+
<Button
|
|
278
|
+
size="sm"
|
|
279
|
+
onClick={handleExerciseSave}
|
|
280
|
+
disabled={saving}
|
|
281
|
+
>
|
|
282
|
+
{saving ? 'Saving...' : '💾 Save'}
|
|
283
|
+
</Button>
|
|
284
|
+
</div>
|
|
285
|
+
<div className="border-b border-slate-800 bg-slate-900/40 p-2 md:hidden">
|
|
286
|
+
<select
|
|
287
|
+
value={selectedExercise || ''}
|
|
288
|
+
onChange={(e) => handleExerciseSelect(e.target.value)}
|
|
289
|
+
className="h-9 w-full rounded-md border border-slate-700 bg-slate-950 px-2 text-sm text-slate-100"
|
|
290
|
+
>
|
|
291
|
+
{exercises.map((ex) => (
|
|
292
|
+
<option key={ex.filename} value={ex.filename}>
|
|
293
|
+
{ex.filename.replace('.md', '')}
|
|
294
|
+
</option>
|
|
295
|
+
))}
|
|
296
|
+
</select>
|
|
297
|
+
</div>
|
|
298
|
+
<div className="min-h-0 flex-1 overflow-hidden">
|
|
299
|
+
<ExerciseEditor
|
|
300
|
+
value={exerciseContent}
|
|
301
|
+
onChange={setExerciseContent}
|
|
302
|
+
/>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
</>
|
|
306
|
+
)}
|
|
307
|
+
</div>
|
|
308
|
+
) : !content ? (
|
|
309
|
+
<div className="flex h-full flex-col items-center justify-center gap-3 px-6 text-center text-slate-400">
|
|
310
|
+
<div className="flex flex-col items-center gap-2">
|
|
311
|
+
<span className="text-5xl">✨</span>
|
|
312
|
+
{activeTab === 'lecture' ? (
|
|
313
|
+
<>
|
|
314
|
+
<p>No lecture loaded yet.</p>
|
|
315
|
+
<p className="text-sm">
|
|
316
|
+
Type <code className="rounded bg-slate-800 px-1.5 py-0.5 text-slate-200">professor:next</code> in chat to
|
|
317
|
+
start your first section.
|
|
318
|
+
</p>
|
|
319
|
+
</>
|
|
320
|
+
) : (
|
|
321
|
+
<>
|
|
322
|
+
<p>No syllabus found.</p>
|
|
323
|
+
<p className="text-sm">
|
|
324
|
+
Type <code className="rounded bg-slate-800 px-1.5 py-0.5 text-slate-200">professor:new-topic</code> in chat
|
|
325
|
+
to create a course.
|
|
326
|
+
</p>
|
|
327
|
+
</>
|
|
328
|
+
)}
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
) : (
|
|
332
|
+
<div className="min-h-0 flex-1 overflow-y-auto px-4 py-5 md:px-6">
|
|
333
|
+
<div className="max-w-none text-sm leading-7 text-slate-200 [&_a]:text-indigo-300 [&_a:hover]:underline [&_blockquote]:my-4 [&_blockquote]:border-l-2 [&_blockquote]:border-indigo-400 [&_blockquote]:pl-4 [&_blockquote]:italic [&_code]:rounded [&_code]:bg-slate-800 [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:text-xs [&_h1]:mt-6 [&_h1]:text-2xl [&_h1]:font-semibold [&_h2]:mt-5 [&_h2]:text-xl [&_h2]:font-semibold [&_h3]:mt-4 [&_h3]:text-lg [&_h3]:font-medium [&_hr]:my-6 [&_hr]:border-slate-700 [&_img]:my-3 [&_img]:max-w-full [&_img]:rounded-md [&_li]:my-1 [&_ol]:my-3 [&_ol]:pl-6 [&_p]:my-3 [&_pre]:my-4 [&_pre]:overflow-x-auto [&_pre]:rounded-md [&_pre]:border [&_pre]:border-slate-700 [&_pre]:bg-slate-900 [&_pre]:p-4 [&_table]:my-4 [&_table]:w-full [&_table]:border-collapse [&_td]:border [&_td]:border-slate-700 [&_td]:p-2 [&_th]:border [&_th]:border-slate-700 [&_th]:bg-slate-800 [&_th]:p-2 [&_th]:text-left [&_ul]:my-3 [&_ul]:pl-6">
|
|
334
|
+
<ReactMarkdown remarkPlugins={[remarkGfm]} components={CODE_COMPONENTS}>
|
|
335
|
+
{content}
|
|
336
|
+
</ReactMarkdown>
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
)}
|
|
340
|
+
</div>
|
|
341
|
+
);
|
|
342
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MobileTabSwitcher.jsx - Mobile view toggle
|
|
3
|
+
*/
|
|
4
|
+
import { Button } from './ui/button.jsx';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* MobileTabSwitcher component - toggle between lecture and chat on mobile
|
|
8
|
+
* @param {Object} props
|
|
9
|
+
* @param {string} props.activeTab - Current active tab: 'lecture' | 'chat'
|
|
10
|
+
* @param {Function} props.onTabChange - Callback when tab changes
|
|
11
|
+
*/
|
|
12
|
+
export default function MobileTabSwitcher({ activeTab = 'lecture', onTabChange }) {
|
|
13
|
+
function handleLectureClick() {
|
|
14
|
+
onTabChange?.('lecture');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function handleChatClick() {
|
|
18
|
+
onTabChange?.('chat');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="flex shrink-0 border-b border-slate-800 bg-slate-900 md:hidden">
|
|
23
|
+
<Button
|
|
24
|
+
variant="ghost"
|
|
25
|
+
className={`h-11 flex-1 justify-center rounded-none border-b-2 ${
|
|
26
|
+
activeTab === 'lecture'
|
|
27
|
+
? 'border-indigo-400 text-indigo-300'
|
|
28
|
+
: 'border-transparent text-slate-400'
|
|
29
|
+
}`}
|
|
30
|
+
onClick={handleLectureClick}
|
|
31
|
+
aria-pressed={activeTab === 'lecture'}
|
|
32
|
+
>
|
|
33
|
+
<span>📖</span>
|
|
34
|
+
<span>Lecture</span>
|
|
35
|
+
</Button>
|
|
36
|
+
<Button
|
|
37
|
+
variant="ghost"
|
|
38
|
+
className={`h-11 flex-1 justify-center rounded-none border-b-2 ${
|
|
39
|
+
activeTab === 'chat'
|
|
40
|
+
? 'border-indigo-400 text-indigo-300'
|
|
41
|
+
: 'border-transparent text-slate-400'
|
|
42
|
+
}`}
|
|
43
|
+
onClick={handleChatClick}
|
|
44
|
+
aria-pressed={activeTab === 'chat'}
|
|
45
|
+
>
|
|
46
|
+
<span>💬</span>
|
|
47
|
+
<span>Chat</span>
|
|
48
|
+
</Button>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SplitPane.jsx - 50/50 split layout with divider
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* SplitPane component - 50/50 split layout with mobile support
|
|
7
|
+
* @param {Object} props
|
|
8
|
+
* @param {React.ReactNode} props.lecturePanel - Content for lecture panel (left)
|
|
9
|
+
* @param {React.ReactNode} props.chatPanel - Content for chat panel (right)
|
|
10
|
+
* @param {string} [props.activeTab] - 'lecture' | 'chat' - which panel to show on mobile
|
|
11
|
+
* @param {string} [props.mobileTab] - Current mobile tab state (controlled)
|
|
12
|
+
*/
|
|
13
|
+
export default function SplitPane({
|
|
14
|
+
lecturePanel,
|
|
15
|
+
chatPanel,
|
|
16
|
+
activeTab = 'both',
|
|
17
|
+
mobileTab = 'lecture'
|
|
18
|
+
}) {
|
|
19
|
+
// On mobile, show only the active tab
|
|
20
|
+
const showLecture = activeTab === 'both' || mobileTab === 'lecture';
|
|
21
|
+
const showChat = activeTab === 'both' || mobileTab === 'chat';
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="flex h-full min-h-0 w-full flex-1 flex-col overflow-hidden md:flex-row">
|
|
25
|
+
{showLecture && (
|
|
26
|
+
<div className="flex min-h-0 w-full flex-1 flex-col overflow-hidden md:w-1/2">
|
|
27
|
+
{lecturePanel}
|
|
28
|
+
</div>
|
|
29
|
+
)}
|
|
30
|
+
|
|
31
|
+
{activeTab === 'both' && showLecture && showChat && (
|
|
32
|
+
<div className="hidden w-px shrink-0 bg-border md:block" />
|
|
33
|
+
)}
|
|
34
|
+
|
|
35
|
+
{showChat && (
|
|
36
|
+
<div className="flex min-h-0 w-full flex-1 flex-col overflow-hidden md:w-1/2">
|
|
37
|
+
{chatPanel}
|
|
38
|
+
</div>
|
|
39
|
+
)}
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { cn } from '../../lib/utils.js';
|
|
2
|
+
|
|
3
|
+
export function Alert({ className, children, variant = 'default', ...props }) {
|
|
4
|
+
const variants = {
|
|
5
|
+
default: 'border-slate-700 bg-slate-900 text-slate-200',
|
|
6
|
+
destructive: 'border-red-700/60 bg-red-900/30 text-red-200',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div
|
|
11
|
+
role="alert"
|
|
12
|
+
className={cn(
|
|
13
|
+
'rounded-md border px-3 py-2 text-sm',
|
|
14
|
+
variants[variant] || variants.default,
|
|
15
|
+
className
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
>
|
|
19
|
+
{children}
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function AlertTitle({ className, children, ...props }) {
|
|
25
|
+
return (
|
|
26
|
+
<h5 className={cn('mb-1 text-sm font-semibold', className)} {...props}>
|
|
27
|
+
{children}
|
|
28
|
+
</h5>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function AlertDescription({ className, children, ...props }) {
|
|
33
|
+
return (
|
|
34
|
+
<div className={cn('text-xs leading-5', className)} {...props}>
|
|
35
|
+
{children}
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { cn } from '../../lib/utils.js';
|
|
2
|
+
|
|
3
|
+
export function Badge({ className, children, variant = 'default', ...props }) {
|
|
4
|
+
const variants = {
|
|
5
|
+
default: 'border border-slate-600 bg-slate-800 text-slate-100',
|
|
6
|
+
secondary: 'border border-slate-700 bg-slate-900 text-slate-300',
|
|
7
|
+
warning: 'border border-amber-600/60 bg-amber-900/40 text-amber-200',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<span
|
|
12
|
+
className={cn(
|
|
13
|
+
'inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium',
|
|
14
|
+
variants[variant] || variants.default,
|
|
15
|
+
className
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
>
|
|
19
|
+
{children}
|
|
20
|
+
</span>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default Badge;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { cn } from '../../lib/utils.js';
|
|
2
|
+
|
|
3
|
+
export function Button({ className, variant = 'default', children, ...props }) {
|
|
4
|
+
const variants = {
|
|
5
|
+
default: 'bg-indigo-600 text-white hover:bg-indigo-700 disabled:opacity-40',
|
|
6
|
+
secondary: 'bg-slate-800 text-slate-100 border border-slate-700 hover:bg-slate-700',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<button
|
|
11
|
+
className={cn(
|
|
12
|
+
'inline-flex items-center justify-center rounded-md px-3 py-2 text-sm font-medium transition-colors',
|
|
13
|
+
variants[variant] || variants.default,
|
|
14
|
+
className
|
|
15
|
+
)}
|
|
16
|
+
{...props}
|
|
17
|
+
>
|
|
18
|
+
{children}
|
|
19
|
+
</button>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default Button;
|