Перейти к содержанию

Транскрипт в веб-интерфейсе

Веб-интерфейс Speech-Expert позволяет не только просмотреть результат STT, но и подготовить его к дальнейшей работе: назначить спикерам понятные имена, перейти к найденным сущностям, открыть эмоциональный эпизод на исходном аудио и скачать транскрипт в TXT, SRT, WebVTT или JSON. Эти действия доступны сразу после распознавания и в разделе сохранённых записей.

Порядок обработки

Перед запуском распознавания в интерфейсе можно включить LLM-коррекцию текста, извлечение именованных сущностей и для русского locale — эмоциональную динамику. LLM-этапы выполняются последовательно, а локальный emotion inference запускается параллельно ASR:

  1. распознавание речи;
  2. восстановление пунктуации, если оно используется выбранным сценарием;
  3. LLM-коррекция готового транскрипта;
  4. извлечение сущностей уже из исправленного текста;
  5. саммаризация окончательного текста, если выбран пресет.

Таким образом, текст и символьные позиции в entities[].occurrences относятся к окончательной версии реплик, которую возвращает API.

Фоновая операция: прогресс, отмена и восстановление

Веб-интерфейс отправляет распознавание через POST /api/stt/v1:recognizeAsync, а затем опрашивает операцию раз в две секунды. Во время обработки показываются короткий идентификатор, серверный статус, процент выполнения и текущий этап: очередь, загрузка или конвертация аудио, распознавание, коррекция текста, извлечение сущностей, саммаризация и финализация.

Кнопка Отменить вызывает POST /api/operations/{operation_id}:cancel и останавливает дальнейший polling. Для ElevenLabs-Scribe-v2 она намеренно недоступна: текущий бэкенд этой модели не поддерживает отмену. Если операция была запущена над уже сохранённой записью, отмена не удаляет саму запись.

Активная неприватная задача сохраняется в localStorage браузера на срок до 24 часов. Идентификатор приватной операции намеренно не записывается в localStorage, хотя ещё активную задачу интерфейс может найти через серверный список операций. При повторном открытии страницы интерфейс:

  1. проверяет сохранённый идентификатор через GET /api/operations/{id};
  2. если он недоступен, ищет незавершённые операции пользователя через GET /api/operations?done=false&kind=stt_v1_async;
  3. восстанавливает выбранные модель, язык, режим каналов, диаризацию, maxSpeakers, LLM-коррекцию, извлечение сущностей, эмоциональную динамику и её порог, пресет и подробность саммаризации, а также metadata приватности и согласия;
  4. продолжает показывать прогресс или сразу открывает готовый результат.

Полный исходный URL удалённого видео в localStorage не записывается. Доступ к самой операции всё равно проверяется сервером в рамках текущего пользователя.

Повторное распознавание сохранённой записи

В разделе Мои записи кнопка Распознать открывает запись в STT-панели. Можно выбрать другую модель и параметры и нажать Распознать снова. В этом случае интерфейс передаёт recordingId в multipart-форме асинхронного метода; аудио повторно из браузера не загружается. Метод работает только для записи, принадлежащей текущему пользователю, и после успешного выполнения обновляет её STT-результат.

Диаризация и режим каналов

Блок диаризации в Playground показывается всегда, а значение по умолчанию остаётся выключенным. Его поведение зависит от выбранного режима:

  • в mono можно включить диаризацию и задать maxSpeakers от 1 до 20;
  • в stereo переключатель неактивен с подсказкой «Для диаризации сведите каналы в mono», потому что физические каналы уже распознаются независимо;
  • для ElevenLabs-Scribe-v2 вместо переключателя отображается «Диаризация встроена», отдельная внешняя кластеризация не запускается.

stereo следует выбирать только тогда, когда разные участники действительно записаны в разные каналы. Если оба канала содержат один общий микс, сервер пока не удаляет дубли автоматически и текст может повториться — выберите mono.

Текущая граница ElevenLabs

Надпись в UI отражает способность провайдера, но текущий файловый STT v1 не гарантирует utterances с provider-native speaker labels для ElevenLabs-Scribe-v2. Не полагайтесь на это поле без проверки ответа.

