449d1fa728
* config/index.js: добавлен reloadAi() — асинхронно подтягивает значения из app_settings (категория ai_providers), мутирует config.ai in-place. Сервисы продолжают использовать config.ai.* синхронно. 3-уровневый fallback: app_settings.value → process.env (новое имя) → process.env (старое имя) → дефолт. * index.js: добавлен await config.reloadAi() в startup после migrate(). Добавлен middleware AsyncLocalStorage для проброса service/userId в AI-сервисы. Сервис определяется по URL-префиксу (zeropost-blog vs zeropost-tool). Подмонтирован роут /api/usage. * routes/settings.js: PUT и invalidate вызывают config.reloadAi() после изменения настройки категории ai_providers — горячая перезагрузка без рестарта PM2. * routes/usage.js (новый): GET /api/usage/summary?range&group_by — сводка расходов с разбивкой по сервису/провайдеру/модели. GET /api/usage/recent — последние вызовы. * lib/aiContext.js (новый): обёртка над AsyncLocalStorage для service/userId. * services/aiUsage.js (новый): log() с расчётом cost_rub по справочнику цен Anthropic/OpenAI (USD/1M токенов или USD/картинку) × markup × usd_rub. Никогда не бросает наружу — ошибки логирования не валят генерацию. * services/ai.js: chat() и image() обёрнуты в try/catch с aiUsage.log(). Все вышестоящие функции (generatePost, transformPost, generateTopics, generateArticle) идут через chat() — покрытие 100%. * services/covers.js: 3 call sites хукнуты (generateCoverViaResponses, generateCoverViaImagesEndpoint, generateCoverViaImageGenerations). Заодно поправлен process.env.AI_MODEL_IMAGE_VIA_RESPONSES → config.ai.imageModelViaResponses. Удалён мёртвый код после throw. * services/postImages.js: 1 call site (/responses + image_generation tool). Тот же fix env-ref. * services/articleAutoSeries.js: 1 call site (/messages, Anthropic-формат). usage.input_tokens / output_tokens корректно парсится. Verify: * Все 11 строк app_settings ai_providers заполнены значениями из .env. * Горячая перезагрузка: PUT /api/settings/admin/AI_TEXT_MODEL_POST -> runtime обновился без рестарта PM2. * Live test: ai.chat() через aiprimetech, 118+49 токенов, 0.0414 ₽ — запись в ai_usage с правильным service/provider/model. * GET /api/usage/summary возвращает корректные totals + breakdown.
33 lines
1.1 KiB
JavaScript
33 lines
1.1 KiB
JavaScript
// Контекст вызова — пробрасывается через AsyncLocalStorage, чтобы
|
|
// сервисы (ai.js, covers.js, postImages.js, articleAutoSeries.js) могли
|
|
// логировать расход не получая лишних параметров.
|
|
//
|
|
// Заполняется middleware в index.js (на основе URL-префикса) либо
|
|
// явно из воркеров/cron: aiContext.run({ service: 'zeropost-blog', userId: 0 }, fn).
|
|
|
|
const { AsyncLocalStorage } = require('node:async_hooks');
|
|
const als = new AsyncLocalStorage();
|
|
|
|
function run(ctx, fn) {
|
|
return als.run({ ...ctx }, fn);
|
|
}
|
|
|
|
function get() {
|
|
return als.getStore() || { service: 'zeropost-unknown', userId: null };
|
|
}
|
|
|
|
function service() {
|
|
return get().service;
|
|
}
|
|
|
|
function userId() {
|
|
return get().userId;
|
|
}
|
|
|
|
// Удобно для воркеров: оборачивает функцию в run() с заданным контекстом.
|
|
function withContext(ctx, fn) {
|
|
return (...args) => als.run({ ...ctx }, () => fn(...args));
|
|
}
|
|
|
|
module.exports = { run, get, service, userId, withContext };
|