@lobehub/chat 1.77.16 → 1.77.18

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 (145) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/contributing/Basic/Architecture.md +1 -1
  4. package/contributing/Basic/Architecture.zh-CN.md +1 -1
  5. package/contributing/Basic/Chat-API.md +326 -108
  6. package/contributing/Basic/Chat-API.zh-CN.md +313 -133
  7. package/contributing/Basic/Contributing-Guidelines.md +7 -4
  8. package/contributing/Basic/Contributing-Guidelines.zh-CN.md +7 -6
  9. package/contributing/Home.md +5 -5
  10. package/contributing/State-Management/State-Management-Intro.md +1 -1
  11. package/contributing/State-Management/State-Management-Intro.zh-CN.md +1 -1
  12. package/docker-compose/local/docker-compose.yml +2 -1
  13. package/locales/ar/components.json +4 -0
  14. package/locales/ar/modelProvider.json +1 -0
  15. package/locales/ar/models.json +8 -5
  16. package/locales/ar/tool.json +21 -1
  17. package/locales/bg-BG/components.json +4 -0
  18. package/locales/bg-BG/modelProvider.json +1 -0
  19. package/locales/bg-BG/models.json +8 -5
  20. package/locales/bg-BG/tool.json +21 -1
  21. package/locales/de-DE/components.json +4 -0
  22. package/locales/de-DE/modelProvider.json +1 -0
  23. package/locales/de-DE/models.json +8 -5
  24. package/locales/de-DE/tool.json +21 -1
  25. package/locales/en-US/components.json +4 -0
  26. package/locales/en-US/modelProvider.json +1 -0
  27. package/locales/en-US/models.json +8 -5
  28. package/locales/en-US/tool.json +21 -1
  29. package/locales/es-ES/components.json +4 -0
  30. package/locales/es-ES/modelProvider.json +1 -0
  31. package/locales/es-ES/models.json +7 -4
  32. package/locales/es-ES/tool.json +21 -1
  33. package/locales/fa-IR/components.json +4 -0
  34. package/locales/fa-IR/modelProvider.json +1 -0
  35. package/locales/fa-IR/models.json +7 -4
  36. package/locales/fa-IR/tool.json +21 -1
  37. package/locales/fr-FR/components.json +4 -0
  38. package/locales/fr-FR/modelProvider.json +1 -0
  39. package/locales/fr-FR/models.json +8 -5
  40. package/locales/fr-FR/tool.json +21 -1
  41. package/locales/it-IT/components.json +4 -0
  42. package/locales/it-IT/modelProvider.json +1 -0
  43. package/locales/it-IT/models.json +7 -4
  44. package/locales/it-IT/tool.json +21 -1
  45. package/locales/ja-JP/components.json +4 -0
  46. package/locales/ja-JP/modelProvider.json +1 -0
  47. package/locales/ja-JP/models.json +8 -5
  48. package/locales/ja-JP/tool.json +21 -1
  49. package/locales/ko-KR/components.json +4 -0
  50. package/locales/ko-KR/modelProvider.json +1 -0
  51. package/locales/ko-KR/models.json +8 -5
  52. package/locales/ko-KR/tool.json +21 -1
  53. package/locales/nl-NL/components.json +4 -0
  54. package/locales/nl-NL/modelProvider.json +1 -0
  55. package/locales/nl-NL/models.json +8 -5
  56. package/locales/nl-NL/tool.json +21 -1
  57. package/locales/pl-PL/components.json +4 -0
  58. package/locales/pl-PL/modelProvider.json +1 -0
  59. package/locales/pl-PL/models.json +8 -5
  60. package/locales/pl-PL/tool.json +21 -1
  61. package/locales/pt-BR/components.json +4 -0
  62. package/locales/pt-BR/modelProvider.json +1 -0
  63. package/locales/pt-BR/models.json +7 -4
  64. package/locales/pt-BR/tool.json +21 -1
  65. package/locales/ru-RU/components.json +4 -0
  66. package/locales/ru-RU/modelProvider.json +1 -0
  67. package/locales/ru-RU/models.json +7 -4
  68. package/locales/ru-RU/tool.json +21 -1
  69. package/locales/tr-TR/components.json +4 -0
  70. package/locales/tr-TR/modelProvider.json +1 -0
  71. package/locales/tr-TR/models.json +8 -5
  72. package/locales/tr-TR/tool.json +21 -1
  73. package/locales/vi-VN/components.json +4 -0
  74. package/locales/vi-VN/modelProvider.json +1 -0
  75. package/locales/vi-VN/models.json +8 -5
  76. package/locales/vi-VN/tool.json +21 -1
  77. package/locales/zh-CN/components.json +4 -0
  78. package/locales/zh-CN/modelProvider.json +1 -0
  79. package/locales/zh-CN/models.json +9 -6
  80. package/locales/zh-CN/tool.json +30 -1
  81. package/locales/zh-TW/components.json +4 -0
  82. package/locales/zh-TW/modelProvider.json +1 -0
  83. package/locales/zh-TW/models.json +7 -4
  84. package/locales/zh-TW/tool.json +21 -1
  85. package/package.json +1 -1
  86. package/src/app/(backend)/webapi/models/[provider]/pull/route.ts +34 -0
  87. package/src/app/(backend)/webapi/{chat/models → models}/[provider]/route.ts +1 -2
  88. package/src/app/[variants]/(main)/settings/llm/ProviderList/Ollama/index.tsx +0 -7
  89. package/src/app/[variants]/(main)/settings/provider/(detail)/ollama/CheckError.tsx +1 -1
  90. package/src/components/FormAction/index.tsx +1 -1
  91. package/src/database/models/__tests__/aiProvider.test.ts +100 -0
  92. package/src/database/models/aiProvider.ts +11 -1
  93. package/src/features/Conversation/Error/OllamaBizError/InvalidOllamaModel.tsx +43 -0
  94. package/src/features/Conversation/Error/OllamaDesktopSetupGuide/index.tsx +61 -0
  95. package/src/features/Conversation/Error/index.tsx +7 -0
  96. package/src/features/DevPanel/SystemInspector/ServerConfig.tsx +18 -2
  97. package/src/features/DevPanel/SystemInspector/index.tsx +25 -6
  98. package/src/features/OllamaModelDownloader/index.tsx +149 -0
  99. package/src/libs/agent-runtime/AgentRuntime.ts +6 -0
  100. package/src/libs/agent-runtime/BaseAI.ts +7 -0
  101. package/src/libs/agent-runtime/ollama/index.ts +84 -2
  102. package/src/libs/agent-runtime/openrouter/__snapshots__/index.test.ts.snap +24 -3263
  103. package/src/libs/agent-runtime/openrouter/fixtures/frontendModels.json +25 -0
  104. package/src/libs/agent-runtime/openrouter/fixtures/models.json +0 -3353
  105. package/src/libs/agent-runtime/openrouter/index.test.ts +56 -1
  106. package/src/libs/agent-runtime/openrouter/index.ts +9 -4
  107. package/src/libs/agent-runtime/types/index.ts +1 -0
  108. package/src/libs/agent-runtime/types/model.ts +44 -0
  109. package/src/libs/agent-runtime/utils/streams/index.ts +1 -0
  110. package/src/libs/agent-runtime/utils/streams/model.ts +110 -0
  111. package/src/locales/default/components.ts +4 -0
  112. package/src/locales/default/modelProvider.ts +1 -0
  113. package/src/locales/default/tool.ts +30 -1
  114. package/src/server/modules/SearXNG.ts +10 -2
  115. package/src/server/routers/tools/__test__/search.test.ts +3 -1
  116. package/src/server/routers/tools/search.ts +10 -2
  117. package/src/services/__tests__/models.test.ts +21 -0
  118. package/src/services/_url.ts +4 -1
  119. package/src/services/chat.ts +1 -1
  120. package/src/services/models.ts +153 -7
  121. package/src/services/search.ts +2 -2
  122. package/src/store/aiInfra/slices/aiModel/action.ts +1 -1
  123. package/src/store/aiInfra/slices/aiProvider/action.ts +2 -1
  124. package/src/store/chat/slices/builtinTool/actions/searXNG.test.ts +28 -8
  125. package/src/store/chat/slices/builtinTool/actions/searXNG.ts +22 -5
  126. package/src/store/user/slices/modelList/action.test.ts +2 -2
  127. package/src/store/user/slices/modelList/action.ts +1 -1
  128. package/src/tools/web-browsing/Portal/Search/index.tsx +1 -1
  129. package/src/tools/web-browsing/Render/Search/SearchQuery/SearchView.tsx +1 -1
  130. package/src/tools/web-browsing/Render/Search/SearchQuery/index.tsx +1 -1
  131. package/src/tools/web-browsing/Render/Search/SearchResult/index.tsx +1 -1
  132. package/src/tools/web-browsing/components/CategoryAvatar.tsx +27 -0
  133. package/src/tools/web-browsing/components/SearchBar.tsx +84 -4
  134. package/src/tools/web-browsing/const.ts +26 -0
  135. package/src/tools/web-browsing/index.ts +58 -28
  136. package/src/tools/web-browsing/systemRole.ts +62 -1
  137. package/src/types/tool/search.ts +10 -1
  138. package/src/app/[variants]/(main)/settings/llm/ProviderList/Ollama/Checker.tsx +0 -73
  139. package/src/app/[variants]/(main)/settings/provider/(detail)/ollama/OllamaModelDownloader/index.tsx +0 -127
  140. package/src/features/Conversation/Error/OllamaBizError/InvalidOllamaModel/index.tsx +0 -154
  141. package/src/features/Conversation/Error/OllamaBizError/InvalidOllamaModel/useDownloadMonitor.ts +0 -29
  142. package/src/helpers/url.ts +0 -17
  143. package/src/services/__tests__/ollama.test.ts +0 -28
  144. package/src/services/ollama.ts +0 -83
  145. /package/src/{app/[variants]/(main)/settings/provider/(detail)/ollama → features}/OllamaModelDownloader/useDownloadMonitor.ts +0 -0
