- 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
17 KiB
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 — простая, детерминированная):
- Из N ответов модели берём только валидные (прошедшие lxml-валидацию).
- Если валидных 0 — показываем ошибку
"no_valid_candidates"(см. §6). - Каждому валидному кандидату считаем score = размер_файла_KB (инвертированный: меньше = лучше). Это прокси "компактность", не качество.
- Лучший = кандидат с минимальным score.
- Показ в 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.
- отправляется ровно 1 запрос в LM Studio с
- 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 браузер получает файл с MIMEimage/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_candidatesslider, then диапазон 1–8, шаг 1, дефолт зависит от mode (icon=4, illustration=2). - Given UI запущен, when я двигаю
temperatureslider, 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 ", статус генерации error, в лог пишется traceback. |
| 5 | LM Studio вернул 5xx | gr.Error "LM Studio 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.