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.
- package/LICENSE +249 -21
- package/NOTICE +42 -0
- package/README.md +115 -20
- package/avatars/chinese.webp +0 -0
- package/avatars/english.webp +0 -0
- package/avatars/math.webp +0 -0
- package/bin/cotutor.js +15 -5
- package/cards/canvas/card.css +8 -0
- package/cards/canvas/card.md +68 -0
- package/cards/choice/card.css +18 -0
- package/cards/choice/card.md +55 -0
- package/cards/code/card.css +5 -0
- package/cards/code/card.md +46 -0
- package/cards/dictation/card.css +10 -0
- package/cards/dictation/card.md +97 -0
- package/cards/image/card.css +10 -0
- package/cards/image/card.md +52 -0
- package/cards/lecture/card.css +9 -0
- package/cards/lecture/card.md +69 -0
- package/cards/paint/card.css +20 -0
- package/cards/paint/card.md +77 -0
- package/cards/photo/card.css +22 -0
- package/cards/photo/card.md +61 -0
- package/cards/read/card.css +6 -0
- package/cards/read/card.md +51 -0
- package/cards/text/card.css +3 -0
- package/cards/text/card.md +69 -0
- package/cards/tianzige/card.css +17 -0
- package/cards/tianzige/card.md +78 -0
- package/cards/tutor/card.css +79 -0
- package/cards/tutor/card.md +149 -0
- package/cards/word/card.css +24 -0
- package/cards/word/card.md +91 -0
- package/cards//346/235/277/344/271/246/346/200/216/344/271/210/345/206/231.md +159 -0
- package/dist/app/activity-eval-rules.d.ts +62 -0
- package/dist/app/activity-eval-rules.js +276 -0
- package/dist/app/activity-eval.d.ts +65 -0
- package/dist/app/activity-eval.js +159 -0
- package/dist/app/build.d.ts +11 -0
- package/dist/app/build.js +45 -0
- package/dist/app/cli/activities.d.ts +70 -0
- package/dist/app/cli/activities.js +196 -0
- package/dist/app/cli/cert.d.ts +12 -0
- package/dist/app/cli/cert.js +49 -0
- package/dist/app/cli/chalk/bake.d.ts +10 -0
- package/dist/app/cli/chalk/bake.js +58 -0
- package/dist/app/cli/chalk/browser.d.ts +16 -0
- package/dist/app/cli/chalk/browser.js +157 -0
- package/dist/app/cli/chalk/build.d.ts +14 -0
- package/dist/app/cli/chalk/build.js +100 -0
- package/dist/app/cli/chalk/dub.d.ts +51 -0
- package/dist/app/cli/chalk/dub.js +106 -0
- package/dist/app/cli/chalk/load-scene.d.ts +34 -0
- package/dist/app/cli/chalk/load-scene.js +117 -0
- package/dist/app/cli/chalk/main.d.ts +7 -0
- package/dist/app/cli/chalk/main.js +199 -0
- package/dist/app/cli/chalk/serve.d.ts +4 -0
- package/dist/app/cli/chalk/serve.js +28 -0
- package/dist/app/cli/chalk/snap.d.ts +65 -0
- package/dist/app/cli/chalk/snap.js +103 -0
- package/dist/app/cli/chalk/typecheck.d.ts +7 -0
- package/dist/app/cli/chalk/typecheck.js +51 -0
- package/dist/app/cli/doctor.d.ts +24 -0
- package/dist/app/cli/doctor.js +553 -0
- package/dist/app/cli/firewall.d.ts +24 -0
- package/dist/app/cli/firewall.js +79 -0
- package/dist/app/cli/grades.d.ts +14 -0
- package/dist/app/cli/grades.js +44 -0
- package/dist/{cli → app/cli}/init.d.ts +3 -1
- package/dist/app/cli/init.js +158 -0
- package/dist/app/cli/main.js +1038 -0
- package/dist/app/cli/migrate.d.ts +49 -0
- package/dist/app/cli/migrate.js +195 -0
- package/dist/app/cli/port-holder.d.ts +16 -0
- package/dist/app/cli/port-holder.js +34 -0
- package/dist/app/cli/port.d.ts +37 -0
- package/dist/app/cli/port.js +116 -0
- package/dist/app/cli/sandbox.d.ts +25 -0
- package/dist/app/cli/sandbox.js +166 -0
- package/dist/app/cli/serve-log.d.ts +13 -0
- package/dist/app/cli/serve-log.js +90 -0
- package/dist/app/cli/serve.d.ts +51 -0
- package/dist/app/cli/serve.js +168 -0
- package/dist/app/cli/service-windows.d.ts +50 -0
- package/dist/app/cli/service-windows.js +117 -0
- package/dist/app/cli/service.d.ts +138 -0
- package/dist/app/cli/service.js +429 -0
- package/dist/app/cli/shipped.d.ts +30 -0
- package/dist/app/cli/shipped.js +47 -0
- package/dist/app/cli/skeleton.d.ts +38 -0
- package/dist/app/cli/skeleton.js +161 -0
- package/dist/app/cli/skills.d.ts +59 -0
- package/dist/app/cli/skills.js +199 -0
- package/dist/app/cli/themes.d.ts +52 -0
- package/dist/app/cli/themes.js +148 -0
- package/dist/app/cli/tray-icons.d.ts +11 -0
- package/dist/app/cli/tray-icons.js +87 -0
- package/dist/app/cli/tray-ids.d.ts +6 -0
- package/dist/app/cli/tray-ids.js +6 -0
- package/dist/app/cli/tray-mac.d.ts +8 -0
- package/dist/app/cli/tray-mac.js +140 -0
- package/dist/app/cli/tray-run.d.ts +1 -0
- package/dist/app/cli/tray-run.js +33 -0
- package/dist/app/cli/tray-windows.d.ts +5 -0
- package/dist/app/cli/tray-windows.js +166 -0
- package/dist/app/cli/tray.d.ts +205 -0
- package/dist/app/cli/tray.js +618 -0
- package/dist/{cli → app/cli}/tutors.d.ts +15 -26
- package/dist/{cli → app/cli}/tutors.js +86 -110
- package/dist/app/cli/usage.d.ts +2 -0
- package/dist/app/cli/usage.js +35 -0
- package/dist/app/dictation/recognize.d.ts +18 -0
- package/dist/app/dictation/recognize.js +6 -0
- package/dist/app/dictation/route.d.ts +52 -0
- package/dist/app/dictation/route.js +511 -0
- package/dist/app/diff.d.ts +11 -0
- package/dist/app/diff.js +71 -0
- package/dist/app/http/access.d.ts +62 -0
- package/dist/app/http/access.js +180 -0
- package/dist/app/http/app.d.ts +241 -0
- package/dist/app/http/app.js +1159 -0
- package/dist/app/http/avatars.d.ts +17 -0
- package/dist/app/http/avatars.js +35 -0
- package/dist/app/http/dev-activities.d.ts +115 -0
- package/dist/app/http/dev-activities.js +193 -0
- package/dist/app/http/dev-overview.d.ts +108 -0
- package/dist/app/http/dev-overview.js +209 -0
- package/dist/app/http/dev-tutor-files.d.ts +80 -0
- package/dist/app/http/dev-tutor-files.js +151 -0
- package/dist/app/http/parent-home.d.ts +51 -0
- package/dist/app/http/parent-home.js +266 -0
- package/dist/app/http/parent-look.d.ts +2 -0
- package/dist/app/http/parent-look.js +151 -0
- package/dist/app/http/range.d.ts +9 -0
- package/dist/app/http/range.js +19 -0
- package/dist/app/http/raw-view.d.ts +117 -0
- package/dist/app/http/raw-view.js +149 -0
- package/dist/app/http/send-file.d.ts +7 -0
- package/dist/app/http/send-file.js +29 -0
- package/dist/{server → app/http}/stage.d.ts +9 -0
- package/dist/{server → app/http}/stage.js +34 -6
- package/dist/app/http/theme.d.ts +13 -0
- package/dist/app/http/theme.js +41 -0
- package/dist/app/http/voice-preview.d.ts +4 -0
- package/dist/app/http/voice-preview.js +34 -0
- package/dist/app/machine/bailian.d.ts +26 -0
- package/dist/app/machine/bailian.js +87 -0
- package/dist/app/mock/mock-live.d.ts +16 -0
- package/dist/app/mock/mock-live.js +140 -0
- package/dist/{server → app/mock}/mock.d.ts +38 -5
- package/dist/app/mock/mock.js +1125 -0
- package/dist/app/replay.d.ts +94 -0
- package/dist/app/replay.js +170 -0
- package/dist/app/skills/analyze-doc.d.ts +3 -0
- package/dist/app/skills/analyze-doc.js +47 -0
- package/dist/app/skills/home-doc.d.ts +5 -0
- package/dist/app/skills/home-doc.js +112 -0
- package/dist/app/skills/tune-doc.d.ts +3 -0
- package/dist/app/skills/tune-doc.js +80 -0
- package/dist/app/talk/list.d.ts +11 -0
- package/dist/app/talk/list.js +9 -0
- package/dist/app/talk/route.d.ts +16 -0
- package/dist/app/talk/route.js +142 -0
- package/dist/app/workspace.d.ts +63 -0
- package/dist/{cli → app}/workspace.js +100 -52
- package/dist/app/ws/route.d.ts +31 -0
- package/dist/app/ws/route.js +33 -0
- package/dist/app/ws/ws.d.ts +31 -0
- package/dist/app/ws/ws.js +169 -0
- package/dist/board/canvas-chat.d.ts +97 -0
- package/dist/board/canvas-chat.js +175 -0
- package/dist/board/card-guards.d.ts +15 -0
- package/dist/board/card-guards.js +38 -0
- package/dist/board/cards/canvas.d.ts +41 -0
- package/dist/board/cards/canvas.js +83 -0
- package/dist/{cards → board/cards}/choice.js +0 -12
- package/dist/{cards → board/cards}/code.js +0 -3
- package/dist/board/cards/dictation.d.ts +42 -0
- package/dist/board/cards/dictation.js +105 -0
- package/dist/board/cards/docs.d.ts +47 -0
- package/dist/board/cards/docs.js +152 -0
- package/dist/{cards → board/cards}/image.js +1 -10
- package/dist/board/cards/index.d.ts +62 -0
- package/dist/board/cards/index.js +144 -0
- package/dist/{cards → board/cards}/kind.d.ts +10 -4
- package/dist/board/cards/kind.js +9 -0
- package/dist/board/cards/lecture.d.ts +26 -0
- package/dist/board/cards/lecture.js +62 -0
- package/dist/board/cards/paint.d.ts +38 -0
- package/dist/board/cards/paint.js +85 -0
- package/dist/board/cards/photo.d.ts +15 -0
- package/dist/board/cards/photo.js +29 -0
- package/dist/{cards → board/cards}/read.js +1 -10
- package/dist/board/cards/text.d.ts +20 -0
- package/dist/board/cards/text.js +59 -0
- package/dist/board/cards/tianzige.d.ts +16 -0
- package/dist/board/cards/tianzige.js +64 -0
- package/dist/board/cards/tutor.d.ts +72 -0
- package/dist/board/cards/tutor.js +134 -0
- package/dist/board/cards/word.d.ts +20 -0
- package/dist/board/cards/word.js +76 -0
- package/dist/board/lecture.d.ts +100 -0
- package/dist/board/lecture.js +204 -0
- package/dist/board/paper.d.ts +20 -0
- package/dist/board/paper.js +76 -0
- package/dist/board/parse.d.ts +118 -0
- package/dist/board/parse.js +324 -0
- package/dist/board/presentation.d.ts +289 -0
- package/dist/board/presentation.js +613 -0
- package/dist/board/sections.d.ts +16 -0
- package/dist/board/sections.js +130 -0
- package/dist/brain/api-tools.d.ts +8 -0
- package/dist/brain/api-tools.js +191 -0
- package/dist/brain/api.d.ts +57 -0
- package/dist/brain/api.js +394 -0
- package/dist/brain/index.d.ts +97 -0
- package/dist/brain/index.js +11 -0
- package/dist/brain/log.d.ts +5 -0
- package/dist/brain/log.js +22 -0
- package/dist/brain/runtime.d.ts +30 -0
- package/dist/brain/runtime.js +23 -0
- package/dist/brain/stream-json.d.ts +16 -0
- package/dist/brain/stream-json.js +82 -0
- package/dist/brain/stream.d.ts +20 -0
- package/dist/brain/stream.js +70 -0
- package/dist/brain/transcript.d.ts +45 -0
- package/dist/brain/transcript.js +132 -0
- package/dist/brain/types.d.ts +99 -0
- package/dist/brain/types.js +14 -0
- package/dist/chalk/actions.d.ts +37 -0
- package/dist/chalk/actions.js +69 -0
- package/dist/chalk/bundle.d.ts +26 -0
- package/dist/chalk/bundle.js +69 -0
- package/dist/chalk/check.d.ts +28 -0
- package/dist/chalk/check.js +291 -0
- package/dist/chalk/circle.d.ts +64 -0
- package/dist/chalk/circle.js +193 -0
- package/dist/chalk/clock.d.ts +84 -0
- package/dist/chalk/clock.js +84 -0
- package/dist/chalk/geometry.d.ts +44 -0
- package/dist/chalk/geometry.js +139 -0
- package/dist/chalk/glyphs/capitals.d.ts +12 -0
- package/dist/chalk/glyphs/capitals.js +264 -0
- package/dist/chalk/glyphs/index.d.ts +21 -0
- package/dist/chalk/glyphs/index.js +18 -0
- package/dist/chalk/glyphs/letters.d.ts +13 -0
- package/dist/chalk/glyphs/letters.js +280 -0
- package/dist/chalk/glyphs/metrics.d.ts +33 -0
- package/dist/chalk/glyphs/metrics.js +28 -0
- package/dist/chalk/glyphs/registry.d.ts +7 -0
- package/dist/chalk/glyphs/registry.js +8 -0
- package/dist/chalk/glyphs/sample.d.ts +32 -0
- package/dist/chalk/glyphs/sample.js +102 -0
- package/dist/chalk/glyphs/skeleton.d.ts +64 -0
- package/dist/chalk/glyphs/skeleton.js +126 -0
- package/dist/chalk/glyphs/types.d.ts +58 -0
- package/dist/chalk/glyphs/types.js +17 -0
- package/dist/chalk/index.d.ts +23 -0
- package/dist/chalk/index.js +15 -0
- package/dist/chalk/manifest.d.ts +41 -0
- package/dist/chalk/manifest.js +6 -0
- package/dist/chalk/paint.d.ts +180 -0
- package/dist/chalk/paint.js +210 -0
- package/dist/chalk/pointer.d.ts +8 -0
- package/dist/chalk/pointer.js +21 -0
- package/dist/chalk/prepare.d.ts +11 -0
- package/dist/chalk/prepare.js +15 -0
- package/dist/chalk/scene-schema.d.ts +199 -0
- package/dist/chalk/scene-schema.js +91 -0
- package/dist/chalk/seed.d.ts +3 -0
- package/dist/chalk/seed.js +10 -0
- package/dist/chalk/text-metrics.d.ts +8 -0
- package/dist/chalk/text-metrics.js +156 -0
- package/dist/chalk/timeline.d.ts +47 -0
- package/dist/chalk/timeline.js +84 -0
- package/dist/chalk/types.d.ts +150 -0
- package/dist/chalk/types.js +8 -0
- package/dist/lib/index.d.ts +14 -12
- package/dist/lib/index.js +14 -12
- package/dist/media/draw.d.ts +23 -0
- package/dist/media/draw.js +44 -0
- package/dist/media/mp3.d.ts +6 -0
- package/dist/media/mp3.js +58 -0
- package/dist/media/mp4.d.ts +1 -0
- package/dist/media/mp4.js +58 -0
- package/dist/media/tts-voices.d.ts +18 -0
- package/dist/media/tts-voices.js +606 -0
- package/dist/media/tts.d.ts +56 -0
- package/dist/media/tts.js +174 -0
- package/dist/media/vision.d.ts +25 -0
- package/dist/media/vision.js +71 -0
- package/dist/memory/commit.d.ts +67 -0
- package/dist/memory/commit.js +130 -0
- package/dist/memory/context.d.ts +86 -0
- package/dist/memory/context.js +97 -0
- package/dist/memory/diary.d.ts +84 -0
- package/dist/memory/diary.js +183 -0
- package/dist/memory/grade.d.ts +2 -0
- package/dist/memory/grade.js +7 -0
- package/dist/{lib → memory}/plan.d.ts +1 -1
- package/dist/{lib → memory}/plan.js +1 -1
- package/dist/memory/proposals.d.ts +11 -0
- package/dist/memory/proposals.js +43 -0
- package/dist/memory/retrieval.d.ts +44 -0
- package/dist/memory/retrieval.js +157 -0
- package/dist/{lib → memory}/timetable.d.ts +0 -3
- package/dist/{lib → memory}/timetable.js +0 -11
- package/dist/memory/vault-notes.d.ts +165 -0
- package/dist/memory/vault-notes.js +359 -0
- package/dist/memory/vault.d.ts +70 -0
- package/dist/memory/vault.js +169 -0
- package/dist/realtime/bailian.d.ts +120 -0
- package/dist/realtime/bailian.js +289 -0
- package/dist/realtime/cues.d.ts +51 -0
- package/dist/realtime/cues.js +94 -0
- package/dist/realtime/listen.d.ts +32 -0
- package/dist/realtime/listen.js +53 -0
- package/dist/realtime/preview.d.ts +23 -0
- package/dist/realtime/preview.js +38 -0
- package/dist/realtime/prompt.d.ts +8 -0
- package/dist/realtime/prompt.js +13 -0
- package/dist/realtime/voices.d.ts +21 -0
- package/dist/realtime/voices.js +71 -0
- package/dist/runtime/activities.d.ts +29 -0
- package/dist/runtime/activities.js +72 -0
- package/dist/runtime/activity-state.d.ts +22 -0
- package/dist/runtime/activity-state.js +83 -0
- package/dist/runtime/activity.d.ts +63 -0
- package/dist/runtime/activity.js +428 -0
- package/dist/runtime/agent-file.d.ts +24 -0
- package/dist/runtime/agent-file.js +59 -0
- package/dist/runtime/auto-bookkeep.d.ts +30 -0
- package/dist/runtime/auto-bookkeep.js +84 -0
- package/dist/{lib → runtime/context}/context-pack.d.ts +3 -3
- package/dist/runtime/context/context-pack.js +182 -0
- package/dist/runtime/context/thread-digest.d.ts +4 -0
- package/dist/runtime/context/thread-digest.js +35 -0
- package/dist/runtime/context/tutor-rules.d.ts +25 -0
- package/dist/runtime/context/tutor-rules.js +47 -0
- package/dist/runtime/delivery/delivery.d.ts +94 -0
- package/dist/runtime/delivery/delivery.js +145 -0
- package/dist/runtime/delivery/playback.d.ts +25 -0
- package/dist/runtime/delivery/playback.js +29 -0
- package/dist/runtime/delivery/player.d.ts +145 -0
- package/dist/runtime/delivery/player.js +221 -0
- package/dist/runtime/delivery/queue.d.ts +89 -0
- package/dist/runtime/delivery/queue.js +59 -0
- package/dist/runtime/home/edit.d.ts +39 -0
- package/dist/runtime/home/edit.js +132 -0
- package/dist/runtime/home/home.d.ts +191 -0
- package/dist/runtime/home/home.js +318 -0
- package/dist/runtime/home/store.d.ts +142 -0
- package/dist/runtime/home/store.js +455 -0
- package/dist/runtime/journal/conversation.d.ts +46 -0
- package/dist/runtime/journal/conversation.js +89 -0
- package/dist/runtime/journal/events.d.ts +27 -0
- package/dist/runtime/journal/events.js +136 -0
- package/dist/runtime/journal/kid-log.d.ts +105 -0
- package/dist/runtime/journal/kid-log.js +133 -0
- package/dist/runtime/journal/ledger.d.ts +17 -0
- package/dist/{lib → runtime/journal}/ledger.js +7 -40
- package/dist/runtime/journal/mix.d.ts +42 -0
- package/dist/runtime/journal/mix.js +129 -0
- package/dist/runtime/journal/mouth-index.d.ts +26 -0
- package/dist/runtime/journal/mouth-index.js +34 -0
- package/dist/runtime/journal/mouth.d.ts +54 -0
- package/dist/runtime/journal/mouth.js +102 -0
- package/dist/runtime/journal/recorder.d.ts +91 -0
- package/dist/runtime/journal/recorder.js +148 -0
- package/dist/runtime/journal/store.d.ts +123 -0
- package/dist/runtime/journal/store.js +316 -0
- package/dist/runtime/kid-input.d.ts +16 -0
- package/dist/runtime/kid-input.js +194 -0
- package/dist/runtime/kid-view.d.ts +124 -0
- package/dist/runtime/kid-view.js +157 -0
- package/dist/runtime/lecture.d.ts +86 -0
- package/dist/runtime/lecture.js +237 -0
- package/dist/runtime/live/call.d.ts +231 -0
- package/dist/runtime/live/call.js +1244 -0
- package/dist/runtime/live/turns.d.ts +48 -0
- package/dist/runtime/live/turns.js +102 -0
- package/dist/runtime/runner.d.ts +243 -0
- package/dist/runtime/runner.js +777 -0
- package/dist/runtime/talk-run.d.ts +21 -0
- package/dist/runtime/talk-run.js +34 -0
- package/dist/scenes/dictation/match.d.ts +57 -0
- package/dist/scenes/dictation/match.js +255 -0
- package/dist/scenes/dictation/recognize.d.ts +86 -0
- package/dist/scenes/dictation/recognize.js +163 -0
- package/dist/scenes/dictation/store.d.ts +112 -0
- package/dist/scenes/dictation/store.js +99 -0
- package/dist/scenes/look.d.ts +26 -0
- package/dist/scenes/look.js +1 -0
- package/dist/scenes/paint/painter.d.ts +84 -0
- package/dist/scenes/paint/painter.js +171 -0
- package/dist/scenes/paint/prompt.d.ts +23 -0
- package/dist/scenes/paint/prompt.js +36 -0
- package/dist/scenes/talk/en-words.d.ts +8 -0
- package/dist/scenes/talk/en-words.js +44 -0
- package/dist/scenes/talk/list.d.ts +44 -0
- package/dist/scenes/talk/list.js +88 -0
- package/dist/scenes/talk/store.d.ts +27 -0
- package/dist/scenes/talk/store.js +61 -0
- package/dist/scenes/talk/warmup.d.ts +16 -0
- package/dist/scenes/talk/warmup.js +39 -0
- package/dist/schema/activity.d.ts +149 -0
- package/dist/schema/activity.js +113 -0
- package/dist/schema/board.d.ts +143 -2
- package/dist/schema/board.js +15 -3
- package/dist/schema/config.d.ts +339 -103
- package/dist/schema/config.js +269 -84
- package/dist/schema/context-pack.d.ts +76 -14
- package/dist/schema/context-pack.js +91 -10
- package/dist/{lib/conversation.d.ts → schema/conversation-files.d.ts} +11 -33
- package/dist/schema/conversation-files.js +26 -0
- package/dist/schema/conversation.d.ts +391 -49
- package/dist/schema/conversation.js +135 -19
- package/dist/schema/events.d.ts +138 -0
- package/dist/schema/events.js +1 -0
- package/dist/schema/home.d.ts +56 -0
- package/dist/schema/home.js +35 -0
- package/dist/schema/index.d.ts +14 -3
- package/dist/schema/index.js +14 -3
- package/dist/schema/kid-device.d.ts +20 -0
- package/dist/schema/kid-device.js +32 -0
- package/dist/schema/ledger.d.ts +8 -63
- package/dist/schema/ledger.js +7 -31
- package/dist/schema/memory.d.ts +73 -0
- package/dist/schema/memory.js +47 -0
- package/dist/schema/plan.d.ts +2 -2
- package/dist/schema/plan.js +2 -2
- package/dist/schema/sections.d.ts +32 -25
- package/dist/schema/sections.js +21 -22
- package/dist/schema/theme.d.ts +25 -0
- package/dist/schema/theme.js +26 -0
- package/dist/schema/timetable.d.ts +2 -2
- package/dist/schema/timetable.js +2 -2
- package/dist/schema/workspace.d.ts +68 -0
- package/dist/schema/workspace.js +37 -0
- package/dist/shared/default-port.d.ts +2 -0
- package/dist/shared/default-port.js +2 -0
- package/dist/shared/errors.d.ts +8 -0
- package/dist/shared/errors.js +13 -0
- package/dist/shared/home.d.ts +2 -0
- package/dist/shared/home.js +16 -0
- package/dist/shared/human-notes.d.ts +5 -0
- package/dist/shared/human-notes.js +7 -0
- package/dist/shared/qianwen.d.ts +54 -0
- package/dist/shared/qianwen.js +113 -0
- package/dist/shared/site-url.d.ts +2 -0
- package/dist/shared/site-url.js +5 -0
- package/dist/shared/time.d.ts +9 -0
- package/dist/shared/time.js +22 -0
- package/dist/shared/tokens.d.ts +18 -0
- package/dist/shared/tokens.js +11 -0
- package/dist/shared/wav.d.ts +10 -0
- package/dist/shared/wav.js +50 -0
- package/dist/stage/chunk-226TNLQA.js +129 -0
- package/dist/stage/chunk-24LGQEKE.js +1 -0
- package/dist/stage/chunk-25XV6UY3.js +10 -0
- package/dist/stage/chunk-2AZI3NHJ.js +1 -0
- package/dist/stage/chunk-2BQZTTBE.js +1 -0
- package/dist/stage/chunk-2M7CGY3Y.js +11 -0
- package/dist/stage/chunk-2VR4WFQ7.js +10 -0
- package/dist/stage/chunk-2ZYXBUWG.js +9 -0
- package/dist/stage/chunk-32LAWMGY.js +1 -0
- package/dist/stage/chunk-3LEMTSOO.js +11 -0
- package/dist/stage/chunk-3QCKGZYE.js +32 -0
- package/dist/stage/chunk-44P66WFA.js +1 -0
- package/dist/stage/chunk-4AM6FRR6.js +7 -0
- package/dist/stage/chunk-4QDE4SUK.js +1 -0
- package/dist/stage/chunk-4QESHV3U.js +1 -0
- package/dist/stage/chunk-4SG5QPHB.js +2 -0
- package/dist/stage/chunk-4T4UVCOC.js +1 -0
- package/dist/stage/chunk-4WUEPPG3.js +34 -0
- package/dist/stage/chunk-4YCN6UZ5.js +99 -0
- package/dist/stage/chunk-5V7ZY2AP.js +1 -0
- package/dist/stage/chunk-5VS5Y2VJ.js +6 -0
- package/dist/stage/chunk-5W4NO2ZD.js +62 -0
- package/dist/stage/chunk-65CRNICM.js +1 -0
- package/dist/stage/chunk-66MULO7F.js +5 -0
- package/dist/stage/chunk-6COFQTBS.js +162 -0
- package/dist/stage/chunk-6VZG3M3I.js +8 -0
- package/dist/stage/chunk-7HVDH43T.js +1 -0
- package/dist/stage/chunk-7JLJN4EZ.js +8 -0
- package/dist/stage/chunk-7QDSP4LN.js +1 -0
- package/dist/stage/chunk-7R6OQAO3.js +1 -0
- package/dist/stage/chunk-7VTTHZKW.js +7 -0
- package/dist/stage/chunk-ATHR5KEN.js +10 -0
- package/dist/stage/chunk-B6R5LTP2.js +36 -0
- package/dist/stage/chunk-BD4WE264.js +1 -0
- package/dist/stage/chunk-BONANU47.js +206 -0
- package/dist/stage/chunk-BTVW7OFK.js +89 -0
- package/dist/stage/chunk-BUTJIDHG.js +8 -0
- package/dist/stage/chunk-C2IPYMML.js +84 -0
- package/dist/stage/chunk-C54FTPPV.js +9 -0
- package/dist/stage/chunk-C6LOUF2Z.js +90 -0
- package/dist/stage/chunk-CFF3PILO.js +339 -0
- package/dist/stage/chunk-CI2FQ2JY.js +9 -0
- package/dist/stage/chunk-CW5O4RV7.js +12 -0
- package/dist/stage/chunk-CYABNQYD.js +1 -0
- package/dist/stage/chunk-D7FFA4NS.js +15 -0
- package/dist/stage/chunk-DUNNXU6Z.js +8 -0
- package/dist/stage/chunk-E3E4Q6JF.js +6 -0
- package/dist/stage/chunk-EB3U3LH7.js +9 -0
- package/dist/stage/chunk-EGSZ736B.js +8 -0
- package/dist/stage/chunk-EOBXW5VQ.js +2 -0
- package/dist/stage/chunk-ERZC3B7Y.js +11 -0
- package/dist/stage/chunk-ESDOOBIH.js +11 -0
- package/dist/stage/chunk-EVU4QAL4.js +178 -0
- package/dist/stage/chunk-EVVZI2NY.js +39 -0
- package/dist/stage/chunk-FBKAZUKD.js +39 -0
- package/dist/stage/chunk-FDVKYQJN.js +1 -0
- package/dist/stage/chunk-FGPVYHVR.js +5 -0
- package/dist/stage/chunk-FHUD4AZC.js +1 -0
- package/dist/stage/chunk-FKUJ2DMY.js +96 -0
- package/dist/stage/chunk-FNZGVIJV.js +1 -0
- package/dist/stage/chunk-GBYSEGYM.js +78 -0
- package/dist/stage/chunk-GCO4TDHX.js +1 -0
- package/dist/stage/chunk-GKTML2LG.js +10 -0
- package/dist/stage/chunk-GMDIHHXQ.js +11 -0
- package/dist/stage/chunk-GOUNACI3.js +10 -0
- package/dist/stage/chunk-GXAYATPF.js +3 -0
- package/dist/stage/chunk-H2K2DDXV.js +8 -0
- package/dist/stage/chunk-H3UJI6ZY.js +13 -0
- package/dist/stage/chunk-H6PUGCME.js +7 -0
- package/dist/stage/chunk-HNBUIMIG.js +1 -0
- package/dist/stage/chunk-HRZY2BJQ.js +10 -0
- package/dist/stage/chunk-HSPJGALW.js +30 -0
- package/dist/stage/chunk-IBYORHIP.js +25 -0
- package/dist/stage/chunk-IHF4EWTY.js +1 -0
- package/dist/stage/chunk-ISBRPNKI.js +1 -0
- package/dist/stage/chunk-IZ3OI3C2.js +2 -0
- package/dist/stage/chunk-JEFZAC2R.js +1 -0
- package/dist/stage/chunk-JFIGRDPO.js +1 -0
- package/dist/stage/chunk-JFVYBOOC.js +9 -0
- package/dist/stage/chunk-JGD4TUTQ.js +1 -0
- package/dist/stage/chunk-JO4BEHDH.js +4 -0
- package/dist/stage/chunk-JOWBS42C.js +1 -0
- package/dist/stage/chunk-JP3TM2TG.js +1 -0
- package/dist/stage/chunk-JVK2C52O.js +1 -0
- package/dist/stage/chunk-K4I76XBX.js +14 -0
- package/dist/stage/chunk-KA6CZZ4M.js +1 -0
- package/dist/stage/chunk-KESSJ36M.js +1 -0
- package/dist/stage/chunk-LKATEVRU.js +1 -0
- package/dist/stage/chunk-LS4EJ2EI.js +1 -0
- package/dist/stage/chunk-LUKJQC2X.js +70 -0
- package/dist/stage/chunk-LWULR3SM.js +1 -0
- package/dist/stage/chunk-LWVRFMKX.js +2 -0
- package/dist/stage/chunk-LXC4ZBPZ.js +1 -0
- package/dist/stage/chunk-M2UP255H.js +1 -0
- package/dist/stage/chunk-MAPPXUZQ.js +231 -0
- package/dist/stage/chunk-MBMPW7Z5.js +1 -0
- package/dist/stage/chunk-MD3HMMTK.js +1 -0
- package/dist/stage/chunk-MLPCUIKR.js +17 -0
- package/dist/stage/chunk-MNQIRPDQ.js +8 -0
- package/dist/stage/chunk-MRG7MJVS.js +1 -0
- package/dist/stage/chunk-MU6EXW72.js +10 -0
- package/dist/stage/chunk-MVU2X6LU.js +4 -0
- package/dist/stage/chunk-N6UA5CZV.js +36 -0
- package/dist/stage/chunk-NLZIT2TJ.js +7 -0
- package/dist/stage/chunk-NOJR3J7M.js +1 -0
- package/dist/stage/chunk-NT7ARDOL.js +1 -0
- package/dist/stage/chunk-NWOGMPZH.js +1 -0
- package/dist/stage/chunk-NXE4KMG3.js +40 -0
- package/dist/stage/chunk-O4YCZXD2.js +257 -0
- package/dist/stage/chunk-OAJZ7HLK.js +1 -0
- package/dist/stage/chunk-OCFCFASO.js +1 -0
- package/dist/stage/chunk-ODGPG5RY.js +7 -0
- package/dist/stage/chunk-ODUEA52Q.js +3 -0
- package/dist/stage/chunk-OGDJ3FCI.js +1 -0
- package/dist/stage/chunk-OLDJ55TT.js +1 -0
- package/dist/stage/chunk-OOCL4ATI.js +2 -0
- package/dist/stage/chunk-OQEFDUDV.js +1 -0
- package/dist/stage/chunk-OQWVEWG7.js +4 -0
- package/dist/stage/chunk-OUIX6BGK.js +1 -0
- package/dist/stage/chunk-OV7OZYUO.js +7 -0
- package/dist/stage/chunk-PFGDY33O.js +1 -0
- package/dist/stage/chunk-PFJDUJB3.js +139 -0
- package/dist/stage/chunk-PGEW76ER.js +1 -0
- package/dist/stage/chunk-PP76TAQX.js +32 -0
- package/dist/stage/chunk-PTBOP4OT.js +2 -0
- package/dist/stage/chunk-PX2RVHCK.js +1 -0
- package/dist/stage/chunk-Q7K3PNAD.js +1 -0
- package/dist/stage/chunk-QDDPLZDB.js +1 -0
- package/dist/stage/chunk-QLGLZ77F.js +1 -0
- package/dist/stage/chunk-QOPFDFHI.js +1 -0
- package/dist/stage/chunk-QXT4KAEJ.js +1 -0
- package/dist/stage/chunk-R4YVZCIT.js +1 -0
- package/dist/stage/chunk-R6P6EHWA.js +62 -0
- package/dist/stage/chunk-RCH3OXBI.js +9 -0
- package/dist/stage/chunk-RHPE44I2.js +1 -0
- package/dist/stage/chunk-RJ2R4FVC.js +1 -0
- package/dist/stage/chunk-RJH2YVTD.js +1 -0
- package/dist/stage/chunk-RLWWW5OB.js +6 -0
- package/dist/stage/chunk-S3WO35Z7.js +120 -0
- package/dist/stage/chunk-S7ZGX6MW.js +1 -0
- package/dist/stage/chunk-SIAWX27B.js +1 -0
- package/dist/stage/chunk-SLG4X622.js +1 -0
- package/dist/stage/chunk-SLJRBC77.js +168 -0
- package/dist/stage/chunk-SRORWQJZ.js +1 -0
- package/dist/stage/chunk-STZTUIIF.js +2 -0
- package/dist/stage/chunk-SVQ6MWNV.js +9 -0
- package/dist/stage/chunk-TL3HLL2S.js +132 -0
- package/dist/stage/chunk-U3DOE6SF.js +24 -0
- package/dist/stage/chunk-UABQIZKW.js +6 -0
- package/dist/stage/chunk-UDK3R764.js +1 -0
- package/dist/stage/chunk-UFF4E54B.js +41 -0
- package/dist/stage/chunk-UFP5Q4AY.js +1 -0
- package/dist/stage/chunk-UMH2TSW2.js +72 -0
- package/dist/stage/chunk-UTASVNF5.js +10 -0
- package/dist/stage/chunk-UW7ML5ML.js +88 -0
- package/dist/stage/chunk-V3J7F6OP.js +1 -0
- package/dist/stage/chunk-VEHP5B5Q.js +5 -0
- package/dist/stage/chunk-VRXWB733.js +9 -0
- package/dist/stage/chunk-VVDHBBS5.js +5 -0
- package/dist/stage/chunk-VYFOH6FU.js +106 -0
- package/dist/stage/chunk-W3VZTLQS.js +3 -0
- package/dist/stage/chunk-W7WNRWTZ.js +8 -0
- package/dist/stage/chunk-WHUCV53C.js +1 -0
- package/dist/stage/chunk-WULMYHIF.js +1 -0
- package/dist/stage/chunk-X54EX7AW.js +9 -0
- package/dist/stage/chunk-X565ISZM.js +1 -0
- package/dist/stage/chunk-XGZMDECB.js +1 -0
- package/dist/stage/chunk-XHONC4RF.js +172 -0
- package/dist/stage/chunk-XIK4XQ6E.js +24 -0
- package/dist/stage/chunk-XJQQWFTE.js +1 -0
- package/dist/stage/chunk-XM4WOU3I.js +13 -0
- package/dist/stage/chunk-XM7Y5HP5.js +38 -0
- package/dist/stage/chunk-XNYYRGD5.js +292 -0
- package/dist/stage/chunk-XOYVR3SV.js +1 -0
- package/dist/stage/chunk-XQIW5HCE.js +1 -0
- package/dist/stage/chunk-YEWL7FUW.js +8 -0
- package/dist/stage/chunk-YI55PY3V.js +1 -0
- package/dist/stage/chunk-YM63HI6W.js +1 -0
- package/dist/stage/chunk-YNLGLU4P.js +9 -0
- package/dist/stage/chunk-YTNMNOWP.js +1 -0
- package/dist/stage/chunk-YV62XKTC.js +1 -0
- package/dist/stage/chunk-YVBA3QW6.js +1 -0
- package/dist/stage/chunk-Z2SY2HOC.js +1 -0
- package/dist/stage/chunk-ZBTCQEBK.js +10 -0
- package/dist/stage/chunk-ZCUFN74D.js +1 -0
- package/dist/stage/chunk-ZHOULXEJ.js +11 -0
- package/dist/stage/chunk-ZTRZIET2.js +1 -0
- package/dist/stage/index.html +1 -0
- package/dist/stage/stage.css +1 -1
- package/dist/stage/stage.js +1 -4383
- package/dist/ui/dev/dev-page.d.ts +1 -0
- package/dist/ui/dev/dev-page.js +2021 -0
- package/dist/ui/dev/dev-theme.d.ts +6 -0
- package/dist/ui/dev/dev-theme.js +67 -0
- package/dist/ui/dev/voice-gallery-page.d.ts +1 -0
- package/dist/ui/dev/voice-gallery-page.js +121 -0
- package/dist/ui/dev/voice-scenes.d.ts +21 -0
- package/dist/ui/dev/voice-scenes.js +30 -0
- package/dist/ui/dictation/en-box.d.ts +2 -0
- package/dist/ui/dictation/en-box.js +86 -0
- package/dist/ui/dictation/en-page.d.ts +1 -0
- package/dist/ui/dictation/en-page.js +484 -0
- package/dist/ui/dictation/page.d.ts +1 -0
- package/dist/ui/dictation/page.js +1084 -0
- package/dist/ui/dictation/parent-page.d.ts +1 -0
- package/dist/ui/dictation/parent-page.js +234 -0
- package/dist/ui/dictation/reel-mode.d.ts +1 -0
- package/dist/ui/dictation/reel-mode.js +87 -0
- package/dist/ui/dictation/reel-page.d.ts +1 -0
- package/dist/ui/dictation/reel-page.js +62 -0
- package/dist/ui/dictation/reel.d.ts +163 -0
- package/dist/ui/dictation/reel.js +436 -0
- package/dist/ui/dictation/ui.d.ts +44 -0
- package/dist/ui/dictation/ui.js +80 -0
- package/dist/ui/dictation/unlock.d.ts +7 -0
- package/dist/ui/dictation/unlock.js +16 -0
- package/dist/ui/kid/audio-client.d.ts +11 -0
- package/dist/ui/kid/audio-client.js +74 -0
- package/dist/ui/kid/board-mark-art.d.ts +37 -0
- package/dist/ui/kid/board-mark-art.js +5 -0
- package/dist/ui/kid/board-mark.d.ts +24 -0
- package/dist/ui/kid/board-mark.js +58 -0
- package/dist/ui/kid/home-tiles.d.ts +14 -0
- package/dist/ui/kid/home-tiles.js +44 -0
- package/dist/ui/kid/kid-log.d.ts +15 -0
- package/dist/ui/kid/kid-log.js +87 -0
- package/dist/ui/kid/kid-nav.d.ts +11 -0
- package/dist/ui/kid/kid-nav.js +22 -0
- package/dist/ui/kid/kid-page.d.ts +9 -0
- package/dist/ui/kid/kid-page.js +3684 -0
- package/dist/ui/kid/letters.d.ts +26 -0
- package/dist/ui/kid/letters.js +51 -0
- package/dist/ui/kid/photo-edit.d.ts +83 -0
- package/dist/ui/kid/photo-edit.js +106 -0
- package/dist/ui/kid/photo-ui.d.ts +23 -0
- package/dist/ui/kid/photo-ui.js +60 -0
- package/dist/ui/kid/reel.d.ts +419 -0
- package/dist/ui/kid/reel.js +648 -0
- package/dist/ui/kid/tianzige.d.ts +8 -0
- package/dist/ui/kid/tianzige.js +45 -0
- package/dist/ui/kid/tutor-face.d.ts +35 -0
- package/dist/ui/kid/tutor-face.js +84 -0
- package/dist/ui/kid/voice-ui.d.ts +70 -0
- package/dist/ui/kid/voice-ui.js +316 -0
- package/dist/ui/kid/voice-view.d.ts +71 -0
- package/dist/ui/kid/voice-view.js +75 -0
- package/dist/ui/kid/word-draw.d.ts +2 -0
- package/dist/ui/kid/word-draw.js +107 -0
- package/dist/ui/kid/word-emoji.d.ts +10 -0
- package/dist/ui/kid/word-emoji.js +68 -0
- package/dist/ui/kid/word-layout.d.ts +25 -0
- package/dist/ui/kid/word-layout.js +29 -0
- package/dist/ui/parent/parent-home-page.d.ts +1 -0
- package/dist/ui/parent/parent-home-page.js +722 -0
- package/dist/ui/parent/parent-page.d.ts +2 -0
- package/dist/ui/parent/parent-page.js +511 -0
- package/dist/ui/parent/parent-settings-page.d.ts +1 -0
- package/dist/ui/parent/parent-settings-page.js +133 -0
- package/dist/ui/parent/qr-page.d.ts +8 -0
- package/dist/ui/parent/qr-page.js +66 -0
- package/dist/ui/shared/brand.d.ts +38 -0
- package/dist/ui/shared/brand.js +53 -0
- package/dist/ui/shared/favicon.d.ts +12 -0
- package/dist/ui/shared/favicon.js +96 -0
- package/dist/ui/shared/icon.d.ts +9 -0
- package/dist/ui/shared/icon.js +35 -0
- package/dist/ui/shared/inline-lib.d.ts +1 -0
- package/dist/ui/shared/inline-lib.js +28 -0
- package/dist/ui/shared/logo.d.ts +31 -0
- package/dist/ui/shared/logo.js +43 -0
- package/dist/ui/shared/page-dom.d.ts +12 -0
- package/dist/ui/shared/page-dom.js +36 -0
- package/dist/ui/shared/png.d.ts +1 -0
- package/dist/ui/shared/png.js +45 -0
- package/dist/ui/shared/qr-svg.d.ts +3 -0
- package/dist/ui/shared/qr-svg.js +32 -0
- package/dist/ui/shared/qr.d.ts +14 -0
- package/dist/ui/shared/qr.js +281 -0
- package/dist/ui/shared/reel-dock.d.ts +22 -0
- package/dist/ui/shared/reel-dock.js +365 -0
- package/dist/ui/shared/tutor-color.d.ts +15 -0
- package/dist/ui/shared/tutor-color.js +47 -0
- package/dist/ui/talk/page.d.ts +6 -0
- package/dist/ui/talk/page.js +294 -0
- package/grades/grade-2/README.md +11 -0
- package/grades/grade-2/VALIDATION.md +41 -0
- package/grades/grade-2/agents/chinese-tutor/activities//344/275/240/350/257/264/346/210/221/347/224/273.md +157 -0
- 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
- 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
- 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
- package/grades/grade-2/agents/chinese-tutor/activities//345/206/231/350/257/235.md +64 -0
- package/grades/grade-2/agents/chinese-tutor/activities//345/206/231/350/257/235./344/276/213/345/255/220.md +31 -0
- package/grades/grade-2/agents/chinese-tutor/activities//345/220/254/345/206/231.md +48 -0
- package/grades/grade-2/agents/chinese-tutor/activities//345/244/215/344/271/240.md +79 -0
- package/grades/grade-2/agents/chinese-tutor/activities//345/244/215/344/271/240./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/chinese-tutor/activities//345/260/217/350/257/276/345/240/202.md +53 -0
- package/grades/grade-2/agents/chinese-tutor/activities//346/243/200/346/237/245/344/275/234/344/270/232.md +81 -0
- 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
- package/grades/grade-2/agents/chinese-tutor/activities//347/234/213/345/233/276/350/257/264/350/257/235.md +94 -0
- 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
- package/grades/grade-2/agents/chinese-tutor/activities//351/242/204/344/271/240.md +82 -0
- package/grades/grade-2/agents/chinese-tutor/activities//351/242/204/344/271/240./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/chinese-tutor.md +59 -0
- package/grades/grade-2/agents/english-tutor/activities//345/217/243/350/257/255/350/257/276.md +149 -0
- 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
- package/grades/grade-2/agents/english-tutor/activities//345/220/254/345/206/231.md +47 -0
- package/grades/grade-2/agents/english-tutor/activities//345/244/215/344/271/240.md +78 -0
- package/grades/grade-2/agents/english-tutor/activities//345/244/215/344/271/240./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/english-tutor/activities//345/260/217/350/257/276/345/240/202.md +53 -0
- package/grades/grade-2/agents/english-tutor/activities//346/243/200/346/237/245/344/275/234/344/270/232.md +80 -0
- 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
- package/grades/grade-2/agents/english-tutor/activities//351/242/204/344/271/240.md +86 -0
- package/grades/grade-2/agents/english-tutor/activities//351/242/204/344/271/240./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/english-tutor.md +57 -0
- package/grades/grade-2/agents/math-tutor/activities//345/244/215/344/271/240.md +78 -0
- package/grades/grade-2/agents/math-tutor/activities//345/244/215/344/271/240./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/math-tutor/activities//345/260/217/350/257/276/345/240/202.md +56 -0
- 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
- package/grades/grade-2/agents/math-tutor/activities//346/243/200/346/237/245/344/275/234/344/270/232.md +80 -0
- 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
- package/grades/grade-2/agents/math-tutor/activities//351/242/204/344/271/240.md +81 -0
- package/grades/grade-2/agents/math-tutor/activities//351/242/204/344/271/240./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/math-tutor.md +51 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/index.json +44 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-1.mp3 +0 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-2.mp3 +0 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-3.mp3 +0 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-4.mp3 +0 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-5.mp3 +0 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/bake.json +1 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/manifest.json +63 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/scene.json +247 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen.md +40 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen.ts +26 -0
- package/grades/grade-2/examples//345/271/263/345/235/207/345/210/206./344/276/213/345/255/220.md +24 -0
- package/grades/grade-2/examples//346/225/260/345/255/246/345/271/263/345/235/207/345/210/206.md +42 -0
- package/grades/grade-2/validation/2026-10-27.json +4410 -0
- package/package.json +27 -15
- package/skills/chalk-cli/SKILL.md +62 -0
- package/skills/chalk-scene/SKILL.md +91 -0
- package/skills/chalk-teaching/SKILL.md +82 -0
- 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
- package/skills/chalk-teaching/models//345/207/221/345/215/201.md +7 -0
- package/skills/chalk-teaching/models//345/221/250/346/234/237/350/247/204/345/276/213.md +6 -0
- package/skills/chalk-teaching/models//345/256/236/347/211/251/345/233/276-/345/242/236/345/207/217.md +6 -0
- 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
- 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
- package/skills/chalk-teaching/models//345/260/217/346/243/222/350/277/233/344/275/215/345/212/240.md +7 -0
- package/skills/chalk-teaching/models//345/260/217/346/243/222/351/200/200/344/275/215/345/207/217.md +6 -0
- package/skills/chalk-teaching/models//346/216/222/351/230/237/347/202/271/351/230/265.md +7 -0
- package/skills/chalk-teaching/models//346/227/266/351/227/264/346/225/260/350/275/264.md +6 -0
- package/skills/chalk-teaching/models//346/245/274/345/261/202/351/227/264/351/232/224/345/233/276.md +7 -0
- package/skills/chalk-teaching/models//347/240/264/345/215/201.md +7 -0
- package/skills/chalk-teaching/models//347/253/226/345/274/217/351/200/200/344/275/215.md +6 -0
- package/skills/chalk-teaching/models//347/272/277/346/256/265/345/233/276.md +7 -0
- package/skills/chalk-teaching/models-index.md +24 -0
- 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
- package/skills/chalk-teaching/references//350/257/264/346/230/216/346/226/207/344/273/266.md +102 -0
- package/skills/chalk-verify/SKILL.md +57 -0
- package/skills/chalk-verify/references//350/257/204/345/210/244/346/240/207/345/207/206.md +69 -0
- package/skills/cotutor-analyze/SKILL.md +106 -0
- package/skills/cotutor-analyze/references//345/221/275/344/273/244/344/270/216/346/226/207/344/273/266.md +31 -0
- package/skills/cotutor-board/SKILL.md +373 -0
- package/skills/cotutor-board/references/README.md +38 -0
- package/skills/cotutor-board/references/canvas.md +68 -0
- package/skills/cotutor-board/references/choice.md +55 -0
- package/skills/cotutor-board/references/code.md +46 -0
- package/skills/cotutor-board/references/image.md +52 -0
- package/skills/cotutor-board/references/lecture.md +69 -0
- package/skills/cotutor-board/references/paint.md +77 -0
- package/skills/cotutor-board/references/photo.md +61 -0
- package/skills/cotutor-board/references/read.md +51 -0
- package/skills/cotutor-board/references/text.md +69 -0
- package/skills/cotutor-board/references/tianzige.md +78 -0
- package/skills/cotutor-board/references/word.md +91 -0
- package/skills/cotutor-home/SKILL.md +170 -0
- package/skills/cotutor-home/references/dictation.md +97 -0
- package/skills/cotutor-home/references/tutor.md +149 -0
- package/skills/cotutor-home/references//345/221/275/344/273/244.md +21 -0
- package/skills/cotutor-home/references//351/246/226/351/241/265/350/257/255/346/263/225.md +143 -0
- package/skills/cotutor-tune/SKILL.md +147 -0
- package/skills/cotutor-tune/references//345/255/227/346/256/265.md +53 -0
- package/skills/cotutor-tutor/SKILL.md +253 -0
- package/skills/cotutor-tutor/references//344/270/212/344/270/213/346/226/207/345/214/205.md +78 -0
- package/themes/default/kid.css +86 -0
- package/themes/default/theme.json +26 -0
- package/agents/chinese-tutor.md +0 -67
- package/agents/homework-tutor.md +0 -17
- package/agents/math-tutor.md +0 -68
- package/agents/planner.md +0 -18
- package/agents/reading-tutor.md +0 -65
- package/agents/scene-maker.md +0 -41
- package/dist/cards/canvas.d.ts +0 -22
- package/dist/cards/canvas.js +0 -65
- package/dist/cards/fill.d.ts +0 -14
- package/dist/cards/fill.js +0 -64
- package/dist/cards/index.d.ts +0 -48
- package/dist/cards/index.js +0 -115
- package/dist/cards/kind.js +0 -7
- package/dist/cards/scene.d.ts +0 -24
- package/dist/cards/scene.js +0 -67
- package/dist/cards/text.d.ts +0 -18
- package/dist/cards/text.js +0 -41
- package/dist/cli/cert.d.ts +0 -7
- package/dist/cli/cert.js +0 -36
- package/dist/cli/doctor.d.ts +0 -22
- package/dist/cli/doctor.js +0 -355
- package/dist/cli/init.js +0 -116
- package/dist/cli/main.js +0 -243
- package/dist/cli/serve.d.ts +0 -30
- package/dist/cli/serve.js +0 -56
- package/dist/cli/skeleton.d.ts +0 -39
- package/dist/cli/skeleton.js +0 -184
- package/dist/cli/skills.d.ts +0 -40
- package/dist/cli/skills.js +0 -170
- package/dist/cli/workspace.d.ts +0 -69
- package/dist/lib/agent-file.d.ts +0 -10
- package/dist/lib/agent-file.js +0 -16
- package/dist/lib/board.d.ts +0 -15
- package/dist/lib/board.js +0 -107
- package/dist/lib/context-pack.js +0 -55
- package/dist/lib/conversation.js +0 -88
- package/dist/lib/kid-board.d.ts +0 -148
- package/dist/lib/kid-board.js +0 -294
- package/dist/lib/kid-view.d.ts +0 -63
- package/dist/lib/kid-view.js +0 -93
- package/dist/lib/ledger.d.ts +0 -29
- package/dist/lib/run-plan.d.ts +0 -30
- package/dist/lib/run-plan.js +0 -32
- package/dist/lib/sections.d.ts +0 -12
- package/dist/lib/sections.js +0 -151
- package/dist/lib/stream.d.ts +0 -15
- package/dist/lib/stream.js +0 -69
- package/dist/lib/transcript.d.ts +0 -36
- package/dist/lib/transcript.js +0 -95
- package/dist/server/app.d.ts +0 -74
- package/dist/server/app.js +0 -406
- package/dist/server/kid-page.d.ts +0 -1
- package/dist/server/kid-page.js +0 -768
- package/dist/server/mock.js +0 -403
- package/dist/server/parent-page.d.ts +0 -8
- package/dist/server/parent-page.js +0 -393
- package/dist/server/runner.d.ts +0 -64
- package/dist/server/runner.js +0 -277
- package/dist/server/scene-props.d.ts +0 -16
- package/dist/server/scene-props.js +0 -39
- package/dist/server/store.d.ts +0 -43
- package/dist/server/store.js +0 -210
- package/dist/server/tts.d.ts +0 -51
- package/dist/server/tts.js +0 -99
- /package/dist/{cli → app/cli}/main.d.ts +0 -0
- /package/dist/{cards → board/cards}/choice.d.ts +0 -0
- /package/dist/{cards → board/cards}/code.d.ts +0 -0
- /package/dist/{cards → board/cards}/image.d.ts +0 -0
- /package/dist/{cards → board/cards}/read.d.ts +0 -0
package/dist/server/kid-page.js
DELETED
|
@@ -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());
|