@gooddata/sdk-ui-pluggable-host 11.42.0-alpha.3 → 11.42.0-alpha.5

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 (43) hide show
  1. package/esm/components/HostUiContainer.d.ts.map +1 -1
  2. package/esm/components/HostUiContainer.js +15 -3
  3. package/esm/translations/de-DE.json +0 -13
  4. package/esm/translations/en-AU.json +0 -13
  5. package/esm/translations/en-GB.json +0 -13
  6. package/esm/translations/en-US.json +0 -52
  7. package/esm/translations/es-419.json +0 -13
  8. package/esm/translations/es-ES.json +0 -13
  9. package/esm/translations/fi-FI.json +0 -13
  10. package/esm/translations/fr-CA.json +0 -13
  11. package/esm/translations/fr-FR.json +0 -13
  12. package/esm/translations/id-ID.json +0 -13
  13. package/esm/translations/it-IT.json +0 -13
  14. package/esm/translations/ja-JP.json +0 -13
  15. package/esm/translations/ko-KR.json +0 -13
  16. package/esm/translations/nl-NL.json +0 -13
  17. package/esm/translations/pl-PL.json +0 -13
  18. package/esm/translations/pt-BR.json +0 -13
  19. package/esm/translations/pt-PT.json +0 -13
  20. package/esm/translations/ru-RU.json +0 -13
  21. package/esm/translations/sl-SI.json +0 -13
  22. package/esm/translations/th-TH.json +0 -13
  23. package/esm/translations/tr-TR.json +0 -13
  24. package/esm/translations/uk-UA.json +0 -13
  25. package/esm/translations/vi-VN.json +0 -13
  26. package/esm/translations/zh-HK.json +0 -13
  27. package/esm/translations/zh-Hans.json +0 -13
  28. package/esm/translations/zh-Hant.json +0 -13
  29. package/esm/ui/DefaultHostUi.d.ts.map +1 -1
  30. package/esm/ui/DefaultHostUi.js +42 -6
  31. package/esm/ui/GenAIChat.d.ts +21 -1
  32. package/esm/ui/GenAIChat.d.ts.map +1 -1
  33. package/esm/ui/GenAIChat.js +9 -4
  34. package/esm/ui/HostChrome.d.ts +25 -2
  35. package/esm/ui/HostChrome.d.ts.map +1 -1
  36. package/esm/ui/HostChrome.js +60 -9
  37. package/esm/ui/PluggableApplicationRenderer.d.ts +3 -1
  38. package/esm/ui/PluggableApplicationRenderer.d.ts.map +1 -1
  39. package/esm/ui/PluggableApplicationRenderer.js +34 -2
  40. package/esm/ui/useHostChromeChat.d.ts +17 -2
  41. package/esm/ui/useHostChromeChat.d.ts.map +1 -1
  42. package/esm/ui/useHostChromeChat.js +25 -2
  43. package/package.json +17 -17
@@ -24,19 +24,6 @@
24
24
  "gs.host.demoApp.validUntil": "示範應用程式有效期至 {date}",
25
25
  "gs.host.notification.newDeployment.message": "有新版本的 GoodData 可供使用。",
26
26
  "gs.host.notification.newDeployment.reloadLink": "重新載入",
27
- "gs.header.helpMenu.gettingStarted": "開始使用",
28
- "gs.header.helpMenu.connectData": "連接數據",
29
- "gs.header.helpMenu.manage.ws": "管理工作區和工作區層次結構",
30
- "gs.header.helpMenu.manage.user": "管理使用者和使用者群組",
31
- "gs.header.helpMenu.connecting": "連接數據",
32
- "gs.header.helpMenu.starting": "開始使用 LDM Modeler",
33
- "gs.header.helpMenu.modeling": "深入了解資料建模",
34
- "gs.header.helpMenu.understanding": "了解邏輯數據模型",
35
- "gs.header.helpMenu.learning": "學習資料建模原則",
36
- "gs.header.helpMenu.firstSteps": "分析設計器的第一步",
37
- "gs.header.helpMenu.visualizing": "可視化數據",
38
- "gs.header.helpMenu.sorting": "對結果進行排序和篩選",
39
- "gs.header.helpMenu.embedding": "把Analytical Designer嵌入到應用中",
40
27
  "messages.genAi.visualisation.saved.success": "很好!我哋儲存咗您嘅可視化效果。",
