diff --git a/sdkjs-plugins/content/ai/customAssistant.html b/sdkjs-plugins/content/ai/customAssistant.html index 3df71118..51d5f639 100644 --- a/sdkjs-plugins/content/ai/customAssistant.html +++ b/sdkjs-plugins/content/ai/customAssistant.html @@ -37,7 +37,10 @@ + + + @@ -52,6 +55,7 @@ + diff --git a/sdkjs-plugins/content/ai/index.html b/sdkjs-plugins/content/ai/index.html index d887535c..4ef353d8 100644 --- a/sdkjs-plugins/content/ai/index.html +++ b/sdkjs-plugins/content/ai/index.html @@ -56,7 +56,9 @@ - + + + diff --git a/sdkjs-plugins/content/ai/scripts/custom-annotations/annotation-popup.js b/sdkjs-plugins/content/ai/scripts/custom-annotations/annotation-popup.js new file mode 100644 index 00000000..b633a6b6 --- /dev/null +++ b/sdkjs-plugins/content/ai/scripts/custom-annotations/annotation-popup.js @@ -0,0 +1,204 @@ +/* + * (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 + * + */ + +function CustomAnnotationPopup() +{ + this.popup = null; + this.type = 0; // 0 - spelling, 1 - grammar + this.paraId = -1; + this.rangeId = -1; + + this.content = ""; + this.width = 318; + this.height = 500; + + this.open = function(type, paraId, rangeId, data) + { + if (this.popup && 0 === this.type && 1 === type) + return null; + + 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(), + 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() { + let name2color = { + "theme-light": "#F62211", + "theme-classic-light": "#D9534F", + + "theme-dark": "#F62211", + "theme-contrast-dark": "#F62211", + + "theme-gray": "#F62211", + + "theme-white": "#F23D3D", + "theme-night": "#F23D3D" + }; + let type2color = { + "light": "#F62211", + "dark": "#F62211" + }; + + let color = type2color["light"]; + if (window.Asc.plugin.theme) + { + if (window.Asc.plugin.theme.Name && name2color[window.Asc.plugin.theme.Name]) + color = name2color[window.Asc.plugin.theme.Name]; + else if (window.Asc.plugin.theme.Type && type2color[window.Asc.plugin.theme.Type]) + color = type2color[window.Asc.plugin.theme.Type]; + } + + popup.command("onUpdateContent", { + content : _t.content, + color : color + }); + }); + + 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 ? "Spelling suggestion" : "Grammar suggestion"); + }; + + this._getButtons = function() + { + return [ + { text: window.Asc.plugin.tr('Accept'), primary: true }, + { text: window.Asc.plugin.tr('Reject'), primary: false } + ]; + }; + + 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 = `
"; + + 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(); diff --git a/sdkjs-plugins/content/ai/scripts/text-annotations/custom-assistant.js b/sdkjs-plugins/content/ai/scripts/custom-annotations/assistant.js similarity index 84% rename from sdkjs-plugins/content/ai/scripts/text-annotations/custom-assistant.js rename to sdkjs-plugins/content/ai/scripts/custom-annotations/assistant.js index c2451ec6..0bc9ec38 100644 --- a/sdkjs-plugins/content/ai/scripts/text-annotations/custom-assistant.js +++ b/sdkjs-plugins/content/ai/scripts/custom-annotations/assistant.js @@ -32,12 +32,12 @@ function CustomAssistant(assistantData) { - TextAnnotator.call(this); + CustomAnnotator.call(this); this.type = 1; this.assistantData = assistantData; } -CustomAssistant.prototype = Object.create(TextAnnotator.prototype); +CustomAssistant.prototype = Object.create(CustomAnnotator.prototype); CustomAssistant.prototype.constructor = CustomAssistant; CustomAssistant.prototype.annotateParagraph = async function(paraId, recalcId, text) @@ -55,28 +55,7 @@ CustomAssistant.prototype.annotateParagraph = async function(paraId, recalcId, t 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: -The "sentence" field in your JSON response MUST contain the EXACT text from the original input with NO changes whatsoever - not even fixing capitalization, punctuation, or anything else. Copy it character-by-character exactly as it appears in the original. Only the "suggestion" field should contain corrections. - -Your task is to: -- Check ONLY for punctuation errors (commas, periods, semicolons, colons, apostrophes, quotation marks, etc.) and style issues (sentence structure, word order, grammar, capitalization) -- Completely ignore spelling errors and typos. Do not mention them, do not flag them, do not include sentences just because they contain spelling errors. Pretend all words are spelled correctly. -- Return corrections in JSON format only - -What counts as an error: -- Missing or incorrect punctuation (periods, commas, semicolons, etc.) -- Run-on sentences needing punctuation -- Incorrect sentence structure or word order -- Grammar issues (subject-verb agreement, tense consistency, etc.) -- Capitalization errors - -What does NOT count as an error: -- Misspelled words or typos -- Missing letters in words -- Wrong letters in words - + let argPrompt = `${this.assistantData.query} Response format - return ONLY this JSON array with no additional text: [ { diff --git a/sdkjs-plugins/content/ai/scripts/custom-annotations/custom-annotator.js b/sdkjs-plugins/content/ai/scripts/custom-annotations/custom-annotator.js new file mode 100644 index 00000000..c00a1ef4 --- /dev/null +++ b/sdkjs-plugins/content/ai/scripts/custom-annotations/custom-annotator.js @@ -0,0 +1,175 @@ +/* + * (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 + * + */ + +function CustomAnnotator() +{ + this.paraId = null; + this.rangeId = null; + + this.paragraphs = {}; + this.waitParagraphs = {}; + this.paraToCheck = new Set(); + this.checked = new Set(); // was checked on the previous request + + this.type = -1; +} +CustomAnnotator.prototype.onChangeParagraph = async function(paraId, recalcId, text, ranges) +{ + this._handleNewRanges(ranges, paraId, text); + this.waitParagraphs[paraId] = { + recalcId : recalcId, + text : text + }; + + this._checkParagraph(paraId); +}; +CustomAnnotator.prototype.checkParagraphs = async function(paraIds) +{ + this.paraToCheck.clear() + let _t = this; + paraIds.forEach(function(paraId) { + if (!_t.checked.has(paraId) || _t.waitParagraphs[paraId]) + _t.paraToCheck.add(paraId); + }); + + this.paraToCheck.forEach(paraId => this._checkParagraph(paraId)); +}; +CustomAnnotator.prototype._checkParagraph = async function(paraId) +{ + if (!this.paraToCheck.has(paraId) || !this.waitParagraphs[paraId]) + return; + + let recalcId = this.waitParagraphs[paraId].recalcId; + let text = this.waitParagraphs[paraId].text; + + // TODO: Temporarily for simplicity + let range = this.getAnnotationRangeObj(paraId); + range["rangeId"] = undefined; + range["all"] = true; + await Asc.Editor.callMethod("RemoveAnnotationRange", [range]); + await this.annotateParagraph(paraId, recalcId, text); + + delete this.waitParagraphs[paraId]; + this.paraToCheck.delete(paraId); + + this.checked.add(paraId); +}; +CustomAnnotator.prototype.annotateParagraph = async function(paraId, recalcId, text) +{ +}; +CustomAnnotator.prototype.openPopup = async function(paraId, rangeId) +{ + if (!customAnnotationPopup) + return; + + let popup = customAnnotationPopup.open(this.type, paraId, rangeId, this.getInfoForPopup(paraId, rangeId)); + if (!popup) + return; + + let _t = this; + popup.onAccept = async function() { + await _t.onAccept(paraId, rangeId); + _t.closePopup(); + }; + popup.onReject = async function() { + await _t.onReject(paraId, rangeId); + _t.closePopup(); + }; +}; +CustomAnnotator.prototype.closePopup = function() +{ + if (!customAnnotationPopup) + return; + + customAnnotationPopup.close(this.type); +}; +CustomAnnotator.prototype.getInfoForPopup = function(paraId, rangeId) +{ + return {}; +}; +CustomAnnotator.prototype.onAccept = async function(paraId, rangeId) +{ +}; +CustomAnnotator.prototype.onReject = async function(paraId, rangeId) +{ + let range = this.getAnnotationRangeObj(paraId, rangeId); + await Asc.Editor.callMethod("RemoveAnnotationRange", [range]); +}; +CustomAnnotator.prototype.getAnnotation = function(paraId, rangeId) +{ + if (!paraId || !rangeId || !this.paragraphs[paraId] || !this.paragraphs[paraId][rangeId]) + return {}; + + return this.paragraphs[paraId][rangeId]; +}; +CustomAnnotator.prototype.getAnnotationRangeObj = function(paraId, rangeId) +{ + return { + "paragraphId" : paraId, + "rangeId" : rangeId + }; +}; +CustomAnnotator.prototype.onClick = function(paraId, ranges) +{ + if (!ranges || !ranges.length) + this.closePopup(); + else + this.openPopup(paraId, ranges[0]); +}; +CustomAnnotator.prototype.onBlur = function() +{ + this.closePopup(); + this.resetCurrentRange(); +}; +CustomAnnotator.prototype.onFocus = function(paraId, rangeId) +{ +}; +CustomAnnotator.prototype.resetCurrentRange = function() +{ + this.paraId = null; + this.rangeId = null; +}; +CustomAnnotator.prototype._handleNewRanges = function(ranges, paraId, text) +{ + if (!ranges || !Array.isArray(ranges)) + return; + + ranges.forEach(range => this._handleNewRangePositions(range, paraId, text)); + // ↓↓↓ TODO: the cycle seems to make no sense ↓↓↓ + for (let i = 0; i < ranges.length; ++i) + { + this._handleNewRangePositions(ranges[i]); + } +}; +CustomAnnotator.prototype._handleNewRangePositions = function(range, paraId, text) +{ +}; diff --git a/sdkjs-plugins/content/ai/scripts/customAssistant.js b/sdkjs-plugins/content/ai/scripts/customAssistant.js index 23c44bfa..6a96cb19 100644 --- a/sdkjs-plugins/content/ai/scripts/customAssistant.js +++ b/sdkjs-plugins/content/ai/scripts/customAssistant.js @@ -33,16 +33,19 @@ // @ts-check ///