"use client" import { $fetch } from "@lib/api" import { useAuth } from "@lib/auth-context" import { cn } from "@lib/utils" import { useQuery } from "@tanstack/react-query" import { ArrowRight, Check, FileText, Loader2, UserPlus } from "lucide-react" import { useQueryState } from "nuqs" import { useSettingsModal } from "@/components/settings/settings-modal" import { useBrainTrial } from "@/hooks/use-brain-trial" import { useTrialStatus } from "@/hooks/use-trial-status" import { dmSans125ClassName } from "@/lib/fonts" import { useViewMode } from "@/lib/view-mode-context" import { AskInSlackCard, CONNECT_TOOLS_CARD_ID, ConnectToolsCard, useConnectionsBoard, } from "./connections-board" const BACKEND = process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai" const cardStyle = { boxShadow: "0 2.842px 14.211px 0 rgba(0, 0, 0, 0.25), 0.711px 0.711px 0.711px 0 rgba(255, 255, 255, 0.10) inset", } type RecentDoc = { id?: string title?: string | null createdAt?: string | Date | null updatedAt?: string | Date | null } type RolloutOverview = { status: "running" | "done" | "failed" discovered: number joined: number ready: number introduced: number failed: number } type BrainOverview = { research: { status: string | null } slack: { connected: boolean teamName: string | null rollout: RolloutOverview | null } connections: { apps: number } members: { count: number } } function useBrainOverview() { const { user, org } = useAuth() const enabled = !!user && !!org?.id const docs = useQuery({ queryKey: ["brain-recents", org?.id], queryFn: async () => { const res = await $fetch("@post/documents/documents", { body: { page: 1, limit: 6, sort: "createdAt", order: "desc", containerTags: [], }, disableValidation: true, }) if (res.error) throw new Error(res.error?.message) return res.data as unknown as { documents?: RecentDoc[] pagination?: { totalItems?: number } } }, staleTime: 60_000, enabled, }) const lastUpdated = useQuery({ queryKey: ["brain-last-updated", org?.id], queryFn: async () => { const res = await $fetch("@post/documents/documents", { body: { page: 1, limit: 1, sort: "updatedAt", order: "desc", containerTags: [], }, disableValidation: true, }) if (res.error) return null const docs = (res.data as { documents?: RecentDoc[] })?.documents return docs?.[0]?.updatedAt ?? null }, staleTime: 60_000, enabled, }) const connectors = useQuery({ queryKey: ["brain-home", "connectors"], queryFn: async () => { const res = await $fetch("@post/connections/list", { body: { containerTags: [] }, }) if (res.error) return [] as Array<{ provider?: string }> return (res.data ?? []) as Array<{ provider?: string }> }, staleTime: 30_000, enabled, }) const mcp = useQuery({ queryKey: ["mcp-status"], queryFn: async () => { const res = await $fetch("@get/mcp/has-login") if (res.error) return false return Boolean((res.data as { previousLogin?: boolean })?.previousLogin) }, staleTime: 60_000, enabled, }) const overview = useQuery({ queryKey: ["brain-overview", org?.id], queryFn: async (): Promise => { const res = await fetch(`${BACKEND}/brain/overview`, { credentials: "include", }) if (!res.ok) return null return (await res.json()) as BrainOverview }, staleTime: 30_000, enabled, }) const memoriesCount = docs.data?.pagination?.totalItems ?? 0 const slackConnected = overview.data?.slack.connected ?? false const appsCount = (overview.data?.connections.apps ?? 0) + (connectors.data?.length ?? 0) const connectedCount = appsCount + (slackConnected ? 1 : 0) const currentRole = org?.members ?.find((m) => m.userId === user?.id) ?.role?.toLowerCase() return { loading: docs.isPending, recentDocs: docs.data?.documents ?? [], lastUpdatedAt: lastUpdated.data ?? null, memoriesCount, connectedCount, membersCount: overview.data?.members.count ?? org?.members?.length ?? 0, canInvite: currentRole === "owner" || currentRole === "admin", hasSource: connectedCount > 0, hasAgent: mcp.data ?? false, hasMemory: memoriesCount > 0, hasApps: appsCount > 0, slackConnected, researchStatus: overview.data?.research.status ?? null, rollout: overview.data?.slack.rollout ?? null, } } export function BrainHomeView() { const o = useBrainOverview() const trial = useBrainTrial() const board = useConnectionsBoard() // Rows with no reported state (older orgs, pre-Slack) don't count or render. const milestones = [ ...(o.researchStatus != null ? [o.researchStatus === "done"] : []), o.slackConnected, ...(o.rollout != null ? [o.rollout.status === "done"] : []), o.hasApps, o.membersCount > 1, o.hasMemory, ] const milestonesDone = milestones.filter(Boolean).length const milestonesTotal = milestones.length const showTimeline = !o.loading && (trial.state !== "none" || milestonesDone < milestonesTotal) return (
{board.showBoard && }
{showTimeline && ( 1} hasMemory={o.hasMemory} canInvite={o.canInvite} toolsCardVisible={board.showBoard} /> )}
) } function StatsRow({ memories, connected, members, canInvite, setupDone, setupTotal, lastUpdatedAt, }: { memories: number connected: number members: number canInvite: boolean setupDone: number setupTotal: number lastUpdatedAt: string | Date | null }) { const { openSettings } = useSettingsModal() const [, setInvite] = useQueryState("invite") const onInvite = () => { setInvite("1") openSettings("account") } const tiles: { label: string value: string action?: React.ReactNode }[] = [ { label: "Memories", value: memories.toLocaleString() }, { label: "Connected sources", value: String(connected) }, { label: "Active members", value: String(members), action: canInvite ? ( ) : undefined, }, setupDone < setupTotal ? { label: "Setup", value: `${setupDone}/${setupTotal}` } : { label: "Last updated", value: formatWhen(lastUpdatedAt) || "—", }, ] return (
{tiles.map((t, index) => (

{t.action}

{t.value}

{index === 2 && canInvite && ( )}
))}
) } function MobileStatLabel({ label }: { label: string }) { const mobile = label === "Connected sources" ? "Sources" : label === "Active members" ? "Members" : label return ( <> {mobile} {label} ) } function RecentMemories({ docs, loading, }: { docs: RecentDoc[] loading: boolean }) { return (

Recent memories

{loading ? (
Loading…
) : docs.length === 0 ? (

No memories yet

Connect a source or ask your brain below — what you save shows up here.

) : ( )}
) } function TrialStrip() { const trial = useBrainTrial() const { openSettings } = useSettingsModal() if (trial.state === "ended") { return (

Trial ended · your brain is paused

) } if (trial.state !== "trialing" || !trial.startedAtMs || !trial.endsAtMs) { return null } const DAY_MS = 24 * 60 * 60 * 1000 const totalDays = Math.max( 1, Math.round((trial.endsAtMs - trial.startedAtMs) / DAY_MS), ) const days = trial.daysRemaining ?? 0 const dayNum = Math.min(totalDays, Math.max(1, totalDays - days + 1)) const pct = Math.min(100, Math.max(4, (dayNum / totalDays) * 100)) return ( ) } function BrainTimeline({ researchStatus, slackConnected, rollout, hasApps, invited, hasMemory, canInvite, toolsCardVisible, }: { researchStatus: string | null slackConnected: boolean rollout: RolloutOverview | null hasApps: boolean invited: boolean hasMemory: boolean canInvite: boolean toolsCardVisible: boolean }) { const { needsSetup } = useTrialStatus() const { openSettings } = useSettingsModal() const { setViewMode } = useViewMode() const [, setInvite] = useQueryState("invite") const onInvite = () => { setInvite("1") openSettings("account") } const onSetUpApps = () => { const card = document.getElementById(CONNECT_TOOLS_CARD_ID) if (toolsCardVisible && card) { card.scrollIntoView({ behavior: "smooth", block: "center" }) } else { void setViewMode("configure") } } const researching = researchStatus === "queued" || researchStatus === "running" type Step = { done: boolean busy?: boolean title: string hint?: string action?: { label: string; onClick?: () => void; href?: string } } // Rows with unreported state are omitted rather than shown as never-started. const steps: Step[] = [ ...(researchStatus != null ? [ { done: researchStatus === "done", busy: researching, title: researching ? "Researching your company…" : "Company research", }, ] : []), { done: slackConnected, title: slackConnected ? "Slack connected" : "Connect Slack", hint: slackConnected ? undefined : needsSetup ? "Starts with your trial." : "Ask your brain from any channel.", action: slackConnected || needsSetup ? undefined : { label: "Add", href: `${BACKEND}/brain/slack/oauth/install` }, }, ...(rollout != null ? [ { done: rollout.status === "done", busy: rollout.status === "running", title: rollout.status === "running" ? `Learning from channels… ${rollout.ready}/${rollout.discovered} ready` : rollout.status === "done" ? `Learning from channels · ${rollout.ready} ready` : "Learning from channels", }, ] : []), { done: hasApps, title: "Connect apps", hint: hasApps ? undefined : "Linear, Notion, GitHub and more.", action: hasApps ? undefined : { label: "Set up", onClick: onSetUpApps }, }, { done: invited, title: "Invite teammates", hint: invited ? undefined : "Multiply what the brain remembers.", action: invited || !canInvite ? undefined : { label: "Invite", onClick: onInvite }, }, { done: hasMemory, title: "First memories captured", hint: hasMemory ? undefined : "Save a doc, or ask your brain below.", }, ] return (

Your Company Brain

How far you've come.

    {steps.map((step) => (
  • {step.done ? ( ) : step.busy ? ( ) : null}

    {step.title}

    {!step.done && step.action && (step.action.href ? ( {step.action.label} ) : ( ))}
    {!step.done && step.hint && (

    {step.hint}

    )}
  • ))}
) } function formatWhen(value?: string | Date | null): string { if (!value) return "" const d = new Date(value) if (Number.isNaN(d.getTime())) return "" const min = Math.round((Date.now() - d.getTime()) / 60000) if (min < 1) return "just now" if (min < 60) return `${min}m` const hr = Math.round(min / 60) if (hr < 24) return `${hr}h` const day = Math.round(hr / 24) if (day < 7) return `${day}d` return d.toLocaleDateString() }