@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.
- package/.cursor/rules/add-provider-doc.mdc +183 -0
- package/.cursor/rules/project-introduce.mdc +1 -15
- package/.cursor/rules/project-structure.mdc +227 -0
- package/.cursor/rules/testing-guide/db-model-test.mdc +5 -3
- package/.cursor/rules/testing-guide/testing-guide.mdc +153 -168
- package/.env.example +8 -0
- package/.github/workflows/claude.yml +1 -1
- package/.github/workflows/release.yml +3 -3
- package/.github/workflows/test.yml +10 -5
- package/CHANGELOG.md +50 -0
- package/CLAUDE.md +17 -33
- package/Dockerfile +5 -1
- package/Dockerfile.database +5 -1
- package/Dockerfile.pglite +5 -1
- package/changelog/v1.json +14 -0
- package/docs/development/basic/feature-development.mdx +1 -1
- package/docs/development/basic/feature-development.zh-CN.mdx +1 -1
- package/docs/development/basic/setup-development.mdx +10 -13
- package/docs/development/basic/setup-development.zh-CN.mdx +9 -12
- package/docs/self-hosting/environment-variables/model-provider.mdx +27 -2
- package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +27 -2
- package/docs/usage/providers/bfl.mdx +68 -0
- package/docs/usage/providers/bfl.zh-CN.mdx +67 -0
- package/locales/ar/components.json +11 -0
- package/locales/ar/error.json +11 -0
- package/locales/ar/models.json +64 -4
- package/locales/ar/providers.json +3 -0
- package/locales/bg-BG/components.json +11 -0
- package/locales/bg-BG/error.json +11 -0
- package/locales/bg-BG/models.json +64 -4
- package/locales/bg-BG/providers.json +3 -0
- package/locales/de-DE/components.json +11 -0
- package/locales/de-DE/error.json +11 -12
- package/locales/de-DE/models.json +64 -4
- package/locales/de-DE/providers.json +3 -0
- package/locales/en-US/components.json +6 -0
- package/locales/en-US/error.json +11 -12
- package/locales/en-US/models.json +64 -4
- package/locales/en-US/providers.json +3 -0
- package/locales/es-ES/components.json +11 -0
- package/locales/es-ES/error.json +11 -0
- package/locales/es-ES/models.json +64 -6
- package/locales/es-ES/providers.json +3 -0
- package/locales/fa-IR/components.json +11 -0
- package/locales/fa-IR/error.json +11 -0
- package/locales/fa-IR/models.json +64 -4
- package/locales/fa-IR/providers.json +3 -0
- package/locales/fr-FR/components.json +11 -0
- package/locales/fr-FR/error.json +11 -12
- package/locales/fr-FR/models.json +64 -4
- package/locales/fr-FR/providers.json +3 -0
- package/locales/it-IT/components.json +11 -0
- package/locales/it-IT/error.json +11 -0
- package/locales/it-IT/models.json +64 -4
- package/locales/it-IT/providers.json +3 -0
- package/locales/ja-JP/components.json +11 -0
- package/locales/ja-JP/error.json +11 -12
- package/locales/ja-JP/models.json +64 -4
- package/locales/ja-JP/providers.json +3 -0
- package/locales/ko-KR/components.json +11 -0
- package/locales/ko-KR/error.json +11 -12
- package/locales/ko-KR/models.json +64 -6
- package/locales/ko-KR/providers.json +3 -0
- package/locales/nl-NL/components.json +11 -0
- package/locales/nl-NL/error.json +11 -0
- package/locales/nl-NL/models.json +62 -4
- package/locales/nl-NL/providers.json +3 -0
- package/locales/pl-PL/components.json +11 -0
- package/locales/pl-PL/error.json +11 -0
- package/locales/pl-PL/models.json +64 -4
- package/locales/pl-PL/providers.json +3 -0
- package/locales/pt-BR/components.json +11 -0
- package/locales/pt-BR/error.json +11 -0
- package/locales/pt-BR/models.json +64 -4
- package/locales/pt-BR/providers.json +3 -0
- package/locales/ru-RU/components.json +11 -0
- package/locales/ru-RU/error.json +11 -0
- package/locales/ru-RU/models.json +64 -4
- package/locales/ru-RU/providers.json +3 -0
- package/locales/tr-TR/components.json +11 -0
- package/locales/tr-TR/error.json +11 -0
- package/locales/tr-TR/models.json +64 -4
- package/locales/tr-TR/providers.json +3 -0
- package/locales/vi-VN/components.json +11 -0
- package/locales/vi-VN/error.json +11 -0
- package/locales/vi-VN/models.json +64 -4
- package/locales/vi-VN/providers.json +3 -0
- package/locales/zh-CN/components.json +6 -0
- package/locales/zh-CN/error.json +11 -0
- package/locales/zh-CN/models.json +64 -4
- package/locales/zh-CN/providers.json +3 -0
- package/locales/zh-TW/components.json +11 -0
- package/locales/zh-TW/error.json +11 -12
- package/locales/zh-TW/models.json +64 -6
- package/locales/zh-TW/providers.json +3 -0
- package/package.json +4 -4
- package/packages/const/src/image.ts +28 -0
- package/packages/const/src/index.ts +1 -0
- package/packages/database/package.json +4 -2
- package/packages/database/src/repositories/aiInfra/index.ts +1 -1
- package/packages/database/tests/setup-db.ts +3 -0
- package/packages/database/vitest.config.mts +33 -0
- package/packages/model-runtime/src/google/index.ts +3 -0
- package/packages/model-runtime/src/qwen/createImage.test.ts +0 -19
- package/packages/model-runtime/src/qwen/createImage.ts +1 -27
- package/packages/model-runtime/src/utils/modelParse.ts +1 -1
- package/packages/model-runtime/src/utils/streams/google-ai.ts +26 -14
- package/packages/types/src/aiModel.ts +2 -1
- package/packages/utils/src/client/imageDimensions.test.ts +95 -0
- package/packages/utils/src/client/imageDimensions.ts +54 -0
- package/packages/utils/src/number.test.ts +3 -1
- package/packages/utils/src/number.ts +1 -2
- package/src/app/[variants]/(main)/image/@menu/components/SeedNumberInput/index.tsx +1 -1
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/DimensionControlGroup.tsx +0 -1
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUpload.tsx +16 -6
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUrl.tsx +14 -2
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUrlsUpload.tsx +27 -2
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/MultiImagesUpload/index.tsx +23 -5
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/hooks/useAutoDimensions.ts +56 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/index.tsx +82 -5
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/__tests__/dimensionConstraints.test.ts +235 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/__tests__/imageValidation.test.ts +401 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/dimensionConstraints.ts +54 -0
- package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicItem.tsx +3 -1
- package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicList.tsx +15 -2
- package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/utils.ts +5 -4
- package/src/config/aiModels/google.ts +22 -1
- package/src/config/aiModels/qwen.ts +2 -2
- package/src/config/aiModels/vertexai.ts +22 -0
- package/src/libs/standard-parameters/index.ts +1 -1
- package/src/server/services/generation/index.ts +1 -1
- package/src/store/chat/slices/builtinTool/actions/dalle.test.ts +20 -13
- package/src/store/file/slices/upload/action.ts +18 -7
- package/src/store/image/slices/generationConfig/hooks.ts +1 -1
- package/tsconfig.json +1 -10
- package/.cursor/rules/debug.mdc +0 -193
- package/packages/const/src/imageGeneration.ts +0 -16
- package/src/app/(backend)/trpc/desktop/[trpc]/route.ts +0 -26
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/AspectRatioSelect.tsx +0 -24
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/SizeSliderInput.tsx +0 -15
- package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicItemContainer.tsx +0 -91
- package/src/app/desktop/devtools/page.tsx +0 -89
- package/src/app/desktop/layout.tsx +0 -31
- /package/apps/desktop/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/packages/database/{vitest.config.ts → vitest.config.server.mts} +0 -0
- /package/packages/electron-server-ipc/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/packages/file-loaders/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/packages/model-runtime/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/packages/prompts/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/packages/utils/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/packages/web-crawler/{vitest.config.ts → vitest.config.mts} +0 -0
- /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
|
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
|
115
|
-
if (!dimensions.
|
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
|
-
|
120
|
-
const
|
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: '
|
1361
|
-
enum: ['
|
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,
|
@@ -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/
|
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
|
-
|
45
|
-
|
46
|
-
vi.spyOn(
|
47
|
-
|
48
|
-
|
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(
|
52
|
-
|
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
|
-
|
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
|
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.
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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
|
-
//
|
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 =
|
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
|
}
|
package/.cursor/rules/debug.mdc
DELETED
@@ -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 };
|
package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/AspectRatioSelect.tsx
DELETED
@@ -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;
|
package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/SizeSliderInput.tsx
DELETED
@@ -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;
|