@oh-story/dsh 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (524) hide show
  1. package/README.md +58 -12
  2. package/lib/client.js +2970 -537
  3. package/lib/client.js.map +4 -4
  4. package/lib/drama/manifest.json +218 -178
  5. package/lib/drama/skills/short-drama/SKILL.md +110 -117
  6. package/lib/drama/skills/short-drama/references/audience-reveal.md +14 -67
  7. package/lib/drama/skills/short-drama/references/contract-and-ownership.md +26 -77
  8. package/lib/drama/skills/short-drama/references/creator-documents.md +204 -0
  9. package/lib/drama/skills/short-drama/references/creator-workflow.md +73 -63
  10. package/lib/drama/skills/short-drama/references/knowhow-index.md +5 -0
  11. package/lib/drama/skills/short-drama/references/look-development.md +2 -3
  12. package/lib/drama/skills/short-drama/references/pickup-and-alternate.md +14 -48
  13. package/lib/drama/skills/short-drama/references/production-form-profiles.md +1 -1
  14. package/lib/drama/skills/short-drama/references/reference-roles.md +32 -135
  15. package/lib/drama/skills/short-drama/references/runtime-preflight.md +16 -19
  16. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +1002 -0
  17. package/lib/drama/skills/short-drama/scripts/project_tool.py +485 -5
  18. package/lib/drama/skills/short-drama-assets/SKILL.md +59 -170
  19. package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +18 -25
  20. package/lib/drama/skills/short-drama-assets/references/character-and-look.md +13 -15
  21. package/lib/drama/skills/short-drama-assets/references/continuity-delta.md +11 -14
  22. package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +116 -0
  23. package/lib/drama/skills/short-drama-assets/references/identity-vs-variant.md +6 -12
  24. package/lib/drama/skills/short-drama-assets/references/occurrence-extraction.md +8 -11
  25. package/lib/drama/skills/short-drama-assets/references/prop-and-state.md +21 -2
  26. package/lib/drama/skills/short-drama-assets/references/stage-contract.md +12 -105
  27. package/lib/drama/skills/short-drama-assets/references/voice-direction.md +12 -32
  28. package/lib/drama/skills/short-drama-assets/scripts/asset_check.py +1 -1
  29. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +45 -155
  30. package/lib/drama/skills/short-drama-image-prompts/references/character-and-look.md +12 -13
  31. package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +12 -13
  32. package/lib/drama/skills/short-drama-image-prompts/references/edit-and-revision.md +39 -51
  33. package/lib/drama/skills/short-drama-image-prompts/references/location-plate.md +3 -4
  34. package/lib/drama/skills/short-drama-image-prompts/references/look-and-state-variant.md +15 -14
  35. package/lib/drama/skills/short-drama-image-prompts/references/lookdev-frame.md +11 -15
  36. package/lib/drama/skills/short-drama-image-prompts/references/production-sheet-recipes.md +1 -1
  37. package/lib/drama/skills/short-drama-image-prompts/references/prop-plate.md +4 -7
  38. package/lib/drama/skills/short-drama-image-prompts/references/review-and-fixtures.md +23 -23
  39. package/lib/drama/skills/short-drama-image-prompts/references/stage-contract.md +8 -65
  40. package/lib/drama/skills/short-drama-image-prompts/scripts/image_prompt_check.py +8 -8
  41. package/lib/drama/skills/short-drama-image-prompts/scripts/selftest.py +4 -2
  42. package/lib/drama/skills/short-drama-produce/SKILL.md +67 -15
  43. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +65 -8
  44. package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +85 -0
  45. package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +27 -10
  46. package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +362 -3
  47. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +627 -40
  48. package/lib/drama/skills/short-drama-produce/scripts/selftest.py +5 -4
  49. package/lib/drama/skills/short-drama-review/SKILL.md +61 -123
  50. package/lib/drama/skills/short-drama-review/references/anti-template-repair.md +10 -15
  51. package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +15 -16
  52. package/lib/drama/skills/short-drama-review/references/project-calibration.md +7 -9
  53. package/lib/drama/skills/short-drama-review/references/review-method.md +8 -9
  54. package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +9 -13
  55. package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +104 -4
  56. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +27 -19
  57. package/lib/drama/skills/short-drama-review/references/stage-contract.md +4 -63
  58. package/lib/drama/skills/short-drama-review/scripts/review_check.py +1 -1
  59. package/lib/drama/skills/short-drama-storyboard/SKILL.md +116 -238
  60. package/lib/drama/skills/short-drama-storyboard/assets/shot-template.jsonl +1 -1
  61. package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +9 -34
  62. package/lib/drama/skills/short-drama-storyboard/references/comic-keyframe-lexicon.md +156 -0
  63. package/lib/drama/skills/short-drama-storyboard/references/coverage-audition.md +5 -10
  64. package/lib/drama/skills/short-drama-storyboard/references/keyframe-craft.md +12 -7
  65. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +19 -4
  66. package/lib/drama/skills/short-drama-storyboard/references/review-and-fixtures.md +27 -62
  67. package/lib/drama/skills/short-drama-storyboard/references/scene-visual-plan.md +16 -25
  68. package/lib/drama/skills/short-drama-storyboard/references/screenplay-to-keyframe-example.md +21 -23
  69. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +8 -10
  70. package/lib/drama/skills/short-drama-storyboard/references/shot-revision-identity.md +9 -13
  71. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +16 -71
  72. package/lib/drama/skills/short-drama-storyboard/scripts/storyboard_check.py +1 -1
  73. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +123 -230
  74. package/lib/drama/skills/short-drama-video-prompts/assets/motion-spec.jsonl.md +6 -1
  75. package/lib/drama/skills/short-drama-video-prompts/assets/video-prompts.md +17 -1
  76. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +10 -8
  77. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +24 -6
  78. package/lib/drama/skills/short-drama-video-prompts/references/generability.md +2 -2
  79. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +154 -0
  80. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +35 -27
  81. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +8 -11
  82. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +13 -10
  83. package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +34 -63
  84. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +41 -0
  85. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +66 -0
  86. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +17 -73
  87. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +115 -0
  88. package/lib/drama/skills/short-drama-video-prompts/scripts/container_check.py +1 -1
  89. package/lib/drama/skills/short-drama-video-prompts/scripts/music_spec_check.py +1 -1
  90. package/lib/drama/skills/short-drama-write/SKILL.md +74 -268
  91. package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +7 -0
  92. package/lib/drama/skills/short-drama-write/references/scene-handoff-capsule.md +17 -43
  93. package/lib/drama/skills/short-drama-write/references/screenplay-format.md +15 -15
  94. package/lib/drama/skills/short-drama-write/references/script-craft.md +208 -21
  95. package/lib/drama/skills/short-drama-write/references/stage-contract.md +11 -70
  96. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +1 -1
  97. package/lib/drama/skills/short-drama-write/scripts/screenplay_index.py +2 -2
  98. package/lib/drama/skills/short-drama-write/scripts/voice_sheet_check.py +1 -1
  99. package/lib/index.js +971 -46
  100. package/lib/index.js.map +4 -4
  101. package/lib/novel-to-game/LICENSE +21 -0
  102. package/lib/novel-to-game/README.md +180 -0
  103. package/lib/novel-to-game/README_ZH.md +178 -0
  104. package/lib/novel-to-game/VERSION +1 -0
  105. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +217 -0
  106. package/lib/novel-to-game/examples/jin-ping-mei/build/app/.vercelignore +3 -0
  107. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +130 -0
  108. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/compound_new_guofeng.webp +0 -0
  109. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/fate_coda.webp +0 -0
  110. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/meng.webp +0 -0
  111. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pan.webp +0 -0
  112. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pinger.webp +0 -0
  113. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/xuee.webp +0 -0
  114. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/yue.webp +0 -0
  115. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
  116. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_afterglow_five.webp +0 -0
  117. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_alliance.webp +0 -0
  118. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day10.webp +0 -0
  119. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
  120. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day5.webp +0 -0
  121. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/title_new_guofeng.webp +0 -0
  122. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_pinger.webp +0 -0
  123. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_xuee.webp +0 -0
  124. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_meng.webp +0 -0
  125. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pan.webp +0 -0
  126. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pinger.webp +0 -0
  127. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/explicit.webp +0 -0
  128. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/prelude.webp +0 -0
  129. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/meng.webp +0 -0
  130. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pan.webp +0 -0
  131. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pinger.webp +0 -0
  132. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/xuee.webp +0 -0
  133. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/yue.webp +0 -0
  134. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/explicit.webp +0 -0
  135. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/prelude.webp +0 -0
  136. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/explicit.webp +0 -0
  137. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/prelude.webp +0 -0
  138. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/explicit.webp +0 -0
  139. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/prelude.webp +0 -0
  140. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/explicit.webp +0 -0
  141. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/prelude.webp +0 -0
  142. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/meng/night.webp +0 -0
  143. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pan/night.webp +0 -0
  144. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pinger/night.webp +0 -0
  145. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/xuee/night.webp +0 -0
  146. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/yue/night.webp +0 -0
  147. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
  148. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/scene/gate_collector.webp +0 -0
  149. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +1105 -0
  150. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +2539 -0
  151. package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +18 -0
  152. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +147 -0
  153. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +332 -0
  154. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +7472 -0
  155. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +13077 -0
  156. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +3028 -0
  157. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +32 -0
  158. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +10 -0
  159. package/lib/novel-to-game/examples/jin-ping-mei/build/app/vercel.json +23 -0
  160. package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +161 -0
  161. package/lib/novel-to-game/examples/jin-ping-mei/example.json +12 -0
  162. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +31 -0
  163. package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +99 -0
  164. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +41 -0
  165. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +129 -0
  166. package/lib/novel-to-game/manifest.json +508 -0
  167. package/lib/novel-to-game/skills/game-art-direction/SKILL.md +73 -0
  168. package/lib/novel-to-game/skills/game-art-direction/agents/openai.yaml +4 -0
  169. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +119 -0
  170. package/lib/novel-to-game/skills/game-build/SKILL.md +84 -0
  171. package/lib/novel-to-game/skills/game-build/agents/openai.yaml +4 -0
  172. package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +109 -0
  173. package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +56 -0
  174. package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +81 -0
  175. package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +37 -0
  176. package/lib/novel-to-game/skills/game-concept/SKILL.md +72 -0
  177. package/lib/novel-to-game/skills/game-concept/agents/openai.yaml +4 -0
  178. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +94 -0
  179. package/lib/novel-to-game/skills/game-qa/SKILL.md +50 -0
  180. package/lib/novel-to-game/skills/game-qa/agents/openai.yaml +4 -0
  181. package/lib/novel-to-game/skills/game-qa/references/qa-contract.md +66 -0
  182. package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +45 -0
  183. package/lib/novel-to-game/skills/game-world-design/SKILL.md +63 -0
  184. package/lib/novel-to-game/skills/game-world-design/agents/openai.yaml +4 -0
  185. package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +59 -0
  186. package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +53 -0
  187. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +84 -0
  188. package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +56 -0
  189. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +124 -0
  190. package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +77 -0
  191. package/lib/novel-to-game/skills/novel-game-analyze/agents/openai.yaml +4 -0
  192. package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +65 -0
  193. package/lib/novel-to-game/skills/novel-to-game/SKILL.md +71 -0
  194. package/lib/novel-to-game/skills/novel-to-game/agents/openai.yaml +4 -0
  195. package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +42 -0
  196. package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +51 -0
  197. package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +65 -0
  198. package/lib/oh-story/manifest.json +419 -309
  199. package/lib/oh-story/roles/character-designer.md +4 -4
  200. package/lib/oh-story/roles/consistency-checker.md +1 -1
  201. package/lib/oh-story/roles/narrative-writer.md +41 -42
  202. package/lib/oh-story/roles/story-architect.md +19 -23
  203. package/lib/oh-story/roles/story-explorer.md +8 -5
  204. package/lib/oh-story/skills/browser-cdp/SKILL.md +1 -1
  205. package/lib/oh-story/skills/story/SKILL.md +16 -9
  206. package/lib/oh-story/skills/story/VERSION +1 -1
  207. package/lib/oh-story/skills/story/references/author-memory.md +163 -0
  208. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +871 -0
  209. package/lib/oh-story/skills/story-cover/SKILL.md +1 -1
  210. package/lib/oh-story/skills/story-deslop/SKILL.md +10 -5
  211. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +2 -1
  212. package/lib/oh-story/skills/story-deslop/references/author-memory.md +163 -0
  213. package/lib/oh-story/skills/story-deslop/references/banned-words.md +6 -4
  214. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +871 -0
  215. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +64 -1
  216. package/lib/oh-story/skills/story-import/SKILL.md +21 -8
  217. package/lib/oh-story/skills/story-import/references/format-and-structure.md +9 -10
  218. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +2 -2
  219. package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +1 -1
  220. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +14 -6
  221. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +251 -0
  222. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +98 -9
  223. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +246 -0
  224. package/lib/oh-story/skills/story-long-analyze/SKILL.md +4 -4
  225. package/lib/oh-story/skills/story-long-scan/SKILL.md +1 -1
  226. package/lib/oh-story/skills/story-long-write/SKILL.md +35 -21
  227. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +2 -1
  228. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +7 -3
  229. package/lib/oh-story/skills/story-long-write/references/author-memory.md +163 -0
  230. package/lib/oh-story/skills/story-long-write/references/banned-words.md +6 -4
  231. package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +6 -6
  232. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
  233. package/lib/oh-story/skills/story-long-write/references/genre-readers.md +1 -1
  234. package/lib/oh-story/skills/{story-setup/references/agent-references/hooks-chapter.md → story-long-write/references/long-chapter-hooks.md} +5 -5
  235. package/lib/oh-story/skills/story-long-write/references/{quality-checklist.md → long-chapter-quality.md} +2 -1
  236. package/lib/oh-story/skills/story-long-write/references/long-format.md +42 -0
  237. package/lib/oh-story/skills/{story-setup/references/agent-references/genre-catalog.md → story-long-write/references/long-genre-catalog.md} +11 -97
  238. package/lib/oh-story/skills/story-long-write/references/{genre-core-mechanics.md → long-genre-mechanics.md} +1 -1
  239. package/lib/oh-story/skills/story-long-write/references/{reversal-toolkit.md → long-reversal.md} +18 -32
  240. package/lib/oh-story/skills/story-long-write/references/{hooks-suspense.md → long-suspense.md} +6 -6
  241. package/lib/oh-story/skills/story-long-write/references/opening-design.md +1 -1
  242. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +1 -1
  243. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  244. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +14 -6
  245. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +32 -27
  246. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +5 -4
  247. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +2 -2
  248. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +17 -11
  249. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +54 -54
  250. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +871 -0
  251. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +64 -1
  252. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +251 -0
  253. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +303 -0
  254. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +98 -9
  255. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +246 -0
  256. package/lib/oh-story/skills/story-review/SKILL.md +26 -17
  257. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +2 -1
  258. package/lib/oh-story/skills/story-review/references/author-memory.md +163 -0
  259. package/lib/oh-story/skills/story-review/references/banned-words.md +6 -4
  260. package/lib/oh-story/skills/story-review/references/{quality-checklist.md → review-quality.md} +22 -21
  261. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +14 -6
  262. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +871 -0
  263. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +64 -1
  264. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +98 -9
  265. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +246 -0
  266. package/lib/oh-story/skills/story-setup/SKILL.md +85 -41
  267. package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +72 -0
  268. package/lib/oh-story/skills/story-setup/references/agent-references/agent-reference-profiles.md +55 -0
  269. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +2 -1
  270. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +6 -4
  271. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +9 -10
  272. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
  273. package/lib/oh-story/skills/story-setup/references/agent-references/genre-readers.md +1 -1
  274. package/lib/oh-story/skills/{story-short-analyze/references/hooks-chapter.md → story-setup/references/agent-references/long-chapter-hooks.md} +5 -5
  275. package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +209 -0
  276. package/lib/oh-story/skills/{story-long-write/references/genre-catalog.md → story-setup/references/agent-references/long-genre-catalog.md} +11 -97
  277. package/lib/oh-story/skills/story-setup/references/agent-references/{genre-core-mechanics.md → long-genre-mechanics.md} +1 -1
  278. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +40 -0
  279. package/lib/oh-story/skills/{story-short-write/references/reversal-toolkit.md → story-setup/references/agent-references/long-reversal.md} +18 -32
  280. package/lib/oh-story/skills/{story-short-write/references/hooks-suspense.md → story-setup/references/agent-references/long-suspense.md} +6 -6
  281. package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +1 -1
  282. package/lib/oh-story/skills/story-setup/references/agent-references/{emotional-methods.md → short-emotional-methods.md} +4 -4
  283. package/lib/oh-story/skills/{story-long-write/references/genre-writing-formulas.md → story-setup/references/agent-references/short-genre-formulas.md} +32 -44
  284. package/lib/oh-story/skills/story-setup/references/agent-references/short-quality.md +39 -0
  285. package/lib/oh-story/skills/story-setup/references/agent-references/{reversal-toolkit.md → short-reversal.md} +29 -23
  286. package/lib/oh-story/skills/story-setup/references/agent-references/{hooks-suspense.md → short-suspense.md} +31 -31
  287. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  288. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +55 -55
  289. package/lib/oh-story/skills/story-short-analyze/SKILL.md +28 -28
  290. package/lib/oh-story/skills/story-short-analyze/references/{character-basics.md → analysis-character-basics.md} +1 -1
  291. package/lib/oh-story/skills/story-short-analyze/references/{character-design-methods.md → analysis-character-design.md} +1 -1
  292. package/lib/oh-story/skills/story-short-analyze/references/{character-relations.md → analysis-character-relations.md} +1 -1
  293. package/lib/oh-story/skills/story-short-analyze/references/{hooks-paragraph.md → analysis-paragraph-hooks.md} +1 -1
  294. package/lib/oh-story/skills/story-short-analyze/references/{genre-readers.md → analysis-reader-profile.md} +2 -2
  295. package/lib/oh-story/skills/story-short-analyze/references/analysis-report-style.md +45 -0
  296. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-genres.md +65 -0
  297. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-hooks.md +77 -0
  298. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-mechanics.md +82 -0
  299. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-patterns.md +50 -0
  300. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-suspense.md +98 -0
  301. package/lib/oh-story/skills/story-short-analyze/references/{genre-writing-techniques.md → analysis-writing-techniques.md} +21 -19
  302. package/lib/oh-story/skills/story-short-analyze/references/deconstruction-examples.md +1 -1
  303. package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +21 -28
  304. package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +1 -1
  305. package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +17 -17
  306. package/lib/oh-story/skills/story-short-analyze/references/{quality-checklist.md → source-story-quality.md} +28 -49
  307. package/lib/oh-story/skills/story-short-analyze/references/zhihu-style.md +2 -2
  308. package/lib/oh-story/skills/story-short-scan/SKILL.md +1 -1
  309. package/lib/oh-story/skills/story-short-write/SKILL.md +78 -55
  310. package/lib/oh-story/skills/story-short-write/references/author-memory.md +163 -0
  311. package/lib/oh-story/skills/story-short-write/references/banned-words.md +6 -4
  312. package/lib/oh-story/skills/story-short-write/references/cross-book-recall.md +3 -3
  313. package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +4 -4
  314. package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +3 -3
  315. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +2 -2
  316. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/244/215/344/273/207/346/211/223/350/204/270.md +3 -3
  317. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +6 -6
  318. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/200/273/350/243/201/350/261/252/351/227/250.md +3 -3
  319. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +4 -4
  320. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +7 -7
  321. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/262/231/351/233/225/350/204/221/346/264/236.md +5 -5
  322. package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +4 -4
  323. package/lib/oh-story/skills/story-short-write/references/genre-styles//350/277/275/345/246/273/347/201/253/350/221/254/345/234/272.md +3 -3
  324. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +32 -44
  325. package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +20 -18
  326. package/lib/oh-story/skills/story-short-write/references/output-contract.md +1 -1
  327. package/lib/oh-story/skills/story-short-write/references/short-craft.md +18 -18
  328. package/lib/oh-story/skills/story-short-write/references/short-deslop.md +1 -1
  329. package/lib/oh-story/skills/story-short-write/references/short-format.md +38 -8
  330. package/lib/oh-story/skills/story-short-write/references/{quality-checklist.md → short-prose-quality.md} +22 -21
  331. package/lib/oh-story/skills/story-short-write/references/short-reversal.md +355 -0
  332. package/lib/oh-story/skills/{story-short-analyze/references/hooks-suspense.md → story-short-write/references/short-suspense.md} +31 -31
  333. package/lib/oh-story/skills/story-short-write/references/submission-craft.md +2 -2
  334. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +31 -22
  335. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +871 -0
  336. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +64 -1
  337. package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +212 -0
  338. package/lib/oh-story/skills/story-short-write/scripts/check-phase2-contract.js +363 -0
  339. package/lib/types/client/drama-production-view.d.ts +34 -0
  340. package/lib/types/client/drama-production-view.d.ts.map +1 -0
  341. package/lib/types/client/drama-production.d.ts +92 -0
  342. package/lib/types/client/drama-production.d.ts.map +1 -0
  343. package/lib/types/client/file-activity.d.ts +9 -2
  344. package/lib/types/client/file-activity.d.ts.map +1 -1
  345. package/lib/types/client/index.d.ts +1 -1
  346. package/lib/types/client/index.d.ts.map +1 -1
  347. package/lib/types/client/production-intents.d.ts +9 -0
  348. package/lib/types/client/production-intents.d.ts.map +1 -0
  349. package/lib/types/client/production-prompts.d.ts +9 -0
  350. package/lib/types/client/production-prompts.d.ts.map +1 -0
  351. package/lib/types/client/production-runtime.d.ts +58 -0
  352. package/lib/types/client/production-runtime.d.ts.map +1 -0
  353. package/lib/types/client/video-studio.d.ts +41 -0
  354. package/lib/types/client/video-studio.d.ts.map +1 -0
  355. package/lib/types/client/workbench-ui.d.ts +5 -0
  356. package/lib/types/client/workbench-ui.d.ts.map +1 -0
  357. package/lib/types/game-verification.d.ts +16 -0
  358. package/lib/types/game-verification.d.ts.map +1 -0
  359. package/lib/types/index.d.ts +5 -2
  360. package/lib/types/index.d.ts.map +1 -1
  361. package/lib/types/production-intent.d.ts +21 -0
  362. package/lib/types/production-intent.d.ts.map +1 -0
  363. package/lib/types/production-tool.d.ts +5 -0
  364. package/lib/types/production-tool.d.ts.map +1 -0
  365. package/lib/types/reference-tool.d.ts +6 -0
  366. package/lib/types/reference-tool.d.ts.map +1 -0
  367. package/lib/types/role-provider.d.ts.map +1 -1
  368. package/lib/types/role-tool.d.ts +3 -1
  369. package/lib/types/role-tool.d.ts.map +1 -1
  370. package/lib/types/skill-provider.d.ts +7 -0
  371. package/lib/types/skill-provider.d.ts.map +1 -1
  372. package/lib/types/video-project.d.ts +42 -0
  373. package/lib/types/video-project.d.ts.map +1 -0
  374. package/lib/types/workspace-request-trust.d.ts +6 -0
  375. package/lib/types/workspace-request-trust.d.ts.map +1 -1
  376. package/lib/types/workspace-route.d.ts +20 -0
  377. package/lib/types/workspace-route.d.ts.map +1 -1
  378. package/lib/video-recap/CHANGELOG.md +282 -0
  379. package/lib/video-recap/LICENSE +21 -0
  380. package/lib/video-recap/README.en.md +257 -0
  381. package/lib/video-recap/README.md +255 -0
  382. package/lib/video-recap/manifest.json +684 -0
  383. package/lib/video-recap/skills/video-assemble/SKILL.md +97 -0
  384. package/lib/video-recap/skills/video-assemble/references/jianying/LICENSE.duo-video +21 -0
  385. package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +15 -0
  386. package/lib/video-recap/skills/video-assemble/references/jianying/empty_draft_meta_info.json +69 -0
  387. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +84 -0
  388. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +98 -0
  389. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +204 -0
  390. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_material_video.json +83 -0
  391. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_meta_material_value.json +23 -0
  392. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_project_info.json +133 -0
  393. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_segment.json +70 -0
  394. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_text_styles.json +30 -0
  395. package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_audio.json +41 -0
  396. package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_text.json +109 -0
  397. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +88 -0
  398. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +345 -0
  399. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +34 -0
  400. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +305 -0
  401. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +94 -0
  402. package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +205 -0
  403. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +555 -0
  404. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +261 -0
  405. package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +767 -0
  406. package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +111 -0
  407. package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +24 -0
  408. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +109 -0
  409. package/lib/video-recap/skills/video-assemble/scripts/jianying_templates.py +37 -0
  410. package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +191 -0
  411. package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +75 -0
  412. package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +394 -0
  413. package/lib/video-recap/skills/video-assemble/scripts/lib.py +239 -0
  414. package/lib/video-recap/skills/video-assemble/scripts/media.py +243 -0
  415. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +477 -0
  416. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +46 -0
  417. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +486 -0
  418. package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +376 -0
  419. package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +88 -0
  420. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +248 -0
  421. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +89 -0
  422. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +512 -0
  423. package/lib/video-recap/skills/video-cut/SKILL.md +90 -0
  424. package/lib/video-recap/skills/video-cut/scripts/cut.py +67 -0
  425. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +320 -0
  426. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +545 -0
  427. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +348 -0
  428. package/lib/video-recap/skills/video-cut/scripts/lib.py +77 -0
  429. package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +341 -0
  430. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +249 -0
  431. package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +745 -0
  432. package/lib/video-recap/skills/video-recap/SKILL.md +216 -0
  433. package/lib/video-recap/skills/video-recap/references/config-playbook.md +61 -0
  434. package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +225 -0
  435. package/lib/video-recap/skills/video-recap/references/data-schema.md +534 -0
  436. package/lib/video-recap/skills/video-recap/references/research-guide.md +74 -0
  437. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +23 -0
  438. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +251 -0
  439. package/lib/video-recap/skills/video-recap/scripts/doctor.py +483 -0
  440. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +737 -0
  441. package/lib/video-recap/skills/video-recap/scripts/lib.py +242 -0
  442. package/lib/video-recap/skills/video-recap/scripts/materials.py +457 -0
  443. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +95 -0
  444. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +18 -0
  445. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_contract.py +9 -0
  446. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +371 -0
  447. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +201 -0
  448. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +227 -0
  449. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +362 -0
  450. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +129 -0
  451. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +338 -0
  452. package/lib/video-recap/skills/video-recap/scripts/recap.py +9 -0
  453. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +140 -0
  454. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +550 -0
  455. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +77 -0
  456. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +654 -0
  457. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +335 -0
  458. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +217 -0
  459. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +795 -0
  460. package/lib/video-recap/skills/video-script/SKILL.md +248 -0
  461. package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +225 -0
  462. package/lib/video-recap/skills/video-script/references/research-guide.md +75 -0
  463. package/lib/video-recap/skills/video-script/scripts/agent_brief.py +399 -0
  464. package/lib/video-recap/skills/video-script/scripts/agent_text.py +410 -0
  465. package/lib/video-recap/skills/video-script/scripts/brief_context.py +403 -0
  466. package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +332 -0
  467. package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +442 -0
  468. package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +237 -0
  469. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +492 -0
  470. package/lib/video-recap/skills/video-script/scripts/lib.py +327 -0
  471. package/lib/video-recap/skills/video-script/scripts/narration.py +12 -0
  472. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +678 -0
  473. package/lib/video-recap/skills/video-script/scripts/review.py +42 -0
  474. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +262 -0
  475. package/lib/video-recap/skills/video-script/scripts/review_response.py +761 -0
  476. package/lib/video-recap/skills/video-script/scripts/review_runner.py +200 -0
  477. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +204 -0
  478. package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +199 -0
  479. package/lib/video-recap/skills/video-script/scripts/validate.py +164 -0
  480. package/lib/video-recap/skills/video-understanding/SKILL.md +75 -0
  481. package/lib/video-recap/skills/video-understanding/references/data-schema.md +299 -0
  482. package/lib/video-recap/skills/video-understanding/references/prompt-templates.md +37 -0
  483. package/lib/video-recap/skills/video-understanding/references/research-guide.md +74 -0
  484. package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +399 -0
  485. package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +410 -0
  486. package/lib/video-recap/skills/video-understanding/scripts/asr.py +260 -0
  487. package/lib/video-recap/skills/video-understanding/scripts/brief.py +12 -0
  488. package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +403 -0
  489. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +332 -0
  490. package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +442 -0
  491. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +647 -0
  492. package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +237 -0
  493. package/lib/video-recap/skills/video-understanding/scripts/detect.py +513 -0
  494. package/lib/video-recap/skills/video-understanding/scripts/extract.py +62 -0
  495. package/lib/video-recap/skills/video-understanding/scripts/lib.py +470 -0
  496. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +678 -0
  497. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +204 -0
  498. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +507 -0
  499. package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +199 -0
  500. package/lib/video-recap/skills/video-understanding/scripts/understand.py +9 -0
  501. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +216 -0
  502. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +327 -0
  503. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +367 -0
  504. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +162 -0
  505. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +705 -0
  506. package/lib/video-recap/skills/video-voiceover/SKILL.md +81 -0
  507. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +637 -0
  508. package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +109 -0
  509. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +421 -0
  510. package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +113 -0
  511. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +782 -0
  512. package/package.json +57 -41
  513. package/lib/drama/skills/short-drama/references/lifecycle-commands.md +0 -130
  514. package/lib/oh-story/skills/story-deslop/scripts/check-outline-copy.js +0 -241
  515. package/lib/oh-story/skills/story-long-write/references/format-and-structure.md +0 -169
  516. package/lib/oh-story/skills/story-setup/references/agent-references/genre-writing-formulas.md +0 -444
  517. package/lib/oh-story/skills/story-setup/references/agent-references/quality-checklist.md +0 -247
  518. package/lib/oh-story/skills/story-short-analyze/references/anti-ai-writing.md +0 -582
  519. package/lib/oh-story/skills/story-short-analyze/references/banned-words.md +0 -147
  520. package/lib/oh-story/skills/story-short-analyze/references/genre-catalog.md +0 -490
  521. package/lib/oh-story/skills/story-short-analyze/references/genre-core-mechanics.md +0 -425
  522. package/lib/oh-story/skills/story-short-analyze/references/genre-writing-formulas.md +0 -453
  523. /package/lib/oh-story/skills/{story-long-write/references/hooks-chapter.md → story-setup/references/agent-references/short-chapter-hooks.md} +0 -0
  524. /package/lib/oh-story/skills/{story-long-write/references/hooks-paragraph.md → story-setup/references/agent-references/short-paragraph-hooks.md} +0 -0
package/lib/client.js CHANGED
@@ -24,10 +24,19 @@ __export(index_exports, {
24
24
  name: () => name
25
25
  });
26
26
  module.exports = __toCommonJS(index_exports);
27
- var import_client = require("@deepseek-ai/dsh-client-runtime/client"), import_react = require("react"), import_react_dom = require("react-dom");
27
+ var import_dsh_client_store = require("@deepseek-ai/dsh-client-store"), import_react3 = require("react"), import_react_dom = require("react-dom");
28
28
 
29
29
  // src/client/file-activity.ts
