feat: 自定义 OpenAI 兼容上游聚合 + 权重/并发调度

- custom provider:把任意 v1 端点当账号(base_url+key),按 model id 路由,
  直连不走代理;支持 images/generations、images/edits、Sora 式异步视频
- 调度器全局权重优先 + 每账号并发感知(上游账号可配并发,其余系统固定)
- 后台:添加/编辑上游、自定义模型表单(每档普通/代理价,留空=不支持)、
  账号权重/并发列与导入权重
- grok-video / nano-banana-2(runway) 写入硬编码目录(开源自带);
  去掉 540p,Adobe 视频最低 720p
- UI:SelectMenu 与输入框等高、测试耗时改用秒

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-28 21:59:46 +08:00
co-authored by Claude Opus 4.8
parent d289ec378d
commit 3ef84bb9b3
17 changed files with 1149 additions and 64 deletions
+17 -2
View File
@@ -7,10 +7,14 @@ import Icon from './Icon.vue'
const emit = defineEmits(['close', 'imported'])
const input = ref('')
const weight = ref(0)
const status = ref('')
const isError = ref(false)
const submitting = ref(false)
// type → token pool (for the post-import weight PATCH).
const TYPE_POOL = { openai: 'chatgpt', adobe: 'adobe', runway: 'runway', leonardo: 'leonardo', krea: 'krea', imagine: 'imagine', grok: 'grok' }
// Live preview of what the parser would extract — updates as the user types
// so they can see whether their paste was understood before clicking import.
const detected = computed(() => {
@@ -56,8 +60,15 @@ async function doSmartImport() {
: it.type === 'imagine'
? await api('/tokens/import-imagine-token', jsonBody('POST', { value: it.value }))
: await api('/tokens/import-adobe-cookie', jsonBody('POST', { cookie: it.value }))
if (r.ok) ok++
else { fail++; errs.push(`${it.type}: ${r.data?.detail || r.status}`) }
if (r.ok) {
ok++
// Apply the chosen weight to the freshly-imported account (best-effort).
const w = Number(weight.value) || 0
const pool = TYPE_POOL[it.type]
if (w !== 0 && pool && r.data?.id) {
try { await api(`/tokens/${pool}/${r.data.id}`, jsonBody('PATCH', { weight: w })) } catch (_) {}
}
} else { fail++; errs.push(`${it.type}: ${r.data?.detail || r.status}`) }
} catch (e) {
fail++; errs.push(`${it.type}: ${e}`)
}
@@ -131,6 +142,10 @@ async function doSmartImport() {
</template>
<span v-else class="text-rose-600">未识别到任何 Cookie JWT</span>
</div>
<div class="mt-3 flex items-center gap-2">
<label class="text-xs text-slate-500 whitespace-nowrap">权重(本批账号,高的优先)</label>
<input v-model.number="weight" type="number" class="field !w-24" placeholder="0" />
</div>
<button @click="doSmartImport" :disabled="submitting || !detected.total" class="btn-primary w-full mt-3">
{{ submitting ? '导入中…' : (detected.total ? `识别并导入 (${detected.total})` : '识别并导入') }}
</button>