The Daily Newsstand · Free, Always
Friday, September 25, 2026

Нажал кнопку, сказал задачу, забыл: как я связал PLAUD, Raspberry Pi, Codex и Microsoft To Do

Translate

Телефон для быстрых заметок подходит не всегда. Нужно достать его, разблокировать, открыть приложение, что-то напечатать. Особенно неудобно, когда одновременно занят другой задачей и не хочется полностью переключать контекст.

Поэтому я начал смотреть, что вообще есть на рынке для создания голосовых заметок, которые автоматически превращаются в текст. Главными критериями были качественное распознавание русской речи и возможность носить устройство на запястье. Купил PLAUD и довольно быстро понял, что сам по себе гаджет решает только половину задачи.

Я могу нажать кнопку и сказать:

Работа. В понедельник в 15:00 проверить резервные копии Exchange, напомнить за час.

PLAUD нормально сохранит запись и создаст текст.

Но дальше мне всё равно нужно открыть приложение, найти нужную запись, прочитать её и руками создать задачу в Microsoft To Do. То есть вместо одного действия я всё равно получаю несколько.

Мне хотелось другого:

нажал кнопку → сказал мысль → забыл → задача сама появилась в To Do с категорией, сроком и напоминанием.

В итоге я собрал небольшую систему на Raspberry Pi Zero 2 W.

Что вы найдёте в статье

На примере своей системы покажу:

  • Как связать PLAUD, Codex и Microsoft To Do: надиктовал мысль — получил готовую задачу.

  • Как сократить расход токенов, передав рутинные операции Python и оставив модели только разбор текста.

  • Как извлекать из разговорной речи сроки, напоминания и категории задач.

  • Как запустить всё на Raspberry Pi, чтобы автоматизация работала круглосуточно.

  • Как хранить состояние обработки в SQLite, повторять попытки после сбоев и избегать дубликатов.

  • Какие сложности возникли с авторизацией сервисов и как я их решил.

  • Как использовать накопленные задачи для недельных отчётов и коротких сводок на дейли.

Что получилось

Сейчас сценарий выглядит именно так, как мне изначально хотелось. 

Например, я могу сказать:

Здоровье. В пятницу сходить к стоматологу.

Через некоторое время в моем таскере Microsoft To Do появляется:

Здоровье: Сходить к стоматологу

  • с категорией Здоровье;

  • соответствующей датой;

  • исходной транскрипцией PLAUD в заметке;

  • идентификатором исходной записи.

Или:

Работа. В понедельник в 15:00 проверить резервные копии Exchange, напомнить за час.

Система создаст задачу с:

Название:
Работа: Проверить резервные копии Exchange

Due:
понедельник, 15:00

Reminder:
14:00

Category:
Работа

При этом Raspberry работает полностью автономно. Никакой открытой SSH-сессии или постоянно работающего Codex не требуется.

Архитектура

Сейчас схема выглядит примерно так:

А всё это запускается systemd timer каждые 15 минут.

Почему вообще Raspberry Pi

Хотелось, чтобы автоматизация:

  • не зависела от моего рабочего компьютера;

  • не требовала открытого браузера;

  • переживала перезагрузки;

  • работала 24/7;

  • почти ничего не потребляла.

Для этой задачи мощности Raspberry Pi Zero 2 W хватает с большим запасом.

На нём сейчас работают:

Python
Node.js
PLAUD MCP
Codex CLI
SQLite
systemd
Microsoft Graph/MSAL

Сама модель, разумеется, на Raspberry не запускается — Codex обращается к облачной модели.

Первая версия оказалась слишком дорогой

Первоначально я сделал самый очевидный вариант.

Codex сам получал доступ к PLAUD через подключенный MCP и выполнял запрос вроде:

List my recent Plaud recordings

Работало прекрасно. Я отправлял запрос — агент получал записи и возвращал результат.

Но потом я посмотрел на расход токенов.

Один очень простой запрос:

list my recent Plaud recordings

съел:

16 901 tokens

Хотя конечный результат состоял буквально из нескольких названий записей.

Причина довольно очевидная: агенту передавался не только мой prompt.

В контекст попадали:

  • системные инструкции Codex;

  • описание доступных инструментов;

  • MCP tools;

  • вызов list_files;

  • ответ PLAUD;

  • агентный контекст;

  • финальный ответ.

