@lobehub/lobehub 2.0.0-next.298 → 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 (82) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/changelog/v1.json +9 -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)/model/features/Sidebar/ActionButton/ChatWithModel.tsx +3 -8
  11. package/src/app/[variants]/(main)/community/(list)/assistant/features/MarketSourceSwitch.tsx +44 -23
  12. package/src/app/[variants]/(main)/community/(list)/features/SortButton/index.tsx +40 -19
  13. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/Actions.tsx +4 -13
  14. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/index.tsx +2 -9
  15. package/src/app/[variants]/(main)/group/features/Conversation/ConversationArea.tsx +2 -2
  16. package/src/app/[variants]/(main)/group/features/Conversation/Header/Tags/KnowledgeTag.tsx +3 -4
  17. package/src/app/[variants]/(main)/group/profile/features/AgentBuilder/AgentBuilderProvider.tsx +2 -2
  18. package/src/app/[variants]/(main)/group/profile/features/MemberProfile/MentionList/types.ts +4 -2
  19. package/src/app/[variants]/(main)/home/_layout/Body/Agent/Actions.tsx +3 -11
  20. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/Group/Actions.tsx +3 -12
  21. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/Group/Item.tsx +2 -9
  22. package/src/app/[variants]/(main)/home/_layout/Body/Agent/index.tsx +2 -9
  23. package/src/app/[variants]/(main)/home/_layout/Body/Project/index.tsx +2 -9
  24. package/src/app/[variants]/(main)/home/features/CommunityAgents/index.tsx +11 -13
  25. package/src/app/[variants]/(main)/home/features/FeaturedPlugins/index.tsx +11 -13
  26. package/src/app/[variants]/(main)/home/features/RecentPage/index.tsx +12 -14
  27. package/src/app/[variants]/(main)/home/features/RecentResource/index.tsx +12 -14
  28. package/src/app/[variants]/(main)/memory/contexts/features/ContextDropdown.tsx +5 -3
  29. package/src/app/[variants]/(main)/memory/experiences/features/ExperienceDropdown.tsx +5 -3
  30. package/src/app/[variants]/(main)/memory/identities/features/IdentityDropdown.tsx +5 -3
  31. package/src/app/[variants]/(main)/memory/preferences/features/PreferenceDropdown.tsx +5 -3
  32. package/src/app/[variants]/(main)/page/_layout/Body/Actions.tsx +3 -13
  33. package/src/app/[variants]/(main)/page/_layout/Body/index.tsx +2 -9
  34. package/src/app/[variants]/(main)/resource/features/DndContextWrapper.tsx +1 -1
  35. package/src/app/[variants]/(main)/settings/profile/features/SSOProvidersList/index.tsx +3 -3
  36. package/src/app/[variants]/(main)/settings/provider/ProviderMenu/Actions.tsx +3 -11
  37. package/src/app/[variants]/(main)/settings/provider/ProviderMenu/List.tsx +12 -28
  38. package/src/app/[variants]/(main)/settings/provider/features/ModelList/DisabledModels.tsx +7 -8
  39. package/src/app/[variants]/(main)/settings/provider/features/ModelList/ModelTitle/index.tsx +18 -20
  40. package/src/app/[variants]/(mobile)/(home)/features/SessionListContent/CollapseGroup/Actions.tsx +10 -14
  41. package/src/app/[variants]/(mobile)/(home)/features/SessionListContent/List/Item/Actions.tsx +3 -13
  42. package/src/app/[variants]/share/t/[id]/SharedMessageList.tsx +2 -2
  43. package/src/business/server/lambda-routers/file.ts +1 -1
  44. package/src/features/AgentBuilder/AgentBuilderProvider.tsx +2 -2
  45. package/src/features/ChatInput/ActionBar/History/index.tsx +1 -1
  46. package/src/features/ChatInput/ActionBar/STT/common.tsx +1 -1
  47. package/src/features/ChatInput/ActionBar/Search/index.tsx +1 -1
  48. package/src/features/ChatInput/ActionBar/Upload/ServerMode.tsx +1 -0
  49. package/src/features/ChatInput/ActionBar/components/Action.tsx +4 -8
  50. package/src/features/ChatInput/ActionBar/components/ActionDropdown.tsx +225 -37
  51. package/src/features/Conversation/ConversationProvider.tsx +2 -1
  52. package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +10 -6
  53. package/src/features/Conversation/Messages/AssistantGroup/Actions/index.tsx +10 -6
  54. package/src/features/Conversation/Messages/AssistantGroup/Tool/Detail/Intervention/ApprovalActions.tsx +11 -13
  55. package/src/features/Conversation/Messages/AssistantGroup/Tool/Detail/Intervention/ModeSelector.tsx +8 -10
  56. package/src/features/Conversation/Messages/Supervisor/Actions/index.tsx +10 -6
  57. package/src/features/Conversation/Messages/Task/Actions/index.tsx +10 -6
  58. package/src/features/Conversation/Messages/User/Actions/index.tsx +10 -6
  59. package/src/features/Conversation/StoreUpdater.tsx +1 -1
  60. package/src/features/Conversation/store/initialState.ts +3 -1
  61. package/src/features/Conversation/store/slices/data/action.ts +6 -5
  62. package/src/features/LibraryModal/AssignKnowledgeBase/Item/Action.tsx +23 -26
  63. package/src/features/ModelSwitchPanel/components/List/MultipleProvidersModelItem.tsx +16 -18
  64. package/src/features/ModelSwitchPanel/styles.ts +18 -1
  65. package/src/features/PageEditor/Copilot/AgentSelector/Actions.tsx +6 -13
  66. package/src/features/PageEditor/PageAgentProvider.tsx +2 -2
  67. package/src/features/PluginStore/InstalledList/List/Item/Action.tsx +33 -36
  68. package/src/features/PluginStore/McpList/List/Action.tsx +25 -28
  69. package/src/features/PluginStore/PluginList/List/Action.tsx +25 -28
  70. package/src/features/PluginTag/index.tsx +3 -4
  71. package/src/features/Portal/Artifacts/Body/Renderer/SVG.tsx +14 -11
  72. package/src/features/Portal/Thread/Chat/index.tsx +2 -2
  73. package/src/features/ProfileEditor/AgentTool.tsx +1 -1
  74. package/src/features/ResourceManager/components/Explorer/ToolBar/SortDropdown.tsx +21 -18
  75. package/src/features/ResourceManager/components/Explorer/ToolBar/ViewSwitcher.tsx +7 -13
  76. package/src/features/ResourceManager/components/Header/AddButton.tsx +4 -11
  77. package/src/features/User/UserPanel/LangButton.tsx +56 -44
  78. package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +1 -1
  79. package/src/services/document/index.ts +11 -1
  80. package/src/store/page/slices/crud/action.ts +0 -48
  81. package/src/styles/global.ts +2 -2
  82. package/src/types/shim-lobe-ui.d.ts +7 -0
