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))