@lobehub/lobehub 2.0.0-next.297 → 2.0.0-next.299

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 (88) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/changelog/v1.json +18 -0
  3. package/package.json +2 -2
  4. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/Actions.tsx +4 -13
  5. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/Actions.tsx +4 -13
  6. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/index.tsx +2 -9
  7. package/src/app/[variants]/(main)/agent/features/Conversation/ConversationArea.tsx +2 -2
  8. package/src/app/[variants]/(main)/agent/features/Conversation/Header/Tags/KnowledgeTag.tsx +3 -4
  9. package/src/app/[variants]/(main)/agent/profile/features/ProfileEditor/MentionList/types.ts +4 -2
  10. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/Nav.tsx +0 -1
  11. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/Related/index.tsx +9 -5
  12. package/src/app/[variants]/(main)/community/(detail)/model/features/Sidebar/ActionButton/ChatWithModel.tsx +3 -8
  13. package/src/app/[variants]/(main)/community/(list)/assistant/features/MarketSourceSwitch.tsx +44 -23
  14. package/src/app/[variants]/(main)/community/(list)/features/SortButton/index.tsx +40 -19
  15. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/Actions.tsx +4 -13
  16. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/index.tsx +2 -9
  17. package/src/app/[variants]/(main)/group/features/Conversation/ConversationArea.tsx +2 -2
  18. package/src/app/[variants]/(main)/group/features/Conversation/Header/Tags/KnowledgeTag.tsx +3 -4
  19. package/src/app/[variants]/(main)/group/profile/features/AgentBuilder/AgentBuilderProvider.tsx +2 -2
  20. package/src/app/[variants]/(main)/group/profile/features/MemberProfile/MentionList/types.ts +4 -2
  21. package/src/app/[variants]/(main)/home/_layout/Body/Agent/Actions.tsx +3 -11
  22. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/Group/Actions.tsx +3 -12
  23. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/Group/Item.tsx +2 -9
  24. package/src/app/[variants]/(main)/home/_layout/Body/Agent/index.tsx +2 -9
  25. package/src/app/[variants]/(main)/home/_layout/Body/Project/index.tsx +2 -9
  26. package/src/app/[variants]/(main)/home/_layout/HomeAgentIdSync.tsx +23 -0
  27. package/src/app/[variants]/(main)/home/_layout/index.tsx +2 -0
  28. package/src/app/[variants]/(main)/home/features/CommunityAgents/index.tsx +11 -13
  29. package/src/app/[variants]/(main)/home/features/FeaturedPlugins/index.tsx +11 -13
  30. package/src/app/[variants]/(main)/home/features/RecentPage/index.tsx +12 -14
  31. package/src/app/[variants]/(main)/home/features/RecentResource/index.tsx +12 -14
  32. package/src/app/[variants]/(main)/memory/contexts/features/ContextDropdown.tsx +5 -3
  33. package/src/app/[variants]/(main)/memory/experiences/features/ExperienceDropdown.tsx +5 -3
  34. package/src/app/[variants]/(main)/memory/identities/features/IdentityDropdown.tsx +5 -3
  35. package/src/app/[variants]/(main)/memory/preferences/features/PreferenceDropdown.tsx +5 -3
  36. package/src/app/[variants]/(main)/page/_layout/Body/Actions.tsx +3 -13
  37. package/src/app/[variants]/(main)/page/_layout/Body/index.tsx +2 -9
  38. package/src/app/[variants]/(main)/resource/features/DndContextWrapper.tsx +1 -1
  39. package/src/app/[variants]/(main)/settings/profile/features/SSOProvidersList/index.tsx +3 -3
  40. package/src/app/[variants]/(main)/settings/provider/ProviderMenu/Actions.tsx +3 -11
  41. package/src/app/[variants]/(main)/settings/provider/ProviderMenu/List.tsx +12 -28
  42. package/src/app/[variants]/(main)/settings/provider/features/ModelList/DisabledModels.tsx +7 -8
  43. package/src/app/[variants]/(main)/settings/provider/features/ModelList/ModelTitle/index.tsx +18 -20
  44. package/src/app/[variants]/(mobile)/(home)/features/SessionListContent/CollapseGroup/Actions.tsx +10 -14
  45. package/src/app/[variants]/(mobile)/(home)/features/SessionListContent/List/Item/Actions.tsx +3 -13
  46. package/src/app/[variants]/share/t/[id]/SharedMessageList.tsx +2 -2
  47. package/src/business/server/lambda-routers/file.ts +1 -1
  48. package/src/features/AgentBuilder/AgentBuilderProvider.tsx +2 -2
  49. package/src/features/ChatInput/ActionBar/History/index.tsx +1 -1
  50. package/src/features/ChatInput/ActionBar/STT/common.tsx +1 -1
  51. package/src/features/ChatInput/ActionBar/Search/index.tsx +1 -1
  52. package/src/features/ChatInput/ActionBar/Upload/ServerMode.tsx +1 -0
  53. package/src/features/ChatInput/ActionBar/components/Action.tsx +4 -8
  54. package/src/features/ChatInput/ActionBar/components/ActionDropdown.tsx +225 -37
  55. package/src/features/Conversation/ConversationProvider.tsx +2 -1
  56. package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +10 -6
  57. package/src/features/Conversation/Messages/AssistantGroup/Actions/index.tsx +10 -6
  58. package/src/features/Conversation/Messages/AssistantGroup/Tool/Detail/Intervention/ApprovalActions.tsx +11 -13
  59. package/src/features/Conversation/Messages/AssistantGroup/Tool/Detail/Intervention/ModeSelector.tsx +8 -10
  60. package/src/features/Conversation/Messages/Supervisor/Actions/index.tsx +10 -6
  61. package/src/features/Conversation/Messages/Task/Actions/index.tsx +10 -6
  62. package/src/features/Conversation/Messages/User/Actions/index.tsx +10 -6
  63. package/src/features/Conversation/StoreUpdater.tsx +1 -1
  64. package/src/features/Conversation/store/initialState.ts +3 -1
  65. package/src/features/Conversation/store/slices/data/action.ts +6 -5
  66. package/src/features/LibraryModal/AssignKnowledgeBase/Item/Action.tsx +23 -26
  67. package/src/features/ModelSwitchPanel/components/List/MultipleProvidersModelItem.tsx +16 -18
  68. package/src/features/ModelSwitchPanel/styles.ts +18 -1
  69. package/src/features/PageEditor/Copilot/AgentSelector/Actions.tsx +6 -13
  70. package/src/features/PageEditor/PageAgentProvider.tsx +2 -2
  71. package/src/features/PluginStore/InstalledList/List/Item/Action.tsx +33 -36
  72. package/src/features/PluginStore/McpList/List/Action.tsx +25 -28
  73. package/src/features/PluginStore/PluginList/List/Action.tsx +25 -28
  74. package/src/features/PluginTag/index.tsx +3 -4
  75. package/src/features/Portal/Artifacts/Body/Renderer/SVG.tsx +14 -11
  76. package/src/features/Portal/Thread/Chat/index.tsx +2 -2
  77. package/src/features/ProfileEditor/AgentTool.tsx +1 -1
  78. package/src/features/ResourceManager/components/Explorer/ToolBar/SortDropdown.tsx +21 -18
  79. package/src/features/ResourceManager/components/Explorer/ToolBar/ViewSwitcher.tsx +7 -13
  80. package/src/features/ResourceManager/components/Header/AddButton.tsx +4 -11
  81. package/src/features/User/UserPanel/LangButton.tsx +56 -44
  82. package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +1 -1
  83. package/src/services/discover.ts +6 -4
  84. package/src/services/document/index.ts +11 -1
  85. package/src/store/page/slices/crud/action.ts +0 -48
  86. package/src/store/tool/slices/lobehubSkillStore/action.ts +1 -2
  87. package/src/styles/global.ts +2 -2
  88. package/src/types/shim-lobe-ui.d.ts +7 -0
