Add customProviders modal

This commit is contained in:
Alexey Koshelev
2025-02-25 16:38:02 +03:00
parent 371dcdf4f9
commit 47d7328a2f
5 changed files with 411 additions and 18 deletions

View File

@ -0,0 +1,55 @@
<!--
(c) Copyright Ascensio System SIA 2020
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Custom providers</title>
<script type="text/javascript" src="https://onlyoffice.github.io/sdkjs-plugins/v1/plugins.js"></script>
<script type="text/javascript" src="https://onlyoffice.github.io/sdkjs-plugins/v1/plugins-ui.js"></script>
<script type="text/javascript" src="components/Tooltip/script.js"></script>
<script type="text/javascript" src="components/ListView/script.js"></script>
<link rel="stylesheet" href="https://onlyoffice.github.io/sdkjs-plugins/v1/plugins.css">
<link rel="stylesheet" href="./resources/styles/common.css">
<link rel="stylesheet" href="./resources/styles/customProviders.css">
<link rel="stylesheet" href="components/Tooltip/style.css">
<link rel="stylesheet" href="components/ListView/style.css">
</head>
<body class="noselect">
<div id="label-row">
<label class="i18n">Connected custom providers</label>
<img id="alert-icon" src="resources/icons/error-small/error.png"/>
</div>
<div id="list-row">
<div id="providers-list" class="empty">
<!-- Dynamic render items -->
</div>
<div id="buttons-block">
<button id="add-btn" class="btn-text-default">
<img src="resources/icons/light/btn-zoomup.png" class="icon"/>
</button>
<button id="delete-btn" class="btn-text-default" disabled>
<img src="resources/icons/light/btn-remove.png" class="icon"/>
</button>
</div>
</div>
<label id="error-label" class="hide"></label>
<input id="file-input" type="file" multiple accept=".js"/>
<script type="text/javascript" src="scripts/customProviders.js"></script>
</body>
</html>

View File

@ -0,0 +1,90 @@
body {
display: flex;
flex-direction: column;
}
#label-row {
display: flex;
align-items: center;
margin-bottom: 4px;
}
#label-row label {
font-weight: bold;
}
#alert-icon {
cursor: pointer;
margin-left: 4px;
width: 20px;
height: 20px;
opacity: 0.6;
filter: grayscale(1);
}
#alert-inner-popover {
line-height: 14px;
}
#list-row {
display: flex;
justify-content: space-between;
flex: 1;
position: relative;
height: calc(100% - (20px + 4px) - (12px + 4px));
}
#providers-list {
margin-right: 4px;
}
#providers-list.empty {
cursor: pointer;
}
#providers-list.empty:hover {
opacity: 0.75;
}
#providers-list.dragged {
opacity: 0.75;
border-style: dashed;
}
#providers-list .item {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#buttons-block {
display: flex;
flex-direction: column;
}
#buttons-block button {
padding: 0;
height: auto;
display: flex;
}
#buttons-block button img {
width: 20px;
height: 20px;
margin: 1px;
}
#buttons-block button:not(:first-child) {
margin-top: 2px;
}
#file-input {
display: none;
}
#error-label {
height: 12px;
color: #F62211;
margin-top: 4px;
transition: 0.15s opacity;
}
#error-label.hide {
opacity: 0;
}

View File

