修复前端调试与基础交互问题

This commit is contained in:
MMEXA
2026-07-04 05:24:40 +08:00
parent b86d4e1f0c
commit 242081433e
33 changed files with 525 additions and 320 deletions
@@ -1,5 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { createApp, defineComponent, h, nextTick, type App } from 'vue'
import { createApp, defineComponent, nextTick, type App } from 'vue'
import { createMemoryHistory, createRouter } from 'vue-router'
import LoginDialog from '../LoginDialog.vue'
@@ -917,11 +917,6 @@ async function handleSubmit() {
}
form.value.supported_capabilities = caps.size > 0 ? [...caps] : []
// 清理空的 config
const cleanConfig = form.value.config && Object.keys(form.value.config).length > 0
? form.value.config
: undefined
submitting.value = true
try {
if (isEditMode.value && props.model) {
@@ -216,8 +216,8 @@
<Button
v-for="action in desktopPostProxyActions"
:key="action.key"
v-show="hasSelectedProvider"
:key="action.key"
variant="ghost"
size="icon"
class="h-8 w-8"
@@ -133,7 +133,10 @@ export function buildPoolProxyDistributionPlan(
}
const assignments = nodeIds.map((nodeId) => {
const assignment = mutableAssignments.get(nodeId)!
const assignment = mutableAssignments.get(nodeId)
if (!assignment) {
throw new Error(`Missing proxy distribution assignment for node ${nodeId}`)
}
const nodeKeys = [...assignment.retainedKeys, ...assignment.assignedKeys]
const changedKeys = nodeKeys.filter(key => getKeyProxyNodeId(key) !== nodeId)
return {
@@ -54,8 +54,12 @@
<div class="flex items-start gap-2">
<AlertTriangle class="mt-0.5 h-4 w-4 flex-shrink-0" />
<div class="min-w-0">
<p class="font-medium">关联健康加载失败</p>
<p class="mt-1 text-xs">{{ errorMessage }}</p>
<p class="font-medium">
关联健康加载失败
</p>
<p class="mt-1 text-xs">
{{ errorMessage }}
</p>
</div>
</div>
<Button
@@ -56,6 +56,11 @@ const props = withDefaults(defineProps<{
entityLabel?: string
entityName?: string | null
}>(), {
timeline: null,
timelineDetails: null,
timeRangeStart: null,
timeRangeEnd: null,
generatedAt: null,
lookbackHours: 6,
fallbackSegments: 60,
entityLabel: '',
@@ -1,5 +1,8 @@
<template>
<Card variant="default" class="overflow-hidden">
<Card
variant="default"
class="overflow-hidden"
>
<HealthMonitorHeader
v-model:lookback-hours="lookbackHours"
:title="title"
@@ -23,10 +26,15 @@
>
<Bot class="w-12 h-12 mb-3 opacity-30" />
<p>暂无模型健康监控数据</p>
<p class="text-xs mt-1">模型尚未产生请求记录</p>
<p class="text-xs mt-1">
模型尚未产生请求记录
</p>
</div>
<div v-else class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
<div
v-else
class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4"
>
<div
v-for="monitor in monitors"
:key="monitor.model"
@@ -46,7 +54,10 @@
{{ monitor.display_name || monitor.model }}
</h4>
</div>
<Badge :variant="getHealthBadgeVariant(monitor)" class="shrink-0">
<Badge
:variant="getHealthBadgeVariant(monitor)"
class="shrink-0"
>
{{ getHealthLabel(monitor) }}
</Badge>
</div>
@@ -597,9 +597,9 @@
: 'text-muted-foreground hover:text-foreground'"
:disabled="importing"
@click="setWindsurfImportMethod(method.key)"
>
{{ legacyT(method.label) }}
</button>
>
{{ legacyT(method.label) }}
</button>
</div>
<div
@@ -135,8 +135,6 @@ import { formatBillingType } from '@/utils/format'
import type { BalanceExtraItem } from '@/features/providers/auth-templates'
import { useI18n } from '@/i18n'
const { legacyT } = useI18n()
defineProps<{
provider: ProviderWithEndpointsSummary
isBalanceLoading: (providerId: string) => boolean
@@ -150,4 +148,6 @@ defineProps<{
formatResetCountdown: (resetsAt: number) => string
getQuotaUsedColorClass: (provider: ProviderWithEndpointsSummary) => string
}>()
const { legacyT } = useI18n()
</script>
@@ -948,7 +948,6 @@ import {
canExportOAuthCredential,
canRefreshOAuthCredential,
isOAuthManagedCredential,
isServiceAccountCredential,
getProviderMaskedSecretLabel,
shouldShowOAuthRefreshControl,
} from '@/utils/providerKeyAuth'
@@ -1302,14 +1301,6 @@ async function toggleFormatConversion() {
}
}
// Provider 级别代理配置
function handleProviderProxyPopoverToggle(open: boolean) {
providerProxyPopoverOpen.value = open
if (open) {
proxyNodesStore.ensureLoaded()
}
}
function getProviderProxyNodeName(): string {
const nodeId = provider.value?.proxy?.node_id
if (!nodeId) return legacyT('未知节点')
@@ -124,7 +124,7 @@ import {
summarizeHealthMonitorItems
} from './health-monitor-utils'
const props = withDefaults(defineProps<{
withDefaults(defineProps<{
title?: string
}>(), {
title: '提供商健康监控'
@@ -1,4 +1,3 @@
/* eslint-disable vue/one-component-per-file, vue/require-default-prop */
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { createApp, nextTick, type App } from 'vue'
import OAuthAccountDialog from '@/features/providers/components/OAuthAccountDialog.vue'
@@ -1496,7 +1496,7 @@ const formatConversionPair = (source: string, target: string): string =>
`${formatApiFormat(source.trim())} → ${formatApiFormat(target.trim())}`
const extractFieldDetail = (message: string): string => {
const fieldMatch = message.match(/field\s+([^;=]+?)\s*=\s*(\"(?:\\.|[^"\\])*\"|[^;]+)/i)
const fieldMatch = message.match(/field\s+([^;=]+?)\s*=\s*("(?:\\.|[^"\\])*"|[^;]+)/i)
const unsupportedFieldMatch = message.match(/field\s+([^;]+?)\s+is unsupported/i)
if (fieldMatch?.[1]) {
return `字段 ${normalizeDiagnosticFieldPath(fieldMatch[1])} = ${fieldMatch[2].trim()}`
@@ -876,7 +876,7 @@ import Skeleton from '@/components/ui/skeleton.vue'
import Tabs from '@/components/ui/tabs.vue'
import TabsContent from '@/components/ui/tabs-content.vue'
import { AlertTriangle, Check, Columns2, RefreshCw, X, Monitor, Server, MessageSquareText, Code2, Terminal, Play } from 'lucide-vue-next'
import { dashboardApi, type RequestDetail, type RequestErrorDomain } from '@/api/dashboard'
import { dashboardApi, type RequestDetail } from '@/api/dashboard'
import type { ImageProgress, RequestTrace } from '@/api/requestTrace'
import { formatApiFormat } from '@/api/endpoints/types/api-format'
import {
@@ -889,7 +889,6 @@ import { log } from '@/utils/logger'
import { getEffectiveInputTokens } from '../token-normalization'
import {
formatDurationMs,
formatOutputRate,
formatOutputRateValue,
getDisplayOutputRate,
} from '../performance'
@@ -1006,15 +1005,6 @@ type JsonRecord = Record<string, unknown>
const METADATA_BYTE_FIELD_PATTERN = /(^bytes$|_bytes$|bytes$)/i
type NormalizedErrorDomain = {
source?: string | null
status_code?: number | null
type?: string | null
message: string
code?: string | number | null
category?: string | null
}
function asRecord(value: unknown): JsonRecord | null {
if (!value || typeof value !== 'object' || Array.isArray(value)) return null
return value as JsonRecord
@@ -1043,28 +1033,6 @@ function formatMetadataDisplayValue(value: unknown, key = ''): unknown {
return value
}
function normalizeErrorDomain(domain: RequestErrorDomain | null | undefined): NormalizedErrorDomain | null {
if (!domain || typeof domain !== 'object') return null
const message = typeof domain.message === 'string' ? domain.message.trim() : ''
if (!message) return null
return {
source: domain.source ?? null,
status_code: domain.status_code ?? null,
type: domain.type ?? null,
message,
code: domain.code ?? null,
category: domain.category ?? null,
}
}
function formatErrorDomainMeta(domain: NormalizedErrorDomain): string {
const parts: string[] = []
if (domain.status_code != null) parts.push(`HTTP ${domain.status_code}`)
if (domain.type) parts.push(domain.type)
if (domain.source) parts.push(`source=${domain.source}`)
return parts.join(' · ')
}
function mapTraceFinalStatusToRequestStatus(
status?: RequestTrace['final_status'] | null
): RequestStateStatus | undefined {
@@ -674,16 +674,18 @@ export class OpenAIParser implements ApiFormatParser {
: typeof chunk.delta === 'string'
? chunk.delta
: null
if (key && toolCalls.has(key) && args != null) {
toolCalls.get(key)!.args = [args]
const toolCall = key ? toolCalls.get(key) : undefined
if (toolCall && args != null) {
toolCall.args = [args]
}
continue
}
if (eventType === 'response.custom_tool_call_input.done') {
const key = resolveToolKey(chunk)
if (key && toolCalls.has(key) && typeof chunk.input === 'string') {
toolCalls.get(key)!.args = [chunk.input]
const toolCall = key ? toolCalls.get(key) : undefined
if (toolCall && typeof chunk.input === 'string') {
toolCall.args = [chunk.input]
}
continue
}
@@ -13,9 +13,9 @@
</Badge>
<span
v-else
class="font-semibold tabular-nums"
:class="[
mobile ? 'text-base leading-none' : 'text-sm',
'font-semibold tabular-nums',
row.isNegativeBalance ? 'text-rose-600' : 'text-foreground',
]"
>