adminizer 5.0.0-build.2 → 5.0.0-build.21

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 (300) hide show
  1. package/assets/abnfDiagram-VCTEODGH-CkmsvdDi.js +1 -0
  2. package/assets/add-CESPH_aI.js +1 -0
  3. package/assets/add-group-CQ4Xm8Ru.js +1 -0
  4. package/assets/add-user-B-QNjR4B.js +1 -0
  5. package/assets/ai-assistant/agent.es.js +28362 -0
  6. package/assets/app-C5Ju5Zra.css +1 -0
  7. package/assets/app-Ca5yUSBv.js +138 -0
  8. package/assets/arc-B-Dt8YNl.js +1 -0
  9. package/assets/architectureDiagram-5GKGNRK7-Ca58kq-l.js +36 -0
  10. package/assets/asmcrypto-DZeQH0LC.js +1 -0
  11. package/assets/blockDiagram-NRAW4CY4-CuqE52IG.js +129 -0
  12. package/assets/c4Diagram-UCG6FXSJ-Cx8z-0Bw.js +38 -0
  13. package/assets/catalog-CkSbxvx0.js +6 -0
  14. package/assets/channel-C9z6Pi_j.js +1 -0
  15. package/assets/chunk-2Q5K7J3B-CXISVKTC.js +1 -0
  16. package/assets/chunk-5VM5RSS4-DYGeeVYe.js +15 -0
  17. package/assets/chunk-F27PBJKO-BeY-dbKj.js +1 -0
  18. package/assets/chunk-G27WJ6UU-DeX_eH7C.js +231 -0
  19. package/assets/chunk-JWPE2WC7-DaxI9q0u.js +1 -0
  20. package/assets/chunk-LCL6LL3I-B7PkaEz_.js +206 -0
  21. package/assets/chunk-POPQ4Y6H-lwu6ZpsN.js +1 -0
  22. package/assets/chunk-SVP7TREG-CVFllgl1.js +88 -0
  23. package/assets/chunk-XXDRQBXY-DDLN0rcg.js +1 -0
  24. package/assets/classDiagram-DTDB5LWJ-BKtCIC3B.js +1 -0
  25. package/assets/classDiagram-v2-JRS7N3AN-BKtCIC3B.js +1 -0
  26. package/assets/controls/ckeditor.es.js +9370 -9306
  27. package/assets/controls/handsontable.es.js +11971 -11938
  28. package/assets/controls/jsoneditor.es.js +13104 -13008
  29. package/assets/controls/leaflet.es.js +1023 -1004
  30. package/assets/controls/toast-ui.es.js +1843 -1834
  31. package/assets/cose-bilkent-JH36ORCC-CWRyUkhQ.js +1 -0
  32. package/assets/cynefin-OW5HDTMX-B1iLDXCC.js +166 -0
  33. package/assets/cynefinDiagram-5FMLGOSQ-DaXQHr3l.js +62 -0
  34. package/assets/cytoscape.esm-yzknjiTM.js +321 -0
  35. package/assets/dagre-3AP2YEHR-DFORa4Hz.js +4 -0
  36. package/assets/dashboard-Ssa1_Of3.js +3 -0
  37. package/assets/debounce-B3mXI_sx.js +1 -0
  38. package/assets/defaultLocale-DX6XiGOO.js +1 -0
  39. package/assets/diagram-S7CK7UJ4-CgP6KBo0.js +30 -0
  40. package/assets/diagram-UQ7AKVKN-oYfQe6w0.js +41 -0
  41. package/assets/diagram-VSXAHHWV-DfCU5wgt.js +3 -0
  42. package/assets/diagram-VX7I27RA-BITJVrNY.js +24 -0
  43. package/assets/diagram-Z3DM3KII-D8J9Ssjy.js +24 -0
  44. package/assets/docs-DHMt_Rq1.js +1 -0
  45. package/assets/ebnfDiagram-PWID7BFC-qpNmoYgx.js +1 -0
  46. package/assets/erDiagram-SSCWMZ5O-DDF-Y0zs.js +99 -0
  47. package/assets/flowDiagram-A5DVABFB-CjKVkR9T.js +168 -0
  48. package/assets/ganttDiagram-EL5Y4UJY-BXGQ53w6.js +292 -0
  49. package/assets/gitGraphDiagram-WWUBYQGX-BLMevdLh.js +106 -0
  50. package/assets/history-tBOo2gd5.js +1 -0
  51. package/assets/infoDiagram-RXCK75RN-BAnPdnyw.js +2 -0
  52. package/assets/init-Gi6I4Gst.js +1 -0
  53. package/assets/init-user-DrNUHa6r.js +1 -0
  54. package/assets/isObject-DL-BuaBl.js +1 -0
  55. package/assets/ishikawaDiagram-5VMMS53U-PnL0Z4Li.js +70 -0
  56. package/assets/journeyDiagram-EYS64GPL-CLcsCSwF.js +139 -0
  57. package/assets/kanban-definition-3QL26DDD-C2eU3uBE.js +89 -0
  58. package/assets/katex-HP8lGamR.js +257 -0
  59. package/assets/layout-BXoYdHdx.js +1 -0
  60. package/assets/linear-BSzrP0oP.js +1 -0
  61. package/assets/list-CLg6xeIK.js +4 -0
  62. package/assets/login-Ce7o3-o_.js +1 -0
  63. package/assets/manifest.json +834 -58
  64. package/assets/{media-manager-BDbpSSjH.js → media-manager-D7G1P3fz.js} +3 -3
  65. package/assets/mermaid.core-aKMJHeEk.js +310 -0
  66. package/assets/mindmap-definition-FBJOCRG2-Dzbz5ghA.js +96 -0
  67. package/assets/module-BsXph_pA.js +1 -0
  68. package/assets/notification-D-Zz1gF_.js +20 -0
  69. package/assets/ordinal-Cboi1Yqb.js +1 -0
  70. package/assets/pegDiagram-XKGWAZYB-BTdWkU0Y.js +1 -0
  71. package/assets/pieDiagram-E7YTZNPT-Dnw8-TB-.js +39 -0
  72. package/assets/quadrantDiagram-AXDQQJYC-CALzLlg9.js +7 -0
  73. package/assets/railroadDiagram-O6MQD6OU-CTA1vzcv.js +1 -0
  74. package/assets/register-CNnBkzej.js +1 -0
  75. package/assets/requirementDiagram-EFPCY7ZU-UX2CF5HA.js +84 -0
  76. package/assets/ru-CoNgAwFK.js +1 -0
  77. package/assets/sankeyDiagram-P5KCCOFB-4aPp3ATV.js +40 -0
  78. package/assets/sequenceDiagram-WJ2MYXX4--kltkwJw.js +162 -0
  79. package/assets/sizeCapture-X5ZJPWSS-Ca9-g5QY.js +1 -0
  80. package/assets/stateDiagram-HBIQ2CUA-Diaf5Q7I.js +1 -0
  81. package/assets/stateDiagram-v2-4QOOHH4V-C6FlRp7u.js +1 -0
  82. package/assets/swimlanes-XN3QIQJK-DhqX_myE.js +1 -0
  83. package/assets/swimlanesDiagram-VK2B7HYN-i_3LTEBk.js +8 -0
  84. package/assets/timeline-definition-24CTP7MA-CwkJQbhq.js +120 -0
  85. package/assets/toNumber-DZb-r9sy.js +1 -0
  86. package/assets/use-appearance-DC-DESKB.js +1 -0
  87. package/assets/user-filters-list-W47DTBp2.js +1 -0
  88. package/assets/vennDiagram-4TSXK5OY-BNM3_cVS.js +34 -0
  89. package/assets/wardleyDiagram-VM6X3IG4-BODb1NP8.js +78 -0
  90. package/assets/welcome-BeBm6NIi.js +1 -0
  91. package/assets/with-app-layout-B90DtzYC.js +31 -0
  92. package/assets/xychartDiagram-S5SC5T6Z-DthqEh3g.js +7 -0
  93. package/controllers/access-rights/permissionOptions.d.ts +2 -0
  94. package/controllers/access-rights/permissionOptions.js +23 -0
  95. package/controllers/add.js +1 -1
  96. package/controllers/addGroup.js +15 -6
  97. package/controllers/ai/AiAgentController.d.ts +66 -0
  98. package/controllers/ai/AiAgentController.js +435 -0
  99. package/controllers/ai/AiAssistantController.d.ts +0 -4
  100. package/controllers/ai/AiAssistantController.js +4 -78
  101. package/controllers/catalog/FrontendCatalogAdapter.d.ts +1 -1
  102. package/controllers/catalog/FrontentCatalogAdapter.d.ts +1 -1
  103. package/controllers/docs/index.d.ts +22 -0
  104. package/controllers/docs/index.js +126 -0
  105. package/controllers/edit.js +1 -1
  106. package/controllers/editGroup.js +15 -6
  107. package/controllers/feedback/feedbackController.d.ts +1 -1
  108. package/controllers/filter-fields/savedFilters.js +1 -1
  109. package/controllers/history-actions/HistoryController.d.ts +1 -1
  110. package/controllers/history-actions/HistoryController.js +6 -6
  111. package/controllers/list.js +1 -1
  112. package/controllers/login.js +1 -1
  113. package/controllers/media-manager/mediaManagerAdapter.d.ts +1 -1
  114. package/controllers/notifications/NotificationController.js +14 -8
  115. package/controllers/view.js +2 -2
  116. package/controllers/widgets/filterInfo.js +1 -1
  117. package/helpers/accessRightsHelper.d.ts +11 -4
  118. package/helpers/accessRightsHelper.js +82 -12
  119. package/helpers/assetVersionHelper.d.ts +14 -0
  120. package/helpers/assetVersionHelper.js +42 -0
  121. package/helpers/configHelper.d.ts +3 -3
  122. package/helpers/configHelper.js +20 -18
  123. package/helpers/controllerHelper.d.ts +3 -2
  124. package/helpers/controllerHelper.js +25 -15
  125. package/helpers/fieldsHelper.d.ts +2 -2
  126. package/helpers/fieldsHelper.js +3 -3
  127. package/helpers/filterDatetimeHelper.d.ts +1 -1
  128. package/helpers/inertiaActionsHelper.d.ts +3 -3
  129. package/helpers/inertiaActionsHelper.js +4 -5
  130. package/helpers/inertiaAddHelper.d.ts +5 -5
  131. package/helpers/inertiaAddHelper.js +7 -6
  132. package/helpers/inertiaGroupHelper.d.ts +3 -3
  133. package/helpers/inertiaGroupHelper.js +10 -2
  134. package/helpers/inertiaListHelper.d.ts +4 -4
  135. package/helpers/inertiaListHelper.js +7 -8
  136. package/helpers/inertiaMenuHelper.d.ts +2 -3
  137. package/helpers/inertiaMenuHelper.js +3 -26
  138. package/helpers/inertiaUserHelper.d.ts +3 -3
  139. package/helpers/menuHelper.d.ts +2 -2
  140. package/helpers/modelResourceHelper.d.ts +9 -0
  141. package/helpers/modelResourceHelper.js +43 -0
  142. package/helpers/navigationAccessHelper.d.ts +13 -0
  143. package/helpers/navigationAccessHelper.js +40 -0
  144. package/helpers/translationHelper.d.ts +2 -2
  145. package/index.d.ts +69 -58
  146. package/index.js +11 -0
  147. package/interfaces/adminpanelConfig.d.ts +18 -5
  148. package/interfaces/global.d.ts +6 -6
  149. package/interfaces/internalModelAccess.d.ts +1 -1
  150. package/interfaces/listQueryBuilder.d.ts +1 -1
  151. package/interfaces/types.d.ts +171 -2
  152. package/lib/Adminizer.d.ts +46 -26
  153. package/lib/Adminizer.js +37 -1
  154. package/lib/DataAccessor.d.ts +7 -6
  155. package/lib/DataAccessor.js +50 -48
  156. package/lib/MiddlewareManager.d.ts +1 -1
  157. package/lib/access-rights/AccessRightsHandler.d.ts +2 -2
  158. package/lib/admin-links/AdminLinkHandler.d.ts +72 -0
  159. package/lib/admin-links/AdminLinkHandler.js +175 -0
  160. package/lib/ai-assistant/AbstractAiConversationHistoryService.d.ts +39 -0
  161. package/lib/ai-assistant/AbstractAiConversationHistoryService.js +98 -0
  162. package/lib/ai-assistant/AbstractAiModelService.d.ts +140 -3
  163. package/lib/ai-assistant/AbstractAiModelService.js +154 -1
  164. package/lib/ai-assistant/AiAgentRunManager.d.ts +35 -0
  165. package/lib/ai-assistant/AiAgentRunManager.js +109 -0
  166. package/lib/ai-assistant/AiAssistantAgentSkillHandler.d.ts +58 -0
  167. package/lib/ai-assistant/AiAssistantAgentSkillHandler.js +87 -0
  168. package/lib/ai-assistant/AiAssistantHandler.d.ts +16 -5
  169. package/lib/ai-assistant/AiAssistantHandler.js +35 -1
  170. package/lib/ai-assistant/AiAssistantUiMethodHandler.d.ts +60 -0
  171. package/lib/ai-assistant/AiAssistantUiMethodHandler.js +138 -0
  172. package/lib/ai-assistant/builtinAgentSkills.d.ts +9 -0
  173. package/lib/ai-assistant/builtinAgentSkills.js +256 -0
  174. package/lib/ai-assistant/jsonSafe.d.ts +13 -0
  175. package/lib/ai-assistant/jsonSafe.js +81 -0
  176. package/lib/app-manager/AdminizerApp.d.ts +40 -11
  177. package/lib/app-manager/AppManager.d.ts +2 -2
  178. package/lib/app-manager/AppManager.js +54 -50
  179. package/lib/app-manager/AssetHandler.d.ts +2 -2
  180. package/lib/app-manager/ConfigLayerHandler.d.ts +2 -2
  181. package/lib/app-manager/ControllerHandler.d.ts +2 -2
  182. package/lib/catalog/AbstractCatalog.d.ts +1 -1
  183. package/lib/catalog/CatalogHandler.d.ts +1 -1
  184. package/lib/controls/ControlsHandler.d.ts +1 -1
  185. package/lib/controls/codeEditor/MonacoEditor.d.ts +1 -1
  186. package/lib/controls/geojsoneditor/GeoEditor.d.ts +1 -1
  187. package/lib/controls/jsoneditor/JsonEditor.d.ts +1 -1
  188. package/lib/controls/markdown/ToastUiEditor.d.ts +1 -1
  189. package/lib/controls/table/Handsontable.d.ts +1 -1
  190. package/lib/controls/wysiwyg/CKeditor.d.ts +1 -1
  191. package/lib/docs/AbstractDocumentation.d.ts +100 -0
  192. package/lib/docs/AbstractDocumentation.js +108 -0
  193. package/lib/docs/DocumentationHandler.d.ts +100 -0
  194. package/lib/docs/DocumentationHandler.js +198 -0
  195. package/lib/docs/FileDocumentation.d.ts +47 -0
  196. package/lib/docs/FileDocumentation.js +189 -0
  197. package/lib/docs/docsAgentSkills.d.ts +10 -0
  198. package/lib/docs/docsAgentSkills.js +93 -0
  199. package/lib/feedback/FeedbackHandler.d.ts +2 -2
  200. package/lib/filters/ConditionValidator.d.ts +1 -1
  201. package/lib/filters/FilterCustomFieldHandler.d.ts +2 -2
  202. package/lib/filters/FilterService.d.ts +4 -4
  203. package/lib/filters/FilterService.js +4 -3
  204. package/lib/filters/index.d.ts +6 -6
  205. package/lib/history-actions/AbstractHistoryAdapter.d.ts +6 -6
  206. package/lib/history-actions/AbstractHistoryAdapter.js +11 -9
  207. package/lib/history-actions/DefaultHistoryAdapter.d.ts +4 -4
  208. package/lib/history-actions/HistoryHandler.d.ts +1 -1
  209. package/lib/list-query-builder/ListQueryBuilder.d.ts +7 -7
  210. package/lib/media-manager/AbstractMediaManager.d.ts +2 -2
  211. package/lib/media-manager/MediaManagerHandler.d.ts +1 -1
  212. package/lib/media-manager/Thumb.d.ts +1 -1
  213. package/lib/media-manager/helpers/MediaManagerHelper.d.ts +5 -5
  214. package/lib/model/AbstractModel.d.ts +11 -4
  215. package/lib/model/InternalModelAccessFactory.d.ts +2 -2
  216. package/lib/model/ModelHandler.d.ts +50 -5
  217. package/lib/model/ModelHandler.js +107 -8
  218. package/lib/model/adapter/sequelize.d.ts +7 -5
  219. package/lib/model/adapter/sequelize.js +79 -46
  220. package/lib/model/adapter/typeorm.d.ts +2 -2
  221. package/lib/model/adapter/typeorm.js +1 -1
  222. package/lib/model/internalModelRepository.d.ts +2 -2
  223. package/lib/notifications/AbstractNotificationService.d.ts +4 -4
  224. package/lib/notifications/GeneralNotificationService.d.ts +2 -2
  225. package/lib/notifications/GeneralNotificationService.js +6 -5
  226. package/lib/notifications/NotificationHandler.d.ts +2 -2
  227. package/lib/notifications/SystemNotificationService.d.ts +2 -2
  228. package/lib/notifications/SystemNotificationService.js +1 -1
  229. package/lib/requestProcessor.d.ts +1 -1
  230. package/lib/ui-i18n/uiTranslationKeys.d.ts +2 -1
  231. package/lib/ui-i18n/uiTranslationKeys.js +42 -1
  232. package/lib/widgets/abstractAction.d.ts +1 -1
  233. package/lib/widgets/abstractCustom.d.ts +2 -2
  234. package/lib/widgets/abstractInfo.d.ts +1 -1
  235. package/lib/widgets/abstractLink.d.ts +2 -2
  236. package/lib/widgets/abstractSwitch.d.ts +1 -1
  237. package/lib/widgets/abstractWidgetBase.d.ts +1 -1
  238. package/lib/widgets/widgetHandler.d.ts +9 -9
  239. package/lib/widgets/widgetHandler.js +5 -1
  240. package/models/Filter.d.ts +1 -1
  241. package/models/FilterColumn.d.ts +1 -1
  242. package/models/Group.d.ts +3 -2
  243. package/models/HistoryActions.d.ts +1 -1
  244. package/models/MediaManagerAP.d.ts +2 -2
  245. package/models/MediaManagerAssociationsAP.d.ts +1 -1
  246. package/models/MediaManagerMetaAP.d.ts +1 -1
  247. package/models/User.d.ts +2 -2
  248. package/models/UserNotification.d.ts +1 -1
  249. package/package.json +29 -20
  250. package/policies/authPolicies.js +6 -6
  251. package/policies/permissionResolvers.d.ts +1 -0
  252. package/policies/permissionResolvers.js +3 -0
  253. package/services/FeedService.d.ts +3 -3
  254. package/system/Router.d.ts +1 -1
  255. package/system/Router.js +27 -1
  256. package/system/bindAccessRights.d.ts +1 -1
  257. package/system/bindAccessRights.js +2 -3
  258. package/system/bindAuthorization.d.ts +1 -1
  259. package/system/bindControls.d.ts +1 -1
  260. package/system/bindCors.d.ts +1 -1
  261. package/system/bindCustomFilterHandlers.d.ts +1 -1
  262. package/system/bindDashboardWidgets.d.ts +1 -1
  263. package/system/bindDev.d.ts +1 -1
  264. package/system/bindDocs.d.ts +13 -0
  265. package/system/bindDocs.js +77 -0
  266. package/system/bindHistory.d.ts +1 -1
  267. package/system/bindInertia.d.ts +1 -1
  268. package/system/bindInertia.js +23 -1
  269. package/system/bindMediaManager.d.ts +1 -1
  270. package/system/bindNotifications.d.ts +1 -1
  271. package/system/bindReqFunctions.d.ts +1 -1
  272. package/system/bindTranslations.d.ts +1 -1
  273. package/system/buildInternalModelAccess.d.ts +3 -3
  274. package/system/buildInternalModelAccess.js +5 -2
  275. package/system/defaults.d.ts +1 -1
  276. package/system/routePolicies.d.ts +1 -1
  277. package/system/systemModelContracts.d.ts +1 -1
  278. package/system/systemModelContracts.js +11 -7
  279. package/system/validateSystemModels.d.ts +1 -1
  280. package/system/validateSystemModels.js +29 -9
  281. package/assets/add-group-BdHFzKng.js +0 -1
  282. package/assets/add-p6yGCoZ7.js +0 -1
  283. package/assets/add-user-CDspCLu4.js +0 -1
  284. package/assets/app-DNXiApCk.css +0 -1
  285. package/assets/app-layout-DCr_-JY3.js +0 -1
  286. package/assets/app.js +0 -138
  287. package/assets/catalog-XN-_p9TB.js +0 -6
  288. package/assets/dashboard-CHZO2QFY.js +0 -3
  289. package/assets/debounce-BMmXVQ06.js +0 -1
  290. package/assets/history-CQd0FojQ.js +0 -1
  291. package/assets/init-user-8aUohVDq.js +0 -1
  292. package/assets/list-PHWgTHsg.js +0 -4
  293. package/assets/login-CO4laf6m.js +0 -1
  294. package/assets/module-DVyQtIxm.js +0 -1
  295. package/assets/notification-B87XG8nQ.js +0 -20
  296. package/assets/register-DBYIMXGQ.js +0 -1
  297. package/assets/ru-rYKdQbWF.js +0 -1
  298. package/assets/use-appearance-_EbbjHTh.js +0 -1
  299. package/assets/user-filters-list-BRzDbPkx.js +0 -1
  300. package/assets/welcome-DiXmrTgc.js +0 -1
