Skip to content

Commit 6656987

Browse files
[codex] Add compact Freebuff live map (#684)
Co-authored-by: James Grugett <jahooma@gmail.com>
1 parent 08415a2 commit 6656987

5 files changed

Lines changed: 120 additions & 21 deletions

File tree

freebuff/web/src/app/api/live/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ export async function GET() {
99
const stats = await getFreebuffLiveStats()
1010
return NextResponse.json(stats, {
1111
headers: {
12-
'Cache-Control': 'no-store, max-age=0',
12+
'Cache-Control':
13+
'public, max-age=0, s-maxage=60, stale-while-revalidate=30',
1314
},
1415
})
1516
}

freebuff/web/src/app/home-client.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { CopyButton } from '@/components/copy-button'
1313
import { HeroGrid } from '@/components/hero-grid'
1414
import { Icons } from '@/components/icons'
1515
import { cn } from '@/lib/utils'
16+
import { CompactLiveStats } from './live/live-client'
1617

1718
const INSTALL_COMMAND = 'npm install -g freebuff'
1819

@@ -567,6 +568,8 @@ export default function HomeClient() {
567568
</div>
568569
</div>
569570
</div>
571+
572+
<CompactLiveStats />
570573
</div>
571574
)
572575
}

freebuff/web/src/app/live/live-client.tsx

Lines changed: 86 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,23 @@ import Link from 'next/link'
77
import { useEffect, useState } from 'react'
88

99
import { CopyButton } from '@/components/copy-button'
10+
import { cn } from '@/lib/utils'
1011

1112
import { COUNTRY_POINTS, WORLD_LAND_PATHS } from './world-map-data'
1213

1314
import type { FreebuffLiveStats } from '@/server/live-stats'
1415
import type { LucideIcon } from 'lucide-react'
1516

1617
const INSTALL_COMMAND = 'npm install -g freebuff'
17-
const POLL_MS = 15_000
18+
const POLL_MS = 60_000
1819
const MAP_SIZE = { width: 1000, height: 520 }
1920
const REGION_NAMES = new Intl.DisplayNames(['en'], { type: 'region' })
21+
const EMPTY_LIVE_STATS: FreebuffLiveStats = {
22+
totalLiveUsers: 0,
23+
countries: [],
24+
models: [],
25+
generatedAt: '1970-01-01T00:00:00.000Z',
26+
}
2027
type CountryPoint = readonly [lat: number, lon: number]
2128
type PlottedCountry = FreebuffLiveStats['countries'][number] & {
2229
point: CountryPoint
@@ -106,7 +113,10 @@ function isPlottedCountry(
106113
return country !== null
107114
}
108115

109-
function useLiveStats(initialStats: FreebuffLiveStats) {
116+
function useLiveStats(
117+
initialStats: FreebuffLiveStats,
118+
options: { refreshOnMount?: boolean } = {},
119+
) {
110120
const [stats, setStats] = useState(initialStats)
111121

112122
useEffect(() => {
@@ -123,12 +133,16 @@ function useLiveStats(initialStats: FreebuffLiveStats) {
123133
}
124134
}
125135

136+
if (options.refreshOnMount) {
137+
void refresh()
138+
}
139+
126140
const interval = window.setInterval(refresh, POLL_MS)
127141
return () => {
128142
isMounted = false
129143
window.clearInterval(interval)
130144
}
131-
}, [])
145+
}, [options.refreshOnMount])
132146

133147
return stats
134148
}
@@ -186,7 +200,15 @@ function EmptyState({ children }: { children: React.ReactNode }) {
186200
)
187201
}
188202

