@lobehub/chat 1.49.10 → 1.49.12
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 +50 -0
- package/changelog/v1.json +18 -0
- package/locales/ar/components.json +24 -0
- package/locales/ar/modelProvider.json +0 -24
- package/locales/ar/models.json +15 -0
- package/locales/bg-BG/components.json +24 -0
- package/locales/bg-BG/modelProvider.json +0 -24
- package/locales/bg-BG/models.json +15 -0
- package/locales/de-DE/components.json +24 -0
- package/locales/de-DE/modelProvider.json +0 -24
- package/locales/de-DE/models.json +15 -0
- package/locales/en-US/components.json +24 -0
- package/locales/en-US/modelProvider.json +0 -24
- package/locales/en-US/models.json +15 -0
- package/locales/es-ES/components.json +24 -0
- package/locales/es-ES/modelProvider.json +0 -24
- package/locales/es-ES/models.json +15 -0
- package/locales/fa-IR/components.json +24 -0
- package/locales/fa-IR/modelProvider.json +0 -24
- package/locales/fa-IR/models.json +15 -0
- package/locales/fr-FR/components.json +24 -0
- package/locales/fr-FR/modelProvider.json +0 -24
- package/locales/fr-FR/models.json +15 -0
- package/locales/it-IT/components.json +24 -0
- package/locales/it-IT/modelProvider.json +0 -24
- package/locales/it-IT/models.json +15 -0
- package/locales/ja-JP/components.json +24 -0
- package/locales/ja-JP/modelProvider.json +0 -24
- package/locales/ja-JP/models.json +15 -0
- package/locales/ko-KR/components.json +24 -0
- package/locales/ko-KR/modelProvider.json +0 -24
- package/locales/ko-KR/models.json +4 -0
- package/locales/nl-NL/components.json +24 -0
- package/locales/nl-NL/modelProvider.json +0 -24
- package/locales/nl-NL/models.json +15 -0
- package/locales/pl-PL/components.json +24 -0
- package/locales/pl-PL/modelProvider.json +0 -24
- package/locales/pl-PL/models.json +15 -0
- package/locales/pt-BR/components.json +24 -0
- package/locales/pt-BR/modelProvider.json +0 -24
- package/locales/pt-BR/models.json +15 -0
- package/locales/ru-RU/components.json +24 -0
- package/locales/ru-RU/modelProvider.json +0 -24
- package/locales/ru-RU/models.json +15 -0
- package/locales/tr-TR/components.json +24 -0
- package/locales/tr-TR/modelProvider.json +0 -24
- package/locales/tr-TR/models.json +15 -0
- package/locales/vi-VN/components.json +24 -0
- package/locales/vi-VN/modelProvider.json +0 -24
- package/locales/vi-VN/models.json +15 -0
- package/locales/zh-CN/components.json +24 -0
- package/locales/zh-CN/modelProvider.json +0 -24
- package/locales/zh-CN/models.json +16 -1
- package/locales/zh-TW/components.json +24 -0
- package/locales/zh-TW/modelProvider.json +0 -24
- package/locales/zh-TW/models.json +15 -0
- package/package.json +1 -1
- package/src/app/(main)/chat/(workspace)/@portal/_layout/Mobile.tsx +1 -0
- package/src/app/(main)/chat/(workspace)/_layout/Desktop/Portal.tsx +26 -2
- package/src/app/(main)/settings/provider/(detail)/[id]/page.tsx +10 -3
- package/src/app/(main)/settings/provider/(detail)/ollama/CheckError.tsx +70 -0
- package/src/app/(main)/settings/provider/(detail)/ollama/Container.tsx +57 -0
- package/src/app/(main)/settings/provider/(detail)/ollama/OllamaModelDownloader/index.tsx +127 -0
- package/src/app/(main)/settings/provider/(detail)/ollama/OllamaModelDownloader/useDownloadMonitor.ts +29 -0
- package/src/app/(main)/settings/provider/(detail)/ollama/page.tsx +2 -7
- package/src/app/(main)/settings/provider/features/ProviderConfig/Checker.tsx +90 -69
- package/src/app/(main)/settings/provider/features/ProviderConfig/index.tsx +6 -6
- package/src/components/FormAction/index.tsx +66 -0
- package/src/components/OllamaSetupGuide/index.tsx +217 -0
- package/src/components/Thinking/index.tsx +14 -16
- package/src/config/aiModels/ollama.ts +12 -19
- package/src/config/modelProviders/ollama.ts +1 -0
- package/src/config/modelProviders/siliconcloud.ts +2 -2
- package/src/database/repositories/aiInfra/index.ts +33 -2
- package/src/database/server/models/aiProvider.ts +5 -1
- package/src/features/Conversation/Error/OllamaBizError/SetupGuide.tsx +2 -209
- package/src/features/Conversation/components/MarkdownElements/LobeThinking/Render.tsx +7 -58
- package/src/libs/agent-runtime/ollama/index.ts +1 -1
- package/src/libs/agent-runtime/siliconcloud/index.ts +33 -1
- package/src/locales/default/components.ts +26 -0
- package/src/locales/default/modelProvider.ts +0 -26
- package/src/server/routers/lambda/aiProvider.ts +2 -10
- package/src/services/aiProvider/client.ts +2 -8
- package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +10 -10
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +4 -3
- package/src/store/chat/slices/aiChat/initialState.ts +1 -1
- package/src/store/chat/slices/message/action.ts +4 -3
- package/src/store/global/initialState.ts +2 -0
- package/src/store/global/selectors.ts +2 -0
- package/src/store/serverConfig/selectors.test.ts +3 -0
- package/src/store/serverConfig/store.test.ts +3 -2
- package/src/store/serverConfig/store.ts +1 -1
- package/src/store/user/slices/common/action.test.ts +1 -0
- package/src/types/serverConfig.ts +1 -1
- package/src/app/(main)/settings/provider/(detail)/ollama/Checker.tsx +0 -73
package/src/app/(main)/settings/provider/(detail)/ollama/OllamaModelDownloader/useDownloadMonitor.ts
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
2
|
+
|
3
|
+
import { formatSpeed, formatTime } from '@/utils/format';
|
4
|
+
|
5
|
+
export const useDownloadMonitor = (totalSize: number, completedSize: number) => {
|
6
|
+
const [downloadSpeed, setDownloadSpeed] = useState<string>('0 KB/s');
|
7
|
+
const [remainingTime, setRemainingTime] = useState<string>('-');
|
8
|
+
|
9
|
+
const lastCompletedRef = useRef(completedSize);
|
10
|
+
const lastTimedRef = useRef(Date.now());
|
11
|
+
|
12
|
+
useEffect(() => {
|
13
|
+
const currentTime = Date.now();
|
14
|
+
const elapsedTime = (currentTime - lastTimedRef.current) / 1000; // in seconds
|
15
|
+
if (completedSize > 0 && elapsedTime > 1) {
|
16
|
+
const speed = Math.max(0, (completedSize - lastCompletedRef.current) / elapsedTime); // in bytes per second
|
17
|
+
setDownloadSpeed(formatSpeed(speed));
|
18
|
+
|
19
|
+
const remainingSize = totalSize - completedSize;
|
20
|
+
const time = remainingSize / speed; // in seconds
|
21
|
+
setRemainingTime(formatTime(time));
|
22
|
+
|
23
|
+
lastCompletedRef.current = completedSize;
|
24
|
+
lastTimedRef.current = currentTime;
|
25
|
+
}
|
26
|
+
}, [completedSize]);
|
27
|
+
|
28
|
+
return { downloadSpeed, remainingTime };
|
29
|
+
};
|
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { OllamaProviderCard } from '@/config/modelProviders';
|
6
6
|
|
7
7
|
import ProviderDetail from '../[id]';
|
8
|
-
import
|
8
|
+
import CheckError from './CheckError';
|
9
9
|
|
10
10
|
const Page = () => {
|
11
11
|
const { t } = useTranslation('modelProvider');
|
@@ -13,12 +13,7 @@ const Page = () => {
|
|
13
13
|
return (
|
14
14
|
<ProviderDetail
|
15
15
|
{...OllamaProviderCard}
|
16
|
-
|
17
|
-
children: <Checker />,
|
18
|
-
desc: t('ollama.checker.desc'),
|
19
|
-
label: t('ollama.checker.title'),
|
20
|
-
minWidth: undefined,
|
21
|
-
}}
|
16
|
+
checkErrorRender={CheckError}
|
22
17
|
settings={{
|
23
18
|
...OllamaProviderCard.settings,
|
24
19
|
proxyUrl: {
|
@@ -4,7 +4,7 @@ import { CheckCircleFilled } from '@ant-design/icons';
|
|
4
4
|
import { Alert, Highlighter } from '@lobehub/ui';
|
5
5
|
import { Button } from 'antd';
|
6
6
|
import { useTheme } from 'antd-style';
|
7
|
-
import { memo, useState } from 'react';
|
7
|
+
import { ReactNode, memo, useState } from 'react';
|
8
8
|
import { useTranslation } from 'react-i18next';
|
9
9
|
import { Flexbox } from 'react-layout-kit';
|
10
10
|
|
@@ -38,86 +38,107 @@ const Error = memo<{ error: ChatMessageError }>(({ error }) => {
|
|
38
38
|
);
|
39
39
|
});
|
40
40
|
|
41
|
+
export type CheckErrorRender = (props: {
|
42
|
+
defaultError: ReactNode;
|
43
|
+
error?: ChatMessageError;
|
44
|
+
setError: (error?: ChatMessageError) => void;
|
45
|
+
}) => ReactNode;
|
46
|
+
|
41
47
|
interface ConnectionCheckerProps {
|
48
|
+
checkErrorRender?: CheckErrorRender;
|
42
49
|
model: string;
|
43
50
|
provider: string;
|
44
51
|
}
|
45
52
|
|
46
|
-
const Checker = memo<ConnectionCheckerProps>(
|
47
|
-
|
53
|
+
const Checker = memo<ConnectionCheckerProps>(
|
54
|
+
({ model, provider, checkErrorRender: CheckErrorRender }) => {
|
55
|
+
const { t } = useTranslation('setting');
|
48
56
|
|
49
|
-
|
57
|
+
const disabled = useAiInfraStore(aiProviderSelectors.isProviderConfigUpdating(provider));
|
50
58
|
|
51
|
-
|
52
|
-
|
59
|
+
const [loading, setLoading] = useState(false);
|
60
|
+
const [pass, setPass] = useState(false);
|
53
61
|
|
54
|
-
|
55
|
-
|
62
|
+
const theme = useTheme();
|
63
|
+
const [error, setError] = useState<ChatMessageError | undefined>();
|
56
64
|
|
57
|
-
|
58
|
-
|
65
|
+
const checkConnection = async () => {
|
66
|
+
let isError = false;
|
59
67
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
setPass(false);
|
64
|
-
isError = true;
|
65
|
-
},
|
66
|
-
onFinish: async (value) => {
|
67
|
-
if (!isError && value) {
|
68
|
-
setError(undefined);
|
69
|
-
setPass(true);
|
70
|
-
} else {
|
68
|
+
await chatService.fetchPresetTaskResult({
|
69
|
+
onError: (_, rawError) => {
|
70
|
+
setError(rawError);
|
71
71
|
setPass(false);
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
72
|
+
isError = true;
|
73
|
+
},
|
74
|
+
onFinish: async (value) => {
|
75
|
+
if (!isError && value) {
|
76
|
+
setError(undefined);
|
77
|
+
setPass(true);
|
78
|
+
} else {
|
79
|
+
setPass(false);
|
80
|
+
setError({
|
81
|
+
body: value,
|
82
|
+
message: t('response.ConnectionCheckFailed', { ns: 'error' }),
|
83
|
+
type: 'ConnectionCheckFailed',
|
84
|
+
});
|
85
|
+
}
|
86
|
+
},
|
87
|
+
onLoadingChange: (loading) => {
|
88
|
+
setLoading(loading);
|
89
|
+
},
|
90
|
+
params: {
|
91
|
+
messages: [
|
92
|
+
{
|
93
|
+
content: 'hello',
|
94
|
+
role: 'user',
|
95
|
+
},
|
96
|
+
],
|
97
|
+
model,
|
98
|
+
provider,
|
99
|
+
},
|
100
|
+
trace: {
|
101
|
+
sessionId: `connection:${provider}`,
|
102
|
+
topicId: model,
|
103
|
+
traceName: TraceNameMap.ConnectivityChecker,
|
104
|
+
},
|
105
|
+
});
|
106
|
+
};
|
107
|
+
const isMobile = useIsMobile();
|
100
108
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
109
|
+
const defaultError = error ? <Error error={error as ChatMessageError} /> : null;
|
110
|
+
|
111
|
+
const errorContent = CheckErrorRender ? (
|
112
|
+
<CheckErrorRender defaultError={defaultError} error={error} setError={setError} />
|
113
|
+
) : (
|
114
|
+
defaultError
|
115
|
+
);
|
116
|
+
|
117
|
+
return (
|
118
|
+
<Flexbox align={isMobile ? 'flex-start' : 'flex-end'} gap={8}>
|
119
|
+
<Flexbox
|
120
|
+
align={'center'}
|
121
|
+
direction={isMobile ? 'horizontal-reverse' : 'horizontal'}
|
122
|
+
gap={12}
|
123
|
+
>
|
124
|
+
{pass && (
|
125
|
+
<Flexbox gap={4} horizontal>
|
126
|
+
<CheckCircleFilled
|
127
|
+
style={{
|
128
|
+
color: theme.colorSuccess,
|
129
|
+
}}
|
130
|
+
/>
|
131
|
+
{t('llm.checker.pass')}
|
132
|
+
</Flexbox>
|
133
|
+
)}
|
134
|
+
<Button disabled={disabled} loading={loading} onClick={checkConnection}>
|
135
|
+
{t('llm.checker.button')}
|
136
|
+
</Button>
|
137
|
+
</Flexbox>
|
138
|
+
{error && errorContent}
|
117
139
|
</Flexbox>
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
});
|
140
|
+
);
|
141
|
+
},
|
142
|
+
);
|
122
143
|
|
123
144
|
export default Checker;
|
@@ -25,7 +25,7 @@ import {
|
|
25
25
|
} from '@/types/aiProvider';
|
26
26
|
|
27
27
|
import { KeyVaultsConfigKey, LLMProviderApiTokenKey, LLMProviderBaseUrlKey } from '../../const';
|
28
|
-
import Checker from './Checker';
|
28
|
+
import Checker, { CheckErrorRender } from './Checker';
|
29
29
|
import EnableSwitch from './EnableSwitch';
|
30
30
|
import { SkeletonInput } from './SkeletonInput';
|
31
31
|
import UpdateProviderInfo from './UpdateProviderInfo';
|
@@ -91,7 +91,7 @@ const useStyles = createStyles(({ css, prefixCls, responsive, token }) => ({
|
|
91
91
|
export interface ProviderConfigProps extends Omit<AiProviderDetailItem, 'enabled' | 'source'> {
|
92
92
|
apiKeyItems?: FormItemProps[];
|
93
93
|
canDeactivate?: boolean;
|
94
|
-
|
94
|
+
checkErrorRender?: CheckErrorRender;
|
95
95
|
className?: string;
|
96
96
|
enabled?: boolean;
|
97
97
|
extra?: ReactNode;
|
@@ -113,9 +113,9 @@ const ProviderConfig = memo<ProviderConfigProps>(
|
|
113
113
|
id,
|
114
114
|
settings,
|
115
115
|
checkModel,
|
116
|
-
checkerItem,
|
117
116
|
logo,
|
118
117
|
className,
|
118
|
+
checkErrorRender,
|
119
119
|
name,
|
120
120
|
showAceGcm = true,
|
121
121
|
extra,
|
@@ -271,16 +271,16 @@ const ProviderConfig = memo<ProviderConfigProps>(
|
|
271
271
|
endpointItem,
|
272
272
|
clientFetchItem,
|
273
273
|
showChecker
|
274
|
-
?
|
274
|
+
? {
|
275
275
|
children: isLoading ? (
|
276
276
|
<Skeleton.Button active />
|
277
277
|
) : (
|
278
|
-
<Checker model={checkModel!} provider={id} />
|
278
|
+
<Checker checkErrorRender={checkErrorRender} model={checkModel!} provider={id} />
|
279
279
|
),
|
280
280
|
desc: t('providerModels.config.checker.desc'),
|
281
281
|
label: t('providerModels.config.checker.title'),
|
282
282
|
minWidth: undefined,
|
283
|
-
}
|
283
|
+
}
|
284
284
|
: undefined,
|
285
285
|
showAceGcm && isServerMode && aceGcmItem,
|
286
286
|
].filter(Boolean) as FormItemProps[];
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import { Avatar } from '@lobehub/ui';
|
2
|
+
import { createStyles } from 'antd-style';
|
3
|
+
import { ReactNode, memo } from 'react';
|
4
|
+
import { Center, CenterProps, Flexbox } from 'react-layout-kit';
|
5
|
+
|
6
|
+
export const useStyles = createStyles(({ css, token }) => ({
|
7
|
+
container: css`
|
8
|
+
border: 1px solid ${token.colorSplit};
|
9
|
+
border-radius: 8px;
|
10
|
+
color: ${token.colorText};
|
11
|
+
background: ${token.colorBgContainer};
|
12
|
+
`,
|
13
|
+
desc: css`
|
14
|
+
color: ${token.colorTextTertiary};
|
15
|
+
text-align: center;
|
16
|
+
`,
|
17
|
+
form: css`
|
18
|
+
width: 100%;
|
19
|
+
max-width: 300px;
|
20
|
+
`,
|
21
|
+
}));
|
22
|
+
|
23
|
+
const FormAction = memo<
|
24
|
+
{
|
25
|
+
animation?: boolean;
|
26
|
+
avatar: ReactNode;
|
27
|
+
background?: string;
|
28
|
+
description: string;
|
29
|
+
title: string;
|
30
|
+
} & CenterProps
|
31
|
+
>(
|
32
|
+
({
|
33
|
+
children,
|
34
|
+
background,
|
35
|
+
title,
|
36
|
+
description,
|
37
|
+
avatar,
|
38
|
+
animation,
|
39
|
+
className,
|
40
|
+
gap = 16,
|
41
|
+
...rest
|
42
|
+
}) => {
|
43
|
+
const { cx, styles, theme } = useStyles();
|
44
|
+
|
45
|
+
return (
|
46
|
+
<Center className={cx(styles.form, className)} gap={gap} {...rest}>
|
47
|
+
<Avatar
|
48
|
+
animation={animation}
|
49
|
+
avatar={avatar}
|
50
|
+
background={background ?? theme.colorFillContent}
|
51
|
+
gap={12}
|
52
|
+
size={80}
|
53
|
+
/>
|
54
|
+
<Flexbox gap={8} width={'100%'}>
|
55
|
+
<Flexbox style={{ fontSize: 18, fontWeight: 'bold', textAlign: 'center' }}>
|
56
|
+
{title}
|
57
|
+
</Flexbox>
|
58
|
+
<Flexbox className={styles.desc}>{description}</Flexbox>
|
59
|
+
</Flexbox>
|
60
|
+
{children}
|
61
|
+
</Center>
|
62
|
+
);
|
63
|
+
},
|
64
|
+
);
|
65
|
+
|
66
|
+
export default FormAction;
|
@@ -0,0 +1,217 @@
|
|
1
|
+
import { Highlighter, Snippet, TabsNav } from '@lobehub/ui';
|
2
|
+
import { Steps } from 'antd';
|
3
|
+
import { createStyles } from 'antd-style';
|
4
|
+
import Link from 'next/link';
|
5
|
+
import { readableColor } from 'polished';
|
6
|
+
import { memo } from 'react';
|
7
|
+
import { Trans, useTranslation } from 'react-i18next';
|
8
|
+
import { Flexbox } from 'react-layout-kit';
|
9
|
+
|
10
|
+
const useStyles = createStyles(({ css, prefixCls, token }) => ({
|
11
|
+
steps: css`
|
12
|
+
margin-block-start: 32px;
|
13
|
+
&.${prefixCls}-steps-small .${prefixCls}-steps-item-title {
|
14
|
+
margin-block-end: 16px;
|
15
|
+
font-size: 16px;
|
16
|
+
font-weight: bold;
|
17
|
+
}
|
18
|
+
|
19
|
+
.${prefixCls}-steps-item-description {
|
20
|
+
margin-block-end: 24px;
|
21
|
+
}
|
22
|
+
|
23
|
+
.${prefixCls}-steps-icon {
|
24
|
+
color: ${readableColor(token.colorPrimary)} !important;
|
25
|
+
}
|
26
|
+
`,
|
27
|
+
}));
|
28
|
+
|
29
|
+
const SetupGuide = memo(() => {
|
30
|
+
const { styles } = useStyles();
|
31
|
+
const { t } = useTranslation('components');
|
32
|
+
return (
|
33
|
+
<TabsNav
|
34
|
+
items={[
|
35
|
+
{
|
36
|
+
children: (
|
37
|
+
<Steps
|
38
|
+
className={styles.steps}
|
39
|
+
direction={'vertical'}
|
40
|
+
items={[
|
41
|
+
{
|
42
|
+
description: (
|
43
|
+
<Trans i18nKey={'OllamaSetupGuide.install.description'} ns={'components'}>
|
44
|
+
请确认你已经开启 Ollama ,如果没有安装 Ollama ,请前往官网
|
45
|
+
<Link href={'https://ollama.com/download'}>下载</Link>
|
46
|
+
</Trans>
|
47
|
+
),
|
48
|
+
status: 'process',
|
49
|
+
title: t('OllamaSetupGuide.install.title'),
|
50
|
+
},
|
51
|
+
{
|
52
|
+
description: (
|
53
|
+
<Flexbox gap={8}>
|
54
|
+
{t('OllamaSetupGuide.cors.description')}
|
55
|
+
|
56
|
+
<Flexbox gap={8}>
|
57
|
+
{t('OllamaSetupGuide.cors.macos')}
|
58
|
+
<Snippet language={'bash'}>
|
59
|
+
{/* eslint-disable-next-line react/no-unescaped-entities */}
|
60
|
+
launchctl setenv OLLAMA_ORIGINS "*"
|
61
|
+
</Snippet>
|
62
|
+
{t('OllamaSetupGuide.cors.reboot')}
|
63
|
+
</Flexbox>
|
64
|
+
</Flexbox>
|
65
|
+
),
|
66
|
+
status: 'process',
|
67
|
+
title: t('OllamaSetupGuide.cors.title'),
|
68
|
+
},
|
69
|
+
]}
|
70
|
+
size={'small'}
|
71
|
+
/>
|
72
|
+
),
|
73
|
+
key: 'macos',
|
74
|
+
label: 'macOS',
|
75
|
+
},
|
76
|
+
{
|
77
|
+
children: (
|
78
|
+
<Steps
|
79
|
+
className={styles.steps}
|
80
|
+
direction={'vertical'}
|
81
|
+
items={[
|
82
|
+
{
|
83
|
+
description: (
|
84
|
+
<Trans i18nKey={'OllamaSetupGuide.install.description'} ns={'components'}>
|
85
|
+
请确认你已经开启 Ollama ,如果没有安装 Ollama ,请前往官网
|
86
|
+
<Link href={'https://ollama.com/download'}>下载</Link>
|
87
|
+
</Trans>
|
88
|
+
),
|
89
|
+
status: 'process',
|
90
|
+
title: t('OllamaSetupGuide.install.title'),
|
91
|
+
},
|
92
|
+
{
|
93
|
+
description: (
|
94
|
+
<Flexbox gap={8}>
|
95
|
+
{t('OllamaSetupGuide.cors.description')}
|
96
|
+
<div>{t('OllamaSetupGuide.cors.windows')}</div>
|
97
|
+
<div>{t('OllamaSetupGuide.cors.reboot')}</div>
|
98
|
+
</Flexbox>
|
99
|
+
),
|
100
|
+
status: 'process',
|
101
|
+
title: t('OllamaSetupGuide.cors.title'),
|
102
|
+
},
|
103
|
+
]}
|
104
|
+
size={'small'}
|
105
|
+
/>
|
106
|
+
),
|
107
|
+
key: 'windows',
|
108
|
+
label: t('OllamaSetupGuide.install.windowsTab'),
|
109
|
+
},
|
110
|
+
{
|
111
|
+
children: (
|
112
|
+
<Steps
|
113
|
+
className={styles.steps}
|
114
|
+
direction={'vertical'}
|
115
|
+
items={[
|
116
|
+
{
|
117
|
+
description: (
|
118
|
+
<Flexbox gap={8}>
|
119
|
+
{t('OllamaSetupGuide.install.linux.command')}
|
120
|
+
<Snippet language={'bash'}>
|
121
|
+
curl -fsSL https://ollama.com/install.sh | sh
|
122
|
+
</Snippet>
|
123
|
+
<div>
|
124
|
+
<Trans i18nKey={'OllamaSetupGuide.install.linux.manual'} ns={'components'}>
|
125
|
+
或者,你也可以参考
|
126
|
+
<Link href={'https://github.com/ollama/ollama/blob/main/docs/linux.md'}>
|
127
|
+
Linux 手动安装指南
|
128
|
+
</Link>
|
129
|
+
。
|
130
|
+
</Trans>
|
131
|
+
</div>
|
132
|
+
</Flexbox>
|
133
|
+
),
|
134
|
+
status: 'process',
|
135
|
+
title: t('OllamaSetupGuide.install.title'),
|
136
|
+
},
|
137
|
+
{
|
138
|
+
description: (
|
139
|
+
<Flexbox gap={8}>
|
140
|
+
<div>{t('OllamaSetupGuide.cors.description')}</div>
|
141
|
+
|
142
|
+
<div>{t('OllamaSetupGuide.cors.linux.systemd')}</div>
|
143
|
+
{/* eslint-disable-next-line react/no-unescaped-entities */}
|
144
|
+
<Snippet language={'bash'}> sudo systemctl edit ollama.service</Snippet>
|
145
|
+
{t('OllamaSetupGuide.cors.linux.env')}
|
146
|
+
<Highlighter
|
147
|
+
// eslint-disable-next-line react/no-children-prop
|
148
|
+
children={`[Service]
|
149
|
+
|
150
|
+
Environment="OLLAMA_ORIGINS=*"`}
|
151
|
+
fileName={'ollama.service'}
|
152
|
+
fullFeatured
|
153
|
+
language={'bash'}
|
154
|
+
showLanguage
|
155
|
+
/>
|
156
|
+
{t('OllamaSetupGuide.cors.linux.reboot')}
|
157
|
+
</Flexbox>
|
158
|
+
),
|
159
|
+
status: 'process',
|
160
|
+
title: t('OllamaSetupGuide.cors.title'),
|
161
|
+
},
|
162
|
+
]}
|
163
|
+
size={'small'}
|
164
|
+
/>
|
165
|
+
),
|
166
|
+
key: 'linux',
|
167
|
+
label: 'Linux',
|
168
|
+
},
|
169
|
+
{
|
170
|
+
children: (
|
171
|
+
<Steps
|
172
|
+
className={styles.steps}
|
173
|
+
direction={'vertical'}
|
174
|
+
items={[
|
175
|
+
{
|
176
|
+
description: (
|
177
|
+
<Flexbox gap={8}>
|
178
|
+
{t('OllamaSetupGuide.install.description')}
|
179
|
+
<div>{t('OllamaSetupGuide.install.docker')}</div>
|
180
|
+
<Snippet language={'bash'}>docker pull ollama/ollama</Snippet>
|
181
|
+
</Flexbox>
|
182
|
+
),
|
183
|
+
status: 'process',
|
184
|
+
title: t('OllamaSetupGuide.install.title'),
|
185
|
+
},
|
186
|
+
{
|
187
|
+
description: (
|
188
|
+
<Flexbox gap={8}>
|
189
|
+
{t('OllamaSetupGuide.cors.description')}
|
190
|
+
<Highlighter
|
191
|
+
fileName={'ollama.service'}
|
192
|
+
fullFeatured
|
193
|
+
language={'bash'}
|
194
|
+
showLanguage
|
195
|
+
>
|
196
|
+
{/* eslint-disable-next-line react/no-unescaped-entities */}
|
197
|
+
docker run -d --gpus=all -v ollama:/root/.ollama -e OLLAMA_ORIGINS="*" -p
|
198
|
+
11434:11434 --name ollama ollama/ollama
|
199
|
+
</Highlighter>
|
200
|
+
</Flexbox>
|
201
|
+
),
|
202
|
+
status: 'process',
|
203
|
+
title: t('OllamaSetupGuide.cors.title'),
|
204
|
+
},
|
205
|
+
]}
|
206
|
+
size={'small'}
|
207
|
+
/>
|
208
|
+
),
|
209
|
+
key: 'docker',
|
210
|
+
label: 'Docker',
|
211
|
+
},
|
212
|
+
]}
|
213
|
+
/>
|
214
|
+
);
|
215
|
+
});
|
216
|
+
|
217
|
+
export default SetupGuide;
|
@@ -3,7 +3,7 @@ import { createStyles } from 'antd-style';
|
|
3
3
|
import { AnimatePresence, motion } from 'framer-motion';
|
4
4
|
import { AtomIcon, ChevronDown, ChevronRight } from 'lucide-react';
|
5
5
|
import { rgba } from 'polished';
|
6
|
-
import { memo, useEffect, useState } from 'react';
|
6
|
+
import { CSSProperties, memo, useEffect, useState } from 'react';
|
7
7
|
import { useTranslation } from 'react-i18next';
|
8
8
|
import { Flexbox } from 'react-layout-kit';
|
9
9
|
|
@@ -61,30 +61,26 @@ const useStyles = createStyles(({ css, token, isDarkMode }) => ({
|
|
61
61
|
interface ThinkingProps {
|
62
62
|
content?: string;
|
63
63
|
duration?: number;
|
64
|
+
style?: CSSProperties;
|
64
65
|
thinking?: boolean;
|
65
66
|
}
|
66
67
|
|
67
|
-
const Thinking = memo<ThinkingProps>(({ content, duration, thinking }) => {
|
68
|
+
const Thinking = memo<ThinkingProps>(({ content, duration, thinking, style }) => {
|
68
69
|
const { t } = useTranslation(['components', 'common']);
|
69
70
|
const { styles, cx } = useStyles();
|
70
71
|
|
71
72
|
const [showDetail, setShowDetail] = useState(false);
|
72
73
|
|
73
74
|
useEffect(() => {
|
74
|
-
|
75
|
-
|
76
|
-
}
|
77
|
-
|
78
|
-
if (!thinking) {
|
79
|
-
setShowDetail(false);
|
80
|
-
}
|
81
|
-
}, [thinking, content]);
|
75
|
+
setShowDetail(!!thinking);
|
76
|
+
}, [thinking]);
|
82
77
|
|
83
78
|
return (
|
84
|
-
<Flexbox className={cx(styles.container, showDetail && styles.expand)} gap={16}>
|
79
|
+
<Flexbox className={cx(styles.container, showDetail && styles.expand)} gap={16} style={style}>
|
85
80
|
<Flexbox
|
86
81
|
distribution={'space-between'}
|
87
82
|
flex={1}
|
83
|
+
gap={8}
|
88
84
|
horizontal
|
89
85
|
onClick={() => {
|
90
86
|
setShowDetail(!showDetail);
|
@@ -92,18 +88,20 @@ const Thinking = memo<ThinkingProps>(({ content, duration, thinking }) => {
|
|
92
88
|
style={{ cursor: 'pointer' }}
|
93
89
|
>
|
94
90
|
{thinking ? (
|
95
|
-
<Flexbox gap={8} horizontal>
|
91
|
+
<Flexbox align={'center'} gap={8} horizontal>
|
96
92
|
<Icon icon={AtomIcon} />
|
97
93
|
<Flexbox className={styles.shinyText} horizontal>
|
98
94
|
{t('Thinking.thinking')}
|
99
95
|
</Flexbox>
|
100
96
|
</Flexbox>
|
101
97
|
) : (
|
102
|
-
<Flexbox gap={8} horizontal>
|
98
|
+
<Flexbox align={'center'} gap={8} horizontal>
|
103
99
|
<Icon icon={AtomIcon} />
|
104
|
-
|
105
|
-
|
106
|
-
|
100
|
+
<Flexbox>
|
101
|
+
{!duration
|
102
|
+
? t('Thinking.thoughtWithDuration')
|
103
|
+
: t('Thinking.thought', { duration: ((duration || 0) / 1000).toFixed(1) })}
|
104
|
+
</Flexbox>
|
107
105
|
</Flexbox>
|
108
106
|
)}
|
109
107
|
<Flexbox gap={4} horizontal>
|