fix(grok): gateway media/voice routing, models, and status UI polish

Align gateway Grok media/voice paths and model lists, harden upstream failure
and quota handling, clear non-Grok video generation config migration, and polish
temp-unsched/status indicators with model whitelist updates.
This commit is contained in:
IanShaw027
2026-08-08 01:07:27 +08:00
parent 2526a04226
commit 165b072908
31 changed files with 744 additions and 153 deletions
@@ -14,15 +14,19 @@
<!-- Main Status Badge (shown when not rate limited/overloaded) -->
<template v-else>
<button
v-if="isTempUnschedulable"
type="button"
:class="['badge text-xs', statusClass, 'cursor-pointer']"
:title="t('admin.accounts.status.viewTempUnschedDetails')"
@click="handleTempUnschedClick"
>
{{ statusText }}
</button>
<div v-if="isTempUnschedulable" class="flex flex-col items-center gap-1">
<button
type="button"
:class="['badge text-xs', statusClass, 'cursor-pointer']"
:title="t('admin.accounts.status.viewTempUnschedDetails')"
@click="handleTempUnschedClick"
>
{{ statusText }}
</button>
<span class="max-w-[180px] text-center text-[11px] leading-4 text-gray-500 dark:text-gray-400">
{{ tempUnschedRecoveryText }}
</span>
</div>
<span v-else :class="['badge text-xs', statusClass]">
{{ statusText }}
</span>
@@ -302,6 +306,13 @@ const overloadCountdown = computed(() => {
return formatCountdownWithSuffix(props.account.overload_until)
})
const tempUnschedRecoveryText = computed(() => {
if (!isTempUnschedulable.value || !props.account.temp_unschedulable_until) return ''
return t('admin.accounts.status.tempUnschedulableUntil', {
time: formatDateTime(props.account.temp_unschedulable_until)
})
})
// Computed: status badge class
const statusClass = computed(() => {
if (hasError.value) {
@@ -101,6 +101,14 @@
{{ state?.error_message || '-' }}
</div>
</div>
<div
v-if="hasThresholdEvidence"
class="rounded-lg border border-blue-200 bg-blue-50 p-3 text-sm text-blue-800 dark:border-blue-500/30 dark:bg-blue-500/10 dark:text-blue-300"
data-testid="temp-unsched-trigger-evidence"
>
{{ triggerEvidenceText }}
</div>
</div>
</div>
@@ -176,10 +184,28 @@ const isActive = computed(() => {
})
const ruleIndexDisplay = computed(() => {
if (!state.value) return '-'
if (!state.value || !state.value.matched_keyword || state.value.rule_index < 0) return '-'
return state.value.rule_index + 1
})
const hasThresholdEvidence = computed(() => (state.value?.trigger_count || 0) > 1)
const triggerEvidenceText = computed(() => {
const count = state.value?.trigger_count || 0
const threshold = state.value?.trigger_threshold || 0
const minutes = state.value?.trigger_window_minutes || 0
if (threshold > 0 && minutes > 0) {
return t('admin.accounts.tempUnschedulable.multipleErrorTrigger', { count, threshold, minutes })
}
if (threshold > 0) {
return t('admin.accounts.tempUnschedulable.multipleErrorTriggerNoWindow', { count, threshold })
}
if (minutes > 0) {
return t('admin.accounts.tempUnschedulable.multipleErrorCountInWindow', { count, minutes })
}
return t('admin.accounts.tempUnschedulable.multipleErrorCount', { count })
})
const triggeredAtText = computed(() => {
if (!state.value?.triggered_at_unix) return '-'
return formatDateTime(new Date(state.value.triggered_at_unix * 1000))
@@ -49,6 +49,7 @@ describe('useModelWhitelist', () => {
expect(models).toContain('grok-4.5')
expect(models).toContain('grok-4.5-latest')
expect(models).toContain('grok-build-latest')
expect(models).toContain('grok-imagine-video-1.5-preview')
})
it('combined 模式支持 Grok 4.5 官方别名映射', () => {
@@ -74,7 +75,7 @@ describe('useModelWhitelist', () => {
const models = getModelsByPlatform('grok')
expect(models).toContain('grok-composer-2.5-fast')
expect(models).toContain('grok-composer')
expect(models).not.toContain('grok-composer')
expect(models).toContain('composer-2.5')
})
@@ -143,20 +143,20 @@ const xaiModels = [
'grok-4.20-0309-reasoning',
'grok-4.20-0309-non-reasoning',
'grok-4.20-multi-agent-0309',
'grok',
'grok-4.20-multi-agent',
'grok-4.20-multi-agent-latest',
'grok-4.3-latest',
'grok-latest',
'grok-4.5-latest',
'grok-build',
'grok-build-latest',
'grok-composer',
'composer-2.5',
'grok-4.20-reasoning',
'grok-4.20-non-reasoning',
'grok-imagine',
'grok-imagine-image-quality',
'grok-imagine-image',
'grok-imagine-edit',
'grok-imagine-video',
'grok-imagine-video-1.5-preview',
'grok-imagine-video-1.5'
]
@@ -314,7 +314,7 @@ const grokPresetMappings = [
{ label: '4.20 Reasoning', from: 'grok-4.20-reasoning', to: 'grok-4.20-0309-reasoning', color: 'bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400' },
{ label: '4.20 Non Reasoning', from: 'grok-4.20-non-reasoning', to: 'grok-4.20-0309-non-reasoning', color: 'bg-violet-100 text-violet-700 hover:bg-violet-200 dark:bg-violet-900/30 dark:text-violet-400' },
{ label: 'Imagine Image', from: 'grok-imagine', to: 'grok-imagine-image-quality', color: 'bg-sky-100 text-sky-700 hover:bg-sky-200 dark:bg-sky-900/30 dark:text-sky-400' },
{ label: 'Imagine Edit', from: 'grok-imagine-edit', to: 'grok-imagine-edit', color: 'bg-rose-100 text-rose-700 hover:bg-rose-200 dark:bg-rose-900/30 dark:text-rose-400' },
{ label: 'Imagine Edit', from: 'grok-imagine-edit', to: 'grok-imagine-image-quality', color: 'bg-rose-100 text-rose-700 hover:bg-rose-200 dark:bg-rose-900/30 dark:text-rose-400' },
{ label: 'Imagine Video', from: 'grok-imagine-video-1.5', to: 'grok-imagine-video-1.5', color: 'bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-400' }
]