@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
@@ -24,11 +24,12 @@ import {
24
24
  useToast,
25
25
  } from '@vertesia/ui/core';
26
26
  import { useUITranslation } from '@vertesia/ui/i18n';
27
- import { NavLink } from '@vertesia/ui/router';
27
+ import { NavLink, useNavigate } from '@vertesia/ui/router';
28
28
  import { useUserSession } from '@vertesia/ui/session';
29
29
  import { JSONDisplay, Progress } from '@vertesia/ui/widgets';
30
- import { AlertTriangle, Copy, Download, FileSearch, ScanSearch, SquarePen } from 'lucide-react';
31
- import { memo, type RefObject, useEffect, useMemo, useRef, useState } from 'react';
30
+ import { AlertTriangle, Copy, Download, FileSearch, ScanSearch, Sparkles, SquarePen } from 'lucide-react';
31
+ import { memo, type RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react';
32
+ import type { SendAgentMessageFn } from '../../../agent/chat/ModernAgentConversation.js';
32
33
  import {
33
34
  type UniversalDocumentConverter,
34
35
  type UniversalDocumentSource,
@@ -44,6 +45,15 @@ import { AudioPanel, ImagePanel, VideoPanel } from '../../../media-viewer';
44
45
  import { SecureButton } from '../../../permissions/SecureButton.js';
45
46
  import { getWorkflowStatusColor, getWorkflowStatusName, isPreviewableAsPdf } from '../../../utils/index.js';
46
47
  import { resolveTypeCached } from '../../types/typeCatalogCache.js';
48
+ import { DocumentEditingPanel } from './DocumentEditingPanel.js';
49
+ import {
50
+ createDocumentEditingScopeKey,
51
+ getDocumentTextActionAccess,
52
+ isDocumentEditingContentType,
53
+ isDocumentEditingScopeOpen,
54
+ isMarkdownContentType,
55
+ setDocumentEditingScopeOpen,
56
+ } from './documentEditingRun.js';
47
57
  import { PropertiesEditorModal } from './PropertiesEditorModal';
48
58
  import { TextEditorPanel } from './TextEditorPanel.js';
49
59
  import { useObjectText, useOfficePdfConversion, usePdfProcessingStatus } from './useContentPanelHooks.js';
@@ -60,6 +70,9 @@ interface TextActionsProps {
60
70
  isEditing?: boolean;
61
71
  onToggleEdit?: () => void;
62
72
  canEdit?: boolean;
73
+ isCollaborating?: boolean;
74
+ onToggleCollaborate?: () => void;
75
+ canCollaborate?: boolean;
63
76
  }
64
77
 
65
78
  interface TextPanelProps {
@@ -178,10 +191,87 @@ interface ContentOverviewProps {
178
191
  loadText?: boolean;
179
192
  refetch?: () => Promise<unknown>;
180
193
  canEditProperties?: boolean;
194
+ canCollaborate?: boolean;
181
195
  }
182
- export function ContentOverview({ object, loadText, refetch, canEditProperties = true }: ContentOverviewProps) {
196
+ export function ContentOverview({
197
+ object,
198
+ loadText,
199
+ refetch,
200
+ canEditProperties = true,
201
+ canCollaborate = false,
202
+ }: ContentOverviewProps) {
183
203
  const toast = useToast();
184
204
  const { t } = useUITranslation();
205
+ const { project, store } = useUserSession();
206
+ const navigate = useNavigate();
207
+ const documentRootId = object.revision?.root || object.id;
208
+ const editingScopeKey = project?.id ? createDocumentEditingScopeKey(project.id, documentRootId) : undefined;
209
+ const [activeObject, setActiveObject] = useState(object);
210
+ const [isCollaborating, setIsCollaborating] = useState(() =>
211
+ editingScopeKey ? isDocumentEditingScopeOpen(editingScopeKey) : false,
212
+ );
213
+ const sendMessageRef = useRef<SendAgentMessageFn | null>(null);
214
+ const latestDocumentIdRef = useRef(object.id);
215
+ const sourceObjectIdRef = useRef(object.id);
216
+ const editingScopeKeyRef = useRef(editingScopeKey);
217
+
218
+ useEffect(() => {
219
+ if (editingScopeKeyRef.current === editingScopeKey) return;
220
+ editingScopeKeyRef.current = editingScopeKey;
221
+ setIsCollaborating(editingScopeKey ? isDocumentEditingScopeOpen(editingScopeKey) : false);
222
+ }, [editingScopeKey]);
223
+
224
+ const toggleCollaboration = useCallback(() => {
225
+ setIsCollaborating((current) => {
226
+ const next = !current;
227
+ if (editingScopeKey) setDocumentEditingScopeOpen(editingScopeKey, next);
228
+ return next;
229
+ });
230
+ }, [editingScopeKey]);
231
+
232
+ const closeCollaboration = useCallback(() => {
233
+ if (editingScopeKey) setDocumentEditingScopeOpen(editingScopeKey, false);
234
+ setIsCollaborating(false);
235
+ }, [editingScopeKey]);
236
+
237
+ useEffect(() => {
238
+ const sourceObjectChanged = sourceObjectIdRef.current !== object.id;
239
+ sourceObjectIdRef.current = object.id;
240
+ setActiveObject((current) => {
241
+ if (!sourceObjectChanged && current.id !== object.id) return current;
242
+ latestDocumentIdRef.current = object.id;
243
+ return current.id === object.id && current.text && !object.text
244
+ ? { ...object, text: current.text }
245
+ : object;
246
+ });
247
+ }, [object]);
248
+
249
+ const handleDocumentUpdated = useCallback(
250
+ async (updatedDocumentId: string, createdVersion: boolean) => {
251
+ latestDocumentIdRef.current = updatedDocumentId;
252
+
253
+ try {
254
+ const [updatedObject, updatedText] = await Promise.all([
255
+ store.objects.retrieve(updatedDocumentId),
256
+ store.objects.getObjectText(updatedDocumentId),
257
+ ]);
258
+ if (latestDocumentIdRef.current === updatedDocumentId) {
259
+ setActiveObject({ ...updatedObject, text: updatedText.text });
260
+ }
261
+ } catch (err: unknown) {
262
+ console.error('Failed to load the updated document revision', err);
263
+ toast({
264
+ status: 'error',
265
+ title: t('agent.failedToLoadDocument'),
266
+ description: err instanceof Error ? err.message : undefined,
267
+ duration: 5000,
268
+ });
269
+ } finally {
270
+ if (createdVersion) navigate(`/objects/${updatedDocumentId}`);
271
+ }
272
+ },
273
+ [navigate, store.objects, t, toast],
274
+ );
185
275
 
186
276
  const handleCopyContent = async (content: string, type: 'text' | 'properties') => {
187
277
  try {
@@ -206,25 +296,41 @@ export function ContentOverview({ object, loadText, refetch, canEditProperties =
206
296
  };
207
297
 
208
298
  return (
209
- <ResizablePanelGroup direction="horizontal" className="h-full">
210
- <ResizablePanel defaultSize={67} className="min-w-[100px]">
211
- <DataPanel
212
- object={object}
213
- loadText={loadText ?? false}
214
- handleCopyContent={handleCopyContent}
215
- refetch={refetch}
216
- />
217
- </ResizablePanel>
218
- <ResizableHandle withHandle />
219
- <ResizablePanel defaultSize={33} className="min-w-[100px]">
220
- <PropertiesPanel
221
- object={object}
222
- refetch={refetch ?? (() => Promise.resolve())}
223
- handleCopyContent={handleCopyContent}
224
- canEditProperties={canEditProperties}
299
+ <>
300
+ <ResizablePanelGroup direction="horizontal" className="h-full">
301
+ <ResizablePanel defaultSize={67} className="min-w-[100px]">
302
+ <DataPanel
303
+ object={activeObject}
304
+ loadText={loadText ?? false}
305
+ handleCopyContent={handleCopyContent}
306
+ refetch={refetch}
307
+ canCollaborate={canCollaborate}
308
+ isCollaborating={isCollaborating}
309
+ onToggleCollaborate={toggleCollaboration}
310
+ />
311
+ </ResizablePanel>
312
+ <ResizableHandle withHandle />
313
+ <ResizablePanel defaultSize={33} className="min-w-[100px]">
314
+ <PropertiesPanel
315
+ object={activeObject}
316
+ refetch={refetch ?? (() => Promise.resolve())}
317
+ handleCopyContent={handleCopyContent}
318
+ canEditProperties={canEditProperties}
319
+ />
320
+ </ResizablePanel>
321
+ </ResizablePanelGroup>
322
+ {isCollaborating ? (
323
+ <DocumentEditingPanel
324
+ object={activeObject}
325
+ initialContent={activeObject.text ?? ''}
326
+ onClose={closeCollaboration}
327
+ onDocumentUpdated={(updatedDocumentId, createdVersion) =>
328
+ void handleDocumentUpdated(updatedDocumentId, createdVersion)
329
+ }
330
+ sendMessageRef={sendMessageRef}
225
331
  />
226
- </ResizablePanel>
227
- </ResizablePanelGroup>
332
+ ) : null}
333
+ </>
228
334
  );
229
335
  }
230
336
 
@@ -349,6 +455,9 @@ interface DataPanelProps {
349
455
  loadText: boolean;
350
456
  handleCopyContent: (content: string, type: 'text' | 'properties') => Promise<void>;
351
457
  refetch?: () => Promise<unknown>;
458
+ canCollaborate: boolean;
459
+ isCollaborating: boolean;
460
+ onToggleCollaborate: () => void;
352
461
  }
353
462
 
354
463
  /**
@@ -386,6 +495,9 @@ function DataPanelContent({
386
495
  handleCopyContent,
387
496
  refetch,
388
497
  defaultView,
498
+ canCollaborate,
499
+ isCollaborating,
500
+ onToggleCollaborate,
389
501
  }: DataPanelProps & { defaultView?: IntakeDefaultView }) {
390
502
  const { client } = useUserSession();
391
503
  const { t } = useUITranslation();
@@ -439,15 +551,14 @@ function DataPanelContent({
439
551
  // Text editing state
440
552
  const [isEditing, setIsEditing] = useState(false);
441
553
  const canEdit = !!(
442
- object.content?.source &&
443
554
  object.content?.type &&
444
- !isCreatedOrProcessing &&
445
555
  !object.is_locked &&
446
556
  object.user_permissions?.can_write !== false &&
447
557
  (object.content.type.startsWith('text/') ||
448
558
  object.content.type === 'application/json' ||
449
559
  object.content.type === 'application/xml')
450
560
  );
561
+ const textActionAccess = getDocumentTextActionAccess(canEdit, canCollaborate);
451
562
 
452
563
  // Use custom hooks for text loading, PDF processing, and Office conversion
453
564
  const {
@@ -456,7 +567,7 @@ function DataPanelContent({
456
567
  isLoading: isLoadingText,
457
568
  isCropped: isTextCropped,
458
569
  loadText: reloadText,
459
- } = useObjectText(object.id, object.text, loadText);
570
+ } = useObjectText(object.id, object.text, loadText, object.revision?.root || object.id);
460
571
 
461
572
  // Only poll while the active panel can actually surface processing progress.
462
573
  const shouldPollProgress =
@@ -624,7 +735,10 @@ function DataPanelContent({
624
735
  textContainerRef={textContainerRef}
625
736
  isEditing={isEditing}
626
737
  onToggleEdit={() => setIsEditing(true)}
627
- canEdit={canEdit}
738
+ canEdit={textActionAccess.canEdit}
739
+ isCollaborating={isCollaborating}
740
+ onToggleCollaborate={onToggleCollaborate}
741
+ canCollaborate={textActionAccess.canCollaborate}
628
742
  />
629
743
  )}
630
744
  {currentPanel === PanelView.Pdf && isPreviewableAsPdfDoc && (pdfRendition || officePdfUrl) && (
@@ -673,23 +787,26 @@ function DataPanelContent({
673
787
  <PdfProcessingPanel progress={pdfProgress} status={pdfStatus} />
674
788
  </div>
675
789
  )}
676
- {currentPanel === PanelView.Text && !showProcessingPanel && !isEditing && isLoadingText && (
790
+ {currentPanel === PanelView.Text && !showProcessingPanel && !isEditing && isLoadingText && !displayText && (
677
791
  <div className={getPanelVisibility(true)}>
678
792
  <div className="flex justify-center items-center flex-1">
679
793
  <Spinner size="lg" />
680
794
  </div>
681
795
  </div>
682
796
  )}
683
- {currentPanel === PanelView.Text && !showProcessingPanel && !isEditing && !isLoadingText && (
684
- <div className={getPanelVisibility(true)}>
685
- <TextPanel
686
- object={object}
687
- text={displayText}
688
- isTextCropped={isTextCropped}
689
- textContainerRef={textContainerRef}
690
- />
691
- </div>
692
- )}
797
+ {currentPanel === PanelView.Text &&
798
+ !showProcessingPanel &&
799
+ !isEditing &&
800
+ (!isLoadingText || displayText) && (
801
+ <div className={getPanelVisibility(true)}>
802
+ <TextPanel
803
+ object={object}
804
+ text={displayText}
805
+ isTextCropped={isTextCropped}
806
+ textContainerRef={textContainerRef}
807
+ />
808
+ </div>
809
+ )}
693
810
  {isEditing && currentPanel === PanelView.Text && fullText != null && (
694
811
  <TextEditorPanel
695
812
  object={object}
@@ -706,7 +823,17 @@ function DataPanelContent({
706
823
  );
707
824
  }
708
825
 
709
- function TextActions({ object, text, fullText, handleCopyContent, onToggleEdit, canEdit }: TextActionsProps) {
826
+ function TextActions({
827
+ object,
828
+ text,
829
+ fullText,
830
+ handleCopyContent,
831
+ onToggleEdit,
832
+ canEdit,
833
+ isCollaborating,
834
+ onToggleCollaborate,
835
+ canCollaborate,
836
+ }: TextActionsProps) {
710
837
  const { client, project } = useUserSession();
711
838
  const toast = useToast();
712
839
  const { t } = useUITranslation();
@@ -718,7 +845,8 @@ function TextActions({ object, text, fullText, handleCopyContent, onToggleEdit,
718
845
  );
719
846
  const pdfTemplateObjectId = fullProject?.configuration?.pdf_template_object_id;
720
847
 
721
- const isMarkdown = content?.type && content.type === 'text/markdown';
848
+ const isMarkdown = isMarkdownContentType(content?.type);
849
+ const isDocumentEditingContent = isDocumentEditingContentType(content?.type);
722
850
 
723
851
  const handleExportDocument = async (format: MarkdownRenditionFormat, useDefaultTemplate?: boolean) => {
724
852
  // Prevent multiple concurrent exports
@@ -781,31 +909,24 @@ function TextActions({ object, text, fullText, handleCopyContent, onToggleEdit,
781
909
  <div className="h-[41px] text-lg font-semibold flex justify-between items-center px-2">
782
910
  <div className="flex items-center gap-2">
783
911
  {fullText && (
784
- <>
785
- <Button
786
- variant="ghost"
787
- size="sm"
788
- title="Copy text"
789
- onClick={() => handleCopyContent(fullText, 'text')}
790
- >
791
- <Copy className="size-4" />
792
- </Button>
793
- {canEdit && onToggleEdit && (
794
- <SecureButton
795
- permission={Permission.content_write}
796
- variant="ghost"
797
- size="sm"
798
- onClick={onToggleEdit}
799
- title={t('store.editText')}
800
- className="flex items-center gap-2"
801
- >
802
- <SquarePen className="size-4" />
803
- </SecureButton>
804
- )}
805
- </>
912
+ <Button
913
+ variant="ghost"
914
+ size="sm"
915
+ title="Copy text"
916
+ onClick={() => handleCopyContent(fullText, 'text')}
917
+ >
918
+ <Copy className="size-4" />
919
+ </Button>
806
920
  )}
807
921
  {isDownloading ? (
808
- <Button variant="ghost" size="sm" disabled className="flex items-center gap-2" title="download">
922
+ <Button
923
+ variant="ghost"
924
+ size="sm"
925
+ disabled
926
+ className="h-8 w-8 p-0"
927
+ title={t('agent.download')}
928
+ aria-label={t('agent.download')}
929
+ >
809
930
  <Spinner size="sm" />
810
931
  </Button>
811
932
  ) : (
@@ -815,8 +936,9 @@ function TextActions({ object, text, fullText, handleCopyContent, onToggleEdit,
815
936
  variant="ghost"
816
937
  size="sm"
817
938
  disabled={!text}
818
- className="flex items-center gap-2"
819
- title="download"
939
+ className="h-8 w-8 p-0"
940
+ title={t('agent.download')}
941
+ aria-label={t('agent.download')}
820
942
  >
821
943
  <Download className="size-4" />
822
944
  </Button>
@@ -854,6 +976,35 @@ function TextActions({ object, text, fullText, handleCopyContent, onToggleEdit,
854
976
  )}
855
977
  </Dropdown>
856
978
  )}
979
+ {fullText && (
980
+ <>
981
+ {canEdit && onToggleEdit && (
982
+ <SecureButton
983
+ permission={Permission.content_write}
984
+ variant="ghost"
985
+ size="sm"
986
+ onClick={onToggleEdit}
987
+ title={t('store.editText')}
988
+ aria-label={t('store.editText')}
989
+ className="h-8 w-8 p-0"
990
+ >
991
+ <SquarePen className="size-4" />
992
+ </SecureButton>
993
+ )}
994
+ {canCollaborate && onToggleCollaborate && isDocumentEditingContent && (
995
+ <Button
996
+ variant={isCollaborating ? 'primary' : 'ghost'}
997
+ size="sm"
998
+ onClick={onToggleCollaborate}
999
+ title={t('agent.editWithAI')}
1000
+ aria-label={t('agent.editWithAI')}
1001
+ className="h-8 w-8 p-0"
1002
+ >
1003
+ <Sparkles className="size-4" />
1004
+ </Button>
1005
+ )}
1006
+ </>
1007
+ )}
857
1008
  </div>
858
1009
  </div>
859
1010
  );
@@ -867,7 +1018,7 @@ const TextPanel = memo(({ object, text, isTextCropped, textContainerRef }: TextP
867
1018
  const isXml = contentProcessorType === 'xml' || content?.type === 'application/xml' || content?.type === 'text/xml';
868
1019
 
869
1020
  // Check if content type is markdown or plain text
870
- const isMarkdownOrText = content?.type && (content.type === 'text/markdown' || content.type === 'text/plain');
1021
+ const isMarkdownOrText = isDocumentEditingContentType(content?.type);
871
1022
 
872
1023
  // Render as markdown if it's markdown/text type OR if text looks like markdown (but not if XML)
873
1024
  const shouldRenderAsMarkdown = !isXml && (isMarkdownOrText || looksLikeMarkdown(text));
@@ -0,0 +1,34 @@
1
+ import type { Project } from '@vertesia/common';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { getDocumentEditingProjectDefault } from './DocumentEditingConfigurationSelector.js';
4
+
5
+ describe('document editing configuration', () => {
6
+ it('prefers the project agent default over the base default', () => {
7
+ const project = {
8
+ configuration: {
9
+ defaults: {
10
+ base: { environment: 'base-env', model: 'base-model' },
11
+ system: { agent: { environment: 'agent-env', model: 'agent-model' } },
12
+ },
13
+ },
14
+ } as Pick<Project, 'configuration'>;
15
+
16
+ expect(getDocumentEditingProjectDefault(project)).toEqual({
17
+ environment: 'agent-env',
18
+ model: 'agent-model',
19
+ });
20
+ });
21
+
22
+ it('falls back to the project base default', () => {
23
+ const project = {
24
+ configuration: {
25
+ defaults: { base: { environment: 'base-env', model: 'base-model' } },
26
+ },
27
+ } as Pick<Project, 'configuration'>;
28
+
29
+ expect(getDocumentEditingProjectDefault(project)).toEqual({
30
+ environment: 'base-env',
31
+ model: 'base-model',
32
+ });
33
+ });
34
+ });
@@ -0,0 +1,211 @@
1
+ import type { ExecutionEnvironmentRef, InteractionExecutionConfiguration, Project } from '@vertesia/common';
2
+ import { Button, Popover, PopoverContent, PopoverTrigger, SelectBox, Spinner } from '@vertesia/ui/core';
3
+ import { useUITranslation } from '@vertesia/ui/i18n';
4
+ import { useUserSession } from '@vertesia/ui/session';
5
+ import { ChevronDown, SlidersHorizontal } from 'lucide-react';
6
+ import { useEffect, useMemo, useRef, useState } from 'react';
7
+
8
+ export interface DocumentEditingConfiguration {
9
+ environment?: string;
10
+ model?: string;
11
+ model_options?: InteractionExecutionConfiguration['model_options'];
12
+ }
13
+
14
+ interface ModelOption {
15
+ id: string;
16
+ name: string;
17
+ }
18
+
19
+ export function getDocumentEditingProjectDefault(
20
+ project: Pick<Project, 'configuration'>,
21
+ ): DocumentEditingConfiguration {
22
+ const defaults = project.configuration?.defaults?.system?.agent ?? project.configuration?.defaults?.base;
23
+ return {
24
+ environment: defaults?.environment,
25
+ model: defaults?.model,
26
+ };
27
+ }
28
+
29
+ function environmentLabel(environment: ExecutionEnvironmentRef): string {
30
+ return environment.name;
31
+ }
32
+
33
+ function modelLabel(model: ModelOption): string {
34
+ return model.name;
35
+ }
36
+
37
+ /** Provider model ids can be long resource paths; display only the final segment. */
38
+ function shortModelName(model: string): string {
39
+ const segment = model.split('/').filter(Boolean).pop();
40
+ return segment || model;
41
+ }
42
+
43
+ interface DocumentEditingConfigurationSelectorProps {
44
+ value: DocumentEditingConfiguration;
45
+ onChange: (value: DocumentEditingConfiguration) => void;
46
+ disabled?: boolean;
47
+ isLoading?: boolean;
48
+ }
49
+
50
+ export function DocumentEditingConfigurationSelector({
51
+ value,
52
+ onChange,
53
+ disabled = false,
54
+ isLoading = false,
55
+ }: DocumentEditingConfigurationSelectorProps) {
56
+ const { client } = useUserSession();
57
+ const { t } = useUITranslation();
58
+ const [environments, setEnvironments] = useState<ExecutionEnvironmentRef[]>([]);
59
+ const [models, setModels] = useState<ModelOption[]>([]);
60
+ const [isLoadingEnvironments, setIsLoadingEnvironments] = useState(true);
61
+ const [isLoadingModels, setIsLoadingModels] = useState(false);
62
+ const latestEnvironmentRequestRef = useRef<string | undefined>(undefined);
63
+
64
+ useEffect(() => {
65
+ let cancelled = false;
66
+ setIsLoadingEnvironments(true);
67
+ void client.environments
68
+ .list()
69
+ .then((items) => {
70
+ if (!cancelled) setEnvironments(items.slice().sort((a, b) => a.name.localeCompare(b.name)));
71
+ })
72
+ .catch((err: unknown) => {
73
+ console.warn('Failed to list document editing environments', err);
74
+ if (!cancelled) setEnvironments([]);
75
+ })
76
+ .finally(() => {
77
+ if (!cancelled) setIsLoadingEnvironments(false);
78
+ });
79
+ return () => {
80
+ cancelled = true;
81
+ };
82
+ }, [client]);
83
+
84
+ useEffect(() => {
85
+ if (!value.environment) {
86
+ setModels([]);
87
+ setIsLoadingModels(false);
88
+ return;
89
+ }
90
+
91
+ let cancelled = false;
92
+ const environmentId = value.environment;
93
+ setIsLoadingModels(true);
94
+ void client.environments
95
+ .retrieve(environmentId)
96
+ .then((environment) => {
97
+ if (cancelled) return;
98
+ setModels(
99
+ (environment.enabled_models ?? [])
100
+ .map((model) => ({ id: model.id, name: model.name || model.id }))
101
+ .sort((a, b) => a.name.localeCompare(b.name)),
102
+ );
103
+ })
104
+ .catch((err: unknown) => {
105
+ console.warn('Failed to load document editing models', err);
106
+ if (!cancelled) setModels([]);
107
+ })
108
+ .finally(() => {
109
+ if (!cancelled) setIsLoadingModels(false);
110
+ });
111
+ return () => {
112
+ cancelled = true;
113
+ };
114
+ }, [client, value.environment]);
115
+
116
+ const selectedEnvironment = environments.find((environment) => environment.id === value.environment);
117
+ const selectedModel = useMemo<ModelOption | undefined>(() => {
118
+ if (!value.model) return undefined;
119
+ return (
120
+ models.find((model) => model.id === value.model) ?? { id: value.model, name: shortModelName(value.model) }
121
+ );
122
+ }, [models, value.model]);
123
+
124
+ const selectEnvironment = (environment: ExecutionEnvironmentRef) => {
125
+ latestEnvironmentRequestRef.current = environment.id;
126
+ onChange({ environment: environment.id });
127
+ void client.environments
128
+ .retrieve(environment.id)
129
+ .then((details) => {
130
+ if (latestEnvironmentRequestRef.current !== environment.id) return;
131
+ const nextModel = details.default_model ?? details.enabled_models?.[0]?.id;
132
+ onChange({ environment: environment.id, model: nextModel });
133
+ })
134
+ .catch((err: unknown) => {
135
+ console.warn('Failed to select a default document editing model', err);
136
+ });
137
+ };
138
+
139
+ const selectModel = (model: ModelOption) => {
140
+ latestEnvironmentRequestRef.current = undefined;
141
+ onChange({ environment: value.environment, model: model.id });
142
+ };
143
+
144
+ const buttonLabel = selectedModel?.name || t('agent.documentEditingSelectModel');
145
+
146
+ return (
147
+ <Popover>
148
+ <PopoverTrigger asChild>
149
+ <Button
150
+ type="button"
151
+ variant="ghost"
152
+ size="sm"
153
+ className="h-7 max-w-[15rem] gap-1.5 px-2 text-xs"
154
+ aria-label={t('agent.documentEditingConfiguration')}
155
+ title={t('agent.documentEditingConfiguration')}
156
+ >
157
+ {isLoading ? <Spinner size="sm" /> : <SlidersHorizontal className="size-3.5 shrink-0" />}
158
+ <span className="min-w-0 truncate">{buttonLabel}</span>
159
+ <ChevronDown className="size-3.5 shrink-0 opacity-70" />
160
+ </Button>
161
+ </PopoverTrigger>
162
+ <PopoverContent align="end" className="z-[1000000] w-80 space-y-3 p-3">
163
+ <div>
164
+ <div className="text-sm font-semibold text-foreground">
165
+ {t('agent.documentEditingConfiguration')}
166
+ </div>
167
+ <div className="mt-0.5 text-xs leading-4 text-muted">
168
+ {disabled
169
+ ? t('agent.documentEditingConfigurationLocked')
170
+ : t('agent.documentEditingConfigurationDescription')}
171
+ </div>
172
+ </div>
173
+ <div className="space-y-1">
174
+ <label className="text-xs font-medium text-foreground" htmlFor="document-editing-environment">
175
+ {t('agent.environment')}
176
+ </label>
177
+ <SelectBox
178
+ id="document-editing-environment"
179
+ by="id"
180
+ options={environments}
181
+ value={selectedEnvironment}
182
+ optionLabel={environmentLabel}
183
+ filterBy="name"
184
+ placeholder={t('agent.documentEditingSelectEnvironment')}
185
+ onChange={selectEnvironment}
186
+ disabled={disabled}
187
+ isLoading={isLoadingEnvironments}
188
+ />
189
+ </div>
190
+ <div className="space-y-1">
191
+ <label className="text-xs font-medium text-foreground" htmlFor="document-editing-model">
192
+ {t('agent.model')}
193
+ </label>
194
+ <SelectBox
195
+ id="document-editing-model"
196
+ by="id"
197
+ options={models}
198
+ value={selectedModel}
199
+ optionLabel={modelLabel}
200
+ filterBy="name"
201
+ placeholder={t('agent.documentEditingSelectModel')}
202
+ onChange={selectModel}
203
+ disabled={disabled || !value.environment}
204
+ isLoading={isLoadingModels}
205
+ warnOnMissingValue={false}
206
+ />
207
+ </div>
208
+ </PopoverContent>
209
+ </Popover>
210
+ );
211
+ }