Add context tracking
This commit is contained in:
@@ -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").
|
||||
|
||||
Reference in New Issue
Block a user