@gooddata/sdk-ui-pluggable-host 11.45.0-alpha.0 → 11.45.0-alpha.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"HostUiContainer.d.ts","sourceRoot":"","sources":["../../src/components/HostUiContainer.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,EAAE,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAEH,KAAK,gBAAgB,EAExB,MAAM,2CAA2C,CAAC;AAWnD,OAAO,wBAAwB,CAAC;AAEhC,MAAM,WAAW,qBAAqB;IAClC,GAAG,EAAE,gBAAgB,CAAC;IACtB,IAAI,EAAE,gCAAgC,EAAE,CAAC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,gBAAgB,CAAC;CACpC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,EAAE,qBAAqB,2CAmM7F"}
1
+ {"version":3,"file":"HostUiContainer.d.ts","sourceRoot":"","sources":["../../src/components/HostUiContainer.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,EAA0B,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAEH,KAAK,gBAAgB,EAExB,MAAM,2CAA2C,CAAC;AAiBnD,OAAO,wBAAwB,CAAC;AAEhC,MAAM,WAAW,qBAAqB;IAClC,GAAG,EAAE,gBAAgB,CAAC;IACtB,IAAI,EAAE,gCAAgC,EAAE,CAAC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,gBAAgB,CAAC;CACpC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,EAAE,qBAAqB,2CA8Q7F"}
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // (C) 2026 GoodData Corporation
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { createRoot } from "react-dom/client";
@@ -7,6 +7,7 @@ import { now } from "../debug.js";
7
7
  import { setActiveHostHandle } from "../lib/hostNotifications.js";
8
8
  import { getAppLifecycleCallbacks } from "../loader/pluggableApplicationsLoader.js";
9
9
  import { getActiveInternalApplication } from "../loader/routing.js";
10
+ import { HostChat, } from "../ui/HostChat.js";
10
11
  import { HostIntlProvider } from "../ui/HostIntlProvider.js";
11
12
  import { PluggableApplicationRenderer } from "../ui/PluggableApplicationRenderer.js";
12
13
  import { resolveHostUiModule } from "../ui/resolveHostUiModule.js";
@@ -25,9 +26,35 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
25
26
  const latestMountStateRef = useAutoupdateRef({ ctx, apps, pathname });
26
27
  const [headerOptions, setHeaderOptions] = useState(undefined);
27
28
  const [pageTitle, setPageTitle] = useState(undefined);
28
- // Host-owned chat open-state, reported by the host UI and forwarded to the active pluggable app.
29
+ // Host-owned chat open-state, sourced from HostChat and forwarded to the active pluggable app.
29
30
  const [aiAssistantOpen, setAiAssistantOpen] = useState(false);
30
- const onAiAssistantOpenChange = useCallback((open) => setAiAssistantOpen(open), []);
31
+ // Header chat-button state (visibility + open) pushed down to the host UI module so it can render
32
+ // the header button to match the host-owned chat.
33
+ const [chatButtonState, setChatButtonState] = useState({ showChatItem: false, isOpen: false });
34
+ const onChatStateChange = useCallback((state) => {
35
+ setChatButtonState((prev) => prev.showChatItem === state.showChatItem && prev.isOpen === state.isOpen ? prev : state);
36
+ }, []);
37
+ // Open/close/toggle requests and tag scope driving the host's single chat (HostChat). Sources:
38
+ // app events (via PluggableApplicationRenderer), the header chat button, and the header search.
39
+ const [aiVisibility, setAiVisibility] = useState(null);
40
+ const [aiContext, setAiContext] = useState(null);
41
+ const aiVisibilitySeqRef = useRef(0);
42
+ const requestOpenAi = useCallback((question, userContext) => {
43
+ setAiVisibility({ kind: "open", question, userContext, seq: ++aiVisibilitySeqRef.current });
44
+ }, []);
45
+ const requestCloseAi = useCallback(() => {
46
+ setAiVisibility({ kind: "close", seq: ++aiVisibilitySeqRef.current });
47
+ }, []);
48
+ const requestToggleAi = useCallback(() => {
49
+ setAiVisibility({ kind: "toggle", seq: ++aiVisibilitySeqRef.current });
50
+ }, []);
51
+ const setAiAssistantContext = useCallback((context) => {
52
+ setAiContext(context);
53
+ }, []);
54
+ // The active app's chat-link handler, populated by PluggableApplicationRenderer (which holds the
55
+ // app mount handle) and read by HostChat's chat so embedded apps can handle link clicks in-app.
56
+ const appAiLinkClickRef = useRef(undefined);
57
+ const onAppLinkClick = useCallback((link) => appAiLinkClickRef.current?.(link) ?? false, []);
31
58
  const activeAppRef = useAutoupdateRef(activeInternalApplication);
32
59
  const onHeaderChange = useCallback((appId, header) => {
33
60
  if (activeAppRef.current?.id === appId) {
@@ -47,7 +74,12 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
47
74
  const replace = useCallback((url) => {
48
75
  void navigateRef.current(url, { replace: true });
49
76
  }, [navigateRef]);
50
- const navigationMountRef = useAutoupdateRef({ navigate, replace, onAiAssistantOpenChange });
77
+ const navigationMountRef = useAutoupdateRef({
78
+ navigate,
79
+ replace,
80
+ onChatToggleRequested: requestToggleAi,
81
+ onAskAiAssistant: requestOpenAi,
82
+ });
51
83
  // Mount the host UI once; obtain the app container for rendering active apps
52
84
  useEffect(() => {
53
85
  const container = containerRef.current;
@@ -68,7 +100,8 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
68
100
  pathname: latestState.pathname,
69
101
  navigate: navigationMountRef.current.navigate,
70
102
  replace: navigationMountRef.current.replace,
71
- onAiAssistantOpenChange: navigationMountRef.current.onAiAssistantOpenChange,
103
+ onChatToggleRequested: navigationMountRef.current.onChatToggleRequested,
104
+ onAskAiAssistant: navigationMountRef.current.onAskAiAssistant,
72
105
  });
73
106
  handleRef.current = handle;
74
107
  appRootRef.current = createRoot(handle.getAppContainer());
@@ -127,6 +160,13 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
127
160
  }
128
161
  handleRef.current?.updateDocumentTitle?.(pageTitle);
129
162
  }, [hostReady, pageTitle]);
163
+ // Push the host-owned chat button state (visibility + open) to the host UI whenever it changes
164
+ useEffect(() => {
165
+ if (!hostReady) {
166
+ return;
167
+ }
168
+ handleRef.current?.updateChatState?.(chatButtonState);
169
+ }, [hostReady, chatButtonState]);
130
170
  // Track app navigation and page views when the active application changes.
131
171
  // Also clear header options on app switch so stale customizations don't leak.
132
172
  const prevAppIdRef = useRef(undefined);
@@ -141,6 +181,9 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
141
181
  prevAppIdRef.current = activeId;
142
182
  setHeaderOptions(undefined);
143
183
  setPageTitle(undefined);
184
+ // Clear the previous app's AI context (tag scope + embedded presentation/placement) so it
185
+ // does not leak into the next app, which may never emit its own aiAssistantContextChanged.
186
+ setAiContext(null);
144
187
  }
145
188
  }, [activeInternalApplication, pathname]);
146
189
  // Render the active pluggable application into the host UI's app container
@@ -149,7 +192,7 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
149
192
  return;
150
193
  }
151
194
  if (activeInternalApplication) {
152
- appRootRef.current.render(_jsx(HostIntlProvider, { locale: resolveLocale(ctx.preferredLocale), children: _jsx(PluggableApplicationRenderer, { app: activeInternalApplication, ctx: ctx, pathname: pathname, aiAssistantOpen: aiAssistantOpen, onHeaderChange: onHeaderChange, onDocumentTitleChange: onDocumentTitleChange }, activeInternalApplication.id) }));
195
+ appRootRef.current.render(_jsx(HostIntlProvider, { locale: resolveLocale(ctx.preferredLocale), children: _jsx(PluggableApplicationRenderer, { app: activeInternalApplication, ctx: ctx, pathname: pathname, aiAssistantOpen: aiAssistantOpen, onOpenAiAssistant: requestOpenAi, onCloseAiAssistant: requestCloseAi, onAiAssistantContext: setAiAssistantContext, aiLinkClickHandlerRef: appAiLinkClickRef, onHeaderChange: onHeaderChange, onDocumentTitleChange: onDocumentTitleChange }, activeInternalApplication.id) }));
153
196
  }
154
197
  else {
155
198
  appRootRef.current.render(null);
@@ -162,6 +205,10 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
162
205
  onDocumentTitleChange,
163
206
  pathname,
164
207
  aiAssistantOpen,
208
+ requestOpenAi,
209
+ requestCloseAi,
210
+ setAiAssistantContext,
165
211
  ]);
166
- return _jsx("div", { ref: containerRef, className: "gd-host-ui-container" });
212
+ return (_jsxs(_Fragment, { children: [
213
+ _jsx("div", { ref: containerRef, className: "gd-host-ui-container" }), hostReady && !ctx.isExportMode ? (_jsx(HostChat, { ctx: ctx, resolvedApplications: apps, pathname: pathname, activeAppId: activeInternalApplication?.id, visibility: aiVisibility, context: aiContext, onOpenChange: setAiAssistantOpen, onChatStateChange: onChatStateChange, onAppLinkClick: onAppLinkClick })) : null] }));
167
214
  }
package/esm/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export { type ILoadPlatformContextCallbacks } from "./platformContext/loadPlatfo
4
4
  export { registerLocalApplicationLoaders, type LocalPluggableApplicationLoader, } from "./loader/localLoader.js";
5
5
  export { registerLocalApplications } from "./registry/pluggableApplicationsRegistry.js";
6
6
  export { registerAppLifecycleCallbacks } from "./loader/pluggableApplicationsLoader.js";
7
+ export { mapLegacyUrlToHost, type ILegacyLocation } from "./loader/legacyRedirect.js";
7
8
  export { Root, type IRootCallbacks } from "./components/Root.js";
8
9
  export { type IAppLifecycleCallbacks } from "./types/lifecycle.js";
9
10
  export { STALE_CHUNK_RELOAD_PARAM, installPreloadErrorHandler, installVersionWatcher, reloadForStaleChunks, setStaleChunkReloadListener, type IStaleChunkReloadInfo, type IVersionWatcherOptions, type StaleChunkReloadListener, } from "./lib/chunkReloadGuard.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACjF,OAAO,EAAE,gCAAgC,EAAE,MAAM,6CAA6C,CAAC;AAC/F,OAAO,EAAE,KAAK,6BAA6B,EAAE,MAAM,0CAA0C,CAAC;AAE9F,OAAO,EACH,+BAA+B,EAC/B,KAAK,+BAA+B,GACvC,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,yBAAyB,EAAE,MAAM,6CAA6C,CAAC;AACxF,OAAO,EAAE,6BAA6B,EAAE,MAAM,yCAAyC,CAAC;AAExF,OAAO,EAAE,IAAI,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,OAAO,EACH,wBAAwB,EACxB,0BAA0B,EAC1B,qBAAqB,EACrB,oBAAoB,EACpB,2BAA2B,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,GAChC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,OAAO,EACH,uBAAuB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,GAC1B,MAAM,8BAA8B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACjF,OAAO,EAAE,gCAAgC,EAAE,MAAM,6CAA6C,CAAC;AAC/F,OAAO,EAAE,KAAK,6BAA6B,EAAE,MAAM,0CAA0C,CAAC;AAE9F,OAAO,EACH,+BAA+B,EAC/B,KAAK,+BAA+B,GACvC,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,yBAAyB,EAAE,MAAM,6CAA6C,CAAC;AACxF,OAAO,EAAE,6BAA6B,EAAE,MAAM,yCAAyC,CAAC;AAExF,OAAO,EAAE,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAEtF,OAAO,EAAE,IAAI,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,OAAO,EACH,wBAAwB,EACxB,0BAA0B,EAC1B,qBAAqB,EACrB,oBAAoB,EACpB,2BAA2B,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,GAChC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,OAAO,EACH,uBAAuB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,GAC1B,MAAM,8BAA8B,CAAC"}