Эмоциональная динамика

Переключатель доступен только при lang=ru-RU. После обработки интерфейс показывает долю времени с негативным сигналом, пик, изменение сигнала к финалу, временную шкалу и устойчивые негативные, позитивные или неопределённые эпизоды. Пять исходных вероятностей DUSHA (angry, neutral, other, positive, sad) раскрываются по запросу и не подменяются одной категоричной меткой.

Нажатие на окно или эпизод перематывает плеер к start_ms, подсвечивает пересекающуюся реплику и показывает её текст/спикера, если такая атрибуция однозначна. В сохранённой записи панель строится из response.emotion_analysis, поэтому повторно запускать модель для просмотра не нужно. Кнопки JSON и CSV в заголовке панели выгружают полный emotion-контракт или плоский список эпизодов; если устойчивых эпизодов нет, CSV содержит окна.

Это вероятностный акустический слой для навигации по записи. other означает другую или неопределённую окраску, а uncertain — производное состояние слоя эмоциональной динамики при низкой уверенности или малом отрыве классов. Результат нельзя использовать как единственное основание для оценки человека или автоматического кадрового/финансового решения. Подробные формулы и API-контракт приведены в Emotion API и STT v1.

Экспорт транскрипта

Меню Скачать находится в заголовке готового транскрипта. Файлы формируются в браузере из utterances, затем из segments, а при их отсутствии — из цельного result. Отдельные words не превращаются в субтитры автоматически. В JSON сохраняются слова с непустым текстом и полными числовыми границами start/end; неполные элементы отбрасываются.

Формат Содержимое Когда доступен
TXT Отображаемый текст; у размеченных реплик — с таймкодами и назначенными именами спикеров Для любого готового результата
SRT Нумерованные субтитры с миллисекундами и именами спикеров Когда у каждой непустой строки есть пригодные таймкоды, не помеченные synthetic или timing_estimated=true
VTT WebVTT с заголовком WEBVTT, таймкодами и именами спикеров На тех же условиях, что и SRT
JSON Версионированный документ с метаданными, репликами, словами, спикерами и сущностями Для любого готового результата

Пункты SRT и VTT отключены и показывают сообщение Нет таймкодов, если хотя бы у одной непустой строки нет начала или допустимого конца либо её тайминг явно помечен synthetic/timing_estimated=true. Отсутствие метки происхождения само по себе экспорт не блокирует. Для реплики с корректным началом, но без конца, конец берётся из начала следующей реплики или известной длительности записи.

Назначенные имена входят в TXT, SRT и VTT как подписи, например Оператор: Добрый день. В JSON они сохраняются в speakers[].name и utterances[].speaker_name.

JSON-схема экспорта

Текущая версия экспортного документа — 1.2. Основные поля:

{
  "schema_version": "1.2",
  "name": "call.wav",
  "duration_ms": 42000,
  "language": "ru-RU",
  "model": "SpeechExpert-STT",
  "channel_mode": "stereo",
  "timing_source": "riva",
  "timing_estimated": false,
  "transcript": "Добрый день\nЗдравствуйте",
  "speakers": [
    {"id": 0, "name": "Оператор"},
    {"id": 1, "name": "Клиент"}
  ],
  "utterances": [],
  "words": [],
  "entities": [],
  "entity_extraction_status": "ready",
  "summarization": {
    "schema_version": "2.0",
    "id": "4a518cc8-4f70-470d-b868-40e39420fba5",
    "created_at": "2026-07-15T12:00:00+00:00",
    "preset": "conversation",
    "detail_level": "standard",
    "output_language": null,
    "title": "Итоги звонка",
    "summary": "Клиент подтвердил следующий шаг.",
    "key_points": ["Клиент согласовал демонстрацию"],
    "sections": [],
    "decisions": ["Провести демонстрацию"],
    "action_items": ["Оператор отправит приглашение"],
    "open_questions": [],
    "risks": [],
    "structured": {
      "key_points": [],
      "sections": [],
      "decisions": [
        {
          "text": "Провести демонстрацию",
          "confirmation": "confirmed",
          "sources": [
            {
              "ref": "S0004",
              "quote": "Давайте проведём демонстрацию завтра.",
              "start_ms": 18400,
              "end_ms": 20700,
              "speaker": 1,
              "speaker_name": "Клиент",
              "utterance_index": 3,
              "timing_estimated": false
            }
          ]
        }
      ],
      "action_items": [
        {
          "text": "Оператор отправит приглашение",
          "assignee": "Оператор",
          "due_date": "сегодня",
          "sources": [
            {
              "ref": "S0005",
              "quote": "Сегодня отправлю приглашение.",
              "start_ms": 21000,
              "end_ms": 22600,
              "speaker": 0,
              "speaker_name": "Оператор",
              "utterance_index": 4,
              "timing_estimated": false
            }
          ]
        }
      ],
      "open_questions": [],
      "risks": []
    }
  },
  "summarization_status": "ready"
}

