Ранее я уже рассказывал, что при разработке api2app вдохновлялся идеей Python-библиотеки Gradio. У этой библиотеки есть очень полезный функционал: можно запустить приложение на локальном компьютере в режиме share (поделиться), тогда будет сгенерирована публичная ссылка, по которой это приложение будет доступно на любом другом компьютере через Интернет.
В этой статье пошагово опишу свой способ. Есть свои плюсы и свои минусы, но в любом случае, думаю, альтернатива не будет лишней. Для примера создам онлайн-приложение для создания транскрипций аудио с помощью проекта whisper.cpp. Делаю всё на Ubuntu, но, думаю, на Windows это повторить будет не сложно.
Установка whisper.cpp
Клонирую репозиторий и перехожу в папку:
git clone https://github.com/ggerganov/whisper.cpp.git
cd whisper.cpp
Делаю сборку:
cmake -B build
cmake --build build --config Release
Скачиваю модель medium (~1.5 GB):
bash ./models/download-ggml-model.sh medium
Если скачивание не сработало, можно попробовать в bash-скрипте заменить домен "huggingface.co" на "hf-mirror.com".
Проверяю работу:
./build/bin/whisper-cli --model ./models/ggml-medium.bin \
--language en \
--output-txt \
--file samples/jfk.wav
В папке "samples" должен появиться файл "jfk.wav.txt" с транскрипцией.
Создание обработчика запросов
Сначала нужно создать задачу в Менеджере задач. Открываю приложение: https://api2app.org/ru/apps/shared/666e778a-f6a5-11ef-861d-525400f8f94f

Заполняю поля, нажимаю кнопку "Создать задачу" - получаю идентификатор задачи и API-ключ.
Данное приложение использует API, исходный код которого можно найти здесь:
https://github.com/andchir/queue-manager
Клонирую репозиторий и перехожу в папку:
git clone https://github.com/andchir/queue-manager.git
cd queue-manager
Создаю виртуальное окружение Python и активирую его:
python3.12 -m venv venv
source venv/bin/activate
Локально мне не нужно устанавливать полностью это приложение, поэтому я устанавливаю только минимальное число зависимостей:
pip install -r requirements-min.txt
Создаю .env файл, в котором будут мои локальные настройки:
cp .env-dest .env
В этом файле мне нужно изменить домен API менеджера очередей:
APP_SERVER_NAME=queue.api2app.org
Клонирую дополнительный репозиторий с обработчиками запросов. Я их назвал воркерами (workers):
git clone https://github.com/andchir/api2app-workers.git
Тут уже есть готовый воркер для whisper.cpp, но я опишу немного подробнее как я его создал. Создаю новый файл "whisper-cpp.py" в папке "api2app-workers" пока с таким содержанием:
import os
import sys
import subprocess
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from utils.queue_manager import polling_queue, upload_queue_files, send_queue_error
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def processing(queue_item):
upload_dir_path = os.path.join(ROOT_DIR, 'uploads', 'whisper')
# Загрузка всех файлов в локальную папку
image_file_path, audio_file_path, video_file_path, image_file_path2 = upload_queue_files(queue_item, upload_dir_path)
print('Получена новая задача', queue_item['uuid'])
print('Аудио файл:', audio_file_path)
# Отправка ошибки выполнения
send_queue_error(queue_item['uuid'], 'Error.')
if __name__ == '__main__':
# Опрос на появление новых задач
polling_queue('ea474c9a-c631-4bac-9141-ba26b0ff56c5', processing)
Я думаю из этого кода вы поняли, что функция polling_queue делает периодические запросы к серверу, используя ID задачи, который я получил ранее. Если задача пришла, запускается функция processing.
Теперь мне нужно протестировать работу этого воркера. Я отправлю запрос для создания новой задачи. Нужно отправить аудио-файл, используя ID задачи и API ключ, которые я получил ранее на сайте api2app.org. Позже я создам графический интерфейс для этого, но пока могу отправить запрос с помощью curl:
curl -X POST \
-F "audio_file=@/path/to/audio.mp3" \
-F 'data={"language": "en", "output_type": "txt"}' \
-H "Accept: application/json" \
-H "API-KEY: 47688355-a431-4bc3-8913-35115b2da36c" \
"https://queue.api2app.org/queue/ea474c9a-c631-4bac-9141-ba26b0ff56c5"
Запущу воркер:
python api2app-workers/whisper-cpp.py
Если всё в порядке, должен быть примерно такой вывод:
Waiting for a task...
Получена новая задача 1ab642f7-0c2c-4ff1-868a-a5d38c57d480
Аудио файл:
/path/to/queue-manager/uploads/whisper/962ff1aa-f6ec-11ef-be86-33f5f54d4fd6.mp3
Полный код воркера с комментариями можно посмотреть здесь:
https://github.com/andchir/api2app-workers/blob/main/whisper-cpp.py
Этот воркер запускает "whisper-cli" из проекта whisper.cpp, который в свою очередь создает транскрипцию аудио-файла. Далее с помощью функции send_queue_result_dict
результат отправляется менеджеру задач.
Скрипт для управления воркерами (только Linux)
Для более удобного управления воркерами есть bash-скрипт "worker-manager.sh". Для его использования нужно установить права на запуск:
chmod +x api2app-workers/worker-manager.sh
Если открыть его код, в самом начале можно увидеть настройки:
WORKERS=('api2app-workers/whisper-cpp.py')
WORKERS_NUM=(1)
QUEUE_SIZE_URLS=('https://queue.api2app.org/queue_size/ea474c9a-c631-4bac-9141-ba26b0ff56c5')
здесь
WORKERS - массив воркеров, которые нужно запускать (разделяются пробелом).
WORKERS_NUM - массив с числами, сколько нужно запустить копий воркеров (будут выполняться параллельно).
QUEUE_SIZE_URLS - массив URL для проверки размера очереди.
Команды для использования:
./api2app-workers/worker-manager.sh status
./api2app-workers/worker-manager.sh start
./api2app-workers/worker-manager.sh stop
Вывод статуса примерно такой:
-----------------------------------------
STATUS
-----------------------------------------
- api2app-workers/whisper-cpp.py [1]
Queue size: 1
Создание графического интерфейса для приложения
Теперь создам графический интерфейс для приложения, которое будет создавать транскрипции аудио файлов.
Иду на сайт api2app.org. Сначала нужно создать конфигурацию API (по примеру с кодом curl, который был выше). Иду в раздел "API", затем в подраздел "Мои API", нажимаю кнопку "Импортировать". Вставляю свою curl-команду и нажимаю кнопку "Импортировать".

