@opentiny/fluent-editor 4.0.0-alpha.4 → 4.0.0-alpha.6

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.
Files changed (106) hide show
  1. package/es/modules/ai/constants.es.js +50 -0
  2. package/es/modules/ai/constants.es.js.map +1 -0
  3. package/es/modules/ai/icons.es.js +319 -0
  4. package/es/modules/ai/icons.es.js.map +1 -0
  5. package/es/modules/ai/index.es.js +533 -101
  6. package/es/modules/ai/index.es.js.map +1 -1
  7. package/es/modules/custom-image/blot-formatter.es.js +4 -0
  8. package/es/modules/custom-image/blot-formatter.es.js.map +1 -1
  9. package/es/modules/custom-image/image.es.js +8 -1
  10. package/es/modules/custom-image/image.es.js.map +1 -1
  11. package/es/modules/custom-image/options.es.js +3 -0
  12. package/es/modules/custom-image/options.es.js.map +1 -1
  13. package/es/modules/custom-uploader.es.js +1 -1
  14. package/es/modules/custom-uploader.es.js.map +1 -1
  15. package/es/modules/emoji.es.js +3 -15
  16. package/es/modules/emoji.es.js.map +1 -1
  17. package/es/modules/table-up/index.es.js +16 -10
  18. package/es/modules/table-up/index.es.js.map +1 -1
  19. package/es/modules/toolbar/better-toolbar.es.js +1 -1
  20. package/es/modules/toolbar/better-toolbar.es.js.map +1 -1
  21. package/es/themes/snow.es.js +12 -0
  22. package/es/themes/snow.es.js.map +1 -1
  23. package/es/tools/format-painter.es.js +8 -2
  24. package/es/tools/format-painter.es.js.map +1 -1
  25. package/es/ui/icons.config.es.js +43 -13
  26. package/es/ui/icons.config.es.js.map +1 -1
  27. package/lib/modules/ai/constants.cjs.js +50 -0
  28. package/lib/modules/ai/constants.cjs.js.map +1 -0
  29. package/lib/modules/ai/icons.cjs.js +319 -0
  30. package/lib/modules/ai/icons.cjs.js.map +1 -0
  31. package/lib/modules/ai/index.cjs.js +533 -101
  32. package/lib/modules/ai/index.cjs.js.map +1 -1
  33. package/lib/modules/custom-image/blot-formatter.cjs.js +4 -0
  34. package/lib/modules/custom-image/blot-formatter.cjs.js.map +1 -1
  35. package/lib/modules/custom-image/image.cjs.js +8 -1
  36. package/lib/modules/custom-image/image.cjs.js.map +1 -1
  37. package/lib/modules/custom-image/options.cjs.js +3 -0
  38. package/lib/modules/custom-image/options.cjs.js.map +1 -1
  39. package/lib/modules/custom-uploader.cjs.js +1 -1
  40. package/lib/modules/custom-uploader.cjs.js.map +1 -1
  41. package/lib/modules/emoji.cjs.js +3 -15
  42. package/lib/modules/emoji.cjs.js.map +1 -1
  43. package/lib/modules/table-up/index.cjs.js +16 -10
  44. package/lib/modules/table-up/index.cjs.js.map +1 -1
  45. package/lib/modules/toolbar/better-toolbar.cjs.js +1 -1
  46. package/lib/modules/toolbar/better-toolbar.cjs.js.map +1 -1
  47. package/lib/themes/snow.cjs.js +12 -0
  48. package/lib/themes/snow.cjs.js.map +1 -1
  49. package/lib/tools/format-painter.cjs.js +8 -2
  50. package/lib/tools/format-painter.cjs.js.map +1 -1
  51. package/lib/ui/icons.config.cjs.js +43 -13
  52. package/lib/ui/icons.config.cjs.js.map +1 -1
  53. package/package.json +3 -3
  54. package/style.css +2471 -1509
  55. package/types/config/types/editor-config.interface.d.ts +9 -6
  56. package/types/modules/ai/constants.d.ts +30 -0
  57. package/types/modules/ai/icons.d.ts +21 -0
  58. package/types/modules/ai/index.d.ts +71 -13
  59. package/types/modules/ai/types.d.ts +16 -0
  60. package/types/modules/custom-image/image.d.ts +2 -0
  61. package/types/modules/custom-image/{Options.d.ts → options.d.ts} +1 -0
  62. package/types/modules/emoji.d.ts +0 -1
  63. package/types/modules/table-up/index.d.ts +0 -31
  64. package/types/tools/format-painter.d.ts +1 -2
  65. package/types/ui/icons.config.d.ts +1 -1
  66. package/types/config/types/additional-toolbar-item.interface.d.ts +0 -8
  67. package/types/config/types/content-change.interface.d.ts +0 -13
  68. package/types/config/types/content-save.interface.d.ts +0 -6
  69. package/types/config/types/counter-option.interface.d.ts +0 -9
  70. package/types/config/types/editor-toolbar.interface.d.ts +0 -6
  71. package/types/config/types/file-operation.interface.d.ts +0 -12
  72. package/types/config/types/focus-change.interface.d.ts +0 -4
  73. package/types/config/types/fullscreen-module.interface.d.ts +0 -4
  74. package/types/config/types/help-panel-item.interface.d.ts +0 -5
  75. package/types/config/types/help-panel-option.interface.d.ts +0 -7
  76. package/types/config/types/image-module.interface.d.ts +0 -3
  77. package/types/config/types/image-upload.interface.d.ts +0 -7
  78. package/types/config/types/load-on-demand-module.interface.d.ts +0 -5
  79. package/types/config/types/mention-module.interface.d.ts +0 -8
  80. package/types/config/types/paste-change.interface.d.ts +0 -6
  81. package/types/config/types/quick-menu-module.interface.d.ts +0 -3
  82. package/types/config/types/range.interface.d.ts +0 -4
  83. package/types/config/types/registry-options.interface.d.ts +0 -5
  84. package/types/config/types/selection-change.interface.d.ts +0 -8
  85. package/types/config/types/toolbar-item.interface.d.ts +0 -13
  86. package/types/config/types/validate-error.interface.d.ts +0 -13
  87. package/types/modules/custom-image/BlotFormatter.d.ts +0 -29
  88. package/types/modules/custom-image/actions/CustomResizeAction.d.ts +0 -24
  89. package/types/modules/custom-image/actions/DeleteAction.d.ts +0 -7
  90. package/types/modules/custom-image/image-bar.d.ts +0 -17
  91. package/types/modules/custom-image/specs/BlotSpec.d.ts +0 -13
  92. package/types/modules/custom-image/specs/CustomImageSpec.d.ts +0 -21
  93. package/types/modules/custom-image/specs/ImageSpec.d.ts +0 -10
  94. package/types/modules/emoji/emoji-list/index.d.ts +0 -1
  95. package/types/modules/emoji/emoji-list/people.d.ts +0 -1
  96. package/types/modules/emoji/emoji-list.d.ts +0 -2
  97. package/types/modules/emoji/emoji-map.d.ts +0 -2
  98. package/types/modules/emoji/emoji-sprite.d.ts +0 -1
  99. package/types/modules/emoji/formats/emoji-blot.d.ts +0 -15
  100. package/types/modules/emoji/index.d.ts +0 -3
  101. package/types/modules/emoji/modules/emoji.d.ts +0 -38
  102. package/types/modules/emoji/modules/toolbar-emoji.d.ts +0 -9
  103. package/types/modules/emoji/utils.d.ts +0 -1
  104. package/types/modules/mention/MentionLink.d.ts +0 -16
  105. /package/types/modules/custom-image/actions/{Action.d.ts → action.d.ts} +0 -0
  106. /package/types/modules/mention/{Mention.d.ts → mention.d.ts} +0 -0
@@ -3,7 +3,8 @@ var __defProp = Object.defineProperty;
3
3
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
5
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
6
- const icons_config = require("../../ui/icons.config.cjs.js");
6
+ const constants = require("./constants.cjs.js");
7
+ const icons = require("./icons.cjs.js");
7
8
  class AI {
8
9
  constructor(quill, options) {
9
10
  __publicField(this, "toolbar");
@@ -13,22 +14,56 @@ class AI {
13
14
  __publicField(this, "message");
14
15
  __publicField(this, "isBreak", false);
15
16
  // 打断标记
17
+ __publicField(this, "textNumber");
18
+ // 文本字数限制
19
+ __publicField(this, "_isSelectRangeMode", false);
20
+ // 选择/点击模式
21
+ __publicField(this, "_charCount", 0);
22
+ // 文本字数
23
+ __publicField(this, "_debounceTimer", null);
24
+ __publicField(this, "_inputPlaceholder", "");
25
+ __publicField(this, "_showOperationMenu", false);
26
+ __publicField(this, "_isThinking", false);
27
+ // 思考中
28
+ __publicField(this, "_showResultPopupEl", false);
29
+ // 结果弹窗
30
+ __publicField(this, "selectedText", "");
31
+ // 选择的文本
16
32
  __publicField(this, "inputValue", "");
17
33
  // 存储输入框的值
18
- __publicField(this, "SEND", "发送");
19
- // 发送按钮文字
20
- __publicField(this, "BREAK", "停止");
21
- // 取消按钮文字
22
- __publicField(this, "DONE", "完成");
23
- __publicField(this, "REGENERATE", "重新生成");
24
- __publicField(this, "CLOSE", "关闭");
34
+ __publicField(this, "resultMenuList", []);
35
+ __publicField(this, "operationMenuList", []);
36
+ __publicField(this, "_operationMenuItemList", []);
37
+ __publicField(this, "alertEl", null);
38
+ __publicField(this, "alertTimer", null);
39
+ __publicField(this, "selectionBubbleEl", null);
40
+ __publicField(this, "selectionRange", null);
25
41
  __publicField(this, "dialogContainerEl", null);
26
42
  __publicField(this, "wrapContainerEl", null);
27
- __publicField(this, "aiPreTextEl", null);
43
+ __publicField(this, "aiIconEl", null);
28
44
  __publicField(this, "inputContainerEl", null);
29
45
  __publicField(this, "inputEl", null);
30
- __publicField(this, "sendButtonEl", null);
46
+ __publicField(this, "menuContainerEl", null);
47
+ __publicField(this, "subMenuEl", null);
48
+ __publicField(this, "subMenuEditorEl", null);
49
+ __publicField(this, "subMenuToneEl", null);
50
+ __publicField(this, "subMenuAdjustEl", null);
51
+ __publicField(this, "inputRightEl", null);
52
+ __publicField(this, "inputSendBtnEl", null);
53
+ __publicField(this, "inputCloseBtnEl", null);
54
+ __publicField(this, "thinkContainerEl", null);
55
+ // 思考元素
56
+ __publicField(this, "thinkBtnEl", null);
31
57
  __publicField(this, "resultPopupEl", null);
58
+ __publicField(this, "resultPopupHeaderEl", null);
59
+ __publicField(this, "resultPopupContentEl", null);
60
+ __publicField(this, "resultPopupFooterEl", null);
61
+ __publicField(this, "resultPopupFooterTextEl", null);
62
+ __publicField(this, "resultRefreshBtnEl", null);
63
+ __publicField(this, "resultCopyBtnEl", null);
64
+ // 分享和朗读功能待放开
65
+ // private resultShareBtnEl: HTMLSpanElement | null = null
66
+ // private resultVoiceBtnEl: HTMLSpanElement | null = null
32
67
  __publicField(this, "actionMenuEl", null);
33
68
  this.quill = quill;
34
69
  this.options = options;
@@ -37,97 +72,434 @@ class AI {
37
72
  if (typeof this.toolbar !== "undefined") {
38
73
  this.toolbar.addHandler("ai", this.showAIInput.bind(this));
39
74
  }
40
- this.host = options.host;
75
+ this.quill.on("selection-change", this.handleSelectionChange.bind(this));
76
+ this.host = options.host || "https://api.deepseek.com/v1";
41
77
  this.apiKey = options.apiKey;
42
- this.model = options.model;
78
+ this.model = options.model || "deepseek-chat";
79
+ this.textNumber = options.contentMaxLength || 5e3;
80
+ this.resultMenuList = [
81
+ { text: constants.REPLACE_SELECT, icon: icons.REPLACE_SELECT_ICON },
82
+ { text: constants.INSERT_TEXT, icon: icons.INSERT_ICON, selectText: constants.INSERT_SUB_CONTENT_TEXT },
83
+ { text: constants.REGENERATE, icon: icons.REBUILD_ICON },
84
+ { text: constants.CLOSE, icon: icons.MENU_CLOSE_ICON }
85
+ ];
86
+ this.operationMenuList = [
87
+ { id: "editor", text: "编辑调整内容", icon: icons.EDITOR_ICON },
88
+ { id: "tone", text: "改写口吻", icon: icons.CALL_ICON },
89
+ { id: "adjust", text: "整理选区内容", icon: icons.ADJUST_ICON }
90
+ ];
43
91
  }
44
- positionElements() {
45
- if (!this.dialogContainerEl) return;
46
- const range = this.quill.getSelection();
47
- if (range) {
48
- const bounds = this.quill.getBounds(range.index);
49
- this.dialogContainerEl.style.position = "absolute";
50
- this.dialogContainerEl.style.top = `${bounds.top + bounds.height}px`;
92
+ // 工具栏启动
93
+ showAIInput() {
94
+ this.create();
95
+ this.selectionRange = this.quill.getSelection();
96
+ if (this.selectionRange.length) {
97
+ this.isSelectRangeMode = true;
98
+ } else {
99
+ this.isSelectRangeMode = false;
51
100
  }
101
+ this.positionElements();
102
+ const handleKeyDown = (e) => {
103
+ if (e.key === "Escape") {
104
+ this.closeAIPanel();
105
+ this.quill.container.removeEventListener("keydown", handleKeyDown);
106
+ }
107
+ };
108
+ this.quill.container.addEventListener("keydown", handleKeyDown);
109
+ }
110
+ // 气泡启动
111
+ selectTextEvent() {
112
+ if (!this.selectionRange) return;
113
+ this.create();
114
+ this.positionElements();
115
+ this.isSelectRangeMode = true;
116
+ }
117
+ create() {
118
+ this.createResultElement();
119
+ this.createOperationMenuElements();
120
+ this.createInputBoxElements();
121
+ this.addInputEvent();
122
+ this.addResultEvent();
123
+ this.handleActionMenuDisplay();
124
+ this.quill.container.appendChild(this.dialogContainerEl);
52
125
  }
53
- // 创建AI提示语元素
54
- createAiPreTextEl() {
55
- if (!this.aiPreTextEl) {
56
- this.aiPreTextEl = document.createElement("span");
57
- this.aiPreTextEl.className = "ql-ai-tip";
126
+ // 创建结果弹窗
127
+ createResultElement() {
128
+ if (!this.resultPopupEl) {
129
+ this.resultPopupEl = document.createElement("div");
130
+ this.resultPopupEl.className = "ql-ai-result";
131
+ this.resultPopupHeaderEl = document.createElement("div");
132
+ this.resultPopupHeaderEl.className = "ql-ai-result-header";
133
+ this.resultPopupHeaderEl.textContent = constants.RESULT_HEADER_TEXT;
134
+ this.resultPopupContentEl = document.createElement("div");
135
+ this.resultPopupContentEl.className = "ql-ai-result-content";
136
+ this.resultPopupFooterEl = document.createElement("div");
137
+ this.resultPopupFooterEl.className = "ql-ai-result-footer";
138
+ this.resultPopupFooterTextEl = document.createElement("span");
139
+ this.resultPopupFooterTextEl.className = "ql-ai-result-footer-text";
140
+ this.resultPopupFooterTextEl.textContent = `0`;
141
+ this.resultRefreshBtnEl = document.createElement("span");
142
+ this.resultRefreshBtnEl.className = "ql-ai-result-footer-refresh";
143
+ this.resultRefreshBtnEl.innerHTML = icons.REFRESH_ICON;
144
+ this.resultCopyBtnEl = document.createElement("span");
145
+ this.resultCopyBtnEl.className = "ql-ai-result-footer-copy";
146
+ this.resultCopyBtnEl.innerHTML = icons.COPY_ICON;
147
+ const resultFooterRightEl = document.createElement("div");
148
+ resultFooterRightEl.className = "ql-ai-result-footer-right";
149
+ resultFooterRightEl.appendChild(this.resultRefreshBtnEl);
150
+ resultFooterRightEl.appendChild(this.resultCopyBtnEl);
151
+ this.resultPopupFooterEl.appendChild(this.resultPopupFooterTextEl);
152
+ this.resultPopupFooterEl.appendChild(resultFooterRightEl);
153
+ this.resultPopupEl.appendChild(this.resultPopupHeaderEl);
154
+ this.resultPopupEl.appendChild(this.resultPopupContentEl);
155
+ this.resultPopupEl.appendChild(this.resultPopupFooterEl);
58
156
  }
157
+ this.showResultPopupEl = false;
59
158
  }
60
- // 创建弹出框
61
- createElements() {
159
+ createOperationMenuElements() {
160
+ if (!this.menuContainerEl) {
161
+ this.menuContainerEl = document.createElement("div");
162
+ this.menuContainerEl.className = "ql-ai-menu-container";
163
+ const mainMenu = document.createElement("div");
164
+ mainMenu.className = "ql-ai-main-menu";
165
+ this.operationMenuList.forEach(({ text, icon, id }) => {
166
+ const menuItem = document.createElement("div");
167
+ menuItem.className = "ql-ai-menu-item";
168
+ menuItem.innerHTML = `${icon}<span>${text}</span>${icons.RIGHT_ARROW_ICON}`;
169
+ menuItem.addEventListener("mouseenter", (e) => {
170
+ e.stopPropagation();
171
+ this.subMenuEl.style.display = "block";
172
+ this.subMenuEl.className = `ql-ai-sub-menu ${id}`;
173
+ this.createOperationMenuItem(id);
174
+ });
175
+ mainMenu.appendChild(menuItem);
176
+ });
177
+ if (!this.subMenuEl) {
178
+ this.subMenuEl = document.createElement("div");
179
+ this.subMenuEl.className = "ql-ai-sub-menu";
180
+ this.subMenuEl.style.display = "none";
181
+ }
182
+ this.menuContainerEl.appendChild(mainMenu);
183
+ this.menuContainerEl.appendChild(this.subMenuEl);
184
+ }
185
+ this.showOperationMenu = false;
186
+ }
187
+ createOperationMenuItem(id) {
188
+ let menuItemBox = this[constants.MENU_ID_MAP[id]];
189
+ if (!menuItemBox) {
190
+ menuItemBox = document.createElement("div");
191
+ }
192
+ while (this.subMenuEl.firstChild) {
193
+ this.subMenuEl.removeChild(this.subMenuEl.firstChild);
194
+ }
195
+ constants.MENU_TITLE_DATA[id].forEach(({ text, icon, id: id2 }) => {
196
+ const menuItem = document.createElement("div");
197
+ menuItem.className = "ql-ai-menu-item";
198
+ menuItem.innerHTML = `${icon || ""}<span>${text}</span>`;
199
+ menuItem.addEventListener("click", (e) => {
200
+ e.stopPropagation();
201
+ this.handleOperationMenuItemClick(text, id2);
202
+ });
203
+ menuItemBox.appendChild(menuItem);
204
+ });
205
+ this.subMenuEl.appendChild(menuItemBox);
206
+ }
207
+ createInputBoxElements() {
62
208
  if (!this.dialogContainerEl) {
63
209
  this.dialogContainerEl = document.createElement("div");
64
210
  this.dialogContainerEl.className = "ql-ai-dialog";
65
211
  this.wrapContainerEl = document.createElement("div");
66
212
  this.wrapContainerEl.className = "ql-ai-wrapper";
67
- this.wrapContainerEl.style.width = `${this.quill.container.clientWidth * 0.9}px`;
68
- this.inputContainerEl = document.createElement("div");
69
- this.inputContainerEl.className = "ql-ai-input";
70
- const aiIcon = document.createElement("div");
71
- aiIcon.className = "ql-ai-icon";
72
- aiIcon.innerHTML = icons_config.AI_ICON;
73
- this.createAiPreTextEl();
213
+ this.wrapContainerEl.style.width = `${this.quill.container.clientWidth - 30}px`;
214
+ this.createAIInputIcon();
74
215
  this.inputEl = document.createElement("input");
75
216
  this.inputEl.type = "text";
76
- this.inputEl.placeholder = "请输入内容";
77
- this.sendButtonEl = document.createElement("div");
78
- this.sendButtonEl.className = "ql-ai-send";
79
- this.resultPopupEl = document.createElement("div");
80
- this.resultPopupEl.className = "ql-ai-result";
81
- this.wrapContainerEl.appendChild(this.resultPopupEl);
82
- this.inputContainerEl.appendChild(aiIcon);
83
- this.inputContainerEl.appendChild(this.aiPreTextEl);
217
+ this.inputPlaceholder = this._isSelectRangeMode ? constants.SELECT_PLACEHOLDER : constants.INPUT_PLACEHOLDER;
218
+ this.inputSendBtnEl = document.createElement("span");
219
+ this.inputSendBtnEl.className = "ql-ai-input-right-send";
220
+ this.inputSendBtnEl.innerHTML = icons.SEND_BTN_ICON;
221
+ this.inputCloseBtnEl = document.createElement("span");
222
+ this.inputCloseBtnEl.className = "ql-ai-input-right-close";
223
+ this.inputCloseBtnEl.innerHTML = icons.CLOSE_ICON;
224
+ this.inputRightEl = document.createElement("div");
225
+ this.inputRightEl.className = "ql-ai-input-right";
226
+ this.inputContainerEl = document.createElement("div");
227
+ this.inputContainerEl.className = "ql-ai-input";
228
+ this.inputContainerEl.appendChild(this.aiIconEl);
84
229
  this.inputContainerEl.appendChild(this.inputEl);
85
- this.inputContainerEl.appendChild(this.sendButtonEl);
230
+ this.inputRightEl.appendChild(this.inputSendBtnEl);
231
+ this.inputRightEl.appendChild(this.inputCloseBtnEl);
232
+ this.inputContainerEl.appendChild(this.inputRightEl);
233
+ this.wrapContainerEl.appendChild(this.resultPopupEl);
86
234
  this.wrapContainerEl.appendChild(this.inputContainerEl);
235
+ this.wrapContainerEl.appendChild(this.menuContainerEl);
87
236
  this.dialogContainerEl.appendChild(this.wrapContainerEl);
237
+ } else {
238
+ this.dialogContainerEl.style.display = "block";
88
239
  }
89
- this.aiPreTextEl.textContent = `${this.model}帮你写:`;
90
- this.sendButtonEl.textContent = this.SEND;
91
- this.sendButtonEl.style.display = "block";
92
- this.resultPopupEl.style.display = "none";
93
- this.quill.container.appendChild(this.dialogContainerEl);
240
+ this.hiddenInputSendBtnEl();
94
241
  }
95
- showAIInput() {
96
- this.createElements();
97
- this.positionElements();
242
+ hiddenInputSendBtnEl(display = "none") {
243
+ if (this.inputEl && this.inputSendBtnEl) {
244
+ this.inputSendBtnEl.style.display = display;
245
+ }
246
+ }
247
+ copyResult() {
248
+ if (!this.resultPopupContentEl) return;
249
+ try {
250
+ const textToCopy = this.resultPopupContentEl.textContent || "";
251
+ navigator.clipboard.writeText(textToCopy).then(() => {
252
+ this.showAlert("内容已复制到剪贴板");
253
+ }).catch((err) => {
254
+ this.showAlert(`复制失败:${err}`);
255
+ });
256
+ } catch (err) {
257
+ this.showAlert(`复制失败:${err}`);
258
+ const textarea = document.createElement("textarea");
259
+ textarea.value = this.resultPopupContentEl.textContent || "";
260
+ document.body.appendChild(textarea);
261
+ textarea.select();
262
+ document.execCommand("copy");
263
+ document.body.removeChild(textarea);
264
+ }
265
+ }
266
+ // 分享和朗读功能待放开
267
+ // private shareResult() {
268
+ // if (!this.resultPopupContentEl) return
269
+ // const textToShare = this.resultPopupContentEl.textContent || ''
270
+ // const title = 'AI生成内容分享'
271
+ // if (navigator.share) {
272
+ // navigator.share({
273
+ // title,
274
+ // text: textToShare,
275
+ // })
276
+ // .catch((err) => {
277
+ // this.showAlert(`分享失败:${err}`)
278
+ // })
279
+ // }
280
+ // else {
281
+ // // 兼容不支持Web Share API的浏览器
282
+ // const shareUrl = `mailto:?subject=${encodeURIComponent(title)}&body=${encodeURIComponent(textToShare)}`
283
+ // window.open(shareUrl, '_blank')
284
+ // }
285
+ // }
286
+ // private voiceResult() {
287
+ // if (!this.resultPopupContentEl) return
288
+ // const textToSpeak = this.resultPopupContentEl.textContent || ''
289
+ // if ('speechSynthesis' in window) {
290
+ // const utterance = new SpeechSynthesisUtterance(textToSpeak)
291
+ // utterance.lang = 'zh-CN' // 设置中文语音
292
+ // speechSynthesis.speak(utterance)
293
+ // }
294
+ // else {
295
+ // this.showAlert('当前浏览器不支持语音合成API')
296
+ // // 可以在这里添加不支持语音的提示
297
+ // }
298
+ // }
299
+ addResultEvent() {
300
+ if (this.resultRefreshBtnEl) {
301
+ this.resultRefreshBtnEl.addEventListener("click", () => {
302
+ this.regenerateResponse();
303
+ });
304
+ }
305
+ if (this.resultCopyBtnEl) {
306
+ this.resultCopyBtnEl.addEventListener("click", () => {
307
+ this.copyResult();
308
+ });
309
+ }
310
+ }
311
+ // 显示选中文本的气泡
312
+ showSelectionBubble() {
313
+ if (!this.selectionBubbleEl) {
314
+ this.selectionBubbleEl = document.createElement("div");
315
+ this.selectionBubbleEl.className = "ql-ai-selection-bubble";
316
+ const icon = icons.AI_ICON.replaceAll("paint_linear_2", "paint_linear_bubble");
317
+ this.selectionBubbleEl.innerHTML = `${icon}<span>AI 智能</span>`;
318
+ this.selectionBubbleEl.addEventListener("click", () => this.selectTextEvent());
319
+ document.body.appendChild(this.selectionBubbleEl);
320
+ }
321
+ const { left, top } = this.quill.getBounds(this.selectionRange.index);
322
+ const { left: endLeft } = this.quill.getBounds(this.selectionRange.index + this.selectionRange.length);
323
+ const width = (endLeft - left) / 2;
324
+ const editorRect = this.quill.container.getBoundingClientRect();
325
+ this.selectionBubbleEl.style.display = "flex";
326
+ this.selectionBubbleEl.style.left = `${left + editorRect.left + width - 45}px`;
327
+ this.selectionBubbleEl.style.top = `${top + editorRect.top - 40}px`;
328
+ }
329
+ // 隐藏选中文本的气泡
330
+ hideSelectionBubble() {
331
+ if (this.selectionBubbleEl) {
332
+ this.selectionBubbleEl.style.display = "none";
333
+ }
334
+ }
335
+ // 处理文本选中变化
336
+ handleSelectionChange(range) {
337
+ if (range && range.length > 0) {
338
+ this.selectionRange = range;
339
+ this.showSelectionBubble();
340
+ this.selectedText = this.quill.getText(range.index, range.length);
341
+ } else {
342
+ if (range && range.index !== null) {
343
+ this.selectedText = "";
344
+ this.closeAIPanel();
345
+ } else {
346
+ this.hideSelectionBubble();
347
+ }
348
+ }
349
+ }
350
+ addInputEvent() {
351
+ if (this.inputContainerEl) {
352
+ this.inputContainerEl.addEventListener("click", () => {
353
+ });
354
+ }
355
+ if (this.inputEl) {
356
+ this.inputEl.addEventListener("input", () => {
357
+ this.hiddenInputSendBtnEl(this.inputEl.value.trim() ? "flex" : "none");
358
+ if (this.menuContainerEl && this._isSelectRangeMode) {
359
+ this.showOperationMenu = !this.inputEl.value.trim() && !this._showResultPopupEl;
360
+ }
361
+ });
362
+ }
363
+ if (this.inputSendBtnEl) {
364
+ this.inputSendBtnEl.addEventListener("click", async () => {
365
+ await this.queryAI();
366
+ });
367
+ }
98
368
  this.inputEl.addEventListener("keydown", async (e) => {
99
369
  if (e.key === "Enter") {
100
370
  await this.queryAI();
101
371
  }
102
372
  });
103
- this.sendButtonEl.addEventListener("click", async (e) => {
104
- if (e.target instanceof HTMLElement && e.target.textContent === this.BREAK) {
105
- this.isBreak = true;
106
- } else {
107
- await this.queryAI();
108
- }
109
- });
110
- const handleKeyDown = (e) => {
111
- if (e.key === "Escape") {
373
+ if (this.inputCloseBtnEl) {
374
+ this.inputCloseBtnEl.addEventListener("click", () => {
112
375
  this.closeAIPanel();
113
- this.quill.container.removeEventListener("keydown", handleKeyDown);
376
+ });
377
+ }
378
+ }
379
+ positionElements() {
380
+ if (!this.dialogContainerEl) return;
381
+ const range = this.selectionRange;
382
+ if (range) {
383
+ const bounds = this.quill.getBounds(range.index);
384
+ this.dialogContainerEl.style.position = "absolute";
385
+ this.dialogContainerEl.style.top = `${bounds.top + bounds.height + 20}px`;
386
+ }
387
+ }
388
+ // 添加创建alert元素的方法
389
+ createAlertElement() {
390
+ if (!this.alertEl) {
391
+ this.alertEl = document.createElement("div");
392
+ this.alertEl.className = "ql-ai-alert";
393
+ this.alertEl.style.display = "none";
394
+ document.body.appendChild(this.alertEl);
395
+ }
396
+ }
397
+ // 添加显示alert的方法
398
+ showAlert(message, duration = 3e3) {
399
+ this.createAlertElement();
400
+ if (!this.alertEl) return;
401
+ if (this.alertTimer) {
402
+ clearTimeout(this.alertTimer);
403
+ this.alertTimer = null;
404
+ }
405
+ this.alertEl.textContent = message;
406
+ this.alertEl.style.display = "block";
407
+ this.alertTimer = setTimeout(() => {
408
+ if (this.alertEl) {
409
+ this.alertEl.style.display = "none";
114
410
  }
115
- };
116
- this.quill.container.addEventListener("keydown", handleKeyDown);
411
+ this.alertTimer = null;
412
+ }, duration);
413
+ }
414
+ createAIInputIcon() {
415
+ if (!this.aiIconEl) {
416
+ this.aiIconEl = document.createElement("span");
417
+ this.aiIconEl.className = "ql-ai-input-pre-icon";
418
+ const icon = icons.AI_ICON.replaceAll("paint_linear_2", "paint_linear_ai_input");
419
+ this.aiIconEl.innerHTML = icon;
420
+ }
421
+ }
422
+ // 添加处理子菜单点击的方法
423
+ handleOperationMenuItemClick(text, id = "") {
424
+ let quetion = "";
425
+ if (id.startsWith("1-") || id.startsWith("3-")) {
426
+ quetion = `将目标文字${text},目标文字为:${this.selectedText}`;
427
+ } else if (id.startsWith("2-")) {
428
+ quetion = `改写目标文字的口吻,让其变得${text},目标文字为:${this.selectedText}`;
429
+ }
430
+ this.showOperationMenu = false;
431
+ this.queryAI(quetion);
432
+ }
433
+ createActionMenu() {
434
+ if (!this.actionMenuEl) {
435
+ this.actionMenuEl = document.createElement("div");
436
+ this.actionMenuEl.className = "ql-ai-actions";
437
+ this.resultMenuList.forEach(({ text, icon }) => {
438
+ const menuItem = document.createElement("div");
439
+ menuItem.className = "ql-ai-action-item";
440
+ menuItem.innerHTML = `${icon}<span class="ql-ai-result-menu-text">${text}</span>`;
441
+ menuItem.addEventListener("click", () => this.handleAction(text));
442
+ this.actionMenuEl.appendChild(menuItem);
443
+ });
444
+ this.wrapContainerEl.appendChild(this.actionMenuEl);
445
+ }
446
+ const secondMenuItemText = this.actionMenuEl.children[1].querySelector(".ql-ai-result-menu-text");
447
+ const firstChild = this.actionMenuEl.firstChild;
448
+ if (!this._isSelectRangeMode) {
449
+ if (firstChild instanceof Element) {
450
+ firstChild.classList.add("hidden");
451
+ }
452
+ secondMenuItemText.textContent = constants.INSERT_TEXT;
453
+ } else {
454
+ if (firstChild instanceof Element) {
455
+ firstChild.classList.remove("hidden");
456
+ }
457
+ secondMenuItemText.textContent = constants.INSERT_SUB_CONTENT_TEXT;
458
+ }
459
+ this.isThinking = false;
460
+ }
461
+ handleActionMenuDisplay(value = "none") {
462
+ if (this.actionMenuEl) {
463
+ this.actionMenuEl.style.display = value;
464
+ }
465
+ }
466
+ switchInputEl(showInput = true) {
467
+ if (this.inputContainerEl) {
468
+ this.inputContainerEl.style.display = showInput ? "flex" : "none";
469
+ }
470
+ this.handleActionMenuDisplay(showInput ? "block" : "none");
471
+ if (this.thinkContainerEl) {
472
+ this.thinkContainerEl.style.display = showInput ? "none" : "flex";
473
+ }
474
+ }
475
+ // 创建思考元素
476
+ createThinkElements() {
477
+ if (!this.thinkContainerEl) {
478
+ this.thinkContainerEl = document.createElement("div");
479
+ this.thinkContainerEl.className = "ql-ai-input";
480
+ this.thinkContainerEl.innerHTML = `<span class="ql-ai-input-pre-icon ql-ai-think-icon">${icons.THINK_ICON}</span><span class="ql-ai-think-text">${constants.THINK_TEXT}</span>`;
481
+ this.thinkBtnEl = document.createElement("div");
482
+ this.thinkBtnEl.className = "ql-ai-think-btn";
483
+ this.thinkBtnEl.innerHTML = `${icons.STOP_ICON}<span>${constants.STOP_ANSWER}</span>`;
484
+ this.thinkContainerEl.appendChild(this.thinkBtnEl);
485
+ this.wrapContainerEl.appendChild(this.thinkContainerEl);
486
+ this.thinkBtnEl.addEventListener("click", () => {
487
+ this.isBreak = true;
488
+ this.isThinking = false;
489
+ });
490
+ }
491
+ this.isThinking = true;
117
492
  }
118
493
  // AI查询
119
494
  async queryAI(question) {
495
+ this.createThinkElements();
120
496
  this.inputValue = question || this.inputEl.value;
121
- this.inputEl.value = "";
122
497
  if (this.inputValue.trim() === "") {
123
498
  return;
124
499
  }
125
500
  this.isBreak = false;
126
- this.sendButtonEl.textContent = this.BREAK;
127
- this.sendButtonEl.style.display = "block";
128
- this.aiPreTextEl.textContent = "按ESC退出 | 正在编写...";
129
501
  try {
130
- const response = await fetch(`${this.host}/api/generate`, {
502
+ const response = await fetch(`${this.host}`, {
131
503
  method: "POST",
132
504
  headers: {
133
505
  "Content-Type": "application/json",
@@ -165,11 +537,8 @@ class AI {
165
537
  }
166
538
  }
167
539
  this.createActionMenu();
168
- if (content) {
169
- this.aiPreTextEl.textContent = "";
170
- this.sendButtonEl.textContent = this.SEND;
171
- this.sendButtonEl.style.display = "none";
172
- }
540
+ this.inputEl.value = "";
541
+ this.hiddenInputSendBtnEl();
173
542
  return content;
174
543
  } catch (error) {
175
544
  console.error("AI查询失败:", error);
@@ -178,60 +547,123 @@ class AI {
178
547
  }
179
548
  showAIResponse(response) {
180
549
  if (!this.resultPopupEl) return;
181
- this.resultPopupEl.innerHTML = response;
182
- this.resultPopupEl.style.display = "block";
183
- }
184
- createActionMenu() {
185
- if (!this.actionMenuEl) {
186
- this.actionMenuEl = document.createElement("div");
187
- this.actionMenuEl.className = "ql-ai-actions";
188
- const actions = [this.DONE, this.REGENERATE, this.CLOSE];
189
- actions.forEach((action) => {
190
- const menuItem = document.createElement("div");
191
- menuItem.className = "ql-ai-action-item";
192
- menuItem.textContent = action;
193
- menuItem.addEventListener("click", () => this.handleAction(action));
194
- this.actionMenuEl.appendChild(menuItem);
195
- });
196
- this.wrapContainerEl.appendChild(this.actionMenuEl);
550
+ if (this._charCount <= this.textNumber) {
551
+ this.resultPopupContentEl.innerHTML = response;
552
+ this.charCount = this.resultPopupContentEl.textContent.replace(/\s+/g, "").length;
553
+ } else {
554
+ this.isBreak = true;
555
+ this.charCount = 0;
197
556
  }
198
- this.actionMenuEl.style.display = "block";
557
+ this.showResultPopupEl = true;
199
558
  }
200
559
  handleAction(action) {
201
560
  switch (action) {
202
- case this.DONE:
561
+ case constants.REPLACE_SELECT:
562
+ this.replaceSelectText();
563
+ break;
564
+ case constants.INSERT_TEXT:
203
565
  this.insertAIResponse();
204
566
  break;
205
- case this.REGENERATE:
567
+ case constants.REGENERATE:
206
568
  this.regenerateResponse();
207
569
  break;
208
- case this.CLOSE:
570
+ case constants.CLOSE:
209
571
  this.closeAIPanel();
210
572
  break;
211
573
  }
212
574
  }
575
+ replaceSelectText() {
576
+ if (!this.resultPopupContentEl) return;
577
+ const range = this.quill.getSelection(true);
578
+ if (range && range.length > 0) {
579
+ this.quill.deleteText(range.index, range.length);
580
+ this.quill.clipboard.dangerouslyPasteHTML(range.index, this.resultPopupContentEl.innerHTML);
581
+ }
582
+ this.closeAIPanel();
583
+ }
213
584
  insertAIResponse() {
214
- if (!this.resultPopupEl) return;
585
+ if (!this.resultPopupContentEl) return;
215
586
  const range = this.quill.getSelection(true);
216
587
  if (range) {
217
- this.quill.clipboard.dangerouslyPasteHTML(
218
- range.index,
219
- this.resultPopupEl.innerHTML
220
- );
588
+ this.quill.clipboard.dangerouslyPasteHTML(range.index + range.length, this.resultPopupContentEl.innerHTML);
221
589
  }
222
590
  this.closeAIPanel();
223
591
  }
224
592
  async regenerateResponse() {
225
- this.actionMenuEl.style.display = "none";
226
593
  await this.queryAI(this.inputValue);
227
594
  }
228
595
  closeAIPanel() {
229
596
  this.isBreak = true;
230
597
  if (this.dialogContainerEl) {
231
- this.quill.container.removeChild(this.dialogContainerEl);
598
+ this.dialogContainerEl.style.display = "none";
232
599
  }
233
- this.dialogContainerEl = null;
234
- this.actionMenuEl = null;
600
+ if (this.actionMenuEl) {
601
+ this.actionMenuEl.style.display = "none";
602
+ }
603
+ this.showResultPopupEl = false;
604
+ if (this.inputEl && this.inputEl.value.trim() !== "") {
605
+ this.inputEl.value = "";
606
+ }
607
+ this.hideSelectionBubble();
608
+ }
609
+ set charCount(value) {
610
+ if (this._debounceTimer) {
611
+ clearTimeout(this._debounceTimer);
612
+ }
613
+ this._debounceTimer = setTimeout(() => {
614
+ this._charCount = value;
615
+ if (this.resultPopupFooterTextEl) {
616
+ this.resultPopupFooterTextEl.textContent = `${this._charCount}/${this.textNumber}`;
617
+ }
618
+ clearTimeout(this._debounceTimer);
619
+ this._debounceTimer = null;
620
+ }, 210);
621
+ }
622
+ get charCount() {
623
+ return this._charCount;
624
+ }
625
+ set inputPlaceholder(value) {
626
+ this._inputPlaceholder = value;
627
+ if (this.inputEl) {
628
+ this.inputEl.placeholder = value;
629
+ }
630
+ }
631
+ get inputPlaceholder() {
632
+ return this._inputPlaceholder;
633
+ }
634
+ set showOperationMenu(value) {
635
+ this._showOperationMenu = value;
636
+ if (this.menuContainerEl) {
637
+ this.menuContainerEl.style.display = value ? "flex" : "none";
638
+ }
639
+ }
640
+ get showOperationMenu() {
641
+ return this._showOperationMenu;
642
+ }
643
+ set isSelectRangeMode(value) {
644
+ this._isSelectRangeMode = value;
645
+ this.showOperationMenu = value;
646
+ this.inputPlaceholder = value ? constants.SELECT_PLACEHOLDER : constants.INPUT_PLACEHOLDER;
647
+ this.hideSelectionBubble();
648
+ }
649
+ get isSelectRangeMode() {
650
+ return this._isSelectRangeMode;
651
+ }
652
+ set isThinking(value) {
653
+ this._isThinking = value;
654
+ this.switchInputEl(!value);
655
+ }
656
+ get isThinking() {
657
+ return this._isThinking;
658
+ }
659
+ set showResultPopupEl(value) {
660
+ this._showResultPopupEl = value;
661
+ if (this.resultPopupEl) {
662
+ this.resultPopupEl.style.display = value ? "block" : "none";
663
+ }
664
+ }
665
+ get showResultPopupEl() {
666
+ return this._showResultPopupEl;
235
667
  }
236
668
  }
237
669
  exports.AI = AI;