@vertesia/ui 1.5.0-dev.20260722.120446Z → 1.5.0-dev.20260725.083715Z

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 (307) hide show
  1. package/lib/core/components/shadcn/filters/filterBar.d.ts.map +1 -1
  2. package/lib/core/components/shadcn/filters/filterBar.js +1 -1
  3. package/lib/core/components/shadcn/filters/filterBar.js.map +1 -1
  4. package/lib/core/components/shadcn/modal/dialog.d.ts +1 -1
  5. package/lib/core/components/shadcn/modal/dialog.d.ts.map +1 -1
  6. package/lib/core/components/shadcn/modal/dialog.js +2 -0
  7. package/lib/core/components/shadcn/modal/dialog.js.map +1 -1
  8. package/lib/core/components/shadcn/tabs.d.ts +12 -1
  9. package/lib/core/components/shadcn/tabs.d.ts.map +1 -1
  10. package/lib/core/components/shadcn/tabs.js +12 -4
  11. package/lib/core/components/shadcn/tabs.js.map +1 -1
  12. package/lib/features/agent/chat/AgentRightPanel.d.ts +5 -1
  13. package/lib/features/agent/chat/AgentRightPanel.d.ts.map +1 -1
  14. package/lib/features/agent/chat/AgentRightPanel.js +2 -2
  15. package/lib/features/agent/chat/AgentRightPanel.js.map +1 -1
  16. package/lib/features/agent/chat/ArtifactsTab.d.ts +5 -1
  17. package/lib/features/agent/chat/ArtifactsTab.d.ts.map +1 -1
  18. package/lib/features/agent/chat/ArtifactsTab.js +48 -11
  19. package/lib/features/agent/chat/ArtifactsTab.js.map +1 -1
  20. package/lib/features/agent/chat/DocumentEditingActionCard.d.ts +6 -0
  21. package/lib/features/agent/chat/DocumentEditingActionCard.d.ts.map +1 -0
  22. package/lib/features/agent/chat/DocumentEditingActionCard.js +129 -0
  23. package/lib/features/agent/chat/DocumentEditingActionCard.js.map +1 -0
  24. package/lib/features/agent/chat/ModernAgentConversation.d.ts +19 -1
  25. package/lib/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
  26. package/lib/features/agent/chat/ModernAgentConversation.js +125 -27
  27. package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
  28. package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.d.ts +45 -0
  29. package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.d.ts.map +1 -0
  30. package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.js +40 -0
  31. package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.js.map +1 -0
  32. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts +3 -1
  33. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
  34. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +94 -49
  35. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
  36. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.d.ts +2 -1
  37. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.d.ts.map +1 -1
  38. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.js +9 -5
  39. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.js.map +1 -1
  40. package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.d.ts +25 -0
  41. package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.d.ts.map +1 -0
  42. package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.js +33 -0
  43. package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.js.map +1 -0
  44. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts +3 -1
  45. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
  46. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js +22 -24
  47. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
  48. package/lib/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.d.ts +14 -0
  49. package/lib/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.d.ts.map +1 -0
  50. package/lib/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.js +21 -0
  51. package/lib/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.js.map +1 -0
  52. package/lib/features/agent/chat/ModernAgentOutput/ResourceChip.d.ts +30 -0
  53. package/lib/features/agent/chat/ModernAgentOutput/ResourceChip.d.ts.map +1 -0
  54. package/lib/features/agent/chat/ModernAgentOutput/ResourceChip.js +53 -0
  55. package/lib/features/agent/chat/ModernAgentOutput/ResourceChip.js.map +1 -0
  56. package/lib/features/agent/chat/ModernAgentOutput/ToolCallGroup.d.ts.map +1 -1
  57. package/lib/features/agent/chat/ModernAgentOutput/ToolCallGroup.js +21 -2
  58. package/lib/features/agent/chat/ModernAgentOutput/ToolCallGroup.js.map +1 -1
  59. package/lib/features/agent/chat/ModernAgentOutput/resourceLinks.d.ts +6 -0
  60. package/lib/features/agent/chat/ModernAgentOutput/resourceLinks.d.ts.map +1 -0
  61. package/lib/features/agent/chat/ModernAgentOutput/resourceLinks.js +29 -0
  62. package/lib/features/agent/chat/ModernAgentOutput/resourceLinks.js.map +1 -0
  63. package/lib/features/agent/chat/ModernAgentOutput/resourceSummary.d.ts +33 -0
  64. package/lib/features/agent/chat/ModernAgentOutput/resourceSummary.d.ts.map +1 -0
  65. package/lib/features/agent/chat/ModernAgentOutput/resourceSummary.js +129 -0
  66. package/lib/features/agent/chat/ModernAgentOutput/resourceSummary.js.map +1 -0
  67. package/lib/features/agent/chat/ModernAgentOutput/utils.d.ts +7 -0
  68. package/lib/features/agent/chat/ModernAgentOutput/utils.d.ts.map +1 -1
  69. package/lib/features/agent/chat/ModernAgentOutput/utils.js +11 -1
  70. package/lib/features/agent/chat/ModernAgentOutput/utils.js.map +1 -1
  71. package/lib/features/agent/chat/hooks/useAgentStream.d.ts +1 -1
  72. package/lib/features/agent/chat/hooks/useAgentStream.d.ts.map +1 -1
  73. package/lib/features/agent/chat/hooks/useAgentStream.js +24 -9
  74. package/lib/features/agent/chat/hooks/useAgentStream.js.map +1 -1
  75. package/lib/features/agent/chat/index.d.ts +2 -1
  76. package/lib/features/agent/chat/index.d.ts.map +1 -1
  77. package/lib/features/agent/chat/index.js +3 -0
  78. package/lib/features/agent/chat/index.js.map +1 -1
  79. package/lib/features/store/objects/DocumentSearchResults.js +2 -2
  80. package/lib/features/store/objects/DocumentSearchResults.js.map +1 -1
  81. package/lib/features/store/objects/components/ContentOverview.d.ts +2 -1
  82. package/lib/features/store/objects/components/ContentOverview.d.ts.map +1 -1
  83. package/lib/features/store/objects/components/ContentOverview.js +86 -15
  84. package/lib/features/store/objects/components/ContentOverview.js.map +1 -1
  85. package/lib/features/store/objects/components/DocumentEditingConfigurationSelector.d.ts +16 -0
  86. package/lib/features/store/objects/components/DocumentEditingConfigurationSelector.d.ts.map +1 -0
  87. package/lib/features/store/objects/components/DocumentEditingConfigurationSelector.js +116 -0
  88. package/lib/features/store/objects/components/DocumentEditingConfigurationSelector.js.map +1 -0
  89. package/lib/features/store/objects/components/DocumentEditingLockStatus.d.ts +7 -0
  90. package/lib/features/store/objects/components/DocumentEditingLockStatus.d.ts.map +1 -0
  91. package/lib/features/store/objects/components/DocumentEditingLockStatus.js +13 -0
  92. package/lib/features/store/objects/components/DocumentEditingLockStatus.js.map +1 -0
  93. package/lib/features/store/objects/components/DocumentEditingPanel.d.ts +48 -0
  94. package/lib/features/store/objects/components/DocumentEditingPanel.d.ts.map +1 -0
  95. package/lib/features/store/objects/components/DocumentEditingPanel.js +720 -0
  96. package/lib/features/store/objects/components/DocumentEditingPanel.js.map +1 -0
  97. package/lib/features/store/objects/components/TextEditorPanel.d.ts.map +1 -1
  98. package/lib/features/store/objects/components/TextEditorPanel.js +22 -9
  99. package/lib/features/store/objects/components/TextEditorPanel.js.map +1 -1
  100. package/lib/features/store/objects/components/documentArtifactRefs.d.ts +26 -0
  101. package/lib/features/store/objects/components/documentArtifactRefs.d.ts.map +1 -0
  102. package/lib/features/store/objects/components/documentArtifactRefs.js +52 -0
  103. package/lib/features/store/objects/components/documentArtifactRefs.js.map +1 -0
  104. package/lib/features/store/objects/components/documentEditingAgentConfig.d.ts +4 -0
  105. package/lib/features/store/objects/components/documentEditingAgentConfig.d.ts.map +1 -0
  106. package/lib/features/store/objects/components/documentEditingAgentConfig.js +40 -0
  107. package/lib/features/store/objects/components/documentEditingAgentConfig.js.map +1 -0
  108. package/lib/features/store/objects/components/documentEditingPrompts.d.ts +3 -0
  109. package/lib/features/store/objects/components/documentEditingPrompts.d.ts.map +1 -0
  110. package/lib/features/store/objects/components/documentEditingPrompts.js +13 -0
  111. package/lib/features/store/objects/components/documentEditingPrompts.js.map +1 -0
  112. package/lib/features/store/objects/components/documentEditingRun.d.ts +27 -0
  113. package/lib/features/store/objects/components/documentEditingRun.d.ts.map +1 -0
  114. package/lib/features/store/objects/components/documentEditingRun.js +126 -0
  115. package/lib/features/store/objects/components/documentEditingRun.js.map +1 -0
  116. package/lib/features/store/objects/components/documentEditingTarget.d.ts +20 -0
  117. package/lib/features/store/objects/components/documentEditingTarget.d.ts.map +1 -0
  118. package/lib/features/store/objects/components/documentEditingTarget.js +40 -0
  119. package/lib/features/store/objects/components/documentEditingTarget.js.map +1 -0
  120. package/lib/features/store/objects/components/index.d.ts +1 -0
  121. package/lib/features/store/objects/components/index.d.ts.map +1 -1
  122. package/lib/features/store/objects/components/index.js +1 -0
  123. package/lib/features/store/objects/components/index.js.map +1 -1
  124. package/lib/features/store/objects/components/useContentPanelHooks.d.ts +5 -1
  125. package/lib/features/store/objects/components/useContentPanelHooks.d.ts.map +1 -1
  126. package/lib/features/store/objects/components/useContentPanelHooks.js +53 -29
  127. package/lib/features/store/objects/components/useContentPanelHooks.js.map +1 -1
  128. package/lib/features/store/objects/components/useDownloadFile.d.ts +2 -1
  129. package/lib/features/store/objects/components/useDownloadFile.d.ts.map +1 -1
  130. package/lib/features/store/objects/components/useDownloadFile.js +3 -0
  131. package/lib/features/store/objects/components/useDownloadFile.js.map +1 -1
  132. package/lib/features/store/objects/selection/actions/StartWorkflowButton.js +1 -1
  133. package/lib/features/store/objects/selection/actions/StartWorkflowButton.js.map +1 -1
  134. package/lib/i18n/locales/ar.json +118 -1
  135. package/lib/i18n/locales/de.json +118 -1
  136. package/lib/i18n/locales/en.json +118 -0
  137. package/lib/i18n/locales/es.json +118 -1
  138. package/lib/i18n/locales/fr.json +118 -1
  139. package/lib/i18n/locales/it.json +118 -1
  140. package/lib/i18n/locales/ja.json +118 -1
  141. package/lib/i18n/locales/ko.json +118 -1
  142. package/lib/i18n/locales/pt.json +118 -1
  143. package/lib/i18n/locales/ru.json +118 -1
  144. package/lib/i18n/locales/tr.json +118 -1
  145. package/lib/i18n/locales/zh-TW.json +118 -1
  146. package/lib/i18n/locales/zh.json +118 -1
  147. package/lib/rich-text/EditorToolbar.d.ts +37 -0
  148. package/lib/rich-text/EditorToolbar.d.ts.map +1 -0
  149. package/lib/rich-text/EditorToolbar.js +215 -0
  150. package/lib/rich-text/EditorToolbar.js.map +1 -0
  151. package/lib/rich-text/VertesiaRichTextEditor.d.ts +26 -0
  152. package/lib/rich-text/VertesiaRichTextEditor.d.ts.map +1 -0
  153. package/lib/rich-text/VertesiaRichTextEditor.js +67 -0
  154. package/lib/rich-text/VertesiaRichTextEditor.js.map +1 -0
  155. package/lib/rich-text/index.d.ts +2 -0
  156. package/lib/rich-text/index.d.ts.map +1 -0
  157. package/lib/rich-text/index.js +2 -0
  158. package/lib/rich-text/index.js.map +1 -0
  159. package/lib/vertesia-ui-core.js +2 -2
  160. package/lib/vertesia-ui-core.js.map +1 -1
  161. package/lib/vertesia-ui-features.js +35 -30
  162. package/lib/vertesia-ui-features.js.map +1 -1
  163. package/lib/vertesia-ui-i18n.js +14 -14
  164. package/lib/vertesia-ui-i18n.js.map +1 -1
  165. package/lib/vertesia-ui-rich-text.js +5 -0
  166. package/lib/vertesia-ui-rich-text.js.map +1 -0
  167. package/lib/vertesia-ui-widgets.js +25 -7
  168. package/lib/vertesia-ui-widgets.js.map +1 -1
  169. package/lib/widgets/markdown/AgentResourceResolver.d.ts +31 -0
  170. package/lib/widgets/markdown/AgentResourceResolver.d.ts.map +1 -0
  171. package/lib/widgets/markdown/AgentResourceResolver.js +39 -0
  172. package/lib/widgets/markdown/AgentResourceResolver.js.map +1 -0
  173. package/lib/widgets/markdown/ArtifactEditingSurface.d.ts +63 -0
  174. package/lib/widgets/markdown/ArtifactEditingSurface.d.ts.map +1 -0
  175. package/lib/widgets/markdown/ArtifactEditingSurface.js +582 -0
  176. package/lib/widgets/markdown/ArtifactEditingSurface.js.map +1 -0
  177. package/lib/widgets/markdown/CollaborativeMarkdownRenderer.d.ts +69 -0
  178. package/lib/widgets/markdown/CollaborativeMarkdownRenderer.d.ts.map +1 -0
  179. package/lib/widgets/markdown/CollaborativeMarkdownRenderer.js +374 -0
  180. package/lib/widgets/markdown/CollaborativeMarkdownRenderer.js.map +1 -0
  181. package/lib/widgets/markdown/MarkdownLink.d.ts +3 -1
  182. package/lib/widgets/markdown/MarkdownLink.d.ts.map +1 -1
  183. package/lib/widgets/markdown/MarkdownLink.js +41 -14
  184. package/lib/widgets/markdown/MarkdownLink.js.map +1 -1
  185. package/lib/widgets/markdown/MarkdownRenderer.d.ts +8 -1
  186. package/lib/widgets/markdown/MarkdownRenderer.d.ts.map +1 -1
  187. package/lib/widgets/markdown/MarkdownRenderer.js +20 -4
  188. package/lib/widgets/markdown/MarkdownRenderer.js.map +1 -1
  189. package/lib/widgets/markdown/index.d.ts +5 -1
  190. package/lib/widgets/markdown/index.d.ts.map +1 -1
  191. package/lib/widgets/markdown/index.js +5 -1
  192. package/lib/widgets/markdown/index.js.map +1 -1
  193. package/lib/widgets/markdown/normalizeCustomSchemeLinks.d.ts.map +1 -1
  194. package/lib/widgets/markdown/normalizeCustomSchemeLinks.js +14 -2
  195. package/lib/widgets/markdown/normalizeCustomSchemeLinks.js.map +1 -1
  196. package/lib/widgets/markdown/textDiff.d.ts +52 -0
  197. package/lib/widgets/markdown/textDiff.d.ts.map +1 -0
  198. package/lib/widgets/markdown/textDiff.js +362 -0
  199. package/lib/widgets/markdown/textDiff.js.map +1 -0
  200. package/lib/widgets/markdown/useArtifactContent.d.ts +5 -3
  201. package/lib/widgets/markdown/useArtifactContent.d.ts.map +1 -1
  202. package/lib/widgets/markdown/useArtifactContent.js +6 -3
  203. package/lib/widgets/markdown/useArtifactContent.js.map +1 -1
  204. package/lib/widgets/markdown/useResolvedUrl.d.ts +1 -5
  205. package/lib/widgets/markdown/useResolvedUrl.d.ts.map +1 -1
  206. package/lib/widgets/markdown/useResolvedUrl.js +19 -31
  207. package/lib/widgets/markdown/useResolvedUrl.js.map +1 -1
  208. package/lib/widgets/xml-viewer/hooks/useXMLViewer.d.ts +9 -0
  209. package/lib/widgets/xml-viewer/hooks/useXMLViewer.d.ts.map +1 -1
  210. package/lib/widgets/xml-viewer/hooks/useXMLViewer.js +20 -17
  211. package/lib/widgets/xml-viewer/hooks/useXMLViewer.js.map +1 -1
  212. package/package.json +13 -9
  213. package/src/__tests__/a11y.test.tsx +4 -1
  214. package/src/__tests__/axe-helper.ts +17 -7
  215. package/src/__tests__/setup.ts +25 -0
  216. package/src/core/components/shadcn/filters/filterBar.tsx +1 -0
  217. package/src/core/components/shadcn/modal/dialog.tsx +3 -1
  218. package/src/core/components/shadcn/tabs.test.tsx +65 -0
  219. package/src/core/components/shadcn/tabs.tsx +25 -3
  220. package/src/features/agent/chat/AgentRightPanel.tsx +16 -1
  221. package/src/features/agent/chat/ArtifactsTab.test.tsx +55 -0
  222. package/src/features/agent/chat/ArtifactsTab.tsx +203 -52
  223. package/src/features/agent/chat/DocumentEditingActionCard.test.tsx +105 -0
  224. package/src/features/agent/chat/DocumentEditingActionCard.tsx +210 -0
  225. package/src/features/agent/chat/ModernAgentConversation.test.tsx +237 -40
  226. package/src/features/agent/chat/ModernAgentConversation.tsx +247 -75
  227. package/src/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.test.tsx +79 -0
  228. package/src/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.tsx +94 -0
  229. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.test.tsx +257 -34
  230. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +217 -105
  231. package/src/features/agent/chat/ModernAgentOutput/AttachmentPreview.tsx +17 -0
  232. package/src/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.test.tsx +44 -0
  233. package/src/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.tsx +88 -0
  234. package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +30 -46
  235. package/src/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.tsx +40 -0
  236. package/src/features/agent/chat/ModernAgentOutput/ResourceChip.test.tsx +148 -0
  237. package/src/features/agent/chat/ModernAgentOutput/ResourceChip.tsx +141 -0
  238. package/src/features/agent/chat/ModernAgentOutput/ToolCallGroup.tsx +33 -1
  239. package/src/features/agent/chat/ModernAgentOutput/resourceLinks.tsx +43 -0
  240. package/src/features/agent/chat/ModernAgentOutput/resourceSummary.test.ts +229 -0
  241. package/src/features/agent/chat/ModernAgentOutput/resourceSummary.ts +152 -0
  242. package/src/features/agent/chat/ModernAgentOutput/utils.test.ts +17 -0
  243. package/src/features/agent/chat/ModernAgentOutput/utils.ts +14 -1
  244. package/src/features/agent/chat/hooks/useAgentStream.test.tsx +188 -0
  245. package/src/features/agent/chat/hooks/useAgentStream.ts +28 -8
  246. package/src/features/agent/chat/index.ts +11 -0
  247. package/src/features/store/objects/DocumentSearchResults.tsx +2 -2
  248. package/src/features/store/objects/components/ContentOverview.tsx +216 -65
  249. package/src/features/store/objects/components/DocumentEditingConfigurationSelector.test.ts +34 -0
  250. package/src/features/store/objects/components/DocumentEditingConfigurationSelector.tsx +211 -0
  251. package/src/features/store/objects/components/DocumentEditingLockStatus.test.tsx +32 -0
  252. package/src/features/store/objects/components/DocumentEditingLockStatus.tsx +45 -0
  253. package/src/features/store/objects/components/DocumentEditingPanel.test.tsx +336 -0
  254. package/src/features/store/objects/components/DocumentEditingPanel.tsx +1236 -0
  255. package/src/features/store/objects/components/TextEditorPanel.test.tsx +48 -0
  256. package/src/features/store/objects/components/TextEditorPanel.tsx +41 -20
  257. package/src/features/store/objects/components/documentArtifactRefs.test.ts +65 -0
  258. package/src/features/store/objects/components/documentArtifactRefs.ts +72 -0
  259. package/src/features/store/objects/components/documentEditingAgentConfig.test.ts +28 -0
  260. package/src/features/store/objects/components/documentEditingAgentConfig.ts +41 -0
  261. package/src/features/store/objects/components/documentEditingPrompts.test.ts +26 -0
  262. package/src/features/store/objects/components/documentEditingPrompts.ts +12 -0
  263. package/src/features/store/objects/components/documentEditingRun.test.ts +176 -0
  264. package/src/features/store/objects/components/documentEditingRun.ts +184 -0
  265. package/src/features/store/objects/components/documentEditingTarget.test.ts +96 -0
  266. package/src/features/store/objects/components/documentEditingTarget.ts +63 -0
  267. package/src/features/store/objects/components/index.ts +1 -0
  268. package/src/features/store/objects/components/useContentPanelHooks.ts +52 -27
  269. package/src/features/store/objects/components/useDownloadFile.test.ts +20 -0
  270. package/src/features/store/objects/components/useDownloadFile.ts +5 -1
  271. package/src/features/store/objects/selection/actions/StartWorkflowButton.tsx +2 -2
  272. package/src/i18n/locales/ar.json +118 -1
  273. package/src/i18n/locales/de.json +118 -1
  274. package/src/i18n/locales/en.json +118 -0
  275. package/src/i18n/locales/es.json +118 -1
  276. package/src/i18n/locales/fr.json +118 -1
  277. package/src/i18n/locales/it.json +118 -1
  278. package/src/i18n/locales/ja.json +118 -1
  279. package/src/i18n/locales/ko.json +118 -1
  280. package/src/i18n/locales/pt.json +118 -1
  281. package/src/i18n/locales/ru.json +118 -1
  282. package/src/i18n/locales/tr.json +118 -1
  283. package/src/i18n/locales/zh-TW.json +118 -1
  284. package/src/i18n/locales/zh.json +118 -1
  285. package/src/rich-text/EditorToolbar.tsx +708 -0
  286. package/src/rich-text/VertesiaRichTextEditor.test.tsx +211 -0
  287. package/src/rich-text/VertesiaRichTextEditor.tsx +241 -0
  288. package/src/rich-text/index.ts +9 -0
  289. package/src/session/UserSession.test.ts +11 -11
  290. package/src/widgets/markdown/AgentResourceResolver.test.ts +24 -0
  291. package/src/widgets/markdown/AgentResourceResolver.tsx +70 -0
  292. package/src/widgets/markdown/ArtifactEditingSurface.test.tsx +537 -0
  293. package/src/widgets/markdown/ArtifactEditingSurface.tsx +878 -0
  294. package/src/widgets/markdown/CollaborativeMarkdownRenderer.test.tsx +484 -0
  295. package/src/widgets/markdown/CollaborativeMarkdownRenderer.tsx +825 -0
  296. package/src/widgets/markdown/MarkdownLink.test.tsx +122 -0
  297. package/src/widgets/markdown/MarkdownLink.tsx +102 -17
  298. package/src/widgets/markdown/MarkdownRenderer.tsx +30 -3
  299. package/src/widgets/markdown/index.ts +39 -1
  300. package/src/widgets/markdown/normalizeCustomSchemeLinks.ts +15 -2
  301. package/src/widgets/markdown/textDiff.test.ts +156 -0
  302. package/src/widgets/markdown/textDiff.ts +399 -0
  303. package/src/widgets/markdown/useArtifactContent.ts +9 -3
  304. package/src/widgets/markdown/useResolvedUrl.test.ts +29 -40
  305. package/src/widgets/markdown/useResolvedUrl.ts +33 -35
  306. package/src/widgets/xml-viewer/hooks/useXMLViewer.test.ts +26 -0
  307. package/src/widgets/xml-viewer/hooks/useXMLViewer.ts +23 -18
