diff --git a/docs/architecture.md b/docs/architecture.md index 2acdbdb..03a7b2f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -107,6 +107,13 @@ registry/{tools,pages}/*.ts `registry-schema.ts`, поэтому добавление вида поля не трогает страницы. - **Модель превью отделена от вёрстки.** `schema-preview-model.ts` — чистая функция, покрыта unit-тестами; `SchemaPreview.svelte` только рисует её вывод. +- **Цепочка работает над набором файлов (fan-out).** `run-chain.ts`: «ток» между + шагами — массив именованных изображений (одиночная загрузка = набор из + одного); image-шаг применяется к каждому файлу, files-шаг (split) склеивает + имена частей (`<вход>-<часть>`), analyze-шаг (text/verdict) при batch-входе + видит первый файл. Шаг роняет цепочку, только если упали все файлы + (`ChainStepError`), частичные сбои — warnings. Мультивыбор файлов в drop-зоне; + набор на выходе скачивается zip через `zip.ts`. ## 3. Инструмент и страница diff --git a/docs/backlog.md b/docs/backlog.md index e9189c3..2f14ae4 100644 --- a/docs/backlog.md +++ b/docs/backlog.md @@ -18,33 +18,20 @@ - [ ] посмотреть реализацию flip animation svelte для анимации переключения режима aligned\normal - [ ] переделать визуал toggle компонента (которым режим переключается) -- [ ] **Идея 4. Batch-обработка архивов выбранным chain** — оценка L. Вход — - zip-архив картинок (распаковка в браузере), прогон текущего или выбранного - сохранённого chain по каждому файлу, сборка результата обратно в zip для - скачивания. Зависит от идеи 3 (выбор цепочки) и от экспортного формата - pipeline JSON; прогресс и ошибки — пофайлово. -- [ ] **Идея 15. Набор файлов в пайплайне: 1 → many не должен быть - терминальным** — оценка L. **Сейчас** `execute` принимает один - `source: PixelImage | undefined` и возвращает один `ToolResult`, а - `SchemaToolView` раскладывает вывод в `result` / `fileResult` / - `textResult` по `resultKind` — поэтому шаг с `result: "files"` упирается в - конец цепочки. **Направление:** следующий инструмент применяется **ко всем - картинкам** предыдущего результата (fan-out), а many → 1 (вход = архив или - несколько файлов) — обратная сторона того же контракта. Терминальными - остаются только результаты, которые не картинка (text, verdict). Что - обдумать: - где проходит граница: один вызов `run(ctx)` на файл (имя части - склеивается в `FileResult.files`) или батч целиком на стороне исполнителя; - `executor/protocol.ts` `files` уже сериализуются, но при батче нужен - прогресс и отмена; - частичные ошибки: один битый файл не должен ронять - весь шаг — нужен per-file отчёт, а не один `ToolError` на шаг; - имена - частей на каждом шаге и Download для набора (сейчас zip по `page.slug`, - `SchemaToolView.download()`); - обратная сторона: распаковка zip в - браузере, порядок файлов, поведение many → 1 → many в одной цепочке. - Зависит от идеи 10 (остальные потребители 1 → many) и идеи 4 (batch по - архиву), `docs/roadmap.md` фаза 8 «batch-страница». +- [ ] **Идея 4. Batch-обработка архивов выбранным chain** — оценка L. Fan-out и + мультивыбор уже есть (идея 15: `web/src/lib/run-chain.ts`, zip-выгрузка + набора). Осталось: zip на вход (распаковка в браузере, порядок файлов, + поведение many → 1 → many), прогресс и отмена пофайлово, запуск по + сохранённой цепочке из `/chains`. +- [ ] **Идея 15. Остатки fan-out** — сделано: 1 → many не терминален, + мультизагрузка, склейка имён, per-file ошибки с warning. Отложено: + parts-grid на промежуточных тайлах (сейчас первая картинка набора), + прогресс/отмена, analyze-шаги пофайлово (сейчас firstOnly-warning). ## Инструменты +- [ ] split-to-parts, загружаем картинку, после этого переключаемся на режим by + tile - интерфейс начинает тормозить. уже пора переходить на worker\wasm? - [ ] remove-alpha-channel - дубль инструмента с выбором цвета на замену, что с такими делать вообще? оставить как seo? можно ли под капотом использовать другой инструмент с хардкодед настройками? @@ -54,9 +41,6 @@ выделенной области - [ ] сделать конвертацию в смайлики\награды для твитча (6 размеров или выбор какой размер - 112\56\28px или 72\36\18) -- [ ] сделать несколько проходное сжатие png для получения необходимого размера - файлов, посмотреть реализацию в - [twitch-emote-resizer](https://github.com/tma02/twitch-emote-resizer) - [ ] Font Style - обдумать выбор шрифта (google fonts???) как организовать шрифты с кириллицей? сделать галочку "только с кириллицей"??? (решение о наборе и лицензиях: `docs/plan-platform.md` §4 — только OFL/Apache с @@ -134,6 +118,8 @@ ## UI и качество +- [ ] при загрузке нескольких файлов, показывает source не той же высоты, как + result - [ ] split to parts - посмотреть вариант с toggle component - может будет удобнее чем sectioned buttons - [ ] чип «Маска» (`resultCard.maskToggle` на тайлах результата) — попробовать diff --git a/web/src/lib/chains.svelte.ts b/web/src/lib/chains.svelte.ts index acecf21..166b1bb 100644 --- a/web/src/lib/chains.svelte.ts +++ b/web/src/lib/chains.svelte.ts @@ -8,6 +8,7 @@ import { serializeSteps, type ChainStep, } from "./pipeline.svelte"; +import type { ToolImageFile } from "./registry"; export interface NamedChain { id: string; @@ -135,6 +136,7 @@ export function deleteChain(id: string): void { /** State that cannot persist across the tool -> pipeline route swap. */ export interface ChainHandoff { source: PixelImage | null; + sourceFiles?: ToolImageFile[]; textSource: string; format: OutputMime; limitKb: number | undefined; diff --git a/web/src/lib/components/schema/Dropzone.svelte b/web/src/lib/components/schema/Dropzone.svelte index cbffae6..77325d3 100644 --- a/web/src/lib/components/schema/Dropzone.svelte +++ b/web/src/lib/components/schema/Dropzone.svelte @@ -5,15 +5,18 @@ interface Props { accept?: string; onfile?: (file: File) => void; + onfiles?: (files: File[]) => void; } - let { accept = "image/*", onfile }: Props = $props(); + let { accept = "image/*", onfile, onfiles }: Props = $props(); let dragging = $state(false); let input = $state(null); function handleFiles(files: FileList | null | undefined) { - const file = files?.[0]; - if (file) onfile?.(file); + const list = Array.from(files ?? []); + if (list.length === 0) return; + if (list.length > 1 && onfiles) onfiles(list); + else if (list[0]) onfile?.(list[0]); } function onDrop(e: DragEvent) { @@ -50,6 +53,7 @@ bind:this={input} type="file" {accept} + multiple hidden onchange={(e) => handleFiles((e.target as HTMLInputElement).files)} /> diff --git a/web/src/lib/components/schema/PartsGrid.svelte b/web/src/lib/components/schema/PartsGrid.svelte new file mode 100644 index 0000000..fbaa27c --- /dev/null +++ b/web/src/lib/components/schema/PartsGrid.svelte @@ -0,0 +1,70 @@ + + +
+
+ {#each parts as part (part.name)} +
+ {part.name} +
{part.name}
+
+ {/each} +
+
+ + diff --git a/web/src/lib/components/schema/SchemaPreview.svelte b/web/src/lib/components/schema/SchemaPreview.svelte index 50de80c..f94be22 100644 --- a/web/src/lib/components/schema/SchemaPreview.svelte +++ b/web/src/lib/components/schema/SchemaPreview.svelte @@ -1,6 +1,7 @@