feat: dmxapi painting dynamic model (#8302)

* 模型调整为动态

* 模型调整为动态

* 代码优化,新增类型

* 代码优化,新增类型

* 调整locales的顺序

* 修复lint报错
This commit is contained in:
Caelan 2025-07-21 10:15:40 +08:00 committed by GitHub
parent 38bb9a77e0
commit 37508493fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 127 additions and 106 deletions

View File

@ -1134,6 +1134,7 @@
"quality": "Quality", "quality": "Quality",
"turbo": "Turbo" "turbo": "Turbo"
}, },
"req_error_model": "Failed to fetch the model",
"req_error_no_balance": "Please check the validity of the token", "req_error_no_balance": "Please check the validity of the token",
"req_error_text": "The server is busy or the prompt contains \"copyrighted\" or \"sensitive\" terms. Please try again.", "req_error_text": "The server is busy or the prompt contains \"copyrighted\" or \"sensitive\" terms. Please try again.",
"req_error_token": "Please check the validity of the token", "req_error_token": "Please check the validity of the token",

View File

@ -1134,6 +1134,7 @@
"quality": "高品質", "quality": "高品質",
"turbo": "高速" "turbo": "高速"
}, },
"req_error_model": "モデルの取得に失敗しました",
"req_error_no_balance": "トークンの有効性を確認してください", "req_error_no_balance": "トークンの有効性を確認してください",
"req_error_text": "サーバーが混雑しているか、プロンプトに「著作権用語」または「敏感な用語」が含まれています。もう一度お試しください。", "req_error_text": "サーバーが混雑しているか、プロンプトに「著作権用語」または「敏感な用語」が含まれています。もう一度お試しください。",
"req_error_token": "トークンの有効性を確認してください", "req_error_token": "トークンの有効性を確認してください",

View File

@ -1134,6 +1134,7 @@
"quality": "Качественно", "quality": "Качественно",
"turbo": "Быстро" "turbo": "Быстро"
}, },
"req_error_model": "Не удалось получить модель",
"req_error_no_balance": "Пожалуйста, проверьте действительность токена", "req_error_no_balance": "Пожалуйста, проверьте действительность токена",
"req_error_text": "Сервер перегружен или в запросе обнаружены «авторские» либо «чувствительные» слова. Пожалуйста, повторите попытку.", "req_error_text": "Сервер перегружен или в запросе обнаружены «авторские» либо «чувствительные» слова. Пожалуйста, повторите попытку.",
"req_error_token": "Пожалуйста, проверьте действительность токена", "req_error_token": "Пожалуйста, проверьте действительность токена",

View File

@ -1134,6 +1134,7 @@
"quality": "高质量", "quality": "高质量",
"turbo": "快速" "turbo": "快速"
}, },
"req_error_model": "获取模型失败",
"req_error_no_balance": "请检查令牌有效性", "req_error_no_balance": "请检查令牌有效性",
"req_error_text": "服务器繁忙或提示词出现 \"版权词\" 和 \"敏感词\" ,请重试。", "req_error_text": "服务器繁忙或提示词出现 \"版权词\" 和 \"敏感词\" ,请重试。",
"req_error_token": "请检查令牌有效性", "req_error_token": "请检查令牌有效性",

View File

@ -1134,6 +1134,7 @@
"quality": "高品質", "quality": "高品質",
"turbo": "快速" "turbo": "快速"
}, },
"req_error_model": "獲取模型失敗",
"req_error_no_balance": "請檢查令牌的有效性", "req_error_no_balance": "請檢查令牌的有效性",
"req_error_text": "伺服器繁忙或提示詞中出現「版權詞」或「敏感詞」,請重試。", "req_error_text": "伺服器繁忙或提示詞中出現「版權詞」或「敏感詞」,請重試。",
"req_error_token": "請檢查令牌的有效性", "req_error_token": "請檢查令牌的有效性",

View File

