@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.
Files changed (95) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/ar/components.json +24 -0
  4. package/locales/ar/modelProvider.json +0 -24
  5. package/locales/ar/models.json +15 -0
  6. package/locales/bg-BG/components.json +24 -0
  7. package/locales/bg-BG/modelProvider.json +0 -24
  8. package/locales/bg-BG/models.json +15 -0
  9. package/locales/de-DE/components.json +24 -0
  10. package/locales/de-DE/modelProvider.json +0 -24
  11. package/locales/de-DE/models.json +15 -0
  12. package/locales/en-US/components.json +24 -0
  13. package/locales/en-US/modelProvider.json +0 -24
  14. package/locales/en-US/models.json +15 -0
  15. package/locales/es-ES/components.json +24 -0
  16. package/locales/es-ES/modelProvider.json +0 -24
  17. package/locales/es-ES/models.json +15 -0
  18. package/locales/fa-IR/components.json +24 -0
  19. package/locales/fa-IR/modelProvider.json +0 -24
  20. package/locales/fa-IR/models.json +15 -0
  21. package/locales/fr-FR/components.json +24 -0
  22. package/locales/fr-FR/modelProvider.json +0 -24
  23. package/locales/fr-FR/models.json +15 -0
  24. package/locales/it-IT/components.json +24 -0
  25. package/locales/it-IT/modelProvider.json +0 -24
  26. package/locales/it-IT/models.json +15 -0
  27. package/locales/ja-JP/components.json +24 -0
  28. package/locales/ja-JP/modelProvider.json +0 -24
  29. package/locales/ja-JP/models.json +15 -0
  30. package/locales/ko-KR/components.json +24 -0
  31. package/locales/ko-KR/modelProvider.json +0 -24
  32. package/locales/ko-KR/models.json +4 -0
  33. package/locales/nl-NL/components.json +24 -0
  34. package/locales/nl-NL/modelProvider.json +0 -24
  35. package/locales/nl-NL/models.json +15 -0
  36. package/locales/pl-PL/components.json +24 -0
  37. package/locales/pl-PL/modelProvider.json +0 -24
  38. package/locales/pl-PL/models.json +15 -0
  39. package/locales/pt-BR/components.json +24 -0
  40. package/locales/pt-BR/modelProvider.json +0 -24
  41. package/locales/pt-BR/models.json +15 -0
  42. package/locales/ru-RU/components.json +24 -0
  43. package/locales/ru-RU/modelProvider.json +0 -24
  44. package/locales/ru-RU/models.json +15 -0
  45. package/locales/tr-TR/components.json +24 -0
  46. package/locales/tr-TR/modelProvider.json +0 -24
  47. package/locales/tr-TR/models.json +15 -0
  48. package/locales/vi-VN/components.json +24 -0
  49. package/locales/vi-VN/modelProvider.json +0 -24
  50. package/locales/vi-VN/models.json +15 -0
  51. package/locales/zh-CN/components.json +24 -0
  52. package/locales/zh-CN/modelProvider.json +0 -24
  53. package/locales/zh-CN/models.json +16 -1
  54. package/locales/zh-TW/components.json +24 -0
  55. package/locales/zh-TW/modelProvider.json +0 -24
  56. package/locales/zh-TW/models.json +15 -0
  57. package/package.json +1 -1
  58. package/src/app/(main)/chat/(workspace)/@portal/_layout/Mobile.tsx +1 -0
  59. package/src/app/(main)/chat/(workspace)/_layout/Desktop/Portal.tsx +26 -2
  60. package/src/app/(main)/settings/provider/(detail)/[id]/page.tsx +10 -3
  61. package/src/app/(main)/settings/provider/(detail)/ollama/CheckError.tsx +70 -0
  62. package/src/app/(main)/settings/provider/(detail)/ollama/Container.tsx +57 -0
  63. package/src/app/(main)/settings/provider/(detail)/ollama/OllamaModelDownloader/index.tsx +127 -0
  64. package/src/app/(main)/settings/provider/(detail)/ollama/OllamaModelDownloader/useDownloadMonitor.ts +29 -0
  65. package/src/app/(main)/settings/provider/(detail)/ollama/page.tsx +2 -7
  66. package/src/app/(main)/settings/provider/features/ProviderConfig/Checker.tsx +90 -69
  67. package/src/app/(main)/settings/provider/features/ProviderConfig/index.tsx +6 -6
  68. package/src/components/FormAction/index.tsx +66 -0
  69. package/src/components/OllamaSetupGuide/index.tsx +217 -0
  70. package/src/components/Thinking/index.tsx +14 -16
  71. package/src/config/aiModels/ollama.ts +12 -19
  72. package/src/config/modelProviders/ollama.ts +1 -0
  73. package/src/config/modelProviders/siliconcloud.ts +2 -2
  74. package/src/database/repositories/aiInfra/index.ts +33 -2
  75. package/src/database/server/models/aiProvider.ts +5 -1
  76. package/src/features/Conversation/Error/OllamaBizError/SetupGuide.tsx +2 -209
  77. package/src/features/Conversation/components/MarkdownElements/LobeThinking/Render.tsx +7 -58
  78. package/src/libs/agent-runtime/ollama/index.ts +1 -1
  79. package/src/libs/agent-runtime/siliconcloud/index.ts +33 -1
  80. package/src/locales/default/components.ts +26 -0
  81. package/src/locales/default/modelProvider.ts +0 -26
  82. package/src/server/routers/lambda/aiProvider.ts +2 -10
  83. package/src/services/aiProvider/client.ts +2 -8
  84. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +10 -10
  85. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +4 -3
  86. package/src/store/chat/slices/aiChat/initialState.ts +1 -1
  87. package/src/store/chat/slices/message/action.ts +4 -3
  88. package/src/store/global/initialState.ts +2 -0
  89. package/src/store/global/selectors.ts +2 -0
  90. package/src/store/serverConfig/selectors.test.ts +3 -0
  91. package/src/store/serverConfig/store.test.ts +3 -2
  92. package/src/store/serverConfig/store.ts +1 -1
  93. package/src/store/user/slices/common/action.test.ts +1 -0
  94. package/src/types/serverConfig.ts +1 -1
  95. package/src/app/(main)/settings/provider/(detail)/ollama/Checker.tsx +0 -73
