@lobehub/chat 1.103.0 → 1.103.2

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 (99) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/apps/desktop/build/icon-beta.ico +0 -0
  3. package/apps/desktop/build/icon-dev.ico +0 -0
  4. package/apps/desktop/build/icon-nightly.ico +0 -0
  5. package/apps/desktop/build/icon.ico +0 -0
  6. package/apps/desktop/electron.vite.config.ts +4 -2
  7. package/apps/desktop/package.json +1 -0
  8. package/apps/desktop/src/main/appBrowsers.ts +2 -2
  9. package/apps/desktop/src/main/const/env.ts +5 -4
  10. package/apps/desktop/src/main/const/store.ts +1 -0
  11. package/apps/desktop/src/main/const/theme.ts +11 -0
  12. package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +1 -1
  13. package/apps/desktop/src/main/controllers/NotificationCtr.ts +2 -4
  14. package/apps/desktop/src/main/controllers/SystemCtr.ts +4 -0
  15. package/apps/desktop/src/main/controllers/TrayMenuCtr.ts +5 -5
  16. package/apps/desktop/src/main/controllers/index.ts +1 -1
  17. package/apps/desktop/src/main/core/App.ts +9 -10
  18. package/apps/desktop/src/main/core/{Browser.ts → browser/Browser.ts} +129 -88
  19. package/apps/desktop/src/main/core/{BrowserManager.ts → browser/BrowserManager.ts} +13 -3
  20. package/apps/desktop/src/main/core/{StaticFileServerManager.ts → infrastructure/StaticFileServerManager.ts} +13 -7
  21. package/apps/desktop/src/main/core/{StoreManager.ts → infrastructure/StoreManager.ts} +1 -1
  22. package/apps/desktop/src/main/core/{UpdaterManager.ts → infrastructure/UpdaterManager.ts} +1 -1
  23. package/apps/desktop/src/main/core/{MenuManager.ts → ui/MenuManager.ts} +2 -2
  24. package/apps/desktop/src/main/core/{ShortcutManager.ts → ui/ShortcutManager.ts} +7 -1
  25. package/apps/desktop/src/main/core/{Tray.ts → ui/Tray.ts} +61 -59
  26. package/apps/desktop/src/main/core/{TrayManager.ts → ui/TrayManager.ts} +5 -5
  27. package/apps/desktop/src/main/shortcuts/config.ts +2 -2
  28. package/apps/desktop/src/main/types/store.ts +1 -0
  29. package/changelog/v1.json +21 -0
  30. package/docs/development/basic/add-new-image-model.mdx +162 -0
  31. package/docs/development/basic/add-new-image-model.zh-CN.mdx +162 -0
  32. package/docs/usage/features/desktop.mdx +1 -1
  33. package/docs/usage/features/mcp-market.mdx +1 -1
  34. package/docs/usage/providers/fal.mdx +1 -1
  35. package/docs/usage/providers/fal.zh-CN.mdx +1 -1
  36. package/locales/ar/models.json +3 -0
  37. package/locales/bg-BG/models.json +3 -0
  38. package/locales/de-DE/models.json +3 -0
  39. package/locales/en-US/models.json +3 -0
  40. package/locales/es-ES/models.json +3 -0
  41. package/locales/fa-IR/models.json +3 -0
  42. package/locales/fr-FR/models.json +3 -0
  43. package/locales/it-IT/models.json +3 -0
  44. package/locales/ja-JP/models.json +3 -0
  45. package/locales/ko-KR/models.json +3 -0
  46. package/locales/nl-NL/models.json +3 -0
  47. package/locales/pl-PL/models.json +3 -0
  48. package/locales/pt-BR/models.json +3 -0
  49. package/locales/ru-RU/models.json +3 -0
  50. package/locales/tr-TR/models.json +3 -0
  51. package/locales/vi-VN/models.json +3 -0
  52. package/locales/zh-CN/models.json +3 -0
  53. package/locales/zh-TW/models.json +3 -0
  54. package/package.json +66 -66
  55. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/Portal.tsx +3 -1
  56. package/src/app/[variants]/(main)/chat/(workspace)/features/AgentSettings/index.tsx +4 -2
  57. package/src/app/[variants]/(main)/image/@menu/components/SeedNumberInput/index.tsx +1 -1
  58. package/src/app/[variants]/(main)/image/features/GenerationFeed/BatchItem.tsx +39 -3
  59. package/src/app/[variants]/(main)/image/features/GenerationFeed/ReferenceImages.tsx +122 -0
  60. package/src/config/aiModels/fal.ts +31 -7
  61. package/src/config/aiModels/openai.ts +10 -1
  62. package/src/features/ElectronTitlebar/WinControl/index.tsx +85 -90
  63. package/src/features/ElectronTitlebar/hooks/useWatchThemeUpdate.ts +10 -5
  64. package/src/features/ImageTopicPanel/index.tsx +0 -1
  65. package/src/features/PluginDevModal/index.tsx +3 -1
  66. package/src/features/User/__tests__/UserAvatar.test.tsx +5 -4
  67. package/src/libs/model-runtime/fal/index.ts +1 -1
  68. package/src/libs/model-runtime/types/image.ts +1 -1
  69. package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.ts +1 -1
  70. package/src/libs/model-runtime/utils/response.ts +2 -0
  71. package/src/libs/model-runtime/utils/streams/google-ai.test.ts +46 -0
  72. package/src/libs/model-runtime/utils/streams/google-ai.ts +4 -4
  73. package/src/libs/model-runtime/utils/streams/vertex-ai.ts +6 -8
  74. package/src/libs/standard-parameters/{meta-schema.test.ts → index.test.ts} +1 -1
  75. package/src/libs/standard-parameters/index.ts +152 -1
  76. package/src/server/ld.test.ts +4 -3
  77. package/src/server/routers/async/image.ts +1 -1
  78. package/src/services/__tests__/chat.test.ts +3 -4
  79. package/src/store/chat/slices/message/selectors.test.ts +2 -3
  80. package/src/store/chat/slices/plugin/action.test.ts +2 -1
  81. package/src/store/image/slices/generationConfig/action.test.ts +2 -2
  82. package/src/store/image/slices/generationConfig/action.ts +1 -1
  83. package/src/store/image/slices/generationConfig/hooks.test.ts +2 -2
  84. package/src/store/image/slices/generationConfig/hooks.ts +1 -4
  85. package/src/store/image/slices/generationConfig/initialState.ts +2 -2
  86. package/src/store/image/slices/generationConfig/selectors.test.ts +2 -2
  87. package/src/store/image/slices/generationConfig/selectors.ts +1 -1
  88. package/src/store/user/slices/auth/selectors.test.ts +3 -2
  89. package/src/types/generation/index.ts +1 -0
  90. package/docs/development/basic/add-new-ai-image-model.mdx +0 -36
  91. package/docs/development/basic/add-new-ai-image-model.zh-CN.mdx +0 -0
  92. package/src/config/paramsSchemas/fal/flux-kontext-dev.ts +0 -8
  93. package/src/config/paramsSchemas/fal/flux-pro-kontext.ts +0 -11
  94. package/src/config/paramsSchemas/fal/flux-schnell.ts +0 -9
  95. package/src/config/paramsSchemas/fal/imagen4.ts +0 -10
  96. package/src/config/paramsSchemas/openai/gpt-image-1.ts +0 -10
  97. package/src/libs/standard-parameters/meta-schema.ts +0 -147
  98. /package/apps/desktop/src/main/core/{I18nManager.ts → infrastructure/I18nManager.ts} +0 -0
  99. /package/apps/desktop/src/main/core/{IoCContainer.ts → infrastructure/IoCContainer.ts} +0 -0