package/esm/index.js CHANGED
@@ -4,6 +4,7 @@ export { registerPlatformContextCallbacks } from "./platformContext/useLoadPlatf
4
4
  export { registerLocalApplicationLoaders, } from "./loader/localLoader.js";
5
5
  export { registerLocalApplications } from "./registry/pluggableApplicationsRegistry.js";
6
6
  export { registerAppLifecycleCallbacks } from "./loader/pluggableApplicationsLoader.js";
7
+ export { mapLegacyUrlToHost } from "./loader/legacyRedirect.js";
7
8
  export { Root } from "./components/Root.js";
8
9
  export { STALE_CHUNK_RELOAD_PARAM, installPreloadErrorHandler, installVersionWatcher, reloadForStaleChunks, setStaleChunkReloadListener, } from "./lib/chunkReloadGuard.js";
9
10
  export { dispatchHostNotification } from "./lib/hostNotifications.js";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @alpha
3
+ */
4
+ export interface ILegacyLocation {
5
+ pathname: string;
6
+ hash: string;
7
+ search: string;
8
+ }
9
+ /**
10
+ * Maps a legacy KD/AD/modeler/metrics URL (embedded or standalone) to its host equivalent, or
11
+ * `null` when it is not a recognized legacy URL. Mirrors the standalone→host redirects that live
12
+ * in the legacy apps; runs client-side because the workspace id is in the (server-invisible) hash.
13
+ *
14
+ * @alpha
15
+ */
16
+ export declare function mapLegacyUrlToHost(location: ILegacyLocation): string | null;
17
+ //# sourceMappingURL=legacyRedirect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacyRedirect.d.ts","sourceRoot":"","sources":["../../src/loader/legacyRedirect.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAClB;AAoBD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CA2C3E"}
@@ -0,0 +1,51 @@
1
+ // (C) 2026 GoodData Corporation
2
+ function isUnder(pathname, prefix) {
3
+ return pathname === prefix || pathname.startsWith(`${prefix}/`);
4
+ }
5
+ // [/embedded]/workspace/<ws>/<app>/#/<rest> — KD and AD keep their route in the hash; the only
6
+ // embedded difference is the /embedded path prefix.
7
+ function toHashHostUrl(embedded, app, workspaceId, remainder, search) {
8
+ const prefix = embedded ? "/embedded" : "";
9
+ const hash = remainder ? `#${remainder}` : "";
10
+ return `${prefix}/workspace/${workspaceId}/${app}/${search}${hash}`;
11
+ }
12
+ /**
13
+ * Maps a legacy KD/AD/modeler/metrics URL (embedded or standalone) to its host equivalent, or
14
+ * `null` when it is not a recognized legacy URL. Mirrors the standalone→host redirects that live
15
+ * in the legacy apps; runs client-side because the workspace id is in the (server-invisible) hash.
16
+ *
17
+ * @alpha
18
+ */
19
+ export function mapLegacyUrlToHost(location) {
20
+ const { pathname, hash, search } = location;
21
+ // KD legacy hash: #/workspace|project|client/<ws>/<rest>. Lift <ws> to the path, keep <rest>.
22
+ if (isUnder(pathname, "/dashboards")) {
23
+ const match = /^#\/(?:workspace|project|client)\/([^/?]+)(.*)$/.exec(hash);
24
+ return match
25
+ ? toHashHostUrl(isUnder(pathname, "/dashboards/embedded"), "dashboards", match[1], match[2], search)
26
+ : null;
27
+ }
28
+ // AD legacy hash: #/<ws>/<rest> — workspace is the bare first segment.
29
+ if (isUnder(pathname, "/analyze")) {
30
+ const match = /^#\/([^/?]+)(.*)$/.exec(hash);
31
+ return match
32
+ ? toHashHostUrl(isUnder(pathname, "/analyze/embedded"), "analyze", match[1], match[2], search)
33
+ : null;
34
+ }
35
+ // Metric editor (standalone only): hash #/<ws>[/rest] becomes the path /workspace/<ws>/metrics[/rest].
36
+ if (isUnder(pathname, "/metrics")) {
37
+ const match = /^#\/([^/?]+)(.*)$/.exec(hash);
38
+ return match ? `/workspace/${match[1]}/metrics${match[2]}` : null;
39
+ }
40
+ // LDM modeler (standalone only): #/<ws>[/...] → /workspace/<ws>/modeler. Deep routes have no host
41
+ // equivalent yet; edit-mode intent is preserved as ?displayEditMode.
42
+ if (isUnder(pathname, "/modeler")) {
43
+ const match = /^#\/([^/?]+)/.exec(hash);
44
+ if (!match) {
45
+ return null;
46
+ }
47
+ const editMode = /displayEditMode/.test(hash + search) ? "?displayEditMode" : "";
48
+ return `/workspace/${match[1]}/modeler${editMode}`;
49
+ }
50
+ return null;
51
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"pluggableApplicationsRegistry.d.ts","sourceRoot":"","sources":["../../src/registry/pluggableApplicationsRegistry.tsx"],"names":[],"mappings":"AAIA,OAAO,EACH,KAAK,gBAAgB,EAKrB,KAAK,kCAAkC,EACvC,KAAK,gCAAgC,EACrC,KAAK,mCAAmC,EAE3C,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEH,KAAK,gBAAgB,EACxB,MAAM,2CAA2C,CAAC;AAMnD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,kCAAkC,GAAG,IAAI,CAE5F;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,gBAAgB,GAAG,mCAAmC,GAAG,SAAS,CAaxG;AAyMD,UAAU,2BAA2B;IACjC;;OAEG;IACH,SAAS,EAAE,gCAAgC,EAAE,CAAC;IAC9C;;OAEG;IACH,cAAc,EAAE,mCAAmC,GAAG,SAAS,CAAC;IAChE;;OAEG;IACH,GAAG,EAAE,gBAAgB,CAAC;IACtB;;OAEG;IACH,KAAK,EAAE,gBAAgB,GAAG,SAAS,CAAC;CACvC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,EAChC,SAAS,EACT,cAAc,EACd,GAAG,EACH,KAAK,EACR,EAAE,2BAA2B,GAAG,gCAAgC,EAAE,CAelE;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,gBAAgB,GAAG,gCAAgC,EAAE,CAWlG"}
1
+ {"version":3,"file":"pluggableApplicationsRegistry.d.ts","sourceRoot":"","sources":["../../src/registry/pluggableApplicationsRegistry.tsx"],"names":[],"mappings":"AAIA,OAAO,EACH,KAAK,gBAAgB,EAKrB,KAAK,kCAAkC,EACvC,KAAK,gCAAgC,EACrC,KAAK,mCAAmC,EAE3C,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEH,KAAK,gBAAgB,EACxB,MAAM,2CAA2C,CAAC;AAMnD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,kCAAkC,GAAG,IAAI,CAE5F;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,gBAAgB,GAAG,mCAAmC,GAAG,SAAS,CAaxG;AAyND,UAAU,2BAA2B;IACjC;;OAEG;IACH,SAAS,EAAE,gCAAgC,EAAE,CAAC;IAC9C;;OAEG;IACH,cAAc,EAAE,mCAAmC,GAAG,SAAS,CAAC;IAChE;;OAEG;IACH,GAAG,EAAE,gBAAgB,CAAC;IACtB;;OAEG;IACH,KAAK,EAAE,gBAAgB,GAAG,SAAS,CAAC;CACvC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,EAChC,SAAS,EACT,cAAc,EACd,GAAG,EACH,KAAK,EACR,EAAE,2BAA2B,GAAG,gCAAgC,EAAE,CAelE;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,gBAAgB,GAAG,gCAAgC,EAAE,CAWlG"}
@@ -120,8 +120,21 @@ function toPluggableApplicationOrganizationPermissions(permissions) {
120
120
  hasBaseUiAccess: permissions.hasBaseUiAccess ?? false,
121
121
  };
122
122
  }
