Every script is a draft, saved on its own in ComfyUI's user folder (anomalous_model_browser/voice_over_drafts.json through /userdata), read again and joined before each write so two tabs keep each other's drafts. A bar over the script names it and leads to New, Drafts and Close; the box shows a card per draft (open, rename, copy, delete) and Recently deleted (30 days). A reload opens the draft that was open; a file voiced again from the gallery becomes a draft of its own; the one localStorage draft of the earlier build moves in once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
93 lines
5.4 KiB
JavaScript
93 lines
5.4 KiB
JavaScript
// The voice-over drafts box without storage (web/modules/script_drafts.js): what a kept script
|
|
// may hold, the box's moves, two tabs' boxes joined, and the titles and times on its cards.
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
cleanBox, cleanScript, copyDraft, deleteDraft, deletedDrafts, draftSpeakers, draftTitle, dropOldDeleted, emptyBox,
|
|
hasLines, keepScript, liveDrafts, makeDraft, mergeBoxes, putDraft, renameDraft, restoreDraft, whenKey,
|
|
} from '../web/modules/script_drafts.js';
|
|
|
|
const DAY = 24 * 60 * 60 * 1000;
|
|
const script = (...texts) => ({ format: 1, lines: texts.map(text => ({ text, emotion: 'main', speaker: 'gpt_sovits:Aris' })), groupKey: 'gpt_sovits:Aris', script: {}, cast: {} });
|
|
|
|
// A kept script: blank lines and bad settings go, the right ones stay in a fixed order.
|
|
const cleaned = cleanScript({
|
|
format: 1,
|
|
groupKey: 'gpt_sovits:Aris',
|
|
lines: [{ text: ' ' }, { text: 'a', tune: { speed: 1.2, language: 'ja', odd: 1 } }, { text: 'b', speaker: 3 }, null],
|
|
script: { speed: 'fast', preset: 'stable' },
|
|
cast: { 'gpt_sovits:Aris': { language: 'ja' }, 'gpt_sovits:Momoi': { speed: '1' } },
|
|
});
|
|
assert.deepEqual(cleaned, {
|
|
format: 1,
|
|
lines: [{ text: 'a', emotion: 'main', speaker: null, tune: { language: 'ja', speed: 1.2 } }, { text: 'b', emotion: 'main', speaker: null }],
|
|
groupKey: 'gpt_sovits:Aris',
|
|
script: { preset: 'stable' },
|
|
cast: { 'gpt_sovits:Aris': { language: 'ja' } },
|
|
});
|
|
assert.equal(cleanScript({ format: 2, lines: [] }), null);
|
|
assert.equal(hasLines(cleanScript({ format: 1, lines: [{ text: ' ' }] })), false);
|
|
assert.deepEqual(cleanBox(null), emptyBox());
|
|
assert.deepEqual(cleanBox({ format: 1, current: 'x', drafts: [{ id: 'x', script: { format: 9 } }, { id: '', script: script('a') }] }), emptyBox());
|
|
|
|
// The box: new, renamed, copied, deleted, brought back; the last changed first.
|
|
const box = emptyBox();
|
|
const first = putDraft(box, makeDraft(script('おはよう、先生!今日もいい天気ですね'), 1000, { id: 'a' }));
|
|
const second = putDraft(box, makeDraft(script('b'), 2000, { id: 'b' }));
|
|
box.current = 'a';
|
|
assert.deepEqual(liveDrafts(box).map(draft => draft.id), ['b', 'a']);
|
|
keepScript(box, 'a', script('c'), 3000);
|
|
assert.deepEqual(liveDrafts(box).map(draft => draft.id), ['a', 'b']);
|
|
assert.equal(draftTitle(first), 'c');
|
|
renameDraft(box, 'a', ' 第 3 集开场 ', 3500);
|
|
assert.equal(draftTitle(first), '第 3 集开场');
|
|
assert.equal(draftTitle(makeDraft(script('おはよう、先生!今日もいい天気ですね。'))), 'おはよう、先生!今日もいい天気ですね…');
|
|
assert.equal(draftTitle(makeDraft(script())), '');
|
|
const copy = copyDraft(box, 'a', '第 3 集开场(副本)', 4000);
|
|
assert.equal(copy.name, '第 3 集开场(副本)');
|
|
copy.script.lines[0].text = 'changed';
|
|
assert.equal(first.script.lines[0].text, 'c', 'a copy is its own');
|
|
deleteDraft(box, 'a', 5000);
|
|
assert.equal(box.current, null, 'the open draft is let go when deleted');
|
|
assert.deepEqual(deletedDrafts(box).map(draft => draft.id), ['a']);
|
|
assert.equal(liveDrafts(box).some(draft => draft.id === 'a'), false);
|
|
restoreDraft(box, 'a', 6000);
|
|
assert.equal(first.deleted, null);
|
|
deleteDraft(box, 'b', 7000);
|
|
assert.equal(dropOldDeleted(box, 7000 + 29 * DAY), false);
|
|
assert.equal(dropOldDeleted(box, 7000 + 30 * DAY), true);
|
|
assert.equal(box.drafts.some(draft => draft.id === 'b'), false);
|
|
assert.equal(second.deleted, 7000);
|
|
|
|
// Who reads, in the order they first speak (a line without a speaker is the cast's pick).
|
|
assert.deepEqual(draftSpeakers({ groupKey: 'k:A', lines: [{ text: 'x', speaker: 'k:B' }, { text: 'y', speaker: null }, { text: 'z', speaker: 'k:B' }] }), ['k:B', 'k:A']);
|
|
|
|
// Two tabs: each draft as it was changed last; the draft open here stays this tab's.
|
|
const mine = { format: 1, current: 'm', drafts: [
|
|
makeDraft(script('mine'), 100, { id: 'm' }),
|
|
makeDraft(script('old here'), 100, { id: 's' }),
|
|
] };
|
|
const theirs = { format: 1, current: 'o', drafts: [
|
|
makeDraft(script('newer there'), 200, { id: 'm' }),
|
|
makeDraft(script('newer there'), 200, { id: 's' }),
|
|
makeDraft(script('only there'), 150, { id: 'o' }),
|
|
] };
|
|
const joined = mergeBoxes(mine, theirs);
|
|
const text = id => joined.drafts.find(draft => draft.id === id).script.lines[0].text;
|
|
assert.equal(joined.current, 'm');
|
|
assert.equal(text('m'), 'mine');
|
|
assert.equal(text('s'), 'newer there');
|
|
assert.equal(text('o'), 'only there');
|
|
const gone = { format: 1, current: null, drafts: [{ ...makeDraft(script('x'), 100, { id: 's' }), deleted: 300, updated: 300 }] };
|
|
assert.equal(mergeBoxes(gone, theirs).drafts.find(draft => draft.id === 's').deleted, 300, 'a deletion is a change');
|
|
|
|
// When: just now, minutes, today, yesterday, a date (with the year when it is another one).
|
|
const now = new Date(2026, 9, 20, 15, 30).getTime();
|
|
assert.deepEqual(whenKey(now - 20 * 1000, now), { key: 'draftsJustNow' });
|
|
assert.deepEqual(whenKey(now - 5 * 60000, now), { key: 'draftsMinutesAgo', values: { count: 5 } });
|
|
assert.deepEqual(whenKey(new Date(2026, 9, 20, 9, 5).getTime(), now), { key: 'draftsTodayAt', values: { time: '09:05' } });
|
|
assert.deepEqual(whenKey(new Date(2026, 9, 19, 23, 0).getTime(), now), { key: 'draftsYesterdayAt', values: { time: '23:00' } });
|
|
assert.deepEqual(whenKey(new Date(2026, 9, 18, 8, 0).getTime(), now), { key: 'draftsDate', values: { month: 10, day: 18, year: 2026 } });
|
|
assert.equal(whenKey(new Date(2025, 11, 31).getTime(), now).key, 'draftsDateYear');
|
|
|
|
console.log('script drafts: ok');
|