Show total number of tokens by using tokenizer
This commit is contained in:
parent
3657a8a7ad
commit
fb2095e815
Binary file not shown.
|
|
@ -2,7 +2,7 @@
|
||||||
import { notebookStore } from '$lib/stores/notebook.svelte';
|
import { notebookStore } from '$lib/stores/notebook.svelte';
|
||||||
import Button from '$lib/components/ui/button/button.svelte';
|
import Button from '$lib/components/ui/button/button.svelte';
|
||||||
import Textarea from '$lib/components/ui/textarea/textarea.svelte';
|
import Textarea from '$lib/components/ui/textarea/textarea.svelte';
|
||||||
import { Play, Square, Settings, Undo, Redo } from '@lucide/svelte';
|
import { Play, Square, Settings, Undo, Redo, RulerDimensionLine } from '@lucide/svelte';
|
||||||
import { config } from '$lib/stores/settings.svelte';
|
import { config } from '$lib/stores/settings.svelte';
|
||||||
import DialogChatSettings from '$lib/components/app/dialogs/DialogChatSettings.svelte';
|
import DialogChatSettings from '$lib/components/app/dialogs/DialogChatSettings.svelte';
|
||||||
import { ChatMessageStatistics, DialogChatError, KeyboardShortcutInfo, ModelsSelector } from '$lib/components/app';
|
import { ChatMessageStatistics, DialogChatError, KeyboardShortcutInfo, ModelsSelector } from '$lib/components/app';
|
||||||
|
|
@ -41,6 +41,7 @@
|
||||||
// Sync local input with store content
|
// Sync local input with store content
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
inputContent = notebookStore.content;
|
inputContent = notebookStore.content;
|
||||||
|
notebookStore.updateTokenCount();
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleInput(e: Event) {
|
function handleInput(e: Event) {
|
||||||
|
|
@ -335,16 +336,32 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if showMessageStats && (notebookStore.promptTokens > 0 || notebookStore.predictedTokens > 0)}
|
{#if showMessageStats}
|
||||||
<div class="flex w-full justify-end md:w-auto">
|
<div class="flex w-full flex-col items-end justify-center gap-0.5 md:w-auto">
|
||||||
<ChatMessageStatistics
|
{#if notebookStore.totalTokens > 0}
|
||||||
promptTokens={notebookStore.promptTokens}
|
<Tooltip.Root>
|
||||||
promptMs={notebookStore.promptMs}
|
<Tooltip.Trigger>
|
||||||
predictedTokens={notebookStore.predictedTokens}
|
<div class="pr-3.5 flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||||
predictedMs={notebookStore.predictedMs}
|
<RulerDimensionLine class="h-3.5 w-3.5" />
|
||||||
isLive={notebookStore.isGenerating}
|
<span>{notebookStore.totalTokens} tokens</span>
|
||||||
isProcessingPrompt={notebookStore.isGenerating && notebookStore.predictedTokens === 0}
|
</div>
|
||||||
/>
|
</Tooltip.Trigger>
|
||||||
|
<Tooltip.Content>
|
||||||
|
<p>Total tokens</p>
|
||||||
|
</Tooltip.Content>
|
||||||
|
</Tooltip.Root>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if notebookStore.promptTokens > 0 || notebookStore.predictedTokens > 0}
|
||||||
|
<ChatMessageStatistics
|
||||||
|
promptTokens={notebookStore.promptTokens}
|
||||||
|
promptMs={notebookStore.promptMs}
|
||||||
|
predictedTokens={notebookStore.predictedTokens}
|
||||||
|
predictedMs={notebookStore.predictedMs}
|
||||||
|
isLive={notebookStore.isGenerating}
|
||||||
|
isProcessingPrompt={notebookStore.isGenerating && notebookStore.predictedTokens === 0}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,17 @@
|
||||||
import { getJsonHeaders } from '$lib/utils';
|
import { getJsonHeaders } from '$lib/utils';
|
||||||
import { AttachmentType } from '$lib/enums';
|
import { AttachmentType } from '$lib/enums';
|
||||||
|
import type {
|
||||||
|
ApiChatCompletionRequest,
|
||||||
|
ApiChatCompletionResponse,
|
||||||
|
ApiChatCompletionStreamChunk,
|
||||||
|
ApiChatCompletionToolCall,
|
||||||
|
ApiChatCompletionToolCallDelta,
|
||||||
|
ApiChatMessageContentPart,
|
||||||
|
ApiChatMessageData,
|
||||||
|
ApiCompletionRequest,
|
||||||
|
ApiCompletionResponse,
|
||||||
|
ApiCompletionStreamChunk
|
||||||
|
} from '$lib/types/api';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ChatService - Low-level API communication layer for Chat Completions
|
* ChatService - Low-level API communication layer for Chat Completions
|
||||||
|
|
@ -1116,4 +1128,31 @@ export class ChatService {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Tokenizes the provided text using the server's tokenizer.
|
||||||
|
*
|
||||||
|
* @param content - The text content to tokenize
|
||||||
|
* @param signal - Optional AbortSignal
|
||||||
|
* @returns {Promise<number[]>} Promise that resolves to an array of token IDs
|
||||||
|
*/
|
||||||
|
static async tokenize(content: string, signal?: AbortSignal): Promise<number[]> {
|
||||||
|
try {
|
||||||
|
const response = await fetch('./tokenize', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getJsonHeaders(),
|
||||||
|
body: JSON.stringify({ content }),
|
||||||
|
signal
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Tokenize failed: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.tokens;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Tokenize error:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,9 @@ export class NotebookStore {
|
||||||
promptMs = $state(0);
|
promptMs = $state(0);
|
||||||
predictedTokens = $state(0);
|
predictedTokens = $state(0);
|
||||||
predictedMs = $state(0);
|
predictedMs = $state(0);
|
||||||
|
totalTokens = $state(0);
|
||||||
|
generationStartTokens = $state(0);
|
||||||
|
tokenizeTimeout: ReturnType<typeof setTimeout> | undefined;
|
||||||
|
|
||||||
error = $state<{
|
error = $state<{
|
||||||
message: string;
|
message: string;
|
||||||
|
|
@ -36,6 +39,9 @@ export class NotebookStore {
|
||||||
this.predictedTokens = 0;
|
this.predictedTokens = 0;
|
||||||
this.predictedMs = 0;
|
this.predictedMs = 0;
|
||||||
|
|
||||||
|
// Snapshot the current total tokens as the baseline for this generation
|
||||||
|
this.generationStartTokens = this.totalTokens;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const currentConfig = config();
|
const currentConfig = config();
|
||||||
await ChatService.sendCompletion(
|
await ChatService.sendCompletion(
|
||||||
|
|
@ -62,9 +68,13 @@ export class NotebookStore {
|
||||||
if (processed > 0) this.promptTokens = processed;
|
if (processed > 0) this.promptTokens = processed;
|
||||||
if (time_ms > 0) this.promptMs = time_ms;
|
if (time_ms > 0) this.promptMs = time_ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update totalTokens live
|
||||||
|
this.totalTokens = this.generationStartTokens + this.predictedTokens;
|
||||||
},
|
},
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
this.isGenerating = false;
|
this.isGenerating = false;
|
||||||
|
this.totalTokens = this.generationStartTokens + this.predictedTokens;
|
||||||
},
|
},
|
||||||
onError: (error: unknown) => {
|
onError: (error: unknown) => {
|
||||||
if (error instanceof Error && error.name === 'AbortError') {
|
if (error instanceof Error && error.name === 'AbortError') {
|
||||||
|
|
@ -123,6 +133,17 @@ export class NotebookStore {
|
||||||
}
|
}
|
||||||
this.isGenerating = false;
|
this.isGenerating = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateTokenCount() {
|
||||||
|
if (this.tokenizeTimeout) {
|
||||||
|
clearTimeout(this.tokenizeTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tokenizeTimeout = setTimeout(async () => {
|
||||||
|
const tokens = await ChatService.tokenize(this.content);
|
||||||
|
this.totalTokens = tokens.length;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const notebookStore = new NotebookStore();
|
export const notebookStore = new NotebookStore();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue