mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-15 17:30:13 +08:00
feat: add manual model addition functionality and search capability in ProviderPage
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"title": "Service Provider Management",
|
||||
"subtitle": "Manage model service providers",
|
||||
"title": "Providers",
|
||||
"subtitle": "Manage model providers",
|
||||
"providers": {
|
||||
"title": "Service Providers",
|
||||
"settings": "Settings",
|
||||
@@ -121,6 +121,14 @@
|
||||
"deleteSuccess": "Model deleted successfully",
|
||||
"deleteError": "Failed to delete model",
|
||||
"testSuccess": "Model {id} test passed",
|
||||
"testError": "Model test failed"
|
||||
"testError": "Model test failed",
|
||||
"searchPlaceholder": "Search models or ID",
|
||||
"manualAddButton": "Custom Model",
|
||||
"manualDialogTitle": "Add Custom Model",
|
||||
"manualDialogModelLabel": "Model ID (e.g. gpt-4.1-mini)",
|
||||
"manualDialogPreviewLabel": "Display ID (auto generated)",
|
||||
"manualDialogPreviewHint": "Generated as sourceId/modelId",
|
||||
"manualModelRequired": "Please enter a model ID",
|
||||
"manualModelExists": "Model already exists"
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,14 @@
|
||||
"deleteSuccess": "模型删除成功",
|
||||
"deleteError": "模型删除失败",
|
||||
"testSuccess": "模型 {id} 测试通过",
|
||||
"testError": "模型测试失败"
|
||||
"testError": "模型测试失败",
|
||||
"searchPlaceholder": "搜索模型或 ID",
|
||||
"manualAddButton": "自定义模型",
|
||||
"manualDialogTitle": "添加自定义模型",
|
||||
"manualDialogModelLabel": "模型 ID(如 gpt-4.1-mini)",
|
||||
"manualDialogPreviewLabel": "显示 ID(自动生成)",
|
||||
"manualDialogPreviewHint": "生成规则:源ID/模型ID",
|
||||
"manualModelRequired": "请输入模型 ID",
|
||||
"manualModelExists": "该模型已存在"
|
||||
}
|
||||
}
|
||||
@@ -127,18 +127,25 @@
|
||||
<h3 class="text-h5 font-weight-bold mb-0">{{ tm('models.configured') }}</h3>
|
||||
<small style="color: grey;" v-if="availableModels.length">{{ tm('models.available') }} {{
|
||||
availableModels.length }}</small>
|
||||
<v-text-field v-model="modelSearch" density="compact" prepend-inner-icon="mdi-magnify"
|
||||
hide-details variant="solo-filled" flat class="ml-1" style="max-width: 240px;"
|
||||
:placeholder="tm('models.searchPlaceholder')" />
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="primary" prepend-icon="mdi-download" :loading="loadingModels"
|
||||
@click="fetchAvailableModels" variant="tonal" size="small">
|
||||
{{ isSourceModified ? tm('providerSources.saveAndFetchModels') :
|
||||
tm('providerSources.fetchModels') }}
|
||||
</v-btn>
|
||||
<v-btn color="primary" prepend-icon="mdi-pencil-plus" variant="text" size="small"
|
||||
class="ml-1" @click="openManualModelDialog">
|
||||
{{ tm('models.manualAddButton') }}
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<v-list density="compact" class="rounded-lg border"
|
||||
style="max-height: 520px; overflow-y: auto; font-family:system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;">
|
||||
<template v-if="mergedModelEntries.length > 0">
|
||||
<template v-for="entry in mergedModelEntries"
|
||||
<template v-if="filteredMergedModelEntries.length > 0">
|
||||
<template v-for="entry in filteredMergedModelEntries"
|
||||
:key="entry.type === 'configured' ? `provider-${entry.provider.id}` : `model-${entry.model}`">
|
||||
<v-list-item v-if="entry.type === 'configured'" class="provider-compact-item"
|
||||
@click="openProviderEdit(entry.provider)">
|
||||
@@ -273,6 +280,22 @@
|
||||
<AddNewProvider v-model:show="showAddProviderDialog" :metadata="metadata"
|
||||
@select-template="selectProviderTemplate" />
|
||||
|
||||
<!-- 手动添加模型对话框 -->
|
||||
<v-dialog v-model="showManualModelDialog" max-width="400">
|
||||
<v-card :title="tm('models.manualDialogTitle')">
|
||||
<v-card-text class="py-4">
|
||||
<v-text-field v-model="manualModelId" :label="tm('models.manualDialogModelLabel')" flat variant="solo-filled" autofocus clearable></v-text-field>
|
||||
<v-text-field :model-value="manualProviderId" flat variant="solo-filled" :label="tm('models.manualDialogPreviewLabel')" persistent-hint
|
||||
:hint="tm('models.manualDialogPreviewHint')"></v-text-field>
|
||||
</v-card-text>
|
||||
<v-card-actions class="pa-4">
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn variant="text" @click="showManualModelDialog = false">取消</v-btn>
|
||||
<v-btn color="primary" @click="confirmManualModel">添加</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 配置对话框 -->
|
||||
<v-dialog v-model="showProviderCfg" width="900" persistent>
|
||||
<v-card
|
||||
@@ -398,6 +421,9 @@ const showAgentRunnerDialog = ref(false)
|
||||
const sourceProviderPanels = ref(null)
|
||||
const showProviderEditDialog = ref(false)
|
||||
const providerEditData = ref(null)
|
||||
const showManualModelDialog = ref(false)
|
||||
const manualModelId = ref('')
|
||||
const modelSearch = ref('')
|
||||
|
||||
let suppressSourceWatch = false
|
||||
|
||||
@@ -547,6 +573,29 @@ const mergedModelEntries = computed(() => {
|
||||
return [...configuredEntries, ...availableEntries]
|
||||
})
|
||||
|
||||
const filteredMergedModelEntries = computed(() => {
|
||||
const term = modelSearch.value.trim().toLowerCase()
|
||||
if (!term) return mergedModelEntries.value
|
||||
|
||||
return mergedModelEntries.value.filter(entry => {
|
||||
if (entry.type === 'configured') {
|
||||
const id = entry.provider.id?.toLowerCase() || ''
|
||||
const model = entry.provider.model?.toLowerCase() || ''
|
||||
return id.includes(term) || model.includes(term)
|
||||
}
|
||||
|
||||
const model = entry.model?.toLowerCase() || ''
|
||||
return model.includes(term)
|
||||
})
|
||||
})
|
||||
|
||||
const manualProviderId = computed(() => {
|
||||
if (!selectedProviderSource.value) return ''
|
||||
const modelId = manualModelId.value.trim()
|
||||
if (!modelId) return ''
|
||||
return `${selectedProviderSource.value.id}/${modelId}`
|
||||
})
|
||||
|
||||
// 基础配置:只包含常用字段
|
||||
const basicSourceConfig = computed(() => {
|
||||
if (!editableProviderSource.value) return null
|
||||
@@ -681,6 +730,33 @@ function openProviderEdit(provider) {
|
||||
showProviderEditDialog.value = true
|
||||
}
|
||||
|
||||
function openManualModelDialog() {
|
||||
if (!selectedProviderSource.value) {
|
||||
showMessage(tm('providerSources.selectHint'), 'error')
|
||||
return
|
||||
}
|
||||
manualModelId.value = ''
|
||||
showManualModelDialog.value = true
|
||||
}
|
||||
|
||||
async function confirmManualModel() {
|
||||
const modelId = manualModelId.value.trim()
|
||||
if (!selectedProviderSource.value) {
|
||||
showMessage(tm('providerSources.selectHint'), 'error')
|
||||
return
|
||||
}
|
||||
if (!modelId) {
|
||||
showMessage(tm('models.manualModelRequired'), 'error')
|
||||
return
|
||||
}
|
||||
if (modelAlreadyConfigured(modelId)) {
|
||||
showMessage(tm('models.manualModelExists'), 'error')
|
||||
return
|
||||
}
|
||||
await addModelProvider(modelId)
|
||||
showManualModelDialog.value = false
|
||||
}
|
||||
|
||||
// ===== Methods =====
|
||||
function showMessage(message, color = 'success') {
|
||||
snackbar.value = { show: true, message, color }
|
||||
|
||||
Reference in New Issue
Block a user