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
@@ -314,13 +314,19 @@
:result="modelTest.testResult.value"
mode="direct"
:selecting-model-name="testingModelName"
:endpoints="activeEndpoints"
:selected-endpoint="selectedTestEndpoint"
:testing="modelTest.testing.value"
:trace="modelTest.testTrace.value"
:request-id="modelTest.requestId.value"
:message-draft="testMessageDraft"
:request-body-draft="testRequestBodyDraft"
:request-body-error="testRequestBodyError"
:start-disabled="!selectedTestEndpoint || !!testRequestBodyError"
@close="handleTestDialogClose"
@back="handleTestDialogBack"
@select-endpoint="handleSelectTestEndpoint"
@start="handleStartMappingTest"
@update:message-draft="testMessageDraft = $event"
@update:request-body-draft="testRequestBodyDraft = $event"
/>
</template>
@@ -338,6 +344,7 @@ import ModelTestDialog from './ModelTestDialog.vue'
import { useToast } from '@/composables/useToast'
import {
type Model,
type ProviderEndpoint,
type ProviderModelAlias,
type ProviderMappingPreviewResponse,
} from '@/api/endpoints'
@@ -345,6 +352,10 @@ import { type EndpointAPIKey } from '@/api/endpoints/keys'
import { updateModel } from '@/api/endpoints/models'
import { parseApiError } from '@/utils/errorParser'
import type { ProviderWithEndpointsSummary } from '@/api/endpoints'
import {
buildDefaultModelTestRequestBody,
parseModelTestRequestBodyDraft,
} from './model-test-request'
interface MappingItem {
name: string
@@ -372,6 +383,7 @@ interface CombinedMapping {
const props = defineProps<{
provider: ProviderWithEndpointsSummary
endpoints?: ProviderEndpoint[]
providerKeys?: EndpointAPIKey[]
models?: Model[]
mappingPreview?: ProviderMappingPreviewResponse | null
@@ -396,7 +408,11 @@ const testingMapping = ref<string | null>(null)
const pendingMappingKey = ref<string | null>(null)
const testingModelName = ref<string | null>(null)
const preselectedModelId = ref<string | null>(null)
const testMessageDraft = ref('')
const selectedTestEndpoint = ref<ProviderEndpoint | null>(null)
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)
// 使用 props 传入的数据
const models = computed(() => props.models ?? [])
@@ -634,31 +650,60 @@ function handleTestDialogClose() {
pendingMappingKey.value = null
testingModelName.value = null
testingMapping.value = null
selectedTestEndpoint.value = null
testRequestBodyDraft.value = ''
}
function handleTestDialogBack() {
if (modelTest.testing.value) return
modelTest.testResult.value = null
modelTest.stopPolling()
}
function handleSelectTestEndpoint(endpointId: string) {
const endpoint = activeEndpoints.value.find(item => item.id === endpointId)
if (!endpoint) return
selectedTestEndpoint.value = endpoint
}
// 测试映射(直连测试,带故障转移和实时进度)
function runMappingTest(testingKey: string, modelName: string) {
if (activeEndpoints.value.length === 0) {
showError('暂无可用于测试的活跃端点')
return
}
pendingMappingKey.value = testingKey
modelTest.testResult.value = null
modelTest.dialogOpen.value = true
testingMapping.value = null
testingModelName.value = modelName
selectedTestEndpoint.value = activeEndpoints.value[0] ?? null
testRequestBodyDraft.value = buildDefaultModelTestRequestBody(modelName)
}
async function handleStartMappingTest() {
if (modelTest.testing.value || !testingModelName.value || !pendingMappingKey.value) return
if (modelTest.testing.value || !testingModelName.value) return
const endpoint = selectedTestEndpoint.value || activeEndpoints.value[0]
if (!endpoint) {
showError('请选择要测试的端点')
return
}
const currentMappingKey = pendingMappingKey.value
testingMapping.value = currentMappingKey
const { value: requestBody, error } = parsedTestRequestBody.value
if (!requestBody || error) {
showError(`测试请求体无效: ${error || '无效 JSON'}`)
return
}
const currentMappingKey = pendingMappingKey.value || testingModelName.value
testingMapping.value = pendingMappingKey.value ? currentMappingKey : null
await modelTest.startTest({
mode: 'direct',
modelName: testingModelName.value,
displayLabel: `映射 "${testingModelName.value}"`,
message: testMessageDraft.value,
onSuccess: () => {
pendingMappingKey.value = null
testingModelName.value = null
},
displayLabel: `[${endpoint.api_format}] 映射 "${testingModelName.value}"`,
apiFormat: endpoint.api_format,
endpointId: endpoint.id,
requestBody,
})
if (pendingMappingKey.value === currentMappingKey) {
pendingMappingKey.value = null