| <template> |
| <button |
| type="button" |
| class="group text-left p-5 rounded-2xl min-h-[280px] w-full bg-white/70 backdrop-blur-xl border border-gray-200/80 shadow-card dark:bg-dark-800/60 dark:border-dark-700/70 hover:-translate-y-1 hover:shadow-card-hover dark:hover:border-primary-500/30 hover:border-gray-300 transition-all duration-300 ease-out flex flex-col" |
| @click="emit('click')" |
| > |
| |
| <div class="flex items-start gap-3"> |
| <span |
| class="w-9 h-9 rounded-xl ring-1 ring-black/5 dark:ring-white/10 grid place-items-center flex-shrink-0" |
| :class="[providerGradient(item.provider), providerTintClass]" |
| > |
| <ProviderIcon :provider="item.provider" :size="20" /> |
| </span> |
| <div class="flex-1 min-w-0"> |
| <div class="text-base font-semibold truncate text-gray-900 dark:text-gray-100"> |
| {{ item.name }} |
| </div> |
| <div class="mt-0.5 flex items-center gap-1.5 min-w-0"> |
| <span |
| class="inline-flex items-center rounded-md px-1.5 py-0.5 text-[10px] font-medium flex-shrink-0" |
| :class="providerBadgeClass(item.provider)" |
| > |
| {{ providerLabel(item.provider) }} |
| </span> |
| <span class="font-mono text-xs truncate text-gray-500 dark:text-gray-400"> |
| {{ item.primary_model }} |
| </span> |
| <span |
| v-if="item.group_name" |
| class="inline-flex items-center rounded-md px-1.5 py-0.5 text-[10px] font-medium bg-gray-100 text-gray-600 dark:bg-dark-700 dark:text-gray-300 flex-shrink-0" |
| > |
| {{ item.group_name }} |
| </span> |
| </div> |
| </div> |
| <span |
| class="px-2.5 py-1 rounded-full text-xs font-semibold flex-shrink-0" |
| :class="statusBadgeClass(item.primary_status)" |
| > |
| {{ statusLabel(item.primary_status) }} |
| </span> |
| </div> |
| |
| |
| <MonitorMetricPair |
| primary-icon="bolt" |
| :primary-label="t('monitorCommon.dialogLatency')" |
| :primary-value="formatLatency(item.primary_latency_ms)" |
| primary-unit="ms" |
| secondary-icon="globe" |
| :secondary-label="t('monitorCommon.endpointPing')" |
| :secondary-value="formatLatency(item.primary_ping_latency_ms)" |
| secondary-unit="ms" |
| /> |
| |
| |
| <div class="mt-4 border-t border-gray-100 dark:border-dark-700/60"></div> |
| |
| |
| <MonitorAvailabilityRow |
| :window-label="availabilityLabel" |
| :value="availabilityValue" |
| :samples-label="extraModelsCountLabel" |
| /> |
| |
| |
| <MonitorTimeline |
| :buckets="item.timeline" |
| :countdown-seconds="countdownSeconds" |
| /> |
| </button> |
| </template> |
| |
| <script setup lang="ts"> |
| import { computed } from 'vue' |
| import { useI18n } from 'vue-i18n' |
| import type { UserMonitorView } from '@/api/channelMonitor' |
| import { |
| useChannelMonitorFormat, |
| providerGradient, |
| } from '@/composables/useChannelMonitorFormat' |
| import ProviderIcon from './ProviderIcon.vue' |
| import MonitorMetricPair from './MonitorMetricPair.vue' |
| import MonitorAvailabilityRow from './MonitorAvailabilityRow.vue' |
| import MonitorTimeline from './MonitorTimeline.vue' |
| |
| const PROVIDER_TINT: Record<string, string> = { |
| openai: 'text-emerald-600 dark:text-emerald-300', |
| anthropic: 'text-orange-600 dark:text-orange-300', |
| gemini: 'text-sky-600 dark:text-sky-300', |
| } |
| |
| const props = defineProps<{ |
| item: UserMonitorView |
| window: '7d' | '15d' | '30d' |
| availabilityValue: number | null |
| countdownSeconds: number |
| }>() |
| |
| const emit = defineEmits<{ |
| (e: 'click'): void |
| }>() |
| |
| const { t } = useI18n() |
| const { |
| statusLabel, |
| statusBadgeClass, |
| providerLabel, |
| providerBadgeClass, |
| formatLatency, |
| } = useChannelMonitorFormat() |
| |
| const providerTintClass = computed(() => |
| PROVIDER_TINT[props.item.provider] ?? 'text-gray-500 dark:text-gray-300' |
| ) |
| |
| const availabilityLabel = computed(() => { |
| const win = t(`channelStatus.windowTab.${props.window}`) |
| return `${t('monitorCommon.availabilityPrefix')} · ${win}` |
| }) |
| |
| const extraModelsCountLabel = computed(() => { |
| const count = props.item.extra_models?.length ?? 0 |
| if (count === 0) return undefined |
| return t('monitorCommon.extraModelsCount', { n: count }) |
| }) |
| </script> |
| |