Для ручной работы это нормально.

Но запускать такую конструкцию каждые 5 минут — уже сомнительная идея.

288 запусков в сутки только для того, чтобы узнать, появилась ли новая запись.

Главное изменение архитектуры

После этого я разделил систему на две части.

Все механические операции выполняет обычный Python:

получить список PLAUD записей
↓
сравнить ID с SQLite
↓
получить только новые транскрипции
↓
сохранить их

А LLM используется только там, где действительно нужен смысл текста.

То есть теперь:

Python → PLAUD
Python → SQLite
           ↓ только новые тексты
          Codex

Если новых записей нет:

Codex вообще не запускается.

Это оказалось важным архитектурным решением проекта.

Экономия оказалась существенной

Первый тест:

Codex → PLAUD MCP → list_files

дал:

16 901 tokens

После переноса получения PLAUD-данных в Python тот же смысловой тест на двух заметках:

2 712 tokens

Потом я попробовал заставить Codex отдавать результат через довольно подробную JSON Schema.

Получил:

12 146 tokens

Schema была убрана.

Финальный облегчённый классификатор обычно показывает примерно:

2 700–3 500 tokens

на небольшой batch заметок.

Получилось примерно так:

Версия

Что делает LLM

Расход в тесте

Первая

сама ходит в PLAUD через MCP

16 901

Structured output

классификация + большая schema

12 146

Финальная

получает только текст

~2 700–3 500

При этом точный расход квоты Codex, конечно, не обязан линейно соответствовать tokens used, но разница в объёме контекста хорошо видна.

Получение записей из PLAUD без Codex

PLAUD MCP предоставляет несколько полезных методов:

list_files
get_file
get_note
get_transcript

Сначала Python вызывает:

list_files

и получает примерно:

{
  "id": "of_...",
  "name": "2026-09-16 13:44:54",
  "created_at": "2026-09-16T10:44:54",
  "duration": 8000
}

Главное поле здесь — id.

Оно отлично подходит для дедупликации.

Если такого ID в SQLite ещё нет, Raspberry получает транскрипцию:

get_transcript(
    file_id,
    block="transaction_polish"
)

Я предпочитаю transaction_polish, потому что PLAUD уже немного очищает распознанную речь.

Если polished-вариант недоступен, можно использовать обычный:

transaction

Само аудио Raspberry вообще не скачивает.

SQLite как небольшая state machine

Для такой автоматизации оказалось очень удобно не пытаться делать всё одним огромным скриптом.

Каждая запись имеет состояние.

Основная цепочка:

waiting_transcript
        ↓
pending_ai
        ↓
ready_todo
        ↓
synced

Есть также конечные состояния:

done
note
question
ignored

Например, запись:

Работа. Создать тестовую задачу для интеграции PLAUD и Codex.

получает:

pending_ai

после классификации:

ready_todo

после успешного Microsoft Graph POST:

synced

А фраза:

Отчет. Заказал кабели, всё уже приехало.

может быть классифицирована как:

done

и в Microsoft To Do попадет как уже закрытая - в будущем, это пригодится в отчетах о проделанной работе.

Я сохраняю ещё и временную трассировку

В базе лежат:

created_at
first_seen_at
transcript_received_at
classified_at
todo_created_at

Это позволяет посмотреть весь pipeline:

PLAUD создал запись
        ↓
Raspberry увидел
        ↓
получил transcript
        ↓
Luna классифицировала
        ↓
Microsoft To Do создал задачу

Например, можно потом посчитать реальную задержку:

voice → To Do = 4 мин 18 сек

Или быстро понять, где именно возникла проблема.

Что делает LLM

На этом этапе задача модели очень узкая.

Она получает что-то вроде:

[
  {
    "id": "of_123",
    "text": "Работа. Завтра проверить резервные копии Exchange"
  }
]

и должна определить:

type
title
due_date
due_time
reminder_at
category

Пример результата:

{
  "id": "of_123",
  "type": "task",
  "title": "Проверить резервные копии Exchange",
  "due_date": "2026-09-18",
  "due_time": null,
  "reminder_at": null,
  "category": "work"
}

Использую GPT-5.6 Luna через Codex CLI.

Для подобной классификации более тяжёлая модель мне просто не нужна.

Естественные даты

Отдельно хотелось иметь возможность говорить нормально, а не диктовать ISO 8601.

