2394eff1c0
- LM Studio client (httpx-based, OpenAI-compatible) - SVG validator (lxml, whitelist tags, no <script>/<foreignObject>/http refs) - PNG renderer (resvg-py primary, cairosvg fallback - no native cairo dep) - History (SQLite, tracks raw/validated/preview paths) - Gradio UI on 127.0.0.1:8788 with: * mode radio (icon/illustration) * n_candidates slider (default 1) * image upload for image-to-SVG * LM Studio URL/token inputs * model dropdown + refresh button - prompts/ with system_icon.txt, system_illustration.txt, few_shot_examples.txt - docs/spec.md, docs/design.md - 122 unit/integration tests passing
252 lines
17 KiB
Markdown
252 lines
17 KiB
Markdown
# OmniSVG-Lite — Spec
|
||
|
||
> MVP-спецификация. Цель: дать пользователю веб-приложение, в котором можно
|
||
> из текста или картинки получить N кандидатов SVG в одном из двух режимов
|
||
> (icon / illustration), посмотреть PNG-превью и сохранить историю генераций.
|
||
|
||
---
|
||
|
||
## 1. Цели и не-цели
|
||
|
||
**Цель MVP:**
|
||
- Локально запускаемый Gradio UI, который ходит в LM Studio (OpenAI-compatible)
|
||
и возвращает валидный SVG + PNG-превью.
|
||
- Один пользователь, без авторизации, без облака.
|
||
- История генераций хранится в локальной SQLite.
|
||
|
||
**Не-цели (Out of Scope для MVP):**
|
||
- Редактирование SVG в браузере (paint / drag).
|
||
- Экспорт в Lottie / GIF / видео.
|
||
- Batch-генерация (CSV промптов, генерация по расписанию).
|
||
- Авторизация, multi-user, разграничение прав.
|
||
- Деплой / Docker / CI.
|
||
- Per-mode температура — единый слайдер на UI.
|
||
- Fine-tune, RLHF, оценка качества моделей.
|
||
- Поддержка моделей, отличных от LM Studio (Ollama, vLLM, OpenAI cloud) — клиент
|
||
пишется под OpenAI-compatible chat completions, но другие провайдеры не
|
||
тестируются.
|
||
|
||
---
|
||
|
||
## 2. Режимы генерации
|
||
|
||
### 2.1. Mode: `icon`
|
||
|
||
**Назначение:** маленькие, узнаваемые, плоские или с лёгкой стилизацией иконки
|
||
для UI. Допустимы монохром и 2–3 цвета. Геометрия упрощённая, без мелких деталей.
|
||
|
||
**Требования к SVG:**
|
||
- `viewBox="0 0 64 64"` (фиксировано, чтобы иконки были консистентны).
|
||
- Геометрия привязана к пиксельной сетке (целочисленные координаты ±0.5).
|
||
- Без градиентов со множеством stops, без фильтров (`feGaussianBlur` и т.п.).
|
||
- Допустимы: `<path>`, `<rect>`, `<circle>`, `<ellipse>`, `<line>`, `<polygon>`,
|
||
`<polyline>`, `<g>`, `<text>`, базовые `<linearGradient>`/`<radialGradient>`.
|
||
|
||
**Промпт-стиль (направление для пользователя):**
|
||
"outline battery", "filled magnifying glass", "settings gear, line style".
|
||
|
||
### 2.2. Mode: `illustration`
|
||
|
||
**Назначение:** полноценные сцены / объекты / паттерны. Больше деталей, больше
|
||
цветов, допустимы градиенты и простые фильтры.
|
||
|
||
**Требования к SVG:**
|
||
- `viewBox="0 0 512 512"` (фиксировано).
|
||
- Допустимы все валидные теги SVG 1.1 кроме `<script>` и `<foreignObject>`.
|
||
- Допустимы простые `<filter>` (без `<feImage>` на внешний URL).
|
||
- Палитра ограничена заявленной пользователем (если указана).
|
||
|
||
**Промпт-стиль:**
|
||
"flat illustration of a fox in a forest, autumn colors",
|
||
"isometric server room, blue and purple palette".
|
||
|
||
### 2.3. Сводная таблица
|
||
|
||
| Параметр | icon | illustration |
|
||
|----------------------|---------------------------------|----------------------------------|
|
||
| viewBox | `0 0 64 64` | `0 0 512 512` |
|
||
| Размер файла (лимит) | 32 KB | 256 KB |
|
||
| Допустимые теги | Базовые shapes + g + text | Полный SVG 1.1 минус опасные |
|
||
| Градиенты | ≤ 2 stops | без ограничений |
|
||
| Фильтры | нет | простые (без feImage на URL) |
|
||
| Текст | допустим, но не основной канал | допустим |
|
||
| Дефолтный n_candidates | 4 | 2 |
|
||
|
||
---
|
||
|
||
## 3. Типы задач
|
||
|
||
### 3.1. text-to-SVG
|
||
|
||
- Вход: `prompt: str` (1–1000 символов), `mode ∈ {icon, illustration}`,
|
||
`n_candidates: int ∈ [1, 8]`.
|
||
- Опционально: палитра (поле в UI, не обязательное).
|
||
- Модель получает только текст.
|
||
|
||
### 3.2. image-to-SVG
|
||
|
||
- Вход: `prompt: str` (1–1000 символов), `image: PIL.Image | file path`,
|
||
`mode ∈ {icon, illustration}`, `n_candidates: int ∈ [1, 8]`.
|
||
- Допустимые форматы картинки: PNG, JPEG, WEBP. Максимум 10 MB, до 4096×4096.
|
||
- Картинка передаётся в модель как `image_url` (data: URL, base64).
|
||
- В промпте добавляется явная инструкция: "Recreate the visual content of the
|
||
attached image as SVG. Do not describe, just generate the markup."
|
||
|
||
---
|
||
|
||
## 4. Поведение N кандидатов
|
||
|
||
**Логика выбора лучшего кандидата (для MVP — простая, детерминированная):**
|
||
|
||
1. Из N ответов модели берём только валидные (прошедшие lxml-валидацию).
|
||
2. Если валидных 0 — показываем ошибку `"no_valid_candidates"` (см. §6).
|
||
3. Каждому валидному кандидату считаем **score = размер_файла_KB** (инвертированный:
|
||
меньше = лучше). Это прокси "компактность", не качество.
|
||
4. Лучший = кандидат с минимальным score.
|
||
5. **Показ в UI:**
|
||
- `Gallery` отображает PNG-превью всех валидных кандидатов в сетке.
|
||
- Лучший помечается визуально: бейдж "★ best" над PNG.
|
||
- В выпадашке/таблице истории сохраняются все валидные SVG + пути к PNG.
|
||
|
||
**Замечание:** для MVP не используем VLM-as-judge или pairwise — это сильно
|
||
увеличит latency и стоимость. Выбор по размеру файла — намеренно простой.
|
||
|
||
---
|
||
|
||
## 5. User Stories + Acceptance Criteria
|
||
|
||
### US-1. Сгенерировать иконку по текстовому промпту
|
||
|
||
**As a** дизайнер,
|
||
**I want** ввести текстовое описание иконки и получить N вариантов SVG,
|
||
**so that** я могу быстро подобрать визуал для кнопки/меню.
|
||
|
||
**Acceptance Criteria (Given/When/Then):**
|
||
|
||
- **Given** UI запущен и LM Studio отвечает, **when** я выбираю mode=`icon`,
|
||
ввожу `prompt="filled magnifying glass"`, `n_candidates=4`, **then**:
|
||
- отправляется ровно 1 запрос в LM Studio с `n=4`,
|
||
- получаю 4 PNG-превью в Gallery (или меньше, если часть невалидна),
|
||
- у лучшего есть бейдж "★ best",
|
||
- история в SQLite содержит запись со всеми raw/validated SVG.
|
||
- **Given** промпт пустой или длиннее 1000 символов, **when** я нажимаю
|
||
Generate, **then** вижу `gr.Warning` "Prompt must be 1–1000 characters",
|
||
запрос в LM Studio НЕ отправляется.
|
||
- **Given** LM Studio не отвечает 30+ секунд, **when** идёт генерация, **then**
|
||
запрос отменяется, UI показывает ошибку "LM Studio timeout".
|
||
|
||
### US-2. Сгенерировать иллюстрацию по картинке
|
||
|
||
**As a** иллюстратор,
|
||
**I want** загрузить референс-картинку и текстовое описание, получить N SVG,
|
||
**so that** я могу быстро сделать vector-версию понравившегося эскиза.
|
||
|
||
**Acceptance Criteria:**
|
||
|
||
- **Given** UI запущен, **when** я загружаю PNG/JPEG/WEBP, ввожу
|
||
`prompt="flat illustration, blue and teal palette"`, выбираю
|
||
mode=`illustration`, `n_candidates=2`, **then**:
|
||
- картинка кодируется в base64 data: URL и отправляется в LM Studio,
|
||
- получаю до 2 PNG в Gallery,
|
||
- все raw-ответы и валидные SVG попадают в историю.
|
||
- **Given** загружена картинка > 10 MB или > 4096×4096, **when** я нажимаю
|
||
Generate, **then** UI отклоняет с ошибкой валидации до отправки в LM Studio.
|
||
|
||
### US-3. Посмотреть и скачать результат
|
||
|
||
**As a** пользователь,
|
||
**I want** увидеть PNG-превью, скачать выбранный SVG и PNG,
|
||
**so that** я могу сразу использовать результат в своих задачах.
|
||
|
||
**Acceptance Criteria:**
|
||
|
||
- **Given** генерация завершилась с ≥ 1 валидным кандидатом, **when** я смотрю
|
||
на Gallery, **then**:
|
||
- каждая картинка кликабельна,
|
||
- под/возле картинки — кнопки `Download SVG` и `Download PNG`,
|
||
- скачиваются именно те файлы, которые видны на превью (source of truth —
|
||
путь в `preview_paths`).
|
||
- **Given** я нажимаю `Download SVG`, **then** браузер получает файл с MIME
|
||
`image/svg+xml` и расширением `.svg`.
|
||
|
||
### US-4. Просмотреть историю генераций
|
||
|
||
**As a** пользователь,
|
||
**I want** видеть таблицу последних генераций и кликнуть строку, чтобы
|
||
повторно открыть результаты,
|
||
**so that** я могу вернуться к удачному варианту.
|
||
|
||
**Acceptance Criteria:**
|
||
|
||
- **Given** UI запущен, **when** я смотрю на History, **then**:
|
||
- таблица показывает последние 50 записей (id, prompt, mode, model, n,
|
||
status, created_at),
|
||
- новые записи появляются вверху,
|
||
- строки сортируются по `created_at DESC`.
|
||
- **Given** я кликаю строку в History, **when** выбираю запись, **then**
|
||
Gallery обновляется превью из `preview_paths` этой записи, raw SVG
|
||
подгружается в отдельный code-block для копирования.
|
||
- **Given** приложение перезапущено, **when** я открываю History, **then**
|
||
данные сохранились (SQLite на диске).
|
||
|
||
### US-5. Контроль параметров (температура, n)
|
||
|
||
**As a** пользователь,
|
||
**I want** управлять количеством кандидатов и креативностью,
|
||
**so that** балансировать скорость/стоимость и разнообразие.
|
||
|
||
**Acceptance Criteria:**
|
||
|
||
- **Given** UI запущен, **when** я двигаю `n_candidates` slider, **then**
|
||
диапазон 1–8, шаг 1, дефолт зависит от mode (icon=4, illustration=2).
|
||
- **Given** UI запущен, **when** я двигаю `temperature` slider, **then**
|
||
диапазон 0.0–1.5, шаг 0.05, дефолт 0.4.
|
||
- **Given** `temperature=0`, **when** идёт генерация, **then** отправляется
|
||
параметр `temperature=0` (детерминированный режим).
|
||
|
||
---
|
||
|
||
## 6. Edge Cases (таблица)
|
||
|
||
| # | Сценарий | Поведение |
|
||
|---|---------------------------------------------|--------------------------------------------------------------------------------------------------------|
|
||
| 1 | Пустой промпт | `gr.Warning` "Prompt must be 1–1000 characters", запрос не отправляется. |
|
||
| 2 | Промпт > 1000 символов | То же, что (1). |
|
||
| 3 | Картинка > 10 MB / > 4096×4096 / не PNG/JPEG/WEBP | Отклоняется до LM Studio, `gr.Warning` с указанием ограничения. |
|
||
| 4 | LM Studio не отвечает (timeout 30s) | `gr.Error` "LM Studio timeout at <url>", статус генерации `error`, в лог пишется traceback. |
|
||
| 5 | LM Studio вернул 5xx | `gr.Error` "LM Studio error: <code> <body>", статус `error`. |
|
||
| 6 | Модель вернула не-SVG (текст/JSON/мусор) | Парсер пытается вытащить `<svg>...</svg>` regex'ом; если не нашёл — кандидат помечается invalid, идёт в history со status=`invalid`, в Gallery не попадает. |
|
||
| 7 | SVG не well-formed XML | lxml бросает `XMLSyntaxError` → кандидат invalid, в лог `WARNING`, в history. |
|
||
| 8 | Отсутствует / кривой `viewBox` | `ValidatorError("missing_viewbox")` → invalid. |
|
||
| 9 | Внутри `<script>` или `<foreignObject>` | `ValidatorError("disallowed_tag")` → invalid. |
|
||
| 10| `http://` / `https://` в `href` / `xlink:href` / `url(...)` | `ValidatorError("external_ref")` → invalid. |
|
||
| 11| Размер файла > лимита (32 KB / 256 KB) | `ValidatorError("too_large")` → invalid. |
|
||
| 12| Все N кандидатов invalid | `gr.Info` "All N candidates failed validation. See history for details.", в history — запись со status=`all_invalid`. |
|
||
| 13| Картинка в image-to-SVG повреждена | PIL при открытии бросает `UnidentifiedImageError` → `gr.Warning` "Cannot read image", запрос не отправляется. |
|
||
| 14| Пользователь жмёт Generate повторно во время генерации | Кнопка `interactive=False` на время выполнения, повторный клик игнорируется. |
|
||
|
||
---
|
||
|
||
## 7. Не-функциональные требования
|
||
|
||
| Категория | Требование |
|
||
|----------------|------------------------------------------------------------------------------------------|
|
||
| Performance | Генерация 1 иконки (n=1) — p95 ≤ 20 секунд на одной 5090 / Ryzen 9. |
|
||
| Performance | PNG-рендер одного SVG — p95 ≤ 200 ms. |
|
||
| Privacy | Никакие промпты/картинки не уходят за пределы LM Studio на этой машине. |
|
||
| Reliability | Падение LM Studio не валит UI: ошибка показывается, остальные кнопки работают. |
|
||
| Storage | SQLite WAL mode, `data/omnisvg.db`. Превью PNG — `data/previews/<id>_<n>.png`. |
|
||
| Logging | `INFO` для старта/конца генерации, `WARNING` для invalid, `ERROR` для LM Studio errors. |
|
||
| Concurrency | MVP — однопользовательский, очередь не требуется. UI блокирует кнопку на время генерации. |
|
||
|
||
---
|
||
|
||
## 8. Открытые вопросы
|
||
|
||
- Требуется ли streaming в UI (как в LM Studio)? **Для MVP — нет**, выбран non-stream
|
||
режим: n>1 плохо сочетается со streaming, плюс парсить инкрементально тяжелее.
|
||
- Нужна ли кнопка "regenerate" с теми же параметрами? **Не вошла в MVP**, но UI
|
||
должен позволять скопировать промпт обратно в textbox.
|
||
- Нужен ли экспорт PNG в высоком разрешении (1024×1024)? **Нет**, достаточно
|
||
рендера в нативный viewBox.
|