From 2da0f0c1b35f4913757366f85579c35d33c11911 Mon Sep 17 00:00:00 2001 From: Sergey Konovalov Date: Thu, 22 May 2025 12:46:04 +0300 Subject: [PATCH] [feature] Fix bugs in aiProxyHandler.js --- Common/config/default.json | 7 +- Common/sources/utils.js | 2 + DocService/sources/DocsCoServer.js | 2 + DocService/sources/ai/aiEngineWrapper.js | 3 +- DocService/sources/ai/aiProxyHandler.js | 90 +++++++++++------------- 5 files changed, 55 insertions(+), 49 deletions(-) diff --git a/Common/config/default.json b/Common/config/default.json index f5b0f00d..c7985285 100644 --- a/Common/config/default.json +++ b/Common/config/default.json @@ -97,7 +97,12 @@ "timeout": "30s", "allowedCorsOrigins": [ "https://onlyoffice.github.io" - ] + ], + "cache": { + "enable": true, + "ttl": 300, + "cachesize": 1000 + } }, "log": { "filePath": "", diff --git a/Common/sources/utils.js b/Common/sources/utils.js index 95644f3b..9f0c0c4e 100644 --- a/Common/sources/utils.js +++ b/Common/sources/utils.js @@ -1359,3 +1359,5 @@ function deepMergeObjects(target, ...sources) { } exports.isObject = isObject; exports.deepMergeObjects = deepMergeObjects; +exports.NodeCache = NodeCache;//todo via require + diff --git a/DocService/sources/DocsCoServer.js b/DocService/sources/DocsCoServer.js index 8d7caa8a..a3a87168 100644 --- a/DocService/sources/DocsCoServer.js +++ b/DocService/sources/DocsCoServer.js @@ -3937,6 +3937,8 @@ exports.install = function(server, callbackFunction) { ); }); }); + + void aiProxyHandler.getPluginSettings(operationContext.global); }; exports.setLicenseInfo = async function(globalCtx, data, original) { tenantManager.setDefLicense(data, original); diff --git a/DocService/sources/ai/aiEngineWrapper.js b/DocService/sources/ai/aiEngineWrapper.js index 988732c0..906b636f 100644 --- a/DocService/sources/ai/aiEngineWrapper.js +++ b/DocService/sources/ai/aiEngineWrapper.js @@ -45,7 +45,8 @@ const cfgAiApiTimeout = config.get('ai-api.timeout'); function setCtx(ctx) { sandbox.ctx = ctx; - sandbox.console = ctx.logger; + console.log = ctx.logger.debug.bind(ctx.logger);//todo make default in logger + console.error = ctx.logger.error.bind(ctx.logger); } // Set up the environment for the client-side engine.js diff --git a/DocService/sources/ai/aiProxyHandler.js b/DocService/sources/ai/aiProxyHandler.js index 038fd233..a8931c54 100644 --- a/DocService/sources/ai/aiProxyHandler.js +++ b/DocService/sources/ai/aiProxyHandler.js @@ -40,13 +40,16 @@ const operationContext = require('./../../../Common/sources/operationContext'); const commonDefines = require('./../../../Common/sources/commondefines'); const docsCoServer = require('./../DocsCoServer'); -// Import the new aiEngineWrapper module -const aiEngineWrapper = require('./aiEngineWrapper'); +// Import the new aiEngine module +const aiEngine = require('./aiEngineWrapper'); const cfgAiApiAllowedOrigins = config.get('ai-api.allowedCorsOrigins'); const cfgAiApiTimeout = config.get('ai-api.timeout'); +const cfgAiApiCache = config.get('ai-api.cache'); const cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser'); +const AI = aiEngine.AI; +const nodeCache = new utils.NodeCache(cfgAiApiCache); /** * Helper function to set CORS headers if the request origin is allowed * @@ -136,37 +139,21 @@ async function proxyRequest(req, res) { // Get request size limit if configured const sizeLimit = 10 * 1024 * 1024; // Default to 10MB - // Create a copy of the headers from the request - const headers = { ...body.headers }; - - // Get API key based on the target URL - const aiApi = config.get('ai-api'); - let apiKey; + let providerHeaders; // Determine which API key to use based on the target URL if (body.target) { // Find the provider that matches the target URL - const matchedProvider = aiApi.providers.find(provider => - body.target.includes(provider.url)); - - if (matchedProvider) { - apiKey = matchedProvider.key; + for (let providerName in AI.Providers) {//todo try for of + if (body.target.includes(AI.Providers[providerName].url)) { + providerHeaders = AI._getHeaders(AI.Providers[providerName]); + break; + } } } - - // Add authorization header if API key is available - if (apiKey) { - if (headers['x-api-key']) { - headers['x-api-key'] = apiKey; - } else if (body.target.includes('key=')) { - body.target = body.target.replace('key=', `key=${apiKey}&`); - } else { - headers['Authorization'] = `Bearer ${apiKey}`; - } - } else { - throw new Error('No API key found for the target URL'); - } - + // Merge key in headers + const headers = { ...body.headers, ...providerHeaders }; + // Create request parameters object const requestParams = { method: body.method, @@ -210,7 +197,7 @@ async function proxyRequest(req, res) { } catch (error) { ctx.logger.error(`AI API request error: %s`, error); - if(error.response){ + if (error.response){ // Set the response headers to match the target response res.set(error.response.headers); @@ -269,27 +256,26 @@ function processActions(ctx, actions) { * * @param {Object} ctx - Operation context * @param {Object} provider - Provider configuration - * @param {boolean} includeDisabled - Whether to include disabled models * @returns {Promise} Processed provider with models or null if provider is invalid */ -async function processProvider(ctx, provider, includeDisabled) { +async function processProvider(ctx, provider) { const logger = ctx.logger; if (!provider.url || !provider.key) { return null; } let engineModels = []; + let engineModelsUI = []; try { if (provider.url && provider.key) { - aiEngineWrapper.setCtx(ctx); - // logger.info("processProvider %j", AI.Providers); - aiEngineWrapper.AI.Providers[provider.name].key = provider.key; + AI.Providers[provider.name].key = provider.key; // Call getModels from engine.js - const result = await aiEngineWrapper.AI.getModels(provider); - logger.info(`Got ${JSON.stringify(result)} from AI.getModels for ${provider.name}`); + aiEngine.setCtx(ctx); + const result = await AI.getModels(provider); // Process result - if (!result.error && Array.isArray(result.models)) { - engineModels = result.models; + if (AI.TmpProviderForModels.models) { + engineModels = AI.TmpProviderForModels.models; + engineModelsUI = AI.TmpProviderForModels.modelsUI; } } } catch (error) { @@ -300,7 +286,8 @@ async function processProvider(ctx, provider, includeDisabled) { name: provider.name, url: provider.url, key: "", - models: engineModels + models: engineModels, + modelsUI: engineModelsUI }; } @@ -308,16 +295,22 @@ async function processProvider(ctx, provider, includeDisabled) { * Retrieves all AI models from the configuration and dynamically from providers * * @param {Object} ctx - Operation context - * @param {boolean} [includeDisabled=false] - Whether to include disabled providers in the result * @returns {Promise} Object containing providers and their models along with action configurations */ -async function getPluginSettings(ctx, includeDisabled = false) { +async function getPluginSettings(ctx) { const logger = ctx.logger; logger.info('Starting getPluginSettings'); + let res = nodeCache.get(ctx.tenant); + if (res) { + ctx.logger.debug('getPluginSettings from cache'); + return res; + } const result = { + version: 3, actions: {}, providers: {}, - models: [] + models: [], + customProviders: {} }; try { // Get AI API configuration @@ -326,8 +319,8 @@ async function getPluginSettings(ctx, includeDisabled = false) { if (aiApi?.providers && Array.isArray(aiApi.providers)) { // Create an array of promises for each provider const providerPromises = aiApi.providers - .filter(provider => includeDisabled || provider.enable !== false || !provider.key || !provider.url) - .map(provider => processProvider(ctx, provider, includeDisabled)); + .filter(provider => provider.enable !== false || !provider.key || !provider.url) + .map(provider => processProvider(ctx, provider)); try { let providers = await Promise.allSettled(providerPromises); @@ -340,8 +333,9 @@ async function getPluginSettings(ctx, includeDisabled = false) { for(let i = 0; i < providers.length; i++) { const provider = providers[i].value; totalModels += provider.models.length; - result.providers[provider.name] = provider - result.models.push(...provider.models); + result.models.push(...provider.modelsUI); + delete provider.modelsUI;//todo remove + result.providers[provider.name] = provider; } logger.info(`Successfully processed ${providerCount} providers with a total of ${totalModels} models`); @@ -354,11 +348,13 @@ async function getPluginSettings(ctx, includeDisabled = false) { if (aiApi?.actions && typeof aiApi.actions === 'object') { result.actions = processActions(ctx, aiApi.actions); } - - logger.info('Completed getPluginSettings successfully'); + nodeCache.set(ctx.tenant, result); } catch (error) { logger.error('Error retrieving AI models from config:', error); } + finally { + logger.info('Completed getPluginSettings'); + } return result; }