增加模型别名
This commit is contained in:
@@ -15,6 +15,7 @@ const ALL_RES = ['1K', '2K', '4K', '720p', '1080p']
|
||||
const DUR_OPTS = ['5s', '6s', '8s', '10s', '15s']
|
||||
|
||||
const id = ref('')
|
||||
const alias = ref('')
|
||||
const type = ref('image')
|
||||
const ratios = ref(['1:1', '16:9', '9:16'])
|
||||
const maxRefs = ref(0)
|
||||
@@ -89,6 +90,7 @@ async function save() {
|
||||
const body = {
|
||||
id: mid,
|
||||
name: mid,
|
||||
alias: alias.value.trim(),
|
||||
type: type.value,
|
||||
provider: 'custom',
|
||||
prices: r.prices,
|
||||
@@ -127,7 +129,7 @@ async function save() {
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<p class="text-xs text-slate-500 leading-relaxed">
|
||||
id 要与上游模型名<strong class="text-slate-700">一致</strong> —— 生成时按 id 自动路由到「支持该 id 的上游账号」。价格按本地价计费。
|
||||
id 要与上游模型名<strong class="text-slate-700">一致</strong> —— 生成时按 id 自动路由到「支持该 id 的上游账号」。价格按本地价计费。设了别名后,对外只用别名调用(原 id 调不到),但内部仍按 id 路由到上游,不影响。
|
||||
</p>
|
||||
|
||||
<div class="flex gap-3">
|
||||
@@ -135,6 +137,10 @@ async function save() {
|
||||
<label class="text-xs text-slate-500 block mb-1">模型 id <span class="text-rose-500">*</span></label>
|
||||
<input v-model="id" class="field font-mono text-xs h-10" placeholder="gpt-image-2" />
|
||||
</div>
|
||||
<div class="w-40">
|
||||
<label class="text-xs text-slate-500 block mb-1">别名(选填)</label>
|
||||
<input v-model="alias" class="field font-mono text-xs h-10" placeholder="对外调用名" />
|
||||
</div>
|
||||
<div class="w-28">
|
||||
<label class="text-xs text-slate-500 block mb-1">类型</label>
|
||||
<SelectMenu v-model="type" :options="[{value:'image',label:'图像'},{value:'video',label:'视频'}]" />
|
||||
|
||||
@@ -18,6 +18,7 @@ const REF_MODE_LABEL = { none: '无', frame: '首帧/首尾帧', asset: '参考
|
||||
const catalog = ref([])
|
||||
const loading = ref(true)
|
||||
const selectedId = ref(props.model?.id || '')
|
||||
const alias = ref(props.model?.alias || '')
|
||||
const imagePrices = ref({}) // 普通价 { '1K': '', '2K': '', ... } keyed by resolutions
|
||||
const videoPrices = ref({}) // 普通价 { '5s': '', '10s': '', ... } keyed by durations
|
||||
const imagePricesAgent = ref({}) // 代理价(留空 = 跟随普通价)
|
||||
@@ -138,6 +139,7 @@ async function save() {
|
||||
duration_prices,
|
||||
prices_agent,
|
||||
duration_prices_agent,
|
||||
alias: alias.value.trim(),
|
||||
max_reference_images: e.max_reference_images || 0,
|
||||
reference_mode: e.reference_mode || 'none',
|
||||
weight: Number(weight.value) || 0,
|
||||
@@ -153,6 +155,7 @@ async function save() {
|
||||
prices,
|
||||
prices_agent,
|
||||
image_to_image: !!e.image_to_image,
|
||||
alias: alias.value.trim(),
|
||||
// 多参考图:把目录定义的张数(gpt=3/seedream=6/flux=4 …)写进模型,
|
||||
// 否则后端仍按旧值(默认 1)限制。
|
||||
max_reference_images: e.max_reference_images || 0,
|
||||
@@ -197,6 +200,12 @@ async function save() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="lbl">别名</label>
|
||||
<input v-model="alias" class="field font-mono" placeholder="可选,对外名" />
|
||||
<p class="text-[11px] text-white/40 mt-1.5">设置后原模型名将不可调用,画图台 / API / 文档都改用别名</p>
|
||||
</div>
|
||||
|
||||
<!-- read-only param summary, straight from the loaded catalog -->
|
||||
<div v-if="entry" class="rounded-xl bg-white/[0.03] ring-1 ring-white/[0.06] p-3.5 space-y-2.5">
|
||||
<div class="flex items-center gap-2">
|
||||
|
||||
@@ -8,6 +8,7 @@ const props = defineProps({
|
||||
model: { type: Object, required: true },
|
||||
})
|
||||
const emit = defineEmits(['close'])
|
||||
const publishName = computed(() => props.model.alias || props.model.id)
|
||||
|
||||
const isVideo = props.model.type === 'video'
|
||||
|
||||
@@ -124,7 +125,7 @@ async function run() {
|
||||
resultUrl.value = ''
|
||||
resultKind.value = ''
|
||||
const payload = {
|
||||
model: props.model.id,
|
||||
model: publishName.value,
|
||||
prompt: prompt.value,
|
||||
ratio: ratio.value,
|
||||
resolution: resolution.value,
|
||||
@@ -198,7 +199,7 @@ async function recover() {
|
||||
<div class="px-5 py-4 border-b border-white/[0.06] flex items-center justify-between">
|
||||
<div class="min-w-0">
|
||||
<h2 class="text-sm font-semibold">测试模型</h2>
|
||||
<div class="text-xs text-white/45 font-mono truncate">{{ model.id }}</div>
|
||||
<div class="text-xs text-white/45 font-mono truncate">{{ publishName }}</div>
|
||||
</div>
|
||||
<button @click="emit('close')" class="text-white/40 hover:text-white transition-colors">
|
||||
<Icon name="close" class="w-5 h-5" />
|
||||
|
||||
@@ -19,8 +19,11 @@ onMounted(async () => {
|
||||
|
||||
const imageModels = computed(() => models.value.filter((m) => m.type === 'image'))
|
||||
const videoModels = computed(() => models.value.filter((m) => m.type === 'video'))
|
||||
const sampleImage = computed(() => imageModels.value[0]?.id || 'firefly-image-4')
|
||||
const sampleVideo = computed(() => videoModels.value[0]?.id || 'firefly-kling3')
|
||||
function pubName(m) {
|
||||
return m?.alias || m?.id || ''
|
||||
}
|
||||
const sampleImage = computed(() => pubName(imageModels.value[0]) || 'firefly-image-4')
|
||||
const sampleVideo = computed(() => pubName(videoModels.value[0]) || 'firefly-kling3')
|
||||
const sampleSeconds = computed(() => String(videoModels.value[0]?.durations?.[0] || '8s').replace(/s$/, ''))
|
||||
|
||||
function priceOf(m) {
|
||||
@@ -41,18 +44,18 @@ function priceOf(m) {
|
||||
|
||||
// ---- request parameter tables ----
|
||||
const imageParams = [
|
||||
['model', 'string', '必填', '模型 id,见上表(图像)'],
|
||||
['model', 'string', '必填', '模型名(别名优先),见上表(图像)'],
|
||||
['prompt', 'string', '必填', '文字描述'],
|
||||
['size', 'string', '可选', '宽x高,如 "1024x1024"。同时决定「比例」+「分辨率档」(按长边)。具体怎么填见下方对照表;留空 = 1:1 · 2K'],
|
||||
]
|
||||
const editParams = [
|
||||
['image', 'file', '必填', '输入图;多张参考图重复 image[] 字段(multipart 文件上传)'],
|
||||
['prompt', 'string', '必填', '编辑/参考描述'],
|
||||
['model', 'string', '必填', '模型 id(需支持图生图)'],
|
||||
['model', 'string', '必填', '模型名(别名优先,需支持图生图)'],
|
||||
['size', 'string', '可选', '同图像:决定比例 + 分辨率档(见下方对照表)'],
|
||||
]
|
||||
const videoParams = [
|
||||
['model', 'string', '必填', '模型 id,见上表(视频)'],
|
||||
['model', 'string', '必填', '模型名(别名优先),见上表(视频)'],
|
||||
['prompt', 'string', '必填', '文字描述'],
|
||||
['seconds', 'string|int', '必填', '时长秒数,如 "5" "8"(取决于模型支持)'],
|
||||
['size', 'string', '可选', '如 "1280x720" / "720x1280" → 决定比例与分辨率'],
|
||||
@@ -273,7 +276,7 @@ async function copy(text) {
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="m in models" :key="m.id" class="border-b border-white/[0.04] last:border-0">
|
||||
<td class="px-4 py-3 font-mono text-white/90">{{ m.id }}</td>
|
||||
<td class="px-4 py-3 font-mono text-white/90">{{ pubName(m) }}</td>
|
||||
<td class="px-4 py-3 text-white/60">{{ m.type === 'video' ? '视频' : '图像' }}</td>
|
||||
<td class="px-4 py-3 text-white/60">{{ (m.type === 'video' ? m.durations : m.resolutions || [])?.join(' · ') || '—' }}</td>
|
||||
<td class="px-4 py-3 text-right tabular-nums text-white/80">{{ priceOf(m) }}</td>
|
||||
|
||||
@@ -66,7 +66,7 @@ const filtered = computed(() => {
|
||||
if (kindFilter.value && m.type !== kindFilter.value) return false
|
||||
if (statusFilter.value === 'enabled' && m.enabled === false) return false
|
||||
if (statusFilter.value === 'disabled' && m.enabled !== false) return false
|
||||
if (q && !(m.id.toLowerCase().includes(q) || (m.provider || '').toLowerCase().includes(q))) return false
|
||||
if (q && !(m.id.toLowerCase().includes(q) || (m.alias || '').toLowerCase().includes(q) || (m.provider || '').toLowerCase().includes(q))) return false
|
||||
return true
|
||||
})
|
||||
})
|
||||
@@ -114,7 +114,7 @@ onMounted(loadModels)
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-1 min-w-[200px]">
|
||||
<input v-model="search" class="field !py-1.5 text-xs" placeholder="搜索 模型 ID / Provider…" />
|
||||
<input v-model="search" class="field !py-1.5 text-xs" placeholder="搜索 模型 ID / 别名 / Provider…" />
|
||||
</div>
|
||||
<button @click="loadModels" class="btn-soft">
|
||||
<Icon name="refresh" class="w-3.5 h-3.5" /> 刷新
|
||||
@@ -163,7 +163,10 @@ onMounted(loadModels)
|
||||
class="border-b border-white/[0.04] hover:bg-white/[0.03] transition-colors">
|
||||
<!-- Model id + provider underneath -->
|
||||
<td class="px-5 py-3.5 align-middle min-w-0">
|
||||
<div class="font-mono text-xs text-white/90 truncate" :title="m.id">{{ m.id }}</div>
|
||||
<div class="flex items-start gap-2 min-w-0">
|
||||
<div class="font-mono text-xs text-white/90 truncate" :title="m.id">{{ m.id }}</div>
|
||||
<span v-if="m.alias" class="inline-flex items-center rounded-full px-2 py-0.5 text-[10px] bg-sky-500/10 text-sky-300 ring-1 ring-sky-400/20 shrink-0">别名: {{ m.alias }}</span>
|
||||
</div>
|
||||
<div class="mt-1 text-[10px] text-white/45 capitalize truncate">{{ m.provider || '—' }}</div>
|
||||
</td>
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ const models = computed(() =>
|
||||
allModels.value.filter((m) => m.enabled !== false && m.type === mode.value),
|
||||
)
|
||||
const modelOptions = computed(() =>
|
||||
models.value.map((m) => ({ value: m.id, label: m.name || m.id })),
|
||||
models.value.map((m) => ({ value: m.id, label: m.alias || m.name || m.id })),
|
||||
)
|
||||
const model = computed(() => allModels.value.find((m) => m.id === modelId.value) || null)
|
||||
const familyPreset = computed(() => {
|
||||
@@ -305,7 +305,7 @@ async function fireOne() {
|
||||
// user edits the form (or fires another batch) while this one runs.
|
||||
const task = {
|
||||
id: Math.random().toString(36).slice(2, 10),
|
||||
model: modelId.value,
|
||||
model: model.value?.alias || modelId.value,
|
||||
kind: mode.value,
|
||||
prompt: prompt.value,
|
||||
ratio: ratio.value,
|
||||
|
||||
Reference in New Issue
Block a user