@xiaobuyu/nodesign 0.0.1

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 (372) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +140 -0
  3. package/bin/nodesign.js +176 -0
  4. package/package.json +104 -0
  5. package/server/api/README.md +45 -0
  6. package/server/api/_guard.js +75 -0
  7. package/server/api/admin.js +187 -0
  8. package/server/api/assets/docx-page.js +103 -0
  9. package/server/api/assets/helpers.js +22 -0
  10. package/server/api/assets/notes.js +144 -0
  11. package/server/api/assets.js +896 -0
  12. package/server/api/board.js +57 -0
  13. package/server/api/browse.js +138 -0
  14. package/server/api/canvas.js +444 -0
  15. package/server/api/chatai.js +324 -0
  16. package/server/api/exports/build-standalone.js +980 -0
  17. package/server/api/exports/cards.js +118 -0
  18. package/server/api/exports/docx-pdf.js +46 -0
  19. package/server/api/exports/export-page.js +142 -0
  20. package/server/api/exports/handoff.js +147 -0
  21. package/server/api/exports.js +690 -0
  22. package/server/api/instruction.js +75 -0
  23. package/server/api/local.js +115 -0
  24. package/server/api/me.js +116 -0
  25. package/server/api/memory.js +185 -0
  26. package/server/api/pending-changes.js +366 -0
  27. package/server/api/plugins.js +164 -0
  28. package/server/api/projects.js +220 -0
  29. package/server/api/publish.js +64 -0
  30. package/server/api/recent.js +73 -0
  31. package/server/api/sessions.js +589 -0
  32. package/server/api/skills.js +77 -0
  33. package/server/api/standalone-fit.js +168 -0
  34. package/server/api/turn-compose.js +180 -0
  35. package/server/api/turn-inflight.js +44 -0
  36. package/server/api/turn.js +669 -0
  37. package/server/auth/README.md +17 -0
  38. package/server/auth/middleware.js +138 -0
  39. package/server/auth/origin-guard.js +71 -0
  40. package/server/auth/session.js +108 -0
  41. package/server/auth/tier.js +113 -0
  42. package/server/auth/users-store.js +348 -0
  43. package/server/edit/README.md +29 -0
  44. package/server/engine/README.md +113 -0
  45. package/server/engine/agent/agent-shared.js +550 -0
  46. package/server/engine/agent/auto-mode-default-hard-deny.txt +4 -0
  47. package/server/engine/agent/auto-mode-rules.js +78 -0
  48. package/server/engine/agent/context.js +418 -0
  49. package/server/engine/agent/events.js +464 -0
  50. package/server/engine/agent/hooks/canvas-validate.js +199 -0
  51. package/server/engine/agent/hooks/failure.js +120 -0
  52. package/server/engine/agent/hooks/file-events.js +75 -0
  53. package/server/engine/agent/hooks/lifecycle.js +233 -0
  54. package/server/engine/agent/hooks/post-canvas-focus.js +80 -0
  55. package/server/engine/agent/hooks/post-guidance.js +206 -0
  56. package/server/engine/agent/hooks/post-subagent-report.js +104 -0
  57. package/server/engine/agent/hooks/post-trim.js +58 -0
  58. package/server/engine/agent/hooks/pre-board-neighborhood.js +34 -0
  59. package/server/engine/agent/hooks/pre-defaults.js +84 -0
  60. package/server/engine/agent/hooks/pre-injectors.js +277 -0
  61. package/server/engine/agent/hooks/pre-performance-log-guard.js +78 -0
  62. package/server/engine/agent/hooks/pre-starter-files.js +61 -0
  63. package/server/engine/agent/hooks/pre-workspace-scope-guard.js +78 -0
  64. package/server/engine/agent/hooks/site-validate.js +94 -0
  65. package/server/engine/agent/hooks/tool-prompts.js +31 -0
  66. package/server/engine/agent/hooks/turn-state-memory.js +56 -0
  67. package/server/engine/agent/hooks/user-prompt-submit.js +215 -0
  68. package/server/engine/agent/hooks.js +354 -0
  69. package/server/engine/agent/init-contract.js +69 -0
  70. package/server/engine/agent/isolation.js +159 -0
  71. package/server/engine/agent/model-context.js +359 -0
  72. package/server/engine/agent/model-table.js +292 -0
  73. package/server/engine/agent/plugin-loader.js +266 -0
  74. package/server/engine/agent/prompts/nodesign-prelude.md +336 -0
  75. package/server/engine/agent/prompts/tools/ask-user-question-protocol.md +71 -0
  76. package/server/engine/agent/prompts/tools/direct-edit-protocol.md +98 -0
  77. package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +583 -0
  78. package/server/engine/agent/prompts/tools/generate-image-cookbook.md +483 -0
  79. package/server/engine/agent/prompts/tools/hybrid-reference.md +100 -0
  80. package/server/engine/agent/prompts/tools/paint-still-cookbook.md +270 -0
  81. package/server/engine/agent/prompts/tools/roll-film-cookbook.md +70 -0
  82. package/server/engine/agent/prompts/tools/site-reference.md +106 -0
  83. package/server/engine/agent/prompts/tools/tweaks-syntax.md +125 -0
  84. package/server/engine/agent/prompts/tools/vision-checker-dispatch.md +48 -0
  85. package/server/engine/agent/session-loop.js +1024 -0
  86. package/server/engine/agent/session-model.js +164 -0
  87. package/server/engine/agent/skill.js +204 -0
  88. package/server/engine/agent/system-prompts.js +86 -0
  89. package/server/engine/agent/task-events.js +78 -0
  90. package/server/engine/agents/ds-extractor.md +132 -0
  91. package/server/engine/agents/explorer.md +190 -0
  92. package/server/engine/agents/index.js +230 -0
  93. package/server/engine/agents/schemas/design-system.json +156 -0
  94. package/server/engine/agents/schemas/tweak-schema.json +113 -0
  95. package/server/engine/agents/tweak-proposer.md +127 -0
  96. package/server/engine/agents/vision-checker.md +254 -0
  97. package/server/engine/browse/capture.js +422 -0
  98. package/server/engine/browse/card.js +137 -0
  99. package/server/engine/browse/handover.js +59 -0
  100. package/server/engine/browse/page-digest.js +119 -0
  101. package/server/engine/browse/refs.js +196 -0
  102. package/server/engine/browse/registry.js +307 -0
  103. package/server/engine/browse/screencast.js +170 -0
  104. package/server/engine/browse/state.js +121 -0
  105. package/server/engine/chatai/chat-log.js +79 -0
  106. package/server/engine/chatai/index.js +193 -0
  107. package/server/engine/chatai/openai-compat.js +152 -0
  108. package/server/engine/chatai/orchestrate.js +309 -0
  109. package/server/engine/chatai/perform.js +40 -0
  110. package/server/engine/chatai/summarize.js +79 -0
  111. package/server/engine/mcp/capability-gate.js +52 -0
  112. package/server/engine/mcp/index.js +339 -0
  113. package/server/engine/mcp/param-sanitizer.js +142 -0
  114. package/server/engine/mcp/tools/arrange-on-board.js +124 -0
  115. package/server/engine/mcp/tools/artifact-session.js +278 -0
  116. package/server/engine/mcp/tools/browse-computer.js +421 -0
  117. package/server/engine/mcp/tools/browse-find-batch.js +183 -0
  118. package/server/engine/mcp/tools/browse-screenshot.js +157 -0
  119. package/server/engine/mcp/tools/browse.js +480 -0
  120. package/server/engine/mcp/tools/build-docx.js +101 -0
  121. package/server/engine/mcp/tools/clear-pending-changes.js +99 -0
  122. package/server/engine/mcp/tools/create-on-board.js +155 -0
  123. package/server/engine/mcp/tools/crystallize-skill.js +168 -0
  124. package/server/engine/mcp/tools/deliver-files.js +0 -0
  125. package/server/engine/mcp/tools/explain-style.js +237 -0
  126. package/server/engine/mcp/tools/export-handoff.js +133 -0
  127. package/server/engine/mcp/tools/expose-tweaks.js +149 -0
  128. package/server/engine/mcp/tools/generate-image.js +842 -0
  129. package/server/engine/mcp/tools/get-computed-styles.js +164 -0
  130. package/server/engine/mcp/tools/get-pending-changes.js +205 -0
  131. package/server/engine/mcp/tools/h3box-ssh.js +84 -0
  132. package/server/engine/mcp/tools/helpers/acquire-page.js +82 -0
  133. package/server/engine/mcp/tools/helpers/motion-lab.js +554 -0
  134. package/server/engine/mcp/tools/helpers/motion-scroll.js +112 -0
  135. package/server/engine/mcp/tools/helpers/perception-page.js +215 -0
  136. package/server/engine/mcp/tools/helpers/reference-download.js +103 -0
  137. package/server/engine/mcp/tools/helpers/rembg-bridge.py +80 -0
  138. package/server/engine/mcp/tools/helpers/rembg.js +273 -0
  139. package/server/engine/mcp/tools/helpers/shot-pipeline.js +280 -0
  140. package/server/engine/mcp/tools/highlight.js +68 -0
  141. package/server/engine/mcp/tools/list-pages.js +222 -0
  142. package/server/engine/mcp/tools/lookup-tags.js +59 -0
  143. package/server/engine/mcp/tools/navigate-to-page.js +60 -0
  144. package/server/engine/mcp/tools/organize-board.js +64 -0
  145. package/server/engine/mcp/tools/paint-still.js +434 -0
  146. package/server/engine/mcp/tools/pin-to-board.js +130 -0
  147. package/server/engine/mcp/tools/preview-deck.js +130 -0
  148. package/server/engine/mcp/tools/profile-scroll.js +305 -0
  149. package/server/engine/mcp/tools/publish-site.js +102 -0
  150. package/server/engine/mcp/tools/query-elements.js +195 -0
  151. package/server/engine/mcp/tools/read-board.js +103 -0
  152. package/server/engine/mcp/tools/read-document.js +93 -0
  153. package/server/engine/mcp/tools/read-page.js +264 -0
  154. package/server/engine/mcp/tools/read-tavern-json.js +143 -0
  155. package/server/engine/mcp/tools/record-decision.js +140 -0
  156. package/server/engine/mcp/tools/relate-on-board.js +110 -0
  157. package/server/engine/mcp/tools/remove-background.js +391 -0
  158. package/server/engine/mcp/tools/report-issue.js +115 -0
  159. package/server/engine/mcp/tools/roll-film.js +266 -0
  160. package/server/engine/mcp/tools/screenshot-docx.js +138 -0
  161. package/server/engine/mcp/tools/screenshot-url.js +195 -0
  162. package/server/engine/mcp/tools/screenshot.js +583 -0
  163. package/server/engine/mcp/tools/tier-gate.js +96 -0
  164. package/server/engine/mcp/tools/trace-motion.js +215 -0
  165. package/server/engine/mcp/tools/web-search.js +548 -0
  166. package/server/engine/motion/inventory.js +349 -0
  167. package/server/engine/perception/session.js +235 -0
  168. package/server/engine/plugins/nodesign/.claude-plugin/plugin.json +5 -0
  169. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +192 -0
  170. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/canvas.template.html +667 -0
  171. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/hybrid-grid.md +22 -0
  172. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/image-led-cover.md +25 -0
  173. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/portrait.md +23 -0
  174. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/quote-backdrop.md +21 -0
  175. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/section-divider.md +23 -0
  176. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/text-led.md +21 -0
  177. package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +162 -0
  178. package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +407 -0
  179. package/server/engine/plugins/nodesign/skills/docx-craft//346/226/207/346/241/243.template.json +80 -0
  180. package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +228 -0
  181. package/server/engine/plugins/nodesign/skills/rp-craft/patterns//346/274/224/345/207/272/351/241/265-/346/234/200/345/260/217/345/256/236/347/216/260.html +78 -0
  182. package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +268 -0
  183. package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +323 -0
  184. package/server/engine/plugins/nodesign/skills/site-craft/patterns/build-lane.md +134 -0
  185. package/server/engine/plugins/nodesign/skills/site-craft/patterns/cutout-collage.md +61 -0
  186. package/server/engine/plugins/nodesign/skills/site-craft/patterns/mock-app.md +163 -0
  187. package/server/engine/plugins/nodesign/skills/site-craft/patterns/page-transitions.md +86 -0
  188. package/server/engine/runs/active-runs.js +698 -0
  189. package/server/engine/runs/live-turn.js +252 -0
  190. package/server/engine/runs/store.js +364 -0
  191. package/server/engine/runs/turn-relay.js +217 -0
  192. package/server/engine/runtime/workspace.js +164 -0
  193. package/server/index.js +235 -0
  194. package/server/lib/artifact-file-path.js +80 -0
  195. package/server/lib/artifact-target.js +284 -0
  196. package/server/lib/asset-refs.js +239 -0
  197. package/server/lib/async-queue.js +104 -0
  198. package/server/lib/auto-relations.js +130 -0
  199. package/server/lib/binding-types.js +60 -0
  200. package/server/lib/board-kind-sizes.js +62 -0
  201. package/server/lib/board-relations.js +133 -0
  202. package/server/lib/browse-proxy.js +180 -0
  203. package/server/lib/canvas-id.js +36 -0
  204. package/server/lib/cover.js +227 -0
  205. package/server/lib/danbooru-tags.js +291 -0
  206. package/server/lib/doc-extract.js +156 -0
  207. package/server/lib/docx/build-from-source.js +260 -0
  208. package/server/lib/docx/build.js +490 -0
  209. package/server/lib/docx/dump-styles.js +287 -0
  210. package/server/lib/docx/fonts/nodesign-cjk.conf +164 -0
  211. package/server/lib/docx/merge-runs.js +129 -0
  212. package/server/lib/docx/numbering.js +218 -0
  213. package/server/lib/docx/order.js +152 -0
  214. package/server/lib/docx/rawzip.js +172 -0
  215. package/server/lib/docx/render.js +92 -0
  216. package/server/lib/docx/text-lint.js +225 -0
  217. package/server/lib/docx/tokens.js +335 -0
  218. package/server/lib/docx/units.js +29 -0
  219. package/server/lib/docx/xml.js +291 -0
  220. package/server/lib/docx-pages.js +187 -0
  221. package/server/lib/export-collect.js +289 -0
  222. package/server/lib/export-package.js +210 -0
  223. package/server/lib/html-srcset.js +145 -0
  224. package/server/lib/image-variant.js +460 -0
  225. package/server/lib/ingress/forward-openai-chat.js +305 -0
  226. package/server/lib/ingress/openai-chat.js +491 -0
  227. package/server/lib/ingress/session-notice.js +66 -0
  228. package/server/lib/ingress/session-routes.js +68 -0
  229. package/server/lib/ingress/slot-probe.js +130 -0
  230. package/server/lib/ingress/upstream-billing.js +52 -0
  231. package/server/lib/ingress/upstream-fail-streak.js +72 -0
  232. package/server/lib/ingress/upstream-truncation.js +48 -0
  233. package/server/lib/issues-store.js +182 -0
  234. package/server/lib/kinds/deck.js +72 -0
  235. package/server/lib/kinds/docx.js +283 -0
  236. package/server/lib/kinds/file-kinds.js +113 -0
  237. package/server/lib/kinds/index.js +202 -0
  238. package/server/lib/kinds/site.js +161 -0
  239. package/server/lib/model-ingress.js +543 -0
  240. package/server/lib/moderation.js +255 -0
  241. package/server/lib/notice-store.js +103 -0
  242. package/server/lib/plugin-install.js +104 -0
  243. package/server/lib/plugin-validator.js +721 -0
  244. package/server/lib/publish-store.js +115 -0
  245. package/server/lib/quick-summary.js +52 -0
  246. package/server/lib/quota.js +270 -0
  247. package/server/lib/rate-window.js +29 -0
  248. package/server/lib/reference-assets.js +92 -0
  249. package/server/lib/region-shot.js +135 -0
  250. package/server/lib/safe-path.js +73 -0
  251. package/server/lib/sdk-session.js +33 -0
  252. package/server/lib/showcase-store.js +92 -0
  253. package/server/lib/site-publish.js +465 -0
  254. package/server/lib/ssrf-guard.js +432 -0
  255. package/server/lib/task-scan.js +158 -0
  256. package/server/lib/tavern-json.js +154 -0
  257. package/server/lib/video-variant.js +237 -0
  258. package/server/lib/workspace-path.js +33 -0
  259. package/server/ops/fix-sdk-musl.mjs +40 -0
  260. package/server/ops/install-macos-fonts.sh +114 -0
  261. package/server/ops/macos-fonts.conf +336 -0
  262. package/server/ops/sandbox-shim/bwrap +57 -0
  263. package/server/projects/assets-summary.js +119 -0
  264. package/server/projects/auto-name.js +53 -0
  265. package/server/projects/board-store.js +640 -0
  266. package/server/projects/move-entry.js +103 -0
  267. package/server/projects/store.js +270 -0
  268. package/server/projects/ui-config.js +54 -0
  269. package/server/projects/workspace-templates.js +53 -0
  270. package/server/projects/workspace.js +1025 -0
  271. package/server/runtime/capabilities.js +153 -0
  272. package/server/runtime/local-config.js +177 -0
  273. package/server/runtime/local-env.js +88 -0
  274. package/server/runtime/platform.js +342 -0
  275. package/server/runtime/profile.js +72 -0
  276. package/server/services/rembg-launcher.js +313 -0
  277. package/server/services/rembg-service.py +334 -0
  278. package/server/shared/README.md +25 -0
  279. package/server/shared/deck.js +54 -0
  280. package/server/shared/time.js +49 -0
  281. package/server/style-pipeline/README.md +53 -0
  282. package/server/ws/broker.js +36 -0
  283. package/server/ws/browse-channel.js +177 -0
  284. package/server/ws/index.js +386 -0
  285. package/web/README.md +89 -0
  286. package/web/dist/assets/BrowserWindow-Dc7J7CYc.js +11 -0
  287. package/web/dist/assets/DeckWindow-4G0xX7P3.js +16 -0
  288. package/web/dist/assets/DocxWindow-BXQsoPkh.js +16 -0
  289. package/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  290. package/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  291. package/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  292. package/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  293. package/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  294. package/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  295. package/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  296. package/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  297. package/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  298. package/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  299. package/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  300. package/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  301. package/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  302. package/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  303. package/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  304. package/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  305. package/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  306. package/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  307. package/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  308. package/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  309. package/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  310. package/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  311. package/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  312. package/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  313. package/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  314. package/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  315. package/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  316. package/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  317. package/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  318. package/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  319. package/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  320. package/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  321. package/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  322. package/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  323. package/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  324. package/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  325. package/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  326. package/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  327. package/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  328. package/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  329. package/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  330. package/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  331. package/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  332. package/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  333. package/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  334. package/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  335. package/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  336. package/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  337. package/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  338. package/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  339. package/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  340. package/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  341. package/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  342. package/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  343. package/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  344. package/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  345. package/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  346. package/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  347. package/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  348. package/web/dist/assets/SiteWindow-D6eS-2gM.js +18 -0
  349. package/web/dist/assets/again-D8tfgKKO.webp +0 -0
  350. package/web/dist/assets/caveat-nd-CP6HlsNg.woff2 +0 -0
  351. package/web/dist/assets/clock-xJhvfYpS.webp +0 -0
  352. package/web/dist/assets/film-cut-BBKMwH7q.webp +0 -0
  353. package/web/dist/assets/film-still-BZ_DL9Tt.webp +0 -0
  354. package/web/dist/assets/index-C0KUf6Wd.css +1 -0
  355. package/web/dist/assets/index-Cbl3ATvp.js +2125 -0
  356. package/web/dist/assets/ink-desk-BRbxeDsR.webp +0 -0
  357. package/web/dist/assets/ink-night-Bz4BT3XH.webp +0 -0
  358. package/web/dist/assets/ink-portrait-C0DM4V8Q.webp +0 -0
  359. package/web/dist/assets/longcang-regular-C0uvTuKa.woff2 +0 -0
  360. package/web/dist/assets/lxgw-nd-bold-DbRfiTgo.woff2 +0 -0
  361. package/web/dist/assets/lxgw-nd-regular-CpMw9Ehg.woff2 +0 -0
  362. package/web/dist/assets/pending-edit-apply-DTjo2geW.js +31 -0
  363. package/web/dist/assets/question-8H4HSqxh.webp +0 -0
  364. package/web/dist/assets/reject-BaPJqElj.webp +0 -0
  365. package/web/dist/assets/rotate-cw-LMT3x1Zj.js +11 -0
  366. package/web/dist/assets/rp-portrait-CbrGpew2.webp +0 -0
  367. package/web/dist/assets/rp-street-C4jVKjxx.webp +0 -0
  368. package/web/dist/assets/square-dashed-mouse-pointer-CBoAkfhi.js +11 -0
  369. package/web/dist/assets/tangle-DIxHVNuT.webp +0 -0
  370. package/web/dist/assets/thisone-BylZKglV.webp +0 -0
  371. package/web/dist/assets/thumb-DO1wfK1I.webp +0 -0
  372. package/web/dist/index.html +18 -0
