Newer
Older
navi-1 / webclient / src / components / ui / ContextBar.vue
<template>
  <button
    v-if="chat.maxContextTokens > 0"
    type="button"
    class="context-bar"
    :title="title"
    aria-haspopup="dialog"
    @click="emit('info')"
  >
    <span class="context-bar-pct" :class="fillClass">{{ pct }}%</span>
  </button>
</template>

<script setup>
import { computed } from 'vue'
import { useChatStore } from '@/stores/chat.js'
import { t } from '@/i18n/index.js'

const emit = defineEmits(['info'])

const chat = useChatStore()

const pct = computed(() => {
  if (!chat.maxContextTokens) return 0
  return Math.min(100, Math.round((chat.contextTokens / chat.maxContextTokens) * 100))
})

const fillClass = computed(() => {
  if (pct.value >= 80) return 'is-crit'
  if (pct.value >= 60) return 'is-warn'
  return ''
})

// The percentage is the one place that answers "why is it slow / why did it
// compress?" — so it doubles as the way into the session-info dialog.
const title = computed(
  () => `${compact(chat.contextTokens)} / ${compact(chat.maxContextTokens)} tokens · ${t('chat.sessionInfo')}`
)

function compact(n) {
  if (n >= 1000) return (n / 1000).toFixed(1).replace('.0', '') + 'k'
  return String(n)
}
</script>

<style scoped>
.context-bar {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  line-height: 1;
  cursor: pointer;
  border-radius: 4px;
}

.context-bar:hover .context-bar-pct {
  text-decoration: underline;
}

.context-bar:focus-visible {
  outline: 2px solid var(--gn-color-primary);
  outline-offset: 2px;
}

.context-bar-pct {
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--gn-color-success);
  white-space: nowrap;

  &.is-warn { color: var(--gn-color-warning); }
  &.is-crit { color: var(--gn-color-error); }
}
</style>