123
+ // Embedded mode forces the shell flags on so each remote becomes eligible and its legacy-external
124
+ // twin (which requires the flag false) drops out — exactly one survives. Non-embedded is untouched.
125
+ function settingsForRequirements(ctx) {
126
+ if (ctx.embeddingMode !== "iframe") {
127
+ return ctx.settings;
128
+ }
129
+ return {
130
+ ...ctx.settings,
131
+ enableShellApplication_dashboards: true,
132
+ enableShellApplication_analyticalDesigner: true,
133
+ };
134
+ }
123
135
  function appMeetsRequirements({ requiredSettings, requiredWorkspacePermissions, requiredOrganizationPermissions, requiredEntitlements, }, ctx) {
124
- if (requiredSettings !== undefined && !evaluateCondition(requiredSettings, ctx.settings)) {
136
+ if (requiredSettings !== undefined &&
137
+ !evaluateCondition(requiredSettings, settingsForRequirements(ctx))) {
125
138
  return false;
126
139
  }
127
140
  if (requiredWorkspacePermissions !== undefined) {
@@ -60,6 +60,15 @@ export declare interface IHostChromePricing {
60
60
  onUpsellButtonClick: () => void;
61
61
  }
62
62
 
63
+ /**
64
+ * @alpha
65
+ */
66
+ export declare interface ILegacyLocation {
67
+ pathname: string;
68
+ hash: string;
69
+ search: string;
70
+ }
71
+
63
72
  /**
64
73
  * @alpha
65
74
  */
@@ -134,6 +143,15 @@ export declare type LocalPluggableApplicationLoader = () => Promise<{
134
143
  pluggableApp?: IPluggableApp;
135
144
  }>;
136
145
 
146
+ /**
147
+ * Maps a legacy KD/AD/modeler/metrics URL (embedded or standalone) to its host equivalent, or
148
+ * `null` when it is not a recognized legacy URL. Mirrors the standalone→host redirects that live
149
+ * in the legacy apps; runs client-side because the workspace id is in the (server-invisible) hash.
150
+ *
151
+ * @alpha
152
+ */
153
+ export declare function mapLegacyUrlToHost(location: ILegacyLocation): string | null;
154
+
137
155
  /**
138
156
  * Registers app lifecycle callbacks used by the loader (e.g. for preload telemetry).
139
157
  * Called by the host or harness at startup.
@@ -1,71 +1,38 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  // (C) 2026 GoodData Corporation
3
- import { useCallback, useLayoutEffect, useRef, useState } from "react";
3
+ import { useLayoutEffect, useRef, useState } from "react";
4
4
  import { flushSync } from "react-dom";
5
5
  import { createRoot } from "react-dom/client";
6
6
  import { HostChrome } from "./HostChrome.js";
7
7
  import { e } from "./hostChromeBem.js";
8
8
  import "./DefaultHostUi.scss";
9
- function HostUiBridge({ initialCtx, initialApps, initialPathname, navigate, replace, onAiAssistantOpenChange, onAppContainerReady, onReady, }) {
9
+ function HostUiBridge({ initialCtx, initialApps, initialPathname, navigate, replace, onChatToggleRequested, onAskAiAssistant, onAppContainerReady, onReady, }) {
10
10
  const [ctx, setCtx] = useState(initialCtx);
11
11
  const [apps, setApps] = useState(initialApps);
12
12
  const [pathname, setPathname] = useState(initialPathname);
13
13
  const [headerOptions, setHeaderOptions] = useState(undefined);
14
14
  const [notification, setNotification] = useState(null);
15
15
  const [pageTitle, setPageTitle] = useState(undefined);
16
- // AI-assistant signals are kept in their own state slots, separate from the single generic
17
- // `notification` slot. Open/close and context-change notifications can arrive in the same
18
- // synchronous tick; routing them to independent state ensures one does not overwrite the
19
- // other (a single shared slot would drop the earlier one — LX-2544).
20
- const [aiVisibility, setAiVisibility] = useState(null);
21
- const [aiContext, setAiContext] = useState(null);
22
- const aiVisibilitySeqRef = useRef(0);
16
+ // The host runtime owns the single chat instance (outside this UI module); it pushes the chat
17
+ // button state here so the header button reflects the chat's availability and open-state.
18
+ const [chatState, setChatState] = useState({ showChatItem: false, isOpen: false });
23
19
  const appContainerRef = useRef(null);
24
- const handleNotify = useCallback((notification) => {
25
- if (notification === null) {
26
- setNotification(null);
27
- return;
28
- }
29
- switch (notification.type) {
30
- case "openAiAssistant":
31
- setAiVisibility({
32
- kind: "open",
33
- question: notification.question,
34
- userContext: notification.userContext,
35
- // A fresh seq on every request re-triggers the host effect even when the
36
- // question is identical to the previous one (e.g. "Summarize" clicked again).
37
- seq: ++aiVisibilitySeqRef.current,
38
- });
39
- break;
40
- case "closeAiAssistant":
41
- setAiVisibility({ kind: "close", seq: ++aiVisibilitySeqRef.current });
42
- break;
43
- case "aiAssistantContext":
44
- setAiContext({
45
- includeTags: notification.includeTags,
46
- excludeTags: notification.excludeTags,
47
- });
48
- break;
49
- default:
50
- setNotification(notification);
51
- }
52
- }, []);
53
20
  // Layout effect runs in the same synchronous commit as flushSync.
54
21
  useLayoutEffect(() => {
55
- onReady(setCtx, setApps, setPathname, setHeaderOptions, handleNotify, setPageTitle);
22
+ onReady(setCtx, setApps, setPathname, setHeaderOptions, setNotification, setPageTitle, setChatState);
56
23
  if (appContainerRef.current) {
57
24
  onAppContainerReady(appContainerRef.current);
58
25
  }
59
26
  // Only call once on mount
60
27
  // eslint-disable-next-line react-hooks/exhaustive-deps
61
28
  }, []);
62
- return (_jsx(HostChrome, { ctx: ctx, resolvedApplications: apps, pathname: pathname, onNavigate: navigate, onReplace: replace, headerOptions: headerOptions, notification: notification, aiVisibility: aiVisibility, aiContext: aiContext, onAiAssistantOpenChange: onAiAssistantOpenChange, appPageTitle: pageTitle, children: _jsx("div", { ref: appContainerRef, className: e("app-container") }) }));
29
+ return (_jsx(HostChrome, { ctx: ctx, resolvedApplications: apps, pathname: pathname, onNavigate: navigate, onReplace: replace, headerOptions: headerOptions, notification: notification, showChatItem: chatState.showChatItem, chatIsOpen: chatState.isOpen, onChatToggle: onChatToggleRequested, onAskAiAssistant: onAskAiAssistant, appPageTitle: pageTitle, children: _jsx("div", { ref: appContainerRef, className: e("app-container") }) }));
63
30
  }
64
31
  // ---------------------------------------------------------------------------
65
32
  // Imperative mount function conforming to IHostUiModule
66
33
  // ---------------------------------------------------------------------------
67
34
  function mountDefaultHostUi(options) {
68
- const { container, ctx, resolvedApplications, pathname, navigate, replace, onAiAssistantOpenChange } = options;
35
+ const { container, ctx, resolvedApplications, pathname, navigate, replace, onChatToggleRequested, onAskAiAssistant, } = options;
69
36
  let reactRoot = createRoot(container);
70
37
  let appContainer = null;
71
38
  let updateCtxFn = null;
@@ -74,18 +41,20 @@ function mountDefaultHostUi(options) {
74
41
  let updateHeaderFn = null;
75
42
  let updateNotificationFn = null;
76
43
  let updateDocumentTitleFn = null;
44
+ let updateChatStateFn = null;
77
45
  // Use flushSync so that the DOM is ready synchronously after mount() returns,
78
46
  // making getAppContainer() safe to call immediately.
79
47
  flushSync(() => {
80
- reactRoot?.render(_jsx(HostUiBridge, { initialCtx: ctx, initialApps: resolvedApplications, initialPathname: pathname, navigate: navigate, replace: replace, onAiAssistantOpenChange: onAiAssistantOpenChange, onAppContainerReady: (el) => {
48
+ reactRoot?.render(_jsx(HostUiBridge, { initialCtx: ctx, initialApps: resolvedApplications, initialPathname: pathname, navigate: navigate, replace: replace, onChatToggleRequested: onChatToggleRequested, onAskAiAssistant: onAskAiAssistant, onAppContainerReady: (el) => {
81
49
  appContainer = el;
82
- }, onReady: (setCtx, setApps, setPathname, setHeaderOptions, setNotification, setPageTitle) => {
50
+ }, onReady: (setCtx, setApps, setPathname, setHeaderOptions, setNotification, setPageTitle, setChatState) => {
83
51
  updateCtxFn = setCtx;
84
52
  updateAppsFn = setApps;
85
53
  updatePathnameFn = setPathname;
86
54
  updateHeaderFn = setHeaderOptions;
87
55
  updateNotificationFn = setNotification;
88
56
  updateDocumentTitleFn = setPageTitle;
57
+ updateChatStateFn = setChatState;
89
58
  } }));
90
59
  });
91
60
  return {
@@ -100,6 +69,7 @@ function mountDefaultHostUi(options) {
100
69
  updateHeaderFn = null;
101
70
  updateNotificationFn = null;
102
71
  updateDocumentTitleFn = null;
72
+ updateChatStateFn = null;
103
73
  root.unmount();
104
74
  }
105
75
  },
@@ -118,6 +88,9 @@ function mountDefaultHostUi(options) {
118
88
  updateDocumentTitle(pageTitle) {
119
89
  updateDocumentTitleFn?.(pageTitle);
120
90
  },
91
+ updateChatState(state) {
92
+ updateChatStateFn?.(state);
93
+ },
121
94
  getAppContainer() {
122
95
  if (!appContainer) {
123
96
  throw new Error("Host UI app container is not available. " +
@@ -58,7 +58,24 @@ export interface IGenAIChatProps {
58
58
  canAnalyzeProject?: boolean;
59
59
  canFullControl?: boolean;
60
60
  settings?: IUserWorkspaceSettings;
61
+ /** Where to place the chat (e.g. an embedded dashboard's left/right `showassistant` param). */
62
+ dialogPosition?: "left" | "right";
63
+ /** Whether the active app is embedded; switches the chat to the embedded presentation. */
64
+ embedded?: boolean;
65
+ /** Delegates a chat link click to the active app; returns true if the app handled it. */
66
+ onAppLinkClick?: (link: {
67
+ type?: string;
68
+ id?: string;
69
+ itemUrl?: string;
70
+ newTab?: boolean;
71
+ }) => boolean;
61
72
  onEvent?: (event: GenAIChatEvent) => void;
62
73
  }
63
- export declare function GenAIChat({ workspaceId, open, onOpen, onClose, askedQuestion, askSeq, userContext, includeTags, excludeTags, canManageProject, canAnalyzeProject, canFullControl, settings, onEvent }: IGenAIChatProps): import("react/jsx-runtime").JSX.Element;
74
+ /**
75
+ * Host chrome's chat adapter. The generic chat wiring (object types, the seeded-question dispatch flow,
76
+ * default link handling, event normalization) lives in `GenAIChatDialogConnected`
77
+ * (`@gooddata/sdk-ui-gen-ai/internal`); this adapter only supplies the host's data sources, renders the
78
+ * host's own save-visualization / copy toasts, and forwards telemetry events through `onEvent`.
79
+ */
80
+ export declare function GenAIChat({ workspaceId, open, onOpen, onClose, askedQuestion, askSeq, userContext, includeTags, excludeTags, canManageProject, canAnalyzeProject, canFullControl, settings, dialogPosition, embedded, onAppLinkClick, onEvent }: IGenAIChatProps): import("react/jsx-runtime").JSX.Element;
64
81
  //# sourceMappingURL=GenAIChat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GenAIChat.d.ts","sourceRoot":"","sources":["../../src/ui/GenAIChat.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,KAAK,EAAmB,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE9E,OAAO,EACH,KAAK,yBAAyB,EAC9B,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,cAAc,EAEnB,KAAK,oBAAoB,EAY5B,MAAM,yBAAyB,CAAC;AAYjC;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GACpB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,eAAe,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,eAAe,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,iBAAiB,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,OAAO,EAAE,oBAAoB,CAAA;CAAE,GAC5D;IAAE,IAAI,EAAE,wBAAwB,CAAC;IAAC,OAAO,EAAE,yBAAyB,CAAA;CAAE,CAAC;AAI7E,MAAM,WAAW,eAAe;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;CAC7C;AAED,wBAAgB,SAAS,CAAC,EACtB,WAAW,EACX,IAAI,EACJ,MAAM,EACN,OAAO,EACP,aAAa,EACb,MAAM,EACN,WAAW,EACX,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,QAAQ,EACR,OAAO,EACV,EAAE,eAAe,2CAoIjB"}
1
+ {"version":3,"file":"GenAIChat.d.ts","sourceRoot":"","sources":["../../src/ui/GenAIChat.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,OAAO,EACH,KAAK,yBAAyB,EAC9B,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EAE5B,MAAM,yBAAyB,CAAC;AAUjC;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GACpB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,eAAe,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,eAAe,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,iBAAiB,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,OAAO,EAAE,oBAAoB,CAAA;CAAE,GAC5D;IAAE,IAAI,EAAE,wBAAwB,CAAC;IAAC,OAAO,EAAE,yBAAyB,CAAA;CAAE,CAAC;AAE7E,MAAM,WAAW,eAAe;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,+FAA+F;IAC/F,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yFAAyF;IACzF,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC;IACvG,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;CAC7C;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,EACtB,WAAW,EACX,IAAI,EACJ,MAAM,EACN,OAAO,EACP,aAAa,EACb,MAAM,EACN,WAAW,EACX,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,QAAQ,EACR,cAAc,EACd,QAAQ,EACR,cAAc,EACd,OAAO,EACV,EAAE,eAAe,2CAiGjB"}
@@ -1,108 +1,80 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  // (C) 2026 GoodData Corporation
3
- import { useCallback, useEffect, useMemo, useState } from "react";
3
+ import { useCallback } from "react";
4
4
  import { defineMessage, useIntl } from "react-intl";
5
5
  import { useBackendStrict } from "@gooddata/sdk-ui";
6
- import { isChatAssistantMessageEvent, isChatClosedEvent, isChatCopyToClipboardEvent, isChatFeedbackEvent, isChatOpenedEvent, isChatResetEvent, isChatSaveVisualizationErrorEvent, isChatSaveVisualizationSuccessEvent, isChatUserMessageEvent, makeUserItem, } from "@gooddata/sdk-ui-gen-ai";
7
- import { GenAIChatDialog, clearThreadAction, makeTextContents, makeUserMessage, newMessageAction, setUserContextAction, } from "@gooddata/sdk-ui-gen-ai/internal";
6
+ import { GenAIChatDialogConnected } from "@gooddata/sdk-ui-gen-ai/internal";
8
7
  import { HEADER_CHAT_BUTTON_ID, useToastMessage } from "@gooddata/sdk-ui-kit";
9
- export function GenAIChat({ workspaceId, open, onOpen, onClose, askedQuestion, askSeq, userContext, includeTags, excludeTags, canManageProject, canAnalyzeProject, canFullControl, settings, onEvent, }) {
8
+ // DOM id of the embedded dashboard's AI trigger button (defined in gdc-dashboards-runtime as
9
+ // EMBED_AI_TRIGGER_ID). Used as the chat's focus-return target when running embedded, where the host
10
+ // header chat button does not exist. Kept as a literal to avoid a host→app package dependency.
11
+ const EMBEDDED_AI_TRIGGER_ID = "embed-ai-trigger";
12
+ const EMBEDDED_CHAT_CLASS = "gd-gen-ai-chat-embedded";
13
+ /**
14
+ * Host chrome's chat adapter. The generic chat wiring (object types, the seeded-question dispatch flow,
15
+ * default link handling, event normalization) lives in `GenAIChatDialogConnected`
16
+ * (`@gooddata/sdk-ui-gen-ai/internal`); this adapter only supplies the host's data sources, renders the
17
+ * host's own save-visualization / copy toasts, and forwards telemetry events through `onEvent`.
18
+ */
19
+ export function GenAIChat({ workspaceId, open, onOpen, onClose, askedQuestion, askSeq, userContext, includeTags, excludeTags, canManageProject, canAnalyzeProject, canFullControl, settings, dialogPosition, embedded, onAppLinkClick, onEvent, }) {
10
20
  const { addSuccess, addError } = useToastMessage();
11
21
  const intl = useIntl();
12
22
  const backend = useBackendStrict();
13
- const onLinkClick = useCallback(({ itemUrl, newTab, preventDefault }) => {
14
- if (itemUrl) {
15
- preventDefault();
16
- if (newTab) {
17
- window.open(itemUrl, "_blank");
18
- }
19
- else {
20
- window.location.assign(itemUrl);
21
- }
23
+ // Embedded link handling (this handler is only wired when `embedded`). Delegate to the active app —
24
+ // it opens visualization links as an in-place overlay — and never let a link navigate the embedded
25
+ // iframe away, matching the previous embedded dashboard chat which prevented default for every link
26
+ // type (only visualizations did anything). Non-embedded mode uses the connected wrapper's default
27
+ // open/navigate handler instead.
28
+ const onLinkClick = useCallback((link) => {
29
+ onAppLinkClick?.({
30
+ type: link.type,
31
+ id: link.id,
32
+ itemUrl: link.itemUrl,
33
+ newTab: link.newTab,
34
+ });
35
+ link.preventDefault();
36
+ return undefined;
37
+ }, [onAppLinkClick]);
38
+ const handleEvent = useCallback((event) => {
39
+ switch (event.name) {
40
+ case "opened":
41
+ onEvent?.({ name: "chat.opened", payload: event.payload });
42
+ break;
43
+ case "closed":
44
+ onEvent?.({ name: "chat.closed", payload: event.payload });
45
+ break;
46
+ case "reset":
47
+ onEvent?.({ name: "chat.reset", payload: event.payload });
48
+ break;
49
+ case "feedback":
50
+ onEvent?.({ name: "chat.feedback", payload: event.payload });
51
+ break;
52
+ case "user-message":
53
+ onEvent?.({ name: "chat.user-message", payload: event.payload });
54
+ break;
55
+ case "assistant-message":
56
+ onEvent?.({ name: "chat.assistant-message", payload: event.payload });
57
+ break;
58
+ case "save-visualization-success":
59
+ addSuccess(defineMessage({ id: "messages.genAi.visualisation.saved.success" }));
60
+ break;
61
+ case "save-visualization-error":
62
+ addError(defineMessage({ id: "messages.genAi.visualisation.saved.error" }), {
63
+ showMore: intl.formatMessage({ id: "messages.showMore" }),
64
+ showLess: intl.formatMessage({ id: "messages.showLess" }),
65
+ errorDetail: intl.formatMessage({ id: "messages.genAi.visualisation.saved.error.detail" }, {
66
+ errorType: event.payload.errorType,
67
+ errorMessage: event.payload.errorMessage,
68
+ }),
69
+ duration: 0,
70
+ });
71
+ break;
72
+ case "copy-to-clipboard":
73
+ addSuccess(defineMessage({ id: "messages.genAi.visualisation.link.copied" }));
74
+ break;
75
+ default:
76
+ break;
22
77
  }
23
- return itemUrl;
24
- }, []);
25
- const events = useMemo(() => {
26
- return [
27
- {
28
- eval: isChatOpenedEvent,
29
- handler: (data) => onEvent?.({ name: "chat.opened", payload: data }),
30
- },
31
- {
32
- eval: isChatClosedEvent,
33
- handler: (data) => onEvent?.({ name: "chat.closed", payload: data }),
34
- },
35
- {
36
- eval: isChatResetEvent,
37
- handler: (data) => onEvent?.({ name: "chat.reset", payload: data }),
38
- },
39
- {
40
- eval: isChatFeedbackEvent,
41
- handler: (data) => onEvent?.({ name: "chat.feedback", payload: data }),
42
- },
43
- {
44
- eval: isChatUserMessageEvent,
45
- handler: (data) => onEvent?.({ name: "chat.user-message", payload: data }),
46
- },
47
- {
48
- eval: isChatAssistantMessageEvent,
49
- handler: (data) => onEvent?.({ name: "chat.assistant-message", payload: data }),
50
- },
51
- {
52
- eval: isChatSaveVisualizationSuccessEvent,
53
- handler: () => {
54
- addSuccess(defineMessage({ id: "messages.genAi.visualisation.saved.success" }));
55
- },
56
- },
57
- {
58
- eval: isChatSaveVisualizationErrorEvent,
59
- handler: ({ errorType, errorMessage }) => {
60
- addError(defineMessage({ id: "messages.genAi.visualisation.saved.error" }), {
61
- showMore: intl.formatMessage({ id: "messages.showMore" }),
62
- showLess: intl.formatMessage({ id: "messages.showLess" }),
63
- errorDetail: intl.formatMessage({ id: "messages.genAi.visualisation.saved.error.detail" }, {
64
- errorType,
65
- errorMessage,
66
- }),
67
- duration: 0,
68
- });
69
- },
70
- },
71
- {
72
- eval: isChatCopyToClipboardEvent,
73
- handler: () => {
74
- addSuccess(defineMessage({ id: "messages.genAi.visualisation.link.copied" }));
75
- },
76
- },
77
- ];
78
78
  }, [addError, addSuccess, intl, onEvent]);
79
- const objectTypes = useMemo(() => {
80
- const types = ["dashboard", "visualization"];
81
- if (canManageProject) {
82
- types.push("metric");
83
- }
84
- return types;
85
- }, [canManageProject]);
86
- const [chatDispatcher, setDispatcher] = useState(null);
87
- const onDispatcher = useCallback((dispatcher) => {
88
- setDispatcher(() => dispatcher);
89
- }, []);
90
- useEffect(() => {
91
- if (!chatDispatcher || !askedQuestion || !settings) {
92
- return;
93
- }
94
- chatDispatcher(clearThreadAction());
95
- // Always set (and thereby clear when undefined) so a follow-up ask without context does not
96
- // inherit the previous ask's user context (LX-2544).
97
- chatDispatcher(setUserContextAction({ userContext }));
98
- if (settings.enableAiAgenticConversations) {
99
- chatDispatcher(newMessageAction(makeUserItem({ type: "text", text: askedQuestion })));
100
- }
101
- else {
102
- chatDispatcher(newMessageAction(makeUserMessage([makeTextContents(askedQuestion, [])])));
103
- }
104
- // `askSeq` is included so a repeated identical question (same `askedQuestion`) still re-seeds.
105
- // eslint-disable-next-line react-hooks/exhaustive-deps
106
- }, [chatDispatcher, askedQuestion, askSeq, userContext, settings]);
107
- return (_jsx(GenAIChatDialog, { isOpen: open, locale: intl.locale, canManage: canManageProject, canAnalyze: canAnalyzeProject, canFullControl: canFullControl, objectTypes: objectTypes, includeTags: includeTags, excludeTags: excludeTags, onLinkClick: onLinkClick, onClose: onClose, onOpen: onOpen, workspace: workspaceId, backend: backend, settings: settings, eventHandlers: events, onDispatcher: onDispatcher, returnFocusTo: HEADER_CHAT_BUTTON_ID }));
79
+ return (_jsx(GenAIChatDialogConnected, { backend: backend, workspace: workspaceId, locale: intl.locale, isOpen: open, onOpen: onOpen, onClose: onClose, askedQuestion: askedQuestion, askSeq: askSeq, userContext: userContext, includeTags: includeTags, excludeTags: excludeTags, canManage: canManageProject, canAnalyze: canAnalyzeProject, canFullControl: canFullControl, settings: settings, dialogPosition: dialogPosition, allowNativeLinks: embedded ? false : undefined, className: embedded ? EMBEDDED_CHAT_CLASS : undefined, onLinkClick: embedded ? onLinkClick : undefined, onEvent: handleEvent, returnFocusTo: embedded ? EMBEDDED_AI_TRIGGER_ID : HEADER_CHAT_BUTTON_ID }));
108
80
  }
@@ -0,0 +1,64 @@
1
+ import { type IGenAIUserContext, type PluggableApplicationRegistryItem } from "@gooddata/sdk-model";
2
+ import { type IPlatformContext } from "@gooddata/sdk-pluggable-application-model";
3
+ import "@gooddata/sdk-ui-gen-ai/styles/css/main.css";
4
+ /**
5
+ * AI-assistant open/close/toggle request. `seq` changes on every request so an identical repeat
6
+ * (e.g. "Summarize" clicked again, or the header button toggled) still re-triggers the effect.
7
+ */
8
+ export interface IHostChatVisibility {
9
+ kind: "open" | "close" | "toggle";
10
+ question?: string;
11
+ userContext?: IGenAIUserContext;
12
+ seq: number;
13
+ }
14
+ /** A link clicked inside the chat, delegated to the active application for in-app handling. */
15
+ export interface IHostChatLink {
16
+ type?: string;
17
+ id?: string;
18
+ itemUrl?: string;
19
+ newTab?: boolean;
20
+ }
21
+ /**
22
+ * AI-assistant presentation/context reported by the active pluggable application: its object-search
23
+ * tag scope plus, when embedded, how the host chat should be presented.
24
+ */
25
+ export interface IHostChatContext {
26
+ includeTags?: string[];
27
+ excludeTags?: string[];
28
+ dialogPosition?: "left" | "right";
29
+ embedded?: boolean;
30
+ }
31
+ export interface IHostChatProps {
32
+ ctx: IPlatformContext;
33
+ resolvedApplications: PluggableApplicationRegistryItem[];
34
+ pathname: string;
35
+ /** Id of the active pluggable application; used to reset tag scope on app switch. */
36
+ activeAppId?: string;
37
+ /** Latest open/close/toggle request (from app events, the header button, or header search). */
38
+ visibility?: IHostChatVisibility | null;
39
+ /** Latest tag scope / presentation reported by the active pluggable application. */
40
+ context?: IHostChatContext | null;
41
+ /** Reports the chat open-state so the runtime can forward it to the active app. */
42
+ onOpenChange?: (open: boolean) => void;
43
+ /** Reports the header chat-button state (visibility + open) so the host UI can render it. */
44
+ onChatStateChange?: (state: {
45
+ showChatItem: boolean;
46
+ isOpen: boolean;
47
+ }) => void;
48
+ /**
49
+ * Delegates a chat link click to the active application (e.g. an embedded dashboard opening a
50
+ * visualization as an in-place overlay). Returns true if the app handled it.
51
+ */
52
+ onAppLinkClick?: (link: IHostChatLink) => boolean;
53
+ }
54
+ /**
55
+ * Owns and renders the host's single GenAI chat, decoupled from the host UI module.
56
+ *
57
+ * @remarks
58
+ * Rendered by {@link HostUiContainer} in its own React tree (with the providers the chat needs), so
59
+ * the chat survives a custom remote UI module and stays mounted when the chrome is hidden
60
+ * (embedded/export). The header chat button and app-side controls drive it through the `visibility`
61
+ * and `context` props; it reports its open-state and button state back via callbacks.
62
+ */
63
+ export declare function HostChat({ ctx, resolvedApplications, pathname, activeAppId, visibility, context, onOpenChange, onChatStateChange, onAppLinkClick }: IHostChatProps): import("react/jsx-runtime").JSX.Element;
64
+ //# sourceMappingURL=HostChat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HostChat.d.ts","sourceRoot":"","sources":["../../src/ui/HostChat.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAUlF,OAAO,6CAA6C,CAAC;AAErD;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,GAAG,EAAE,MAAM,CAAC;CACf;AAED,+FAA+F;AAC/F,MAAM,WAAW,aAAa;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC7B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC3B,GAAG,EAAE,gBAAgB,CAAC;IACtB,oBAAoB,EAAE,gCAAgC,EAAE,CAAC;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+FAA+F;IAC/F,UAAU,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACxC,oFAAoF;IACpF,OAAO,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAClC,mFAAmF;IACnF,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,6FAA6F;IAC7F,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,YAAY,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IAChF;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC;CACrD;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,EACrB,GAAG,EACH,oBAAoB,EACpB,QAAQ,EACR,WAAW,EACX,UAAiB,EACjB,OAAc,EACd,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACjB,EAAE,cAAc,2CA6EhB"}
@@ -0,0 +1,75 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // (C) 2026 GoodData Corporation
3
+ import { useEffect, useMemo } from "react";
4
+ import { BackendProvider, resolveLocale } from "@gooddata/sdk-ui";
5
+ import { ToastsCenterContextProvider } from "@gooddata/sdk-ui-kit";
6
+ import { getAppLifecycleCallbacks } from "../loader/pluggableApplicationsLoader.js";
7
+ import { getBackend } from "../platformContext/backend.js";
8
+ import { HostIntlProvider } from "./HostIntlProvider.js";
9
+ import { useHostChromeChat } from "./useHostChromeChat.js";
10
+ import { useHostChromeWorkspaceFeatures } from "./useHostChromeWorkspaceFeatures.js";
11
+ import "@gooddata/sdk-ui-gen-ai/styles/css/main.css";
12
+ /**
13
+ * Owns and renders the host's single GenAI chat, decoupled from the host UI module.
14
+ *
15
+ * @remarks
16
+ * Rendered by {@link HostUiContainer} in its own React tree (with the providers the chat needs), so
17
+ * the chat survives a custom remote UI module and stays mounted when the chrome is hidden
18
+ * (embedded/export). The header chat button and app-side controls drive it through the `visibility`
19
+ * and `context` props; it reports its open-state and button state back via callbacks.
20
+ */
21
+ export function HostChat({ ctx, resolvedApplications, pathname, activeAppId, visibility = null, context = null, onOpenChange, onChatStateChange, onAppLinkClick, }) {
22
+ const features = useHostChromeWorkspaceFeatures(resolvedApplications, ctx, pathname);
23
+ const shellTelemetry = useMemo(() => getAppLifecycleCallbacks()?.createTelemetryCallbacks?.("host-ui"), []);
24
+ const chat = useHostChromeChat({
25
+ features,
26
+ ctx,
27
+ telemetry: shellTelemetry,
28
+ dialogPosition: context?.dialogPosition,
29
+ embedded: context?.embedded,
30
+ onAppLinkClick,
31
+ });
32
+ const { askAiAssistant: chatAskAiAssistant, open: chatOpen, close: chatClose, toggle: chatToggle, setTags: chatSetTags, isOpen: chatIsOpen, showChatItem, } = chat;
33
+ // Report the chat open-state outward so the runtime can forward it to the active pluggable
34
+ // application, keeping app-side assistant controls (and their echoed results) aligned (LX-2544).
35
+ useEffect(() => {
36
+ onOpenChange?.(chatIsOpen);
37
+ }, [chatIsOpen, onOpenChange]);
38
+ // Report the header chat-button state so the host UI module can render the button to match.
39
+ useEffect(() => {
40
+ onChatStateChange?.({ showChatItem, isOpen: chatIsOpen });
41
+ }, [showChatItem, chatIsOpen, onChatStateChange]);
42
+ // Effect order matters: the tag-scope effects are declared before the open/ask effect so that,
43
+ // when a tag-scope change and an open/ask request land in the same commit, the host chat is
44
+ // already scoped before the seeded question is sent.
45
+ // Clear any stale tag scope when the active application changes. The newly active app re-reports
46
+ // its own scope (or none) right after it mounts; without this reset, switching from a tag-scoped
47
+ // app to one that reports no scope (e.g. the metric editor) would leak the old scope.
48
+ useEffect(() => {
49
+ chatSetTags(undefined, undefined);
50
+ }, [activeAppId, chatSetTags]);
51
+ useEffect(() => {
52
+ chatSetTags(context?.includeTags, context?.excludeTags);
53
+ }, [context, chatSetTags]);
54
+ const visibilitySeq = visibility?.seq;
55
+ useEffect(() => {
56
+ if (!visibility) {
57
+ return;
58
+ }
59
+ if (visibility.kind === "close") {
60
+ chatClose();
61
+ }
62
+ else if (visibility.kind === "toggle") {
63
+ chatToggle();
64
+ }
65
+ else if (visibility.question) {
66
+ chatAskAiAssistant(visibility.question, visibility.userContext);
67
+ }
68
+ else {
69
+ chatOpen();
70
+ }
71
+ // `seq` changes on every request, so a repeated identical open/ask/toggle still re-runs this.
72
+ // eslint-disable-next-line react-hooks/exhaustive-deps
73
+ }, [visibilitySeq, chatAskAiAssistant, chatOpen, chatClose, chatToggle]);
74
+ return (_jsx(HostIntlProvider, { locale: resolveLocale(ctx.preferredLocale), children: _jsx(BackendProvider, { backend: getBackend(), children: _jsx(ToastsCenterContextProvider, { children: chat.element }) }) }));
75
+ }
@@ -6,23 +6,6 @@ import "@gooddata/sdk-ui-ext/styles/css/main.css";
6
6
  import "@gooddata/sdk-ui-gen-ai/styles/css/main.css";
7
7
  import "@gooddata/sdk-ui-semantic-search/styles/css/main.css";
8
8
  import "@gooddata/sdk-ui-semantic-search/styles/css/internal.css";
9
- /**
10
- * AI-assistant open/close request pushed from the active pluggable application. `seq` changes on
11
- * every request so an identical repeat (e.g. "Summarize" clicked again) still re-triggers the host.
12
- */
13
- export interface IHostChromeAiVisibility {
14
- kind: "open" | "close";
15
- question?: string;
16
- userContext?: IGenAIUserContext;
17
- seq: number;
18
- }
19
- /**
20
- * AI-assistant object-search tag scope reported by the active pluggable application.
21
- */
22
- export interface IHostChromeAiContext {
23
- includeTags?: string[];
24
- excludeTags?: string[];
25
- }
26
9
  export interface IHostChromeProps {
27
10
  ctx: IPlatformContext;
28
11
  resolvedApplications: PluggableApplicationRegistryItem[];
@@ -31,12 +14,17 @@ export interface IHostChromeProps {
31
14
  onReplace: (url: string) => void;
32
15
  headerOptions?: IAppHeaderOptions;
33
16
  notification?: IHostUiNotification | null;
34
- /** Latest AI-assistant open/close request from the active pluggable application. */
35
- aiVisibility?: IHostChromeAiVisibility | null;
36
- /** Latest AI-assistant tag scope reported by the active pluggable application. */
37
- aiContext?: IHostChromeAiContext | null;
38
- /** Reports the host chat open-state so the runtime can forward it to the active app. */
39
- onAiAssistantOpenChange?: (open: boolean) => void;
17
+ /**
18
+ * Whether the header chat button should be shown. Reflects the host-owned chat's availability
19
+ * (feature flag, permissions and the runtime LLM-availability probe), pushed in by the runtime.
20
+ */
21
+ showChatItem?: boolean;
22
+ /** Whether the host-owned chat is currently open (for the header button's active state). */
23
+ chatIsOpen?: boolean;
24
+ /** Toggles the host-owned chat; wired to the header chat button. */
25
+ onChatToggle?: () => void;
26
+ /** Hands a question to the host-owned chat; wired to the header search "ask AI" action. */
27
+ onAskAiAssistant?: (question: string, userContext?: IGenAIUserContext) => void;
40
28
  /**
41
29
  * Page-title segment set by the active pluggable application via a document-title-changed
42
30
  * event. When omitted, the active application's manifest title is used instead.
@@ -44,5 +32,5 @@ export interface IHostChromeProps {
44
32
  appPageTitle?: string;
45
33
  children?: ReactNode;
46
34
  }
47
- export declare function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, onReplace: _onReplace, headerOptions, notification, aiVisibility, aiContext, onAiAssistantOpenChange, appPageTitle, children }: IHostChromeProps): import("react/jsx-runtime").JSX.Element;
35
+ export declare function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, onReplace: _onReplace, headerOptions, notification, showChatItem, onChatToggle, onAskAiAssistant, appPageTitle, children }: IHostChromeProps): import("react/jsx-runtime").JSX.Element;
48
36
  //# sourceMappingURL=HostChrome.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"HostChrome.d.ts","sourceRoot":"","sources":["../../src/ui/HostChrome.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAuC,KAAK,SAAS,EAAmC,MAAM,OAAO,CAAC;AAE7G,OAAO,EAEH,KAAK,iBAAiB,EAEtB,KAAK,gCAAgC,EACxC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACxB,MAAM,2CAA2C,CAAC;AAgCnD,OAAO,mBAAmB,CAAC;AAK3B,OAAO,0CAA0C,CAAC;AAClD,OAAO,6CAA6C,CAAC;AACrD,OAAO,sDAAsD,CAAC;AAC9D,OAAO,0DAA0D,CAAC;AAYlE;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACpC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,GAAG,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACjC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,gBAAgB,CAAC;IACtB,oBAAoB,EAAE,gCAAgC,EAAE,CAAC;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,YAAY,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC1C,oFAAoF;IACpF,YAAY,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAC;IAC9C,kFAAkF;IAClF,SAAS,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACxC,wFAAwF;IACxF,uBAAuB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,wBAAgB,UAAU,CAAC,EACvB,GAAG,EACH,oBAAoB,EACpB,QAAQ,EACR,UAAU,EACV,SAAS,EAAE,UAAU,EACrB,aAAa,EACb,YAAmB,EACnB,YAAmB,EACnB,SAAgB,EAChB,uBAAuB,EACvB,YAAY,EACZ,QAAQ,EACX,EAAE,gBAAgB,2CA+QlB"}
1
+ {"version":3,"file":"HostChrome.d.ts","sourceRoot":"","sources":["../../src/ui/HostChrome.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAuC,KAAK,SAAS,EAAwB,MAAM,OAAO,CAAC;AAElG,OAAO,EAEH,KAAK,iBAAiB,EAEtB,KAAK,gCAAgC,EACxC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACxB,MAAM,2CAA2C,CAAC;AA+BnD,OAAO,mBAAmB,CAAC;AAK3B,OAAO,0CAA0C,CAAC;AAClD,OAAO,6CAA6C,CAAC;AACrD,OAAO,sDAAsD,CAAC;AAC9D,OAAO,0DAA0D,CAAC;AAYlE,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,gBAAgB,CAAC;IACtB,oBAAoB,EAAE,gCAAgC,EAAE,CAAC;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,YAAY,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC1C;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,2FAA2F;IAC3F,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC/E;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,wBAAgB,UAAU,CAAC,EACvB,GAAG,EACH,oBAAoB,EACpB,QAAQ,EACR,UAAU,EACV,SAAS,EAAE,UAAU,EACrB,aAAa,EACb,YAAmB,EACnB,YAAoB,EACpB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,QAAQ,EACX,EAAE,gBAAgB,2CAoOlB"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // (C) 2026 GoodData Corporation
3
- import { useCallback, useEffect, useMemo } from "react";
3
+ import { useCallback, useMemo } from "react";
4
4
  import { isExternalPluggableApplicationRegistryItem, } from "@gooddata/sdk-model";
5
5
  import { BackendProvider, resolveLocale } from "@gooddata/sdk-ui";
6
6
  import { AppHeaderNotifications } from "@gooddata/sdk-ui-application-header";
@@ -15,7 +15,6 @@ import { getUserDisplayName, swapWorkspaceInPath } from "./chromeHelpers.js";
15
15
  import { b, e } from "./hostChromeBem.js";
16
16
  import { HostIntlProvider } from "./HostIntlProvider.js";
17
17
  import { HostNotificationDispatcher } from "./HostNotificationDispatcher.js";
18
- import { useHostChromeChat } from "./useHostChromeChat.js";
19
18
  import { useHostChromePricing } from "./useHostChromePricing.js";
20
19
  import { useHostChromeSearch } from "./useHostChromeSearch.js";
21
20
  import { useHostChromeWorkspaceFeatures } from "./useHostChromeWorkspaceFeatures.js";
@@ -36,60 +35,23 @@ const LOGOUT_MENU_ITEM_KEY = "gs.header.logout";
36
35
  // favicon is configured (see faviconUrl below).
37
36
  const initialFaviconUrl = (typeof document !== "undefined" && document.querySelector("link[rel~='icon']")?.getAttribute("href")) ||
38
37
  "/favicon.ico";
39
- export function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, onReplace: _onReplace, headerOptions, notification = null, aiVisibility = null, aiContext = null, onAiAssistantOpenChange, appPageTitle, children, }) {
38
+ export function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, onReplace: _onReplace, headerOptions, notification = null, showChatItem = false, onChatToggle, onAskAiAssistant, appPageTitle, children, }) {
40
39
  const locale = resolveLocale(ctx.preferredLocale);
41
40
  const userName = getUserDisplayName(ctx.user);
42
41
  const features = useHostChromeWorkspaceFeatures(resolvedApplications, ctx, pathname);
43
42
  const shellTelemetry = useMemo(() => getAppLifecycleCallbacks()?.createTelemetryCallbacks?.("host-ui"), []);
44
43
  const pricing = useHostChromePricing(ctx, locale);
45
- const chat = useHostChromeChat({ features, ctx, telemetry: shellTelemetry });
46
- const { askAiAssistant: chatAskAiAssistant, open: chatOpen, close: chatClose, setTags: chatSetTags, isOpen: chatIsOpen, } = chat;
47
- // Report the host-owned chat open-state outward so the runtime can forward it to the active
48
- // pluggable application, keeping app-side assistant controls (and their echoed results) aligned
49
- // with the real state e.g. the embedded dashboard's toggleAIAssistant result (LX-2544).
50
- useEffect(() => {
51
- onAiAssistantOpenChange?.(chatIsOpen);
52
- }, [chatIsOpen, onAiAssistantOpenChange]);
53
- // The active pluggable application must not render its own chat dialog on hosted routes — the
54
- // chrome owns the single chat instance there (LX-2544). It drives that instance through these
55
- // signals instead: the object-search tag scope of its current view and open/close requests.
56
- //
57
- // Effect order matters: the tag-scope effects are declared before the open/ask effect so that,
58
- // when a tag-scope change and an open/ask request land in the same commit, the host chat is
59
- // already scoped before the seeded question is sent.
60
- // Clear any stale tag scope when the active application changes. The newly active app re-reports
61
- // its own scope (or none) right after it mounts; without this reset, switching from a
62
- // tag-scoped app to one that reports no scope (e.g. the metric editor) would leak the old scope.
63
- // Declared before the context effect so that, in the rare commit where both change, the fresh
64
- // scope wins over the reset.
65
- const activeAppId = getActiveInternalApplication(resolvedApplications, ctx, pathname)?.id;
66
- useEffect(() => {
67
- chatSetTags(undefined, undefined);
68
- }, [activeAppId, chatSetTags]);
69
- useEffect(() => {
70
- chatSetTags(aiContext?.includeTags, aiContext?.excludeTags);
71
- }, [aiContext, chatSetTags]);
72
- const aiVisibilitySeq = aiVisibility?.seq;
73
- useEffect(() => {
74
- if (!aiVisibility) {
75
- return;
76
- }
77
- if (aiVisibility.kind === "close") {
78
- chatClose();
79
- }
80
- else if (aiVisibility.question) {
81
- chatAskAiAssistant(aiVisibility.question, aiVisibility.userContext);
82
- }
83
- else {
84
- chatOpen();
85
- }
86
- // `seq` changes on every request, so a repeated identical open/ask still re-runs this.
87
- // eslint-disable-next-line react-hooks/exhaustive-deps
88
- }, [aiVisibilitySeq, chatAskAiAssistant, chatOpen, chatClose]);
44
+ // The host's single GenAI chat is owned by the runtime (HostChat), not the chrome — so it survives
45
+ // a custom UI module and stays mounted when the chrome is hidden (embedded/export). The chrome only
46
+ // renders the header chat button, which toggles that chat via `onChatToggle`, and hands the header
47
+ // search's questions to it via `onAskAiAssistant`.
48
+ const handleAskAiAssistant = useCallback((question, userContext) => {
49
+ onAskAiAssistant?.(question, userContext);
50
+ }, [onAskAiAssistant]);
89
51
  const search = useHostChromeSearch({
90
52
  features,
91
53
  isTrial: pricing.isTrial,
92
- onAskAiAssistant: chat.askAiAssistant,
54
+ onAskAiAssistant: handleAskAiAssistant,
93
55
  telemetry: shellTelemetry,
94
56
  });
95
57
  const { menuItemsGroups, messages: appMessages } = useMemo(() => buildAppMenu(resolvedApplications, ctx, pathname, ctx.preferredLocale), [resolvedApplications, ctx, pathname]);
@@ -182,9 +144,9 @@ export function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, on
182
144
  const hideChrome = ctx.embeddingMode === "iframe" || ctx.isExportMode === true;
183
145
  return (_jsx(HostIntlProvider, { locale: locale, additionalMessages: appMessages, children: _jsx(BackendProvider, { backend: getBackend(), children: _jsx(ToastsCenterContextProvider, { children: _jsxs("div", { className: b(), children: [
184
146
  _jsx(DocumentHeader, { pageTitle: documentPageTitle, brandTitle: documentBrandTitle, faviconUrl: faviconUrl, appleTouchIconUrl: ctx.whiteLabeling?.appleTouchIconUrl }), hideChrome ? null : (_jsx("div", { className: e("header"), onMouseOver: handleHeaderMouseOver, children: _jsx(AppHeader, { logoUrl: ctx.whiteLabeling?.logoUrl || defaultLogoUrl, logoHref: "/organization" // switch the host scope to organization, the first org app will be chosen
185
- , logoTitle: logoTitle, headerColor: headerColor, headerTextColor: headerTextColor, activeColor: activeColor, userName: userName, organizationName: ctx.organization?.title, isAccessibilityCompliant: true, workspacePicker: workspacePicker, menuItemsGroups: menuItemsGroups, helpMenuItems: helpMenuItems, accountMenuItems: accountMenuItems, onMenuItemClick: handleMenuItemClick, showUpsellButton: pricing.isTrial, onUpsellButtonClick: pricing.onUpsellButtonClick, expiredDate: pricing.isTrial ? pricing.expiredDate : undefined, search: search.element, showChatItem: chat.showChatItem, onChatItemClick: chat.toggle, notificationsPanel: ctx.userSettings.enableInPlatformNotifications
147
+ , logoTitle: logoTitle, headerColor: headerColor, headerTextColor: headerTextColor, activeColor: activeColor, userName: userName, organizationName: ctx.organization?.title, isAccessibilityCompliant: true, workspacePicker: workspacePicker, menuItemsGroups: menuItemsGroups, helpMenuItems: helpMenuItems, accountMenuItems: accountMenuItems, onMenuItemClick: handleMenuItemClick, showUpsellButton: pricing.isTrial, onUpsellButtonClick: pricing.onUpsellButtonClick, expiredDate: pricing.isTrial ? pricing.expiredDate : undefined, search: search.element, showChatItem: showChatItem, onChatItemClick: onChatToggle, notificationsPanel: ctx.userSettings.enableInPlatformNotifications
186
148
  ? ({ isMobile, closeNotificationsOverlay }) => (_jsx(AppHeaderNotifications, { locale: locale, isMobile: isMobile, closeNotificationsOverlay: closeNotificationsOverlay, useAsOfDateParam: ctx.userSettings.enableExecutionTimestamp ?? false, enableExportToDocumentStorage: ctx.userSettings.enableExportToDocumentStorage ??
187
149
  false }))
188
- : undefined }) })), _jsx("main", { className: e("content"), children: children }), hideChrome ? null : chat.element, pricing.element, _jsx(HostNotificationDispatcher, { notification: notification })
150
+ : undefined }) })), _jsx("main", { className: e("content"), children: children }), pricing.element, _jsx(HostNotificationDispatcher, { notification: notification })
189
151
  ] }) }) }) }));
190
152
  }
@@ -1,4 +1,5 @@
1
- import { type PluggableApplicationRegistryItem } from "@gooddata/sdk-model";
1
+ import { type RefObject } from "react";
2
+ import { type IGenAIUserContext, type PluggableApplicationRegistryItem } from "@gooddata/sdk-model";
2
3
  import { type IAppHeaderOptions, type IPlatformContext } from "@gooddata/sdk-pluggable-application-model";
3
4
  import "./PluggableApplicationRenderer.scss";
4
5
  export interface IPluggableApplicationRendererProps {
@@ -7,8 +8,29 @@ export interface IPluggableApplicationRendererProps {
7
8
  pathname: string;
8
9
  /** Host-owned AI assistant chat open-state, forwarded to the mounted app's handle. */
9
10
  aiAssistantOpen?: boolean;
11
+ /** Open/ask the host-owned chat, requested by the active app via an open-assistant event. */
12
+ onOpenAiAssistant?: (question?: string, userContext?: IGenAIUserContext) => void;
13
+ /** Close the host-owned chat, requested by the active app. */
14
+ onCloseAiAssistant?: () => void;
15
+ /** Report the active app's AI-assistant tag scope and presentation to the host-owned chat. */
16
+ onAiAssistantContext?: (context: {
17
+ includeTags?: string[];
18
+ excludeTags?: string[];
19
+ dialogPosition?: "left" | "right";
20
+ embedded?: boolean;
21
+ }) => void;
22
+ /**
23
+ * Ref the renderer populates with a handler that delegates a host-chat link click to the active
24
+ * app's mount handle (`onAiAssistantLinkClicked`), so an embedded app can handle it in-app.
25
+ */
26
+ aiLinkClickHandlerRef?: RefObject<((link: {
27
+ type?: string;
28
+ id?: string;
29
+ itemUrl?: string;
30
+ newTab?: boolean;
31
+ }) => boolean) | undefined>;
10
32
  onHeaderChange?: (appId: string, header: IAppHeaderOptions) => void;
11
33
  onDocumentTitleChange?: (appId: string, pageTitle: string | undefined) => void;
12
34
  }
13
- export declare function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOpen, onHeaderChange, onDocumentTitleChange }: IPluggableApplicationRendererProps): import("react/jsx-runtime").JSX.Element;
35
+ export declare function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOpen, onOpenAiAssistant, onCloseAiAssistant, onAiAssistantContext, aiLinkClickHandlerRef, onHeaderChange, onDocumentTitleChange }: IPluggableApplicationRendererProps): import("react/jsx-runtime").JSX.Element;
14
36
  //# sourceMappingURL=PluggableApplicationRenderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PluggableApplicationRenderer.d.ts","sourceRoot":"","sources":["../../src/ui/PluggableApplicationRenderer.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EASxB,MAAM,2CAA2C,CAAC;AAenD,OAAO,qCAAqC,CAAC;AAkB7C,MAAM,WAAW,kCAAkC;IAC/C,GAAG,EAAE,gCAAgC,CAAC;IACtC,GAAG,EAAE,gBAAgB,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACpE,qBAAqB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;CAClF;AAED,wBAAgB,4BAA4B,CAAC,EACzC,GAAG,EACH,GAAG,EACH,QAAQ,EACR,eAAe,EACf,cAAc,EACd,qBAAqB,EACxB,EAAE,kCAAkC,2CAsOpC"}
1
+ {"version":3,"file":"PluggableApplicationRenderer.d.ts","sourceRoot":"","sources":["../../src/ui/PluggableApplicationRenderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAqD,MAAM,OAAO,CAAC;AAI1F,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EASxB,MAAM,2CAA2C,CAAC;AAcnD,OAAO,qCAAqC,CAAC;AAkB7C,MAAM,WAAW,kCAAkC;IAC/C,GAAG,EAAE,gCAAgC,CAAC;IACtC,GAAG,EAAE,gBAAgB,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,6FAA6F;IAC7F,iBAAiB,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACjF,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,8FAA8F;IAC9F,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE;QAC7B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QACvB,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;KACtB,KAAK,IAAI,CAAC;IACX;;;OAGG;IACH,qBAAqB,CAAC,EAAE,SAAS,CAC7B,CAAC,CAAC,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,GAAG,SAAS,CACtG,CAAC;IACF,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACpE,qBAAqB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;CAClF;AAED,wBAAgB,4BAA4B,CAAC,EACzC,GAAG,EACH,GAAG,EACH,QAAQ,EACR,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACrB,cAAc,EACd,qBAAqB,EACxB,EAAE,kCAAkC,2CA0PpC"}
@@ -6,7 +6,6 @@ import { isAiAssistantContextChangedEvent, isCloseAiAssistantRequestedEvent, isD
6
6
  import { LoadingComponent, useAutoupdateRef } from "@gooddata/sdk-ui";
7
7
  import { bemFactory } from "@gooddata/sdk-ui-kit";
8
8
  import { now } from "../debug.js";
9
- import { dispatchHostNotification } from "../lib/hostNotifications.js";
10
9
  import { getSecuredRemoteAppValidUntil, validateAppSecurity, } from "../loader/appSecurityValidation.js";
11
10
  import { getAppLifecycleCallbacks, loadPluggableApplication } from "../loader/pluggableApplicationsLoader.js";
12
11
  import { getApplicationHref } from "../loader/routing.js";
@@ -21,12 +20,15 @@ const SECURITY_FAILURE_MESSAGES = {
21
20
  "build-expired": defineMessage({ id: "gs.host.error.appBuildExpired" }),
22
21
  "metadata-missing": defineMessage({ id: "gs.host.error.appSecurityMetadataMissing" }),
23
22
  };
24
- export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOpen, onHeaderChange, onDocumentTitleChange, }) {
23
+ export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOpen, onOpenAiAssistant, onCloseAiAssistant, onAiAssistantContext, aiLinkClickHandlerRef, onHeaderChange, onDocumentTitleChange, }) {
25
24
  const intl = useIntl();
26
25
  const intlRef = useAutoupdateRef(intl);
27
26
  const ctxRef = useAutoupdateRef(ctx);
28
27
  const onHeaderChangeRef = useAutoupdateRef(onHeaderChange);
29
28
  const onDocumentTitleChangeRef = useAutoupdateRef(onDocumentTitleChange);
29
+ const onOpenAiAssistantRef = useAutoupdateRef(onOpenAiAssistant);
30
+ const onCloseAiAssistantRef = useAutoupdateRef(onCloseAiAssistant);
31
+ const onAiAssistantContextRef = useAutoupdateRef(onAiAssistantContext);
30
32
  const containerRef = useRef(null);
31
33
  const mountHandleRef = useRef(undefined);
32
34
  // The app/module pair currently mounted. Held so the context-change effect can
@@ -47,32 +49,35 @@ export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOp
47
49
  return;
48
50
  }
49
51
  // The active pluggable application owns no chat dialog on hosted routes; it requests
50
- // the host's single assistant through these events (open/ask and tag-scope changes),
51
- // which the host chrome consumes via the notification channel.
52
+ // the host's single (host-owned) assistant through these events (open/ask, close and
53
+ // tag-scope changes), which the host runtime drives directly via these callbacks.
52
54
  if (isOpenAiAssistantRequestedEvent(event)) {
53
- dispatchHostNotification({
54
- type: "openAiAssistant",
55
- question: event.payload.question,
56
- userContext: event.payload.userContext,
57
- });
55
+ onOpenAiAssistantRef.current?.(event.payload.question, event.payload.userContext);
58
56
  return;
59
57
  }
60
58
  if (isCloseAiAssistantRequestedEvent(event)) {
61
- dispatchHostNotification({ type: "closeAiAssistant" });
59
+ onCloseAiAssistantRef.current?.();
62
60
  return;
63
61
  }
64
62
  if (isAiAssistantContextChangedEvent(event)) {
65
- dispatchHostNotification({
66
- type: "aiAssistantContext",
63
+ onAiAssistantContextRef.current?.({
67
64
  includeTags: event.payload.includeTags,
68
65
  excludeTags: event.payload.excludeTags,
66
+ dialogPosition: event.payload.dialogPosition,
67
+ embedded: event.payload.embedded,
69
68
  });
70
69
  return;
71
70
  }
72
71
  if (isDocumentTitleChangedEvent(event)) {
73
72
  onDocumentTitleChangeRef.current?.(app.id, event.payload.pageTitle);
74
73
  }
75
- }, [app.id, onDocumentTitleChangeRef]);
74
+ }, [
75
+ app.id,
76
+ onDocumentTitleChangeRef,
77
+ onOpenAiAssistantRef,
78
+ onCloseAiAssistantRef,
79
+ onAiAssistantContextRef,
80
+ ]);
76
81
  useEffect(() => {
77
82
  let cancelled = false;
78
83
  const mountId = `${app.id}:${Date.now()}`;
@@ -181,6 +186,19 @@ export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOp
181
186
  }
182
187
  mountHandleRef.current?.setAiAssistantOpen?.(aiAssistantOpen);
183
188
  }, [aiAssistantOpen, viewState.state]);
