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
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 课包 → 场景(纯函数,浏览器外可用):scene.json + manifest.json 拼回 ChalkScene,过一遍 zod。
|
|
3
|
+
* 取文件是调用方的事(浏览器 fetch、Node readFile);拼法与校验只有这一份,播放页、CLI、应用都用它。
|
|
4
|
+
* parseBundle 拼 + 校验;assembleBundle 只拼(信得过的课包,省掉 zod)。
|
|
5
|
+
* 课包可能是旧版本或被手改过,错误要能说清楚改哪里。
|
|
6
|
+
*/
|
|
7
|
+
import { sceneSchema } from "./scene-schema.js";
|
|
8
|
+
export class BundleError extends Error {
|
|
9
|
+
name = 'BundleError';
|
|
10
|
+
}
|
|
11
|
+
const isObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
12
|
+
function isBounds(v) {
|
|
13
|
+
return isObject(v) && Object.values(v).every((b) => Array.isArray(b) && b.length === 4 && b.every((n) => typeof n === 'number'));
|
|
14
|
+
}
|
|
15
|
+
/** scene.json 与 manifest.json 拼回场景的样子(还没校验) */
|
|
16
|
+
function joinBundle(sceneJson, manifest) {
|
|
17
|
+
return {
|
|
18
|
+
id: sceneJson.id,
|
|
19
|
+
title: sceneJson.title,
|
|
20
|
+
problem: sceneJson.problem,
|
|
21
|
+
model: sceneJson.model,
|
|
22
|
+
template: sceneJson.template,
|
|
23
|
+
...(sceneJson.subject != null ? { subject: sceneJson.subject } : {}),
|
|
24
|
+
...(sceneJson.background != null ? { background: sceneJson.background } : {}),
|
|
25
|
+
skeletons: sceneJson.skeletons,
|
|
26
|
+
...(sceneJson.groups != null ? { groups: sceneJson.groups } : {}),
|
|
27
|
+
steps: manifest.steps,
|
|
28
|
+
...(manifest.blocks != null ? { blocks: manifest.blocks } : {}),
|
|
29
|
+
...(manifest.subscenes != null ? { subscenes: manifest.subscenes } : {}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function withPicture(scene, sceneJson) {
|
|
33
|
+
const bounds = sceneJson.bounds;
|
|
34
|
+
return {
|
|
35
|
+
scene,
|
|
36
|
+
...(bounds ? { bounds } : {}),
|
|
37
|
+
picture: {
|
|
38
|
+
...(scene.groups ? { groups: scene.groups } : {}),
|
|
39
|
+
...(scene.blocks ? { blocks: scene.blocks } : {}),
|
|
40
|
+
...(bounds ? { bounds } : {}),
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** 两份 JSON(已 parse)→ 场景,过 zod;不合契约抛 BundleError(消息不带课包名,调用方加) */
|
|
45
|
+
export function parseBundle(sceneJson, manifest) {
|
|
46
|
+
if (!isObject(sceneJson))
|
|
47
|
+
throw new BundleError('scene.json 不是对象');
|
|
48
|
+
if (!isObject(manifest))
|
|
49
|
+
throw new BundleError('manifest.json 不是对象');
|
|
50
|
+
const parsed = sceneSchema.safeParse(joinBundle(sceneJson, manifest));
|
|
51
|
+
if (!parsed.success) {
|
|
52
|
+
const first = parsed.error.issues
|
|
53
|
+
.slice(0, 3)
|
|
54
|
+
.map((i) => `${i.path.join('.')}:${i.message}`)
|
|
55
|
+
.join(';');
|
|
56
|
+
throw new BundleError(`不符合场景契约:${first}`);
|
|
57
|
+
}
|
|
58
|
+
if (sceneJson.bounds != null && !isBounds(sceneJson.bounds))
|
|
59
|
+
throw new BundleError('scene.json 的 bounds 不是「元素 id → [minX, minY, maxX, maxY]」');
|
|
60
|
+
return withPicture(parsed.data, sceneJson);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 只拼不校验:信得过的课包(自己的服务端读过、校验过)在浏览器里用它,首屏不为 zod 付钱
|
|
64
|
+
* (只 import 它,打包时 zod 会被摇掉)。缺省值同 schema:steps 缺了是空数组。
|
|
65
|
+
*/
|
|
66
|
+
export function assembleBundle(sceneJson, manifest) {
|
|
67
|
+
const joined = joinBundle(sceneJson, manifest);
|
|
68
|
+
return withPicture({ ...joined, steps: joined.steps ?? [] }, sceneJson);
|
|
69
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 阶梯层 2–3:schema 校验、语义 lint、确定性布局检查。
|
|
3
|
+
* 纯函数,不碰文件系统;错误信息 = 修复指南(README「Design principles」)。
|
|
4
|
+
* 层 1(类型,内嵌 tsc)在 typecheck.ts,模块求值在 load-scene.ts;层 4(keyframe 截图)归 snap。
|
|
5
|
+
*/
|
|
6
|
+
import type { ChalkScene, ChalkSkeleton } from './types.ts';
|
|
7
|
+
export interface Finding {
|
|
8
|
+
level: 'error' | 'warning';
|
|
9
|
+
rule: string;
|
|
10
|
+
message: string;
|
|
11
|
+
}
|
|
12
|
+
export interface CheckResult {
|
|
13
|
+
scene: ChalkScene | null;
|
|
14
|
+
findings: Finding[];
|
|
15
|
+
}
|
|
16
|
+
export declare function checkScene(raw: unknown): CheckResult;
|
|
17
|
+
export interface Box {
|
|
18
|
+
id: string;
|
|
19
|
+
minX: number;
|
|
20
|
+
minY: number;
|
|
21
|
+
maxX: number;
|
|
22
|
+
maxY: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 包围盒:core/geometry 的 elementBounds(文字按真播放器量出来的字宽表;元素带的 width 更宽以它为准)。
|
|
26
|
+
* snap 的元素清单、build 落进 scene.json 的 bounds 都是这一份。
|
|
27
|
+
*/
|
|
28
|
+
export declare function bboxOf(el: ChalkSkeleton): Box;
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 阶梯层 2–3:schema 校验、语义 lint、确定性布局检查。
|
|
3
|
+
* 纯函数,不碰文件系统;错误信息 = 修复指南(README「Design principles」)。
|
|
4
|
+
* 层 1(类型,内嵌 tsc)在 typecheck.ts,模块求值在 load-scene.ts;层 4(keyframe 截图)归 snap。
|
|
5
|
+
*/
|
|
6
|
+
import { KNOWN_ELEMENT_TYPES, sceneSchema } from "./scene-schema.js";
|
|
7
|
+
import { elementBounds } from "./geometry.js";
|
|
8
|
+
import { isAction, resolveActions } from "./actions.js";
|
|
9
|
+
export function checkScene(raw) {
|
|
10
|
+
const findings = [];
|
|
11
|
+
const parsed = sceneSchema.safeParse(raw);
|
|
12
|
+
if (!parsed.success) {
|
|
13
|
+
for (const issue of parsed.error.issues) {
|
|
14
|
+
findings.push({ level: 'error', rule: 'schema', message: `${formatPath(issue.path)}:${issue.message}` });
|
|
15
|
+
}
|
|
16
|
+
return { scene: null, findings };
|
|
17
|
+
}
|
|
18
|
+
const scene = parsed.data;
|
|
19
|
+
lintSemantics(scene, findings);
|
|
20
|
+
lintLayout(scene, findings);
|
|
21
|
+
return { scene, findings };
|
|
22
|
+
}
|
|
23
|
+
function formatPath(path) {
|
|
24
|
+
if (!path.length)
|
|
25
|
+
return '(场景根)';
|
|
26
|
+
return path.reduce((acc, seg) => (typeof seg === 'number' ? `${acc}[${seg}]` : acc ? `${acc}.${String(seg)}` : String(seg)), '');
|
|
27
|
+
}
|
|
28
|
+
// ---- 层 2:语义 lint ----
|
|
29
|
+
function relativeAudioOk(src) {
|
|
30
|
+
return !src.startsWith('/') && !/^[a-z]+:/i.test(src);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 一幅画面(skeletons + steps)的语义 lint,主场景与每个子场景各跑一遍。
|
|
34
|
+
* ctx 是消息前缀(主场景为空,子场景形如「子场景 'blend-ram' 」)。
|
|
35
|
+
* steps 为空 = 静态画面,跳过覆盖检查;「空得合不合法」由 template 一致性规则单独判。
|
|
36
|
+
*/
|
|
37
|
+
function lintDrawing(skeletons, steps, findings, ctx = '') {
|
|
38
|
+
const idSeq = skeletons.map((e) => e.id);
|
|
39
|
+
const seen = new Set();
|
|
40
|
+
for (const id of idSeq) {
|
|
41
|
+
if (seen.has(id)) {
|
|
42
|
+
findings.push({ level: 'error', rule: 'unique-id', message: `${ctx}元素 id '${id}' 重复;id 是步与元素的对齐键,必须唯一` });
|
|
43
|
+
}
|
|
44
|
+
seen.add(id);
|
|
45
|
+
}
|
|
46
|
+
for (const el of skeletons) {
|
|
47
|
+
if (isAction(el)) {
|
|
48
|
+
if (ctx)
|
|
49
|
+
findings.push({ level: 'error', rule: 'subscene-action', message: `${ctx}里有动作 '${el.id}'(${el.type});弹窗子场景不擦、不换画板,去掉它` });
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (typeof el.x !== 'number' || typeof el.y !== 'number') {
|
|
53
|
+
findings.push({ level: 'error', rule: 'xy', message: `${ctx}元素 '${el.id}' 缺 x / y(数字,场景坐标);只有动作(erase、board)不用写位置` });
|
|
54
|
+
}
|
|
55
|
+
if (el.targets !== undefined || el.background !== undefined) {
|
|
56
|
+
findings.push({ level: 'warning', rule: 'action-field', message: `${ctx}元素 '${el.id}'(${el.type})带了 ${el.targets !== undefined ? 'targets' : 'background'},那是动作的字段(erase 的 targets、board 的 background),画的元素用不上` });
|
|
57
|
+
}
|
|
58
|
+
if (/animate(Duration|Order):/.test(el.id)) {
|
|
59
|
+
findings.push({ level: 'error', rule: 'clean-id', message: `${ctx}元素 '${el.id}' 的 id 携带 animate 指令;时长写成 animateDuration 字段,顺序由数组位置决定` });
|
|
60
|
+
}
|
|
61
|
+
if (Array.isArray(el.groupIds) && el.groupIds.length) {
|
|
62
|
+
findings.push({ level: 'error', rule: 'no-group', message: `${ctx}元素 '${el.id}' 带 groupIds,会破坏逐笔串行调度;去掉 groupIds,顺序用数组表达` });
|
|
63
|
+
}
|
|
64
|
+
if (!KNOWN_ELEMENT_TYPES.includes(el.type)) {
|
|
65
|
+
findings.push({ level: 'warning', rule: 'element-type', message: `${ctx}元素 '${el.id}' 的类型 '${el.type}' 未经引擎验证;已验证类型:${KNOWN_ELEMENT_TYPES.join('、')}` });
|
|
66
|
+
}
|
|
67
|
+
if (el.type === 'text') {
|
|
68
|
+
if (typeof el.text !== 'string' || !el.text) {
|
|
69
|
+
findings.push({ level: 'error', rule: 'text-content', message: `${ctx}text 元素 '${el.id}' 缺 text 内容(字符串)` });
|
|
70
|
+
}
|
|
71
|
+
if (typeof el.fontSize !== 'number') {
|
|
72
|
+
findings.push({ level: 'error', rule: 'text-content', message: `${ctx}text 元素 '${el.id}' 缺 fontSize(数字,px)` });
|
|
73
|
+
}
|
|
74
|
+
else if (el.fontSize < 16) {
|
|
75
|
+
findings.push({ level: 'warning', rule: 'text-size', message: `${ctx}text 元素 '${el.id}' 的 fontSize ${el.fontSize} 偏小;iPad 上建议 ≥16` });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
steps.forEach((s, i) => {
|
|
80
|
+
if (s.step !== i + 1) {
|
|
81
|
+
findings.push({ level: 'error', rule: 'step-order', message: `${ctx}第 ${i + 1} 个步的 step 是 ${s.step};step 必须从 1 连续递增` });
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
// 笔顺覆盖:steps 的 elementIds 依次拼接后须与 skeletons 顺序完全一致(静态画面无 steps,跳过)
|
|
85
|
+
if (steps.length) {
|
|
86
|
+
const flat = steps.flatMap((s) => s.elementIds);
|
|
87
|
+
const n = Math.max(flat.length, idSeq.length);
|
|
88
|
+
for (let i = 0; i < n; i++) {
|
|
89
|
+
if (flat[i] !== idSeq[i]) {
|
|
90
|
+
findings.push({
|
|
91
|
+
level: 'error',
|
|
92
|
+
rule: 'stroke-cover',
|
|
93
|
+
message: `${ctx}steps 必须按 skeletons 顺序连续覆盖全部元素(笔顺即步序):` +
|
|
94
|
+
`第 ${i + 1} 笔期望 '${idSeq[i] ?? '(无:skeletons 已用尽,steps 多出元素)'}',steps 给的是 '${flat[i] ?? '(缺:steps 提前结束)'}'`,
|
|
95
|
+
});
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
for (const s of steps) {
|
|
101
|
+
if (s.audioSrc && !relativeAudioOk(s.audioSrc)) {
|
|
102
|
+
findings.push({ level: 'warning', rule: 'audio-path', message: `${ctx}step ${s.step} 的 audioSrc '${s.audioSrc}' 不是相对路径;课包自包含,音频放课包目录内(如 audio/step1.mp3)` });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function lintSemantics(scene, findings) {
|
|
107
|
+
lintDrawing(scene.skeletons, scene.steps, findings);
|
|
108
|
+
// 模板与 steps 一致性:static 必空,讲解三档必非空
|
|
109
|
+
if (scene.template === 'static' && scene.steps.length) {
|
|
110
|
+
findings.push({ level: 'error', rule: 'static-steps', message: `template 'static' 是无时间轴的静态场景,steps 必须为空;要逐步讲解请换讲解模板(paper-strict / camera-free / motion)` });
|
|
111
|
+
}
|
|
112
|
+
if (scene.template !== 'static' && !scene.steps.length) {
|
|
113
|
+
findings.push({ level: 'error', rule: 'steps-required', message: `template '${scene.template}' 是讲解模板,steps 不能为空;静态点读/圈读页请用 template 'static'` });
|
|
114
|
+
}
|
|
115
|
+
if (scene.background && !relativeAudioOk(scene.background.src)) {
|
|
116
|
+
findings.push({ level: 'warning', rule: 'background-path', message: `background.src '${scene.background.src}' 不是相对路径;写成相对场景文件的路径,build 会把图拷进课包` });
|
|
117
|
+
}
|
|
118
|
+
lintActions(scene, findings);
|
|
119
|
+
lintInteractions(scene, findings);
|
|
120
|
+
lintGroups(scene, findings);
|
|
121
|
+
}
|
|
122
|
+
/** 笔顺里的动作(擦掉、换画板):引用对不对、放的地方对不对 */
|
|
123
|
+
function lintActions(scene, findings) {
|
|
124
|
+
const acts = scene.skeletons.filter(isAction);
|
|
125
|
+
if (!acts.length)
|
|
126
|
+
return;
|
|
127
|
+
if (scene.template === 'static') {
|
|
128
|
+
findings.push({ level: 'error', rule: 'static-action', message: `静态场景(template 'static')没有时间轴,不能有动作(${acts.map((a) => a.id).join('、')});要擦、换画板请换讲解模板` });
|
|
129
|
+
}
|
|
130
|
+
for (const p of resolveActions(scene.skeletons, scene.background).problems) {
|
|
131
|
+
findings.push({ level: 'error', rule: 'erase-target', message: p.message });
|
|
132
|
+
}
|
|
133
|
+
if (scene.skeletons[0]?.type === 'board') {
|
|
134
|
+
findings.push({ level: 'error', rule: 'board-first', message: `第一笔 '${scene.skeletons[0].id}' 就换画板;第一块画板的背景图写在场景的 background 上,board 只用来换到下一块` });
|
|
135
|
+
}
|
|
136
|
+
for (const a of acts) {
|
|
137
|
+
if (a.type === 'board' && a.targets !== undefined) {
|
|
138
|
+
findings.push({ level: 'warning', rule: 'action-field', message: `换画板 '${a.id}' 不用写 targets:旧板上的东西都会擦掉` });
|
|
139
|
+
}
|
|
140
|
+
if (a.type === 'erase' && a.background !== undefined) {
|
|
141
|
+
findings.push({ level: 'warning', rule: 'action-field', message: `擦除 '${a.id}' 带了 background;换背景图要用 { type: 'board', background }` });
|
|
142
|
+
}
|
|
143
|
+
const bg = a.background;
|
|
144
|
+
if (a.type === 'board' && bg && typeof bg.src === 'string' && !relativeAudioOk(bg.src)) {
|
|
145
|
+
findings.push({ level: 'warning', rule: 'background-path', message: `换画板 '${a.id}' 的 background.src '${bg.src}' 不是相对路径;写成相对场景文件的路径,build 会把图拷进课包` });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/** groups 的引用完整性:id 不重、成员都在场景里、一组里不重复 */
|
|
150
|
+
function lintGroups(scene, findings) {
|
|
151
|
+
const els = new Set(scene.skeletons.map((e) => e.id));
|
|
152
|
+
const seen = new Set();
|
|
153
|
+
for (const g of scene.groups ?? []) {
|
|
154
|
+
if (seen.has(g.id))
|
|
155
|
+
findings.push({ level: 'error', rule: 'group-id', message: `groups 里 id '${g.id}' 重复;每组一个 id` });
|
|
156
|
+
seen.add(g.id);
|
|
157
|
+
const missing = g.elementIds.filter((id) => !els.has(id));
|
|
158
|
+
if (missing.length)
|
|
159
|
+
findings.push({ level: 'error', rule: 'group-ref', message: `group '${g.id}'(${g.label})引用了不存在的元素 ${missing.join(', ')};elementIds 写 skeletons 里的 id` });
|
|
160
|
+
if (new Set(g.elementIds).size !== g.elementIds.length)
|
|
161
|
+
findings.push({ level: 'warning', rule: 'group-dup', message: `group '${g.id}' 的 elementIds 有重复` });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** blocks / subscenes 的引用完整性与触发语义 */
|
|
165
|
+
function lintInteractions(scene, findings) {
|
|
166
|
+
const blocks = scene.blocks ?? [];
|
|
167
|
+
const subscenes = scene.subscenes ?? [];
|
|
168
|
+
const elById = new Map(scene.skeletons.map((e) => [e.id, e]));
|
|
169
|
+
const subIds = new Set();
|
|
170
|
+
for (const sub of subscenes) {
|
|
171
|
+
if (subIds.has(sub.id)) {
|
|
172
|
+
findings.push({ level: 'error', rule: 'unique-id', message: `子场景 id '${sub.id}' 重复;blocks[].subsceneId 靠它寻址,必须唯一` });
|
|
173
|
+
}
|
|
174
|
+
subIds.add(sub.id);
|
|
175
|
+
lintDrawing(sub.skeletons, sub.steps ?? [], findings, `子场景 '${sub.id}' `);
|
|
176
|
+
if (sub.audioSrc && !relativeAudioOk(sub.audioSrc)) {
|
|
177
|
+
findings.push({ level: 'warning', rule: 'audio-path', message: `子场景 '${sub.id}' 的 audioSrc '${sub.audioSrc}' 不是相对路径;课包自包含,音频放课包目录内` });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const blockIds = new Set();
|
|
181
|
+
const referencedSubs = new Set();
|
|
182
|
+
for (const b of blocks) {
|
|
183
|
+
if (blockIds.has(b.id)) {
|
|
184
|
+
findings.push({ level: 'error', rule: 'unique-id', message: `块 id '${b.id}' 重复;事件记录靠它区分词条,必须唯一` });
|
|
185
|
+
}
|
|
186
|
+
blockIds.add(b.id);
|
|
187
|
+
for (const id of b.elementIds) {
|
|
188
|
+
if (!elById.has(id)) {
|
|
189
|
+
findings.push({ level: 'error', rule: 'block-element', message: `块 '${b.id}' 引用元素 '${id}',skeletons 中不存在;块锚在底层元素上,先修元素 id` });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (b.subsceneId) {
|
|
193
|
+
referencedSubs.add(b.subsceneId);
|
|
194
|
+
if (!subIds.has(b.subsceneId)) {
|
|
195
|
+
findings.push({ level: 'error', rule: 'block-subscene', message: `块 '${b.id}' 引用子场景 '${b.subsceneId}',subscenes 中不存在;现有:${[...subIds].join('、') || '(无)'}` });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (!b.audioSrc && !b.subsceneId) {
|
|
199
|
+
findings.push({ level: 'warning', rule: 'block-trigger', message: `块 '${b.id}' 既无 audioSrc 也无 subsceneId,圈中后无事发生;给一个触发,或删掉这个块` });
|
|
200
|
+
}
|
|
201
|
+
if (b.audioSrc && !relativeAudioOk(b.audioSrc)) {
|
|
202
|
+
findings.push({ level: 'warning', rule: 'audio-path', message: `块 '${b.id}' 的 audioSrc '${b.audioSrc}' 不是相对路径;课包自包含,音频放课包目录内(如 audio/tokens/ram.mp3)` });
|
|
203
|
+
}
|
|
204
|
+
// 词条文本与元素文本一致性(重点字母色拆分后 r + am 拼回 ram)
|
|
205
|
+
const joined = b.elementIds
|
|
206
|
+
.map((id) => elById.get(id))
|
|
207
|
+
.filter((e) => !!e && e.type === 'text' && typeof e.text === 'string')
|
|
208
|
+
.map((e) => e.text)
|
|
209
|
+
.join('');
|
|
210
|
+
if (joined && joined !== b.text) {
|
|
211
|
+
findings.push({ level: 'warning', rule: 'block-text', message: `块 '${b.id}' 的 text 是 '${b.text}',但所引 text 元素拼起来是 '${joined}';两者应一致,否则事件记录与画面脱节` });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
for (const sub of subscenes) {
|
|
215
|
+
if (!referencedSubs.has(sub.id)) {
|
|
216
|
+
findings.push({ level: 'warning', rule: 'subscene-orphan', message: `子场景 '${sub.id}' 没有任何块引用,永远不会被触发;接到某个 blocks[].subsceneId,或删掉它` });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* 包围盒:core/geometry 的 elementBounds(文字按真播放器量出来的字宽表;元素带的 width 更宽以它为准)。
|
|
222
|
+
* snap 的元素清单、build 落进 scene.json 的 bounds 都是这一份。
|
|
223
|
+
*/
|
|
224
|
+
export function bboxOf(el) {
|
|
225
|
+
return { id: el.id, ...elementBounds(el) };
|
|
226
|
+
}
|
|
227
|
+
/** 近似测量留 2px 容差 */
|
|
228
|
+
function intersects(a, b, margin = 2) {
|
|
229
|
+
return a.minX + margin < b.maxX - margin && b.minX + margin < a.maxX - margin && a.minY + margin < b.maxY - margin && b.minY + margin < a.maxY - margin;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* 一幅画面的布局检查;主场景与每个子场景各自独立的坐标系,分开跑,互不误报。
|
|
233
|
+
* together(i, j):两个元素同时在画面上过没有(擦掉一个再写另一个、在两块画板上,叠在一处不算错);缺省 = 总在一起
|
|
234
|
+
*/
|
|
235
|
+
function lintLayoutOf(skeletons, findings, ctx = '', together = () => true) {
|
|
236
|
+
const textBoxes = skeletons.filter((e) => e.type === 'text').map(bboxOf);
|
|
237
|
+
for (let i = 0; i < textBoxes.length; i++) {
|
|
238
|
+
for (let j = i + 1; j < textBoxes.length; j++) {
|
|
239
|
+
if (intersects(textBoxes[i], textBoxes[j]) && together(textBoxes[i].id, textBoxes[j].id)) {
|
|
240
|
+
findings.push({
|
|
241
|
+
level: 'error',
|
|
242
|
+
rule: 'text-overlap',
|
|
243
|
+
message: `${ctx}文本 '${textBoxes[i].id}' 与 '${textBoxes[j].id}' 包围盒重叠;移开其一或缩小 fontSize(近似测量,最终以 snap 截图为准)`,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const all = skeletons.map(bboxOf);
|
|
249
|
+
const minX = Math.min(...all.map((b) => b.minX));
|
|
250
|
+
const maxX = Math.max(...all.map((b) => b.maxX));
|
|
251
|
+
const minY = Math.min(...all.map((b) => b.minY));
|
|
252
|
+
const maxY = Math.max(...all.map((b) => b.maxY));
|
|
253
|
+
const w = maxX - minX;
|
|
254
|
+
const h = maxY - minY;
|
|
255
|
+
if (h > 0 && w / h > 5) {
|
|
256
|
+
findings.push({ level: 'warning', rule: 'aspect', message: `${ctx}画面过宽(${Math.round(w)}×${Math.round(h)});播放器等比缩放后笔画会变小,考虑分行` });
|
|
257
|
+
}
|
|
258
|
+
if (w > 0 && h / w > 3) {
|
|
259
|
+
findings.push({ level: 'warning', rule: 'aspect', message: `${ctx}画面过高(${Math.round(w)}×${Math.round(h)});播放器等比缩放后笔画会变小,考虑并列` });
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function lintLayout(scene, findings) {
|
|
263
|
+
const acts = resolveActions(scene.skeletons, scene.background);
|
|
264
|
+
const drawn = scene.skeletons.filter((e) => !isAction(e));
|
|
265
|
+
// 每块画板各查各的(各自的视口);同一块上,擦掉以后才写的不算和它叠
|
|
266
|
+
const order = new Map(scene.skeletons.map((e, i) => [e.id, i]));
|
|
267
|
+
const life = (id) => [order.get(id) ?? 0, order.get(acts.erasedBy.get(id) ?? '') ?? Infinity];
|
|
268
|
+
const together = (a, b) => {
|
|
269
|
+
const [a0, a1] = life(a);
|
|
270
|
+
const [b0, b1] = life(b);
|
|
271
|
+
return a0 < b1 && b0 < a1;
|
|
272
|
+
};
|
|
273
|
+
acts.boards.forEach((board, k) => {
|
|
274
|
+
const on = drawn.filter((e) => (acts.boardOf.get(e.id) ?? 0) === k);
|
|
275
|
+
if (!on.length)
|
|
276
|
+
return;
|
|
277
|
+
lintLayoutOf(on, findings, acts.boards.length > 1 ? `画板 ${k + 1}:` : '', together);
|
|
278
|
+
// 有背景图时画板 = 图片,出界的笔迹会被裁掉
|
|
279
|
+
const bg = board.background;
|
|
280
|
+
if (bg) {
|
|
281
|
+
for (const b of on.map(bboxOf)) {
|
|
282
|
+
if (b.minX < 0 || b.minY < 0 || b.maxX > bg.width || b.maxY > bg.height) {
|
|
283
|
+
findings.push({ level: 'warning', rule: 'background-bounds', message: `元素 '${b.id}' 超出背景图范围(0,0)–(${bg.width},${bg.height});画板即图片,图外的笔迹会被裁掉` });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
for (const sub of scene.subscenes ?? []) {
|
|
289
|
+
lintLayoutOf(sub.skeletons, findings, `子场景 '${sub.id}' `);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 圈住了什么(纯函数,浏览器外可用):孩子在课的某一刻停下,在画面上圈了一圈(场景坐标的一串点)→ 那一刻画面上已经有的东西里,哪些被圈住了。
|
|
3
|
+
* 认的顺序:分组(groups,圈住大半就说组名)> 命中块(blocks,说块的字)> 带 label 的元素 > 文字(说字)> 其余按第几步、什么、颜色归堆。
|
|
4
|
+
* circleHits 给数据,describeCircle 把它说成一句中文(「散的 3 根小棒;文字『3』」),给老师、家长看。
|
|
5
|
+
*/
|
|
6
|
+
import type { ChalkBlock, ChalkGroup } from './types.ts';
|
|
7
|
+
import { type LessonClock } from './clock.ts';
|
|
8
|
+
import { type BoundsTuple } from './geometry.ts';
|
|
9
|
+
/** 画面的语义:分组、命中块、build 时量好的包围盒(都可缺) */
|
|
10
|
+
export interface LessonPicture {
|
|
11
|
+
groups?: readonly ChalkGroup[];
|
|
12
|
+
blocks?: readonly ChalkBlock[];
|
|
13
|
+
bounds?: Readonly<Record<string, BoundsTuple>>;
|
|
14
|
+
}
|
|
15
|
+
/** 圈住的一样东西 */
|
|
16
|
+
export type CircleHit = {
|
|
17
|
+
kind: 'group';
|
|
18
|
+
id: string;
|
|
19
|
+
label: string;
|
|
20
|
+
ids: string[];
|
|
21
|
+
of: number;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'block';
|
|
24
|
+
id: string;
|
|
25
|
+
text: string;
|
|
26
|
+
ids: string[];
|
|
27
|
+
} | {
|
|
28
|
+
kind: 'label';
|
|
29
|
+
label: string;
|
|
30
|
+
ids: string[];
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'text';
|
|
33
|
+
text: string;
|
|
34
|
+
ids: string[];
|
|
35
|
+
} | {
|
|
36
|
+
kind: 'shapes';
|
|
37
|
+
step: number;
|
|
38
|
+
type: string;
|
|
39
|
+
color: string;
|
|
40
|
+
ids: string[];
|
|
41
|
+
nearText?: string;
|
|
42
|
+
};
|
|
43
|
+
export interface CircleResult {
|
|
44
|
+
/** 那一刻在第几段(0 起)、念哪句 */
|
|
45
|
+
index: number;
|
|
46
|
+
line: string;
|
|
47
|
+
/** tiny = 圈得太小看不出;hit = 圈住了东西;near = 没圈住,旁边有东西;empty = 那一带还没画东西 */
|
|
48
|
+
kind: 'tiny' | 'hit' | 'near' | 'empty';
|
|
49
|
+
hits: CircleHit[];
|
|
50
|
+
/** 没圈住时离圈最近的那样东西 */
|
|
51
|
+
near?: CircleHit;
|
|
52
|
+
/** 圈住的元素 id(全部,不论归到哪一样) */
|
|
53
|
+
ids: string[];
|
|
54
|
+
}
|
|
55
|
+
/** 笔画颜色 → 一个字(黑、灰、太浅的不说) */
|
|
56
|
+
export declare function colorWord(hex: unknown): string;
|
|
57
|
+
export declare function circleHits(clock: LessonClock, ms: number, path: readonly [number, number][], picture?: LessonPicture): CircleResult;
|
|
58
|
+
/** 一样东西说成几个字 */
|
|
59
|
+
export declare function hitPhrase(h: CircleHit): string;
|
|
60
|
+
/**
|
|
61
|
+
* 圈住了什么,说成一句:「散的 3 根小棒;文字『3』」/「没圈住东西,圈在文字『3』旁边」/「圈的地方那时还没画东西」/「圈得太小,看不出圈的是什么」。
|
|
62
|
+
* 不带时刻与讲稿(应用自己拼:「0:26 圈的,那时在讲『…』;」)。
|
|
63
|
+
*/
|
|
64
|
+
export declare function describeCircle(r: CircleResult): string;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 圈住了什么(纯函数,浏览器外可用):孩子在课的某一刻停下,在画面上圈了一圈(场景坐标的一串点)→ 那一刻画面上已经有的东西里,哪些被圈住了。
|
|
3
|
+
* 认的顺序:分组(groups,圈住大半就说组名)> 命中块(blocks,说块的字)> 带 label 的元素 > 文字(说字)> 其余按第几步、什么、颜色归堆。
|
|
4
|
+
* circleHits 给数据,describeCircle 把它说成一句中文(「散的 3 根小棒;文字『3』」),给老师、家长看。
|
|
5
|
+
*/
|
|
6
|
+
import { clockAt } from "./clock.js";
|
|
7
|
+
import { isAction } from "./actions.js";
|
|
8
|
+
import { boundsGap, boundsOfPoints, elementBounds, insidePolygon, pointsOf, samplePoints, unionBounds } from "./geometry.js";
|
|
9
|
+
/** 样点有多少在圈里算圈住了 */
|
|
10
|
+
const HIT_SHARE = 0.5;
|
|
11
|
+
/** 一组里圈住多少算圈住了这一组 */
|
|
12
|
+
const GROUP_SHARE = 0.5;
|
|
13
|
+
/** 没圈住时,离圈多近的东西算「旁边」;没字的东西「旁边写着」的字要多近(场景坐标) */
|
|
14
|
+
const NEAR = 80;
|
|
15
|
+
const flat = (s) => s.replace(/\s*\n\s*/g, ' ').trim();
|
|
16
|
+
/** 笔画颜色 → 一个字(黑、灰、太浅的不说) */
|
|
17
|
+
export function colorWord(hex) {
|
|
18
|
+
const m = typeof hex === 'string' ? /^#?([0-9a-f]{6})$/i.exec(hex.trim()) : null;
|
|
19
|
+
if (!m)
|
|
20
|
+
return '';
|
|
21
|
+
const [r, g, b] = [0, 2, 4].map((i) => parseInt(m[1].slice(i, i + 2), 16) / 255);
|
|
22
|
+
const max = Math.max(r, g, b);
|
|
23
|
+
const min = Math.min(r, g, b);
|
|
24
|
+
const l = (max + min) / 2;
|
|
25
|
+
const d = max - min;
|
|
26
|
+
const s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
|
|
27
|
+
if (s < 0.3 || l < 0.15 || l > 0.92)
|
|
28
|
+
return '';
|
|
29
|
+
const h = (max === r ? ((g - b) / d + 6) % 6 : max === g ? (b - r) / d + 2 : (r - g) / d + 4) * 60;
|
|
30
|
+
return h < 15 || h >= 340 ? '红' : h < 40 ? '橙' : h < 70 ? '黄' : h < 170 ? '绿' : h < 255 ? '蓝' : '紫';
|
|
31
|
+
}
|
|
32
|
+
/** 点到线段的距离 */
|
|
33
|
+
function segmentGap([px, py], [ax, ay], [bx, by]) {
|
|
34
|
+
const dx = bx - ax;
|
|
35
|
+
const dy = by - ay;
|
|
36
|
+
const len = dx * dx + dy * dy;
|
|
37
|
+
const t = len ? Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / len)) : 0;
|
|
38
|
+
return Math.hypot(px - (ax + t * dx), py - (ay + t * dy));
|
|
39
|
+
}
|
|
40
|
+
/** 橡皮那一笔擦完了这个元素:元素画在橡皮之前,样点大半落在橡皮的笔迹里(离橡皮的线不到半个笔宽) */
|
|
41
|
+
function erasedBy(eraser, e, start, clock, boundsOf) {
|
|
42
|
+
const ew = clock.elements.get(eraser.id);
|
|
43
|
+
const line = pointsOf(eraser);
|
|
44
|
+
if (!ew || start >= ew.start || !line || !line.length)
|
|
45
|
+
return false;
|
|
46
|
+
const half = (typeof eraser.strokeWidth === 'number' ? eraser.strokeWidth : 1) / 2 + 1;
|
|
47
|
+
const s = samplePoints(e, boundsOf(e));
|
|
48
|
+
const covered = (p) => (line.length === 1 ? Math.hypot(p[0] - line[0][0], p[1] - line[0][1]) <= half : line.slice(1).some((b, i) => segmentGap(p, line[i], b) <= half));
|
|
49
|
+
return s.length > 0 && s.filter(covered).length / s.length >= HIT_SHARE;
|
|
50
|
+
}
|
|
51
|
+
export function circleHits(clock, ms, path, picture = {}) {
|
|
52
|
+
const at = clockAt(clock, ms);
|
|
53
|
+
const base = { index: at.index, line: clock.segments[at.index]?.line ?? '' };
|
|
54
|
+
if (path.length < 3)
|
|
55
|
+
return { ...base, kind: 'tiny', hits: [], ids: [] };
|
|
56
|
+
const boundsOf = (e) => {
|
|
57
|
+
const t = picture.bounds?.[e.id];
|
|
58
|
+
return t ? { minX: t[0], minY: t[1], maxX: t[2], maxY: t[3] } : elementBounds(e);
|
|
59
|
+
};
|
|
60
|
+
// 那一刻画面上已经有的(开始画了就算;动作与橡皮笔画不算东西;擦掉了的不算:擦除动作擦完的、换画板换走的,橡皮笔画盖住大半的)
|
|
61
|
+
const erasers = clock.skeletons.filter((e) => e.customData?.tool === 'eraser' && (clock.elements.get(e.id)?.end ?? Infinity) <= at.svgMs);
|
|
62
|
+
const shown = clock.skeletons.filter((e) => {
|
|
63
|
+
const w = clock.elements.get(e.id);
|
|
64
|
+
if (w === undefined || w.start >= at.svgMs || isAction(e) || e.customData?.tool === 'eraser')
|
|
65
|
+
return false;
|
|
66
|
+
if (w.erased && w.erased.end <= at.svgMs)
|
|
67
|
+
return false;
|
|
68
|
+
return !erasers.some((x) => erasedBy(x, e, w.start, clock, boundsOf));
|
|
69
|
+
});
|
|
70
|
+
const shownIds = new Set(shown.map((e) => e.id));
|
|
71
|
+
const ring = boundsOfPoints(path);
|
|
72
|
+
const hit = shown.filter((e) => {
|
|
73
|
+
const b = boundsOf(e);
|
|
74
|
+
const s = samplePoints(e, b);
|
|
75
|
+
if (s.length && s.filter((p) => insidePolygon(path, p)).length / s.length >= HIT_SHARE)
|
|
76
|
+
return true;
|
|
77
|
+
// 长的一行字里圈了几个字:圈整个落在这行字里
|
|
78
|
+
return e.type === 'text' && ring.minX >= b.minX - 8 && ring.maxX <= b.maxX + 8 && ring.minY >= b.minY - 8 && ring.maxY <= b.maxY + 8;
|
|
79
|
+
});
|
|
80
|
+
const stepOf = (e) => clock.elements.get(e.id)?.step ?? -1;
|
|
81
|
+
const describeOne = (e) => {
|
|
82
|
+
const g = (picture.groups ?? []).find((x) => x.elementIds.includes(e.id));
|
|
83
|
+
if (typeof e.label === 'string' && e.label)
|
|
84
|
+
return { kind: 'label', label: e.label, ids: [e.id] };
|
|
85
|
+
if (g)
|
|
86
|
+
return { kind: 'group', id: g.id, label: g.label, ids: [e.id], of: g.elementIds.length };
|
|
87
|
+
if (e.type === 'text')
|
|
88
|
+
return { kind: 'text', text: flat(String(e.text ?? '')), ids: [e.id] };
|
|
89
|
+
return { kind: 'shapes', step: stepOf(e), type: String(e.type), color: colorWord(e.strokeColor), ids: [e.id] };
|
|
90
|
+
};
|
|
91
|
+
if (!hit.length) {
|
|
92
|
+
const center = { minX: (ring.minX + ring.maxX) / 2, maxX: (ring.minX + ring.maxX) / 2, minY: (ring.minY + ring.maxY) / 2, maxY: (ring.minY + ring.maxY) / 2 };
|
|
93
|
+
const near = shown
|
|
94
|
+
.map((e) => ({ e, d: boundsGap(boundsOf(e), ring), c: boundsGap(boundsOf(e), center) }))
|
|
95
|
+
.filter((x) => x.d <= NEAR)
|
|
96
|
+
.sort((a, b) => a.d - b.d || a.c - b.c)[0];
|
|
97
|
+
return near ? { ...base, kind: 'near', hits: [], near: describeOne(near.e), ids: [] } : { ...base, kind: 'empty', hits: [], ids: [] };
|
|
98
|
+
}
|
|
99
|
+
const hits = [];
|
|
100
|
+
const taken = new Set();
|
|
101
|
+
// 1. 分组:这一组画面上已经有的成员,圈住了大半 → 说组名;圈得更满的组先认,一样满先认小的(「13 根小棒」不吞掉圈满了的「散的 3 根」)
|
|
102
|
+
const hitIds = new Set(hit.map((e) => e.id));
|
|
103
|
+
const groups = (picture.groups ?? [])
|
|
104
|
+
.map((g) => {
|
|
105
|
+
const members = g.elementIds.filter((id) => shownIds.has(id));
|
|
106
|
+
const got = members.filter((id) => hitIds.has(id));
|
|
107
|
+
return { g, members, got, share: members.length ? got.length / members.length : 0 };
|
|
108
|
+
})
|
|
109
|
+
.filter((x) => x.got.length && x.share >= GROUP_SHARE)
|
|
110
|
+
.sort((a, b) => b.share - a.share || a.members.length - b.members.length);
|
|
111
|
+
for (const x of groups) {
|
|
112
|
+
const fresh = x.got.filter((id) => !taken.has(id));
|
|
113
|
+
if (!fresh.length)
|
|
114
|
+
continue;
|
|
115
|
+
fresh.forEach((id) => taken.add(id));
|
|
116
|
+
hits.push({ kind: 'group', id: x.g.id, label: x.g.label, ids: x.got, of: x.members.length });
|
|
117
|
+
}
|
|
118
|
+
// 2. 命中块:圈住了块里的字就说整个块
|
|
119
|
+
for (const b of picture.blocks ?? []) {
|
|
120
|
+
const got = b.elementIds.filter((id) => hitIds.has(id) && !taken.has(id));
|
|
121
|
+
if (!got.length)
|
|
122
|
+
continue;
|
|
123
|
+
got.forEach((id) => taken.add(id));
|
|
124
|
+
hits.push({ kind: 'block', id: b.id, text: b.text, ids: got });
|
|
125
|
+
}
|
|
126
|
+
const rest = hit.filter((e) => !taken.has(e.id));
|
|
127
|
+
// 3. 带 label 的元素(同一个 label 归一样)、文字
|
|
128
|
+
const labels = new Map();
|
|
129
|
+
for (const e of rest)
|
|
130
|
+
if (typeof e.label === 'string' && e.label)
|
|
131
|
+
labels.set(e.label, [...(labels.get(e.label) ?? []), e.id]);
|
|
132
|
+
for (const [label, ids] of labels) {
|
|
133
|
+
ids.forEach((id) => taken.add(id));
|
|
134
|
+
hits.push({ kind: 'label', label, ids });
|
|
135
|
+
}
|
|
136
|
+
for (const e of rest)
|
|
137
|
+
if (!taken.has(e.id) && e.type === 'text') {
|
|
138
|
+
taken.add(e.id);
|
|
139
|
+
hits.push({ kind: 'text', text: flat(String(e.text ?? '')), ids: [e.id] });
|
|
140
|
+
}
|
|
141
|
+
// 4. 没字的:按(第几步、什么、颜色)归堆,附上离这一堆最近、没被圈住的字
|
|
142
|
+
const piles = new Map();
|
|
143
|
+
for (const e of rest) {
|
|
144
|
+
if (taken.has(e.id))
|
|
145
|
+
continue;
|
|
146
|
+
const k = `${stepOf(e)}|${String(e.type)}|${colorWord(e.strokeColor)}`;
|
|
147
|
+
piles.set(k, [...(piles.get(k) ?? []), e]);
|
|
148
|
+
}
|
|
149
|
+
const texts = shown.filter((e) => e.type === 'text' && !hitIds.has(e.id));
|
|
150
|
+
for (const [k, els] of piles) {
|
|
151
|
+
const [step, type, color] = k.split('|');
|
|
152
|
+
const gb = unionBounds(els.map(boundsOf));
|
|
153
|
+
const label = texts.map((t) => ({ t, d: boundsGap(boundsOf(t), gb) })).filter((x) => x.d <= NEAR / 2).sort((a, b) => a.d - b.d)[0];
|
|
154
|
+
hits.push({ kind: 'shapes', step: Number(step), type, color, ids: els.map((e) => e.id), ...(label ? { nearText: flat(String(label.t.text ?? '')) } : {}) });
|
|
155
|
+
}
|
|
156
|
+
return { ...base, kind: 'hit', hits, ids: hit.map((e) => e.id) };
|
|
157
|
+
}
|
|
158
|
+
const SHAPE_WORD = { line: ['条', '线'], arrow: ['个', '箭头'], rectangle: ['个', '框'], ellipse: ['个', '圈'], diamond: ['个', '菱形'], freedraw: ['处', '笔画'], image: ['张', '图'] };
|
|
159
|
+
/** 一样东西说成几个字 */
|
|
160
|
+
export function hitPhrase(h) {
|
|
161
|
+
switch (h.kind) {
|
|
162
|
+
case 'group':
|
|
163
|
+
return h.ids.length < h.of ? `${h.label}(圈住了其中 ${h.ids.length} 个)` : h.label;
|
|
164
|
+
case 'block':
|
|
165
|
+
return `『${h.text}』`;
|
|
166
|
+
case 'label':
|
|
167
|
+
return h.label;
|
|
168
|
+
case 'text':
|
|
169
|
+
return `文字『${h.text}』`;
|
|
170
|
+
case 'shapes': {
|
|
171
|
+
const [m, noun] = SHAPE_WORD[h.type] ?? ['样', '东西'];
|
|
172
|
+
const n = h.ids.length;
|
|
173
|
+
const step = h.step >= 0 ? `第 ${h.step + 1} 步画的` : '';
|
|
174
|
+
return `${step}${n > 1 ? ` ${n} ${m}` : ''}${h.color ? `${h.color}色的` : ''}${noun}${h.nearText ? `(旁边写着『${h.nearText}』)` : ''}`.trim();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* 圈住了什么,说成一句:「散的 3 根小棒;文字『3』」/「没圈住东西,圈在文字『3』旁边」/「圈的地方那时还没画东西」/「圈得太小,看不出圈的是什么」。
|
|
180
|
+
* 不带时刻与讲稿(应用自己拼:「0:26 圈的,那时在讲『…』;」)。
|
|
181
|
+
*/
|
|
182
|
+
export function describeCircle(r) {
|
|
183
|
+
switch (r.kind) {
|
|
184
|
+
case 'tiny':
|
|
185
|
+
return '圈得太小,看不出圈的是什么';
|
|
186
|
+
case 'empty':
|
|
187
|
+
return '圈的地方那时还没画东西';
|
|
188
|
+
case 'near':
|
|
189
|
+
return `没圈住东西,圈在${hitPhrase(r.near)}旁边`;
|
|
190
|
+
case 'hit':
|
|
191
|
+
return `圈住了:${r.hits.map(hitPhrase).join(';')}`;
|
|
192
|
+
}
|
|
193
|
+
}
|