@@ -17,6 +17,7 @@ import { exitFreebuffCleanly } from '../utils/freebuff-exit'
1717import { getLogoAccentColor , getLogoBlockColor } from '../utils/theme-system'
1818
1919import type { FreebuffSessionResponse } from '../types/freebuff-session'
20+ import type { FreebuffIpPrivacySignal } from '@codebuff/common/types/freebuff-session'
2021
2122interface WaitingRoomScreenProps {
2223 session : FreebuffSessionResponse | null
@@ -55,6 +56,35 @@ const formatRetryAfter = (ms: number): string => {
5556 return rem === 0 ? `${ hours } h` : `${ hours } h ${ rem } m`
5657}
5758
59+ const PRIVACY_SIGNAL_LABELS : Partial < Record < FreebuffIpPrivacySignal , string > > =
60+ {
61+ anonymous : 'anonymized network' ,
62+ proxy : 'proxy' ,
63+ relay : 'relay' ,
64+ res_proxy : 'residential proxy' ,
65+ tor : 'Tor' ,
66+ vpn : 'VPN' ,
67+ }
68+
69+ const formatPrivacySignalList = (
70+ signals : FreebuffIpPrivacySignal [ ] | undefined ,
71+ ) : string => {
72+ const labels = Array . from (
73+ new Set (
74+ signals
75+ ?. map ( ( signal ) => PRIVACY_SIGNAL_LABELS [ signal ] )
76+ . filter ( ( label ) : label is string => Boolean ( label ) ) ?? [ ] ,
77+ ) ,
78+ )
79+
80+ if ( labels . length === 0 ) {
81+ return 'VPN, Tor, proxy, relay, or anonymized network'
82+ }
83+ if ( labels . length === 1 ) return labels [ 0 ]
84+ if ( labels . length === 2 ) return `${ labels [ 0 ] } or ${ labels [ 1 ] } `
85+ return `${ labels . slice ( 0 , - 1 ) . join ( ', ' ) } , or ${ labels [ labels . length - 1 ] } `
86+ }
87+
5888export const WaitingRoomScreen : React . FC < WaitingRoomScreenProps > = ( {
5989 session,
6090 error,
@@ -85,7 +115,7 @@ export const WaitingRoomScreen: React.FC<WaitingRoomScreenProps> = ({
85115 // forceStart bypasses the "wait for first user message" gate inside the hook,
86116 // which would otherwise block ads here since no conversation exists yet.
87117 // Try Gravity first, then fall back to Carbon when Gravity doesn't fill.
88- const { adData , recordImpression } = useGravityAd ( {
118+ const { ads , recordImpression } = useGravityAd ( {
89119 enabled : true ,
90120 forceStart : true ,
91121 provider : 'gravity' ,
@@ -221,23 +251,24 @@ export const WaitingRoomScreen: React.FC<WaitingRoomScreenProps> = ({
221251 < span fg = { theme . muted } > / { session . queueDepth } </ span >
222252 </ text >
223253 < text style = { { fg : theme . muted , alignSelf : 'flex-start' } } >
224- < span > Wait </ span >
254+ < span > Wait </ span >
225255 { session . position === 1
226256 ? 'any moment now'
227257 : formatWait ( session . estimatedWaitMs ) }
228258 </ text >
229259 < text style = { { fg : theme . muted , alignSelf : 'flex-start' } } >
230- < span > Elapsed </ span >
260+ < span > Elapsed </ span >
231261 { formatElapsed ( elapsedMs ) }
232262 </ text >
233- { /* Per-model session quota (e.g. GLM 5.1 caps at 5/20h ). Only
263+ { /* Per-model session quota (e.g. Kimi K2.6 caps at 5/12h ). Only
234264 rendered for rate-limited models so the Minimax queue stays
235265 clutter-free. */ }
236266 { session . rateLimit && (
237267 < text style = { { fg : theme . muted , alignSelf : 'flex-start' } } >
238268 < span > Sessions </ span >
239269 < span fg = { theme . foreground } >
240- { session . rateLimit . recentCount } / { session . rateLimit . limit }
270+ { session . rateLimit . recentCount } /{ ' ' }
271+ { session . rateLimit . limit }
241272 </ span >
242273 < span > used in last { session . rateLimit . windowHours } h</ span >
243274 </ text >
@@ -262,10 +293,36 @@ export const WaitingRoomScreen: React.FC<WaitingRoomScreenProps> = ({
262293 ⚠ Free mode isn't available in your region
263294 </ text >
264295 < text style = { { fg : theme . muted , wrapMode : 'word' } } >
265- We detected your location as{ ' ' }
266- < span fg = { theme . foreground } > { session . countryCode } </ span > ,
267- which is outside the countries where freebuff is currently
268- offered. Press Ctrl+C to exit.
296+ { session . countryBlockReason === 'anonymous_network' ? (
297+ < >
298+ We detected{ ' ' }
299+ { formatPrivacySignalList ( session . ipPrivacySignals ) } traffic
300+ { session . countryCode === 'UNKNOWN' ? (
301+ ''
302+ ) : (
303+ < >
304+ { ' ' }
305+ from{ ' ' }
306+ < span fg = { theme . foreground } > { session . countryCode } </ span >
307+ </ >
308+ ) }
309+ . Freebuff can't be used from anonymized networks. Press
310+ Ctrl+C to exit.
311+ </ >
312+ ) : session . countryCode === 'UNKNOWN' ? (
313+ < >
314+ We couldn't verify an eligible location for this request.
315+ VPN, Tor, proxy, or unknown-location traffic can't use
316+ freebuff. Press Ctrl+C to exit.
317+ </ >
318+ ) : (
319+ < >
320+ We detected your location as{ ' ' }
321+ < span fg = { theme . foreground } > { session . countryCode } </ span > ,
322+ which is outside the countries where freebuff is currently
323+ offered. Press Ctrl+C to exit.
324+ </ >
325+ ) }
269326 </ text >
270327 </ >
271328 ) }
@@ -279,14 +336,15 @@ export const WaitingRoomScreen: React.FC<WaitingRoomScreenProps> = ({
279336 ⚠ Account unavailable
280337 </ text >
281338 < text style = { { fg : theme . muted , wrapMode : 'word' } } >
282- This account has been suspended and can't use freebuff. If you think this is a
283- mistake, contact support@codebuff.com. Press Ctrl+C to exit.
339+ This account has been suspended and can't use freebuff. If you
340+ think this is a mistake, contact support@codebuff.com. Press
341+ Ctrl+C to exit.
284342 </ text >
285343 </ >
286344 ) }
287345
288- { /* Per-model session quota exhausted (e.g. 5+ GLM sessions in the
289- last 20h ). Terminal for this run — the user can exit and come
346+ { /* Per-model session quota exhausted (e.g. 5+ Kimi sessions in the
347+ last 12h ). Terminal for this run — the user can exit and come
290348 back once the oldest session in the window rolls off. */ }
291349 { session ?. status === 'rate_limited' && (
292350 < >
@@ -311,17 +369,14 @@ export const WaitingRoomScreen: React.FC<WaitingRoomScreenProps> = ({
311369 </ box >
312370
313371 { /* Ad banner pinned to the bottom, same look-and-feel as in chat. */ }
314- { adData && (
372+ { ads && (
315373 < box style = { { flexShrink : 0 } } >
316- < ChoiceAdBanner
317- ads = { adData . variant === 'choice' ? adData . ads : [ adData . ad ] }
318- onImpression = { recordImpression }
319- />
374+ < ChoiceAdBanner ads = { ads } onImpression = { recordImpression } />
320375 </ box >
321376 ) }
322377
323378 { /* Horizontal separator (mirrors chat input divider style) */ }
324- { ! adData && (
379+ { ! ads && (
325380 < text style = { { fg : theme . muted , flexShrink : 0 } } >
326381 { '─' . repeat ( terminalWidth ) }
327382 </ text >
0 commit comments