30
- var STORY_DIRECTORIES = /* @__PURE__ */ new Set(["\u6B63\u6587", "\u5927\u7EB2", "\u8BBE\u5B9A", "\u8FFD\u8E2A", "\u5BF9\u6807", "\u53C2\u8003\u8D44\u6599"]), DRAMA_DIRECTORIES = /* @__PURE__ */ new Set(["\u8F93\u5165", "\u9879\u76EE\u5F00\u53D1", "\u8BBE\u5B9A\u96C6", "\u5267\u96C6", "\u4EA4\u4ED8", "\u521B\u4F5C\u8005\u51B3\u7B56", "\u5BA1\u67E5"]), EDITABLE_EXTENSION = /\.(?:md|txt|json|jsonl)$/iu, MUTATING_CALLS = /* @__PURE__ */ new Set(["write", "edit", "str_replace_editor", "bash", "run_code", "oh_story_role"]);
30
+ var WORKBENCH_LABELS = {
31
+ story: "\u5C0F\u8BF4",
32
+ drama: "\u77ED\u5267",
33
+ game: "\u6E38\u620F",
34
+ video: "\u89C6\u9891"
35
+ };
36
+ function workbenchLabel(mode) {
37
+ return WORKBENCH_LABELS[mode];
38
+ }
39
+ var STORY_DIRECTORIES = /* @__PURE__ */ new Set(["\u6B63\u6587", "\u5927\u7EB2", "\u8BBE\u5B9A", "\u8FFD\u8E2A", "\u5BF9\u6807", "\u53C2\u8003\u8D44\u6599"]), DRAMA_DIRECTORIES = /* @__PURE__ */ new Set(["\u8F93\u5165", "\u9879\u76EE\u5F00\u53D1", "\u8BBE\u5B9A\u96C6", "\u5267\u96C6", "\u4EA4\u4ED8", "\u521B\u4F5C\u8005\u51B3\u7B56", "\u5BA1\u67E5"]), GAME_DIRECTORY = "game-adaptations", VIDEO_DIRECTORY = "video-recaps", EDITABLE_EXTENSION = /\.(?:md|txt|json|jsonl|html|css|[cm]?js|tsx?|jsx)$/iu, MUTATING_CALLS = /* @__PURE__ */ new Set(["write", "edit", "str_replace_editor", "bash", "run_code", "oh_story_role"]);
31
40
  function streamingAssistant(timeline) {
32
41
  for (let turnNumber of timeline.turnOrder.toReversed()) {
33
42
  let turn = timeline.turns.get(turnNumber);
@@ -161,27 +170,15 @@ function mutationFromArgs(name2, callId, argsRaw, stage) {
161
170
  replaceAll: !1
162
171
  } : { callId, name: name2, argsRaw, stage, path, operation: void 0, oldText: void 0, newText: void 0, replaceAll: !1 };
163
172
  }
164
- function mutationsFromRunning(call) {
165
- let direct = mutationFromArgs(call.name, call.callId, call.argsRaw, "running");
166
- if (direct === void 0) return [];
167
- let view = call.callView;
168
- return view?.card !== "diff" || view.diffs.length === 0 ? [direct] : view.diffs.map((diff, index) => ({
169
- ...direct,
170
- callId: view.diffs.length === 1 ? call.callId : `${call.callId}:${String(index)}`,
171
- path: diff.path,
172
- operation: diff.oldText === null ? "replace-file" : "replace-text",
173
- oldText: diff.oldText ?? void 0,
174
- newText: diff.newText
175
- }));
176
- }
177
- function visitRunning(blocks, visit) {
173
+ function visitRunning(blocks, visit2) {
178
174
  for (let block of blocks)
179
- "kind" in block || visit(block), visitRunning(block.subCalls, visit);
175
+ "kind" in block || visit2(block), visitRunning(block.subCalls, visit2);
180
176
  }
181
177
  function fileMutations(runningCalls, partial = null) {
182
178
  let values = [];
183
179
  visitRunning(runningCalls, (call) => {
184
- values.push(...mutationsFromRunning(call));
180
+ let mutation = mutationFromArgs(call.name, call.callId, call.argsRaw, "running");
181
+ mutation !== void 0 && values.push(mutation);
185
182
  });
186
183
  for (let block of partial?.blocks ?? []) {
187
184
  if (block.kind !== "tool-call") continue;
@@ -198,13 +195,7 @@ function mutatingCallIds(runningCalls) {
198
195
  }
199
196
  function settledMutationSignals(block) {
200
197
  let nested = block.subCalls.flatMap(settledMutationSignals);
201
- if (!("kind" in block) || block.isError) return nested;
202
- let diffs = block.resultView?.card === "diff" ? block.resultView.diffs : block.callView?.card === "diff" ? block.callView.diffs : void 0;
203
- if (diffs !== void 0) return [
204
- ...diffs.map((diff, index) => `${block.callId}:${String(index)}\0${diff.path}`),
205
- ...nested
206
- ];
207
- if (block.call === null) return nested;
198
+ if (!("kind" in block) || block.isError || block.call === null) return nested;
208
199
  let mutation = mutationFromArgs(block.call.name, block.callId, block.call.argsRaw, "running");
209
200
  return mutation?.path === void 0 ? nested : [`${block.callId}\0${mutation.path}`, ...nested];
210
201
  }
@@ -221,7 +212,7 @@ function creativeRelativePath(path, cwd) {
221
212
  let normalized = path.replaceAll("\\", "/"), root = cwd?.replaceAll("\\", "/").replace(/\/$/u, ""), insideRoot = root !== void 0 && normalized.startsWith(`${root}/`);
222
213
  if ((normalized.startsWith("/") || /^[a-z]:\//iu.test(normalized) || normalized.startsWith("file:")) && !insideRoot) return;
223
214
  let relative = insideRoot ? normalized.slice(root.length + 1) : normalized.replace(/^\.\//u, ""), [directory2] = relative.split("/", 1);
224
- if (!(!(directory2 !== void 0 && (STORY_DIRECTORIES.has(directory2) || DRAMA_DIRECTORIES.has(directory2))) && relative !== "short-drama.json" || !EDITABLE_EXTENSION.test(relative)) && !relative.split("/").some((part) => part === ".." || part === "." || part === ""))
215
+ if (!(!(directory2 !== void 0 && (STORY_DIRECTORIES.has(directory2) || DRAMA_DIRECTORIES.has(directory2) || directory2 === GAME_DIRECTORY || directory2 === VIDEO_DIRECTORY)) && relative !== "short-drama.json" || !EDITABLE_EXTENSION.test(relative)) && !relative.split("/").some((part) => part === ".." || part === "." || part === ""))
225
216
  return relative;
226
217
  }
227
218
  function workbenchModeForPath(path) {
@@ -229,6 +220,35 @@ function workbenchModeForPath(path) {
229
220
  let directory2 = path?.split("/", 1)[0];
230
221
  if (directory2 !== void 0 && STORY_DIRECTORIES.has(directory2)) return "story";
231
222
  if (directory2 !== void 0 && DRAMA_DIRECTORIES.has(directory2)) return "drama";
223
+ if (directory2 === GAME_DIRECTORY) return "game";
224
+ if (directory2 === VIDEO_DIRECTORY) return "video";
225
+ }
226
+ function preferredWorkbenchFile(files, mode) {
227
+ let matching = files.filter((file) => workbenchModeForPath(file.path) === mode), preferences = mode === "story" ? [/^正文\/.*\.md$/u, /^大纲\/.*\.md$/u, /\.md$/u] : mode === "drama" ? [
228
+ /^剧集\/EP0*1\/剧本\.md$/u,
229
+ /^剧集\/.*\/剧本\.md$/u,
230
+ /^剧集\/EP0*1\/screenplay\.md$/iu,
231
+ /^剧集\/.*\/screenplay\.md$/iu,
232
+ /^项目开发\/creative-brief\.md$/u,
233
+ /^输入\/.*\.md$/u,
234
+ /\.md$/u,
235
+ /^short-drama\.json$/u
236
+ ] : mode === "game" ? [
237
+ /^game-adaptations\/[^/]+\/PRODUCT_BRIEF\.md$/u,
238
+ /^game-adaptations\/[^/]+\/design\/GAME_DESIGN\.md$/u,
239
+ /^game-adaptations\/[^/]+\/qa\/verification\.json$/u,
240
+ /^game-adaptations\/[^/]+\/build\/app\/index\.html$/u,
241
+ /\.md$/u
242
+ ] : [
243
+ /^video-recaps\/[^/]+\/work\/recap_story_plan\.json$/u,
244
+ /^video-recaps\/[^/]+\/work\/narration\.json$/u,
245
+ /^video-recaps\/[^/]+\/work\/assembly_manifest\.json$/u
246
+ ];
247
+ for (let pattern of preferences) {
248
+ let match = matching.find((file) => pattern.test(file.path));
249
+ if (match !== void 0) return match.path;
250
+ }
251
+ return matching[0]?.path;
232
252
  }
233
253
  function previewMutation(activity, base) {
234
254
  if (activity.operation === "replace-file") return activity.newText;
@@ -524,254 +544,1648 @@ function MarkdownPreview({ content, label }) {
524
544
  }) });
525
545
  }
526
546
 
527
- // inline-css:/home/runner/work/oh-story-dsh/oh-story-dsh/packages/dsh-plugin/src/client/plugin.css
528
- var plugin_default = `.oh-story-bridge-marker { display: none; }
529
- .oh-story-split-surface { display: contents; font-family: var(--dsw-font-family); }
530
-
531
- /* DSH documents every [data-slot] wrapper as a stable addressable styling
532
- seam. The official Session body and composer stay mounted; this only turns
533
- their scroll host into a three-column writing surface. */
534
- [data-conversation-scroll]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
535
- --os-border: var(--dsw-alias-border-l2);
536
- --os-muted: var(--dsw-alias-label-secondary);
537
- display: grid;
538
- grid-template-columns: clamp(184px, 16%, 200px) minmax(240px, 1fr) clamp(408px, 40%, 520px);
539
- grid-template-rows: minmax(0, 1fr);
540
- min-height: 0;
541
- overflow-x: hidden;
542
- overflow-y: auto;
543
- position: relative;
544
- scrollbar-gutter: stable;
545
- scroll-padding-bottom: calc(var(--dsh-composer-height, 152px) + 16px);
547
+ // src/client/drama-production.ts
548
+ var PRODUCTION_PROTOCOL_VERSION = "short-drama/v1", CREATOR_DOCUMENT_NAMES = /* @__PURE__ */ new Set(["\u5267\u672C.md", "\u89C6\u89C9\u8BBE\u5B9A.md", "\u5206\u955C.md", "\u56FE\u7247\u63D0\u793A\u8BCD.md", "\u89C6\u9891\u63D0\u793A\u8BCD.md"]);
549
+ function episodeDirectoryForPath(path) {
550
+ return path === void 0 ? void 0 : /^(剧集\/[^/]+)\/[^/]+$/u.exec(path)?.[1];
551
+ }
552
+ function isCreatorDocumentPath(path) {
553
+ let directory2 = episodeDirectoryForPath(path), name2 = path.split("/").at(-1);
554
+ return directory2 !== void 0 && name2 !== void 0 && CREATOR_DOCUMENT_NAMES.has(name2);
555
+ }
556
+ function creatorDocumentPaths(files, episodeDirectory) {
557
+ return files.map((file) => file.path).filter((path) => path.startsWith(`${episodeDirectory}/`) && CREATOR_DOCUMENT_NAMES.has(path.slice(episodeDirectory.length + 1))).sort((left, right) => creatorDocumentOrder(left) - creatorDocumentOrder(right) || left.localeCompare(right, "zh-Hans-CN"));
558
+ }
559
+ function parseEpisodeProduction(documents, episodeDirectory) {
560
+ let storyboardPath = `${episodeDirectory}/\u5206\u955C.md`, imagePromptPath = `${episodeDirectory}/\u56FE\u7247\u63D0\u793A\u8BCD.md`, videoPromptPath = `${episodeDirectory}/\u89C6\u9891\u63D0\u793A\u8BCD.md`, visualPath = `${episodeDirectory}/\u89C6\u89C9\u8BBE\u5B9A.md`, shots = parseStoryboard(storyboardPath, documents[storyboardPath] ?? ""), assets = parseImagePrompts(imagePromptPath, documents[imagePromptPath] ?? ""), motions = parseVideoPrompts(videoPromptPath, documents[videoPromptPath] ?? ""), visualAssets = parseVisualAssets(visualPath, documents[visualPath] ?? ""), motionByShot = new Map(motions.flatMap((motion) => motion.shotId === void 0 ? [] : [[motion.shotId, motion]])), linkedShots = shots.map((shot) => ({ ...shot, motion: motionByShot.get(shot.id) })), targets = /* @__PURE__ */ new Map();
561
+ for (let item of [...linkedShots, ...assets, ...motions, ...visualAssets])
562
+ targets.set(item.id, { path: item.path, offset: item.offset, id: item.id });
563
+ for (let shot of linkedShots)
564
+ if (shot.source !== void 0) {
565
+ let screenplayPath = `${episodeDirectory}/\u5267\u672C.md`, target = sectionTarget(screenplayPath, documents[screenplayPath] ?? "", shot.source);
566
+ target !== void 0 && targets.set(shot.source, target);
567
+ }
568
+ let diagnostics = validateProductionProtocol({
569
+ documents,
570
+ episodeDirectory,
571
+ shots: linkedShots,
572
+ assets,
573
+ visualAssets,
574
+ motions
575
+ });
576
+ return {
577
+ protocolVersion: PRODUCTION_PROTOCOL_VERSION,
578
+ episodeDirectory,
579
+ shots: linkedShots,
580
+ assets,
581
+ visualAssets,
582
+ motions,
583
+ targets,
584
+ documentPaths: Object.keys(documents).filter((path) => path.startsWith(`${episodeDirectory}/`)),
585
+ diagnostics
586
+ };
546
587
  }
547
-
548
- /* Keep the final Chat flow above the overlapping official Composer. */
549
- [data-conversation-scroll]:has(> [data-slot="conversation.session"] > .oh-story-split-surface)
550
- > [data-slot="conversation.session"] > :not(.oh-story-split-surface) {
551
- grid-column: 3;
552
- grid-row: 1;
553
- min-width: 0;
554
- min-height: 100%;
555
- border-left: 1px solid var(--os-border);
588
+ function parseStoryboard(path, content) {
589
+ return levelTwoSections(content).flatMap((section) => {
590
+ let match = /^(SHOT-[A-Z0-9-]+)\s*(?:[·||]\s*)?(.*)$/iu.exec(section.heading.trim());
591
+ if (match === null || match[1] === void 0) return [];
592
+ let fields = bulletFields(section.body), id = match[1].toLocaleUpperCase();
593
+ return [{
594
+ id,
595
+ title: match[2]?.trim() || id,
596
+ path,
597
+ offset: section.offset,
598
+ source: firstField(fields, "\u6765\u6E90", "\u573A\u6B21"),
599
+ durationSeconds: seconds(firstField(fields, "\u65F6\u957F")),
600
+ purpose: firstField(fields, "\u76EE\u7684", "\u955C\u5934\u76EE\u7684"),
601
+ shotSpec: firstField(fields, "\u666F\u522B/\u673A\u4F4D", "\u666F\u522B", "\u955C\u5934\u89C4\u683C"),
602
+ start: firstField(fields, "\u8D77\u70B9", "\u8D77\u59CB"),
603
+ end: firstField(fields, "\u7EC8\u70B9", "\u7ED3\u675F"),
604
+ references: splitReferences(firstField(fields, "\u56FE\u7247\u63D0\u793A\u8BCD\u9879", "\u53C2\u8003", "\u5173\u8054\u8D44\u4EA7")),
605
+ keyframePrompt: quoteUnderHeading(section.body, "\u51BB\u7ED3\u5173\u952E\u5E27\u63D0\u793A\u8BCD")
606
+ }];
607
+ });
556
608
  }
557
-
558
- [data-conversation-scroll]:has(> [data-slot="conversation.session"] > .oh-story-split-surface)
559
- [data-chat-flow] {
560
- padding-bottom: calc(var(--dsh-composer-height, 152px) + 16px);
609
+ function parseImagePrompts(path, content) {
610
+ return levelTwoSections(content).flatMap((section) => {
611
+ let match = /^(IMG-[A-Z0-9-]+)\s*(?:[·||]\s*)?(.*)$/iu.exec(section.heading.trim());
612
+ if (match === null || match[1] === void 0) return [];
613
+ let fields = bulletFields(section.body), id = match[1].toLocaleUpperCase(), title = match[2]?.trim() || id;
614
+ return [{
615
+ id,
616
+ title,
617
+ kind: inferAssetKind(`${id} ${title} ${firstField(fields, "\u7528\u9014") ?? ""}`),
618
+ path,
619
+ offset: section.offset,
620
+ purpose: firstField(fields, "\u7528\u9014"),
621
+ reference: firstField(fields, "\u53C2\u8003", "\u53C2\u8003\u7EA6\u675F"),
622
+ prompt: quoteUnderHeading(section.body, "\u53EF\u590D\u5236\u63D0\u793A\u8BCD")
623
+ }];
624
+ });
561
625
  }
562
-
563
- [data-conversation-scroll]:has(> [data-slot="conversation.session"] > .oh-story-split-surface)
564
- > [data-composer-seat] {
565
- grid-column: 3;
566
- grid-row: 1;
567
- align-self: start;
568
- min-width: 0;
569
- width: 100%;
570
- position: sticky;
571
- top: calc(var(--oh-story-scroll-height, 720px) - var(--dsh-composer-height, 152px));
572
- bottom: auto;
626
+ function parseVideoPrompts(path, content) {
627
+ return levelTwoSections(content).flatMap((section) => {
628
+ let match = /^(MOTION-[A-Z0-9-]+)\s*(?:[·||]\s*)?(.*)$/iu.exec(section.heading.trim());
629
+ if (match === null || match[1] === void 0) return [];
630
+ let fields = bulletFields(section.body), id = match[1].toLocaleUpperCase(), shotId = firstField(fields, "\u5206\u955C", "\u955C\u5934")?.match(/SHOT-[A-Z0-9-]+/iu)?.[0]?.toLocaleUpperCase();
631
+ return [{
632
+ id,
633
+ title: match[2]?.trim() || id,
634
+ path,
635
+ offset: section.offset,
636
+ shotId,
637
+ durationSeconds: seconds(firstField(fields, "\u65F6\u957F")),
638
+ startFrame: firstField(fields, "\u8D77\u59CB\u5E27", "\u8D77\u70B9"),
639
+ end: firstField(fields, "\u7EC8\u70B9", "\u7ED3\u675F"),
640
+ prompt: quoteUnderHeading(section.body, "\u53EF\u590D\u5236\u63D0\u793A\u8BCD")
641
+ }];
642
+ });
573
643
  }
574
-
575
- .oh-story-tree,
576
- .oh-story-editor {
577
- box-sizing: border-box;
578
- grid-row: 1;
579
- position: sticky;
580
- top: 0;
581
- align-self: start;
582
- min-width: 0;
583
- min-height: 0;
584
- height: 100%;
585
- color: inherit;
586
- background: var(--dsw-alias-bg-base);
644
+ function parseVisualAssets(path, content) {
645
+ return levelTwoSections(content).flatMap((section) => {
646
+ let match = /^(人物|角色|造型|地点|场景|道具|状态)\s*(?:[·||:]\s*)?(.*)$/u.exec(section.heading.trim());
647
+ if (match === null || match[1] === void 0) return [];
648
+ let fields = bulletFields(section.body), title = match[2]?.trim() || section.heading.trim(), declaredId = firstField(fields, "ID", "\u8D44\u4EA7 ID", "\u8D44\u4EA7ID")?.trim().toLocaleUpperCase(), stableId = declaredId !== void 0 && /^VISUAL-[A-Z0-9-]+$/u.test(declaredId);
649
+ return [{ id: stableId ? declaredId : `VISUAL-${slug(`${match[1]}-${title}`)}`, title, kind: inferAssetKind(`${match[1]} ${title}`), path, offset: section.offset, description: section.body.trim(), stableId, declaredId }];
650
+ });
587
651
  }
588
-
589
- .oh-story-tree {
590
- grid-column: 1;
591
- overflow: auto;
592
- padding: 14px 10px 24px;
593
- border-right: 1px solid var(--os-border);
594
- background: var(--dsw-specific-sidebar-fill);
652
+ function validateProductionProtocol(input) {
653
+ let diagnostics = [], add = (value) => {
654
+ diagnostics.push({ ...value, line: lineAt(input.documents[value.path] ?? "", value.offset) });
655
+ }, all = [...input.shots, ...input.assets, ...input.visualAssets, ...input.motions], byId = /* @__PURE__ */ new Map();
656
+ for (let item of all) byId.set(item.id, [...byId.get(item.id) ?? [], item]);
657
+ for (let [id, items] of byId)
658
+ if (!(items.length < 2))
659
+ for (let item of items) add({ severity: "error", code: "duplicate_id", path: item.path, offset: item.offset, targetId: id, message: `${id} \u5728\u5F53\u524D\u96C6\u5185\u91CD\u590D\uFF0C\u540E\u51FA\u73B0\u7684\u6761\u76EE\u4F1A\u906E\u853D\u524D\u4E00\u6761\u3002` });
660
+ for (let visual of input.visualAssets)
661
+ visual.stableId || add(visual.declaredId === void 0 ? { severity: "warning", code: "generated_visual_id", path: visual.path, offset: visual.offset, targetId: visual.id, message: `${visual.title} \u7F3A\u5C11\u7A33\u5B9A\u7684 \u201C- ID\uFF1AVISUAL-*\u201D\uFF1B\u4FEE\u6539\u6807\u9898\u4F1A\u6539\u53D8\u753B\u5E03\u8282\u70B9\u8EAB\u4EFD\u3002` } : { severity: "warning", code: "invalid_visual_id", path: visual.path, offset: visual.offset, targetId: visual.id, message: `${visual.title} \u7684 \u201C- ID\uFF1A${visual.declaredId}\u201D \u4E0D\u662F\u53EF\u7528\u5F62\u5F0F\uFF0C\u5DF2\u56DE\u9000\u5230\u6807\u9898\u6D3E\u751F ID\uFF1B\u8BF7\u5199\u6210 VISUAL- \u52A0\u5927\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u6216\u8FDE\u5B57\u7B26\u3002` });
662
+ let knownReferences = new Set([...input.assets, ...input.visualAssets, ...input.shots, ...input.motions].map((item) => item.id)), screenplayPath = `${input.episodeDirectory}/\u5267\u672C.md`;
663
+ for (let shot of input.shots) {
664
+ for (let reference of shot.references)
665
+ knownReferences.has(reference) || add({ severity: "warning", code: "unknown_reference", path: shot.path, offset: shot.offset, targetId: shot.id, message: `${shot.id} \u5F15\u7528\u4E86\u672A\u89E3\u6790\u7684 ${reference}\u3002` });
666
+ shot.source !== void 0 && sectionTarget(screenplayPath, input.documents[screenplayPath] ?? "", shot.source) === void 0 && add({ severity: "warning", code: "unknown_source", path: shot.path, offset: shot.offset, targetId: shot.id, message: `${shot.id} \u7684\u6765\u6E90 ${shot.source} \u5728\u5267\u672C\u4E2D\u4E0D\u5B58\u5728\u3002` });
667
+ }
668
+ let shots = new Set(input.shots.map((shot) => shot.id)), motionsByShot = /* @__PURE__ */ new Map();
669
+ for (let motion of input.motions) {
670
+ if (motion.shotId === void 0) {
671
+ add({ severity: "warning", code: "motion_without_shot", path: motion.path, offset: motion.offset, targetId: motion.id, message: `${motion.id} \u6CA1\u6709\u53EF\u89E3\u6790\u7684 SHOT-* \u5206\u955C\u5B57\u6BB5\u3002` });
672
+ continue;
673
+ }
674
+ shots.has(motion.shotId) || add({ severity: "error", code: "unknown_motion_shot", path: motion.path, offset: motion.offset, targetId: motion.id, message: `${motion.id} \u6307\u5411\u4E0D\u5B58\u5728\u7684 ${motion.shotId}\u3002` }), motionsByShot.set(motion.shotId, [...motionsByShot.get(motion.shotId) ?? [], motion]);
675
+ }
676
+ for (let [shotId, motions] of motionsByShot)
677
+ if (!(motions.length < 2))
678
+ for (let motion of motions) add({ severity: "error", code: "multiple_motions", path: motion.path, offset: motion.offset, targetId: motion.id, message: `${shotId} \u540C\u65F6\u7ED1\u5B9A\u4E86\u591A\u4E2A MOTION\uFF0C\u753B\u5E03\u53EA\u80FD\u786E\u5B9A\u4E00\u4E2A\u3002` });
679
+ let parsedOffsets = new Set([...input.shots, ...input.assets, ...input.motions].map((item) => `${item.path}:${String(item.offset)}`));
680
+ for (let [path, prefix] of [
681
+ [`${input.episodeDirectory}/\u5206\u955C.md`, "SHOT"],
682
+ [`${input.episodeDirectory}/\u56FE\u7247\u63D0\u793A\u8BCD.md`, "IMG"],
683
+ [`${input.episodeDirectory}/\u89C6\u9891\u63D0\u793A\u8BCD.md`, "MOTION"]
684
+ ])
685
+ for (let section of levelTwoSections(input.documents[path] ?? ""))
686
+ section.heading.toLocaleUpperCase().startsWith(prefix) && !parsedOffsets.has(`${path}:${String(section.offset)}`) && add({ severity: "error", code: "malformed_heading", path, offset: section.offset, message: `\u65E0\u6CD5\u89E3\u6790\u6807\u9898 \u201C${section.heading.trim()}\u201D\uFF0C\u9700\u8981\u7A33\u5B9A\u7684 ${prefix}-* ID\u3002` });
687
+ return diagnostics.sort((left, right) => left.path.localeCompare(right.path, "zh-Hans-CN") || left.offset - right.offset || left.code.localeCompare(right.code));
688
+ }
689
+ function productionCompleteness(shot) {
690
+ let keyframe = !!shot.keyframePrompt?.trim(), motion = !!shot.motion?.prompt?.trim(), references = shot.references.length > 0;
691
+ return { keyframe, motion, references, complete: keyframe && motion };
692
+ }
693
+ function levelTwoSections(content) {
694
+ let matches = [...content.matchAll(/^##\s+(.+)\s*$/gmu)];
695
+ return matches.map((match, index) => {
696
+ let start = (match.index ?? 0) + match[0].length, end = matches[index + 1]?.index ?? content.length;
697
+ return { heading: match[1] ?? "", body: content.slice(start, end), offset: match.index ?? 0 };
698
+ });
595
699
  }
596
-
597
- .oh-story-editor {
598
- display: flex;
599
- grid-column: 2;
600
- overflow: hidden;
601
- flex-direction: column;
700
+ function bulletFields(body) {
701
+ let fields = /* @__PURE__ */ new Map();
702
+ for (let match of body.matchAll(/^\s*[-*]\s+([^::\n]+)[::]\s*(.+?)\s*$/gmu)) {
703
+ let key = match[1]?.trim(), value = match[2]?.trim();
704
+ key !== void 0 && value !== void 0 && fields.set(key, value);
705
+ }
706
+ return fields;
602
707
  }
603
-
604
- .oh-story-brand,
605
- .oh-story-editor header,
606
- .oh-story-role summary {
607
- display: flex;
608
- align-items: center;
609
- justify-content: space-between;
610
- gap: 12px;
708
+ function firstField(fields, ...names) {
709
+ for (let name2 of names) {
710
+ let value = fields.get(name2);
711
+ if (value !== void 0 && value !== "") return value;
712
+ }
713
+ }
714
+ function quoteUnderHeading(body, heading) {
715
+ let escaped = heading.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"), match = new RegExp(`^###\\s+${escaped}\\s*$([\\s\\S]*?)(?=^###\\s+|(?![\\s\\S]))`, "imu").exec(body);
716
+ if (match?.[1] === void 0) return;
717
+ let value = match[1].split(/\r?\n/u).filter((line) => /^\s*>/u.test(line)).map((line) => line.replace(/^\s*>\s?/u, "").trimEnd()).join(`
718
+ `).trim();
719
+ return value === "" ? void 0 : value;
720
+ }
721
+ function seconds(value) {
722
+ if (value === void 0) return;
723
+ let match = /([0-9]+(?:\.[0-9]+)?)\s*(?:s|秒)/iu.exec(value);
724
+ if (match?.[1] === void 0) return;
725
+ let parsed = Number(match[1]);
726
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
727
+ }
728
+ function splitReferences(value) {
729
+ if (value === void 0) return [];
730
+ let ids = value.match(/(?:IMG|SHOT|MOTION)-[A-Z0-9-]+/giu) ?? [];
731
+ return [...new Set(ids.map((id) => id.toLocaleUpperCase()))];
732
+ }
733
+ function inferAssetKind(value) {
734
+ return /(人物|角色|造型|character|portrait|sheet)/iu.test(value) ? "character" : /(地点|场景|环境|scene|location|corridor|room)/iu.test(value) ? "scene" : /(道具|物件|prop|object)/iu.test(value) ? "prop" : /(状态|state|look)/iu.test(value) ? "state" : "unknown";
735
+ }
736
+ function slug(value) {
737
+ return value.trim().toLocaleUpperCase().replace(/[^\p{Letter}\p{Number}]+/gu, "-").replace(/^-|-$/gu, "") || "ITEM";
738
+ }
739
+ function lineAt(content, offset) {
740
+ return content.slice(0, Math.max(0, offset)).split(/\r?\n/u).length;
741
+ }
742
+ function sectionTarget(path, content, id) {
743
+ let offset = content.search(new RegExp(`^##\\s+${id.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}(?:\\s|$)`, "imu"));
744
+ return offset < 0 ? void 0 : { path, offset, id };
745
+ }
746
+ function creatorDocumentOrder(path) {
747
+ let name2 = path.split("/").at(-1);
748
+ return ["\u5267\u672C.md", "\u89C6\u89C9\u8BBE\u5B9A.md", "\u5206\u955C.md", "\u56FE\u7247\u63D0\u793A\u8BCD.md", "\u89C6\u9891\u63D0\u793A\u8BCD.md"].indexOf(name2 ?? "");
611
749
  }
612
750
 
613
- .oh-story-brand { min-height: 28px; gap: 8px; padding: 0 5px 8px 7px; font-size: 14px; font-weight: 500; white-space: nowrap; }
614
- .oh-story-brand-cluster { display: flex; min-width: 0; align-items: center; gap: 6px; }
615
- .oh-story-brand-cluster strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: inherit; }
616
- .oh-story-kind { flex: none; padding: 1px 6px; border-radius: 5px; color: var(--dsw-alias-label-secondary); background: var(--dsw-alias-button-ghost-active-fill); font-size: 11px; line-height: 16px; font-weight: 500; }
751
+ // src/client/drama-production-view.tsx
752
+ var import_react = require("react");
617
753
 
618
- .oh-story-brand button,
619
- .oh-story-save,
620
- .oh-story-role button {
621
- border: 1px solid var(--dsw-alias-border-l2);
622
- border-radius: 999px;
623
- color: var(--dsw-alias-label-secondary);
624
- background: var(--dsw-alias-bg-base);
625
- padding: 3px 9px;
626
- cursor: pointer;
627
- font: inherit;
628
- }
754
+ // src/client/production-prompts.ts
755
+ var authorityBoundary = "\u53EA\u4F7F\u7528\u5F53\u524D DSH Preset \u53EF\u89C1\u7684\u5DE5\u5177\uFF1B\u6240\u6709\u6587\u4EF6\u3001\u7F51\u7EDC\u3001\u751F\u6210\u548C\u547D\u4EE4\u64CD\u4F5C\u7EE7\u7EED\u9075\u5B88 DSH \u6743\u9650\u4E0E\u5BA1\u6279\u3002";
756
+ function nativeProductionPrompt(production, job, references) {
757
+ let referenceText = references.length === 0 ? "\u65E0" : references.map((item) => `${item.targetId}: ${item.path ?? item.url}`).join(`
758
+ `);
759
+ return `/short-drama-produce
629
760
 
630
- .oh-story-brand button { display: grid; width: 28px; height: 28px; place-items: center; border: 0; padding: 0; background: transparent; font-size: 16px; }
631
- .oh-story-brand button:hover,
632
- .oh-story-save:hover:not(:disabled),
633
- .oh-story-role button:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-interactive-bg-hover); }
634
- .oh-story-brand button:focus-visible,
635
- .oh-story-save:focus-visible,
636
- .oh-story-mode-tabs button:focus-visible,
637
- .oh-story-editor-tabs button:focus-visible,
638
- .oh-story-tree nav button:focus-visible,
639
- .oh-story-role button:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary); outline-offset: -2px; }
640
- .oh-story-save:disabled { opacity: .48; cursor: default; }
761
+ \u53EA\u51C6\u5907\u5F53\u524D\u5355\u9879\u751F\u4EA7\u4EFB\u52A1\uFF0C\u4E0D\u8FD0\u884C Provider\u3002
762
+ - \u4EFB\u52A1 ID\uFF1A${job.id}
763
+ - \u4EFB\u52A1\u7C7B\u578B\uFF1A${job.kind === "image" ? "\u56FE\u7247/\u5173\u952E\u5E27" : "\u955C\u5934\u89C6\u9891"}
764
+ - \u5EFA\u8BAE adapter \u5951\u7EA6\uFF1A${job.kind === "image" ? "gpt-image-2" : "seedance"}\uFF08\u5B9E\u9645\u914D\u7F6E\u4E0E\u6A21\u578B\u4EE5\u5F53\u524D DSH \u8FD0\u884C\u73AF\u5883\u4E3A\u51C6\uFF09
765
+ - \u6295\u4EA7\u5BF9\u8C61\uFF1A${job.targetId}
766
+ - \u521B\u4F5C\u6587\u6863\u76EE\u5F55\uFF1A${production.episodeDirectory}
767
+ - \u53C2\u8003\u7D20\u6750\uFF1A
768
+ ${referenceText}
769
+ - \u8F93\u51FA\u76EE\u5F55\uFF1A${production.episodeDirectory}/\u5236\u4F5C\u6210\u679C/${job.targetId}
770
+ - \u8F93\u51FA\u6587\u4EF6\u540D\u5FC5\u987B\u540C\u65F6\u5305\u542B\u6295\u4EA7\u5BF9\u8C61 ID \u4E0E\u4EFB\u52A1 ID ${job.id}\uFF0C\u4EE5\u4FBF DSH \u5DE5\u4F5C\u53F0\u5173\u8054\u7248\u672C\u3002
641
771
 
642
- .oh-story-mode-tabs { display: flex; margin: 0 4px 8px; padding: 2px; border: 1px solid var(--os-border); border-radius: 7px; background: var(--dsw-alias-bg-layer-1); }
772
+ \u5F85\u9884\u68C0\u63D0\u793A\u8BCD\uFF1A
773
+ ${job.prompt}
643
774
 
644
- .oh-story-mode-tabs button,
645
- .oh-story-editor-tabs button {
646
- position: relative;
647
- min-height: 24px;
648
- flex: 1;
649
- border: 0;
650
- color: var(--os-muted);
651
- background: transparent;
652
- border-radius: 5px;
653
- cursor: pointer;
654
- font: inherit;
655
- font-size: 11px;
775
+ \u6309 short-drama-produce \u7684\u786C\u95F8\u95E8\u5EFA\u7ACB\u4E34\u65F6 job \u5E76\u6267\u884C prepare\uFF0C\u5728 Chat \u4E2D\u5B8C\u6574\u5C55\u793A adapter\u3001\u6A21\u578B/profile\u3001\u6570\u91CF\u3001\u53C2\u6570\u3001references\u3001outputs \u4E0E overwrite\u3002\u6B64\u6309\u94AE\u53EA\u8868\u8FBE\u201C\u51C6\u5907\u9884\u89C8\u201D\uFF0C\u4E0D\u6784\u6210\u770B\u5230\u9884\u89C8\u540E\u7684\u751F\u4EA7\u786E\u8BA4\uFF1B\u4E0D\u5F97 confirm \u6216 run\u3002\u7528\u6237\u5728\u540E\u7EED\u6D88\u606F\u660E\u786E\u786E\u8BA4\u8FD9\u4EFD\u9884\u89C8\u540E\uFF0C\u624D\u53EF\u8C03\u7528 oh_story_production track_job \u767B\u8BB0\u540C\u4E00\u4E2A\u4EFB\u52A1 ID\uFF0C\u5E76\u8FD0\u884C Provider\u3002${authorityBoundary}`;
656
776
  }
777
+ function nativeBatchPrompt(production, job, candidates) {
778
+ return `/short-drama-produce
657
779
 
658
- .oh-story-mode-tabs button:hover,
659
- .oh-story-editor-tabs button:hover { color: var(--dsw-alias-label-primary); }
660
- .oh-story-mode-tabs button[aria-selected="true"],
661
- .oh-story-editor-tabs button[aria-selected="true"] { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-base); box-shadow: 0 1px 2px rgb(0 0 0 / 8%); font-weight: 500; }
780
+ \u53EA\u51C6\u5907\u5F53\u524D\u6279\u91CF\u751F\u4EA7\u4EFB\u52A1\uFF0C\u4E0D\u8FD0\u884C Provider\u3002
781
+ - \u6279\u6B21\u4EFB\u52A1 ID\uFF1A${job.id}
782
+ - \u4EFB\u52A1\u7C7B\u578B\uFF1A${job.kind === "image" ? "\u6279\u91CF\u5173\u952E\u5E27" : "\u6279\u91CF\u955C\u5934\u89C6\u9891"}
783
+ - \u5EFA\u8BAE adapter \u5951\u7EA6\uFF1A${job.kind === "image" ? "gpt-image-2" : "seedance"}\uFF08\u5B9E\u9645\u914D\u7F6E\u4E0E\u6A21\u578B\u4EE5\u5F53\u524D DSH \u8FD0\u884C\u73AF\u5883\u4E3A\u51C6\uFF09
784
+ - \u521B\u4F5C\u6587\u6863\u76EE\u5F55\uFF1A${production.episodeDirectory}
785
+ - \u8F93\u51FA\u6839\u76EE\u5F55\uFF1A${production.episodeDirectory}/\u5236\u4F5C\u6210\u679C
786
+ - \u6BCF\u4E2A\u8F93\u51FA\u6587\u4EF6\u540D\u5FC5\u987B\u5305\u542B\u5BF9\u5E94\u955C\u5934 ID \u4E0E\u6279\u6B21\u4EFB\u52A1 ID ${job.id}\u3002
662
787
 
663
- .oh-story-file-group,
664
- .oh-story-file-folder { margin: 0; }
665
- .oh-story-file-group > summary,
666
- .oh-story-file-folder > summary {
667
- display: flex;
668
- align-items: center;
669
- gap: 5px;
670
- min-height: 26px;
671
- padding: 0 7px;
672
- border-radius: 6px;
673
- color: var(--os-muted);
674
- cursor: pointer;
675
- list-style: none;
676
- font-size: 11px;
677
- font-weight: 600;
678
- }
679
- .oh-story-file-folder > summary { padding-left: min(calc(7px + var(--oh-story-indent, 0px)), 49px); }
680
- .oh-story-file-group > summary { margin-top: 3px; }
681
- .oh-story-file-group > summary::-webkit-details-marker,
682
- .oh-story-file-folder > summary::-webkit-details-marker { display: none; }
683
- .oh-story-file-group > summary::before,
684
- .oh-story-file-folder > summary::before { width: 8px; content: "\u203A"; transform: rotate(0); transition: transform .12s ease; }
685
- .oh-story-file-group[open] > summary::before,
686
- .oh-story-file-folder[open] > summary::before { transform: rotate(90deg); }
687
- .oh-story-file-group > summary:hover,
688
- .oh-story-file-folder > summary:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-interactive-bg-hover); }
689
- .oh-story-file-group > summary span,
690
- .oh-story-file-folder > summary span { margin-left: auto; color: var(--dsw-alias-label-secondary); font-weight: 400; }
788
+ ${candidates.map((item) => `## ${item.id}
789
+ ${item.prompt}`).join(`
691
790
 
692
- .oh-story-tree nav button {
693
- display: block;
694
- width: 100%;
695
- overflow: hidden;
696
- padding: 6px 8px 6px min(calc(14px + var(--oh-story-indent, 0px)), 56px);
697
- border: 0;
698
- border-radius: 6px;
699
- color: inherit;
700
- background: transparent;
701
- text-align: left;
702
- text-overflow: ellipsis;
703
- white-space: nowrap;
704
- cursor: pointer;
705
- font: inherit;
706
- font-size: 12px;
707
- }
708
- .oh-story-tree nav button:hover { background: var(--dsw-alias-interactive-bg-hover); }
709
- .oh-story-tree nav button[aria-current="page"] { background: var(--dsw-alias-button-ghost-active-fill); color: var(--dsw-alias-label-primary); }
710
- .oh-story-tree nav button[data-agent-target] { box-shadow: inset 2px 0 var(--dsw-alias-state-business-primary); }
791
+ `)}
711
792
 
712
- .oh-story-editor header {
713
- min-height: 44px;
714
- flex: none;
715
- padding: 0 14px;
716
- border-bottom: 1px solid var(--os-border);
793
+ \u628A\u6570\u91CF\u3001\u9010\u9879\u8F93\u51FA\u548C\u6210\u672C\u8FB9\u754C\u5B8C\u6574\u5C55\u793A\u7ED9\u521B\u4F5C\u8005\u3002\u6B64\u6309\u94AE\u53EA\u8868\u8FBE\u201C\u51C6\u5907\u9884\u89C8\u201D\uFF0C\u4E0D\u6784\u6210\u770B\u5230\u9884\u89C8\u540E\u7684\u751F\u4EA7\u786E\u8BA4\uFF1B\u4E0D\u5F97 confirm \u6216 run\u3002\u7528\u6237\u5728\u540E\u7EED\u6D88\u606F\u660E\u786E\u786E\u8BA4\u8FD9\u4EFD\u9884\u89C8\u540E\uFF0C\u624D\u53EF\u8C03\u7528 oh_story_production track_job \u767B\u8BB0\u540C\u4E00\u4E2A\u6279\u6B21\u4EFB\u52A1 ID\uFF0C\u5E76\u8FD0\u884C Provider\u3002${authorityBoundary}`;
717
794
  }
795
+ function nativeCompositionPrompt(production, job, orderedPaths) {
796
+ return `/short-drama-produce
718
797
 
719
- .oh-story-editor-path {
720
- overflow: hidden;
721
- min-width: 0;
722
- flex: 1;
723
- text-overflow: ellipsis;
724
- white-space: nowrap;
725
- font-size: 12px;
726
- color: var(--os-muted);
727
- }
728
- .oh-story-editor-path > span { overflow: hidden; display: block; text-overflow: ellipsis; }
729
- .oh-story-editor-path > strong { display: none; overflow: hidden; text-overflow: ellipsis; font: inherit; }
798
+ \u6267\u884C\u521B\u4F5C\u8005\u5DF2\u660E\u786E\u786E\u8BA4\u7684\u6210\u7247\u5408\u6210\u4EFB\u52A1\u3002
799
+ - \u4EFB\u52A1 ID\uFF1A${job.id}
800
+ - \u5267\u96C6\uFF1A${production.episodeDirectory}
801
+ - \u6309\u4EE5\u4E0B\u987A\u5E8F\u5408\u6210\uFF0C\u4E0D\u5F97\u81EA\u884C\u6362\u5E8F\uFF1A
802
+ ${orderedPaths.map((path, index) => `${String(index + 1)}. ${path}`).join(`
803
+ `)}
804
+ - \u8F93\u51FA\uFF1A${production.episodeDirectory}/\u5236\u4F5C\u6210\u679C/\u6210\u7247-${job.id}.mp4
730
805
 
731
- .oh-story-editor-actions { display: flex; flex: none; align-items: center; gap: 8px; }
732
- .oh-story-editor-tabs {
733
- display: flex;
734
- padding: 2px;
735
- border: 1px solid var(--os-border);
736
- border-radius: 7px;
737
- background: var(--dsw-alias-bg-layer-1);
806
+ \u5148\u9A8C\u8BC1\u8F93\u5165\u5747\u5B58\u5728\u4E14\u53EF\u8BFB\uFF0C\u518D\u4F7F\u7528\u5F53\u524D DSH Preset \u53EF\u89C1\u7684\u5A92\u4F53/\u547D\u4EE4\u5DE5\u5177\u6267\u884C\uFF1B\u97F3\u89C6\u9891\u53C2\u6570\u4E0D\u517C\u5BB9\u65F6\u505A\u660E\u786E\u3001\u53EF\u5BA1\u8BA1\u7684\u6807\u51C6\u5316\u3002\u6240\u6709\u547D\u4EE4\u548C\u5199\u5165\u7EE7\u7EED\u9075\u5B88 DSH \u6743\u9650\u4E0E\u5BA1\u6279\uFF0C\u4E0D\u5F97\u4F2A\u9020\u6210\u529F\u3002`;
738
807
  }
739
- .oh-story-editor-tabs button { min-height: 24px; padding: 0 9px; border-radius: 5px; font-size: 11px; }
740
- .oh-story-save { min-height: 28px; }
741
808
 
742
- .oh-story-editor textarea {
743
- box-sizing: border-box;
744
- width: 100%;
745
- min-height: 0;
746
- flex: 1;
747
- resize: none;
748
- border: 0;
749
- outline: 0;
750
- padding: clamp(20px, 6%, 48px) clamp(22px, 10%, 84px) 48px;
751
- color: var(--dsw-alias-label-primary);
752
- background: transparent;
753
- font: 16px/1.9 ui-serif, "Songti SC", "STSong", Georgia, serif;
809
+ // src/client/production-runtime.ts
810
+ function createPendingJob(input) {
811
+ return {
812
+ id: input.id,
813
+ targetId: input.targetId,
814
+ kind: input.kind,
815
+ status: "pending",
816
+ progress: 0,
817
+ prompt: input.prompt,
818
+ expectedOutputs: Math.max(1, Math.floor(input.expectedOutputs ?? 1)),
819
+ completedOutputs: 0
820
+ };
754
821
  }
755
- .oh-story-editor textarea[data-format="structured"] {
756
- tab-size: 2;
757
- font: 13px/1.65 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
822
+ function selectedVersionForTarget(targetId, versions, selections, kind) {
823
+ let candidates = versions.filter((version) => version.targetId === targetId && (kind === void 0 || version.kind === kind));
824
+ return candidates.find((version) => version.id === selections[targetId]) ?? candidates.at(-1);
758
825
  }
759
-
760
- .oh-story-markdown {
761
- box-sizing: border-box;
762
- width: 100%;
763
- min-height: 0;
764
- flex: 1;
765
- overflow: auto;
766
- padding: clamp(24px, 8%, 56px) clamp(24px, 10%, 92px) 64px;
767
- color: var(--dsw-alias-label-primary);
768
- font: 16px/1.85 ui-serif, "Songti SC", "STSong", Georgia, serif;
769
- overflow-wrap: anywhere;
826
+ function mediaTargetFromPath(path, knownTargets) {
827
+ let segments = path.toLocaleUpperCase().split("/"), filename = segments.at(-1) ?? "";
828
+ return [...knownTargets].sort((left, right) => right.length - left.length).find((target) => {
829
+ let canonical = target.toLocaleUpperCase();
830
+ return segments.includes(canonical) || filename === canonical || filename.startsWith(`${canonical}.`) || filename.startsWith(`${canonical}-`);
831
+ });
770
832
  }
771
- .oh-story-markdown > :first-child { margin-top: 0; }
772
- .oh-story-markdown > :last-child { margin-bottom: 0; }
773
- .oh-story-markdown h1,
774
- .oh-story-markdown h2,
833
+ function mediaVersionMatchesJob(version, jobId) {
834
+ if (version.path === void 0 || jobId.trim() === "") return !1;
835
+ let escaped = jobId.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"), basename = version.path.split("/").at(-1) ?? "";
836
+ return new RegExp(`(?:^|[-_.])${escaped}(?:[-_.]|$)`, "u").test(basename);
837
+ }
838
+ function referencesForTarget(targetId, production, versions, selections, libraryVersions = versions, manualReferences = {}) {
839
+ let shot = production.shots.find((item) => item.id === targetId);
840
+ if (shot === void 0) return [];
841
+ let declared = shot.references.flatMap((id) => {
842
+ let version = selectedVersionForTarget(id, versions, selections, "image");
843
+ return version === void 0 ? [] : [version];
844
+ }), manual = (manualReferences[targetId] ?? []).flatMap((id) => {
845
+ let version = libraryVersions.find((item) => item.id === id && item.kind === "image");
846
+ return version === void 0 ? [] : [version];
847
+ });
848
+ return [...new Map([...declared, ...manual].map((version) => [version.id, version])).values()];
849
+ }
850
+ function queuedItemForJob(jobId, queue) {
851
+ if (jobId.trim() === "") return;
852
+ let escaped = jobId.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"), labelled = new RegExp(`\u4EFB\u52A1\\s*ID\\s*[\uFF1A:]\\s*${escaped}(?:[\\s.,;\u3001\u3002]|$)`, "u");
853
+ return queue.find((item) => labelled.test(item.preview));
854
+ }
855
+ function activeProductionJobId(jobs, queue, sessionRunning) {
856
+ if (sessionRunning)
857
+ return [...jobs].reverse().find((job) => (job.status === "awaiting_confirmation" || job.status === "pending" || job.status === "running") && queuedItemForJob(job.id, queue) === void 0)?.id;
858
+ }
859
+ function reconcileProductionJobs(jobs, queue, sessionRunning, versions) {
860
+ let latestPending = [...jobs].reverse().find((job) => job.status === "pending" && queuedItemForJob(job.id, queue) === void 0);
861
+ return jobs.map((job) => {
862
+ let queued = queuedItemForJob(job.id, queue) !== void 0, outputs = versions.filter((version) => mediaVersionMatchesJob(version, job.id));
863
+ return outputs.length >= job.expectedOutputs ? {
864
+ ...job,
865
+ status: "succeeded",
866
+ progress: 100,
867
+ completedOutputs: outputs.length,
868
+ output: outputs[0],
869
+ error: void 0
870
+ } : job.status === "canceled" || job.status === "succeeded" || job.status === "awaiting_confirmation" && outputs.length === 0 ? job : job.status === "running" && queued ? { ...job, status: "pending", progress: 0 } : job.status === "pending" && sessionRunning && !queued && job === latestPending ? { ...job, status: "running", progress: Math.max(10, job.progress), error: void 0 } : !sessionRunning && !queued && (job.status === "running" || job.status === "dispatched_unknown") ? {
871
+ ...job,
872
+ status: "dispatched_unknown",
873
+ progress: Math.round(outputs.length / job.expectedOutputs * 100),
874
+ completedOutputs: outputs.length,
875
+ error: outputs.length === 0 ? "DSH Turn \u5DF2\u7ED3\u675F\uFF0C\u5C1A\u672A\u53D1\u73B0\u5173\u8054\u6210\u679C\u3002\u4EFB\u52A1\u53EF\u80FD\u5DF2\u6D3E\u53D1\uFF0C\u8BF7\u5148\u5237\u65B0\u6210\u679C\uFF0C\u907F\u514D\u91CD\u590D\u8BA1\u8D39\u3002" : `DSH Turn \u5DF2\u7ED3\u675F\uFF0C\u5DF2\u53D1\u73B0 ${String(outputs.length)}/${String(job.expectedOutputs)} \u9879\u6210\u679C\uFF1B\u8BF7\u5237\u65B0\u6838\u5BF9\u5269\u4F59\u8F93\u51FA\u3002`
876
+ } : outputs.length > 0 ? {
877
+ ...job,
878
+ status: job.status === "failed" ? "failed" : "running",
879
+ progress: Math.max(10, Math.round(outputs.length / job.expectedOutputs * 100)),
880
+ completedOutputs: outputs.length,
881
+ error: job.status === "failed" ? job.error : void 0
882
+ } : job;
883
+ });
884
+ }
885
+ function reconcileSequence(shotIds, current, versions, selections) {
886
+ let shotSet = new Set(shotIds), preserved = current.filter((item) => shotSet.has(item.shotId)), present = new Set(preserved.map((item) => item.shotId)), appended = shotIds.filter((shotId) => !present.has(shotId)).map((shotId) => ({ shotId }));
887
+ return [...preserved, ...appended].map((item) => ({
888
+ shotId: item.shotId,
889
+ versionId: selectedVersionForTarget(item.shotId, versions, selections, "video")?.id
890
+ }));
891
+ }
892
+ function sequenceIssues(sequence, versions) {
893
+ let versionById = new Map(versions.map((version) => [version.id, version])), issues = [];
894
+ for (let item of sequence) {
895
+ let version = item.versionId === void 0 ? void 0 : versionById.get(item.versionId);
896
+ version === void 0 || version.kind !== "video" ? issues.push(`${item.shotId} \u7F3A\u5C11\u5DF2\u9009\u89C6\u9891\u7248\u672C`) : version.path === void 0 && issues.push(`${item.shotId} \u7684\u89C6\u9891\u6CA1\u6709\u53EF\u4F9B DSH \u8BFB\u53D6\u7684\u5DE5\u4F5C\u533A\u8DEF\u5F84`);
897
+ }
898
+ return issues;
899
+ }
900
+ function reorderSequence(sequence, source, target) {
901
+ if (!Number.isInteger(source) || !Number.isInteger(target)) return [...sequence];
902
+ if (source < 0 || target < 0 || source >= sequence.length || target >= sequence.length || source === target) return [...sequence];
903
+ let next = [...sequence], [item] = next.splice(source, 1);
904
+ return item !== void 0 && next.splice(target, 0, item), next;
905
+ }
906
+
907
+ // src/client/drama-production-view.tsx
908
+ var import_jsx_runtime3 = require("react/jsx-runtime"), SECTION_LABELS = { shots: "\u955C\u5934", assets: "\u7D20\u6750", tasks: "\u4EFB\u52A1", sequence: "\u6210\u7247", canvas: "\u753B\u5E03" }, SECTION_ORDER = Object.keys(SECTION_LABELS), STATUS_LABELS = { awaiting_confirmation: "\u7B49\u5F85\u786E\u8BA4", pending: "\u5DF2\u63D0\u4EA4", running: "DSH \u6267\u884C\u4E2D", dispatched_unknown: "\u5F85\u6838\u5BF9", succeeded: "\u5DF2\u5B8C\u6210", failed: "\u5931\u8D25", canceled: "\u5DF2\u53D6\u6D88" }, ASSET_KIND_LABEL = { character: "\u4EBA\u7269", scene: "\u573A\u666F", prop: "\u9053\u5177", state: "\u72B6\u6001", unknown: "\u8BBE\u5B9A" }, JOB_KIND_LABEL = { image: "\u56FE\u7247", video: "\u89C6\u9891", composition: "\u6210\u7247" };
909
+ function handleSectionKey(event, current, onChange) {
910
+ let index = SECTION_ORDER.indexOf(current), next = event.key === "Home" ? 0 : event.key === "End" ? SECTION_ORDER.length - 1 : event.key === "ArrowRight" ? (index + 1) % SECTION_ORDER.length : event.key === "ArrowLeft" ? (index - 1 + SECTION_ORDER.length) % SECTION_ORDER.length : void 0;
911
+ next !== void 0 && (event.preventDefault(), onChange(SECTION_ORDER[next]), event.currentTarget.parentElement?.querySelectorAll("[role='tab']")[next]?.focus());
912
+ }
913
+ function DramaProductionView(props) {
914
+ let [notice, setNotice] = (0, import_react.useState)(), protocolErrors = props.production.diagnostics.filter((item) => item.severity === "error").length, jobsRef = (0, import_react.useRef)(props.jobs), commitJobs = (0, import_react.useCallback)((next) => {
915
+ jobsRef.current = next, props.onJobsChange(next);
916
+ }, [props.onJobsChange]);
917
+ (0, import_react.useEffect)(() => {
918
+ jobsRef.current = props.jobs;
919
+ }, [props.jobs]), (0, import_react.useEffect)(() => {
920
+ let next = reconcileSequence(props.production.shots.map((shot) => shot.id), props.sequence, props.versions, props.selections);
921
+ JSON.stringify(next) !== JSON.stringify(props.sequence) && props.onSequenceChange(next);
922
+ }, [props.production.shots, props.selections, props.sequence, props.versions]), (0, import_react.useEffect)(() => {
923
+ let next = reconcileProductionJobs(props.jobs, props.queue, props.sessionRunning, props.versions);
924
+ JSON.stringify(next) !== JSON.stringify(props.jobs) && commitJobs(next);
925
+ }, [commitJobs, props.jobs, props.queue, props.sessionRunning, props.versions]);
926
+ let dispatchJob = async (job, references = []) => {
927
+ commitJobs([...jobsRef.current, { ...job, status: "awaiting_confirmation" }]), props.onSectionChange("tasks");
928
+ try {
929
+ await props.onDispatchPrompt(nativeProductionPrompt(props.production, job, references)), setNotice(`${job.targetId} \u6B63\u5728\u51C6\u5907\u5B8C\u6574\u9884\u68C0\uFF1B\u8BF7\u5728 Chat \u67E5\u770B\u5E76\u660E\u786E\u786E\u8BA4\u540E\u518D\u751F\u4EA7\u3002`);
930
+ } catch (error) {
931
+ commitJobs(jobsRef.current.map((item) => item.id === job.id ? { ...item, status: "failed", error: error instanceof Error ? error.message : String(error) } : item));
932
+ }
933
+ }, createJob = async (targetId, kind, prompt) => {
934
+ if (prompt.trim() === "") {
935
+ setNotice(`${targetId} \u6CA1\u6709\u53EF\u6295\u4EA7\u63D0\u793A\u8BCD\u3002`);
936
+ return;
937
+ }
938
+ let job = createPendingJob({ id: crypto.randomUUID(), targetId, kind, prompt });
939
+ await dispatchJob(job, kind === "video" ? referencesForTarget(targetId, props.production, props.versions, props.selections, props.libraryVersions, props.manualReferences) : []);
940
+ }, createBatch = async (kind) => {
941
+ let candidates = props.production.shots.flatMap((shot) => {
942
+ let prompt = kind === "image" ? shot.keyframePrompt : shot.motion?.prompt;
943
+ return prompt === void 0 ? [] : [{ id: shot.id, prompt }];
944
+ });
945
+ if (candidates.length === 0) {
946
+ setNotice(kind === "image" ? "\u6CA1\u6709\u53EF\u6295\u4EA7\u7684\u5173\u952E\u5E27\u63D0\u793A\u8BCD\u3002" : "\u6CA1\u6709\u53EF\u6295\u4EA7\u7684\u89C6\u9891\u63D0\u793A\u8BCD\u3002");
947
+ return;
948
+ }
949
+ let job = createPendingJob({ id: crypto.randomUUID(), targetId: kind === "image" ? "BATCH-KEYFRAMES" : "BATCH-VIDEOS", kind, prompt: candidates.map((item) => `${item.id}
950
+ ${item.prompt}`).join(`
951
+
952
+ `), expectedOutputs: candidates.length });
953
+ commitJobs([...jobsRef.current, { ...job, status: "awaiting_confirmation" }]), props.onSectionChange("tasks");
954
+ try {
955
+ await props.onDispatchPrompt(nativeBatchPrompt(props.production, job, candidates)), setNotice(`${String(candidates.length)} \u4E2A\u955C\u5934\u6B63\u5728\u51C6\u5907\u540C\u4E00\u6279\u6B21\u9884\u68C0\uFF1B\u8BF7\u5728 Chat \u6838\u5BF9\u540E\u660E\u786E\u786E\u8BA4\u3002`);
956
+ } catch (error) {
957
+ commitJobs(jobsRef.current.map((item) => item.id === job.id ? { ...item, status: "failed", error: error instanceof Error ? error.message : String(error) } : item));
958
+ }
959
+ }, dispatchComposition = async (job) => {
960
+ let versionById = new Map(props.versions.map((version) => [version.id, version])), ordered = props.sequence.flatMap((item) => {
961
+ let version = item.versionId === void 0 ? void 0 : versionById.get(item.versionId);
962
+ return version === void 0 ? [] : [version.path ?? version.url];
963
+ });
964
+ commitJobs([...jobsRef.current, job]), props.onSectionChange("tasks");
965
+ try {
966
+ await props.onDispatchPrompt(nativeCompositionPrompt(props.production, job, ordered)), setNotice("\u6210\u7247\u4EFB\u52A1\u5DF2\u8FDB\u5165 DSH \u539F\u751F\u961F\u5217\uFF1B\u6587\u4EF6\u3001FFmpeg \u548C\u5199\u5165\u7EE7\u7EED\u53D7 DSH \u6743\u9650\u4E0E\u5BA1\u6279\u63A7\u5236\u3002");
967
+ } catch (error) {
968
+ commitJobs(jobsRef.current.map((item) => item.id === job.id ? { ...item, status: "failed", error: error instanceof Error ? error.message : String(error) } : item));
969
+ }
970
+ }, cancelJob = async (job) => {
971
+ try {
972
+ await props.onCancelTurn(), commitJobs(jobsRef.current.map((item) => item.id === job.id ? { ...item, status: "canceled", progress: 0 } : item)), setNotice("\u5DF2\u8BF7\u6C42\u505C\u6B62\u5F53\u524D DSH Turn\uFF1BDSH Queue \u4E2D\u7684\u5176\u4ED6\u4EFB\u52A1\u4F1A\u4FDD\u7559\u3002");
973
+ } catch (error) {
974
+ setNotice(error instanceof Error ? error.message : String(error));
975
+ }
976
+ }, removeQueuedJob = async (job, itemId) => {
977
+ try {
978
+ await props.onRemoveQueued(itemId), commitJobs(jobsRef.current.map((item) => item.id === job.id ? { ...item, status: "canceled", progress: 0 } : item)), setNotice(`${job.targetId} \u5DF2\u4ECE DSH Queue \u79FB\u9664\u3002`);
979
+ } catch (error) {
980
+ setNotice(error instanceof Error ? error.message : String(error));
981
+ }
982
+ }, composeSequence = () => {
983
+ let issues = sequenceIssues(props.sequence, props.versions);
984
+ if (issues.length > 0) {
985
+ setNotice(issues[0]);
986
+ return;
987
+ }
988
+ dispatchComposition(createPendingJob({ id: crypto.randomUUID(), targetId: props.production.episodeDirectory, kind: "composition", prompt: "\u6309\u6210\u7247\u987A\u5E8F\u5408\u6210" }));
989
+ };
990
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-production", children: [
991
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-production-bar", children: [
992
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-production-tabs", role: "tablist", "aria-label": "\u77ED\u5267\u751F\u4EA7\u89C6\u56FE", children: SECTION_ORDER.map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", role: "tab", tabIndex: props.section === item ? 0 : -1, "aria-selected": props.section === item, onKeyDown: (event) => {
993
+ handleSectionKey(event, item, props.onSectionChange);
994
+ }, onClick: () => {
995
+ props.onSectionChange(item);
996
+ }, children: SECTION_LABELS[item] }, item)) }),
997
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-production-meta", children: [
998
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "oh-story-production-summary", children: [
999
+ props.production.shots.length,
1000
+ " \u955C \xB7 ",
1001
+ props.production.assets.length + props.production.visualAssets.length,
1002
+ " \u7D20\u6750 \xB7 ",
1003
+ props.jobs.filter((job) => job.status === "awaiting_confirmation" || job.status === "running" || job.status === "pending").length,
1004
+ " \u4EFB\u52A1"
1005
+ ] }),
1006
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: props.onRefresh, children: "\u5237\u65B0" })
1007
+ ] })
1008
+ ] }),
1009
+ notice !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-production-notice", role: "status", children: [
1010
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: notice }),
1011
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", "aria-label": "\u5173\u95ED\u63D0\u793A", onClick: () => {
1012
+ setNotice(void 0);
1013
+ }, children: "\xD7" })
1014
+ ] }),
1015
+ props.production.diagnostics.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("details", { className: "oh-story-production-diagnostics", children: [
1016
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("summary", { children: protocolErrors > 0 ? `${String(protocolErrors)} \u4E2A\u534F\u8BAE\u9519\u8BEF` : `${String(props.production.diagnostics.length)} \u4E2A\u683C\u5F0F\u63D0\u9192` }),
1017
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { children: props.production.diagnostics.slice(0, 8).map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { "data-severity": item.severity, children: [
1018
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("button", { type: "button", onClick: () => {
1019
+ props.onNavigate({ path: item.path, offset: item.offset, id: item.targetId ?? item.code });
1020
+ }, children: [
1021
+ item.path.split("/").at(-1),
1022
+ ":",
1023
+ item.line
1024
+ ] }),
1025
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: item.message })
1026
+ ] }, `${item.path}:${String(item.offset)}:${item.code}`)) }),
1027
+ props.production.diagnostics.length > 8 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { children: [
1028
+ "\u53E6\u6709 ",
1029
+ props.production.diagnostics.length - 8,
1030
+ " \u9879\uFF0C\u8BF7\u6309\u6587\u6863\u4F4D\u7F6E\u4FEE\u590D\u3002"
1031
+ ] })
1032
+ ] }),
1033
+ props.section === "shots" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ShotBoard, { ...props, onCreateJob: createJob, onBatch: createBatch }),
1034
+ props.section === "assets" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AssetBoard, { ...props, onCreateJob: createJob }),
1035
+ props.section === "tasks" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TaskBoard, { jobs: props.jobs, queue: props.queue, sessionRunning: props.sessionRunning, onCancel: cancelJob, onRemoveQueued: removeQueuedJob }),
1036
+ props.section === "sequence" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SequenceBoard, { ...props, onCompose: composeSequence }),
1037
+ props.section === "canvas" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ProductionCanvas, { ...props })
1038
+ ] });
1039
+ }
1040
+ function ShotBoard(props) {
1041
+ let selectedRef = useScrollIntoView(props.selectedId);
1042
+ return props.production.shots.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("section", { className: "oh-story-shot-board", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MissingDocument, { document: `${props.production.episodeDirectory}/\u5206\u955C.md`, documentPaths: props.production.documentPaths, what: "\u955C\u5934", skill: "/short-drama-storyboard", onNavigate: props.onNavigate }) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "oh-story-shot-board", children: [
1043
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-production-actions", children: [
1044
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: () => {
1045
+ props.onBatch("image");
1046
+ }, children: "\u51C6\u5907\u6279\u91CF\u5173\u952E\u5E27" }),
1047
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: () => {
1048
+ props.onBatch("video");
1049
+ }, children: "\u51C6\u5907\u6279\u91CF\u89C6\u9891" })
1050
+ ] }),
1051
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-shot-grid", children: props.production.shots.map((shot) => {
1052
+ let completeness = productionCompleteness(shot), versions = props.versions.filter((version) => version.targetId === shot.id), selected = selectedVersionForTarget(shot.id, props.versions, props.selections, "image") ?? selectedVersionForTarget(shot.id, props.versions, props.selections, "video");
1053
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("article", { className: "oh-story-shot-card", role: "button", tabIndex: 0, "aria-pressed": props.selectedId === shot.id, "aria-label": `\u9009\u4E2D\u955C\u5934 ${shot.id} ${shot.title}`, ref: props.selectedId === shot.id ? selectedRef : void 0, "data-selected": props.selectedId === shot.id || void 0, onKeyDown: (event) => {
1054
+ (event.key === "Enter" || event.key === " ") && (event.preventDefault(), props.onSelect(shot.id));
1055
+ }, onClick: () => {
1056
+ props.onSelect(shot.id);
1057
+ }, children: [
1058
+ selected === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-shot-placeholder", children: [
1059
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: shot.id.split("-").at(-1) }),
1060
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "\u7B49\u5F85\u5173\u952E\u5E27\u6210\u679C" })
1061
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MediaPreview, { version: selected }),
1062
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { children: [
1063
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: (event) => {
1064
+ event.stopPropagation(), props.onNavigate({ path: shot.path, offset: shot.offset, id: shot.id });
1065
+ }, children: shot.id }),
1066
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: shot.durationSeconds === void 0 ? "\u2014" : `${String(shot.durationSeconds)}s` })
1067
+ ] }),
1068
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { children: shot.title }),
1069
+ shot.shotSpec !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: shot.shotSpec }),
1070
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("dl", { children: [
1071
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dt", { children: "\u8D77" }),
1072
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dd", { children: shot.start ?? "\u672A\u586B\u5199" }),
1073
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dt", { children: "\u7EC8" }),
1074
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dd", { children: shot.end ?? "\u672A\u586B\u5199" })
1075
+ ] }),
1076
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-shot-status", children: [
1077
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ReadinessBadge, { label: "\u5173\u952E\u5E27", ready: completeness.keyframe }),
1078
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ReadinessBadge, { label: "\u8FD0\u52A8", ready: completeness.motion }),
1079
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ReadinessBadge, { label: "\u53C2\u8003", ready: completeness.references }),
1080
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { children: [
1081
+ versions.length,
1082
+ " \u7248\u672C"
1083
+ ] })
1084
+ ] }),
1085
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-reference-links", children: [
1086
+ shot.source !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ReferenceButton, { id: shot.source, production: props.production, onNavigate: props.onNavigate }),
1087
+ shot.references.map((id) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ReferenceButton, { id, production: props.production, onNavigate: props.onNavigate }, id))
1088
+ ] }),
1089
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-card-actions", children: [
1090
+ shot.keyframePrompt !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: (event) => {
1091
+ event.stopPropagation(), props.onCreateJob(shot.id, "image", shot.keyframePrompt ?? "");
1092
+ }, children: "\u51C6\u5907\u5173\u952E\u5E27" }),
1093
+ shot.motion?.prompt !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: (event) => {
1094
+ event.stopPropagation(), props.onCreateJob(shot.id, "video", shot.motion?.prompt ?? "");
1095
+ }, children: "\u51C6\u5907\u89C6\u9891" })
1096
+ ] }),
1097
+ versions.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(VersionStrip, { targetId: shot.id, versions, selections: props.selections, onSelectionsChange: props.onSelectionsChange })
1098
+ ] }, shot.id);
1099
+ }) })
1100
+ ] });
1101
+ }
1102
+ function AssetBoard(props) {
1103
+ let [query, setQuery] = (0, import_react.useState)(""), [kind, setKind] = (0, import_react.useState)("all"), assets = [...props.production.assets, ...props.production.visualAssets.filter((visual) => !props.production.assets.some((asset) => asset.title === visual.title))], needle = query.trim().toLocaleLowerCase(), library = props.libraryVersions.filter((version) => (kind === "all" || version.kind === kind) && (needle === "" || `${version.targetId} ${version.path ?? ""}`.toLocaleLowerCase().includes(needle))), selectedRef = useScrollIntoView(props.selectedId), referenceTarget = props.selectedId?.startsWith("SHOT-") === !0 ? props.selectedId : void 0, toggleReference = (versionId) => {
1104
+ if (referenceTarget === void 0) return;
1105
+ let current = props.manualReferences[referenceTarget] ?? [], next = current.includes(versionId) ? current.filter((id) => id !== versionId) : [...current, versionId], mutable = Object.fromEntries(Object.entries(props.manualReferences).map(([target, ids]) => [target, [...ids]]));
1106
+ props.onManualReferencesChange({ ...mutable, [referenceTarget]: next });
1107
+ };
1108
+ return assets.length === 0 && props.libraryVersions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("section", { className: "oh-story-assets", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MissingDocument, { document: `${props.production.episodeDirectory}/\u56FE\u7247\u63D0\u793A\u8BCD.md`, documentPaths: props.production.documentPaths, what: "\u7D20\u6750", skill: "/short-drama-image-prompts", onNavigate: props.onNavigate }) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "oh-story-assets", children: [
1109
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-asset-grid", children: assets.map((asset) => {
1110
+ let prompt = "prompt" in asset ? asset.prompt : asset.description, versions = props.versions.filter((version) => version.targetId === asset.id), selected = selectedVersionForTarget(asset.id, props.versions, props.selections, "image");
1111
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("article", { className: "oh-story-asset-card", ref: props.selectedId === asset.id ? selectedRef : void 0, "data-selected": props.selectedId === asset.id || void 0, children: [
1112
+ selected === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-asset-placeholder", children: asset.kind === "character" ? "\u4EBA" : asset.kind === "scene" ? "\u666F" : asset.kind === "prop" ? "\u7269" : "\u8BBE" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MediaPreview, { version: selected }),
1113
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1114
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("small", { children: ASSET_KIND_LABEL[asset.kind] }),
1115
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { children: asset.title }),
1116
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: () => {
1117
+ props.onNavigate({ path: asset.path, offset: asset.offset, id: asset.id });
1118
+ }, children: asset.id })
1119
+ ] }),
1120
+ prompt !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "oh-story-asset-description", children: prompt }),
1121
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-card-actions", children: prompt !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: () => {
1122
+ props.onCreateJob(asset.id, "image", prompt);
1123
+ }, children: "\u51C6\u5907\u7D20\u6750" }) }),
1124
+ versions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(VersionStrip, { targetId: asset.id, versions, selections: props.selections, onSelectionsChange: props.onSelectionsChange })
1125
+ ] }, asset.id);
1126
+ }) }),
1127
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-media-library", children: [
1128
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { children: [
1129
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1130
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "\u9879\u76EE\u5A92\u4F53\u5E93" }),
1131
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { children: [
1132
+ library.length,
1133
+ "/",
1134
+ props.libraryVersions.length,
1135
+ " \u9879 \xB7 \u53EF\u8DE8\u96C6\u590D\u7528"
1136
+ ] })
1137
+ ] }),
1138
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1139
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { "aria-label": "\u641C\u7D22\u9879\u76EE\u5A92\u4F53", value: query, placeholder: "\u641C\u7D22 ID \u6216\u8DEF\u5F84", onChange: (event) => {
1140
+ setQuery(event.target.value);
1141
+ } }),
1142
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("select", { "aria-label": "\u7B5B\u9009\u5A92\u4F53\u7C7B\u578B", value: kind, onChange: (event) => {
1143
+ setKind(event.target.value);
1144
+ }, children: [
1145
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "all", children: "\u5168\u90E8" }),
1146
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "image", children: "\u56FE\u7247" }),
1147
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "video", children: "\u89C6\u9891" })
1148
+ ] })
1149
+ ] })
1150
+ ] }),
1151
+ referenceTarget === void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "oh-story-projection-note", children: "\u5148\u5728\u955C\u5934\u9875\u9009\u4E2D\u4E00\u4E2A\u955C\u5934\uFF0C\u518D\u56DE\u5230\u8FD9\u91CC\u628A\u5DF2\u6709\u56FE\u7247\u8BBE\u4E3A\u8BE5\u955C\u5934\u7684\u989D\u5916\u53C2\u8003\u3002" }),
1152
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-media-library-grid", children: library.map((version) => {
1153
+ let selected = referenceTarget !== void 0 && (props.manualReferences[referenceTarget] ?? []).includes(version.id);
1154
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("article", { children: [
1155
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MediaPreview, { version }),
1156
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: version.targetId }),
1157
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { title: version.path, children: version.path }),
1158
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("footer", { children: [
1159
+ version.path !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: () => {
1160
+ props.onOpenMedia(version.path);
1161
+ }, children: "\u6253\u5F00\u6587\u4EF6" }),
1162
+ referenceTarget !== void 0 && version.kind === "image" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", "aria-pressed": selected, "aria-label": `${selected ? "\u53D6\u6D88" : "\u8BBE\u4E3A"} ${referenceTarget} \u53C2\u8003 ${version.targetId}`, onClick: () => {
1163
+ toggleReference(version.id);
1164
+ }, children: selected ? "\u5DF2\u5F15\u7528" : "\u4F5C\u4E3A\u53C2\u8003" })
1165
+ ] })
1166
+ ] }, version.id);
1167
+ }) })
1168
+ ] })
1169
+ ] });
1170
+ }
1171
+ function TaskBoard({ jobs, queue, sessionRunning, onCancel, onRemoveQueued }) {
1172
+ let activeJobId = activeProductionJobId(jobs, queue, sessionRunning);
1173
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "oh-story-task-board", children: [
1174
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-projection-note", children: "\u56FE\u7247\u4E0E\u89C6\u9891\u5148\u9884\u68C0\u3001\u540E\u786E\u8BA4\u3002\u5185\u7F6E\u5951\u7EA6\u652F\u6301 GPT Image 2 / Seedance\uFF1B\u5B9E\u9645\u8D26\u53F7\u3001\u6A21\u578B\u4E0E\u53EF\u7528\u6027\u7531\u5F53\u524D DSH \u8FD0\u884C\u73AF\u5883\u51B3\u5B9A\u3002" }),
1175
+ jobs.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-production-empty", children: "\u8FD8\u6CA1\u6709\u751F\u4EA7\u4EFB\u52A1\u3002\u53EF\u4ECE\u955C\u5934\u6216\u7D20\u6750\u9875\u63D0\u4EA4\u5355\u4E2A\u6216\u6279\u91CF\u4EFB\u52A1\u3002" }) : [...jobs].reverse().map((job) => {
1176
+ let queued = queuedItemForJob(job.id, queue), displayStatus = queued === void 0 ? STATUS_LABELS[job.status] : "DSH Queue";
1177
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("article", { "data-job-id": job.id, "data-status": job.status, children: [
1178
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { children: [
1179
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { title: job.targetId, children: job.targetId }),
1180
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: JOB_KIND_LABEL[job.kind] }),
1181
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: displayStatus })
1182
+ ] }),
1183
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-task-progress", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("i", { style: { width: `${String(job.progress)}%` } }) }),
1184
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("details", { children: [
1185
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("summary", { children: "\u67E5\u770B\u6295\u4EA7\u63D0\u793A\u8BCD" }),
1186
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: job.prompt })
1187
+ ] }),
1188
+ job.expectedOutputs > 1 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("small", { children: [
1189
+ job.completedOutputs,
1190
+ "/",
1191
+ job.expectedOutputs,
1192
+ " \u9879\u6210\u679C"
1193
+ ] }),
1194
+ job.error !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-error", children: job.error }),
1195
+ job.output !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MediaPreview, { version: job.output }),
1196
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("footer", { children: [
1197
+ queued !== void 0 && (job.status === "awaiting_confirmation" || job.status === "pending" || job.status === "running") && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: () => {
1198
+ onRemoveQueued(job, queued.id);
1199
+ }, children: "\u4ECE DSH Queue \u79FB\u9664" }),
1200
+ activeJobId === job.id && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: () => {
1201
+ onCancel(job);
1202
+ }, children: "\u505C\u6B62\u5F53\u524D DSH Turn" })
1203
+ ] })
1204
+ ] }, job.id);
1205
+ })
1206
+ ] });
1207
+ }
1208
+ function SequenceBoard(props) {
1209
+ let issues = sequenceIssues(props.sequence, props.versions), versionById = new Map(props.versions.map((version) => [version.id, version])), move = (index, delta) => {
1210
+ let source = props.sequence[index], target = props.sequence[index + delta];
1211
+ source !== void 0 && target !== void 0 && props.onSequenceChange(reorderSequence(props.sequence, index, index + delta));
1212
+ };
1213
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "oh-story-sequence", children: [
1214
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-sequence-summary", children: [
1215
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("strong", { children: [
1216
+ props.sequence.length,
1217
+ " \u4E2A\u955C\u5934"
1218
+ ] }),
1219
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: props.sequence.length === 0 ? "\u8FD8\u6CA1\u6709\u955C\u5934" : issues.length === 0 ? "\u5DF2\u53EF\u5408\u6210" : `${String(issues.length)} \u4E2A\u963B\u585E\u9879` }),
1220
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", disabled: issues.length > 0 || props.sequence.length < 2, onClick: props.onCompose, children: "\u5408\u6210\u6210\u7247" })
1221
+ ] }),
1222
+ issues.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("ul", { className: "oh-story-sequence-issues", children: [
1223
+ issues.slice(0, 3).map((issue) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { children: issue }, issue)),
1224
+ issues.length > 3 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { children: [
1225
+ "\u53E6\u6709 ",
1226
+ issues.length - 3,
1227
+ " \u4E2A\u963B\u585E\u9879\uFF0C\u8BF7\u5728\u4E0B\u65B9\u955C\u5934\u884C\u8865\u9F50\u89C6\u9891\u3002"
1228
+ ] })
1229
+ ] }),
1230
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ol", { children: props.sequence.map((item, index) => {
1231
+ let version = item.versionId === void 0 ? void 0 : versionById.get(item.versionId);
1232
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { children: [
1233
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: String(index + 1).padStart(2, "0") }),
1234
+ version === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-sequence-missing", children: "\u7F3A\u5C11\u89C6\u9891" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MediaPreview, { version, interactive: !1 }),
1235
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: item.shotId }),
1236
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1237
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", "aria-label": `\u4E0A\u79FB ${item.shotId}`, disabled: index === 0, onClick: () => {
1238
+ move(index, -1);
1239
+ }, children: "\u2191" }),
1240
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", "aria-label": `\u4E0B\u79FB ${item.shotId}`, disabled: index === props.sequence.length - 1, onClick: () => {
1241
+ move(index, 1);
1242
+ }, children: "\u2193" })
1243
+ ] })
1244
+ ] }, item.shotId);
1245
+ }) })
1246
+ ] });
1247
+ }
1248
+ function ProductionCanvas(props) {
1249
+ let nodes = (0, import_react.useMemo)(() => {
1250
+ let assets = [...props.production.assets, ...props.production.visualAssets].map((asset, index) => ({ id: asset.id, label: asset.title, type: "asset", initial: { x: 80, y: 80 + index * 150 } })), shots = props.production.shots.map((shot, index) => ({ id: shot.id, label: shot.title, type: "shot", initial: { x: 640, y: 80 + index * 180 } }));
1251
+ return [...assets, ...shots];
1252
+ }, [props.production.assets, props.production.shots, props.production.visualAssets]), positions = Object.fromEntries(nodes.map((node) => [node.id, props.canvas[node.id] ?? node.initial])), startDrag = (event, id) => {
1253
+ event.currentTarget.setPointerCapture(event.pointerId);
1254
+ let origin = positions[id] ?? { x: 0, y: 0 }, start = { x: event.clientX, y: event.clientY }, move = (moveEvent) => {
1255
+ props.onCanvasChange({ ...props.canvas, [id]: { x: origin.x + (moveEvent.clientX - start.x) / props.zoom, y: origin.y + (moveEvent.clientY - start.y) / props.zoom } });
1256
+ }, end = () => {
1257
+ globalThis.removeEventListener("pointermove", move), globalThis.removeEventListener("pointerup", end);
1258
+ };
1259
+ globalThis.addEventListener("pointermove", move), globalThis.addEventListener("pointerup", end);
1260
+ }, moveNode = (id, x, y) => {
1261
+ let origin = positions[id] ?? { x: 0, y: 0 };
1262
+ props.onCanvasChange({ ...props.canvas, [id]: { x: origin.x + x, y: origin.y + y } });
1263
+ }, connections = props.production.shots.flatMap((shot) => shot.references.map((reference) => [reference, shot.id]));
1264
+ return nodes.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("section", { className: "oh-story-canvas-shell", "aria-label": "\u77ED\u5267\u7D20\u6750\u4E0E\u955C\u5934\u5173\u7CFB\u753B\u5E03", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MissingDocument, { document: `${props.production.episodeDirectory}/\u5206\u955C.md`, documentPaths: props.production.documentPaths, what: "\u5173\u7CFB", skill: "/short-drama-storyboard", onNavigate: props.onNavigate }) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "oh-story-canvas-shell", "aria-label": "\u77ED\u5267\u7D20\u6750\u4E0E\u955C\u5934\u5173\u7CFB\u753B\u5E03", children: [
1265
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-projection-note", children: "\u6587\u6863\u5173\u7CFB \xB7 \u5E03\u5C40\u4EC5\u4FDD\u5B58\u5728\u5F53\u524D DSH Session" }),
1266
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-canvas-controls", children: [
1267
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", "aria-label": "\u7F29\u5C0F\u753B\u5E03", onClick: () => {
1268
+ props.onZoomChange(Math.max(0.5, props.zoom - 0.1));
1269
+ }, children: "\u2212" }),
1270
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { children: [
1271
+ Math.round(props.zoom * 100),
1272
+ "%"
1273
+ ] }),
1274
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", "aria-label": "\u653E\u5927\u753B\u5E03", onClick: () => {
1275
+ props.onZoomChange(Math.min(1.8, props.zoom + 0.1));
1276
+ }, children: "\uFF0B" }),
1277
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: () => {
1278
+ props.onCanvasChange({}), props.onZoomChange(0.65);
1279
+ }, children: "\u590D\u4F4D" })
1280
+ ] }),
1281
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-canvas-viewport", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-canvas", style: { transform: `scale(${String(props.zoom)})` }, children: [
1282
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { "aria-hidden": "true", children: connections.map(([from, to]) => {
1283
+ let a = positions[from], b = positions[to];
1284
+ return a === void 0 || b === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { "data-active": to === props.selectedId || void 0, d: `M ${String(a.x + 180)} ${String(a.y + 50)} C ${String(a.x + 360)} ${String(a.y + 50)}, ${String(b.x - 180)} ${String(b.y + 50)}, ${String(b.x)} ${String(b.y + 50)}` }, `${from}:${to}`);
1285
+ }) }),
1286
+ nodes.map((node) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("article", { tabIndex: 0, "aria-label": `${node.type === "asset" ? "\u7D20\u6750" : "\u955C\u5934"} ${node.label}`, "data-node-type": node.type, "data-selected": node.id === props.selectedId || void 0, style: { left: positions[node.id]?.x, top: positions[node.id]?.y }, onKeyDown: (event) => {
1287
+ let step = event.shiftKey ? 40 : 10, delta = event.key === "ArrowLeft" ? [-step, 0] : event.key === "ArrowRight" ? [step, 0] : event.key === "ArrowUp" ? [0, -step] : event.key === "ArrowDown" ? [0, step] : void 0;
1288
+ delta !== void 0 && (event.preventDefault(), moveNode(node.id, delta[0], delta[1]));
1289
+ }, onPointerDown: (event) => {
1290
+ startDrag(event, node.id);
1291
+ }, onDoubleClick: () => {
1292
+ let target = props.production.targets.get(node.id);
1293
+ target !== void 0 && props.onNavigate(target);
1294
+ }, children: [
1295
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("small", { children: node.type === "asset" ? "\u7D20\u6750" : "\u955C\u5934" }),
1296
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: node.label }),
1297
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: node.id })
1298
+ ] }, node.id))
1299
+ ] }) })
1300
+ ] });
1301
+ }
1302
+ function useScrollIntoView(selectedId) {
1303
+ let ref = (0, import_react.useRef)(null);
1304
+ return (0, import_react.useEffect)(() => {
1305
+ ref.current?.scrollIntoView({ block: "nearest" });
1306
+ }, [selectedId]), ref;
1307
+ }
1308
+ function ReadinessBadge({ label, ready }) {
1309
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { "data-ready": ready, "aria-label": `${label}${ready ? "\u5DF2\u5907" : "\u5F85\u8865"}`, children: [
1310
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("i", { "aria-hidden": "true", children: ready ? "\u2713" : "\u2014" }),
1311
+ label
1312
+ ] });
1313
+ }
1314
+ function MissingDocument({ document, documentPaths, what, skill, onNavigate }) {
1315
+ let present = documentPaths.includes(document);
1316
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-production-empty", children: [
1317
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("strong", { children: [
1318
+ "\u8FD8\u6CA1\u6709\u53EF\u6295\u5F71\u7684",
1319
+ what,
1320
+ "\u3002"
1321
+ ] }),
1322
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: present ? `${document} \u5DF2\u5B58\u5728\uFF0C\u4F46\u6CA1\u6709\u89E3\u6790\u51FA\u6761\u76EE\u3002\u8BF7\u68C0\u67E5\u4E8C\u7EA7\u6807\u9898\u662F\u5426\u4E3A\u7A33\u5B9A\u7684 ID \u5F62\u5F0F\u3002` : `\u672C\u96C6\u8FD8\u6CA1\u6709 ${document}\u3002\u5728\u53F3\u4FA7 Chat \u7528 ${skill} \u5199\u597D\u8FD9\u4EFD\u6587\u6863\u540E\uFF0C\u8FD9\u91CC\u4F1A\u81EA\u52A8\u51FA\u73B0\u3002` }),
1323
+ present && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("button", { type: "button", onClick: () => {
1324
+ onNavigate({ path: document, offset: 0, id: document });
1325
+ }, children: [
1326
+ "\u6253\u5F00 ",
1327
+ document.split("/").at(-1)
1328
+ ] })
1329
+ ] });
1330
+ }
1331
+ function ReferenceButton({ id, production, onNavigate }) {
1332
+ let target = production.targets.get(id);
1333
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", disabled: target === void 0, onClick: (event) => {
1334
+ event.stopPropagation(), target !== void 0 && onNavigate(target);
1335
+ }, children: id });
1336
+ }
1337
+ function MediaPreview({ version, interactive = !0 }) {
1338
+ return version.kind === "image" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("img", { className: "oh-story-media-preview", src: version.url, alt: version.targetId, loading: "lazy" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("video", { className: "oh-story-media-preview", src: version.url, controls: interactive, muted: !interactive, preload: "metadata" });
1339
+ }
1340
+ function VersionStrip({ targetId, versions, selections, onSelectionsChange }) {
1341
+ let selected = selectedVersionForTarget(targetId, versions, selections)?.id;
1342
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-version-strip", "aria-label": `${targetId} \u6210\u679C\u7248\u672C`, children: versions.map((version, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("button", { type: "button", "aria-pressed": version.id === selected, "aria-label": `\u9009\u62E9 ${targetId} \u7248\u672C ${String(index + 1)}`, "data-selected": version.id === selected || void 0, onClick: (event) => {
1343
+ event.stopPropagation(), onSelectionsChange({ ...selections, [targetId]: version.id });
1344
+ }, children: [
1345
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MediaPreview, { version, interactive: !1 }),
1346
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { children: [
1347
+ "V",
1348
+ String(index + 1)
1349
+ ] })
1350
+ ] }, version.id)) });
1351
+ }
1352
+
1353
+ // src/production-intent.ts
1354
+ var OH_STORY_PRODUCTION_TOOL_NAME = "oh_story_production";
1355
+ function requiredText(value, field) {
1356
+ let normalized = value?.trim();
1357
+ if (!normalized) throw new Error(`oh_story_production ${field} is required for this action.`);
1358
+ if (normalized.length > 512) throw new Error(`oh_story_production ${field} is too long.`);
1359
+ return normalized;
1360
+ }
1361
+ function validateProductionIntent(args) {
1362
+ let episode = args.episode.trim().replaceAll("\\", "/").replace(/\/$/u, "");
1363
+ if (!/^剧集\/EP\d{3,}$/u.test(episode))
1364
+ throw new Error("oh_story_production episode must use the creator path form \u5267\u96C6/EP001.");
1365
+ if (args.action === "open_section") {
1366
+ if (args.section === void 0) throw new Error("oh_story_production section is required for open_section.");
1367
+ return { action: args.action, episode, section: args.section };
1368
+ }
1369
+ if (args.action === "focus_target")
1370
+ return { action: args.action, episode, targetId: requiredText(args.targetId, "targetId"), section: args.section };
1371
+ if (args.action === "set_sequence") {
1372
+ let shotIds = args.shotIds?.map((value) => value.trim()).filter((value) => value !== "") ?? [];
1373
+ if (shotIds.length === 0) throw new Error("oh_story_production shotIds must contain at least one shot for set_sequence.");
1374
+ if (shotIds.length > 500 || new Set(shotIds).size !== shotIds.length || shotIds.some((value) => !/^SHOT-[A-Z0-9-]+$/u.test(value)))
1375
+ throw new Error("oh_story_production shotIds must be unique canonical SHOT-* identifiers.");
1376
+ return { action: args.action, episode, shotIds };
1377
+ }
1378
+ let expectedOutputs = args.expectedOutputs;
1379
+ if (expectedOutputs !== void 0 && (!Number.isInteger(expectedOutputs) || expectedOutputs < 1 || expectedOutputs > 500))
1380
+ throw new Error("oh_story_production expectedOutputs must be an integer between 1 and 500.");
1381
+ if (args.jobKind === void 0) throw new Error("oh_story_production jobKind is required for track_job.");
1382
+ let prompt = args.prompt?.trim();
1383
+ return {
1384
+ action: args.action,
1385
+ episode,
1386
+ jobId: requiredText(args.jobId, "jobId"),
1387
+ targetId: requiredText(args.targetId, "targetId"),
1388
+ jobKind: args.jobKind,
1389
+ expectedOutputs,
1390
+ prompt: prompt === "" ? void 0 : prompt
1391
+ };
1392
+ }
1393
+
1394
+ // src/client/production-intents.ts
1395
+ function parsedIntent(block) {
1396
+ if (!(!("kind" in block) || block.isError || block.call?.name !== OH_STORY_PRODUCTION_TOOL_NAME))
1397
+ try {
1398
+ let args = JSON.parse(block.call.argsRaw);
1399
+ return { callId: block.callId, intent: validateProductionIntent(args) };
1400
+ } catch {
1401
+ return;
1402
+ }
1403
+ }
1404
+ function visit(block, output) {
1405
+ let direct = parsedIntent(block);
1406
+ direct !== void 0 && output.push(direct);
1407
+ for (let child of block.subCalls) visit(child, output);
1408
+ }
1409
+ function settledProductionIntents(chat) {
1410
+ let output = [];
1411
+ for (let key of chat.order) {
1412
+ let node = chat.nodes.get(key);
1413
+ if (node?.kind !== "tool-call") continue;
1414
+ let root = node.data.root;
1415
+ root !== void 0 && visit(root, output);
1416
+ }
1417
+ return output;
1418
+ }
1419
+
1420
+ // src/client/video-studio.tsx
1421
+ var import_react2 = require("react");
1422
+
1423
+ // src/client/workbench-ui.ts
1424
+ function handleTabKey(event, values, current, select) {
1425
+ let index;
1426
+ if (event.key === "Home" ? index = 0 : event.key === "End" ? index = values.length - 1 : event.key === "ArrowRight" ? index = (values.indexOf(current) + 1) % values.length : event.key === "ArrowLeft" && (index = (values.indexOf(current) - 1 + values.length) % values.length), index === void 0) return;
1427
+ event.preventDefault();
1428
+ let value = values[index];
1429
+ value !== void 0 && (select(value), event.currentTarget.parentElement?.querySelectorAll("[role='tab']")[index]?.focus());
1430
+ }
1431
+ function endpoint(path, sessionId, file) {
1432
+ let url = new URL(`/oh-story/${path}`, globalThis.location.origin);
1433
+ return url.searchParams.set("sessionId", sessionId), file !== void 0 && url.searchParams.set("path", file), url.toString();
1434
+ }
1435
+
1436
+ // src/client/video-studio.tsx
1437
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1438
+ function preferredPreview(project) {
1439
+ return project.previews.find((item) => item.role === "final") ?? project.previews.find((item) => item.role === "edited") ?? project.previews.find((item) => item.role === "source");
1440
+ }
1441
+ function readableBytes(bytes) {
1442
+ return bytes < 1024 * 1024 ? `${(bytes / 1024).toFixed(1)} KB` : bytes < 1024 * 1024 * 1024 ? `${(bytes / (1024 * 1024)).toFixed(1)} MB` : `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
1443
+ }
1444
+ function ActionIcon({ name: name2 }) {
1445
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { "aria-hidden": !0, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", children: {
1446
+ reload: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1447
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M20 11a8 8 0 1 0-2.34 5.66" }),
1448
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M20 4v7h-7" })
1449
+ ] }),
1450
+ fullscreen: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M8 3H3v5M16 3h5v5M8 21H3v-5M16 21h5v-5" }) }),
1451
+ external: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1452
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M14 3h7v7M21 3l-9 9" }),
1453
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" })
1454
+ ] })
1455
+ }[name2] });
1456
+ }
1457
+ function preferredArtifact(project) {
1458
+ return project.artifacts.find((item) => item.path.endsWith("final_qc.json")) ?? project.artifacts.find((item) => item.path.endsWith("assembly_qc.json")) ?? project.artifacts.find((item) => item.kind === "quality") ?? project.artifacts.at(-1);
1459
+ }
1460
+ function artifactKindLabel(kind) {
1461
+ return { plan: "\u65B9\u6848", script: "\u6587\u7A3F", subtitle: "\u5B57\u5E55", quality: "\u8D28\u68C0", manifest: "\u6E05\u5355" }[kind];
1462
+ }
1463
+ function VideoPreview({ project, sessionId, running }) {
1464
+ let shellRef = (0, import_react2.useRef)(null), initial = preferredPreview(project), [role, setRole] = (0, import_react2.useState)(initial?.role), selected = project.previews.find((item) => item.role === role) ?? preferredPreview(project), [loaded, setLoaded] = (0, import_react2.useState)(initial), [revision, setRevision] = (0, import_react2.useState)(0), [ready, setReady] = (0, import_react2.useState)(!1), [error, setError] = (0, import_react2.useState)(!1);
1465
+ if (selected === void 0 || loaded === void 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "oh-video-preview-empty", children: [
1466
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "aria-hidden": !0, children: "\u25B6" }),
1467
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: "\u8FD8\u6CA1\u6709\u53EF\u9884\u89C8\u7684\u89C6\u9891" }),
1468
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { children: [
1469
+ "\u628A\u539F\u7247\u5BFC\u5165 ",
1470
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { children: "video-recaps/<\u9879\u76EE>/sources/" }),
1471
+ "\uFF0C\u7136\u540E\u5728\u53F3\u4FA7 Chat \u4F7F\u7528 ",
1472
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { children: "/video-recap" }),
1473
+ "\u3002\u526A\u540E\u7247\u548C\u6700\u7EC8\u6210\u7247\u4F1A\u81EA\u52A8\u51FA\u73B0\u5728\u8FD9\u91CC\u3002"
1474
+ ] }),
1475
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "oh-video-prompt-example", children: [
1476
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "\u63CF\u8FF0\u793A\u4F8B" }),
1477
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("q", { children: "\u628A\u8FD9\u6BB5\u89C6\u9891\u505A\u6210 3 \u5206\u949F\u4E2D\u6587\u89E3\u8BF4\uFF0C\u4FDD\u7559\u5173\u952E\u539F\u58F0\uFF0C\u5B57\u5E55\u70E7\u8FDB\u753B\u9762\u3002" })
1478
+ ] })
1479
+ ] });
1480
+ let pending = selected.path !== loaded.path || selected.version !== loaded.version, load = (asset) => {
1481
+ setRole(asset.role), setLoaded(asset), setReady(!1), setError(!1), setRevision((value) => value + 1);
1482
+ }, runtimeState = error ? "\u89C6\u9891\u8F7D\u5165\u5931\u8D25 \xB7 \u53EF\u91CD\u65B0\u8F7D\u5165" : running ? "Agent \u6B63\u5728\u66F4\u65B0\u9879\u76EE \xB7 \u5F53\u524D\u64AD\u653E\u4FDD\u6301\u4E0D\u53D8" : pending ? "\u65B0\u7248\u672C\u5DF2\u5C31\u7EEA \xB7 \u7531\u4F60\u51B3\u5B9A\u4F55\u65F6\u8F7D\u5165" : ready ? `${loaded.label}\u5DF2\u8F7D\u5165 \xB7 ${readableBytes(loaded.bytes)}` : "\u6B63\u5728\u8BFB\u53D6\u89C6\u9891\u4FE1\u606F\u2026", mediaUrl = `${endpoint("media", sessionId, loaded.path)}&version=${encodeURIComponent(loaded.version)}&reload=${String(revision)}`;
1483
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { ref: shellRef, className: "oh-video-preview-shell", "data-state": error ? "error" : running ? "building" : ready ? "ready" : "loading", children: [
1484
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "oh-video-stagebar", children: [
1485
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "oh-video-version-tabs", role: "tablist", "aria-label": "\u9884\u89C8\u7248\u672C", children: project.previews.map((asset) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1486
+ "button",
1487
+ {
1488
+ type: "button",
1489
+ role: "tab",
1490
+ "aria-selected": asset.role === selected.role,
1491
+ tabIndex: asset.role === selected.role ? 0 : -1,
1492
+ onKeyDown: (event) => {
1493
+ handleTabKey(event, project.previews.map((item) => item.role), selected.role, (next) => {
1494
+ let asset2 = project.previews.find((item) => item.role === next);
1495
+ asset2 !== void 0 && load(asset2);
1496
+ });
1497
+ },
1498
+ onClick: () => {
1499
+ load(asset);
1500
+ },
1501
+ children: asset.label
1502
+ },
1503
+ asset.role
1504
+ )) }),
1505
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "oh-video-runtime-state", role: "status", "aria-live": "polite", children: [
1506
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("i", { "aria-hidden": !0 }),
1507
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("em", { children: runtimeState })
1508
+ ] }),
1509
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "oh-video-preview-actions", children: [
1510
+ pending && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", onClick: () => {
1511
+ load(selected);
1512
+ }, children: "\u8F7D\u5165\u65B0\u7248\u672C" }),
1513
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", title: "\u91CD\u65B0\u8F7D\u5165", "aria-label": "\u91CD\u65B0\u8F7D\u5165\u89C6\u9891", onClick: () => {
1514
+ load(loaded);
1515
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ActionIcon, { name: "reload" }) }),
1516
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", title: "\u5168\u5C4F", "aria-label": "\u5168\u5C4F\u9884\u89C8", onClick: () => {
1517
+ shellRef.current?.requestFullscreen();
1518
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ActionIcon, { name: "fullscreen" }) }),
1519
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("a", { href: mediaUrl, target: "_blank", rel: "noreferrer", title: "\u5728\u65B0\u7A97\u53E3\u6253\u5F00", "aria-label": "\u5728\u65B0\u7A97\u53E3\u6253\u5F00\u89C6\u9891", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ActionIcon, { name: "external" }) })
1520
+ ] })
1521
+ ] }),
1522
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "oh-video-player-stage", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1523
+ "video",
1524
+ {
1525
+ src: mediaUrl,
1526
+ controls: !0,
1527
+ preload: "metadata",
1528
+ playsInline: !0,
1529
+ onLoadedMetadata: () => {
1530
+ setReady(!0), setError(!1);
1531
+ },
1532
+ onError: () => {
1533
+ setError(!0), setReady(!1);
1534
+ }
1535
+ },
1536
+ `${loaded.path}:${loaded.version}:${String(revision)}`
1537
+ ) })
1538
+ ] });
1539
+ }
1540
+ function VideoArtifacts({ project, sessionId }) {
1541
+ let [selected, setSelected] = (0, import_react2.useState)(preferredArtifact(project)?.path), [content, setContent] = (0, import_react2.useState)(), [error, setError] = (0, import_react2.useState)(), [preflight, setPreflight] = (0, import_react2.useState)();
1542
+ (0, import_react2.useEffect)(() => {
1543
+ setSelected(preferredArtifact(project)?.path);
1544
+ }, [project.id]), (0, import_react2.useEffect)(() => {
1545
+ if (selected === void 0) {
1546
+ setContent(void 0);
1547
+ return;
1548
+ }
1549
+ let controller = new AbortController();
1550
+ return setContent(void 0), setError(void 0), fetch(endpoint("file", sessionId, selected), { signal: controller.signal }).then(async (response) => {
1551
+ let payload = await response.json();
1552
+ if (!response.ok) throw new Error(payload.error ?? `HTTP ${String(response.status)}`);
1553
+ setContent(payload.content);
1554
+ }).catch((reason) => {
1555
+ controller.signal.aborted || setError(reason instanceof Error ? reason.message : String(reason));
1556
+ }), () => {
1557
+ controller.abort();
1558
+ };
1559
+ }, [selected, sessionId]);
1560
+ let selectedArtifact = project.artifacts.find((item) => item.path === selected);
1561
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "oh-video-artifacts", children: [
1562
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("aside", { "aria-label": "\u89C6\u9891\u9879\u76EE\u4EA7\u7269", children: [
1563
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "oh-video-artifacts-heading", children: [
1564
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: "\u9879\u76EE\u4EA7\u7269" }),
1565
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: project.artifacts.length })
1566
+ ] }),
1567
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "oh-video-environment", children: [
1568
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: "\u8FD0\u884C\u73AF\u5883" }),
1569
+ typeof preflight == "object" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1570
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { "data-ready": preflight.python.ok || void 0, children: [
1571
+ "Python ",
1572
+ preflight.python.version ?? "\u672A\u627E\u5230"
1573
+ ] }),
1574
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { "data-ready": preflight.ffmpeg.ok && preflight.ffmpeg.subtitles || void 0, children: [
1575
+ "ffmpeg ",
1576
+ preflight.ffmpeg.subtitles ? "\xB7 libass" : "\xB7 \u7F3A\u5B57\u5E55\u6EE4\u955C"
1577
+ ] }),
1578
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { "data-ready": preflight.ffprobe.ok || void 0, children: [
1579
+ "ffprobe ",
1580
+ preflight.ffprobe.ok ? "\u53EF\u7528" : "\u672A\u627E\u5230"
1581
+ ] }),
1582
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { "data-ready": preflight.credentials.mimo || void 0, children: [
1583
+ "MiMo Key ",
1584
+ preflight.credentials.mimo ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E"
1585
+ ] }),
1586
+ preflight.credentials.ttsProvider === "fish" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { "data-ready": preflight.credentials.fish || void 0, children: [
1587
+ "Fish Key ",
1588
+ preflight.credentials.fish ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E"
1589
+ ] }),
1590
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("em", { children: [
1591
+ "DSH Host \u8FDB\u7A0B\u73AF\u5883\uFF1BAgent \u6267\u884C\u4E16\u754C\u4EE5 ",
1592
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { children: "video-recap --doctor" }),
1593
+ " \u4E3A\u51C6\u3002"
1594
+ ] })
1595
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", onClick: () => {
1596
+ fetch(endpoint("video-preflight", sessionId)).then(async (response) => {
1597
+ if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
1598
+ setPreflight(await response.json());
1599
+ }).catch(() => {
1600
+ setPreflight("failed");
1601
+ });
1602
+ }, children: preflight === "failed" ? "\u73AF\u5883\u68C0\u67E5\u5931\u8D25 \xB7 \u91CD\u8BD5" : "\u68C0\u67E5\u73AF\u5883" })
1603
+ ] }),
1604
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("nav", { children: [
1605
+ project.artifacts.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: "\u6D41\u6C34\u7EBF\u542F\u52A8\u540E\uFF0C\u5173\u952E\u4EA7\u7269\u4F1A\u51FA\u73B0\u5728\u8FD9\u91CC\u3002" }),
1606
+ project.artifacts.map((artifact) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1607
+ "button",
1608
+ {
1609
+ type: "button",
1610
+ "aria-current": artifact.path === selected ? "page" : void 0,
1611
+ onClick: () => {
1612
+ setSelected(artifact.path);
1613
+ },
1614
+ children: [
1615
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: artifact.label }),
1616
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("small", { children: artifact.path.split("/").at(-1) })
1617
+ ]
1618
+ },
1619
+ artifact.path
1620
+ ))
1621
+ ] })
1622
+ ] }),
1623
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { children: [
1624
+ selectedArtifact !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { className: "oh-video-artifact-header", children: [
1625
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
1626
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: selectedArtifact.label }),
1627
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: selectedArtifact.path.split("/").at(-1) })
1628
+ ] }),
1629
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("em", { children: artifactKindLabel(selectedArtifact.kind) })
1630
+ ] }),
1631
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "oh-video-artifact-content", children: project.artifacts.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "oh-video-artifacts-empty", children: "\u6545\u4E8B\u65B9\u6848\u3001\u89E3\u8BF4\u8BCD\u3001\u5B57\u5E55\u548C\u8D28\u68C0\u62A5\u544A\u4F1A\u6309\u4E0A\u6E38\u6D41\u6C34\u7EBF\u5199\u5165\u8FD9\u91CC\u3002" }) : error !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "oh-story-error", children: error }) : content === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "oh-video-artifacts-empty", children: "\u6B63\u5728\u8F7D\u5165\u4EA7\u7269\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("pre", { children: content }) })
1632
+ ] })
1633
+ ] });
1634
+ }
1635
+ function VideoStudio({
1636
+ sessionId,
1637
+ projects,
1638
+ running,
1639
+ projectId,
1640
+ tab,
1641
+ hidden,
1642
+ workbenches,
1643
+ paneId,
1644
+ labelledBy,
1645
+ onProject,
1646
+ onTab,
1647
+ onWorkbench
1648
+ }) {
1649
+ let project = projects.find((item) => item.id === projectId) ?? projects[0], tabsId = (0, import_react2.useId)();
1650
+ (0, import_react2.useEffect)(() => {
1651
+ project !== void 0 && project.id !== projectId && onProject(project.id);
1652
+ }, [onProject, project, projectId]);
1653
+ let tabs = ["preview", "artifacts"];
1654
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("main", { id: paneId, className: "oh-video-studio", role: "tabpanel", "aria-labelledby": labelledBy, hidden, children: [
1655
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { className: "oh-video-toolbar", children: [
1656
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "oh-video-mode-tabs", role: "tablist", "aria-label": "\u521B\u4F5C\u5DE5\u4F5C\u53F0", children: workbenches.map((mode) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1657
+ "button",
1658
+ {
1659
+ type: "button",
1660
+ role: "tab",
1661
+ "aria-selected": mode === "video",
1662
+ tabIndex: mode === "video" ? 0 : -1,
1663
+ onKeyDown: (event) => {
1664
+ handleTabKey(event, workbenches, "video", onWorkbench);
1665
+ },
1666
+ onClick: () => {
1667
+ onWorkbench(mode);
1668
+ },
1669
+ children: workbenchLabel(mode)
1670
+ },
1671
+ mode
1672
+ )) }),
1673
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "oh-video-project", children: [
1674
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "\u89C6\u9891\u9879\u76EE" }),
1675
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("select", { "aria-label": "\u89C6\u9891\u9879\u76EE", value: project?.id ?? "", disabled: project === void 0, onChange: (event) => {
1676
+ onProject(event.target.value);
1677
+ }, children: projects.map((item) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: item.id, children: item.title }, item.id)) })
1678
+ ] }),
1679
+ project !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "oh-video-stage", "data-state": project.state, children: [
1680
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("i", { "aria-hidden": !0 }),
1681
+ project.stageLabel
1682
+ ] }),
1683
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "oh-video-tabs", role: "tablist", "aria-label": "\u89C6\u9891\u5DE5\u4F5C\u53F0", children: tabs.map((item) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1684
+ "button",
1685
+ {
1686
+ type: "button",
1687
+ role: "tab",
1688
+ id: `${tabsId}-${item}-tab`,
1689
+ "aria-controls": `${tabsId}-${item}-panel`,
1690
+ "aria-selected": tab === item,
1691
+ tabIndex: tab === item ? 0 : -1,
1692
+ onKeyDown: (event) => {
1693
+ handleTabKey(event, tabs, tab, onTab);
1694
+ },
1695
+ onClick: () => {
1696
+ onTab(item);
1697
+ },
1698
+ children: item === "preview" ? "\u9884\u89C8" : "\u4EA7\u7269"
1699
+ },
1700
+ item
1701
+ )) })
1702
+ ] }),
1703
+ project === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "oh-video-preview-empty", children: [
1704
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "aria-hidden": !0, children: "\u25B6" }),
1705
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: "\u8FD8\u6CA1\u6709\u89C6\u9891\u9879\u76EE" }),
1706
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { children: [
1707
+ "\u5728\u53F3\u4FA7 Chat \u544A\u8BC9 Agent \u8981\u5904\u7406\u7684\u89C6\u9891\uFF1B\u9879\u76EE\u4F1A\u521B\u5EFA\u5728 ",
1708
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { children: "video-recaps/<\u9879\u76EE>/" }),
1709
+ "\u3002"
1710
+ ] })
1711
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "oh-video-panels", children: [
1712
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { role: "tabpanel", id: `${tabsId}-preview-panel`, "aria-labelledby": `${tabsId}-preview-tab`, hidden: tab !== "preview", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(VideoPreview, { project, sessionId, running }, project.id) }),
1713
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { role: "tabpanel", id: `${tabsId}-artifacts-panel`, "aria-labelledby": `${tabsId}-artifacts-tab`, hidden: tab !== "artifacts", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(VideoArtifacts, { project, sessionId }) })
1714
+ ] })
1715
+ ] });
1716
+ }
1717
+
1718
+ // inline-css:/home/runner/work/oh-story-dsh/oh-story-dsh/packages/dsh-plugin/src/client/plugin.css
1719
+ var plugin_default = `.oh-story-bridge-marker { display: none; }
1720
+ .oh-story-split-surface { display: contents; font-family: var(--dsw-font-family); }
1721
+
1722
+ /* DSH documents every [data-slot] wrapper as a stable addressable styling
1723
+ seam. The official Session body and composer stay mounted; this only turns
1724
+ their scroll host into a three-column writing surface. */
1725
+ [data-conversation-scroll]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
1726
+ --os-border: var(--dsw-alias-border-l2);
1727
+ --os-muted: var(--dsw-alias-label-secondary);
1728
+ display: grid;
1729
+ grid-template-columns: clamp(184px, 16%, 200px) minmax(240px, 1fr) clamp(408px, 40%, 520px);
1730
+ grid-template-rows: minmax(0, 1fr);
1731
+ min-height: 0;
1732
+ overflow-x: hidden;
1733
+ overflow-y: auto;
1734
+ position: relative;
1735
+ scrollbar-gutter: stable;
1736
+ scroll-padding-bottom: calc(max(var(--dsh-composer-height, 152px), var(--oh-story-composer-height, 0px)) + 16px);
1737
+ }
1738
+
1739
+ /* Keep the final Chat flow above the overlapping official Composer. */
1740
+ [data-conversation-scroll]:has(> [data-slot="conversation.session"] > .oh-story-split-surface)
1741
+ > [data-slot="conversation.session"] > :not(.oh-story-split-surface) {
1742
+ grid-column: 3;
1743
+ grid-row: 1;
1744
+ min-width: 0;
1745
+ min-height: 100%;
1746
+ border-left: 1px solid var(--os-border);
1747
+ }
1748
+
1749
+ [data-conversation-scroll]:has(> [data-slot="conversation.session"] > .oh-story-split-surface)
1750
+ [data-chat-flow] {
1751
+ padding-bottom: calc(max(var(--dsh-composer-height, 152px), var(--oh-story-composer-height, 0px)) + 16px);
1752
+ }
1753
+
1754
+ [data-conversation-scroll]:has(> [data-slot="conversation.session"] > .oh-story-split-surface)
1755
+ > [data-composer-seat] {
1756
+ grid-column: 3;
1757
+ grid-row: 1;
1758
+ align-self: start;
1759
+ min-width: 0;
1760
+ width: 100%;
1761
+ position: sticky;
1762
+ top: calc(var(--oh-story-scroll-height, 720px) - max(var(--dsh-composer-height, 152px), var(--oh-story-composer-height, 0px)));
1763
+ bottom: auto;
1764
+ }
1765
+
1766
+ /* Game mode keeps the official Conversation on the right but merges the two
1767
+ creator columns into one playable Studio. */
1768
+ [data-conversation-scroll][data-oh-story-workbench="game"]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
1769
+ --game-stage: #0d100e;
1770
+ --game-stage-raised: #161a17;
1771
+ --game-accent: var(--dsw-alias-state-business-primary);
1772
+ --game-warning: #f4bd55;
1773
+ --game-danger: #f06b63;
1774
+ grid-template-columns: minmax(0, 1fr) clamp(400px, 34%, 520px);
1775
+ overflow: hidden;
1776
+ }
1777
+
1778
+ [data-conversation-scroll][data-oh-story-workbench="game"]:has(> [data-slot="conversation.session"] > .oh-story-split-surface)
1779
+ > [data-slot="conversation.session"] > :not(.oh-story-split-surface),
1780
+ [data-conversation-scroll][data-oh-story-workbench="game"]:has(> [data-slot="conversation.session"] > .oh-story-split-surface)
1781
+ > [data-composer-seat] { grid-column: 2; }
1782
+
1783
+ /* DSH 0.1.2 lays two 40px column-resize strips (z-index 8) over the whole
1784
+ conversation area. The workbench replaces that column geometry, so the strip
1785
+ no longer marks a real column seam here \u2014 it just swallows clicks on the
1786
+ right edge of the file tree. The creator panes take the clicks back. */
1787
+ .oh-story-tree,
1788
+ .oh-story-editor {
1789
+ box-sizing: border-box;
1790
+ grid-row: 1;
1791
+ position: sticky;
1792
+ z-index: 9;
1793
+ top: 0;
1794
+ align-self: start;
1795
+ min-width: 0;
1796
+ min-height: 0;
1797
+ height: 100%;
1798
+ color: inherit;
1799
+ background: var(--dsw-alias-bg-base);
1800
+ }
1801
+
1802
+ .oh-game-studio {
1803
+ box-sizing: border-box;
1804
+ display: flex;
1805
+ grid-column: 1;
1806
+ grid-row: 1;
1807
+ position: sticky;
1808
+ z-index: 9;
1809
+ top: 0;
1810
+ min-width: 0;
1811
+ min-height: 0;
1812
+ height: 100%;
1813
+ align-self: start;
1814
+ overflow: hidden;
1815
+ flex-direction: column;
1816
+ border-right: 1px solid var(--os-border);
1817
+ color: var(--dsw-alias-label-primary);
1818
+ background: var(--dsw-alias-bg-base);
1819
+ }
1820
+ .oh-game-studio[hidden] { display: none; }
1821
+
1822
+ .oh-game-toolbar {
1823
+ box-sizing: border-box;
1824
+ display: grid;
1825
+ min-height: 56px;
1826
+ flex: none;
1827
+ grid-template-columns: auto minmax(160px, 1fr) auto;
1828
+ align-items: center;
1829
+ gap: 9px;
1830
+ padding: 8px 12px;
1831
+ border-bottom: 1px solid var(--os-border);
1832
+ background: var(--dsw-alias-bg-base);
1833
+ }
1834
+
1835
+ .oh-game-mode-tabs {
1836
+ display: flex;
1837
+ margin-right: 2px;
1838
+ padding-right: 9px;
1839
+ border-right: 1px solid var(--os-border);
1840
+ }
1841
+ .oh-game-mode-tabs button {
1842
+ min-width: 42px;
1843
+ min-height: 36px;
1844
+ border: 0;
1845
+ border-radius: 5px;
1846
+ color: var(--os-muted);
1847
+ background: transparent;
1848
+ padding: 4px 7px;
1849
+ cursor: pointer;
1850
+ white-space: nowrap;
1851
+ font: 11px/1 var(--dsw-font-family);
1852
+ }
1853
+ .oh-game-mode-tabs button:hover { color: var(--dsw-alias-label-primary); }
1854
+ .oh-game-mode-tabs button[aria-selected="true"] { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-button-ghost-active-fill); font-weight: 500; }
1855
+
1856
+ .oh-game-project { display: flex; min-width: 0; align-items: center; gap: 7px; color: var(--os-muted); font-size: 11px; }
1857
+ .oh-game-project > span { flex: none; }
1858
+ .oh-game-project select,
1859
+ .oh-game-design select {
1860
+ min-width: 0;
1861
+ border: 1px solid var(--os-border);
1862
+ border-radius: 8px;
1863
+ color: var(--dsw-alias-label-primary);
1864
+ background: var(--dsw-alias-bg-base);
1865
+ padding: 6px 28px 6px 9px;
1866
+ font: inherit;
1867
+ }
1868
+ .oh-game-project select { max-width: 360px; min-height: 36px; flex: 1; overflow: hidden; text-overflow: ellipsis; font-weight: 500; }
1869
+ .oh-game-design select { min-height: 36px; }
1870
+ .oh-game-project select:focus-visible,
1871
+ .oh-game-design select:focus-visible,
1872
+ .oh-game-mode-tabs button:focus-visible,
1873
+ .oh-game-tabs button:focus-visible,
1874
+ .oh-game-preview-status button:focus-visible,
1875
+ .oh-game-mobile-switcher button:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary); outline-offset: 1px; }
1876
+
1877
+ .oh-game-tabs,
1878
+ .oh-game-mobile-switcher {
1879
+ display: flex;
1880
+ padding: 2px;
1881
+ border: 1px solid var(--os-border);
1882
+ border-radius: 9px;
1883
+ background: var(--dsw-alias-bg-base);
1884
+ }
1885
+ .oh-game-tabs button,
1886
+ .oh-game-mobile-switcher button {
1887
+ min-width: 56px;
1888
+ min-height: 36px;
1889
+ border: 0;
1890
+ border-radius: 6px;
1891
+ color: var(--os-muted);
1892
+ background: transparent;
1893
+ cursor: pointer;
1894
+ font: 12px/1 var(--dsw-font-family);
1895
+ }
1896
+ .oh-game-tabs button:hover,
1897
+ .oh-game-mobile-switcher button:hover { color: var(--dsw-alias-label-primary); }
1898
+ .oh-game-tabs button[aria-selected="true"],
1899
+ .oh-game-mobile-switcher button[aria-selected="true"] { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-button-ghost-active-fill); box-shadow: 0 1px 2px rgb(0 0 0 / 7%); font-weight: 600; }
1900
+ .oh-game-panels,
1901
+ .oh-game-panel {
1902
+ display: flex;
1903
+ min-width: 0;
1904
+ min-height: 0;
1905
+ flex: 1;
1906
+ overflow: hidden;
1907
+ flex-direction: column;
1908
+ }
1909
+ .oh-game-panel[hidden] { display: none; }
1910
+
1911
+ .oh-game-preview-shell {
1912
+ display: grid;
1913
+ min-width: 0;
1914
+ min-height: 0;
1915
+ flex: 1;
1916
+ grid-template-rows: 40px minmax(0, 1fr);
1917
+ position: relative;
1918
+ overflow: hidden;
1919
+ border-top: 2px solid var(--game-accent);
1920
+ background-color: var(--game-stage);
1921
+ }
1922
+ .oh-game-preview-shell[data-state="building"] { border-top-color: var(--game-warning); animation: oh-game-build-line 1.4s linear infinite; }
1923
+ .oh-game-preview-shell[data-state="error"] { border-top-color: var(--game-danger); }
1924
+ .oh-game-preview-status {
1925
+ display: flex;
1926
+ min-width: 0;
1927
+ align-items: center;
1928
+ justify-content: space-between;
1929
+ gap: 12px;
1930
+ padding: 0 10px 0 12px;
1931
+ color: rgb(244 248 240 / 76%);
1932
+ background: var(--game-stage-raised);
1933
+ font-size: 11px;
1934
+ }
1935
+ .oh-game-runtime-state { display: inline-flex; overflow: hidden; min-width: 0; align-items: center; gap: 7px; text-overflow: ellipsis; white-space: nowrap; }
1936
+ .oh-game-runtime-state > i { width: 6px; height: 6px; flex: none; border-radius: 50%; background: var(--dsw-alias-state-success-primary, #45b96f); }
1937
+ .oh-game-preview-shell[data-state="loading"] .oh-game-runtime-state > i { background: rgb(255 255 255 / 42%); box-shadow: none; }
1938
+ .oh-game-preview-shell[data-state="building"] .oh-game-runtime-state > i { background: var(--game-warning); }
1939
+ .oh-game-preview-shell[data-state="error"] .oh-game-runtime-state > i { background: var(--game-danger); }
1940
+ .oh-game-preview-status > div { display: flex; flex: none; gap: 6px; }
1941
+ .oh-game-preview-status button {
1942
+ min-height: 36px;
1943
+ border: 1px solid rgb(255 255 255 / 14%);
1944
+ border-radius: 6px;
1945
+ color: rgb(255 255 255 / 84%);
1946
+ background: rgb(255 255 255 / 5%);
1947
+ padding: 3px 8px;
1948
+ cursor: pointer;
1949
+ font: inherit;
1950
+ }
1951
+ .oh-game-preview-status button:hover { background: rgb(255 255 255 / 11%); }
1952
+ .oh-game-reload { display: inline-flex; align-items: center; gap: 5px; }
1953
+ .oh-game-reload b { font: inherit; font-weight: 500; }
1954
+ .oh-game-preview-shell iframe { width: 100%; height: 100%; min-width: 0; min-height: 0; border: 0; background: var(--game-stage); }
1955
+ .oh-game-focus-hint {
1956
+ position: absolute;
1957
+ right: 10px;
1958
+ bottom: 10px;
1959
+ z-index: 2;
1960
+ padding: 4px 8px;
1961
+ border: 1px solid rgb(255 255 255 / 12%);
1962
+ border-radius: 6px;
1963
+ color: rgb(255 255 255 / 62%);
1964
+ background: rgb(8 10 9 / 82%);
1965
+ pointer-events: none;
1966
+ font-size: 10px;
1967
+ }
1968
+ .oh-game-focus-hint[data-focused] { color: var(--game-accent); }
1969
+
1970
+ .oh-game-preview-empty,
1971
+ .oh-game-design-empty {
1972
+ display: grid;
1973
+ max-width: 560px;
1974
+ margin: auto;
1975
+ padding: 36px;
1976
+ place-items: center;
1977
+ gap: 8px;
1978
+ color: var(--os-muted);
1979
+ text-align: center;
1980
+ line-height: 1.65;
1981
+ }
1982
+ .oh-game-preview-empty { width: 100%; max-width: none; min-height: 0; flex: 1; box-sizing: border-box; align-content: center; color: rgb(244 248 240 / 70%); background: var(--game-stage); }
1983
+ .oh-game-preview-empty > span { color: var(--game-accent); font-size: 30px; }
1984
+ .oh-game-preview-empty strong,
1985
+ .oh-game-design-empty strong { color: inherit; font-size: 15px; }
1986
+ .oh-game-preview-empty p,
1987
+ .oh-game-design-empty p { margin: 0; }
1988
+ .oh-game-preview-empty code,
1989
+ .oh-game-design-empty code { font: 11px/1.5 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
1990
+ .oh-game-prompt-example { display: grid; max-width: 560px; gap: 4px; margin-top: 8px; padding: 10px 12px; border: 1px solid rgb(255 255 255 / 12%); border-radius: 8px; text-align: left; }
1991
+ .oh-game-prompt-example span { color: var(--game-accent); font-size: 10px; font-weight: 600; }
1992
+ .oh-game-prompt-example q { color: rgb(244 248 240 / 72%); quotes: none; font-size: 11px; }
1993
+
1994
+ .oh-game-design { display: flex; min-height: 0; flex: 1; overflow: hidden; flex-direction: column; }
1995
+ .oh-game-design > label { display: flex; min-height: 42px; flex: none; align-items: center; gap: 10px; padding: 0 14px; border-bottom: 1px solid var(--os-border); color: var(--os-muted); font-size: 11px; }
1996
+ .oh-game-design select { max-width: min(560px, 75%); }
1997
+ .oh-game-source { box-sizing: border-box; min-height: 0; flex: 1; overflow: auto; margin: 0; padding: 18px 22px 48px; color: var(--dsw-alias-label-secondary); background: var(--dsw-alias-markdown-code-block); white-space: pre-wrap; overflow-wrap: anywhere; font: 12px/1.65 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
1998
+
1999
+ .oh-game-mobile-switcher { display: none; }
2000
+
2001
+ @keyframes oh-game-build-line { 50% { border-top-color: var(--game-accent); } }
2002
+
2003
+ .oh-story-tree {
2004
+ grid-column: 1;
2005
+ overflow: auto;
2006
+ padding: 14px 10px 24px;
2007
+ border-right: 1px solid var(--os-border);
2008
+ background: var(--dsw-specific-sidebar-fill);
2009
+ }
2010
+
2011
+ .oh-story-editor {
2012
+ display: flex;
2013
+ grid-column: 2;
2014
+ overflow: hidden;
2015
+ flex-direction: column;
2016
+ }
2017
+
2018
+ .oh-story-brand,
2019
+ .oh-story-editor header,
2020
+ .oh-story-role summary {
2021
+ display: flex;
2022
+ align-items: center;
2023
+ justify-content: space-between;
2024
+ gap: 12px;
2025
+ }
2026
+
2027
+ .oh-story-brand { min-height: 28px; gap: 8px; padding: 0 5px 8px 7px; font-size: 14px; font-weight: 500; white-space: nowrap; }
2028
+ .oh-story-brand-cluster { display: flex; min-width: 0; align-items: center; gap: 6px; }
2029
+ .oh-story-brand-cluster strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: inherit; }
2030
+ .oh-story-kind { flex: none; padding: 1px 6px; border-radius: 5px; color: var(--dsw-alias-label-secondary); background: var(--dsw-alias-button-ghost-active-fill); font-size: 11px; line-height: 16px; font-weight: 500; }
2031
+
2032
+ .oh-story-brand button,
2033
+ .oh-story-save,
2034
+ .oh-story-role button {
2035
+ border: 1px solid var(--dsw-alias-border-l2);
2036
+ border-radius: 999px;
2037
+ color: var(--dsw-alias-label-secondary);
2038
+ background: var(--dsw-alias-bg-base);
2039
+ padding: 3px 9px;
2040
+ cursor: pointer;
2041
+ font: inherit;
2042
+ }
2043
+
2044
+ .oh-story-brand button { display: grid; width: 28px; height: 28px; place-items: center; border: 0; padding: 0; background: transparent; font-size: 16px; }
2045
+ .oh-story-brand button:hover,
2046
+ .oh-story-save:hover:not(:disabled),
2047
+ .oh-story-role button:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-interactive-bg-hover); }
2048
+ .oh-story-brand button:focus-visible,
2049
+ .oh-story-save:focus-visible,
2050
+ .oh-story-mode-tabs button:focus-visible,
2051
+ .oh-story-editor-tabs button:focus-visible,
2052
+ .oh-story-tree nav button:focus-visible,
2053
+ .oh-story-role button:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary); outline-offset: -2px; }
2054
+ .oh-story-save:disabled { opacity: .48; cursor: default; }
2055
+
2056
+ .oh-story-mode-tabs { display: flex; margin: 0 4px 8px; padding: 2px; border: 1px solid var(--os-border); border-radius: 7px; background: var(--dsw-alias-bg-layer-1); }
2057
+
2058
+ .oh-story-mode-tabs button,
2059
+ .oh-story-editor-tabs button {
2060
+ position: relative;
2061
+ min-height: 24px;
2062
+ flex: 1;
2063
+ border: 0;
2064
+ color: var(--os-muted);
2065
+ background: transparent;
2066
+ border-radius: 5px;
2067
+ cursor: pointer;
2068
+ font: inherit;
2069
+ font-size: 11px;
2070
+ }
2071
+
2072
+ .oh-story-mode-tabs button:hover,
2073
+ .oh-story-editor-tabs button:hover { color: var(--dsw-alias-label-primary); }
2074
+ .oh-story-mode-tabs button[aria-selected="true"],
2075
+ .oh-story-editor-tabs button[aria-selected="true"] { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-base); box-shadow: 0 1px 2px rgb(0 0 0 / 8%); font-weight: 500; }
2076
+
2077
+ .oh-story-file-group,
2078
+ .oh-story-file-folder { margin: 0; }
2079
+ .oh-story-file-group > summary,
2080
+ .oh-story-file-folder > summary {
2081
+ display: flex;
2082
+ align-items: center;
2083
+ gap: 5px;
2084
+ min-height: 26px;
2085
+ padding: 0 7px;
2086
+ border-radius: 6px;
2087
+ color: var(--os-muted);
2088
+ cursor: pointer;
2089
+ list-style: none;
2090
+ font-size: 11px;
2091
+ font-weight: 600;
2092
+ }
2093
+ .oh-story-file-folder > summary { padding-left: min(calc(7px + var(--oh-story-indent, 0px)), 49px); }
2094
+ .oh-story-file-group > summary { margin-top: 3px; }
2095
+ .oh-story-file-group > summary::-webkit-details-marker,
2096
+ .oh-story-file-folder > summary::-webkit-details-marker { display: none; }
2097
+ .oh-story-file-group > summary::before,
2098
+ .oh-story-file-folder > summary::before { width: 8px; content: "\u203A"; transform: rotate(0); transition: transform .12s ease; }
2099
+ .oh-story-file-group[open] > summary::before,
2100
+ .oh-story-file-folder[open] > summary::before { transform: rotate(90deg); }
2101
+ .oh-story-file-group > summary:hover,
2102
+ .oh-story-file-folder > summary:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-interactive-bg-hover); }
2103
+ .oh-story-file-group > summary span,
2104
+ .oh-story-file-folder > summary span { margin-left: auto; color: var(--dsw-alias-label-secondary); font-weight: 400; }
2105
+
2106
+ .oh-story-tree nav button {
2107
+ display: block;
2108
+ width: 100%;
2109
+ overflow: hidden;
2110
+ padding: 6px 8px 6px min(calc(14px + var(--oh-story-indent, 0px)), 56px);
2111
+ border: 0;
2112
+ border-radius: 6px;
2113
+ color: inherit;
2114
+ background: transparent;
2115
+ text-align: left;
2116
+ text-overflow: ellipsis;
2117
+ white-space: nowrap;
2118
+ cursor: pointer;
2119
+ font: inherit;
2120
+ font-size: 12px;
2121
+ }
2122
+ .oh-story-tree nav button:hover { background: var(--dsw-alias-interactive-bg-hover); }
2123
+ .oh-story-tree nav button[aria-current="page"] { background: var(--dsw-alias-button-ghost-active-fill); color: var(--dsw-alias-label-primary); }
2124
+ .oh-story-tree nav button[data-agent-target] { box-shadow: inset 2px 0 var(--dsw-alias-state-business-primary); }
2125
+
2126
+ .oh-story-editor > header {
2127
+ min-height: 44px;
2128
+ flex: none;
2129
+ padding: 0 14px;
2130
+ border-bottom: 1px solid var(--os-border);
2131
+ }
2132
+
2133
+ .oh-story-editor-path {
2134
+ overflow: hidden;
2135
+ min-width: 0;
2136
+ flex: 1;
2137
+ text-overflow: ellipsis;
2138
+ white-space: nowrap;
2139
+ font-size: 12px;
2140
+ color: var(--os-muted);
2141
+ }
2142
+ .oh-story-editor-path > span { overflow: hidden; display: block; text-overflow: ellipsis; }
2143
+ .oh-story-editor-path > strong { display: none; overflow: hidden; text-overflow: ellipsis; font: inherit; }
2144
+
2145
+ .oh-story-editor-actions { display: flex; flex: none; align-items: center; gap: 8px; }
2146
+ .oh-story-editor-tabs {
2147
+ display: flex;
2148
+ padding: 2px;
2149
+ border: 1px solid var(--os-border);
2150
+ border-radius: 7px;
2151
+ background: var(--dsw-alias-bg-layer-1);
2152
+ }
2153
+ .oh-story-editor-tabs button { min-height: 24px; padding: 0 9px; border-radius: 5px; font-size: 11px; }
2154
+ .oh-story-save { min-height: 28px; }
2155
+
2156
+ .oh-story-editor textarea {
2157
+ box-sizing: border-box;
2158
+ width: 100%;
2159
+ min-height: 0;
2160
+ flex: 1;
2161
+ resize: none;
2162
+ border: 0;
2163
+ outline: 0;
2164
+ padding: clamp(20px, 6%, 48px) clamp(22px, 10%, 84px) 48px;
2165
+ color: var(--dsw-alias-label-primary);
2166
+ background: transparent;
2167
+ font: 16px/1.9 ui-serif, "Songti SC", "STSong", Georgia, serif;
2168
+ }
2169
+ .oh-story-editor textarea[data-format="structured"] {
2170
+ tab-size: 2;
2171
+ font: 13px/1.65 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
2172
+ }
2173
+
2174
+ .oh-story-markdown {
2175
+ box-sizing: border-box;
2176
+ width: 100%;
2177
+ min-height: 0;
2178
+ flex: 1;
2179
+ overflow: auto;
2180
+ padding: clamp(24px, 8%, 56px) clamp(24px, 10%, 92px) 64px;
2181
+ color: var(--dsw-alias-label-primary);
2182
+ font: 16px/1.85 ui-serif, "Songti SC", "STSong", Georgia, serif;
2183
+ overflow-wrap: anywhere;
2184
+ }
2185
+ .oh-story-markdown > :first-child { margin-top: 0; }
2186
+ .oh-story-markdown > :last-child { margin-bottom: 0; }
2187
+ .oh-story-markdown h1,
2188
+ .oh-story-markdown h2,
775
2189
  .oh-story-markdown h3,
