mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-04 11:49:02 +08:00
feat: painting aihubmix support model: gpt-image-1 (#6486)
* update select style * add openai painting * support base64 response * update config * fix upload preview bug * fix remix default model * fix history data * feat: optimize structure * fix history data --------- Co-authored-by: zhaochenxue <zhaochenxue@bixin.cn>
This commit is contained in:
parent
e475ba0f95
commit
4eaf6fdf15
@ -111,6 +111,7 @@ export enum IpcChannel {
|
|||||||
File_WriteWithId = 'file:writeWithId',
|
File_WriteWithId = 'file:writeWithId',
|
||||||
File_SaveImage = 'file:saveImage',
|
File_SaveImage = 'file:saveImage',
|
||||||
File_Base64Image = 'file:base64Image',
|
File_Base64Image = 'file:base64Image',
|
||||||
|
File_SaveBase64Image = 'file:saveBase64Image',
|
||||||
File_Download = 'file:download',
|
File_Download = 'file:download',
|
||||||
File_Copy = 'file:copy',
|
File_Copy = 'file:copy',
|
||||||
File_BinaryImage = 'file:binaryImage',
|
File_BinaryImage = 'file:binaryImage',
|
||||||
|
|||||||
@ -249,6 +249,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|||||||
ipcMain.handle(IpcChannel.File_WriteWithId, fileManager.writeFileWithId)
|
ipcMain.handle(IpcChannel.File_WriteWithId, fileManager.writeFileWithId)
|
||||||
ipcMain.handle(IpcChannel.File_SaveImage, fileManager.saveImage)
|
ipcMain.handle(IpcChannel.File_SaveImage, fileManager.saveImage)
|
||||||
ipcMain.handle(IpcChannel.File_Base64Image, fileManager.base64Image)
|
ipcMain.handle(IpcChannel.File_Base64Image, fileManager.base64Image)
|
||||||
|
ipcMain.handle(IpcChannel.File_SaveBase64Image, fileManager.saveBase64Image)
|
||||||
ipcMain.handle(IpcChannel.File_Base64File, fileManager.base64File)
|
ipcMain.handle(IpcChannel.File_Base64File, fileManager.base64File)
|
||||||
ipcMain.handle(IpcChannel.File_Download, fileManager.downloadFile)
|
ipcMain.handle(IpcChannel.File_Download, fileManager.downloadFile)
|
||||||
ipcMain.handle(IpcChannel.File_Copy, fileManager.copyFile)
|
ipcMain.handle(IpcChannel.File_Copy, fileManager.copyFile)
|
||||||
|
|||||||
@ -268,6 +268,51 @@ class FileStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public saveBase64Image = async (_: Electron.IpcMainInvokeEvent, base64Data: string): Promise<FileType> => {
|
||||||
|
try {
|
||||||
|
if (!base64Data) {
|
||||||
|
throw new Error('Base64 data is required')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除 base64 头部信息(如果存在)
|
||||||
|
const base64String = base64Data.replace(/^data:.*;base64,/, '')
|
||||||
|
const buffer = Buffer.from(base64String, 'base64')
|
||||||
|
const uuid = uuidv4()
|
||||||
|
const ext = '.png'
|
||||||
|
const destPath = path.join(this.storageDir, uuid + ext)
|
||||||
|
|
||||||
|
logger.info('[FileStorage] Saving base64 image:', {
|
||||||
|
storageDir: this.storageDir,
|
||||||
|
destPath,
|
||||||
|
bufferSize: buffer.length
|
||||||
|
})
|
||||||
|
|
||||||
|
// 确保目录存在
|
||||||
|
if (!fs.existsSync(this.storageDir)) {
|
||||||
|
fs.mkdirSync(this.storageDir, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.promises.writeFile(destPath, buffer)
|
||||||
|
|
||||||
|
const fileMetadata: FileType = {
|
||||||
|
id: uuid,
|
||||||
|
origin_name: uuid + ext,
|
||||||
|
name: uuid + ext,
|
||||||
|
path: destPath,
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
size: buffer.length,
|
||||||
|
ext: ext.slice(1),
|
||||||
|
type: getFileType(ext),
|
||||||
|
count: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileMetadata
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[FileStorage] Failed to save base64 image:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public base64File = async (_: Electron.IpcMainInvokeEvent, id: string): Promise<{ data: string; mime: string }> => {
|
public base64File = async (_: Electron.IpcMainInvokeEvent, id: string): Promise<{ data: string; mime: string }> => {
|
||||||
const filePath = path.join(this.storageDir, id)
|
const filePath = path.join(this.storageDir, id)
|
||||||
const buffer = await fs.promises.readFile(filePath)
|
const buffer = await fs.promises.readFile(filePath)
|
||||||
|
|||||||
@ -76,7 +76,9 @@ const api = {
|
|||||||
selectFolder: () => ipcRenderer.invoke(IpcChannel.File_SelectFolder),
|
selectFolder: () => ipcRenderer.invoke(IpcChannel.File_SelectFolder),
|
||||||
saveImage: (name: string, data: string) => ipcRenderer.invoke(IpcChannel.File_SaveImage, name, data),
|
saveImage: (name: string, data: string) => ipcRenderer.invoke(IpcChannel.File_SaveImage, name, data),
|
||||||
base64Image: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_Base64Image, fileId),
|
base64Image: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_Base64Image, fileId),
|
||||||
download: (url: string, isUseContentType?: boolean) => ipcRenderer.invoke(IpcChannel.File_Download, url, isUseContentType),
|
saveBase64Image: (data: string) => ipcRenderer.invoke(IpcChannel.File_SaveBase64Image, data),
|
||||||
|
download: (url: string, isUseContentType?: boolean) =>
|
||||||
|
ipcRenderer.invoke(IpcChannel.File_Download, url, isUseContentType),
|
||||||
copy: (fileId: string, destPath: string) => ipcRenderer.invoke(IpcChannel.File_Copy, fileId, destPath),
|
copy: (fileId: string, destPath: string) => ipcRenderer.invoke(IpcChannel.File_Copy, fileId, destPath),
|
||||||
binaryImage: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_BinaryImage, fileId),
|
binaryImage: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_BinaryImage, fileId),
|
||||||
base64File: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_Base64File, fileId),
|
base64File: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_Base64File, fileId),
|
||||||
|
|||||||
@ -820,12 +820,12 @@
|
|||||||
"seed_desc_tip": "The same seed and prompt can generate similar images, setting -1 will generate different results each time",
|
"seed_desc_tip": "The same seed and prompt can generate similar images, setting -1 will generate different results each time",
|
||||||
"title": "Images",
|
"title": "Images",
|
||||||
"magic_prompt_option": "Magic Prompt",
|
"magic_prompt_option": "Magic Prompt",
|
||||||
"model": "Model Version",
|
"model": "Model",
|
||||||
"aspect_ratio": "Aspect Ratio",
|
"aspect_ratio": "Aspect Ratio",
|
||||||
"style_type": "Style",
|
"style_type": "Style",
|
||||||
"rendering_speed": "Rendering Speed",
|
"rendering_speed": "Rendering Speed",
|
||||||
"learn_more": "Learn More",
|
"learn_more": "Learn More",
|
||||||
"paint_course":"tutorial",
|
"paint_course": "tutorial",
|
||||||
"prompt_placeholder_edit": "Enter your image description, text drawing uses \"double quotes\" to wrap",
|
"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",
|
"proxy_required": "Currently, you need to open a proxy to view the generated images, it will be supported in the future",
|
||||||
"image_file_required": "Please upload an image first",
|
"image_file_required": "Please upload an image first",
|
||||||
@ -846,6 +846,29 @@
|
|||||||
"turbo": "Turbo",
|
"turbo": "Turbo",
|
||||||
"quality": "Quality"
|
"quality": "Quality"
|
||||||
},
|
},
|
||||||
|
"quality_options": {
|
||||||
|
"auto": "Auto",
|
||||||
|
"low": "Low",
|
||||||
|
"medium": "Medium",
|
||||||
|
"high": "High"
|
||||||
|
},
|
||||||
|
"moderation_options": {
|
||||||
|
"auto": "Auto",
|
||||||
|
"low": "Low"
|
||||||
|
},
|
||||||
|
"background_options": {
|
||||||
|
"auto": "Auto",
|
||||||
|
"transparent": "Transparent",
|
||||||
|
"opaque": "Opaque"
|
||||||
|
},
|
||||||
|
"aspect_ratios": {
|
||||||
|
"square": "Square",
|
||||||
|
"portrait": "Portrait",
|
||||||
|
"landscape": "Landscape"
|
||||||
|
},
|
||||||
|
"quality": "Quality",
|
||||||
|
"moderation": "Moderation",
|
||||||
|
"background": "Background",
|
||||||
"mode": {
|
"mode": {
|
||||||
"generate": "Draw",
|
"generate": "Draw",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
|
|||||||
@ -820,12 +820,12 @@
|
|||||||
"seed_desc_tip": "同じシードとプロンプトで類似した画像を生成できますが、-1 に設定すると毎回異なる結果が生成されます",
|
"seed_desc_tip": "同じシードとプロンプトで類似した画像を生成できますが、-1 に設定すると毎回異なる結果が生成されます",
|
||||||
"title": "画像",
|
"title": "画像",
|
||||||
"magic_prompt_option": "プロンプト強化",
|
"magic_prompt_option": "プロンプト強化",
|
||||||
"model": "モデルバージョン",
|
"model": "モデル",
|
||||||
"aspect_ratio": "画幅比例",
|
"aspect_ratio": "画幅比例",
|
||||||
"style_type": "スタイル",
|
"style_type": "スタイル",
|
||||||
"learn_more": "詳しくはこちら",
|
"learn_more": "詳しくはこちら",
|
||||||
"prompt_placeholder_edit": "画像の説明を入力します。テキスト描画には '二重引用符' を使用します",
|
"prompt_placeholder_edit": "画像の説明を入力します。テキスト描画には '二重引用符' を使用します",
|
||||||
"paint_course":"チュートリアル",
|
"paint_course": "チュートリアル",
|
||||||
"proxy_required": "現在、プロキシを開く必要があります。これは、将来サポートされる予定です",
|
"proxy_required": "現在、プロキシを開く必要があります。これは、将来サポートされる予定です",
|
||||||
"image_file_required": "画像を先にアップロードしてください",
|
"image_file_required": "画像を先にアップロードしてください",
|
||||||
"image_file_retry": "画像を先にアップロードしてください",
|
"image_file_retry": "画像を先にアップロードしてください",
|
||||||
@ -844,6 +844,29 @@
|
|||||||
"turbo": "高速",
|
"turbo": "高速",
|
||||||
"quality": "高品質"
|
"quality": "高品質"
|
||||||
},
|
},
|
||||||
|
"quality_options": {
|
||||||
|
"auto": "自動",
|
||||||
|
"low": "低",
|
||||||
|
"medium": "中",
|
||||||
|
"high": "高"
|
||||||
|
},
|
||||||
|
"moderation_options": {
|
||||||
|
"auto": "自動",
|
||||||
|
"low": "低"
|
||||||
|
},
|
||||||
|
"background_options": {
|
||||||
|
"auto": "自動",
|
||||||
|
"transparent": "透明",
|
||||||
|
"opaque": "不透明"
|
||||||
|
},
|
||||||
|
"aspect_ratios": {
|
||||||
|
"square": "正方形",
|
||||||
|
"portrait": "縦図",
|
||||||
|
"landscape": "横図"
|
||||||
|
},
|
||||||
|
"quality": "品質",
|
||||||
|
"moderation": "敏感度",
|
||||||
|
"background": "背景",
|
||||||
"mode": {
|
"mode": {
|
||||||
"generate": "画像生成",
|
"generate": "画像生成",
|
||||||
"edit": "部分編集",
|
"edit": "部分編集",
|
||||||
|
|||||||
@ -820,13 +820,13 @@
|
|||||||
"seed_desc_tip": "Одинаковые сиды и промпты могут генерировать похожие изображения, установка -1 будет создавать разные результаты каждый раз",
|
"seed_desc_tip": "Одинаковые сиды и промпты могут генерировать похожие изображения, установка -1 будет создавать разные результаты каждый раз",
|
||||||
"title": "Изображения",
|
"title": "Изображения",
|
||||||
"magic_prompt_option": "Улучшение промпта",
|
"magic_prompt_option": "Улучшение промпта",
|
||||||
"model": "Версия",
|
"model": "Модель",
|
||||||
"aspect_ratio": "Пропорции изображения",
|
"aspect_ratio": "Пропорции изображения",
|
||||||
"style_type": "Стиль",
|
"style_type": "Стиль",
|
||||||
"rendering_speed": "Скорость рендеринга",
|
"rendering_speed": "Скорость рендеринга",
|
||||||
"learn_more": "Узнать больше",
|
"learn_more": "Узнать больше",
|
||||||
"prompt_placeholder_edit": "Введите ваше описание изображения, текстовая отрисовка использует двойные кавычки для обертки",
|
"prompt_placeholder_edit": "Введите ваше описание изображения, текстовая отрисовка использует двойные кавычки для обертки",
|
||||||
"paint_course":"Руководство / Учебник",
|
"paint_course": "Руководство / Учебник",
|
||||||
"proxy_required": "Сейчас необходимо открыть прокси для просмотра сгенерированных изображений, в будущем будет поддерживаться прямое соединение",
|
"proxy_required": "Сейчас необходимо открыть прокси для просмотра сгенерированных изображений, в будущем будет поддерживаться прямое соединение",
|
||||||
"image_file_required": "Пожалуйста, сначала загрузите изображение",
|
"image_file_required": "Пожалуйста, сначала загрузите изображение",
|
||||||
"image_file_retry": "Пожалуйста, сначала загрузите изображение",
|
"image_file_retry": "Пожалуйста, сначала загрузите изображение",
|
||||||
@ -841,11 +841,34 @@
|
|||||||
"3d": "3D",
|
"3d": "3D",
|
||||||
"anime": "Аниме"
|
"anime": "Аниме"
|
||||||
},
|
},
|
||||||
|
"quality_options": {
|
||||||
|
"auto": "Авто",
|
||||||
|
"low": "Низкое",
|
||||||
|
"medium": "Среднее",
|
||||||
|
"high": "Высокое"
|
||||||
|
},
|
||||||
|
"moderation_options": {
|
||||||
|
"auto": "Авто",
|
||||||
|
"low": "Низкое"
|
||||||
|
},
|
||||||
|
"background_options": {
|
||||||
|
"auto": "Авто",
|
||||||
|
"transparent": "Прозрачный",
|
||||||
|
"opaque": "Непрозрачный"
|
||||||
|
},
|
||||||
"rendering_speeds": {
|
"rendering_speeds": {
|
||||||
"default": "По умолчанию",
|
"default": "По умолчанию",
|
||||||
"turbo": "Быстро",
|
"turbo": "Быстро",
|
||||||
"quality": "Качественно"
|
"quality": "Качественно"
|
||||||
},
|
},
|
||||||
|
"aspect_ratios": {
|
||||||
|
"square": "Квадрат",
|
||||||
|
"portrait": "Портрет",
|
||||||
|
"landscape": "Пейзаж"
|
||||||
|
},
|
||||||
|
"quality": "Качество",
|
||||||
|
"moderation": "Сенсорность",
|
||||||
|
"background": "Фон",
|
||||||
"mode": {
|
"mode": {
|
||||||
"generate": "Рисование",
|
"generate": "Рисование",
|
||||||
"edit": "Редактирование",
|
"edit": "Редактирование",
|
||||||
@ -892,7 +915,6 @@
|
|||||||
"seed_tip": "Контролирует случайный характер увеличения изображений для воспроизводимых результатов",
|
"seed_tip": "Контролирует случайный характер увеличения изображений для воспроизводимых результатов",
|
||||||
"magic_prompt_option_tip": "Улучшает увеличение изображений с помощью интеллектуального оптимизирования промптов"
|
"magic_prompt_option_tip": "Улучшает увеличение изображений с помощью интеллектуального оптимизирования промптов"
|
||||||
},
|
},
|
||||||
"rendering_speed": "Скорость рендеринга",
|
|
||||||
"text_desc_required": "Пожалуйста, сначала введите описание изображения"
|
"text_desc_required": "Пожалуйста, сначала введите описание изображения"
|
||||||
},
|
},
|
||||||
"prompts": {
|
"prompts": {
|
||||||
|
|||||||
@ -820,12 +820,12 @@
|
|||||||
"seed_desc_tip": "相同的种子和提示词可以生成相似的图片,设置 -1 每次生成都不一样",
|
"seed_desc_tip": "相同的种子和提示词可以生成相似的图片,设置 -1 每次生成都不一样",
|
||||||
"title": "图片",
|
"title": "图片",
|
||||||
"magic_prompt_option": "提示词增强",
|
"magic_prompt_option": "提示词增强",
|
||||||
"model": "版本",
|
"model": "模型",
|
||||||
"aspect_ratio": "画幅比例",
|
"aspect_ratio": "画幅比例",
|
||||||
"style_type": "风格",
|
"style_type": "风格",
|
||||||
"rendering_speed": "渲染速度",
|
"rendering_speed": "渲染速度",
|
||||||
"learn_more": "了解更多",
|
"learn_more": "了解更多",
|
||||||
"paint_course":"教程",
|
"paint_course": "教程",
|
||||||
"prompt_placeholder_edit": "输入你的图片描述,文本绘制用 \"双引号\" 包裹",
|
"prompt_placeholder_edit": "输入你的图片描述,文本绘制用 \"双引号\" 包裹",
|
||||||
"proxy_required": "目前需要打开代理才能查看生成图片,后续会支持国内直连",
|
"proxy_required": "目前需要打开代理才能查看生成图片,后续会支持国内直连",
|
||||||
"image_file_required": "请先上传图片",
|
"image_file_required": "请先上传图片",
|
||||||
@ -846,11 +846,34 @@
|
|||||||
"turbo": "快速",
|
"turbo": "快速",
|
||||||
"quality": "高质量"
|
"quality": "高质量"
|
||||||
},
|
},
|
||||||
|
"quality_options": {
|
||||||
|
"auto": "自动",
|
||||||
|
"low": "低",
|
||||||
|
"medium": "中",
|
||||||
|
"high": "高"
|
||||||
|
},
|
||||||
|
"moderation_options": {
|
||||||
|
"auto": "自动",
|
||||||
|
"low": "低"
|
||||||
|
},
|
||||||
|
"background_options": {
|
||||||
|
"auto": "自动",
|
||||||
|
"transparent": "透明",
|
||||||
|
"opaque": "不透明"
|
||||||
|
},
|
||||||
|
"aspect_ratios": {
|
||||||
|
"square": "方形",
|
||||||
|
"portrait": "竖图",
|
||||||
|
"landscape": "横图"
|
||||||
|
},
|
||||||
|
"quality": "质量",
|
||||||
|
"moderation": "敏感度",
|
||||||
|
"background": "背景",
|
||||||
"mode": {
|
"mode": {
|
||||||
"generate": "绘图",
|
"generate": "绘图",
|
||||||
"edit": "编辑",
|
"edit": "编辑",
|
||||||
"remix": "混合",
|
"remix": "混合",
|
||||||
"upscale": "放大"
|
"upscale": "高清增强"
|
||||||
},
|
},
|
||||||
"generate": {
|
"generate": {
|
||||||
"model_tip": "模型版本:V3 为最新版本,V2 为之前版本,V2A 为快速模型、V_1 为初代模型,_TURBO 为加速版本",
|
"model_tip": "模型版本:V3 为最新版本,V2 为之前版本,V2A 为快速模型、V_1 为初代模型,_TURBO 为加速版本",
|
||||||
|
|||||||
@ -821,12 +821,12 @@
|
|||||||
"seed_desc_tip": "相同的種子和提示詞可以生成相似的圖片,設置 -1 每次生成都不一樣",
|
"seed_desc_tip": "相同的種子和提示詞可以生成相似的圖片,設置 -1 每次生成都不一樣",
|
||||||
"title": "繪圖",
|
"title": "繪圖",
|
||||||
"magic_prompt_option": "提示詞增強",
|
"magic_prompt_option": "提示詞增強",
|
||||||
"model": "版本",
|
"model": "模型",
|
||||||
"aspect_ratio": "畫幅比例",
|
"aspect_ratio": "畫幅比例",
|
||||||
"style_type": "風格",
|
"style_type": "風格",
|
||||||
"learn_more": "了解更多",
|
"learn_more": "了解更多",
|
||||||
"prompt_placeholder_edit": "輸入你的圖片描述,文本繪製用 '雙引號' 包裹",
|
"prompt_placeholder_edit": "輸入你的圖片描述,文本繪製用 '雙引號' 包裹",
|
||||||
"paint_course":"教程",
|
"paint_course": "教程",
|
||||||
"proxy_required": "目前需要打開代理才能查看生成圖片,後續會支持國內直連",
|
"proxy_required": "目前需要打開代理才能查看生成圖片,後續會支持國內直連",
|
||||||
"image_file_required": "請先上傳圖片",
|
"image_file_required": "請先上傳圖片",
|
||||||
"image_file_retry": "請重新上傳圖片",
|
"image_file_retry": "請重新上傳圖片",
|
||||||
@ -846,6 +846,29 @@
|
|||||||
"turbo": "快速",
|
"turbo": "快速",
|
||||||
"quality": "高品質"
|
"quality": "高品質"
|
||||||
},
|
},
|
||||||
|
"quality_options": {
|
||||||
|
"auto": "自動",
|
||||||
|
"low": "低",
|
||||||
|
"medium": "中",
|
||||||
|
"high": "高"
|
||||||
|
},
|
||||||
|
"moderation_options": {
|
||||||
|
"auto": "自動",
|
||||||
|
"low": "低"
|
||||||
|
},
|
||||||
|
"background_options": {
|
||||||
|
"auto": "自動",
|
||||||
|
"transparent": "透明",
|
||||||
|
"opaque": "不透明"
|
||||||
|
},
|
||||||
|
"aspect_ratios": {
|
||||||
|
"square": "方形",
|
||||||
|
"portrait": "豎圖",
|
||||||
|
"landscape": "橫圖"
|
||||||
|
},
|
||||||
|
"quality": "品質",
|
||||||
|
"moderation": "敏感度",
|
||||||
|
"background": "背景",
|
||||||
"mode": {
|
"mode": {
|
||||||
"generate": "繪圖",
|
"generate": "繪圖",
|
||||||
"edit": "編輯",
|
"edit": "編輯",
|
||||||
|
|||||||
@ -22,17 +22,16 @@ import { Avatar, Button, Input, InputNumber, Radio, Segmented, Select, Slider, S
|
|||||||
import TextArea from 'antd/es/input/TextArea'
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
import { Info } from 'lucide-react'
|
import { Info } from 'lucide-react'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import SendMessageButton from '../home/Inputbar/SendMessageButton'
|
import SendMessageButton from '../home/Inputbar/SendMessageButton'
|
||||||
import { SettingHelpLink, SettingTitle } from '../settings'
|
import { SettingHelpLink, SettingTitle } from '../settings'
|
||||||
import Artboard from './Artboard'
|
import Artboard from './components/Artboard'
|
||||||
import { type ConfigItem, createModeConfigs } from './config/aihubmixConfig'
|
import PaintingsList from './components/PaintingsList'
|
||||||
import { DEFAULT_PAINTING } from './config/constants'
|
import { type ConfigItem, createModeConfigs, DEFAULT_PAINTING } from './config/aihubmixConfig'
|
||||||
import PaintingsList from './PaintingsList'
|
|
||||||
|
|
||||||
// 使用函数创建配置项
|
// 使用函数创建配置项
|
||||||
const modeConfigs = createModeConfigs()
|
const modeConfigs = createModeConfigs()
|
||||||
@ -74,12 +73,13 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
{ label: t('paintings.mode.upscale'), value: 'upscale' }
|
{ label: t('paintings.mode.upscale'), value: 'upscale' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const getNewPainting = () => {
|
const getNewPainting = useCallback(() => {
|
||||||
return {
|
return {
|
||||||
...DEFAULT_PAINTING,
|
...DEFAULT_PAINTING,
|
||||||
|
model: mode === 'generate' ? 'gpt-image-1' : 'V_3',
|
||||||
id: uuid()
|
id: uuid()
|
||||||
}
|
}
|
||||||
}
|
}, [mode])
|
||||||
|
|
||||||
const textareaRef = useRef<any>(null)
|
const textareaRef = useRef<any>(null)
|
||||||
|
|
||||||
@ -89,6 +89,47 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
updatePainting(mode, updatedPainting)
|
updatePainting(mode, updatedPainting)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleError = (error: unknown) => {
|
||||||
|
if (error instanceof Error && error.name !== 'AbortError') {
|
||||||
|
window.modal.error({
|
||||||
|
content: getErrorMessage(error),
|
||||||
|
centered: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadImages = async (urls: string[]) => {
|
||||||
|
const downloadedFiles = await Promise.all(
|
||||||
|
urls.map(async (url) => {
|
||||||
|
try {
|
||||||
|
if (!url?.trim()) {
|
||||||
|
console.error('图像URL为空,可能是提示词违禁')
|
||||||
|
window.message.warning({
|
||||||
|
content: t('message.empty_url'),
|
||||||
|
key: 'empty-url-warning'
|
||||||
|
})
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return await window.api.file.download(url)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('下载图像失败:', error)
|
||||||
|
if (
|
||||||
|
error instanceof Error &&
|
||||||
|
(error.message.includes('Failed to parse URL') || error.message.includes('Invalid URL'))
|
||||||
|
) {
|
||||||
|
window.message.warning({
|
||||||
|
content: t('message.empty_url'),
|
||||||
|
key: 'empty-url-warning'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return downloadedFiles.filter((file): file is FileType => file !== null)
|
||||||
|
}
|
||||||
|
|
||||||
const onGenerate = async () => {
|
const onGenerate = async () => {
|
||||||
if (painting.files.length > 0) {
|
if (painting.files.length > 0) {
|
||||||
const confirmed = await window.modal.confirm({
|
const confirmed = await window.modal.confirm({
|
||||||
@ -129,11 +170,11 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
dispatch(setGenerating(true))
|
dispatch(setGenerating(true))
|
||||||
|
|
||||||
let body: string | FormData = ''
|
let body: string | FormData = ''
|
||||||
const headers: Record<string, string> = {
|
let headers: Record<string, string> = {
|
||||||
'Api-Key': aihubmixProvider.apiKey
|
'Api-Key': aihubmixProvider.apiKey
|
||||||
}
|
}
|
||||||
|
let url = aihubmixProvider.apiHost + `/ideogram/` + mode
|
||||||
|
|
||||||
// 不使用 AiProvider 的通用规则,而是直接调用自定义接口
|
|
||||||
try {
|
try {
|
||||||
if (mode === 'generate') {
|
if (mode === 'generate') {
|
||||||
if (painting.model === 'V_3') {
|
if (painting.model === 'V_3') {
|
||||||
@ -214,67 +255,47 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
console.log('V3 API响应:', data)
|
console.log('V3 API响应:', data)
|
||||||
const urls = data.data.map((item) => item.url)
|
const urls = data.data.map((item) => item.url)
|
||||||
|
|
||||||
// Rest of the code for handling image downloads is the same
|
|
||||||
if (urls.length > 0) {
|
if (urls.length > 0) {
|
||||||
const downloadedFiles = await Promise.all(
|
const validFiles = await downloadImages(urls)
|
||||||
urls.map(async (url) => {
|
|
||||||
try {
|
|
||||||
// 检查URL是否为空
|
|
||||||
if (!url || url.trim() === '') {
|
|
||||||
console.error('图像URL为空,可能是提示词违禁')
|
|
||||||
window.message.warning({
|
|
||||||
content: t('message.empty_url'),
|
|
||||||
key: 'empty-url-warning'
|
|
||||||
})
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return await window.api.file.download(url)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('下载图像失败:', error)
|
|
||||||
// 检查是否是URL解析错误
|
|
||||||
if (
|
|
||||||
error instanceof Error &&
|
|
||||||
(error.message.includes('Failed to parse URL') || error.message.includes('Invalid URL'))
|
|
||||||
) {
|
|
||||||
window.message.warning({
|
|
||||||
content: t('message.empty_url'),
|
|
||||||
key: 'empty-url-warning'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const validFiles = downloadedFiles.filter((file): file is FileType => file !== null)
|
|
||||||
await FileManager.addFiles(validFiles)
|
await FileManager.addFiles(validFiles)
|
||||||
updatePaintingState({ files: validFiles, urls })
|
updatePaintingState({ files: validFiles, urls })
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error && error.name !== 'AbortError') {
|
handleError(error)
|
||||||
window.modal.error({
|
|
||||||
content: getErrorMessage(error),
|
|
||||||
centered: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
dispatch(setGenerating(false))
|
dispatch(setGenerating(false))
|
||||||
setAbortController(null)
|
setAbortController(null)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Existing V1/V2 API
|
let requestData: any = {}
|
||||||
const requestData = {
|
if (painting.model === 'gpt-image-1') {
|
||||||
image_request: {
|
requestData = {
|
||||||
prompt,
|
prompt,
|
||||||
model: painting.model,
|
model: painting.model,
|
||||||
aspect_ratio: painting.aspectRatio,
|
size: painting.size === 'auto' ? undefined : painting.size,
|
||||||
num_images: painting.numImages,
|
n: painting.n,
|
||||||
style_type: painting.styleType,
|
quality: painting.quality,
|
||||||
seed: painting.seed ? +painting.seed : undefined,
|
moderation: painting.moderation
|
||||||
negative_prompt: painting.negativePrompt || undefined,
|
}
|
||||||
magic_prompt_option: painting.magicPromptOption ? 'ON' : 'OFF'
|
url = aihubmixProvider.apiHost + `/v1/images/generations`
|
||||||
|
headers = {
|
||||||
|
Authorization: `Bearer ${aihubmixProvider.apiKey}`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Existing V1/V2 API
|
||||||
|
requestData = {
|
||||||
|
image_request: {
|
||||||
|
prompt,
|
||||||
|
model: painting.model,
|
||||||
|
aspect_ratio: painting.aspectRatio,
|
||||||
|
num_images: painting.numImages,
|
||||||
|
style_type: painting.styleType,
|
||||||
|
seed: painting.seed ? +painting.seed : undefined,
|
||||||
|
negative_prompt: painting.negativePrompt || undefined,
|
||||||
|
magic_prompt_option: painting.magicPromptOption ? 'ON' : 'OFF'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
body = JSON.stringify(requestData)
|
body = JSON.stringify(requestData)
|
||||||
@ -352,37 +373,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
|
|
||||||
// Handle the downloaded images
|
// Handle the downloaded images
|
||||||
if (urls.length > 0) {
|
if (urls.length > 0) {
|
||||||
const downloadedFiles = await Promise.all(
|
const validFiles = await downloadImages(urls)
|
||||||
urls.map(async (url) => {
|
|
||||||
try {
|
|
||||||
// 检查URL是否为空
|
|
||||||
if (!url || url.trim() === '') {
|
|
||||||
console.error('图像URL为空,可能是提示词违禁')
|
|
||||||
window.message.warning({
|
|
||||||
content: t('message.empty_url'),
|
|
||||||
key: 'empty-url-warning'
|
|
||||||
})
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return await window.api.file.download(url)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('下载图像失败:', error)
|
|
||||||
// 检查是否是URL解析错误
|
|
||||||
if (
|
|
||||||
error instanceof Error &&
|
|
||||||
(error.message.includes('Failed to parse URL') || error.message.includes('Invalid URL'))
|
|
||||||
) {
|
|
||||||
window.message.warning({
|
|
||||||
content: t('message.empty_url'),
|
|
||||||
key: 'empty-url-warning'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const validFiles = downloadedFiles.filter((file): file is FileType => file !== null)
|
|
||||||
await FileManager.addFiles(validFiles)
|
await FileManager.addFiles(validFiles)
|
||||||
updatePaintingState({ files: validFiles, urls })
|
updatePaintingState({ files: validFiles, urls })
|
||||||
}
|
}
|
||||||
@ -405,119 +396,6 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
form.append('image_file', fileMap[painting.imageFile] as unknown as Blob)
|
form.append('image_file', fileMap[painting.imageFile] as unknown as Blob)
|
||||||
body = form
|
body = form
|
||||||
}
|
}
|
||||||
} else if (mode === 'edit') {
|
|
||||||
if (!painting.imageFile) {
|
|
||||||
window.modal.error({
|
|
||||||
content: t('paintings.image_file_required'),
|
|
||||||
centered: true
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!fileMap[painting.imageFile]) {
|
|
||||||
window.modal.error({
|
|
||||||
content: t('paintings.image_file_retry'),
|
|
||||||
centered: true
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (painting.model === 'V_3') {
|
|
||||||
// V3 Edit API
|
|
||||||
const formData = new FormData()
|
|
||||||
formData.append('prompt', prompt)
|
|
||||||
formData.append('rendering_speed', painting.renderingSpeed || 'DEFAULT')
|
|
||||||
formData.append('num_images', String(painting.numImages || 1))
|
|
||||||
|
|
||||||
if (painting.styleType) {
|
|
||||||
formData.append('style_type', painting.styleType)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (painting.seed) {
|
|
||||||
formData.append('seed', painting.seed)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (painting.magicPromptOption !== undefined) {
|
|
||||||
formData.append('magic_prompt', painting.magicPromptOption ? 'ON' : 'OFF')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the image file
|
|
||||||
formData.append('image', fileMap[painting.imageFile] as unknown as Blob)
|
|
||||||
|
|
||||||
// Add the mask if available
|
|
||||||
if (painting.mask) {
|
|
||||||
formData.append('mask', painting.mask as unknown as Blob)
|
|
||||||
}
|
|
||||||
|
|
||||||
body = formData
|
|
||||||
// For V3 Edit endpoint
|
|
||||||
const response = await fetch(`${aihubmixProvider.apiHost}/ideogram/v1/ideogram-v3/edit`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Api-Key': aihubmixProvider.apiKey },
|
|
||||||
body
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json()
|
|
||||||
console.error('V3 Edit API错误:', errorData)
|
|
||||||
throw new Error(errorData.error?.message || '图像编辑失败')
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
console.log('V3 Edit API响应:', data)
|
|
||||||
const urls = data.data.map((item) => item.url)
|
|
||||||
|
|
||||||
// Handle the downloaded images
|
|
||||||
if (urls.length > 0) {
|
|
||||||
const downloadedFiles = await Promise.all(
|
|
||||||
urls.map(async (url) => {
|
|
||||||
try {
|
|
||||||
// 检查URL是否为空
|
|
||||||
if (!url || url.trim() === '') {
|
|
||||||
console.error('图像URL为空,可能是提示词违禁')
|
|
||||||
window.message.warning({
|
|
||||||
content: t('message.empty_url'),
|
|
||||||
key: 'empty-url-warning'
|
|
||||||
})
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return await window.api.file.download(url)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('下载图像失败:', error)
|
|
||||||
// 检查是否是URL解析错误
|
|
||||||
if (
|
|
||||||
error instanceof Error &&
|
|
||||||
(error.message.includes('Failed to parse URL') || error.message.includes('Invalid URL'))
|
|
||||||
) {
|
|
||||||
window.message.warning({
|
|
||||||
content: t('message.empty_url'),
|
|
||||||
key: 'empty-url-warning'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const validFiles = downloadedFiles.filter((file): file is FileType => file !== null)
|
|
||||||
await FileManager.addFiles(validFiles)
|
|
||||||
updatePaintingState({ files: validFiles, urls })
|
|
||||||
}
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
// Existing V1/V2 API for edit
|
|
||||||
const form = new FormData()
|
|
||||||
const imageRequest: Record<string, any> = {
|
|
||||||
prompt,
|
|
||||||
model: painting.model,
|
|
||||||
style_type: painting.styleType,
|
|
||||||
num_images: painting.numImages,
|
|
||||||
seed: painting.seed ? +painting.seed : undefined,
|
|
||||||
magic_prompt_option: painting.magicPromptOption ? 'ON' : 'OFF'
|
|
||||||
}
|
|
||||||
form.append('image_request', JSON.stringify(imageRequest))
|
|
||||||
form.append('image_file', fileMap[painting.imageFile] as unknown as Blob)
|
|
||||||
body = form
|
|
||||||
}
|
|
||||||
} else if (mode === 'upscale') {
|
} else if (mode === 'upscale') {
|
||||||
if (!painting.imageFile) {
|
if (!painting.imageFile) {
|
||||||
window.modal.error({
|
window.modal.error({
|
||||||
@ -549,9 +427,9 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 只针对非V3模型使用通用接口
|
// 只针对非V3模型使用通用接口
|
||||||
if (!painting.model?.includes('V_3')) {
|
if (!painting.model?.includes('V_3') || mode === 'upscale') {
|
||||||
// 直接调用自定义接口
|
// 直接调用自定义接口
|
||||||
const response = await fetch(`${aihubmixProvider.apiHost}/ideogram/${mode}`, { method: 'POST', headers, body })
|
const response = await fetch(url, { method: 'POST', headers, body })
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json()
|
const errorData = await response.json()
|
||||||
@ -561,53 +439,27 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
|
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
console.log('通用API响应:', data)
|
console.log('通用API响应:', data)
|
||||||
const urls = data.data.map((item) => item.url)
|
const urls = data.data.filter((item) => item.url).map((item) => item.url)
|
||||||
|
const base64s = data.data.filter((item) => item.b64_json).map((item) => item.b64_json)
|
||||||
|
|
||||||
if (urls.length > 0) {
|
if (urls.length > 0) {
|
||||||
const downloadedFiles = await Promise.all(
|
const validFiles = await downloadImages(urls)
|
||||||
urls.map(async (url) => {
|
await FileManager.addFiles(validFiles)
|
||||||
try {
|
updatePaintingState({ files: validFiles, urls })
|
||||||
// 检查URL是否为空
|
}
|
||||||
if (!url || url.trim() === '') {
|
|
||||||
console.error('图像URL为空,可能是提示词违禁')
|
if (base64s?.length > 0) {
|
||||||
window.message.warning({
|
const validFiles = await Promise.all(
|
||||||
content: t('message.empty_url'),
|
base64s.map(async (base64) => {
|
||||||
key: 'empty-url-warning'
|
return await window.api.file.saveBase64Image(base64)
|
||||||
})
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return await window.api.file.download(url)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('下载图像失败:', error)
|
|
||||||
// 检查是否是URL解析错误
|
|
||||||
if (
|
|
||||||
error instanceof Error &&
|
|
||||||
(error.message.includes('Failed to parse URL') || error.message.includes('Invalid URL'))
|
|
||||||
) {
|
|
||||||
window.message.warning({
|
|
||||||
content: t('message.empty_url'),
|
|
||||||
key: 'empty-url-warning'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const validFiles = downloadedFiles.filter((file): file is FileType => file !== null)
|
|
||||||
|
|
||||||
await FileManager.addFiles(validFiles)
|
await FileManager.addFiles(validFiles)
|
||||||
|
updatePaintingState({ files: validFiles, urls: validFiles.map((file) => file.name) })
|
||||||
updatePaintingState({ files: validFiles, urls })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error && error.name !== 'AbortError') {
|
handleError(error)
|
||||||
window.modal.error({
|
|
||||||
content: getErrorMessage(error),
|
|
||||||
centered: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
dispatch(setGenerating(false))
|
dispatch(setGenerating(false))
|
||||||
@ -617,43 +469,15 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
|
|
||||||
const handleRetry = async (painting: PaintingAction) => {
|
const handleRetry = async (painting: PaintingAction) => {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
const downloadedFiles = await Promise.all(
|
try {
|
||||||
painting.urls.map(async (url) => {
|
const validFiles = await downloadImages(painting.urls)
|
||||||
try {
|
await FileManager.addFiles(validFiles)
|
||||||
// 检查URL是否为空
|
updatePaintingState({ files: validFiles, urls: painting.urls })
|
||||||
if (!url || url.trim() === '') {
|
} catch (error) {
|
||||||
console.error('图像URL为空,可能是提示词违禁')
|
handleError(error)
|
||||||
window.message.warning({
|
} finally {
|
||||||
content: t('message.empty_url'),
|
setIsLoading(false)
|
||||||
key: 'empty-url-warning'
|
}
|
||||||
})
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return await window.api.file.download(url)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('下载图像失败:', error)
|
|
||||||
// 检查是否是URL解析错误
|
|
||||||
if (
|
|
||||||
error instanceof Error &&
|
|
||||||
(error.message.includes('Failed to parse URL') || error.message.includes('Invalid URL'))
|
|
||||||
) {
|
|
||||||
window.message.warning({
|
|
||||||
content: t('message.empty_url'),
|
|
||||||
key: 'empty-url-warning'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
setIsLoading(false)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const validFiles = downloadedFiles.filter((file): file is FileType => file !== null)
|
|
||||||
|
|
||||||
await FileManager.addFiles(validFiles)
|
|
||||||
|
|
||||||
updatePaintingState({ files: validFiles, urls: painting.urls })
|
|
||||||
setIsLoading(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onCancel = () => {
|
const onCancel = () => {
|
||||||
@ -754,20 +578,8 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 渲染配置项的函数
|
// 渲染配置项的函数
|
||||||
const renderConfigItem = (item: ConfigItem, index: number) => {
|
const renderConfigForm = (item: ConfigItem) => {
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case 'title': {
|
|
||||||
return (
|
|
||||||
<SettingTitle key={index} style={{ marginBottom: 5, marginTop: 15 }}>
|
|
||||||
{t(item.title!)}
|
|
||||||
{item.tooltip && (
|
|
||||||
<Tooltip title={t(item.tooltip)}>
|
|
||||||
<InfoIcon />
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</SettingTitle>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case 'select': {
|
case 'select': {
|
||||||
// 处理函数类型的disabled属性
|
// 处理函数类型的disabled属性
|
||||||
const isDisabled = typeof item.disabled === 'function' ? item.disabled(item, painting) : item.disabled
|
const isDisabled = typeof item.disabled === 'function' ? item.disabled(item, painting) : item.disabled
|
||||||
@ -786,10 +598,11 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
key={index}
|
style={{ width: '100%' }}
|
||||||
|
listHeight={500}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
value={painting[item.key!] || item.initialValue}
|
value={painting[item.key!] || item.initialValue}
|
||||||
options={selectOptions}
|
options={selectOptions as any}
|
||||||
onChange={(v) => updatePaintingState({ [item.key!]: v })}
|
onChange={(v) => updatePaintingState({ [item.key!]: v })}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -809,8 +622,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
key={index}
|
value={painting[item.key!] || item.initialValue}
|
||||||
value={painting[item.key!]}
|
|
||||||
onChange={(e) => updatePaintingState({ [item.key!]: e.target.value })}>
|
onChange={(e) => updatePaintingState({ [item.key!]: e.target.value })}>
|
||||||
{radioOptions!.map((option) => (
|
{radioOptions!.map((option) => (
|
||||||
<Radio.Button key={option.value} value={option.value}>
|
<Radio.Button key={option.value} value={option.value}>
|
||||||
@ -822,96 +634,82 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
}
|
}
|
||||||
case 'slider': {
|
case 'slider': {
|
||||||
return (
|
return (
|
||||||
<SliderContainer key={index}>
|
<SliderContainer>
|
||||||
<Slider
|
<Slider
|
||||||
min={item.min}
|
min={item.min}
|
||||||
max={item.max}
|
max={item.max}
|
||||||
step={item.step}
|
step={item.step}
|
||||||
value={painting[item.key!] as number}
|
value={(painting[item.key!] || item.initialValue) as number}
|
||||||
onChange={(v) => updatePaintingState({ [item.key!]: v })}
|
onChange={(v) => updatePaintingState({ [item.key!]: v })}
|
||||||
/>
|
/>
|
||||||
<StyledInputNumber
|
<StyledInputNumber
|
||||||
min={item.min}
|
min={item.min}
|
||||||
max={item.max}
|
max={item.max}
|
||||||
step={item.step}
|
step={item.step}
|
||||||
value={painting[item.key!] as number}
|
value={(painting[item.key!] || item.initialValue) as number}
|
||||||
onChange={(v) => updatePaintingState({ [item.key!]: v })}
|
onChange={(v) => updatePaintingState({ [item.key!]: v })}
|
||||||
/>
|
/>
|
||||||
</SliderContainer>
|
</SliderContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
case 'input': {
|
case 'input':
|
||||||
// 处理随机种子按钮的特殊情况
|
|
||||||
if (item.key === 'seed') {
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
key={index}
|
|
||||||
value={painting[item.key] as string}
|
|
||||||
onChange={(e) => updatePaintingState({ [item.key!]: e.target.value })}
|
|
||||||
suffix={
|
|
||||||
<RedoOutlined onClick={handleRandomSeed} style={{ cursor: 'pointer', color: 'var(--color-text-2)' }} />
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
key={index}
|
value={(painting[item.key!] || item.initialValue) as string}
|
||||||
value={painting[item.key!] as string}
|
|
||||||
onChange={(e) => updatePaintingState({ [item.key!]: e.target.value })}
|
onChange={(e) => updatePaintingState({ [item.key!]: e.target.value })}
|
||||||
suffix={item.suffix}
|
suffix={
|
||||||
|
item.key === 'seed' ? (
|
||||||
|
<RedoOutlined onClick={handleRandomSeed} style={{ cursor: 'pointer', color: 'var(--color-text-2)' }} />
|
||||||
|
) : (
|
||||||
|
item.suffix
|
||||||
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
case 'inputNumber':
|
||||||
case 'inputNumber': {
|
|
||||||
return (
|
return (
|
||||||
<InputNumber
|
<InputNumber
|
||||||
key={index}
|
|
||||||
min={item.min}
|
min={item.min}
|
||||||
max={item.max}
|
max={item.max}
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
value={painting[item.key!] as number}
|
value={(painting[item.key!] || item.initialValue) as number}
|
||||||
onChange={(v) => updatePaintingState({ [item.key!]: v })}
|
onChange={(v) => updatePaintingState({ [item.key!]: v })}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
case 'textarea':
|
||||||
case 'textarea': {
|
|
||||||
return (
|
return (
|
||||||
<TextArea
|
<TextArea
|
||||||
key={index}
|
value={(painting[item.key!] || item.initialValue) as string}
|
||||||
value={painting[item.key!] as string}
|
|
||||||
onChange={(e) => updatePaintingState({ [item.key!]: e.target.value })}
|
onChange={(e) => updatePaintingState({ [item.key!]: e.target.value })}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
rows={4}
|
rows={4}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
case 'switch':
|
||||||
case 'switch': {
|
|
||||||
return (
|
return (
|
||||||
<HStack key={index}>
|
<HStack>
|
||||||
<Switch
|
<Switch
|
||||||
checked={painting[item.key!] as boolean}
|
checked={(painting[item.key!] || item.initialValue) as boolean}
|
||||||
onChange={(checked) => updatePaintingState({ [item.key!]: checked })}
|
onChange={(checked) => updatePaintingState({ [item.key!]: checked })}
|
||||||
/>
|
/>
|
||||||
</HStack>
|
</HStack>
|
||||||
)
|
)
|
||||||
}
|
|
||||||
case 'image': {
|
case 'image': {
|
||||||
return (
|
return (
|
||||||
<ImageUploadButton
|
<ImageUploadButton
|
||||||
key={index}
|
|
||||||
accept="image/png, image/jpeg, image/gif"
|
accept="image/png, image/jpeg, image/gif"
|
||||||
maxCount={1}
|
maxCount={1}
|
||||||
showUploadList={false}
|
showUploadList={false}
|
||||||
listType="picture-card"
|
listType="picture-card"
|
||||||
onChange={async ({ file }) => {
|
beforeUpload={(file) => {
|
||||||
const path = file.originFileObj?.path || ''
|
const path = URL.createObjectURL(file)
|
||||||
setFileMap({ ...fileMap, [path]: file.originFileObj as unknown as FileType })
|
setFileMap({ ...fileMap, [path]: file as unknown as FileType })
|
||||||
updatePaintingState({ [item.key!]: path })
|
updatePaintingState({ [item.key!]: path })
|
||||||
|
return false // 阻止默认上传行为
|
||||||
}}>
|
}}>
|
||||||
{painting[item.key!] ? (
|
{painting[item.key!] ? (
|
||||||
<ImagePreview>
|
<ImagePreview>
|
||||||
<img src={'file://' + painting[item.key!]} alt="预览图" />
|
<img src={painting[item.key!]} alt="预览图" />
|
||||||
</ImagePreview>
|
</ImagePreview>
|
||||||
) : (
|
) : (
|
||||||
<ImageSizeImage src={IcImageUp} theme={theme} />
|
<ImageSizeImage src={IcImageUp} theme={theme} />
|
||||||
@ -924,6 +722,23 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 渲染配置项的函数
|
||||||
|
const renderConfigItem = (item: ConfigItem, index: number) => {
|
||||||
|
return (
|
||||||
|
<div key={index}>
|
||||||
|
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>
|
||||||
|
{t(item.title!)}
|
||||||
|
{item.tooltip && (
|
||||||
|
<Tooltip title={t(item.tooltip)}>
|
||||||
|
<InfoIcon />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</SettingTitle>
|
||||||
|
{renderConfigForm(item)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const onSelectPainting = (newPainting: PaintingAction) => {
|
const onSelectPainting = (newPainting: PaintingAction) => {
|
||||||
if (generating) return
|
if (generating) return
|
||||||
setPainting(newPainting)
|
setPainting(newPainting)
|
||||||
@ -936,12 +751,13 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
addPainting(mode, newPainting)
|
addPainting(mode, newPainting)
|
||||||
setPainting(newPainting)
|
setPainting(newPainting)
|
||||||
}
|
}
|
||||||
}, [filteredPaintings, mode, addPainting, painting])
|
}, [filteredPaintings, mode, addPainting, painting, getNewPainting])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const timer = spaceClickTimer.current
|
||||||
return () => {
|
return () => {
|
||||||
if (spaceClickTimer.current) {
|
if (timer) {
|
||||||
clearTimeout(spaceClickTimer.current)
|
clearTimeout(timer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
@ -985,7 +801,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
{/* 使用JSON配置渲染设置项 */}
|
{/* 使用JSON配置渲染设置项 */}
|
||||||
{modeConfigs[mode].map(renderConfigItem)}
|
{modeConfigs[mode].filter((item) => (item.condition ? item.condition(painting) : true)).map(renderConfigItem)}
|
||||||
</LeftContainer>
|
</LeftContainer>
|
||||||
<MainContainer>
|
<MainContainer>
|
||||||
{/* 添加功能切换分段控制器 */}
|
{/* 添加功能切换分段控制器 */}
|
||||||
|
|||||||
@ -26,7 +26,8 @@ import styled from 'styled-components'
|
|||||||
|
|
||||||
import SendMessageButton from '../home/Inputbar/SendMessageButton'
|
import SendMessageButton from '../home/Inputbar/SendMessageButton'
|
||||||
import { SettingHelpLink, SettingTitle } from '../settings'
|
import { SettingHelpLink, SettingTitle } from '../settings'
|
||||||
import Artboard from './Artboard'
|
import Artboard from './components/Artboard'
|
||||||
|
import PaintingsList from './components/PaintingsList'
|
||||||
import {
|
import {
|
||||||
COURSE_URL,
|
COURSE_URL,
|
||||||
DEFAULT_PAINTING,
|
DEFAULT_PAINTING,
|
||||||
@ -34,7 +35,6 @@ import {
|
|||||||
STYLE_TYPE_OPTIONS,
|
STYLE_TYPE_OPTIONS,
|
||||||
TEXT_TO_IMAGES_MODELS
|
TEXT_TO_IMAGES_MODELS
|
||||||
} from './config/DmxapiConfig'
|
} from './config/DmxapiConfig'
|
||||||
import PaintingsList from './PaintingsList'
|
|
||||||
|
|
||||||
const generateRandomSeed = () => Math.floor(Math.random() * 1000000).toString()
|
const generateRandomSeed = () => Math.floor(Math.random() * 1000000).toString()
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { Route, Routes } from 'react-router-dom'
|
|||||||
|
|
||||||
import AihubmixPage from './AihubmixPage'
|
import AihubmixPage from './AihubmixPage'
|
||||||
import DmxapiPage from './DmxapiPage'
|
import DmxapiPage from './DmxapiPage'
|
||||||
import SiliconPage from './PaintingsPage'
|
import SiliconPage from './SiliconPage'
|
||||||
|
|
||||||
const Options = ['aihubmix', 'silicon', 'dmxapi']
|
const Options = ['aihubmix', 'silicon', 'dmxapi']
|
||||||
|
|
||||||
|
|||||||
@ -35,8 +35,8 @@ import styled from 'styled-components'
|
|||||||
|
|
||||||
import SendMessageButton from '../home/Inputbar/SendMessageButton'
|
import SendMessageButton from '../home/Inputbar/SendMessageButton'
|
||||||
import { SettingTitle } from '../settings'
|
import { SettingTitle } from '../settings'
|
||||||
import Artboard from './Artboard'
|
import Artboard from './components/Artboard'
|
||||||
import PaintingsList from './PaintingsList'
|
import PaintingsList from './components/PaintingsList'
|
||||||
|
|
||||||
const IMAGE_SIZES = [
|
const IMAGE_SIZES = [
|
||||||
{
|
{
|
||||||
@ -88,7 +88,7 @@ const DEFAULT_PAINTING: Painting = {
|
|||||||
|
|
||||||
// let _painting: Painting
|
// let _painting: Painting
|
||||||
|
|
||||||
const PaintingsPage: FC<{ Options: string[] }> = ({ Options }) => {
|
const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { paintings, addPainting, removePainting, updatePainting } = usePaintings()
|
const { paintings, addPainting, removePainting, updatePainting } = usePaintings()
|
||||||
const [painting, setPainting] = useState<Painting>(paintings[0] || DEFAULT_PAINTING)
|
const [painting, setPainting] = useState<Painting>(paintings[0] || DEFAULT_PAINTING)
|
||||||
@ -645,4 +645,4 @@ const StyledInputNumber = styled(InputNumber)`
|
|||||||
width: 70px;
|
width: 70px;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default PaintingsPage
|
export default SiliconPage
|
||||||
@ -7,7 +7,7 @@ import React, { FC } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import ImagePreview from '../home/Markdown/ImagePreview'
|
import ImagePreview from '../../home/Markdown/ImagePreview'
|
||||||
|
|
||||||
interface ArtboardProps {
|
interface ArtboardProps {
|
||||||
painting: Painting
|
painting: Painting
|
||||||
@ -98,8 +98,8 @@ const Artboard: FC<ArtboardProps> = ({
|
|||||||
{painting.urls.length > 0 && retry ? (
|
{painting.urls.length > 0 && retry ? (
|
||||||
<div>
|
<div>
|
||||||
<ImageList>
|
<ImageList>
|
||||||
{painting.urls.map((url) => (
|
{painting.urls.map((url, index) => (
|
||||||
<ImageListItem key={url}>{url}</ImageListItem>
|
<ImageListItem key={url || index}>{url}</ImageListItem>
|
||||||
))}
|
))}
|
||||||
</ImageList>
|
</ImageList>
|
||||||
<div>
|
<div>
|
||||||
@ -109,11 +109,11 @@ const Artboard: FC<ArtboardProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : imageCover ? (
|
||||||
|
imageCover
|
||||||
) : (
|
) : (
|
||||||
imageCover ?
|
<div>{t('paintings.image_placeholder')}</div>
|
||||||
imageCover:
|
)}
|
||||||
(<div>{t('paintings.image_placeholder')}</div>)
|
|
||||||
)}
|
|
||||||
</ImagePlaceholder>
|
</ImagePlaceholder>
|
||||||
)}
|
)}
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
@ -1,6 +1,14 @@
|
|||||||
import type { PaintingAction, PaintingsState } from '@renderer/types'
|
import type { PaintingAction } from '@renderer/types'
|
||||||
|
|
||||||
import { ASPECT_RATIOS, RENDERING_SPEED_OPTIONS, STYLE_TYPES, V3_STYLE_TYPES } from './constants'
|
import {
|
||||||
|
ASPECT_RATIOS,
|
||||||
|
BACKGROUND_OPTIONS,
|
||||||
|
MODERATION_OPTIONS,
|
||||||
|
QUALITY_OPTIONS,
|
||||||
|
RENDERING_SPEED_OPTIONS,
|
||||||
|
STYLE_TYPES,
|
||||||
|
V3_STYLE_TYPES
|
||||||
|
} from './constants'
|
||||||
|
|
||||||
// 配置项类型定义
|
// 配置项类型定义
|
||||||
export type ConfigItem = {
|
export type ConfigItem = {
|
||||||
@ -19,7 +27,14 @@ export type ConfigItem = {
|
|||||||
title?: string
|
title?: string
|
||||||
tooltip?: string
|
tooltip?: string
|
||||||
options?:
|
options?:
|
||||||
| Array<{ label: string; value: string | number; icon?: string; onlyV2?: boolean }>
|
| Array<{
|
||||||
|
label: string
|
||||||
|
title?: string
|
||||||
|
value?: string | number
|
||||||
|
icon?: string
|
||||||
|
onlyV2?: boolean
|
||||||
|
options?: Array<{ label: string; value: string | number; icon?: string; onlyV2?: boolean }>
|
||||||
|
}>
|
||||||
| ((
|
| ((
|
||||||
config: ConfigItem,
|
config: ConfigItem,
|
||||||
painting: Partial<PaintingAction>
|
painting: Partial<PaintingAction>
|
||||||
@ -32,189 +47,158 @@ export type ConfigItem = {
|
|||||||
disabled?: boolean | ((config: ConfigItem, painting: Partial<PaintingAction>) => boolean)
|
disabled?: boolean | ((config: ConfigItem, painting: Partial<PaintingAction>) => boolean)
|
||||||
initialValue?: string | number
|
initialValue?: string | number
|
||||||
required?: boolean
|
required?: boolean
|
||||||
|
condition?: (painting: PaintingAction) => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AihubmixMode = keyof PaintingsState
|
export type AihubmixMode = 'generate' | 'remix' | 'upscale'
|
||||||
|
|
||||||
// 创建配置项函数
|
// 创建配置项函数
|
||||||
export const createModeConfigs = (): Record<AihubmixMode, ConfigItem[]> => {
|
export const createModeConfigs = (): Record<AihubmixMode, ConfigItem[]> => {
|
||||||
return {
|
return {
|
||||||
paintings: [],
|
|
||||||
DMXAPIPaintings: [],
|
|
||||||
generate: [
|
generate: [
|
||||||
{ type: 'title', title: 'paintings.model', tooltip: 'paintings.generate.model_tip' },
|
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
key: 'model',
|
key: 'model',
|
||||||
|
title: 'paintings.model',
|
||||||
|
tooltip: 'paintings.generate.model_tip',
|
||||||
options: [
|
options: [
|
||||||
{ label: 'ideogram_V_3', value: 'V_3' },
|
{
|
||||||
{ label: 'ideogram_V_2', value: 'V_2' },
|
label: 'OpenAI',
|
||||||
{ label: 'ideogram_V_2_TURBO', value: 'V_2_TURBO' },
|
title: 'OpenAI',
|
||||||
{ label: 'ideogram_V_2A', value: 'V_2A' },
|
options: [{ label: 'gpt-image-1', value: 'gpt-image-1' }]
|
||||||
{ label: 'ideogram_V_2A_TURBO', value: 'V_2A_TURBO' },
|
},
|
||||||
{ label: 'ideogram_V_1', value: 'V_1' },
|
{
|
||||||
{ label: 'ideogram_V_1_TURBO', value: 'V_1_TURBO' }
|
label: 'ideogram',
|
||||||
|
title: 'ideogram',
|
||||||
|
options: [
|
||||||
|
{ label: 'ideogram_V_3', value: 'V_3' },
|
||||||
|
{ label: 'ideogram_V_2', value: 'V_2' },
|
||||||
|
{ label: 'ideogram_V_2_TURBO', value: 'V_2_TURBO' },
|
||||||
|
{ label: 'ideogram_V_2A', value: 'V_2A' },
|
||||||
|
{ label: 'ideogram_V_2A_TURBO', value: 'V_2A_TURBO' },
|
||||||
|
{ label: 'ideogram_V_1', value: 'V_1' },
|
||||||
|
{ label: 'ideogram_V_1_TURBO', value: 'V_1_TURBO' }
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ type: 'title', title: 'paintings.rendering_speed', tooltip: 'paintings.generate.rendering_speed_tip' },
|
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
key: 'renderingSpeed',
|
key: 'renderingSpeed',
|
||||||
|
title: 'paintings.rendering_speed',
|
||||||
|
tooltip: 'paintings.generate.rendering_speed_tip',
|
||||||
options: RENDERING_SPEED_OPTIONS,
|
options: RENDERING_SPEED_OPTIONS,
|
||||||
initialValue: 'DEFAULT',
|
initialValue: 'DEFAULT',
|
||||||
disabled: (_config, painting) => {
|
condition: (painting) => painting.model === 'V_3'
|
||||||
const model = painting?.model
|
|
||||||
return !model || !model.includes('V_3')
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{ type: 'title', title: 'paintings.aspect_ratio' },
|
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
key: 'aspectRatio',
|
key: 'aspectRatio',
|
||||||
options: ASPECT_RATIOS.map((size) => ({
|
title: 'paintings.aspect_ratio',
|
||||||
label: size.label,
|
options: ASPECT_RATIOS,
|
||||||
value: size.value,
|
condition: (painting) => Boolean(painting.model?.startsWith('V_'))
|
||||||
icon: size.icon
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'title',
|
|
||||||
title: 'paintings.number_images',
|
|
||||||
tooltip: 'paintings.generate.number_images_tip'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'slider',
|
type: 'slider',
|
||||||
key: 'numImages',
|
key: 'numImages',
|
||||||
|
title: 'paintings.number_images',
|
||||||
|
tooltip: 'paintings.generate.number_images_tip',
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 8
|
max: 8,
|
||||||
},
|
condition: (painting) => Boolean(painting.model?.startsWith('V_'))
|
||||||
{
|
|
||||||
type: 'title',
|
|
||||||
title: 'paintings.style_type',
|
|
||||||
tooltip: 'paintings.generate.style_type_tip'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
key: 'styleType',
|
key: 'styleType',
|
||||||
|
title: 'paintings.style_type',
|
||||||
|
tooltip: 'paintings.generate.style_type_tip',
|
||||||
options: (_config, painting) => {
|
options: (_config, painting) => {
|
||||||
// 根据模型选择显示不同的样式类型选项
|
// 根据模型选择显示不同的样式类型选项
|
||||||
return painting?.model?.includes('V_3') ? V3_STYLE_TYPES : STYLE_TYPES
|
return painting?.model?.includes('V_3') ? V3_STYLE_TYPES : STYLE_TYPES
|
||||||
},
|
},
|
||||||
disabled: false
|
disabled: false,
|
||||||
},
|
condition: (painting) => Boolean(painting.model?.startsWith('V_'))
|
||||||
{
|
|
||||||
type: 'title',
|
|
||||||
title: 'paintings.seed',
|
|
||||||
tooltip: 'paintings.generate.seed_tip'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
key: 'seed'
|
key: 'seed',
|
||||||
},
|
title: 'paintings.seed',
|
||||||
{
|
tooltip: 'paintings.generate.seed_tip',
|
||||||
type: 'title',
|
condition: (painting) => Boolean(painting.model?.startsWith('V_'))
|
||||||
title: 'paintings.negative_prompt',
|
|
||||||
tooltip: 'paintings.generate.negative_prompt_tip'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'textarea',
|
type: 'textarea',
|
||||||
key: 'negativePrompt'
|
key: 'negativePrompt',
|
||||||
},
|
title: 'paintings.negative_prompt',
|
||||||
{
|
tooltip: 'paintings.generate.negative_prompt_tip',
|
||||||
type: 'title',
|
condition: (painting) => Boolean(painting.model?.startsWith('V_'))
|
||||||
title: 'paintings.magic_prompt_option',
|
|
||||||
tooltip: 'paintings.generate.magic_prompt_option_tip'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'switch',
|
type: 'switch',
|
||||||
key: 'magicPromptOption'
|
key: 'magicPromptOption',
|
||||||
}
|
title: 'paintings.magic_prompt_option',
|
||||||
],
|
tooltip: 'paintings.generate.magic_prompt_option_tip',
|
||||||
edit: [
|
condition: (painting) => Boolean(painting.model?.startsWith('V_'))
|
||||||
{ type: 'title', title: 'paintings.edit.image_file' },
|
|
||||||
{
|
|
||||||
type: 'image',
|
|
||||||
key: 'imageFile'
|
|
||||||
},
|
},
|
||||||
{ type: 'title', title: 'paintings.model', tooltip: 'paintings.edit.model_tip' },
|
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
key: 'model',
|
key: 'size',
|
||||||
|
title: 'paintings.aspect_ratio',
|
||||||
options: [
|
options: [
|
||||||
{ label: 'ideogram_V_3', value: 'V_3' },
|
{ label: '自动', value: 'auto' },
|
||||||
{ label: 'ideogram_V_2', value: 'V_2' },
|
{ label: '1:1', value: '1024x1024' },
|
||||||
{ label: 'ideogram_V_2_TURBO', value: 'V_2_TURBO' },
|
{ label: '3:2', value: '1536x1024' },
|
||||||
{ label: 'ideogram_V_2A', value: 'V_2A' },
|
{ label: '2:3', value: '1024x1536' }
|
||||||
{ label: 'ideogram_V_2A_TURBO', value: 'V_2A_TURBO' },
|
],
|
||||||
{ label: 'ideogram_V_1', value: 'V_1' },
|
initialValue: '1024x1024',
|
||||||
{ label: 'ideogram_V_1_TURBO', value: 'V_1_TURBO' }
|
condition: (painting) => painting.model === 'gpt-image-1'
|
||||||
]
|
|
||||||
},
|
|
||||||
{ type: 'title', title: 'paintings.rendering_speed', tooltip: 'paintings.edit.rendering_speed_tip' },
|
|
||||||
{
|
|
||||||
type: 'select',
|
|
||||||
key: 'renderingSpeed',
|
|
||||||
options: RENDERING_SPEED_OPTIONS,
|
|
||||||
initialValue: 'DEFAULE',
|
|
||||||
disabled: (_config, painting) => {
|
|
||||||
const model = painting?.model
|
|
||||||
return !model || !model.includes('V_3')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'title',
|
|
||||||
title: 'paintings.number_images',
|
|
||||||
tooltip: 'paintings.edit.number_images_tip'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'slider',
|
type: 'slider',
|
||||||
key: 'numImages',
|
key: 'n',
|
||||||
|
title: 'paintings.number_images',
|
||||||
|
tooltip: 'paintings.generate.number_images_tip',
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 8
|
max: 10,
|
||||||
},
|
initialValue: 1,
|
||||||
{
|
condition: (painting) => painting.model === 'gpt-image-1'
|
||||||
type: 'title',
|
|
||||||
title: 'paintings.style_type',
|
|
||||||
tooltip: 'paintings.edit.style_type_tip'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
key: 'styleType',
|
key: 'quality',
|
||||||
options: (_config, painting) => {
|
title: 'paintings.quality',
|
||||||
// 根据模型选择显示不同的样式类型选项
|
options: QUALITY_OPTIONS,
|
||||||
return painting?.model?.includes('V_3') ? V3_STYLE_TYPES : STYLE_TYPES
|
initialValue: 'auto',
|
||||||
},
|
condition: (painting) => painting.model === 'gpt-image-1'
|
||||||
disabled: false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'title',
|
type: 'select',
|
||||||
title: 'paintings.seed',
|
key: 'moderation',
|
||||||
tooltip: 'paintings.edit.seed_tip'
|
title: 'paintings.moderation',
|
||||||
|
options: MODERATION_OPTIONS,
|
||||||
|
initialValue: 'auto',
|
||||||
|
condition: (painting) => painting.model === 'gpt-image-1'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'select',
|
||||||
key: 'seed'
|
key: 'background',
|
||||||
},
|
title: 'paintings.background',
|
||||||
{
|
options: BACKGROUND_OPTIONS,
|
||||||
type: 'title',
|
initialValue: 'auto',
|
||||||
title: 'paintings.magic_prompt_option',
|
condition: (painting) => painting.model === 'gpt-image-1'
|
||||||
tooltip: 'paintings.edit.magic_prompt_option_tip'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'switch',
|
|
||||||
key: 'magicPromptOption'
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
remix: [
|
remix: [
|
||||||
{ type: 'title', title: 'paintings.remix.image_file' },
|
|
||||||
{
|
{
|
||||||
type: 'image',
|
type: 'image',
|
||||||
key: 'imageFile'
|
key: 'imageFile',
|
||||||
|
title: 'paintings.remix.image_file'
|
||||||
},
|
},
|
||||||
{ type: 'title', title: 'paintings.model', tooltip: 'paintings.remix.model_tip' },
|
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
key: 'model',
|
key: 'model',
|
||||||
|
title: 'paintings.model',
|
||||||
|
tooltip: 'paintings.remix.model_tip',
|
||||||
options: [
|
options: [
|
||||||
{ label: 'ideogram_V_3', value: 'V_3' },
|
{ label: 'ideogram_V_3', value: 'V_3' },
|
||||||
{ label: 'ideogram_V_2', value: 'V_2' },
|
{ label: 'ideogram_V_2', value: 'V_2' },
|
||||||
@ -225,10 +209,10 @@ export const createModeConfigs = (): Record<AihubmixMode, ConfigItem[]> => {
|
|||||||
{ label: 'ideogram_V_1_TURBO', value: 'V_1_TURBO' }
|
{ label: 'ideogram_V_1_TURBO', value: 'V_1_TURBO' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ type: 'title', title: 'paintings.rendering_speed', tooltip: 'paintings.remix.rendering_speed_tip' },
|
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
key: 'renderingSpeed',
|
key: 'renderingSpeed',
|
||||||
|
title: 'paintings.rendering_speed',
|
||||||
options: RENDERING_SPEED_OPTIONS,
|
options: RENDERING_SPEED_OPTIONS,
|
||||||
initialValue: 'DEFAULT',
|
initialValue: 'DEFAULT',
|
||||||
disabled: (_config, painting) => {
|
disabled: (_config, painting) => {
|
||||||
@ -236,42 +220,32 @@ export const createModeConfigs = (): Record<AihubmixMode, ConfigItem[]> => {
|
|||||||
return !model || !model.includes('V_3')
|
return !model || !model.includes('V_3')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ type: 'title', title: 'paintings.aspect_ratio' },
|
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
key: 'aspectRatio',
|
key: 'aspectRatio',
|
||||||
options: ASPECT_RATIOS.map((size) => ({
|
title: 'paintings.aspect_ratio',
|
||||||
label: size.label,
|
options: ASPECT_RATIOS
|
||||||
value: size.value,
|
|
||||||
icon: size.icon
|
|
||||||
}))
|
|
||||||
},
|
},
|
||||||
{ type: 'title', title: 'paintings.remix.image_weight' },
|
|
||||||
{
|
{
|
||||||
type: 'slider',
|
type: 'slider',
|
||||||
key: 'imageWeight',
|
key: 'imageWeight',
|
||||||
|
title: 'paintings.remix.image_weight',
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 100
|
max: 100
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: 'title',
|
|
||||||
title: 'paintings.number_images',
|
|
||||||
tooltip: 'paintings.remix.number_images_tip'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: 'slider',
|
type: 'slider',
|
||||||
key: 'numImages',
|
key: 'numImages',
|
||||||
|
title: 'paintings.number_images',
|
||||||
|
tooltip: 'paintings.remix.number_images_tip',
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 8
|
max: 8
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: 'title',
|
|
||||||
title: 'paintings.style_type',
|
|
||||||
tooltip: 'paintings.remix.style_type_tip'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
key: 'styleType',
|
key: 'styleType',
|
||||||
|
title: 'paintings.style_type',
|
||||||
|
tooltip: 'paintings.remix.style_type_tip',
|
||||||
options: (_config, painting) => {
|
options: (_config, painting) => {
|
||||||
// 根据模型选择显示不同的样式类型选项
|
// 根据模型选择显示不同的样式类型选项
|
||||||
return painting?.model?.includes('V_3') ? V3_STYLE_TYPES : STYLE_TYPES
|
return painting?.model?.includes('V_3') ? V3_STYLE_TYPES : STYLE_TYPES
|
||||||
@ -279,78 +253,92 @@ export const createModeConfigs = (): Record<AihubmixMode, ConfigItem[]> => {
|
|||||||
disabled: false
|
disabled: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'title',
|
type: 'input',
|
||||||
|
key: 'seed',
|
||||||
title: 'paintings.seed',
|
title: 'paintings.seed',
|
||||||
tooltip: 'paintings.remix.seed_tip'
|
tooltip: 'paintings.remix.seed_tip'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'textarea',
|
||||||
key: 'seed'
|
key: 'negativePrompt',
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'title',
|
|
||||||
title: 'paintings.negative_prompt',
|
title: 'paintings.negative_prompt',
|
||||||
tooltip: 'paintings.remix.negative_prompt_tip'
|
tooltip: 'paintings.remix.negative_prompt_tip'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'textarea',
|
type: 'switch',
|
||||||
key: 'negativePrompt'
|
key: 'magicPromptOption',
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'title',
|
|
||||||
title: 'paintings.magic_prompt_option',
|
title: 'paintings.magic_prompt_option',
|
||||||
tooltip: 'paintings.remix.magic_prompt_option_tip'
|
tooltip: 'paintings.remix.magic_prompt_option_tip'
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'switch',
|
|
||||||
key: 'magicPromptOption'
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
upscale: [
|
upscale: [
|
||||||
{ type: 'title', title: 'paintings.upscale.image_file' },
|
|
||||||
{
|
{
|
||||||
type: 'image',
|
type: 'image',
|
||||||
key: 'imageFile',
|
key: 'imageFile',
|
||||||
|
title: 'paintings.upscale.image_file',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
{ type: 'title', title: 'paintings.upscale.resemblance', tooltip: 'paintings.upscale.resemblance_tip' },
|
|
||||||
{ type: 'slider', key: 'resemblance', min: 1, max: 100 },
|
|
||||||
{ type: 'title', title: 'paintings.upscale.detail', tooltip: 'paintings.upscale.detail_tip' },
|
|
||||||
{
|
{
|
||||||
type: 'slider',
|
type: 'slider',
|
||||||
key: 'detail',
|
key: 'resemblance',
|
||||||
|
title: 'paintings.upscale.resemblance',
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 100
|
max: 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'title',
|
type: 'slider',
|
||||||
title: 'paintings.number_images',
|
key: 'detail',
|
||||||
tooltip: 'paintings.upscale.number_images_tip'
|
title: 'paintings.upscale.detail',
|
||||||
|
tooltip: 'paintings.upscale.detail_tip',
|
||||||
|
min: 1,
|
||||||
|
max: 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'slider',
|
type: 'slider',
|
||||||
key: 'numImages',
|
key: 'numImages',
|
||||||
|
title: 'paintings.number_images',
|
||||||
|
tooltip: 'paintings.upscale.number_images_tip',
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 8
|
max: 8
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'title',
|
type: 'input',
|
||||||
|
key: 'seed',
|
||||||
title: 'paintings.seed',
|
title: 'paintings.seed',
|
||||||
tooltip: 'paintings.upscale.seed_tip'
|
tooltip: 'paintings.upscale.seed_tip'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'switch',
|
||||||
key: 'seed'
|
key: 'magicPromptOption',
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'title',
|
|
||||||
title: 'paintings.magic_prompt_option',
|
title: 'paintings.magic_prompt_option',
|
||||||
tooltip: 'paintings.upscale.magic_prompt_option_tip'
|
tooltip: 'paintings.upscale.magic_prompt_option_tip'
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'switch',
|
|
||||||
key: 'magicPromptOption'
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 几种默认的绘画配置
|
||||||
|
export const DEFAULT_PAINTING: PaintingAction = {
|
||||||
|
id: 'aihubmix_1',
|
||||||
|
model: 'gpt-image-1',
|
||||||
|
aspectRatio: 'ASPECT_1_1',
|
||||||
|
numImages: 1,
|
||||||
|
styleType: 'AUTO',
|
||||||
|
prompt: '',
|
||||||
|
negativePrompt: '',
|
||||||
|
magicPromptOption: true,
|
||||||
|
seed: '',
|
||||||
|
imageWeight: 50,
|
||||||
|
resemblance: 50,
|
||||||
|
detail: 50,
|
||||||
|
imageFile: undefined,
|
||||||
|
mask: undefined,
|
||||||
|
files: [],
|
||||||
|
urls: [],
|
||||||
|
renderingSpeed: 'DEFAULT',
|
||||||
|
size: '1024x1024',
|
||||||
|
background: 'auto',
|
||||||
|
quality: 'auto',
|
||||||
|
moderation: 'auto',
|
||||||
|
n: 1
|
||||||
|
}
|
||||||
|
|||||||
@ -1,87 +1,78 @@
|
|||||||
import ImageSize1_1 from '@renderer/assets/images/paintings/image-size-1-1.svg'
|
|
||||||
import ImageSize1_2 from '@renderer/assets/images/paintings/image-size-1-2.svg'
|
|
||||||
import ImageSize3_2 from '@renderer/assets/images/paintings/image-size-3-2.svg'
|
|
||||||
import ImageSize3_4 from '@renderer/assets/images/paintings/image-size-3-4.svg'
|
|
||||||
import ImageSize9_16 from '@renderer/assets/images/paintings/image-size-9-16.svg'
|
|
||||||
import ImageSize16_9 from '@renderer/assets/images/paintings/image-size-16-9.svg'
|
|
||||||
import type { PaintingAction } from '@renderer/types'
|
|
||||||
|
|
||||||
// 几种默认的绘画配置
|
|
||||||
export const DEFAULT_PAINTING: PaintingAction = {
|
|
||||||
id: 'aihubmix_1',
|
|
||||||
model: 'V_3',
|
|
||||||
aspectRatio: 'ASPECT_1_1',
|
|
||||||
numImages: 1,
|
|
||||||
styleType: 'AUTO',
|
|
||||||
prompt: '',
|
|
||||||
negativePrompt: '',
|
|
||||||
magicPromptOption: true,
|
|
||||||
seed: '',
|
|
||||||
imageWeight: 50,
|
|
||||||
resemblance: 50,
|
|
||||||
detail: 50,
|
|
||||||
imageFile: undefined,
|
|
||||||
mask: undefined,
|
|
||||||
files: [],
|
|
||||||
urls: [],
|
|
||||||
renderingSpeed: 'DEFAULT'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ASPECT_RATIOS = [
|
export const ASPECT_RATIOS = [
|
||||||
{
|
{
|
||||||
label: '1:1',
|
label: 'paintings.aspect_ratios.square',
|
||||||
value: 'ASPECT_1_1',
|
options: [
|
||||||
icon: ImageSize1_1
|
{
|
||||||
|
label: '1:1',
|
||||||
|
value: 'ASPECT_1_1'
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '3:1',
|
label: 'paintings.aspect_ratios.landscape',
|
||||||
value: 'ASPECT_3_1',
|
options: [
|
||||||
icon: ImageSize3_2
|
{
|
||||||
|
label: '1:2',
|
||||||
|
value: 'ASPECT_1_2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '1:3',
|
||||||
|
value: 'ASPECT_1_3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '2:3',
|
||||||
|
value: 'ASPECT_2_3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '3:4',
|
||||||
|
value: 'ASPECT_3_4'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '4:5',
|
||||||
|
value: 'ASPECT_4_5'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '9:16',
|
||||||
|
value: 'ASPECT_9_16'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '10:16',
|
||||||
|
value: 'ASPECT_10_16'
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '1:3',
|
label: 'paintings.aspect_ratios.landscape',
|
||||||
value: 'ASPECT_1_3',
|
options: [
|
||||||
icon: ImageSize1_2
|
{
|
||||||
},
|
label: '2:1',
|
||||||
{
|
value: 'ASPECT_2_1'
|
||||||
label: '3:2',
|
},
|
||||||
value: 'ASPECT_3_2',
|
{
|
||||||
icon: ImageSize3_2
|
label: '3:1',
|
||||||
},
|
value: 'ASPECT_3_1'
|
||||||
{
|
},
|
||||||
label: '2:3',
|
{
|
||||||
value: 'ASPECT_2_3',
|
label: '3:2',
|
||||||
icon: ImageSize1_2
|
value: 'ASPECT_3_2'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '4:3',
|
label: '4:3',
|
||||||
value: 'ASPECT_4_3',
|
value: 'ASPECT_4_3'
|
||||||
icon: ImageSize3_4
|
},
|
||||||
},
|
{
|
||||||
{
|
label: '5:4',
|
||||||
label: '3:4',
|
value: 'ASPECT_5_4'
|
||||||
value: 'ASPECT_3_4',
|
},
|
||||||
icon: ImageSize3_4
|
{
|
||||||
},
|
label: '16:9',
|
||||||
{
|
value: 'ASPECT_16_9'
|
||||||
label: '16:9',
|
},
|
||||||
value: 'ASPECT_16_9',
|
{
|
||||||
icon: ImageSize16_9
|
label: '16:10',
|
||||||
},
|
value: 'ASPECT_16_10'
|
||||||
{
|
}
|
||||||
label: '9:16',
|
]
|
||||||
value: 'ASPECT_9_16',
|
|
||||||
icon: ImageSize9_16
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '16:10',
|
|
||||||
value: 'ASPECT_16_10',
|
|
||||||
icon: ImageSize16_9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '10:16',
|
|
||||||
value: 'ASPECT_10_16',
|
|
||||||
icon: ImageSize9_16
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -132,3 +123,21 @@ export const RENDERING_SPEED_OPTIONS = [
|
|||||||
value: 'QUALITY'
|
value: 'QUALITY'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const QUALITY_OPTIONS = [
|
||||||
|
{ label: 'paintings.quality_options.auto', value: 'auto' },
|
||||||
|
{ label: 'paintings.quality_options.low', value: 'low' },
|
||||||
|
{ label: 'paintings.quality_options.medium', value: 'medium' },
|
||||||
|
{ label: 'paintings.quality_options.high', value: 'high' }
|
||||||
|
]
|
||||||
|
|
||||||
|
export const MODERATION_OPTIONS = [
|
||||||
|
{ label: 'paintings.moderation_options.auto', value: 'auto' },
|
||||||
|
{ label: 'paintings.moderation_options.low', value: 'low' }
|
||||||
|
]
|
||||||
|
|
||||||
|
export const BACKGROUND_OPTIONS = [
|
||||||
|
{ label: 'paintings.background_options.auto', value: 'auto' },
|
||||||
|
{ label: 'paintings.background_options.transparent', value: 'transparent' },
|
||||||
|
{ label: 'paintings.background_options.opaque', value: 'opaque' }
|
||||||
|
]
|
||||||
|
|||||||
@ -39,6 +39,22 @@ class FileManager {
|
|||||||
return fileData.data
|
return fileData.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async addBase64File(file: FileType): Promise<FileType> {
|
||||||
|
Logger.log(`[FileManager] Adding base64 file: ${JSON.stringify(file)}`)
|
||||||
|
|
||||||
|
const base64File = await window.api.file.base64File(file.id + file.ext)
|
||||||
|
const fileRecord = await db.files.get(base64File.id)
|
||||||
|
|
||||||
|
if (fileRecord) {
|
||||||
|
await db.files.update(fileRecord.id, { ...fileRecord, count: fileRecord.count + 1 })
|
||||||
|
return fileRecord
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.files.add(base64File)
|
||||||
|
|
||||||
|
return base64File
|
||||||
|
}
|
||||||
|
|
||||||
static async uploadFile(file: FileType): Promise<FileType> {
|
static async uploadFile(file: FileType): Promise<FileType> {
|
||||||
Logger.log(`[FileManager] Uploading file: ${JSON.stringify(file)}`)
|
Logger.log(`[FileManager] Uploading file: ${JSON.stringify(file)}`)
|
||||||
|
|
||||||
|
|||||||
@ -207,6 +207,11 @@ export interface GeneratePainting extends PaintingParams {
|
|||||||
negativePrompt?: string
|
negativePrompt?: string
|
||||||
magicPromptOption?: boolean
|
magicPromptOption?: boolean
|
||||||
renderingSpeed?: string
|
renderingSpeed?: string
|
||||||
|
quality?: string
|
||||||
|
moderation?: string
|
||||||
|
n?: number
|
||||||
|
size?: string
|
||||||
|
background?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EditPainting extends PaintingParams {
|
export interface EditPainting extends PaintingParams {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user