formstrap 0.4.7 → 0.4.9

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.
@@ -13461,7 +13461,7 @@ var select_controller_default = class extends Controller {
13461
13461
  }
13462
13462
  firstUrl() {
13463
13463
  return (query) => {
13464
- let url = `${this.remoteUrlValue}.json`;
13464
+ let url = this.remoteUrlValue;
13465
13465
  url = this.setQueryParam(url, this.remoteQueryParamValue, query);
13466
13466
  url = this.setQueryParam(url, "per_page", this.perPage);
13467
13467
  url = this.setQueryParam(url, "page", 1);
@@ -13471,7 +13471,12 @@ var select_controller_default = class extends Controller {
13471
13471
  load() {
13472
13472
  return (query, callback) => {
13473
13473
  let url = this.tomSelect.getUrl(query);
13474
- fetch(url).then((response) => response.json()).then((json) => {
13474
+ fetch(url, {
13475
+ headers: {
13476
+ Accept: "application/json",
13477
+ "Conent-Type": "application/json"
13478
+ }
13479
+ }).then((response) => response.json()).then((json) => {
13475
13480
  if (json.length === this.perPage) {
13476
13481
  const currentPage = parseInt(this.getQueryParam(url, "page")) || 1;
13477
13482
  url = this.setQueryParam(url, "page", currentPage + 1);
@@ -14674,6 +14679,849 @@ Redactor.add("plugin", "linkstyles", {
14674
14679
  }
14675
14680
  });
14676
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
+ let 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
+ let buttons = {};
14787
+ let 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
+ let buttons = {};
14797
+ let 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
+ let 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
+ let apimodel = this.opts.get("ai." + this.promptType + ".model");
14827
+ let message = this._getMessage();
14828
+ let event = this.app.broadcast("ai.create", { prompt: message });
14829
+ message = event.get("prompt");
14830
+ this.modifiedValue = message;
14831
+ if (message === "")
14832
+ return;
14833
+ let 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
+ let 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
+ let event = this.app.broadcast("ai.before.insert", { html });
14867
+ html = event.get("html");
14868
+ let $target = this.savedInstance ? this.savedInstance.getBlock() : this.$prompt;
14869
+ let position = this.savedInstance ? "after" : "before";
14870
+ let remove = this.savedInstance ? false : true;
14871
+ setTimeout(function() {
14872
+ this.app.block.setTool(false);
14873
+ let 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
+ let eventName = e ? "ai.stop" : "ai.complete";
14894
+ let prompt = this.$previewLabel.text();
14895
+ let 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
+ let 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
+ let 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
+ let 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
+ let type = instances[i].getType(), prefix = type === "listitem" ? "- " : "";
14938
+ text = text + prefix + instances[i].getPlainText() + "\n";
14939
+ }
14940
+ }
14941
+ text = text.trim();
14942
+ text = text.replace(/\n$/, "");
14943
+ return text;
14944
+ },
14945
+ _getMessage() {
14946
+ return this.$textarea.val().trim();
14947
+ },
14948
+ _getNode() {
14949
+ let node = this.app.block.create();
14950
+ let $node = node.getBlock();
14951
+ $node = this._buildNode($node, { traverse: true });
14952
+ return $node;
14953
+ },
14954
+ _getInsertedNode() {
14955
+ let $node = this.dom('<div class="rx-inserted-node" style="white-space: pre-wrap;">');
14956
+ $node.html(this.opts.get("ai.spinner"));
14957
+ $node = this._buildNode($node, { traverse: false });
14958
+ return $node;
14959
+ },
14960
+ _setPrompt(text, html, prompt, empty) {
14961
+ if (text === "" && empty !== true)
14962
+ return;
14963
+ this.promptType = "text";
14964
+ this.promptText = text;
14965
+ this.promptHtml = html;
14966
+ let messages = [{ "role": "user", "content": text }, { "role": "user", "content": prompt }];
14967
+ let request = {
14968
+ model: this.opts.get("ai." + this.promptType + ".model"),
14969
+ messages
14970
+ };
14971
+ if (this.opts.is("ai.text.stream")) {
14972
+ let $node = this._getInsertedNode();
14973
+ $node.html(this.defaults.spinner);
14974
+ this.app.dropdown.close();
14975
+ this.app.context.close();
14976
+ this._sendStream($node, messages);
14977
+ } else {
14978
+ this._sendPrompt(request, "_insert");
14979
+ }
14980
+ },
14981
+ _sendPrompt(request, complete) {
14982
+ let data = {
14983
+ url: this.opts.get("ai." + this.promptType + ".endpoint"),
14984
+ data: JSON.stringify(request)
14985
+ };
14986
+ const utils = this.app.create("utils");
14987
+ data = utils.extendData(data, this.opts.get("ai." + this.promptType + ".data"));
14988
+ this.ajax.request("post", {
14989
+ url: this.opts.get("ai." + this.promptType + ".url"),
14990
+ data,
14991
+ before: function(xhr) {
14992
+ let event = this.app.broadcast("ai.before.send", { xhr, data });
14993
+ if (event.isStopped()) {
14994
+ return false;
14995
+ }
14996
+ }.bind(this),
14997
+ success: this[complete].bind(this),
14998
+ error: this._error.bind(this)
14999
+ });
15000
+ },
15001
+ _sendStream($node, message, preview) {
15002
+ const apimodel = this.opts.get("ai." + this.promptType + ".model");
15003
+ const apiurl = this.opts.get("ai." + this.promptType + ".endpoint");
15004
+ const serverurl = this.opts.get("ai." + this.promptType + ".url");
15005
+ let request = {
15006
+ model: apimodel,
15007
+ stream: this.opts.get("ai.text.stream"),
15008
+ messages: preview ? this.conversation : message
15009
+ };
15010
+ let data = {
15011
+ url: apiurl,
15012
+ data: JSON.stringify(request)
15013
+ };
15014
+ const utils = this.app.create("utils");
15015
+ data = utils.extendData(data, this.opts.get("ai." + this.promptType + ".data"));
15016
+ let responseContent = "";
15017
+ let source = this._createSource(serverurl, data);
15018
+ this.isEvent = source;
15019
+ this.currentIndex = 0;
15020
+ let $target = this.app.scroll.getTarget();
15021
+ $node.removeClass("rx-inserted-node-started");
15022
+ source.addEventListener("message", function(event) {
15023
+ this.app.dropdown.close();
15024
+ this.app.context.close();
15025
+ let message2 = event.data, start2 = message2.indexOf(": ", "data") + 2, data2 = message2.slice(start2, message2.length);
15026
+ if (data2 === "[DONE]") {
15027
+ this._sendStreamDone(source, $node, responseContent, preview);
15028
+ } else {
15029
+ data2 = JSON.parse(data2);
15030
+ if (data2.notification) {
15031
+ this._sendStreamDone(source, $node, data2.notification, preview);
15032
+ return;
15033
+ }
15034
+ const choices = data2.choices;
15035
+ if (choices && choices.length > 0) {
15036
+ let content = choices[0].delta.content;
15037
+ if (content) {
15038
+ if (!$node.hasClass("rx-inserted-node-started")) {
15039
+ $node.html("");
15040
+ this._sendStreamPreviewSet(preview);
15041
+ }
15042
+ responseContent += content;
15043
+ this._typeCharacter($node, responseContent);
15044
+ if (this._isElementBottomBeyond($node.get())) {
15045
+ $node.get().scrollIntoView(false);
15046
+ $target.scrollTop($target.scrollTop() + 20);
15047
+ }
15048
+ $node.addClass("rx-inserted-node-started");
15049
+ }
15050
+ }
15051
+ }
15052
+ }.bind(this));
15053
+ source.addEventListener("error", function(error2) {
15054
+ this._error(error2);
15055
+ source.close();
15056
+ this.isEvent = false;
15057
+ }.bind(this));
15058
+ },
15059
+ _sendStreamDone(source, $node, responseContent, preview) {
15060
+ if (!preview) {
15061
+ this._insertAfterNode($node, responseContent);
15062
+ } else {
15063
+ let checkInterval = setInterval(function() {
15064
+ if (this.currentIndex === responseContent.length) {
15065
+ clearInterval(checkInterval);
15066
+ this.stopPrompt(false, responseContent);
15067
+ this.conversation.push({ "role": "assistant", "content": responseContent });
15068
+ }
15069
+ }.bind(this), 100);
15070
+ }
15071
+ source.close();
15072
+ this.isEvent = false;
15073
+ },
15074
+ _sendStreamPreviewSet(preview) {
15075
+ if (!preview)
15076
+ return;
15077
+ let value = this.modifiedValue || this.$textarea.val();
15078
+ this.$progress.html("");
15079
+ this.$previewLabel.html(this._sanitize(value));
15080
+ this.$textarea.val("");
15081
+ this.$preview.html("");
15082
+ this.$preview.css({ "white-space": "pre-wrap" });
15083
+ this.$generate.hide();
15084
+ this.$stop.show();
15085
+ },
15086
+ _buildNode($node, traverse) {
15087
+ let $last;
15088
+ this.instance = false;
15089
+ if (this.app.blocks.is()) {
15090
+ if (!this.app.editor.isSelectAll()) {
15091
+ $last = this.app.blocks.get({ first: true, selected: true });
15092
+ $last = $last.closest("[data-rx-first-level]");
15093
+ $last.before($node);
15094
+ }
15095
+ let isAll = this.app.editor.isSelectAll();
15096
+ $last = this.app.blocks.removeAll();
15097
+ if (isAll) {
15098
+ $node = $last;
15099
+ }
15100
+ } else {
15101
+ this.instance = this.app.block.get();
15102
+ if (!this.instance) {
15103
+ this.instance = this.app.block.create();
15104
+ let $first = this.app.blocks.get({ first: true });
15105
+ $first.before(this.instance.getBlock());
15106
+ }
15107
+ if (this.instance.isType("listitem")) {
15108
+ this.instance.getBlock().html("").append($node);
15109
+ } else if (this.instance.isType("todoitem")) {
15110
+ this.instance.getContentItem().html("").append($node);
15111
+ } else {
15112
+ this.instance.getBlock().before($node);
15113
+ this.instance.remove(traverse);
15114
+ }
15115
+ }
15116
+ return $node;
15117
+ },
15118
+ _buildPrompt(params) {
15119
+ this.savedInstance = false;
15120
+ this.conversation = [];
15121
+ this.$prompt = this._createPrompt(params);
15122
+ this.$textarea.on("input focus", function() {
15123
+ this.app.block.setTool("ai");
15124
+ }.bind(this));
15125
+ let instance = this.app.block.get();
15126
+ let isMultiple = this.app.blocks.is();
15127
+ this.app.dropdown.close();
15128
+ this.app.context.close();
15129
+ if (instance || isMultiple) {
15130
+ if (isMultiple) {
15131
+ instance = this.app.blocks.get({ last: true, selected: true, instances: true });
15132
+ }
15133
+ const types = ["layout", "table", "quote", "list", "todo", "image", "embed"];
15134
+ let $parent = instance.getBlock().closest("[data-rx-type=" + types.join("],[data-rx-type=") + "]");
15135
+ let $column = instance.getBlock().closest("[data-rx-type=column]");
15136
+ if ($parent.length !== 0) {
15137
+ if ($column.length !== 0) {
15138
+ this.savedInstance = instance;
15139
+ }
15140
+ instance = $parent.dataget("instance");
15141
+ }
15142
+ this._insertPrompt(this.$prompt, instance);
15143
+ if (isMultiple) {
15144
+ this.app.blocks.unset();
15145
+ }
15146
+ } else {
15147
+ this._insertPrompt(this.$prompt);
15148
+ }
15149
+ this.app.editor.adjustHeight();
15150
+ },
15151
+ _error(error2, response) {
15152
+ let $node = this.app.editor.getEditor().find(".rx-inserted-node");
15153
+ if ($node.length !== 0) {
15154
+ this.app.dropdown.close();
15155
+ this.app.context.close();
15156
+ let insertion = this.app.create("insertion");
15157
+ insertion.insert({ target: $node, remove: true, caret: "end", html: this.promptHtml });
15158
+ }
15159
+ if (this.$progress) {
15160
+ this.$progress.fadeOut(500, function() {
15161
+ this.$progress.html("").removeAttr("style");
15162
+ }.bind(this));
15163
+ }
15164
+ this.app.broadcast("ai.error", error2 ? error2 : response);
15165
+ },
15166
+ _insert(response) {
15167
+ this.promptButton.setIcon("");
15168
+ if (response.error)
15169
+ return this._error(response.error.message, response);
15170
+ if (!response.choices)
15171
+ return this._error(response);
15172
+ let reply = response.choices[0].message.content;
15173
+ let html = this._parseReply(reply);
15174
+ let insertion = this.app.create("insertion");
15175
+ let event = this.app.broadcast("ai.before.insert", { html });
15176
+ html = event.get("html");
15177
+ this.app.dropdown.close();
15178
+ this.app.context.close();
15179
+ let inserted;
15180
+ let instanceType = this.instance && this.instance.isType(["listitem", "todoitem"]);
15181
+ if (instanceType) {
15182
+ this.instance.setContent(reply);
15183
+ inserted = this.instance.getBlock();
15184
+ } else {
15185
+ let $node = this._getNode();
15186
+ this.app.block.set($node);
15187
+ inserted = insertion.insert({ html, caret: "end" });
15188
+ }
15189
+ this.app.broadcast("ai.insert", { nodes: inserted });
15190
+ },
15191
+ _complete(response) {
15192
+ let reply;
15193
+ let html;
15194
+ let imageUrl;
15195
+ let value = this.modifiedValue || this.$textarea.val();
15196
+ let result;
15197
+ this.$progress.html("");
15198
+ this.$previewLabel.html("");
15199
+ if (response.error)
15200
+ return this._error(response.error.message, response);
15201
+ if (response.notification) {
15202
+ this.$preview.html(response.notification + "<br>");
15203
+ return;
15204
+ }
15205
+ this.$previewLabel.html(this._sanitize(value));
15206
+ this.$textarea.val("");
15207
+ this.$textarea.focus();
15208
+ if (this.promptType === "text") {
15209
+ if (!response.choices)
15210
+ return this._error(response);
15211
+ reply = response.choices[0].message.content;
15212
+ this.conversation.push({ role: "assistant", content: reply });
15213
+ html = this._parseReply(reply);
15214
+ result = html;
15215
+ this.$preview.html(html);
15216
+ this.$insert.show();
15217
+ let prompt = this.$previewLabel.text();
15218
+ this.app.broadcast("ai.complete", { prompt, response: result });
15219
+ this.app.editor.adjustHeight();
15220
+ } else if (this.promptType === "image") {
15221
+ if (!response.data)
15222
+ return this._error(response);
15223
+ imageUrl = response.data[0].url;
15224
+ const saveUrl = this.opts.get("ai.image.save");
15225
+ if (saveUrl) {
15226
+ const utils = this.app.create("utils");
15227
+ let data = {
15228
+ url: imageUrl
15229
+ };
15230
+ data = utils.extendData(data, this.opts.get("ai." + this.promptType + ".data"));
15231
+ this.ajax.request("post", {
15232
+ url: saveUrl,
15233
+ data,
15234
+ before: function(xhr) {
15235
+ let event = this.app.broadcast("ai.before.save", { xhr, data });
15236
+ if (event.isStopped()) {
15237
+ return false;
15238
+ }
15239
+ }.bind(this),
15240
+ success: function(response2) {
15241
+ this.app.broadcast("ai.save", response2);
15242
+ this._completeImage(response2.filename);
15243
+ }.bind(this),
15244
+ error: this._error.bind(this)
15245
+ });
15246
+ } else {
15247
+ this._completeImage(imageUrl);
15248
+ }
15249
+ }
15250
+ },
15251
+ _completeImage(imageUrl) {
15252
+ let $image = this.dom("<img>").attr("src", imageUrl);
15253
+ let result = $image.get().outerHTML;
15254
+ this.$preview.html($image);
15255
+ this.$insert.show();
15256
+ let prompt = this.$previewLabel.text();
15257
+ this.app.broadcast("ai.complete", { prompt, response: result });
15258
+ this.app.editor.adjustHeight();
15259
+ },
15260
+ _parseReply(reply) {
15261
+ let utils = this.app.create("utils");
15262
+ let cleaner = this.app.create("cleaner");
15263
+ let text = utils.parseMarkdown(reply);
15264
+ text = cleaner.store(text, "lists");
15265
+ text = cleaner.store(text, "headings");
15266
+ text = cleaner.store(text, "images");
15267
+ text = cleaner.store(text, "links");
15268
+ text = this._parseMarkdown(text);
15269
+ text = cleaner.restore(text, "lists");
15270
+ text = cleaner.restore(text, "headings");
15271
+ text = cleaner.restore(text, "images");
15272
+ text = cleaner.restore(text, "links");
15273
+ text = text.replace(/<p><(ul|ol)>/g, "<$1>");
15274
+ text = text.replace(/<\/(ul|ol)><\/p>/g, "</$1>");
15275
+ text = text.replace(/<p><\/p>/g, "");
15276
+ return text;
15277
+ },
15278
+ _createPrompt(params) {
15279
+ params = Redactor.extend(true, {}, { image: false }, params);
15280
+ this.promptType = params.image ? "image" : "text";
15281
+ let placeholder = this.lang.get("ai.placeholder-" + this.promptType);
15282
+ let $editor = this.app.editor.getEditor();
15283
+ $editor.find(".rx-ai-main").remove();
15284
+ let $main = this.dom('<div class="rx-in-tool rx-ai-main">').attr({ "contenteditable": false });
15285
+ let $body = this.dom('<div class="rx-ai-body">');
15286
+ let $footer = this.dom('<div class="rx-ai-footer">');
15287
+ let $buttons = this.dom('<div class="rx-ai-buttons">');
15288
+ this.$progress = this.dom('<div class="rx-ai-progress">');
15289
+ this.$previewLabel = this.dom('<div class="rx-ai-preview-label">');
15290
+ this.$preview = this.dom('<div class="rx-ai-preview">');
15291
+ this.$prompt = this.dom('<div class="rx-ai-prompt">');
15292
+ this.$label = this.dom('<label class="rx-ai-label">').html(this.lang.get("ai.prompt"));
15293
+ this.$textarea = this.dom('<textarea class="rx-ai-textarea rx-form-textarea">').attr({ "placeholder": placeholder });
15294
+ this.$select = this.dom('<select class="rx-ai-select rx-form-select">');
15295
+ this.$size = this.dom('<select class="rx-ai-size rx-form-select">');
15296
+ this._createPromptFooter($footer, $buttons);
15297
+ this.$prompt.append(this.$label);
15298
+ this.$prompt.append(this.$textarea);
15299
+ $body.append(this.$progress);
15300
+ $body.append(this.$previewLabel);
15301
+ $body.append(this.$preview);
15302
+ $body.append(this.$prompt);
15303
+ $main.append($body);
15304
+ $main.append($footer);
15305
+ return $main;
15306
+ },
15307
+ _createPromptFooter($footer, $buttons) {
15308
+ this._createTone(this.$select);
15309
+ this._createSize(this.$size);
15310
+ this._createPromptButtons($buttons);
15311
+ $footer.append(this.$select);
15312
+ if (this.promptType === "image") {
15313
+ $footer.append(this.$size);
15314
+ }
15315
+ $footer.append($buttons);
15316
+ },
15317
+ _createPromptButton(label) {
15318
+ return this.dom('<button class="rx-ai-button rx-form-button">').html(label);
15319
+ },
15320
+ _createSize($size) {
15321
+ let items = this.opts.get("ai.size");
15322
+ for (let [key, name] of Object.entries(items)) {
15323
+ let $option = this.dom("<option>").val(key).html(name);
15324
+ $size.append($option);
15325
+ }
15326
+ },
15327
+ _createTone($select) {
15328
+ let items = this.promptType === "image" ? this.opts.get("ai.style") || this.defaults.style : this.opts.get("ai.tone") || this.defaults.tone;
15329
+ let name = this.promptType === "image" ? this.lang.get("ai.image-style") : this.lang.get("ai.change-tone");
15330
+ let $option = this.dom("<option>").val(0).html(name);
15331
+ $select.append($option);
15332
+ $option = this.dom("<option>").val(1).html("---");
15333
+ $select.append($option);
15334
+ for (let i = 0; i < items.length; i++) {
15335
+ name = this.lang.parse(items[i]);
15336
+ $option = this.dom("<option>").val(name).html(name);
15337
+ $select.append($option);
15338
+ }
15339
+ },
15340
+ _createPromptButtons($buttons) {
15341
+ this.$generate = this._createPromptButton(this.lang.get("ai.send")).addClass("rx-form-button-primary").on("click.rx-ai", this.sendPrompt.bind(this));
15342
+ this.$stop = this._createPromptButton(this.lang.get("ai.stop")).addClass("rx-form-button-primary").on("click.rx-ai", this.stopPrompt.bind(this)).hide();
15343
+ this.$discard = this._createPromptButton(this.lang.get("ai.discard")).addClass("rx-form-button-danger").on("click.rx-ai", this.closePrompt.bind(this));
15344
+ this.$insert = this._createPromptButton(this.lang.get("ai.insert")).on("click.rx-ai", this.insertPrompt.bind(this)).hide();
15345
+ $buttons.append(this.$discard);
15346
+ $buttons.append(this.$insert);
15347
+ $buttons.append(this.$stop);
15348
+ $buttons.append(this.$generate);
15349
+ },
15350
+ _createSource(url, data) {
15351
+ const eventTarget = new EventTarget();
15352
+ let ajax = this.ajax.post({
15353
+ url,
15354
+ data,
15355
+ before: function(xhr2) {
15356
+ let event = this.app.broadcast("ai.before.send", { xhr: xhr2, data });
15357
+ if (event.isStopped()) {
15358
+ return false;
15359
+ }
15360
+ }.bind(this)
15361
+ });
15362
+ let xhr = ajax.xhr;
15363
+ let that = this;
15364
+ var ongoing = false, start2 = 0;
15365
+ xhr.onprogress = function() {
15366
+ if (!ongoing) {
15367
+ ongoing = true;
15368
+ eventTarget.dispatchEvent(new Event("open", {
15369
+ status: xhr.status,
15370
+ headers: xhr.getAllResponseHeaders(),
15371
+ url: xhr.responseUrl
15372
+ }));
15373
+ }
15374
+ let i, chunk;
15375
+ if (that._isJsonString(xhr.responseText)) {
15376
+ let response = JSON.parse(xhr.responseText);
15377
+ if (response.error) {
15378
+ that._error(response.error.message, response);
15379
+ eventTarget.close();
15380
+ return;
15381
+ }
15382
+ }
15383
+ while ((i = xhr.responseText.indexOf("\n\n", start2)) >= 0) {
15384
+ chunk = xhr.responseText.slice(start2, i);
15385
+ start2 = i + 2;
15386
+ if (chunk.length) {
15387
+ eventTarget.dispatchEvent(new MessageEvent("message", { data: chunk }));
15388
+ }
15389
+ }
15390
+ };
15391
+ eventTarget.close = function() {
15392
+ xhr.abort();
15393
+ };
15394
+ return eventTarget;
15395
+ },
15396
+ _isElementBottomBeyond(element) {
15397
+ let $target = this.app.scroll.getTarget(), rect = element.getBoundingClientRect(), elementBottom = rect.top + rect.height;
15398
+ return elementBottom > $target.get().innerHeight;
15399
+ },
15400
+ _isJsonString(str) {
15401
+ try {
15402
+ JSON.parse(str);
15403
+ } catch (e) {
15404
+ return false;
15405
+ }
15406
+ return true;
15407
+ },
15408
+ _insertAfterNode($tmp, content) {
15409
+ let inserted;
15410
+ let instanceType = this.instance && this.instance.isType(["listitem", "todoitem"]);
15411
+ let event = this.app.broadcast("ai.before.insert", { html: content });
15412
+ content = event.get("html");
15413
+ if (instanceType) {
15414
+ this.instance.setContent(content);
15415
+ inserted = this.instance.getBlock();
15416
+ } else {
15417
+ let insertion = this.app.create("insertion");
15418
+ let node = this.app.block.create();
15419
+ let $node = node.getBlock();
15420
+ content = this._parseReply(content);
15421
+ $tmp.after($node);
15422
+ $tmp.remove();
15423
+ this.app.block.set($node);
15424
+ inserted = insertion.insert({ html: content, caret: "end" });
15425
+ }
15426
+ this.app.broadcast("ai.insert", { nodes: inserted });
15427
+ },
15428
+ _insertPrompt($prompt, current, params) {
15429
+ let elm = this.app.create("element");
15430
+ let position = "after";
15431
+ if (!current) {
15432
+ if (this.opts.get("addPosition") === "top") {
15433
+ current = this.app.blocks.get({ first: true, instances: true });
15434
+ position = "before";
15435
+ } else {
15436
+ current = this.app.blocks.get({ last: true, instances: true });
15437
+ position = "after";
15438
+ }
15439
+ }
15440
+ let $current = current.getBlock();
15441
+ $current[position]($prompt);
15442
+ elm.scrollTo($prompt);
15443
+ this.app.observer.observeUnset();
15444
+ this.$textarea.focus();
15445
+ this.$textarea.on("input.rx-ai-autoresize keyup.rx-ai-autoreize", this._resize.bind(this));
15446
+ this.$textarea.on("keydown.rx-ai-event", this._promptKeydown.bind(this));
15447
+ },
15448
+ _promptKeydown(e) {
15449
+ if (e.key === "Enter" && !e.shiftKey) {
15450
+ e.preventDefault();
15451
+ this.sendPrompt(e);
15452
+ }
15453
+ },
15454
+ _typeCharacter($node, responseContent) {
15455
+ if (this.currentIndex < responseContent.length) {
15456
+ $node.get().textContent += responseContent.charAt(this.currentIndex);
15457
+ this.currentIndex++;
15458
+ this.app.editor.adjustHeight();
15459
+ setTimeout(function() {
15460
+ this._typeCharacter($node, responseContent);
15461
+ }.bind(this), 100);
15462
+ }
15463
+ },
15464
+ _resize() {
15465
+ this.$textarea.css("height", "auto");
15466
+ this.$textarea.css("height", this.$textarea.get().scrollHeight + "px");
15467
+ this.app.editor.adjustHeight();
15468
+ },
15469
+ _sanitize(str) {
15470
+ return str.replace(/[&<>"']/g, (char) => {
15471
+ switch (char) {
15472
+ case "&":
15473
+ return "&amp;";
15474
+ case "<":
15475
+ return "&lt;";
15476
+ case ">":
15477
+ return "&gt;";
15478
+ case '"':
15479
+ return "&quot;";
15480
+ case "'":
15481
+ return "&#39;";
15482
+ }
15483
+ });
15484
+ },
15485
+ _parseMarkdown(markdown) {
15486
+ let inCodeBlock = false;
15487
+ let result = "";
15488
+ const lines = markdown.split("\n");
15489
+ const tags = this.opts.get("replaceTags");
15490
+ for (const line of lines) {
15491
+ if (line.startsWith("```")) {
15492
+ inCodeBlock = !inCodeBlock;
15493
+ result += inCodeBlock ? this._replaceCodeLine(line) : "</code></pre>";
15494
+ } else if (line.startsWith("####_")) {
15495
+ result += line;
15496
+ } else {
15497
+ result += inCodeBlock ? this._escapeHtml(line) + "\n" : `<p>${this._escapeHtml(line)}</p>`;
15498
+ }
15499
+ }
15500
+ let bTag = tags.b ? tags.b : "b";
15501
+ let iTag = tags.i ? tags.i : "i";
15502
+ result = result.replace(/\*\*\_(.*?)\_\*\*/g, "<" + bTag + "><" + iTag + ">$1</" + iTag + "></" + bTag + ">");
15503
+ result = result.replace(/\*\*(.*?)\*\*/g, "<" + bTag + ">$1</" + bTag + ">");
15504
+ result = result.replace(/\*(.*?)\*/g, "<" + iTag + ">$1</" + iTag + ">");
15505
+ return result;
15506
+ },
15507
+ _replaceCodeLine(line) {
15508
+ return line.replace(/\`\`\`(([^\s]+))?/gm, function(match, p1, p2) {
15509
+ let classAttribute = p2 ? ' class="' + p2 + '"' : "";
15510
+ return "<pre" + classAttribute + "><code>";
15511
+ });
15512
+ },
15513
+ _escapeHtml(text) {
15514
+ const htmlEntities = {
15515
+ "&": "&amp;",
15516
+ "<": "&lt;",
15517
+ ">": "&gt;",
15518
+ '"': "&quot;",
15519
+ "'": "&#39;"
15520
+ };
15521
+ return text.replace(/[&<>"']/g, (match) => htmlEntities[match]);
15522
+ }
15523
+ });
15524
+
14677
15525
  // app/assets/javascripts/formstrap/index.js
14678
15526
  var Formstrap = class {
14679
15527
  static start() {