Files
DemonGatanjieu-Anomalous_Mo…/tests/ai_task_view.mjs
T

86 lines
5.6 KiB
JavaScript

// The AI workbench's rules (web/modules/ai_task_view.js): folding reads, words, counts, addresses.
import assert from 'node:assert/strict';
import {
activityIds, audioResult, duration, flowStages, groupSteps, isVideo, lookTools, outputUrl, outputPicture, pictureKey, picturePreview, uniquePictures, stepDetail, taskCounts, taskResults, taskState, toolKey,
} from '../web/modules/ai_task_view.js';
const look = (tool, ok = true, state = 'done') => ({ kind: 'look', tool, ok, state, summary: tool });
const act = (tool, kind = 'canvas') => ({ kind, tool, ok: true, state: 'done', summary: `${tool}: K` });
// Reads that follow each other fold into one row; a running or failed read and the work stay rows.
const items = groupSteps([look('search_models'), look('describe_canvas'), act('place_combo'), look('list_combos', false), look('search_images')]);
assert.deepEqual(items.map(item => item.type), ['looks', 'step', 'step', 'looks']);
assert.equal(items[0].steps.length, 2);
const inFlight = groupSteps([look('search_models'), look('describe_canvas', true, 'running')]);
assert.deepEqual(inFlight.map(item => item.type), ['looks', 'step'], 'an in-flight read stays visible until it finishes');
assert.deepEqual(groupSteps(undefined), []);
assert.deepEqual(lookTools([look('a'), look('b'), look('a')]), [{ tool: 'a', count: 2 }, { tool: 'b', count: 1 }]);
// Words.
assert.equal(toolKey('place_combo'), 'aiTool_place_combo');
assert.equal(toolKey('x<y>'), 'aiTool_xy');
assert.equal(stepDetail(act('place_combo')), 'K');
assert.equal(stepDetail({ tool: 'describe_canvas', summary: 'describe_canvas' }), '');
assert.equal(stepDetail({ tool: 'report', summary: 'something else' }), 'something else');
// A task's state and counts.
assert.equal(taskState({ live: true, errors: 1 }), 'live');
assert.equal(taskState({ live: true, asks: 1 }), 'asks', 'a question waiting for the user comes first');
assert.equal(taskState({ waiting: true }), 'waiting', 'a task the user started, before an AI takes it');
assert.deepEqual(taskCounts({ asks: 2 }), [{ key: 'aiCountAsks', values: { count: 2 } }]);
assert.equal(taskState({ errors: 2 }), 'error');
assert.equal(taskState({}), 'idle', 'silence without an explicit end is not completion');
assert.equal(taskState({ ended: 100 }), 'done');
assert.deepEqual(taskCounts({ kinds: { canvas: 2, look: 5, speech: 1 }, images: 3, errors: 1 }), [
{ key: 'aiCountCanvas', values: { count: 2 } }, { key: 'aiCountImages', values: { count: 3 } },
{ key: 'aiCountSpeech', values: { count: 1 } }, { key: 'aiCountErrors', values: { count: 1 } },
]);
assert.deepEqual(taskCounts({}), []);
// What a task made, and where it is served.
const made = taskResults({ steps: [
{ results: [{ type: 'image', filename: 'a.png', subfolder: 'x' }, { type: 'canvas', activity: 'e1' }] },
{ results: [{ type: 'model', model_id: 'm' }, { type: 'weird' }] },
] });
assert.equal(made.image.length, 1);
assert.equal(made.canvas[0].activity, 'e1');
assert.equal(made.model.length, 1);
assert.equal(outputUrl({ filename: 'a b.png', subfolder: 'x/y' }), '/view?filename=a+b.png&subfolder=x%2Fy&type=output');
assert.deepEqual(audioResult({ file: 'audio/Aris/a.flac' }), { filename: 'a.flac', subfolder: 'audio/Aris', folder: 'output' });
assert.deepEqual(audioResult({ file: 'a.flac' }), { filename: 'a.flac', subfolder: '', folder: 'output' });
assert.equal(isVideo({ filename: 'clip.MP4' }), true);
assert.equal(isVideo({ filename: 'a.png' }), false);
// Output-only reuse: never compare an input/temp file with a same-named output by accident.
assert.equal(outputPicture({ filename: 'a.png', folder: 'temp' }), null);
assert.equal(outputPicture({ filename: 'a.mp4' }), null);
assert.equal(outputPicture({ filename: 'a.flac' }), null);
assert.deepEqual(outputPicture({ filename: 'a.png', subfolder: 'x' }), { type: 'output', filename: 'a.png', subfolder: 'x' });
const repeated = uniquePictures([{ filename: 'a.png' }, { filename: 'a.png', folder: 'output' }, { filename: 'a.png', subfolder: 'b' }]);
assert.equal(repeated.length, 2);
assert.notEqual(pictureKey(repeated[0]), pictureKey(repeated[1]));
assert.match(picturePreview({ filename: 'a b.png', subfolder: 'x/y' }), /^\/anomalous\/output_thumbnail\?filename=a\+b.png&subfolder=x%2Fy$/);
assert.equal(picturePreview({ filename: 'a.mp4' }), outputUrl({ filename: 'a.mp4' }));
// How long a step took.
assert.equal(duration(400), null);
assert.deepEqual(duration(48_200), { key: 'aiSeconds', values: { count: 48 } });
assert.deepEqual(duration(150_000), { key: 'aiMinutes', values: { count: 3 } });
// The flow: stages in the order they came, each with its last step's state.
const staged = (id, stage, extra = {}) => ({ id, stage, ok: true, state: 'done', results: [], ...extra });
const flow = flowStages({ steps: [
staged('a', 'model'), staged('b', 'prompt'), staged('c', 'model', { state: 'running' }),
{ id: 'd', kind: 'note', ok: true, results: [{ type: 'image', filename: 'x.png' }] }, { id: 'e', kind: 'user', ok: true },
staged('f', 'run', { state: 'asking' }), staged('g', 'canvas', { ok: false, state: 'stopped' }),
] });
assert.deepEqual(flow.map(item => [item.stage, item.state, item.first, item.count]), [
['model', 'running', 'a', 2], ['prompt', 'done', 'b', 1], ['picture', 'done', 'd', 1], ['run', 'asking', 'f', 1], ['canvas', 'error', 'g', 1],
]);
assert.deepEqual(flowStages(null), []);
// The activity entries a task shows: the AI's canvas changes and the user's own.
assert.deepEqual(activityIds({ steps: [{ results: [{ type: 'canvas', activity: 'a1' }, { type: 'model' }] }, { tool: 'user_activity', activity: 'u1', results: [] }] }), ['a1', 'u1']);
console.log('ai_task_view: ok');