unification of custom assistants with text ones

This commit is contained in:
Artur
2026-01-22 11:42:32 +03:00
parent 571852da2b
commit ab65299222
5 changed files with 49 additions and 310 deletions

View File

@ -34,9 +34,9 @@
/// <reference path="./types.js" />
/** @param {localStorageCustomAssistantItem} assistantData */
function AssistantHint(assistantData)
function AssistantHint(annotationPopup, assistantData)
{
CustomAnnotator.call(this, assistantData);
CustomAnnotator.call(this, annotationPopup, assistantData);
}
AssistantHint.prototype = Object.create(CustomAnnotator.prototype);
AssistantHint.prototype.constructor = AssistantHint;
@ -58,7 +58,7 @@ AssistantHint.prototype.annotateParagraph = async function(paraId, recalcId, tex
let response = await this.chatRequest(argPrompt);
if (!response)
return false;
let rangeId = 1;
let ranges = [];
@ -216,36 +216,3 @@ AssistantHint.prototype.onAccept = async function(paraId, rangeId)
await Asc.Editor.callMethod("EndAction", ["GroupActions"]);
await Asc.Editor.callMethod("FocusEditor");
};
/**
* @param {string} paraId
* @param {string} rangeId
*/
AssistantHint.prototype.getAnnotationRangeObj = function(paraId, rangeId)
{
return {
"paragraphId" : paraId,
"rangeId" : rangeId,
"name" : "customAssistant_" + this.assistantData.id
};
};
AssistantHint.prototype._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);
Asc.Editor.callMethod("RemoveAnnotationRange", [annotRange]);
}
};

View File

@ -34,9 +34,9 @@
/// <reference path="./types.js" />
/** @param {localStorageCustomAssistantItem} assistantData */
function AssistantReplaceHint(assistantData)
function AssistantReplaceHint(annotationPopup, assistantData)
{
CustomAnnotator.call(this, assistantData);
CustomAnnotator.call(this, annotationPopup, assistantData);
}
AssistantReplaceHint.prototype = Object.create(CustomAnnotator.prototype);
AssistantReplaceHint.prototype.constructor = AssistantReplaceHint;
@ -52,11 +52,12 @@ AssistantReplaceHint.prototype.annotateParagraph = async function(paraId, recalc
if (text.length === 0)
return false;
const argPrompt = this._createPrompt(text);
let response = await this.chatRequest(argPrompt);
if (!response)
return false;
return false;
let rangeId = 1;
let ranges = [];
@ -65,11 +66,11 @@ AssistantReplaceHint.prototype.annotateParagraph = async function(paraId, recalc
/**
* @param {string} text
* @param {ReplaceHintAiResponse[]} corrections
* @param {ReplaceHintAiResponse[]} matches
*/
function convertToRanges(text, corrections)
function convertToRanges(text, matches)
{
for (const { origin, suggestion, difference, reason, paragraph, occurrence, confidence } of corrections)
for (const { origin, suggestion, difference, reason, paragraph, occurrence, confidence } of matches)
{
if (origin === suggestion || confidence <= 0.7)
continue;
@ -229,36 +230,3 @@ AssistantReplaceHint.prototype.onAccept = async function(paraId, rangeId)
await Asc.Editor.callMethod("EndAction", ["GroupActions"]);
await Asc.Editor.callMethod("FocusEditor");
};
/**
* @param {string} paraId
* @param {string} rangeId
*/
AssistantReplaceHint.prototype.getAnnotationRangeObj = function(paraId, rangeId)
{
return {
"paragraphId" : paraId,
"rangeId" : rangeId,
"name" : "customAssistant_" + this.assistantData.id
};
};
AssistantReplaceHint.prototype._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);
Asc.Editor.callMethod("RemoveAnnotationRange", [annotRange]);
}
};

View File

@ -34,9 +34,9 @@
/// <reference path="./types.js" />
/** @param {localStorageCustomAssistantItem} assistantData */
function AssistantReplace(assistantData)
function AssistantReplace(annotationPopup, assistantData)
{
CustomAnnotator.call(this, assistantData);
CustomAnnotator.call(this, annotationPopup, assistantData);
}
AssistantReplace.prototype = Object.create(CustomAnnotator.prototype);
AssistantReplace.prototype.constructor = AssistantReplace;
@ -52,12 +52,13 @@ AssistantReplace.prototype.annotateParagraph = async function(paraId, recalcId,
if (text.length === 0)
return false;
const argPrompt = this._createPrompt(text);
let response = await this.chatRequest(argPrompt);
if (!response)
return false;
let rangeId = 1;
let ranges = [];
@ -65,11 +66,11 @@ AssistantReplace.prototype.annotateParagraph = async function(paraId, recalcId,
/**
* @param {string} text
* @param {ReplaceAiResponse[]} corrections
* @param {ReplaceAiResponse[]} matches
*/
function convertToRanges(text, corrections)
function convertToRanges(text, matches)
{
for (const { origin, suggestion, paragraph, occurrence, confidence } of corrections)
for (const { origin, suggestion, paragraph, occurrence, confidence } of matches)
{
if (origin === suggestion || confidence <= 0.7)
continue;
@ -216,36 +217,3 @@ AssistantReplace.prototype.onAccept = async function(paraId, rangeId)
await Asc.Editor.callMethod("EndAction", ["GroupActions"]);
await Asc.Editor.callMethod("FocusEditor");
};
/**
* @param {string} paraId
* @param {string} rangeId
*/
AssistantReplace.prototype.getAnnotationRangeObj = function(paraId, rangeId)
{
return {
"paragraphId" : paraId,
"rangeId" : rangeId,
"name" : "customAssistant_" + this.assistantData.id
};
};
AssistantReplace.prototype._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);
Asc.Editor.callMethod("RemoveAnnotationRange", [annotRange]);
}
};