41
28
  "messages.genAi.visualisation.saved.error": "糟糕,保存可視化時出現問題。",
42
29
  "messages.genAi.visualisation.saved.error.detail": "{errorType}: {errorMessage}",
@@ -24,19 +24,6 @@
24
24
  "gs.host.demoApp.validUntil": "演示应用程序有效期至 {date}",
25
25
  "gs.host.notification.newDeployment.message": "GoodData 的新版本已可用。",
26
26
  "gs.host.notification.newDeployment.reloadLink": "重新加载",
27
- "gs.header.helpMenu.gettingStarted": "开始使用",
28
- "gs.header.helpMenu.connectData": "连接数据",
29
- "gs.header.helpMenu.manage.ws": "管理工作区和工作区层次结构",
30
- "gs.header.helpMenu.manage.user": "管理用户和用户组",
31
- "gs.header.helpMenu.connecting": "连接数据",
32
- "gs.header.helpMenu.starting": "开始使用 LDM Modeler",
33
- "gs.header.helpMenu.modeling": "深入了解数据建模",
34
- "gs.header.helpMenu.understanding": "了解逻辑数据模型",
35
- "gs.header.helpMenu.learning": "学习数据建模原理",
36
- "gs.header.helpMenu.firstSteps": "Analytical Designer 的使用入门",
37
- "gs.header.helpMenu.visualizing": "可视化您的数据",
38
- "gs.header.helpMenu.sorting": "对结果进行排序和筛选",
39
- "gs.header.helpMenu.embedding": "将 Analytical Designer 嵌入您的应用",
40
27
  "messages.genAi.visualisation.saved.success": "太棒了!我们已保存您的可视化。",
41
28
  "messages.genAi.visualisation.saved.error": "哎呀,保存可视化时出现问题。",
42
29
  "messages.genAi.visualisation.saved.error.detail": "{errorType}: {errorMessage}",
@@ -24,19 +24,6 @@
24
24
  "gs.host.demoApp.validUntil": "示範應用程式有效至 {date}",
25
25
  "gs.host.notification.newDeployment.message": "有新版本的 GoodData 可供使用。",
26
26
  "gs.host.notification.newDeployment.reloadLink": "重新載入",
27
- "gs.header.helpMenu.gettingStarted": "開始使用",
28
- "gs.header.helpMenu.connectData": "連接資料",
29
- "gs.header.helpMenu.manage.ws": "管理工作區與工作區階層",
30
- "gs.header.helpMenu.manage.user": "管理使用者與使用者群組",
31
- "gs.header.helpMenu.connecting": "連接資料",
32
- "gs.header.helpMenu.starting": "開始使用 LDM Modeler",
33
- "gs.header.helpMenu.modeling": "深入了解資料建模",
34
- "gs.header.helpMenu.understanding": "了解邏輯資料模型",
35
- "gs.header.helpMenu.learning": "學習資料建模原則",
36
- "gs.header.helpMenu.firstSteps": "分析設計器的第一步",
37
- "gs.header.helpMenu.visualizing": "視覺化您的數據",
38
- "gs.header.helpMenu.sorting": "對結果進行排序和過濾",
39
- "gs.header.helpMenu.embedding": "將分析設計器嵌入到您的應用程式中",
40
27
  "messages.genAi.visualisation.saved.success": "偉大的!我們保存了您的視覺化。",
41
28
  "messages.genAi.visualisation.saved.error": "哎呀,保存可視化時出現問題。",
