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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/formstrap/vendor/redactor/index.js +1 -0
- data/app/assets/javascripts/formstrap/vendor/redactor/plugins/ai.js +1053 -0
- data/app/assets/javascripts/formstrap/vendor/redactor/plugins/linkstyles.js +2 -2
- data/app/assets/javascripts/formstrap.js +851 -2
- data/app/controllers/formstrap/ai_controller.rb +22 -0
- data/app/models/formstrap/redactor_view.rb +26 -3
- data/config/locales/de.yml +6 -1
- data/config/locales/en.yml +6 -1
- data/config/locales/formstrap/de.yml +4 -0
- data/config/locales/formstrap/en.yml +4 -0
- data/config/locales/formstrap/fr.yml +4 -0
- data/config/locales/formstrap/nl.yml +4 -0
- data/config/locales/fr.yml +6 -1
- data/config/locales/nl.yml +6 -1
- data/config/routes.rb +4 -0
- data/lib/formstrap/configuration.rb +11 -0
- data/lib/formstrap/version.rb +1 -1
- data/lib/formstrap.rb +9 -0
- data/package.json +1 -1
- metadata +5 -2
@@ -14603,12 +14603,12 @@ Redactor.add("plugin", "linkstyles", {
|
|
14603
14603
|
}
|
14604
14604
|
},
|
14605
14605
|
"link.change": function(e) {
|
14606
|
-
let link = e.
|
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.
|
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 "&";
|
15480
|
+
case "<":
|
15481
|
+
return "<";
|
15482
|
+
case ">":
|
15483
|
+
return ">";
|
15484
|
+
case '"':
|
15485
|
+
return """;
|
15486
|
+
case "'":
|
15487
|
+
return "'";
|
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
|
+
"&": "&",
|
15522
|
+
"<": "<",
|
15523
|
+
">": ">",
|
15524
|
+
'"': """,
|
15525
|
+
"'": "'"
|
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() {
|