View File

@ -32,210 +32,44 @@
/// <reference path="./types.js" />
/** @param {localStorageCustomAssistantItem} assistantData */
function CustomAnnotator(assistantData)
function CustomAnnotator(annotationPopup, assistantData)
{
this.paragraphs = {};
/** @type {Object.<string, {recalcId: number, text: string}>} */
this.waitParagraphs = {};
this.paraToCheck = new Set();
this.checked = new Set(); // was checked on the previous request
this.type = assistantData.type; // 2
this.assistantData = assistantData;
TextAnnotator.call(this, annotationPopup);
this.assistantData = assistantData;
this.type = assistantData.type;
}
CustomAnnotator.prototype = Object.create(TextAnnotator.prototype);
CustomAnnotator.prototype.constructor = CustomAnnotator;
/**
* @param {string} paraId
* @param {number} recalcId
* @param {string} text
* @param {string[]} ranges
* @param {string} rangeId
*/
CustomAnnotator.prototype.onChangeParagraph = async function(paraId, recalcId, text, ranges)
{
this._handleNewRanges(ranges, paraId, text);
this.waitParagraphs[paraId] = {
recalcId : recalcId,
text : text
};
this._checkParagraph(paraId);
};
/**
* @param {string} paraId
* @param {string[]} ranges
*/
CustomAnnotator.prototype.onClick = function(paraId, ranges)
{
if (!ranges || !ranges.length)
this._closePopup();
else
this._openPopup(paraId, ranges[0]);
};
CustomAnnotator.prototype.onBlur = function()
{
this._closePopup();
};
/**
* @param {string[]} paraIds
*/
CustomAnnotator.prototype.checkParagraphs = async function(paraIds)
{
this.paraToCheck.clear()
paraIds.forEach(function(paraId) {
if (!this.checked.has(paraId) || this.waitParagraphs[paraId]) {
this.paraToCheck.add(paraId);
this._checkParagraph(paraId);
}
}, this);
};
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;
/** @type {InfoForPopup} */
const popupInfo = this.getInfoForPopup(paraId, rangeId);
let popup = customAnnotationPopup.open(this.type, paraId, rangeId, popupInfo);
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
"rangeId" : rangeId,
"name" : "customAssistant_" + this.assistantData.id
};
};
CustomAnnotator.prototype._handleNewRanges = function(ranges, paraId, text)
CustomAnnotator.prototype._handleNewRangePositions = async function(range, paraId, text)
{
if (!ranges || !Array.isArray(ranges))
if (!range || range["name"] !== "customAssistant_" + this.assistantData.id || !this.paragraphs[paraId])
return;
for (let i = 0; i < ranges.length; ++i)
{
this._handleNewRangePositions(ranges[i], paraId, text);
}
};
CustomAnnotator.prototype._handleNewRangePositions = function(range, paraId, text)
{
};
CustomAnnotator.prototype.chatRequest = async function(prompt)
{
let requestEngine = AI.Request.create(AI.ActionType.Chat);
if (!requestEngine)
return null;
let rangeId = range["id"];
let annot = this.getAnnotation(paraId, rangeId);
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
*/
CustomAnnotator.prototype.normalizeResponse = function(response) {
if (typeof response !== 'string') {
return response;
}
if (!annot)
return;
let start = range["start"];
let len = range["length"];
// Trim whitespace
let normalized = response.trim();
// Check if response is wrapped in markdown code blocks
// Patterns: ```json\n{...}\n``` or ```\n{...}\n```
const codeBlockPattern = /^```(?:json)?\s*\n?([\s\S]*?)\n?```$/;
const match = normalized.match(codeBlockPattern);
if (match) {
// Extract content between code block markers
normalized = match[1].trim();
if (annot["original"] !== text.substring(start, start + len))
{
let annotRange = this.getAnnotationRangeObj(paraId, rangeId);
Asc.Editor.callMethod("RemoveAnnotationRange", [annotRange]);
}
return normalized;
};
/**
* @param {string} str
* @param {string} searchStr
* @param {string} [fromIndex]
* @returns {number}
*/
CustomAnnotator.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;
}
};

View File

@ -36,11 +36,13 @@
/// <reference path="./assistant-hint.js" />
/// <reference path="./assistant-replace-hint.js" />
/// <reference path="./assistant-replace.js" />
/// <reference path="../text-annotations/text-annotator.js" />
/// <reference path="./annotation-popup.js" />
class CustomAssistantManager {
constructor() {
/**
* @type {Map<string, CustomAnnotator>}
* @type {Map<string, TextAnnotator>}
*/
this._customAssistants = new Map();
this._isCustomAssistantInit = new Map();
@ -62,13 +64,13 @@ class CustomAssistantManager {
}
switch (assistantData.type) {
case 0:
assistant = new AssistantHint(assistantData);
assistant = new AssistantHint(customAnnotationPopup, assistantData);
break;
case 1:
assistant = new AssistantReplaceHint(assistantData);
assistant = new AssistantReplaceHint(customAnnotationPopup, assistantData);
break;
case 2:
assistant = new AssistantReplace(assistantData);
assistant = new AssistantReplace(customAnnotationPopup, assistantData);
break;
default:
throw new Error(