Felipe97/llama-cpp-compiled
01.2k
1import { STATS_UNITS } from '$lib/constants';2import { chatStore } from '$lib/stores';3import type { ApiProcessingState, LiveGenerationStats, LiveProcessingStats } from '$lib/types';4 5export interface UseProcessingStateReturn {6 readonly processingState: ApiProcessingState | null;7 getProcessingDetails(): string[];8 getTechnicalDetails(): string[];9 getProcessingMessage(): string;10 getPromptProgressText(): string | null;11 getLiveProcessingStats(): LiveProcessingStats | null;12 getLiveGenerationStats(): LiveGenerationStats | null;13 shouldShowDetails(): boolean;14 startMonitoring(): void;15 stopMonitoring(): void;16}17 18/**19 * useProcessingState - Reactive processing state hook20 *21 * This hook provides reactive access to the processing state of the server.22 * It directly reads from chatStore's reactive state and provides23 * formatted processing details for UI display.24 *25 * **Features:**26 * - Real-time processing state via direct reactive state binding27 * - Context and output token tracking28 * - Tokens per second calculation29 * - Automatic updates when streaming data arrives30 * - Supports multiple concurrent conversations31 *32 * @returns Hook interface with processing state and control methods33 */34export function useProcessingState(): UseProcessingStateReturn {35 let isMonitoring = $state(false);36 let lastKnownState = $state<ApiProcessingState | null>(null);37 let lastKnownProcessingStats = $state<LiveProcessingStats | null>(null);38 39 // Derive processing state reactively from chatStore's direct state40 const processingState = $derived.by(() => {41 if (!isMonitoring) {42 return lastKnownState;43 }44 45 // Read directly from the reactive state46 return chatStore.processing.activeState;47 });48 49 $effect(() => {50 if (processingState && isMonitoring) {51 lastKnownState = processingState;52 }53 });54 55 // Track last known processing stats for when promptProgress disappears56 $effect(() => {57 if (processingState?.promptProgress) {58 const { cache, processed, time_ms, total } = processingState.promptProgress;59 const actualProcessed = processed - cache;60 const actualTotal = total - cache;61 62 if (actualProcessed > 0 && time_ms > 0) {63 const tokensPerSecond = actualProcessed / (time_ms / 1000);64 65 lastKnownProcessingStats = {66 timeMs: time_ms,67 tokensPerSecond,68 tokensProcessed: actualProcessed,69 totalTokens: actualTotal70 };71 }72 }73 });74 75 function getETASecs(done: number, total: number, elapsedMs: number): number | undefined {76 const elapsedSecs = elapsedMs / 1000;77 const progressETASecs =78 done === 0 || elapsedSecs < 0.579 ? undefined // can be the case for the 0% progress report80 : elapsedSecs * (total / done - 1);81 82 return progressETASecs;83 }84 85 function startMonitoring(): void {86 if (isMonitoring) return;87 88 isMonitoring = true;89 }90 91 function stopMonitoring(): void {92 if (!isMonitoring) return;93 94 isMonitoring = false;95 }96 97 function getProcessingMessage(): string {98 if (!processingState) {99 return 'Processing...';100 }101 102 switch (processingState.status) {103 case 'initializing':104 return 'Initializing...';105 case 'preparing':106 if (processingState.progressPercent !== undefined) {107 return `Processing (${processingState.progressPercent}%)`;108 }109 110 return 'Preparing response...';111 case 'generating':112 return '';113 default:114 return 'Processing...';115 }116 }117 118 function getProcessingDetails(): string[] {119 // Use current processing state or fall back to last known state120 const stateToUse = processingState || lastKnownState;121 122 if (!stateToUse) {123 return [];124 }125 126 const details: string[] = [];127 128 // Show prompt processing progress with ETA during preparation phase129 if (stateToUse.promptProgress) {130 const { cache, processed, time_ms, total } = stateToUse.promptProgress;131 const actualProcessed = processed - cache;132 const actualTotal = total - cache;133 134 if (actualProcessed < actualTotal && actualProcessed > 0) {135 const percent = Math.round((actualProcessed / actualTotal) * 100);136 const eta = getETASecs(actualProcessed, actualTotal, time_ms);137 138 if (eta !== undefined) {139 const etaSecs = Math.ceil(eta);140 141 details.push(`Processing ${percent}% (ETA: ${etaSecs}s)`);142 } else {143 details.push(`Processing ${percent}%`);144 }145 }146 }147 148 // Always show context info when we have valid data149 if (150 typeof stateToUse.contextTotal === 'number' &&151 stateToUse.contextUsed >= 0 &&152 stateToUse.contextTotal > 0153 ) {154 const contextPercent = Math.round((stateToUse.contextUsed / stateToUse.contextTotal) * 100);155 156 details.push(157 `Context: ${stateToUse.contextUsed}/${stateToUse.contextTotal} (${contextPercent}%)`158 );159 }160 161 if (stateToUse.outputTokensUsed > 0) {162 // Handle infinite max_tokens (-1) case163 if (stateToUse.outputTokensMax <= 0) {164 details.push(`Output: ${stateToUse.outputTokensUsed}/∞`);165 } else {166 const outputPercent = Math.round(167 (stateToUse.outputTokensUsed / stateToUse.outputTokensMax) * 100168 );169 170 details.push(171 `Output: ${stateToUse.outputTokensUsed}/${stateToUse.outputTokensMax} (${outputPercent}%)`172 );173 }174 }175 176 if (stateToUse.tokensPerSecond && stateToUse.tokensPerSecond > 0) {177 details.push(`${stateToUse.tokensPerSecond.toFixed(1)} ${STATS_UNITS.TOKENS_PER_SECOND}`);178 }179 180 if (stateToUse.speculative) {181 details.push('Speculative decoding enabled');182 }183 184 return details;185 }186 187 /**188 * Returns technical details without the progress message (for bottom bar)189 */190 function getTechnicalDetails(): string[] {191 const stateToUse = processingState || lastKnownState;192 193 if (!stateToUse) {194 return [];195 }196 197 const details: string[] = [];198 199 // Always show context info when we have valid data200 if (201 typeof stateToUse.contextTotal === 'number' &&202 stateToUse.contextUsed >= 0 &&203 stateToUse.contextTotal > 0204 ) {205 const contextPercent = Math.round((stateToUse.contextUsed / stateToUse.contextTotal) * 100);206 207 details.push(208 `Context: ${stateToUse.contextUsed}/${stateToUse.contextTotal} (${contextPercent}%)`209 );210 }211 212 if (stateToUse.outputTokensUsed > 0) {213 // Handle infinite max_tokens (-1) case214 if (stateToUse.outputTokensMax <= 0) {215 details.push(`Output: ${stateToUse.outputTokensUsed}/∞`);216 } else {217 const outputPercent = Math.round(218 (stateToUse.outputTokensUsed / stateToUse.outputTokensMax) * 100219 );220 221 details.push(222 `Output: ${stateToUse.outputTokensUsed}/${stateToUse.outputTokensMax} (${outputPercent}%)`223 );224 }225 }226 227 if (stateToUse.tokensPerSecond && stateToUse.tokensPerSecond > 0) {228 details.push(`${stateToUse.tokensPerSecond.toFixed(1)} ${STATS_UNITS.TOKENS_PER_SECOND}`);229 }230 231 if (stateToUse.speculative) {232 details.push('Speculative decoding enabled');233 }234 235 return details;236 }237 238 function shouldShowDetails(): boolean {239 return processingState !== null && processingState.status !== 'idle';240 }241 242 /**243 * Returns a short progress message with percent244 */245 function getPromptProgressText(): string | null {246 if (!processingState?.promptProgress) return null;247 248 const { cache, processed, total } = processingState.promptProgress;249 const actualProcessed = processed - cache;250 const actualTotal = total - cache;251 const percent = Math.round((actualProcessed / actualTotal) * 100);252 const eta = getETASecs(actualProcessed, actualTotal, processingState.promptProgress.time_ms);253 254 if (eta !== undefined) {255 const etaSecs = Math.ceil(eta);256 257 return `Processing ${percent}% (ETA: ${etaSecs}s)`;258 }259 260 return `Processing ${percent}%`;261 }262 263 /**264 * Returns live processing statistics for display (prompt processing phase)265 * Returns last known stats when promptProgress becomes unavailable266 */267 function getLiveProcessingStats(): LiveProcessingStats | null {268 if (processingState?.promptProgress) {269 const { cache, processed, time_ms, total } = processingState.promptProgress;270 const actualProcessed = processed - cache;271 const actualTotal = total - cache;272 273 if (actualProcessed > 0 && time_ms > 0) {274 const tokensPerSecond = actualProcessed / (time_ms / 1000);275 276 return {277 timeMs: time_ms,278 tokensPerSecond,279 tokensProcessed: actualProcessed,280 totalTokens: actualTotal281 };282 }283 }284 285 // Return last known stats if promptProgress is no longer available286 return lastKnownProcessingStats;287 }288 289 /**290 * Returns live generation statistics for display (token generation phase)291 */292 function getLiveGenerationStats(): LiveGenerationStats | null {293 if (!processingState) return null;294 295 const { tokensDecoded, tokensPerSecond } = processingState;296 297 if (tokensDecoded <= 0) return null;298 299 // Calculate time from tokens and speed300 const timeMs =301 tokensPerSecond && tokensPerSecond > 0 ? (tokensDecoded / tokensPerSecond) * 1000 : 0;302 303 return {304 timeMs,305 tokensGenerated: tokensDecoded,306 tokensPerSecond: tokensPerSecond || 0307 };308 }309 310 return {311 getLiveGenerationStats,312 getLiveProcessingStats,313 getProcessingDetails,314 getProcessingMessage,315 getPromptProgressText,316 getTechnicalDetails,317 get processingState() {318 return processingState;319 },320 shouldShowDetails,321 startMonitoring,322 stopMonitoring323 };324}325 