189-
function WorldMap({ stats }: { stats: FreebuffLiveStats }) {
203+
function WorldMap({
204+
stats,
205+
compact = false,
206+
isLoading = false,
207+
}: {
208+
stats: FreebuffLiveStats
209+
compact?: boolean
210+
isLoading?: boolean
211+
}) {
190212
const maxCount = Math.max(1, ...stats.countries.map((row) => row.count))
191213
const plottedCountries = stats.countries
192214
.map((country) => {
@@ -199,20 +221,25 @@ function WorldMap({ stats }: { stats: FreebuffLiveStats }) {
199221
return (
200222
<section className="relative self-start overflow-hidden rounded-lg border border-white/10 bg-[#020807] shadow-[0_24px_90px_rgba(0,0,0,0.34),inset_0_1px_0_rgba(255,255,255,0.05)]">
201223
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_50%_28%,rgba(34,211,238,0.14),transparent_38%),linear-gradient(180deg,rgba(124,255,63,0.04),rgba(0,0,0,0.2))]" />
202-
<div className="pointer-events-none absolute left-4 top-4 z-10 rounded-md border border-white/10 bg-black/45 px-3 py-2 backdrop-blur md:left-5 md:top-5">
203-
<div className="font-mono text-[10px] uppercase tracking-[0.22em] text-white/45">
204-
Active countries
205-
</div>
206-
<div className="mt-1 text-2xl font-serif leading-none text-white">
207-
{stats.countries.length.toLocaleString()}
224+
{!compact && (
225+
<div className="pointer-events-none absolute left-4 top-4 z-10 rounded-md border border-white/10 bg-black/45 px-3 py-2 backdrop-blur md:left-5 md:top-5">
226+
<div className="font-mono text-[10px] uppercase tracking-[0.22em] text-white/45">
227+
Active countries
228+
</div>
229+
<div className="mt-1 text-2xl font-serif leading-none text-white">
230+
{stats.countries.length.toLocaleString()}
231+
</div>
208232
</div>
209-
</div>
233+
)}
210234

211235
<svg
212236
viewBox={`0 0 ${MAP_SIZE.width} ${MAP_SIZE.height}`}
213237
role="img"
214238
aria-label="World map of live Freebuff users by country"
215-
className="relative h-[300px] w-full md:h-[520px]"
239+
className={cn(
240+
'relative w-full',
241+
compact ? 'h-[230px] md:h-[380px]' : 'h-[300px] md:h-[520px]',
242+
)}
216243
>
217244
<defs>
218245
<pattern
@@ -355,15 +382,20 @@ function WorldMap({ stats }: { stats: FreebuffLiveStats }) {
355382
})}
356383
</svg>
357384

358-
{plottedCountries.length === 0 && (
385+
{plottedCountries.length === 0 && isLoading && (
386+
<div className="absolute inset-x-6 top-1/2 mx-auto max-w-sm -translate-y-1/2 rounded-lg border border-white/10 bg-black/55 px-5 py-4 text-center backdrop-blur">
387+
<div className="font-serif text-2xl text-white">Loading live map</div>
388+
</div>
389+
)}
390+
{plottedCountries.length === 0 && !isLoading && (
359391
<div className="absolute inset-x-6 top-1/2 mx-auto max-w-sm -translate-y-1/2 rounded-lg border border-white/10 bg-black/55 px-5 py-4 text-center backdrop-blur">
360392
<div className="font-serif text-2xl text-white">Standing by</div>
361393
<div className="mt-1 text-sm text-white/50">
362394
Live sessions will appear here as users start Freebuff.
363395
</div>
364396
</div>
365397
)}
366-
{unplottedCount > 0 && (
398+
{!compact && unplottedCount > 0 && (
367399
<div className="absolute bottom-4 right-4 rounded-md border border-white/10 bg-black/45 px-3 py-2 text-xs text-white/48 backdrop-blur">
368400
{unplottedCount} region{unplottedCount === 1 ? '' : 's'} listed
369401
off-map
@@ -373,6 +405,46 @@ function WorldMap({ stats }: { stats: FreebuffLiveStats }) {
373405
)
374406
}
375407

408+
export function CompactLiveStats({
409+
initialStats = EMPTY_LIVE_STATS,
410+
}: {
411+
initialStats?: FreebuffLiveStats
412+
}) {
413+
const stats = useLiveStats(initialStats, { refreshOnMount: true })
414+
const isLoading = stats.generatedAt === EMPTY_LIVE_STATS.generatedAt
415+
416+
return (
417+
<section className="relative overflow-hidden bg-black py-14 md:py-20">
418+
<div className="absolute inset-0 bg-[linear-gradient(rgba(124,255,63,0.04)_1px,transparent_1px),linear-gradient(90deg,rgba(34,211,238,0.035)_1px,transparent_1px)] bg-[size:56px_56px]" />
419+
<div className="relative container mx-auto px-4">
420+
<div className="mb-6 flex flex-col gap-3 md:mb-8 md:flex-row md:items-end md:justify-between">
421+
<div>
422+
<div className="flex items-center gap-3">
423+
<motion.span
424+
className="h-2.5 w-2.5 rounded-full bg-acid-matrix shadow-[0_0_20px_rgba(124,255,63,0.95)]"
425+
animate={{ opacity: [0.45, 1, 0.45], scale: [0.8, 1.2, 0.8] }}
426+
transition={{
427+
duration: 1.9,
428+
repeat: Infinity,
429+
ease: 'easeInOut',
430+
}}
431+
/>
432+
<span className="font-mono text-xs uppercase tracking-[0.22em] text-white/48">
433+
Active users
434+
</span>
435+
</div>
436+
<div className="mt-2 font-mono text-5xl font-medium leading-none text-acid-matrix neon-text md:text-7xl">
437+
{isLoading ? '...' : stats.totalLiveUsers.toLocaleString()}
438+
</div>
439+
</div>
440+
</div>
441+
442+
<WorldMap stats={stats} compact isLoading={isLoading} />
443+
</div>
444+
</section>
445+
)
446+
}
447+
376448
function ModelBars({ stats }: { stats: FreebuffLiveStats }) {
377449
const maxCount = Math.max(1, ...stats.models.map((model) => model.count))
378450

freebuff/web/src/app/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { siteConfig } from '@/lib/constant'
88

99
export async function generateMetadata(): Promise<Metadata> {
1010
const canonicalUrl = env.NEXT_PUBLIC_CODEBUFF_APP_URL
11-
const title = "Freebuff — the free coding agent"
11+
const title = 'Freebuff — the free coding agent'
1212
const description = siteConfig.description
1313

1414
return {

freebuff/web/src/server/live-stats.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ export interface FreebuffLiveStats {
2121
generatedAt: string
2222
}
2323

24+
const LIVE_STATS_CACHE_MS = 60_000
25+
let cachedLiveStats: {
26+
expiresAt: number
27+
stats: FreebuffLiveStats
28+
} | null = null
29+
2430
const MODEL_LABELS = Object.fromEntries(
2531
SUPPORTED_FREEBUFF_MODELS.map(
2632
(model) => [model.id, model.displayName] as const,
@@ -48,24 +54,32 @@ function sortCounts<T extends { count: number }>(rows: T[]): T[] {
4854
}
4955

5056
export async function getFreebuffLiveStats(
51-
now = new Date(),
57+
now?: Date,
58+
options: { cache?: boolean } = {},
5259
): Promise<FreebuffLiveStats> {
60+
const useCache = options.cache ?? now === undefined
61+
const requestTime = now ?? new Date()
62+
63+
if (useCache && cachedLiveStats && cachedLiveStats.expiresAt > Date.now()) {
64+
return cachedLiveStats.stats
65+
}
66+
5367
const [countryRows, modelRows] = await Promise.all([
5468
db
5569
.select({
5670
countryCode: schema.freeSession.country_code,
5771
count: count(),
5872
})
5973
.from(schema.freeSession)
60-
.where(liveSessionWhere(now))
74+
.where(liveSessionWhere(requestTime))
6175
.groupBy(schema.freeSession.country_code),
6276
db
6377
.select({
6478
modelId: schema.freeSession.model,
6579
count: count(),
6680
})
6781
.from(schema.freeSession)
68-
.where(liveSessionWhere(now))
82+
.where(liveSessionWhere(requestTime))
6983
.groupBy(schema.freeSession.model),
7084
])
7185

@@ -84,10 +98,19 @@ export async function getFreebuffLiveStats(
8498
})),
8599
)
86100

87-
return {
101+
const stats = {
88102
totalLiveUsers: models.reduce((sum, row) => sum + row.count, 0),
89103
countries,
90104
models,
91-
generatedAt: now.toISOString(),
105+
generatedAt: requestTime.toISOString(),
92106
}
107+
108+
if (useCache) {
109+
cachedLiveStats = {
110+
expiresAt: Date.now() + LIVE_STATS_CACHE_MS,
111+
stats,
112+
}
113+
}
114+
115+
return stats
93116
}

0 commit comments

Comments
 (0)