@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,1962 @@
1
+ 'use client';
2
+
3
+ import {
4
+ useCallback, useEffect, useMemo, useRef, useState, type ChangeEvent, type FormEvent, type KeyboardEvent, type Ref, type RefObject
5
+ } from 'react';
6
+
7
+ import { useChat } from '@ai-sdk/react';
8
+ import { Button } from '@docyrus/ui-pro-shared/components/button';
9
+ import { toast } from 'sonner';
10
+ import {
11
+ Dialog, DialogContent, DialogTitle
12
+ } from '@docyrus/ui-pro-shared/components/dialog';
13
+ import {
14
+ DropdownMenu,
15
+ DropdownMenuContent,
16
+ DropdownMenuItem,
17
+ DropdownMenuSeparator,
18
+ DropdownMenuSub,
19
+ DropdownMenuSubContent,
20
+ DropdownMenuSubTrigger,
21
+ DropdownMenuTrigger
22
+ } from '@docyrus/ui-pro-shared/components/dropdown-menu';
23
+ import { Textarea } from '@docyrus/ui-pro-shared/components/textarea';
24
+ import { Input } from '@docyrus/ui-pro-shared/components/input';
25
+ import {
26
+ Tabs, TabsContent, TabsList, TabsTrigger
27
+ } from '@docyrus/ui-pro-shared/components/tabs';
28
+ import { cn } from '@docyrus/ui-pro-shared/lib/utils';
29
+ import {
30
+ DefaultChatTransport,
31
+ type FinishReason,
32
+ lastAssistantMessageIsCompleteWithToolCalls,
33
+ type UIMessage
34
+ } from 'ai';
35
+ import {
36
+ Archive,
37
+ ArrowLeft,
38
+ ArrowRight,
39
+ Edit,
40
+ FolderInput,
41
+ Maximize2,
42
+ Minimize2,
43
+ MoreHorizontal,
44
+ PanelLeft,
45
+ Plus,
46
+ Search,
47
+ Trash,
48
+ X
49
+ } from 'lucide-react';
50
+
51
+ import { AssistantDialogs } from './components/assistant-dialogs';
52
+ import { WorkCard } from './components/work-card';
53
+ import {
54
+ useAssistantApi, useProjectState, useSessionState, useSpeechRecognition, useUIState, useWorksState
55
+ } from './hooks';
56
+ import { AssistantCanvasView } from './views/canvas';
57
+ import { ConversationView, InlineView } from './views/assistant-view';
58
+ import { MemoriesPanel } from './views/memories-panel';
59
+ import { AssistantProjectDetailView } from './views/project-detail';
60
+ import { AssistantProjectsPanel } from './views/projects-panel';
61
+ import { SessionsListView } from './views/sessions-list';
62
+ import { SidebarContent } from './views/sidebar-content';
63
+ import { AssistantWorkDetailView } from './views/work-detail';
64
+ import { useAssistantTranslation } from './i18n';
65
+ import { useApiClient } from './lib/api-client';
66
+ import {
67
+ createThread,
68
+ deleteProject as deleteProjectApi,
69
+ deleteSession as deleteSessionApi,
70
+ fetchAgentThreads,
71
+ fetchProjectThreads as fetchProjectThreadsApi,
72
+ fetchProjects as fetchProjectsApi,
73
+ fetchProjectWorks as fetchProjectWorksApi,
74
+ fetchWorks as fetchWorksApi,
75
+ loadThreadMessages as loadThreadMessagesApi,
76
+ updateProject as updateProjectApi,
77
+ updateProjectInstructions as updateProjectInstructionsApi,
78
+ updateSession as updateSessionApi,
79
+ uploadThreadFile
80
+ } from './lib/assistant-api-actions';
81
+ import { useAssistantConfig } from './lib/assistant-config';
82
+ import { getContainerClassName, getDialogClassName } from './lib/class-utils';
83
+
84
+ import {
85
+ type AssistantSession,
86
+ type DocyAssistantProps,
87
+ type Project,
88
+ type Work
89
+ } from './types';
90
+
91
+ interface ThreadHeaderInlineProps {
92
+ session: AssistantSession;
93
+ projects: Project[];
94
+ projectContext: { id: string; name: string } | null;
95
+ onSaveTitle: (newTitle: string) => Promise<void>;
96
+ onDelete: () => void;
97
+ onArchive: () => Promise<void>;
98
+ onMoveToProject: (projectId: string) => Promise<void>;
99
+ onProjectContextClick?: () => void;
100
+ isFullscreen?: boolean;
101
+ t: (key: string) => string;
102
+ }
103
+
104
+ function ThreadHeaderInline({
105
+ session,
106
+ projects,
107
+ projectContext,
108
+ onSaveTitle,
109
+ onDelete,
110
+ onArchive,
111
+ onMoveToProject,
112
+ onProjectContextClick,
113
+ isFullscreen = false,
114
+ t
115
+ }: ThreadHeaderInlineProps) {
116
+ const dropdownZClass = isFullscreen ? 'z-[10000]' : '';
117
+ const [editValue, setEditValue] = useState<string | null>(null);
118
+
119
+ const handleBlur = async () => {
120
+ if (editValue === null) return;
121
+ const trimmed = editValue.trim();
122
+
123
+ if (trimmed && trimmed !== session.title) {
124
+ await onSaveTitle(trimmed);
125
+ }
126
+
127
+ setEditValue(null);
128
+ };
129
+
130
+ const handleKeyDown = async (e: KeyboardEvent<HTMLInputElement>) => {
131
+ if (e.key === 'Enter') {
132
+ e.currentTarget.blur();
133
+ } else if (e.key === 'Escape') {
134
+ setEditValue(null);
135
+ }
136
+ };
137
+
138
+ return (
139
+ <div className="flex-none h-10 flex bg-background items-center justify-between border-b border-x border-border px-3 mx-3 rounded-b-md shrink-0">
140
+ <div className="flex items-center gap-2 min-w-0 flex-1">
141
+ {projectContext?.id && onProjectContextClick && (
142
+ <Button
143
+ variant="ghost"
144
+ onClick={onProjectContextClick}
145
+ className="inline-flex items-center gap-1 px-2 py-0.5 h-auto rounded-full bg-sky-100 text-sky-700 dark:bg-sky-900/40 dark:text-sky-300 text-xs font-medium flex-none hover:bg-sky-200 transition-colors">
146
+ {projectContext.name}
147
+ </Button>
148
+ )}
149
+ {editValue !== null ? (
150
+ <input
151
+ autoFocus
152
+ value={editValue}
153
+ onChange={e => setEditValue(e.target.value)}
154
+ onBlur={handleBlur}
155
+ onKeyDown={handleKeyDown}
156
+ className="text-sm font-medium text-foreground bg-transparent border-b border-border focus:outline-none min-w-0 flex-1" />
157
+ ) : (
158
+ <div className="group/title flex items-center gap-1 min-w-0">
159
+ <span className="text-sm font-medium text-foreground truncate min-w-0">
160
+ {session.title || t('common.untitled')}
161
+ </span>
162
+ <Button
163
+ variant="ghost"
164
+ size="icon"
165
+ onClick={() => setEditValue(session.title || '')}
166
+ className="opacity-0 group-hover/title:opacity-100 transition-opacity shrink-0 h-5 w-5 text-muted-foreground hover:text-foreground">
167
+ <Edit className="w-3.5 h-3.5" />
168
+ </Button>
169
+ </div>
170
+ )}
171
+ </div>
172
+ <div className="flex items-center flex-none">
173
+ <DropdownMenu>
174
+ <DropdownMenuTrigger asChild>
175
+ <Button variant="ghost" size="icon" className="h-7 w-7 text-muted-foreground hover:text-foreground">
176
+ <MoreHorizontal className="w-4 h-4" />
177
+ </Button>
178
+ </DropdownMenuTrigger>
179
+ <DropdownMenuContent align="end" className={cn('w-48', dropdownZClass)}>
180
+ <DropdownMenuSub>
181
+ <DropdownMenuSubTrigger className="cursor-pointer">
182
+ <FolderInput className="w-4 h-4 mr-2" />
183
+ {t('actions.move_to_project')}
184
+ </DropdownMenuSubTrigger>
185
+ <DropdownMenuSubContent className={cn('w-48', dropdownZClass)}>
186
+ {projects.length === 0 ? (
187
+ <DropdownMenuItem disabled className="text-muted-foreground text-xs">
188
+ {t('messages.no_projects_yet')}
189
+ </DropdownMenuItem>
190
+ ) : (
191
+ projects.map(project => (
192
+ <DropdownMenuItem
193
+ key={project.id}
194
+ className="cursor-pointer"
195
+ onClick={() => onMoveToProject(project.id)}>
196
+ {project.name}
197
+ </DropdownMenuItem>
198
+ ))
199
+ )}
200
+ </DropdownMenuSubContent>
201
+ </DropdownMenuSub>
202
+ <DropdownMenuItem
203
+ className="cursor-pointer"
204
+ onClick={onArchive}>
205
+ <Archive className="w-4 h-4 mr-2" />
206
+ {t('actions.archive')}
207
+ </DropdownMenuItem>
208
+ <DropdownMenuSeparator />
209
+ <DropdownMenuItem
210
+ className="text-destructive focus:text-destructive cursor-pointer"
211
+ onClick={onDelete}>
212
+ <Trash className="w-4 h-4 mr-2" />
213
+ {t('common.delete')}
214
+ </DropdownMenuItem>
215
+ </DropdownMenuContent>
216
+ </DropdownMenu>
217
+ </div>
218
+ </div>
219
+ );
220
+ }
221
+
222
+ function dataUrlToFile(dataUrl: string, filename: string): File {
223
+ const arr = dataUrl.split(',');
224
+ const mime = arr[0].match(/:(.*?);/)?.[1] || 'application/octet-stream';
225
+ const bstr = atob(arr[1]);
226
+ let n = bstr.length;
227
+ const u8arr = new Uint8Array(n);
228
+
229
+ while (n--) {
230
+ u8arr[n] = bstr.charCodeAt(n);
231
+ }
232
+
233
+ return new File([u8arr], filename, { type: mime });
234
+ }
235
+
236
+ export const DocyAssistant = (
237
+ {
238
+ ref, open, onOpenChange, supportWebSearch = false, supportThinking = false, supportFiles = false, supportDocumentSearch = false, supportDeepResearch = false, supportMultiModels = false, supportWorkCanvas = false, apiEndpoint = '/ai/agents/:agentId/chat', title: titleProp, description: descriptionProp, placeholder: placeholderProp, logo, footerText: footerTextProp, variant = 'default', renderMode = 'modal', enableSidebar = true, enableNavDropdown = false, enableVoice = false, enableMicrophone = true, tenantAiAgentId, deploymentId, onMessageSend, onVoiceStart, onVoiceEnd, className, defaultFullscreen = false, hideExpand = false, agentSelectorUrl, baseAgentSelectorUrl, onAgentChange, ...props
239
+ }: DocyAssistantProps & { ref?: RefObject<HTMLDivElement | null> }
240
+ ) => {
241
+ const config = useAssistantConfig();
242
+ const { apiBaseUrl: baseUrl, getAuthToken, user: configUser } = config;
243
+ const apiClient = useApiClient();
244
+ const { t } = useAssistantTranslation();
245
+
246
+ // Localized default values
247
+ const title = titleProp || 'DocyAssistant';
248
+ const description = descriptionProp || t('descriptions.default');
249
+ const placeholder = placeholderProp || t('placeholders.type_message');
250
+ const footerText = footerTextProp || t('descriptions.footer_default');
251
+
252
+ // Custom hooks for grouped state management
253
+ const { state: uiState, actions: uiActions } = useUIState(variant, renderMode);
254
+ const { state: sessionState, actions: sessionActions } = useSessionState();
255
+ const { state: projectState, actions: projectActions } = useProjectState();
256
+ const { state: worksState, actions: worksActions } = useWorksState();
257
+
258
+ // Remaining local state
259
+ const [input, setInput] = useState('');
260
+ const currentUserId = configUser?.id || null;
261
+ const [projectSearchQuery, setProjectSearchQuery] = useState('');
262
+ const [isInlineFullscreen, setIsInlineFullscreen] = useState(defaultFullscreen);
263
+
264
+ // Speech recognition hook
265
+ const { isRecording, recognition, handleMicrophoneClick } = useSpeechRecognition({
266
+ enabled: enableMicrophone,
267
+ onTranscript: transcript => setInput(prev => prev + (prev ? ' ' : '') + transcript),
268
+ onStart: onVoiceStart,
269
+ onEnd: onVoiceEnd
270
+ });
271
+
272
+ // Refs
273
+ const selectedSessionIdRef = useRef<string | null>(null);
274
+ const sidebarCloseTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
275
+ const rightSidebarCloseTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
276
+ const authTokenRef = useRef<string>('');
277
+ const messageOptionsRef = useRef<any>(null);
278
+
279
+ // Keep refs in sync with state
280
+ useEffect(() => {
281
+ selectedSessionIdRef.current = sessionState.selectedSessionId;
282
+ }, [sessionState.selectedSessionId]);
283
+
284
+ // Get and maintain auth token in ref
285
+ useEffect(() => {
286
+ getAuthToken().then((token: string) => {
287
+ if (token) {
288
+ authTokenRef.current = token;
289
+ }
290
+ });
291
+ }, [getAuthToken]);
292
+
293
+ // Create transport with headers and body using functions for dynamic values
294
+ const transport = useMemo(() => {
295
+ // Build URL from apiEndpoint template, replacing :agentId and optionally adding deployment
296
+ const endpointPath = apiEndpoint.replace(':agentId', tenantAiAgentId);
297
+ const apiUrl = deploymentId ? `${baseUrl}${endpointPath.replace('/chat', `/deployments/${deploymentId}/chat`)}` : `${baseUrl}${endpointPath}`;
298
+
299
+ return new DefaultChatTransport({
300
+ api: apiUrl,
301
+ prepareSendMessagesRequest: ({ messages, body }: { messages: any[]; body: any }) => {
302
+ const options = messageOptionsRef.current;
303
+
304
+ return {
305
+ body: {
306
+ ...body,
307
+ agentId: tenantAiAgentId,
308
+ threadId: selectedSessionIdRef.current, // Use ref for immediate access
309
+ messages: messages.slice(-10), // Only send last 10 messages
310
+ modelId: options?.modelId,
311
+ supportMultipleModels: options?.supportMultipleModels,
312
+ supportFiles: options?.supportFiles,
313
+ supportWebSearch: options?.supportWebSearch,
314
+ supportDeepResearch: options?.supportDeepResearch,
315
+ supportDocumentSearch: options?.supportDocumentSearch,
316
+ supportThinking: options?.supportThinking,
317
+ supportWorkCanvas: options?.supportWorkCanvas,
318
+ ...(options?.filePaths?.length ? { files: options.filePaths } : {})
319
+ }
320
+ };
321
+ },
322
+ headers: () => {
323
+ const token = authTokenRef.current;
324
+
325
+ return {
326
+ 'Content-Type': 'application/json',
327
+ ...(token && { Authorization: `Bearer ${token}` })
328
+ };
329
+ },
330
+ body: () => ({
331
+ agentId: tenantAiAgentId,
332
+ threadId: selectedSessionIdRef.current
333
+ })
334
+ });
335
+ }, [
336
+ baseUrl,
337
+ apiEndpoint,
338
+ tenantAiAgentId,
339
+ deploymentId
340
+ ]);
341
+
342
+ // Use the AI SDK's useChat hook with agent-specific endpoint
343
+ const {
344
+ messages = [],
345
+ status,
346
+ sendMessage,
347
+ setMessages,
348
+ stop,
349
+ addToolOutput
350
+ } = useChat({
351
+ id: `docy-assistant:${tenantAiAgentId}`,
352
+ transport,
353
+ sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
354
+ onFinish: (
355
+ message: UIMessage,
356
+ options?: {
357
+ usage?: {
358
+ inputTokens?: number;
359
+ outputTokens?: number;
360
+ totalTokens?: number;
361
+ cacheReadTokens?: number;
362
+ cacheWriteTokens?: number;
363
+ };
364
+ finishReason?: FinishReason;
365
+ }
366
+ ) => {
367
+ const { usage, finishReason } = options || {};
368
+
369
+ if (typeof process !== 'undefined' && process.env.NODE_ENV === 'development') {
370
+ console.info('[AI] Message finished:', {
371
+ messageId: message.id,
372
+ role: message.role,
373
+ finishReason,
374
+ inputTokens: usage?.inputTokens,
375
+ outputTokens: usage?.outputTokens,
376
+ totalTokens: usage?.totalTokens,
377
+ cacheReadTokens: usage?.cacheReadTokens,
378
+ cacheWriteTokens: usage?.cacheWriteTokens
379
+ });
380
+ }
381
+ },
382
+ onError: (error: Error) => {
383
+ console.error('[AI] Chat error:', error);
384
+ }
385
+ } as any);
386
+
387
+ // AI SDK v6 status handling
388
+ const isStreaming = status === 'streaming';
389
+ const isSubmitting = status === 'submitted';
390
+ const isLoading = isStreaming || isSubmitting;
391
+
392
+ const handleToolAction = useCallback((event: {
393
+ tool: string;
394
+ toolCallId: string;
395
+ decision: string;
396
+ input: any;
397
+ }) => {
398
+ switch (event.decision) {
399
+ case 'approve':
400
+
401
+ case 'continue':
402
+ addToolOutput({
403
+ tool: event.tool as any,
404
+ toolCallId: event.toolCallId,
405
+ output: { action: event.decision }
406
+ });
407
+ break;
408
+
409
+ case 'submit':
410
+ addToolOutput({
411
+ tool: event.tool as any,
412
+ toolCallId: event.toolCallId,
413
+ output: event.input
414
+ });
415
+ break;
416
+
417
+ default:
418
+ addToolOutput({
419
+ tool: event.tool as any,
420
+ toolCallId: event.toolCallId,
421
+ output: { decision: event.decision }
422
+ });
423
+ }
424
+ }, [addToolOutput]);
425
+
426
+ // Compute user display info from config
427
+ const userDisplayName = configUser ? `${configUser.firstname || ''} ${configUser.lastname || ''}`.trim() || configUser.email?.split('@')[0] || 'User' : 'User';
428
+ const userPhoto = configUser?.photo || '';
429
+
430
+ // Computed values from state
431
+ const isInlineMode = uiState.currentRenderMode === 'inline';
432
+
433
+ // Auto-open create view when no projects
434
+ useEffect(() => {
435
+ if (
436
+ uiState.activeTab === 2
437
+ && projectState.projectsLoaded
438
+ && projectState.projects.length === 0
439
+ && projectState.view === 'list'
440
+ ) {
441
+ projectActions.setView('create');
442
+ }
443
+ }, [
444
+ uiState.activeTab,
445
+ projectState.projectsLoaded,
446
+ projectState.projects.length,
447
+ projectState.view,
448
+ projectActions
449
+ ]);
450
+
451
+ /*
452
+ * Fetch agent details with request deduplication
453
+ * Uses module-level cache to prevent multiple requests across component instances
454
+ */
455
+ const apiClientRef = useRef(apiClient);
456
+
457
+ apiClientRef.current = apiClient;
458
+
459
+ // Use the assistant API hook for agent details
460
+ const { agentDetails, isLoadingAgentDetails } = useAssistantApi({
461
+ tenantAiAgentId,
462
+ deploymentId,
463
+ logo,
464
+ title,
465
+ t
466
+ });
467
+
468
+ const fetchThreads = useCallback(async () => {
469
+ await fetchAgentThreads(apiClient, tenantAiAgentId, sessionActions.setSessions, currentUserId, deploymentId);
470
+ }, [
471
+ apiClient,
472
+ tenantAiAgentId,
473
+ sessionActions,
474
+ currentUserId,
475
+ deploymentId
476
+ ]);
477
+
478
+ const fetchProjectThreads = useCallback(async (projectId: string) => {
479
+ await fetchProjectThreadsApi(apiClient, projectId, projectActions.setProjectThreads);
480
+ }, [apiClient, projectActions]);
481
+
482
+ const fetchProjectWorks = useCallback(async (projectId: string) => {
483
+ await fetchProjectWorksApi(apiClient, projectId, projectActions.setProjectWorks, tenantAiAgentId, currentUserId);
484
+ }, [
485
+ apiClient,
486
+ projectActions,
487
+ tenantAiAgentId,
488
+ currentUserId
489
+ ]);
490
+
491
+ const fetchProjects = useCallback(async () => {
492
+ await fetchProjectsApi(
493
+ apiClient,
494
+ configUser?.id || null,
495
+ projectActions.setProjects,
496
+ (project) => {
497
+ fetchProjectThreads(project.id);
498
+ fetchProjectWorks(project.id);
499
+ },
500
+ () => projectActions.setProjectsLoaded(true),
501
+ tenantAiAgentId
502
+ );
503
+ }, [
504
+ apiClient,
505
+ configUser?.id,
506
+ projectActions,
507
+ fetchProjectThreads,
508
+ fetchProjectWorks,
509
+ tenantAiAgentId
510
+ ]);
511
+
512
+ const fetchWorks = useCallback(async () => {
513
+ await fetchWorksApi(apiClient, worksActions.setWorks, tenantAiAgentId, currentUserId);
514
+ }, [
515
+ apiClient,
516
+ worksActions,
517
+ tenantAiAgentId,
518
+ currentUserId
519
+ ]);
520
+
521
+ // Fetch threads from API
522
+ useEffect(() => {
523
+ if (open && (enableSidebar || enableNavDropdown)) {
524
+ fetchThreads();
525
+ }
526
+ }, [
527
+ open,
528
+ enableSidebar,
529
+ enableNavDropdown,
530
+ fetchThreads
531
+ ]);
532
+
533
+ // Fetch projects when projects tab is active
534
+ useEffect(() => {
535
+ if (open && (enableSidebar || enableNavDropdown) && uiState.activeTab === 3) {
536
+ fetchWorks();
537
+ } else if (open && (enableSidebar || enableNavDropdown)) {
538
+ projectActions.setProjectsLoaded(false);
539
+ fetchProjects();
540
+ }
541
+ }, [
542
+ open,
543
+ enableSidebar,
544
+ enableNavDropdown,
545
+ uiState.activeTab,
546
+ projectActions,
547
+ fetchProjects,
548
+ fetchWorks
549
+ ]);
550
+
551
+ // Fetch works when right sidebar opens
552
+ useEffect(() => {
553
+ if (open && uiState.isRightSidebarOpen) {
554
+ fetchWorks();
555
+ }
556
+ }, [open, uiState.isRightSidebarOpen, fetchWorks]);
557
+
558
+ const updateProject = async () => {
559
+ await updateProjectApi(apiClient, projectState, projectActions, fetchProjects, t);
560
+ };
561
+
562
+ const deleteProject = async () => {
563
+ await deleteProjectApi(apiClient, projectState, projectActions, fetchProjects, t);
564
+ };
565
+
566
+ const loadThreadMessages = async (threadId: string) => {
567
+ await loadThreadMessagesApi(apiClient, threadId, setMessages);
568
+ };
569
+
570
+ const handleSessionClick = async (session: AssistantSession) => {
571
+ sessionActions.selectSessionId(session.id);
572
+ sessionActions.selectSession(session);
573
+ uiActions.setActiveTab(0); // Switch to Home tab when a session is clicked
574
+ await loadThreadMessages(session.id);
575
+ };
576
+
577
+ const createNewThread = () => {
578
+ sessionActions.selectSessionId(null);
579
+ sessionActions.selectSession(null);
580
+ uiActions.setActiveTab(0); // Switch to Home tab when creating a new thread
581
+ setMessages([]); // Clear messages for new thread
582
+ };
583
+
584
+ const handleSendMessage = async (e?: FormEvent, options?: any) => {
585
+ e?.preventDefault();
586
+ if (!input.trim()) return;
587
+
588
+ // Store options in ref for access in prepareSendMessagesRequest
589
+ messageOptionsRef.current = options;
590
+
591
+ const messageText = input.trim();
592
+ let currentThreadId = selectedSessionIdRef.current;
593
+
594
+ // If no thread ID exists, create a new one
595
+ if (!currentThreadId) {
596
+ const subject = messageText.substring(0, 100);
597
+
598
+ const newThread = await createThread(apiClient, {
599
+ subject,
600
+ body_text: messageText,
601
+ sender_name: userDisplayName,
602
+ deploymentId,
603
+ tenantAiAgentId,
604
+ projectId: projectState.projectContext?.id
605
+ });
606
+
607
+ if (newThread) {
608
+ currentThreadId = newThread.id;
609
+
610
+ // Update the selected session ID
611
+ sessionActions.selectSessionId(currentThreadId);
612
+ selectedSessionIdRef.current = currentThreadId;
613
+
614
+ // Add the new session to the list
615
+ const session: AssistantSession = {
616
+ id: newThread.id,
617
+ title: newThread.subject || subject,
618
+ messages: [],
619
+ createdAt: new Date(newThread.created_on || new Date()),
620
+ updatedAt: new Date(newThread.last_modified_on || new Date())
621
+ };
622
+
623
+ sessionActions.addSession(session);
624
+
625
+ fetchThreads();
626
+
627
+ if (projectState.projectContext?.id) {
628
+ fetchProjectThreads(projectState.projectContext.id);
629
+ }
630
+ } else {
631
+ return;
632
+ }
633
+ }
634
+
635
+ onMessageSend?.(messageText);
636
+ setInput('');
637
+
638
+ // Upload files to thread storage via REST API
639
+ const rawFiles: { url: string; mediaType: string; filename: string }[] = options?.files || [];
640
+ const filePaths: string[] = [];
641
+
642
+ if (rawFiles.length > 0 && currentThreadId) {
643
+ for (const rawFile of rawFiles) {
644
+ try {
645
+ const file = dataUrlToFile(rawFile.url, rawFile.filename);
646
+ const path = await uploadThreadFile(apiClient, currentThreadId, file);
647
+
648
+ if (path) {
649
+ filePaths.push(path);
650
+ }
651
+ } catch (error) {
652
+ console.error('[FILE_UPLOAD] Error:', error);
653
+ }
654
+ }
655
+ }
656
+
657
+ // Store file paths in ref for prepareSendMessagesRequest
658
+ messageOptionsRef.current = {
659
+ ...options,
660
+ filePaths: filePaths.length > 0 ? filePaths : undefined
661
+ };
662
+
663
+ await sendMessage({
664
+ text: messageText,
665
+ metadata: {
666
+ modelId: options?.modelId,
667
+ supportMultipleModels: options?.supportMultipleModels,
668
+ supportFiles: options?.supportFiles,
669
+ supportWebSearch: options?.supportWebSearch,
670
+ supportDeepResearch: options?.supportDeepResearch,
671
+ supportDocumentSearch: options?.supportDocumentSearch,
672
+ supportThinking: options?.supportThinking,
673
+ supportWorkCanvas: options?.supportWorkCanvas
674
+ }
675
+ });
676
+ };
677
+
678
+ const handleInputChange = useCallback((e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
679
+ setInput(e.target.value);
680
+ }, []);
681
+
682
+ const handleProjectMessage = async (e?: FormEvent) => {
683
+ e?.preventDefault();
684
+ if (!input.trim() || !projectState.selectedProject) return;
685
+
686
+ // Clear everything and switch to home tab
687
+ sessionActions.selectSessionId(null);
688
+ selectedSessionIdRef.current = null;
689
+ setMessages([]);
690
+ uiActions.setActiveTab(0);
691
+
692
+ const originalMessage = input.trim();
693
+
694
+ // Store project context for breadcrumb
695
+ projectActions.setContext({
696
+ name: projectState.selectedProject.name,
697
+ id: projectState.selectedProject.id,
698
+ message: originalMessage
699
+ });
700
+
701
+ // Set input and use existing handleSendMessage logic
702
+ setInput(originalMessage);
703
+
704
+ // Small delay to ensure state updates, then send message
705
+ setTimeout(async () => {
706
+ // Double-check that session is cleared before sending
707
+ sessionActions.selectSessionId(null);
708
+ selectedSessionIdRef.current = null;
709
+ await handleSendMessage();
710
+ }, 200);
711
+ };
712
+
713
+ const handleDockLeft = () => {
714
+ // If in inline mode, switch to modal mode when docking
715
+ if (isInlineMode) {
716
+ uiActions.setRenderMode('modal');
717
+ uiActions.setDocked(true);
718
+ uiActions.setExpanded(false);
719
+
720
+ return;
721
+ }
722
+ // If currently docked and original renderMode was inline, return to inline
723
+ if (uiState.isDocked && renderMode === 'inline') {
724
+ uiActions.setRenderMode('inline');
725
+ uiActions.setDocked(false);
726
+ uiActions.setExpanded(false);
727
+
728
+ return;
729
+ }
730
+ uiActions.setDocked(!uiState.isDocked);
731
+ uiActions.setExpanded(false);
732
+ };
733
+
734
+ const handleExpand = () => {
735
+ // In inline mode, always toggle fullscreen overlay (InlineView handles it via portal)
736
+ if (isInlineMode) {
737
+ setIsInlineFullscreen(prev => !prev);
738
+
739
+ return;
740
+ }
741
+ // If currently expanded and original renderMode was inline, return to inline
742
+ if (uiState.isExpanded && renderMode === 'inline') {
743
+ uiActions.setRenderMode('inline');
744
+ uiActions.setExpanded(false);
745
+ uiActions.setDocked(false);
746
+
747
+ return;
748
+ }
749
+ uiActions.setExpanded(!uiState.isExpanded);
750
+ uiActions.setDocked(false);
751
+ };
752
+
753
+ const handleCreateProject = async (e: FormEvent) => {
754
+ e.preventDefault();
755
+
756
+ if (!projectState.form.name.trim()) return;
757
+
758
+ projectActions.setCreating(true);
759
+
760
+ try {
761
+ const response: any = await apiClient.post('/ai/projects', {
762
+ name: projectState.form.name,
763
+ tenant_ai_agent_id: tenantAiAgentId,
764
+ description: projectState.form.description || undefined
765
+ });
766
+
767
+ if (response.success && response.data) {
768
+ const newProject = response.data;
769
+
770
+ // Set the selected project
771
+ projectActions.selectProject(newProject);
772
+
773
+ // Reset form
774
+ projectActions.setForm({ name: '', description: '' });
775
+
776
+ // Navigate to conversation view
777
+ projectActions.setView('conversation');
778
+
779
+ // Refresh projects list
780
+ fetchProjects();
781
+ }
782
+ } catch (error) {
783
+ console.error('Failed to create project:', error);
784
+ // Could add error handling/toast here
785
+ } finally {
786
+ projectActions.setCreating(false);
787
+ }
788
+ };
789
+
790
+ const updateSession = async () => {
791
+ await updateSessionApi(
792
+ apiClient,
793
+ sessionState,
794
+ sessionActions,
795
+ fetchThreads,
796
+ fetchProjectThreads,
797
+ projectState.projectContext?.id || null,
798
+ projectState.selectedProject?.id || null,
799
+ t
800
+ );
801
+ };
802
+
803
+ const deleteSession = async () => {
804
+ await deleteSessionApi(
805
+ apiClient,
806
+ sessionState,
807
+ sessionActions,
808
+ fetchThreads,
809
+ fetchProjectThreads,
810
+ projectState.projectContext?.id || null,
811
+ projectState.selectedProject?.id || null,
812
+ t
813
+ );
814
+ };
815
+
816
+ const updateProjectInstructions = async () => {
817
+ await updateProjectInstructionsApi(apiClient, projectState, projectActions, fetchProjects, t);
818
+ };
819
+
820
+ // Filter sessions based on search query
821
+ const filteredSessions = sessionState.sessions.filter((session) => {
822
+ if (!sessionState.searchQuery.trim()) return true;
823
+
824
+ const query = sessionState.searchQuery.toLowerCase();
825
+
826
+ return session.title.toLowerCase().includes(query);
827
+ });
828
+
829
+ // Sidebar content render helper
830
+ const renderSidebarContent = () => (
831
+ <SidebarContent
832
+ sessions={sessionState.sessions}
833
+ works={worksState.works}
834
+ projects={projectState.projects}
835
+ selectedSessionId={sessionState.selectedSessionId}
836
+ selectedWorkId={worksState.selectedWork?.id}
837
+ activeTab={uiState.activeTab}
838
+ onNewChat={() => {
839
+ createNewThread();
840
+ projectActions.setContext(null);
841
+ }}
842
+ onTabChange={uiActions.setActiveTab}
843
+ onSessionClick={handleSessionClick}
844
+ onDeleteSession={(session) => {
845
+ sessionActions.selectSession(session);
846
+ sessionActions.setDeleteDialogOpen(true);
847
+ }}
848
+ onWorkClick={async (work) => {
849
+ if (work.base_thread_id) {
850
+ sessionActions.selectSessionId(work.base_thread_id);
851
+ uiActions.setActiveTab(0);
852
+ await loadThreadMessages(work.base_thread_id);
853
+ }
854
+ }}
855
+ onProjectClick={(project) => {
856
+ projectActions.selectProject(project);
857
+ projectActions.setView('conversation');
858
+ uiActions.setActiveTab(2);
859
+ }}
860
+ onShowMoreProjects={() => {
861
+ uiActions.setActiveTab(2);
862
+ projectActions.setView('list');
863
+ }}
864
+ onNewProject={() => {
865
+ uiActions.setActiveTab(2);
866
+ projectActions.setView('create');
867
+ }}
868
+ onToggleSidebar={uiState.isExpanded ? undefined : () => uiActions.setSidebarOpen(false)}
869
+ t={t} />
870
+ );
871
+
872
+ const handleWorkNavigation = (work: Work | null) => {
873
+ worksActions.selectWork(work);
874
+ uiActions.setRightSidebarView(work ? 'detail' : 'list');
875
+ };
876
+
877
+ // Inline mode: render directly without Dialog wrapper
878
+ if (isInlineMode) {
879
+ return (
880
+ <>
881
+ <InlineView
882
+ ref={ref as Ref<HTMLDivElement>}
883
+ open={open}
884
+ onOpenChange={onOpenChange}
885
+ messages={messages}
886
+ isLoading={isLoading}
887
+ input={input}
888
+ onInputChange={handleInputChange}
889
+ onSendMessage={handleSendMessage}
890
+ onStop={stop}
891
+ title={agentDetails?.name ?? title}
892
+ description={description}
893
+ welcomeMessage={agentDetails?.welcomeMessage}
894
+ isLoadingAgent={isLoadingAgentDetails}
895
+ logo={agentDetails?.logo ?? logo}
896
+ placeholder={placeholder}
897
+ footerText={footerText}
898
+ className={className}
899
+ userPhoto={userPhoto}
900
+ userDisplayName={userDisplayName}
901
+ enableSidebar={isInlineFullscreen ? true : enableSidebar}
902
+ supportFiles={supportFiles}
903
+ supportWebSearch={supportWebSearch}
904
+ supportDocumentSearch={supportDocumentSearch}
905
+ supportDeepResearch={supportDeepResearch}
906
+ supportThinking={supportThinking}
907
+ supportWorkCanvas={supportWorkCanvas}
908
+ supportMultiModels={supportMultiModels}
909
+ deploymentId={deploymentId}
910
+ tenantAiAgentId={tenantAiAgentId}
911
+ enableMicrophone={enableMicrophone}
912
+ enableVoice={enableVoice}
913
+ isRecording={isRecording}
914
+ recognition={recognition}
915
+ onMicrophoneClick={handleMicrophoneClick}
916
+ isSidebarOpen={uiState.isSidebarOpen}
917
+ onToggleSidebar={() => uiActions.setSidebarOpen(!uiState.isSidebarOpen)}
918
+ renderSidebar={renderSidebarContent}
919
+ activeTab={uiState.activeTab}
920
+ onTabChange={uiActions.setActiveTab}
921
+ onNewChat={() => {
922
+ createNewThread();
923
+ projectActions.setContext(null);
924
+ }}
925
+ enableNavDropdown={isInlineFullscreen ? false : enableNavDropdown}
926
+ isFullscreen={isInlineFullscreen}
927
+ onExpand={hideExpand ? undefined : handleExpand}
928
+ renderThreadHeader={sessionState.selectedSession && sessionState.selectedSession.id === sessionState.selectedSessionId ? () => (
929
+ <ThreadHeaderInline
930
+ session={sessionState.selectedSession!}
931
+ projects={projectState.projects}
932
+ projectContext={projectState.projectContext}
933
+ isFullscreen={isInlineFullscreen}
934
+ onSaveTitle={async (newTitle) => {
935
+ await updateSessionApi(
936
+ apiClient,
937
+ { ...sessionState, name: newTitle },
938
+ sessionActions,
939
+ fetchThreads,
940
+ fetchProjectThreads,
941
+ projectState.projectContext?.id || null,
942
+ projectState.selectedProject?.id || null,
943
+ t
944
+ );
945
+ sessionActions.updateSession({ ...sessionState.selectedSession!, title: newTitle });
946
+ }}
947
+ onDelete={() => sessionActions.setDeleteDialogOpen(true)}
948
+ onArchive={async () => {
949
+ try {
950
+ await apiClient.patch(
951
+ `/apps/base/data-sources/thread/items/${sessionState.selectedSession!.id}`,
952
+ { archived: true }
953
+ );
954
+ sessionActions.removeSession(sessionState.selectedSession!.id);
955
+ sessionActions.selectSession(null);
956
+ sessionActions.selectSessionId(null);
957
+ toast.success(t('toast.session_archived_success'));
958
+ } catch {
959
+ toast.error(t('toast.failed_archive_session'));
960
+ }
961
+ }}
962
+ onMoveToProject={async (projectId) => {
963
+ try {
964
+ await apiClient.patch(
965
+ `/apps/base/data-sources/thread/items/${sessionState.selectedSession!.id}`,
966
+ { tenant_ai_project_id: projectId }
967
+ );
968
+ sessionActions.removeSession(sessionState.selectedSession!.id);
969
+ sessionActions.selectSession(null);
970
+ sessionActions.selectSessionId(null);
971
+ toast.success(t('toast.session_moved_success'));
972
+ } catch {
973
+ toast.error(t('toast.failed_move_session'));
974
+ }
975
+ }}
976
+ onProjectContextClick={() => {
977
+ const project = projectState.projects.find(p => p.id === projectState.projectContext!.id);
978
+
979
+ if (project) {
980
+ projectActions.selectProject(project);
981
+ projectActions.setView('conversation');
982
+ uiActions.setActiveTab(2);
983
+ projectActions.setContext(null);
984
+ }
985
+ }}
986
+ t={t} />
987
+ ) : undefined}
988
+ renderSessionsView={() => (
989
+ <SessionsListView
990
+ sessions={sessionState.sessions}
991
+ filteredSessions={filteredSessions}
992
+ searchQuery={sessionState.searchQuery}
993
+ onSearchChange={sessionActions.setSearchQuery}
994
+ onNewSession={createNewThread}
995
+ onSessionClick={handleSessionClick}
996
+ onEditSession={(session) => {
997
+ sessionActions.selectSession(session);
998
+ sessionActions.setName(session.title || '');
999
+ sessionActions.startEditing();
1000
+ }}
1001
+ onDeleteSession={(session) => {
1002
+ sessionActions.selectSession(session);
1003
+ sessionActions.setDeleteDialogOpen(true);
1004
+ }}
1005
+ t={t} />
1006
+ )}
1007
+ renderProjectsView={() => (
1008
+ projectState.view === 'create' ? (
1009
+ <div className="flex-1 flex flex-col p-6">
1010
+ <div className="flex items-center gap-4 mb-6">
1011
+ {projectState.projects && projectState.projects.length > 0 && (
1012
+ <Button
1013
+ variant="ghost"
1014
+ size="icon"
1015
+ onClick={() => projectActions.setView('list')}
1016
+ className="text-muted-foreground cursor-pointer hover:text-foreground">
1017
+ <ArrowLeft className="w-5 h-5" />
1018
+ </Button>
1019
+ )}
1020
+ </div>
1021
+ <div className="max-w-lg mx-auto w-full">
1022
+ <h1 className="text-2xl font-semibold mb-4 ps-2">{t('dialogs.create_project')}</h1>
1023
+ <form onSubmit={handleCreateProject} className="grid grid-cols-1 gap-4">
1024
+ <div>
1025
+ <label className="text-sm ps-2 mb-1 block font-base">
1026
+ {t('placeholders.what_are_you_working')}
1027
+ </label>
1028
+ <Input
1029
+ value={projectState.form.name}
1030
+ onChange={(e: ChangeEvent<HTMLInputElement>) => projectActions.setForm({ ...projectState.form, name: e.target.value })}
1031
+ className="border border-border-300 hover:border-border-200 transition-colors font-large h-11 px-3 rounded-[0.6rem] w-full"
1032
+ placeholder={t('placeholders.name_your_project')}
1033
+ required />
1034
+ </div>
1035
+ <div>
1036
+ <label className="text-sm ps-2 mb-1 block font-base">
1037
+ {t('placeholders.what_are_you_trying')}
1038
+ </label>
1039
+ <Textarea
1040
+ value={projectState.form.description}
1041
+ onChange={(e: ChangeEvent<HTMLTextAreaElement>) => projectActions.setForm({ ...projectState.form, description: e.target.value })}
1042
+ className="border border-border-300 min-h-25 resize-none p-2 hover:border-border-200 transition-colors font-large px-3 rounded-[0.6rem] w-full"
1043
+ placeholder={t('placeholders.describe_project')} />
1044
+ </div>
1045
+ <div className="mt-2 flex justify-end gap-2">
1046
+ <Button
1047
+ type="button"
1048
+ variant="outline"
1049
+ onClick={() => projectActions.setView('list')}>
1050
+ {t('buttons.cancel')}
1051
+ </Button>
1052
+ <Button
1053
+ type="submit"
1054
+ disabled={projectState.isCreating || !projectState.form.name.trim()}>
1055
+ {projectState.isCreating ? t('buttons.creating') : t('buttons.create_project')}
1056
+ </Button>
1057
+ </div>
1058
+ </form>
1059
+ </div>
1060
+ </div>
1061
+ ) : projectState.view === 'conversation' && projectState.selectedProject ? (
1062
+ <AssistantProjectDetailView
1063
+ project={projectState.selectedProject}
1064
+ projectThreads={projectState.projectThreads}
1065
+ projectWorks={projectState.projectWorks}
1066
+ onBack={() => projectActions.setView('list')}
1067
+ onEditInstructions={(instructions) => {
1068
+ projectActions.setInstructions(instructions);
1069
+ projectActions.startEditingInstructions();
1070
+ }}
1071
+ onProjectMessage={handleProjectMessage}
1072
+ onThreadClick={(thread) => {
1073
+ sessionActions.selectSessionId(thread.id);
1074
+ sessionActions.selectSession(thread);
1075
+ projectActions.setContext({
1076
+ name: projectState.selectedProject!.name,
1077
+ id: projectState.selectedProject!.id,
1078
+ message: thread.title || 'Untitled'
1079
+ });
1080
+ loadThreadMessages(thread.id);
1081
+ uiActions.setActiveTab(0);
1082
+ }}
1083
+ onEditSession={(thread) => {
1084
+ sessionActions.selectSession(thread);
1085
+ sessionActions.setName(thread.title || '');
1086
+ sessionActions.startEditing();
1087
+ }}
1088
+ onDeleteSession={(thread) => {
1089
+ sessionActions.selectSession(thread);
1090
+ sessionActions.setDeleteDialogOpen(true);
1091
+ }}
1092
+ input={input}
1093
+ onChange={handleInputChange}
1094
+ isLoading={isLoading}
1095
+ supportFiles={supportFiles}
1096
+ supportWebSearch={supportWebSearch}
1097
+ supportDocumentSearch={supportDocumentSearch}
1098
+ supportDeepResearch={supportDeepResearch}
1099
+ supportThinking={supportThinking}
1100
+ supportWorkCanvas={supportWorkCanvas}
1101
+ supportMultiModels={supportMultiModels}
1102
+ deploymentId={deploymentId}
1103
+ enableMicrophone={enableMicrophone}
1104
+ enableVoice={enableVoice}
1105
+ isRecording={isRecording}
1106
+ recognition={recognition}
1107
+ onMicrophoneClick={handleMicrophoneClick} />
1108
+ ) : (
1109
+ <AssistantProjectsPanel
1110
+ projects={projectState.projects}
1111
+ searchQuery={projectSearchQuery}
1112
+ onSearchChange={setProjectSearchQuery}
1113
+ onNewProject={() => projectActions.setView('create')}
1114
+ onProjectClick={(project) => {
1115
+ projectActions.selectProject(project);
1116
+ projectActions.setView('conversation');
1117
+ }}
1118
+ onEditProject={(project) => {
1119
+ projectActions.selectProject(project);
1120
+ projectActions.setName(project.name || '');
1121
+ projectActions.setDescription(project.description || '');
1122
+ projectActions.startEditing();
1123
+ }}
1124
+ onDeleteProject={(project) => {
1125
+ projectActions.selectProject(project);
1126
+ projectActions.setDeleteDialogOpen(true);
1127
+ }}
1128
+ compact={enableNavDropdown}
1129
+ t={t} />
1130
+ )
1131
+ )}
1132
+ renderMemoriesView={() => <MemoriesPanel t={t} />}
1133
+ renderWorksView={() => (
1134
+ worksState.detailViewWork ? (
1135
+ <div className="flex-1 flex flex-col overflow-y-auto">
1136
+ <AssistantWorkDetailView
1137
+ work={worksState.detailViewWork}
1138
+ onClose={() => worksActions.setDetailViewWork(null)}
1139
+ onBack={() => worksActions.setDetailViewWork(null)} />
1140
+ </div>
1141
+ ) : (
1142
+ <div className="flex-1 flex flex-col overflow-y-auto p-6">
1143
+ {/* Header */}
1144
+ <div className="flex items-center justify-between mb-6">
1145
+ <div className="flex flex-col min-w-0">
1146
+ <h1 className="text-2xl font-semibold ps-2">{t('tabs.works')}</h1>
1147
+ <div className="text-sm text-muted-foreground ps-2">{t('descriptions.manage_works')}</div>
1148
+ </div>
1149
+ <Button size="sm" className="shrink-0">
1150
+ <Plus className="w-4 h-4" />
1151
+ {t('buttons.new_work')}
1152
+ </Button>
1153
+ </div>
1154
+
1155
+ {/* Search */}
1156
+ <div className="flex items-center gap-4 mb-6">
1157
+ <div className="relative flex-1">
1158
+ <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4" />
1159
+ <Input
1160
+ type="text"
1161
+ placeholder={t('placeholders.search_works')}
1162
+ value={worksState.searchQuery}
1163
+ onChange={(e: ChangeEvent<HTMLInputElement>) => worksActions.setSearchQuery(e.target.value)}
1164
+ className="pl-10 pr-4 py-2 w-full border border-border rounded-lg focus:outline-none" />
1165
+ </div>
1166
+ </div>
1167
+
1168
+ {/* Works Tabs */}
1169
+ <Tabs value={worksState.tabValue} onValueChange={worksActions.setTabValue}>
1170
+ <TabsList className="mb-4 p-1">
1171
+ <TabsTrigger className="py-2" value="1">
1172
+ {t('tabs.my_works')}
1173
+ </TabsTrigger>
1174
+ <TabsTrigger className="py-2" value="2">
1175
+ {t('tabs.shared_to_me')}
1176
+ </TabsTrigger>
1177
+ <TabsTrigger className="py-2" value="3">
1178
+ {t('tabs.library')}
1179
+ </TabsTrigger>
1180
+ </TabsList>
1181
+ <TabsContent value="1">
1182
+ {worksState.works.length === 0 ? (
1183
+ <div className="text-center py-8 text-muted-foreground">{t('messages.no_works_start')}</div>
1184
+ ) : (
1185
+ <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
1186
+ {worksState.works
1187
+ .filter(
1188
+ work => work.title.toLowerCase().includes(worksState.searchQuery.toLowerCase())
1189
+ || work.content_text?.toLowerCase().includes(worksState.searchQuery.toLowerCase())
1190
+ )
1191
+ .map(work => (
1192
+ <WorkCard
1193
+ key={work.id}
1194
+ work={work}
1195
+ onClick={async () => {
1196
+ if (work.base_thread_id) {
1197
+ sessionActions.selectSessionId(work.base_thread_id);
1198
+ uiActions.setActiveTab(0);
1199
+ await loadThreadMessages(work.base_thread_id);
1200
+ }
1201
+ }}
1202
+ t={t} />
1203
+ ))}
1204
+ </div>
1205
+ )}
1206
+ </TabsContent>
1207
+ <TabsContent value="2">
1208
+ {(() => {
1209
+ const sharedWorks = worksState.works.filter(
1210
+ work => work.shared_to
1211
+ && Array.isArray(work.shared_to)
1212
+ && currentUserId
1213
+ && work.shared_to.includes(currentUserId)
1214
+ );
1215
+
1216
+ return sharedWorks.length === 0 ? (
1217
+ <div className="text-center py-8 text-muted-foreground">
1218
+ {t('messages.no_works_shared')}
1219
+ </div>
1220
+ ) : (
1221
+ <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
1222
+ {worksState.works
1223
+ .filter((work) => {
1224
+ const isSharedToMe
1225
+ = work.shared_to
1226
+ && Array.isArray(work.shared_to)
1227
+ && currentUserId
1228
+ && work.shared_to.includes(currentUserId);
1229
+ const matchesSearch
1230
+ = work.title.toLowerCase().includes(worksState.searchQuery.toLowerCase())
1231
+ || work.content_text?.toLowerCase().includes(worksState.searchQuery.toLowerCase());
1232
+
1233
+ return isSharedToMe && matchesSearch;
1234
+ })
1235
+ .map(work => (
1236
+ <WorkCard
1237
+ key={work.id}
1238
+ work={work}
1239
+ onClick={async () => {
1240
+ if (work.base_thread_id) {
1241
+ sessionActions.selectSessionId(work.base_thread_id);
1242
+ uiActions.setActiveTab(0);
1243
+ await loadThreadMessages(work.base_thread_id);
1244
+ }
1245
+ }}
1246
+ t={t} />
1247
+ ))}
1248
+ </div>
1249
+ );
1250
+ })()}
1251
+ </TabsContent>
1252
+ <TabsContent value="3">
1253
+ {worksState.works.length === 0 ? (
1254
+ <div className="text-center py-8 text-muted-foreground">{t('messages.no_works_found')}</div>
1255
+ ) : (
1256
+ <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
1257
+ {worksState.works
1258
+ .filter(
1259
+ work => work.title.toLowerCase().includes(worksState.searchQuery.toLowerCase())
1260
+ || work.content_text?.toLowerCase().includes(worksState.searchQuery.toLowerCase())
1261
+ )
1262
+ .map(work => (
1263
+ <WorkCard
1264
+ key={work.id}
1265
+ work={work}
1266
+ onClick={() => worksActions.setDetailViewWork(work)}
1267
+ t={t} />
1268
+ ))}
1269
+ </div>
1270
+ )}
1271
+ </TabsContent>
1272
+ </Tabs>
1273
+ </div>
1274
+ )
1275
+ )}
1276
+ onToolAction={handleToolAction}
1277
+ threadId={sessionState.selectedSessionId ?? undefined}
1278
+ agentSelectorUrl={agentSelectorUrl}
1279
+ baseAgentSelectorUrl={baseAgentSelectorUrl}
1280
+ onAgentChange={(agentId, agentType) => {
1281
+ createNewThread();
1282
+ onAgentChange?.(agentId, agentType);
1283
+ }}
1284
+ t={t} />
1285
+ <AssistantDialogs
1286
+ projectState={projectState}
1287
+ projectActions={projectActions}
1288
+ sessionState={sessionState}
1289
+ sessionActions={sessionActions}
1290
+ onUpdateProject={updateProject}
1291
+ onDeleteProject={deleteProject}
1292
+ onUpdateSession={updateSession}
1293
+ onDeleteSession={deleteSession}
1294
+ onUpdateProjectInstructions={updateProjectInstructions}
1295
+ t={t} />
1296
+ </>
1297
+ );
1298
+ }
1299
+
1300
+ // Modal mode: render with Dialog wrapper (original implementation)
1301
+ return (
1302
+ <>
1303
+ <Dialog open={open} onOpenChange={onOpenChange}>
1304
+ <DialogTitle className="sr-only">DocyAssistant</DialogTitle>
1305
+ <DialogContent
1306
+ className={cn(getDialogClassName({ isExpanded: uiState.isExpanded, isDocked: uiState.isDocked }), className, '@container')}
1307
+ data-docked={uiState.isDocked}
1308
+ data-expanded={uiState.isExpanded}
1309
+ data-container-query="true"
1310
+ showCloseButton={!uiState.isRightSidebarOpen}
1311
+ style={{
1312
+ position: 'fixed',
1313
+ ...(uiState.isExpanded && {
1314
+ top: 0,
1315
+ left: 0,
1316
+ right: 0,
1317
+ bottom: 0,
1318
+ width: '100vw',
1319
+ height: '100vh',
1320
+ transform: 'none',
1321
+ maxWidth: 'none',
1322
+ maxHeight: 'none'
1323
+ }),
1324
+ ...(uiState.isDocked && {
1325
+ top: 0,
1326
+ left: 0,
1327
+ width: '700px',
1328
+ height: '100vh',
1329
+ transform: 'none',
1330
+ maxWidth: 'none',
1331
+ maxHeight: 'none'
1332
+ })
1333
+ }}
1334
+ {...props}
1335
+ ref={ref}>
1336
+ {/* Floating Sidebar with Overlay - shown only when container < 3xl */}
1337
+ {enableSidebar && uiState.isSidebarOpen && (
1338
+ <>
1339
+ {/* Background Mask */}
1340
+ <div
1341
+ className={cn(
1342
+ 'absolute inset-0 bg-black/30 z-99',
1343
+ // Show only when container is smaller than 3xl
1344
+ '@3xl:hidden'
1345
+ )}
1346
+ onClick={() => uiActions.setSidebarOpen(false)} />
1347
+
1348
+ {/* Floating Sidebar */}
1349
+ <div
1350
+ className={cn(
1351
+ 'absolute left-0 top-0 h-full w-72 rounded-lg z-100 shadow-xl',
1352
+ // Show floating sidebar when container is smaller than 3xl
1353
+ '@3xl:hidden'
1354
+ )}
1355
+ onMouseLeave={() => {
1356
+ // Auto-close when mouse leaves sidebar (not in fullscreen/expanded mode)
1357
+ if (uiState.isExpanded) return;
1358
+ if (sidebarCloseTimeoutRef.current) {
1359
+ clearTimeout(sidebarCloseTimeoutRef.current);
1360
+ }
1361
+ sidebarCloseTimeoutRef.current = setTimeout(() => {
1362
+ uiActions.setSidebarOpen(false);
1363
+ }, 300);
1364
+ }}
1365
+ onMouseEnter={() => {
1366
+ // Clear any pending close timeout when mouse enters
1367
+ if (sidebarCloseTimeoutRef.current) {
1368
+ clearTimeout(sidebarCloseTimeoutRef.current);
1369
+ sidebarCloseTimeoutRef.current = null;
1370
+ }
1371
+ }}>
1372
+ <div className="w-full h-full flex flex-col">
1373
+ {/* Header with Close Button */}
1374
+ <div className="flex items-center justify-end h-10 px-2 border-b">
1375
+ {/* Close Button */}
1376
+ <Button
1377
+ variant="ghost"
1378
+ size="icon"
1379
+ onClick={() => uiActions.setSidebarOpen(false)}
1380
+ className="h-8 w-8 text-muted-foreground hover:text-foreground">
1381
+ <X className="w-4 h-4" />
1382
+ </Button>
1383
+ </div>
1384
+
1385
+ <div className="flex-1 p-4 flex flex-col min-h-0">
1386
+ {renderSidebarContent()}
1387
+ </div>
1388
+ </div>
1389
+ </div>
1390
+ </>
1391
+ )}
1392
+
1393
+ <div className={getContainerClassName({ isExpanded: uiState.isExpanded, isDocked: uiState.isDocked })}>
1394
+ {/* Top Left Controls - Sidebar Toggle & New Thread */}
1395
+ {enableSidebar && (
1396
+ <div className="absolute z-10 top-2.5 left-3 flex items-center gap-2">
1397
+ {/* Sidebar Toggle */}
1398
+ <Button
1399
+ variant="ghost"
1400
+ size="icon"
1401
+ onClick={() => uiActions.setSidebarOpen(!uiState.isSidebarOpen)}
1402
+ data-sidebar-open={uiState.isSidebarOpen}
1403
+ className={cn(
1404
+ 'h-8 w-8 text-muted-foreground hover:bg-accent hover:text-accent-foreground rounded-md transition-all duration-300',
1405
+ // Hide button when sidebar is open
1406
+ uiState.isSidebarOpen && 'opacity-0 pointer-events-none'
1407
+ )}>
1408
+ <PanelLeft
1409
+ className={cn(
1410
+ 'w-5 h-5 transition-transform duration-300',
1411
+ uiState.isSidebarOpen && 'scale-x-[-1]'
1412
+ )} />
1413
+ </Button>
1414
+
1415
+ {/* New Thread Button */}
1416
+ {!uiState.isSidebarOpen && (
1417
+ <Button
1418
+ variant="ghost"
1419
+ size="icon"
1420
+ onClick={createNewThread}
1421
+ className="h-8 w-8 text-muted-foreground hover:bg-accent hover:text-accent-foreground rounded-md transition-all duration-300"
1422
+ title={t('buttons.start_new_thread')}>
1423
+ <Edit className="w-5 h-5" />
1424
+ </Button>
1425
+ )}
1426
+ </div>
1427
+ )}
1428
+
1429
+ {/* Top Right Controls - Dock & Expand */}
1430
+ {!uiState.isRightSidebarOpen && (
1431
+ <div className="absolute top-2 right-12 flex items-center gap-2 z-50">
1432
+ {!uiState.isExpanded && (
1433
+ <Button
1434
+ variant="ghost"
1435
+ size="icon"
1436
+ className="h-8 w-8 text-muted-foreground hover:bg-accent hover:text-accent-foreground rounded-md transition-colors"
1437
+ onClick={handleDockLeft}
1438
+ title={uiState.isDocked ? t('actions.undock') : t('actions.dock_left')}>
1439
+ {uiState.isDocked ? <ArrowRight className="w-5 h-5" /> : <ArrowLeft className="w-5 h-5" />}
1440
+ </Button>
1441
+ )}
1442
+
1443
+ {!uiState.isDocked && (
1444
+ <Button
1445
+ variant="ghost"
1446
+ size="icon"
1447
+ className="h-8 w-8 text-muted-foreground hover:bg-accent hover:text-accent-foreground rounded-md transition-colors"
1448
+ onClick={handleExpand}
1449
+ title={uiState.isExpanded ? t('actions.minimize') : t('actions.expand')}>
1450
+ {uiState.isExpanded ? <Minimize2 className="w-5 h-5" /> : <Maximize2 className="w-5 h-5" />}
1451
+ </Button>
1452
+ )}
1453
+ </div>
1454
+ )}
1455
+
1456
+ {/* Inline Sidebar - shown only when container >= 3xl */}
1457
+ {enableSidebar && (
1458
+ <div
1459
+ className={cn(
1460
+ 'relative flex-none transition-all duration-300',
1461
+ // Show inline sidebar when container is 3xl or larger (48rem/768px)
1462
+ 'hidden @3xl:block',
1463
+ uiState.isSidebarOpen ? 'w-72' : 'w-0 overflow-hidden'
1464
+ )}>
1465
+ <div className="w-72 h-full flex flex-col">
1466
+ <div className="flex-1 p-4 flex flex-col min-h-0">
1467
+ {renderSidebarContent()}
1468
+ </div>
1469
+ </div>
1470
+ </div>
1471
+ )}
1472
+
1473
+ {/* Main Content Area */}
1474
+ <div className={cn('flex-1 flex flex-col bg-background', uiState.isDocked ? 'min-w-0' : 'min-w-96')}>
1475
+ {/* Conditionally render content based on activeTab */}
1476
+ {uiState.activeTab === 1 ? (
1477
+ <SessionsListView
1478
+ sessions={sessionState.sessions}
1479
+ filteredSessions={filteredSessions}
1480
+ searchQuery={sessionState.searchQuery}
1481
+ onSearchChange={sessionActions.setSearchQuery}
1482
+ onNewSession={createNewThread}
1483
+ onSessionClick={handleSessionClick}
1484
+ onEditSession={(session) => {
1485
+ sessionActions.selectSession(session);
1486
+ sessionActions.setName(session.title || '');
1487
+ sessionActions.startEditing();
1488
+ }}
1489
+ onDeleteSession={(session) => {
1490
+ sessionActions.selectSession(session);
1491
+ sessionActions.setDeleteDialogOpen(true);
1492
+ }}
1493
+ t={t} />
1494
+ ) : uiState.activeTab === 2 ? (
1495
+ /* Projects View */
1496
+ projectState.view === 'list' ? (
1497
+ <AssistantProjectsPanel
1498
+ projects={projectState.projects}
1499
+ searchQuery={projectSearchQuery}
1500
+ onSearchChange={setProjectSearchQuery}
1501
+ onNewProject={() => projectActions.setView('create')}
1502
+ onProjectClick={(project) => {
1503
+ projectActions.selectProject(project);
1504
+ projectActions.setView('conversation');
1505
+ }}
1506
+ onEditProject={(project) => {
1507
+ projectActions.selectProject(project);
1508
+ projectActions.setName(project.name || '');
1509
+ projectActions.setDescription(project.description || '');
1510
+ projectActions.startEditing();
1511
+ }}
1512
+ onDeleteProject={(project) => {
1513
+ projectActions.selectProject(project);
1514
+ projectActions.setDeleteDialogOpen(true);
1515
+ }}
1516
+ compact={enableNavDropdown}
1517
+ t={t} />
1518
+ ) : projectState.view === 'create' ? (
1519
+ /* Create Project Form */
1520
+ <div className="flex-1 flex flex-col p-6">
1521
+ {/* Header with Back Button */}
1522
+ <div className="flex items-center gap-4 mb-6">
1523
+ {projectState.projects && projectState.projects.length > 0 && (
1524
+ <Button
1525
+ variant="ghost"
1526
+ size="icon"
1527
+ onClick={() => projectActions.setView('list')}
1528
+ className="text-muted-foreground cursor-pointer hover:text-foreground">
1529
+ <ArrowLeft className="w-5 h-5" />
1530
+ </Button>
1531
+ )}
1532
+ </div>
1533
+
1534
+ <div className="max-w-lg mx-auto w-full">
1535
+ <h1 className="text-2xl font-semibold mb-4 ps-2">{t('dialogs.create_project')}</h1>
1536
+ <form onSubmit={handleCreateProject} className="grid grid-cols-1 gap-4">
1537
+ <div>
1538
+ <label className="text-sm ps-2 mb-1 block font-base">
1539
+ {t('placeholders.what_are_you_working')}
1540
+ </label>
1541
+ <Input
1542
+ value={projectState.form.name}
1543
+ onChange={(e: ChangeEvent<HTMLInputElement>) => projectActions.setForm({ ...projectState.form, name: e.target.value })}
1544
+ className="border border-border-300 hover:border-border-200 transition-colors font-large h-11 px-3 rounded-[0.6rem] w-full"
1545
+ placeholder={t('placeholders.name_your_project')}
1546
+ required />
1547
+ </div>
1548
+
1549
+ <div>
1550
+ <label className="text-sm ps-2 mb-1 block font-base">
1551
+ {t('placeholders.what_are_you_trying')}
1552
+ </label>
1553
+ <Textarea
1554
+ value={projectState.form.description}
1555
+ onChange={(e: ChangeEvent<HTMLTextAreaElement>) => projectActions.setForm({ ...projectState.form, description: e.target.value })}
1556
+ className="border border-border-300 min-h-25 resize-none p-2 hover:border-border-200 transition-colors font-large px-3 rounded-[0.6rem] w-full"
1557
+ placeholder={t('placeholders.describe_project')} />
1558
+ </div>
1559
+
1560
+ <div className="mt-2 flex justify-end gap-2">
1561
+ <Button
1562
+ type="button"
1563
+ variant="outline"
1564
+ onClick={() => projectActions.setView('list')}>
1565
+ {t('buttons.cancel')}
1566
+ </Button>
1567
+ <Button
1568
+ type="submit"
1569
+ disabled={projectState.isCreating || !projectState.form.name.trim()}>
1570
+ {projectState.isCreating ? t('buttons.creating') : t('buttons.create_project')}
1571
+ </Button>
1572
+ </div>
1573
+ </form>
1574
+ </div>
1575
+ </div>
1576
+ ) : (
1577
+ /* Project Conversation View */
1578
+ projectState.selectedProject && (
1579
+ <AssistantProjectDetailView
1580
+ project={projectState.selectedProject}
1581
+ projectThreads={projectState.projectThreads}
1582
+ projectWorks={projectState.projectWorks}
1583
+ onBack={() => projectActions.setView('list')}
1584
+ onEditInstructions={(instructions) => {
1585
+ projectActions.setInstructions(instructions);
1586
+ projectActions.startEditingInstructions();
1587
+ }}
1588
+ onProjectMessage={handleProjectMessage}
1589
+ onThreadClick={(thread) => {
1590
+ sessionActions.selectSessionId(thread.id);
1591
+ sessionActions.selectSession(thread);
1592
+ projectActions.setContext({
1593
+ name: projectState.selectedProject!.name,
1594
+ id: projectState.selectedProject!.id,
1595
+ message: thread.title || 'Untitled'
1596
+ });
1597
+ loadThreadMessages(thread.id);
1598
+ uiActions.setActiveTab(0); // Switch to Home tab
1599
+ }}
1600
+ onEditSession={(thread) => {
1601
+ sessionActions.selectSession(thread);
1602
+ sessionActions.setName(thread.title || '');
1603
+ sessionActions.startEditing();
1604
+ }}
1605
+ onDeleteSession={(thread) => {
1606
+ sessionActions.selectSession(thread);
1607
+ sessionActions.setDeleteDialogOpen(true);
1608
+ }}
1609
+ input={input}
1610
+ onChange={handleInputChange}
1611
+ isLoading={isLoading}
1612
+ supportFiles={supportFiles}
1613
+ supportWebSearch={supportWebSearch}
1614
+ supportDocumentSearch={supportDocumentSearch}
1615
+ supportDeepResearch={supportDeepResearch}
1616
+ supportThinking={supportThinking}
1617
+ supportWorkCanvas={supportWorkCanvas}
1618
+ supportMultiModels={supportMultiModels}
1619
+ deploymentId={deploymentId}
1620
+ enableMicrophone={enableMicrophone}
1621
+ enableVoice={enableVoice}
1622
+ isRecording={isRecording}
1623
+ recognition={recognition}
1624
+ onMicrophoneClick={handleMicrophoneClick} />
1625
+ )
1626
+ )
1627
+ ) : uiState.activeTab === 3 ? (
1628
+ /* Works View */
1629
+ worksState.detailViewWork ? (
1630
+ /* Work Detail View */
1631
+ <div className="flex-1 flex flex-col overflow-y-auto">
1632
+ <AssistantWorkDetailView
1633
+ work={worksState.detailViewWork}
1634
+ onClose={() => worksActions.setDetailViewWork(null)}
1635
+ onBack={() => worksActions.setDetailViewWork(null)} />
1636
+ </div>
1637
+ ) : (
1638
+ <div className="flex-1 flex flex-col overflow-y-auto p-6">
1639
+ {/* Header */}
1640
+ <div className="flex items-center justify-between mb-6">
1641
+ <div className="flex flex-col min-w-0">
1642
+ <h1 className="text-2xl font-semibold ps-2">{t('tabs.works')}</h1>
1643
+ <div className="text-sm text-muted-foreground ps-2">{t('descriptions.manage_works')}</div>
1644
+ </div>
1645
+ <Button size="sm" className="shrink-0">
1646
+ <Plus className="w-4 h-4" />
1647
+ {t('buttons.new_work')}
1648
+ </Button>
1649
+ </div>
1650
+
1651
+ {/* Search */}
1652
+ <div className="flex items-center gap-4 mb-6">
1653
+ <div className="relative flex-1">
1654
+ <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4" />
1655
+ <Input
1656
+ type="text"
1657
+ placeholder={t('placeholders.search_works')}
1658
+ value={worksState.searchQuery}
1659
+ onChange={(e: ChangeEvent<HTMLInputElement>) => worksActions.setSearchQuery(e.target.value)}
1660
+ className="pl-10 pr-4 py-2 w-full border border-border rounded-lg focus:outline-none" />
1661
+ </div>
1662
+ </div>
1663
+
1664
+ {/* Works Tabs */}
1665
+ <Tabs value={worksState.tabValue} onValueChange={worksActions.setTabValue}>
1666
+ <TabsList className="mb-4 p-1">
1667
+ <TabsTrigger className="py-2" value="1">
1668
+ {t('tabs.my_works')}
1669
+ </TabsTrigger>
1670
+ <TabsTrigger className="py-2" value="2">
1671
+ {t('tabs.shared_to_me')}
1672
+ </TabsTrigger>
1673
+ <TabsTrigger className="py-2" value="3">
1674
+ {t('tabs.library')}
1675
+ </TabsTrigger>
1676
+ </TabsList>
1677
+ <TabsContent value="1">
1678
+ {worksState.works.length === 0 ? (
1679
+ <div className="text-center py-8 text-muted-foreground">{t('messages.no_works_start')}</div>
1680
+ ) : (
1681
+ <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
1682
+ {worksState.works
1683
+ .filter(
1684
+ work => work.title.toLowerCase().includes(worksState.searchQuery.toLowerCase())
1685
+ || work.content_text?.toLowerCase().includes(worksState.searchQuery.toLowerCase())
1686
+ )
1687
+ .map(work => (
1688
+ <WorkCard
1689
+ key={work.id}
1690
+ work={work}
1691
+ onClick={async () => {
1692
+ if (work.base_thread_id) {
1693
+ sessionActions.selectSessionId(work.base_thread_id);
1694
+ uiActions.setActiveTab(0);
1695
+ await loadThreadMessages(work.base_thread_id);
1696
+ }
1697
+ }}
1698
+ t={t} />
1699
+ ))}
1700
+ </div>
1701
+ )}
1702
+ </TabsContent>
1703
+ <TabsContent value="2">
1704
+ {(() => {
1705
+ const sharedWorks = worksState.works.filter(
1706
+ work => work.shared_to
1707
+ && Array.isArray(work.shared_to)
1708
+ && currentUserId
1709
+ && work.shared_to.includes(currentUserId)
1710
+ );
1711
+
1712
+ return sharedWorks.length === 0 ? (
1713
+ <div className="text-center py-8 text-muted-foreground">
1714
+ {t('messages.no_works_shared')}
1715
+ </div>
1716
+ ) : (
1717
+ <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
1718
+ {worksState.works
1719
+ .filter((work) => {
1720
+ // Filter by shared_to field containing current user ID
1721
+ const isSharedToMe
1722
+ = work.shared_to
1723
+ && Array.isArray(work.shared_to)
1724
+ && currentUserId
1725
+ && work.shared_to.includes(currentUserId);
1726
+ // Also filter by search query
1727
+ const matchesSearch
1728
+ = work.title.toLowerCase().includes(worksState.searchQuery.toLowerCase())
1729
+ || work.content_text?.toLowerCase().includes(worksState.searchQuery.toLowerCase());
1730
+
1731
+ return isSharedToMe && matchesSearch;
1732
+ })
1733
+ .map(work => (
1734
+ <WorkCard
1735
+ key={work.id}
1736
+ work={work}
1737
+ onClick={async () => {
1738
+ if (work.base_thread_id) {
1739
+ sessionActions.selectSessionId(work.base_thread_id);
1740
+ uiActions.setActiveTab(0);
1741
+ await loadThreadMessages(work.base_thread_id);
1742
+ }
1743
+ }}
1744
+ t={t} />
1745
+ ))}
1746
+ </div>
1747
+ );
1748
+ })()}
1749
+ </TabsContent>
1750
+ <TabsContent value="3">
1751
+ {worksState.works.length === 0 ? (
1752
+ <div className="text-center py-8 text-muted-foreground">{t('messages.no_works_found')}</div>
1753
+ ) : (
1754
+ <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
1755
+ {worksState.works
1756
+ .filter(
1757
+ work => work.title.toLowerCase().includes(worksState.searchQuery.toLowerCase())
1758
+ || work.content_text?.toLowerCase().includes(worksState.searchQuery.toLowerCase())
1759
+ )
1760
+ .map(work => (
1761
+ <WorkCard
1762
+ key={work.id}
1763
+ work={work}
1764
+ onClick={() => worksActions.setDetailViewWork(work)}
1765
+ t={t} />
1766
+ ))}
1767
+ </div>
1768
+ )}
1769
+ </TabsContent>
1770
+ </Tabs>
1771
+ </div>
1772
+ )
1773
+ ) : uiState.activeTab === 4 ? (
1774
+ /* Memories View */
1775
+ <MemoriesPanel t={t} />
1776
+ ) : (
1777
+ <>
1778
+ {sessionState.selectedSession && sessionState.selectedSession.id === sessionState.selectedSessionId && (
1779
+ <ThreadHeaderInline
1780
+ session={sessionState.selectedSession}
1781
+ projects={projectState.projects}
1782
+ projectContext={projectState.projectContext}
1783
+ onSaveTitle={async (newTitle) => {
1784
+ await updateSessionApi(
1785
+ apiClient,
1786
+ { ...sessionState, name: newTitle },
1787
+ sessionActions,
1788
+ fetchThreads,
1789
+ fetchProjectThreads,
1790
+ projectState.projectContext?.id || null,
1791
+ projectState.selectedProject?.id || null,
1792
+ t
1793
+ );
1794
+ sessionActions.updateSession({ ...sessionState.selectedSession!, title: newTitle });
1795
+ }}
1796
+ onDelete={() => sessionActions.setDeleteDialogOpen(true)}
1797
+ onArchive={async () => {
1798
+ try {
1799
+ await apiClient.patch(
1800
+ `/apps/base/data-sources/thread/items/${sessionState.selectedSession!.id}`,
1801
+ { archived: true }
1802
+ );
1803
+ sessionActions.removeSession(sessionState.selectedSession!.id);
1804
+ sessionActions.selectSession(null);
1805
+ sessionActions.selectSessionId(null);
1806
+ toast.success(t('toast.session_archived_success'));
1807
+ } catch {
1808
+ toast.error(t('toast.failed_archive_session'));
1809
+ }
1810
+ }}
1811
+ onMoveToProject={async (projectId) => {
1812
+ try {
1813
+ await apiClient.patch(
1814
+ `/apps/base/data-sources/thread/items/${sessionState.selectedSession!.id}`,
1815
+ { tenant_ai_project_id: projectId }
1816
+ );
1817
+ sessionActions.removeSession(sessionState.selectedSession!.id);
1818
+ sessionActions.selectSession(null);
1819
+ sessionActions.selectSessionId(null);
1820
+ toast.success(t('toast.session_moved_success'));
1821
+ } catch {
1822
+ toast.error(t('toast.failed_move_session'));
1823
+ }
1824
+ }}
1825
+ onProjectContextClick={() => {
1826
+ const project = projectState.projects.find(p => p.id === projectState.projectContext!.id);
1827
+
1828
+ if (project) {
1829
+ projectActions.selectProject(project);
1830
+ projectActions.setView('conversation');
1831
+ uiActions.setActiveTab(2);
1832
+ projectActions.setContext(null);
1833
+ }
1834
+ }}
1835
+ t={t} />
1836
+ )}
1837
+ <ConversationView
1838
+ messages={messages}
1839
+ isLoading={isLoading}
1840
+ input={input}
1841
+ onInputChange={handleInputChange}
1842
+ onSendMessage={handleSendMessage}
1843
+ onStop={stop}
1844
+ logo={logo}
1845
+ userPhoto={userPhoto}
1846
+ userDisplayName={userDisplayName}
1847
+ description={description}
1848
+ title={agentDetails?.name ?? title}
1849
+ welcomeMessage={agentDetails?.welcomeMessage}
1850
+ isLoadingAgent={isLoadingAgentDetails}
1851
+ placeholder={placeholder}
1852
+ footerText={footerText}
1853
+ onWorkSelect={(work) => {
1854
+ worksActions.updateWork(work);
1855
+ worksActions.selectWork(work);
1856
+ }}
1857
+ onOpenRightSidebar={() => {
1858
+ uiActions.setRightSidebarOpen(true);
1859
+ uiActions.setRightSidebarView('detail');
1860
+ }}
1861
+ supportFiles={supportFiles}
1862
+ supportWebSearch={supportWebSearch}
1863
+ supportDocumentSearch={supportDocumentSearch}
1864
+ supportDeepResearch={supportDeepResearch}
1865
+ supportThinking={supportThinking}
1866
+ supportWorkCanvas={supportWorkCanvas}
1867
+ supportMultiModels={supportMultiModels}
1868
+ deploymentId={deploymentId}
1869
+ tenantAiAgentId={tenantAiAgentId}
1870
+ enableMicrophone={enableMicrophone}
1871
+ enableVoice={enableVoice}
1872
+ isRecording={isRecording}
1873
+ recognition={recognition}
1874
+ onMicrophoneClick={handleMicrophoneClick}
1875
+ onToolAction={handleToolAction}
1876
+ threadId={sessionState.selectedSessionId ?? undefined} />
1877
+ </>
1878
+ )}
1879
+ </div>
1880
+
1881
+ {/* Floating Right Sidebar with Overlay - shown only when container < 3xl */}
1882
+ {uiState.isRightSidebarOpen && (
1883
+ <>
1884
+ {/* Background Mask */}
1885
+ <div
1886
+ className={cn(
1887
+ 'absolute inset-0 bg-black/30 z-99',
1888
+ // Show only when container is smaller than 3xl
1889
+ '@3xl:hidden'
1890
+ )}
1891
+ onClick={() => uiActions.setRightSidebarOpen(false)} />
1892
+
1893
+ {/* Floating Right Sidebar */}
1894
+ <div
1895
+ className={cn(
1896
+ 'absolute right-0 top-0 h-full w-80 rounded-lg bg-card z-100 shadow-xl',
1897
+ // Show floating sidebar when container is smaller than 3xl
1898
+ '@3xl:hidden'
1899
+ )}
1900
+ onMouseLeave={() => {
1901
+ // Auto-close when mouse leaves sidebar
1902
+ if (rightSidebarCloseTimeoutRef.current) {
1903
+ clearTimeout(rightSidebarCloseTimeoutRef.current);
1904
+ }
1905
+ rightSidebarCloseTimeoutRef.current = setTimeout(() => {
1906
+ uiActions.setRightSidebarOpen(false);
1907
+ }, 300);
1908
+ }}
1909
+ onMouseEnter={() => {
1910
+ // Clear any pending close timeout when mouse enters
1911
+ if (rightSidebarCloseTimeoutRef.current) {
1912
+ clearTimeout(rightSidebarCloseTimeoutRef.current);
1913
+ rightSidebarCloseTimeoutRef.current = null;
1914
+ }
1915
+ }}>
1916
+ <AssistantCanvasView
1917
+ works={worksState.works}
1918
+ selectedWork={worksState.selectedWork}
1919
+ rightSidebarView={uiState.rightSidebarView}
1920
+ onWorkSelect={handleWorkNavigation}
1921
+ onBackToList={() => handleWorkNavigation(null)}
1922
+ onClose={() => uiActions.setRightSidebarOpen(false)} />
1923
+ </div>
1924
+ </>
1925
+ )}
1926
+
1927
+ {/* Inline Right Sidebar - shown only when container >= 3xl */}
1928
+ <div
1929
+ className={cn(
1930
+ 'relative bg-card flex-none transition-all duration-300 border-l',
1931
+ // Show inline sidebar when container is 3xl or larger
1932
+ 'hidden @3xl:block',
1933
+ uiState.isRightSidebarOpen ? 'w-80' : 'w-0 overflow-hidden'
1934
+ )}>
1935
+ <AssistantCanvasView
1936
+ works={worksState.works}
1937
+ selectedWork={worksState.selectedWork}
1938
+ rightSidebarView={uiState.rightSidebarView}
1939
+ onWorkSelect={handleWorkNavigation}
1940
+ onBackToList={() => handleWorkNavigation(null)}
1941
+ onClose={() => uiActions.setRightSidebarOpen(false)} />
1942
+ </div>
1943
+ </div>
1944
+ </DialogContent>
1945
+ </Dialog>
1946
+
1947
+ {/* CRUD Dialogs */}
1948
+ <AssistantDialogs
1949
+ projectState={projectState}
1950
+ projectActions={projectActions}
1951
+ sessionState={sessionState}
1952
+ sessionActions={sessionActions}
1953
+ onUpdateProject={updateProject}
1954
+ onDeleteProject={deleteProject}
1955
+ onUpdateSession={updateSession}
1956
+ onDeleteSession={deleteSession}
1957
+ onUpdateProjectInstructions={updateProjectInstructions}
1958
+ t={t} />
1959
+
1960
+ </>
1961
+ );
1962
+ };