@@ -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
@@ -1,4 +1,4 @@
1
- import { Button, Dropdown, Flexbox, Popover } from '@lobehub/ui';
1
+ import { Button, DropdownMenu, Flexbox, Popover } from '@lobehub/ui';
2
2
  import { Input, Space } from 'antd';
3
3
  import { ChevronDown } from 'lucide-react';
4
4
  import { memo, useState } from 'react';
@@ -140,17 +140,15 @@ const ApprovalActions = memo<ApprovalActionsProps>(
140
140
  >
141
141
  {t('tool.intervention.approveAndRemember')}
142
142
  </Button>
143
- <Dropdown
144
- menu={{
145
- items: [
146
- {
147
- disabled: approveLoading || isMessageCreating,
148
- key: 'once',
149
- label: t('tool.intervention.approveOnce'),
150
- onClick: () => handleApprove(false),
151
- },
152
- ],
153
- }}
143
+ <DropdownMenu
144
+ items={[
145
+ {
146
+ disabled: approveLoading || isMessageCreating,
147
+ key: 'once',
148
+ label: t('tool.intervention.approveOnce'),
149
+ onClick: () => handleApprove(false),
150
+ },
151
+ ]}
154
152
  >
155
153
  <Button
156
154
  disabled={approveLoading || isMessageCreating}
@@ -158,7 +156,7 @@ const ApprovalActions = memo<ApprovalActionsProps>(
158
156
  size="small"
159
157
  type="primary"
160
158
  />
161
- </Dropdown>
159
+ </DropdownMenu>
162
160
  </Space.Compact>
163
161
  ) : (
164
162
  <Button
@@ -1,7 +1,6 @@
1
- import { Button, Dropdown, Icon, type MenuProps } from '@lobehub/ui';
2
- import { Center } from '@lobehub/ui';
1
+ import { Button, Center, DropdownMenu, Icon, type MenuProps } from '@lobehub/ui';
3
2
  import { createStaticStyles } from 'antd-style';
4
- import { ChevronDown, Hand, ListChecks, Zap } from 'lucide-react';
3
+ import { Check, ChevronDown, Hand, ListChecks, Zap } from 'lucide-react';
5
4
  import { memo, useCallback, useMemo } from 'react';
6
5
  import { useTranslation } from 'react-i18next';
7
6
 
@@ -61,6 +60,7 @@ const ModeSelector = memo(() => {
61
60
  const menuItems = useMemo<MenuProps['items']>(
62
61
  () => [
63
62
  {
63
+ extra: approvalMode === 'auto-run' ? <Icon icon={Check} /> : undefined,
64
64
  icon: (
65
65
  <Center className={styles.icon} height={32} width={32}>
66
66
  <Icon icon={Zap} />
@@ -76,6 +76,7 @@ const ModeSelector = memo(() => {
76
76
  onClick: () => handleModeChange('auto-run'),
77
77
  },
78
78
  {
79
+ extra: approvalMode === 'allow-list' ? <Icon icon={Check} /> : undefined,
79
80
  icon: (
80
81
  <Center className={styles.icon} height={32} width={32}>
81
82
  <Icon icon={ListChecks} />
@@ -91,6 +92,7 @@ const ModeSelector = memo(() => {
91
92
  onClick: () => handleModeChange('allow-list'),
92
93
  },
93
94
  {
95
+ extra: approvalMode === 'manual' ? <Icon icon={Check} /> : undefined,
94
96
  icon: (
95
97
  <Center className={styles.icon} height={32} width={32}>
96
98
  <Icon icon={Hand} />
@@ -106,15 +108,11 @@ const ModeSelector = memo(() => {
106
108
  onClick: () => handleModeChange('manual'),
107
109
  },
108
110
  ],
109
- [modeLabels, handleModeChange, styles, t],
111
+ [approvalMode, modeLabels, handleModeChange, styles, t],
110
112
  );
111
113
 
112
114
  return (
113
- <Dropdown
114
- // @ts-expect-error activeKey 没在 Dropdown key 里很奇怪
115
- menu={{ activeKey: approvalMode, items: menuItems }}
116
- placement="bottomLeft"
117
- >
115
+ <DropdownMenu items={menuItems} placement="bottomLeft">
118
116
  <Button
119
117
  className={styles.modeButton}
120
118
  color={'default'}
@@ -125,7 +123,7 @@ const ModeSelector = memo(() => {
125
123
  >
126
124
  {modeLabels[approvalMode]}
127
125
  </Button>
128
- </Dropdown>
126
+ </DropdownMenu>
129
127
  );
130
128
  });
131
129
 
@@ -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
@@ -18,16 +18,20 @@ import { useAssistantActions } from './useAssistantActions';
18
18
  // Helper to strip handleClick from action items before passing to ActionIconGroup
19
19
  const stripHandleClick = (item: MessageActionItemOrDivider): ActionIconGroupItemType => {
20
20
  if ('type' in item && item.type === 'divider') return item as unknown as ActionIconGroupItemType;
21
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
22
- const { handleClick, children, ...rest } = item as MessageActionItem;
21
+ const { children, ...rest } = item as MessageActionItem;
22
+ const baseItem = { ...rest } as MessageActionItem;
23
+ delete (baseItem as { handleClick?: unknown }).handleClick;
23
24
  if (children) {
24
25
  return {
25
- ...rest,
26
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
27
- children: children.map(({ handleClick: _, ...child }) => child),
26
+ ...baseItem,
27
+ children: children.map((child) => {
28
+ const nextChild = { ...child } as MessageActionItem;
29
+ delete (nextChild as { handleClick?: unknown }).handleClick;
30
+ return nextChild;
31
+ }),
28
32
  } as ActionIconGroupItemType;
29
33
  }
30
- return rest as ActionIconGroupItemType;
34
+ return baseItem as ActionIconGroupItemType;
31
35
  };
32
36
 
33
37
  // Build action items map for handleAction lookup