@lobehub/chat 1.108.1 → 1.109.0

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 (28) hide show
  1. package/.cursor/rules/testing-guide/testing-guide.mdc +18 -0
  2. package/CHANGELOG.md +50 -0
  3. package/changelog/v1.json +18 -0
  4. package/package.json +2 -2
  5. package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/Checker.tsx +15 -2
  6. package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/index.tsx +30 -3
  7. package/src/components/Analytics/LobeAnalyticsProvider.tsx +10 -13
  8. package/src/components/Analytics/LobeAnalyticsProviderWrapper.tsx +16 -4
  9. package/src/config/aiModels/ollama.ts +27 -0
  10. package/src/libs/model-runtime/RouterRuntime/createRuntime.test.ts +538 -0
  11. package/src/libs/model-runtime/RouterRuntime/createRuntime.ts +50 -13
  12. package/src/libs/model-runtime/RouterRuntime/index.ts +1 -1
  13. package/src/libs/model-runtime/aihubmix/index.ts +10 -5
  14. package/src/libs/model-runtime/ppio/index.test.ts +3 -6
  15. package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.ts +8 -6
  16. package/src/server/globalConfig/genServerAiProviderConfig.test.ts +22 -25
  17. package/src/server/globalConfig/genServerAiProviderConfig.ts +34 -22
  18. package/src/server/globalConfig/index.ts +1 -1
  19. package/src/server/services/discover/index.ts +11 -2
  20. package/src/services/chat.ts +1 -1
  21. package/src/store/aiInfra/slices/aiProvider/__tests__/action.test.ts +211 -0
  22. package/src/store/aiInfra/slices/aiProvider/action.ts +46 -35
  23. package/src/store/user/slices/modelList/action.test.ts +5 -5
  24. package/src/store/user/slices/modelList/action.ts +4 -4
  25. package/src/utils/getFallbackModelProperty.test.ts +52 -45
  26. package/src/utils/getFallbackModelProperty.ts +4 -3
  27. package/src/utils/parseModels.test.ts +107 -98
  28. package/src/utils/parseModels.ts +10 -8
@@ -430,6 +430,22 @@ expect(result!.status).toBe('success');
430
430
  // ✅ 使用 any 类型简化复杂的 Mock 设置
431
431
  const mockStream = new ReadableStream() as any;
432
432
  mockStream.toReadableStream = () => mockStream;
