11import { TextAttributes } from '@opentui/core'
22import { useKeyboard } from '@opentui/react'
3- import React , { useCallback , useState } from 'react'
3+ import React , { useCallback , useMemo , useState } from 'react'
44
55import { Button } from './button'
66import { FREEBUFF_MODELS } from '@codebuff/common/constants/freebuff-models'
77
88import { switchFreebuffModel } from '../hooks/use-freebuff-session'
99import { useFreebuffModelStore } from '../state/freebuff-model-store'
10+ import { useFreebuffSessionStore } from '../state/freebuff-session-store'
1011import { useTheme } from '../hooks/use-theme'
1112
1213import type { KeyEvent } from '@opentui/core'
@@ -15,13 +16,33 @@ import type { KeyEvent } from '@opentui/core'
1516 * Lets the user pick which model's queue they're in. Tapping (or pressing the
1617 * row's number key) on a different model triggers a re-POST: the server moves
1718 * them to the back of the new model's queue.
19+ *
20+ * Each row shows a live "N ahead" count sourced from the server's
21+ * `queueDepthByModel` snapshot so the choice is informed (e.g. "3 ahead" vs
22+ * "12 ahead") rather than a blind preference toggle.
1823 */
1924export const FreebuffModelSelector : React . FC = ( ) => {
2025 const theme = useTheme ( )
2126 const selectedModel = useFreebuffModelStore ( ( s ) => s . selectedModel )
27+ const session = useFreebuffSessionStore ( ( s ) => s . session )
2228 const [ pending , setPending ] = useState < string | null > ( null )
2329 const [ hoveredId , setHoveredId ] = useState < string | null > ( null )
2430
31+ // For the user's current queue, "ahead" is `position - 1` (themselves don't
32+ // count). For every other queue, switching would land them at the back, so
33+ // it's that queue's full depth. Null before the first queued snapshot so
34+ // the UI doesn't flash misleading zeros.
35+ const aheadByModel = useMemo < Record < string , number > | null > ( ( ) => {
36+ if ( session ?. status !== 'queued' ) return null
37+ const depths = session . queueDepthByModel ?? { }
38+ const out : Record < string , number > = { }
39+ for ( const { id } of FREEBUFF_MODELS ) {
40+ out [ id ] =
41+ id === session . model ? Math . max ( 0 , session . position - 1 ) : depths [ id ] ?? 0
42+ }
43+ return out
44+ } , [ session ] )
45+
2546 const pick = useCallback (
2647 ( modelId : string ) => {
2748 if ( pending ) return
@@ -71,6 +92,13 @@ export const FreebuffModelSelector: React.FC = () => {
7192 const indicatorColor = isSelected ? theme . primary : theme . muted
7293 const labelColor = isSelected ? theme . foreground : theme . muted
7394 const interactable = ! pending && ! isSelected
95+ const ahead = aheadByModel ?. [ model . id ]
96+ const hint =
97+ ahead === undefined
98+ ? model . tagline
99+ : ahead === 0
100+ ? 'No wait'
101+ : `${ ahead } ahead`
74102 return (
75103 < Button
76104 key = { model . id }
@@ -88,7 +116,7 @@ export const FreebuffModelSelector: React.FC = () => {
88116 >
89117 { model . displayName }
90118 </ span >
91- < span fg = { theme . muted } > { model . tagline } </ span >
119+ < span fg = { theme . muted } > { hint } </ span >
92120 { isPending && < span fg = { theme . muted } > switching…</ span > }
93121 { isHovered && interactable && ! isPending && (
94122 < span fg = { theme . muted } > ↵</ span >
0 commit comments