渠道: 移除主页/favicon 特性, Base URL 可选+完整地址

- 回退渠道 homepage/favicon 字段与代理接口(未采用)
- Base URL 改为可选: 留空按供应商默认(openai/anthropic),
  兼容兼容型渠道必须填; 填完整地址(含 /v1)时归一化去尾
- 网关 upstreamURL 兜底去 /v1, 避免路径重复

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Sakurasan
2026-08-15 20:55:17 +08:00
co-authored by Claude
parent 4f92d7e0a4
commit d0bc28a4fe
8 changed files with 57 additions and 126 deletions
-1
View File
@@ -26,7 +26,6 @@ export interface Channel {
name: string
provider: 'openai' | 'anthropic' | 'compatible'
formats: string[] // chat | responses | messages
homepage?: string
base_url: string
api_key_masked: string
weight: number
+8 -37
View File
@@ -19,7 +19,6 @@ const busyId = ref<number | null>(null)
const form = reactive({
name: '',
formats: ['chat'] as string[],
homepage: '',
base_url: '',
api_key: '',
weight: 1,
@@ -29,24 +28,6 @@ const form = reactive({
enabled: true,
})
// favicon 兜底:灰色地球
const FAV_FALLBACK =
'data:image/svg+xml;utf8,' +
encodeURIComponent(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><circle cx="8" cy="8" r="6.5" fill="none" stroke="#a1a1aa" stroke-width="1.4"/><ellipse cx="8" cy="8" rx="3" ry="6.5" fill="none" stroke="#a1a1aa" stroke-width="1.4"/><path d="M1.8 8h12.4" stroke="#a1a1aa" stroke-width="1.4"/></svg>',
)
function onFavError(e: Event) {
;(e.target as HTMLImageElement).src = FAV_FALLBACK
}
function hostOf(u?: string): string {
if (!u) return '-'
try {
return new URL(u).host
} catch {
return u
}
}
async function load() {
try {
const { data } = await http.get('/admin/channels')
@@ -59,7 +40,7 @@ async function load() {
function openCreate() {
editing.value = null
Object.assign(form, {
name: '', formats: ['chat'], homepage: '', base_url: '', api_key: '',
name: '', formats: ['chat'], base_url: '', api_key: '',
weight: 1, priority: 0, timeout_ms: 120000, max_concurrency: 16, enabled: true,
})
editOpen.value = true
@@ -69,7 +50,6 @@ function openEdit(ch: Channel) {
editing.value = ch
Object.assign(form, {
name: ch.name, formats: [...(ch.formats?.length ? ch.formats : ['chat'])],
homepage: ch.homepage || '',
base_url: ch.base_url, api_key: '',
weight: ch.weight, priority: ch.priority, timeout_ms: ch.timeout_ms,
max_concurrency: ch.max_concurrency, enabled: ch.enabled,
@@ -163,7 +143,6 @@ onMounted(load)
<tr class="border-b border-edge text-left text-xs text-muted">
<th scope="col" class="px-4 py-2.5 font-medium">名称</th>
<th scope="col" class="px-4 py-2.5 font-medium">API 格式</th>
<th scope="col" class="px-4 py-2.5 font-medium">主页</th>
<th scope="col" class="px-4 py-2.5 font-medium">Base URL</th>
<th scope="col" class="px-4 py-2.5 font-medium">Key</th>
<th scope="col" class="px-4 py-2.5 font-medium">健康</th>
@@ -183,18 +162,6 @@ onMounted(load)
>{{ protocolName(f) }}</span>
</div>
</td>
<td class="px-4 py-2.5">
<div class="flex items-center gap-2">
<img
:src="`/api/v1/admin/channels/${ch.id}/favicon`"
:alt="ch.name"
class="size-5 shrink-0 rounded-sm"
loading="lazy"
@error="onFavError"
/>
<span class="text-xs text-muted">{{ hostOf(ch.homepage) }}</span>
</div>
</td>
<td class="px-4 py-2.5 font-mono text-xs text-muted">{{ ch.base_url }}</td>
<td class="px-4 py-2.5 font-mono text-xs text-muted">{{ ch.api_key_masked || '****' }}</td>
<td class="px-4 py-2.5">
@@ -215,7 +182,7 @@ onMounted(load)
</td>
</tr>
<tr v-if="channels.length === 0">
<td colspan="8" class="px-4 py-10 text-center text-sm text-muted">还没有渠道,点击「添加渠道」</td>
<td colspan="7" class="px-4 py-10 text-center text-sm text-muted">还没有渠道,点击「添加渠道」</td>
</tr>
</tbody>
</table>
@@ -245,8 +212,12 @@ onMounted(load)
</div>
<p class="mt-1.5 text-xs text-muted">客户端协议不在其中时,网关自动转换为其支持的格式</p>
</div>
<Input v-model="form.base_url" label="Base URL" placeholder="https://api.openai.com" />
<Input v-model="form.homepage" label="渠道主页" placeholder="https://openai.com(用于展示 favicon)" />
<Input
v-model="form.base_url"
label="Base URL(可选)"
placeholder="https://api.openai.com"
hint="留空按供应商默认;可填完整地址,如 https://api.openai.com/v1"
/>
<Input
v-model="form.api_key"
label="上游 API Key"