У каждой реплики сохраняются index, speaker, speaker_name, start_ms, end_ms, text, timing_source и timing_estimated. Верхнеуровневый timing_source становится mixed, если источники таймкодов у реплик различаются. Если в Playground был выбран пресет саммаризации, JSON также содержит структурированный объект summarization и статус summarization_status; без выбранного пресета оба поля равны null.

После распознавания пресет и подробность можно выбрать или сменить прямо в готовом результате Playground и в разделе Мои записи. Встроены сценарии короткого резюме, конспекта, итогов разговора, задач, тем с таймкодами, лекции, продаж/CRM, контакт-центра, интервью, исследовательского отчёта с JTBD и глав подкаста. Интерфейс вызывает только POST /api/stt/v1:summarize: аудио повторно не распознаётся.

Без отдельного выбора все заголовки, тезисы, задачи, вопросы и ответы создаются на преобладающем языке исходной речи. В Playground, готовом результате и окне дайджеста селектор Язык результата позволяет независимо выбрать русский, английский, казахский, кыргызский, узбекский или таджикский; запрос передаёт summarizationOutputLanguage при распознавании либо outputLanguage при повторной обработке. Выбранный locale распознавания остаётся подсказкой о языке источника и не меняется. Серверные цитаты, ссылки, таймкоды, имена и дословные фрагменты сохраняются в оригинале. Тот же контракт действует для собственных шаблонов, дайджестов и готовых наборов; функция Спросить архив по-прежнему отвечает на преобладающем языке выбранных записей.

В Моих записях отдельное действие Перевести транскрипт создаёт полный перевод, а не резюме. Перевод хранится как самостоятельный производный результат с версиями, показывает исходные реплики и переходит по их таймкодам, но не заменяет исходный текст. Временные границы переведённых слов не вычисляются: интерфейс использует только таймкоды исходных серверных фрагментов. API-контракт описан в разделе Перевод готового транскрипта.

Исследовательский отчёт можно применить к одной записи или к дайджесту из 2–20 интервью. Он отдельно показывает повторяющиеся темы, Jobs to be Done, возражения и выводы; каждый пункт содержит проверенные сервером ссылки на исходные реплики. В межзаписном отчёте тема считается повторяющейся только при подтверждении как минимум из двух разных интервью. Длинные дайджесты обрабатываются в два уровня: сначала сервер собирает доказательные наблюдения по каждой записи, затем сводит их в общий отчёт. Финальный этап видит только реально отобранные исходные реплики и не использует этот набор для расчёта процентов или «большинства».

Отдельные готовые наборы Voice Inbox, Учебный набор и Creator Pack создаются через POST /api/stt/v1:derive; центр поручений использует тот же метод с kind=action_center. Они хранят собственные версии, а подтверждение, исправление или отклонение задачи записывается отдельно от неизменяемого ответа модели. Кнопка Спросить архив ищет по сохранённым расшифровкам и показывает только тезисы с переходом к записи, таймкоду и подтверждающей цитате.

