@lobehub/chat 1.114.6 → 1.116.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 (152) hide show
  1. package/.cursor/rules/add-provider-doc.mdc +183 -0
  2. package/.cursor/rules/project-introduce.mdc +1 -15
  3. package/.cursor/rules/project-structure.mdc +227 -0
  4. package/.cursor/rules/testing-guide/db-model-test.mdc +5 -3
  5. package/.cursor/rules/testing-guide/testing-guide.mdc +153 -168
  6. package/.env.example +8 -0
  7. package/.github/workflows/claude.yml +1 -1
  8. package/.github/workflows/release.yml +3 -3
  9. package/.github/workflows/test.yml +10 -5
  10. package/CHANGELOG.md +50 -0
  11. package/CLAUDE.md +17 -33
  12. package/Dockerfile +5 -1
  13. package/Dockerfile.database +5 -1
  14. package/Dockerfile.pglite +5 -1
  15. package/changelog/v1.json +14 -0
  16. package/docs/development/basic/feature-development.mdx +1 -1
  17. package/docs/development/basic/feature-development.zh-CN.mdx +1 -1
  18. package/docs/development/basic/setup-development.mdx +10 -13
  19. package/docs/development/basic/setup-development.zh-CN.mdx +9 -12
  20. package/docs/self-hosting/environment-variables/model-provider.mdx +27 -2
  21. package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +27 -2
  22. package/docs/usage/providers/bfl.mdx +68 -0
  23. package/docs/usage/providers/bfl.zh-CN.mdx +67 -0
  24. package/locales/ar/components.json +11 -0
  25. package/locales/ar/error.json +11 -0
  26. package/locales/ar/models.json +64 -4
  27. package/locales/ar/providers.json +3 -0
  28. package/locales/bg-BG/components.json +11 -0
  29. package/locales/bg-BG/error.json +11 -0
  30. package/locales/bg-BG/models.json +64 -4
  31. package/locales/bg-BG/providers.json +3 -0
  32. package/locales/de-DE/components.json +11 -0
  33. package/locales/de-DE/error.json +11 -12
  34. package/locales/de-DE/models.json +64 -4
  35. package/locales/de-DE/providers.json +3 -0
  36. package/locales/en-US/components.json +6 -0
  37. package/locales/en-US/error.json +11 -12
  38. package/locales/en-US/models.json +64 -4
  39. package/locales/en-US/providers.json +3 -0
  40. package/locales/es-ES/components.json +11 -0
  41. package/locales/es-ES/error.json +11 -0
  42. package/locales/es-ES/models.json +64 -6
  43. package/locales/es-ES/providers.json +3 -0
  44. package/locales/fa-IR/components.json +11 -0
  45. package/locales/fa-IR/error.json +11 -0
  46. package/locales/fa-IR/models.json +64 -4
  47. package/locales/fa-IR/providers.json +3 -0
  48. package/locales/fr-FR/components.json +11 -0
  49. package/locales/fr-FR/error.json +11 -12
  50. package/locales/fr-FR/models.json +64 -4
  51. package/locales/fr-FR/providers.json +3 -0
  52. package/locales/it-IT/components.json +11 -0
  53. package/locales/it-IT/error.json +11 -0
  54. package/locales/it-IT/models.json +64 -4
  55. package/locales/it-IT/providers.json +3 -0
  56. package/locales/ja-JP/components.json +11 -0
  57. package/locales/ja-JP/error.json +11 -12
  58. package/locales/ja-JP/models.json +64 -4
  59. package/locales/ja-JP/providers.json +3 -0
  60. package/locales/ko-KR/components.json +11 -0
  61. package/locales/ko-KR/error.json +11 -12
  62. package/locales/ko-KR/models.json +64 -6
  63. package/locales/ko-KR/providers.json +3 -0
  64. package/locales/nl-NL/components.json +11 -0
  65. package/locales/nl-NL/error.json +11 -0
  66. package/locales/nl-NL/models.json +62 -4
  67. package/locales/nl-NL/providers.json +3 -0
  68. package/locales/pl-PL/components.json +11 -0
  69. package/locales/pl-PL/error.json +11 -0
  70. package/locales/pl-PL/models.json +64 -4
  71. package/locales/pl-PL/providers.json +3 -0
  72. package/locales/pt-BR/components.json +11 -0
  73. package/locales/pt-BR/error.json +11 -0
  74. package/locales/pt-BR/models.json +64 -4
  75. package/locales/pt-BR/providers.json +3 -0
  76. package/locales/ru-RU/components.json +11 -0
  77. package/locales/ru-RU/error.json +11 -0
  78. package/locales/ru-RU/models.json +64 -4
  79. package/locales/ru-RU/providers.json +3 -0
  80. package/locales/tr-TR/components.json +11 -0
  81. package/locales/tr-TR/error.json +11 -0
  82. package/locales/tr-TR/models.json +64 -4
  83. package/locales/tr-TR/providers.json +3 -0
  84. package/locales/vi-VN/components.json +11 -0
  85. package/locales/vi-VN/error.json +11 -0
  86. package/locales/vi-VN/models.json +64 -4
  87. package/locales/vi-VN/providers.json +3 -0
  88. package/locales/zh-CN/components.json +6 -0
  89. package/locales/zh-CN/error.json +11 -0
  90. package/locales/zh-CN/models.json +64 -4
  91. package/locales/zh-CN/providers.json +3 -0
  92. package/locales/zh-TW/components.json +11 -0
  93. package/locales/zh-TW/error.json +11 -12
  94. package/locales/zh-TW/models.json +64 -6
  95. package/locales/zh-TW/providers.json +3 -0
  96. package/package.json +4 -4
  97. package/packages/const/src/image.ts +28 -0
  98. package/packages/const/src/index.ts +1 -0
  99. package/packages/database/package.json +4 -2
  100. package/packages/database/src/repositories/aiInfra/index.ts +1 -1
  101. package/packages/database/tests/setup-db.ts +3 -0
  102. package/packages/database/vitest.config.mts +33 -0
  103. package/packages/model-runtime/src/google/index.ts +3 -0
  104. package/packages/model-runtime/src/qwen/createImage.test.ts +0 -19
  105. package/packages/model-runtime/src/qwen/createImage.ts +1 -27
  106. package/packages/model-runtime/src/utils/modelParse.ts +1 -1
  107. package/packages/model-runtime/src/utils/streams/google-ai.ts +26 -14
  108. package/packages/types/src/aiModel.ts +2 -1
  109. package/packages/utils/src/client/imageDimensions.test.ts +95 -0
  110. package/packages/utils/src/client/imageDimensions.ts +54 -0
  111. package/packages/utils/src/number.test.ts +3 -1
  112. package/packages/utils/src/number.ts +1 -2
  113. package/src/app/[variants]/(main)/image/@menu/components/SeedNumberInput/index.tsx +1 -1
  114. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/DimensionControlGroup.tsx +0 -1
  115. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUpload.tsx +16 -6
  116. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUrl.tsx +14 -2
  117. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUrlsUpload.tsx +27 -2
  118. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/MultiImagesUpload/index.tsx +23 -5
  119. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/hooks/useAutoDimensions.ts +56 -0
  120. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/index.tsx +82 -5
  121. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/__tests__/dimensionConstraints.test.ts +235 -0
  122. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/__tests__/imageValidation.test.ts +401 -0
  123. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/dimensionConstraints.ts +54 -0
  124. package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicItem.tsx +3 -1
  125. package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicList.tsx +15 -2
  126. package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/utils.ts +5 -4
  127. package/src/config/aiModels/google.ts +22 -1
  128. package/src/config/aiModels/qwen.ts +2 -2
  129. package/src/config/aiModels/vertexai.ts +22 -0
  130. package/src/libs/standard-parameters/index.ts +1 -1
  131. package/src/server/services/generation/index.ts +1 -1
  132. package/src/store/chat/slices/builtinTool/actions/dalle.test.ts +20 -13
  133. package/src/store/file/slices/upload/action.ts +18 -7
  134. package/src/store/image/slices/generationConfig/hooks.ts +1 -1
  135. package/tsconfig.json +1 -10
  136. package/.cursor/rules/debug.mdc +0 -193
  137. package/packages/const/src/imageGeneration.ts +0 -16
  138. package/src/app/(backend)/trpc/desktop/[trpc]/route.ts +0 -26
  139. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/AspectRatioSelect.tsx +0 -24
  140. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/SizeSliderInput.tsx +0 -15
  141. package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicItemContainer.tsx +0 -91
  142. package/src/app/desktop/devtools/page.tsx +0 -89
  143. package/src/app/desktop/layout.tsx +0 -31
  144. /package/apps/desktop/{vitest.config.ts → vitest.config.mts} +0 -0
  145. /package/packages/database/{vitest.config.ts → vitest.config.server.mts} +0 -0
  146. /package/packages/electron-server-ipc/{vitest.config.ts → vitest.config.mts} +0 -0
  147. /package/packages/file-loaders/{vitest.config.ts → vitest.config.mts} +0 -0
  148. /package/packages/model-runtime/{vitest.config.ts → vitest.config.mts} +0 -0
  149. /package/packages/prompts/{vitest.config.ts → vitest.config.mts} +0 -0
  150. /package/packages/utils/{vitest.config.ts → vitest.config.mts} +0 -0
  151. /package/packages/web-crawler/{vitest.config.ts → vitest.config.mts} +0 -0
  152. /package/{vitest.config.ts → vitest.config.mts} +0 -0
