хранилище переехало с PocketBase на SQLite со своим каталогом файлов
- база своя: два пула, захват одним UPDATE ... RETURNING, шаги схемы на goose под файловым замком, одна миграция начальной схемы вместо семи прежних - транспорт переписан на net/http: свои слои, свой ограничитель частоты, отдача файла с проверкой владельца; панель /_/ и пространство /api/ исчезли - по находкам ревью: журнал не пишет путь под корнем приложения, ключ бюджета читается справа налево, узнавание известного идёт читающим пулом
This commit is contained in:
+71
-59
@@ -21,18 +21,20 @@
|
||||
`http-handler-tests-never-green` и `no-user-filename-in-log` 2026-08-11,
|
||||
`pocketbase-storage` и `oidc-login` 2026-08-12,
|
||||
`local-run-without-telegram-token` 2026-08-13, `remove-telegram-intake`
|
||||
2026-08-14;
|
||||
2026-08-14, `storage-without-pocketbase` 2026-08-22;
|
||||
- [pipeline](../openspec/specs/pipeline/spec.md) — пустой прогон воркера, захват
|
||||
задачи и срок его протухания, число попыток, остановка признаком, пауза перед
|
||||
повтором и молчание конвейера наружу: задачи
|
||||
`errors-as-instead-of-typecast` 2026-08-11, `pocketbase-storage` 2026-08-12,
|
||||
`local-run-without-telegram-token` 2026-08-13 и `remove-telegram-intake`
|
||||
2026-08-14. Переходы состояний и отмена
|
||||
`local-run-without-telegram-token` 2026-08-13, `remove-telegram-intake`
|
||||
2026-08-14 и `storage-without-pocketbase` 2026-08-22. Переходы состояний и отмена
|
||||
контекста посреди шага остаются
|
||||
долгом; что именно не описано, перечисляет раздел `Purpose` самой спеки;
|
||||
- [storage](../openspec/specs/storage/spec.md) — где живут запись, её метаданные
|
||||
и её файл, как файл отдаётся и что видит владелец: задача `pocketbase-storage`
|
||||
2026-08-12;
|
||||
и её файл, как файл отдаётся и что видит владелец: задачи `pocketbase-storage`
|
||||
2026-08-12 и `storage-without-pocketbase` 2026-08-22. Последняя убрала
|
||||
встроенное хранилище целиком: база стала своей, файлы — своим каталогом,
|
||||
панель владельца исчезла и не заменена ничем;
|
||||
- [recognition](../openspec/specs/recognition/spec.md) — **попытка распознавания
|
||||
у внешнего провайдера**: что о ней хранится, почему сырой ответ сохраняется
|
||||
целиком и вложением, как из сохранённого строится структура реплик без
|
||||
@@ -66,13 +68,15 @@
|
||||
|
||||
- **Один процесс.** HTTP-сервер и фоновые воркеры живут в одном бинарнике и
|
||||
делят одну базу. Отдельного воркер-процесса нет намеренно.
|
||||
- **Очередь таблицей.** Состояние задачи лежит коллекцией хранилища; неделимость
|
||||
- **Очередь таблицей.** Состояние задачи лежит таблицей базы; неделимость
|
||||
захвата и порядок выборки нормирует
|
||||
[pipeline](../openspec/specs/pipeline/spec.md), «Захват задачи неделим».
|
||||
Внешний брокер не заводим: нагрузка — единицы записей в день (оценка владельца,
|
||||
не замер). Готовую библиотеку очереди тоже не заводим — решено 2026-08-11,
|
||||
[ADR](adr/ADR-2026-08-11-queue-as-pocketbase-collection.md), сравнение
|
||||
кандидатов в [research/job-queue.md](research/job-queue.md).
|
||||
кандидатов в [research/job-queue.md](research/job-queue.md). Решение пережило
|
||||
уход встроенного хранилища: замер снят на том же драйвере, и отменилось у него
|
||||
одно слово — таблица перестала быть коллекцией.
|
||||
- **Шаг конвейера идемпотентен по повтору.** Что делает срок захвата и когда
|
||||
задача возвращается в работу, нормирует
|
||||
[pipeline](../openspec/specs/pipeline/spec.md), «Брошенная задача возвращается
|
||||
@@ -83,11 +87,10 @@
|
||||
`cmd/transcriber`. Слои, их дома и словарь модели — раздел «Слои и модель
|
||||
домена» ниже. Правило механизировано тестами-сканерами `internal/archrules`, и
|
||||
они же держат обратные направления: транспорты не знают друг о друге, адаптер
|
||||
не знает ни ядра, ни транспортов.
|
||||
*Изъятие:* транспорт **вправе** знать адаптер хранилища — `controller/http`
|
||||
импортирует `adapter/repo/pocketbase`, потому что HTTP-поверхность и есть
|
||||
роутер этого хранилища, а не наш сервер поверх него. Правила на это
|
||||
направление нет намеренно.
|
||||
не знает ни ядра, ни транспортов, транспорт не знает адаптеров. Изъятие,
|
||||
разрешавшее транспорту знать адаптер хранилища, снято 2026-08-22 вместе с
|
||||
предметом: HTTP-поверхность была роутером встроенного хранилища, а стала своей,
|
||||
и правило на это направление заведено впервые.
|
||||
|
||||
## Слои и модель домена
|
||||
|
||||
@@ -133,10 +136,10 @@
|
||||
наблюдаем: правило о записи, записанное в `internal/service` условием над её
|
||||
полями, принадлежит `internal/entity`.
|
||||
|
||||
Место, где подход нарушен сегодня, названо изъятием в «Принципах»: транспорт
|
||||
знает адаптер хранилища, потому что HTTP-поверхность и есть роутер этого
|
||||
хранилища. Изъятие снимает задача `storage-without-pocketbase` — своя отдача
|
||||
файла и свои маршруты возвращают транспорту независимость от инфраструктуры.
|
||||
Изъятий у подхода сегодня нет: последнее — транспорт знал адаптер хранилища —
|
||||
снято задачей `storage-without-pocketbase` 2026-08-22. Своя отдача файла и свои
|
||||
маршруты вернули транспорту независимость от инфраструктуры, а узнавание
|
||||
пришедшего приходит ему интерфейсом `contract.UserRepository`.
|
||||
|
||||
## Компоненты
|
||||
|
||||
@@ -146,14 +149,15 @@
|
||||
|
||||
| Компонент | Где | Что делает |
|
||||
| --- | --- | --- |
|
||||
| HTTP API | `internal/controller/http` | Адреса приложения под корнем `/app/`: приём записи, страница своих записей, карточка, текст названного вида, пределы сервера и «кто вошёл» |
|
||||
| HTTP API | `internal/controller/http` | Адреса приложения под корнем `/app/` на `net/http`: приём записи, страница своих записей, карточка, текст названного вида, файл записи, пределы сервера и «кто вошёл». Слои — свои: журнал, восстановление после паники, ограничитель частоты, узнавание, требование учётной записи |
|
||||
| Воркеры | `internal/controller/worker` | Пул одинаковых потоков: каждый берёт любую пригодную запись и опрашивает базу. Число — настройкой, ноль законен |
|
||||
| Сервис расшифровки | `internal/service` | Конвейер: приём, приведение, отправка, опрос, завершение. Шаг выбирается по рубежу записи |
|
||||
| Конвертер и метаданные | `internal/adapter/{converter,metaviewer}/ffmpeg` | `ffmpeg` в ogg/vorbis, `ffprobe` для длительности |
|
||||
| Распознаватель | `internal/adapter/recognizer/yandex` | Заливка в Object Storage и отложенное распознавание SpeechKit; разбор ответа в реплики со временем |
|
||||
| Репозитории | `internal/adapter/repo/pocketbase` | Записи, файлы, тексты, структура, попытки распознавания и журнал событий — коллекциями хранилища; захват — сырым запросом |
|
||||
| Шаги схемы | `internal/adapter/repo/pocketbase/migrations` | Файл на шаг, имя файла — имя шага; там же имена коллекций |
|
||||
| Панель владельца | `internal/adapter/repo/pocketbase`, `panel.go` | Панель хранилища; правила правки записи нормирует [storage](../openspec/specs/storage/spec.md), «Владелец видит записи в панели» |
|
||||
| Репозитории | `internal/adapter/repo/sqlite` | Учётные записи, записи, файлы, тексты, структура, попытки распознавания и журнал событий — таблицами базы; захват — одним запросом с `RETURNING` по пишущему соединению |
|
||||
| Файлы записей | `internal/adapter/repo/sqlite`, `store.go` | Подкаталог на запись под её идентификатором; укладка атомарна — временное имя рядом и переименование |
|
||||
| Шаги схемы | `internal/adapter/repo/sqlite/migrations` | Файл на шаг, версия — число в начале имени; накатывает `pressly/goose/v3` под своим замком |
|
||||
| Оснастка владельца | `cmd/devtools` | Подставной прокси для местного запуска и возврат остановленной записи в работу. Панели у сервиса нет и не будет: экраны правки приносят отдельные задачи |
|
||||
| Приложение | `web/` | Vue 3, роутер пятой версии, сборка Vite. Собранное лежит в `web/embed/dist` и вшивается в бинарник; в git его нет |
|
||||
| Раздача приложения | `internal/controller/http`, `webapp.go` | Корневой маршрут: разметка вне корней сервиса, отказ внутри, срок хранения по каталогу сборщика |
|
||||
|
||||
@@ -174,6 +178,12 @@
|
||||
асинхронное: запрос возвращает идентификатор операции, готовность опрашивается
|
||||
через `operation.api.cloud.yandex.net:443`, текст читается потоком.
|
||||
- **ffmpeg и ffprobe.** Внешние процессы, ищутся в `PATH`.
|
||||
- **SQLite через `modernc.org/sqlite`.** Драйвер на чистом Go: CGO сборке не
|
||||
нужен. База и файлы записей лежат под одним каталогом данных.
|
||||
- **`github.com/pressly/goose/v3`.** Шаги схемы — библиотекой, а не командной
|
||||
строкой: перечень шагов приходит провайдеру доводом, накат идёт при старте.
|
||||
Исключающей блокировки под SQLite библиотека не даёт, и замок каталога данных
|
||||
берём сами.
|
||||
- **Node и его установщик пакетов.** Нужны только сборке приложения и на машину
|
||||
не ставятся: шаг зовёт их контейнером, а образ берёт из ступени `Dockerfile`.
|
||||
Требованием к машине разработчика поэтому становится docker. Реестр пакетов —
|
||||
@@ -191,34 +201,23 @@
|
||||
Секцию `[telegram]` и ключ `server.users_while_list` человек убирает из боевого
|
||||
файла после выкладки: незнакомые ключи разбор настроек не судит, и файл с ними
|
||||
сервис поднимает молча.
|
||||
- **Откат образа через шаг схемы `202608140002` не работает и не говорит об
|
||||
этом.** Шаг удаляет прежнюю коллекцию задач, а библиотека накатывает только
|
||||
те шаги, которые знает сам бинарь: прежний образ шагов новее не видит,
|
||||
поднимается **без единой ошибки** и отвечает зелёной пробой здоровья — после
|
||||
чего всякое обращение к очереди отказывает «коллекции нет». Проверено прогоном
|
||||
двух бинарей на одном каталоге данных.
|
||||
- **Откат образа на версию до 2026-08-22 не работает вовсе.** Каталог данных
|
||||
сменил раскладку целиком: база зовётся другим файлом, файлы записей лежат
|
||||
другими путями, а учёт применённых шагов ведёт другая таблица. Прежний образ на
|
||||
таком каталоге поднимется, накатит **свои** шаги в пустое место и заведёт
|
||||
вторую, чужую схему рядом. Лечится повторной выкладкой вперёд; обратного шага
|
||||
схемы нет и не планируется.
|
||||
|
||||
Значит штатное средство владельца на инциденте — «вернём прошлый образ» — с
|
||||
этого шага делает хуже и молчит. Лечится повторной выкладкой нового образа;
|
||||
обратного шага схемы нет и не планируется. Порог перехода назван прямо: до
|
||||
выкладки `record-centric-model` откат образа работает, после — нет.
|
||||
- **Откат образа через шаг схемы `202608220001` обрывает вход.** Шаг закрывает
|
||||
правила коллекции пользователей наглухо, а прежний образ заводил учётную
|
||||
запись внутренним запросом обмена кода — и этот запрос закрытое правило
|
||||
отвергает. Проверено прогоном прежнего кода поверх нового каталога данных:
|
||||
вход отвечает `401`, в журнале «storage rejected the exchange with code 403».
|
||||
Порог тот же по форме, что и у `202608140002`: до выкладки
|
||||
`trusted-header-login` откат работает, после — нет, и лечится он повторной
|
||||
выкладкой вперёд. Обратного шага схемы нет и не планируется.
|
||||
|
||||
Окно этого порога сегодня пусто: сервис не выложен, а откат уже не работает с
|
||||
шага `202608140002`. Строка стоит здесь потому, что порог принято называть
|
||||
прямо, а не потому, что риск сегодня чем-то грозит.
|
||||
Прежние два порога — шаги `202608140002` и `202608220001` — этим поглощены: до
|
||||
выкладки `record-centric-model` откат работал, после перестал, а с уходом
|
||||
встроенного хранилища перестал окончательно. Окно порога сегодня пусто: сервис
|
||||
не выложен. Строка стоит здесь потому, что порог принято называть прямо, а не
|
||||
потому, что риск сегодня чем-то грозит.
|
||||
- **Внешние зависимости поимённо и чем каждая отказывает.** Столбец «отвечает
|
||||
медленно» читается вместе с тем, что таймаута нет ни у одного обращения
|
||||
наружу — [database.md](database.md), «Настройки с числовым значением»:
|
||||
|
||||
<!-- канон: поведение → openspec/specs/intake, pipeline -->
|
||||
<!-- канон: поведение → openspec/specs/intake, pipeline, storage -->
|
||||
|
||||
| Зависимость | Падает | Отвечает медленно | Молчит | Отдаёт мусор |
|
||||
| --- | --- | --- | --- | --- |
|
||||
@@ -226,7 +225,7 @@
|
||||
| ↳ *остановка сервиса* | Принятие операции от отмены защищено своим пределом в 10 секунд: операцию там могли принять и начать считать деньги, а потерянный идентификатор заставил бы повтор оплатить ту же запись второй раз. Заливка в Object Storage отменяется штатно — её повтор бесплатен, объект ложится под тем же ключом | — | — | — |
|
||||
| Yandex Object Storage | Заливка падает, запись остаётся на рубеже `normalized` | То же, что падение: висит до конца захвата | — | SpeechKit не прочитает объект и вернёт отказ операции |
|
||||
| ffmpeg, ffprobe | Запись останавливается признаком с текстом «сбой конвертации файла» — рубеж при этом сохраняется, и снятие признака продолжает с него. Остановка сервиса — исход другой: процесс убивают контекстом, запись остаётся на повтор и отказа не тратит | Конвейер стоит: вызов синхронный | — | Выходной файл пуст, отказ вылезет на распознавании |
|
||||
| Хранилище (файл на диске) | Приложение не стартует либо шаг падает на каждом запросе | Блокировка записи держит воркеры | — | — |
|
||||
| База (файл на диске) | Старт кончается отказом с именем шага схемы либо шаг падает на каждом запросе | Ожидание занятой базы задано числом; исчерпав его, операция отказывает, и запись остаётся пригодной к повтору | — | — |
|
||||
| Диск | Запись файла падает, задача не заводится | — | — | — |
|
||||
|
||||
- **Кто заметит отказ и когда:** тот, кто загрузил запись, — карточкой записи:
|
||||
@@ -237,8 +236,8 @@
|
||||
«падает приведение» отличается от «падает распознавание». Плюс логи
|
||||
контейнера. Отдельного оповещения нет.
|
||||
- **Журнал событий записи** — второй канал наблюдения, `record_events`. Пишется
|
||||
на смену рубежа, на остановку и на снятие остановки; читает его человек в
|
||||
панели, ни один шаг конвейера на него не смотрит. Экрана у него пока нет.
|
||||
на смену рубежа, на остановку и на возврат в работу; ни один шаг конвейера на
|
||||
него не смотрит. Читается запросом к базе: ни панели, ни экрана у него нет.
|
||||
- **Характер потока:** непрерывный, но разреженный. Воркеры опрашивают базу
|
||||
вхолостую с паузой из
|
||||
[database.md](database.md), «Настройки с числовым значением».
|
||||
@@ -248,7 +247,11 @@
|
||||
| Что | Где |
|
||||
| --- | --- |
|
||||
| Приём аудио и заведение записи | `TranscribeService.createRecord` — единственный путь, которым запись появляется в хранилище |
|
||||
| Правка записи владельцем | панель хранилища; правка запросом проходит правила перехода (`pocketbase.BindPanelRules`), а шаг конвейера пишет только свои поля и правку владельца не стирает |
|
||||
| Возврат остановленной записи в работу | `cmd/devtools resume` — зовёт домен и пишет событие журнала записи с происхождением «человек»; колонок сама не пишет |
|
||||
| Выдача идентификатора строки | `internal/ident` — ULID в нижнем регистре, монотонный внутри миллисекунды; разбор пришедшего снаружи — там же |
|
||||
| Подключение к базе | `internal/adapter/repo/sqlite.Open` — пишущее соединение одно, чтение своим пулом, настройки строкой подключения обоих |
|
||||
| Накат схемы | `internal/adapter/repo/sqlite.Migrate` — до подъёма входов и до старта воркеров, под замком каталога данных |
|
||||
| Раскладка файлов записи | `internal/adapter/repo/sqlite.Store` — подкаталог на запись; путь на диске за её пределы не выходит |
|
||||
| Захват записи воркером | `AudioRecordRepository.FindAndAcquire` — один запрос с `RETURNING`, отдаёт идентификатор и признак захвата |
|
||||
| Объявление рубежа | `internal/entity/stage.go` — выбор шага, отбор захвата, срок протухания и предел простоя выводятся отсюда |
|
||||
| Выбор шага по рубежу | `TranscribeService.stepFor` — таблица, а не привязка к воркеру |
|
||||
@@ -264,13 +267,17 @@
|
||||
| Состояния отбора списка | `internal/entity.ListFilter` вместе с `WorkingStages` и `TerminalStages` — предикаты выводятся из дескриптора рубежа, а не пишутся строкой запроса |
|
||||
| Уборка имени файла отправителя | `internal/entity.SanitizeOriginalFilename` — режет по пределу и убирает управляющие знаки; зовёт её приём |
|
||||
| Адресное пространство сервиса | `internal/controller/http.ServiceMounts` — перечень корней и адресов наблюдения. Он **порождает** регистрацию наших маршрутов, а не описывает её, и из него же выводятся правило неизвестного пути, уровень журнала и область действия узнавания |
|
||||
| Узнавание предъявителя | `pbrepo.EnsureUser` — поиск учётной записи по логину у провайдера и заведение при первом обращении. Дом правила один и лежит в хранилище, а не в транспорте: второй способ представиться (личные токены) возьмёт этот же метод, а уложенное куском в слой оно разошлось бы двумя копиями. Транспорт читает заголовок, судит адрес пира и зовёт метод — `internal/controller/http.TrustedHeaderIdentity` |
|
||||
| Узнавание предъявителя | `sqlite.UserRepository.EnsureUser` — поиск учётной записи по логину у провайдера и заведение при первом обращении. Дом правила один и лежит в хранилище, а не в транспорте: второй способ представиться (личные токены) возьмёт этот же метод, а уложенное куском в слой оно разошлось бы двумя копиями. Транспорт читает заголовок, судит адрес пира и зовёт метод интерфейсом `contract.UserRepository` — `internal/controller/http.TrustedHeaderIdentity` |
|
||||
| Приём значения заголовка | `internal/entity.AcceptProviderLogin`, `AcceptDisplayName`, `AcceptEmail` — правило одно на все способы представиться |
|
||||
| Ограничитель частоты | `internal/controller/http.RateLimit` — бюджет по адресу спрашивающего под корнем приложения; из его чисел выводится объявляемая частота опроса |
|
||||
|
||||
Единых точек, которых **нет** и которые ожидались бы: идентификаторы
|
||||
генерируются вызовом `uuid.NewString()` по месту. Время из этого перечня ушло
|
||||
2026-08-13: его читает `internal/clock`, и запрет держит линтер; отображение
|
||||
доменной ошибки — 2026-08-15 задачей `json-api-for-spa`, и до неё обработчик
|
||||
решал сам: опрос отвечал `404` на упавшую базу, а приём — `500` на негодный файл.
|
||||
Единых точек, которых **нет** и которые ожидались бы, сегодня не осталось.
|
||||
Время ушло из перечня отсутствий 2026-08-13 — его читает `internal/clock`, и
|
||||
запрет держит линтер; отображение доменной ошибки — 2026-08-15 задачей
|
||||
`json-api-for-spa`, и до неё обработчик решал сам: опрос отвечал `404` на упавшую
|
||||
базу, а приём — `500` на негодный файл; выдача идентификаторов — 2026-08-22
|
||||
задачей `storage-without-pocketbase`, и до неё их выдавало встроенное хранилище
|
||||
своим алфавитом, а сервис звал `uuid.NewString()` по месту.
|
||||
|
||||
## Деплой
|
||||
|
||||
@@ -320,8 +327,9 @@ DNS-сервер, молчащий на `AAAA`, оставляет устано
|
||||
[ADR-2026-08-22-login-by-trusted-header](adr/ADR-2026-08-22-login-by-trusted-header.md).
|
||||
**Не решено одно:** как связать чат Telegram с учётной записью — от этого
|
||||
зависит возвращение убранного входа.
|
||||
Панель администратора при этом Authelia не закрывает: у неё свой пароль
|
||||
суперпользователя.
|
||||
Второго периметра на порту сервиса при этом не осталось: панель администратора
|
||||
ушла вместе со встроенным хранилищем 2026-08-22, и закрывать её на прокси
|
||||
больше нечего.
|
||||
- **Приложение.** Каркас поставлен `spa-skeleton` 2026-08-15: приложение
|
||||
открывается, показывает вошедшего и вшито в бинарник. Экранов загрузки и
|
||||
списка нет — их делают `upload-and-status-screen` и `records-list-screen`.
|
||||
@@ -343,7 +351,8 @@ DNS-сервер, молчащий на `AAAA`, оставляет устано
|
||||
шесть часов нормирует [storage](../openspec/specs/storage/spec.md), «Файл
|
||||
записи живёт в хранилище»; откуда взято число —
|
||||
[research/pocketbase-defaults.md](research/pocketbase-defaults.md), «Чего эта
|
||||
записка не узнала».
|
||||
записка не узнала». Записка описывает умолчания ушедшей библиотеки, и живой
|
||||
она осталась только этим числом.
|
||||
- **Приём большого файла.** Форма читается целиком, предел памяти под multipart
|
||||
задан числом в [database.md](database.md), «Настройки с числовым значением»;
|
||||
обрыв начинает загрузку заново.
|
||||
@@ -358,8 +367,8 @@ DNS-сервер, молчащий на `AAAA`, оставляет устано
|
||||
- **Резервные копии.** Копии делает сервер своими средствами, и приложение о них
|
||||
ничего не знает. Не решено, хватит ли копировать каталог данных файлами, или
|
||||
приложению нужна команда выгрузки: база под нагрузкой копируется файлом не
|
||||
всегда целой. Своё копирование по расписанию у PocketBase есть — берём мы его
|
||||
или нет, тоже не решено.
|
||||
всегда целой. Готового копирования по расписанию у сервиса нет вовсе: оно
|
||||
ушло вместе со встроенным хранилищем, и заводить своё пока не решено.
|
||||
- **Формат для распознавания.** Конвертер отдаёт ogg/vorbis (`libvorbis`), а
|
||||
SpeechKit получает `ContainerAudio_OGG_OPUS`. Расхождение не разобрано: то ли
|
||||
сервис определяет содержимое сам, то ли часть записей теряется на этом.
|
||||
@@ -374,12 +383,15 @@ DNS-сервер, молчащий на `AAAA`, оставляет устано
|
||||
паузы, а не замер
|
||||
([research/job-queue.md](research/job-queue.md), «Как снималось»), — при
|
||||
нагрузке в единицы записей в день, и во что это обходится, никто не мерил.
|
||||
Хранилище при этом сменилось задачей `storage-without-pocketbase` 2026-08-22
|
||||
([ADR](adr/ADR-2026-08-22-storage-without-pocketbase.md)), а модель очереди
|
||||
пережила смену: отменилось одно слово — таблица перестала быть коллекцией.
|
||||
- **Наблюдаемость.** `/metrics` остаётся и развивается. Чем — дописывать
|
||||
счётчики через `client_golang` или перейти на OpenTelemetry с трассировкой —
|
||||
решает разведка `opentelemetry-fit`. Коллектор был бы процессом, которого в
|
||||
выкладке сегодня нет.
|
||||
- **Выводы из текста.** Литературный текст, заголовок, темы и пересказ решено
|
||||
считать внешним сервисом с OpenAI-совместимым интерфейсом за шлюзом bifrost.
|
||||
Появляется пятая внешняя зависимость, платная, и текст расшифровки начинает
|
||||
Появляется ещё одна внешняя зависимость, платная, и текст расшифровки начинает
|
||||
уходить ещё на одну сторону — сдвиг периметра [security.md](security.md). Не
|
||||
решено, отдельный это шаг конвейера или продолжение шага распознавания.
|
||||
|
||||
Reference in New Issue
Block a user