feat: browser access (allowed_origins, CORS with Private Network Access), job cancel, Tomie/Unity adapters, benchmark
Includes earlier uncommitted work: Tomie and Unity adapters, benchmark, structured output, call metrics. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
13492929c6
commit
f49ee90477
32 files changed
+2799
-51
No files matched your search
@@ -5,9 +5,11 @@
|
||||
## Запуск на Windows
|
||||
|
||||
1. Установите Python 3.11+ и LM Studio. Скачайте модель, загрузите её и запустите сервер в Developer → Start Server.
|
||||
2. Дважды щёлкните `start.bat`. При первом запуске создаётся `.venv`, скачиваются зависимости из PyPI и копируется `config.example.toml` в `config.toml`.
|
||||
2. Дважды щёлкните `scripts/windows/start.bat`. При первом запуске создаётся `.venv`, скачиваются зависимости из PyPI и копируется `config.example.toml` в `config.toml`.
|
||||
3. Документация с формами запросов: <http://127.0.0.1:8787/docs>. Состояние приложения: <http://127.0.0.1:8787/health>.
|
||||
|
||||
Все Windows-батники находятся в `scripts/windows/`: запуск API и бенчмарк — непосредственно в этой папке, подготовка/установка/откат Tomie — в `scripts/windows/tomie/`, внутренние общие команды — в `scripts/windows/lib/`. Они определяют корень проекта по своему расположению и работают при запуске из другого каталога.
|
||||
|
||||
После установки зависимостей локальный перевод не требует интернета. Чтобы обновить зависимости после изменения `pyproject.toml`, выполните `.venv\Scripts\python.exe -m pip install -e .`.
|
||||
|
||||
В `config.toml` задайте `providers.lmstudio.model` — **точный ID из `GET /providers/lmstudio/models`**, а не произвольное имя файла. Пустое значение допустимо, если `/models` возвращает ровно одну модель. При нескольких моделях приложение потребует явный выбор. Можно переопределить модель в отдельном проекте.
|
||||
@@ -104,14 +106,25 @@ python examples/translate_game.py --project PROJECT_ID --input examples/strings.
|
||||
|
||||
Сервер ждёт результат до 30 секунд: `200` с готовым заданием или `202` с текущим состоянием и `Location: /jobs/{id}`. После `202` опрашивайте этот адрес. Завершение задания с ошибкой тоже возвращает объект задания — проверяйте `status` и состояние каждой строки, одного HTTP-кода недостаточно.
|
||||
|
||||
### Вызов из браузера
|
||||
|
||||
Страница, открытая в браузере (например, библиотека игр), может работать с API напрямую, без посредника-сервера. Впишите её адрес в `config.toml` и перезапустите приложение:
|
||||
|
||||
```toml
|
||||
allowed_origins = ["https://games.example", "http://localhost:5173"]
|
||||
```
|
||||
|
||||
Допустимы только точные адреса вида `схема://хост[:порт]`, без пути и без `*`: иначе любой сайт мог бы пользоваться вашей моделью и читать проекты. Для Chrome сервер отвечает на предварительный запрос с `Access-Control-Request-Private-Network`. Сам браузер при первом обращении https-страницы к `127.0.0.1` спросит разрешение на доступ к локальной сети. `GET /health` возвращает `api_version` (сейчас 2), по которой клиент проверяет совместимость.
|
||||
|
||||
### Долговечные задания
|
||||
|
||||
- `POST /projects/{id}/jobs` — сразу возвращает `202` и ID. Предпочтительный вариант для скриптов.
|
||||
- `GET /jobs/{id}` — прогресс и результаты всех строк.
|
||||
- `GET /projects/{id}/jobs?limit=50&offset=0` — история заданий, новые первыми.
|
||||
- `POST /jobs/{id}/retry` — повторить незавершённые/неудачные строки после ошибки. Сбрасывает их счётчик попыток. Готовые строки сохраняются.
|
||||
- `POST /jobs/{id}/cancel` — остановить задание в очереди или в работе: невыполненные строки получают `failed` с ошибкой `Cancelled`, статус задания — `cancelled`; готовые строки сохраняются. Повторный запрос безвреден; `retry` возобновляет остановленное.
|
||||
|
||||
Статусы задания: `queued`, `running`, `completed`, `completed_with_errors`, `failed`. Статусы строк: `pending`, `completed`, `failed`. В `items` возвращаются `id`, `source`, `translation`, `status`, `error`, `attempts`, `cached`, `position`. При общей ошибке подключения/выбора модели задание получает `failed`, ещё не обработанные строки могут остаться `pending`.
|
||||
Статусы задания: `queued`, `running`, `completed`, `completed_with_errors`, `failed`, `cancelled`. Статусы строк: `pending`, `completed`, `failed`. В `items` возвращаются `id`, `source`, `translation`, `status`, `error`, `attempts`, `cached`, `position`. При общей ошибке подключения/выбора модели задание получает `failed`, ещё не обработанные строки могут остаться `pending`.
|
||||
|
||||
Задание хранит снимок контекста, словаря, правил и настроек провайдера. Обновление проекта не меняет уже созданные задания. Повтор старого задания использует старые настройки; для перевода по новым настройкам создайте новое.
|
||||
|
||||
@@ -171,11 +184,111 @@ Tencent не обещает отсутствие цензуры в карточ
|
||||
.venv\Scripts\python.exe examples/translate_game.py --project-file examples/tomie_project.json --input data/tomie/input.json --output data/tomie/result.json
|
||||
```
|
||||
|
||||
В словаре найдено 61 значение. По умолчанию извлекаются первые 15; `--limit 0` извлекает все. В manifest рядом с входным JSON сохраняются исходные ключи, номера строк, ID Unity-объекта и хеш исходного текста. Экстрактор только читает файлы игры и запрещает вывод внутрь её каталога. Обратная упаковка и замена шрифтов пока не реализованы.
|
||||
В словаре найдено 61 значение. По умолчанию извлекаются первые 15; `--limit 0` извлекает все. В manifest рядом с входным JSON сохраняются исходные ключи, номера строк, ID Unity-объекта и хеш исходного текста. Экстрактор только читает файлы игры и запрещает вывод внутрь её каталога. Для обратной записи интерфейса есть отдельный тестовый адаптер ниже.
|
||||
|
||||
На этой машине выполнен реальный прогон первых 15 строк через HY-MT2-7B Q4_K_M в LM Studio на RTX 3060 с окном 8192: все строки обработаны, цветовые теги сохранены. Проверены повторное использование кэша и сохранность задания после перезапуска API через батник. Проект содержит контекст игры и словарь интерфейсных терминов. Качество диалогов и поведение модели на откровенных сценах этим тестом не оценивались.
|
||||
|
||||
Здесь проверяется только `DefaultUI`: тексты сценариев и динамические реплики не охвачены. Основной контент использует Unity/Naninovel, для полноценной локализации понадобится отдельный разбор сценариев, пользовательского интерфейса и поддержки кириллицы шрифтами.
|
||||
Этот короткий пример проверяет только `DefaultUI`. Для сценариев есть отдельный массовый прогон ниже.
|
||||
|
||||
### Установка тестового перевода в игру
|
||||
|
||||
1. **`scripts/windows/tomie/prepare_tomie_translation.bat`** — извлекает весь `DefaultUI` и выбранные статические надписи на кнопках, переводит через API и собирает пакет в `data/tomie/patches/`. Если API не запущен, пытается запустить его с `config.toml`. При первом запуске создаётся `tomie.json` из `tomie.example.json`; там можно изменить путь к игре. Игровые ресурсы на этом этапе не меняются.
|
||||
2. Закройте игру и запустите **`scripts/windows/tomie/install_tomie_translation.bat`**. Он устанавливает последний успешно собранный пакет, сохраняя оригиналы внутри `.local-translator/backups/` в каталоге игры. Затем запустите игру обычным способом.
|
||||
3. Для отката закройте игру и запустите **`scripts/windows/tomie/restore_tomie_original.bat`**. Сохранения игры эти инструменты не изменяют. Резервные копии после отката остаются на диске.
|
||||
|
||||
Тестовый адаптер меняет 61 запись словаря и выбранные статические UI-надписи в обоих наборах ресурсов: prefab-бандле и `data.unity3d`. Для Text-компонентов без сохранённой схемы используется проверяемая схема Unity UI Text из бандла той же игры. Переводятся также пять названий качества графики; порядок и значения самих графических настроек сохраняются. Короткие кнопки используют сокращения и подгонку размера шрифта. Декоративный шрифт без кириллицы заменяется встроенной Candara; статические TMPro SDF-атласы не перестраиваются. Сценарии, диалоги, текст на картинках и все остальные элементы игры этим патчем не переводятся. Отдельный пункт «Русский» в списке языков не добавляется: заменяются исходные записи интерфейса, а название исходного языка English остаётся корректным.
|
||||
|
||||
Названия качества добавляются как пять недостающих managed-text записей `GameSettingsGraphicsDropdown.GraphicOption1..5`: игра подставляет эти поля при открытии настроек. Для явной офлайн-пересборки можно передать `--result <путь к translations.json>` батнику подготовки. Такой файл должен полностью соответствовать текущему набору ID и исходных строк; неполные и несовместимые результаты отклоняются, а плейсхолдеры проверяются повторно. Если меняете контекст или словарь, выполняйте обычную подготовку без `--result`, чтобы получить новый перевод.
|
||||
|
||||
Пакет содержит `translations.json`, `ui-preview.json`, `DefaultUI.ru.txt`, `manifest.json` и готовые файлы `payload/`. После упаковки они повторно читаются через UnityPy: проверяются ID всех объектов и неизменность объектов, не относящихся к патчу. Для Addressables пересчитывается CRC несжатого бандла и обновляются размер и хеш его записи в каталоге с сохранением смещений бинарных данных.
|
||||
|
||||
Установка проверяет хеши исходников, пакета и резервных копий. При другой версии игры или наличии сторонних изменений она останавливается. Игра должна быть закрыта; параллельные установки блокируются. При ошибке частичной установки выполняется откат уже заменённых файлов, а журнал позволяет повторить восстановление после прерывания. Откат не перезаписывает неизвестные сторонние изменения. Новую сборку можно подготовить при установленном старом переводе: используются проверенные резервные копии оригиналов. Перед установкой новой сборки закройте игру, восстановите оригинал и запустите установку снова.
|
||||
|
||||
Размер тестового пакета — примерно 565 MiB; для резервных и временных копий при установке дополнительно требуется место на диске игры. Поддерживается конкретная локальная Windows-сборка Tomie, универсальной установки в другие Unity-игры пока нет. Проверка отображения и удобства в самой игре выполняется пользователем: проверьте настройки, загрузку/сохранение, русские буквы и помещаются ли надписи в кнопки.
|
||||
|
||||
### Массовый перевод сценариев и динамических реплик
|
||||
|
||||
После подготовки UI-пакета запустите **`scripts/windows/tomie/translate_all_tomie.bat`**. Он извлекает все 108 скомпилированных сценариев этой сборки, реплики, варианты выбора, всплывающие сообщения, оставшийся статический UI, словари `Tips`, `CharacterNames`, `Inventory` и динамические шаблоны из `StreamingAssets/text/`. Изображения с надписями и строки, формируемые непосредственно C#-кодом, не извлекаются. Для этой сборки получено 34 274 сценарных записи и ещё 5 128 записей других ресурсов; после удаления повторов и разбиения длинных значений модели передаются 28 002 уникальных фрагмента.
|
||||
|
||||
Работа идёт последовательно через локальный API: части по **400 строк**, внутри — до **20 строк за запрос**, с ограничением **2 000 исходных символов**. Настройки в `tomie.json`: `full_output`, `full_part_size`, `full_batch_size`, `full_max_batch_chars`. Размер частей ограничен 500 строками API; длинные значения разбиваются вне защищённых тегов и выражений. Общий контекст и словарь проекта берутся из `examples/tomie_project.json`. Между HTTP-запросами история чата модели не сохраняется; контекст передаётся заново с каждой пачкой.
|
||||
|
||||
**`scripts/windows/tomie/translation_status.bat`** показывает сохранённый прогресс. Результаты находятся в `data/tomie/full-translation/`: `dataset.json`, `state.json`, `progress.json`, `parts/*.json`; дополнительный набор — в подпапке `extra/`. При фоновом запуске логи пишутся в `run.log` и `run-error.log`. SQLite сохраняет каждую переведённую строку. Повторный запуск батника продолжает те же задания и повторяет ошибки; не меняйте входные файлы и параметры посреди прогона. Для новой модели или другого контекста выберите другой `full_output`. Если API остановлен, сначала снова запустите его; уже созданное задание сохраняется в БД.
|
||||
|
||||
После успешного завершения обоих наборов автоматически собирается общий пакет, а `data/tomie/patches/latest.json` указывает на него. Неполный перевод не устанавливается. Для замены ранее установленного UI-патча сначала закройте игру и выполните **`restore_tomie_original.bat`**, затем **`install_tomie_translation.bat`** из той же папки. Сам массовый прогон файлы игры не меняет и не запускает игру.
|
||||
|
||||
Сценарии разбираются по схемам из оригинальных DLL с поддержкой старого формата Unity SerializeReference. До перевода каждый объект проходит побайтный цикл чтения/записи; при упаковке сохраняются команды, условия, ссылки, номера строк и хеши исходных реплик. Упакованные сценарии повторно сравниваются целиком с ожидаемыми объектами; неизменяемые объекты и медиаресурсы тоже проверяются. Ключи словарей, разделители подсказок и таблицы синонимов сохраняются. Временные распакованные медиаданные хранятся на диске в `data/tomie/tmp/`, чтобы не создавать несколько больших копий в RAM; для сборки желательно иметь около 15 GiB свободного места на диске проекта. Поддержка кириллицы динамических реплик и естественность случайно комбинируемых шаблонов требуют проверки в игре.
|
||||
|
||||
Командная строка для диагностики: `.venv\Scripts\python.exe -X utf8 -m local_translator.tomie --config tomie.json status`. Чтобы повторно собрать перевод после изменения контекста/словаря, запустите `scripts/windows/tomie/prepare_tomie_translation.bat`; при ошибках API исходники и ID задания сохраняются в каталоге сборки.
|
||||
|
||||
## Проверка скорости на разных моделях
|
||||
|
||||
Запустите **`scripts/windows/benchmark.bat`**. Он создаёт `benchmark.json` из `benchmark.example.json`, устанавливает недостающий UnityPy, извлекает первые 30 строк интерфейса Tomie и прогоняет их на всех скачанных **текстовых** моделях LM Studio. Embedding-модели исключаются; новые модели автоматически не скачиваются. Для другого компьютера измените путь `game` в JSON или задайте `input` с готовым массивом `{id, text}`. Нужны Python 3.11+, LM Studio и доступный `lms` CLI.
|
||||
|
||||
По умолчанию проверяются пачки **1, 5 и 10 строк**, по **3 повтора**. Перед замерами выполняется отдельный прогрев на двух строках. Порядок размеров пачек меняется между повторами, чтобы уменьшить влияние порядка. Пример сокращённого прогона:
|
||||
|
||||
```powershell
|
||||
.\scripts\windows\benchmark.bat --limit 20 --repeats 2 --batches 1 10
|
||||
```
|
||||
|
||||
Выбор конкретных скачанных моделей (ключи и точные пути смотрите в `lms ls --json`):
|
||||
|
||||
```powershell
|
||||
.\scripts\windows\benchmark.bat --models hy-mt2-7b another-model
|
||||
```
|
||||
|
||||
Либо задайте `models` в `benchmark.json`. Пустой список означает все скачанные LLM. Точный путь позволяет различать несколько квантовок одной модели. Пример одного элемента:
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "tencent/Hy-MT2-7B-GGUF/Hy-MT2-7B-Q4_K_M.gguf",
|
||||
"label": "HY-MT2 Q4",
|
||||
"prompt_style": "hy_mt",
|
||||
"temperature": 0.7,
|
||||
"top_p": 0.6,
|
||||
"top_k": 20,
|
||||
"repetition_penalty": 1.05
|
||||
}
|
||||
```
|
||||
|
||||
Для HY-MT автоматически выбирается профиль Tencent, для остальных — `generic` с температурой 0.2 и без специфичных параметров. В бенчмарке LM Studio по умолчанию включён `structured_output: true`; для отдельной модели можно явно отключить. Профиль можно переопределить в каждом элементе списка; фактические настройки сохраняются в отчёте. Сравнение разных профилей — сравнение готовых конфигураций перевода, а не строго одинаковых параметров декодирования.
|
||||
|
||||
### Что измеряется
|
||||
|
||||
Бенчмарк запускает отдельный экземпляр **нашего API** на `127.0.0.1:8788` с собственной SQLite в каталоге результата. Основной API на 8787 продолжает работать, но во время замеров не отправляйте в него задания и не запускайте другие генерации LM Studio. Одинаковые строки, контекст проекта и размер окна используются для всех моделей. Итоговые переводы каждого прогона сохранены для ручного сравнения.
|
||||
|
||||
- Кэш переводов отключён. Из тестового словаря удаляются ключи, совпадающие с целыми входными строками, чтобы все строки реально проходили через модель. Остальные термины остаются в промпте. Рабочий проект игры не меняется.
|
||||
- Время загрузки модели и прогрева записывается отдельно и **не входит** в замер перевода. Нативный prompt cache LM Studio не очищается: это измерение прогретого сервиса.
|
||||
- В полное время входят отправка задания по HTTP, ожидание очереди, генерация, проверки, SQLite и повторные попытки. Опрос готовности идёт раз в 50 мс, поэтому время включает небольшую погрешность опроса.
|
||||
- Скорость — количество успешно переведённых моделью строк или исходных Unicode-символов за полное время. Неуспешный прогон не участвует в медиане скорости успешных прогонов; число ошибок и повторов показано отдельно.
|
||||
- API сохраняет число запросов провайдеру, их суммарное время и usage-токены, если сервер их сообщает. Отсутствующие токены обозначаются `null`, а не нулём. `completion_tokens_per_request_second` делит выходные токены на **полное время запросов**, включая обработку промпта; это не чистые decode tokens/s. Токенизаторы у моделей различаются, поэтому для сравнения полезнее символы/с.
|
||||
- Синтаксические проверки не доказывают качество перевода. Отдельно перечисляются строки, полностью совпавшие с оригиналом: часть из них может быть допустимыми именами/аббревиатурами. Отказы, смягчение смысла и качество русского требуют чтения результатов.
|
||||
|
||||
Модели переключаются последовательно. Занятые модели скрипт не выгружает и останавливается при обнаружении активной генерации. Для освобождения GPU он временно выгружает **простаивающие LLM**, оставляя embedding-модели. По умолчанию модель загружается заново с заданными `context_length` и `gpu`, без интерактивных вопросов, по `modelKey` установленного CLI; после загрузки проверяется путь фактически выбранного GGUF. `reuse_loaded: true` позволяет использовать уже загруженный экземпляр с нужным окном, но его offload скрипт не проверяет. После завершения или обычного Ctrl+C скрипт старается восстановить исходные ID моделей и размеры окон. Специальные настройки движка и offload исходных экземпляров полностью не воспроизводятся. Исходный список сохранён в `original-models.json`; если восстановление не удалось, это указано в отчёте. При принудительном завершении процесса восстановление не гарантируется.
|
||||
|
||||
Не запускайте несколько бенчмарков одновременно. Для одного каталога настроек действует файловая блокировка. Таймаут задания останавливает весь прогон, чтобы не переключать модель во время незавершённого запроса.
|
||||
|
||||
### Результаты
|
||||
|
||||
Каждый запуск создаёт отдельный каталог `data/benchmarks/<время>-<id>/`:
|
||||
|
||||
- `report.html` — обзор для браузера с медианами, ошибками и повторами;
|
||||
- `report.json` — параметры, хеш набора, фактические модели/квантовки, usage и все замеры;
|
||||
- `runs.csv` — замеры для Excel/анализа;
|
||||
- `jobs/` — полные ответы API, исходники и переводы каждого прогона, включая прогрев;
|
||||
- `raw-responses/` — исходные ответы модели до разбора JSON, для диагностики ошибок формата;
|
||||
- `dataset.json` — точный использованный набор;
|
||||
- `translations.sqlite3` — сохранённые проекты, задания и метрики;
|
||||
- `active-job.json` — последнее созданное задание на случай прерывания.
|
||||
|
||||
При наличии `nvidia-smi` в отчёт также записываются название GPU, объём памяти и версия драйвера. Хеш исходников реализации позволяет отличать серии до и после изменений API. Диагностические файлы содержат переводы, а не только числовые показатели; весь каталог результатов исключён из Git.
|
||||
|
||||
Отчёт обновляется после каждого прогона. Ошибки моделей не скрываются: после обычной ошибки скрипт пытается проверить следующую модель и завершится с ненулевым кодом. Перезапуск создаёт новый замер, не переиспользует незавершённую серию. Для запуска без паузы батника:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe -X utf8 -m local_translator.benchmark --config benchmark.json
|
||||
```
|
||||
|
||||
`GET /jobs/{id}` теперь также содержит `metrics` с накопленными метриками вызовов провайдера. При `/retry` они остаются накопительными за всю жизнь задания. Метрики появляются после перезапуска обновлённого API; у старых заданий ранее выполненные вызовы восстановить нельзя.
|
||||
|
||||
## Другие провайдеры
|
||||
|
||||
@@ -183,10 +296,12 @@ Tencent не обещает отсутствие цензуры в карточ
|
||||
|
||||
```powershell
|
||||
$env:OPENROUTER_API_KEY = "ваш ключ"
|
||||
.\start.bat
|
||||
.\scripts\windows\start.bat
|
||||
```
|
||||
|
||||
Для другого сервера добавьте `[providers.my_server]` с `base_url`, `model`, при необходимости `api_key_env`. В `base_url` входит `/v1` или другой префикс, но не `/chat/completions`. После изменения TOML перезапустите приложение. Сервис поддерживает HTTP OpenAI Chat Completions, а не произвольные нативные API всех поставщиков. Структурированный ответ запрашивается промптом, без обязательного `response_format`, чтобы работать с разными серверами.
|
||||
Для другого сервера добавьте `[providers.my_server]` с `base_url`, `model`, при необходимости `api_key_env`. В `base_url` входит `/v1` или другой префикс, но не `/chat/completions`. После изменения TOML перезапустите приложение. Сервис поддерживает HTTP OpenAI Chat Completions, а не произвольные нативные API всех поставщиков.
|
||||
|
||||
Для LM Studio включён `structured_output = true`: ответ ограничен JSON Schema через `response_format`, дополнительно проверяются ID и защищённые фрагменты. Модели передаются короткие внутренние ID вместо длинных ключей игры; клиент получает свои исходные ключи. Это уменьшает объём ответа и не позволяет превратить ключи в переводимый текст. Для сервера/модели без JSON Schema оставьте `structured_output = false` (по умолчанию для произвольного провайдера): тогда формат запрашивается только промптом, с последующей проверкой. Автоматического скрытого переключения между этими режимами нет. [Structured output в LM Studio](https://lmstudio.ai/docs/developer/openai-compat/structured-output).
|
||||
|
||||
## Linux и systemd
|
||||
|
||||
@@ -218,6 +333,6 @@ journalctl -u local-translator -f
|
||||
|
||||
Проверяются обработка пачек, защита синтаксиса, пользовательские правила, словарь, кэш и ревизии, ошибки ответов модели, повтор только неудачных строк и восстановление очереди. Провайдер в тестах подменяется HTTP-имитатором. Windows-батник и работающая LM Studio проверены также в реальном прогоне. Установка systemd на Linux пока не проверялась.
|
||||
|
||||
Используйте один процесс приложения: файловая блокировка запрещает двум экземплярам обрабатывать одну БД. Сервер запускается на loopback, без отдельной авторизации и CORS. В SQLite сохраняются исходные строки, контекст и переводы. Для резервного копирования остановите сервер и скопируйте `data/` вместе с `config.toml`.
|
||||
Используйте один процесс приложения: файловая блокировка запрещает двум экземплярам обрабатывать одну БД. Сервер запускается на loopback, без отдельной авторизации. CORS выключен, пока в `config.toml` пуст `allowed_origins` (см. «Вызов из браузера»). В SQLite сохраняются исходные строки, контекст и переводы. Для резервного копирования остановите сервер и скопируйте `data/` вместе с `config.toml`.
|
||||
|
||||
Пока нет веб-интерфейса управления, потоковой выдачи токенов, отмены заданий, автоматического пополнения словаря, точного подсчёта токенов и адаптеров игровых файлов. Интерактивная `/docs` предназначена для работы с API.
|
||||
Пока нет веб-интерфейса управления, потоковой выдачи токенов, отмены заданий, автоматического пополнения словаря, точного подсчёта токенов и универсальных адаптеров игровых файлов. Адаптер Tomie поддерживает интерфейс и известные текстовые параметры скомпилированных сценариев этой сборки. Интерактивная `/docs` предназначена для работы с API.
|
||||
Reference in new issue
Block a user