refactoring

This commit is contained in:
Artur
2026-01-23 17:24:46 +03:00
parent 0840d14d4c
commit 26a3687951
6 changed files with 471 additions and 526 deletions

View File

@ -38,186 +38,156 @@
* @constructor * @constructor
* @extends CustomAnnotator * @extends CustomAnnotator
*/ */
function AssistantHint(annotationPopup, assistantData) function AssistantHint(annotationPopup, assistantData) {
{ CustomAnnotator.call(this, annotationPopup, assistantData);
CustomAnnotator.call(this, annotationPopup, assistantData);
} }
AssistantHint.prototype = Object.create(CustomAnnotator.prototype); AssistantHint.prototype = Object.create(CustomAnnotator.prototype);
AssistantHint.prototype.constructor = AssistantHint; AssistantHint.prototype.constructor = AssistantHint;
/** Object.assign(AssistantHint.prototype, {
* @param {string} paraId /**
* @param {string} recalcId * @param {string} text
* @param {string} text * @param {Array<HintAiResponse>} matches
*/ */
AssistantHint.prototype.annotateParagraph = async function(paraId, recalcId, text) _convertToRanges: function (paraId, text, matches) {
{ const _t = this;
this.paragraphs[paraId] = {}; let rangeId = 1;
const ranges = [];
for (const {
origin,
reason,
paragraph,
occurrence,
confidence,
} of matches) {
if (confidence <= 0.7) continue;
if (text.length === 0) let count = 0;
return false; let searchStart = 0;
const argPrompt = this._createPrompt(text); while (searchStart < text.length) {
const index = _t.simpleGraphemeIndexOf(
text,
origin,
searchStart,
);
if (index === -1) break;
let response = await this.chatRequest(argPrompt); count++;
if (!response) if (count === occurrence) {
return false; ranges.push({
start: index,
let rangeId = 1; length: [...origin].length,
let ranges = []; id: rangeId,
});
_t.paragraphs[paraId][rangeId] = {
original: origin,
reason: reason,
};
++rangeId;
break;
}
searchStart = index + 1;
}
}
return ranges;
},
let _t = this; /**
* @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:
* @param {string} text [
* @param {Array<HintAiResponse>} matches
*/
function convertToRanges(text, matches)
{
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); "origin": "exact text fragment that matches the query",
if (index === -1) break; "reason": "detailed explanation why it matches the criteria",
"paragraph": paragraph_number,
count++; "occurrence": 1,
if (count === occurrence) "confidence": 0.95
{
ranges.push({
"start": index,
"length": [...origin].length,
"id": rangeId
});
_t.paragraphs[paraId][rangeId] = {
"original" : origin,
"reason" : reason
};
++rangeId;
break;
}
searchStart = index + 1;
} }
} ]
}
try Guidelines for each field:
{ - "origin": EXACT UNCHANGED original text fragment. Do not fix anything in this field.
convertToRanges(text, JSON.parse(response)); - "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.
let obj = { - "paragraph": Paragraph number where the fragment is found (0-based index)
"type": "highlightText", - "occurrence": Which occurrence of this sentence if it appears multiple times (1 for first, 2 for second, etc.)
"paragraphId": paraId, - "confidence": Value between 0 and 1 indicating certainty (1.0 = completely certain, 0.5 = uncertain)
"name" : "customAssistant_" + this.assistantData.id,
"recalcId": recalcId, CRITICAL
"ranges": ranges - Output should be in the exact this format
}; - No any comments are allowed
await Asc.Editor.callMethod("AnnotateParagraph", [obj]);
}
catch (e)
{ }
}
/** CRITICAL - Output Format:
* @param {string} text - Return ONLY the raw JSON array, nothing else
* @returns {string} - DO NOT wrap the response in markdown code blocks (no \`\`\`json or \`\`\`)
*/ - DO NOT include any explanatory text before or after the JSON
AssistantHint.prototype._createPrompt = function(text) { - DO NOT use escaped newlines (\\n) - return the JSON on a single line if possible
let prompt = `You are a multi-disciplinary text analysis assistant. - The response should start with [ and end with ]
Your task is to find text fragments that match the user's criteria. `;
prompt +=
MANDATORY RULES: "\n\nUSER REQUEST:\n```" + this.assistantData.query + "\n```\n\n";
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: prompt += "TEXT TO ANALYZE:\n```\n" + text + "\n```\n\n";
[
{
"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: prompt += `Please analyze this text and find all fragments that match the user's request. Be thorough but precise.`;
- "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 prompt;
- 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.`; /**
* @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,
};
},
return prompt; /**
} * @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);
* @param {string} paraId await Asc.Editor.callMethod("SelectAnnotationRange", [range]);
* @param {string} rangeId
* @return {HintInfoForPopup}
*/
AssistantHint.prototype.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
};
};
/** await Asc.Editor.callCommand(function () {
* @param {string} paraId Api.GetDocument().RemoveSelection();
* @param {string} rangeId });
*/
AssistantHint.prototype.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("RemoveAnnotationRange", [range]);
await Asc.Editor.callMethod("SelectAnnotationRange", [range]); await Asc.Editor.callMethod("EndAction", ["GroupActions"]);
await Asc.Editor.callMethod("FocusEditor");
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");
};

View File

@ -38,99 +38,71 @@
* @constructor * @constructor
* @extends CustomAnnotator * @extends CustomAnnotator
*/ */
function AssistantReplaceHint(annotationPopup, assistantData) function AssistantReplaceHint(annotationPopup, assistantData) {
{ CustomAnnotator.call(this, annotationPopup, assistantData);
CustomAnnotator.call(this, annotationPopup, assistantData);
} }
AssistantReplaceHint.prototype = Object.create(CustomAnnotator.prototype); AssistantReplaceHint.prototype = Object.create(CustomAnnotator.prototype);
AssistantReplaceHint.prototype.constructor = AssistantReplaceHint; AssistantReplaceHint.prototype.constructor = AssistantReplaceHint;
/** Object.assign(AssistantReplaceHint.prototype, {
* @param {string} paraId /**
* @param {string} recalcId * @param {string} text
* @param {string} text * @param {ReplaceHintAiResponse[]} matches
*/ */
AssistantReplaceHint.prototype.annotateParagraph = async function(paraId, recalcId, text) _convertToRanges: function (paraId, text, matches) {
{ const _t = this;
this.paragraphs[paraId] = {}; let rangeId = 1;
const ranges = [];
for (const {
origin,
suggestion,
difference,
reason,
paragraph,
occurrence,
confidence,
} of matches) {
if (origin === suggestion || confidence <= 0.7) continue;
if (text.length === 0) let count = 0;
return false; let searchStart = 0;
const argPrompt = this._createPrompt(text); while (searchStart < text.length) {
const index = _t.simpleGraphemeIndexOf(
text,
origin,
searchStart,
);
if (index === -1) break;
let response = await this.chatRequest(argPrompt); count++;
if (!response) if (count === occurrence) {
return false; ranges.push({
start: index,
let rangeId = 1; length: [...origin].length,
let ranges = []; id: rangeId,
});
_t.paragraphs[paraId][rangeId] = {
original: origin,
suggestion: suggestion,
difference: difference,
reason: reason,
};
++rangeId;
break;
}
searchStart = index + 1;
}
}
return ranges;
},
let _t = this; /**
* @param {string} text
/** * @returns {string}
* @param {string} text */
* @param {ReplaceHintAiResponse[]} matches _createPrompt: function (text) {
*/ let prompt = `You are a multi-disciplinary text analysis and transformation assistant.
function convertToRanges(text, matches)
{
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
});
_t.paragraphs[paraId][rangeId] = {
"original" : origin,
"suggestion" : suggestion,
"difference" : difference,
"reason" : reason
};
++rangeId;
break;
}
searchStart = index + 1;
}
}
}
try
{
convertToRanges(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)
{ }
}
/**
* @param {string} text
* @returns {string}
*/
AssistantReplaceHint.prototype._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. Your task is to analyze text based on user's specific criteria and provide intelligent corrections.
MANDATORY RULES: MANDATORY RULES:
@ -180,58 +152,58 @@ AssistantReplaceHint.prototype._createPrompt = function(text) {
- DO NOT use escaped newlines (\\n) - return the JSON on a single line if possible - DO NOT use escaped newlines (\\n) - return the JSON on a single line if possible
- The response should start with [ and end with ] - The response should start with [ and end with ]
`; `;
prompt += "\n\nUSER REQUEST:\n```" + this.assistantData.query + "\n```\n\n"; 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.`; prompt += "TEXT TO ANALYZE:\n```\n" + text + "\n```\n\n";
return prompt; 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}
*/
AssistantReplaceHint.prototype.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"],
suggested : _s["difference"],
explanation : reason,
type : this.type
};
};
/** /**
* @param {string} paraId * @param {string} paraId
* @param {string} rangeId * @param {string} rangeId
*/ * @returns {ReplaceHintInfoForPopup}
AssistantReplaceHint.prototype.onAccept = async function(paraId, rangeId) */
{ getInfoForPopup: function (paraId, rangeId) {
await CustomAnnotator.prototype.onAccept.call(this, paraId, rangeId); let _s = this.getAnnotation(paraId, rangeId);
let text = this.getAnnotation(paraId, rangeId)["suggestion"]; let reason = _s["reason"];
try {
await Asc.Editor.callMethod("StartAction", ["GroupActions"]); reason = reason.replace(/<a\s+(.*?)>/gi, '<a $1 target="_blank">');
} catch (e) {
console.error(e);
}
return {
original: _s["original"],
suggested: _s["difference"],
explanation: reason,
type: this.type,
};
},
let range = this.getAnnotationRangeObj(paraId, rangeId); /**
await Asc.Editor.callMethod("SelectAnnotationRange", [range]); * @param {string} paraId
* @param {string} rangeId
Asc.scope.text = text; */
await Asc.Editor.callCommand(function(){ onAccept: async function (paraId, rangeId) {
Api.ReplaceTextSmart([Asc.scope.text]); await CustomAnnotator.prototype.onAccept.call(this, paraId, rangeId);
Api.GetDocument().RemoveSelection(); let text = this.getAnnotation(paraId, rangeId)["suggestion"];
});
await Asc.Editor.callMethod("StartAction", ["GroupActions"]);
await Asc.Editor.callMethod("RemoveAnnotationRange", [range]);
await Asc.Editor.callMethod("EndAction", ["GroupActions"]); let range = this.getAnnotationRangeObj(paraId, rangeId);
await Asc.Editor.callMethod("FocusEditor"); 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");
},
});

View File

@ -38,97 +38,67 @@
* @constructor * @constructor
* @extends CustomAnnotator * @extends CustomAnnotator
*/ */
function AssistantReplace(annotationPopup, assistantData) function AssistantReplace(annotationPopup, assistantData) {
{ CustomAnnotator.call(this, annotationPopup, assistantData);
CustomAnnotator.call(this, annotationPopup, assistantData);
} }
AssistantReplace.prototype = Object.create(CustomAnnotator.prototype); AssistantReplace.prototype = Object.create(CustomAnnotator.prototype);
AssistantReplace.prototype.constructor = AssistantReplace; AssistantReplace.prototype.constructor = AssistantReplace;
/** Object.assign(AssistantReplace.prototype, {
* @param {string} paraId /**
* @param {string} recalcId * @param {string} text
* @param {string} text * @param {ReplaceAiResponse[]} matches
*/ */
AssistantReplace.prototype.annotateParagraph = async function(paraId, recalcId, text) _convertToRanges: function (paraId, text, matches) {
{ const _t = this;
this.paragraphs[paraId] = {}; let rangeId = 1;
const ranges = [];
for (const {
origin,
suggestion,
paragraph,
occurrence,
confidence,
} of matches) {
if (origin === suggestion || confidence <= 0.7) continue;
if (text.length === 0) let count = 0;
return false; let searchStart = 0;
const argPrompt = this._createPrompt(text); while (searchStart < text.length) {
const index = _t.simpleGraphemeIndexOf(
text,
origin,
searchStart,
);
if (index === -1) break;
let response = await this.chatRequest(argPrompt); count++;
if (!response) if (count === occurrence) {
return false; ranges.push({
start: index,
length: [...origin].length,
id: rangeId,
});
_t.paragraphs[paraId][rangeId] = {
original: origin,
suggestion: suggestion,
};
++rangeId;
break;
}
searchStart = index + 1;
}
}
return ranges;
},
let rangeId = 1; /**
let ranges = []; * @param {string} text
* @returns {string}
let _t = this; */
_createPrompt: function (text) {
/** let prompt = `You are a multi-disciplinary text analysis and transformation assistant.
* @param {string} text
* @param {ReplaceAiResponse[]} matches
*/
function convertToRanges(text, matches)
{
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;
}
}
}
try
{
convertToRanges(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)
{ }
}
/**
* @param {string} text
* @returns {string}
*/
AssistantReplace.prototype._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. Your task is to analyze text based on user's specific criteria and provide intelligent corrections.
MANDATORY RULES: MANDATORY RULES:
@ -174,51 +144,51 @@ AssistantReplace.prototype._createPrompt = function(text) {
- DO NOT use escaped newlines (\\n) - return the JSON on a single line if possible - DO NOT use escaped newlines (\\n) - return the JSON on a single line if possible
- The response should start with [ and end with ] - The response should start with [ and end with ]
`; `;
prompt += "\n\nUSER REQUEST:\n```" + this.assistantData.query + "\n```\n\n"; 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.`; prompt += "TEXT TO ANALYZE:\n```\n" + text + "\n```\n\n";
return prompt; 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}
*/
AssistantReplace.prototype.getInfoForPopup = function(paraId, rangeId)
{
let _s = this.getAnnotation(paraId, rangeId);
return {
original : _s["original"],
suggested : _s["suggestion"],
type : this.type
};
};
/** /**
* @param {string} paraId * @param {string} paraId
* @param {string} rangeId * @param {string} rangeId
*/ * @returns {ReplaceInfoForPopup}
AssistantReplace.prototype.onAccept = async function(paraId, rangeId) */
{ getInfoForPopup: function (paraId, rangeId) {
await CustomAnnotator.prototype.onAccept.call(this); let _s = this.getAnnotation(paraId, rangeId);
let text = this.getAnnotation(paraId, rangeId)["suggestion"]; return {
original: _s["original"],
await Asc.Editor.callMethod("StartAction", ["GroupActions"]); suggested: _s["suggestion"],
type: this.type,
};
},
let range = this.getAnnotationRangeObj(paraId, rangeId); /**
await Asc.Editor.callMethod("SelectAnnotationRange", [range]); * @param {string} paraId
* @param {string} rangeId
Asc.scope.text = text; */
await Asc.Editor.callCommand(function(){ onAccept: async function (paraId, rangeId) {
Api.ReplaceTextSmart([Asc.scope.text]); await CustomAnnotator.prototype.onAccept.call(this);
Api.GetDocument().RemoveSelection(); let text = this.getAnnotation(paraId, rangeId)["suggestion"];
});
await Asc.Editor.callMethod("StartAction", ["GroupActions"]);
await Asc.Editor.callMethod("RemoveAnnotationRange", [range]);
await Asc.Editor.callMethod("EndAction", ["GroupActions"]); let range = this.getAnnotationRangeObj(paraId, rangeId);
await Asc.Editor.callMethod("FocusEditor"); 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");
},
});

View File

@ -37,61 +37,86 @@
* @constructor * @constructor
* @extends TextAnnotator * @extends TextAnnotator
*/ */
function CustomAnnotator(annotationPopup, assistantData) function CustomAnnotator(annotationPopup, assistantData) {
{ TextAnnotator.call(this, annotationPopup);
TextAnnotator.call(this, annotationPopup); this.assistantData = assistantData;
this.assistantData = assistantData; this.type = assistantData.type;
this.type = assistantData.type; this._skipNextChangeParagraph = false;
this._skipNextChangeParagraph = false;
} }
CustomAnnotator.prototype = Object.create(TextAnnotator.prototype); CustomAnnotator.prototype = Object.create(TextAnnotator.prototype);
CustomAnnotator.prototype.constructor = CustomAnnotator; CustomAnnotator.prototype.constructor = CustomAnnotator;
/** Object.assign(CustomAnnotator.prototype, {
* @param {string} paraId /**
* @param {string} rangeId * @param {string} paraId
*/ * @param {string} recalcId
CustomAnnotator.prototype.getAnnotationRangeObj = function(paraId, rangeId) * @param {string} text
{ */
return { annotateParagraph: async function (paraId, recalcId, text) {
"paragraphId" : paraId, this.paragraphs[paraId] = {};
"rangeId" : rangeId,
"name" : "customAssistant_" + this.assistantData.id
};
};
CustomAnnotator.prototype._handleNewRangePositions = async function(range, paraId, text)
{
if (!range || range["name"] !== "customAssistant_" + this.assistantData.id || !this.paragraphs[paraId])
return;
let rangeId = range["id"]; if (text.length === 0) return false;
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)) const argPrompt = this._createPrompt(text);
{
let annotRange = this.getAnnotationRangeObj(paraId, rangeId); let response = await this.chatRequest(argPrompt);
Asc.Editor.callMethod("RemoveAnnotationRange", [annotRange]); if (!response) return false;
}
}; try {
/** const ranges = this._convertToRanges(paraId, text, JSON.parse(response));
* @param {string[]} paraIds let obj = {
*/ type: "highlightText",
CustomAnnotator.prototype.checkParagraphs = async function(paraIds) paragraphId: paraId,
{ name: "customAssistant_" + this.assistantData.id,
if (this._skipNextChangeParagraph) recalcId: recalcId,
{ ranges: ranges,
this._skipNextChangeParagraph = false; };
return; await Asc.Editor.callMethod("AnnotateParagraph", [obj]);
} } catch (e) {}
TextAnnotator.prototype.checkParagraphs.call(this, paraIds); },
}; /**
CustomAnnotator.prototype.onAccept = async function(paraId, rangeId) * @param {string} paraId
{ * @param {string} rangeId
this._skipNextChangeParagraph = true; */
}; 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);
Asc.Editor.callMethod("RemoveAnnotationRange", [annotRange]);
}
},
/**
* @param {string[]} paraIds
*/
checkParagraphs: async function (paraIds) {
if (this._skipNextChangeParagraph) {
this._skipNextChangeParagraph = false;
return;
}
TextAnnotator.prototype.checkParagraphs.call(this, paraIds);
},
onAccept: async function (paraId, rangeId) {
this._skipNextChangeParagraph = true;
},
});

View File

@ -41,14 +41,14 @@
class CustomAssistantManager { class CustomAssistantManager {
constructor() { constructor() {
/** /**
* @type {Map<string, Assistant>} * @type {Map<string, Assistant>}
*/ */
this._customAssistants = new Map(); this._customAssistants = new Map();
this._isCustomAssistantInit = new Map(); this._isCustomAssistantInit = new Map();
this._isCustomAssistantRunning = new Map(); this._isCustomAssistantRunning = new Map();
/** @type {Map<string, {recalcId: number, text: string, annotations: any}>} */ /** @type {Map<string, {recalcId: string, text: string, annotations: any}>} */
this._paragraphsStack = new Map(); this._paragraphsStack = new Map();
} }
/** /**
@ -62,19 +62,30 @@ class CustomAssistantManager {
let assistant = null; let assistant = null;
switch (assistantData.type) { switch (assistantData.type) {
case 0: case 0:
assistant = new AssistantHint(customAnnotationPopup, assistantData); assistant = new AssistantHint(
customAnnotationPopup,
assistantData,
);
break; break;
case 1: case 1:
assistant = new AssistantReplaceHint(customAnnotationPopup, assistantData); assistant = new AssistantReplaceHint(
customAnnotationPopup,
assistantData,
);
break; break;
case 2: case 2:
assistant = new AssistantReplace(customAnnotationPopup, assistantData); assistant = new AssistantReplace(
customAnnotationPopup,
assistantData,
);
break; break;
} }
if (!assistant) { if (!assistant) {
throw new Error("Unknown custom assistant type: " + assistantData.type); throw new Error(
"Unknown custom assistant type: " + assistantData.type,
);
} }
this._isCustomAssistantInit.set(assistantData.id, false); this._isCustomAssistantInit.set(assistantData.id, false);
this._isCustomAssistantRunning.set(assistantData.id, false); this._isCustomAssistantRunning.set(assistantData.id, false);
this._customAssistants.set(assistantData.id, assistant); this._customAssistants.set(assistantData.id, assistant);
@ -96,13 +107,13 @@ class CustomAssistantManager {
if (!isRunning) { if (!isRunning) {
return assistant; return assistant;
} }
this._paragraphsStack.forEach((value, paraId) => { this._paragraphsStack.forEach((value, paraId) => {
assistant.onChangeParagraph( assistant.onChangeParagraph(
paraId, paraId,
value.recalcId, value.recalcId,
value.text, value.text,
value.annotations value.annotations,
); );
}); });
const paragraphIdsToUpdate = [...assistant.checked]; const paragraphIdsToUpdate = [...assistant.checked];
@ -118,14 +129,11 @@ class CustomAssistantManager {
this._isCustomAssistantRunning.delete(assistantId); this._isCustomAssistantRunning.delete(assistantId);
} }
/** @param {string} assistantId */ /** @param {string} assistantId */
checkNeedToRunAssistant(assistantId) { checkNeedToRunAssistant(assistantId) {
const isRunning = this._isCustomAssistantRunning.get(assistantId); const isRunning = this._isCustomAssistantRunning.get(assistantId);
this._isCustomAssistantRunning.set( this._isCustomAssistantRunning.set(assistantId, !isRunning);
assistantId, return isRunning;
!isRunning
);
return isRunning;
} }
/** /**
@ -134,53 +142,53 @@ class CustomAssistantManager {
*/ */
run(assistantId, paraIds) { run(assistantId, paraIds) {
const assistant = this._customAssistants.get(assistantId); const assistant = this._customAssistants.get(assistantId);
if (!assistant) { if (!assistant) {
console.error("Custom assistant not found: " + assistantId); console.error("Custom assistant not found: " + assistantId);
return; return;
} }
if (!this._isCustomAssistantInit.get(assistantId)) { if (!this._isCustomAssistantInit.get(assistantId)) {
this._paragraphsStack.forEach((value, paraId) => { this._paragraphsStack.forEach((value, paraId) => {
assistant.onChangeParagraph( assistant.onChangeParagraph(
paraId, paraId,
value.recalcId, value.recalcId,
value.text, value.text,
value.annotations value.annotations,
) );
}); });
} }
assistant.checkParagraphs(paraIds); assistant.checkParagraphs(paraIds);
this._isCustomAssistantInit.set(assistantId, true); this._isCustomAssistantInit.set(assistantId, true);
} }
/** /**
* @param {string} paragraphId * @param {string} paragraphId
* @param {number} recalcId * @param {string} recalcId
* @param {string} text * @param {string} text
* @param {*} annotations * @param {*} annotations
*/ */
onChangeParagraph(paragraphId, recalcId, text, annotations) { onChangeParagraph(paragraphId, recalcId, text, annotations) {
this._paragraphsStack.set(paragraphId, { this._paragraphsStack.set(paragraphId, {
recalcId, recalcId,
text, text,
annotations annotations,
}); });
this._customAssistants.forEach((assistant, assistantId) => { this._customAssistants.forEach((assistant, assistantId) => {
const isInit = this._isCustomAssistantInit.get(assistantId); const isInit = this._isCustomAssistantInit.get(assistantId);
if (!isInit) { if (!isInit) {
return; return;
} }
assistant.onChangeParagraph( assistant.onChangeParagraph(
paragraphId, paragraphId,
recalcId, recalcId,
text, text,
annotations annotations,
); );
const isRunning = this._isCustomAssistantRunning.get(assistantId); const isRunning = this._isCustomAssistantRunning.get(assistantId);
if (isRunning) { if (isRunning) {
assistant.checkParagraphs([paragraphId]); assistant.checkParagraphs([paragraphId]);
} }
}); });
} }

View File

@ -37,7 +37,7 @@ function TextAnnotator(annotatorPopup)
this.rangeId = null; this.rangeId = null;
this.paragraphs = {}; this.paragraphs = {};
/** @type {Object.<string, {recalcId: number, text: string}>} */ /** @type {Object.<string, {recalcId: string, text: string}>} */
this.waitParagraphs = {}; this.waitParagraphs = {};
this.paraToCheck = new Set(); this.paraToCheck = new Set();
/** @type {Set<string>} */ /** @type {Set<string>} */
@ -47,7 +47,7 @@ function TextAnnotator(annotatorPopup)
} }
/** /**
* @param {string} paraId * @param {string} paraId
* @param {number} recalcId * @param {string} recalcId
* @param {string} text * @param {string} text
* @param {string[]} ranges * @param {string[]} ranges
*/ */