@@ -1,95 +1,90 @@
1
- import { createStyles } from 'antd-style';
2
- import { Minus, Square, XIcon } from 'lucide-react';
3
-
4
- import { electronSystemService } from '@/services/electron/system';
5
-
6
- import { TITLE_BAR_HEIGHT } from '../const';
7
-
8
- const useStyles = createStyles(({ css, cx, token }) => {
9
- const icon = css`
10
- display: flex;
11
- align-items: center;
12
- justify-content: center;
13
-
14
- width: ${TITLE_BAR_HEIGHT * 1.2}px;
15
- min-height: ${TITLE_BAR_HEIGHT}px;
16
-
17
- color: ${token.colorTextSecondary};
18
-
19
- transition: all ease-in-out 100ms;
20
-
21
- -webkit-app-region: no-drag;
22
-
23
- &:hover {
24
- color: ${token.colorText};
25
- background: ${token.colorFillTertiary};
26
- }
27
-
28
- &:active {
29
- color: ${token.colorText};
30
- background: ${token.colorFillSecondary};
31
- }
32
- `;
33
- return {
34
- close: cx(
35
- icon,
36
- css`
37
- padding-inline-end: 2px;
38
-
39
- &:hover {
40
- color: ${token.colorTextLightSolid};
41
-
42
- /* win11 的色值,亮暗色均不变 */
43
- background: #d33328;
44
- }
45
-
46
- &:active {
47
- color: ${token.colorTextLightSolid};
48
-
49
- /* win11 的色值 */
50
- background: #8b2b25;
51
- }
52
- `,
53
- ),
54
- container: css`
55
- cursor: pointer;
56
- display: flex;
57
- `,
58
- icon,
59
- };
60
- });
1
+ // const useStyles = createStyles(({ css, cx, token }) => {
2
+ // const icon = css`
3
+ // display: flex;
4
+ // align-items: center;
5
+ // justify-content: center;
6
+ //
7
+ // width: ${TITLE_BAR_HEIGHT * 1.2}px;
8
+ // min-height: ${TITLE_BAR_HEIGHT}px;
9
+ //
10
+ // color: ${token.colorTextSecondary};
11
+ //
12
+ // transition: all ease-in-out 100ms;
13
+ //
14
+ // -webkit-app-region: no-drag;
15
+ //
16
+ // &:hover {
17
+ // color: ${token.colorText};
18
+ // background: ${token.colorFillTertiary};
19
+ // }
20
+ //
21
+ // &:active {
22
+ // color: ${token.colorText};
23
+ // background: ${token.colorFillSecondary};
24
+ // }
25
+ // `;
26
+ // return {
27
+ // close: cx(
28
+ // icon,
29
+ // css`
30
+ // padding-inline-end: 2px;
31
+ //
32
+ // &:hover {
33
+ // color: ${token.colorTextLightSolid};
34
+ //
35
+ // /* win11 的色值,亮暗色均不变 */
36
+ // background: #d33328;
37
+ // }
38
+ //
39
+ // &:active {
40
+ // color: ${token.colorTextLightSolid};
41
+ //
42
+ // /* win11 的色值 */
43
+ // background: #8b2b25;
44
+ // }
45
+ // `,
46
+ // ),
47
+ // container: css`
48
+ // cursor: pointer;
49
+ // display: flex;
50
+ // `,
51
+ // icon,
52
+ // };
53
+ // });
61
54
 
