feat(provider): 重构模型测试对话框,加固 Vertex AI 传输层

模型测试:
- 将消息输入替换为完整 JSON 请求体编辑器,支持格式化和校验
- 新增端点选择面板,测试前可选择目标端点
- 新增调试检查器,可查看每次尝试的请求/响应头和体
- 结果视图改用 HorizontalRequestTimeline 组件展示请求追踪
- endpoint_checker 返回完整调试数据,通过 candidate extra_data 持久化

Vertex AI:
- 改进上下文检测逻辑,不再仅依赖 provider_type,支持从 base_url 推断
- Service Account 密钥现支持自动拉取模型(使用 auth_config 而非 api_key)
- 移除 Gemini Developer API 回退,API Key 仅走 Express 模式
- 端点表单为 Vertex AI 显示格式特定的默认路径模板
- 密钥格式校验仅在 auth_type/api_formats 变更时执行

其他:
- 禁用 ClaudeCode 提供商类型创建入口
- Dialog 组件新增 closeOnBackdrop 属性
This commit is contained in:
fawney19
2026-03-19 23:52:17 +08:00
parent e4ebd5cca1
commit 6984984c22
31 changed files with 1609 additions and 434 deletions

View File

@@ -221,13 +221,14 @@
:testing="modelTest.testing.value"
:trace="modelTest.testTrace.value"
:request-id="modelTest.requestId.value"
:show-endpoint-selector="activeEndpoints.length > 1"
:message-draft="testMessageDraft"
:request-body-draft="testRequestBodyDraft"
:request-body-error="testRequestBodyError"
:start-disabled="!selectedTestEndpoint || !!testRequestBodyError"
@close="handleTestDialogClose"
@back="handleTestDialogBack"
@start="handleStartPendingTest"
@select-endpoint="handleSelectTestEndpoint"
@update:message-draft="testMessageDraft = $event"
@update:request-body-draft="testRequestBodyDraft = $event"
/>
</template>
@@ -250,6 +251,10 @@ import { parseApiError } from '@/utils/errorParser'
import { formatApiFormat } from '@/api/endpoints/types/api-format'
import type { ProviderWithEndpointsSummary } from '@/api/endpoints'
import ModelTestDialog from './ModelTestDialog.vue'
import {
buildDefaultModelTestRequestBody,
parseModelTestRequestBodyDraft,
} from './model-test-request'
const props = defineProps<{
provider: ProviderWithEndpointsSummary
@@ -275,8 +280,10 @@ const localModels = ref<Model[]>([])
const togglingModelId = ref<string | null>(null)
const pendingTestModel = ref<Model | null>(null)
const selectedTestEndpoint = ref<ProviderEndpoint | null>(null)
const testMessageDraft = ref('')
const testRequestBodyDraft = ref('')
const activeEndpoints = computed(() => (props.endpoints ?? []).filter(endpoint => endpoint.is_active))
const parsedTestRequestBody = computed(() => parseModelTestRequestBodyDraft(testRequestBodyDraft.value))
const testRequestBodyError = computed(() => parsedTestRequestBody.value.error)
const models = computed(() => props.models ?? localModels.value)
// 按名称排序的模型列表
const sortedModels = computed(() => {
@@ -442,19 +449,38 @@ function handleTestDialogClose() {
modelTest.resetState()
pendingTestModel.value = null
selectedTestEndpoint.value = null
testRequestBodyDraft.value = ''
}
function handleTestDialogBack() {
if (modelTest.testing.value) return
modelTest.testResult.value = null
selectedTestEndpoint.value = null
modelTest.stopPolling()
}
async function handleSelectTestEndpoint(endpointId: string) {
if (!pendingTestModel.value) return
function handleSelectTestEndpoint(endpointId: string) {
const endpoint = activeEndpoints.value.find(item => item.id === endpointId)
if (!endpoint) return
selectedTestEndpoint.value = endpoint
}
async function handleStartPendingTest() {
if (modelTest.testing.value) return
if (!pendingTestModel.value) return
const endpoint = selectedTestEndpoint.value || activeEndpoints.value[0]
if (!endpoint) {
showError('请选择要测试的端点')
return
}
const { value: requestBody, error } = parsedTestRequestBody.value
if (!requestBody || error) {
showError(`测试请求体无效: ${error || '无效 JSON'}`)
return
}
selectedTestEndpoint.value = endpoint
const model = pendingTestModel.value
const modelName = model.global_model_name || model.provider_model_name
const endpointPrefix = `[${formatApiFormat(endpoint.api_format)}] `
@@ -464,28 +490,16 @@ async function handleSelectTestEndpoint(endpointId: string) {
displayLabel: `${endpointPrefix}${modelName}`,
apiFormat: endpoint.api_format,
endpointId: endpoint.id,
message: testMessageDraft.value,
requestBody,
concurrency: 5,
onSuccess: () => {
pendingTestModel.value = null
selectedTestEndpoint.value = null
},
onError: () => {
if (activeEndpoints.value.length > 1) {
selectedTestEndpoint.value = null
return true
}
},
})
}
async function handleStartPendingTest() {
if (modelTest.testing.value) return
const endpoint = activeEndpoints.value[0]
if (!endpoint) return
await handleSelectTestEndpoint(endpoint.id)
}
async function testModelConnection(model: Model) {
if (modelTest.testing.value) return
@@ -495,7 +509,10 @@ async function testModelConnection(model: Model) {
}
pendingTestModel.value = model
selectedTestEndpoint.value = null
selectedTestEndpoint.value = activeEndpoints.value[0] ?? null
testRequestBodyDraft.value = buildDefaultModelTestRequestBody(
model.global_model_name || model.provider_model_name,
)
modelTest.testResult.value = null
modelTest.dialogOpen.value = true
}