March 29, 2026 04:22 PM
Платформа статей с ИИ из коробки: редактор, медиа, аналитика и Markdown для LLM-агентов
Обзор возможностей: серверные вызовы моделей без утечки ключей, структурированные подсказки для SEO и текста, генерация изображений и озвучки, учёт использования моделей и просмотров, а также выдача одной и той же публичной статьи в HTML для людей и в Markdown — с метаданными и оценкой токенов — для инструментов и ИИ-агентов.
Зачем это нужно из коробки
Поставляемый стек закрывает типичный цикл работы редакции и площадки:
Редактор получает подсказки модели в контексте уже сохранённой статьи и ревизии — без отдельного «чата в браузере с ключом».
SEO и превью можно сгенерировать структурированно и применить в формы одним действием, тело статьи — отдельным потоком в Markdown с обратной записью в TipTap.
Картинки и озвучка проходят через тот же медиа-пайлнайн (CDN URL), что и загрузки автора — без ручной передачи файлов между сервисами.
Администратор видит агрегированный расход токенов по источникам и пользователям и отдельно — просмотры статей и ревизий.
Внешние системы и LLM-агенты получают тот же канонический URL
/article/{slug}, что и читатель, но с заголовкомAccept: text/markdown— в ответ приходит документ с YAML front matter, заголовкомContent-Signalи числом токенов вx-markdown-tokens, без парсинга HTML и без отдельного «API для роботов».
Ниже — принципы, страницы интерфейса, маршруты API и места для иллюстраций и примеров ответов.
Принципы работы
Принцип | Смысл |
|---|---|
Ключи только на сервере |
|
Явное включение ИИ |
|
Неизменяемый журнал usage | Каждый значимый вызов модели фиксируется в |
Медиа единообразно | Сгенерированные изображения и mp3 озвучки загружаются через существующий поток медиа; в UI — обычные URL. |
Публичная статья — два представления | HTML и Markdown различаются по |
Переменные окружения для LLM и лимитов чата описаны в config/env.ts (LLM_CONFIG) и в .env.example: LLM_API_KEY, NEXT_PUBLIC_LLM_ENABLED, LLM_CHAT_MODELS, LLM_IMAGE_MODELS, LLM_CHAT_RATE_LIMIT_POINTS, LLM_CHAT_RATE_DURATION_SEC, а также APP_INTERNAL_ORIGIN для корректных серверных запросов к своим API (например за reverse proxy или в Docker) при выставлении Content-Signal на HTML-странице статьи.
Один URL — HTML для людей, Markdown для агентов
Для опубликованной публичной статьи канонический адрес остаётся **/article/{slug}**. Браузер запрашивает HTML как обычно. Клиент, который передаёт **Accept: text/markdown** (с приоритетом выше, чем у text/html), получает не HTML, а текст Markdown с YAML front matter в начале файла: заголовок, описание, slug, canonical, язык, флаг обучения (allow_ai_training), даты публикации и обновления — затем тело статьи в Markdown, согласованное с тем же рендером контента, что используется для HTML.
Практическая польза для LLM и интеграций:
не нужно тянуть HTML, чистить теги и терять структуру;
один запрос даёт сжатый по токенам текст плюс метаданные в машиночитаемом виде;
заголовок
**x-markdown-tokens**даёт оценку объёма документа в токенах (подсчёт черезgpt-tokenizer, профильo200k_base) — удобно для бюджетирования вызовов к своей или внешней модели;заголовок
**Content-Signal**передаёт политику в духе Content Signals: например, разрешено ли использование материала для обучения (ai-train=yes|no) при сохранённыхsearch=yes,ai-input=yesдля публичной выдачи.
Технически запрос обрабатывается в **src/proxy.ts**: при подходящем Accept выполняется rewrite на внутренний маршрут GET /api/v1/public/article/markdown (slug передаётся через query и служебный заголовок). Для HTML-ответа Content-Signal подмешивается отдельным запросом к GET /api/v1/public/article/content-signal?slug=... с базой APP_INTERNAL_ORIGIN, если публичный origin запроса ненадёжен.
Примеры вызовов (curl)
Запрос Markdown по публичному URL статьи (как это сделает агент или скрипт):
curl -sS -D - -o /tmp/article.md \
-H "Accept: text/markdown;q=1.0, text/html;q=0.5" \
"https://example.com/article/my-article-slug"В ответе ожидаются среди прочего:
Content-Type: text/markdown; charset=utf-8Vary: AcceptContent-Signal: ai-train=yes, search=yes, ai-input=yes(значения зависят от настроек статьи)x-markdown-tokens: <целое число>
Прямой вызов внутреннего API (удобно для документации и отладки; на продакшене может отличаться базовый URL и rate limit):
curl -sS -D - -o /tmp/article.md \
"https://example.com/api/v1/public/article/markdown?slug=my-article-slug"Пример JSON от content-signal (для вставки в статью)
Эндпоинт GET /api/v1/public/article/content-signal?slug=... возвращает JSON с готовой строкой для заголовка Content-Signal:
{
"contentSignal": "ai-train=yes, search=yes, ai-input=yes"
}При allowAiTraining: false у статьи в ответе будет ai-train=no.
Пример фрагмента тела ответа Markdown (YAML + начало текста)
Ниже — иллюстративный фрагмент формата (поля front matter соответствуют buildPublicArticleMarkdownDocument):
---
title: "Заголовок статьи"
description: "Краткое описание для превью и агентов"
slug: "my-article-slug"
canonical: "https://example.com/article/my-article-slug"
language: "ru"
allow_ai_training: yes
published_at: "2026-03-01T12:00:00.000Z"
updated_at: "2026-03-15T09:30:00.000Z"Сравнение: HTML vs Markdown для одного slug
Критерий | HTML ( | Markdown ( |
|---|---|---|
Тип контента |
|
|
Удобство для парсинга агентом | низкое (разметка, шум) | высокое (структура, меньше токенов на смысл) |
Метаданные | в основном в | в YAML сверху файла |
Оценка объёма для LLM | нестандартизирована | заголовок |
Редактор: стриминг чата, аудит, структурированные подсказки
Чат
POST /api/v1/llm/chat/stream — поток SSE; контекст собирается из заголовка статьи, SEO-полей и plain text из TipTap (extractPlainTextFromRevisionContent). Доступ только ролям ADMIN / EDITOR; действует отдельный rate limit на пользователя. События учёта: source: chat_stream. История подгружается через GET /api/v1/llm/chat/history (сущности LlmChatSession, LlmChatMessage). Точки входа ИИ в UI доступны после сохранения статьи с ревизией.
{
"enabled": true,
"chat": { "models": [{ "id": "gpt-4o-mini", "label": "GPT-4o mini" }] },
"audit": { "models": [{ "id": "gpt-4o-mini", "label": "GPT-4o mini" }] },
"image": {
"models": [
{
"id": "gpt-image-1",
"label": "GPT Image 1",
"defaultAspectRatioId": "16:9",
"aspectRatios": [{ "id": "16:9", "label": "16:9", "size": "1536x1024" }]
}
]
}
}Аудит статьи
POST /api/v1/llm/article-audit — JSON-режим; результат сохраняется в LlmArticleAudit, список — GET /api/v1/llm/article-audit. В типах ответа — секции preview, content, seo с оценками, сильными сторонами, замечаниями и рекомендациями.
{
"audit": {
"preview": {
"score": 8,
"summary": "Превью соответствует теме.",
"strengths": ["Ясный лид"],
"issues": ["Длинное второе предложение"],
"recommendations": ["Сократить подзаголовок"]
},
"content": { "summary": "…", "strengths": [], "issues": [], "recommendations": [] },
"seo": { "summary": "…", "strengths": [], "issues": [], "recommendations": [] },
"overall": "Готово к публикации с правками превью."
},
"usage": { "promptTokens": 1200, "completionTokens": 400, "totalTokens": 1600 },
"model": "gpt-4o-mini",
"savedId": "67d…"
}SEO, превью и тело (структурированные ответы API)
Маршрут | Назначение | Ключевые поля в JSON |
|---|---|---|
| Мета и OG |
|
| Карточка превью |
|
| Тело статьи |
|
Пример ответа SEO (поля SeoSuggestApiResponse):
{
"suggest": {
"metaTitle": "Новая статья: введение в платформу",
"metaDescription": "Кратко о возможностях редактора и публичной выдачи.",
"ogTitle": "Новая статья — платформа",
"ogDescription": "То же для соцсетей.",
"keywords": "платформа, статьи, markdown, seo"
},
"usage": { "promptTokens": 800, "completionTokens": 200, "totalTokens": 1000 },
"model": "gpt-4o-mini"
}Пример ответа контента (ContentSuggestApiResponse):
{
"suggest": {
"markdown": "## Введение\n\nТекст сгенерированной статьи в Markdown…",
"rationale": "Структура: введение, три раздела, вывод."
},
"usage": { "promptTokens": 2000, "completionTokens": 1500, "totalTokens": 3500 },
"model": "gpt-4o-mini"
}Генерация изображений
Маршруты: POST /api/v1/llm/image/generate, .../image/generate/stream, .../image/prompt/stream. Результат — медиа-ассет и proxyUrl, плюс promptUsed и usage. В интерфейсе — компонент MediaUrlUploadField (превью статьи, OG, диалог картинки в редакторе).
{
"asset": { "id": "…", "mimeType": "image/png" },
"proxyUrl": "https://…",
"usage": { "promptTokens": 0, "completionTokens": 0, "totalTokens": 1000 },
"model": "gpt-image-1",
"aspectRatioId": "16:9",
"promptUsed": "иллюстрация к статье про платформу, плоский стиль"
}Озвучка (TTS)
POST /api/v1/article/listen-audio/generate — генерация mp3, запись полей listenAudioAssetId и связанных на Article. Учёт: source: listen_tts (в числовых полях — символы текста для сопоставления с биллингом TTS).
Просмотры статей
POST /api/v1/article/view — инкремент счётчиков на Article и опубликованной ArticleRevision; дедупликация через Redis ~24 ч на связку посетитель/пользователь + статья + ревизия, если Redis доступен. Админка: GET /api/v1/article/views/dashboard, GET /api/v1/article/views/by-article/[articleId], страница /admin/article-views.
Учёт использования LLM
Журнал LlmUsageEvent с перечислением источников: chat_stream, article_audit, seo_suggest, preview_suggest, content_suggest, listen_tts, image_generate, image_prompt_stream, image_prompt_article.
GET /api/v1/llm/usage/dashboard?days=— сводка для админов (/admin/llm-usage).GET /api/v1/llm/usage/article?articleId=&revisionId=— сводка по статье/ревизии для редактора.
{
"since": "2026-03-01T00:00:00.000Z",
"until": "2026-03-27T23:59:59.999Z",
"days": 14,
"totals": {
"eventCount": 420,
"totalPromptTokens": 900000,
"totalCompletionTokens": 300000,
"totalTokens": 1200000
},
"bySource": [
{ "source": "chat_stream", "count": 200, "totalTokens": 400000 },
{ "source": "seo_suggest", "count": 50, "totalTokens": 80000 }
],
"topUsers": [{ "userId": "…", "eventCount": 80, "totalTokens": 200000 }],
"recent": []
}Сводная таблица HTTP API
Область | Метод | Путь |
|---|---|---|
Чат | POST |
|
Чат | GET |
|
Модели | GET |
|
Аудит | POST, GET |
|
SEO / превью / контент | POST |
|
Картинки | POST |
|
Usage | GET |
|
TTS | POST |
|
Просмотры | POST |
|
Просмотры (админ) | GET |
|
Публичное: Markdown | GET |
|
Публичное: сигнал | GET |
|
Итог
Платформа даёт единый контур для редакции: ИИ встроен в сохранённый документ, структурированные ответы стыкуются с формами и редактором, медиа и озвучка не выбиваются из CDN-потока, расход моделей и просмотры прозрачны в админке. Отдельно выделяется публичная выдача Markdown по согласованию содержимого на том же URL, что и HTML: это снижает стоимость токенов для downstream-LLM, упрощает интеграции и сочетается с заголовками **Content-Signal** и **x-markdown-tokens** — без дублирования «человеческой» и «машинной» версии сайта на разных адресах.
Дополнительные материалы в репозитории
Подробные чеклисты реализации и планов: docs/AI_FEATURES_ROADMAP.md, docs/PRODUCT_ROADMAP.md.