@@ -0,0 +1,708 @@
1
+ import { autoUpdate, FloatingPortal, flip, offset, shift, useFloating } from '@floating-ui/react';
2
+ import type { Editor } from '@vertesia/rich-text';
3
+ import {
4
+ Button,
5
+ cn,
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuItem,
9
+ DropdownMenuTrigger,
10
+ Spinner,
11
+ Textarea,
12
+ usePortalContainer,
13
+ useToast,
14
+ VTooltip,
15
+ } from '@vertesia/ui/core';
16
+ import { useUITranslation } from '@vertesia/ui/i18n';
17
+ import {
18
+ Bold,
19
+ ChevronDown,
20
+ Code,
21
+ Italic,
22
+ List,
23
+ ListOrdered,
24
+ MessageSquarePlus,
25
+ Minus,
26
+ Quote,
27
+ Redo2,
28
+ Send,
29
+ SquareCode,
30
+ Strikethrough,
31
+ Table,
32
+ Undo2,
33
+ X,
34
+ } from 'lucide-react';
35
+ import { type ComponentType, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
36
+
37
+ /**
38
+ * The formatting commands (toggleBold, setHeading, …) are augmented onto Tiptap's chain by the
39
+ * editor extensions, which live in @vertesia/rich-text and are not in @vertesia/ui's type scope.
40
+ * They exist at runtime; type only the ones this toolbar uses, narrowly, to avoid `any`.
41
+ */
42
+ interface EditorChain {
43
+ focus(): EditorChain;
44
+ run(): boolean;
45
+ setParagraph(): EditorChain;
46
+ setHeading(attrs: { level: 1 | 2 | 3 }): EditorChain;
47
+ toggleBold(): EditorChain;
48
+ toggleItalic(): EditorChain;
49
+ toggleStrike(): EditorChain;
50
+ toggleCode(): EditorChain;
51
+ toggleBulletList(): EditorChain;
52
+ toggleOrderedList(): EditorChain;
53
+ toggleBlockquote(): EditorChain;
54
+ toggleCodeBlock(): EditorChain;
55
+ setHorizontalRule(): EditorChain;
56
+ insertTable(attrs: { rows: number; cols: number; withHeaderRow: boolean }): EditorChain;
57
+ addRowBefore(): EditorChain;
58
+ addRowAfter(): EditorChain;
59
+ deleteRow(): EditorChain;
60
+ addColumnBefore(): EditorChain;
61
+ addColumnAfter(): EditorChain;
62
+ deleteColumn(): EditorChain;
63
+ deleteTable(): EditorChain;
64
+ undo(): EditorChain;
65
+ redo(): EditorChain;
66
+ }
67
+
68
+ const chain = (editor: Editor): EditorChain => editor.chain() as unknown as EditorChain;
69
+ const canChain = (editor: Editor): EditorChain => editor.can().chain() as unknown as EditorChain;
70
+
71
+ /** Force a re-render whenever the editor's selection or content changes, so active/enabled state stays live. */
72
+ function useEditorRevision(editor: Editor | null): void {
73
+ const [, setRevision] = useState(0);
74
+ useEffect(() => {
75
+ if (!editor) return;
76
+ const update = () => setRevision((value) => value + 1);
77
+ editor.on('transaction', update);
78
+ editor.on('selectionUpdate', update);
79
+ return () => {
80
+ editor.off('transaction', update);
81
+ editor.off('selectionUpdate', update);
82
+ };
83
+ }, [editor]);
84
+ }
85
+
86
+ // Keep the toolbar from stealing the editor selection when a control is pressed.
87
+ const retainSelection = (event: React.MouseEvent) => event.preventDefault();
88
+
89
+ function createId(): string {
90
+ if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) return crypto.randomUUID();
91
+ return `c-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
92
+ }
93
+
94
+ interface Selection {
95
+ blockLabel: string;
96
+ quote: string;
97
+ from: number;
98
+ to: number;
99
+ }
100
+
101
+ /** Human-readable block label + selected text for the enclosing node of the current selection. */
102
+ function readSelection(editor: Editor): Selection | null {
103
+ const { from, to } = editor.state.selection;
104
+ if (from >= to) return null;
105
+ const quote = editor.state.doc.textBetween(from, to, '\n', ' ').trim();
106
+ if (!quote) return null;
107
+ const $from = editor.state.doc.resolve(from);
108
+ let blockLabel = 'paragraph';
109
+ for (let depth = $from.depth; depth >= 0; depth--) {
110
+ const name = $from.node(depth).type.name;
111
+ if (name === 'heading') {
112
+ blockLabel = 'heading';
113
+ break;
114
+ }
115
+ if (name === 'codeBlock' || name === 'vertesiaCodeBlock') {
116
+ blockLabel = 'code block';
117
+ break;
118
+ }
119
+ if (name === 'blockquote') {
120
+ blockLabel = 'quote';
121
+ break;
122
+ }
123
+ if (name === 'listItem' || name === 'taskItem') {
124
+ blockLabel = 'list item';
125
+ break;
126
+ }
127
+ if (name === 'table') {
128
+ blockLabel = 'table';
129
+ break;
130
+ }
131
+ }
132
+ return { blockLabel, quote, from, to };
133
+ }
134
+
135
+ interface PendingComment extends Selection {
136
+ id: string;
137
+ comment: string;
138
+ }
139
+
140
+ function composeCommentMessage(comments: PendingComment[]): string {
141
+ const count = comments.length;
142
+ const items = comments.map(
143
+ (entry, index) => `${index + 1}. On the ${entry.blockLabel} “${entry.quote}”:\n ${entry.comment}`,
144
+ );
145
+ return [
146
+ `I've left ${count} comment${count === 1 ? '' : 's'} on the document:`,
147
+ '',
148
+ ...items,
149
+ '',
150
+ 'Please revise the document to address each comment, preserving everything else.',
151
+ ].join('\n');
152
+ }
153
+
154
+ function getSelectionAnchor(editor: Editor, selection: Selection) {
155
+ return {
156
+ contextElement: editor.view.dom,
157
+ getBoundingClientRect: () => {
158
+ try {
159
+ const end = editor.view.coordsAtPos(selection.to);
160
+ const height = Math.max(1, end.bottom - end.top);
161
+ return new DOMRect(end.right, end.top, 1, height);
162
+ } catch {
163
+ return editor.view.dom.getBoundingClientRect();
164
+ }
165
+ },
166
+ };
167
+ }
168
+
169
+ interface SelectionCommentProps {
170
+ editor: Editor;
171
+ selection: Selection;
172
+ composing: boolean;
173
+ draft: string;
174
+ onStart: () => void;
175
+ onDraftChange: (value: string) => void;
176
+ onCancel: () => void;
177
+ onAdd: () => void;
178
+ }
179
+
180
+ function SelectionComment({
181
+ editor,
182
+ selection,
183
+ composing,
184
+ draft,
185
+ onStart,
186
+ onDraftChange,
187
+ onCancel,
188
+ onAdd,
189
+ }: SelectionCommentProps) {
190
+ const { t } = useUITranslation();
191
+ const portalContainer = usePortalContainer();
192
+ const { refs, floatingStyles } = useFloating({
193
+ placement: 'right-start',
194
+ strategy: 'fixed',
195
+ middleware: [offset(8), flip({ fallbackPlacements: ['left-start', 'bottom-start'] }), shift({ padding: 12 })],
196
+ whileElementsMounted: autoUpdate,
197
+ });
198
+ const anchor = useMemo(() => getSelectionAnchor(editor, selection), [editor, selection]);
199
+
200
+ useEffect(() => {
201
+ refs.setPositionReference(anchor);
202
+ }, [anchor, refs]);
203
+
204
+ return (
205
+ <FloatingPortal root={portalContainer}>
206
+ <div ref={refs.setFloating} style={floatingStyles} className="pointer-events-auto z-100">
207
+ {composing ? (
208
+ <div
209
+ role="dialog"
210
+ aria-label={t('agent.commentOnSelection')}
211
+ className="w-80 max-w-[calc(100vw-1.5rem)] space-y-2 rounded-lg border border-mixer-muted/25 bg-popover p-3 text-popover-foreground shadow-lg"
212
+ >
213
+ <div className="line-clamp-2 text-xs text-muted">
214
+ <span className="font-medium text-foreground">{t('agent.commentingOn')}</span>{' '}
215
+ <span className="italic">“{selection.quote}”</span>
216
+ </div>
217
+ <Textarea
218
+ value={draft}
219
+ onChange={(event) => onDraftChange(event.target.value)}
220
+ placeholder={t('agent.commentOnSelectionPlaceholder')}
221
+ rows={3}
222
+ autoFocus
223
+ onKeyDown={(event) => {
224
+ if (event.key === 'Escape') {
225
+ event.preventDefault();
226
+ onCancel();
227
+ return;
228
+ }
229
+ if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
230
+ event.preventDefault();
231
+ onAdd();
232
+ }
233
+ }}
234
+ />
235
+ <div className="flex justify-end gap-2">
236
+ <Button variant="outline" size="sm" onClick={onCancel}>
237
+ {t('store.cancelEdit')}
238
+ </Button>
239
+ <Button size="sm" onClick={onAdd} disabled={!draft.trim()}>
240
+ {t('agent.addComment')}
241
+ </Button>
242
+ </div>
243
+ </div>
244
+ ) : (
245
+ <VTooltip description={t('agent.commentOnSelection')} asChild>
246
+ <Button
247
+ variant="secondary"
248
+ size="sm"
249
+ onMouseDown={retainSelection}
250
+ onClick={onStart}
251
+ aria-label={t('agent.commentOnSelection')}
252
+ className="size-9 rounded-full p-0 shadow-md"
253
+ >
254
+ <MessageSquarePlus className="size-4" />
255
+ </Button>
256
+ </VTooltip>
257
+ )}
258
+ </div>
259
+ </FloatingPortal>
260
+ );
261
+ }
262
+
263
+ function ToolButton({
264
+ icon: Icon,
265
+ label,
266
+ active = false,
267
+ disabled = false,
268
+ onClick,
269
+ }: {
270
+ icon: ComponentType<{ className?: string }>;
271
+ label: string;
272
+ active?: boolean;
273
+ disabled?: boolean;
274
+ onClick: () => void;
275
+ }) {
276
+ return (
277
+ <Button
278
+ variant="ghost"
279
+ size="sm"
280
+ aria-label={label}
281
+ title={label}
282
+ aria-pressed={active}
283
+ disabled={disabled}
284
+ onMouseDown={retainSelection}
285
+ onClick={onClick}
286
+ className={cn(
287
+ 'size-8 shrink-0 rounded-md p-0 text-foreground hover:bg-muted/60',
288
+ active && 'bg-muted text-foreground',
289
+ )}
290
+ >
291
+ <Icon className="size-4" />
292
+ </Button>
293
+ );
294
+ }
295
+
296
+ function Divider() {
297
+ return <div className="mx-0.5 h-5 w-px shrink-0 bg-mixer-muted/30" aria-hidden />;
298
+ }
299
+
300
+ export interface EditorToolbarProps {
301
+ editor: Editor | null;
302
+ className?: string;
303
+ /** Whether formatting commands may mutate the document. Comment controls remain available. */
304
+ editable?: boolean;
305
+ /**
306
+ * When provided, the toolbar also shows comment controls: the user queues comments on text
307
+ * selections and this sends the whole batch to the agent as one message.
308
+ */
309
+ onSendComment?: (message: string) => void | Promise<void>;
310
+ /** Hand the user's persisted full-document edits to the active agent. */
311
+ onSendChangesToAgent?: () => void | Promise<void>;
312
+ /** Whether the user has direct edits that have not yet been handed to the agent. */
313
+ hasUnsentChanges?: boolean;
314
+ /** Whether the hand-off is currently being prepared and sent. */
315
+ isSendingChanges?: boolean;
316
+ /** Additional session-level constraints, such as no active run or an agent editing lock. */
317
+ sendChangesDisabled?: boolean;
318
+ /** Compact session status rendered immediately beside the document hand-off action. */
319
+ toolbarStatus?: ReactNode;
320
+ }
321
+
322
+ interface SendChangesToAgentButtonProps {
323
+ onSend: () => void | Promise<void>;
324
+ hasUnsentChanges: boolean;
325
+ isSending: boolean;
326
+ disabled: boolean;
327
+ }
328
+
329
+ export function SendChangesToAgentButton({
330
+ onSend,
331
+ hasUnsentChanges,
332
+ isSending,
333
+ disabled,
334
+ }: SendChangesToAgentButtonProps) {
335
+ const { t } = useUITranslation();
336
+ return (
337
+ <Button
338
+ variant={hasUnsentChanges ? 'primary' : 'ghost'}
339
+ size="sm"
340
+ className={cn('h-8 gap-1.5', !hasUnsentChanges && 'text-muted')}
341
+ onMouseDown={retainSelection}
342
+ onClick={() => void onSend()}
343
+ disabled={disabled || !hasUnsentChanges || isSending}
344
+ >
345
+ {isSending ? <Spinner size="sm" /> : <Send className="size-3.5" />}
346
+ {t('agent.sendChangesToAgent')}
347
+ </Button>
348
+ );
349
+ }
350
+
351
+ /**
352
+ * Clean, icon-based formatting toolbar for the Markdown document editor, optionally with inline
353
+ * comment controls. Reactive to the current selection; uses the shared `richText.*` i18n strings.
354
+ */
355
+ export function EditorToolbar({
356
+ editor,
357
+ className,
358
+ editable = true,
359
+ onSendComment,
360
+ onSendChangesToAgent,
361
+ hasUnsentChanges = false,
362
+ isSendingChanges = false,
363
+ sendChangesDisabled = false,
364
+ toolbarStatus,
365
+ }: EditorToolbarProps) {
366
+ const { t } = useUITranslation();
367
+ const toast = useToast();
368
+ useEditorRevision(editor);
369
+
370
+ const [composing, setComposing] = useState(false);
371
+ const [draft, setDraft] = useState('');
372
+ const [pendingSelection, setPendingSelection] = useState<Selection | null>(null);
373
+ const [activeSelection, setActiveSelection] = useState<Selection | null>(null);
374
+ const [pending, setPending] = useState<PendingComment[]>([]);
375
+ const [showList, setShowList] = useState(false);
376
+ const [isSendingComments, setIsSendingComments] = useState(false);
377
+ const lastSelectionRef = useRef<Selection | null>(null);
378
+
379
+ useEffect(() => {
380
+ if (!editor || !onSendComment) return;
381
+ const update = () => {
382
+ const selection = readSelection(editor);
383
+ setActiveSelection(selection);
384
+ if (selection) lastSelectionRef.current = selection;
385
+ };
386
+ update();
387
+ editor.on('selectionUpdate', update);
388
+ editor.on('transaction', update);
389
+ return () => {
390
+ editor.off('selectionUpdate', update);
391
+ editor.off('transaction', update);
392
+ };
393
+ }, [editor, onSendComment]);
394
+
395
+ const startComment = useCallback(() => {
396
+ const selection = lastSelectionRef.current;
397
+ if (!selection) {
398
+ toast({ status: 'info', title: t('agent.selectTextToComment'), duration: 2500 });
399
+ return;
400
+ }
401
+ setPendingSelection(selection);
402
+ setDraft('');
403
+ setComposing(true);
404
+ }, [t, toast]);
405
+
406
+ const cancelDraft = useCallback(() => {
407
+ setComposing(false);
408
+ setPendingSelection(null);
409
+ setDraft('');
410
+ }, []);
411
+
412
+ const addComment = useCallback(() => {
413
+ const body = draft.trim();
414
+ if (!pendingSelection || !body) return;
415
+ setPending((current) => [...current, { id: createId(), ...pendingSelection, comment: body }]);
416
+ setComposing(false);
417
+ setPendingSelection(null);
418
+ setDraft('');
419
+ setShowList(true);
420
+ }, [draft, pendingSelection]);
421
+
422
+ const removeComment = useCallback((id: string) => {
423
+ setPending((current) => current.filter((entry) => entry.id !== id));
424
+ }, []);
425
+
426
+ const sendAll = useCallback(async () => {
427
+ if (!onSendComment || pending.length === 0 || isSendingComments) return;
428
+ setIsSendingComments(true);
429
+ try {
430
+ await onSendComment(composeCommentMessage(pending));
431
+ setPending([]);
432
+ setShowList(false);
433
+ } finally {
434
+ setIsSendingComments(false);
435
+ }
436
+ }, [isSendingComments, onSendComment, pending]);
437
+
438
+ if (!editor) return null;
439
+
440
+ const headingLevel = ([1, 2, 3] as const).find((level) => editor.isActive('heading', { level }));
441
+ const blockLabel =
442
+ headingLevel === 1
443
+ ? t('richText.heading1')
444
+ : headingLevel === 2
445
+ ? t('richText.heading2')
446
+ : headingLevel === 3
447
+ ? t('richText.heading3')
448
+ : t('richText.paragraph');
449
+ const inTable = editor.isActive('table');
450
+ const commentSelection = pendingSelection ?? activeSelection;
451
+
452
+ return (
453
+ <>
454
+ <div
455
+ role="toolbar"
456
+ aria-label={t('richText.blockStyle')}
457
+ className={cn(
458
+ 'flex min-h-10 shrink-0 items-center gap-0.5 overflow-x-auto border-b border-mixer-muted/25 px-1.5 py-1',
459
+ className,
460
+ )}
461
+ >
462
+ <ToolButton
463
+ icon={Undo2}
464
+ label={t('richText.undo')}
465
+ disabled={!editable || !canChain(editor).focus().undo().run()}
466
+ onClick={() => chain(editor).focus().undo().run()}
467
+ />
468
+ <ToolButton
469
+ icon={Redo2}
470
+ label={t('richText.redo')}
471
+ disabled={!editable || !canChain(editor).focus().redo().run()}
472
+ onClick={() => chain(editor).focus().redo().run()}
473
+ />
474
+
475
+ <Divider />
476
+
477
+ <DropdownMenu>
478
+ <DropdownMenuTrigger asChild>
479
+ <Button
480
+ variant="ghost"
481
+ size="sm"
482
+ disabled={!editable}
483
+ onMouseDown={retainSelection}
484
+ className="h-8 shrink-0 gap-1 px-2 text-xs font-medium text-foreground hover:bg-muted/60"
485
+ >
486
+ {blockLabel}
487
+ <ChevronDown className="size-3 opacity-60" />
488
+ </Button>
489
+ </DropdownMenuTrigger>
490
+ <DropdownMenuContent align="start">
491
+ <DropdownMenuItem onClick={() => chain(editor).focus().setParagraph().run()}>
492
+ {t('richText.paragraph')}
493
+ </DropdownMenuItem>
494
+ <DropdownMenuItem onClick={() => chain(editor).focus().setHeading({ level: 1 }).run()}>
495
+ {t('richText.heading1')}
496
+ </DropdownMenuItem>
497
+ <DropdownMenuItem onClick={() => chain(editor).focus().setHeading({ level: 2 }).run()}>
498
+ {t('richText.heading2')}
499
+ </DropdownMenuItem>
500
+ <DropdownMenuItem onClick={() => chain(editor).focus().setHeading({ level: 3 }).run()}>
501
+ {t('richText.heading3')}
502
+ </DropdownMenuItem>
503
+ </DropdownMenuContent>
504
+ </DropdownMenu>
505
+
506
+ <Divider />
507
+
508
+ <ToolButton
509
+ icon={Bold}
510
+ label={t('richText.bold')}
511
+ active={editor.isActive('bold')}
512
+ disabled={!editable}
513
+ onClick={() => chain(editor).focus().toggleBold().run()}
514
+ />
515
+ <ToolButton
516
+ icon={Italic}
517
+ label={t('richText.italic')}
518
+ active={editor.isActive('italic')}
519
+ disabled={!editable}
520
+ onClick={() => chain(editor).focus().toggleItalic().run()}
521
+ />
522
+ <ToolButton
523
+ icon={Strikethrough}
524
+ label={t('richText.strike')}
525
+ active={editor.isActive('strike')}
526
+ disabled={!editable}
527
+ onClick={() => chain(editor).focus().toggleStrike().run()}
528
+ />
529
+ <ToolButton
530
+ icon={Code}
531
+ label={t('richText.inlineCode')}
532
+ active={editor.isActive('code')}
533
+ disabled={!editable}
534
+ onClick={() => chain(editor).focus().toggleCode().run()}
535
+ />
536
+
537
+ <Divider />
538
+
539
+ <ToolButton
540
+ icon={List}
541
+ label={t('richText.bulletList')}
542
+ active={editor.isActive('bulletList')}
543
+ disabled={!editable}
544
+ onClick={() => chain(editor).focus().toggleBulletList().run()}
545
+ />
546
+ <ToolButton
547
+ icon={ListOrdered}
548
+ label={t('richText.orderedList')}
549
+ active={editor.isActive('orderedList')}
550
+ disabled={!editable}
551
+ onClick={() => chain(editor).focus().toggleOrderedList().run()}
552
+ />
553
+ <ToolButton
554
+ icon={Quote}
555
+ label={t('richText.blockquote')}
556
+ active={editor.isActive('blockquote')}
557
+ disabled={!editable}
558
+ onClick={() => chain(editor).focus().toggleBlockquote().run()}
559
+ />
560
+ <ToolButton
561
+ icon={SquareCode}
562
+ label={t('richText.codeBlock')}
563
+ active={editor.isActive('codeBlock')}
564
+ disabled={!editable}
565
+ onClick={() => chain(editor).focus().toggleCodeBlock().run()}
566
+ />
567
+
568
+ <Divider />
569
+
570
+ <ToolButton
571
+ icon={Minus}
572
+ label={t('richText.horizontalRule')}
573
+ disabled={!editable}
574
+ onClick={() => chain(editor).focus().setHorizontalRule().run()}
575
+ />
576
+ <DropdownMenu>
577
+ <DropdownMenuTrigger asChild>
578
+ <Button
579
+ variant="ghost"
580
+ size="sm"
581
+ disabled={!editable}
582
+ aria-label={t('richText.tableActions')}
583
+ title={inTable ? t('richText.tableActions') : t('richText.table')}
584
+ onMouseDown={retainSelection}
585
+ className={cn(
586
+ 'size-8 shrink-0 rounded-md p-0 text-foreground hover:bg-muted/60',
587
+ inTable && 'bg-muted',
588
+ )}
589
+ >
590
+ <Table className="size-4" />
591
+ </Button>
592
+ </DropdownMenuTrigger>
593
+ <DropdownMenuContent align="start">
594
+ {inTable ? (
595
+ <>
596
+ <DropdownMenuItem onClick={() => chain(editor).focus().addRowBefore().run()}>
597
+ {t('richText.addRowAbove')}
598
+ </DropdownMenuItem>
599
+ <DropdownMenuItem onClick={() => chain(editor).focus().addRowAfter().run()}>
600
+ {t('richText.addRowBelow')}
601
+ </DropdownMenuItem>
602
+ <DropdownMenuItem onClick={() => chain(editor).focus().deleteRow().run()}>
603
+ {t('richText.deleteRow')}
604
+ </DropdownMenuItem>
605
+ <DropdownMenuItem onClick={() => chain(editor).focus().addColumnBefore().run()}>
606
+ {t('richText.addColumnLeft')}
607
+ </DropdownMenuItem>
608
+ <DropdownMenuItem onClick={() => chain(editor).focus().addColumnAfter().run()}>
609
+ {t('richText.addColumnRight')}
610
+ </DropdownMenuItem>
611
+ <DropdownMenuItem onClick={() => chain(editor).focus().deleteColumn().run()}>
612
+ {t('richText.deleteColumn')}
613
+ </DropdownMenuItem>
614
+ <DropdownMenuItem onClick={() => chain(editor).focus().deleteTable().run()}>
615
+ {t('richText.deleteTable')}
616
+ </DropdownMenuItem>
617
+ </>
618
+ ) : (
619
+ <DropdownMenuItem
620
+ onClick={() =>
621
+ chain(editor).focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()
622
+ }
623
+ >
624
+ {t('richText.table')}
625
+ </DropdownMenuItem>
626
+ )}
627
+ </DropdownMenuContent>
628
+ </DropdownMenu>
629
+
630
+ {onSendComment || onSendChangesToAgent ? (
631
+ <div className="ms-auto flex shrink-0 items-center gap-1 ps-1">
632
+ {pending.length > 0 ? (
633
+ <>
634
+ <Button
635
+ variant="ghost"
636
+ size="sm"
637
+ className="h-8 px-2 text-xs text-muted"
638
+ onClick={() => setShowList((value) => !value)}
639
+ >
640
+ {t('agent.comments')} ({pending.length})
641
+ </Button>
642
+ <Button
643
+ size="sm"
644
+ className="h-8"
645
+ onClick={() => void sendAll()}
646
+ disabled={isSendingComments}
647
+ >
648
+ <Send className="me-1 size-4" />
649
+ {t('agent.sendToAgent')}
650
+ </Button>
651
+ </>
652
+ ) : null}
653
+ {onSendChangesToAgent ? (
654
+ <>
655
+ {toolbarStatus}
656
+ <SendChangesToAgentButton
657
+ onSend={onSendChangesToAgent}
658
+ hasUnsentChanges={hasUnsentChanges}
659
+ isSending={isSendingChanges}
660
+ disabled={sendChangesDisabled}
661
+ />
662
+ </>
663
+ ) : null}
664
+ </div>
665
+ ) : null}
666
+ </div>
667
+
668
+ {onSendComment && commentSelection ? (
669
+ <SelectionComment
670
+ editor={editor}
671
+ selection={commentSelection}
672
+ composing={composing}
673
+ draft={draft}
674
+ onStart={startComment}
675
+ onDraftChange={setDraft}
676
+ onCancel={cancelDraft}
677
+ onAdd={addComment}
678
+ />
679
+ ) : null}
680
+
681
+ {showList && pending.length > 0 ? (
682
+ <ul className="max-h-48 shrink-0 space-y-1.5 overflow-y-auto border-b border-mixer-muted/15 px-3 py-2">
683
+ {pending.map((entry) => (
684
+ <li
685
+ key={entry.id}
686
+ className="flex items-start gap-2 rounded-md border border-mixer-muted/20 bg-muted/10 px-2 py-1.5"
687
+ >
688
+ <div className="min-w-0 flex-1">
689
+ <div className="truncate text-[11px] italic text-muted">“{entry.quote}”</div>
690
+ <div className="whitespace-pre-wrap text-xs text-foreground">{entry.comment}</div>
691
+ </div>
692
+ <Button
693
+ variant="ghost"
694
+ size="sm"
695
+ aria-label={t('agent.deleteComment')}
696
+ title={t('agent.deleteComment')}
697
+ className="size-6 shrink-0 p-0 text-muted"
698
+ onClick={() => removeComment(entry.id)}
699
+ >
700
+ <X className="size-3.5" />
701
+ </Button>
702
+ </li>
703
+ ))}
704
+ </ul>
705
+ ) : null}
706
+ </>
707
+ );
708
+ }