@@ -46,29 +46,28 @@ export class DataAccessor {
46
46
  this.action = action;
47
47
  this.actionVerb = getTokenAction(this.action);
48
48
  }
49
- getModelConfig(modelName) {
50
- const resolvedModelName = this.adminizer.modelHandler.resolveModelName(modelName);
51
- const entry = Object.entries(this.adminizer.config.models)
52
- .find(([key, config]) => {
53
- if (key.toLowerCase() === resolvedModelName.toLowerCase()) {
54
- return true;
55
- }
56
- if (!isObject(config)) {
57
- return false;
58
- }
59
- return config.model?.toLowerCase() === resolvedModelName.toLowerCase();
60
- });
61
- const config = entry ? entry[1] : undefined;
49
+ getModelConfig(resourceName) {
50
+ const config = this.adminizer.config.models?.[resourceName];
62
51
  return isObject(config) ? config : undefined;
63
52
  }
64
- accessRelationModel(modelName) {
65
- return this.adminizer.modelHandler.internal("data-accessor").get(modelName);
53
+ accessRelationModel(modelName, resourceName) {
54
+ const resolvedResourceName = this.resolveRelationTarget(modelName, resourceName);
55
+ if (!resolvedResourceName) {
56
+ throw new Error(`Relation model "${modelName}" was not found`);
57
+ }
58
+ return this.adminizer.modelHandler.internal("data-accessor").get(resolvedResourceName);
66
59
  }
67
- resolveRelationTarget(modelName) {
60
+ resolveRelationTarget(modelName, resourceName) {
68
61
  if (!modelName) {
69
62
  return undefined;
70
63
  }
71
- return this.adminizer.modelHandler.resolveModelName(modelName).toLowerCase();
64
+ return this.adminizer.modelHandler.resolveAssociationResource(modelName, resourceName);
65
+ }
66
+ getRelationModel(modelName, resourceName) {
67
+ const resolvedResourceName = this.resolveRelationTarget(modelName, resourceName);
68
+ return resolvedResourceName
69
+ ? this.adminizer.modelHandler.getResource(resolvedResourceName)
70
+ : undefined;
72
71
  }
73
72
  /**
74
73
  * Retrieves the fields for the given ModelResource based on action type,
@@ -86,9 +85,9 @@ export class DataAccessor {
86
85
  const actionConfig = ControllerHelper.findActionConfig(this.modelResource, this.action);
87
86
  const fieldsConfig = this.modelResource.config?.fields || {};
88
87
  const modelAttributes = this.modelResource.model.attributes;
89
- const tokenId = `${this.actionVerb}-${this.modelResource.model.modelname}-${MODEL_TOKEN_SUFFIX}`;
88
+ const tokenId = `${this.actionVerb}-${this.modelResource.name}-${MODEL_TOKEN_SUFFIX}`;
90
89
  if (!this.adminizer.accessRightsHelper.hasPermission(tokenId, this.user)) {
91
- Adminizer.log.debug(`getFieldsConfig > No access rights to ${this.actionVerb} model: ${this.modelResource.model.modelname}`);
90
+ Adminizer.log.debug(`getFieldsConfig > No access rights to ${this.actionVerb} model: ${this.modelResource.name}`);
92
91
  return undefined;
93
92
  }
94
93
  const result = {};
@@ -131,14 +130,14 @@ export class DataAccessor {
131
130
  let populatedModelFieldsConfig = {};
132
131
  if (modelField.type === "association" || modelField.type === "association-many") {
133
132
  const modelName = modelField.model || modelField.collection;
134
- const resolvedModelName = modelName ? this.adminizer.modelHandler.resolveModelName(modelName) : undefined;
133
+ const resolvedModelName = this.resolveRelationTarget(modelName, modelField.resourceName);
135
134
  if (modelName && resolvedModelName) {
136
135
  const tokenId = `read-${resolvedModelName}-${MODEL_TOKEN_SUFFIX}`;
137
136
  if (!this.adminizer.accessRightsHelper.hasPermission(tokenId, this.user)) {
138
- Adminizer.log.silly(`No access rights to model: ${this.modelResource.model.modelname}`);
137
+ Adminizer.log.silly(`No access rights to model: ${this.modelResource.name}`);
139
138
  return undefined;
140
139
  }
141
- const model = this.adminizer.modelHandler.model.get(modelName);
140
+ const model = this.adminizer.modelHandler.getResource(resolvedModelName);
142
141
  if (model) {
143
142
  populatedModelFieldsConfig = this.getAssociatedFieldsConfig(resolvedModelName);
144
143
  const modelCfg = this.getModelConfig(resolvedModelName);
@@ -157,7 +156,11 @@ export class DataAccessor {
157
156
  // Set required and type attributes
158
157
  fldConfig.required = Boolean(fldConfig.required ?? modelField.required);
159
158
  // Default type for field. Could be fetched from config file or model if not defined in config file.
160
- fldConfig.type = (fldConfig.type || modelField.type).toLowerCase();
159
+ // The model layer has a single temporal type (`date`), the UI distinguishes
160
+ // date/datetime/time. Default to `datetime` so the time part survives editing;
161
+ // a date-only column can be narrowed with `type: "date"` in the model config.
162
+ const modelFieldType = modelField.type === "date" ? "datetime" : modelField.type;
163
+ fldConfig.type = (fldConfig.type || modelFieldType).toLowerCase();
161
164
  // Normalize final configuration (fldConfig is always an object here, normalize never returns undefined)
162
165
  fldConfig = this.adminizer.configHelper.normalizeFieldConfig(this.adminizer, fldConfig, key, modelField);
163
166
  // Add new field to result set
@@ -182,17 +185,16 @@ export class DataAccessor {
182
185
  Object.keys(fields).forEach(appendField);
183
186
  return orderedFields;
184
187
  }
185
- getAssociatedFieldsConfig(modelName) {
186
- const resolvedModelName = this.adminizer.modelHandler.resolveModelName(modelName);
187
- const model = this.adminizer.modelHandler.model.get(resolvedModelName);
188
- const modelConfig = this.getModelConfig(resolvedModelName);
188
+ getAssociatedFieldsConfig(resourceName) {
189
+ const model = this.adminizer.modelHandler.getResource(resourceName);
190
+ const modelConfig = this.getModelConfig(resourceName);
189
191
  if (!model || !modelConfig) {
190
192
  return undefined;
191
193
  }
192
194
  // Check if user has access to the associated model
193
- const tokenId = `read-${resolvedModelName}-${MODEL_TOKEN_SUFFIX}`;
195
+ const tokenId = `read-${resourceName}-${MODEL_TOKEN_SUFFIX}`;
194
196
  if (!this.adminizer.accessRightsHelper.hasPermission(tokenId, this.user)) {
195
- Adminizer.log.debug(`getAssociatedFieldsConfig > No access rights to ${this.actionVerb} model: ${resolvedModelName}`);
197
+ Adminizer.log.debug(`getAssociatedFieldsConfig > No access rights to ${this.actionVerb} model: ${resourceName}`);
196
198
  return undefined;
197
199
  }
198
200
  const associatedFields = {};
@@ -375,7 +377,7 @@ export class DataAccessor {
375
377
  async sanitizeUserRelationAccess(criteria = {}) {
376
378
  let sanitizedCriteria = {};
377
379
  // Retrieve model configuration from adminpanel config
378
- const modelName = this.modelResource.model.modelname;
380
+ const modelName = this.modelResource.name;
379
381
  const modelConfig = this.modelResource.config;
380
382
  // Check if the model has `userAccessRelation` configured
381
383
  if (!this.user.isAdministrator && modelConfig && modelConfig.userAccessRelation) {
@@ -388,18 +390,18 @@ export class DataAccessor {
388
390
  // Check if the relation points to `User` or `Group` in the model's attributes
389
391
  const modelAttributes = this.modelResource.model.attributes;
390
392
  const relation = modelAttributes[accessField];
391
- const relationModel = this.resolveRelationTarget(relation?.model);
392
- const relationCollection = this.resolveRelationTarget(relation?.collection);
393
- if (!relation || !['user', 'group'].includes(relationModel ?? relationCollection ?? "")) {
393
+ const relationModel = this.resolveRelationTarget(relation?.model, relation?.resourceName);
394
+ const relationCollection = this.resolveRelationTarget(relation?.collection, relation?.resourceName);
395
+ if (!relation || !['User', 'Group'].includes(relationModel ?? relationCollection ?? "")) {
394
396
  throw new Error(`Invalid userAccessRelation configuration for model ${modelName}`);
395
397
  }
396
398
  // Determine if the current user matches the access criteria
397
399
  if (relationModel) {
398
- if (relationModel === 'user') {
400
+ if (relationModel === 'User') {
399
401
  // Filter by the user's ID if related to User as a model
400
402
  sanitizedCriteria = { ...sanitizedCriteria, [accessField]: this.user.id };
401
403
  }
402
- else if (relationModel === 'group') {
404
+ else if (relationModel === 'Group') {
403
405
  // Filter by user's group membership if related to Group as a model
404
406
  const userGroups = this.user.groups?.map((group) => group.id);
405
407
  sanitizedCriteria = { ...sanitizedCriteria, [accessField]: { in: userGroups } };
@@ -408,11 +410,11 @@ export class DataAccessor {
408
410
  /** Warning: collection relation access is not supported and needs adapter-level processing */
409
411
  if (relationCollection) {
410
412
  Adminizer.log.warn(`Collection relation is not supported and was not tested. You may have an error here: ${JSON.stringify(relation, null, 2)}`);
411
- if (relationCollection === 'user') {
413
+ if (relationCollection === 'User') {
412
414
  // Ensure user's ID is part of the associated collection to User
413
415
  sanitizedCriteria = { ...sanitizedCriteria, [accessField]: { contains: this.user.id } };
414
416
  }
415
- else if (relationCollection === 'group') {
417
+ else if (relationCollection === 'Group') {
416
418
  // Ensure user's groups intersect with the collection to Group
417
419
  const userGroups = this.user.groups?.map((group) => group.id);
418
420
  sanitizedCriteria = { ...sanitizedCriteria, [accessField]: { intersects: userGroups } };
@@ -429,19 +431,19 @@ export class DataAccessor {
429
431
  throw new Error(`Invalid intermediate relation configuration for field "${field}" in model ${modelName}`);
430
432
  }
431
433
  // Retrieve the intermediate model
432
- const intermediateModel = this.adminizer.modelHandler.model.get(intermediateRelation.model.toLowerCase());
434
+ const intermediateModel = this.getRelationModel(intermediateRelation.model, intermediateRelation.resourceName);
433
435
  if (!intermediateModel) {
434
436
  throw new Error(`Intermediate model "${intermediateRelation.model}" not found`);
435
437
  }
436
438
  // Validate the `via` field in the intermediate model
437
439
  const intermediateAttributes = intermediateModel.attributes;
438
440
  const viaRelation = intermediateAttributes[via];
439
- if (!viaRelation || this.resolveRelationTarget(viaRelation.model) !== 'user') {
441
+ if (!viaRelation || this.resolveRelationTarget(viaRelation.model, viaRelation.resourceName) !== 'User') {
440
442
  throw new Error(`Unsupported or invalid via field "${via}" in intermediate model "${intermediateRelation.model}". ` +
441
443
  `Currently, only relations to "User" are supported`);
442
444
  }
443
445
  // Fetch all intermediate records associated with the user
444
- const intermediateRecords = await this.accessRelationModel(intermediateRelation.model).find({ where: { [via]: this.user.id } });
446
+ const intermediateRecords = await this.accessRelationModel(intermediateRelation.model, intermediateRelation.resourceName).find({ where: { [via]: this.user.id } });
445
447
  const intermediateIds = (intermediateRecords || []).map((r) => r.id);
446
448
  // Filter main model by all matching intermediate record IDs
447
449
  sanitizedCriteria = { ...sanitizedCriteria, [field]: { in: intermediateIds } };
@@ -467,14 +469,14 @@ export class DataAccessor {
467
469
  // Check if the relation points to `User` or `Group` in the model's attributes
468
470
  const modelAttributes = this.modelResource.model.attributes;
469
471
  const relation = modelAttributes[accessField];
470
- const relationModel = this.resolveRelationTarget(relation?.model);
471
- if (relation && ['user', 'group'].includes(relationModel ?? "")) {
472
- if (relationModel === 'user') {
472
+ const relationModel = this.resolveRelationTarget(relation?.model, relation?.resourceName);
473
+ if (relation && ['User', 'Group'].includes(relationModel ?? "")) {
474
+ if (relationModel === 'User') {
473
475
  if (!this.user.isAdministrator) {
474
476
  updatedRecord[accessField] = this.user.id;
475
477
  }
476
478
  }
477
- else if (relationModel === 'group') {
479
+ else if (relationModel === 'Group') {
478
480
  const userGroups = this.user.groups || [];
479
481
  if (userGroups.length === 1) {
480
482
  updatedRecord[accessField] = userGroups[0].id;
@@ -496,16 +498,16 @@ export class DataAccessor {
496
498
  const modelAttributes = this.modelResource.model.attributes;
497
499
  const intermediateRelation = modelAttributes[field];
498
500
  if (!intermediateRelation || !intermediateRelation.model) {
499
- throw new Error(`Invalid intermediate relation configuration for field "${field}" in model ${this.modelResource.model.modelname}`);
501
+ throw new Error(`Invalid intermediate relation configuration for field "${field}" in model ${this.modelResource.name}`);
500
502
  }
501
- const intermediateModel = this.adminizer.modelHandler.model.get(intermediateRelation.model.toLowerCase());
503
+ const intermediateModel = this.getRelationModel(intermediateRelation.model, intermediateRelation.resourceName);
502
504
  if (!intermediateModel) {
503
505
  throw new Error(`Intermediate model "${intermediateRelation.model}" not found`);
504
506
  }
505
507
  // Validate the `via` field in the intermediate model
506
508
  const intermediateAttributes = intermediateModel.attributes;
507
509
  const viaRelation = intermediateAttributes[via];
508
- if (!viaRelation || this.resolveRelationTarget(viaRelation.model) !== 'user') {
510
+ if (!viaRelation || this.resolveRelationTarget(viaRelation.model, viaRelation.resourceName) !== 'User') {
509
511
  throw new Error(`Unsupported or invalid via field "${via}" in intermediate model "${intermediateRelation.model}". ` +
510
512
  `Currently, only relations to "User" are supported`);
511
513
  }
@@ -513,7 +515,7 @@ export class DataAccessor {
513
515
  const chosenId = typeof updatedRecord[field] === 'object'
514
516
  ? updatedRecord[field][intermediatePk]
515
517
  : updatedRecord[field];
516
- const record = await this.accessRelationModel(intermediateRelation.model).findOne({ where: { [intermediatePk]: chosenId, [via]: this.user.id } });
518
+ const record = await this.accessRelationModel(intermediateRelation.model, intermediateRelation.resourceName).findOne({ where: { [intermediatePk]: chosenId, [via]: this.user.id } });
517
519
  if (!record) {
518
520
  throw new Error(`Access denied: "${field}" does not belong to the current user`);
519
521
  }
@@ -1,4 +1,4 @@
1
- import { Adminizer } from "./Adminizer";
1
+ import { Adminizer } from "./Adminizer.js";
2
2
  declare class MiddlewareManager {
3
3
  private readonly _adminizer;
4
4
  constructor(adminizer: Adminizer);
@@ -1,5 +1,5 @@
1
- import type { AccessRightsToken } from "../../interfaces/types";
2
- import type { Adminizer } from "../Adminizer";
1
+ import type { AccessRightsToken } from "../../interfaces/types.js";
2
+ import type { Adminizer } from "../Adminizer.js";
3
3
  export interface AccessRightsRecord {
4
4
  id: string;
5
5
  appName: string;
@@ -0,0 +1,72 @@
1
+ import type { User } from '../../models/User.js';
2
+ import type { Adminizer } from '../Adminizer.js';
3
+ export interface AdminLink {
4
+ id: string;
5
+ type: string;
6
+ name: string;
7
+ link: string;
8
+ title?: string;
9
+ section?: string;
10
+ accessRightsToken?: string;
11
+ }
12
+ export interface ResolvedAdminLink extends AdminLink {
13
+ owner: string;
14
+ title: string;
15
+ }
16
+ /** A placeholder of an admin link template, e.g. `id` in `/model/Test/edit/:id`. */
17
+ export interface AdminLinkTemplateParam {
18
+ name: string;
19
+ description?: string;
20
+ }
21
+ /**
22
+ * A parametrized admin page, e.g. `/admin/model/Test/edit/:id`. Templates are
23
+ * what makes a record page reachable: the concrete URL only exists once the
24
+ * caller knows the record id.
25
+ */
26
+ export interface AdminLinkTemplate {
27
+ id: string;
28
+ title: string;
29
+ /** Absolute admin path with `:param` placeholders. */
30
+ template: string;
31
+ description?: string;
32
+ section?: string;
33
+ accessRightsToken?: string;
34
+ /** Placeholder descriptions; placeholders absent here are still required. */
35
+ params?: AdminLinkTemplateParam[];
36
+ }
37
+ export interface ResolvedAdminLinkTemplate extends AdminLinkTemplate {
38
+ owner: string;
39
+ params: AdminLinkTemplateParam[];
40
+ }
41
+ /** Server-side registry of standalone admin pages contributed by applications. */
42
+ export declare class AdminLinkHandler {
43
+ private readonly adminizer;
44
+ private readonly links;
45
+ private readonly templates;
46
+ constructor(adminizer: Adminizer);
47
+ add(link: AdminLink, owner?: string): string;
48
+ remove(id: string, owner?: string): boolean;
49
+ list(user: User, type?: string): ResolvedAdminLink[];
50
+ resolve(user: User, type: string, name: string): ResolvedAdminLink | undefined;
51
+ addTemplate(template: AdminLinkTemplate, owner?: string): string;
52
+ removeTemplate(id: string, owner?: string): boolean;
53
+ /**
54
+ * Every parametrized page this user may open: model record pages, catalog
55
+ * items, templates registered by apps and any navigation link that carries
56
+ * placeholders of its own.
57
+ */
58
+ listTemplates(user: User): ResolvedAdminLinkTemplate[];
59
+ /**
60
+ * Turns a template id (or its title) plus placeholder values into a real
61
+ * admin URL, rejecting templates this user may not open.
62
+ */
63
+ resolveTemplate(user: User, id: string, params?: Record<string, unknown>): string;
64
+ /** Fills the placeholders of a template with url-encoded values. */
65
+ buildHref(template: ResolvedAdminLinkTemplate, params?: Record<string, unknown>): string;
66
+ /** True for paths that change data and therefore must never be navigated to. */
67
+ isDestructivePath(path: string): boolean;
68
+ /** Templates derived from the current configuration, catalogs and navigation. */
69
+ private builtinTemplates;
70
+ private describeParams;
71
+ private slug;
72
+ }
@@ -0,0 +1,175 @@
1
+ /** Segments no template may point at: opening one mutates data. */
2
+ const DESTRUCTIVE_PATH = /\/model\/[^/]+\/remove(\/|$)/i;
3
+ /** Server-side registry of standalone admin pages contributed by applications. */
4
+ export class AdminLinkHandler {
5
+ adminizer;
6
+ links = new Map();
7
+ templates = new Map();
8
+ constructor(adminizer) {
9
+ this.adminizer = adminizer;
10
+ }
11
+ add(link, owner = 'host') {
12
+ const type = link.type.trim();
13
+ const name = link.name.trim();
14
+ const href = link.link.trim();
15
+ if (!type || !name || !href)
16
+ throw new Error('Admin link requires type, name and link');
17
+ if (!href.startsWith('/'))
18
+ throw new Error(`Admin link "${name}" must use an absolute admin path`);
19
+ const id = `${this.slug(type)}:${this.slug(name)}`;
20
+ if (this.links.has(id))
21
+ throw new Error(`Admin link "${id}" is already registered`);
22
+ this.links.set(id, {
23
+ ...link, id, type, name, link: href, owner,
24
+ title: link.title?.trim() || name,
25
+ section: link.section?.trim() || undefined,
26
+ });
27
+ return id;
28
+ }
29
+ remove(id, owner) {
30
+ const link = this.links.get(id);
31
+ if (!link || (owner && link.owner !== owner))
32
+ return false;
33
+ return this.links.delete(id);
34
+ }
35
+ list(user, type) {
36
+ const wanted = type ? this.slug(type) : null;
37
+ return [...this.links.values()].filter((link) => (!wanted || this.slug(link.type) === wanted)
38
+ && (!link.accessRightsToken || this.adminizer.accessRightsHelper.hasPermission(link.accessRightsToken, user)));
39
+ }
40
+ resolve(user, type, name) {
41
+ const wanted = this.slug(name);
42
+ return this.list(user, type).find((link) => this.slug(link.name) === wanted || this.slug(link.title) === wanted);
43
+ }
44
+ addTemplate(template, owner = 'host') {
45
+ const id = template.id.trim();
46
+ const title = template.title.trim();
47
+ const path = template.template.trim();
48
+ if (!id || !title || !path)
49
+ throw new Error('Admin link template requires id, title and template');
50
+ if (!path.startsWith('/'))
51
+ throw new Error(`Admin link template "${id}" must use an absolute admin path`);
52
+ if (this.templates.has(id))
53
+ throw new Error(`Admin link template "${id}" is already registered`);
54
+ this.templates.set(id, {
55
+ ...template, id, title, template: path, owner,
56
+ description: template.description?.trim() || undefined,
57
+ section: template.section?.trim() || undefined,
58
+ params: this.describeParams(path, template.params),
59
+ });
60
+ return id;
61
+ }
62
+ removeTemplate(id, owner) {
63
+ const template = this.templates.get(id);
64
+ if (!template || (owner && template.owner !== owner))
65
+ return false;
66
+ return this.templates.delete(id);
67
+ }
68
+ /**
69
+ * Every parametrized page this user may open: model record pages, catalog
70
+ * items, templates registered by apps and any navigation link that carries
71
+ * placeholders of its own.
72
+ */
73
+ listTemplates(user) {
74
+ const templates = [];
75
+ const push = (template) => {
76
+ if (DESTRUCTIVE_PATH.test(template.template))
77
+ return;
78
+ if (template.accessRightsToken && !this.adminizer.accessRightsHelper.hasPermission(template.accessRightsToken, user))
79
+ return;
80
+ if (templates.some((existing) => existing.template === template.template || existing.id === template.id))
81
+ return;
82
+ templates.push(template);
83
+ };
84
+ for (const template of this.builtinTemplates(user))
85
+ push(template);
86
+ for (const template of this.templates.values())
87
+ push(template);
88
+ return templates;
89
+ }
90
+ /**
91
+ * Turns a template id (or its title) plus placeholder values into a real
92
+ * admin URL, rejecting templates this user may not open.
93
+ */
94
+ resolveTemplate(user, id, params = {}) {
95
+ const wanted = this.slug(id);
96
+ const available = this.listTemplates(user);
97
+ const template = available.find((candidate) => this.slug(candidate.id) === wanted)
98
+ ?? available.find((candidate) => this.slug(candidate.title) === wanted || this.slug(candidate.template) === wanted);
99
+ if (!template)
100
+ throw new Error(`Admin link template "${id}" is not available`);
101
+ return this.buildHref(template, params);
102
+ }
103
+ /** Fills the placeholders of a template with url-encoded values. */
104
+ buildHref(template, params = {}) {
105
+ return template.template.replace(/:([A-Za-z0-9_]+)/g, (_match, name) => {
106
+ const value = params[name];
107
+ if (value === undefined || value === null || value === '') {
108
+ throw new Error(`Admin link template "${template.id}" requires the "${name}" parameter`);
109
+ }
110
+ return encodeURIComponent(String(value));
111
+ });
112
+ }
113
+ /** True for paths that change data and therefore must never be navigated to. */
114
+ isDestructivePath(path) {
115
+ return DESTRUCTIVE_PATH.test(path);
116
+ }
117
+ /** Templates derived from the current configuration, catalogs and navigation. */
118
+ builtinTemplates(user) {
119
+ const prefix = (this.adminizer.config.routePrefix || '').replace(/\/+$/, '');
120
+ const templates = [];
121
+ const builtin = (id, title, path, accessRightsToken, description, section, params = []) => {
122
+ templates.push({
123
+ id, title, template: path, accessRightsToken, description, section, owner: 'host',
124
+ params: this.describeParams(path, params),
125
+ });
126
+ };
127
+ const recordId = [
128
+ { name: 'id', description: 'Identifier of the record, as returned when reading its data.' },
129
+ ];
130
+ for (const [model, config] of Object.entries(this.adminizer.config.models ?? {})) {
131
+ const title = typeof config === 'object' && config?.title ? config.title : model;
132
+ const section = typeof config === 'object' ? config?.navbar?.section : undefined;
133
+ builtin(`model-${model}-edit`, `${title}: open record`, `${prefix}/model/${model}/edit/:id`, `update-${model}-model`, `Open the edit page of a single ${title} record by its id.`, section, recordId);
134
+ builtin(`model-${model}-add`, `${title}: create record`, `${prefix}/model/${model}/add`, `create-${model}-model`, `Open the create form of ${title}.`, section);
135
+ }
136
+ for (const catalog of this.adminizer.catalogHandler.getAll()) {
137
+ if (!catalog?.slug)
138
+ continue;
139
+ builtin(`catalog-${catalog.slug}-item`, `${catalog.name || catalog.slug}: open item`, `${prefix}/catalog/${catalog.slug}/:id`, `catalog-${catalog.slug}`, `Open a single item of the ${catalog.name || catalog.slug} catalog by its id.`, undefined, recordId);
140
+ }
141
+ // Navigation links may carry placeholders themselves (app pages, custom
142
+ // tools). Such a link cannot be opened as-is, so it is a template.
143
+ const fromNavigation = (item, section) => {
144
+ const path = typeof item?.link === 'string' ? item.link : '';
145
+ if (path.startsWith('/') && /:[A-Za-z0-9_]+/.test(path) && item.title) {
146
+ templates.push({
147
+ id: `link-${this.slug(String(item.id || item.title))}`,
148
+ title: String(item.title),
149
+ template: path,
150
+ section: item.section || section,
151
+ accessRightsToken: item.accessRightsToken || undefined,
152
+ owner: 'host',
153
+ params: this.describeParams(path),
154
+ });
155
+ }
156
+ for (const child of item?.actions ?? item?.subItems ?? [])
157
+ fromNavigation(child, item?.section || section);
158
+ };
159
+ for (const item of this.adminizer.menuHelper.getMenuItems(user))
160
+ fromNavigation(item, item.section);
161
+ for (const link of this.links.values())
162
+ fromNavigation(link, link.section);
163
+ return templates;
164
+ }
165
+ describeParams(path, described = []) {
166
+ const names = [...path.matchAll(/:([A-Za-z0-9_]+)/g)].map((match) => match[1]);
167
+ return [...new Set(names)].map((name) => ({
168
+ name,
169
+ description: described.find((param) => param.name === name)?.description,
170
+ }));
171
+ }
172
+ slug(value) {
173
+ return String(value).trim().toLowerCase().replace(/[\s_-]+/g, '-');
174
+ }
175
+ }
@@ -0,0 +1,39 @@
1
+ import type { Adminizer } from '../Adminizer.js';
2
+ import type { AbstractAiModelService } from './AbstractAiModelService.js';
3
+ import type { User } from '../../models/User.js';
4
+ export interface AiConversation {
5
+ id: string;
6
+ title: string;
7
+ createdAt: string;
8
+ updatedAt: string;
9
+ messages: Array<Record<string, unknown>>;
10
+ }
11
+ /**
12
+ * Storage contract for an agent's dialogs. Applications can extend this class
13
+ * to persist conversations in their own database instead of process memory.
14
+ */
15
+ export declare abstract class AbstractAiConversationHistoryService {
16
+ protected agent: AbstractAiModelService;
17
+ protected adminizer: Adminizer;
18
+ initialize(agent: AbstractAiModelService, adminizer: Adminizer): void;
19
+ abstract list(user: User): AiConversation[];
20
+ abstract getActive(user: User): AiConversation;
21
+ abstract create(user: User, title?: string): AiConversation;
22
+ abstract select(user: User, conversationId: string): AiConversation | undefined;
23
+ abstract remove(user: User, conversationId: string): boolean;
24
+ abstract saveActive(user: User, messages: Array<Record<string, unknown>>, title?: string): AiConversation;
25
+ }
26
+ /** Default storage used when an agent does not provide a database-backed one. */
27
+ export declare class InMemoryAiConversationHistoryService extends AbstractAiConversationHistoryService {
28
+ private readonly conversations;
29
+ private readonly activeConversationIds;
30
+ list(user: User): AiConversation[];
31
+ getActive(user: User): AiConversation;
32
+ create(user: User, title?: string): AiConversation;
33
+ select(user: User, conversationId: string): AiConversation | undefined;
34
+ remove(user: User, conversationId: string): boolean;
35
+ saveActive(user: User, messages: Array<Record<string, unknown>>, title?: string): AiConversation;
36
+ private getConversations;
37
+ private getKey;
38
+ private findFirstUserText;
39
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Storage contract for an agent's dialogs. Applications can extend this class
3
+ * to persist conversations in their own database instead of process memory.
4
+ */
5
+ export class AbstractAiConversationHistoryService {
6
+ agent;
7
+ adminizer;
8
+ initialize(agent, adminizer) {
9
+ this.agent = agent;
10
+ this.adminizer = adminizer;
11
+ }
12
+ }
13
+ /** Default storage used when an agent does not provide a database-backed one. */
14
+ export class InMemoryAiConversationHistoryService extends AbstractAiConversationHistoryService {
15
+ conversations = new Map();
16
+ activeConversationIds = new Map();
17
+ list(user) {
18
+ return [...this.getConversations(user)]
19
+ .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
20
+ }
21
+ getActive(user) {
22
+ const key = this.getKey(user);
23
+ const conversations = this.getConversations(user);
24
+ const activeId = this.activeConversationIds.get(key);
25
+ const active = conversations.find((conversation) => conversation.id === activeId);
26
+ if (active)
27
+ return active;
28
+ const conversation = this.create(user);
29
+ this.activeConversationIds.set(key, conversation.id);
30
+ return conversation;
31
+ }
32
+ create(user, title = 'New conversation') {
33
+ const now = new Date().toISOString();
34
+ const conversation = {
35
+ id: `conversation-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`,
36
+ title,
37
+ createdAt: now,
38
+ updatedAt: now,
39
+ messages: [],
40
+ };
41
+ this.getConversations(user).push(conversation);
42
+ this.activeConversationIds.set(this.getKey(user), conversation.id);
43
+ return conversation;
44
+ }
45
+ select(user, conversationId) {
46
+ const conversation = this.getConversations(user).find((item) => item.id === conversationId);
47
+ if (conversation)
48
+ this.activeConversationIds.set(this.getKey(user), conversation.id);
49
+ return conversation;
50
+ }
51
+ remove(user, conversationId) {
52
+ const conversations = this.getConversations(user);
53
+ const index = conversations.findIndex((conversation) => conversation.id === conversationId);
54
+ if (index < 0)
55
+ return false;
56
+ conversations.splice(index, 1);
57
+ if (this.activeConversationIds.get(this.getKey(user)) === conversationId) {
58
+ this.activeConversationIds.delete(this.getKey(user));
59
+ }
60
+ return true;
61
+ }
62
+ saveActive(user, messages, title) {
63
+ const conversation = this.getActive(user);
64
+ conversation.messages = messages;
65
+ conversation.updatedAt = new Date().toISOString();
66
+ if (title)
67
+ conversation.title = title;
68
+ else if (conversation.title === 'New conversation') {
69
+ const firstText = this.findFirstUserText(messages);
70
+ if (firstText)
71
+ conversation.title = firstText.slice(0, 80);
72
+ }
73
+ return conversation;
74
+ }
75
+ getConversations(user) {
76
+ const key = this.getKey(user);
77
+ let conversations = this.conversations.get(key);
78
+ if (!conversations) {
79
+ conversations = [];
80
+ this.conversations.set(key, conversations);
81
+ }
82
+ return conversations;
83
+ }
84
+ getKey(user) {
85
+ return `${user.id}:${this.agent.id}`;
86
+ }
87
+ findFirstUserText(messages) {
88
+ const message = messages.find((item) => item.role === 'user');
89
+ const content = message?.content;
90
+ if (typeof content === 'string')
91
+ return content.trim();
92
+ if (Array.isArray(content)) {
93
+ const text = content.find((part) => part && typeof part === 'object' && part.type === 'text');
94
+ return typeof text?.text === 'string' ? text.text.trim() : undefined;
95
+ }
96
+ return undefined;
97
+ }
98
+ }