diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 974bfc892b..c44caafb44 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -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", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 7833e11a09..a430aba998 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -1134,6 +1134,7 @@ "quality": "高品質", "turbo": "高速" }, + "req_error_model": "モデルの取得に失敗しました", "req_error_no_balance": "トークンの有効性を確認してください", "req_error_text": "サーバーが混雑しているか、プロンプトに「著作権用語」または「敏感な用語」が含まれています。もう一度お試しください。", "req_error_token": "トークンの有効性を確認してください", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index e5a8029335..52055b83ea 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -1134,6 +1134,7 @@ "quality": "Качественно", "turbo": "Быстро" }, + "req_error_model": "Не удалось получить модель", "req_error_no_balance": "Пожалуйста, проверьте действительность токена", "req_error_text": "Сервер перегружен или в запросе обнаружены «авторские» либо «чувствительные» слова. Пожалуйста, повторите попытку.", "req_error_token": "Пожалуйста, проверьте действительность токена", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index ede9a74274..e9a4a6330a 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1134,6 +1134,7 @@ "quality": "高质量", "turbo": "快速" }, + "req_error_model": "获取模型失败", "req_error_no_balance": "请检查令牌有效性", "req_error_text": "服务器繁忙或提示词出现 \"版权词\" 和 \"敏感词\" ,请重试。", "req_error_token": "请检查令牌有效性", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 5071b840b3..0f35eac540 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1134,6 +1134,7 @@ "quality": "高品質", "turbo": "快速" }, + "req_error_model": "獲取模型失敗", "req_error_no_balance": "請檢查令牌的有效性", "req_error_text": "伺服器繁忙或提示詞中出現「版權詞」或「敏感詞」,請重試。", "req_error_token": "請檢查令牌的有效性", diff --git a/src/renderer/src/pages/paintings/DmxapiPage.tsx b/src/renderer/src/pages/paintings/DmxapiPage.tsx index 18547fed0c..87c869c46f 100644 --- a/src/renderer/src/pages/paintings/DmxapiPage.tsx +++ b/src/renderer/src/pages/paintings/DmxapiPage.tsx @@ -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(null) + const [allModels, setAllModels] = useState([]) + const [isLoadingModels, setIsLoadingModels] = useState(true) + const [currentImageIndex, setCurrentImageIndex] = useState(0) const [isLoading, setIsLoading] = useState(false) const [abortController, setAbortController] = useState(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(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) => { 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 ( @@ -715,13 +762,20 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => { )} - {t('common.model')} - {Object.entries(modelOptions).map(([provider, models]) => { - if (models.length === 0) return null + if ((models as any[]).length === 0) return null return ( - {models.map((model) => ( + {(models as any[]).map((model) => ( {model.name} @@ -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 diff --git a/src/renderer/src/pages/paintings/config/DmxapiConfig.ts b/src/renderer/src/pages/paintings/config/DmxapiConfig.ts index 3b95b9ae67..7336b66451 100644 --- a/src/renderer/src/pages/paintings/config/DmxapiConfig.ts +++ b/src/renderer/src/pages/paintings/config/DmxapiConfig.ts @@ -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 + IMAGE_EDIT?: Record + IMAGE_MERGE?: Record +} 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 => { + 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 {} } diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 47410fe063..77305c74cc 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -300,6 +300,7 @@ export interface DmxapiPainting extends PaintingParams { style_type?: string autoCreate?: boolean generationMode?: generationModeType + priceModel?: string } export interface TokenFluxPainting extends PaintingParams {