💄 style: add image aspect ratio and resolution settings for Nano Banana Pro (#10430)

 feat: add image aspect ratio and resolution settings for AI models
This commit is contained in:
sxjeru
2025-11-26 19:40:52 +08:00
committed by GitHub
parent b6dca900e3
commit a197b4b433
10 changed files with 186 additions and 1 deletions
@@ -197,6 +197,7 @@ const googleChatModels: AIChatModelCard[] = [
},
releasedAt: '2025-11-20',
settings: {
extendParams: ['imageAspectRatio', 'imageResolution'],
searchImpl: 'params',
searchProvider: 'google',
},
@@ -446,6 +447,9 @@ const googleChatModels: AIChatModelCard[] = [
],
},
releasedAt: '2025-08-26',
settings: {
extendParams: ['imageAspectRatio'],
},
type: 'chat',
},
{
@@ -469,6 +473,9 @@ const googleChatModels: AIChatModelCard[] = [
],
},
releasedAt: '2025-08-26',
settings: {
extendParams: ['imageAspectRatio'],
},
type: 'chat',
},
{
+2
View File
@@ -243,6 +243,8 @@ export type ExtendParamsType =
| 'thinking'
| 'thinkingBudget'
| 'thinkingLevel'
| 'imageAspectRatio'
| 'imageResolution'
| 'urlContext';
export interface AiModelSettings {
@@ -201,7 +201,7 @@ export class LobeGoogleAI implements LobeRuntimeAI {
async chat(rawPayload: ChatStreamPayload, options?: ChatMethodOptions) {
try {
const payload = this.buildPayload(rawPayload);
const { model, thinkingBudget, thinkingLevel } = payload;
const { model, thinkingBudget, thinkingLevel, imageAspectRatio, imageResolution } = payload;
// https://ai.google.dev/gemini-api/docs/thinking#set-budget
const resolvedThinkingBudget = resolveModelThinkingBudget(model, thinkingBudget);
@@ -242,6 +242,13 @@ export class LobeGoogleAI implements LobeRuntimeAI {
const config: GenerateContentConfig = {
abortSignal: originalSignal,
imageConfig:
modelsWithModalities.has(model) && imageAspectRatio
? {
aspectRatio: imageAspectRatio,
imageSize: imageResolution,
}
: undefined,
maxOutputTokens: payload.max_tokens,
responseModalities: modelsWithModalities.has(model) ? ['Text', 'Image'] : undefined,
// avoid wide sensitive words
+8
View File
@@ -74,6 +74,14 @@ export interface ChatStreamPayload {
* @default 0
*/
frequency_penalty?: number;
/**
* @title Image aspect ratio for image generation
*/
imageAspectRatio?: string;
/**
* @title Image resolution for image generation (e.g., '1K', '2K', '4K')
*/
imageResolution?: '1K' | '2K' | '4K';
/**
* @title 生成文本的最大长度
*/
+10
View File
@@ -40,6 +40,14 @@ export interface LobeAgentChatConfig {
thinking?: 'disabled' | 'auto' | 'enabled';
thinkingLevel?: 'low' | 'high';
thinkingBudget?: number;
/**
* Image aspect ratio for image generation models
*/
imageAspectRatio?: string;
/**
* Image resolution for image generation models
*/
imageResolution?: '1K' | '2K' | '4K';
/**
* Disable context caching
*/
@@ -80,6 +88,8 @@ export const AgentChatConfigSchema = z.object({
gpt5ReasoningEffort: z.enum(['minimal', 'low', 'medium', 'high']).optional(),
gpt5_1ReasoningEffort: z.enum(['none', 'low', 'medium', 'high']).optional(),
historyCount: z.number().optional(),
imageAspectRatio: z.string().optional(),
imageResolution: z.enum(['1K', '2K', '4K']).optional(),
reasoningBudgetToken: z.number().optional(),
reasoningEffort: z.enum(['low', 'medium', 'high']).optional(),
searchFCModel: z
@@ -13,6 +13,8 @@ import { aiModelSelectors, useAiInfraStore } from '@/store/aiInfra';
import ContextCachingSwitch from './ContextCachingSwitch';
import GPT5ReasoningEffortSlider from './GPT5ReasoningEffortSlider';
import GPT51ReasoningEffortSlider from './GPT51ReasoningEffortSlider';
import ImageAspectRatioSelect from './ImageAspectRatioSelect';
import ImageResolutionSlider from './ImageResolutionSlider';
import ReasoningEffortSlider from './ReasoningEffortSlider';
import ReasoningTokenSlider from './ReasoningTokenSlider';
import TextVerbositySlider from './TextVerbositySlider';
@@ -188,6 +190,28 @@ const ControlsForm = memo(() => {
paddingBottom: 0,
},
},
{
children: <ImageAspectRatioSelect />,
label: t('extendParams.imageAspectRatio.title'),
layout: 'horizontal',
minWidth: undefined,
name: 'imageAspectRatio',
style: {
paddingBottom: 0,
},
tag: 'aspectRatio',
},
{
children: <ImageResolutionSlider />,
label: t('extendParams.imageResolution.title'),
layout: 'horizontal',
minWidth: undefined,
name: 'imageResolution',
style: {
paddingBottom: 0,
},
tag: 'imageSize',
},
].filter(Boolean) as FormItemProps[];
return (
@@ -0,0 +1,54 @@
import { Select } from 'antd';
import { memo, useCallback, useMemo } from 'react';
import { useAgentStore } from '@/store/agent';
import { agentChatConfigSelectors } from '@/store/agent/selectors';
const NANO_BANANA_ASPECT_RATIOS = [
'1:1', // 1024x1024 / 2048x2048 / 4096x4096
'2:3', // 848x1264 / 1696x2528 / 3392x5056
'3:2', // 1264x848 / 2528x1696 / 5056x3392
'3:4', // 896x1200 / 1792x2400 / 3584x4800
'4:3', // 1200x896 / 2400x1792 / 4800x3584
'4:5', // 928x1152 / 1856x2304 / 3712x4608
'5:4', // 1152x928 / 2304x1856 / 4608x3712
'9:16', // 768x1376 / 1536x2752 / 3072x5504
'16:9', // 1376x768 / 2752x1536 / 5504x3072
'21:9', // 1584x672 / 3168x1344 / 6336x2688
];
const ImageAspectRatioSelect = memo(() => {
const [config, updateAgentChatConfig] = useAgentStore((s) => [
agentChatConfigSelectors.currentChatConfig(s),
s.updateAgentChatConfig,
]);
const imageAspectRatio = config.imageAspectRatio || '1:1';
const options = useMemo(
() =>
NANO_BANANA_ASPECT_RATIOS.map((ratio) => ({
label: ratio,
value: ratio,
})),
[],
);
const updateAspectRatio = useCallback(
(value: string) => {
updateAgentChatConfig({ imageAspectRatio: value });
},
[updateAgentChatConfig],
);
return (
<Select
onChange={updateAspectRatio}
options={options}
style={{ height: 32, marginRight: 10, width: 75 }}
value={imageAspectRatio}
/>
);
});
export default ImageAspectRatioSelect;
@@ -0,0 +1,59 @@
import { Slider } from 'antd';
import { memo, useCallback } from 'react';
import { Flexbox } from 'react-layout-kit';
import { useAgentStore } from '@/store/agent';
import { agentChatConfigSelectors } from '@/store/agent/selectors';
const IMAGE_RESOLUTIONS = ['1K', '2K', '4K'] as const;
type ImageResolution = (typeof IMAGE_RESOLUTIONS)[number];
const ImageResolutionSlider = memo(() => {
const [config, updateAgentChatConfig] = useAgentStore((s) => [
agentChatConfigSelectors.currentChatConfig(s),
s.updateAgentChatConfig,
]);
const imageResolution = (config.imageResolution as ImageResolution) || '1K';
const marks = {
0: '1K',
1: '2K',
2: '4K',
};
const indexValue = IMAGE_RESOLUTIONS.indexOf(imageResolution);
const currentValue = indexValue === -1 ? 0 : indexValue;
const updateResolution = useCallback(
(value: number) => {
const resolution = IMAGE_RESOLUTIONS[value];
updateAgentChatConfig({ imageResolution: resolution });
},
[updateAgentChatConfig],
);
return (
<Flexbox
align={'center'}
gap={12}
horizontal
paddingInline={'0 20px'}
style={{ minWidth: 150, width: '100%' }}
>
<Flexbox flex={1}>
<Slider
marks={marks}
max={2}
min={0}
onChange={updateResolution}
step={1}
tooltip={{ open: false }}
value={currentValue}
/>
</Flexbox>
</Flexbox>
);
});
export default ImageResolutionSlider;
+6
View File
@@ -54,6 +54,12 @@ export default {
desc: '基于 Claude Thinking 机制限制(<1>了解更多</1>),开启后将自动禁用历史消息数限制',
title: '开启深度思考',
},
imageAspectRatio: {
title: '图片宽高比',
},
imageResolution: {
title: '图片分辨率',
},
reasoningBudgetToken: {
title: '思考消耗 Token',
},
+8
View File
@@ -203,6 +203,14 @@ class ChatService {
if (modelExtendParams!.includes('urlContext') && chatConfig.urlContext) {
extendParams.urlContext = chatConfig.urlContext;
}
if (modelExtendParams!.includes('imageAspectRatio') && chatConfig.imageAspectRatio) {
extendParams.imageAspectRatio = chatConfig.imageAspectRatio;
}
if (modelExtendParams!.includes('imageResolution') && chatConfig.imageResolution) {
extendParams.imageResolution = chatConfig.imageResolution;
}
}
return this.getChatCompletion(