@@ -1,73 +0,0 @@
1
- import { CheckCircleFilled } from '@ant-design/icons';
2
- import { Alert, Highlighter } from '@lobehub/ui';
3
- import { Button } from 'antd';
4
- import { useTheme } from 'antd-style';
5
- import { ListResponse } from 'ollama/browser';
6
- import { memo } from 'react';
7
- import { useTranslation } from 'react-i18next';
8
- import { Flexbox } from 'react-layout-kit';
9
- import useSWR from 'swr';
10
-
11
- import { useIsMobile } from '@/hooks/useIsMobile';
12
- import { ollamaService } from '@/services/ollama';
13
-
14
- const OllamaChecker = memo(() => {
15
- const { t } = useTranslation('setting');
16
-
17
- const theme = useTheme();
18
-
19
- const { data, error, isLoading, mutate } = useSWR<ListResponse>(
20
- 'ollama.list',
21
- ollamaService.getModels,
22
- {
23
- revalidateOnFocus: false,
24
- revalidateOnMount: false,
25
- revalidateOnReconnect: false,
26
- },
27
- );
28
-
29
- const checkConnection = () => {
30
- mutate().catch();
31
- };
32
-
33
- const isMobile = useIsMobile();
34
-
35
- return (
36
- <Flexbox align={isMobile ? 'flex-start' : 'flex-end'} gap={8}>
37
- <Flexbox align={'center'} direction={isMobile ? 'horizontal-reverse' : 'horizontal'} gap={12}>
38
- {!error && data?.models && (
39
- <Flexbox gap={4} horizontal>
40
- <CheckCircleFilled
41
- style={{
42
- color: theme.colorSuccess,
43
- }}
44
- />
45
- {t('llm.checker.pass')}
46
- </Flexbox>
47
- )}
48
- <Button loading={isLoading} onClick={checkConnection}>
49
- {t('llm.checker.button')}
50
- </Button>
51
- </Flexbox>
52
- {error && (
53
- <Flexbox gap={8} style={{ maxWidth: '600px', width: '100%' }}>
54
- <Alert
55
- banner
56
- extra={
57
- <Flexbox>
58
- <Highlighter copyButtonSize={'small'} language={'json'} type={'pure'}>
59
- {JSON.stringify(error.body || error, null, 2)}
60
- </Highlighter>
61
- </Flexbox>
62
- }
63
- message={t(`response.${error.type}` as any, { ns: 'error' })}
64
- showIcon
65
- type={'error'}
66
- />
67
- </Flexbox>
68
- )}
69
- </Flexbox>
70
- );
71
- });
72
-
73
- export default OllamaChecker;
@@ -1,127 +0,0 @@
1
- import { Ollama } from '@lobehub/icons';
2
- import { Button, Input, Progress } from 'antd';
3
- import { useTheme } from 'antd-style';
4
- import { memo, useMemo, useState } from 'react';
5
- import { useTranslation } from 'react-i18next';
6
- import { Center, Flexbox } from 'react-layout-kit';
7
- import useSWR from 'swr';
8
-
9
- import FormAction from '@/components/FormAction';
10
- import { ollamaService } from '@/services/ollama';
11
- import { formatSize } from '@/utils/format';
12
-
13
- import { useDownloadMonitor } from './useDownloadMonitor';
14
-
15
- interface OllamaModelDownloaderProps {
16
- model: string;
17
- }
18
-
19
- const OllamaModelDownloader = memo<OllamaModelDownloaderProps>(({ model }) => {
20
- const { t } = useTranslation(['modelProvider', 'error']);
21
-
22
- const [modelToPull, setModelToPull] = useState(model);
23
- const [completed, setCompleted] = useState(0);
24
- const [total, setTotal] = useState(0);
25
- const { remainingTime, downloadSpeed } = useDownloadMonitor(total, completed);
26
- const percent = useMemo(() => {
27
- return total ? Number(((completed / total) * 100).toFixed(1)) : 0;
28
- }, [completed, total]);
29
-
30
- const theme = useTheme();
31
-
32
- const { mutate, isLoading: isDownloading } = useSWR(
33
- [modelToPull],
34
- async ([model]) => {
35
- const generator = await ollamaService.pullModel(model);
36
- for await (const progress of generator) {
37
- if (progress.completed) {
38
- setCompleted(progress.completed);
39
- setTotal(progress.total);
40
- }
41
- }
42
- return null;
43
- },
44
- {
45
- onSuccess: () => {},
46
- revalidateOnFocus: false,
47
- revalidateOnMount: false,
48
- },
49
- );
50
-
51
- return (
52
- <Center gap={16} paddingBlock={32} style={{ width: '100%' }}>
53
- <FormAction
54
- avatar={<Ollama color={theme.colorPrimary} size={64} />}
55
- description={isDownloading ? t('ollama.download.desc') : t('ollama.unlock.description')}
56
- title={
57
- isDownloading
58
- ? t('ollama.download.title', { model: modelToPull })
59
- : t('ollama.unlock.title')
60
- }
61
- >
62
- {!isDownloading && (
63
- <Input
64
- onChange={(e) => {
65
- setModelToPull(e.target.value);
66
- }}
67
- value={modelToPull}
68
- />
69
- )}
70
- </FormAction>
71
- {isDownloading && (
72
- <Flexbox flex={1} gap={8} style={{ maxWidth: 300 }} width={'100%'}>
73
- <Progress
74
- percent={percent}
75
- showInfo
76
- strokeColor={theme.colorSuccess}
77
- trailColor={theme.colorSuccessBg}
78
- />
79
- <Flexbox
80
- distribution={'space-between'}
81
- horizontal
82
- style={{ color: theme.colorTextDescription, fontSize: 12 }}
83
- >
84
- <span>
85
- {t('ollama.download.remainingTime')}: {remainingTime}
86
- </span>
87
- <span>
88
- {t('ollama.download.speed')}: {downloadSpeed}
89
- </span>
90
- </Flexbox>
91
- </Flexbox>
92
- )}
93
- <Flexbox gap={12} style={{ maxWidth: 300 }} width={'100%'}>
94
- <Button
95
- block
96
- loading={isDownloading}
97
- onClick={() => {
98
- mutate();
99
- }}
100
- style={{ marginTop: 8 }}
101
- type={'primary'}
102
- >
103
- {!isDownloading
104
- ? t('ollama.unlock.confirm')
105
- : // if total is 0, show starting, else show downloaded
106
- !total
107
- ? t('ollama.unlock.starting')
108
- : t('ollama.unlock.downloaded', {
109
- completed: formatSize(completed, 2),
110
- total: formatSize(total, 2),
111
- })}
112
- </Button>
113
- {isDownloading && (
114
- <Button
115
- onClick={() => {
116
- ollamaService.abort();
117
- }}
118
- >
119
- {t('ollama.unlock.cancel')}
120
- </Button>
121
- )}
122
- </Flexbox>
123
- </Center>
124
- );
125
- });
126
-
127
- export default OllamaModelDownloader;
@@ -1,154 +0,0 @@
1
- import { Ollama } from '@lobehub/icons';
2
- import { Button, Input, Progress } from 'antd';
3
- import { useTheme } from 'antd-style';
4
- import { memo, useMemo, useState } from 'react';
5
- import { useTranslation } from 'react-i18next';
6
- import { Center, Flexbox } from 'react-layout-kit';
7
- import useSWR from 'swr';
8
-
9
- import { ollamaService } from '@/services/ollama';
10
- import { useChatStore } from '@/store/chat';
11
- import { formatSize } from '@/utils/format';
12
-
13
- import { ErrorActionContainer, FormAction } from '../../style';
14
- import { useDownloadMonitor } from './useDownloadMonitor';
15
-
16
- interface OllamaModelFormProps {
17
- id: string;
18
- model: string;
19
- }
20
-
21
- const OllamaModelForm = memo<OllamaModelFormProps>(({ id, model }) => {
22
- const { t } = useTranslation(['modelProvider', 'error']);
23
-
24
- const [modelToPull, setModelToPull] = useState(model);
25
- const [completed, setCompleted] = useState(0);
26
- const [total, setTotal] = useState(0);
27
- const { remainingTime, downloadSpeed } = useDownloadMonitor(total, completed);
28
- const percent = useMemo(() => {
29
- return total ? Number(((completed / total) * 100).toFixed(1)) : 0;
30
- }, [completed, total]);
31
-
32
- const [delAndRegenerateMessage, deleteMessage] = useChatStore((s) => [
33
- s.delAndRegenerateMessage,
34
- s.deleteMessage,
35
- ]);
36
- const theme = useTheme();
37
-
38
- const { mutate, isLoading: isDownloading } = useSWR(
39
- [id, modelToPull],
40
- async ([, model]) => {
41
- const generator = await ollamaService.pullModel(model);
42
- for await (const progress of generator) {
43
- if (progress.completed) {
44
- setCompleted(progress.completed);
45
- setTotal(progress.total);
46
- }
47
- }
48
- return null;
49
- },
50
- {
51
- onSuccess: () => {
52
- delAndRegenerateMessage(id);
53
- },
54
- revalidateOnFocus: false,
55
- revalidateOnMount: false,
56
- },
57
- );
58
-
59
- return (
60
- <Center gap={16} style={{ maxWidth: 300, width: '100%' }}>
61
- <FormAction
62
- avatar={<Ollama color={theme.colorPrimary} size={64} />}
63
- description={isDownloading ? t('ollama.download.desc') : t('ollama.unlock.description')}
64
- title={
65
- isDownloading
66
- ? t('ollama.download.title', { model: modelToPull })
67
- : t('ollama.unlock.title')
68
- }
69
- >
70
- {!isDownloading && (
71
- <Input
72
- onChange={(e) => {
73
- setModelToPull(e.target.value);
74
- }}
75
- value={modelToPull}
76
- />
77
- )}
78
- </FormAction>
79
- {isDownloading && (
80
- <Flexbox flex={1} gap={8} width={'100%'}>
81
- <Progress
82
- percent={percent}
83
- showInfo
84
- strokeColor={theme.colorSuccess}
85
- trailColor={theme.colorSuccessBg}
86
- />
87
- <Flexbox
88
- distribution={'space-between'}
89
- horizontal
90
- style={{ color: theme.colorTextDescription, fontSize: 12 }}
91
- >
92
- <span>
93
- {t('ollama.download.remainingTime')}: {remainingTime}
94
- </span>
95
- <span>
96
- {t('ollama.download.speed')}: {downloadSpeed}
97
- </span>
98
- </Flexbox>
99
- </Flexbox>
100
- )}
101
- <Flexbox gap={12} width={'100%'}>
102
- <Button
103
- block
104
- loading={isDownloading}
105
- onClick={() => {
106
- mutate();
107
- }}
108
- style={{ marginTop: 8 }}
109
- type={'primary'}
110
- >
111
- {!isDownloading
112
- ? t('ollama.unlock.confirm')
113
- : // if total is 0, show starting, else show downloaded
114
- !total
115
- ? t('ollama.unlock.starting')
116
- : t('ollama.unlock.downloaded', {
117
- completed: formatSize(completed, 2),
118
- total: formatSize(total, 2),
119
- })}
120
- </Button>
121
- {isDownloading ? (
122
- <Button
123
- onClick={() => {
124
- ollamaService.abort();
125
- }}
126
- >
127
- {t('ollama.unlock.cancel')}
128
- </Button>
129
- ) : (
130
- <Button
131
- onClick={() => {
132
- deleteMessage(id);
133
- }}
134
- >
135
- {t('unlock.closeMessage', { ns: 'error' })}
136
- </Button>
137
- )}
138
- </Flexbox>
139
- </Center>
140
- );
141
- });
142
-
143
- interface InvalidOllamaModelProps {
144
- id: string;
145
- model: string;
146
- }
147
-
148
- const InvalidOllamaModel = memo<InvalidOllamaModelProps>(({ id, model }) => (
149
- <ErrorActionContainer>
150
- <OllamaModelForm id={id} model={model} />
151
- </ErrorActionContainer>
152
- ));
153
-
154
- export default InvalidOllamaModel;
@@ -1,29 +0,0 @@
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
- };
@@ -1,17 +0,0 @@
1
- import { ChatMessage } from '@lobehub/ui';
2
-
3
- import { Compressor } from '@/utils/compass';
4
-
5
- export const genShareMessagesUrl = (messages: ChatMessage[], systemRole?: string) => {
6
- const compassedMsg = systemRole
7
- ? [{ content: systemRole, role: 'system' }, ...messages]
8
- : messages;
9
-
10
- return `/share?messages=${Compressor.compress(JSON.stringify(compassedMsg))}`;
11
- };
12
-
13
- export const genSystemRoleQuery = async (content: string) => {
14
- const x = { state: { systemRole: content } };
15
- const systemRole = await Compressor.compressAsync(JSON.stringify(x));
16
- return `#systemRole=${systemRole}`;
17
- };
@@ -1,28 +0,0 @@
1
- import { Mock, describe, expect, it, vi } from 'vitest';
2
-
3
- import { OllamaService } from '../ollama';
4
-
5
- vi.stubGlobal('fetch', vi.fn());
6
-
7
- const ollamaService = new OllamaService({ fetch });
8
-
9
- describe('OllamaService', () => {
10
- describe('list models', async () => {
11
- it('should make a GET request with the correct payload', async () => {
12
- (fetch as Mock).mockResolvedValueOnce(new Response(JSON.stringify({ models: [] })));
13
-
14
- expect(await ollamaService.getModels()).toEqual({ models: [] });
15
-
16
- expect(fetch).toHaveBeenCalled();
17
- });
18
-
19
- it('should make a GET request with the error', async () => {
20
- const mockResponse = new Response(null, { status: 503 });
21
- (fetch as Mock).mockResolvedValueOnce(mockResponse);
22
-
23
- await expect(ollamaService.getModels()).rejects.toThrow();
24
-
25
- expect(fetch).toHaveBeenCalled();
26
- });
27
- });
28
- });
@@ -1,83 +0,0 @@
1
- import { ListResponse, Ollama as OllamaBrowser, ProgressResponse } from 'ollama/browser';
2
-
3
- import { ModelProvider } from '@/libs/agent-runtime';
4
- import { useUserStore } from '@/store/user';
5
- import { keyVaultsConfigSelectors } from '@/store/user/selectors';
6
- import { ChatErrorType } from '@/types/fetch';
7
- import { createErrorResponse } from '@/utils/errorResponse';
8
- import { getMessageError } from '@/utils/fetch';
9
-
10
- const DEFAULT_BASE_URL = 'http://127.0.0.1:11434';
11
-
12
- interface OllamaServiceParams {
13
- fetch?: typeof fetch;
14
- }
15
-
16
- export class OllamaService {
17
- private _host: string;
18
- private _client: OllamaBrowser;
19
- private _fetch?: typeof fetch;
20
-
21
- constructor(params: OllamaServiceParams = {}) {
22
- this._host = this.getHost();
23
- this._fetch = params.fetch;
24
- this._client = new OllamaBrowser({ fetch: params?.fetch, host: this._host });
25
- }
26
-
27
- getHost = (): string => {
28
- const config = keyVaultsConfigSelectors.ollamaConfig(useUserStore.getState());
29
-
30
- return config.baseURL || DEFAULT_BASE_URL;
31
- };
32
-
33
- getOllamaClient = () => {
34
- if (this.getHost() !== this._host) {
35
- this._host = this.getHost();
36
- this._client = new OllamaBrowser({ fetch: this._fetch, host: this.getHost() });
37
- }
38
- return this._client;
39
- };
40
-
41
- abort = () => {
42
- this._client.abort();
43
- };
44
-
45
- pullModel = async (model: string): Promise<AsyncIterable<ProgressResponse>> => {
46
- let response: Response | AsyncIterable<ProgressResponse>;
47
- try {
48
- response = await this.getOllamaClient().pull({ insecure: true, model, stream: true });
49
- return response;
50
- } catch {
51
- response = createErrorResponse(ChatErrorType.OllamaServiceUnavailable, {
52
- host: this.getHost(),
53
- message: 'please check whether your ollama service is available or set the CORS rules',
54
- provider: ModelProvider.Ollama,
55
- });
56
- }
57
-
58
- if (!response.ok) {
59
- throw await getMessageError(response);
60
- }
61
- return response.json();
62
- };
63
-
64
- getModels = async (): Promise<ListResponse> => {
65
- let response: Response | ListResponse;
66
- try {
67
- return await this.getOllamaClient().list();
68
- } catch {
69
- response = createErrorResponse(ChatErrorType.OllamaServiceUnavailable, {
70
- host: this.getHost(),
71
- message: 'please check whether your ollama service is available or set the CORS rules',
72
- provider: ModelProvider.Ollama,
73
- });
74
- }
75
-
76
- if (!response.ok) {
77
- throw await getMessageError(response);
78
- }
79
- return response.json();
80
- };
81
- }
82
-
83
- export const ollamaService = new OllamaService();