Та же проблема что и у обложек статей:
старый промт 'Topic essence: первые 250 символов' был слишком абстрактным
→ модель рисовала свой дефолт (тёмный фон + геометрия)
Новый промт (3 некофликтующих параметра):
VISUAL CONCEPT: конкретный предмет из getPostVisualConcept()
SETTING: физический антураж (8 вариантов по seed из текста поста)
LIGHTING + COLOR TEMPERATURE: конкретный свет
getPostVisualConcept(): 8 тематических категорий × 4-6 концептов
AI, automation, cybersec, code, marketing, money, education, health
+ 12 универсальных концептов
Seed = hash первых 80 символов поста → детерминировано но уникально
8. SMTP: emailService.js (nodemailer), templates (welcome/payment/low_credits)
/api/admin/email/test — тест отправки
app_settings category=smtp (HOST/PORT/USER/PASS/FROM/ENABLED)
9. Maintenance mode: middleware в index.js, MAINTENANCE_MODE в engine settings
При true → 503 для всех запросов кроме /uploads и /api/settings
10. Blog topic bank:
DB: blog_topics(category,topic,is_used,source,priority)
40 тем мигрированы из хардкода (source=hardcoded)
autogen.js: getNextTopic берёт из DB, fallback на TOPIC_BANK
admin API: GET/POST /blog-topics, DELETE /:id, POST /generate (AI +10)
routes/admin.js:
GET /autogen — настройки+статистика+очередь+размеры банков тем
PATCH /autogen/:category — enabled/per_day/run_hour/run_minute
POST /autogen/:category/run — ручной запуск генерации
POST /autogen/queue — добавить тему с приоритетом
DELETE /autogen/queue/:id — удалить тему
routes/admin.js: GET /logs — объединённые ошибки из 3 источников:
generation_jobs (status=failed), ai_usage (!succeeded), scheduled_posts (status=failed)
Сортировка по времени, топ-5 частых ошибок, группировка по типу
routes/admin.js: GET /queue (stats+recent30+stuck), POST /queue/:id/retry, DELETE /queue/stuck
stuck = processing > 5 min → сбрасываем в failed
retry = pending + requeue через Bull
DB: promo_codes, promo_usages tables
routes/admin.js: CRUD /api/admin/promos (GET/POST/PATCH/DELETE)
routes/billing.js: POST /api/billing/apply-promo
Валидация: exists, active, not expired, not exhausted, not used by this user
type=credits → начисляет через billing.credit()
Проблема: VISUAL CONCEPT и STYLE из COVER_STYLES противоречили друг другу
(sculptor on marble + frosted glass style = модель рисовала свой дефолт)
Решение: 3 простых параметра на статью без конфликтов:
SUBJECT: что изображено (из getVisualMetaphor)
SETTING: антураж (12 вариантов по articleId % 12):
oak desk | marble | slate | workbench | velvet | dawn mist |
terracotta | city night | concrete | library | frost | brick
LIGHTING: конкретный свет (golden hour, studio, rim light, etc.)
COLOR TEMPERATURE: warm amber / cool whites / etc.
Убраны STYLE/PALETTE/MOOD/COMPOSITION блоки которые путали модель.
Теперь каждая статья = уникальная физическая сцена с конкретным светом.
COVER_STYLES: 4 → 12 стилей (amber-terrain, violet-gradient, monochrome-sharp,
coral-horizon, neon-circuits, blueprint-tech, glass-morphism, retro-wave,
zen-minimal, data-cosmos, editorial-ink, teal-architecture)
Теперь стиль повторяется раз в 12 статей (было каждые 4)
buildCoverPrompt(): новая структура промта:
- VISUAL CONCEPT: тематическая метафора из getVisualMetaphor()
- STYLE/PALETTE/MOOD/COMPOSITION: стиль из ротации
Промт явно ставит концепцию первой → картинка отражает тему статьи
getVisualMetaphor(): 10 тематических категорий (cybersec, AI, automation,
data, deepfake, code, marketing, email, vector/rag, prompt engineering)
+ 10 универсальных метафор. Детерминированный выбор по хешу заголовка.
DB: inbox_messages (text, ai_type, ai_reply, status), channels.tg_webhook_enabled
Engine routes/inbox.js:
POST /api/tg-webhook/:channelId — получаем комментарии от TG (публичный)
GET /api/inbox/:channelId — список сообщений с фильтром
GET /api/inbox/stats/:channelId — статистика по типам
POST /api/inbox/:id/reply — отправить ответ в TG
POST /api/inbox/:id/status — изменить статус
POST /api/inbox/:channelId/setup-webhook — зарегистрировать TG webhook
AI: claude haiku → {type, reply} JSON
До: wall.post без attachments → картинка игнорировалась
После:
1. photos.getWallUploadServer → upload_url
2. POST upload_url с файлом (local path или download) → server/photo/hash
3. photos.saveWallPhoto → owner_id + photo_id
4. wall.post с attachments=photo{owner_id}_{id}
При ошибке загрузки фото — публикуем без картинки (graceful degradation)
Поддерживает как локальные /uploads/ файлы так и внешние URL
config/index.js: добавили ROUTERAI_BASE_URL, ROUTERAI_API_KEY в список keys
который загружается из app_settings через reloadAi()
Без этого ключ был NULL и все генерации падали в local SVG fallback
- generatePost: customPrompt или channel.ai_style_prompt → добавляется к userPrompt
- routes/generate.js: принимает customPrompt, передаёт в очередь
- workers/generation.js: передаёт customPrompt в generatePost и generateArticle
covers.js: generateCoverViaRouterAI принимает quality='medium' по умолчанию
postImages.js: quality='low' для постов TG/VK (₽0.25 vs ₽0.84)
Экономия 70% на генерации картинок к постам
generate.js: getChannel(userId, channelId) → getChannel(channelId, userId)
channels.js: getChannel alias → getFullChannel
postImages.js: убран /responses + gpt-5.5 (не работал на aiprimetech),
заменён на Nyxos /images/generations с fallback на aiguoguo
scheduled_posts.cover_regen_attempts: счётчик попыток регенерации.
Если обложка SVG и рег. не удалась:
- попытки < 3: откладываем scheduled_at на +15 мин, не публикуем
- попытки >= 3: публикуем с Zero fallback (не держим пост вечно)
Максимальная задержка = 45 минут после плановой публикации.
scheduledPostsRunner.js: перед отправкой обложки в TG проверяем
размер файла. Если < 30KB (SVG-заглушка) — пробуем перегенерировать
через covers.generateCover(). Если регенерация успешна — публикуем
реальную обложку. Если нет — fallback на позу Зеро вместо SVG.
coverRetry.js: сканирует articles с cover < 30KB (SVG-заглушки),
перегенерирует через covers.generateCover(). При недоступности
провайдера (timeout/502) прерывает цикл до следующего запуска.
Первый запуск через 5 мин после старта engine, далее каждые 30 мин.
- channel_style.image_rubrics (JSONB): 6 рубрик для ZeroPost-блога
(tech-photo, 3d-device, code-screen, data-flow, ai-neural, cinematic-tech)
- selectRubric(): haiku выбирает рубрику по заголовку+тегам статьи
- generateCover(): загружает rubrics из БД, вызывает selectRubric перед генерацией
- buildCoverPrompt(): принимает rubric — рубрика задаёт весь визуальный язык
- Убраны лишние ограничения (no circuit boards, no glowing nodes, no brains)
из базового промпта — теперь только: no text, no logos, no real faces