433
+
434
+ // ✅ 使用中括号访问私有属性和方法(推荐)
435
+ class MyClass {
436
+ private _cache = new Map();
437
+ private getFromCache(key: string) { /* ... */ }
438
+ }
439
+
440
+ const instance = new MyClass();
441
+
442
+ // 推荐:使用中括号访问私有成员
443
+ await instance['getFromCache']('test-key');
444
+ expect(instance['_cache'].size).toBe(1);
445
+
446
+ // 避免:使用 as any 访问私有成员
447
+ await (instance as any).getFromCache('test-key'); // ❌ 不推荐
448
+ expect((instance as any)._cache.size).toBe(1); // ❌ 不推荐
433
449
  ```
434
450
 
435
451
  #### 🎯 适用场景
@@ -437,11 +453,13 @@ mockStream.toReadableStream = () => mockStream;
437
453
  - **Mock 对象**: 对于测试用的 Mock 数据,使用 `as any` 避免复杂的类型定义
438
454
  - **第三方库**: 处理复杂的第三方库类型时,适当使用 `any` 提高效率
439
455
  - **测试断言**: 在确定对象存在的测试场景中,使用 `!` 非空断言
456
+ - **私有成员访问**: 优先使用中括号 `instance['privateMethod']()` 而不是 `(instance as any).privateMethod()`
440
457
  - **临时调试**: 快速编写测试时,先用 `any` 保证功能,后续可选择性地优化类型
441
458
 
442
459
  #### ⚠️ 注意事项
443
460
 
444
461
  - **适度使用**: 不要过度依赖 `any`,核心业务逻辑的类型仍应保持严格
462
+ - **私有成员访问优先级**: 中括号访问 > `as any` 转换,保持更好的类型安全性
445
463
  - **文档说明**: 对于使用 `any` 的复杂场景,添加注释说明原因
446
464
  - **测试覆盖**: 确保即使使用了 `any`,测试仍能有效验证功能正确性
447
465
 
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 1.109.0](https://github.com/lobehub/lobe-chat/compare/v1.108.2...v1.109.0)
6
+
7
+ <sup>Released on **2025-08-05**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Support gpt-oss in ollama provider.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: Support gpt-oss in ollama provider, closes [#8682](https://github.com/lobehub/lobe-chat/issues/8682) ([6e0c386](https://github.com/lobehub/lobe-chat/commit/6e0c386))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ### [Version 1.108.2](https://github.com/lobehub/lobe-chat/compare/v1.108.1...v1.108.2)
31
+
32
+ <sup>Released on **2025-08-05**</sup>
33
+
34
+ #### 🐛 Bug Fixes
35
+
36
+ - **misc**: Provider config checker uses outdated API key.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's fixed
44
+
45
+ - **misc**: Provider config checker uses outdated API key, closes [#8666](https://github.com/lobehub/lobe-chat/issues/8666) ([3a3e73e](https://github.com/lobehub/lobe-chat/commit/3a3e73e))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ### [Version 1.108.1](https://github.com/lobehub/lobe-chat/compare/v1.108.0...v1.108.1)
6
56
 
7
57
  <sup>Released on **2025-08-05**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "features": [
5
+ "Support gpt-oss in ollama provider."
6
+ ]
7
+ },
8
+ "date": "2025-08-05",
9
+ "version": "1.109.0"
10
+ },
11
+ {
12
+ "children": {
13
+ "fixes": [
14
+ "Provider config checker uses outdated API key."
15
+ ]
16
+ },
17
+ "date": "2025-08-05",
18
+ "version": "1.108.2"
19
+ },
2
20
  {
3
21
  "children": {
4
22
  "fixes": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.108.1",
3
+ "version": "1.109.0",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -146,7 +146,7 @@
146
146
  "@lobechat/electron-server-ipc": "workspace:*",
147
147
  "@lobechat/file-loaders": "workspace:*",
148
148
  "@lobechat/web-crawler": "workspace:*",
149
- "@lobehub/analytics": "^1.5.1",
149
+ "@lobehub/analytics": "^1.6.0",
150
150
  "@lobehub/charts": "^2.0.0",
151
151
  "@lobehub/chat-plugin-sdk": "^1.32.4",
152
152
  "@lobehub/chat-plugins-gateway": "^1.9.0",
@@ -46,11 +46,13 @@ export type CheckErrorRender = (props: {
46
46
  interface ConnectionCheckerProps {
47
47
  checkErrorRender?: CheckErrorRender;
48
48
  model: string;
49
+ onAfterCheck: () => Promise<void>;
50
+ onBeforeCheck: () => Promise<void>;
49
51
  provider: string;
50
52
  }
51
53
 
52
54
  const Checker = memo<ConnectionCheckerProps>(
53
- ({ model, provider, checkErrorRender: CheckErrorRender }) => {
55
+ ({ model, provider, checkErrorRender: CheckErrorRender, onBeforeCheck, onAfterCheck }) => {
54
56
  const { t } = useTranslation('setting');
55
57
 
56
58
  const isProviderConfigUpdating = useAiInfraStore(
@@ -152,7 +154,18 @@ const Checker = memo<ConnectionCheckerProps>(
152
154
  value={checkModel}
153
155
  virtual
154
156
  />
155
- <Button disabled={isProviderConfigUpdating} loading={loading} onClick={checkConnection}>
157
+ <Button
158
+ disabled={isProviderConfigUpdating}
159
+ loading={loading}
160
+ onClick={async () => {
161
+ await onBeforeCheck();
162
+ try {
163
+ await checkConnection();
164
+ } finally {
165
+ await onAfterCheck();
166
+ }
167
+ }}
168
+ >
156
169
  {t('llm.checker.button')}
157
170
  </Button>
158
171
  </Flexbox>
@@ -14,7 +14,7 @@ import { Skeleton, Switch } from 'antd';
14
14
  import { createStyles } from 'antd-style';
15
15
  import { Loader2Icon, LockIcon } from 'lucide-react';
16
16
  import Link from 'next/link';
17
- import { ReactNode, memo, useLayoutEffect } from 'react';
17
+ import { ReactNode, memo, useCallback, useLayoutEffect, useRef } from 'react';
18
18
  import { Trans, useTranslation } from 'react-i18next';
19
19
  import { Center, Flexbox } from 'react-layout-kit';
20
20
  import urlJoin from 'url-join';
@@ -173,7 +173,24 @@ const ProviderConfig = memo<ProviderConfigProps>(
173
173
  form.setFieldsValue(data);
174
174
  }, [isLoading, id, data]);
175
175
 
176
- const { run: debouncedUpdate } = useDebounceFn(updateAiProviderConfig, { wait: 500 });
176
+ // 标记是否正在进行连接测试
177
+ const isCheckingConnection = useRef(false);
178
+
179
+ const handleValueChange = useCallback(
180
+ (...params: Parameters<typeof updateAiProviderConfig>) => {
181
+ // 虽然 debouncedHandleValueChange 早于 onBeforeCheck 执行,
182
+ // 但是由于 debouncedHandleValueChange 因为 debounce 的原因,本来就会晚 500ms 执行
183
+ // 所以 isCheckingConnection.current 这时候已经更新了
184
+ // 测试链接时已经出发一次了 updateAiProviderConfig , 不应该重复更新
185
+ if (isCheckingConnection.current) return;
186
+
187
+ updateAiProviderConfig(...params);
188
+ },
189
+ [updateAiProviderConfig],
190
+ );
191
+ const { run: debouncedHandleValueChange } = useDebounceFn(handleValueChange, {
192
+ wait: 500,
193
+ });
177
194
 
178
195
  const isCustom = source === AiProviderSourceEnum.Custom;
179
196
 
@@ -318,6 +335,16 @@ const ProviderConfig = memo<ProviderConfigProps>(
318
335
  <Checker
319
336
  checkErrorRender={checkErrorRender}
320
337
  model={data?.checkModel || checkModel!}
338
+ onAfterCheck={async () => {
339
+ // 重置连接测试状态,允许后续的 onValuesChange 更新
340
+ isCheckingConnection.current = false;
341
+ }}
342
+ onBeforeCheck={async () => {
343
+ // 设置连接测试状态,阻止 onValuesChange 的重复请求
344
+ isCheckingConnection.current = true;
345
+ // 主动保存表单最新值,确保 fetchAiProviderRuntimeState 获取最新数据
346
+ await updateAiProviderConfig(id, form.getFieldsValue());
347
+ }}
321
348
  provider={id}
322
349
  />
323
350
  ),
@@ -389,7 +416,7 @@ const ProviderConfig = memo<ProviderConfigProps>(
389
416
  form={form}
390
417
  items={[model]}
391
418
  onValuesChange={(_, values) => {
392
- debouncedUpdate(id, values);
419
+ debouncedHandleValueChange(id, values);
393
420
  }}
394
421
  variant={'borderless'}
395
422
  {...FORM_STYLE}
@@ -1,6 +1,10 @@
1
1
  'use client';
2
2
 
3
- import { createSingletonAnalytics } from '@lobehub/analytics';
3
+ import {
4
+ GoogleAnalyticsProviderConfig,
5
+ PostHogProviderAnalyticsConfig,
6
+ createSingletonAnalytics,
7
+ } from '@lobehub/analytics';
4
8
  import { AnalyticsProvider } from '@lobehub/analytics/react';
5
9
  import { ReactNode, memo, useMemo } from 'react';
6
10
 
@@ -10,16 +14,14 @@ import { isDev } from '@/utils/env';
10
14
 
11
15
  type Props = {
12
16
  children: ReactNode;
13
- debugPosthog: boolean;
14
- posthogEnabled: boolean;
15
- posthogHost: string;
16
- posthogToken: string;
17
+ ga4Config: GoogleAnalyticsProviderConfig;
18
+ postHogConfig: PostHogProviderAnalyticsConfig;
17
19
  };
18
20
 
19
21
  let analyticsInstance: ReturnType<typeof createSingletonAnalytics> | null = null;
20
22
 
21
23
  export const LobeAnalyticsProvider = memo(
22
- ({ children, posthogHost, posthogToken, posthogEnabled, debugPosthog }: Props) => {
24
+ ({ children, ga4Config, postHogConfig }: Props) => {
23
25
  const analytics = useMemo(() => {
24
26
  if (analyticsInstance) {
25
27
  return analyticsInstance;
@@ -29,13 +31,8 @@ export const LobeAnalyticsProvider = memo(
29
31
  business: BUSINESS_LINE,
30
32
  debug: isDev,
31
33
  providers: {
32
- posthog: {
33
- debug: debugPosthog,
34
- enabled: posthogEnabled,
35
- host: posthogHost,
36
- key: posthogToken,
37
- person_profiles: 'always',
38
- },
34
+ ga4: ga4Config,
35
+ posthog: postHogConfig,
39
36
  },
40
37
  });
41
38
 
@@ -2,6 +2,7 @@ import { ReactNode, memo } from 'react';
2
2
 
3
3
  import { LobeAnalyticsProvider } from '@/components/Analytics/LobeAnalyticsProvider';
4
4
  import { analyticsEnv } from '@/config/analytics';
5
+ import { isDev } from '@/utils/env';
5
6
 
6
7
  type Props = {
7
8
  children: ReactNode;
@@ -10,10 +11,21 @@ type Props = {
10
11
  export const LobeAnalyticsProviderWrapper = memo<Props>(({ children }) => {
11
12
  return (
12
13
  <LobeAnalyticsProvider
13
- debugPosthog={analyticsEnv.DEBUG_POSTHOG_ANALYTICS}
14
- posthogEnabled={analyticsEnv.ENABLED_POSTHOG_ANALYTICS}
15
- posthogHost={analyticsEnv.POSTHOG_HOST}
16
- posthogToken={analyticsEnv.POSTHOG_KEY ?? ''}
14
+ ga4Config={{
15
+ debug: isDev,
16
+ enabled: analyticsEnv.ENABLE_GOOGLE_ANALYTICS,
17
+ gtagConfig: {
18
+ debug_mode: isDev,
19
+ },
20
+ measurementId: analyticsEnv.GOOGLE_ANALYTICS_MEASUREMENT_ID ?? '',
21
+ }}
22
+ postHogConfig={{
23
+ debug: analyticsEnv.DEBUG_POSTHOG_ANALYTICS,
24
+ enabled: analyticsEnv.ENABLED_POSTHOG_ANALYTICS,
25
+ host: analyticsEnv.POSTHOG_HOST,
26
+ key: analyticsEnv.POSTHOG_KEY ?? '',
27
+ person_profiles: 'always',
28
+ }}
17
29
  >
18
30
  {children}
19
31
  </LobeAnalyticsProvider>
@@ -1,6 +1,33 @@
1
1
  import { AIChatModelCard } from '@/types/aiModel';
2
2
 
3
3
  const ollamaChatModels: AIChatModelCard[] = [
4
+ {
5
+ abilities: {
6
+ functionCall: true,
7
+ reasoning: true,
8
+ },
9
+ contextWindowTokens: 32_768,
10
+ description:
11
+ 'GPT-OSS 20B 是 OpenAI 发布的开源大语言模型,采用 MXFP4 量化技术,适合在高端消费级GPU或Apple Silicon Mac上运行。该模型在对话生成、代码编写和推理任务方面表现出色,支持函数调用和工具使用。',
12
+ displayName: 'GPT-OSS 20B',
13
+ enabled: true,
14
+ id: 'gpt-oss',
15
+ releasedAt: '2025-08-05',
16
+ type: 'chat',
17
+ },
18
+ {
19
+ abilities: {
20
+ functionCall: true,
21
+ reasoning: true,
22
+ },
23
+ contextWindowTokens: 32_768,
24
+ description:
25
+ 'GPT-OSS 120B 是 OpenAI 发布的大型开源语言模型,采用 MXFP4 量化技术,为旗舰级模型。需要多GPU或高性能工作站环境运行,在复杂推理、代码生成和多语言处理方面具备卓越性能,支持高级函数调用和工具集成。',
26
+ displayName: 'GPT-OSS 120B',
27
+ id: 'gpt-oss:120b',
28
+ releasedAt: '2025-08-05',
29
+ type: 'chat',
30
+ },
4
31
  {
5
32
  abilities: {
6
33
  reasoning: true,