@ -1,6 +1,7 @@
let settingsWindow = null;
let aiModelsListWindow = null;
let aiModelEditWindow = null;
let customProvidersWindow = null;
let summarizationWindow = null;
let initCounter = 0;
@ -39,6 +40,9 @@ window.Asc.plugin.button = function(id, windowId) {
aiModelEditWindow.close();
aiModelEditWindow = null;
}
} else if (customProvidersWindow && windowId === customProvidersWindow.id) {
customProvidersWindow.close();
customProvidersWindow = null;
} else {
window.Asc.plugin.executeMethod("CloseWindow", [windowId]);
}
@ -51,6 +55,7 @@ window.Asc.plugin.onThemeChanged = function(theme) {
aiModelsListWindow && aiModelsListWindow.command('onThemeChanged', theme);
aiModelEditWindow && aiModelEditWindow.command('onThemeChanged', theme);
summarizationWindow && summarizationWindow.command('onThemeChanged', theme);
customProvidersWindow && customProvidersWindow.command('onThemeChanged', theme);
};
/**
@ -144,29 +149,73 @@ function onOpenEditModal(data) {
],
isModal : true,
EditorsSupport : ["word", "slide", "cell"],
size : [320, 330]
size : [320, 370]
};
aiModelEditWindow = new window.Asc.PluginWindow();
aiModelEditWindow.attachEvent("onChangeModel", function(model){
AI.Storage.addModel(model);
aiModelEditWindow.close();
aiModelEditWindow = null;
});
aiModelEditWindow.attachEvent("onGetModels", async function(provider){
let models = await AI.getModels(provider);
aiModelEditWindow && aiModelEditWindow.command("onGetModels", models);
});
aiModelEditWindow.attachEvent("onInit", function() {
aiModelEditWindow.command('onModelInfo', {
model : data.model ? AI.Storage.getModelByName(data.model.name) : null,
providers : AI.serializeProviders()
if (!aiModelEditWindow) {
aiModelEditWindow = new window.Asc.PluginWindow();
aiModelEditWindow.attachEvent("onChangeModel", function(model){
AI.Storage.addModel(model);
aiModelEditWindow.close();
aiModelEditWindow = null;
});
});
aiModelEditWindow.attachEvent("onGetModels", async function(provider){
let models = await AI.getModels(provider);
aiModelEditWindow && aiModelEditWindow.command("onGetModels", models);
});
aiModelEditWindow.attachEvent("onInit", function() {
aiModelEditWindow.command('onModelInfo', {
model : data.model ? AI.Storage.getModelByName(data.model.name) : null,
providers : AI.serializeProviders()
});
});
aiModelEditWindow.attachEvent('onOpenCustomProvidersModal', onOpenCustomProvidersModal);
}
aiModelEditWindow.show(variation);
}
/**
* CUSTOM PROVIDERS WINDOW
*/
function onOpenCustomProvidersModal() {
let variation = {
url : 'customProviders.html',
description : window.Asc.plugin.tr('Custom providers'),
isVisual : true,
buttons : [
{ text: window.Asc.plugin.tr('Back'), primary: false },
],
isModal : true,
EditorsSupport : ["word", "slide", "cell"],
size : [350, 222]
};
if (!customProvidersWindow) {
customProvidersWindow = new window.Asc.PluginWindow();
customProvidersWindow.attachEvent("onInit", function() {
//TODO: Add set custom providers
customProvidersWindow.command('onSetCustomProvider', []);
});
customProvidersWindow.attachEvent("onAddCustomProvider", function(item) {
console.log('Add custom provider', item);
// If the provider addition is successful, then call "onAddCustomProvider"
// Else call "onErrorCustomProvider"
let isError = false;
if(isError) {
customProvidersWindow.command('onErrorCustomProvider');
} else {
customProvidersWindow.command('onAddCustomProvider', item);
}
});
customProvidersWindow.attachEvent("onDeleteCustomProvider", function(item) {
console.log('Delete custom provider', item);
});
}
customProvidersWindow.show(variation);
}
/**
* SUMMARIZATION WINDOW
*/

View File

