Files
DemonGatanjieuandClaude Opus 5.5 d778aec946 Fix what a walk through every page found before 1.63
Every page in both languages, all themes and at 480:
- Give the window its own text colour: text with none took ComfyUI's, white on the light theme (Tidy headings and file names, the site chip).
- Keep a task's link chip in its row, its x in reach, and colour the next-task chip.
- Let choices in Settings wrap, the Models search shrink, and a workflow's preset row go to two lines.
- Add --amb-on-link for text on the accent: pale in the dark themes, so white was hard to read.
- Lists and colons follow the language (listSeparator, wordColon); tests/words_in_language.mjs keeps it so.
- Right plurals in English, "AI" on the rail, one line left in Chinese translated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 03:18:07 +08:00

126 lines
6.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* The folded rows of the AI workbench's timeline (ai_task_view.js groupSteps says which): reads
* that follow each other as one line ("looked things up 5 times: …") opening to a line each, and a
* chain of the AI's work (reads, canvas changes, decisions) as one row: how many steps, what they
* did as chips, the last one while the AI is at it, and undo for its canvas changes. A chain opens
* to its steps as the timeline draws them; which chains are open is kept while the page lives, so
* the AI's next steps, which draw the row again, do not shut one.
*/
import { app } from '../../../scripts/app.js';
import { translate as t } from './locales.js';
import { timeLabel } from './activity_log.js';
import { canUndo, undoEntry } from './canvas_undo.js';
import { anomalousConfirm } from './ui_dialog.js';
import { iconButton } from './ui_ai_icons.js';
import { chainTools, foldLooks, lookTools, stepDetail, toolKey } from './ai_task_view.js';
const opened = new Set(); // the first step of each chain the user opened
function el(tag, className, text) {
const node = document.createElement(tag);
if (className) node.className = className;
if (text !== undefined) node.textContent = text;
return node;
}
const counted = (tool, count) => (count > 1 ? `${t(toolKey(tool))} ×${count}` : t(toolKey(tool)));
/** A row of reads that follow each other; `number` is its place in the timeline. */
export function looksRow(steps, number) {
const row = el('div', 'anomalous-ai-step is-look is-folded');
row.dataset.stepIds = steps.map(step => step.id).join(' ');
row.append(el('span', 'anomalous-ai-step-dot', number));
const body = el('div', 'anomalous-ai-step-body');
const tools = lookTools(steps).map(({ tool, count }) => counted(tool, count));
const head = el('button', 'anomalous-ai-looks', `${t('aiLooks', { count: steps.length })}${t('wordColon')}${tools.join(t('listSeparator'))}`);
head.type = 'button';
head.onclick = () => {
details.hidden = !details.hidden;
head.setAttribute('aria-expanded', String(!details.hidden));
};
head.setAttribute('aria-expanded', 'false');
const details = el('div', 'anomalous-ai-looks-details');
details.hidden = true;
for (const step of steps) {
const detail = stepDetail(step);
const found = Number.isFinite(step.count) ? t('aiFound', { count: step.count }) : '';
details.append(el('div', 'anomalous-ai-look', [timeLabel(step.at), t(toolKey(step.tool)), detail, found].filter(Boolean).join(' · ')));
}
body.append(head, details);
row.append(body, el('div', 'anomalous-ai-step-when', timeLabel(steps[0].at)));
return row;
}
/** The activity entries of the canvas changes `steps` made, newest first (`entries`: id -> entry). */
function changesOf(steps, entries) {
return steps.flatMap(step => (step.results || []).filter(result => result.type === 'canvas'))
.map(result => entries.get(result.activity)).filter(Boolean).reverse();
}
/**
* Undoes the canvas changes `steps` made, newest first, once the user agrees (`ask`: the locale
* key of the question); stops at a later change of theirs in the way. Whether it undid any.
*/
export async function undoChanges(steps, entries, label, ask) {
const changes = changesOf(steps, entries);
const undoable = changes.filter(entry => canUndo(app, entry));
if (!undoable.length || !await anomalousConfirm(t(ask, { count: undoable.length }), label)) return false;
for (const entry of changes) {
if (!canUndo(app, entry)) break;
try { undoEntry(app, entry); } catch (_) { break; }
}
return true;
}
/**
* A chain of the AI's work as one row. `ctx`: {entries (activity id -> entry), stepRow(step,
* number) for its steps when open, changed() after an undo, live (the AI may still add to it)}.
* `row.reveal()` opens it (a jump to one of its steps).
*/
export function chainRow(steps, number, ctx) {
const row = el('div', 'anomalous-ai-step is-chain is-folded');
row.dataset.stepIds = steps.map(step => step.id).join(' ');
row.append(el('span', 'anomalous-ai-step-dot', number));
const body = el('div', 'anomalous-ai-step-body');
const head = el('div', 'anomalous-ai-chain-head');
const open = el('button', 'anomalous-ai-looks anomalous-ai-chain-open');
open.type = 'button';
open.append(el('span', 'anomalous-ai-chain-title', t('aiChain', { count: steps.length })));
if (ctx.live) open.append(el('span', 'anomalous-ai-chain-last', t('aiChainLast', { what: t(toolKey(steps.at(-1).tool)) })));
head.append(open);
if (changesOf(steps, ctx.entries).some(entry => canUndo(app, entry))) {
head.append(iconButton('anomalous-ai-chain-undo', 'undo', t('aiChainUndo'), async () => {
if (await undoChanges(steps, ctx.entries, t('aiChainUndo'), 'aiChainUndoConfirm')) ctx.changed();
}));
}
const chips = el('div', 'anomalous-ai-chain-chips');
for (const { tool, kind, count } of chainTools(steps)) {
chips.append(el('span', `anomalous-ai-chain-chip is-${kind}`, tool === 'look' ? t('aiLooks', { count }) : counted(tool, count)));
}
const inside = el('div', 'anomalous-ai-chain-steps');
const key = steps[0].id;
const show = (on) => {
inside.hidden = !on;
open.setAttribute('aria-expanded', String(on));
if (on && !inside.childNodes.length) {
inside.append(...foldLooks(steps).map((item, at) => (item.type === 'looks' ? looksRow(item.steps, at + 1) : ctx.stepRow(item.step, at + 1))));
}
};
open.onclick = () => {
const on = inside.hidden;
if (on) opened.add(key);
else opened.delete(key);
show(on);
};
row.reveal = () => {
opened.add(key);
show(true);
};
show(opened.has(key));
body.append(head, chips, inside);
const [first, last] = [timeLabel(steps[0].at), timeLabel(steps.at(-1).at)];
row.append(body, el('div', 'anomalous-ai-step-when', first === last ? first : `${first}–${last}`));
return row;
}