@vc-shell/framework 1.2.2 → 1.2.3-beta.0

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 (231) hide show
  1. package/core/composables/index.ts +2 -0
  2. package/core/composables/useAssets/index.ts +72 -28
  3. package/core/composables/useAsync/index.ts +4 -1
  4. package/core/composables/useBladeRegistry/index.ts +6 -5
  5. package/core/composables/useBreadcrumbs/index.ts +4 -1
  6. package/core/composables/useErrorHandler/index.ts +4 -1
  7. package/core/composables/useFunctions/debounce.ts +0 -1
  8. package/core/composables/useFunctions/delay.ts +0 -1
  9. package/core/composables/useFunctions/index.ts +0 -1
  10. package/core/composables/useFunctions/once.ts +0 -1
  11. package/core/composables/useFunctions/sleep.ts +0 -1
  12. package/core/composables/useFunctions/throttle.ts +0 -1
  13. package/core/composables/useGlobalSearch/index.ts +3 -3
  14. package/core/composables/useMenuService/index.ts +5 -2
  15. package/core/composables/useNotifications/index.ts +5 -2
  16. package/core/composables/useTheme/index.ts +4 -1
  17. package/core/composables/useUser/index.ts +189 -20
  18. package/core/composables/useWidgets/index.ts +5 -2
  19. package/core/constants/defaults.ts +76 -0
  20. package/core/constants/index.ts +2 -0
  21. package/core/constants/ui.ts +68 -0
  22. package/core/interceptors/index.ts +5 -2
  23. package/core/plugins/ai-agent/README.md +336 -0
  24. package/core/plugins/ai-agent/components/VcAiAgentPanel.vue +125 -0
  25. package/core/plugins/ai-agent/components/_internal/VcAiAgentHeader.vue +182 -0
  26. package/core/plugins/ai-agent/components/_internal/VcAiAgentIframe.vue +77 -0
  27. package/core/plugins/ai-agent/components/index.ts +1 -0
  28. package/core/plugins/ai-agent/composables/index.ts +4 -0
  29. package/core/plugins/ai-agent/composables/useAiAgent.ts +231 -0
  30. package/core/plugins/ai-agent/composables/useAiAgentContext.ts +280 -0
  31. package/core/plugins/ai-agent/constants.ts +89 -0
  32. package/core/plugins/ai-agent/index.ts +91 -0
  33. package/core/plugins/ai-agent/services/ai-agent-service.ts +598 -0
  34. package/core/plugins/ai-agent/types.ts +310 -0
  35. package/core/plugins/modularity/index.ts +8 -6
  36. package/core/plugins/modularity/loader.ts +36 -33
  37. package/core/plugins/signalR/index.ts +6 -3
  38. package/core/services/app-bar-menu-service.ts +4 -1
  39. package/core/services/dashboard-service.ts +4 -1
  40. package/core/services/index.ts +2 -0
  41. package/core/services/menu-service.ts +4 -1
  42. package/core/services/settings-menu-service.ts +4 -1
  43. package/core/services/toolbar-service.ts +18 -3
  44. package/core/services/widget-service.ts +7 -4
  45. package/core/types/index.ts +3 -0
  46. package/core/types/services.ts +194 -0
  47. package/core/utilities/errorTypes.ts +126 -0
  48. package/core/utilities/index.ts +2 -0
  49. package/core/utilities/logger.ts +120 -0
  50. package/dist/core/composables/useAssets/index.d.ts.map +1 -1
  51. package/dist/core/composables/useAsync/index.d.ts.map +1 -1
  52. package/dist/core/composables/useBladeRegistry/index.d.ts.map +1 -1
  53. package/dist/core/composables/useBreadcrumbs/index.d.ts.map +1 -1
  54. package/dist/core/composables/useErrorHandler/index.d.ts.map +1 -1
  55. package/dist/core/composables/useFunctions/debounce.d.ts.map +1 -1
  56. package/dist/core/composables/useFunctions/delay.d.ts.map +1 -1
  57. package/dist/core/composables/useFunctions/index.d.ts.map +1 -1
  58. package/dist/core/composables/useFunctions/once.d.ts.map +1 -1
  59. package/dist/core/composables/useFunctions/sleep.d.ts.map +1 -1
  60. package/dist/core/composables/useFunctions/throttle.d.ts.map +1 -1
  61. package/dist/core/composables/useGlobalSearch/index.d.ts.map +1 -1
  62. package/dist/core/composables/useMenuService/index.d.ts.map +1 -1
  63. package/dist/core/composables/useNotifications/index.d.ts.map +1 -1
  64. package/dist/core/composables/useTheme/index.d.ts.map +1 -1
  65. package/dist/core/composables/useUser/index.d.ts +8 -0
  66. package/dist/core/composables/useUser/index.d.ts.map +1 -1
  67. package/dist/core/composables/useWidgets/index.d.ts.map +1 -1
  68. package/dist/core/constants/defaults.d.ts +63 -0
  69. package/dist/core/constants/defaults.d.ts.map +1 -0
  70. package/dist/core/constants/index.d.ts +2 -0
  71. package/dist/core/constants/index.d.ts.map +1 -1
  72. package/dist/core/constants/ui.d.ts +50 -0
  73. package/dist/core/constants/ui.d.ts.map +1 -0
  74. package/dist/core/interceptors/index.d.ts.map +1 -1
  75. package/dist/core/plugins/ai-agent/components/VcAiAgentPanel.vue.d.ts +3 -0
  76. package/dist/core/plugins/ai-agent/components/VcAiAgentPanel.vue.d.ts.map +1 -0
  77. package/dist/core/plugins/ai-agent/components/_internal/VcAiAgentHeader.vue.d.ts +15 -0
  78. package/dist/core/plugins/ai-agent/components/_internal/VcAiAgentHeader.vue.d.ts.map +1 -0
  79. package/dist/core/plugins/ai-agent/components/_internal/VcAiAgentIframe.vue.d.ts +10 -0
  80. package/dist/core/plugins/ai-agent/components/_internal/VcAiAgentIframe.vue.d.ts.map +1 -0
  81. package/dist/core/plugins/ai-agent/components/index.d.ts +2 -0
  82. package/dist/core/plugins/ai-agent/components/index.d.ts.map +1 -0
  83. package/dist/core/plugins/ai-agent/composables/index.d.ts +4 -0
  84. package/dist/core/plugins/ai-agent/composables/index.d.ts.map +1 -0
  85. package/dist/core/plugins/ai-agent/composables/useAiAgent.d.ts +95 -0
  86. package/dist/core/plugins/ai-agent/composables/useAiAgent.d.ts.map +1 -0
  87. package/dist/core/plugins/ai-agent/composables/useAiAgentContext.d.ts +55 -0
  88. package/dist/core/plugins/ai-agent/composables/useAiAgentContext.d.ts.map +1 -0
  89. package/dist/core/plugins/ai-agent/constants.d.ts +47 -0
  90. package/dist/core/plugins/ai-agent/constants.d.ts.map +1 -0
  91. package/dist/core/plugins/ai-agent/index.d.ts +48 -0
  92. package/dist/core/plugins/ai-agent/index.d.ts.map +1 -0
  93. package/dist/core/plugins/ai-agent/services/ai-agent-service.d.ts +45 -0
  94. package/dist/core/plugins/ai-agent/services/ai-agent-service.d.ts.map +1 -0
  95. package/dist/core/plugins/ai-agent/types.d.ts +258 -0
  96. package/dist/core/plugins/ai-agent/types.d.ts.map +1 -0
  97. package/dist/core/plugins/modularity/index.d.ts.map +1 -1
  98. package/dist/core/plugins/modularity/loader.d.ts.map +1 -1
  99. package/dist/core/plugins/signalR/index.d.ts.map +1 -1
  100. package/dist/core/services/app-bar-menu-service.d.ts.map +1 -1
  101. package/dist/core/services/dashboard-service.d.ts.map +1 -1
  102. package/dist/core/services/menu-service.d.ts.map +1 -1
  103. package/dist/core/services/settings-menu-service.d.ts.map +1 -1
  104. package/dist/core/services/toolbar-service.d.ts.map +1 -1
  105. package/dist/core/services/widget-service.d.ts.map +1 -1
  106. package/dist/core/types/index.d.ts.map +1 -1
  107. package/dist/core/types/services.d.ts +169 -0
  108. package/dist/core/types/services.d.ts.map +1 -0
  109. package/dist/core/utilities/errorTypes.d.ts +61 -0
  110. package/dist/core/utilities/errorTypes.d.ts.map +1 -0
  111. package/dist/core/utilities/index.d.ts +2 -0
  112. package/dist/core/utilities/index.d.ts.map +1 -1
  113. package/dist/core/utilities/logger.d.ts +259 -0
  114. package/dist/core/utilities/logger.d.ts.map +1 -0
  115. package/dist/framework.js +9623 -8417
  116. package/dist/index.css +1 -1
  117. package/dist/index.d.ts +19 -0
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/injection-keys.d.ts +21 -6
  120. package/dist/injection-keys.d.ts.map +1 -1
  121. package/dist/shared/components/app-switcher/composables/useAppSwitcher/index.d.ts.map +1 -1
  122. package/dist/shared/components/blade-navigation/components/vc-blade-navigation/vc-blade-navigation.vue.d.ts.map +1 -1
  123. package/dist/shared/components/blade-navigation/composables/useBladeNavigation/index.d.ts.map +1 -1
  124. package/dist/shared/components/blade-navigation/composables/useBladeNavigation/internal/bladeActions.d.ts.map +1 -1
  125. package/dist/shared/components/blade-navigation/composables/useBladeNavigation/internal/bladeRouteResolver.d.ts.map +1 -1
  126. package/dist/shared/components/blade-navigation/composables/useBladeNavigation/internal/routerUtils.d.ts.map +1 -1
  127. package/dist/shared/components/draggable-dashboard/composables/useDashboardDragAndDrop.d.ts.map +1 -1
  128. package/dist/shared/components/draggable-dashboard/composables/useLayoutPersistence.d.ts.map +1 -1
  129. package/dist/shared/components/notifications/composables/useContainer/index.d.ts.map +1 -1
  130. package/dist/shared/components/notifications/composables/useInstance/index.d.ts.map +1 -1
  131. package/dist/shared/components/notifications/core/notification.d.ts.map +1 -1
  132. package/dist/shared/components/popup-handler/components/vc-popup-container/vc-popup-container.vue.d.ts.map +1 -1
  133. package/dist/shared/components/sign-in/useExternalProvider.d.ts.map +1 -1
  134. package/dist/shared/composables/index.d.ts +1 -0
  135. package/dist/shared/composables/index.d.ts.map +1 -1
  136. package/dist/shared/composables/useExternalWidgets.d.ts.map +1 -1
  137. package/dist/shared/composables/useMenuExpanded.d.ts.map +1 -1
  138. package/dist/shared/composables/useTableSelection.d.ts +57 -0
  139. package/dist/shared/composables/useTableSelection.d.ts.map +1 -0
  140. package/dist/shared/composables/useTableSort.d.ts.map +1 -1
  141. package/dist/shared/modules/assets-manager/components/assets-manager/assets-manager.vue.d.ts.map +1 -1
  142. package/dist/shared/pages/LoginPage/components/login/Login.vue.d.ts.map +1 -1
  143. package/dist/shared/utilities/colorUtils.d.ts +0 -6
  144. package/dist/shared/utilities/colorUtils.d.ts.map +1 -1
  145. package/dist/tsconfig.tsbuildinfo +1 -1
  146. package/dist/ui/components/atoms/vc-banner/vc-banner.vue.d.ts.map +1 -1
  147. package/dist/ui/components/atoms/vc-button/vc-button.vue.d.ts +0 -15
  148. package/dist/ui/components/atoms/vc-button/vc-button.vue.d.ts.map +1 -1
  149. package/dist/ui/components/atoms/vc-container/vc-container.vue.d.ts.map +1 -1
  150. package/dist/ui/components/atoms/vc-icon/vc-icon.vue.d.ts.map +1 -1
  151. package/dist/ui/components/atoms/vc-icon/vc-lucide-icon.vue.d.ts.map +1 -1
  152. package/dist/ui/components/atoms/vc-image/vc-image.vue.d.ts.map +1 -1
  153. package/dist/ui/components/atoms/vc-link/vc-link.vue.d.ts.map +1 -1
  154. package/dist/ui/components/atoms/vc-loading/vc-loading.vue.d.ts.map +1 -1
  155. package/dist/ui/components/atoms/vc-status/vc-status.vue.d.ts +0 -5
  156. package/dist/ui/components/atoms/vc-status/vc-status.vue.d.ts.map +1 -1
  157. package/dist/ui/components/atoms/vc-tooltip/vc-tooltip.vue.d.ts.map +1 -1
  158. package/dist/ui/components/atoms/vc-video/vc-video.vue.d.ts.map +1 -1
  159. package/dist/ui/components/atoms/vc-widget/vc-widget.vue.d.ts.map +1 -1
  160. package/dist/ui/components/molecules/vc-breadcrumbs/vc-breadcrumbs.vue.d.ts.map +1 -1
  161. package/dist/ui/components/molecules/vc-input/vc-input.vue.d.ts.map +1 -1
  162. package/dist/ui/components/molecules/vc-pagination/vc-pagination.vue.d.ts.map +1 -1
  163. package/dist/ui/components/molecules/vc-toast/vc-toast.vue.d.ts.map +1 -1
  164. package/dist/ui/components/organisms/vc-app/vc-app.vue.d.ts.map +1 -1
  165. package/dist/ui/components/organisms/vc-blade/_internal/vc-blade-toolbar/_internal/vc-blade-toolbar-buttons/_internal/vc-blade-toolbar-button/vc-blade-toolbar-button.vue.d.ts.map +1 -1
  166. package/dist/ui/components/organisms/vc-blade/vc-blade.vue.d.ts.map +1 -1
  167. package/dist/ui/components/organisms/vc-login-form/vc-login-form.vue.d.ts.map +1 -1
  168. package/dist/ui/components/organisms/vc-table/_internal/vc-table-cell/vc-table-cell.vue.d.ts.map +1 -1
  169. package/dist/ui/components/organisms/vc-table/composables/useTableActions.d.ts.map +1 -1
  170. package/dist/ui/components/organisms/vc-table/composables/useTableColumnResize.d.ts.map +1 -1
  171. package/dist/ui/components/organisms/vc-table/composables/useTableRowReorder.d.ts.map +1 -1
  172. package/dist/ui/components/organisms/vc-table/composables/useTableSelection.d.ts.map +1 -1
  173. package/dist/ui/components/organisms/vc-table/composables/useTableState.d.ts.map +1 -1
  174. package/dist/{vendor-lodash-es-BqkGj3Jl.js → vendor-lodash-es-SgOIjJF8.js} +2 -0
  175. package/package.json +5 -5
  176. package/shared/components/app-switcher/composables/useAppSwitcher/index.ts +4 -1
  177. package/shared/components/blade-navigation/components/vc-blade-navigation/vc-blade-navigation.vue +67 -4
  178. package/shared/components/blade-navigation/composables/useBladeNavigation/index.ts +13 -10
  179. package/shared/components/blade-navigation/composables/useBladeNavigation/internal/bladeActions.ts +7 -4
  180. package/shared/components/blade-navigation/composables/useBladeNavigation/internal/bladeRouteResolver.ts +4 -1
  181. package/shared/components/blade-navigation/composables/useBladeNavigation/internal/routerUtils.ts +4 -1
  182. package/shared/components/change-password/change-password.vue +1 -1
  183. package/shared/components/draggable-dashboard/composables/useDashboardDragAndDrop.ts +14 -5
  184. package/shared/components/draggable-dashboard/composables/useLayoutPersistence.ts +5 -2
  185. package/shared/components/index.ts +2 -0
  186. package/shared/components/notifications/composables/useContainer/index.ts +8 -6
  187. package/shared/components/notifications/composables/useInstance/index.ts +4 -1
  188. package/shared/components/notifications/core/notification.ts +10 -7
  189. package/shared/components/popup-handler/components/vc-popup-container/vc-popup-container.vue +20 -1
  190. package/shared/components/sign-in/useExternalProvider.ts +6 -4
  191. package/shared/composables/index.ts +1 -0
  192. package/shared/composables/useExternalWidgets.ts +7 -4
  193. package/shared/composables/useMenuExpanded.ts +15 -1
  194. package/shared/composables/useTableSelection.ts +151 -0
  195. package/shared/composables/useTableSort.ts +4 -4
  196. package/shared/modules/assets-manager/components/assets-manager/assets-manager.vue +6 -3
  197. package/shared/pages/LoginPage/components/login/Login.vue +4 -1
  198. package/shared/utilities/colorUtils.ts +5 -12
  199. package/ui/components/atoms/vc-banner/vc-banner.vue +4 -1
  200. package/ui/components/atoms/vc-button/vc-button.vue +2 -25
  201. package/ui/components/atoms/vc-container/vc-container.vue +12 -3
  202. package/ui/components/atoms/vc-icon/vc-icon.vue +0 -10
  203. package/ui/components/atoms/vc-icon/vc-lucide-icon.vue +5 -2
  204. package/ui/components/atoms/vc-image/vc-image.vue +4 -1
  205. package/ui/components/atoms/vc-link/vc-link.vue +59 -54
  206. package/ui/components/atoms/vc-loading/vc-loading.vue +4 -0
  207. package/ui/components/atoms/vc-status/vc-status.vue +0 -5
  208. package/ui/components/atoms/vc-status-icon/vc-status-icon.vue +4 -4
  209. package/ui/components/atoms/vc-tooltip/vc-tooltip.vue +8 -1
  210. package/ui/components/atoms/vc-video/vc-video.vue +4 -2
  211. package/ui/components/atoms/vc-widget/vc-widget.vue +4 -1
  212. package/ui/components/molecules/vc-breadcrumbs/vc-breadcrumbs.vue +7 -2
  213. package/ui/components/molecules/vc-input/vc-input.vue +0 -1
  214. package/ui/components/molecules/vc-pagination/vc-pagination.vue +6 -1
  215. package/ui/components/molecules/vc-rating/vc-rating.vue +1 -1
  216. package/ui/components/molecules/vc-textarea/vc-textarea.vue +1 -1
  217. package/ui/components/molecules/vc-toast/vc-toast.vue +11 -1
  218. package/ui/components/organisms/vc-app/vc-app.vue +22 -3
  219. package/ui/components/organisms/vc-blade/_internal/vc-blade-toolbar/_internal/vc-blade-toolbar-buttons/_internal/vc-blade-toolbar-button/vc-blade-toolbar-button.vue +4 -1
  220. package/ui/components/organisms/vc-blade/_internal/vc-blade-toolbar/vc-blade-toolbar.vue +14 -14
  221. package/ui/components/organisms/vc-blade/vc-blade.vue +3 -1
  222. package/ui/components/organisms/vc-login-form/vc-login-form.vue +3 -1
  223. package/ui/components/organisms/vc-table/_internal/vc-table-cell/vc-table-cell.vue +34 -2
  224. package/ui/components/organisms/vc-table/composables/useTableActions.ts +7 -10
  225. package/ui/components/organisms/vc-table/composables/useTableColumnResize.ts +4 -1
  226. package/ui/components/organisms/vc-table/composables/useTableRowReorder.ts +5 -2
  227. package/ui/components/organisms/vc-table/composables/useTableSelection.ts +26 -18
  228. package/ui/components/organisms/vc-table/composables/useTableState.ts +4 -1
  229. package/core/services/global-search-service.ts +0 -36
  230. package/dist/core/services/global-search-service.d.ts +0 -10
  231. package/dist/core/services/global-search-service.d.ts.map +0 -1