@ -0,0 +1,190 @@
let providersList = new ListView(document.getElementById('providers-list'), {
emptyText: 'The list is empty, press + to add the file',
renderItem: function(item) {
var itemEl = document.createElement('div');
itemEl.classList.add('item');
itemEl.innerText = item.name;
return itemEl;
}
});
let scrollbarList = new PerfectScrollbar("#providers-list", {});
providersList.on('select', function() {
deleteBtnEl.removeAttribute('disabled');
});
providersList.on('deselect', function() {
deleteBtnEl.setAttribute('disabled', true);
});
providersList.on('set', function() {
if(providersList.getList().length > 0) {
providersList.$el.classList.remove('empty');
}
});
providersList.on('add', function() {
if(providersList.getList().length > 0) {
providersList.$el.classList.remove('empty');
}
});
providersList.on('delete', function() {
if(providersList.getList().length == 0) {
providersList.$el.classList.add('empty');
}
});
providersList.$el.addEventListener('click', function() {
if(providersList.getList().length == 0) {
fileInputEl.click();
}
});
providersList.$el.addEventListener('dragover', function(e) {
e.stopPropagation();
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
providersList.$el.classList.add('dragged');
});
providersList.$el.addEventListener('dragleave', function() {
providersList.$el.classList.remove('dragged');
});
providersList.$el.addEventListener('drop', function(e) {
e.stopPropagation();
e.preventDefault();
handlerChangeFileInput(e.dataTransfer.files);
providersList.$el.classList.remove('dragged');
});
let addBtnEl = document.getElementById('add-btn');
addBtnEl.addEventListener('click', function() {
fileInputEl.click();
});
let deleteBtnEl = document.getElementById('delete-btn');
deleteBtnEl.addEventListener('click', function() {
if(providersList.getSelected()) {
window.Asc.plugin.sendToPlugin("onDeleteCustomProvider", providersList.getSelected());
providersList.delete(providersList.getSelected());
}
});
let fileInputEl = document.getElementById('file-input');
fileInputEl.addEventListener("change", function (event) {
handlerChangeFileInput(event.target.files);
});
let errorLabelEl = document.getElementById('error-label');
let errorLabelTimeout = null;
// TODO: Change path for template file
let templateFilePath = document.currentScript.src + '/../engine/providers/provider.js';
window.Asc.plugin.init = function() {
window.Asc.plugin.sendToPlugin("onInit");
window.Asc.plugin.attachEvent("onSetCustomProvider", function(list) {
providersList.set(list);
});
window.Asc.plugin.attachEvent("onAddCustomProvider", function(item) {
providersList.add({name: item.name});
});
window.Asc.plugin.attachEvent("onErrorCustomProvider", function(item) {
showErrorLabel('Error adding provider from file, please try again');
});
window.Asc.plugin.attachEvent("onThemeChanged", onThemeChanged);
}
window.Asc.plugin.onThemeChanged = onThemeChanged;
window.Asc.plugin.onTranslate = function () {
let elements = document.querySelectorAll('.i18n');
elements.forEach(function(element) {
element.innerText = window.Asc.plugin.tr(element.innerText);
});
providersList.setEmptyText(window.Asc.plugin.tr(providersList.options.emptyText));
new Tooltip(document.getElementById('alert-icon'), {
renderInner: function() {
let innerEl = document.createElement("div");
innerEl.id = 'alert-inner-popover';
let textEl = document.createElement("div");
textEl.innerText = window.Asc.plugin.tr('Enter the configuration for the AI model API in JS format. Provide the model name, endpoint URLs, and headers.');
let linkEl = document.createElement("a");
linkEl.id = 'popover-link';
linkEl.href = templateFilePath;
linkEl.download = 'providerTemplate.js';
linkEl.innerText = window.Asc.plugin.tr('Download template');
linkEl.addEventListener('click', function() {
console.log('Download template');
});
innerEl.appendChild(textEl);
innerEl.appendChild(linkEl);
return innerEl;
},
xAnchor: 'left',
align: 'left',
yOffset: 4,
width: 150,
hasShadow: true,
keepAliveOnHover: true,
delay: 200,
hideDelay: 200
});
};
function handlerChangeFileInput(files) {
for (let i = 0; i < files.length; i++) {
let file = files[i];
if (file.name.lastIndexOf(".js") == file.name.length - 3) {
let reader = new FileReader();
reader.readAsText(file);
reader.onload = function(e) {
let fileContent = e.target.result;
window.Asc.plugin.sendToPlugin("onAddCustomProvider", {
name: file.name,
content: fileContent
});
};
reader.onerror = function(e) {
showErrorLabel('Error adding provider from file, please try again');
};
} else {
showErrorLabel('Invalid file format, please upload the .js file');
}
}
}
function showErrorLabel(text) {
clearTimeout(errorLabelTimeout);
errorLabelEl.innerText = window.Asc.plugin.tr(text);
errorLabelEl.classList.remove('hide');
errorLabelTimeout = setTimeout(function() {
errorLabelEl.classList.add('hide');
}, 10 * 1000);
}
function hideErrorLabel() {
clearTimeout(errorLabelTimeout);
errorLabelEl.classList.add('hide');
}
function onThemeChanged(theme) {
window.Asc.plugin.onThemeChangedBase(theme);
themeType = theme.type || 'light';
let classes = document.body.className.split(' ');
classes.forEach(function(className) {
if (className.indexOf('theme-') != -1) {
document.body.classList.remove(className);
}
});
document.body.classList.add(theme.name);
document.body.classList.add('theme-type-' + themeType);
let btnIcons = document.getElementsByClassName('icon');
for (let i = 0; i < btnIcons.length; i++) {
let icon = btnIcons[i];
let src = icon.getAttribute('src');
let newSrc = src.replace(/(icons\/)([^\/]+)(\/)/, '$1' + themeType + '$3');
icon.setAttribute('src', newSrc);
}
}

View File

@ -95,5 +95,14 @@
"Chatbot": "Chatbot",
"Ask AI a question about something...": "Ask AI a question about something...",
"This field is required": "This field is required"
"This field is required": "This field is required",
"Custom providers": "Custom providers",
"Back": "Back",
"Connected custom providers": "Connected custom providers",
"The list is empty, press + to add the file": "The list is empty, press + to add the file",
"Enter the configuration for the AI model API in JS format. Provide the model name, endpoint URLs, and headers.": "Enter the configuration for the AI model API in JS format. Provide the model name, endpoint URLs, and headers.",
"Download template": "Download template",
"Invalid file format, please upload the .js file": "Invalid file format, please upload the .js file",
"Error adding provider from file, please try again": "Error adding provider from file, please try again"
}