"use client" import { cn } from "@lib/utils" import { ArrowRight, Loader2 } from "lucide-react" import { useCallback, useEffect, useState } from "react" import { toast } from "sonner" import { useTrialStatus } from "@/hooks/use-trial-status" import { dmSans125ClassName } from "@/lib/fonts" import { useViewMode } from "@/lib/view-mode-context" import { brainConnectorIcon, SlackMark } from "../brain-connector-icons" // Preferred ordering for the dashboard; only unconnected apps are surfaced. const FEATURED_SLUGS: readonly string[] = ["linear", "granola", "sentry"] // Example prompts on the right card — can include apps not shown on the left. const PREVIEW_PROMPT_SLUGS = ["linear", "granola", "github", "sentry"] as const const BACKEND = process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai" const MCP_BASE = `${BACKEND}/brain/mcp-connections` export 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", } export const tileStyle = { boxShadow: "0px 1px 2px 0px rgba(0,43,87,0.1), inset 0px 0px 0px 1px rgba(43,49,67,0.08), inset 0px 1px 1px 0px rgba(0,0,0,0.08), inset 0px 2px 4px 0px rgba(0,0,0,0.02)", } type AuthType = "oauth" | "static" | "none" type CatalogEntry = { slug: string name: string category: string authType: AuthType tokenHint?: string } type ConnRow = { serverSlug: string status: "active" | "pending" | "error" userId: string | null } // Example asks that connecting each app unlocks for the Slack agent. const AGENT_PROMPTS: Record = { linear: "What's blocking the sprint?", github: "Summarize the open PRs on auth", sentry: "Any new errors since the deploy?", notion: "Find our launch checklist", posthog: "How's activation trending this week?", plain: "What are customers asking about?", granola: "Recap yesterday's standup", } function titleCase(s: string) { return s.replace(/\b\w/g, (c) => c.toUpperCase()) } export function useConnectionsBoard() { const [catalog, setCatalog] = useState(null) const [rows, setRows] = useState([]) const [slack, setSlack] = useState<{ connected: boolean teamName: string | null } | null>(null) const [busy, setBusy] = useState(null) const load = useCallback(async () => { try { const [cat, conn, s] = await Promise.all([ fetch(`${MCP_BASE}/catalog`, { credentials: "include" }), fetch(`${MCP_BASE}/`, { credentials: "include" }), fetch(`${BACKEND}/brain/slack/status`, { credentials: "include" }), ]) // Parse each response independently so one bad payload can't strand the others. try { if (cat.ok) { const data: { catalog?: CatalogEntry[] } = await cat.json() setCatalog(data.catalog ?? []) } else setCatalog([]) } catch { setCatalog([]) } try { if (conn.ok) { const data: { connections?: ConnRow[] } = await conn.json() setRows(data.connections ?? []) } else setRows([]) } catch { setRows([]) } try { if (s.ok) setSlack(await s.json()) } catch {} } catch { setCatalog([]) setRows([]) } }, []) useEffect(() => { void load() const onFocus = () => void load() window.addEventListener("focus", onFocus) return () => window.removeEventListener("focus", onFocus) }, [load]) const isConnected = (slug: string) => rows.some((r) => r.serverSlug === slug && r.status === "active") const connect = async (entry: CatalogEntry) => { setBusy(entry.slug) try { if (entry.authType === "static") { const token = window.prompt( `Paste a token for ${entry.name}.${entry.tokenHint ? `\n${entry.tokenHint}` : ""}`, ) if (!token) return const res = await fetch(`${MCP_BASE}/${entry.slug}/connect-static`, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify({ token, shared: false }), }) if (!res.ok) { toast.error("Couldn't connect.") return } toast.success(`${entry.name} connected.`) await load() return } const res = await fetch(`${MCP_BASE}/${entry.slug}/connect`, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify({ shared: false, redirectUrl: window.location.href, }), }) if (!res.ok) { toast.error("Couldn't start the connection.") return } const data: { authUrl?: string; ok?: boolean } = await res.json() if (data.authUrl) window.open(data.authUrl, "_blank", "noopener") else if (data.ok) { toast.success(`${entry.name} connected.`) await load() } else toast.error("Couldn't start the connection.") } catch { toast.error("Couldn't start the connection.") } finally { setBusy(null) } } const apps = catalog ?? [] const loading = catalog === null const unconnected = apps.filter((a) => !isConnected(a.slug)) const featured = [ ...FEATURED_SLUGS.map((slug) => unconnected.find((a) => a.slug === slug), ).filter((a): a is CatalogEntry => Boolean(a)), ...unconnected.filter((a) => !FEATURED_SLUGS.includes(a.slug)), ].slice(0, 3) const overflow = unconnected.filter((a) => !featured.includes(a)) const previewApps = PREVIEW_PROMPT_SLUGS.map((slug) => apps.find((a) => a.slug === slug), ).filter((a): a is CatalogEntry => Boolean(a)) const connectedCount = apps.filter((a) => isConnected(a.slug)).length const showBoard = loading || unconnected.length > 0 return { slack, loading, busy, featured, overflow, previewApps, connectedCount, showBoard, isConnected, connect, } } export type ConnectionsBoardState = ReturnType export const CONNECT_TOOLS_CARD_ID = "connect-tools" export function ConnectToolsCard({ board }: { board: ConnectionsBoardState }) { const { setViewMode } = useViewMode() const { loading, featured, overflow, busy, isConnected, connect } = board const { needsSetup } = useTrialStatus() return (

Connect your tools

{needsSetup ? "Starts with your trial." : "Give your Slack agent live access to the apps your team already uses."}

{loading ? ( Array.from({ length: 3 }).map((_, i) => ( )) ) : ( <> {featured.map((entry, i) => ( connect(entry)} showDivider={i < featured.length - 1 || overflow.length > 0} /> ))} {overflow.length > 0 && ( a.name)} onClick={() => void setViewMode("configure")} /> )} )}
) } export function AskInSlackCard({ board }: { board: ConnectionsBoardState }) { const { previewApps, isConnected, connectedCount } = board const { needsSetup } = useTrialStatus() const prompts = previewApps .filter((a) => AGENT_PROMPTS[a.slug]) .slice(0, 6) .map((a) => ({ slug: a.slug, name: a.name, prompt: AGENT_PROMPTS[a.slug], connected: isConnected(a.slug), })) return (

Ask in Slack

{needsSetup ? "Starts with your trial." : connectedCount > 0 ? "Things your agent can answer now:" : "Connect a tool and your agent can answer:"}

{prompts.map((p, i) => (
{brainConnectorIcon(p.slug, p.name, "size-4")}

"{p.prompt}"

))}
) } function AppTile({ icon, name, subtitle, connected, busy, onConnect, showDivider = false, }: { icon: React.ReactNode name: string subtitle: string connected: boolean busy: boolean onConnect: () => void showDivider?: boolean }) { return (
{icon}

{name}

{subtitle}

{connected ? ( Connected ) : ( )}
) } function MoreTile({ count, names, onClick, }: { count: number names: string[] onClick: () => void }) { return ( ) } function TileSkeleton({ showDivider = false }: { showDivider?: boolean }) { return (
) }