Например:

завтра
послезавтра
в понедельник
в пятницу
на выходных

Если сегодня среда и я говорю:

В понедельник проверить бэкапы.

модель возвращает ближайший будущий понедельник:

2026-09-21

То же самое со временем:

В понедельник в 15:00 проверить бэкапы.

получается:

{
  "due_date": "2026-09-21",
  "due_time": "15:00"
}

Напоминания

Можно сказать:

В понедельник в 15:00 проверить резервные копии, напомнить за час.

Результат:

{
  "due_date": "2026-09-21",
  "due_time": "15:00",
  "reminder_at": "2026-09-21T14:00"
}

В Microsoft Graph уходят уже нормальные:

dueDateTime
reminderDateTime
isReminderOn

В результате To Do показывает срок и обычное push-напоминание.

Самая неожиданная часть — категории

Я быстро привык начинать заметки ключевым словом:

Работа.
Хобби.
Личное.
Здоровье.

И стал использовать первое слово как категорию Microsoft To Do.

Например:

Здоровье. Сходить к стоматологу.

становится:

Здоровье: Сходить к стоматологу

и задача получает настоящую Microsoft category:

Здоровье

В Microsoft Graph это выглядит примерно так:

"categories": [
  "Здоровье"
]

А потом вмешалось распознавание речи

Конечно, STT не всегда слышит слово одинаково.

Я получил варианты вроде:

Здоровье
Здоровью
Здаровье
Здарова

Если создавать category из первого слова, через некоторое время в Microsoft окажется:

Здоровье
Здоровью
Здаровье
Здарова

что довольно быстро превратится в помойку.

Поэтому добавил нормализацию.

Важно: она применяется только к первому слову.

Остальную часть задачи fuzzy matching никогда не трогает.

Exact → alias → fuzzy

Алгоритм такой:

первое слово
    ↓
exact match
    ↓
alias
    ↓
fuzzy match
    ↓
новая категория

Например:

Здоровье
→ exact
→ Здоровье
Здоровью
→ alias
→ Здоровье
Здаровье
→ alias/fuzzy
→ Здоровье
Хобий
→ fuzzy
→ Хобби

Как отличить новую категорию от обычного первого слова

Есть ещё одна проблема: первое слово в голосовой заметке далеко не всегда означает категорию.

Допустим, запись:

Сходить к врачу.

Первое слово — Сходить.

Очевидно, создавать category Сходить не нужно.

Поэтому правило сейчас такое.

Если есть явный разделитель, точка или запятая - тоесть когда ты сказал первое слово и сделал небольшую паузу, получается вот такая транскрипция:

Автомобиль. Купить антифриз.

здесь уже, слово Автомобиль - будет новой категорией, которая создастся автоматически.

Результат в To Do:

Автомобиль: Купить антифриз

Если разделителя нет:

Автомобиль купить антифриз.

то система сначала ищет Автомобиль среди существующих категорий.

Если такой категории нет — задача всё равно создаётся, но без категории.

То есть:

Сходить к врачу
→ обычная task
→ category=None

Это сильно снижает количество ложных категорий.

Microsoft To Do и странности клиентов

Тут обнаружилась любопытная особенность.

Через Microsoft Graph я вижу:

TITLE: Сходить к стоматологу
CATEGORIES: ["Здоровье"]

То есть category точно записана.

В веб-версии Microsoft To Do она прекрасно отображается:

● Здоровье

А вот Windows-приложение категории просто не показывает.

Поэтому я решил дублировать информацию ещё и в названии:

Здоровье: Сходить к стоматологу

Это не замена настоящей category.

Задача одновременно имеет:

Title:
Здоровье: Сходить к стоматологу

Category:
Здоровье

Такой подход оказался удобнее: если клиент Microsoft To Do поддерживает категории — я получаю нормальную категоризацию. Если нет — нужная информация всё равно остаётся прямо в названии задачи.

Microsoft Graph

Для интеграции используется обычный Microsoft Graph с delegated permissions.

Основное разрешение:

Tasks.ReadWrite

Для управления Outlook master categories понадобилось:

MailboxSettings.ReadWrite

Авторизация сделана через Device Code Flow.

На Raspberry хранится MSAL token cache, поэтому повторно вводить пароль при каждом запуске не нужно.