@@ -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 Checker from './Checker';
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
- checkerItem={{
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>(({ model, provider }) => {
47
- const { t } = useTranslation('setting');
53
+ const Checker = memo<ConnectionCheckerProps>(
54
+ ({ model, provider, checkErrorRender: CheckErrorRender }) => {
55
+ const { t } = useTranslation('setting');
48
56
 
49
- const disabled = useAiInfraStore(aiProviderSelectors.isProviderConfigUpdating(provider));
57
+ const disabled = useAiInfraStore(aiProviderSelectors.isProviderConfigUpdating(provider));
50
58
 
51
- const [loading, setLoading] = useState(false);
52
- const [pass, setPass] = useState(false);
59
+ const [loading, setLoading] = useState(false);
60
+ const [pass, setPass] = useState(false);
53
61
 
54
- const theme = useTheme();
55
- const [error, setError] = useState<ChatMessageError | undefined>();
62
+ const theme = useTheme();
63
+ const [error, setError] = useState<ChatMessageError | undefined>();
56
64
 
57
- const checkConnection = async () => {
58
- let isError = false;
65
+ const checkConnection = async () => {
66
+ let isError = false;
59
67
 
60
- await chatService.fetchPresetTaskResult({
61
- onError: (_, rawError) => {
62
- setError(rawError);
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
- setError({
73
- body: value,
74
- message: t('response.ConnectionCheckFailed', { ns: 'error' }),
75
- type: 'ConnectionCheckFailed',
76
- });
77
- }
78
- },
79
- onLoadingChange: (loading) => {
80
- setLoading(loading);
81
- },
82
- params: {
83
- messages: [
84
- {
85
- content: '你好',
86
- role: 'user',
87
- },
88
- ],
89
- model,
90
- provider,
91
- },
92
- trace: {
93
- sessionId: `connection:${provider}`,
94
- topicId: model,
95
- traceName: TraceNameMap.ConnectivityChecker,
96
- },
97
- });
98
- };
99
- const isMobile = useIsMobile();
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
- return (
102
- <Flexbox align={isMobile ? 'flex-start' : 'flex-end'} gap={8}>
103
- <Flexbox align={'center'} direction={isMobile ? 'horizontal-reverse' : 'horizontal'} gap={12}>
104
- {pass && (
105
- <Flexbox gap={4} horizontal>
106
- <CheckCircleFilled
107
- style={{
108
- color: theme.colorSuccess,
109
- }}
110
- />
111
- {t('llm.checker.pass')}
112
- </Flexbox>
113
- )}
114
- <Button disabled={disabled} loading={loading} onClick={checkConnection}>
115
- {t('llm.checker.button')}
116
- </Button>
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
- {error && <Error error={error} />}
119
- </Flexbox>
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
- checkerItem?: FormItemProps;
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
- ? (checkerItem ?? {
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
- if (thinking && !content) {
75
- setShowDetail(true);
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
- {!duration
105
- ? t('Thinking.thoughtWithDuration')
106
- : t('Thinking.thought', { duration: ((duration || 0) / 1000).toFixed(1) })}
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>