Skip to content

Commit a197b4b

Browse files
authored
💄 style: add image aspect ratio and resolution settings for Nano Banana Pro (#10430)
✨ feat: add image aspect ratio and resolution settings for AI models
1 parent b6dca90 commit a197b4b

File tree

10 files changed

+186
-1
lines changed

10 files changed

+186
-1
lines changed

packages/model-bank/src/aiModels/google.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ const googleChatModels: AIChatModelCard[] = [
197197
},
198198
releasedAt: '2025-11-20',
199199
settings: {
200+
extendParams: ['imageAspectRatio', 'imageResolution'],
200201
searchImpl: 'params',
201202
searchProvider: 'google',
202203
},
@@ -446,6 +447,9 @@ const googleChatModels: AIChatModelCard[] = [
446447
],
447448
},
448449
releasedAt: '2025-08-26',
450+
settings: {
451+
extendParams: ['imageAspectRatio'],
452+
},
449453
type: 'chat',
450454
},
451455
{
@@ -469,6 +473,9 @@ const googleChatModels: AIChatModelCard[] = [
469473
],
470474
},
471475
releasedAt: '2025-08-26',
476+
settings: {
477+
extendParams: ['imageAspectRatio'],
478+
},
472479
type: 'chat',
473480
},
474481
{

packages/model-bank/src/types/aiModel.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ export type ExtendParamsType =
243243
| 'thinking'
244244
| 'thinkingBudget'
245245
| 'thinkingLevel'
246+
| 'imageAspectRatio'
247+
| 'imageResolution'
246248
| 'urlContext';
247249

248250
export interface AiModelSettings {

packages/model-runtime/src/providers/google/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ export class LobeGoogleAI implements LobeRuntimeAI {
201201
async chat(rawPayload: ChatStreamPayload, options?: ChatMethodOptions) {
202202
try {
203203
const payload = this.buildPayload(rawPayload);
204-
const { model, thinkingBudget, thinkingLevel } = payload;
204+
const { model, thinkingBudget, thinkingLevel, imageAspectRatio, imageResolution } = payload;
205205

206206
// https://ai.google.dev/gemini-api/docs/thinking#set-budget
207207
const resolvedThinkingBudget = resolveModelThinkingBudget(model, thinkingBudget);
@@ -242,6 +242,13 @@ export class LobeGoogleAI implements LobeRuntimeAI {
242242

243243
const config: GenerateContentConfig = {
244244
abortSignal: originalSignal,
245+
imageConfig:
246+
modelsWithModalities.has(model) && imageAspectRatio
247+
? {
248+
aspectRatio: imageAspectRatio,
249+
imageSize: imageResolution,
250+
}
251+
: undefined,
245252
maxOutputTokens: payload.max_tokens,
246253
responseModalities: modelsWithModalities.has(model) ? ['Text', 'Image'] : undefined,
247254
// avoid wide sensitive words

packages/model-runtime/src/types/chat.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ export interface ChatStreamPayload {
7474
* @default 0
7575
*/
7676
frequency_penalty?: number;
77+
/**
78+
* @title Image aspect ratio for image generation
79+
*/
80+
imageAspectRatio?: string;
81+
/**
82+
* @title Image resolution for image generation (e.g., '1K', '2K', '4K')
83+
*/
84+
imageResolution?: '1K' | '2K' | '4K';
7785
/**
7886
* @title 生成文本的最大长度
7987
*/

packages/types/src/agent/chatConfig.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ export interface LobeAgentChatConfig {
4040
thinking?: 'disabled' | 'auto' | 'enabled';
4141
thinkingLevel?: 'low' | 'high';
4242
thinkingBudget?: number;
43+
/**
44+
* Image aspect ratio for image generation models
45+
*/
46+
imageAspectRatio?: string;
47+
/**
48+
* Image resolution for image generation models
49+
*/
50+
imageResolution?: '1K' | '2K' | '4K';
4351
/**
4452
* Disable context caching
4553
*/
@@ -80,6 +88,8 @@ export const AgentChatConfigSchema = z.object({
8088
gpt5ReasoningEffort: z.enum(['minimal', 'low', 'medium', 'high']).optional(),
8189
gpt5_1ReasoningEffort: z.enum(['none', 'low', 'medium', 'high']).optional(),
8290
historyCount: z.number().optional(),
91+
imageAspectRatio: z.string().optional(),
92+
imageResolution: z.enum(['1K', '2K', '4K']).optional(),
8393
reasoningBudgetToken: z.number().optional(),
8494
reasoningEffort: z.enum(['low', 'medium', 'high']).optional(),
8595
searchFCModel: z

src/features/ChatInput/ActionBar/Model/ControlsForm.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { aiModelSelectors, useAiInfraStore } from '@/store/aiInfra';
1313
import ContextCachingSwitch from './ContextCachingSwitch';
1414
import GPT5ReasoningEffortSlider from './GPT5ReasoningEffortSlider';
1515
import GPT51ReasoningEffortSlider from './GPT51ReasoningEffortSlider';
16+
import ImageAspectRatioSelect from './ImageAspectRatioSelect';
17+
import ImageResolutionSlider from './ImageResolutionSlider';
1618
import ReasoningEffortSlider from './ReasoningEffortSlider';
1719
import ReasoningTokenSlider from './ReasoningTokenSlider';
1820
import TextVerbositySlider from './TextVerbositySlider';
@@ -188,6 +190,28 @@ const ControlsForm = memo(() => {
188190
paddingBottom: 0,
189191
},
190192
},
193+
{
194+
children: <ImageAspectRatioSelect />,
195+
label: t('extendParams.imageAspectRatio.title'),
196+
layout: 'horizontal',
197+
minWidth: undefined,
198+
name: 'imageAspectRatio',
199+
style: {
200+
paddingBottom: 0,
201+
},
202+
tag: 'aspectRatio',
203+
},
204+
{
205+
children: <ImageResolutionSlider />,
206+
label: t('extendParams.imageResolution.title'),
207+
layout: 'horizontal',
208+
minWidth: undefined,
209+
name: 'imageResolution',
210+
style: {
211+
paddingBottom: 0,
212+
},
213+
tag: 'imageSize',
214+
},
191215
].filter(Boolean) as FormItemProps[];
192216

193217
return (
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Select } from 'antd';
2+
import { memo, useCallback, useMemo } from 'react';
3+
4+
import { useAgentStore } from '@/store/agent';
5+
import { agentChatConfigSelectors } from '@/store/agent/selectors';
6+
7+
const NANO_BANANA_ASPECT_RATIOS = [
8+
'1:1', // 1024x1024 / 2048x2048 / 4096x4096
9+
'2:3', // 848x1264 / 1696x2528 / 3392x5056
10+
'3:2', // 1264x848 / 2528x1696 / 5056x3392
11+
'3:4', // 896x1200 / 1792x2400 / 3584x4800
12+
'4:3', // 1200x896 / 2400x1792 / 4800x3584
13+
'4:5', // 928x1152 / 1856x2304 / 3712x4608
14+
'5:4', // 1152x928 / 2304x1856 / 4608x3712
15+
'9:16', // 768x1376 / 1536x2752 / 3072x5504
16+
'16:9', // 1376x768 / 2752x1536 / 5504x3072
17+
'21:9', // 1584x672 / 3168x1344 / 6336x2688
18+
];
19+
20+
const ImageAspectRatioSelect = memo(() => {
21+
const [config, updateAgentChatConfig] = useAgentStore((s) => [
22+
agentChatConfigSelectors.currentChatConfig(s),
23+
s.updateAgentChatConfig,
24+
]);
25+
26+
const imageAspectRatio = config.imageAspectRatio || '1:1';
27+
28+
const options = useMemo(
29+
() =>
30+
NANO_BANANA_ASPECT_RATIOS.map((ratio) => ({
31+
label: ratio,
32+
value: ratio,
33+
})),
34+
[],
35+
);
36+
37+
const updateAspectRatio = useCallback(
38+
(value: string) => {
39+
updateAgentChatConfig({ imageAspectRatio: value });
40+
},
41+
[updateAgentChatConfig],
42+
);
43+
44+
return (
45+
<Select
46+
onChange={updateAspectRatio}
47+
options={options}
48+
style={{ height: 32, marginRight: 10, width: 75 }}
49+
value={imageAspectRatio}
50+
/>
51+
);
52+
});
53+
54+
export default ImageAspectRatioSelect;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Slider } from 'antd';
2+
import { memo, useCallback } from 'react';
3+
import { Flexbox } from 'react-layout-kit';
4+
5+
import { useAgentStore } from '@/store/agent';
6+
import { agentChatConfigSelectors } from '@/store/agent/selectors';
7+
8+
const IMAGE_RESOLUTIONS = ['1K', '2K', '4K'] as const;
9+
type ImageResolution = (typeof IMAGE_RESOLUTIONS)[number];
10+
11+
const ImageResolutionSlider = memo(() => {
12+
const [config, updateAgentChatConfig] = useAgentStore((s) => [
13+
agentChatConfigSelectors.currentChatConfig(s),
14+
s.updateAgentChatConfig,
15+
]);
16+
17+
const imageResolution = (config.imageResolution as ImageResolution) || '1K';
18+
19+
const marks = {
20+
0: '1K',
21+
1: '2K',
22+
2: '4K',
23+
};
24+
25+
const indexValue = IMAGE_RESOLUTIONS.indexOf(imageResolution);
26+
const currentValue = indexValue === -1 ? 0 : indexValue;
27+
28+
const updateResolution = useCallback(
29+
(value: number) => {
30+
const resolution = IMAGE_RESOLUTIONS[value];
31+
updateAgentChatConfig({ imageResolution: resolution });
32+
},
33+
[updateAgentChatConfig],
34+
);
35+
36+
return (
37+
<Flexbox
38+
align={'center'}
39+
gap={12}
40+
horizontal
41+
paddingInline={'0 20px'}
42+
style={{ minWidth: 150, width: '100%' }}
43+
>
44+
<Flexbox flex={1}>
45+
<Slider
46+
marks={marks}
47+
max={2}
48+
min={0}
49+
onChange={updateResolution}
50+
step={1}
51+
tooltip={{ open: false }}
52+
value={currentValue}
53+
/>
54+
</Flexbox>
55+
</Flexbox>
56+
);
57+
});
58+
59+
export default ImageResolutionSlider;

src/locales/default/chat.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ export default {
5454
desc: '基于 Claude Thinking 机制限制(<1>了解更多</1>),开启后将自动禁用历史消息数限制',
5555
title: '开启深度思考',
5656
},
57+
imageAspectRatio: {
58+
title: '图片宽高比',
59+
},
60+
imageResolution: {
61+
title: '图片分辨率',
62+
},
5763
reasoningBudgetToken: {
5864
title: '思考消耗 Token',
5965
},

src/services/chat/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,14 @@ class ChatService {
203203
if (modelExtendParams!.includes('urlContext') && chatConfig.urlContext) {
204204
extendParams.urlContext = chatConfig.urlContext;
205205
}
206+
207+
if (modelExtendParams!.includes('imageAspectRatio') && chatConfig.imageAspectRatio) {
208+
extendParams.imageAspectRatio = chatConfig.imageAspectRatio;
209+
}
210+
211+
if (modelExtendParams!.includes('imageResolution') && chatConfig.imageResolution) {
212+
extendParams.imageResolution = chatConfig.imageResolution;
213+
}
206214
}
207215

208216
return this.getChatCompletion(

0 commit comments

Comments
 (0)