dsh-drawai 0.4.1 → 0.4.3
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/README.md +96 -253
- package/lib/client.js +28 -7
- package/lib/index.js +474 -97
- package/lib/motion/asset-pack.js +109 -50
- package/lib/motion/assets.js +291 -291
- package/lib/motion/demo-assets-lock.js +10 -0
- package/lib/motion/mgstyles/vendor/README.md +28 -0
- package/lib/motion/mgstyles/vendor/modules/d3/LICENSE +13 -0
- package/lib/motion/mgstyles/vendor/modules/d3/dist/d3.min.js +2 -0
- package/lib/motion/mgstyles/vendor/modules/opentype.js/LICENSE +20 -0
- package/lib/motion/mgstyles/vendor/modules/opentype.js/dist/opentype.mjs +14460 -0
- package/lib/motion/mgstyles/vendor/modules/simplex-noise/LICENSE +21 -0
- package/lib/motion/mgstyles/vendor/modules/simplex-noise/dist/esm/simplex-noise.js +466 -0
- package/lib/motion/mgstyles/vendor/modules/topojson-client/LICENSE +13 -0
- package/lib/motion/mgstyles/vendor/modules/topojson-client/dist/topojson-client.min.js +2 -0
- package/lib/motion/preview.js +10 -3
- package/lib/motion/render.js +19 -1
- package/lib/motion/shotcraft/upstream/SKILL.md +286 -286
- package/lib/motion/shotcraft/upstream/assets/lib/Caption.tsx +54 -54
- package/lib/motion/shotcraft/upstream/assets/lib/ClipCard.tsx +200 -200
- package/lib/motion/shotcraft/upstream/assets/lib/DigitRoll.tsx +45 -45
- package/lib/motion/shotcraft/upstream/assets/lib/FlashCut.tsx +20 -20
- package/lib/motion/shotcraft/upstream/assets/lib/FlatPanel.tsx +65 -65
- package/lib/motion/shotcraft/upstream/assets/lib/PageCam.tsx +151 -151
- package/lib/motion/shotcraft/upstream/assets/lib/VerticalTicker.tsx +129 -129
- package/lib/motion/shotcraft/upstream/assets/lib/helpers/camera.tsx +75 -75
- package/lib/motion/shotcraft/upstream/assets/lib/helpers/motion.ts +40 -40
- package/lib/motion/shotcraft/upstream/assets/lib/helpers/rand.ts +11 -11
- package/lib/motion/shotcraft/upstream/assets/lib/helpers/shake.ts +10 -10
- package/lib/motion/shotcraft/upstream/assets/scripts/build-readme-cover.mjs +49 -49
- package/lib/motion/shotcraft/upstream/assets/scripts/capture-template.mjs +210 -210
- package/lib/motion/shotcraft/upstream/assets/scripts/sfx-source.py +423 -423
- package/lib/motion/shotcraft/upstream/assets/scripts/smoke-render-demos.py +170 -170
- package/lib/motion/shotcraft/upstream/assets/scripts/test_sfx_source.py +157 -157
- package/lib/motion/shotcraft/upstream/demos/README.md +120 -120
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Brand.tsx +112 -112
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Fixtures.tsx +647 -647
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Look.tsx +316 -316
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Motion.tsx +82 -82
- package/lib/motion/shotcraft/upstream/demos/_fixtures/PageCam2D.tsx +115 -115
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Polish.tsx +243 -243
- package/lib/motion/shotcraft/upstream/demos/_textures/live-layout.json +388 -388
- package/lib/motion/shotcraft/upstream/demos/camera/basic-3d-scene/Basic3DScene.tsx +250 -250
- package/lib/motion/shotcraft/upstream/demos/camera/crash-zoom-punch/CrashImpactReal.tsx +305 -305
- package/lib/motion/shotcraft/upstream/demos/camera/crash-zoom-punch/CrashZoomReal.tsx +246 -246
- package/lib/motion/shotcraft/upstream/demos/camera/cursor-flyover/CursorFlyover.tsx +390 -390
- package/lib/motion/shotcraft/upstream/demos/camera/depth-layer-moves/MultiplaneReal.tsx +138 -138
- package/lib/motion/shotcraft/upstream/demos/camera/graze-face-tour/GrazeFaceTour.tsx +542 -542
- package/lib/motion/shotcraft/upstream/demos/camera/overhead-camera-moves/OverheadTabletopDrop.tsx +315 -315
- package/lib/motion/shotcraft/upstream/demos/camera/overhead-camera-moves/TiltReveal.tsx +299 -299
- package/lib/motion/shotcraft/upstream/demos/camera/space-camera-moves/DroneDiveLanding.tsx +173 -173
- package/lib/motion/shotcraft/upstream/demos/camera/space-camera-moves/ExplodedView.tsx +348 -348
- package/lib/motion/shotcraft/upstream/demos/camera/steep-tilt-glide/SteepTiltGlide.tsx +366 -366
- package/lib/motion/shotcraft/upstream/demos/camera/tension-camera-moves/BulletTimeFreezeOrbit.tsx +279 -279
- package/lib/motion/shotcraft/upstream/demos/camera/tension-camera-moves/PullBackIsolation.tsx +184 -184
- package/lib/motion/shotcraft/upstream/demos/camera/terminal-3d/Terminal3D.tsx +390 -390
- package/lib/motion/shotcraft/upstream/demos/data/before-after-slider-scrub/BeforeAfterSliderScrub.tsx +155 -155
- package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/AxisRescaleShockV2.tsx +303 -303
- package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/OscilloscopeStreamV2.tsx +290 -290
- package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/UnitDotSwarmRegroupV2.tsx +278 -278
- package/lib/motion/shotcraft/upstream/demos/data/gauge-readout-moves/NeedleSweepSelftest.tsx +307 -307
- package/lib/motion/shotcraft/upstream/demos/data/gauge-readout-moves/TapeScrollFixedPointer.tsx +215 -215
- package/lib/motion/shotcraft/upstream/demos/data/scroll-brake-moves/BrakeReticleLock.tsx +299 -299
- package/lib/motion/shotcraft/upstream/demos/data/scroll-brake-moves/ChangelogScrollBrake.tsx +232 -232
- package/lib/motion/shotcraft/upstream/demos/data/timeline-travel/TimelineTravel.tsx +290 -290
- package/lib/motion/shotcraft/upstream/demos/effects/assemble-then-type-flyin/AssembleThenTypeFlyin.tsx +273 -273
- package/lib/motion/shotcraft/upstream/demos/effects/aurora-bloom-bg-flip/AuroraBloomBgFlip.tsx +167 -167
- package/lib/motion/shotcraft/upstream/demos/effects/brand-frame-snap/BrandFrameSnap.tsx +536 -536
- package/lib/motion/shotcraft/upstream/demos/effects/fui-hud-moves/ReticleLockOn.tsx +164 -164
- package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/FlylineArc.tsx +175 -175
- package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/GlowOrbAmbient.tsx +197 -197
- package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/OrbFlylineRelay.tsx +241 -241
- package/lib/motion/shotcraft/upstream/demos/effects/icon-performance-moves/AttentionBounce.tsx +279 -279
- package/lib/motion/shotcraft/upstream/demos/effects/impact-feedback/AnimeImpact.tsx +230 -230
- package/lib/motion/shotcraft/upstream/demos/effects/impact-feedback/HitCounter.tsx +345 -345
- package/lib/motion/shotcraft/upstream/demos/effects/light-play-moves/SheenSweepRetry.tsx +275 -275
- package/lib/motion/shotcraft/upstream/demos/effects/light-play-moves/SpotlightSweepReveal.tsx +229 -229
- package/lib/motion/shotcraft/upstream/demos/effects/radial-ripple-phone-chips/RadialRipplePhoneChips.tsx +312 -312
- package/lib/motion/shotcraft/upstream/demos/effects/riso-print-hits/RisoMisregistrationHit.tsx +222 -222
- package/lib/motion/shotcraft/upstream/demos/effects/scan-bracket-sweep/ScanBracketSweep.tsx +295 -295
- package/lib/motion/shotcraft/upstream/demos/effects/scanline-annotate-focus/ScanlineAnnotateFocus.tsx +329 -329
- package/lib/motion/shotcraft/upstream/demos/effects/scanline-assemble-flyin/ScanlineAssembleFlyin.tsx +315 -315
- package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/ImpactBurstKit.tsx +264 -264
- package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx +260 -260
- package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/ScoreSlam.tsx +263 -263
- package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/CornerSpotlightReveal.tsx +123 -123
- package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/GlowWakeSleepPanel.tsx +187 -187
- package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/SlideSpotlightPan.tsx +175 -175
- package/lib/motion/shotcraft/upstream/demos/interaction/autolayout-gap-dial/AutolayoutGapDial.tsx +305 -305
- package/lib/motion/shotcraft/upstream/demos/interaction/canvas-materialize-moves/DiagramCascadeBuild.tsx +350 -350
- package/lib/motion/shotcraft/upstream/demos/interaction/canvas-materialize-moves/PanelToCanvasMaterialize.tsx +352 -352
- package/lib/motion/shotcraft/upstream/demos/interaction/chip-grid-single-select-blackout/ChipGridSingleSelectBlackout.tsx +218 -218
- package/lib/motion/shotcraft/upstream/demos/interaction/collab-cursor-moves/CursorCastEnsemble.tsx +297 -297
- package/lib/motion/shotcraft/upstream/demos/interaction/glass-pill-dictation-typing/GlassPillDictationTyping.tsx +157 -157
- package/lib/motion/shotcraft/upstream/demos/interaction/hashtag-to-pill-materialize/HashtagToPillMaterialize.tsx +218 -218
- package/lib/motion/shotcraft/upstream/demos/interaction/picker-carousel-feature-cycle/PickerCarouselFeatureCycle.tsx +202 -202
- package/lib/motion/shotcraft/upstream/demos/interaction/segmented-thumb-hero/SegmentedThumbHero.tsx +265 -265
- package/lib/motion/shotcraft/upstream/demos/interaction/theme-switch-moves/PaletteThemeRipple.tsx +457 -457
- package/lib/motion/shotcraft/upstream/demos/interaction/theme-switch-moves/ThemeSweepToggle.tsx +446 -446
- package/lib/motion/shotcraft/upstream/demos/interaction/type-and-filter/TypeAndFilter.tsx +249 -249
- package/lib/motion/shotcraft/upstream/demos/interaction/voice-waveform-live/VoiceWaveformLive.tsx +278 -278
- package/lib/motion/shotcraft/upstream/demos/opening/crane-rise-reveal/CraneRiseReveal.tsx +332 -332
- package/lib/motion/shotcraft/upstream/demos/opening/dataviz-landscape-open/DatavizLandscapeOpen.tsx +358 -358
- package/lib/motion/shotcraft/upstream/demos/opening/fracture/Fracture.tsx +201 -201
- package/lib/motion/shotcraft/upstream/demos/opening/icon-field-colorize/IconFieldColorize.tsx +200 -200
- package/lib/motion/shotcraft/upstream/demos/opening/letterspace-materialize/LetterspaceMaterialize.tsx +208 -208
- package/lib/motion/shotcraft/upstream/demos/opening/magician-card-flourish/MagicianCardFlourish.tsx +341 -341
- package/lib/motion/shotcraft/upstream/demos/opening/orbit-ring-title-open/OrbitRingTitleOpen.tsx +389 -389
- package/lib/motion/shotcraft/upstream/demos/opening/spotlight-hero-card/SpotlightHeroCard.tsx +293 -293
- package/lib/motion/shotcraft/upstream/demos/opening/stroke-segment-build/StrokeSegmentBuild.tsx +183 -183
- package/lib/motion/shotcraft/upstream/demos/opening/text-as-mask/TextAsMask.tsx +245 -245
- package/lib/motion/shotcraft/upstream/demos/outro/grain-dissolve/GrainDissolve.tsx +222 -222
- package/lib/motion/shotcraft/upstream/demos/outro/logo-shrink-wordmark-lockup/LogoShrinkWordmarkLockup.tsx +223 -223
- package/lib/motion/shotcraft/upstream/demos/outro/neon-triple-marquee/NeonTripleMarquee.tsx +217 -217
- package/lib/motion/shotcraft/upstream/demos/outro/outro-group-photo-launch/OutroGroupPhotoLaunch.tsx +343 -343
- package/lib/motion/shotcraft/upstream/demos/outro/ui-strip-away-outro/UiStripAwayOutro.tsx +357 -357
- package/lib/motion/shotcraft/upstream/demos/outro/ui-to-brand-morph/IconFlipBloomLogo.tsx +187 -187
- package/lib/motion/shotcraft/upstream/demos/outro/ui-to-brand-morph/InputMorphsIntoLogo.tsx +333 -333
- package/lib/motion/shotcraft/upstream/demos/rhythm/beat-cut-moves/PaparazziFlash.tsx +274 -274
- package/lib/motion/shotcraft/upstream/demos/rhythm/beat-step-list-theme-cycle/BeatStepListThemeCycle.tsx +270 -270
- package/lib/motion/shotcraft/upstream/demos/rhythm/panel-grid-moves/ComicPanelSplit.tsx +430 -430
- package/lib/motion/shotcraft/upstream/demos/rhythm/quad-split-parallel-scenes/QuadSplitParallelScenes.tsx +252 -252
- package/lib/motion/shotcraft/upstream/demos/rhythm/rhythm-interrupt-moves/JumpCutPunchIn.tsx +106 -106
- package/lib/motion/shotcraft/upstream/demos/rhythm/sakuga-timing-shift/SakugaTimingShift.tsx +288 -288
- package/lib/motion/shotcraft/upstream/demos/rhythm/speed-ramp-freeze/FreezeAnnotateReal.tsx +362 -362
- package/lib/motion/shotcraft/upstream/demos/rhythm/trailer-grammar-moves/TrailerBumper.tsx +168 -168
- package/lib/motion/shotcraft/upstream/demos/transition/bottom-push-stack-wipe/BottomPushStackWipe.tsx +310 -310
- package/lib/motion/shotcraft/upstream/demos/transition/card-flip-reveal/CardFlipReveal.tsx +261 -261
- package/lib/motion/shotcraft/upstream/demos/transition/card-flock-tumble/CardFlockTumble.tsx +459 -459
- package/lib/motion/shotcraft/upstream/demos/transition/circle-match-iris/CircleMatchIris.tsx +294 -294
- package/lib/motion/shotcraft/upstream/demos/transition/gradient-transition/GradientTransition.tsx +161 -161
- package/lib/motion/shotcraft/upstream/demos/transition/line-carry-transition/LineCarryTransition.tsx +248 -248
- package/lib/motion/shotcraft/upstream/demos/transition/mosaic-reframe/MosaicReframe.tsx +359 -359
- package/lib/motion/shotcraft/upstream/demos/transition/page-turn-transitions/BarnDoorSplit.tsx +227 -227
- package/lib/motion/shotcraft/upstream/demos/transition/print-texture-transitions/InkBleedReveal.tsx +162 -162
- package/lib/motion/shotcraft/upstream/demos/transition/shot-transitions/PortalWipeV2.tsx +248 -248
- package/lib/motion/shotcraft/upstream/demos/transition/shot-transitions/WhipBrakeReal.tsx +186 -186
- package/lib/motion/shotcraft/upstream/demos/transition/tear-streak-transitions/GlitchDisplace.tsx +355 -355
- package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/InvisibleCut.tsx +272 -272
- package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/LightLeakBurn.tsx +262 -262
- package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/VersusSlam.tsx +217 -217
- package/lib/motion/shotcraft/upstream/demos/transition/transition-travel/LetterformZoom.tsx +285 -285
- package/lib/motion/shotcraft/upstream/demos/transition/white-flash-logo-simplify-cut/WhiteFlashLogoSimplifyCut.tsx +205 -205
- package/lib/motion/shotcraft/upstream/demos/transition/wipe-transitions/BlindsSlice.tsx +178 -178
- package/lib/motion/shotcraft/upstream/demos/typography/blur-slide/BlurSlide.tsx +147 -147
- package/lib/motion/shotcraft/upstream/demos/typography/brace-expand/BraceExpand.tsx +143 -143
- package/lib/motion/shotcraft/upstream/demos/typography/brand-ink-open/BrandInkOpen.tsx +134 -134
- package/lib/motion/shotcraft/upstream/demos/typography/countdown-arc-scatter/CountdownArcScatter.tsx +217 -217
- package/lib/motion/shotcraft/upstream/demos/typography/flying-words/FlyingWords.tsx +198 -198
- package/lib/motion/shotcraft/upstream/demos/typography/glitch-cycle/GlitchCycle.tsx +221 -221
- package/lib/motion/shotcraft/upstream/demos/typography/gradient-word-sweep/GradientWordSweep.tsx +312 -312
- package/lib/motion/shotcraft/upstream/demos/typography/lead-word-zoom-assemble/LeadWordZoomAssemble.tsx +261 -261
- package/lib/motion/shotcraft/upstream/demos/typography/marker-underline-title/MarkerUnderlineTitle.tsx +121 -121
- package/lib/motion/shotcraft/upstream/demos/typography/outline-word-fill/OutlineWordFill.tsx +153 -153
- package/lib/motion/shotcraft/upstream/demos/typography/paper-title-card/PaperTitleCard.tsx +179 -179
- package/lib/motion/shotcraft/upstream/demos/typography/pill-chip-slot-cycle-handled/PillChipSlotCycleHandled.tsx +140 -140
- package/lib/motion/shotcraft/upstream/demos/typography/pill-slot-cycle/PillSlotCycle.tsx +309 -309
- package/lib/motion/shotcraft/upstream/demos/typography/text-column-converge/TextColumnConverge.tsx +191 -191
- package/lib/motion/shotcraft/upstream/demos/typography/title-demote-to-label/TitleDemoteToLabel.tsx +292 -292
- package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/LetterformDriftAssembly.tsx +135 -135
- package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/SplitTextStagger.tsx +124 -124
- package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/TextOnPath.tsx +255 -255
- package/lib/motion/shotcraft/upstream/demos/typography/type-entrance-moves/LetterDropPhysics.tsx +173 -173
- package/lib/motion/shotcraft/upstream/demos/typography/type-rhythm-sync/FontWeightPump.tsx +225 -225
- package/lib/motion/shotcraft/upstream/demos/typography/type-rhythm-sync/KaraokeFillSync.tsx +141 -141
- package/lib/motion/shotcraft/upstream/demos/typography/typewriter-moves/TypewriterErrorRetype.tsx +137 -137
- package/lib/motion/shotcraft/upstream/demos/typography/vertical-word-roll-blur-cycle/VerticalWordRollBlurCycle.tsx +147 -147
- package/lib/motion/shotcraft/upstream/demos/typography/word-relay-filmstrip/WordRelayFilmstrip.tsx +409 -409
- package/lib/motion/shotcraft/upstream/demos/typography/word-relay-geometry/WordRelayGeometry.tsx +280 -280
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/avatar-bracket-carousel/AvatarBracketCarousel.tsx +271 -271
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/bezier-source-converge-merge/BezierSourceConvergeMerge.tsx +385 -385
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/card-stack/CardStack.tsx +312 -312
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/carousel-3d/Carousel3D.tsx +303 -303
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/clipcard-looping/ClipCardLooping.tsx +77 -77
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/cloner-depth-echo/ClonerDepthEcho.tsx +311 -311
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/deck-deal-flyin/DeckDealFlyin.tsx +307 -307
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/doc-park-left-pill-deal/DocParkLeftPillDeal.tsx +349 -349
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/document-typewriter-reveal/DocumentTypewriterReveal.tsx +255 -255
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/draw-svg-trace/DrawSvgTrace.tsx +244 -244
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/element-body-moves/ContactShadowLift.tsx +217 -217
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/integration-hub-map/IntegrationHubMap.tsx +652 -652
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/list-reveal/ListReveal.tsx +153 -153
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/list-stack-press/ListStackPress.tsx +238 -238
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/neon-frame-forerun/NeonFrameForerun.tsx +324 -324
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/neon-frame-orbit-drop/NeonFrameForerunOrbit.tsx +354 -354
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/page-waterfall-wall/PageWaterfallWall.tsx +101 -101
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/page-waterfall-wall/VerticalTicker.tsx +129 -129
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/product-card-progressive-assemble/ProductCardProgressiveAssemble.tsx +297 -297
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/radial-wave/RadialWave.tsx +57 -57
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/research-card-stack-scroll/ResearchCardStackScroll.tsx +155 -155
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/row-embed/RowEmbed.tsx +175 -175
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/value-stagger-gradient/ValueStaggerGradient.tsx +153 -153
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/wall-reveal-moves/BentoLightUp.tsx +146 -146
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/wall-reveal-moves/WireframeDrawOn.tsx +226 -226
- package/lib/motion/shotcraft/upstream/gallery/api/library.json +1 -1
- package/lib/motion/shotcraft/upstream/references/aesthetic-rules.md +193 -193
- package/lib/motion/shotcraft/upstream/references/final-review.md +121 -121
- package/lib/motion/shotcraft/upstream/references/guided-free-creation.md +199 -199
- package/lib/motion/shotcraft/upstream/references/jianying-export.md +183 -183
- package/lib/motion/shotcraft/upstream/references/music-beat-sync.md +257 -257
- package/lib/motion/shotcraft/upstream/references/pipeline.md +382 -382
- package/lib/motion/shotcraft/upstream/references/publish-upload-post.md +111 -111
- package/lib/motion/shotcraft/upstream/references/sequences/promo-energy-arc.md +50 -50
- package/lib/motion/shotcraft/upstream/references/sfx-sourcing.md +95 -95
- package/lib/motion/shotcraft/upstream/references/shots/camera/basic-3d-scene.md +41 -41
- package/lib/motion/shotcraft/upstream/references/shots/camera/crash-zoom-punch.md +52 -52
- package/lib/motion/shotcraft/upstream/references/shots/camera/cursor-flyover.md +39 -39
- package/lib/motion/shotcraft/upstream/references/shots/camera/depth-layer-moves.md +39 -39
- package/lib/motion/shotcraft/upstream/references/shots/camera/graze-face-tour.md +57 -57
- package/lib/motion/shotcraft/upstream/references/shots/camera/overhead-camera-moves.md +51 -51
- package/lib/motion/shotcraft/upstream/references/shots/camera/space-camera-moves.md +46 -46
- package/lib/motion/shotcraft/upstream/references/shots/camera/steep-tilt-glide.md +52 -52
- package/lib/motion/shotcraft/upstream/references/shots/camera/tension-camera-moves.md +43 -43
- package/lib/motion/shotcraft/upstream/references/shots/camera/terminal-3d.md +42 -42
- package/lib/motion/shotcraft/upstream/references/shots/data/before-after-slider-scrub.md +48 -48
- package/lib/motion/shotcraft/upstream/references/shots/data/chart-live-moves.md +52 -52
- package/lib/motion/shotcraft/upstream/references/shots/data/gauge-readout-moves.md +45 -45
- package/lib/motion/shotcraft/upstream/references/shots/data/scroll-brake-moves.md +46 -46
- package/lib/motion/shotcraft/upstream/references/shots/data/timeline-travel.md +44 -44
- package/lib/motion/shotcraft/upstream/references/shots/effects/assemble-then-type-flyin.md +63 -63
- package/lib/motion/shotcraft/upstream/references/shots/effects/aurora-bloom-bg-flip.md +54 -54
- package/lib/motion/shotcraft/upstream/references/shots/effects/brand-frame-snap.md +58 -58
- package/lib/motion/shotcraft/upstream/references/shots/effects/fui-hud-moves.md +35 -35
- package/lib/motion/shotcraft/upstream/references/shots/effects/glow-flyline-moves.md +53 -53
- package/lib/motion/shotcraft/upstream/references/shots/effects/icon-performance-moves.md +42 -42
- package/lib/motion/shotcraft/upstream/references/shots/effects/impact-feedback.md +50 -50
- package/lib/motion/shotcraft/upstream/references/shots/effects/light-play-moves.md +46 -46
- package/lib/motion/shotcraft/upstream/references/shots/effects/radial-ripple-phone-chips.md +56 -56
- package/lib/motion/shotcraft/upstream/references/shots/effects/riso-print-hits.md +49 -49
- package/lib/motion/shotcraft/upstream/references/shots/effects/scan-bracket-sweep.md +50 -50
- package/lib/motion/shotcraft/upstream/references/shots/effects/scanline-annotate-focus.md +53 -53
- package/lib/motion/shotcraft/upstream/references/shots/effects/scanline-assemble-flyin.md +53 -53
- package/lib/motion/shotcraft/upstream/references/shots/effects/slam-entrance-moves.md +55 -55
- package/lib/motion/shotcraft/upstream/references/shots/effects/spotlight-sweep-moves.md +52 -52
- package/lib/motion/shotcraft/upstream/references/shots/interaction/autolayout-gap-dial.md +60 -60
- package/lib/motion/shotcraft/upstream/references/shots/interaction/canvas-materialize-moves.md +57 -57
- package/lib/motion/shotcraft/upstream/references/shots/interaction/chip-grid-single-select-blackout.md +51 -51
- package/lib/motion/shotcraft/upstream/references/shots/interaction/collab-cursor-moves.md +41 -41
- package/lib/motion/shotcraft/upstream/references/shots/interaction/glass-pill-dictation-typing.md +53 -53
- package/lib/motion/shotcraft/upstream/references/shots/interaction/hashtag-to-pill-materialize.md +60 -60
- package/lib/motion/shotcraft/upstream/references/shots/interaction/picker-carousel-feature-cycle.md +55 -55
- package/lib/motion/shotcraft/upstream/references/shots/interaction/segmented-thumb-hero.md +57 -57
- package/lib/motion/shotcraft/upstream/references/shots/interaction/theme-switch-moves.md +47 -47
- package/lib/motion/shotcraft/upstream/references/shots/interaction/type-and-filter.md +37 -37
- package/lib/motion/shotcraft/upstream/references/shots/interaction/voice-waveform-live.md +54 -54
- package/lib/motion/shotcraft/upstream/references/shots/opening/brand-ink-open.md +36 -36
- package/lib/motion/shotcraft/upstream/references/shots/opening/crane-rise-reveal.md +56 -56
- package/lib/motion/shotcraft/upstream/references/shots/opening/dataviz-landscape-open.md +56 -56
- package/lib/motion/shotcraft/upstream/references/shots/opening/fracture.md +40 -40
- package/lib/motion/shotcraft/upstream/references/shots/opening/icon-field-colorize.md +60 -60
- package/lib/motion/shotcraft/upstream/references/shots/opening/letterspace-materialize.md +57 -57
- package/lib/motion/shotcraft/upstream/references/shots/opening/magician-card-flourish.md +73 -73
- package/lib/motion/shotcraft/upstream/references/shots/opening/orbit-ring-title-open.md +87 -87
- package/lib/motion/shotcraft/upstream/references/shots/opening/spotlight-hero-card.md +55 -55
- package/lib/motion/shotcraft/upstream/references/shots/opening/stroke-segment-build.md +50 -50
- package/lib/motion/shotcraft/upstream/references/shots/opening/text-as-mask.md +59 -59
- package/lib/motion/shotcraft/upstream/references/shots/outro/grain-dissolve.md +38 -38
- package/lib/motion/shotcraft/upstream/references/shots/outro/logo-shrink-wordmark-lockup.md +38 -38
- package/lib/motion/shotcraft/upstream/references/shots/outro/neon-triple-marquee.md +59 -59
- package/lib/motion/shotcraft/upstream/references/shots/outro/outro-group-photo-launch.md +39 -39
- package/lib/motion/shotcraft/upstream/references/shots/outro/ui-strip-away-outro.md +61 -61
- package/lib/motion/shotcraft/upstream/references/shots/outro/ui-to-brand-morph.md +52 -52
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/beat-cut-moves.md +50 -50
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/beat-step-list-theme-cycle.md +66 -66
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/panel-grid-moves.md +43 -43
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/quad-split-parallel-scenes.md +38 -38
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/rhythm-interrupt-moves.md +38 -38
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/sakuga-timing-shift.md +56 -56
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/speed-ramp-freeze.md +44 -44
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/trailer-grammar-moves.md +38 -38
- package/lib/motion/shotcraft/upstream/references/shots/transition/bottom-push-stack-wipe.md +63 -63
- package/lib/motion/shotcraft/upstream/references/shots/transition/card-flip-reveal.md +57 -57
- package/lib/motion/shotcraft/upstream/references/shots/transition/card-flock-tumble.md +63 -63
- package/lib/motion/shotcraft/upstream/references/shots/transition/circle-match-iris.md +58 -58
- package/lib/motion/shotcraft/upstream/references/shots/transition/gradient-transition.md +45 -45
- package/lib/motion/shotcraft/upstream/references/shots/transition/line-carry-transition.md +55 -55
- package/lib/motion/shotcraft/upstream/references/shots/transition/mosaic-reframe.md +49 -49
- package/lib/motion/shotcraft/upstream/references/shots/transition/page-turn-transitions.md +42 -42
- package/lib/motion/shotcraft/upstream/references/shots/transition/print-texture-transitions.md +42 -42
- package/lib/motion/shotcraft/upstream/references/shots/transition/shot-transitions.md +45 -45
- package/lib/motion/shotcraft/upstream/references/shots/transition/tear-streak-transitions.md +48 -48
- package/lib/motion/shotcraft/upstream/references/shots/transition/transition-hidden-cut.md +55 -55
- package/lib/motion/shotcraft/upstream/references/shots/transition/transition-travel.md +43 -43
- package/lib/motion/shotcraft/upstream/references/shots/transition/white-flash-logo-simplify-cut.md +41 -41
- package/lib/motion/shotcraft/upstream/references/shots/transition/wipe-transitions.md +42 -42
- package/lib/motion/shotcraft/upstream/references/shots/typography/blur-slide.md +59 -59
- package/lib/motion/shotcraft/upstream/references/shots/typography/brace-expand.md +55 -55
- package/lib/motion/shotcraft/upstream/references/shots/typography/countdown-arc-scatter.md +65 -65
- package/lib/motion/shotcraft/upstream/references/shots/typography/document-typewriter-reveal.md +42 -42
- package/lib/motion/shotcraft/upstream/references/shots/typography/flying-words.md +64 -64
- package/lib/motion/shotcraft/upstream/references/shots/typography/glitch-cycle.md +64 -64
- package/lib/motion/shotcraft/upstream/references/shots/typography/gradient-word-sweep.md +54 -54
- package/lib/motion/shotcraft/upstream/references/shots/typography/lead-word-zoom-assemble.md +91 -91
- package/lib/motion/shotcraft/upstream/references/shots/typography/marker-underline-title.md +51 -51
- package/lib/motion/shotcraft/upstream/references/shots/typography/outline-word-fill.md +66 -66
- package/lib/motion/shotcraft/upstream/references/shots/typography/paper-title-card.md +38 -38
- package/lib/motion/shotcraft/upstream/references/shots/typography/pill-chip-slot-cycle-handled.md +66 -66
- package/lib/motion/shotcraft/upstream/references/shots/typography/pill-slot-cycle.md +58 -58
- package/lib/motion/shotcraft/upstream/references/shots/typography/text-column-converge.md +61 -61
- package/lib/motion/shotcraft/upstream/references/shots/typography/title-demote-to-label.md +64 -64
- package/lib/motion/shotcraft/upstream/references/shots/typography/type-assembly-moves.md +45 -45
- package/lib/motion/shotcraft/upstream/references/shots/typography/type-entrance-moves.md +41 -41
- package/lib/motion/shotcraft/upstream/references/shots/typography/type-rhythm-sync.md +51 -51
- package/lib/motion/shotcraft/upstream/references/shots/typography/typewriter-moves.md +42 -42
- package/lib/motion/shotcraft/upstream/references/shots/typography/vertical-word-roll-blur-cycle.md +63 -63
- package/lib/motion/shotcraft/upstream/references/shots/typography/word-relay-filmstrip.md +57 -57
- package/lib/motion/shotcraft/upstream/references/shots/typography/word-relay-geometry.md +75 -75
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/avatar-bracket-carousel.md +59 -59
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/bezier-source-converge-merge.md +67 -67
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/card-stack.md +60 -60
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/carousel-3d.md +66 -66
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/cloner-depth-echo.md +40 -40
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/deck-deal-flyin.md +45 -45
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/doc-park-left-pill-deal.md +62 -62
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/draw-svg-trace.md +55 -55
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/element-body-moves.md +37 -37
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/integration-hub-map.md +55 -55
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/list-reveal.md +50 -50
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/list-stack-press.md +43 -43
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/neon-frame-forerun.md +55 -55
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/neon-frame-orbit-drop.md +51 -51
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/page-waterfall-wall.md +51 -51
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/product-card-progressive-assemble.md +56 -56
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/radial-wave.md +49 -49
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/research-card-stack-scroll.md +57 -57
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/row-embed.md +36 -36
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/value-stagger-gradient.md +64 -64
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/wall-reveal-moves.md +47 -47
- package/lib/motion/shotcraft/upstream/references/sound-design.md +170 -170
- package/lib/motion/shotcraft/upstream/references/workbench.md +157 -157
- package/lib/motion/shotcraft/upstream/template/TEMPLATE.md +103 -103
- package/lib/motion/shotcraft/upstream/template/THEMES.md +44 -44
- package/lib/motion/shotcraft/upstream/template/package.json +20 -20
- package/lib/motion/shotcraft/upstream/template/remotion.config.ts +6 -6
- package/lib/motion/shotcraft/upstream/template/src/Root.tsx +17 -17
- package/lib/motion/shotcraft/upstream/template/src/aifl/Caption.tsx +65 -65
- package/lib/motion/shotcraft/upstream/template/src/aifl/DigitRoll.tsx +48 -48
- package/lib/motion/shotcraft/upstream/template/src/aifl/FlashCut.tsx +24 -24
- package/lib/motion/shotcraft/upstream/template/src/aifl/Main.tsx +192 -192
- package/lib/motion/shotcraft/upstream/template/src/aifl/PaperTitleCard.tsx +111 -111
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/PageCam.tsx +150 -150
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneDetail.tsx +158 -158
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneFlyIn.tsx +468 -468
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneOpen.tsx +552 -552
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneOutroLive.tsx +411 -411
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/ScenePapers.tsx +200 -200
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneWbr.tsx +324 -324
- package/lib/motion/shotcraft/upstream/template/src/aifl/live-layout.json +388 -388
- package/lib/motion/shotcraft/upstream/template/src/index.ts +4 -4
- package/lib/motion/shotcraft/upstream/template/src/themes/ThemedFilm.tsx +7 -7
- package/lib/motion/shotcraft/upstream/template/src/themes/manifest.tsx +68 -68
- package/lib/motion/shotcraft/upstream/template/src/themes/palette-assets.json +1 -1
- package/lib/motion/shotcraft/upstream/template/src/themes/palette-assets.ts +24 -24
- package/lib/motion/shotcraft/upstream/template/src/themes/palettes.json +74 -74
- package/lib/motion/shotcraft/upstream/template/src/themes/visual-theme.tsx +91 -91
- package/lib/motion/shotcraft/upstream/template/src/workbench.ts +213 -213
- package/lib/motion/shotcraft/upstream/template/tsconfig.json +15 -15
- package/lib/motion/style-frames.js +47 -12
- package/lib/motion-project.js +22 -3
- package/lib/motion-render-worker.js +31 -9
- package/lib/routing/router.js +12 -1
- package/lib/style-kernel.js +16 -6
- package/lib/video-trace-pool.js +128 -128
- package/package.json +1 -1
- package/LICENSE +0 -4175
package/lib/index.js
CHANGED
|
@@ -257,6 +257,26 @@ function messageOf(error) {
|
|
|
257
257
|
return String(error)
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
// json 参数在工具 schema 里编译成 `{}`(没有类型约束),有的模型/宿主会把它当成字符串送进来,
|
|
261
|
+
// 比如 frames 收到的是 "[0, 120]"。这里统一还原一次:看起来像 JSON 且解析成功就用解析结果,
|
|
262
|
+
// 否则原样返回。不这么做的话,Array.isArray(字符串) 恒为假,参数会被静默忽略、退回默认值。
|
|
263
|
+
function jsonValue(value) {
|
|
264
|
+
if (typeof value !== 'string') return value
|
|
265
|
+
const text = value.trim()
|
|
266
|
+
if (!text || !'[{"]-0123456789ntf'.includes(text[0])) return value
|
|
267
|
+
try { return JSON.parse(text) } catch { return value }
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function jsonObject(value) {
|
|
271
|
+
const parsed = jsonValue(value)
|
|
272
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function jsonArray(value) {
|
|
276
|
+
const parsed = jsonValue(value)
|
|
277
|
+
return Array.isArray(parsed) ? parsed : []
|
|
278
|
+
}
|
|
279
|
+
|
|
260
280
|
function numberOr(value, fallback) {
|
|
261
281
|
const n = Number(value)
|
|
262
282
|
return Number.isFinite(n) ? n : fallback
|
|
@@ -2386,6 +2406,12 @@ function placeMissing(doc) {
|
|
|
2386
2406
|
|
|
2387
2407
|
/** 画布写回端点:人工编辑唯一的落盘通道。 */
|
|
2388
2408
|
export const SAVE_PATH = '/drawai/api/save'
|
|
2409
|
+
/**
|
|
2410
|
+
* 只读诊断端点:把"这次到底注册了哪几个 host 工具"直接摆出来。
|
|
2411
|
+
* 起因是 issue #8 —— 画布能用、apply 也确实跑过,但模型侧一个工具都没有;
|
|
2412
|
+
* 现场只有一张截图,缺的正是这条数据(没注册 / 注册报错 / 注册完又被回收)。
|
|
2413
|
+
*/
|
|
2414
|
+
export const DIAGNOSTICS_PATH = '/drawai/api/diagnostics'
|
|
2389
2415
|
/** 「打开」菜单最多下钻几个子目录 —— 不做全盘递归,避免撞上 node_modules 这类大目录。 */
|
|
2390
2416
|
const MAX_SCAN_DIRS = 12
|
|
2391
2417
|
/** 自定义请求头 —— 跨站表单设不了它,跨域 fetch 会被预检拦住(CSRF 围栏)。 */
|
|
@@ -2441,9 +2467,40 @@ export const inject = ['tools', 'fs', 'sessions', 'sandboxPolicy', 'webServer']
|
|
|
2441
2467
|
* 它出现在会话的技能目录里,模型需要时按需取全文 —— 于是"怎么在这张画布上操作"这件事
|
|
2442
2468
|
* 跟着插件走,而不是跟着这份仓库走。
|
|
2443
2469
|
*
|
|
2470
|
+
* **拆成两份**(drawai-canvas / drawai-video),因为这是两种任务、两套工具族:画布那份讲
|
|
2471
|
+
* diagram_read / diagram_apply 的全部 op 与几何规则,视频那份讲十步制作流程与 motion_* 那组工具。
|
|
2472
|
+
* 合成一份时正文近三万字,而技能目录里只显示 description 那一句 —— 用户说"做个宣传片"会先被
|
|
2473
|
+
* "读写画布"这句挡住;分开后两条简介各自对口,画图的人也不必每次为视频流程付上下文。
|
|
2474
|
+
* 视频那份自带一段"画布最小集"(见下),单独加载也不会写错 op;画布那份末尾指向视频技能。
|
|
2475
|
+
*
|
|
2444
2476
|
* 写成模板串但**不用反引号**(markdown 里要写示例,反引号会破坏模板串):代码块用四空格缩进。
|
|
2477
|
+
* 两份各自是独立模板串;`tools/build.mjs` 的反引号配平检查按整份文件数,照样管得住。
|
|
2445
2478
|
*/
|
|
2446
|
-
|
|
2479
|
+
|
|
2480
|
+
/** 技能 1/2:视频。位置保持原样(正文接在原来的地方),改动只剩标题与开头那段最小集。 */
|
|
2481
|
+
const SKILL_BODY_VIDEO = `# DrawAI 画布做视频:从问到出片
|
|
2482
|
+
|
|
2483
|
+
这个技能只讲**怎么把一条片子做出来**。画布怎么读、怎么改(diagram_read / diagram_apply 的完整签名、
|
|
2484
|
+
折点与端点、图层与顺序、traceImage 位图矢量化)在技能 drawai-canvas 里 —— 视频流程真正用到的那几个 op
|
|
2485
|
+
下面列全了,不够用时再加载那个技能。
|
|
2486
|
+
|
|
2487
|
+
## 画布最小集(视频只用到这些 op)
|
|
2488
|
+
|
|
2489
|
+
// 1) 先读回当前那张画布:用户说"这张"就别猜路径,不传 path 读一次
|
|
2490
|
+
diagram_read() // → { path, absolute, nodes, edges, ... },path 拿去给 apply
|
|
2491
|
+
// 2) 一次写完:建节点、连卡、放分镜表;几何已经规划好就 layout:"none"
|
|
2492
|
+
diagram_apply({ path, layout:"none", ops:[
|
|
2493
|
+
{op:"addNode", label:"分镜表", shape:"text", x:40, y:40, w:520, h:360, as:"board"},
|
|
2494
|
+
{op:"addNode", label:"主片", shape:"video", x:600, y:40, w:480, h:270, as:"film"},
|
|
2495
|
+
{op:"addEdge", from:"shot-card", to:"film"} // shot-card 是画布上已存在的镜头卡 id
|
|
2496
|
+
]})
|
|
2497
|
+
|
|
2498
|
+
- **path 必填**:diagram_apply 永远要显式传 path;diagram_read 不传时返回当前聚焦的那张。
|
|
2499
|
+
- **from / to 必须是已存在的节点 id**(写错在写盘前就报错);as 起的别名只在同一次 ops 数组里有效。
|
|
2500
|
+
- **建了镜头卡就必须连**:断连的卡既没进 assets、也不排期,motion_project create 会直接报错。
|
|
2501
|
+
- 连线用圆角折线,折点自己规划(points 只放中间转折,不放源/目标连接点),不要启用自动避障。
|
|
2502
|
+
- 挪位置用 move,改文字用 setLabel,换样式用 setStyle;改一组就传 ids:[...]。改接边用 setEdge,
|
|
2503
|
+
不要 remove + addEdge 重画 —— 那会丢掉端点约束和折点。
|
|
2447
2504
|
|
|
2448
2505
|
## 视频节点与工作流编排
|
|
2449
2506
|
|
|
@@ -2500,12 +2557,19 @@ const SKILL_BODY = `# DrawAI 画布:怎么读、怎么改
|
|
|
2500
2557
|
参考图:diagram_apply 连接图片节点后会把画布快照冻结进工程 assets/,场景里通过 props.assets 取用。视频参数(宽高/fps/duration)在 motion_project create 时写入节点;用户指定时长必须显式传 duration(支持 9.3 等小数秒),不能只写在 prompt 里。
|
|
2501
2558
|
旧任务记录用 motion_task list/status/cancel/apply/focus/remove 管理,不要再指望它 start。多页或多个视频时明确指定 page/nodeId。
|
|
2502
2559
|
不要把这条流程想成"提交一个任务等结果":你是在一个普通工程里写代码、看图、渲染,每一步都由本次对话直接完成。
|
|
2560
|
+
`
|
|
2561
|
+
|
|
2562
|
+
/** 技能 2/2:画布。正文从原来的位置原样接上,只多了标题下的一条"做视频去那边"。 */
|
|
2563
|
+
const SKILL_BODY_CANVAS = `# DrawAI 画布:怎么读、怎么改
|
|
2503
2564
|
|
|
2504
2565
|
## 一句话
|
|
2505
2566
|
工作区里任何一个 .drawio 文件**就是**一张 DrawAI 画布 —— 它是 drawio 自己的 mxfile 格式。
|
|
2506
2567
|
用户在 DSH 右栏画布面板里看到的、以及用 drawio 桌面版打开的,是同一份文件;
|
|
2507
2568
|
没有"导入/导出"这一步,你直接读写这个文件就是读写那张画布。
|
|
2508
2569
|
|
|
2570
|
+
视频节点、镜头卡、分镜表也都是这张画布上的对象,但**做视频**(选卡、写分镜表、出样片与成片)
|
|
2571
|
+
是另一套流程:加载技能 drawai-video。这里只讲怎么读写它们。
|
|
2572
|
+
|
|
2509
2573
|
## 两个工具怎么配合
|
|
2510
2574
|
1. **diagram_read(path?, layer?, ids?)** —— 先读。返回节点(id/label/shape/style/**x·y·w·h 坐标尺寸**)、
|
|
2511
2575
|
边(id/from/to/label/style/dash/arrow/折点/悬空端的自由点/标签位置 labelX·labelY)、
|
|
@@ -2741,8 +2805,12 @@ diagram_read —— 返回里的 path 就是它(这两条是一致的,同一
|
|
|
2741
2805
|
## 想看效果图的时候(diagram_read 带 render:true)
|
|
2742
2806
|
- 传 render:true 就是"让我看一眼这张画布":正交路由、避让、圆角、文字断线这些
|
|
2743
2807
|
**只有看图才知道**的东西,几何数据里没有(宿主也不算走线,那是画布算的)。
|
|
2744
|
-
-
|
|
2745
|
-
|
|
2808
|
+
- 渲染在**浏览器那一半**做,所以只有用户在右侧栏**打开着这张画布**时才有人接活:
|
|
2809
|
+
· 画布没打开(宿主知道你打开的是别的文件)→ look.status="not-open",附原因,别重试;
|
|
2810
|
+
· 打开了 → 第一次返回 look.status="pending"(已请求,带 waitedMs),**等几秒再调一次**
|
|
2811
|
+
diagram_read(render:true),图就作为图片内容块跟回来了;
|
|
2812
|
+
· 挂了请求却一直没人接(25 秒)→ look.status="timeout",带已等待秒数 —— 这时别再
|
|
2813
|
+
傻等,要么请用户先打开画布,要么直接用几何数据。
|
|
2746
2814
|
- **不会在用户的工作区里生成任何文件**:图片存在宿主那边的附件库里(与用户上传图片同一套存储,
|
|
2747
2815
|
在工作区之外),看完即取走 —— 要再看就再要一次(图不会被反复塞给你)。
|
|
2748
2816
|
- **纯改图不需要它**:几何、样式、图层、纸张从普通 diagram_read 里就有。
|
|
@@ -2782,16 +2850,30 @@ HTML 标签按纯文本显示、图层结构原样保留(画布把它们叠在
|
|
|
2782
2850
|
`
|
|
2783
2851
|
|
|
2784
2852
|
/**
|
|
2785
|
-
*
|
|
2853
|
+
* 技能目录里那两条(模型先看到简介,需要时才取全文)。
|
|
2786
2854
|
* 名字用 kebab-case —— DSH 的注册表按名字寻址并要求这个形状。
|
|
2855
|
+
*
|
|
2856
|
+
* description 是**唯一的检索入口**:会话的技能目录只渲染 name + description(whenToUse 不出现在
|
|
2857
|
+
* 目录里),所以两边各写各的任务词,互不侵占 —— 这正是拆分的主要收益。
|
|
2787
2858
|
*/
|
|
2788
|
-
const
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2859
|
+
const SKILL_DEFINITIONS = [
|
|
2860
|
+
{
|
|
2861
|
+
name: 'drawai-canvas',
|
|
2862
|
+
description:
|
|
2863
|
+
'读写 DrawAI 画布(.drawio):diagram_read 看现状、diagram_apply 结构化改图;支持图像复刻、视频分段复刻及 SVG 帧序列导出。',
|
|
2864
|
+
whenToUse:
|
|
2865
|
+
'用户让你画流程图/架构图/泳道图、复刻图片或视频、将视频转为 SVG 帧动画,或让你读/改某张 .drawio 画布,或问"这张图里有什么""画布上怎么操作"时。',
|
|
2866
|
+
content: SKILL_BODY_CANVAS,
|
|
2867
|
+
},
|
|
2868
|
+
{
|
|
2869
|
+
name: 'drawai-video',
|
|
2870
|
+
description:
|
|
2871
|
+
'用 DrawAI 画布做视频:定镜头卡与整片风格、写分镜表、生成工程、出样片与成片;含品牌素材收集、配音与踩点。',
|
|
2872
|
+
whenToUse:
|
|
2873
|
+
'用户让你做视频/宣传片/产品片/MG 动画、给已有视频改镜头或配音、把画布上的流程做成片子,或问"样片怎么出""镜头卡怎么连""配乐怎么踩点"时。',
|
|
2874
|
+
content: SKILL_BODY_VIDEO,
|
|
2875
|
+
},
|
|
2876
|
+
]
|
|
2795
2877
|
|
|
2796
2878
|
/**
|
|
2797
2879
|
* 列出工作区里的画布文档(非递归进子目录,最多扫 MAX_SCAN_DIRS 个)。
|
|
@@ -2853,7 +2935,7 @@ async function listCanvases(root, cwd, dir) {
|
|
|
2853
2935
|
if (first.entries === null) notes.push('无法列出 ' + startAbs + ':' + first.error)
|
|
2854
2936
|
else collect(first.entries, '')
|
|
2855
2937
|
for (let d = 0; d < dirs.length; d += 1) {
|
|
2856
|
-
const subAbs = toAbsolute(dirs[d],
|
|
2938
|
+
const subAbs = toAbsolute(dirs[d], startAbs)
|
|
2857
2939
|
const sub = await readDir(subAbs)
|
|
2858
2940
|
if (sub.entries === null) {
|
|
2859
2941
|
notes.push('无法列出 ' + subAbs + ':' + sub.error)
|
|
@@ -2868,6 +2950,102 @@ async function listCanvases(root, cwd, dir) {
|
|
|
2868
2950
|
|
|
2869
2951
|
export function apply(ctx) {
|
|
2870
2952
|
const documents = createDocumentCoordinator(), mediaSlots = createMediaWorkSlots(2), taskPatches = createTaskPatchJournal()
|
|
2953
|
+
|
|
2954
|
+
// ── host 工具注册的可观测化(issue #8)────────────────────────────────────
|
|
2955
|
+
// 现场是"画布全好、apply 也执行了、模型侧一个工具都没有,而且哪儿都不报错"。
|
|
2956
|
+
// 三种机制必须能被区分开,光凭"apply 进去了"证明不了什么:
|
|
2957
|
+
// ① apply 期间 tools 服务还没 ACTIVE —— 注册等于扔进空处;
|
|
2958
|
+
// ② 某个注册抛错,把同一段里后面的注册和收尾一起带走;
|
|
2959
|
+
// ③ 注册成功了,之后整个 effect scope 被回收 —— 只有"注册了 N 个"这条日志能反推。
|
|
2960
|
+
// 所以每个工具逐项记名、逐项兜错,结果同时挂到 DIAGNOSTICS_PATH 上供远端取证。
|
|
2961
|
+
const toolRegistrations = []
|
|
2962
|
+
const pendingToolRegistrations = []
|
|
2963
|
+
let pendingFlushRequested = false, toolReportLogged = false
|
|
2964
|
+
|
|
2965
|
+
/** 取工具登记表本身;取不到返回 undefined(不抛),调用方据此走挂起路径。 */
|
|
2966
|
+
function toolsService() {
|
|
2967
|
+
const usable = (value) => value !== null && value !== undefined && typeof value.register === 'function'
|
|
2968
|
+
// 两条路都要试:真实 Cordis 走 service 解析(ctx.get),而宿主注入的 ctx 上
|
|
2969
|
+
// tools 也可能是普通属性 —— 只看其中一条会把"可用"误判成"没就绪"。
|
|
2970
|
+
try {
|
|
2971
|
+
if (typeof ctx.get === 'function' && usable(ctx.get('tools'))) return ctx.get('tools')
|
|
2972
|
+
} catch (error) {
|
|
2973
|
+
// 解析本身失败(服务未 ACTIVE)不该中断:接着看直接属性。
|
|
2974
|
+
}
|
|
2975
|
+
try {
|
|
2976
|
+
return usable(ctx.tools) ? ctx.tools : undefined
|
|
2977
|
+
} catch (error) {
|
|
2978
|
+
return undefined
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
function registerHostTool(tool) {
|
|
2983
|
+
const named = tool !== null && tool !== undefined && typeof tool.name === 'string' && tool.name.length > 0
|
|
2984
|
+
const entry = { name: named ? tool.name : '(unnamed)', ok: false, deferred: false, error: '', disposer: undefined }
|
|
2985
|
+
toolRegistrations.push(entry)
|
|
2986
|
+
const attempt = () => {
|
|
2987
|
+
const tools = toolsService()
|
|
2988
|
+
if (tools === undefined) return false
|
|
2989
|
+
try {
|
|
2990
|
+
const disposer = tools.register(tool)
|
|
2991
|
+
entry.disposer = disposer
|
|
2992
|
+
entry.ok = typeof disposer === 'function'
|
|
2993
|
+
entry.error = entry.ok ? '' : 'register() 没有返回 disposer(tools 服务可能未 ACTIVE)'
|
|
2994
|
+
} catch (error) {
|
|
2995
|
+
entry.ok = false
|
|
2996
|
+
entry.error = messageOf(error)
|
|
2997
|
+
}
|
|
2998
|
+
if (!entry.ok) console.error('[drawai] 工具注册失败 ' + entry.name + ':' + entry.error)
|
|
2999
|
+
return true
|
|
3000
|
+
}
|
|
3001
|
+
if (!attempt()) {
|
|
3002
|
+
// tools 还没就绪:挂起,等它 ACTIVE 再补注册。这里绝不能抛 —— 抛出去会把 apply
|
|
3003
|
+
// 后半段(包括其余 17 个工具)一起带走,那正是 issue #8 里"全没了"的样子。
|
|
3004
|
+
entry.deferred = true
|
|
3005
|
+
pendingToolRegistrations.push({ entry: entry, attempt: attempt })
|
|
3006
|
+
schedulePendingFlush()
|
|
3007
|
+
}
|
|
3008
|
+
return () => {
|
|
3009
|
+
const index = pendingToolRegistrations.findIndex((item) => item.entry === entry)
|
|
3010
|
+
if (index >= 0) pendingToolRegistrations.splice(index, 1)
|
|
3011
|
+
if (typeof entry.disposer === 'function') entry.disposer()
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
/** 只申请一次;ctx.inject 的回调在 tools ACTIVE 后立刻跑(本来就 ACTIVE 就当场跑)。 */
|
|
3016
|
+
function schedulePendingFlush() {
|
|
3017
|
+
if (pendingFlushRequested) return
|
|
3018
|
+
pendingFlushRequested = true
|
|
3019
|
+
try {
|
|
3020
|
+
ctx.inject(['tools'], () => {
|
|
3021
|
+
for (const item of pendingToolRegistrations.splice(0)) item.attempt()
|
|
3022
|
+
reportToolRegistrations(true)
|
|
3023
|
+
})
|
|
3024
|
+
} catch (error) {
|
|
3025
|
+
console.error('[drawai] 等待 tools 服务就绪失败:' + messageOf(error))
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
function reportToolRegistrations(final) {
|
|
3030
|
+
const registered = toolRegistrations.filter((item) => item.ok).map((item) => item.name)
|
|
3031
|
+
const failed = toolRegistrations.filter((item) => !item.ok && !item.deferred)
|
|
3032
|
+
const waiting = toolRegistrations.filter((item) => item.deferred).length
|
|
3033
|
+
if (waiting > 0 && final !== true) {
|
|
3034
|
+
console.error('[drawai] ' + registered.length + ' 个 host 工具已注册,' + waiting + ' 个等待 tools 服务就绪')
|
|
3035
|
+
return
|
|
3036
|
+
}
|
|
3037
|
+
if (toolReportLogged) return
|
|
3038
|
+
toolReportLogged = true
|
|
3039
|
+
if (failed.length === 0) console.error('[drawai] 已注册 ' + registered.length + ' 个 host 工具:' + registered.join('、'))
|
|
3040
|
+
else console.error('[drawai] host 工具注册 ' + registered.length + '/' + toolRegistrations.length + ' 成功,失败 ' + failed.length + ' 个:' + failed.map((item) => item.name + '(' + item.error + ')').join(';'))
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
// 注册成功、模型侧却看不见时,这条日志是"插件被卸载 / 上下文被回收"唯一的线索。
|
|
3044
|
+
ctx.effect(() => () => {
|
|
3045
|
+
const registered = toolRegistrations.filter((item) => item.ok).length
|
|
3046
|
+
if (registered > 0) console.error('[drawai] 插件上下文释放:' + registered + ' 个已注册 host 工具随之注销')
|
|
3047
|
+
})
|
|
3048
|
+
|
|
2871
3049
|
function sessionOf(sessionId) {
|
|
2872
3050
|
try {
|
|
2873
3051
|
if (typeof sessionId !== 'string' || sessionId.length === 0) return undefined
|
|
@@ -3053,7 +3231,9 @@ export function apply(ctx) {
|
|
|
3053
3231
|
type: 'boolean',
|
|
3054
3232
|
description:
|
|
3055
3233
|
'**看一眼这张画布的渲染结果**(正交路由、避让、圆角、文字断线这些只有看图才知道的东西)。' +
|
|
3056
|
-
'
|
|
3234
|
+
'渲染在浏览器那一半做,只有用户**在右侧栏打开着这张画布**时才有人接:没打开会回 look.status="not-open"(别重试);' +
|
|
3235
|
+
'打开了第一次调用回 look.status="pending"(已请求,带 waitedMs),**等几秒再调一次**就会带上图片块;' +
|
|
3236
|
+
'挂着的请求 25 秒没人接就变成 look.status="timeout"(带已等待秒数),别傻等。' +
|
|
3057
3237
|
'图片经附件通道直接进上下文 —— **不会在你的工作区里生成任何文件**。' +
|
|
3058
3238
|
'只在确实需要"看"的时候用(判断重叠/压线/观感),纯粹改图不需要它。',
|
|
3059
3239
|
},
|
|
@@ -3103,7 +3283,8 @@ export function apply(ctx) {
|
|
|
3103
3283
|
error: { type: 'string' },
|
|
3104
3284
|
},
|
|
3105
3285
|
},
|
|
3106
|
-
// 「看一眼」的状态(只有传了 render:true 才有):
|
|
3286
|
+
// 「看一眼」的状态(只有传了 render:true 才有):
|
|
3287
|
+
// pending / ready / unsupported / failed / not-open / timeout。
|
|
3107
3288
|
// 图片本身不走 schema,而是走 render 的输出块(见下面的 render())。
|
|
3108
3289
|
look: {
|
|
3109
3290
|
type: 'object',
|
|
@@ -3112,6 +3293,8 @@ export function apply(ctx) {
|
|
|
3112
3293
|
status: { type: 'string', required: true },
|
|
3113
3294
|
stale: { type: 'boolean' },
|
|
3114
3295
|
error: { type: 'string' },
|
|
3296
|
+
// 已经等了多久(毫秒):pending 时是"已挂请求多久",timeout 时是"没人接多久"。
|
|
3297
|
+
waitedMs: { type: 'number' },
|
|
3115
3298
|
},
|
|
3116
3299
|
},
|
|
3117
3300
|
// 这张图的附件引用(有图才有)。render() 用它拼出 `{type:'image', attachment}` 块。
|
|
@@ -3328,11 +3511,21 @@ export function apply(ctx) {
|
|
|
3328
3511
|
if (value.look !== undefined && value.look !== null && typeof value.look.status === 'string') {
|
|
3329
3512
|
const look = value.look
|
|
3330
3513
|
if (look.status === 'pending') {
|
|
3331
|
-
|
|
3514
|
+
const waited = Number(look.waitedMs)
|
|
3515
|
+
lines.push(
|
|
3516
|
+
' 👁 已请求渲染这张画布' + (Number.isFinite(waited) && waited > 0 ? '(已等 ' + Math.round(waited / 1000) + ' 秒)' : '') +
|
|
3517
|
+
':画布几秒内渲好,**再调一次 diagram_read(render:true)**就能看到图',
|
|
3518
|
+
)
|
|
3332
3519
|
} else if (look.status === 'ready') {
|
|
3333
3520
|
lines.push(' 👁 渲染结果在图块里' + (look.stale === true ? '(**注意:这是你请求渲染时的样子,之后文件又被改过**)' : ''))
|
|
3334
3521
|
} else if (look.status === 'unsupported') {
|
|
3335
3522
|
lines.push(' 👁 看不了图:' + String(look.error === undefined ? '这个部署没有可用的图片通道' : look.error))
|
|
3523
|
+
} else if (look.status === 'not-open' || look.status === 'timeout') {
|
|
3524
|
+
// 这两种都不是"渲坏了",而是"根本没人在渲":别让模型以为再等等就好了。
|
|
3525
|
+
lines.push(
|
|
3526
|
+
' 👁 ' + (look.status === 'not-open' ? '画布没打开,图不会渲出来:' : '渲染请求没人接(超时):') +
|
|
3527
|
+
String(look.error === undefined ? '请先在右侧栏打开这张画布' : look.error),
|
|
3528
|
+
)
|
|
3336
3529
|
} else {
|
|
3337
3530
|
lines.push(' 👁 渲染失败:' + String(look.error === undefined ? '未知原因' : look.error))
|
|
3338
3531
|
}
|
|
@@ -3502,8 +3695,9 @@ export function apply(ctx) {
|
|
|
3502
3695
|
const exportState = exportStateFor(sessionIdOf(exec), loaded.absolute)
|
|
3503
3696
|
// 「看一眼」(render:true):交图,或挂一个渲染请求。
|
|
3504
3697
|
//
|
|
3505
|
-
//
|
|
3506
|
-
//
|
|
3698
|
+
// 四道门依次过:附件服务在不在(图片的落脚处)→ 模型声明不声明图片输入 → 手上有没有渲好的图
|
|
3699
|
+
// → 画布是不是真开着(渲染在浏览器里做,没打开就没人接请求)。
|
|
3700
|
+
// 过不去时**给一句人话**(而不是抛错)—— AI 还能照常读几何、改图,只是看不到图。
|
|
3507
3701
|
let lookState = undefined
|
|
3508
3702
|
let imageValue = undefined
|
|
3509
3703
|
if (args.render === true) {
|
|
@@ -3526,18 +3720,45 @@ export function apply(ctx) {
|
|
|
3526
3720
|
imageValue = taken.image
|
|
3527
3721
|
}
|
|
3528
3722
|
} else {
|
|
3529
|
-
//
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3723
|
+
// 渲染在**浏览器**里做:只有用户正打开着这张画布时客户端才会接这个请求。
|
|
3724
|
+
// 宿主知道用户看的是**别的**画布时就直接说清楚(not-open)—— 否则会挂一个
|
|
3725
|
+
// 永远等不到回执的请求,模型只能反复重试(issue #12)。聚焦未知时仍按老规矩挂
|
|
3726
|
+
// pending:刚打开的画布可能还没来得及上报,误判成"没开"会更让人困惑。
|
|
3727
|
+
const focusedAbsolute = await focusedAbsoluteOf(sessionIdOf(exec))
|
|
3728
|
+
if (focusedAbsolute !== undefined && samePath(focusedAbsolute, loaded.absolute) === false) {
|
|
3729
|
+
lookState = {
|
|
3730
|
+
status: 'not-open',
|
|
3731
|
+
error:
|
|
3732
|
+
'看不了这张画布的渲染图:右侧栏现在打开的是 ' + focusedAbsolute +
|
|
3733
|
+
',渲染请求没有画布接。先把这张画布打开再调一次;只要几何数据的话去掉 render:true。',
|
|
3734
|
+
}
|
|
3735
|
+
} else if (state !== undefined && state.status === 'timeout') {
|
|
3736
|
+
lookState = {
|
|
3737
|
+
status: 'timeout',
|
|
3738
|
+
waitedMs: state.waitedMs,
|
|
3739
|
+
error:
|
|
3740
|
+
'渲染请求挂了 ' + Math.round(state.waitedMs / 1000) + ' 秒没人接:多半是右侧栏没打开这张画布(或画布与宿主的连接断了)。' +
|
|
3741
|
+
'先打开它再调一次;只想要几何数据的话去掉 render:true。',
|
|
3742
|
+
}
|
|
3743
|
+
} else {
|
|
3744
|
+
// 同一张画布已有请求就沿用(客户端按 requestId 去重,换 id 会让它再渲一遍)。
|
|
3745
|
+
const existing = renderRequests.get(sessionIdOf(exec))
|
|
3746
|
+
if (existing === undefined || existing.absolute !== loaded.absolute) {
|
|
3747
|
+
// 换了画布:上一张渲好的图作废(否则会把 A 图的画面当 B 图交给模型)。
|
|
3748
|
+
renderedImages.delete(sessionIdOf(exec))
|
|
3749
|
+
renderRequests.set(sessionIdOf(exec), {
|
|
3750
|
+
requestId: 'r' + Date.now().toString(36) + Math.random().toString(36).slice(2, 8),
|
|
3751
|
+
absolute: loaded.absolute,
|
|
3752
|
+
revision: doc.revision,
|
|
3753
|
+
// 请求挂上的时刻:UI 那半边一直不回执时,靠它算"已经等了多久"(见 renderStateFor 的超时)。
|
|
3754
|
+
at: Date.now(),
|
|
3755
|
+
})
|
|
3756
|
+
}
|
|
3757
|
+
lookState = {
|
|
3758
|
+
status: 'pending',
|
|
3759
|
+
waitedMs: state !== undefined && state.status === 'pending' ? state.waitedMs : 0,
|
|
3760
|
+
}
|
|
3539
3761
|
}
|
|
3540
|
-
lookState = { status: 'pending' }
|
|
3541
3762
|
}
|
|
3542
3763
|
}
|
|
3543
3764
|
}
|
|
@@ -4023,7 +4244,11 @@ export function apply(ctx) {
|
|
|
4023
4244
|
// mode 回落到 'dagre-tb',人手工摆好的版面被整张重排 —— 正是上面那个判断要防的事。
|
|
4024
4245
|
// 教训:用整体赋值覆盖 meta 时,"没被显式处理"的字段会静默消失;新增 meta 字段时先看这里。
|
|
4025
4246
|
const routingResult = documentRouter.update(routingBefore, doc)
|
|
4026
|
-
if (routingResult.
|
|
4247
|
+
if (routingResult.unavailable !== undefined) {
|
|
4248
|
+
// 引擎整体不可用(最常见:从 git 装的包少了 lib/routing/vendor/*)时,写回照常进行,
|
|
4249
|
+
// 只是这次没有自动路线。回执里必须说清楚,否则模型会以为"改完就是没走线"。
|
|
4250
|
+
notes.push('~ 自动避障不可用(' + routingResult.unavailable + '):这次保留现有折点,新画的边按直线写回')
|
|
4251
|
+
} else if (routingResult.failed.length) notes.push('~ 自动避障暂无可用通道:' + routingResult.failed.join(', '))
|
|
4027
4252
|
doc.meta = Object.assign({}, doc.meta, { layout: mode }, pinned ? { pinned: true } : {})
|
|
4028
4253
|
const rendered = renderDocText(loaded.text, doc, { page: loaded.pageIndex, format: isNativeCanvasPath(loaded.absolute) ? 'drawai' : 'drawio' })
|
|
4029
4254
|
doc.revision = rendered.revision
|
|
@@ -4143,6 +4368,14 @@ function sanitizeNewName(raw) {
|
|
|
4143
4368
|
const renderedImages = new Map()
|
|
4144
4369
|
/** 渲染结果的保鲜期:超过它就不再交付(要新的请重新请求一次,免得给模型看一张老图)。 */
|
|
4145
4370
|
const RENDER_TTL_MS = 5 * 60 * 1000
|
|
4371
|
+
/**
|
|
4372
|
+
* 挂着的渲染请求多久没人接就算超时。
|
|
4373
|
+
*
|
|
4374
|
+
* 渲染在浏览器里做:用户没打开画布时这条请求**根本没人接**,而旧代码只会一直回 pending
|
|
4375
|
+
* (issue #12:请求没有过期,模型分不清"正在渲"和"没人渲")。25 秒足够大画布渲完,
|
|
4376
|
+
* 超过它就该如实说"没人接"而不是让模型反复重试。
|
|
4377
|
+
*/
|
|
4378
|
+
const RENDER_PENDING_TTL_MS = 25 * 1000
|
|
4146
4379
|
|
|
4147
4380
|
/** 附件服务(可选依赖):这个部署没装它就没法"看图",但插件其余功能照旧。 */
|
|
4148
4381
|
function attachmentsService() {
|
|
@@ -4196,13 +4429,21 @@ function sanitizeNewName(raw) {
|
|
|
4196
4429
|
/**
|
|
4197
4430
|
* 「看图」这条路当前的可用性 + 待交付的图。
|
|
4198
4431
|
*
|
|
4199
|
-
* 状态:`pending
|
|
4432
|
+
* 状态:`pending`(已请求,等画布渲染,带 waitedMs)/ `ready`(有图,这一次带回模型)/
|
|
4433
|
+
* `timeout`(挂了超过 RENDER_PENDING_TTL_MS 没人接,带 waitedMs —— 多半是画布没打开)/
|
|
4200
4434
|
* `unsupported`(这个部署/模型看不了图,附一句为什么)/ `failed`(渲染回执说失败了)。
|
|
4201
4435
|
*/
|
|
4202
4436
|
function renderStateFor(sessionId, absolute, revision) {
|
|
4203
4437
|
if (typeof sessionId !== 'string' || sessionId.length === 0) return undefined
|
|
4204
4438
|
const pending = renderRequests.get(sessionId)
|
|
4205
|
-
if (pending !== undefined && pending.absolute
|
|
4439
|
+
if (pending !== undefined && samePath(pending.absolute, absolute)) {
|
|
4440
|
+
const since = Number(pending.at)
|
|
4441
|
+
const waitedMs = Number.isFinite(since) ? Math.max(0, Date.now() - since) : 0
|
|
4442
|
+
// 不删这条请求:迟到一步的回执还要按 requestId 配回来(删了就永远配不上)。
|
|
4443
|
+
// 只是不再说"pending"——模型至少知道"没人接",而不是"正在渲"。
|
|
4444
|
+
if (waitedMs > RENDER_PENDING_TTL_MS) return { status: 'timeout', waitedMs: waitedMs }
|
|
4445
|
+
return { status: 'pending', waitedMs: waitedMs }
|
|
4446
|
+
}
|
|
4206
4447
|
const done = renderedImages.get(sessionId)
|
|
4207
4448
|
if (done === undefined || done.absolute !== absolute) return undefined
|
|
4208
4449
|
if (Date.now() - done.at > RENDER_TTL_MS) {
|
|
@@ -4351,6 +4592,33 @@ function sanitizeNewName(raw) {
|
|
|
4351
4592
|
return focusedCanvas.get(sessionId)
|
|
4352
4593
|
}
|
|
4353
4594
|
|
|
4595
|
+
/** 两个绝对路径是不是同一个文件(Windows 大小写不敏感、斜杠两种都认)。 */
|
|
4596
|
+
function samePath(a, b) {
|
|
4597
|
+
if (a === b) return true
|
|
4598
|
+
if (typeof a !== 'string' || typeof b !== 'string') return false
|
|
4599
|
+
if (process.platform !== 'win32') return false
|
|
4600
|
+
return a.replace(/\//g, '\\').toLowerCase() === b.replace(/\//g, '\\').toLowerCase()
|
|
4601
|
+
}
|
|
4602
|
+
|
|
4603
|
+
/**
|
|
4604
|
+
* 用户此刻打开的那张画布的**绝对路径**(归一后);没上报过 / 解析不出来 → undefined。
|
|
4605
|
+
*
|
|
4606
|
+
* 只给「看图」的门禁用(diagram_read 的 render:true 分支):渲染发生在浏览器里,只有**用户正打开着**
|
|
4607
|
+
* 这张画布时客户端才接请求,否则请求永远挂着(issue #12)。聚焦路径是客户端上报的、
|
|
4608
|
+
* 可能是相对工作区的,所以这里统一 resolve + processPath 成与 `loaded.absolute` 同一套形状。
|
|
4609
|
+
*/
|
|
4610
|
+
async function focusedAbsoluteOf(sessionId) {
|
|
4611
|
+
const raw = focusedPathFor(sessionId)
|
|
4612
|
+
if (raw === undefined || raw.length === 0) return undefined
|
|
4613
|
+
const root = workspaceRootOf(sessionId)
|
|
4614
|
+
if (root === undefined) return undefined
|
|
4615
|
+
try {
|
|
4616
|
+
return ctx.fs.processPath(await ctx.fs.resolve(raw, { cwd: root }))
|
|
4617
|
+
} catch (error) {
|
|
4618
|
+
return undefined
|
|
4619
|
+
}
|
|
4620
|
+
}
|
|
4621
|
+
|
|
4354
4622
|
/** 用户画布当前页(0 起);没上报过则 0。 */
|
|
4355
4623
|
function focusedPageFor(sessionId) {
|
|
4356
4624
|
if (typeof sessionId !== 'string' || sessionId.length === 0) return 0
|
|
@@ -4917,8 +5185,10 @@ function sanitizeNewName(raw) {
|
|
|
4917
5185
|
sendJson(res, 400, { ok: false, error: 'resolve failed: ' + messageOf(error) })
|
|
4918
5186
|
return
|
|
4919
5187
|
}
|
|
5188
|
+
// rootTarget 两个 try 都要用(包含校验 + 响应里的 root 字段),声明放到 if 块这一层。
|
|
5189
|
+
let rootTarget
|
|
4920
5190
|
try {
|
|
4921
|
-
|
|
5191
|
+
rootTarget = await ctx.fs.resolve(root)
|
|
4922
5192
|
if (!ctx.fs.contains(rootTarget, dirTarget)) {
|
|
4923
5193
|
sendJson(res, 403, { ok: false, error: '目录不在工作区内:' + absoluteHint(dirTarget, dir) })
|
|
4924
5194
|
return
|
|
@@ -4932,15 +5202,18 @@ function sanitizeNewName(raw) {
|
|
|
4932
5202
|
// 同时给出**绝对路径**:客户端按路径去重(同一个文件不该开出两个标签),
|
|
4933
5203
|
// 而标签上的路径可能来自 tab 的资源地址(绝对),与列表里的相对路径不相等。
|
|
4934
5204
|
// 让客户端用绝对路径当"身份",就不会重复开标签。
|
|
5205
|
+
// listed.files 里的相对路径是**相对 dir** 的,必须拼回 dir —— 拼到工作区根会指到
|
|
5206
|
+
// 一个不存在的同名文件(issue #19:子目录里的画布「列得出、打不开」)。
|
|
5207
|
+
const dirAbs = absoluteHint(dirTarget, dir)
|
|
4935
5208
|
const abs = []
|
|
4936
|
-
for (let i = 0; i < listed.files.length; i += 1) abs.push(toAbsolute(listed.files[i],
|
|
5209
|
+
for (let i = 0; i < listed.files.length; i += 1) abs.push(toAbsolute(listed.files[i], dirAbs))
|
|
4937
5210
|
sendJson(res, 200, {
|
|
4938
5211
|
ok: true,
|
|
4939
5212
|
files: listed.files,
|
|
4940
5213
|
absolute: abs,
|
|
4941
5214
|
notes: listed.notes,
|
|
4942
|
-
root: absoluteHint(
|
|
4943
|
-
dir:
|
|
5215
|
+
root: absoluteHint(rootTarget, root),
|
|
5216
|
+
dir: dirAbs,
|
|
4944
5217
|
})
|
|
4945
5218
|
} catch (error) {
|
|
4946
5219
|
sendJson(res, 500, { ok: false, error: 'list failed: ' + messageOf(error) })
|
|
@@ -4956,7 +5229,7 @@ function sanitizeNewName(raw) {
|
|
|
4956
5229
|
files: listed.files,
|
|
4957
5230
|
absolute: abs,
|
|
4958
5231
|
notes: listed.notes,
|
|
4959
|
-
root: absoluteHint(root, root),
|
|
5232
|
+
root: absoluteHint(await ctx.fs.resolve(root), root),
|
|
4960
5233
|
})
|
|
4961
5234
|
} catch (error) {
|
|
4962
5235
|
sendJson(res, 500, { ok: false, error: 'list failed: ' + messageOf(error) })
|
|
@@ -6189,9 +6462,27 @@ function sanitizeNewName(raw) {
|
|
|
6189
6462
|
sendJson(res, 200, { ok: true, revision: rendered.revision, path: absolute, dropped: rendered.dropped, ...(backgroundPatch ? { backgroundPatch } : {}) })
|
|
6190
6463
|
}
|
|
6191
6464
|
|
|
6465
|
+
/**
|
|
6466
|
+
* 只读诊断:不碰画布、不改文件,只回答"这个进程里到底注册上了哪几个 host 工具"。
|
|
6467
|
+
* issue #8 那种"画布能用、工具全没"的现场,把这个 URL 的 JSON 贴回来就够了。
|
|
6468
|
+
*/
|
|
6469
|
+
function handleDiagnostics(_req, res) {
|
|
6470
|
+
sendJson(res, 200, {
|
|
6471
|
+
ok: true,
|
|
6472
|
+
plugin: 'dsh-drawai',
|
|
6473
|
+
toolsService: toolsService() !== undefined,
|
|
6474
|
+
registered: toolRegistrations.filter((item) => item.ok).map((item) => item.name),
|
|
6475
|
+
pending: toolRegistrations.filter((item) => item.deferred).map((item) => item.name),
|
|
6476
|
+
failed: toolRegistrations.filter((item) => !item.ok && !item.deferred).map((item) => ({ name: item.name, error: item.error })),
|
|
6477
|
+
})
|
|
6478
|
+
}
|
|
6479
|
+
|
|
6480
|
+
// 诊断路由排在写回路由之前:自测按 routes[last] 取写回路由,这个顺序不能反过来。
|
|
6481
|
+
ctx.effect(() => ctx.webServer.register({ kind: 'exact', path: DIAGNOSTICS_PATH, handler: handleDiagnostics }), 'drawai: ' + DIAGNOSTICS_PATH)
|
|
6482
|
+
|
|
6192
6483
|
ctx.effect(() => ctx.webServer.register({ kind: 'exact', path: SAVE_PATH, handler: handleSave }), 'drawai: ' + SAVE_PATH)
|
|
6193
6484
|
|
|
6194
|
-
ctx.effect(() =>
|
|
6485
|
+
ctx.effect(() => registerHostTool(defineTool({
|
|
6195
6486
|
name: 'shotcraft_search',
|
|
6196
6487
|
description: '搜索 DrawAI 内置的 124 张 Shotcraft 镜头卡 / 150 个准确样式。按关键词(中英文卡名、样式、用途)和分类检索;返回唯一 demo、样片及可用于 diagram_apply 的节点参数。先搜索再添加 shape:shotcard 节点并连到视频节点,支持同时参考多卡。',
|
|
6197
6488
|
parameters: { query: { type: 'string' }, category: { type: 'string' }, cardId: { type: 'string', description: '精确卡名,查看该卡所有样式;配合 styleKey 会直接返回这张卡的配方与 demo 源码' },
|
|
@@ -6218,7 +6509,7 @@ function sanitizeNewName(raw) {
|
|
|
6218
6509
|
styles: matches.slice(offset, offset + limit).map((item) => ({ ...withPath(item), node: { op: 'addNode', shape: 'shotcard', cardId: item.cardId, styleKey: item.styleKey } })) }) }
|
|
6219
6510
|
},
|
|
6220
6511
|
})))
|
|
6221
|
-
ctx.effect(() =>
|
|
6512
|
+
ctx.effect(() => registerHostTool(defineTool({
|
|
6222
6513
|
name: 'mg_style',
|
|
6223
6514
|
description: 'DrawAI 内置的 14 种整片 MG 动效风格(mg-styles 库):扁平矢量 / 线条动画 / 等轴 2.5D / 逐帧手绘 / 拼贴 / 液态 / 形变 / 包豪斯 / 80s 复古 / 玻璃拟态 / 综艺花字 / 粗描边贴纸科普 / 像素 / 赛博 HUD。上游那支 04-3d-render(C4D/Blender 质感的 CGI 软着陆)没有收录:画面是 Blender 离线渲的,插件里没有同等质感的等价物——用 CSS 滤镜凑出来的 CGI 只会更假,需要那种质感就外部渲好当素材回灌(motion_assets capture / 参考段)。这是「整片风格」(一种片子的整体视觉语言),与 shotcraft_search 的「单镜动效卡」互补、可同时用。action:"list" 列出全部;"search" 按关键词检索;"read" 读某个风格的施工包(可检验判据 + 色板 + 字体 + 节奏 + 效果配方 + 上游提示词 + Remotion 移植 brief);"source" 读上游成片源码正文;**"frames" 从上游 demo 按 cue 时间点抽参考帧**——动态、转场、冲击、颗粒这些只有看帧才知道,写场景前先取。选中后 diagram_apply 加 shape:"mgstyle" 节点并连到视频节点,motion_project 会把风格写进工程主题与 STYLE.md。',
|
|
6224
6515
|
parameters: { action: { type: 'string', enum: ['list', 'search', 'read', 'source', 'frames'], required: true },
|
|
@@ -6283,10 +6574,10 @@ function sanitizeNewName(raw) {
|
|
|
6283
6574
|
throw new Error('未知 action:' + action)
|
|
6284
6575
|
},
|
|
6285
6576
|
})))
|
|
6286
|
-
ctx.effect(() =>
|
|
6577
|
+
ctx.effect(() => registerHostTool(defineTool({
|
|
6287
6578
|
name:'audio_asset',
|
|
6288
6579
|
description:'为画布搜索和导入音频。search 从 Freesound 搜索;get 按 fs:数字 下载并创建音频节点;import 从当前工作区导入 mp3/wav/m4a/aac/ogg/flac/webm,32MB/10分钟以内;targetId 可自动连接视频。configure 修改音频节点的 audioRole(sfx/bgm/voice)、audioGainDb、audioTrimStart/End(秒,End=0至末尾)、audioAt(全片秒)、audioFadeIn/Out(秒)、audioLoop(字符串0/1)。inspect 返回素材来源、哈希和检测结果。音频需要连接到视频;motion_task settings.audioMode=auto让AI安排镜头声音,manual使用节点参数或audioPlan,off静音;remix只重混已有成片。没有真实试听不得宣称听感通过。',
|
|
6289
|
-
parameters:{action:{type:'string',enum:['search','get','import','configure','inspect'],required:true},path:{type:'string'},page:{type:'number'},query:{type:'string'},limit:{type:'number'},id:{type:'string'},file:{type:'string'},nodeId:{type:'string'},targetId:{type:'string'},role:{type:'string',enum:['sfx','bgm','voice']},settings:{type:'
|
|
6580
|
+
parameters:{action:{type:'string',enum:['search','get','import','configure','inspect'],required:true},path:{type:'string'},page:{type:'number'},query:{type:'string'},limit:{type:'number'},id:{type:'string'},file:{type:'string'},nodeId:{type:'string'},targetId:{type:'string'},role:{type:'string',enum:['sfx','bgm','voice']},settings:{type:'object',additionalProperties:false,properties:{audioRole:{type:'string',enum:['sfx','bgm','voice']},audioGainDb:{type:'number'},audioTrimStart:{type:'number'},audioTrimEnd:{type:'number'},audioAt:{type:'number'},audioFadeIn:{type:'number'},audioFadeOut:{type:'number'},audioLoop:{type:'string',enum:['0','1']}},description:'configure:要改的音频设置,键名就是节点 data 里的名字(audioRole / audioGainDb / audioTrimStart…)'},x:{type:'number'},y:{type:'number'}},
|
|
6290
6581
|
output:{schema:{type:'object',additionalProperties:false,properties:{summary:{type:'string',required:true},details:{type:'string',required:true}}},render:(_args,value)=>[{type:'text',text:value.summary+'\n'+value.details}]},
|
|
6291
6582
|
async execute(args,exec){
|
|
6292
6583
|
const sessionId=sessionIdOf(exec),path=args.path||focusedPathFor(sessionId)
|
|
@@ -6330,8 +6621,8 @@ function sanitizeNewName(raw) {
|
|
|
6330
6621
|
})
|
|
6331
6622
|
}
|
|
6332
6623
|
})))
|
|
6333
|
-
ctx.effect(() =>
|
|
6334
|
-
ctx.effect(() =>
|
|
6624
|
+
ctx.effect(() => registerHostTool(readTool))
|
|
6625
|
+
ctx.effect(() => registerHostTool(defineTool({
|
|
6335
6626
|
name: 'motion_task',
|
|
6336
6627
|
description: '画布视频节点的任务记录:list 列出任务、status 查某个任务、cancel 取消、focus 在面板定位、remove 移除记录。开始制作请走 motion_project → 改 scene-*.tsx → motion_preview → motion_render;成片回填节点用 motion_render apply。',
|
|
6337
6628
|
parameters: {
|
|
@@ -6505,7 +6796,7 @@ function sanitizeNewName(raw) {
|
|
|
6505
6796
|
}
|
|
6506
6797
|
}
|
|
6507
6798
|
|
|
6508
|
-
ctx.effect(() =>
|
|
6799
|
+
ctx.effect(() => registerHostTool(defineTool({
|
|
6509
6800
|
name: 'motion_project',
|
|
6510
6801
|
description: '视频工程:把画布里的一个视频节点落成磁盘上的小文件工程(timeline.json + scene-<n>.tsx + assets/ + theme.ts)。create 建工程并返回文件清单与分镜表,之后用 read/edit/write 直接改场景文件;open 重新读时间轴、文件清单和主题;theme 换配色(重写 theme.ts,场景里用它的常量就一起变)。这里不启动任何模型,也不渲染。按用户要求确定风格和素材;除非用户要求跳过,先制作 3–6 秒样片并回填到独立的样片视频节点,用户要求先审样时等待反馈。create 前镜头卡必须 addEdge 连到视频节点:连线同时是参考、镜头顺序和段长,画布上有断连的卡会直接报错。',
|
|
6511
6802
|
parameters: {
|
|
@@ -6513,12 +6804,29 @@ function sanitizeNewName(raw) {
|
|
|
6513
6804
|
path: { type: 'string', description: '画布路径,省略时使用当前画布' },
|
|
6514
6805
|
nodeId: { type: 'string', required: true, description: '视频节点 id' },
|
|
6515
6806
|
page: { type: 'number' },
|
|
6516
|
-
spec: {
|
|
6807
|
+
spec: {
|
|
6808
|
+
type: 'object',
|
|
6809
|
+
additionalProperties: true,
|
|
6810
|
+
properties: { width: { type: 'number' }, height: { type: 'number' }, fps: { type: 'number' }, duration: { type: 'number' } },
|
|
6811
|
+
description: '视频规格 {width,height,fps,duration}(像素与秒),省略时沿用节点设置',
|
|
6812
|
+
},
|
|
6517
6813
|
prompt: { type: 'string', description: '这条视频要什么,会写进 BRIEF.md' },
|
|
6518
6814
|
segments: { type: 'number', description: '初始分几段,默认 4' },
|
|
6519
|
-
storyboard: {
|
|
6815
|
+
storyboard: {
|
|
6816
|
+
type: 'array',
|
|
6817
|
+
items: { type: 'json' },
|
|
6818
|
+
description: '分镜表:[{intent:"这一镜要做什么", onScreen:"画面上出现什么", copy:"配字"}],按镜头顺序;也可以给 ["一句话"] 简写。会写成 storyboard.md。',
|
|
6819
|
+
},
|
|
6520
6820
|
theme: { type: 'string', description: '配色主题名:vintage-kraft / deep-ocean / obsidian-violet / modern-light / midnight / solar-pop / coral-burst / color-play / paper-ink。给了就在工程里生成 theme.ts,场景用 {{PAPER, INK, DUST, STAMP}} 引用。' },
|
|
6521
|
-
themeTokens: {
|
|
6821
|
+
themeTokens: {
|
|
6822
|
+
type: 'object',
|
|
6823
|
+
additionalProperties: true,
|
|
6824
|
+
properties: {
|
|
6825
|
+
page: { type: 'string' }, text: { type: 'string' }, muted: { type: 'string' }, accent: { type: 'string' },
|
|
6826
|
+
surface: { type: 'string' }, field: { type: 'string' }, border: { type: 'string' },
|
|
6827
|
+
},
|
|
6828
|
+
description: '直接给一套色 {page,text,muted,accent,surface,field,border}(#RRGGBB,优先于 theme;也认 paper/card/ink 这些别名)。',
|
|
6829
|
+
},
|
|
6522
6830
|
style: { type: 'string', description: '整片风格 slug(mg_style list 里的 slug,如 09-bauhaus)。给了就用这个风格:色板当主题、节奏当规格缺省、施工说明与引擎契约写进 STYLE.md。也可以不传,改为在画布上连一个「整片风格」节点。' },
|
|
6523
6831
|
allowUnlinkedCards: { type: 'boolean', description: 'create 时允许画布上存在没连线的镜头卡(当作灵感,不进 assets/timeline)。默认不允许:断连的卡会同时漏掉参考与排期,所以直接报错,让你补 addEdge。' },
|
|
6524
6832
|
},
|
|
@@ -6532,10 +6840,15 @@ function sanitizeNewName(raw) {
|
|
|
6532
6840
|
const node = loaded.doc.nodes.find((item) => item.id === args.nodeId)
|
|
6533
6841
|
if (!node) throw new Error('找不到视频节点 ' + args.nodeId)
|
|
6534
6842
|
const dir = projectDirOf(node, scope)
|
|
6843
|
+
// json 参数有可能是字符串(见 jsonValue 注释),先统一还原,后面只读这几个局部量。
|
|
6844
|
+
const specArg = jsonObject(args.spec)
|
|
6845
|
+
const styleArg = jsonValue(args.style)
|
|
6846
|
+
const storyboardArg = jsonValue(args.storyboard)
|
|
6847
|
+
const themeTokensArg = jsonValue(args.themeTokens)
|
|
6535
6848
|
if (args.action === 'theme') {
|
|
6536
6849
|
if (policyFor(scope.sessionId)?.mode === 'read-only') throw new Error('当前会话为只读')
|
|
6537
6850
|
if (!existsSync(dir)) throw new Error('工程还没建:先 action:"create"')
|
|
6538
|
-
const resolved = resolveTheme(
|
|
6851
|
+
const resolved = resolveTheme(themeTokensArg || args.theme)
|
|
6539
6852
|
if (!resolved) throw new Error('请给 theme(主题名)或 themeTokens(一套色)。可选主题:' + listThemes().map((item) => item.name).join('、'))
|
|
6540
6853
|
const written = writeTheme(dir, resolved)
|
|
6541
6854
|
return { summary: '已换主题:' + written.name, details: JSON.stringify({ dir, theme: written, themes: listThemes() }) }
|
|
@@ -6547,10 +6860,10 @@ function sanitizeNewName(raw) {
|
|
|
6547
6860
|
}
|
|
6548
6861
|
if (!isMotionNode(node)) throw new Error('请选择空视频或绘制视频节点')
|
|
6549
6862
|
const before = { data: JSON.parse(JSON.stringify(node.data)), style: node.style }
|
|
6550
|
-
const style =
|
|
6863
|
+
const style = styleArg ? resolveMgStyle(mgStyleCatalog, styleArg) : mgStyleOf(loaded.doc, node.id)
|
|
6551
6864
|
const kit = style ? mgStyleKit({ slug: style.slug }) : null
|
|
6552
6865
|
// 规格优先级:风格节奏(最弱)< 节点上存的 < 这次显式给的
|
|
6553
|
-
const spec = readSpec({ ...rhythmSpec(style?.rhythm), ...(node.data.motion || {}), ...
|
|
6866
|
+
const spec = readSpec({ ...rhythmSpec(style?.rhythm), ...(node.data.motion || {}), ...specArg })
|
|
6554
6867
|
const errors = validateSpec(spec)
|
|
6555
6868
|
if (errors.length) throw new Error('视频规格不合法:' + errors.join(';'))
|
|
6556
6869
|
const cards = shotCardsOf(loaded.doc, node.id)
|
|
@@ -6566,14 +6879,14 @@ function sanitizeNewName(raw) {
|
|
|
6566
6879
|
// 这也是"分镜被拆成八条文本节点"能被发现的地方 —— readCanvasSpec 会把它们列出来。
|
|
6567
6880
|
const canvasSpec = readCanvasSpec(loaded.doc, { fps: spec.fps })
|
|
6568
6881
|
const storyboardInput = args.storyboard !== undefined
|
|
6569
|
-
?
|
|
6882
|
+
? storyboardArg
|
|
6570
6883
|
: canvasSpec.storyboard.length > 0 ? canvasSpec.storyboard : undefined
|
|
6571
6884
|
// 显式给了时长就以时长为总长:卡片之间按相对时长等比拉伸,不再被卡片总和改短。
|
|
6572
|
-
const durationGiven = Number(
|
|
6885
|
+
const durationGiven = Number(specArg.duration) > 0
|
|
6573
6886
|
// 建工程要往 assets/fonts 里拷字体:先确保素材包在(首次会下载一次)。
|
|
6574
6887
|
await ensureMotionAssets('建视频工程')
|
|
6575
6888
|
const created = createProject(dir, { spec, prompt: args.prompt ?? node.data.motion?.prompt, segments: args.segments, cards, storyboard: storyboardInput,
|
|
6576
|
-
theme:
|
|
6889
|
+
theme: themeTokensArg || args.theme, style: kit, fitFrames: durationGiven ? spec.totalFrames : 0 })
|
|
6577
6890
|
registerProject(dir)
|
|
6578
6891
|
const seconds = created.timeline.durationInFrames / created.timeline.fps
|
|
6579
6892
|
node.data.motion = { ...(node.data.motion || {}), mode: spec.mode, width: created.timeline.width, height: created.timeline.height, fps: created.timeline.fps, duration: Math.round(seconds * 100) / 100,
|
|
@@ -6624,7 +6937,7 @@ function sanitizeNewName(raw) {
|
|
|
6624
6937
|
// 以前这三块只活在对话里(或 storyboard.md 里),于是"分镜表"在画布上就变成
|
|
6625
6938
|
// **一镜一个文本节点**——八镜八条,谁也不觉得那是一张表,motion_project 也读不到。
|
|
6626
6939
|
// 这里给一个工具:建(scaffold)、读(read)、验(validate)。
|
|
6627
|
-
ctx.effect(() =>
|
|
6940
|
+
ctx.effect(() => registerHostTool(defineTool({
|
|
6628
6941
|
name: 'motion_spec',
|
|
6629
6942
|
description: '画布上的视频规格三块,用**一个节点**写清楚:\n【分镜表】一张 Markdown 六列表(镜头 | 帧区间 | 镜头卡 | 这一镜要做什么 | 屏幕上出现什么 | 文案 / 声音),每一镜一行——**不要一镜建一个节点**;\n【风格】一行一个键(page #F7F3EA / serif "Noto Serif SC" / materials: kraft);\n【旁白】`## shot-N` 分节,一句一行。\naction:"scaffold" 返回把缺的块摆到视频附近空位的 diagram_apply ops(已有一张【分镜表】就不再建第二张);"read" 读回来;"validate" 逐条报错(分镜被拆成多条、帧数之和对不上节点时长、镜号不连号…)。motion_project create 不给 storyboard 时会直接读这张表。',
|
|
6630
6943
|
parameters: {
|
|
@@ -6632,10 +6945,21 @@ function sanitizeNewName(raw) {
|
|
|
6632
6945
|
path: { type: 'string', description: '画布路径,省略时使用当前画布' },
|
|
6633
6946
|
nodeId: { type: 'string', description: '视频节点 id(规格块的锚点)' },
|
|
6634
6947
|
page: { type: 'number' },
|
|
6635
|
-
storyboard: {
|
|
6636
|
-
|
|
6948
|
+
storyboard: {
|
|
6949
|
+
oneOf: [{ type: 'array', items: { type: 'json' } }, { type: 'string' }],
|
|
6950
|
+
description: '分镜行 [{intent,onScreen,copy,card,durationFrames}],或一段 markdown 表;省略时 scaffold 只出表头',
|
|
6951
|
+
},
|
|
6952
|
+
style: {
|
|
6953
|
+
oneOf: [{ type: 'string' }, { type: 'object', additionalProperties: true }],
|
|
6954
|
+
description: '风格:一段文本,或 {palette,fonts,materials}',
|
|
6955
|
+
},
|
|
6637
6956
|
narration: { type: 'string', description: '旁白脚本(## shot-N 分节,一句一行)' },
|
|
6638
|
-
spec: {
|
|
6957
|
+
spec: {
|
|
6958
|
+
type: 'object',
|
|
6959
|
+
additionalProperties: true,
|
|
6960
|
+
properties: { width: { type: 'number' }, height: { type: 'number' }, fps: { type: 'number' }, duration: { type: 'number' } },
|
|
6961
|
+
description: '视频规格 {width,height,fps,duration}(像素与秒),省略时读节点上的',
|
|
6962
|
+
},
|
|
6639
6963
|
},
|
|
6640
6964
|
output: { schema: { type: 'object', additionalProperties: false, properties: { summary: { type: 'string', required: true }, details: { type: 'string', required: true } } },
|
|
6641
6965
|
render: (_args, value) => [{ type: 'text', text: value.summary + '\n' + value.details }] },
|
|
@@ -6644,10 +6968,10 @@ function sanitizeNewName(raw) {
|
|
|
6644
6968
|
const loaded = await loadDoc(scope.path, scope.sessionId, args.page)
|
|
6645
6969
|
const node = args.nodeId ? loaded.doc.nodes.find((item) => item.id === args.nodeId) : loaded.doc.nodes.find(isMotionNode)
|
|
6646
6970
|
if (!node) throw new Error('找不到视频节点' + (args.nodeId ? ' ' + args.nodeId : ''))
|
|
6647
|
-
const spec = readSpec({ ...(node.data?.motion || {}), ...(args.spec
|
|
6971
|
+
const spec = readSpec({ ...(node.data?.motion || {}), ...jsonObject(args.spec) })
|
|
6648
6972
|
if (args.action === 'scaffold') {
|
|
6649
6973
|
if (policyFor(scope.sessionId)?.mode === 'read-only') throw new Error('当前会话为只读')
|
|
6650
|
-
const built = scaffoldOps(loaded.doc, { videoNode: node, storyboard: args.storyboard, style: args.style,
|
|
6974
|
+
const built = scaffoldOps(loaded.doc, { videoNode: node, storyboard: jsonValue(args.storyboard), style: jsonValue(args.style),
|
|
6651
6975
|
narration: args.narration, fps: spec.fps, totalFrames: spec.totalFrames })
|
|
6652
6976
|
if (built.ops.length === 0) {
|
|
6653
6977
|
return { summary: '规格块已经齐了(' + built.already.join('、') + '),不必再建',
|
|
@@ -6681,14 +7005,22 @@ function sanitizeNewName(raw) {
|
|
|
6681
7005
|
},
|
|
6682
7006
|
})))
|
|
6683
7007
|
|
|
6684
|
-
ctx.effect(() =>
|
|
7008
|
+
ctx.effect(() => registerHostTool(defineTool({
|
|
6685
7009
|
name: 'motion_preview',
|
|
6686
7010
|
description: '渲染工程里的几帧并**直接返回拼图**(同一轮就能看见画面)。第一次调用要打包(约 10–30 秒),之后同一份源码复用缓存只要几秒。frames 是绝对帧号,例如 [0,60,120,180];省略时取每段的中点(最多 6 帧),定调阶段只写 scene-0 时用它看一张定妆帧。**要看画质(锯齿、色带、字重)就传 crop:只出 1:1 原尺寸局部**,拼图是缩过的,缩过的图看不出这些毛病。',
|
|
6687
7011
|
parameters: {
|
|
6688
7012
|
path: { type: 'string' }, page: { type: 'number' }, nodeId: { type: 'string', required: true },
|
|
6689
|
-
frames: { type: '
|
|
7013
|
+
frames: { type: 'array', items: { type: 'number' }, description: '绝对帧号数组,最多 9 帧;省略 = 每段中点' },
|
|
6690
7014
|
cols: { type: 'number', description: '拼图列数,默认 3' },
|
|
6691
|
-
crop: {
|
|
7015
|
+
crop: {
|
|
7016
|
+
type: 'object',
|
|
7017
|
+
additionalProperties: false,
|
|
7018
|
+
properties: {
|
|
7019
|
+
x: { type: 'number' }, y: { type: 'number' }, w: { type: 'number' }, h: { type: 'number' },
|
|
7020
|
+
cx: { type: 'number' }, cy: { type: 'number' },
|
|
7021
|
+
},
|
|
7022
|
+
description: '只出局部:{x,y,w,h} 或 {cx,cy,w,h},像素。传了就按 1:1 原尺寸返回(不缩放),用来判断 AA / 色带 / 字体',
|
|
7023
|
+
},
|
|
6692
7024
|
},
|
|
6693
7025
|
output: { schema: { type: 'object', additionalProperties: false, properties: {
|
|
6694
7026
|
summary: { type: 'string', required: true },
|
|
@@ -6704,12 +7036,13 @@ function sanitizeNewName(raw) {
|
|
|
6704
7036
|
const node = loaded.doc.nodes.find((item) => item.id === args.nodeId)
|
|
6705
7037
|
if (!node) throw new Error('找不到视频节点 ' + args.nodeId)
|
|
6706
7038
|
const dir = projectDirOf(node, scope)
|
|
6707
|
-
const
|
|
7039
|
+
const requestedFrames = jsonArray(args.frames)
|
|
7040
|
+
const requested = requestedFrames.length ? requestedFrames.map(Number).filter((n) => Number.isFinite(n)).slice(0, 9) : []
|
|
6708
7041
|
const frames = requested.length ? requested : (() => {
|
|
6709
7042
|
const timeline = readTimeline(dir)
|
|
6710
7043
|
return timeline.shots.slice(0, 6).map((shot) => shot.startFrame + Math.floor(shot.durationFrames * 0.6))
|
|
6711
7044
|
})()
|
|
6712
|
-
const preview = await previewProject(dir, { frames, cols: args.cols, crop: args.crop, attachments: ctx.get?.('attachments') })
|
|
7045
|
+
const preview = await previewProject(dir, { frames, cols: args.cols, crop: jsonValue(args.crop), attachments: ctx.get?.('attachments') })
|
|
6713
7046
|
const payload = { summary: '已渲染 ' + frames.length + ' 帧并拼图' + (preview.crop ? '(1:1 局部 ' + preview.crop.w + '×' + preview.crop.h + ')' : ''), details: JSON.stringify({ frames: preview.frames, sheet: preview.sheet, crop: preview.crop }) }
|
|
6714
7047
|
const ref = preview.attachment
|
|
6715
7048
|
if (ref && typeof ref.attachmentId === 'string') {
|
|
@@ -6725,15 +7058,15 @@ function sanitizeNewName(raw) {
|
|
|
6725
7058
|
},
|
|
6726
7059
|
})))
|
|
6727
7060
|
|
|
6728
|
-
ctx.effect(() =>
|
|
7061
|
+
ctx.effect(() => registerHostTool(defineTool({
|
|
6729
7062
|
name: 'motion_render',
|
|
6730
7063
|
description: '渲染工程。start 立即返回 jobId(后台跑,不要等);**start 前会按 STYLE.md 的引擎契约扫工程源码,three / webgl2 风格缺真引擎会直接拒绝渲染**(样片也拦;确实要换实现先在 STYLE.md 写「偏离:引擎 …」);range 渲的是**样片**(写 .out/pilot.mp4,任务 kind=pilot),不传就是整片(.out/film.mp4);status 查进度;list 列最近任务;cancel 取消;apply 把渲好的片子回填到视频节点——**nodeId 决定回填到哪个节点**(样片回填到另建的「样片」节点,成片回填到主视频节点),不给 jobId 时主视频节点取整片、样片节点取样片。frames 只渲染指定帧。',
|
|
6731
7064
|
parameters: {
|
|
6732
7065
|
action: { type: 'string', enum: ['start', 'status', 'list', 'cancel', 'apply'], required: true },
|
|
6733
7066
|
path: { type: 'string' }, page: { type: 'number' }, nodeId: { type: 'string', description: 'start:从哪个视频节点对应的工程渲;apply:回填到哪个节点(省略时:主视频节点回填整片、另建的样片节点回填样片)' }, jobId: { type: 'string' },
|
|
6734
7067
|
source: { type: 'string', enum: ['film', 'pilot'], description: 'apply 时回填哪一种:film=整片(主视频节点)、pilot=样片(样片节点)' },
|
|
6735
|
-
frames: { type: '
|
|
6736
|
-
range: { type: '
|
|
7068
|
+
frames: { type: 'array', items: { type: 'number' }, description: '只渲染这些帧' },
|
|
7069
|
+
range: { type: 'array', items: { type: 'number' }, description: '帧区间 [from,to]:渲样片(写 pilot.mp4)' },
|
|
6737
7070
|
},
|
|
6738
7071
|
output: { schema: { type: 'object', additionalProperties: false, properties: { summary: { type: 'string', required: true }, details: { type: 'string', required: true } } },
|
|
6739
7072
|
render: (_args, value) => [{ type: 'text', text: value.summary + '\n' + value.details }] },
|
|
@@ -6824,8 +7157,10 @@ function sanitizeNewName(raw) {
|
|
|
6824
7157
|
})
|
|
6825
7158
|
}
|
|
6826
7159
|
if (policyFor(scope.sessionId)?.mode === 'read-only') throw new Error('当前会话为只读')
|
|
6827
|
-
const
|
|
6828
|
-
const
|
|
7160
|
+
const renderFrames = jsonArray(args.frames)
|
|
7161
|
+
const renderRange = jsonArray(args.range)
|
|
7162
|
+
const operation = renderFrames.length ? 'still' : 'full'
|
|
7163
|
+
const ranged = renderRange.length === 2
|
|
6829
7164
|
// 样片的输出必须跟整片分开:以前 range 渲染也写 film.mp4,下一次 apply 就会把 4 秒样片当整片塞进主节点。
|
|
6830
7165
|
const kind = operation === 'still' ? 'still' : ranged ? 'pilot' : 'film'
|
|
6831
7166
|
const outputName = kind === 'still' ? 'still.mp4' : kind === 'pilot' ? 'pilot.mp4' : 'film.mp4'
|
|
@@ -6847,7 +7182,7 @@ function sanitizeNewName(raw) {
|
|
|
6847
7182
|
const qualityGate = kind === 'still' ? null : assertQualityEvidence(dir, styleForGate)
|
|
6848
7183
|
const gateWarnings = [engineGate?.warning, ...(qualityGate?.warnings || [])].filter(Boolean)
|
|
6849
7184
|
const job = motionJobs.create({ kind, dir, nodeId: node.id })
|
|
6850
|
-
const handle = renderProject(dir, { operation, frames:
|
|
7185
|
+
const handle = renderProject(dir, { operation, frames: renderFrames.length ? renderFrames : undefined, frameRange: ranged ? renderRange : undefined,
|
|
6851
7186
|
output: outputPath(dir, outputName),
|
|
6852
7187
|
onProgress: (message) => motionJobs.progress(job, message.phase, message.progress, message.message) })
|
|
6853
7188
|
motionJobs.attach(job, handle)
|
|
@@ -6863,7 +7198,7 @@ function sanitizeNewName(raw) {
|
|
|
6863
7198
|
})))
|
|
6864
7199
|
|
|
6865
7200
|
// 帧检:把"片子有没有毛病"变成一张带帧号的清单,而不是靠眼睛。
|
|
6866
|
-
ctx.effect(() =>
|
|
7201
|
+
ctx.effect(() => registerHostTool(defineTool({
|
|
6867
7202
|
name: 'motion_qa',
|
|
6868
7203
|
description: '成片帧检:不靠眼睛,靠像素和响度。inspect 立刻检一遍(默认检工程里的 .out/film.mp4,也可以指定 file):帧数对不对、有没有黑场、有没有静止太久、有没有单帧闪烁、有没有空白帧、画面有没有顶到边、音轨有没有削波。report 读上次的结果。每条问题都带帧号和镜头号,能直接回去改对应场景。',
|
|
6869
7204
|
parameters: {
|
|
@@ -6902,7 +7237,7 @@ function sanitizeNewName(raw) {
|
|
|
6902
7237
|
})))
|
|
6903
7238
|
|
|
6904
7239
|
// 剪映导出:把成片拆成"还能继续剪"的素材包。
|
|
6905
|
-
ctx.effect(() =>
|
|
7240
|
+
ctx.effect(() => registerHostTool(defineTool({
|
|
6906
7241
|
name: 'motion_export',
|
|
6907
7242
|
description: '把工程导出成剪映可继续剪的素材包:逐镜头无声音轨 clips/、原音轨 audio/(BGM 与音效分开)、shots.json / audio.json / README.md,有字幕数据时再给 subtitles.srt,draft:true 另写一份 draft_content.json 草稿。run 导出,list 看导出过哪些。镜头按时间轴精确切(重编码,不靠关键帧),cut 点不会偏。',
|
|
6908
7243
|
parameters: {
|
|
@@ -6910,7 +7245,15 @@ function sanitizeNewName(raw) {
|
|
|
6910
7245
|
path: { type: 'string' }, nodeId: { type: 'string' }, page: { type: 'number' },
|
|
6911
7246
|
name: { type: 'string', description: '导出目录名,默认 jianying' },
|
|
6912
7247
|
draft: { type: 'boolean', description: '另写一份 draft_content.json(剪映草稿结构,未在剪映里实测过)' },
|
|
6913
|
-
subtitles: {
|
|
7248
|
+
subtitles: {
|
|
7249
|
+
type: 'array',
|
|
7250
|
+
items: {
|
|
7251
|
+
type: 'object',
|
|
7252
|
+
additionalProperties: true,
|
|
7253
|
+
properties: { startFrame: { type: 'number' }, endFrame: { type: 'number' }, text: { type: 'string' } },
|
|
7254
|
+
},
|
|
7255
|
+
description: '字幕数组 [{startFrame,endFrame,text}];工程 timeline 里有 subtitles 就不用传',
|
|
7256
|
+
},
|
|
6914
7257
|
file: { type: 'string', description: '当成片对照的 mp4,默认 .out/film.mp4' },
|
|
6915
7258
|
},
|
|
6916
7259
|
output: { schema: { type: 'object', additionalProperties: false, properties: { summary: { type: 'string', required: true }, details: { type: 'string', required: true } } },
|
|
@@ -6928,7 +7271,7 @@ function sanitizeNewName(raw) {
|
|
|
6928
7271
|
if (policyFor(scope.sessionId)?.mode === 'read-only') throw new Error('当前会话为只读')
|
|
6929
7272
|
const timeline = readTimeline(dir)
|
|
6930
7273
|
const result = await exportPackage({ dir, timeline, name: args.name || 'jianying', draft: Boolean(args.draft),
|
|
6931
|
-
subtitles: args.subtitles, film: args.file ? workspacePath(scope, isAbsolute(args.file) ? args.file : joinPath(dir, args.file)) : undefined })
|
|
7274
|
+
subtitles: jsonValue(args.subtitles), film: args.file ? workspacePath(scope, isAbsolute(args.file) ? args.file : joinPath(dir, args.file)) : undefined })
|
|
6932
7275
|
return { summary: '已导出 ' + result.shots.length + ' 镜 / ' + result.stems.length + ' 条音轨到 ' + result.folder
|
|
6933
7276
|
+ (result.subtitles ? '(含字幕 ' + result.subtitles + ' 条)' : '')
|
|
6934
7277
|
+ (result.missing.length ? ',缺 ' + result.missing.length + ' 个音频文件' : ''),
|
|
@@ -6956,7 +7299,7 @@ function sanitizeNewName(raw) {
|
|
|
6956
7299
|
}
|
|
6957
7300
|
|
|
6958
7301
|
// motion_assets 真素材进工程(原字节 + 尺寸 + 色板),场景用 staticFile 取
|
|
6959
|
-
ctx.effect(() =>
|
|
7302
|
+
ctx.effect(() => registerHostTool(defineTool({
|
|
6960
7303
|
name: 'motion_assets',
|
|
6961
7304
|
description: '视频工程素材:list 查看工程素材;library 按 query(如 纸张、颗粒、钢琴)或 category 搜索插件自带纹理及乐器采样;use 用 assetId 按需导入,返回 file,图片用 staticFile(file),音频须另用 motion_audio add 上时间轴。素材库是可选来源,用户自带素材优先,采样是单音而非完整配乐;使用前查看许可。gather 按站点收集品牌素材、配色与字体,collect 扫单页;capture 收本地素材;fetch 下载原图;crop 裁切;page 截网页;palette 读色板。',
|
|
6962
7305
|
parameters: {
|
|
@@ -7049,7 +7392,7 @@ function sanitizeNewName(raw) {
|
|
|
7049
7392
|
})))
|
|
7050
7393
|
|
|
7051
7394
|
// motion_sfx 音效采购:Freesound 搜索/下载与素材索引
|
|
7052
|
-
ctx.effect(() =>
|
|
7395
|
+
ctx.effect(() => registerHostTool(defineTool({
|
|
7053
7396
|
name: 'motion_sfx',
|
|
7054
7397
|
description: '音效采购(Freesound)。search 按英文关键词搜,返回时长与作者;get 按 id 下载 HQ mp3 预览到 <工程>/assets/sfx/,量峰值与有效区间,维护一份 assets/audio-manifest.json;list 看已下载的音效。下载完用 motion_audio {action:"add", file:"sfx/xxx.mp3", role:"sfx"} 挂到轴上。',
|
|
7055
7398
|
parameters: {
|
|
@@ -7060,7 +7403,7 @@ function sanitizeNewName(raw) {
|
|
|
7060
7403
|
minDuration: { type: 'number', description: 'search:最短秒数,默认 0' },
|
|
7061
7404
|
maxDuration: { type: 'number', description: 'search:最长秒数' },
|
|
7062
7405
|
limit: { type: 'number', description: 'search:最多返回几条,默认 8' },
|
|
7063
|
-
ids: { type: '
|
|
7406
|
+
ids: { type: 'array', items: { type: 'string' }, description: 'get:音效 id 数组,例如 ["fs:12345"]' },
|
|
7064
7407
|
},
|
|
7065
7408
|
output: { schema: { type: 'object', additionalProperties: false, properties: { summary: { type: 'string', required: true }, details: { type: 'string', required: true } } },
|
|
7066
7409
|
render: (_args, value) => [{ type: 'text', text: value.summary + '\n' + value.details }] },
|
|
@@ -7076,7 +7419,7 @@ function sanitizeNewName(raw) {
|
|
|
7076
7419
|
details: JSON.stringify({ ...found, next: '挑一条再 motion_sfx {action:"get", ids:["fs:xxxx"]};想先听可以按 pageUrl 打开' }) }
|
|
7077
7420
|
}
|
|
7078
7421
|
if (policyFor(scope.sessionId)?.mode === 'read-only') throw new Error('当前会话为只读')
|
|
7079
|
-
const result = await getSfx(dir, args.ids)
|
|
7422
|
+
const result = await getSfx(dir, jsonArray(args.ids))
|
|
7080
7423
|
const ok = result.results.filter((item) => item.ok)
|
|
7081
7424
|
const bad = result.results.filter((item) => !item.ok)
|
|
7082
7425
|
return { summary: '下载 ' + ok.length + ' 条' + (bad.length ? ',失败 ' + bad.length + ' 条' : ''),
|
|
@@ -7091,7 +7434,7 @@ function sanitizeNewName(raw) {
|
|
|
7091
7434
|
})))
|
|
7092
7435
|
|
|
7093
7436
|
// motion_audio 音频轴:加/删/列音轨,量节拍,把切镜和目标音钉到拍子上
|
|
7094
|
-
ctx.effect(() =>
|
|
7437
|
+
ctx.effect(() => registerHostTool(defineTool({
|
|
7095
7438
|
name: 'motion_audio',
|
|
7096
7439
|
description: '音频轴工具。list 列出时间轴上的音轨;add 把一条音频放到指定秒(默认写进 <工程>/assets/,素材没进工程会自动捕获);remove 按 cueId 删;analyze 量 BPM/网格/重音(返回 bpm、grid 残差、hits 三分类、peakLag);apply 写 beats.json 并把切点吸到拍子上,给 cueId 时改把这条音轨钉到拍子(自动扣掉音源峰值滞后与输出偏移)。bpm 为 null 说明量不出稳定网格——不要硬对。',
|
|
7097
7440
|
parameters: {
|
|
@@ -7223,13 +7566,37 @@ function sanitizeNewName(raw) {
|
|
|
7223
7566
|
},
|
|
7224
7567
|
})))
|
|
7225
7568
|
|
|
7226
|
-
ctx.effect(() =>
|
|
7569
|
+
ctx.effect(() => registerHostTool(defineTool({
|
|
7227
7570
|
name: 'motion_sync',
|
|
7228
7571
|
description: '音乐拍点→切镜/动作→音效峰值统一绑定。bind 替换绑定表并分析音乐;list 查看解析结果;clear 解除绑定、保留当前排期。anchors:[{id,type:"cut"|"action",shotId,beat?:0起拍号,at?:源音乐秒,name?:动作名,visual?:{metric:"change"|"brightness",roi:{x,y,w,h}},sfx?:[{cueId,peakSeconds:素材源峰值秒}]}]。beat/at 二选一;动作场景接收 anchors,使用 anchors.<name> 本镜局部帧写关键帧。动作验收要选 ROI/metric,切镜默认测帧差。绑定后 motion_audio_verify 对照成片。音乐需要非循环音轨,音乐 from/trimStart 参与换算。',
|
|
7229
7572
|
parameters: {
|
|
7230
7573
|
action: {type:'string',enum:['bind','list','clear'],required:true},
|
|
7231
7574
|
nodeId: {type:'string',required:true}, path:{type:'string'},page:{type:'number'},
|
|
7232
|
-
musicCueId:{type:'string'},
|
|
7575
|
+
musicCueId:{type:'string'},
|
|
7576
|
+
anchors:{
|
|
7577
|
+
type:'array',
|
|
7578
|
+
items:{
|
|
7579
|
+
type:'object',
|
|
7580
|
+
additionalProperties:true,
|
|
7581
|
+
properties:{
|
|
7582
|
+
id:{type:'string'},
|
|
7583
|
+
type:{type:'string',enum:['cut','action']},
|
|
7584
|
+
shotId:{type:'string'},
|
|
7585
|
+
beat:{type:'number'},
|
|
7586
|
+
at:{type:'number'},
|
|
7587
|
+
name:{type:'string'},
|
|
7588
|
+
visual:{
|
|
7589
|
+
type:'object',
|
|
7590
|
+
additionalProperties:true,
|
|
7591
|
+
properties:{
|
|
7592
|
+
metric:{type:'string',enum:['change','brightness']},
|
|
7593
|
+
roi:{type:'object',additionalProperties:false,properties:{x:{type:'number'},y:{type:'number'},w:{type:'number'},h:{type:'number'}}},
|
|
7594
|
+
},
|
|
7595
|
+
},
|
|
7596
|
+
sfx:{type:'array',items:{type:'object',additionalProperties:true,properties:{cueId:{type:'string'},peakSeconds:{type:'number'}}}},
|
|
7597
|
+
},
|
|
7598
|
+
},
|
|
7599
|
+
},
|
|
7233
7600
|
outputOffsetSeconds:{type:'number',description:'具体渲染管线实测音轨延迟(秒),默认0;验收报告给出多点测得的偏移参考'},
|
|
7234
7601
|
toleranceFrames:{type:'number',description:'音画及音效误差容差,默认2帧,最大3帧'},
|
|
7235
7602
|
},
|
|
@@ -7249,7 +7616,7 @@ function sanitizeNewName(raw) {
|
|
|
7249
7616
|
const analysis=await analyzeBeats(assetPath(dir,cue.file))
|
|
7250
7617
|
const tolerance=args.toleranceFrames??2
|
|
7251
7618
|
if(!Number.isFinite(tolerance)||tolerance<0||tolerance>3)throw new Error('toleranceFrames 必须在0–3内')
|
|
7252
|
-
timeline.sync={version:1,musicCueId:cue.id,analysis,analysisSourceHash:filmHash(assetPath(dir,cue.file)),anchors:args.anchors,outputOffsetSeconds:args.outputOffsetSeconds??0,toleranceFrames:tolerance}
|
|
7619
|
+
timeline.sync={version:1,musicCueId:cue.id,analysis,analysisSourceHash:filmHash(assetPath(dir,cue.file)),anchors:jsonArray(args.anchors),outputOffsetSeconds:args.outputOffsetSeconds??0,toleranceFrames:tolerance}
|
|
7253
7620
|
timeline=resolveSync(timeline)
|
|
7254
7621
|
}
|
|
7255
7622
|
writeTimeline(dir,timeline)
|
|
@@ -7258,7 +7625,7 @@ function sanitizeNewName(raw) {
|
|
|
7258
7625
|
})))
|
|
7259
7626
|
|
|
7260
7627
|
// motion_audio verify 渲后回测:成片里的声音到底偏了多少帧
|
|
7261
|
-
ctx.effect(() =>
|
|
7628
|
+
ctx.effect(() => registerHostTool(defineTool({
|
|
7262
7629
|
name: 'motion_audio_verify',
|
|
7263
7630
|
description: '音画对照验收:按 motion_sync 锚点,从成片匹配源音乐和音效波形、测切镜或动作ROI的实际变化帧,逐项报告音画及音效误差。无证据标 unverified,超容差 fail。报告写入 .out/qa/sync/report.json。',
|
|
7264
7631
|
parameters: {
|
|
@@ -7288,7 +7655,7 @@ function sanitizeNewName(raw) {
|
|
|
7288
7655
|
// 为什么时长由旁白说了算:旁白是"内容的长度",画面是"内容的形状"。
|
|
7289
7656
|
// 先按字速估、合成后用真实时长再重排一次,镜头跟着句子走 —— 反过来做
|
|
7290
7657
|
// (先定死镜头再塞旁白)只能靠变速或砍句,两个都难看。
|
|
7291
|
-
ctx.effect(() =>
|
|
7658
|
+
ctx.effect(() => registerHostTool(defineTool({
|
|
7292
7659
|
name: 'motion_voice',
|
|
7293
7660
|
description: '旁白(TTS):把 voice.script.md(## shot-N 分节、一句一行)变成 voice 音轨 + 字幕,并让镜头时长跟着句子走。action=list 看现状/音色;plan 只估算与重排(不合成,先看排期是否合理);say 真合成(edge-tts,拿不到就退 SAPI)+ 用真实时长重排;align 已有音频只按真实时长重排。合成结果按台词哈希缓存,改一句只重合成一句。字幕写进 timeline.subtitles,导出 SRT / 剪映字幕轨零改动。',
|
|
7294
7661
|
parameters: {
|
|
@@ -7368,7 +7735,7 @@ function sanitizeNewName(raw) {
|
|
|
7368
7735
|
},
|
|
7369
7736
|
})))
|
|
7370
7737
|
|
|
7371
|
-
ctx.effect(() =>
|
|
7738
|
+
ctx.effect(() => registerHostTool({ ...applyTool, execute: async (args, exec) => {
|
|
7372
7739
|
const sessionId = sessionIdOf(exec)
|
|
7373
7740
|
const path = typeof args.path === 'string' && args.path ? args.path : focusedPathFor(sessionId) || DEFAULT_PATH
|
|
7374
7741
|
const target = await resolveTarget(path, sessionId)
|
|
@@ -7378,24 +7745,34 @@ function sanitizeNewName(raw) {
|
|
|
7378
7745
|
})
|
|
7379
7746
|
} }))
|
|
7380
7747
|
|
|
7381
|
-
//
|
|
7748
|
+
// 注册段收尾:一行汇总(成功几个、失败哪几个)。远端出问题时先看这条,
|
|
7749
|
+
// 再看 DIAGNOSTICS_PATH 的 JSON —— 两者对不上就说明注册完又被回收了。
|
|
7750
|
+
reportToolRegistrations()
|
|
7751
|
+
|
|
7752
|
+
// 把"怎么用这张画布"注册成**内嵌技能**(见 SKILL_DEFINITIONS 的注释):
|
|
7382
7753
|
// 换了电脑、只装了插件(没有源码/README/tools)时,模型依然知道该怎么操作。
|
|
7383
7754
|
//
|
|
7384
7755
|
// `skills` 是**可选**依赖:没装 skill 注册表的部署里照样能用(工具描述本身已经够启动),
|
|
7385
7756
|
// 所以走 ctx.get 而不是写进 inject —— 缺它不该让整个插件停在等待态。
|
|
7386
7757
|
const skills = typeof ctx.get === 'function' ? ctx.get('skills') : undefined
|
|
7387
7758
|
if (skills !== undefined && skills !== null && typeof skills.register === 'function') {
|
|
7388
|
-
ctx.effect(() =>
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7759
|
+
ctx.effect(() => {
|
|
7760
|
+
const disposers = SKILL_DEFINITIONS.map((definition) =>
|
|
7761
|
+
skills.register({
|
|
7762
|
+
name: definition.name,
|
|
7763
|
+
description: definition.description,
|
|
7764
|
+
whenToUse: definition.whenToUse,
|
|
7765
|
+
// `source: 'runtime'` 不能省:注册时只校验 name/description/invocation,
|
|
7766
|
+
// 但**取全文**时注册表会再跑一次 validateDefinition,那里要求 source 是字符串
|
|
7767
|
+
// (少了它,目录里看得见、一 load 就抛 "source must be a string")。
|
|
7768
|
+
source: 'runtime',
|
|
7769
|
+
content: definition.content,
|
|
7770
|
+
}),
|
|
7771
|
+
)
|
|
7772
|
+
// 两条注册各返回一个 disposer;合成一个交回 Cordis,卸载时一并撤掉。
|
|
7773
|
+
return () => {
|
|
7774
|
+
for (const dispose of disposers) if (typeof dispose === 'function') dispose()
|
|
7775
|
+
}
|
|
7776
|
+
})
|
|
7400
7777
|
}
|
|
7401
7778
|
}
|