cotutor 0.2.0 → 0.4.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.
Files changed (912) hide show
  1. package/LICENSE +249 -21
  2. package/NOTICE +42 -0
  3. package/README.md +115 -20
  4. package/avatars/chinese.webp +0 -0
  5. package/avatars/english.webp +0 -0
  6. package/avatars/math.webp +0 -0
  7. package/bin/cotutor.js +15 -5
  8. package/cards/canvas/card.css +8 -0
  9. package/cards/canvas/card.md +68 -0
  10. package/cards/choice/card.css +18 -0
  11. package/cards/choice/card.md +55 -0
  12. package/cards/code/card.css +5 -0
  13. package/cards/code/card.md +46 -0
  14. package/cards/dictation/card.css +10 -0
  15. package/cards/dictation/card.md +97 -0
  16. package/cards/image/card.css +10 -0
  17. package/cards/image/card.md +52 -0
  18. package/cards/lecture/card.css +9 -0
  19. package/cards/lecture/card.md +69 -0
  20. package/cards/paint/card.css +20 -0
  21. package/cards/paint/card.md +77 -0
  22. package/cards/photo/card.css +22 -0
  23. package/cards/photo/card.md +61 -0
  24. package/cards/read/card.css +6 -0
  25. package/cards/read/card.md +51 -0
  26. package/cards/text/card.css +3 -0
  27. package/cards/text/card.md +69 -0
  28. package/cards/tianzige/card.css +17 -0
  29. package/cards/tianzige/card.md +78 -0
  30. package/cards/tutor/card.css +79 -0
  31. package/cards/tutor/card.md +149 -0
  32. package/cards/word/card.css +24 -0
  33. package/cards/word/card.md +91 -0
  34. package/cards//346/235/277/344/271/246/346/200/216/344/271/210/345/206/231.md +159 -0
  35. package/dist/app/activity-eval-rules.d.ts +62 -0
  36. package/dist/app/activity-eval-rules.js +276 -0
  37. package/dist/app/activity-eval.d.ts +65 -0
  38. package/dist/app/activity-eval.js +159 -0
  39. package/dist/app/build.d.ts +11 -0
  40. package/dist/app/build.js +45 -0
  41. package/dist/app/cli/activities.d.ts +70 -0
  42. package/dist/app/cli/activities.js +196 -0
  43. package/dist/app/cli/cert.d.ts +12 -0
  44. package/dist/app/cli/cert.js +49 -0
  45. package/dist/app/cli/chalk/bake.d.ts +10 -0
  46. package/dist/app/cli/chalk/bake.js +58 -0
  47. package/dist/app/cli/chalk/browser.d.ts +16 -0
  48. package/dist/app/cli/chalk/browser.js +157 -0
  49. package/dist/app/cli/chalk/build.d.ts +14 -0
  50. package/dist/app/cli/chalk/build.js +100 -0
  51. package/dist/app/cli/chalk/dub.d.ts +51 -0
  52. package/dist/app/cli/chalk/dub.js +106 -0
  53. package/dist/app/cli/chalk/load-scene.d.ts +34 -0
  54. package/dist/app/cli/chalk/load-scene.js +117 -0
  55. package/dist/app/cli/chalk/main.d.ts +7 -0
  56. package/dist/app/cli/chalk/main.js +199 -0
  57. package/dist/app/cli/chalk/serve.d.ts +4 -0
  58. package/dist/app/cli/chalk/serve.js +28 -0
  59. package/dist/app/cli/chalk/snap.d.ts +65 -0
  60. package/dist/app/cli/chalk/snap.js +103 -0
  61. package/dist/app/cli/chalk/typecheck.d.ts +7 -0
  62. package/dist/app/cli/chalk/typecheck.js +51 -0
  63. package/dist/app/cli/doctor.d.ts +24 -0
  64. package/dist/app/cli/doctor.js +553 -0
  65. package/dist/app/cli/firewall.d.ts +24 -0
  66. package/dist/app/cli/firewall.js +79 -0
  67. package/dist/app/cli/grades.d.ts +14 -0
  68. package/dist/app/cli/grades.js +44 -0
  69. package/dist/{cli → app/cli}/init.d.ts +3 -1
  70. package/dist/app/cli/init.js +158 -0
  71. package/dist/app/cli/main.js +1038 -0
  72. package/dist/app/cli/migrate.d.ts +49 -0
  73. package/dist/app/cli/migrate.js +195 -0
  74. package/dist/app/cli/port-holder.d.ts +16 -0
  75. package/dist/app/cli/port-holder.js +34 -0
  76. package/dist/app/cli/port.d.ts +37 -0
  77. package/dist/app/cli/port.js +116 -0
  78. package/dist/app/cli/sandbox.d.ts +25 -0
  79. package/dist/app/cli/sandbox.js +166 -0
  80. package/dist/app/cli/serve-log.d.ts +13 -0
  81. package/dist/app/cli/serve-log.js +90 -0
  82. package/dist/app/cli/serve.d.ts +51 -0
  83. package/dist/app/cli/serve.js +168 -0
  84. package/dist/app/cli/service-windows.d.ts +50 -0
  85. package/dist/app/cli/service-windows.js +117 -0
  86. package/dist/app/cli/service.d.ts +138 -0
  87. package/dist/app/cli/service.js +429 -0
  88. package/dist/app/cli/shipped.d.ts +30 -0
  89. package/dist/app/cli/shipped.js +47 -0
  90. package/dist/app/cli/skeleton.d.ts +38 -0
  91. package/dist/app/cli/skeleton.js +161 -0
  92. package/dist/app/cli/skills.d.ts +59 -0
  93. package/dist/app/cli/skills.js +199 -0
  94. package/dist/app/cli/themes.d.ts +52 -0
  95. package/dist/app/cli/themes.js +148 -0
  96. package/dist/app/cli/tray-icons.d.ts +11 -0
  97. package/dist/app/cli/tray-icons.js +87 -0
  98. package/dist/app/cli/tray-ids.d.ts +6 -0
  99. package/dist/app/cli/tray-ids.js +6 -0
  100. package/dist/app/cli/tray-mac.d.ts +8 -0
  101. package/dist/app/cli/tray-mac.js +140 -0
  102. package/dist/app/cli/tray-run.d.ts +1 -0
  103. package/dist/app/cli/tray-run.js +33 -0
  104. package/dist/app/cli/tray-windows.d.ts +5 -0
  105. package/dist/app/cli/tray-windows.js +166 -0
  106. package/dist/app/cli/tray.d.ts +205 -0
  107. package/dist/app/cli/tray.js +618 -0
  108. package/dist/{cli → app/cli}/tutors.d.ts +15 -26
  109. package/dist/{cli → app/cli}/tutors.js +86 -110
  110. package/dist/app/cli/usage.d.ts +2 -0
  111. package/dist/app/cli/usage.js +35 -0
  112. package/dist/app/dictation/recognize.d.ts +18 -0
  113. package/dist/app/dictation/recognize.js +6 -0
  114. package/dist/app/dictation/route.d.ts +52 -0
  115. package/dist/app/dictation/route.js +511 -0
  116. package/dist/app/diff.d.ts +11 -0
  117. package/dist/app/diff.js +71 -0
  118. package/dist/app/http/access.d.ts +62 -0
  119. package/dist/app/http/access.js +180 -0
  120. package/dist/app/http/app.d.ts +241 -0
  121. package/dist/app/http/app.js +1159 -0
  122. package/dist/app/http/avatars.d.ts +17 -0
  123. package/dist/app/http/avatars.js +35 -0
  124. package/dist/app/http/dev-activities.d.ts +115 -0
  125. package/dist/app/http/dev-activities.js +193 -0
  126. package/dist/app/http/dev-overview.d.ts +108 -0
  127. package/dist/app/http/dev-overview.js +209 -0
  128. package/dist/app/http/dev-tutor-files.d.ts +80 -0
  129. package/dist/app/http/dev-tutor-files.js +151 -0
  130. package/dist/app/http/parent-home.d.ts +51 -0
  131. package/dist/app/http/parent-home.js +266 -0
  132. package/dist/app/http/parent-look.d.ts +2 -0
  133. package/dist/app/http/parent-look.js +151 -0
  134. package/dist/app/http/range.d.ts +9 -0
  135. package/dist/app/http/range.js +19 -0
  136. package/dist/app/http/raw-view.d.ts +117 -0
  137. package/dist/app/http/raw-view.js +149 -0
  138. package/dist/app/http/send-file.d.ts +7 -0
  139. package/dist/app/http/send-file.js +29 -0
  140. package/dist/{server → app/http}/stage.d.ts +9 -0
  141. package/dist/{server → app/http}/stage.js +34 -6
  142. package/dist/app/http/theme.d.ts +13 -0
  143. package/dist/app/http/theme.js +41 -0
  144. package/dist/app/http/voice-preview.d.ts +4 -0
  145. package/dist/app/http/voice-preview.js +34 -0
  146. package/dist/app/machine/bailian.d.ts +26 -0
  147. package/dist/app/machine/bailian.js +87 -0
  148. package/dist/app/mock/mock-live.d.ts +16 -0
  149. package/dist/app/mock/mock-live.js +140 -0
  150. package/dist/{server → app/mock}/mock.d.ts +38 -5
  151. package/dist/app/mock/mock.js +1125 -0
  152. package/dist/app/replay.d.ts +94 -0
  153. package/dist/app/replay.js +170 -0
  154. package/dist/app/skills/analyze-doc.d.ts +3 -0
  155. package/dist/app/skills/analyze-doc.js +47 -0
  156. package/dist/app/skills/home-doc.d.ts +5 -0
  157. package/dist/app/skills/home-doc.js +112 -0
  158. package/dist/app/skills/tune-doc.d.ts +3 -0
  159. package/dist/app/skills/tune-doc.js +80 -0
  160. package/dist/app/talk/list.d.ts +11 -0
  161. package/dist/app/talk/list.js +9 -0
  162. package/dist/app/talk/route.d.ts +16 -0
  163. package/dist/app/talk/route.js +142 -0
  164. package/dist/app/workspace.d.ts +63 -0
  165. package/dist/{cli → app}/workspace.js +100 -52
  166. package/dist/app/ws/route.d.ts +31 -0
  167. package/dist/app/ws/route.js +33 -0
  168. package/dist/app/ws/ws.d.ts +31 -0
  169. package/dist/app/ws/ws.js +169 -0
  170. package/dist/board/canvas-chat.d.ts +97 -0
  171. package/dist/board/canvas-chat.js +175 -0
  172. package/dist/board/card-guards.d.ts +15 -0
  173. package/dist/board/card-guards.js +38 -0
  174. package/dist/board/cards/canvas.d.ts +41 -0
  175. package/dist/board/cards/canvas.js +83 -0
  176. package/dist/{cards → board/cards}/choice.js +0 -12
  177. package/dist/{cards → board/cards}/code.js +0 -3
  178. package/dist/board/cards/dictation.d.ts +42 -0
  179. package/dist/board/cards/dictation.js +105 -0
  180. package/dist/board/cards/docs.d.ts +47 -0
  181. package/dist/board/cards/docs.js +152 -0
  182. package/dist/{cards → board/cards}/image.js +1 -10
  183. package/dist/board/cards/index.d.ts +62 -0
  184. package/dist/board/cards/index.js +144 -0
  185. package/dist/{cards → board/cards}/kind.d.ts +10 -4
  186. package/dist/board/cards/kind.js +9 -0
  187. package/dist/board/cards/lecture.d.ts +26 -0
  188. package/dist/board/cards/lecture.js +62 -0
  189. package/dist/board/cards/paint.d.ts +38 -0
  190. package/dist/board/cards/paint.js +85 -0
  191. package/dist/board/cards/photo.d.ts +15 -0
  192. package/dist/board/cards/photo.js +29 -0
  193. package/dist/{cards → board/cards}/read.js +1 -10
  194. package/dist/board/cards/text.d.ts +20 -0
  195. package/dist/board/cards/text.js +59 -0
  196. package/dist/board/cards/tianzige.d.ts +16 -0
  197. package/dist/board/cards/tianzige.js +64 -0
  198. package/dist/board/cards/tutor.d.ts +72 -0
  199. package/dist/board/cards/tutor.js +134 -0
  200. package/dist/board/cards/word.d.ts +20 -0
  201. package/dist/board/cards/word.js +76 -0
  202. package/dist/board/lecture.d.ts +100 -0
  203. package/dist/board/lecture.js +204 -0
  204. package/dist/board/paper.d.ts +20 -0
  205. package/dist/board/paper.js +76 -0
  206. package/dist/board/parse.d.ts +118 -0
  207. package/dist/board/parse.js +324 -0
  208. package/dist/board/presentation.d.ts +289 -0
  209. package/dist/board/presentation.js +613 -0
  210. package/dist/board/sections.d.ts +16 -0
  211. package/dist/board/sections.js +130 -0
  212. package/dist/brain/api-tools.d.ts +8 -0
  213. package/dist/brain/api-tools.js +191 -0
  214. package/dist/brain/api.d.ts +57 -0
  215. package/dist/brain/api.js +394 -0
  216. package/dist/brain/index.d.ts +97 -0
  217. package/dist/brain/index.js +11 -0
  218. package/dist/brain/log.d.ts +5 -0
  219. package/dist/brain/log.js +22 -0
  220. package/dist/brain/runtime.d.ts +30 -0
  221. package/dist/brain/runtime.js +23 -0
  222. package/dist/brain/stream-json.d.ts +16 -0
  223. package/dist/brain/stream-json.js +82 -0
  224. package/dist/brain/stream.d.ts +20 -0
  225. package/dist/brain/stream.js +70 -0
  226. package/dist/brain/transcript.d.ts +45 -0
  227. package/dist/brain/transcript.js +132 -0
  228. package/dist/brain/types.d.ts +99 -0
  229. package/dist/brain/types.js +14 -0
  230. package/dist/chalk/actions.d.ts +37 -0
  231. package/dist/chalk/actions.js +69 -0
  232. package/dist/chalk/bundle.d.ts +26 -0
  233. package/dist/chalk/bundle.js +69 -0
  234. package/dist/chalk/check.d.ts +28 -0
  235. package/dist/chalk/check.js +291 -0
  236. package/dist/chalk/circle.d.ts +64 -0
  237. package/dist/chalk/circle.js +193 -0
  238. package/dist/chalk/clock.d.ts +84 -0
  239. package/dist/chalk/clock.js +84 -0
  240. package/dist/chalk/geometry.d.ts +44 -0
  241. package/dist/chalk/geometry.js +139 -0
  242. package/dist/chalk/glyphs/capitals.d.ts +12 -0
  243. package/dist/chalk/glyphs/capitals.js +264 -0
  244. package/dist/chalk/glyphs/index.d.ts +21 -0
  245. package/dist/chalk/glyphs/index.js +18 -0
  246. package/dist/chalk/glyphs/letters.d.ts +13 -0
  247. package/dist/chalk/glyphs/letters.js +280 -0
  248. package/dist/chalk/glyphs/metrics.d.ts +33 -0
  249. package/dist/chalk/glyphs/metrics.js +28 -0
  250. package/dist/chalk/glyphs/registry.d.ts +7 -0
  251. package/dist/chalk/glyphs/registry.js +8 -0
  252. package/dist/chalk/glyphs/sample.d.ts +32 -0
  253. package/dist/chalk/glyphs/sample.js +102 -0
  254. package/dist/chalk/glyphs/skeleton.d.ts +64 -0
  255. package/dist/chalk/glyphs/skeleton.js +126 -0
  256. package/dist/chalk/glyphs/types.d.ts +58 -0
  257. package/dist/chalk/glyphs/types.js +17 -0
  258. package/dist/chalk/index.d.ts +23 -0
  259. package/dist/chalk/index.js +15 -0
  260. package/dist/chalk/manifest.d.ts +41 -0
  261. package/dist/chalk/manifest.js +6 -0
  262. package/dist/chalk/paint.d.ts +180 -0
  263. package/dist/chalk/paint.js +210 -0
  264. package/dist/chalk/pointer.d.ts +8 -0
  265. package/dist/chalk/pointer.js +21 -0
  266. package/dist/chalk/prepare.d.ts +11 -0
  267. package/dist/chalk/prepare.js +15 -0
  268. package/dist/chalk/scene-schema.d.ts +199 -0
  269. package/dist/chalk/scene-schema.js +91 -0
  270. package/dist/chalk/seed.d.ts +3 -0
  271. package/dist/chalk/seed.js +10 -0
  272. package/dist/chalk/text-metrics.d.ts +8 -0
  273. package/dist/chalk/text-metrics.js +156 -0
  274. package/dist/chalk/timeline.d.ts +47 -0
  275. package/dist/chalk/timeline.js +84 -0
  276. package/dist/chalk/types.d.ts +150 -0
  277. package/dist/chalk/types.js +8 -0
  278. package/dist/lib/index.d.ts +14 -12
  279. package/dist/lib/index.js +14 -12
  280. package/dist/media/draw.d.ts +23 -0
  281. package/dist/media/draw.js +44 -0
  282. package/dist/media/mp3.d.ts +6 -0
  283. package/dist/media/mp3.js +58 -0
  284. package/dist/media/mp4.d.ts +1 -0
  285. package/dist/media/mp4.js +58 -0
  286. package/dist/media/tts-voices.d.ts +18 -0
  287. package/dist/media/tts-voices.js +606 -0
  288. package/dist/media/tts.d.ts +56 -0
  289. package/dist/media/tts.js +174 -0
  290. package/dist/media/vision.d.ts +25 -0
  291. package/dist/media/vision.js +71 -0
  292. package/dist/memory/commit.d.ts +67 -0
  293. package/dist/memory/commit.js +130 -0
  294. package/dist/memory/context.d.ts +86 -0
  295. package/dist/memory/context.js +97 -0
  296. package/dist/memory/diary.d.ts +84 -0
  297. package/dist/memory/diary.js +183 -0
  298. package/dist/memory/grade.d.ts +2 -0
  299. package/dist/memory/grade.js +7 -0
  300. package/dist/{lib → memory}/plan.d.ts +1 -1
  301. package/dist/{lib → memory}/plan.js +1 -1
  302. package/dist/memory/proposals.d.ts +11 -0
  303. package/dist/memory/proposals.js +43 -0
  304. package/dist/memory/retrieval.d.ts +44 -0
  305. package/dist/memory/retrieval.js +157 -0
  306. package/dist/{lib → memory}/timetable.d.ts +0 -3
  307. package/dist/{lib → memory}/timetable.js +0 -11
  308. package/dist/memory/vault-notes.d.ts +165 -0
  309. package/dist/memory/vault-notes.js +359 -0
  310. package/dist/memory/vault.d.ts +70 -0
  311. package/dist/memory/vault.js +169 -0
  312. package/dist/realtime/bailian.d.ts +120 -0
  313. package/dist/realtime/bailian.js +289 -0
  314. package/dist/realtime/cues.d.ts +51 -0
  315. package/dist/realtime/cues.js +94 -0
  316. package/dist/realtime/listen.d.ts +32 -0
  317. package/dist/realtime/listen.js +53 -0
  318. package/dist/realtime/preview.d.ts +23 -0
  319. package/dist/realtime/preview.js +38 -0
  320. package/dist/realtime/prompt.d.ts +8 -0
  321. package/dist/realtime/prompt.js +13 -0
  322. package/dist/realtime/voices.d.ts +21 -0
  323. package/dist/realtime/voices.js +71 -0
  324. package/dist/runtime/activities.d.ts +29 -0
  325. package/dist/runtime/activities.js +72 -0
  326. package/dist/runtime/activity-state.d.ts +22 -0
  327. package/dist/runtime/activity-state.js +83 -0
  328. package/dist/runtime/activity.d.ts +63 -0
  329. package/dist/runtime/activity.js +428 -0
  330. package/dist/runtime/agent-file.d.ts +24 -0
  331. package/dist/runtime/agent-file.js +59 -0
  332. package/dist/runtime/auto-bookkeep.d.ts +30 -0
  333. package/dist/runtime/auto-bookkeep.js +84 -0
  334. package/dist/{lib → runtime/context}/context-pack.d.ts +3 -3
  335. package/dist/runtime/context/context-pack.js +182 -0
  336. package/dist/runtime/context/thread-digest.d.ts +4 -0
  337. package/dist/runtime/context/thread-digest.js +35 -0
  338. package/dist/runtime/context/tutor-rules.d.ts +25 -0
  339. package/dist/runtime/context/tutor-rules.js +47 -0
  340. package/dist/runtime/delivery/delivery.d.ts +94 -0
  341. package/dist/runtime/delivery/delivery.js +145 -0
  342. package/dist/runtime/delivery/playback.d.ts +25 -0
  343. package/dist/runtime/delivery/playback.js +29 -0
  344. package/dist/runtime/delivery/player.d.ts +145 -0
  345. package/dist/runtime/delivery/player.js +221 -0
  346. package/dist/runtime/delivery/queue.d.ts +89 -0
  347. package/dist/runtime/delivery/queue.js +59 -0
  348. package/dist/runtime/home/edit.d.ts +39 -0
  349. package/dist/runtime/home/edit.js +132 -0
  350. package/dist/runtime/home/home.d.ts +191 -0
  351. package/dist/runtime/home/home.js +318 -0
  352. package/dist/runtime/home/store.d.ts +142 -0
  353. package/dist/runtime/home/store.js +455 -0
  354. package/dist/runtime/journal/conversation.d.ts +46 -0
  355. package/dist/runtime/journal/conversation.js +89 -0
  356. package/dist/runtime/journal/events.d.ts +27 -0
  357. package/dist/runtime/journal/events.js +136 -0
  358. package/dist/runtime/journal/kid-log.d.ts +105 -0
  359. package/dist/runtime/journal/kid-log.js +133 -0
  360. package/dist/runtime/journal/ledger.d.ts +17 -0
  361. package/dist/{lib → runtime/journal}/ledger.js +7 -40
  362. package/dist/runtime/journal/mix.d.ts +42 -0
  363. package/dist/runtime/journal/mix.js +129 -0
  364. package/dist/runtime/journal/mouth-index.d.ts +26 -0
  365. package/dist/runtime/journal/mouth-index.js +34 -0
  366. package/dist/runtime/journal/mouth.d.ts +54 -0
  367. package/dist/runtime/journal/mouth.js +102 -0
  368. package/dist/runtime/journal/recorder.d.ts +91 -0
  369. package/dist/runtime/journal/recorder.js +148 -0
  370. package/dist/runtime/journal/store.d.ts +123 -0
  371. package/dist/runtime/journal/store.js +316 -0
  372. package/dist/runtime/kid-input.d.ts +16 -0
  373. package/dist/runtime/kid-input.js +194 -0
  374. package/dist/runtime/kid-view.d.ts +124 -0
  375. package/dist/runtime/kid-view.js +157 -0
  376. package/dist/runtime/lecture.d.ts +86 -0
  377. package/dist/runtime/lecture.js +237 -0
  378. package/dist/runtime/live/call.d.ts +231 -0
  379. package/dist/runtime/live/call.js +1244 -0
  380. package/dist/runtime/live/turns.d.ts +48 -0
  381. package/dist/runtime/live/turns.js +102 -0
  382. package/dist/runtime/runner.d.ts +243 -0
  383. package/dist/runtime/runner.js +777 -0
  384. package/dist/runtime/talk-run.d.ts +21 -0
  385. package/dist/runtime/talk-run.js +34 -0
  386. package/dist/scenes/dictation/match.d.ts +57 -0
  387. package/dist/scenes/dictation/match.js +255 -0
  388. package/dist/scenes/dictation/recognize.d.ts +86 -0
  389. package/dist/scenes/dictation/recognize.js +163 -0
  390. package/dist/scenes/dictation/store.d.ts +112 -0
  391. package/dist/scenes/dictation/store.js +99 -0
  392. package/dist/scenes/look.d.ts +26 -0
  393. package/dist/scenes/look.js +1 -0
  394. package/dist/scenes/paint/painter.d.ts +84 -0
  395. package/dist/scenes/paint/painter.js +171 -0
  396. package/dist/scenes/paint/prompt.d.ts +23 -0
  397. package/dist/scenes/paint/prompt.js +36 -0
  398. package/dist/scenes/talk/en-words.d.ts +8 -0
  399. package/dist/scenes/talk/en-words.js +44 -0
  400. package/dist/scenes/talk/list.d.ts +44 -0
  401. package/dist/scenes/talk/list.js +88 -0
  402. package/dist/scenes/talk/store.d.ts +27 -0
  403. package/dist/scenes/talk/store.js +61 -0
  404. package/dist/scenes/talk/warmup.d.ts +16 -0
  405. package/dist/scenes/talk/warmup.js +39 -0
  406. package/dist/schema/activity.d.ts +149 -0
  407. package/dist/schema/activity.js +113 -0
  408. package/dist/schema/board.d.ts +143 -2
  409. package/dist/schema/board.js +15 -3
  410. package/dist/schema/config.d.ts +339 -103
  411. package/dist/schema/config.js +269 -84
  412. package/dist/schema/context-pack.d.ts +76 -14
  413. package/dist/schema/context-pack.js +91 -10
  414. package/dist/{lib/conversation.d.ts → schema/conversation-files.d.ts} +11 -33
  415. package/dist/schema/conversation-files.js +26 -0
  416. package/dist/schema/conversation.d.ts +391 -49
  417. package/dist/schema/conversation.js +135 -19
  418. package/dist/schema/events.d.ts +138 -0
  419. package/dist/schema/events.js +1 -0
  420. package/dist/schema/home.d.ts +56 -0
  421. package/dist/schema/home.js +35 -0
  422. package/dist/schema/index.d.ts +14 -3
  423. package/dist/schema/index.js +14 -3
  424. package/dist/schema/kid-device.d.ts +20 -0
  425. package/dist/schema/kid-device.js +32 -0
  426. package/dist/schema/ledger.d.ts +8 -63
  427. package/dist/schema/ledger.js +7 -31
  428. package/dist/schema/memory.d.ts +73 -0
  429. package/dist/schema/memory.js +47 -0
  430. package/dist/schema/plan.d.ts +2 -2
  431. package/dist/schema/plan.js +2 -2
  432. package/dist/schema/sections.d.ts +32 -25
  433. package/dist/schema/sections.js +21 -22
  434. package/dist/schema/theme.d.ts +25 -0
  435. package/dist/schema/theme.js +26 -0
  436. package/dist/schema/timetable.d.ts +2 -2
  437. package/dist/schema/timetable.js +2 -2
  438. package/dist/schema/workspace.d.ts +68 -0
  439. package/dist/schema/workspace.js +37 -0
  440. package/dist/shared/default-port.d.ts +2 -0
  441. package/dist/shared/default-port.js +2 -0
  442. package/dist/shared/errors.d.ts +8 -0
  443. package/dist/shared/errors.js +13 -0
  444. package/dist/shared/home.d.ts +2 -0
  445. package/dist/shared/home.js +16 -0
  446. package/dist/shared/human-notes.d.ts +5 -0
  447. package/dist/shared/human-notes.js +7 -0
  448. package/dist/shared/qianwen.d.ts +54 -0
  449. package/dist/shared/qianwen.js +113 -0
  450. package/dist/shared/site-url.d.ts +2 -0
  451. package/dist/shared/site-url.js +5 -0
  452. package/dist/shared/time.d.ts +9 -0
  453. package/dist/shared/time.js +22 -0
  454. package/dist/shared/tokens.d.ts +18 -0
  455. package/dist/shared/tokens.js +11 -0
  456. package/dist/shared/wav.d.ts +10 -0
  457. package/dist/shared/wav.js +50 -0
  458. package/dist/stage/chunk-226TNLQA.js +129 -0
  459. package/dist/stage/chunk-24LGQEKE.js +1 -0
  460. package/dist/stage/chunk-25XV6UY3.js +10 -0
  461. package/dist/stage/chunk-2AZI3NHJ.js +1 -0
  462. package/dist/stage/chunk-2BQZTTBE.js +1 -0
  463. package/dist/stage/chunk-2M7CGY3Y.js +11 -0
  464. package/dist/stage/chunk-2VR4WFQ7.js +10 -0
  465. package/dist/stage/chunk-2ZYXBUWG.js +9 -0
  466. package/dist/stage/chunk-32LAWMGY.js +1 -0
  467. package/dist/stage/chunk-3LEMTSOO.js +11 -0
  468. package/dist/stage/chunk-3QCKGZYE.js +32 -0
  469. package/dist/stage/chunk-44P66WFA.js +1 -0
  470. package/dist/stage/chunk-4AM6FRR6.js +7 -0
  471. package/dist/stage/chunk-4QDE4SUK.js +1 -0
  472. package/dist/stage/chunk-4QESHV3U.js +1 -0
  473. package/dist/stage/chunk-4SG5QPHB.js +2 -0
  474. package/dist/stage/chunk-4T4UVCOC.js +1 -0
  475. package/dist/stage/chunk-4WUEPPG3.js +34 -0
  476. package/dist/stage/chunk-4YCN6UZ5.js +99 -0
  477. package/dist/stage/chunk-5V7ZY2AP.js +1 -0
  478. package/dist/stage/chunk-5VS5Y2VJ.js +6 -0
  479. package/dist/stage/chunk-5W4NO2ZD.js +62 -0
  480. package/dist/stage/chunk-65CRNICM.js +1 -0
  481. package/dist/stage/chunk-66MULO7F.js +5 -0
  482. package/dist/stage/chunk-6COFQTBS.js +162 -0
  483. package/dist/stage/chunk-6VZG3M3I.js +8 -0
  484. package/dist/stage/chunk-7HVDH43T.js +1 -0
  485. package/dist/stage/chunk-7JLJN4EZ.js +8 -0
  486. package/dist/stage/chunk-7QDSP4LN.js +1 -0
  487. package/dist/stage/chunk-7R6OQAO3.js +1 -0
  488. package/dist/stage/chunk-7VTTHZKW.js +7 -0
  489. package/dist/stage/chunk-ATHR5KEN.js +10 -0
  490. package/dist/stage/chunk-B6R5LTP2.js +36 -0
  491. package/dist/stage/chunk-BD4WE264.js +1 -0
  492. package/dist/stage/chunk-BONANU47.js +206 -0
  493. package/dist/stage/chunk-BTVW7OFK.js +89 -0
  494. package/dist/stage/chunk-BUTJIDHG.js +8 -0
  495. package/dist/stage/chunk-C2IPYMML.js +84 -0
  496. package/dist/stage/chunk-C54FTPPV.js +9 -0
  497. package/dist/stage/chunk-C6LOUF2Z.js +90 -0
  498. package/dist/stage/chunk-CFF3PILO.js +339 -0
  499. package/dist/stage/chunk-CI2FQ2JY.js +9 -0
  500. package/dist/stage/chunk-CW5O4RV7.js +12 -0
  501. package/dist/stage/chunk-CYABNQYD.js +1 -0
  502. package/dist/stage/chunk-D7FFA4NS.js +15 -0
  503. package/dist/stage/chunk-DUNNXU6Z.js +8 -0
  504. package/dist/stage/chunk-E3E4Q6JF.js +6 -0
  505. package/dist/stage/chunk-EB3U3LH7.js +9 -0
  506. package/dist/stage/chunk-EGSZ736B.js +8 -0
  507. package/dist/stage/chunk-EOBXW5VQ.js +2 -0
  508. package/dist/stage/chunk-ERZC3B7Y.js +11 -0
  509. package/dist/stage/chunk-ESDOOBIH.js +11 -0
  510. package/dist/stage/chunk-EVU4QAL4.js +178 -0
  511. package/dist/stage/chunk-EVVZI2NY.js +39 -0
  512. package/dist/stage/chunk-FBKAZUKD.js +39 -0
  513. package/dist/stage/chunk-FDVKYQJN.js +1 -0
  514. package/dist/stage/chunk-FGPVYHVR.js +5 -0
  515. package/dist/stage/chunk-FHUD4AZC.js +1 -0
  516. package/dist/stage/chunk-FKUJ2DMY.js +96 -0
  517. package/dist/stage/chunk-FNZGVIJV.js +1 -0
  518. package/dist/stage/chunk-GBYSEGYM.js +78 -0
  519. package/dist/stage/chunk-GCO4TDHX.js +1 -0
  520. package/dist/stage/chunk-GKTML2LG.js +10 -0
  521. package/dist/stage/chunk-GMDIHHXQ.js +11 -0
  522. package/dist/stage/chunk-GOUNACI3.js +10 -0
  523. package/dist/stage/chunk-GXAYATPF.js +3 -0
  524. package/dist/stage/chunk-H2K2DDXV.js +8 -0
  525. package/dist/stage/chunk-H3UJI6ZY.js +13 -0
  526. package/dist/stage/chunk-H6PUGCME.js +7 -0
  527. package/dist/stage/chunk-HNBUIMIG.js +1 -0
  528. package/dist/stage/chunk-HRZY2BJQ.js +10 -0
  529. package/dist/stage/chunk-HSPJGALW.js +30 -0
  530. package/dist/stage/chunk-IBYORHIP.js +25 -0
  531. package/dist/stage/chunk-IHF4EWTY.js +1 -0
  532. package/dist/stage/chunk-ISBRPNKI.js +1 -0
  533. package/dist/stage/chunk-IZ3OI3C2.js +2 -0
  534. package/dist/stage/chunk-JEFZAC2R.js +1 -0
  535. package/dist/stage/chunk-JFIGRDPO.js +1 -0
  536. package/dist/stage/chunk-JFVYBOOC.js +9 -0
  537. package/dist/stage/chunk-JGD4TUTQ.js +1 -0
  538. package/dist/stage/chunk-JO4BEHDH.js +4 -0
  539. package/dist/stage/chunk-JOWBS42C.js +1 -0
  540. package/dist/stage/chunk-JP3TM2TG.js +1 -0
  541. package/dist/stage/chunk-JVK2C52O.js +1 -0
  542. package/dist/stage/chunk-K4I76XBX.js +14 -0
  543. package/dist/stage/chunk-KA6CZZ4M.js +1 -0
  544. package/dist/stage/chunk-KESSJ36M.js +1 -0
  545. package/dist/stage/chunk-LKATEVRU.js +1 -0
  546. package/dist/stage/chunk-LS4EJ2EI.js +1 -0
  547. package/dist/stage/chunk-LUKJQC2X.js +70 -0
  548. package/dist/stage/chunk-LWULR3SM.js +1 -0
  549. package/dist/stage/chunk-LWVRFMKX.js +2 -0
  550. package/dist/stage/chunk-LXC4ZBPZ.js +1 -0
  551. package/dist/stage/chunk-M2UP255H.js +1 -0
  552. package/dist/stage/chunk-MAPPXUZQ.js +231 -0
  553. package/dist/stage/chunk-MBMPW7Z5.js +1 -0
  554. package/dist/stage/chunk-MD3HMMTK.js +1 -0
  555. package/dist/stage/chunk-MLPCUIKR.js +17 -0
  556. package/dist/stage/chunk-MNQIRPDQ.js +8 -0
  557. package/dist/stage/chunk-MRG7MJVS.js +1 -0
  558. package/dist/stage/chunk-MU6EXW72.js +10 -0
  559. package/dist/stage/chunk-MVU2X6LU.js +4 -0
  560. package/dist/stage/chunk-N6UA5CZV.js +36 -0
  561. package/dist/stage/chunk-NLZIT2TJ.js +7 -0
  562. package/dist/stage/chunk-NOJR3J7M.js +1 -0
  563. package/dist/stage/chunk-NT7ARDOL.js +1 -0
  564. package/dist/stage/chunk-NWOGMPZH.js +1 -0
  565. package/dist/stage/chunk-NXE4KMG3.js +40 -0
  566. package/dist/stage/chunk-O4YCZXD2.js +257 -0
  567. package/dist/stage/chunk-OAJZ7HLK.js +1 -0
  568. package/dist/stage/chunk-OCFCFASO.js +1 -0
  569. package/dist/stage/chunk-ODGPG5RY.js +7 -0
  570. package/dist/stage/chunk-ODUEA52Q.js +3 -0
  571. package/dist/stage/chunk-OGDJ3FCI.js +1 -0
  572. package/dist/stage/chunk-OLDJ55TT.js +1 -0
  573. package/dist/stage/chunk-OOCL4ATI.js +2 -0
  574. package/dist/stage/chunk-OQEFDUDV.js +1 -0
  575. package/dist/stage/chunk-OQWVEWG7.js +4 -0
  576. package/dist/stage/chunk-OUIX6BGK.js +1 -0
  577. package/dist/stage/chunk-OV7OZYUO.js +7 -0
  578. package/dist/stage/chunk-PFGDY33O.js +1 -0
  579. package/dist/stage/chunk-PFJDUJB3.js +139 -0
  580. package/dist/stage/chunk-PGEW76ER.js +1 -0
  581. package/dist/stage/chunk-PP76TAQX.js +32 -0
  582. package/dist/stage/chunk-PTBOP4OT.js +2 -0
  583. package/dist/stage/chunk-PX2RVHCK.js +1 -0
  584. package/dist/stage/chunk-Q7K3PNAD.js +1 -0
  585. package/dist/stage/chunk-QDDPLZDB.js +1 -0
  586. package/dist/stage/chunk-QLGLZ77F.js +1 -0
  587. package/dist/stage/chunk-QOPFDFHI.js +1 -0
  588. package/dist/stage/chunk-QXT4KAEJ.js +1 -0
  589. package/dist/stage/chunk-R4YVZCIT.js +1 -0
  590. package/dist/stage/chunk-R6P6EHWA.js +62 -0
  591. package/dist/stage/chunk-RCH3OXBI.js +9 -0
  592. package/dist/stage/chunk-RHPE44I2.js +1 -0
  593. package/dist/stage/chunk-RJ2R4FVC.js +1 -0
  594. package/dist/stage/chunk-RJH2YVTD.js +1 -0
  595. package/dist/stage/chunk-RLWWW5OB.js +6 -0
  596. package/dist/stage/chunk-S3WO35Z7.js +120 -0
  597. package/dist/stage/chunk-S7ZGX6MW.js +1 -0
  598. package/dist/stage/chunk-SIAWX27B.js +1 -0
  599. package/dist/stage/chunk-SLG4X622.js +1 -0
  600. package/dist/stage/chunk-SLJRBC77.js +168 -0
  601. package/dist/stage/chunk-SRORWQJZ.js +1 -0
  602. package/dist/stage/chunk-STZTUIIF.js +2 -0
  603. package/dist/stage/chunk-SVQ6MWNV.js +9 -0
  604. package/dist/stage/chunk-TL3HLL2S.js +132 -0
  605. package/dist/stage/chunk-U3DOE6SF.js +24 -0
  606. package/dist/stage/chunk-UABQIZKW.js +6 -0
  607. package/dist/stage/chunk-UDK3R764.js +1 -0
  608. package/dist/stage/chunk-UFF4E54B.js +41 -0
  609. package/dist/stage/chunk-UFP5Q4AY.js +1 -0
  610. package/dist/stage/chunk-UMH2TSW2.js +72 -0
  611. package/dist/stage/chunk-UTASVNF5.js +10 -0
  612. package/dist/stage/chunk-UW7ML5ML.js +88 -0
  613. package/dist/stage/chunk-V3J7F6OP.js +1 -0
  614. package/dist/stage/chunk-VEHP5B5Q.js +5 -0
  615. package/dist/stage/chunk-VRXWB733.js +9 -0
  616. package/dist/stage/chunk-VVDHBBS5.js +5 -0
  617. package/dist/stage/chunk-VYFOH6FU.js +106 -0
  618. package/dist/stage/chunk-W3VZTLQS.js +3 -0
  619. package/dist/stage/chunk-W7WNRWTZ.js +8 -0
  620. package/dist/stage/chunk-WHUCV53C.js +1 -0
  621. package/dist/stage/chunk-WULMYHIF.js +1 -0
  622. package/dist/stage/chunk-X54EX7AW.js +9 -0
  623. package/dist/stage/chunk-X565ISZM.js +1 -0
  624. package/dist/stage/chunk-XGZMDECB.js +1 -0
  625. package/dist/stage/chunk-XHONC4RF.js +172 -0
  626. package/dist/stage/chunk-XIK4XQ6E.js +24 -0
  627. package/dist/stage/chunk-XJQQWFTE.js +1 -0
  628. package/dist/stage/chunk-XM4WOU3I.js +13 -0
  629. package/dist/stage/chunk-XM7Y5HP5.js +38 -0
  630. package/dist/stage/chunk-XNYYRGD5.js +292 -0
  631. package/dist/stage/chunk-XOYVR3SV.js +1 -0
  632. package/dist/stage/chunk-XQIW5HCE.js +1 -0
  633. package/dist/stage/chunk-YEWL7FUW.js +8 -0
  634. package/dist/stage/chunk-YI55PY3V.js +1 -0
  635. package/dist/stage/chunk-YM63HI6W.js +1 -0
  636. package/dist/stage/chunk-YNLGLU4P.js +9 -0
  637. package/dist/stage/chunk-YTNMNOWP.js +1 -0
  638. package/dist/stage/chunk-YV62XKTC.js +1 -0
  639. package/dist/stage/chunk-YVBA3QW6.js +1 -0
  640. package/dist/stage/chunk-Z2SY2HOC.js +1 -0
  641. package/dist/stage/chunk-ZBTCQEBK.js +10 -0
  642. package/dist/stage/chunk-ZCUFN74D.js +1 -0
  643. package/dist/stage/chunk-ZHOULXEJ.js +11 -0
  644. package/dist/stage/chunk-ZTRZIET2.js +1 -0
  645. package/dist/stage/index.html +1 -0
  646. package/dist/stage/stage.css +1 -1
  647. package/dist/stage/stage.js +1 -4383
  648. package/dist/ui/dev/dev-page.d.ts +1 -0
  649. package/dist/ui/dev/dev-page.js +2021 -0
  650. package/dist/ui/dev/dev-theme.d.ts +6 -0
  651. package/dist/ui/dev/dev-theme.js +67 -0
  652. package/dist/ui/dev/voice-gallery-page.d.ts +1 -0
  653. package/dist/ui/dev/voice-gallery-page.js +121 -0
  654. package/dist/ui/dev/voice-scenes.d.ts +21 -0
  655. package/dist/ui/dev/voice-scenes.js +30 -0
  656. package/dist/ui/dictation/en-box.d.ts +2 -0
  657. package/dist/ui/dictation/en-box.js +86 -0
  658. package/dist/ui/dictation/en-page.d.ts +1 -0
  659. package/dist/ui/dictation/en-page.js +484 -0
  660. package/dist/ui/dictation/page.d.ts +1 -0
  661. package/dist/ui/dictation/page.js +1084 -0
  662. package/dist/ui/dictation/parent-page.d.ts +1 -0
  663. package/dist/ui/dictation/parent-page.js +234 -0
  664. package/dist/ui/dictation/reel-mode.d.ts +1 -0
  665. package/dist/ui/dictation/reel-mode.js +87 -0
  666. package/dist/ui/dictation/reel-page.d.ts +1 -0
  667. package/dist/ui/dictation/reel-page.js +62 -0
  668. package/dist/ui/dictation/reel.d.ts +163 -0
  669. package/dist/ui/dictation/reel.js +436 -0
  670. package/dist/ui/dictation/ui.d.ts +44 -0
  671. package/dist/ui/dictation/ui.js +80 -0
  672. package/dist/ui/dictation/unlock.d.ts +7 -0
  673. package/dist/ui/dictation/unlock.js +16 -0
  674. package/dist/ui/kid/audio-client.d.ts +11 -0
  675. package/dist/ui/kid/audio-client.js +74 -0
  676. package/dist/ui/kid/board-mark-art.d.ts +37 -0
  677. package/dist/ui/kid/board-mark-art.js +5 -0
  678. package/dist/ui/kid/board-mark.d.ts +24 -0
  679. package/dist/ui/kid/board-mark.js +58 -0
  680. package/dist/ui/kid/home-tiles.d.ts +14 -0
  681. package/dist/ui/kid/home-tiles.js +44 -0
  682. package/dist/ui/kid/kid-log.d.ts +15 -0
  683. package/dist/ui/kid/kid-log.js +87 -0
  684. package/dist/ui/kid/kid-nav.d.ts +11 -0
  685. package/dist/ui/kid/kid-nav.js +22 -0
  686. package/dist/ui/kid/kid-page.d.ts +9 -0
  687. package/dist/ui/kid/kid-page.js +3684 -0
  688. package/dist/ui/kid/letters.d.ts +26 -0
  689. package/dist/ui/kid/letters.js +51 -0
  690. package/dist/ui/kid/photo-edit.d.ts +83 -0
  691. package/dist/ui/kid/photo-edit.js +106 -0
  692. package/dist/ui/kid/photo-ui.d.ts +23 -0
  693. package/dist/ui/kid/photo-ui.js +60 -0
  694. package/dist/ui/kid/reel.d.ts +419 -0
  695. package/dist/ui/kid/reel.js +648 -0
  696. package/dist/ui/kid/tianzige.d.ts +8 -0
  697. package/dist/ui/kid/tianzige.js +45 -0
  698. package/dist/ui/kid/tutor-face.d.ts +35 -0
  699. package/dist/ui/kid/tutor-face.js +84 -0
  700. package/dist/ui/kid/voice-ui.d.ts +70 -0
  701. package/dist/ui/kid/voice-ui.js +316 -0
  702. package/dist/ui/kid/voice-view.d.ts +71 -0
  703. package/dist/ui/kid/voice-view.js +75 -0
  704. package/dist/ui/kid/word-draw.d.ts +2 -0
  705. package/dist/ui/kid/word-draw.js +107 -0
  706. package/dist/ui/kid/word-emoji.d.ts +10 -0
  707. package/dist/ui/kid/word-emoji.js +68 -0
  708. package/dist/ui/kid/word-layout.d.ts +25 -0
  709. package/dist/ui/kid/word-layout.js +29 -0
  710. package/dist/ui/parent/parent-home-page.d.ts +1 -0
  711. package/dist/ui/parent/parent-home-page.js +722 -0
  712. package/dist/ui/parent/parent-page.d.ts +2 -0
  713. package/dist/ui/parent/parent-page.js +511 -0
  714. package/dist/ui/parent/parent-settings-page.d.ts +1 -0
  715. package/dist/ui/parent/parent-settings-page.js +133 -0
  716. package/dist/ui/parent/qr-page.d.ts +8 -0
  717. package/dist/ui/parent/qr-page.js +66 -0
  718. package/dist/ui/shared/brand.d.ts +38 -0
  719. package/dist/ui/shared/brand.js +53 -0
  720. package/dist/ui/shared/favicon.d.ts +12 -0
  721. package/dist/ui/shared/favicon.js +96 -0
  722. package/dist/ui/shared/icon.d.ts +9 -0
  723. package/dist/ui/shared/icon.js +35 -0
  724. package/dist/ui/shared/inline-lib.d.ts +1 -0
  725. package/dist/ui/shared/inline-lib.js +28 -0
  726. package/dist/ui/shared/logo.d.ts +31 -0
  727. package/dist/ui/shared/logo.js +43 -0
  728. package/dist/ui/shared/page-dom.d.ts +12 -0
  729. package/dist/ui/shared/page-dom.js +36 -0
  730. package/dist/ui/shared/png.d.ts +1 -0
  731. package/dist/ui/shared/png.js +45 -0
  732. package/dist/ui/shared/qr-svg.d.ts +3 -0
  733. package/dist/ui/shared/qr-svg.js +32 -0
  734. package/dist/ui/shared/qr.d.ts +14 -0
  735. package/dist/ui/shared/qr.js +281 -0
  736. package/dist/ui/shared/reel-dock.d.ts +22 -0
  737. package/dist/ui/shared/reel-dock.js +365 -0
  738. package/dist/ui/shared/tutor-color.d.ts +15 -0
  739. package/dist/ui/shared/tutor-color.js +47 -0
  740. package/dist/ui/talk/page.d.ts +6 -0
  741. package/dist/ui/talk/page.js +294 -0
  742. package/grades/grade-2/README.md +11 -0
  743. package/grades/grade-2/VALIDATION.md +41 -0
  744. package/grades/grade-2/agents/chinese-tutor/activities//344/275/240/350/257/264/346/210/221/347/224/273.md +157 -0
  745. package/grades/grade-2/agents/chinese-tutor/activities//344/275/240/350/257/264/346/210/221/347/224/273./344/276/213/345/255/220.md +78 -0
  746. package/grades/grade-2/agents/chinese-tutor/activities//344/276/213/345/255/220/345/233/276//345/200/237/344/274/236.jpg +0 -0
  747. package/grades/grade-2/agents/chinese-tutor/activities//344/276/213/345/255/220/345/233/276//345/260/217/347/214/264/346/221/230/346/241/203.jpg +0 -0
  748. package/grades/grade-2/agents/chinese-tutor/activities//345/206/231/350/257/235.md +64 -0
  749. package/grades/grade-2/agents/chinese-tutor/activities//345/206/231/350/257/235./344/276/213/345/255/220.md +31 -0
  750. package/grades/grade-2/agents/chinese-tutor/activities//345/220/254/345/206/231.md +48 -0
  751. package/grades/grade-2/agents/chinese-tutor/activities//345/244/215/344/271/240.md +79 -0
  752. package/grades/grade-2/agents/chinese-tutor/activities//345/244/215/344/271/240./344/276/213/345/255/220.md +35 -0
  753. package/grades/grade-2/agents/chinese-tutor/activities//345/260/217/350/257/276/345/240/202.md +53 -0
  754. package/grades/grade-2/agents/chinese-tutor/activities//346/243/200/346/237/245/344/275/234/344/270/232.md +81 -0
  755. package/grades/grade-2/agents/chinese-tutor/activities//346/243/200/346/237/245/344/275/234/344/270/232./344/276/213/345/255/220.md +35 -0
  756. package/grades/grade-2/agents/chinese-tutor/activities//347/234/213/345/233/276/350/257/264/350/257/235.md +94 -0
  757. package/grades/grade-2/agents/chinese-tutor/activities//347/234/213/345/233/276/350/257/264/350/257/235./344/276/213/345/255/220.md +63 -0
  758. package/grades/grade-2/agents/chinese-tutor/activities//351/242/204/344/271/240.md +82 -0
  759. package/grades/grade-2/agents/chinese-tutor/activities//351/242/204/344/271/240./344/276/213/345/255/220.md +35 -0
  760. package/grades/grade-2/agents/chinese-tutor.md +59 -0
  761. package/grades/grade-2/agents/english-tutor/activities//345/217/243/350/257/255/350/257/276.md +149 -0
  762. package/grades/grade-2/agents/english-tutor/activities//345/217/243/350/257/255/350/257/276./344/276/213/345/255/220.md +13 -0
  763. package/grades/grade-2/agents/english-tutor/activities//345/220/254/345/206/231.md +47 -0
  764. package/grades/grade-2/agents/english-tutor/activities//345/244/215/344/271/240.md +78 -0
  765. package/grades/grade-2/agents/english-tutor/activities//345/244/215/344/271/240./344/276/213/345/255/220.md +35 -0
  766. package/grades/grade-2/agents/english-tutor/activities//345/260/217/350/257/276/345/240/202.md +53 -0
  767. package/grades/grade-2/agents/english-tutor/activities//346/243/200/346/237/245/344/275/234/344/270/232.md +80 -0
  768. package/grades/grade-2/agents/english-tutor/activities//346/243/200/346/237/245/344/275/234/344/270/232./344/276/213/345/255/220.md +35 -0
  769. package/grades/grade-2/agents/english-tutor/activities//351/242/204/344/271/240.md +86 -0
  770. package/grades/grade-2/agents/english-tutor/activities//351/242/204/344/271/240./344/276/213/345/255/220.md +35 -0
  771. package/grades/grade-2/agents/english-tutor.md +57 -0
  772. package/grades/grade-2/agents/math-tutor/activities//345/244/215/344/271/240.md +78 -0
  773. package/grades/grade-2/agents/math-tutor/activities//345/244/215/344/271/240./344/276/213/345/255/220.md +35 -0
  774. package/grades/grade-2/agents/math-tutor/activities//345/260/217/350/257/276/345/240/202.md +56 -0
  775. package/grades/grade-2/agents/math-tutor/activities//345/260/217/350/257/276/345/240/202./344/276/213/345/255/220.md +52 -0
  776. package/grades/grade-2/agents/math-tutor/activities//346/243/200/346/237/245/344/275/234/344/270/232.md +80 -0
  777. package/grades/grade-2/agents/math-tutor/activities//346/243/200/346/237/245/344/275/234/344/270/232./344/276/213/345/255/220.md +35 -0
  778. package/grades/grade-2/agents/math-tutor/activities//351/242/204/344/271/240.md +81 -0
  779. package/grades/grade-2/agents/math-tutor/activities//351/242/204/344/271/240./344/276/213/345/255/220.md +35 -0
  780. package/grades/grade-2/agents/math-tutor.md +51 -0
  781. package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/index.json +44 -0
  782. package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-1.mp3 +0 -0
  783. package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-2.mp3 +0 -0
  784. package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-3.mp3 +0 -0
  785. package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-4.mp3 +0 -0
  786. package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-5.mp3 +0 -0
  787. package/grades/grade-2/examples/2026-10-27-pingjunfen/bake.json +1 -0
  788. package/grades/grade-2/examples/2026-10-27-pingjunfen/manifest.json +63 -0
  789. package/grades/grade-2/examples/2026-10-27-pingjunfen/scene.json +247 -0
  790. package/grades/grade-2/examples/2026-10-27-pingjunfen.md +40 -0
  791. package/grades/grade-2/examples/2026-10-27-pingjunfen.ts +26 -0
  792. package/grades/grade-2/examples//345/271/263/345/235/207/345/210/206./344/276/213/345/255/220.md +24 -0
  793. package/grades/grade-2/examples//346/225/260/345/255/246/345/271/263/345/235/207/345/210/206.md +42 -0
  794. package/grades/grade-2/validation/2026-10-27.json +4410 -0
  795. package/package.json +27 -15
  796. package/skills/chalk-cli/SKILL.md +62 -0
  797. package/skills/chalk-scene/SKILL.md +91 -0
  798. package/skills/chalk-teaching/SKILL.md +82 -0
  799. package/skills/chalk-teaching/models//344/272/244/351/224/231/346/225/260/345/210/227/345/210/206/350/241/214.md +8 -0
  800. package/skills/chalk-teaching/models//345/207/221/345/215/201.md +7 -0
  801. package/skills/chalk-teaching/models//345/221/250/346/234/237/350/247/204/345/276/213.md +6 -0
  802. package/skills/chalk-teaching/models//345/256/236/347/211/251/345/233/276-/345/242/236/345/207/217.md +6 -0
  803. package/skills/chalk-teaching/models//345/256/236/347/211/251/345/233/276-/346/257/224/345/244/232/345/260/221-/346/261/202/345/244/247/346/225/260/346/261/202/345/260/217/346/225/260.md +7 -0
  804. package/skills/chalk-teaching/models//345/256/236/347/211/251/345/233/276-/346/257/224/345/244/232/345/260/221-/346/261/202/345/267/256.md +6 -0
  805. package/skills/chalk-teaching/models//345/260/217/346/243/222/350/277/233/344/275/215/345/212/240.md +7 -0
  806. package/skills/chalk-teaching/models//345/260/217/346/243/222/351/200/200/344/275/215/345/207/217.md +6 -0
  807. package/skills/chalk-teaching/models//346/216/222/351/230/237/347/202/271/351/230/265.md +7 -0
  808. package/skills/chalk-teaching/models//346/227/266/351/227/264/346/225/260/350/275/264.md +6 -0
  809. package/skills/chalk-teaching/models//346/245/274/345/261/202/351/227/264/351/232/224/345/233/276.md +7 -0
  810. package/skills/chalk-teaching/models//347/240/264/345/215/201.md +7 -0
  811. package/skills/chalk-teaching/models//347/253/226/345/274/217/351/200/200/344/275/215.md +6 -0
  812. package/skills/chalk-teaching/models//347/272/277/346/256/265/345/233/276.md +7 -0
  813. package/skills/chalk-teaching/models-index.md +24 -0
  814. package/skills/chalk-teaching/references//350/256/262/350/247/243/346/250/241/346/235/277/344/270/211/346/241/243.md +32 -0
  815. package/skills/chalk-teaching/references//350/257/264/346/230/216/346/226/207/344/273/266.md +102 -0
  816. package/skills/chalk-verify/SKILL.md +57 -0
  817. package/skills/chalk-verify/references//350/257/204/345/210/244/346/240/207/345/207/206.md +69 -0
  818. package/skills/cotutor-analyze/SKILL.md +106 -0
  819. package/skills/cotutor-analyze/references//345/221/275/344/273/244/344/270/216/346/226/207/344/273/266.md +31 -0
  820. package/skills/cotutor-board/SKILL.md +373 -0
  821. package/skills/cotutor-board/references/README.md +38 -0
  822. package/skills/cotutor-board/references/canvas.md +68 -0
  823. package/skills/cotutor-board/references/choice.md +55 -0
  824. package/skills/cotutor-board/references/code.md +46 -0
  825. package/skills/cotutor-board/references/image.md +52 -0
  826. package/skills/cotutor-board/references/lecture.md +69 -0
  827. package/skills/cotutor-board/references/paint.md +77 -0
  828. package/skills/cotutor-board/references/photo.md +61 -0
  829. package/skills/cotutor-board/references/read.md +51 -0
  830. package/skills/cotutor-board/references/text.md +69 -0
  831. package/skills/cotutor-board/references/tianzige.md +78 -0
  832. package/skills/cotutor-board/references/word.md +91 -0
  833. package/skills/cotutor-home/SKILL.md +170 -0
  834. package/skills/cotutor-home/references/dictation.md +97 -0
  835. package/skills/cotutor-home/references/tutor.md +149 -0
  836. package/skills/cotutor-home/references//345/221/275/344/273/244.md +21 -0
  837. package/skills/cotutor-home/references//351/246/226/351/241/265/350/257/255/346/263/225.md +143 -0
  838. package/skills/cotutor-tune/SKILL.md +147 -0
  839. package/skills/cotutor-tune/references//345/255/227/346/256/265.md +53 -0
  840. package/skills/cotutor-tutor/SKILL.md +253 -0
  841. package/skills/cotutor-tutor/references//344/270/212/344/270/213/346/226/207/345/214/205.md +78 -0
  842. package/themes/default/kid.css +86 -0
  843. package/themes/default/theme.json +26 -0
  844. package/agents/chinese-tutor.md +0 -67
  845. package/agents/homework-tutor.md +0 -17
  846. package/agents/math-tutor.md +0 -68
  847. package/agents/planner.md +0 -18
  848. package/agents/reading-tutor.md +0 -65
  849. package/agents/scene-maker.md +0 -41
  850. package/dist/cards/canvas.d.ts +0 -22
  851. package/dist/cards/canvas.js +0 -65
  852. package/dist/cards/fill.d.ts +0 -14
  853. package/dist/cards/fill.js +0 -64
  854. package/dist/cards/index.d.ts +0 -48
  855. package/dist/cards/index.js +0 -115
  856. package/dist/cards/kind.js +0 -7
  857. package/dist/cards/scene.d.ts +0 -24
  858. package/dist/cards/scene.js +0 -67
  859. package/dist/cards/text.d.ts +0 -18
  860. package/dist/cards/text.js +0 -41
  861. package/dist/cli/cert.d.ts +0 -7
  862. package/dist/cli/cert.js +0 -36
  863. package/dist/cli/doctor.d.ts +0 -22
  864. package/dist/cli/doctor.js +0 -355
  865. package/dist/cli/init.js +0 -116
  866. package/dist/cli/main.js +0 -243
  867. package/dist/cli/serve.d.ts +0 -30
  868. package/dist/cli/serve.js +0 -56
  869. package/dist/cli/skeleton.d.ts +0 -39
  870. package/dist/cli/skeleton.js +0 -184
  871. package/dist/cli/skills.d.ts +0 -40
  872. package/dist/cli/skills.js +0 -170
  873. package/dist/cli/workspace.d.ts +0 -69
  874. package/dist/lib/agent-file.d.ts +0 -10
  875. package/dist/lib/agent-file.js +0 -16
  876. package/dist/lib/board.d.ts +0 -15
  877. package/dist/lib/board.js +0 -107
  878. package/dist/lib/context-pack.js +0 -55
  879. package/dist/lib/conversation.js +0 -88
  880. package/dist/lib/kid-board.d.ts +0 -148
  881. package/dist/lib/kid-board.js +0 -294
  882. package/dist/lib/kid-view.d.ts +0 -63
  883. package/dist/lib/kid-view.js +0 -93
  884. package/dist/lib/ledger.d.ts +0 -29
  885. package/dist/lib/run-plan.d.ts +0 -30
  886. package/dist/lib/run-plan.js +0 -32
  887. package/dist/lib/sections.d.ts +0 -12
  888. package/dist/lib/sections.js +0 -151
  889. package/dist/lib/stream.d.ts +0 -15
  890. package/dist/lib/stream.js +0 -69
  891. package/dist/lib/transcript.d.ts +0 -36
  892. package/dist/lib/transcript.js +0 -95
  893. package/dist/server/app.d.ts +0 -74
  894. package/dist/server/app.js +0 -406
  895. package/dist/server/kid-page.d.ts +0 -1
  896. package/dist/server/kid-page.js +0 -768
  897. package/dist/server/mock.js +0 -403
  898. package/dist/server/parent-page.d.ts +0 -8
  899. package/dist/server/parent-page.js +0 -393
  900. package/dist/server/runner.d.ts +0 -64
  901. package/dist/server/runner.js +0 -277
  902. package/dist/server/scene-props.d.ts +0 -16
  903. package/dist/server/scene-props.js +0 -39
  904. package/dist/server/store.d.ts +0 -43
  905. package/dist/server/store.js +0 -210
  906. package/dist/server/tts.d.ts +0 -51
  907. package/dist/server/tts.js +0 -99
  908. /package/dist/{cli → app/cli}/main.d.ts +0 -0
  909. /package/dist/{cards → board/cards}/choice.d.ts +0 -0
  910. /package/dist/{cards → board/cards}/code.d.ts +0 -0
  911. /package/dist/{cards → board/cards}/image.d.ts +0 -0
  912. /package/dist/{cards → board/cards}/read.d.ts +0 -0