62
55
  const WinControl = () => {
63
- const { styles } = useStyles();
64
-
65
- return (
66
- <div className={styles.container}>
67
- <div
68
- className={styles.icon}
69
- onClick={() => {
70
- electronSystemService.minimizeWindow();
71
- }}
72
- >
73
- <Minus absoluteStrokeWidth size={14} strokeWidth={1.2} />
74
- </div>
75
- <div
76
- className={styles.icon}
77
- onClick={() => {
78
- electronSystemService.maximizeWindow();
79
- }}
80
- >
81
- <Square absoluteStrokeWidth size={10} strokeWidth={1.2} />
82
- </div>
83
- <div
84
- className={styles.close}
85
- onClick={() => {
86
- electronSystemService.closeWindow();
87
- }}
88
- >
89
- <XIcon absoluteStrokeWidth size={14} strokeWidth={1.2} />
90
- </div>
91
- </div>
92
- );
56
+ return <div style={{ width: 132 }} />;
57
+
58
+ // const { styles } = useStyles();
59
+ //
60
+ // return (
61
+ // <div className={styles.container}>
62
+ // <div
63
+ // className={styles.icon}
64
+ // onClick={() => {
65
+ // electronSystemService.minimizeWindow();
66
+ // }}
67
+ // >
68
+ // <Minus absoluteStrokeWidth size={14} strokeWidth={1.2} />
69
+ // </div>
70
+ // <div
71
+ // className={styles.icon}
72
+ // onClick={() => {
73
+ // electronSystemService.maximizeWindow();
74
+ // }}
75
+ // >
76
+ // <Square absoluteStrokeWidth size={10} strokeWidth={1.2} />
77
+ // </div>
78
+ // <div
79
+ // className={styles.close}
80
+ // onClick={() => {
81
+ // electronSystemService.closeWindow();
82
+ // }}
83
+ // >
84
+ // <XIcon absoluteStrokeWidth size={14} strokeWidth={1.2} />
85
+ // </div>
86
+ // </div>
87
+ // );
93
88
  };
94
89
 
