@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.
- package/LICENSE +661 -0
- package/README.md +140 -0
- package/bin/nodesign.js +176 -0
- package/package.json +104 -0
- package/server/api/README.md +45 -0
- package/server/api/_guard.js +75 -0
- package/server/api/admin.js +187 -0
- package/server/api/assets/docx-page.js +103 -0
- package/server/api/assets/helpers.js +22 -0
- package/server/api/assets/notes.js +144 -0
- package/server/api/assets.js +896 -0
- package/server/api/board.js +57 -0
- package/server/api/browse.js +138 -0
- package/server/api/canvas.js +444 -0
- package/server/api/chatai.js +324 -0
- package/server/api/exports/build-standalone.js +980 -0
- package/server/api/exports/cards.js +118 -0
- package/server/api/exports/docx-pdf.js +46 -0
- package/server/api/exports/export-page.js +142 -0
- package/server/api/exports/handoff.js +147 -0
- package/server/api/exports.js +690 -0
- package/server/api/instruction.js +75 -0
- package/server/api/local.js +115 -0
- package/server/api/me.js +116 -0
- package/server/api/memory.js +185 -0
- package/server/api/pending-changes.js +366 -0
- package/server/api/plugins.js +164 -0
- package/server/api/projects.js +220 -0
- package/server/api/publish.js +64 -0
- package/server/api/recent.js +73 -0
- package/server/api/sessions.js +589 -0
- package/server/api/skills.js +77 -0
- package/server/api/standalone-fit.js +168 -0
- package/server/api/turn-compose.js +180 -0
- package/server/api/turn-inflight.js +44 -0
- package/server/api/turn.js +669 -0
- package/server/auth/README.md +17 -0
- package/server/auth/middleware.js +138 -0
- package/server/auth/origin-guard.js +71 -0
- package/server/auth/session.js +108 -0
- package/server/auth/tier.js +113 -0
- package/server/auth/users-store.js +348 -0
- package/server/edit/README.md +29 -0
- package/server/engine/README.md +113 -0
- package/server/engine/agent/agent-shared.js +550 -0
- package/server/engine/agent/auto-mode-default-hard-deny.txt +4 -0
- package/server/engine/agent/auto-mode-rules.js +78 -0
- package/server/engine/agent/context.js +418 -0
- package/server/engine/agent/events.js +464 -0
- package/server/engine/agent/hooks/canvas-validate.js +199 -0
- package/server/engine/agent/hooks/failure.js +120 -0
- package/server/engine/agent/hooks/file-events.js +75 -0
- package/server/engine/agent/hooks/lifecycle.js +233 -0
- package/server/engine/agent/hooks/post-canvas-focus.js +80 -0
- package/server/engine/agent/hooks/post-guidance.js +206 -0
- package/server/engine/agent/hooks/post-subagent-report.js +104 -0
- package/server/engine/agent/hooks/post-trim.js +58 -0
- package/server/engine/agent/hooks/pre-board-neighborhood.js +34 -0
- package/server/engine/agent/hooks/pre-defaults.js +84 -0
- package/server/engine/agent/hooks/pre-injectors.js +277 -0
- package/server/engine/agent/hooks/pre-performance-log-guard.js +78 -0
- package/server/engine/agent/hooks/pre-starter-files.js +61 -0
- package/server/engine/agent/hooks/pre-workspace-scope-guard.js +78 -0
- package/server/engine/agent/hooks/site-validate.js +94 -0
- package/server/engine/agent/hooks/tool-prompts.js +31 -0
- package/server/engine/agent/hooks/turn-state-memory.js +56 -0
- package/server/engine/agent/hooks/user-prompt-submit.js +215 -0
- package/server/engine/agent/hooks.js +354 -0
- package/server/engine/agent/init-contract.js +69 -0
- package/server/engine/agent/isolation.js +159 -0
- package/server/engine/agent/model-context.js +359 -0
- package/server/engine/agent/model-table.js +292 -0
- package/server/engine/agent/plugin-loader.js +266 -0
- package/server/engine/agent/prompts/nodesign-prelude.md +336 -0
- package/server/engine/agent/prompts/tools/ask-user-question-protocol.md +71 -0
- package/server/engine/agent/prompts/tools/direct-edit-protocol.md +98 -0
- package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +583 -0
- package/server/engine/agent/prompts/tools/generate-image-cookbook.md +483 -0
- package/server/engine/agent/prompts/tools/hybrid-reference.md +100 -0
- package/server/engine/agent/prompts/tools/paint-still-cookbook.md +270 -0
- package/server/engine/agent/prompts/tools/roll-film-cookbook.md +70 -0
- package/server/engine/agent/prompts/tools/site-reference.md +106 -0
- package/server/engine/agent/prompts/tools/tweaks-syntax.md +125 -0
- package/server/engine/agent/prompts/tools/vision-checker-dispatch.md +48 -0
- package/server/engine/agent/session-loop.js +1024 -0
- package/server/engine/agent/session-model.js +164 -0
- package/server/engine/agent/skill.js +204 -0
- package/server/engine/agent/system-prompts.js +86 -0
- package/server/engine/agent/task-events.js +78 -0
- package/server/engine/agents/ds-extractor.md +132 -0
- package/server/engine/agents/explorer.md +190 -0
- package/server/engine/agents/index.js +230 -0
- package/server/engine/agents/schemas/design-system.json +156 -0
- package/server/engine/agents/schemas/tweak-schema.json +113 -0
- package/server/engine/agents/tweak-proposer.md +127 -0
- package/server/engine/agents/vision-checker.md +254 -0
- package/server/engine/browse/capture.js +422 -0
- package/server/engine/browse/card.js +137 -0
- package/server/engine/browse/handover.js +59 -0
- package/server/engine/browse/page-digest.js +119 -0
- package/server/engine/browse/refs.js +196 -0
- package/server/engine/browse/registry.js +307 -0
- package/server/engine/browse/screencast.js +170 -0
- package/server/engine/browse/state.js +121 -0
- package/server/engine/chatai/chat-log.js +79 -0
- package/server/engine/chatai/index.js +193 -0
- package/server/engine/chatai/openai-compat.js +152 -0
- package/server/engine/chatai/orchestrate.js +309 -0
- package/server/engine/chatai/perform.js +40 -0
- package/server/engine/chatai/summarize.js +79 -0
- package/server/engine/mcp/capability-gate.js +52 -0
- package/server/engine/mcp/index.js +339 -0
- package/server/engine/mcp/param-sanitizer.js +142 -0
- package/server/engine/mcp/tools/arrange-on-board.js +124 -0
- package/server/engine/mcp/tools/artifact-session.js +278 -0
- package/server/engine/mcp/tools/browse-computer.js +421 -0
- package/server/engine/mcp/tools/browse-find-batch.js +183 -0
- package/server/engine/mcp/tools/browse-screenshot.js +157 -0
- package/server/engine/mcp/tools/browse.js +480 -0
- package/server/engine/mcp/tools/build-docx.js +101 -0
- package/server/engine/mcp/tools/clear-pending-changes.js +99 -0
- package/server/engine/mcp/tools/create-on-board.js +155 -0
- package/server/engine/mcp/tools/crystallize-skill.js +168 -0
- package/server/engine/mcp/tools/deliver-files.js +0 -0
- package/server/engine/mcp/tools/explain-style.js +237 -0
- package/server/engine/mcp/tools/export-handoff.js +133 -0
- package/server/engine/mcp/tools/expose-tweaks.js +149 -0
- package/server/engine/mcp/tools/generate-image.js +842 -0
- package/server/engine/mcp/tools/get-computed-styles.js +164 -0
- package/server/engine/mcp/tools/get-pending-changes.js +205 -0
- package/server/engine/mcp/tools/h3box-ssh.js +84 -0
- package/server/engine/mcp/tools/helpers/acquire-page.js +82 -0
- package/server/engine/mcp/tools/helpers/motion-lab.js +554 -0
- package/server/engine/mcp/tools/helpers/motion-scroll.js +112 -0
- package/server/engine/mcp/tools/helpers/perception-page.js +215 -0
- package/server/engine/mcp/tools/helpers/reference-download.js +103 -0
- package/server/engine/mcp/tools/helpers/rembg-bridge.py +80 -0
- package/server/engine/mcp/tools/helpers/rembg.js +273 -0
- package/server/engine/mcp/tools/helpers/shot-pipeline.js +280 -0
- package/server/engine/mcp/tools/highlight.js +68 -0
- package/server/engine/mcp/tools/list-pages.js +222 -0
- package/server/engine/mcp/tools/lookup-tags.js +59 -0
- package/server/engine/mcp/tools/navigate-to-page.js +60 -0
- package/server/engine/mcp/tools/organize-board.js +64 -0
- package/server/engine/mcp/tools/paint-still.js +434 -0
- package/server/engine/mcp/tools/pin-to-board.js +130 -0
- package/server/engine/mcp/tools/preview-deck.js +130 -0
- package/server/engine/mcp/tools/profile-scroll.js +305 -0
- package/server/engine/mcp/tools/publish-site.js +102 -0
- package/server/engine/mcp/tools/query-elements.js +195 -0
- package/server/engine/mcp/tools/read-board.js +103 -0
- package/server/engine/mcp/tools/read-document.js +93 -0
- package/server/engine/mcp/tools/read-page.js +264 -0
- package/server/engine/mcp/tools/read-tavern-json.js +143 -0
- package/server/engine/mcp/tools/record-decision.js +140 -0
- package/server/engine/mcp/tools/relate-on-board.js +110 -0
- package/server/engine/mcp/tools/remove-background.js +391 -0
- package/server/engine/mcp/tools/report-issue.js +115 -0
- package/server/engine/mcp/tools/roll-film.js +266 -0
- package/server/engine/mcp/tools/screenshot-docx.js +138 -0
- package/server/engine/mcp/tools/screenshot-url.js +195 -0
- package/server/engine/mcp/tools/screenshot.js +583 -0
- package/server/engine/mcp/tools/tier-gate.js +96 -0
- package/server/engine/mcp/tools/trace-motion.js +215 -0
- package/server/engine/mcp/tools/web-search.js +548 -0
- package/server/engine/motion/inventory.js +349 -0
- package/server/engine/perception/session.js +235 -0
- package/server/engine/plugins/nodesign/.claude-plugin/plugin.json +5 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +192 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/canvas.template.html +667 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/hybrid-grid.md +22 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/image-led-cover.md +25 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/portrait.md +23 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/quote-backdrop.md +21 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/section-divider.md +23 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/text-led.md +21 -0
- package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +162 -0
- package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +407 -0
- package/server/engine/plugins/nodesign/skills/docx-craft//346/226/207/346/241/243.template.json +80 -0
- package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +228 -0
- 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
- package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +268 -0
- package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +323 -0
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/build-lane.md +134 -0
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/cutout-collage.md +61 -0
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/mock-app.md +163 -0
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/page-transitions.md +86 -0
- package/server/engine/runs/active-runs.js +698 -0
- package/server/engine/runs/live-turn.js +252 -0
- package/server/engine/runs/store.js +364 -0
- package/server/engine/runs/turn-relay.js +217 -0
- package/server/engine/runtime/workspace.js +164 -0
- package/server/index.js +235 -0
- package/server/lib/artifact-file-path.js +80 -0
- package/server/lib/artifact-target.js +284 -0
- package/server/lib/asset-refs.js +239 -0
- package/server/lib/async-queue.js +104 -0
- package/server/lib/auto-relations.js +130 -0
- package/server/lib/binding-types.js +60 -0
- package/server/lib/board-kind-sizes.js +62 -0
- package/server/lib/board-relations.js +133 -0
- package/server/lib/browse-proxy.js +180 -0
- package/server/lib/canvas-id.js +36 -0
- package/server/lib/cover.js +227 -0
- package/server/lib/danbooru-tags.js +291 -0
- package/server/lib/doc-extract.js +156 -0
- package/server/lib/docx/build-from-source.js +260 -0
- package/server/lib/docx/build.js +490 -0
- package/server/lib/docx/dump-styles.js +287 -0
- package/server/lib/docx/fonts/nodesign-cjk.conf +164 -0
- package/server/lib/docx/merge-runs.js +129 -0
- package/server/lib/docx/numbering.js +218 -0
- package/server/lib/docx/order.js +152 -0
- package/server/lib/docx/rawzip.js +172 -0
- package/server/lib/docx/render.js +92 -0
- package/server/lib/docx/text-lint.js +225 -0
- package/server/lib/docx/tokens.js +335 -0
- package/server/lib/docx/units.js +29 -0
- package/server/lib/docx/xml.js +291 -0
- package/server/lib/docx-pages.js +187 -0
- package/server/lib/export-collect.js +289 -0
- package/server/lib/export-package.js +210 -0
- package/server/lib/html-srcset.js +145 -0
- package/server/lib/image-variant.js +460 -0
- package/server/lib/ingress/forward-openai-chat.js +305 -0
- package/server/lib/ingress/openai-chat.js +491 -0
- package/server/lib/ingress/session-notice.js +66 -0
- package/server/lib/ingress/session-routes.js +68 -0
- package/server/lib/ingress/slot-probe.js +130 -0
- package/server/lib/ingress/upstream-billing.js +52 -0
- package/server/lib/ingress/upstream-fail-streak.js +72 -0
- package/server/lib/ingress/upstream-truncation.js +48 -0
- package/server/lib/issues-store.js +182 -0
- package/server/lib/kinds/deck.js +72 -0
- package/server/lib/kinds/docx.js +283 -0
- package/server/lib/kinds/file-kinds.js +113 -0
- package/server/lib/kinds/index.js +202 -0
- package/server/lib/kinds/site.js +161 -0
- package/server/lib/model-ingress.js +543 -0
- package/server/lib/moderation.js +255 -0
- package/server/lib/notice-store.js +103 -0
- package/server/lib/plugin-install.js +104 -0
- package/server/lib/plugin-validator.js +721 -0
- package/server/lib/publish-store.js +115 -0
- package/server/lib/quick-summary.js +52 -0
- package/server/lib/quota.js +270 -0
- package/server/lib/rate-window.js +29 -0
- package/server/lib/reference-assets.js +92 -0
- package/server/lib/region-shot.js +135 -0
- package/server/lib/safe-path.js +73 -0
- package/server/lib/sdk-session.js +33 -0
- package/server/lib/showcase-store.js +92 -0
- package/server/lib/site-publish.js +465 -0
- package/server/lib/ssrf-guard.js +432 -0
- package/server/lib/task-scan.js +158 -0
- package/server/lib/tavern-json.js +154 -0
- package/server/lib/video-variant.js +237 -0
- package/server/lib/workspace-path.js +33 -0
- package/server/ops/fix-sdk-musl.mjs +40 -0
- package/server/ops/install-macos-fonts.sh +114 -0
- package/server/ops/macos-fonts.conf +336 -0
- package/server/ops/sandbox-shim/bwrap +57 -0
- package/server/projects/assets-summary.js +119 -0
- package/server/projects/auto-name.js +53 -0
- package/server/projects/board-store.js +640 -0
- package/server/projects/move-entry.js +103 -0
- package/server/projects/store.js +270 -0
- package/server/projects/ui-config.js +54 -0
- package/server/projects/workspace-templates.js +53 -0
- package/server/projects/workspace.js +1025 -0
- package/server/runtime/capabilities.js +153 -0
- package/server/runtime/local-config.js +177 -0
- package/server/runtime/local-env.js +88 -0
- package/server/runtime/platform.js +342 -0
- package/server/runtime/profile.js +72 -0
- package/server/services/rembg-launcher.js +313 -0
- package/server/services/rembg-service.py +334 -0
- package/server/shared/README.md +25 -0
- package/server/shared/deck.js +54 -0
- package/server/shared/time.js +49 -0
- package/server/style-pipeline/README.md +53 -0
- package/server/ws/broker.js +36 -0
- package/server/ws/browse-channel.js +177 -0
- package/server/ws/index.js +386 -0
- package/web/README.md +89 -0
- package/web/dist/assets/BrowserWindow-Dc7J7CYc.js +11 -0
- package/web/dist/assets/DeckWindow-4G0xX7P3.js +16 -0
- package/web/dist/assets/DocxWindow-BXQsoPkh.js +16 -0
- package/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/web/dist/assets/SiteWindow-D6eS-2gM.js +18 -0
- package/web/dist/assets/again-D8tfgKKO.webp +0 -0
- package/web/dist/assets/caveat-nd-CP6HlsNg.woff2 +0 -0
- package/web/dist/assets/clock-xJhvfYpS.webp +0 -0
- package/web/dist/assets/film-cut-BBKMwH7q.webp +0 -0
- package/web/dist/assets/film-still-BZ_DL9Tt.webp +0 -0
- package/web/dist/assets/index-C0KUf6Wd.css +1 -0
- package/web/dist/assets/index-Cbl3ATvp.js +2125 -0
- package/web/dist/assets/ink-desk-BRbxeDsR.webp +0 -0
- package/web/dist/assets/ink-night-Bz4BT3XH.webp +0 -0
- package/web/dist/assets/ink-portrait-C0DM4V8Q.webp +0 -0
- package/web/dist/assets/longcang-regular-C0uvTuKa.woff2 +0 -0
- package/web/dist/assets/lxgw-nd-bold-DbRfiTgo.woff2 +0 -0
- package/web/dist/assets/lxgw-nd-regular-CpMw9Ehg.woff2 +0 -0
- package/web/dist/assets/pending-edit-apply-DTjo2geW.js +31 -0
- package/web/dist/assets/question-8H4HSqxh.webp +0 -0
- package/web/dist/assets/reject-BaPJqElj.webp +0 -0
- package/web/dist/assets/rotate-cw-LMT3x1Zj.js +11 -0
- package/web/dist/assets/rp-portrait-CbrGpew2.webp +0 -0
- package/web/dist/assets/rp-street-C4jVKjxx.webp +0 -0
- package/web/dist/assets/square-dashed-mouse-pointer-CBoAkfhi.js +11 -0
- package/web/dist/assets/tangle-DIxHVNuT.webp +0 -0
- package/web/dist/assets/thisone-BylZKglV.webp +0 -0
- package/web/dist/assets/thumb-DO1wfK1I.webp +0 -0
- package/web/dist/index.html +18 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcp/tools/navigate-to-page.js — navigate_to_page MCP tool
|
|
3
|
+
*
|
|
4
|
+
* 让 agent 主动把前端 canvas 切到第 N 页。典型场景:agent 在分析多页 deck,
|
|
5
|
+
* 一边讲一边切;或者用户问"第 3 页那个图怎么改",agent 先切过去再选元素。
|
|
6
|
+
*
|
|
7
|
+
* 实现:纯 emit run.canvas_navigate 事件 → 已订阅 '*' 的 ws bridge 自动转发,
|
|
8
|
+
* 前端 ProjectWorkspace 收到后调 SlideNavigator setActivePage / iframe 内
|
|
9
|
+
* scrollIntoView 对应 section[data-page="N"]。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { tool } from '@anthropic-ai/claude-agent-sdk';
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {object} deps
|
|
17
|
+
* @param {import('../../agent/context.js').AgentContext} [deps.ctx]
|
|
18
|
+
*/
|
|
19
|
+
export function makeNavigateToPageTool({ ctx }) {
|
|
20
|
+
return tool(
|
|
21
|
+
'navigate_to_page',
|
|
22
|
+
`Switch the canvas in the user's frontend to a specific page (1-based).
|
|
23
|
+
|
|
24
|
+
Use this when:
|
|
25
|
+
- You're explaining/changing page N and want the user to see the same page
|
|
26
|
+
- The user asks about "page 3" — switch to it before discussing
|
|
27
|
+
- After editing a non-current page so the user sees the result
|
|
28
|
+
|
|
29
|
+
The frontend will scroll to <section data-page="N"> and update the page nav.`,
|
|
30
|
+
{
|
|
31
|
+
index: z
|
|
32
|
+
.number()
|
|
33
|
+
.int()
|
|
34
|
+
.min(1)
|
|
35
|
+
.describe('Target page number (1-based, matches data-page="N")'),
|
|
36
|
+
},
|
|
37
|
+
async ({ index }) => {
|
|
38
|
+
try {
|
|
39
|
+
ctx?.emit?.({
|
|
40
|
+
type: 'run.canvas_navigate',
|
|
41
|
+
page: index,
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
content: [{
|
|
45
|
+
type: 'text',
|
|
46
|
+
text: `Navigated frontend canvas to page ${index}.`,
|
|
47
|
+
}],
|
|
48
|
+
};
|
|
49
|
+
} catch (err) {
|
|
50
|
+
return {
|
|
51
|
+
content: [{
|
|
52
|
+
type: 'text',
|
|
53
|
+
text: `navigate_to_page failed: ${err?.message || String(err)}`,
|
|
54
|
+
}],
|
|
55
|
+
isError: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcp/tools/organize-board.js —— organize_board(2026-08-14,用户提议)
|
|
3
|
+
*
|
|
4
|
+
* 画布语言的收纳动词:把散在桌面上的产物(生成图 / 文件 / 文件夹)归进
|
|
5
|
+
* 文件夹。在这之前 agent 只能裸 Bash mv —— 能用,但它不知道搬家的画布语义
|
|
6
|
+
* (id=路径,搬=换身份,关系线端点要跟着走),裸 mv 只靠每轮 commit 对账
|
|
7
|
+
* 兜底,窗口期里剪枝器还可能把正在改名的东西连坐剪掉。
|
|
8
|
+
*
|
|
9
|
+
* 实现 = **和用户拖拽「移动到…」同一份核心**(projects/move-entry.js):
|
|
10
|
+
* 磁盘先行、画布身份同步、转发表记账,一个字不重写。
|
|
11
|
+
*
|
|
12
|
+
* 批量制(同 roll_film / paint_still):≤16 件、串行、**中途失败即停**
|
|
13
|
+
* (后面的不动,报告哪件停的)。目标夹不存在就建 —— 归纳常配新夹。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { tool } from '@anthropic-ai/claude-agent-sdk';
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
import { moveEntry, MoveError } from '../../../projects/move-entry.js';
|
|
19
|
+
import { Events } from '../../agent/events.js';
|
|
20
|
+
|
|
21
|
+
export function makeOrganizeBoardTool({ projectId, ctx }) {
|
|
22
|
+
return tool(
|
|
23
|
+
'organize_board',
|
|
24
|
+
`Tidy the workbench canvas: move artifacts (generated images, files, folders) into a folder. Same semantics as the user dragging a card into a folder — the file really moves on disk, and its canvas identity (position, relation lines) follows automatically.
|
|
25
|
+
|
|
26
|
+
Use for: grouping generated images into a folder, collecting a site's materials into <site>/assets/, un-cluttering the desktop root. Sticky notes (notes/*.md) may move too, but they become plain .md file cards outside notes/ — lose the flippable sticky form.
|
|
27
|
+
Not for: site roots as destination (they are artifacts, not storage — a site takes materials in its assets/ subfolder).
|
|
28
|
+
|
|
29
|
+
Batch: up to 16 items, moved in order, stops at first failure.`,
|
|
30
|
+
{
|
|
31
|
+
items: z.array(z.string().min(1)).min(1).max(16)
|
|
32
|
+
.describe('Workspace-relative paths to move (files or folders), e.g. ["assets/generated/a.png", "旧稿.html"]'),
|
|
33
|
+
into: z.string()
|
|
34
|
+
.describe('Destination folder (workspace-relative), e.g. "素材" or "观察日志/assets". Created if missing. "" = workspace root (un-nest).'),
|
|
35
|
+
},
|
|
36
|
+
async ({ items, into }) => {
|
|
37
|
+
const lines = [];
|
|
38
|
+
let moved = 0;
|
|
39
|
+
for (const item of items) {
|
|
40
|
+
try {
|
|
41
|
+
const out = await moveEntry(projectId, item, into, { createFolder: true });
|
|
42
|
+
moved += 1;
|
|
43
|
+
lines.push(out.moved ? `✓ ${out.from} → ${out.to}` : `· ${out.from}(已在原地)`);
|
|
44
|
+
if (out.moved) {
|
|
45
|
+
try {
|
|
46
|
+
// 补一发 file_changed(MCP 写盘不走 PostToolUse 直发):前端产物
|
|
47
|
+
// 清单重拉 + 在场精灵的挂账路径补射都吃这个
|
|
48
|
+
ctx?.emit?.(Events.fileChanged(out.to, 'rename')); // 工作区相对路径=正字法
|
|
49
|
+
} catch { /* fail-soft */ }
|
|
50
|
+
}
|
|
51
|
+
} catch (err) {
|
|
52
|
+
const why = err instanceof MoveError ? err.message : (err?.message || String(err));
|
|
53
|
+
lines.push(`✗ ${item}:${why}`);
|
|
54
|
+
lines.push(`(后面 ${items.length - moved - 1} 件没动 —— 修正后重调)`);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
if (moved > 0) ctx?.emit?.({ type: 'board.updated', sessionId: null, summary: `归纳了 ${moved} 件到 ${into || '桌面根'}` });
|
|
60
|
+
} catch { /* fail-soft */ }
|
|
61
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcp/tools/paint-still.js — paint_still MCP tool(2026-08-08;批量制+FLUX.2 同晚补上)
|
|
3
|
+
*
|
|
4
|
+
* 站主本地 GPU 盒子生图。一次调用 = 一批(1-16 条,串行渲、出一张上墙一张);
|
|
5
|
+
* 每条还可以再开 batch(同提示词多变体,一次采样出 N 张,抽卡用,比串行快数倍)。
|
|
6
|
+
*
|
|
7
|
+
* 模型档(08-11 扩到五档):
|
|
8
|
+
* noobai NoobAI-XL V-Pred 1.0,danbooru/e621 标签,解剖与标签理解最强
|
|
9
|
+
* noobai-eps NoobAI-XL 1.1 eps,只在 LoRA 仅有 eps 版时用(LoRA 跨预测目标会发灰)
|
|
10
|
+
* pony Pony Diffusion V6 XL,score_9 六段串体系,clip skip 2 已在盒端配好
|
|
11
|
+
* anima 自然语言英文
|
|
12
|
+
* krea2 Krea 2 Turbo 12B 审美向,自然语言
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ 许可:noobai/noobai-eps **禁止任何形式商业化,含生成物**(Laxhar Lab 在 FAIPL
|
|
15
|
+
* 之上自加条款);pony **禁止在任何货币化的站点/应用上跑推理**;anima 非商用。
|
|
16
|
+
* 三者都只适合站主自用,不能做成对外服务。要商用走 Illustrious-XL(RAIL++-M
|
|
17
|
+
* 明文允许 SaaS)或 krea2。08-11 前这里写着 noobai"商用可",是错的。
|
|
18
|
+
*
|
|
19
|
+
* 配方 = 08-08/08-11 实测 + 官方模板。盒子不在线就明说,不静默降级。
|
|
20
|
+
* 落盘/缩略图/事件照 generate-image.js;只回文本路径(图不进返回值),
|
|
21
|
+
* 但 agent 可以自己去看那些文件挑废图(2026-08-18 解禁),审美判断仍归用户。
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import os from 'node:os';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import fs from 'node:fs/promises';
|
|
27
|
+
import { tool } from '@anthropic-ai/claude-agent-sdk';
|
|
28
|
+
import { z } from 'zod';
|
|
29
|
+
import sharp from 'sharp';
|
|
30
|
+
import { Events } from '../../agent/events.js';
|
|
31
|
+
import { getProject } from '../../../projects/store.js';
|
|
32
|
+
import { getUserById } from '../../../auth/users-store.js';
|
|
33
|
+
import { can, localGenApproved, DENIAL } from '../../../auth/tier.js';
|
|
34
|
+
import {
|
|
35
|
+
THUMBNAIL_MAX_DIM, THUMBNAIL_QUALITY, enqueueWarm, warmSpecsFor,
|
|
36
|
+
} from '../../../lib/image-variant.js';
|
|
37
|
+
import { boxConfig, shq, runBox, sshArgs, scpArgs, localBoxEnabled, BOX_OFF_MSG } from './h3box-ssh.js';
|
|
38
|
+
|
|
39
|
+
const SSH_TIMEOUT_MS = Number(process.env.NODESIGN_H3BOX_TIMEOUT_MS) || 240_000;
|
|
40
|
+
// krea2 bf16 24G 全驻卡;换模型后的首张要付一次装载(~1 分钟),给足余量
|
|
41
|
+
const TIMEOUT_BY_MODEL = {
|
|
42
|
+
noobai: SSH_TIMEOUT_MS, 'noobai-eps': SSH_TIMEOUT_MS, pony: SSH_TIMEOUT_MS,
|
|
43
|
+
anima: SSH_TIMEOUT_MS, krea2: 400_000,
|
|
44
|
+
};
|
|
45
|
+
// batch 会线性拉长单次渲染,超时按张数放大(封顶 10 分钟,别让挂死的活撑满)
|
|
46
|
+
const timeoutFor = (still) => Math.min(
|
|
47
|
+
(TIMEOUT_BY_MODEL[still.model] || SSH_TIMEOUT_MS) * Math.max(1, (still.batch || 1) * 0.6),
|
|
48
|
+
600_000,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
async function makeThumbnail(rawBuf) {
|
|
52
|
+
try {
|
|
53
|
+
const meta = await sharp(rawBuf).metadata();
|
|
54
|
+
const w = meta.width || 0; const h = meta.height || 0;
|
|
55
|
+
let pipeline = sharp(rawBuf);
|
|
56
|
+
if (Math.max(w, h) > THUMBNAIL_MAX_DIM) {
|
|
57
|
+
pipeline = pipeline.resize({
|
|
58
|
+
width: w >= h ? THUMBNAIL_MAX_DIM : null,
|
|
59
|
+
height: h > w ? THUMBNAIL_MAX_DIM : null,
|
|
60
|
+
fit: 'inside', withoutEnlargement: true,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const buf = await pipeline.webp({ quality: THUMBNAIL_QUALITY }).toBuffer();
|
|
64
|
+
return { buf, mimeType: 'image/webp' };
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.warn(`[paint-still] thumbnail failed (${err.message})`);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 把工作区里的参考图推到盒子,返回盒子上的绝对路径。
|
|
73
|
+
* 失败返回 {err},调用方据此中止这条 still —— 参考图丢了还照跑等于白烧。
|
|
74
|
+
*/
|
|
75
|
+
/**
|
|
76
|
+
* 参考图推盒前先缩:上行实测只有 ~0.2MB/s(08-11),2MB 原图光上传就 10 秒。
|
|
77
|
+
* IP-Adapter 的 CLIP 端最终只吃 224px,ref 缩到 512 零损失;init/control 参与
|
|
78
|
+
* 生成分辨率,封顶 1344(SDXL 原生上限)。统一出 PNG 保透明;缩完反而更大
|
|
79
|
+
* (罕见,如高压缩 JPEG 转 PNG)就推原图。缩图失败也推原图,别因优化挂正事。
|
|
80
|
+
*/
|
|
81
|
+
async function shrinkForPush(abs, slot) {
|
|
82
|
+
const maxDim = slot.startsWith('ref') ? 512 : 1344;
|
|
83
|
+
try {
|
|
84
|
+
const meta = await sharp(abs).metadata();
|
|
85
|
+
if (!meta.width || !meta.height || Math.max(meta.width, meta.height) <= maxDim) return null;
|
|
86
|
+
const buf = await sharp(abs)
|
|
87
|
+
.resize({ width: maxDim, height: maxDim, fit: 'inside', withoutEnlargement: true })
|
|
88
|
+
.png().toBuffer();
|
|
89
|
+
if (buf.length >= (await fs.stat(abs)).size) return null;
|
|
90
|
+
return buf;
|
|
91
|
+
} catch { return null; }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function pushRef({ box, root, relPath, jobId, slot, signal }) {
|
|
95
|
+
const abs = path.resolve(root, relPath);
|
|
96
|
+
// 越狱防护:参考图必须在工作区内
|
|
97
|
+
if (!abs.startsWith(path.resolve(root))) return { err: `${slot} 路径越出工作区:${relPath}` };
|
|
98
|
+
try { await fs.access(abs); } catch { return { err: `${slot} 找不到文件:${relPath}` }; }
|
|
99
|
+
const shrunk = await shrinkForPush(abs, slot);
|
|
100
|
+
const remote = `~/refs/${jobId}-${slot}${shrunk ? '.png' : (path.extname(abs) || '.png')}`;
|
|
101
|
+
const mk = await runBox(box, 'ssh', [...sshArgs(box), 'mkdir -p ~/refs'], { timeoutMs: 30_000, signal });
|
|
102
|
+
if (mk.code !== 0) return { err: `盒子建 refs 目录失败:${(mk.err || '').slice(-200)}` };
|
|
103
|
+
let src = abs; let tmp = null;
|
|
104
|
+
if (shrunk) {
|
|
105
|
+
tmp = path.join(os.tmpdir(), `h3ref-${jobId}-${slot}.png`);
|
|
106
|
+
await fs.writeFile(tmp, shrunk);
|
|
107
|
+
src = tmp;
|
|
108
|
+
}
|
|
109
|
+
const put = await runBox(box, 'scp',
|
|
110
|
+
[...scpArgs(box), src, `${box.target}:${remote}`], { timeoutMs: 120_000, signal });
|
|
111
|
+
if (tmp) fs.unlink(tmp).catch(() => { /* */ });
|
|
112
|
+
if (put.code !== 0) return { err: `${slot} 上传失败:${(put.err || '').slice(-200)}` };
|
|
113
|
+
return { remote };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** 单张落盘 + 缩略图 + sidecar + 上墙事件;返回 caption 行 */
|
|
117
|
+
async function landStill({ imgBuf, still, seed, outDir, ctx, sessionId, wallS, idx = 0, total = 1 }) {
|
|
118
|
+
// batch>1 时同一条 still 出 N 张,文件名加序号;单张时不加,保持老路径形态
|
|
119
|
+
const finalName = `still-${still.jobId}-${still.name}${total > 1 ? `-${idx + 1}` : ''}`;
|
|
120
|
+
const fileName = `${finalName}.png`;
|
|
121
|
+
const absOut = path.join(outDir, fileName);
|
|
122
|
+
await fs.writeFile(absOut, imgBuf);
|
|
123
|
+
|
|
124
|
+
const thumbDir = path.join(outDir, '.thumbnails');
|
|
125
|
+
await fs.mkdir(thumbDir, { recursive: true });
|
|
126
|
+
const thumb = await makeThumbnail(imgBuf);
|
|
127
|
+
let thumbAgentRelPath = null;
|
|
128
|
+
if (thumb) {
|
|
129
|
+
await fs.writeFile(path.join(thumbDir, `${finalName}.thumb.webp`), thumb.buf);
|
|
130
|
+
thumbAgentRelPath = path.posix.join('assets', 'generated', '.thumbnails', `${finalName}.thumb.webp`);
|
|
131
|
+
}
|
|
132
|
+
fs.stat(absOut)
|
|
133
|
+
.then((st) => enqueueWarm(absOut, st, warmSpecsFor()))
|
|
134
|
+
.catch(() => { /* 预热失败下次请求现编 */ });
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const metaDir = path.join(outDir, '.meta');
|
|
138
|
+
await fs.mkdir(metaDir, { recursive: true });
|
|
139
|
+
await fs.writeFile(path.join(metaDir, `${finalName}.json`), JSON.stringify({
|
|
140
|
+
prompt: still.prompt, negative: still.negative || null, provider: 'h3box',
|
|
141
|
+
model: still.model, seed, size: still.size,
|
|
142
|
+
sessionId: ctx?.sessionId || sessionId || null,
|
|
143
|
+
runId: ctx?.runId || null,
|
|
144
|
+
ts: new Date().toISOString(),
|
|
145
|
+
}, null, 2));
|
|
146
|
+
} catch (e) { console.warn(`[paint-still] meta sidecar failed: ${e.message}`); }
|
|
147
|
+
|
|
148
|
+
// 发**工作区相对路径**(fileChanged 的正字法,hooks 同款)——发绝对路径的话
|
|
149
|
+
// 前端寻址/版本记账全部静默失配(2026-08-14 普查改)
|
|
150
|
+
try { ctx?.emit?.(Events.fileChanged(path.posix.join('assets', 'generated', finalName), 'add')); } catch { /* fail-safe */ }
|
|
151
|
+
try {
|
|
152
|
+
ctx?.emit?.({
|
|
153
|
+
type: 'run.image_generated',
|
|
154
|
+
path: path.posix.join('assets', 'generated', fileName),
|
|
155
|
+
thumbnailPath: thumbAgentRelPath,
|
|
156
|
+
absPath: absOut,
|
|
157
|
+
sizeBytes: imgBuf.length,
|
|
158
|
+
thumbnailSizeBytes: thumb?.buf.length || null,
|
|
159
|
+
prompt: still.prompt, assetRole: null, aspectRatio: null, imageSize: still.size,
|
|
160
|
+
model: `h3box-${still.model}`,
|
|
161
|
+
referenceImageCount: (still.ref_image ? String(still.ref_image).split(',').filter((s) => s.trim()).length : 0)
|
|
162
|
+
+ (still.init_image ? 1 : 0) + (still.control_image ? 1 : 0),
|
|
163
|
+
accompanyText: null,
|
|
164
|
+
});
|
|
165
|
+
} catch { /* fail-safe */ }
|
|
166
|
+
|
|
167
|
+
const tag = total > 1 ? `${still.name} #${idx + 1}/${total}` : still.name;
|
|
168
|
+
return `[${tag}] assets/generated/${fileName} — ${still.model} ${still.size} seed=${seed}${total > 1 ? `+${idx}` : ''} ${(imgBuf.length / 1024).toFixed(0)}KB ${wallS}s`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** 核心流程(从 handler 拆出,便于不起 SDK 直接实弹测试) */
|
|
172
|
+
export async function paintStills(
|
|
173
|
+
{ workspaceRoot, sharedRoot, projectId, sessionId, ctx },
|
|
174
|
+
{ stills },
|
|
175
|
+
) {
|
|
176
|
+
const asText = (text, isError = false) =>
|
|
177
|
+
({ content: [{ type: 'text', text }], ...(isError ? { isError: true } : {}) });
|
|
178
|
+
try {
|
|
179
|
+
if (!localBoxEnabled()) {
|
|
180
|
+
return asText(BOX_OFF_MSG, true);
|
|
181
|
+
}
|
|
182
|
+
const box = boxConfig();
|
|
183
|
+
if (!box) {
|
|
184
|
+
return asText('本地生图盒子未配置(站主没开机或没设 NODESIGN_H3BOX_SSH)。转告用户,改用 generate_image。', true);
|
|
185
|
+
}
|
|
186
|
+
const project = getProject(projectId);
|
|
187
|
+
if (!project) return asText('错误:项目不存在', true);
|
|
188
|
+
const owner = project.ownerId ? getUserById(project.ownerId) : null;
|
|
189
|
+
// 档位闸 + 逐人批准(auth/tier.js):basic 档不开任何生图;pro 档还要被站主批过本地产线
|
|
190
|
+
if (!can(owner, 'localGen')) return asText(DENIAL.localGenTier, true);
|
|
191
|
+
if (!localGenApproved(owner)) return asText(`${DENIAL.localGenApproval} 改用 generate_image。`, true);
|
|
192
|
+
|
|
193
|
+
const outDir = path.join(sharedRoot || workspaceRoot, 'assets', 'generated');
|
|
194
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
195
|
+
const signal = ctx?.abortController?.signal;
|
|
196
|
+
const batch = Date.now().toString(36);
|
|
197
|
+
const lines = []; const failed = [];
|
|
198
|
+
|
|
199
|
+
for (let i = 0; i < stills.length; i++) {
|
|
200
|
+
const still = stills[i];
|
|
201
|
+
still.jobId = `${batch}p${i}`;
|
|
202
|
+
const seed = still.seed ?? ((Date.now() + i * 7919) % 1_000_000);
|
|
203
|
+
const nBatch = Math.max(1, Math.min(8, still.batch || 1));
|
|
204
|
+
|
|
205
|
+
// 参考图先推上盒子(h3box.py 的 LoadImage 只认盒子本地路径)
|
|
206
|
+
const refRoot = sharedRoot || workspaceRoot;
|
|
207
|
+
const refArgs = []; let refErr = null;
|
|
208
|
+
for (const [slot, rel] of [
|
|
209
|
+
['init', still.init_image], ['control', still.control_image],
|
|
210
|
+
]) {
|
|
211
|
+
if (!rel) continue;
|
|
212
|
+
const r = await pushRef({ box, root: refRoot, relPath: rel, jobId: still.jobId, slot, signal });
|
|
213
|
+
if (r.err) { refErr = r.err; break; }
|
|
214
|
+
if (slot === 'init') {
|
|
215
|
+
refArgs.push(`--init ${shq(r.remote)}`);
|
|
216
|
+
if (still.denoise != null) refArgs.push(`--denoise ${still.denoise}`);
|
|
217
|
+
} else {
|
|
218
|
+
refArgs.push(`--control ${shq(r.remote)}`,
|
|
219
|
+
`--control-type ${shq(still.control_type || 'openpose')}`,
|
|
220
|
+
`--control-strength ${still.control_strength ?? 0.7}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// ref_image 可以是多张(逗号分隔,最多 5 张)—— 同一角色的不同视角一起喂,
|
|
224
|
+
// 一致性明显强于单张。每张各自推上盒子,再按同样顺序拼回去。
|
|
225
|
+
if (!refErr && still.ref_image) {
|
|
226
|
+
const rels = String(still.ref_image).split(',').map((s) => s.trim()).filter(Boolean);
|
|
227
|
+
if (rels.length > 5) {
|
|
228
|
+
refErr = `ref_image 最多 5 张,给了 ${rels.length}`;
|
|
229
|
+
} else {
|
|
230
|
+
const remotes = [];
|
|
231
|
+
for (let k = 0; k < rels.length; k++) {
|
|
232
|
+
const r = await pushRef({
|
|
233
|
+
box, root: refRoot, relPath: rels[k], jobId: still.jobId, slot: `ref${k}`, signal,
|
|
234
|
+
});
|
|
235
|
+
if (r.err) { refErr = r.err; break; }
|
|
236
|
+
remotes.push(r.remote);
|
|
237
|
+
}
|
|
238
|
+
if (!refErr) {
|
|
239
|
+
refArgs.push(`--ref ${shq(remotes.join(','))}`,
|
|
240
|
+
`--ref-weight ${shq(String(still.ref_weight ?? '0.8'))}`);
|
|
241
|
+
if (still.ref_mode) refArgs.push(`--ref-mode ${shq(still.ref_mode)}`);
|
|
242
|
+
if (still.ref_combine) refArgs.push(`--ref-combine ${shq(still.ref_combine)}`);
|
|
243
|
+
if (still.ref_preset) refArgs.push(`--ref-preset ${shq(still.ref_preset)}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (refErr) { failed.push(`[${still.name}] ${refErr}`); break; }
|
|
248
|
+
|
|
249
|
+
const remoteCmd = [
|
|
250
|
+
'python3 ~/h3box.py image',
|
|
251
|
+
`-p ${shq(still.prompt)}`,
|
|
252
|
+
`--model ${still.model}`,
|
|
253
|
+
`--seed ${seed}`,
|
|
254
|
+
`--size ${still.size}`,
|
|
255
|
+
`--name ${still.jobId}`,
|
|
256
|
+
nBatch > 1 ? `--batch ${nBatch}` : '',
|
|
257
|
+
still.negative ? `--neg ${shq(still.negative)}` : '',
|
|
258
|
+
still.lora ? `--lora ${shq(still.lora)} --lora-strength ${shq(String(still.lora_strength ?? '0.8'))}` : '',
|
|
259
|
+
...refArgs,
|
|
260
|
+
].filter(Boolean).join(' ');
|
|
261
|
+
|
|
262
|
+
const t0 = Date.now();
|
|
263
|
+
const gen = await runBox(box, 'ssh', [...sshArgs(box), remoteCmd],
|
|
264
|
+
{ timeoutMs: timeoutFor({ ...still, batch: nBatch }), signal });
|
|
265
|
+
let failMsg = null; const bufs = [];
|
|
266
|
+
if (gen.code !== 0) {
|
|
267
|
+
failMsg = gen.code === 255 ? `盒子连不上(没开机/地址过期):${(gen.err || '').slice(-300)}`
|
|
268
|
+
: `生成失败 exit ${gen.code}:${(gen.err || gen.out).slice(-500)}`;
|
|
269
|
+
} else {
|
|
270
|
+
const remotePaths = gen.out.split('\n').map((l) => l.trim())
|
|
271
|
+
.filter((l) => l.includes('/outputs/') && /\.(png|webp|jpg)$/.test(l));
|
|
272
|
+
if (!remotePaths.length) failMsg = `盒子跑完但没报出文件路径:${gen.out.slice(-400)}`;
|
|
273
|
+
// batch 的 N 张全取回来,别只拿第一张(08-11 前就是丢了后面全部)。
|
|
274
|
+
// 多源拼进一次 scp —— 逐张各开连接的老写法,握手开销能跟生成时间打平(08-11 实测)
|
|
275
|
+
if (!failMsg && remotePaths.length) {
|
|
276
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), `h3pull-${still.jobId}-`));
|
|
277
|
+
const pull = await runBox(box, 'scp',
|
|
278
|
+
[...scpArgs(box), ...remotePaths.map((r) => `${box.target}:${r}`), tmpDir],
|
|
279
|
+
{ timeoutMs: 60_000 + 20_000 * remotePaths.length });
|
|
280
|
+
if (pull.code !== 0) failMsg = `取图失败:${(pull.err || '').slice(-300)}`;
|
|
281
|
+
else {
|
|
282
|
+
try {
|
|
283
|
+
for (const r of remotePaths) bufs.push(await fs.readFile(path.join(tmpDir, path.basename(r))));
|
|
284
|
+
} catch (e) { failMsg = `取图落盘缺文件:${e.message}`; }
|
|
285
|
+
}
|
|
286
|
+
await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => { /* */ });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const wallS = Math.round((Date.now() - t0) / 1000);
|
|
290
|
+
for (let k = 0; k < bufs.length; k++) {
|
|
291
|
+
lines.push(await landStill({
|
|
292
|
+
imgBuf: bufs[k], still, seed: seed + k, outDir, ctx, sessionId, wallS,
|
|
293
|
+
idx: k, total: bufs.length,
|
|
294
|
+
}));
|
|
295
|
+
}
|
|
296
|
+
if (!bufs.length) {
|
|
297
|
+
failed.push(`[${still.name}] ${failMsg}`);
|
|
298
|
+
break; // 串行批中途失败即停:后张大概率同因,别空烧
|
|
299
|
+
}
|
|
300
|
+
if (failMsg) failed.push(`[${still.name}] 部分失败:${failMsg}`);
|
|
301
|
+
if (signal?.aborted) break;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ── 标签体检(2026-08-18)──
|
|
305
|
+
// danbooru 系模型的标签写错不报错、只静默失效,一个 agent 因此白烧过至少
|
|
306
|
+
// 五轮出图。fail-open:查不到就什么都不说。位置有讲究 —— 放在"看图挑废图"
|
|
307
|
+
// 那句**之前**,因为它说的是"这批图可能根本不该按它判断方向"。
|
|
308
|
+
let tagNote = null;
|
|
309
|
+
try {
|
|
310
|
+
const danbooruStills = stills.filter(s => ['noobai', 'noobai-eps', 'pony'].includes(s.model ?? 'noobai'));
|
|
311
|
+
if (danbooruStills.length) {
|
|
312
|
+
const { lintTags, formatTagLint } = await import('../../../lib/danbooru-tags.js');
|
|
313
|
+
tagNote = formatTagLint(await lintTags(
|
|
314
|
+
danbooruStills.flatMap(s => [s.prompt, s.negative].filter(Boolean)),
|
|
315
|
+
));
|
|
316
|
+
}
|
|
317
|
+
} catch { /* 体检本身不能变成新的故障源 */ }
|
|
318
|
+
|
|
319
|
+
const head = `Batch done ${lines.length}/${stills.length} stills`;
|
|
320
|
+
// ("产物可以看、只挑技术性废图"08-21 起只写在工具描述和 prelude 里,不再每批返回都带)
|
|
321
|
+
if (failed.length) {
|
|
322
|
+
return asText([head, ...lines, 'FAILED(批在此中断):', ...failed, tagNote]
|
|
323
|
+
.filter(Boolean).join('\n'), lines.length === 0);
|
|
324
|
+
}
|
|
325
|
+
return asText([head, ...lines, tagNote].filter(Boolean).join('\n'));
|
|
326
|
+
} catch (err) {
|
|
327
|
+
return asText(`paint_still 失败:${err.message}`, true);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* @param {object} deps
|
|
333
|
+
*/
|
|
334
|
+
export function makePaintStillTool(deps) {
|
|
335
|
+
return tool(
|
|
336
|
+
'paint_still',
|
|
337
|
+
(localBoxEnabled()
|
|
338
|
+
? ''
|
|
339
|
+
: '⛔ CURRENTLY UNAVAILABLE — the owner powers this GPU box on and off by hand, '
|
|
340
|
+
+ 'and it is off right now. Do not call this tool. Use generate_image instead, '
|
|
341
|
+
+ 'and tell the user the local box is off if they asked for it specifically.\n\n')
|
|
342
|
+
+ `Generate anime/illustration images on the owner's local GPU box. One call =
|
|
343
|
+
1-16 stills rendered serially; each finished image lands on the canvas
|
|
344
|
+
immediately. Each still can also set batch=N to get N variations of the SAME
|
|
345
|
+
prompt from one sampling pass — far cheaper than N separate stills, and it is
|
|
346
|
+
the right way to "roll" for a good result.
|
|
347
|
+
|
|
348
|
+
Models per still:
|
|
349
|
+
- "noobai" (default): NoobAI-XL V-Pred 1.0. Danbooru/e621 tags,
|
|
350
|
+
comma-separated. Best anatomy and tag control for anime. ~25-40s.
|
|
351
|
+
- "noobai-eps": NoobAI-XL 1.1 (epsilon). Same tag language. Use ONLY when a
|
|
352
|
+
LoRA you need exists solely in an eps build — eps LoRAs on the v-pred model
|
|
353
|
+
wash out or oversaturate.
|
|
354
|
+
- "pony": Pony Diffusion V6 XL. Quality prefix is added box-side; write plain
|
|
355
|
+
danbooru-ish tags. Its LoRA ecosystem is separate from NoobAI's and the two
|
|
356
|
+
do NOT interchange. ~25-40s.
|
|
357
|
+
- "anima": natural-language English. ~20-40s.
|
|
358
|
+
- "krea2": Krea 2 Turbo 12B — natural-language English, aesthetic-first, good
|
|
359
|
+
photoreal/editorial. 8-step, seconds per image once warm (first call after
|
|
360
|
+
another model ~1 min to load 24GB). The negative field is a NO-OP here.
|
|
361
|
+
|
|
362
|
+
REFERENCE IMAGES (SDXL models: noobai / noobai-eps / pony). Three independent
|
|
363
|
+
channels, stackable, all taking workspace-relative paths:
|
|
364
|
+
- ref_image -> IP-Adapter. Carries the CHARACTER'S LOOK across into a new
|
|
365
|
+
picture. This is the right tool for "draw this character somewhere else".
|
|
366
|
+
- control_image -> ControlNet. Locks pose/structure; prompt drives the rest.
|
|
367
|
+
- init_image -> img2img. Repaints on top of the given picture (denoise 0.3 =
|
|
368
|
+
touch-up, 0.6 default, 0.8 = loose reinterpretation).
|
|
369
|
+
Combine them: ref_image for who it is + control_image for the pose + prompt
|
|
370
|
+
for the scene is the strongest setup for keeping a character consistent.
|
|
371
|
+
|
|
372
|
+
Quality prefixes and per-model sampler settings are applied box-side — do not
|
|
373
|
+
repeat them in the prompt. LoRA trigger words DO have to be written into the
|
|
374
|
+
prompt yourself; the cookbook lists them.
|
|
375
|
+
|
|
376
|
+
TAG DISCIPLINE (noobai / noobai-eps / pony): pure comma-separated tags, no
|
|
377
|
+
sentences, spaces not underscores. Before the FIRST still of a new subject or
|
|
378
|
+
scene: understand what the user wants → write candidate tags → run
|
|
379
|
+
lookup_tags ONCE on all of them → paint with the verified ones. Re-rolls of
|
|
380
|
+
the same scene need no new lookup. The return of this tool carries a tag
|
|
381
|
+
check-up (weak/missing/sentence-like fragments) — fix those before judging
|
|
382
|
+
the batch.
|
|
383
|
+
|
|
384
|
+
Use for anime needs and video keyframes (1344x768 matches the video lane).
|
|
385
|
+
Requires the box online — if unreachable, tell the user and fall back to
|
|
386
|
+
generate_image.
|
|
387
|
+
|
|
388
|
+
Outputs land at assets/generated/still-*.png. You MAY look at them to catch
|
|
389
|
+
technical write-offs — duplicated figures, broken limbs, colour cast, all
|
|
390
|
+
black/white, stray watermark text — and just re-roll those yourself. Taste and
|
|
391
|
+
style direction stay the user's call: do not re-roll because you dislike it, and
|
|
392
|
+
do not tell the user which one is better. Default detail is enough to spot
|
|
393
|
+
breakage; do not burn high-detail on every frame.`,
|
|
394
|
+
{
|
|
395
|
+
stills: z.array(z.object({
|
|
396
|
+
prompt: z.string().describe('danbooru/e621 tags (noobai/noobai-eps/pony) or natural English (anima/krea2)'),
|
|
397
|
+
model: z.enum(['noobai', 'noobai-eps', 'pony', 'anima', 'krea2']).default('noobai'),
|
|
398
|
+
negative: z.string().optional().describe('overrides the per-model default; no-op for krea2'),
|
|
399
|
+
size: z.string().regex(/^\d{3,4}x\d{3,4}$/).default('1344x768'),
|
|
400
|
+
seed: z.number().int().optional().describe('omit for fresh random per still; batch uses seed, seed+1, ...'),
|
|
401
|
+
name: z.string().regex(/^[\w-]{1,40}$/).default('still'),
|
|
402
|
+
batch: z.number().int().min(1).max(8).optional()
|
|
403
|
+
.describe('variations of this same prompt in one pass (default 1). Use 4-8 to roll for a keeper.'),
|
|
404
|
+
lora: z.string().optional()
|
|
405
|
+
.describe('LoRA filename(s) in the box loras/ dir, comma-separated for stacking. Only names from the cookbook or given by the user.'),
|
|
406
|
+
lora_strength: z.string().optional()
|
|
407
|
+
.describe('single value applied to all, or comma-separated per LoRA. Default "0.8". Slider-type LoRAs want 2-4 and accept negatives — do not assume 0-1.'),
|
|
408
|
+
// ---- 参考图三路,可叠加。路径都是工作区相对路径 ----
|
|
409
|
+
init_image: z.string().optional()
|
|
410
|
+
.describe('img2img base, workspace-relative path. Redraws ON TOP of this image. SDXL models only.'),
|
|
411
|
+
denoise: z.number().min(0.1).max(1).optional()
|
|
412
|
+
.describe('with init_image only. Default 0.6. Lower = closer to the original (0.3 = light touch-up, 0.8 = loose reinterpretation).'),
|
|
413
|
+
control_image: z.string().optional()
|
|
414
|
+
.describe('ControlNet reference, workspace-relative. Locks POSE/STRUCTURE while the prompt decides everything else.'),
|
|
415
|
+
control_type: z.enum(['openpose', 'depth', 'canny', 'lineart', 'scribble', 'none'])
|
|
416
|
+
.optional().describe('what to extract from control_image. Default openpose. "none" = image is already a processed control map.'),
|
|
417
|
+
control_strength: z.number().min(0).max(2).optional().describe('default 0.7'),
|
|
418
|
+
ref_image: z.string().optional()
|
|
419
|
+
.describe('IP-Adapter reference(s), workspace-relative, comma-separated for UP TO 5. Transfers the CHARACTER LOOK into a new picture — the one for "draw my character somewhere else". Feeding 2-4 shots of the same character from different angles is markedly more consistent than one.'),
|
|
420
|
+
ref_weight: z.string().optional()
|
|
421
|
+
.describe('single value for all, or comma-separated per reference. Default "0.8". Higher = closer to the reference, less obedient to the prompt.'),
|
|
422
|
+
ref_mode: z.enum(['style and composition', 'style transfer', 'composition',
|
|
423
|
+
'strong style transfer', 'style transfer precise', 'composition precise']).optional()
|
|
424
|
+
.describe('what to carry over. Default "style and composition". Use "style transfer" to take the look but NOT the layout — usually what you want when moving a character to a new scene.'),
|
|
425
|
+
ref_combine: z.enum(['concat', 'add', 'subtract', 'average', 'norm average', 'max', 'min'])
|
|
426
|
+
.optional().describe('how multiple references merge. Default concat. "average" is calmer when the refs disagree.'),
|
|
427
|
+
ref_preset: z.enum(['PLUS (high strength)', 'PLUS FACE (portraits)',
|
|
428
|
+
'STANDARD (medium strength)', 'VIT-G (medium strength)']).optional()
|
|
429
|
+
.describe('IP-Adapter weight set. Default PLUS. Switch to "PLUS FACE (portraits)" when the point is keeping a FACE consistent.'),
|
|
430
|
+
})).min(1).max(16).describe('stills rendered serially; each may itself batch'),
|
|
431
|
+
},
|
|
432
|
+
(args) => paintStills(deps, args),
|
|
433
|
+
);
|
|
434
|
+
}
|