| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { useI18n } from 'vue-i18n' |
| import type { MonitorStatus, Provider } from '@/api/admin/channelMonitor' |
| import { |
| PROVIDER_OPENAI, |
| PROVIDER_ANTHROPIC, |
| PROVIDER_GEMINI, |
| STATUS_OPERATIONAL, |
| STATUS_DEGRADED, |
| STATUS_FAILED, |
| STATUS_ERROR, |
| } from '@/constants/channelMonitor' |
|
|
| const NEUTRAL_BADGE = 'bg-gray-100 text-gray-800 dark:bg-dark-700 dark:text-gray-300' |
|
|
| |
| const HSL_HUE_PER_PERCENT = 1.2 |
| const HSL_SATURATION = 72 |
| const HSL_LIGHTNESS = 42 |
|
|
| export interface AvailabilityRow { |
| primary_status: MonitorStatus | '' |
| availability_7d: number | null | undefined |
| } |
|
|
| export function useChannelMonitorFormat() { |
| const { t } = useI18n() |
|
|
| function statusLabel(s: MonitorStatus | ''): string { |
| if (!s) return t('monitorCommon.status.unknown') |
| return t(`monitorCommon.status.${s}`) |
| } |
|
|
| function statusBadgeClass(s: MonitorStatus | ''): string { |
| switch (s) { |
| case STATUS_OPERATIONAL: |
| return 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300' |
| case STATUS_DEGRADED: |
| return 'bg-amber-100 text-amber-700 dark:bg-amber-500/15 dark:text-amber-300' |
| case STATUS_FAILED: |
| return 'bg-red-100 text-red-700 dark:bg-red-500/15 dark:text-red-300' |
| case STATUS_ERROR: |
| default: |
| return NEUTRAL_BADGE |
| } |
| } |
|
|
| function providerLabel(p: Provider | string): string { |
| if (p === PROVIDER_OPENAI || p === PROVIDER_ANTHROPIC || p === PROVIDER_GEMINI) { |
| return t(`monitorCommon.providers.${p}`) |
| } |
| return p || '-' |
| } |
|
|
| function providerBadgeClass(p: Provider | string): string { |
| switch (p) { |
| case PROVIDER_OPENAI: |
| return 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300' |
| case PROVIDER_ANTHROPIC: |
| return 'bg-orange-100 text-orange-700 dark:bg-orange-500/15 dark:text-orange-300' |
| case PROVIDER_GEMINI: |
| return 'bg-sky-100 text-sky-700 dark:bg-sky-500/15 dark:text-sky-300' |
| default: |
| return NEUTRAL_BADGE |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| function providerPickerClass(p: Provider | string, active: boolean): string { |
| switch (p) { |
| case PROVIDER_OPENAI: |
| return active |
| ? 'border-emerald-500 bg-emerald-50 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300 dark:border-emerald-400' |
| : 'border-gray-200 bg-white text-gray-600 hover:border-emerald-300 hover:text-emerald-700 dark:border-dark-700 dark:bg-dark-800 dark:text-gray-400 dark:hover:border-emerald-500/50' |
| case PROVIDER_ANTHROPIC: |
| return active |
| ? 'border-orange-500 bg-orange-50 text-orange-700 dark:bg-orange-500/15 dark:text-orange-300 dark:border-orange-400' |
| : 'border-gray-200 bg-white text-gray-600 hover:border-orange-300 hover:text-orange-700 dark:border-dark-700 dark:bg-dark-800 dark:text-gray-400 dark:hover:border-orange-500/50' |
| case PROVIDER_GEMINI: |
| return active |
| ? 'border-sky-500 bg-sky-50 text-sky-700 dark:bg-sky-500/15 dark:text-sky-300 dark:border-sky-400' |
| : 'border-gray-200 bg-white text-gray-600 hover:border-sky-300 hover:text-sky-700 dark:border-dark-700 dark:bg-dark-800 dark:text-gray-400 dark:hover:border-sky-500/50' |
| default: |
| return active |
| ? 'border-gray-400 bg-gray-50 text-gray-700 dark:border-dark-500 dark:bg-dark-700 dark:text-gray-200' |
| : 'border-gray-200 bg-white text-gray-600 hover:border-gray-300 dark:border-dark-700 dark:bg-dark-800 dark:text-gray-400' |
| } |
| } |
|
|
| function formatLatency(ms: number | null | undefined): string { |
| if (ms == null) return t('monitorCommon.latencyEmpty') |
| return String(Math.round(ms)) |
| } |
|
|
| function formatPercent(v: number | null | undefined): string { |
| if (v == null || Number.isNaN(v)) return '-' |
| return `${v.toFixed(2)}%` |
| } |
|
|
| function formatAvailability(row: AvailabilityRow): string { |
| if (!row.primary_status) return '-' |
| return formatPercent(row.availability_7d) |
| } |
|
|
| function formatRelativeTime(iso: string | null | undefined): string { |
| if (!iso) return t('monitorCommon.latencyEmpty') |
| const ts = Date.parse(iso) |
| if (Number.isNaN(ts)) return t('monitorCommon.latencyEmpty') |
| const diffSec = Math.max(0, Math.floor((Date.now() - ts) / 1000)) |
| if (diffSec < 60) return t('monitorCommon.relativeSecondsAgo', { n: diffSec }) |
| const diffMin = Math.floor(diffSec / 60) |
| if (diffMin < 60) return t('monitorCommon.relativeMinutesAgo', { n: diffMin }) |
| const diffHour = Math.floor(diffMin / 60) |
| if (diffHour < 24) return t('monitorCommon.relativeHoursAgo', { n: diffHour }) |
| const diffDay = Math.floor(diffHour / 24) |
| return t('monitorCommon.relativeDaysAgo', { n: diffDay }) |
| } |
|
|
| return { |
| statusLabel, |
| statusBadgeClass, |
| providerLabel, |
| providerBadgeClass, |
| providerPickerClass, |
| formatLatency, |
| formatPercent, |
| formatAvailability, |
| formatRelativeTime, |
| } |
| } |
|
|
| |
| |
| |
| |
| export function hslForPct(pct: number | null | undefined): string | undefined { |
| if (pct === null || pct === undefined || Number.isNaN(pct)) return undefined |
| const clamped = Math.max(0, Math.min(100, pct)) |
| const hue = clamped * HSL_HUE_PER_PERCENT |
| return `hsl(${hue} ${HSL_SATURATION}% ${HSL_LIGHTNESS}%)` |
| } |
|
|
| |
| |
| |
| export function providerGradient(provider: string): string { |
| switch (provider) { |
| case PROVIDER_OPENAI: |
| return 'bg-gradient-to-br from-emerald-50 to-emerald-100 dark:from-emerald-500/10 dark:to-emerald-500/20' |
| case PROVIDER_ANTHROPIC: |
| return 'bg-gradient-to-br from-orange-50 to-amber-100 dark:from-orange-500/10 dark:to-amber-500/20' |
| case PROVIDER_GEMINI: |
| return 'bg-gradient-to-br from-sky-50 to-indigo-100 dark:from-sky-500/10 dark:to-indigo-500/20' |
| default: |
| return 'bg-gradient-to-br from-gray-100 to-gray-200 dark:from-dark-700 dark:to-dark-600' |
| } |
| } |
|
|