cozyclay 2.0.2 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/README.md +17 -2
- package/THIRD_PARTY_NOTICES.md +8 -0
- package/bin/agent/agent-routes.mjs +211 -36
- package/bin/agent/agent-runner.mjs +98 -4
- package/bin/agent/codex-client.mjs +2 -1
- package/bin/agent/fal-contract.mjs +0 -17
- package/bin/agent/h3-preservation.mjs +6 -8
- package/bin/agent/provider-keys.mjs +1 -1
- package/bin/agent/providers.mjs +124 -3
- package/bin/agent/script-tool.mjs +74 -0
- package/bin/agent/session-store.mjs +5 -5
- package/bin/agent/studio-prompt.mjs +2 -1
- package/bin/agent/studio-rebase.mjs +314 -0
- package/bin/agent/studio-tools.mjs +21 -14
- package/bin/agent/supervisor.mjs +311 -0
- package/bin/agent/video-adapters.mjs +0 -7
- package/bin/codex-auth.mjs +4 -4
- package/bin/live/cli.mjs +23 -8
- package/bin/live/client.mjs +1 -1
- package/bin/live/studio.mjs +5 -2
- package/bin/mcp-runtime.mjs +2 -2
- package/bin/telemetry-state.mjs +3 -3
- package/dist/app/index.html +6 -6
- package/dist/assets/analytics-C9pMpW7s.js +1 -0
- package/dist/assets/app-B_2XB9rw.css +1 -0
- package/dist/assets/app-DAuvKPP8.js +4920 -0
- package/dist/assets/{demo-ByeYp-dY.js → demo-CvKuBgy5.js} +1 -1
- package/dist/assets/{first-shot-handoff-DgT28ZiV.js → first-shot-handoff-msLI71g1.js} +1 -1
- package/dist/assets/{landing-BaXkpFip.js → landing-ChUvB_zL.js} +1 -1
- package/dist/assets/scene-asset-sync-DKkpjKBV.js +19 -0
- package/dist/assets/{shot-prompt-CbqWcWYi.css → scene-asset-sync-DjJ_cJ2R.css} +1 -1
- package/dist/assets/{ticket-BcmsnHn5.js → ticket-BPk2Ni95.js} +1 -1
- package/dist/assets/workflow-jJRBR94Z.js +179 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/index.html +328 -222
- package/dist/llms.txt +14 -0
- package/dist/sitemap.xml +1 -1
- package/dist/workflow/index.html +4 -4
- package/mcp/LIVE-PROTOCOL.md +23 -2
- package/mcp/README.md +37 -2
- package/mcp/ardy-prompts.mjs +1 -1
- package/mcp/image-file.mjs +132 -0
- package/mcp/live-hub.mjs +2 -2
- package/mcp/package.json +1 -0
- package/mcp/runtime/package-lock.json +7 -0
- package/mcp/runtime/package.json +1 -0
- package/mcp/server.mjs +0 -6
- package/mcp/tool-handlers.mjs +289 -87
- package/package.json +11 -2
- package/src/App.jsx +813 -278
- package/src/agent-image-client.js +94 -0
- package/src/analytics.js +47 -3
- package/src/app-stage.jsx +129 -2745
- package/src/ardy/auto-physics.js +6 -16
- package/src/ardy/collision-blockers.js +3 -7
- package/src/ardy/fix-collisions.js +5 -35
- package/src/ardy/ik-drag.js +86 -5
- package/src/ardy/ik-key-json.js +3 -1
- package/src/ardy/ik.js +15 -13
- package/src/ardy/motion-calibration.js +1 -1
- package/src/ardy/npz.js +0 -8
- package/src/ardy/physics-panel.jsx +3 -2
- package/src/ardy/physics-review.js +3 -2
- package/src/ardy/physics-support.js +1 -1
- package/src/ardy/physics-surface.js +1 -1
- package/src/ardy/platform-fit-panel.jsx +2 -1
- package/src/ardy/playback.js +1 -5
- package/src/ardy/range-pin.js +2 -2
- package/src/ardy/root-drop.js +0 -1
- package/src/ardy/timeline.css +184 -6
- package/src/ardy/timeline.jsx +121 -36
- package/src/ardy/to-cskel27.js +0 -2
- package/src/ardy/waypoints.js +0 -3
- package/src/ardy-bridge-contract.js +281 -0
- package/src/asset-pane.jsx +19 -9
- package/src/auto-color.js +1 -1
- package/src/burn-in.js +1 -1
- package/src/camera-block.js +1 -1
- package/src/camera-follow.js +75 -32
- package/src/camera-move.js +0 -6
- package/src/camera-tutorial.jsx +6 -7
- package/src/character-kind.js +79 -0
- package/src/command-bus.js +76 -17
- package/src/commands/cast.js +31 -7
- package/src/commands/elements/object.js +1 -1
- package/src/commands/elements/shot.js +1 -1
- package/src/commands/elements.js +1 -1
- package/src/commands/export.js +3 -3
- package/src/commands/motion.js +2 -2
- package/src/commands/objects.js +105 -10
- package/src/commands/project.js +15 -2
- package/src/commands/sculpt-images.js +53 -0
- package/src/commands/shot.js +88 -10
- package/src/commands/view.js +10 -4
- package/src/coplanar-depth.js +216 -0
- package/src/cuts.js +7 -6
- package/src/domains/cast.js +60 -43
- package/src/domains/motion/motion-intent-codec.js +50 -0
- package/src/domains/motion/motion-range-pin-rules.js +23 -0
- package/src/domains/motion/motion-video-capture.js +664 -0
- package/src/domains/motion.js +253 -742
- package/src/domains/objects.js +106 -5
- package/src/domains/scenes.js +79 -28
- package/src/domains/shots.js +33 -7
- package/src/error-boundary.jsx +1 -1
- package/src/execution-telemetry.js +5 -5
- package/src/facing-marks.js +14 -1
- package/src/first-success-guide.jsx +35 -4
- package/src/gizmo-claim.js +3 -1
- package/src/grid-floor.jsx +1 -4
- package/src/hierarchy-model.js +48 -35
- package/src/hierarchy-panel.css +1 -0
- package/src/hierarchy-panel.jsx +54 -102
- package/src/history.js +0 -2
- package/src/i2v-motion-client.js +1 -4
- package/src/ime.js +6 -0
- package/src/keyframe-pack.js +15 -6
- package/src/line-edit.js +4 -4
- package/src/live-control.js +6 -6
- package/src/matte-editor.js +2 -8
- package/src/matte.js +0 -19
- package/src/mesh-graph-clone.js +16 -2
- package/src/mesh-sniff.js +0 -1
- package/src/monotone-cubic.js +40 -0
- package/src/motion-store.js +4 -4
- package/src/motion-trail.js +5 -11
- package/src/multimodel-ingest.js +1 -3
- package/src/object-catalog.jsx +7 -5
- package/src/object-gizmo.jsx +45 -31
- package/src/object-path.js +310 -67
- package/src/object-travel.js +224 -0
- package/src/otio.js +8 -3
- package/src/panels/CharacterTransformPanel.jsx +24 -6
- package/src/panels/ObjectTransformPanel.jsx +4 -3
- package/src/panels/PoseImagePanel.jsx +205 -0
- package/src/panels/PromptBlocksPanel.jsx +3 -1
- package/src/panels/ProxyFigurePanel.jsx +56 -0
- package/src/panels/ReferenceImageField.jsx +1 -1
- package/src/panels/RigControlPanel.jsx +24 -3
- package/src/panels/RigPanel.jsx +2 -1
- package/src/panels/SculptPanel.jsx +405 -0
- package/src/panels/SubjectBox.jsx +1 -1
- package/src/panels/SubjectsPanel.jsx +0 -2
- package/src/panels/VideoCapturePanel.jsx +2 -1
- package/src/panels/pose-image.css +42 -0
- package/src/panels/pose.css +3 -0
- package/src/panels/sculpt.css +162 -0
- package/src/plan-diagram.js +184 -0
- package/src/planview.jsx +127 -80
- package/src/playground.js +3 -5
- package/src/pose-fade.js +77 -0
- package/src/pose-gizmo.css +23 -0
- package/src/pose-gizmo.js +96 -0
- package/src/posestudio.jsx +134 -83
- package/src/project-browser.css +53 -0
- package/src/project-browser.jsx +49 -4
- package/src/project.js +55 -24
- package/src/props.jsx +177 -36
- package/src/proxy-figure.jsx +69 -0
- package/src/range-pin-panel.jsx +0 -1
- package/src/receipt-findings.js +20 -0
- package/src/render-passes.js +0 -1
- package/src/resource-status.jsx +1 -1
- package/src/root-path.js +48 -0
- package/src/route-owner.js +52 -0
- package/src/scene-asset-cache.js +1 -2
- package/src/scene-assets.js +12 -4
- package/src/scene-fbx.js +1 -1
- package/src/scene-mesh-cache.js +0 -1
- package/src/scene-objects.js +319 -19
- package/src/scenes.js +58 -9
- package/src/sculpt-blueprint.js +65 -0
- package/src/sculpt-compare.js +384 -0
- package/src/sculpt-geometry.js +141 -0
- package/src/sculpt-mode.js +168 -0
- package/src/sculpt-recipe.js +385 -0
- package/src/sculpt-run.js +351 -0
- package/src/sculpt-stage.jsx +221 -0
- package/src/settings-menu.jsx +1 -1
- package/src/shell/BoardSlot.jsx +341 -0
- package/src/shell/BottomDock.jsx +60 -16
- package/src/shell/CompositionGuideButton.jsx +20 -0
- package/src/shell/DetailsSlot.jsx +26 -6
- package/src/shell/MenuBar.jsx +78 -6
- package/src/shell/OutlinerSlot.jsx +3 -1
- package/src/shell/ShotPreviewHeader.jsx +17 -0
- package/src/shell/TopBar.jsx +18 -3
- package/src/shell/ViewportToolbar.jsx +36 -31
- package/src/shell/dock.css +239 -0
- package/src/shell/glass-regions.css +3 -2
- package/src/shell/log-store.js +4 -4
- package/src/shell/mode.css +20 -0
- package/src/shell/studio-shell-context.js +4 -1
- package/src/shell/viewport.css +12 -0
- package/src/shot-authoring.js +69 -6
- package/src/stable-items.js +1 -1
- package/src/stage-attach-frames.js +169 -0
- package/src/stage-canvas-rigs.jsx +402 -0
- package/src/stage-capture.jsx +216 -0
- package/src/stage-characters.jsx +302 -0
- package/src/stage-editor-gizmos.jsx +1229 -0
- package/src/stage-labels-ko.js +204 -0
- package/src/stage-startup-defaults.js +182 -0
- package/src/starter-scenes.js +1 -1
- package/src/storyboard.js +24 -5
- package/src/studio-actions.js +42 -9
- package/src/studio-agent-commands.js +213 -23
- package/src/studio-agent-context.js +11 -9
- package/src/studio-agent-motion.js +2 -1
- package/src/studio-agent-protocol.js +60 -24
- package/src/studio-app-binding.js +56 -23
- package/src/studio-contact-sheet.js +3 -3
- package/src/studio-elements.js +11 -2
- package/src/studio-geometry-facts.js +114 -0
- package/src/styles.css +92 -9
- package/src/ui-scrub.js +4 -4
- package/src/ui.jsx +6 -4
- package/src/use-asset-shelf.js +60 -0
- package/src/use-camera-tutorial.js +50 -0
- package/src/use-embed-bridge.js +12 -0
- package/src/use-first-success-prompt.js +7 -0
- package/src/use-i2v-capture-modal.js +15 -0
- package/src/use-sculpt-mode.js +95 -0
- package/src/workflow/AgentPanel.jsx +106 -18
- package/src/workflow/WorkflowBuilder.jsx +1 -1
- package/src/workflow/agent-client.js +113 -45
- package/src/workflow/agent-panel.css +35 -0
- package/src/workflow/attachment-image.js +1 -1
- package/src/workflow/canvas-commands.js +3 -3
- package/src/workflow/cozy-scene-node.js +3 -3
- package/src/workflow/local-workflow.js +2 -2
- package/src/workflow/motion-input.js +3 -3
- package/src/workflow/node-schema.js +1 -1
- package/src/workflow/scene-asset-sync.js +2 -9
- package/src/workflow/shot-prompt-node.js +1 -1
- package/src/workflow/video-contract.js +1 -1
- package/src/workflow/workflow-resources.js +0 -8
- package/src/workflow-send.js +87 -0
- package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
- package/tools/check-eslint.mjs +57 -0
- package/tools/check-knip.mjs +60 -0
- package/tools/check-loc.mjs +54 -0
- package/tools/demo-worker/api-client.mjs +7 -13
- package/tools/demo-worker/generate.mjs +2 -4
- package/tools/eslint-baseline.json +23 -0
- package/tools/gt-render/browser.mjs +23 -0
- package/tools/headless-studio.mjs +85 -0
- package/tools/kimodo/effector-constraints.mjs +4 -4
- package/tools/kimodo/generate.mjs +1 -1
- package/tools/knip-baseline.json +331 -0
- package/tools/loc-baseline.json +126 -0
- package/tools/process-supervisor.mjs +1 -1
- package/tools/projflow/replay.mjs +4 -4
- package/tools/projflow/runner.mjs +3 -3
- package/tools/qa/studio-control-count.mjs +2 -0
- package/tools/run-tests.mjs +95 -7
- package/dist/assets/analytics-MVZOEW8O.js +0 -1
- package/dist/assets/app-BxBLC6GT.js +0 -4876
- package/dist/assets/app-CJdxvnrl.css +0 -1
- package/dist/assets/shot-prompt-hN0j_O-p.js +0 -17
- package/dist/assets/workflow-B5XZwVfe.js +0 -179
|
@@ -8,6 +8,9 @@ import {
|
|
|
8
8
|
attachmentFromFile,
|
|
9
9
|
transferCarriesFiles,
|
|
10
10
|
} from "./attachment-image.js";
|
|
11
|
+
import { isImeComposing } from "../ime.js";
|
|
12
|
+
import { useStudioShell } from "../shell/studio-shell-context.js";
|
|
13
|
+
import { ko } from "../locale.js";
|
|
11
14
|
import {
|
|
12
15
|
AGENT_PANEL_OVERLAY_BREAKPOINT,
|
|
13
16
|
AGENT_PANEL_RAIL_WIDTH,
|
|
@@ -34,9 +37,14 @@ import {
|
|
|
34
37
|
formatElapsed,
|
|
35
38
|
formatResetIn,
|
|
36
39
|
readStoredPanelWidth,
|
|
40
|
+
readStoredSupervisor,
|
|
37
41
|
storeModel,
|
|
38
42
|
storePanelWidth,
|
|
43
|
+
storeSupervisor,
|
|
39
44
|
STUDIO_SESSION_STORAGE_KEY,
|
|
45
|
+
selectedModelRoles,
|
|
46
|
+
shortModelId,
|
|
47
|
+
supervisorFollowUp,
|
|
40
48
|
} from "./agent-client.js";
|
|
41
49
|
import "./agent-panel.css";
|
|
42
50
|
|
|
@@ -122,6 +130,18 @@ function PausedCard({ resetAt, onRetry, onSwitchModel }) {
|
|
|
122
130
|
</div>;
|
|
123
131
|
}
|
|
124
132
|
|
|
133
|
+
// The lineup the sidecar derived for the selected model (#717), read-only:
|
|
134
|
+
// what a turn may lean on beside it. Vision is named only when it is not the
|
|
135
|
+
// selected model itself.
|
|
136
|
+
function ModelRolesLine({ model, roles }) {
|
|
137
|
+
if (!roles) return null;
|
|
138
|
+
const parts = [[ko("helper", "보조"), roles.helper], [ko("advisor", "자문"), roles.advisor]];
|
|
139
|
+
if (roles.vision !== model) parts.push([ko("vision", "비전"), roles.vision]);
|
|
140
|
+
return <p className="agent-model-roles" data-agent-model-roles="true">
|
|
141
|
+
{parts.map(([label, key]) => `${label} · ${shortModelId(key)}`).join(" · ")}
|
|
142
|
+
</p>;
|
|
143
|
+
}
|
|
144
|
+
|
|
125
145
|
// A generation is a server-owned job. The card shows the state the runtime
|
|
126
146
|
// reported, the phase it named and only a progress value it actually sent.
|
|
127
147
|
function JobCard({ job, onStop, onAccept }) {
|
|
@@ -182,6 +202,47 @@ function ReceiptCard({ item }) {
|
|
|
182
202
|
</div>;
|
|
183
203
|
}
|
|
184
204
|
|
|
205
|
+
const SUPERVISOR_SEVERITY_COPY = { blocker: ko("blocker", "차단"), concern: ko("concern", "우려"), note: ko("note", "참고") };
|
|
206
|
+
const SUPERVISOR_STATUS_COPY = { done: ko("done", "완료"), partial: ko("partial", "일부"), refused: ko("refused", "거부됨"), missing: ko("missing", "누락") };
|
|
207
|
+
|
|
208
|
+
// The supervisor's note on a finished Studio turn (#728): muted, collapsed to
|
|
209
|
+
// its summary and counts, and never acted on by itself. Its one action puts
|
|
210
|
+
// the blockers and concerns into the composer as the author's next turn.
|
|
211
|
+
function SupervisorCard({ note, onSend, defaultOpen = false }) {
|
|
212
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
213
|
+
if (note.verdict !== "reviewed") return <div className="agent-card agent-supervisor-card" data-supervisor-verdict="unavailable">
|
|
214
|
+
<p className="agent-supervisor-summary">{ko("Supervisor review unavailable", "감독 검토를 할 수 없었어요")}{note.reason ? ` — ${note.reason}` : ""}</p>
|
|
215
|
+
</div>;
|
|
216
|
+
const followUp = supervisorFollowUp(note);
|
|
217
|
+
// Validated run_action drafts (#743), shown under the issue each one fixes.
|
|
218
|
+
const fixes = Array.isArray(note.fixes) ? note.fixes : [];
|
|
219
|
+
return <div className="agent-card agent-supervisor-card" data-supervisor-verdict="reviewed" data-open={open ? "true" : "false"}>
|
|
220
|
+
<div className="agent-supervisor-head">
|
|
221
|
+
<span className="agent-job-label">{ko("Supervisor", "감독")}</span>
|
|
222
|
+
{["blocker", "concern", "note"].filter((severity) => note.counts?.[severity] > 0).map((severity) => <span key={severity} className={`agent-job-badge agent-supervisor-badge ${severity}`} data-severity={severity}>{note.counts[severity]} {SUPERVISOR_SEVERITY_COPY[severity]}</span>)}
|
|
223
|
+
<button type="button" className="agent-ghost-button agent-supervisor-toggle" aria-expanded={open} onClick={() => setOpen((value) => !value)}>{open ? ko("Less", "접기") : ko("Details", "자세히")}</button>
|
|
224
|
+
</div>
|
|
225
|
+
<p className="agent-supervisor-summary">{note.summary}</p>
|
|
226
|
+
{open && note.items?.length > 0 && <ul className="agent-receipt-notes agent-supervisor-items">
|
|
227
|
+
{note.items.map((item, index) => <li key={index} data-status={item.status}>{SUPERVISOR_STATUS_COPY[item.status] ?? item.status} · {item.text}{item.evidence ? ` — ${item.evidence}` : ""}</li>)}
|
|
228
|
+
</ul>}
|
|
229
|
+
{open && note.issues?.length > 0 && <ul className="agent-receipt-notes agent-supervisor-issues">
|
|
230
|
+
{note.issues.map((issue, index) => {
|
|
231
|
+
const fix = fixes.find((entry) => entry.issueIndex === index);
|
|
232
|
+
return <li key={index} data-severity={issue.severity}>{SUPERVISOR_SEVERITY_COPY[issue.severity] ?? issue.severity} · {issue.text}{issue.evidence ? ` — ${issue.evidence}` : ""}
|
|
233
|
+
{fix && <code className="agent-supervisor-fix" data-fix-action={fix.action}>{`run_action ${JSON.stringify({ action: fix.action, args: fix.args })}`}</code>}
|
|
234
|
+
</li>;
|
|
235
|
+
})}
|
|
236
|
+
</ul>}
|
|
237
|
+
{followUp && <div className="agent-job-actions">
|
|
238
|
+
<button type="button" className="agent-ghost-button agent-supervisor-send" onClick={() => onSend(followUp)}
|
|
239
|
+
title={note.autofix?.eligible ? ko("Autofix is on: the blocker fixes would be applied as written", "자동 수정 켜짐: 차단 수정안을 적힌 그대로 적용합니다") : undefined}>
|
|
240
|
+
{fixes.length ? ko("Apply fixes as next prompt", "수정안 다음 턴으로 보내기") : ko("Send as next prompt", "다음 턴으로 보내기")}
|
|
241
|
+
</button>
|
|
242
|
+
</div>}
|
|
243
|
+
</div>;
|
|
244
|
+
}
|
|
245
|
+
|
|
185
246
|
const RECOVERY_COPY = {
|
|
186
247
|
none: "No recovery is available for this command.",
|
|
187
248
|
inspect: "Ask the agent to inspect the target before trying again.",
|
|
@@ -286,6 +347,11 @@ export default function AgentPanel({
|
|
|
286
347
|
const transport = useMemo(() => injectedTransport || createAgentTransport({ surface }), [injectedTransport, surface]);
|
|
287
348
|
const mockState = transport.mock ? transport.state : null;
|
|
288
349
|
|
|
350
|
+
// Inside the studio the title names the project's previs mode (#650).
|
|
351
|
+
const previsMode = useStudioShell()?.previsMode;
|
|
352
|
+
const titleMode = surface === "studio" && previsMode
|
|
353
|
+
? (previsMode === "storyboard" ? ko("Storyboard", "스토리보드") : ko("Animation", "애니메이션"))
|
|
354
|
+
: null;
|
|
289
355
|
const [collapsed, setCollapsed] = useState(defaultCollapsed);
|
|
290
356
|
// Only the dock owns a width; an embedded host sizes the panel itself and
|
|
291
357
|
// never reads the dock's stored key.
|
|
@@ -302,6 +368,8 @@ export default function AgentPanel({
|
|
|
302
368
|
const [modelProviders, setModelProviders] = useState([]);
|
|
303
369
|
const [model, setModel] = useState("");
|
|
304
370
|
const [modelsState, setModelsState] = useState("loading");
|
|
371
|
+
// The roles derived per main model (#717); shown, never sent or stored.
|
|
372
|
+
const [modelRoles, setModelRoles] = useState(null);
|
|
305
373
|
// null = the model's backend default; picking a model resets it.
|
|
306
374
|
const [effort, setEffort] = useState(null);
|
|
307
375
|
const efforts = useMemo(() => effortOptions(models.find((entry) => entry.id === model)), [models, model]);
|
|
@@ -309,6 +377,10 @@ export default function AgentPanel({
|
|
|
309
377
|
// remembered so a reload does not quietly move the next turn to another
|
|
310
378
|
// provider's model.
|
|
311
379
|
const chooseModel = useCallback((id) => { setModel(id); setEffort(null); storeModel(id); }, []);
|
|
380
|
+
// The supervisor switch (#728): a preference like the model pick. Off skips
|
|
381
|
+
// the post-turn review for every turn sent from this panel.
|
|
382
|
+
const [supervisorOn, setSupervisorOn] = useState(() => readStoredSupervisor());
|
|
383
|
+
const toggleSupervisor = useCallback(() => setSupervisorOn((current) => { storeSupervisor(!current); return !current; }), []);
|
|
312
384
|
// One place takes `{ providers, models }` from the sidecar: the grouped list
|
|
313
385
|
// the dropdown draws, the flat key-addressed list every lookup uses, and the
|
|
314
386
|
// model to open with. Returns false when nothing was advertised.
|
|
@@ -318,6 +390,7 @@ export default function AgentPanel({
|
|
|
318
390
|
const providers = Array.isArray(advertised?.providers) ? advertised.providers : [];
|
|
319
391
|
setModelProviders(providers);
|
|
320
392
|
setModels(list);
|
|
393
|
+
setModelRoles(advertised?.roles?.byMain ?? null);
|
|
321
394
|
// A saved or removed key arrives through this same path, so a selection the
|
|
322
395
|
// panel opened with is dropped here the moment its provider cannot run it.
|
|
323
396
|
setModel((current) => nextSelectedModel(providers, list, current));
|
|
@@ -345,6 +418,7 @@ export default function AgentPanel({
|
|
|
345
418
|
const [restoreReady, setRestoreReady] = useState(surface !== "studio");
|
|
346
419
|
|
|
347
420
|
const composerRef = useRef(null);
|
|
421
|
+
const focusPendingRef = useRef(false);
|
|
348
422
|
const transcriptRef = useRef(null);
|
|
349
423
|
|
|
350
424
|
// The host's context builder and image-action handler are read through refs
|
|
@@ -401,7 +475,6 @@ export default function AgentPanel({
|
|
|
401
475
|
try { globalThis.localStorage?.setItem(STUDIO_SESSION_STORAGE_KEY, chat.sessionId); } catch { /* storage may be unavailable */ }
|
|
402
476
|
}, [chat.sessionId, restoreReady, surface]);
|
|
403
477
|
|
|
404
|
-
// --- activity line -----------------------------------------------------
|
|
405
478
|
// One line that always states what the panel is doing, ticking while a turn
|
|
406
479
|
// is live and holding the turn's outcome for a few seconds after it ends.
|
|
407
480
|
const [activityNow, setActivityNow] = useState(() => Date.now());
|
|
@@ -416,7 +489,6 @@ export default function AgentPanel({
|
|
|
416
489
|
return () => clearInterval(timer);
|
|
417
490
|
}, [activity.ticking]);
|
|
418
491
|
|
|
419
|
-
// --- session bootstrap -------------------------------------------------
|
|
420
492
|
// Two ways in (#379): the ChatGPT sign-in, or a provider key the sidecar
|
|
421
493
|
// already holds. `providersConfigured` counts the second, so a session that
|
|
422
494
|
// never signed in to ChatGPT still gets a composer instead of a sign-in wall
|
|
@@ -520,7 +592,6 @@ export default function AgentPanel({
|
|
|
520
592
|
store.send("Give me a wide two-shot of this scene", { attachFrame: false, model });
|
|
521
593
|
}, [authState, items.length, mockState, model, store]);
|
|
522
594
|
|
|
523
|
-
// --- layout ------------------------------------------------------------
|
|
524
595
|
useEffect(() => {
|
|
525
596
|
if (embedded) return;
|
|
526
597
|
const onResize = () => setOverlay((globalThis.innerWidth || 1440) < AGENT_PANEL_OVERLAY_BREAKPOINT);
|
|
@@ -557,7 +628,10 @@ export default function AgentPanel({
|
|
|
557
628
|
// Focus moves to the composer whenever the panel opens. The composer only
|
|
558
629
|
// mounts once the session resolves, so authState is a dependency too:
|
|
559
630
|
// otherwise this fires against a composer that does not exist yet. A hidden
|
|
560
|
-
// embedded panel never pulls focus out of the host's own surface.
|
|
631
|
+
// embedded panel never pulls focus out of the host's own surface. An
|
|
632
|
+
// embedded panel never collapses itself (its host hides it), so shown means
|
|
633
|
+
// open there: without that the Studio composer opened unfocused and the
|
|
634
|
+
// next Cmd+V landed on the stage.
|
|
561
635
|
useEffect(() => {
|
|
562
636
|
if (hidden) {
|
|
563
637
|
// Chrome blurs a hidden focused field only on its next lifecycle step;
|
|
@@ -565,8 +639,12 @@ export default function AgentPanel({
|
|
|
565
639
|
if (composerRef.current && composerRef.current === document.activeElement) composerRef.current.blur();
|
|
566
640
|
return;
|
|
567
641
|
}
|
|
568
|
-
if (!collapsed)
|
|
569
|
-
|
|
642
|
+
if (!(embedded || !collapsed)) return;
|
|
643
|
+
composerRef.current?.focus();
|
|
644
|
+
// A composer still waiting on the model list is disabled and refuses
|
|
645
|
+
// focus; the open is remembered so it lands once the composer enables.
|
|
646
|
+
focusPendingRef.current = document.activeElement !== composerRef.current;
|
|
647
|
+
}, [authState, collapsed, embedded, hidden]);
|
|
570
648
|
|
|
571
649
|
useEffect(() => {
|
|
572
650
|
const node = transcriptRef.current;
|
|
@@ -597,12 +675,13 @@ export default function AgentPanel({
|
|
|
597
675
|
});
|
|
598
676
|
}, []);
|
|
599
677
|
|
|
600
|
-
// --- pasted and dropped pictures ---------------------------------------
|
|
601
678
|
// A screenshot is the fastest thing an author can show the agent, so the
|
|
602
679
|
// composer takes one from the clipboard or the desktop. Only a transfer that
|
|
603
680
|
// actually carries a picture is intercepted: a text paste still lands in the
|
|
604
681
|
// caret, which is the whole reason the studio's document handler steps aside
|
|
605
|
-
// for a textarea in the first place.
|
|
682
|
+
// for a textarea in the first place. The paste is taken on the composer
|
|
683
|
+
// block, not the textarea, so the Send and model buttons a click just left
|
|
684
|
+
// focus on do not hand the picture to the stage.
|
|
606
685
|
const attachFiles = useCallback(async (files) => {
|
|
607
686
|
const prepared = [];
|
|
608
687
|
let failed = 0;
|
|
@@ -612,7 +691,6 @@ export default function AgentPanel({
|
|
|
612
691
|
const rejected = prepared.length ? store.addAttachments(prepared).rejected : 0;
|
|
613
692
|
setAttachNotice(rejected ? ATTACHMENT_LIMIT_NOTICE : failed ? ATTACHMENT_FAILED_NOTICE : null);
|
|
614
693
|
}, [store]);
|
|
615
|
-
/** Whether this transfer belongs to the composer. */
|
|
616
694
|
const takeTransfer = useCallback((transfer) => {
|
|
617
695
|
const { images, unsupported } = attachmentFilesFromTransfer(transfer);
|
|
618
696
|
if (!images.length) {
|
|
@@ -643,12 +721,11 @@ export default function AgentPanel({
|
|
|
643
721
|
if (takeTransfer(event.dataTransfer)) event.preventDefault();
|
|
644
722
|
}, [takeTransfer]);
|
|
645
723
|
|
|
646
|
-
// --- turn --------------------------------------------------------------
|
|
647
724
|
const runTurn = useCallback((text) => {
|
|
648
725
|
setAttachNotice(null);
|
|
649
726
|
setSteerNotice("");
|
|
650
|
-
return store.send(text, { attachFrame, model, effort: effort ?? undefined });
|
|
651
|
-
}, [attachFrame, effort, model, store]);
|
|
727
|
+
return store.send(text, { attachFrame, model, effort: effort ?? undefined, supervisor: supervisorOn });
|
|
728
|
+
}, [attachFrame, effort, model, store, supervisorOn]);
|
|
652
729
|
const stopTurn = useCallback(() => store.stop(), [store]);
|
|
653
730
|
// Steering the turn that is already running: the composer's text joins THAT
|
|
654
731
|
// turn instead of starting a second one. A refusal (the turn ended while the
|
|
@@ -717,7 +794,6 @@ export default function AgentPanel({
|
|
|
717
794
|
}
|
|
718
795
|
}, [store, transport]);
|
|
719
796
|
|
|
720
|
-
// --- provider keys -----------------------------------------------------
|
|
721
797
|
// The section is read when it opens; every write goes back through the
|
|
722
798
|
// refresh above, because a key changes the session, not just a row.
|
|
723
799
|
const toggleProviderKeys = useCallback(() => {
|
|
@@ -761,7 +837,7 @@ export default function AgentPanel({
|
|
|
761
837
|
stopTurn();
|
|
762
838
|
return;
|
|
763
839
|
}
|
|
764
|
-
if (event.key === "Enter" && !event.shiftKey) {
|
|
840
|
+
if (event.key === "Enter" && !isImeComposing(event) && !event.shiftKey) {
|
|
765
841
|
event.preventDefault();
|
|
766
842
|
// Enter does what the button under it says: Send, or Steer while a turn
|
|
767
843
|
// on a steerable surface is still running.
|
|
@@ -784,6 +860,13 @@ export default function AgentPanel({
|
|
|
784
860
|
// can send to: the composer stays shut instead of submitting a turn the
|
|
785
861
|
// provider would refuse.
|
|
786
862
|
const composerDisabled = panelState === "rate-limited" || !model || !modelIsSelectable(modelProviders, model);
|
|
863
|
+
useEffect(() => {
|
|
864
|
+
if (hidden) focusPendingRef.current = false;
|
|
865
|
+
if (hidden || composerDisabled || !focusPendingRef.current) return;
|
|
866
|
+
focusPendingRef.current = false;
|
|
867
|
+
// Only claim focus nobody else has taken in the meantime.
|
|
868
|
+
if (!document.activeElement || document.activeElement === document.body) composerRef.current?.focus();
|
|
869
|
+
}, [composerDisabled, hidden]);
|
|
787
870
|
|
|
788
871
|
if (collapsed && !embedded) {
|
|
789
872
|
return <aside className="agent-panel collapsed" data-agent-state={panelState} data-agent-collapsed="true" aria-label="Agent panel, collapsed">
|
|
@@ -821,7 +904,7 @@ export default function AgentPanel({
|
|
|
821
904
|
|
|
822
905
|
<header className="agent-header">
|
|
823
906
|
<StatusDot tone={statusTone} title={panelState} />
|
|
824
|
-
<h2 className="agent-title">Agent</h2>
|
|
907
|
+
<h2 className="agent-title" data-previs-mode={titleMode ? previsMode : undefined}>{titleMode ? `Agent · ${titleMode}` : "Agent"}</h2>
|
|
825
908
|
<span className="agent-header-spacer" />
|
|
826
909
|
<button type="button" className="agent-icon-button agent-new" aria-label="New conversation" title="New conversation" onClick={newSession}><FiPlus size={14} aria-hidden="true" /></button>
|
|
827
910
|
{presentation.history && <span className="agent-history-wrap">
|
|
@@ -906,6 +989,7 @@ export default function AgentPanel({
|
|
|
906
989
|
if (item.kind === "job") return <div className="agent-row" key={item.id}><JobCard job={item} onStop={stopTurn} onAccept={(job) => store.acceptJob(job.jobId)} /></div>;
|
|
907
990
|
if (item.kind === "receipt") return <div className="agent-row" key={item.id}><ReceiptCard item={item} /></div>;
|
|
908
991
|
if (item.kind === "failure") return <div className="agent-row" key={item.id}><FailureCard failure={item.failure} onRetry={() => runTurn(chat.lastPrompt)} /></div>;
|
|
992
|
+
if (item.kind === "supervisor") return <div className="agent-row" key={item.id}><SupervisorCard note={item} onSend={(text) => { store.setDraft(text); runTurn(text); }} /></div>;
|
|
909
993
|
return <div className="agent-row" key={item.id}><ImageResultCard image={item} onUse={(image) => store.applyImage(image.id)} onUndo={(image) => store.undoImage(image.id)} onRegenerate={() => runTurn(chat.lastPrompt)} onOpen={setLightbox} /></div>;
|
|
910
994
|
})}
|
|
911
995
|
|
|
@@ -925,6 +1009,7 @@ export default function AgentPanel({
|
|
|
925
1009
|
onDragOver={composerDisabled ? undefined : onComposerDragOver}
|
|
926
1010
|
onDragLeave={composerDisabled ? undefined : onComposerDragLeave}
|
|
927
1011
|
onDrop={composerDisabled ? undefined : onComposerDrop}
|
|
1012
|
+
onPaste={composerDisabled ? undefined : onComposerPaste}
|
|
928
1013
|
>
|
|
929
1014
|
{pendingAttachments.length > 0 && <ul className="agent-attachments" aria-label="Attached images">
|
|
930
1015
|
{pendingAttachments.map((attachment) => <li key={attachment.id} className="agent-attachment">
|
|
@@ -933,8 +1018,6 @@ export default function AgentPanel({
|
|
|
933
1018
|
</li>)}
|
|
934
1019
|
</ul>}
|
|
935
1020
|
{attachNotice && <p className="agent-attachment-notice" role="status">{attachNotice}</p>}
|
|
936
|
-
{/* One field: the message, and under it on the same surface the
|
|
937
|
-
frame toggle, the model and effort picks, and Send. */}
|
|
938
1021
|
<div className="agent-field">
|
|
939
1022
|
<textarea
|
|
940
1023
|
ref={composerRef}
|
|
@@ -945,7 +1028,6 @@ export default function AgentPanel({
|
|
|
945
1028
|
disabled={composerDisabled}
|
|
946
1029
|
onChange={(event) => store.setDraft(event.target.value)}
|
|
947
1030
|
onKeyDown={onComposerKeyDown}
|
|
948
|
-
onPaste={onComposerPaste}
|
|
949
1031
|
onDragOver={onComposerDragOver}
|
|
950
1032
|
onDrop={onComposerDrop}
|
|
951
1033
|
/>
|
|
@@ -974,6 +1056,11 @@ export default function AgentPanel({
|
|
|
974
1056
|
{efforts.map((value, index) => <option key={value} value={value} title={index === 0 ? "Default for this model" : undefined}>{value}</option>)}
|
|
975
1057
|
</select>
|
|
976
1058
|
)}
|
|
1059
|
+
{surface === "studio" && (
|
|
1060
|
+
<button type="button" className={`agent-supervisor-toggle${supervisorOn ? " is-on" : ""}`} role="switch" aria-checked={supervisorOn} aria-label={ko("Supervisor review", "감독 검토")} title={ko(supervisorOn ? "Supervisor review is on: a note arrives after each editing turn" : "Supervisor review is off", supervisorOn ? "감독 검토 켜짐: 편집 턴이 끝나면 메모가 옵니다" : "감독 검토 꺼짐")} onClick={toggleSupervisor}>
|
|
1061
|
+
{ko("Supervisor", "감독")} {supervisorOn ? "ON" : "OFF"}
|
|
1062
|
+
</button>
|
|
1063
|
+
)}
|
|
977
1064
|
<span className="agent-composer-spacer" aria-hidden="true" />
|
|
978
1065
|
{surface === "studio" && i2vActionRef.current && <button
|
|
979
1066
|
type="button"
|
|
@@ -995,6 +1082,7 @@ export default function AgentPanel({
|
|
|
995
1082
|
</>
|
|
996
1083
|
: <button type="button" className="agent-send" aria-label="Send" title="Send (Enter)" disabled={composerDisabled || !draft.trim()} onClick={() => runTurn(draft)}><FiArrowUp size={14} aria-hidden="true" /></button>}
|
|
997
1084
|
</div>
|
|
1085
|
+
<ModelRolesLine model={model} roles={selectedModelRoles(modelRoles, model)} />
|
|
998
1086
|
</div>
|
|
999
1087
|
</div>}
|
|
1000
1088
|
{/* The hint exists to warn about image spend; a surface that cannot
|
|
@@ -14,7 +14,7 @@ import { Toaster, toast } from "react-hot-toast";
|
|
|
14
14
|
import { loadWorkflowGraph, normalizeWorkflowGraph, storeWorkflowGraph, WORKFLOW_STORAGE_KEY } from "../project.js";
|
|
15
15
|
import AgentPanel from "./AgentPanel.jsx";
|
|
16
16
|
import CozySceneNode from "./CozySceneNode.jsx";
|
|
17
|
-
import { applyCozyScenePatch, normalizeCozySceneData, sceneConnectionAllowed
|
|
17
|
+
import { applyCozyScenePatch, normalizeCozySceneData, sceneConnectionAllowed } from "./cozy-scene-node.js";
|
|
18
18
|
import { activeSceneCharacters, characterHandleId, characterIdFromHandle, normalizeMotionInputData, motionInputOutput } from "./motion-input.js";
|
|
19
19
|
import { DEFAULT_NODE_SCHEMAS, defaultFormValues, schemaCategoryForType, schemaModelEntries, schemaProperties } from "./node-schema.js";
|
|
20
20
|
import { executeLocalWorkflowGraph } from "./local-workflow.js";
|