mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-17 21:08:36 +00:00
💄 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:
@@ -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',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 生成文本的最大长度
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
@@ -54,6 +54,12 @@ export default {
|
||||
desc: '基于 Claude Thinking 机制限制(<1>了解更多</1>),开启后将自动禁用历史消息数限制',
|
||||
title: '开启深度思考',
|
||||
},
|
||||
imageAspectRatio: {
|
||||
title: '图片宽高比',
|
||||
},
|
||||
imageResolution: {
|
||||
title: '图片分辨率',
|
||||
},
|
||||
reasoningBudgetToken: {
|
||||
title: '思考消耗 Token',
|
||||
},
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user