@ -137,3 +137,7 @@
|
||||
|
||||
## 3.0.2
|
||||
* Bug fix.
|
||||
|
||||
## 3.0.5
|
||||
* New Feature: Build Your Own AI Assistants
|
||||
* Known issues: immediately after adding the assistant in the desktop application (and only there), the Edit and Delete buttons will not work. After restarting the plugin or reopening the file, they will work correctly. This will be fixed in version 9.3.0.
|
||||
|
||||
@ -34,6 +34,7 @@
|
||||
<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">
|
||||
<style id="pluginStyles"></style>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Annotation popup</title>
|
||||
@ -45,13 +46,15 @@
|
||||
window.Asc.plugin.sendToPlugin("onWindowReady", {});
|
||||
window.Asc.plugin.attachEvent("onUpdateContent", function(obj) {
|
||||
document.body.innerHTML = obj.content;
|
||||
document.querySelectorAll('strong').forEach(function(el){
|
||||
el.style.color = obj.color;
|
||||
});
|
||||
if (obj.theme) {
|
||||
onThemeChanged(obj.theme);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.Asc.plugin.attachEvent("onThemeChanged", function(theme) {
|
||||
window.Asc.plugin.attachEvent("onThemeChanged", onThemeChanged);
|
||||
|
||||
function onThemeChanged(theme) {
|
||||
let backColor = theme["background-normal"];
|
||||
let textColor = theme["text-normal"];
|
||||
let borderColor = theme["border-divider"];
|
||||
@ -76,7 +79,29 @@
|
||||
for (let i = 0; i < ballonColorElements.length; i++) {
|
||||
ballonColorElements[i].style.background = ballonColor;
|
||||
}
|
||||
});
|
||||
|
||||
let textRules =
|
||||
".original { color: " +
|
||||
theme["border-error"] +
|
||||
"; }\n";
|
||||
if (theme.name !== "theme-white" && theme.name !== "theme-night") {
|
||||
textRules +=
|
||||
".corrected strong { color: " +
|
||||
"#009900" +
|
||||
"; }\n";
|
||||
}
|
||||
textRules += ".corrected strong { font-weight:normal; }\n";
|
||||
|
||||
let styleTheme = document.getElementById("pluginStyles");
|
||||
if (!styleTheme) {
|
||||
styleTheme = document.createElement("style");
|
||||
styleTheme.id = "pluginStyles";
|
||||
styleTheme.innerHTML = textRules;
|
||||
document.getElementsByTagName("head")[0].appendChild(styleTheme);
|
||||
} else {
|
||||
styleTheme.innerHTML = textRules;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -17,7 +17,7 @@
|
||||
},
|
||||
|
||||
"guid" : "asc.{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}",
|
||||
"version": "3.0.2",
|
||||
"version": "3.0.5",
|
||||
"minVersion" : "8.2.0",
|
||||
|
||||
"variations" : [
|
||||
|
||||
69
sdkjs-plugins/content/ai/customAssistant.html
Normal file
@ -0,0 +1,69 @@
|
||||
<!--
|
||||
(c) Copyright Ascensio System SIA 2010-2025
|
||||
|
||||
This program is a free software product. You can redistribute it and/or
|
||||
modify it under the terms of the GNU Affero General Public License (AGPL)
|
||||
version 3 as published by the Free Software Foundation. In accordance with
|
||||
Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
|
||||
that Ascensio System SIA expressly excludes the warranty of non-infringement
|
||||
of any third-party rights.
|
||||
|
||||
This program is distributed WITHOUT ANY WARRANTY; without even the implied
|
||||
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
|
||||
details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
|
||||
street, Riga, Latvia, EU, LV-1050.
|
||||
|
||||
The interactive user interfaces in modified source and object code versions
|
||||
of the Program must display Appropriate Legal Notices, as required under
|
||||
Section 5 of the GNU AGPL version 3.
|
||||
|
||||
Pursuant to Section 7(b) of the License you must retain the original Product
|
||||
logo when distributing the program. Pursuant to Section 7(e) we decline to
|
||||
grant you any rights under trademark law for use of our trademarks.
|
||||
|
||||
All the Product's GUI elements, including illustrations and icon sets, as
|
||||
well as technical writing content are licensed under the terms of the
|
||||
Creative Commons Attribution-ShareAlike 4.0 International. See the License
|
||||
terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Create your own AI assistant</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">
|
||||
<link rel="stylesheet" href="vendor/select2-4.0.6-rc.1/dist/css/select2.css" />
|
||||
<link rel="stylesheet" href="./resources/styles/customAssistant.css">
|
||||
<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>
|
||||
<script type="text/javascript" src="scripts/utils/theme.js" defer></script>
|
||||
<script type="text/javascript" src="scripts/customAssistant.js" defer></script>
|
||||
</head>
|
||||
|
||||
<body style="display: flex;">
|
||||
<div id="custom_assistant_window" class="custom_assistant_window">
|
||||
<div id="custom_assistant" class="noselect">
|
||||
<span class="i18n">Turn any repetitive text task into a custom button on your toolbar. Automate specific editing, checking, or rewriting tasks using the power of AI.</span>
|
||||
</div>
|
||||
|
||||
<form id="input_prompt_wrapper" autocomplete="off">
|
||||
<input type="hidden" id="input_prompt_id" value="" />
|
||||
<label for="input_prompt_name" class="noselect"><span class="i18n">Name</span><strong>*</strong></label>
|
||||
<input type="text" id="input_prompt_name" class="form-control i18n" maxlength="30"
|
||||
placeholder="Give your tool a short name for the toolbar" spellcheck="false" required />
|
||||
<label for="input_prompt" class="noselect"><span class="i18n">Prompt</span><strong>*</strong></label>
|
||||
<textarea id="input_prompt" minlength="1" rows="1" class="form-control i18n"
|
||||
placeholder='Tell the AI what to do with the selected text (e.g., "Find factual errors" or "Summarize")'
|
||||
spellcheck="false" required></textarea>
|
||||
<label for="input_prompt" class="noselect"><span class="i18n">Action</span></label>
|
||||
<select id="assistantType" class="form-control" title="Type"></select>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -56,6 +56,12 @@
|
||||
<script type="text/javascript" src="scripts/text-annotations/text-annotator.js"></script>
|
||||
<script type="text/javascript" src="scripts/text-annotations/spelling.js"></script>
|
||||
<script type="text/javascript" src="scripts/text-annotations/grammar.js"></script>
|
||||
<script type="text/javascript" src="scripts/text-annotations/custom-annotations/annotation-popup.js"></script>
|
||||
<script type="text/javascript" src="scripts/text-annotations/custom-annotations/custom-annotator.js"></script>
|
||||
<script type="text/javascript" src="scripts/text-annotations/custom-annotations/manager.js"></script>
|
||||
<script type="text/javascript" src="scripts/text-annotations/custom-annotations/assistant-replace.js"></script>
|
||||
<script type="text/javascript" src="scripts/text-annotations/custom-annotations/assistant-hint.js"></script>
|
||||
<script type="text/javascript" src="scripts/text-annotations/custom-annotations/assistant-replace-hint.js"></script>
|
||||
|
||||
<script type="text/javascript" src="scripts/generate.js"></script>
|
||||
<script type="text/javascript" src="scripts/code.js"></script>
|
||||
|
||||
|
After Width: | Height: | Size: 288 B |
|
After Width: | Height: | Size: 359 B |
|
After Width: | Height: | Size: 396 B |
|
After Width: | Height: | Size: 495 B |
|
After Width: | Height: | Size: 546 B |
|
After Width: | Height: | Size: 196 B |
|
After Width: | Height: | Size: 231 B |
|
After Width: | Height: | Size: 272 B |
|
After Width: | Height: | Size: 301 B |
|
After Width: | Height: | Size: 345 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 348 B |
|
After Width: | Height: | Size: 391 B |
|
After Width: | Height: | Size: 473 B |
|
After Width: | Height: | Size: 529 B |
|
After Width: | Height: | Size: 195 B |
|
After Width: | Height: | Size: 229 B |
|
After Width: | Height: | Size: 263 B |
|
After Width: | Height: | Size: 306 B |
|
After Width: | Height: | Size: 352 B |
@ -0,0 +1,93 @@
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0px !important;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
margin-top: 9px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
label strong {
|
||||
color: red;
|
||||
color: var(--border-error);
|
||||
}
|
||||
|
||||
.select2-container {
|
||||
width: auto !important;
|
||||
}
|
||||
.select2-container--default .select2-selection--single,
|
||||
.form-control {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.custom_assistant_window {
|
||||
display: flex;
|
||||
padding: 1px 15px 0 15px;
|
||||
flex: 1;
|
||||
width: calc(100% - 24px);
|
||||
background-color: #fff;
|
||||
background-color: var(--background-normal);
|
||||
}
|
||||
.custom_assistant_window:not(.warning) {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#warning_text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
line-height: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
#warning_text p {
|
||||
width: calc(100% - 76px);
|
||||
}
|
||||
|
||||
|
||||
#custom_assistant {
|
||||
height: auto;
|
||||
padding: 14px 0 8px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
#input_prompt_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
#input_prompt {
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
padding: 6px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
resize: none;
|
||||
line-height: 16px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #e1e1e1;
|
||||
border: 1px solid var(--border-regular-control);
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
#input_prompt:focus {
|
||||
border-color: #4a87e7;
|
||||
border-color: var(--border-control-focus);
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
@ -30,6 +30,8 @@
|
||||
*
|
||||
*/
|
||||
|
||||
/// <reference path="./text-annotations/custom-annotations/manager.js" />
|
||||
|
||||
let settingsWindow = null;
|
||||
let aiModelsListWindow = null;
|
||||
let aiModelEditWindow = null;
|
||||
@ -37,9 +39,11 @@ let customProvidersWindow = null;
|
||||
let summarizationWindow = null;
|
||||
let translateSettingsWindow = null;
|
||||
let helperWindow = null;
|
||||
let customAssistantWindow = null;
|
||||
|
||||
let spellchecker = null;
|
||||
let grammar = null;
|
||||
let customAssistantManager = new CustomAssistantManager();
|
||||
|
||||
window.getActionsInfo = function() {
|
||||
let actions = [];
|
||||
@ -67,6 +71,9 @@ window.addSupportAgentMode = function(editorVersion) {
|
||||
if (e.keyCode === 27 && textAnnotatorPopup) {
|
||||
textAnnotatorPopup.close();
|
||||
}
|
||||
if (e.keyCode === 27 && customAnnotationPopup) {
|
||||
customAnnotationPopup.close();
|
||||
}
|
||||
|
||||
if (e.keyCode === 27 && helperWindow) {
|
||||
helperWindow.close();
|
||||
@ -171,16 +178,14 @@ window.addSupportAgentMode = function(editorVersion) {
|
||||
});
|
||||
}
|
||||
|
||||
let markdownStreamer = new MarkDownStreamer();
|
||||
|
||||
let isSupportStreaming = window.EditorHelper.isSupportStreaming;
|
||||
let dataStream = "";
|
||||
async function onStreamEvent(data, end) {
|
||||
if (isSupportStreaming)
|
||||
await Asc.Library.PasteText(data);
|
||||
dataStream += data;
|
||||
if (true === end && "" !== dataStream) {
|
||||
await Asc.Library.PasteText(dataStream);
|
||||
dataStream = "";
|
||||
}
|
||||
await markdownStreamer.onStreamChunk(data, end);
|
||||
else if (end)
|
||||
await Asc.Library.PasteText(data);
|
||||
}
|
||||
|
||||
let result = await requestEngine.chatRequest(copyMessages, false, async function(data) {
|
||||
@ -203,14 +208,18 @@ window.addSupportAgentMode = function(editorVersion) {
|
||||
await onStreamEvent(data);
|
||||
});
|
||||
|
||||
if (!isSupportStreaming)
|
||||
buffer = result;
|
||||
|
||||
if (checkBuffer && !buffer.startsWith(bufferWait)) {
|
||||
checkBuffer = false;
|
||||
await onStreamEvent(buffer, true);
|
||||
}
|
||||
|
||||
if (!isSupportStreaming) {
|
||||
await onStreamEvent("", true);
|
||||
}
|
||||
if (!isSupportStreaming && !checkBuffer)
|
||||
await onStreamEvent(buffer, true);
|
||||
|
||||
if (isSupportStreaming)
|
||||
markdownStreamer.onStreamEnd();
|
||||
|
||||
await checkEndAction();
|
||||
|
||||
@ -678,8 +687,13 @@ class Provider extends AI.Provider {\n\
|
||||
|
||||
});
|
||||
|
||||
spellchecker = new SpellChecker();
|
||||
grammar = new GrammarChecker();
|
||||
spellchecker = new SpellChecker(textAnnotatorPopup);
|
||||
grammar = new GrammarChecker(textAnnotatorPopup);
|
||||
JSON.parse(
|
||||
localStorage.getItem("onlyoffice_ai_saved_assistants") || "[]"
|
||||
).forEach(assistantData => {
|
||||
customAssistantManager.createAssistant(assistantData);
|
||||
});
|
||||
|
||||
this.attachEditorEvent("onParagraphText", function(obj) {
|
||||
if (!obj)
|
||||
@ -687,6 +701,8 @@ class Provider extends AI.Provider {\n\
|
||||
|
||||
spellchecker.onChangeParagraph(obj["paragraphId"], obj["recalcId"], obj["text"], obj["annotations"]);
|
||||
grammar.onChangeParagraph(obj["paragraphId"], obj["recalcId"], obj["text"], obj["annotations"]);
|
||||
|
||||
customAssistantManager.onChangeParagraph(obj["paragraphId"], obj["recalcId"], obj["text"], obj["annotations"]);
|
||||
});
|
||||
|
||||
this.attachEditorEvent("onFocusAnnotation", function(obj) {
|
||||
@ -702,6 +718,10 @@ class Provider extends AI.Provider {\n\
|
||||
spellchecker.onBlur();
|
||||
else if ("grammar" === obj["name"])
|
||||
grammar.onBlur();
|
||||
else if ("customAssistant" === obj["name"].slice(0, 15)) {
|
||||
const assistantId = obj["name"].slice(16);
|
||||
customAssistantManager.onBlur(assistantId);
|
||||
}
|
||||
});
|
||||
|
||||
this.attachEditorEvent("onClickAnnotation", function(obj) {
|
||||
@ -712,6 +732,10 @@ class Provider extends AI.Provider {\n\
|
||||
grammar.onClick(obj["paragraphId"], obj["ranges"]);
|
||||
else if ("spelling" === obj["name"])
|
||||
spellchecker.onClick(obj["paragraphId"], obj["ranges"]);
|
||||
else if ("customAssistant" === obj["name"].slice(0, 15)) {
|
||||
const assistantId = obj["name"].slice(16);
|
||||
customAssistantManager.onClick(assistantId, obj["paragraphId"], obj["ranges"]);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
@ -752,6 +776,21 @@ window.Asc.plugin.button = async function(id, windowId) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (customAnnotationPopup && customAnnotationPopup.popup && customAnnotationPopup.popup.id === windowId)
|
||||
{
|
||||
switch (id) {
|
||||
case 0:
|
||||
await customAnnotationPopup.popup.onAccept();
|
||||
break;
|
||||
case 1:
|
||||
await customAnnotationPopup.popup.onReject();
|
||||
break;
|
||||
default:
|
||||
customAnnotationPopup.close();
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (settingsWindow && windowId === settingsWindow.id) {
|
||||
settingsWindow.close();
|
||||
@ -793,9 +832,12 @@ window.Asc.plugin.onThemeChanged = function(theme) {
|
||||
customProvidersWindow && customProvidersWindow.command('onThemeChanged', theme);
|
||||
window.chatWindow && window.chatWindow.command('onThemeChanged', theme);
|
||||
helperWindow && helperWindow.command('onThemeChanged', theme);
|
||||
customAssistantWindow && customAssistantWindow.command('onThemeChanged', theme);
|
||||
|
||||
if (textAnnotatorPopup && textAnnotatorPopup.popup)
|
||||
textAnnotatorPopup.popup.command('onThemeChanged', theme);
|
||||
if (customAnnotationPopup && customAnnotationPopup.popup)
|
||||
customAnnotationPopup.popup.command('onThemeChanged', theme);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -913,6 +955,268 @@ async function onCheckGrammarSpelling(isCurrent)
|
||||
grammar.checkParagraphs(paraIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* CUSTOM ASSISTANT
|
||||
* @param {string} [assistantId] Assistant ID for editing
|
||||
* @param {Asc.ButtonToolbar} [buttonAssistant]
|
||||
*/
|
||||
function customAssistantWindowShow(assistantId, buttonAssistant)
|
||||
{
|
||||
if (window.customAssistantWindow) {
|
||||
customAssistantWindowClose();
|
||||
}
|
||||
const actionButtonText = assistantId ? 'Save' : 'Create';
|
||||
const description = assistantId ? 'Edit' : 'Create a new assistant';
|
||||
|
||||
let variation = {
|
||||
url : "customAssistant.html",
|
||||
description : window.Asc.plugin.tr(description),
|
||||
isVisual : true,
|
||||
buttons : [
|
||||
{ text: window.Asc.plugin.tr(actionButtonText), primary: true },
|
||||
{ text: window.Asc.plugin.tr('Cancel'), primary: false },
|
||||
],
|
||||
isModal : false,
|
||||
isCanDocked: false,
|
||||
type: "window",
|
||||
EditorsSupport : ["word"],
|
||||
size : [ 427, 303 ] //383
|
||||
};
|
||||
|
||||
customAssistantWindow = new window.Asc.PluginWindow();
|
||||
customAssistantWindow.attachEvent("onWindowReady", function() {
|
||||
Asc.Editor.callMethod("ResizeWindow", [customAssistantWindow.id, [427, 303], [427, 303], [0, 0]]);
|
||||
if (assistantId) {
|
||||
customAssistantWindow.command('onEditAssistant', assistantId);
|
||||
}
|
||||
});
|
||||
|
||||
customAssistantWindow.show(variation);
|
||||
|
||||
window.pluginsButtonsCallback = window.Asc.plugin.button;
|
||||
window.Asc.plugin.button = async function(id, windowId, ...args) {
|
||||
if (customAssistantWindow && windowId === customAssistantWindow.id) {
|
||||
if (id === 0) {
|
||||
const element = await new Promise(resolve => {
|
||||
customAssistantWindow.attachEvent("onAddEditAssistant", resolve);
|
||||
customAssistantWindow.command('onClickAdd');
|
||||
});
|
||||
if (!element) return;
|
||||
if (buttonAssistant) {
|
||||
buttonAssistant.text = element.name;
|
||||
customAssistantManager.updateAssistant(element);
|
||||
} else {
|
||||
buttonAssistant = new Asc.ButtonToolbar(null);
|
||||
buttonAssistant.text = element.name;
|
||||
buttonAssistant.icons = getToolBarButtonIcons("written-plugin");
|
||||
buttonAssistant.split = true;
|
||||
buttonAssistant.enableToggle = true;
|
||||
buttonAssistant.menu = [{
|
||||
text: 'Edit',
|
||||
id: element.id + '-edit',
|
||||
onclick: () => customAssistantWindowShow(element.id, buttonAssistant)
|
||||
},
|
||||
{
|
||||
text: 'Delete',
|
||||
id: element.id + '-delete',
|
||||
onclick: () => customAssistantWindowDeleteConfirm(element.id, buttonAssistant)
|
||||
}];
|
||||
buttonAssistant.attachOnClick(async function(){
|
||||
customAssistantOnClickToolbarIcon(element.id, buttonAssistant);
|
||||
});
|
||||
customAssistantManager.createAssistant(element);
|
||||
}
|
||||
Asc.Buttons.updateToolbarMenu(window.buttonMainToolbar.id, window.buttonMainToolbar.name, [buttonAssistant]);
|
||||
}
|
||||
customAssistantWindowClose();
|
||||
} else {
|
||||
await window.pluginsButtonsCallback(id, windowId, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
window.customAssistantWindow = customAssistantWindow;
|
||||
}
|
||||
|
||||
function customAssistantWindowClose() {
|
||||
if (window.customAssistantWindow) {
|
||||
window.customAssistantWindow.close();
|
||||
window.customAssistantWindow = null;
|
||||
}
|
||||
if (window.pluginsButtonsCallback) {
|
||||
window.Asc.plugin.button = window.pluginsButtonsCallback;
|
||||
window.pluginsButtonsCallback = null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {string} assistantId
|
||||
* @param {Asc.ButtonToolbar} buttonAssistant
|
||||
*/
|
||||
function customAssistantWindowDeleteConfirm(assistantId, buttonAssistant) {
|
||||
if (window.customAssistantWindow) {
|
||||
customAssistantWindowClose();
|
||||
}
|
||||
|
||||
const savedAssistants = JSON.parse(
|
||||
localStorage.getItem("onlyoffice_ai_saved_assistants") || "[]"
|
||||
);
|
||||
const index = savedAssistants.findIndex((item) => item.id === assistantId);
|
||||
const assistant = savedAssistants[index];
|
||||
|
||||
let variation = {
|
||||
url : "customAssistant.html",
|
||||
description : assistant.name + ' - ' + window.Asc.plugin.tr('Delete Assistant'),
|
||||
isVisual : true,
|
||||
buttons : [
|
||||
{ text: window.Asc.plugin.tr('Yes'), primary: true },
|
||||
{ text: window.Asc.plugin.tr('No'), primary: false },
|
||||
],
|
||||
isModal : true,
|
||||
isCanDocked: false,
|
||||
type: "window",
|
||||
EditorsSupport : ["word"],
|
||||
size : [ 400, 70 ]
|
||||
};
|
||||
|
||||
const customAssistantWindow = new window.Asc.PluginWindow();
|
||||
customAssistantWindow.attachEvent("onWindowReady", function() {
|
||||
Asc.Editor.callMethod("ResizeWindow", [customAssistantWindow.id, [400, 70], [400, 70], [0, 0]]);
|
||||
if (assistantId) {
|
||||
let text = window.Asc.plugin.tr('Are you sure you want to delete this assistant?');
|
||||
text += '<br>' + window.Asc.plugin.tr("This action cannot be undone.");
|
||||
customAssistantWindow.command('onWarningAssistant', text);
|
||||
}
|
||||
});
|
||||
|
||||
customAssistantWindow.show(variation);
|
||||
|
||||
window.pluginsButtonsCallback = window.Asc.plugin.button;
|
||||
window.Asc.plugin.button = async function(id, windowId, ...args) {
|
||||
if (customAssistantWindow && windowId === customAssistantWindow.id) {
|
||||
if (id === 0) {
|
||||
|
||||
if (index !== -1) {
|
||||
savedAssistants.splice(index, 1);
|
||||
localStorage.setItem(
|
||||
"onlyoffice_ai_saved_assistants",
|
||||
JSON.stringify(savedAssistants)
|
||||
);
|
||||
if (buttonAssistant) {
|
||||
buttonAssistant.removed = true;
|
||||
Asc.Buttons.updateToolbarMenu(window.buttonMainToolbar.id, window.buttonMainToolbar.name, [buttonAssistant]);
|
||||
}
|
||||
}
|
||||
}
|
||||
customAssistantWindowClose();
|
||||
} else {
|
||||
await window.pluginsButtonsCallback(id, windowId, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
window.customAssistantWindow = customAssistantWindow;
|
||||
}
|
||||
/**
|
||||
* @param {string} warningText
|
||||
* @param {localStorageCustomAssistantItem} [assistantData]
|
||||
*/
|
||||
function customAssistantWarning(warningText, assistantData) {
|
||||
if (window.customAssistantWindow) {
|
||||
customAssistantWindowClose();
|
||||
}
|
||||
|
||||
let variation = {
|
||||
url : "customAssistant.html",
|
||||
description : window.Asc.plugin.tr('Warning!'),
|
||||
isVisual : true,
|
||||
buttons : [
|
||||
{ text: window.Asc.plugin.tr('OK'), primary: true },
|
||||
],
|
||||
isModal : true,
|
||||
isCanDocked: false,
|
||||
type: "window",
|
||||
EditorsSupport : ["word"],
|
||||
size : [ 350, 76 ]
|
||||
};
|
||||
|
||||
const customAssistantWindow = new window.Asc.PluginWindow();
|
||||
customAssistantWindow.attachEvent("onWindowReady", function() {
|
||||
Asc.Editor.callMethod("ResizeWindow", [customAssistantWindow.id, [350, 76], [350, 76], [0, 0]]);
|
||||
customAssistantWindow.command('onWarningAssistant', warningText);
|
||||
|
||||
});
|
||||
|
||||
customAssistantWindow.show(variation);
|
||||
|
||||
window.pluginsButtonsCallback = window.Asc.plugin.button;
|
||||
window.Asc.plugin.button = async function(id, windowId, ...args) {
|
||||
if (customAssistantWindow && windowId === customAssistantWindow.id) {
|
||||
customAssistantWindowClose();
|
||||
} else {
|
||||
await window.pluginsButtonsCallback(id, windowId, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
window.customAssistantWindow = customAssistantWindow;
|
||||
}
|
||||
/**
|
||||
* @param {string} assistantId
|
||||
* @param {Asc.ButtonToolbar} buttonAssistant
|
||||
* @returns
|
||||
*/
|
||||
async function customAssistantOnClickToolbarIcon(assistantId, buttonAssistant)
|
||||
{
|
||||
const isAssistantRunning = customAssistantManager.isCustomAssistantRunning(assistantId);
|
||||
if (isAssistantRunning) {
|
||||
customAssistantManager.stop(assistantId);
|
||||
return;
|
||||
}
|
||||
let paraIds = [];
|
||||
|
||||
let selectedText = await Asc.Library.GetSelectedText();
|
||||
let preloaderMessage = !!selectedText ? window.Asc.plugin.tr("Processing selection...") : window.Asc.plugin.tr("Processing document...");
|
||||
|
||||
Asc.scope.hasSelectedText = !!selectedText;
|
||||
paraIds = await Asc.Editor.callCommand(function(){
|
||||
let result = [];
|
||||
let paragraphs;
|
||||
if (Asc.scope.hasSelectedText) {
|
||||
const range = Api.GetDocument().GetRangeBySelect();
|
||||
paragraphs = range.GetAllParagraphs();
|
||||
} else {
|
||||
paragraphs = Api.GetDocument().GetAllParagraphs();
|
||||
}
|
||||
paragraphs.forEach(p => result.push(p.GetInternalId()));
|
||||
return result;
|
||||
});
|
||||
|
||||
await Asc.Editor.callMethod("StartAction", ["Block", preloaderMessage]);
|
||||
|
||||
const status = await customAssistantManager.run(assistantId, paraIds);
|
||||
switch (status) {
|
||||
case customAssistantManager.STATUSES.OK:
|
||||
break;
|
||||
case customAssistantManager.STATUSES.NOT_FOUND:
|
||||
console.error("Custom assistant not found: " + assistantId);
|
||||
customAssistantWarning(window.Asc.plugin.tr("Custom assistant is not available. Please check your configuration."));
|
||||
//buttonAssistant.disabled = true;
|
||||
//Asc.Buttons.updateToolbarMenu(window.buttonMainToolbar.id, window.buttonMainToolbar.name, [buttonAssistant]);
|
||||
break;
|
||||
case customAssistantManager.STATUSES.ERROR:
|
||||
customAssistantWarning(
|
||||
window.Asc.plugin.tr("Not able to perform this action. Please use prompts related to text analysis, editing, or formatting.")
|
||||
);
|
||||
// TODO: Add the ability to remove a button press.
|
||||
// buttonAssistant.PRESSED = false;
|
||||
// Asc.Buttons.updateToolbarMenu(window.buttonMainToolbar.id, window.buttonMainToolbar.name, [buttonAssistant]);
|
||||
// customAssistantManager.stop(assistantId);
|
||||
break;
|
||||
case customAssistantManager.NO_AI_MODEL_SELECTED:
|
||||
// A window with settings will appear.
|
||||
break;
|
||||
}
|
||||
|
||||
await Asc.Editor.callMethod("EndAction", ["Block", preloaderMessage]);
|
||||
}
|
||||
|
||||
/**
|
||||
* MODELS WINDOW
|
||||
*/
|
||||
|
||||
237
sdkjs-plugins/content/ai/scripts/customAssistant.js
Normal file
@ -0,0 +1,237 @@
|
||||
/*
|
||||
* (c) Copyright Ascensio System SIA 2010-2025
|
||||
*
|
||||
* This program is a free software product. You can redistribute it and/or
|
||||
* modify it under the terms of the GNU Affero General Public License (AGPL)
|
||||
* version 3 as published by the Free Software Foundation. In accordance with
|
||||
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
|
||||
* that Ascensio System SIA expressly excludes the warranty of non-infringement
|
||||
* of any third-party rights.
|
||||
*
|
||||
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
|
||||
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||||
*
|
||||
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
|
||||
* street, Riga, Latvia, EU, LV-1050.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of the Program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU AGPL version 3.
|
||||
*
|
||||
* Pursuant to Section 7(b) of the License you must retain the original Product
|
||||
* logo when distributing the program. Pursuant to Section 7(e) we decline to
|
||||
* grant you any rights under trademark law for use of our trademarks.
|
||||
*
|
||||
* All the Product's GUI elements, including illustrations and icon sets, as
|
||||
* well as technical writing content are licensed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
|
||||
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
*
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/// <reference path="./utils/theme.js" />
|
||||
/// <reference path="../vendor/select2-4.0.6-rc.1/dist/js/select2.js" />
|
||||
/// <reference path="./text-annotations/custom-annotations/types.js" />
|
||||
|
||||
|
||||
(function (window) {
|
||||
const LOCAL_STORAGE_KEY = "onlyoffice_ai_saved_assistants";
|
||||
/** @type {any} */
|
||||
let selectType = null;
|
||||
const { form, textarea, inputId, inputName } = initFormElements();
|
||||
const mainContainer = document.getElementById("custom_assistant_window");
|
||||
if (!mainContainer) {
|
||||
console.error("Custom Assistant: required elements are missing");
|
||||
return;
|
||||
}
|
||||
|
||||
window.Asc.plugin.init = function () {
|
||||
window.Asc.plugin.sendToPlugin("onWindowReady", {});
|
||||
|
||||
inputId.value = generateHashedDateString();
|
||||
inputName.focus();
|
||||
|
||||
mainContainer.addEventListener("click", function (e) {
|
||||
if (e.target instanceof HTMLAnchorElement) {
|
||||
e.preventDefault();
|
||||
window.open(e.target.href, "_blank");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.Asc.plugin.attachEvent("onClickAdd", () => {
|
||||
let formFields = null;
|
||||
if (form.checkValidity()) {
|
||||
formFields = saveCustomAssistantToLocalStorage();
|
||||
} else {
|
||||
//form.reportValidity();
|
||||
if (!inputName.value) {
|
||||
inputName.focus();
|
||||
} else {
|
||||
textarea.focus();
|
||||
}
|
||||
}
|
||||
|
||||
window.Asc.plugin.sendToPlugin("onAddEditAssistant", formFields);
|
||||
});
|
||||
|
||||
window.Asc.plugin.attachEvent(
|
||||
"onEditAssistant",
|
||||
(/** @type {string} */ assistantId) => {
|
||||
const savedAssistants = JSON.parse(
|
||||
localStorage.getItem(LOCAL_STORAGE_KEY) || "[]"
|
||||
);
|
||||
const assistant = savedAssistants.find(
|
||||
(/** @type {localStorageCustomAssistantItem} */ item) =>
|
||||
item.id === assistantId
|
||||
);
|
||||
if (assistant) {
|
||||
inputId.value = assistant.id;
|
||||
inputName.value = assistant.name;
|
||||
selectType.val(assistant.type).trigger('change');
|
||||
textarea.value = assistant.query;
|
||||
}
|
||||
|
||||
textarea.focus();
|
||||
}
|
||||
);
|
||||
|
||||
window.Asc.plugin.attachEvent(
|
||||
"onWarningAssistant",
|
||||
(/** @type {string} */ warningText) => {
|
||||
const image = '<svg width="44" height="39" viewBox="0 0 44 39" fill="none" xmlns="http://www.w3.org/2000/svg">' +
|
||||
'<path d="M20.5201 0.853631C21.1693 -0.284655 22.8103 -0.284653 23.4594 0.853633L43.7548 36.4414C44.398 37.5693 43.5835 38.9714 42.2851 38.9714H1.69445C0.396056 38.9714 -0.418416 37.5693 0.224796 36.4414L20.5201 0.853631Z" fill="#F2BE08"/>' +
|
||||
'<circle cx="21.99" cy="32.4614" r="2.51612" fill="white"/>' +
|
||||
'<path d="M25.3447 12.3324C25.3447 13.1968 24.33 17.5992 23.6672 21.5581C23.0761 25.0894 22.8285 28.2678 22.8285 28.2678C22.4092 28.2678 21.7103 28.2678 21.1511 28.2678C21.1511 28.2678 20.9036 25.0894 20.3124 21.5581C19.6496 17.5992 18.635 13.1968 18.635 12.3324C18.635 10.4795 20.137 8.97754 21.9898 8.97754C23.8427 8.97754 25.3447 10.4795 25.3447 12.3324Z" fill="white"/>' +
|
||||
'</svg>';
|
||||
const text = '<div id="warning_text" class="noselect">' +
|
||||
image + '<p class="i18n">' + warningText + '</p></div>'
|
||||
mainContainer.innerHTML = text;
|
||||
mainContainer.classList.add("warning");
|
||||
}
|
||||
);
|
||||
|
||||
function onThemeChanged(theme) {
|
||||
window.Asc.plugin.onThemeChangedBase(theme);
|
||||
updateBodyThemeClasses(theme.type, theme.name);
|
||||
updateThemeVariables(theme);
|
||||
}
|
||||
window.Asc.plugin.onTranslate = function () {
|
||||
let elements = document.querySelectorAll(".i18n");
|
||||
elements.forEach(function (element) {
|
||||
if (
|
||||
element instanceof HTMLTextAreaElement ||
|
||||
element instanceof HTMLInputElement
|
||||
) {
|
||||
element.placeholder = window.Asc.plugin.tr(element.placeholder);
|
||||
} else if (element instanceof HTMLElement) {
|
||||
element.innerText = window.Asc.plugin.tr(element.innerText);
|
||||
}
|
||||
});
|
||||
};
|
||||
window.Asc.plugin.onThemeChanged = onThemeChanged;
|
||||
window.Asc.plugin.attachEvent("onThemeChanged", onThemeChanged);
|
||||
|
||||
/** @returns {localStorageCustomAssistantItem} */
|
||||
function saveCustomAssistantToLocalStorage() {
|
||||
const id = inputId.value;
|
||||
const name = inputName.value.trim();
|
||||
const type = Number(selectType.val());
|
||||
const query = textarea.value.trim();
|
||||
|
||||
/** @type {localStorageCustomAssistantItem[]} */
|
||||
const savedAssistants = JSON.parse(
|
||||
localStorage.getItem(LOCAL_STORAGE_KEY) || "[]"
|
||||
);
|
||||
const existingAssistantIndex = savedAssistants.findIndex(
|
||||
(item) => item.id === id
|
||||
);
|
||||
if (existingAssistantIndex !== -1) {
|
||||
savedAssistants[existingAssistantIndex] = { id, name, type, query };
|
||||
} else {
|
||||
savedAssistants.push({ id, name, type, query });
|
||||
}
|
||||
|
||||
localStorage.setItem(
|
||||
LOCAL_STORAGE_KEY,
|
||||
JSON.stringify(savedAssistants)
|
||||
);
|
||||
|
||||
return { id, name, type, query };
|
||||
}
|
||||
|
||||
/** @returns {{textarea: HTMLTextAreaElement, inputId: HTMLInputElement, inputName: HTMLInputElement, form: HTMLFormElement}} */
|
||||
function initFormElements() {
|
||||
const form = document.getElementById("input_prompt_wrapper");
|
||||
const inputId = document.getElementById("input_prompt_id");
|
||||
const inputName = document.getElementById("input_prompt_name");
|
||||
const textarea = document.getElementById("input_prompt");
|
||||
if (form instanceof HTMLFormElement === false) {
|
||||
throw new Error("Custom Assistant: form is not HTMLFormElement");
|
||||
}
|
||||
if (textarea instanceof HTMLTextAreaElement === false) {
|
||||
throw new Error(
|
||||
"Custom Assistant: textarea is not HTMLTextAreaElement"
|
||||
);
|
||||
}
|
||||
if (inputId instanceof HTMLInputElement === false) {
|
||||
throw new Error(
|
||||
"Custom Assistant: input id is not HTMLInputElement"
|
||||
);
|
||||
}
|
||||
if (inputName instanceof HTMLInputElement === false) {
|
||||
throw new Error(
|
||||
"Custom Assistant: input name is not HTMLInputElement"
|
||||
);
|
||||
}
|
||||
form.onsubmit = function (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
selectType = $('#assistantType');
|
||||
selectType.select2({
|
||||
data : [{
|
||||
id: 0,
|
||||
text: "Hint"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
text: "Replace"
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
text: "Replace + Hint"
|
||||
}],
|
||||
tags: true,
|
||||
minimumResultsForSearch: Infinity,
|
||||
dropdownAutoWidth: true
|
||||
});
|
||||
selectType.on('select2:select', (e) => {
|
||||
|
||||
});
|
||||
selectType.val(0); // Default value
|
||||
|
||||
selectType.trigger('select2:select');
|
||||
selectType.trigger('change');
|
||||
|
||||
return { form, textarea, inputId, inputName };
|
||||
}
|
||||
|
||||
/** @returns {string} */
|
||||
function generateHashedDateString() {
|
||||
const date = new Date();
|
||||
const data = date.toISOString() + Math.random() + performance.now();
|
||||
|
||||
let hash = 0;
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const char = data.charCodeAt(i);
|
||||
hash = (hash << 5) - hash + char;
|
||||
hash = hash & hash;
|
||||
}
|
||||
|
||||
return Math.abs(hash).toString(36) + "_" + date.getTime().toString(36);
|
||||
}
|
||||
})(window);
|
||||
@ -608,7 +608,14 @@
|
||||
Library.prototype.getMarkdownResult = function(data) {
|
||||
let markdownEscape = data.indexOf("```md");
|
||||
if (-1 !== markdownEscape && markdownEscape < 5)
|
||||
data = data.substring(markdownEscape + 5);
|
||||
data = data.substring(markdownEscape + 5);
|
||||
return this.trimResult(data);
|
||||
};
|
||||
|
||||
Library.prototype.getJSONResult = function(data) {
|
||||
let markdownEscape = data.indexOf("```json");
|
||||
if (-1 !== markdownEscape && markdownEscape < 5)
|
||||
data = data.substring(markdownEscape + 5);
|
||||
return this.trimResult(data);
|
||||
};
|
||||
|
||||
|
||||
@ -642,7 +642,7 @@ async function registerButtons(window, undefined)
|
||||
button2.icons = getToolBarButtonIcons("ocr");
|
||||
button2.attachOnClick(on_click_ocr);
|
||||
}
|
||||
|
||||
|
||||
if (editorVersion >= 9002000 && Asc.Editor.getType() === "word")
|
||||
{
|
||||
let buttonGS = new Asc.ButtonToolbar(buttonMainToolbar);
|
||||
@ -663,6 +663,45 @@ async function registerButtons(window, undefined)
|
||||
});
|
||||
buttonGS.split = true;
|
||||
}
|
||||
|
||||
let neededVersionForAiAssistant = 9002000;
|
||||
/*if (window.AscDesktopEditor) {
|
||||
neededVersionForAiAssistant = 9003000;
|
||||
}*/
|
||||
if (editorVersion >= neededVersionForAiAssistant && Asc.Editor.getType() === "word")
|
||||
{
|
||||
const buttonCustomAssistant = new Asc.ButtonToolbar(buttonMainToolbar);
|
||||
buttonCustomAssistant.text = "Create AI assistant";
|
||||
buttonCustomAssistant.icons = getToolBarButtonIcons("plugin-writer");
|
||||
buttonCustomAssistant.separator = true;
|
||||
buttonCustomAssistant.attachOnClick(function(){
|
||||
customAssistantWindowShow();
|
||||
});
|
||||
const savedAssistants = JSON.parse(
|
||||
localStorage.getItem("onlyoffice_ai_saved_assistants") || "[]"
|
||||
);
|
||||
|
||||
savedAssistants.forEach(element => {
|
||||
const buttonAssistant = new Asc.ButtonToolbar(buttonMainToolbar);
|
||||
buttonAssistant.text = element.name;
|
||||
buttonAssistant.icons = getToolBarButtonIcons("written-plugin");
|
||||
buttonAssistant.split = true;
|
||||
buttonAssistant.enableToggle = true;
|
||||
buttonAssistant.menu = [{
|
||||
text: 'Edit',
|
||||
id: element.id + '-edit',
|
||||
onclick: () => customAssistantWindowShow(element.id, buttonAssistant)
|
||||
},
|
||||
{
|
||||
text: 'Delete',
|
||||
id: element.id + '-delete',
|
||||
onclick: () => customAssistantWindowDeleteConfirm(element.id, buttonAssistant)
|
||||
}];
|
||||
buttonAssistant.attachOnClick(async function(){
|
||||
customAssistantOnClickToolbarIcon(element.id, buttonAssistant);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// register actions
|
||||
|
||||
@ -0,0 +1,129 @@
|
||||
# AI Assistant for AI plugin in Only Office
|
||||
|
||||
## 🚀 Overview
|
||||
|
||||
Welcome to the AI Assistant Plugin – your intelligent writing companion! This powerful plugin allows you to create custom AI assistants that analyze your documents for specific issues and suggest improvements. Whether you need fact-checking, style improvements, or specialized text analysis, you can now build AI helpers tailored to your exact needs.
|
||||
|
||||
### 🎯 **Three Assistant Types**
|
||||
|
||||
Choose how assistants interact with your text:
|
||||
|
||||
| Type | Action | Best For |
|
||||
|------|--------|----------|
|
||||
| **Hint** | Shows explanation why text was highlighted | Learning, suggestions without direct edits |
|
||||
| **Replace** | Offers replacement with simple accept/reject | Quick fixes, consistent terminology |
|
||||
| **Replace + Hint** | Provides both replacement and detailed explanation | Complex edits needing justification |
|
||||
|
||||
### 🔗 **Smart Source Linking**
|
||||
|
||||
If your assistant prompt requests sources/links:
|
||||
|
||||
- Clickable URLs appear in popup windows
|
||||
- Sources support your assistant's suggestions
|
||||
- Links open in new tabs automatically
|
||||
|
||||
### ⚡ **Smart Text Selection**
|
||||
|
||||
- **With selection**: If text is selected when activating an assistant, only selected paragraphs are analyzed
|
||||
- **Without selection**: If no text is selected, the entire document is analyzed
|
||||
- **Dynamic updates**: Edited paragraphs are automatically re-analyzed
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
### Creating Your First Assistant
|
||||
|
||||
1. **Open the AI Plugin Panel**
|
||||
- Click the AI icon in your editor toolbar
|
||||
|
||||
2. **Create a New Assistant**
|
||||
- Click **"Create AI assistant"** button
|
||||
- Fill in the creation form:
|
||||
- **Name**: Give your assistant a descriptive name
|
||||
- **Type**: Choose Hint, Replace, or Replace+Hint
|
||||
- **Description/Prompt**: Write clear instructions for what to look for
|
||||
|
||||
3. **Example Prompts:**
|
||||
|
||||
- "Find all incorrect dates in historical documents and suggest corrections"
|
||||
- "Replace all examples of youth slang and informal language in the text.
|
||||
Look for:
|
||||
1. Modern slang terms (like "lit", "savage", "flex", "GOAT")
|
||||
2. Internet memes and viral expressions
|
||||
3. Common abbreviations ("LOL", "BRB", "IMO", "TBH")
|
||||
4. English borrowings used in informal contexts
|
||||
5. Colloquial expressions and trendy phrases"
|
||||
- "Check for consistency in character names throughout this novel"
|
||||
- "Verify scientific facts against current research with source links"
|
||||
|
||||
### Using Assistants
|
||||
|
||||
**1. Activate an Assistant**
|
||||
|
||||
- Click the assistant's icon to enable it
|
||||
- Click again to disable it
|
||||
|
||||
**2. Review Findings**
|
||||
|
||||
- Text matches appear highlighted in your document
|
||||
- Click highlighted text to see assistant's suggestions
|
||||
- Choose to accept or reject suggestions
|
||||
|
||||
**3. Multiple Assistants**
|
||||
|
||||
- Run multiple assistants simultaneously
|
||||
- Each uses different colors for easy identification
|
||||
|
||||
## 📋 Assistant Types in Detail
|
||||
|
||||
### **Type 1: Hint**
|
||||
|
||||
- Text is highlighted for your attention
|
||||
- Click to see **why** it was flagged
|
||||
- No automatic changes – you decide what to do
|
||||
- **Best for**: Learning, awareness, suggestions
|
||||
|
||||
### **Type 2: Replace**
|
||||
|
||||
- Text is highlighted with suggested replacement
|
||||
- Click to see the **replacement dialog**
|
||||
- Accept or reject with one click
|
||||
- **Best for**: Quick fixes, consistent terminology
|
||||
|
||||
### **Type 3: Replace + Hint**
|
||||
|
||||
- Combines both replacement and explanation
|
||||
- Click to see **replacement dialog with detailed reasoning**
|
||||
- Includes source links if requested
|
||||
- **Best for**: Complex edits, fact-checking, learning
|
||||
|
||||
## 🔧 Managing Assistants
|
||||
|
||||
### **Edit an Assistant**
|
||||
|
||||
If an assistant isn't working as expected:
|
||||
|
||||
1. Open the Assistant icon's drop-down menu
|
||||
2. Click "Edit" next to the assistant
|
||||
3. Refine the prompt or change the type
|
||||
4. Save changes
|
||||
|
||||
### **Delete an Assistant**
|
||||
|
||||
1. Open the Assistant icon's drop-down menu
|
||||
2. Click "Delete" next to the assistant
|
||||
3. Confirm deletion
|
||||
|
||||
## 💡 Best Practices for Writing Prompts
|
||||
|
||||
### **Be Specific**
|
||||
|
||||
✅ GOOD: "Find dates before 1900 that might be incorrect"
|
||||
❌ VAGUE: "Check for wrong dates"
|
||||
|
||||
✅ AUDIENCE-AWARE: "Simplify technical jargon for general readers"
|
||||
❌ GENERIC: "Make text easier to read"
|
||||
|
||||
> 💡 **Pro Tip**: The most effective assistants are those with clear, specific instructions. Don't be afraid to experiment – you can always edit or delete assistants that don't work as expected!
|
||||
|
||||
---
|
||||
*We can't wait to see what amazing assistants you'll create! Your creativity will help shape the future of this feature.*
|
||||
@ -0,0 +1,198 @@
|
||||
/*
|
||||
* (c) Copyright Ascensio System SIA 2010-2025
|
||||
*
|
||||
* This program is a free software product. You can redistribute it and/or
|
||||
* modify it under the terms of the GNU Affero General Public License (AGPL)
|
||||
* version 3 as published by the Free Software Foundation. In accordance with
|
||||
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
|
||||
* that Ascensio System SIA expressly excludes the warranty of non-infringement
|
||||
* of any third-party rights.
|
||||
*
|
||||
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
|
||||
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||||
*
|
||||
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
|
||||
* street, Riga, Latvia, EU, LV-1050.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of the Program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU AGPL version 3.
|
||||
*
|
||||
* Pursuant to Section 7(b) of the License you must retain the original Product
|
||||
* logo when distributing the program. Pursuant to Section 7(e) we decline to
|
||||
* grant you any rights under trademark law for use of our trademarks.
|
||||
*
|
||||
* All the Product's GUI elements, including illustrations and icon sets, as
|
||||
* well as technical writing content are licensed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
|
||||
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
*
|
||||
*/
|
||||
|
||||
/// <reference path="./types.js" />
|
||||
|
||||
function CustomAnnotationPopup()
|
||||
{
|
||||
this.popup = null;
|
||||
this.type = 0; // 0 - hint, 1 - replace + hint, 2 - replace
|
||||
this.paraId = -1;
|
||||
this.rangeId = -1;
|
||||
|
||||
this.content = "";
|
||||
this.width = 318;
|
||||
this.height = 500;
|
||||
|
||||
/**
|
||||
* @param {number} type
|
||||
* @param {string} paraId
|
||||
* @param {string} rangeId
|
||||
* @param {InfoForPopup} data
|
||||
* @returns
|
||||
*/
|
||||
this.open = function(type, paraId, rangeId, data)
|
||||
{
|
||||
this._calculateWindowSize(data);
|
||||
return this._open(type, paraId, rangeId);
|
||||
};
|
||||
|
||||
this._open = function(type, paraId, rangeId)
|
||||
{
|
||||
if (this.type === type
|
||||
&& rangeId === this.rangeId
|
||||
&& paraId === this.paraId)
|
||||
return this.popup;
|
||||
|
||||
this.type = type;
|
||||
this.paraId = paraId;
|
||||
this.rangeId = rangeId;
|
||||
|
||||
if (this.popup)
|
||||
this.popup.close();
|
||||
|
||||
let variation = {
|
||||
url : 'annotationPopup.html',
|
||||
isVisual : true,
|
||||
buttons : this._getButtons(type),
|
||||
isModal : false,
|
||||
description: this._getTitle(),
|
||||
EditorsSupport : ["word", "slide", "cell", "pdf"],
|
||||
size : [this.width, this.height],
|
||||
fixedSize : true,
|
||||
isTargeted : true
|
||||
};
|
||||
let popup = new window.Asc.PluginWindow();
|
||||
|
||||
let _t = this;
|
||||
popup.attachEvent("onWindowReady", function() {
|
||||
popup.command("onUpdateContent", {
|
||||
content : _t.content,
|
||||
theme : window.Asc.plugin.theme
|
||||
});
|
||||
});
|
||||
|
||||
popup.show(variation);
|
||||
this.popup = popup;
|
||||
return popup;
|
||||
};
|
||||
|
||||
this.close = function(type)
|
||||
{
|
||||
if (undefined !== type && this.type !== type)
|
||||
return;
|
||||
|
||||
if (!this.popup)
|
||||
return;
|
||||
|
||||
this.type = -1;
|
||||
this.rangeId = -1;
|
||||
this.paraId = -1;
|
||||
|
||||
this.popup.close();
|
||||
this.popup = null;
|
||||
Asc.Editor.callMethod("FocusEditor");
|
||||
};
|
||||
|
||||
this._getTitle = function()
|
||||
{
|
||||
return window.Asc.plugin.tr(this.type === 0 ? "Match" : "Proposal for replacement");
|
||||
};
|
||||
|
||||
this._getButtons = function()
|
||||
{
|
||||
const buttons = [];
|
||||
if (this.type === 0) {
|
||||
buttons.push({ text: window.Asc.plugin.tr('OK'), primary: true });
|
||||
} else {
|
||||
buttons.push({ text: window.Asc.plugin.tr('Accept'), primary: true });
|
||||
buttons.push({ text: window.Asc.plugin.tr('Reject'), primary: false });
|
||||
}
|
||||
return buttons;
|
||||
};
|
||||
|
||||
/** @param {InfoForPopup} data */
|
||||
this._calculateWindowSize = function(data)
|
||||
{
|
||||
let backColor = window.Asc.plugin.theme ? window.Asc.plugin.theme["background-normal"] : "#FFFFFF";
|
||||
let textColor = window.Asc.plugin.theme ? window.Asc.plugin.theme["text-normal"] : "#3D3D3D";
|
||||
let borderColor = window.Asc.plugin.theme ? window.Asc.plugin.theme["border-divider"] : "#666666";
|
||||
let ballonColor = window.Asc.plugin.theme ? window.Asc.plugin.theme["canvas-background"] : "#F5F5F5";
|
||||
this.content = "";
|
||||
|
||||
if (data.type === 0) { // Hint
|
||||
this.content = `<div>
|
||||
<div class="ballon-color text-color border-color" style="font-size:12px; color:${textColor}; line-height:1.5; padding:10px;">${data.explanation}</div>
|
||||
</div>`;
|
||||
} else { // Replace + Hint or Replace
|
||||
if (data.suggested) {
|
||||
this.content = `<div class="back-color text-color" style="background:${backColor}; overflow:hidden; max-width:320px; min-width:280px;color:${textColor}; user-select:none;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;">
|
||||
<div style="padding:16px 16px 0px 16px;">
|
||||
|
||||
<div style="margin-bottom:12px;">
|
||||
<div class="text-color" style="font-size:11px; font-weight:700; margin-bottom:6px;">
|
||||
${window.Asc.plugin.tr("Suggested correction")}
|
||||
</div>
|
||||
|
||||
<div class="ballon-color text-color border-color" style="font-size:12px; line-height:1.5; background:${ballonColor}; border:1px solid ${borderColor}; border-radius:3px; padding:10px;">
|
||||
<div style="display:flex; align-items:center; gap:8px;">
|
||||
<span class="original" font-weight:normal;">${data.original}</span>
|
||||
<span class="style="font-weight:bold;">→</span>
|
||||
<span class="corrected" style="font-weight:normal;">${data.suggested}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
if (data.explanation) {
|
||||
this.content += `<div style="margin-bottom:16px;">
|
||||
<div class="text-color" class="text-color" style="font-size:11px; font-weight:700; color:${textColor}; margin-bottom:6px;">
|
||||
${window.Asc.plugin.tr("Explanation")}
|
||||
</div>
|
||||
|
||||
<div class="ballon-color text-color border-color" style="font-size:12px; color:${textColor}; line-height:1.5; background:${ballonColor}; border:1px solid ${borderColor}; border-radius:3px; padding:10px;">${data.explanation}</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
this.content += "</div></div>";
|
||||
|
||||
let measureDiv = document.createElement("div");
|
||||
measureDiv.style.position = "absolute";
|
||||
measureDiv.style.left = "-9999px";
|
||||
measureDiv.style.top = "-9999px";
|
||||
measureDiv.style.width = this.width + "px";
|
||||
measureDiv.style.visibility = "hidden";
|
||||
measureDiv.style.pointerEvents = "none";
|
||||
measureDiv.style.opacity = "0";
|
||||
measureDiv.style.margin = "0";
|
||||
measureDiv.style.padding = "0";
|
||||
measureDiv.innerHTML = this.content;
|
||||
|
||||
document.body.appendChild(measureDiv);
|
||||
|
||||
this.height = measureDiv.scrollHeight;
|
||||
|
||||
document.body.removeChild(measureDiv);
|
||||
};
|
||||
}
|
||||
|
||||
var customAnnotationPopup = new CustomAnnotationPopup();
|
||||
@ -0,0 +1,193 @@
|
||||
/*
|
||||
* (c) Copyright Ascensio System SIA 2010-2025
|
||||
*
|
||||
* This program is a free software product. You can redistribute it and/or
|
||||
* modify it under the terms of the GNU Affero General Public License (AGPL)
|
||||
* version 3 as published by the Free Software Foundation. In accordance with
|
||||
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
|
||||
* that Ascensio System SIA expressly excludes the warranty of non-infringement
|
||||
* of any third-party rights.
|
||||
*
|
||||
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
|
||||
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||||
*
|
||||
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
|
||||
* street, Riga, Latvia, EU, LV-1050.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of the Program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU AGPL version 3.
|
||||
*
|
||||
* Pursuant to Section 7(b) of the License you must retain the original Product
|
||||
* logo when distributing the program. Pursuant to Section 7(e) we decline to
|
||||
* grant you any rights under trademark law for use of our trademarks.
|
||||
*
|
||||
* All the Product's GUI elements, including illustrations and icon sets, as
|
||||
* well as technical writing content are licensed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
|
||||
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
*
|
||||
*/
|
||||
|
||||
/// <reference path="./custom-annotator.js" />
|
||||
/// <reference path="./types.js" />
|
||||
|
||||
/**
|
||||
* @param {localStorageCustomAssistantItem} assistantData
|
||||
* @constructor
|
||||
* @extends CustomAnnotator
|
||||
*/
|
||||
function AssistantHint(annotationPopup, assistantData) {
|
||||
CustomAnnotator.call(this, annotationPopup, assistantData);
|
||||
}
|
||||
AssistantHint.prototype = Object.create(CustomAnnotator.prototype);
|
||||
AssistantHint.prototype.constructor = AssistantHint;
|
||||
|
||||
Object.assign(AssistantHint.prototype, {
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {Array<HintAiResponse>} matches
|
||||
*/
|
||||
_convertToRanges: function (paraId, text, matches) {
|
||||
const _t = this;
|
||||
let rangeId = 1;
|
||||
const ranges = [];
|
||||
for (const {
|
||||
origin,
|
||||
reason,
|
||||
paragraph,
|
||||
occurrence,
|
||||
confidence,
|
||||
} of matches) {
|
||||
if (confidence <= 0.7) continue;
|
||||
|
||||
let count = 0;
|
||||
let searchStart = 0;
|
||||
|
||||
while (searchStart < text.length) {
|
||||
const index = _t.simpleGraphemeIndexOf(
|
||||
text,
|
||||
origin,
|
||||
searchStart,
|
||||
);
|
||||
if (index === -1) break;
|
||||
|
||||
count++;
|
||||
if (count === occurrence) {
|
||||
ranges.push({
|
||||
start: index,
|
||||
length: [...origin].length,
|
||||
id: rangeId,
|
||||
});
|
||||
_t.paragraphs[paraId][rangeId] = {
|
||||
original: origin,
|
||||
reason: reason,
|
||||
};
|
||||
++rangeId;
|
||||
break;
|
||||
}
|
||||
searchStart = index + 1;
|
||||
}
|
||||
}
|
||||
return ranges;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @returns {string}
|
||||
*/
|
||||
_createPrompt: function (text) {
|
||||
let prompt = `You are a multi-disciplinary text analysis assistant.
|
||||
Your task is to find text fragments that match the user's criteria.
|
||||
|
||||
MANDATORY RULES:
|
||||
1. Analyze ONLY the provided text.
|
||||
2. Find words, phrases, or sentences that match the user's criteria.
|
||||
3. For EACH match you find:
|
||||
- Provide the exact quote.
|
||||
- Explain WHY it matches the criteria.
|
||||
- Provide position information (paragraph number).
|
||||
4. If no matches are found, return an empty array: [].
|
||||
5. Format your response STRICTLY in JSON format.
|
||||
6. Support multiple languages (English, Russian, etc.)
|
||||
|
||||
Response format - return ONLY this JSON array with no additional text:
|
||||
[
|
||||
{
|
||||
"origin": "exact text fragment that matches the query",
|
||||
"reason": "detailed explanation why it matches the criteria",
|
||||
"paragraph": paragraph_number,
|
||||
"occurrence": 1,
|
||||
"confidence": 0.95
|
||||
}
|
||||
]
|
||||
|
||||
Guidelines for each field:
|
||||
- "origin": EXACT UNCHANGED original text fragment. Do not fix anything in this field.
|
||||
- "reason": Clear explanation of why this fragment matches the criteria; IF the user's request contains words like "source", "reference", "link", "cite", "website", "URL", "Wikipedia", "proof", "evidence", "verify" - then you MUST include actual working links in your explanations in html format.
|
||||
- "paragraph": Paragraph number where the fragment is found (0-based index)
|
||||
- "occurrence": Which occurrence of this sentence if it appears multiple times (1 for first, 2 for second, etc.)
|
||||
- "confidence": Value between 0 and 1 indicating certainty (1.0 = completely certain, 0.5 = uncertain)
|
||||
|
||||
CRITICAL
|
||||
- Output should be in the exact this format
|
||||
- No any comments are allowed
|
||||
|
||||
CRITICAL - Output Format:
|
||||
- Return ONLY the raw JSON array, nothing else
|
||||
- DO NOT wrap the response in markdown code blocks (no \`\`\`json or \`\`\`)
|
||||
- DO NOT include any explanatory text before or after the JSON
|
||||
- DO NOT use escaped newlines (\\n) - return the JSON on a single line if possible
|
||||
- The response should start with [ and end with ]
|
||||
`;
|
||||
prompt +=
|
||||
"\n\nUSER REQUEST:\n```" + this.assistantData.query + "\n```\n\n";
|
||||
|
||||
prompt += "TEXT TO ANALYZE:\n```\n" + text + "\n```\n\n";
|
||||
|
||||
prompt += `Please analyze this text and find all fragments that match the user's request. Be thorough but precise.`;
|
||||
|
||||
return prompt;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string} rangeId
|
||||
* @return {HintInfoForPopup}
|
||||
*/
|
||||
getInfoForPopup: function (paraId, rangeId) {
|
||||
let _s = this.getAnnotation(paraId, rangeId);
|
||||
let reason = _s["reason"];
|
||||
try {
|
||||
reason = reason.replace(/<a\s+(.*?)>/gi, '<a $1 target="_blank">');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
return {
|
||||
original: _s["original"],
|
||||
explanation: reason,
|
||||
type: this.type,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string} rangeId
|
||||
*/
|
||||
onAccept: async function (paraId, rangeId) {
|
||||
await CustomAnnotator.prototype.onAccept.call(this);
|
||||
await Asc.Editor.callMethod("StartAction", ["GroupActions"]);
|
||||
|
||||
let range = this.getAnnotationRangeObj(paraId, rangeId);
|
||||
await Asc.Editor.callMethod("SelectAnnotationRange", [range]);
|
||||
|
||||
await Asc.Editor.callCommand(function () {
|
||||
Api.GetDocument().RemoveSelection();
|
||||
});
|
||||
|
||||
await Asc.Editor.callMethod("RemoveAnnotationRange", [range]);
|
||||
await Asc.Editor.callMethod("EndAction", ["GroupActions"]);
|
||||
await Asc.Editor.callMethod("FocusEditor");
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,223 @@
|
||||
/*
|
||||
* (c) Copyright Ascensio System SIA 2010-2025
|
||||
*
|
||||
* This program is a free software product. You can redistribute it and/or
|
||||
* modify it under the terms of the GNU Affero General Public License (AGPL)
|
||||
* version 3 as published by the Free Software Foundation. In accordance with
|
||||
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
|
||||
* that Ascensio System SIA expressly excludes the warranty of non-infringement
|
||||
* of any third-party rights.
|
||||
*
|
||||
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
|
||||
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||||
*
|
||||
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
|
||||
* street, Riga, Latvia, EU, LV-1050.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of the Program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU AGPL version 3.
|
||||
*
|
||||
* Pursuant to Section 7(b) of the License you must retain the original Product
|
||||
* logo when distributing the program. Pursuant to Section 7(e) we decline to
|
||||
* grant you any rights under trademark law for use of our trademarks.
|
||||
*
|
||||
* All the Product's GUI elements, including illustrations and icon sets, as
|
||||
* well as technical writing content are licensed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
|
||||
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
*
|
||||
*/
|
||||
|
||||
/// <reference path="./custom-annotator.js" />
|
||||
/// <reference path="./types.js" />
|
||||
|
||||
/**
|
||||
* @param {localStorageCustomAssistantItem} assistantData
|
||||
* @constructor
|
||||
* @extends CustomAnnotator
|
||||
*/
|
||||
function AssistantReplaceHint(annotationPopup, assistantData) {
|
||||
CustomAnnotator.call(this, annotationPopup, assistantData);
|
||||
}
|
||||
AssistantReplaceHint.prototype = Object.create(CustomAnnotator.prototype);
|
||||
AssistantReplaceHint.prototype.constructor = AssistantReplaceHint;
|
||||
|
||||
Object.assign(AssistantReplaceHint.prototype, {
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {ReplaceHintAiResponse[]} matches
|
||||
*/
|
||||
_convertToRanges: function (paraId, text, matches) {
|
||||
const _t = this;
|
||||
let rangeId = 1;
|
||||
const ranges = [];
|
||||
for (const {
|
||||
origin,
|
||||
suggestion,
|
||||
difference,
|
||||
reason,
|
||||
paragraph,
|
||||
occurrence,
|
||||
confidence,
|
||||
} of matches) {
|
||||
if (origin === suggestion || confidence <= 0.7) continue;
|
||||
|
||||
let count = 0;
|
||||
let searchStart = 0;
|
||||
|
||||
while (searchStart < text.length) {
|
||||
const index = _t.simpleGraphemeIndexOf(
|
||||
text,
|
||||
origin,
|
||||
searchStart,
|
||||
);
|
||||
if (index === -1) break;
|
||||
|
||||
count++;
|
||||
if (count === occurrence) {
|
||||
ranges.push({
|
||||
start: index,
|
||||
length: [...origin].length,
|
||||
id: rangeId,
|
||||
});
|
||||
if (difference.indexOf(origin + " → ") === 0) {
|
||||
difference = difference.slice(
|
||||
origin.length + 3,
|
||||
);
|
||||
}
|
||||
_t.paragraphs[paraId][rangeId] = {
|
||||
original: origin,
|
||||
suggestion: suggestion,
|
||||
difference: difference,
|
||||
reason: reason,
|
||||
};
|
||||
++rangeId;
|
||||
break;
|
||||
}
|
||||
searchStart = index + 1;
|
||||
}
|
||||
}
|
||||
return ranges;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @returns {string}
|
||||
*/
|
||||
_createPrompt: function (text) {
|
||||
let prompt = `You are a multi-disciplinary text analysis and transformation assistant.
|
||||
Your task is to analyze text based on user's specific criteria and provide intelligent corrections.
|
||||
|
||||
MANDATORY RULES:
|
||||
1. UNDERSTAND the user's intent from their criteria.
|
||||
2. Find words, phrases, or sentences that match the user's criteria.
|
||||
3. For EACH match you find:
|
||||
- Provide the exact quote.
|
||||
- SUGGEST appropriate replacements.
|
||||
- Explain WHY it matches the criteria.
|
||||
- Provide position information (paragraph number).
|
||||
4. If no matches are found, return an empty array: [].
|
||||
5. Format your response STRICTLY in JSON format.
|
||||
6. Support multiple languages (English, Russian, etc.)
|
||||
|
||||
Response format - return ONLY this JSON array with no additional text:
|
||||
[
|
||||
{
|
||||
"origin": "exact text fragment that matches the query",
|
||||
"suggestion": "suggested replacement (plain text)",
|
||||
"reason": "detailed explanation why it matches the criteria",
|
||||
"difference":"visual representation showing exact changes between origin and suggestion"
|
||||
"paragraph": paragraph_number,
|
||||
"occurrence": 1,
|
||||
"confidence": 0.95
|
||||
}
|
||||
]
|
||||
|
||||
Guidelines for each field:
|
||||
- "origin": EXACT UNCHANGED original text fragment. Do not fix anything in this field.
|
||||
- "suggestion": Your suggested replacement for the fragment.
|
||||
* Ensure it aligns with the user's criteria.
|
||||
* Maintain coherence with surrounding text.
|
||||
- "reason": Clear explanation of why this fragment matches the criteria; IF the user's request contains words like "source", "reference", "link", "cite", "website", "URL", "Wikipedia", "proof", "evidence", "verify" - then you MUST include actual working links in your explanations in html format.
|
||||
- "difference": The difference between origin and suggestion in html format.
|
||||
- "paragraph": Paragraph number where the fragment is found (0-based index)
|
||||
- "occurrence": Which occurrence of this sentence if it appears multiple times (1 for first, 2 for second, etc.)
|
||||
- "confidence": Value between 0 and 1 indicating certainty (1.0 = completely certain, 0.5 = uncertain)
|
||||
|
||||
CRITICAL: Rules for the "difference" field:
|
||||
- Format: "original → corrected", you need to leave only "corrected", never show the "original"
|
||||
- "<strong>" for added characters - use for the corrected version
|
||||
- Show exact character-level changes
|
||||
|
||||
CRITICAL:
|
||||
- Output should be in the exact this format
|
||||
- No any comments are allowed
|
||||
|
||||
CRITICAL - Output Format:
|
||||
- Return ONLY the raw JSON array, nothing else
|
||||
- DO NOT wrap the response in markdown code blocks (no \`\`\`json or \`\`\`)
|
||||
- DO NOT include any explanatory text before or after the JSON
|
||||
- DO NOT use escaped newlines (\\n) - return the JSON on a single line if possible
|
||||
- The response should start with [ and end with ]
|
||||
`;
|
||||
prompt +=
|
||||
"\n\nUSER REQUEST:\n```" + this.assistantData.query + "\n```\n\n";
|
||||
|
||||
prompt += "TEXT TO ANALYZE:\n```\n" + text + "\n```\n\n";
|
||||
|
||||
prompt += `Please analyze this text and find all fragments that match the user's request. Be thorough but precise.`;
|
||||
|
||||
return prompt;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string} rangeId
|
||||
* @returns {ReplaceHintInfoForPopup}
|
||||
*/
|
||||
getInfoForPopup: function (paraId, rangeId) {
|
||||
let _s = this.getAnnotation(paraId, rangeId);
|
||||
let reason = _s["reason"];
|
||||
try {
|
||||
reason = reason.replace(/<a\s+(.*?)>/gi, '<a $1 target="_blank">');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
let suggested = _s["difference"];
|
||||
if (suggested.indexOf('</strong>') === -1) {
|
||||
suggested = `<strong>${suggested}</strong>`;
|
||||
}
|
||||
return {
|
||||
original: _s["original"],
|
||||
suggested: suggested,
|
||||
explanation: reason,
|
||||
type: this.type,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string} rangeId
|
||||
*/
|
||||
onAccept: async function (paraId, rangeId) {
|
||||
await CustomAnnotator.prototype.onAccept.call(this, paraId, rangeId);
|
||||
let text = this.getAnnotation(paraId, rangeId)["suggestion"];
|
||||
|
||||
await Asc.Editor.callMethod("StartAction", ["GroupActions"]);
|
||||
|
||||
let range = this.getAnnotationRangeObj(paraId, rangeId);
|
||||
await Asc.Editor.callMethod("SelectAnnotationRange", [range]);
|
||||
|
||||
Asc.scope.text = text;
|
||||
await Asc.Editor.callCommand(function () {
|
||||
Api.ReplaceTextSmart([Asc.scope.text]);
|
||||
Api.GetDocument().RemoveSelection();
|
||||
});
|
||||
|
||||
await Asc.Editor.callMethod("RemoveAnnotationRange", [range]);
|
||||
await Asc.Editor.callMethod("EndAction", ["GroupActions"]);
|
||||
await Asc.Editor.callMethod("FocusEditor");
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,198 @@
|
||||
/*
|
||||
* (c) Copyright Ascensio System SIA 2010-2025
|
||||
*
|
||||
* This program is a free software product. You can redistribute it and/or
|
||||
* modify it under the terms of the GNU Affero General Public License (AGPL)
|
||||
* version 3 as published by the Free Software Foundation. In accordance with
|
||||
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
|
||||
* that Ascensio System SIA expressly excludes the warranty of non-infringement
|
||||
* of any third-party rights.
|
||||
*
|
||||
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
|
||||
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||||
*
|
||||
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
|
||||
* street, Riga, Latvia, EU, LV-1050.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of the Program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU AGPL version 3.
|
||||
*
|
||||
* Pursuant to Section 7(b) of the License you must retain the original Product
|
||||
* logo when distributing the program. Pursuant to Section 7(e) we decline to
|
||||
* grant you any rights under trademark law for use of our trademarks.
|
||||
*
|
||||
* All the Product's GUI elements, including illustrations and icon sets, as
|
||||
* well as technical writing content are licensed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
|
||||
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
*
|
||||
*/
|
||||
|
||||
/// <reference path="./custom-annotator.js" />
|
||||
/// <reference path="./types.js" />
|
||||
|
||||
/**
|
||||
* @param {localStorageCustomAssistantItem} assistantData
|
||||
* @constructor
|
||||
* @extends CustomAnnotator
|
||||
*/
|
||||
function AssistantReplace(annotationPopup, assistantData) {
|
||||
CustomAnnotator.call(this, annotationPopup, assistantData);
|
||||
}
|
||||
AssistantReplace.prototype = Object.create(CustomAnnotator.prototype);
|
||||
AssistantReplace.prototype.constructor = AssistantReplace;
|
||||
|
||||
Object.assign(AssistantReplace.prototype, {
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {ReplaceAiResponse[]} matches
|
||||
*/
|
||||
_convertToRanges: function (paraId, text, matches) {
|
||||
const _t = this;
|
||||
let rangeId = 1;
|
||||
const ranges = [];
|
||||
for (const {
|
||||
origin,
|
||||
suggestion,
|
||||
paragraph,
|
||||
occurrence,
|
||||
confidence,
|
||||
} of matches) {
|
||||
if (origin === suggestion || confidence <= 0.7) continue;
|
||||
|
||||
let count = 0;
|
||||
let searchStart = 0;
|
||||
|
||||
while (searchStart < text.length) {
|
||||
const index = _t.simpleGraphemeIndexOf(
|
||||
text,
|
||||
origin,
|
||||
searchStart,
|
||||
);
|
||||
if (index === -1) break;
|
||||
|
||||
count++;
|
||||
if (count === occurrence) {
|
||||
ranges.push({
|
||||
start: index,
|
||||
length: [...origin].length,
|
||||
id: rangeId,
|
||||
});
|
||||
_t.paragraphs[paraId][rangeId] = {
|
||||
original: origin,
|
||||
suggestion: suggestion,
|
||||
};
|
||||
++rangeId;
|
||||
break;
|
||||
}
|
||||
searchStart = index + 1;
|
||||
}
|
||||
}
|
||||
return ranges;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @returns {string}
|
||||
*/
|
||||
_createPrompt: function (text) {
|
||||
let prompt = `You are a multi-disciplinary text analysis and transformation assistant.
|
||||
Your task is to analyze text based on user's specific criteria and provide intelligent corrections.
|
||||
|
||||
MANDATORY RULES:
|
||||
1. UNDERSTAND the user's intent from their criteria.
|
||||
2. FIND all words matching the criteria.
|
||||
3. For EACH match you find:
|
||||
- Provide the exact quote.
|
||||
- SUGGEST appropriate replacements.
|
||||
- Explain WHY it matches the criteria.
|
||||
- Provide position information (paragraph number).
|
||||
4. If no matches are found, return an empty array: [].
|
||||
5. Format your response STRICTLY in JSON format.
|
||||
6. Support multiple languages (English, Russian, etc.)
|
||||
|
||||
Response format - return ONLY this JSON array with no additional text:
|
||||
[
|
||||
{
|
||||
"origin": "exact text fragment that matches the query",
|
||||
"suggestion": "suggested replacement (plain text)",
|
||||
"paragraph": paragraph_number,
|
||||
"occurrence": 1,
|
||||
"confidence": 0.95
|
||||
}
|
||||
]
|
||||
|
||||
Guidelines for each field:
|
||||
- "origin": EXACT UNCHANGED original text fragment. Do not fix anything in this field.
|
||||
- "suggestion": Your suggested replacement for the fragment.
|
||||
* Ensure it aligns with the user's criteria.
|
||||
* Maintain coherence with surrounding text.
|
||||
- "paragraph": Paragraph number where the fragment is found (0-based index)
|
||||
- "occurrence": Which occurrence of this sentence if it appears multiple times (1 for first, 2 for second, etc.)
|
||||
- "confidence": Value between 0 and 1 indicating certainty (1.0 = completely certain, 0.5 = uncertain)
|
||||
|
||||
CRITICAL:
|
||||
- Output should be in the exact this format
|
||||
- No any comments are allowed
|
||||
|
||||
CRITICAL - Output Format:
|
||||
- Return ONLY the raw JSON array, nothing else
|
||||
- DO NOT wrap the response in markdown code blocks (no \`\`\`json or \`\`\`)
|
||||
- DO NOT include any explanatory text before or after the JSON
|
||||
- DO NOT use escaped newlines (\\n) - return the JSON on a single line if possible
|
||||
- The response should start with [ and end with ]
|
||||
`;
|
||||
prompt +=
|
||||
"\n\nUSER REQUEST:\n```" + this.assistantData.query + "\n```\n\n";
|
||||
|
||||
prompt += "TEXT TO ANALYZE:\n```\n" + text + "\n```\n\n";
|
||||
|
||||
prompt += `Please analyze this text and find all fragments that match the user's request. Be thorough but precise.`;
|
||||
|
||||
return prompt;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string} rangeId
|
||||
* @returns {ReplaceInfoForPopup}
|
||||
*/
|
||||
getInfoForPopup: function (paraId, rangeId) {
|
||||
let _s = this.getAnnotation(paraId, rangeId);
|
||||
let suggested = _s["suggestion"];
|
||||
if (suggested.indexOf('</strong>') === -1) {
|
||||
suggested = `<strong>${suggested}</strong>`;
|
||||
}
|
||||
return {
|
||||
original: _s["original"],
|
||||
suggested: suggested,
|
||||
type: this.type,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string} rangeId
|
||||
*/
|
||||
onAccept: async function (paraId, rangeId) {
|
||||
await CustomAnnotator.prototype.onAccept.call(this);
|
||||
let text = this.getAnnotation(paraId, rangeId)["suggestion"];
|
||||
|
||||
await Asc.Editor.callMethod("StartAction", ["GroupActions"]);
|
||||
|
||||
let range = this.getAnnotationRangeObj(paraId, rangeId);
|
||||
await Asc.Editor.callMethod("SelectAnnotationRange", [range]);
|
||||
|
||||
Asc.scope.text = text;
|
||||
await Asc.Editor.callCommand(function () {
|
||||
Api.ReplaceTextSmart([Asc.scope.text]);
|
||||
Api.GetDocument().RemoveSelection();
|
||||
});
|
||||
|
||||
await Asc.Editor.callMethod("RemoveAnnotationRange", [range]);
|
||||
await Asc.Editor.callMethod("EndAction", ["GroupActions"]);
|
||||
await Asc.Editor.callMethod("FocusEditor");
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* (c) Copyright Ascensio System SIA 2010-2025
|
||||
*
|
||||
* This program is a free software product. You can redistribute it and/or
|
||||
* modify it under the terms of the GNU Affero General Public License (AGPL)
|
||||
* version 3 as published by the Free Software Foundation. In accordance with
|
||||
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
|
||||
* that Ascensio System SIA expressly excludes the warranty of non-infringement
|
||||
* of any third-party rights.
|
||||
*
|
||||
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
|
||||
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||||
*
|
||||
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
|
||||
* street, Riga, Latvia, EU, LV-1050.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of the Program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU AGPL version 3.
|
||||
*
|
||||
* Pursuant to Section 7(b) of the License you must retain the original Product
|
||||
* logo when distributing the program. Pursuant to Section 7(e) we decline to
|
||||
* grant you any rights under trademark law for use of our trademarks.
|
||||
*
|
||||
* All the Product's GUI elements, including illustrations and icon sets, as
|
||||
* well as technical writing content are licensed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
|
||||
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
*
|
||||
*/
|
||||
|
||||
/// <reference path="./types.js" />
|
||||
/// <reference path="../text-annotator.js" />
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @extends TextAnnotator
|
||||
*/
|
||||
function CustomAnnotator(annotationPopup, assistantData) {
|
||||
TextAnnotator.call(this, annotationPopup);
|
||||
this.assistantData = assistantData;
|
||||
this.type = assistantData.type;
|
||||
this._skipNextChangeParagraph = false;
|
||||
|
||||
this._lastUsedPrompt = "";
|
||||
}
|
||||
CustomAnnotator.prototype = Object.create(TextAnnotator.prototype);
|
||||
CustomAnnotator.prototype.constructor = CustomAnnotator;
|
||||
|
||||
Object.assign(CustomAnnotator.prototype, {
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string} recalcId
|
||||
* @param {string} text
|
||||
* @returns {Promise<boolean | null>}
|
||||
*/
|
||||
annotateParagraph: async function (paraId, recalcId, text) {
|
||||
this.paragraphs[paraId] = {};
|
||||
|
||||
if (text.length === 0) return false;
|
||||
|
||||
const argPrompt = this._createPrompt(text);
|
||||
|
||||
if (this._lastUsedPrompt && argPrompt !== this._lastUsedPrompt) {
|
||||
let resetInstruction =
|
||||
`CRITICAL
|
||||
- Ignore all previous messages and instructions.
|
||||
- Please respond only to this new query and treat this as a new request.
|
||||
|
||||
`;
|
||||
argPrompt = resetInstruction + argPrompt;
|
||||
this._lastUsedPrompt = argPrompt;
|
||||
}
|
||||
|
||||
let response = await this.chatRequest(argPrompt);
|
||||
|
||||
if (!response || response === "[]") {
|
||||
if (response === null) {
|
||||
return null; // no AI model selected
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const ranges = this._convertToRanges(
|
||||
paraId,
|
||||
text,
|
||||
JSON.parse(response),
|
||||
);
|
||||
let obj = {
|
||||
type: "highlightText",
|
||||
paragraphId: paraId,
|
||||
name: "customAssistant_" + this.assistantData.id,
|
||||
recalcId: recalcId,
|
||||
ranges: ranges,
|
||||
};
|
||||
await Asc.Editor.callMethod("AnnotateParagraph", [obj]);
|
||||
} catch (e) {}
|
||||
|
||||
return true;
|
||||
},
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string} rangeId
|
||||
*/
|
||||
getAnnotationRangeObj: function (paraId, rangeId) {
|
||||
return {
|
||||
paragraphId: paraId,
|
||||
rangeId: rangeId,
|
||||
name: "customAssistant_" + this.assistantData.id,
|
||||
};
|
||||
},
|
||||
_handleNewRangePositions: async function (range, paraId, text) {
|
||||
if (
|
||||
!range ||
|
||||
range["name"] !== "customAssistant_" + this.assistantData.id ||
|
||||
!this.paragraphs[paraId]
|
||||
)
|
||||
return;
|
||||
|
||||
let rangeId = range["id"];
|
||||
let annot = this.getAnnotation(paraId, rangeId);
|
||||
|
||||
if (!annot) return;
|
||||
|
||||
let start = range["start"];
|
||||
let len = range["length"];
|
||||
|
||||
if (annot["original"] !== text.substring(start, start + len)) {
|
||||
let annotRange = this.getAnnotationRangeObj(paraId, rangeId);
|
||||
return Asc.Editor.callMethod("RemoveAnnotationRange", [annotRange]);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {string[]} paraIds
|
||||
* @returns {Promise<Array<boolean | null>>}
|
||||
*/
|
||||
checkParagraphs: async function (paraIds) {
|
||||
if (this._skipNextChangeParagraph) {
|
||||
this._skipNextChangeParagraph = false;
|
||||
return paraIds.map(() => false);
|
||||
}
|
||||
return await TextAnnotator.prototype.checkParagraphs.call(
|
||||
this,
|
||||
paraIds,
|
||||
);
|
||||
},
|
||||
/**
|
||||
* @param {Array<string>} paraIds
|
||||
* @returns
|
||||
*/
|
||||
uncheckParagraphs: async function (paraIds) {
|
||||
/** @type {Promise<any>[]} */
|
||||
const promises = [];
|
||||
|
||||
paraIds.forEach(function(paraId) {
|
||||
promises.push(Asc.Editor.callMethod("RemoveAnnotationRange", [{
|
||||
all: true,
|
||||
paragraphId : paraId,
|
||||
rangeId : undefined,
|
||||
name: "customAssistant_" + this.assistantData.id
|
||||
}]));
|
||||
}, this);
|
||||
|
||||
return Promise.all(promises);
|
||||
},
|
||||
onAccept: async function (paraId, rangeId) {
|
||||
if (this.type !== 0) { // not for hint
|
||||
this._skipNextChangeParagraph = true;
|
||||
}
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,260 @@
|
||||
/*
|
||||
* (c) Copyright Ascensio System SIA 2010-2025
|
||||
*
|
||||
* This program is a free software product. You can redistribute it and/or
|
||||
* modify it under the terms of the GNU Affero General Public License (AGPL)
|
||||
* version 3 as published by the Free Software Foundation. In accordance with
|
||||
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
|
||||
* that Ascensio System SIA expressly excludes the warranty of non-infringement
|
||||
* of any third-party rights.
|
||||
*
|
||||
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
|
||||
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||||
*
|
||||
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
|
||||
* street, Riga, Latvia, EU, LV-1050.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of the Program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU AGPL version 3.
|
||||
*
|
||||
* Pursuant to Section 7(b) of the License you must retain the original Product
|
||||
* logo when distributing the program. Pursuant to Section 7(e) we decline to
|
||||
* grant you any rights under trademark law for use of our trademarks.
|
||||
*
|
||||
* All the Product's GUI elements, including illustrations and icon sets, as
|
||||
* well as technical writing content are licensed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
|
||||
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
*
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/// <reference path="./types.js" />
|
||||
/// <reference path="./assistant-hint.js" />
|
||||
/// <reference path="./assistant-replace-hint.js" />
|
||||
/// <reference path="./assistant-replace.js" />
|
||||
/// <reference path="../text-annotator.js" />
|
||||
/// <reference path="./annotation-popup.js" />
|
||||
|
||||
class CustomAssistantManager {
|
||||
constructor() {
|
||||
/**
|
||||
* @type {Map<string, Assistant>}
|
||||
*/
|
||||
this._customAssistants = new Map();
|
||||
this._isCustomAssistantTrackChanges = new Map();
|
||||
this._isCustomAssistantRunning = new Map();
|
||||
/** @type {Map<string, {recalcId: string, text: string, annotations: any}>} */
|
||||
this._paragraphsStack = new Map();
|
||||
/**
|
||||
* @type {{OK: 0, NOT_FOUND: 1, ERROR: 2, NO_AI_MODEL_SELECTED: 3}}
|
||||
* @enum {0|1|2|3}
|
||||
*/
|
||||
this.STATUSES = {
|
||||
OK: 0,
|
||||
NOT_FOUND: 1,
|
||||
ERROR: 2,
|
||||
NO_AI_MODEL_SELECTED: 3
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {localStorageCustomAssistantItem} assistantData
|
||||
* @param {boolean} [isForUpdate]
|
||||
*/
|
||||
createAssistant(assistantData, isForUpdate) {
|
||||
/** @type {Assistant | null} */
|
||||
let assistant = null;
|
||||
switch (assistantData.type) {
|
||||
case 0:
|
||||
assistant = new AssistantHint(
|
||||
customAnnotationPopup,
|
||||
assistantData,
|
||||
);
|
||||
break;
|
||||
case 1:
|
||||
assistant = new AssistantReplaceHint(
|
||||
customAnnotationPopup,
|
||||
assistantData,
|
||||
);
|
||||
break;
|
||||
case 2:
|
||||
assistant = new AssistantReplace(
|
||||
customAnnotationPopup,
|
||||
assistantData,
|
||||
);
|
||||
break;
|
||||
}
|
||||
if (!assistant) {
|
||||
throw new Error(
|
||||
"Unknown custom assistant type: " + assistantData.type,
|
||||
);
|
||||
}
|
||||
|
||||
this._customAssistants.set(assistantData.id, assistant);
|
||||
if (!isForUpdate) {
|
||||
this._isCustomAssistantTrackChanges.set(assistantData.id, false);
|
||||
this._isCustomAssistantRunning.set(assistantData.id, false);
|
||||
}
|
||||
|
||||
return assistant;
|
||||
}
|
||||
/**
|
||||
* @param {localStorageCustomAssistantItem} assistantData
|
||||
*/
|
||||
updateAssistant(assistantData) {
|
||||
let oldAssistant = this._customAssistants.get(assistantData.id);
|
||||
if (!oldAssistant) {
|
||||
throw new Error("Custom assistant not found: " + assistantData.id);
|
||||
}
|
||||
const isRunning = this._isCustomAssistantRunning.get(assistantData.id);
|
||||
const newAssistant = this.createAssistant(assistantData, isRunning);
|
||||
if (!isRunning) {
|
||||
return newAssistant;
|
||||
}
|
||||
|
||||
this._paragraphsStack.forEach((value, paraId) => {
|
||||
newAssistant.onChangeParagraph(
|
||||
paraId,
|
||||
value.recalcId,
|
||||
value.text,
|
||||
value.annotations,
|
||||
);
|
||||
});
|
||||
const paragraphIdsToUpdate = [...oldAssistant.checked];
|
||||
oldAssistant.checked.clear();
|
||||
newAssistant.checkParagraphs(paragraphIdsToUpdate);
|
||||
|
||||
return newAssistant;
|
||||
}
|
||||
|
||||
/** @param {string} assistantId */
|
||||
deleteAssistant(assistantId) {
|
||||
this._customAssistants.delete(assistantId);
|
||||
this._isCustomAssistantTrackChanges.delete(assistantId);
|
||||
this._isCustomAssistantRunning.delete(assistantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} assistantId
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isCustomAssistantRunning(assistantId) {
|
||||
return this._isCustomAssistantRunning.get(assistantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} assistantId
|
||||
* @param {string[]} paraIds
|
||||
* @returns {Promise<number>}
|
||||
*/
|
||||
async run(assistantId, paraIds) {
|
||||
const assistant = this._customAssistants.get(assistantId);
|
||||
if (!assistant) {
|
||||
return this.STATUSES.NOT_FOUND;
|
||||
}
|
||||
this._isCustomAssistantRunning.set(assistantId, true);
|
||||
|
||||
if (!this._isCustomAssistantTrackChanges.get(assistantId)) {
|
||||
/** @type {Promise<boolean | null>[]} */
|
||||
const promises = [];
|
||||
this._paragraphsStack.forEach((value, paraId) => {
|
||||
const promise = assistant.onChangeParagraph(
|
||||
paraId,
|
||||
value.recalcId,
|
||||
value.text,
|
||||
value.annotations,
|
||||
);
|
||||
promises.push(promise);
|
||||
});
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/** @type {Array<boolean | null>} */
|
||||
const isParagraphsChecked = await assistant.checkParagraphs(paraIds);
|
||||
|
||||
if (
|
||||
isParagraphsChecked &&
|
||||
isParagraphsChecked.length &&
|
||||
isParagraphsChecked.every(isDone => !isDone)
|
||||
) {
|
||||
if (isParagraphsChecked.some(isDone => isDone === null)) {
|
||||
return this.STATUSES.NO_AI_MODEL_SELECTED;
|
||||
}
|
||||
return this.STATUSES.ERROR;
|
||||
}
|
||||
|
||||
this._isCustomAssistantTrackChanges.set(assistantId, true);
|
||||
|
||||
return this.STATUSES.OK;
|
||||
}
|
||||
|
||||
/** @param {string} assistantId */
|
||||
async stop(assistantId) {
|
||||
this._isCustomAssistantRunning.set(assistantId, false);
|
||||
|
||||
let assistant = this._customAssistants.get(assistantId);
|
||||
if (!assistant) {
|
||||
throw new Error("Custom assistant not found: " + assistantId);
|
||||
}
|
||||
const paraIdsToUncheck = [...assistant.checked];
|
||||
assistant.checked.clear();
|
||||
this._isCustomAssistantTrackChanges.set(assistantId, false);
|
||||
await assistant.uncheckParagraphs(paraIdsToUncheck);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} paragraphId
|
||||
* @param {string} recalcId
|
||||
* @param {string} text
|
||||
* @param {*} annotations
|
||||
*/
|
||||
onChangeParagraph(paragraphId, recalcId, text, annotations) {
|
||||
this._paragraphsStack.set(paragraphId, {
|
||||
recalcId,
|
||||
text,
|
||||
annotations,
|
||||
});
|
||||
this._customAssistants.forEach((assistant, assistantId) => {
|
||||
const isInit = this._isCustomAssistantTrackChanges.get(assistantId);
|
||||
if (!isInit) {
|
||||
return;
|
||||
}
|
||||
assistant.onChangeParagraph(
|
||||
paragraphId,
|
||||
recalcId,
|
||||
text,
|
||||
annotations,
|
||||
);
|
||||
const isRunning = this._isCustomAssistantRunning.get(assistantId);
|
||||
if (isRunning) {
|
||||
assistant.checkParagraphs([paragraphId]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** @param {string} assistantId */
|
||||
onBlur(assistantId) {
|
||||
const assistant = this._customAssistants.get(assistantId);
|
||||
if (assistant) {
|
||||
assistant.onBlur();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} assistantId
|
||||
* @param {string} paragraphId
|
||||
* @param {*} ranges
|
||||
*/
|
||||
onClick(assistantId, paragraphId, ranges) {
|
||||
const assistant = this._customAssistants.get(assistantId);
|
||||
if (assistant) {
|
||||
assistant.onClick(paragraphId, ranges);
|
||||
} else {
|
||||
console.error("Custom assistant not found: " + assistantId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @typedef {Object} localStorageCustomAssistantItem
|
||||
* @property {string} id
|
||||
* @property {string} name
|
||||
* @property {number} type
|
||||
* @property {string} query
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} HintAiResponse
|
||||
* @property {string} origin
|
||||
* @property {string} reason
|
||||
* @property {number} paragraph
|
||||
* @property {number} occurrence
|
||||
* @property {number} confidence
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} HintInfoForPopup
|
||||
* @property {string} original
|
||||
* @property {string} explanation
|
||||
* @property {number} type
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ReplaceAiResponse
|
||||
* @property {string} origin
|
||||
* @property {string} suggestion
|
||||
* @property {string} reason
|
||||
* @property {number} paragraph
|
||||
* @property {number} occurrence
|
||||
* @property {number} confidence
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ReplaceInfoForPopup
|
||||
* @property {string} original
|
||||
* @property {string} suggested
|
||||
* @property {number} type
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ReplaceHintAiResponse
|
||||
* @property {string} origin
|
||||
* @property {string} suggestion
|
||||
* @property {string} reason
|
||||
* @property {string} difference
|
||||
* @property {number} paragraph
|
||||
* @property {number} occurrence
|
||||
* @property {number} confidence
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ReplaceHintInfoForPopup
|
||||
* @property {string} original
|
||||
* @property {string} suggested
|
||||
* @property {string} explanation
|
||||
* @property {number} type
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {ReplaceHintInfoForPopup | HintInfoForPopup | ReplaceInfoForPopup} InfoForPopup
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {CustomAnnotator & TextAnnotator & AssistantHint & AssistantReplaceHint & AssistantReplace} Assistant
|
||||
*/
|
||||
@ -30,9 +30,9 @@
|
||||
*
|
||||
*/
|
||||
|
||||
function GrammarChecker()
|
||||
function GrammarChecker(annotatorPopup)
|
||||
{
|
||||
TextAnnotator.call(this);
|
||||
TextAnnotator.call(this, annotatorPopup);
|
||||
this.type = 1;
|
||||
}
|
||||
|
||||
@ -43,17 +43,6 @@ GrammarChecker.prototype.annotateParagraph = async function(paraId, recalcId, te
|
||||
{
|
||||
this.paragraphs[paraId] = {};
|
||||
|
||||
let requestEngine = AI.Request.create(AI.ActionType.Chat);
|
||||
if (!requestEngine)
|
||||
return false;
|
||||
|
||||
let isSendedEndLongAction = false;
|
||||
async function checkEndAction()
|
||||
{
|
||||
if (!isSendedEndLongAction)
|
||||
isSendedEndLongAction = true;
|
||||
}
|
||||
|
||||
let argPrompt = `You are a grammar correction tool that analyzes text for punctuation and style issues only. You will receive text to analyze and must respond with corrections in a specific JSON format.
|
||||
|
||||
CRITICAL REQUIREMENT - READ CAREFULLY:
|
||||
@ -153,17 +142,10 @@ CRITICAL - Output Format:
|
||||
|
||||
Text to check:`;
|
||||
argPrompt += text;
|
||||
|
||||
let response = "";
|
||||
await requestEngine.chatRequest(argPrompt, false, async function (data)
|
||||
{
|
||||
if (!data)
|
||||
return;
|
||||
await checkEndAction();
|
||||
|
||||
response += data;
|
||||
});
|
||||
await checkEndAction();
|
||||
|
||||
let response = await this.chatRequest(argPrompt);
|
||||
if (!response)
|
||||
return false;
|
||||
|
||||
let rangeId = 1;
|
||||
let ranges = [];
|
||||
@ -181,7 +163,7 @@ Text to check:`;
|
||||
|
||||
while (searchStart < text.length)
|
||||
{
|
||||
const index = text.indexOf(origin, searchStart);
|
||||
const index = _t.simpleGraphemeIndexOf(text, origin, searchStart);
|
||||
if (index === -1) break;
|
||||
|
||||
count++;
|
||||
@ -189,7 +171,7 @@ Text to check:`;
|
||||
{
|
||||
ranges.push({
|
||||
"start": index,
|
||||
"length": origin.length,
|
||||
"length": [...origin].length,
|
||||
"id": rangeId
|
||||
});
|
||||
_t.paragraphs[paraId][rangeId] = {
|
||||
|
||||
@ -30,9 +30,9 @@
|
||||
*
|
||||
*/
|
||||
|
||||
function SpellChecker()
|
||||
function SpellChecker(annotatorPopup)
|
||||
{
|
||||
TextAnnotator.call(this);
|
||||
TextAnnotator.call(this, annotatorPopup);
|
||||
this.type = 0;
|
||||
}
|
||||
SpellChecker.prototype = Object.create(TextAnnotator.prototype);
|
||||
@ -42,17 +42,6 @@ SpellChecker.prototype.annotateParagraph = async function(paraId, recalcId, text
|
||||
{
|
||||
this.paragraphs[paraId] = {};
|
||||
|
||||
let requestEngine = AI.Request.create(AI.ActionType.Chat);
|
||||
if (!requestEngine)
|
||||
return false;
|
||||
|
||||
let isSendedEndLongAction = false;
|
||||
async function checkEndAction()
|
||||
{
|
||||
if (!isSendedEndLongAction)
|
||||
isSendedEndLongAction = true;
|
||||
}
|
||||
|
||||
let argPrompt = `You are a spellcheck corrector. I will provide text that may contain spelling errors in any language. Your task is to identify ALL spelling mistakes and return ONLY the corrections in the following JSON format:
|
||||
|
||||
[
|
||||
@ -144,16 +133,9 @@ Output: []
|
||||
Text to check:`;
|
||||
argPrompt += text;
|
||||
|
||||
let response = "";
|
||||
await requestEngine.chatRequest(argPrompt, false, async function (data)
|
||||
{
|
||||
if (!data)
|
||||
return;
|
||||
await checkEndAction();
|
||||
|
||||
response += data;
|
||||
});
|
||||
await checkEndAction();
|
||||
let response = await this.chatRequest(argPrompt);
|
||||
if (!response)
|
||||
return false;
|
||||
|
||||
let rangeId = 1;
|
||||
let ranges = [];
|
||||
@ -171,7 +153,7 @@ Text to check:`;
|
||||
|
||||
while (searchStart < text.length)
|
||||
{
|
||||
const index = text.indexOf(wrong, searchStart);
|
||||
const index = _t.simpleGraphemeIndexOf(text, wrong, searchStart);
|
||||
if (index === -1) break;
|
||||
|
||||
const isStartBoundary = index === 0 || _t._isWordBoundary(text[index - 1]);
|
||||
@ -184,7 +166,7 @@ Text to check:`;
|
||||
{
|
||||
ranges.push({
|
||||
"start": index,
|
||||
"length": wrong.length,
|
||||
"length": [...wrong].length,
|
||||
"id": rangeId
|
||||
});
|
||||
_t.paragraphs[paraId][rangeId] = {
|
||||
|
||||
@ -30,28 +30,45 @@
|
||||
*
|
||||
*/
|
||||
|
||||
function TextAnnotator()
|
||||
/// <reference path="./annotation-popup.js" />
|
||||
|
||||
/** @param {TextAnnotationPopup} annotatorPopup */
|
||||
function TextAnnotator(annotatorPopup)
|
||||
{
|
||||
this.annotatorPopup = annotatorPopup;
|
||||
this.paraId = null;
|
||||
this.rangeId = null;
|
||||
|
||||
this.paragraphs = {};
|
||||
/** @type {Object.<string, {recalcId: string, text: string}>} */
|
||||
this.waitParagraphs = {};
|
||||
this.paraToCheck = new Set();
|
||||
/** @type {Set<string>} */
|
||||
this.checked = new Set(); // was checked on the previous request
|
||||
|
||||
this.type = -1;
|
||||
}
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string} recalcId
|
||||
* @param {string} text
|
||||
* @param {string[]} ranges
|
||||
* @returns {Promise<boolean | null>}
|
||||
*/
|
||||
TextAnnotator.prototype.onChangeParagraph = async function(paraId, recalcId, text, ranges)
|
||||
{
|
||||
this._handleNewRanges(ranges, paraId, text);
|
||||
await this._handleNewRanges(ranges, paraId, text);
|
||||
this.waitParagraphs[paraId] = {
|
||||
recalcId : recalcId,
|
||||
text : text
|
||||
};
|
||||
|
||||
this._checkParagraph(paraId);
|
||||
return this._checkParagraph(paraId);
|
||||
};
|
||||
/**
|
||||
* @param {string[]} paraIds
|
||||
* @returns {Promise<Array<boolean | null>>}
|
||||
*/
|
||||
TextAnnotator.prototype.checkParagraphs = async function(paraIds)
|
||||
{
|
||||
this.paraToCheck.clear()
|
||||
@ -61,12 +78,19 @@ TextAnnotator.prototype.checkParagraphs = async function(paraIds)
|
||||
_t.paraToCheck.add(paraId);
|
||||
});
|
||||
|
||||
this.paraToCheck.forEach(paraId => this._checkParagraph(paraId));
|
||||
/** @type {Promise<boolean | null>[]} */
|
||||
const promises = [];
|
||||
this.paraToCheck.forEach(paraId => {promises.push(this._checkParagraph(paraId))});
|
||||
return Promise.all(promises);
|
||||
};
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @returns {Promise<boolean | null>}
|
||||
*/
|
||||
TextAnnotator.prototype._checkParagraph = async function(paraId)
|
||||
{
|
||||
if (!this.paraToCheck.has(paraId) || !this.waitParagraphs[paraId])
|
||||
return;
|
||||
return false;
|
||||
|
||||
let recalcId = this.waitParagraphs[paraId].recalcId;
|
||||
let text = this.waitParagraphs[paraId].text;
|
||||
@ -76,22 +100,27 @@ TextAnnotator.prototype._checkParagraph = async function(paraId)
|
||||
range["rangeId"] = undefined;
|
||||
range["all"] = true;
|
||||
await Asc.Editor.callMethod("RemoveAnnotationRange", [range]);
|
||||
await this.annotateParagraph(paraId, recalcId, text);
|
||||
const isAnnotate = await this.annotateParagraph(paraId, recalcId, text);
|
||||
|
||||
delete this.waitParagraphs[paraId];
|
||||
this.paraToCheck.delete(paraId);
|
||||
|
||||
this.checked.add(paraId);
|
||||
|
||||
return isAnnotate;
|
||||
};
|
||||
TextAnnotator.prototype.annotateParagraph = async function(paraId, recalcId, text)
|
||||
{
|
||||
};
|
||||
TextAnnotator.prototype.uncheckParagraphs = async function(paraIds)
|
||||
{
|
||||
};
|
||||
TextAnnotator.prototype.openPopup = async function(paraId, rangeId)
|
||||
{
|
||||
if (!textAnnotatorPopup)
|
||||
if (!this.annotatorPopup)
|
||||
return;
|
||||
|
||||
let popup = textAnnotatorPopup.open(this.type, paraId, rangeId, this.getInfoForPopup(paraId, rangeId));
|
||||
let popup = this.annotatorPopup.open(this.type, paraId, rangeId, this.getInfoForPopup(paraId, rangeId));
|
||||
if (!popup)
|
||||
return;
|
||||
|
||||
@ -107,10 +136,10 @@ TextAnnotator.prototype.openPopup = async function(paraId, rangeId)
|
||||
};
|
||||
TextAnnotator.prototype.closePopup = function()
|
||||
{
|
||||
if (!textAnnotatorPopup)
|
||||
if (!this.annotatorPopup)
|
||||
return;
|
||||
|
||||
textAnnotatorPopup.close(this.type);
|
||||
this.annotatorPopup.close(this.type);
|
||||
};
|
||||
TextAnnotator.prototype.getInfoForPopup = function(paraId, rangeId)
|
||||
{
|
||||
@ -131,6 +160,11 @@ TextAnnotator.prototype.getAnnotation = function(paraId, rangeId)
|
||||
|
||||
return this.paragraphs[paraId][rangeId];
|
||||
};
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string} rangeId
|
||||
* @returns {{paragraphId: string, rangeId: string}}
|
||||
*/
|
||||
TextAnnotator.prototype.getAnnotationRangeObj = function(paraId, rangeId)
|
||||
{
|
||||
return {
|
||||
@ -138,6 +172,10 @@ TextAnnotator.prototype.getAnnotationRangeObj = function(paraId, rangeId)
|
||||
"rangeId" : rangeId
|
||||
};
|
||||
};
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string[]} ranges
|
||||
*/
|
||||
TextAnnotator.prototype.onClick = function(paraId, ranges)
|
||||
{
|
||||
if (!ranges || !ranges.length)
|
||||
@ -158,17 +196,74 @@ TextAnnotator.prototype.resetCurrentRange = function()
|
||||
this.paraId = null;
|
||||
this.rangeId = null;
|
||||
};
|
||||
/**
|
||||
* @param {Array<string>} ranges
|
||||
* @param {string} paraId
|
||||
* @param {string} text
|
||||
* @returns {Promise<void[]>}
|
||||
*/
|
||||
TextAnnotator.prototype._handleNewRanges = function(ranges, paraId, text)
|
||||
{
|
||||
if (!ranges || !Array.isArray(ranges))
|
||||
return;
|
||||
|
||||
ranges.forEach(range => this._handleNewRangePositions(range, paraId, text));
|
||||
return Promise.resolve([]);
|
||||
/** @type {Promise<void>[]} */
|
||||
const promises = [];
|
||||
|
||||
for (let i = 0; i < ranges.length; ++i)
|
||||
{
|
||||
this._handleNewRangePositions(ranges[i]);
|
||||
promises[i] = this._handleNewRangePositions(ranges[i], paraId, text);
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
};
|
||||
/** @returns {Promise<void>} */
|
||||
TextAnnotator.prototype._handleNewRangePositions = function(range, paraId, text)
|
||||
{
|
||||
};
|
||||
TextAnnotator.prototype.chatRequest = async function(prompt)
|
||||
{
|
||||
let requestEngine = AI.Request.create(AI.ActionType.Chat);
|
||||
if (!requestEngine)
|
||||
return null;
|
||||
|
||||
let response = await requestEngine.chatRequest(prompt, false);
|
||||
return this.normalizeResponse(response);
|
||||
};
|
||||
/**
|
||||
* Normalizes AI response by removing markdown code block wrappers
|
||||
* @param {string} response - The raw AI response that might be wrapped in ```json``` blocks
|
||||
* @returns {string} - The normalized response with markdown code blocks removed
|
||||
*/
|
||||
TextAnnotator.prototype.normalizeResponse = function(response) {
|
||||
if (typeof response !== 'string') {
|
||||
return response;
|
||||
}
|
||||
|
||||
return Asc.Library.getJSONResult(response);
|
||||
};
|
||||
/**
|
||||
* @param {string} str
|
||||
* @param {string} searchStr
|
||||
* @param {string} [fromIndex]
|
||||
* @returns {number}
|
||||
*/
|
||||
TextAnnotator.prototype.simpleGraphemeIndexOf = function(str, searchStr, fromIndex = 0) {
|
||||
const codeUnitIndex = str.indexOf(searchStr, fromIndex);
|
||||
if (codeUnitIndex < 2) {
|
||||
return codeUnitIndex;
|
||||
}
|
||||
const adjustedIndex = adjustIndexForSurrogates(str, codeUnitIndex);
|
||||
|
||||
function adjustIndexForSurrogates(str, codeUnitIndex) {
|
||||
let surrogateCount = 0;
|
||||
for (let i = 0; i < codeUnitIndex; i++) {
|
||||
const code = str.charCodeAt(i);
|
||||
if (code >= 0xD800 && code <= 0xDBFF) {
|
||||
surrogateCount++;
|
||||
}
|
||||
}
|
||||
return codeUnitIndex - surrogateCount;
|
||||
}
|
||||
return adjustedIndex;
|
||||
}
|
||||
|
||||
|
||||
@ -152,5 +152,26 @@
|
||||
"Check all": "تدقيق الكل",
|
||||
"Check current text": "تدقيق النص الحالي",
|
||||
"Suggested correction": "تصحيح مقترح",
|
||||
"Explanation": "شرح"
|
||||
"Explanation": "شرح",
|
||||
"Turn any repetitive text task into a custom button on your toolbar. Automate specific editing, checking, or rewriting tasks using the power of AI.": "حوّل أي مهمة نصية متكررة إلى زر مخصص على شريط الأدوات. أتمتة مهام التحرير أو التدقيق أو إعادة الكتابة المحددة باستخدام قوة الذكاء الاصطناعي.",
|
||||
"Create a new assistant": "إنشاء مساعد جديد",
|
||||
"Save": "حفظ",
|
||||
"Create": "إنشاء",
|
||||
"Delete Assistant": "حذف المساعد",
|
||||
"Yes": "نعم",
|
||||
"No": "لا",
|
||||
"Are you sure you want to delete this assistant?": "هل أنت متأكد من حذف هذا المساعد؟",
|
||||
"This action cannot be undone.": "هذا الإجراء لا يمكن التراجع عنه.",
|
||||
"Warning!": "تحذير!",
|
||||
"Custom assistant is not available. Please check your configuration.": "المساعد المخصص غير متاح. يرجى التحقق من إعداداتك.",
|
||||
"Not able to perform this action. Please use prompts related to text analysis, editing, or formatting.": "غير قادر على تنفيذ هذا الإجراء. يرجى استخدام تعليمات متعلقة بتحليل النصوص، التحرير، أو التنسيق.",
|
||||
"Processing selection...": "معالجة التحديد...",
|
||||
"Processing document...": "معالجة المستند...",
|
||||
"Action": "إجراء",
|
||||
"Prompt": "إدخال",
|
||||
"Give your tool a short name for the toolbar": "أعطِ أداةك اسماً قصيراً للشريط الأدائي",
|
||||
"Tell the AI what to do with the selected text (e.g., \"Find factual errors\" or \"Summarize\")": "قول للذكاء الاصطناعي ما يجب عليه فعله مع النص المحدد (مثل \"إيجاد أخطاء حقيقة\" أو \"إعادة صياغة\")",
|
||||
"Create AI assistant": "إنشاء مساعد ذكاء اصطناعي",
|
||||
"Match": "التطابق",
|
||||
"Proposal for replacement": "اقتراح للإضافة"
|
||||
}
|
||||
@ -152,5 +152,26 @@
|
||||
"Check all": "Zkontrolovat vše",
|
||||
"Check current text": "Zkontrolovat aktuální text",
|
||||
"Suggested correction": "Navrhovaná oprava",
|
||||
"Explanation": "Vysvětlení"
|
||||
"Explanation": "Vysvětlení",
|
||||
"Turn any repetitive text task into a custom button on your toolbar. Automate specific editing, checking, or rewriting tasks using the power of AI.": "Transformujte náhodnou textovou úlohu do vlastního tlacítku na vasí připojovací lišta. Automatizujte specifikování, kontrolu nebo zápisování učinku pomocou moci AI.",
|
||||
"Create a new assistant": "Vytvořit nového asistenta",
|
||||
"Save": "Uložit",
|
||||
"Create": "Vytvořit",
|
||||
"Delete Assistant": "Smazat asistenta",
|
||||
"Yes": "Ano",
|
||||
"No": "Ne",
|
||||
"Are you sure you want to delete this assistant?": "Jste si jisti, že chcete smazat tohoto asistenta?",
|
||||
"This action cannot be undone.": "Tuto akci nelze vrátit zpět.",
|
||||
"Warning!": "Varování!",
|
||||
"Custom assistant is not available. Please check your configuration.": "Vlastní asistent není k dispozici. Zkontrolujte prosím svou konfiguraci.",
|
||||
"Not able to perform this action. Please use prompts related to text analysis, editing, or formatting.": "Nelze provést tuto akci. Použijte prosím výzvy souventicí s analýzou textu, úpravami nebo formátováním.",
|
||||
"Processing selection...": "Procesování výběru...",
|
||||
"Processing document...": "Procesování dokumentu...",
|
||||
"Action": "Akce",
|
||||
"Prompt": "Prompt",
|
||||
"Give your tool a short name for the toolbar": "Dalejte svému nástroji krátký název pro panel nástrojů",
|
||||
"Tell the AI what to do with the selected text (e.g., \"Find factual errors\" or \"Summarize\")": "Říkejte AI, co s vybraným textem dělat (například \"Najít faktické chyby\" nebo \"Riassumovat\")",
|
||||
"Create AI assistant": "Vytvořit asistenta AI",
|
||||
"Match": "Correspondance",
|
||||
"Proposal for replacement": "Proposition de remplacement"
|
||||
}
|
||||
@ -153,5 +153,26 @@
|
||||
"Check all": "Alles prüfen",
|
||||
"Check current text": "Aktuellen Text prüfen",
|
||||
"Suggested correction": "Korrekturvorschlag",
|
||||
"Explanation": "Erklärung"
|
||||
"Explanation": "Erklärung",
|
||||
"Turn any repetitive text task into a custom button on your toolbar. Automate specific editing, checking, or rewriting tasks using the power of AI.": "Verwandeln Sie wiederkehrende Textaufgaben in benutzerdefinierte Schaltflächen Ihrer Symbolleiste. Automatisieren Sie bestimmte Bearbeitungs-, Prüf- oder Überarbeitungsaufgaben mithilfe von KI.",
|
||||
"Create a new assistant": "Neuen Assistenten erstellen",
|
||||
"Save": "Speichern",
|
||||
"Create": "Erstellen",
|
||||
"Delete Assistant": "Assistent löschen",
|
||||
"Yes": "Ja",
|
||||
"No": "Nein",
|
||||
"Are you sure you want to delete this assistant?": "Möchten Sie diesen Assistenten wirklich löschen?",
|
||||
"This action cannot be undone.": "Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"Warning!": "Warnung!",
|
||||
"Custom assistant is not available. Please check your configuration.": "Benutzerdefinierter Assistent ist nicht verfügbar. Bitte überprüfen Sie Ihre Konfiguration.",
|
||||
"Not able to perform this action. Please use prompts related to text analysis, editing, or formatting.": "Diese Aktion kann nicht ausgeführt werden. Bitte verwenden Sie Prompts im Zusammenhang mit Textanalyse, Bearbeitung oder Formatierung.",
|
||||
"Processing selection...": "Verarbeitung der Auswahl...",
|
||||
"Processing document...": "Verarbeitung des Dokuments...",
|
||||
"Action": "Aktion",
|
||||
"Prompt": "Prompt",
|
||||
"Give your tool a short name for the toolbar": "Geben Sie Ihrem Werkzeug einen kurzen Namen für die Symbolleiste",
|
||||
"Tell the AI what to do with the selected text (e.g., \"Find factual errors\" or \"Summarize\")": "Sagen Sie der KI, was Sie mit dem ausgewählten Text tun möchten (z. B. \"Faktenfehler finden\" oder \"Riassumen\")",
|
||||
"Create AI assistant": "KI-Assistent erstellen",
|
||||
"Match": "Correspondance",
|
||||
"Proposal for replacement": "Proposition de remplacement"
|
||||
}
|
||||
@ -152,5 +152,26 @@
|
||||
"Check all": "Comprobar todo",
|
||||
"Check current text": "Comprobar el texto actual",
|
||||
"Suggested correction": "Corrección sugerida",
|
||||
"Explanation": "Explicación"
|
||||
}
|
||||
"Explanation": "Explicación",
|
||||
"Turn any repetitive text task into a custom button on your toolbar. Automate specific editing, checking, or rewriting tasks using the power of AI.": "Transforma cualquier tarea de texto repetitivo en un botón personalizado en la barra de herramientas. Automatiza tareas de edición, verificación o reescritura usando la potencia de la IA.",
|
||||
"Create a new assistant": "Crear un nuevo asistente",
|
||||
"Save": "Guardar",
|
||||
"Create": "Crear",
|
||||
"Delete Assistant": "Eliminar asistente",
|
||||
"Yes": "Sí",
|
||||
"No": "No",
|
||||
"Are you sure you want to delete this assistant?": "¿Está seguro de que desea eliminar este asistente?",
|
||||
"This action cannot be undone.": "Esta acción no se puede deshacer.",
|
||||
"Warning!": "¡Advertencia!",
|
||||
"Custom assistant is not available. Please check your configuration.": "El asistente personalizado no está disponible. Por favor, verifique su configuración.",
|
||||
"Not able to perform this action. Please use prompts related to text analysis, editing, or formatting.": "No se puede realizar esta acción. Por favor, use prompts relacionados con el análisis, edición o formato de texto.",
|
||||
"Processing selection...": "Procesamiento de la selección...",
|
||||
"Processing document...": "Procesamiento del documento...",
|
||||
"Action": "Acción",
|
||||
"Prompt": "Prompt",
|
||||
"Give your tool a short name for the toolbar": "Dale un nombre corto a tu herramienta para la barra de herramientas",
|
||||
"Tell the AI what to do with the selected text (e.g., \"Find factual errors\" or \"Summarize\")": "Dile a la IA qué hacer con el texto seleccionado (por ejemplo, \"Encontrar errores factuales\" o \"Resumir\")",
|
||||
"Create AI assistant": "Crear asistente IA",
|
||||
"Match": "Correspondencia",
|
||||
"Proposal for replacement": "Propuesta de reemplazo"
|
||||
}
|
||||
|
||||
@ -153,5 +153,26 @@
|
||||
"Check all": "Vérifier tout",
|
||||
"Check current text": "Vérifier le texte actuel",
|
||||
"Suggested correction": "Correction suggérée",
|
||||
"Explanation": "Explication"
|
||||
"Explanation": "Explication",
|
||||
"Turn any repetitive text task into a custom button on your toolbar. Automate specific editing, checking, or rewriting tasks using the power of AI.": "Transformez n'importe quel taquin de texte en bouton personnalisé sur votre barre d'outils. Automatiser les taches d'edition, de verification ou de remplacement utilisant le pouvoir de l'IA.",
|
||||
"Create a new assistant": "Créer un nouvel assistant",
|
||||
"Save": "Sauvegarder",
|
||||
"Create": "Créer",
|
||||
"Delete Assistant": "Supprimer l'assistant",
|
||||
"Yes": "Oui",
|
||||
"No": "Non",
|
||||
"Are you sure you want to delete this assistant?": "Êtes-vous sûr de vouloir supprimer cet assistant ?",
|
||||
"This action cannot be undone.": "Cette action ne peut pas être annulée.",
|
||||
"Warning!": "Avertissement!",
|
||||
"Custom assistant is not available. Please check your configuration.": "L'assistant personnalisé n'est pas disponible. Veuillez vérifier votre configuration.",
|
||||
"Not able to perform this action. Please use prompts related to text analysis, editing, or formatting.": "Impossible d'effectuer cette action. Veuillez utiliser des invites liées à l'analyse, l'édition ou le formatage du texte.",
|
||||
"Processing selection...": "Traitement de la sélection...",
|
||||
"Processing document...": "Traitement du document...",
|
||||
"Action": "Action",
|
||||
"Prompt": "Prompt",
|
||||
"Give your tool a short name for the toolbar": "Donnez un nom court à votre outil pour la barre d'outils",
|
||||
"Tell the AI what to do with the selected text (e.g., \"Find factual errors\" or \"Summarize\")": "Dites à l'IA ce qu'elle doit faire avec le texte sélectionné (par exemple, \"Trouver les erreurs factuelles\" ou \"Riassumer\")",
|
||||
"Create AI assistant": "Créer un assistant IA",
|
||||
"Match": "Correspondance",
|
||||
"Proposal for replacement": "Proposition de remplacement"
|
||||
}
|
||||
@ -153,5 +153,26 @@
|
||||
"Check all": "Controlla tutto",
|
||||
"Check current text": "Controlla il testo corrente",
|
||||
"Suggested correction": "Correzione suggerita",
|
||||
"Explanation": "Spiegazione"
|
||||
}
|
||||
"Explanation": "Spiegazione",
|
||||
"Turn any repetitive text task into a custom button on your toolbar. Automate specific editing, checking, or rewriting tasks using the power of AI.": "Rendi qualsiasi compito di testo ripetitivo un pulsante personalizzato nella barra degli strumenti. Automatizza le attività di modifica, controllo o riscrittura utilizzando la potenza dell'IA.",
|
||||
"Create a new assistant": "Crea un nuovo assistente",
|
||||
"Save": "Salva",
|
||||
"Create": "Crea",
|
||||
"Delete Assistant": "Elimina assistente",
|
||||
"Yes": "Sì",
|
||||
"No": "No",
|
||||
"Are you sure you want to delete this assistant?": "Sei sicuro di voler eliminare questo assistente?",
|
||||
"This action cannot be undone.": "Questa azione non può essere annullata.",
|
||||
"Warning!": "Attenzione!",
|
||||
"Custom assistant is not available. Please check your configuration.": "L'assistente personalizzato non è disponibile. Controlla la tua configurazione.",
|
||||
"Not able to perform this action. Please use prompts related to text analysis, editing, or formatting.": "Impossibile eseguire questa azione. Usa prompt relativi all'analisi, modifica o formattazione del testo.",
|
||||
"Processing selection...": "Processamento selezione...",
|
||||
"Processing document...": "Processamento documento...",
|
||||
"Action": "Azione",
|
||||
"Prompt": "Prompt",
|
||||
"Give your tool a short name for the toolbar": "Dai un nome breve al tuo strumento per la barra degli strumenti",
|
||||
"Tell the AI what to do with the selected text (e.g., \"Find factual errors\" or \"Summarize\")": "Dai all'AI le istruzioni su come gestire il testo selezionato (ad esempio, \"Trova errori fatti\" o \"Riassumi\")",
|
||||
"Create AI assistant": "Crea assistente AI",
|
||||
"Match": "Corrispondenza",
|
||||
"Proposal for replacement": "Proposta di sostituzione"
|
||||
}
|
||||
|
||||
@ -153,5 +153,26 @@
|
||||
"Check all": "すべてチェック",
|
||||
"Check current text": "現在のテキストをチェック",
|
||||
"Suggested correction": "修正提案",
|
||||
"Explanation": "説明"
|
||||
"Explanation": "説明",
|
||||
"Turn any repetitive text task into a custom button on your toolbar. Automate specific editing, checking, or rewriting tasks using the power of AI.": "重複するテキストタスクをカスタムボタンで置き換えます。AIの力を使って特定の編集、チェック、または再書き込みタスクを自動化します。",
|
||||
"Create a new assistant": "新しいアシスタントを作成",
|
||||
"Save": "保存",
|
||||
"Create": "作成",
|
||||
"Delete Assistant": "アシスタントを削除",
|
||||
"Yes": "はい",
|
||||
"No": "いいえ",
|
||||
"Are you sure you want to delete this assistant?": "このアシスタントを削除してもよろしいですか?",
|
||||
"This action cannot be undone.": "この操作は元に戻せません。",
|
||||
"Warning!": "警告!",
|
||||
"Custom assistant is not available. Please check your configuration.": "カスタムアシスタントは利用できません。設定を確認してください。",
|
||||
"Not able to perform this action. Please use prompts related to text analysis, editing, or formatting.": "この操作は実行できません。テキスト分析、編集、またはフォーマットに関連するプロンプトを使用してください。",
|
||||
"Processing selection...": "選択を処理中...",
|
||||
"Processing document...": "文書を処理中...",
|
||||
"Action": "アクション",
|
||||
"Prompt": "プロンプト",
|
||||
"Give your tool a short name for the toolbar": "ツールバーに短い名前をつけてください",
|
||||
"Tell the AI what to do with the selected text (e.g., \"Find factual errors\" or \"Summarize\")": "選択したテキストに対してAIに何をすべきかを教える (例: \"事実上の誤りを見つけ\"または\"要約\")",
|
||||
"Create AI assistant": "AIアシスタントを作成",
|
||||
"Match": "一致",
|
||||
"Proposal for replacement": "置き換えの提案"
|
||||
}
|
||||
@ -153,5 +153,26 @@
|
||||
"Check all": "Verificar tudo",
|
||||
"Check current text": "Verificar o texto atual",
|
||||
"Suggested correction": "Correção sugerida",
|
||||
"Explanation": "Explicação"
|
||||
"Explanation": "Explicação",
|
||||
"Turn any repetitive text task into a custom button on your toolbar. Automate specific editing, checking, or rewriting tasks using the power of AI.": "Transforme qualquer tarefa de texto repetitivo em um botão personalizado na barra de ferramentas. Automatize tarefas de edição, verificação ou reescrita usando a potência da IA.",
|
||||
"Create a new assistant": "Criar novo assistente",
|
||||
"Save": "Salvar",
|
||||
"Create": "Criar",
|
||||
"Delete Assistant": "Excluir assistente",
|
||||
"Yes": "Sim",
|
||||
"No": "Não",
|
||||
"Are you sure you want to delete this assistant?": "Tem certeza de que deseja excluir este assistente?",
|
||||
"This action cannot be undone.": "Esta ação não pode ser desfeita.",
|
||||
"Warning!": "Aviso!",
|
||||
"Custom assistant is not available. Please check your configuration.": "O assistente personalizado não está disponível. Verifique sua configuração.",
|
||||
"Not able to perform this action. Please use prompts related to text analysis, editing, or formatting.": "Não é possível realizar esta ação. Use prompts relacionados à análise de texto, edição ou formatação.",
|
||||
"Processing selection...": "Processando seleção...",
|
||||
"Processing document...": "Processando documento...",
|
||||
"Action": "Ação",
|
||||
"Prompt": "Prompt",
|
||||
"Give your tool a short name for the toolbar": "Dê um nome curto para o seu ferramentário",
|
||||
"Tell the AI what to do with the selected text (e.g., \"Find factual errors\" or \"Summarize\")": "Diga ao AI o que fazer com o texto selecionado (por exemplo, \"Encontre erros fatos\" ou \"Resumir\")",
|
||||
"Create AI assistant": "Criar assistente de IA",
|
||||
"Match": "Correspondência",
|
||||
"Proposal for replacement": "Proposta de substituição"
|
||||
}
|
||||
@ -153,5 +153,26 @@
|
||||
"Check all": "Проверить все",
|
||||
"Check current text": "Проверить текущий текст",
|
||||
"Suggested correction": "Предложение",
|
||||
"Explanation": "Объяснение"
|
||||
"Explanation": "Объяснение",
|
||||
"Turn any repetitive text task into a custom button on your toolbar. Automate specific editing, checking, or rewriting tasks using the power of AI.": "Превратите любую повторяющуюся текстовую задачу в настраиваемую кнопку на панели инструментов. Автоматизируйте конкретные задачи редактирования, проверки или переписывания текста, используя возможности искусственного интеллекта.",
|
||||
"Create a new assistant": "Создать нового ассистента",
|
||||
"Save": "Сохранить",
|
||||
"Create": "Создать",
|
||||
"Delete Assistant": "Удалить ассистента",
|
||||
"Yes": "Да",
|
||||
"No": "Нет",
|
||||
"Are you sure you want to delete this assistant?": "Вы уверены, что хотите удалить этого ассистента?",
|
||||
"This action cannot be undone.": "Это действие не может быть отменено.",
|
||||
"Warning!": "Предупреждение!",
|
||||
"Custom assistant is not available. Please check your configuration.": "Пользовательский ассистент недоступен. Пожалуйста, проверьте вашу конфигурацию.",
|
||||
"Not able to perform this action. Please use prompts related to text analysis, editing, or formatting.": "Не удается выполнить это действие. Пожалуйста, используйте запросы, связанные с анализом текста, редактированием или форматированием.",
|
||||
"Processing selection...": "Обработка выделенного фрагмента...",
|
||||
"Processing document...": "Обработка документа...",
|
||||
"Action": "Действие",
|
||||
"Prompt": "Пометка",
|
||||
"Give your tool a short name for the toolbar": "Дайте вашему инструменту короткое имя для панели инструментов",
|
||||
"Tell the AI what to do with the selected text (e.g., \"Find factual errors\" or \"Summarize\")": "Попросите ИИ выполнить действие над выбранным текстом (например, \"Найти фактические ошибки\" или \"Сжать\")",
|
||||
"Create AI assistant": "Создать ассистента ИИ",
|
||||
"Match": "Совпадение",
|
||||
"Proposal for replacement": "Предложение для замены"
|
||||
}
|
||||
@ -152,5 +152,26 @@
|
||||
"Check all": "Kontrolloni të gjitha",
|
||||
"Check current text": "Kontrolloni tekstin aktual",
|
||||
"Suggested correction": "Korrigjim i sugjeruar",
|
||||
"Explanation": "Shpjegim"
|
||||
"Explanation": "Shpjegim",
|
||||
"Turn any repetitive text task into a custom button on your toolbar. Automate specific editing, checking, or rewriting tasks using the power of AI.": "Pretvori biletjen e tekstit te ri i ri ne nje buton personalizuar ne toolbaren tuaj. Automatizo tasket e editimit, kontrollimit ose rikthejeve duke perdorur moshen e IA.",
|
||||
"Create a new assistant": "Krijoni një asistent të ri",
|
||||
"Save": "Ruaj",
|
||||
"Create": "Krijoni",
|
||||
"Delete Assistant": "Fshijeni Asistentin",
|
||||
"Yes": "Po",
|
||||
"No": "Jo",
|
||||
"Are you sure you want to delete this assistant?": "A jeni të sigurt që dëshironi të fshini këtë asistent?",
|
||||
"This action cannot be undone.": "Kjo veprim nuk mund të zhbëhet.",
|
||||
"Warning!": "Paralajmërim!",
|
||||
"Custom assistant is not available. Please check your configuration.": "Asistenti i personalizuar nuk është i disponueshëm. Ju lutemi kontrolloni konfigurimin tuaj.",
|
||||
"Not able to perform this action. Please use prompts related to text analysis, editing, or formatting.": "Nuk mund të kryhet kjo veprim. Ju lutemi përdorni promptet të lidhura me analizën e tekstit, redaktimin ose formatimin.",
|
||||
"Processing selection...": "Procesimi i përsëritjes së seleksionit...",
|
||||
"Processing document...": "Procesimi i dokumentit...",
|
||||
"Action": "Veprim",
|
||||
"Prompt": "Prompt",
|
||||
"Give your tool a short name for the toolbar": "Dajte vashem alatu kratko ime za alatnu traku",
|
||||
"Tell the AI what to do with the selected text (e.g., \"Find factual errors\" or \"Summarize\")": "Rekajte AI što da radi sa odabranim tekstom (npr., \"Pronalazǐte grěsě u faktu\" ili \"Sažmi\")",
|
||||
"Create AI assistant": "Krijoni asistent AI",
|
||||
"Match": "Përputhje",
|
||||
"Proposal for replacement": "Propozicion për zëvëndësim"
|
||||
}
|
||||
@ -153,5 +153,26 @@
|
||||
"Check all": "Означи све",
|
||||
"Check current text": "Провери тренутни текст",
|
||||
"Suggested correction": "Предложена исправка",
|
||||
"Explanation": "Објашњење"
|
||||
"Explanation": "Објашњење",
|
||||
"Turn any repetitive text task into a custom button on your toolbar. Automate specific editing, checking, or rewriting tasks using the power of AI.": "Претворите било који понављајући текстуални задатак у прилагођено дугме на вашој траци са алаткама. Аутоматизујте одређене задатке уређивања, провере или преписивања користећи моћ вештачке интелигенције.",
|
||||
"Create a new assistant": "Направите новог асистента",
|
||||
"Save": "Сачувај",
|
||||
"Create": "Направите",
|
||||
"Delete Assistant": "Обришите асистента",
|
||||
"Yes": "Да",
|
||||
"No": "Не",
|
||||
"Are you sure you want to delete this assistant?": "Да ли сте сигурни да желите да обришете овог асистента?",
|
||||
"This action cannot be undone.": "Ова акција не може бити обрисана.",
|
||||
"Warning!": "Упозорење!",
|
||||
"Custom assistant is not available. Please check your configuration.": "Кориснички асистент није доступан. Молимо проверите вашу конфигурацију.",
|
||||
"Not able to perform this action. Please use prompts related to text analysis, editing, or formatting.": "Није могуће извршити ову акцију. Молимо користите промпте повезане са анализом текста, уређивањем или форматирањем.",
|
||||
"Processing selection...": "Процесирање избора...",
|
||||
"Processing document...": "Процесирање документа...",
|
||||
"Action": "Акција",
|
||||
"Prompt": "Пометка",
|
||||
"Give your tool a short name for the toolbar": "Дајте вашем alatu kratko ime za alatnu traku",
|
||||
"Tell the AI what to do with the selected text (e.g., \"Find factual errors\" or \"Summarize\")": "Рекајте AI шта да ради са одабраним текстом (нпр., \"Пронађи грешке у факту\" или \"Сажми\")",
|
||||
"Create AI assistant": "Креирај AI асистента",
|
||||
"Match": "Поређај",
|
||||
"Proposal for replacement": "Предлог за замену"
|
||||
}
|
||||
@ -153,5 +153,26 @@
|
||||
"Check all": "Proveri sve",
|
||||
"Check current text": "Proveri trenutni tekst",
|
||||
"Suggested correction": "Predložena ispravka",
|
||||
"Explanation": "Objašnjenje"
|
||||
"Explanation": "Objašnjenje",
|
||||
"Turn any repetitive text task into a custom button on your toolbar. Automate specific editing, checking, or rewriting tasks using the power of AI.": "Pretvori bilo koje ponavljajuce tekstove u prilagođenu gumbu u vasoj alatnoj trci. Automatizujte specificne zadatke za uređivanje, provjeru ili pisanje koristeći moc veštačke inteligencije.",
|
||||
"Create a new assistant": "Kreiraj novog asistent",
|
||||
"Save": "Spremi",
|
||||
"Create": "Kreiraj",
|
||||
"Delete Assistant": "Obriši asistent",
|
||||
"Yes": "Da",
|
||||
"No": "Ne",
|
||||
"Are you sure you want to delete this assistant?": "Da li ste sigurni da želite obrisati ovog asistenta?",
|
||||
"This action cannot be undone.": "Ova akcija ne može biti ponovljena.",
|
||||
"Warning!": "Upozorenje!",
|
||||
"Custom assistant is not available. Please check your configuration.": "Prilagođeni asistent nije dostupan. Molimo provjerite konfiguraciju.",
|
||||
"Not able to perform this action. Please use prompts related to text analysis, editing, or formatting.": "Nije u stanju da izvrsi ovu akciju. Molimo koristi prompove vezane za analizu teksta, uređivanje ili formatiranje.",
|
||||
"Processing selection...": "Procesiranje izbora...",
|
||||
"Processing document...": "Procesiranje dokumenta...",
|
||||
"Action": "Akcija",
|
||||
"Prompt": "Prompt",
|
||||
"Give your tool a short name for the toolbar": "Dajte vam alatku kratko ime za alatnu traku",
|
||||
"Tell the AI what to do with the selected text (e.g., \"Find factual errors\" or \"Summarize\")": "Rekajte AI što da radi sa odabranim tekstom (npr., \"Pronalazǐte grěsě u faktu\" ili \"Sažmi\")",
|
||||
"Create AI assistant": "Kreiraj AI asistenta",
|
||||
"Match": "Porijedaj",
|
||||
"Proposal for replacement": "Propozicija za zamenjivanje"
|
||||
}
|
||||
@ -153,5 +153,28 @@
|
||||
"Check all": "检查全部",
|
||||
"Check current text": "检查当前文本",
|
||||
"Suggested correction": "修改建议",
|
||||
"Explanation": "解释说明"
|
||||
"Explanation": "解释说明",
|
||||
"Turn any repetitive text task into a custom button on your toolbar. Automate specific editing, checking, or rewriting tasks using the power of AI.": "将任何重复的文本任务转换为工具栏上的自定义按钮。使用 AI 的力量自动化特定的编辑、检查或重写任务。",
|
||||
"Create a new assistant": "创建新助手",
|
||||
"Save": "保存",
|
||||
"Create": "创建",
|
||||
"Delete Assistant": "删除助手",
|
||||
"Yes": "是",
|
||||
"No": "否",
|
||||
"Are you sure you want to delete this assistant?": "确定要删除此助手吗?",
|
||||
"This action cannot be undone.": "此操作无法撤销。",
|
||||
"Warning!": "警告!",
|
||||
"Custom assistant is not available. Please check your configuration.": "自定义助手不可用。请检查您的配置。",
|
||||
"Not able to perform this action. Please use prompts related to text analysis, editing, or formatting.": "无法执行此操作。请使用与文本分析、编辑或格式化相关的提示。",
|
||||
"Processing selection...": "处理选择...",
|
||||
"Processing document...": "处理文档...",
|
||||
"Action": "操作",
|
||||
"Prompt": "提示词",
|
||||
"Give your tool a short name for the toolbar": "为工具栏提供一个简短的名称",
|
||||
"Tell the AI what to do with the selected text (e.g., \"Find factual errors\" or \"Summarize\")": "告诉 AI 选中文本要做什么(例如,\"查找事实错误\" 或 \"摘要\")",
|
||||
"Create AI assistant": "创建 AI 助手",
|
||||
"Match": "匹配",
|
||||
"Proposal for replacement": "替换建议"
|
||||
|
||||
|
||||
}
|
||||