Для содержательной long-form записи расширенный Creator Pack возвращает тезисы, главы, кандидаты фрагментов, готовую к редактированию статью, show notes, описание видео, рассылку и публикации для social, Telegram и VK. Текст статьи сохраняет абзацы. Субтитры SRT/VTT экспортируются из исходного транскрипта, когда у него есть тайминги. Разбор, статья и публикации генерируются отдельными структурированными этапами и собираются сервером в один проверенный пакет. Короткая запись или пользовательский шаблон могут вернуть полезный частичный пакет; интерфейс помечает такой результат отдельно.

У сохранённой записи каждый успешный запуск добавляется в историю. Текущая версия остаётся в response.summarization для совместимости, а переключение версии не запускает LLM. Источники внутри structured ведут к точной реплике и позиции проигрывателя; таймкод и цитата берутся сервером из STT-ответа, а не из текста модели. Решения помечаются как подтверждённые, предложенные, отклонённые или неясные; у задач отдельно показываются явно названные исполнитель и срок. Ошибка LLM не удаляет уже имеющуюся саммаризацию.

В приватном режиме новый результат остаётся только в открытой вкладке: запись не создаётся и не обновляется, идентификатор операции не сохраняется в localStorage, а Playground очищает серверное содержимое вызовом :purge после копирования результата. Резервный deadline по умолчанию равен 15 минутам от запуска операции и проверяется сервером раз в минуту. Без отдельного согласия отключаются внешние LLM-функции и ElevenLabs. Для обычной сохранённой записи аудио можно удалить отдельно, оставив транскрипт и все производные результаты. Живые субтитры из микрофона работают через WebSocket и также не сохраняются автоматически.

В разделе Мои записи также доступны собственные шаблоны и дайджест 2–20 записей. Шаблон задаёт фокус поверх встроенного сценария и сохраняется с номером ревизии. В дайджесте источник дополнительно указывает запись, из которой взят факт. API-контракты истории, шаблонов и дайджестов описаны в разделе Саммаризация STT v1.

Экспорт выполняется на клиенте

Отдельных HTTP-методов для SRT, VTT и JSON сейчас нет. API-клиент получает структурированный STT-ответ и при необходимости формирует нужный формат самостоятельно.

Переименование спикеров

Блок Участники появляется, когда результат содержит реплики с полем speaker. Для каждого исходного идентификатора SPK N можно задать имя длиной до 80 символов. Лишние пробелы нормализуются.

  • Enter или потеря фокуса применяют имя;
  • Escape отменяет несохранённое изменение;
  • пустое значение возвращает подпись Спикер N;
  • новое имя сразу применяется ко всем репликам, экспортам и подписям спикеров у найденных сущностей.

Для сохранённой записи интерфейс записывает словарь в response.speaker_names и одновременно обновляет текстовое представление через PATCH /api/account/recordings/{recording_id}. Этот метод требует сессию портала. У ещё не сохранённого результата имя меняется локально; интерфейс предупреждает, что для постоянного хранения нужно сохранить запись.

При самостоятельном PATCH передавайте полный пользовательский вариант response, а не только speaker_names. Сервер при этом сохраняет принадлежащие ему поля активной саммаризации (summarization и summarization_status): их нельзя подменить или стереть общим методом обновления записи. Специального публичного STT-метода для переименования спикера сейчас нет.

Именованные сущности

В веб-интерфейсе извлечение сущностей включено по умолчанию. Для прямого вызова STT v1 оно включается явно параметром entityExtraction=true; значение можно передать в query string или multipart-форме:

curl -X POST \
  "https://api.speech.example.com/api/stt/v1:recognizeAsync?llmCorrection=true&entityExtraction=true" \
  -H "Authorization: Api-Key $API_KEY" \
  -F "audio=@call.wav"

Параметр поддерживается синхронным POST /api/stt/v1 и асинхронным POST /api/stt/v1:recognizeAsync. По умолчанию в API он равен false.

Сервис извлекает только явно присутствующие в тексте упоминания следующих типов:

person, organization, location, product, event, date, time, money, phone, email, url, address, document, identifier.