@ -30,11 +30,10 @@ import Artboard from './components/Artboard'
import ImageUploader from './components/ImageUploader' import ImageUploader from './components/ImageUploader'
import PaintingsList from './components/PaintingsList' import PaintingsList from './components/PaintingsList'
import { import {
ALL_MODELS,
COURSE_URL, COURSE_URL,
DEFAULT_PAINTING, DEFAULT_PAINTING,
GetModelGroup,
IMAGE_SIZES, IMAGE_SIZES,
MODEL_GROUPS,
MODEOPTIONS, MODEOPTIONS,
STYLE_TYPE_OPTIONS STYLE_TYPE_OPTIONS
} from './config/DmxapiConfig' } from './config/DmxapiConfig'
@ -58,6 +57,11 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
const dmxapiProvider = providers.find((p) => p.id === 'dmxapi')! const dmxapiProvider = providers.find((p) => p.id === 'dmxapi')!
// 动态模型数据状态
const [dynamicModelGroups, setDynamicModelGroups] = useState<any>(null)
const [allModels, setAllModels] = useState<any[]>([])
const [isLoadingModels, setIsLoadingModels] = useState(true)
const [currentImageIndex, setCurrentImageIndex] = useState(0) const [currentImageIndex, setCurrentImageIndex] = useState(0)
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [abortController, setAbortController] = useState<AbortController | null>(null) const [abortController, setAbortController] = useState<AbortController | null>(null)
@ -84,16 +88,20 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
}) })
const getModelOptions = (mode: generationModeType) => { const getModelOptions = (mode: generationModeType) => {
if (!dynamicModelGroups) {
return {}
}
if (mode === generationModeType.EDIT) { if (mode === generationModeType.EDIT) {
return MODEL_GROUPS.IMAGE_EDIT return dynamicModelGroups.IMAGE_EDIT || {}
} }
if (mode === generationModeType.MERGE) { if (mode === generationModeType.MERGE) {
return MODEL_GROUPS.IMAGE_MERGE return dynamicModelGroups.IMAGE_MERGE || {}
} }
// 默认情况或其它模式下的选项 // 默认情况或其它模式下的选项
return MODEL_GROUPS.TEXT_TO_IMAGES return dynamicModelGroups.TEXT_TO_IMAGES || {}
} }
const [modelOptions, setModelOptions] = useState(() => { const [modelOptions, setModelOptions] = useState(() => {
@ -104,6 +112,23 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
const textareaRef = useRef<any>(null) const textareaRef = useRef<any>(null)
// 加载模型数据
const loadModelData = async () => {
try {
setIsLoadingModels(true)
const modelData = await GetModelGroup()
setDynamicModelGroups(modelData)
const allModelsList = Object.values(modelData).flatMap((group) => Object.values(group).flat())
setAllModels(allModelsList)
} catch (error) {
// 如果加载失败,可以设置一个默认的空状态
} finally {
setIsLoadingModels(false)
}
}
// 更新painting状态的辅助函数 // 更新painting状态的辅助函数
const updatePaintingState = (updates: Partial<DmxapiPainting>) => { const updatePaintingState = (updates: Partial<DmxapiPainting>) => {
const updatedPainting = { ...painting, ...updates } const updatedPainting = { ...painting, ...updates }
@ -145,9 +170,9 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
} }
const onSelectModel = (modelId: string) => { const onSelectModel = (modelId: string) => {
const model = ALL_MODELS.find((m) => m.id === modelId) const model = allModels.find((m) => m.id === modelId)
if (model) { if (model) {
updatePaintingState({ model: modelId }) updatePaintingState({ model: modelId, priceModel: model.price })
} }
} }
@ -224,9 +249,11 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
// 获取第一个非空分组的第一个模型 // 获取第一个非空分组的第一个模型
let firstModel = '' let firstModel = ''
let priceModel = ''
for (const provider of Object.keys(newModelGroups)) { for (const provider of Object.keys(newModelGroups)) {
if (newModelGroups[provider].length > 0) { if (newModelGroups[provider] && newModelGroups[provider].length > 0) {
firstModel = newModelGroups[provider][0].id firstModel = newModelGroups[provider][0].id
priceModel = newModelGroups[provider][0].price
break break
} }
} }
@ -235,7 +262,8 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
if (Array.isArray(painting.urls) && painting.urls.length > 0) { if (Array.isArray(painting.urls) && painting.urls.length > 0) {
const newPainting = getNewPainting({ const newPainting = getNewPainting({
generationMode: v, generationMode: v,
model: firstModel // 使用新模式下的第一个模型 model: firstModel, // 使用新模式下的第一个模型
priceModel: priceModel
}) })
const addedPainting = addPainting('DMXAPIPaintings', newPainting) const addedPainting = addPainting('DMXAPIPaintings', newPainting)
setPainting(addedPainting) setPainting(addedPainting)
@ -243,7 +271,8 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
// 否则更新当前painting // 否则更新当前painting
updatePaintingState({ updatePaintingState({
generationMode: v, generationMode: v,
model: firstModel // 使用新模式下的第一个模型 model: firstModel, // 使用新模式下的第一个模型
priceModel: priceModel
}) })
} }
} }
@ -381,14 +410,6 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
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; b64_json: string }) => { return data.data.map((item: { url: string; b64_json: string }) => {
if (item.b64_json) { if (item.b64_json) {
return 'data:image/png;base64,' + item.b64_json return 'data:image/png;base64,' + item.b64_json
@ -634,6 +655,14 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
} }
useEffect(() => { useEffect(() => {
loadModelData().then(() => {})
}, [])
useEffect(() => {
if (isLoadingModels || !dynamicModelGroups) {
return
}
if (!DMXAPIPaintings || DMXAPIPaintings.length === 0) { if (!DMXAPIPaintings || DMXAPIPaintings.length === 0) {
const newPainting = getNewPainting() const newPainting = getNewPainting()
addPainting('DMXAPIPaintings', newPainting) addPainting('DMXAPIPaintings', newPainting)
@ -657,8 +686,26 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
if (painting?.generationMode) { if (painting?.generationMode) {
setModelOptions(getModelOptions(painting.generationMode as generationModeType)) setModelOptions(getModelOptions(painting.generationMode as generationModeType))
} }
// 如果当前painting没有model设置默认模型
if (painting && !painting.model && allModels.length > 0) {
const currentMode = painting.generationMode || MODEOPTIONS[0].value
const modelGroups = getModelOptions(currentMode as generationModeType)
let firstModel = ''
let priceModel = ''
for (const provider of Object.keys(modelGroups)) {
if (modelGroups[provider] && modelGroups[provider].length > 0) {
firstModel = modelGroups[provider][0].id
priceModel = modelGroups[provider][0].price
break
}
}
if (firstModel) {
updatePaintingState({ model: firstModel, priceModel: priceModel })
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) // 空依赖数组,只在组件挂载时执行一次 }, [isLoadingModels, dynamicModelGroups]) // 依赖模型加载状态
return ( return (
<Container> <Container>
@ -715,13 +762,20 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
</> </>
)} )}
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>{t('common.model')}</SettingTitle> <SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>
<Select value={painting.model} onChange={onSelectModel} style={{ width: '100%' }}> {t('common.model')} <SettingPrice>{painting.priceModel !== '0' ? painting.priceModel : ''}</SettingPrice>
</SettingTitle>
<Select
value={painting.model}
onChange={onSelectModel}
style={{ width: '100%' }}
loading={isLoadingModels}
placeholder={isLoadingModels ? t('common.loading') : t('common.select_model')}>
{Object.entries(modelOptions).map(([provider, models]) => { {Object.entries(modelOptions).map(([provider, models]) => {
if (models.length === 0) return null if ((models as any[]).length === 0) return null
return ( return (
<Select.OptGroup label={provider} key={provider}> <Select.OptGroup label={provider} key={provider}>
{models.map((model) => ( {(models as any[]).map((model) => (
<Select.Option key={model.id} value={model.id}> <Select.Option key={model.id} value={model.id}>
{model.name} {model.name}
</Select.Option> </Select.Option>
@ -1034,4 +1088,11 @@ const LoadTextWrap = styled.div`
1px 1px 0 #ffffff; 1px 1px 0 #ffffff;
` `
const SettingPrice = styled.div`
margin-left: auto;
color: var(--color-primary);
font-size: 11px;
font-weight: 500;
`
export default DmxapiPage export default DmxapiPage

View File

@ -5,9 +5,24 @@ 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 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 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 { t } from 'i18next'
import { generationModeType } from '../../../types' import { DmxapiPainting, generationModeType } from '../../../types'
// 模型数据类型
export type DMXApiModelData = {
id: string
provider: string
name: string
price: string
}
// 模型分组类型
export type DMXApiModelGroups = {
TEXT_TO_IMAGES?: Record<string, DMXApiModelData[]>
IMAGE_EDIT?: Record<string, DMXApiModelData[]>
IMAGE_MERGE?: Record<string, DMXApiModelData[]>
}
export const STYLE_TYPE_OPTIONS = [ export const STYLE_TYPE_OPTIONS = [
{ label: '吉卜力', value: '吉卜力' }, { label: '吉卜力', value: '吉卜力' },
@ -39,67 +54,6 @@ export const STYLE_TYPE_OPTIONS = [
{ label: '巴洛克', value: '巴洛克' } { label: '巴洛克', value: '巴洛克' }
] ]
export const TEXT_TO_IMAGES_MODELS = [
{
id: 'seedream-3.0',
provider: 'doubao',
name: ' 即梦 seedream-3.0'
},
{
id: 'flux-kontext-pro',
provider: 'Black Forest Labs',
name: 'flux-kontext-pro'
},
{
id: 'flux-kontext-max',
provider: 'Black Forest Labs',
name: 'flux-kontext-max'
},
{
id: 'imagen4',
provider: 'Google',
name: 'imagen4'
}
]
export const IMAGE_EDIT_MODELS = [
{
id: 'gpt-image-1',
provider: 'OpenAI',
name: 'gpt-image-1'
},
{
id: 'flux-kontext-pro',
provider: 'Black Forest Labs',
name: 'flux-kontext-pro'
},
{
id: 'flux-kontext-max',
provider: 'Black Forest Labs',
name: 'flux-kontext-max'
}
]
export const IMAGE_MERGE_MODELS = [
{
id: 'gpt-image-1',
provider: 'OpenAI',
name: 'gpt-image-1'
},
{
id: 'flux-kontext-pro',
provider: 'Black Forest Labs',
name: 'flux-kontext-pro'
},
{
id: 'flux-kontext-max',
provider: 'Black Forest Labs',
name: 'flux-kontext-max'
}
]
export const ALL_MODELS = [...TEXT_TO_IMAGES_MODELS, ...IMAGE_EDIT_MODELS, ...IMAGE_MERGE_MODELS]
export const IMAGE_SIZES = [ export const IMAGE_SIZES = [
{ {
label: '1:1', label: '1:1',
@ -145,7 +99,7 @@ export const DEFAULT_PAINTING: DmxapiPainting = {
n: 1, n: 1,
seed: '', seed: '',
style_type: '', style_type: '',
model: TEXT_TO_IMAGES_MODELS[0].id, model: '', // 将在运行时动态设置
autoCreate: false, autoCreate: false,
generationMode: generationModeType.GENERATION generationMode: generationModeType.GENERATION
} }
@ -156,24 +110,24 @@ export const MODEOPTIONS = [
{ label: '合并图', value: generationModeType.MERGE } { label: '合并图', value: generationModeType.MERGE }
] ]
// 按品牌分组的模型配置 // 获取模型分组数据
export const MODEL_GROUPS = { export const GetModelGroup = async (): Promise<DMXApiModelGroups> => {
TEXT_TO_IMAGES: { try {
Doubao: TEXT_TO_IMAGES_MODELS.filter((model) => model.provider === 'doubao'), const response = await fetch('https://dmxapi.cn/cherry_painting_models.json')
OpenAI: TEXT_TO_IMAGES_MODELS.filter((model) => model.provider === 'OpenAI'),
'Black Forest Labs': IMAGE_EDIT_MODELS.filter((model) => model.provider === 'Black Forest Labs'), if (response.ok) {
Google: TEXT_TO_IMAGES_MODELS.filter((model) => model.provider === 'Google') const data = await response.json()
},
IMAGE_EDIT: { if (data) {
Doubao: IMAGE_EDIT_MODELS.filter((model) => model.provider === 'doubao'), return data
OpenAI: IMAGE_EDIT_MODELS.filter((model) => model.provider === 'OpenAI'), }
'Black Forest Labs': IMAGE_EDIT_MODELS.filter((model) => model.provider === 'Black Forest Labs'), }
Google: IMAGE_EDIT_MODELS.filter((model) => model.provider === 'Google') } catch {
}, /* empty */
IMAGE_MERGE: {
Doubao: IMAGE_MERGE_MODELS.filter((model) => model.provider === 'doubao'),
OpenAI: IMAGE_MERGE_MODELS.filter((model) => model.provider === 'OpenAI'),
'Black Forest Labs': IMAGE_MERGE_MODELS.filter((model) => model.provider === 'Black Forest Labs'),
Google: IMAGE_MERGE_MODELS.filter((model) => model.provider === 'Google')
} }
window.message.error({
content: t('paintings.req_error_model')
})
return {}
} }

View File

@ -300,6 +300,7 @@ export interface DmxapiPainting extends PaintingParams {
style_type?: string style_type?: string
autoCreate?: boolean autoCreate?: boolean
generationMode?: generationModeType generationMode?: generationModeType
priceModel?: string
} }
export interface TokenFluxPainting extends PaintingParams { export interface TokenFluxPainting extends PaintingParams {