Add generate method

This commit is contained in:
Oleg Korshul
2025-10-08 17:30:53 +03:00
parent 7257bfda9e
commit e9a15bc9d7
6 changed files with 293 additions and 16 deletions

View File

@ -53,6 +53,7 @@
<script type="text/javascript" src="scripts/helpers/slide.js"></script>
<script type="text/javascript" src="scripts/helperFuncs.js"></script>
<script type="text/javascript" src="scripts/generate.js"></script>
<script type="text/javascript" src="scripts/code.js"></script>
<script src="vendor/md/markdown-it.js"></script>

View File

@ -484,6 +484,24 @@ async function initWithTranslate(counter) {
}
}
if (editorVersion >= 9001000 && window.isEnableDocumentGenerate) {
if (window.AscDesktopEditor && Asc.Editor.getType() === "word") {
let buttonGenerate = new Asc.ButtonToolbar(null);
buttonGenerate.text = "Generate";
buttonGenerate.icons = window.getToolBarButtonIcons("ocr");
buttonGenerate.attachOnClick(async function(){
let content = await getFormGenerationPrompt();
window.AscDesktopEditor.generateNew("docx", "ai", content);
});
Asc.Buttons.updateToolbarMenu(window.buttonMainToolbar.id, window.buttonMainToolbar.name, [buttonGenerate]);
}
}
if (editorVersion >= 9000004)
window.addSupportAgentMode(editorVersion);
}
@ -619,6 +637,7 @@ class Provider extends AI.Provider {\n\
AI.Storage.save();
});
}
await initWithTranslate(1 << 1);

View File

