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