@lobehub/chat 1.113.3 → 1.114.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/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 1.114.0](https://github.com/lobehub/lobe-chat/compare/v1.113.3...v1.114.0)
6
+
7
+ <sup>Released on **2025-08-19**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **models**: Add Qwen Image Edit model.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **models**: Add Qwen Image Edit model, closes [#8851](https://github.com/lobehub/lobe-chat/issues/8851) ([4d7a060](https://github.com/lobehub/lobe-chat/commit/4d7a060))
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
+
5
30
  ### [Version 1.113.3](https://github.com/lobehub/lobe-chat/compare/v1.113.2...v1.113.3)
6
31
 
7
32
  <sup>Released on **2025-08-19**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,9 @@
1
1
  [
2
+ {
3
+ "children": {},
4
+ "date": "2025-08-19",
5
+ "version": "1.114.0"
6
+ },
2
7
  {
3
8
  "children": {
4
9
  "fixes": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.113.3",
3
+ "version": "1.114.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",
@@ -195,6 +195,11 @@ export const createRouterRuntime = ({
195
195
  }
196
196
  }
197
197
 
198
+ async createImage(payload: CreateImagePayload) {
199
+ const runtime = await this.getRuntimeByModel(payload.model);
200
+ return runtime.createImage!(payload);
201
+ }
202
+
198
203
  async textToImage(payload: TextToImagePayload) {
199
204
  const runtime = await this.getRuntimeByModel(payload.model);
200
205
 
@@ -203,12 +208,12 @@ export const createRouterRuntime = ({
203
208
 
204
209
  async models() {
205
210
  if (models && typeof models === 'function') {
206
- // 如果是函数式配置,使用最后一个 runtime 的 client 调用函数
207
- const lastRuntime = this._runtimes.at(-1)?.runtime;
208
- if (lastRuntime && 'client' in lastRuntime) {
209
- return await models({ client: (lastRuntime as any).client });
210
- }
211
+ // 如果是函数式配置,使用最后一个 runtime 的 client 调用函数
212
+ const lastRuntime = this._runtimes.at(-1)?.runtime;
213
+ if (lastRuntime && 'client' in lastRuntime) {
214
+ return await models({ client: (lastRuntime as any).client });
211
215
  }
216
+ }
212
217
  return this._runtimes.at(-1)?.runtime.models?.();
213
218
  }
214
219
 
@@ -49,6 +49,22 @@ exports[`LobeOpenAI > models > should get models 1`] = `
49
49
  "functionCall": false,
50
50
  "id": "dall-e-2",
51
51
  "maxOutput": undefined,
52
+ "parameters": {
53
+ "imageUrl": {
54
+ "default": null,
55
+ },
56
+ "prompt": {
57
+ "default": "",
58
+ },
59
+ "size": {
60
+ "default": "1024x1024",
61
+ "enum": [
62
+ "256x256",
63
+ "512x512",
64
+ "1024x1024",
65
+ ],
66
+ },
67
+ },
52
68
  "reasoning": false,
53
69
  "releasedAt": "2023-11-01",
54
70
  "type": "image",
@@ -231,6 +247,19 @@ exports[`LobeOpenAI > models > should get models 1`] = `
231
247
  "functionCall": false,
232
248
  "id": "dall-e-3",
233
249
  "maxOutput": undefined,
250
+ "parameters": {
251
+ "prompt": {
252
+ "default": "",
253
+ },
254
+ "size": {
255
+ "default": "1024x1024",
256
+ "enum": [
257
+ "1024x1024",
258
+ "1792x1024",
259
+ "1024x1792",
260
+ ],
261
+ },
262
+ },
234
263
  "reasoning": false,
235
264
  "releasedAt": "2023-10-31",
236
265
  "type": "image",
@@ -85,7 +85,7 @@ export const MODEL_LIST_CONFIGS = {
85
85
  export const PROVIDER_DETECTION_CONFIG = {
86
86
  anthropic: ['claude'],
87
87
  deepseek: ['deepseek'],
88
- google: ['gemini'],
88
+ google: ['gemini', 'imagen'],
89
89
  llama: ['llama', 'llava'],
90
90
  moonshot: ['moonshot', 'kimi'],
91
91
  openai: ['o1', 'o3', 'o4', 'gpt-'],
@@ -251,7 +251,7 @@ const processModelCard = (
251
251
  model: { [key: string]: any; id: string },
252
252
  config: ModelProcessorConfig,
253
253
  knownModel?: any,
254
- ): ChatModelCard => {
254
+ ): ChatModelCard | undefined => {
255
255
  const {
256
256
  functionCallKeywords = [],
257
257
  visionKeywords = [],
@@ -260,6 +260,20 @@ const processModelCard = (
260
260
  } = config;
261
261
 
262
262
  const isExcludedModel = isKeywordListMatch(model.id.toLowerCase(), excludeKeywords);
263
+ const modelType =
264
+ model.type ||
265
+ knownModel?.type ||
266
+ (isKeywordListMatch(
267
+ model.id.toLowerCase(),
268
+ IMAGE_MODEL_KEYWORDS.map((k) => k.toLowerCase()),
269
+ )
270
+ ? 'image'
271
+ : 'chat');
272
+
273
+ // image model can't find parameters
274
+ if (modelType === 'image' && !model.parameters && !knownModel?.parameters) {
275
+ return undefined;
276
+ }
263
277
 
264
278
  return {
265
279
  contextWindowTokens: model.contextWindowTokens ?? knownModel?.contextWindowTokens ?? undefined,
@@ -281,15 +295,11 @@ const processModelCard = (
281
295
  knownModel?.abilities?.reasoning ??
282
296
  (isKeywordListMatch(model.id.toLowerCase(), reasoningKeywords) || false),
283
297
  releasedAt: processReleasedAt(model, knownModel),
284
- type:
285
- model.type ||
286
- knownModel?.type ||
287
- (isKeywordListMatch(
288
- model.id.toLowerCase(),
289
- IMAGE_MODEL_KEYWORDS.map((k) => k.toLowerCase()),
290
- )
291
- ? 'image'
292
- : 'chat'),
298
+ type: modelType,
299
+ // current, only image model use the parameters field
300
+ ...(modelType === 'image' && {
301
+ parameters: model.parameters ?? knownModel?.parameters,
302
+ }),
293
303
  vision:
294
304
  model.vision ??
295
305
  knownModel?.abilities?.vision ??
@@ -329,7 +339,7 @@ export const processModelList = async (
329
339
 
330
340
  return processModelCard(model, config, knownModel);
331
341
  }),
332
- ).then((results) => results.filter(Boolean));
342
+ ).then((results) => results.filter((result) => !!result));
333
343
  };
334
344
 
335
345
  /**
@@ -381,11 +391,15 @@ export const processMultiProviderModelList = async (
381
391
  const processedModel = processModelCard(model, config, knownModel);
382
392
 
383
393
  // 如果找到了本地配置中的模型,使用其 enabled 状态
384
- if (providerLocalModelConfig && typeof providerLocalModelConfig.enabled === 'boolean') {
394
+ if (
395
+ processedModel &&
396
+ providerLocalModelConfig &&
397
+ typeof providerLocalModelConfig.enabled === 'boolean'
398
+ ) {
385
399
  processedModel.enabled = providerLocalModelConfig.enabled;
386
400
  }
387
401
 
388
402
  return processedModel;
389
403
  }),
390
- ).then((results) => results.filter(Boolean));
404
+ ).then((results) => results.filter((result) => !!result));
391
405
  };
@@ -326,7 +326,7 @@ export interface AiProviderModelListItem {
326
326
  displayName?: string;
327
327
  enabled: boolean;
328
328
  id: string;
329
- parameters?: Record<string, any>;
329
+ parameters?: ModelParamsSchema;
330
330
  pricing?: Pricing;
331
331
  releasedAt?: string;
332
332
  settings?: AiModelSettings;
@@ -378,7 +378,7 @@ export interface EnabledAiModel {
378
378
  displayName?: string;
379
379
  enabled?: boolean;
380
380
  id: string;
381
- parameters?: Record<string, any>;
381
+ parameters?: ModelParamsSchema;
382
382
  providerId: string;
383
383
  settings?: AiModelSettings;
384
384
  sort?: number;
@@ -1,5 +1,6 @@
1
1
  import { ReactNode } from 'react';
2
2
 
3
+ import { ModelParamsSchema } from '@/libs/standard-parameters';
3
4
  import { AiModelType, Pricing } from '@/types/aiModel';
4
5
  import { AiProviderSettings } from '@/types/aiProvider';
5
6
 
@@ -41,6 +42,8 @@ export interface ChatModelCard {
41
42
  */
42
43
  legacy?: boolean;
43
44
  maxOutput?: number;
45
+ parameters?: ModelParamsSchema;
46
+
44
47
  pricing?: Pricing;
45
48
 
46
49
  /**
@@ -3,6 +3,8 @@
3
3
  import { memo } from 'react';
4
4
  import { Flexbox } from 'react-layout-kit';
5
5
 
6
+ import { useAiInfraStore } from '@/store/aiInfra';
7
+
6
8
  import ModelList from '../../features/ModelList';
7
9
  import ProviderConfig, { ProviderConfigProps } from '../../features/ProviderConfig';
8
10
 
@@ -10,6 +12,9 @@ interface ProviderDetailProps extends ProviderConfigProps {
10
12
  showConfig?: boolean;
11
13
  }
12
14
  const ProviderDetail = memo<ProviderDetailProps>(({ showConfig = true, ...card }) => {
15
+ const useFetchAiProviderItem = useAiInfraStore((s) => s.useFetchAiProviderItem);
16
+ useFetchAiProviderItem(card.id);
17
+
13
18
  return (
14
19
  <Flexbox gap={24} paddingBlock={8}>
15
20
  {/* ↓ cloud slot ↓ */}
@@ -143,7 +143,7 @@ const ProviderConfig = memo<ProviderConfigProps>(
143
143
  const { cx, styles, theme } = useStyles();
144
144
 
145
145
  const [
146
- useFetchAiProviderItem,
146
+ data,
147
147
  updateAiProviderConfig,
148
148
  enabled,
149
149
  isLoading,
@@ -153,7 +153,7 @@ const ProviderConfig = memo<ProviderConfigProps>(
153
153
  isProviderEndpointNotEmpty,
154
154
  isProviderApiKeyNotEmpty,
155
155
  ] = useAiInfraStore((s) => [
156
- s.useFetchAiProviderItem,
156
+ aiProviderSelectors.activeProviderConfig(s),
157
157
  s.updateAiProviderConfig,
158
158
  aiProviderSelectors.isProviderEnabled(id)(s),
159
159
  aiProviderSelectors.isAiProviderConfigLoading(id)(s),
@@ -164,8 +164,6 @@ const ProviderConfig = memo<ProviderConfigProps>(
164
164
  aiProviderSelectors.isActiveProviderApiKeyNotEmpty(s),
165
165
  ]);
166
166
 
167
- const { data } = useFetchAiProviderItem(id);
168
-
169
167
  useLayoutEffect(() => {
170
168
  if (isLoading) return;
171
169
 
@@ -20,6 +20,8 @@ export const fluxKreaParamsSchema: ModelParamsSchema = {
20
20
 
21
21
  export const qwenImageParamsSchema: ModelParamsSchema = {
22
22
  cfg: { default: 2.5, max: 20, min: 0, step: 0.1 },
23
+ // 实测 fal 宽高 最大就支持到 1536
24
+ // 默认值取自 https://chat.qwen.ai/ 官网的默认值
23
25
  height: { default: 1328, max: 1536, min: 512, step: 1 },
24
26
  prompt: { default: '' },
25
27
  seed: { default: null },
@@ -27,6 +29,16 @@ export const qwenImageParamsSchema: ModelParamsSchema = {
27
29
  width: { default: 1328, max: 1536, min: 512, step: 1 },
28
30
  };
29
31
 
32
+ export const qwenEditParamsSchema: ModelParamsSchema = {
33
+ cfg: { default: 4, max: 20, min: 0, step: 0.1 },
34
+ height: { default: 1328, max: 1536, min: 512, step: 1 },
35
+ imageUrl: { default: null },
36
+ prompt: { default: '' },
37
+ seed: { default: null },
38
+ steps: { default: 30, max: 50, min: 2, step: 1 },
39
+ width: { default: 1328, max: 1536, min: 512, step: 1 },
40
+ };
41
+
30
42
  const falImageModels: AIImageModelCard[] = [
31
43
  {
32
44
  description: '专注于图像编辑任务的FLUX.1模型,支持文本和图像输入。',
@@ -112,7 +124,20 @@ const falImageModels: AIImageModelCard[] = [
112
124
  },
113
125
  {
114
126
  description:
115
- 'Qwen团队带来的强大生图模型,具有令人印象深刻的中文文字生成能力和多样图片视觉风格。',
127
+ 'Qwen 团队发布的专业图像编辑模型,支持语义编辑和外观编辑,能够精确编辑中英文文字,实现风格转换、对象旋转等高质量图像编辑。',
128
+ displayName: 'Qwen Edit',
129
+ enabled: true,
130
+ id: 'qwen-image-edit',
131
+ parameters: qwenEditParamsSchema,
132
+ pricing: {
133
+ units: [{ name: 'imageGeneration', rate: 0.025, strategy: 'fixed', unit: 'image' }],
134
+ },
135
+ releasedAt: '2025-08-19',
136
+ type: 'image',
137
+ },
138
+ {
139
+ description:
140
+ 'Qwen 团队带来的强大生图模型,具有令人印象深刻的中文文字生成能力和多样图片视觉风格。',
116
141
  displayName: 'Qwen Image',
117
142
  enabled: true,
118
143
  id: 'qwen-image',
@@ -48,7 +48,10 @@ describe('getModelListByType', () => {
48
48
  abilities: {} as ModelAbilities,
49
49
  displayName: 'DALL-E 3',
50
50
  enabled: true,
51
- parameters: { size: '1024x1024', quality: 'standard' },
51
+ parameters: {
52
+ prompt: { default: '' },
53
+ size: { default: '1024x1024', enum: ['512x512', '1024x1024', '1536x1536'] }
54
+ },
52
55
  },
53
56
  {
54
57
  id: 'midjourney',
@@ -89,7 +92,10 @@ describe('getModelListByType', () => {
89
92
  contextWindowTokens: undefined,
90
93
  displayName: 'DALL-E 3',
91
94
  id: 'dall-e-3',
92
- parameters: { size: '1024x1024', quality: 'standard' },
95
+ parameters: {
96
+ prompt: { default: '' },
97
+ size: { default: '1024x1024', enum: ['512x512', '1024x1024', '1536x1536'] }
98
+ },
93
99
  });
94
100
  });
95
101