160 lines
8.1 KiB
Vue
160 lines
8.1 KiB
Vue
<script setup lang="ts">
|
|
import { useFloating, offset, flip, shift, autoUpdate, hide } from '@floating-ui/vue';
|
|
|
|
const tokenDetailsDropdownRef = ref<HTMLElement | null>(null);
|
|
const { triggerElement, activeGeneration: generation, close, isOpen } = useTokenDropdown();
|
|
|
|
const { floatingStyles, middlewareData } = useFloating(triggerElement, tokenDetailsDropdownRef, {
|
|
placement: 'bottom-end',
|
|
whileElementsMounted: autoUpdate,
|
|
middleware: [
|
|
offset(4),
|
|
flip(),
|
|
shift({ padding: 10 }),
|
|
hide(),
|
|
],
|
|
transform: false,
|
|
});
|
|
|
|
const outputPercentage = computed(() => {
|
|
const total = generation.value?.tokens?.output ?? 0;
|
|
const thinking = generation.value?.tokens?.thinking ?? 0;
|
|
if (total === 0) return 0;
|
|
return (thinking / total) * 100;
|
|
});
|
|
|
|
const inputStats = computed(() => {
|
|
const totalInput = generation.value?.tokens?.input ?? 0;
|
|
const cachedInput = generation.value?.tokens?.cache?.read ?? 0;
|
|
const uncachedInput = Math.max(0, totalInput - cachedInput);
|
|
const output = generation.value?.tokens?.output ?? 0;
|
|
|
|
const combinedTotal = totalInput + output;
|
|
|
|
if (combinedTotal === 0) return { uncached: 0, cached: 0, output: 0, rawUncached: 0, rawCached: 0, rawOutput: 0 };
|
|
|
|
return {
|
|
uncached: (uncachedInput / combinedTotal) * 100,
|
|
cached: (cachedInput / combinedTotal) * 100,
|
|
output: (output / combinedTotal) * 100,
|
|
rawUncached: uncachedInput,
|
|
rawCached: cachedInput,
|
|
rawOutput: output
|
|
};
|
|
});
|
|
|
|
const formatNumber = (num: number | null | undefined) => {
|
|
if (num === undefined || num === null) return '0';
|
|
return num.toLocaleString(undefined, { maximumFractionDigits: 1 });
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<Teleport to="body">
|
|
<Transition
|
|
enter-active-class="transition-[opacity,transform] duration-150 ease-[cubic-bezier(0.5,_1,_0.89,_1)]"
|
|
enter-from-class="opacity-0 scale-95" enter-to-class="opacity-100 scale-100"
|
|
leave-active-class="transition-[opacity,transform] duration-100 ease-[cubic-bezier(0.5,_1,_0.89,_1)]"
|
|
leave-from-class="opacity-100 scale-100" leave-to-class="opacity-0 scale-95">
|
|
|
|
<div v-if="isOpen && generation && generation.tokens" v-click-outside="close" ref="tokenDetailsDropdownRef"
|
|
:style="{
|
|
...floatingStyles,
|
|
visibility: middlewareData.hide?.referenceHidden
|
|
? 'hidden'
|
|
: 'visible',
|
|
}"
|
|
class="absolute z-50 flex flex-col min-w-[280px] bg-[var(--bg-surface)] rounded-md border border-[var(--color-border)] shadow-2xl text-[var(--text-secondary)] font-sans">
|
|
|
|
<!-- Output Details Section -->
|
|
<div class="px-3 pt-3 pb-4">
|
|
<div class="text-[12px] mb-2 px-0.5 font-medium">Output Details</div>
|
|
|
|
<div class="relative w-full h-[5px] mb-4 rounded-full overflow-hidden flex">
|
|
<div class="h-full bg-pink-500" :style="{ width: `${outputPercentage}%` }"></div>
|
|
<div class="h-full bg-emerald-400" :style="{ width: `${100 - outputPercentage}%` }"></div>
|
|
</div>
|
|
|
|
<div class="space-y-1.5 px-0.5">
|
|
<div v-if="generation.tokens.thinking ?? 0 > 0" class="flex items-center justify-between">
|
|
<div class="flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-pink-500 rounded-full"></span>
|
|
<span class="text-[14px]">Deep Thinking</span>
|
|
</div>
|
|
<span class="text-[14px] font-medium text-[var(--text-primary)]">
|
|
{{ formatNumber(generation.tokens.thinking ?? 0) }}
|
|
</span>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-emerald-400 rounded-full"></span>
|
|
<span class="text-[14px]">Text Output</span>
|
|
</div>
|
|
<span class="text-[14px] font-medium text-[var(--text-primary)]">
|
|
{{ formatNumber((generation.tokens.output ?? 0) - (generation.tokens.thinking ?? 0)) }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Input/Output Progress Bar -->
|
|
<div class="px-3 pb-4">
|
|
<div class="relative w-full h-[5px] mb-4 bg-[var(--text-dim)] rounded-full overflow-hidden flex">
|
|
<div class="h-full bg-[var(--text-dim)]" :style="{ width: `${inputStats.uncached}%` }"></div>
|
|
<div class="h-full bg-orange-400" :style="{ width: `${inputStats.cached}%` }"></div>
|
|
<div class="h-full bg-lime-300" :style="{ width: `${inputStats.output}%` }"></div>
|
|
</div>
|
|
|
|
<div class="space-y-1.5 px-0.5">
|
|
<div v-if="inputStats.rawUncached > 0" class="flex items-center justify-between">
|
|
<div class="flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-[var(--text-dim)] rounded-full"></span>
|
|
<span class="text-[14px]">Uncached Input</span>
|
|
</div>
|
|
<span class="text-[14px] font-medium text-[var(--text-primary)]">
|
|
{{ formatNumber(inputStats.rawUncached) }}
|
|
</span>
|
|
</div>
|
|
<div v-if="inputStats.cached > 0" class="flex items-center justify-between">
|
|
<div class="flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
<span class="text-[14px]">Cached Input</span>
|
|
</div>
|
|
<span class="text-[14px] font-medium text-[var(--text-primary)]">
|
|
{{ formatNumber(inputStats.rawCached) }}
|
|
</span>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-lime-300 rounded-full"></span>
|
|
<span class="text-[14px]">Output</span>
|
|
</div>
|
|
<span class="text-[14px] font-medium text-[var(--text-primary)]">
|
|
{{ formatNumber(inputStats.rawOutput) }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer Stats -->
|
|
<div class="flex border-t border-[var(--color-border)] bg-[var(--bg-base)] rounded-b-md px-1 py-1">
|
|
<div class="flex-1 py-1.5 text-center border-r border-[var(--color-border)]">
|
|
<div class="text-[9px] font-bold text-[var(--text-tertiary)] uppercase tracking-wider mb-0.5">
|
|
TTFT</div>
|
|
<div class="text-[13px] font-semibold text-[var(--text-primary)]">
|
|
{{ formatNumber(generation.tokens.ttft) }}ms
|
|
</div>
|
|
</div>
|
|
<div class="flex-1 py-1.5 text-center">
|
|
<div class="text-[9px] font-bold text-[var(--text-tertiary)] uppercase tracking-wider mb-0.5">
|
|
Speed</div>
|
|
<div class="text-[13px] font-semibold text-[var(--text-primary)]">
|
|
{{ formatNumber(generation.tokens.tps) }} tps
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</Teleport>
|
|
</template>
|