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

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 (329) hide show
  1. package/lib/core/components/SidePanel.js +1 -1
  2. package/lib/core/components/SidePanel.js.map +1 -1
  3. package/lib/core/components/shadcn/button.d.ts +10 -3
  4. package/lib/core/components/shadcn/button.d.ts.map +1 -1
  5. package/lib/core/components/shadcn/button.js +41 -9
  6. package/lib/core/components/shadcn/button.js.map +1 -1
  7. package/lib/core/components/shadcn/modal/dialog.js +1 -1
  8. package/lib/core/components/shadcn/modal/dialog.js.map +1 -1
  9. package/lib/core/components/shadcn/theme/ThemeSwitcher.js +1 -1
  10. package/lib/core/components/shadcn/theme/ThemeSwitcher.js.map +1 -1
  11. package/lib/features/agent/chat/AgentChatPlaybackControls.d.ts.map +1 -1
  12. package/lib/features/agent/chat/AgentChatPlaybackControls.js +2 -2
  13. package/lib/features/agent/chat/AgentChatPlaybackControls.js.map +1 -1
  14. package/lib/features/agent/chat/AgentRequestInputOverlay.d.ts.map +1 -1
  15. package/lib/features/agent/chat/AgentRequestInputOverlay.js +2 -2
  16. package/lib/features/agent/chat/AgentRequestInputOverlay.js.map +1 -1
  17. package/lib/features/agent/chat/ArtifactsTab.d.ts.map +1 -1
  18. package/lib/features/agent/chat/ArtifactsTab.js +5 -2
  19. package/lib/features/agent/chat/ArtifactsTab.js.map +1 -1
  20. package/lib/features/agent/chat/ImageLightbox.d.ts.map +1 -1
  21. package/lib/features/agent/chat/ImageLightbox.js +1 -1
  22. package/lib/features/agent/chat/ImageLightbox.js.map +1 -1
  23. package/lib/features/agent/chat/ModernAgentConversation.js +1 -1
  24. package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
  25. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
  26. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +5 -5
  27. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
  28. package/lib/features/agent/chat/ModernAgentOutput/Header.d.ts.map +1 -1
  29. package/lib/features/agent/chat/ModernAgentOutput/Header.js +4 -2
  30. package/lib/features/agent/chat/ModernAgentOutput/Header.js.map +1 -1
  31. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts.map +1 -1
  32. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js +2 -2
  33. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js.map +1 -1
  34. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
  35. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js +2 -1
  36. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
  37. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.d.ts +6 -0
  38. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.d.ts.map +1 -1
  39. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.js +15 -1
  40. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.js.map +1 -1
  41. package/lib/features/environment/SelectEnvironment.d.ts +28 -0
  42. package/lib/features/environment/SelectEnvironment.d.ts.map +1 -0
  43. package/lib/features/environment/SelectEnvironment.js +97 -0
  44. package/lib/features/environment/SelectEnvironment.js.map +1 -0
  45. package/lib/features/environment/index.d.ts +2 -0
  46. package/lib/features/environment/index.d.ts.map +1 -0
  47. package/lib/features/environment/index.js +2 -0
  48. package/lib/features/environment/index.js.map +1 -0
  49. package/lib/features/index.d.ts +2 -0
  50. package/lib/features/index.d.ts.map +1 -1
  51. package/lib/features/index.js +2 -0
  52. package/lib/features/index.js.map +1 -1
  53. package/lib/features/magic-pdf/AnnotatedImageSlider.js +12 -12
  54. package/lib/features/magic-pdf/AnnotatedImageSlider.js.map +1 -1
  55. package/lib/features/magic-pdf/DownloadPopover.d.ts.map +1 -1
  56. package/lib/features/magic-pdf/DownloadPopover.js +3 -17
  57. package/lib/features/magic-pdf/DownloadPopover.js.map +1 -1
  58. package/lib/features/magic-pdf/ExtractedContentView.js +2 -6
  59. package/lib/features/magic-pdf/ExtractedContentView.js.map +1 -1
  60. package/lib/features/magic-pdf/GroundedExtractionAssistantPanel.d.ts +25 -0
  61. package/lib/features/magic-pdf/GroundedExtractionAssistantPanel.d.ts.map +1 -0
  62. package/lib/features/magic-pdf/GroundedExtractionAssistantPanel.js +42 -0
  63. package/lib/features/magic-pdf/GroundedExtractionAssistantPanel.js.map +1 -0
  64. package/lib/features/magic-pdf/GroundedExtractionView.d.ts +27 -0
  65. package/lib/features/magic-pdf/GroundedExtractionView.d.ts.map +1 -0
  66. package/lib/features/magic-pdf/GroundedExtractionView.js +377 -0
  67. package/lib/features/magic-pdf/GroundedExtractionView.js.map +1 -0
  68. package/lib/features/magic-pdf/MagicPdfProvider.d.ts +0 -2
  69. package/lib/features/magic-pdf/MagicPdfProvider.d.ts.map +1 -1
  70. package/lib/features/magic-pdf/MagicPdfProvider.js +15 -42
  71. package/lib/features/magic-pdf/MagicPdfProvider.js.map +1 -1
  72. package/lib/features/magic-pdf/MagicPdfView.d.ts.map +1 -1
  73. package/lib/features/magic-pdf/MagicPdfView.js +2 -19
  74. package/lib/features/magic-pdf/MagicPdfView.js.map +1 -1
  75. package/lib/features/magic-pdf/index.d.ts +1 -0
  76. package/lib/features/magic-pdf/index.d.ts.map +1 -1
  77. package/lib/features/magic-pdf/index.js +1 -0
  78. package/lib/features/magic-pdf/index.js.map +1 -1
  79. package/lib/features/magic-pdf/types.d.ts +1 -1
  80. package/lib/features/magic-pdf/types.d.ts.map +1 -1
  81. package/lib/features/oauth/McpConnectionsButton.d.ts.map +1 -1
  82. package/lib/features/oauth/McpConnectionsButton.js +2 -2
  83. package/lib/features/oauth/McpConnectionsButton.js.map +1 -1
  84. package/lib/features/oauth/McpConnectionsDialog.d.ts.map +1 -1
  85. package/lib/features/oauth/McpConnectionsDialog.js +1 -1
  86. package/lib/features/oauth/McpConnectionsDialog.js.map +1 -1
  87. package/lib/features/oauth/RemoteMcpConnectionButton.d.ts.map +1 -1
  88. package/lib/features/oauth/RemoteMcpConnectionButton.js +1 -1
  89. package/lib/features/oauth/RemoteMcpConnectionButton.js.map +1 -1
  90. package/lib/features/pdf-viewer/PdfPageSlider.js +2 -2
  91. package/lib/features/pdf-viewer/PdfPageSlider.js.map +1 -1
  92. package/lib/features/pdf-viewer/SimplePdfViewer.d.ts.map +1 -1
  93. package/lib/features/pdf-viewer/SimplePdfViewer.js +3 -3
  94. package/lib/features/pdf-viewer/SimplePdfViewer.js.map +1 -1
  95. package/lib/features/permissions/UserPermissionsProvider.js +1 -1
  96. package/lib/features/permissions/UserPermissionsProvider.js.map +1 -1
  97. package/lib/features/store/objects/DocumentQuickFilter.d.ts.map +1 -1
  98. package/lib/features/store/objects/DocumentQuickFilter.js +5 -5
  99. package/lib/features/store/objects/DocumentQuickFilter.js.map +1 -1
  100. package/lib/features/store/objects/DocumentSearchResults.d.ts.map +1 -1
  101. package/lib/features/store/objects/DocumentSearchResults.js +2 -2
  102. package/lib/features/store/objects/DocumentSearchResults.js.map +1 -1
  103. package/lib/features/store/objects/components/ContentDispositionButton.d.ts.map +1 -1
  104. package/lib/features/store/objects/components/ContentDispositionButton.js +1 -1
  105. package/lib/features/store/objects/components/ContentDispositionButton.js.map +1 -1
  106. package/lib/features/store/objects/components/ContentOverview.d.ts.map +1 -1
  107. package/lib/features/store/objects/components/ContentOverview.js +78 -21
  108. package/lib/features/store/objects/components/ContentOverview.js.map +1 -1
  109. package/lib/features/store/objects/components/DocumentIcon.js +1 -1
  110. package/lib/features/store/objects/components/VectorSearchWidget.js +1 -1
  111. package/lib/features/store/objects/components/VectorSearchWidget.js.map +1 -1
  112. package/lib/features/store/objects/components/useContentPanelHooks.d.ts +1 -2
  113. package/lib/features/store/objects/components/useContentPanelHooks.d.ts.map +1 -1
  114. package/lib/features/store/objects/components/useContentPanelHooks.js +4 -8
  115. package/lib/features/store/objects/components/useContentPanelHooks.js.map +1 -1
  116. package/lib/features/store/objects/layout/renderers.d.ts.map +1 -1
  117. package/lib/features/store/objects/layout/renderers.js +2 -2
  118. package/lib/features/store/objects/layout/renderers.js.map +1 -1
  119. package/lib/features/store/objects/selection/SelectionActions.js +1 -1
  120. package/lib/features/store/objects/selection/SelectionActions.js.map +1 -1
  121. package/lib/features/store/types/ContentObjectTypesTable.d.ts.map +1 -1
  122. package/lib/features/store/types/ContentObjectTypesTable.js +4 -4
  123. package/lib/features/store/types/ContentObjectTypesTable.js.map +1 -1
  124. package/lib/features/store/types/IntakePolicyEditor.d.ts +15 -0
  125. package/lib/features/store/types/IntakePolicyEditor.d.ts.map +1 -0
  126. package/lib/features/store/types/IntakePolicyEditor.js +455 -0
  127. package/lib/features/store/types/IntakePolicyEditor.js.map +1 -0
  128. package/lib/features/store/types/IntakePolicyForm.d.ts +11 -0
  129. package/lib/features/store/types/IntakePolicyForm.d.ts.map +1 -0
  130. package/lib/features/store/types/IntakePolicyForm.js +91 -0
  131. package/lib/features/store/types/IntakePolicyForm.js.map +1 -0
  132. package/lib/features/store/types/SelectContentTypeModal.js +1 -1
  133. package/lib/features/store/types/SelectContentTypeModal.js.map +1 -1
  134. package/lib/features/store/types/index.d.ts +2 -0
  135. package/lib/features/store/types/index.d.ts.map +1 -1
  136. package/lib/features/store/types/index.js +2 -0
  137. package/lib/features/store/types/index.js.map +1 -1
  138. package/lib/features/store/types/intake-policy-editor.logic.d.ts +7 -0
  139. package/lib/features/store/types/intake-policy-editor.logic.d.ts.map +1 -0
  140. package/lib/features/store/types/intake-policy-editor.logic.js +60 -0
  141. package/lib/features/store/types/intake-policy-editor.logic.js.map +1 -0
  142. package/lib/features/store/types/typeCatalogCache.d.ts +5 -0
  143. package/lib/features/store/types/typeCatalogCache.d.ts.map +1 -0
  144. package/lib/features/store/types/typeCatalogCache.js +40 -0
  145. package/lib/features/store/types/typeCatalogCache.js.map +1 -0
  146. package/lib/features/user/UserInfo.js +7 -3
  147. package/lib/features/user/UserInfo.js.map +1 -1
  148. package/lib/features/views/ViewExperience.d.ts +20 -0
  149. package/lib/features/views/ViewExperience.d.ts.map +1 -0
  150. package/lib/features/views/ViewExperience.js +188 -0
  151. package/lib/features/views/ViewExperience.js.map +1 -0
  152. package/lib/features/views/ViewNavigation.d.ts +3 -0
  153. package/lib/features/views/ViewNavigation.d.ts.map +1 -0
  154. package/lib/features/views/ViewNavigation.js +36 -0
  155. package/lib/features/views/ViewNavigation.js.map +1 -0
  156. package/lib/features/views/ViewResults.d.ts +9 -0
  157. package/lib/features/views/ViewResults.d.ts.map +1 -0
  158. package/lib/features/views/ViewResults.js +229 -0
  159. package/lib/features/views/ViewResults.js.map +1 -0
  160. package/lib/features/views/ViewSearch.d.ts +3 -0
  161. package/lib/features/views/ViewSearch.d.ts.map +1 -0
  162. package/lib/features/views/ViewSearch.js +74 -0
  163. package/lib/features/views/ViewSearch.js.map +1 -0
  164. package/lib/features/views/index.d.ts +7 -0
  165. package/lib/features/views/index.d.ts.map +1 -0
  166. package/lib/features/views/index.js +7 -0
  167. package/lib/features/views/index.js.map +1 -0
  168. package/lib/features/views/types.d.ts +39 -0
  169. package/lib/features/views/types.d.ts.map +1 -0
  170. package/lib/features/views/types.js +2 -0
  171. package/lib/features/views/types.js.map +1 -0
  172. package/lib/features/views/viewState.d.ts +14 -0
  173. package/lib/features/views/viewState.d.ts.map +1 -0
  174. package/lib/features/views/viewState.js +117 -0
  175. package/lib/features/views/viewState.js.map +1 -0
  176. package/lib/i18n/locales/ar.json +16 -0
  177. package/lib/i18n/locales/de.json +12 -0
  178. package/lib/i18n/locales/en.json +245 -33
  179. package/lib/i18n/locales/es.json +13 -0
  180. package/lib/i18n/locales/fr.json +13 -0
  181. package/lib/i18n/locales/it.json +13 -0
  182. package/lib/i18n/locales/ja.json +11 -0
  183. package/lib/i18n/locales/ko.json +11 -0
  184. package/lib/i18n/locales/pt.json +13 -0
  185. package/lib/i18n/locales/ru.json +14 -0
  186. package/lib/i18n/locales/tr.json +12 -0
  187. package/lib/i18n/locales/zh-TW.json +11 -0
  188. package/lib/i18n/locales/zh.json +11 -0
  189. package/lib/router/HistoryNavigator.d.ts.map +1 -1
  190. package/lib/router/HistoryNavigator.js +23 -13
  191. package/lib/router/HistoryNavigator.js.map +1 -1
  192. package/lib/router/Nav.d.ts.map +1 -1
  193. package/lib/router/Nav.js +15 -6
  194. package/lib/router/Nav.js.map +1 -1
  195. package/lib/session/UserSession.d.ts.map +1 -1
  196. package/lib/session/UserSession.js +5 -3
  197. package/lib/session/UserSession.js.map +1 -1
  198. package/lib/vertesia-ui-core.js +4 -4
  199. package/lib/vertesia-ui-core.js.map +1 -1
  200. package/lib/vertesia-ui-features.js +34 -27
  201. package/lib/vertesia-ui-features.js.map +1 -1
  202. package/lib/vertesia-ui-i18n.js +15 -15
  203. package/lib/vertesia-ui-i18n.js.map +1 -1
  204. package/lib/vertesia-ui-router.js +1 -1
  205. package/lib/vertesia-ui-router.js.map +1 -1
  206. package/lib/vertesia-ui-session.js +1 -1
  207. package/lib/vertesia-ui-session.js.map +1 -1
  208. package/lib/vertesia-ui-shell.js +1 -1
  209. package/lib/vertesia-ui-shell.js.map +1 -1
  210. package/lib/vertesia-ui-widgets.js +7 -7
  211. package/lib/vertesia-ui-widgets.js.map +1 -1
  212. package/lib/widgets/form/Form.js +1 -1
  213. package/lib/widgets/form/Form.js.map +1 -1
  214. package/lib/widgets/json-view/JSONSwitcher.js +1 -1
  215. package/lib/widgets/json-view/JSONSwitcher.js.map +1 -1
  216. package/lib/widgets/json-view/JSONView.js +6 -6
  217. package/lib/widgets/json-view/JSONView.js.map +1 -1
  218. package/lib/widgets/monacoEditor/MonacoEditor.d.ts.map +1 -1
  219. package/lib/widgets/monacoEditor/MonacoEditor.js +29 -0
  220. package/lib/widgets/monacoEditor/MonacoEditor.js.map +1 -1
  221. package/lib/widgets/schema-editor/ManagedSchema.d.ts +8 -0
  222. package/lib/widgets/schema-editor/ManagedSchema.d.ts.map +1 -1
  223. package/lib/widgets/schema-editor/ManagedSchema.js +16 -0
  224. package/lib/widgets/schema-editor/ManagedSchema.js.map +1 -1
  225. package/lib/widgets/schema-editor/editor/EditableSchemaProperty.d.ts +2 -0
  226. package/lib/widgets/schema-editor/editor/EditableSchemaProperty.d.ts.map +1 -1
  227. package/lib/widgets/schema-editor/editor/EditableSchemaProperty.js +1 -0
  228. package/lib/widgets/schema-editor/editor/EditableSchemaProperty.js.map +1 -1
  229. package/lib/widgets/schema-editor/editor/PropertyViewer.d.ts.map +1 -1
  230. package/lib/widgets/schema-editor/editor/PropertyViewer.js +4 -1
  231. package/lib/widgets/schema-editor/editor/PropertyViewer.js.map +1 -1
  232. package/lib/widgets/schema-editor/editor/SchemaEditor.d.ts.map +1 -1
  233. package/lib/widgets/schema-editor/editor/SchemaEditor.js +22 -11
  234. package/lib/widgets/schema-editor/editor/SchemaEditor.js.map +1 -1
  235. package/package.json +32 -32
  236. package/src/__tests__/a11y.test.tsx +73 -0
  237. package/src/core/components/SidePanel.tsx +1 -1
  238. package/src/core/components/shadcn/button.tsx +67 -12
  239. package/src/core/components/shadcn/modal/dialog.tsx +1 -1
  240. package/src/core/components/shadcn/theme/ThemeSwitcher.tsx +1 -1
  241. package/src/features/agent/chat/AgentChatPlaybackControls.tsx +0 -5
  242. package/src/features/agent/chat/AgentRequestInputOverlay.test.tsx +31 -0
  243. package/src/features/agent/chat/AgentRequestInputOverlay.tsx +6 -2
  244. package/src/features/agent/chat/ArtifactsTab.tsx +19 -9
  245. package/src/features/agent/chat/ImageLightbox.tsx +0 -1
  246. package/src/features/agent/chat/ModernAgentConversation.test.tsx +48 -0
  247. package/src/features/agent/chat/ModernAgentConversation.tsx +0 -2
  248. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.test.tsx +6 -1
  249. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +21 -15
  250. package/src/features/agent/chat/ModernAgentOutput/Header.tsx +3 -3
  251. package/src/features/agent/chat/ModernAgentOutput/MessageInput.tsx +0 -2
  252. package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +4 -1
  253. package/src/features/agent/chat/ModernAgentOutput/requestInputMessages.test.ts +80 -0
  254. package/src/features/agent/chat/ModernAgentOutput/requestInputMessages.ts +21 -1
  255. package/src/features/environment/SelectEnvironment.tsx +186 -0
  256. package/src/features/environment/index.ts +1 -0
  257. package/src/features/index.ts +2 -0
  258. package/src/features/magic-pdf/AnnotatedImageSlider.tsx +62 -66
  259. package/src/features/magic-pdf/DownloadPopover.tsx +5 -48
  260. package/src/features/magic-pdf/ExtractedContentView.tsx +2 -14
  261. package/src/features/magic-pdf/GroundedExtractionAssistantPanel.tsx +86 -0
  262. package/src/features/magic-pdf/GroundedExtractionView.test.tsx +65 -0
  263. package/src/features/magic-pdf/GroundedExtractionView.tsx +1055 -0
  264. package/src/features/magic-pdf/MagicPdfProvider.tsx +17 -47
  265. package/src/features/magic-pdf/MagicPdfView.tsx +3 -51
  266. package/src/features/magic-pdf/index.ts +1 -0
  267. package/src/features/magic-pdf/types.ts +1 -1
  268. package/src/features/oauth/McpConnectionsButton.tsx +0 -2
  269. package/src/features/oauth/McpConnectionsDialog.tsx +0 -1
  270. package/src/features/oauth/RemoteMcpConnectionButton.tsx +0 -1
  271. package/src/features/pdf-viewer/PdfPageSlider.tsx +5 -5
  272. package/src/features/pdf-viewer/SimplePdfViewer.tsx +11 -12
  273. package/src/features/permissions/UserPermissionsProvider.tsx +1 -1
  274. package/src/features/store/objects/DocumentQuickFilter.tsx +13 -15
  275. package/src/features/store/objects/DocumentSearchResults.tsx +3 -6
  276. package/src/features/store/objects/components/ContentDispositionButton.tsx +5 -1
  277. package/src/features/store/objects/components/ContentOverview.tsx +144 -44
  278. package/src/features/store/objects/components/DocumentIcon.tsx +1 -1
  279. package/src/features/store/objects/components/VectorSearchWidget.tsx +2 -2
  280. package/src/features/store/objects/components/useContentPanelHooks.ts +3 -15
  281. package/src/features/store/objects/layout/renderers.tsx +1 -2
  282. package/src/features/store/objects/selection/SelectionActions.tsx +1 -1
  283. package/src/features/store/types/ContentObjectTypesTable.tsx +12 -14
  284. package/src/features/store/types/IntakePolicyEditor.tsx +679 -0
  285. package/src/features/store/types/IntakePolicyForm.tsx +838 -0
  286. package/src/features/store/types/SelectContentTypeModal.tsx +2 -2
  287. package/src/features/store/types/index.ts +2 -0
  288. package/src/features/store/types/intake-policy-editor.logic.test.ts +109 -0
  289. package/src/features/store/types/intake-policy-editor.logic.ts +87 -0
  290. package/src/features/store/types/typeCatalogCache.ts +45 -0
  291. package/src/features/user/UserInfo.tsx +7 -7
  292. package/src/features/views/ViewExperience.test.tsx +273 -0
  293. package/src/features/views/ViewExperience.tsx +408 -0
  294. package/src/features/views/ViewNavigation.test.tsx +86 -0
  295. package/src/features/views/ViewNavigation.tsx +187 -0
  296. package/src/features/views/ViewResults.test.ts +82 -0
  297. package/src/features/views/ViewResults.tsx +609 -0
  298. package/src/features/views/ViewSearch.test.tsx +147 -0
  299. package/src/features/views/ViewSearch.tsx +205 -0
  300. package/src/features/views/index.ts +6 -0
  301. package/src/features/views/types.ts +57 -0
  302. package/src/features/views/viewState.test.ts +111 -0
  303. package/src/features/views/viewState.ts +132 -0
  304. package/src/i18n/locales/ar.json +16 -0
  305. package/src/i18n/locales/de.json +12 -0
  306. package/src/i18n/locales/en.json +965 -753
  307. package/src/i18n/locales/es.json +13 -0
  308. package/src/i18n/locales/fr.json +13 -0
  309. package/src/i18n/locales/it.json +13 -0
  310. package/src/i18n/locales/ja.json +11 -0
  311. package/src/i18n/locales/ko.json +11 -0
  312. package/src/i18n/locales/pt.json +13 -0
  313. package/src/i18n/locales/ru.json +14 -0
  314. package/src/i18n/locales/tr.json +12 -0
  315. package/src/i18n/locales/zh-TW.json +11 -0
  316. package/src/i18n/locales/zh.json +11 -0
  317. package/src/router/HistoryNavigator.test.ts +48 -0
  318. package/src/router/HistoryNavigator.ts +24 -13
  319. package/src/router/Nav.tsx +18 -8
  320. package/src/session/UserSession.test.ts +39 -0
  321. package/src/session/UserSession.ts +5 -3
  322. package/src/widgets/form/Form.tsx +1 -1
  323. package/src/widgets/json-view/JSONSwitcher.tsx +2 -2
  324. package/src/widgets/json-view/JSONView.tsx +7 -7
  325. package/src/widgets/monacoEditor/MonacoEditor.tsx +29 -0
  326. package/src/widgets/schema-editor/ManagedSchema.ts +22 -0
  327. package/src/widgets/schema-editor/editor/EditableSchemaProperty.tsx +3 -0
  328. package/src/widgets/schema-editor/editor/PropertyViewer.tsx +14 -3
  329. package/src/widgets/schema-editor/editor/SchemaEditor.tsx +80 -29
