Unify ComfyUI ingress model ID and fix seed validation errors.
CI / docker (push) Successful in 3m46s

Merge model_id/alias in the image provider UI, and ensure ComfyUI never receives seed -1 after cloneMap type coercion.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
renjue
2026-06-24 14:17:49 +08:00
parent 76ba500417
commit e761074fb9
8 changed files with 192 additions and 34 deletions
+4 -8
View File
@@ -1,11 +1,9 @@
<template>
<el-dialog v-model="visible" :title="t('providers.workflowDialogTitle')" width="720px" destroy-on-close @closed="reset">
<el-form label-width="100px">
<el-form-item :label="t('providers.modelId')">
<el-form-item :label="t('providers.ingressModelId')" required>
<el-input v-model="form.model_id" placeholder="luminary/flux-sdxl" />
</el-form-item>
<el-form-item :label="t('providers.modelAlias')">
<el-input v-model="form.alias" :placeholder="t('providers.modelAliasPlaceholder')" />
<p class="form-hint">{{ t('providers.ingressModelIdHint') }}</p>
</el-form-item>
<el-form-item :label="t('providers.displayName')">
<el-input v-model="form.display_name" />
@@ -92,7 +90,6 @@ const bindings = ref<BindingRow[]>([])
const form = ref({
model_id: '',
alias: '',
display_name: '',
workflow_type: 'txt2img',
workflow_json: '',
@@ -103,7 +100,7 @@ const requiredParams = computed(() =>
)
function reset() {
form.value = { model_id: '', alias: '', display_name: '', workflow_type: 'txt2img', workflow_json: '' }
form.value = { model_id: '', display_name: '', workflow_type: 'txt2img', workflow_json: '' }
nodes.value = []
bindings.value = []
}
@@ -158,14 +155,13 @@ function buildBinding() {
async function save() {
if (!form.value.model_id.trim()) {
ElMessage.warning(t('providers.modelIdRequired'))
ElMessage.warning(t('providers.ingressModelIdRequired'))
return
}
saving.value = true
try {
await api.post(`/providers/${props.providerId}/models`, {
model_id: form.value.model_id.trim(),
alias: form.value.alias.trim(),
display_name: form.value.display_name || form.value.model_id,
workflow_type: form.value.workflow_type,
workflow_json: form.value.workflow_json,
+3
View File
@@ -122,6 +122,9 @@ export default {
addModel: 'Add Model',
addModelDialogTitle: 'Add Model',
modelId: 'Model ID',
ingressModelId: 'Ingress Model ID',
ingressModelIdHint: 'Clients use this name in the version field of /v1/predictions',
ingressModelIdRequired: 'Ingress Model ID is required',
modelAlias: 'Ingress alias',
modelAliasPlaceholder: 'Optional; ingress API uses this name',
modelAliasHint: 'Leave empty to use upstream Model ID at ingress',
+3
View File
@@ -144,6 +144,9 @@ export default {
confirmDeleteModel: '确认删除此模型?',
workflowDialogTitle: '注册 Workflow 模型',
modelId: 'Model ID',
ingressModelId: '入口 Model ID',
ingressModelIdHint: '客户端在 /v1/predictions 的 version 字段使用此名称',
ingressModelIdRequired: '请填写入口 Model ID',
modelAlias: '入口别名',
modelAliasPlaceholder: '可选,入口 API 使用此名称',
modelAliasHint: '留空则入口使用上游 Model ID',
+2 -4
View File
@@ -313,11 +313,9 @@ async function loadCatalog() {
const ingressId = m.alias || m.model_id
models.push({
model_id: ingressId,
display_name: m.display_name || m.model_id,
display_name: m.display_name || ingressId,
provider_name: p.name,
label: m.alias
? `${m.alias}${m.model_id} (${p.name})`
: `${m.model_id} (${p.name})`,
label: `${ingressId} (${p.name})`,
})
}
}
+46 -18
View File
@@ -29,10 +29,17 @@
<div class="models-expand">
<div v-if="!row.models?.length" class="models-empty">{{ t('providers.modelsEmpty') }}</div>
<el-table v-else :data="row.models" size="small" class="models-table">
<el-table-column prop="model_id" :label="t('providers.upstreamModelId')" min-width="180" show-overflow-tooltip />
<el-table-column :label="t('providers.modelAlias')" min-width="140" show-overflow-tooltip>
<template #default="{ row: m }">{{ m.alias || '—' }}</template>
</el-table-column>
<template v-if="row.category === 'image'">
<el-table-column :label="t('providers.ingressModelId')" min-width="180" show-overflow-tooltip>
<template #default="{ row: m }">{{ modelIngressId(m) }}</template>
</el-table-column>
</template>
<template v-else>
<el-table-column prop="model_id" :label="t('providers.upstreamModelId')" min-width="180" show-overflow-tooltip />
<el-table-column :label="t('providers.modelAlias')" min-width="140" show-overflow-tooltip>
<template #default="{ row: m }">{{ m.alias || '—' }}</template>
</el-table-column>
</template>
<el-table-column v-if="row.category === 'image'" prop="workflow_type" :label="t('providers.workflowType')" width="100" />
<el-table-column prop="display_name" :label="t('providers.displayName')" min-width="160" show-overflow-tooltip />
<el-table-column :label="t('common.enabled')" width="80">
@@ -193,10 +200,17 @@
</span>
</div>
<el-table :data="providerModels" size="small" v-loading="modelsLoading">
<el-table-column prop="model_id" :label="t('providers.upstreamModelId')" min-width="180" show-overflow-tooltip />
<el-table-column :label="t('providers.modelAlias')" min-width="140" show-overflow-tooltip>
<template #default="{ row }">{{ row.alias || '—' }}</template>
</el-table-column>
<template v-if="current?.category === 'image'">
<el-table-column :label="t('providers.ingressModelId')" min-width="180" show-overflow-tooltip>
<template #default="{ row }">{{ modelIngressId(row) }}</template>
</el-table-column>
</template>
<template v-else>
<el-table-column prop="model_id" :label="t('providers.upstreamModelId')" min-width="180" show-overflow-tooltip />
<el-table-column :label="t('providers.modelAlias')" min-width="140" show-overflow-tooltip>
<template #default="{ row }">{{ row.alias || '—' }}</template>
</el-table-column>
</template>
<el-table-column v-if="current?.category === 'image'" prop="workflow_type" :label="t('providers.workflowType')" width="100" />
<el-table-column prop="display_name" :label="t('providers.displayName')" min-width="160" show-overflow-tooltip />
<el-table-column :label="t('common.enabled')" width="80">
@@ -241,13 +255,21 @@
<el-dialog v-model="showEditModel" :title="t('providers.editModelDialogTitle')" width="480px" destroy-on-close>
<el-form label-width="110px">
<el-form-item :label="t('providers.upstreamModelId')">
<el-input v-model="editModelForm.model_id" />
</el-form-item>
<el-form-item :label="t('providers.modelAlias')">
<el-input v-model="editModelForm.alias" :placeholder="t('providers.modelAliasPlaceholder')" />
<p class="form-hint">{{ t('providers.modelAliasHint') }}</p>
</el-form-item>
<template v-if="current?.category === 'image'">
<el-form-item :label="t('providers.ingressModelId')" required>
<el-input v-model="editModelForm.model_id" placeholder="luminary/flux-sdxl" />
<p class="form-hint">{{ t('providers.ingressModelIdHint') }}</p>
</el-form-item>
</template>
<template v-else>
<el-form-item :label="t('providers.upstreamModelId')">
<el-input v-model="editModelForm.model_id" />
</el-form-item>
<el-form-item :label="t('providers.modelAlias')">
<el-input v-model="editModelForm.alias" :placeholder="t('providers.modelAliasPlaceholder')" />
<p class="form-hint">{{ t('providers.modelAliasHint') }}</p>
</el-form-item>
</template>
<el-form-item :label="t('providers.displayName')">
<el-input v-model="editModelForm.display_name" />
</el-form-item>
@@ -639,11 +661,16 @@ function openAddModel() {
showAddModel.value = true
}
function modelIngressId(m: ModelEntry) {
return m.alias || m.model_id
}
function openEditModel(m: ModelEntry) {
const isImage = current.value?.category === 'image'
editModelForm.value = {
id: m.id,
model_id: m.model_id,
alias: m.alias || '',
model_id: isImage ? modelIngressId(m) : m.model_id,
alias: isImage ? '' : (m.alias || ''),
display_name: m.display_name || '',
}
showEditModel.value = true
@@ -651,10 +678,11 @@ function openEditModel(m: ModelEntry) {
async function saveEditModel() {
if (!current.value || !editModelForm.value.id) return
const isImage = current.value.category === 'image'
try {
await api.put(`/providers/${current.value.id}/models/${editModelForm.value.id}`, {
model_id: editModelForm.value.model_id.trim(),
alias: editModelForm.value.alias.trim(),
...(isImage ? { alias: '' } : { alias: editModelForm.value.alias.trim() }),
display_name: editModelForm.value.display_name.trim(),
})
} catch (e: any) {