mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-10 15:49:29 +08:00
parent
79d7ffcbad
commit
a244057b3a
@ -1703,24 +1703,6 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
|||||||
name: 'ERNIE-Speed-128K',
|
name: 'ERNIE-Speed-128K',
|
||||||
group: '免费模型'
|
group: '免费模型'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'THUDM/glm-4-9b-chat',
|
|
||||||
provider: 'dmxapi',
|
|
||||||
name: 'THUDM/glm-4-9b-chat',
|
|
||||||
group: '免费模型'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'glm-4-flash',
|
|
||||||
provider: 'dmxapi',
|
|
||||||
name: 'glm-4-flash',
|
|
||||||
group: '免费模型'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'hunyuan-lite',
|
|
||||||
provider: 'dmxapi',
|
|
||||||
name: 'hunyuan-lite',
|
|
||||||
group: '免费模型'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'gpt-4o',
|
id: 'gpt-4o',
|
||||||
provider: 'dmxapi',
|
provider: 'dmxapi',
|
||||||
|
|||||||
@ -940,7 +940,10 @@
|
|||||||
"magic_prompt_option_tip": "Intelligently enhances upscaling prompts"
|
"magic_prompt_option_tip": "Intelligently enhances upscaling prompts"
|
||||||
},
|
},
|
||||||
"text_desc_required": "Please enter image description first",
|
"text_desc_required": "Please enter image description first",
|
||||||
|
"image_handle_required": "Please upload an image first.",
|
||||||
"req_error_text": "Operation failed. Please try again. Avoid using 'copyrighted' or 'sensitive' words in your prompt.",
|
"req_error_text": "Operation failed. Please try again. Avoid using 'copyrighted' or 'sensitive' words in your prompt.",
|
||||||
|
"req_error_token": "Please check the validity of the token",
|
||||||
|
"req_error_no_balance": "Please check the validity of the token",
|
||||||
"auto_create_paint": "Auto-create image",
|
"auto_create_paint": "Auto-create image",
|
||||||
"auto_create_paint_tip": "After the image is generated, a new image will be created automatically.",
|
"auto_create_paint_tip": "After the image is generated, a new image will be created automatically.",
|
||||||
"select_model": "Select Model",
|
"select_model": "Select Model",
|
||||||
|
|||||||
@ -940,7 +940,10 @@
|
|||||||
"rendering_speed": "レンダリング速度",
|
"rendering_speed": "レンダリング速度",
|
||||||
"translating": "翻訳中...",
|
"translating": "翻訳中...",
|
||||||
"text_desc_required": "画像の説明を先に入力してください",
|
"text_desc_required": "画像の説明を先に入力してください",
|
||||||
|
"image_handle_required": "最初に画像をアップロードしてください。",
|
||||||
"req_error_text": "実行に失敗しました。もう一度お試しください。プロンプトに「著作権用語」や「センシティブな用語」を含めないでください。",
|
"req_error_text": "実行に失敗しました。もう一度お試しください。プロンプトに「著作権用語」や「センシティブな用語」を含めないでください。",
|
||||||
|
"req_error_token": "トークンの有効性を確認してください",
|
||||||
|
"req_error_no_balance": "トークンの有効性を確認してください",
|
||||||
"auto_create_paint": "画像を自動作成",
|
"auto_create_paint": "画像を自動作成",
|
||||||
"auto_create_paint_tip": "画像が生成された後、自動的に新しい画像が作成されます。",
|
"auto_create_paint_tip": "画像が生成された後、自動的に新しい画像が作成されます。",
|
||||||
"select_model": "モデルを選択",
|
"select_model": "モデルを選択",
|
||||||
|
|||||||
@ -940,7 +940,10 @@
|
|||||||
"magic_prompt_option_tip": "Улучшает увеличение изображений с помощью интеллектуального оптимизирования промптов"
|
"magic_prompt_option_tip": "Улучшает увеличение изображений с помощью интеллектуального оптимизирования промптов"
|
||||||
},
|
},
|
||||||
"text_desc_required": "Пожалуйста, сначала введите описание изображения",
|
"text_desc_required": "Пожалуйста, сначала введите описание изображения",
|
||||||
|
"image_handle_required": "Пожалуйста, сначала загрузите изображение.",
|
||||||
"req_error_text": "Операция не удалась, повторите попытку. Пожалуйста, избегайте защищенных авторским правом терминов и конфиденциальных слов в запросах.",
|
"req_error_text": "Операция не удалась, повторите попытку. Пожалуйста, избегайте защищенных авторским правом терминов и конфиденциальных слов в запросах.",
|
||||||
|
"req_error_token": "Пожалуйста, проверьте действительность токена",
|
||||||
|
"req_error_no_balance": "Пожалуйста, проверьте действительность токена",
|
||||||
"auto_create_paint": "Автоматическое создание изображения",
|
"auto_create_paint": "Автоматическое создание изображения",
|
||||||
"auto_create_paint_tip": "После генерации изображения будет автоматически создано новое.",
|
"auto_create_paint_tip": "После генерации изображения будет автоматически создано новое.",
|
||||||
"select_model": "Выбрать модель",
|
"select_model": "Выбрать модель",
|
||||||
|
|||||||
@ -941,6 +941,9 @@
|
|||||||
},
|
},
|
||||||
"text_desc_required": "请先输入图片描述",
|
"text_desc_required": "请先输入图片描述",
|
||||||
"req_error_text": "运行失败,请重试。提示词避免“版权词”和”敏感词”哦。",
|
"req_error_text": "运行失败,请重试。提示词避免“版权词”和”敏感词”哦。",
|
||||||
|
"req_error_token": "请检查令牌有效性",
|
||||||
|
"req_error_no_balance": "请检查令牌有效性",
|
||||||
|
"image_handle_required": "请先上传图片",
|
||||||
"auto_create_paint": "自动新建图片",
|
"auto_create_paint": "自动新建图片",
|
||||||
"auto_create_paint_tip": "在图片生成后,会自动新建图片",
|
"auto_create_paint_tip": "在图片生成后,会自动新建图片",
|
||||||
"select_model": "选择模型",
|
"select_model": "选择模型",
|
||||||
|
|||||||
@ -940,7 +940,10 @@
|
|||||||
},
|
},
|
||||||
"rendering_speed": "渲染速度",
|
"rendering_speed": "渲染速度",
|
||||||
"text_desc_required": "請先輸入圖片描述",
|
"text_desc_required": "請先輸入圖片描述",
|
||||||
|
"image_handle_required": "請先上傳圖片。",
|
||||||
"req_error_text": "运行失败,请重试。提示词避免“版权词”和”敏感词”哦。",
|
"req_error_text": "运行失败,请重试。提示词避免“版权词”和”敏感词”哦。",
|
||||||
|
"req_error_token": "請檢查令牌的有效性",
|
||||||
|
"req_error_no_balance": "請檢查令牌的有效性",
|
||||||
"auto_create_paint": "自動新增圖片",
|
"auto_create_paint": "自動新增圖片",
|
||||||
"auto_create_paint_tip": "圖片生成後,會自動新增圖片",
|
"auto_create_paint_tip": "圖片生成後,會自動新增圖片",
|
||||||
"select_model": "選擇模型",
|
"select_model": "選擇模型",
|
||||||
|
|||||||
@ -15,8 +15,8 @@ import { useAppDispatch } from '@renderer/store'
|
|||||||
import { setGenerating } from '@renderer/store/runtime'
|
import { setGenerating } from '@renderer/store/runtime'
|
||||||
import type { FileType, PaintingsState } from '@renderer/types'
|
import type { FileType, PaintingsState } from '@renderer/types'
|
||||||
import { uuid } from '@renderer/utils'
|
import { uuid } from '@renderer/utils'
|
||||||
import { DmxapiPainting, PaintingAction } from '@types'
|
import { DmxapiPainting } from '@types'
|
||||||
import { Avatar, Button, Input, Radio, Select, Switch, Tooltip } from 'antd'
|
import { Avatar, Button, Input, Radio, Segmented, Select, Switch, Tooltip } from 'antd'
|
||||||
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 React, { FC } from 'react'
|
import React, { FC } from 'react'
|
||||||
@ -25,14 +25,19 @@ 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 { generationModeType } from '../../types'
|
||||||
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 './components/Artboard'
|
import Artboard from './components/Artboard'
|
||||||
|
import ImageUploader from './components/ImageUploader'
|
||||||
import PaintingsList from './components/PaintingsList'
|
import PaintingsList from './components/PaintingsList'
|
||||||
import {
|
import {
|
||||||
COURSE_URL,
|
COURSE_URL,
|
||||||
DEFAULT_PAINTING,
|
DEFAULT_PAINTING,
|
||||||
|
IMAGE_EDIT_MODELS,
|
||||||
|
IMAGE_MERGE_MODELS,
|
||||||
IMAGE_SIZES,
|
IMAGE_SIZES,
|
||||||
|
MODEOPTIONS,
|
||||||
STYLE_TYPE_OPTIONS,
|
STYLE_TYPE_OPTIONS,
|
||||||
TEXT_TO_IMAGES_MODELS
|
TEXT_TO_IMAGES_MODELS
|
||||||
} from './config/DmxapiConfig'
|
} from './config/DmxapiConfig'
|
||||||
@ -64,11 +69,71 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
const getNewPainting = () => {
|
interface FileMapType {
|
||||||
|
imageFiles?: FileType[]
|
||||||
|
paths?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const [fileMap, setFileMap] = useState<FileMapType>({
|
||||||
|
imageFiles: [],
|
||||||
|
paths: []
|
||||||
|
})
|
||||||
|
|
||||||
|
const modeOptions = MODEOPTIONS.map((ele) => {
|
||||||
|
return {
|
||||||
|
label: t(ele.label),
|
||||||
|
value: ele.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const getModelOptions = (mode: generationModeType) => {
|
||||||
|
if (mode === generationModeType.EDIT) {
|
||||||
|
return IMAGE_EDIT_MODELS.map((model) => ({
|
||||||
|
label: model.name,
|
||||||
|
value: model.id
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === generationModeType.MERGE) {
|
||||||
|
return IMAGE_MERGE_MODELS.map((model) => ({
|
||||||
|
label: model.name,
|
||||||
|
value: model.id
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认情况或其它模式下的选项
|
||||||
|
return TEXT_TO_IMAGES_MODELS.map((model) => ({
|
||||||
|
label: model.name,
|
||||||
|
value: model.id
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const [modelOptions, setModelOptions] = useState(() => {
|
||||||
|
// 根据当前painting的generationMode初始化modelOptions
|
||||||
|
const currentMode = painting?.generationMode || (MODEOPTIONS[0].value as generationModeType)
|
||||||
|
return getModelOptions(currentMode)
|
||||||
|
})
|
||||||
|
|
||||||
|
const textareaRef = useRef<any>(null)
|
||||||
|
|
||||||
|
// 更新painting状态的辅助函数
|
||||||
|
const updatePaintingState = (updates: Partial<DmxapiPainting>) => {
|
||||||
|
const updatedPainting = { ...painting, ...updates }
|
||||||
|
setPainting(updatedPainting)
|
||||||
|
updatePainting('DMXAPIPaintings', updatedPainting)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNewPainting = (params?: Partial<DmxapiPainting>) => {
|
||||||
|
clearImages()
|
||||||
|
const generationMode = params?.generationMode || painting?.generationMode || MODEOPTIONS[0].value
|
||||||
|
const modelOptionsList = getModelOptions(generationMode as generationModeType)
|
||||||
return {
|
return {
|
||||||
...DEFAULT_PAINTING,
|
...DEFAULT_PAINTING,
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
seed: generateRandomSeed()
|
seed: generateRandomSeed(),
|
||||||
|
generationMode,
|
||||||
|
model: modelOptionsList[0]?.value,
|
||||||
|
...params
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,19 +147,6 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
setPainting(addPainting('DMXAPIPaintings', copyPainting))
|
setPainting(addPainting('DMXAPIPaintings', copyPainting))
|
||||||
}
|
}
|
||||||
|
|
||||||
const modelOptions = TEXT_TO_IMAGES_MODELS.map((model) => ({
|
|
||||||
label: model.name,
|
|
||||||
value: model.id
|
|
||||||
}))
|
|
||||||
|
|
||||||
const textareaRef = useRef<any>(null)
|
|
||||||
|
|
||||||
const updatePaintingState = (updates: Partial<DmxapiPainting>) => {
|
|
||||||
const updatedPainting = { ...painting, ...updates }
|
|
||||||
setPainting(updatedPainting)
|
|
||||||
updatePainting('DMXAPIPaintings', updatedPainting)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSelectModel = (modelId: string) => {
|
const onSelectModel = (modelId: string) => {
|
||||||
const model = TEXT_TO_IMAGES_MODELS.find((m) => m.id === modelId)
|
const model = TEXT_TO_IMAGES_MODELS.find((m) => m.id === modelId)
|
||||||
if (model) {
|
if (model) {
|
||||||
@ -135,6 +187,62 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onbeforeunload = (file, index?: number) => {
|
||||||
|
const path = URL.createObjectURL(file)
|
||||||
|
|
||||||
|
// 更新 fileMap
|
||||||
|
setFileMap((prevFileMap) => {
|
||||||
|
const currentFiles = prevFileMap.imageFiles || []
|
||||||
|
const currentPaths = prevFileMap.paths || []
|
||||||
|
|
||||||
|
let newFiles: FileType[]
|
||||||
|
let newPaths: string[]
|
||||||
|
|
||||||
|
if (index !== undefined) {
|
||||||
|
// 替换指定索引的图片
|
||||||
|
newFiles = [...currentFiles]
|
||||||
|
newFiles[index] = file as FileType
|
||||||
|
|
||||||
|
newPaths = [...currentPaths]
|
||||||
|
newPaths[index] = path
|
||||||
|
} else {
|
||||||
|
// 添加新图片到最后
|
||||||
|
newFiles = [...currentFiles, file as FileType]
|
||||||
|
newPaths = [...currentPaths, path]
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
imageFiles: newFiles,
|
||||||
|
paths: newPaths
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return false // 阻止默认上传行为
|
||||||
|
}
|
||||||
|
|
||||||
|
const onGenerationModeChange = (v: generationModeType) => {
|
||||||
|
clearImages()
|
||||||
|
const newModelOptions = getModelOptions(v)
|
||||||
|
setModelOptions(newModelOptions)
|
||||||
|
const firstModel = newModelOptions[0]?.value
|
||||||
|
|
||||||
|
// 如果有urls,创建新的painting
|
||||||
|
if (Array.isArray(painting.urls) && painting.urls.length > 0) {
|
||||||
|
const newPainting = getNewPainting({
|
||||||
|
generationMode: v,
|
||||||
|
model: firstModel // 使用新模式下的第一个模型
|
||||||
|
})
|
||||||
|
const addedPainting = addPainting('DMXAPIPaintings', newPainting)
|
||||||
|
setPainting(addedPainting)
|
||||||
|
} else {
|
||||||
|
// 否则更新当前painting
|
||||||
|
updatePaintingState({
|
||||||
|
generationMode: v,
|
||||||
|
model: firstModel // 使用新模式下的第一个模型
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 检查提供者状态函数
|
// 检查提供者状态函数
|
||||||
const checkProviderStatus = () => {
|
const checkProviderStatus = () => {
|
||||||
if (!dmxapiProvider.enabled) {
|
if (!dmxapiProvider.enabled) {
|
||||||
@ -152,6 +260,14 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
if (!painting.prompt) {
|
if (!painting.prompt) {
|
||||||
throw new Error('paintings.text_desc_required')
|
throw new Error('paintings.text_desc_required')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
painting.generationMode &&
|
||||||
|
[generationModeType.EDIT, generationModeType.MERGE].includes(painting.generationMode) &&
|
||||||
|
(!fileMap.imageFiles || fileMap.imageFiles.length === 0)
|
||||||
|
) {
|
||||||
|
throw new Error('paintings.image_handle_required')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 准备V1生成请求函数
|
// 准备V1生成请求函数
|
||||||
@ -162,6 +278,10 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
n: painting.n
|
n: painting.n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const headerExpand = {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
if (painting.aspect_ratio) {
|
if (painting.aspect_ratio) {
|
||||||
params['aspect_ratio'] = painting.aspect_ratio
|
params['aspect_ratio'] = painting.aspect_ratio
|
||||||
}
|
}
|
||||||
@ -184,21 +304,57 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
body: JSON.stringify(params),
|
body: JSON.stringify(params),
|
||||||
|
headerExpand: headerExpand,
|
||||||
endpoint: `${dmxapiProvider.apiHost}/v1/images/generations`
|
endpoint: `${dmxapiProvider.apiHost}/v1/images/generations`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// API请求函数
|
// 准备V2生成请求函数
|
||||||
const callApi = async (requestConfig: { endpoint: string; body: any }, controller: AbortController) => {
|
const prepareV2GenerateRequest = (prompt: string, painting: DmxapiPainting) => {
|
||||||
const { endpoint, body } = requestConfig
|
const params = {
|
||||||
const headers = {}
|
prompt,
|
||||||
|
n: painting.n,
|
||||||
|
model: painting.model
|
||||||
|
}
|
||||||
|
|
||||||
// 如果是JSON数据,添加Content-Type头
|
if (painting.image_size) {
|
||||||
if (typeof body === 'string') {
|
params['size'] = '1024x1024'
|
||||||
headers['Content-Type'] = 'application/json'
|
}
|
||||||
headers['Authorization'] = `Bearer ${dmxapiProvider.apiKey}`
|
|
||||||
headers['User-Agent'] = 'DMXAPI/1.0.0 (https://www.dmxapi.com)'
|
if (painting.style_type) {
|
||||||
headers['Accept'] = 'application/json'
|
params.prompt = prompt + ',风格:' + painting.style_type
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData()
|
||||||
|
|
||||||
|
for (const key in params) {
|
||||||
|
formData.append(key, params[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(fileMap.imageFiles)) {
|
||||||
|
fileMap.imageFiles.forEach((file) => {
|
||||||
|
formData.append(`image`, file as unknown as Blob)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
body: formData,
|
||||||
|
endpoint: `${dmxapiProvider.apiHost}/v1/images/edits`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API请求函数
|
||||||
|
const callApi = async (
|
||||||
|
requestConfig: { endpoint: string; body: any; headerExpand?: any },
|
||||||
|
controller: AbortController
|
||||||
|
) => {
|
||||||
|
const { endpoint, body, headerExpand } = requestConfig
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
Accept: 'application/json',
|
||||||
|
Authorization: `Bearer ${dmxapiProvider.apiKey}`,
|
||||||
|
'User-Agent': 'DMXAPI/1.0.0 (https://www.dmxapi.com)',
|
||||||
|
...headerExpand
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(endpoint, {
|
const response = await fetch(endpoint, {
|
||||||
@ -209,10 +365,23 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
if (response.status === 401) {
|
||||||
|
throw new Error('paintings.req_error_token')
|
||||||
|
} else if (response.status === 403) {
|
||||||
|
throw new Error('paintings.req_error_no_balance')
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error('操作失败,请稍后重试')
|
throw new Error('操作失败,请稍后重试')
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
|
|
||||||
|
if (
|
||||||
|
painting.generationMode &&
|
||||||
|
[generationModeType.EDIT, generationModeType.MERGE].includes(painting.generationMode)
|
||||||
|
) {
|
||||||
|
return data.data.map((item: { b64_json: string }) => 'data:image/png;base64,' + item.b64_json)
|
||||||
|
}
|
||||||
return data.data.map((item: { url: string }) => item.url)
|
return data.data.map((item: { url: string }) => item.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,9 +415,16 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 准备请求配置函数
|
// 准备请求配置函数
|
||||||
const prepareRequestConfig = (prompt: string, painting: PaintingAction) => {
|
const prepareRequestConfig = (prompt: string, painting: DmxapiPainting) => {
|
||||||
// 根据模式和模型版本返回不同的请求配置
|
// 根据模式和模型版本返回不同的请求配置
|
||||||
return prepareV1GenerateRequest(prompt, painting)
|
if (
|
||||||
|
painting.generationMode !== undefined &&
|
||||||
|
[generationModeType.MERGE, generationModeType.EDIT].includes(painting.generationMode)
|
||||||
|
) {
|
||||||
|
return prepareV2GenerateRequest(prompt, painting)
|
||||||
|
} else {
|
||||||
|
return prepareV1GenerateRequest(prompt, painting)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onGenerate = async () => {
|
const onGenerate = async () => {
|
||||||
@ -338,7 +514,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
setCurrentImageIndex((prev) => (prev - 1 + painting.files.length) % painting.files.length)
|
setCurrentImageIndex((prev) => (prev - 1 + painting.files.length) % painting.files.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onDeletePainting = (paintingToDelete: DmxapiPainting) => {
|
const onDeletePainting = async (paintingToDelete: DmxapiPainting) => {
|
||||||
if (paintingToDelete.id === painting.id) {
|
if (paintingToDelete.id === painting.id) {
|
||||||
const currentIndex = DMXAPIPaintings.findIndex((p) => p.id === paintingToDelete.id)
|
const currentIndex = DMXAPIPaintings.findIndex((p) => p.id === paintingToDelete.id)
|
||||||
|
|
||||||
@ -349,17 +525,25 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removePainting(mode, paintingToDelete).then(() => {})
|
// 删除绘画
|
||||||
|
await removePainting(mode, paintingToDelete)
|
||||||
|
|
||||||
|
// 检查是否删除空了
|
||||||
|
if (!DMXAPIPaintings || DMXAPIPaintings.length === 1) {
|
||||||
|
// 如果删除后没有绘画了,创建一个新的
|
||||||
|
const newPainting = getNewPainting()
|
||||||
|
const addedPainting = addPainting('DMXAPIPaintings', newPainting)
|
||||||
|
setPainting(addedPainting)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSelectPainting = (newPainting: DmxapiPainting) => {
|
const onSelectPainting = (newPainting: DmxapiPainting) => {
|
||||||
if (generating) return
|
if (generating) return
|
||||||
|
clearImages()
|
||||||
setPainting(newPainting)
|
setPainting(newPainting)
|
||||||
setCurrentImageIndex(0)
|
setCurrentImageIndex(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
const spaceClickTimer = useRef<NodeJS.Timeout>(null)
|
|
||||||
|
|
||||||
const handleProviderChange = (providerId: string) => {
|
const handleProviderChange = (providerId: string) => {
|
||||||
const routeName = location.pathname.split('/').pop()
|
const routeName = location.pathname.split('/').pop()
|
||||||
if (providerId !== routeName) {
|
if (providerId !== routeName) {
|
||||||
@ -367,20 +551,97 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除图片函数
|
||||||
|
const clearImages = () => {
|
||||||
|
setFileMap(() => ({ paths: [], imageFiles: [] }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteImage = (index: number) => {
|
||||||
|
setFileMap((prevFileMap) => {
|
||||||
|
const newPaths = [...(prevFileMap.paths || [])]
|
||||||
|
const newImageFiles = [...(prevFileMap.imageFiles || [])]
|
||||||
|
|
||||||
|
// 删除指定索引的图片
|
||||||
|
newPaths.splice(index, 1)
|
||||||
|
newImageFiles.splice(index, 1)
|
||||||
|
|
||||||
|
return {
|
||||||
|
paths: newPaths,
|
||||||
|
imageFiles: newImageFiles
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义大图的默认图片
|
||||||
|
const defaultCoverImage = () => {
|
||||||
|
if (painting.generationMode === generationModeType.EDIT) {
|
||||||
|
if (painting?.urls.length === 0 && fileMap.paths && fileMap.paths?.length > 0 && fileMap.paths[0]) {
|
||||||
|
return (
|
||||||
|
<EmptyImgBox>
|
||||||
|
<EmptyImg bgUrl={fileMap.paths[0]}></EmptyImg>
|
||||||
|
</EmptyImgBox>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (painting?.urls?.length > 0 || DMXAPIPaintings?.length > 1) {
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<EmptyImgBox>
|
||||||
|
<EmptyImg></EmptyImg>
|
||||||
|
</EmptyImgBox>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultLoadText = () => {
|
||||||
|
if (
|
||||||
|
painting.generationMode &&
|
||||||
|
[generationModeType.EDIT, generationModeType.MERGE].includes(painting.generationMode)
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<LoadTextWrap>
|
||||||
|
<div>
|
||||||
|
正在用 OpenAI 官方 gpt-image-1 模型生产,
|
||||||
|
<br />
|
||||||
|
预计等待2~5分钟效果最好,
|
||||||
|
<br />
|
||||||
|
本次消耗金额请到DMIAPI后台日志查看
|
||||||
|
</div>
|
||||||
|
</LoadTextWrap>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!DMXAPIPaintings || DMXAPIPaintings.length === 0) {
|
if (!DMXAPIPaintings || DMXAPIPaintings.length === 0) {
|
||||||
const newPainting = getNewPainting()
|
const newPainting = getNewPainting()
|
||||||
addPainting('DMXAPIPaintings', newPainting)
|
addPainting('DMXAPIPaintings', newPainting)
|
||||||
setPainting(newPainting)
|
setPainting(newPainting)
|
||||||
|
} else if (painting && !painting.generationMode) {
|
||||||
|
// 如果当前painting没有generationMode,添加默认值
|
||||||
|
const updatedPainting = { ...painting, generationMode: MODEOPTIONS[0].value }
|
||||||
|
setPainting(updatedPainting)
|
||||||
|
updatePainting('DMXAPIPaintings', updatedPainting)
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
// 确保所有paintings都有generationMode属性
|
||||||
if (spaceClickTimer.current) {
|
DMXAPIPaintings.forEach((p) => {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
if (!p.generationMode) {
|
||||||
clearTimeout(spaceClickTimer.current)
|
const updatedPainting = { ...p, generationMode: MODEOPTIONS[0].value }
|
||||||
|
updatePainting('DMXAPIPaintings', updatedPainting)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 确保modelOptions与当前painting的generationMode保持一致
|
||||||
|
if (painting?.generationMode) {
|
||||||
|
setModelOptions(getModelOptions(painting.generationMode as generationModeType))
|
||||||
}
|
}
|
||||||
}, [DMXAPIPaintings, DMXAPIPaintings.length, addPainting, mode])
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []) // 空依赖数组,只在组件挂载时执行一次
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
@ -422,40 +683,60 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
</Select.Option>
|
</Select.Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
|
{painting.generationMode &&
|
||||||
|
[generationModeType.EDIT, generationModeType.MERGE].includes(painting.generationMode) && (
|
||||||
|
<>
|
||||||
|
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>参考图</SettingTitle>
|
||||||
|
<ImageUploader
|
||||||
|
fileMap={fileMap}
|
||||||
|
maxImages={painting.generationMode === generationModeType.EDIT ? 1 : 3}
|
||||||
|
onClearImages={clearImages}
|
||||||
|
onDeleteImage={handleDeleteImage}
|
||||||
|
onAddImage={onbeforeunload}
|
||||||
|
mode={painting.generationMode}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>{t('common.model')}</SettingTitle>
|
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>{t('common.model')}</SettingTitle>
|
||||||
<Select value={painting.model} options={modelOptions} onChange={onSelectModel} />
|
<Select value={painting.model} options={modelOptions} onChange={onSelectModel} />
|
||||||
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>{t('paintings.image.size')}</SettingTitle>
|
|
||||||
<Radio.Group
|
|
||||||
value={painting.image_size}
|
|
||||||
onChange={(e) => onSelectImageSize(e.target.value)}
|
|
||||||
style={{ display: 'flex' }}>
|
|
||||||
{IMAGE_SIZES.map((size) => (
|
|
||||||
<RadioButton value={size.value} key={size.value}>
|
|
||||||
<VStack alignItems="center">
|
|
||||||
<ImageSizeImage src={size.icon} theme={theme} />
|
|
||||||
<span>{size.label}</span>
|
|
||||||
</VStack>
|
|
||||||
</RadioButton>
|
|
||||||
))}
|
|
||||||
</Radio.Group>
|
|
||||||
|
|
||||||
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>
|
{painting.generationMode === generationModeType.GENERATION && (
|
||||||
{t('paintings.seed')}
|
<>
|
||||||
<Tooltip title={t('paintings.seed_desc_tip')}>
|
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>{t('paintings.image.size')}</SettingTitle>
|
||||||
<InfoIcon />
|
<Radio.Group
|
||||||
</Tooltip>
|
value={painting.image_size}
|
||||||
</SettingTitle>
|
onChange={(e) => onSelectImageSize(e.target.value)}
|
||||||
<Input
|
style={{ display: 'flex' }}>
|
||||||
value={painting.seed}
|
{IMAGE_SIZES.map((size) => (
|
||||||
pattern="[0-9]*"
|
<RadioButton value={size.value} key={size.value}>
|
||||||
onChange={(e) => onInputSeed(e)}
|
<VStack alignItems="center">
|
||||||
suffix={
|
<ImageSizeImage src={size.icon} theme={theme} />
|
||||||
<RedoOutlined
|
<span>{size.label}</span>
|
||||||
onClick={() => updatePaintingState({ seed: Math.floor(Math.random() * 1000000).toString() })}
|
</VStack>
|
||||||
style={{ cursor: 'pointer', color: 'var(--color-text-2)' }}
|
</RadioButton>
|
||||||
|
))}
|
||||||
|
</Radio.Group>
|
||||||
|
|
||||||
|
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>
|
||||||
|
{t('paintings.seed')}
|
||||||
|
<Tooltip title={t('paintings.seed_desc_tip')}>
|
||||||
|
<InfoIcon />
|
||||||
|
</Tooltip>
|
||||||
|
</SettingTitle>
|
||||||
|
<Input
|
||||||
|
value={painting.seed}
|
||||||
|
pattern="[0-9]*"
|
||||||
|
onChange={(e) => onInputSeed(e)}
|
||||||
|
suffix={
|
||||||
|
<RedoOutlined
|
||||||
|
onClick={() => updatePaintingState({ seed: Math.floor(Math.random() * 1000000).toString() })}
|
||||||
|
style={{ cursor: 'pointer', color: 'var(--color-text-2)' }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
}
|
</>
|
||||||
/>
|
)}
|
||||||
|
|
||||||
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>{t('paintings.style_type')}</SettingTitle>
|
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>{t('paintings.style_type')}</SettingTitle>
|
||||||
<SliderContainer>
|
<SliderContainer>
|
||||||
@ -482,6 +763,14 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
</HStack>
|
</HStack>
|
||||||
</LeftContainer>
|
</LeftContainer>
|
||||||
<MainContainer>
|
<MainContainer>
|
||||||
|
<ModeSegmentedContainer>
|
||||||
|
<Segmented
|
||||||
|
shape="round"
|
||||||
|
value={painting.generationMode}
|
||||||
|
onChange={onGenerationModeChange}
|
||||||
|
options={modeOptions}
|
||||||
|
/>
|
||||||
|
</ModeSegmentedContainer>
|
||||||
<Artboard
|
<Artboard
|
||||||
painting={painting}
|
painting={painting}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
@ -489,13 +778,8 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
onPrevImage={prevImage}
|
onPrevImage={prevImage}
|
||||||
onNextImage={nextImage}
|
onNextImage={nextImage}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
imageCover={
|
imageCover={defaultCoverImage()}
|
||||||
painting?.urls?.length > 0 || DMXAPIPaintings?.length > 1 ? null : (
|
loadText={defaultLoadText()}
|
||||||
<EmptyImgBox>
|
|
||||||
<EmptyImg></EmptyImg>
|
|
||||||
</EmptyImgBox>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<InputContainer>
|
<InputContainer>
|
||||||
<Textarea
|
<Textarea
|
||||||
@ -684,6 +968,13 @@ const RadioTextItem = styled.div`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
// 添加新的样式组件
|
||||||
|
const ModeSegmentedContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 24px;
|
||||||
|
`
|
||||||
|
|
||||||
const EmptyImgBox = styled.div`
|
const EmptyImgBox = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@ -692,11 +983,25 @@ const EmptyImgBox = styled.div`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
`
|
`
|
||||||
|
|
||||||
const EmptyImg = styled.div`
|
const EmptyImg = styled.div<{ bgUrl?: string }>`
|
||||||
width: 70vh;
|
width: 70vh;
|
||||||
height: 70vh;
|
height: 70vh;
|
||||||
background-size: 100% 100%;
|
background-size: cover;
|
||||||
background-image: url(${DMXAPIToImg});
|
background-image: ${(props) => (props.bgUrl ? `url(${props.bgUrl})` : `url(${DMXAPIToImg})`)};
|
||||||
|
`
|
||||||
|
|
||||||
|
const LoadTextWrap = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
color: black;
|
||||||
|
text-shadow:
|
||||||
|
-1px -1px 0 #ffffff,
|
||||||
|
1px -1px 0 #ffffff,
|
||||||
|
-1px 1px 0 #ffffff,
|
||||||
|
1px 1px 0 #ffffff;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default DmxapiPage
|
export default DmxapiPage
|
||||||
|
|||||||
@ -15,6 +15,7 @@ interface ArtboardProps {
|
|||||||
onCancel: () => void
|
onCancel: () => void
|
||||||
retry?: (painting: Painting) => void
|
retry?: (painting: Painting) => void
|
||||||
imageCover?: React.ReactNode
|
imageCover?: React.ReactNode
|
||||||
|
loadText?: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
const Artboard: FC<ArtboardProps> = ({
|
const Artboard: FC<ArtboardProps> = ({
|
||||||
@ -25,7 +26,8 @@ const Artboard: FC<ArtboardProps> = ({
|
|||||||
onNextImage,
|
onNextImage,
|
||||||
onCancel,
|
onCancel,
|
||||||
retry,
|
retry,
|
||||||
imageCover
|
imageCover,
|
||||||
|
loadText
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
@ -82,6 +84,8 @@ const Artboard: FC<ArtboardProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
) : imageCover ? (
|
) : imageCover ? (
|
||||||
imageCover
|
imageCover
|
||||||
|
) : loadText && isLoading ? (
|
||||||
|
''
|
||||||
) : (
|
) : (
|
||||||
<div>{t('paintings.image_placeholder')}</div>
|
<div>{t('paintings.image_placeholder')}</div>
|
||||||
)}
|
)}
|
||||||
@ -90,6 +94,7 @@ const Artboard: FC<ArtboardProps> = ({
|
|||||||
{isLoading && (
|
{isLoading && (
|
||||||
<LoadingOverlay>
|
<LoadingOverlay>
|
||||||
<Spin size="large" />
|
<Spin size="large" />
|
||||||
|
{loadText ? loadText : ''}
|
||||||
<CancelButton onClick={onCancel}>{t('common.cancel')}</CancelButton>
|
<CancelButton onClick={onCancel}>{t('common.cancel')}</CancelButton>
|
||||||
</LoadingOverlay>
|
</LoadingOverlay>
|
||||||
)}
|
)}
|
||||||
|
|||||||
201
src/renderer/src/pages/paintings/components/ImageUploader.tsx
Normal file
201
src/renderer/src/pages/paintings/components/ImageUploader.tsx
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
import { DeleteOutlined } from '@ant-design/icons'
|
||||||
|
import IcImageUp from '@renderer/assets/images/paintings/ic_ImageUp.svg'
|
||||||
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
|
import type { FileType } from '@renderer/types'
|
||||||
|
import { Popconfirm, Upload } from 'antd'
|
||||||
|
import { Button } from 'antd'
|
||||||
|
import type { RcFile, UploadProps } from 'antd/es/upload'
|
||||||
|
import React from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
interface ImageUploaderProps {
|
||||||
|
fileMap: {
|
||||||
|
imageFiles?: FileType[]
|
||||||
|
paths?: string[]
|
||||||
|
}
|
||||||
|
maxImages: number
|
||||||
|
onClearImages: () => void
|
||||||
|
onDeleteImage: (index: number) => void
|
||||||
|
onAddImage: (file: File, index?: number) => void
|
||||||
|
mode: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImageUploader: React.FC<ImageUploaderProps> = ({
|
||||||
|
fileMap,
|
||||||
|
maxImages,
|
||||||
|
onClearImages,
|
||||||
|
onDeleteImage,
|
||||||
|
onAddImage
|
||||||
|
}) => {
|
||||||
|
const { theme } = useTheme()
|
||||||
|
|
||||||
|
const handleBeforeUpload = (file: RcFile, index?: number) => {
|
||||||
|
onAddImage(file, index)
|
||||||
|
return false // 阻止默认上传行为
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义上传请求,不执行任何网络请求
|
||||||
|
const customRequest: UploadProps['customRequest'] = ({ onSuccess }) => {
|
||||||
|
if (onSuccess) {
|
||||||
|
onSuccess('ok' as any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<HeaderContainer>
|
||||||
|
{fileMap.imageFiles && fileMap.imageFiles.length > 0 && (
|
||||||
|
<Button size="small" onClick={onClearImages}>
|
||||||
|
清除全部
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</HeaderContainer>
|
||||||
|
|
||||||
|
<UploadImageList>
|
||||||
|
{fileMap.paths && fileMap.paths.length > 0 ? (
|
||||||
|
<>
|
||||||
|
{fileMap.paths.map((src, index) => (
|
||||||
|
<UploadImageItem key={index}>
|
||||||
|
<ImageUploadButton
|
||||||
|
accept="image/png, image/jpeg"
|
||||||
|
maxCount={1}
|
||||||
|
multiple={false}
|
||||||
|
showUploadList={false}
|
||||||
|
listType="picture-card"
|
||||||
|
action=""
|
||||||
|
customRequest={customRequest}
|
||||||
|
beforeUpload={(file) => {
|
||||||
|
handleBeforeUpload(file, index)
|
||||||
|
}}>
|
||||||
|
<ImagePreview>
|
||||||
|
<img src={src} alt={`预览图${index + 1}`} />
|
||||||
|
</ImagePreview>
|
||||||
|
</ImageUploadButton>
|
||||||
|
<Popconfirm
|
||||||
|
title="确定要删除这张图片吗?"
|
||||||
|
okText="确定"
|
||||||
|
cancelText="取消"
|
||||||
|
onConfirm={() => onDeleteImage(index)}>
|
||||||
|
<DeleteButton>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</DeleteButton>
|
||||||
|
</Popconfirm>
|
||||||
|
</UploadImageItem>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
|
||||||
|
{fileMap.imageFiles && fileMap.imageFiles.length < maxImages ? (
|
||||||
|
<UploadImageItem>
|
||||||
|
<ImageUploadButton
|
||||||
|
multiple={false}
|
||||||
|
accept="image/png, image/jpeg"
|
||||||
|
maxCount={1}
|
||||||
|
showUploadList={false}
|
||||||
|
listType="picture-card"
|
||||||
|
action=""
|
||||||
|
customRequest={customRequest}
|
||||||
|
beforeUpload={(file) => {
|
||||||
|
handleBeforeUpload(file)
|
||||||
|
}}>
|
||||||
|
<ImageSizeImage src={IcImageUp} theme={theme} />
|
||||||
|
</ImageUploadButton>
|
||||||
|
</UploadImageItem>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</UploadImageList>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 样式组件
|
||||||
|
const HeaderContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ImageUploadButton = styled(Upload)`
|
||||||
|
& .ant-upload.ant-upload-select,
|
||||||
|
.ant-upload-list-item-container {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
aspect-ratio: 1 !important;
|
||||||
|
}
|
||||||
|
margin-bottom: 5px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ImagePreview = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover::after {
|
||||||
|
content: '点击替换';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const ImageSizeImage = styled.img<{ theme: string }>`
|
||||||
|
filter: ${({ theme }) => (theme === 'dark' ? 'invert(100%)' : 'none')};
|
||||||
|
margin-top: 8px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const UploadImageList = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
`
|
||||||
|
|
||||||
|
const UploadImageItem = styled.div`
|
||||||
|
width: 45%;
|
||||||
|
height: 45%;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
position: relative;
|
||||||
|
`
|
||||||
|
|
||||||
|
const DeleteButton = styled.button`
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export default ImageUploader
|
||||||
@ -7,6 +7,8 @@ import ImageSize16_9 from '@renderer/assets/images/paintings/image-size-16-9.svg
|
|||||||
import { uuid } from '@renderer/utils'
|
import { uuid } from '@renderer/utils'
|
||||||
import { DmxapiPainting } from '@types'
|
import { DmxapiPainting } from '@types'
|
||||||
|
|
||||||
|
import { generationModeType } from '../../../types'
|
||||||
|
|
||||||
export const STYLE_TYPE_OPTIONS = [
|
export const STYLE_TYPE_OPTIONS = [
|
||||||
{ label: '吉卜力', value: '吉卜力' },
|
{ label: '吉卜力', value: '吉卜力' },
|
||||||
{ label: '皮克斯', value: '皮克斯' },
|
{ label: '皮克斯', value: '皮克斯' },
|
||||||
@ -45,6 +47,22 @@ export const TEXT_TO_IMAGES_MODELS = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const IMAGE_EDIT_MODELS = [
|
||||||
|
{
|
||||||
|
id: 'gpt-image-1',
|
||||||
|
provider: 'DMXAPI',
|
||||||
|
name: 'OpenAI:gpt-image-1'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const IMAGE_MERGE_MODELS = [
|
||||||
|
{
|
||||||
|
id: 'gpt-image-1',
|
||||||
|
provider: 'DMXAPI',
|
||||||
|
name: 'OpenAI:gpt-image-1'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
export const IMAGE_SIZES = [
|
export const IMAGE_SIZES = [
|
||||||
{
|
{
|
||||||
label: '1:1',
|
label: '1:1',
|
||||||
@ -91,5 +109,12 @@ export const DEFAULT_PAINTING: DmxapiPainting = {
|
|||||||
seed: '',
|
seed: '',
|
||||||
style_type: '',
|
style_type: '',
|
||||||
model: TEXT_TO_IMAGES_MODELS[0].id,
|
model: TEXT_TO_IMAGES_MODELS[0].id,
|
||||||
autoCreate: false
|
autoCreate: false,
|
||||||
|
generationMode: generationModeType.GENERATION
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const MODEOPTIONS = [
|
||||||
|
{ label: 'paintings.mode.generate', value: generationModeType.GENERATION },
|
||||||
|
{ label: '改图', value: generationModeType.EDIT },
|
||||||
|
{ label: '合并图', value: generationModeType.MERGE }
|
||||||
|
]
|
||||||
|
|||||||
@ -256,6 +256,12 @@ export interface ScalePainting extends PaintingParams {
|
|||||||
renderingSpeed?: string
|
renderingSpeed?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum generationModeType {
|
||||||
|
GENERATION = 'generation',
|
||||||
|
EDIT = 'edit',
|
||||||
|
MERGE = 'merge'
|
||||||
|
}
|
||||||
|
|
||||||
export interface DmxapiPainting extends PaintingParams {
|
export interface DmxapiPainting extends PaintingParams {
|
||||||
model?: string
|
model?: string
|
||||||
prompt?: string
|
prompt?: string
|
||||||
@ -265,6 +271,7 @@ export interface DmxapiPainting extends PaintingParams {
|
|||||||
seed?: string
|
seed?: string
|
||||||
style_type?: string
|
style_type?: string
|
||||||
autoCreate?: boolean
|
autoCreate?: boolean
|
||||||
|
generationMode?: generationModeType
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TokenFluxPainting extends PaintingParams {
|
export interface TokenFluxPainting extends PaintingParams {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user