95
90
  export default WinControl;
@@ -7,10 +7,14 @@ import { useElectronStore } from '@/store/electron';
7
7
  import { useGlobalStore } from '@/store/global';
8
8
 
9
9
  export const useWatchThemeUpdate = () => {
10
- const [systemAppearance, updateElectronAppState] = useElectronStore((s) => [
11
- s.appState.systemAppearance,
12
- s.updateElectronAppState,
13
- ]);
10
+ const [isAppStateInit, systemAppearance, updateElectronAppState, isMac] = useElectronStore(
11
+ (s) => [
12
+ s.isAppStateInit,
13
+ s.appState.systemAppearance,
14
+ s.updateElectronAppState,
15
+ s.appState.isMac,
16
+ ],
17
+ );
14
18
  const switchThemeMode = useGlobalStore((s) => s.switchThemeMode);
15
19
 
16
20
  const theme = useTheme();
@@ -24,11 +28,12 @@ export const useWatchThemeUpdate = () => {
24
28
  });
25
29
 
26
30
  useEffect(() => {
31
+ if (!isAppStateInit || !isMac) return;
27
32
  document.documentElement.style.background = 'none';
28
33
 
29
34
  // https://x.com/alanblogsooo/status/1939208908993896684
30
35
  const isNotSameTheme = !systemAppearance ? true : theme.appearance !== systemAppearance;
31
36
 
32
37
  document.body.style.background = rgba(theme.colorBgLayout, isNotSameTheme ? 0.95 : 0.66);
33
- }, [theme, systemAppearance]);
38
+ }, [theme, systemAppearance, isAppStateInit, isMac]);
34
39
  };
@@ -67,7 +67,6 @@ const ImageTopicPanel = memo<PropsWithChildren>(({ children }) => {
67
67
  flex: 'none',
68
68
  height: '100%',
69
69
  minWidth: 80,
70
- padding: 16,
71
70
  }}
72
71
  >
73
72
  {children}
@@ -7,6 +7,8 @@ import { Trans, useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
8
 
9
9
  import { WIKI_PLUGIN_GUIDE } from '@/const/url';
10
+ import { isDesktop } from '@/const/version';
11
+ import { TITLE_BAR_HEIGHT } from '@/features/ElectronTitlebar';
10
12
  import { LobeToolCustomPlugin } from '@/types/tool/plugin';
11
13
 
12
14
  import MCPManifestForm from './MCPManifestForm';
@@ -112,7 +114,7 @@ const DevModal = memo<DevModalProps>(
112
114
  containerMaxWidth={'auto'}
113
115
  destroyOnHidden
114
116
  footer={footer}
115
- height={'100vh'}
117
+ height={isDesktop ? `calc(100vh - ${TITLE_BAR_HEIGHT}px)` : '100vh'}
116
118
  onClose={(e) => {
117
119
  e.stopPropagation();
118
120
  onOpenChange(false);
@@ -1,6 +1,7 @@
1
1
  import { act, render, screen } from '@testing-library/react';
2
2
  import { afterEach, describe, expect, it, vi } from 'vitest';
3
3
 
4
+ import { BRANDING_NAME } from '@/const/branding';
4
5
  import { DEFAULT_USER_AVATAR_URL } from '@/const/meta';
5
6
  import { useUserStore } from '@/store/user';
6
7
 
@@ -63,8 +64,8 @@ describe('UserAvatar', () => {
63
64
  });
64
65
 
65
66
  render(<UserAvatar />);
66
- expect(screen.getByAltText('LobeChat')).toBeInTheDocument();
67
- expect(screen.getByAltText('LobeChat')).toHaveAttribute('src', DEFAULT_USER_AVATAR_URL);
67
+ expect(screen.getByAltText(BRANDING_NAME)).toBeInTheDocument();
68
+ expect(screen.getByAltText(BRANDING_NAME)).toHaveAttribute('src', DEFAULT_USER_AVATAR_URL);
68
69
  });
69
70
  });
70
71
 
@@ -76,8 +77,8 @@ describe('UserAvatar', () => {
76
77
  });
77
78
 
78
79
  render(<UserAvatar />);
79
- expect(screen.getByAltText('LobeChat')).toBeInTheDocument();
80
- expect(screen.getByAltText('LobeChat')).toHaveAttribute('src', DEFAULT_USER_AVATAR_URL);
80
+ expect(screen.getByAltText(BRANDING_NAME)).toBeInTheDocument();
81
+ expect(screen.getByAltText(BRANDING_NAME)).toHaveAttribute('src', DEFAULT_USER_AVATAR_URL);
81
82
  });