Client secret для такого сценария не требуется.

Отказоустойчивость

Я специально не стал делать:

получили PLAUD
↓
сразу вызвали AI
↓
сразу POST Microsoft

Проблема такого подхода в том, что при любой ошибке становится непонятно, на каком этапе всё остановилось. 

Например, запись уже обработали через AI, но Microsoft Graph вернул ошибку. Или задача создалась, но система упала до того, как успела сохранить результат.

 Поэтому между всеми этапами появилась SQLite. Это позволяет спокойно переживать:

  • отсутствие интернета;

  • временную ошибку PLAUD;

  • исчерпанный Codex limit;

  • ошибку Microsoft Graph;

  • reboot Raspberry;

  • неготовый transcript.

Если AI временно недоступен, запись просто остаётся:

pending_ai

Если Microsoft Graph не отвечает:

ready_todo

В итоге ошибка на одном из этапов не означает потерю записи: она остаётся в базе и может быть обработана при следующем запуске. Сама база тоже пригодится — например, поверх неё можно сделать свой интерфейс с поиском, фильтрами и просмотром истории обработки.

Дедупликация

Основной ключ:

PLAUD recording_id

В SQLite это PRIMARY KEY.

Кроме того, после успешного создания Microsoft-задачи сохраняется:

todo_id

Поэтому повторный запуск pipeline не должен породить ещё одну такую же задачу.

Автоматический запуск

За оркестрацию отвечает небольшой shell-script:

run_pipeline.sh

Он делает:

1. ingest_plaud.py

2. SELECT COUNT(*)
   WHERE status='pending_ai'

3. Если count > 0:
      classify.py
   иначе:
      Codex НЕ запускать

4. SELECT COUNT(*)
   WHERE status='ready_todo'

5. Если count > 0:
      sync_todo.py

Это довольно важный момент.

Каждые 5 минут происходит:

PLAUD list_files

Но AI вызывается только тогда, когда действительно есть новые заметки.

systemd

Service обычный oneshot.

Timer:

[Timer]
OnCalendar=*:0/5
Persistent=true
AccuracySec=30s

То есть запуск происходит:

05
10
15
20
...

каждого часа.

Что лежит на Raspberry

Примерно такая структура:

/opt/plaud-todo/
├── .venv/
├── config/
│   ├── plaud-todo.env
│   └── msal_cache.json
│
├── data/
│   ├── state.db
│   └── pipeline.lock
│
└── scripts/
    ├── ingest_plaud.py
    ├── classify.py
    ├── sync_todo.py
    └── run_pipeline.sh

.venv/ — Python-окружение проекта.
Содержит интерпретатор и библиотеки для работы с PLAUD MCP, Microsoft Graph и авторизацией Microsoft. Скрипты запускаются через Python из этого окружения.

config/plaud-todo.env — настройки интеграции.
Здесь указаны идентификатор зарегистрированного Microsoft-приложения, название списка To Do и часовой пояс для сроков и напоминаний.

config/msal_cache.json — кэш авторизации Microsoft.
Используется библиотекой MSAL для получения токенов без ручного входа при каждом запуске. Создаётся при первоначальной авторизации и обновляется по мере работы. Содержит чувствительные данные.

data/state.db — база состояния SQLite.
Хранит записи PLAUD, транскрипты, результаты классификации, категории, ошибки и идентификаторы созданных задач. Статусы показывают, на каком этапе находится каждая запись: ожидает транскрипта, классификации, отправки или уже обработана. Здесь же хранится checkpoint для обнаружения новых записей
.

data/pipeline.lock — файл блокировки.
Используется командой flock, чтобы два запуска pipeline не выполнялись одновременно. Создаётся автоматически. Блокировку удерживает работающий процесс.

scripts/ingest_plaud.py — получение записей из PLAUD.
Обращается к PLAUD MCP, находит новые записи и получает их транскрипты. Сначала запрашивает обработанный текст, при необходимости — исходный. Сохраняет данные в SQLite и переводит готовые записи в pending_ai. Аудиофайлы не скачивает.

scripts/classify.py — смысловой разбор заметок.
Берёт из SQLite до 20 ожидающих записей и передаёт их тексты Codex. Модель определяет тип записи, формулирует название задачи, извлекает дату, время и напоминание. Скрипт проверяет JSON-ответ и сохраняет результат. Записи типа task получают статус ready_todo.

