渠道: 支持手工录入模型(无 /models) + 模型映射自定义名称
- AdminChannelAddModel 改为 upstream_model + custom_name: 无 /models 接口的渠道直接填上游模型名, 可选自定义名作为客户端调用名, 全局模型不存在时自动创建 - "支持的模型"弹窗: 手工添加 + 建议下拉(自建,紧贴输入框)/水平排列/交换位置 - 网关仍按绑定映射改写请求 model 字段 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { http, errMsg } from '@/api/client'
|
||||
import { useToastStore } from '@/stores/toast'
|
||||
import { PROTOCOL_OPTIONS, protocolShort } from '@/lib/protocol'
|
||||
@@ -127,13 +127,24 @@ async function importModels(ch: Channel) {
|
||||
const modelsOpen = ref(false)
|
||||
const mappingChan = ref<Channel | null>(null)
|
||||
const mappings = ref<ChannelModelMapping[]>([])
|
||||
const availableModels = ref<string[]>([])
|
||||
const mappingForm = reactive({ model_name: '', upstream_model: '', weight: 1 })
|
||||
const availableModels = ref<string[]>([]) // 已存在模型名,供上游模型输入联想
|
||||
const mappingForm = reactive({ upstream_model: '', custom_name: '', weight: 1 })
|
||||
const showModelSuggestions = ref(false)
|
||||
const filteredModels = computed(() => {
|
||||
const q = mappingForm.upstream_model.trim().toLowerCase()
|
||||
if (!q) return availableModels.value
|
||||
return availableModels.value.filter((m) => m.toLowerCase().includes(q))
|
||||
})
|
||||
|
||||
function pickSuggestion(m: string) {
|
||||
mappingForm.upstream_model = m
|
||||
showModelSuggestions.value = false
|
||||
}
|
||||
|
||||
async function openModels(ch: Channel) {
|
||||
mappingChan.value = ch
|
||||
mappingForm.model_name = ''
|
||||
mappingForm.upstream_model = ''
|
||||
mappingForm.custom_name = ''
|
||||
mappingForm.weight = 1
|
||||
modelsOpen.value = true
|
||||
await Promise.all([loadMappings(), loadAvailableModels()])
|
||||
@@ -159,15 +170,17 @@ async function loadAvailableModels() {
|
||||
}
|
||||
|
||||
async function addMapping() {
|
||||
if (!mappingChan.value || !mappingForm.model_name) return
|
||||
if (!mappingChan.value || !mappingForm.upstream_model) return
|
||||
try {
|
||||
await http.post(`/admin/channels/${mappingChan.value.id}/models`, {
|
||||
model_name: mappingForm.model_name,
|
||||
upstream_model: mappingForm.upstream_model,
|
||||
custom_name: mappingForm.custom_name,
|
||||
weight: Number(mappingForm.weight) || 1,
|
||||
})
|
||||
toast.ok('已绑定')
|
||||
toast.ok('已添加')
|
||||
mappingForm.upstream_model = ''
|
||||
mappingForm.custom_name = ''
|
||||
showModelSuggestions.value = false
|
||||
await loadMappings()
|
||||
} catch (e) {
|
||||
toast.err(errMsg(e))
|
||||
@@ -314,13 +327,15 @@ onMounted(load)
|
||||
</Modal>
|
||||
|
||||
<!-- 模型名称映射 -->
|
||||
<Modal :open="modelsOpen" :title="`模型映射 · ${mappingChan?.name}`" @close="modelsOpen = false">
|
||||
<div class="mb-3 text-xs text-muted">客户端调用「模型名」时,网关转发为右侧「上游名称」。</div>
|
||||
<Modal :open="modelsOpen" :title="`支持的模型 · ${mappingChan?.name}`" @close="modelsOpen = false">
|
||||
<div class="mb-3 text-xs text-muted">
|
||||
客户端调用「客户端名称」,网关转发为「上游模型」。无 /models 接口的渠道可手工添加。
|
||||
</div>
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-edge text-left text-xs text-muted">
|
||||
<th scope="col" class="px-3 py-2 font-medium">模型</th>
|
||||
<th scope="col" class="px-3 py-2 font-medium">上游名称</th>
|
||||
<th scope="col" class="px-3 py-2 font-medium">客户端名称</th>
|
||||
<th scope="col" class="px-3 py-2 font-medium">上游模型</th>
|
||||
<th scope="col" class="px-3 py-2 font-medium">权重</th>
|
||||
<th scope="col" class="px-3 py-2" />
|
||||
</tr>
|
||||
@@ -343,25 +358,42 @@ onMounted(load)
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="mappings.length === 0">
|
||||
<td colspan="4" class="px-3 py-6 text-center text-xs text-muted">尚未绑定模型,可在下方添加</td>
|
||||
<td colspan="4" class="px-3 py-6 text-center text-xs text-muted">尚未添加模型,可在下方手工填写</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="mt-3 flex items-center gap-2 border-t border-edge pt-3">
|
||||
<select
|
||||
v-model="mappingForm.model_name"
|
||||
class="h-9 flex-1 rounded-md border border-edge2 bg-surface px-2 text-xs text-ink outline-none focus:border-accent"
|
||||
>
|
||||
<option value="" disabled>选择全局模型</option>
|
||||
<option v-for="m in availableModels" :key="m" :value="m">{{ m }}</option>
|
||||
</select>
|
||||
<input
|
||||
v-model="mappingForm.upstream_model"
|
||||
placeholder="上游名称(默认同名)"
|
||||
class="h-9 w-44 rounded-md border border-edge2 bg-surface px-2 font-mono text-xs outline-none focus:border-accent"
|
||||
v-model="mappingForm.custom_name"
|
||||
placeholder="自定义名称(可选)"
|
||||
class="h-9 w-36 shrink-0 rounded-md border border-edge2 bg-surface px-2 font-mono text-xs outline-none focus:border-accent"
|
||||
@keyup.enter="addMapping"
|
||||
/>
|
||||
<Button size="sm" @click="addMapping">绑定</Button>
|
||||
<div class="relative min-w-0 flex-1">
|
||||
<input
|
||||
v-model="mappingForm.upstream_model"
|
||||
placeholder="上游模型名"
|
||||
class="h-9 w-full rounded-md border border-edge2 bg-surface px-2 font-mono text-xs outline-none focus:border-accent"
|
||||
@focus="showModelSuggestions = true"
|
||||
@input="showModelSuggestions = true"
|
||||
@keydown.enter="addMapping"
|
||||
@keydown.esc="showModelSuggestions = false"
|
||||
/>
|
||||
<div
|
||||
v-if="showModelSuggestions && filteredModels.length"
|
||||
class="absolute left-0 right-0 top-full z-10 mt-1 max-h-40 overflow-y-auto rounded-md border border-edge bg-surface py-1 shadow-lg"
|
||||
>
|
||||
<button
|
||||
v-for="m in filteredModels"
|
||||
:key="m"
|
||||
class="block w-full truncate px-2.5 py-1.5 text-left font-mono text-xs text-ink hover:bg-surface2"
|
||||
@mousedown.prevent="pickSuggestion(m)"
|
||||
>
|
||||
{{ m }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Button size="sm" class="shrink-0" @click="addMapping">添加</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user