82
83
  });
83
84
  });
@@ -3,7 +3,7 @@ import debug from 'debug';
3
3
  import { pick } from 'lodash-es';
4
4
  import { ClientOptions } from 'openai';
5
5
 
6
- import { RuntimeImageGenParamsValue } from '@/libs/standard-parameters/meta-schema';
6
+ import { RuntimeImageGenParamsValue } from '@/libs/standard-parameters/index';
7
7
 
8
8
  import { LobeRuntimeAI } from '../BaseAI';
9
9
  import { AgentRuntimeErrorType } from '../error';
@@ -1,4 +1,4 @@
1
- import { RuntimeImageGenParams } from '@/libs/standard-parameters/meta-schema';
1
+ import { RuntimeImageGenParams } from '@/libs/standard-parameters/index';
2
2
 
3
3
  export type CreateImagePayload = {
4
4
  model: string;
@@ -5,7 +5,7 @@ import OpenAI, { ClientOptions } from 'openai';
5
5
  import { Stream } from 'openai/streaming';
6
6
 
7
7
  import { LOBE_DEFAULT_MODEL_LIST } from '@/config/aiModels';
8
- import { RuntimeImageGenParamsValue } from '@/libs/standard-parameters/meta-schema';
8
+ import { RuntimeImageGenParamsValue } from '@/libs/standard-parameters/index';
9
9
  import type { ChatModelCard } from '@/types/llm';
10
10
  import { getModelPropertyWithFallback } from '@/utils/getFallbackModelProperty';
11
11
 
@@ -6,6 +6,8 @@ export const StreamingResponse = (
6
6
  headers: {
7
7
  'Cache-Control': 'no-cache',
8
8
  'Content-Type': 'text/event-stream',
9
+ // for Nginx: disable chunk buffering
10
+ 'X-Accel-Buffering': 'no',
9
11
  ...options?.headers,
10
12
  },
11
13
  });
@@ -186,6 +186,52 @@ describe('GoogleGenerativeAIStream', () => {
186
186
  ]);
187
187
  });
188
188
 
