@lobehub/lobehub 2.0.0-next.95 → 2.0.0-next.96
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/locales/ar/common.json +21 -0
- package/locales/ar/hotkey.json +4 -0
- package/locales/bg-BG/common.json +21 -0
- package/locales/bg-BG/hotkey.json +4 -0
- package/locales/de-DE/common.json +21 -0
- package/locales/de-DE/hotkey.json +4 -0
- package/locales/en-US/common.json +21 -0
- package/locales/en-US/hotkey.json +4 -0
- package/locales/es-ES/common.json +21 -0
- package/locales/es-ES/hotkey.json +4 -0
- package/locales/fa-IR/common.json +21 -0
- package/locales/fa-IR/hotkey.json +4 -0
- package/locales/fr-FR/common.json +21 -0
- package/locales/fr-FR/hotkey.json +4 -0
- package/locales/it-IT/common.json +21 -0
- package/locales/it-IT/hotkey.json +4 -0
- package/locales/ja-JP/common.json +21 -0
- package/locales/ja-JP/hotkey.json +4 -0
- package/locales/ko-KR/common.json +21 -0
- package/locales/ko-KR/hotkey.json +4 -0
- package/locales/nl-NL/common.json +21 -0
- package/locales/nl-NL/hotkey.json +4 -0
- package/locales/pl-PL/common.json +21 -0
- package/locales/pl-PL/hotkey.json +4 -0
- package/locales/pt-BR/common.json +21 -0
- package/locales/pt-BR/hotkey.json +4 -0
- package/locales/ru-RU/common.json +21 -0
- package/locales/ru-RU/hotkey.json +4 -0
- package/locales/tr-TR/common.json +21 -0
- package/locales/tr-TR/hotkey.json +4 -0
- package/locales/vi-VN/common.json +21 -0
- package/locales/vi-VN/hotkey.json +4 -0
- package/locales/zh-CN/common.json +21 -0
- package/locales/zh-CN/hotkey.json +4 -0
- package/locales/zh-TW/common.json +21 -0
- package/locales/zh-TW/hotkey.json +4 -0
- package/package.json +3 -1
- package/packages/const/src/hotkeys.ts +6 -0
- package/packages/conversation-flow/src/__tests__/indexing.test.ts +513 -0
- package/packages/conversation-flow/src/__tests__/structuring.test.ts +600 -0
- package/packages/types/src/hotkey.ts +1 -0
- package/src/app/[variants]/(main)/settings/_layout/Desktop/index.tsx +41 -8
- package/src/app/[variants]/(main)/settings/provider/(list)/ProviderGrid/Card.tsx +6 -4
- package/src/app/[variants]/(main)/settings/provider/(list)/ProviderGrid/index.tsx +16 -4
- package/src/app/[variants]/(main)/settings/provider/(list)/index.tsx +15 -3
- package/src/app/[variants]/(main)/settings/provider/detail/index.tsx +23 -15
- package/src/layout/GlobalProvider/Cmdk.tsx +470 -0
- package/src/layout/GlobalProvider/CmdkLazy.tsx +17 -0
- package/src/layout/GlobalProvider/index.tsx +2 -0
- package/src/locales/default/common.ts +21 -0
- package/src/locales/default/hotkey.ts +4 -0
|
@@ -5,7 +5,6 @@ import { memo } from 'react';
|
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import { Flexbox } from 'react-layout-kit';
|
|
7
7
|
|
|
8
|
-
import Link from '@/components/Link';
|
|
9
8
|
import { AiProviderListItem } from '@/types/aiProvider';
|
|
10
9
|
|
|
11
10
|
import EnableSwitch from './EnableSwitch';
|
|
@@ -13,9 +12,10 @@ import { useStyles } from './style';
|
|
|
13
12
|
|
|
14
13
|
interface ProviderCardProps extends AiProviderListItem {
|
|
15
14
|
loading?: boolean;
|
|
15
|
+
onProviderSelect: (provider: string) => void;
|
|
16
16
|
}
|
|
17
17
|
const ProviderCard = memo<ProviderCardProps>(
|
|
18
|
-
({ id, description, name, enabled, source, logo, loading }) => {
|
|
18
|
+
({ id, description, name, enabled, source, logo, loading, onProviderSelect }) => {
|
|
19
19
|
const { t } = useTranslation('providers');
|
|
20
20
|
const { cx, styles, theme } = useStyles();
|
|
21
21
|
|
|
@@ -33,7 +33,9 @@ const ProviderCard = memo<ProviderCardProps>(
|
|
|
33
33
|
return (
|
|
34
34
|
<Flexbox className={cx(styles.container)} gap={24}>
|
|
35
35
|
<Flexbox gap={12} padding={16} width={'100%'}>
|
|
36
|
-
<
|
|
36
|
+
<div onClick={() => {
|
|
37
|
+
onProviderSelect(id);
|
|
38
|
+
}} style={{ cursor: 'pointer' }}>
|
|
37
39
|
<Flexbox gap={12} width={'100%'}>
|
|
38
40
|
<Flexbox align={'center'} horizontal justify={'space-between'}>
|
|
39
41
|
{source === 'builtin' ? (
|
|
@@ -68,7 +70,7 @@ const ProviderCard = memo<ProviderCardProps>(
|
|
|
68
70
|
{source === 'custom' ? description : t(`${id}.description`)}
|
|
69
71
|
</Text>
|
|
70
72
|
</Flexbox>
|
|
71
|
-
</
|
|
73
|
+
</div>
|
|
72
74
|
<Divider style={{ margin: '4px 0' }} />
|
|
73
75
|
<Flexbox horizontal justify={'space-between'}>
|
|
74
76
|
<div />
|
|
@@ -14,7 +14,12 @@ const loadingArr = Array.from({ length: 12 })
|
|
|
14
14
|
.fill('-')
|
|
15
15
|
.map((item, index) => `${index}x${item}`);
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
type ListProps = {
|
|
18
|
+
onProviderSelect: (provider: string) => void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const List = memo((props: ListProps) => {
|
|
22
|
+
const { onProviderSelect } = props;
|
|
18
23
|
const { t } = useTranslation('modelProvider');
|
|
19
24
|
const enabledList = useAiInfraStore(aiProviderSelectors.enabledAiProviderList, isEqual);
|
|
20
25
|
const disabledList = useAiInfraStore(aiProviderSelectors.disabledAiProviderList, isEqual);
|
|
@@ -30,7 +35,14 @@ const List = memo(() => {
|
|
|
30
35
|
</Flexbox>
|
|
31
36
|
<Grid gap={16} rows={3}>
|
|
32
37
|
{loadingArr.map((item) => (
|
|
33
|
-
<Card
|
|
38
|
+
<Card
|
|
39
|
+
enabled={false}
|
|
40
|
+
id={item}
|
|
41
|
+
key={item}
|
|
42
|
+
loading
|
|
43
|
+
onProviderSelect={onProviderSelect}
|
|
44
|
+
source={'builtin'}
|
|
45
|
+
/>
|
|
34
46
|
))}
|
|
35
47
|
</Grid>
|
|
36
48
|
</Flexbox>
|
|
@@ -47,7 +59,7 @@ const List = memo(() => {
|
|
|
47
59
|
</Flexbox>
|
|
48
60
|
<Grid gap={16} rows={3}>
|
|
49
61
|
{enabledList.map((item) => (
|
|
50
|
-
<Card {...item} key={item.id} />
|
|
62
|
+
<Card {...item} key={item.id} onProviderSelect={onProviderSelect} />
|
|
51
63
|
))}
|
|
52
64
|
</Grid>
|
|
53
65
|
</Flexbox>
|
|
@@ -60,7 +72,7 @@ const List = memo(() => {
|
|
|
60
72
|
</Flexbox>
|
|
61
73
|
<Grid gap={16} rows={3}>
|
|
62
74
|
{disabledList.map((item) => (
|
|
63
|
-
<Card {...item} key={item.id} />
|
|
75
|
+
<Card {...item} key={item.id} onProviderSelect={onProviderSelect} />
|
|
64
76
|
))}
|
|
65
77
|
</Grid>
|
|
66
78
|
</Flexbox>
|
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { isCustomBranding } from '@/const/version';
|
|
4
|
-
import { parseAsString, useQueryParam } from '@/hooks/useQueryParam';
|
|
5
4
|
|
|
6
5
|
import DesktopLayout from '../_layout/Desktop';
|
|
7
6
|
import MobileLayout from '../_layout/Mobile';
|
|
8
7
|
import ProviderDetailPage from '../detail';
|
|
9
8
|
import Footer from './Footer';
|
|
9
|
+
import { useMemo, useState } from 'react';
|
|
10
|
+
import { useSearchParams } from 'react-router-dom';
|
|
10
11
|
|
|
11
12
|
const Page = (props: { mobile?: boolean }) => {
|
|
12
|
-
const [
|
|
13
|
+
const [SearchParams, setSearchParams] = useSearchParams();
|
|
14
|
+
const [provider, setProviderState] = useState(SearchParams.get('provider') || 'all');
|
|
15
|
+
const setProvider = (provider: string) => {
|
|
16
|
+
setSearchParams({ active: 'provider', provider });
|
|
17
|
+
setProviderState(provider);
|
|
18
|
+
};
|
|
19
|
+
|
|
13
20
|
const { mobile } = props;
|
|
14
21
|
const ProviderLayout = mobile ? MobileLayout : DesktopLayout;
|
|
22
|
+
|
|
23
|
+
const ProviderListPage = useMemo(() => {
|
|
24
|
+
return <ProviderDetailPage id={provider} onProviderSelect={setProvider} />;
|
|
25
|
+
}, [provider]);
|
|
26
|
+
|
|
15
27
|
return (
|
|
16
28
|
<ProviderLayout onProviderSelect={setProvider}>
|
|
17
|
-
|
|
29
|
+
{ProviderListPage}
|
|
18
30
|
{!isCustomBranding && <Footer />}
|
|
19
31
|
</ProviderLayout>
|
|
20
32
|
);
|
|
@@ -1,22 +1,30 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import Azure from './azure';
|
|
4
|
-
import AzureAI from './azureai';
|
|
5
|
-
import Bedrock from './bedrock';
|
|
6
|
-
import Cloudflare from './cloudflare';
|
|
7
|
-
import ComfyUI from './comfyui';
|
|
8
|
-
import DefaultPage from './default/ProviderDetialPage';
|
|
9
|
-
import GitHub from './github';
|
|
10
|
-
import Ollama from './ollama';
|
|
11
|
-
import OpenAI from './openai';
|
|
12
|
-
import VertexAI from './vertexai';
|
|
1
|
+
import dynamic from 'next/dynamic';
|
|
2
|
+
import Loading from '@/components/Loading/BrandTextLoading';
|
|
13
3
|
|
|
14
|
-
const
|
|
15
|
-
|
|
4
|
+
const NewAPI = dynamic(() => import('./newapi'), { loading: () => <Loading />, ssr: false });
|
|
5
|
+
const OpenAI = dynamic(() => import('./openai'), { loading: () => <Loading />, ssr: false });
|
|
6
|
+
const VertexAI = dynamic(() => import('./vertexai'), { loading: () => <Loading />, ssr: false });
|
|
7
|
+
const GitHub = dynamic(() => import('./github'), { loading: () => <Loading />, ssr: false });
|
|
8
|
+
const Ollama = dynamic(() => import('./ollama'), { loading: () => <Loading />, ssr: false });
|
|
9
|
+
const ComfyUI = dynamic(() => import('./comfyui'), { loading: () => <Loading />, ssr: false });
|
|
10
|
+
const Cloudflare = dynamic(() => import('./cloudflare'), { loading: () => <Loading />, ssr: false });
|
|
11
|
+
const Bedrock = dynamic(() => import('./bedrock'), { loading: () => <Loading />, ssr: false });
|
|
12
|
+
const AzureAI = dynamic(() => import('./azureai'), { loading: () => <Loading />, ssr: false });
|
|
13
|
+
const Azure = dynamic(() => import('./azure'), { loading: () => <Loading />, ssr: false });
|
|
14
|
+
const ProviderGrid = dynamic(() => import('../(list)/ProviderGrid'), { loading: () => <Loading />, ssr: false });
|
|
15
|
+
const DefaultPage = dynamic(() => import('./default/ProviderDetialPage'), { loading: () => <Loading />, ssr: false });
|
|
16
|
+
|
|
17
|
+
type ProviderDetailPageProps = {
|
|
18
|
+
id?: string | null;
|
|
19
|
+
onProviderSelect: (provider: string) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const ProviderDetailPage = (props: ProviderDetailPageProps) => {
|
|
23
|
+
const { id, onProviderSelect } = props;
|
|
16
24
|
|
|
17
25
|
switch (id) {
|
|
18
26
|
case 'all': {
|
|
19
|
-
return <ProviderGrid />;
|
|
27
|
+
return <ProviderGrid onProviderSelect={onProviderSelect} />;
|
|
20
28
|
}
|
|
21
29
|
case 'azure': {
|
|
22
30
|
return <Azure />;
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Tag } from '@lobehub/ui';
|
|
4
|
+
import { createStyles } from 'antd-style';
|
|
5
|
+
import { Command } from 'cmdk';
|
|
6
|
+
import {
|
|
7
|
+
ArrowLeft,
|
|
8
|
+
ArrowUpDown,
|
|
9
|
+
BookOpen,
|
|
10
|
+
Bot,
|
|
11
|
+
Compass,
|
|
12
|
+
CornerDownLeft,
|
|
13
|
+
Github,
|
|
14
|
+
MessageCircle,
|
|
15
|
+
Monitor,
|
|
16
|
+
Moon,
|
|
17
|
+
Palette,
|
|
18
|
+
Settings,
|
|
19
|
+
Star,
|
|
20
|
+
Sun,
|
|
21
|
+
} from 'lucide-react';
|
|
22
|
+
import { usePathname, useRouter } from 'next/navigation';
|
|
23
|
+
import { memo, useEffect, useState } from 'react';
|
|
24
|
+
import { createPortal } from 'react-dom';
|
|
25
|
+
import { useTranslation } from 'react-i18next';
|
|
26
|
+
|
|
27
|
+
import { useHotkeyById } from '@/hooks/useHotkeys/useHotkeyById';
|
|
28
|
+
import { useGlobalStore } from '@/store/global';
|
|
29
|
+
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
|
30
|
+
import { useSessionStore } from '@/store/session';
|
|
31
|
+
import { HotkeyEnum } from '@/types/hotkey';
|
|
32
|
+
|
|
33
|
+
const useStyles = createStyles(({ css, token }) => ({
|
|
34
|
+
backTag: css`
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
|
|
37
|
+
&:hover {
|
|
38
|
+
opacity: 0.8;
|
|
39
|
+
}
|
|
40
|
+
`,
|
|
41
|
+
commandFooter: css`
|
|
42
|
+
display: flex;
|
|
43
|
+
gap: 16px;
|
|
44
|
+
align-items: center;
|
|
45
|
+
justify-content: flex-end;
|
|
46
|
+
|
|
47
|
+
padding-block: 8px;
|
|
48
|
+
padding-inline: 16px;
|
|
49
|
+
border-block-start: 1px solid ${token.colorBorderSecondary};
|
|
50
|
+
|
|
51
|
+
background: ${token.colorBgContainer};
|
|
52
|
+
`,
|
|
53
|
+
commandRoot: css`
|
|
54
|
+
overflow: hidden;
|
|
55
|
+
display: flex;
|
|
56
|
+
flex-direction: column;
|
|
57
|
+
|
|
58
|
+
width: min(640px, 90vw);
|
|
59
|
+
max-height: min(500px, 70vh);
|
|
60
|
+
border-radius: ${token.borderRadiusLG}px;
|
|
61
|
+
|
|
62
|
+
background: ${token.colorBgElevated};
|
|
63
|
+
box-shadow: ${token.boxShadowSecondary};
|
|
64
|
+
|
|
65
|
+
animation: slide-down 0.12s ease-out;
|
|
66
|
+
|
|
67
|
+
@keyframes slide-down {
|
|
68
|
+
from {
|
|
69
|
+
transform: translateY(-20px) scale(0.96);
|
|
70
|
+
opacity: 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
to {
|
|
74
|
+
transform: translateY(0) scale(1);
|
|
75
|
+
opacity: 1;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
[cmdk-input] {
|
|
80
|
+
flex: 1;
|
|
81
|
+
|
|
82
|
+
min-width: 0;
|
|
83
|
+
padding: 0;
|
|
84
|
+
border: none;
|
|
85
|
+
|
|
86
|
+
font-family: inherit;
|
|
87
|
+
font-size: 16px;
|
|
88
|
+
color: ${token.colorText};
|
|
89
|
+
|
|
90
|
+
background: transparent;
|
|
91
|
+
outline: none;
|
|
92
|
+
|
|
93
|
+
&::placeholder {
|
|
94
|
+
color: ${token.colorTextPlaceholder};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
[cmdk-list] {
|
|
99
|
+
overflow-y: auto;
|
|
100
|
+
max-height: 400px;
|
|
101
|
+
padding: 8px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
[cmdk-empty] {
|
|
105
|
+
padding-block: 32px;
|
|
106
|
+
padding-inline: 16px;
|
|
107
|
+
|
|
108
|
+
font-size: 14px;
|
|
109
|
+
color: ${token.colorTextTertiary};
|
|
110
|
+
text-align: center;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
[cmdk-item] {
|
|
114
|
+
cursor: pointer;
|
|
115
|
+
user-select: none;
|
|
116
|
+
|
|
117
|
+
display: flex;
|
|
118
|
+
gap: 12px;
|
|
119
|
+
align-items: center;
|
|
120
|
+
|
|
121
|
+
padding-block: 12px;
|
|
122
|
+
padding-inline: 16px;
|
|
123
|
+
border-radius: ${token.borderRadius}px;
|
|
124
|
+
|
|
125
|
+
color: ${token.colorText};
|
|
126
|
+
|
|
127
|
+
transition: all 0.15s ease;
|
|
128
|
+
|
|
129
|
+
&[aria-selected='true'] {
|
|
130
|
+
background: ${token.colorBgTextHover};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
&:hover {
|
|
134
|
+
background: ${token.colorBgTextHover};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
[cmdk-group-heading] {
|
|
139
|
+
user-select: none;
|
|
140
|
+
|
|
141
|
+
padding-block: 8px;
|
|
142
|
+
padding-inline: 16px;
|
|
143
|
+
|
|
144
|
+
font-size: 12px;
|
|
145
|
+
font-weight: 500;
|
|
146
|
+
color: ${token.colorTextSecondary};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
[cmdk-separator] {
|
|
150
|
+
height: 1px;
|
|
151
|
+
margin-block: 4px;
|
|
152
|
+
background: ${token.colorBorderSecondary};
|
|
153
|
+
}
|
|
154
|
+
`,
|
|
155
|
+
icon: css`
|
|
156
|
+
flex-shrink: 0;
|
|
157
|
+
width: 20px;
|
|
158
|
+
height: 20px;
|
|
159
|
+
color: ${token.colorTextSecondary};
|
|
160
|
+
`,
|
|
161
|
+
inputWrapper: css`
|
|
162
|
+
display: flex;
|
|
163
|
+
gap: 8px;
|
|
164
|
+
align-items: center;
|
|
165
|
+
|
|
166
|
+
padding: 16px;
|
|
167
|
+
border-block-end: 1px solid ${token.colorBorderSecondary};
|
|
168
|
+
`,
|
|
169
|
+
itemContent: css`
|
|
170
|
+
flex: 1;
|
|
171
|
+
min-width: 0;
|
|
172
|
+
`,
|
|
173
|
+
itemDescription: css`
|
|
174
|
+
margin-block-start: 2px;
|
|
175
|
+
font-size: 12px;
|
|
176
|
+
line-height: 1.4;
|
|
177
|
+
color: ${token.colorTextTertiary};
|
|
178
|
+
`,
|
|
179
|
+
itemLabel: css`
|
|
180
|
+
font-size: 14px;
|
|
181
|
+
font-weight: 500;
|
|
182
|
+
line-height: 1.4;
|
|
183
|
+
`,
|
|
184
|
+
kbd: css`
|
|
185
|
+
display: inline-flex;
|
|
186
|
+
gap: 4px;
|
|
187
|
+
align-items: center;
|
|
188
|
+
|
|
189
|
+
padding-block: 2px;
|
|
190
|
+
padding-inline: 6px;
|
|
191
|
+
border-radius: ${token.borderRadiusSM}px;
|
|
192
|
+
|
|
193
|
+
font-size: 11px;
|
|
194
|
+
font-weight: 500;
|
|
195
|
+
line-height: 1.2;
|
|
196
|
+
color: ${token.colorTextSecondary};
|
|
197
|
+
|
|
198
|
+
background: ${token.colorFillQuaternary};
|
|
199
|
+
`,
|
|
200
|
+
kbdIcon: css`
|
|
201
|
+
width: 12px;
|
|
202
|
+
height: 12px;
|
|
203
|
+
`,
|
|
204
|
+
overlay: css`
|
|
205
|
+
position: fixed;
|
|
206
|
+
z-index: 9999;
|
|
207
|
+
inset: 0;
|
|
208
|
+
|
|
209
|
+
display: flex;
|
|
210
|
+
justify-content: center;
|
|
211
|
+
|
|
212
|
+
padding-block-start: 15vh;
|
|
213
|
+
|
|
214
|
+
background: ${token.colorBgMask};
|
|
215
|
+
|
|
216
|
+
animation: fade-in 0.1s ease-in-out;
|
|
217
|
+
|
|
218
|
+
@keyframes fade-in {
|
|
219
|
+
from {
|
|
220
|
+
opacity: 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
to {
|
|
224
|
+
opacity: 1;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
`,
|
|
228
|
+
}));
|
|
229
|
+
|
|
230
|
+
const Cmdk = memo(() => {
|
|
231
|
+
const [open, setOpen] = useState(false);
|
|
232
|
+
const [mounted, setMounted] = useState(false);
|
|
233
|
+
const [search, setSearch] = useState('');
|
|
234
|
+
const [pages, setPages] = useState<string[]>([]);
|
|
235
|
+
const router = useRouter();
|
|
236
|
+
const pathname = usePathname();
|
|
237
|
+
const { t } = useTranslation('common');
|
|
238
|
+
const { styles } = useStyles();
|
|
239
|
+
const switchThemeMode = useGlobalStore((s) => s.switchThemeMode);
|
|
240
|
+
const createSession = useSessionStore((s) => s.createSession);
|
|
241
|
+
const { showCreateSession } = useServerConfigStore(featureFlagsSelectors);
|
|
242
|
+
|
|
243
|
+
const page = pages.at(-1);
|
|
244
|
+
|
|
245
|
+
// Ensure we're mounted on the client
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
setMounted(true);
|
|
248
|
+
}, []);
|
|
249
|
+
|
|
250
|
+
// Register Cmd+K / Ctrl+K hotkey
|
|
251
|
+
useHotkeyById(HotkeyEnum.CommandPalette, () => {
|
|
252
|
+
setOpen((prev) => !prev);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Close on Escape key and prevent body scroll
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
if (open) {
|
|
258
|
+
const originalStyle = window.getComputedStyle(document.body).overflow;
|
|
259
|
+
document.body.style.overflow = 'hidden';
|
|
260
|
+
|
|
261
|
+
return () => {
|
|
262
|
+
document.body.style.overflow = originalStyle;
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}, [open]);
|
|
266
|
+
|
|
267
|
+
// Reset pages and search when opening/closing
|
|
268
|
+
useEffect(() => {
|
|
269
|
+
if (open) {
|
|
270
|
+
setPages([]);
|
|
271
|
+
setSearch('');
|
|
272
|
+
}
|
|
273
|
+
}, [open]);
|
|
274
|
+
|
|
275
|
+
const handleNavigate = (path: string) => {
|
|
276
|
+
router.push(path);
|
|
277
|
+
setOpen(false);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const handleExternalLink = (url: string) => {
|
|
281
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
282
|
+
setOpen(false);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const handleThemeChange = (theme: 'light' | 'dark' | 'auto') => {
|
|
286
|
+
switchThemeMode(theme);
|
|
287
|
+
setOpen(false);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
if (!mounted || !open) return null;
|
|
291
|
+
|
|
292
|
+
return createPortal(
|
|
293
|
+
<div className={styles.overlay} onClick={() => setOpen(false)}>
|
|
294
|
+
<div onClick={(e) => e.stopPropagation()}>
|
|
295
|
+
<Command
|
|
296
|
+
className={styles.commandRoot}
|
|
297
|
+
onKeyDown={(e) => {
|
|
298
|
+
// Escape goes to previous page or closes
|
|
299
|
+
if (e.key === 'Escape') {
|
|
300
|
+
e.preventDefault();
|
|
301
|
+
if (pages.length > 0) {
|
|
302
|
+
setPages((prev) => prev.slice(0, -1));
|
|
303
|
+
} else {
|
|
304
|
+
setOpen(false);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// Backspace goes to previous page when search is empty
|
|
308
|
+
if (e.key === 'Backspace' && !search && pages.length > 0) {
|
|
309
|
+
e.preventDefault();
|
|
310
|
+
setPages((prev) => prev.slice(0, -1));
|
|
311
|
+
}
|
|
312
|
+
}}
|
|
313
|
+
shouldFilter={true}
|
|
314
|
+
>
|
|
315
|
+
<div className={styles.inputWrapper}>
|
|
316
|
+
{pages.length > 0 && (
|
|
317
|
+
<Tag
|
|
318
|
+
className={styles.backTag}
|
|
319
|
+
icon={<ArrowLeft size={12} />}
|
|
320
|
+
onClick={() => setPages((prev) => prev.slice(0, -1))}
|
|
321
|
+
/>
|
|
322
|
+
)}
|
|
323
|
+
<Command.Input
|
|
324
|
+
autoFocus
|
|
325
|
+
onValueChange={setSearch}
|
|
326
|
+
placeholder={t('cmdk.searchPlaceholder')}
|
|
327
|
+
value={search}
|
|
328
|
+
/>
|
|
329
|
+
<Tag>ESC</Tag>
|
|
330
|
+
</div>
|
|
331
|
+
<Command.List>
|
|
332
|
+
<Command.Empty>{t('cmdk.noResults')}</Command.Empty>
|
|
333
|
+
|
|
334
|
+
{!page && (
|
|
335
|
+
<>
|
|
336
|
+
{showCreateSession && (
|
|
337
|
+
<Command.Item
|
|
338
|
+
onSelect={() => {
|
|
339
|
+
createSession();
|
|
340
|
+
setOpen(false);
|
|
341
|
+
}}
|
|
342
|
+
value="new-agent"
|
|
343
|
+
>
|
|
344
|
+
<Bot className={styles.icon} />
|
|
345
|
+
<div className={styles.itemContent}>
|
|
346
|
+
<div className={styles.itemLabel}>{t('cmdk.newAgent')}</div>
|
|
347
|
+
</div>
|
|
348
|
+
</Command.Item>
|
|
349
|
+
)}
|
|
350
|
+
|
|
351
|
+
{!pathname?.startsWith('/settings') && (
|
|
352
|
+
<Command.Item onSelect={() => handleNavigate('/settings')} value="settings">
|
|
353
|
+
<Settings className={styles.icon} />
|
|
354
|
+
<div className={styles.itemContent}>
|
|
355
|
+
<div className={styles.itemLabel}>{t('cmdk.settings')}</div>
|
|
356
|
+
</div>
|
|
357
|
+
</Command.Item>
|
|
358
|
+
)}
|
|
359
|
+
|
|
360
|
+
<Command.Item onSelect={() => setPages([...pages, 'theme'])} value="theme">
|
|
361
|
+
<Monitor className={styles.icon} />
|
|
362
|
+
<div className={styles.itemContent}>
|
|
363
|
+
<div className={styles.itemLabel}>{t('cmdk.theme')}</div>
|
|
364
|
+
</div>
|
|
365
|
+
</Command.Item>
|
|
366
|
+
|
|
367
|
+
<Command.Group heading={t('cmdk.navigate')}>
|
|
368
|
+
{!pathname?.startsWith('/discover') && (
|
|
369
|
+
<Command.Item onSelect={() => handleNavigate('/discover')} value="discover">
|
|
370
|
+
<Compass className={styles.icon} />
|
|
371
|
+
<div className={styles.itemContent}>
|
|
372
|
+
<div className={styles.itemLabel}>{t('cmdk.discover')}</div>
|
|
373
|
+
</div>
|
|
374
|
+
</Command.Item>
|
|
375
|
+
)}
|
|
376
|
+
{!pathname?.startsWith('/image') && (
|
|
377
|
+
<Command.Item onSelect={() => handleNavigate('/image')} value="painting">
|
|
378
|
+
<Palette className={styles.icon} />
|
|
379
|
+
<div className={styles.itemContent}>
|
|
380
|
+
<div className={styles.itemLabel}>{t('cmdk.painting')}</div>
|
|
381
|
+
</div>
|
|
382
|
+
</Command.Item>
|
|
383
|
+
)}
|
|
384
|
+
{!pathname?.startsWith('/knowledge') && (
|
|
385
|
+
<Command.Item onSelect={() => handleNavigate('/knowledge')} value="knowledge">
|
|
386
|
+
<BookOpen className={styles.icon} />
|
|
387
|
+
<div className={styles.itemContent}>
|
|
388
|
+
<div className={styles.itemLabel}>{t('cmdk.knowledgeBase')}</div>
|
|
389
|
+
</div>
|
|
390
|
+
</Command.Item>
|
|
391
|
+
)}
|
|
392
|
+
</Command.Group>
|
|
393
|
+
|
|
394
|
+
<Command.Group heading={t('cmdk.about')}>
|
|
395
|
+
<Command.Item
|
|
396
|
+
onSelect={() =>
|
|
397
|
+
handleExternalLink('https://github.com/lobehub/lobe-chat/issues/new/choose')
|
|
398
|
+
}
|
|
399
|
+
value="submit-issue"
|
|
400
|
+
>
|
|
401
|
+
<Github className={styles.icon} />
|
|
402
|
+
<div className={styles.itemContent}>
|
|
403
|
+
<div className={styles.itemLabel}>{t('cmdk.submitIssue')}</div>
|
|
404
|
+
</div>
|
|
405
|
+
</Command.Item>
|
|
406
|
+
<Command.Item
|
|
407
|
+
onSelect={() => handleExternalLink('https://github.com/lobehub/lobe-chat')}
|
|
408
|
+
value="star-github"
|
|
409
|
+
>
|
|
410
|
+
<Star className={styles.icon} />
|
|
411
|
+
<div className={styles.itemContent}>
|
|
412
|
+
<div className={styles.itemLabel}>{t('cmdk.starOnGitHub')}</div>
|
|
413
|
+
</div>
|
|
414
|
+
</Command.Item>
|
|
415
|
+
<Command.Item
|
|
416
|
+
onSelect={() => handleExternalLink('https://discord.gg/AYFPHvv2jT')}
|
|
417
|
+
value="discord"
|
|
418
|
+
>
|
|
419
|
+
<MessageCircle className={styles.icon} />
|
|
420
|
+
<div className={styles.itemContent}>
|
|
421
|
+
<div className={styles.itemLabel}>{t('cmdk.communitySupport')}</div>
|
|
422
|
+
</div>
|
|
423
|
+
</Command.Item>
|
|
424
|
+
</Command.Group>
|
|
425
|
+
</>
|
|
426
|
+
)}
|
|
427
|
+
|
|
428
|
+
{page === 'theme' && (
|
|
429
|
+
<>
|
|
430
|
+
<Command.Item onSelect={() => handleThemeChange('light')} value="theme-light">
|
|
431
|
+
<Sun className={styles.icon} />
|
|
432
|
+
<div className={styles.itemContent}>
|
|
433
|
+
<div className={styles.itemLabel}>{t('cmdk.themeLight')}</div>
|
|
434
|
+
</div>
|
|
435
|
+
</Command.Item>
|
|
436
|
+
<Command.Item onSelect={() => handleThemeChange('dark')} value="theme-dark">
|
|
437
|
+
<Moon className={styles.icon} />
|
|
438
|
+
<div className={styles.itemContent}>
|
|
439
|
+
<div className={styles.itemLabel}>{t('cmdk.themeDark')}</div>
|
|
440
|
+
</div>
|
|
441
|
+
</Command.Item>
|
|
442
|
+
<Command.Item onSelect={() => handleThemeChange('auto')} value="theme-auto">
|
|
443
|
+
<Monitor className={styles.icon} />
|
|
444
|
+
<div className={styles.itemContent}>
|
|
445
|
+
<div className={styles.itemLabel}>{t('cmdk.themeAuto')}</div>
|
|
446
|
+
</div>
|
|
447
|
+
</Command.Item>
|
|
448
|
+
</>
|
|
449
|
+
)}
|
|
450
|
+
</Command.List>
|
|
451
|
+
<div className={styles.commandFooter}>
|
|
452
|
+
<div className={styles.kbd}>
|
|
453
|
+
<CornerDownLeft className={styles.kbdIcon} />
|
|
454
|
+
<span>{t('cmdk.toOpen')}</span>
|
|
455
|
+
</div>
|
|
456
|
+
<div className={styles.kbd}>
|
|
457
|
+
<ArrowUpDown className={styles.kbdIcon} />
|
|
458
|
+
<span>{t('cmdk.toSelect')}</span>
|
|
459
|
+
</div>
|
|
460
|
+
</div>
|
|
461
|
+
</Command>
|
|
462
|
+
</div>
|
|
463
|
+
</div>,
|
|
464
|
+
document.body,
|
|
465
|
+
);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
Cmdk.displayName = 'Cmdk';
|
|
469
|
+
|
|
470
|
+
export default Cmdk;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import dynamic from 'next/dynamic';
|
|
4
|
+
import { memo } from 'react';
|
|
5
|
+
|
|
6
|
+
// Lazy load the CMDK component with Next.js dynamic import
|
|
7
|
+
// This splits the CMDK code into a separate chunk that only loads when needed
|
|
8
|
+
// ssr: false ensures it only loads on the client side
|
|
9
|
+
const CmdkComponent = dynamic(() => import('./Cmdk'), {
|
|
10
|
+
ssr: false,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const CmdkLazy = memo(() => <CmdkComponent />);
|
|
14
|
+
|
|
15
|
+
CmdkLazy.displayName = 'CmdkLazy';
|
|
16
|
+
|
|
17
|
+
export default CmdkLazy;
|
|
@@ -10,6 +10,7 @@ import { getAntdLocale } from '@/utils/locale';
|
|
|
10
10
|
|
|
11
11
|
import AntdV5MonkeyPatch from './AntdV5MonkeyPatch';
|
|
12
12
|
import AppTheme from './AppTheme';
|
|
13
|
+
import CmdkLazy from './CmdkLazy';
|
|
13
14
|
import ImportSettings from './ImportSettings';
|
|
14
15
|
import Locale from './Locale';
|
|
15
16
|
import QueryProvider from './Query';
|
|
@@ -65,6 +66,7 @@ const GlobalLayout = async ({
|
|
|
65
66
|
<ImportSettings />
|
|
66
67
|
{process.env.NODE_ENV === 'development' && <DevPanel />}
|
|
67
68
|
</Suspense>
|
|
69
|
+
<CmdkLazy />
|
|
68
70
|
</ServerConfigStoreProvider>
|
|
69
71
|
</AppTheme>
|
|
70
72
|
</Locale>
|