@redocly/theme 0.70.0-next.5 → 0.70.0-next.6

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 (87) hide show
  1. package/lib/components/Buttons/ConnectMCPButton.js +8 -13
  2. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.js +1 -1
  3. package/lib/components/Catalog/CatalogFilter/CatalogFilterDateRange.js +2 -1
  4. package/lib/components/Dropdown/Dropdown.d.ts +4 -2
  5. package/lib/components/Dropdown/Dropdown.js +28 -8
  6. package/lib/components/Filter/FilterDateRange.js +36 -2
  7. package/lib/components/Search/SearchAiActivity.d.ts +10 -0
  8. package/lib/components/Search/SearchAiActivity.js +278 -0
  9. package/lib/components/Search/SearchAiDialog.js +5 -1
  10. package/lib/components/Search/SearchAiMessage.d.ts +2 -1
  11. package/lib/components/Search/SearchAiMessage.js +20 -108
  12. package/lib/components/Search/SearchItem.js +1 -8
  13. package/lib/components/Search/variables.js +15 -2
  14. package/lib/components/Select/SelectInput.js +2 -8
  15. package/lib/components/SplitButton/SplitButton.js +37 -3
  16. package/lib/core/constants/mcp.d.ts +2 -2
  17. package/lib/core/constants/mcp.js +3 -8
  18. package/lib/core/constants/search.js +5 -1
  19. package/lib/core/hooks/index.d.ts +1 -0
  20. package/lib/core/hooks/index.js +1 -0
  21. package/lib/core/hooks/menu/use-collapse.js +1 -1
  22. package/lib/core/hooks/search/use-search-ai-activity.d.ts +11 -0
  23. package/lib/core/hooks/search/use-search-ai-activity.js +15 -0
  24. package/lib/core/hooks/use-connect-mcp-button.js +2 -1
  25. package/lib/core/hooks/use-full-height.d.ts +1 -1
  26. package/lib/core/hooks/use-full-height.js +8 -8
  27. package/lib/core/styles/global.d.ts +2 -0
  28. package/lib/core/styles/global.js +80 -76
  29. package/lib/core/styles/index.d.ts +1 -0
  30. package/lib/core/styles/index.js +3 -1
  31. package/lib/core/styles/search-ai-appear.d.ts +1 -0
  32. package/lib/core/styles/search-ai-appear.js +18 -0
  33. package/lib/core/types/dropdown.d.ts +1 -0
  34. package/lib/core/types/dropdown.js +3 -0
  35. package/lib/core/types/index.d.ts +1 -0
  36. package/lib/core/types/l10n.d.ts +1 -1
  37. package/lib/core/types/mcp.d.ts +2 -1
  38. package/lib/core/types/search.d.ts +23 -0
  39. package/lib/core/utils/assign-ref.d.ts +2 -0
  40. package/lib/core/utils/assign-ref.js +12 -0
  41. package/lib/core/utils/content-segments.d.ts +12 -1
  42. package/lib/core/utils/content-segments.js +90 -5
  43. package/lib/core/utils/dropdown.d.ts +2 -0
  44. package/lib/core/utils/dropdown.js +12 -0
  45. package/lib/core/utils/index.d.ts +2 -0
  46. package/lib/core/utils/index.js +2 -0
  47. package/lib/core/utils/mcp.d.ts +2 -1
  48. package/lib/core/utils/mcp.js +5 -0
  49. package/lib/core/utils/search-ai-activity.d.ts +4 -0
  50. package/lib/core/utils/search-ai-activity.js +83 -0
  51. package/lib/markdoc/tags/connect-mcp.js +15 -0
  52. package/package.json +2 -2
  53. package/src/components/Buttons/ConnectMCPButton.tsx +8 -14
  54. package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.tsx +3 -1
  55. package/src/components/Catalog/CatalogFilter/CatalogFilterDateRange.tsx +3 -2
  56. package/src/components/Dropdown/Dropdown.tsx +28 -6
  57. package/src/components/Filter/FilterDateRange.tsx +3 -2
  58. package/src/components/Search/SearchAiActivity.tsx +318 -0
  59. package/src/components/Search/SearchAiDialog.tsx +4 -3
  60. package/src/components/Search/SearchAiMessage.tsx +27 -135
  61. package/src/components/Search/SearchItem.tsx +2 -9
  62. package/src/components/Search/variables.ts +15 -2
  63. package/src/components/Select/SelectInput.tsx +2 -8
  64. package/src/components/SplitButton/SplitButton.tsx +8 -2
  65. package/src/core/constants/mcp.ts +3 -9
  66. package/src/core/constants/search.ts +5 -1
  67. package/src/core/hooks/__mocks__/index.ts +1 -0
  68. package/src/core/hooks/index.ts +1 -0
  69. package/src/core/hooks/menu/use-collapse.ts +1 -1
  70. package/src/core/hooks/search/use-search-ai-activity.ts +31 -0
  71. package/src/core/hooks/use-connect-mcp-button.ts +7 -2
  72. package/src/core/hooks/use-full-height.ts +8 -8
  73. package/src/core/styles/global.ts +80 -75
  74. package/src/core/styles/index.ts +2 -1
  75. package/src/core/styles/search-ai-appear.ts +16 -0
  76. package/src/core/types/dropdown.ts +1 -0
  77. package/src/core/types/index.ts +1 -0
  78. package/src/core/types/l10n.ts +1 -1
  79. package/src/core/types/mcp.ts +3 -1
  80. package/src/core/types/search.ts +21 -0
  81. package/src/core/utils/assign-ref.ts +9 -0
  82. package/src/core/utils/content-segments.ts +98 -6
  83. package/src/core/utils/dropdown.ts +14 -0
  84. package/src/core/utils/index.ts +2 -0
  85. package/src/core/utils/mcp.ts +7 -1
  86. package/src/core/utils/search-ai-activity.ts +110 -0
  87. package/src/markdoc/tags/connect-mcp.ts +16 -1
