import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { AlertTriangle, Check, Coffee, DollarSign, Heart, Home, Layers, LoaderCircle, Maximize2, MessageCircle, Minimize2, Route, SendHorizontal, Shield, SlidersHorizontal, Sparkles, Square, TrainFront, TrendingUp, Users, Volume2, X, } from "lucide-react"; import MapCanvas from "./components/MapCanvas"; import { DIMENSIONS, type DimensionKey, type LayerKey } from "./data/neighborhoods"; import { runAgentBackend, type AgentBackendRun } from "./lib/agentApi"; import { preloadKokoro, synthesizeKokoro } from "./lib/tts"; import { agentDimension, cityAgents, colorForScore, consensus, defaultPrompt, getAgentFinding, getAgentThinking, initialAgentStates, layerScore, parsePrompt, queuedAgentStates, rankNeighborhoods, tagFor, type AgentState, type ParsedPrompt, type RankedNeighborhood, } from "./lib/scoring"; type NavMode = "overview" | "agents" | "commute" | "affordability" | "lifestyle" | "growth"; type Phase = "done" | "running"; type RunStyle = "cascade" | "radar"; type ResearchTour = { runId: number; neighborhoodIds: string[]; }; type ResearchFact = NonNullable["facts"]>[number]; type SummaryAudioState = "idle" | "generating" | "ready" | "playing"; type SummaryAudioCacheEntry = { key: string; text: string; url: string | null; status: "generating" | "ready" | "error"; promise: Promise | null; lastUsed: number; }; const initialParsed = parsePrompt(defaultPrompt); const initialRanked = rankNeighborhoods(initialParsed); const navItems: Array<{ id: NavMode; label: string; icon: typeof Layers; }> = [ { id: "overview", label: "Overview", icon: Layers }, { id: "agents", label: "Agents", icon: Users }, { id: "commute", label: "Commute", icon: TrainFront }, { id: "affordability", label: "Affordability", icon: DollarSign }, { id: "lifestyle", label: "Lifestyle", icon: Coffee }, { id: "growth", label: "Growth", icon: TrendingUp }, ]; const layerLabels: Record = { overall: "Match", safety: "Safety", rent: "Affordability", commute: "Commute", amenities: "Amenities", transit: "Transit", lifestyle: "Lifestyle", growth: "Growth", }; const RESEARCH_TOUR_LIMIT = 5; const RESEARCH_TOUR_OVERVIEW_MS = 0; // Must match TOUR_DWELL_MS in MapCanvas: the camera dwells on each area while it is researched. const RESEARCH_TOUR_VISIT_MS = 5400; const RESEARCH_TOUR_SETTLE_MS = 520; const SUMMARY_AUDIO_CACHE_LIMIT = 10; const summaryAudioCache = new Map(); // Quick-start chips above the composer. They are just shortcuts — the real entry point is // whatever the user types in the box. const SUGGESTED_PROMPTS: Array<{ label: string; prompt: string }> = [ { label: "Safe & walkable near Union", prompt: "I make $80k, work near Union, want safe streets, cafes, under 40 min commute, max rent $2,200.", }, { label: "Family-friendly with parks", prompt: "Family-friendly area with good schools and parks, under 45 min to downtown, max rent $2,800.", }, ]; export default function App() { const [prompt, setPrompt] = useState(""); const [hasRun, setHasRun] = useState(false); const [parsed, setParsed] = useState(initialParsed); const [ranked, setRanked] = useState(initialRanked); const [selectedId, setSelectedId] = useState(initialRanked[0].id); const [hoverId, setHoverId] = useState(null); const [phase, setPhase] = useState("done"); const [consensusActive, setConsensusActive] = useState(false); const [agents, setAgents] = useState(() => initialAgentStates(initialRanked[0])); const [runStyle, setRunStyle] = useState("cascade"); const [navMode, setNavMode] = useState("overview"); const [agentPanelOpen, setAgentPanelOpen] = useState(false); const [signalLayers, setSignalLayers] = useState>>({ safety: false, rent: false, commute: false, amenities: false, transit: false, }); const [bright, setBright] = useState(false); const [compareOpen, setCompareOpen] = useState(false); const [compare, setCompare] = useState([]); const [scoreT, setScoreT] = useState(1); const [webResearch, setWebResearch] = useState(null); const [recommendation, setRecommendation] = useState(null); const [researchTour, setResearchTour] = useState(null); const [tourFocusId, setTourFocusId] = useState(null); const [mapFocus, setMapFocus] = useState(false); const timersRef = useRef([]); const frameRef = useRef(null); const runSeqRef = useRef(0); useEffect(() => { return () => clearSchedules(); }, []); const selected = useMemo( () => ranked.find((neighborhood) => neighborhood.id === selectedId) ?? ranked[0], [ranked, selectedId], ); const activeLayer = activeLayerFromNav(navMode); const panelsVisible = hasRun || phase === "running"; const visibleRanked = panelsVisible ? ranked : []; const visibleSelectedId = panelsVisible ? selected.id : ""; const showRail = panelsVisible && (phase === "running" || agentPanelOpen); const tourFocusName = tourFocusId ? ranked.find((neighborhood) => neighborhood.id === tourFocusId)?.name : null; const activeChipLead = phase === "running" ? `Researching ${tourFocusName ?? "Toronto"}` : "Layer"; const activeChipDetail = phase === "running" ? `${layerLabels[activeLayer]} evidence` : `${layerLabels[activeLayer]}${activeLayer === "overall" ? " candidates" : ""}`; const runAgents = (promptText: string = prompt) => { if (phase === "running") return; const ask = promptText.trim(); if (!ask) return; const nextParsed = parsePrompt(ask); const nextRanked = rankNeighborhoods(nextParsed); const top = nextRanked[0]; const runId = runSeqRef.current + 1; const tourIds = researchTourIds(nextRanked); const tourPromise = waitForResearchTour(tourIds.length); runSeqRef.current = runId; const backendPromise = runAgentBackend(ask); clearSchedules(); // Warm the TTS model (off-thread) during research so summaries can be voiced the moment // the agent finishes — the download happens while the agents work. preloadKokoro(); // The prompt is in flight; clear the composer so the next ask starts from a blank box. setHasRun(true); setPrompt(""); setParsed(nextParsed); setRanked(nextRanked); setPhase("running"); setConsensusActive(false); setAgents(queuedAgentStates()); setScoreT(0); setWebResearch(null); setRecommendation(null); setResearchTour({ runId, neighborhoodIds: tourIds }); setTourFocusId(null); setAgentPanelOpen(true); setNavMode("agents"); const setAgent = (index: number, patch: Partial) => { setAgents((current) => { const next = current.slice(); next[index] = { ...next[index], ...patch }; return next; }); }; const scoreFor = (agent: AgentState) => agent.id === "recommendation" ? top.overall : top.dims[agentDimension(agent.id)]; if (runStyle === "cascade") { const step = 560; cityAgents.forEach((agent, index) => { const lines = getAgentThinking(agent.id, nextParsed); const offset = index * step; schedule(() => setAgent(index, { status: "scanning", lines: [lines[0]] }), offset + 60); schedule(() => setAgent(index, { lines: lines.slice(0, 2) }), offset + 250); schedule(() => setAgent(index, { status: "scoring", lines }), offset + 380); if (agent.id === "recommendation") { schedule(() => setConsensusActive(true), offset + 120); } else { schedule(() => setAgent(index, { status: "done", score: scoreFor({ ...agent, status: "done", lines: [], score: null }) }), offset + 500); } }); schedule(() => { setAgent(cityAgents.length - 1, { status: "done", score: top.overall, lines: getAgentThinking("recommendation", nextParsed), }); finishRun(backendPromise, tourPromise, runId, nextParsed, nextRanked, top); }, cityAgents.length * step + 260); return; } cityAgents.forEach((agent, index) => { const lines = getAgentThinking(agent.id, nextParsed); schedule(() => setAgent(index, { status: "scanning", lines: [lines[0]] }), 80 + index * 40); schedule(() => setAgent(index, { status: "scoring", lines: lines.slice(0, 2) }), 1100 + index * 40); }); schedule(() => setConsensusActive(true), 1850); schedule(() => { cityAgents.forEach((agent, index) => { setAgent(index, { status: "done", score: agent.id === "recommendation" ? top.overall : top.dims[agentDimension(agent.id)], lines: getAgentThinking(agent.id, nextParsed), }); }); }, 2450); schedule(() => finishRun(backendPromise, tourPromise, runId, nextParsed, nextRanked, top), 2700); }; const finishRun = ( backendPromise: Promise, tourPromise: Promise, runId: number, fallbackParsed: ParsedPrompt, fallbackRanked: RankedNeighborhood[], fallbackTop: RankedNeighborhood, ) => { void (async () => { const [backend] = await Promise.all([backendPromise, tourPromise]); if (runSeqRef.current !== runId) return; const nextParsed = isParsedPrompt(backend?.parsed) ? backend.parsed : fallbackParsed; const nextRanked = normalizeBackendRanked(backend?.ranked, fallbackRanked); const top = nextRanked.find((neighborhood) => neighborhood.id === backend?.selectedId) ?? nextRanked[0] ?? fallbackTop; if (backend?.agents?.length) { setAgents(agentStatesFromBackend(backend.agents, top, nextParsed)); } else { setAgents(initialAgentStates(top)); } setWebResearch(backend?.webResearch ?? null); setRecommendation(backend?.recommendation ?? null); commitRun(nextParsed, nextRanked, top); })(); }; const commitRun = ( nextParsed: ParsedPrompt, nextRanked: RankedNeighborhood[], top: RankedNeighborhood, ) => { setParsed(nextParsed); setRanked(nextRanked); setSelectedId(top.id); setPhase("done"); setConsensusActive(false); setResearchTour(null); setTourFocusId(null); setNavMode("overview"); setAgentPanelOpen(false); animateScores(); }; const schedule = (fn: () => void, delay: number) => { timersRef.current.push(window.setTimeout(fn, delay)); }; function clearSchedules() { timersRef.current.forEach(window.clearTimeout); timersRef.current = []; if (frameRef.current) window.cancelAnimationFrame(frameRef.current); frameRef.current = null; } function animateScores() { const start = performance.now(); const duration = 850; const tick = (now: number) => { const progress = Math.min(1, (now - start) / duration); setScoreT(1 - Math.pow(1 - progress, 3)); if (progress < 1) frameRef.current = window.requestAnimationFrame(tick); }; frameRef.current = window.requestAnimationFrame(tick); } function waitForResearchTour(stopCount: number) { const duration = RESEARCH_TOUR_OVERVIEW_MS + Math.max(1, stopCount) * RESEARCH_TOUR_VISIT_MS + RESEARCH_TOUR_SETTLE_MS; return new Promise((resolve) => { window.setTimeout(resolve, duration); }); } const selectNeighborhood = (id: string) => { setSelectedId(id); if (phase !== "running") setAgentPanelOpen(false); }; const handleTourStep = useCallback((id: string | null) => { setTourFocusId(id); if (id) setSelectedId(id); }, []); const selectNav = (mode: NavMode) => { setNavMode(mode); setAgentPanelOpen(mode === "agents"); }; const toggleSignalLayer = (layer: LayerKey) => { setSignalLayers((current) => ({ ...current, [layer]: !current[layer] })); }; const toggleCompare = (id: string) => { setCompare((current) => current.includes(id) ? current.filter((item) => item !== id) : [...current, id], ); }; return (
setBright((current) => !current)} />
6ixPulse Find your Toronto neighbourhood, agent-researched
Ask 6ixPulse
{SUGGESTED_PROMPTS.map((suggestion) => ( ))}