Files
olivv-cs-ComfyUI-FunPack/app/shell/inspector.js
T
digital-garbage aceb06803d app, core: a rating is liked or it is not
dev went binary on H3 and said why: the steering reads the SIGN of a rating
and never its size, so a ten-point scale was the UI claiming a precision
nothing downstream used -- and at the sample sizes this actually runs at, a
mediocre middle rating only diluted the direction. v5 had invented a
four-way scale of its own; it now stores what dev stores.

Pressing the one that is on takes it back. Without a way out a mis-click is
a permanent opinion, and "no opinion" is a real answer nothing should have
to guess at.
2026-09-06 12:52:13 +03:00

115 lines
4.2 KiB
JavaScript

// The inspector: what the PROJECT is, and what THIS SCENE is.
//
// Two tabs because they are two different things and v4 proved that mixing them
// is how a per-scene value quietly becomes the project's. The rule this file
// exists to make visible: a regenerate reads the PROJECT, and a crop is the
// scene's own -- so Length appears in both places and means something different
// in each, and both say so.
import { composer } from "../composer/composer.js";
// Two, because two is all anything downstream reads: what learns from these
// takes the sign of a rating and never its size. A finer scale would be the UI
// claiming a precision nothing uses.
const RATINGS = [
{ value: "liked", label: "Liked" },
{ value: "disliked", label: "Disliked" },
];
export function createInspector({ project, onRename } = {}) {
const host = composer.region.stack({ gap: "sm", fill: true });
let tab = "scene";
const tabs = composer.tabs.underline({
value: tab, label: "Inspector",
tabs: [{ value: "scene", label: "Scene" }, { value: "project", label: "Project" }],
onChange: (next) => { tab = next; draw(); },
});
const rows = composer.region.stack({ gap: "sm", label: "Inspector rows" });
host.set([tabs, rows]);
function sceneRows() {
const scene = project.selected;
if (!scene) {
return [composer.emptyState.default({
icon: "▭", title: "No scene", hint: "Add one on the timeline." })];
}
const at = project.scenes.indexOf(scene) + 1;
return [
composer.label.section({ text: `Scene ${at}` }),
composer.settingsRow.default({
label: "Length on the timeline",
// The rule, said where it applies: this is a crop, and a regenerate
// does not read it.
hint: "Frames this clip plays for. Regenerating uses the project's length.",
control: composer.number.md({
value: scene.length ?? project.video.length ?? 1, min: 1, label: "Length",
onChange: (v) => project.setScene(scene.id, "length", v),
}),
}),
composer.settingsRow.default({
label: "Rating",
hint: "Liked or not. Press it again to take it back.",
control: composer.buttonGroup.md({
label: "Rating", value: scene.rating || undefined, items: RATINGS,
// Pressing the one that is on clears it. Without a way back, a
// mis-click is a permanent opinion -- and "no opinion" is a real
// answer that nothing downstream should be given a guess about.
onChange: (v) => {
const next = v === scene.rating ? null : v;
project.setScene(scene.id, "rating", next);
draw();
},
}),
}),
composer.settingsRow.default({
label: "Result",
hint: scene.result ? "This scene has been generated." : "Not generated yet.",
}),
];
}
function projectRows() {
const open = project.project;
if (!open) return [composer.hint.default({ text: "No project is open." })];
return [
composer.label.section({ text: "Project" }),
composer.settingsRow.default({
label: "Name",
control: composer.input.md({
value: open.name, label: "Project name",
onCommit: (v) => { if (onRename) onRename(v); },
}),
}),
composer.settingsRow.default({
label: "Scenes", hint: `${project.scenes.length} on the timeline`,
}),
composer.hint.default({
text: "Size and length live in the Constructor: they are what every scene "
+ "is generated at.",
}),
];
}
function draw() {
tabs.setValue(tab);
// Wrapped in a group, which is what draws the card AND is the container the
// stacking rule measures: settings rows outside one keep their 180px control
// column whatever the width, and a four-button group is then clipped.
const [head, ...body] = tab === "scene" ? sceneRows() : projectRows();
rows.set([head, composer.group.default({ rows: body })]);
}
draw();
return {
node: host.node,
draw,
/** Which tab is showing. Tests and the wheel both ask. */
get tab() { return tab; },
show(next) { tab = next; draw(); },
destroy() { host.destroy(); },
};
}