@@ -1,4 +1,4 @@
1
- import { ActionIcon, Dropdown, Flexbox, Icon, Text, TooltipGroup } from '@lobehub/ui';
1
+ import { ActionIcon, DropdownMenu, Flexbox, Icon, Text, TooltipGroup } from '@lobehub/ui';
2
2
  import type { ItemType } from 'antd/es/menu/interface';
3
3
  import isEqual from 'fast-deep-equal';
4
4
  import { ArrowDownUpIcon, LucideCheck } from 'lucide-react';
@@ -240,9 +240,9 @@ const DisabledModels = memo<DisabledModelsProps>(({ activeTab, providerId }) =>
240
240
  {t('providerModels.list.disabled')}
241
241
  </Text>
242
242
  {sourceDisabledModels.length > 1 && (
243
- <Dropdown
244
- menu={{
245
- items: [
243
+ <DropdownMenu
244
+ items={
245
+ [
246
246
  {
247
247
  icon: sortType === SortType.Default ? <Icon icon={LucideCheck} /> : <div />,
248
248
  key: 'default',
@@ -286,16 +286,15 @@ const DisabledModels = memo<DisabledModelsProps>(({ activeTab, providerId }) =>
286
286
  label: t('providerModels.list.disabledActions.sortReleasedAtDesc'),
287
287
  onClick: () => updateSortType(SortType.ReleasedAtDesc),
288
288
  },
289
- ] as ItemType[],
290
- }}
291
- trigger={['click']}
289
+ ] as ItemType[]
290
+ }
292
291
  >
293
292
  <ActionIcon
294
293
  icon={ArrowDownUpIcon}
295
294
  size={'small'}
296
295
  title={t('providerModels.list.disabledActions.sort')}
297
296
  />
298
- </Dropdown>
297
+ </DropdownMenu>
299
298
  )}
300
299
  </Flexbox>
301
300
  <TooltipGroup>
@@ -1,4 +1,4 @@
1
- import { ActionIcon, Button, Dropdown, Flexbox, Skeleton, Text } from '@lobehub/ui';
1
+ import { ActionIcon, Button, DropdownMenu, Flexbox, Skeleton, Text } from '@lobehub/ui';
2
2
  import { App, Space } from 'antd';
3
3
  import { cssVar } from 'antd-style';
4
4
  import { CircleX, EllipsisVertical, LucideRefreshCcwDot, PlusIcon } from 'lucide-react';
@@ -137,28 +137,26 @@ const ModelTitle = memo<ModelFetcherProps>(
137
137
  <CreateNewModelModal open={showModal} setOpen={setShowModal} />
138
138
  </>
139
139
  )}
140
- <Dropdown
141
- menu={{
142
- items: [
143
- {
144
- key: 'reset',
145
- label: t('providerModels.list.resetAll.title'),
146
- onClick: async () => {
147
- modal.confirm({
148
- content: t('providerModels.list.resetAll.conform'),
149
- onOk: async () => {
150
- await clearModelsByProvider(provider);
151
- message.success(t('providerModels.list.resetAll.success'));
152
- },
153
- title: t('providerModels.list.resetAll.title'),
154
- });
155
- },
140
+ <DropdownMenu
141
+ items={[
142
+ {
143
+ key: 'reset',
144
+ label: t('providerModels.list.resetAll.title'),
145
+ onClick: async () => {
146
+ modal.confirm({
147
+ content: t('providerModels.list.resetAll.conform'),
148
+ onOk: async () => {
149
+ await clearModelsByProvider(provider);
150
+ message.success(t('providerModels.list.resetAll.success'));
151
+ },
152
+ title: t('providerModels.list.resetAll.title'),
153
+ });
156
154
  },
157
- ],
158
- }}
155
+ },
156
+ ]}
159
157
  >
160
158
  <Button icon={EllipsisVertical} size={'small'} />
161
- </Dropdown>
159
+ </DropdownMenu>
162
160
  </Space.Compact>
163
161
  </Flexbox>
164
162
  )}