scripts/sync_todo.py — создание задач в Microsoft To Do.
Читает готовые результаты, определяет категорию по первому слову транскрипта и нормализует её через точные совпадения, aliases и fuzzy matching. При необходимости создаёт Microsoft category, добавляет её в заголовок и отправляет задачу через Graph. После успешного создания сохраняет todo_id и статус synced.

scripts/run_pipeline.sh — общий сценарий запуска.
Берёт блокировку и последовательно запускает получение записей, классификацию и синхронизацию. Перед классификацией проверяет наличие pending_ai: если очередь пустая, Codex не запускается. Аналогично отправщик запускается только при наличии готовых задач.

Авторизация PLAUD MCP и Codex CLI хранится отдельно, в домашнем каталоге пользователя, от которого работает сервис. Запуск по расписанию обеспечивают plaud-todo.service и plaud-todo.timer в /etc/systemd/system/. Во время классификации также создаётся каталог tmp/ для временного JSON-ответа Codex.

Codex тоже запускается не от root.

Безопасность

Несколько вещей, которые я сразу постарался не делать.

Никаких токенов внутри исходников.

Microsoft MSAL cache:

chmod 600

PLAUD tokens:

chmod 600

Codex auth:

chmod 600

Service запускается:

User=one
Group=one
NoNewPrivileges=true

Плюс используется flock, чтобы два экземпляра pipeline случайно не начали одновременно менять SQLite и создавать задачи.

Что получилось в итоге

Теперь интерфейс создания задачи фактически состоит из одной физической кнопки.

Я могу идти по квартире, работать с сервером, паять что-нибудь или собирать робота и просто сказать:

Работа. Завтра провести встречу с Васей в 14:00.

И больше к этой мысли не возвращаться.

Через некоторое время появляется:

Работа: Завтра провести встречу с Васей в 14:00.

с правильной датой и категорией.

Или:

Хобби. На выходных распечатать крепление для гусенечного манипулятора.

Получаю:

Хобби: На выходных распечатать крепление для гусенечного манипулятора.

Или вообще без категории:

Купить батарейки.

Получаю обычную:

Купить батарейки

Анализ задач: отчеты за неделю

У этой связки есть ещё одно применение: задачи можно использовать для подготовки коротких отчётов.

За неделю в Microsoft To Do накапливается история: что планировал, что закрыл, что осталось висеть. Если передать эти данные в Codex или чат GPT — через подключённый инструмент либо обычную выгрузку, — можно получить готовый дайджест.

Например, попросить:

Сделай сводку по моим рабочим задачам за неделю: что завершено, что осталось в работе и что запланировано дальше. Сгруппируй по темам, убери повторы. Отдельно выдели блокеры, если они указаны в заметках. Подготовь короткую версию для дейли, которую можно рассказать за минуту.

Для ежедневного дейли достаточно поменять период: что сделал вчера, что собираюсь делать сегодня и где нужна помощь. Для недельного отчёта — собрать общую картину по проектам.

Здесь пригодится то, что в задачах сохраняется исходная транскрипция. Короткий заголовок может звучать как «Проверить резервные копии», а в заметке останутся детали: какой сервер, что именно проверить и зачем.

Для этого на этапе тестирования я поднял на своём ПК локальный MCP-сервер для взаимодействия Codex с Microsoft To Do, основанный на пакете @mag-cie/mcp-microsoft-todo. Через него Codex получает задачи напрямую, поэтому можно прямо в чате попросить проанализировать выполненные дела, собрать дайджест за неделю или подготовить короткое саммари для дейли. В перспективе, планирую перенести этот функционал на Rasberry Pi, с созданием автоматических отчетов в OneNote.

Что ещё хочется добавить

Сейчас система уже делает то, ради чего создавалась, но направлений развития хватает.

Например:

  • обработку длинных встреч отдельно от коротких заметок для записи в протокола в OneNote;

  • автоматические weekly/daily summaries;

  • веб-интерфейс для SQLite - свой таскер с нужными фильтрами. To Do не идеален;

  • автоматическое обучение списка alias по повторяющимся ошибкам PLAUD.

Что пошло не так: аутентификация

