formstrap 0.4.8 → 0.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14603,12 +14603,12 @@ Redactor.add("plugin", "linkstyles", {
14603
14603
  }
14604
14604
  },
14605
14605
  "link.change": function(e) {
14606
- let link = e.params.element.nodes[0];
14606
+ let link = e.data.element.nodes[0];
14607
14607
  link = this.ensureValidProtocol(link);
14608
14608
  this.applyStylingToLink(link);
14609
14609
  },
14610
14610
  "link.add": function(e) {
14611
- let link = e.params.element.nodes[0];
14611
+ let link = e.data.element.nodes[0];
14612
14612
  link = this.ensureValidProtocol(link);
14613
14613
  this.applyStylingToLink(link);
14614
14614
  }
@@ -14679,6 +14679,855 @@ Redactor.add("plugin", "linkstyles", {
14679
14679
  }
14680
14680
  });
14681
14681
 
14682
+ // app/assets/javascripts/formstrap/vendor/redactor/plugins/ai.js
14683
+ Redactor.add("plugin", "ai", {
14684
+ translations: {
14685
+ en: {
14686
+ ai: {
14687
+ "placeholder-image": "Describe the image you want to generate.",
14688
+ "placeholder-text": "Tell me what you want to write.",
14689
+ send: "Send",
14690
+ stop: "Stop",
14691
+ discard: "Discard",
14692
+ insert: "Insert",
14693
+ prompt: "Prompt",
14694
+ "image-style": "Image style",
14695
+ "change-tone": "Change tone"
14696
+ }
14697
+ }
14698
+ },
14699
+ dropdowns: {
14700
+ items: {
14701
+ improve: { title: "Improve it", command: "ai.set", params: { prompt: "Improve it" } },
14702
+ simplify: { title: "Simplify it", command: "ai.set", params: { prompt: "Simplify it" } },
14703
+ fix: { title: "Fix any mistakes", command: "ai.set", params: { prompt: "Fix any mistakes" } },
14704
+ shorten: { title: "Make it shorter", command: "ai.set", params: { prompt: "Make it shorter" } },
14705
+ detailed: { title: "Make it more detailed", command: "ai.set", params: { prompt: "Make it more detailed" } },
14706
+ complete: { title: "Complete sentence", command: "ai.set", params: { prompt: "Complete sentence" } },
14707
+ tone: { title: "Change tone", command: "ai.popupTone" },
14708
+ translate: { title: "Translate", command: "ai.popupTranslate" }
14709
+ }
14710
+ },
14711
+ defaults: {
14712
+ tone: [
14713
+ "Academic",
14714
+ "Assertive",
14715
+ "Casual",
14716
+ "Confident",
14717
+ "Constructive",
14718
+ "Empathetic",
14719
+ "Exciting",
14720
+ "Fluent",
14721
+ "Formal",
14722
+ "Friendly",
14723
+ "Inspirational",
14724
+ "Professional"
14725
+ ],
14726
+ style: [
14727
+ "3d model",
14728
+ "Digital art",
14729
+ "Isometric",
14730
+ "Line art",
14731
+ "Photorealistic",
14732
+ "Pixel art"
14733
+ ],
14734
+ translate: [
14735
+ "Arabic",
14736
+ "Chinese",
14737
+ "English",
14738
+ "French",
14739
+ "German",
14740
+ "Greek",
14741
+ "Italian",
14742
+ "Japanese",
14743
+ "Korean",
14744
+ "Portuguese",
14745
+ "Russian",
14746
+ "Spanish",
14747
+ "Swedish",
14748
+ "Ukrainian"
14749
+ ],
14750
+ size: {
14751
+ "1792x1024": "Landscape",
14752
+ "1024x1792": "Portrait",
14753
+ "1024x1024": "Square"
14754
+ },
14755
+ text: {
14756
+ stream: true
14757
+ },
14758
+ image: {
14759
+ save: false
14760
+ },
14761
+ makeit: "Make it",
14762
+ translateto: "Translate to",
14763
+ spinner: '<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_nOfF{animation:spinner_qtyZ 2s cubic-bezier(0.36,.6,.31,1) infinite}.spinner_fVhf{animation-delay:-.5s}.spinner_piVe{animation-delay:-1s}.spinner_MSNs{animation-delay:-1.5s}@keyframes spinner_qtyZ{0%{r:0}25%{r:3px;cx:4px}50%{r:3px;cx:12px}75%{r:3px;cx:20px}100%{r:0;cx:20px}}</style><circle class="spinner_nOfF" cx="4" cy="12" r="3"/><circle class="spinner_nOfF spinner_fVhf" cx="4" cy="12" r="3"/><circle class="spinner_nOfF spinner_piVe" cx="4" cy="12" r="3"/><circle class="spinner_nOfF spinner_MSNs" cx="4" cy="12" r="3"/></svg>'
14764
+ },
14765
+ observe(obj, name, toolbar) {
14766
+ if (name === "ai-tools" && !this.opts.is("ai.text.url")) {
14767
+ return;
14768
+ } else if (name === "ai-image" && !this.opts.is("ai.image.url")) {
14769
+ return;
14770
+ }
14771
+ return obj;
14772
+ },
14773
+ popup(e, button) {
14774
+ const uiState = this.app.ui.getState();
14775
+ if (uiState.type !== "addbar") {
14776
+ this.app.dropdown.create("ai-tools", { items: this.opts.get("ai.items") || this.dropdowns.items });
14777
+ this.app.dropdown.open(e, button);
14778
+ } else {
14779
+ this._buildPrompt();
14780
+ }
14781
+ },
14782
+ promptImage(e, button) {
14783
+ this._buildPrompt({ image: true });
14784
+ },
14785
+ popupTone(e, button) {
14786
+ const buttons = {};
14787
+ const items = this.opts.get("ai.tone") || this.defaults.tone;
14788
+ const makeit = this.opts.get("ai.makeit") || this.defaults.makeit;
14789
+ for (let i = 0; i < items.length; i++) {
14790
+ buttons[i] = { title: items[i], command: "ai.set", params: { prompt: makeit + " " + items[i] } };
14791
+ }
14792
+ this.app.dropdown.create("ai-tone", { items: buttons });
14793
+ this.app.dropdown.open(e, button);
14794
+ },
14795
+ popupTranslate(e, button) {
14796
+ const buttons = {};
14797
+ const items = this.opts.get("ai.translate") || this.defaults.translate;
14798
+ const translateto = this.opts.get("ai.translateto") || this.defaults.translateto;
14799
+ for (let i = 0; i < items.length; i++) {
14800
+ buttons[i] = { title: items[i], command: "ai.set", params: { prompt: translateto + " " + items[i] } };
14801
+ }
14802
+ this.app.dropdown.create("ai-translate", { items: buttons });
14803
+ this.app.dropdown.open(e, button);
14804
+ },
14805
+ set(params, button) {
14806
+ this.promptButton = button;
14807
+ let text = this._getText();
14808
+ let html = this._getHtml();
14809
+ if (text !== "" && params.empty !== true) {
14810
+ this.promptButton.setIcon(this.defaults.spinner);
14811
+ }
14812
+ if (params.empty) {
14813
+ text = "";
14814
+ html = "";
14815
+ this.promptButton.setIcon(this.defaults.spinner);
14816
+ }
14817
+ let message = params.prompt;
14818
+ const event = this.app.broadcast("ai.create", { prompt: message });
14819
+ message = event.get("prompt");
14820
+ this.modifiedValue = message;
14821
+ this._setPrompt(text, html, message, params.empty);
14822
+ },
14823
+ sendPrompt(e) {
14824
+ e.preventDefault();
14825
+ e.stopPropagation();
14826
+ const apimodel = this.opts.get("ai." + this.promptType + ".model");
14827
+ let message = this._getMessage();
14828
+ const event = this.app.broadcast("ai.create", { prompt: message });
14829
+ message = event.get("prompt");
14830
+ this.modifiedValue = message;
14831
+ if (message === "")
14832
+ return;
14833
+ const tone = this._getTone(message);
14834
+ if (this.promptType === "text") {
14835
+ this.conversation.push({ role: "user", content: message });
14836
+ if (tone) {
14837
+ this.conversation.push({ role: "user", content: tone });
14838
+ }
14839
+ }
14840
+ let request = {
14841
+ model: apimodel,
14842
+ stream: this.opts.get("ai.text.stream"),
14843
+ messages: this.conversation
14844
+ };
14845
+ let size = "1024x1024";
14846
+ if (this.promptType === "image") {
14847
+ size = this.$size.val();
14848
+ }
14849
+ request = this.promptType === "image" ? { model: apimodel, n: 1, size, prompt: message } : request;
14850
+ this.$progress.html(this.defaults.spinner);
14851
+ if (this.promptType !== "image" && this.opts.is("ai.text.stream")) {
14852
+ this._sendStream(this.$preview, message, true);
14853
+ } else {
14854
+ this._sendPrompt(request, "_complete");
14855
+ }
14856
+ },
14857
+ insertPrompt(e) {
14858
+ e.preventDefault();
14859
+ e.stopPropagation();
14860
+ const insertion = this.app.create("insertion");
14861
+ let html = this.$preview.html();
14862
+ if (this.promptType === "image") {
14863
+ const tag = this.opts.get("image.tag");
14864
+ html = `<${tag}>${html}</${tag}>`;
14865
+ }
14866
+ const event = this.app.broadcast("ai.before.insert", { html });
14867
+ html = event.get("html");
14868
+ const $target = this.savedInstance ? this.savedInstance.getBlock() : this.$prompt;
14869
+ const position = this.savedInstance ? "after" : "before";
14870
+ const remove = !this.savedInstance;
14871
+ setTimeout(function() {
14872
+ this.app.block.setTool(false);
14873
+ const inserted = insertion.insert({ html, target: $target, position, remove });
14874
+ this.$prompt.remove();
14875
+ this.conversation = [];
14876
+ this.app.broadcast("ai.insert", { nodes: inserted });
14877
+ }.bind(this), 3);
14878
+ },
14879
+ stopPrompt(e, reply) {
14880
+ if (e) {
14881
+ e.preventDefault();
14882
+ e.stopPropagation();
14883
+ reply = this.$preview.text();
14884
+ if (this.isEvent) {
14885
+ this.isEvent.close();
14886
+ }
14887
+ }
14888
+ this.$preview.css({ "white-space": "" });
14889
+ this.$preview.html(this._parseReply(reply));
14890
+ this.$insert.show();
14891
+ this.$stop.hide();
14892
+ this.$generate.show();
14893
+ const eventName = e ? "ai.stop" : "ai.complete";
14894
+ const prompt = this.$previewLabel.text();
14895
+ const result = e ? { prompt } : { prompt, response: this._parseReply(reply) };
14896
+ this.app.broadcast(eventName, result);
14897
+ },
14898
+ closePrompt(e) {
14899
+ e.preventDefault();
14900
+ e.stopPropagation();
14901
+ this.app.block.setTool(false);
14902
+ this.$prompt.remove();
14903
+ this.conversation = [];
14904
+ if (this.isEvent) {
14905
+ this.isEvent.close();
14906
+ }
14907
+ this.app.broadcast("ai.discard");
14908
+ },
14909
+ _getTone(message) {
14910
+ const tone = this.$select.val();
14911
+ if (tone === "0" || tone === "1") {
14912
+ return false;
14913
+ }
14914
+ return `${this.opts.get("ai.makeit") || this.defaults.makeit} ${tone}`;
14915
+ },
14916
+ _getHtml() {
14917
+ let html = "";
14918
+ let instances = this.app.blocks.get({ selected: true, instances: true });
14919
+ const instance = this.app.block.get();
14920
+ if (instances.length === 0 && instance) {
14921
+ instances = [instance];
14922
+ }
14923
+ for (let i = 0; i < instances.length; i++) {
14924
+ html = html + instances[i].getOuterHtml();
14925
+ }
14926
+ return html;
14927
+ },
14928
+ _getText() {
14929
+ let text = "";
14930
+ let instances = this.app.blocks.get({ selected: true, instances: true });
14931
+ const instance = this.app.block.get();
14932
+ if (instances.length === 0 && instance) {
14933
+ instances = [instance];
14934
+ }
14935
+ for (let i = 0; i < instances.length; i++) {
14936
+ if (instances[i].isEditable()) {
14937
+ const type = instances[i].getType();
14938
+ const prefix = type === "listitem" ? "- " : "";
14939
+ text = text + prefix + instances[i].getPlainText() + "\n";
14940
+ }
14941
+ }
14942
+ text = text.trim();
14943
+ text = text.replace(/\n$/, "");
14944
+ return text;
14945
+ },
14946
+ _getMessage() {
14947
+ return this.$textarea.val().trim();
14948
+ },
14949
+ _getNode() {
14950
+ const node = this.app.block.create();
14951
+ let $node = node.getBlock();
14952
+ $node = this._buildNode($node, { traverse: true });
14953
+ return $node;
14954
+ },
14955
+ _getInsertedNode() {
14956
+ let $node = this.dom('<div class="rx-inserted-node" style="white-space: pre-wrap;">');
14957
+ $node.html(this.opts.get("ai.spinner"));
14958
+ $node = this._buildNode($node, { traverse: false });
14959
+ return $node;
14960
+ },
14961
+ _setPrompt(text, html, prompt, empty) {
14962
+ if (text === "" && empty !== true)
14963
+ return;
14964
+ this.promptType = "text";
14965
+ this.promptText = text;
14966
+ this.promptHtml = html;
14967
+ const messages = [{ role: "user", content: text }, { role: "user", content: prompt }];
14968
+ const request = {
14969
+ model: this.opts.get("ai." + this.promptType + ".model"),
14970
+ messages
14971
+ };
14972
+ if (this.opts.is("ai.text.stream")) {
14973
+ const $node = this._getInsertedNode();
14974
+ $node.html(this.defaults.spinner);
14975
+ this.app.dropdown.close();
14976
+ this.app.context.close();
14977
+ this._sendStream($node, messages);
14978
+ } else {
14979
+ this._sendPrompt(request, "_insert");
14980
+ }
14981
+ },
14982
+ _sendPrompt(request, complete) {
14983
+ let data = {
14984
+ url: this.opts.get("ai." + this.promptType + ".endpoint"),
14985
+ data: JSON.stringify(request)
14986
+ };
14987
+ const utils = this.app.create("utils");
14988
+ data = utils.extendData(data, this.opts.get("ai." + this.promptType + ".data"));
14989
+ this.ajax.request("post", {
14990
+ url: this.opts.get("ai." + this.promptType + ".url"),
14991
+ data,
14992
+ before: function(xhr) {
14993
+ const event = this.app.broadcast("ai.before.send", { xhr, data });
14994
+ if (event.isStopped()) {
14995
+ return false;
14996
+ }
14997
+ }.bind(this),
14998
+ success: this[complete].bind(this),
14999
+ error: this._error.bind(this)
15000
+ });
15001
+ },
15002
+ _sendStream($node, message, preview) {
15003
+ const apimodel = this.opts.get("ai." + this.promptType + ".model");
15004
+ const apiurl = this.opts.get("ai." + this.promptType + ".endpoint");
15005
+ const serverurl = this.opts.get("ai." + this.promptType + ".url");
15006
+ const request = {
15007
+ model: apimodel,
15008
+ stream: this.opts.get("ai.text.stream"),
15009
+ messages: preview ? this.conversation : message
15010
+ };
15011
+ let data = {
15012
+ url: apiurl,
15013
+ data: JSON.stringify(request)
15014
+ };
15015
+ const utils = this.app.create("utils");
15016
+ data = utils.extendData(data, this.opts.get("ai." + this.promptType + ".data"));
15017
+ let responseContent = "";
15018
+ const source = this._createSource(serverurl, data);
15019
+ this.isEvent = source;
15020
+ this.currentIndex = 0;
15021
+ const $target = this.app.scroll.getTarget();
15022
+ $node.removeClass("rx-inserted-node-started");
15023
+ source.addEventListener("message", function(event) {
15024
+ this.app.dropdown.close();
15025
+ this.app.context.close();
15026
+ const message2 = event.data;
15027
+ const start2 = message2.indexOf(": ", "data") + 2;
15028
+ let data2 = message2.slice(start2, message2.length);
15029
+ if (data2 === "[DONE]") {
15030
+ this._sendStreamDone(source, $node, responseContent, preview);
15031
+ } else {
15032
+ data2 = JSON.parse(data2);
15033
+ if (data2.notification) {
15034
+ this._sendStreamDone(source, $node, data2.notification, preview);
15035
+ return;
15036
+ }
15037
+ const choices = data2.choices;
15038
+ if (choices && choices.length > 0) {
15039
+ const content = choices[0].delta.content;
15040
+ if (content) {
15041
+ if (!$node.hasClass("rx-inserted-node-started")) {
15042
+ $node.html("");
15043
+ this._sendStreamPreviewSet(preview);
15044
+ }
15045
+ responseContent += content;
15046
+ this._typeCharacter($node, responseContent);
15047
+ if (this._isElementBottomBeyond($node.get())) {
15048
+ $node.get().scrollIntoView(false);
15049
+ $target.scrollTop($target.scrollTop() + 20);
15050
+ }
15051
+ $node.addClass("rx-inserted-node-started");
15052
+ }
15053
+ }
15054
+ }
15055
+ }.bind(this));
15056
+ source.addEventListener("error", function(error2) {
15057
+ this._error(error2);
15058
+ source.close();
15059
+ this.isEvent = false;
15060
+ }.bind(this));
15061
+ },
15062
+ _sendStreamDone(source, $node, responseContent, preview) {
15063
+ if (!preview) {
15064
+ this._insertAfterNode($node, responseContent);
15065
+ } else {
15066
+ const checkInterval = setInterval(function() {
15067
+ if (this.currentIndex === responseContent.length) {
15068
+ clearInterval(checkInterval);
15069
+ this.stopPrompt(false, responseContent);
15070
+ this.conversation.push({ role: "assistant", content: responseContent });
15071
+ }
15072
+ }.bind(this), 100);
15073
+ }
15074
+ source.close();
15075
+ this.isEvent = false;
15076
+ },
15077
+ _sendStreamPreviewSet(preview) {
15078
+ if (!preview)
15079
+ return;
15080
+ const value = this.modifiedValue || this.$textarea.val();
15081
+ this.$progress.html("");
15082
+ this.$previewLabel.html(this._sanitize(value));
15083
+ this.$textarea.val("");
15084
+ this.$preview.html("");
15085
+ this.$preview.css({ "white-space": "pre-wrap" });
15086
+ this.$generate.hide();
15087
+ this.$stop.show();
15088
+ },
15089
+ _buildNode($node, traverse) {
15090
+ let $last;
15091
+ this.instance = false;
15092
+ if (this.app.blocks.is()) {
15093
+ if (!this.app.editor.isSelectAll()) {
15094
+ $last = this.app.blocks.get({ first: true, selected: true });
15095
+ $last = $last.closest("[data-rx-first-level]");
15096
+ $last.before($node);
15097
+ }
15098
+ const isAll = this.app.editor.isSelectAll();
15099
+ $last = this.app.blocks.removeAll();
15100
+ if (isAll) {
15101
+ $node = $last;
15102
+ }
15103
+ } else {
15104
+ this.instance = this.app.block.get();
15105
+ if (!this.instance) {
15106
+ this.instance = this.app.block.create();
15107
+ const $first = this.app.blocks.get({ first: true });
15108
+ $first.before(this.instance.getBlock());
15109
+ }
15110
+ if (this.instance.isType("listitem")) {
15111
+ this.instance.getBlock().html("").append($node);
15112
+ } else if (this.instance.isType("todoitem")) {
15113
+ this.instance.getContentItem().html("").append($node);
15114
+ } else {
15115
+ this.instance.getBlock().before($node);
15116
+ this.instance.remove(traverse);
15117
+ }
15118
+ }
15119
+ return $node;
15120
+ },
15121
+ _buildPrompt(params) {
15122
+ this.savedInstance = false;
15123
+ this.conversation = [];
15124
+ this.$prompt = this._createPrompt(params);
15125
+ this.$textarea.on("input focus", function() {
15126
+ this.app.block.setTool("ai");
15127
+ }.bind(this));
15128
+ let instance = this.app.block.get();
15129
+ const isMultiple = this.app.blocks.is();
15130
+ this.app.dropdown.close();
15131
+ this.app.context.close();
15132
+ if (instance || isMultiple) {
15133
+ if (isMultiple) {
15134
+ instance = this.app.blocks.get({ last: true, selected: true, instances: true });
15135
+ }
15136
+ const types = ["layout", "table", "quote", "list", "todo", "image", "embed"];
15137
+ const $parent = instance.getBlock().closest("[data-rx-type=" + types.join("],[data-rx-type=") + "]");
15138
+ const $column = instance.getBlock().closest("[data-rx-type=column]");
15139
+ if ($parent.length !== 0) {
15140
+ if ($column.length !== 0) {
15141
+ this.savedInstance = instance;
15142
+ }
15143
+ instance = $parent.dataget("instance");
15144
+ }
15145
+ this._insertPrompt(this.$prompt, instance);
15146
+ if (isMultiple) {
15147
+ this.app.blocks.unset();
15148
+ }
15149
+ } else {
15150
+ this._insertPrompt(this.$prompt);
15151
+ }
15152
+ this.app.editor.adjustHeight();
15153
+ },
15154
+ _error(error2, response) {
15155
+ const $node = this.app.editor.getEditor().find(".rx-inserted-node");
15156
+ if ($node.length !== 0) {
15157
+ this.app.dropdown.close();
15158
+ this.app.context.close();
15159
+ const insertion = this.app.create("insertion");
15160
+ insertion.insert({ target: $node, remove: true, caret: "end", html: this.promptHtml });
15161
+ }
15162
+ if (this.$progress) {
15163
+ this.$progress.fadeOut(500, function() {
15164
+ this.$progress.html("").removeAttr("style");
15165
+ }.bind(this));
15166
+ }
15167
+ this.app.broadcast("ai.error", error2 || response);
15168
+ },
15169
+ _insert(response) {
15170
+ this.promptButton.setIcon("");
15171
+ if (response.error)
15172
+ return this._error(response.error.message, response);
15173
+ if (!response.choices)
15174
+ return this._error(response);
15175
+ const reply = response.choices[0].message.content;
15176
+ let html = this._parseReply(reply);
15177
+ const insertion = this.app.create("insertion");
15178
+ const event = this.app.broadcast("ai.before.insert", { html });
15179
+ html = event.get("html");
15180
+ this.app.dropdown.close();
15181
+ this.app.context.close();
15182
+ let inserted;
15183
+ const instanceType = this.instance && this.instance.isType(["listitem", "todoitem"]);
15184
+ if (instanceType) {
15185
+ this.instance.setContent(reply);
15186
+ inserted = this.instance.getBlock();
15187
+ } else {
15188
+ const $node = this._getNode();
15189
+ this.app.block.set($node);
15190
+ inserted = insertion.insert({ html, caret: "end" });
15191
+ }
15192
+ this.app.broadcast("ai.insert", { nodes: inserted });
15193
+ },
15194
+ _complete(response) {
15195
+ let reply;
15196
+ let html;
15197
+ let imageUrl;
15198
+ const value = this.modifiedValue || this.$textarea.val();
15199
+ let result;
15200
+ this.$progress.html("");
15201
+ this.$previewLabel.html("");
15202
+ if (response.error)
15203
+ return this._error(response.error.message, response);
15204
+ if (response.notification) {
15205
+ this.$preview.html(response.notification + "<br>");
15206
+ return;
15207
+ }
15208
+ this.$previewLabel.html(this._sanitize(value));
15209
+ this.$textarea.val("");
15210
+ this.$textarea.focus();
15211
+ if (this.promptType === "text") {
15212
+ if (!response.choices)
15213
+ return this._error(response);
15214
+ reply = response.choices[0].message.content;
15215
+ this.conversation.push({ role: "assistant", content: reply });
15216
+ html = this._parseReply(reply);
15217
+ result = html;
15218
+ this.$preview.html(html);
15219
+ this.$insert.show();
15220
+ const prompt = this.$previewLabel.text();
15221
+ this.app.broadcast("ai.complete", { prompt, response: result });
15222
+ this.app.editor.adjustHeight();
15223
+ } else if (this.promptType === "image") {
15224
+ if (!response.data)
15225
+ return this._error(response);
15226
+ imageUrl = response.data[0].url;
15227
+ const saveUrl = this.opts.get("ai.image.save");
15228
+ if (saveUrl) {
15229
+ const utils = this.app.create("utils");
15230
+ let data = {
15231
+ url: imageUrl
15232
+ };
15233
+ data = utils.extendData(data, this.opts.get("ai." + this.promptType + ".data"));
15234
+ this.ajax.request("post", {
15235
+ url: saveUrl,
15236
+ data,
15237
+ before: function(xhr) {
15238
+ const event = this.app.broadcast("ai.before.save", { xhr, data });
15239
+ if (event.isStopped()) {
15240
+ return false;
15241
+ }
15242
+ }.bind(this),
15243
+ success: function(response2) {
15244
+ this.app.broadcast("ai.save", response2);
15245
+ this._completeImage(response2.filename);
15246
+ }.bind(this),
15247
+ error: this._error.bind(this)
15248
+ });
15249
+ } else {
15250
+ this._completeImage(imageUrl);
15251
+ }
15252
+ }
15253
+ },
15254
+ _completeImage(imageUrl) {
15255
+ const $image = this.dom("<img>").attr("src", imageUrl);
15256
+ const result = $image.get().outerHTML;
15257
+ this.$preview.html($image);
15258
+ this.$insert.show();
15259
+ const prompt = this.$previewLabel.text();
15260
+ this.app.broadcast("ai.complete", { prompt, response: result });
15261
+ this.app.editor.adjustHeight();
15262
+ },
15263
+ _parseReply(reply) {
15264
+ const utils = this.app.create("utils");
15265
+ const cleaner = this.app.create("cleaner");
15266
+ let text = utils.parseMarkdown(reply);
15267
+ text = cleaner.store(text, "lists");
15268
+ text = cleaner.store(text, "headings");
15269
+ text = cleaner.store(text, "images");
15270
+ text = cleaner.store(text, "links");
15271
+ text = this._parseMarkdown(text);
15272
+ text = cleaner.restore(text, "lists");
15273
+ text = cleaner.restore(text, "headings");
15274
+ text = cleaner.restore(text, "images");
15275
+ text = cleaner.restore(text, "links");
15276
+ text = text.replace(/<p><(ul|ol)>/g, "<$1>");
15277
+ text = text.replace(/<\/(ul|ol)><\/p>/g, "</$1>");
15278
+ text = text.replace(/<p><\/p>/g, "");
15279
+ return text;
15280
+ },
15281
+ _createPrompt(params) {
15282
+ params = Redactor.extend(true, {}, { image: false }, params);
15283
+ this.promptType = params.image ? "image" : "text";
15284
+ const placeholder = this.lang.get("ai.placeholder-" + this.promptType);
15285
+ const $editor = this.app.editor.getEditor();
15286
+ $editor.find(".rx-ai-main").remove();
15287
+ const $main = this.dom('<div class="rx-in-tool rx-ai-main">').attr({ contenteditable: false });
15288
+ const $body = this.dom('<div class="rx-ai-body">');
15289
+ const $footer = this.dom('<div class="rx-ai-footer">');
15290
+ const $buttons = this.dom('<div class="rx-ai-buttons">');
15291
+ this.$progress = this.dom('<div class="rx-ai-progress">');
15292
+ this.$previewLabel = this.dom('<div class="rx-ai-preview-label">');
15293
+ this.$preview = this.dom('<div class="rx-ai-preview">');
15294
+ this.$prompt = this.dom('<div class="rx-ai-prompt">');
15295
+ this.$label = this.dom('<label class="rx-ai-label">').html(this.lang.get("ai.prompt"));
15296
+ this.$textarea = this.dom('<textarea class="rx-ai-textarea rx-form-textarea">').attr({ placeholder });
15297
+ this.$select = this.dom('<select class="rx-ai-select rx-form-select">');
15298
+ this.$size = this.dom('<select class="rx-ai-size rx-form-select">');
15299
+ this._createPromptFooter($footer, $buttons);
15300
+ this.$prompt.append(this.$label);
15301
+ this.$prompt.append(this.$textarea);
15302
+ $body.append(this.$progress);
15303
+ $body.append(this.$previewLabel);
15304
+ $body.append(this.$preview);
15305
+ $body.append(this.$prompt);
15306
+ $main.append($body);
15307
+ $main.append($footer);
15308
+ return $main;
15309
+ },
15310
+ _createPromptFooter($footer, $buttons) {
15311
+ this._createTone(this.$select);
15312
+ this._createSize(this.$size);
15313
+ this._createPromptButtons($buttons);
15314
+ $footer.append(this.$select);
15315
+ if (this.promptType === "image") {
15316
+ $footer.append(this.$size);
15317
+ }
15318
+ $footer.append($buttons);
15319
+ },
15320
+ _createPromptButton(label) {
15321
+ return this.dom('<button class="rx-ai-button rx-form-button">').html(label);
15322
+ },
15323
+ _createSize($size) {
15324
+ const items = this.opts.get("ai.size");
15325
+ for (const [key, name] of Object.entries(items)) {
15326
+ const $option = this.dom("<option>").val(key).html(name);
15327
+ $size.append($option);
15328
+ }
15329
+ },
15330
+ _createTone($select) {
15331
+ const items = this.promptType === "image" ? this.opts.get("ai.style") || this.defaults.style : this.opts.get("ai.tone") || this.defaults.tone;
15332
+ let name = this.promptType === "image" ? this.lang.get("ai.image-style") : this.lang.get("ai.change-tone");
15333
+ let $option = this.dom("<option>").val(0).html(name);
15334
+ $select.append($option);
15335
+ $option = this.dom("<option>").val(1).html("---");
15336
+ $select.append($option);
15337
+ for (let i = 0; i < items.length; i++) {
15338
+ name = this.lang.parse(items[i]);
15339
+ $option = this.dom("<option>").val(name).html(name);
15340
+ $select.append($option);
15341
+ }
15342
+ },
15343
+ _createPromptButtons($buttons) {
15344
+ this.$generate = this._createPromptButton(this.lang.get("ai.send")).addClass("rx-form-button-primary").on("click.rx-ai", this.sendPrompt.bind(this));
15345
+ this.$stop = this._createPromptButton(this.lang.get("ai.stop")).addClass("rx-form-button-primary").on("click.rx-ai", this.stopPrompt.bind(this)).hide();
15346
+ this.$discard = this._createPromptButton(this.lang.get("ai.discard")).addClass("rx-form-button-danger").on("click.rx-ai", this.closePrompt.bind(this));
15347
+ this.$insert = this._createPromptButton(this.lang.get("ai.insert")).on("click.rx-ai", this.insertPrompt.bind(this)).hide();
15348
+ $buttons.append(this.$discard);
15349
+ $buttons.append(this.$insert);
15350
+ $buttons.append(this.$stop);
15351
+ $buttons.append(this.$generate);
15352
+ },
15353
+ _createSource(url, data) {
15354
+ const eventTarget = new EventTarget();
15355
+ const ajax = this.ajax.post({
15356
+ url,
15357
+ data,
15358
+ before: function(xhr2) {
15359
+ const event = this.app.broadcast("ai.before.send", { xhr: xhr2, data });
15360
+ if (event.isStopped()) {
15361
+ return false;
15362
+ }
15363
+ }.bind(this)
15364
+ });
15365
+ const xhr = ajax.xhr;
15366
+ const that = this;
15367
+ let ongoing = false;
15368
+ let start2 = 0;
15369
+ xhr.onprogress = function() {
15370
+ if (!ongoing) {
15371
+ ongoing = true;
15372
+ eventTarget.dispatchEvent(new Event("open", {
15373
+ status: xhr.status,
15374
+ headers: xhr.getAllResponseHeaders(),
15375
+ url: xhr.responseUrl
15376
+ }));
15377
+ }
15378
+ let i, chunk;
15379
+ if (that._isJsonString(xhr.responseText)) {
15380
+ const response = JSON.parse(xhr.responseText);
15381
+ if (response.error) {
15382
+ that._error(response.error.message, response);
15383
+ eventTarget.close();
15384
+ return;
15385
+ }
15386
+ }
15387
+ while ((i = xhr.responseText.indexOf("\n\n", start2)) >= 0) {
15388
+ chunk = xhr.responseText.slice(start2, i);
15389
+ start2 = i + 2;
15390
+ if (chunk.length) {
15391
+ eventTarget.dispatchEvent(new MessageEvent("message", { data: chunk }));
15392
+ }
15393
+ }
15394
+ };
15395
+ eventTarget.close = function() {
15396
+ xhr.abort();
15397
+ };
15398
+ return eventTarget;
15399
+ },
15400
+ _isElementBottomBeyond(element) {
15401
+ const $target = this.app.scroll.getTarget();
15402
+ const rect = element.getBoundingClientRect();
15403
+ const elementBottom = rect.top + rect.height;
15404
+ return elementBottom > $target.get().innerHeight;
15405
+ },
15406
+ _isJsonString(str) {
15407
+ try {
15408
+ JSON.parse(str);
15409
+ } catch (e) {
15410
+ return false;
15411
+ }
15412
+ return true;
15413
+ },
15414
+ _insertAfterNode($tmp, content) {
15415
+ let inserted;
15416
+ const instanceType = this.instance && this.instance.isType(["listitem", "todoitem"]);
15417
+ const event = this.app.broadcast("ai.before.insert", { html: content });
15418
+ content = event.get("html");
15419
+ if (instanceType) {
15420
+ this.instance.setContent(content);
15421
+ inserted = this.instance.getBlock();
15422
+ } else {
15423
+ const insertion = this.app.create("insertion");
15424
+ const node = this.app.block.create();
15425
+ const $node = node.getBlock();
15426
+ content = this._parseReply(content);
15427
+ $tmp.after($node);
15428
+ $tmp.remove();
15429
+ this.app.block.set($node);
15430
+ inserted = insertion.insert({ html: content, caret: "end" });
15431
+ }
15432
+ this.app.broadcast("ai.insert", { nodes: inserted });
15433
+ },
15434
+ _insertPrompt($prompt, current, params) {
15435
+ const elm = this.app.create("element");
15436
+ let position = "after";
15437
+ if (!current) {
15438
+ if (this.opts.get("addPosition") === "top") {
15439
+ current = this.app.blocks.get({ first: true, instances: true });
15440
+ position = "before";
15441
+ } else {
15442
+ current = this.app.blocks.get({ last: true, instances: true });
15443
+ position = "after";
15444
+ }
15445
+ }
15446
+ const $current = current.getBlock();
15447
+ $current[position]($prompt);
15448
+ elm.scrollTo($prompt);
15449
+ this.app.observer.observeUnset();
15450
+ this.$textarea.focus();
15451
+ this.$textarea.on("input.rx-ai-autoresize keyup.rx-ai-autoreize", this._resize.bind(this));
15452
+ this.$textarea.on("keydown.rx-ai-event", this._promptKeydown.bind(this));
15453
+ },
15454
+ _promptKeydown(e) {
15455
+ if (e.key === "Enter" && !e.shiftKey) {
15456
+ e.preventDefault();
15457
+ this.sendPrompt(e);
15458
+ }
15459
+ },
15460
+ _typeCharacter($node, responseContent) {
15461
+ if (this.currentIndex < responseContent.length) {
15462
+ $node.get().textContent += responseContent.charAt(this.currentIndex);
15463
+ this.currentIndex++;
15464
+ this.app.editor.adjustHeight();
15465
+ setTimeout(function() {
15466
+ this._typeCharacter($node, responseContent);
15467
+ }.bind(this), 100);
15468
+ }
15469
+ },
15470
+ _resize() {
15471
+ this.$textarea.css("height", "auto");
15472
+ this.$textarea.css("height", this.$textarea.get().scrollHeight + "px");
15473
+ this.app.editor.adjustHeight();
15474
+ },
15475
+ _sanitize(str) {
15476
+ return str.replace(/[&<>"']/g, (char) => {
15477
+ switch (char) {
15478
+ case "&":
15479
+ return "&amp;";
15480
+ case "<":
15481
+ return "&lt;";
15482
+ case ">":
15483
+ return "&gt;";
15484
+ case '"':
15485
+ return "&quot;";
15486
+ case "'":
15487
+ return "&#39;";
15488
+ }
15489
+ });
15490
+ },
15491
+ _parseMarkdown(markdown) {
15492
+ let inCodeBlock = false;
15493
+ let result = "";
15494
+ const lines = markdown.split("\n");
15495
+ const tags = this.opts.get("replaceTags");
15496
+ for (const line of lines) {
15497
+ if (line.startsWith("```")) {
15498
+ inCodeBlock = !inCodeBlock;
15499
+ result += inCodeBlock ? this._replaceCodeLine(line) : "</code></pre>";
15500
+ } else if (line.startsWith("####_")) {
15501
+ result += line;
15502
+ } else {
15503
+ result += inCodeBlock ? this._escapeHtml(line) + "\n" : `<p>${this._escapeHtml(line)}</p>`;
15504
+ }
15505
+ }
15506
+ const bTag = tags.b ? tags.b : "b";
15507
+ const iTag = tags.i ? tags.i : "i";
15508
+ result = result.replace(/\*\*\_(.*?)\_\*\*/g, "<" + bTag + "><" + iTag + ">$1</" + iTag + "></" + bTag + ">");
15509
+ result = result.replace(/\*\*(.*?)\*\*/g, "<" + bTag + ">$1</" + bTag + ">");
15510
+ result = result.replace(/\*(.*?)\*/g, "<" + iTag + ">$1</" + iTag + ">");
15511
+ return result;
15512
+ },
15513
+ _replaceCodeLine(line) {
15514
+ return line.replace(/\`\`\`(([^\s]+))?/gm, function(match, p1, p2) {
15515
+ const classAttribute = p2 ? ' class="' + p2 + '"' : "";
15516
+ return "<pre" + classAttribute + "><code>";
15517
+ });
15518
+ },
15519
+ _escapeHtml(text) {
15520
+ const htmlEntities = {
15521
+ "&": "&amp;",
15522
+ "<": "&lt;",
15523
+ ">": "&gt;",
15524
+ '"': "&quot;",
15525
+ "'": "&#39;"
15526
+ };
15527
+ return text.replace(/[&<>"']/g, (match) => htmlEntities[match]);
15528
+ }
15529
+ });
15530
+
14682
15531
  // app/assets/javascripts/formstrap/index.js
14683
15532
  var Formstrap = class {
14684
15533
  static start() {