@@ -80,6 +80,8 @@ const PRIVACY_SIGNAL_LABELS: Partial<Record<FreebuffIpPrivacySignal, string>> =
8080 res_proxy : 'residential proxy' ,
8181 tor : 'Tor' ,
8282 vpn : 'VPN' ,
83+ hosting : 'hosting network' ,
84+ service : 'privacy service' ,
8385 }
8486
8587const formatPrivacySignalList = (
@@ -101,6 +103,38 @@ const formatPrivacySignalList = (
101103 return `${ labels . slice ( 0 , - 1 ) . join ( ', ' ) } , or ${ labels [ labels . length - 1 ] } `
102104}
103105
106+ const getLimitedModeReason = (
107+ session : FreebuffSessionResponse | null ,
108+ ) : string | null => {
109+ if ( ! session || ! ( 'countryBlockReason' in session ) ) {
110+ return 'reduced free model access'
111+ }
112+
113+ const countryCode =
114+ 'countryCode' in session &&
115+ session . countryCode &&
116+ session . countryCode !== 'UNKNOWN'
117+ ? session . countryCode
118+ : null
119+
120+ switch ( session . countryBlockReason ) {
121+ case 'anonymous_network' :
122+ return `${ formatPrivacySignalList (
123+ session . ipPrivacySignals ?? undefined ,
124+ ) } detected`
125+ case 'country_not_allowed' :
126+ return `outside available countries${ countryCode ? ` (${ countryCode } )` : '' } `
127+ case 'anonymized_or_unknown_country' :
128+ case 'missing_client_ip' :
129+ case 'unresolved_client_ip' :
130+ return 'location could not be verified'
131+ case 'ip_privacy_lookup_failed' :
132+ return 'network check could not finish'
133+ default :
134+ return 'reduced free model access'
135+ }
136+ }
137+
104138const TakeoverPrompt : React . FC = ( ) => {
105139 const theme = useTheme ( )
106140 const [ pending , setPending ] = useState ( false )
@@ -261,6 +295,8 @@ export const WaitingRoomScreen: React.FC<WaitingRoomScreenProps> = ({
261295 const isQueued = session ?. status === 'queued'
262296 const accessTier =
263297 session && 'accessTier' in session ? session . accessTier : 'full'
298+ const limitedModeReason =
299+ accessTier === 'limited' ? getLimitedModeReason ( session ) : null
264300 // 'none' = user hasn't joined any queue yet. We're in the pre-chat landing
265301 // state: show the picker with live N-in-line hints and a prompt. Picking a
266302 // model triggers joinFreebuffQueue, which POSTs and transitions us to
@@ -337,17 +373,28 @@ export const WaitingRoomScreen: React.FC<WaitingRoomScreenProps> = ({
337373 >
338374 { /* Top-right exit affordance so mouse users have a clear way out even
339375 when they don't know Ctrl+C works. width: '100%' is required for
340- justifyContent: 'flex-end' to actually push the X to the right. */ }
376+ justifyContent to actually push the X to the right. */ }
341377 < box
342378 style = { {
343379 width : '100%' ,
344380 flexDirection : 'row' ,
345- justifyContent : 'flex-end ' ,
381+ justifyContent : 'space-between ' ,
346382 paddingTop : 1 ,
383+ paddingLeft : 2 ,
347384 paddingRight : 2 ,
348385 flexShrink : 0 ,
349386 } }
350387 >
388+ < box >
389+ { limitedModeReason && (
390+ < text style = { { fg : theme . muted , wrapMode : 'word' } } >
391+ < span fg = { theme . secondary } attributes = { TextAttributes . BOLD } >
392+ Limited mode
393+ </ span >
394+ < span fg = { theme . muted } > · { limitedModeReason } </ span >
395+ </ text >
396+ ) }
397+ </ box >
351398 < Button
352399 onClick = { exitFreebuffCleanly }
353400 onMouseOver = { ( ) => setExitHover ( true ) }
0 commit comments