@@ -19,55 +19,50 @@ const CopyIcon_1 = require("../../icons/CopyIcon/CopyIcon");
19
19
  const CheckmarkFilledIcon_1 = require("../../icons/CheckmarkFilledIcon/CheckmarkFilledIcon");
20
20
  const hooks_1 = require("../../core/hooks");
21
21
  const constants_1 = require("../../core/constants");
22
- const MENU_OPTIONS = [
23
- {
24
- key: 'cursor',
22
+ const MENU_OPTIONS = {
23
+ cursor: {
25
24
  icon: CursorIcon_1.CursorIcon,
26
25
  titleTranslationKey: 'page.actions.connectMcp.cursor',
27
26
  titleDefault: 'Connect to Cursor',
28
27
  descriptionTranslationKey: 'page.actions.connectMcp.cursorDescription',
29
28
  descriptionDefault: 'Install MCP server on Cursor',
30
29
  },
31
- {
32
- key: 'vscode',
30
+ vscode: {
33
31
  icon: VSCodeIcon_1.VSCodeIcon,
34
32
  titleTranslationKey: 'page.actions.connectMcp.vscode',
35
33
  titleDefault: 'Connect to VS Code',
36
34
  descriptionTranslationKey: 'page.actions.connectMcp.vscodeDescription',
37
35
  descriptionDefault: 'Install MCP server on VS Code',
38
36
  },
39
- {
40
- key: 'claude-code',
37
+ 'claude-code': {
41
38
  icon: ClaudeIcon_1.ClaudeIcon,
42
39
  titleTranslationKey: 'page.actions.connectMcp.claudeCode',
43
40
  titleDefault: 'Connect to Claude Code',
44
41
  descriptionTranslationKey: 'page.actions.connectMcp.claudeCodeDescription',
45
42
  descriptionDefault: 'Copy the Claude Code CLI command',
46
43
  },
47
- {
48
- key: 'codex',
44
+ codex: {
49
45
  icon: CodexIcon_1.CodexIcon,
50
46
  titleTranslationKey: 'page.actions.connectMcp.codex',
51
47
  titleDefault: 'Connect to Codex CLI',
52
48
  descriptionTranslationKey: 'page.actions.connectMcp.codexDescription',
53
49
  descriptionDefault: 'Copy the Codex CLI command',
54
50
  },
55
- {
56
- key: 'copy',
51
+ copy: {
57
52
  icon: CopyIcon_1.CopyIcon,
58
53
  titleTranslationKey: 'page.actions.connectMcp.copyConfig',
59
54
  titleDefault: 'Copy MCP Configuration',
60
55
  descriptionTranslationKey: 'page.actions.connectMcp.copyConfigDescription',
61
56
  descriptionDefault: 'Copy MCP JSON Configuration',
62
57
  },
63
- ];
58
+ };
64
59
  // Copy actions keep the dropdown open so the copied checkmark is visible.
65
60
  const COPY_OPTIONS = ['claude-code', 'codex', 'copy'];
66
61
  function ConnectMCPButton({ placement = 'bottom', alignment = 'end', options = constants_1.DEFAULT_MCP_OPTIONS, }) {
67
62
  const { useTranslate } = (0, hooks_1.useThemeHooks)();
68
63
  const { translate } = useTranslate();
69
64
  const { copiedOption, visibleOptions, handleAction } = (0, hooks_1.useConnectMCPButton)({ options });
70
- const menuOptions = MENU_OPTIONS.filter((option) => visibleOptions.includes(option.key));
65
+ const menuOptions = visibleOptions.map((key) => (Object.assign({ key }, MENU_OPTIONS[key])));
71
66
  const primaryOption = menuOptions[0];
72
67
  const dropdownOptions = menuOptions.slice(1);
73
68
  if (!primaryOption)
@@ -35,5 +35,5 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.CatalogEntityRelationsGraph = void 0;
37
37
  const utils_1 = require("../../../../core/utils");
38
- exports.CatalogEntityRelationsGraph = (0, utils_1.dynamic)(() => Promise.resolve().then(() => __importStar(require('./CatalogEntityRelationsGraph'))).then((mod) => mod.CatalogEntityRelationsGraph));
38
+ exports.CatalogEntityRelationsGraph = (0, utils_1.dynamic)(() => Promise.resolve().then(() => __importStar(require('../../../../components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph'))).then((mod) => mod.CatalogEntityRelationsGraph));
39
39
  //# sourceMappingURL=CatalogEntityRelationsGraph.lazy.js.map
@@ -46,6 +46,7 @@ const hooks_1 = require("../../../core/hooks");
46
46
  const utils_1 = require("../../../core/utils");
47
47
  const Accordion_1 = require("../../../components/Accordion/Accordion");
48
48
  function CatalogFilterDateRange({ filter, className, }) {
49
+ const [today] = (0, react_1.useState)(() => new Date());
49
50
  const { useTranslate } = (0, hooks_1.useThemeHooks)();
50
51
  const { translate } = useTranslate();
51
52
  const from = (0, react_1.useMemo)(() => (filter.selectedOptions.from ? new Date(filter.selectedOptions.from) : undefined), [filter.selectedOptions.from]);
@@ -62,7 +63,7 @@ function CatalogFilterDateRange({ filter, className, }) {
62
63
  react_1.default.createElement(DateRangeWrapper, null,
63
64
  react_1.default.createElement(DateRangeRow, null,
64
65
  react_1.default.createElement("span", null, translate('filter.dateRange.from', 'From:')),
65
- react_1.default.createElement(DatePicker_1.DatePicker, { closeCalendar: true, format: "y-MM-dd", dayPlaceholder: "DD", monthPlaceholder: "MM", yearPlaceholder: "YYYY", value: from, maxDate: new Date(), onChange: (from) => {
66
+ react_1.default.createElement(DatePicker_1.DatePicker, { closeCalendar: true, format: "y-MM-dd", dayPlaceholder: "DD", monthPlaceholder: "MM", yearPlaceholder: "YYYY", value: from, maxDate: today, onChange: (from) => {
66
67
  if (Array.isArray(from))
67
68
  return;
68
69
  filter.selectOption(Object.assign(Object.assign({}, filter.selectedOptions), { from: (0, utils_1.formatDateWithoutTimeZone)(from), to: (0, utils_1.formatDateWithoutTimeZone)(to) }));
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import type { RenderableChildren } from '../../core/types';
2
+ import type { DropdownAlignment, RenderableChildren } from '../../core/types';
3
3
  type DropdownMenuContext = {
4
4
  close: () => void;
5
5
  isOpen: boolean;
@@ -9,13 +9,15 @@ export type DropdownProps = {
9
9
  children: RenderableChildren<DropdownMenuContext>;
10
10
  triggerEvent?: 'click' | 'hover';
11
11
  placement?: 'top' | 'bottom';
12
- alignment?: 'start' | 'end';
12
+ alignment?: DropdownAlignment;
13
13
  active?: boolean;
14
14
  closeOnClick?: boolean;
15
15
  dataAttributes?: Record<string, string>;
16
16
  className?: string;
17
17
  withArrow?: boolean;
18
18
  portalled?: boolean;
19
+ /** Element the menu lines up with when it is not the dropdown itself, e.g. a whole split button. */
20
+ alignmentAnchorRef?: React.RefObject<HTMLElement | null>;
19
21
  onClick?: (event: React.UIEvent) => void;
20
22
  onClose?: () => void;
21
23
  onOpen?: () => void;
@@ -38,15 +38,17 @@ const react_1 = __importStar(require("react"));
38
38
  const react_dom_1 = require("react-dom");
39
39
  const styled_components_1 = __importStar(require("styled-components"));
40
40
  const hooks_1 = require("../../core/hooks");
41
+ const utils_1 = require("../../core/utils");
41
42
  const ChevronDownIcon_1 = require("../../icons/ChevronDownIcon/ChevronDownIcon");
42
43
  const ChevronUpIcon_1 = require("../../icons/ChevronUpIcon/ChevronUpIcon");
43
44
  const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? react_1.useLayoutEffect : react_1.useEffect;
44
- exports.Dropdown = (0, react_1.forwardRef)(({ children, className, active, trigger, triggerEvent = 'click', closeOnClick = true, withArrow, dataAttributes, placement, alignment, portalled = false, onClick, onClose, onOpen, }, ref) => {
45
+ exports.Dropdown = (0, react_1.forwardRef)(({ children, className, active, trigger, triggerEvent = 'click', closeOnClick = true, withArrow, dataAttributes, placement, alignment, portalled = false, alignmentAnchorRef, onClick, onClose, onOpen, }, ref) => {
45
46
  var _a, _b;
46
47
  const dropdownRef = (0, react_1.useRef)(null);
47
48
  const menuRef = (0, react_1.useRef)(null);
48
49
  const [isOpen, setIsOpen] = (0, hooks_1.useControlledState)(false, active);
49
50
  const [menuStyle, setMenuStyle] = (0, react_1.useState)({});
51
+ const [fittedAlignment, setFittedAlignment] = (0, react_1.useState)(alignment);
50
52
  const wrapperRef = ref || dropdownRef;
51
53
  const handleOpen = () => {
52
54
  setIsOpen(true);
@@ -78,13 +80,13 @@ exports.Dropdown = (0, react_1.forwardRef)(({ children, className, active, trigg
78
80
  }
79
81
  };
80
82
  const updateMenuPosition = (0, react_1.useCallback)(() => {
81
- var _a;
82
- var _b;
83
+ var _a, _b;
84
+ var _c, _d, _e;
83
85
  const wrapper = wrapperRef.current;
84
86
  if (!wrapper)
85
87
  return;
86
- const rect = wrapper.getBoundingClientRect();
87
- const menuHeight = (_b = (_a = menuRef.current) === null || _a === void 0 ? void 0 : _a.offsetHeight) !== null && _b !== void 0 ? _b : 0;
88
+ const rect = ((_c = alignmentAnchorRef === null || alignmentAnchorRef === void 0 ? void 0 : alignmentAnchorRef.current) !== null && _c !== void 0 ? _c : wrapper).getBoundingClientRect();
89
+ const menuHeight = (_d = (_a = menuRef.current) === null || _a === void 0 ? void 0 : _a.offsetHeight) !== null && _d !== void 0 ? _d : 0;
88
90
  const fitsBelow = rect.bottom + menuHeight <= window.innerHeight;
89
91
  const fitsAbove = rect.top - menuHeight >= 0;
90
92
  const openUpward = placement === 'top' ? fitsAbove || !fitsBelow : !fitsBelow && fitsAbove;
@@ -95,7 +97,7 @@ exports.Dropdown = (0, react_1.forwardRef)(({ children, className, active, trigg
95
97
  else {
96
98
  style.top = rect.bottom;
97
99
  }
98
- if (alignment === 'end') {
100
+ if ((0, utils_1.fitDropdownAlignment)(rect, (_e = (_b = menuRef.current) === null || _b === void 0 ? void 0 : _b.offsetWidth) !== null && _e !== void 0 ? _e : 0, alignment) === 'end') {
99
101
  style.right = window.innerWidth - rect.right;
100
102
  }
101
103
  else {
@@ -106,7 +108,25 @@ exports.Dropdown = (0, react_1.forwardRef)(({ children, className, active, trigg
106
108
  style.zIndex = zIndex;
107
109
  }
108
110
  setMenuStyle(style);
109
- }, [placement, alignment, wrapperRef]);
111
+ }, [placement, alignment, wrapperRef, alignmentAnchorRef]);
112
+ /* The absolute-positioned menu is placed by CSS, so the side is measured once it is
113
+ shown and again on resize; a side that runs off the viewport flips to the other one. */
114
+ const updateFittedAlignment = (0, react_1.useCallback)(() => {
115
+ var _a;
116
+ const wrapper = wrapperRef.current;
117
+ const menu = menuRef.current;
118
+ if (!wrapper || !menu)
119
+ return;
120
+ const rect = ((_a = alignmentAnchorRef === null || alignmentAnchorRef === void 0 ? void 0 : alignmentAnchorRef.current) !== null && _a !== void 0 ? _a : wrapper).getBoundingClientRect();
121
+ setFittedAlignment((0, utils_1.fitDropdownAlignment)(rect, menu.offsetWidth, alignment));
122
+ }, [alignment, wrapperRef, alignmentAnchorRef]);
123
+ useIsomorphicLayoutEffect(() => {
124
+ if (portalled || !isOpen)
125
+ return;
126
+ updateFittedAlignment();
127
+ window.addEventListener('resize', updateFittedAlignment);
128
+ return () => window.removeEventListener('resize', updateFittedAlignment);
129
+ }, [portalled, isOpen, updateFittedAlignment]);
110
130
  useIsomorphicLayoutEffect(() => {
111
131
  if (!portalled || !isOpen)
112
132
  return;
@@ -123,7 +143,7 @@ exports.Dropdown = (0, react_1.forwardRef)(({ children, className, active, trigg
123
143
  const triggerChild = react_1.default.Children.only(trigger);
124
144
  const isLinkTrigger = Boolean(((_a = triggerChild.props) === null || _a === void 0 ? void 0 : _a.to) || ((_b = triggerChild.props) === null || _b === void 0 ? void 0 : _b.href));
125
145
  const dropdownTrigger = (0, react_1.cloneElement)(triggerChild, Object.assign(Object.assign(Object.assign({ onClick: triggerEvent === 'click' ? handleToggle : undefined, 'aria-haspopup': 'menu', 'aria-expanded': isOpen, icon: withArrow ? isOpen ? react_1.default.createElement(ChevronUpIcon_1.ChevronUpIcon, null) : react_1.default.createElement(ChevronDownIcon_1.ChevronDownIcon, null) : undefined }, (withArrow ? { iconPosition: 'right' } : {})), triggerChild.props), { onKeyDown: triggerEvent === 'click' || !isLinkTrigger ? handleKeyDown : undefined }));
126
- const menu = (react_1.default.createElement(ChildrenWrapper, { ref: menuRef, $placement: placement, $alignment: alignment, $isOpen: isOpen, $portalled: portalled, style: portalled ? menuStyle : undefined, onClick: closeOnClick ? handleChildClick : undefined }, menuContent));
146
+ const menu = (react_1.default.createElement(ChildrenWrapper, { ref: menuRef, $placement: placement, $alignment: fittedAlignment, $isOpen: isOpen, $portalled: portalled, style: portalled ? menuStyle : undefined, onClick: closeOnClick ? handleChildClick : undefined }, menuContent));
127
147
  const portalledMenu = isOpen && typeof document !== 'undefined' ? (0, react_dom_1.createPortal)(menu, document.body) : null;
128
148
  return (react_1.default.createElement(DropdownWrapper, Object.assign({ "data-component-name": "Dropdown/Dropdown", "data-testid": "dropdown" }, dataAttributes, { className: className, ref: wrapperRef, onPointerEnter: triggerEvent === 'hover' ? handleOpen : undefined, onPointerLeave: triggerEvent === 'hover' ? handleClose : undefined, onClick: onClick }),
129
149
  dropdownTrigger,
@@ -1,10 +1,43 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
6
39
  exports.FilterDateRange = FilterDateRange;
7
- const react_1 = __importDefault(require("react"));
40
+ const react_1 = __importStar(require("react"));
8
41
  const styled_components_1 = __importDefault(require("styled-components"));
9
42
  const FilterOptions_1 = require("../../components/Filter/FilterOptions");
10
43
  const FilterTitle_1 = require("../../components/Filter/FilterTitle");
@@ -12,6 +45,7 @@ const DatePicker_1 = require("../../components/DatePicker/DatePicker");
12
45
  const hooks_1 = require("../../core/hooks");
13
46
  const utils_1 = require("../../core/utils");
14
47
  function FilterDateRange({ filter }) {
48
+ const [today] = (0, react_1.useState)(() => new Date());
15
49
  const { useTranslate } = (0, hooks_1.useThemeHooks)();
16
50
  const { translate } = useTranslate();
17
51
  const from = filter.selectedOptions.from ? new Date(filter.selectedOptions.from) : undefined;
@@ -22,7 +56,7 @@ function FilterDateRange({ filter }) {
22
56
  react_1.default.createElement(DateRangeWrapper, null,
23
57
  react_1.default.createElement(DateRangeRow, null,
24
58
  react_1.default.createElement("span", null, translate('filter.dateRange.from', 'From:')),
25
- react_1.default.createElement(DatePicker_1.DatePicker, { closeCalendar: true, format: "y-MM-dd", dayPlaceholder: "DD", monthPlaceholder: "MM", yearPlaceholder: "YYYY", value: from, maxDate: new Date(), onChange: (from) => {
59
+ react_1.default.createElement(DatePicker_1.DatePicker, { closeCalendar: true, format: "y-MM-dd", dayPlaceholder: "DD", monthPlaceholder: "MM", yearPlaceholder: "YYYY", value: from, maxDate: today, onChange: (from) => {
26
60
  if (Array.isArray(from))
27
61
  return;
28
62
  filter.selectOption(Object.assign(Object.assign({}, filter.selectedOptions), { from: (0, utils_1.formatDateWithoutTimeZone)(from), to: (0, utils_1.formatDateWithoutTimeZone)(to) }));
@@ -0,0 +1,10 @@
1
+ import { type JSX } from 'react';
2
+ import type { ToolCall } from '../../core/types';
3
+ export type SearchAiActivityProps = {
4
+ /** One run of consecutive tool calls; an empty list means the assistant has not called a tool yet. */
5
+ toolCalls: ToolCall[];
6
+ /** The assistant works on this group now; once it moves on, a call without a result counts as finished. */
7
+ isActive: boolean;
8
+ className?: string;
9
+ };
10
+ export declare function SearchAiActivity({ toolCalls, isActive, className, }: SearchAiActivityProps): JSX.Element;
@@ -0,0 +1,278 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.SearchAiActivity = SearchAiActivity;
37
+ const react_1 = __importStar(require("react"));
38
+ const styled_components_1 = __importStar(require("styled-components"));
39
+ const hooks_1 = require("../../core/hooks");
40
+ const styles_1 = require("../../core/styles");
41
+ const AiStarsIcon_1 = require("../../icons/AiStarsIcon/AiStarsIcon");
42
+ const ChatIcon_1 = require("../../icons/ChatIcon/ChatIcon");
43
+ const ChevronDownIcon_1 = require("../../icons/ChevronDownIcon/ChevronDownIcon");
44
+ const CodeIcon_1 = require("../../icons/CodeIcon/CodeIcon");
45
+ const SearchIcon_1 = require("../../icons/SearchIcon/SearchIcon");
46
+ const ACTIVITY_KIND_ICONS = {
47
+ thinking: AiStarsIcon_1.AiStarsIcon,
48
+ search: SearchIcon_1.SearchIcon,
49
+ support: ChatIcon_1.ChatIcon,
50
+ code: CodeIcon_1.CodeIcon,
51
+ };
52
+ function SearchAiActivity({ toolCalls, isActive, className, }) {
53
+ var _a;
54
+ const [isExpanded, setIsExpanded] = (0, react_1.useState)(false);
55
+ const listId = (0, react_1.useId)();
56
+ const { status, steps } = (0, hooks_1.useSearchAiActivity)({ toolCalls, isActive });
57
+ const StatusIcon = ACTIVITY_KIND_ICONS[status.kind];
58
+ const header = (react_1.default.createElement(react_1.default.Fragment, null,
59
+ react_1.default.createElement(IconWrapper, { "aria-hidden": true },
60
+ react_1.default.createElement(StatusIcon, { size: "var(--search-ai-activity-icon-size)" })),
61
+ react_1.default.createElement(StatusLabel, { key: `${status.label}${(_a = status.detail) !== null && _a !== void 0 ? _a : ''}`, title: status.detail ? `${status.label} “${status.detail}”` : status.label, $isRunning: status.isRunning },
62
+ status.label,
63
+ status.detail && react_1.default.createElement(StatusDetail, null,
64
+ " \u201C",
65
+ status.detail,
66
+ "\u201D")),
67
+ steps.length > 1 && (react_1.default.createElement(react_1.default.Fragment, null,
68
+ ' ',
69
+ react_1.default.createElement(ToolCallCount, null, steps.length))),
70
+ steps.length > 0 && (react_1.default.createElement(ExpandIcon, { size: "var(--search-ai-activity-icon-size)", $isExpanded: isExpanded }))));
71
+ return (react_1.default.createElement(SearchAiActivityWrapper, { "data-component-name": "Search/SearchAiActivity", "data-testid": "tool-calls-info", className: className }, steps.length > 0 ? (react_1.default.createElement(react_1.default.Fragment, null,
72
+ react_1.default.createElement(HeaderButton, { type: "button", "aria-expanded": isExpanded, "aria-controls": listId, onClick: () => setIsExpanded((expanded) => !expanded) }, header),
73
+ react_1.default.createElement(StepsWrapper, { id: listId, $isExpanded: isExpanded },
74
+ react_1.default.createElement(StepList, null, steps.map((step) => {
75
+ const StepIcon = ACTIVITY_KIND_ICONS[step.kind];
76
+ return (react_1.default.createElement(StepItem, { key: step.id },
77
+ react_1.default.createElement(StepIconWrapper, { "aria-hidden": true },
78
+ react_1.default.createElement(StepIcon, { size: "var(--search-ai-activity-icon-size)" })),
79
+ react_1.default.createElement(StepText, { $isRunning: step.isRunning && isExpanded }, step.label)));
80
+ }))))) : (react_1.default.createElement(HeaderWrapper, null, header))));
81
+ }
82
+ const labelIn = (0, styled_components_1.keyframes) `
83
+ from {
84
+ opacity: 0.35;
85
+ }
86
+ `;
87
+ const shimmerSweep = (0, styled_components_1.keyframes) `
88
+ from {
89
+ background-position: 100% 0;
90
+ }
91
+ to {
92
+ background-position: 0 0;
93
+ }
94
+ `;
95
+ const shimmer = (0, styled_components_1.css) `
96
+ @supports (background-clip: text) or (-webkit-background-clip: text) {
97
+ @media (prefers-reduced-motion: no-preference) {
98
+ background:
99
+ linear-gradient(
100
+ 90deg,
101
+ transparent 0%,
102
+ transparent 40%,
103
+ var(--search-ai-activity-shimmer-color) 50%,
104
+ transparent 60%,
105
+ transparent 100%
106
+ )
107
+ 100% 0 / 250% 100% no-repeat,
108
+ linear-gradient(currentColor, currentColor);
109
+ -webkit-background-clip: text;
110
+ background-clip: text;
111
+ -webkit-text-fill-color: transparent;
112
+ animation:
113
+ ${labelIn} var(--search-ai-activity-fade-duration) ease-out,
114
+ ${shimmerSweep} var(--search-ai-activity-shimmer-duration) linear
115
+ infinite;
116
+ }
117
+ }
118
+ `;
119
+ const SearchAiActivityWrapper = styled_components_1.default.div `
120
+ font-size: var(--search-ai-activity-font-size);
121
+ line-height: var(--search-ai-activity-line-height);
122
+ color: var(--search-ai-activity-text-color);
123
+ `;
124
+ const headerStyles = (0, styled_components_1.css) `
125
+ display: flex;
126
+ align-items: center;
127
+ gap: var(--spacing-xs);
128
+ max-width: 100%;
129
+ min-height: var(--search-ai-activity-line-height);
130
+ white-space: nowrap;
131
+ `;
132
+ const HeaderWrapper = styled_components_1.default.div `
133
+ ${headerStyles}
134
+ `;
135
+ const HeaderButton = styled_components_1.default.button `
136
+ ${headerStyles}
137
+ margin: 0;
138
+ padding: 0;
139
+ border: 0;
140
+ border-radius: var(--border-radius);
141
+ background: none;
142
+ font: inherit;
143
+ color: inherit;
144
+ text-align: start;
145
+ cursor: pointer;
146
+ transition: color var(--search-ai-activity-fade-duration) ease-out;
147
+
148
+ &:hover {
149
+ color: var(--search-ai-activity-text-color-hover);
150
+ }
151
+
152
+ &:focus-visible {
153
+ outline: 2px solid var(--button-border-color-focused);
154
+ outline-offset: 2px;
155
+ }
156
+ `;
157
+ const IconWrapper = styled_components_1.default.span `
158
+ display: inline-flex;
159
+ flex-shrink: 0;
160
+ width: var(--search-ai-activity-icon-size);
161
+ height: var(--search-ai-activity-icon-size);
162
+ `;
163
+ const StatusLabel = styled_components_1.default.span `
164
+ min-width: 0;
165
+ overflow: hidden;
166
+ text-overflow: ellipsis;
167
+ font-weight: var(--font-weight-regular);
168
+ animation: ${labelIn} var(--search-ai-activity-fade-duration) ease-out;
169
+
170
+ @media (prefers-reduced-motion: reduce) {
171
+ animation: none;
172
+ }
173
+
174
+ ${({ $isRunning }) => $isRunning && shimmer}
175
+ `;
176
+ const StatusDetail = styled_components_1.default.span `
177
+ color: var(--search-ai-activity-detail-color);
178
+ `;
179
+ const ToolCallCount = styled_components_1.default.span `
180
+ flex-shrink: 0;
181
+ min-width: calc(var(--search-ai-activity-line-height) - 4px);
182
+ padding: 0 var(--spacing-xxs);
183
+ border-radius: calc(var(--search-ai-activity-line-height) / 2);
184
+ background: var(--search-ai-activity-count-bg-color);
185
+ font-size: var(--search-ai-activity-count-font-size);
186
+ line-height: calc(var(--search-ai-activity-line-height) - 4px);
187
+ font-variant-numeric: tabular-nums;
188
+ text-align: center;
189
+ `;
190
+ const ExpandIcon = (0, styled_components_1.default)(ChevronDownIcon_1.ChevronDownIcon) `
191
+ flex-shrink: 0;
192
+ margin-inline-start: calc(var(--spacing-xxs) * -1);
193
+ transform: rotate(${({ $isExpanded }) => ($isExpanded ? '180deg' : '0deg')});
194
+ transition: transform var(--search-ai-activity-expand-duration)
195
+ var(--search-ai-activity-easing);
196
+
197
+ @media (prefers-reduced-motion: reduce) {
198
+ transition: none;
199
+ }
200
+ `;
201
+ const StepsWrapper = styled_components_1.default.div `
202
+ display: grid;
203
+ grid-template-rows: ${({ $isExpanded }) => ($isExpanded ? '1fr' : '0fr')};
204
+ opacity: ${({ $isExpanded }) => ($isExpanded ? 1 : 0)};
205
+ visibility: ${({ $isExpanded }) => ($isExpanded ? 'visible' : 'hidden')};
206
+ ${({ $isExpanded }) => !$isExpanded &&
207
+ (0, styled_components_1.css) `
208
+ contain: inline-size;
209
+ `}
210
+ transition:
211
+ grid-template-rows var(--search-ai-activity-expand-duration) var(--search-ai-activity-easing),
212
+ opacity var(--search-ai-activity-expand-duration) ease-out,
213
+ visibility var(--search-ai-activity-expand-duration);
214
+
215
+ @media (prefers-reduced-motion: reduce) {
216
+ transition: none;
217
+ }
218
+ `;
219
+ const STEP_GAP = 'var(--spacing-xs)';
220
+ const ICON_OFFSET = 'calc((var(--search-ai-activity-line-height) - var(--search-ai-activity-icon-size)) / 2)';
221
+ const RAIL_GAP = '3px';
222
+ const RAIL_INSET = 'calc(var(--search-ai-activity-icon-size) / 2 - 0.5px)';
223
+ const StepList = styled_components_1.default.ol `
224
+ min-height: 0;
225
+ overflow: hidden;
226
+ margin: 0;
227
+ padding: 0;
228
+ list-style: none;
229
+
230
+ /* Padding would stay visible while the list is collapsed; the spacer collapses with it. */
231
+ &::before {
232
+ content: "";
233
+ display: block;
234
+ height: ${STEP_GAP};
235
+ }
236
+ `;
237
+ const StepItem = styled_components_1.default.li `
238
+ position: relative;
239
+ display: flex;
240
+ gap: var(--spacing-xs);
241
+ padding-bottom: ${STEP_GAP};
242
+ ${styles_1.searchAiAppearAnimation}
243
+
244
+ &:last-child {
245
+ padding-bottom: 0;
246
+ }
247
+
248
+ &:first-child::before,
249
+ &:not(:last-child)::after {
250
+ content: "";
251
+ position: absolute;
252
+ inset-inline-start: ${RAIL_INSET};
253
+ width: 1px;
254
+ background: var(--search-ai-activity-rail-color);
255
+ }
256
+
257
+ &:first-child::before {
258
+ top: calc(${STEP_GAP} * -1 - ${ICON_OFFSET} + ${RAIL_GAP});
259
+ height: calc(${STEP_GAP} + ${ICON_OFFSET} * 2 - ${RAIL_GAP} * 2);
260
+ }
261
+
262
+ &:not(:last-child)::after {
263
+ top: calc(
264
+ ${ICON_OFFSET} + var(--search-ai-activity-icon-size) + ${RAIL_GAP}
265
+ );
266
+ bottom: calc(${RAIL_GAP} - ${ICON_OFFSET});
267
+ }
268
+ `;
269
+ const StepIconWrapper = (0, styled_components_1.default)(IconWrapper) `
270
+ margin-top: ${ICON_OFFSET};
271
+ `;
272
+ const StepText = styled_components_1.default.span `
273
+ min-width: 0;
274
+ overflow-wrap: anywhere;
275
+
276
+ ${({ $isRunning }) => $isRunning && shimmer}
277
+ `;
278
+ //# sourceMappingURL=SearchAiActivity.js.map
@@ -99,6 +99,10 @@ function SearchAiDialog({ isGeneratingResponse, response, initialMessage, error,
99
99
  }
100
100
  // oxlint-disable-next-line react-you-might-not-need-an-effect/no-pass-data-to-parent -- merges the streamed assistant response into the lifted conversation state as chunks arrive
101
101
  setConversation((prev) => {
102
+ // A reset can land between this effect's render and its update.
103
+ if (prev.length === 0) {
104
+ return prev;
105
+ }
102
106
  const lastMessage = prev[prev.length - 1];
103
107
  const content = response || '';
104
108
  if (lastMessage && lastMessage.role === constants_1.AiSearchConversationRole.ASSISTANT) {
@@ -153,7 +157,7 @@ function SearchAiDialog({ isGeneratingResponse, response, initialMessage, error,
153
157
  react_1.default.createElement(ConversationWrapper, null,
154
158
  conversation.map((item, index) => (react_1.default.createElement(SearchAiMessage_1.SearchAiMessage, { key: `search-ai-message-${index}`, role: item.role, content: item.content, isThinking: item.role === constants_1.AiSearchConversationRole.ASSISTANT &&
155
159
  isGeneratingResponse &&
156
- index === conversation.length - 1, resources: item.resources, messageId: item.messageId, feedback: item.feedback, onFeedbackChange: handleFeedbackChange, toolCalls: item.toolCalls || (index === conversation.length - 1 ? toolCalls : undefined), contentSegments: item.contentSegments }))),
160
+ index === conversation.length - 1, resources: item.resources, messageId: item.messageId, feedback: item.feedback, onFeedbackChange: handleFeedbackChange, contentSegments: item.contentSegments }))),
157
161
  error && (react_1.default.createElement(Admonition_1.Admonition, { type: "danger", name: translate(constants_1.AI_SEARCH_ERROR_CONFIG[error].headerKey, constants_1.AI_SEARCH_ERROR_CONFIG[error].headerDefault) }, translate(constants_1.AI_SEARCH_ERROR_CONFIG[error].messageKey, constants_1.AI_SEARCH_ERROR_CONFIG[error].messageDefault))),
158
162
  !conversation.length && !error && (react_1.default.createElement(SuggestionsWrapper, null, suggestions === null || suggestions === void 0 ? void 0 : suggestions.map((suggestion) => (react_1.default.createElement(Button_1.Button, { key: suggestion, variant: "outlined", onClick: () => handleOnMessageSent(suggestion) }, suggestion))))),
159
163
  react_1.default.createElement("div", { ref: conversationEndRef })),
@@ -10,9 +10,10 @@ export type SearchAiMessageProps = {
10
10
  messageId?: string;
11
11
  feedback?: FeedbackType;
12
12
  onFeedbackChange: (messageId: string, feedback: FeedbackType | undefined) => void;
13
+ /** @deprecated Not used; the message reads its tool calls from `contentSegments`. */
13
14
  toolCalls?: ToolCall[];
14
15
  contentSegments?: ContentSegment[];
15
16
  };
16
- declare function SearchAiMessageComponent({ role, content, isThinking, resources, className, messageId, feedback, onFeedbackChange, toolCalls, contentSegments, }: SearchAiMessageProps): JSX.Element;
17
+ declare function SearchAiMessageComponent({ role, content, isThinking, resources, className, messageId, feedback, onFeedbackChange, contentSegments, }: SearchAiMessageProps): JSX.Element;
17
18
  export declare const SearchAiMessage: React.MemoExoticComponent<typeof SearchAiMessageComponent>;
18
19
  export {};