cozyclay 1.10.0 → 2.0.0
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 +137 -0
- package/bin/agent/agent-routes.mjs +74 -70
- 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/cozyclay.mjs +26 -8
- package/bin/live/cli.mjs +22 -3
- package/bin/mcp-runtime.mjs +35 -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-C1sdVbg4.css +1 -0
- package/dist/assets/app-CA55tcI3.js +4876 -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-Yrt99wZx.js +17 -0
- package/dist/assets/{ticket-BgIzOBOf.js → ticket-BcmsnHn5.js} +1 -1
- package/dist/assets/workflow-DRdwoDBT.css +1 -0
- package/dist/assets/workflow-dNPGY9xz.js +179 -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 +6 -6
- 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 +1249 -9076
- 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 +1199 -0
- package/src/ardy/timeline.jsx +149 -41
- package/src/ardy/waypoints.js +2 -2
- package/src/asset-pane.css +462 -0
- package/src/asset-pane.jsx +461 -232
- 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 +817 -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 +284 -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 +5 -0
- 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 +443 -1753
- package/src/theme.js +44 -0
- package/src/timeline-extent.js +16 -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 +82 -69
- 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/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -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 +58 -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
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import SourceOffer from "../source-offer.jsx";
|
|
3
|
+
import { useStudioShell } from "./studio-shell-context.js";
|
|
4
|
+
import { ko } from "../locale.js";
|
|
5
|
+
import "./topbar.css";
|
|
6
|
+
|
|
7
|
+
const BUSY = ["preparing", "encoding", "finalizing"];
|
|
8
|
+
|
|
9
|
+
// 2a status bar: status text on the left (export progress, retry and cancel
|
|
10
|
+
// take that slot while an export runs, G7); fps and the source offer on the
|
|
11
|
+
// right. #570: the save state sits under the project name (ProjectHead).
|
|
12
|
+
// In the studio shell there is no standing row: a fixed "Ready", the fps the
|
|
13
|
+
// Sequencer already shows and a source link Help › About already carries were
|
|
14
|
+
// a band of noise under the dock. Only a live status (an export, or a status
|
|
15
|
+
// message) shows, as a capsule floating in the viewport's bottom-left corner.
|
|
16
|
+
// Embeds have no menu bar, so they keep the full row and its source offer.
|
|
17
|
+
export default function StatusBar({ embedded = false }) {
|
|
18
|
+
const { statusText, exportStatus, exportPhaseLabel, resultOpen, recState, tlFps } = useStudioShell();
|
|
19
|
+
// A frame export's status is already shown inside its result dialog.
|
|
20
|
+
const exportShown = exportStatus && !(resultOpen && exportStatus.kind === "frame");
|
|
21
|
+
if (!embedded) {
|
|
22
|
+
if (!exportShown && !statusText) return null;
|
|
23
|
+
return (
|
|
24
|
+
<footer className="brandbar v2-statusbar statusbar-float">
|
|
25
|
+
{exportShown
|
|
26
|
+
? <ExportStatus status={exportStatus} label={exportPhaseLabel} recording={recState === "recording"} />
|
|
27
|
+
: <span className="statusbar-text">{statusText}</span>}
|
|
28
|
+
</footer>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return (
|
|
32
|
+
<footer className="brandbar v2-statusbar">
|
|
33
|
+
<div className="statusbar-left">
|
|
34
|
+
{exportShown
|
|
35
|
+
? <ExportStatus status={exportStatus} label={exportPhaseLabel} recording={recState === "recording"} />
|
|
36
|
+
: <span className="statusbar-text">{statusText || ko("Ready", "준비")}</span>}
|
|
37
|
+
</div>
|
|
38
|
+
<span className="statusbar-fps" title={ko("Timeline frame rate", "타임라인 프레임 레이트")}>{tlFps} fps</span>
|
|
39
|
+
<SourceOffer />
|
|
40
|
+
</footer>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function ExportStatus({ status, label, recording }) {
|
|
45
|
+
const { phase, kind, message, completedFrames, frameCount, stage, cancellable, retryable, handedOff, cancel, retry } = status;
|
|
46
|
+
const busy = BUSY.includes(phase);
|
|
47
|
+
const detail = [
|
|
48
|
+
message,
|
|
49
|
+
retryable && ko("Retry keeps the original shot, camera, range and settings; it does not change your edits.", "다시 시도하면 원래 샷·카메라·범위·설정을 사용하며 편집 내용은 바꾸지 않아요."),
|
|
50
|
+
handedOff > 0 && ko(`${handedOff} file(s) already handed off. Retry skips those downloads.`, `파일 ${handedOff}개는 이미 전달했어요. 다시 시도할 때 해당 다운로드는 건너뛰어요.`),
|
|
51
|
+
busy && stage === "mux" && ko("Finalizing MP4; this stage cannot be interrupted.", "MP4 마무리 중이에요. 이 단계는 중단할 수 없어요."),
|
|
52
|
+
].filter(Boolean).join(" ");
|
|
53
|
+
return (
|
|
54
|
+
<section
|
|
55
|
+
className="export-status statusbar-export"
|
|
56
|
+
data-testid="export-status"
|
|
57
|
+
data-phase={phase}
|
|
58
|
+
data-kind={kind}
|
|
59
|
+
role="status"
|
|
60
|
+
aria-live="polite"
|
|
61
|
+
aria-atomic="true"
|
|
62
|
+
title={detail || undefined}
|
|
63
|
+
>
|
|
64
|
+
<strong className="statusbar-export-phase">{label(phase)}{status.label ? ` · ${status.label}` : ""}</strong>
|
|
65
|
+
{busy && frameCount > 0 && (
|
|
66
|
+
<>
|
|
67
|
+
<progress aria-label={label(phase)} max={frameCount} value={completedFrames} />
|
|
68
|
+
<span className="statusbar-export-count">{completedFrames}/{frameCount}</span>
|
|
69
|
+
</>
|
|
70
|
+
)}
|
|
71
|
+
{!busy && message && <span className="statusbar-export-message">{message}</span>}
|
|
72
|
+
{busy && cancellable && (
|
|
73
|
+
<button type="button" className="statusbar-action" data-testid="export-cancel" onClick={cancel}>
|
|
74
|
+
{ko("Cancel", "취소")}
|
|
75
|
+
</button>
|
|
76
|
+
)}
|
|
77
|
+
{retryable && (
|
|
78
|
+
<button type="button" className="statusbar-action" data-testid="export-retry" disabled={recording} onClick={() => void retry()}>
|
|
79
|
+
{ko("Retry", "재시도")}
|
|
80
|
+
</button>
|
|
81
|
+
)}
|
|
82
|
+
</section>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function relativeTime(ms) {
|
|
87
|
+
const minutes = Math.floor(ms / 60000);
|
|
88
|
+
if (minutes < 1) return ko("just now", "방금");
|
|
89
|
+
if (minutes < 60) return ko(`${minutes} min ago`, `${minutes}분 전`);
|
|
90
|
+
const hours = Math.floor(minutes / 60);
|
|
91
|
+
return ko(`${hours} h ago`, `${hours}시간 전`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// "Saved 2 min ago" once a save completes in this session; otherwise the
|
|
95
|
+
// project's own save state (Unsaved changes, Saving…, Save failed, Not saved).
|
|
96
|
+
export function SaveState({ status }) {
|
|
97
|
+
const [savedAt, setSavedAt] = useState(null);
|
|
98
|
+
const [now, setNow] = useState(() => Date.now());
|
|
99
|
+
const previous = useRef(status.state);
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (previous.current === "saving" && status.state === "saved") setSavedAt(Date.now());
|
|
102
|
+
if (status.dirty) setSavedAt(null);
|
|
103
|
+
previous.current = status.state;
|
|
104
|
+
}, [status.state, status.dirty]);
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (savedAt === null) return undefined;
|
|
107
|
+
setNow(Date.now());
|
|
108
|
+
const timer = setInterval(() => setNow(Date.now()), 30000);
|
|
109
|
+
return () => clearInterval(timer);
|
|
110
|
+
}, [savedAt]);
|
|
111
|
+
const text = savedAt !== null && status.state === "saved" && !status.dirty
|
|
112
|
+
? ko(`Saved ${relativeTime(now - savedAt)}`, `저장됨 · ${relativeTime(now - savedAt)}`)
|
|
113
|
+
: status.text;
|
|
114
|
+
return (
|
|
115
|
+
<span
|
|
116
|
+
className={"project-save-status status-" + status.state}
|
|
117
|
+
data-testid="project-save-status"
|
|
118
|
+
data-dirty={status.dirty ? "true" : undefined}
|
|
119
|
+
role="status"
|
|
120
|
+
aria-live="polite"
|
|
121
|
+
>
|
|
122
|
+
{text}
|
|
123
|
+
</span>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { ko } from "../locale.js";
|
|
3
|
+
import { useStudioShell } from "./studio-shell-context.js";
|
|
4
|
+
import TopBar, { ProjectHead } from "./TopBar.jsx";
|
|
5
|
+
import OutlinerSlot from "./OutlinerSlot.jsx";
|
|
6
|
+
import DetailsSlot from "./DetailsSlot.jsx";
|
|
7
|
+
import BottomDock from "./BottomDock.jsx";
|
|
8
|
+
import StatusBar from "./StatusBar.jsx";
|
|
9
|
+
import PreferencesSlot from "./PreferencesSlot.jsx";
|
|
10
|
+
import "./shell.css";
|
|
11
|
+
import "./glass.css";
|
|
12
|
+
import "./glass-regions.css";
|
|
13
|
+
import "./agent-glass.css";
|
|
14
|
+
|
|
15
|
+
const DOCK_TAB_KEY = "cozyclay.dock.tab.v1";
|
|
16
|
+
|
|
17
|
+
function readDockTab() {
|
|
18
|
+
try {
|
|
19
|
+
return globalThis.localStorage?.getItem(DOCK_TAB_KEY) === "assets" ? "assets" : "animation";
|
|
20
|
+
} catch {
|
|
21
|
+
return "animation";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// #570 floating-glass shell: the viewport fills the window and every region
|
|
26
|
+
// floats over it. Left: the Outliner. Right: Details over Agent. Bottom, from
|
|
27
|
+
// the left edge to the right column: the dock, switching between Animation
|
|
28
|
+
// (the Sequencer with the shot preview at its right end) and Assets.
|
|
29
|
+
export default function StudioShell({ viewport, children, ...props }) {
|
|
30
|
+
const { beginWorkspaceResize } = useStudioShell();
|
|
31
|
+
const [dockTab, setDockTab] = useState(readDockTab);
|
|
32
|
+
// Embeds (playview, playground) keep the Sequencer-only dock.
|
|
33
|
+
const embedded = Boolean(props["data-embed-mode"]);
|
|
34
|
+
const shownTab = embedded ? "animation" : dockTab;
|
|
35
|
+
function changeDockTab(next) {
|
|
36
|
+
setDockTab(next);
|
|
37
|
+
try {
|
|
38
|
+
globalThis.localStorage?.setItem(DOCK_TAB_KEY, next);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.warn(`[cozyclay] could not store ${DOCK_TAB_KEY}`, error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return (
|
|
44
|
+
<div {...props} data-shell="glass" data-dock-tab={shownTab}>
|
|
45
|
+
<TopBar preferences={<PreferencesSlot />} />
|
|
46
|
+
<div className="main">
|
|
47
|
+
<div className="workspace">
|
|
48
|
+
{viewport}
|
|
49
|
+
<div className="studio-left-column">
|
|
50
|
+
<ProjectHead />
|
|
51
|
+
<OutlinerSlot />
|
|
52
|
+
</div>
|
|
53
|
+
<div
|
|
54
|
+
className="workspace-splitter shell-splitter shell-left-splitter"
|
|
55
|
+
role="separator"
|
|
56
|
+
aria-orientation="vertical"
|
|
57
|
+
aria-label={ko("Resize hierarchy panel", "계층 패널 크기 조절")}
|
|
58
|
+
onPointerDown={(event) => beginWorkspaceResize("left", event)}
|
|
59
|
+
/>
|
|
60
|
+
<div className="studio-right-column">
|
|
61
|
+
<DetailsSlot />
|
|
62
|
+
</div>
|
|
63
|
+
<div
|
|
64
|
+
className="workspace-splitter workspace-splitter-vertical shell-splitter shell-sidebar-splitter"
|
|
65
|
+
role="separator"
|
|
66
|
+
aria-orientation="vertical"
|
|
67
|
+
aria-label={ko("Resize hierarchy and inspector panel", "계층 및 속성 패널 크기 조절")}
|
|
68
|
+
onPointerDown={(event) => beginWorkspaceResize("sidebar", event)}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
<div className="studio-dock-slot">
|
|
72
|
+
<div
|
|
73
|
+
className="workspace-splitter timeline-splitter"
|
|
74
|
+
role="separator"
|
|
75
|
+
aria-label={ko("Resize frame monitor", "프레임 모니터 크기 조절")}
|
|
76
|
+
onPointerDown={(event) => beginWorkspaceResize("timeline", event)}
|
|
77
|
+
/>
|
|
78
|
+
<BottomDock tab={shownTab} onTabChange={changeDockTab} embedded={embedded} />
|
|
79
|
+
<div
|
|
80
|
+
className="workspace-splitter shell-splitter shell-camera-splitter"
|
|
81
|
+
role="separator"
|
|
82
|
+
aria-orientation="vertical"
|
|
83
|
+
aria-label={ko("Resize shot preview", "샷 미리보기 크기 조절")}
|
|
84
|
+
onPointerDown={(event) => beginWorkspaceResize("camera", event)}
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<StatusBar embedded={embedded} />
|
|
89
|
+
{children}
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { useRef, useState } from "react";
|
|
2
|
+
import { useStudioShell } from "./studio-shell-context.js";
|
|
3
|
+
import ProjectPanel from "../panels/ProjectPanel.jsx";
|
|
4
|
+
import MenuBar, { MenuPopover } from "./MenuBar.jsx";
|
|
5
|
+
import { SaveState } from "./StatusBar.jsx";
|
|
6
|
+
import { ko, isKo } from "../locale.js";
|
|
7
|
+
import "./topbar.css";
|
|
8
|
+
|
|
9
|
+
// 2a top bar: File/Edit/Window/Help, then only the MCP state and the one
|
|
10
|
+
// primary action. Save and Export live in File (G7). #570: the project name
|
|
11
|
+
// and its save state head the left column (ProjectHead below).
|
|
12
|
+
export default function TopBar({ preferences }) {
|
|
13
|
+
const { recState, liveHubStatus, liveWorkspaceHandle } = useStudioShell();
|
|
14
|
+
const connected = liveHubStatus === "connected";
|
|
15
|
+
return (
|
|
16
|
+
<header className="topbar v2-topbar" data-rec-state={recState}>
|
|
17
|
+
<MenuBar preferences={preferences} />
|
|
18
|
+
<div className="topbar-spacer" />
|
|
19
|
+
<span
|
|
20
|
+
className={"topbar-mcp" + (liveWorkspaceHandle ? " live-workspace-handle" : "")}
|
|
21
|
+
data-state={connected ? "connected" : "disconnected"}
|
|
22
|
+
data-live-workspace={liveWorkspaceHandle || undefined}
|
|
23
|
+
title={liveWorkspaceHandle || ko("No MCP client is attached to this studio", "이 스튜디오에 연결된 MCP 클라이언트가 없어요")}
|
|
24
|
+
>
|
|
25
|
+
<i className="topbar-mcp-dot" aria-hidden="true" />
|
|
26
|
+
<span className="topbar-mcp-text">{connected ? ko("MCP connected", "MCP 연결됨") : ko("MCP offline", "MCP 꺼짐")}</span>
|
|
27
|
+
</span>
|
|
28
|
+
<GenerateMotion />
|
|
29
|
+
</header>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// #570: the project heads the left column — its name opens the project menu,
|
|
34
|
+
// the line under it is the save state.
|
|
35
|
+
export function ProjectHead() {
|
|
36
|
+
const {
|
|
37
|
+
projectMenuOpen, setProjectMenuOpen, projectDirty, projectName, projectStartupOpen,
|
|
38
|
+
projectManifest, saveStatus,
|
|
39
|
+
} = useStudioShell();
|
|
40
|
+
return (
|
|
41
|
+
<div className="topbar-project studio-project-head">
|
|
42
|
+
<ProjectPanel
|
|
43
|
+
projectMenuOpen={projectMenuOpen}
|
|
44
|
+
setProjectMenuOpen={setProjectMenuOpen}
|
|
45
|
+
projectDirty={projectDirty}
|
|
46
|
+
projectName={projectName}
|
|
47
|
+
projectStartupOpen={projectStartupOpen}
|
|
48
|
+
projectManifest={projectManifest}
|
|
49
|
+
/>
|
|
50
|
+
<SaveState status={saveStatus} />
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// "Generate Motion" is today's Scene generate (G6) and the one home for
|
|
56
|
+
// generation (IS-2): with authored prompt blocks it runs them all, as the
|
|
57
|
+
// removed Details "Generate all blocks" did; otherwise it runs the prompt.
|
|
58
|
+
// The caret holds the take bar's Scene actions; a refused action says why in
|
|
59
|
+
// place and in a toast (R3).
|
|
60
|
+
function GenerateMotion() {
|
|
61
|
+
const {
|
|
62
|
+
runArdy, sceneGenerateDisabledReason, sceneAgainDisabledReason, runSceneAgain,
|
|
63
|
+
addSceneBlock, tlFrame, setToast, ardyRunning, promptClips, runStudioAction,
|
|
64
|
+
} = useStudioShell();
|
|
65
|
+
const hasBlocks = promptClips.some((clip) => clip.text.trim());
|
|
66
|
+
const [open, setOpen] = useState(false);
|
|
67
|
+
const caretRef = useRef(null);
|
|
68
|
+
const reason = sceneGenerateDisabledReason();
|
|
69
|
+
const actions = [
|
|
70
|
+
{ id: "new", label: ko("Start over", "새로 만들기"), reason, run: () => runArdy({ fresh: true }) },
|
|
71
|
+
{ id: "again", label: ko("Take it again", "다시 뽑기"), reason: sceneAgainDisabledReason(), run: runSceneAgain },
|
|
72
|
+
{ id: "block", label: isKo ? `프레임 ${tlFrame}에 블록 추가` : `Add block at frame ${tlFrame}`, reason: "", run: addSceneBlock },
|
|
73
|
+
];
|
|
74
|
+
return (
|
|
75
|
+
<div className="topbar-generate" role="group" aria-label={ko("Generate Motion", "모션 생성")}>
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
className="topbar-generate-main"
|
|
79
|
+
data-testid="topbar-generate"
|
|
80
|
+
aria-disabled={reason ? "true" : undefined}
|
|
81
|
+
data-disabled-reason={reason || undefined}
|
|
82
|
+
title={reason || ko("Generate motion from the prompt and blocks", "프롬프트와 블록으로 모션을 생성합니다")}
|
|
83
|
+
onClick={() => (reason ? setToast(reason) : hasBlocks ? runStudioAction("motion.generateAllBlocks") : runArdy())}
|
|
84
|
+
>
|
|
85
|
+
{ardyRunning ? ko("Generating…", "생성 중…") : (
|
|
86
|
+
<>
|
|
87
|
+
<span className="topbar-generate-full">{ko("Generate Motion", "모션 생성")}</span>
|
|
88
|
+
<span className="topbar-generate-short">{ko("Generate", "생성")}</span>
|
|
89
|
+
</>
|
|
90
|
+
)}
|
|
91
|
+
</button>
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
className="topbar-generate-caret"
|
|
95
|
+
data-testid="topbar-generate-menu"
|
|
96
|
+
ref={caretRef}
|
|
97
|
+
aria-haspopup="menu"
|
|
98
|
+
aria-expanded={open}
|
|
99
|
+
aria-label={ko("More generate actions", "생성 작업 더 보기")}
|
|
100
|
+
onClick={() => setOpen((value) => !value)}
|
|
101
|
+
>
|
|
102
|
+
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 9l6 6 6-6" /></svg>
|
|
103
|
+
</button>
|
|
104
|
+
{open && (
|
|
105
|
+
<MenuPopover anchorRef={caretRef} align="end" onClose={() => setOpen(false)} label={ko("Generate actions", "생성 작업")}>
|
|
106
|
+
{actions.map((action) => (
|
|
107
|
+
<button
|
|
108
|
+
type="button"
|
|
109
|
+
role="menuitem"
|
|
110
|
+
key={action.id}
|
|
111
|
+
className="menubar-item"
|
|
112
|
+
data-generate-action={action.id}
|
|
113
|
+
aria-disabled={action.reason ? "true" : undefined}
|
|
114
|
+
data-disabled-reason={action.reason || undefined}
|
|
115
|
+
onClick={() => {
|
|
116
|
+
setOpen(false);
|
|
117
|
+
if (action.reason) setToast(action.reason);
|
|
118
|
+
else action.run();
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
<span className="menubar-item-label">{action.label}</span>
|
|
122
|
+
{action.reason && <small className="menubar-item-reason">{action.reason}</small>}
|
|
123
|
+
</button>
|
|
124
|
+
))}
|
|
125
|
+
</MenuPopover>
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
}
|