@@ -0,0 +1,598 @@
1
+ import { computed, ref, shallowRef, watch, type ShallowRef } from "vue";
2
+ import { cloneDeep } from "lodash-es";
3
+ import { createLogger } from "../../../utilities";
4
+ import {
5
+ IAiAgentService,
6
+ IAiAgentConfig,
7
+ IAiAgentContext,
8
+ IAiAgentMessage,
9
+ AiAgentPanelState,
10
+ AiAgentMessageType,
11
+ AiAgentContextType,
12
+ IAiAgentBladeContext,
13
+ IAiAgentUserContext,
14
+ IInitContextPayload,
15
+ IUpdateContextPayload,
16
+ IChatBladeContext,
17
+ INavigateToAppPayload,
18
+ IApplyChangesPayload,
19
+ IChatErrorPayload,
20
+ IPreviewChangesPayload,
21
+ IDownloadFilePayload,
22
+ ISuggestion,
23
+ } from "../types";
24
+ import { DEFAULT_AI_AGENT_CONFIG } from "../constants";
25
+
26
+ const logger = createLogger("ai-agent-service");
27
+
28
+ /**
29
+ * Options for creating the AI agent service
30
+ */
31
+ export interface CreateAiAgentServiceOptions {
32
+ /** Function to get current user information */
33
+ userGetter: () => IAiAgentUserContext | undefined;
34
+ /** Function to get current blade context */
35
+ bladeGetter: () => IAiAgentBladeContext | null;
36
+ /** Function to get user locale */
37
+ localeGetter: () => string;
38
+ /** Function to get access token (handles automatic refresh) */
39
+ tokenGetter?: () => Promise<string | null>;
40
+ /** Function to navigate to blade */
41
+ navigateToBlade?: (bladeName: string, param?: string, options?: Record<string, unknown>) => void;
42
+ /** Function to reload current blade */
43
+ reloadBlade?: () => void;
44
+ /** Initial configuration */
45
+ initialConfig?: Partial<IAiAgentConfig>;
46
+ }
47
+
48
+ /**
49
+ * Extended AI Agent Service with internal methods for component use
50
+ */
51
+ export interface IAiAgentServiceInternal extends IAiAgentService {
52
+ /** Reference to the iframe element (set by component) */
53
+ iframeRef: ShallowRef<HTMLIFrameElement | null>;
54
+ /** Set the iframe reference (called by component) */
55
+ _setIframeRef: (iframe: HTMLIFrameElement | null) => void;
56
+ /** Handle incoming postMessage events (called by component) */
57
+ _handleIncomingMessage: (event: MessageEvent) => void;
58
+ /** Start listening for postMessage events */
59
+ _startListening: () => void;
60
+ /** Stop listening for postMessage events */
61
+ _stopListening: () => void;
62
+ /** Update context data for a blade (called by useAiAgentContext) */
63
+ _setContextData: (
64
+ items: Record<string, unknown>[],
65
+ type: AiAgentContextType,
66
+ suggestions?: ISuggestion[],
67
+ bladeId?: string,
68
+ ) => void;
69
+ /** Register preview changes handler */
70
+ _onPreviewChanges: (handler: (payload: IPreviewChangesPayload) => void) => () => void;
71
+ }
72
+
73
+ /**
74
+ * Convert internal blade context to chatbot format
75
+ */
76
+ function toBladeContext(blade: IAiAgentBladeContext | null): IChatBladeContext {
77
+ if (!blade) {
78
+ return { id: "unknown", name: "unknown", title: "Unknown" };
79
+ }
80
+ return {
81
+ id: blade.id,
82
+ name: blade.name,
83
+ title: blade.title || blade.name,
84
+ param: blade.param,
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Decode organization_id from JWT if present.
90
+ * Used as fallback when tenantId is not configured in app options.
91
+ *
92
+ * @deprecated Prefer configuring tenantId via VirtoShellFramework aiAgent options.
93
+ * organization_id from JWT identifies a specific organization within the platform,
94
+ * while tenantId should identify the platform installation itself.
95
+ */
96
+ function decodeOrganizationIdFromJwt(token?: string | null): string | undefined {
97
+ if (!token) return undefined;
98
+ try {
99
+ const parts = token.split(".");
100
+ if (parts.length < 2) return undefined;
101
+ const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
102
+ const json = atob(base64);
103
+ const payload = JSON.parse(json);
104
+ return payload.organization_id;
105
+ } catch (error) {
106
+ logger.debug("decodeOrganizationIdFromJwt_failed", error);
107
+ return undefined;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Download a file from base64 content
113
+ */
114
+ function downloadFile(payload: IDownloadFilePayload): void {
115
+ const { filename, contentType, content } = payload;
116
+
117
+ try {
118
+ const byteCharacters = atob(content);
119
+ const byteNumbers = new Array(byteCharacters.length);
120
+ for (let i = 0; i < byteCharacters.length; i++) {
121
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
122
+ }
123
+ const byteArray = new Uint8Array(byteNumbers);
124
+ const blob = new Blob([byteArray], { type: contentType });
125
+
126
+ const url = URL.createObjectURL(blob);
127
+ const link = document.createElement("a");
128
+ link.href = url;
129
+ link.download = filename;
130
+ document.body.appendChild(link);
131
+ link.click();
132
+ document.body.removeChild(link);
133
+ URL.revokeObjectURL(url);
134
+
135
+ logger.debug(`File downloaded: ${filename}`);
136
+ } catch (error) {
137
+ logger.error("Failed to download file:", error);
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Creates an AI agent service for managing the AI panel state and communication.
143
+ */
144
+ export function createAiAgentService(options: CreateAiAgentServiceOptions): IAiAgentServiceInternal {
145
+ const { userGetter, bladeGetter, localeGetter, tokenGetter, navigateToBlade, reloadBlade, initialConfig } = options;
146
+
147
+ // State
148
+ const panelState = ref<AiAgentPanelState>("closed");
149
+ const config = ref<IAiAgentConfig>({ ...DEFAULT_AI_AGENT_CONFIG, ...initialConfig });
150
+ const iframeRef: ShallowRef<HTMLIFrameElement | null> = shallowRef(null);
151
+ const isInitialized = ref(false);
152
+ const isListenerRegistered = ref(false);
153
+ const pendingInitContext = ref(false);
154
+
155
+ // Context data from useAiAgentContext - stored per blade
156
+ // Key is blade id, value is context for that blade
157
+ const bladeContexts = ref<
158
+ Map<
159
+ string,
160
+ {
161
+ items: Record<string, unknown>[];
162
+ type: AiAgentContextType;
163
+ suggestions?: ISuggestion[];
164
+ }
165
+ >
166
+ >(new Map());
167
+
168
+ // Get context for the active blade (last in the navigation stack)
169
+ const getActiveBladeContext = () => {
170
+ const activeBlade = bladeGetter();
171
+ if (!activeBlade) return { items: [], type: "list" as AiAgentContextType, suggestions: undefined };
172
+
173
+ const bladeContext = bladeContexts.value.get(activeBlade.id);
174
+ return bladeContext || { items: [], type: "list" as AiAgentContextType, suggestions: undefined };
175
+ };
176
+
177
+ // Computed accessors for backward compatibility
178
+ const contextItems = computed(() => getActiveBladeContext().items);
179
+ const contextType = computed(() => getActiveBladeContext().type);
180
+ const contextSuggestions = computed(() => getActiveBladeContext().suggestions);
181
+
182
+ // Message handlers registry
183
+ const messageHandlers = new Set<(event: IAiAgentMessage) => void>();
184
+ const previewChangesHandlers = new Set<(payload: IPreviewChangesPayload) => void>();
185
+
186
+ // Computed: is panel open
187
+ const isOpen = computed(() => panelState.value !== "closed");
188
+
189
+ // Computed: is panel expanded
190
+ const isExpanded = computed(() => panelState.value === "expanded");
191
+
192
+ // Computed: total items count
193
+ const totalItemsCount = computed(() => contextItems.value.length);
194
+
195
+ // Computed: current context (reactive)
196
+ const context = computed<IAiAgentContext>(() => ({
197
+ user: userGetter(),
198
+ currentBlade: bladeGetter(),
199
+ items: contextItems.value,
200
+ timestamp: Date.now(),
201
+ }));
202
+
203
+ /**
204
+ * Build INIT_CONTEXT payload for chatbot
205
+ * Uses cloneDeep to ensure data is fully serializable (postMessage cannot clone Vue proxies)
206
+ * Fetches fresh access token (with automatic refresh if expired)
207
+ *
208
+ * tenantId priority:
209
+ * 1. config.tenantId (configured by app via VirtoShellFramework options)
210
+ * 2. organization_id from JWT (deprecated fallback)
211
+ */
212
+ const buildInitContextPayload = async (): Promise<IInitContextPayload> => {
213
+ const accessToken = tokenGetter ? ((await tokenGetter()) ?? undefined) : undefined;
214
+ // Use configured tenantId, fallback to JWT organization_id for backward compatibility
215
+ const tenantId = config.value.tenantId || decodeOrganizationIdFromJwt(accessToken);
216
+ return {
217
+ userId: userGetter()?.id || "",
218
+ locale: localeGetter(),
219
+ tenantId,
220
+ blade: toBladeContext(bladeGetter()),
221
+ contextType: contextType.value,
222
+ items: cloneDeep(contextItems.value),
223
+ suggestions: contextSuggestions.value ? cloneDeep(contextSuggestions.value) : undefined,
224
+ accessToken,
225
+ };
226
+ };
227
+
228
+ /**
229
+ * Build UPDATE_CONTEXT payload for chatbot
230
+ * Uses cloneDeep to ensure data is fully serializable (postMessage cannot clone Vue proxies)
231
+ * Fetches fresh access token (with automatic refresh if expired)
232
+ *
233
+ * tenantId priority:
234
+ * 1. config.tenantId (configured by app via VirtoShellFramework options)
235
+ * 2. organization_id from JWT (deprecated fallback)
236
+ */
237
+ const buildUpdateContextPayload = async (): Promise<IUpdateContextPayload> => {
238
+ const accessToken = tokenGetter ? ((await tokenGetter()) ?? undefined) : undefined;
239
+ // Use configured tenantId, fallback to JWT organization_id for backward compatibility
240
+ const tenantId = config.value.tenantId || decodeOrganizationIdFromJwt(accessToken);
241
+ return {
242
+ tenantId,
243
+ blade: toBladeContext(bladeGetter()),
244
+ contextType: contextType.value,
245
+ items: cloneDeep(contextItems.value),
246
+ suggestions: contextSuggestions.value ? cloneDeep(contextSuggestions.value) : undefined,
247
+ locale: localeGetter(),
248
+ accessToken,
249
+ };
250
+ };
251
+
252
+ // Internal functions defined below, forward-declared here for watchers
253
+ let sendRawMessage: (message: { type: string; payload?: unknown }) => void;
254
+ let _handleIncomingMessage: (event: MessageEvent) => void;
255
+
256
+ // Watch for context changes and send UPDATE_CONTEXT to chatbot
257
+ watch(
258
+ () => ({
259
+ currentBlade: context.value.currentBlade,
260
+ items: context.value.items,
261
+ }),
262
+ async () => {
263
+ if (isOpen.value && isInitialized.value && iframeRef.value?.contentWindow) {
264
+ const payload = await buildUpdateContextPayload();
265
+ sendRawMessage({ type: "UPDATE_CONTEXT", payload });
266
+ }
267
+ },
268
+ { deep: true },
269
+ );
270
+
271
+ // Watch for iframe ref changes - send pending INIT_CONTEXT when iframe becomes available
272
+ watch(iframeRef, async (iframe) => {
273
+ if (iframe?.contentWindow && pendingInitContext.value) {
274
+ logger.debug("Iframe became available, sending pending INIT_CONTEXT");
275
+ pendingInitContext.value = false;
276
+ isInitialized.value = true;
277
+ const payload = await buildInitContextPayload();
278
+ sendRawMessage({ type: "INIT_CONTEXT", payload });
279
+ }
280
+ });
281
+
282
+ /**
283
+ * Start listening for messages (called by component on mount)
284
+ */
285
+ const _startListening = (): void => {
286
+ if (!isListenerRegistered.value) {
287
+ window.addEventListener("message", _handleIncomingMessage);
288
+ isListenerRegistered.value = true;
289
+ logger.debug("Message listener registered");
290
+ }
291
+ };
292
+
293
+ /**
294
+ * Stop listening for messages (called by component on unmount)
295
+ */
296
+ const _stopListening = (): void => {
297
+ if (isListenerRegistered.value) {
298
+ window.removeEventListener("message", _handleIncomingMessage);
299
+ isListenerRegistered.value = false;
300
+ logger.debug("Message listener unregistered");
301
+ }
302
+ };
303
+
304
+ /**
305
+ * Open the AI panel
306
+ */
307
+ const openPanel = (): void => {
308
+ if (panelState.value === "closed") {
309
+ panelState.value = "open";
310
+ logger.debug("Panel opened");
311
+ }
312
+ };
313
+
314
+ /**
315
+ * Close the AI panel
316
+ */
317
+ const closePanel = (): void => {
318
+ panelState.value = "closed";
319
+ // Reset initialized state since iframe will be destroyed (v-if in panel component)
320
+ // Next time panel opens, iframe will send CHAT_READY and we'll send INIT_CONTEXT again
321
+ isInitialized.value = false;
322
+ logger.debug("Panel closed, reset initialized state");
323
+ };
324
+
325
+ /**
326
+ * Toggle panel open/close
327
+ */
328
+ const togglePanel = (): void => {
329
+ if (panelState.value === "closed") {
330
+ openPanel();
331
+ } else {
332
+ closePanel();
333
+ }
334
+ };
335
+
336
+ /**
337
+ * Expand the panel to larger width
338
+ */
339
+ const expandPanel = (): void => {
340
+ if (panelState.value === "open") {
341
+ panelState.value = "expanded";
342
+ logger.debug("Panel expanded");
343
+ }
344
+ };
345
+
346
+ /**
347
+ * Collapse panel to normal width
348
+ */
349
+ const collapsePanel = (): void => {
350
+ if (panelState.value === "expanded") {
351
+ panelState.value = "open";
352
+ logger.debug("Panel collapsed");
353
+ }
354
+ };
355
+
356
+ /**
357
+ * Update configuration
358
+ */
359
+ const setConfig = (newConfig: Partial<IAiAgentConfig>): void => {
360
+ config.value = { ...config.value, ...newConfig };
361
+ logger.debug("Config updated", newConfig);
362
+ };
363
+
364
+ /**
365
+ * Send raw message to iframe (chatbot protocol format)
366
+ * Payload data should already be unwrapped from Vue proxies via toRaw() in build*Payload functions
367
+ */
368
+ sendRawMessage = (message: { type: string; payload?: unknown }): void => {
369
+ if (!iframeRef.value?.contentWindow) {
370
+ logger.warn("Cannot send message: iframe not available");
371
+ return;
372
+ }
373
+
374
+ const targetOrigin = config.value.allowedOrigins?.[0] || "*";
375
+ iframeRef.value.contentWindow.postMessage(message, targetOrigin);
376
+ logger.debug(`Message sent: ${message.type}`, message.payload);
377
+ };
378
+
379
+ /**
380
+ * Send message to AI agent iframe (public API)
381
+ */
382
+ const sendMessage = (type: AiAgentMessageType, payload: unknown): void => {
383
+ sendRawMessage({ type, payload });
384
+ };
385
+
386
+ /**
387
+ * Register message handler, returns unsubscribe function
388
+ */
389
+ const onMessage = (handler: (event: IAiAgentMessage) => void): (() => void) => {
390
+ messageHandlers.add(handler);
391
+ return () => {
392
+ messageHandlers.delete(handler);
393
+ };
394
+ };
395
+
396
+ /**
397
+ * Set the iframe reference (called by component)
398
+ */
399
+ const _setIframeRef = (iframe: HTMLIFrameElement | null): void => {
400
+ iframeRef.value = iframe;
401
+ logger.debug("Iframe ref set:", iframe ? "available" : "null");
402
+ };
403
+
404
+ /**
405
+ * Set context data for a specific blade (called by useAiAgentContext)
406
+ * If bladeId is not provided, uses the current active blade
407
+ */
408
+ const _setContextData = (
409
+ items: Record<string, unknown>[],
410
+ type: AiAgentContextType,
411
+ suggestions?: ISuggestion[],
412
+ bladeId?: string,
413
+ ): void => {
414
+ const targetBladeId = bladeId || bladeGetter()?.id;
415
+ if (!targetBladeId) {
416
+ logger.warn("Cannot set context data: no blade id available");
417
+ return;
418
+ }
419
+
420
+ if (items.length === 0 && !suggestions) {
421
+ // Remove context for this blade when cleared
422
+ bladeContexts.value.delete(targetBladeId);
423
+ logger.debug(`Context cleared for blade: ${targetBladeId}`);
424
+ } else {
425
+ // Set context for this blade
426
+ bladeContexts.value.set(targetBladeId, { items, type, suggestions });
427
+ logger.debug(`Context set for blade: ${targetBladeId}, items: ${items.length}, type: ${type}`);
428
+ }
429
+ };
430
+
431
+ /**
432
+ * Register preview changes handler
433
+ */
434
+ const _onPreviewChanges = (handler: (payload: IPreviewChangesPayload) => void): (() => void) => {
435
+ previewChangesHandlers.add(handler);
436
+ return () => {
437
+ previewChangesHandlers.delete(handler);
438
+ };
439
+ };
440
+
441
+ /**
442
+ * Handle incoming postMessage events (called by component)
443
+ */
444
+ _handleIncomingMessage = (event: MessageEvent): void => {
445
+ // Log all incoming messages for debugging
446
+ if (event.data?.type) {
447
+ logger.debug("Raw message event received:", {
448
+ origin: event.origin,
449
+ type: event.data.type,
450
+ hasIframe: !!iframeRef.value,
451
+ });
452
+ }
453
+
454
+ // Validate origin if configured
455
+ const allowedOrigins = config.value.allowedOrigins || ["*"];
456
+ if (!allowedOrigins.includes("*") && !allowedOrigins.includes(event.origin)) {
457
+ logger.warn(`Message from unauthorized origin: ${event.origin}`);
458
+ return;
459
+ }
460
+
461
+ // Validate message structure
462
+ const message = event.data;
463
+ if (!message?.type || typeof message.type !== "string") {
464
+ return;
465
+ }
466
+
467
+ logger.debug(`Message received: ${message.type}`, message.payload);
468
+
469
+ // Handle chatbot protocol messages
470
+ switch (message.type) {
471
+ case "CHAT_READY":
472
+ if (iframeRef.value?.contentWindow) {
473
+ isInitialized.value = true;
474
+ buildInitContextPayload().then((payload) => {
475
+ sendRawMessage({ type: "INIT_CONTEXT", payload });
476
+ logger.info("Chatbot ready, sent INIT_CONTEXT");
477
+ });
478
+ } else {
479
+ pendingInitContext.value = true;
480
+ logger.info("Chatbot ready, but iframe ref not available yet - pending INIT_CONTEXT");
481
+ }
482
+ break;
483
+
484
+ case "NAVIGATE_TO_APP": {
485
+ const navPayload = message.payload as INavigateToAppPayload;
486
+ if (navigateToBlade && navPayload?.bladeName) {
487
+ navigateToBlade(navPayload.bladeName, navPayload.param, navPayload.options);
488
+ logger.debug(`Navigation requested to: ${navPayload.bladeName}`);
489
+ }
490
+ break;
491
+ }
492
+
493
+ case "RELOAD_BLADE":
494
+ if (reloadBlade) {
495
+ reloadBlade();
496
+ logger.debug("Blade reload requested");
497
+ }
498
+ break;
499
+
500
+ case "PREVIEW_CHANGES": {
501
+ const previewPayload = message.payload as IPreviewChangesPayload;
502
+ console.log("[AI-AGENT-SERVICE] PREVIEW_CHANGES received", {
503
+ handlersCount: previewChangesHandlers.size,
504
+ payloadDataKeys: previewPayload?.data ? Object.keys(previewPayload.data) : [],
505
+ changedFields: previewPayload?.changedFields,
506
+ payloadPreview: JSON.stringify(previewPayload).substring(0, 500),
507
+ });
508
+ if (previewChangesHandlers.size === 0) {
509
+ console.warn("[AI-AGENT-SERVICE] No preview changes handlers registered!");
510
+ }
511
+ previewChangesHandlers.forEach((handler) => {
512
+ try {
513
+ console.log("[AI-AGENT-SERVICE] Calling preview changes handler");
514
+ handler(previewPayload);
515
+ } catch (error) {
516
+ console.error("[AI-AGENT-SERVICE] Error in preview changes handler:", error);
517
+ }
518
+ });
519
+ break;
520
+ }
521
+
522
+ case "DOWNLOAD_FILE": {
523
+ const downloadPayload = message.payload as IDownloadFilePayload;
524
+ if (downloadPayload) {
525
+ downloadFile(downloadPayload);
526
+ }
527
+ break;
528
+ }
529
+
530
+ case "APPLY_CHANGES": {
531
+ const changesPayload = message.payload as IApplyChangesPayload;
532
+ logger.debug("Apply changes requested:", changesPayload?.changes);
533
+ break;
534
+ }
535
+
536
+ case "CHAT_ERROR": {
537
+ const errorPayload = message.payload as IChatErrorPayload;
538
+ logger.error(`Chatbot error [${errorPayload?.code}]: ${errorPayload?.message}`);
539
+ break;
540
+ }
541
+
542
+ default:
543
+ break;
544
+ }
545
+
546
+ // Notify all registered handlers
547
+ const normalizedMessage: IAiAgentMessage = {
548
+ type: message.type,
549
+ payload: message.payload,
550
+ timestamp: Date.now(),
551
+ };
552
+
553
+ messageHandlers.forEach((handler) => {
554
+ try {
555
+ handler(normalizedMessage);
556
+ } catch (error) {
557
+ logger.error("Error in message handler:", error);
558
+ }
559
+ });
560
+ };
561
+
562
+ // Auto-register listener when service is created
563
+ _startListening();
564
+ logger.debug("AI Agent Service initialized, listener auto-registered");
565
+
566
+ return {
567
+ // State
568
+ panelState,
569
+ config,
570
+ context,
571
+ isOpen,
572
+ isExpanded,
573
+ totalItemsCount,
574
+
575
+ // Panel control
576
+ openPanel,
577
+ closePanel,
578
+ togglePanel,
579
+ expandPanel,
580
+ collapsePanel,
581
+
582
+ // Configuration
583
+ setConfig,
584
+
585
+ // Communication
586
+ sendMessage,
587
+ onMessage,
588
+
589
+ // Internal (for component use)
590
+ iframeRef,
591
+ _setIframeRef,
592
+ _handleIncomingMessage,
593
+ _startListening,
594
+ _stopListening,
595
+ _setContextData,
596
+ _onPreviewChanges,
597
+ };
598
+ }