Данные успешно импортировались. Теперь нужно только немного подправить и протестировать:

Нажимаю кнопку "Тест", вижу ответ сервера, сохраняю.
Возвращаюсь в подраздел "Мои API", нажимаю кнопку "Поделиться" -> "Сделать публичным" -> "Скрыть".

API добавления задания отдает нам идентификатор задания и URL для получения результата, поэтому нужно ещё создать конфигурацию API для этого (авторизация здесь не нужна):

Так же, как с предыдущим, делаю это API публичным и скрытым.
Приступаю к созданию графического интерфейса. Перехожу в раздел "Приложения" -> "Мои приложения", нажимаю кнопку "Создать приложение".
Сначала нужно добавить поле для загрузки аудио-файлов:

После добавления поля нажимаю кнопку "Параметры". В поле "Принимать типы файлов" ввожу "audio/*" для фильтрации файлов по типу при выборе с устройства.

Далее добавляю поле с типом "Выбор (select)" для выбора языка. Язык может быть определен автоматически, поэтому по умолчанию будет значение "auto":

Теперь нужно добавить поле для выбора формата транскрипции (txt, json, srt и т.д.):

Добавляю кнопку с текстом "Создать транскрипцию". На этом шаге у меня получился такой интерфейс в редакторе:

API возвращает идентификатор задачи, по которому потом можно получить результат, для этого мне нужно создать скрытое поле и элемент "Прогресс операции".
У скрытого поля в параметрах нужно обязательно включить опцию "Хранить значение поля". Для элемента "Прогресс операции" нужно указать среднее время выполнения операции - параметр "Время выполнения операции (в секундах)", остальные значения можно не менять.

Добавляю элемент "Текст", в нём будет выводиться результат. Устанавливаю такие параметры:

В параметрах первого блока включаю опцию "Очищать после отправки".

Результат у меня будет появляться во второй колонке. Также я добавил ещё текстовую информацию:

Теперь нужно связать все поля с ранее созданными конфигурациями API.
Поле файла служит для ввода данных (выбора файла), поэтому нужно указать Действие ввода - какое поле API использовать. Нажимаю кнопку "Действие ввода" у этого поля:

Нахожу конфигурацию API для добавления задания, которое создал ранее, выбираю поле "audio_file", сохраняю.

Для выбора языка тоже выбираю это API в окне "Действие ввода" и поле "data.language" (поле из JSON).

То же самое делаю для поля "Формат транскрипции", только выбираю поле "data.output_type".
Действие ввода для кнопки только одно - "Отправить". Выбираю его и сохраняю.

Теперь нужно установить действие вывода для текстового элемента. Здесь уже нужно использовать API получения результата. Выбираю поле "result_data.result":

Для скрытого поля нудно выбрать действие вывода - откуда поле получает значение. Выбираю API добавления задания и поле "uuid".

Действие ввода - получение результата. Значение используется в URL запроса:

Для получения результата я не добавлял кнопку, поэтому запрос будет отправлен автоматически после того как скрытое поле получит значение (ID задания).
Последним добавлю действие вывода для элемента "Прогресс операции":


Для элемента нужны все данные, которые отдаёт API, поэтому выбираю поле "value".
На этом создание графического интерфейса окончено. Результат можно увидеть и протестировать по ссылке:
https://api2app.org/ru/apps/shared/dc6481f4-f778-11ef-9fb8-525400f8f94f

Таким образом с минимальным программированием можно создавать более сложные приложения. Пока на сайте нет возможности монетизировать такие приложения (планируется), но есть возможность скачивания всех файлов приложения. Кроме того, Вы можете давать ссылку на приложение за плату.
Предупреждение:
Всё описанное Вы можете повторить на свой страх и риск, безопасность пока под вопросом.