Add generation statistics to notebook page
This commit is contained in:
parent
8a71126e5b
commit
301c3fec7e
Binary file not shown.
|
|
@ -5,7 +5,7 @@
|
|||
import { Play, Square, Settings } from '@lucide/svelte';
|
||||
import { config } from '$lib/stores/settings.svelte';
|
||||
import DialogChatSettings from '$lib/components/app/dialogs/DialogChatSettings.svelte';
|
||||
import { ModelsSelector } from '$lib/components/app';
|
||||
import { ModelsSelector, ChatMessageStatistics } from '$lib/components/app';
|
||||
import { useModelChangeValidation } from '$lib/hooks/use-model-change-validation.svelte';
|
||||
import { modelsStore, modelOptions, selectedModelId } from '$lib/stores/models.svelte';
|
||||
import { isRouterMode } from '$lib/stores/server.svelte';
|
||||
|
|
@ -24,6 +24,7 @@
|
|||
import { onMount } from 'svelte';
|
||||
|
||||
let disableAutoScroll = $derived(Boolean(config().disableAutoScroll));
|
||||
let showMessageStats = $derived(config().showMessageStats);
|
||||
let autoScrollEnabled = $state(true);
|
||||
let scrollContainer: HTMLTextAreaElement | null = $state(null);
|
||||
let lastScrollTop = $state(0);
|
||||
|
|
@ -190,19 +191,19 @@
|
|||
</Button>
|
||||
</header>
|
||||
|
||||
<div class="flex-1 overflow-y-auto p-4 md:p-6">
|
||||
<div class="flex-1 overflow-y-auto p-2 md:p-4">
|
||||
<Textarea
|
||||
bind:ref={scrollContainer}
|
||||
onscroll={handleScroll}
|
||||
value={inputContent}
|
||||
oninput={handleInput}
|
||||
class="h-full min-h-[500px] w-full resize-none rounded-xl border-none bg-muted p-4 text-base focus-visible:ring-0 md:p-6"
|
||||
class="h-full min-h-[100px] w-full resize-none rounded-xl border-none bg-muted p-4 text-base focus-visible:ring-0 md:p-6"
|
||||
placeholder="Enter your text here..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-border/40 bg-background p-4 md:px-6 md:py-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="bg-background p-2 md:p-4">
|
||||
<div class="flex flex-col-reverse gap-4 md:flex-row md:items-center md:justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
{#snippet generateButton(props = {})}
|
||||
<Button
|
||||
|
|
@ -244,6 +245,19 @@
|
|||
disabled={notebookStore.isGenerating}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if showMessageStats && (notebookStore.promptTokens > 0 || notebookStore.predictedTokens > 0)}
|
||||
<div class="flex w-full justify-end md:w-auto">
|
||||
<ChatMessageStatistics
|
||||
promptTokens={notebookStore.promptTokens}
|
||||
promptMs={notebookStore.promptMs}
|
||||
predictedTokens={notebookStore.predictedTokens}
|
||||
predictedMs={notebookStore.predictedMs}
|
||||
isLive={notebookStore.isGenerating}
|
||||
isProcessingPrompt={notebookStore.isGenerating && notebookStore.predictedTokens === 0}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ export const SETTING_CONFIG_INFO: Record<string, string> = {
|
|||
'Show raw LLM output without backend parsing and frontend Markdown rendering to inspect streaming across different models.',
|
||||
keepStatsVisible: 'Keep processing statistics visible after generation finishes.',
|
||||
showMessageStats:
|
||||
'Display generation statistics (tokens/second, token count, duration) below each assistant message.',
|
||||
'Display generation statistics (tokens/second, token count, duration).',
|
||||
askForTitleConfirmation:
|
||||
'Ask for confirmation before automatically changing conversation title when editing the first message.',
|
||||
pdfAsImage:
|
||||
|
|
|
|||
|
|
@ -1019,6 +1019,7 @@ export class ChatService {
|
|||
const content = parsed.content;
|
||||
const timings = parsed.timings;
|
||||
const model = parsed.model;
|
||||
const promptProgress = parsed.prompt_progress;
|
||||
|
||||
if (parsed.stop) {
|
||||
streamFinished = true;
|
||||
|
|
@ -1029,8 +1030,12 @@ export class ChatService {
|
|||
onModel?.(model);
|
||||
}
|
||||
|
||||
if (promptProgress) {
|
||||
ChatService.notifyTimings(undefined, promptProgress, onTimings);
|
||||
}
|
||||
|
||||
if (timings) {
|
||||
ChatService.notifyTimings(timings, undefined, onTimings);
|
||||
ChatService.notifyTimings(timings, promptProgress, onTimings);
|
||||
lastTimings = timings;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,21 +32,29 @@ export class NotebookStore {
|
|||
...currentConfig,
|
||||
model: model ?? currentConfig.model,
|
||||
stream: true,
|
||||
onChunk: (chunk) => {
|
||||
timings_per_token: true,
|
||||
onChunk: (chunk: string) => {
|
||||
this.content += chunk;
|
||||
},
|
||||
onTimings: (timings) => {
|
||||
onTimings: (timings: ChatMessageTimings, promptProgress: ChatMessagePromptProgress) => {
|
||||
if (timings) {
|
||||
if (timings.prompt_n) this.promptTokens = timings.prompt_n;
|
||||
if (timings.prompt_ms) this.promptMs = timings.prompt_ms;
|
||||
if (timings.predicted_n) this.predictedTokens = timings.predicted_n;
|
||||
if (timings.predicted_ms) this.predictedMs = timings.predicted_ms;
|
||||
}
|
||||
|
||||
if (promptProgress) {
|
||||
// Update prompt stats from progress
|
||||
const { processed, time_ms } = promptProgress;
|
||||
if (processed > 0) this.promptTokens = processed;
|
||||
if (time_ms > 0) this.promptMs = time_ms;
|
||||
}
|
||||
},
|
||||
onComplete: () => {
|
||||
this.isGenerating = false;
|
||||
},
|
||||
onError: (error) => {
|
||||
onError: (error: unknown) => {
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
// aborted by user
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Reference in New Issue