@lobehub/chat 1.124.4 → 1.126.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/react-component.mdc +1 -0
- package/.github/scripts/pr-comment.js +11 -2
- package/.github/workflows/auto-i18n.yml +1 -1
- package/.github/workflows/desktop-pr-build.yml +103 -23
- package/.github/workflows/docker-database.yml +1 -4
- package/.github/workflows/release-desktop-beta.yml +101 -24
- package/.github/workflows/release.yml +3 -2
- package/.github/workflows/test.yml +12 -9
- package/CHANGELOG.md +50 -0
- package/apps/desktop/electron-builder.js +8 -4
- package/changelog/v1.json +14 -0
- package/locales/ar/editor.json +7 -0
- package/locales/bg-BG/editor.json +7 -0
- package/locales/de-DE/editor.json +7 -0
- package/locales/en-US/editor.json +7 -0
- package/locales/es-ES/editor.json +7 -0
- package/locales/fa-IR/editor.json +7 -0
- package/locales/fr-FR/editor.json +7 -0
- package/locales/it-IT/editor.json +7 -0
- package/locales/ja-JP/editor.json +7 -0
- package/locales/ko-KR/editor.json +7 -0
- package/locales/nl-NL/editor.json +7 -0
- package/locales/pl-PL/editor.json +7 -0
- package/locales/pt-BR/editor.json +7 -0
- package/locales/ru-RU/editor.json +7 -0
- package/locales/tr-TR/editor.json +7 -0
- package/locales/vi-VN/editor.json +7 -0
- package/locales/zh-CN/editor.json +7 -0
- package/locales/zh-TW/editor.json +7 -0
- package/package.json +2 -2
- package/scripts/electronWorkflow/mergeMacReleaseFiles.js +179 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/ClassicChat.tsx +153 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/GroupChat.tsx +153 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/index.tsx +3 -145
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageConfigSkeleton.tsx +53 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/index.tsx +14 -2
- package/src/features/ChatInput/InputEditor/index.tsx +20 -5
- package/src/features/ChatInput/TypoBar/index.tsx +17 -0
- package/src/hooks/useFetchAiImageConfig.ts +49 -0
- package/src/locales/default/editor.ts +7 -0
- package/src/store/aiInfra/slices/aiModel/selectors.test.ts +1 -0
- package/src/store/aiInfra/slices/aiProvider/__tests__/action.test.ts +199 -140
- package/src/store/aiInfra/slices/aiProvider/action.ts +11 -4
- package/src/store/aiInfra/slices/aiProvider/initialState.ts +2 -0
- package/src/store/aiInfra/slices/aiProvider/selectors.ts +3 -0
- package/src/store/global/initialState.ts +8 -0
- package/src/store/global/selectors/systemStatus.ts +5 -3
- package/src/store/image/slices/generationConfig/action.test.ts +331 -150
- package/src/store/image/slices/generationConfig/action.ts +100 -23
- package/src/store/image/slices/generationConfig/initialState.ts +6 -0
- package/src/store/image/utils/aspectRatio.test.ts +148 -0
- package/src/store/image/utils/aspectRatio.ts +45 -0
|
@@ -9,8 +9,12 @@ import {
|
|
|
9
9
|
import { StateCreator } from 'zustand/vanilla';
|
|
10
10
|
|
|
11
11
|
import { aiProviderSelectors, getAiInfraStoreState } from '@/store/aiInfra';
|
|
12
|
+
import { useGlobalStore } from '@/store/global';
|
|
13
|
+
import { useUserStore } from '@/store/user';
|
|
14
|
+
import { authSelectors } from '@/store/user/selectors';
|
|
12
15
|
|
|
13
16
|
import type { ImageStore } from '../../store';
|
|
17
|
+
import { calculateInitialAspectRatio } from '../../utils/aspectRatio';
|
|
14
18
|
import { adaptSizeToRatio, parseRatio } from '../../utils/size';
|
|
15
19
|
|
|
16
20
|
export interface GenerationConfigAction {
|
|
@@ -34,6 +38,13 @@ export interface GenerationConfigAction {
|
|
|
34
38
|
setHeight(height: number): void;
|
|
35
39
|
toggleAspectRatioLock(): void;
|
|
36
40
|
setAspectRatio(aspectRatio: string): void;
|
|
41
|
+
|
|
42
|
+
// 初始化相关方法
|
|
43
|
+
initializeImageConfig(
|
|
44
|
+
isLogin?: boolean,
|
|
45
|
+
lastSelectedImageModel?: string,
|
|
46
|
+
lastSelectedImageProvider?: string,
|
|
47
|
+
): void;
|
|
37
48
|
}
|
|
38
49
|
|
|
39
50
|
/**
|
|
@@ -43,9 +54,22 @@ export interface GenerationConfigAction {
|
|
|
43
54
|
*/
|
|
44
55
|
export function getModelAndDefaults(model: string, provider: string) {
|
|
45
56
|
const enabledImageModelList = aiProviderSelectors.enabledImageModelList(getAiInfraStoreState());
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
57
|
+
|
|
58
|
+
const providerItem = enabledImageModelList.find((providerItem) => providerItem.id === provider);
|
|
59
|
+
if (!providerItem) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Provider "${provider}" not found in enabled image provider list. Available providers: ${enabledImageModelList.map((p) => p.id).join(', ')}`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const activeModel = providerItem.children.find(
|
|
66
|
+
(modelItem) => modelItem.id === model,
|
|
67
|
+
) as unknown as AIImageModelCard;
|
|
68
|
+
if (!activeModel) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Model "${model}" not found in provider "${provider}". Available models: ${providerItem.children.map((m) => m.id).join(', ')}`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
49
73
|
|
|
50
74
|
const parametersSchema = activeModel.parameters as ModelParamsSchema;
|
|
51
75
|
const defaultValues = extractDefaultValues(parametersSchema);
|
|
@@ -53,6 +77,22 @@ export function getModelAndDefaults(model: string, provider: string) {
|
|
|
53
77
|
return { defaultValues, activeModel, parametersSchema };
|
|
54
78
|
}
|
|
55
79
|
|
|
80
|
+
/**
|
|
81
|
+
* @internal Helper
|
|
82
|
+
* Internal utility to derive initial config for a given provider/model.
|
|
83
|
+
* Not exported; tests should cover through public actions.
|
|
84
|
+
*/
|
|
85
|
+
function prepareModelConfigState(model: string, provider: string) {
|
|
86
|
+
const { defaultValues, parametersSchema } = getModelAndDefaults(model, provider);
|
|
87
|
+
const initialActiveRatio = calculateInitialAspectRatio(parametersSchema, defaultValues);
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
defaultValues,
|
|
91
|
+
parametersSchema,
|
|
92
|
+
initialActiveRatio,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
56
96
|
export const createGenerationConfigSlice: StateCreator<
|
|
57
97
|
ImageStore,
|
|
58
98
|
[['zustand/devtools', never]],
|
|
@@ -237,38 +277,32 @@ export const createGenerationConfigSlice: StateCreator<
|
|
|
237
277
|
},
|
|
238
278
|
|
|
239
279
|
setModelAndProviderOnSelect: (model, provider) => {
|
|
240
|
-
const { defaultValues,
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
// 如果模型没有原生比例或尺寸参数,但有宽高,则启用虚拟比例控制
|
|
246
|
-
if (
|
|
247
|
-
!parametersSchema?.aspectRatio &&
|
|
248
|
-
!parametersSchema?.size &&
|
|
249
|
-
parametersSchema?.width &&
|
|
250
|
-
parametersSchema?.height
|
|
251
|
-
) {
|
|
252
|
-
const { width, height } = defaultValues;
|
|
253
|
-
if (typeof width === 'number' && typeof height === 'number' && width > 0 && height > 0) {
|
|
254
|
-
initialActiveRatio = `${width}:${height}`;
|
|
255
|
-
} else {
|
|
256
|
-
initialActiveRatio = '1:1';
|
|
257
|
-
}
|
|
258
|
-
}
|
|
280
|
+
const { defaultValues, parametersSchema, initialActiveRatio } = prepareModelConfigState(
|
|
281
|
+
model,
|
|
282
|
+
provider,
|
|
283
|
+
);
|
|
259
284
|
|
|
260
285
|
set(
|
|
261
286
|
{
|
|
262
287
|
model,
|
|
263
288
|
provider,
|
|
264
289
|
parameters: defaultValues,
|
|
265
|
-
parametersSchema
|
|
290
|
+
parametersSchema,
|
|
266
291
|
isAspectRatioLocked: false,
|
|
267
292
|
activeAspectRatio: initialActiveRatio,
|
|
268
293
|
},
|
|
269
294
|
false,
|
|
270
295
|
`setModelAndProviderOnSelect/${model}/${provider}`,
|
|
271
296
|
);
|
|
297
|
+
|
|
298
|
+
// 仅在登录用户下记忆上次选择,保持与恢复策略一致
|
|
299
|
+
const isLogin = authSelectors.isLogin(useUserStore.getState());
|
|
300
|
+
if (isLogin) {
|
|
301
|
+
useGlobalStore.getState().updateSystemStatus({
|
|
302
|
+
lastSelectedImageModel: model,
|
|
303
|
+
lastSelectedImageProvider: provider,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
272
306
|
},
|
|
273
307
|
|
|
274
308
|
setImageNum: (imageNum) => {
|
|
@@ -292,4 +326,47 @@ export const createGenerationConfigSlice: StateCreator<
|
|
|
292
326
|
reuseSeed: (seed: number) => {
|
|
293
327
|
set((state) => ({ parameters: { ...state.parameters, seed } }), false, `reuseSeed/${seed}`);
|
|
294
328
|
},
|
|
329
|
+
|
|
330
|
+
initializeImageConfig: (isLogin, lastSelectedImageModel, lastSelectedImageProvider) => {
|
|
331
|
+
// If no parameters are passed, get from store (backward compatibility)
|
|
332
|
+
let actualIsLogin = isLogin;
|
|
333
|
+
let actualLastSelectedImageModel = lastSelectedImageModel;
|
|
334
|
+
let actualLastSelectedImageProvider = lastSelectedImageProvider;
|
|
335
|
+
|
|
336
|
+
if (typeof isLogin === 'undefined') {
|
|
337
|
+
const globalStatus = useGlobalStore.getState().status;
|
|
338
|
+
actualIsLogin = authSelectors.isLogin(useUserStore.getState());
|
|
339
|
+
actualLastSelectedImageModel = globalStatus.lastSelectedImageModel;
|
|
340
|
+
actualLastSelectedImageProvider = globalStatus.lastSelectedImageProvider;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (actualIsLogin && actualLastSelectedImageModel && actualLastSelectedImageProvider) {
|
|
344
|
+
try {
|
|
345
|
+
const { defaultValues, parametersSchema, initialActiveRatio } = prepareModelConfigState(
|
|
346
|
+
actualLastSelectedImageModel,
|
|
347
|
+
actualLastSelectedImageProvider,
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
set(
|
|
351
|
+
{
|
|
352
|
+
model: actualLastSelectedImageModel,
|
|
353
|
+
provider: actualLastSelectedImageProvider,
|
|
354
|
+
parameters: defaultValues,
|
|
355
|
+
parametersSchema,
|
|
356
|
+
isAspectRatioLocked: false,
|
|
357
|
+
activeAspectRatio: initialActiveRatio,
|
|
358
|
+
isInit: true,
|
|
359
|
+
},
|
|
360
|
+
false,
|
|
361
|
+
`initializeImageConfig/${actualLastSelectedImageModel}/${actualLastSelectedImageProvider}`,
|
|
362
|
+
);
|
|
363
|
+
} catch {
|
|
364
|
+
// If restoration fails, simply mark as initialized to use default configuration
|
|
365
|
+
set({ isInit: true }, false, 'initializeImageConfig/fallback');
|
|
366
|
+
}
|
|
367
|
+
} else {
|
|
368
|
+
// No remembered model, directly mark as initialized (use default values)
|
|
369
|
+
set({ isInit: true }, false, 'initializeImageConfig/default');
|
|
370
|
+
}
|
|
371
|
+
},
|
|
295
372
|
});
|
|
@@ -21,6 +21,11 @@ export interface GenerationConfigState {
|
|
|
21
21
|
|
|
22
22
|
isAspectRatioLocked: boolean;
|
|
23
23
|
activeAspectRatio: string | null; // string - 虚拟比例; null - 原生比例
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 标记配置是否已初始化(包括从记忆中恢复)
|
|
27
|
+
*/
|
|
28
|
+
isInit: boolean;
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
export const DEFAULT_IMAGE_GENERATION_PARAMETERS: RuntimeImageGenParams =
|
|
@@ -34,4 +39,5 @@ export const initialGenerationConfigState: GenerationConfigState = {
|
|
|
34
39
|
parametersSchema: gptImage1ParamsSchema,
|
|
35
40
|
isAspectRatioLocked: false,
|
|
36
41
|
activeAspectRatio: null,
|
|
42
|
+
isInit: false,
|
|
37
43
|
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { ModelParamsSchema } from 'model-bank';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { calculateInitialAspectRatio, supportsVirtualAspectRatio } from './aspectRatio';
|
|
5
|
+
|
|
6
|
+
// Test data fixtures
|
|
7
|
+
const createBaseSchema = (overrides: Partial<ModelParamsSchema> = {}): ModelParamsSchema => ({
|
|
8
|
+
prompt: { default: '' },
|
|
9
|
+
...overrides,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const createDimensionSchema = (overrides: Partial<ModelParamsSchema> = {}): ModelParamsSchema =>
|
|
13
|
+
createBaseSchema({
|
|
14
|
+
width: { default: 512, min: 256, max: 2048 },
|
|
15
|
+
height: { default: 512, min: 256, max: 2048 },
|
|
16
|
+
...overrides,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const createDefaultValues = (values: Record<string, any> = {}) => ({
|
|
20
|
+
prompt: '',
|
|
21
|
+
...values,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('aspectRatio utils', () => {
|
|
25
|
+
describe('calculateInitialAspectRatio', () => {
|
|
26
|
+
it('should return null when native aspect controls are present', () => {
|
|
27
|
+
// Models with native aspectRatio parameter
|
|
28
|
+
const aspectRatioSchema = createBaseSchema({
|
|
29
|
+
aspectRatio: { default: '1:1', enum: ['1:1', '16:9', '4:3'] },
|
|
30
|
+
});
|
|
31
|
+
const aspectRatioValues = createDefaultValues({ aspectRatio: '1:1' });
|
|
32
|
+
|
|
33
|
+
expect(calculateInitialAspectRatio(aspectRatioSchema, aspectRatioValues)).toBeNull();
|
|
34
|
+
|
|
35
|
+
// Models with native size parameter
|
|
36
|
+
const sizeSchema = createBaseSchema({
|
|
37
|
+
size: { default: '1024x1024', enum: ['512x512', '1024x1024', '1536x1536'] },
|
|
38
|
+
});
|
|
39
|
+
const sizeValues = createDefaultValues({ size: '1024x1024' });
|
|
40
|
+
|
|
41
|
+
expect(calculateInitialAspectRatio(sizeSchema, sizeValues)).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should return null when width or height parameters are missing', () => {
|
|
45
|
+
const schemaWithoutWidth = createBaseSchema({
|
|
46
|
+
height: { default: 512, min: 256, max: 2048 },
|
|
47
|
+
});
|
|
48
|
+
const valuesWithoutWidth = createDefaultValues({ height: 512 });
|
|
49
|
+
|
|
50
|
+
expect(calculateInitialAspectRatio(schemaWithoutWidth, valuesWithoutWidth)).toBeNull();
|
|
51
|
+
|
|
52
|
+
const schemaWithoutHeight = createBaseSchema({
|
|
53
|
+
width: { default: 512, min: 256, max: 2048 },
|
|
54
|
+
});
|
|
55
|
+
const valuesWithoutHeight = createDefaultValues({ width: 512 });
|
|
56
|
+
|
|
57
|
+
expect(calculateInitialAspectRatio(schemaWithoutHeight, valuesWithoutHeight)).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should calculate aspect ratio from width and height values', () => {
|
|
61
|
+
const schema = createDimensionSchema({
|
|
62
|
+
width: { default: 1024, min: 256, max: 2048 },
|
|
63
|
+
height: { default: 768, min: 256, max: 2048 },
|
|
64
|
+
});
|
|
65
|
+
const values = createDefaultValues({ width: 1024, height: 768 });
|
|
66
|
+
|
|
67
|
+
expect(calculateInitialAspectRatio(schema, values)).toBe('1024:768');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should handle square dimensions correctly', () => {
|
|
71
|
+
const schema = createDimensionSchema();
|
|
72
|
+
const values = createDefaultValues({ width: 512, height: 512 });
|
|
73
|
+
|
|
74
|
+
expect(calculateInitialAspectRatio(schema, values)).toBe('512:512');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should return fallback ratio for invalid dimension values', () => {
|
|
78
|
+
const schema = createDimensionSchema();
|
|
79
|
+
|
|
80
|
+
// Invalid values should fallback to 1:1
|
|
81
|
+
const testCases = [
|
|
82
|
+
{ width: NaN, height: NaN },
|
|
83
|
+
{ width: 0, height: 512 },
|
|
84
|
+
{ width: -512, height: 512 },
|
|
85
|
+
{ height: 512 }, // missing width
|
|
86
|
+
{ width: 512 }, // missing height
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
testCases.forEach((testCase) => {
|
|
90
|
+
const values = createDefaultValues(testCase);
|
|
91
|
+
expect(calculateInitialAspectRatio(schema, values)).toBe('1:1');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('supportsVirtualAspectRatio', () => {
|
|
97
|
+
it('should return true for models with width/height but no native aspect controls', () => {
|
|
98
|
+
const schema = createDimensionSchema();
|
|
99
|
+
|
|
100
|
+
expect(supportsVirtualAspectRatio(schema)).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should return false when native aspect controls are present', () => {
|
|
104
|
+
// Schema with native aspectRatio parameter
|
|
105
|
+
const aspectRatioSchema = createDimensionSchema({
|
|
106
|
+
aspectRatio: { default: '1:1', enum: ['1:1', '16:9', '4:3'] },
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(supportsVirtualAspectRatio(aspectRatioSchema)).toBe(false);
|
|
110
|
+
|
|
111
|
+
// Schema with native size parameter
|
|
112
|
+
const sizeSchema = createDimensionSchema({
|
|
113
|
+
size: { default: '1024x1024', enum: ['512x512', '1024x1024', '1536x1536'] },
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(supportsVirtualAspectRatio(sizeSchema)).toBe(false);
|
|
117
|
+
|
|
118
|
+
// Schema with both aspectRatio and size parameters
|
|
119
|
+
const bothSchema = createDimensionSchema({
|
|
120
|
+
aspectRatio: { default: '1:1', enum: ['1:1', '16:9', '4:3'] },
|
|
121
|
+
size: { default: '1024x1024', enum: ['512x512', '1024x1024', '1536x1536'] },
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(supportsVirtualAspectRatio(bothSchema)).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should return false when required dimension parameters are missing', () => {
|
|
128
|
+
// Missing width parameter
|
|
129
|
+
const schemaWithoutWidth = createBaseSchema({
|
|
130
|
+
height: { default: 512, min: 256, max: 2048 },
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(supportsVirtualAspectRatio(schemaWithoutWidth)).toBe(false);
|
|
134
|
+
|
|
135
|
+
// Missing height parameter
|
|
136
|
+
const schemaWithoutHeight = createBaseSchema({
|
|
137
|
+
width: { default: 512, min: 256, max: 2048 },
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(supportsVirtualAspectRatio(schemaWithoutHeight)).toBe(false);
|
|
141
|
+
|
|
142
|
+
// Missing both width and height parameters
|
|
143
|
+
const emptySchema = createBaseSchema();
|
|
144
|
+
|
|
145
|
+
expect(supportsVirtualAspectRatio(emptySchema)).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ModelParamsSchema } from 'model-bank';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Calculate initial aspect ratio for image generation models
|
|
5
|
+
* @param parametersSchema - The model's parameter schema
|
|
6
|
+
* @param defaultValues - Default parameter values from the model
|
|
7
|
+
* @returns Initial aspect ratio string or null if not applicable
|
|
8
|
+
*/
|
|
9
|
+
export const calculateInitialAspectRatio = (
|
|
10
|
+
parametersSchema: ModelParamsSchema,
|
|
11
|
+
defaultValues: Record<string, any>,
|
|
12
|
+
): string | null => {
|
|
13
|
+
// If model has native aspect ratio or size parameters, don't use virtual ratio control
|
|
14
|
+
if (parametersSchema?.aspectRatio || parametersSchema?.size) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// If model doesn't have width/height parameters, no virtual ratio needed
|
|
19
|
+
if (!parametersSchema?.width || !parametersSchema?.height) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { width, height } = defaultValues;
|
|
24
|
+
|
|
25
|
+
// Ensure we have valid numeric width and height values
|
|
26
|
+
if (typeof width === 'number' && typeof height === 'number' && width > 0 && height > 0) {
|
|
27
|
+
return `${width}:${height}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Default fallback ratio
|
|
31
|
+
return '1:1';
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if a model supports virtual aspect ratio control
|
|
36
|
+
* Virtual aspect ratio is enabled when model has width/height but no native aspect ratio/size controls
|
|
37
|
+
*/
|
|
38
|
+
export const supportsVirtualAspectRatio = (parametersSchema: ModelParamsSchema): boolean => {
|
|
39
|
+
return (
|
|
40
|
+
!parametersSchema?.aspectRatio &&
|
|
41
|
+
!parametersSchema?.size &&
|
|
42
|
+
!!parametersSchema?.width &&
|
|
43
|
+
!!parametersSchema?.height
|
|
44
|
+
);
|
|
45
|
+
};
|