42
29
  "messages.genAi.visualisation.saved.error.detail": "{errorType}: {errorMessage}",
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultHostUi.d.ts","sourceRoot":"","sources":["../../src/ui/DefaultHostUi.tsx"],"names":[],"mappings":"AAQA,OAAO,EAEH,KAAK,aAAa,EAKrB,MAAM,2CAA2C,CAAC;AAInD,OAAO,sBAAsB,CAAC;AAgK9B;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAEjC,CAAC"}
1
+ {"version":3,"file":"DefaultHostUi.d.ts","sourceRoot":"","sources":["../../src/ui/DefaultHostUi.tsx"],"names":[],"mappings":"AAQA,OAAO,EAEH,KAAK,aAAa,EAKrB,MAAM,2CAA2C,CAAC;AAInD,OAAO,sBAAsB,CAAC;AA4M9B;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAEjC,CAAC"}
@@ -1,35 +1,71 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  // (C) 2026 GoodData Corporation
3
- import { useLayoutEffect, useRef, useState } from "react";
3
+ import { useCallback, 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, onAppContainerReady, onReady, }) {
9
+ function HostUiBridge({ initialCtx, initialApps, initialPathname, navigate, replace, onAiAssistantOpenChange, 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
23
  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
+ }, []);
17
53
  // Layout effect runs in the same synchronous commit as flushSync.
18
54
  useLayoutEffect(() => {
19
- onReady(setCtx, setApps, setPathname, setHeaderOptions, setNotification, setPageTitle);
55
+ onReady(setCtx, setApps, setPathname, setHeaderOptions, handleNotify, setPageTitle);
20
56
  if (appContainerRef.current) {
21
57
  onAppContainerReady(appContainerRef.current);
22
58
  }
23
59
  // Only call once on mount
24
60
  // eslint-disable-next-line react-hooks/exhaustive-deps
25
61
  }, []);
26
- return (_jsx(HostChrome, { ctx: ctx, resolvedApplications: apps, pathname: pathname, onNavigate: navigate, onReplace: replace, headerOptions: headerOptions, notification: notification, appPageTitle: pageTitle, children: _jsx("div", { ref: appContainerRef, className: e("app-container") }) }));
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") }) }));
27
63
  }
28
64
  // ---------------------------------------------------------------------------
29
65
  // Imperative mount function conforming to IHostUiModule
30
66
  // ---------------------------------------------------------------------------
