@docyrus/ui-pro-ai-assistant 0.0.1

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 (308) hide show
  1. package/dist/index.js +26161 -0
  2. package/dist/index.js.map +1 -0
  3. package/package.json +155 -0
  4. package/src/components/assistant-animations.tsx +29 -0
  5. package/src/components/assistant-dialogs.tsx +235 -0
  6. package/src/components/code-view.tsx +278 -0
  7. package/src/components/create-agent-task.tsx +104 -0
  8. package/src/components/create-new-work-version.tsx +30 -0
  9. package/src/components/extract-web.tsx +160 -0
  10. package/src/components/forward-to-agent.tsx +90 -0
  11. package/src/components/generate-chart.tsx +101 -0
  12. package/src/components/generative-action-button.tsx +122 -0
  13. package/src/components/generative-tool.tsx +685 -0
  14. package/src/components/generative-ui-object.tsx +210 -0
  15. package/src/components/input-area.tsx +1209 -0
  16. package/src/components/json-schema-layout.tsx +326 -0
  17. package/src/components/list-item-card.tsx +92 -0
  18. package/src/components/mermaid-diagram.tsx +192 -0
  19. package/src/components/preview-image.tsx +47 -0
  20. package/src/components/request-approval.tsx +48 -0
  21. package/src/components/request-user-input.tsx +270 -0
  22. package/src/components/search-web.tsx +319 -0
  23. package/src/components/sheet-command.tsx +88 -0
  24. package/src/components/shell-canvas.tsx +122 -0
  25. package/src/components/show-advanced-data-table.tsx +352 -0
  26. package/src/components/show-generated-content-options.tsx +93 -0
  27. package/src/components/show-people-cards.tsx +180 -0
  28. package/src/components/subagent-tool.tsx +180 -0
  29. package/src/components/text-editor-tool.tsx +328 -0
  30. package/src/components/work-canvas.tsx +88 -0
  31. package/src/components/work-card.tsx +42 -0
  32. package/src/declarations.d.ts +1 -0
  33. package/src/docy-assistant.tsx +1962 -0
  34. package/src/hooks/index.ts +7 -0
  35. package/src/hooks/use-assistant-api.ts +507 -0
  36. package/src/hooks/use-deployment-data.ts +162 -0
  37. package/src/hooks/use-project-state.ts +347 -0
  38. package/src/hooks/use-session-state.ts +207 -0
  39. package/src/hooks/use-speech-recognition.ts +137 -0
  40. package/src/hooks/use-ui-state.ts +185 -0
  41. package/src/hooks/use-works-state.ts +146 -0
  42. package/src/i18n/context.tsx +253 -0
  43. package/src/i18n/index.ts +19 -0
  44. package/src/i18n/locales/de.json +198 -0
  45. package/src/i18n/locales/el.json +198 -0
  46. package/src/i18n/locales/en.json +226 -0
  47. package/src/i18n/locales/es.json +198 -0
  48. package/src/i18n/locales/fr.json +198 -0
  49. package/src/i18n/locales/it.json +198 -0
  50. package/src/i18n/locales/pt.json +198 -0
  51. package/src/i18n/locales/sl.json +198 -0
  52. package/src/i18n/locales/tr.json +211 -0
  53. package/src/i18n/types.ts +23 -0
  54. package/src/i18n/use-translation.ts +17 -0
  55. package/src/index.ts +18 -0
  56. package/src/internal/plate-editor/editor/auth-context.ts +11 -0
  57. package/src/internal/plate-editor/editor/editor-base-kit.tsx +39 -0
  58. package/src/internal/plate-editor/editor/editor-kit.tsx +89 -0
  59. package/src/internal/plate-editor/editor/plate-editor.tsx +75 -0
  60. package/src/internal/plate-editor/editor/plate-types.ts +126 -0
  61. package/src/internal/plate-editor/editor/plugins/ai-kit.tsx +172 -0
  62. package/src/internal/plate-editor/editor/plugins/autoformat-kit.tsx +211 -0
  63. package/src/internal/plate-editor/editor/plugins/basic-blocks-base-kit.tsx +26 -0
  64. package/src/internal/plate-editor/editor/plugins/basic-blocks-kit.tsx +51 -0
  65. package/src/internal/plate-editor/editor/plugins/basic-marks-base-kit.tsx +24 -0
  66. package/src/internal/plate-editor/editor/plugins/basic-marks-kit.tsx +38 -0
  67. package/src/internal/plate-editor/editor/plugins/basic-nodes-kit.tsx +6 -0
  68. package/src/internal/plate-editor/editor/plugins/block-menu-kit.tsx +14 -0
  69. package/src/internal/plate-editor/editor/plugins/block-placeholder-kit.tsx +17 -0
  70. package/src/internal/plate-editor/editor/plugins/block-selection-kit.tsx +31 -0
  71. package/src/internal/plate-editor/editor/plugins/callout-base-kit.tsx +5 -0
  72. package/src/internal/plate-editor/editor/plugins/callout-kit.tsx +7 -0
  73. package/src/internal/plate-editor/editor/plugins/code-block-base-kit.tsx +23 -0
  74. package/src/internal/plate-editor/editor/plugins/code-block-kit.tsx +26 -0
  75. package/src/internal/plate-editor/editor/plugins/column-base-kit.tsx +8 -0
  76. package/src/internal/plate-editor/editor/plugins/column-kit.tsx +7 -0
  77. package/src/internal/plate-editor/editor/plugins/comment-base-kit.tsx +5 -0
  78. package/src/internal/plate-editor/editor/plugins/comment-kit.tsx +174 -0
  79. package/src/internal/plate-editor/editor/plugins/copilot-kit.tsx +68 -0
  80. package/src/internal/plate-editor/editor/plugins/cursor-overlay-kit.tsx +13 -0
  81. package/src/internal/plate-editor/editor/plugins/date-base-kit.tsx +5 -0
  82. package/src/internal/plate-editor/editor/plugins/date-kit.tsx +7 -0
  83. package/src/internal/plate-editor/editor/plugins/discussion-kit.tsx +36 -0
  84. package/src/internal/plate-editor/editor/plugins/dnd-kit.tsx +27 -0
  85. package/src/internal/plate-editor/editor/plugins/docx-export-kit.tsx +43 -0
  86. package/src/internal/plate-editor/editor/plugins/docx-kit.tsx +6 -0
  87. package/src/internal/plate-editor/editor/plugins/emoji-kit.tsx +15 -0
  88. package/src/internal/plate-editor/editor/plugins/exit-break-kit.tsx +12 -0
  89. package/src/internal/plate-editor/editor/plugins/floating-toolbar-kit.tsx +19 -0
  90. package/src/internal/plate-editor/editor/plugins/font-base-kit.tsx +36 -0
  91. package/src/internal/plate-editor/editor/plugins/font-kit.tsx +47 -0
  92. package/src/internal/plate-editor/editor/plugins/indent-base-kit.tsx +19 -0
  93. package/src/internal/plate-editor/editor/plugins/indent-kit.tsx +22 -0
  94. package/src/internal/plate-editor/editor/plugins/link-base-kit.tsx +5 -0
  95. package/src/internal/plate-editor/editor/plugins/link-kit.tsx +35 -0
  96. package/src/internal/plate-editor/editor/plugins/list-base-kit.tsx +24 -0
  97. package/src/internal/plate-editor/editor/plugins/list-kit.tsx +27 -0
  98. package/src/internal/plate-editor/editor/plugins/markdown-kit.tsx +18 -0
  99. package/src/internal/plate-editor/editor/plugins/math-base-kit.tsx +8 -0
  100. package/src/internal/plate-editor/editor/plugins/math-kit.tsx +10 -0
  101. package/src/internal/plate-editor/editor/plugins/media-base-kit.tsx +37 -0
  102. package/src/internal/plate-editor/editor/plugins/media-kit.tsx +53 -0
  103. package/src/internal/plate-editor/editor/plugins/mention-base-kit.tsx +5 -0
  104. package/src/internal/plate-editor/editor/plugins/mention-kit.tsx +36 -0
  105. package/src/internal/plate-editor/editor/plugins/slash-kit.tsx +17 -0
  106. package/src/internal/plate-editor/editor/plugins/suggestion-base-kit.tsx +5 -0
  107. package/src/internal/plate-editor/editor/plugins/suggestion-kit.tsx +95 -0
  108. package/src/internal/plate-editor/editor/plugins/table-base-kit.tsx +20 -0
  109. package/src/internal/plate-editor/editor/plugins/table-kit.tsx +22 -0
  110. package/src/internal/plate-editor/editor/plugins/toc-base-kit.tsx +5 -0
  111. package/src/internal/plate-editor/editor/plugins/toc-kit.tsx +14 -0
  112. package/src/internal/plate-editor/editor/plugins/toggle-base-kit.tsx +5 -0
  113. package/src/internal/plate-editor/editor/plugins/toggle-kit.tsx +9 -0
  114. package/src/internal/plate-editor/editor/transforms.ts +165 -0
  115. package/src/internal/plate-editor/editor/use-chat.ts +152 -0
  116. package/src/internal/plate-editor/hooks/index.ts +3 -0
  117. package/src/internal/plate-editor/hooks/use-copy-to-clipboard.ts +31 -0
  118. package/src/internal/plate-editor/hooks/use-is-touch-device.ts +26 -0
  119. package/src/internal/plate-editor/hooks/use-lock-scroll.ts +21 -0
  120. package/src/internal/plate-editor/hooks/use-media-query.ts +44 -0
  121. package/src/internal/plate-editor/hooks/use-mounted.ts +18 -0
  122. package/src/internal/plate-editor/hooks/use-on-click-outside.ts +114 -0
  123. package/src/internal/plate-editor/hooks/use-upload-file.ts +81 -0
  124. package/src/internal/plate-editor/i18n/context.tsx +58 -0
  125. package/src/internal/plate-editor/i18n/index.ts +3 -0
  126. package/src/internal/plate-editor/i18n/locales/de.json +57 -0
  127. package/src/internal/plate-editor/i18n/locales/el.json +57 -0
  128. package/src/internal/plate-editor/i18n/locales/en.json +57 -0
  129. package/src/internal/plate-editor/i18n/locales/es.json +57 -0
  130. package/src/internal/plate-editor/i18n/locales/fr.json +57 -0
  131. package/src/internal/plate-editor/i18n/locales/it.json +57 -0
  132. package/src/internal/plate-editor/i18n/locales/pt.json +57 -0
  133. package/src/internal/plate-editor/i18n/locales/sl.json +57 -0
  134. package/src/internal/plate-editor/i18n/locales/tr.json +57 -0
  135. package/src/internal/plate-editor/i18n/types.ts +59 -0
  136. package/src/internal/plate-editor/i18n/use-translation.ts +22 -0
  137. package/src/internal/plate-editor/index.ts +39 -0
  138. package/src/internal/plate-editor/lib/ai-output-converter.ts +153 -0
  139. package/src/internal/plate-editor/lib/download-file.ts +17 -0
  140. package/src/internal/plate-editor/plate-ui/ai-chat-editor.tsx +26 -0
  141. package/src/internal/plate-editor/plate-ui/ai-menu.tsx +828 -0
  142. package/src/internal/plate-editor/plate-ui/ai-node.tsx +41 -0
  143. package/src/internal/plate-editor/plate-ui/ai-toolbar-button.tsx +27 -0
  144. package/src/internal/plate-editor/plate-ui/alert-dialog.tsx +147 -0
  145. package/src/internal/plate-editor/plate-ui/align-toolbar-button.tsx +90 -0
  146. package/src/internal/plate-editor/plate-ui/avatar.tsx +3 -0
  147. package/src/internal/plate-editor/plate-ui/block-context-menu.tsx +106 -0
  148. package/src/internal/plate-editor/plate-ui/block-discussion.tsx +364 -0
  149. package/src/internal/plate-editor/plate-ui/block-draggable.tsx +556 -0
  150. package/src/internal/plate-editor/plate-ui/block-list-static.tsx +78 -0
  151. package/src/internal/plate-editor/plate-ui/block-list.tsx +85 -0
  152. package/src/internal/plate-editor/plate-ui/block-menu.tsx +557 -0
  153. package/src/internal/plate-editor/plate-ui/block-selection.tsx +47 -0
  154. package/src/internal/plate-editor/plate-ui/block-suggestion.tsx +469 -0
  155. package/src/internal/plate-editor/plate-ui/blockquote-node-static.tsx +10 -0
  156. package/src/internal/plate-editor/plate-ui/blockquote-node.tsx +11 -0
  157. package/src/internal/plate-editor/plate-ui/button.tsx +190 -0
  158. package/src/internal/plate-editor/plate-ui/calendar.tsx +3 -0
  159. package/src/internal/plate-editor/plate-ui/callout-node-static.tsx +76 -0
  160. package/src/internal/plate-editor/plate-ui/callout-node.tsx +54 -0
  161. package/src/internal/plate-editor/plate-ui/caption.tsx +48 -0
  162. package/src/internal/plate-editor/plate-ui/checkbox.tsx +3 -0
  163. package/src/internal/plate-editor/plate-ui/code-block-node-static.tsx +172 -0
  164. package/src/internal/plate-editor/plate-ui/code-block-node.tsx +228 -0
  165. package/src/internal/plate-editor/plate-ui/code-node-static.tsx +11 -0
  166. package/src/internal/plate-editor/plate-ui/code-node.tsx +12 -0
  167. package/src/internal/plate-editor/plate-ui/column-node-static.tsx +65 -0
  168. package/src/internal/plate-editor/plate-ui/column-node.tsx +24 -0
  169. package/src/internal/plate-editor/plate-ui/command.tsx +204 -0
  170. package/src/internal/plate-editor/plate-ui/comment-node-static.tsx +12 -0
  171. package/src/internal/plate-editor/plate-ui/comment-node.tsx +45 -0
  172. package/src/internal/plate-editor/plate-ui/comment-toolbar-button.tsx +24 -0
  173. package/src/internal/plate-editor/plate-ui/comment.tsx +619 -0
  174. package/src/internal/plate-editor/plate-ui/cursor-overlay.tsx +85 -0
  175. package/src/internal/plate-editor/plate-ui/date-node-static.tsx +43 -0
  176. package/src/internal/plate-editor/plate-ui/date-node.tsx +56 -0
  177. package/src/internal/plate-editor/plate-ui/dialog.tsx +426 -0
  178. package/src/internal/plate-editor/plate-ui/dropdown-menu.tsx +266 -0
  179. package/src/internal/plate-editor/plate-ui/editor-static.tsx +40 -0
  180. package/src/internal/plate-editor/plate-ui/editor.tsx +148 -0
  181. package/src/internal/plate-editor/plate-ui/emoji-node.tsx +48 -0
  182. package/src/internal/plate-editor/plate-ui/emoji-toolbar-button.tsx +626 -0
  183. package/src/internal/plate-editor/plate-ui/equation-node-static.tsx +155 -0
  184. package/src/internal/plate-editor/plate-ui/equation-node.tsx +226 -0
  185. package/src/internal/plate-editor/plate-ui/equation-toolbar-button.tsx +26 -0
  186. package/src/internal/plate-editor/plate-ui/export-toolbar-button.tsx +208 -0
  187. package/src/internal/plate-editor/plate-ui/fixed-toolbar-buttons.tsx +157 -0
  188. package/src/internal/plate-editor/plate-ui/fixed-toolbar.tsx +27 -0
  189. package/src/internal/plate-editor/plate-ui/floating-discussion.tsx +1129 -0
  190. package/src/internal/plate-editor/plate-ui/floating-toolbar-buttons.tsx +129 -0
  191. package/src/internal/plate-editor/plate-ui/floating-toolbar.tsx +99 -0
  192. package/src/internal/plate-editor/plate-ui/font-color-toolbar-button.tsx +211 -0
  193. package/src/internal/plate-editor/plate-ui/font-size-toolbar-button.tsx +154 -0
  194. package/src/internal/plate-editor/plate-ui/ghost-text.tsx +20 -0
  195. package/src/internal/plate-editor/plate-ui/heading-node-static.tsx +52 -0
  196. package/src/internal/plate-editor/plate-ui/heading-node.tsx +56 -0
  197. package/src/internal/plate-editor/plate-ui/highlight-node-static.tsx +9 -0
  198. package/src/internal/plate-editor/plate-ui/highlight-node.tsx +11 -0
  199. package/src/internal/plate-editor/plate-ui/history-toolbar-button.tsx +52 -0
  200. package/src/internal/plate-editor/plate-ui/hover-card.tsx +7 -0
  201. package/src/internal/plate-editor/plate-ui/hr-node-static.tsx +18 -0
  202. package/src/internal/plate-editor/plate-ui/hr-node.tsx +28 -0
  203. package/src/internal/plate-editor/plate-ui/import-toolbar-button.tsx +124 -0
  204. package/src/internal/plate-editor/plate-ui/indent-toolbar-button.tsx +34 -0
  205. package/src/internal/plate-editor/plate-ui/inline-combobox.tsx +409 -0
  206. package/src/internal/plate-editor/plate-ui/input.tsx +39 -0
  207. package/src/internal/plate-editor/plate-ui/insert-toolbar-button.tsx +260 -0
  208. package/src/internal/plate-editor/plate-ui/label.tsx +1 -0
  209. package/src/internal/plate-editor/plate-ui/line-height-toolbar-button.tsx +71 -0
  210. package/src/internal/plate-editor/plate-ui/link-node-static.tsx +15 -0
  211. package/src/internal/plate-editor/plate-ui/link-node.tsx +33 -0
  212. package/src/internal/plate-editor/plate-ui/link-toolbar-button.tsx +30 -0
  213. package/src/internal/plate-editor/plate-ui/link-toolbar.tsx +149 -0
  214. package/src/internal/plate-editor/plate-ui/list-toolbar-button.tsx +179 -0
  215. package/src/internal/plate-editor/plate-ui/mark-toolbar-button.tsx +36 -0
  216. package/src/internal/plate-editor/plate-ui/media-audio-node-static.tsx +21 -0
  217. package/src/internal/plate-editor/plate-ui/media-audio-node.tsx +32 -0
  218. package/src/internal/plate-editor/plate-ui/media-embed-node.tsx +103 -0
  219. package/src/internal/plate-editor/plate-ui/media-file-node-static.tsx +30 -0
  220. package/src/internal/plate-editor/plate-ui/media-file-node.tsx +52 -0
  221. package/src/internal/plate-editor/plate-ui/media-image-node-static.tsx +37 -0
  222. package/src/internal/plate-editor/plate-ui/media-image-node.tsx +183 -0
  223. package/src/internal/plate-editor/plate-ui/media-placeholder-node.tsx +441 -0
  224. package/src/internal/plate-editor/plate-ui/media-preview-dialog.tsx +127 -0
  225. package/src/internal/plate-editor/plate-ui/media-toolbar-button.tsx +229 -0
  226. package/src/internal/plate-editor/plate-ui/media-toolbar.tsx +216 -0
  227. package/src/internal/plate-editor/plate-ui/media-upload-toast.tsx +73 -0
  228. package/src/internal/plate-editor/plate-ui/media-video-node-static.tsx +35 -0
  229. package/src/internal/plate-editor/plate-ui/media-video-node.tsx +119 -0
  230. package/src/internal/plate-editor/plate-ui/mention-node-static.tsx +46 -0
  231. package/src/internal/plate-editor/plate-ui/mention-node.tsx +79 -0
  232. package/src/internal/plate-editor/plate-ui/menu.tsx +532 -0
  233. package/src/internal/plate-editor/plate-ui/mode-toolbar-button.tsx +126 -0
  234. package/src/internal/plate-editor/plate-ui/more-toolbar-button.tsx +34 -0
  235. package/src/internal/plate-editor/plate-ui/paragraph-node-static.tsx +15 -0
  236. package/src/internal/plate-editor/plate-ui/paragraph-node.tsx +16 -0
  237. package/src/internal/plate-editor/plate-ui/popover.tsx +77 -0
  238. package/src/internal/plate-editor/plate-ui/progress.tsx +1 -0
  239. package/src/internal/plate-editor/plate-ui/remote-cursor-overlay.tsx +81 -0
  240. package/src/internal/plate-editor/plate-ui/resize-handle.tsx +88 -0
  241. package/src/internal/plate-editor/plate-ui/separator.tsx +43 -0
  242. package/src/internal/plate-editor/plate-ui/slash-node.tsx +435 -0
  243. package/src/internal/plate-editor/plate-ui/spinner.tsx +1 -0
  244. package/src/internal/plate-editor/plate-ui/suggestion-node-static.tsx +35 -0
  245. package/src/internal/plate-editor/plate-ui/suggestion-node.tsx +168 -0
  246. package/src/internal/plate-editor/plate-ui/suggestion-toolbar-button.tsx +24 -0
  247. package/src/internal/plate-editor/plate-ui/table-node-static.tsx +85 -0
  248. package/src/internal/plate-editor/plate-ui/table-node.tsx +285 -0
  249. package/src/internal/plate-editor/plate-ui/table-toolbar-button.tsx +254 -0
  250. package/src/internal/plate-editor/plate-ui/tabs.tsx +3 -0
  251. package/src/internal/plate-editor/plate-ui/textarea.tsx +58 -0
  252. package/src/internal/plate-editor/plate-ui/toc-node-static.tsx +142 -0
  253. package/src/internal/plate-editor/plate-ui/toc-node.tsx +57 -0
  254. package/src/internal/plate-editor/plate-ui/toc-sidebar.tsx +50 -0
  255. package/src/internal/plate-editor/plate-ui/toggle-node-static.tsx +18 -0
  256. package/src/internal/plate-editor/plate-ui/toggle-node.tsx +33 -0
  257. package/src/internal/plate-editor/plate-ui/toggle-toolbar-button.tsx +26 -0
  258. package/src/internal/plate-editor/plate-ui/toggle.tsx +3 -0
  259. package/src/internal/plate-editor/plate-ui/toolbar.tsx +380 -0
  260. package/src/internal/plate-editor/plate-ui/tooltip.tsx +149 -0
  261. package/src/internal/plate-editor/plate-ui/turn-into-toolbar-button.tsx +177 -0
  262. package/src/internal/plate-editor/types/index.ts +22 -0
  263. package/src/internal/plate-editor/vite.ts +284 -0
  264. package/src/internal/sheets/components/univer-sheets.tsx +1104 -0
  265. package/src/internal/sheets/i18n/context.tsx +183 -0
  266. package/src/internal/sheets/i18n/index.ts +19 -0
  267. package/src/internal/sheets/i18n/locales/de.json +21 -0
  268. package/src/internal/sheets/i18n/locales/el.json +21 -0
  269. package/src/internal/sheets/i18n/locales/en.json +21 -0
  270. package/src/internal/sheets/i18n/locales/es.json +21 -0
  271. package/src/internal/sheets/i18n/locales/fr.json +21 -0
  272. package/src/internal/sheets/i18n/locales/it.json +21 -0
  273. package/src/internal/sheets/i18n/locales/pt.json +21 -0
  274. package/src/internal/sheets/i18n/locales/sl.json +21 -0
  275. package/src/internal/sheets/i18n/locales/tr.json +21 -0
  276. package/src/internal/sheets/i18n/types.ts +23 -0
  277. package/src/internal/sheets/i18n/use-translation.ts +17 -0
  278. package/src/internal/sheets/index.ts +14 -0
  279. package/src/internal/sheets/types/css.d.ts +11 -0
  280. package/src/internal/sheets/types/index.ts +260 -0
  281. package/src/internal/sheets/xlsx.ts +1169 -0
  282. package/src/lib/api-client.ts +77 -0
  283. package/src/lib/assistant-api-actions.ts +549 -0
  284. package/src/lib/assistant-config.tsx +71 -0
  285. package/src/lib/class-utils.ts +40 -0
  286. package/src/lib/index.ts +7 -0
  287. package/src/lib/message-utils.ts +131 -0
  288. package/src/tools/tools-schema.json +512 -0
  289. package/src/types/index.ts +235 -0
  290. package/src/views/assistant-view.tsx +1137 -0
  291. package/src/views/canvas-app.tsx +839 -0
  292. package/src/views/canvas-code.tsx +93 -0
  293. package/src/views/canvas-deep-research.tsx +44 -0
  294. package/src/views/canvas-image.tsx +25 -0
  295. package/src/views/canvas-record-view.tsx +285 -0
  296. package/src/views/canvas-spreadsheet.tsx +125 -0
  297. package/src/views/canvas-text.tsx +52 -0
  298. package/src/views/canvas.tsx +274 -0
  299. package/src/views/chat-panel.tsx +149 -0
  300. package/src/views/index.ts +20 -0
  301. package/src/views/memories-panel.tsx +365 -0
  302. package/src/views/message-list.tsx +370 -0
  303. package/src/views/project-detail.tsx +257 -0
  304. package/src/views/projects-panel.tsx +131 -0
  305. package/src/views/sessions-list.tsx +98 -0
  306. package/src/views/sidebar-content.tsx +256 -0
  307. package/src/views/work-detail.tsx +98 -0
  308. package/src/vite.ts +284 -0