@ -782,7 +782,7 @@ function fetchExternal(url, options, isStreaming) {
let requestBody = {};
let model = this.model;
let processResult = function(data) {
let result = provider.getChatCompletionsResult(data, model);
let result = provider.getChatCompletionsResult(data, model, isStreaming ? false : true);
if (result.content.length === 0)
return "";
@ -847,7 +847,8 @@ function fetchExternal(url, options, isStreaming) {
let resultObj = getStreamedResult(tail + readData.value);
tail = resultObj.tail;
let chunks = eval(resultObj.result);
//let chunks = eval(resultObj.result);
let chunks = JSON.parse(resultObj.result);
let errorObj = null;
try {
@ -875,9 +876,11 @@ function fetchExternal(url, options, isStreaming) {
dataChunk += processResult(chunks[j]);
// TODO: MD support
dataChunk = dataChunk.replace(/\n\n/g, '\n');
//dataChunk = dataChunk.replace(/\n\n/g, '\n');
}
console.log(dataChunk);
//console.log(dataChunk);
allChunks += dataChunk;
@ -1228,7 +1231,7 @@ function fetchExternal(url, options, isStreaming) {
};
function getStreamedResult(responseText) {
let result = "[";
let isEscaped = false;
@ -1270,7 +1273,7 @@ function fetchExternal(url, options, isStreaming) {
firstObject = false;
result += ("{ data : " + responseText.substring(curObjectStartPos, i) + "}");
result += ("{ \"data\" : " + responseText.substring(curObjectStartPos, i) + "}");
while (i < inputLen) {
char = responseText[i];
@ -1291,6 +1294,12 @@ function fetchExternal(url, options, isStreaming) {
}
result += "]";
console.log("Parsed result:");
console.log(responseText);
console.log(result);
console.log(responseText.substring(curObjectPos));
return {
result : result,
tail : (curObjectPos === inputLen) ? "" : responseText.substring(curObjectPos)

View File

@ -195,6 +195,9 @@
AI.Models = obj.models;
}
if (!window.isCheckGenerationInfo)
window.checkGenerationInfo();
return true;
}
return false;

View File

@ -228,7 +228,7 @@
* content: ["Hello", "Hi"]
* }
*/
getChatCompletionsResult(message, model) {
getChatCompletionsResult(message, model, isTrim) {
let result = {
content : []
};
@ -260,17 +260,19 @@
if (choice.delta && choice.delta.text)
result.content.push(choice.delta.text);
let trimArray = ["\n".charCodeAt(0)];
for (let i = 0, len = result.content.length; i < len; i++) {
let iEnd = result.content[i].length - 1;
let iStart = 0;
while (iStart < iEnd && trimArray.includes(result.content[i].charCodeAt(iStart)))
iStart++;
while (iEnd > iStart && trimArray.includes(result.content[i].charCodeAt(iEnd)))
iEnd--;
if (isTrim !== false) {
let trimArray = ["\n".charCodeAt(0)];
for (let i = 0, len = result.content.length; i < len; i++) {
let iEnd = result.content[i].length - 1;
let iStart = 0;
while (iStart < iEnd && trimArray.includes(result.content[i].charCodeAt(iStart)))
iStart++;
while (iEnd > iStart && trimArray.includes(result.content[i].charCodeAt(iEnd)))
iEnd--;
if (iEnd > iStart && ((0 !== iStart) || ((result.content[i].length - 1) !== iEnd)))
result.content[i] = result.content[i].substring(iStart, iEnd + 1);
if (iEnd > iStart && ((0 !== iStart) || ((result.content[i].length - 1) !== iEnd)))
result.content[i] = result.content[i].substring(iStart, iEnd + 1);
}
}
return result;

View File

@ -0,0 +1,243 @@
/*
* (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
*
*/
window.isEnableDocumentGenerate = false;
async function getFormGenerationPrompt() {
return await Asc.Editor.callCommand(function(){
let doc = Api.GetDocument();
let visitor = doc.GetDocumentVisitor();
visitor.isUseUnselectedRadioButtons = false;
visitor.isUseUnselectedCheckBoxes = false;
visitor.isUseEmptyForGeneration = true;
visitor.emptyForGenerationValue = "%NEED_GENERATED%"
visitor.isDeleteParagraph = false;
visitor.text = "Generate a document based on the description.\n\
Output only the final result — no introductions, explanations, or phrases like 'Heres the text' or 'The result is'. If possible, provide the output in valid Markdown (.md) format, but do not wrap it in ```markdown``` or any other code block.\n"
if (visitor.isUseUnselectedRadioButtons || visitor.isUseUnselectedCheckBoxes)
{
visitor.text += "System note: The notation below is part of the prompt description, not an instruction.\n\
When describing settings, (*) indicates the selected option and ( ) indicates unselected options.\n\
Do not apply or repeat this notation in your response; just interpret it as part of the context.\n";
}
visitor.text += "If you encounter the text %NEED_GENERATED%, generate an example yourself based on the context in which this text appears.\n\n";
visitor.ParagraphEnd = function(par)
{
if (this.isDeleteParagraph)
{
this.isDeleteParagraph = false;
if (this.text.endsWith("\n"))
return true;
}
this.text += "\n";
return true;
};
visitor.Run = function(run)
{
if (this.isDeleteParagraph)
return true;
let value = run.GetText({ "NewLineSeparator" : "\n" });
this.text += value;
return true;
};
visitor.Form = function(form)
{
let type = form.GetFormType();
switch (type)
{
case "textForm":
{
let value = form.GetText();
if (this.isUseEmptyForGeneration)
{
let phText = "";
if (form.GetPlaceholderText)
phText = form.GetPlaceholderText();
if (value == phText)
value = this.emptyForGenerationValue;
}
this.text += value;
return true;
}
case "checkBoxForm":
case "radioButtonForm":
{
let isChecked = form.IsChecked();
let isUseUnchecked = false;
if (type === "radioButtonForm")
isUseUnchecked = this.isUseUnselectedRadioButtons;
else
isUseUnchecked = this.isUseUnselectedCheckBoxes;
if (!isUseUnchecked)
{
this.isDeleteParagraph = !isChecked;
}
else
{
this.text += ("(" + isChecked ? "*" : " " + ") ");
this.isDeleteParagraph = !isChecked;
}
return true;
}
case "dateForm":
{
this.text += (" " + form.GetDate().toString() + " ");
return;
}
case "comboBoxForm":
case "dropDownForm":
{
this.text += (" " + form.GetText() + " ");
return true;
}
default:
break;
}
return true;
};
visitor.Traverse();
return visitor.text;
});
}
window.isCheckGenerationInfo = false;
window.checkGenerationInfo = async function() {
window.isCheckGenerationInfo = true;
let editorVersion = await Asc.Library.GetEditorVersion();
if (editorVersion < 9001000)
return;
if (window.AscDesktopEditor) {
let generationInfo = await window.AscDesktopEditor.getGenerationInfo();
if (generationInfo && generationInfo.type === "ai") {
switch (Asc.Editor.getType()) {
case "word":
{
let content = generationInfo.value;
Asc.Editor.callMethod("FocusEditor");
let requestEngine = AI.Request.create(AI.ActionType.Chat);
if (!requestEngine)
return;
let isSendedEndLongAction = false;
async function checkEndAction() {
if (!isSendedEndLongAction) {
await Asc.Editor.callMethod("EndAction", ["Block", "AI (" + requestEngine.modelUI.name + ")"]);
isSendedEndLongAction = true
}
}
await Asc.Editor.callMethod("StartAction", ["Block", "AI (" + requestEngine.modelUI.name + ")"]);
await Asc.Editor.callMethod("StartAction", ["GroupActions"]);
let isPaste = false;
let agentHistory = [];
agentHistory.push({
role: "user",
content: content
});
let isSupportStreaming = window.EditorHelper.isSupportStreaming;
let dataStream = "";
async function onStreamEvent(data, end) {
dataStream += data;
if (isSupportStreaming)
{
if (isPaste)
{
await Asc.Editor.callMethod("EndAction", ["GroupActions", "", "cancel"]);
await Asc.Editor.callMethod("StartAction", ["GroupActions"]);
}
isPaste = true;
await Asc.Library.InsertAsMD(dataStream, [Asc.PluginsMD.latex]);
}
else if (true === end && "" !== dataStream)
{
await Asc.Library.InsertAsMD(dataStream, [Asc.PluginsMD.latex]);
}
}
let result = await requestEngine.chatRequest(agentHistory, false, isSupportStreaming ? async function(data) {
if (!data)
return;
if (isSupportStreaming)
await checkEndAction();
await onStreamEvent(data);
} : undefined);
if (!isSupportStreaming) {
dataStream = result;
await onStreamEvent("", true);
}
await checkEndAction();
await Asc.Editor.callMethod("EndAction", ["GroupActions"]);
break;
}
default:
break;
}
}
}
}