@lobehub/lobehub 2.0.0-next.324 → 2.0.0-next.326
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 +58 -0
- package/CLAUDE.md +4 -0
- package/apps/desktop/src/main/core/browser/Browser.ts +40 -1
- package/apps/desktop/src/main/core/infrastructure/I18nManager.ts +0 -11
- package/apps/desktop/src/main/core/infrastructure/UpdaterManager.ts +52 -2
- package/apps/desktop/src/main/core/infrastructure/__tests__/UpdaterManager.test.ts +41 -0
- package/changelog/v1.json +10 -0
- package/package.json +2 -2
- package/packages/database/src/models/__tests__/session.test.ts +0 -29
- package/src/app/[variants]/(main)/agent/features/Conversation/AgentWelcome/OpeningQuestions.tsx +0 -2
- package/src/app/[variants]/(main)/community/(detail)/agent/features/Sidebar/TocList/index.tsx +0 -36
- package/src/app/[variants]/(main)/community/(list)/_layout/Header.tsx +0 -2
- package/src/app/[variants]/(main)/group/_layout/Sidebar/GroupConfig/GroupMember.tsx +0 -4
- package/src/app/[variants]/(main)/group/features/Conversation/ConversationArea.tsx +0 -7
- package/src/app/[variants]/(main)/group/features/Conversation/MainChatInput/GroupChat.tsx +0 -2
- package/src/app/[variants]/(main)/home/_layout/Body/index.tsx +0 -2
- package/src/app/[variants]/(main)/page/_layout/Body/List/Item/useDropdownMenu.tsx +0 -6
- package/src/app/[variants]/(main)/page/_layout/Body/useDropdownMenu.tsx +0 -15
- package/src/app/[variants]/(main)/resource/features/DndContextWrapper.tsx +0 -5
- package/src/app/[variants]/(main)/settings/provider/features/ModelList/CreateNewModelModal/index.tsx +0 -1
- package/src/app/[variants]/(main)/settings/provider/features/ModelList/ModelItem.tsx +0 -10
- package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/Checker.tsx +1 -1
- package/src/app/[variants]/(mobile)/(home)/features/SessionListContent/List/Item/Actions.tsx +0 -1
- package/src/app/[variants]/layout.tsx +0 -2
- package/src/envs/__tests__/app.test.ts +0 -6
- package/src/features/ChatInput/ActionBar/Knowledge/useControls.tsx +0 -22
- package/src/features/ChatInput/store/action.ts +0 -2
- package/src/features/Conversation/Messages/Task/TaskDetailPanel/index.tsx +1 -15
- package/src/features/DataImporter/ImportDetail.tsx +0 -20
- package/src/features/DevPanel/features/Table/TableCell.tsx +1 -36
- package/src/features/DevPanel/index.tsx +0 -9
- package/src/features/ModelSwitchPanel/__mocks__/mockEnabledChatModels.ts +159 -0
- package/src/features/ModelSwitchPanel/components/List/{VirtualItemRenderer.tsx → ListItemRenderer.tsx} +15 -25
- package/src/features/ModelSwitchPanel/components/List/MultipleProvidersModelItem.tsx +95 -69
- package/src/features/ModelSwitchPanel/components/List/index.tsx +39 -40
- package/src/features/ModelSwitchPanel/components/PanelContent.tsx +0 -8
- package/src/features/ModelSwitchPanel/hooks/{useBuildVirtualItems.ts → useBuildListItems.ts} +7 -17
- package/src/features/ModelSwitchPanel/index.tsx +24 -23
- package/src/features/ModelSwitchPanel/styles.ts +3 -0
- package/src/features/ModelSwitchPanel/types.ts +3 -8
- package/src/features/ModelSwitchPanel/utils.ts +2 -2
- package/src/features/NavPanel/SideBarDrawer.tsx +12 -2
- package/src/features/NavPanel/SideBarHeaderLayout.tsx +3 -1
- package/src/features/Portal/GroupThread/Body/index.tsx +0 -6
- package/src/features/ResourceManager/components/Header/AddButton.tsx +0 -16
- package/src/features/ShareModal/ShareImage/index.tsx +0 -8
- package/src/hooks/useProviderName.ts +0 -1
- package/src/layout/GlobalProvider/Locale.tsx +0 -12
- package/src/layout/GlobalProvider/index.tsx +0 -1
- package/src/libs/better-auth/sso/helpers.ts +0 -1
- package/src/libs/next/config/define-config.ts +5 -0
- package/src/locales/create.ts +0 -17
- package/src/services/aiChat.ts +0 -4
- package/src/services/debug.ts +1 -34
- package/src/services/models.ts +0 -15
- package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +0 -9
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +0 -3
- package/src/store/chat/slices/aiChat/actions/index.ts +1 -3
- package/src/store/file/slices/chat/action.test.ts +0 -89
- package/src/store/file/slices/chunk/selectors.ts +0 -1
- package/src/store/file/slices/fileManager/selectors.ts +0 -1
- package/src/store/file/slices/tts/selectors.ts +0 -2
- package/src/store/tool/slices/customPlugin/index.ts +0 -1
- package/src/store/tool/slices/mcpStore/index.ts +0 -1
- package/src/store/tool/slices/oldStore/index.ts +0 -1
- package/src/store/tool/slices/plugin/index.ts +0 -1
- package/src/styles/global.ts +6 -0
- package/src/utils/router.tsx +1 -7
- package/src/utils/server/parseModels.ts +0 -1
|
@@ -33,28 +33,6 @@ export const useControls = ({
|
|
|
33
33
|
]);
|
|
34
34
|
|
|
35
35
|
const items: ItemType[] = [
|
|
36
|
-
// {
|
|
37
|
-
// children: [
|
|
38
|
-
// {
|
|
39
|
-
// icon: <RepoIcon />,
|
|
40
|
-
// key: 'allFiles',
|
|
41
|
-
// label: <KnowledgeBaseItem id={'all'} label={t('knowledgeBase.allFiles')} />,
|
|
42
|
-
// },
|
|
43
|
-
// {
|
|
44
|
-
// icon: <RepoIcon />,
|
|
45
|
-
// key: 'allRepos',
|
|
46
|
-
// label: <KnowledgeBaseItem id={'all'} label={t('knowledgeBase.allLibraries')} />,
|
|
47
|
-
// },
|
|
48
|
-
// ],
|
|
49
|
-
// key: 'all',
|
|
50
|
-
// label: (
|
|
51
|
-
// <Flexbox horizontal justify={'space-between'}>
|
|
52
|
-
// {t('knowledgeBase.all')}
|
|
53
|
-
// {/*<Link href={'/files'}>{t('knowledgeBase.more')}</Link>*/}
|
|
54
|
-
// </Flexbox>
|
|
55
|
-
// ),
|
|
56
|
-
// type: 'group',
|
|
57
|
-
// },
|
|
58
36
|
{
|
|
59
37
|
children: [
|
|
60
38
|
// first the files
|
|
@@ -17,21 +17,7 @@ interface TaskDetailPanelProps {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const TaskDetailPanel = memo<TaskDetailPanelProps>(({ taskDetail, content, messageId }) => {
|
|
20
|
-
return
|
|
21
|
-
<>
|
|
22
|
-
{/* Instruction Header */}
|
|
23
|
-
{/*{instruction && (*/}
|
|
24
|
-
{/* <Block padding={12}>*/}
|
|
25
|
-
{/* <Text fontSize={13} type={'secondary'}>*/}
|
|
26
|
-
{/* {instruction}*/}
|
|
27
|
-
{/* </Text>*/}
|
|
28
|
-
{/* </Block>*/}
|
|
29
|
-
{/*)}*/}
|
|
30
|
-
|
|
31
|
-
{/* Status Content */}
|
|
32
|
-
<StatusContent content={content} messageId={messageId} taskDetail={taskDetail} />
|
|
33
|
-
</>
|
|
34
|
-
);
|
|
20
|
+
return <StatusContent content={content} messageId={messageId} taskDetail={taskDetail} />;
|
|
35
21
|
});
|
|
36
22
|
|
|
37
23
|
TaskDetailPanel.displayName = 'TaskDetailPanel';
|
|
@@ -170,26 +170,6 @@ const ImportPreviewModal = ({
|
|
|
170
170
|
size="small"
|
|
171
171
|
/>
|
|
172
172
|
</div>
|
|
173
|
-
|
|
174
|
-
{/*<Flexbox>*/}
|
|
175
|
-
{/* 重复数据处理方式:*/}
|
|
176
|
-
{/* <div className={styles.duplicateOptions}>*/}
|
|
177
|
-
{/* <Radio.Group*/}
|
|
178
|
-
{/* onChange={(e) => setDuplicateAction(e.target.value)}*/}
|
|
179
|
-
{/* value={duplicateAction}*/}
|
|
180
|
-
{/* >*/}
|
|
181
|
-
{/* <Space>*/}
|
|
182
|
-
{/* <Radio value="skip">跳过</Radio>*/}
|
|
183
|
-
{/* <Radio value="overwrite">覆盖</Radio>*/}
|
|
184
|
-
{/* </Space>*/}
|
|
185
|
-
{/* </Radio.Group>*/}
|
|
186
|
-
{/* </div>*/}
|
|
187
|
-
{/* <div className={styles.duplicateDescription}>*/}
|
|
188
|
-
{/* {duplicateAction === 'skip'*/}
|
|
189
|
-
{/* ? '选择跳过将仅导入不重复的数据,保留现有数据不变。'*/}
|
|
190
|
-
{/* : '选择覆盖将使用导入数据替换系统中具有相同 ID 的现有记录。'}*/}
|
|
191
|
-
{/* </div>*/}
|
|
192
|
-
{/*</Flexbox>*/}
|
|
193
173
|
</Flexbox>
|
|
194
174
|
</div>
|
|
195
175
|
</Modal>
|
|
@@ -2,27 +2,6 @@ import dayjs from 'dayjs';
|
|
|
2
2
|
import { get, isDate } from 'es-toolkit/compat';
|
|
3
3
|
import React, { useMemo } from 'react';
|
|
4
4
|
|
|
5
|
-
// import TooltipContent from './TooltipContent';
|
|
6
|
-
|
|
7
|
-
// const { Text } = Typography;
|
|
8
|
-
|
|
9
|
-
// const useStyles = createStyles(({ token, css }) => ({
|
|
10
|
-
// cell: css`
|
|
11
|
-
// font-family: ${token.fontFamilyCode};
|
|
12
|
-
// font-size: ${token.fontSizeSM}px;
|
|
13
|
-
// `,
|
|
14
|
-
// tooltip: css`
|
|
15
|
-
// border: 1px solid ${token.colorBorder};
|
|
16
|
-
//
|
|
17
|
-
// font-family: ${token.fontFamilyCode};
|
|
18
|
-
// font-size: ${token.fontSizeSM}px;
|
|
19
|
-
// color: ${token.colorText} !important;
|
|
20
|
-
// word-break: break-all;
|
|
21
|
-
//
|
|
22
|
-
// background: ${token.colorBgElevated} !important;
|
|
23
|
-
// `,
|
|
24
|
-
// }));
|
|
25
|
-
|
|
26
5
|
interface TableCellProps {
|
|
27
6
|
column: string;
|
|
28
7
|
dataItem: any;
|
|
@@ -30,7 +9,6 @@ interface TableCellProps {
|
|
|
30
9
|
}
|
|
31
10
|
|
|
32
11
|
const TableCell = ({ dataItem, column, rowIndex }: TableCellProps) => {
|
|
33
|
-
// const { styles } = useStyles();
|
|
34
12
|
const data = get(dataItem, column);
|
|
35
13
|
const content = useMemo(() => {
|
|
36
14
|
if (isDate(data)) return dayjs(data).format('YYYY-MM-DD HH:mm:ss');
|
|
@@ -52,21 +30,8 @@ const TableCell = ({ dataItem, column, rowIndex }: TableCellProps) => {
|
|
|
52
30
|
|
|
53
31
|
return (
|
|
54
32
|
<td key={column} onDoubleClick={() => console.log('Edit cell:', rowIndex, column)}>
|
|
55
|
-
{content}
|
|
56
|
-
|
|
57
33
|
{/* 不能使用 antd 的 Text, 会有大量的重渲染导致滚动极其卡顿 */}
|
|
58
|
-
{
|
|
59
|
-
{/* className={styles.cell}*/}
|
|
60
|
-
{/* ellipsis={{*/}
|
|
61
|
-
{/* tooltip: {*/}
|
|
62
|
-
{/* arrow: false,*/}
|
|
63
|
-
{/* classNames: { body: styles.tooltip },*/}
|
|
64
|
-
{/* title: <TooltipContent>{content}</TooltipContent>,*/}
|
|
65
|
-
{/* },*/}
|
|
66
|
-
{/* }}*/}
|
|
67
|
-
{/*>*/}
|
|
68
|
-
{/* {content}*/}
|
|
69
|
-
{/*</Text>*/}
|
|
34
|
+
{content}
|
|
70
35
|
</td>
|
|
71
36
|
);
|
|
72
37
|
};
|
|
@@ -15,15 +15,6 @@ const FloatPanel = dynamic(() => import('./features/FloatPanel'), {
|
|
|
15
15
|
const DevPanel = () => (
|
|
16
16
|
<FloatPanel
|
|
17
17
|
items={[
|
|
18
|
-
// ...(isDesktop
|
|
19
|
-
// ? [
|
|
20
|
-
// {
|
|
21
|
-
// children: <PostgresViewer />,
|
|
22
|
-
// icon: <DatabaseIcon size={16} />,
|
|
23
|
-
// key: 'Postgres Viewer',
|
|
24
|
-
// },
|
|
25
|
-
// ]
|
|
26
|
-
// : []),
|
|
27
18
|
{
|
|
28
19
|
children: <MetadataViewer />,
|
|
29
20
|
icon: <BookText size={16} />,
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { AiModelForSelect } from 'model-bank';
|
|
2
|
+
|
|
3
|
+
import type { EnabledProviderWithModels } from '@/types/aiProvider';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Mock data for testing ModelSwitchPanel
|
|
7
|
+
*
|
|
8
|
+
* This data includes:
|
|
9
|
+
* - Multiple providers (OpenAI, Azure, Ollama)
|
|
10
|
+
* - Same model provided by multiple providers (gpt-4o -> model-item-multiple)
|
|
11
|
+
* - Single provider model (llama3 -> model-item-single)
|
|
12
|
+
*/
|
|
13
|
+
export const mockEnabledChatModels: EnabledProviderWithModels[] = [
|
|
14
|
+
{
|
|
15
|
+
children: [
|
|
16
|
+
{
|
|
17
|
+
abilities: {
|
|
18
|
+
functionCall: true,
|
|
19
|
+
reasoning: false,
|
|
20
|
+
vision: true,
|
|
21
|
+
},
|
|
22
|
+
contextWindowTokens: 128_000,
|
|
23
|
+
displayName: 'GPT-4o',
|
|
24
|
+
id: 'gpt-4o',
|
|
25
|
+
maxOutput: 16_384,
|
|
26
|
+
releasedAt: '2024-05-13',
|
|
27
|
+
type: 'chat',
|
|
28
|
+
} as AiModelForSelect,
|
|
29
|
+
{
|
|
30
|
+
abilities: {
|
|
31
|
+
functionCall: true,
|
|
32
|
+
reasoning: false,
|
|
33
|
+
vision: true,
|
|
34
|
+
},
|
|
35
|
+
contextWindowTokens: 128_000,
|
|
36
|
+
displayName: 'GPT-4o Mini',
|
|
37
|
+
id: 'gpt-4o-mini',
|
|
38
|
+
maxOutput: 16_384,
|
|
39
|
+
releasedAt: '2024-07-18',
|
|
40
|
+
type: 'chat',
|
|
41
|
+
} as AiModelForSelect,
|
|
42
|
+
{
|
|
43
|
+
abilities: {
|
|
44
|
+
functionCall: true,
|
|
45
|
+
reasoning: true,
|
|
46
|
+
vision: false,
|
|
47
|
+
},
|
|
48
|
+
contextWindowTokens: 200_000,
|
|
49
|
+
displayName: 'o1',
|
|
50
|
+
id: 'o1',
|
|
51
|
+
maxOutput: 100_000,
|
|
52
|
+
releasedAt: '2024-12-17',
|
|
53
|
+
type: 'chat',
|
|
54
|
+
} as AiModelForSelect,
|
|
55
|
+
],
|
|
56
|
+
id: 'openai',
|
|
57
|
+
logo: 'https://registry.npmmirror.com/@lobehub/icons-static-png/1.45.0/files/dark/openai.png',
|
|
58
|
+
name: 'OpenAI',
|
|
59
|
+
source: 'builtin',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
children: [
|
|
63
|
+
{
|
|
64
|
+
// Same displayName as OpenAI's gpt-4o -> will create model-item-multiple
|
|
65
|
+
abilities: {
|
|
66
|
+
functionCall: true,
|
|
67
|
+
reasoning: false,
|
|
68
|
+
vision: true,
|
|
69
|
+
},
|
|
70
|
+
contextWindowTokens: 128_000,
|
|
71
|
+
displayName: 'GPT-4o',
|
|
72
|
+
id: 'gpt-4o',
|
|
73
|
+
maxOutput: 16_384,
|
|
74
|
+
type: 'chat',
|
|
75
|
+
} as AiModelForSelect,
|
|
76
|
+
{
|
|
77
|
+
// Same displayName as OpenAI's gpt-4o-mini -> will create model-item-multiple
|
|
78
|
+
abilities: {
|
|
79
|
+
functionCall: true,
|
|
80
|
+
reasoning: false,
|
|
81
|
+
vision: true,
|
|
82
|
+
},
|
|
83
|
+
contextWindowTokens: 128_000,
|
|
84
|
+
displayName: 'GPT-4o Mini',
|
|
85
|
+
id: 'gpt-4o-mini',
|
|
86
|
+
maxOutput: 16_384,
|
|
87
|
+
type: 'chat',
|
|
88
|
+
} as AiModelForSelect,
|
|
89
|
+
],
|
|
90
|
+
id: 'azure',
|
|
91
|
+
logo: 'https://registry.npmmirror.com/@lobehub/icons-static-png/1.45.0/files/dark/azure.png',
|
|
92
|
+
name: 'Azure OpenAI',
|
|
93
|
+
source: 'builtin',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
children: [
|
|
97
|
+
{
|
|
98
|
+
// Unique model -> will create model-item-single
|
|
99
|
+
abilities: {
|
|
100
|
+
functionCall: true,
|
|
101
|
+
reasoning: false,
|
|
102
|
+
vision: false,
|
|
103
|
+
},
|
|
104
|
+
contextWindowTokens: 128_000,
|
|
105
|
+
displayName: 'Llama 3.3 70B',
|
|
106
|
+
id: 'llama3.3:70b',
|
|
107
|
+
maxOutput: 8192,
|
|
108
|
+
type: 'chat',
|
|
109
|
+
} as AiModelForSelect,
|
|
110
|
+
{
|
|
111
|
+
abilities: {
|
|
112
|
+
functionCall: false,
|
|
113
|
+
reasoning: false,
|
|
114
|
+
vision: true,
|
|
115
|
+
},
|
|
116
|
+
contextWindowTokens: 128_000,
|
|
117
|
+
displayName: 'Llava',
|
|
118
|
+
id: 'llava:latest',
|
|
119
|
+
maxOutput: 4096,
|
|
120
|
+
type: 'chat',
|
|
121
|
+
} as AiModelForSelect,
|
|
122
|
+
],
|
|
123
|
+
id: 'ollama',
|
|
124
|
+
logo: 'https://registry.npmmirror.com/@lobehub/icons-static-png/1.45.0/files/dark/ollama.png',
|
|
125
|
+
name: 'Ollama',
|
|
126
|
+
source: 'builtin',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
children: [
|
|
130
|
+
{
|
|
131
|
+
// Same as OpenAI's o1 -> will create model-item-multiple
|
|
132
|
+
abilities: {
|
|
133
|
+
functionCall: true,
|
|
134
|
+
reasoning: true,
|
|
135
|
+
vision: false,
|
|
136
|
+
},
|
|
137
|
+
contextWindowTokens: 200_000,
|
|
138
|
+
displayName: 'o1',
|
|
139
|
+
id: 'o1',
|
|
140
|
+
maxOutput: 100_000,
|
|
141
|
+
type: 'chat',
|
|
142
|
+
} as AiModelForSelect,
|
|
143
|
+
],
|
|
144
|
+
id: 'openrouter',
|
|
145
|
+
logo: 'https://registry.npmmirror.com/@lobehub/icons-static-png/1.45.0/files/dark/openrouter.png',
|
|
146
|
+
name: 'OpenRouter',
|
|
147
|
+
source: 'builtin',
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Expected result when groupMode = 'byModel':
|
|
153
|
+
*
|
|
154
|
+
* - GPT-4o (model-item-multiple) -> OpenAI, Azure
|
|
155
|
+
* - GPT-4o Mini (model-item-multiple) -> OpenAI, Azure
|
|
156
|
+
* - Llama 3.3 70B (model-item-single) -> Ollama
|
|
157
|
+
* - Llava (model-item-single) -> Ollama
|
|
158
|
+
* - o1 (model-item-multiple) -> OpenAI, OpenRouter
|
|
159
|
+
*/
|
|
@@ -9,21 +9,22 @@ import urlJoin from 'url-join';
|
|
|
9
9
|
import { ModelItemRender, ProviderItemRender } from '@/components/ModelSelect';
|
|
10
10
|
|
|
11
11
|
import { styles } from '../../styles';
|
|
12
|
-
import type {
|
|
12
|
+
import type { ListItem } from '../../types';
|
|
13
13
|
import { menuKey } from '../../utils';
|
|
14
14
|
import { MultipleProvidersModelItem } from './MultipleProvidersModelItem';
|
|
15
15
|
import { SingleProviderModelItem } from './SingleProviderModelItem';
|
|
16
16
|
|
|
17
|
-
interface
|
|
17
|
+
interface ListItemRendererProps {
|
|
18
18
|
activeKey: string;
|
|
19
|
-
|
|
19
|
+
isScrolling: boolean;
|
|
20
|
+
item: ListItem;
|
|
20
21
|
newLabel: string;
|
|
21
22
|
onClose: () => void;
|
|
22
23
|
onModelChange: (modelId: string, providerId: string) => Promise<void>;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
export const
|
|
26
|
-
({ activeKey, item, newLabel, onModelChange, onClose }) => {
|
|
26
|
+
export const ListItemRenderer = memo<ListItemRendererProps>(
|
|
27
|
+
({ activeKey, isScrolling, item, newLabel, onModelChange, onClose }) => {
|
|
27
28
|
const { t } = useTranslation('components');
|
|
28
29
|
const navigate = useNavigate();
|
|
29
30
|
|
|
@@ -145,27 +146,16 @@ export const VirtualItemRenderer = memo<VirtualItemRendererProps>(
|
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
case 'model-item-multiple': {
|
|
148
|
-
// Check if any provider of this model is active
|
|
149
|
-
const activeProvider = item.data.providers.find(
|
|
150
|
-
(p) => menuKey(p.id, item.data.model.id) === activeKey,
|
|
151
|
-
);
|
|
152
|
-
const isActive = !!activeProvider;
|
|
153
|
-
|
|
154
149
|
return (
|
|
155
|
-
<
|
|
156
|
-
|
|
157
|
-
|
|
150
|
+
<MultipleProvidersModelItem
|
|
151
|
+
activeKey={activeKey}
|
|
152
|
+
data={item.data}
|
|
153
|
+
isScrolling={isScrolling}
|
|
158
154
|
key={item.data.displayName}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
data={item.data}
|
|
164
|
-
newLabel={newLabel}
|
|
165
|
-
onClose={onClose}
|
|
166
|
-
onModelChange={onModelChange}
|
|
167
|
-
/>
|
|
168
|
-
</Block>
|
|
155
|
+
newLabel={newLabel}
|
|
156
|
+
onClose={onClose}
|
|
157
|
+
onModelChange={onModelChange}
|
|
158
|
+
/>
|
|
169
159
|
);
|
|
170
160
|
}
|
|
171
161
|
|
|
@@ -176,4 +166,4 @@ export const VirtualItemRenderer = memo<VirtualItemRendererProps>(
|
|
|
176
166
|
},
|
|
177
167
|
);
|
|
178
168
|
|
|
179
|
-
|
|
169
|
+
ListItemRenderer.displayName = 'ListItemRenderer';
|
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ActionIcon,
|
|
3
|
+
DropdownMenuGroup,
|
|
4
|
+
DropdownMenuGroupLabel,
|
|
5
|
+
DropdownMenuItem,
|
|
6
|
+
DropdownMenuItemExtra,
|
|
7
|
+
DropdownMenuItemIcon,
|
|
8
|
+
DropdownMenuItemLabel,
|
|
9
|
+
DropdownMenuPopup,
|
|
10
|
+
DropdownMenuPortal,
|
|
11
|
+
DropdownMenuPositioner,
|
|
12
|
+
DropdownMenuSubmenuRoot,
|
|
13
|
+
DropdownMenuSubmenuTrigger,
|
|
14
|
+
menuSharedStyles,
|
|
15
|
+
} from '@lobehub/ui';
|
|
16
|
+
import { cx } from 'antd-style';
|
|
2
17
|
import { Check, LucideBolt } from 'lucide-react';
|
|
3
|
-
import { memo,
|
|
18
|
+
import { memo, useEffect, useState } from 'react';
|
|
4
19
|
import { useTranslation } from 'react-i18next';
|
|
5
20
|
import { useNavigate } from 'react-router-dom';
|
|
6
21
|
import urlJoin from 'url-join';
|
|
@@ -14,85 +29,96 @@ import { menuKey } from '../../utils';
|
|
|
14
29
|
interface MultipleProvidersModelItemProps {
|
|
15
30
|
activeKey: string;
|
|
16
31
|
data: ModelWithProviders;
|
|
32
|
+
isScrolling: boolean;
|
|
17
33
|
newLabel: string;
|
|
18
34
|
onClose: () => void;
|
|
19
35
|
onModelChange: (modelId: string, providerId: string) => Promise<void>;
|
|
20
36
|
}
|
|
21
37
|
|
|
22
38
|
export const MultipleProvidersModelItem = memo<MultipleProvidersModelItemProps>(
|
|
23
|
-
({ activeKey, data, newLabel, onModelChange, onClose }) => {
|
|
39
|
+
({ activeKey, data, isScrolling, newLabel, onModelChange, onClose }) => {
|
|
24
40
|
const { t } = useTranslation('components');
|
|
25
41
|
const navigate = useNavigate();
|
|
42
|
+
const [submenuOpen, setSubmenuOpen] = useState(false);
|
|
26
43
|
|
|
27
|
-
|
|
28
|
-
()
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
label: t('ModelSwitchPanel.useModelFrom'),
|
|
33
|
-
type: 'group',
|
|
34
|
-
},
|
|
35
|
-
...data.providers.map((p) => {
|
|
36
|
-
const key = menuKey(p.id, data.model.id);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (isScrolling) {
|
|
46
|
+
setSubmenuOpen(false);
|
|
47
|
+
}
|
|
48
|
+
}, [isScrolling]);
|
|
37
49
|
|
|
38
|
-
|
|
39
|
-
extra: (
|
|
40
|
-
<ActionIcon
|
|
41
|
-
className={'settings-icon'}
|
|
42
|
-
icon={LucideBolt}
|
|
43
|
-
onClick={(e) => {
|
|
44
|
-
e.preventDefault();
|
|
45
|
-
e.stopPropagation();
|
|
46
|
-
const url = urlJoin('/settings/provider', p.id || 'all');
|
|
47
|
-
if (e.ctrlKey || e.metaKey) {
|
|
48
|
-
window.open(url, '_blank');
|
|
49
|
-
} else {
|
|
50
|
-
navigate(url);
|
|
51
|
-
}
|
|
52
|
-
}}
|
|
53
|
-
size={'small'}
|
|
54
|
-
title={t('ModelSwitchPanel.goToSettings')}
|
|
55
|
-
/>
|
|
56
|
-
),
|
|
57
|
-
icon: activeKey === key ? Check : undefined,
|
|
58
|
-
key,
|
|
59
|
-
label: (
|
|
60
|
-
<ProviderItemRender
|
|
61
|
-
logo={p.logo}
|
|
62
|
-
name={p.name}
|
|
63
|
-
provider={p.id}
|
|
64
|
-
size={20}
|
|
65
|
-
source={p.source}
|
|
66
|
-
type={'avatar'}
|
|
67
|
-
/>
|
|
68
|
-
),
|
|
69
|
-
onClick: async () => {
|
|
70
|
-
onModelChange(data.model.id, p.id);
|
|
71
|
-
onClose();
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
}),
|
|
75
|
-
] as DropdownItem[],
|
|
76
|
-
[activeKey, data.model.id, data.providers, navigate, onModelChange, onClose, t],
|
|
77
|
-
);
|
|
50
|
+
const isActive = data.providers.some((p) => menuKey(p.id, data.model.id) === activeKey);
|
|
78
51
|
|
|
79
52
|
return (
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
53
|
+
<DropdownMenuSubmenuRoot onOpenChange={setSubmenuOpen} open={submenuOpen}>
|
|
54
|
+
<DropdownMenuSubmenuTrigger
|
|
55
|
+
className={cx(menuSharedStyles.item, isActive && styles.menuItemActive)}
|
|
56
|
+
>
|
|
57
|
+
<ModelItemRender
|
|
58
|
+
{...data.model}
|
|
59
|
+
{...data.model.abilities}
|
|
60
|
+
newBadgeLabel={newLabel}
|
|
61
|
+
showInfoTag={true}
|
|
62
|
+
/>
|
|
63
|
+
</DropdownMenuSubmenuTrigger>
|
|
64
|
+
<DropdownMenuPortal>
|
|
65
|
+
<DropdownMenuPositioner anchor={null} placement="rightTop" sideOffset={-4}>
|
|
66
|
+
<DropdownMenuPopup className={styles.dropdownMenu}>
|
|
67
|
+
<DropdownMenuGroup>
|
|
68
|
+
<DropdownMenuGroupLabel>
|
|
69
|
+
{t('ModelSwitchPanel.useModelFrom')}
|
|
70
|
+
</DropdownMenuGroupLabel>
|
|
71
|
+
{data.providers.map((p) => {
|
|
72
|
+
const key = menuKey(p.id, data.model.id);
|
|
73
|
+
const isProviderActive = activeKey === key;
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<DropdownMenuItem
|
|
77
|
+
key={key}
|
|
78
|
+
onClick={async () => {
|
|
79
|
+
await onModelChange(data.model.id, p.id);
|
|
80
|
+
onClose();
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
<DropdownMenuItemIcon>
|
|
84
|
+
{isProviderActive ? <Check size={16} /> : null}
|
|
85
|
+
</DropdownMenuItemIcon>
|
|
86
|
+
<DropdownMenuItemLabel>
|
|
87
|
+
<ProviderItemRender
|
|
88
|
+
logo={p.logo}
|
|
89
|
+
name={p.name}
|
|
90
|
+
provider={p.id}
|
|
91
|
+
size={20}
|
|
92
|
+
source={p.source}
|
|
93
|
+
type={'avatar'}
|
|
94
|
+
/>
|
|
95
|
+
</DropdownMenuItemLabel>
|
|
96
|
+
<DropdownMenuItemExtra>
|
|
97
|
+
<ActionIcon
|
|
98
|
+
className={'settings-icon'}
|
|
99
|
+
icon={LucideBolt}
|
|
100
|
+
onClick={(e) => {
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
e.stopPropagation();
|
|
103
|
+
const url = urlJoin('/settings/provider', p.id || 'all');
|
|
104
|
+
if (e.ctrlKey || e.metaKey) {
|
|
105
|
+
window.open(url, '_blank');
|
|
106
|
+
} else {
|
|
107
|
+
navigate(url);
|
|
108
|
+
}
|
|
109
|
+
}}
|
|
110
|
+
size={'small'}
|
|
111
|
+
title={t('ModelSwitchPanel.goToSettings')}
|
|
112
|
+
/>
|
|
113
|
+
</DropdownMenuItemExtra>
|
|
114
|
+
</DropdownMenuItem>
|
|
115
|
+
);
|
|
116
|
+
})}
|
|
117
|
+
</DropdownMenuGroup>
|
|
118
|
+
</DropdownMenuPopup>
|
|
119
|
+
</DropdownMenuPositioner>
|
|
120
|
+
</DropdownMenuPortal>
|
|
121
|
+
</DropdownMenuSubmenuRoot>
|
|
96
122
|
);
|
|
97
123
|
},
|
|
98
124
|
);
|