776
2190
  .oh-story-markdown h4,
777
2191
  .oh-story-markdown h5,
@@ -876,67 +2290,536 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
876
2290
  .oh-story-role pre { max-height: 260px; overflow: auto; margin: 4px 0 4px 4px; padding: 12px 16px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px; color: var(--dsw-alias-label-secondary); background: var(--dsw-alias-markdown-code-block); white-space: pre-wrap; font: var(--dsw-font-markdown-code-block-small); }
877
2291
  .oh-story-role > button { align-self: flex-start; margin: 4px 0 2px 4px; font-size: 11px; }
878
2292
 
879
- [data-conversation-scroll][data-oh-story-layout="medium"]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
880
- grid-template-columns: clamp(104px, 18%, 184px) minmax(200px, 1fr) clamp(300px, 45%, 408px);
881
- }
2293
+ /* Short-drama production views are projections of the creator Markdown and
2294
+ DSH Session state. They intentionally use only DSH semantic tokens. */
2295
+ .oh-story-production { min-height: 0; flex: 1; overflow: auto; background: var(--dsw-alias-bg-base); font: 12px/1.5 var(--dsw-font-family); }
2296
+ .oh-story-production button,
2297
+ .oh-story-production select { border: 1px solid var(--os-border); border-radius: 6px; color: inherit; background: var(--dsw-alias-bg-base); padding: 4px 8px; cursor: pointer; font: inherit; }
2298
+ .oh-story-production button:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
2299
+ .oh-story-production button:disabled { opacity: .45; cursor: default; }
2300
+ .oh-story-production button:focus-visible,
2301
+ .oh-story-canvas article:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary); outline-offset: 2px; }
2302
+ .oh-story-production-bar,
2303
+ .oh-story-production-notice,
2304
+ .oh-story-production-actions,
2305
+ .oh-story-sequence-summary { display: flex; align-items: center; gap: 8px; }
2306
+ .oh-story-production-bar { position: sticky; z-index: 5; top: 0; justify-content: space-between; min-height: 44px; box-sizing: border-box; padding: 6px 12px; border-bottom: 1px solid var(--os-border); background: color-mix(in srgb, var(--dsw-alias-bg-base) 94%, transparent); backdrop-filter: blur(12px); }
2307
+ .oh-story-production-tabs { display: flex; align-self: stretch; gap: 16px; }
2308
+ .oh-story-production-tabs button { position: relative; border: 0; border-radius: 0; padding: 0; background: transparent; color: var(--os-muted); }
2309
+ .oh-story-production-tabs button[aria-selected="true"] { color: var(--dsw-alias-label-primary); }
2310
+ .oh-story-production-tabs button[aria-selected="true"]::after { position: absolute; right: 0; bottom: -6px; left: 0; height: 2px; border-radius: 2px; background: var(--dsw-alias-state-business-primary); content: ""; }
2311
+ .oh-story-production-meta { display: flex; align-items: center; gap: 8px; }
2312
+ .oh-story-production-meta > button { padding-block: 2px; color: var(--os-muted); }
2313
+ .oh-story-production-summary { color: var(--os-muted); white-space: nowrap; }
2314
+ .oh-story-production-notice { justify-content: space-between; margin: 8px 12px 0; padding: 7px 9px; border: 1px solid var(--os-border); border-radius: 7px; color: var(--dsw-alias-label-secondary); background: var(--dsw-alias-bg-layer-1); }
2315
+ .oh-story-production-notice button { border: 0; background: transparent; }
2316
+ .oh-story-production-diagnostics { margin: 8px 12px 0; padding: 7px 9px; border: 1px solid var(--dsw-alias-state-warn-label); border-radius: 7px; background: var(--dsw-specific-tip); }
2317
+ .oh-story-production-diagnostics summary { color: var(--dsw-alias-state-warn-label); cursor: pointer; }
2318
+ .oh-story-production-diagnostics ul { display: grid; gap: 4px; margin: 8px 0 0; padding: 0; list-style: none; }
2319
+ .oh-story-production-diagnostics li { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: start; gap: 6px; color: var(--dsw-alias-label-secondary); }
2320
+ .oh-story-production-diagnostics li[data-severity="error"] { color: var(--dsw-alias-state-error-primary); }
2321
+ .oh-story-production-diagnostics li button { border: 0; padding: 0; color: inherit; background: transparent; text-align: left; }
2322
+ .oh-story-production-diagnostics p { margin: 6px 0 0; color: var(--os-muted); }
2323
+ .oh-story-production-actions { justify-content: flex-end; margin-bottom: 12px; }
2324
+ .oh-story-production-actions button:last-child,
2325
+ .oh-story-sequence-summary button { color: var(--dsw-alias-state-business-primary); border-color: var(--dsw-alias-state-business-primary); }
2326
+ .oh-story-shot-board { padding: 16px 12px; }
2327
+ .oh-story-shot-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); align-items: start; gap: 12px; }
2328
+ .oh-story-shot-card,
2329
+ .oh-story-asset-card,
2330
+ .oh-story-task-board > article { min-width: 0; overflow: hidden; border: 1px solid var(--os-border); border-radius: 10px; background: var(--dsw-alias-bg-layer-1); }
2331
+ .oh-story-shot-card { padding: 10px; cursor: default; transition: border-color .15s ease, background-color .15s ease; }
2332
+ .oh-story-shot-card:hover { border-color: var(--dsw-alias-border-l2); }
2333
+ .oh-story-shot-card { cursor: pointer; }
2334
+ .oh-story-shot-card:focus-visible,
2335
+ .oh-story-asset-card:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary); outline-offset: 2px; }
2336
+ .oh-story-shot-card[data-selected],
2337
+ .oh-story-asset-card[data-selected] { border-color: var(--dsw-alias-state-business-primary); background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 4%, var(--dsw-alias-bg-layer-1)); }
2338
+ .oh-story-shot-card > header,
2339
+ .oh-story-task-board article > header { display: flex; min-height: 0; align-items: center; gap: 8px; padding: 0; border: 0; }
2340
+ .oh-story-shot-card > header > button { overflow: hidden; min-width: 0; flex: 1; border: 0; padding: 0; color: var(--dsw-alias-state-business-primary); background: transparent; text-align: left; text-overflow: ellipsis; white-space: nowrap; }
2341
+ .oh-story-shot-card > header > span { color: var(--os-muted); font-variant-numeric: tabular-nums; }
2342
+ .oh-story-shot-card h3,
2343
+ .oh-story-asset-card h3 { margin: 7px 0 4px; font-size: 13px; font-weight: 600; }
2344
+ .oh-story-shot-card > p,
2345
+ .oh-story-asset-card > p,
2346
+ .oh-story-task-board article > p { margin: 5px 0; color: var(--os-muted); }
2347
+ .oh-story-shot-placeholder { display: grid; height: 132px; place-content: center; gap: 3px; margin-bottom: 8px; border-radius: 7px; color: var(--os-muted); background: var(--dsw-alias-button-ghost-active-fill); text-align: center; }
2348
+ .oh-story-shot-placeholder strong { color: var(--dsw-alias-label-secondary); font-size: 22px; font-variant-numeric: tabular-nums; }
2349
+ .oh-story-shot-card dl { display: grid; grid-template-columns: 20px minmax(0, 1fr); margin: 7px 0; }
2350
+ .oh-story-shot-card dt { color: var(--dsw-alias-state-business-primary); }
2351
+ .oh-story-shot-card dd { min-width: 0; margin: 0; overflow-wrap: anywhere; }
2352
+ .oh-story-shot-status,
2353
+ .oh-story-reference-links,
2354
+ .oh-story-card-actions { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 7px; }
2355
+ .oh-story-shot-status span { padding: 1px 5px; border-radius: 5px; color: var(--os-muted); background: var(--dsw-alias-button-ghost-active-fill); }
2356
+ .oh-story-shot-status span[data-ready="true"] { color: var(--dsw-alias-state-business-primary); }
2357
+ .oh-story-shot-status span > i { margin-right: 3px; font-style: normal; }
2358
+ .oh-story-reference-links button { max-width: 100%; overflow: hidden; padding: 1px 5px; border: 0; color: var(--dsw-alias-state-business-primary); background: transparent; text-overflow: ellipsis; }
2359
+ .oh-story-card-actions { padding-top: 8px; border-top: 1px solid var(--os-border); }
2360
+ .oh-story-card-actions button { flex: 1; padding-block: 4px; }
2361
+ .oh-story-card-actions button:last-child { color: var(--dsw-alias-state-business-primary); border-color: color-mix(in srgb, var(--dsw-alias-state-business-primary) 45%, var(--os-border)); }
2362
+ .oh-story-media-preview { display: block; width: 100%; height: 168px; border-radius: 7px; background: var(--dsw-alias-bg-base); object-fit: contain; }
2363
+ .oh-story-media-document { display: grid; min-height: 0; flex: 1; overflow: auto; place-items: center; padding: 24px; }
2364
+ .oh-story-media-document img,
2365
+ .oh-story-media-document video { display: block; max-width: 100%; max-height: 100%; object-fit: contain; }
2366
+ .oh-story-media-document audio { width: min(520px, 100%); }
2367
+ .oh-story-version-strip { display: flex; gap: 5px; overflow-x: auto; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--os-border); }
2368
+ .oh-story-version-strip > button { position: relative; width: 64px; min-width: 64px; padding: 2px; }
2369
+ .oh-story-version-strip > button[data-selected] { border-color: var(--dsw-alias-state-business-primary); }
2370
+ .oh-story-version-strip .oh-story-media-preview { height: 72px; }
2371
+ .oh-story-version-strip span { position: absolute; right: 3px; bottom: 3px; padding: 0 3px; border-radius: 3px; background: var(--dsw-alias-bg-base); font-size: 10px; }
2372
+ .oh-story-asset-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; padding: 16px 12px; }
2373
+ .oh-story-asset-card { padding: 10px; }
2374
+ .oh-story-asset-card > div:nth-child(2) > button { overflow: hidden; max-width: 100%; border: 0; padding: 0; color: var(--dsw-alias-state-business-primary); background: transparent; text-overflow: ellipsis; }
2375
+ .oh-story-asset-card small { color: var(--os-muted); text-transform: uppercase; }
2376
+ .oh-story-asset-placeholder { display: grid; height: 112px; place-items: center; border-radius: 7px; color: var(--os-muted); background: var(--dsw-alias-button-ghost-active-fill); font-size: 28px; }
2377
+ .oh-story-asset-description { display: -webkit-box; overflow: hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 5; line-clamp: 5; }
2378
+ .oh-story-media-library { margin: 0 12px 14px; padding-top: 12px; border-top: 1px solid var(--os-border); }
2379
+ .oh-story-media-library > header { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
2380
+ .oh-story-media-library > header > div { display: flex; align-items: center; gap: 7px; }
2381
+ .oh-story-media-library > header span { color: var(--os-muted); }
2382
+ .oh-story-media-library input { width: 180px; box-sizing: border-box; border: 1px solid var(--os-border); border-radius: 6px; padding: 5px 8px; color: inherit; background: var(--dsw-alias-bg-base); font: inherit; }
2383
+ .oh-story-media-library > .oh-story-projection-note { margin-bottom: 10px; }
2384
+ .oh-story-media-library-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 8px; }
2385
+ .oh-story-media-library-grid > article { display: flex; min-width: 0; flex-direction: column; gap: 5px; padding: 7px; border: 1px solid var(--os-border); border-radius: 8px; background: var(--dsw-alias-bg-layer-1); }
2386
+ .oh-story-media-library-grid .oh-story-media-preview { height: 116px; }
2387
+ .oh-story-media-library-grid > article > span { overflow: hidden; color: var(--os-muted); font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }
2388
+ .oh-story-media-library-grid footer { display: flex; gap: 4px; margin-top: auto; }
2389
+ .oh-story-media-library-grid footer button { min-width: 0; flex: 1; padding-inline: 4px; }
2390
+ .oh-story-media-library-grid footer button[aria-pressed="true"] { color: var(--dsw-alias-state-business-primary); border-color: var(--dsw-alias-state-business-primary); }
2391
+ .oh-story-task-board { display: grid; gap: 10px; padding: 12px; }
2392
+ .oh-story-task-board > article { padding: 11px; }
2393
+ .oh-story-task-board > article[data-status="dispatched_unknown"] { border-color: var(--dsw-alias-state-warn-label); }
2394
+ .oh-story-task-board > article[data-status="dispatched_unknown"] .oh-story-error { color: var(--dsw-alias-state-warn-label); background: var(--dsw-specific-tip); }
2395
+ .oh-story-task-board article > header strong { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; }
2396
+ .oh-story-task-board article > header span { padding: 1px 5px; border-radius: 5px; color: var(--os-muted); background: var(--dsw-alias-button-ghost-active-fill); }
2397
+ .oh-story-task-progress { height: 3px; overflow: hidden; margin-top: 8px; border-radius: 3px; background: var(--os-border); }
2398
+ .oh-story-task-progress i { display: block; height: 100%; background: var(--dsw-alias-state-business-primary); transition: width .2s ease; }
2399
+ .oh-story-task-board details { margin-top: 8px; color: var(--os-muted); }
2400
+ .oh-story-task-board details summary { cursor: pointer; }
2401
+ .oh-story-task-board details p { max-height: 120px; overflow: auto; margin: 6px 0; padding: 8px; border-radius: 6px; background: var(--dsw-alias-bg-base); white-space: pre-wrap; }
2402
+ .oh-story-task-board footer { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 5px; margin-top: 8px; }
2403
+ .oh-story-projection-note { padding: 7px 10px; border: 1px solid var(--os-border); border-radius: 7px; color: var(--os-muted); background: var(--dsw-alias-bg-layer-1); }
2404
+ .oh-story-task-board > .oh-story-projection-note { margin-bottom: 0; }
2405
+ .oh-story-production-empty { padding: 48px 20px; color: var(--os-muted); text-align: center; }
2406
+ .oh-story-production-empty strong { display: block; margin-bottom: 6px; color: var(--dsw-alias-text-1); font-size: 13px; }
2407
+ .oh-story-production-empty p { margin: 0 auto; max-width: 44em; line-height: 1.6; }
2408
+ .oh-story-production-empty button { margin-top: 12px; }
2409
+ .oh-story-sequence { padding: 12px; }
2410
+ .oh-story-sequence-summary { justify-content: space-between; margin-bottom: 10px; }
2411
+ .oh-story-sequence-summary span { margin-left: auto; color: var(--os-muted); }
2412
+ .oh-story-sequence-issues { margin: 0 0 10px; padding: 8px 8px 8px 28px; border-radius: 7px; color: var(--dsw-alias-state-warn-label); background: var(--dsw-specific-tip); }
2413
+ .oh-story-sequence-issues li + li { margin-top: 2px; }
2414
+ .oh-story-sequence ol { display: grid; gap: 6px; margin: 0; padding: 0; list-style: none; }
2415
+ .oh-story-sequence > ol > li { display: grid; min-height: 70px; grid-template-columns: 28px 96px minmax(0, 1fr) auto; align-items: center; gap: 8px; padding: 7px; border: 1px solid var(--os-border); border-radius: 8px; background: var(--dsw-alias-bg-layer-1); }
2416
+ .oh-story-sequence > ol > li > span { color: var(--os-muted); font-variant-numeric: tabular-nums; }
2417
+ .oh-story-sequence > ol > li .oh-story-media-preview,
2418
+ .oh-story-sequence-missing { width: 96px; height: 54px; }
2419
+ .oh-story-sequence-missing { display: grid; place-items: center; border-radius: 5px; color: var(--os-muted); background: var(--dsw-alias-button-ghost-active-fill); }
2420
+ .oh-story-sequence > ol > li > div:last-child { display: flex; gap: 3px; }
2421
+ .oh-story-canvas-shell { position: relative; display: flex; height: max(580px, calc(100% - 84px)); min-height: 580px; flex-direction: column; overflow: hidden; }
2422
+ .oh-story-canvas-shell > .oh-story-projection-note { z-index: 3; flex: none; margin: 8px 168px 8px 8px; }
2423
+ .oh-story-canvas-controls { position: absolute; z-index: 4; top: 8px; right: 8px; display: flex; align-items: center; gap: 3px; padding: 3px; border: 1px solid var(--os-border); border-radius: 8px; background: var(--dsw-alias-bg-base); }
2424
+ .oh-story-canvas-controls span { min-width: 44px; color: var(--os-muted); text-align: center; }
2425
+ .oh-story-canvas-viewport { width: 100%; min-height: 0; flex: 1; overflow: auto; background-color: var(--dsw-alias-bg-base); background-image: radial-gradient(var(--os-border) 1px, transparent 1px); background-size: 20px 20px; }
2426
+ .oh-story-canvas { position: relative; width: 1800px; height: 1800px; transform-origin: 0 0; }
2427
+ .oh-story-canvas svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }
2428
+ .oh-story-canvas path { fill: none; opacity: .35; stroke: var(--dsw-alias-border-l2); stroke-width: 1.5; }
2429
+ .oh-story-canvas path[data-active] { opacity: .8; stroke: var(--dsw-alias-state-business-primary); }
2430
+ .oh-story-canvas article { position: absolute; display: flex; width: 180px; min-height: 76px; box-sizing: border-box; flex-direction: column; gap: 4px; padding: 10px; border: 1px solid var(--os-border); border-radius: 8px; background: var(--dsw-alias-bg-layer-1); box-shadow: 0 4px 14px rgb(0 0 0 / 8%); cursor: grab; user-select: none; touch-action: none; }
2431
+ .oh-story-canvas article:active { cursor: grabbing; }
2432
+ .oh-story-canvas article[data-node-type="shot"] { border-color: var(--dsw-alias-state-business-primary); }
2433
+ .oh-story-canvas article[data-selected] { box-shadow: 0 0 0 2px color-mix(in srgb, var(--dsw-alias-state-business-primary) 28%, transparent), 0 4px 14px rgb(0 0 0 / 8%); }
2434
+ .oh-story-canvas article small { color: var(--os-muted); text-transform: uppercase; }
2435
+ .oh-story-canvas article strong,
2436
+ .oh-story-canvas article span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2437
+ .oh-story-canvas article span { color: var(--os-muted); font-size: 10px; }
2438
+
2439
+ [data-conversation-scroll][data-oh-story-layout="medium"]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
2440
+ grid-template-columns: clamp(104px, 18%, 184px) minmax(200px, 1fr) clamp(300px, 45%, 408px);
2441
+ }
2442
+
2443
+ [data-conversation-scroll][data-oh-story-layout="compact"]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
2444
+ grid-template-columns: clamp(80px, 16%, 104px) minmax(0, 1fr) clamp(228px, 48%, 300px);
2445
+ }
2446
+
2447
+ [data-oh-story-layout="medium"] .oh-story-file-folder > summary { padding-left: min(calc(7px + var(--oh-story-indent, 0px)), 35px); }
2448
+ [data-oh-story-layout="medium"] .oh-story-tree nav button { padding-left: min(calc(14px + var(--oh-story-indent, 0px)), 42px); }
2449
+ [data-oh-story-layout="medium"] .oh-story-editor textarea { padding-right: 28px; padding-left: 28px; }
2450
+
2451
+ [data-oh-story-layout="compact"] .oh-story-tree { padding-inline: 4px; }
2452
+ [data-oh-story-layout="compact"] .oh-story-brand { gap: 4px; padding-inline: 2px; font-size: 11px; }
2453
+ [data-oh-story-layout="compact"] .oh-story-brand-cluster { gap: 4px; }
2454
+ [data-oh-story-layout="compact"] .oh-story-brand-cluster strong > span { display: none; }
2455
+ [data-oh-story-layout="compact"] .oh-story-kind { padding-inline: 4px; font-size: 11px; }
2456
+ [data-oh-story-layout="compact"] .oh-story-brand button { width: 24px; height: 24px; }
2457
+ [data-oh-story-layout="compact"] .oh-story-mode-tabs { margin-inline: 0; }
2458
+ [data-oh-story-layout="compact"] .oh-story-mode-tabs button { padding-inline: 3px; font-size: 11px; }
2459
+ [data-oh-story-layout="compact"] .oh-story-file-folder > summary { padding-left: min(calc(5px + var(--oh-story-indent, 0px)), 19px); }
2460
+ [data-oh-story-layout="compact"] .oh-story-tree nav button { padding-right: 4px; padding-left: min(calc(7px + var(--oh-story-indent, 0px)), 21px); }
2461
+ [data-oh-story-layout="compact"] .oh-story-editor > header {
2462
+ display: grid;
2463
+ min-height: 68px;
2464
+ grid-template-columns: minmax(0, 1fr);
2465
+ grid-template-rows: 24px 36px;
2466
+ align-content: center;
2467
+ gap: 0;
2468
+ padding: 4px 3px;
2469
+ }
2470
+ [data-oh-story-layout="compact"] .oh-story-editor-path > span { display: none; }
2471
+ [data-oh-story-layout="compact"] .oh-story-editor-path > strong { display: block; font-size: 11px; line-height: 24px; }
2472
+ [data-oh-story-layout="compact"] .oh-story-editor-actions { width: 100%; min-width: 0; justify-content: flex-end; gap: 4px; }
2473
+ [data-oh-story-layout="compact"] .oh-story-editor-tabs { min-width: 0; flex: 1; }
2474
+ [data-oh-story-layout="compact"] .oh-story-editor-tabs button { min-width: 0; padding-inline: 2px; white-space: nowrap; }
2475
+ [data-oh-story-layout="compact"] .oh-story-save { flex: none; padding-inline: 4px; white-space: nowrap; font-size: 11px; }
2476
+ [data-oh-story-layout="compact"] .oh-story-editor textarea { padding: 16px 12px 40px; }
2477
+ [data-oh-story-layout="compact"] .oh-story-markdown { padding: 18px 12px 48px; }
2478
+ [data-oh-story-layout="compact"] .oh-story-jsonl-summary { padding-inline: 10px; }
2479
+ [data-oh-story-layout="compact"] .oh-story-jsonl-records { padding: 8px 7px 28px; }
2480
+ [data-oh-story-layout="compact"] .oh-story-jsonl summary { gap: 6px; padding-inline: 7px; }
2481
+ [data-oh-story-layout="compact"] .oh-story-jsonl summary > span { display: none; }
2482
+ [data-oh-story-layout="compact"] .oh-story-jsonl summary > strong { min-width: 32px; }
2483
+ [data-oh-story-layout="compact"] .oh-story-jsonl summary > code,
2484
+ [data-oh-story-layout="compact"] .oh-story-jsonl summary > em { display: none; }
2485
+
2486
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="medium"]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
2487
+ grid-template-columns: minmax(0, 1fr) clamp(360px, 42%, 440px);
2488
+ }
2489
+ [data-oh-story-workbench="game"][data-oh-story-layout="medium"] .oh-game-toolbar { grid-template-columns: auto minmax(140px, 1fr) auto; }
2490
+
2491
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
2492
+ display: grid;
2493
+ grid-template-columns: minmax(0, 1fr);
2494
+ overflow: hidden;
2495
+ }
2496
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"]
2497
+ > [data-slot="conversation.session"] > :not(.oh-story-split-surface),
2498
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"]
2499
+ > [data-composer-seat],
2500
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-studio {
2501
+ grid-column: 1;
2502
+ }
2503
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"][data-oh-studio-pane="studio"]
2504
+ > [data-slot="conversation.session"] > :not(.oh-story-split-surface),
2505
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"][data-oh-studio-pane="studio"]
2506
+ > [data-composer-seat],
2507
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"][data-oh-studio-pane="chat"] .oh-game-studio { display: none; }
2508
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-mobile-switcher {
2509
+ display: flex;
2510
+ position: absolute;
2511
+ top: 8px;
2512
+ right: 8px;
2513
+ z-index: 20;
2514
+ border-color: var(--dsw-alias-border-l1);
2515
+ box-shadow: 0 4px 14px rgb(0 0 0 / 12%);
2516
+ }
2517
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-mobile-switcher button { min-width: 52px; min-height: 32px; }
2518
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-toolbar {
2519
+ min-height: 101px;
2520
+ grid-template-columns: auto minmax(0, 1fr) 112px;
2521
+ grid-template-rows: 44px 44px;
2522
+ align-content: center;
2523
+ gap: 5px;
2524
+ padding: 4px 8px;
2525
+ }
2526
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-mode-tabs { grid-column: 1; grid-row: 1; width: fit-content; }
2527
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-project > span { display: none; }
2528
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-project { grid-column: 1 / -1; grid-row: 2; }
2529
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-project select { max-width: none; width: 100%; min-height: 44px; }
2530
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-tabs { grid-column: 2; grid-row: 1; width: fit-content; justify-self: end; }
2531
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-mode-tabs button,
2532
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-tabs button,
2533
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-mobile-switcher button { min-height: 44px; }
2534
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-preview-shell { grid-template-rows: 46px minmax(0, 1fr); }
2535
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-preview-status button { min-height: 36px; }
2536
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-reload b { display: none; }
2537
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-preview-status { padding-left: 8px; }
2538
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-preview-status > span { max-width: 40%; }
2539
+ .oh-game-runtime-state > em { display: block; overflow: hidden; min-width: 0; font-style: normal; text-overflow: ellipsis; white-space: nowrap; }
2540
+
2541
+ /* DSH may keep its navigation drawer open at a 500 px browser viewport,
2542
+ leaving the conversation center close to 212 px wide. The Studio publishes
2543
+ its measured width because a container style query cannot style the
2544
+ container itself. Row one stays clear for the \u5236\u4F5C / \u5BF9\u8BDD switch. */
2545
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-studio[data-oh-game-narrow] .oh-game-toolbar {
2546
+ min-height: 193px;
2547
+ grid-template-columns: minmax(0, 1fr);
2548
+ grid-template-rows: repeat(4, 44px);
2549
+ align-content: center;
2550
+ padding: 4px 8px;
2551
+ }
2552
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-studio[data-oh-game-narrow] .oh-game-mode-tabs {
2553
+ grid-column: 1;
2554
+ grid-row: 2;
2555
+ justify-self: start;
2556
+ }
2557
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-studio[data-oh-game-narrow] .oh-game-tabs {
2558
+ grid-column: 1;
2559
+ grid-row: 3;
2560
+ justify-self: start;
2561
+ }
2562
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-studio[data-oh-game-narrow] .oh-game-project {
2563
+ grid-column: 1;
2564
+ grid-row: 4;
2565
+ }
2566
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-studio[data-oh-game-narrow] .oh-game-project select {
2567
+ width: 100%;
2568
+ max-width: none;
2569
+ min-height: 44px;
2570
+ }
2571
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-studio[data-oh-game-narrow] .oh-game-preview-status {
2572
+ gap: 4px;
2573
+ padding-right: 6px;
2574
+ padding-left: 6px;
2575
+ }
2576
+ [data-conversation-scroll][data-oh-story-workbench="game"][data-oh-story-layout="compact"] .oh-game-studio[data-oh-game-narrow] .oh-game-preview-status > span { max-width: 36%; }
2577
+
2578
+ @media (prefers-reduced-motion: reduce) {
2579
+ .oh-game-preview-shell[data-state="building"] { animation: none; }
2580
+ }
2581
+
2582
+ @media (prefers-contrast: more) {
2583
+ .oh-game-preview-status button,
2584
+ .oh-game-tabs,
2585
+ .oh-game-mode-tabs,
2586
+ .oh-game-mobile-switcher { border-width: 2px; }
2587
+ }
2588
+ [data-oh-story-layout="compact"] .oh-story-production-summary { display: none; }
2589
+ [data-oh-story-layout="compact"] .oh-story-production-bar { flex-wrap: wrap; padding-inline: 4px; }
2590
+ [data-oh-story-layout="compact"] .oh-story-production-tabs { width: 100%; gap: 0; }
2591
+ [data-oh-story-layout="compact"] .oh-story-production-tabs button { min-width: 0; flex: 1; padding-inline: 0; font-size: 11px; }
2592
+ [data-oh-story-layout="compact"] .oh-story-production-meta { width: 100%; justify-content: flex-end; }
2593
+ [data-oh-story-layout="compact"] .oh-story-shot-grid,
2594
+ [data-oh-story-layout="compact"] .oh-story-asset-grid { grid-template-columns: minmax(0, 1fr); }
2595
+ [data-oh-story-layout="compact"] .oh-story-media-library > header { align-items: stretch; flex-direction: column; }
2596
+ [data-oh-story-layout="compact"] .oh-story-media-library > header > div:last-child { display: grid; grid-template-columns: minmax(0, 1fr) auto; }
2597
+ [data-oh-story-layout="compact"] .oh-story-media-library input { width: 100%; }
2598
+ [data-oh-story-layout="compact"] .oh-story-sequence { padding-inline: 6px; }
2599
+ [data-oh-story-layout="compact"] .oh-story-sequence-summary { flex-wrap: wrap; }
2600
+ [data-oh-story-layout="compact"] .oh-story-sequence-summary span { display: none; }
2601
+ [data-oh-story-layout="compact"] .oh-story-sequence > ol > li { grid-template-columns: 24px 64px minmax(0, 1fr); gap: 5px; }
2602
+ [data-oh-story-layout="compact"] .oh-story-sequence > ol > li .oh-story-media-preview,
2603
+ [data-oh-story-layout="compact"] .oh-story-sequence-missing { width: 64px; height: 40px; }
2604
+ [data-oh-story-layout="compact"] .oh-story-sequence > ol > li > div:last-child { grid-column: 2 / 4; justify-content: flex-end; }
2605
+ [data-oh-story-layout="compact"] .oh-story-canvas-shell > .oh-story-projection-note { overflow: hidden; max-height: 48px; margin: 8px; }
2606
+ [data-oh-story-layout="compact"] .oh-story-canvas-controls { position: static; flex: none; align-self: flex-end; margin: 0 8px 8px; }
2607
+
2608
+ /* Video mode intentionally stays a preview surface: one player, a read-only artifact view,
2609
+ and the official DSH Conversation. It shares no nonlinear-editor or timeline runtime. */
2610
+ [data-conversation-scroll][data-oh-story-workbench="video"]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
2611
+ --video-stage: #0b0d10;
2612
+ --video-stage-raised: #15181d;
2613
+ --video-accent: var(--dsw-alias-state-business-primary);
2614
+ --video-warning: #f4bd55;
2615
+ --video-danger: #f06b63;
2616
+ grid-template-columns: minmax(0, 1fr) clamp(400px, 34%, 520px);
2617
+ overflow: hidden;
2618
+ }
2619
+ [data-conversation-scroll][data-oh-story-workbench="video"]:has(> [data-slot="conversation.session"] > .oh-story-split-surface)
2620
+ > [data-slot="conversation.session"] > :not(.oh-story-split-surface),
2621
+ [data-conversation-scroll][data-oh-story-workbench="video"]:has(> [data-slot="conversation.session"] > .oh-story-split-surface)
2622
+ > [data-composer-seat] { grid-column: 2; }
2623
+
2624
+ .oh-video-studio {
2625
+ box-sizing: border-box;
2626
+ display: flex;
2627
+ grid-column: 1;
2628
+ grid-row: 1;
2629
+ position: sticky;
2630
+ top: 0;
2631
+ min-width: 0;
2632
+ min-height: 0;
2633
+ height: 100%;
2634
+ align-self: start;
2635
+ overflow: hidden;
2636
+ flex-direction: column;
2637
+ border-right: 1px solid var(--os-border);
2638
+ color: var(--dsw-alias-label-primary);
2639
+ background: var(--dsw-alias-bg-base);
2640
+ }
2641
+ .oh-video-studio[hidden] { display: none; }
2642
+ .oh-video-toolbar {
2643
+ box-sizing: border-box;
2644
+ display: grid;
2645
+ min-height: 100px;
2646
+ flex: none;
2647
+ grid-template-columns: minmax(0, 1fr) auto;
2648
+ grid-template-rows: 40px 40px;
2649
+ align-items: center;
2650
+ gap: 4px 12px;
2651
+ padding: 8px 12px 9px;
2652
+ border-bottom: 1px solid var(--os-border);
2653
+ background: var(--dsw-alias-bg-base);
2654
+ }
2655
+ .oh-video-mode-tabs { display: flex; width: fit-content; grid-column: 1; grid-row: 1; padding: 2px; border: 1px solid var(--os-border); border-radius: 9px; background: var(--dsw-alias-bg-layer-1); }
2656
+ .oh-video-mode-tabs button,
2657
+ .oh-video-tabs button,
2658
+ .oh-video-version-tabs button {
2659
+ min-height: 36px;
2660
+ border: 0;
2661
+ border-radius: 6px;
2662
+ padding: 4px 8px;
2663
+ color: var(--os-muted);
2664
+ background: transparent;
2665
+ cursor: pointer;
2666
+ white-space: nowrap;
2667
+ font: 12px/1 var(--dsw-font-family);
2668
+ }
2669
+ .oh-video-mode-tabs button { min-width: 48px; }
2670
+ .oh-video-mode-tabs button:hover,
2671
+ .oh-video-tabs button:hover,
2672
+ .oh-video-version-tabs button:hover { color: var(--dsw-alias-label-primary); }
2673
+ .oh-video-mode-tabs button[aria-selected="true"],
2674
+ .oh-video-tabs button[aria-selected="true"],
2675
+ .oh-video-version-tabs button[aria-selected="true"] { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-base); box-shadow: 0 1px 2px rgb(0 0 0 / 9%); font-weight: 600; }
2676
+ .oh-video-project { display: flex; min-width: 0; grid-column: 1; grid-row: 2; align-items: center; gap: 9px; color: var(--os-muted); font-size: 11px; }
2677
+ .oh-video-project > span { flex: none; }
2678
+ .oh-video-project select {
2679
+ width: min(100%, 440px);
2680
+ min-width: 0;
2681
+ min-height: 36px;
2682
+ border: 1px solid var(--os-border);
2683
+ border-radius: 8px;
2684
+ padding: 6px 28px 6px 9px;
2685
+ color: var(--dsw-alias-label-primary);
2686
+ background: var(--dsw-alias-bg-base);
2687
+ overflow: hidden;
2688
+ text-overflow: ellipsis;
2689
+ font: inherit;
2690
+ font-weight: 500;
2691
+ }
2692
+ .oh-video-stage { display: inline-flex; grid-column: 2; grid-row: 2; align-items: center; gap: 7px; border: 1px solid var(--os-border); border-radius: 999px; padding: 5px 9px; color: var(--os-muted); background: var(--dsw-alias-bg-layer-1); white-space: nowrap; font-size: 11px; }
2693
+ .oh-video-stage > i { width: 7px; height: 7px; border-radius: 50%; background: var(--video-warning); }
2694
+ .oh-video-stage[data-state="ready"] > i { background: var(--dsw-alias-state-success-primary, #45b96f); }
2695
+ .oh-video-stage[data-state="not-started"] > i { background: var(--os-muted); }
2696
+ .oh-video-tabs,
2697
+ .oh-video-version-tabs { display: flex; padding: 2px; border: 1px solid var(--os-border); border-radius: 9px; }
2698
+ .oh-video-tabs { grid-column: 2; grid-row: 1; }
2699
+ .oh-video-tabs button { min-width: 56px; }
2700
+ .oh-video-panels,
2701
+ .oh-video-panels > [role="tabpanel"] { display: flex; min-width: 0; min-height: 0; flex: 1; overflow: hidden; flex-direction: column; }
2702
+ .oh-video-panels > [hidden] { display: none; }
2703
+
2704
+ .oh-video-preview-shell { display: grid; min-width: 0; min-height: 0; flex: 1; grid-template-rows: 48px minmax(0, 1fr); overflow: hidden; border-top: 2px solid var(--video-accent); background: var(--video-stage); }
2705
+ .oh-video-preview-shell[data-state="building"] { border-top-color: var(--video-warning); }
2706
+ .oh-video-preview-shell[data-state="error"] { border-top-color: var(--video-danger); }
2707
+ .oh-video-stagebar { display: grid; min-width: 0; grid-template-columns: auto minmax(80px, 1fr) auto; align-items: center; gap: 10px; padding: 0 10px; color: rgb(244 248 255 / 74%); background: var(--video-stage-raised); font-size: 11px; }
2708
+ .oh-video-version-tabs { border-color: rgb(255 255 255 / 13%); }
2709
+ .oh-video-version-tabs button[aria-selected="true"] { color: #fff; background: rgb(255 255 255 / 12%); }
2710
+ .oh-video-runtime-state { display: flex; min-width: 0; align-items: center; justify-content: center; gap: 7px; overflow: hidden; }
2711
+ .oh-video-runtime-state > i { width: 6px; height: 6px; flex: none; border-radius: 50%; background: var(--dsw-alias-state-success-primary, #45b96f); }
2712
+ .oh-video-preview-shell[data-state="building"] .oh-video-runtime-state > i { background: var(--video-warning); }
2713
+ .oh-video-preview-shell[data-state="error"] .oh-video-runtime-state > i { background: var(--video-danger); }
2714
+ .oh-video-runtime-state > em { overflow: hidden; font-style: normal; text-overflow: ellipsis; white-space: nowrap; }
2715
+ .oh-video-preview-actions { display: flex; gap: 5px; }
2716
+ .oh-video-preview-actions button,
2717
+ .oh-video-preview-actions a { display: grid; min-width: 34px; min-height: 34px; box-sizing: border-box; place-items: center; border: 1px solid rgb(255 255 255 / 14%); border-radius: 6px; padding: 3px 8px; color: rgb(255 255 255 / 84%); background: rgb(255 255 255 / 5%); cursor: pointer; text-decoration: none; font: inherit; }
2718
+ .oh-video-preview-actions svg { width: 16px; height: 16px; }
2719
+ .oh-video-preview-actions button:hover,
2720
+ .oh-video-preview-actions a:hover { background: rgb(255 255 255 / 11%); }
2721
+ .oh-video-player-stage { display: grid; min-width: 0; min-height: 0; place-items: center; overflow: hidden; padding: 14px; background: radial-gradient(circle at 50% 40%, #1b2027, var(--video-stage) 62%); }
2722
+ .oh-video-player-stage video { display: block; width: min(100%, 1200px); height: auto; max-width: 100%; max-height: 100%; border-radius: 4px; object-fit: contain; background: #000; box-shadow: 0 16px 50px rgb(0 0 0 / 32%); }
2723
+ .oh-video-preview-shell:fullscreen { width: 100vw; height: 100vh; }
882
2724
 
883
- [data-conversation-scroll][data-oh-story-layout="compact"]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
884
- grid-template-columns: clamp(80px, 16%, 104px) minmax(0, 1fr) clamp(228px, 48%, 300px);
885
- }
2725
+ .oh-video-preview-empty,
2726
+ .oh-video-artifacts-empty { display: grid; max-width: 620px; margin: auto; place-items: center; gap: 9px; padding: 36px; color: var(--os-muted); text-align: center; line-height: 1.65; }
2727
+ .oh-video-preview-empty { width: 100%; max-width: none; min-height: 0; box-sizing: border-box; flex: 1; align-content: center; color: rgb(244 248 255 / 70%); background: var(--video-stage); }
2728
+ .oh-video-preview-empty > span { color: var(--video-accent); font-size: 32px; }
2729
+ .oh-video-preview-empty strong { color: rgb(255 255 255 / 92%); }
2730
+ .oh-video-preview-empty p { max-width: 560px; margin: 0; }
2731
+ .oh-video-preview-empty code { color: rgb(255 255 255 / 88%); }
2732
+ .oh-video-prompt-example { max-width: 560px; margin-top: 10px; padding: 12px 14px; border: 1px solid rgb(255 255 255 / 11%); border-radius: 9px; background: rgb(255 255 255 / 4%); text-align: left; }
2733
+ .oh-video-prompt-example span { display: block; margin-bottom: 4px; color: var(--video-accent); font-size: 10px; font-weight: 600; }
2734
+ .oh-video-prompt-example q { quotes: none; }
886
2735
 
887
- [data-oh-story-layout="medium"] .oh-story-file-folder > summary { padding-left: min(calc(7px + var(--oh-story-indent, 0px)), 35px); }
888
- [data-oh-story-layout="medium"] .oh-story-tree nav button { padding-left: min(calc(14px + var(--oh-story-indent, 0px)), 42px); }
889
- [data-oh-story-layout="medium"] .oh-story-editor textarea { padding-right: 28px; padding-left: 28px; }
2736
+ .oh-video-artifacts { display: grid; min-width: 0; min-height: 0; flex: 1; grid-template-columns: minmax(150px, 220px) minmax(0, 1fr); overflow: hidden; }
2737
+ .oh-video-artifacts > aside { display: flex; min-height: 0; flex-direction: column; overflow: hidden; padding: 8px; border-right: 1px solid var(--os-border); background: var(--dsw-alias-bg-layer-1); }
2738
+ .oh-video-artifacts-heading { display: flex; min-height: 30px; flex: none; align-items: center; justify-content: space-between; padding: 0 7px; }
2739
+ .oh-video-artifacts-heading strong { font-size: 11px; }
2740
+ .oh-video-artifacts-heading span { min-width: 20px; border-radius: 999px; padding: 1px 5px; color: var(--os-muted); background: var(--dsw-alias-button-ghost-active-fill); text-align: center; font-size: 10px; }
2741
+ .oh-video-artifacts > aside nav { min-height: 0; flex: 1; overflow: auto; }
2742
+ .oh-video-artifacts > aside nav button { display: flex; width: 100%; min-width: 0; position: relative; flex-direction: column; align-items: flex-start; gap: 3px; border: 0; border-radius: 7px; padding: 9px; color: inherit; background: transparent; cursor: pointer; text-align: left; }
2743
+ .oh-video-artifacts > aside nav button:hover,
2744
+ .oh-video-artifacts > aside nav button[aria-current="page"] { background: var(--dsw-alias-button-ghost-active-fill); }
2745
+ .oh-video-artifacts > aside nav button[aria-current="page"]::before { content: ""; width: 2px; position: absolute; inset-block: 8px; left: 2px; border-radius: 2px; background: var(--video-accent); }
2746
+ .oh-video-artifacts > aside nav small { width: 100%; overflow: hidden; color: var(--os-muted); text-overflow: ellipsis; white-space: nowrap; }
2747
+ .oh-video-artifacts > aside nav > p { margin: 10px 4px; color: var(--os-muted); font-size: 11px; line-height: 1.5; }
2748
+ .oh-video-environment { display: grid; gap: 5px; margin-bottom: 8px; padding: 8px; border: 1px solid var(--os-border); border-radius: 8px; background: var(--dsw-alias-bg-layer-1); }
2749
+ .oh-video-environment strong { font-size: 11px; }
2750
+ .oh-video-environment span { color: var(--os-muted); font-size: 10px; }
2751
+ .oh-video-environment em { color: var(--os-muted); font-size: 10px; font-style: normal; line-height: 1.5; }
2752
+ .oh-video-environment code { font-size: 10px; }
2753
+ .oh-video-environment span::before { content: "\u25CF"; margin-right: 5px; color: var(--video-danger); }
2754
+ .oh-video-environment span[data-ready]::before { color: var(--dsw-alias-state-success-primary, #45b96f); }
2755
+ .oh-video-environment button { min-height: 30px; border: 1px solid var(--os-border); border-radius: 6px; color: inherit; background: var(--dsw-alias-bg-base); cursor: pointer; font: inherit; }
2756
+ .oh-video-artifacts > section { display: flex; min-width: 0; min-height: 0; overflow: hidden; flex-direction: column; }
2757
+ .oh-video-artifact-header { display: flex; min-height: 54px; box-sizing: border-box; flex: none; align-items: center; justify-content: space-between; gap: 12px; padding: 8px 16px; border-bottom: 1px solid var(--os-border); }
2758
+ .oh-video-artifact-header > div { display: flex; min-width: 0; flex-direction: column; gap: 3px; }
2759
+ .oh-video-artifact-header strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }
2760
+ .oh-video-artifact-header span { overflow: hidden; color: var(--os-muted); text-overflow: ellipsis; white-space: nowrap; font-size: 10px; }
2761
+ .oh-video-artifact-header em { flex: none; border: 1px solid var(--os-border); border-radius: 999px; padding: 3px 7px; color: var(--os-muted); background: var(--dsw-alias-bg-layer-1); font-size: 10px; font-style: normal; }
2762
+ .oh-video-artifact-content { min-width: 0; min-height: 0; flex: 1; overflow: auto; }
2763
+ .oh-video-artifact-content pre { min-width: 0; min-height: 100%; box-sizing: border-box; margin: 0; padding: 20px; color: inherit; background: var(--dsw-alias-bg-base); white-space: pre-wrap; overflow-wrap: anywhere; font: 12px/1.65 var(--dsw-font-family-mono, ui-monospace, monospace); }
890
2764
 
891
- [data-oh-story-layout="compact"] .oh-story-tree { padding-inline: 4px; }
892
- [data-oh-story-layout="compact"] .oh-story-brand { gap: 4px; padding-inline: 2px; font-size: 11px; }
893
- [data-oh-story-layout="compact"] .oh-story-brand-cluster { gap: 4px; }
894
- [data-oh-story-layout="compact"] .oh-story-brand-cluster strong > span { display: none; }
895
- [data-oh-story-layout="compact"] .oh-story-kind { padding-inline: 4px; font-size: 11px; }
896
- [data-oh-story-layout="compact"] .oh-story-brand button { width: 24px; height: 24px; }
897
- [data-oh-story-layout="compact"] .oh-story-mode-tabs { margin-inline: 0; }
898
- [data-oh-story-layout="compact"] .oh-story-mode-tabs button { padding-inline: 3px; font-size: 11px; }
899
- [data-oh-story-layout="compact"] .oh-story-file-folder > summary { padding-left: min(calc(5px + var(--oh-story-indent, 0px)), 19px); }
900
- [data-oh-story-layout="compact"] .oh-story-tree nav button { padding-right: 4px; padding-left: min(calc(7px + var(--oh-story-indent, 0px)), 21px); }
901
- [data-oh-story-layout="compact"] .oh-story-editor header {
902
- display: grid;
903
- min-height: 68px;
904
- grid-template-columns: minmax(0, 1fr);
905
- grid-template-rows: 24px 36px;
906
- align-content: center;
907
- gap: 0;
908
- padding: 4px 3px;
909
- }
910
- [data-oh-story-layout="compact"] .oh-story-editor-path > span { display: none; }
911
- [data-oh-story-layout="compact"] .oh-story-editor-path > strong { display: block; font-size: 11px; line-height: 24px; }
912
- [data-oh-story-layout="compact"] .oh-story-editor-actions { width: 100%; min-width: 0; justify-content: flex-end; gap: 4px; }
913
- [data-oh-story-layout="compact"] .oh-story-editor-tabs { min-width: 0; flex: 1; }
914
- [data-oh-story-layout="compact"] .oh-story-editor-tabs button { min-width: 0; padding-inline: 2px; white-space: nowrap; }
915
- [data-oh-story-layout="compact"] .oh-story-save { flex: none; padding-inline: 4px; white-space: nowrap; font-size: 11px; }
916
- [data-oh-story-layout="compact"] .oh-story-editor textarea { padding: 16px 12px 40px; }
917
- [data-oh-story-layout="compact"] .oh-story-markdown { padding: 18px 12px 48px; }
918
- [data-oh-story-layout="compact"] .oh-story-jsonl-summary { padding-inline: 10px; }
919
- [data-oh-story-layout="compact"] .oh-story-jsonl-records { padding: 8px 7px 28px; }
920
- [data-oh-story-layout="compact"] .oh-story-jsonl summary { gap: 6px; padding-inline: 7px; }
921
- [data-oh-story-layout="compact"] .oh-story-jsonl summary > span { display: none; }
922
- [data-oh-story-layout="compact"] .oh-story-jsonl summary > strong { min-width: 32px; }
923
- [data-oh-story-layout="compact"] .oh-story-jsonl summary > code,
924
- [data-oh-story-layout="compact"] .oh-story-jsonl summary > em { display: none; }
2765
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="medium"]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) { grid-template-columns: minmax(0, 1fr) clamp(360px, 42%, 440px); }
2766
+ [data-oh-story-workbench="video"][data-oh-story-layout="medium"] .oh-video-project select { width: 100%; }
2767
+
2768
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) { display: grid; grid-template-columns: minmax(0, 1fr); overflow: hidden; }
2769
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"]
2770
+ > [data-slot="conversation.session"] > :not(.oh-story-split-surface),
2771
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"]
2772
+ > [data-composer-seat],
2773
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"] .oh-video-studio { grid-column: 1; }
2774
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"][data-oh-studio-pane="studio"]
2775
+ > [data-slot="conversation.session"] > :not(.oh-story-split-surface),
2776
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"][data-oh-studio-pane="studio"]
2777
+ > [data-composer-seat],
2778
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"][data-oh-studio-pane="chat"] .oh-video-studio { display: none; }
2779
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"] .oh-game-mobile-switcher { display: flex; position: absolute; top: 8px; right: 8px; z-index: 20; border-color: var(--dsw-alias-border-l1); box-shadow: 0 4px 14px rgb(0 0 0 / 12%); }
2780
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"] .oh-video-toolbar { min-height: 142px; grid-template-columns: minmax(0, 1fr) auto; grid-template-rows: 44px 44px 38px; align-content: center; gap: 2px 6px; padding: 4px 8px; }
2781
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"] .oh-video-mode-tabs { grid-column: 1 / -1; grid-row: 1; width: fit-content; }
2782
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"] .oh-video-tabs { grid-column: 2; grid-row: 2; justify-self: end; }
2783
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"] .oh-video-project { grid-column: 1; grid-row: 2; }
2784
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"] .oh-video-project > span { display: none; }
2785
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"] .oh-video-stage { grid-column: 1 / -1; grid-row: 3; justify-self: start; }
2786
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"] .oh-video-stagebar { grid-template-columns: minmax(0, 1fr) auto; grid-template-rows: 42px 38px; gap: 2px 6px; padding: 3px 7px; }
2787
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"] .oh-video-preview-shell { grid-template-rows: 86px minmax(0, 1fr); }
2788
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"] .oh-video-version-tabs { grid-column: 1; grid-row: 1; }
2789
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"] .oh-video-runtime-state { grid-column: 1; grid-row: 2; justify-content: flex-start; }
2790
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"] .oh-video-preview-actions { grid-column: 2; grid-row: 1 / 3; }
2791
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"] .oh-video-preview-actions button:first-child { max-width: 70px; }
2792
+ [data-conversation-scroll][data-oh-story-workbench="video"][data-oh-story-layout="compact"] .oh-video-artifacts { grid-template-columns: minmax(105px, 35%) minmax(0, 1fr); }
925
2793
  `;
926
2794
 
927
2795
  // src/client/index.tsx
928
- var import_jsx_runtime3 = require("react/jsx-runtime"), name = "oh-story", inject = ["slots"];
2796
+ var import_jsx_runtime5 = require("react/jsx-runtime"), name = "oh-story", inject = ["slots", "sessions", "conversation"];
929
2797
  function applyUpdate(current, update) {
930
2798
  return typeof update == "function" ? update(current) : update;
931
2799
  }
932
2800
  function createWorkbenchStore() {
933
- return (0, import_client.defineStore)({
2801
+ return (0, import_dsh_client_store.defineStore)({
934
2802
  init: () => ({
935
2803
  buffers: {},
936
2804
  editorMode: "preview",
937
2805
  expanded: {},
938
2806
  selected: void 0,
939
- workbench: "story"
2807
+ workbench: "story",
2808
+ gameTab: "preview",
2809
+ gameProjectId: void 0,
2810
+ gamePane: "studio",
2811
+ videoTab: "preview",
2812
+ videoProjectId: void 0,
2813
+ videoPane: "studio",
2814
+ productionSection: "shots",
2815
+ productionSelectedIds: {},
2816
+ productionJobs: {},
2817
+ productionSelections: {},
2818
+ productionReferences: {},
2819
+ productionSequence: {},
2820
+ productionCanvas: {},
2821
+ productionZoom: {},
2822
+ productionIntentCalls: {}
940
2823
  }),
941
2824
  actions: {
942
2825
  setBuffers: (draft, update) => {
@@ -953,6 +2836,51 @@ function createWorkbenchStore() {
953
2836
  },
954
2837
  setWorkbench: (draft, update) => {
955
2838
  draft.workbench = applyUpdate(draft.workbench, update);
2839
+ },
2840
+ setGameTab: (draft, update) => {
2841
+ draft.gameTab = applyUpdate(draft.gameTab, update);
2842
+ },
2843
+ setGameProjectId: (draft, update) => {
2844
+ draft.gameProjectId = applyUpdate(draft.gameProjectId, update);
2845
+ },
2846
+ setGamePane: (draft, update) => {
2847
+ draft.gamePane = applyUpdate(draft.gamePane, update);
2848
+ },
2849
+ setVideoTab: (draft, update) => {
2850
+ draft.videoTab = applyUpdate(draft.videoTab, update);
2851
+ },
2852
+ setVideoProjectId: (draft, update) => {
2853
+ draft.videoProjectId = applyUpdate(draft.videoProjectId, update);
2854
+ },
2855
+ setVideoPane: (draft, update) => {
2856
+ draft.videoPane = applyUpdate(draft.videoPane, update);
2857
+ },
2858
+ setProductionSection: (draft, update) => {
2859
+ draft.productionSection = applyUpdate(draft.productionSection, update);
2860
+ },
2861
+ setProductionSelectedIds: (draft, update) => {
2862
+ draft.productionSelectedIds = applyUpdate(draft.productionSelectedIds, update);
2863
+ },
2864
+ setProductionJobs: (draft, update) => {
2865
+ draft.productionJobs = applyUpdate(draft.productionJobs, update);
2866
+ },
2867
+ setProductionSelections: (draft, update) => {
2868
+ draft.productionSelections = applyUpdate(draft.productionSelections, update);
2869
+ },
2870
+ setProductionReferences: (draft, update) => {
2871
+ draft.productionReferences = applyUpdate(draft.productionReferences, update);
2872
+ },
2873
+ setProductionSequence: (draft, update) => {
2874
+ draft.productionSequence = applyUpdate(draft.productionSequence, update);
2875
+ },
2876
+ setProductionCanvas: (draft, update) => {
2877
+ draft.productionCanvas = applyUpdate(draft.productionCanvas, update);
2878
+ },
2879
+ setProductionZoom: (draft, update) => {
2880
+ draft.productionZoom = applyUpdate(draft.productionZoom, update);
2881
+ },
2882
+ setProductionIntentCalls: (draft, update) => {
2883
+ draft.productionIntentCalls = applyUpdate(draft.productionIntentCalls, update);
956
2884
  }
957
2885
  }
958
2886
  });
@@ -965,30 +2893,13 @@ var WorkspaceRequestError = class extends Error {
965
2893
  status;
966
2894
  }, GROUP_ORDER = {
967
2895
  story: ["\u6B63\u6587", "\u5927\u7EB2", "\u8BBE\u5B9A", "\u8FFD\u8E2A", "\u5BF9\u6807", "\u53C2\u8003\u8D44\u6599"],
968
- drama: ["\u9879\u76EE", "\u8F93\u5165", "\u9879\u76EE\u5F00\u53D1", "\u8BBE\u5B9A\u96C6", "\u5267\u96C6", "\u5BA1\u67E5", "\u521B\u4F5C\u8005\u51B3\u7B56", "\u4EA4\u4ED8"]
969
- }, WORKBENCH_MODES = ["story", "drama"], EDITOR_MODES = ["preview", "source"];
970
- function handleTabKey(event, values, current, select) {
971
- let index;
972
- if (event.key === "Home" ? index = 0 : event.key === "End" ? index = values.length - 1 : event.key === "ArrowRight" ? index = (values.indexOf(current) + 1) % values.length : event.key === "ArrowLeft" && (index = (values.indexOf(current) - 1 + values.length) % values.length), index === void 0) return;
973
- event.preventDefault();
974
- let value = values[index];
975
- value !== void 0 && (select(value), event.currentTarget.parentElement?.querySelectorAll("[role='tab']")[index]?.focus());
976
- }
2896
+ drama: ["\u9879\u76EE", "\u8F93\u5165", "\u9879\u76EE\u5F00\u53D1", "\u8BBE\u5B9A\u96C6", "\u5267\u96C6", "\u5BA1\u67E5", "\u521B\u4F5C\u8005\u51B3\u7B56", "\u4EA4\u4ED8"],
2897
+ game: ["game-adaptations"],
2898
+ video: ["video-recaps"]
2899
+ }, WORKBENCH_MODES = ["story", "drama", "game", "video"], EDITOR_MODES = ["preview", "source", "production"];
977
2900
  function groupForPath(path) {
978
2901
  return path === "short-drama.json" ? "\u9879\u76EE" : path.split("/", 1)[0] ?? "\u5176\u4ED6";
979
2902
  }
980
- function preferredFile(files, mode) {
981
- let matching = files.filter((file) => workbenchModeForPath(file.path) === mode), preferences = mode === "story" ? [/^正文\/.*\.md$/u, /^大纲\/.*\.md$/u, /\.md$/u] : [/^剧集\/EP0*1\/screenplay\.md$/iu, /^剧集\/.*\/screenplay\.md$/iu, /^项目开发\/creative-brief\.md$/u, /^输入\/.*\.md$/u, /\.md$/u, /^short-drama\.json$/u];
982
- for (let pattern of preferences) {
983
- let match = matching.find((file) => pattern.test(file.path));
984
- if (match !== void 0) return match.path;
985
- }
986
- return matching[0]?.path;
987
- }
988
- function endpoint(path, sessionId, file) {
989
- let url = new URL(`/oh-story/${path}`, globalThis.location.origin);
990
- return url.searchParams.set("sessionId", sessionId), file !== void 0 && url.searchParams.set("path", file), url.toString();
991
- }
992
2903
  async function json(response) {
993
2904
  let value = await response.json();
994
2905
  if (!response.ok) throw new WorkspaceRequestError(response.status, value.error ?? `HTTP ${String(response.status)}`);
@@ -1003,8 +2914,8 @@ function FileTreeNodes({
1003
2914
  onToggle,
1004
2915
  onSelect
1005
2916
  }) {
1006
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: nodes.map((node) => {
1007
- if (node.kind === "file") return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2917
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: nodes.map((node) => {
2918
+ if (node.kind === "file") return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1008
2919
  "button",
1009
2920
  {
1010
2921
  type: "button",
@@ -1022,14 +2933,14 @@ function FileTreeNodes({
1022
2933
  node.path
1023
2934
  );
1024
2935
  let open = selected?.startsWith(`${node.path}/`) === !0 || expanded[node.path] === !0;
1025
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("details", { className: "oh-story-file-folder", open, onToggle: (event) => {
2936
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("details", { className: "oh-story-file-folder", open, onToggle: (event) => {
1026
2937
  onToggle(node.path, event.currentTarget.open);
1027
2938
  }, children: [
1028
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("summary", { style: { "--oh-story-indent": `${String(depth * 14)}px` }, title: node.path, children: [
2939
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("summary", { style: { "--oh-story-indent": `${String(depth * 14)}px` }, title: node.path, children: [
1029
2940
  node.name,
1030
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: node.fileCount })
2941
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: node.fileCount })
1031
2942
  ] }),
1032
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2943
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1033
2944
  FileTreeNodes,
1034
2945
  {
1035
2946
  nodes: node.children,
@@ -1045,10 +2956,10 @@ function FileTreeNodes({
1045
2956
  }) });
1046
2957
  }
1047
2958
  function useWorkspace(sessionId) {
1048
- let [version, setVersion] = (0, import_react.useState)(0), [workspace, setWorkspace] = (0, import_react.useState)(), [error, setError] = (0, import_react.useState)(), [loading, setLoading] = (0, import_react.useState)(!0), reload = (0, import_react.useCallback)(() => {
2959
+ let [version, setVersion] = (0, import_react3.useState)(0), [workspace, setWorkspace] = (0, import_react3.useState)(), [error, setError] = (0, import_react3.useState)(), [loading, setLoading] = (0, import_react3.useState)(!0), reload = (0, import_react3.useCallback)(() => {
1049
2960
  setLoading(!0), setVersion((value) => value + 1);
1050
2961
  }, []);
1051
- return (0, import_react.useEffect)(() => {
2962
+ return (0, import_react3.useEffect)(() => {
1052
2963
  let controller = new AbortController();
1053
2964
  return setError(void 0), fetch(endpoint("workspace", sessionId), { signal: controller.signal }).then((response) => json(response)).then(setWorkspace).catch((reason) => {
1054
2965
  controller.signal.aborted || setError(reason instanceof Error ? reason.message : String(reason));
@@ -1059,32 +2970,309 @@ function useWorkspace(sessionId) {
1059
2970
  };
1060
2971
  }, [sessionId, version]), { workspace, error, loading, reload };
1061
2972
  }
2973
+ function isolatedPreviewUrl(path, version, revision) {
2974
+ let url = new URL(path, globalThis.location.origin);
2975
+ return url.hostname === "127.0.0.1" ? url.hostname = "localhost" : url.hostname === "localhost" && (url.hostname = "127.0.0.1"), url.searchParams.set("build", version), url.searchParams.set("reload", String(revision)), { href: url.toString(), isolated: url.origin !== globalThis.location.origin };
2976
+ }
2977
+ function PreviewProbe({ href, onFailed }) {
2978
+ let failedRef = (0, import_react3.useRef)(onFailed);
2979
+ return (0, import_react3.useEffect)(() => {
2980
+ failedRef.current = onFailed;
2981
+ }, [onFailed]), (0, import_react3.useEffect)(() => {
2982
+ let controller = new AbortController();
2983
+ return fetch(href, { signal: controller.signal }).then((response) => {
2984
+ if (controller.signal.aborted) return;
2985
+ let type = response.headers.get("content-type") ?? "";
2986
+ (!response.ok || !type.includes("text/html")) && failedRef.current();
2987
+ }).catch(() => {
2988
+ controller.signal.aborted || failedRef.current();
2989
+ }), () => {
2990
+ controller.abort();
2991
+ };
2992
+ }, [href]), null;
2993
+ }
2994
+ function GamePreview({ project, building }) {
2995
+ let shellRef = (0, import_react3.useRef)(null), fullscreenButtonRef = (0, import_react3.useRef)(null), restoreFullscreenFocus = (0, import_react3.useRef)(!1), [focused, setFocused] = (0, import_react3.useState)(!1), [loaded, setLoaded] = (0, import_react3.useState)(!1), [loadError, setLoadError] = (0, import_react3.useState)(!1), [revision, setRevision] = (0, import_react3.useState)(0), [loadedVersion, setLoadedVersion] = (0, import_react3.useState)(project.previewVersion);
2996
+ if ((0, import_react3.useEffect)(() => {
2997
+ let document = shellRef.current?.ownerDocument;
2998
+ if (document === void 0) return;
2999
+ let restore = () => {
3000
+ document.fullscreenElement !== null || !restoreFullscreenFocus.current || (restoreFullscreenFocus.current = !1, fullscreenButtonRef.current?.focus());
3001
+ };
3002
+ return document.addEventListener("fullscreenchange", restore), () => {
3003
+ document.removeEventListener("fullscreenchange", restore);
3004
+ };
3005
+ }, []), !project.previewReady || project.previewUrl === void 0) return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "oh-game-preview-empty", children: [
3006
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "aria-hidden": !0, children: "\u25EB" }),
3007
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: "\u8FD8\u6CA1\u6709\u53EF\u8BD5\u73A9\u7248\u672C" }),
3008
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { children: [
3009
+ "\u5728\u53F3\u4FA7 Chat \u4F7F\u7528 ",
3010
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("code", { children: "/novel-to-game quick" }),
3011
+ "\uFF0C\u4EA7\u7269\u5199\u5165 ",
3012
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("code", { children: "game-adaptations/<project>/build/app/" }),
3013
+ " \u540E\u4F1A\u81EA\u52A8\u51FA\u73B0\u5728\u8FD9\u91CC\u3002"
3014
+ ] }),
3015
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "oh-game-prompt-example", children: [
3016
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "\u63CF\u8FF0\u793A\u4F8B" }),
3017
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("q", { children: "\u628A\u300A\u4F5C\u54C1\u540D\u300B\u6539\u7F16\u6210\u7F51\u9875\u4E92\u52A8\u6E38\u620F\uFF0C\u76EE\u6807\u73A9\u5BB6\u662F\u2026\u2026\uFF0C\u6838\u5FC3\u73A9\u6CD5\u662F\u2026\u2026\uFF0C\u5E0C\u671B\u6574\u4F53\u98CE\u683C\u2026\u2026" })
3018
+ ] })
3019
+ ] });
3020
+ let preview = isolatedPreviewUrl(project.previewUrl, loadedVersion, revision), pending = project.previewVersion !== loadedVersion, reload = () => {
3021
+ setLoaded(!1), setLoadError(!1), setLoadedVersion(project.previewVersion), setRevision((value) => value + 1);
3022
+ }, refresh = () => {
3023
+ setLoaded(!1), setLoadError(!1), setRevision((value) => value + 1);
3024
+ }, runtimeState = loadError ? "\u9884\u89C8\u8F7D\u5165\u5931\u8D25 \xB7 \u53EF\u91CD\u65B0\u8F7D\u5165" : building ? "Agent \u6B63\u5728\u66F4\u65B0\u6E38\u620F\u6587\u4EF6 \xB7 \u5F53\u524D\u9884\u89C8\u4FDD\u6301\u4E0D\u53D8" : pending ? "\u65B0\u7248\u672C\u5DF2\u5C31\u7EEA \xB7 \u7531\u4F60\u51B3\u5B9A\u4F55\u65F6\u8F7D\u5165" : loaded ? preview.isolated ? "\u9884\u89C8\u5DF2\u8F7D\u5165" : "\u9884\u89C8\u5DF2\u8F7D\u5165 \xB7 \u5F53\u524D\u90E8\u7F72\u65E0\u6CD5\u9694\u79BB\u6765\u6E90\uFF0C\u5B58\u6863\u529F\u80FD\u4E0D\u53EF\u7528" : "\u6B63\u5728\u8F7D\u5165\u9884\u89C8\u2026";
3025
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: shellRef, className: "oh-game-preview-shell", "data-state": loadError ? "error" : building ? "building" : loaded ? "ready" : "loading", children: [
3026
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "oh-game-preview-status", children: [
3027
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "oh-game-runtime-state", role: "status", "aria-live": "polite", title: runtimeState, children: [
3028
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("i", { "aria-hidden": !0 }),
3029
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("em", { children: runtimeState })
3030
+ ] }),
3031
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
3032
+ pending && !building && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: reload, children: "\u8F7D\u5165\u65B0\u7248\u672C" }),
3033
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("button", { className: "oh-game-reload", type: "button", onClick: refresh, "aria-label": "\u91CD\u65B0\u8F7D\u5165\u6E38\u620F", children: [
3034
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "aria-hidden": !0, children: "\u21BB" }),
3035
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("b", { children: "\u5237\u65B0" })
3036
+ ] }),
3037
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { ref: fullscreenButtonRef, type: "button", onClick: () => {
3038
+ let shell = shellRef.current;
3039
+ shell !== null && (restoreFullscreenFocus.current = !0, shell.requestFullscreen().catch(() => {
3040
+ restoreFullscreenFocus.current = !1;
3041
+ }));
3042
+ }, children: "\u5168\u5C4F\u8BD5\u73A9" })
3043
+ ] })
3044
+ ] }),
3045
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PreviewProbe, { href: preview.href, onFailed: () => {
3046
+ setLoaded(!1), setLoadError(!0);
3047
+ } }),
3048
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3049
+ "iframe",
3050
+ {
3051
+ src: preview.href,
3052
+ title: `\u300A${project.title}\u300B\u53EF\u8BD5\u73A9\u9884\u89C8`,
3053
+ sandbox: preview.isolated ? "allow-scripts allow-same-origin allow-forms allow-modals allow-downloads" : "allow-scripts allow-forms allow-modals allow-downloads",
3054
+ allow: "autoplay; fullscreen; gamepad",
3055
+ allowFullScreen: !0,
3056
+ referrerPolicy: "no-referrer",
3057
+ onLoad: () => {
3058
+ setLoadError(!1), setLoaded(!0);
3059
+ },
3060
+ onError: () => {
3061
+ setLoaded(!1), setLoadError(!0);
3062
+ },
3063
+ onFocus: () => {
3064
+ setFocused(!0);
3065
+ },
3066
+ onBlur: () => {
3067
+ setFocused(!1);
3068
+ }
3069
+ },
3070
+ `${project.id}:${loadedVersion}:${String(revision)}`
3071
+ ),
3072
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "oh-game-focus-hint", "data-focused": focused || void 0, children: focused ? "\u6E38\u620F\u6B63\u5728\u63A5\u6536\u952E\u9F20\u8F93\u5165" : "\u70B9\u51FB\u753B\u9762\u8FDB\u5165\u8BD5\u73A9" })
3073
+ ] });
3074
+ }
3075
+ function GameDesign({
3076
+ project,
3077
+ files,
3078
+ selected,
3079
+ sessionId,
3080
+ onSelect
3081
+ }) {
3082
+ let documents = (0, import_react3.useMemo)(() => files.filter((file) => file.path.startsWith(`${project.root}/`) && /\.(?:md|txt|json|jsonl|html|css|[cm]?js|tsx?|jsx)$/iu.test(file.path)), [files, project.root]), preferred = selected !== void 0 && documents.some((file) => file.path === selected) ? selected : documents.find((file) => file.path === `${project.root}/PRODUCT_BRIEF.md`)?.path ?? documents[0]?.path, [path, setPath] = (0, import_react3.useState)(preferred), [content, setContent] = (0, import_react3.useState)(), [error, setError] = (0, import_react3.useState)();
3083
+ if ((0, import_react3.useEffect)(() => {
3084
+ setPath(preferred);
3085
+ }, [preferred, project.id]), (0, import_react3.useEffect)(() => {
3086
+ if (path === void 0 || project.source === "example") {
3087
+ setContent(void 0);
3088
+ return;
3089
+ }
3090
+ let controller = new AbortController();
3091
+ return setContent(void 0), setError(void 0), fetch(endpoint("file", sessionId, path), { signal: controller.signal }).then((response) => json(response)).then((file) => {
3092
+ setContent(file.content);
3093
+ }).catch((reason) => {
3094
+ controller.signal.aborted || setError(reason instanceof Error ? reason.message : String(reason));
3095
+ }), () => {
3096
+ controller.abort();
3097
+ };
3098
+ }, [path, project.source, sessionId]), project.source === "example") return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "oh-game-design-empty", children: [
3099
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: "\u5185\u7F6E\u5B8C\u6574\u793A\u4F8B" }),
3100
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: "\u300A\u91D1\u74F6\u6885 \xB7 \u98CE\u6708\u603B\u8D26\u300B\u7684\u5B8C\u6574\u53EF\u73A9\u6784\u5EFA\u4E0E QA \u6821\u9A8C\u7ED3\u679C\u968F\u63D2\u4EF6\u6253\u5305\uFF0C\u53EF\u76F4\u63A5\u5728\u5DE6\u4FA7\u8BD5\u73A9\u3002\u4E0A\u6E38\u7684\u4EA7\u54C1\u7B80\u62A5\u3001\u5206\u6790\u3001\u6982\u5FF5\u3001\u8BBE\u8BA1\u4E0E\u6E90\u5C0F\u8BF4\u4E0D\u968F\u5305\u5206\u53D1\uFF0C\u53EF\u5728 novel-to-game \u4ED3\u5E93\u67E5\u770B\u5B8C\u6574\u521B\u4F5C\u8FC7\u7A0B\u3002" }),
3101
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("code", { children: "novel-to-game/examples/jin-ping-mei" })
3102
+ ] });
3103
+ if (documents.length === 0 || path === void 0) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "oh-game-design-empty", children: "\u5F53\u524D\u9879\u76EE\u8FD8\u6CA1\u6709\u53EF\u68C0\u67E5\u7684\u8BBE\u8BA1\u6216\u6E90\u6587\u4EF6\u3002" });
3104
+ let markdown = path.toLocaleLowerCase().endsWith(".md");
3105
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "oh-game-design", children: [
3106
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { children: [
3107
+ "\u9879\u76EE\u6587\u4EF6",
3108
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("select", { value: path, onChange: (event) => {
3109
+ setPath(event.target.value), onSelect(event.target.value);
3110
+ }, children: documents.map((file) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: file.path, children: file.path.slice(project.root.length + 1) }, file.path)) })
3111
+ ] }),
3112
+ error !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "oh-story-error", children: error }) : content === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "oh-game-design-empty", children: "\u6B63\u5728\u8F7D\u5165\u6587\u4EF6\u2026" }) : markdown ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MarkdownPreview, { content, label: path }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("pre", { className: "oh-game-source", "aria-label": `${path} \u6E90\u7801`, children: content })
3113
+ ] });
3114
+ }
3115
+ function GameStudio({
3116
+ sessionId,
3117
+ workspace,
3118
+ building,
3119
+ selected,
3120
+ gameTab,
3121
+ gameProjectId,
3122
+ hidden,
3123
+ onGameTab,
3124
+ onGameProject,
3125
+ workbenches,
3126
+ paneId,
3127
+ labelledBy,
3128
+ onWorkbench,
3129
+ onSelect
3130
+ }) {
3131
+ let project = workspace.games.find((value) => value.id === gameProjectId) ?? workspace.games[0], studioRef = (0, import_react3.useRef)(null), tabsId = (0, import_react3.useId)();
3132
+ if ((0, import_react3.useLayoutEffect)(() => {
3133
+ let studio = studioRef.current;
3134
+ if (studio === null) return;
3135
+ let publishWidth = () => {
3136
+ studio.toggleAttribute("data-oh-game-narrow", studio.clientWidth <= 300);
3137
+ };
3138
+ publishWidth();
3139
+ let observer = new ResizeObserver(publishWidth);
3140
+ return observer.observe(studio), () => {
3141
+ observer.disconnect();
3142
+ };
3143
+ }, []), (0, import_react3.useEffect)(() => {
3144
+ project !== void 0 && project.id !== gameProjectId && onGameProject(project.id);
3145
+ }, [gameProjectId, onGameProject, project]), project === void 0) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("main", { ref: studioRef, id: paneId, className: "oh-game-studio", role: "tabpanel", "aria-labelledby": labelledBy, hidden, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "oh-game-design-empty", children: "\u6E38\u620F\u80FD\u529B\u6B63\u5728\u8F7D\u5165\u2026" }) });
3146
+ let tabs = ["preview", "design"];
3147
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("main", { ref: studioRef, id: paneId, className: "oh-game-studio", "data-source": project.source, role: "tabpanel", "aria-labelledby": labelledBy, hidden, children: [
3148
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("header", { className: "oh-game-toolbar", children: [
3149
+ workbenches.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "oh-game-mode-tabs", role: "tablist", "aria-label": "\u521B\u4F5C\u5DE5\u4F5C\u53F0", children: workbenches.map((mode) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3150
+ "button",
3151
+ {
3152
+ type: "button",
3153
+ role: "tab",
3154
+ "aria-selected": mode === "game",
3155
+ tabIndex: mode === "game" ? 0 : -1,
3156
+ onKeyDown: (event) => {
3157
+ handleTabKey(event, workbenches, "game", onWorkbench);
3158
+ },
3159
+ onClick: () => {
3160
+ onWorkbench(mode);
3161
+ },
3162
+ children: workbenchLabel(mode)
3163
+ },
3164
+ mode
3165
+ )) }),
3166
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "oh-game-project", title: "\u5207\u6362\u9879\u76EE\u5C06\u91CD\u65B0\u8F7D\u5165\u8BD5\u73A9", children: [
3167
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "\u6E38\u620F\u9879\u76EE" }),
3168
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("select", { "aria-label": "\u6E38\u620F\u9879\u76EE\uFF1B\u5207\u6362\u5C06\u91CD\u65B0\u8F7D\u5165\u8BD5\u73A9", value: project.id, onChange: (event) => {
3169
+ onGameProject(event.target.value);
3170
+ }, children: [
3171
+ workspace.games.some((item) => item.source === "workspace") && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("optgroup", { label: "\u6211\u7684\u9879\u76EE", children: workspace.games.filter((item) => item.source === "workspace").map((item) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: item.id, children: `\u6211\u7684\u9879\u76EE \xB7 ${item.title}` }, item.id)) }),
3172
+ workspace.games.some((item) => item.source === "example") && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("optgroup", { label: "\u5185\u7F6E\u793A\u4F8B", children: workspace.games.filter((item) => item.source === "example").map((item) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: item.id, children: `\u5185\u7F6E\u793A\u4F8B \xB7 ${item.title}` }, item.id)) })
3173
+ ] })
3174
+ ] }),
3175
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "oh-game-tabs", role: "tablist", "aria-label": "\u6E38\u620F\u5DE5\u4F5C\u53F0", children: tabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3176
+ "button",
3177
+ {
3178
+ type: "button",
3179
+ role: "tab",
3180
+ tabIndex: gameTab === tab ? 0 : -1,
3181
+ "aria-selected": gameTab === tab,
3182
+ id: `${tabsId}-${tab}-tab`,
3183
+ "aria-controls": `${tabsId}-${tab}-panel`,
3184
+ onKeyDown: (event) => {
3185
+ handleTabKey(event, tabs, gameTab, onGameTab);
3186
+ },
3187
+ onClick: () => {
3188
+ onGameTab(tab);
3189
+ },
3190
+ children: tab === "preview" ? "\u8BD5\u73A9" : project.source === "example" ? "\u8BF4\u660E" : "\u9879\u76EE\u6587\u4EF6"
3191
+ },
3192
+ tab
3193
+ )) })
3194
+ ] }),
3195
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "oh-game-panels", children: [
3196
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "oh-game-panel", role: "tabpanel", id: `${tabsId}-preview-panel`, "aria-labelledby": `${tabsId}-preview-tab`, hidden: gameTab !== "preview", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(GamePreview, { project, building }, `${project.id}:${String(project.previewReady)}`) }),
3197
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "oh-game-panel", role: "tabpanel", id: `${tabsId}-design-panel`, "aria-labelledby": `${tabsId}-design-tab`, hidden: gameTab !== "design", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(GameDesign, { project, files: workspace.files, selected, sessionId, onSelect }) })
3198
+ ] })
3199
+ ] });
3200
+ }
1062
3201
  function CreativeWorkbench({
1063
3202
  sessionId,
1064
3203
  runningCalls,
1065
3204
  partial,
1066
3205
  settledMutation,
3206
+ sessionRunning,
3207
+ productionQueue,
3208
+ productionIntents,
3209
+ sendProductionPrompt,
3210
+ cancelProduction,
3211
+ removeQueuedProduction,
1067
3212
  useStore,
1068
3213
  actions
1069
3214
  }) {
1070
- let { workspace, error, loading: workspaceLoading, reload } = useWorkspace(sessionId), activities = (0, import_react.useMemo)(
3215
+ let { workspace, error, loading: workspaceLoading, reload } = useWorkspace(sessionId), activities = (0, import_react3.useMemo)(
1071
3216
  () => fileMutations(runningCalls, partial),
1072
3217
  [partial, runningCalls]
1073
- ), normalizedActivities = (0, import_react.useMemo)(() => activities.flatMap((activity2) => {
3218
+ ), normalizedActivities = (0, import_react3.useMemo)(() => activities.flatMap((activity2) => {
1074
3219
  let path = creativeRelativePath(activity2.path, workspace?.cwd);
1075
3220
  return path === void 0 ? [] : [{ activity: activity2, path }];
1076
- }), [activities, workspace?.cwd]), primaryActivity = normalizedActivities.at(-1), activityPaths = (0, import_react.useMemo)(() => new Set(normalizedActivities.map((value) => value.path)), [normalizedActivities]), activity = primaryActivity?.activity, activityPath = primaryActivity?.path, workbench = useStore((memory) => memory.workbench), setWorkbench = actions.setWorkbench, initializedWorkbench = (0, import_react.useRef)(!1), selected = useStore((memory) => memory.selected), setSelected = actions.setSelected, buffers = useStore((memory) => memory.buffers), setBuffers = actions.setBuffers, buffersRef = (0, import_react.useRef)({}), expanded = useStore((memory) => memory.expanded), setExpanded = actions.setExpanded, surfaceRef = (0, import_react.useRef)(null), navRef = (0, import_react.useRef)(null), activityBases = (0, import_react.useRef)(/* @__PURE__ */ new Map()), previousSignals = (0, import_react.useRef)(/* @__PURE__ */ new Set()), previousSettledMutation = (0, import_react.useRef)(settledMutation), saveLocks = (0, import_react.useRef)(/* @__PURE__ */ new Set()), buffer = selected === void 0 ? void 0 : buffers[selected], dirty = buffer?.source === "human" && buffer.content !== buffer.saved, saving = buffer?.saving === !0, fileError = buffer?.error, conflict = buffer?.conflict, selectedLower = selected?.toLocaleLowerCase(), markdown = selectedLower?.endsWith(".md") === !0, jsonl = selectedLower?.endsWith(".jsonl") === !0, structured = jsonl || selectedLower?.endsWith(".json") === !0, previewable = markdown || jsonl, editorMode = useStore((memory) => memory.editorMode), setEditorMode = actions.setEditorMode, modeSelection = (0, import_react.useRef)(selected), textareaRef = (0, import_react.useRef)(null), editorPositions = (0, import_react.useRef)(/* @__PURE__ */ new Map()), editorReady = buffer !== void 0 && buffer.missing !== !0, availableModes = (0, import_react.useMemo)(() => {
1077
- let value = /* @__PURE__ */ new Set();
1078
- for (let file of workspace?.files ?? []) {
1079
- let mode = workbenchModeForPath(file.path);
1080
- mode !== void 0 && value.add(mode);
1081
- }
1082
- return WORKBENCH_MODES.filter((mode) => value.has(mode));
1083
- }, [workspace?.files]), showModeTabs = workspace !== void 0 && availableModes.length !== 1, workspaceKind = availableModes.length === 1 ? availableModes[0] : void 0;
1084
- (0, import_react.useEffect)(() => {
3221
+ }), [activities, workspace?.cwd]), primaryActivity = normalizedActivities.at(-1), activityPaths = (0, import_react3.useMemo)(() => new Set(normalizedActivities.map((value) => value.path)), [normalizedActivities]), activity = primaryActivity?.activity, activityPath = primaryActivity?.path, workbench = useStore((memory) => memory.workbench), setWorkbench = actions.setWorkbench, gameTab = useStore((memory) => memory.gameTab), setGameTab = actions.setGameTab, gameProjectId = useStore((memory) => memory.gameProjectId), setGameProjectId = actions.setGameProjectId, gamePane = useStore((memory) => memory.gamePane), setGamePane = actions.setGamePane, videoTab = useStore((memory) => memory.videoTab), setVideoTab = actions.setVideoTab, videoProjectId = useStore((memory) => memory.videoProjectId), setVideoProjectId = actions.setVideoProjectId, videoPane = useStore((memory) => memory.videoPane), setVideoPane = actions.setVideoPane, selected = useStore((memory) => memory.selected), setSelected = actions.setSelected, buffers = useStore((memory) => memory.buffers), setBuffers = actions.setBuffers, buffersRef = (0, import_react3.useRef)({}), expanded = useStore((memory) => memory.expanded), setExpanded = actions.setExpanded, productionSection = useStore((memory) => memory.productionSection), setProductionSection = actions.setProductionSection, productionSelectedIds = useStore((memory) => memory.productionSelectedIds), productionJobsByEpisode = useStore((memory) => memory.productionJobs), productionSelectionsByEpisode = useStore((memory) => memory.productionSelections), productionReferencesByEpisode = useStore((memory) => memory.productionReferences), productionSequenceByEpisode = useStore((memory) => memory.productionSequence), productionCanvasByEpisode = useStore((memory) => memory.productionCanvas), productionZoomByEpisode = useStore((memory) => memory.productionZoom), productionIntentCalls = useStore((memory) => memory.productionIntentCalls), surfaceRef = (0, import_react3.useRef)(null), compactTabsId = (0, import_react3.useId)(), compactStudioId = `${compactTabsId}-studio-panel`, compactVideoStudioId = `${compactTabsId}-video-studio-panel`, compactChatId = `${compactTabsId}-chat-panel`, navRef = (0, import_react3.useRef)(null), activityBases = (0, import_react3.useRef)(/* @__PURE__ */ new Map()), previousSignals = (0, import_react3.useRef)(/* @__PURE__ */ new Set()), previousSettledMutation = (0, import_react3.useRef)(settledMutation), saveLocks = (0, import_react3.useRef)(/* @__PURE__ */ new Set()), buffer = selected === void 0 ? void 0 : buffers[selected], selectedFile = workspace?.files.find((file) => file.path === selected), selectedMedia = selectedFile?.kind === "media", dirty = buffer?.source === "human" && buffer.content !== buffer.saved, saving = buffer?.saving === !0, fileError = buffer?.error, conflict = buffer?.conflict, selectedLower = selected?.toLocaleLowerCase(), markdown = selectedLower?.endsWith(".md") === !0, jsonl = selectedLower?.endsWith(".jsonl") === !0, structured = jsonl || selectedLower?.endsWith(".json") === !0, previewable = markdown || jsonl, episodeDirectory = episodeDirectoryForPath(selected), productionAvailable = selected !== void 0 && isCreatorDocumentPath(selected) && episodeDirectory !== void 0, editorModes = productionAvailable ? EDITOR_MODES : EDITOR_MODES.filter((mode) => mode !== "production"), episodeDocumentPaths = (0, import_react3.useMemo)(
3222
+ () => episodeDirectory === void 0 ? [] : creatorDocumentPaths(workspace?.files.filter((file) => file.kind === "text") ?? [], episodeDirectory),
3223
+ [episodeDirectory, workspace?.files]
3224
+ ), episodeDocuments = (0, import_react3.useMemo)(() => Object.fromEntries(episodeDocumentPaths.flatMap((path) => {
3225
+ let current = buffers[path];
3226
+ return current === void 0 || current.missing === !0 ? [] : [[path, current.content]];
3227
+ })), [buffers, episodeDocumentPaths]), episodeProduction = (0, import_react3.useMemo)(
3228
+ () => episodeDirectory === void 0 ? void 0 : parseEpisodeProduction(episodeDocuments, episodeDirectory),
3229
+ [episodeDirectory, episodeDocuments]
3230
+ ), productionLibrary = (0, import_react3.useMemo)(() => (workspace?.files ?? []).flatMap((file) => {
3231
+ if (file.kind !== "media" || file.mimeType?.startsWith("audio/") === !0) return [];
3232
+ if (!file.path.startsWith("\u5267\u96C6/") && !file.path.startsWith("\u4EA4\u4ED8/")) return [];
3233
+ let targetId = file.path.toLocaleUpperCase().match(/(?:SHOT|IMG|MOTION|VISUAL)-[A-Z0-9-]+/u)?.[0] ?? file.path.split("/").at(-2) ?? "PROJECT-MEDIA";
3234
+ return [{
3235
+ id: `workspace:${file.path}:${file.version}`,
3236
+ targetId,
3237
+ kind: file.mimeType?.startsWith("image/") === !0 ? "image" : "video",
3238
+ url: endpoint("media", sessionId, file.path),
3239
+ path: file.path
3240
+ }];
3241
+ }), [sessionId, workspace?.files]), productionVersions = (0, import_react3.useMemo)(() => {
3242
+ if (episodeProduction === void 0) return [];
3243
+ let episodeName = episodeProduction.episodeDirectory.split("/").at(-1) ?? "", knownTargets = [
3244
+ ...episodeProduction.shots.map((shot) => shot.id),
3245
+ ...episodeProduction.assets.map((asset) => asset.id),
3246
+ ...episodeProduction.visualAssets.map((asset) => asset.id),
3247
+ ...episodeProduction.motions.map((motion) => motion.id)
3248
+ ].sort((left, right) => right.length - left.length), motionTargets = new Map(episodeProduction.motions.flatMap((motion) => motion.shotId === void 0 ? [] : [[motion.id, motion.shotId]])), fromWorkspace = productionLibrary.flatMap((version) => {
3249
+ if (version.path === void 0 || !version.path.startsWith(`${episodeProduction.episodeDirectory}/`) && !version.path.startsWith(`\u4EA4\u4ED8/${episodeName}/`)) return [];
3250
+ let matched = mediaTargetFromPath(version.path, knownTargets), composition = /(?:^|\/)成片-[^/]+\.mp4$/iu.test(version.path);
3251
+ if (matched === void 0 && !composition) return [];
3252
+ let targetId = matched === void 0 ? episodeProduction.episodeDirectory : motionTargets.get(matched) ?? matched;
3253
+ return [{ ...version, targetId }];
3254
+ }), byId = /* @__PURE__ */ new Map();
3255
+ for (let version of fromWorkspace) byId.set(version.id, version);
3256
+ return [...byId.values()];
3257
+ }, [episodeProduction, productionLibrary]), productionSelectedId = episodeDirectory === void 0 ? void 0 : productionSelectedIds[episodeDirectory], productionJobs = episodeDirectory === void 0 ? [] : productionJobsByEpisode[episodeDirectory] ?? [], productionSelections = episodeDirectory === void 0 ? {} : productionSelectionsByEpisode[episodeDirectory] ?? {}, productionReferences = episodeDirectory === void 0 ? {} : productionReferencesByEpisode[episodeDirectory] ?? {}, productionSequence = episodeDirectory === void 0 ? [] : productionSequenceByEpisode[episodeDirectory] ?? [], productionCanvas = episodeDirectory === void 0 ? {} : productionCanvasByEpisode[episodeDirectory] ?? {}, productionZoom = episodeDirectory === void 0 ? 0.65 : productionZoomByEpisode[episodeDirectory] ?? 0.65, setProductionSelectedId = (0, import_react3.useCallback)((selectedId) => {
3258
+ episodeDirectory !== void 0 && actions.setProductionSelectedIds((current) => ({ ...current, [episodeDirectory]: selectedId }));
3259
+ }, [actions, episodeDirectory]), setProductionJobs = (0, import_react3.useCallback)((jobs) => {
3260
+ episodeDirectory !== void 0 && actions.setProductionJobs((current) => ({ ...current, [episodeDirectory]: jobs }));
3261
+ }, [actions, episodeDirectory]), setProductionSelections = (0, import_react3.useCallback)((selections) => {
3262
+ episodeDirectory !== void 0 && actions.setProductionSelections((current) => ({ ...current, [episodeDirectory]: selections }));
3263
+ }, [actions, episodeDirectory]), setProductionReferences = (0, import_react3.useCallback)((references) => {
3264
+ episodeDirectory !== void 0 && actions.setProductionReferences((current) => ({ ...current, [episodeDirectory]: references }));
3265
+ }, [actions, episodeDirectory]), setProductionSequence = (0, import_react3.useCallback)((sequence) => {
3266
+ episodeDirectory !== void 0 && actions.setProductionSequence((current) => ({ ...current, [episodeDirectory]: sequence }));
3267
+ }, [actions, episodeDirectory]), setProductionCanvas = (0, import_react3.useCallback)((canvas) => {
3268
+ episodeDirectory !== void 0 && actions.setProductionCanvas((current) => ({ ...current, [episodeDirectory]: canvas }));
3269
+ }, [actions, episodeDirectory]), setProductionZoom = (0, import_react3.useCallback)((zoom) => {
3270
+ episodeDirectory !== void 0 && actions.setProductionZoom((current) => ({ ...current, [episodeDirectory]: zoom }));
3271
+ }, [actions, episodeDirectory]), editorMode = useStore((memory) => memory.editorMode), setEditorMode = actions.setEditorMode, modeSelection = (0, import_react3.useRef)(selected), textareaRef = (0, import_react3.useRef)(null), editorPositions = (0, import_react3.useRef)(/* @__PURE__ */ new Map()), editorReady = buffer !== void 0 && buffer.missing !== !0, workspaceKind = workbench === "game" || workbench === "video" ? void 0 : workbench, gameBuilding = normalizedActivities.some(({ path }) => path.startsWith("game-adaptations/")), videoBuilding = normalizedActivities.some(({ path }) => path.startsWith("video-recaps/"));
3272
+ (0, import_react3.useEffect)(() => {
1085
3273
  buffersRef.current = buffers;
1086
3274
  }, [buffers]);
1087
- let rememberEditorPosition = (0, import_react.useCallback)(() => {
3275
+ let rememberEditorPosition = (0, import_react3.useCallback)(() => {
1088
3276
  let element = textareaRef.current;
1089
3277
  element === null || selected === void 0 || element.getAttribute("aria-label") !== selected || editorPositions.current.set(selected, {
1090
3278
  scrollTop: element.scrollTop,
@@ -1092,13 +3280,13 @@ function CreativeWorkbench({
1092
3280
  selectionEnd: element.selectionEnd
1093
3281
  });
1094
3282
  }, [selected]);
1095
- (0, import_react.useLayoutEffect)(() => {
3283
+ (0, import_react3.useLayoutEffect)(() => {
1096
3284
  if (editorMode !== "source" || selected === void 0 || !editorReady) return;
1097
3285
  let element = textareaRef.current, position = editorPositions.current.get(selected);
1098
3286
  if (element === null || position === void 0) return;
1099
3287
  let end = Math.min(position.selectionEnd, element.value.length);
1100
3288
  element.setSelectionRange(Math.min(position.selectionStart, end), end), element.scrollTop = position.scrollTop;
1101
- }, [editorMode, editorReady, selected]), (0, import_react.useEffect)(() => {
3289
+ }, [editorMode, editorReady, selected]), (0, import_react3.useEffect)(() => {
1102
3290
  let warn = (event) => {
1103
3291
  Object.values(buffersRef.current).some((value) => value.source === "human" && value.content !== value.saved) && event.preventDefault();
1104
3292
  };
@@ -1106,7 +3294,7 @@ function CreativeWorkbench({
1106
3294
  globalThis.removeEventListener("beforeunload", warn);
1107
3295
  };
1108
3296
  }, []);
1109
- let expandPath = (0, import_react.useCallback)((path) => {
3297
+ let expandPath = (0, import_react3.useCallback)((path) => {
1110
3298
  let segments = path.split("/"), ancestors = [groupForPath(path)];
1111
3299
  for (let index = 1; index < segments.length - 1; index += 1) ancestors.push(segments.slice(0, index + 1).join("/"));
1112
3300
  setExpanded((current) => {
@@ -1114,22 +3302,85 @@ function CreativeWorkbench({
1114
3302
  for (let ancestor of ancestors) next[ancestor] = !0;
1115
3303
  return next;
1116
3304
  });
1117
- }, []), revealPath = (0, import_react.useCallback)((path) => {
1118
- rememberEditorPosition(), setWorkbench(workbenchModeForPath(path) ?? "story"), setSelected(path), expandPath(path);
1119
- }, [expandPath, rememberEditorPosition]), followAgentPath = (0, import_react.useCallback)((path) => {
3305
+ }, []), revealPath = (0, import_react3.useCallback)((path) => {
3306
+ rememberEditorPosition();
3307
+ let nextWorkbench = workbenchModeForPath(path) ?? "story";
3308
+ setWorkbench(nextWorkbench), nextWorkbench === "game" && !path.includes("/build/app/") && setGameTab("design"), setSelected(path), expandPath(path);
3309
+ }, [expandPath, rememberEditorPosition]);
3310
+ (0, import_react3.useEffect)(() => {
3311
+ if (workspace === void 0) return;
3312
+ let pending = productionIntents.filter(({ callId }) => productionIntentCalls[callId] !== !0);
3313
+ if (pending.length !== 0) {
3314
+ for (let { intent } of pending) {
3315
+ if (intent.action === "open_section" || intent.action === "focus_target") {
3316
+ let documentPath = ["\u5206\u955C.md", "\u56FE\u7247\u63D0\u793A\u8BCD.md", "\u89C6\u89C9\u8BBE\u5B9A.md", "\u5267\u672C.md", "\u89C6\u9891\u63D0\u793A\u8BCD.md"].map((name2) => `${intent.episode}/${name2}`).find((path) => workspace.files.some((file) => file.path === path));
3317
+ documentPath !== void 0 && (setWorkbench("drama"), setSelected(documentPath), expandPath(documentPath), globalThis.setTimeout(() => {
3318
+ setEditorMode("production");
3319
+ }, 0));
3320
+ }
3321
+ if (intent.action === "open_section") setProductionSection(intent.section ?? "shots");
3322
+ else if (intent.action === "focus_target")
3323
+ actions.setProductionSelectedIds((current) => ({ ...current, [intent.episode]: intent.targetId })), setProductionSection(intent.section ?? (intent.targetId?.startsWith("SHOT-") === !0 ? "shots" : "assets"));
3324
+ else if (intent.action === "set_sequence")
3325
+ actions.setProductionSequence((current) => ({
3326
+ ...current,
3327
+ [intent.episode]: (intent.shotIds ?? []).map((shotId) => ({ shotId }))
3328
+ }));
3329
+ else if (intent.action === "track_job" && intent.jobId !== void 0 && intent.targetId !== void 0 && intent.jobKind !== void 0) {
3330
+ let { jobId, targetId, jobKind } = intent;
3331
+ actions.setProductionJobs((current) => {
3332
+ let jobs = current[intent.episode] ?? [];
3333
+ return jobs.some((job) => job.id === jobId) ? {
3334
+ ...current,
3335
+ [intent.episode]: jobs.map((job) => job.id === jobId ? {
3336
+ ...job,
3337
+ targetId,
3338
+ kind: jobKind,
3339
+ status: "running",
3340
+ progress: Math.max(10, job.progress),
3341
+ prompt: intent.prompt ?? job.prompt,
3342
+ expectedOutputs: intent.expectedOutputs ?? job.expectedOutputs,
3343
+ error: void 0
3344
+ } : job)
3345
+ } : {
3346
+ ...current,
3347
+ [intent.episode]: [...jobs, {
3348
+ ...createPendingJob({
3349
+ id: jobId,
3350
+ targetId,
3351
+ kind: jobKind,
3352
+ prompt: intent.prompt ?? "",
3353
+ expectedOutputs: intent.expectedOutputs
3354
+ }),
3355
+ status: "running",
3356
+ progress: 10
3357
+ }]
3358
+ };
3359
+ });
3360
+ }
3361
+ }
3362
+ actions.setProductionIntentCalls((current) => ({
3363
+ ...current,
3364
+ ...Object.fromEntries(pending.map(({ callId }) => [callId, !0]))
3365
+ }));
3366
+ }
3367
+ }, [actions, expandPath, productionIntentCalls, productionIntents, setEditorMode, setProductionSection, setSelected, setWorkbench, workspace]);
3368
+ let openedGame = (0, import_react3.useRef)(!1);
3369
+ workbench === "game" && (openedGame.current = !0);
3370
+ let gameStudioMounted = openedGame.current, openedVideo = (0, import_react3.useRef)(!1);
3371
+ workbench === "video" && (openedVideo.current = !0);
3372
+ let videoStudioMounted = openedVideo.current, followAgentPath = (0, import_react3.useCallback)((path) => {
1120
3373
  expandPath(path);
1121
3374
  let current = selected === void 0 ? void 0 : buffersRef.current[selected];
1122
- path !== selected && current?.source === "human" && current.content !== current.saved && surfaceRef.current?.ownerDocument.activeElement === textareaRef.current || revealPath(path);
1123
- }, [expandPath, revealPath, selected]);
1124
- (0, import_react.useEffect)(() => {
1125
- workspace === void 0 || initializedWorkbench.current || (availableModes.length === 1 && setWorkbench(availableModes[0] ?? "story"), initializedWorkbench.current = !0);
1126
- }, [availableModes, workspace]), (0, import_react.useEffect)(() => {
1127
- activityPath !== void 0 && activityPath === selected && setEditorMode("source");
1128
- }, [activityPath, selected]), (0, import_react.useEffect)(() => {
1129
- modeSelection.current !== selected && (modeSelection.current = selected, setEditorMode(activityPath === selected ? "source" : previewable ? "preview" : "source"));
1130
- }, [activityPath, previewable, selected]), (0, import_react.useEffect)(() => {
1131
- workspaceLoading || activityPath === void 0 && (selected !== void 0 && ((workspace?.files.some((file) => file.path === selected) ?? !1) || buffers[selected] !== void 0) && workbenchModeForPath(selected) === workbench || setSelected(workspace === void 0 ? void 0 : preferredFile(workspace.files, workbench)));
1132
- }, [activityPath, buffers, selected, workbench, workspace, workspaceLoading]), (0, import_react.useEffect)(() => {
3375
+ path !== selected && current?.source === "human" && current.content !== current.saved && surfaceRef.current?.ownerDocument.activeElement === textareaRef.current || workbench === "game" && gameTab === "preview" || workbench === "video" && videoTab === "preview" || revealPath(path);
3376
+ }, [expandPath, gameTab, revealPath, selected, videoTab, workbench]);
3377
+ (0, import_react3.useEffect)(() => {
3378
+ activityPath !== void 0 && activityPath === selected && !selectedMedia && setEditorMode("source");
3379
+ }, [activityPath, selected, selectedMedia]), (0, import_react3.useEffect)(() => {
3380
+ modeSelection.current !== selected && (modeSelection.current = selected, setEditorMode(selected !== void 0 && activityPaths.has(selected) ? "source" : selectedMedia || previewable ? "preview" : "source"));
3381
+ }, [activityPaths, previewable, selected, selectedMedia]), (0, import_react3.useEffect)(() => {
3382
+ workspaceLoading || activityPath === void 0 && (selected !== void 0 && ((workspace?.files.some((file) => file.path === selected) ?? !1) || buffers[selected] !== void 0) && workbenchModeForPath(selected) === workbench || setSelected(workspace === void 0 ? void 0 : preferredWorkbenchFile(workspace.files, workbench)));
3383
+ }, [activityPath, buffers, selected, workbench, workspace, workspaceLoading]), (0, import_react3.useEffect)(() => {
1133
3384
  if (workspace === void 0 || workspaceLoading) return;
1134
3385
  let paths = new Set(workspace.files.map((file) => file.path));
1135
3386
  setBuffers((current) => {
@@ -1138,8 +3389,8 @@ function CreativeWorkbench({
1138
3389
  paths.has(path) || activityPaths.has(path) || (value.source === "human" && value.content !== value.saved ? value.missing !== !0 && (next[path] = { ...value, missing: !0, error: "\u6587\u4EF6\u5DF2\u4ECE workspace \u79FB\u9664\u3002\u672C\u5730\u8349\u7A3F\u4ECD\u4FDD\u7559\uFF0C\u53EF\u590D\u5236\u540E\u653E\u5F03\u8349\u7A3F\u3002" }, changed = !0) : (delete next[path], changed = !0));
1139
3390
  return changed ? next : current;
1140
3391
  });
1141
- }, [activityPaths, workspace, workspaceLoading]), (0, import_react.useEffect)(() => {
1142
- if (selected === void 0 || activityPaths.has(selected) || !(workspace?.files.some((file) => file.path === selected) ?? !1)) return;
3392
+ }, [activityPaths, workspace, workspaceLoading]), (0, import_react3.useEffect)(() => {
3393
+ if (selected === void 0 || selectedMedia || activityPaths.has(selected) || !(workspace?.files.some((file) => file.path === selected) ?? !1)) return;
1143
3394
  let controller = new AbortController();
1144
3395
  return setBuffers((current) => {
1145
3396
  let existing = current[selected];
@@ -1175,7 +3426,33 @@ function CreativeWorkbench({
1175
3426
  }), () => {
1176
3427
  controller.abort();
1177
3428
  };
1178
- }, [activityPaths, selected, sessionId, workspace?.files]), (0, import_react.useEffect)(() => {
3429
+ }, [activityPaths, selected, selectedMedia, sessionId, workspace?.files]), (0, import_react3.useEffect)(() => {
3430
+ if (!productionAvailable) return;
3431
+ let missing = episodeDocumentPaths.filter((path) => buffersRef.current[path] === void 0 && !activityPaths.has(path));
3432
+ if (missing.length === 0) return;
3433
+ let controller = new AbortController();
3434
+ return Promise.all(missing.map((path) => fetch(endpoint("file", sessionId, path), { signal: controller.signal }).then((response) => json(response)))).then((files) => {
3435
+ setBuffers((current) => {
3436
+ let next = { ...current };
3437
+ for (let file of files) {
3438
+ let existing = next[file.path];
3439
+ existing?.source === "human" && existing.content !== existing.saved || (next[file.path] = { content: file.content, saved: file.content, source: "disk", version: file.version });
3440
+ }
3441
+ return next;
3442
+ });
3443
+ }).catch((reason) => {
3444
+ controller.signal.aborted || setBuffers((current) => {
3445
+ let next = { ...current };
3446
+ for (let path of missing) {
3447
+ let existing = next[path];
3448
+ existing !== void 0 && (next[path] = { ...existing, error: reason instanceof Error ? reason.message : String(reason) });
3449
+ }
3450
+ return next;
3451
+ });
3452
+ }), () => {
3453
+ controller.abort();
3454
+ };
3455
+ }, [activityPaths, episodeDocumentPaths, productionAvailable, sessionId]), (0, import_react3.useEffect)(() => {
1179
3456
  if (normalizedActivities.length !== 0) {
1180
3457
  for (let { path } of normalizedActivities) expandPath(path);
1181
3458
  activityPath !== void 0 && followAgentPath(activityPath), setBuffers((current) => {
@@ -1208,19 +3485,19 @@ function CreativeWorkbench({
1208
3485
  return next;
1209
3486
  });
1210
3487
  }
1211
- }, [activityPath, expandPath, followAgentPath, normalizedActivities]), (0, import_react.useEffect)(() => {
3488
+ }, [activityPath, expandPath, followAgentPath, normalizedActivities]), (0, import_react3.useEffect)(() => {
1212
3489
  let signals = new Set(mutatingCallIds(runningCalls));
1213
3490
  for (let { activity: currentActivity } of normalizedActivities) signals.add(currentActivity.callId.split(":", 1)[0] ?? currentActivity.callId);
1214
3491
  let settled = [...previousSignals.current].some((callId) => !signals.has(callId));
1215
3492
  for (let callId of activityBases.current.keys())
1216
3493
  signals.has(callId.split(":", 1)[0] ?? callId) || activityBases.current.delete(callId);
1217
3494
  previousSignals.current = signals, settled && reload();
1218
- }, [normalizedActivities, reload, runningCalls]), (0, import_react.useEffect)(() => {
1219
- if (settledMutation === void 0 || settledMutation === previousSettledMutation.current) return;
3495
+ }, [normalizedActivities, reload, runningCalls]), (0, import_react3.useEffect)(() => {
3496
+ if (settledMutation === void 0 || settledMutation === previousSettledMutation.current || workspace?.cwd === void 0) return;
1220
3497
  previousSettledMutation.current = settledMutation;
1221
- let path = creativeRelativePath(settledMutation.slice(settledMutation.indexOf("\0") + 1), workspace?.cwd);
3498
+ let path = creativeRelativePath(settledMutation.slice(settledMutation.indexOf("\0") + 1), workspace.cwd);
1222
3499
  path !== void 0 && followAgentPath(path), reload();
1223
- }, [followAgentPath, reload, settledMutation, workspace?.cwd]), (0, import_react.useEffect)(() => {
3500
+ }, [followAgentPath, reload, settledMutation, workspace?.cwd]), (0, import_react3.useEffect)(() => {
1224
3501
  if (selected !== void 0) {
1225
3502
  for (let button of navRef.current?.querySelectorAll("button[data-file-path]") ?? [])
1226
3503
  if (button.dataset.filePath === selected) {
@@ -1228,7 +3505,7 @@ function CreativeWorkbench({
1228
3505
  break;
1229
3506
  }
1230
3507
  }
1231
- }, [selected]), (0, import_react.useEffect)(() => {
3508
+ }, [selected]), (0, import_react3.useEffect)(() => {
1232
3509
  if (normalizedActivities.length > 0 || workspace === void 0) return;
1233
3510
  let sessionSurface = surfaceRef.current?.parentElement;
1234
3511
  if (sessionSurface == null) return;
@@ -1249,8 +3526,20 @@ function CreativeWorkbench({
1249
3526
  return sessionSurface.addEventListener("click", followOfficialFileLink, !0), () => {
1250
3527
  sessionSurface.removeEventListener("click", followOfficialFileLink, !0);
1251
3528
  };
1252
- }, [normalizedActivities.length, revealPath, workspace]);
1253
- let savePath = (0, import_react.useCallback)(async (path) => {
3529
+ }, [normalizedActivities.length, revealPath, workspace]), (0, import_react3.useEffect)(() => {
3530
+ if (workbench !== "game" && workbench !== "video") return;
3531
+ let surface = surfaceRef.current, sessionSurface = surface?.parentElement, chat = Array.from(sessionSurface?.children ?? []).find((child) => child !== surface && child instanceof HTMLElement);
3532
+ if (!(chat instanceof HTMLElement)) return;
3533
+ let previous = {
3534
+ id: chat.id,
3535
+ role: chat.getAttribute("role"),
3536
+ labelledBy: chat.getAttribute("aria-labelledby")
3537
+ };
3538
+ return chat.id = compactChatId, chat.setAttribute("role", "tabpanel"), chat.setAttribute("aria-labelledby", `${compactTabsId}-chat-tab`), () => {
3539
+ chat.id = previous.id, previous.role === null ? chat.removeAttribute("role") : chat.setAttribute("role", previous.role), previous.labelledBy === null ? chat.removeAttribute("aria-labelledby") : chat.setAttribute("aria-labelledby", previous.labelledBy);
3540
+ };
3541
+ }, [compactChatId, compactTabsId, workbench]);
3542
+ let savePath = (0, import_react3.useCallback)(async (path) => {
1254
3543
  if (saveLocks.current.has(path)) return;
1255
3544
  let submitted = buffersRef.current[path];
1256
3545
  if (!(submitted === void 0 || submitted.missing === !0 || submitted.content === submitted.saved)) {
@@ -1320,7 +3609,7 @@ function CreativeWorkbench({
1320
3609
  }
1321
3610
  }
1322
3611
  }, [reload, sessionId]);
1323
- (0, import_react.useEffect)(() => {
3612
+ (0, import_react3.useEffect)(() => {
1324
3613
  let saveShortcut = (event) => {
1325
3614
  !(event.metaKey || event.ctrlKey) || event.key.toLocaleLowerCase() !== "s" || (event.preventDefault(), selected !== void 0 && savePath(selected));
1326
3615
  };
@@ -1328,9 +3617,9 @@ function CreativeWorkbench({
1328
3617
  globalThis.removeEventListener("keydown", saveShortcut);
1329
3618
  };
1330
3619
  }, [savePath, selected]);
1331
- let groups = (0, import_react.useMemo)(() => {
3620
+ let groups = (0, import_react3.useMemo)(() => {
1332
3621
  let value = /* @__PURE__ */ new Map(), all = [...workspace?.files ?? []].filter((file) => workbenchModeForPath(file.path) === workbench);
1333
- activityPath !== void 0 && !all.some((file) => file.path === activityPath) && all.push({ path: activityPath, bytes: 0, version: "" }), all.sort((left, right) => left.path.localeCompare(right.path, "zh-Hans-CN"));
3622
+ activityPath !== void 0 && !all.some((file) => file.path === activityPath) && all.push({ path: activityPath, bytes: 0, version: "", kind: "text" }), all.sort((left, right) => left.path.localeCompare(right.path, "zh-Hans-CN"));
1334
3623
  for (let file of all) {
1335
3624
  let directory2 = groupForPath(file.path), files = value.get(directory2) ?? [];
1336
3625
  files.push(file), value.set(directory2, files);
@@ -1341,12 +3630,18 @@ function CreativeWorkbench({
1341
3630
  return (leftIndex < 0 ? order.length : leftIndex) - (rightIndex < 0 ? order.length : rightIndex) || left.localeCompare(right, "zh-Hans-CN");
1342
3631
  });
1343
3632
  }, [activityPath, workbench, workspace]), selectWorkbench = (next) => {
1344
- setWorkbench(next);
1345
- let target = workspace === void 0 ? void 0 : preferredFile(workspace.files, next);
3633
+ if (setWorkbench(next), next === "game" || next === "video") {
3634
+ next === "game" ? setGameTab("preview") : setVideoTab("preview"), setSelected(void 0);
3635
+ return;
3636
+ }
3637
+ let target = workspace === void 0 ? void 0 : preferredWorkbenchFile(workspace.files, next);
1346
3638
  target === void 0 ? setSelected(void 0) : revealPath(target);
1347
3639
  }, selectEditorMode = (next) => {
1348
3640
  next === "preview" && rememberEditorPosition(), setEditorMode(next);
1349
- }, selectedLabel = selected ?? `\u5728\u5F53\u524D DSH workspace \u4E2D\u9009\u62E9${workbench === "story" ? "\u5C0F\u8BF4" : "\u77ED\u5267"}\u6587\u4EF6`, selectedBasename = selected?.split("/").at(-1) ?? selectedLabel, selectedGroup = selected === void 0 ? void 0 : groupForPath(selected), toggleGroup = (key, open) => {
3641
+ }, navigateProductionTarget = (target) => {
3642
+ let before = (buffersRef.current[target.path]?.content ?? "").slice(0, target.offset), approximateScrollTop = Math.max(0, before.split(/\r?\n/u).length * 28 - 96);
3643
+ editorPositions.current.set(target.path, { scrollTop: approximateScrollTop, selectionStart: target.offset, selectionEnd: target.offset }), modeSelection.current = target.path, revealPath(target.path), setEditorMode("source");
3644
+ }, selectedLabel = selected ?? `\u5728\u5F53\u524D DSH workspace \u4E2D\u9009\u62E9${workbenchLabel(workbench)}\u6587\u4EF6`, selectedBasename = selected?.split("/").at(-1) ?? selectedLabel, selectedGroup = selected === void 0 ? void 0 : groupForPath(selected), toggleGroup = (key, open) => {
1350
3645
  setExpanded((current) => ({ ...current, [key]: open }));
1351
3646
  }, resolveConflict = (keepLocal) => {
1352
3647
  if (selected === void 0 || conflict?.theirs === void 0 || conflict.theirsVersion === void 0) return;
@@ -1359,160 +3654,252 @@ function CreativeWorkbench({
1359
3654
  };
1360
3655
  });
1361
3656
  };
1362
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { ref: surfaceRef, className: "oh-story-split-surface", children: [
1363
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: plugin_default }),
1364
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("aside", { className: "oh-story-tree", children: [
1365
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-brand", children: [
1366
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "oh-story-brand-cluster", children: [
1367
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("strong", { children: [
1368
- "\u2726 ",
1369
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Oh Story" })
3657
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: surfaceRef, className: "oh-story-split-surface", "data-workbench": workbench, children: [
3658
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: plugin_default }),
3659
+ (workbench === "game" || workbench === "video") && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "oh-game-mobile-switcher", role: "tablist", "aria-label": workbench === "game" ? "\u7A84\u5C4F\u6E38\u620F\u5DE5\u4F5C\u53F0" : "\u7A84\u5C4F\u89C6\u9891\u5DE5\u4F5C\u53F0", children: ["studio", "chat"].map((pane) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3660
+ "button",
3661
+ {
3662
+ type: "button",
3663
+ role: "tab",
3664
+ id: `${compactTabsId}-${pane}-tab`,
3665
+ "aria-controls": pane === "chat" ? compactChatId : workbench === "game" ? compactStudioId : compactVideoStudioId,
3666
+ "aria-selected": (workbench === "game" ? gamePane : videoPane) === pane,
3667
+ tabIndex: (workbench === "game" ? gamePane : videoPane) === pane ? 0 : -1,
3668
+ onKeyDown: (event) => {
3669
+ handleTabKey(event, ["studio", "chat"], workbench === "game" ? gamePane : videoPane, workbench === "game" ? setGamePane : setVideoPane);
3670
+ },
3671
+ onClick: () => {
3672
+ workbench === "game" ? setGamePane(pane) : setVideoPane(pane);
3673
+ },
3674
+ children: pane === "studio" ? "\u5236\u4F5C" : "\u5BF9\u8BDD"
3675
+ },
3676
+ pane
3677
+ )) }),
3678
+ workbench === "game" && workspace === void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("main", { id: compactStudioId, className: "oh-game-studio", role: "tabpanel", "aria-labelledby": `${compactTabsId}-studio-tab`, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "oh-game-design-empty", children: error ?? "\u6B63\u5728\u8FDE\u63A5\u6E38\u620F\u5DE5\u4F5C\u53F0\u2026" }) }),
3679
+ workspace !== void 0 && gameStudioMounted && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3680
+ GameStudio,
3681
+ {
3682
+ sessionId,
3683
+ workspace,
3684
+ building: gameBuilding,
3685
+ selected,
3686
+ gameTab,
3687
+ gameProjectId,
3688
+ hidden: workbench !== "game",
3689
+ onGameTab: setGameTab,
3690
+ onGameProject: setGameProjectId,
3691
+ workbenches: WORKBENCH_MODES,
3692
+ paneId: compactStudioId,
3693
+ labelledBy: `${compactTabsId}-studio-tab`,
3694
+ onWorkbench: selectWorkbench,
3695
+ onSelect: revealPath
3696
+ }
3697
+ ),
3698
+ workbench === "video" && workspace === void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("main", { id: compactVideoStudioId, className: "oh-video-studio", role: "tabpanel", "aria-labelledby": `${compactTabsId}-studio-tab`, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "oh-video-preview-empty", children: error ?? "\u6B63\u5728\u8FDE\u63A5\u89C6\u9891\u5DE5\u4F5C\u53F0\u2026" }) }),
3699
+ workspace !== void 0 && videoStudioMounted && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3700
+ VideoStudio,
3701
+ {
3702
+ sessionId,
3703
+ projects: workspace.videos,
3704
+ running: videoBuilding,
3705
+ projectId: videoProjectId,
3706
+ tab: videoTab,
3707
+ hidden: workbench !== "video",
3708
+ workbenches: WORKBENCH_MODES,
3709
+ paneId: compactVideoStudioId,
3710
+ labelledBy: `${compactTabsId}-studio-tab`,
3711
+ onProject: setVideoProjectId,
3712
+ onTab: setVideoTab,
3713
+ onWorkbench: selectWorkbench
3714
+ }
3715
+ ),
3716
+ workbench !== "game" && workbench !== "video" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3717
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("aside", { className: "oh-story-tree", children: [
3718
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "oh-story-brand", children: [
3719
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "oh-story-brand-cluster", children: [
3720
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("strong", { children: [
3721
+ "\u2726 ",
3722
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Oh Story" })
3723
+ ] }),
3724
+ workspaceKind !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "oh-story-kind", children: workspaceKind === "story" ? "\u5C0F\u8BF4" : "\u77ED\u5267" })
1370
3725
  ] }),
1371
- workspaceKind !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "oh-story-kind", children: workspaceKind === "story" ? "\u5C0F\u8BF4" : "\u77ED\u5267" })
3726
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: reload, title: "\u5237\u65B0", "aria-label": "\u5237\u65B0\u9879\u76EE\u6587\u4EF6", children: "\u21BB" })
1372
3727
  ] }),
1373
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: reload, title: "\u5237\u65B0", "aria-label": "\u5237\u65B0\u9879\u76EE\u6587\u4EF6", children: "\u21BB" })
1374
- ] }),
1375
- showModeTabs && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-mode-tabs", role: "tablist", "aria-label": "\u521B\u4F5C\u5DE5\u4F5C\u53F0", children: WORKBENCH_MODES.map((mode) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1376
- "button",
1377
- {
1378
- type: "button",
1379
- role: "tab",
1380
- tabIndex: workbench === mode ? 0 : -1,
1381
- "aria-selected": workbench === mode,
1382
- onKeyDown: (event) => {
1383
- handleTabKey(event, WORKBENCH_MODES, workbench, selectWorkbench);
1384
- },
1385
- onClick: () => {
1386
- selectWorkbench(mode);
3728
+ workspace !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "oh-story-mode-tabs", role: "tablist", "aria-label": "\u521B\u4F5C\u5DE5\u4F5C\u53F0", children: WORKBENCH_MODES.map((mode) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3729
+ "button",
3730
+ {
3731
+ type: "button",
3732
+ role: "tab",
3733
+ tabIndex: workbench === mode ? 0 : -1,
3734
+ "aria-selected": workbench === mode,
3735
+ onKeyDown: (event) => {
3736
+ handleTabKey(event, WORKBENCH_MODES, workbench, selectWorkbench);
3737
+ },
3738
+ onClick: () => {
3739
+ selectWorkbench(mode);
3740
+ },
3741
+ children: workbenchLabel(mode)
1387
3742
  },
1388
- children: mode === "story" ? "\u5C0F\u8BF4" : "\u77ED\u5267"
1389
- },
1390
- mode
1391
- )) }),
1392
- error !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-error", children: error }),
1393
- workspace?.metadataErrors.map((message) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-warning", children: message }, message)),
1394
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("nav", { ref: navRef, "aria-label": workbench === "story" ? "\u5C0F\u8BF4\u9879\u76EE\u6587\u4EF6" : "\u77ED\u5267\u9879\u76EE\u6587\u4EF6", children: groups.map(([directory2, files]) => {
1395
- let groupOpen = selectedGroup === directory2 || expanded[directory2] === !0;
1396
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("details", { className: "oh-story-file-group", open: groupOpen, onToggle: (event) => {
1397
- toggleGroup(directory2, event.currentTarget.open);
1398
- }, children: [
1399
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("summary", { children: [
1400
- directory2,
1401
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: files.length })
3743
+ mode
3744
+ )) }),
3745
+ error !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "oh-story-error", children: error }),
3746
+ workspace?.metadataErrors.map((message) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "oh-story-warning", children: message }, message)),
3747
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("nav", { ref: navRef, "aria-label": workbench === "story" ? "\u5C0F\u8BF4\u9879\u76EE\u6587\u4EF6" : "\u77ED\u5267\u9879\u76EE\u6587\u4EF6", children: groups.map(([directory2, files]) => {
3748
+ let groupOpen = selectedGroup === directory2 || expanded[directory2] === !0;
3749
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("details", { className: "oh-story-file-group", open: groupOpen, onToggle: (event) => {
3750
+ toggleGroup(directory2, event.currentTarget.open);
3751
+ }, children: [
3752
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("summary", { children: [
3753
+ directory2,
3754
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: files.length })
3755
+ ] }),
3756
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3757
+ FileTreeNodes,
3758
+ {
3759
+ nodes: buildFileTree(files, directory2),
3760
+ depth: 1,
3761
+ expanded,
3762
+ selected,
3763
+ activityPath,
3764
+ onToggle: toggleGroup,
3765
+ onSelect: revealPath
3766
+ }
3767
+ )
3768
+ ] }, directory2);
3769
+ }) })
3770
+ ] }),
3771
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("main", { className: "oh-story-editor", children: [
3772
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("header", { children: [
3773
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "oh-story-editor-path", title: selected, children: [
3774
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: selectedLabel }),
3775
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: selectedBasename })
1402
3776
  ] }),
1403
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1404
- FileTreeNodes,
1405
- {
1406
- nodes: buildFileTree(files, directory2),
1407
- depth: 1,
1408
- expanded,
1409
- selected,
1410
- activityPath,
1411
- onToggle: toggleGroup,
1412
- onSelect: revealPath
1413
- }
1414
- )
1415
- ] }, directory2);
1416
- }) })
1417
- ] }),
1418
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("main", { className: "oh-story-editor", children: [
1419
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { children: [
1420
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "oh-story-editor-path", title: selected, children: [
1421
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: selectedLabel }),
1422
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: selectedBasename })
1423
- ] }),
1424
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-editor-actions", children: [
1425
- previewable && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-editor-tabs", role: "tablist", "aria-label": markdown ? "Markdown \u67E5\u770B\u65B9\u5F0F" : "JSONL \u67E5\u770B\u65B9\u5F0F", children: EDITOR_MODES.map((mode) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1426
- "button",
1427
- {
1428
- type: "button",
1429
- role: "tab",
1430
- tabIndex: editorMode === mode ? 0 : -1,
1431
- "aria-selected": editorMode === mode,
1432
- onKeyDown: (event) => {
1433
- handleTabKey(event, EDITOR_MODES, editorMode, selectEditorMode);
3777
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "oh-story-editor-actions", children: [
3778
+ (previewable || productionAvailable) && !selectedMedia && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "oh-story-editor-tabs", role: "tablist", "aria-label": productionAvailable ? "\u77ED\u5267\u6587\u6863\u67E5\u770B\u65B9\u5F0F" : markdown ? "Markdown \u67E5\u770B\u65B9\u5F0F" : "JSONL \u67E5\u770B\u65B9\u5F0F", children: editorModes.map((mode) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3779
+ "button",
3780
+ {
3781
+ type: "button",
3782
+ role: "tab",
3783
+ tabIndex: editorMode === mode ? 0 : -1,
3784
+ "aria-selected": editorMode === mode,
3785
+ onKeyDown: (event) => {
3786
+ handleTabKey(event, editorModes, editorMode, selectEditorMode);
3787
+ },
3788
+ onClick: () => {
3789
+ selectEditorMode(mode);
3790
+ },
3791
+ children: mode === "preview" ? "\u9884\u89C8" : mode === "source" ? "\u6E90\u7801" : "\u751F\u4EA7"
1434
3792
  },
1435
- onClick: () => {
1436
- selectEditorMode(mode);
1437
- },
1438
- children: mode === "preview" ? "\u9884\u89C8" : "\u6E90\u7801"
3793
+ mode
3794
+ )) }),
3795
+ (dirty || saving) && selected !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { className: "oh-story-save", type: "button", disabled: saving || buffer?.missing === !0, onClick: () => {
3796
+ savePath(selected);
3797
+ }, children: saving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58" })
3798
+ ] })
3799
+ ] }),
3800
+ activity !== void 0 && activityPath !== void 0 && activityPath === selected && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "oh-story-stream", "data-stage": activity.stage, role: "status", "aria-live": "polite", children: [
3801
+ "\u25CF ",
3802
+ activity.stage === "running" ? "Agent \u6B63\u5728\u5E94\u7528\u4FEE\u6539" : "Agent \u6B63\u5728\u751F\u6210\u6587\u4EF6\u5185\u5BB9"
3803
+ ] }),
3804
+ conflict !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "oh-story-conflict", role: "alert", children: [
3805
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: conflict.message }),
3806
+ conflict.theirs !== void 0 && conflict.theirsVersion !== void 0 && selected !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
3807
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: () => {
3808
+ resolveConflict(!1);
3809
+ }, children: "\u8F7D\u5165\u78C1\u76D8\u7248\u672C" }),
3810
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: () => {
3811
+ resolveConflict(!0);
3812
+ }, children: "\u4FDD\u7559\u672C\u5730\u8349\u7A3F" })
3813
+ ] })
3814
+ ] }),
3815
+ fileError !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "oh-story-error", children: fileError }),
3816
+ selected === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "oh-story-empty", children: workbench === "story" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3817
+ "\u5F53\u524D workspace \u8FD8\u6CA1\u6709\u5C0F\u8BF4\u6587\u4EF6\u3002\u53EF\u5728\u53F3\u4FA7 Chat \u4E2D\u8FD0\u884C ",
3818
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("code", { children: "/story-setup" }),
3819
+ "\u3002"
3820
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3821
+ "\u5F53\u524D workspace \u8FD8\u6CA1\u6709\u77ED\u5267\u9879\u76EE\u3002\u53EF\u5728\u53F3\u4FA7 Chat \u4E2D\u8FD0\u884C ",
3822
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("code", { children: "/short-drama" }),
3823
+ "\u3002"
3824
+ ] }) }) : selectedMedia && selectedFile !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "oh-story-media-document", children: selectedFile.mimeType?.startsWith("image/") === !0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { src: endpoint("media", sessionId, selectedFile.path), alt: selectedFile.path }) : selectedFile.mimeType?.startsWith("audio/") === !0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("audio", { src: endpoint("media", sessionId, selectedFile.path), controls: !0 }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("video", { src: endpoint("media", sessionId, selectedFile.path), controls: !0, preload: "metadata" }) }) : buffer === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "oh-story-empty", children: [
3825
+ "\u6B63\u5728\u52A0\u8F7D ",
3826
+ selected,
3827
+ "\u2026"
3828
+ ] }) : buffer.missing === !0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "oh-story-empty", children: [
3829
+ "\u6587\u4EF6\u5DF2\u4ECE workspace \u79FB\u9664\uFF0C\u672C\u5730\u8349\u7A3F\u4ECD\u4FDD\u7559\u3002\u8BF7\u5148\u590D\u5236\u9700\u8981\u7684\u5185\u5BB9\uFF0C\u518D\u653E\u5F03\u8349\u7A3F\u3002",
3830
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: () => {
3831
+ setBuffers((current) => {
3832
+ let next = { ...current };
3833
+ return delete next[selected], next;
3834
+ }), setSelected(workspace === void 0 ? void 0 : preferredWorkbenchFile(workspace.files, workbench));
3835
+ }, children: "\u653E\u5F03\u672C\u5730\u8349\u7A3F" })
3836
+ ] }) : editorMode === "production" && productionAvailable && episodeProduction !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3837
+ DramaProductionView,
3838
+ {
3839
+ production: episodeProduction,
3840
+ sessionRunning,
3841
+ queue: productionQueue,
3842
+ section: productionSection,
3843
+ selectedId: productionSelectedId,
3844
+ jobs: productionJobs,
3845
+ versions: productionVersions,
3846
+ libraryVersions: productionLibrary,
3847
+ selections: productionSelections,
3848
+ manualReferences: productionReferences,
3849
+ sequence: productionSequence,
3850
+ canvas: productionCanvas,
3851
+ zoom: productionZoom,
3852
+ onSectionChange: setProductionSection,
3853
+ onSelect: setProductionSelectedId,
3854
+ onNavigate: navigateProductionTarget,
3855
+ onJobsChange: setProductionJobs,
3856
+ onSelectionsChange: setProductionSelections,
3857
+ onManualReferencesChange: setProductionReferences,
3858
+ onOpenMedia: (path) => {
3859
+ revealPath(path);
1439
3860
  },
1440
- mode
1441
- )) }),
1442
- (dirty || saving) && selected !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "oh-story-save", type: "button", disabled: saving || buffer?.missing === !0, onClick: () => {
1443
- savePath(selected);
1444
- }, children: saving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58" })
1445
- ] })
1446
- ] }),
1447
- activity !== void 0 && activityPath !== void 0 && activityPath === selected && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-stream", "data-stage": activity.stage, role: "status", "aria-live": "polite", children: [
1448
- "\u25CF ",
1449
- activity.stage === "running" ? "Agent \u6B63\u5728\u5E94\u7528\u4FEE\u6539" : "Agent \u6B63\u5728\u751F\u6210\u6587\u4EF6\u5185\u5BB9"
1450
- ] }),
1451
- conflict !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-conflict", role: "alert", children: [
1452
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: conflict.message }),
1453
- conflict.theirs !== void 0 && conflict.theirsVersion !== void 0 && selected !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1454
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: () => {
1455
- resolveConflict(!1);
1456
- }, children: "\u8F7D\u5165\u78C1\u76D8\u7248\u672C" }),
1457
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: () => {
1458
- resolveConflict(!0);
1459
- }, children: "\u4FDD\u7559\u672C\u5730\u8349\u7A3F" })
1460
- ] })
1461
- ] }),
1462
- fileError !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-error", children: fileError }),
1463
- selected === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-empty", children: workbench === "story" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1464
- "\u5F53\u524D workspace \u8FD8\u6CA1\u6709\u5C0F\u8BF4\u6587\u4EF6\u3002\u53EF\u5728\u53F3\u4FA7 Chat \u4E2D\u8FD0\u884C ",
1465
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { children: "/story-setup" }),
1466
- "\u3002"
1467
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1468
- "\u5F53\u524D workspace \u8FD8\u6CA1\u6709\u77ED\u5267\u9879\u76EE\u3002\u53EF\u5728\u53F3\u4FA7 Chat \u4E2D\u8FD0\u884C ",
1469
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { children: "/short-drama" }),
1470
- "\u3002"
1471
- ] }) }) : buffer === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-empty", children: [
1472
- "\u6B63\u5728\u52A0\u8F7D ",
1473
- selected,
1474
- "\u2026"
1475
- ] }) : buffer.missing === !0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-empty", children: [
1476
- "\u6587\u4EF6\u5DF2\u4ECE workspace \u79FB\u9664\uFF0C\u672C\u5730\u8349\u7A3F\u4ECD\u4FDD\u7559\u3002\u8BF7\u5148\u590D\u5236\u9700\u8981\u7684\u5185\u5BB9\uFF0C\u518D\u653E\u5F03\u8349\u7A3F\u3002",
1477
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: () => {
1478
- setBuffers((current) => {
1479
- let next = { ...current };
1480
- return delete next[selected], next;
1481
- }), setSelected(workspace === void 0 ? void 0 : preferredFile(workspace.files, workbench));
1482
- }, children: "\u653E\u5F03\u672C\u5730\u8349\u7A3F" })
1483
- ] }) : previewable && editorMode === "preview" ? markdown ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MarkdownPreview, { content: buffer.content, label: selected }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(JsonlPreview, { content: buffer.content, label: selected }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1484
- "textarea",
1485
- {
1486
- ref: textareaRef,
1487
- value: buffer.content,
1488
- "data-format": structured ? "structured" : "prose",
1489
- onBlur: rememberEditorPosition,
1490
- onScroll: rememberEditorPosition,
1491
- onSelect: rememberEditorPosition,
1492
- onChange: (event) => {
1493
- let content = event.target.value;
1494
- setBuffers((current) => ({
1495
- ...current,
1496
- [selected]: {
1497
- content,
1498
- saved: current[selected]?.saved ?? "",
1499
- source: "human",
1500
- version: current[selected]?.version ?? "",
1501
- conflict: current[selected]?.conflict,
1502
- saving: current[selected]?.saving
1503
- }
1504
- }));
1505
- },
1506
- spellCheck: !structured,
1507
- "aria-label": selected
1508
- }
1509
- )
3861
+ onSequenceChange: setProductionSequence,
3862
+ onCanvasChange: setProductionCanvas,
3863
+ onZoomChange: setProductionZoom,
3864
+ onDispatchPrompt: sendProductionPrompt,
3865
+ onCancelTurn: cancelProduction,
3866
+ onRemoveQueued: removeQueuedProduction,
3867
+ onRefresh: reload
3868
+ }
3869
+ ) : previewable && editorMode === "preview" ? markdown ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MarkdownPreview, { content: buffer.content, label: selected }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(JsonlPreview, { content: buffer.content, label: selected }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3870
+ "textarea",
3871
+ {
3872
+ ref: textareaRef,
3873
+ value: buffer.content,
3874
+ "data-format": structured ? "structured" : "prose",
3875
+ onBlur: rememberEditorPosition,
3876
+ onScroll: rememberEditorPosition,
3877
+ onSelect: rememberEditorPosition,
3878
+ onChange: (event) => {
3879
+ let content = event.target.value;
3880
+ setBuffers((current) => ({
3881
+ ...current,
3882
+ [selected]: {
3883
+ content,
3884
+ saved: current[selected]?.saved ?? "",
3885
+ source: "human",
3886
+ version: current[selected]?.version ?? "",
3887
+ conflict: current[selected]?.conflict,
3888
+ saving: current[selected]?.saving
3889
+ }
3890
+ }));
3891
+ },
3892
+ spellCheck: !structured,
3893
+ "aria-label": selected
3894
+ }
3895
+ )
3896
+ ] })
1510
3897
  ] })
1511
3898
  ] });
1512
3899
  }
1513
- function CreativeSplitBridge({ sessionId, useSession, useStore, actions }) {
1514
- let marker = (0, import_react.useRef)(null), [target, setTarget] = (0, import_react.useState)(), runningCalls = useSession((snapshot) => snapshot.runningCalls), partial = useSession((snapshot) => streamingAssistant(snapshot.chat.timeline)), settledMutation = useSession((snapshot) => latestSettledMutation(snapshot.chat));
1515
- return (0, import_react.useLayoutEffect)(() => {
3900
+ function CreativeSplitBridge({ sessionId, useSession, useChat, useStore, actions, sendProductionPrompt, cancelProduction, removeQueuedProduction }) {
3901
+ let marker = (0, import_react3.useRef)(null), [target, setTarget] = (0, import_react3.useState)(), runningCalls = useChat((snapshot) => snapshot.legacy.runningCalls), partial = useChat((snapshot) => streamingAssistant(snapshot.timeline)), settledMutation = useChat((snapshot) => latestSettledMutation(snapshot)), workbench = useStore((memory) => memory.workbench), gamePane = useStore((memory) => memory.gamePane), videoPane = useStore((memory) => memory.videoPane), sessionRunning = useSession((snapshot) => snapshot.running), productionQueue = useSession((snapshot) => snapshot.queue.map((item) => ({ id: item.id, preview: item.preview }))), chat = useChat((snapshot) => snapshot), productionIntents = (0, import_react3.useMemo)(() => settledProductionIntents(chat), [chat]);
3902
+ return (0, import_react3.useLayoutEffect)(() => {
1516
3903
  let document = marker.current?.ownerDocument;
1517
3904
  if (document === void 0) return;
1518
3905
  let locate = () => {
@@ -1524,28 +3911,42 @@ function CreativeSplitBridge({ sessionId, useSession, useStore, actions }) {
1524
3911
  return observer.observe(document.body, { childList: !0, subtree: !0 }), () => {
1525
3912
  observer.disconnect();
1526
3913
  };
1527
- }, [sessionId]), (0, import_react.useLayoutEffect)(() => {
3914
+ }, [sessionId]), (0, import_react3.useLayoutEffect)(() => {
1528
3915
  let scroller = target?.parentElement;
1529
3916
  if (scroller == null) return;
1530
- let publishLayout = () => {
1531
- scroller.style.setProperty("--oh-story-scroll-height", `${String(scroller.clientHeight)}px`);
1532
- let layout = scroller.clientWidth < 620 ? "compact" : scroller.clientWidth < 900 ? "medium" : "wide";
1533
- scroller.dataset.ohStoryLayout !== layout && (scroller.dataset.ohStoryLayout = layout);
3917
+ let composerSeat = () => scroller.querySelector(":scope > [data-composer-seat]"), publishLayout = () => {
3918
+ let pinned = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight <= 8;
3919
+ scroller.style.setProperty("--oh-story-scroll-height", `${String(scroller.clientHeight)}px`), scroller.style.setProperty("--oh-story-composer-height", `${String(composerSeat()?.getBoundingClientRect().height ?? 0)}px`), scroller.dataset.ohStoryWorkbench = workbench;
3920
+ let studioPane = workbench === "video" ? videoPane : gamePane;
3921
+ scroller.dataset.ohStudioPane = studioPane;
3922
+ let compactAt = workbench === "game" || workbench === "video" ? 720 : 620, mediumAt = workbench === "game" || workbench === "video" ? 960 : 900, layout = scroller.clientWidth < compactAt ? "compact" : scroller.clientWidth < mediumAt ? "medium" : "wide";
3923
+ scroller.dataset.ohStoryLayout !== layout && (scroller.dataset.ohStoryLayout = layout), pinned && (scroller.scrollTop = scroller.scrollHeight - scroller.clientHeight);
3924
+ }, observer = new ResizeObserver(publishLayout), observed = /* @__PURE__ */ new WeakSet(), observeSeat = () => {
3925
+ let seat = composerSeat();
3926
+ seat === null || observed.has(seat) || (observed.add(seat), observer.observe(seat));
1534
3927
  };
1535
- publishLayout();
1536
- let observer = new ResizeObserver(publishLayout);
1537
- return observer.observe(scroller), () => {
1538
- observer.disconnect(), scroller.style.removeProperty("--oh-story-scroll-height"), delete scroller.dataset.ohStoryLayout;
3928
+ publishLayout(), observer.observe(scroller), observeSeat();
3929
+ let seats = new MutationObserver(() => {
3930
+ observeSeat(), publishLayout();
3931
+ });
3932
+ return seats.observe(scroller, { childList: !0 }), () => {
3933
+ observer.disconnect(), seats.disconnect(), scroller.style.removeProperty("--oh-story-scroll-height"), scroller.style.removeProperty("--oh-story-composer-height"), delete scroller.dataset.ohStoryLayout, delete scroller.dataset.ohStoryWorkbench, delete scroller.dataset.ohStudioPane;
1539
3934
  };
1540
- }, [target]), /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1541
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { ref: marker, className: "oh-story-bridge-marker", "aria-hidden": !0 }),
1542
- target === void 0 ? null : (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3935
+ }, [gamePane, target, videoPane, workbench]), /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3936
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { ref: marker, className: "oh-story-bridge-marker", "aria-hidden": !0 }),
3937
+ target === void 0 ? null : (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1543
3938
  CreativeWorkbench,
1544
3939
  {
1545
3940
  sessionId,
1546
3941
  runningCalls,
1547
3942
  partial,
1548
3943
  settledMutation,
3944
+ sessionRunning,
3945
+ productionQueue,
3946
+ productionIntents,
3947
+ sendProductionPrompt,
3948
+ cancelProduction,
3949
+ removeQueuedProduction,
1549
3950
  useStore,
1550
3951
  actions
1551
3952
  }
@@ -1553,7 +3954,7 @@ function CreativeSplitBridge({ sessionId, useSession, useStore, actions }) {
1553
3954
  ] });
1554
3955
  }
1555
3956
  function WorkbenchSeat({ SessionProvider, renderSlot }) {
1556
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SessionProvider, { children: () => renderSlot("oh-story.workspace", {}) });
3957
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SessionProvider, { children: renderSlot("oh-story.workspace", {}) });
1557
3958
  }
1558
3959
  function argsOf(block) {
1559
3960
  let raw = ("kind" in block ? block.call?.argsRaw : block.argsRaw) ?? "{}";
@@ -1571,15 +3972,32 @@ function resultOf(block) {
1571
3972
  }
1572
3973
  function RoleToolView({ block, inspect }) {
1573
3974
  let args = argsOf(block), role = typeof args.role == "string" ? args.role : "story-role", output = resultOf(block), state = "kind" in block ? block.isError ? "error" : "done" : "running";
1574
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("details", { className: "oh-story-role", "data-state": state, children: [
1575
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: plugin_default }),
1576
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("summary", { children: [
1577
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "\u2726 Role" }),
1578
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: role }),
1579
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("em", { children: state === "running" ? "\u8FD0\u884C\u4E2D" : state === "error" ? "\u5931\u8D25" : "\u5B8C\u6210" })
3975
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("details", { className: "oh-story-role", "data-state": state, children: [
3976
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: plugin_default }),
3977
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("summary", { children: [
3978
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "\u2726 Role" }),
3979
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: role }),
3980
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("em", { children: state === "running" ? "\u8FD0\u884C\u4E2D" : state === "error" ? "\u5931\u8D25" : "\u5B8C\u6210" })
1580
3981
  ] }),
1581
- output !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("pre", { children: output }),
1582
- inspect !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: inspect, children: "\u5728\u8F68\u8FF9\u4E2D\u68C0\u67E5" })
3982
+ output !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("pre", { children: output }),
3983
+ inspect !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: inspect, children: "\u5728\u8F68\u8FF9\u4E2D\u68C0\u67E5" })
3984
+ ] });
3985
+ }
3986
+ function ProductionToolView({ block, inspect }) {
3987
+ let args = argsOf(block), action = typeof args.action == "string" ? args.action : "production", episode = typeof args.episode == "string" ? args.episode : "\u77ED\u5267", state = "kind" in block ? block.isError ? "error" : "done" : "running";
3988
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("details", { className: "oh-story-role", "data-state": state, children: [
3989
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: plugin_default }),
3990
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("summary", { children: [
3991
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "\u25A6 \u751F\u4EA7" }),
3992
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("strong", { children: [
3993
+ episode,
3994
+ " \xB7 ",
3995
+ action
3996
+ ] }),
3997
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("em", { children: state === "running" ? "\u6267\u884C\u4E2D" : state === "error" ? "\u5931\u8D25" : "\u5DF2\u5E94\u7528" })
3998
+ ] }),
3999
+ resultOf(block) !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("pre", { children: resultOf(block) }),
4000
+ inspect !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: inspect, children: "\u5728\u8F68\u8FF9\u4E2D\u68C0\u67E5" })
1583
4001
  ] });
1584
4002
  }
1585
4003
  function apply(context) {
@@ -1591,13 +4009,28 @@ function apply(context) {
1591
4009
  children: { "oh-story.workspace": { kind: "single", scope: "session" } }
1592
4010
  }, WorkbenchSeat), disposeWorkbench = context.slots.register({
1593
4011
  name: "oh-story.workspace",
1594
- store: createWorkbenchStore
4012
+ store: createWorkbenchStore,
4013
+ inject: (sessionId) => {
4014
+ let binding = context.sessions.binding(sessionId), conversation = binding?.ctx.get("conversation");
4015
+ return binding === void 0 || conversation === void 0 ? {
4016
+ sendProductionPrompt: () => Promise.reject(new Error("DSH \u4F1A\u8BDD\u5F53\u524D\u4E0D\u53EF\u7528\u3002")),
4017
+ cancelProduction: () => Promise.reject(new Error("DSH \u4F1A\u8BDD\u5F53\u524D\u4E0D\u53EF\u7528\u3002")),
4018
+ removeQueuedProduction: () => Promise.reject(new Error("DSH \u4F1A\u8BDD\u5F53\u524D\u4E0D\u53EF\u7528\u3002"))
4019
+ } : {
4020
+ sendProductionPrompt: (prompt) => conversation.send(prompt),
4021
+ cancelProduction: () => conversation.cancel(),
4022
+ removeQueuedProduction: (itemId) => conversation.updateQueue(itemId, { kind: "remove" })
4023
+ };
4024
+ }
1595
4025
  }, CreativeSplitBridge);
1596
4026
  return [disposeSeat, disposeWorkbench];
1597
4027
  }), context.slots.inject("tool.call.toolview", () => context.slots.register({
1598
4028
  name: "tool.call.toolview",
1599
4029
  key: "oh_story_role"
1600
- }, RoleToolView));
4030
+ }, RoleToolView)), context.slots.inject("tool.call.toolview", () => context.slots.register({
4031
+ name: "tool.call.toolview",
4032
+ key: OH_STORY_PRODUCTION_TOOL_NAME
4033
+ }, ProductionToolView));
1601
4034
  }
1602
4035
  var index_default = { name, inject, apply };
1603
4036
  ;return module.exports;}});