mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-29 05:51:26 +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_SaveImage = 'file:saveImage',
|
||||
File_Base64Image = 'file:base64Image',
|
||||
File_SaveBase64Image = 'file:saveBase64Image',
|
||||
File_Download = 'file:download',
|
||||
File_Copy = 'file:copy',
|
||||
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_SaveImage, fileManager.saveImage)
|
||||
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_Download, fileManager.downloadFile)
|
||||
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 }> => {
|
||||
const filePath = path.join(this.storageDir, id)
|
||||
const buffer = await fs.promises.readFile(filePath)
|
||||
|
||||
@ -76,7 +76,9 @@ const api = {
|
||||
selectFolder: () => ipcRenderer.invoke(IpcChannel.File_SelectFolder),
|
||||
saveImage: (name: string, data: string) => ipcRenderer.invoke(IpcChannel.File_SaveImage, name, data),
|
||||
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),
|
||||
binaryImage: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_BinaryImage, 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",
|
||||
"title": "Images",
|
||||
"magic_prompt_option": "Magic Prompt",
|
||||
"model": "Model Version",
|
||||
"model": "Model",
|
||||
"aspect_ratio": "Aspect Ratio",
|
||||
"style_type": "Style",
|
||||
"rendering_speed": "Rendering Speed",
|
||||
"learn_more": "Learn More",
|
||||
"paint_course":"tutorial",
|
||||
"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",
|
||||
"image_file_required": "Please upload an image first",
|
||||
@ -846,6 +846,29 @@
|
||||
"turbo": "Turbo",
|
||||
"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": {
|
||||
"generate": "Draw",
|
||||
"edit": "Edit",
|
||||
|
||||
@ -820,12 +820,12 @@
|
||||
"seed_desc_tip": "同じシードとプロンプトで類似した画像を生成できますが、-1 に設定すると毎回異なる結果が生成されます",
|
||||
"title": "画像",
|
||||
"magic_prompt_option": "プロンプト強化",
|
||||
"model": "モデルバージョン",
|
||||
"model": "モデル",
|
||||
"aspect_ratio": "画幅比例",
|
||||
"style_type": "スタイル",
|
||||
"learn_more": "詳しくはこちら",
|
||||
"prompt_placeholder_edit": "画像の説明を入力します。テキスト描画には '二重引用符' を使用します",
|
||||
"paint_course":"チュートリアル",
|
||||
"paint_course": "チュートリアル",
|
||||
"proxy_required": "現在、プロキシを開く必要があります。これは、将来サポートされる予定です",
|
||||
"image_file_required": "画像を先にアップロードしてください",
|
||||
"image_file_retry": "画像を先にアップロードしてください",
|
||||
@ -844,6 +844,29 @@
|
||||
"turbo": "高速",
|
||||
"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": {
|
||||
"generate": "画像生成",
|
||||
"edit": "部分編集",
|
||||
|
||||
@ -820,13 +820,13 @@
|
||||
"seed_desc_tip": "Одинаковые сиды и промпты могут генерировать похожие изображения, установка -1 будет создавать разные результаты каждый раз",
|
||||
"title": "Изображения",
|
||||
"magic_prompt_option": "Улучшение промпта",
|
||||
"model": "Версия",
|
||||
"model": "Модель",
|
||||
"aspect_ratio": "Пропорции изображения",
|
||||
"style_type": "Стиль",
|
||||
"rendering_speed": "Скорость рендеринга",
|
||||
"learn_more": "Узнать больше",
|
||||
"prompt_placeholder_edit": "Введите ваше описание изображения, текстовая отрисовка использует двойные кавычки для обертки",
|
||||
"paint_course":"Руководство / Учебник",
|
||||
"paint_course": "Руководство / Учебник",
|
||||
"proxy_required": "Сейчас необходимо открыть прокси для просмотра сгенерированных изображений, в будущем будет поддерживаться прямое соединение",
|
||||
"image_file_required": "Пожалуйста, сначала загрузите изображение",
|
||||
"image_file_retry": "Пожалуйста, сначала загрузите изображение",
|
||||
@ -841,11 +841,34 @@
|
||||
"3d": "3D",
|
||||
"anime": "Аниме"
|
||||
},
|
||||
"quality_options": {
|
||||
"auto": "Авто",
|
||||
"low": "Низкое",
|
||||
"medium": "Среднее",
|
||||
"high": "Высокое"
|
||||
},
|
||||
"moderation_options": {
|
||||
"auto": "Авто",
|
||||
"low": "Низкое"
|
||||
},
|
||||
"background_options": {
|
||||
"auto": "Авто",
|
||||
"transparent": "Прозрачный",
|
||||
"opaque": "Непрозрачный"
|
||||
},
|
||||
"rendering_speeds": {
|
||||
"default": "По умолчанию",
|
||||
"turbo": "Быстро",
|
||||
"quality": "Качественно"
|
||||
},
|
||||
"aspect_ratios": {
|
||||
"square": "Квадрат",
|
||||
"portrait": "Портрет",
|
||||
"landscape": "Пейзаж"
|
||||
},
|
||||
"quality": "Качество",
|
||||
"moderation": "Сенсорность",
|
||||
"background": "Фон",
|
||||
"mode": {
|
||||
"generate": "Рисование",
|
||||
"edit": "Редактирование",
|
||||
@ -892,7 +915,6 @@
|
||||
"seed_tip": "Контролирует случайный характер увеличения изображений для воспроизводимых результатов",
|
||||
"magic_prompt_option_tip": "Улучшает увеличение изображений с помощью интеллектуального оптимизирования промптов"
|
||||
},
|
||||
"rendering_speed": "Скорость рендеринга",
|
||||
"text_desc_required": "Пожалуйста, сначала введите описание изображения"
|
||||
},
|
||||
"prompts": {
|
||||
|
||||
@ -820,12 +820,12 @@
|
||||
"seed_desc_tip": "相同的种子和提示词可以生成相似的图片,设置 -1 每次生成都不一样",
|
||||
"title": "图片",
|
||||
"magic_prompt_option": "提示词增强",
|
||||
"model": "版本",
|
||||
"model": "模型",
|
||||
"aspect_ratio": "画幅比例",
|
||||
"style_type": "风格",
|
||||
"rendering_speed": "渲染速度",
|
||||
"learn_more": "了解更多",
|
||||
"paint_course":"教程",
|
||||
"paint_course": "教程",
|
||||
"prompt_placeholder_edit": "输入你的图片描述,文本绘制用 \"双引号\" 包裹",
|
||||
"proxy_required": "目前需要打开代理才能查看生成图片,后续会支持国内直连",
|
||||
"image_file_required": "请先上传图片",
|
||||
@ -846,11 +846,34 @@
|
||||
"turbo": "快速",
|
||||
"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": {
|
||||
"generate": "绘图",
|
||||
"edit": "编辑",
|
||||
"remix": "混合",
|
||||
"upscale": "放大"
|
||||
"upscale": "高清增强"
|
||||
},
|
||||
"generate": {
|
||||
"model_tip": "模型版本:V3 为最新版本,V2 为之前版本,V2A 为快速模型、V_1 为初代模型,_TURBO 为加速版本",
|
||||
|
||||
@ -821,12 +821,12 @@
|
||||
"seed_desc_tip": "相同的種子和提示詞可以生成相似的圖片,設置 -1 每次生成都不一樣",
|
||||
"title": "繪圖",
|
||||
"magic_prompt_option": "提示詞增強",
|
||||
"model": "版本",
|
||||
"model": "模型",
|
||||
"aspect_ratio": "畫幅比例",
|
||||
"style_type": "風格",
|
||||
"learn_more": "了解更多",
|
||||
"prompt_placeholder_edit": "輸入你的圖片描述,文本繪製用 '雙引號' 包裹",
|
||||
"paint_course":"教程",
|
||||
"paint_course": "教程",
|
||||
"proxy_required": "目前需要打開代理才能查看生成圖片,後續會支持國內直連",
|
||||
"image_file_required": "請先上傳圖片",
|
||||
"image_file_retry": "請重新上傳圖片",
|
||||
@ -846,6 +846,29 @@
|
||||
"turbo": "快速",
|
||||
"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": {
|
||||
"generate": "繪圖",
|
||||
"edit": "編輯",
|
||||
|
||||
@ -22,17 +22,16 @@ import { Avatar, Button, Input, InputNumber, Radio, Segmented, Select, Slider, S
|
||||
import TextArea from 'antd/es/input/TextArea'
|
||||
import { Info } from 'lucide-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 { useLocation, useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import SendMessageButton from '../home/Inputbar/SendMessageButton'
|
||||
import { SettingHelpLink, SettingTitle } from '../settings'
|
||||
import Artboard from './Artboard'
|
||||
import { type ConfigItem, createModeConfigs } from './config/aihubmixConfig'
|
||||
import { DEFAULT_PAINTING } from './config/constants'
|
||||
import PaintingsList from './PaintingsList'
|
||||
import Artboard from './components/Artboard'
|
||||
import PaintingsList from './components/PaintingsList'
|
||||
import { type ConfigItem, createModeConfigs, DEFAULT_PAINTING } from './config/aihubmixConfig'
|
||||
|
||||
// 使用函数创建配置项
|
||||
const modeConfigs = createModeConfigs()
|
||||
@ -74,12 +73,13 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
{ label: t('paintings.mode.upscale'), value: 'upscale' }
|
||||
]
|
||||
|
||||
const getNewPainting = () => {
|
||||
const getNewPainting = useCallback(() => {
|
||||
return {
|
||||
...DEFAULT_PAINTING,
|
||||
model: mode === 'generate' ? 'gpt-image-1' : 'V_3',
|
||||
id: uuid()
|
||||
}
|
||||
}
|
||||
}, [mode])
|
||||
|
||||
const textareaRef = useRef<any>(null)
|
||||
|
||||
@ -89,6 +89,47 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
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 () => {
|
||||
if (painting.files.length > 0) {
|
||||
const confirmed = await window.modal.confirm({
|
||||
@ -129,11 +170,11 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
dispatch(setGenerating(true))
|
||||
|
||||
let body: string | FormData = ''
|
||||
const headers: Record<string, string> = {
|
||||
let headers: Record<string, string> = {
|
||||
'Api-Key': aihubmixProvider.apiKey
|
||||
}
|
||||
let url = aihubmixProvider.apiHost + `/ideogram/` + mode
|
||||
|
||||
// 不使用 AiProvider 的通用规则,而是直接调用自定义接口
|
||||
try {
|
||||
if (mode === 'generate') {
|
||||
if (painting.model === 'V_3') {
|
||||
@ -214,67 +255,47 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
console.log('V3 API响应:', data)
|
||||
const urls = data.data.map((item) => item.url)
|
||||
|
||||
// Rest of the code for handling image downloads is the same
|
||||
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)
|
||||
const validFiles = await downloadImages(urls)
|
||||
await FileManager.addFiles(validFiles)
|
||||
updatePaintingState({ files: validFiles, urls })
|
||||
}
|
||||
return
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error && error.name !== 'AbortError') {
|
||||
window.modal.error({
|
||||
content: getErrorMessage(error),
|
||||
centered: true
|
||||
})
|
||||
}
|
||||
handleError(error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
dispatch(setGenerating(false))
|
||||
setAbortController(null)
|
||||
}
|
||||
} else {
|
||||
// Existing V1/V2 API
|
||||
const requestData = {
|
||||
image_request: {
|
||||
let requestData: any = {}
|
||||
if (painting.model === 'gpt-image-1') {
|
||||
requestData = {
|
||||
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'
|
||||
size: painting.size === 'auto' ? undefined : painting.size,
|
||||
n: painting.n,
|
||||
quality: painting.quality,
|
||||
moderation: painting.moderation
|
||||
}
|
||||
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)
|
||||
@ -352,37 +373,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
|
||||
// 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)
|
||||
const validFiles = await downloadImages(urls)
|
||||
await FileManager.addFiles(validFiles)
|
||||
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)
|
||||
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') {
|
||||
if (!painting.imageFile) {
|
||||
window.modal.error({
|
||||
@ -549,9 +427,9 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
}
|
||||
|
||||
// 只针对非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) {
|
||||
const errorData = await response.json()
|
||||
@ -561,53 +439,27 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
|
||||
const data = await response.json()
|
||||
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) {
|
||||
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 = await downloadImages(urls)
|
||||
await FileManager.addFiles(validFiles)
|
||||
updatePaintingState({ files: validFiles, urls })
|
||||
}
|
||||
|
||||
if (base64s?.length > 0) {
|
||||
const validFiles = await Promise.all(
|
||||
base64s.map(async (base64) => {
|
||||
return await window.api.file.saveBase64Image(base64)
|
||||
})
|
||||
)
|
||||
|
||||
const validFiles = downloadedFiles.filter((file): file is FileType => file !== null)
|
||||
|
||||
await FileManager.addFiles(validFiles)
|
||||
|
||||
updatePaintingState({ files: validFiles, urls })
|
||||
updatePaintingState({ files: validFiles, urls: validFiles.map((file) => file.name) })
|
||||
}
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error && error.name !== 'AbortError') {
|
||||
window.modal.error({
|
||||
content: getErrorMessage(error),
|
||||
centered: true
|
||||
})
|
||||
}
|
||||
handleError(error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
dispatch(setGenerating(false))
|
||||
@ -617,43 +469,15 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
|
||||
const handleRetry = async (painting: PaintingAction) => {
|
||||
setIsLoading(true)
|
||||
const downloadedFiles = await Promise.all(
|
||||
painting.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'
|
||||
})
|
||||
}
|
||||
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)
|
||||
try {
|
||||
const validFiles = await downloadImages(painting.urls)
|
||||
await FileManager.addFiles(validFiles)
|
||||
updatePaintingState({ files: validFiles, urls: painting.urls })
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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': {
|
||||
// 处理函数类型的disabled属性
|
||||
const isDisabled = typeof item.disabled === 'function' ? item.disabled(item, painting) : item.disabled
|
||||
@ -786,10 +598,11 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
|
||||
return (
|
||||
<Select
|
||||
key={index}
|
||||
style={{ width: '100%' }}
|
||||
listHeight={500}
|
||||
disabled={isDisabled}
|
||||
value={painting[item.key!] || item.initialValue}
|
||||
options={selectOptions}
|
||||
options={selectOptions as any}
|
||||
onChange={(v) => updatePaintingState({ [item.key!]: v })}
|
||||
/>
|
||||
)
|
||||
@ -809,8 +622,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
|
||||
return (
|
||||
<Radio.Group
|
||||
key={index}
|
||||
value={painting[item.key!]}
|
||||
value={painting[item.key!] || item.initialValue}
|
||||
onChange={(e) => updatePaintingState({ [item.key!]: e.target.value })}>
|
||||
{radioOptions!.map((option) => (
|
||||
<Radio.Button key={option.value} value={option.value}>
|
||||
@ -822,96 +634,82 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
}
|
||||
case 'slider': {
|
||||
return (
|
||||
<SliderContainer key={index}>
|
||||
<SliderContainer>
|
||||
<Slider
|
||||
min={item.min}
|
||||
max={item.max}
|
||||
step={item.step}
|
||||
value={painting[item.key!] as number}
|
||||
value={(painting[item.key!] || item.initialValue) as number}
|
||||
onChange={(v) => updatePaintingState({ [item.key!]: v })}
|
||||
/>
|
||||
<StyledInputNumber
|
||||
min={item.min}
|
||||
max={item.max}
|
||||
step={item.step}
|
||||
value={painting[item.key!] as number}
|
||||
value={(painting[item.key!] || item.initialValue) as number}
|
||||
onChange={(v) => updatePaintingState({ [item.key!]: v })}
|
||||
/>
|
||||
</SliderContainer>
|
||||
)
|
||||
}
|
||||
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)' }} />
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case 'input':
|
||||
return (
|
||||
<Input
|
||||
key={index}
|
||||
value={painting[item.key!] as string}
|
||||
value={(painting[item.key!] || item.initialValue) as string}
|
||||
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 (
|
||||
<InputNumber
|
||||
key={index}
|
||||
min={item.min}
|
||||
max={item.max}
|
||||
style={{ width: '100%' }}
|
||||
value={painting[item.key!] as number}
|
||||
value={(painting[item.key!] || item.initialValue) as number}
|
||||
onChange={(v) => updatePaintingState({ [item.key!]: v })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case 'textarea': {
|
||||
case 'textarea':
|
||||
return (
|
||||
<TextArea
|
||||
key={index}
|
||||
value={painting[item.key!] as string}
|
||||
value={(painting[item.key!] || item.initialValue) as string}
|
||||
onChange={(e) => updatePaintingState({ [item.key!]: e.target.value })}
|
||||
spellCheck={false}
|
||||
rows={4}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case 'switch': {
|
||||
case 'switch':
|
||||
return (
|
||||
<HStack key={index}>
|
||||
<HStack>
|
||||
<Switch
|
||||
checked={painting[item.key!] as boolean}
|
||||
checked={(painting[item.key!] || item.initialValue) as boolean}
|
||||
onChange={(checked) => updatePaintingState({ [item.key!]: checked })}
|
||||
/>
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
case 'image': {
|
||||
return (
|
||||
<ImageUploadButton
|
||||
key={index}
|
||||
accept="image/png, image/jpeg, image/gif"
|
||||
maxCount={1}
|
||||
showUploadList={false}
|
||||
listType="picture-card"
|
||||
onChange={async ({ file }) => {
|
||||
const path = file.originFileObj?.path || ''
|
||||
setFileMap({ ...fileMap, [path]: file.originFileObj as unknown as FileType })
|
||||
beforeUpload={(file) => {
|
||||
const path = URL.createObjectURL(file)
|
||||
setFileMap({ ...fileMap, [path]: file as unknown as FileType })
|
||||
updatePaintingState({ [item.key!]: path })
|
||||
return false // 阻止默认上传行为
|
||||
}}>
|
||||
{painting[item.key!] ? (
|
||||
<ImagePreview>
|
||||
<img src={'file://' + painting[item.key!]} alt="预览图" />
|
||||
<img src={painting[item.key!]} alt="预览图" />
|
||||
</ImagePreview>
|
||||
) : (
|
||||
<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) => {
|
||||
if (generating) return
|
||||
setPainting(newPainting)
|
||||
@ -936,12 +751,13 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
addPainting(mode, newPainting)
|
||||
setPainting(newPainting)
|
||||
}
|
||||
}, [filteredPaintings, mode, addPainting, painting])
|
||||
}, [filteredPaintings, mode, addPainting, painting, getNewPainting])
|
||||
|
||||
useEffect(() => {
|
||||
const timer = spaceClickTimer.current
|
||||
return () => {
|
||||
if (spaceClickTimer.current) {
|
||||
clearTimeout(spaceClickTimer.current)
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
@ -985,7 +801,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
</Select>
|
||||
|
||||
{/* 使用JSON配置渲染设置项 */}
|
||||
{modeConfigs[mode].map(renderConfigItem)}
|
||||
{modeConfigs[mode].filter((item) => (item.condition ? item.condition(painting) : true)).map(renderConfigItem)}
|
||||
</LeftContainer>
|
||||
<MainContainer>
|
||||
{/* 添加功能切换分段控制器 */}
|
||||
|
||||
@ -26,7 +26,8 @@ import styled from 'styled-components'
|
||||
|
||||
import SendMessageButton from '../home/Inputbar/SendMessageButton'
|
||||
import { SettingHelpLink, SettingTitle } from '../settings'
|
||||
import Artboard from './Artboard'
|
||||
import Artboard from './components/Artboard'
|
||||
import PaintingsList from './components/PaintingsList'
|
||||
import {
|
||||
COURSE_URL,
|
||||
DEFAULT_PAINTING,
|
||||
@ -34,7 +35,6 @@ import {
|
||||
STYLE_TYPE_OPTIONS,
|
||||
TEXT_TO_IMAGES_MODELS
|
||||
} from './config/DmxapiConfig'
|
||||
import PaintingsList from './PaintingsList'
|
||||
|
||||
const generateRandomSeed = () => Math.floor(Math.random() * 1000000).toString()
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import { Route, Routes } from 'react-router-dom'
|
||||
|
||||
import AihubmixPage from './AihubmixPage'
|
||||
import DmxapiPage from './DmxapiPage'
|
||||
import SiliconPage from './PaintingsPage'
|
||||
import SiliconPage from './SiliconPage'
|
||||
|
||||
const Options = ['aihubmix', 'silicon', 'dmxapi']
|
||||
|
||||
|
||||
@ -35,8 +35,8 @@ import styled from 'styled-components'
|
||||
|
||||
import SendMessageButton from '../home/Inputbar/SendMessageButton'
|
||||
import { SettingTitle } from '../settings'
|
||||
import Artboard from './Artboard'
|
||||
import PaintingsList from './PaintingsList'
|
||||
import Artboard from './components/Artboard'
|
||||
import PaintingsList from './components/PaintingsList'
|
||||
|
||||
const IMAGE_SIZES = [
|
||||
{
|
||||
@ -88,7 +88,7 @@ const DEFAULT_PAINTING: Painting = {
|
||||
|
||||
// let _painting: Painting
|
||||
|
||||
const PaintingsPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
const { t } = useTranslation()
|
||||
const { paintings, addPainting, removePainting, updatePainting } = usePaintings()
|
||||
const [painting, setPainting] = useState<Painting>(paintings[0] || DEFAULT_PAINTING)
|
||||
@ -645,4 +645,4 @@ const StyledInputNumber = styled(InputNumber)`
|
||||
width: 70px;
|
||||
`
|
||||
|
||||
export default PaintingsPage
|
||||
export default SiliconPage
|
||||
@ -7,7 +7,7 @@ import React, { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import ImagePreview from '../home/Markdown/ImagePreview'
|
||||
import ImagePreview from '../../home/Markdown/ImagePreview'
|
||||
|
||||
interface ArtboardProps {
|
||||
painting: Painting
|
||||
@ -98,8 +98,8 @@ const Artboard: FC<ArtboardProps> = ({
|
||||
{painting.urls.length > 0 && retry ? (
|
||||
<div>
|
||||
<ImageList>
|
||||
{painting.urls.map((url) => (
|
||||
<ImageListItem key={url}>{url}</ImageListItem>
|
||||
{painting.urls.map((url, index) => (
|
||||
<ImageListItem key={url || index}>{url}</ImageListItem>
|
||||
))}
|
||||
</ImageList>
|
||||
<div>
|
||||
@ -109,11 +109,11 @@ const Artboard: FC<ArtboardProps> = ({
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : imageCover ? (
|
||||
imageCover
|
||||
) : (
|
||||
imageCover ?
|
||||
imageCover:
|
||||
(<div>{t('paintings.image_placeholder')}</div>)
|
||||
)}
|
||||
<div>{t('paintings.image_placeholder')}</div>
|
||||
)}
|
||||
</ImagePlaceholder>
|
||||
)}
|
||||
{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 = {
|
||||
@ -19,7 +27,14 @@ export type ConfigItem = {
|
||||
title?: string
|
||||
tooltip?: string
|
||||
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,
|
||||
painting: Partial<PaintingAction>
|
||||
@ -32,189 +47,158 @@ export type ConfigItem = {
|
||||
disabled?: boolean | ((config: ConfigItem, painting: Partial<PaintingAction>) => boolean)
|
||||
initialValue?: string | number
|
||||
required?: boolean
|
||||
condition?: (painting: PaintingAction) => boolean
|
||||
}
|
||||
|
||||
export type AihubmixMode = keyof PaintingsState
|
||||
export type AihubmixMode = 'generate' | 'remix' | 'upscale'
|
||||
|
||||
// 创建配置项函数
|
||||
export const createModeConfigs = (): Record<AihubmixMode, ConfigItem[]> => {
|
||||
return {
|
||||
paintings: [],
|
||||
DMXAPIPaintings: [],
|
||||
generate: [
|
||||
{ type: 'title', title: 'paintings.model', tooltip: 'paintings.generate.model_tip' },
|
||||
{
|
||||
type: 'select',
|
||||
key: 'model',
|
||||
title: 'paintings.model',
|
||||
tooltip: 'paintings.generate.model_tip',
|
||||
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' }
|
||||
{
|
||||
label: 'OpenAI',
|
||||
title: 'OpenAI',
|
||||
options: [{ label: 'gpt-image-1', value: 'gpt-image-1' }]
|
||||
},
|
||||
{
|
||||
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',
|
||||
key: 'renderingSpeed',
|
||||
title: 'paintings.rendering_speed',
|
||||
tooltip: 'paintings.generate.rendering_speed_tip',
|
||||
options: RENDERING_SPEED_OPTIONS,
|
||||
initialValue: 'DEFAULT',
|
||||
disabled: (_config, painting) => {
|
||||
const model = painting?.model
|
||||
return !model || !model.includes('V_3')
|
||||
}
|
||||
condition: (painting) => painting.model === 'V_3'
|
||||
},
|
||||
{ type: 'title', title: 'paintings.aspect_ratio' },
|
||||
{
|
||||
type: 'select',
|
||||
key: 'aspectRatio',
|
||||
options: ASPECT_RATIOS.map((size) => ({
|
||||
label: size.label,
|
||||
value: size.value,
|
||||
icon: size.icon
|
||||
}))
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
title: 'paintings.number_images',
|
||||
tooltip: 'paintings.generate.number_images_tip'
|
||||
title: 'paintings.aspect_ratio',
|
||||
options: ASPECT_RATIOS,
|
||||
condition: (painting) => Boolean(painting.model?.startsWith('V_'))
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
key: 'numImages',
|
||||
title: 'paintings.number_images',
|
||||
tooltip: 'paintings.generate.number_images_tip',
|
||||
min: 1,
|
||||
max: 8
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
title: 'paintings.style_type',
|
||||
tooltip: 'paintings.generate.style_type_tip'
|
||||
max: 8,
|
||||
condition: (painting) => Boolean(painting.model?.startsWith('V_'))
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
key: 'styleType',
|
||||
title: 'paintings.style_type',
|
||||
tooltip: 'paintings.generate.style_type_tip',
|
||||
options: (_config, painting) => {
|
||||
// 根据模型选择显示不同的样式类型选项
|
||||
return painting?.model?.includes('V_3') ? V3_STYLE_TYPES : STYLE_TYPES
|
||||
},
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
title: 'paintings.seed',
|
||||
tooltip: 'paintings.generate.seed_tip'
|
||||
disabled: false,
|
||||
condition: (painting) => Boolean(painting.model?.startsWith('V_'))
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
key: 'seed'
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
title: 'paintings.negative_prompt',
|
||||
tooltip: 'paintings.generate.negative_prompt_tip'
|
||||
key: 'seed',
|
||||
title: 'paintings.seed',
|
||||
tooltip: 'paintings.generate.seed_tip',
|
||||
condition: (painting) => Boolean(painting.model?.startsWith('V_'))
|
||||
},
|
||||
{
|
||||
type: 'textarea',
|
||||
key: 'negativePrompt'
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
title: 'paintings.magic_prompt_option',
|
||||
tooltip: 'paintings.generate.magic_prompt_option_tip'
|
||||
key: 'negativePrompt',
|
||||
title: 'paintings.negative_prompt',
|
||||
tooltip: 'paintings.generate.negative_prompt_tip',
|
||||
condition: (painting) => Boolean(painting.model?.startsWith('V_'))
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
key: 'magicPromptOption'
|
||||
}
|
||||
],
|
||||
edit: [
|
||||
{ type: 'title', title: 'paintings.edit.image_file' },
|
||||
{
|
||||
type: 'image',
|
||||
key: 'imageFile'
|
||||
key: 'magicPromptOption',
|
||||
title: 'paintings.magic_prompt_option',
|
||||
tooltip: 'paintings.generate.magic_prompt_option_tip',
|
||||
condition: (painting) => Boolean(painting.model?.startsWith('V_'))
|
||||
},
|
||||
{ type: 'title', title: 'paintings.model', tooltip: 'paintings.edit.model_tip' },
|
||||
{
|
||||
type: 'select',
|
||||
key: 'model',
|
||||
key: 'size',
|
||||
title: 'paintings.aspect_ratio',
|
||||
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.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'
|
||||
{ label: '自动', value: 'auto' },
|
||||
{ label: '1:1', value: '1024x1024' },
|
||||
{ label: '3:2', value: '1536x1024' },
|
||||
{ label: '2:3', value: '1024x1536' }
|
||||
],
|
||||
initialValue: '1024x1024',
|
||||
condition: (painting) => painting.model === 'gpt-image-1'
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
key: 'numImages',
|
||||
key: 'n',
|
||||
title: 'paintings.number_images',
|
||||
tooltip: 'paintings.generate.number_images_tip',
|
||||
min: 1,
|
||||
max: 8
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
title: 'paintings.style_type',
|
||||
tooltip: 'paintings.edit.style_type_tip'
|
||||
max: 10,
|
||||
initialValue: 1,
|
||||
condition: (painting) => painting.model === 'gpt-image-1'
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
key: 'styleType',
|
||||
options: (_config, painting) => {
|
||||
// 根据模型选择显示不同的样式类型选项
|
||||
return painting?.model?.includes('V_3') ? V3_STYLE_TYPES : STYLE_TYPES
|
||||
},
|
||||
disabled: false
|
||||
key: 'quality',
|
||||
title: 'paintings.quality',
|
||||
options: QUALITY_OPTIONS,
|
||||
initialValue: 'auto',
|
||||
condition: (painting) => painting.model === 'gpt-image-1'
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
title: 'paintings.seed',
|
||||
tooltip: 'paintings.edit.seed_tip'
|
||||
type: 'select',
|
||||
key: 'moderation',
|
||||
title: 'paintings.moderation',
|
||||
options: MODERATION_OPTIONS,
|
||||
initialValue: 'auto',
|
||||
condition: (painting) => painting.model === 'gpt-image-1'
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
key: 'seed'
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
title: 'paintings.magic_prompt_option',
|
||||
tooltip: 'paintings.edit.magic_prompt_option_tip'
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
key: 'magicPromptOption'
|
||||
type: 'select',
|
||||
key: 'background',
|
||||
title: 'paintings.background',
|
||||
options: BACKGROUND_OPTIONS,
|
||||
initialValue: 'auto',
|
||||
condition: (painting) => painting.model === 'gpt-image-1'
|
||||
}
|
||||
],
|
||||
remix: [
|
||||
{ type: 'title', title: 'paintings.remix.image_file' },
|
||||
{
|
||||
type: 'image',
|
||||
key: 'imageFile'
|
||||
key: 'imageFile',
|
||||
title: 'paintings.remix.image_file'
|
||||
},
|
||||
{ type: 'title', title: 'paintings.model', tooltip: 'paintings.remix.model_tip' },
|
||||
{
|
||||
type: 'select',
|
||||
key: 'model',
|
||||
title: 'paintings.model',
|
||||
tooltip: 'paintings.remix.model_tip',
|
||||
options: [
|
||||
{ label: 'ideogram_V_3', value: 'V_3' },
|
||||
{ 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' }
|
||||
]
|
||||
},
|
||||
{ type: 'title', title: 'paintings.rendering_speed', tooltip: 'paintings.remix.rendering_speed_tip' },
|
||||
{
|
||||
type: 'select',
|
||||
key: 'renderingSpeed',
|
||||
title: 'paintings.rendering_speed',
|
||||
options: RENDERING_SPEED_OPTIONS,
|
||||
initialValue: 'DEFAULT',
|
||||
disabled: (_config, painting) => {
|
||||
@ -236,42 +220,32 @@ export const createModeConfigs = (): Record<AihubmixMode, ConfigItem[]> => {
|
||||
return !model || !model.includes('V_3')
|
||||
}
|
||||
},
|
||||
{ type: 'title', title: 'paintings.aspect_ratio' },
|
||||
{
|
||||
type: 'select',
|
||||
key: 'aspectRatio',
|
||||
options: ASPECT_RATIOS.map((size) => ({
|
||||
label: size.label,
|
||||
value: size.value,
|
||||
icon: size.icon
|
||||
}))
|
||||
title: 'paintings.aspect_ratio',
|
||||
options: ASPECT_RATIOS
|
||||
},
|
||||
{ type: 'title', title: 'paintings.remix.image_weight' },
|
||||
{
|
||||
type: 'slider',
|
||||
key: 'imageWeight',
|
||||
title: 'paintings.remix.image_weight',
|
||||
min: 1,
|
||||
max: 100
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
title: 'paintings.number_images',
|
||||
tooltip: 'paintings.remix.number_images_tip'
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
key: 'numImages',
|
||||
title: 'paintings.number_images',
|
||||
tooltip: 'paintings.remix.number_images_tip',
|
||||
min: 1,
|
||||
max: 8
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
title: 'paintings.style_type',
|
||||
tooltip: 'paintings.remix.style_type_tip'
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
key: 'styleType',
|
||||
title: 'paintings.style_type',
|
||||
tooltip: 'paintings.remix.style_type_tip',
|
||||
options: (_config, painting) => {
|
||||
// 根据模型选择显示不同的样式类型选项
|
||||
return painting?.model?.includes('V_3') ? V3_STYLE_TYPES : STYLE_TYPES
|
||||
@ -279,78 +253,92 @@ export const createModeConfigs = (): Record<AihubmixMode, ConfigItem[]> => {
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
type: 'input',
|
||||
key: 'seed',
|
||||
title: 'paintings.seed',
|
||||
tooltip: 'paintings.remix.seed_tip'
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
key: 'seed'
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
type: 'textarea',
|
||||
key: 'negativePrompt',
|
||||
title: 'paintings.negative_prompt',
|
||||
tooltip: 'paintings.remix.negative_prompt_tip'
|
||||
},
|
||||
{
|
||||
type: 'textarea',
|
||||
key: 'negativePrompt'
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
type: 'switch',
|
||||
key: 'magicPromptOption',
|
||||
title: 'paintings.magic_prompt_option',
|
||||
tooltip: 'paintings.remix.magic_prompt_option_tip'
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
key: 'magicPromptOption'
|
||||
}
|
||||
],
|
||||
upscale: [
|
||||
{ type: 'title', title: 'paintings.upscale.image_file' },
|
||||
{
|
||||
type: 'image',
|
||||
key: 'imageFile',
|
||||
title: 'paintings.upscale.image_file',
|
||||
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',
|
||||
key: 'detail',
|
||||
key: 'resemblance',
|
||||
title: 'paintings.upscale.resemblance',
|
||||
min: 1,
|
||||
max: 100
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
title: 'paintings.number_images',
|
||||
tooltip: 'paintings.upscale.number_images_tip'
|
||||
type: 'slider',
|
||||
key: 'detail',
|
||||
title: 'paintings.upscale.detail',
|
||||
tooltip: 'paintings.upscale.detail_tip',
|
||||
min: 1,
|
||||
max: 100
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
key: 'numImages',
|
||||
title: 'paintings.number_images',
|
||||
tooltip: 'paintings.upscale.number_images_tip',
|
||||
min: 1,
|
||||
max: 8
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
type: 'input',
|
||||
key: 'seed',
|
||||
title: 'paintings.seed',
|
||||
tooltip: 'paintings.upscale.seed_tip'
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
key: 'seed'
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
type: 'switch',
|
||||
key: 'magicPromptOption',
|
||||
title: 'paintings.magic_prompt_option',
|
||||
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 = [
|
||||
{
|
||||
label: '1:1',
|
||||
value: 'ASPECT_1_1',
|
||||
icon: ImageSize1_1
|
||||
label: 'paintings.aspect_ratios.square',
|
||||
options: [
|
||||
{
|
||||
label: '1:1',
|
||||
value: 'ASPECT_1_1'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '3:1',
|
||||
value: 'ASPECT_3_1',
|
||||
icon: ImageSize3_2
|
||||
label: 'paintings.aspect_ratios.landscape',
|
||||
options: [
|
||||
{
|
||||
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',
|
||||
value: 'ASPECT_1_3',
|
||||
icon: ImageSize1_2
|
||||
},
|
||||
{
|
||||
label: '3:2',
|
||||
value: 'ASPECT_3_2',
|
||||
icon: ImageSize3_2
|
||||
},
|
||||
{
|
||||
label: '2:3',
|
||||
value: 'ASPECT_2_3',
|
||||
icon: ImageSize1_2
|
||||
},
|
||||
{
|
||||
label: '4:3',
|
||||
value: 'ASPECT_4_3',
|
||||
icon: ImageSize3_4
|
||||
},
|
||||
{
|
||||
label: '3:4',
|
||||
value: 'ASPECT_3_4',
|
||||
icon: ImageSize3_4
|
||||
},
|
||||
{
|
||||
label: '16:9',
|
||||
value: 'ASPECT_16_9',
|
||||
icon: ImageSize16_9
|
||||
},
|
||||
{
|
||||
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
|
||||
label: 'paintings.aspect_ratios.landscape',
|
||||
options: [
|
||||
{
|
||||
label: '2:1',
|
||||
value: 'ASPECT_2_1'
|
||||
},
|
||||
{
|
||||
label: '3:1',
|
||||
value: 'ASPECT_3_1'
|
||||
},
|
||||
{
|
||||
label: '3:2',
|
||||
value: 'ASPECT_3_2'
|
||||
},
|
||||
{
|
||||
label: '4:3',
|
||||
value: 'ASPECT_4_3'
|
||||
},
|
||||
{
|
||||
label: '5:4',
|
||||
value: 'ASPECT_5_4'
|
||||
},
|
||||
{
|
||||
label: '16:9',
|
||||
value: 'ASPECT_16_9'
|
||||
},
|
||||
{
|
||||
label: '16:10',
|
||||
value: 'ASPECT_16_10'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@ -132,3 +123,21 @@ export const RENDERING_SPEED_OPTIONS = [
|
||||
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
|
||||
}
|
||||
|
||||
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> {
|
||||
Logger.log(`[FileManager] Uploading file: ${JSON.stringify(file)}`)
|
||||
|
||||
|
||||
@ -207,6 +207,11 @@ export interface GeneratePainting extends PaintingParams {
|
||||
negativePrompt?: string
|
||||
magicPromptOption?: boolean
|
||||
renderingSpeed?: string
|
||||
quality?: string
|
||||
moderation?: string
|
||||
n?: number
|
||||
size?: string
|
||||
background?: string
|
||||
}
|
||||
|
||||
export interface EditPainting extends PaintingParams {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user