AI.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. import {AIBubbleMenuItem, BubbleMenuItem} from "../../types.ts";
  2. import {t} from "i18next";
  3. import {InnerEditor} from "../../../../core/LinwitDocEditor.ts";
  4. import {AiModelManager} from "../../../../ai/AiModelManager.ts";
  5. import {Svgs} from "../../../../commons/Svgs.ts";
  6. import {AiClient} from "../../../../ai/core/AiClient.ts";
  7. import tippy, {Instance} from "tippy.js";
  8. import {SmoothAppender} from "../../../../util/SmoothAppender.ts";
  9. import {getAIBoundingClientRect} from "../../../../util/getAIBoundingClientRect.ts";
  10. import { TextSelection } from "@tiptap/pm/state";
  11. type Holder = {
  12. editor?: InnerEditor,
  13. aiPanelInstance?: Instance,
  14. tippyInstance?: Instance,
  15. aiClient?: AiClient
  16. }
  17. export const defaultAiPanelMenus = [
  18. {
  19. prompt: `<content>{content}</content>\n请帮我优化一下这段内容,并直接返回优化后的结果。\n注意:你应该先判断一下这句话是中文还是英文,如果是中文,请给我返回中文的内容,如果是英文,请给我返回英文内容,只需要返回内容即可,不需要告知我是中文还是英文。`,
  20. icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M15.1986 9.94447C14.7649 9.5337 14.4859 8.98613 14.4085 8.39384L14.0056 5.31138L11.275 6.79724C10.7503 7.08274 10.1433 7.17888 9.55608 7.06948L6.49998 6.50015L7.06931 9.55625C7.17871 10.1435 7.08257 10.7505 6.79707 11.2751L5.31121 14.0057L8.39367 14.4086C8.98596 14.4861 9.53353 14.7651 9.94431 15.1987L12.0821 17.4557L13.4178 14.6486C13.6745 14.1092 14.109 13.6747 14.6484 13.418L17.4555 12.0823L15.1986 9.94447ZM15.2238 15.5079L13.0111 20.1581C12.8687 20.4573 12.5107 20.5844 12.2115 20.442C12.1448 20.4103 12.0845 20.3665 12.0337 20.3129L8.49229 16.5741C8.39749 16.474 8.27113 16.4096 8.13445 16.3918L3.02816 15.7243C2.69958 15.6814 2.46804 15.3802 2.51099 15.0516C2.52056 14.9784 2.54359 14.9075 2.5789 14.8426L5.04031 10.3192C5.1062 10.1981 5.12839 10.058 5.10314 9.92253L4.16 4.85991C4.09931 4.53414 4.3142 4.22086 4.63997 4.16017C4.7126 4.14664 4.78711 4.14664 4.85974 4.16017L9.92237 5.10331C10.0579 5.12855 10.198 5.10637 10.319 5.04048L14.8424 2.57907C15.1335 2.42068 15.4979 2.52825 15.6562 2.81931C15.6916 2.88421 15.7146 2.95507 15.7241 3.02833L16.3916 8.13462C16.4095 8.2713 16.4739 8.39766 16.5739 8.49245L20.3127 12.0338C20.5533 12.2617 20.5636 12.6415 20.3357 12.8821C20.2849 12.9357 20.2246 12.9795 20.1579 13.0112L15.5078 15.224C15.3833 15.2832 15.283 15.3835 15.2238 15.5079ZM16.0206 17.435L17.4348 16.0208L21.6775 20.2634L20.2633 21.6776L16.0206 17.435Z"></path></svg>`,
  21. title: 'improve-writing',
  22. },
  23. {
  24. prompt: `<content>{content}</content>\n请帮我检查一下这段内容,是否有拼写错误或者语法上的错误。\n注意:你应该先判断一下这句话是中文还是英文,如果是中文,请给我返回中文的内容,如果是英文,请给我返回英文内容,只需要返回内容即可,不需要告知我是中文还是英文。`,
  25. icon: ` <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 19C12.8284 19 13.5 19.6716 13.5 20.5C13.5 21.3284 12.8284 22 12 22C11.1716 22 10.5 21.3284 10.5 20.5C10.5 19.6716 11.1716 19 12 19ZM6.5 19C7.32843 19 8 19.6716 8 20.5C8 21.3284 7.32843 22 6.5 22C5.67157 22 5 21.3284 5 20.5C5 19.6716 5.67157 19 6.5 19ZM17.5 19C18.3284 19 19 19.6716 19 20.5C19 21.3284 18.3284 22 17.5 22C16.6716 22 16 21.3284 16 20.5C16 19.6716 16.6716 19 17.5 19ZM13 2V4H19V6L17.0322 6.0006C16.2423 8.3666 14.9984 10.5065 13.4107 12.302C14.9544 13.6737 16.7616 14.7204 18.7379 15.3443L18.2017 17.2736C15.8917 16.5557 13.787 15.3326 12.0005 13.7257C10.214 15.332 8.10914 16.5553 5.79891 17.2734L5.26257 15.3442C7.2385 14.7203 9.04543 13.6737 10.5904 12.3021C9.46307 11.0285 8.50916 9.58052 7.76789 8.00128L10.0074 8.00137C10.5706 9.03952 11.2401 10.0037 11.9998 10.8772C13.2283 9.46508 14.2205 7.81616 14.9095 6.00101L5 6V4H11V2H13Z"></path></svg>`,
  26. title: 'check-spelling-and-grammar',
  27. },
  28. {
  29. prompt: `<content>{content}</content>\n这句话的内容较长,帮我简化一下这个内容,并直接返回简化后的内容结果。\n注意:你应该先判断一下这句话是中文还是英文,如果是中文,请给我返回中文的内容,如果是英文,请给我返回英文内容,只需要返回内容即可,不需要告知我是中文还是英文。`,
  30. icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M21 6.75736L19 8.75736V4H10V9H5V20H19V17.2426L21 15.2426V21.0082C21 21.556 20.5551 22 20.0066 22H3.9934C3.44476 22 3 21.5501 3 20.9932V8L9.00319 2H19.9978C20.5513 2 21 2.45531 21 2.9918V6.75736ZM21.7782 8.80761L23.1924 10.2218L15.4142 18L13.9979 17.9979L14 16.5858L21.7782 8.80761Z"></path></svg>`,
  31. title: 'make-shorter',
  32. },
  33. {
  34. prompt: `<content>{content}</content>\n这句话的内容较简短,帮我简单的优化和丰富一下内容,并直接返回优化后的结果。注意:优化的内容不能超过原来内容的 2 倍。\n注意:你应该先判断一下这句话是中文还是英文,如果是中文,请给我返回中文的内容,如果是英文,请给我返回英文内容,只需要返回内容即可,不需要告知我是中文还是英文。`,
  35. icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M20 2C20.5523 2 21 2.44772 21 3V6.757L19 8.757V4H5V20H19V17.242L21 15.242V21C21 21.5523 20.5523 22 20 22H4C3.44772 22 3 21.5523 3 21V3C3 2.44772 3.44772 2 4 2H20ZM21.7782 8.80761L23.1924 10.2218L15.4142 18L13.9979 17.9979L14 16.5858L21.7782 8.80761ZM13 12V14H8V12H13ZM16 8V10H8V8H16Z"></path></svg>`,
  36. title: 'make-longer',
  37. },
  38. '<hr/>',
  39. {
  40. prompt: `<content>{content}</content>\n请帮我翻译以上内容,在翻译之前,想先判断一下这个内容是不是中文,如果是中文,则翻译为英文,如果是其他语言,则需要翻译为中文,注意,你只需要返回翻译的结果,不需要对此进行任何解释,不需要除了翻译结果以外的其他任何内容。`,
  41. icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M5 15V17C5 18.0544 5.81588 18.9182 6.85074 18.9945L7 19H10V21H7C4.79086 21 3 19.2091 3 17V15H5ZM18 10L22.4 21H20.245L19.044 18H14.954L13.755 21H11.601L16 10H18ZM17 12.8852L15.753 16H18.245L17 12.8852ZM8 2V4H12V11H8V14H6V11H2V4H6V2H8ZM17 3C19.2091 3 21 4.79086 21 7V9H19V7C19 5.89543 18.1046 5 17 5H14V3H17ZM6 6H4V9H6V6ZM10 6H8V9H10V6Z"></path></svg>`,
  42. title: 'translate',
  43. },
  44. {
  45. prompt: `<content>{content}</content>\n请帮我总结以上内容,并直接返回总结的结果\n注意:你应该先判断一下这句话是中文还是英文,如果是中文,请给我返回中文的内容,如果是英文,请给我返回英文内容,只需要返回内容即可,不需要告知我是中文还是英文。`,
  46. icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M18 3C19.6569 3 21 4.34315 21 6C21 7.65685 19.6569 9 18 9H15C13.6941 9 12.5831 8.16562 12.171 7.0009L11 7C9.9 7 9 7.9 9 9L9.0009 9.17102C10.1656 9.58312 11 10.6941 11 12C11 13.3059 10.1656 14.4169 9.0009 14.829L9 15C9 16.1 9.9 17 11 17L12.1707 17.0001C12.5825 15.8349 13.6937 15 15 15H18C19.6569 15 21 16.3431 21 18C21 19.6569 19.6569 21 18 21H15C13.6941 21 12.5831 20.1656 12.171 19.0009L11 19C8.79 19 7 17.21 7 15H5C3.34315 15 2 13.6569 2 12C2 10.3431 3.34315 9 5 9H7C7 6.79086 8.79086 5 11 5L12.1707 5.00009C12.5825 3.83485 13.6937 3 15 3H18ZM18 17H15C14.4477 17 14 17.4477 14 18C14 18.5523 14.4477 19 15 19H18C18.5523 19 19 18.5523 19 18C19 17.4477 18.5523 17 18 17ZM8 11H5C4.44772 11 4 11.4477 4 12C4 12.5523 4.44772 13 5 13H8C8.55228 13 9 12.5523 9 12C9 11.4477 8.55228 11 8 11ZM18 5H15C14.4477 5 14 5.44772 14 6C14 6.55228 14.4477 7 15 7H18C18.5523 7 19 6.55228 19 6C19 5.44772 18.5523 5 18 5Z"></path></svg>`,
  47. title: 'summarize',
  48. },
  49. ] as AIBubbleMenuItem[]
  50. const startChat = (holder: Holder, container: HTMLDivElement, prompt: string) => {
  51. console.log("startChat",holder)
  52. if (holder.aiClient) {
  53. holder.aiClient.stop();
  54. } else {
  55. const textarea = container.querySelector("textarea")!;
  56. textarea.value = "";
  57. const {selection, doc} = holder.editor!.state
  58. const selectedText = doc.textBetween(selection.from, selection.to);
  59. const options = holder.editor!.linwitDocEditor.options;
  60. const aiModel = AiModelManager.get(options.ai?.bubblePanelModel!);
  61. if (aiModel) {
  62. const smoothAppender = new SmoothAppender(30, textarea)
  63. aiModel.chat(selectedText, prompt, {
  64. onStart(aiClient) {
  65. holder.aiClient = aiClient;
  66. container.querySelector<HTMLElement>(".linwiteditor-ai-panel-actions")!.style.display = "none";
  67. container.querySelector<HTMLElement>(".loader")!.style.display = "block";
  68. container.querySelector<HTMLElement>(".linwiteditor-ai-panel-body-content")!.style.display = "block";
  69. container.querySelector("#go")!.innerHTML = Svgs.aiPanelStop;
  70. },
  71. onStop() {
  72. holder.aiClient = undefined;
  73. container.querySelector("#go")!.innerHTML = Svgs.aiPanelStart;
  74. container.querySelector<HTMLElement>(".linwiteditor-ai-panel-footer")!.style.display = "block";
  75. container.querySelector<HTMLElement>(".loader")!.style.display = "none";
  76. container.querySelector<HTMLElement>(".linwiteditor-ai-panel-actions")!.style.display = "none";
  77. smoothAppender.finished();
  78. },
  79. onMessage(message) {
  80. smoothAppender.appendText(message.content);
  81. }
  82. })
  83. } else {
  84. console.error("AI model name config error. can not get AI model by name: \"" + options.ai?.bubblePanelModel + "\", please check config \"options.ai.bubblePanelModel\"")
  85. }
  86. }
  87. }
  88. const createAiPanelElement = (holder: Holder, aiBubbleMenuItems: AIBubbleMenuItem[]) => {
  89. const container = document.createElement("div");
  90. container.classList.add("linwiteditor-ai-panel")
  91. container.innerHTML = `
  92. <div class="linwiteditor-ai-panel-body">
  93. <div class="linwiteditor-ai-panel-body-content" style="display: none"><div class="loader">${Svgs.refresh}</div><textarea readonly></textarea></div>
  94. <div class="linwiteditor-ai-panel-body-input"><input id="prompt" placeholder="${t('placeholder-tell-ai-what-to-do-next')}" type="text" />
  95. <button type="button" id="go" style="width: 30px;height: 30px">${Svgs.aiPanelStart}</button></div>
  96. <div class="linwiteditor-ai-panel-body-tips"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9 17C9 17 16 18 19 21H20C20.5523 21 21 20.5523 21 20V13.937C21.8626 13.715 22.5 12.9319 22.5 12C22.5 11.0681 21.8626 10.285 21 10.063V4C21 3.44772 20.5523 3 20 3H19C16 6 9 7 9 7H5C3.89543 7 3 7.89543 3 9V15C3 16.1046 3.89543 17 5 17H6L7 22H9V17ZM11 8.6612C11.6833 8.5146 12.5275 8.31193 13.4393 8.04373C15.1175 7.55014 17.25 6.77262 19 5.57458V18.4254C17.25 17.2274 15.1175 16.4499 13.4393 15.9563C12.5275 15.6881 11.6833 15.4854 11 15.3388V8.6612ZM5 9H9V15H5V9Z" fill="currentColor"></path></svg>
  97. ${t("tip-you-can-enter-text-above-or-select-the-operation-below")}</div>
  98. </div>
  99. <div class="linwiteditor-ai-panel-footer" style="display: none">
  100. <div class="linwiteditor-ai-panel-footer-tips">${t("you-can-do-the-following")}</div>
  101. <p id="insert"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M2 18H12V20H2V18ZM2 11H22V13H2V11ZM2 4H22V6H2V4ZM18 18V15H20V18H23V20H20V23H18V20H15V18H18Z" fill="currentColor"></path></svg>
  102. ${t("ai-append")}</p>
  103. <p id="replace"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10.071 4.92902L11.4852 6.34323L6.82834 11.0001L16.0002 11.0002L16.0002 13.0002L6.82839 13.0001L11.4852 17.6569L10.071 19.0712L2.99994 12.0001L10.071 4.92902ZM18.0001 19V5.00003H20.0001V19H18.0001Z" fill="currentColor"></path></svg>
  104. ${t("ai-replace")}</p>
  105. <hr/>
  106. <p id="hide"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17 6H22V8H20V21C20 21.5523 19.5523 22 19 22H5C4.44772 22 4 21.5523 4 21V8H2V6H7V3C7 2.44772 7.44772 2 8 2H16C16.5523 2 17 2.44772 17 3V6ZM18 8H6V20H18V8ZM9 4V6H15V4H9Z" fill="currentColor"></path></svg>
  107. ${t("ai-cancel")}</p>
  108. </div>
  109. <!--linwiteditor-ai-panel-actions-->
  110. <div class="linwiteditor-ai-panel-footer linwiteditor-ai-panel-actions" >
  111. <div class="linwiteditor-ai-panel-footer-tips">${t("you-can-do-the-following")}</div>
  112. ${aiBubbleMenuItems.map((menuItem) => {
  113. return typeof menuItem === "string" ? menuItem :
  114. `<p data-prompt="${menuItem.prompt}">${menuItem.icon} ${t(menuItem.title)} </p>`;
  115. }).join('')}
  116. </div>
  117. `;
  118. // container.innerHTML = `
  119. // <div class="aie-ai-panel-body">
  120. // <div class="aie-ai-panel-body-content" style="display: none"><div class="loader">${Svgs.refresh}</div><textarea readonly></textarea></div>
  121. // <div class="aie-ai-panel-body-input"><input id="prompt" placeholder="${t('placeholder-tell-ai-what-to-do-next')}" type="text" />
  122. // <button type="button" id="go" style="width: 30px;height: 30px">${Svgs.aiPanelStart}</button></div>
  123. // <div class="aie-ai-panel-body-tips"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9 17C9 17 16 18 19 21H20C20.5523 21 21 20.5523 21 20V13.937C21.8626 13.715 22.5 12.9319 22.5 12C22.5 11.0681 21.8626 10.285 21 10.063V4C21 3.44772 20.5523 3 20 3H19C16 6 9 7 9 7H5C3.89543 7 3 7.89543 3 9V15C3 16.1046 3.895..." fill="currentColor"></path></svg>
  124. // ${t("tip-you-can-enter-text-above-or-select-the-operation-below")}</div>
  125. // </div>
  126. // <div class="aie-ai-panel-footer" style="display: none">
  127. // <div class="aie-ai-panel-footer-tips">${t("you-can-do-the-following")}</div>
  128. // <p id="insert"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M2 18H12V20H2V18ZM2 11H22V13H2V11ZM2 4H22V6H2V4ZM18 18V15H20V18H23V20H20V23H18V20H15V18H18Z" fill="currentColor"></path></svg>
  129. // ${t("ai-append")}</p>
  130. // <p id="replace"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10.071 4.92902L11.4852 6.34323L6.82834 11.0001L16.0002 11.0002L16.0002 13.0002L6.82839 13.0001L11.4852 17.6569L10.071 19.0712L2.99994 12.0001L10.071 4.92902ZM18.0001 19V5.00003H20.0001V19H18.0001Z" fill="currentColor"></path></svg>
  131. // ${t("ai-replace")}</p>
  132. // <hr/>
  133. // <p id="hide"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17 6H22V8H20V21C20 21.5523 19.5523 22 19 22H5C4.44772 22 4 21.5523 4 21V8H2V6H7V3C7 2.44772 7.44772 2 8 2H16C16.5523 2 17 2.44772 17 3V6ZM18 8H6V20H18V8ZM9 4V6H15V4H9Z" fill="currentColor"></path></svg>
  134. // ${t("ai-cancel")}</p>
  135. // </div>
  136. // <!-- aie-ai-panel-actions -->
  137. // <div class="aie-ai-panel-footer aie-ai-panel-actions">
  138. // <div class="aie-ai-panel-footer-tips">${t("you-can-do-the-following")}</div>
  139. // ${aiBubbleMenuItems.map((menuItem) => {
  140. // if (menuItem.children) { // 判断是否有 children
  141. // return `
  142. // <div class="menu-item-with-children">
  143. // <p>${menuItem.icon} ${t(menuItem.title)}</p>
  144. // <div class="submenu">
  145. // ${menuItem.children.map(child => `
  146. // <p data-prompt="${child.prompt}">${child.icon} ${t(child.title)}</p>
  147. // `).join('')}
  148. // </div>
  149. // </div>
  150. // `;
  151. // } else {
  152. // return `
  153. // <p data-prompt="${menuItem.prompt}">${menuItem.icon} ${t(menuItem.title)}</p>
  154. // `;
  155. // }
  156. // }).join('')}
  157. // </div>
  158. // `;
  159. container.querySelector("#replace")!.addEventListener("click", () => {
  160. const textarea = container.querySelector("textarea")!;
  161. if (textarea.value) {
  162. holder.editor?.insertMarkdown(textarea.value)
  163. holder.aiPanelInstance?.hide();
  164. holder.tippyInstance?.show();
  165. }
  166. });
  167. container.querySelector("#insert")!.addEventListener("click", () => {
  168. const textarea = container.querySelector("textarea")!;
  169. if (textarea.value) {
  170. const {state: {selection, doc}} = holder.editor!
  171. holder.editor?.commands.setTextSelection(TextSelection.create(doc, selection.to));
  172. holder.editor?.insertMarkdown(textarea.value)
  173. holder.aiPanelInstance?.hide();
  174. if (selection.from != selection.to) {
  175. holder.tippyInstance?.show()
  176. }
  177. }
  178. });
  179. container.querySelector("#hide")!.addEventListener("click", () => {
  180. holder.aiPanelInstance?.hide()
  181. const {state: {selection}} = holder.editor!
  182. if (selection.from != selection.to) {
  183. holder.tippyInstance?.show()
  184. }
  185. });
  186. container.querySelector("#go")!.addEventListener("click", () => {
  187. const prompt = (container.querySelector("#prompt") as HTMLInputElement).value
  188. startChat(holder, container, prompt);
  189. });
  190. container.querySelectorAll(".linwiteditor-ai-panel-actions p").forEach((element) => {
  191. const prompt = element.getAttribute("data-prompt")!;
  192. element.addEventListener("click", () => {
  193. startChat(holder, container, prompt);
  194. })
  195. })
  196. container.addEventListener("click", e => {
  197. if (e.target === container) {
  198. holder.aiPanelInstance?.hide();
  199. holder.tippyInstance?.show();
  200. }
  201. })
  202. return container;
  203. }
  204. export const AI = {
  205. id: "ai",
  206. title: "ai",
  207. icon: `<div style="display: flex;height: 20px">
  208. <div style="width: 18px;height: 18px;display: inline-block" >
  209. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="rgba(100,205,138,1)"><path d="M13.5 2C13.5 2.44425 13.3069 2.84339 13 3.11805V5H18C19.6569 5 21 6.34315 21 8V18C21 19.6569 19.6569 21 18 21H6C4.34315 21 3 19.6569 3 18V8C3 6.34315 4.34315 5 6 5H11V3.11805C10.6931 2.84339 10.5 2.44425 10.5 2C10.5 1.17157 11.1716 0.5 12 0.5C12.8284 0.5 13.5 1.17157 13.5 2ZM6 7C5.44772 7 5 7.44772 5 8V18C5 18.5523 5.44772 19 6 19H18C18.5523 19 19 18.5523 19 18V8C19 7.44772 18.5523 7 18 7H13H11H6ZM2 10H0V16H2V10ZM22 10H24V16H22V10ZM9 14.5C9.82843 14.5 10.5 13.8284 10.5 13C10.5 12.1716 9.82843 11.5 9 11.5C8.17157 11.5 7.5 12.1716 7.5 13C7.5 13.8284 8.17157 14.5 9 14.5ZM15 14.5C15.8284 14.5 16.5 13.8284 16.5 13C16.5 12.1716 15.8284 11.5 15 11.5C14.1716 11.5 13.5 12.1716 13.5 13C13.5 13.8284 14.1716 14.5 15 14.5Z"></path></svg>
  210. </div>
  211. <div style="width: 18px;height: 18px;display: inline-block" >
  212. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"></path><path d="M12 14L8 10H16L12 14Z"></path></svg>
  213. </div>
  214. </div>
  215. `,
  216. onInit: ({innerEditor}, tippyInstance, parentEle) => {
  217. const holder: Holder = {editor: innerEditor, tippyInstance};
  218. const aiPanelMenus = innerEditor.linwitDocEditor.options.ai?.bubblePanelMenus || defaultAiPanelMenus;
  219. holder.aiPanelInstance = tippy(parentEle.querySelector("#ai")!, {
  220. content: createAiPanelElement(holder, aiPanelMenus),
  221. appendTo: innerEditor!.view.dom.closest(".linwiteditor-container")!,
  222. trigger: 'click',
  223. interactive: true,
  224. arrow: false,
  225. placement: 'bottom',
  226. getReferenceClientRect: () => getAIBoundingClientRect(innerEditor),
  227. onShow: (_) => {
  228. window.setTimeout(() => _.popper.querySelector<HTMLInputElement>("#prompt")?.focus(), 0);
  229. }
  230. })
  231. return holder;
  232. },
  233. onClick: (____, _, __, holder: Holder) => {
  234. holder.tippyInstance?.hide()
  235. }
  236. } as BubbleMenuItem