Ответ содержит сгруппированные сущности и каждое точное упоминание:

{
  "entities": [
    {
      "id": "organization:1",
      "type": "organization",
      "text": "Acme",
      "normalized": "ACME",
      "occurrences": [
        {
          "utterance_index": 0,
          "mention": "Acme",
          "char_start": 0,
          "char_end": 4,
          "speaker": 0,
          "start_ms": 1200,
          "end_ms": 3100
        }
      ]
    }
  ],
  "entity_extraction_status": "ready"
}

char_start включителен, char_end исключителен. Это позиции Unicode-символов в тексте соответствующей реплики. Сервер вычисляет их только для точного вхождения mention и отклоняет упоминания, которых нет в исходном сегменте. Варианты написания с одинаковыми типом и нормализованным значением объединяются в одну сущность.

Статусы извлечения

Статус Значение
ready Все части транскрипта обработаны и прошли проверку
partial Сохранены валидные сущности, но часть ответа, чанков или упоминаний была отклонена либо достигнут лимит
failed Извлечение не удалось; сам транскрипт всё равно возвращён
unavailable Не настроен API-ключ LLM; транскрипт возвращён без сущностей

Извлечение работает по принципу fail-open: ошибка внешней LLM не превращает успешное распознавание в ошибку STT.

Переход к упоминанию

Панель Сущности и реквизиты группирует результаты по типам. Кнопка упоминания показывает таймкод, номер реплики или подпись текст — в зависимости от доступных данных. При нажатии интерфейс:

  1. плавно прокручивает транскрипт к нужной реплике;
  2. выделяет точный диапазон char_start:char_end;
  3. переводит позицию аудио- или видеоплеера на start_ms, если он известен.

Позиции обрабатываются как Unicode code points, поэтому выделение остаётся корректным и при наличии emoji перед сущностью. Переход меняет позицию проигрывателя, но не запускает воспроизведение автоматически.

Настройка модели NER

Извлечение использует OpenAI-совместимый POST /chat/completions. Если отдельные настройки NER пусты, сервис наследует настройки LLM-коррекции:

Переменная Назначение Значение по умолчанию / fallback
LLM_ENTITY_EXTRACTION_API_KEY Отдельный ключ NER LLM_CORRECTION_API_KEY, затем VSEGPT_API_KEY
LLM_ENTITY_EXTRACTION_BASE_URL URL OpenAI-совместимого API LLM_CORRECTION_BASE_URL
LLM_ENTITY_EXTRACTION_MODEL Модель NER LLM_CORRECTION_MODEL
LLM_ENTITY_EXTRACTION_TIMEOUT Таймаут одного запроса, секунды 60
LLM_ENTITY_EXTRACTION_MAX_TOKENS Максимум токенов ответа 8000
LLM_ENTITY_EXTRACTION_TEMPERATURE Температура 0
LLM_ENTITY_EXTRACTION_CHUNK_CHARS Размер чанка транскрипта 12000
LLM_ENTITY_EXTRACTION_MAX_ENTITIES Максимум валидных упоминаний 500

В .env.example и Docker Compose отдельная модель NER не задана, поэтому при настройках по умолчанию используется google/gemini-3.5-flash-minimal из LLM_CORRECTION_MODEL. После изменения модели, URL или лимитов перезапустите API, чтобы сервис перечитал настройки.

Границы текущей реализации

  • entityExtraction реализован для синхронного и асинхронного STT v1; в потоковом WebSocket/gRPC и STT v3 этот флаг сейчас не используется.
  • переименование и скачивание файлов — функции веб-интерфейса, а не новые форматы ответа публичного STT API;
  • SRT и VTT недоступны для таймкодов, явно помеченных синтетическими или оценочными, а также для строк без начала или выводимого конца;
  • имена спикеров постоянно хранятся только вместе с записью портала.

Схемы исходного STT-ответа и таймстемпов описаны в разделе Синхронное распознавание, а получение фоновой операции — в Операциях. Полный CRUD сохранённых файлов и транскриптов описан в API записей.