Webui/prompt processing progress (#18300)
* webui: display prompt preprocessing progress * webui: add percentage/ETA and exclude cached tokens from progress Address review feedback from ngxson * webui: add minutes and first chunk (0%) case * Update tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageAssistant.svelte Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com> * Update tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageAssistant.svelte Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com> * webui: address review feedback from allozaur * chore: update webui build output * webui: address review feedback from allozaur * nit * chore: update webui build output * feat: Enhance chat processing state * feat: Improve chat processing statistics UI * chore: update webui build output * feat: Add live generation statistics to processing state hook * feat: Persist prompt processing stats in hook for better UX * refactor: Enhance ChatMessageStatistics for live stream display * feat: Implement enhanced live chat statistics into assistant message * chore: update webui build output * fix: Proper tab for each stage of prompt processing/generation * chore: update webui build output * fix: Improved ETA calculation & display logic * chore: update webui build output * feat: Simplify logic & remove ETA from prompt progress * chore: update webui build output --------- Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com>
This commit is contained in:
@@ -1,10 +1,26 @@
|
||||
import { activeProcessingState } from '$lib/stores/chat.svelte';
|
||||
import { config } from '$lib/stores/settings.svelte';
|
||||
|
||||
export interface LiveProcessingStats {
|
||||
tokensProcessed: number;
|
||||
totalTokens: number;
|
||||
timeMs: number;
|
||||
tokensPerSecond: number;
|
||||
}
|
||||
|
||||
export interface LiveGenerationStats {
|
||||
tokensGenerated: number;
|
||||
timeMs: number;
|
||||
tokensPerSecond: number;
|
||||
}
|
||||
|
||||
export interface UseProcessingStateReturn {
|
||||
readonly processingState: ApiProcessingState | null;
|
||||
getProcessingDetails(): string[];
|
||||
getProcessingMessage(): string;
|
||||
getPromptProgressText(): string | null;
|
||||
getLiveProcessingStats(): LiveProcessingStats | null;
|
||||
getLiveGenerationStats(): LiveGenerationStats | null;
|
||||
shouldShowDetails(): boolean;
|
||||
startMonitoring(): void;
|
||||
stopMonitoring(): void;
|
||||
@@ -29,6 +45,7 @@ export interface UseProcessingStateReturn {
|
||||
export function useProcessingState(): UseProcessingStateReturn {
|
||||
let isMonitoring = $state(false);
|
||||
let lastKnownState = $state<ApiProcessingState | null>(null);
|
||||
let lastKnownProcessingStats = $state<LiveProcessingStats | null>(null);
|
||||
|
||||
// Derive processing state reactively from chatStore's direct state
|
||||
const processingState = $derived.by(() => {
|
||||
@@ -46,6 +63,25 @@ export function useProcessingState(): UseProcessingStateReturn {
|
||||
}
|
||||
});
|
||||
|
||||
// Track last known processing stats for when promptProgress disappears
|
||||
$effect(() => {
|
||||
if (processingState?.promptProgress) {
|
||||
const { processed, total, time_ms, cache } = processingState.promptProgress;
|
||||
const actualProcessed = processed - cache;
|
||||
const actualTotal = total - cache;
|
||||
|
||||
if (actualProcessed > 0 && time_ms > 0) {
|
||||
const tokensPerSecond = actualProcessed / (time_ms / 1000);
|
||||
lastKnownProcessingStats = {
|
||||
tokensProcessed: actualProcessed,
|
||||
totalTokens: actualTotal,
|
||||
timeMs: time_ms,
|
||||
tokensPerSecond
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function startMonitoring(): void {
|
||||
if (isMonitoring) return;
|
||||
isMonitoring = true;
|
||||
@@ -59,28 +95,25 @@ export function useProcessingState(): UseProcessingStateReturn {
|
||||
const currentConfig = config();
|
||||
if (!currentConfig.keepStatsVisible) {
|
||||
lastKnownState = null;
|
||||
lastKnownProcessingStats = null;
|
||||
}
|
||||
}
|
||||
|
||||
function getProcessingMessage(): string {
|
||||
const state = processingState;
|
||||
if (!state) {
|
||||
if (!processingState) {
|
||||
return 'Processing...';
|
||||
}
|
||||
|
||||
switch (state.status) {
|
||||
switch (processingState.status) {
|
||||
case 'initializing':
|
||||
return 'Initializing...';
|
||||
case 'preparing':
|
||||
if (state.progressPercent !== undefined) {
|
||||
return `Processing (${state.progressPercent}%)`;
|
||||
if (processingState.progressPercent !== undefined) {
|
||||
return `Processing (${processingState.progressPercent}%)`;
|
||||
}
|
||||
return 'Preparing response...';
|
||||
case 'generating':
|
||||
if (state.tokensDecoded > 0) {
|
||||
return `Generating... (${state.tokensDecoded} tokens)`;
|
||||
}
|
||||
return 'Generating...';
|
||||
return '';
|
||||
default:
|
||||
return 'Processing...';
|
||||
}
|
||||
@@ -131,8 +164,70 @@ export function useProcessingState(): UseProcessingStateReturn {
|
||||
}
|
||||
|
||||
function shouldShowDetails(): boolean {
|
||||
const state = processingState;
|
||||
return state !== null && state.status !== 'idle';
|
||||
return processingState !== null && processingState.status !== 'idle';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a short progress message with percent
|
||||
*/
|
||||
function getPromptProgressText(): string | null {
|
||||
if (!processingState?.promptProgress) return null;
|
||||
|
||||
const { processed, total, cache } = processingState.promptProgress;
|
||||
|
||||
const actualProcessed = processed - cache;
|
||||
const actualTotal = total - cache;
|
||||
const percent = Math.round((actualProcessed / actualTotal) * 100);
|
||||
|
||||
return `Processing ${percent}%`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns live processing statistics for display (prompt processing phase)
|
||||
* Returns last known stats when promptProgress becomes unavailable
|
||||
*/
|
||||
function getLiveProcessingStats(): LiveProcessingStats | null {
|
||||
if (processingState?.promptProgress) {
|
||||
const { processed, total, time_ms, cache } = processingState.promptProgress;
|
||||
|
||||
const actualProcessed = processed - cache;
|
||||
const actualTotal = total - cache;
|
||||
|
||||
if (actualProcessed > 0 && time_ms > 0) {
|
||||
const tokensPerSecond = actualProcessed / (time_ms / 1000);
|
||||
|
||||
return {
|
||||
tokensProcessed: actualProcessed,
|
||||
totalTokens: actualTotal,
|
||||
timeMs: time_ms,
|
||||
tokensPerSecond
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Return last known stats if promptProgress is no longer available
|
||||
return lastKnownProcessingStats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns live generation statistics for display (token generation phase)
|
||||
*/
|
||||
function getLiveGenerationStats(): LiveGenerationStats | null {
|
||||
if (!processingState) return null;
|
||||
|
||||
const { tokensDecoded, tokensPerSecond } = processingState;
|
||||
|
||||
if (tokensDecoded <= 0) return null;
|
||||
|
||||
// Calculate time from tokens and speed
|
||||
const timeMs =
|
||||
tokensPerSecond && tokensPerSecond > 0 ? (tokensDecoded / tokensPerSecond) * 1000 : 0;
|
||||
|
||||
return {
|
||||
tokensGenerated: tokensDecoded,
|
||||
timeMs,
|
||||
tokensPerSecond: tokensPerSecond || 0
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -141,6 +236,9 @@ export function useProcessingState(): UseProcessingStateReturn {
|
||||
},
|
||||
getProcessingDetails,
|
||||
getProcessingMessage,
|
||||
getPromptProgressText,
|
||||
getLiveProcessingStats,
|
||||
getLiveGenerationStats,
|
||||
shouldShowDetails,
|
||||
startMonitoring,
|
||||
stopMonitoring
|
||||
|
||||
Reference in New Issue
Block a user