31
67
  function mountDefaultHostUi(options) {
32
- const { container, ctx, resolvedApplications, pathname, navigate, replace } = options;
68
+ const { container, ctx, resolvedApplications, pathname, navigate, replace, onAiAssistantOpenChange } = options;
33
69
  let reactRoot = createRoot(container);
34
70
  let appContainer = null;
35
71
  let updateCtxFn = null;
@@ -41,7 +77,7 @@ function mountDefaultHostUi(options) {
41
77
  // Use flushSync so that the DOM is ready synchronously after mount() returns,
42
78
  // making getAppContainer() safe to call immediately.
43
79
  flushSync(() => {
44
- reactRoot?.render(_jsx(HostUiBridge, { initialCtx: ctx, initialApps: resolvedApplications, initialPathname: pathname, navigate: navigate, replace: replace, onAppContainerReady: (el) => {
80
+ reactRoot?.render(_jsx(HostUiBridge, { initialCtx: ctx, initialApps: resolvedApplications, initialPathname: pathname, navigate: navigate, replace: replace, onAiAssistantOpenChange: onAiAssistantOpenChange, onAppContainerReady: (el) => {
45
81
  appContainer = el;
46
82
  }, onReady: (setCtx, setApps, setPathname, setHeaderOptions, setNotification, setPageTitle) => {
47
83
  updateCtxFn = setCtx;
@@ -1,4 +1,5 @@
1
1
  import { type IUserWorkspaceSettings } from "@gooddata/sdk-backend-spi";
2
+ import type { IGenAIUserContext } from "@gooddata/sdk-model";
2
3
  import { type ChatAssistantMessageEvent, type ChatClosedEvent, type ChatFeedbackEvent, type ChatOpenedEvent, type ChatResetEvent, type ChatUserMessageEvent } from "@gooddata/sdk-ui-gen-ai";
3
4
  /**
4
5
  * Discriminated union of GenAI chat events that are forwarded to the host
@@ -34,11 +35,30 @@ export interface IGenAIChatProps {
34
35
  onOpen: () => void;
35
36
  onClose: () => void;
36
37
  askedQuestion?: string | null;
38
+ /**
39
+ * Monotonic counter bumped on every ask. Keying the seeding effect on it re-seeds the chat
40
+ * (clear thread + send message) even when `askedQuestion` is identical to the previous ask.
41
+ */
42
+ askSeq?: number;
43
+ /**
44
+ * Context of the user's location when the question was asked (e.g. the active dashboard
45
+ * of a hosted application), passed to the assistant alongside the seeded question.
46
+ */
47
+ userContext?: IGenAIUserContext;
48
+ /**
49
+ * Tag identifiers the assistant's object search/autocomplete should be restricted to,
50
+ * reflecting the active hosted application's current view.
51
+ */
52
+ includeTags?: string[];
53
+ /**
54
+ * Tag identifiers the assistant's object search/autocomplete should exclude.
55
+ */
56
+ excludeTags?: string[];
37
57
  canManageProject?: boolean;
38
58
  canAnalyzeProject?: boolean;
39
59
  canFullControl?: boolean;
40
60
  settings?: IUserWorkspaceSettings;
41
61
  onEvent?: (event: GenAIChatEvent) => void;
42
62
  }
43
- export declare function GenAIChat({ workspaceId, open, onOpen, onClose, askedQuestion, canManageProject, canAnalyzeProject, canFullControl, settings, onEvent }: IGenAIChatProps): import("react/jsx-runtime").JSX.Element;
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;
44
64
  //# 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;AAGxE,OAAO,EACH,KAAK,yBAAyB,EAC9B,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,cAAc,EAEnB,KAAK,oBAAoB,EAY5B,MAAM,yBAAyB,CAAC;AAWjC;;;;;;;;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,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,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,QAAQ,EACR,OAAO,EACV,EAAE,eAAe,2CAyHjB"}
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,2CAgIjB"}
@@ -4,9 +4,9 @@ import { useCallback, useEffect, useMemo, useState } from "react";
4
4
  import { defineMessage, useIntl } from "react-intl";
5
5
  import { useBackendStrict } from "@gooddata/sdk-ui";
6
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, } from "@gooddata/sdk-ui-gen-ai/internal";
7
+ import { GenAIChatDialog, clearThreadAction, makeTextContents, makeUserMessage, newMessageAction, setUserContextAction, } from "@gooddata/sdk-ui-gen-ai/internal";
8
8
  import { HEADER_CHAT_BUTTON_ID, useToastMessage } from "@gooddata/sdk-ui-kit";
9
- export function GenAIChat({ workspaceId, open, onOpen, onClose, askedQuestion, canManageProject, canAnalyzeProject, canFullControl, settings, onEvent, }) {
9
+ export function GenAIChat({ workspaceId, open, onOpen, onClose, askedQuestion, askSeq, userContext, includeTags, excludeTags, canManageProject, canAnalyzeProject, canFullControl, settings, onEvent, }) {
10
10
  const { addSuccess, addError } = useToastMessage();
11
11
  const intl = useIntl();
12
12
  const backend = useBackendStrict();
@@ -91,12 +91,17 @@ export function GenAIChat({ workspaceId, open, onOpen, onClose, askedQuestion, c
91
91
  return;
92
92
  }
93
93
  chatDispatcher(clearThreadAction());
94
+ // Always set (and thereby clear when undefined) so a follow-up ask without context does not
95
+ // inherit the previous ask's user context (LX-2544).
96
+ chatDispatcher(setUserContextAction({ userContext }));
94
97
  if (settings.enableAiAgenticConversations) {
95
98
  chatDispatcher(newMessageAction(makeUserItem({ type: "text", text: askedQuestion })));
96
99
  }
97
100
  else {
98
101
  chatDispatcher(newMessageAction(makeUserMessage([makeTextContents(askedQuestion, [])])));
99
102
  }
100
- }, [chatDispatcher, askedQuestion, settings]);
101
- return (_jsx(GenAIChatDialog, { isOpen: open, locale: intl.locale, canManage: canManageProject, canAnalyze: canAnalyzeProject, canFullControl: canFullControl, objectTypes: objectTypes, onLinkClick: onLinkClick, onClose: onClose, onOpen: onOpen, workspace: workspaceId, backend: backend, settings: settings, eventHandlers: events, onDispatcher: onDispatcher, returnFocusTo: HEADER_CHAT_BUTTON_ID }));
103
+ // `askSeq` is included so a repeated identical question (same `askedQuestion`) still re-seeds.
104
+ // eslint-disable-next-line react-hooks/exhaustive-deps
105
+ }, [chatDispatcher, askedQuestion, askSeq, userContext, settings]);
106
+ 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 }));
102
107
  }
@@ -1,11 +1,28 @@
1
1
  import { type ReactNode } from "react";
2
- import { type PluggableApplicationRegistryItem } from "@gooddata/sdk-model";
2
+ import { type IGenAIUserContext, type PluggableApplicationRegistryItem } from "@gooddata/sdk-model";
3
3
  import { type IAppHeaderOptions, type IHostUiNotification, type IPlatformContext } from "@gooddata/sdk-pluggable-application-model";
4
4
  import "./HostChrome.scss";
5
5
  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
+ }
9
26
  export interface IHostChromeProps {
10
27
  ctx: IPlatformContext;
11
28
  resolvedApplications: PluggableApplicationRegistryItem[];
@@ -14,6 +31,12 @@ export interface IHostChromeProps {
14
31
  onReplace: (url: string) => void;
15
32
  headerOptions?: IAppHeaderOptions;
16
33
  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
40
  /**
18
41
  * Page-title segment set by the active pluggable application via a document-title-changed
19
42
  * event. When omitted, the active application's manifest title is used instead.
@@ -21,5 +44,5 @@ export interface IHostChromeProps {
21
44
  appPageTitle?: string;
22
45
  children?: ReactNode;
23
46
  }
24
- export declare function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, onReplace: _onReplace, headerOptions, notification, appPageTitle, children }: IHostChromeProps): import("react/jsx-runtime").JSX.Element;
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;
25
48
  //# 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,EAAwB,MAAM,OAAO,CAAC;AAElG,OAAO,EAGH,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;AAIlE,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,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,YAAY,EACZ,QAAQ,EACX,EAAE,gBAAgB,2CAwNlB"}
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,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, useMemo } from "react";
3
+ import { useCallback, useEffect, 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";
@@ -30,13 +30,62 @@ import "@gooddata/sdk-ui-gen-ai/styles/css/main.css";
30
30
  import "@gooddata/sdk-ui-semantic-search/styles/css/main.css";
31
31
  import "@gooddata/sdk-ui-semantic-search/styles/css/internal.css";
32
32
  const LOGOUT_MENU_ITEM_KEY = "gs.header.logout";
33
- export function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, onReplace: _onReplace, headerOptions, notification = null, appPageTitle, children, }) {
33
+ // The favicon that index.html shipped resolved to "<cdnurl>/organization/favicon.ico" on the live
34
+ // platform, "/favicon.ico" in dev. Captured once at module load, before DocumentHeader can overwrite
35
+ // the <link rel="icon">, so the host can restore the organization favicon when no custom white-label
36
+ // favicon is configured (see faviconUrl below).
37
+ const initialFaviconUrl = (typeof document !== "undefined" && document.querySelector("link[rel~='icon']")?.getAttribute("href")) ||
38
+ "/favicon.ico";
39
+ export function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, onReplace: _onReplace, headerOptions, notification = null, aiVisibility = null, aiContext = null, onAiAssistantOpenChange, appPageTitle, children, }) {
34
40
  const locale = resolveLocale(ctx.preferredLocale);
35
41
  const userName = getUserDisplayName(ctx.user);
36
42
  const features = useHostChromeWorkspaceFeatures(resolvedApplications, ctx, pathname);
37
43
  const shellTelemetry = useMemo(() => getAppLifecycleCallbacks()?.createTelemetryCallbacks?.("host-ui"), []);
38
44
  const pricing = useHostChromePricing(ctx, locale);
39
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]);
40
89
  const search = useHostChromeSearch({
41
90
  features,
42
91
  isTrial: pricing.isTrial,
@@ -105,11 +154,13 @@ export function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, on
105
154
  const logoTitle = ctx.whiteLabeling?.enabled
106
155
  ? ctx.user.organizationName || defaultLogoTitle
107
156
  : defaultLogoTitle;
108
- // White-label icons are applied whenever their URLs are set, independent of the `enabled`
109
- // flag (matching the standalone apps and the Appearance behavior). The favicon falls back to
110
- // the default served at /favicon.ico so a previously applied custom favicon is reset once its
111
- // URL is cleared, instead of leaving the stale icon in place.
112
- const faviconUrl = ctx.whiteLabeling?.faviconUrl || "/favicon.ico";
157
+ // White-label icons are applied whenever their URLs are set, independent of the `enabled` flag
158
+ // (matching the standalone apps and the Appearance behavior). When no custom favicon is set we
159
+ // fall back to the favicon index.html shipped (the organization favicon) instead of a hardcoded
160
+ // "/favicon.ico": on the live platform index.html resolves the icon to
161
+ // "<cdnurl>/organization/favicon.ico", and "/favicon.ico" is not served at the origin root — so
162
+ // hardcoding it removed the favicon once a module mounted (DocumentHeader overwrites the link).
163
+ const faviconUrl = ctx.whiteLabeling?.faviconUrl || initialFaviconUrl;
113
164
  // The host owns the browser tab title as "{page} - {brand}". The page segment defaults to the
114
165
  // active application's manifest title, but an embedded app can override it dynamically by emitting
115
166
  // a document-title-changed event (surfaced here as `appPageTitle`). When white-labeling
@@ -131,9 +182,9 @@ export function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, on
131
182
  const hideChrome = ctx.embeddingMode === "iframe" || ctx.isExportMode === true;
132
183
  return (_jsx(HostIntlProvider, { locale: locale, additionalMessages: appMessages, children: _jsx(BackendProvider, { backend: getBackend(), children: _jsx(ToastsCenterContextProvider, { children: _jsxs("div", { className: b(), children: [
133
184
  _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
134
- , 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.open, notificationsPanel: ctx.userSettings.enableInPlatformNotifications
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
135
186
  ? ({ isMobile, closeNotificationsOverlay }) => (_jsx(AppHeaderNotifications, { locale: locale, isMobile: isMobile, closeNotificationsOverlay: closeNotificationsOverlay, useAsOfDateParam: ctx.userSettings.enableExecutionTimestamp ?? false, enableExportToDocumentStorage: ctx.userSettings.enableExportToDocumentStorage ??
136
187
  false }))
137
- : undefined }) })), _jsx("main", { className: e("content"), children: children }), chat.element, pricing.element, _jsx(HostNotificationDispatcher, { notification: notification })
188
+ : undefined }) })), _jsx("main", { className: e("content"), children: children }), hideChrome ? null : chat.element, pricing.element, _jsx(HostNotificationDispatcher, { notification: notification })
138
189
  ] }) }) }) }));
139
190
  }
@@ -5,8 +5,10 @@ export interface IPluggableApplicationRendererProps {
5
5
  app: PluggableApplicationRegistryItem;
6
6
  ctx: IPlatformContext;
7
7
  pathname: string;
8
+ /** Host-owned AI assistant chat open-state, forwarded to the mounted app's handle. */
9
+ aiAssistantOpen?: boolean;
8
10
  onHeaderChange?: (appId: string, header: IAppHeaderOptions) => void;
9
11
  onDocumentTitleChange?: (appId: string, pageTitle: string | undefined) => void;
10
12
  }
11
- export declare function PluggableApplicationRenderer({ app, ctx, pathname, onHeaderChange, onDocumentTitleChange }: IPluggableApplicationRendererProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOpen, onHeaderChange, onDocumentTitleChange }: IPluggableApplicationRendererProps): import("react/jsx-runtime").JSX.Element;
12
14
  //# 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,EAMxB,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,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,cAAc,EACd,qBAAqB,EACxB,EAAE,kCAAkC,2CAsMpC"}
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"}
@@ -2,10 +2,11 @@ import { 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 { FormattedMessage, defineMessage, useIntl } from "react-intl";
5
- import { isDocumentTitleChangedEvent, isReloadPlatformContextRequestedEvent, } from "@gooddata/sdk-pluggable-application-model";
5
+ import { isAiAssistantContextChangedEvent, isCloseAiAssistantRequestedEvent, isDocumentTitleChangedEvent, isOpenAiAssistantRequestedEvent, isReloadPlatformContextRequestedEvent, } from "@gooddata/sdk-pluggable-application-model";
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";
9
10
  import { getSecuredRemoteAppValidUntil, validateAppSecurity, } from "../loader/appSecurityValidation.js";
10
11
  import { getAppLifecycleCallbacks, loadPluggableApplication } from "../loader/pluggableApplicationsLoader.js";
11
12
  import { getApplicationHref } from "../loader/routing.js";
@@ -20,7 +21,7 @@ const SECURITY_FAILURE_MESSAGES = {
20
21
  "build-expired": defineMessage({ id: "gs.host.error.appBuildExpired" }),
21
22
  "metadata-missing": defineMessage({ id: "gs.host.error.appSecurityMetadataMissing" }),
22
23
  };
23
- export function PluggableApplicationRenderer({ app, ctx, pathname, onHeaderChange, onDocumentTitleChange, }) {
24
+ export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOpen, onHeaderChange, onDocumentTitleChange, }) {
24
25
  const intl = useIntl();
25
26
  const intlRef = useAutoupdateRef(intl);
26
27
  const ctxRef = useAutoupdateRef(ctx);
@@ -45,6 +46,29 @@ export function PluggableApplicationRenderer({ app, ctx, pathname, onHeaderChang
45
46
  void BackendPlatformContextProvider.load();
46
47
  return;
47
48
  }
49
+ // 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
+ if (isOpenAiAssistantRequestedEvent(event)) {
53
+ dispatchHostNotification({
54
+ type: "openAiAssistant",
55
+ question: event.payload.question,
56
+ userContext: event.payload.userContext,
57
+ });
58
+ return;
59
+ }
60
+ if (isCloseAiAssistantRequestedEvent(event)) {
61
+ dispatchHostNotification({ type: "closeAiAssistant" });
62
+ return;
63
+ }
64
+ if (isAiAssistantContextChangedEvent(event)) {
65
+ dispatchHostNotification({
66
+ type: "aiAssistantContext",
67
+ includeTags: event.payload.includeTags,
68
+ excludeTags: event.payload.excludeTags,
69
+ });
70
+ return;
71
+ }
48
72
  if (isDocumentTitleChangedEvent(event)) {
49
73
  onDocumentTitleChangeRef.current?.(app.id, event.payload.pageTitle);
50
74
  }
@@ -149,6 +173,14 @@ export function PluggableApplicationRenderer({ app, ctx, pathname, onHeaderChang
149
173
  }
150
174
  handle.updateContext?.(ctx);
151
175
  }, [ctx, intlRef, lifecycle]);
176
+ // Forward the host-owned chat open-state to the mounted app once it is ready, and on every
177
+ // change, so app-side assistant controls stay aligned with the real (host) state (LX-2544).
178
+ useEffect(() => {
179
+ if (viewState.state !== "ready" || aiAssistantOpen === undefined) {
180
+ return;
181
+ }
182
+ mountHandleRef.current?.setAiAssistantOpen?.(aiAssistantOpen);
183
+ }, [aiAssistantOpen, viewState.state]);
152
184
  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: [
153
185
  _jsx("h2", { children: _jsx(FormattedMessage, { id: "gs.host.error.applicationFailedToLoad" }) }), _jsx("p", { children: viewState.message })
154
186
  ] })) : 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: {
@@ -1,9 +1,12 @@
1
1
  import { type ReactNode } from "react";
2
+ import { type IGenAIUserContext } from "@gooddata/sdk-model";
2
3
  import { type IPlatformContext, type IPluggableAppTelemetryCallbacks } from "@gooddata/sdk-pluggable-application-model";
3
4
  import { type IHostChromeWorkspaceFeatures } from "./useHostChromeWorkspaceFeatures.js";
4
5
  export interface IHostChromeChat {
5
6
  /** The `<GenAIChat>` element to mount, or `null` when chat is gated off. */
6
7
  element: ReactNode;
8
+ /** Whether the chat dialog is currently open. */
9
+ isOpen: boolean;
7
10
  /**
8
11
  * Whether the chat entry point should be visible in the header. Reflects both the
9
12
  * feature-flag/permission gate and the runtime LLM availability probe.
@@ -11,8 +14,20 @@ export interface IHostChromeChat {
11
14
  showChatItem: boolean;
12
15
  /** Open the chat dialog. */
13
16
  open: () => void;
14
- /** Open the chat dialog with a pre-seeded user question. */
15
- askAiAssistant: (question: string) => void;
17
+ /** Close the chat dialog. */
18
+ close: () => void;
19
+ /** Toggle the chat dialog open/closed — used by the header chat button. */
20
+ toggle: () => void;
21
+ /**
22
+ * Open the chat dialog with a pre-seeded user question and optional user-location context
23
+ * (e.g. the active dashboard a hosted application forwards alongside the question).
24
+ */
25
+ askAiAssistant: (question: string, userContext?: IGenAIUserContext) => void;
26
+ /**
27
+ * Set the assistant's object-search tag scope, reflecting the active hosted application's
28
+ * current view (e.g. AD's include/exclude tag route filters). Pass empty/undefined to clear.
29
+ */
30
+ setTags: (includeTags?: string[], excludeTags?: string[]) => void;
16
31
  }
17
32
  export interface IUseHostChromeChatArgs {
18
33
  features: IHostChromeWorkspaceFeatures;
@@ -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,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;;;OAGG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB,4BAA4B;IAC5B,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,4DAA4D;IAC5D,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C;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,CAqDvG"}
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"}
@@ -14,25 +14,48 @@ import { GenAIChat } from "./GenAIChat.js";
14
14
  export function useHostChromeChat({ features, ctx, telemetry }) {
15
15
  const [isChatOpen, setIsChatOpen] = useState(false);
16
16
  const [askedQuestion, setAskedQuestion] = useState(null);
17
+ // Bumped on every ask so the chat re-seeds (clears the thread + sends the message) even when the
18
+ // same prompt is asked again — e.g. "Summarize" clicked twice, or repeated after a close (the
19
+ // seeding effect is otherwise keyed on the question text, which does not change on a repeat).
20
+ const [askSeq, setAskSeq] = useState(0);
21
+ const [userContext, setUserContext] = useState(undefined);
22
+ const [includeTags, setIncludeTags] = useState(undefined);
23
+ const [excludeTags, setExcludeTags] = useState(undefined);
17
24
  const open = useCallback(() => {
18
25
  setIsChatOpen(true);
19
26
  }, []);
20
27
  const close = useCallback(() => {
21
28
  setIsChatOpen(false);
22
29
  }, []);
23
- const askAiAssistant = useCallback((question) => {
30
+ // The header chat button toggles the dialog (click to open, click again to close),
31
+ // matching the standalone apps' behavior (LX-2544).
32
+ const toggle = useCallback(() => {
33
+ setIsChatOpen((isOpen) => !isOpen);
34
+ }, []);
35
+ const askAiAssistant = useCallback((question, questionUserContext) => {
24
36
  setAskedQuestion(question);
37
+ setUserContext(questionUserContext);
38
+ setAskSeq((seq) => seq + 1);
25
39
  setIsChatOpen(true);
26
40
  }, []);
41
+ const setTags = useCallback((nextIncludeTags, nextExcludeTags) => {
42
+ // Normalize empty arrays to undefined so the chat treats "no scope" uniformly.
43
+ setIncludeTags(nextIncludeTags?.length ? nextIncludeTags : undefined);
44
+ setExcludeTags(nextExcludeTags?.length ? nextExcludeTags : undefined);
45
+ }, []);
27
46
  const handleChatEvent = useCallback((event) => {
28
47
  telemetry?.trackEvent(event.name, event.payload);
29
48
  }, [telemetry]);
30
49
  const showChatItem = useGenAiChatAvailability(getBackend(), features.workspaceId, features.showChat, features.canManageProject);
31
- const element = features.showChat && features.workspaceId ? (_jsx(GenAIChat, { workspaceId: features.workspaceId, open: isChatOpen, onOpen: open, onClose: close, askedQuestion: askedQuestion, 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, onEvent: handleChatEvent })) : null;
32
51
  return {
33
52
  element,
53
+ isOpen: isChatOpen,
34
54
  showChatItem,
35
55
  open,
56
+ close,
57
+ toggle,
36
58
  askAiAssistant,
59
+ setTags,
37
60
  };
38
61
  }