- Marker: "make a repaint mask" becomes How to change it (leave it to the AI, only the marked areas, the whole picture anew); the AI reads the choice first as the user's, the timeline row says it, the repaint brush picks "only the marked areas". - Task list: folded, it stays laid out out of sight and slides in at once from its button or the page's left edge; redraws make anew only the rows that changed (sync_rows.js). A picked task shows at once from the copy read before or read ahead on hover (ai_task_cache.js), then what changed; activity entries follow instead of holding it up. Stage pictures load as thumbnails first, the file itself only when shown larger. - Header: "Own tab" is "AI edits a copy ▾", a two-way menu with what each means (kept open across redraws), and "Show the copy". - Materials: the Source list lights the hovered row and ticks the one shown (a later rule had hidden both), tooltips only for cut names, arrows and Enter (ui_ai_facet_menu.js). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
56 lines
1.8 KiB
JavaScript
56 lines
1.8 KiB
JavaScript
/**
|
|
* The AI workbench's tasks as last read from the server (ui_ai_workbench.js), so picking one is
|
|
* quick: a task read before shows at once from here while the page reads it again, and the pointer
|
|
* resting on a task in the list reads that task ahead. One read per task at a time; the latest few
|
|
* tasks are kept.
|
|
*/
|
|
|
|
const KEEP = 8;
|
|
const AHEAD_MS = 90; // the pointer passing over the list reads nothing
|
|
|
|
const kept = new Map(); // task id -> its record as last read, the latest last
|
|
const reading = new Map(); // task id -> the read on its way
|
|
let aheadTimer = null;
|
|
|
|
function keep(id, task) {
|
|
kept.delete(id);
|
|
if (!task) return;
|
|
kept.set(id, task);
|
|
while (kept.size > KEEP) kept.delete(kept.keys().next().value);
|
|
}
|
|
|
|
/** Task `id` from the server (null when there is none); kept for keptTask. */
|
|
export function readTask(id) {
|
|
if (reading.has(id)) return reading.get(id);
|
|
const read = fetch(`/anomalous/ai_tasks/${encodeURIComponent(id)}`)
|
|
.then((response) => {
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
return response.json();
|
|
})
|
|
.then((data) => {
|
|
keep(id, data.task || null);
|
|
return data.task || null;
|
|
})
|
|
.finally(() => reading.delete(id));
|
|
reading.set(id, read);
|
|
return read;
|
|
}
|
|
|
|
/** Task `id` as last read, or null. */
|
|
export const keptTask = id => kept.get(id) || null;
|
|
|
|
/** The pointer rests on task `id` in the list: read it ahead (once it has stayed a moment). */
|
|
export function readAhead(id) {
|
|
clearTimeout(aheadTimer);
|
|
aheadTimer = setTimeout(() => { if (!reading.has(id)) readTask(id).catch(() => {}); }, AHEAD_MS);
|
|
}
|
|
|
|
export function stopReadAhead() {
|
|
clearTimeout(aheadTimer);
|
|
}
|
|
|
|
/** A task removed: not shown again from here. */
|
|
export function forgetTask(id) {
|
|
kept.delete(id);
|
|
}
|