mirror of
https://github.com/ONLYOFFICE/onlyoffice.github.io.git
synced 2026-02-10 18:05:06 +08:00
refactoring
This commit is contained in:
@ -38,186 +38,156 @@
|
||||
* @constructor
|
||||
* @extends CustomAnnotator
|
||||
*/
|
||||
function AssistantHint(annotationPopup, assistantData)
|
||||
{
|
||||
CustomAnnotator.call(this, annotationPopup, assistantData);
|
||||
function AssistantHint(annotationPopup, assistantData) {
|
||||
CustomAnnotator.call(this, annotationPopup, assistantData);
|
||||
}
|
||||
AssistantHint.prototype = Object.create(CustomAnnotator.prototype);
|
||||
AssistantHint.prototype.constructor = AssistantHint;
|
||||
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string} recalcId
|
||||
* @param {string} text
|
||||
*/
|
||||
AssistantHint.prototype.annotateParagraph = async function(paraId, recalcId, text)
|
||||
{
|
||||
this.paragraphs[paraId] = {};
|
||||
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;
|
||||
|
||||
if (text.length === 0)
|
||||
return false;
|
||||
let count = 0;
|
||||
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);
|
||||
if (!response)
|
||||
return false;
|
||||
|
||||
let rangeId = 1;
|
||||
let ranges = [];
|
||||
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;
|
||||
},
|
||||
|
||||
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.)
|
||||
|
||||
/**
|
||||
* @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)
|
||||
Response format - return ONLY this JSON array with no additional text:
|
||||
[
|
||||
{
|
||||
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;
|
||||
"origin": "exact text fragment that matches the query",
|
||||
"reason": "detailed explanation why it matches the criteria",
|
||||
"paragraph": paragraph_number,
|
||||
"occurrence": 1,
|
||||
"confidence": 0.95
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
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)
|
||||
{ }
|
||||
}
|
||||
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
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @returns {string}
|
||||
*/
|
||||
AssistantHint.prototype._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.)
|
||||
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";
|
||||
|
||||
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
|
||||
}
|
||||
]
|
||||
prompt += "TEXT TO ANALYZE:\n```\n" + text + "\n```\n\n";
|
||||
|
||||
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
|
||||
prompt += `Please analyze this text and find all fragments that match the user's request. Be thorough but precise.`;
|
||||
|
||||
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";
|
||||
return prompt;
|
||||
},
|
||||
|
||||
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"]);
|
||||
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @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
|
||||
};
|
||||
};
|
||||
let range = this.getAnnotationRangeObj(paraId, rangeId);
|
||||
await Asc.Editor.callMethod("SelectAnnotationRange", [range]);
|
||||
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string} rangeId
|
||||
*/
|
||||
AssistantHint.prototype.onAccept = async function(paraId, rangeId)
|
||||
{
|
||||
await CustomAnnotator.prototype.onAccept.call(this);
|
||||
await Asc.Editor.callMethod("StartAction", ["GroupActions"]);
|
||||
await Asc.Editor.callCommand(function () {
|
||||
Api.GetDocument().RemoveSelection();
|
||||
});
|
||||
|
||||
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");
|
||||
};
|
||||
await Asc.Editor.callMethod("RemoveAnnotationRange", [range]);
|
||||
await Asc.Editor.callMethod("EndAction", ["GroupActions"]);
|
||||
await Asc.Editor.callMethod("FocusEditor");
|
||||
},
|
||||
});
|
||||
|
||||
@ -38,99 +38,71 @@
|
||||
* @constructor
|
||||
* @extends CustomAnnotator
|
||||
*/
|
||||
function AssistantReplaceHint(annotationPopup, assistantData)
|
||||
{
|
||||
CustomAnnotator.call(this, annotationPopup, assistantData);
|
||||
function AssistantReplaceHint(annotationPopup, assistantData) {
|
||||
CustomAnnotator.call(this, annotationPopup, assistantData);
|
||||
}
|
||||
AssistantReplaceHint.prototype = Object.create(CustomAnnotator.prototype);
|
||||
AssistantReplaceHint.prototype.constructor = AssistantReplaceHint;
|
||||
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string} recalcId
|
||||
* @param {string} text
|
||||
*/
|
||||
AssistantReplaceHint.prototype.annotateParagraph = async function(paraId, recalcId, text)
|
||||
{
|
||||
this.paragraphs[paraId] = {};
|
||||
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;
|
||||
|
||||
if (text.length === 0)
|
||||
return false;
|
||||
let count = 0;
|
||||
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);
|
||||
if (!response)
|
||||
return false;
|
||||
|
||||
let rangeId = 1;
|
||||
let ranges = [];
|
||||
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;
|
||||
}
|
||||
}
|
||||
return ranges;
|
||||
},
|
||||
|
||||
let _t = this;
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {ReplaceHintAiResponse[]} matches
|
||||
*/
|
||||
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.
|
||||
/**
|
||||
* @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:
|
||||
@ -180,58 +152,58 @@ AssistantReplaceHint.prototype._createPrompt = function(text) {
|
||||
- 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 +=
|
||||
"\n\nUSER REQUEST:\n```" + this.assistantData.query + "\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.`;
|
||||
|
||||
/**
|
||||
* @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
|
||||
};
|
||||
};
|
||||
return prompt;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string} rangeId
|
||||
*/
|
||||
AssistantReplaceHint.prototype.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"]);
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
return {
|
||||
original: _s["original"],
|
||||
suggested: _s["difference"],
|
||||
explanation: reason,
|
||||
type: this.type,
|
||||
};
|
||||
},
|
||||
|
||||
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");
|
||||
};
|
||||
/**
|
||||
* @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");
|
||||
},
|
||||
});
|
||||
|
||||
@ -38,97 +38,67 @@
|
||||
* @constructor
|
||||
* @extends CustomAnnotator
|
||||
*/
|
||||
function AssistantReplace(annotationPopup, assistantData)
|
||||
{
|
||||
CustomAnnotator.call(this, annotationPopup, assistantData);
|
||||
function AssistantReplace(annotationPopup, assistantData) {
|
||||
CustomAnnotator.call(this, annotationPopup, assistantData);
|
||||
}
|
||||
AssistantReplace.prototype = Object.create(CustomAnnotator.prototype);
|
||||
AssistantReplace.prototype.constructor = AssistantReplace;
|
||||
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string} recalcId
|
||||
* @param {string} text
|
||||
*/
|
||||
AssistantReplace.prototype.annotateParagraph = async function(paraId, recalcId, text)
|
||||
{
|
||||
this.paragraphs[paraId] = {};
|
||||
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;
|
||||
|
||||
if (text.length === 0)
|
||||
return false;
|
||||
let count = 0;
|
||||
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);
|
||||
if (!response)
|
||||
return false;
|
||||
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;
|
||||
},
|
||||
|
||||
let rangeId = 1;
|
||||
let ranges = [];
|
||||
|
||||
let _t = this;
|
||||
|
||||
/**
|
||||
* @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.
|
||||
/**
|
||||
* @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:
|
||||
@ -174,51 +144,51 @@ AssistantReplace.prototype._createPrompt = function(text) {
|
||||
- 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 +=
|
||||
"\n\nUSER REQUEST:\n```" + this.assistantData.query + "\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.`;
|
||||
|
||||
/**
|
||||
* @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
|
||||
};
|
||||
};
|
||||
return prompt;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string} rangeId
|
||||
*/
|
||||
AssistantReplace.prototype.onAccept = async function(paraId, rangeId)
|
||||
{
|
||||
await CustomAnnotator.prototype.onAccept.call(this);
|
||||
let text = this.getAnnotation(paraId, rangeId)["suggestion"];
|
||||
|
||||
await Asc.Editor.callMethod("StartAction", ["GroupActions"]);
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string} rangeId
|
||||
* @returns {ReplaceInfoForPopup}
|
||||
*/
|
||||
getInfoForPopup: function (paraId, rangeId) {
|
||||
let _s = this.getAnnotation(paraId, rangeId);
|
||||
return {
|
||||
original: _s["original"],
|
||||
suggested: _s["suggestion"],
|
||||
type: this.type,
|
||||
};
|
||||
},
|
||||
|
||||
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");
|
||||
};
|
||||
/**
|
||||
* @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");
|
||||
},
|
||||
});
|
||||
|
||||
@ -37,61 +37,86 @@
|
||||
* @constructor
|
||||
* @extends TextAnnotator
|
||||
*/
|
||||
function CustomAnnotator(annotationPopup, assistantData)
|
||||
{
|
||||
TextAnnotator.call(this, annotationPopup);
|
||||
this.assistantData = assistantData;
|
||||
this.type = assistantData.type;
|
||||
this._skipNextChangeParagraph = false;
|
||||
function CustomAnnotator(annotationPopup, assistantData) {
|
||||
TextAnnotator.call(this, annotationPopup);
|
||||
this.assistantData = assistantData;
|
||||
this.type = assistantData.type;
|
||||
this._skipNextChangeParagraph = false;
|
||||
}
|
||||
CustomAnnotator.prototype = Object.create(TextAnnotator.prototype);
|
||||
CustomAnnotator.prototype.constructor = CustomAnnotator;
|
||||
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string} rangeId
|
||||
*/
|
||||
CustomAnnotator.prototype.getAnnotationRangeObj = function(paraId, rangeId)
|
||||
{
|
||||
return {
|
||||
"paragraphId" : 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;
|
||||
Object.assign(CustomAnnotator.prototype, {
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {string} recalcId
|
||||
* @param {string} text
|
||||
*/
|
||||
annotateParagraph: async function (paraId, recalcId, text) {
|
||||
this.paragraphs[paraId] = {};
|
||||
|
||||
let rangeId = range["id"];
|
||||
let annot = this.getAnnotation(paraId, rangeId);
|
||||
|
||||
if (!annot)
|
||||
return;
|
||||
|
||||
let start = range["start"];
|
||||
let len = range["length"];
|
||||
if (text.length === 0) return false;
|
||||
|
||||
if (annot["original"] !== text.substring(start, start + len))
|
||||
{
|
||||
let annotRange = this.getAnnotationRangeObj(paraId, rangeId);
|
||||
Asc.Editor.callMethod("RemoveAnnotationRange", [annotRange]);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @param {string[]} paraIds
|
||||
*/
|
||||
CustomAnnotator.prototype.checkParagraphs = async function(paraIds)
|
||||
{
|
||||
if (this._skipNextChangeParagraph)
|
||||
{
|
||||
this._skipNextChangeParagraph = false;
|
||||
return;
|
||||
}
|
||||
TextAnnotator.prototype.checkParagraphs.call(this, paraIds);
|
||||
};
|
||||
CustomAnnotator.prototype.onAccept = async function(paraId, rangeId)
|
||||
{
|
||||
this._skipNextChangeParagraph = true;
|
||||
};
|
||||
const argPrompt = this._createPrompt(text);
|
||||
|
||||
let response = await this.chatRequest(argPrompt);
|
||||
if (!response) 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) {}
|
||||
},
|
||||
/**
|
||||
* @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);
|
||||
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;
|
||||
},
|
||||
});
|
||||
|
||||
@ -41,14 +41,14 @@
|
||||
|
||||
class CustomAssistantManager {
|
||||
constructor() {
|
||||
/**
|
||||
* @type {Map<string, Assistant>}
|
||||
*/
|
||||
/**
|
||||
* @type {Map<string, Assistant>}
|
||||
*/
|
||||
this._customAssistants = new Map();
|
||||
this._isCustomAssistantInit = new Map();
|
||||
this._isCustomAssistantRunning = new Map();
|
||||
/** @type {Map<string, {recalcId: number, text: string, annotations: any}>} */
|
||||
this._paragraphsStack = new Map();
|
||||
/** @type {Map<string, {recalcId: string, text: string, annotations: any}>} */
|
||||
this._paragraphsStack = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -62,19 +62,30 @@ class CustomAssistantManager {
|
||||
let assistant = null;
|
||||
switch (assistantData.type) {
|
||||
case 0:
|
||||
assistant = new AssistantHint(customAnnotationPopup, assistantData);
|
||||
assistant = new AssistantHint(
|
||||
customAnnotationPopup,
|
||||
assistantData,
|
||||
);
|
||||
break;
|
||||
case 1:
|
||||
assistant = new AssistantReplaceHint(customAnnotationPopup, assistantData);
|
||||
assistant = new AssistantReplaceHint(
|
||||
customAnnotationPopup,
|
||||
assistantData,
|
||||
);
|
||||
break;
|
||||
case 2:
|
||||
assistant = new AssistantReplace(customAnnotationPopup, assistantData);
|
||||
assistant = new AssistantReplace(
|
||||
customAnnotationPopup,
|
||||
assistantData,
|
||||
);
|
||||
break;
|
||||
}
|
||||
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._isCustomAssistantRunning.set(assistantData.id, false);
|
||||
this._customAssistants.set(assistantData.id, assistant);
|
||||
@ -96,13 +107,13 @@ class CustomAssistantManager {
|
||||
if (!isRunning) {
|
||||
return assistant;
|
||||
}
|
||||
|
||||
|
||||
this._paragraphsStack.forEach((value, paraId) => {
|
||||
assistant.onChangeParagraph(
|
||||
paraId,
|
||||
value.recalcId,
|
||||
value.text,
|
||||
value.annotations
|
||||
value.annotations,
|
||||
);
|
||||
});
|
||||
const paragraphIdsToUpdate = [...assistant.checked];
|
||||
@ -118,14 +129,11 @@ class CustomAssistantManager {
|
||||
this._isCustomAssistantRunning.delete(assistantId);
|
||||
}
|
||||
|
||||
/** @param {string} assistantId */
|
||||
/** @param {string} assistantId */
|
||||
checkNeedToRunAssistant(assistantId) {
|
||||
const isRunning = this._isCustomAssistantRunning.get(assistantId);
|
||||
this._isCustomAssistantRunning.set(
|
||||
assistantId,
|
||||
!isRunning
|
||||
);
|
||||
return isRunning;
|
||||
const isRunning = this._isCustomAssistantRunning.get(assistantId);
|
||||
this._isCustomAssistantRunning.set(assistantId, !isRunning);
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,53 +142,53 @@ class CustomAssistantManager {
|
||||
*/
|
||||
run(assistantId, paraIds) {
|
||||
const assistant = this._customAssistants.get(assistantId);
|
||||
if (!assistant) {
|
||||
console.error("Custom assistant not found: " + assistantId);
|
||||
return;
|
||||
}
|
||||
if (!assistant) {
|
||||
console.error("Custom assistant not found: " + assistantId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._isCustomAssistantInit.get(assistantId)) {
|
||||
this._paragraphsStack.forEach((value, paraId) => {
|
||||
assistant.onChangeParagraph(
|
||||
paraId,
|
||||
value.recalcId,
|
||||
value.text,
|
||||
value.annotations
|
||||
)
|
||||
});
|
||||
}
|
||||
if (!this._isCustomAssistantInit.get(assistantId)) {
|
||||
this._paragraphsStack.forEach((value, paraId) => {
|
||||
assistant.onChangeParagraph(
|
||||
paraId,
|
||||
value.recalcId,
|
||||
value.text,
|
||||
value.annotations,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
assistant.checkParagraphs(paraIds);
|
||||
this._isCustomAssistantInit.set(assistantId, true);
|
||||
this._isCustomAssistantInit.set(assistantId, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} paragraphId
|
||||
* @param {number} recalcId
|
||||
* @param {string} recalcId
|
||||
* @param {string} text
|
||||
* @param {*} annotations
|
||||
*/
|
||||
onChangeParagraph(paragraphId, recalcId, text, annotations) {
|
||||
this._paragraphsStack.set(paragraphId, {
|
||||
recalcId,
|
||||
text,
|
||||
annotations
|
||||
});
|
||||
this._paragraphsStack.set(paragraphId, {
|
||||
recalcId,
|
||||
text,
|
||||
annotations,
|
||||
});
|
||||
this._customAssistants.forEach((assistant, assistantId) => {
|
||||
const isInit = this._isCustomAssistantInit.get(assistantId);
|
||||
if (!isInit) {
|
||||
return;
|
||||
}
|
||||
assistant.onChangeParagraph(
|
||||
if (!isInit) {
|
||||
return;
|
||||
}
|
||||
assistant.onChangeParagraph(
|
||||
paragraphId,
|
||||
recalcId,
|
||||
text,
|
||||
annotations
|
||||
annotations,
|
||||
);
|
||||
const isRunning = this._isCustomAssistantRunning.get(assistantId);
|
||||
if (isRunning) {
|
||||
assistant.checkParagraphs([paragraphId]);
|
||||
}
|
||||
const isRunning = this._isCustomAssistantRunning.get(assistantId);
|
||||
if (isRunning) {
|
||||
assistant.checkParagraphs([paragraphId]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -37,7 +37,7 @@ function TextAnnotator(annotatorPopup)
|
||||
this.rangeId = null;
|
||||
|
||||
this.paragraphs = {};
|
||||
/** @type {Object.<string, {recalcId: number, text: string}>} */
|
||||
/** @type {Object.<string, {recalcId: string, text: string}>} */
|
||||
this.waitParagraphs = {};
|
||||
this.paraToCheck = new Set();
|
||||
/** @type {Set<string>} */
|
||||
@ -47,7 +47,7 @@ function TextAnnotator(annotatorPopup)
|
||||
}
|
||||
/**
|
||||
* @param {string} paraId
|
||||
* @param {number} recalcId
|
||||
* @param {string} recalcId
|
||||
* @param {string} text
|
||||
* @param {string[]} ranges
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user