@lobehub/lobehub 2.0.0-next.188 → 2.0.0-next.189
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.
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/e2e/CLAUDE.md +109 -2
- package/e2e/docs/llm-mock.md +68 -0
- package/e2e/docs/local-setup.md +354 -0
- package/e2e/docs/testing-tips.md +94 -0
- package/e2e/src/features/journeys/agent/agent-conversation.feature +0 -32
- package/e2e/src/mocks/llm/index.ts +6 -6
- package/e2e/src/steps/agent/conversation.steps.ts +3 -471
- package/package.json +2 -2
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/List/Item/Actions.tsx +4 -13
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/List/Item/index.tsx +23 -29
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/List/Item/useDropdownMenu.tsx +3 -3
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/Actions.tsx +4 -13
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/index.tsx +10 -18
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/useDropdownMenu.tsx +3 -3
- package/src/app/[variants]/(main)/community/(detail)/assistant/features/Sidebar/ActionButton/AddAgent.tsx +47 -27
- package/src/app/[variants]/(main)/community/(detail)/user/features/UserAgentCard.tsx +4 -3
- package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/Actions.tsx +4 -13
- package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/index.tsx +23 -29
- package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/useDropdownMenu.tsx +3 -3
- package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/Actions.tsx +4 -13
- package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/index.tsx +10 -18
- package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/useDropdownMenu.tsx +3 -3
- package/src/app/[variants]/(main)/group/profile/features/AgentBuilder/TopicSelector.tsx +18 -20
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/index.tsx +19 -25
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +21 -26
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/Item/Actions.tsx +4 -13
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/Item/useDropdownMenu.tsx +3 -3
- package/src/app/[variants]/(main)/home/_layout/Body/Project/List/Actions.tsx +4 -13
- package/src/app/[variants]/(main)/home/_layout/Body/Project/List/Item.tsx +8 -15
- package/src/app/[variants]/(main)/home/_layout/Body/Project/List/useDropdownMenu.tsx +3 -3
- package/src/app/[variants]/(main)/home/_layout/Header/components/AddButton.tsx +3 -4
- package/src/app/[variants]/(main)/page/_layout/Body/List/Item/Actions.tsx +4 -13
- package/src/app/[variants]/(main)/page/_layout/Body/List/Item/index.tsx +13 -20
- package/src/app/[variants]/(main)/page/_layout/Body/List/Item/useDropdownMenu.tsx +3 -3
- package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/List/Item/Actions.tsx +4 -13
- package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/List/Item/index.tsx +16 -23
- package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/List/Item/useDropdownMenu.tsx +3 -3
- package/src/app/[variants]/(main)/resource/library/_layout/Header/LibraryHead.tsx +4 -6
- package/src/features/AgentBuilder/TopicSelector.tsx +18 -17
- package/src/features/Conversation/ChatItem/style.ts +7 -0
- package/src/features/Conversation/Messages/Assistant/Actions/Error.tsx +1 -3
- package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +37 -16
- package/src/features/Conversation/Messages/AssistantGroup/Actions/index.tsx +36 -17
- package/src/features/Conversation/Messages/Supervisor/Actions/index.tsx +36 -17
- package/src/features/Conversation/Messages/Task/Actions/Error.tsx +1 -3
- package/src/features/Conversation/Messages/Task/Actions/index.tsx +31 -15
- package/src/features/Conversation/Messages/User/Actions/index.tsx +1 -1
- package/src/features/Conversation/Messages/index.tsx +8 -59
- package/src/features/Conversation/components/ShareMessageModal/index.tsx +1 -1
- package/src/features/Conversation/hooks/useChatItemContextMenu.tsx +313 -83
- package/src/features/NavPanel/components/NavItem.tsx +33 -3
- package/src/features/PageEditor/Copilot/TopicSelector/Actions.tsx +6 -14
- package/src/features/PageEditor/Copilot/TopicSelector/TopicItem.tsx +1 -0
- package/src/features/PageEditor/Copilot/TopicSelector/useDropdownMenu.tsx +6 -3
- package/src/features/ResourceManager/components/Explorer/ItemDropdown/DropdownMenu.tsx +12 -35
- package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +4 -8
- package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +162 -160
- package/src/features/ResourceManager/components/Explorer/MasonryView/MasonryFileItem/index.tsx +16 -8
- package/src/features/ResourceManager/components/Explorer/ToolBar/ActionIconWithChevron.tsx +4 -3
- package/src/features/ResourceManager/components/Explorer/ToolBar/BatchActionsDropdown.tsx +6 -12
- package/src/features/ResourceManager/components/Explorer/ToolBar/SortDropdown.tsx +8 -8
- package/src/features/ResourceManager/components/Explorer/ToolBar/ViewSwitcher.tsx +8 -11
- package/src/features/ResourceManager/components/Tree/index.tsx +121 -122
- package/src/layout/GlobalProvider/index.tsx +5 -2
- package/src/styles/global.ts +6 -0
- package/src/features/Conversation/components/ContextMenu.tsx +0 -418
|
@@ -1,49 +1,338 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
type ActionIconGroupEvent,
|
|
3
|
+
type ActionIconGroupItemType,
|
|
4
|
+
type DropdownItem,
|
|
5
|
+
type GenericItemType,
|
|
6
|
+
createRawModal,
|
|
7
|
+
showContextMenu,
|
|
8
|
+
} from '@lobehub/ui';
|
|
9
|
+
import { App } from 'antd';
|
|
10
|
+
import isEqual from 'fast-deep-equal';
|
|
11
|
+
import type { MouseEvent, ReactNode } from 'react';
|
|
12
|
+
import { useCallback, useMemo, useRef } from 'react';
|
|
13
|
+
import { useTranslation } from 'react-i18next';
|
|
4
14
|
|
|
5
15
|
import { MSG_CONTENT_CLASSNAME } from '@/features/Conversation/ChatItem/components/MessageContent';
|
|
16
|
+
import { useSessionStore } from '@/store/session';
|
|
17
|
+
import { sessionSelectors } from '@/store/session/selectors';
|
|
6
18
|
import { useUserStore } from '@/store/user';
|
|
7
19
|
import { userGeneralSettingsSelectors } from '@/store/user/selectors';
|
|
8
20
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
21
|
+
import ShareMessageModal from '../components/ShareMessageModal';
|
|
22
|
+
import {
|
|
23
|
+
dataSelectors,
|
|
24
|
+
messageStateSelectors,
|
|
25
|
+
useConversationStore,
|
|
26
|
+
useConversationStoreApi,
|
|
27
|
+
} from '../store';
|
|
28
|
+
import { useChatListActionsBar } from './useChatListActionsBar';
|
|
29
|
+
|
|
30
|
+
interface ActionMenuItem extends ActionIconGroupItemType {
|
|
31
|
+
children?: { key: string; label: ReactNode }[];
|
|
32
|
+
disable?: boolean;
|
|
33
|
+
popupClassName?: string;
|
|
13
34
|
}
|
|
14
35
|
|
|
36
|
+
type MenuItem = ActionMenuItem | { type: 'divider' };
|
|
37
|
+
type ContextMenuEvent = ActionIconGroupEvent & { selectedText?: string };
|
|
38
|
+
|
|
15
39
|
interface UseChatItemContextMenuProps {
|
|
16
40
|
editing?: boolean;
|
|
17
41
|
id: string;
|
|
18
|
-
|
|
42
|
+
inPortalThread: boolean;
|
|
43
|
+
topic?: string | null;
|
|
19
44
|
}
|
|
20
45
|
|
|
21
46
|
export const useChatItemContextMenu = ({
|
|
22
|
-
onActionClick,
|
|
23
47
|
editing,
|
|
24
|
-
|
|
48
|
+
id,
|
|
49
|
+
inPortalThread,
|
|
50
|
+
topic,
|
|
51
|
+
}: UseChatItemContextMenuProps) => {
|
|
25
52
|
const contextMenuMode = useUserStore(userGeneralSettingsSelectors.contextMenuMode);
|
|
53
|
+
const { message } = App.useApp();
|
|
54
|
+
const { t } = useTranslation('common');
|
|
55
|
+
|
|
56
|
+
const selectedTextRef = useRef<string | undefined>(undefined);
|
|
57
|
+
|
|
58
|
+
const storeApi = useConversationStoreApi();
|
|
59
|
+
|
|
60
|
+
const [role, error, isCollapsed, hasThread, isRegenerating] = useConversationStore((s) => {
|
|
61
|
+
const item = dataSelectors.getDisplayMessageById(id)(s);
|
|
62
|
+
return [
|
|
63
|
+
item?.role,
|
|
64
|
+
item?.error,
|
|
65
|
+
messageStateSelectors.isMessageCollapsed(id)(s),
|
|
66
|
+
messageStateSelectors.hasThreadBySourceMsgId(id)(s),
|
|
67
|
+
messageStateSelectors.isMessageRegenerating(id)(s),
|
|
68
|
+
];
|
|
69
|
+
}, isEqual);
|
|
70
|
+
|
|
71
|
+
const isThreadMode = useConversationStore(messageStateSelectors.isThreadMode);
|
|
72
|
+
const isGroupSession = useSessionStore(sessionSelectors.isCurrentSessionGroupSession);
|
|
73
|
+
const actionsBar = useChatListActionsBar({ hasThread, isRegenerating });
|
|
74
|
+
const inThread = isThreadMode || inPortalThread;
|
|
75
|
+
|
|
76
|
+
const [
|
|
77
|
+
toggleMessageEditing,
|
|
78
|
+
deleteMessage,
|
|
79
|
+
regenerateUserMessage,
|
|
80
|
+
regenerateAssistantMessage,
|
|
81
|
+
translateMessage,
|
|
82
|
+
ttsMessage,
|
|
83
|
+
delAndRegenerateMessage,
|
|
84
|
+
copyMessage,
|
|
85
|
+
openThreadCreator,
|
|
86
|
+
resendThreadMessage,
|
|
87
|
+
delAndResendThreadMessage,
|
|
88
|
+
toggleMessageCollapsed,
|
|
89
|
+
] = useConversationStore((s) => [
|
|
90
|
+
s.toggleMessageEditing,
|
|
91
|
+
s.deleteMessage,
|
|
92
|
+
s.regenerateUserMessage,
|
|
93
|
+
s.regenerateAssistantMessage,
|
|
94
|
+
s.translateMessage,
|
|
95
|
+
s.ttsMessage,
|
|
96
|
+
s.delAndRegenerateMessage,
|
|
97
|
+
s.copyMessage,
|
|
98
|
+
s.openThreadCreator,
|
|
99
|
+
s.resendThreadMessage,
|
|
100
|
+
s.delAndResendThreadMessage,
|
|
101
|
+
s.toggleMessageCollapsed,
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
const getMessage = useCallback(
|
|
105
|
+
() => dataSelectors.getDisplayMessageById(id)(storeApi.getState()),
|
|
106
|
+
[id, storeApi],
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const menuItems = useMemo<MenuItem[]>(() => {
|
|
110
|
+
if (!role) return [];
|
|
111
|
+
|
|
112
|
+
const {
|
|
113
|
+
branching,
|
|
114
|
+
collapse,
|
|
115
|
+
copy,
|
|
116
|
+
del,
|
|
117
|
+
delAndRegenerate,
|
|
118
|
+
divider,
|
|
119
|
+
edit,
|
|
120
|
+
expand,
|
|
121
|
+
regenerate,
|
|
122
|
+
share,
|
|
123
|
+
translate,
|
|
124
|
+
tts,
|
|
125
|
+
} = actionsBar;
|
|
126
|
+
|
|
127
|
+
if (role === 'assistant') {
|
|
128
|
+
if (error) {
|
|
129
|
+
return [edit, copy, divider, del, divider, regenerate].filter(Boolean) as MenuItem[];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const collapseAction = isCollapsed ? expand : collapse;
|
|
133
|
+
const list: MenuItem[] = [edit, copy, collapseAction];
|
|
134
|
+
|
|
135
|
+
if (!inThread && !isGroupSession) list.push(branching);
|
|
136
|
+
|
|
137
|
+
list.push(
|
|
138
|
+
divider,
|
|
139
|
+
tts,
|
|
140
|
+
translate,
|
|
141
|
+
divider,
|
|
142
|
+
share,
|
|
143
|
+
divider,
|
|
144
|
+
regenerate,
|
|
145
|
+
delAndRegenerate,
|
|
146
|
+
del,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return list.filter(Boolean) as MenuItem[];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (role === 'assistantGroup') {
|
|
153
|
+
if (error) {
|
|
154
|
+
return [edit, copy, divider, del, divider, regenerate].filter(Boolean) as MenuItem[];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const collapseAction = isCollapsed ? expand : collapse;
|
|
158
|
+
const list: MenuItem[] = [
|
|
159
|
+
edit,
|
|
160
|
+
copy,
|
|
161
|
+
collapseAction,
|
|
162
|
+
divider,
|
|
163
|
+
share,
|
|
164
|
+
divider,
|
|
165
|
+
regenerate,
|
|
166
|
+
del,
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
return list.filter(Boolean) as MenuItem[];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (role === 'user') {
|
|
173
|
+
const list: MenuItem[] = [edit, copy];
|
|
174
|
+
|
|
175
|
+
if (!inThread) list.push(branching);
|
|
176
|
+
|
|
177
|
+
list.push(divider, tts, translate, divider, regenerate, del);
|
|
178
|
+
|
|
179
|
+
return list.filter(Boolean) as MenuItem[];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return [];
|
|
183
|
+
}, [actionsBar, error, inThread, isCollapsed, isGroupSession, role]);
|
|
26
184
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
});
|
|
185
|
+
const handleShare = useCallback(() => {
|
|
186
|
+
const item = getMessage();
|
|
187
|
+
if (!item || item.role !== 'assistant') return;
|
|
31
188
|
|
|
32
|
-
|
|
189
|
+
createRawModal(
|
|
190
|
+
ShareMessageModal,
|
|
191
|
+
{
|
|
192
|
+
message: item,
|
|
193
|
+
},
|
|
194
|
+
{ onCloseKey: 'onCancel', openKey: 'open' },
|
|
195
|
+
);
|
|
196
|
+
}, [getMessage]);
|
|
197
|
+
|
|
198
|
+
const handleAction = useCallback(
|
|
199
|
+
async (action: ContextMenuEvent) => {
|
|
200
|
+
const item = getMessage();
|
|
201
|
+
if (!item) return;
|
|
202
|
+
|
|
203
|
+
switch (action.key) {
|
|
204
|
+
case 'edit': {
|
|
205
|
+
toggleMessageEditing(id, true);
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
case 'copy': {
|
|
209
|
+
await copyMessage(id, item.content);
|
|
210
|
+
message.success(t('copySuccess'));
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case 'expand':
|
|
214
|
+
case 'collapse': {
|
|
215
|
+
toggleMessageCollapsed(id);
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
case 'branching': {
|
|
219
|
+
if (!topic) {
|
|
220
|
+
message.warning(t('branchingRequiresSavedTopic'));
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
openThreadCreator(id);
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
case 'del': {
|
|
227
|
+
deleteMessage(id);
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case 'regenerate': {
|
|
231
|
+
if (inPortalThread) {
|
|
232
|
+
resendThreadMessage(id);
|
|
233
|
+
} else if (role === 'assistant') {
|
|
234
|
+
regenerateAssistantMessage(id);
|
|
235
|
+
} else {
|
|
236
|
+
regenerateUserMessage(id);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (item.error) deleteMessage(id);
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
case 'delAndRegenerate': {
|
|
243
|
+
if (inPortalThread) {
|
|
244
|
+
delAndResendThreadMessage(id);
|
|
245
|
+
} else {
|
|
246
|
+
delAndRegenerateMessage(id);
|
|
247
|
+
}
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
case 'tts': {
|
|
251
|
+
ttsMessage(id);
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
case 'share': {
|
|
255
|
+
handleShare();
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (action.keyPath?.[0] === 'translate') {
|
|
261
|
+
const lang = action.keyPath.at(-1);
|
|
262
|
+
if (lang) translateMessage(id, lang);
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
[
|
|
266
|
+
copyMessage,
|
|
267
|
+
deleteMessage,
|
|
268
|
+
delAndRegenerateMessage,
|
|
269
|
+
delAndResendThreadMessage,
|
|
270
|
+
getMessage,
|
|
271
|
+
handleShare,
|
|
272
|
+
id,
|
|
273
|
+
inPortalThread,
|
|
274
|
+
message,
|
|
275
|
+
openThreadCreator,
|
|
276
|
+
regenerateAssistantMessage,
|
|
277
|
+
regenerateUserMessage,
|
|
278
|
+
resendThreadMessage,
|
|
279
|
+
role,
|
|
280
|
+
t,
|
|
281
|
+
toggleMessageCollapsed,
|
|
282
|
+
toggleMessageEditing,
|
|
283
|
+
topic,
|
|
284
|
+
translateMessage,
|
|
285
|
+
ttsMessage,
|
|
286
|
+
],
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
const handleMenuClick = useCallback(
|
|
290
|
+
(info: ActionIconGroupEvent) => {
|
|
291
|
+
handleAction({
|
|
292
|
+
...info,
|
|
293
|
+
selectedText: selectedTextRef.current,
|
|
294
|
+
} as ContextMenuEvent);
|
|
295
|
+
},
|
|
296
|
+
[handleAction],
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
const contextMenuItems = useMemo<GenericItemType[]>(() => {
|
|
300
|
+
if (!menuItems) return [];
|
|
301
|
+
return menuItems.filter(Boolean).map((item) => {
|
|
302
|
+
if ('type' in item && item.type === 'divider') return { type: 'divider' as const };
|
|
303
|
+
|
|
304
|
+
const actionItem = item as ActionMenuItem;
|
|
305
|
+
const children = actionItem.children?.map((child) => ({
|
|
306
|
+
key: child.key,
|
|
307
|
+
label: child.label,
|
|
308
|
+
onClick: handleMenuClick,
|
|
309
|
+
}));
|
|
310
|
+
const disabled =
|
|
311
|
+
actionItem.disabled ??
|
|
312
|
+
(typeof actionItem.disable === 'boolean' ? actionItem.disable : undefined);
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
children,
|
|
316
|
+
danger: actionItem.danger,
|
|
317
|
+
disabled,
|
|
318
|
+
icon: actionItem.icon,
|
|
319
|
+
key: actionItem.key,
|
|
320
|
+
label: actionItem.label,
|
|
321
|
+
onClick: children ? undefined : handleMenuClick,
|
|
322
|
+
} satisfies DropdownItem;
|
|
323
|
+
});
|
|
324
|
+
}, [handleMenuClick, menuItems]);
|
|
33
325
|
|
|
34
326
|
const handleContextMenu = useCallback(
|
|
35
|
-
(event:
|
|
36
|
-
// Don't show context menu if disabled in settings
|
|
327
|
+
(event: MouseEvent<HTMLDivElement>) => {
|
|
37
328
|
if (contextMenuMode === 'disabled') {
|
|
38
329
|
return;
|
|
39
330
|
}
|
|
40
331
|
|
|
41
|
-
// Don't show context menu in editing mode
|
|
42
332
|
if (editing) {
|
|
43
333
|
return;
|
|
44
334
|
}
|
|
45
335
|
|
|
46
|
-
// Check if the clicked element or its parents have an id containing "msg_"
|
|
47
336
|
let target = event.target as HTMLElement;
|
|
48
337
|
let hasMessageId = false;
|
|
49
338
|
|
|
@@ -55,82 +344,23 @@ export const useChatItemContextMenu = ({
|
|
|
55
344
|
target = target.parentElement as HTMLElement;
|
|
56
345
|
}
|
|
57
346
|
|
|
58
|
-
if (!hasMessageId) {
|
|
347
|
+
if (!hasMessageId || contextMenuItems.length === 0) {
|
|
59
348
|
return;
|
|
60
349
|
}
|
|
61
350
|
|
|
62
351
|
event.preventDefault();
|
|
63
352
|
event.stopPropagation();
|
|
64
353
|
|
|
65
|
-
// Get selected text
|
|
66
354
|
const selection = window.getSelection();
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
setContextMenuState({
|
|
70
|
-
position: { x: event.clientX, y: event.clientY },
|
|
71
|
-
selectedText,
|
|
72
|
-
visible: true,
|
|
73
|
-
});
|
|
74
|
-
},
|
|
75
|
-
[contextMenuMode, editing],
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
const hideContextMenu = useCallback(() => {
|
|
79
|
-
setContextMenuState((prev) => ({ ...prev, visible: false }));
|
|
80
|
-
}, []);
|
|
355
|
+
selectedTextRef.current = selection?.toString().trim() || '';
|
|
81
356
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (action.key === 'quote' && contextMenuState.selectedText) {
|
|
85
|
-
// Handle quote action - this will be integrated with ChatInput
|
|
86
|
-
onActionClick({
|
|
87
|
-
...action,
|
|
88
|
-
selectedText: contextMenuState.selectedText,
|
|
89
|
-
} as ActionIconGroupEvent & { selectedText: string });
|
|
90
|
-
} else {
|
|
91
|
-
onActionClick(action);
|
|
92
|
-
}
|
|
93
|
-
hideContextMenu();
|
|
357
|
+
console.log(contextMenuItems);
|
|
358
|
+
showContextMenu(contextMenuItems);
|
|
94
359
|
},
|
|
95
|
-
[
|
|
360
|
+
[contextMenuItems, contextMenuMode, editing],
|
|
96
361
|
);
|
|
97
362
|
|
|
98
|
-
// Close context menu when clicking outside
|
|
99
|
-
useEffect(() => {
|
|
100
|
-
const handleClickOutside = () => {
|
|
101
|
-
if (contextMenuState.visible) {
|
|
102
|
-
hideContextMenu();
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const handleScroll = (event: Event) => {
|
|
107
|
-
if (contextMenuState.visible) {
|
|
108
|
-
// Check if the scroll event is from a dropdown sub-menu
|
|
109
|
-
const target = event.target as HTMLElement;
|
|
110
|
-
if (target && target.classList && target.classList.contains('ant-dropdown-menu-sub')) {
|
|
111
|
-
return; // Don't hide the context menu when scrolling within sub-menu
|
|
112
|
-
}
|
|
113
|
-
hideContextMenu();
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
if (contextMenuState.visible) {
|
|
118
|
-
document.addEventListener('click', handleClickOutside);
|
|
119
|
-
document.addEventListener('scroll', handleScroll, true);
|
|
120
|
-
|
|
121
|
-
return () => {
|
|
122
|
-
document.removeEventListener('click', handleClickOutside);
|
|
123
|
-
document.removeEventListener('scroll', handleScroll, true);
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
}, [contextMenuState.visible, hideContextMenu]);
|
|
127
|
-
|
|
128
363
|
return {
|
|
129
|
-
containerRef,
|
|
130
|
-
contextMenuMode,
|
|
131
|
-
contextMenuState,
|
|
132
364
|
handleContextMenu,
|
|
133
|
-
handleMenuClick,
|
|
134
|
-
hideContextMenu,
|
|
135
365
|
};
|
|
136
366
|
};
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Block,
|
|
5
|
+
type BlockProps,
|
|
6
|
+
Center,
|
|
7
|
+
ContextMenuTrigger,
|
|
8
|
+
Flexbox,
|
|
9
|
+
type GenericItemType,
|
|
10
|
+
Icon,
|
|
11
|
+
type IconProps,
|
|
12
|
+
Text,
|
|
13
|
+
} from '@lobehub/ui';
|
|
4
14
|
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
|
5
15
|
import { Loader2Icon } from 'lucide-react';
|
|
6
16
|
import { type ReactNode, memo } from 'react';
|
|
@@ -18,6 +28,11 @@ const styles = createStaticStyles(({ css }) => ({
|
|
|
18
28
|
margin-inline-end: 2px;
|
|
19
29
|
opacity: 0;
|
|
20
30
|
transition: opacity 0.2s ${cssVar.motionEaseOut};
|
|
31
|
+
|
|
32
|
+
&:has([data-popup-open]) {
|
|
33
|
+
width: unset;
|
|
34
|
+
opacity: 1;
|
|
35
|
+
}
|
|
21
36
|
}
|
|
22
37
|
|
|
23
38
|
&:hover {
|
|
@@ -32,6 +47,7 @@ const styles = createStaticStyles(({ css }) => ({
|
|
|
32
47
|
export interface NavItemProps extends Omit<BlockProps, 'children' | 'title'> {
|
|
33
48
|
actions?: ReactNode;
|
|
34
49
|
active?: boolean;
|
|
50
|
+
contextMenuItems?: GenericItemType[] | (() => GenericItemType[]);
|
|
35
51
|
disabled?: boolean;
|
|
36
52
|
extra?: ReactNode;
|
|
37
53
|
icon?: IconProps['icon'];
|
|
@@ -40,13 +56,25 @@ export interface NavItemProps extends Omit<BlockProps, 'children' | 'title'> {
|
|
|
40
56
|
}
|
|
41
57
|
|
|
42
58
|
const NavItem = memo<NavItemProps>(
|
|
43
|
-
({
|
|
59
|
+
({
|
|
60
|
+
className,
|
|
61
|
+
actions,
|
|
62
|
+
contextMenuItems,
|
|
63
|
+
active,
|
|
64
|
+
icon,
|
|
65
|
+
title,
|
|
66
|
+
onClick,
|
|
67
|
+
disabled,
|
|
68
|
+
loading,
|
|
69
|
+
extra,
|
|
70
|
+
...rest
|
|
71
|
+
}) => {
|
|
44
72
|
const iconColor = active ? cssVar.colorText : cssVar.colorTextDescription;
|
|
45
73
|
const textColor = active ? cssVar.colorText : cssVar.colorTextSecondary;
|
|
46
74
|
const variant = active ? 'filled' : 'borderless';
|
|
47
75
|
const iconComponent = loading ? Loader2Icon : icon;
|
|
48
76
|
|
|
49
|
-
|
|
77
|
+
const Content = (
|
|
50
78
|
<Block
|
|
51
79
|
align={'center'}
|
|
52
80
|
className={cx(styles.container, className)}
|
|
@@ -102,6 +130,8 @@ const NavItem = memo<NavItemProps>(
|
|
|
102
130
|
</Flexbox>
|
|
103
131
|
</Block>
|
|
104
132
|
);
|
|
133
|
+
if (!contextMenuItems) return Content;
|
|
134
|
+
return <ContextMenuTrigger items={contextMenuItems}>{Content}</ContextMenuTrigger>;
|
|
105
135
|
},
|
|
106
136
|
);
|
|
107
137
|
|
|
@@ -1,27 +1,19 @@
|
|
|
1
|
-
import { ActionIcon,
|
|
1
|
+
import { ActionIcon, type DropdownItem, DropdownMenu } from '@lobehub/ui';
|
|
2
2
|
import { MoreHorizontalIcon } from 'lucide-react';
|
|
3
3
|
import { memo } from 'react';
|
|
4
4
|
|
|
5
5
|
interface ActionsProps {
|
|
6
|
-
dropdownMenu:
|
|
6
|
+
dropdownMenu: DropdownItem[] | (() => DropdownItem[]);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
const Actions = memo<ActionsProps>(({ dropdownMenu }) => {
|
|
10
|
-
if (!dropdownMenu || dropdownMenu.length === 0)
|
|
10
|
+
if (!dropdownMenu || (typeof dropdownMenu !== 'function' && dropdownMenu.length === 0))
|
|
11
|
+
return null;
|
|
11
12
|
|
|
12
13
|
return (
|
|
13
|
-
<
|
|
14
|
-
arrow={false}
|
|
15
|
-
menu={{
|
|
16
|
-
items: dropdownMenu,
|
|
17
|
-
onClick: ({ domEvent }) => {
|
|
18
|
-
domEvent.stopPropagation();
|
|
19
|
-
},
|
|
20
|
-
}}
|
|
21
|
-
trigger={['click']}
|
|
22
|
-
>
|
|
14
|
+
<DropdownMenu items={dropdownMenu}>
|
|
23
15
|
<ActionIcon icon={MoreHorizontalIcon} size={'small'} />
|
|
24
|
-
</
|
|
16
|
+
</DropdownMenu>
|
|
25
17
|
);
|
|
26
18
|
});
|
|
27
19
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Icon, type MenuProps } from '@lobehub/ui';
|
|
2
2
|
import { App } from 'antd';
|
|
3
3
|
import { Trash2 } from 'lucide-react';
|
|
4
|
-
import {
|
|
4
|
+
import { useCallback } from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
|
|
7
7
|
import { useChatStore } from '@/store/chat';
|
|
@@ -12,7 +12,10 @@ interface UseDropdownMenuProps {
|
|
|
12
12
|
topicTitle: string;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export const useDropdownMenu = ({
|
|
15
|
+
export const useDropdownMenu = ({
|
|
16
|
+
onClose,
|
|
17
|
+
topicId,
|
|
18
|
+
}: UseDropdownMenuProps): (() => MenuProps['items']) => {
|
|
16
19
|
const { t } = useTranslation(['common', 'topic']);
|
|
17
20
|
const { modal } = App.useApp();
|
|
18
21
|
const removeTopic = useChatStore((s) => s.removeTopic);
|
|
@@ -32,7 +35,7 @@ export const useDropdownMenu = ({ onClose, topicId }: UseDropdownMenuProps): Men
|
|
|
32
35
|
});
|
|
33
36
|
};
|
|
34
37
|
|
|
35
|
-
return
|
|
38
|
+
return useCallback(
|
|
36
39
|
() =>
|
|
37
40
|
[
|
|
38
41
|
{
|
|
@@ -1,42 +1,19 @@
|
|
|
1
|
-
import { ActionIcon,
|
|
1
|
+
import { ActionIcon, DropdownMenu as DropdownMenuUI } from '@lobehub/ui';
|
|
2
|
+
import { type ItemType } from 'antd/es/menu/interface';
|
|
2
3
|
import { MoreHorizontalIcon } from 'lucide-react';
|
|
3
|
-
import { memo
|
|
4
|
-
|
|
5
|
-
import { useFileItemDropdown } from './useFileItemDropdown';
|
|
4
|
+
import { memo } from 'react';
|
|
6
5
|
|
|
7
6
|
interface DropdownMenuProps {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
id: string;
|
|
11
|
-
knowledgeBaseId?: string;
|
|
12
|
-
onRenameStart?: () => void;
|
|
13
|
-
sourceType?: string;
|
|
14
|
-
url: string;
|
|
7
|
+
className?: string;
|
|
8
|
+
items: ItemType[] | (() => ItemType[]);
|
|
15
9
|
}
|
|
16
10
|
|
|
17
|
-
const DropdownMenu = memo<DropdownMenuProps>(
|
|
18
|
-
(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
enabled: isOpen,
|
|
25
|
-
fileType,
|
|
26
|
-
filename,
|
|
27
|
-
id,
|
|
28
|
-
knowledgeBaseId,
|
|
29
|
-
onRenameStart,
|
|
30
|
-
sourceType,
|
|
31
|
-
url,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<Dropdown menu={{ items: menuItems }} onOpenChange={setIsOpen} open={isOpen}>
|
|
36
|
-
<ActionIcon icon={MoreHorizontalIcon} size={'small'} />
|
|
37
|
-
</Dropdown>
|
|
38
|
-
);
|
|
39
|
-
},
|
|
40
|
-
);
|
|
11
|
+
const DropdownMenu = memo<DropdownMenuProps>(({ items, className }) => {
|
|
12
|
+
return (
|
|
13
|
+
<DropdownMenuUI items={items}>
|
|
14
|
+
<ActionIcon className={className} icon={MoreHorizontalIcon} size={'small'} />
|
|
15
|
+
</DropdownMenuUI>
|
|
16
|
+
);
|
|
17
|
+
});
|
|
41
18
|
|
|
42
19
|
export default DropdownMenu;
|
package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
PencilIcon,
|
|
11
11
|
Trash,
|
|
12
12
|
} from 'lucide-react';
|
|
13
|
-
import {
|
|
13
|
+
import { useCallback } from 'react';
|
|
14
14
|
import { useTranslation } from 'react-i18next';
|
|
15
15
|
|
|
16
16
|
import RepoIcon from '@/components/LibIcon';
|
|
@@ -33,11 +33,10 @@ interface UseFileItemDropdownParams {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
interface UseFileItemDropdownReturn {
|
|
36
|
-
menuItems: ItemType[];
|
|
36
|
+
menuItems: () => ItemType[];
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export const useFileItemDropdown = ({
|
|
40
|
-
enabled = true,
|
|
41
40
|
id,
|
|
42
41
|
knowledgeBaseId,
|
|
43
42
|
url,
|
|
@@ -65,10 +64,7 @@ export const useFileItemDropdown = ({
|
|
|
65
64
|
const isFolder = fileType === 'custom/folder';
|
|
66
65
|
const isPage = sourceType === 'document' || fileType === 'custom/document';
|
|
67
66
|
|
|
68
|
-
const menuItems =
|
|
69
|
-
// Skip building menu items when dropdown is not open (performance optimization)
|
|
70
|
-
if (!enabled) return [];
|
|
71
|
-
|
|
67
|
+
const menuItems = useCallback(() => {
|
|
72
68
|
// Filter out current knowledge base and create submenu items
|
|
73
69
|
const availableKnowledgeBases = (knowledgeBases || []).filter(
|
|
74
70
|
(kb) => kb.id !== knowledgeBaseId,
|
|
@@ -264,7 +260,7 @@ export const useFileItemDropdown = ({
|
|
|
264
260
|
},
|
|
265
261
|
] as ItemType[]
|
|
266
262
|
).filter(Boolean);
|
|
267
|
-
}, [
|
|
263
|
+
}, [inKnowledgeBase, isFolder, knowledgeBases, knowledgeBaseId, id]);
|
|
268
264
|
|
|
269
265
|
return { menuItems };
|
|
270
266
|
};
|