mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-27 06:02:00 +08:00
feat: dmxapi painting dynamic model (#8302)
* 模型调整为动态 * 模型调整为动态 * 代码优化,新增类型 * 代码优化,新增类型 * 调整locales的顺序 * 修复lint报错
This commit is contained in:
parent
38bb9a77e0
commit
37508493fb
@ -1134,6 +1134,7 @@
|
||||
"quality": "Quality",
|
||||
"turbo": "Turbo"
|
||||
},
|
||||
"req_error_model": "Failed to fetch the model",
|
||||
"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_token": "Please check the validity of the token",
|
||||
|
||||
@ -1134,6 +1134,7 @@
|
||||
"quality": "高品質",
|
||||
"turbo": "高速"
|
||||
},
|
||||
"req_error_model": "モデルの取得に失敗しました",
|
||||
"req_error_no_balance": "トークンの有効性を確認してください",
|
||||
"req_error_text": "サーバーが混雑しているか、プロンプトに「著作権用語」または「敏感な用語」が含まれています。もう一度お試しください。",
|
||||
"req_error_token": "トークンの有効性を確認してください",
|
||||
|
||||
@ -1134,6 +1134,7 @@
|
||||
"quality": "Качественно",
|
||||
"turbo": "Быстро"
|
||||
},
|
||||
"req_error_model": "Не удалось получить модель",
|
||||
"req_error_no_balance": "Пожалуйста, проверьте действительность токена",
|
||||
"req_error_text": "Сервер перегружен или в запросе обнаружены «авторские» либо «чувствительные» слова. Пожалуйста, повторите попытку.",
|
||||
"req_error_token": "Пожалуйста, проверьте действительность токена",
|
||||
|
||||
@ -1134,6 +1134,7 @@
|
||||
"quality": "高质量",
|
||||
"turbo": "快速"
|
||||
},
|
||||
"req_error_model": "获取模型失败",
|
||||
"req_error_no_balance": "请检查令牌有效性",
|
||||
"req_error_text": "服务器繁忙或提示词出现 \"版权词\" 和 \"敏感词\" ,请重试。",
|
||||
"req_error_token": "请检查令牌有效性",
|
||||
|
||||
@ -1134,6 +1134,7 @@
|
||||
"quality": "高品質",
|
||||
"turbo": "快速"
|
||||
},
|
||||
"req_error_model": "獲取模型失敗",
|
||||
"req_error_no_balance": "請檢查令牌的有效性",
|
||||
"req_error_text": "伺服器繁忙或提示詞中出現「版權詞」或「敏感詞」,請重試。",
|
||||
"req_error_token": "請檢查令牌的有效性",
|
||||
|
||||
@ -30,11 +30,10 @@ import Artboard from './components/Artboard'
|
||||
import ImageUploader from './components/ImageUploader'
|
||||
import PaintingsList from './components/PaintingsList'
|
||||
import {
|
||||
ALL_MODELS,
|
||||
COURSE_URL,
|
||||
DEFAULT_PAINTING,
|
||||
GetModelGroup,
|
||||
IMAGE_SIZES,
|
||||
MODEL_GROUPS,
|
||||
MODEOPTIONS,
|
||||
STYLE_TYPE_OPTIONS
|
||||
} from './config/DmxapiConfig'
|
||||
@ -58,6 +57,11 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
|
||||
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 [isLoading, setIsLoading] = useState(false)
|
||||
const [abortController, setAbortController] = useState<AbortController | null>(null)
|
||||
@ -84,16 +88,20 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
})
|
||||
|
||||
const getModelOptions = (mode: generationModeType) => {
|
||||
if (!dynamicModelGroups) {
|
||||
return {}
|
||||
}
|
||||
|
||||
if (mode === generationModeType.EDIT) {
|
||||
return MODEL_GROUPS.IMAGE_EDIT
|
||||
return dynamicModelGroups.IMAGE_EDIT || {}
|
||||
}
|
||||
|
||||
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(() => {
|
||||
@ -104,6 +112,23 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
|
||||
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状态的辅助函数
|
||||
const updatePaintingState = (updates: Partial<DmxapiPainting>) => {
|
||||
const updatedPainting = { ...painting, ...updates }
|
||||
@ -145,9 +170,9 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
}
|
||||
|
||||
const onSelectModel = (modelId: string) => {
|
||||
const model = ALL_MODELS.find((m) => m.id === modelId)
|
||||
const model = allModels.find((m) => m.id === modelId)
|
||||
if (model) {
|
||||
updatePaintingState({ model: modelId })
|
||||
updatePaintingState({ model: modelId, priceModel: model.price })
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,9 +249,11 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
|
||||
// 获取第一个非空分组的第一个模型
|
||||
let firstModel = ''
|
||||
let priceModel = ''
|
||||
for (const provider of Object.keys(newModelGroups)) {
|
||||
if (newModelGroups[provider].length > 0) {
|
||||
if (newModelGroups[provider] && newModelGroups[provider].length > 0) {
|
||||
firstModel = newModelGroups[provider][0].id
|
||||
priceModel = newModelGroups[provider][0].price
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -235,7 +262,8 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
if (Array.isArray(painting.urls) && painting.urls.length > 0) {
|
||||
const newPainting = getNewPainting({
|
||||
generationMode: v,
|
||||
model: firstModel // 使用新模式下的第一个模型
|
||||
model: firstModel, // 使用新模式下的第一个模型
|
||||
priceModel: priceModel
|
||||
})
|
||||
const addedPainting = addPainting('DMXAPIPaintings', newPainting)
|
||||
setPainting(addedPainting)
|
||||
@ -243,7 +271,8 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
// 否则更新当前painting
|
||||
updatePaintingState({
|
||||
generationMode: v,
|
||||
model: firstModel // 使用新模式下的第一个模型
|
||||
model: firstModel, // 使用新模式下的第一个模型
|
||||
priceModel: priceModel
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -381,14 +410,6 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
|
||||
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 }) => {
|
||||
if (item.b64_json) {
|
||||
return 'data:image/png;base64,' + item.b64_json
|
||||
@ -634,6 +655,14 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadModelData().then(() => {})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoadingModels || !dynamicModelGroups) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!DMXAPIPaintings || DMXAPIPaintings.length === 0) {
|
||||
const newPainting = getNewPainting()
|
||||
addPainting('DMXAPIPaintings', newPainting)
|
||||
@ -657,8 +686,26 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
if (painting?.generationMode) {
|
||||
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
|
||||
}, []) // 空依赖数组,只在组件挂载时执行一次
|
||||
}, [isLoadingModels, dynamicModelGroups]) // 依赖模型加载状态
|
||||
|
||||
return (
|
||||
<Container>
|
||||
@ -715,13 +762,20 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
</>
|
||||
)}
|
||||
|
||||
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>{t('common.model')}</SettingTitle>
|
||||
<Select value={painting.model} onChange={onSelectModel} style={{ width: '100%' }}>
|
||||
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>
|
||||
{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]) => {
|
||||
if (models.length === 0) return null
|
||||
if ((models as any[]).length === 0) return null
|
||||
return (
|
||||
<Select.OptGroup label={provider} key={provider}>
|
||||
{models.map((model) => (
|
||||
{(models as any[]).map((model) => (
|
||||
<Select.Option key={model.id} value={model.id}>
|
||||
{model.name}
|
||||
</Select.Option>
|
||||
@ -1034,4 +1088,11 @@ const LoadTextWrap = styled.div`
|
||||
1px 1px 0 #ffffff;
|
||||
`
|
||||
|
||||
const SettingPrice = styled.div`
|
||||
margin-left: auto;
|
||||
color: var(--color-primary);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
`
|
||||
|
||||
export default DmxapiPage
|
||||
|
||||
@ -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 ImageSize16_9 from '@renderer/assets/images/paintings/image-size-16-9.svg'
|
||||
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 = [
|
||||
{ label: '吉卜力', value: '吉卜力' },
|
||||
@ -39,67 +54,6 @@ export const STYLE_TYPE_OPTIONS = [
|
||||
{ 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 = [
|
||||
{
|
||||
label: '1:1',
|
||||
@ -145,7 +99,7 @@ export const DEFAULT_PAINTING: DmxapiPainting = {
|
||||
n: 1,
|
||||
seed: '',
|
||||
style_type: '',
|
||||
model: TEXT_TO_IMAGES_MODELS[0].id,
|
||||
model: '', // 将在运行时动态设置
|
||||
autoCreate: false,
|
||||
generationMode: generationModeType.GENERATION
|
||||
}
|
||||
@ -156,24 +110,24 @@ export const MODEOPTIONS = [
|
||||
{ label: '合并图', value: generationModeType.MERGE }
|
||||
]
|
||||
|
||||
// 按品牌分组的模型配置
|
||||
export const MODEL_GROUPS = {
|
||||
TEXT_TO_IMAGES: {
|
||||
Doubao: TEXT_TO_IMAGES_MODELS.filter((model) => model.provider === 'doubao'),
|
||||
OpenAI: TEXT_TO_IMAGES_MODELS.filter((model) => model.provider === 'OpenAI'),
|
||||
'Black Forest Labs': IMAGE_EDIT_MODELS.filter((model) => model.provider === 'Black Forest Labs'),
|
||||
Google: TEXT_TO_IMAGES_MODELS.filter((model) => model.provider === 'Google')
|
||||
},
|
||||
IMAGE_EDIT: {
|
||||
Doubao: IMAGE_EDIT_MODELS.filter((model) => model.provider === 'doubao'),
|
||||
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')
|
||||
},
|
||||
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')
|
||||
// 获取模型分组数据
|
||||
export const GetModelGroup = async (): Promise<DMXApiModelGroups> => {
|
||||
try {
|
||||
const response = await fetch('https://dmxapi.cn/cherry_painting_models.json')
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
|
||||
if (data) {
|
||||
return data
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
window.message.error({
|
||||
content: t('paintings.req_error_model')
|
||||
})
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
@ -300,6 +300,7 @@ export interface DmxapiPainting extends PaintingParams {
|
||||
style_type?: string
|
||||
autoCreate?: boolean
|
||||
generationMode?: generationModeType
|
||||
priceModel?: string
|
||||
}
|
||||
|
||||
export interface TokenFluxPainting extends PaintingParams {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user