@@ -1,4 +1,10 @@
1
- import { ActionIcon, Dropdown, type DropdownProps, Icon, type MenuProps } from '@lobehub/ui';
1
+ import {
2
+ ActionIcon,
3
+ DropdownMenu,
4
+ type DropdownMenuProps,
5
+ Icon,
6
+ type MenuProps,
7
+ } from '@lobehub/ui';
2
8
  import { App } from 'antd';
3
9
  import { createStaticStyles } from 'antd-style';
4
10
  import { MoreVertical, PencilLine, Plus, Settings2, Trash, UsersRound } from 'lucide-react';
@@ -15,7 +21,7 @@ const styles = createStaticStyles(({ css }) => ({
15
21
  z-index: 2000;
16
22
  `,
17
23
  }));
18
- interface ActionsProps extends Pick<DropdownProps, 'onOpenChange'> {
24
+ interface ActionsProps extends Pick<DropdownMenuProps, 'onOpenChange'> {
19
25
  id?: string;
20
26
  isCustomGroup?: boolean;
21
27
  isPinned?: boolean;
@@ -170,17 +176,7 @@ const Actions = memo<ActionsProps>(
170
176
 
171
177
  return (
172
178
  <>
173
- <Dropdown
174
- arrow={false}
175
- menu={{
176
- items: menuItems,
177
- onClick: ({ domEvent }) => {
178
- domEvent.stopPropagation();
179
- },
180
- }}
181
- onOpenChange={onOpenChange}
182
- trigger={['click']}
183
- >
179
+ <DropdownMenu items={menuItems} onOpenChange={onOpenChange}>
184
180
  <ActionIcon
185
181
  active={isMobile ? true : false}
186
182
  icon={MoreVertical}
@@ -191,7 +187,7 @@ const Actions = memo<ActionsProps>(
191
187
  size={{ blockSize: 22, size: 16 }}
192
188
  style={{ background: isMobile ? 'transparent' : '', marginRight: -8 }}
193
189
  />
194
- </Dropdown>
190
+ </DropdownMenu>
195
191
 
196
192
  <MemberSelectionModal
197
193
  mode="create"
@@ -1,4 +1,4 @@
1
- import { ActionIcon, Dropdown, Icon } from '@lobehub/ui';
1
+ import { ActionIcon, DropdownMenu, Icon } from '@lobehub/ui';
2
2
  import { App } from 'antd';
3
3
  import { createStaticStyles } from 'antd-style';
4
4
  import { type ItemType } from 'antd/es/menu/interface';
@@ -183,17 +183,7 @@ const Actions = memo<ActionProps>(({ group, id, openCreateGroupModal, parentType
183
183
  );
184
184
 
185
185
  return (
186
- <Dropdown
187
- arrow={false}
188
- menu={{
189
- items,
190
- onClick: ({ domEvent }) => {
191
- domEvent.stopPropagation();
192
- },
193
- }}
194
- onOpenChange={setOpen}
195
- trigger={['click']}
196
- >
186
+ <DropdownMenu items={items} onOpenChange={setOpen}>
197
187
  <ActionIcon
198
188
  icon={MoreVertical}
199
189
  size={{
@@ -201,7 +191,7 @@ const Actions = memo<ActionProps>(({ group, id, openCreateGroupModal, parentType
201
191
  size: 16,
202
192
  }}
203
193
  />
204
- </Dropdown>
194
+ </DropdownMenu>
205
195
  );
206
196
  });
207
197
 
@@ -40,8 +40,8 @@ const SharedMessageList = memo<SharedMessageListProps>(({ agentId, groupId, shar
40
40
  context={context}
41
41
  hasInitMessages={!!messages}
42
42
  messages={messages}
43
- onMessagesChange={(messages) => {
44
- replaceMessages(messages, { context });
43
+ onMessagesChange={(messages, ctx) => {
44
+ replaceMessages(messages, { context: ctx });
45
45
  }}
46
46
  >
47
47
  <Flexbox flex={1}>
@@ -8,5 +8,5 @@ export interface BusinessFileUploadCheckParams {
8
8
 
9
9
  export async function businessFileUploadCheck(
10
10
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
11
- params: BusinessFileUploadCheckParams,
11
+ _params: BusinessFileUploadCheckParams,
12
12
  ): Promise<void> {}
@@ -46,8 +46,8 @@ const AgentBuilderProvider = memo<AgentBuilderProviderProps>(({ agentId, childre
46
46
  context={context}
47
47
  hasInitMessages={!!messages}
48
48
  messages={messages}
49
- onMessagesChange={(msgs) => {
50
- replaceMessages(msgs, { context });
49
+ onMessagesChange={(msgs, ctx) => {
50
+ replaceMessages(msgs, { context: ctx });
51
51
  }}
52
52
  operationState={operationState}
53
53
  >
@@ -55,7 +55,7 @@ const History = memo(() => {
55
55
  popover={{
56
56
  content: <Controls setUpdating={setUpdating} updating={updating} />,
57
57
  minWidth: 240,
58
- trigger: isMobile ? ['click'] : ['hover'],
58
+ trigger: isMobile ? 'click' : 'hover',
59
59
  }}
60
60
  showTooltip={false}
61
61
  title={title}
@@ -104,7 +104,7 @@ const CommonSTT = memo<{
104
104
  />
105
105
  )
106
106
  : undefined,
107
- trigger: ['click'],
107
+ trigger: 'click',
108
108
  }}
109
109
  icon={isLoading ? MicOff : Mic}
110
110
  onClick={handleTriggerStartStop}
@@ -51,7 +51,7 @@ const Search = memo(() => {
51
51
  padding: 4,
52
52
  },
53
53
  },
54
- trigger: isMobile ? ['click'] : ['hover'],
54
+ trigger: isMobile ? 'click' : 'hover',
55
55
  }}
56
56
  showTooltip={false}
57
57
  title={t('search.title')}
@@ -231,6 +231,7 @@ const FileUpload = memo(() => {
231
231
  loading={updating}
232
232
  showTooltip={false}
233
233
  title={t('upload.action.tooltip')}
234
+ trigger={'both'}
234
235
  />
235
236
  );
236
237
 
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { ActionIcon, type ActionIconProps } from '@lobehub/ui';
3
+ import { ActionIcon, type ActionIconProps, type PopoverTrigger } from '@lobehub/ui';
4
4
  import { isUndefined } from 'es-toolkit/compat';
5
5
  import { memo } from 'react';
6
6
  import useMergeState from 'use-merge-value';
@@ -12,12 +12,12 @@ import ActionDropdown, { type ActionDropdownProps } from './ActionDropdown';
12
12
  import ActionPopover, { type ActionPopoverProps } from './ActionPopover';
13
13
 
14
14
  interface ActionProps extends Omit<ActionIconProps, 'popover'> {
15
- dropdown?: ActionDropdownProps;
15
+ dropdown?: Omit<ActionDropdownProps, 'children'>;
16
16
  onOpenChange?: (open: boolean) => void;
17
17
  open?: boolean;
18
18
  popover?: ActionPopoverProps;
19
19
  showTooltip?: boolean;
20
- trigger?: ActionDropdownProps['trigger'];
20
+ trigger?: PopoverTrigger;
21
21
  }
22
22
 
23
23
  const Action = memo<ActionProps>(
@@ -84,11 +84,7 @@ const Action = memo<ActionProps>(
84
84
  <ActionPopover
85
85
  onOpenChange={setShow}
86
86
  open={show}
87
- trigger={
88
- Array.isArray(trigger)
89
- ? (trigger.filter((t) => t !== 'contextMenu') as ('click' | 'hover')[])
90
- : trigger
91
- }
87
+ trigger={trigger}
92
88
  {...popover}
93
89
  minWidth={mobile ? '100%' : popover.minWidth}
94
90
  placement={mobile ? 'top' : (dropdownPlacement ?? popover.placement)}
@@ -1,68 +1,256 @@
1
1
  'use client';
2
2
 
3
- import { Dropdown, type DropdownProps } from '@lobehub/ui';
3
+ import {
4
+ DropdownMenuPopup,
5
+ type DropdownMenuPopupProps,
6
+ DropdownMenuPortal,
7
+ DropdownMenuPositioner,
8
+ type DropdownMenuProps,
9
+ DropdownMenuRoot,
10
+ DropdownMenuTrigger,
11
+ type MenuProps,
12
+ type PopoverTrigger,
13
+ renderDropdownMenuItems,
14
+ } from '@lobehub/ui';
4
15
  import { createStaticStyles, cx } from 'antd-style';
5
- import { memo } from 'react';
16
+ import {
17
+ type CSSProperties,
18
+ type ReactNode,
19
+ memo,
20
+ useCallback,
21
+ useEffect,
22
+ useMemo,
23
+ useRef,
24
+ useState,
25
+ } from 'react';
6
26
 
7
27
  import { useIsMobile } from '@/hooks/useIsMobile';
8
28
 
9
- const prefixCls = 'ant';
10
-
11
29
  const styles = createStaticStyles(({ css }) => ({
12
30
  dropdownMenu: css`
13
- &.${prefixCls}-dropdown-menu {
14
- .${prefixCls}-dropdown-menu-item-group-list {
15
- margin: 0;
16
- }
17
- .${prefixCls}-avatar {
18
- margin-inline-end: var(--ant-margin-xs);
19
- }
31
+ .ant-avatar {
32
+ margin-inline-end: var(--ant-margin-xs);
20
33
  }
21
34
  `,
22
35
  }));
23
36
 
24
- export interface ActionDropdownProps extends DropdownProps {
37
+ type ActionDropdownMenu = Omit<Pick<MenuProps, 'className' | 'onClick' | 'style'>, 'items'> & {
38
+ items: MenuProps['items'] | (() => MenuProps['items']);
39
+ };
40
+
41
+ export interface ActionDropdownProps extends Omit<DropdownMenuProps, 'items'> {
25
42
  maxHeight?: number | string;
26
43
  maxWidth?: number | string;
44
+ menu: ActionDropdownMenu;
27
45
  minHeight?: number | string;
28
46
  minWidth?: number | string;
47
+ popupRender?: (menu: ReactNode) => ReactNode;
29
48
  /**
30
49
  * 是否在挂载时预渲染弹层,避免首次触发展开时的渲染卡顿
31
50
  */
32
51
  prefetch?: boolean;
52
+ trigger?: PopoverTrigger;
33
53
  }
34
54
 
35
55
  const ActionDropdown = memo<ActionDropdownProps>(
36
- ({ menu, maxHeight, minWidth, maxWidth, children, placement = 'top', minHeight, ...rest }) => {
56
+ ({
57
+ children,
58
+ defaultOpen,
59
+ menu,
60
+ trigger,
61
+ maxHeight,
62
+ maxWidth,
63
+ minHeight,
64
+ minWidth,
65
+ onOpenChange,
66
+ onOpenChangeComplete,
67
+ open,
68
+ placement = 'top',
69
+ popupProps,
70
+ popupRender,
71
+ portalProps,
72
+ positionerProps,
73
+ prefetch,
74
+
75
+ triggerProps,
76
+ ...rest
77
+ }) => {
37
78
  const isMobile = useIsMobile();
79
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(Boolean(defaultOpen));
80
+ const menuItemsRef = useRef<ReactNode[] | null>(null);
81
+
82
+ useEffect(() => {
83
+ if (open === undefined) return;
84
+ setUncontrolledOpen(open);
85
+ }, [open]);
86
+
87
+ const handleOpenChange = useCallback(
88
+ (nextOpen: boolean, details: Parameters<NonNullable<typeof onOpenChange>>[1]) => {
89
+ onOpenChange?.(nextOpen, details);
90
+ if (open === undefined) setUncontrolledOpen(nextOpen);
91
+ },
92
+ [onOpenChange, open],
93
+ );
94
+
95
+ const handleOpenChangeComplete = useCallback(
96
+ (nextOpen: boolean) => {
97
+ onOpenChangeComplete?.(nextOpen);
98
+ if (!nextOpen) menuItemsRef.current = null;
99
+ },
100
+ [onOpenChangeComplete],
101
+ );
102
+
103
+ const isOpen = open ?? uncontrolledOpen;
104
+ const openOnHover = useMemo(() => {
105
+ if (!trigger) return undefined;
106
+ if (trigger === 'both') return true;
107
+ if (Array.isArray(trigger)) return trigger.includes('hover');
108
+ return trigger === 'hover';
109
+ }, [trigger]);
110
+ const resolvedTriggerProps = useMemo(() => {
111
+ if (openOnHover === undefined) return triggerProps;
112
+ return {
113
+ ...triggerProps,
114
+ openOnHover,
115
+ };
116
+ }, [openOnHover, triggerProps]);
117
+
118
+ const decorateMenuItems = useCallback(
119
+ (items: MenuProps['items']): MenuProps['items'] => {
120
+ if (!items) return items;
121
+
122
+ return items.map((item) => {
123
+ if (!item) return item;
124
+ if ('type' in item && item.type === 'divider') return item;
125
+ if ('type' in item && item.type === 'group') {
126
+ return {
127
+ ...item,
128
+ children: item.children ? decorateMenuItems(item.children) : item.children,
129
+ };
130
+ }
131
+
132
+ if ('children' in item && item.children) {
133
+ return {
134
+ ...item,
135
+ children: decorateMenuItems(item.children),
136
+ };
137
+ }
138
+ const itemOnClick = 'onClick' in item ? item.onClick : undefined;
139
+
140
+ return {
141
+ ...item,
142
+ onClick: (info) => {
143
+ info.domEvent.preventDefault();
144
+ menu.onClick?.(info);
145
+ itemOnClick?.(info);
146
+ },
147
+ };
148
+ });
149
+ },
150
+ [menu],
151
+ );
152
+
153
+ const renderedItems = useMemo(() => {
154
+ if (!prefetch && !isOpen) return menuItemsRef.current;
155
+ const sourceItems = typeof menu.items === 'function' ? menu.items() : menu.items;
156
+ const nextItems = renderDropdownMenuItems(decorateMenuItems(sourceItems ?? []));
157
+
158
+ menuItemsRef.current = nextItems;
159
+
160
+ return nextItems;
161
+ }, [decorateMenuItems, isOpen, menu.items, prefetch]);
162
+
163
+ const menuContent = useMemo(() => {
164
+ if (!popupRender) return renderedItems;
165
+ return popupRender(renderedItems ?? null);
166
+ }, [popupRender, renderedItems]);
167
+
168
+ const resolvedPopupClassName = useMemo<DropdownMenuPopupProps['className']>(() => {
169
+ const popupClassName = popupProps?.className;
170
+ if (typeof popupClassName === 'function') {
171
+ return (state) => cx(styles.dropdownMenu, menu.className, popupClassName(state));
172
+ }
173
+ return cx(styles.dropdownMenu, menu.className, popupClassName);
174
+ }, [menu.className, popupProps?.className]);
175
+
176
+ const resolvedPopupStyle = useMemo<DropdownMenuPopupProps['style']>(() => {
177
+ const baseStyle: CSSProperties = {
178
+ maxHeight,
179
+ maxWidth: isMobile ? undefined : maxWidth,
180
+ minHeight,
181
+ minWidth: isMobile ? undefined : minWidth,
182
+ overflowX: 'hidden',
183
+ overflowY: 'scroll',
184
+ width: isMobile ? '100vw' : undefined,
185
+ };
186
+ const popupStyle = popupProps?.style;
187
+
188
+ if (typeof popupStyle === 'function') {
189
+ return (state) => ({
190
+ ...baseStyle,
191
+ ...menu.style,
192
+ ...popupStyle(state),
193
+ });
194
+ }
195
+
196
+ return {
197
+ ...baseStyle,
198
+ ...menu.style,
199
+ ...popupStyle,
200
+ };
201
+ }, [isMobile, maxHeight, maxWidth, menu.style, minHeight, minWidth, popupProps?.style]);
202
+
203
+ const resolvedPopupProps = useMemo(() => {
204
+ if (!popupProps) {
205
+ return {
206
+ className: resolvedPopupClassName,
207
+ style: resolvedPopupStyle,
208
+ };
209
+ }
210
+
211
+ return {
212
+ ...popupProps,
213
+ className: resolvedPopupClassName,
214
+ style: resolvedPopupStyle,
215
+ };
216
+ }, [popupProps, resolvedPopupClassName, resolvedPopupStyle]);
217
+
218
+ const { container: portalContainer, ...restPortalProps } = portalProps ?? {};
219
+ const resolvedPortalContainer = useMemo<HTMLElement | null | undefined>(() => {
220
+ if (!portalContainer) return portalContainer ?? undefined;
221
+ if (typeof portalContainer === 'object' && 'current' in portalContainer) {
222
+ const current = portalContainer.current;
223
+ if (!current) return null;
224
+ if (typeof ShadowRoot !== 'undefined' && current instanceof ShadowRoot) {
225
+ return current.host as HTMLElement;
226
+ }
227
+ return current as HTMLElement;
228
+ }
229
+ if (typeof ShadowRoot !== 'undefined' && portalContainer instanceof ShadowRoot) {
230
+ return portalContainer.host as HTMLElement;
231
+ }
232
+ return portalContainer as HTMLElement;
233
+ }, [portalContainer]);
38
234
 
39
235
  return (
40
- <Dropdown
41
- arrow={false}
42
- destroyOnHidden={false}
43
- menu={{
44
- ...menu,
45
- className: cx(styles.dropdownMenu, menu.className),
46
- onClick: (e) => {
47
- e.domEvent.preventDefault();
48
- menu.onClick?.(e);
49
- },
50
- style: {
51
- maxHeight,
52
- maxWidth: isMobile ? undefined : maxWidth,
53
- minHeight,
54
- minWidth: isMobile ? undefined : minWidth,
55
- overflowX: 'hidden',
56
- overflowY: 'scroll',
57
- width: isMobile ? '100vw' : undefined,
58
- ...menu.style,
59
- },
60
- }}
61
- placement={isMobile ? 'top' : placement}
236
+ <DropdownMenuRoot
62
237
  {...rest}
238
+ defaultOpen={defaultOpen}
239
+ onOpenChange={handleOpenChange}
240
+ onOpenChangeComplete={handleOpenChangeComplete}
241
+ open={open}
63
242
  >
64
- {children}
65
- </Dropdown>
243
+ <DropdownMenuTrigger {...resolvedTriggerProps}>{children}</DropdownMenuTrigger>
244
+ <DropdownMenuPortal container={resolvedPortalContainer} {...restPortalProps}>
245
+ <DropdownMenuPositioner
246
+ {...positionerProps}
247
+ hoverTrigger={Boolean(resolvedTriggerProps?.openOnHover)}
248
+ placement={isMobile ? 'top' : placement}
249
+ >
250
+ <DropdownMenuPopup {...resolvedPopupProps}>{menuContent}</DropdownMenuPopup>
251
+ </DropdownMenuPositioner>
252
+ </DropdownMenuPortal>
253
+ </DropdownMenuRoot>
66
254
  );
67
255
  },
68
256
  );
@@ -42,8 +42,9 @@ export interface ConversationProviderProps {
42
42
  * Use this to sync messages back to external state (e.g., ChatStore)
43
43
  *
44
44
  * @param messages - The updated messages array
45
+ * @param context - The context that this data belongs to (prevents race conditions)
45
46
  */
46
- onMessagesChange?: (messages: UIChatMessage[]) => void;
47
+ onMessagesChange?: (messages: UIChatMessage[], context: ConversationContext) => void;
47
48
  /**
48
49
  * External operation state (from ChatStore)
49
50
  *
@@ -22,16 +22,20 @@ import { useAssistantActions } from './useAssistantActions';
22
22
  // Helper to strip handleClick from action items before passing to ActionIconGroup
23
23
  const stripHandleClick = (item: MessageActionItemOrDivider): ActionIconGroupItemType => {
24
24
  if ('type' in item && item.type === 'divider') return item as unknown as ActionIconGroupItemType;
25
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
26
- const { handleClick, children, ...rest } = item as MessageActionItem;
25
+ const { children, ...rest } = item as MessageActionItem;
26
+ const baseItem = { ...rest } as MessageActionItem;
27
+ delete (baseItem as { handleClick?: unknown }).handleClick;
27
28
  if (children) {
28
29
  return {
29
- ...rest,
30
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
31
- children: children.map(({ handleClick: _, ...child }) => child),
30
+ ...baseItem,
31
+ children: children.map((child) => {
32
+ const nextChild = { ...child } as MessageActionItem;
33
+ delete (nextChild as { handleClick?: unknown }).handleClick;
34
+ return nextChild;
35
+ }),
32
36
  } as ActionIconGroupItemType;
33
37
  }
34
- return rest as ActionIconGroupItemType;
38
+ return baseItem as ActionIconGroupItemType;
35
39
  };
36
40
 
37
41
  // Build action items map for handleAction lookup
@@ -21,16 +21,20 @@ import { useGroupActions } from './useGroupActions';
21
21
  // Helper to strip handleClick from action items before passing to ActionIconGroup
22
22
  const stripHandleClick = (item: MessageActionItemOrDivider): ActionIconGroupItemType => {
23
23
  if ('type' in item && item.type === 'divider') return item as unknown as ActionIconGroupItemType;
24
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
25
- const { handleClick, children, ...rest } = item as MessageActionItem;
24
+ const { children, ...rest } = item as MessageActionItem;
25
+ const baseItem = { ...rest } as MessageActionItem;
26
+ delete (baseItem as { handleClick?: unknown }).handleClick;
26
27
  if (children) {
27
28
  return {
28
- ...rest,
29
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
30
- children: children.map(({ handleClick: _, ...child }) => child),
29
+ ...baseItem,
30
+ children: children.map((child) => {
31
+ const nextChild = { ...child } as MessageActionItem;
32
+ delete (nextChild as { handleClick?: unknown }).handleClick;
33
+ return nextChild;
34
+ }),
31
35
  } as ActionIconGroupItemType;
32
36
  }
33
- return rest as ActionIconGroupItemType;
37
+ return baseItem as ActionIconGroupItemType;
34
38
  };
35
39
 
36
40
  // Build action items map for handleAction lookup