[feature] Add info/plugin handler
@ -5,99 +5,19 @@
|
||||
"port": "8125",
|
||||
"prefix": "ds."
|
||||
},
|
||||
"ai-api": {
|
||||
"providers": [
|
||||
{
|
||||
"enable": true,
|
||||
"name": "OpenAI",
|
||||
"url": "https://api.openai.com",
|
||||
"key": "",
|
||||
"models": []
|
||||
},
|
||||
{
|
||||
"enable": true,
|
||||
"name": "Google Gemini",
|
||||
"url": "https://generativelanguage.googleapis.com",
|
||||
"key": "",
|
||||
"models": []
|
||||
},
|
||||
{
|
||||
"enable": true,
|
||||
"name": "Anthropic",
|
||||
"url": "https://api.anthropic.com",
|
||||
"key": "",
|
||||
"models": []
|
||||
},
|
||||
{
|
||||
"enable": true,
|
||||
"name": "Azure OpenAI",
|
||||
"url": "https://{region}.api.cognitive.microsoft.com",
|
||||
"key": "",
|
||||
"models": []
|
||||
},
|
||||
{
|
||||
"enable": true,
|
||||
"name": "Cohere",
|
||||
"url": "https://api.cohere.ai",
|
||||
"key": "",
|
||||
"models": []
|
||||
},
|
||||
{
|
||||
"enable": true,
|
||||
"name": "Mistral AI",
|
||||
"url": "https://api.mistral.ai",
|
||||
"key": "",
|
||||
"models": []
|
||||
},
|
||||
{
|
||||
"enable": true,
|
||||
"name": "Aleph Alpha",
|
||||
"url": "https://api.aleph-alpha.com",
|
||||
"key": "",
|
||||
"models": []
|
||||
},
|
||||
{
|
||||
"enable": true,
|
||||
"name": "Stability AI",
|
||||
"url": "https://api.stability.ai",
|
||||
"key": "",
|
||||
"models": [
|
||||
"Stable Diffusion",
|
||||
"Stable Image Core",
|
||||
"Stable Image Ultra"
|
||||
]
|
||||
}
|
||||
],
|
||||
"aiSettings": {
|
||||
"actions": {
|
||||
"Chat": {
|
||||
"name": "Ask AI",
|
||||
"icon": "ask-ai",
|
||||
"model": "llama-3.2-90b-vision-preview",
|
||||
"capabilities": 1
|
||||
},
|
||||
"Summarization": {
|
||||
"name": "Summarization",
|
||||
"icon": "summarization",
|
||||
"model": "llama3.2:latest",
|
||||
"capabilities": 1
|
||||
},
|
||||
"Translation": {
|
||||
"name": "Translation",
|
||||
"icon": "translation",
|
||||
"model": "gemini-1.5-pro-latest",
|
||||
"capabilities": 1
|
||||
},
|
||||
"TextAnalyze": {
|
||||
"name": "Text analysis",
|
||||
"icon": "",
|
||||
"model": "claude-3-sonnet-20240229",
|
||||
"capabilities": 1
|
||||
}
|
||||
},
|
||||
"models": [
|
||||
],
|
||||
"providers": {
|
||||
},
|
||||
"version": 3,
|
||||
"timeout": "30s",
|
||||
"allowedCorsOrigins": [
|
||||
"https://onlyoffice.github.io"
|
||||
],
|
||||
"pluginDir" : "../branding/info/ai",
|
||||
"cache": {
|
||||
"enable": true,
|
||||
"ttl": 300,
|
||||
|
||||
@ -41,7 +41,10 @@ const path = require('path');
|
||||
const vm = require('vm');
|
||||
|
||||
// Configuration constants
|
||||
const cfgAiApiTimeout = config.get('ai-api.timeout');
|
||||
const cfgAiApiTimeout = config.get('aiSettings.timeout');
|
||||
const cfgAiApiModels = config.get('aiSettings.models');
|
||||
const cfgAiApiActions = config.get('aiSettings.actions');
|
||||
const cfgAiPluginDir = config.get('aiSettings.pluginDir');
|
||||
|
||||
function setCtx(ctx) {
|
||||
sandbox.ctx = ctx;
|
||||
@ -53,6 +56,14 @@ function setCtx(ctx) {
|
||||
const sandbox = {
|
||||
ctx: null,
|
||||
window: {AI: {}},
|
||||
Asc: {
|
||||
plugin: {
|
||||
tr: function(text) {
|
||||
// Just return the original text in the stub
|
||||
return text;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Implementation of fetch that delegates to utils.httpRequest
|
||||
@ -98,7 +109,7 @@ const sandbox = {
|
||||
};
|
||||
|
||||
// Initialize minimal AI object with required functionality
|
||||
sandbox.AI = sandbox.window.AI;
|
||||
const AI = sandbox.AI = sandbox.window.AI;
|
||||
setCtx(operationContext.global);
|
||||
|
||||
/**
|
||||
@ -106,7 +117,7 @@ setCtx(operationContext.global);
|
||||
*/
|
||||
function loadInternalProviders() {
|
||||
// Add simple provider loading logic
|
||||
const enginePath = path.join(__dirname, 'engine', 'providers', 'internal');
|
||||
const enginePath = path.join(cfgAiPluginDir, 'scripts/engine/providers/internal');
|
||||
|
||||
try {
|
||||
// Read providers directory
|
||||
@ -138,21 +149,97 @@ function loadInternalProviders() {
|
||||
sandbox.ctx.logger.error('Error loading internal providers:', error);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Simple loadInternalProviders implementation
|
||||
*/
|
||||
function fillConfigObjects() {
|
||||
AI.Models = cfgAiApiModels;
|
||||
for (let i in cfgAiApiActions)
|
||||
{
|
||||
if (AI.Actions[i] && cfgAiApiActions[i].model) {
|
||||
AI.Actions[i].model = cfgAiApiActions[i].model;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load engine.js
|
||||
let engineCode = '';
|
||||
engineCode += fs.readFileSync(path.join(__dirname, 'engine', 'storage.js'), 'utf8');
|
||||
engineCode += fs.readFileSync(path.join(__dirname, 'engine', 'local_storage.js'), 'utf8');
|
||||
engineCode += fs.readFileSync(path.join(__dirname, 'engine', 'providers', 'base.js'), 'utf8');
|
||||
engineCode += fs.readFileSync(path.join(__dirname, 'engine', 'providers', 'provider.js'), 'utf8');
|
||||
engineCode += fs.readFileSync(path.join(__dirname, 'engine', 'engine.js'), 'utf8');
|
||||
engineCode += fs.readFileSync(path.join(cfgAiPluginDir, 'scripts/engine/storage.js'), 'utf8');
|
||||
engineCode += fs.readFileSync(path.join(cfgAiPluginDir, 'scripts/engine/local_storage.js'), 'utf8');
|
||||
engineCode += fs.readFileSync(path.join(cfgAiPluginDir, 'scripts/engine/providers/base.js'), 'utf8');
|
||||
engineCode += fs.readFileSync(path.join(cfgAiPluginDir, 'scripts/engine/providers/provider.js'), 'utf8');
|
||||
engineCode += fs.readFileSync(path.join(cfgAiPluginDir, 'scripts/engine/engine.js'), 'utf8');
|
||||
vm.runInNewContext(engineCode, sandbox);
|
||||
|
||||
//start from engine/register.js
|
||||
(function() {
|
||||
const AI = sandbox.AI;
|
||||
const Asc = sandbox.Asc;
|
||||
|
||||
AI.ActionType = {
|
||||
Chat : "Chat",
|
||||
Summarization : "Summarization",
|
||||
Translation : "Translation",
|
||||
TextAnalyze : "TextAnalyze",
|
||||
ImageGeneration : "ImageGeneration",
|
||||
OCR : "OCR",
|
||||
Vision : "Vision"
|
||||
};
|
||||
|
||||
AI.Actions = {};
|
||||
|
||||
function ActionUI(name, icon, modelId, capabilities) {
|
||||
this.name = name || "";
|
||||
this.icon = icon || "";
|
||||
this.model = modelId || "";
|
||||
this.capabilities = (capabilities === undefined) ? AI.CapabilitiesUI.Chat : capabilities;
|
||||
}
|
||||
|
||||
AI.Actions[AI.ActionType.Chat] = new ActionUI("Chatbot", "ask-ai");
|
||||
AI.Actions[AI.ActionType.Summarization] = new ActionUI("Summarization", "summarization");
|
||||
AI.Actions[AI.ActionType.Translation] = new ActionUI("Translation", "translation");
|
||||
AI.Actions[AI.ActionType.TextAnalyze] = new ActionUI("Text analysis", "text-analysis-ai");
|
||||
AI.Actions[AI.ActionType.ImageGeneration] = new ActionUI("Image generation", "image-ai", "", AI.CapabilitiesUI.Image);
|
||||
AI.Actions[AI.ActionType.OCR] = new ActionUI("OCR", "text-analysis-ai", "", AI.CapabilitiesUI.Vision);
|
||||
AI.Actions[AI.ActionType.Vision] = new ActionUI("Vision", "vision-ai", "", AI.CapabilitiesUI.Vision);
|
||||
|
||||
AI.ActionsGetKeys = function()
|
||||
{
|
||||
return [
|
||||
AI.ActionType.Chat,
|
||||
AI.ActionType.Summarization,
|
||||
AI.ActionType.Translation,
|
||||
AI.ActionType.TextAnalyze,
|
||||
AI.ActionType.ImageGeneration,
|
||||
AI.ActionType.OCR,
|
||||
AI.ActionType.Vision
|
||||
];
|
||||
};
|
||||
|
||||
AI.ActionsGetSorted = function()
|
||||
{
|
||||
let keys = AI.ActionsGetKeys();
|
||||
let count = keys.length;
|
||||
let actions = new Array(count);
|
||||
for (let i = 0; i < count; i++)
|
||||
{
|
||||
let src = AI.Actions[keys[i]];
|
||||
actions[i] = {
|
||||
id : keys[i],
|
||||
name : Asc.plugin.tr(src.name),
|
||||
icon : src.icon,
|
||||
model : src.model,
|
||||
capabilities : src.capabilities
|
||||
}
|
||||
}
|
||||
return actions;
|
||||
};
|
||||
//end from engine/register.js
|
||||
})();
|
||||
|
||||
sandbox.AI.loadInternalProviders = loadInternalProviders;
|
||||
loadInternalProviders();
|
||||
|
||||
|
||||
|
||||
fillConfigObjects();
|
||||
|
||||
exports.setCtx = setCtx;
|
||||
exports.AI = sandbox.AI;
|
||||
|
||||
@ -43,9 +43,9 @@ const docsCoServer = require('./../DocsCoServer');
|
||||
// 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 cfgAiApiAllowedOrigins = config.get('aiSettings.allowedCorsOrigins');
|
||||
const cfgAiApiTimeout = config.get('aiSettings.timeout');
|
||||
const cfgAiApiCache = config.get('aiSettings.cache');
|
||||
const cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser');
|
||||
|
||||
const AI = aiEngine.AI;
|
||||
@ -216,41 +216,6 @@ async function proxyRequest(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process AI actions from configuration
|
||||
*
|
||||
* @param {Object} ctx - Operation context
|
||||
* @param {Object} actions - The actions from configuration
|
||||
* @returns {Object} Processed actions object
|
||||
*/
|
||||
function processActions(ctx, actions) {
|
||||
const logger = ctx.logger;
|
||||
|
||||
if (!actions || typeof actions !== 'object') {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
const processedActions = Object.entries(actions).reduce((acc, [key, value]) => {
|
||||
if (value) {
|
||||
acc[key] = {
|
||||
name: value.name || key,
|
||||
icon: value.icon || '',
|
||||
model: value.model || '',
|
||||
capabilities: Array.isArray(value.capabilities) ? value.capabilities : []
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
logger.info(`Processed ${Object.keys(processedActions).length} AI actions`);
|
||||
return processedActions;
|
||||
} catch (error) {
|
||||
logger.error('Error processing AI actions:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a single AI provider and its models
|
||||
*
|
||||
@ -261,17 +226,17 @@ function processActions(ctx, actions) {
|
||||
async function processProvider(ctx, provider) {
|
||||
const logger = ctx.logger;
|
||||
|
||||
if (!provider.url || !provider.key) {
|
||||
if (!provider.url) {
|
||||
return null;
|
||||
}
|
||||
let engineModels = [];
|
||||
let engineModelsUI = [];
|
||||
try {
|
||||
if (provider.url && provider.key) {
|
||||
// Call getModels from engine.js
|
||||
if (provider.key) {
|
||||
AI.Providers[provider.name].key = provider.key;
|
||||
// Call getModels from engine.js
|
||||
aiEngine.setCtx(ctx);
|
||||
const result = await AI.getModels(provider);
|
||||
await AI.getModels(provider);
|
||||
// Process result
|
||||
if (AI.TmpProviderForModels.models) {
|
||||
engineModels = AI.TmpProviderForModels.models;
|
||||
@ -314,41 +279,61 @@ async function getPluginSettings(ctx) {
|
||||
};
|
||||
try {
|
||||
// Get AI API configuration
|
||||
const aiApi = config.get('ai-api');
|
||||
const aiApi = config.get('aiSettings');
|
||||
// Process providers and their models if configuration exists
|
||||
if (aiApi?.providers && Array.isArray(aiApi.providers)) {
|
||||
if (aiApi?.providers && typeof aiApi.providers === 'object') {
|
||||
// Create an array of promises for each provider
|
||||
const providerPromises = aiApi.providers
|
||||
.filter(provider => provider.enable !== false || !provider.key || !provider.url)
|
||||
.map(provider => processProvider(ctx, provider));
|
||||
// const providerPromises = aiApi.providers
|
||||
// .filter(provider => provider.enable !== false || !provider.key || !provider.url)
|
||||
// .map(provider => processProvider(ctx, provider));
|
||||
|
||||
try {
|
||||
let providers = await Promise.allSettled(providerPromises);
|
||||
providers = providers.filter(provider => provider.status === 'fulfilled' && provider.value && provider.value.name && provider.value.models?.length > 0);
|
||||
// try {
|
||||
// let providers = await Promise.allSettled(providerPromises);
|
||||
// // providers = providers.filter(provider => provider.status === 'fulfilled' && provider.value && provider.value.name && provider.value.models?.length > 0);
|
||||
// providers = providers.filter(provider => provider.status === 'fulfilled' && provider.value && provider.value.name);
|
||||
|
||||
const providerCount = providers.length;
|
||||
let totalModels = 0;
|
||||
// Convert providers array to object by provider name
|
||||
result.providers = {};
|
||||
for(let i = 0; i < providers.length; i++) {
|
||||
const provider = providers[i].value;
|
||||
totalModels += provider.models.length;
|
||||
result.models.push(...provider.modelsUI);
|
||||
delete provider.modelsUI;//todo remove
|
||||
// const providerCount = providers.length;
|
||||
// let totalModels = 0;
|
||||
// // Convert providers array to object by provider name
|
||||
// result.providers = {};
|
||||
// for(let i = 0; i < providers.length; i++) {
|
||||
// const provider = providers[i].value;
|
||||
// totalModels += provider.models.length;
|
||||
// // 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`);
|
||||
// } catch (error) {
|
||||
// logger.error('Error resolving provider promises:', error);
|
||||
// }
|
||||
if (true) {
|
||||
const providers = AI.serializeProviders();
|
||||
for (let i = 0; i < providers.length; i++) {
|
||||
const provider = providers[i];
|
||||
const cfgProvider = aiApi.providers[provider.name];
|
||||
if (cfgProvider) {
|
||||
//todo clone
|
||||
provider.key = cfgProvider.key;
|
||||
}
|
||||
result.providers[provider.name] = provider;
|
||||
}
|
||||
|
||||
logger.info(`Successfully processed ${providerCount} providers with a total of ${totalModels} models`);
|
||||
} catch (error) {
|
||||
logger.error('Error resolving provider promises:', error);
|
||||
}
|
||||
}
|
||||
// Process AI actions
|
||||
if (aiApi?.models && typeof aiApi.models === 'object') {
|
||||
// result.actions = aiApi.actions;
|
||||
result.models = AI.Storage.serializeModels();
|
||||
}
|
||||
|
||||
// Process AI actions
|
||||
if (aiApi?.actions && typeof aiApi.actions === 'object') {
|
||||
result.actions = processActions(ctx, aiApi.actions);
|
||||
// result.actions = aiApi.actions;
|
||||
result.actions = AI.ActionsGetSorted();
|
||||
}
|
||||
nodeCache.set(ctx.tenant, result);
|
||||
result.version = aiApi.version;
|
||||
// nodeCache.set(ctx.tenant, result);
|
||||
} catch (error) {
|
||||
logger.error('Error retrieving AI models from config:', error);
|
||||
}
|
||||
@ -358,7 +343,36 @@ async function getPluginSettings(ctx) {
|
||||
return result;
|
||||
}
|
||||
|
||||
async function requestSettings(req, res) {
|
||||
const ctx = new operationContext.Context();
|
||||
ctx.initFromRequest(req);
|
||||
try {
|
||||
await ctx.initTenantCache();
|
||||
const result = await getPluginSettings(ctx);
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
ctx.logger.error('getSettings error: %s', error.stack);
|
||||
res.sendStatus(400);
|
||||
}
|
||||
}
|
||||
|
||||
async function requestModels(req, res) {
|
||||
const ctx = new operationContext.Context();
|
||||
ctx.initFromRequest(req);
|
||||
try {
|
||||
await ctx.initTenantCache();
|
||||
let body = JSON.parse(req.body);
|
||||
let models = await AI.getModels(body);
|
||||
res.json(models);
|
||||
} catch (error) {
|
||||
ctx.logger.error('getModels error: %s', error.stack);
|
||||
res.sendStatus(400);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
proxyRequest,
|
||||
getPluginSettings
|
||||
getPluginSettings,
|
||||
requestSettings,
|
||||
requestModels
|
||||
};
|
||||
|
||||
@ -37,6 +37,7 @@ const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const tenantManager = require('../../../Common/sources/tenantManager');
|
||||
const operationContext = require('../../../Common/sources/operationContext');
|
||||
const aiProxyHandler = require('../ai/aiProxyHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@ -49,17 +50,32 @@ router.get('/', async (req, res) => {
|
||||
try {
|
||||
ctx.initFromRequest(req);
|
||||
await ctx.initTenantCache();
|
||||
ctx.logger.debug('config get start');
|
||||
if (tenantManager.isMultitenantMode(ctx) && !tenantManager.isDefaultTenant(ctx)) {
|
||||
//todo
|
||||
}
|
||||
|
||||
let configPath = path.join(process.env.NODE_CONFIG_DIR, 'local.json');
|
||||
result = await readFile(configPath);
|
||||
try {
|
||||
result = await readFile(configPath, {encoding: 'utf8'});
|
||||
} catch (e) {
|
||||
ctx.logger.debug('config get error: %s', e.stack);
|
||||
}
|
||||
//todo get default setting in separate request
|
||||
let config = JSON.parse(result);
|
||||
if (!(config.aiSettings && Object.keys(config.aiSettings?.actions).length > 0)) {
|
||||
let pluginSettings = await aiProxyHandler.getPluginSettings(ctx);
|
||||
config.aiSettings = pluginSettings;
|
||||
result = JSON.stringify(config);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
ctx.logger.error('baseurl error: %s', error.stack);
|
||||
ctx.logger.error('config get error: %s', error.stack);
|
||||
}
|
||||
finally {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.send(result);
|
||||
ctx.logger.debug('config end');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -238,6 +238,8 @@ docsCoServer.install(server, () => {
|
||||
});
|
||||
app.get('/info/info.json', utils.checkClientIp, docsCoServer.licenseInfo);
|
||||
app.use('/info/config', utils.checkClientIp, configRouter);
|
||||
app.get('/info/plugin/settings', utils.checkClientIp, aiProxyHandler.requestSettings);
|
||||
app.post('/info/plugin/models', utils.checkClientIp, rawFileParser, aiProxyHandler.requestModels);
|
||||
app.put('/internal/cluster/inactive', utils.checkClientIp, docsCoServer.shutdown);
|
||||
app.delete('/internal/cluster/inactive', utils.checkClientIp, docsCoServer.shutdown);
|
||||
app.get('/internal/connections/edit', docsCoServer.getEditorConnectionsCount);
|
||||
|
||||
91
branding/info/ai/CHANGELOG.md
Normal file
@ -0,0 +1,91 @@
|
||||
# Change Log
|
||||
|
||||
## 1.0.0
|
||||
|
||||
* Initial release.
|
||||
|
||||
## 1.0.1
|
||||
|
||||
* Add new model.
|
||||
|
||||
## 1.0.2
|
||||
|
||||
* Change plugin structure. Now it's non visual plugin and it can work by context menu.
|
||||
* Add new model for chat variation.
|
||||
|
||||
## 1.1.0
|
||||
|
||||
* Commont improvements.
|
||||
|
||||
## 1.1.1
|
||||
|
||||
* Add new languages for translating and add translations for it.
|
||||
* Change plugin type from "system" to "background".
|
||||
* Increase minimal editor version to "7.5.0".
|
||||
* Change type for chat window (now it's not a modal window).
|
||||
|
||||
## 1.1.2
|
||||
|
||||
* Disable plugin for IE.
|
||||
|
||||
## 1.1.3
|
||||
|
||||
* Add notification, that service isn't enable in user region (if we can't load list of models).
|
||||
|
||||
## 1.1.4
|
||||
* Change list of models for custom requests (add new models and remove old).
|
||||
* Change default model for request by context menu (now we use gpt-3.5-turbo-16k model for chat and gpt-4 for other request).
|
||||
* Add new functions: "Fix spelling & grammar", "Rewrite differently", "Make longer", "Make shorter", "Make simpler" (it has restriction by gpt-4 model: 8k tokens)
|
||||
|
||||
## 2.0.0
|
||||
* The plugin has been completely redesigned.
|
||||
|
||||
## 2.1.0
|
||||
* Bug fix.
|
||||
|
||||
## 2.1.1
|
||||
* Bug fix.
|
||||
|
||||
## 2.1.2
|
||||
* Fix add provider.
|
||||
|
||||
## 2.1.3
|
||||
* Bug fix. Remove v1 suffix for endpoints.
|
||||
|
||||
## 2.1.4
|
||||
* Add proxy for together.ai.
|
||||
* Add Groc as internal provider.
|
||||
* Bug fix
|
||||
|
||||
## 2.1.5
|
||||
* Bug fix
|
||||
|
||||
## 2.2.0
|
||||
* Refactoring
|
||||
|
||||
## 2.2.1
|
||||
* Bug fix
|
||||
|
||||
## 2.2.2
|
||||
* Add xAI as internal provider.
|
||||
|
||||
## 2.2.3
|
||||
* Fix translations.
|
||||
|
||||
## 2.2.4
|
||||
* Refactoring chat. Add docked mode for chat window.
|
||||
|
||||
## 2.2.5
|
||||
* Bug fix
|
||||
|
||||
## 2.2.6
|
||||
* Add interface for system role detection.
|
||||
* Fixed the work of providers antropic and gemini when working with a system role.
|
||||
* Fix bug with custom functions in spreadsheets editor.
|
||||
|
||||
## 2.2.7
|
||||
* Add image actions.
|
||||
* Add Stability AI provider.
|
||||
|
||||
## 2.2.8
|
||||
* Fix image actions. Add "OCR" and "Image to text" support.
|
||||
114
branding/info/ai/aiModelEdit.html
Normal file
@ -0,0 +1,114 @@
|
||||
<!--
|
||||
(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>Edit action</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="scripts/engine/providers/base.js"></script>
|
||||
<script type="text/javascript" src="components/Tooltip/script.js"></script>
|
||||
<script type="text/javascript" src="../js/plugin-stub.js"></script>
|
||||
<script src="vendor/jquery/jquery-3.7.1.min.js"></script>
|
||||
<script src="vendor/select2-4.0.6-rc.1/dist/js/select2.js"></script>
|
||||
<link rel="stylesheet" href="https://onlyoffice.github.io/sdkjs-plugins/v1/plugins.css">
|
||||
<link rel="stylesheet" href="vendor/select2-4.0.6-rc.1/dist/css/select2.css"/>
|
||||
<link rel="stylesheet" href="./resources/styles/common.css">
|
||||
<link rel="stylesheet" href="./resources/styles/aiModelEdit.css">
|
||||
<link rel="stylesheet" href="components/Tooltip/style.css">
|
||||
</head>
|
||||
<body class="noselect">
|
||||
<div class="section">
|
||||
<div class="form-vertical-item">
|
||||
<div class="section-label">
|
||||
<label class="d-flex-center">
|
||||
<span class="i18n">Model name</span>
|
||||
<span class="required-mark">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<input id="name-input" type="text" class="form-control" maxlength="80"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-label" id="provider-section-label">
|
||||
<label class="i18n">Provider</label>
|
||||
</div>
|
||||
<div class="form-horizontal-item">
|
||||
<label class="d-flex-center">
|
||||
<span class="i18n">Name</span>
|
||||
<span class="required-mark">*</span>
|
||||
</label>
|
||||
<select id="provider-name-cmb" class="form-control"></select>
|
||||
</div>
|
||||
<div class="form-horizontal-item">
|
||||
<label class="d-flex-center">
|
||||
<span class="i18n">URL</span>
|
||||
<span class="required-mark">*</span>
|
||||
</label>
|
||||
<input id="provider-url-input" type="text" class="form-control"/>
|
||||
</div>
|
||||
<div class="form-horizontal-item">
|
||||
<label class="i18n">Key</label>
|
||||
<input id="provider-key-input" type="text" class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="form-vertical-item">
|
||||
<div class="section-label">
|
||||
<label class="d-flex-center">
|
||||
<span class="i18n">Model</span>
|
||||
<span class="required-mark">*</span>
|
||||
</label>
|
||||
<div id="update-models-row">
|
||||
<div id="update-models-loader-container" class="asc-loader-container"></div>
|
||||
<button id="update-models-btn" class="action-btn i18n">Update models list</button>
|
||||
<img id="update-models-error" class="icon" src="resources/icons/light/error.png" style="display: none;"/>
|
||||
</div>
|
||||
</div>
|
||||
<select id="model-name-cmb" class="form-control"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section" style="margin-bottom: 0;">
|
||||
<div class="form-vertical-item">
|
||||
<div class="section-label">
|
||||
<label class="i18n">Use model for</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-horizontal-item" id="block-use-for" style="margin-bottom: 0;">
|
||||
<div id="use-for-text"></div>
|
||||
<div id="use-for-image"></div>
|
||||
<div id="use-for-embeddings"></div>
|
||||
<div id="use-for-audio"></div>
|
||||
<div id="use-for-moderations"></div>
|
||||
<div id="use-for-realtime"></div>
|
||||
<div id="use-for-code"></div>
|
||||
<div id="use-for-vision"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="custom-providers-button">
|
||||
<label class="i18n">
|
||||
Custom providers
|
||||
</label>
|
||||
</div>
|
||||
<script type="text/javascript" src="scripts/aiModelEdit.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
50
branding/info/ai/aiModelsList.html
Normal file
@ -0,0 +1,50 @@
|
||||
<!--
|
||||
(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>AI Models list</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/ListView/script.js"></script>
|
||||
<script type="text/javascript" src="../js/plugin-stub.js"></script>
|
||||
<script src="vendor/jquery/jquery-3.7.1.min.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="./components/ListView/style.css">
|
||||
<link rel="stylesheet" href="./resources/styles/aiModelsList.css">
|
||||
</head>
|
||||
<body class="noselect">
|
||||
<div id="ai-models-list" class="list">
|
||||
<!-- 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="edit-btn" class="btn-text-default">
|
||||
<img src="resources/icons/light/btn-edit.png" class="icon"/>
|
||||
</button>
|
||||
<button id="delete-btn" class="btn-text-default">
|
||||
<img src="resources/icons/light/btn-remove.png" class="icon"/>
|
||||
</button>
|
||||
</div>
|
||||
<script type="text/javascript" src="scripts/aiModelsList.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
87
branding/info/ai/chat.html
Normal file
@ -0,0 +1,87 @@
|
||||
<!--
|
||||
(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>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>OpenAI</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>
|
||||
<link rel="stylesheet" href="https://onlyoffice.github.io/sdkjs-plugins/v1/plugins.css">
|
||||
<script src="vendor/jquery/jquery-3.7.1.min.js"></script>
|
||||
<script type="text/javascript" src="vendor/GPT-3-Encoder/encoder.js"></script>
|
||||
<script src="vendor/md/markdown-it.js"></script>
|
||||
<script type="text/javascript" src="components/Tooltip/script.js"></script>
|
||||
<link rel="stylesheet" href="components/Tooltip/style.css">
|
||||
<link rel="stylesheet" href="./resources/styles/chat.css">
|
||||
<script type="text/javascript" src="scripts/chat.js"></script>
|
||||
</head>
|
||||
<body style="display: flex;">
|
||||
<div id="chat_window" class="chat_window hidden">
|
||||
<div id="chat_wrapper" class="form-control empty">
|
||||
<div id="chat" class="noselect">
|
||||
<div id="start_panel">
|
||||
<div id="welcome_text">
|
||||
<br/>
|
||||
<br/>
|
||||
<span class="i18n">I'm here to assist you with all your text creation and editing needs. Feel free to ask me anything about your document or anything else that's on your mind.</span>
|
||||
<br/>
|
||||
<br/>
|
||||
<span class="i18n">Let’s make your content shine together!</span>
|
||||
</div>
|
||||
<div id="welcome_buttons_list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tokens_info" class="tokens_info">
|
||||
<div class="form-control div_title_chat hidden">
|
||||
<span class="i18n">Maximum 16000 tokens are available.</span>
|
||||
<br>
|
||||
<span class="i18n">For work with this model we should save chat history and sent it into a request.</span>
|
||||
<span class="i18n">But we have a limit on the number of tokens in the request.</span>
|
||||
<span class="i18n">That's why sometimes you should clear your chat history.</span>
|
||||
<div class="text_align_end">
|
||||
<button id="clear_history" style="margin-top: 5px;" class="form-control btn-text-default i18n">Clear history</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text_align_end">
|
||||
<span class="i18n">Tokens in the request about: </span>
|
||||
<span id="cur_tokens">0</span>
|
||||
</div>
|
||||
<div class="text_align_end">
|
||||
<span class="i18n">Total tokens are used in last request: </span>
|
||||
<span id="total_tokens">0</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="attached_text_wrapper" class="hidden">
|
||||
<div id="attached_text"></div>
|
||||
<img id="attached_text_close" draggable="false" src="resources/icons/light/close.png" class="icon"/>
|
||||
</div>
|
||||
<div id="input_message_wrapper">
|
||||
<textarea id="input_message" rows="1" class="form-control" spellcheck="false"></textarea>
|
||||
<div id="input_message_submit" class="noselect">
|
||||
<img src="resources/icons/light/btn-demote.png" draggable="false" class="icon"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="loader-container" class="loader hidden"></div>
|
||||
<div id="div_err" class="hidden div_error err_background">
|
||||
<label class="lb_err i18n" style="font-weight: bold;">Error:</label>
|
||||
<label id="lb_err" class="lb_err"></label>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
175
branding/info/ai/components/ListView/script.js
Normal file
@ -0,0 +1,175 @@
|
||||
function ListView($el, options) {
|
||||
this._init = function() {
|
||||
var defaults = {
|
||||
emptyText: '',
|
||||
renderItem: function(item, index) {
|
||||
var itemEl = document.createElement('div');
|
||||
itemEl.classList.add('item');
|
||||
itemEl.innerText = item.label;
|
||||
return itemEl;
|
||||
}
|
||||
};
|
||||
this.options = Object.assign({}, defaults, options);
|
||||
|
||||
$el.classList.add('list-view');
|
||||
this.$el = $el;
|
||||
this.list = [];
|
||||
this.selectedItem = null;
|
||||
this.events = {
|
||||
set: [],
|
||||
add: [],
|
||||
edit: [],
|
||||
delete: [],
|
||||
select: [],
|
||||
deselect: [],
|
||||
};
|
||||
|
||||
this._render();
|
||||
};
|
||||
|
||||
this._render = function() {
|
||||
var me = this;
|
||||
this.$el.innerHTML = '';
|
||||
if(this.list.length > 0) {
|
||||
this.list.forEach(function(item, index) {
|
||||
let itemEl = me.options.renderItem(item, index);
|
||||
itemEl.addEventListener('click', function() {
|
||||
me.setSelected(index);
|
||||
});
|
||||
me.$el.appendChild(itemEl);
|
||||
});
|
||||
} else if(this.options.emptyText) {
|
||||
let emptyEl = document.createElement('div');
|
||||
emptyEl.innerText = this.options.emptyText;
|
||||
emptyEl.classList.add('empty-text');
|
||||
this.$el.appendChild(emptyEl);
|
||||
}
|
||||
};
|
||||
|
||||
this._trigger = function(event, data) {
|
||||
if(this.events[event]) {
|
||||
this.events[event].forEach(function(callback) {
|
||||
callback(data);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.getList = function() {
|
||||
return this.list;
|
||||
};
|
||||
|
||||
this.set = function(list) {
|
||||
this.list = list;
|
||||
this._render();
|
||||
if(list.length > 0) {
|
||||
this.setSelected(0);
|
||||
} else {
|
||||
this.deselect();
|
||||
}
|
||||
scrollbarList.update();
|
||||
scrollbarList.update();
|
||||
|
||||
this._trigger('set', list);
|
||||
};
|
||||
|
||||
this.add = function(item) {
|
||||
this.list.push(item);
|
||||
this._render();
|
||||
this.setSelected(this.list.length - 1);
|
||||
this.$el.scrollTop = this.$el.scrollHeight;
|
||||
scrollbarList.update();
|
||||
scrollbarList.update();
|
||||
|
||||
this._trigger('add', item);
|
||||
};
|
||||
|
||||
this.edit = function(item) {
|
||||
var findedItem = this.list.filter(function(el) {
|
||||
return el.id == item.id;
|
||||
})[0];
|
||||
|
||||
if(!findedItem) return;
|
||||
|
||||
for (var key in item) {
|
||||
if (findedItem[key]) {
|
||||
findedItem[key] = item[key];
|
||||
}
|
||||
}
|
||||
this._render();
|
||||
|
||||
this._trigger('edit', findedItem, item);
|
||||
};
|
||||
|
||||
this.delete = function(item) {
|
||||
var indexDeletedItem = this.list.indexOf(item);
|
||||
if(indexDeletedItem != -1) {
|
||||
this.list = this.list.filter(function(el) {
|
||||
return el != item;
|
||||
});
|
||||
this._render();
|
||||
|
||||
if(this.list.length == 0) {
|
||||
this.deselect();
|
||||
} else if(indexDeletedItem < this.list.length) {
|
||||
this.setSelected(indexDeletedItem);
|
||||
} else {
|
||||
this.setSelected(this.list.length - 1);
|
||||
}
|
||||
|
||||
this._trigger('delete', item);
|
||||
}
|
||||
};
|
||||
|
||||
this.deleteByIndex = function(index) {
|
||||
if(!this.list[index]) return;
|
||||
|
||||
this.delete(this.list[index]);
|
||||
};
|
||||
|
||||
this.setSelected = function(index) {
|
||||
if(this.list.length == 0) return;
|
||||
|
||||
if(index == -1) {
|
||||
index = this.list.length - 1;
|
||||
}
|
||||
if(!this.list[index]) return;
|
||||
|
||||
this.deselect();
|
||||
this.selectedItem = this.list[index];
|
||||
this.$el.children[index].classList.add('selected');
|
||||
|
||||
this._trigger('select', this.selectedItem);
|
||||
};
|
||||
|
||||
this.getSelected = function() {
|
||||
return this.selectedItem;
|
||||
};
|
||||
|
||||
this.deselect = function() {
|
||||
var previouslySelectedItem = this.selectedItem;
|
||||
this.selectedItem = null;
|
||||
|
||||
var itemsEl = this.$el.getElementsByClassName('item');
|
||||
for (var i = 0; i < itemsEl.length; i++) {
|
||||
itemsEl[i].classList.remove('selected');
|
||||
}
|
||||
|
||||
if (previouslySelectedItem) {
|
||||
this._trigger('deselect', previouslySelectedItem);
|
||||
}
|
||||
};
|
||||
|
||||
this.setEmptyText = function(text) {
|
||||
this.options.emptyText = text;
|
||||
this._render();
|
||||
};
|
||||
|
||||
this.on = function(event, callback) {
|
||||
if(this.events[event]) {
|
||||
this.events[event].push(callback);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this._init();
|
||||
}
|
||||
85
branding/info/ai/components/ListView/style.css
Normal file
@ -0,0 +1,85 @@
|
||||
.list-view {
|
||||
flex: 1;
|
||||
border: 1px solid;
|
||||
position: relative;
|
||||
}
|
||||
.list-view .item {
|
||||
padding: 6px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
|
||||
.list-view .empty-text {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Themes style for Models List */
|
||||
body.theme-classic-light .list-view {
|
||||
border-color: #cfcfcf;
|
||||
}
|
||||
body.theme-light .list-view,
|
||||
body.theme-gray .list-view {
|
||||
border-color: #c0c0c0;
|
||||
}
|
||||
body.theme-dark .list-view {
|
||||
border-color: #666;
|
||||
}
|
||||
body.theme-contrast-dark .list-view {
|
||||
border-color: #696969;
|
||||
}
|
||||
|
||||
/* Themes style for Item Models List */
|
||||
body.theme-classic-light .list-view .item {
|
||||
border-color: #cfcfcf;
|
||||
}
|
||||
body.theme-classic-light .list-view .item:hover {
|
||||
background-color: #d8dadc;
|
||||
color: #444;
|
||||
}
|
||||
body.theme-classic-light .list-view .item.selected {
|
||||
background-color: #7d858c;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
body.theme-light .list-view .item,
|
||||
body.theme-gray .list-view .item {
|
||||
border-color: #c0c0c0;
|
||||
}
|
||||
body.theme-light .list-view .item:hover,
|
||||
body.theme-gray .list-view .item:hover {
|
||||
background-color: #e0e0e0;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
body.theme-light .list-view .item.selected,
|
||||
body.theme-gray .list-view .item.selected {
|
||||
background-color: #cbcbcb;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
body.theme-dark .list-view .item {
|
||||
border-color: #666;
|
||||
}
|
||||
body.theme-dark .list-view .item:hover {
|
||||
background-color: #555;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
body.theme-dark .list-view .item.selected {
|
||||
background-color: #707070;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
body.theme-contrast-dark .list-view .item {
|
||||
border-color: #696969;
|
||||
}
|
||||
body.theme-contrast-dark .list-view .item:hover {
|
||||
background-color: #424242;
|
||||
color: #e8e8e8;
|
||||
}
|
||||
body.theme-contrast-dark .list-view .item.selected {
|
||||
background-color: #666666;
|
||||
color: #e8e8e8;
|
||||
}
|
||||
128
branding/info/ai/components/Tooltip/script.js
Normal file
@ -0,0 +1,128 @@
|
||||
function Tooltip(targetEl, options) {
|
||||
this._init = function() {
|
||||
var me = this;
|
||||
var defaults = {
|
||||
renderInner: null,
|
||||
text: '',
|
||||
xAnchor: 'center',
|
||||
yAnchor: 'bottom',
|
||||
xOffset: 0,
|
||||
yOffset: 3,
|
||||
align: 'center',
|
||||
width: null,
|
||||
hasShadow: false,
|
||||
keepAliveOnHover: false,
|
||||
delay: 0,
|
||||
hideDelay: 0
|
||||
};
|
||||
this.options = Object.assign({}, defaults, options);
|
||||
|
||||
|
||||
targetEl.addEventListener('mouseover', function() {
|
||||
// Set a timer for the delay before showing
|
||||
me.showTimeout = setTimeout(function() {
|
||||
me._createTooltipElement();
|
||||
if(me.options.renderInner) {
|
||||
me.tooltipEl.appendChild(me.options.renderInner());
|
||||
} else {
|
||||
me.tooltipEl.innerText = me.options.text;
|
||||
}
|
||||
if (me.options.width) {
|
||||
me.tooltipEl.style.width = me.options.width + 'px';
|
||||
me.tooltipEl.style.whiteSpace = '';
|
||||
} else {
|
||||
me.tooltipEl.style.width = '';
|
||||
me.tooltipEl.style.whiteSpace = 'nowrap';
|
||||
}
|
||||
|
||||
if (me.options.hasShadow) {
|
||||
me.tooltipEl.classList.add('has-shadow');
|
||||
} else {
|
||||
me.tooltipEl.classList.remove('has-shadow');
|
||||
}
|
||||
me._updatePosition();
|
||||
}, me.options.delay);
|
||||
});
|
||||
|
||||
targetEl.addEventListener('mouseleave', function() {
|
||||
clearTimeout(me.showTimeout); // Clear the show timer if the user leaves the element
|
||||
|
||||
// Set a timer for the delay before hiding
|
||||
me.hideTimeout = setTimeout(function() {
|
||||
me._deleteTooltipElement();
|
||||
}, me.options.hideDelay);
|
||||
});
|
||||
};
|
||||
|
||||
this._createTooltipElement = function () {
|
||||
if (!this.tooltipEl) {
|
||||
let me = this;
|
||||
this.tooltipEl = document.createElement("div");
|
||||
this.tooltipEl.classList.add("tooltip");
|
||||
|
||||
this.tooltipEl.addEventListener('mouseenter', function() {
|
||||
if (me.options.keepAliveOnHover) {
|
||||
clearTimeout(me.hideTimeout); // Clear the hide timer when hovering over the tooltip
|
||||
}
|
||||
});
|
||||
|
||||
this.tooltipEl.addEventListener('mouseleave', function() {
|
||||
if (me.options.keepAliveOnHover) {
|
||||
// Set a timer for the delay before hiding the tooltip
|
||||
me.hideTimeout = setTimeout(function() {
|
||||
me._deleteTooltipElement();
|
||||
}, me.options.hideDelay);
|
||||
}
|
||||
});
|
||||
|
||||
document.body.appendChild(this.tooltipEl);
|
||||
}
|
||||
};
|
||||
|
||||
this._deleteTooltipElement = function() {
|
||||
if (this.tooltipEl) {
|
||||
this.tooltipEl.remove();
|
||||
this.tooltipEl = null;
|
||||
}
|
||||
};
|
||||
|
||||
this._updatePosition = function() {
|
||||
var rectTooltip = this.tooltipEl.getBoundingClientRect();
|
||||
var rectEl = targetEl.getBoundingClientRect();
|
||||
var yOffset = this.options.yOffset;
|
||||
var xOffset = this.options.xOffset;
|
||||
if (this.options.align == 'right') {
|
||||
xOffset = -rectTooltip.width;
|
||||
} else if (this.options.align == 'center') {
|
||||
xOffset = -rectTooltip.width / 2;
|
||||
}
|
||||
|
||||
if (this.options.xAnchor == 'right') {
|
||||
this.tooltipEl.style.left = rectEl.right + xOffset + 'px';
|
||||
} else if (this.options.xAnchor == 'left') {
|
||||
this.tooltipEl.style.left = rectEl.left + xOffset + 'px';
|
||||
} else if (this.options.xAnchor == 'center') {
|
||||
this.tooltipEl.style.left = rectEl.left + rectEl.width / 2 + xOffset + 'px';
|
||||
}
|
||||
|
||||
if (this.options.yAnchor == 'bottom') {
|
||||
this.tooltipEl.style.top = rectEl.bottom + yOffset + 'px';
|
||||
} else if (this.options.yAnchor == 'top') {
|
||||
this.tooltipEl.style.top = rectEl.top - yOffset - rectTooltip.height + 'px';
|
||||
}
|
||||
};
|
||||
|
||||
this.getText = function() {
|
||||
return this.options.text;
|
||||
};
|
||||
|
||||
this.setText = function(text) {
|
||||
this.options.text = text;
|
||||
if(this.tooltipEl) {
|
||||
this.tooltipEl.innerText = text;
|
||||
this._updatePosition();
|
||||
}
|
||||
};
|
||||
|
||||
this._init();
|
||||
}
|
||||
29
branding/info/ai/components/Tooltip/style.css
Normal file
@ -0,0 +1,29 @@
|
||||
.tooltip {
|
||||
line-height: 12px;
|
||||
word-break: break-word;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.tooltip.has-shadow {
|
||||
box-shadow: 0px 2px 5px #00000033;
|
||||
}
|
||||
body.theme-light .tooltip,
|
||||
body.theme-gray .tooltip {
|
||||
background-color: #fff;
|
||||
border-color: #c0c0c0;
|
||||
}
|
||||
body.theme-classic-light .tooltip {
|
||||
background-color: #fff;
|
||||
border-color: #cfcfcf;
|
||||
}
|
||||
body.theme-dark .tooltip {
|
||||
background-color: #333;
|
||||
border-color: #666;
|
||||
}
|
||||
body.theme-contrast-dark .tooltip {
|
||||
background-color: #1e1e1e;
|
||||
border-color: #696969;
|
||||
}
|
||||
73
branding/info/ai/config.json
Normal file
@ -0,0 +1,73 @@
|
||||
{
|
||||
"name" : "AI",
|
||||
"nameLocale": {
|
||||
"ru": "ИИ",
|
||||
"fr": "AI",
|
||||
"es": "AI",
|
||||
"de": "AI",
|
||||
"cs": "AI",
|
||||
"zh": "AI",
|
||||
"pt-BR": "AI",
|
||||
"sr-Cyrl-RS": "AI",
|
||||
"sr-Latn-RS": "AI",
|
||||
"ja-JA": "AI",
|
||||
"sq-AL": "AI",
|
||||
"it": "IA"
|
||||
},
|
||||
|
||||
"guid" : "asc.{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}",
|
||||
"version": "2.2.8",
|
||||
"minVersion" : "8.2.0",
|
||||
|
||||
"variations" : [
|
||||
{
|
||||
"description": "Use the AI chatbot to perform tasks which involve understanding or generating natural language or code.",
|
||||
"descriptionLocale": {
|
||||
"ru": "Используйте чат-бот AI для выполнения задач, связанных с пониманием или генерацией естественного языка или кода.",
|
||||
"fr": "Utilisez le chatbot AI pour effectuer des tâches qui impliquent la compréhension ou la génération de langage naturel ou de code.",
|
||||
"es": "Utilice el chatbot AI para realizar tareas que impliquen la comprensión o generación de lenguaje natural o de código.",
|
||||
"pt-BR": "Use o chatbot AI para realizar tarefas que envolvam compreensão ou geração de linguagem ou código natural.",
|
||||
"de": "Verwenden Sie den AI-Chatbot, um Aufgaben auszuführen, die das Verstehen oder Generieren von natürlicher Sprache oder Code beinhalten.",
|
||||
"cs": "Použijte chatbota AI k provádění úkolů, který zahrnuje porozumění nebo generování přirozeného jazyka nebo kódu.",
|
||||
"zh": "使用 AI 聊天机器人完成有关理解、生成自然语言或代码的任务。",
|
||||
"sr-Cyrl-RS": "Користите AI чет робота за обављање задатака који укључују разумевање или генерисање природног језика или кода.",
|
||||
"sr-Latn-RS": "Koristite AI čet robota za obavljanje zadataka koji uključuju razumevanje ili generisanje prirodnog jezika ili koda.",
|
||||
"ja-JA": "自然言語やコードの理解または生成が必要なタスクを行うには、AIチャットボットを使用できます。",
|
||||
"sq-AL": "Shtoni dhe selektoni modele AI për detyra të ndryshme.",
|
||||
"it": "Utilizza il chatbot dell'IA per eseguire attività che implicano la comprensione o la generazione di codice o linguaggio naturale."
|
||||
},
|
||||
|
||||
"url" : "index.html",
|
||||
|
||||
"icons": "resources/%theme-type%(light|dark)/icon%scale%(default).%extension%(png)",
|
||||
|
||||
"isViewer" : false,
|
||||
"EditorsSupport" : ["word", "cell", "slide", "pdf"],
|
||||
"type" : "background",
|
||||
"initDataType" : "none",
|
||||
"buttons" : [],
|
||||
"events" : ["onContextMenuShow", "onContextMenuClick", "onToolbarMenuClick"],
|
||||
|
||||
"store": {
|
||||
"background": {
|
||||
"light" : "linear-gradient(90deg, #F9B6FF 0%, #E370EE 102.01%)",
|
||||
"dark" : "linear-gradient(90deg, #F9B6FF 0%, #E370EE 102.01%)"
|
||||
},
|
||||
"screenshots" :
|
||||
[
|
||||
"resources/store/screenshots/screen_1.png",
|
||||
"resources/store/screenshots/screen_2.png",
|
||||
"resources/store/screenshots/screen_3.png",
|
||||
"resources/store/screenshots/screen_4.png",
|
||||
"resources/store/screenshots/screen_5.png",
|
||||
"resources/store/screenshots/screen_6.png"
|
||||
],
|
||||
"icons" : {
|
||||
"light" : "resources/store/icons",
|
||||
"dark" : "resources/store/icons"
|
||||
},
|
||||
"categories": ["specAbilities", "work", "recommended"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
56
branding/info/ai/customProviders.html
Normal file
@ -0,0 +1,56 @@
|
||||
<!--
|
||||
(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>
|
||||
<script src="vendor/jquery/jquery-3.7.1.min.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" class="icon" src="resources/icons/light/info.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>
|
||||
BIN
branding/info/ai/deploy/ai.plugin
Normal file
35
branding/info/ai/hyperlink.html
Normal file
@ -0,0 +1,35 @@
|
||||
<!--
|
||||
(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>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>ChatGPT</title>
|
||||
<script type="text/javascript" src="https://onlyoffice.github.io/sdkjs-plugins/v1/plugins.js"></script>
|
||||
<script type="text/javascript" src="scripts/hyperlink.js"></script>
|
||||
<style>
|
||||
html,body,iframe {
|
||||
margin: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="iframe"></iframe>
|
||||
</body>
|
||||
</html>
|
||||
43
branding/info/ai/index.html
Normal file
@ -0,0 +1,43 @@
|
||||
<!--
|
||||
(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>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>AI Constructor</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>
|
||||
<link rel="stylesheet" href="https://onlyoffice.github.io/sdkjs-plugins/v1/plugins.css">
|
||||
|
||||
<script type="text/javascript" src="vendor/GPT-3-Encoder/encoder.js"></script>
|
||||
|
||||
<script type="text/javascript" src="scripts/engine/storage.js"></script>
|
||||
<script type="text/javascript" src="scripts/engine/local_storage.js"></script>
|
||||
<script type="text/javascript" src="scripts/engine/providers/base.js"></script>
|
||||
<script type="text/javascript" src="scripts/engine/providers/provider.js"></script>
|
||||
<script type="text/javascript" src="scripts/engine/engine.js"></script>
|
||||
|
||||
<script type="text/javascript" src="scripts/engine/library.js"></script>
|
||||
<script type="text/javascript" src="scripts/engine/buttons.js"></script>
|
||||
<script type="text/javascript" src="scripts/engine/register.js"></script>
|
||||
|
||||
<script type="text/javascript" src="scripts/code.js"></script>
|
||||
|
||||
<script src="vendor/md/markdown-it.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
BIN
branding/info/ai/resources/dark/icon.png
Normal file
|
After Width: | Height: | Size: 245 B |
BIN
branding/info/ai/resources/dark/icon@1.25x.png
Normal file
|
After Width: | Height: | Size: 277 B |
BIN
branding/info/ai/resources/dark/icon@1.5x.png
Normal file
|
After Width: | Height: | Size: 346 B |
BIN
branding/info/ai/resources/dark/icon@1.75x.png
Normal file
|
After Width: | Height: | Size: 381 B |
BIN
branding/info/ai/resources/dark/icon@2x.png
Normal file
|
After Width: | Height: | Size: 456 B |
BIN
branding/info/ai/resources/icons/dark/ai-audio.png
Normal file
|
After Width: | Height: | Size: 117 B |
BIN
branding/info/ai/resources/icons/dark/ai-audio@1.25x.png
Normal file
|
After Width: | Height: | Size: 121 B |
BIN
branding/info/ai/resources/icons/dark/ai-audio@1.5x.png
Normal file
|
After Width: | Height: | Size: 125 B |
BIN
branding/info/ai/resources/icons/dark/ai-audio@1.75x.png
Normal file
|
After Width: | Height: | Size: 133 B |
BIN
branding/info/ai/resources/icons/dark/ai-audio@2x.png
Normal file
|
After Width: | Height: | Size: 147 B |
BIN
branding/info/ai/resources/icons/dark/ai-code.png
Normal file
|
After Width: | Height: | Size: 163 B |
BIN
branding/info/ai/resources/icons/dark/ai-code@1.25x.png
Normal file
|
After Width: | Height: | Size: 173 B |
BIN
branding/info/ai/resources/icons/dark/ai-code@1.5x.png
Normal file
|
After Width: | Height: | Size: 247 B |
BIN
branding/info/ai/resources/icons/dark/ai-code@1.75x.png
Normal file
|
After Width: | Height: | Size: 213 B |
BIN
branding/info/ai/resources/icons/dark/ai-code@2x.png
Normal file
|
After Width: | Height: | Size: 238 B |
BIN
branding/info/ai/resources/icons/dark/ai-embeddings.png
Normal file
|
After Width: | Height: | Size: 187 B |
BIN
branding/info/ai/resources/icons/dark/ai-embeddings@1.25x.png
Normal file
|
After Width: | Height: | Size: 209 B |
BIN
branding/info/ai/resources/icons/dark/ai-embeddings@1.5x.png
Normal file
|
After Width: | Height: | Size: 227 B |
BIN
branding/info/ai/resources/icons/dark/ai-embeddings@1.75x.png
Normal file
|
After Width: | Height: | Size: 264 B |
BIN
branding/info/ai/resources/icons/dark/ai-embeddings@2x.png
Normal file
|
After Width: | Height: | Size: 306 B |
BIN
branding/info/ai/resources/icons/dark/ai-images.png
Normal file
|
After Width: | Height: | Size: 177 B |
BIN
branding/info/ai/resources/icons/dark/ai-images@1.25x.png
Normal file
|
After Width: | Height: | Size: 177 B |
BIN
branding/info/ai/resources/icons/dark/ai-images@1.5x.png
Normal file
|
After Width: | Height: | Size: 215 B |
BIN
branding/info/ai/resources/icons/dark/ai-images@1.75x.png
Normal file
|
After Width: | Height: | Size: 215 B |
BIN
branding/info/ai/resources/icons/dark/ai-images@2x.png
Normal file
|
After Width: | Height: | Size: 294 B |
BIN
branding/info/ai/resources/icons/dark/ai-moderations.png
Normal file
|
After Width: | Height: | Size: 172 B |
BIN
branding/info/ai/resources/icons/dark/ai-moderations@1.25x.png
Normal file
|
After Width: | Height: | Size: 204 B |
BIN
branding/info/ai/resources/icons/dark/ai-moderations@1.5x.png
Normal file
|
After Width: | Height: | Size: 236 B |
BIN
branding/info/ai/resources/icons/dark/ai-moderations@1.75x.png
Normal file
|
After Width: | Height: | Size: 255 B |
BIN
branding/info/ai/resources/icons/dark/ai-moderations@2x.png
Normal file
|
After Width: | Height: | Size: 269 B |
BIN
branding/info/ai/resources/icons/dark/ai-realtime.png
Normal file
|
After Width: | Height: | Size: 229 B |
BIN
branding/info/ai/resources/icons/dark/ai-realtime@1.25x.png
Normal file
|
After Width: | Height: | Size: 279 B |
BIN
branding/info/ai/resources/icons/dark/ai-realtime@1.5x.png
Normal file
|
After Width: | Height: | Size: 297 B |
BIN
branding/info/ai/resources/icons/dark/ai-realtime@1.75x.png
Normal file
|
After Width: | Height: | Size: 335 B |
BIN
branding/info/ai/resources/icons/dark/ai-realtime@2x.png
Normal file
|
After Width: | Height: | Size: 417 B |
BIN
branding/info/ai/resources/icons/dark/ai-texts.png
Normal file
|
After Width: | Height: | Size: 101 B |
BIN
branding/info/ai/resources/icons/dark/ai-texts@1.25x.png
Normal file
|
After Width: | Height: | Size: 104 B |
BIN
branding/info/ai/resources/icons/dark/ai-texts@1.5x.png
Normal file
|
After Width: | Height: | Size: 108 B |
BIN
branding/info/ai/resources/icons/dark/ai-texts@1.75x.png
Normal file
|
After Width: | Height: | Size: 113 B |
BIN
branding/info/ai/resources/icons/dark/ai-texts@2x.png
Normal file
|
After Width: | Height: | Size: 123 B |
BIN
branding/info/ai/resources/icons/dark/ai-visual-analysis.png
Normal file
|
After Width: | Height: | Size: 201 B |
|
After Width: | Height: | Size: 237 B |
|
After Width: | Height: | Size: 262 B |
|
After Width: | Height: | Size: 289 B |
BIN
branding/info/ai/resources/icons/dark/ai-visual-analysis@2x.png
Normal file
|
After Width: | Height: | Size: 340 B |
BIN
branding/info/ai/resources/icons/dark/ask-ai.png
Normal file
|
After Width: | Height: | Size: 162 B |
BIN
branding/info/ai/resources/icons/dark/ask-ai@1.25x.png
Normal file
|
After Width: | Height: | Size: 179 B |
BIN
branding/info/ai/resources/icons/dark/ask-ai@1.5x.png
Normal file
|
After Width: | Height: | Size: 202 B |
BIN
branding/info/ai/resources/icons/dark/ask-ai@1.75x.png
Normal file
|
After Width: | Height: | Size: 210 B |
BIN
branding/info/ai/resources/icons/dark/ask-ai@2x.png
Normal file
|
After Width: | Height: | Size: 245 B |
BIN
branding/info/ai/resources/icons/dark/big/ask-ai.png
Normal file
|
After Width: | Height: | Size: 202 B |
BIN
branding/info/ai/resources/icons/dark/big/ask-ai@1.25x.png
Normal file
|
After Width: | Height: | Size: 210 B |
BIN
branding/info/ai/resources/icons/dark/big/ask-ai@1.5x.png
Normal file
|
After Width: | Height: | Size: 263 B |
BIN
branding/info/ai/resources/icons/dark/big/ask-ai@1.75x.png
Normal file
|
After Width: | Height: | Size: 289 B |
BIN
branding/info/ai/resources/icons/dark/big/ask-ai@2x.png
Normal file
|
After Width: | Height: | Size: 345 B |
BIN
branding/info/ai/resources/icons/dark/big/default.png
Normal file
|
After Width: | Height: | Size: 284 B |
BIN
branding/info/ai/resources/icons/dark/big/default@1.25.png
Normal file
|
After Width: | Height: | Size: 327 B |
BIN
branding/info/ai/resources/icons/dark/big/default@1.5x.png
Normal file
|
After Width: | Height: | Size: 273 B |
BIN
branding/info/ai/resources/icons/dark/big/default@1.75x.png
Normal file
|
After Width: | Height: | Size: 305 B |
BIN
branding/info/ai/resources/icons/dark/big/default@2x.png
Normal file
|
After Width: | Height: | Size: 372 B |
BIN
branding/info/ai/resources/icons/dark/big/ocr.png
Normal file
|
After Width: | Height: | Size: 125 B |
BIN
branding/info/ai/resources/icons/dark/big/ocr@1.25x.png
Normal file
|
After Width: | Height: | Size: 131 B |
BIN
branding/info/ai/resources/icons/dark/big/ocr@1.5x.png
Normal file
|
After Width: | Height: | Size: 141 B |
BIN
branding/info/ai/resources/icons/dark/big/ocr@1.75x.png
Normal file
|
After Width: | Height: | Size: 143 B |
BIN
branding/info/ai/resources/icons/dark/big/ocr@2x.png
Normal file
|
After Width: | Height: | Size: 173 B |
BIN
branding/info/ai/resources/icons/dark/big/settings.png
Normal file
|
After Width: | Height: | Size: 306 B |
BIN
branding/info/ai/resources/icons/dark/big/settings@1.25x.png
Normal file
|
After Width: | Height: | Size: 360 B |
BIN
branding/info/ai/resources/icons/dark/big/settings@1.5x.png
Normal file
|
After Width: | Height: | Size: 419 B |
BIN
branding/info/ai/resources/icons/dark/big/settings@1.75x.png
Normal file
|
After Width: | Height: | Size: 461 B |
BIN
branding/info/ai/resources/icons/dark/big/settings@2x.png
Normal file
|
After Width: | Height: | Size: 583 B |
BIN
branding/info/ai/resources/icons/dark/big/summarization.png
Normal file
|
After Width: | Height: | Size: 179 B |
|
After Width: | Height: | Size: 191 B |
BIN
branding/info/ai/resources/icons/dark/big/summarization@1.5x.png
Normal file
|
After Width: | Height: | Size: 236 B |
|
After Width: | Height: | Size: 267 B |
BIN
branding/info/ai/resources/icons/dark/big/summarization@2x.png
Normal file
|
After Width: | Height: | Size: 323 B |
BIN
branding/info/ai/resources/icons/dark/big/text-to-image.png
Normal file
|
After Width: | Height: | Size: 258 B |
|
After Width: | Height: | Size: 270 B |
BIN
branding/info/ai/resources/icons/dark/big/text-to-image@1.5x.png
Normal file
|
After Width: | Height: | Size: 352 B |
|
After Width: | Height: | Size: 396 B |
BIN
branding/info/ai/resources/icons/dark/big/text-to-image@2x.png
Normal file
|
After Width: | Height: | Size: 531 B |
BIN
branding/info/ai/resources/icons/dark/big/translation.png
Normal file
|
After Width: | Height: | Size: 252 B |
BIN
branding/info/ai/resources/icons/dark/big/translation@1.25x.png
Normal file
|
After Width: | Height: | Size: 300 B |