189
+ // Expose a host-chat link-click delegate that defers to the active app's mount handle, so an
190
+ // embedded app can handle clicks in-app (e.g. open a visualization overlay) rather than navigating.
191
+ // Read through the ref at call time so it always targets the currently mounted app.
192
+ useEffect(() => {
193
+ const ref = aiLinkClickHandlerRef;
194
+ if (!ref) {
195
+ return;
196
+ }
197
+ ref.current = (link) => mountHandleRef.current?.onAiAssistantLinkClicked?.(link) ?? false;
198
+ return () => {
199
+ ref.current = undefined;
200
+ };
201
+ }, [aiLinkClickHandlerRef]);
184
202
  return (_jsxs("section", { className: b(), children: [viewState.state === "loading" ? (_jsx("div", { className: e("loading"), children: _jsx(LoadingComponent, { height: 40 }) })) : null, viewState.state === "error" ? (_jsxs("div", { className: e("error"), children: [
185
203
  _jsx("h2", { children: _jsx(FormattedMessage, { id: "gs.host.error.applicationFailedToLoad" }) }), _jsx("p", { children: viewState.message })
186
204
  ] })) : null, _jsx("div", { ref: containerRef, className: e("container", { visible: viewState.state === "ready" }) }), viewState.state === "ready" && viewState.validUntil !== undefined ? (_jsx("div", { className: e("demoBadge"), role: "status", children: _jsx(FormattedMessage, { id: "gs.host.demoApp.validUntil", values: {
@@ -33,6 +33,17 @@ export interface IUseHostChromeChatArgs {
33
33
  features: IHostChromeWorkspaceFeatures;
34
34
  ctx: IPlatformContext;
35
35
  telemetry: IPluggableAppTelemetryCallbacks | undefined;
36
+ /** Where to place the chat (e.g. an embedded dashboard's left/right `showassistant` param). */
37
+ dialogPosition?: "left" | "right";
38
+ /** Whether the active app is embedded; switches the chat to the embedded presentation. */
39
+ embedded?: boolean;
40
+ /** Delegates a chat link click to the active app; returns true if the app handled it. */
41
+ onAppLinkClick?: (link: {
42
+ type?: string;
43
+ id?: string;
44
+ itemUrl?: string;
45
+ newTab?: boolean;
46
+ }) => boolean;
36
47
  }
37
48
  /**
38
49
  * Owns the GenAI chat state and renders the `<GenAIChat>` element for the host chrome.
@@ -41,5 +52,5 @@ export interface IUseHostChromeChatArgs {
41
52
  * points, the runtime availability probe (`useGenAiChatAvailability`) and forwarding chat
42
53
  * events to the host telemetry callbacks.
43
54
  */
44
- export declare function useHostChromeChat({ features, ctx, telemetry }: IUseHostChromeChatArgs): IHostChromeChat;
55
+ export declare function useHostChromeChat({ features, ctx, telemetry, dialogPosition, embedded, onAppLinkClick }: IUseHostChromeChatArgs): IHostChromeChat;
45
56
  //# sourceMappingURL=useHostChromeChat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useHostChromeChat.d.ts","sourceRoot":"","sources":["../../src/ui/useHostChromeChat.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAyB,MAAM,OAAO,CAAC;AAE9D,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EACH,KAAK,gBAAgB,EACrB,KAAK,+BAA+B,EACvC,MAAM,2CAA2C,CAAC;AAMnD,OAAO,EAAE,KAAK,4BAA4B,EAAE,MAAM,qCAAqC,CAAC;AAExF,MAAM,WAAW,eAAe;IAC5B,4EAA4E;IAC5E,OAAO,EAAE,SAAS,CAAC;IACnB,iDAAiD;IACjD,MAAM,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB,4BAA4B;IAC5B,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,6BAA6B;IAC7B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,6EAA2E;IAC3E,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB;;;OAGG;IACH,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC5E;;;OAGG;IACH,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CACrE;AAED,MAAM,WAAW,sBAAsB;IACnC,QAAQ,EAAE,4BAA4B,CAAC;IACvC,GAAG,EAAE,gBAAgB,CAAC;IACtB,SAAS,EAAE,+BAA+B,GAAG,SAAS,CAAC;CAC1D;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,sBAAsB,GAAG,eAAe,CAkFvG"}
1
+ {"version":3,"file":"useHostChromeChat.d.ts","sourceRoot":"","sources":["../../src/ui/useHostChromeChat.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAyB,MAAM,OAAO,CAAC;AAE9D,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EACH,KAAK,gBAAgB,EACrB,KAAK,+BAA+B,EACvC,MAAM,2CAA2C,CAAC;AAMnD,OAAO,EAAE,KAAK,4BAA4B,EAAE,MAAM,qCAAqC,CAAC;AAExF,MAAM,WAAW,eAAe;IAC5B,4EAA4E;IAC5E,OAAO,EAAE,SAAS,CAAC;IACnB,iDAAiD;IACjD,MAAM,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB,4BAA4B;IAC5B,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,6BAA6B;IAC7B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,6EAA2E;IAC3E,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB;;;OAGG;IACH,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC5E;;;OAGG;IACH,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CACrE;AAED,MAAM,WAAW,sBAAsB;IACnC,QAAQ,EAAE,4BAA4B,CAAC;IACvC,GAAG,EAAE,gBAAgB,CAAC;IACtB,SAAS,EAAE,+BAA+B,GAAG,SAAS,CAAC;IACvD,+FAA+F;IAC/F,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yFAAyF;IACzF,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC;CAC1G;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,EAC9B,QAAQ,EACR,GAAG,EACH,SAAS,EACT,cAAc,EACd,QAAQ,EACR,cAAc,EACjB,EAAE,sBAAsB,GAAG,eAAe,CAqF1C"}
@@ -11,7 +11,7 @@ import { GenAIChat } from "./GenAIChat.js";
11
11
  * points, the runtime availability probe (`useGenAiChatAvailability`) and forwarding chat
12
12
  * events to the host telemetry callbacks.
13
13
  */
14
- export function useHostChromeChat({ features, ctx, telemetry }) {
14
+ export function useHostChromeChat({ features, ctx, telemetry, dialogPosition, embedded, onAppLinkClick, }) {
15
15
  const [isChatOpen, setIsChatOpen] = useState(false);
16
16
  const [askedQuestion, setAskedQuestion] = useState(null);
17
17
  // Bumped on every ask so the chat re-seeds (clears the thread + sends the message) even when the
@@ -47,7 +47,7 @@ export function useHostChromeChat({ features, ctx, telemetry }) {
47
47
  telemetry?.trackEvent(event.name, event.payload);
48
48
  }, [telemetry]);
49
49
  const showChatItem = useGenAiChatAvailability(getBackend(), features.workspaceId, features.showChat, features.canManageProject);
50
- const element = features.showChat && features.workspaceId ? (_jsx(GenAIChat, { workspaceId: features.workspaceId, open: isChatOpen, onOpen: open, onClose: close, askedQuestion: askedQuestion, askSeq: askSeq, userContext: userContext, includeTags: includeTags, excludeTags: excludeTags, canManageProject: features.canManageProject, canAnalyzeProject: features.canAccessWorkbench, canFullControl: features.canFullControl, settings: ctx.workspaceSettings, onEvent: handleChatEvent })) : null;
50
+ const element = features.showChat && features.workspaceId ? (_jsx(GenAIChat, { workspaceId: features.workspaceId, open: isChatOpen, onOpen: open, onClose: close, askedQuestion: askedQuestion, askSeq: askSeq, userContext: userContext, includeTags: includeTags, excludeTags: excludeTags, canManageProject: features.canManageProject, canAnalyzeProject: features.canAccessWorkbench, canFullControl: features.canFullControl, settings: ctx.workspaceSettings, dialogPosition: dialogPosition, embedded: embedded, onAppLinkClick: onAppLinkClick, onEvent: handleChatEvent })) : null;
51
51
  return {
52
52
  element,
53
53
  isOpen: isChatOpen,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-ui-pluggable-host",
3
- "version": "11.45.0-alpha.0",
3
+ "version": "11.45.0-alpha.1",
4
4
  "description": "GoodData SDK runtime for hosting pluggable applications — registry, loader, routing, platform context, default UI chrome",
5
5
  "license": "MIT",
6
6
  "author": "GoodData Corporation",
@@ -29,20 +29,20 @@
29
29
  "dependencies": {
30
30
  "@module-federation/runtime": "2.3.1",
31
31
  "lodash-es": "^4.17.23",
32
- "@gooddata/sdk-backend-base": "11.45.0-alpha.0",
33
- "@gooddata/sdk-backend-spi": "11.45.0-alpha.0",
34
- "@gooddata/sdk-embedding": "11.45.0-alpha.0",
35
- "@gooddata/sdk-backend-tiger": "11.45.0-alpha.0",
36
- "@gooddata/sdk-model": "11.45.0-alpha.0",
37
- "@gooddata/sdk-pluggable-application-model": "11.45.0-alpha.0",
38
- "@gooddata/sdk-ui": "11.45.0-alpha.0",
39
- "@gooddata/sdk-ui-application-header": "11.45.0-alpha.0",
40
- "@gooddata/sdk-ui-ext": "11.45.0-alpha.0",
41
- "@gooddata/sdk-ui-gen-ai": "11.45.0-alpha.0",
42
- "@gooddata/sdk-ui-kit": "11.45.0-alpha.0",
43
- "@gooddata/sdk-ui-semantic-search": "11.45.0-alpha.0",
44
- "@gooddata/sdk-ui-theme-provider": "11.45.0-alpha.0",
45
- "@gooddata/util": "11.45.0-alpha.0"
32
+ "@gooddata/sdk-backend-base": "11.45.0-alpha.1",
33
+ "@gooddata/sdk-backend-spi": "11.45.0-alpha.1",
34
+ "@gooddata/sdk-embedding": "11.45.0-alpha.1",
35
+ "@gooddata/sdk-model": "11.45.0-alpha.1",
36
+ "@gooddata/sdk-backend-tiger": "11.45.0-alpha.1",
37
+ "@gooddata/sdk-pluggable-application-model": "11.45.0-alpha.1",
38
+ "@gooddata/sdk-ui-application-header": "11.45.0-alpha.1",
39
+ "@gooddata/sdk-ui": "11.45.0-alpha.1",
40
+ "@gooddata/sdk-ui-ext": "11.45.0-alpha.1",
41
+ "@gooddata/sdk-ui-gen-ai": "11.45.0-alpha.1",
42
+ "@gooddata/sdk-ui-kit": "11.45.0-alpha.1",
43
+ "@gooddata/sdk-ui-semantic-search": "11.45.0-alpha.1",
44
+ "@gooddata/sdk-ui-theme-provider": "11.45.0-alpha.1",
45
+ "@gooddata/util": "11.45.0-alpha.1"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@microsoft/api-documenter": "^7.17.0",
@@ -85,8 +85,8 @@
85
85
  "vite": "8.0.16",
86
86
  "vitest": "4.1.8",
87
87
  "vitest-dom": "0.1.1",
88
- "@gooddata/eslint-config": "11.45.0-alpha.0",
89
- "@gooddata/oxlint-config": "11.45.0-alpha.0"
88
+ "@gooddata/eslint-config": "11.45.0-alpha.1",
89
+ "@gooddata/oxlint-config": "11.45.0-alpha.1"
90
90
  },
91
91
  "peerDependencies": {
92
92
  "react": ">=18.3.1",