@@ -1,768 +0,0 @@
1
- /**
2
- * 孩子端 `/`:首页(老师们 + 今天可以问 + 今天)与老师页(= 板书页)。零依赖内联脚本,只走 /api/kid/* 与 /api/audio。
3
- * 铁律(《产品规划.md》):界面上永远没有错误与评判——后端不通、老师出错、识别失败,都只是「什么都不出现」或头像灰;
4
- * 文字尽量少,语音优先。孩子设备上没有通往家长端的入口(2026-09-10 拍板)。
5
- *
6
- * 老师页照豆包爱学的形态(《豆包录屏分析.md》):板书是一天一份、越讲越长的文档,孩子的话不上板,只在字幕行回显一下;
7
- * 一轮回复 = 一节:卡整块铺出,讲稿逐句播(有 mp3 放 mp3,没有用浏览器合成,再没有按字数计时),
8
- * 播到哪句就在卡上画标注、滚到那张卡;末句是问句就停下,暂停钮换成「继续」。
9
- * 输入条照豆包通用版:相机 | 发消息或按住说话 | 加号(相册)。平板横屏:板书两列(最宽 1040 居中),没有左栏。
10
- *
11
- * 交互逻辑在 ../lib/kid-board.ts(纯函数,有测试),这里把它剥掉类型内联进页面,两处一份源码。卡按 kind 渲染(text / read / choice / fill / code),
12
- * 每种卡的紧凑态只读;点卡开舞台(盖住板书那块,顶栏是卡的名字 + 关闭,讲稿暂停):选择题在舞台里点大按钮,选了就 PUT 状态,
13
- * 「交给老师」= 发一条 {text:'', action:'submit', focus:{card}};舞台开着时发的消息都带 focus.card。「继续」= {text:'', action:'continue'}。
14
- * 状态存服务端,重开页面从 section.cards[n].state 读回;卡上永远不画对错。
15
- * 重卡(scene / canvas)的舞台在 iframe 里装 /stage/?card=<id>(舞台包,src/stage/),postMessage 协议见 src/stage/protocol.ts:
16
- * 页面发 card(props + state + 课包 URL),包回 phase / state / submit / close;场景在播时字幕行显示场景讲稿、按钮映射到播放器;
17
- * 讲稿 [[play]] 锚到场景卡 → 念完那句把动画铺满播,done 了关舞台接着念。
18
- * 点读段:card.assets 里有 <段号>.mp3 的放服务端配的,没有的浏览器合成;填空舞台逐空打字、「交给老师」;图片舞台双指缩放。
19
- * 流式:老师还在说时 pending 条目带 partial 板书,卡按下标只追加不重画(先出的卡不闪),讲稿不播;整轮跑完那节换成正式的,声音从第一句起。
20
- * __TITLE__ 由路由替换。调试:`?step=<节>.<句>` 直接停在某句(标注画齐、不出声),截图与测试用。
21
- */
22
- import { readFileSync } from 'node:fs';
23
- import { stripTypeScriptTypes } from 'node:module';
24
- /** 读 kid-board 的源码(仓库里是 .ts,npm 包里是 dist 的 .js),剥类型、去 export,变成能内联的普通脚本 */
25
- function boardLibSource() {
26
- let src;
27
- try {
28
- const ts = readFileSync(new URL('../lib/kid-board.ts', import.meta.url), 'utf8');
29
- // stripTypeScriptTypes 会发一条 ExperimentalWarning;这里只剥自己的文件,警告对用户没有信息量,压掉
30
- const warn = process.emitWarning;
31
- process.emitWarning = () => { };
32
- try {
33
- src = stripTypeScriptTypes(ts);
34
- }
35
- finally {
36
- process.emitWarning = warn;
37
- }
38
- }
39
- catch {
40
- src = readFileSync(new URL('../lib/kid-board.js', import.meta.url), 'utf8');
41
- }
42
- return src
43
- .replace(/^export (?=(?:async )?(?:function|const|let|class) )/gm, '')
44
- .replace(/^export \{[^}]*\};?[ \t]*$/gm, '')
45
- .replace(/^import [^\n]*$/gm, '');
46
- }
47
- const PAGE = `<!doctype html>
48
- <html lang="zh-CN">
49
- <meta charset="utf-8">
50
- <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover,user-scalable=no">
51
- <meta name="apple-mobile-web-app-capable" content="yes">
52
- <title>__TITLE__</title>
53
- <style>
54
- :root { --paper:#f6f4ee; --ink:#2b2b2b; --dim:#8a8781; --line:#e2dfd6; --card:#fffdf8; --kid:#dbeeff; --tutor:#fff3d6; --accent:#e8743b; --hi:#ffe3a8; --blue:#eef4ff; --cream:#f7f1e3; --ok:#e8f6ee; --ok-ink:#1f8a4c; --purple:#efe9fb; --purple-ink:#7a4fc9; --grey:#eeece6; }
55
- * { box-sizing:border-box; -webkit-tap-highlight-color:transparent; }
56
- html, body { height:100%; }
57
- body { margin:0; background:var(--paper); color:var(--ink); font:18px/1.5 -apple-system,"PingFang SC","Helvetica Neue",sans-serif; -webkit-user-select:none; user-select:none; overscroll-behavior:none; }
58
- button { font:inherit; color:inherit; background:none; border:0; padding:0; cursor:pointer; }
59
- .ic svg { display:block; }
60
- /* ---- 首页 ---- */
61
- #home { min-height:100%; padding:calc(env(safe-area-inset-top) + 16px) 20px calc(env(safe-area-inset-bottom) + 40px); max-width:960px; margin:0 auto; display:flex; flex-direction:column; gap:18px; }
62
- h1 { font-size:20px; font-weight:600; margin:0; color:var(--dim); }
63
- .tutors { display:flex; gap:16px; flex-wrap:wrap; justify-content:center; }
64
- .tutor { display:flex; flex-direction:column; align-items:center; gap:8px; padding:4px; width:104px; }
65
- .av { border-radius:50%; background:var(--card); border:3px solid var(--line); display:grid; place-items:center; font-weight:700; box-shadow:0 4px 12px #0000000f; flex:0 0 auto; overflow:hidden; }
66
- .av img { width:100%; height:100%; object-fit:cover; }
67
- .tutor .av { width:104px; height:104px; font-size:48px; transition:transform .15s; }
68
- .tutor:active .av { transform:scale(.94); }
69
- .tutor .nm { font-size:16px; font-weight:600; }
70
- .tutor.off { pointer-events:none; }
71
- .tutor.off .av { filter:grayscale(1); opacity:.4; box-shadow:none; }
72
- .tutor.off .nm { color:var(--dim); }
73
- .lbl { font-size:14px; color:var(--dim); }
74
- .sug, .today { display:flex; flex-direction:column; gap:8px; }
75
- .chip { display:inline-flex; align-items:center; gap:10px; align-self:flex-start; padding:10px 16px; border-radius:22px; background:var(--card); border:1px solid var(--line); font-size:17px; text-align:left; }
76
- .chip .dot, .slot .dot { width:10px; height:10px; border-radius:50%; flex:0 0 auto; }
77
- .slots { display:flex; gap:8px; flex-wrap:wrap; }
78
- .slot { display:inline-flex; align-items:center; gap:8px; background:var(--card); border:1px solid var(--line); border-radius:14px; padding:6px 14px; font-size:15px; }
79
- .slot.now { border-color:var(--accent); box-shadow:0 0 0 3px #e8743b33; }
80
- .path { display:flex; align-items:flex-end; gap:10px; padding:6px 0 0; }
81
- .station { display:flex; flex-direction:column; align-items:center; gap:4px; flex:1; }
82
- .station .ring { width:30px; height:30px; border-radius:50%; border:4px solid var(--line); background:var(--card); }
83
- .station.now .ring { width:44px; height:44px; border-width:6px; }
84
- .station .d { font-size:12px; color:var(--dim); }
85
- .station.now .d { color:var(--ink); font-weight:600; }
86
- .station.past { opacity:.45; } .station.later { opacity:.7; }
87
- .stacks { display:grid; grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); gap:12px; }
88
- .stack { background:var(--card); border:2px solid var(--line); border-radius:18px; padding:14px 16px; }
89
- .stack .name { font-weight:600; } .stack .item { font-size:15px; color:var(--dim); margin-top:4px; }
90
- #rest { display:none; text-align:center; color:var(--dim); font-size:16px; padding:12px 0; }
91
- body.offline #rest { display:block; }
92
- body.offline .tutor { pointer-events:none; }
93
- body.offline .tutor .av { filter:grayscale(1); opacity:.4; box-shadow:none; }
94
- /* ---- 老师页 = 板书页 ---- */
95
- #tutor { position:fixed; inset:0; background:var(--paper); display:none; z-index:10; }
96
- #tutor.on { display:flex; }
97
- #main { flex:1; min-width:0; min-height:0; display:flex; flex-direction:column; }
98
- #main header { display:flex; align-items:center; gap:12px; padding:calc(env(safe-area-inset-top) + 10px) 16px 10px; background:var(--card); border-bottom:1px solid var(--line); }
99
- #main header .av { width:44px; height:44px; font-size:20px; }
100
- .who { flex:1; display:flex; flex-direction:column; min-width:0; }
101
- .who .nm { font-size:18px; font-weight:600; } .who .mo { font-size:13px; color:var(--dim); }
102
- .hb { width:40px; height:40px; display:grid; place-items:center; color:var(--dim); }
103
- .hb.on { color:var(--accent); }
104
- #wrap { position:relative; flex:1; min-height:0; display:flex; flex-direction:column; }
105
- #board { flex:1; min-height:0; overflow:auto; padding:14px 16px 24px; display:flex; flex-direction:column; gap:12px; -webkit-overflow-scrolling:touch; scroll-behavior:smooth; }
106
- /* 舞台:盖住板书那块,留 8px 边;顶栏 = 卡的名字 + 关闭;字幕行与输入条还在下面 */
107
- #stage { position:absolute; inset:8px; border-radius:20px; background:var(--card); box-shadow:0 8px 30px #00000029; display:none; flex-direction:column; z-index:5; overflow:hidden; }
108
- #stage.on { display:flex; }
109
- #stage .top { display:flex; align-items:center; gap:10px; padding:12px 14px 12px 20px; border-bottom:1px solid var(--line); }
110
- #stage .top .ttl { flex:1; min-width:0; font-size:17px; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
111
- #stage .top .kd { font-size:12px; color:var(--dim); border:1px solid var(--line); border-radius:10px; padding:1px 8px; }
112
- #st-x { width:40px; height:40px; display:grid; place-items:center; color:var(--dim); }
113
- #st-body { flex:1; min-height:0; overflow:auto; padding:20px; display:flex; flex-direction:column; gap:14px; -webkit-overflow-scrolling:touch; }
114
- #st-body .c { font-size:20px; line-height:1.6; }
115
- #st-body .c-cover { min-height:220px; } #st-body .c-cover .t { font-size:32px; } #st-body .c-cover .s { font-size:16px; }
116
- #st-body .c-note { font-size:26px; } #st-body .c-formula { font-size:28px; } #st-body .rd { font-size:22px; }
117
- #st-body .c-code { font-size:15px; }
118
- .sq { font-size:22px; font-weight:600; line-height:1.5; }
119
- .so { display:flex; align-items:center; gap:14px; padding:16px 18px; border-radius:16px; background:var(--paper); border:2px solid var(--line); font-size:20px; text-align:left; width:100%; transition:transform .1s; }
120
- .so:active { transform:scale(.985); }
121
- .so i { flex:0 0 auto; width:34px; height:34px; border-radius:50%; border:2px solid var(--line); background:#fff; display:grid; place-items:center; font-style:normal; font-size:15px; font-weight:700; color:var(--dim); }
122
- .so.on { border-color:var(--purple-ink); background:var(--purple); }
123
- .so.on i { background:var(--purple-ink); border-color:var(--purple-ink); color:#fff; }
124
- .ch-o.on { border:2px solid var(--purple-ink); background:var(--purple); }
125
- .ch-o.on i { background:var(--purple-ink); border-color:var(--purple-ink); color:#fff; }
126
- #st-act { padding:12px 20px calc(16px); display:flex; justify-content:flex-end; border-top:1px solid var(--line); }
127
- #st-act[hidden] { display:none; }
128
- #st-go { padding:0 22px; height:48px; border-radius:24px; background:var(--accent); color:#fff; font-size:17px; font-weight:600; }
129
- #st-go:disabled { opacity:.35; }
130
- body.limit #st-go, body.pending #st-go { opacity:.35; pointer-events:none; }
131
- .sec { display:contents; }
132
- .c { border-radius:16px; padding:14px 16px; background:var(--card); font-size:16px; line-height:1.55; -webkit-user-select:text; user-select:text; }
133
- .c-cover { position:relative; min-height:150px; background:linear-gradient(135deg,#d9cfb8,#8e9c8a); color:#fff; display:flex; flex-direction:column; justify-content:flex-end; gap:2px; text-shadow:0 1px 6px #00000066; }
134
- .c-cover .t { font-size:24px; font-weight:700; } .c-cover .s { font-size:13px; opacity:.9; white-space:pre-line; }
135
- .c-note { background:var(--blue); font-size:20px; font-weight:600; text-align:center; line-height:1.5; }
136
- .c-quote { background:var(--cream); font-style:italic; text-align:center; color:#5a5650; }
137
- .c-formula { background:none; text-align:center; font-family:"Times New Roman","Songti SC",serif; font-size:20px; }
138
- .c-step { background:var(--ok); white-space:pre-line; } .st-t { font-size:15px; font-weight:700; color:var(--ok-ink); }
139
- .c-text { background:var(--tutor); font-size:18px; white-space:pre-line; }
140
- .c-read { background:none; padding:0; display:flex; flex-direction:column; gap:8px; }
141
- .rd { padding:12px 16px; border-radius:12px; background:var(--cream); font-size:18px; line-height:1.6; cursor:pointer; }
142
- .rd.on { background:var(--hi); }
143
- .c-choice { background:var(--blue); display:flex; flex-direction:column; gap:8px; }
144
- .ch-q { font-weight:600; }
145
- .ch-o { display:flex; align-items:center; gap:10px; padding:10px 14px; border-radius:12px; background:var(--card); font-size:16px; }
146
- .ch-o i { flex:0 0 auto; width:24px; height:24px; border-radius:50%; border:2px solid var(--line); display:grid; place-items:center; font-style:normal; font-size:12px; font-weight:700; color:var(--dim); }
147
- .c-fill { background:var(--cream); font-size:18px; line-height:2; white-space:pre-line; }
148
- .bl { display:inline-block; min-width:64px; border-bottom:2px solid var(--ink); margin:0 4px; height:1.2em; vertical-align:bottom; }
149
- .c-image { padding:0; overflow:hidden; background:var(--card); }
150
- .c-image img { display:block; width:100%; max-height:320px; object-fit:cover; }
151
- .c-image .cap { padding:10px 16px; font-size:15px; color:#5a5650; white-space:pre-line; }
152
- .bl.f { border-bottom-color:var(--purple-ink); color:var(--purple-ink); font-weight:600; text-align:center; padding:0 6px; }
153
- .fq { font-size:22px; line-height:2.2; }
154
- .fi { display:inline-block; width:120px; margin:0 4px; padding:2px 8px; font:inherit; font-size:20px; font-weight:600; color:var(--purple-ink); text-align:center; background:var(--card); border:0; border-bottom:3px solid var(--purple-ink); border-radius:6px 6px 0 0; outline:0; -webkit-user-select:text; user-select:text; }
155
- .zoom { flex:1; min-height:0; overflow:hidden; display:grid; place-items:center; touch-action:none; }
156
- .zoom img { max-width:100%; max-height:100%; transform-origin:center; will-change:transform; }
157
- .c-scene { background:var(--card); border:2px solid var(--line); display:flex; flex-direction:column; gap:8px; padding:12px 14px; }
158
- .c-scene .sp { font-weight:600; }
159
- .c-scene .th { position:relative; border-radius:12px; overflow:hidden; background:var(--grey); min-height:110px; display:grid; place-items:center; color:var(--dim); font-size:15px; }
160
- .c-scene .th img { display:block; width:100%; max-height:220px; object-fit:contain; background:#fff; }
161
- .c-scene .th .pl { position:absolute; right:10px; bottom:10px; background:#000000aa; color:#fff; border-radius:14px; padding:3px 10px; font-size:13px; }
162
- .c-scene .tx { font-size:15px; color:#5a5650; }
163
- .c-canvas { background:var(--purple); display:flex; flex-direction:column; gap:8px; }
164
- .c-canvas .cp { font-weight:600; } .c-canvas .cb { border:2px dashed var(--purple-ink); border-radius:12px; padding:22px 12px; text-align:center; color:var(--purple-ink); background:#fff; }
165
- #st-frame { flex:1; min-height:0; width:100%; border:0; display:block; background:var(--card); }
166
- #st-frame[hidden] { display:none; }
167
- .c-code { background:#2b2b2b; color:#f6f4ee; font-family:ui-monospace,Menlo,monospace; font-size:14px; white-space:pre-wrap; }
168
- .c-code .lg { display:block; font-size:11px; color:#aaa; margin-bottom:6px; font-family:inherit; }
169
- /* 笔 */
170
- .mk-marker { background:var(--hi); padding:0 3px; border-radius:3px; }
171
- .mk-wave { text-decoration:underline wavy var(--accent); text-underline-offset:4px; }
172
- .mk-box { border:2px solid var(--purple-ink); border-radius:4px; padding:0 3px; }
173
- .mk-green { background:#cdeed9; padding:0 3px; border-radius:3px; }
174
- .mk-circle { position:relative; display:inline-block; padding:0 2px; }
175
- .mk-circle::after { content:""; position:absolute; left:-6px; right:-6px; top:-4px; bottom:-4px; border:2.5px solid var(--ok-ink); border-radius:50%; transform:rotate(-3deg); }
176
- .mk { animation:pen .35s ease-out; }
177
- @keyframes pen { from { opacity:0; } to { opacity:1; } }
178
- /* 字幕行 */
179
- #sub { display:flex; align-items:center; gap:12px; padding:8px 16px 4px; min-height:52px; }
180
- #sub-text { flex:1; font-size:15px; line-height:1.45; color:#5a5650; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
181
- #sub-text.echo, #sub-text.thinking { color:var(--dim); }
182
- #sub-btn { flex:0 0 auto; height:36px; min-width:36px; border-radius:18px; background:#fff; display:inline-flex; align-items:center; justify-content:center; gap:4px; padding:0; font-size:15px; font-weight:600; }
183
- #sub-btn.cont { padding:0 14px; border:1px solid var(--line); }
184
- #sub-btn[hidden] { display:none; }
185
- /* 输入条 */
186
- #bar { padding:8px 16px calc(env(safe-area-inset-bottom) + 12px); }
187
- #pill { display:flex; align-items:center; gap:12px; height:56px; padding:0 14px; border-radius:28px; background:#fff; border:1px solid var(--line); box-shadow:0 2px 10px #00000010; }
188
- #pill .ic { color:var(--ink); display:grid; place-items:center; width:32px; height:44px; }
189
- #mid { flex:1; min-width:0; min-height:44px; display:flex; align-items:center; font-size:17px; color:var(--dim); touch-action:none; }
190
- #typed { flex:1; min-width:0; font:inherit; font-size:18px; color:var(--ink); border:0; outline:0; background:none; -webkit-user-select:text; user-select:text; }
191
- #typed[hidden] { display:none; }
192
- #go { width:36px; height:36px; border-radius:50%; background:var(--ink); color:#fff; display:grid; place-items:center; }
193
- #go[hidden] { display:none; }
194
- body.limit #pill, body.pending #pill { opacity:.45; pointer-events:none; }
195
- #hold { position:absolute; left:0; right:0; bottom:0; height:300px; background:linear-gradient(180deg,#3b82e800 0%,#3b82e8cc 45%,#2f6fd6 100%); display:none; flex-direction:column; align-items:center; justify-content:flex-end; gap:22px; padding-bottom:calc(env(safe-area-inset-bottom) + 70px); color:#fff; pointer-events:none; z-index:20; }
196
- #hold.on { display:flex; }
197
- #hold .w { display:flex; align-items:center; gap:3px; height:28px; }
198
- #hold .w i { width:3px; border-radius:2px; background:#ffffffdd; animation:wave .8s ease-in-out infinite alternate; }
199
- @keyframes wave { from { transform:scaleY(.4); } to { transform:scaleY(1); } }
200
- #sheet { position:absolute; inset:0; display:none; z-index:30; }
201
- #sheet.on { display:block; }
202
- #sheet .dimmer { position:absolute; inset:0; background:#00000033; }
203
- #sheet .panel { position:absolute; left:0; right:0; bottom:0; background:var(--card); border-radius:22px 22px 0 0; padding:18px 20px calc(env(safe-area-inset-bottom) + 24px); display:flex; flex-direction:column; gap:14px; }
204
- #sheet .grab { width:40px; height:4px; border-radius:2px; background:var(--line); align-self:center; }
205
- #sheet .opts { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px; }
206
- #sheet label { display:flex; flex-direction:column; align-items:center; gap:8px; padding:18px 0; border-radius:16px; background:var(--paper); font-size:15px; font-weight:600; }
207
- input[type=file] { display:none; }
208
- /* ---- 平板横屏 ---- */
209
- @media (min-width:900px) and (orientation:landscape) {
210
- #home { max-width:none; display:grid; grid-template-columns:1fr 380px; gap:40px; padding:calc(env(safe-area-inset-top) + 40px) 48px 40px; align-content:start; }
211
- #home h1 { grid-column:1 / -1; font-size:22px; }
212
- #home .tutors { gap:40px; } #home .tutor { width:140px; } #home .tutor .av { width:120px; height:120px; font-size:56px; }
213
- #home .sug { grid-column:1; } #home .side { grid-column:2; grid-row:2 / span 3; display:flex; flex-direction:column; gap:14px; }
214
- #main header { padding-left:32px; padding-right:32px; }
215
- #board { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:14px; align-content:start; width:100%; max-width:1040px; margin:0 auto; padding:16px 32px 24px; }
216
- .c-cover, .c-note, .c-choice, .c-read { grid-column:1 / -1; }
217
- #stage { left:50%; transform:translateX(-50%); width:min(1040px, calc(100% - 16px)); }
218
- #st-body { padding:28px 40px; }
219
- #sub, #bar { width:100%; max-width:760px; margin:0 auto; }
220
- }
221
- </style>
222
- <div id="home">
223
- <h1 id="title">__TITLE__</h1>
224
- <div class="tutors" id="tutors"></div>
225
- <p id="rest">老师们休息中</p>
226
- <div class="sug" id="sug"></div>
227
- <div class="side" id="side"></div>
228
- </div>
229
- <section id="tutor">
230
- <div id="main">
231
- <header>
232
- <button class="hb" id="back" type="button"></button>
233
- <span class="av" id="c-av"></span>
234
- <div class="who"><span class="nm" id="c-nm"></span><span class="mo" id="c-mo"></span></div>
235
- <button class="hb on" id="spk" type="button"></button>
236
- </header>
237
- <div id="wrap">
238
- <div id="board"></div>
239
- <div id="stage"><div class="top"><span class="ttl" id="st-ttl"></span><span class="kd" id="st-kd"></span><button id="st-x" type="button"></button></div><div id="st-body"></div><iframe id="st-frame" hidden title="stage"></iframe><div id="st-act" hidden><button id="st-go" type="button">交给老师</button></div></div>
240
- </div>
241
- <div id="sub"><span id="sub-text"></span><button id="sub-btn" type="button" hidden></button></div>
242
- <div id="bar">
243
- <div id="pill">
244
- <label class="ic" id="cam"><input type="file" accept="image/*" capture="environment"></label>
245
- <div id="mid"><span id="ph">发消息或按住说话…</span><input id="typed" type="text" autocomplete="off" hidden></div>
246
- <button id="go" type="button" hidden></button>
247
- <button class="ic" id="plus" type="button"></button>
248
- </div>
249
- </div>
250
- <div id="hold"><span>松手发送,上移取消</span><div class="w"></div></div>
251
- </div>
252
- <div id="sheet"><div class="dimmer"></div><div class="panel"><div class="grab"></div><div class="opts">
253
- <label><span class="ic" id="ic-album"></span>相册<input type="file" accept="image/*"></label>
254
- <label><span class="ic" id="ic-cam2"></span>拍照<input type="file" accept="image/*" capture="environment"></label>
255
- </div></div></div>
256
- </section>
257
- <script>
258
- (() => {
259
- __BOARD_JS__
260
-
261
- const $ = (s) => document.querySelector(s);
262
- const h = (tag, attrs = {}, ...kids) => {
263
- const el = document.createElement(tag);
264
- for (const [k, v] of Object.entries(attrs)) {
265
- if (k === 'class') el.className = v;
266
- else if (k === 'html') el.innerHTML = v;
267
- else if (k === 'on') for (const [e, f] of Object.entries(v)) el.addEventListener(e, f);
268
- else if (v !== undefined && v !== null) el.setAttribute(k, v);
269
- }
270
- for (const k of kids.flat()) if (k !== null && k !== undefined) el.append(k.nodeType ? k : document.createTextNode(String(k)));
271
- return el;
272
- };
273
- const SVG = (paths, size, sw) => '<svg width="' + size + '" height="' + size + '" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="' + (sw || 2) + '" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + paths + '</svg>';
274
- const ICON = {
275
- back: SVG('<path d="M15 5l-7 7 7 7"></path>', 28, 2.2),
276
- camera: SVG('<path d="M4 8h3l2-3h6l2 3h3v11H4z"></path><circle cx="12" cy="13" r="3.5"></circle>', 22),
277
- plus: SVG('<path d="M12 5v14M5 12h14"></path>', 22, 2.2),
278
- play: SVG('<path d="M8 5l11 7-11 7z" fill="currentColor"></path>', 16, 1.5),
279
- pause: SVG('<path d="M8 5v14M16 5v14"></path>', 16, 2.6),
280
- speaker: SVG('<path d="M4 10v4h4l5 4V6L8 10z"></path><path d="M16 9a4 4 0 0 1 0 6M18.5 6.5a8 8 0 0 1 0 11"></path>', 24),
281
- mute: SVG('<path d="M4 10v4h4l5 4V6L8 10z"></path><path d="M17 9l4 6M21 9l-4 6"></path>', 24),
282
- send: SVG('<path d="M12 19V5M5 12l7-7 7 7"></path>', 20, 2.4),
283
- image: SVG('<rect x="3" y="4" width="18" height="16" rx="2"></rect><path d="M3 16l5-5 4 4 3-3 6 6"></path><circle cx="16" cy="9" r="1.5"></circle>', 36, 1.6),
284
- album: SVG('<rect x="3" y="5" width="18" height="14" rx="2"></rect><path d="M3 15l5-4 4 3 3-2 6 4"></path>', 28),
285
- close: SVG('<path d="M6 6l12 12M18 6L6 18"></path>', 24, 2.2),
286
- check: SVG('<path d="M5 12l5 5 9-10"></path>', 16, 3),
287
- };
288
- const api = async (method, path, body) => {
289
- const r = await fetch(path, { method, headers: body ? { 'content-type': 'application/json' } : {}, body: body ? JSON.stringify(body) : undefined, cache: 'no-store' });
290
- if (!r.ok) { const e = new Error('http ' + r.status); e.status = r.status; throw e; }
291
- return r.json();
292
- };
293
- const PALETTE = ['#e8743b', '#3b82e8', '#2fa36b', '#b45fd1', '#d9a520', '#e0508a'];
294
- const FIXED = { '语文': '#e0508a', '数学': '#3b82e8', '英语': '#2fa36b' };
295
- const color = (s) => { if (FIXED[s]) return FIXED[s]; let x = 0; for (const ch of s || '') x = (x * 31 + ch.codePointAt(0)) >>> 0; return PALETTE[x % PALETTE.length]; };
296
- const DAYS = ['', '一', '二', '三', '四', '五', '六', '日'];
297
- const avatarEl = (t, cls) => {
298
- const el = h('span', { class: 'av ' + (cls || ''), style: 'border-color:' + color(t.subject || t.display) + ';color:' + color(t.subject || t.display) });
299
- if (t.avatar && /\.(png|jpe?g|webp|svg)$/i.test(t.avatar)) el.append(h('img', { src: '/api/kid/avatar/' + t.name, alt: '' }));
300
- else el.textContent = t.avatar || (t.subject || t.display || '?').slice(0, 1);
301
- return el;
302
- };
303
- const debug = new URLSearchParams(location.search);
304
-
305
- // ---- 状态 ----
306
- const S = { home: null, tutor: null, day: null, sections: [], played: new Set(), state: { section: -1, line: -1, status: 'idle' }, echo: null, echoTimer: null, pending: false, limit: false, offline: false, autoplay: true, bar: 'idle', pollTimer: null, stage: null, partial: null };
307
- try { S.autoplay = localStorage.getItem('kid-autoplay') !== '0'; } catch {}
308
-
309
- // ---- 声音:共享 Audio,首个手势解锁(iOS);没配音退回浏览器合成;都没有按字数计时 ----
310
- const audioEl = new Audio();
311
- let unlocked = false;
312
- const unlock = () => { if (unlocked) return; try { audioEl.src = 'data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAIA+AAACABAAZGF0YQAAAAA='; audioEl.play().then(() => { unlocked = true; }).catch(() => {}); } catch {} };
313
- let voiceToken = 0;
314
- const stopVoice = () => { voiceToken++; try { audioEl.pause(); } catch {} try { if ('speechSynthesis' in window) speechSynthesis.cancel(); } catch {} };
315
- /** 念一句;念完调 onEnd(被打断不调) */
316
- const say = (line, onEnd) => {
317
- const token = ++voiceToken;
318
- const finish = () => { if (token === voiceToken) onEnd(); };
319
- const fallback = () => { const ms = lineDurationMs(line.text); setTimeout(finish, ms); };
320
- if (line.audio && S.tutor) {
321
- try { if ('speechSynthesis' in window) speechSynthesis.cancel(); } catch {}
322
- audioEl.onended = finish; audioEl.onerror = () => speak(plainLine(line.text), finish, fallback);
323
- audioEl.src = '/api/audio/' + S.tutor.name + '/' + line.audio.split('/').map(encodeURIComponent).join('/');
324
- audioEl.play().catch(() => speak(plainLine(line.text), finish, fallback));
325
- } else speak(plainLine(line.text), finish, fallback);
326
- };
327
- const speak = (text, onEnd, onFail) => {
328
- try {
329
- if (!('speechSynthesis' in window)) return onFail();
330
- speechSynthesis.cancel();
331
- const u = new SpeechSynthesisUtterance(text); u.lang = 'zh-CN'; u.rate = 0.95;
332
- let ended = false;
333
- u.onend = () => { if (!ended) { ended = true; onEnd(); } };
334
- u.onerror = () => { if (!ended) { ended = true; onFail(); } };
335
- speechSynthesis.speak(u);
336
- // 有的浏览器不发 onend:兜一个按字数的上限
337
- setTimeout(() => { if (!ended) { ended = true; onEnd(); } }, lineDurationMs(text) * 2 + 1500);
338
- } catch { onFail(); }
339
- };
340
-
341
- // ---- 首页 ----
342
- const renderHome = () => {
343
- const H = S.home;
344
- document.title = H.title; $('#title').textContent = H.title;
345
- $('#tutors').replaceChildren(...H.tutors.map((t) => h('button', { type: 'button', class: 'tutor' + (t.available ? '' : ' off'), on: { click: () => openTutor(t) } }, avatarEl(t), h('span', { class: 'nm' }, t.display))));
346
- const sug = Array.isArray(H.suggestions) ? H.suggestions.filter((s) => s && s.text && H.tutors.some((t) => t.name === s.tutor && t.available)) : [];
347
- $('#sug').replaceChildren(...(sug.length ? [h('span', { class: 'lbl' }, '今天可以问'), ...sug.map((s) => { const t = H.tutors.find((x) => x.name === s.tutor); return h('button', { type: 'button', class: 'chip', on: { click: () => openTutor(t, s.text) } }, h('span', { class: 'dot', style: 'background:' + color(t.subject || t.display) }), s.text); })] : []));
348
- const days = [...new Set([...H.timetable.map((e) => e.day), H.day])].sort((a, b) => a - b);
349
- const slots = H.timetable.filter((e) => e.day === H.day).map((e) => h('span', { class: 'slot' + (H.slot === e.subject + ' ' + e.start + '-' + e.end ? ' now' : '') }, h('span', { class: 'dot', style: 'background:' + color(e.subject) }), e.subject + ' ' + e.start + '–' + e.end));
350
- const path = h('div', { class: 'path' }, ...days.map((d) => {
351
- const subjects = [...new Set(H.timetable.filter((e) => e.day === d).map((e) => e.subject))];
352
- const when = d === H.day ? 'now' : d < H.day ? 'past' : 'later';
353
- return h('div', { class: 'station ' + when }, h('span', { class: 'ring', style: 'border-color:' + (subjects.length ? color(subjects[0]) : 'var(--line)') }), h('span', { class: 'd' }, d === H.day ? '今天' : '周' + DAYS[d]));
354
- }));
355
- const stacks = (H.stacks || []).map((s) => h('div', { class: 'stack', style: 'border-color:' + color(s.subject) }, h('div', { class: 'name' }, s.subject), ...s.items.map((a) => h('div', { class: 'item' }, a.id))));
356
- $('#side').replaceChildren(...[h('div', { class: 'today' }, h('span', { class: 'lbl' }, '今天'), slots.length ? h('div', { class: 'slots' }, ...slots) : null, path), stacks.length ? h('div', { class: 'stacks' }, ...stacks) : null].filter(Boolean));
357
- };
358
- const loadHome = async () => {
359
- try { S.home = await api('GET', '/api/kid/home'); setOffline(false); renderHome(); }
360
- catch { setOffline(true); }
361
- };
362
- const setOffline = (off) => {
363
- if (S.offline === off) return;
364
- S.offline = off;
365
- document.body.classList.toggle('offline', off);
366
- if (off && S.tutor) closeTutor();
367
- };
368
-
369
- // ---- 老师页 ----
370
- const openTutor = (t, sendText) => {
371
- unlock();
372
- S.tutor = t; S.sections = []; S.played = new Set(); S.state = { section: -1, line: -1, status: 'idle' }; S.echo = null; S.pending = false; S.limit = false; S.stage = null; S.partial = null; $('#stage').classList.remove('on');
373
- $('#c-av').replaceWith(Object.assign(avatarEl(t), { id: 'c-av' }));
374
- $('#c-nm').textContent = t.display;
375
- $('#c-mo').textContent = t.motto || '';
376
- $('#board').replaceChildren();
377
- $('#tutor').classList.add('on');
378
- setBar('idle');
379
- loadDay(true).then(() => { if (sendText) send(sendText); });
380
- };
381
- const closeTutor = () => { clearTimeout(S.pollTimer); stopVoice(); S.tutor = null; $('#tutor').classList.remove('on'); document.body.classList.remove('pending', 'limit'); loadHome(); };
382
- $('#back').addEventListener('click', closeTutor);
383
- $('#back').innerHTML = ICON.back;
384
-
385
- // 卡片:按 kind 分支(轻插件内联;不认识的 kind 把 props 里的字都显示出来)。紧凑态只读,点了开舞台;stage=true 是舞台里的画法
386
- const renderCard = (c, idx, secIdx, stage) => {
387
- const p = c.props || {};
388
- const box = (cls, ...kids) => h('div', { class: 'c c-' + cls, 'data-card': idx, on: stage ? {} : { click: () => openStage(secIdx, idx) } }, ...kids);
389
- switch (c.kind) {
390
- case 'text': {
391
- const st = p.style;
392
- if (st === 'cover') return box('cover', h('span', { class: 't' }, p.title || ''), p.text ? h('span', { class: 's' }, p.text) : null);
393
- if (st === 'note') return box('note', p.text || '');
394
- if (st === 'quote') return box('quote', '「' + (p.text || '') + '」');
395
- if (st === 'formula') return box('formula', p.text || '');
396
- if (st === 'step') return box('step', h('div', { class: 'st-t' }, p.title || ''), h('div', {}, p.text || ''));
397
- return box('text', p.text || '');
398
- }
399
- case 'read':
400
- return box('read', ...(p.segments || []).map((seg, k) => h('div', { class: 'rd', on: { click: (e) => { e.stopPropagation(); readSegment(e.currentTarget, c, k, seg); } } }, seg)));
401
- case 'image': {
402
- const src = /^https?:\\/\\//.test(p.src || '') ? p.src : '/api/kid/image?p=' + encodeURIComponent(p.src || '');
403
- if (stage) { const el = box('image'); el.className = 'zoom'; el.append(h('img', { src, alt: p.caption || '' })); pinch(el); return el; }
404
- return box('image', h('img', { src, alt: '', loading: 'lazy' }), p.caption ? h('div', { class: 'cap' }, p.caption) : null);
405
- }
406
- case 'choice': {
407
- const picked = (c.state && Array.isArray(c.state.picked)) ? c.state.picked : [];
408
- if (stage) return box('choice', h('div', { class: 'sq' }, p.question || ''), ...(p.options || []).map((o, i) => h('button', { type: 'button', class: 'so' + (picked.includes(i) ? ' on' : ''), on: { click: () => pick(secIdx, idx, i) } }, h('i', {}, picked.includes(i) ? h('span', { html: ICON.check }) : 'ABCDEFGH'[i] || ''), o)));
409
- return box('choice', h('div', { class: 'ch-q' }, p.question || ''), ...(p.options || []).map((o, i) => h('div', { class: 'ch-o' + (picked.includes(i) ? ' on' : '') }, h('i', {}, 'ABCDEFGH'[i] || ''), o)));
410
- }
411
- case 'fill': {
412
- const el = box('fill');
413
- const parts = String(p.text || '').split(/_{2,}/);
414
- const got = filledAnswers(c);
415
- if (stage) {
416
- el.classList.add('fq');
417
- parts.forEach((t, i) => { el.append(t); if (i < parts.length - 1) el.append(h('input', { class: 'fi', type: 'text', value: got[i] || '', autocomplete: 'off', enterkeyhint: 'done', on: { input: (e) => fillIn(secIdx, idx, i, e.target.value), keydown: (e) => { if (e.key === 'Enter') e.target.blur(); }, click: (e) => e.stopPropagation() } })); });
418
- return el;
419
- }
420
- parts.forEach((t, i) => { el.append(t); if (i < parts.length - 1) el.append(h('span', { class: 'bl' + (got[i] ? ' f' : '') }, got[i] || '')); });
421
- return el;
422
- }
423
- case 'scene': {
424
- const ready = sceneReady(c);
425
- const n = Array.isArray(p.steps) ? p.steps.length : 0;
426
- const thumb = ready && p.thumb ? h('img', { src: '/api/kid/image?p=' + encodeURIComponent(p.thumb), alt: '' }) : null;
427
- return box('scene', p.problem ? h('div', { class: 'sp' }, p.problem) : (p.title ? h('div', { class: 'sp' }, p.title) : null), h('div', { class: 'th' }, thumb || (ready ? '' : '图还在路上'), ready ? h('span', { class: 'pl' }, (n ? n + ' 步 ' : '') + '▷') : null), p.text ? h('div', { class: 'tx' }, p.text) : null);
428
- }
429
- case 'canvas': {
430
- const n = inkCount(c);
431
- return box('canvas', h('div', { class: 'cp' }, p.prompt || '画一画'), h('div', { class: 'cb' }, n ? '已经画了 ' + n + ' 笔,点开接着画' : '点开画一画 ✎'));
432
- }
433
- case 'code':
434
- return box('code', p.lang ? h('span', { class: 'lg' }, p.lang) : null, p.text || '');
435
- default:
436
- return box('text', cardTexts(c).filter(Boolean).join('\\n'));
437
- }
438
- };
439
- /** 点读:点哪段念哪段——服务端配好的段(card.assets 里有 <段号>.mp3)放 mp3,没好的用浏览器合成声;讲稿在播就先停下 */
440
- const readSegment = (el, card, k, seg) => {
441
- stopVoice();
442
- if (S.state.status === 'playing') { S.state = { ...S.state, status: 'paused' }; renderSubtitle(); }
443
- for (const x of document.querySelectorAll('.rd.on')) x.classList.remove('on');
444
- el.classList.add('on');
445
- const off = () => el.classList.remove('on');
446
- say({ text: seg, audio: segmentAudio(card, k) }, off);
447
- };
448
- /** 填空:改一个空 → 本地状态、紧凑态重画、400ms 后 PUT(打字中不刷舞台,免得输入框失焦) */
449
- let fillTimer = null;
450
- const fillIn = (secIdx, idx, i, value) => {
451
- const card = S.sections[secIdx].cards[idx];
452
- const answers = filledAnswers(card); answers[i] = value;
453
- card.state = { answers };
454
- $('#st-go').disabled = !stateSummary(card).length;
455
- repaintCard(secIdx, idx);
456
- clearTimeout(fillTimer);
457
- fillTimer = setTimeout(() => saveState(S.sections[secIdx].job, idx, card.state), 400);
458
- };
459
- /** 图片舞台:双指缩放 + 单指拖,双击复位 */
460
- const pinch = (el) => {
461
- const img = el.querySelector('img');
462
- const pts = new Map(); let scale = 1, tx = 0, ty = 0, start = null, lastTap = 0;
463
- const apply = () => { img.style.transform = 'translate(' + tx + 'px,' + ty + 'px) scale(' + scale + ')'; };
464
- const dist = () => { const [a, b] = [...pts.values()]; return Math.hypot(a.x - b.x, a.y - b.y); };
465
- el.addEventListener('pointerdown', (e) => { pts.set(e.pointerId, { x: e.clientX, y: e.clientY }); try { el.setPointerCapture(e.pointerId); } catch {} start = { scale, tx, ty, d: pts.size === 2 ? dist() : 0, x: e.clientX, y: e.clientY }; if (pts.size === 1) { const now = Date.now(); if (now - lastTap < 300) { scale = 1; tx = 0; ty = 0; apply(); } lastTap = now; } });
466
- el.addEventListener('pointermove', (e) => { if (!pts.has(e.pointerId) || !start) return; pts.set(e.pointerId, { x: e.clientX, y: e.clientY }); if (pts.size === 2 && start.d) scale = Math.min(6, Math.max(1, start.scale * dist() / start.d)); else if (pts.size === 1 && scale > 1) { tx = start.tx + (e.clientX - start.x); ty = start.ty + (e.clientY - start.y); } apply(); });
467
- const up = (e) => { pts.delete(e.pointerId); start = pts.size ? { scale, tx, ty, d: pts.size === 2 ? dist() : 0, x: [...pts.values()][0].x, y: [...pts.values()][0].y } : null; };
468
- el.addEventListener('pointerup', up); el.addEventListener('pointercancel', up);
469
- };
470
- const renderSection = (s, i) => h('div', { class: 'sec', 'data-sec': i }, ...s.cards.map((c, idx) => renderCard(c, idx, i, false)));
471
- /** 老师还在说:这节的卡按下标只追加(下标 = 它跑完后会得到的节号,总是最后一节);讲稿不播,声音整轮跑完再从头起 */
472
- const renderPartial = (e) => {
473
- const idx = S.sections.length;
474
- if (!S.partial || S.partial.job !== e.job) { if (S.partial) S.partial.el.remove(); S.partial = { job: e.job, el: h('div', { class: 'sec', 'data-sec': idx }) }; $('#board').append(S.partial.el); }
475
- const el = S.partial.el;
476
- for (let i = el.children.length; i < e.cards.length; i++) { const c = renderCard(e.cards[i], i, idx, false); el.append(c); c.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); }
477
- };
478
- /** 一张卡的状态变了:紧凑态原地重画(标注会掉,重画本节已播到的) */
479
- const repaintCard = (secIdx, idx) => {
480
- const sec = $('#board').querySelector('[data-sec="' + secIdx + '"]');
481
- const old = sec && sec.querySelector('[data-card="' + idx + '"]');
482
- if (!old) return;
483
- old.replaceWith(renderCard(S.sections[secIdx].cards[idx], idx, secIdx, false));
484
- const upTo = S.state.section === secIdx ? S.state.line : S.state.section > secIdx ? undefined : -1;
485
- if (upTo !== -1) for (const l of S.sections[secIdx].lines.slice(0, upTo === undefined ? undefined : upTo + 1)) for (const m of l.marks) if (m.card === idx) applyMark(secIdx, m);
486
- };
487
-
488
- // ---- 舞台:点卡放大,交互都在这里;开着时讲稿暂停,关了字幕行出「播放」 ----
489
- const KIND_NAME = { text: '', read: '点读', choice: '选一选', fill: '填一填', image: '看图', scene: '讲解动画', canvas: '画一画', code: '' };
490
- const GO_LABEL = { canvas: '给老师看' };
491
- const openStage = (secIdx, idx, opts = {}) => {
492
- const card = S.sections[secIdx] && S.sections[secIdx].cards[idx];
493
- if (!card) return;
494
- if (isHeavy(card) && !sceneReady(card) && card.kind === 'scene') return; // 课包还没到:紧凑态写着「图还在路上」,不开
495
- if (S.state.status === 'playing' && !opts.delegate) { stopVoice(); S.state = { ...S.state, status: 'paused' }; renderSubtitle(); }
496
- S.stage = { section: secIdx, card: idx, id: S.sections[secIdx].job + '/' + idx, scene: null, delegate: Boolean(opts.delegate), autoplay: Boolean(opts.autoplay) };
497
- $('#st-ttl').textContent = cardTitle(card);
498
- $('#st-kd').textContent = KIND_NAME[card.kind] || card.kind;
499
- $('#st-kd').hidden = !(KIND_NAME[card.kind] || card.kind);
500
- renderStage();
501
- $('#stage').classList.add('on');
502
- };
503
- const STAGE_SOURCE = 'cotutor-stage';
504
- const frame = $('#st-frame');
505
- const postStage = (m) => { try { frame.contentWindow.postMessage({ source: STAGE_SOURCE, ...m }, '*'); } catch {} };
506
- const renderStage = () => {
507
- if (!S.stage) return;
508
- const card = S.sections[S.stage.section].cards[S.stage.card];
509
- const heavy = isHeavy(card);
510
- $('#st-body').hidden = heavy;
511
- frame.hidden = !heavy;
512
- if (heavy) { $('#st-body').replaceChildren(); frame.src = '/stage/?card=' + encodeURIComponent(S.stage.id); }
513
- else { frame.src = 'about:blank'; $('#st-body').replaceChildren(renderCard(card, S.stage.card, S.stage.section, true)); }
514
- const act = $('#st-act'); act.hidden = !hasState(card);
515
- $('#st-go').textContent = GO_LABEL[card.kind] || '交给老师';
516
- $('#st-go').disabled = !stateSummary(card).length;
517
- };
518
- /** 舞台包说话:ready → 把卡发过去;phase → 字幕行;state → 存;done 且是讲稿委托的 → 关舞台接着念 */
519
- window.addEventListener('message', (e) => {
520
- const m = e.data;
521
- if (!m || m.source !== STAGE_SOURCE || !S.stage) return;
522
- const card = S.sections[S.stage.section].cards[S.stage.card];
523
- if (m.type === 'ready') { const b = card.kind === 'scene' ? card.props.bundle : card.kind === 'canvas' && card.props.base && card.props.base.bundle ? card.props.base.bundle : null; postStage({ type: 'card', id: S.stage.id, kind: card.kind, props: card.props, state: card.state === undefined ? null : card.state, bundleUrl: b ? '/api/bundles/' + encodeURIComponent(b) + '/' : undefined, autoplay: S.stage.autoplay }); }
524
- else if (m.type === 'phase') { S.stage.scene = { phase: m.phase, line: m.line, step: m.step, total: m.total }; renderSubtitle(); if (m.phase === 'done' && S.stage.delegate) { const d = S.stage; closeStage(); resumeAfter(d); } }
525
- else if (m.type === 'state') { card.state = m.state; $('#st-go').disabled = !stateSummary(card).length; repaintCard(S.stage.section, S.stage.card); saveState(S.sections[S.stage.section].job, S.stage.card, m.state); }
526
- else if (m.type === 'submit') { card.state = m.state; const id = S.stage.id; const job = S.sections[S.stage.section].job; const idx = S.stage.card; closeStage(); api('PUT', '/api/kid/conversations/' + S.tutor.name + '/cards/' + job + '/' + idx, m.image ? { ...m.state, image: m.image } : m.state).catch(() => {}).then(() => send('', { action: 'submit', focus: { card: id }, echoText: '你:' + (stateSummary(card).join('、') || '给老师看') })); }
527
- else if (m.type === 'close' || m.type === 'error') { const d = S.stage; closeStage(); if (d.delegate) resumeAfter(d); }
528
- });
529
- /** 讲稿委托给场景播完(或孩子关了)→ 接着念下一句 */
530
- const resumeAfter = (d) => { if (S.state.status !== 'stage') return; S.state = advance({ ...S.state, status: 'playing' }, S.sections); if (S.state.status === 'playing') playLine(); else renderSubtitle(); };
531
- const closeStage = () => { S.stage = null; frame.src = 'about:blank'; $('#stage').classList.remove('on'); renderSubtitle(); };
532
- $('#st-x').innerHTML = ICON.close;
533
- $('#st-x').addEventListener('click', closeStage);
534
- /** 选择题:点一项 → 本地改状态、重画、PUT 到服务端(失败不响,下次再点再存) */
535
- const pick = (secIdx, idx, i) => {
536
- const card = S.sections[secIdx].cards[idx];
537
- const cur = (card.state && Array.isArray(card.state.picked)) ? card.state.picked : [];
538
- card.state = { picked: togglePick(cur, i, Boolean(card.props.multi)) };
539
- renderStage(); repaintCard(secIdx, idx);
540
- saveState(S.sections[secIdx].job, idx, card.state);
541
- };
542
- const saveState = (job, idx, state) => { if (!S.tutor) return; api('PUT', '/api/kid/conversations/' + S.tutor.name + '/cards/' + job + '/' + idx, state).catch(() => {}); };
543
- $('#st-go').addEventListener('click', () => {
544
- if (!S.stage) return;
545
- const card = S.sections[S.stage.section].cards[S.stage.card];
546
- if (isHeavy(card)) { postStage({ type: 'control', action: 'submit' }); return; } // 画板:让舞台包导出 png 再交
547
- const labels = stateSummary(card);
548
- if (!labels.length) return;
549
- const id = S.stage.id;
550
- clearTimeout(fillTimer); if (card.kind === 'fill') saveState(S.sections[S.stage.section].job, S.stage.card, card.state);
551
- closeStage();
552
- send('', { action: 'submit', focus: { card: id }, echoText: '你:' + labels.join('、') });
553
- });
554
- const applyMark = (secIdx, mark) => {
555
- const sec = $('#board').querySelector('[data-sec="' + secIdx + '"]');
556
- const card = sec && sec.querySelector('[data-card="' + mark.card + '"]');
557
- if (!card) return null;
558
- const cardData = S.sections[secIdx].cards[mark.card];
559
- const walker = document.createTreeWalker(card, NodeFilter.SHOW_TEXT);
560
- let n;
561
- while ((n = walker.nextNode())) {
562
- const i = n.nodeValue.indexOf(mark.phrase);
563
- if (i < 0 || n.parentElement.classList.contains('mk')) continue;
564
- const range = document.createRange(); range.setStart(n, i); range.setEnd(n, i + mark.phrase.length);
565
- const span = document.createElement('span'); span.className = 'mk mk-' + markStyle(cardData);
566
- range.surroundContents(span);
567
- return card;
568
- }
569
- return card;
570
- };
571
- const renderSubtitle = () => {
572
- let v = subtitleFor({ state: S.state, sections: S.sections, echo: S.echo, pending: S.pending, thinking: (S.tutor && S.tutor.thinking) || '让我想想…', limit: S.limit });
573
- if (S.stage && S.stage.scene && !S.echo && !S.limit) v = sceneSubtitle(S.stage.scene.phase, S.stage.scene.line, S.stage.scene.step, S.stage.scene.total);
574
- const t = $('#sub-text'); t.textContent = v.text; t.className = v.kind;
575
- const b = $('#sub-btn');
576
- b.hidden = v.right === 'none';
577
- b.className = v.right === 'continue' ? 'cont' : '';
578
- b.innerHTML = v.right === 'pause' ? ICON.pause : v.right === 'play' ? ICON.play : v.right === 'continue' ? ICON.play + '<span>继续</span>' : '';
579
- document.body.classList.toggle('pending', S.pending);
580
- document.body.classList.toggle('limit', S.limit);
581
- };
582
- $('#sub-btn').addEventListener('click', () => {
583
- if (S.stage && S.stage.scene) { postStage({ type: 'control', action: 'toggle' }); return; }
584
- if (S.state.status === 'playing') { stopVoice(); S.state = { ...S.state, status: 'paused' }; renderSubtitle(); }
585
- else if (S.state.status === 'paused') { S.state = { ...S.state, status: 'playing' }; playLine(); }
586
- else if (S.state.status === 'waiting') send('', { action: 'continue', echo: false });
587
- });
588
-
589
- // 播放:一句 = 字幕 + 标注 + 滚到那张卡 + 声音;播完往下走
590
- const playLine = () => {
591
- const s = S.sections[S.state.section]; const line = s && s.lines[S.state.line];
592
- if (!line) { S.state = { ...S.state, status: 'done' }; renderSubtitle(); return; }
593
- renderSubtitle();
594
- let target = null;
595
- for (const m of line.marks) target = applyMark(S.state.section, m) || target;
596
- if (!target) { const at = lineTarget(line); target = $('#board').querySelector('[data-sec="' + S.state.section + '"] ' + (at === null ? '.c' : '[data-card="' + at + '"]')); }
597
- if (target) target.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
598
- say(line, () => {
599
- if (S.state.status !== 'playing') return;
600
- // [[play]]:念完这句把场景铺满播,播完(done)再接着念
601
- const play = line.cues.find((c) => c.name === 'play');
602
- const target = play && S.sections[S.state.section].cards[play.card];
603
- if (target && target.kind === 'scene' && sceneReady(target)) { S.state = { ...S.state, status: 'stage' }; openStage(S.state.section, play.card, { delegate: true, autoplay: true }); return; }
604
- S.state = advance(S.state, S.sections);
605
- if (S.state.status === 'playing') playLine();
606
- else { renderSubtitle(); if (S.state.status === 'waiting') openAskCard(S.state.section, line); }
607
- });
608
- };
609
- /** 末句问句停下时,锚点卡有交互(选择题)就把它推到舞台等答 */
610
- const openAskCard = (secIdx, line) => {
611
- const at = lineTarget(line);
612
- const card = at !== null && S.sections[secIdx] && S.sections[secIdx].cards[at];
613
- if (card && hasState(card) && !S.stage) openStage(secIdx, at);
614
- };
615
- /** 把一节的标注一次画齐(打开页面、不出声时) */
616
- const paintAll = (secIdx, upTo) => { const s = S.sections[secIdx]; if (!s) return; for (const l of s.lines.slice(0, upTo === undefined ? s.lines.length : upTo + 1)) for (const m of l.marks) applyMark(secIdx, m); };
617
-
618
- const loadDay = async (silent) => {
619
- if (!S.tutor) return;
620
- try {
621
- const d = await api('GET', '/api/kid/conversations/' + S.tutor.name + '/today');
622
- setOffline(false);
623
- S.day = d;
624
- S.limit = d.remaining <= 0;
625
- const entries = sectionsFromMessages(d.messages);
626
- const fresh = [];
627
- for (const e of entries) {
628
- if (S.played.has(e.job)) continue;
629
- if (e.partial) { renderPartial(e); continue; }
630
- S.played.add(e.job); S.sections.push(e); fresh.push(S.sections.length - 1);
631
- const idx = S.sections.length - 1;
632
- if (S.partial && S.partial.job === e.job) {
633
- // 流式时先铺的卡留着,只补后面的;节的编号就是它现在的位置
634
- const el = S.partial.el; S.partial = null;
635
- for (let i = el.children.length; i < e.cards.length; i++) el.append(renderCard(e.cards[i], i, idx, false));
636
- el.setAttribute('data-sec', idx);
637
- } else $('#board').append(renderSection(e, idx));
638
- }
639
- if (S.partial && !entries.some((e) => e.partial && e.job === S.partial.job)) { S.partial.el.remove(); S.partial = null; }
640
- // 服务端的状态是真相(别的设备上选的、重开页面):没在舞台里改着的卡照它画
641
- entries.forEach((e) => { const i = S.sections.findIndex((x) => x.job === e.job); if (i < 0 || fresh.includes(i)) return; e.cards.forEach((c, idx) => { const mine = S.sections[i].cards[idx]; if (JSON.stringify(mine.state) !== JSON.stringify(c.state) && !(S.stage && S.stage.section === i && S.stage.card === idx)) { mine.state = c.state; repaintCard(i, idx); } }); });
642
- if (fresh.length && S.stage) closeStage();
643
- const stillPending = Boolean(d.pending) || d.messages.some((m) => m.pending);
644
- S.pending = stillPending;
645
- if (fresh.length) {
646
- if (silent || !S.autoplay) { for (const i of fresh) paintAll(i); S.state = playerAtEnd(S.sections); renderSubtitle(); const last = $('#board').querySelector('[data-sec="' + (S.sections.length - 1) + '"] .c'); if (last) last.scrollIntoView({ block: 'start' }); }
647
- else { stopVoice(); S.state = startSection(fresh[0], S.sections); if (S.state.status === 'playing') playLine(); else renderSubtitle(); }
648
- } else renderSubtitle();
649
- clearTimeout(S.pollTimer);
650
- if (stillPending) S.pollTimer = setTimeout(() => loadDay(false), 1000);
651
- } catch (e) {
652
- if (e && e.status === 404) return closeTutor();
653
- setOffline(true);
654
- }
655
- };
656
- const showEcho = (text) => { clearTimeout(S.echoTimer); S.echo = text; renderSubtitle(); S.echoTimer = setTimeout(() => { S.echo = null; renderSubtitle(); }, 2500); };
657
- const send = async (text, opts = {}) => {
658
- text = (text || '').trim();
659
- if ((!text && !opts.action) || !S.tutor || (S.limit && opts.action !== 'continue')) return;
660
- unlock();
661
- stopVoice();
662
- if (S.state.status === 'playing' || S.state.status === 'paused' || S.state.status === 'stage') S.state = { ...S.state, status: 'done' };
663
- if (opts.echo !== false) showEcho(opts.echoText || ('你:' + text));
664
- S.pending = true; renderSubtitle();
665
- const body = { text };
666
- if (opts.action) body.action = opts.action;
667
- const focus = opts.focus || (S.stage ? { card: S.stage.id } : null);
668
- if (focus) body.focus = focus;
669
- try {
670
- await api('POST', '/api/kid/conversations/' + S.tutor.name + '/messages', body);
671
- clearTimeout(S.pollTimer); S.pollTimer = setTimeout(() => loadDay(false), 1200);
672
- } catch (e) {
673
- // 忙 / 上限 / 不通:什么都不报;刷新一下让状态说话
674
- S.pending = false;
675
- if (e && e.status === 429) { S.limit = true; renderSubtitle(); } else if (e && e.status === 409) loadDay(true); else setOffline(true);
676
- }
677
- };
678
-
679
- // ---- 喇叭:自动朗读开关 ----
680
- const spk = $('#spk');
681
- const renderSpk = () => { spk.innerHTML = S.autoplay ? ICON.speaker : ICON.mute; spk.classList.toggle('on', S.autoplay); };
682
- spk.addEventListener('click', () => { S.autoplay = !S.autoplay; try { localStorage.setItem('kid-autoplay', S.autoplay ? '1' : '0'); } catch {} renderSpk(); if (!S.autoplay && S.state.status === 'playing') { stopVoice(); paintAll(S.state.section); S.state = playerAtEnd(S.sections); renderSubtitle(); } });
683
- renderSpk();
684
-
685
- // ---- 输入条:相机 | 发消息或按住说话 | 加号 ----
686
- $('#cam').insertAdjacentHTML('afterbegin', ICON.camera);
687
- $('#plus').innerHTML = ICON.plus;
688
- $('#go').innerHTML = ICON.send;
689
- $('#ic-album').innerHTML = ICON.album; $('#ic-cam2').innerHTML = ICON.camera;
690
- const setBar = (mode) => {
691
- S.bar = mode;
692
- $('#ph').hidden = mode === 'typing'; $('#typed').hidden = mode !== 'typing'; $('#go').hidden = mode !== 'typing';
693
- $('#hold').classList.toggle('on', mode === 'holding');
694
- if (mode === 'typing') $('#typed').focus();
695
- };
696
- const mid = $('#mid'), typed = $('#typed');
697
- $('#go').addEventListener('click', () => { const t = typed.value; typed.value = ''; setBar(barNext(S.bar, 'sent')); send(t); });
698
- typed.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); $('#go').click(); } });
699
- typed.addEventListener('blur', () => { if (!typed.value.trim()) setBar(barNext(S.bar, 'blur')); });
700
- $('#plus').addEventListener('click', () => $('#sheet').classList.add('on'));
701
- $('#sheet .dimmer').addEventListener('click', () => $('#sheet').classList.remove('on'));
702
- for (const f of document.querySelectorAll('input[type=file]')) f.addEventListener('change', () => { $('#sheet').classList.remove('on'); f.value = ''; /* 照片走 R5 作业线,现在只收下不发 */ });
703
- $('#hold .w').replaceChildren(...[6, 10, 16, 22, 12, 26, 18, 8, 14, 24, 20, 10, 16, 28, 12, 8, 18, 22, 10, 14, 6, 12, 20, 16, 8].map((v, i) => h('i', { style: 'height:' + v + 'px;animation-delay:' + (i * 37 % 400) + 'ms' })));
704
-
705
- // 中间那段:点 = 打字;按住 150ms = 说话(浏览器识别),松手发,上滑 60px 取消;没有识别就只有打字
706
- const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
707
- let press = null, rec = null, finalText = '';
708
- const startRec = () => {
709
- finalText = '';
710
- try {
711
- rec = new SR(); rec.lang = 'zh-CN'; rec.interimResults = true; rec.continuous = false; rec.maxAlternatives = 1;
712
- rec.onresult = (ev) => { let s = ''; for (const r of ev.results) s += r[0].transcript; finalText = s; };
713
- rec.onerror = () => {};
714
- rec.onend = () => { const cancelled = press && press.cancelled; const t = finalText; rec = null; if (!cancelled && t.trim()) send(t); };
715
- rec.start();
716
- } catch { rec = null; }
717
- };
718
- mid.addEventListener('pointerdown', (e) => {
719
- if (S.bar === 'typing') return;
720
- e.preventDefault(); unlock();
721
- press = { y: e.clientY, cancelled: false, held: false, timer: setTimeout(() => { if (!press || !SR) return; press.held = true; setBar(barNext(S.bar, 'holdStart')); stopVoice(); startRec(); }, 150) };
722
- try { mid.setPointerCapture(e.pointerId); } catch {}
723
- });
724
- mid.addEventListener('pointermove', (e) => { if (press && press.held) { const up = press.y - e.clientY > 60; if (up !== press.cancelled) { press.cancelled = up; $('#hold span').textContent = up ? '松手取消' : '松手发送,上移取消'; } } });
725
- const release = () => {
726
- if (!press) return;
727
- clearTimeout(press.timer);
728
- if (press.held) { setBar(barNext(S.bar, press.cancelled ? 'holdCancel' : 'holdEnd')); if (rec) { try { rec.stop(); } catch {} } }
729
- else setBar(barNext(S.bar, 'tap'));
730
- const p = press; press = null;
731
- if (rec && p.cancelled) { const r = rec; rec = null; try { r.onend = null; r.abort(); } catch {} }
732
- };
733
- mid.addEventListener('pointerup', release);
734
- mid.addEventListener('pointercancel', release);
735
- mid.addEventListener('contextmenu', (e) => e.preventDefault());
736
-
737
- // ---- 调试:?step=<节>.<句> 停在某句(截图 / 测试用,不出声) ----
738
- const jumpTo = () => {
739
- const step = debug.get('step');
740
- if ((!step && !debug.get('stage')) || !S.sections.length) return;
741
- const [a, b] = (step || '0.0').split('.').map(Number);
742
- stopVoice();
743
- const sec = Math.min(Math.max(a || 0, 0), S.sections.length - 1);
744
- const line = Math.min(Math.max(b || 0, 0), Math.max(S.sections[sec].lines.length - 1, 0));
745
- S.state = { section: sec, line, status: 'paused' };
746
- for (let i = 0; i < sec; i++) paintAll(i);
747
- paintAll(sec, line);
748
- if (S.sections[sec].lines[line] && S.sections[sec].lines[line].ask && line === S.sections[sec].lines.length - 1 && sec === S.sections.length - 1) S.state.status = 'waiting';
749
- renderSubtitle();
750
- const tgt = S.sections[sec].lines[line] ? lineTarget(S.sections[sec].lines[line]) : null;
751
- const at = $('#board').querySelector('[data-sec="' + sec + '"] ' + (tgt === null ? '.c' : '[data-card="' + tgt + '"]'));
752
- if (at) at.scrollIntoView({ block: 'center' });
753
- const st = debug.get('stage');
754
- if (st) { const [x, y] = st.split('.').map(Number); openStage(x || 0, y || 0); }
755
- };
756
-
757
- // ---- 启动与心跳:不通就头像灰,什么都不报 ----
758
- loadHome().then(() => {
759
- const open = debug.get('tutor');
760
- if (open && S.home) { const t = S.home.tutors.find((x) => x.name === open); if (t) { openTutor(t); setTimeout(jumpTo, 400); } }
761
- });
762
- setInterval(() => { if (S.tutor) { if (!S.pending) loadDay(true); } else loadHome(); }, 5000);
763
- document.addEventListener('visibilitychange', () => { if (!document.hidden) (S.tutor ? loadDay(true) : loadHome()); });
764
- })();
765
- </script>
766
- </html>
767
- `;
768
- export const KID_PAGE = PAGE.replace('__BOARD_JS__', boardLibSource());