На бумаге схема выглядела просто: авторизовать PLAUD, авторизовать Codex, получить токен Microsoft Graph — и можно писать интеграцию. На практике именно аутентификация оказалась одной из неприятных моментов проекта, в основном потому, что Raspberry Pi работает headless, а OAuth-потоки разных сервисов предполагают наличие браузера на той же машине.

PLAUD OAuth и localhost, который оказался не тем localhost

PLAUD MCP устанавливался командой:

npx -y @plaud-ai/mcp@latest install

Инсталлятор нашёл конфигурацию Codex:

Codex Desktop /home/one/.codex/config.toml

и запустил OAuth.

Проблема появилась на callback. PLAUD использовал адрес:

http://localhost:8199/auth/callback

Но браузер у меня был открыт на Windows, а сам OAuth listener работал на Raspberry Pi.

В результате после авторизации браузер пытался открыть:

localhost:8199

на моём Windows-компе, а не на Raspberry. PLAUD MCP на Pi callback не получал, и OAuth завершался по timeout.

Решение — SSH port forwarding:

ssh -F NUL -L 8199:127.0.0.1:8199 one@10.1.9.149

Цепочка стала выглядеть так:

Browser Windows
     ↓
localhost:8199
     ↓ SSH tunnel
Raspberry Pi:8199
     ↓
PLAUD MCP OAuth listener

OAuth-токены PLAUD сохранились локально.

Codex: Device Code внезапно потребовал номер телефона

Следующей проблемой оказался сам Codex.

Я хотел использовать авторизацию через ChatGPT, а не OpenAI API key. Это было принципиально: API key означал бы отдельный usage-based API billing, тогда как Codex через ChatGPT использует лимиты моего ChatGPT workspace.

При попытке пройти стандартную headless-авторизацию через Device Code браузер в какой-то момент отправил меня на:

auth.openai.com/add-phone

и потребовал добавить номер телефона.

Проблема была в том, что нужной страны в списке не оказалось.

При этом на Windows у меня уже был нормально авторизован Codex Desktop под тем же ChatGPT workspace. Поэтому я использовал существующую авторизацию Codex на Raspberry и перенёс файл авторизации в:

/home/one/.codex/auth.json

После этого ограничил права:

chmod 600 ~/.codex/auth.json

Проверка:

codex login status

дала:

Logged in using ChatGPT

То есть Raspberry стал использовать именно ChatGPT-аутентификацию, без API key.

Microsoft To Do: личный аккаунт потребовал немного другой OAuth-схемы

Microsoft To Do у меня находится на личном Microsoft Account, а не в корпоративном Entra tenant.

Поэтому App Registration создавался с поддержкой personal Microsoft accounts, а в MSAL использовался authority:

https://login.microsoftonline.com/consumers

Приложение работает как public client:

Allow public client flows = Yes

Client Secret здесь вообще не нужен.

Первоначальное Graph permission:

Tasks.ReadWrite

Позже, когда я добавил автоматическое создание категорий, понадобилось ещё:

MailboxSettings.ReadWrite

Для Raspberry Device Code Flow оказался здесь удобным. Скрипт выводил что-то вроде:

To sign in, use a web browser to open the page
https://www.microsoft.com/link
and enter the code XXXXXXXX

Открыл страницу на ПК, ввел код, а Raspberry получал токен.

После этого повторный login уже не требуется — MSAL пытается обновлять access token из cache автоматически.

В итоге получилось три независимых механизма аутентификации

PLAUD
→ OAuth token
→ ~/.plaud/tokens-mcp.json

Codex
→ ChatGPT auth
→ ~/.codex/auth.json

Microsoft
→ OAuth/MSAL
→ /opt/plaud-todo/config/msal_cache.json

И systemd service обязательно запускается от пользователя:

User=one
Group=one

а не от root.

Иначе сервис просто не увидит пользовательские PLAUD/Codex credentials.

Все чувствительные файлы в итоге получили права 600.

Главный вывод

Самой полезной частью этого проекта для меня оказался не сам AI.

Первая версия была буквально:

дать агенту все инструменты
и попросить сделать всё

Работало.

Но оказалось дорого и избыточно.

Финальная архитектура наоборот максимально скучная:

Python делает детерминированные вещи.
SQLite хранит состояние.
LLM используется только там, где действительно требуется понимание языка.

Именно после этого система стала похожа не на демонстрацию возможностей нейросети, а на нормальный постоянно работающий сервис.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.