@@ -0,0 +1,679 @@
1
+ import { type ContentObjectType, type ContentTypeIntakePolicy, ContentTypeIntakePolicySchema } from '@vertesia/common';
2
+ import {
3
+ Badge,
4
+ Button,
5
+ Dropdown,
6
+ errorMessage,
7
+ MenuGroup,
8
+ MenuItem,
9
+ Panel,
10
+ Tabs,
11
+ TabsBar,
12
+ TabsPanel,
13
+ useTheme,
14
+ useToast,
15
+ } from '@vertesia/ui/core';
16
+ import { useUITranslation } from '@vertesia/ui/i18n';
17
+ import { useUserSession } from '@vertesia/ui/session';
18
+ import { type EditorApi, type Monaco, MonacoEditor } from '@vertesia/ui/widgets';
19
+ import Ajv, { type ErrorObject, type ValidateFunction } from 'ajv';
20
+ import addFormats from 'ajv-formats';
21
+ import type { TFunction } from 'i18next';
22
+ import { Braces, CheckCircle2, FileText, RotateCcw, Save, WandSparkles } from 'lucide-react';
23
+ import { useMemo, useRef, useState } from 'react';
24
+ import { IntakePolicyForm, type IntakePolicyFormSection } from './IntakePolicyForm.js';
25
+
26
+ interface IntakePolicyEditorProps {
27
+ /** Content type whose intake policy is edited; omit when editing a standalone policy. */
28
+ objectType?: ContentObjectType;
29
+ /** Initial policy value when no objectType is given (e.g. the project default policy). */
30
+ value?: ContentTypeIntakePolicy;
31
+ /** Custom persistence (e.g. save to project configuration). Returns the saved policy.
32
+ * Defaults to saving the objectType's intake when objectType is provided. */
33
+ onSave?: (policy: ContentTypeIntakePolicy) => Promise<ContentTypeIntakePolicy | undefined>;
34
+ onIntakeUpdate?: (value: ContentTypeIntakePolicy | undefined) => void;
35
+ readonly?: boolean;
36
+ }
37
+
38
+ type IntakeExampleKey =
39
+ | 'minimal'
40
+ | 'extraction_only'
41
+ | 'grounded'
42
+ | 'visual_first'
43
+ | 'structured_spreadsheet'
44
+ | 'media_no_transcript'
45
+ | 'full_reference';
46
+
47
+ interface IntakeExample {
48
+ key: IntakeExampleKey;
49
+ value: ContentTypeIntakePolicy;
50
+ }
51
+
52
+ const INTAKE_EXAMPLES: IntakeExample[] = [
53
+ {
54
+ key: 'minimal',
55
+ value: {
56
+ identification: {
57
+ guidance: 'Documents that match this content type.',
58
+ distinguish_from: 'Do not select this type for unrelated documents.',
59
+ },
60
+ },
61
+ },
62
+ {
63
+ key: 'extraction_only',
64
+ value: {
65
+ text_conversion: {
66
+ enabled: false,
67
+ },
68
+ extraction: {
69
+ enabled: true,
70
+ source: 'vision',
71
+ instructions:
72
+ 'Extract required fields only. Ignore cover pages, appendices, legal boilerplate, and marketing content.',
73
+ },
74
+ rendering_template: '# {{properties.title}}\n\n{{properties.summary}}',
75
+ embeddings: {
76
+ text: true,
77
+ properties: true,
78
+ },
79
+ default_view: 'pdf',
80
+ },
81
+ },
82
+ {
83
+ key: 'grounded',
84
+ value: {
85
+ extraction: {
86
+ enabled: true,
87
+ grounding: {
88
+ enabled: true,
89
+ use_vision: true,
90
+ // Easy tier: Gemini Flash for clean digital / light scans.
91
+ config: {
92
+ model: 'publishers/google/models/gemini-3.5-flash',
93
+ model_options: {
94
+ _option_id: 'vertexai-gemini',
95
+ max_tokens: 32000,
96
+ thinking_level: 'LOW',
97
+ temperature: 0,
98
+ } as unknown as NonNullable<
99
+ import('@vertesia/common').InteractionExecutionConfiguration['model_options']
100
+ >,
101
+ },
102
+ // Hard tier: Gemini Pro when hardness escalates (handwriting, degraded OCR).
103
+ hard_config: {
104
+ model: 'publishers/google/models/gemini-3.1-pro-preview',
105
+ model_options: {
106
+ _option_id: 'vertexai-gemini',
107
+ max_tokens: 32000,
108
+ thinking_level: 'LOW',
109
+ temperature: 0,
110
+ } as unknown as NonNullable<
111
+ import('@vertesia/common').InteractionExecutionConfiguration['model_options']
112
+ >,
113
+ },
114
+ hardness_threshold: 0.5,
115
+ min_citation_density: 0.3,
116
+ window_pages: 3,
117
+ review: {
118
+ enabled: true,
119
+ // Flash + MEDIUM thinking: strong review quality at flash cost.
120
+ config: {
121
+ model: 'publishers/google/models/gemini-3.5-flash',
122
+ model_options: {
123
+ _option_id: 'vertexai-gemini',
124
+ thinking_level: 'MEDIUM',
125
+ max_tokens: 32000,
126
+ temperature: 0,
127
+ } as unknown as NonNullable<
128
+ import('@vertesia/common').InteractionExecutionConfiguration['model_options']
129
+ >,
130
+ },
131
+ coverage_threshold: 0.2,
132
+ },
133
+ },
134
+ },
135
+ default_view: 'pdf',
136
+ },
137
+ },
138
+ {
139
+ key: 'visual_first',
140
+ value: {
141
+ identification: {
142
+ guidance: 'Presentation decks or visual reports with slides, charts, diagrams, tables, or callouts.',
143
+ },
144
+ text_conversion: {
145
+ enabled: true,
146
+ method: 'auto',
147
+ },
148
+ extraction: {
149
+ enabled: true,
150
+ source: 'auto',
151
+ instructions:
152
+ 'Use visual evidence when layout, charts, diagrams, or slide structure contain material information.',
153
+ },
154
+ default_view: 'pdf',
155
+ },
156
+ },
157
+ {
158
+ key: 'structured_spreadsheet',
159
+ value: {
160
+ text_conversion: {
161
+ enabled: true,
162
+ method: 'auto',
163
+ instructions: 'Preserve sheet names, headers, row labels, totals, and period columns.',
164
+ },
165
+ extraction: {
166
+ enabled: true,
167
+ source: 'text',
168
+ instructions: 'Extract values from worksheet text. Do not infer values from charts.',
169
+ },
170
+ default_view: 'properties',
171
+ },
172
+ },
173
+ {
174
+ key: 'media_no_transcript',
175
+ value: {
176
+ text_conversion: {
177
+ enabled: false,
178
+ },
179
+ extraction: {
180
+ enabled: true,
181
+ source: 'auto',
182
+ instructions: 'Analyze available metadata and source evidence without creating a transcript.',
183
+ },
184
+ default_view: 'properties',
185
+ },
186
+ },
187
+ {
188
+ key: 'full_reference',
189
+ value: {
190
+ mode: 'programmatic',
191
+ identification: {
192
+ guidance:
193
+ 'Supplier invoices: billing documents with an invoice number, line items, ' +
194
+ 'amounts due, tax breakdown, and payment terms.',
195
+ distinguish_from:
196
+ 'Quotes and estimates are offers, not payment requests. Credit notes carry ' +
197
+ 'negative amounts. Receipts confirm a payment already made.',
198
+ examples: [],
199
+ },
200
+ text_conversion: {
201
+ enabled: true,
202
+ method: 'auto',
203
+ instructions: 'Keep the commercial terms and totals. Drop boilerplate and legal footers.',
204
+ output_format: 'markdown',
205
+ custom: {
206
+ interaction: 'my-project:ConvertSpecialDocument',
207
+ agent: 'my-project:DocumentConversionAgent',
208
+ },
209
+ },
210
+ extraction: {
211
+ enabled: true,
212
+ source: 'auto',
213
+ instructions:
214
+ 'Extract totals from the summary block, not from line items. ' +
215
+ 'Amounts include currency. Dates use ISO 8601.',
216
+ interaction: 'sys:ExtractInformation',
217
+ verification: {
218
+ enabled: false,
219
+ model: 'publishers/anthropic/models/claude-sonnet',
220
+ environment: 'default',
221
+ materiality:
222
+ 'Amounts, dates, identifiers, and party names are material. Typos in free text are not.',
223
+ threshold: 0.85,
224
+ max_retries: 1,
225
+ on_fail: 'flag',
226
+ },
227
+ grounding: {
228
+ enabled: true,
229
+ interaction: 'sys:ExtractInformationGrounded',
230
+ max_pages: 20,
231
+ force_ocr: false,
232
+ use_vision: true,
233
+ omit_block_boxes: false,
234
+ // Easy / hard extraction models
235
+ config: {
236
+ model: 'publishers/google/models/gemini-3.5-flash',
237
+ },
238
+ hard_config: {
239
+ model: 'publishers/google/models/gemini-3.1-pro-preview',
240
+ },
241
+ hardness_threshold: 0.5,
242
+ window_pages: 3,
243
+ update_properties: true,
244
+ min_citation_density: 0.3,
245
+ refresh_ocr: false,
246
+ review: {
247
+ enabled: true,
248
+ config: {
249
+ model: 'publishers/google/models/gemini-3.5-flash',
250
+ },
251
+ coverage_threshold: 0.2,
252
+ },
253
+ },
254
+ },
255
+ rendering_template:
256
+ '# Invoice {{properties.invoice_number}}\n\n' +
257
+ '- Vendor: {{properties.vendor_name}}\n' +
258
+ '- Total: {{properties.currency}} {{properties.total_amount}}\n' +
259
+ '- Due: {{properties.due_date}}',
260
+ embeddings: {
261
+ text: true,
262
+ image: false,
263
+ properties: true,
264
+ },
265
+ generate_toc: false,
266
+ default_view: 'auto',
267
+ },
268
+ },
269
+ ];
270
+
271
+ const EMPTY_POLICY: ContentTypeIntakePolicy = {};
272
+
273
+ export function IntakePolicyEditor({
274
+ objectType,
275
+ value,
276
+ onSave: onSaveProp,
277
+ onIntakeUpdate,
278
+ readonly = false,
279
+ }: IntakePolicyEditorProps) {
280
+ const { store } = useUserSession();
281
+ const { t } = useUITranslation();
282
+ const toast = useToast();
283
+ const { theme } = useTheme();
284
+ const editorRef = useRef<EditorApi | undefined>(undefined);
285
+ const validatePolicy = useMemo(() => createPolicyValidator(), []);
286
+
287
+ const [isUpdating, setUpdating] = useState(false);
288
+ const initialPolicy = objectType?.intake ?? value;
289
+ const [editorValue, setEditorValue] = useState(() => stringifyPolicy(initialPolicy));
290
+ const [savedValue, setSavedValue] = useState(() => stringifyPolicy(initialPolicy));
291
+ const [validationMessage, setValidationMessage] = useState<string | undefined>(undefined);
292
+ const [activeTab, setActiveTab] = useState<IntakePolicyFormSection | 'json'>('overview');
293
+
294
+ const isDirty = editorValue !== savedValue;
295
+ const currentPolicy = useMemo(() => {
296
+ const result = parseAndValidatePolicy(editorValue, validatePolicy);
297
+ return result.ok ? result.policy : undefined;
298
+ }, [editorValue, validatePolicy]);
299
+ const summaryPolicy = currentPolicy ?? initialPolicy ?? EMPTY_POLICY;
300
+
301
+ const beforeMount = (monaco: Monaco) => {
302
+ const namespace = monaco as unknown as {
303
+ languages?: { json?: { jsonDefaults?: { setDiagnosticsOptions(options: unknown): void } } };
304
+ json?: { jsonDefaults?: { setDiagnosticsOptions(options: unknown): void } };
305
+ };
306
+ const jsonDefaults = namespace.languages?.json?.jsonDefaults ?? namespace.json?.jsonDefaults;
307
+ // ContentTypeIntakePolicySchema (from @vertesia/common) drives autocomplete,
308
+ // hover docs, and validation. Clone so Monaco cannot mutate the shared export.
309
+ const schema = structuredClone(ContentTypeIntakePolicySchema) as typeof ContentTypeIntakePolicySchema;
310
+ jsonDefaults?.setDiagnosticsOptions({
311
+ validate: true,
312
+ allowComments: false,
313
+ schemas: [
314
+ {
315
+ uri: 'vertesia://schemas/content-type-intake-policy.json',
316
+ fileMatch: ['*'],
317
+ schema,
318
+ },
319
+ ],
320
+ });
321
+ };
322
+
323
+ const validateCurrentEditorValue = () => {
324
+ const value = activeTab === 'json' ? (editorRef.current?.getValue() ?? editorValue) : editorValue;
325
+ const result = parseAndValidatePolicy(value, validatePolicy);
326
+ if (!result.ok) {
327
+ setValidationMessage(result.message);
328
+ return undefined;
329
+ }
330
+ setValidationMessage(undefined);
331
+ return result.policy;
332
+ };
333
+
334
+ const onSave = () => {
335
+ const policy = validateCurrentEditorValue();
336
+ if (!policy) {
337
+ return;
338
+ }
339
+
340
+ const persist =
341
+ onSaveProp ??
342
+ (objectType
343
+ ? (next: ContentTypeIntakePolicy) =>
344
+ store.types.update(objectType.id, { intake: next }).then((response) => response.intake)
345
+ : undefined);
346
+ if (!persist) {
347
+ return;
348
+ }
349
+
350
+ setUpdating(true);
351
+ persist(policy)
352
+ .then((saved) => {
353
+ const nextValue = stringifyPolicy(saved ?? policy);
354
+ setEditorValue(nextValue);
355
+ setSavedValue(nextValue);
356
+ onIntakeUpdate?.(saved ?? policy);
357
+ toast({
358
+ status: 'success',
359
+ title: t('intakePolicy.toast.updated'),
360
+ description: t('intakePolicy.toast.saved'),
361
+ duration: 2000,
362
+ });
363
+ })
364
+ .catch((err: unknown) => {
365
+ toast({
366
+ status: 'error',
367
+ title: t('intakePolicy.toast.updateFailed'),
368
+ description: errorMessage(err),
369
+ duration: 5000,
370
+ });
371
+ })
372
+ .finally(() => {
373
+ setUpdating(false);
374
+ });
375
+ };
376
+
377
+ const onValidate = () => {
378
+ const value = activeTab === 'json' ? (editorRef.current?.getValue() ?? editorValue) : editorValue;
379
+ const result = parseAndValidatePolicy(value, validatePolicy);
380
+ setValidationMessage(result.ok ? undefined : result.message);
381
+ toast({
382
+ status: result.ok ? 'success' : 'error',
383
+ title: result.ok ? t('intakePolicy.toast.valid') : t('intakePolicy.toast.invalid'),
384
+ description: result.ok ? t('intakePolicy.toast.schemaValid') : result.message,
385
+ duration: 3000,
386
+ });
387
+ };
388
+
389
+ const onRevert = () => {
390
+ setEditorValue(savedValue);
391
+ editorRef.current?.setValue(savedValue);
392
+ setValidationMessage(undefined);
393
+ };
394
+
395
+ const onFormat = () => {
396
+ const policy = validateCurrentEditorValue();
397
+ if (!policy) {
398
+ return;
399
+ }
400
+ const value = stringifyPolicy(policy);
401
+ setEditorValue(value);
402
+ editorRef.current?.setValue(value);
403
+ };
404
+
405
+ const insertExample = (example: IntakeExample) => {
406
+ const value = stringifyPolicy(example.value);
407
+ setEditorValue(value);
408
+ editorRef.current?.setValue(value);
409
+ setValidationMessage(undefined);
410
+ setActiveTab('overview');
411
+ };
412
+
413
+ const onFormChange = (policy: ContentTypeIntakePolicy) => {
414
+ setEditorValue(stringifyPolicy(policy));
415
+ setValidationMessage(undefined);
416
+ };
417
+
418
+ const onTabChange = (tab: string) => {
419
+ const nextTab = tab as IntakePolicyFormSection | 'json';
420
+ if (nextTab !== 'json' && !currentPolicy) {
421
+ setValidationMessage(t('intakePolicy.error.fixJsonBeforeForm'));
422
+ setActiveTab('json');
423
+ return;
424
+ }
425
+ setActiveTab(nextTab);
426
+ };
427
+
428
+ const title = (
429
+ <div className="flex items-center gap-2">
430
+ <div className="text-base font-semibold">{t('intakePolicy.title')}</div>
431
+ {isDirty && <Badge variant="attention">{t('intakePolicy.status.unsaved')}</Badge>}
432
+ </div>
433
+ );
434
+
435
+ const action = !readonly ? (
436
+ <>
437
+ <Dropdown
438
+ align="right"
439
+ trigger={
440
+ <Button variant="outline" size="sm">
441
+ <WandSparkles className="size-4" />
442
+ {t('intakePolicy.action.examples')}
443
+ </Button>
444
+ }
445
+ >
446
+ <MenuGroup label={t('intakePolicy.action.insertExample')}>
447
+ {INTAKE_EXAMPLES.map((example) => (
448
+ <MenuItem key={example.key} onClick={() => insertExample(example)}>
449
+ <FileText className="size-4" />
450
+ <div className="flex flex-col">
451
+ <span>{t(`intakePolicy.example.${example.key}.label`)}</span>
452
+ <span className="text-xs text-muted">
453
+ {t(`intakePolicy.example.${example.key}.description`)}
454
+ </span>
455
+ </div>
456
+ </MenuItem>
457
+ ))}
458
+ </MenuGroup>
459
+ </Dropdown>
460
+ {activeTab === 'json' && (
461
+ <>
462
+ <Button variant="outline" size="sm" onClick={onFormat}>
463
+ <Braces className="size-4" />
464
+ {t('intakePolicy.action.format')}
465
+ </Button>
466
+ <Button variant="outline" size="sm" onClick={onValidate}>
467
+ <CheckCircle2 className="size-4" />
468
+ {t('intakePolicy.action.validate')}
469
+ </Button>
470
+ </>
471
+ )}
472
+ <Button variant="outline" size="sm" onClick={onRevert} disabled={!isDirty}>
473
+ <RotateCcw className="size-4" />
474
+ {t('intakePolicy.action.revert')}
475
+ </Button>
476
+ <Button isLoading={isUpdating} size="sm" onClick={onSave} disabled={!isDirty}>
477
+ <Save className="size-4" />
478
+ {t('intakePolicy.action.save')}
479
+ </Button>
480
+ </>
481
+ ) : undefined;
482
+
483
+ return (
484
+ <Panel title={title} className="bg-background! h-full" action={action}>
485
+ <div className="flex h-full min-h-0 flex-col gap-3">
486
+ <IntakeSummary policy={summaryPolicy} />
487
+ {validationMessage && (
488
+ <div className="rounded-sm border border-destructive bg-mixer-destructive/10 px-3 py-2 text-sm whitespace-pre-line text-destructive">
489
+ {validationMessage}
490
+ </div>
491
+ )}
492
+ <Tabs
493
+ tabs={createEditorTabs(t, currentPolicy ?? EMPTY_POLICY, readonly, onFormChange, editorValue, {
494
+ editorRef,
495
+ beforeMount,
496
+ onChange: (value) => {
497
+ setEditorValue(value);
498
+ setValidationMessage(undefined);
499
+ },
500
+ theme,
501
+ })}
502
+ current={activeTab}
503
+ onTabChange={onTabChange}
504
+ updateHash={false}
505
+ responsive
506
+ fullHeight
507
+ className="px-0"
508
+ >
509
+ <TabsBar sticky />
510
+ <TabsPanel className="min-h-0 flex-1 overflow-auto pt-1" />
511
+ </Tabs>
512
+ </div>
513
+ </Panel>
514
+ );
515
+ }
516
+
517
+ function createEditorTabs(
518
+ t: TFunction,
519
+ policy: ContentTypeIntakePolicy,
520
+ readonly: boolean,
521
+ onChange: (policy: ContentTypeIntakePolicy) => void,
522
+ editorValue: string,
523
+ json: {
524
+ editorRef: { current: EditorApi | undefined };
525
+ beforeMount: (monaco: Monaco) => void;
526
+ onChange: (value: string) => void;
527
+ theme: string;
528
+ },
529
+ ) {
530
+ const formTab = (name: IntakePolicyFormSection, label: string) => ({
531
+ name,
532
+ label,
533
+ content: <IntakePolicyForm policy={policy} section={name} onChange={onChange} readonly={readonly} />,
534
+ });
535
+
536
+ return [
537
+ formTab('overview', t('intakePolicy.tab.overview')),
538
+ formTab('conversion', t('intakePolicy.tab.conversion')),
539
+ formTab('extraction', t('intakePolicy.tab.extraction')),
540
+ formTab('grounding', t('intakePolicy.tab.grounding')),
541
+ formTab('output', t('intakePolicy.tab.output')),
542
+ {
543
+ name: 'json',
544
+ label: t('intakePolicy.tab.json'),
545
+ content: (
546
+ <div className="flex h-full min-h-[36rem] gap-4 py-3">
547
+ <div className="min-w-0 flex-1 overflow-hidden rounded-sm border">
548
+ <MonacoEditor
549
+ value={editorValue}
550
+ language="json"
551
+ editorRef={json.editorRef}
552
+ beforeMount={json.beforeMount}
553
+ onChange={(update) => json.onChange(update.state.doc.toString())}
554
+ theme={json.theme === 'dark' ? 'vs-dark' : 'vs'}
555
+ options={{
556
+ readOnly: readonly,
557
+ minimap: { enabled: false },
558
+ scrollBeyondLastLine: false,
559
+ wordWrap: 'on',
560
+ lineNumbers: 'on',
561
+ automaticLayout: true,
562
+ formatOnPaste: true,
563
+ formatOnType: true,
564
+ tabSize: 2,
565
+ }}
566
+ />
567
+ </div>
568
+ <IntakeHelp />
569
+ </div>
570
+ ),
571
+ },
572
+ ];
573
+ }
574
+
575
+ function IntakeSummary({ policy }: { policy: ContentTypeIntakePolicy }) {
576
+ const { t } = useUITranslation();
577
+ const grounding = policy.extraction?.grounding;
578
+ const values = [
579
+ [t('intakePolicy.summary.mode'), optionLabel(t, policy.mode)],
580
+ [t('intakePolicy.summary.conversion'), enabledLabel(t, policy.text_conversion?.enabled)],
581
+ [t('intakePolicy.summary.method'), optionLabel(t, policy.text_conversion?.method)],
582
+ [t('intakePolicy.summary.source'), optionLabel(t, policy.extraction?.source)],
583
+ [t('intakePolicy.summary.extraction'), enabledLabel(t, policy.extraction?.enabled)],
584
+ [t('intakePolicy.summary.grounding'), enabledLabel(t, grounding?.enabled)],
585
+ [t('intakePolicy.summary.defaultView'), optionLabel(t, policy.default_view)],
586
+ [t('intakePolicy.summary.toc'), enabledLabel(t, policy.generate_toc)],
587
+ [
588
+ t('intakePolicy.summary.template'),
589
+ policy.rendering_template ? t('intakePolicy.option.set') : t('intakePolicy.option.inherit'),
590
+ ],
591
+ ];
592
+
593
+ return (
594
+ <div className="flex flex-wrap gap-2">
595
+ {values.map(([label, value]) => (
596
+ <Badge key={label} variant="outline" className="gap-1">
597
+ <span className="text-muted">{label}:</span>
598
+ <span>{value}</span>
599
+ </Badge>
600
+ ))}
601
+ </div>
602
+ );
603
+ }
604
+
605
+ function IntakeHelp() {
606
+ const { t } = useUITranslation();
607
+ return (
608
+ <aside className="hidden w-80 shrink-0 overflow-y-auto rounded-sm border bg-mixer-muted/20 p-3 text-sm lg:block">
609
+ <div className="mb-3 font-semibold">{t('intakePolicy.help.title')}</div>
610
+ <div className="space-y-3 text-muted">
611
+ <HelpItem label="identification" text={t('intakePolicy.help.identification')} />
612
+ <HelpItem label="text_conversion" text={t('intakePolicy.help.textConversion')} />
613
+ <HelpItem label="extraction.source" text={t('intakePolicy.help.extractionSource')} />
614
+ <HelpItem label="extraction.grounding" text={t('intakePolicy.help.grounding')} />
615
+ <HelpItem label="extraction.grounding.review" text={t('intakePolicy.help.review')} />
616
+ <HelpItem label="rendering_template" text={t('intakePolicy.help.renderingTemplate')} />
617
+ <HelpItem label="embeddings" text={t('intakePolicy.help.embeddings')} />
618
+ <HelpItem label="default_view" text={t('intakePolicy.help.defaultView')} />
619
+ </div>
620
+ </aside>
621
+ );
622
+ }
623
+
624
+ function HelpItem({ label, text }: { label: string; text: string }) {
625
+ return (
626
+ <div>
627
+ <div className="font-medium text-foreground">{label}</div>
628
+ <div>{text}</div>
629
+ </div>
630
+ );
631
+ }
632
+
633
+ function enabledLabel(t: TFunction, value: boolean | undefined) {
634
+ if (value === true) return t('intakePolicy.option.enabled');
635
+ if (value === false) return t('intakePolicy.option.disabled');
636
+ return t('intakePolicy.option.inherit');
637
+ }
638
+
639
+ function optionLabel(t: TFunction, value: string | undefined) {
640
+ return value ? t(`intakePolicy.option.${value}`) : t('intakePolicy.option.inherit');
641
+ }
642
+
643
+ function stringifyPolicy(policy: ContentTypeIntakePolicy | undefined) {
644
+ return JSON.stringify(policy ?? EMPTY_POLICY, null, 2);
645
+ }
646
+
647
+ function createPolicyValidator() {
648
+ const ajv = new Ajv({
649
+ strict: false,
650
+ allErrors: true,
651
+ });
652
+ addFormats(ajv);
653
+ return ajv.compile(ContentTypeIntakePolicySchema);
654
+ }
655
+
656
+ function parseAndValidatePolicy(
657
+ content: string,
658
+ validatePolicy: ValidateFunction<ContentTypeIntakePolicy>,
659
+ ): { ok: true; policy: ContentTypeIntakePolicy } | { ok: false; message: string } {
660
+ let parsed: unknown;
661
+ try {
662
+ parsed = content.trim() ? JSON.parse(content) : {};
663
+ } catch (err: unknown) {
664
+ return { ok: false, message: `Invalid JSON: ${errorMessage(err)}` };
665
+ }
666
+
667
+ if (!validatePolicy(parsed)) {
668
+ return { ok: false, message: formatAjvErrors(validatePolicy.errors) };
669
+ }
670
+
671
+ return { ok: true, policy: parsed as ContentTypeIntakePolicy };
672
+ }
673
+
674
+ function formatAjvErrors(errors: ErrorObject[] | null | undefined) {
675
+ if (!errors?.length) {
676
+ return 'The intake policy does not match the schema.';
677
+ }
678
+ return errors.map((err) => `${err.instancePath || '/'} ${err.message ?? 'is invalid'}`).join('\n');
679
+ }