cozyclay 1.6.0 → 1.7.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 +151 -0
- package/README.md +62 -23
- package/bin/cozyclay.mjs +35 -22
- package/dist/app/index.html +3 -2
- package/dist/assets/app-BdCI2Bys.js +4851 -0
- package/dist/assets/app-CrB3UF4K.css +1 -0
- package/dist/assets/config-Bx7pYsD9.js +1 -0
- package/dist/assets/config-DeoFTYzb.css +1 -0
- package/dist/assets/demo-DEAy-8Eq.js +1 -0
- package/dist/assets/modulepreload-polyfill-P2Xu9kJm.js +1 -0
- package/dist/assets/ticket-DbrIKBrJ.js +1 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/index.html +8 -10
- package/dist/robots.txt +2 -0
- package/dist/sw.js +8 -2
- package/mcp/ardy-prompts.mjs +39 -9
- package/mcp/live-hub.mjs +10 -0
- package/mcp/package.json +1 -1
- package/mcp/runtime/package-lock.json +1 -1
- package/mcp/runtime/package.json +1 -1
- package/mcp/server.mjs +124 -22
- package/package.json +13 -7
- package/src/App.jsx +4699 -1969
- package/src/analytics.js +2 -0
- package/src/app-stage.jsx +2806 -0
- package/src/ardy/export.js +9 -1
- package/src/ardy/ik.js +238 -4
- package/src/ardy/motion-edit.js +41 -0
- package/src/ardy/motion-url.js +55 -0
- package/src/ardy/timeline.jsx +1019 -285
- package/src/asset-pane.jsx +18 -7
- package/src/auto-color.js +63 -0
- package/src/burn-in.js +91 -0
- package/src/camera-block.js +21 -4
- package/src/camera-follow.js +16 -6
- package/src/controls.jsx +44 -4
- package/src/dualview.jsx +89 -10
- package/src/error-boundary.jsx +41 -0
- package/src/gizmo-claim.js +48 -0
- package/src/grid-floor.jsx +97 -0
- package/src/grid-view.js +40 -0
- package/src/hierarchy-model.js +124 -71
- package/src/hierarchy-panel.jsx +101 -6
- package/src/line-edit.js +1348 -0
- package/src/live-control.js +11 -1
- package/src/main.jsx +4 -1
- package/src/matte.js +55 -0
- package/src/motion-trail.js +250 -0
- package/src/object-gizmo.jsx +57 -18
- package/src/object-path.js +224 -0
- package/src/planview.jsx +165 -14
- package/src/pose-thumbs.js +1 -1
- package/src/posestudio.jsx +99 -84
- package/src/project-browser.jsx +63 -6
- package/src/project.js +44 -0
- package/src/props.jsx +169 -27
- package/src/result-modal.jsx +2 -14
- package/src/room.jsx +37 -10
- package/src/scene-asset-cache.js +3 -0
- package/src/scene-objects.js +156 -3
- package/src/scenes.js +22 -1
- package/src/shot-authoring.js +9 -0
- package/src/shot-guides.js +90 -0
- package/src/shot.js +6 -7
- package/src/speed-envelope.js +254 -0
- package/src/styles.css +4831 -3436
- package/src/take-recipe.js +165 -0
- package/src/timeline-extent.js +39 -0
- package/src/use-render-activity.js +6 -0
- package/tools/ardy/BRIDGE.md +20 -316
- package/tools/ardy/README.md +22 -201
- package/tools/ardy/bridge.mjs +598 -73
- package/tools/ardy/bvh-cskel27.mjs +63 -5
- package/tools/ardy/prompt-limits.mjs +5 -0
- package/tools/ardy/run-on-box.sh +1 -1
- package/tools/ardy/runners/index.mjs +11 -16
- package/tools/ardy/visual-qa.mjs +38 -30
- package/tools/ardy/vq-car.mjs +1 -1
- package/tools/demo-worker/README.md +137 -0
- package/tools/demo-worker/api-client.mjs +239 -0
- package/tools/demo-worker/generate.mjs +303 -0
- package/tools/demo-worker/index.mjs +466 -0
- package/tools/demo-worker/sign-core.mjs +112 -0
- package/tools/demo-worker/sign.mjs +161 -0
- package/tools/dev-full.mjs +54 -36
- package/tools/kimodo/compare-seam.mjs +108 -0
- package/tools/kimodo/constraints.mjs +244 -0
- package/tools/kimodo/edit.mjs +264 -0
- package/tools/kimodo/effector-constraints.mjs +200 -0
- package/tools/kimodo/generate.mjs +597 -0
- package/tools/kimodo/measure-pose.mjs +65 -0
- package/tools/kimodo/measure-preserve.mjs +899 -0
- package/tools/kimodo/measure-waypoints.mjs +55 -0
- package/tools/kimodo/pose-constraints.mjs +250 -0
- package/tools/kimodo/preserve-mask.mjs +543 -0
- package/tools/kimodo/read-npz.mjs +189 -0
- package/tools/kimodo/run-edit-on-box.mjs +155 -0
- package/tools/kimodo/run-sequence-on-box.mjs +178 -0
- package/tools/kimodo/runner.mjs +246 -0
- package/tools/kimodo/seam-batch.mjs +77 -0
- package/tools/kimodo/setup-local.mjs +30 -0
- package/tools/kimodo/setup-on-box.sh +83 -0
- package/tools/kimodo/soma77-to-cskel27.mjs +305 -0
- package/tools/process-supervisor.mjs +1 -1
- package/tools/projflow/driver.py +1069 -0
- package/tools/projflow/generate.mjs +805 -0
- package/tools/projflow/hml22-to-cskel27.mjs +735 -0
- package/tools/projflow/line-edit-job.mjs +547 -0
- package/tools/projflow/replay.mjs +452 -0
- package/tools/projflow/runner.mjs +269 -0
- package/tools/projflow/service.mjs +742 -0
- package/tools/run-tests.mjs +108 -19
- package/dist/assets/app-CqOk49Bk.css +0 -1
- package/dist/assets/app-G55dPEDB.js +0 -4811
- package/src/generation/client.js +0 -56
- package/src/generation/generation-request.js +0 -17
- package/src/generation/session.js +0 -47
- package/src/generation/shot-spec.js +0 -40
- package/src/generation/use-generation.js +0 -44
- package/tools/generation/README.md +0 -29
- package/tools/generation/bridge.mjs +0 -162
- package/tools/generation/job-store.mjs +0 -51
- package/tools/generation/providers/kling.mjs +0 -76
- package/tools/generation/providers/runway.mjs +0 -101
- package/tools/generation/providers/seedance.mjs +0 -87
- package/tools/generation/providers/shared.mjs +0 -49
- package/tools/generation/providers/veo.mjs +0 -84
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runner.mjs — ProjFlow backend for the generation bridge.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the surface tools/kimodo/runner.mjs exposes ({ mode, describe,
|
|
5
|
+
* probeHealth, ... , <mode>Command }) so bridge.mjs (agent M4, wave 2) can spawn
|
|
6
|
+
* the returned command and grep its `done - <path> (<bytes>)` line exactly as it
|
|
7
|
+
* does for Kimodo. Nothing here talks to the box directly except probeHealth;
|
|
8
|
+
* everything else builds a command for the bridge to spawn.
|
|
9
|
+
*
|
|
10
|
+
* WHAT THIS BACKEND DOES. One thing: line edits. ProjFlow is adopted as an
|
|
11
|
+
* ENGINE-PER-TASK, not an engine-per-session (contract C6) — the bridge routes a
|
|
12
|
+
* `lineEdit` request here regardless of CCLAY_MOTION_BACKEND and keeps sending
|
|
13
|
+
* everything else to Kimodo. So the text-to-motion, sequencing, preserve and
|
|
14
|
+
* base-clip entry points are not implemented and refuse by name rather than
|
|
15
|
+
* quietly producing a take that ignored their inputs.
|
|
16
|
+
*
|
|
17
|
+
* (ProjFlow's checkpoint is a text-conditioned model and CAN generate from a
|
|
18
|
+
* prompt alone, so `singleCommand` is a deliberate refusal, not a missing
|
|
19
|
+
* feature: Kimodo already owns that path, produces rotations rather than
|
|
20
|
+
* positions, and swapping engines under an unchanged request would silently
|
|
21
|
+
* change the take's skeleton fidelity.)
|
|
22
|
+
*
|
|
23
|
+
* WHAT THE COMMAND PRODUCES. The raw hml22 (T,22,3) .npy the box wrote, plus its
|
|
24
|
+
* .meta.json sidecar — NOT a cclay npz. Lifting 22-joint positions to cskel27
|
|
25
|
+
* (contract C8, agent M2) and splicing the edited span back into the source take
|
|
26
|
+
* (agent M5) are separate modules that do not exist yet; wave-2 integration
|
|
27
|
+
* composes them on top of this output. The raw motion is also exactly what a
|
|
28
|
+
* LATER line edit sources from, so keeping it is not scaffolding.
|
|
29
|
+
*
|
|
30
|
+
* ENV. Everything is read at construction so `describe()` can report it:
|
|
31
|
+
*
|
|
32
|
+
* CCLAY_PROJFLOW_HOST the GPU box; falls back to CCLAY_KIMODO_HOST,
|
|
33
|
+
* because one box serves both engines today and making
|
|
34
|
+
* the operator set two variables to the same value is
|
|
35
|
+
* how one of them goes stale.
|
|
36
|
+
* CCLAY_PROJFLOW_REPO the ProjFlow clone (S1 put it at
|
|
37
|
+
* /home/yun/projflow-scout/repo — note `repo`, not
|
|
38
|
+
* `ProjFlow`; the clone was named by the setup script).
|
|
39
|
+
* CCLAY_PROJFLOW_PYTHON the scout venv's python (S1: python3.10 venv, pinned
|
|
40
|
+
* numpy==1.23.5 / timm==1.0.9 — the system python
|
|
41
|
+
* cannot run the repo).
|
|
42
|
+
* CCLAY_PROJFLOW_HOME HOME override for every box run, so CLIP's 338 MB
|
|
43
|
+
* ViT-B/32 download stays inside the scout directory.
|
|
44
|
+
* CCLAY_PROJFLOW_STEPS / _RIDGE / _PRESERVE_STRIDE / _PRESERVE_MARGIN
|
|
45
|
+
* tunables read by generate.mjs itself.
|
|
46
|
+
* CCLAY_PROJFLOW_RESIDENT=0
|
|
47
|
+
* turn the warm service off (contract C11); every
|
|
48
|
+
* edit then takes the cold path it always had.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
import { spawn } from "node:child_process";
|
|
52
|
+
import { dirname, join } from "node:path";
|
|
53
|
+
import { fileURLToPath } from "node:url";
|
|
54
|
+
|
|
55
|
+
import { peekResidentService, residentEnabled } from "./service.mjs";
|
|
56
|
+
|
|
57
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
58
|
+
/** generate.mjs doubles as the box wrapper; see the CLI note at the bottom of
|
|
59
|
+
* that file for why ProjFlow has one script where Kimodo has two. */
|
|
60
|
+
const RUN_LINE_EDIT = join(HERE, "generate.mjs");
|
|
61
|
+
|
|
62
|
+
const SSH_OPTS = ["-o", "BatchMode=yes", "-o", "ConnectTimeout=10"];
|
|
63
|
+
|
|
64
|
+
export const DEFAULT_REPO = "/home/yun/projflow-scout/repo";
|
|
65
|
+
export const DEFAULT_PYTHON = "/home/yun/projflow-scout/venv/bin/python";
|
|
66
|
+
export const DEFAULT_BOX_HOME = "/home/yun/projflow-scout/home";
|
|
67
|
+
|
|
68
|
+
function run(argv, timeoutMs = 60_000) {
|
|
69
|
+
return new Promise((resolve) => {
|
|
70
|
+
const child = spawn(argv[0], argv.slice(1), { stdio: ["ignore", "pipe", "pipe"] });
|
|
71
|
+
let stdout = "";
|
|
72
|
+
let stderr = "";
|
|
73
|
+
const timer = setTimeout(() => child.kill("SIGKILL"), timeoutMs);
|
|
74
|
+
child.stdout.on("data", (chunk) => (stdout += chunk));
|
|
75
|
+
child.stderr.on("data", (chunk) => (stderr += chunk));
|
|
76
|
+
child.on("close", (code) => {
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
resolve({ code, stdout, stderr });
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Where a run whose bridge output is `outputPath` keeps the RAW hml22 motion.
|
|
85
|
+
*
|
|
86
|
+
* Same role, and the same one-place-decides rule, as Kimodo's
|
|
87
|
+
* nativeMotionPath: generate.mjs writes the file, a later edit finds it again.
|
|
88
|
+
* The suffix says which engine wrote it because the two formats are not
|
|
89
|
+
* interchangeable — Kimodo's is a 77-joint rotation npz, this is a 22-joint
|
|
90
|
+
* position npy.
|
|
91
|
+
*/
|
|
92
|
+
export function nativeMotionPath(outputPath) {
|
|
93
|
+
return `${String(outputPath).replace(/\.(npz|npy)$/i, "")}.projflow.npy`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The metadata sidecar driver.py wrote beside a native motion: row count,
|
|
97
|
+
* sampling seconds, and the exactness numbers gates GP1/GP2 read. */
|
|
98
|
+
export function metaPath(nativePath) {
|
|
99
|
+
return `${String(nativePath).replace(/\.npy$/i, "")}.meta.json`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function unsupported(feature) {
|
|
103
|
+
return () => {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`the ProjFlow backend only implements line edits; ${feature} stays on the Kimodo backend`
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function createProjflowRunner() {
|
|
111
|
+
const HOST = process.env.CCLAY_PROJFLOW_HOST || process.env.CCLAY_KIMODO_HOST || "";
|
|
112
|
+
const REPO = process.env.CCLAY_PROJFLOW_REPO || DEFAULT_REPO;
|
|
113
|
+
const PYTHON = process.env.CCLAY_PROJFLOW_PYTHON || DEFAULT_PYTHON;
|
|
114
|
+
const BOX_HOME = process.env.CCLAY_PROJFLOW_HOME || DEFAULT_BOX_HOME;
|
|
115
|
+
|
|
116
|
+
if (!HOST) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
"CCLAY_PROJFLOW_HOST (or CCLAY_KIMODO_HOST) is required for the ProjFlow backend (for example: user@gpu-box)"
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Can the box actually run a line edit?
|
|
124
|
+
*
|
|
125
|
+
* The check is "does the venv python import the REPO", not "is there a
|
|
126
|
+
* python": S1's install needed pinned numpy 1.23.5 (1.24+ dies on
|
|
127
|
+
* `np.finfo(np.float)` in utils/quaternion.py) and timm 1.0.9 (newer timm
|
|
128
|
+
* passes `device=` into the norm layer and blows up at models/ACMDM.py:467).
|
|
129
|
+
* Both failures happen at IMPORT time, so importing models.ACMDM is exactly
|
|
130
|
+
* the check that separates "the env is fine" from "the next real run will die
|
|
131
|
+
* six seconds in". The checkpoint's presence is verified too, because a
|
|
132
|
+
* missing 625 MB tar is the other way a healthy-looking box fails a run.
|
|
133
|
+
*/
|
|
134
|
+
async function probeHealth() {
|
|
135
|
+
const script = [
|
|
136
|
+
"import sys, os, torch",
|
|
137
|
+
"import models.ACMDM as m",
|
|
138
|
+
"ck = os.path.join('checkpoints', 't2m', 'ACMDM_Raw_Flow_S_PatchSize22', 'model', 'latest.tar')",
|
|
139
|
+
"print('device=' + ('cuda:0' if torch.cuda.is_available() else 'cpu'))",
|
|
140
|
+
"print('models=%d' % len(m.ACMDM_models))",
|
|
141
|
+
"print('checkpoint=%s' % ('yes' if os.path.exists(ck) else 'no'))",
|
|
142
|
+
].join("; ");
|
|
143
|
+
const remote = [
|
|
144
|
+
`cd ${JSON.stringify(REPO)}`,
|
|
145
|
+
`HOME=${BOX_HOME} ${PYTHON} -c ${JSON.stringify(script)}`,
|
|
146
|
+
].join(" && ");
|
|
147
|
+
const { code, stdout, stderr } = await run(["ssh", ...SSH_OPTS, HOST, remote], 120_000);
|
|
148
|
+
if (code !== 0) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`ssh probe on ${HOST} failed (exit ${code}): ${stderr.trim().split("\n").pop()}`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const device = /^device=(.*)$/m.exec(stdout)?.[1] ?? "unknown";
|
|
154
|
+
const checkpoint = /^checkpoint=(.*)$/m.exec(stdout)?.[1] === "yes";
|
|
155
|
+
if (!checkpoint) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`${HOST}:${REPO} has no ACMDM_Raw_Flow_S_PatchSize22 checkpoint; a line edit would fail after the ssh round trip`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
// No encoder sidecar: CLIP loads in the generation process (S1 measured
|
|
161
|
+
// the whole model load at 3.9 s), so health has nothing to report for it
|
|
162
|
+
// and says so rather than faking a port.
|
|
163
|
+
return {
|
|
164
|
+
ok: true,
|
|
165
|
+
host: HOST,
|
|
166
|
+
repo: REPO,
|
|
167
|
+
encoder: "in-process",
|
|
168
|
+
device,
|
|
169
|
+
checkpoint: true,
|
|
170
|
+
resident: await residentUp(),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Is a WARM child actually there right now (contract C11)?
|
|
176
|
+
*
|
|
177
|
+
* Deliberately three things it is not. It does not START a resident: a
|
|
178
|
+
* health probe that loads 800 MiB of weights onto the GPU as a side effect
|
|
179
|
+
* would be a surprising answer to "are you well". It does not trust
|
|
180
|
+
* `state === "ready"`, which only says the child announced itself at some
|
|
181
|
+
* point; it sends a ping and waits for the answer, because the interesting
|
|
182
|
+
* failure is a child that died an hour ago. And it never throws — a
|
|
183
|
+
* resident that cannot be reached is `false`, not a failed health check,
|
|
184
|
+
* since every edit still works through the cold path.
|
|
185
|
+
*/
|
|
186
|
+
async function residentUp() {
|
|
187
|
+
if (!residentEnabled()) return false;
|
|
188
|
+
const service = peekResidentService({ host: HOST, python: PYTHON, repo: REPO, boxHome: BOX_HOME });
|
|
189
|
+
if (!service || !service.isReady()) return false;
|
|
190
|
+
try {
|
|
191
|
+
const pong = await service.ping();
|
|
192
|
+
return pong?.pong === true;
|
|
193
|
+
} catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ProjFlow takes a source motion, not a base clip; there is nothing to list.
|
|
199
|
+
// An empty listing is a valid answer, not a failure, and the bridge already
|
|
200
|
+
// tolerates it for Kimodo.
|
|
201
|
+
async function listBases() {
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** The environment the box wrapper inherits. Every key is set explicitly and
|
|
206
|
+
* the run-scoped one is DELETED when unused: process.env is inherited from a
|
|
207
|
+
* long-lived sidecar, and a leftover output path would have the next request
|
|
208
|
+
* silently overwrite the previous take. */
|
|
209
|
+
function boxEnv({ output } = {}) {
|
|
210
|
+
const env = {
|
|
211
|
+
...process.env,
|
|
212
|
+
CCLAY_PROJFLOW_HOST: HOST,
|
|
213
|
+
CCLAY_PROJFLOW_REPO: REPO,
|
|
214
|
+
CCLAY_PROJFLOW_PYTHON: PYTHON,
|
|
215
|
+
CCLAY_PROJFLOW_HOME: BOX_HOME,
|
|
216
|
+
};
|
|
217
|
+
delete env.CCLAY_PROJFLOW_NATIVE_OUT;
|
|
218
|
+
if (output) env.CCLAY_PROJFLOW_NATIVE_OUT = nativeMotionPath(output);
|
|
219
|
+
return env;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Which artifact of an earlier run this backend can edit, or null when that
|
|
223
|
+
* take has none — it came from Kimodo, or predates line editing. The bridge
|
|
224
|
+
* refuses the edit and says so rather than converting a lossy cskel27 file
|
|
225
|
+
* back into positions. */
|
|
226
|
+
function sourceMotionFor(motionPath) {
|
|
227
|
+
return nativeMotionPath(motionPath);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* The one command this backend builds.
|
|
232
|
+
*
|
|
233
|
+
* `line` is a C6 request; it travels as a FILE rather than on argv because a
|
|
234
|
+
* 20-point polyline plus a 3x3 rotation is not a command line, and because the
|
|
235
|
+
* bridge already writes per-run JSON for Kimodo's constraints. The caller
|
|
236
|
+
* writes it; this function only names it.
|
|
237
|
+
*/
|
|
238
|
+
function lineEditCommand({ source, line, output, steps, preview = false, seed }) {
|
|
239
|
+
if (!source) throw new Error("lineEditCommand: source (an hml22 .npy take) is required");
|
|
240
|
+
if (!line) throw new Error("lineEditCommand: line (a path to the C6 request JSON) is required");
|
|
241
|
+
if (!output) throw new Error("lineEditCommand: output is required");
|
|
242
|
+
const args = [RUN_LINE_EDIT, "--source", source, "--line", line, "--output", nativeMotionPath(output)];
|
|
243
|
+
if (preview) args.push("--preview");
|
|
244
|
+
else if (Number.isInteger(steps)) args.push("--steps", String(steps));
|
|
245
|
+
if (Number.isInteger(seed)) args.push("--seed", String(seed));
|
|
246
|
+
return {
|
|
247
|
+
command: process.execPath,
|
|
248
|
+
args,
|
|
249
|
+
env: boxEnv({ output }),
|
|
250
|
+
doneRe: /^run-projflow-line-edit: done - (.+) \((\d+) bytes\)$/,
|
|
251
|
+
label: "run-projflow-line-edit",
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
mode: "projflow",
|
|
257
|
+
describe: () =>
|
|
258
|
+
`box ${HOST} (repo ${REPO}, ACMDM_Raw_Flow_S_PatchSize22, 22-joint positions @ 20 fps)`,
|
|
259
|
+
probeHealth,
|
|
260
|
+
listBases,
|
|
261
|
+
sourceMotionFor,
|
|
262
|
+
lineEditCommand,
|
|
263
|
+
// Named refusals, so a mis-routed request reads as a routing bug rather
|
|
264
|
+
// than a crash inside an unrelated module.
|
|
265
|
+
singleCommand: unsupported("text-to-motion generation"),
|
|
266
|
+
sequenceCommand: unsupported("multi-segment sequencing"),
|
|
267
|
+
editCommand: unsupported("prompt-based span regeneration"),
|
|
268
|
+
};
|
|
269
|
+
}
|