cozyclay 1.10.0 → 2.0.2
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/CHANGELOG.md +193 -0
- package/bin/agent/agent-routes.mjs +82 -72
- package/bin/agent/agent-runner.mjs +10 -3
- package/bin/agent/motion-runtime.mjs +32 -260
- package/bin/agent/providers.mjs +46 -3
- package/bin/agent/session-store.mjs +41 -5
- package/bin/agent/studio-prompt.mjs +1 -1
- package/bin/agent/studio-tools.mjs +48 -11
- package/bin/codex-auth.mjs +2 -1
- package/bin/cozyclay.mjs +38 -11
- package/bin/live/cli.mjs +22 -3
- package/bin/mcp-runtime.mjs +45 -20
- package/bin/telemetry-state.mjs +5 -2
- package/bin/update-check.mjs +15 -0
- package/dist/app/index.html +6 -6
- package/dist/assets/analytics-MVZOEW8O.js +1 -0
- package/dist/assets/app-BxBLC6GT.js +4876 -0
- package/dist/assets/app-CJdxvnrl.css +1 -0
- package/dist/assets/{demo-B-eg1CT8.js → demo-ByeYp-dY.js} +1 -1
- package/dist/assets/{first-shot-handoff-CGgnvNu8.js → first-shot-handoff-DgT28ZiV.js} +1 -1
- package/dist/assets/{landing-IbRp3FnQ.js → landing-BaXkpFip.js} +1 -1
- package/dist/assets/shot-prompt-CbqWcWYi.css +1 -0
- package/dist/assets/shot-prompt-hN0j_O-p.js +17 -0
- package/dist/assets/{ticket-BgIzOBOf.js → ticket-BcmsnHn5.js} +1 -1
- package/dist/assets/workflow-B5XZwVfe.js +179 -0
- package/dist/assets/workflow-DRdwoDBT.css +1 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/fonts/IBM-Plex-Sans-OFL.txt +92 -0
- package/dist/fonts/JetBrains-Mono-OFL.txt +93 -0
- package/dist/fonts/README.md +13 -9
- package/dist/fonts/ibm-plex-sans-400-latin.woff2 +0 -0
- package/dist/fonts/ibm-plex-sans-500-latin.woff2 +0 -0
- package/dist/fonts/ibm-plex-sans-600-latin.woff2 +0 -0
- package/dist/fonts/jetbrains-mono-400-latin.woff2 +0 -0
- package/dist/fonts/jetbrains-mono-500-latin.woff2 +0 -0
- package/dist/index.html +7 -7
- package/dist/privacy/index.html +13 -8
- package/dist/sitemap.xml +2 -2
- package/dist/workflow/index.html +5 -5
- package/mcp/live-hub.mjs +35 -93
- package/mcp/mesh-file.mjs +48 -0
- package/mcp/server.mjs +6 -77
- package/mcp/tool-handlers.mjs +219 -376
- package/package.json +2 -1
- package/src/App.jsx +1275 -9086
- package/src/analytics.js +393 -9
- package/src/app-context.js +211 -0
- package/src/app-stage.jsx +25 -24
- package/src/ardy/auto-fix-panel.css +108 -0
- package/src/ardy/fix-collisions.js +10 -66
- package/src/ardy/ik-drag.js +101 -0
- package/src/ardy/ik-key-json.js +43 -0
- package/src/ardy/ik.js +97 -67
- package/src/ardy/physics-panel.css +2 -0
- package/src/ardy/physics-panel.jsx +17 -5
- package/src/ardy/platform-fit-panel.css +64 -0
- package/src/ardy/platform-fit-panel.jsx +40 -0
- package/src/ardy/platform-fit.js +326 -0
- package/src/ardy/playback.js +166 -20
- package/src/ardy/range-pin.js +299 -0
- package/src/ardy/timeline-coordinates.js +26 -0
- package/src/ardy/timeline.css +1214 -0
- package/src/ardy/timeline.jsx +152 -41
- package/src/ardy/waypoints.js +3 -3
- package/src/asset-pane.css +462 -0
- package/src/asset-pane.jsx +461 -232
- package/src/camera-tutorial.jsx +38 -18
- package/src/command-bus.js +349 -0
- package/src/commands/ai.js +31 -0
- package/src/commands/cast.js +140 -0
- package/src/commands/elements/character.js +13 -0
- package/src/commands/elements/motion.js +14 -0
- package/src/commands/elements/object.js +22 -0
- package/src/commands/elements/scene.js +16 -0
- package/src/commands/elements/shot.js +9 -0
- package/src/commands/elements/stage.js +11 -0
- package/src/commands/elements.js +155 -0
- package/src/commands/export.js +20 -0
- package/src/commands/index.js +40 -0
- package/src/commands/motion.js +231 -0
- package/src/commands/objects.js +174 -0
- package/src/commands/project.js +53 -0
- package/src/commands/scene.js +81 -0
- package/src/commands/shared.js +19 -0
- package/src/commands/shot.js +103 -0
- package/src/commands/stage.js +17 -0
- package/src/commands/view.js +42 -0
- package/src/document-store.js +144 -0
- package/src/domains/cast.js +1007 -0
- package/src/domains/motion.js +3219 -0
- package/src/domains/objects.js +932 -0
- package/src/domains/scenes.js +823 -0
- package/src/domains/shots.js +404 -0
- package/src/domains/stage.js +93 -0
- package/src/dualview.jsx +113 -57
- package/src/facing-marks.js +3 -0
- package/src/first-success-guide.jsx +14 -14
- package/src/grid-view.js +18 -5
- package/src/hierarchy-panel.css +386 -0
- package/src/hierarchy-panel.jsx +88 -19
- package/src/{fal-motion-client.js → i2v-motion-client.js} +13 -13
- package/src/i2v-motion-studio.jsx +180 -0
- package/src/ik-camera.js +3 -0
- package/src/main.jsx +6 -0
- package/src/motion/generation.js +105 -0
- package/src/motion-readiness-ui.jsx +4 -4
- package/src/motion-readiness.js +11 -0
- package/src/motion-trail.js +366 -9
- package/src/object-gizmo.jsx +7 -1
- package/src/panels/CameraPanel.jsx +60 -0
- package/src/panels/CharacterTransformPanel.jsx +48 -0
- package/src/panels/EnvironmentPanel.jsx +40 -0
- package/src/panels/Foldout.jsx +32 -0
- package/src/panels/LightPanel.jsx +24 -0
- package/src/panels/ObjectTransformPanel.jsx +554 -0
- package/src/panels/PosePanel.jsx +97 -0
- package/src/panels/ProjectPanel.jsx +33 -0
- package/src/panels/PromptBlocksPanel.jsx +442 -0
- package/src/panels/PropsPanel.jsx +70 -0
- package/src/panels/ReferenceImageField.jsx +91 -0
- package/src/panels/RigControlPanel.jsx +78 -0
- package/src/panels/RigPanel.jsx +37 -0
- package/src/panels/SubjectBox.jsx +47 -0
- package/src/panels/SubjectsPanel.jsx +39 -0
- package/src/panels/VideoCapturePanel.jsx +180 -0
- package/src/panels/details.css +1171 -0
- package/src/panels/motion.css +197 -0
- package/src/panels/pose.css +39 -0
- package/src/planview.jsx +60 -31
- package/src/posestudio.jsx +59 -4
- package/src/project-browser.css +1073 -0
- package/src/project-browser.jsx +288 -123
- package/src/range-pin-object-transform.js +19 -0
- package/src/range-pin-panel.css +518 -0
- package/src/range-pin-panel.jsx +344 -0
- package/src/result-modal.jsx +5 -5
- package/src/room.jsx +79 -51
- package/src/scenes.js +9 -8
- package/src/semantic-edit.js +2 -0
- package/src/settings-menu.jsx +39 -105
- package/src/shell/BottomDock.jsx +450 -0
- package/src/shell/DetailsSlot.jsx +513 -0
- package/src/shell/LibrarySlot.jsx +86 -0
- package/src/shell/MenuBar.jsx +601 -0
- package/src/shell/OutlinerSlot.jsx +50 -0
- package/src/shell/PreferencesDialog.jsx +538 -0
- package/src/shell/PreferencesSlot.jsx +40 -0
- package/src/shell/StatusBar.jsx +125 -0
- package/src/shell/StudioShell.jsx +92 -0
- package/src/shell/TopBar.jsx +129 -0
- package/src/shell/ViewportToolbar.jsx +656 -0
- package/src/shell/agent-glass.css +296 -0
- package/src/shell/dock.css +93 -0
- package/src/shell/glass-regions.css +985 -0
- package/src/shell/glass.css +687 -0
- package/src/shell/log-store.js +52 -0
- package/src/shell/mode.css +9 -0
- package/src/shell/preferences.css +651 -0
- package/src/shell/shell.css +282 -0
- package/src/shell/studio-shell-context.js +19 -0
- package/src/shell/topbar.css +435 -0
- package/src/shell/viewport.css +451 -0
- package/src/store/authored-intent.js +22 -0
- package/src/store/runtime-adapters.js +92 -0
- package/src/store/scene-stage.js +21 -0
- package/src/store/use-document-store.js +14 -0
- package/src/studio-actions.js +210 -0
- package/src/studio-agent-commands.js +38 -268
- package/src/studio-agent-context.js +38 -9
- package/src/studio-agent-motion.js +122 -378
- package/src/studio-agent-protocol.js +66 -30
- package/src/studio-app-binding.js +379 -0
- package/src/studio-contact-sheet.js +73 -0
- package/src/studio-elements.js +58 -51
- package/src/styles/themes.css +200 -0
- package/src/styles/tokens.css +69 -0
- package/src/styles.css +456 -1748
- package/src/theme.js +44 -0
- package/src/timeline-extent.js +16 -0
- package/src/timeline-fps.js +13 -0
- package/src/trail-key-conflicts.js +39 -0
- package/src/trail-pick.js +59 -0
- package/src/ui.jsx +8 -8
- package/src/use-case-question.jsx +66 -0
- package/src/workflow/AgentPanel.jsx +86 -71
- package/src/workflow/agent-client.js +12 -2
- package/src/workflow/agent-panel.css +34 -32
- package/src/workflow/cozy-scene-node.css +2 -0
- package/src/workflow/workflow.css +2 -0
- package/tools/ardy/bridge.mjs +164 -146
- package/tools/ardy/visual-qa.mjs +5 -5
- package/tools/bench/EXP3.md +61 -0
- package/tools/bench/cclay_bench_extract_incam.py +125 -0
- package/tools/bench/cclay_bench_extract_obs.py +204 -0
- package/tools/bench/cclay_bench_runner.py +42 -0
- package/tools/bench/cube-contact.mjs +162 -0
- package/tools/bench/exp3.mjs +152 -0
- package/tools/bench/extract-bench-lib.mjs +216 -0
- package/tools/bench/extract-bench.mjs +302 -0
- package/tools/bench/fal-generate.mjs +88 -0
- package/tools/bench/fit/README.md +189 -0
- package/tools/bench/fit/camera.mjs +59 -0
- package/tools/bench/fit/contact.mjs +419 -0
- package/tools/bench/fit/footlock.mjs +234 -0
- package/tools/bench/fit/motion.mjs +85 -0
- package/tools/bench/fit/pin.mjs +27 -0
- package/tools/bench/fit/remote.mjs +105 -0
- package/tools/bench/fit-bench.mjs +126 -0
- package/tools/bench/fit-sanity.mjs +45 -0
- package/tools/bench/metrics.mjs +313 -0
- package/tools/bench/obs/depth.mjs +258 -0
- package/tools/bench/obs/extrinsics.mjs +175 -0
- package/tools/bench/obs/fit_mannequin_betas.py +340 -0
- package/tools/bench/obs/ground.mjs +294 -0
- package/tools/bench/obs/heading.mjs +151 -0
- package/tools/bench/obs/ladder.mjs +555 -0
- package/tools/bench/obs/mannequin-betas.json +124 -0
- package/tools/bench/obs/remote.mjs +83 -0
- package/tools/bench/obs/rest_joints.py +52 -0
- package/tools/bench/obs/ybot-targets.mjs +49 -0
- package/tools/bench/obs-bench.mjs +520 -0
- package/tools/bench/score.mjs +343 -0
- package/tools/bench/summarize.mjs +86 -0
- package/tools/dev/pages/privacy.html +13 -8
- package/tools/gt-render/browser.mjs +159 -0
- package/tools/gt-render/camera-math.mjs +276 -0
- package/tools/gt-render/page.mjs +267 -0
- package/tools/gt-render/render.mjs +514 -0
- package/tools/gt-render/scene-box.mjs +31 -0
- package/tools/gt-render/take-transform.mjs +87 -0
- package/tools/qa/css-rule-usage.mjs +283 -0
- package/tools/qa/studio-control-count.mjs +70 -32
- package/tools/run-tests.mjs +60 -3
- package/tools/track/DESIGN.md +513 -0
- package/tools/track/backfill-provenance.mjs +116 -0
- package/tools/track/budget.mjs +8 -0
- package/tools/track/check-rig.mjs +209 -0
- package/tools/track/diagnostics.schema.json +60 -0
- package/tools/track/export-rig.mjs +256 -0
- package/tools/track/fallback.mjs +7 -0
- package/tools/track/fk-parity-fixture.mjs +161 -0
- package/tools/track/gate.mjs +349 -0
- package/tools/track/masks.mjs +318 -0
- package/tools/track/metrics.mjs +201 -0
- package/tools/track/publish-obs.mjs +108 -0
- package/tools/track/py/check_env.py +61 -0
- package/tools/track/py/eval_lr.py +229 -0
- package/tools/track/py/lr_viterbi.py +294 -0
- package/tools/track/py/masks.py +405 -0
- package/tools/track/py/objective.py +382 -0
- package/tools/track/py/rig.py +508 -0
- package/tools/track/py/scene.py +326 -0
- package/tools/track/py/test_joint_indices.py +123 -0
- package/tools/track/py/test_lr_viterbi.py +195 -0
- package/tools/track/py/test_masks.py +229 -0
- package/tools/track/py/test_rig.py +288 -0
- package/tools/track/py/test_scene.py +354 -0
- package/tools/track/py/test_track.py +328 -0
- package/tools/track/py/track.py +411 -0
- package/tools/track/remote.mjs +140 -0
- package/tools/track/rig-dump.mjs +227 -0
- package/tools/track/run-box-tests.mjs +14 -0
- package/tools/track/run-box.mjs +168 -0
- package/tools/track/setup-box.sh +59 -0
- package/tools/track/study-2d.mjs +728 -0
- package/dist/assets/analytics-B1hnH66c.js +0 -1
- package/dist/assets/app-Cu1cexZR.css +0 -1
- package/dist/assets/app-Dxdh8kds.js +0 -4861
- package/dist/assets/shot-prompt-C_g2BHVc.css +0 -1
- package/dist/assets/shot-prompt-HVJRP4Gr.js +0 -17
- package/dist/assets/workflow-CJd-PDtV.js +0 -179
- package/dist/assets/workflow-DsudxKHF.css +0 -1
- package/src/fal-motion-studio.jsx +0 -180
- package/src/scene-history.js +0 -129
- package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.react-flow{direction:ltr}.react-flow__container{width:100%;height:100%;position:absolute;top:0;left:0}.react-flow__pane{z-index:1;cursor:-webkit-grab;cursor:grab}.react-flow__pane.selection{cursor:pointer}.react-flow__pane.dragging{cursor:-webkit-grabbing;cursor:grabbing}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow .react-flow__edges{pointer-events:none;overflow:visible}.react-flow__edge-path,.react-flow__connection-path{stroke:#b1b1b7;stroke-width:1px;fill:none}.react-flow__edge{pointer-events:visibleStroke;cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:.5s linear infinite dashdraw}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge:focus .react-flow__edge-path,.react-flow__edge:focus-visible .react-flow__edge-path{stroke:#555}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge-textbg{fill:#fff}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;user-select:none}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:.5s linear infinite dashdraw}.react-flow__connectionline{z-index:1001}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{-webkit-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:-webkit-grab;cursor:grab;position:absolute}.react-flow__node.dragging{cursor:-webkit-grabbing;cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:0 0;pointer-events:none}.react-flow__nodesselection-rect{pointer-events:all;cursor:-webkit-grab;cursor:grab;position:absolute}.react-flow__handle{pointer-events:none;background:#1a192b;border:1px solid #fff;border-radius:100%;width:6px;min-width:5px;height:6px;min-height:5px;position:absolute}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;bottom:-4px;left:50%;transform:translate(-50%)}.react-flow__handle-top{top:-4px;left:50%;transform:translate(-50%)}.react-flow__handle-left{top:50%;left:-4px;transform:translateY(-50%)}.react-flow__handle-right{top:50%;right:-4px;transform:translateY(-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__panel{z-index:5;margin:15px;position:absolute}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.center{left:50%;transform:translate(-50%)}.react-flow__attribution{background:#ffffff80;margin:0;padding:2px 3px;font-size:10px}.react-flow__attribution a{color:#999;text-decoration:none}@keyframes dashdraw{0%{stroke-dashoffset:10px}}.react-flow__edgelabel-renderer{pointer-events:none;-webkit-user-select:none;user-select:none;width:100%;height:100%;position:absolute}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-default,.react-flow__node-input,.react-flow__node-output,.react-flow__node-group{color:#222;text-align:center;background-color:#fff;border:1px solid #1a192b;border-radius:3px;width:150px;padding:10px;font-size:12px}.react-flow__node-default.selectable:hover,.react-flow__node-input.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:0 1px 4px 1px #00000014}.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:0 0 0 .5px #1a192b}.react-flow__node-group{background-color:#f0f0f040}.react-flow__nodesselection-rect,.react-flow__selection{background:#0059dc14;border:1px dotted #0059dccc}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls{box-shadow:0 0 2px 1px #00000014}.react-flow__controls-button{box-sizing:content-box;cursor:pointer;-webkit-user-select:none;user-select:none;background:#fefefe;border:none;border-bottom:1px solid #eee;justify-content:center;align-items:center;width:16px;height:16px;padding:5px;display:flex}.react-flow__controls-button:hover{background:#f4f4f4}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__minimap{background-color:#fff}.react-flow__minimap svg{display:block}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{background-color:#3367d9;border:1px solid #fff;border-radius:1px;width:4px;height:4px;transform:translate(-50%,-50%)}.react-flow__resize-control.handle.left{top:50%;left:0}.react-flow__resize-control.handle.right{top:50%;left:100%}.react-flow__resize-control.handle.top{top:0;left:50%}.react-flow__resize-control.handle.bottom{top:100%;left:50%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border:0 solid #3367d9}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;height:100%;top:0;transform:translate(-50%)}.react-flow__resize-control.line.left{border-left-width:1px;left:0}.react-flow__resize-control.line.right{border-right-width:1px;left:100%}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{width:100%;height:1px;left:0;transform:translateY(-50%)}.react-flow__resize-control.line.top{border-top-width:1px;top:0}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}:root{color:#edf0fb;font-synthesis:none;background:#101116;font-family:Inter,ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif}*{box-sizing:border-box}body{background:#101116;min-width:900px;margin:0}#workflow-root{height:100vh}.workflow-app{color:#edf0fb;background:#101116;flex-direction:column;height:100vh;display:flex}.workflow-topbar{background:#171921;border-bottom:1px solid #292d38;justify-content:space-between;align-items:center;height:58px;padding:0 22px;display:flex}.workflow-brand{color:#afb4c6;align-items:center;gap:9px;font-size:14px;display:flex}.workflow-brand strong{color:#f5f7ff;font-size:14px}.workflow-brand-mark{color:#fff;background:linear-gradient(135deg,#ef759d,#6c7cff);border-radius:7px;place-items:center;width:23px;height:23px;font-size:12px;font-weight:800;display:grid}.workflow-divider{color:#555b70;margin-left:3px}.workflow-top-actions{align-items:center;gap:9px;display:flex}.workflow-top-actions button,.workflow-sidebar-bottom button,.workflow-sidebar-bottom a{color:#c8ccdb;font:inherit;cursor:pointer;background:#20232d;border:1px solid #353a4a;border-radius:6px;padding:7px 12px;font-size:12px;text-decoration:none}.workflow-top-actions button:hover,.workflow-sidebar-bottom button:hover,.workflow-sidebar-bottom a:hover{background:#2b3040;border-color:#606b9a}.workflow-status{color:#7f879e;align-items:center;gap:7px;margin-right:8px;font-size:11px;display:flex}.workflow-status i{background:#4dcda4;border-radius:50%;width:6px;height:6px;box-shadow:0 0 8px #4dcda4}.workflow-main{flex:1;min-height:0;display:flex}.workflow-sidebar{background:#171921;border-right:1px solid #292d38;flex-direction:column;flex:none;width:232px;padding:22px 14px;display:flex}.workflow-sidebar-title{letter-spacing:.08em;text-transform:uppercase;color:#f3f5fd;font-size:12px;font-weight:700}.workflow-sidebar-copy{color:#737b92;margin:8px 6px 18px 0;font-size:11px;line-height:1.5}.workflow-node-menu{flex-direction:column;gap:5px;display:flex}.workflow-add-node{color:#bcc2d4;width:100%;font:inherit;text-align:left;cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px;align-items:center;gap:10px;padding:9px 10px;font-size:12px;display:flex}.workflow-add-node:hover{color:#f5f7ff;background:#222631;border-color:#373d50}.workflow-add-node span:nth-child(2){flex:1}.workflow-sidebar-bottom{flex-direction:column;gap:8px;margin-top:auto;display:flex}.workflow-sidebar-bottom button,.workflow-sidebar-bottom a{text-align:center;font-size:11px}.workflow-canvas{flex:1;min-width:0;position:relative}.workflow-canvas .react-flow{background:#11141c}.workflow-canvas .react-flow__controls{bottom:22px;left:20px;box-shadow:0 5px 18px #0005}.workflow-canvas .react-flow__controls-button{fill:#aeb5c9;color:#aeb5c9;background:#20242e;border-bottom:1px solid #323744;width:28px;height:28px}.workflow-canvas .react-flow__minimap{border:1px solid #353a49;border-radius:7px;bottom:20px;right:20px;overflow:hidden}.workflow-canvas-panel{margin:14px}.workflow-canvas-panel button{color:#e7eaff;font:inherit;cursor:pointer;background:#27304a;border:1px solid #454d70;border-radius:6px;align-items:center;gap:6px;padding:8px 11px;font-size:11px;display:flex}.workflow-node{color:#e5e8f5;background:#20232d;border:1px solid #3c4152;border-radius:9px;width:230px;position:relative;overflow:visible;box-shadow:0 10px 30px #0004}.workflow-node:hover{border-color:#6877bd}.workflow-node-header{background:#252935;border-bottom:1px solid #343946;border-radius:9px 9px 0 0;align-items:center;gap:8px;padding:10px 11px;font-size:12px;display:flex}.workflow-node-icon{color:#aeb8ff;background:#303648;border-radius:6px;place-items:center;width:23px;height:23px;display:grid}.workflow-node-image .workflow-node-icon{color:#67d8b6}.workflow-node-video .workflow-node-icon{color:#eca56e}.workflow-node-upload .workflow-node-icon{color:#c4a8ef}.workflow-node-concat .workflow-node-icon{color:#e3c969}.workflow-node-scene .workflow-node-icon{color:#ef82a7}.workflow-node-id{color:#6c7389;margin-left:auto;font:10px ui-monospace,monospace}.workflow-node-body{flex-direction:column;gap:7px;padding:11px;display:flex}.workflow-node label{color:#8d95ab;font-size:10px}.workflow-node input,.workflow-node textarea,.workflow-node select{color:#e5e8f5;width:100%;font:inherit;resize:vertical;background:#171a21;border:1px solid #3a3f4e;border-radius:5px;outline:0;padding:7px 8px;font-size:11px}.workflow-node input:focus,.workflow-node textarea:focus,.workflow-node select:focus{border-color:#707dce}.workflow-textarea{height:65px}.workflow-node-foot{color:#737b92;justify-content:space-between;align-items:center;margin-top:3px;font-size:10px;display:flex}.workflow-mini-button,.workflow-run-button{color:#e6e9ff;font:inherit;cursor:pointer;background:#303b71;border:1px solid #4c5aab;border-radius:5px;align-items:center;gap:4px;padding:5px 8px;font-size:10px;display:flex}.workflow-dropzone{color:#78819c;text-align:center;border:1px dashed #49516a;border-radius:6px;justify-content:center;align-items:center;gap:7px;height:74px;font-size:10px;display:flex}.workflow-upload{color:#a89ce4;cursor:pointer;border:1px dashed #525b79;border-radius:6px;flex-direction:column;justify-content:center;align-items:center;gap:5px;height:90px;display:flex;position:relative}.workflow-upload input{opacity:0;cursor:pointer;position:absolute;inset:0}.workflow-schema-fields{border-top:1px solid #323746;flex-direction:column;gap:6px;padding-top:4px;display:flex}.workflow-schema-title{color:#8d95ab;text-transform:uppercase;letter-spacing:.08em;font-size:9px}.workflow-schema-fields label{flex-direction:column;gap:4px;display:flex}.workflow-schema-check{align-items:center;flex-direction:row!important;gap:6px!important;display:flex!important}.workflow-schema-check input{width:auto}.workflow-node-cost{color:#78d9b0;margin-left:4px}.workflow-hint{color:#666e83;margin:0;font-size:9px;line-height:1.35}.workflow-scene-preview{color:#7b849e;background:#171a23;border:1px solid #3d4357;border-radius:6px;justify-content:center;align-items:center;height:80px;font-size:10px;display:flex;position:relative;overflow:hidden}.workflow-scene-grid{opacity:.32;background-color:#0000;background-image:linear-gradient(135deg,#0000 46%,#687090 47%,#0000 49%),linear-gradient(45deg,#0000 46%,#687090 47%,#0000 49%);background-position:0 0,0 0;background-repeat:repeat,repeat;background-size:22px 22px;background-attachment:scroll,scroll;background-origin:padding-box,padding-box;background-clip:border-box,border-box;position:absolute;inset:0}.workflow-scene-preview span{position:relative}.workflow-scene-controls{gap:4px;display:flex}.workflow-scene-controls button{color:#9da5ba;font:inherit;cursor:pointer;background:#292d39;border:1px solid #3b4050;border-radius:4px;flex:1;padding:5px 2px;font-size:9px}.workflow-handle{background:#8994ff;border:2px solid #20232d;width:9px;height:9px}.workflow-handle.target{background:#4c566f}.workflow-node-image .workflow-handle{background:#44c2a4}.workflow-node-video .workflow-handle{background:#d9955b}.workflow-node-scene .workflow-handle{background:#ef759d}.workflow-node-upload .workflow-handle{background:#a88cdb}.workflow-video-preview{background:#11141c;border:1px solid #3d4357;border-radius:6px;width:100%;max-height:150px;display:block}.workflow-provider-hint{color:#737b92;margin-top:-3px;font-size:9px}.workflow-node-search{color:#e5e8f5;width:100%;font:inherit;background:#11141b;border:1px solid #353a4a;border-radius:6px;outline:0;margin:0 0 8px;padding:8px 9px;font-size:11px}.workflow-node-search:focus{border-color:#707dce}.workflow-model-results{background:#12151d;border:1px solid #303545;border-radius:6px;flex-direction:column;gap:2px;margin:0 0 7px;padding:4px;display:flex}.workflow-model-results .workflow-hint{padding:6px}.workflow-node-motion-input .workflow-node-icon{color:#79b5ed}.workflow-motion-upload{height:70px}.workflow-node-motion-input .workflow-handle{background:#79b5ed}.workflow-agent-toggle{position:relative}.workflow-main{position:relative;overflow:hidden}.workflow-image-preview{object-fit:contain;background:#11141c;border:1px solid #3d4357;border-radius:6px;width:100%;max-height:150px;display:block}.workflow-error{color:#ef759d;font-size:9px;line-height:1.35}.workflow-preservation-ok{color:#78d9b0}.workflow-preservation-failed{background:#3a202c;border:1px solid #8b405d;border-radius:5px;padding:6px 7px}.workflow-versions{flex-direction:column;gap:5px;display:flex}.workflow-ab-toggle{cursor:pointer;border-color:#67d8b6;box-shadow:0 0 0 1px #67d8b64d}.workflow-version-bar{align-items:center;gap:4px;display:flex}.workflow-version-bar button{color:#9da5ba;font:inherit;cursor:pointer;background:#292d39;border:1px solid #3b4050;border-radius:4px;padding:4px 7px;font-size:9px;line-height:1}.workflow-version-bar button:hover:not(:disabled){color:#e5e8f5;border-color:#606b9a}.workflow-version-bar button:disabled{opacity:.4;cursor:default}.workflow-version-count{text-align:center;color:#78d9b0;flex:1;font:10px ui-monospace,monospace}.workflow-version-ab.active{color:#78d9b0;background:#25443c;border-color:#4c8f7c}.workflow-version-compare{color:#8d95ab;align-items:center;gap:6px;font-size:9px;flex-direction:row!important;display:flex!important}.workflow-version-compare select{flex:1;width:auto}.workflow-version-pin{color:#8d95ab;font-size:9px}.workflow-version-reuse{color:#e6e9ff;font:inherit;cursor:pointer;background:#303b71;border:1px solid #4c5aab;border-radius:5px;padding:5px 8px;font-size:10px}.workflow-version-reuse:hover{background:#3a468a;border-color:#7180d6}.workflow-node-shot-prompt .workflow-node-icon{color:#8fd4b0}.workflow-node-shot-prompt .workflow-handle{background:#8fd4b0}.workflow-shot-prompt-text{color:#cfe6da;height:92px;font:11px ui-monospace,monospace}.workflow-shot-prompt-copy{color:#a9e6c8;font:inherit;cursor:pointer;background:#25443c;border:1px solid #4c8f7c;border-radius:5px;justify-content:center;align-items:center;gap:5px;padding:5px 8px;font-size:10px;display:flex}.workflow-shot-prompt-copy:hover{background:#2e564b;border-color:#6fc0a2}.workflow-warning{color:#e8c78c;background:#2b2419;border:1px solid #8d6b3d;border-radius:5px;margin:0;padding:6px 7px;font-size:9px;line-height:1.35}.cozy-scene-node{color:#e4e9ef;background:#202631;border:1px solid #3b4756;border-radius:10px;width:318px;font:12px/1.35 Inter,system-ui,sans-serif;position:relative;overflow:visible;box-shadow:0 12px 28px #00000052}.cozy-scene-node.is-selected{border-color:#79b7e8;box-shadow:0 0 0 2px #79b7e842,0 12px 28px #00000052}.cozy-scene-node-header,.cozy-scene-node-footer{justify-content:space-between;align-items:center;gap:10px;padding:11px 13px;display:flex}.cozy-scene-node-kicker{color:#78a9d3;letter-spacing:.14em;font-size:9px;font-weight:800}.cozy-scene-node h3{margin:2px 0 0;font-size:14px;font-weight:700}.cozy-scene-node-status{color:#aebccc;text-align:right;max-width:112px;font-size:10px}.cozy-scene-node-status.has-pack{word-break:break-word;max-width:178px}.status-running{color:#f0c979}.status-complete{color:#8dd5b1}.status-error{color:#f08d8d}.cozy-scene-preview{background:linear-gradient(145deg,#32475d,#121820 70%);border:1px solid #3c4b5d;border-radius:6px;height:134px;margin:0 10px;position:relative;overflow:hidden}.cozy-scene-canvas{position:absolute;inset:0}.cozy-scene-live-frame{transform-origin:0 0;background:#111722;border:0;width:200%;height:200%;position:absolute;top:0;left:0;transform:scale(.5)}.cozy-scene-world-image{filter:saturate(.78)brightness(.82);background-image:url(/media/cozyclay-demo-poster.jpg);background-position:0;background-repeat:no-repeat;background-size:auto 250%;position:absolute;inset:0}.cozy-scene-world-tint{pointer-events:none;background:linear-gradient(#090f160a,#090f169e);position:absolute;inset:0}.cozy-scene-render-thumb{background:#111722;border:1px solid #3c4b5d;border-radius:5px;width:76px;margin:0;position:absolute;bottom:8px;left:8px;overflow:hidden;box-shadow:0 4px 10px #00000057}.cozy-scene-render-thumb img{aspect-ratio:16/9;object-fit:cover;width:100%;display:block}.cozy-scene-render-thumb figcaption{color:#9eb0c2;letter-spacing:.08em;text-transform:uppercase;background:#121722db;padding:2px 5px;font-size:9px;font-weight:800}.cozy-scene-grid{background:repeating-linear-gradient(90deg,#0000 0 19px,#85b4da2e 20px 21px),repeating-linear-gradient(0deg,#0000 0 17px,#85b4da24 18px 19px);position:absolute;inset:45% -25% -55%;transform:perspective(80px)rotateX(58deg)}.cozy-scene-stage-mark{background:#79b7e82e;border:2px solid #b4d7f1b8;border-radius:8px;width:78px;height:78px;position:absolute;top:25px;left:50%;transform:translate(-50%)rotate(45deg)}.cozy-scene-stage-mark span{background:#527c9d;border:1px solid #c3e5ff;border-radius:50%;width:8px;height:8px;position:absolute}.cozy-scene-stage-mark span:first-child{top:-5px;left:-5px}.cozy-scene-stage-mark span:nth-child(2){top:-5px;right:-5px}.cozy-scene-stage-mark span:nth-child(3){bottom:-5px;right:-5px}.cozy-scene-preview-copy{color:#d7e4ef;text-align:right;background:#121722c7;border-radius:5px;gap:1px;padding:4px 7px;display:grid;position:absolute;bottom:8px;right:8px}.cozy-scene-preview-copy strong{font-size:11px}.cozy-scene-preview-copy span{color:#9eb0c2;font-size:10px}.cozy-scene-controls{gap:8px;padding:11px 13px 9px;display:grid}.cozy-scene-control-row{gap:6px;display:flex}.cozy-scene-button,.cozy-scene-send,.cozy-scene-video,.cozy-scene-run,.cozy-scene-camera-row button{color:#d8e5f0;font:inherit;cursor:pointer;background:#2c3846;border:1px solid #465668;border-radius:5px;padding:5px 8px}.cozy-scene-button:hover,.cozy-scene-camera-row button:hover{background:#344658}.cozy-scene-run{background:#35658c;border-color:#5f96c3;margin-left:auto}.cozy-scene-run:disabled,.cozy-scene-send:disabled{cursor:wait;opacity:.7}.cozy-scene-send{color:#cfe6f8;background:#2a4256;border-color:#4d6c88}.cozy-scene-send:hover:not(:disabled){background:#33506a}.cozy-scene-handoff{align-items:center;gap:8px;display:flex}.cozy-scene-video{color:#cfe6f8;background:#2a4256;border-color:#4d6c88;flex:none}.cozy-scene-video:hover{background:#33506a}.cozy-scene-hint{color:#8799aa;margin:0;font-size:10px;line-height:1.3}.cozy-scene-frame-control{color:#acbbc9;grid-template-columns:auto 1fr auto;align-items:center;gap:7px;font-size:10px;display:grid}.cozy-scene-frame-control input{accent-color:#79b7e8;width:100%}.cozy-scene-frame-control output{color:#dce7f1;text-align:right;min-width:40px;font-family:ui-monospace,monospace}.cozy-scene-camera-row{color:#9eafbf;justify-content:center;align-items:center;gap:8px;font-size:10px;display:flex}.cozy-scene-camera-row button{padding:2px 7px}.cozy-scene-node-footer{color:#8799aa;border-top:1px solid #33404e;font-size:10px}.cozy-scene-node-footer span+span{margin-left:auto}.cozy-scene-node-inputs,.cozy-scene-node-outputs{pointer-events:none;width:12px;position:absolute;top:0;bottom:0}.cozy-scene-node-inputs{left:-7px}.cozy-scene-node-outputs{right:-7px}.cozy-scene-handle{pointer-events:auto;background:#243443;border:2px solid #a9d1f0;border-radius:50%;width:10px;height:10px;display:block;position:absolute}.cozy-scene-handle-target{left:0}.cozy-scene-handle-source{right:0}.cozy-scene-handle-asset{top:85px}.cozy-scene-handle-motion{top:115px}.cozy-scene-handle-render{top:108px}.cozy-scene-handle-scene{top:140px}.cozy-scene-reactflow-handle{z-index:5}.cozy-scene-reactflow-handle-asset{top:85px!important}.cozy-scene-reactflow-handle-motion{top:115px!important}.cozy-scene-reactflow-handle-render{top:108px!important}.cozy-scene-reactflow-handle-scene{top:140px!important}
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { ko, isKo } from "./locale.js";
|
|
2
|
-
import { buildH3MotionPrompt, FAL_MOTION_DURATIONS, FAL_MOTION_MIN_DURATION } from "./fal-motion-client.js";
|
|
3
|
-
|
|
4
|
-
// The Fal motion workflow split by what needs the viewport (#407 UX). The
|
|
5
|
-
// card stays in the Pose foldout and holds only the steps that operate on the
|
|
6
|
-
// 3D scene (framing, shaded mode, A/B capture, camera lock). The modal takes
|
|
7
|
-
// the text-heavy authoring (mode, description, duration, prompt, generate)
|
|
8
|
-
// onto a wide surface. A blocking overlay would hide the viewport, so capture
|
|
9
|
-
// never lives in the modal.
|
|
10
|
-
//
|
|
11
|
-
// App builds one `model` (state + derived booleans) and one `actions` object
|
|
12
|
-
// (handlers) and passes both to each surface, so the two cannot drift.
|
|
13
|
-
|
|
14
|
-
function Stepper({ step }) {
|
|
15
|
-
return (
|
|
16
|
-
<div className="fal-motion-stepper" aria-label={ko("Fal 모션 진행 단계", "Fal motion steps")}>
|
|
17
|
-
{[
|
|
18
|
-
[1, ko("음영", "Shaded")],
|
|
19
|
-
[2, "A"],
|
|
20
|
-
[3, "B"],
|
|
21
|
-
[4, ko("생성", "Generate")],
|
|
22
|
-
].map(([n, label]) => <span key={n} className={step === n ? "active" : step > n ? "done" : ""}><b>{n}</b>{label}</span>)}
|
|
23
|
-
</div>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function CameraStatus({ model, actions }) {
|
|
28
|
-
const { hasA, hasB, cameraMatch, cameraUnlocked, currentCameraMatch } = model;
|
|
29
|
-
return (
|
|
30
|
-
<>
|
|
31
|
-
<p className={"fal-motion-camera-status" + (cameraMatch ? " ready" : "")} data-testid="fal-motion-camera-status">
|
|
32
|
-
{hasA
|
|
33
|
-
? cameraUnlocked
|
|
34
|
-
? ko("카메라 잠금 해제됨 · A를 다시 캡처하면 새 기준을 저장합니다.", "Camera unlocked · recapture A to save a new reference.")
|
|
35
|
-
: currentCameraMatch
|
|
36
|
-
? cameraMatch ? ko("✓ A/B 카메라 프레이밍 일치 확인 · 카메라 잠금", "✓ A/B camera framing matched · camera locked") : ko("✓ A 카메라 기준 저장 · 카메라 잠금", "✓ A camera saved · camera locked")
|
|
37
|
-
: ko("A 이후 카메라가 바뀌었어요. A 카메라로 복원하세요.", "The camera changed after A. Restore the A camera.")
|
|
38
|
-
: ko("A를 먼저 캡처하면 카메라 기준을 저장합니다.", "Capture A first to save the camera reference.")}
|
|
39
|
-
</p>
|
|
40
|
-
{hasA && <div className="fal-motion-camera-actions">
|
|
41
|
-
<button type="button" className="btn ghost" onClick={actions.toggleCameraLock}>{cameraUnlocked ? ko("카메라 잠그기", "Lock camera") : ko("카메라 잠금 해제", "Unlock camera")}</button>
|
|
42
|
-
{!currentCameraMatch && <button type="button" className="btn ghost" onClick={actions.restoreCamera}>{ko("A 카메라로 복원", "Restore A camera")}</button>}
|
|
43
|
-
</div>}
|
|
44
|
-
</>
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function Thumbs({ model }) {
|
|
49
|
-
const { falMotion } = model;
|
|
50
|
-
if (!falMotion.a && !falMotion.b) return null;
|
|
51
|
-
return (
|
|
52
|
-
<div className="fal-motion-thumbs" aria-label={ko("Fal motion reference poses", "Fal motion reference poses")}>
|
|
53
|
-
{falMotion.a && <figure><img src={falMotion.a.dataUrl} alt="Pose A" /><figcaption>A · {falMotion.a.width}×{falMotion.a.height}</figcaption></figure>}
|
|
54
|
-
{falMotion.b && <figure><img src={falMotion.b.dataUrl} alt="Pose B" /><figcaption>B · {falMotion.b.width}×{falMotion.b.height}</figcaption></figure>}
|
|
55
|
-
</div>
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** The viewport-side strip in the Pose foldout. Capture and framing only. */
|
|
60
|
-
export function FalMotionCaptureCard({ model, actions, onOpen }) {
|
|
61
|
-
const { falMotion, mode, enabled, segmentationReady, step, hasA, hasB, cameraUnlocked, framingActive } = model;
|
|
62
|
-
return (
|
|
63
|
-
<section className="fal-motion-card" data-testid="fal-motion-studio" data-motion-enabled={enabled ? "true" : "false"}>
|
|
64
|
-
<div className="fal-motion-head">
|
|
65
|
-
<strong>{ko("Fal 모션 생성", "Fal motion")}</strong>
|
|
66
|
-
<span>{!enabled ? ko("QA 잠금", "QA lock") : falMotion.job?.status === "done" ? ko("완료", "Done") : falMotion.status === "error" ? ko("확인 필요", "Needs attention") : "H3 Max Turbo · 480P"}</span>
|
|
67
|
-
</div>
|
|
68
|
-
<Stepper step={step} />
|
|
69
|
-
<div className="fal-motion-segmentation-row">
|
|
70
|
-
<p className="inspector-hint fal-motion-ratio">{ko("샷 시점을 H3 비율(832×480)로 맞추고 구도를 잡으세요.", "Frame the shot at the H3 ratio (832×480) in the shot view.")}</p>
|
|
71
|
-
<button type="button" className="btn fal-motion-flat-cta" data-testid="fal-motion-frame-shot" onClick={actions.enterFraming} disabled={framingActive}>{ko("fal 비율로 샷 시점", "Fal-ratio shot view")}</button>
|
|
72
|
-
</div>
|
|
73
|
-
<div className={"fal-motion-segmentation-row" + (segmentationReady ? " ready" : "")}>
|
|
74
|
-
<p className="inspector-hint fal-motion-segmentation">{segmentationReady ? ko("색 세그멘테이션 음영 모드 ON · A/B 캡처 가능", "Shaded body-part segmentation ON · A/B capture ready") : ko("A/B 참조에는 부위 색상 음영 모드가 필요합니다.", "Shaded body-part colours are required for A/B refs.")}</p>
|
|
75
|
-
{!segmentationReady && <button type="button" className="btn fal-motion-flat-cta" onClick={actions.enableShaded}>{ko("음영 모드 켜기", "Enable Shaded")}</button>}
|
|
76
|
-
</div>
|
|
77
|
-
<div className="fal-motion-capture-status" aria-live="polite" data-testid="fal-motion-capture-status">
|
|
78
|
-
<div className={"fal-motion-capture-slot" + (hasA ? " captured" : "")} data-testid="fal-motion-ref-a-status">
|
|
79
|
-
<strong>A · {hasA ? ko("캡처 완료", "Captured") : ko("미캡처", "Not captured")}</strong>
|
|
80
|
-
<span>{hasA ? `${falMotion.a.width}×${falMotion.a.height} · Shaded ${falMotion.a.partColours.length}개` : ko("현재 프레임에서 A 캡처를 누르세요", "Press Capture A on the current frame")}</span>
|
|
81
|
-
</div>
|
|
82
|
-
<div className={"fal-motion-capture-slot" + (hasB ? " captured" : "")} data-testid="fal-motion-ref-b-status">
|
|
83
|
-
<strong>B · {hasB ? ko("캡처 완료", "Captured") : ko("미캡처", "Not captured")}</strong>
|
|
84
|
-
<span>{hasB ? `${falMotion.b.width}×${falMotion.b.height} · Shaded ${falMotion.b.partColours.length}개` : ko("B 포즈를 만든 뒤 카메라를 움직이지 말고 B 캡처를 누르세요", "Set the B pose, keep the camera still, then press Capture B")}</span>
|
|
85
|
-
{hasB && <button type="button" className="fal-motion-slot-action" onClick={() => actions.clearPose("b")}>{ko("B 제거", "Remove B")}</button>}
|
|
86
|
-
</div>
|
|
87
|
-
</div>
|
|
88
|
-
<CameraStatus model={model} actions={actions} />
|
|
89
|
-
<div className="fal-motion-pose-row">
|
|
90
|
-
<button type="button" className={falMotion.a ? "btn active" : "btn"} disabled={!segmentationReady} onClick={() => actions.markPose("a")}>{falMotion.a ? ko("A 재캡처", "Recapture A") : ko("A 캡처", "Capture A")}</button>
|
|
91
|
-
{mode === "interpolate" && <button type="button" className={falMotion.b ? "btn active" : "btn"} disabled={!segmentationReady || cameraUnlocked} onClick={() => actions.markPose("b")}>{falMotion.b ? ko("B 재캡처", "Recapture B") : ko("B 캡처", "Capture B")}</button>}
|
|
92
|
-
{(falMotion.a || falMotion.b) && <button type="button" className="btn ghost" onClick={actions.clear}>{ko("초기화", "Clear")}</button>}
|
|
93
|
-
</div>
|
|
94
|
-
<Thumbs model={model} />
|
|
95
|
-
<div className="fal-motion-open-row">
|
|
96
|
-
<button type="button" className="btn primary fal-motion-open" data-testid="fal-motion-open" onClick={onOpen}>{ko("동작 설명 · 생성", "Describe & generate")}</button>
|
|
97
|
-
</div>
|
|
98
|
-
</section>
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** The wide authoring surface: description, duration, prompt, generate. */
|
|
103
|
-
export function FalMotionModal({ model, actions, onClose }) {
|
|
104
|
-
const { falMotion, mode, enabled, hasA, hasB, step } = model;
|
|
105
|
-
const interpolate = mode === "interpolate";
|
|
106
|
-
const generateDisabled = interpolate
|
|
107
|
-
? (!enabled || falMotion.status === "submitting" || falMotion.status === "queued" || !hasA || !hasB)
|
|
108
|
-
: (!enabled || falMotion.status === "submitting" || falMotion.status === "queued" || !hasA || !!falMotion.b || !falMotion.instruction.trim());
|
|
109
|
-
const generateTitle = !enabled
|
|
110
|
-
? ko("소유자 테스트가 끝날 때까지 잠겨 있습니다.", "Locked until owner testing is complete.")
|
|
111
|
-
: interpolate && !hasB
|
|
112
|
-
? ko("B 포즈를 먼저 캡처하세요.", "Capture B before generating.")
|
|
113
|
-
: !interpolate && !!falMotion.b
|
|
114
|
-
? ko("A만 동작 모드에서는 B를 제거하세요.", "Remove B for A-only action mode.")
|
|
115
|
-
: !interpolate && !falMotion.instruction.trim()
|
|
116
|
-
? ko("동작 설명을 입력하세요.", "Enter an action description.")
|
|
117
|
-
: "";
|
|
118
|
-
const busy = falMotion.status === "submitting" || falMotion.status === "queued";
|
|
119
|
-
return (
|
|
120
|
-
<div className="modal-overlay" onClick={onClose}>
|
|
121
|
-
<div className="modal fal-motion-modal" role="dialog" aria-modal="true" aria-labelledby="fal-motion-title" onClick={(event) => event.stopPropagation()}>
|
|
122
|
-
<div className="modal-head">
|
|
123
|
-
<h3 id="fal-motion-title">{ko("Fal 모션 생성", "Fal motion")}</h3>
|
|
124
|
-
<button type="button" className="x" onClick={onClose} aria-label={ko("닫기", "Close")}>✕</button>
|
|
125
|
-
</div>
|
|
126
|
-
<div className="fal-motion-modal-body">
|
|
127
|
-
<div className="fal-motion-modal-left">
|
|
128
|
-
<Stepper step={step} />
|
|
129
|
-
<Thumbs model={model} />
|
|
130
|
-
{step < 4 && <p className="inspector-hint">{ko("A/B 캡처는 뷰포트에서 합니다. 닫고 카드에서 캡처하세요.", "Capture A/B in the viewport — close this and use the card.")}</p>}
|
|
131
|
-
{falMotion.job?.status === "done" && falMotion.job.video?.url && <video className="fal-motion-video" src={falMotion.job.video.url} controls playsInline preload="metadata" />}
|
|
132
|
-
</div>
|
|
133
|
-
<div className="fal-motion-modal-right">
|
|
134
|
-
<div className="fal-motion-mode-tabs" role="tablist" aria-label={ko("Fal 모션 방식", "Fal motion mode")}>
|
|
135
|
-
<button type="button" role="tab" aria-selected={interpolate} className={interpolate ? "active" : ""} onClick={() => actions.setMode("interpolate")}>{ko("A→B 보간", "A→B interpolate")}</button>
|
|
136
|
-
<button type="button" role="tab" aria-selected={!interpolate} className={!interpolate ? "active" : ""} onClick={() => actions.setMode("act")}>{ko("A만 동작", "A-only action")}</button>
|
|
137
|
-
</div>
|
|
138
|
-
<p className="inspector-hint fal-motion-mode-hint">
|
|
139
|
-
{interpolate
|
|
140
|
-
? ko("A와 B 사이를 어떻게 움직일지 설명과 함께 만듭니다.", "Describe how the character moves from A to B.")
|
|
141
|
-
: ko("A 포즈 하나와 동작 설명으로 움직임을 만듭니다.", "Creates motion from one A pose and an action description.")}
|
|
142
|
-
</p>
|
|
143
|
-
<textarea
|
|
144
|
-
className="fal-motion-instruction"
|
|
145
|
-
value={falMotion.instruction}
|
|
146
|
-
placeholder={interpolate
|
|
147
|
-
? ko("A에서 B로 어떻게 움직이는지 적으세요. 예: 벤치로 걸어가 돌아서 앉는다", "Describe the motion from A to B. Example: walk to the bench, turn, and sit")
|
|
148
|
-
: ko("A만 캡처한 뒤 동작을 적으세요. 예: 검을 머리 위로 휘두르고 한 걸음 전진", "With A only, describe the action. Example: swing the sword overhead and step forward")}
|
|
149
|
-
onChange={(event) => actions.setFalMotion((current) => ({ ...current, instruction: event.target.value }))}
|
|
150
|
-
/>
|
|
151
|
-
<div className="fal-motion-duration-row">
|
|
152
|
-
<span className="inspector-hint">{ko("길이", "Length")}</span>
|
|
153
|
-
{FAL_MOTION_DURATIONS.map((seconds) => (
|
|
154
|
-
<button key={seconds} type="button" className={"btn small" + ((falMotion.duration ?? FAL_MOTION_MIN_DURATION) === seconds ? " active" : "")} onClick={() => actions.setFalMotion((current) => ({ ...current, duration: seconds }))}>{seconds}{ko("초", "s")}</button>
|
|
155
|
-
))}
|
|
156
|
-
</div>
|
|
157
|
-
<details className="fal-motion-prompt-edit">
|
|
158
|
-
<summary>{ko("보낼 프롬프트", "Prompt to send")}</summary>
|
|
159
|
-
<textarea
|
|
160
|
-
className="fal-motion-instruction fal-motion-prompt-override"
|
|
161
|
-
value={falMotion.promptOverride ?? ""}
|
|
162
|
-
placeholder={buildH3MotionPrompt(falMotion.instruction, { interpolate })}
|
|
163
|
-
onChange={(event) => actions.setFalMotion((current) => ({ ...current, promptOverride: event.target.value }))}
|
|
164
|
-
/>
|
|
165
|
-
<p className="inspector-hint">{ko("비워 두면 위 설명으로 자동 생성됩니다. 직접 고치면 그대로 전송됩니다.", "Left blank, this is built from the description above. Edit it and your text is sent verbatim.")}</p>
|
|
166
|
-
</details>
|
|
167
|
-
{falMotion.status === "error" && <p className="studio-hint error fal-motion-inline-error" role="alert">{falMotion.error}</p>}
|
|
168
|
-
<div className="fal-motion-actions">
|
|
169
|
-
<button type="button" className="btn primary" title={generateTitle} disabled={generateDisabled} onClick={() => actions.generate(mode)}>
|
|
170
|
-
{busy ? ko("생성 중…", "Generating…") : interpolate ? ko("A→B 보간 생성", "Interpolate A→B") : ko("동작 생성", "Generate action")}
|
|
171
|
-
</button>
|
|
172
|
-
</div>
|
|
173
|
-
{!enabled && <p className="inspector-hint">{ko("소유자 테스트가 끝날 때까지 생성 요청은 서버에서 차단됩니다.", "Generation requests stay blocked on the server until owner testing is complete.")}</p>}
|
|
174
|
-
{falMotion.dailyRemaining !== null && <p className="inspector-hint">{ko(`오늘 남은 생성 ${falMotion.dailyRemaining}회`, `${falMotion.dailyRemaining} motion generations left today`)}</p>}
|
|
175
|
-
</div>
|
|
176
|
-
</div>
|
|
177
|
-
</div>
|
|
178
|
-
</div>
|
|
179
|
-
);
|
|
180
|
-
}
|
package/src/scene-history.js
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
// The single mutation owner for scene objects. All four producer classes —
|
|
2
|
-
// gizmo, plan board, inspector number scrubs, one-shot hierarchy/inspector
|
|
3
|
-
// atomics — mutate through this store so a drag becomes exactly ONE undo
|
|
4
|
-
// entry and can be cancelled. No React, node-importable (the test seam).
|
|
5
|
-
import {
|
|
6
|
-
createHistory,
|
|
7
|
-
pushHistory,
|
|
8
|
-
undoHistory,
|
|
9
|
-
redoHistory,
|
|
10
|
-
canUndo as historyCanUndo,
|
|
11
|
-
canRedo as historyCanRedo,
|
|
12
|
-
createTransactions,
|
|
13
|
-
beginTransaction,
|
|
14
|
-
isCurrentTransaction,
|
|
15
|
-
endTransaction,
|
|
16
|
-
settleTransaction,
|
|
17
|
-
} from "./history.js";
|
|
18
|
-
|
|
19
|
-
// Invariant, stated once: whenever no transaction is open,
|
|
20
|
-
// `store.present()` is reference-equal to `store.objects`. `history` is
|
|
21
|
-
// assigned in exactly four places — applyAtomic, end(commit:true), settle,
|
|
22
|
-
// and undo/redo — and every one pushes or steps to the POST-change present.
|
|
23
|
-
export function createSceneHistoryStore(initialObjects, { onObjects, onCommit }) {
|
|
24
|
-
let objects = initialObjects;
|
|
25
|
-
let history = createHistory(initialObjects);
|
|
26
|
-
const tx = createTransactions();
|
|
27
|
-
let before = null;
|
|
28
|
-
|
|
29
|
-
function emit(next) {
|
|
30
|
-
objects = next;
|
|
31
|
-
onObjects?.(next);
|
|
32
|
-
}
|
|
33
|
-
// A settle is triggered by the user starting something else; the
|
|
34
|
-
// travel already applied is real and becomes its own entry, so the
|
|
35
|
-
// very next Ctrl+Z discards it deliberately.
|
|
36
|
-
function settle(notify = true) {
|
|
37
|
-
if (settleTransaction(tx) === null) return;
|
|
38
|
-
history = pushHistory(history, objects);
|
|
39
|
-
if (notify) onCommit?.(before, objects);
|
|
40
|
-
before = null;
|
|
41
|
-
}
|
|
42
|
-
return {
|
|
43
|
-
get objects() {
|
|
44
|
-
return objects;
|
|
45
|
-
},
|
|
46
|
-
|
|
47
|
-
// Public so the present === objects invariant is assertable from
|
|
48
|
-
// Node and from QA without touching private state.
|
|
49
|
-
present() {
|
|
50
|
-
return history.present;
|
|
51
|
-
},
|
|
52
|
-
|
|
53
|
-
// Settle any open drag first, then apply one atomic mutation. A fn
|
|
54
|
-
// that returns the same array creates no entry (coalescing).
|
|
55
|
-
applyAtomic(fn) {
|
|
56
|
-
settle();
|
|
57
|
-
const next = fn(objects);
|
|
58
|
-
if (next === objects) return;
|
|
59
|
-
const previous = objects;
|
|
60
|
-
emit(next);
|
|
61
|
-
history = pushHistory(history, next);
|
|
62
|
-
onCommit?.(previous, next);
|
|
63
|
-
},
|
|
64
|
-
|
|
65
|
-
// Settle first so a nested begin commits the previous drag as ONE
|
|
66
|
-
// entry; history is not touched at begin.
|
|
67
|
-
begin(owner, cancel) {
|
|
68
|
-
settle();
|
|
69
|
-
before = objects;
|
|
70
|
-
return beginTransaction(tx, { owner, cancel });
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
// Stream applies land on the live array only while the token is the
|
|
74
|
-
// current transaction; history is not touched here.
|
|
75
|
-
applyIn(token, fn) {
|
|
76
|
-
if (!isCurrentTransaction(tx, token)) return;
|
|
77
|
-
const next = fn(objects);
|
|
78
|
-
if (next === objects) return;
|
|
79
|
-
emit(next);
|
|
80
|
-
},
|
|
81
|
-
|
|
82
|
-
end(token, { commit }) {
|
|
83
|
-
if (!endTransaction(tx, token)) return false;
|
|
84
|
-
if (commit) {
|
|
85
|
-
// pushHistory coalesces: nothing applied pushes nothing.
|
|
86
|
-
history = pushHistory(history, objects);
|
|
87
|
-
onCommit?.(before, objects);
|
|
88
|
-
} else {
|
|
89
|
-
// Rollback restores the whole pre-drag array by reference —
|
|
90
|
-
// strictly more correct than replaying one record.
|
|
91
|
-
emit(before);
|
|
92
|
-
}
|
|
93
|
-
before = null;
|
|
94
|
-
return true;
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
settle,
|
|
98
|
-
|
|
99
|
-
undo() {
|
|
100
|
-
settle(false);
|
|
101
|
-
const stepped = undoHistory(history);
|
|
102
|
-
if (stepped === null) return null;
|
|
103
|
-
history = stepped;
|
|
104
|
-
emit(stepped.present);
|
|
105
|
-
return stepped.present;
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
redo() {
|
|
109
|
-
settle(false);
|
|
110
|
-
const stepped = redoHistory(history);
|
|
111
|
-
if (stepped === null) return null;
|
|
112
|
-
history = stepped;
|
|
113
|
-
emit(stepped.present);
|
|
114
|
-
return stepped.present;
|
|
115
|
-
},
|
|
116
|
-
|
|
117
|
-
canUndo() {
|
|
118
|
-
return historyCanUndo(history);
|
|
119
|
-
},
|
|
120
|
-
|
|
121
|
-
canRedo() {
|
|
122
|
-
return historyCanRedo(history);
|
|
123
|
-
},
|
|
124
|
-
|
|
125
|
-
depths() {
|
|
126
|
-
return { past: history.past.length, future: history.future.length };
|
|
127
|
-
},
|
|
128
|
-
};
|
|
129
|
-
}
|
|
Binary file
|