Merge pull request 'fix/ai-settings2' (#36) from fix/ai-settings2 into release/v9.0.0

Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/server/pulls/36
This commit is contained in:
Oleg Korshul
2025-06-11 12:57:52 +00:00
5 changed files with 46 additions and 45 deletions

View File

@ -15,7 +15,7 @@
"version": 3,
"timeout": "30s",
"allowedCorsOrigins": [
"https://onlyoffice.github.io"
"https://onlyoffice.github.io", "https://onlyoffice-plugins.github.io"
],
"pluginDir" : "../branding/info/ai",
"cache": {

View File

@ -6,7 +6,7 @@
"pluginDir" : "/var/www/onlyoffice/documentserver/server/info/ai"
},
"runtimeConfig": {
"filePath": "/etc/onlyoffice/documentserver/runtime.json"
"filePath": "/var/www/onlyoffice/documentserver/../Data/runtime.json"
},
"storage": {
"fs": {

View File

@ -62,14 +62,16 @@ const nodeCache = new utils.NodeCache(cfgAiApiCache);
*/
function handleCorsHeaders(req, res, ctx, handleOptions = true) {
const requestOrigin = req.headers.origin;
const tenAiApiAllowedOrigins = ctx.getCfg('aiSettings.allowedCorsOrigins', cfgAiApiAllowedOrigins);
// If no origin in request or allowed origins list is empty, do nothing
if (!requestOrigin || cfgAiApiAllowedOrigins.length === 0) {
if (!requestOrigin || tenAiApiAllowedOrigins.length === 0) {
return false;
}
// If the origin is in our allowed list
if (cfgAiApiAllowedOrigins.includes(requestOrigin)) {
if (tenAiApiAllowedOrigins.includes(requestOrigin)) {
res.setHeader('Access-Control-Allow-Origin', requestOrigin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Vary', 'Origin'); // Important when using dynamic origin
@ -113,7 +115,9 @@ async function proxyRequest(req, res) {
try {
ctx.logger.info('Start proxyRequest');
await ctx.initTenantCache();
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
const tenAiApiTimeout = ctx.getCfg('aiSettings.timeout', cfgAiApiTimeout);
const tenAiApi = ctx.getCfg('aiSettings', cfgAiSettings);
// 1. Handle CORS preflight (OPTIONS) requests if necessary
@ -134,8 +138,8 @@ async function proxyRequest(req, res) {
// Configure timeout options for the request
const timeoutOptions = {
connectionAndInactivity: cfgAiApiTimeout,
wholeCycle: cfgAiApiTimeout
connectionAndInactivity: tenAiApiTimeout,
wholeCycle: tenAiApiTimeout
};
// Get request size limit if configured

View File

@ -134,6 +134,7 @@ const AIIntegration = {
/**
* Load the current view in the iframe
* @returns {void} No return value
*/
loadCurrentView() {
if (this.isCollapsed) return;
@ -144,27 +145,34 @@ const AIIntegration = {
if (!iframeSettings || !iframeEdit || !iframeList) return;
// Switch iframe visibility based on current view
// Hide all iframes first
[iframeSettings, iframeEdit, iframeList].forEach(iframe => {
iframe.classList.add('hidden');
});
// Show loading overlay
const overlay = document.getElementById('ai-overlay');
if (overlay) {
overlay.classList.add('loading');
}
// Always reassign src to force reload, then show the appropriate iframe
switch (this.currentView) {
case 'settings':
iframeSettings.src = 'ai/settings.html';
iframeSettings.classList.remove('hidden');
iframeEdit.classList.add('hidden');
iframeList.classList.add('hidden');
break;
case 'aiModelEdit':
iframeSettings.classList.add('hidden');
iframeEdit.src = 'ai/aiModelEdit.html';
iframeEdit.classList.remove('hidden');
iframeList.classList.add('hidden');
break;
case 'aiModelsList':
iframeSettings.classList.add('hidden');
iframeEdit.classList.add('hidden');
iframeList.src = 'ai/aiModelsList.html';
iframeList.classList.remove('hidden');
break;
default:
iframeSettings.src = 'ai/settings.html';
iframeSettings.classList.remove('hidden');
iframeEdit.classList.add('hidden');
iframeList.classList.add('hidden');
}
this.updateControls();
@ -205,11 +213,11 @@ const AIIntegration = {
/**
* Handle iframe load event
* @returns {void} No return value
*/
onIframeLoad() {
this.loadedIframes++;
const overlay = document.getElementById('ai-overlay');
if (overlay && this.loadedIframes === this.totalIframes) {
if (overlay) {
// Hide loading overlay after a short delay
setTimeout(() => {
overlay.classList.remove('loading');

View File

@ -40,6 +40,8 @@
var urlSettings = 'plugin/settings';
var urlModels = 'plugin/models';
var urlConfig = 'config';
var tmpModel = null;
// Initialize AI functionality when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
@ -152,6 +154,20 @@
name: 'onThemeChanged',
data: {type:'light', name: 'theme-light'}
}, source);
const providers = Object.keys(settings.providers).map(function(key) { return settings.providers[key]; });
var model = {id: "", name: "", provider: "", capabilities: 0};
if (tmpModel) {
model = settings.models.find(function(model) { return model.name === tmpModel.name; });
tmpModel = null;
}
var data = {
model : model,
providers : providers
}
sendMessageToSettings({
name: 'onModelInfo',
data: data
}, source);
}
/**
@ -241,35 +257,8 @@
}
break;
case 'onOpenEditModal':
tmpModel = message.data.model;
AIIntegration.navigateToView('aiModelEdit');
var aiModelEditWindow = findIframeBySrcPart('aiModelEdit');
if(aiModelEditWindow) {
const providers = Object.keys(settings.providers).map(function(key) { return settings.providers[key]; });
var model = {id: "", name: "", provider: "", capabilities: 0};
if (message.data.model) {
model = settings.models.find(function(model) { return model.name === message.data.model.name; });
}
var data = {
model : model,
providers : providers
}
sendMessageToSettings({
name: 'onProvidersUpdate',
data: data
}, aiModelEditWindow.contentWindow);
sendMessageToSettings({
name: 'onModelInfo',
data: data
}, aiModelEditWindow.contentWindow);
// sendMessageToSettings({
// name: 'onGetModels',
// data: {error: 1, models: []}
// }, aiModelEditWindow.contentWindow);
}
break;
case 'onDeleteAiModel':
for (var i = 0; i < settings.models.length; i++) {