189
+ it('should handle token count with cached token count', async () => {
190
+ vi.spyOn(uuidModule, 'nanoid').mockReturnValueOnce('1');
191
+
192
+ const data = {
193
+ candidates: [{ content: { role: 'model' }, finishReason: 'STOP', index: 0 }],
194
+ usageMetadata: {
195
+ promptTokenCount: 15725,
196
+ candidatesTokenCount: 1053,
197
+ totalTokenCount: 16778,
198
+ cachedContentTokenCount: 14286,
199
+ promptTokensDetails: [{ modality: 'TEXT', tokenCount: 15725 }],
200
+ cacheTokensDetails: [{ modality: 'TEXT', tokenCount: 14286 }],
201
+ },
202
+ modelVersion: 'gemini-2.0-flash-exp',
203
+ };
204
+
205
+ const mockGoogleStream = new ReadableStream({
206
+ start(controller) {
207
+ controller.enqueue(data);
208
+
209
+ controller.close();
210
+ },
211
+ });
212
+
213
+ const protocolStream = GoogleGenerativeAIStream(mockGoogleStream);
214
+
215
+ const decoder = new TextDecoder();
216
+ const chunks = [];
217
+
218
+ // @ts-ignore
219
+ for await (const chunk of protocolStream) {
220
+ chunks.push(decoder.decode(chunk, { stream: true }));
221
+ }
222
+
223
+ expect(chunks).toEqual([
224
+ // stop
225
+ 'id: chat_1\n',
226
+ 'event: stop\n',
227
+ `data: "STOP"\n\n`,
228
+ // usage
229
+ 'id: chat_1\n',
230
+ 'event: usage\n',
231
+ `data: {"inputCachedTokens":14286,"inputTextTokens":15725,"outputTextTokens":1053,"totalInputTokens":15725,"totalOutputTokens":1053,"totalTokens":16778}\n\n`,
232
+ ]);
233
+ });
234
+
189
235
  it('should handle stop with content', async () => {
190
236
  vi.spyOn(uuidModule, 'nanoid').mockReturnValueOnce('1');
191
237
 
@@ -33,10 +33,10 @@ const transformGoogleGenerativeAIStream = (
33
33
  { data: candidate.finishReason, id: context?.id, type: 'stop' },
34
34
  {
35
35
  data: {
36
- // TODO: Google SDK 0.24.0 don't have promptTokensDetails types
37
- inputImageTokens: usage.promptTokensDetails?.find((i: any) => i.modality === 'IMAGE')
36
+ inputCachedTokens: usage.cachedContentTokenCount,
37
+ inputImageTokens: usage.promptTokensDetails?.find((i) => i.modality === 'IMAGE')
38
38
  ?.tokenCount,
39
- inputTextTokens: usage.promptTokensDetails?.find((i: any) => i.modality === 'TEXT')
39
+ inputTextTokens: usage.promptTokensDetails?.find((i) => i.modality === 'TEXT')
40
40
  ?.tokenCount,
41
41
  outputReasoningTokens: reasoningTokens,
42
42
  outputTextTokens,
@@ -79,7 +79,7 @@ const transformGoogleGenerativeAIStream = (
79
79
  // 首先检查是否为 reasoning 内容 (thought: true)
80
80
  if (Array.isArray(candidate.content?.parts) && candidate.content.parts.length > 0) {
81
81
  for (const part of candidate.content.parts) {
82
- if (part && part.text && (part as any).thought === true) {
82
+ if (part && part.text && part.thought === true) {
83
83
  return { data: part.text, id: context.id, type: 'reasoning' };
84
84
  }
85
85
  }
@@ -31,13 +31,11 @@ const transformVertexAIStream = (
31
31
  { data: candidate.finishReason, id: context?.id, type: 'stop' },
32
32
  {
33
33
  data: {
34
- // TODO: Google SDK 0.24.0 don't have promptTokensDetails types
35
- inputImageTokens: (usage as any).promptTokensDetails?.find(
36
- (i: any) => i.modality === 'IMAGE',
37
- )?.tokenCount,
38
- inputTextTokens: (usage as any).promptTokensDetails?.find(
39
- (i: any) => i.modality === 'TEXT',
40
- )?.tokenCount,
34
+ inputCachedTokens: usage.cachedContentTokenCount,
35
+ inputImageTokens: usage.promptTokensDetails?.find((i) => i.modality === 'IMAGE')
36
+ ?.tokenCount,
37
+ inputTextTokens: usage.promptTokensDetails?.find((i) => i.modality === 'TEXT')
38
+ ?.tokenCount,
41
39
  outputReasoningTokens,
42
40
  outputTextTokens,
43
41
  totalInputTokens: usage.promptTokenCount,
@@ -56,7 +54,7 @@ const transformVertexAIStream = (
56
54
  candidate.content.parts.length > 0
57
55
  ) {
58
56
  for (const part of candidate.content.parts) {
59
- if (part && part.text && (part as any).thought === true) {
57
+ if (part && part.text && part.thought === true) {
60
58
  return { data: part.text, id: context.id, type: 'reasoning' };
61
59
  }
62
60
  }
@@ -6,7 +6,7 @@ import {
6
6
  type RuntimeImageGenParams,
7
7
  extractDefaultValues,
8
8
  validateModelParamsSchema,
9
- } from './meta-schema';
9
+ } from './index';
10
10
 
11
11
  describe('meta-schema', () => {
12
12
  describe('ModelParamsMetaSchema', () => {
@@ -1 +1,152 @@
1
- export * from './meta-schema';
1
+ import type { Simplify } from 'type-fest';
2
+ import { z } from 'zod';
3
+
4
+ export const MAX_SEED = 2 ** 31 - 1;
5
+
6
+ // 定义顶层的元规范 - 平铺结构
7
+ export const ModelParamsMetaSchema = z.object({
8
+ aspectRatio: z
9
+ .object({
10
+ default: z.string(),
11
+ description: z.string().optional(),
12
+ enum: z.array(z.string()),
13
+ type: z.literal('string').optional(),
14
+ })
15
+ .optional(),
16
+
17
+ cfg: z
18
+ .object({
19
+ default: z.number(),
20
+ description: z.string().optional(),
21
+ max: z.number(),
22
+ min: z.number(),
23
+ step: z.number(),
24
+ type: z.literal('number').optional(),
25
+ })
26
+ .optional(),
27
+
28
+ height: z
29
+ .object({
30
+ default: z.number(),
31
+ description: z.string().optional(),
32
+ max: z.number(),
33
+ min: z.number(),
34
+ step: z.number().optional().default(1),
35
+ type: z.literal('number').optional(),
36
+ })
37
+ .optional(),
38
+
39
+ imageUrl: z
40
+ .object({
41
+ default: z.string().nullable().optional(),
42
+ description: z.string().optional(),
43
+ type: z.tuple([z.literal('string'), z.literal('null')]).optional(),
44
+ })
45
+ .optional(),
46
+
47
+ imageUrls: z
48
+ .object({
49
+ default: z.array(z.string()),
50
+ description: z.string().optional(),
51
+ type: z.literal('array').optional(),
52
+ })
53
+ .optional(),
54
+
55
+ /**
56
+ * Prompt 是唯一一个每个模型都有的参数
57
+ */
58
+ prompt: z.object({
59
+ default: z.string().optional().default(''),
60
+ description: z.string().optional(),
61
+ type: z.literal('string').optional(),
62
+ }),
63
+
64
+ seed: z
65
+ .object({
66
+ default: z.number().nullable().default(null),
67
+ description: z.string().optional(),
68
+ max: z.number().optional().default(MAX_SEED),
69
+ min: z.number().optional().default(0),
70
+ type: z.tuple([z.literal('number'), z.literal('null')]).optional(),
71
+ })
72
+ .optional(),
73
+
74
+ size: z
75
+ .object({
76
+ default: z.string(),
77
+ description: z.string().optional(),
78
+ enum: z.array(z.string()),
79
+ type: z.literal('string').optional(),
80
+ })
81
+ .optional(),
82
+
83
+ steps: z
84
+ .object({
85
+ default: z.number(),
86
+ description: z.string().optional(),
87
+ max: z.number(),
88
+ min: z.number(),
89
+ step: z.number().optional().default(1),
90
+ type: z.literal('number').optional(),
91
+ })
92
+ .optional(),
93
+
94
+ width: z
95
+ .object({
96
+ default: z.number(),
97
+ description: z.string().optional(),
98
+ max: z.number(),
99
+ min: z.number(),
100
+ step: z.number().optional().default(1),
101
+ type: z.literal('number').optional(),
102
+ })
103
+ .optional(),
104
+ });
105
+ // 导出推断出的类型,供定义对象使用
106
+ export type ModelParamsSchema = z.input<typeof ModelParamsMetaSchema>;
107
+ export type ModelParamsOutputSchema = z.output<typeof ModelParamsMetaSchema>;
108
+ export type ModelParamsKeys = Simplify<keyof ModelParamsOutputSchema>;
109
+
110
+ type TypeMapping<T> = T extends 'string'
111
+ ? string
112
+ : T extends 'number'
113
+ ? number
114
+ : T extends ['number', 'null']
115
+ ? number | null
116
+ : T extends ['string', 'null']
117
+ ? string | null
118
+ : T extends 'string'
119
+ ? string
120
+ : T extends 'boolean'
121
+ ? boolean
122
+ : never;
123
+ type TypeType<K extends ModelParamsKeys> = NonNullable<ModelParamsOutputSchema[K]>['type'];
124
+ type DefaultType<K extends ModelParamsKeys> = NonNullable<ModelParamsOutputSchema[K]>['default'];
125
+ type _StandardImageGenerationParameters<P extends ModelParamsKeys = ModelParamsKeys> = {
126
+ [key in P]: NonNullable<TypeType<key>> extends 'array'
127
+ ? DefaultType<key>
128
+ : TypeMapping<TypeType<key>>;
129
+ };
130
+
131
+ export type RuntimeImageGenParams = Pick<_StandardImageGenerationParameters, 'prompt'> &
132
+ Partial<Omit<_StandardImageGenerationParameters, 'prompt'>>;
133
+ export type RuntimeImageGenParamsKeys = keyof RuntimeImageGenParams;
134
+ export type RuntimeImageGenParamsValue = RuntimeImageGenParams[RuntimeImageGenParamsKeys];
135
+
136
+ // 验证函数
137
+ export function validateModelParamsSchema(paramsSchema: unknown): ModelParamsOutputSchema {
138
+ return ModelParamsMetaSchema.parse(paramsSchema);
139
+ }
140
+
141
+ /**
142
+ * 从参数定义对象提取默认值
143
+ */
144
+ export function extractDefaultValues(paramsSchema: ModelParamsSchema) {
145
+ // 部分默认值从 ModelParamsMetaSchema 中获取
146
+ const schemaWithDefault = ModelParamsMetaSchema.parse(paramsSchema);
147
+ return Object.fromEntries(
148
+ Object.entries(schemaWithDefault).map(([key, value]) => {
149
+ return [key, value.default];
150
+ }),
151
+ ) as RuntimeImageGenParams;
152
+ }
@@ -1,6 +1,7 @@
1
1
  // @vitest-environment node
2
2
  import { describe, expect, it } from 'vitest';
3
3
 
4
+ import { BRANDING_NAME } from '@/const/branding';
4
5
  import { DEFAULT_LANG } from '@/const/locale';
5
6
 
6
7
  import { AUTHOR_LIST, Ld } from './ld';
@@ -57,7 +58,7 @@ describe('Ld', () => {
57
58
  });
58
59
 
59
60
  expect(webpage['@type']).toBe('WebPage');
60
- expect(webpage.name).toBe('Test Page · LobeChat');
61
+ expect(webpage.name).toBe(`Test Page · ${BRANDING_NAME}`);
61
62
  expect(webpage.description).toBe('Test Description');
62
63
  });
63
64
  });
@@ -79,7 +80,7 @@ describe('Ld', () => {
79
80
  const website = ld.genWebSite();
80
81
 
81
82
  expect(website['@type']).toBe('WebSite');
82
- expect(website.name).toBe('LobeChat');
83
+ expect(website.name).toBe(BRANDING_NAME);
83
84
  });
84
85
  });
85
86
 
@@ -95,7 +96,7 @@ describe('Ld', () => {
95
96
  });
96
97
 
97
98
  expect(article['@type']).toBe('Article');
98
- expect(article.headline).toBe('Test Article · LobeChat');
99
+ expect(article.headline).toBe(`Test Article · ${BRANDING_NAME}`);
99
100
  expect(article.author['@type']).toBe('Person');
100
101
  });
101
102
  });
@@ -5,7 +5,7 @@ import { ASYNC_TASK_TIMEOUT, AsyncTaskModel } from '@/database/models/asyncTask'
5
5
  import { FileModel } from '@/database/models/file';
6
6
  import { GenerationModel } from '@/database/models/generation';
7
7
  import { AgentRuntimeErrorType } from '@/libs/model-runtime/error';
8
- import { RuntimeImageGenParams } from '@/libs/standard-parameters/meta-schema';
8
+ import { RuntimeImageGenParams } from '@/libs/standard-parameters/index';
9
9
  import { asyncAuthedProcedure, asyncRouter as router } from '@/libs/trpc/async';
10
10
  import { initAgentRuntimeWithUserPayload } from '@/server/modules/AgentRuntime';
11
11
  import { GenerationService } from '@/server/services/generation';
@@ -4,6 +4,7 @@ import { merge } from 'lodash-es';
4
4
  import OpenAI from 'openai';
5
5
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
6
6
 
7
+ import { DEFAULT_USER_AVATAR } from '@/const/meta';
7
8
  import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
8
9
  import {
9
10
  LobeAnthropicAI,
@@ -31,8 +32,6 @@ import { aiModelSelectors } from '@/store/aiInfra';
31
32
  import { useToolStore } from '@/store/tool';
32
33
  import { toolSelectors } from '@/store/tool/selectors';
33
34
  import { UserStore } from '@/store/user';
34
- import { useUserStore } from '@/store/user';
35
- import { modelConfigSelectors } from '@/store/user/selectors';
36
35
  import { UserSettingsState, initialSettingsState } from '@/store/user/slices/settings/initialState';
37
36
  import { DalleManifest } from '@/tools/dalle';
38
37
  import { WebBrowsingManifest } from '@/tools/web-browsing';
@@ -671,7 +670,7 @@ describe('ChatService', () => {
671
670
  updatedAt: 1702723964330,
672
671
  extra: {},
673
672
  meta: {
674
- avatar: '😀',
673
+ avatar: DEFAULT_USER_AVATAR,
675
674
  },
676
675
  },
677
676
  ] as ChatMessage[];
@@ -913,7 +912,7 @@ describe('ChatService', () => {
913
912
  updatedAt: 1702723964330,
914
913
  extra: {},
915
914
  meta: {
916
- avatar: '😀',
915
+ avatar: DEFAULT_USER_AVATAR,
917
916
  },
918
917
  },
919
918
  ] as ChatMessage[];