@lobehub/lobehub 2.0.0-next.109 → 2.0.0-next.110
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 +26 -0
- package/apps/desktop/src/common/routes.ts +0 -6
- package/apps/desktop/src/main/appBrowsers.ts +0 -13
- package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +29 -48
- package/apps/desktop/src/main/controllers/__tests__/BrowserWindowsCtr.test.ts +21 -72
- package/apps/desktop/src/main/core/browser/Browser.ts +1 -0
- package/apps/desktop/src/main/core/browser/BrowserManager.ts +1 -56
- package/apps/desktop/src/main/menus/impls/macOS.ts +9 -3
- package/changelog/v1.json +9 -0
- package/locales/ar/setting.json +7 -1
- package/locales/bg-BG/setting.json +7 -1
- package/locales/de-DE/setting.json +7 -1
- package/locales/en-US/setting.json +7 -1
- package/locales/es-ES/setting.json +7 -1
- package/locales/fa-IR/setting.json +7 -1
- package/locales/fr-FR/setting.json +7 -1
- package/locales/it-IT/setting.json +7 -1
- package/locales/ja-JP/setting.json +7 -1
- package/locales/ko-KR/setting.json +7 -1
- package/locales/nl-NL/setting.json +7 -1
- package/locales/pl-PL/setting.json +7 -1
- package/locales/pt-BR/setting.json +7 -1
- package/locales/ru-RU/setting.json +7 -1
- package/locales/tr-TR/setting.json +7 -1
- package/locales/vi-VN/setting.json +7 -1
- package/locales/zh-CN/setting.json +6 -0
- package/locales/zh-TW/setting.json +7 -1
- package/package.json +1 -1
- package/packages/const/src/settings/common.ts +1 -0
- package/packages/model-bank/src/aiModels/ollamacloud.ts +0 -1
- package/packages/types/src/user/settings/general.ts +3 -0
- package/src/app/[variants]/(main)/chat/components/topic/features/Topic/TopicListContent/TopicItem/index.tsx +32 -18
- package/src/app/[variants]/(main)/layouts/desktop/DesktopLayoutContainer.tsx +3 -6
- package/src/app/[variants]/(main)/layouts/desktop/SideBar/PinList/index.tsx +21 -14
- package/src/app/[variants]/(main)/settings/common/features/Common/Common.tsx +23 -1
- package/src/features/ChatItem/components/MessageContent.tsx +2 -1
- package/src/features/ChatList/Messages/Assistant/Actions/index.tsx +1 -0
- package/src/features/ChatList/Messages/Assistant/index.tsx +1 -1
- package/src/features/ChatList/Messages/Default.tsx +2 -0
- package/src/features/ChatList/Messages/index.tsx +80 -31
- package/src/features/ChatList/components/ContextMenu.tsx +391 -0
- package/src/features/ChatList/hooks/useChatItemContextMenu.tsx +135 -0
- package/src/locales/default/setting.ts +6 -0
- package/src/store/user/slices/settings/selectors/general.ts +8 -0
|
@@ -293,6 +293,12 @@
|
|
|
293
293
|
"elegant": "Elegancki",
|
|
294
294
|
"title": "Animacja reakcji"
|
|
295
295
|
},
|
|
296
|
+
"contextMenuMode": {
|
|
297
|
+
"default": "Domyślnie",
|
|
298
|
+
"desc": "Wybierz sposób wyświetlania menu kontekstowego wiadomości czatu",
|
|
299
|
+
"disabled": "Nie używaj",
|
|
300
|
+
"title": "Schemat menu kontekstowego"
|
|
301
|
+
},
|
|
296
302
|
"neutralColor": {
|
|
297
303
|
"desc": "Dostosowanie odcieni szarości w różnych kolorach",
|
|
298
304
|
"title": "Kolor neutralny"
|
|
@@ -777,4 +783,4 @@
|
|
|
777
783
|
},
|
|
778
784
|
"title": "Narzędzia rozszerzeń"
|
|
779
785
|
}
|
|
780
|
-
}
|
|
786
|
+
}
|
|
@@ -293,6 +293,12 @@
|
|
|
293
293
|
"elegant": "Elegante",
|
|
294
294
|
"title": "Animação de Resposta"
|
|
295
295
|
},
|
|
296
|
+
"contextMenuMode": {
|
|
297
|
+
"default": "Padrão",
|
|
298
|
+
"desc": "Escolha o modo de exibição do menu de contexto ao clicar com o botão direito nas mensagens de chat",
|
|
299
|
+
"disabled": "Desativado",
|
|
300
|
+
"title": "Modo do menu de contexto"
|
|
301
|
+
},
|
|
296
302
|
"neutralColor": {
|
|
297
303
|
"desc": "Personalização de escala de cinza com diferentes inclinações de cor",
|
|
298
304
|
"title": "Cor Neutra"
|
|
@@ -777,4 +783,4 @@
|
|
|
777
783
|
},
|
|
778
784
|
"title": "Ferramentas de Extensão"
|
|
779
785
|
}
|
|
780
|
-
}
|
|
786
|
+
}
|
|
@@ -293,6 +293,12 @@
|
|
|
293
293
|
"elegant": "Элегантный",
|
|
294
294
|
"title": "Анимация отклика"
|
|
295
295
|
},
|
|
296
|
+
"contextMenuMode": {
|
|
297
|
+
"default": "По умолчанию",
|
|
298
|
+
"desc": "Выберите способ отображения контекстного меню сообщений чата",
|
|
299
|
+
"disabled": "Не использовать",
|
|
300
|
+
"title": "Схема контекстного меню"
|
|
301
|
+
},
|
|
296
302
|
"neutralColor": {
|
|
297
303
|
"desc": "Настройка градаций серого с различными цветовыми наклонами",
|
|
298
304
|
"title": "Нейтральный цвет"
|
|
@@ -777,4 +783,4 @@
|
|
|
777
783
|
},
|
|
778
784
|
"title": "Дополнительные инструменты"
|
|
779
785
|
}
|
|
780
|
-
}
|
|
786
|
+
}
|
|
@@ -293,6 +293,12 @@
|
|
|
293
293
|
"elegant": "Zarif",
|
|
294
294
|
"title": "Yanıt Animasyonu"
|
|
295
295
|
},
|
|
296
|
+
"contextMenuMode": {
|
|
297
|
+
"default": "Varsayılan",
|
|
298
|
+
"desc": "Sohbet mesajları sağ tıklama menüsünün görüntüleme seçeneğini seçin",
|
|
299
|
+
"disabled": "Kullanma",
|
|
300
|
+
"title": "Sağ Tıklama Menüsü Seçeneği"
|
|
301
|
+
},
|
|
296
302
|
"neutralColor": {
|
|
297
303
|
"desc": "Farklı renk eğilimlerine sahip gri tonları özelleştirme",
|
|
298
304
|
"title": "Nötr Renk"
|
|
@@ -777,4 +783,4 @@
|
|
|
777
783
|
},
|
|
778
784
|
"title": "Uzantı Araçları"
|
|
779
785
|
}
|
|
780
|
-
}
|
|
786
|
+
}
|
|
@@ -293,6 +293,12 @@
|
|
|
293
293
|
"elegant": "Thanh lịch",
|
|
294
294
|
"title": "Hoạt ảnh phản hồi"
|
|
295
295
|
},
|
|
296
|
+
"contextMenuMode": {
|
|
297
|
+
"default": "Mặc định",
|
|
298
|
+
"desc": "Chọn phương án hiển thị menu chuột phải cho tin nhắn trò chuyện",
|
|
299
|
+
"disabled": "Không sử dụng",
|
|
300
|
+
"title": "Phương án menu chuột phải"
|
|
301
|
+
},
|
|
296
302
|
"neutralColor": {
|
|
297
303
|
"desc": "Tùy chỉnh thang độ xám với các xu hướng màu sắc khác nhau",
|
|
298
304
|
"title": "Màu trung tính"
|
|
@@ -777,4 +783,4 @@
|
|
|
777
783
|
},
|
|
778
784
|
"title": "Công cụ mở rộng"
|
|
779
785
|
}
|
|
780
|
-
}
|
|
786
|
+
}
|
|
@@ -293,6 +293,12 @@
|
|
|
293
293
|
"elegant": "优雅",
|
|
294
294
|
"title": "响应动画"
|
|
295
295
|
},
|
|
296
|
+
"contextMenuMode": {
|
|
297
|
+
"default": "默认",
|
|
298
|
+
"desc": "选择聊天消息右键菜单的显示方案",
|
|
299
|
+
"disabled": "不使用",
|
|
300
|
+
"title": "右键菜单方案"
|
|
301
|
+
},
|
|
296
302
|
"neutralColor": {
|
|
297
303
|
"desc": "不同色彩倾向的灰阶自定义",
|
|
298
304
|
"title": "中性色"
|
|
@@ -293,6 +293,12 @@
|
|
|
293
293
|
"elegant": "優雅",
|
|
294
294
|
"title": "回應動畫"
|
|
295
295
|
},
|
|
296
|
+
"contextMenuMode": {
|
|
297
|
+
"default": "預設",
|
|
298
|
+
"desc": "選擇聊天訊息右鍵選單的顯示方案",
|
|
299
|
+
"disabled": "不使用",
|
|
300
|
+
"title": "右鍵選單方案"
|
|
301
|
+
},
|
|
296
302
|
"neutralColor": {
|
|
297
303
|
"desc": "不同色彩傾向的灰階自訂",
|
|
298
304
|
"title": "中性色"
|
|
@@ -777,4 +783,4 @@
|
|
|
777
783
|
},
|
|
778
784
|
"title": "擴展工具"
|
|
779
785
|
}
|
|
780
|
-
}
|
|
786
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.110",
|
|
4
4
|
"description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -2,6 +2,7 @@ import { UserGeneralConfig } from '@lobechat/types';
|
|
|
2
2
|
|
|
3
3
|
export const DEFAULT_COMMON_SETTINGS: UserGeneralConfig = {
|
|
4
4
|
animationMode: 'agile',
|
|
5
|
+
// contextMenuMode not set default value, use env to calc
|
|
5
6
|
fontSize: 14,
|
|
6
7
|
highlighterTheme: 'lobe-theme',
|
|
7
8
|
mermaidTheme: 'lobe-theme',
|
|
@@ -23,7 +23,6 @@ const ollamaCloudModels: AIChatModelCard[] = [
|
|
|
23
23
|
description:
|
|
24
24
|
'Gemini 3 Pro 是 Google 最智能的模型,具有 SOTA 推理和多模式理解,以及强大的代理和氛围编码功能。',
|
|
25
25
|
displayName: 'Gemini 3 Pro Preview',
|
|
26
|
-
enabled: true,
|
|
27
26
|
id: 'gemini-3-pro-preview',
|
|
28
27
|
releasedAt: '2025-11-20',
|
|
29
28
|
type: 'chat',
|
|
@@ -4,8 +4,11 @@ import type { ResponseAnimationStyle } from '../../aiProvider';
|
|
|
4
4
|
|
|
5
5
|
export type AnimationMode = 'disabled' | 'agile' | 'elegant';
|
|
6
6
|
|
|
7
|
+
export type ContextMenuMode = 'disabled' | 'default';
|
|
8
|
+
|
|
7
9
|
export interface UserGeneralConfig {
|
|
8
10
|
animationMode?: AnimationMode;
|
|
11
|
+
contextMenuMode?: ContextMenuMode;
|
|
9
12
|
fontSize: number;
|
|
10
13
|
highlighterTheme?: HighlighterProps['theme'];
|
|
11
14
|
mermaidTheme?: MermaidProps['theme'];
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { Skeleton } from 'antd';
|
|
2
2
|
import { createStyles } from 'antd-style';
|
|
3
|
+
import qs from 'query-string';
|
|
3
4
|
import { Suspense, memo, useState } from 'react';
|
|
4
5
|
import { Flexbox } from 'react-layout-kit';
|
|
6
|
+
import { Link } from 'react-router-dom';
|
|
5
7
|
|
|
6
8
|
import { useChatStore } from '@/store/chat';
|
|
7
9
|
import { useGlobalStore } from '@/store/global';
|
|
10
|
+
import { useSessionStore } from '@/store/session';
|
|
8
11
|
|
|
9
12
|
import ThreadList from '../ThreadList';
|
|
10
13
|
import DefaultContent from './DefaultContent';
|
|
@@ -52,32 +55,43 @@ const TopicItem = memo<ConfigCellProps>(({ title, active, id, fav, threadId }) =
|
|
|
52
55
|
const { styles, cx } = useStyles();
|
|
53
56
|
const toggleConfig = useGlobalStore((s) => s.toggleMobileTopic);
|
|
54
57
|
const [toggleTopic] = useChatStore((s) => [s.switchTopic]);
|
|
58
|
+
const activeId = useSessionStore((s) => s.activeId);
|
|
55
59
|
const [isHover, setHovering] = useState(false);
|
|
56
60
|
|
|
61
|
+
const topicUrl = qs.stringifyUrl({
|
|
62
|
+
query: id ? { session: activeId, topic: id } : { session: activeId },
|
|
63
|
+
url: '/chat',
|
|
64
|
+
});
|
|
65
|
+
|
|
57
66
|
return (
|
|
58
67
|
<Flexbox style={{ position: 'relative' }}>
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
distribution={'space-between'}
|
|
63
|
-
horizontal
|
|
64
|
-
onClick={() => {
|
|
68
|
+
<Link
|
|
69
|
+
onClick={(e) => {
|
|
70
|
+
e.preventDefault();
|
|
65
71
|
toggleTopic(id);
|
|
66
72
|
toggleConfig(false);
|
|
67
73
|
}}
|
|
68
|
-
|
|
69
|
-
setHovering(true);
|
|
70
|
-
}}
|
|
71
|
-
onMouseLeave={() => {
|
|
72
|
-
setHovering(false);
|
|
73
|
-
}}
|
|
74
|
+
to={topicUrl}
|
|
74
75
|
>
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
<Flexbox
|
|
77
|
+
align={'center'}
|
|
78
|
+
className={cx(styles.container, 'topic-item', active && !threadId && styles.active)}
|
|
79
|
+
distribution={'space-between'}
|
|
80
|
+
horizontal
|
|
81
|
+
onMouseEnter={() => {
|
|
82
|
+
setHovering(true);
|
|
83
|
+
}}
|
|
84
|
+
onMouseLeave={() => {
|
|
85
|
+
setHovering(false);
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
{!id ? (
|
|
89
|
+
<DefaultContent />
|
|
90
|
+
) : (
|
|
91
|
+
<TopicContent fav={fav} id={id} showMore={isHover} title={title} />
|
|
92
|
+
)}
|
|
93
|
+
</Flexbox>
|
|
94
|
+
</Link>
|
|
81
95
|
{active && (
|
|
82
96
|
<Suspense
|
|
83
97
|
fallback={
|
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
import { useTheme } from 'antd-style';
|
|
2
2
|
import { PropsWithChildren, Suspense, memo } from 'react';
|
|
3
3
|
import { Flexbox } from 'react-layout-kit';
|
|
4
|
-
import { useLocation } from 'react-router-dom';
|
|
5
4
|
|
|
6
5
|
import SideBar from './SideBar';
|
|
7
6
|
|
|
8
7
|
const DesktopLayoutContainer = memo<PropsWithChildren>(({ children }) => {
|
|
9
8
|
const theme = useTheme();
|
|
10
|
-
|
|
11
|
-
const pathname = location.pathname;
|
|
12
|
-
const hideSideBar = pathname.startsWith('/settings');
|
|
9
|
+
|
|
13
10
|
return (
|
|
14
11
|
<>
|
|
15
12
|
<Suspense>
|
|
16
|
-
|
|
13
|
+
<SideBar />
|
|
17
14
|
</Suspense>
|
|
18
15
|
<Flexbox
|
|
19
16
|
style={{
|
|
20
17
|
background: theme.colorBgLayout,
|
|
21
18
|
borderInlineStart: `1px solid ${theme.colorBorderSecondary}`,
|
|
22
|
-
borderStartStartRadius:
|
|
19
|
+
borderStartStartRadius: 12,
|
|
23
20
|
borderTop: `1px solid ${theme.colorBorderSecondary}`,
|
|
24
21
|
overflow: 'hidden',
|
|
25
22
|
}}
|
|
@@ -3,7 +3,9 @@ import { Divider } from 'antd';
|
|
|
3
3
|
import { createStyles } from 'antd-style';
|
|
4
4
|
import isEqual from 'fast-deep-equal';
|
|
5
5
|
import { Flexbox } from 'react-layout-kit';
|
|
6
|
+
import { Link } from 'react-router-dom';
|
|
6
7
|
|
|
8
|
+
import { SESSION_CHAT_URL } from '@/const/url';
|
|
7
9
|
import { usePinnedAgentState } from '@/hooks/usePinnedAgentState';
|
|
8
10
|
import { useSwitchSession } from '@/hooks/useSwitchSession';
|
|
9
11
|
import { useSessionStore } from '@/store/session';
|
|
@@ -93,21 +95,26 @@ const PinList = () => {
|
|
|
93
95
|
placement={'right'}
|
|
94
96
|
title={sessionHelpers.getTitle(item.meta)}
|
|
95
97
|
>
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
<Link
|
|
99
|
+
onClick={(e) => {
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
switchAgent(item.id);
|
|
102
|
+
}}
|
|
103
|
+
to={SESSION_CHAT_URL(item.id)}
|
|
101
104
|
>
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
105
|
+
<Flexbox
|
|
106
|
+
className={cx(
|
|
107
|
+
styles.ink,
|
|
108
|
+
isPinned && activeId === item.id ? styles.inkActive : undefined,
|
|
109
|
+
)}
|
|
110
|
+
>
|
|
111
|
+
<Avatar
|
|
112
|
+
avatar={sessionHelpers.getAvatar(item.meta)}
|
|
113
|
+
background={item.meta.backgroundColor}
|
|
114
|
+
size={40}
|
|
115
|
+
/>
|
|
116
|
+
</Flexbox>
|
|
117
|
+
</Link>
|
|
111
118
|
</Tooltip>
|
|
112
119
|
</Flexbox>
|
|
113
120
|
))}
|
|
@@ -4,7 +4,7 @@ import { Form, type FormGroupItemType, Icon, ImageSelect, InputPassword } from '
|
|
|
4
4
|
import { Select } from '@lobehub/ui';
|
|
5
5
|
import { Segmented, Skeleton } from 'antd';
|
|
6
6
|
import isEqual from 'fast-deep-equal';
|
|
7
|
-
import { Ban, Gauge, Loader2Icon, Monitor, Moon, Sun, Waves } from 'lucide-react';
|
|
7
|
+
import { Ban, Gauge, Loader2Icon, Monitor, Moon, Mouse, Sun, Waves } from 'lucide-react';
|
|
8
8
|
import { memo, useState } from 'react';
|
|
9
9
|
import { useTranslation } from 'react-i18next';
|
|
10
10
|
|
|
@@ -113,6 +113,28 @@ const Common = memo(() => {
|
|
|
113
113
|
minWidth: undefined,
|
|
114
114
|
name: 'animationMode',
|
|
115
115
|
},
|
|
116
|
+
{
|
|
117
|
+
children: (
|
|
118
|
+
<Segmented
|
|
119
|
+
options={[
|
|
120
|
+
{
|
|
121
|
+
icon: <Icon icon={Ban} size={16} />,
|
|
122
|
+
label: t('settingAppearance.contextMenuMode.disabled'),
|
|
123
|
+
value: 'disabled',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
icon: <Icon icon={Mouse} size={16} />,
|
|
127
|
+
label: t('settingAppearance.contextMenuMode.default'),
|
|
128
|
+
value: 'default',
|
|
129
|
+
},
|
|
130
|
+
]}
|
|
131
|
+
/>
|
|
132
|
+
),
|
|
133
|
+
desc: t('settingAppearance.contextMenuMode.desc'),
|
|
134
|
+
label: t('settingAppearance.contextMenuMode.title'),
|
|
135
|
+
minWidth: undefined,
|
|
136
|
+
name: 'contextMenuMode',
|
|
137
|
+
},
|
|
116
138
|
|
|
117
139
|
{
|
|
118
140
|
children: (
|
|
@@ -5,6 +5,7 @@ import { type ReactNode, memo, useMemo } from 'react';
|
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import { Flexbox } from 'react-layout-kit';
|
|
7
7
|
|
|
8
|
+
import { MessageContentClassName } from '@/features/ChatList/Messages/Default';
|
|
8
9
|
import { useChatStore } from '@/store/chat';
|
|
9
10
|
import { useUserStore } from '@/store/user';
|
|
10
11
|
import { userGeneralSettingsSelectors } from '@/store/user/selectors';
|
|
@@ -83,7 +84,7 @@ const MessageContent = memo<MessageContentProps>(
|
|
|
83
84
|
|
|
84
85
|
return (
|
|
85
86
|
<Flexbox
|
|
86
|
-
className={cx(styles.message, editing && styles.editingContainer,
|
|
87
|
+
className={cx(styles.message, className, editing && styles.editingContainer, MessageContentClassName)}
|
|
87
88
|
onDoubleClick={onDoubleClick}
|
|
88
89
|
>
|
|
89
90
|
{messageContent}
|
|
@@ -21,6 +21,7 @@ interface AssistantActionsProps {
|
|
|
21
21
|
id: string;
|
|
22
22
|
index: number;
|
|
23
23
|
}
|
|
24
|
+
|
|
24
25
|
export const AssistantActionsBar = memo<AssistantActionsProps>(({ id, data, index }) => {
|
|
25
26
|
const { error, tools } = data;
|
|
26
27
|
const [isThreadMode, hasThread, isRegenerating, isCollapsed] = useChatStore((s) => [
|
|
@@ -6,6 +6,8 @@ import { LOADING_FLAT } from '@/const/message';
|
|
|
6
6
|
import { useChatStore } from '@/store/chat';
|
|
7
7
|
import { messageStateSelectors } from '@/store/chat/selectors';
|
|
8
8
|
|
|
9
|
+
export const MessageContentClassName = 'msg_content_flag'
|
|
10
|
+
|
|
9
11
|
export const DefaultMessage = memo<
|
|
10
12
|
UIChatMessage & {
|
|
11
13
|
addIdOnDOM?: boolean;
|
|
@@ -3,24 +3,28 @@
|
|
|
3
3
|
import { isDesktop } from '@lobechat/const';
|
|
4
4
|
import { createStyles } from 'antd-style';
|
|
5
5
|
import isEqual from 'fast-deep-equal';
|
|
6
|
-
import {
|
|
6
|
+
import { useSearchParams } from 'next/navigation';
|
|
7
|
+
import { MouseEvent, ReactNode, memo, use, useCallback, useEffect, useRef } from 'react';
|
|
7
8
|
import { Flexbox } from 'react-layout-kit';
|
|
8
9
|
|
|
9
|
-
import
|
|
10
|
-
removeVirtuaVisibleItem,
|
|
11
|
-
upsertVirtuaVisibleItem,
|
|
12
|
-
} from '@/features/ChatList/components/VirtualizedList/VirtuosoContext';
|
|
13
|
-
import { getChatStoreState, useChatStore } from '@/store/chat';
|
|
14
|
-
import { displayMessageSelectors, messageStateSelectors } from '@/store/chat/selectors';
|
|
15
|
-
|
|
10
|
+
import ContextMenu from '../components/ContextMenu';
|
|
16
11
|
import History from '../components/History';
|
|
17
12
|
import { InPortalThreadContext } from '../context/InPortalThreadContext';
|
|
13
|
+
import { useChatItemContextMenu } from '../hooks/useChatItemContextMenu';
|
|
18
14
|
import AssistantMessage from './Assistant';
|
|
19
15
|
import GroupMessage from './Group';
|
|
20
16
|
import SupervisorMessage from './Supervisor';
|
|
21
17
|
import ToolMessage from './Tool';
|
|
22
18
|
import UserMessage from './User';
|
|
23
19
|
|
|
20
|
+
import {
|
|
21
|
+
VirtuaContext,
|
|
22
|
+
removeVirtuaVisibleItem,
|
|
23
|
+
upsertVirtuaVisibleItem,
|
|
24
|
+
} from '@/features/ChatList/components/VirtualizedList/VirtuosoContext';
|
|
25
|
+
import { getChatStoreState, useChatStore } from '@/store/chat';
|
|
26
|
+
import { displayMessageSelectors, messageStateSelectors } from '@/store/chat/selectors';
|
|
27
|
+
|
|
24
28
|
const useStyles = createStyles(({ css, prefixCls }) => ({
|
|
25
29
|
loading: css`
|
|
26
30
|
opacity: 0.6;
|
|
@@ -58,17 +62,41 @@ const Item = memo<ChatListItemProps>(
|
|
|
58
62
|
}) => {
|
|
59
63
|
const { styles, cx } = useStyles();
|
|
60
64
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
65
|
+
const virtuaRef = use(VirtuaContext);
|
|
66
|
+
const searchParams = useSearchParams();
|
|
67
|
+
const topic = searchParams.get('topic');
|
|
68
|
+
|
|
69
|
+
const [role, editing, isMessageCreating] = useChatStore(
|
|
70
|
+
(s) => {
|
|
71
|
+
const item = displayMessageSelectors.getDisplayMessageById(id)(s);
|
|
72
|
+
return [
|
|
73
|
+
item?.role,
|
|
74
|
+
messageStateSelectors.isMessageEditing(id)(s),
|
|
75
|
+
messageStateSelectors.isMessageCreating(id)(s),
|
|
76
|
+
];
|
|
77
|
+
},
|
|
78
|
+
isEqual,
|
|
79
|
+
);
|
|
61
80
|
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
81
|
+
const {
|
|
82
|
+
containerRef: contextMenuContainerRef,
|
|
83
|
+
contextMenuState,
|
|
84
|
+
handleContextMenu,
|
|
85
|
+
hideContextMenu,
|
|
86
|
+
} = useChatItemContextMenu({
|
|
87
|
+
editing,
|
|
88
|
+
onActionClick: () => {},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const setContainerRef = useCallback(
|
|
92
|
+
(node: HTMLDivElement | null) => {
|
|
93
|
+
containerRef.current = node;
|
|
94
|
+
contextMenuContainerRef.current = node;
|
|
95
|
+
},
|
|
96
|
+
[contextMenuContainerRef],
|
|
97
|
+
);
|
|
66
98
|
|
|
67
99
|
// ======================= Performance Optimization ======================= //
|
|
68
|
-
// these useMemo/useCallback are all for the performance optimization
|
|
69
|
-
// maybe we can remove it in React 19
|
|
70
|
-
// ======================================================================== //
|
|
71
|
-
|
|
72
100
|
useEffect(() => {
|
|
73
101
|
if (typeof window === 'undefined' || typeof IntersectionObserver === 'undefined') return;
|
|
74
102
|
|
|
@@ -107,22 +135,32 @@ const Item = memo<ChatListItemProps>(
|
|
|
107
135
|
};
|
|
108
136
|
}, [index]);
|
|
109
137
|
|
|
110
|
-
const onContextMenu = useCallback(
|
|
111
|
-
|
|
138
|
+
const onContextMenu = useCallback(
|
|
139
|
+
async (event: MouseEvent<HTMLDivElement>) => {
|
|
140
|
+
if (!role || (role !== 'user' && role !== 'assistant')) return;
|
|
112
141
|
|
|
113
|
-
|
|
114
|
-
|
|
142
|
+
const item = displayMessageSelectors.getDisplayMessageById(id)(getChatStoreState());
|
|
143
|
+
if (!item) return;
|
|
115
144
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
145
|
+
if (isDesktop) {
|
|
146
|
+
const { electronSystemService } = await import('@/services/electron/system');
|
|
147
|
+
|
|
148
|
+
electronSystemService.showContextMenu('chat', {
|
|
149
|
+
content: item.content,
|
|
150
|
+
hasError: !!item.error,
|
|
151
|
+
messageId: id,
|
|
152
|
+
role: item.role,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
handleContextMenu(event);
|
|
159
|
+
},
|
|
160
|
+
[handleContextMenu, id, role],
|
|
161
|
+
);
|
|
124
162
|
|
|
125
|
-
const renderContent =
|
|
163
|
+
const renderContent = useCallback(() => {
|
|
126
164
|
switch (role) {
|
|
127
165
|
case 'user': {
|
|
128
166
|
return <UserMessage disableEditing={disableEditing} id={id} index={index} />;
|
|
@@ -171,11 +209,22 @@ const Item = memo<ChatListItemProps>(
|
|
|
171
209
|
className={cx(styles.message, className, isMessageCreating && styles.loading)}
|
|
172
210
|
data-index={index}
|
|
173
211
|
onContextMenu={onContextMenu}
|
|
174
|
-
ref={
|
|
212
|
+
ref={setContainerRef}
|
|
175
213
|
>
|
|
176
|
-
{renderContent}
|
|
214
|
+
{renderContent()}
|
|
177
215
|
{endRender}
|
|
178
216
|
</Flexbox>
|
|
217
|
+
<ContextMenu
|
|
218
|
+
id={id}
|
|
219
|
+
inPortalThread={inPortalThread}
|
|
220
|
+
index={index}
|
|
221
|
+
onClose={hideContextMenu}
|
|
222
|
+
position={contextMenuState.position}
|
|
223
|
+
selectedText={contextMenuState.selectedText}
|
|
224
|
+
topic={topic}
|
|
225
|
+
virtuaRef={virtuaRef}
|
|
226
|
+
visible={contextMenuState.visible}
|
|
227
|
+
/>
|
|
179
228
|
</InPortalThreadContext.Provider>
|
|
180
229
|
);
|
|
181
230
|
},
|