@@ -23,10 +23,11 @@ const formatTime = (date: Date, locale: string) => {
23
23
 
24
24
  interface TopicItemProps {
25
25
  showMoreInfo?: boolean;
26
+ style?: React.CSSProperties;
26
27
  topic: ImageGenerationTopic;
27
28
  }
28
29
 
29
- const TopicItem = memo<TopicItemProps>(({ topic, showMoreInfo }) => {
30
+ const TopicItem = memo<TopicItemProps>(({ topic, showMoreInfo, style }) => {
30
31
  const theme = useTheme();
31
32
  const { t } = useTranslation('image');
32
33
  const { modal } = App.useApp();
@@ -111,6 +112,7 @@ const TopicItem = memo<TopicItemProps>(({ topic, showMoreInfo }) => {
111
112
  onClick={handleClick}
112
113
  style={{
113
114
  cursor: 'pointer',
115
+ ...style,
114
116
  }}
115
117
  width={'100%'}
116
118
  >
@@ -47,8 +47,21 @@ const TopicsList = memo(() => {
47
47
  showMoreInfo={showMoreInfo}
48
48
  />
49
49
  <Flexbox align="center" gap={12} ref={parent} width={'100%'}>
50
- {generationTopics.map((topic) => (
51
- <TopicItem key={topic.id} showMoreInfo={showMoreInfo} topic={topic} />
50
+ {generationTopics.map((topic, index) => (
51
+ <TopicItem
52
+ key={topic.id}
53
+ showMoreInfo={showMoreInfo}
54
+ style={{
55
+ padding:
56
+ // fix the avatar border is clipped by overflow hidden
57
+ generationTopics.length === 1
58
+ ? '4px 0'
59
+ : index === generationTopics.length - 1
60
+ ? '0 0 4px'
61
+ : '0',
62
+ }}
63
+ topic={topic}
64
+ />
52
65
  ))}
53
66
  </Flexbox>
54
67
  </Flexbox>
@@ -111,13 +111,14 @@ export const getThumbnailMaxWidth = (
111
111
  ): number => {
112
112
  const dimensions = getImageDimensions(generation, generationBatch);
113
113
 
114
- // Return default width if dimensions are not available
115
- if (!dimensions.width || !dimensions.height) {
114
+ // Return default width if no dimension information is available
115
+ if (!dimensions.aspectRatio) {
116
116
  return DEFAULT_MAX_ITEM_WIDTH;
117
117
  }
118
118
 
119
- const { width: originalWidth, height: originalHeight } = dimensions;
120
- const aspectRatio = originalWidth / originalHeight;
119
+ // Parse aspect ratio string (format: "16 / 9")
120
+ const [widthStr, heightStr] = dimensions.aspectRatio.split(' / ');
121
+ const aspectRatio = Number(widthStr) / Number(heightStr);
121
122
 
122
123
  // Apply screen height constraint (half of screen height)
123
124
  // Note: window.innerHeight is safe to use here as this function is client-side only
@@ -189,6 +189,28 @@ const googleChatModels: AIChatModelCard[] = [
189
189
  },
190
190
  type: 'chat',
191
191
  },
192
+ {
193
+ abilities: {
194
+ imageOutput: true,
195
+ vision: true,
196
+ },
197
+ contextWindowTokens: 32_768 + 32_768,
198
+ description:
199
+ 'Gemini 2.5 Flash Image Preview 是 Google 最新、最快、最高效的原生多模态模型,它允许您通过对话生成和编辑图像。',
200
+ displayName: 'Gemini 2.5 Flash Image Preview',
201
+ enabled: true,
202
+ id: 'gemini-2.5-flash-image-preview',
203
+ maxOutput: 32_768,
204
+ pricing: {
205
+ units: [
206
+ { name: 'textInput', rate: 0.3, strategy: 'fixed', unit: 'millionTokens' },
207
+ { name: 'textOutput', rate: 2.5, strategy: 'fixed', unit: 'millionTokens' },
208
+ { name: 'imageOutput', rate: 3, strategy: 'fixed', unit: 'millionTokens' },
209
+ ],
210
+ },
211
+ releasedAt: '2025-08-27',
212
+ type: 'chat',
213
+ },
192
214
  {
193
215
  abilities: {
194
216
  functionCall: true,
@@ -304,7 +326,6 @@ const googleChatModels: AIChatModelCard[] = [
304
326
  contextWindowTokens: 32_768 + 8192,
305
327
  description: 'Gemini 2.0 Flash 预览模型,支持图像生成',
306
328
  displayName: 'Gemini 2.0 Flash Preview Image Generation',
307
- enabled: true,
308
329
  id: 'gemini-2.0-flash-preview-image-generation',
309
330
  maxOutput: 8192,
310
331
  pricing: {
@@ -1357,8 +1357,8 @@ const qwenImageModels: AIImageModelCard[] = [
1357
1357
  },
1358
1358
  seed: { default: null },
1359
1359
  size: {
1360
- default: '1328*1328',
1361
- enum: ['1664*928', '1472*1140', '1328*1328', '1140*1472', '928*1664'],
1360
+ default: '1328x1328',
1361
+ enum: ['1664x928', '1472x1140', '1328x1328', '1140x1472', '928x1664'],
1362
1362
  },
1363
1363
  },
1364
1364
  pricing: {
@@ -121,6 +121,28 @@ const vertexaiChatModels: AIChatModelCard[] = [
121
121
  releasedAt: '2025-04-17',
122
122
  type: 'chat',
123
123
  },
124
+ {
125
+ abilities: {
126
+ imageOutput: true,
127
+ vision: true,
128
+ },
129
+ contextWindowTokens: 32_768 + 32_768,
130
+ description:
131
+ 'Gemini 2.5 Flash Image Preview 是 Google 最新、最快、最高效的原生多模态模型,它允许您通过对话生成和编辑图像。',
132
+ displayName: 'Gemini 2.5 Flash Image Preview',
133
+ enabled: true,
134
+ id: 'gemini-2.5-flash-image-preview',
135
+ maxOutput: 32_768,
136
+ pricing: {
137
+ units: [
138
+ { name: 'textInput', rate: 0.3, strategy: 'fixed', unit: 'millionTokens' },
139
+ { name: 'textOutput', rate: 2.5, strategy: 'fixed', unit: 'millionTokens' },
140
+ { name: 'imageOutput', rate: 3, strategy: 'fixed', unit: 'millionTokens' },
141
+ ],
142
+ },
143
+ releasedAt: '2025-08-27',
144
+ type: 'chat',
145
+ },
124
146
  {
125
147
  abilities: {
126
148
  functionCall: true,
@@ -1,7 +1,7 @@
1
1
  import type { Simplify } from 'type-fest';
2
2
  import { z } from 'zod';
3
3
 
4
- export const MAX_SEED = 2 ** 31 - 1;
4
+ import { MAX_SEED } from '@/const/image';
5
5
 
6
6
  // 定义顶层的元规范 - 平铺结构
7
7
  export const ModelParamsMetaSchema = z.object({
@@ -4,7 +4,7 @@ import mime from 'mime';
4
4
  import { nanoid } from 'nanoid';
5
5
  import sharp from 'sharp';
6
6
 
7
- import { IMAGE_GENERATION_CONFIG } from '@/const/imageGeneration';
7
+ import { IMAGE_GENERATION_CONFIG } from '@/const/image';
8
8
  import { LobeChatDatabase } from '@/database/type';
9
9
  import { parseDataUri } from '@/libs/model-runtime/utils/uriParser';
10
10
  import { FileService } from '@/server/services/file';
@@ -7,6 +7,7 @@ import { messageService } from '@/services/message';
7
7
  import { imageGenerationService } from '@/services/textToImage';
8
8
  import { uploadService } from '@/services/upload';
9
9
  import { chatSelectors } from '@/store/chat/selectors';
10
+ import { useFileStore } from '@/store/file';
10
11
  import { ChatMessage } from '@/types/message';
11
12
  import { DallEImageItem } from '@/types/tool/dalle';
12
13
 
@@ -41,24 +42,28 @@ describe('chatToolSlice - dalle', () => {
41
42
  vi.spyOn(uploadService, 'getImageFileByUrlWithCORS').mockResolvedValue(
42
43
  new File(['1'], 'file.png', { type: 'image/png' }),
43
44
  );
44
- // @ts-ignore
45
- vi.spyOn(uploadService, 'uploadToClientS3').mockResolvedValue({} as any);
46
- vi.spyOn(ClientService.prototype, 'createFile').mockResolvedValue({
47
- id: mockId,
48
- url: '',
49
- });
45
+
46
+ // Mock the new uploadWithProgress method from useFileStore
47
+ vi.spyOn(useFileStore, 'getState').mockReturnValue({
48
+ uploadWithProgress: vi.fn().mockResolvedValue({
49
+ id: mockId,
50
+ url: '',
51
+ dimensions: { width: 512, height: 512 },
52
+ filename: 'file.png',
53
+ }),
54
+ } as any);
55
+
56
+ // Mock store methods that are called in the implementation
50
57
  vi.spyOn(result.current, 'toggleDallEImageLoading');
51
- vi.spyOn(ClientService.prototype, 'checkFileHash').mockImplementation(
52
- async () => ({ isExist: false }) as any,
53
- );
58
+ vi.spyOn(result.current, 'updatePluginState').mockResolvedValue(undefined);
59
+ vi.spyOn(result.current, 'internal_updateMessageContent').mockResolvedValue(undefined);
54
60
 
55
61
  await act(async () => {
56
62
  await result.current.generateImageFromPrompts(prompts, messageId);
57
63
  });
58
64
  // For each prompt, loading is toggled on and then off
59
65
  expect(imageGenerationService.generateImage).toHaveBeenCalledTimes(prompts.length);
60
- // @ts-ignore
61
- expect(uploadService.uploadToClientS3).toHaveBeenCalledTimes(prompts.length);
66
+ expect(useFileStore.getState().uploadWithProgress).toHaveBeenCalledTimes(prompts.length);
62
67
  expect(result.current.toggleDallEImageLoading).toHaveBeenCalledTimes(prompts.length * 2);
63
68
  });
64
69
  });
@@ -74,7 +79,7 @@ describe('chatToolSlice - dalle', () => {
74
79
  draft[0].previewUrl = 'new-url';
75
80
  draft[0].imageId = 'new-id';
76
81
  };
77
- vi.spyOn(result.current, 'internal_updateMessageContent');
82
+ vi.spyOn(result.current, 'internal_updateMessageContent').mockResolvedValue(undefined);
78
83
 
79
84
  // 模拟 getMessageById 返回消息内容
80
85
  vi.spyOn(chatSelectors, 'getMessageById').mockImplementationOnce(
@@ -105,7 +110,9 @@ describe('chatToolSlice - dalle', () => {
105
110
  const data = [{ prompt: 'prompt 1' }, { prompt: 'prompt 2' }] as DallEImageItem[];
106
111
 
107
112
  // Mock generateImageFromPrompts
108
- const generateImageFromPromptsMock = vi.spyOn(result.current, 'generateImageFromPrompts');
113
+ const generateImageFromPromptsMock = vi
114
+ .spyOn(result.current, 'generateImageFromPrompts')
115
+ .mockResolvedValue(undefined);
109
116
 
110
117
  await act(async () => {
111
118
  await result.current.text2image(id, data);
@@ -7,6 +7,7 @@ import { LOBE_CHAT_CLOUD } from '@/const/branding';
7
7
  import { fileService } from '@/services/file';
8
8
  import { uploadService } from '@/services/upload';
9
9
  import { FileMetadata, UploadFileItem } from '@/types/files';
10
+ import { getImageDimensions } from '@/utils/client/imageDimensions';
10
11
 
11
12
  import { FileStore } from '../../store';
12
13
 
@@ -36,6 +37,10 @@ interface UploadWithProgressParams {
36
37
  }
37
38
 
38
39
  interface UploadWithProgressResult {
40
+ dimensions?: {
41
+ height: number;
42
+ width: number;
43
+ };
39
44
  filename?: string;
40
45
  id: string;
41
46
  url: string;
@@ -61,6 +66,9 @@ export const createFileUploadSlice: StateCreator<
61
66
  FileUploadAction
62
67
  > = () => ({
63
68
  uploadBase64FileWithProgress: async (base64) => {
69
+ // Extract image dimensions from base64 data
70
+ const dimensions = await getImageDimensions(base64);
71
+
64
72
  const { metadata, fileType, size, hash } = await uploadService.uploadBase64ToS3(base64);
65
73
 
66
74
  const res = await fileService.createFile({
@@ -71,18 +79,21 @@ export const createFileUploadSlice: StateCreator<
71
79
  size: size,
72
80
  url: metadata.path,
73
81
  });
74
- return { ...res, filename: metadata.filename };
82
+ return { ...res, dimensions, filename: metadata.filename };
75
83
  },
76
84
  uploadWithProgress: async ({ file, onStatusUpdate, knowledgeBaseId, skipCheckFileType }) => {
77
85
  const fileArrayBuffer = await file.arrayBuffer();
78
86
 
79
- // 1. check file hash
87
+ // 1. extract image dimensions if applicable
88
+ const dimensions = await getImageDimensions(file);
89
+
90
+ // 2. check file hash
80
91
  const hash = sha256(fileArrayBuffer);
81
92
 
82
93
  const checkStatus = await fileService.checkFileHash(hash);
83
94
  let metadata: FileMetadata;
84
95
 
85
- // 2. if file exist, just skip upload
96
+ // 3. if file exist, just skip upload
86
97
  if (checkStatus.isExist) {
87
98
  metadata = checkStatus.metadata as FileMetadata;
88
99
  onStatusUpdate?.({
@@ -91,7 +102,7 @@ export const createFileUploadSlice: StateCreator<
91
102
  value: { status: 'processing', uploadState: { progress: 100, restTime: 0, speed: 0 } },
92
103
  });
93
104
  }
94
- // 2. if file don't exist, need upload files
105
+ // 3. if file don't exist, need upload files
95
106
  else {
96
107
  const { data, success } = await uploadService.uploadFileToS3(file, {
97
108
  onNotSupported: () => {
@@ -119,7 +130,7 @@ export const createFileUploadSlice: StateCreator<
119
130
  metadata = data;
120
131
  }
121
132
 
122
- // 3. use more powerful file type detector to get file type
133
+ // 4. use more powerful file type detector to get file type
123
134
  let fileType = file.type;
124
135
 
125
136
  if (!file.type) {
@@ -129,7 +140,7 @@ export const createFileUploadSlice: StateCreator<
129
140
  fileType = type?.mime || 'text/plain';
130
141
  }
131
142
 
132
- // 4. create file to db
143
+ // 5. create file to db
133
144
  const data = await fileService.createFile(
134
145
  {
135
146
  fileType,
@@ -153,6 +164,6 @@ export const createFileUploadSlice: StateCreator<
153
164
  },
154
165
  });
155
166
 
156
- return { ...data, filename: file.name };
167
+ return { ...data, dimensions, filename: file.name };
157
168
  },
158
169
  });
@@ -97,7 +97,7 @@ export function useDimensionControl() {
97
97
  }, [paramsSchema]);
98
98
 
99
99
  // 只要不是所有维度相关的控件都不显示,那么这个容器就应该显示
100
- const showDimensionControl = !(!isSupportAspectRatio && !isSupportWidth && !isSupportHeight);
100
+ const showDimensionControl = isSupportAspectRatio || isSupportWidth || isSupportHeight;
101
101
 
102
102
  return {
103
103
  isLocked: store.isAspectRatioLocked,
package/tsconfig.json CHANGED
@@ -34,14 +34,5 @@
34
34
  ]
35
35
  },
36
36
  "exclude": ["node_modules", "public/sw.js", "apps/desktop", "tmp", "temp", ".temp"],
37
- "include": [
38
- "**/*.d.ts",
39
- "**/*.ts",
40
- "**/*.tsx",
41
- ".next/types/**/*.ts",
42
- "next-env.d.ts",
43
- "src",
44
- "tests",
45
- "vitest.config.ts"
46
- ]
37
+ "include": ["**/*.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "next-env.d.ts"]
47
38
  }
@@ -1,193 +0,0 @@
1
- ---
2
- description: Debug 调试指南
3
- globs:
4
- alwaysApply: false
5
- ---
6
- # Debug 调试指南
7
-
8
- ## 💡 调试流程概览
9
-
10
- 当遇到问题时,请按照以下优先级进行处理:
11
-
12
- 1. **快速判断** - 对于熟悉的错误,直接提供解决方案
13
- 2. **信息收集** - 使用工具搜索相关代码和配置
14
- 3. **网络搜索** - 查找现有解决方案
15
- 4. **定位调试** - 添加日志进行问题定位
16
- 5. **临时方案** - 如果找不到根本解决方案,提供临时解决方案
17
- 6. **解决实施** - 提供可维护的最终解决方案
18
-
19
- ## 🔍 错误信息分析
20
-
21
- ### 错误来源识别
22
-
23
- 错误信息可能来自:
24
-
25
- - **Terminal 输出** - 构建、运行时错误
26
- - **浏览器控制台** - 前端 JavaScript 错误
27
- - **开发工具** - ESLint、TypeScript、测试框架等
28
- - **服务器日志** - API、数据库连接等后端错误
29
- - **截图或文本** - 用户直接提供的错误信息
30
-
31
- ## 🛠️ 信息收集工具
32
-
33
- ### 代码搜索工具
34
-
35
- 使用以下工具收集相关信息,并根据场景选择最合适的工具:
36
-
37
- - **`codebase_search` (语义搜索)**
38
- - **何时使用**: 当你不确定具体的代码实现,想要寻找相关概念、功能或逻辑时。
39
- - **示例**: `查询"文件上传"功能的实现`
40
- - **`grep_search` (精确/正则搜索)**
41
- - **何时使用**: 当你知道要查找的确切字符串、函数名、变量名或一个特定的模式时。
42
- - **示例**: `查找所有使用了 'useState' 的地方`
43
- - **`file_search` (文件搜索)**
44
- - **何时使用**: 当你知道文件名的一部分,需要快速定位文件时。
45
- - **示例**: `查找 'Button.tsx' 组件`
46
- - **`read_file` (内容读取)**
47
- - **何时使用**: 在定位到具体文件后,用于查看其完整内容和上下文。
48
- - **`web_search` (网络搜索)**
49
- - **何时使用**: 当错误信息可能与第三方库、API 或常见问题相关时,用于获取外部信息。
50
-
51
- ### 环境与依赖检查
52
-
53
- - **检查 `package.json`**: 查看 `scripts` 了解项目如何运行、构建和测试。查看 `dependencies` 和 `devDependencies` 确认库版本,版本冲突有时是问题的根源。
54
- - **运行测试**: 使用 `ni vitest` 运行单元测试和集成测试,这可以快速定位功能回归或组件错误。
55
-
56
- ### 项目特定搜索目标
57
-
58
- 针对 lobe-chat 项目,重点关注:
59
-
60
- - **配置文件**: [package.json](mdc:package.json), [next.config.mjs](mdc:next.config.mjs)
61
- - **核心功能**: `src/features/` 下的相关模块
62
- - **状态管理**: `src/store/` 下的 Zustand stores
63
- - **数据库**: `src/database/` 和 `src/migrations/`
64
- - **类型定义**: `src/types/` 下的类型文件
65
- - **服务层**: `src/services/` 下的 API 服务
66
- - **启动流程**: [apps/desktop/src/main/core/App.ts](mdc:apps/desktop/src/main/core/App.ts) - 了解应用启动流程
67
-
68
- ## 🌐 网络搜索策略
69
-
70
- ### 搜索顺序优先级
71
-
72
- 1. **和问题相关的项目的 github issue**
73
-
74
- 2. **技术社区**
75
- - Stack Overflow
76
- - GitHub Discussions
77
- - Reddit
78
-
79
- 3. **官方文档**
80
- - 使用 `mcp_context7_resolve-library-id` 和 `mcp_context7_get-library-docs` 工具
81
- - 查阅官方文档网站
82
-
83
- ### 搜索关键词策略
84
-
85
- - **错误信息**: 完整的错误消息
86
- - **技术栈**: "Next.js 15" + "error message"
87
- - **上下文**: 添加功能相关的关键词
88
-
89
- ## 🔧 问题定位与结构化思考
90
-
91
- 如果问题比较复杂,我们要按照先定位问题,再解决问题的大方向进行。
92
-
93
- ### 结构化思考工具
94
-
95
- 对于复杂或多步骤的调试任务,使用 `mcp_sequential-thinking_sequentialthinking` 工具来结构化思考过程。这有助于:
96
-
97
- - **分解问题**: 将大问题拆解成可管理的小步骤。
98
- - **清晰追踪**: 记录每一步的发现和决策,避免遗漏。
99
- - **自我修正**: 在过程中评估和调整调试路径。
100
-
101
- ### 日志调试
102
-
103
- 在问题产生的路径上添加日志,可以简单使用 `console.log` 或者参考 [debug-usage.mdc](mdc:.cursor/rules/debug-usage.mdc) 使用 `debug` 模块。添加完日志后,请求我运行相关的代码并提供关键输出和错误信息。
104
-
105
- ### 引导式交互调试
106
-
107
- 虽然我无法直接操作浏览器开发者工具,但我可以引导你进行交互式调试:
108
-
109
- 1. **设置断点**: 我会告诉你可以在哪些关键代码行设置断点。
110
- 2. **检查变量**: 我会请你在断点处检查特定变量的值或 `props`/`state`。
111
- 3. **分析调用栈**: 我会请你提供调用栈信息,以帮助理解代码执行流程。
112
-
113
- ## 💡 临时解决方案策略
114
-
115
- 当无法找到根本解决方案时,提供临时解决方案:
116
-
117
- ### 临时方案准则
118
-
119
- - **快速修复** - 优先让功能可用
120
- - **最小修改** - 减少对现有代码的影响
121
- - **清晰标记** - 明确标注这是临时方案
122
- - **后续计划** - 说明后续如何找到更好的解决方案
123
-
124
- ### 临时方案模板
125
-
126
- ```markdown
127
- ## 临时解决方案 ⚠️
128
-
129
- **问题**: [简要描述问题]
130
-
131
- **临时修复**:
132
- [具体的临时修复步骤]
133
-
134
- **风险说明**:
135
-
136
- - [可能的副作用或限制]
137
- - [需要注意的事项]
138
-
139
- **后续计划**:
140
-
141
- - [ ] 深入调研根本原因
142
- - [ ] 寻找更优雅的解决方案
143
- - [ ] 监控是否有其他影响
144
- ```
145
-
146
- ## ✅ 解决方案准则
147
-
148
- ### 方案质量标准
149
-
150
- 提供的解决方案应该:
151
-
152
- - **✅ 低侵入性** - 最小化对现有代码的修改
153
- - **✅ 可维护性** - 易于理解和后续维护
154
- - **✅ 类型安全** - 符合 TypeScript 规范
155
- - **✅ 最佳实践** - 遵循项目的编码规范
156
- - **✅ 测试友好** - 便于编写和运行测试
157
- - **❌ 避免长期 Hack** - 临时方案可以 hack,但要明确标注
158
-
159
- ### 解决方案模板
160
-
161
- ```markdown
162
- ## 问题原因
163
-
164
- [简要说明问题产生的根本原因]
165
-
166
- ## 解决方案
167
-
168
- [详细的解决步骤]
169
-
170
- ## 代码修改
171
-
172
- [具体的代码变更]
173
-
174
- ## 验证方法
175
-
176
- [如何验证问题已解决]
177
-
178
- ## 预防措施
179
-
180
- [如何避免类似问题再次发生]
181
- ```
182
-
183
- ## 🔄 迭代调试流程
184
-
185
- 如果初次解决方案无效:
186
-
187
- 1. **重新收集信息** - 基于新的错误信息搜索
188
- 2. **深入代码分析** - 查看更多相关代码文件
189
- 3. **运行相关测试** - 编写或运行一个失败的测试来稳定复现问题。
190
- 4. **扩大搜索范围** - 搜索更广泛的相关问题
191
- 5. **请求更多日志** - 添加更详细的调试信息
192
- 6. **提供临时方案** - 如果根本解决方案复杂,先提供临时修复
193
- 7. **分解问题** - 将复杂问题拆解为更小的子问题
@@ -1,16 +0,0 @@
1
- /**
2
- * Image generation and processing configuration constants
3
- */
4
- export const IMAGE_GENERATION_CONFIG = {
5
- /**
6
- * Maximum cover image size in pixels (longest edge)
7
- * Used for generating cover images from source images
8
- */
9
- COVER_MAX_SIZE: 256,
10
-
11
- /**
12
- * Maximum thumbnail size in pixels (longest edge)
13
- * Used for generating thumbnail images from original images
14
- */
15
- THUMBNAIL_MAX_SIZE: 512,
16
- } as const;
@@ -1,26 +0,0 @@
1
- import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
2
- import type { NextRequest } from 'next/server';
3
-
4
- import { pino } from '@/libs/logger';
5
- import { createLambdaContext } from '@/libs/trpc/lambda/context';
6
- import { desktopRouter } from '@/server/routers/desktop';
7
-
8
- const handler = (req: NextRequest) =>
9
- fetchRequestHandler({
10
- /**
11
- * @link https://trpc.io/docs/v11/context
12
- */
13
- createContext: () => createLambdaContext(req),
14
-
15
- endpoint: '/trpc/desktop',
16
-
17
- onError: ({ error, path, type }) => {
18
- pino.info(`Error in tRPC handler (desktop) on path: ${path}, type: ${type}`);
19
- console.error(error);
20
- },
21
-
22
- req,
23
- router: desktopRouter,
24
- });
25
-
26
- export { handler as GET, handler as POST };
@@ -1,24 +0,0 @@
1
- import { memo } from 'react';
2
-
3
- import { useGenerationConfigParam } from '@/store/image/slices/generationConfig/hooks';
4
-
5
- import Select from '../../../components/AspectRatioSelect';
6
-
7
- const AspectRatioSelect = memo(() => {
8
- const { value, setValue, enumValues } = useGenerationConfigParam('aspectRatio');
9
-
10
- // 如果模型支持 ratio 枚举值,则使用枚举值
11
- if (enumValues && enumValues.length > 0) {
12
- const options = enumValues.map((ratio) => ({
13
- label: ratio,
14
- value: ratio,
15
- }));
16
-
17
- return <Select onChange={setValue} options={options} style={{ width: '100%' }} value={value} />;
18
- }
19
-
20
- // 如果模型不支持 ratio 参数,返回 null(由外部处理是否显示)
21
- return null;
22
- });
23
-
24
- export default AspectRatioSelect;
@@ -1,15 +0,0 @@
1
- import { SliderWithInput } from '@lobehub/ui';
2
- import { memo } from 'react';
3
-
4
- import { useGenerationConfigParam } from '@/store/image/slices/generationConfig/hooks';
5
-
6
- interface SizeSliderInputProps {
7
- paramName: 'width' | 'height';
8
- }
9
-
10
- const SizeSliderInput = memo(({ paramName }: SizeSliderInputProps) => {
11
- const { value, setValue, min, max } = useGenerationConfigParam(paramName);
12
- return <SliderWithInput max={max} min={min} onChange={setValue} value={value} />;
13
- });
14
-
15
- export default SizeSliderInput;