@@ -0,0 +1,228 @@
1
+ ---
2
+ name: rp-craft
3
+ version: 0.1.1
4
+ description: NoDesign 互动演出(RP)方法论 —— 搭一场角色扮演:编排上下文(编排.yaml 三区模型)、写角色与世界、造演出页前端、串起 chatai 通路。只要用户想角色扮演就用本 skill —— 包括直接说「开一场RP」「我们来玩角色扮演」并当场给出人物设定的情况:你是搭台的场务,不是台上的演员,绝不在设计对话里亲自扮演角色,而是按本 skill 把演出搭成能反复进场的作品(角色在专门的演出页里开口,那条通路便宜得多、也不占设计会话)。也用于把酒馆(SillyTavern)角色卡搬进来。不要用于写小说(那是另一回事,别拿这套硬套)。
5
+ ---
6
+
7
+ # 一场演出是什么
8
+
9
+ 三样东西住在**一个文件夹**里,缺一不可:
10
+
11
+ 1. `编排.yaml` —— 上下文的播放单(进模型的一切按它拼)
12
+ 2. 设定文件 —— 角色、世界、文风,自由组织,被播放单引用
13
+ 3. `index.html` —— 演出页。有它这个文件夹就会被认成站点,用通用站点窗打开
14
+
15
+ 对话记录 `对话.jsonl` 和摘要 `摘要.json` 演出中自动生成。**摘要是派生态,永远别手写。**
16
+
17
+ 台上说话的是 **chatai**:一次纯补全调用,没有工具、没有会话、改不了任何文件。
18
+ 你(agent)只负责搭台和调台,不在台上。演出的每一轮花费按平台单价表记入用户额度。
19
+
20
+ # 机制契约(这一节照抄不走样)
21
+
22
+ ## 编排.yaml 全貌
23
+
24
+ ```yaml
25
+ 模型: gemini-3.7-flash # 可省 = 平台默认。摘要可单配便宜的(摘要.模型)
26
+ 思考: 低 # 思考档,可省 = 该模型最省的那档。gemini 系 低/中/高、
27
+ # claude 系 关/高(开扩展思考);写了模型没有的档不报错,
28
+ # 按默认档跑并在回包里说明。摘要另有 摘要.思考,不写就走
29
+ # 默认档(压缩不是创作,别给摘要开高思考烧钱)
30
+ 最大输出: 400 # RP 短回合是质量旋钮:300–600 合适,放大了戏会水
31
+ 上下文预算: 60000 # 输入侧预算:系统层和尾部照给,历史是弹性区被从最旧挤压
32
+
33
+ 系统层: # → system 参数。整区冻结,是缓存的本体
34
+ - 名字: 身份与规则
35
+ 文件: 设定/老芦.md
36
+ - 名字: 世界观
37
+ 文件: 设定/夜行集市.md
38
+ - 名字: 文风
39
+ 内容: |
40
+ 第二人称现在时,只写可感知的事,结尾留一个能接的动作或问题。
41
+
42
+ 历史:
43
+ 保留轮数: 40 # 硬上限;预算不够时还会再被挤
44
+
45
+ 摘要: # 滚动前情提要:活历史攒到触发轮数就折叠一次
46
+ 保留轮数: 12
47
+ 触发轮数: 24 # 必须 > 保留轮数,差值是滞回区间
48
+ 长度: 500
49
+
50
+ 尾部: # → 拼进当轮 user 消息前部。唯一允许每轮变的区
51
+ - 名字: 当前场景
52
+ 文件: 状态/场景.md # 谁改这个文件,下一轮就生效
53
+ - 名字: 交易提醒
54
+ 内容: 涉及买卖时报价要具体到铜币数目。
55
+ 触发: [买, 卖, 价钱] # 触发词命中最近几轮或当轮输入才插入
56
+ # 停用: true # 播放单开关
57
+ ```
58
+
59
+ 铁律三条:条目 `文件`/`内容` 二选一;**触发条目只能住尾部**(住系统层直接拒开演);
60
+ 文件引用不许跑出演出文件夹。配置错了端点会拒演并报人话,照改就是。
61
+
62
+ ## 两条 API(演出页用,cookie 同源自动带)
63
+
64
+ - `GET /api/projects/<pid>/chatai/log?dir=<文件夹>` → `{records, 摘要, 配置}`,开场铺台用
65
+ - `POST /api/projects/<pid>/chatai/turn` body `{dir, input}` → SSE 流:
66
+ `data:{delta}`… 最后 `data:{done, text, usage, costUsd}`;错误 `data:{error}`。
67
+ 拒绝码:429 太快或额度尽、409 上一轮没回完、403 账号未开通演出通路
68
+
69
+ ## 开场白(角色先开口)
70
+
71
+ 往 `对话.jsonl` 种第一条记录:
72
+
73
+ ```
74
+ {"seq":1,"role":"assistant","text":"<开场白>","at":"<ISO时间>"}
75
+ ```
76
+
77
+ 它不算轮数,够老了会随摘要折叠。用户视角:一进场角色已经在说话。
78
+
79
+ # 三区纪律 —— 为什么这么摆
80
+
81
+ 顺序即上下文:**稳定的在前吃缓存,易变的在后换自由。** 缓存按字节精确匹配前缀,
82
+ 前缀里任何一个字变了,它后面的一切全部重算。
83
+
84
+ 反面教材是酒馆的世界书:几百个词条按关键词触发、插在前缀中段,每轮触发集一变
85
+ 后面全废 —— 实测 573 条词库缓存命中率 **0%,一轮一美元**。所以这里触发条目被
86
+ 结构性关进尾部:尾部本来就在最新对话之后、缓存必然失效的位置,触发集怎么变都
87
+ 伤不到前缀。
88
+
89
+ 「每轮允许多大范围变化」是用户的取舍旋钮,空间化了:一段内容放系统层 = 冻住换
90
+ 缓存,挪尾部 = 放开换灵活。用户要"自由一点",就是把东西往尾部挪。
91
+
92
+ # 特化菜谱(参考,不当分类)
93
+
94
+ **身份与规则**(系统层第一条):演谁、对用户用什么人称、回合怎么接(只推进一拍,
95
+ 别替用户行动)、回复长度、这场戏的禁区。这一条的质量决定八成体验。
96
+
97
+ **世界观:整本冻结,别切条。** 这条路是 1M 窗口 + 便宜的 flash,几万字设定冻在
98
+ 系统层一次写进缓存,比切几百条按关键词捞又稳又便宜。酒馆玩家的切条肌肉记忆是
99
+ 8k 窗口时代逼出来的,别带过来。
100
+
101
+ **文风锚**:三五条"怎么写"的约束 + 一小段范文,比一堆形容词管用。范文是最强的锚。
102
+
103
+ **状态文件当活道具**:`状态/场景.md` 这类尾部条目,演出中你或用户改文件就等于
104
+ 改状态,不需要任何变量系统。要推进剧情就改状态文件——**不要去读 `对话.jsonl`**
105
+ (见下方隐私纪律;平台也会拦)。用户想跟你讨论某段戏,会自己粘贴过来。
106
+
107
+ **触发条目 = 导演小纸条**,一场十条以内:战斗时收紧节奏、某个名字出现时提醒一段
108
+ 旧怨。它是"到了这一幕才递的条子",不是知识检索。
109
+
110
+ # 从酒馆搬东西进来
111
+
112
+ ⚠️ **任何酒馆导出的 JSON 都先用 `read_tavern_json`,不要用 Read。** 真样本
113
+ (一份社区预设)464KB、210 条提示词而启用的只有 56 条 —— Read 一次就是十几万
114
+ token 进上下文,换回来的绝大部分是停用的备选。工具两步走:`mode="digest"` 看结构
115
+ (名字/角色/字数/一句引子),挑好了再 `mode="fetch"` + `entries=[…]` 拿正文。它只读不写,怎么分组
116
+ 是你的判断。
117
+
118
+ 酒馆导出的 JSON 有三种,别搞混:
119
+
120
+ ## ① 预设(Chat Completion preset)—— `prompts[]` + `prompt_order[]`
121
+
122
+ **它跟 `编排.yaml` 是同一类东西**:管的是"每次请求怎么拼上下文 + 采样参数",
123
+ 里面没有角色也没有剧情。搬法:
124
+
125
+ 1. `read_tavern_json` 摘要 → 看清启用了哪些、顺序如何。
126
+ 2. 跟用户过一遍**分歧点**(预设通常把同一功能备了好几版只开一个):文风、
127
+ 思维链、人称、对白量、NSFW 档。问"这几个开关你要哪一档",不要自己替他选。
128
+ 3. `mode="fetch"` 拿选中条目的正文,按功能并成两三份设定文件(例:
129
+ `设定/规则.md`、`设定/文风.md`、`设定/思维链.md`),**顺序照抄原顺序**,
130
+ 全部进**系统层**(它们每轮不变,正是缓存本体)。
131
+ 4. **丢掉**:marker 占位条(角色描述/性格/场景/Chat History —— 我们由
132
+ 编排.yaml 自己管历史和角色)、0 字的分节标题、以及针对特定平台的破甲话术
133
+ (这条通路不过内容审查,尺度由账号档位定,那些绕过话术在这儿没有意义,
134
+ 还白占前缀)。
135
+ 5. 采样参数(temperature / top_p / 各种惩罚)**我们没有这些旋钮**,照实告诉
136
+ 用户:这边能调的是 模型 / 思考档 / 最大输出 / 上下文预算。预设里的
137
+ `openai_max_tokens` 可以参考着填 `最大输出`,但 RP 短回合 300–600 更好。
138
+
139
+ 一个真数字供你估:那份 56 条的预设,正文合计约 9.8k 字符 ≈ 4–5k token 常驻前缀。
140
+ 搬进系统层没问题,但要跟用户说清这是每轮都在的开销。
141
+
142
+ ## ② 角色卡(V2/V3)—— `spec: chara_card_v2` 或有 `first_mes`
143
+
144
+ `description/personality/scenario` 并成一份角色文件进系统层;`first_mes` 种成
145
+ 开场白(往 `对话.jsonl` 写 assistant seq 1);`mes_example` 提炼进文风锚;
146
+ `alternate_greetings` 问用户要哪一条。`character_book` 见下。
147
+
148
+ ## ③ 世界书(lorebook)—— `entries`
149
+
150
+ constant 的并进世界观正文(系统层);带 keys 的挑**十条以内**做成尾部触发条目
151
+ (`触发: [关键词…]`),其余并进世界观。别把整本世界书搬成触发条目 —— 触发条目
152
+ 住尾部是为了缓存稳定,几十条一起塞进去就把尾部撑成第二个前缀了。
153
+
154
+ # 建场流程
155
+
156
+ 1. **先问再动手**:题材与基调、角色是谁跟用户什么关系、文风参照(有没有喜欢的
157
+ 作品口吻)、回复长短、禁区、谁先开口。两三个问题一起问,别拷问。
158
+ 2. 落设定文件和 `编排.yaml`,要开场白就种上。
159
+ 3. 造前端,两件事分开做:
160
+ - **管线不用自己写,也不用去找**:加载本 skill 时平台已经把 `演出.template.js`
161
+ 拷进工作区根,直接 `cp 演出.template.js <演出文件夹>/演出.js`,**一个字都别改**。
162
+ URL 自省、铺场、SSE 跨分片解析、错误话术、中断、输入法回车、滚动锚定、
163
+ 摘要提示、隐私声明自检、探针模式全在里面 —— 这些是协议不是设计,现写必错
164
+ (真页面统计:三张手写的里两张漏了隐私声明和探针分支,还有一张每来一片就
165
+ 抢滚动,用户往上翻看前情会被拽回底部)。
166
+ - **皮肤自己长**:`index.html` 按题材重设计 —— 字体、色纸、气泡形态、台面装饰
167
+ 都是戏的一部分,手机窄屏必须能玩。接管线只需要回答"一条消息长什么样":
168
+
169
+ ```html
170
+ <!-- ⚠️ classic script,**不许写 type="module"**:你自检截图走 file://,
171
+ ES module 在那儿被当跨源拦掉,整页脚本一起死 -->
172
+ <script src="演出.js"></script>
173
+ <script>
174
+ const 台 = document.getElementById('台');
175
+ const 条 = (cls, 文) => { const el = document.createElement('div');
176
+ el.className = cls; el.textContent = 文; 台.appendChild(el); return el; };
177
+ ND演出.挂载({
178
+ 台,
179
+ 输入: document.getElementById('输入'),
180
+ 发送钮: document.getElementById('发送'),
181
+ 画: {
182
+ 用户: (文) => 条('回合 用户', 文),
183
+ 演出: () => { const el = 条('回合 演出 等', '');
184
+ return { 写: (全) => { el.classList.remove('等'); el.textContent = 全; },
185
+ 完成: (全) => { el.classList.remove('等'); el.textContent = 全; },
186
+ 撤: () => el.remove() }; },
187
+ 提示: (文) => 条('提示', 文),
188
+ 出错: (文) => 条('出错', 文),
189
+ },
190
+ });
191
+ </script>
192
+ ```
193
+
194
+ `写(全文)` 每来一片调一次,给的是**到目前为止的全文**不是增量,直接
195
+ `textContent = 全文` 最省事。想看一整张能跑的皮肤范例:
196
+ `patterns/演出页-最小实现.html`(找不到就算了,上面这段就够接)。
197
+
198
+ 4. 自检:screenshot 演出页,确认铺场正常、开场白在台上。第一轮留给用户开。
199
+ 5. 告诉用户:点开这个文件夹的站点窗就能进场;想调口味(换文风、改场景、挪冻结
200
+ 范围)随时叫你改 —— 你就是这场戏的场务。
201
+
202
+ # 价钱小抄(跟用户聊配置时用)
203
+
204
+ gemini-3.7-flash:$0.75/M 入、$3.75/M 出(促销价,思考 token 计入输出且关不掉,
205
+ 实测约为正文的三倍半);claude-sonnet:$3/$15。一轮典型 RP(几千字上下文、几百字
206
+ 回复)flash 约 $0.003–0.008。预算紧就守住:最大输出别超 600、上下文预算 4–6 万。
207
+
208
+ 思考档不是免费的旋钮:思考 token 一律按输出价计。gemini 三档实测差异有限(别拿它
209
+ 当质量保证),claude 的「高」是真开扩展思考、一轮更贵。用户没提就别自作主张往上调。
210
+
211
+ # 隐私纪律(跟机制契约同级,照办)
212
+
213
+ 演出记录是用户的隐私:**台词只活在 chatai 通路里,永远不进你的上下文。**
214
+
215
+ - **不读 `对话.jsonl` / `摘要.json`**(Read/Grep 会被平台拦下)。调度剧情的接口
216
+ 是 状态/ 文件;用户想给你看片段会自己粘贴——主动粘贴即知情同意。
217
+ - 这两道保险住在 `演出.js` 里,改皮时**不许删也不许改**:`<meta name="nd-privacy">`
218
+ (用户在演出页上标注元素时,平台据此剥文本只发结构给你;模块发现页面没有会
219
+ 自己补一条);探针分支(显式带 `?nd_probe=1` 打开时渲样例台账——⚠️ 你的自检截图走 http、跟用户预览同源,**不会**自动进这个分支;你看到的就是用户看到的,别把真问题当样例——
220
+ screenshot 走的就是 file://,所以你看到的是样例,那是**正常的**,不是页面坏了,
221
+ 别去"修")。
222
+ - 自检截图想看特定版面状态:`artifact_open` 开进会话真的点到那一步再量(量具带 `live:true`),或 `beforeShot` 塞假台账 —— 两条都行,别想办法弄真记录。
223
+
224
+ # 边界
225
+
226
+ - 页面里永远不出现任何密钥(不需要:端点靠 cookie)。
227
+ - 页面不做定时轮询、自动重发 —— 限频闸会拦,但根上就别写。
228
+ - 演出内容平台不审(chatai 无工具无副作用),尺度是用户和角色设定之间的事。
@@ -0,0 +1,78 @@
1
+ <!doctype html>
2
+ <html lang="zh">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <!-- 隐私声明:平台据它把标注里的文本剥成占位符再发给 agent。
7
+ 演出.js 发现没有会自己补一条,但**自己写上更好**(不依赖脚本跑起来) -->
8
+ <meta name="nd-privacy" content="演出">
9
+ <title>演出</title>
10
+ <style>
11
+ /* ── 这一整块是"皮肤",按题材整个重画:字体、色纸、气泡形态都是戏的一部分 ── */
12
+ :root { --纸: #F4EFE4; --墨: #2B2117; --淡: #8A7A62; }
13
+ * { box-sizing: border-box; }
14
+ body { margin: 0; height: 100dvh; display: flex; flex-direction: column;
15
+ background: var(--纸); color: var(--墨);
16
+ font: 16px/1.85 "Songti SC", "Noto Serif CJK SC", serif; }
17
+ #台 { flex: 1; overflow-y: auto; padding: 24px 18px 8px; max-width: 720px; width: 100%; margin: 0 auto; }
18
+ .回合 { margin: 0 0 18px; white-space: pre-wrap; }
19
+ .回合.用户 { color: var(--淡); }
20
+ .回合.用户::before { content: "› "; }
21
+ .等::after { content: "…"; opacity: .5; }
22
+ .提示, .出错 { font-size: 13px; margin: 10px 0; }
23
+ .提示 { color: var(--淡); }
24
+ .出错 { color: #A8362B; }
25
+ form { display: flex; gap: 10px; padding: 12px 18px 18px; max-width: 720px; width: 100%; margin: 0 auto; }
26
+ textarea { flex: 1; resize: none; height: 44px; padding: 10px 12px; font: inherit; font-size: 15px;
27
+ background: #fff8; border: 1px solid #0002; border-radius: 2px; outline: none; color: inherit; }
28
+ button { padding: 0 18px; font: inherit; font-size: 14px; letter-spacing: .2em;
29
+ background: var(--墨); color: var(--纸); border: 0; border-radius: 2px; cursor: pointer; }
30
+ button:disabled { background: var(--淡); cursor: default; }
31
+ </style>
32
+ </head>
33
+ <body>
34
+ <div id="台"></div>
35
+ <form id="条" autocomplete="off">
36
+ <textarea id="输入" rows="1" placeholder="你做什么、说什么…"></textarea>
37
+ <button id="发送" type="submit">进</button>
38
+ </form>
39
+
40
+ <!-- ⚠️ classic script,**不要写 type="module"**:agent 自检截图走 file://,
41
+ ES module 在 file: 下会被当跨源拦掉,整页脚本一起死 -->
42
+ <script src="演出.js"></script>
43
+ <script>
44
+ const 台 = document.getElementById('台');
45
+
46
+ /** 皮肤只需要回答"一条消息长什么样",管线的事一件都不用管 */
47
+ function 条(cls, 文) {
48
+ const el = document.createElement('div');
49
+ el.className = cls;
50
+ el.textContent = 文;
51
+ 台.appendChild(el);
52
+ return el;
53
+ }
54
+
55
+ const 场 = ND演出.挂载({
56
+ 台,
57
+ 输入: document.getElementById('输入'),
58
+ 发送钮: document.getElementById('发送'),
59
+ 画: {
60
+ 用户: (文) => 条('回合 用户', 文),
61
+ 演出: () => {
62
+ const el = 条('回合 演出 等', '');
63
+ return {
64
+ 写: (全文) => { el.classList.remove('等'); el.textContent = 全文; },
65
+ 完成: (全文) => { el.classList.remove('等'); el.textContent = 全文; },
66
+ 撤: () => el.remove(),
67
+ };
68
+ },
69
+ 提示: (文) => 条('提示', 文),
70
+ 出错: (文) => 条('出错', 文),
71
+ },
72
+ });
73
+
74
+ // 表单提交也走同一个口(回车已由 演出.js 接管,含输入法判定)
75
+ document.getElementById('条').addEventListener('submit', (e) => { e.preventDefault(); 场.发送(); });
76
+ </script>
77
+ </body>
78
+ </html>
@@ -0,0 +1,268 @@
1
+ /**
2
+ * 演出.js —— 演出页的管线(NoDesign RP,2026-08-15)
3
+ *
4
+ * ⭐ **起手文件**:加载 rp-craft skill 时平台自动把它拷进工作区根,
5
+ * agent 只需 `cp 演出.template.js <演出文件夹>/演出.js`。**一个字都别改** ——
6
+ * 这里面全是协议和踩过的坑,不是设计。
7
+ * (为什么不放 patterns/ 让 agent 自己读:真跑实测它会 `find /` 全盘搜,
8
+ * 搜出生产仓和开发 worktree 两份再自己猜哪份对 —— 今天字节相同侥幸没事,
9
+ * 哪天两边不同步就会静默抄错版本。起手文件由平台投递,没有猜的余地。)
10
+ * 页面负责长什么样(`画` 里那几个函数随便写),这个文件负责让它能玩:
11
+ *
12
+ * URL 自省 · 铺场 · SSE 跨分片解析 · 错误话术 · 中断 · 输入法回车 ·
13
+ * 滚动锚定 · 摘要折叠提示 · 隐私声明 · 探针模式
14
+ *
15
+ * ⚠️ **必须用 classic script 引**:`<script src="演出.js"></script>`,
16
+ * **不能写 type="module"**。
17
+ *
18
+ * 理由更新(2026-08-18):原来的理由是"agent 自检走 file://,module 在 file: 下
19
+ * 被当跨源拦掉"—— 感知层今天改走 http 了,这个理由**已经不成立**。规则本身仍然
20
+ * 保留,因为它保护的是另一个真场景:**用户把演出页导出成 zip、在本地双击打开**,
21
+ * 那才是 file://,module 在那里照样整页死。
22
+ * (规则对但理由死了的注释,比没有注释更危险 —— 下一个人会按死理由去推翻规则。)
23
+ *
24
+ * ## 怎么用
25
+ *
26
+ * const 场 = ND演出.挂载({
27
+ * 台: document.getElementById('台'), // 滚动容器
28
+ * 输入: document.getElementById('输入'), // 可选,给了就接管回车
29
+ * 发送钮: document.getElementById('发送'), // 可选,给了就接管点击与禁用态
30
+ * 画: {
31
+ * 用户: (文) => {…; return 节点}, // 画一条用户消息
32
+ * 演出: () => ({ 写(全文), 完成(全文), 撤() }), // 画一条演出气泡,返回把手
33
+ * 提示: (文) => {…}, // 灰字提示(摘要折叠、探针说明)
34
+ * 出错: (文) => {…}, // 错误条
35
+ * },
36
+ * });
37
+ * // 想自己接管发送按钮就调 场.发送(文本);想中断当前这轮调 场.中断()
38
+ *
39
+ * `画.演出()` 返回的把手:`写(全文)` 每来一片调一次(给的是**到目前为止的全文**,
40
+ * 不是增量,页面直接 textContent = 全文 最省事);`完成(全文)` 收尾调一次;
41
+ * `撤()` 出错时把这条气泡拿掉。
42
+ */
43
+ (function (global) {
44
+ 'use strict';
45
+
46
+ /** 端点上限,跟服务端 MAX_INPUT_CHARS 对齐 */
47
+ var 输入上限 = 8000;
48
+
49
+ /** 样例台账:探针模式下渲这个,真台账一个字都不取 */
50
+ var 样例 = [
51
+ { role: 'assistant', text: '〔样例〕雾夜里,摊子上的豆油灯亮着。' },
52
+ { role: 'user', text: '〔样例〕我走近摊子。' },
53
+ ];
54
+
55
+ /**
56
+ * 从页面 URL 认出项目和演出文件夹。
57
+ * 线上是 `/api/projects/<pid>/artifact-file/<dir>/index.html`;
58
+ * file:// 打开时认不出来(导出的 zip 在本地双击就是这种)—— 那是探针模式,
59
+ * 不是坏了。
60
+ */
61
+ function 自省() {
62
+ var m = location.pathname.match(/^\/api\/projects\/([^/]+)\/artifact-file\/(.+)$/);
63
+ if (!m) return { pid: null, dir: null };
64
+ // ⚠️ pathname 是**已经百分号编码**的:中文文件夹名在这儿长成 %E6%A8%A1…,
65
+ // 不解码就会在 encodeURIComponent 时二次编码,服务端收到 %25E6… 直接 404。
66
+ // 逐段解码('/' 是分隔符,不能整串解)。这个产品的文件夹名默认就是中文,
67
+ // 所以这一步不是边角情况,是主路径。
68
+ var dir = m[2].split('/').slice(0, -1).map(decodeURIComponent).join('/');
69
+ return { pid: m[1], dir: dir };
70
+ }
71
+
72
+ /**
73
+ * 隐私声明自检:页面上没有 `<meta name="nd-privacy">` 就现补一条。
74
+ *
75
+ * 这道防线不能指望"皮肤作者记得写"—— 平台据它把标注里的文本剥成占位符再发给
76
+ * agent。三张真页面里只有一张带着它(2026-08-15 查实),所以责任内建在管线里。
77
+ */
78
+ function 保证隐私声明() {
79
+ if (document.querySelector('meta[name="nd-privacy"]')) return;
80
+ var meta = document.createElement('meta');
81
+ meta.setAttribute('name', 'nd-privacy');
82
+ meta.setAttribute('content', '演出');
83
+ (document.head || document.documentElement).appendChild(meta);
84
+ }
85
+
86
+ /** 用户是不是本来就贴着底:只有贴着底才跟随滚动,不然会把正在往上翻的人拽回来 */
87
+ function 贴底(台) {
88
+ if (!台) return false;
89
+ return 台.scrollHeight - 台.scrollTop - 台.clientHeight < 40;
90
+ }
91
+ function 跟随(台, 要跟) {
92
+ if (台 && 要跟) 台.scrollTop = 台.scrollHeight;
93
+ }
94
+
95
+ /** 输入法回车判定:组字中的回车不是发送(10 处踩过的同一个坑) */
96
+ function 是输入法回车(e) {
97
+ return e.isComposing || e.keyCode === 229 || e.which === 229;
98
+ }
99
+
100
+ function 话术(status, 服务端说的) {
101
+ if (服务端说的) return 服务端说的; // 服务端的话最准,优先用
102
+ if (status === 409) return '这场演出正有一轮在跑,等它回完';
103
+ if (status === 429) return '太快了,或者额度用完了,歇一会儿';
104
+ if (status === 403) return '这个账号还没开通演出通路';
105
+ if (status === 404) return '找不到这场演出的文件夹';
106
+ if (status === 422) return '这一轮没被接受(输入是空的或者太长)';
107
+ return '出错了(' + status + ')';
108
+ }
109
+
110
+ function 挂载(选项) {
111
+ var 台 = 选项.台 || null;
112
+ var 输入 = 选项.输入 || null;
113
+ var 发送钮 = 选项.发送钮 || null;
114
+ var 画 = 选项.画 || {};
115
+ var 当摘要 = 选项.当摘要 || function () {};
116
+ var 当状态 = 选项.当状态 || function () {};
117
+
118
+ 保证隐私声明();
119
+
120
+ var 址 = 自省();
121
+ var 探针 = location.protocol === 'file:'
122
+ || new URLSearchParams(location.search).has('nd_probe')
123
+ || !址.pid;
124
+ var 跑着 = false;
125
+ var 控制器 = null;
126
+
127
+ function api(路) {
128
+ return '/api/projects/' + 址.pid + '/chatai/' + 路;
129
+ }
130
+ function 忙(值) {
131
+ 跑着 = 值;
132
+ if (发送钮) 发送钮.disabled = 值;
133
+ 当状态(值 ? '跑着' : '闲着');
134
+ }
135
+
136
+ /** 开场铺台账。探针模式渲样例,绝不打网络。 */
137
+ async function 铺场() {
138
+ if (探针) {
139
+ 样例.forEach(function (r) {
140
+ if (r.role === 'user') 画.用户(r.text);
141
+ else { var h = 画.演出(); h.完成(r.text); }
142
+ });
143
+ if (画.提示) 画.提示('(探针模式:样例台账,没有读真实记录)');
144
+ 跟随(台, true);
145
+ return;
146
+ }
147
+ try {
148
+ var r = await fetch(api('log') + '?dir=' + encodeURIComponent(址.dir));
149
+ var j = await r.json().catch(function () { return {}; });
150
+ if (!r.ok) { if (画.出错) 画.出错(话术(r.status, j.error)); return; }
151
+ (j.records || []).forEach(function (rec) { // 端点给的就叫 records
152
+ if (rec.role === 'user') 画.用户(rec.text);
153
+ else { var h = 画.演出(); h.完成(rec.text); }
154
+ });
155
+ 跟随(台, true);
156
+ } catch (e) {
157
+ if (画.出错) 画.出错('读不到台账:' + (e && e.message ? e.message : '网络断了'));
158
+ }
159
+ }
160
+
161
+ /** 发一轮。文本从参数来(没给就从输入框取),走 SSE。 */
162
+ async function 发送(文本) {
163
+ var text = String(文本 != null ? 文本 : (输入 ? 输入.value : '')).trim();
164
+ if (!text || 跑着) return;
165
+ if (text.length > 输入上限) {
166
+ if (画.出错) 画.出错('这段太长了(上限 ' + 输入上限 + ' 字)');
167
+ return;
168
+ }
169
+ 忙(true);
170
+ var 要跟 = 贴底(台);
171
+ 画.用户(text);
172
+ 跟随(台, 要跟);
173
+ if (输入) 输入.value = '';
174
+
175
+ if (探针) { // 探针模式不打网络,回一句样例
176
+ var 样 = 画.演出();
177
+ 样.完成('〔样例〕摊主抬头看了你一眼,没说话。');
178
+ 跟随(台, 要跟);
179
+ 忙(false);
180
+ return;
181
+ }
182
+
183
+ var 手 = 画.演出();
184
+ var 还回去 = function (话) {
185
+ if (手.撤) 手.撤();
186
+ if (画.出错) 画.出错(话);
187
+ if (输入 && !输入.value) 输入.value = text; // 失败把话还给用户,别让他重打
188
+ 忙(false);
189
+ };
190
+
191
+ 控制器 = new AbortController();
192
+ var res;
193
+ try {
194
+ res = await fetch(api('turn'), {
195
+ method: 'POST',
196
+ headers: { 'Content-Type': 'application/json' },
197
+ body: JSON.stringify({ dir: 址.dir, input: text }),
198
+ signal: 控制器.signal,
199
+ });
200
+ } catch (e) {
201
+ return 还回去(e && e.name === 'AbortError' ? '这一轮中断了' : '网络断了,稍后重发');
202
+ }
203
+ if (!res.ok) {
204
+ var j2 = await res.json().catch(function () { return {}; });
205
+ return 还回去(话术(res.status, j2.error));
206
+ }
207
+
208
+ // SSE:`data: {...}\n\n`。⚠️ 一个事件会跨 chunk 到(中转站分片很粗),
209
+ // 所以必须按 '\n\n' 切、把最后半截留在缓冲里,不能逐 chunk 解析。
210
+ var reader = res.body.getReader();
211
+ var dec = new TextDecoder();
212
+ var buf = '';
213
+ var 全文 = '';
214
+ var 出过错 = false;
215
+ try {
216
+ while (true) {
217
+ var 步 = await reader.read();
218
+ if (步.done) break;
219
+ buf += dec.decode(步.value, { stream: true });
220
+ var 块 = buf.split('\n\n');
221
+ buf = 块.pop();
222
+ for (var i = 0; i < 块.length; i++) {
223
+ var 行 = 块[i].split('\n').filter(function (l) { return l.indexOf('data:') === 0; })[0];
224
+ if (!行) continue;
225
+ var ev;
226
+ try { ev = JSON.parse(行.slice(5)); } catch (e) { continue; }
227
+ if (ev.delta) { 全文 += ev.delta; 手.写(全文); 跟随(台, 要跟); }
228
+ if (ev.error) { 出过错 = true; 还回去(ev.error); }
229
+ if (ev.done) {
230
+ if (!全文 && ev.text) 全文 = ev.text;
231
+ 手.完成(全文);
232
+ 跟随(台, 要跟);
233
+ if (ev.摘要 && 画.提示) {
234
+ 画.提示(ev.摘要.失败 ? '(前情提要没写成,下一轮再试)' : '(已把旧的对话折进前情提要)');
235
+ }
236
+ 当摘要(ev.摘要 || null);
237
+ }
238
+ }
239
+ }
240
+ } catch (e) {
241
+ if (!出过错) return 还回去(e && e.name === 'AbortError' ? '这一轮中断了' : '流断了,刚才那段可能没说完');
242
+ }
243
+ 控制器 = null;
244
+ if (!出过错) 忙(false);
245
+ if (输入 && 输入.focus) 输入.focus();
246
+ }
247
+
248
+ /** 掐掉正在跑的那一轮(服务端会一起掐上游,不留半条记录) */
249
+ function 中断() {
250
+ if (控制器) { 控制器.abort(); 控制器 = null; }
251
+ }
252
+
253
+ if (输入) {
254
+ 输入.addEventListener('keydown', function (e) {
255
+ if (e.key === 'Enter' && !e.shiftKey && !是输入法回车(e)) {
256
+ e.preventDefault();
257
+ 发送();
258
+ }
259
+ });
260
+ }
261
+ if (发送钮) 发送钮.addEventListener('click', function () { 发送(); });
262
+
263
+ 铺场();
264
+ return { 发送: 发送, 中断: 中断, 铺场: 铺场, 探针: 探针, 址: 址 };
265
+ }
266
+
267
+ global.ND演出 = { 挂载: 挂载, 版本: '1.0' };
268
+ }(window));