forked from admin/zeropost-engine
AI config migration to app_settings + ai_usage logging
* 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.
This commit is contained in:
+55
-7
@@ -1,21 +1,30 @@
|
||||
require('dotenv').config();
|
||||
|
||||
module.exports = {
|
||||
// settings подгружаем лениво, чтобы избежать циклической зависимости
|
||||
// (settings → config/db → config/index). require здесь синхронный, но settings.getMany async.
|
||||
let _settingsSvc = null;
|
||||
function settingsSvc() {
|
||||
if (!_settingsSvc) _settingsSvc = require('../services/settings');
|
||||
return _settingsSvc;
|
||||
}
|
||||
|
||||
const config = {
|
||||
port: parseInt(process.env.ZEROPOST_PORT || 3030),
|
||||
|
||||
// AI gateway (OpenAI-compatible: aiprimetech.io)
|
||||
// AI gateway — значения мутируются reloadAi() из app_settings.
|
||||
// Дефолты ниже — последний fallback, если БД и .env пустые.
|
||||
ai: {
|
||||
baseUrl: process.env.AI_BASE_URL || 'https://aiprimetech.io/v1',
|
||||
apiKey: process.env.AI_API_KEY,
|
||||
imageApiKey: process.env.AI_IMAGE_API_KEY || process.env.AI_API_KEY,
|
||||
imageBaseUrl: process.env.AI_IMAGE_BASE_URL || process.env.AI_BASE_URL || 'https://aiprimetech.io/v1',
|
||||
apiKey: process.env.AI_API_KEY || null,
|
||||
imageApiKey: process.env.AI_IMAGE_API_KEY || process.env.AI_API_KEY || null,
|
||||
imageBaseUrl: process.env.AI_IMAGE_BASE_URL || 'https://api.aiguoguo199.com/v1',
|
||||
imageModel: process.env.AI_MODEL_IMAGE || 'gpt-image-1-mini',
|
||||
// Per-task model selection — tune cost vs quality here
|
||||
imageModelViaResponses: process.env.AI_MODEL_IMAGE_VIA_RESPONSES || 'gpt-5.5',
|
||||
models: {
|
||||
post: process.env.AI_MODEL_POST || 'claude-haiku-4-5-20251001',
|
||||
article: process.env.AI_MODEL_ARTICLE || 'claude-sonnet-4-6',
|
||||
topics: process.env.AI_MODEL_TOPICS || 'claude-haiku-4-5-20251001',
|
||||
image: process.env.AI_MODEL_IMAGE || 'gpt-image-1',
|
||||
image: process.env.AI_MODEL_IMAGE || 'gpt-image-1-mini',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -32,3 +41,42 @@ module.exports = {
|
||||
},
|
||||
internalSecret: process.env.INTERNAL_SECRET || 'dev-secret-change-in-prod',
|
||||
};
|
||||
|
||||
/**
|
||||
* Перегружает блок ai из app_settings.
|
||||
* Приоритет: app_settings.value → process.env[НОВОЕ_ИМЯ] → process.env[СТАРОЕ_ИМЯ] → дефолт.
|
||||
* Старое имя ENV нужно для безопасной миграции — если БД пуста, .env держит прод на ногах.
|
||||
* Мутирует config.ai в месте, чтобы все require'нувшие config продолжали видеть актуальные значения.
|
||||
*/
|
||||
async function reloadAi() {
|
||||
const keys = [
|
||||
'AI_TEXT_BASE_URL', 'AI_TEXT_API_KEY',
|
||||
'AI_TEXT_MODEL_POST', 'AI_TEXT_MODEL_ARTICLE', 'AI_TEXT_MODEL_TOPICS',
|
||||
'AI_IMAGE_BASE_URL', 'AI_IMAGE_API_KEY',
|
||||
'AI_IMAGE_MODEL', 'AI_IMAGE_MODEL_VIA_RESPONSES',
|
||||
];
|
||||
let s = {};
|
||||
try {
|
||||
s = await settingsSvc().getMany(keys);
|
||||
} catch (err) {
|
||||
console.warn('[config.reloadAi] DB unavailable, using ENV fallback:', err.message);
|
||||
}
|
||||
const pick = (dbKey, envOld, def) =>
|
||||
(s[dbKey] && s[dbKey].trim()) || process.env[dbKey] || process.env[envOld] || def;
|
||||
|
||||
config.ai.baseUrl = pick('AI_TEXT_BASE_URL', 'AI_BASE_URL', 'https://aiprimetech.io/v1');
|
||||
config.ai.apiKey = pick('AI_TEXT_API_KEY', 'AI_API_KEY', null);
|
||||
config.ai.imageBaseUrl= pick('AI_IMAGE_BASE_URL', 'AI_IMAGE_BASE_URL', 'https://api.aiguoguo199.com/v1');
|
||||
config.ai.imageApiKey = pick('AI_IMAGE_API_KEY', 'AI_IMAGE_API_KEY', config.ai.apiKey);
|
||||
config.ai.imageModel = pick('AI_IMAGE_MODEL', 'AI_MODEL_IMAGE', 'gpt-image-1-mini');
|
||||
config.ai.imageModelViaResponses = pick('AI_IMAGE_MODEL_VIA_RESPONSES', 'AI_MODEL_IMAGE_VIA_RESPONSES', 'gpt-5.5');
|
||||
config.ai.models.post = pick('AI_TEXT_MODEL_POST', 'AI_MODEL_POST', 'claude-haiku-4-5-20251001');
|
||||
config.ai.models.article = pick('AI_TEXT_MODEL_ARTICLE', 'AI_MODEL_ARTICLE', 'claude-sonnet-4-6');
|
||||
config.ai.models.topics = pick('AI_TEXT_MODEL_TOPICS', 'AI_MODEL_TOPICS', 'claude-haiku-4-5-20251001');
|
||||
config.ai.models.image = pick('AI_IMAGE_MODEL', 'AI_MODEL_IMAGE', 'gpt-image-1-mini');
|
||||
|
||||
return config.ai;
|
||||
}
|
||||
|
||||
config.reloadAi = reloadAi;
|
||||
module.exports = config;
|
||||
|
||||
Reference in New Issue
Block a user