@@ -0,0 +1,828 @@
1
+ 'use client';
2
+
3
+ import {
4
+ useCallback, useEffect, useLayoutEffect, useMemo, useState, type ComponentType, type ReactNode
5
+ } from 'react';
6
+
7
+ import {
8
+ AIChatPlugin,
9
+ AIPlugin,
10
+ useEditorChat,
11
+ useLastAssistantMessage
12
+ } from '@platejs/ai/react';
13
+ import { BlockSelectionPlugin, useIsSelecting } from '@platejs/selection/react';
14
+ import { getTransientSuggestionKey } from '@platejs/suggestion';
15
+ import {
16
+ AlbumIcon,
17
+ ArrowUpIcon,
18
+ BadgeHelpIcon,
19
+ CheckIcon,
20
+ CornerUpLeftIcon,
21
+ FeatherIcon,
22
+ LanguagesIcon,
23
+ ListEnd,
24
+ ListMinusIcon,
25
+ ListPlusIcon,
26
+ PenLineIcon,
27
+ Wand,
28
+ X
29
+ } from 'lucide-react';
30
+ import {
31
+ isHotkey, KEYS, NodeApi, RangeApi, type NodeEntry
32
+ } from 'platejs';
33
+ import {
34
+ type PlateEditor,
35
+ useEditorPlugin,
36
+ useEditorRef,
37
+ useHotkeys,
38
+ usePluginOption
39
+ } from 'platejs/react';
40
+
41
+ import { cn } from '@docyrus/ui-pro-shared/lib/utils';
42
+
43
+ import { AIChatEditor } from './ai-chat-editor';
44
+ import { Button } from './button';
45
+ import {
46
+ type Action,
47
+ ComboboxContent,
48
+ ComboboxInput,
49
+ ComboboxList,
50
+ filterMenuGroups,
51
+ filterMenuItems,
52
+ Menu,
53
+ MenuContent,
54
+ MenuGroup,
55
+ MenuItem,
56
+ MenuTrigger,
57
+ useComboboxValueState,
58
+ useMenuStore
59
+ } from './menu';
60
+ import { TextareaAutosize } from './textarea';
61
+
62
+ /**
63
+ * Returns the selected text when using a text selection (not block selection).
64
+ * Returns null if block selection is active, selection is collapsed, or no editor selection.
65
+ */
66
+ function getSelectedText(editor: PlateEditor): string | null {
67
+ // Block selection active — this is not a text selection
68
+ const blockSelectedNodes = editor
69
+ .getApi(BlockSelectionPlugin)
70
+ .blockSelection.getNodes();
71
+
72
+ if (blockSelectedNodes.length > 0) return null;
73
+
74
+ const { selection } = editor;
75
+
76
+ if (!selection || RangeApi.isCollapsed(selection)) return null;
77
+
78
+ try {
79
+ return editor.api.string(selection) || null;
80
+ } catch {
81
+ return null;
82
+ }
83
+ }
84
+
85
+ export function AIMenu() {
86
+ const { api, editor } = useEditorPlugin(AIChatPlugin);
87
+ const open = usePluginOption(AIChatPlugin, 'open');
88
+ const mode = usePluginOption(AIChatPlugin, 'mode');
89
+ const isSelecting = useIsSelecting();
90
+ const streaming = usePluginOption(AIChatPlugin, 'streaming');
91
+
92
+ const [input, setInput] = useState('');
93
+ const toolName = usePluginOption(AIChatPlugin, 'toolName');
94
+ const chat = usePluginOption(AIChatPlugin, 'chat');
95
+
96
+ const { messages, status } = chat;
97
+
98
+ /*
99
+ * Determine toolName for free-form custom input.
100
+ * Our backend does not emit `data-toolName` events like the reference OpenAI
101
+ * implementation, so toolName would stay null and nothing would render.
102
+ * We pick the tool based on selection state — the same heuristic the reference
103
+ * project's AI would choose in the vast majority of cases.
104
+ */
105
+ const handleSubmit = useCallback(
106
+ (inputText: string) => {
107
+ if (!inputText.trim()) return;
108
+
109
+ // Follow-up messages: keep the existing toolName Plate.js already set.
110
+ if (messages.length > 0) {
111
+ void api.aiChat.submit(inputText);
112
+ } else if (isSelecting) {
113
+ // Text / block is selected → user wants to edit it.
114
+ void api.aiChat.submit(inputText, { toolName: 'edit' });
115
+ } else {
116
+ // Cursor only → user wants to generate / insert new content.
117
+ void api.aiChat.submit(inputText, {
118
+ mode: 'insert',
119
+ toolName: 'generate'
120
+ });
121
+ }
122
+
123
+ setInput('');
124
+ },
125
+ [api, isSelecting, messages.length]
126
+ );
127
+
128
+ const isLoading = status === 'streaming' || status === 'submitted';
129
+
130
+ const content = useLastAssistantMessage()?.parts.find(
131
+ part => part.type === 'text'
132
+ )?.text;
133
+
134
+ const { show, store } = useMenuStore();
135
+
136
+ useEffect(() => {
137
+ if (streaming) {
138
+ const anchor = api.aiChat.node({ anchor: true });
139
+
140
+ setTimeout(() => {
141
+ const anchorDom = editor.api.toDOMNode(anchor![0])!;
142
+
143
+ store.setAnchorElement(anchorDom);
144
+ }, 0);
145
+ }
146
+ // eslint-disable-next-line react-hooks/exhaustive-deps
147
+ }, [streaming]);
148
+
149
+ useEditorChat({
150
+ chat,
151
+ onOpenBlockSelection: (blocks: NodeEntry[]) => {
152
+ show(editor.api.toDOMNode(blocks.at(-1)![0])!);
153
+ },
154
+ onOpenChange: (open) => {
155
+ if (!open) {
156
+ // Don't close when window loses focus (tab switch, alt-tab)
157
+ if (document.hidden) return;
158
+
159
+ store.hideAll();
160
+ setInput('');
161
+ }
162
+ },
163
+ onOpenCursor: () => {
164
+ const [ancestor] = editor.api.block({ highest: true })!;
165
+
166
+ if (!editor.api.isAt({ end: true }) && !editor.api.isEmpty(ancestor)) {
167
+ editor
168
+ .getApi(BlockSelectionPlugin)
169
+ .blockSelection.set(ancestor.id as string);
170
+ }
171
+
172
+ show(editor.api.toDOMNode(ancestor)!);
173
+ },
174
+ onOpenSelection: () => {
175
+ show(editor.api.toDOMNode(editor.api.blocks().at(-1)![0])!);
176
+ }
177
+ });
178
+
179
+ useHotkeys('escape', () => {
180
+ if (isLoading) {
181
+ api.aiChat.stop();
182
+ } else {
183
+ api.aiChat.hide();
184
+ }
185
+ });
186
+
187
+ useLayoutEffect(() => {
188
+ if (toolName === 'edit' && mode === 'chat' && status === 'ready') {
189
+ let anchorNode = editor.api.node({
190
+ at: [],
191
+ reverse: true,
192
+ match: n => !!n[KEYS.suggestion] && !!n[getTransientSuggestionKey()]
193
+ });
194
+
195
+ if (!anchorNode) {
196
+ anchorNode = editor
197
+ .getApi(BlockSelectionPlugin)
198
+ .blockSelection.getNodes({ selectionFallback: true, sort: true })
199
+ .at(-1);
200
+ }
201
+ if (!anchorNode) return;
202
+
203
+ // BUG
204
+ setTimeout(() => {
205
+ const block = editor.api.block({ at: anchorNode[1] });
206
+ const domNode = editor.api.toDOMNode(block![0]!)!;
207
+
208
+ store.setAnchorElement(domNode);
209
+ }, 0);
210
+ }
211
+
212
+ // eslint-disable-next-line react-hooks/exhaustive-deps
213
+ }, [status]);
214
+
215
+ useEffect(() => {
216
+ if (status === 'ready') {
217
+ setInput('');
218
+ }
219
+ }, [status]);
220
+
221
+ if (toolName === 'comment' && status === 'ready') return null;
222
+
223
+ return (
224
+ <Menu open={open} placement="bottom-start" store={store}>
225
+ <MenuContent
226
+ flip={false}
227
+ onClickOutside={() => {
228
+ api.aiChat.hide();
229
+ }}
230
+ variant="ai"
231
+ wrapperProps={{
232
+ /*
233
+ * FIXME: It is best to set it to 100.
234
+ * But it will cause a horizontal scrollbar to appear.
235
+ * A method should be found to disable translate-x.
236
+ */
237
+ className: 'w-[98%]!'
238
+ }}>
239
+ <ComboboxContent variant="ai">
240
+ {mode === 'chat'
241
+ && isSelecting
242
+ && content
243
+ && toolName === 'generate' && <AIChatEditor content={content} />}
244
+
245
+ <div className="flex gap-1.5 px-3 text-sm">
246
+ {isLoading ? (
247
+ <div className="flex grow select-none items-center gap-2 py-2 text-muted-foreground">
248
+ {messages.length > 1 ? 'Editing' : 'Thinking'}
249
+
250
+ <LoadingIcon />
251
+ </div>
252
+ ) : (
253
+ <AIMenuCombobox
254
+ input={input}
255
+ onSubmit={handleSubmit}
256
+ setInput={setInput} />
257
+ )}
258
+
259
+ <Button
260
+ className="no-focus-ring mt-1 shrink-0"
261
+ disabled={!isLoading && input.trim().length === 0}
262
+ onClick={() => {
263
+ if (isLoading) {
264
+ api.aiChat.stop();
265
+ } else {
266
+ handleSubmit(input);
267
+ }
268
+ }}
269
+ size="iconSm"
270
+ variant="ghost">
271
+ {isLoading ? <StopIcon /> : <SubmitIcon />}
272
+ </Button>
273
+ </div>
274
+ </ComboboxContent>
275
+
276
+ {!isLoading && (
277
+ <ComboboxList
278
+ className={cn('[&_.menu-item-icon]:text-purple-700')}
279
+ variant="ai">
280
+ <AIMenuItems input={input} setInput={setInput} store={store} />
281
+ </ComboboxList>
282
+ )}
283
+ </MenuContent>
284
+ </Menu>
285
+ );
286
+ }
287
+
288
+ type EditorChatState
289
+ = | 'cursorCommand'
290
+ | 'cursorSuggestion'
291
+ | 'selectionCommand'
292
+ | 'selectionSuggestion';
293
+
294
+ const GROUP = {
295
+ LANGUAGES: 'group_languages',
296
+ SELECTION_LANGUAGES: 'group_selection_languages'
297
+ } as const;
298
+
299
+ const aiChatItems = {
300
+ accept: {
301
+ icon: <CheckIcon />,
302
+ label: 'Accept',
303
+ value: 'accept',
304
+ onSelect: ({ aiEditor, editor }) => {
305
+ const { mode, toolName } = editor.getOptions(AIChatPlugin);
306
+
307
+ if (mode === 'chat' && toolName === 'generate') {
308
+ return editor
309
+ .getTransforms(AIChatPlugin)
310
+ .aiChat.replaceSelection(aiEditor);
311
+ }
312
+
313
+ editor.getTransforms(AIChatPlugin).aiChat.accept();
314
+ setTimeout(() => {
315
+ editor.tf.focus({ edge: 'end' });
316
+ }, 0);
317
+ }
318
+ },
319
+ continueWrite: {
320
+ icon: <PenLineIcon />,
321
+ label: 'Continue writing',
322
+ value: 'continueWrite',
323
+ onSelect: ({ editor, input }) => {
324
+ const ancestorNode = editor.api.block({ highest: true });
325
+
326
+ if (!ancestorNode) return;
327
+
328
+ const isEmpty = NodeApi.string(ancestorNode[0]).trim().length === 0;
329
+
330
+ void editor.getApi(AIChatPlugin).aiChat.submit(input, {
331
+ mode: 'insert',
332
+ prompt: isEmpty ? `<Document>
333
+ {editor}
334
+ </Document>
335
+ Start writing a new paragraph AFTER <Document> ONLY ONE SENTENCE` : 'Continue writing AFTER <Block> ONLY ONE SENTENCE. DONT REPEAT THE TEXT.',
336
+ toolName: 'generate'
337
+ });
338
+ }
339
+ },
340
+ discard: {
341
+ icon: <X />,
342
+ label: 'Discard',
343
+ shortcut: 'Escape',
344
+ value: 'discard',
345
+ onSelect: ({ editor }) => {
346
+ editor.getTransforms(AIPlugin).ai.undo();
347
+ editor.getApi(AIChatPlugin).aiChat.hide();
348
+ }
349
+ },
350
+ explain: {
351
+ icon: <BadgeHelpIcon />,
352
+ label: 'Explain',
353
+ value: 'explain',
354
+ onSelect: ({ editor, input }) => {
355
+ void editor.getApi(AIChatPlugin).aiChat.submit(input, {
356
+ prompt: {
357
+ default: 'Explain {editor}',
358
+ selecting: 'Explain'
359
+ },
360
+ toolName: 'generate'
361
+ });
362
+ }
363
+ },
364
+ fixSpelling: {
365
+ icon: <CheckIcon />,
366
+ label: 'Fix spelling & grammar',
367
+ value: 'fixSpelling',
368
+ onSelect: ({ editor, input }) => {
369
+ const selectedText = getSelectedText(editor);
370
+
371
+ void editor.getApi(AIChatPlugin).aiChat.submit(input, {
372
+ prompt: selectedText ? `Fix spelling, grammar, and punctuation errors only in this selected text: "${selectedText}". Do not modify any other part of the content.` : 'Fix spelling, grammar, and punctuation errors within each block only, without changing meaning, tone, or adding new information.',
373
+ toolName: 'edit'
374
+ });
375
+ }
376
+ },
377
+ [GROUP.LANGUAGES]: {
378
+ component: TranslateMenuItems,
379
+ filterItems: true,
380
+ icon: <LanguagesIcon className="text-green-800" />,
381
+ items: [
382
+ { label: 'English', value: 'translate_english' },
383
+ { label: 'Korean', value: 'translate_korean' },
384
+ {
385
+ label: 'Chinese, Simplified',
386
+ value: 'translate_chinese_simplified'
387
+ },
388
+ {
389
+ label: 'Chinese, Traditional',
390
+ value: 'translate_chinese_traditional'
391
+ },
392
+ { label: 'Japanese', value: 'translate_japanese' },
393
+ { label: 'Spanish', value: 'translate_spanish' },
394
+ { label: 'Russian', value: 'translate_russian' },
395
+ { label: 'French', value: 'translate_french' },
396
+ { label: 'Portuguese', value: 'translate_portuguese' },
397
+ { label: 'German', value: 'translate_german' },
398
+ { label: 'Italian', value: 'translate_italian' },
399
+ { label: 'Dutch', value: 'translate_dutch' },
400
+ { label: 'Indonesian', value: 'translate_indonesian' },
401
+ { label: 'Filipino', value: 'translate_filipino' },
402
+ { label: 'Vietnamese', value: 'translate_vietnamese' },
403
+ { label: 'Turkish', value: 'translate_turkish' },
404
+ { label: 'Arabic', value: 'translate_arabic' }
405
+ ],
406
+ label: 'Languages',
407
+ value: GROUP.LANGUAGES
408
+ },
409
+ [GROUP.SELECTION_LANGUAGES]: {
410
+ component: TranslateMenuItems,
411
+ filterItems: true,
412
+ icon: <LanguagesIcon className="text-green-800" />,
413
+ items: [
414
+ { label: 'English', value: 'translate_english' },
415
+ { label: 'Korean', value: 'translate_korean' },
416
+ {
417
+ label: 'Chinese, Simplified',
418
+ value: 'translate_chinese_simplified'
419
+ },
420
+ {
421
+ label: 'Chinese, Traditional',
422
+ value: 'translate_chinese_traditional'
423
+ },
424
+ { label: 'Japanese', value: 'translate_japanese' },
425
+ { label: 'Spanish', value: 'translate_spanish' },
426
+ { label: 'Russian', value: 'translate_russian' },
427
+ { label: 'French', value: 'translate_french' },
428
+ { label: 'Portuguese', value: 'translate_portuguese' },
429
+ { label: 'German', value: 'translate_german' },
430
+ { label: 'Italian', value: 'translate_italian' },
431
+ { label: 'Dutch', value: 'translate_dutch' },
432
+ { label: 'Indonesian', value: 'translate_indonesian' },
433
+ { label: 'Filipino', value: 'translate_filipino' },
434
+ { label: 'Vietnamese', value: 'translate_vietnamese' },
435
+ { label: 'Turkish', value: 'translate_turkish' },
436
+ { label: 'Arabic', value: 'translate_arabic' }
437
+ ],
438
+ label: 'Languages',
439
+ value: GROUP.LANGUAGES
440
+ },
441
+ improveWriting: {
442
+ icon: <Wand />,
443
+ label: 'Improve writing',
444
+ value: 'improveWriting',
445
+ onSelect: ({ editor, input }) => {
446
+ const selectedText = getSelectedText(editor);
447
+
448
+ void editor.getApi(AIChatPlugin).aiChat.submit(input, {
449
+ prompt: selectedText ? `Improve the writing of this selected text only: "${selectedText}". Do not modify any other part of the content.` : 'Improve the writing for clarity and flow, without changing meaning or adding new information.',
450
+ toolName: 'edit'
451
+ });
452
+ }
453
+ },
454
+ insertBelow: {
455
+ icon: <ListEnd />,
456
+ label: 'Insert below',
457
+ value: 'insertBelow',
458
+ onSelect: ({ aiEditor, editor }) => {
459
+ const { selectedIds } = editor.getOptions(BlockSelectionPlugin);
460
+
461
+ if (!selectedIds || selectedIds.size === 0) {
462
+ const { chatNodes } = editor.getOptions(AIChatPlugin);
463
+
464
+ if (chatNodes && chatNodes.length > 0) {
465
+ const ids = chatNodes
466
+ .map(n => n.id)
467
+ .filter((id): id is string => typeof id === 'string');
468
+
469
+ if (ids.length > 0) {
470
+ editor.setOption(BlockSelectionPlugin, 'selectedIds', new Set(ids));
471
+ }
472
+ }
473
+ }
474
+
475
+ void editor
476
+ .getTransforms(AIChatPlugin)
477
+ .aiChat.insertBelow(aiEditor, { format: 'none' });
478
+ }
479
+ },
480
+ makeLonger: {
481
+ icon: <ListPlusIcon />,
482
+ label: 'Make longer',
483
+ value: 'makeLonger',
484
+ onSelect: ({ editor, input }) => {
485
+ const selectedText = getSelectedText(editor);
486
+
487
+ void editor.getApi(AIChatPlugin).aiChat.submit(input, {
488
+ prompt: selectedText ? `Make this selected text longer by elaborating on existing ideas: "${selectedText}". Do not modify any other part of the content.` : 'Make the content longer by elaborating on existing ideas within each block only, without changing meaning or adding new information.',
489
+ toolName: 'edit'
490
+ });
491
+ }
492
+ },
493
+ makeShorter: {
494
+ icon: <ListMinusIcon />,
495
+ label: 'Make shorter',
496
+ value: 'makeShorter',
497
+ onSelect: ({ editor, input }) => {
498
+ const selectedText = getSelectedText(editor);
499
+
500
+ void editor.getApi(AIChatPlugin).aiChat.submit(input, {
501
+ prompt: selectedText ? `Make this selected text shorter by reducing verbosity: "${selectedText}". Do not modify any other part of the content.` : 'Make the content shorter by reducing verbosity within each block only, without changing meaning or removing essential information.',
502
+ toolName: 'edit'
503
+ });
504
+ }
505
+ },
506
+ simplifyLanguage: {
507
+ icon: <FeatherIcon />,
508
+ label: 'Simplify language',
509
+ value: 'simplifyLanguage',
510
+ onSelect: ({ editor, input }) => {
511
+ const selectedText = getSelectedText(editor);
512
+
513
+ void editor.getApi(AIChatPlugin).aiChat.submit(input, {
514
+ prompt: selectedText ? `Simplify the language of this selected text only: "${selectedText}". Do not modify any other part of the content.` : 'Simplify the language by using clearer and more straightforward wording within each block only, without changing meaning or adding new information.',
515
+ toolName: 'edit'
516
+ });
517
+ }
518
+ },
519
+ summarize: {
520
+ icon: <AlbumIcon />,
521
+ label: 'Add a summary',
522
+ value: 'summarize',
523
+ onSelect: ({ editor, input }) => {
524
+ void editor.getApi(AIChatPlugin).aiChat.submit(input, {
525
+ mode: 'insert',
526
+ prompt: {
527
+ default: 'Summarize {editor}',
528
+ selecting: 'Summarize'
529
+ },
530
+ toolName: 'generate'
531
+ });
532
+ }
533
+ },
534
+ tryAgain: {
535
+ icon: <CornerUpLeftIcon />,
536
+ label: 'Try again',
537
+ value: 'tryAgain',
538
+ onSelect: ({ editor, store }) => {
539
+ void editor.getApi(AIChatPlugin).aiChat.reload();
540
+
541
+ setTimeout(() => {
542
+ const anchor = editor
543
+ .getApi(BlockSelectionPlugin)
544
+ .blockSelection.getNodes({ selectionFallback: true, sort: true })
545
+ .at(-1)!;
546
+ const anchorDom = editor.api.toDOMNode(anchor[0])!;
547
+
548
+ store.setAnchorElement(anchorDom);
549
+ }, 0);
550
+ }
551
+ }
552
+ } satisfies Record<
553
+ string,
554
+ {
555
+ icon: ReactNode;
556
+ label: string;
557
+ value: string;
558
+ component?: ComponentType<{ menuState: EditorChatState; input?: string }>;
559
+ filterItems?: boolean;
560
+ items?: { label: string; value: string }[];
561
+ shortcut?: string;
562
+ onSelect?: ({
563
+ aiEditor,
564
+ editor,
565
+ input,
566
+ store
567
+ }: {
568
+ aiEditor: PlateEditor;
569
+ editor: PlateEditor;
570
+ input: string;
571
+ store: any;
572
+ }) => void;
573
+ }
574
+ >;
575
+
576
+ const menuStateItems = {
577
+ cursorCommand: [
578
+ {
579
+ items: [aiChatItems.continueWrite, aiChatItems.summarize, aiChatItems.explain]
580
+ }
581
+ ],
582
+ cursorSuggestion: [
583
+ {
584
+ items: [aiChatItems.accept, aiChatItems.discard, aiChatItems.tryAgain]
585
+ }
586
+ ],
587
+ selectionCommand: [
588
+ {
589
+ items: [
590
+ aiChatItems.improveWriting,
591
+ aiChatItems.makeLonger,
592
+ aiChatItems.makeShorter,
593
+ aiChatItems.fixSpelling,
594
+ aiChatItems.simplifyLanguage
595
+ ]
596
+ },
597
+ {
598
+ items: [aiChatItems[GROUP.SELECTION_LANGUAGES]]
599
+ }
600
+ ],
601
+ selectionSuggestion: [
602
+ {
603
+ items: [
604
+ aiChatItems.accept,
605
+ aiChatItems.discard,
606
+ aiChatItems.insertBelow,
607
+ aiChatItems.tryAgain
608
+ ]
609
+ }
610
+ ]
611
+ };
612
+
613
+ function AIMenuItems({
614
+ input,
615
+ setInput,
616
+ store
617
+ }: {
618
+ input: string;
619
+ store: any;
620
+ setInput: (value: string) => void;
621
+ }) {
622
+ const editor = useEditorRef();
623
+ const [searchValue] = useComboboxValueState();
624
+ const { messages } = usePluginOption(AIChatPlugin, 'chat');
625
+ const aiEditor = usePluginOption(AIChatPlugin, 'aiEditor')!;
626
+ const isSelecting = useIsSelecting();
627
+
628
+ const menuState = useMemo(() => {
629
+ if (messages && messages.length > 0) {
630
+ return isSelecting ? 'selectionSuggestion' : 'cursorSuggestion';
631
+ }
632
+
633
+ return isSelecting ? 'selectionCommand' : 'cursorCommand';
634
+ }, [isSelecting, messages]);
635
+
636
+ const menuGroups = useMemo(() => {
637
+ const items = menuStateItems[menuState] || [];
638
+
639
+ return filterMenuGroups(items, searchValue) || items;
640
+ }, [menuState, searchValue]);
641
+
642
+ return (
643
+ <>
644
+ {menuGroups.map((group, index) => (
645
+ <MenuGroup key={index} label={group.label}>
646
+ {group.items?.map((item: Action) => {
647
+ const menuItem = (aiChatItems as any)[item.value!];
648
+
649
+ if (menuItem.component) {
650
+ const ItemComponent = menuItem.component;
651
+
652
+ return (
653
+ <ItemComponent
654
+ input={input}
655
+ key={item.value}
656
+ menuState={menuState} />
657
+ );
658
+ }
659
+
660
+ return (
661
+ <MenuItem
662
+ icon={menuItem.icon}
663
+ key={item.value}
664
+ label={menuItem.label}
665
+ onClick={() => {
666
+ menuItem.onSelect?.({
667
+ aiEditor, editor, input, store
668
+ });
669
+ setInput('');
670
+ }}
671
+ shortcutEnter />
672
+ );
673
+ })}
674
+ </MenuGroup>
675
+ ))}
676
+ </>
677
+ );
678
+ }
679
+
680
+ function TranslateMenuItems({
681
+ input = '',
682
+ menuState
683
+ }: {
684
+ menuState: EditorChatState;
685
+ input?: string;
686
+ }) {
687
+ const editor = useEditorRef();
688
+ const [searchValue] = useComboboxValueState();
689
+
690
+ const menuItems = useMemo(
691
+ () => filterMenuItems(aiChatItems[GROUP.LANGUAGES], searchValue),
692
+ [searchValue]
693
+ );
694
+
695
+ const handleTranslate = (value: string) => {
696
+ if (menuState === 'cursorCommand') {
697
+ void editor.getApi(AIChatPlugin).aiChat.submit(input, {
698
+ mode: 'insert',
699
+ prompt: `Translate to ${value} the "Block" content`,
700
+ toolName: 'edit'
701
+ });
702
+
703
+ return;
704
+ }
705
+ if (menuState === 'selectionCommand') {
706
+ void editor.getApi(AIChatPlugin).aiChat.submit(input, {
707
+ prompt: `Translate to ${value}`,
708
+ toolName: 'edit'
709
+ });
710
+
711
+ return;
712
+ }
713
+ };
714
+
715
+ const content = (
716
+ <>
717
+ {menuItems.map(item => (
718
+ <MenuItem
719
+ icon={item.icon}
720
+ key={item.value}
721
+ label={item.label}
722
+ onClick={() => handleTranslate(item.label!)}
723
+ shortcutEnter />
724
+ ))}
725
+ </>
726
+ );
727
+
728
+ if (searchValue)
729
+ return (
730
+ <MenuGroup label={aiChatItems[GROUP.LANGUAGES].label}>
731
+ {content}
732
+ </MenuGroup>
733
+ );
734
+
735
+ return (
736
+ <Menu
737
+ trigger={
738
+ <MenuTrigger
739
+ icon={aiChatItems[GROUP.LANGUAGES].icon}
740
+ label={aiChatItems[GROUP.LANGUAGES].label} />
741
+ }>
742
+ <MenuContent variant="aiSub">
743
+ <MenuGroup>{content}</MenuGroup>
744
+ </MenuContent>
745
+ </Menu>
746
+ );
747
+ }
748
+
749
+ function AIMenuCombobox({
750
+ input,
751
+ onSubmit,
752
+ setInput
753
+ }: {
754
+ input: string;
755
+ onSubmit: (value: string) => void;
756
+ setInput: (value: string) => void;
757
+ }) {
758
+ const { api } = useEditorPlugin(AIChatPlugin);
759
+ const [, setValue] = useComboboxValueState();
760
+
761
+ useEffect(() => {
762
+ setValue(input ?? '');
763
+ }, [input, setValue]);
764
+
765
+ return (
766
+ <ComboboxInput autoFocus autoSelect="always">
767
+ <TextareaAutosize
768
+ className="grow"
769
+ data-plate-focus
770
+ onChange={e => setInput(e.target.value)}
771
+ onKeyDown={(e) => {
772
+ if (isHotkey('backspace')(e) && input?.length === 0) {
773
+ e.preventDefault();
774
+ api.aiChat.hide();
775
+ }
776
+ if (isHotkey('enter')(e) && !e.shiftKey) {
777
+ e.preventDefault();
778
+
779
+ if (input && input.length > 0) {
780
+ onSubmit(input);
781
+ }
782
+ }
783
+ }}
784
+ placeholder="Ask AI anything..."
785
+ variant="ai" />
786
+ </ComboboxInput>
787
+ );
788
+ }
789
+
790
+ function StopIcon() {
791
+ return (
792
+ <svg
793
+ height="20"
794
+ viewBox="0 0 20 20"
795
+ width="20"
796
+ xmlns="http://www.w3.org/2000/svg">
797
+ <circle cx="10" cy="10" fill="black" r="10" />
798
+ <rect fill="white" height="6" rx="1" width="6" x="7" y="7" />
799
+ </svg>
800
+ );
801
+ }
802
+
803
+ function SubmitIcon() {
804
+ return (
805
+ <div
806
+ className={cn(
807
+ 'flex size-5 items-center justify-center rounded-full bg-brand'
808
+ )}>
809
+ <ArrowUpIcon className="size-3! stroke-[3px] text-background" />
810
+ </div>
811
+ );
812
+ }
813
+
814
+ function LoadingIcon() {
815
+ return (
816
+ <div className="flex gap-0.5">
817
+ {['#eab308', '#ea580c', '#6EB6F2'].map((color, index) => (
818
+ <div
819
+ className="size-1 animate-ai-bounce rounded-full"
820
+ key={color}
821
+ style={{
822
+ animationDelay: `${index * 0.1}s`,
823
+ backgroundColor: color
824
+ }} />
825
+ ))}
826
+ </div>
827
+ );
828
+ }