feat: aihubmix painting support imagen (#6525)

* add imagen

* feat: support imagen model

* update proxy notice

---------

Co-authored-by: zhaochenxue <zhaochenxue@bixin.cn>
This commit is contained in:
chenxue 2025-05-27 21:02:02 +08:00 committed by GitHub
parent 2642b8f693
commit 998992fe1f
12 changed files with 167 additions and 23 deletions

View File

@ -827,7 +827,8 @@
"learn_more": "Learn More",
"paint_course": "tutorial",
"prompt_placeholder_edit": "Enter your image description, text drawing uses \"double quotes\" to wrap",
"proxy_required": "Currently, you need to open a proxy to view the generated images, it will be supported in the future",
"prompt_placeholder_en": "Enter your image description, currently Imagen only supports English prompts",
"proxy_required": "Open the proxy and enable “TUN mode” to view generated images or copy them to the browser for opening. In the future, domestic direct connection will be supported",
"image_file_required": "Please upload an image first",
"image_file_retry": "Please re-upload an image first",
"image_placeholder": "No image available",
@ -866,6 +867,11 @@
"portrait": "Portrait",
"landscape": "Landscape"
},
"person_generation_options": {
"allow_all": "Allow all",
"allow_adult": "Allow adult",
"allow_none": "Not allowed"
},
"quality": "Quality",
"moderation": "Moderation",
"background": "Background",
@ -882,7 +888,9 @@
"negative_prompt_tip": "Describe unwanted elements, only for V_1, V_1_TURBO, V_2, and V_2_TURBO",
"magic_prompt_option_tip": "Intelligently enhances prompts for better results",
"style_type_tip": "Image generation style for V_2 and above",
"rendering_speed_tip": "Controls rendering speed vs. quality trade-off, only available for V_3"
"rendering_speed_tip": "Controls rendering speed vs. quality trade-off, only available for V_3",
"person_generation": "Generate person",
"person_generation_tip": "Allow model to generate person images"
},
"edit": {
"image_file": "Edited Image",

View File

@ -825,8 +825,9 @@
"style_type": "スタイル",
"learn_more": "詳しくはこちら",
"prompt_placeholder_edit": "画像の説明を入力します。テキスト描画には '二重引用符' を使用します",
"prompt_placeholder_en": "「英語」の説明を入力します。Imagenは現在、英語のプロンプト語のみをサポートしています",
"paint_course": "チュートリアル",
"proxy_required": "現在、プロキシを開く必要があります。これは、将来サポートされる予定です",
"proxy_required": "打開代理並開啟”TUN模式“查看生成圖片或複製到瀏覽器開啟後續會支持國內直連",
"image_file_required": "画像を先にアップロードしてください",
"image_file_retry": "画像を先にアップロードしてください",
"image_placeholder": "画像がありません",
@ -864,6 +865,11 @@
"portrait": "縦図",
"landscape": "横図"
},
"person_generation_options": {
"allow_all": "許可する",
"allow_adult": "許可する",
"allow_none": "許可しない"
},
"quality": "品質",
"moderation": "敏感度",
"background": "背景",
@ -880,7 +886,9 @@
"negative_prompt_tip": "画像に含めたくない内容を説明します",
"magic_prompt_option_tip": "生成効果を向上させるための提示詞を最適化します",
"style_type_tip": "画像生成スタイル、V_2 以上のバージョンでのみ適用",
"rendering_speed_tip": "レンダリング速度と品質のバランスを調整します。V_3バージョンでのみ利用可能です"
"rendering_speed_tip": "レンダリング速度と品質のバランスを調整します。V_3バージョンでのみ利用可能です",
"person_generation": "人物生成",
"person_generation_tip": "人物画像を生成する"
},
"edit": {
"image_file": "編集画像",

View File

@ -826,8 +826,9 @@
"rendering_speed": "Скорость рендеринга",
"learn_more": "Узнать больше",
"prompt_placeholder_edit": "Введите ваше описание изображения, текстовая отрисовка использует двойные кавычки для обертки",
"prompt_placeholder_en": "Введите” английский “описание изображения, текстовая отрисовка использует двойные кавычки для обертки",
"paint_course": "Руководство / Учебник",
"proxy_required": "Сейчас необходимо открыть прокси для просмотра сгенерированных изображений, в будущем будет поддерживаться прямое соединение",
"proxy_required": "Открыть прокси и включить “TUN режим” для просмотра сгенерированных изображений или скопировать их в браузер для открытия. В будущем будет поддерживаться прямое соединение",
"image_file_required": "Пожалуйста, сначала загрузите изображение",
"image_file_retry": "Пожалуйста, сначала загрузите изображение",
"image_placeholder": "Изображение недоступно",
@ -866,6 +867,11 @@
"portrait": "Портрет",
"landscape": "Пейзаж"
},
"person_generation_options": {
"allow_all": "Разрешено все",
"allow_adult": "Разрешено взрослые",
"allow_none": "Не разрешено"
},
"quality": "Качество",
"moderation": "Сенсорность",
"background": "Фон",
@ -882,7 +888,9 @@
"negative_prompt_tip": "Описывает, что вы не хотите видеть в изображении",
"magic_prompt_option_tip": "Интеллектуально оптимизирует подсказки для улучшения эффекта генерации",
"style_type_tip": "Стиль генерации изображений, доступен только для версий V_2 и выше",
"rendering_speed_tip": "Управляет балансом между скоростью рендеринга и качеством, доступно только для V_3"
"rendering_speed_tip": "Управляет балансом между скоростью рендеринга и качеством, доступно только для V_3",
"person_generation": "Генерация персонажа",
"person_generation_tip": "Разрешить модель генерировать изображения людей"
},
"edit": {
"image_file": "Изображение для редактирования",

View File

@ -827,7 +827,8 @@
"learn_more": "了解更多",
"paint_course": "教程",
"prompt_placeholder_edit": "输入你的图片描述,文本绘制用 \"双引号\" 包裹",
"proxy_required": "目前需要打开代理才能查看生成图片,后续会支持国内直连",
"prompt_placeholder_en": "输入”英文“图片描述,目前 Imagen 仅支持英文提示词",
"proxy_required": "打开代理并开启”TUN模式“查看生成图片或复制到浏览器打开后续会支持国内直连",
"image_file_required": "请先上传图片",
"image_file_retry": "请重新上传图片",
"image_placeholder": "暂无图片",
@ -861,6 +862,11 @@
"transparent": "透明",
"opaque": "不透明"
},
"person_generation_options": {
"allow_all": "允许所有",
"allow_adult": "允许成人",
"allow_none": "不允许"
},
"aspect_ratios": {
"square": "方形",
"portrait": "竖图",
@ -882,7 +888,9 @@
"negative_prompt_tip": "描述不想在图像中出现的元素,仅支持 V_1、V_1_TURBO、V_2 和 V_2_TURBO 版本",
"magic_prompt_option_tip": "智能优化提示词以提升生成效果",
"style_type_tip": "图像生成风格,仅适用于 V_2 及以上版本",
"rendering_speed_tip": "控制渲染速度与质量的平衡,仅适用于 V_3 版本"
"rendering_speed_tip": "控制渲染速度与质量的平衡,仅适用于 V_3 版本",
"person_generation": "生成人物",
"person_generation_tip": "允许模型生成人物图像"
},
"edit": {
"image_file": "编辑的图像",

View File

@ -826,8 +826,9 @@
"style_type": "風格",
"learn_more": "了解更多",
"prompt_placeholder_edit": "輸入你的圖片描述,文本繪製用 '雙引號' 包裹",
"prompt_placeholder_en": "輸入”英文“圖片描述,目前 Imagen 僅支持英文提示詞",
"paint_course": "教程",
"proxy_required": "目前需要打開代理才能查看生成圖片,後續會支持國內直連",
"proxy_required": "打開代理並開啟”TUN模式“查看生成圖片或複製到瀏覽器開啟,後續會支持國內直連",
"image_file_required": "請先上傳圖片",
"image_file_retry": "請重新上傳圖片",
"image_placeholder": "無圖片",
@ -866,6 +867,11 @@
"portrait": "豎圖",
"landscape": "橫圖"
},
"person_generation_options": {
"allow_all": "允許所有",
"allow_adult": "允許成人",
"allow_none": "不允許"
},
"quality": "品質",
"moderation": "敏感度",
"background": "背景",
@ -882,7 +888,9 @@
"negative_prompt_tip": "描述不想在圖像中出現的內容",
"magic_prompt_option_tip": "智能優化生成效果的提示詞",
"style_type_tip": "圖像生成風格,僅適用於 V_2 及以上版本",
"rendering_speed_tip": "控制渲染速度與品質之間的平衡僅適用於V_3版本"
"rendering_speed_tip": "控制渲染速度與品質之間的平衡僅適用於V_3版本",
"person_generation": "人物生成",
"person_generation_tip": "允許模型生成人物圖像"
},
"edit": {
"image_file": "編輯圖像",

View File

@ -11,6 +11,7 @@ import { usePaintings } from '@renderer/hooks/usePaintings'
import { useAllProviders } from '@renderer/hooks/useProvider'
import { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings'
import AiProvider from '@renderer/providers/AiProvider'
import FileManager from '@renderer/services/FileManager'
import { translateText } from '@renderer/services/TranslateService'
import { useAppDispatch } from '@renderer/store'
@ -68,7 +69,6 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
const modeOptions = [
{ label: t('paintings.mode.generate'), value: 'generate' },
// { label: t('paintings.mode.edit'), value: 'edit' },
{ label: t('paintings.mode.remix'), value: 'remix' },
{ label: t('paintings.mode.upscale'), value: 'upscale' }
]
@ -177,7 +177,28 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
try {
if (mode === 'generate') {
if (painting.model === 'V_3') {
if (painting.model.startsWith('imagen-')) {
const AI = new AiProvider(aihubmixProvider)
const base64s = await AI.generateImage({
prompt,
model: painting.model,
config: {
aspectRatio: painting.aspectRatio?.replace('ASPECT_', '').replace('_', ':'),
numberOfImages: painting.model.startsWith('imagen-4.0-ultra-generate-exp') ? 1 : painting.numberOfImages,
personGeneration: painting.personGeneration
}
})
if (base64s?.length > 0) {
const validFiles = await Promise.all(
base64s.map(async (base64) => {
return await window.api.file.saveBase64Image(base64)
})
)
await FileManager.addFiles(validFiles)
updatePaintingState({ files: validFiles, urls: validFiles.map((file) => file.name) })
}
return
} else if (painting.model === 'V_3') {
// V3 API uses different endpoint and parameters format
const formData = new FormData()
formData.append('prompt', prompt)
@ -825,7 +846,13 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
value={painting.prompt}
spellCheck={false}
onChange={(e) => updatePaintingState({ prompt: e.target.value })}
placeholder={isTranslating ? t('paintings.translating') : t('paintings.prompt_placeholder_edit')}
placeholder={
isTranslating
? t('paintings.translating')
: painting.model?.startsWith('imagen-')
? t('paintings.prompt_placeholder_en')
: t('paintings.prompt_placeholder_edit')
}
onKeyDown={handleKeyDown}
/>
<Toolbar>

View File

@ -4,6 +4,7 @@ import {
ASPECT_RATIOS,
BACKGROUND_OPTIONS,
MODERATION_OPTIONS,
PERSON_GENERATION_OPTIONS,
QUALITY_OPTIONS,
RENDERING_SPEED_OPTIONS,
STYLE_TYPES,
@ -67,6 +68,15 @@ export const createModeConfigs = (): Record<AihubmixMode, ConfigItem[]> => {
title: 'OpenAI',
options: [{ label: 'gpt-image-1', value: 'gpt-image-1' }]
},
{
label: 'Gemini',
title: 'Gemini',
options: [
{ label: 'imagen-4.0-preview', value: 'imagen-4.0-generate-preview-05-20' },
{ label: 'imagen-4.0-ultra-exp', value: 'imagen-4.0-ultra-generate-exp-05-20' },
{ label: 'imagen-3.0', value: 'imagen-3.0-generate-001' }
]
},
{
label: 'ideogram',
title: 'ideogram',
@ -186,6 +196,40 @@ export const createModeConfigs = (): Record<AihubmixMode, ConfigItem[]> => {
options: BACKGROUND_OPTIONS,
initialValue: 'auto',
condition: (painting) => painting.model === 'gpt-image-1'
},
{
type: 'slider',
key: 'numberOfImages',
title: 'paintings.number_images',
tooltip: 'paintings.generate.number_images_tip',
min: 1,
max: 4,
initialValue: 4,
condition: (painting) =>
Boolean(painting.model?.startsWith('imagen-') && painting.model !== 'imagen-4.0-ultra-generate-exp-05-20')
},
{
type: 'select',
key: 'aspectRatio',
title: 'paintings.aspect_ratio',
options: [
{ label: '1:1', value: 'ASPECT_1_1' },
{ label: '3:4', value: 'ASPECT_3_4' },
{ label: '4:3', value: 'ASPECT_4_3' },
{ label: '9:16', value: 'ASPECT_9_16' },
{ label: '16:9', value: 'ASPECT_16_9' }
],
initialValue: 'ASPECT_1_1',
condition: (painting) => Boolean(painting.model?.startsWith('imagen-'))
},
{
type: 'select',
key: 'personGeneration',
title: 'paintings.generate.person_generation',
tooltip: 'paintings.generate.person_generation_tip',
options: PERSON_GENERATION_OPTIONS,
initialValue: 'ALLOW_ALL',
condition: (painting) => Boolean(painting.model?.startsWith('imagen-'))
}
],
remix: [
@ -340,5 +384,6 @@ export const DEFAULT_PAINTING: PaintingAction = {
background: 'auto',
quality: 'auto',
moderation: 'auto',
n: 1
n: 1,
numberOfImages: 4
}

View File

@ -141,3 +141,9 @@ export const BACKGROUND_OPTIONS = [
{ label: 'paintings.background_options.transparent', value: 'transparent' },
{ label: 'paintings.background_options.opaque', value: 'opaque' }
]
export const PERSON_GENERATION_OPTIONS = [
{ label: 'paintings.person_generation_options.allow_all', value: 'ALLOW_ALL' },
{ label: 'paintings.person_generation_options.allow_adult', value: 'ALLOW_ADULT' },
{ label: 'paintings.person_generation_options.allow_none', value: 'DONT_ALLOW' }
]

View File

@ -43,8 +43,8 @@ export default class AihubmixProvider extends BaseProvider {
if (id.startsWith('claude')) {
return this.providers.get('claude')!
}
// gemini开头 且不以-nothink、-search结尾
if (id.startsWith('gemini') && !id.endsWith('-nothink') && !id.endsWith('-search')) {
// gemini开头 或 imagen开头 且不以-nothink、-search结尾
if ((id.startsWith('gemini') || id.startsWith('imagen')) && !id.endsWith('-nothink') && !id.endsWith('-search')) {
return this.providers.get('gemini')!
}
if (isOpenAILLMModel(model)) {
@ -64,7 +64,9 @@ export default class AihubmixProvider extends BaseProvider {
}
public async generateImage(params: any): Promise<string[]> {
return this.defaultProvider.generateImage(params)
return this.getProvider({
id: params.model
} as unknown as Model).generateImage(params)
}
public async generateImageByChat(params: any): Promise<void> {

View File

@ -6,6 +6,7 @@ import {
FunctionCall,
GenerateContentConfig,
GenerateContentResponse,
GenerateImagesParameters,
GoogleGenAI,
HarmBlockThreshold,
HarmCategory,
@ -887,10 +888,30 @@ export default class GeminiProvider extends BaseProvider {
/**
* Generate an image
* @returns The generated image
* @param params - The parameters for image generation
* @returns The generated image URLs
*/
public async generateImage(): Promise<string[]> {
return []
public async generateImage(params: GenerateImagesParameters): Promise<string[]> {
try {
console.log('[GeminiProvider] generateImage params:', params)
const response = await this.sdk.models.generateImages(params)
if (!response.generatedImages || response.generatedImages.length === 0) {
return []
}
const images = response.generatedImages
.filter((image) => image.image?.imageBytes)
.map((image) => {
const dataPrefix = `data:${image.image?.mimeType || 'image/png'};base64,`
return dataPrefix + image.image?.imageBytes
})
// console.log(response?.generatedImages?.[0]?.image?.imageBytes);
return images
} catch (error) {
console.error('[generateImage] error:', error)
throw error
}
}
/**

View File

@ -1,3 +1,4 @@
import { GenerateImagesParameters } from '@google/genai'
import BaseProvider from '@renderer/providers/AiProvider/BaseProvider'
import ProviderFactory from '@renderer/providers/AiProvider/ProviderFactory'
import type { Assistant, GenerateImageParams, MCPTool, Model, Provider, Suggestion } from '@renderer/types'
@ -70,8 +71,8 @@ export default class AiProvider {
return this.sdk.getApiKey()
}
public async generateImage(params: GenerateImageParams): Promise<string[]> {
return this.sdk.generateImage(params)
public async generateImage(params: GenerateImageParams | GenerateImagesParameters): Promise<string[]> {
return this.sdk.generateImage(params as GenerateImageParams)
}
public async generateImageByChat({

View File

@ -1,5 +1,5 @@
import type { WebSearchResultBlock } from '@anthropic-ai/sdk/resources'
import type { GroundingMetadata } from '@google/genai'
import type { GenerateImagesConfig, GroundingMetadata } from '@google/genai'
import type OpenAI from 'openai'
import type { CSSProperties } from 'react'
@ -214,6 +214,8 @@ export interface GeneratePainting extends PaintingParams {
n?: number
size?: string
background?: string
personGeneration?: GenerateImagesConfig['personGeneration']
numberOfImages?: number
}
export interface EditPainting extends PaintingParams {