Add context tracking

This commit is contained in:
dwrz
2026-06-27 12:35:11 +00:00
parent f2edd39160
commit ec9e0a80f8
5 changed files with 110 additions and 6 deletions

View File

@@ -69,6 +69,10 @@ class Odidere {
this.leafId = null;
this.rootId = null;
// Context tracking
this.modelContextSizes = new Map(); // model -> context_size
this.totalTokens = 0;
// TTS state
this.ttsURL = document.querySelector('meta[name="tts-url"]')?.content || '';
this.ttsDefaultVoice =
@@ -99,6 +103,7 @@ class Odidere {
this.$textInput = document.getElementById('text-input');
this.$voice = document.getElementById('voice');
this.$mute = document.getElementById('mute');
this.$context = document.getElementById('context');
this.$transcribe = document.getElementById('transcribe');
// Settings modal
@@ -167,6 +172,8 @@ class Odidere {
this.#clearAttachments();
this.$textInput.value = '';
this.$textInput.style.height = 'auto';
this.totalTokens = 0;
this.#updateContext();
this.#updateSendButtonState();
}
@@ -529,6 +536,11 @@ class Odidere {
if ($el) $el.remove();
}
// Token count is no longer accurate after deletion;
// it will be refreshed on the next LLM response.
this.totalTokens = 0;
this.#updateContext();
this.#saveToStorage();
this.#updateSendButtonState();
}
@@ -1627,6 +1639,12 @@ class Odidere {
if (eventType === StreamEventDone) {
if (message.role !== 'assistant') continue;
// Track cumulative token usage.
if (event.usage?.total_tokens) {
this.totalTokens = event.usage.total_tokens;
this.#updateContext();
}
const meta = streamingMeta(event.usage, event.timings);
let finalMessage = message;
let $streaming = null;
@@ -1946,6 +1964,7 @@ class Odidere {
*/
#populateModels(providers, defaultProvider, defaultModel) {
this.$model.innerHTML = '';
this.modelContextSizes.clear();
// Sort provider names.
const sortedProviders = Object.keys(providers).sort();
@@ -1963,6 +1982,9 @@ class Odidere {
$opt.value = m.model;
$opt.dataset.provider = provider;
$opt.dataset.model = m.model;
if (m.context_size) {
this.modelContextSizes.set(m.model, m.context_size);
}
switch (m.status) {
case 'available':
@@ -2040,6 +2062,51 @@ class Odidere {
}
}
/**
* #updateContext updates the context usage indicator in the toolbar.
* Shows percentage if context_size is known, token count if only
* usage data is available, or hides the indicator otherwise.
*/
#updateContext() {
if (!this.$context) return;
const model = this.$model?.value;
const contextSize = model ? this.modelContextSizes.get(model) : null;
// Hide if no data.
if (this.totalTokens === 0) {
this.$context.hidden = true;
return;
}
this.$context.hidden = false;
// Remove severity classes.
this.$context.classList.remove(
'footer__toolbar-context--warning',
'footer__toolbar-context--critical',
);
const tokenLabel =
this.totalTokens >= 1000
? `${(this.totalTokens / 1000).toFixed(1)}k tokens`
: `${this.totalTokens} tokens`;
if (contextSize && contextSize > 0) {
const pct = Math.round((this.totalTokens / contextSize) * 100);
this.$context.textContent = `${pct}%`;
this.$context.title = tokenLabel;
if (pct >= 90) {
this.$context.classList.add('footer__toolbar-context--critical');
} else if (pct >= 70) {
this.$context.classList.add('footer__toolbar-context--warning');
}
} else {
// No context_size known; show token count.
this.$context.textContent = tokenLabel;
this.$context.title = '';
}
}
/**
* #populateVoiceSelect populates voice selector grouped by language.
* Voice IDs follow the pattern: {lang}{gender}_{name} (e.g., "af_bella").