diff --git a/apps/web/components/app-experience.tsx b/apps/web/components/app-experience.tsx index 259584ee..402fd6fd 100644 --- a/apps/web/components/app-experience.tsx +++ b/apps/web/components/app-experience.tsx @@ -20,6 +20,7 @@ import { useHasCompanyBrain } from "@/hooks/use-company-brain" import { MemoriesGrid } from "@/components/memories-grid" import { GraphLayoutView } from "@/components/graph-layout-view" import { IntegrationsView, DetailWrapper } from "@/components/integrations-view" +import { ConfigureView } from "@/components/configure-view" import { MCPDetailView } from "@/components/mcp-modal/mcp-detail-view" import { XBookmarksDetailView } from "@/components/onboarding/x-bookmarks-detail-view" import { ChromeDetail } from "@/components/integrations/chrome-detail" @@ -698,6 +699,10 @@ export function AppExperience() { onOpenDocument={handleOpenDocument} /> + ) : viewMode === "configure" ? ( +
+ +
) : viewMode === "mcp" ? ( void setViewMode("integrations")} diff --git a/apps/web/components/brain-home/connections-board.tsx b/apps/web/components/brain-home/connections-board.tsx index 4f7be0b6..479c343d 100644 --- a/apps/web/components/brain-home/connections-board.tsx +++ b/apps/web/components/brain-home/connections-board.tsx @@ -5,11 +5,11 @@ import { ArrowRight, Loader2 } from "lucide-react" import { useCallback, useEffect, useState } from "react" import { toast } from "sonner" import { dmSans125ClassName } from "@/lib/fonts" -import { useSettingsModal } from "@/components/settings/settings-modal" +import { useViewMode } from "@/lib/view-mode-context" import { brainConnectorIcon, SlackMark } from "../brain-connector-icons" -// Apps surfaced on the dashboard; the rest live behind "More" in settings. -const FEATURED_SLUGS = ["linear", "granola", "sentry"] as const +// 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 @@ -155,76 +155,91 @@ export function ConnectionsBoard() { } } - const { openSettings } = useSettingsModal() + const { setViewMode } = useViewMode() const apps = catalog ?? [] const loading = catalog === null - const featured = FEATURED_SLUGS.map((slug) => - apps.find((a) => a.slug === slug), - ).filter((a): a is CatalogEntry => Boolean(a)) + 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 remainingCount = Math.max(apps.length - featured.length, 0) const connectedCount = apps.filter((a) => isConnected(a.slug)).length + const showBoard = loading || unconnected.length > 0 return (
{slack && !slack.connected && }
-
-
-

- Connect your tools -

-

- 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 || remainingCount > 0} - /> - ))} - {remainingCount > 0 && ( - openSettings("company-brain")} - /> + {showBoard ? ( +
+
+

- )} -

-
+ > + Connect your tools +

+

+ 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")} + /> + )} + + )} +
+
+ ) : null}
@@ -235,10 +250,12 @@ function AgentPreview({ apps, isConnected, connectedCount, + wide = false, }: { apps: CatalogEntry[] isConnected: (slug: string) => boolean connectedCount: number + wide?: boolean }) { const prompts = apps .filter((a) => AGENT_PROMPTS[a.slug]) @@ -252,7 +269,10 @@ function AgentPreview({ return (
@@ -359,7 +379,15 @@ function AppTile({ ) } -function MoreTile({ count, onClick }: { count: number; onClick: () => void }) { +function MoreTile({ + count, + names, + onClick, +}: { + count: number + names: string[] + onClick: () => void +}) { return (
diff --git a/apps/web/components/company-brain-header.tsx b/apps/web/components/company-brain-header.tsx index cde0cd77..12662a12 100644 --- a/apps/web/components/company-brain-header.tsx +++ b/apps/web/components/company-brain-header.tsx @@ -19,11 +19,11 @@ import { ExternalLink, Home, LifeBuoy, - Link2, LayoutGrid, MenuIcon, SearchIcon, Settings, + Settings2, UserPlus, ChevronRight, Sun, @@ -35,7 +35,7 @@ import { DomainLogo } from "@/components/onboarding-brain/step-about" import { FeedbackModal } from "@/components/feedback-modal" import { OrgPlanBadge, resolveOrgPlan } from "@/components/org-plan-badge" import { SlackMark } from "@/components/brain-connector-icons" -import { GraphIcon, IntegrationsIcon } from "@/components/integration-icons" +import { GraphIcon } from "@/components/integration-icons" import { SpaceSelector } from "@/components/space-selector" import { UserProfileMenu } from "@/components/user-profile-menu" import { useTokenUsage } from "@/hooks/use-token-usage" @@ -121,7 +121,6 @@ export function CompanyBrainHeader({ onOpenSearch }: CompanyBrainHeaderProps) { feedbackParam, ) const [, setInvite] = useQueryState("invite") - const [settingsTab] = useQueryState("settings") const { data: slackStatus } = useSlackStatus() const planByOrgId = new Map( @@ -140,10 +139,10 @@ export function CompanyBrainHeader({ onOpenSearch }: CompanyBrainHeaderProps) { ?.role?.toLowerCase() const canInvite = memberRole === "owner" || memberRole === "admin" - const isOverview = viewMode === "dashboard" && settingsTab !== "company-brain" + const isOverview = viewMode === "dashboard" const isGraph = viewMode === "graph" const isMemories = viewMode === "list" - const isConnections = settingsTab === "company-brain" + const isConfigure = viewMode === "configure" const slackConnected = slackStatus?.connected ?? false const selectOrg = useCallback( @@ -166,9 +165,9 @@ export function CompanyBrainHeader({ onOpenSearch }: CompanyBrainHeaderProps) { void setViewMode("list") }, [setViewMode]) - const goConnections = useCallback(() => { - openSettings("company-brain") - }, [openSettings]) + const goConfigure = useCallback(() => { + void setViewMode("configure") + }, [setViewMode]) const goIntegrations = useCallback(() => { void setViewMode("integrations") @@ -184,8 +183,8 @@ export function CompanyBrainHeader({ onOpenSearch }: CompanyBrainHeaderProps) { }, [setFeedbackOpen]) return ( -
-
+
+
)} -
+
{isMobile ? ( <> - - Connections + + Configure {slackConnected ? ( diff --git a/apps/web/components/configure-view.tsx b/apps/web/components/configure-view.tsx new file mode 100644 index 00000000..19d9928f --- /dev/null +++ b/apps/web/components/configure-view.tsx @@ -0,0 +1,129 @@ +"use client" + +import { cn } from "@lib/utils" +import { Blocks, CalendarClock, Cpu } from "lucide-react" +import { useState } from "react" +import CompanyBrainConnections from "@/components/settings/company-brain-connections" +import CompanyBrainModels from "@/components/settings/company-brain-models" +import Proactiveness from "@/components/settings/proactiveness" +import { ErrorBoundary } from "@/components/error-boundary" +import { dmSans125ClassName } from "@/lib/fonts" + +type ConfigureSection = "company-brain" | "models" | "automations" + +const SECTIONS: { + id: ConfigureSection + label: string + description: string + icon: typeof Blocks +}[] = [ + { + id: "company-brain", + label: "Integrations", + description: + "Connect the tools your brain works with. Your account covers your own actions and reads; workspace accounts are a shared fallback.", + icon: Blocks, + }, + { + id: "models", + label: "Models", + description: "Choose the models used for reasoning, triage, and research.", + icon: Cpu, + }, + { + id: "automations", + label: "Automations", + description: + "Read-only scheduled summaries posted to Slack channels or DMs. You manage the ones you create.", + icon: CalendarClock, + }, +] + +export function ConfigureView() { + const [activeSection, setActiveSection] = + useState("company-brain") + const active = SECTIONS.find((section) => section.id === activeSection) + if (!active) return null + + return ( +
+
+
+ + +
+
+

+ {active.label} +

+

+ {active.description} +

+
+ + + Something went wrong loading this section. +

+ } + > + {activeSection === "company-brain" ? ( + + ) : activeSection === "models" ? ( + + ) : ( + + )} +
+
+
+
+
+ ) +} diff --git a/apps/web/components/onboarding-brain/research-action-rail.tsx b/apps/web/components/onboarding-brain/research-action-rail.tsx index 9033d5e1..6804aacd 100644 --- a/apps/web/components/onboarding-brain/research-action-rail.tsx +++ b/apps/web/components/onboarding-brain/research-action-rail.tsx @@ -13,7 +13,7 @@ import { brainConnectorIcon, SlackMark, } from "@/components/brain-connector-icons" -import { useSettingsModal } from "@/components/settings/settings-modal" +import { useRouter } from "next/navigation" import { useResearchStatus } from "@/hooks/use-research-status" import { dmSans125ClassName } from "@/lib/fonts" import { cardSurfaceStyle, inputBevelStyle, inputClass } from "./step-about" @@ -83,7 +83,7 @@ export function ResearchActionRail({ onStatsChange?: (stats: ParallelSetupStats) => void }) { const { org } = useAuth() - const { openSettings } = useSettingsModal() + const router = useRouter() const { events } = useResearchStatus() const [catalog, setCatalog] = useState(null) const [rows, setRows] = useState([]) @@ -448,7 +448,7 @@ export function ResearchActionRail({ onConnect={connect} onBrowse={() => { pauseRotation() - openSettings("company-brain") + router.push("/?view=configure") }} /> )} diff --git a/apps/web/components/settings/company-brain-automations.tsx b/apps/web/components/settings/company-brain-automations.tsx index bea9e912..74f8e7b0 100644 --- a/apps/web/components/settings/company-brain-automations.tsx +++ b/apps/web/components/settings/company-brain-automations.tsx @@ -8,26 +8,19 @@ import { CalendarClock, ChevronDown, ChevronUp, - FilePlus2, FileText, GitPullRequest, Info, LifeBuoy, ListTodo, Loader2, - Lock, MessageCircleQuestion, Plus, + Radar, Trash2, } from "lucide-react" import { useRef, useState } from "react" import { toast } from "sonner" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@ui/components/dropdown-menu" import { Select, SelectContent, @@ -42,7 +35,6 @@ import { TooltipTrigger, } from "@ui/components/tooltip" import { useHasCompanyBrain } from "@/hooks/use-company-brain" -import { useOrgMemberRole } from "@/hooks/use-org-member-role" import { dmSans125ClassName } from "@/lib/fonts" const BACKEND = @@ -167,7 +159,7 @@ const PRESETS: Preset[] = [ { id: "standup", category: "team", - label: "Daily standup", + label: "Morning checkup", description: "Shipped work, decisions, blockers & open questions from the last 24h.", icon: CalendarClock, @@ -179,7 +171,7 @@ const PRESETS: Preset[] = [ { id: "weekly-recap", category: "team", - label: "Weekly team recap", + label: "Company progress", description: "Decisions, shipped work & unresolved threads from the past week.", icon: CalendarClock, @@ -236,6 +228,18 @@ const PRESETS: Preset[] = [ frequency: "daily", time: "09:00", }, + { + id: "competitor-check", + category: "product", + label: "Competitor check", + description: "What competitors shipped, announced, or changed this week.", + icon: Radar, + prompt: + "Check what our competitors shipped, announced, or changed recently — launches, pricing changes, and anything the team should react to.", + frequency: "weekly", + weekday: 1, + time: "09:00", + }, { id: "release-notes", category: "product", @@ -273,38 +277,12 @@ function sortPresets(connected: Set): Preset[] { return [...PRESETS].sort((a, b) => rank(a) - rank(b)) } -// Up to 3 presets spanning distinct categories (connection-preferred order). -function galleryPresets(sorted: Preset[]): Preset[] { - const seen = new Set() - const out: Preset[] = [] - for (const p of sorted) { - if (seen.has(p.category)) continue - seen.add(p.category) - out.push(p) - if (out.length === 3) break - } - return out -} - function cadenceLabel(p: Preset): string { if (p.frequency === "weekly") return `Weekly · ${WEEKDAYS[p.weekday ?? 1] ?? "Mon"} ${p.time}` return `Daily · ${p.time}` } -function SectionTitle({ children }: { children: React.ReactNode }) { - return ( -

- {children} -

- ) -} - const controlClass = cn( dmSans125ClassName(), "h-9 w-full rounded-[10px] border border-white/[0.08] bg-[#0D0F14] px-3 text-[13px] text-[#FAFAFA] outline-none disabled:opacity-50", @@ -318,9 +296,6 @@ const selectItemClass = "cursor-pointer rounded-[8px] text-[13px] text-[#FAFAFA] hover:bg-white/10 hover:text-white data-[highlighted]:bg-white/10 data-[highlighted]:text-white focus:bg-white/10 focus:text-white" const DM_VALUE = "__dm__" -const menuItemClass = - "cursor-pointer gap-2.5 rounded-lg px-2.5 py-2 text-[13px] font-medium text-white/85 hover:bg-white/[0.06] hover:text-white data-[highlighted]:bg-white/[0.06] data-[highlighted]:text-white focus:bg-white/[0.06] focus:text-white" - function AutomationCard({ initial, id, @@ -830,7 +805,7 @@ function AutomationRow({ ) } -function PresetTile({ +function PresetCard({ preset, onPick, }: { @@ -844,19 +819,31 @@ function PresetTile({ onClick={onPick} className={cn( dmSans125ClassName(), - "flex items-center gap-3 rounded-[10px] border border-white/[0.06] bg-[#14161A] px-3 py-2.5 text-left transition-colors hover:border-white/[0.12] hover:bg-[#171A1F]", + "flex min-w-0 cursor-pointer flex-col justify-between gap-3 rounded-xl bg-[#14161A] p-4 text-left", + "shadow-[inset_2.42px_2.42px_4.263px_rgba(11,15,21,0.7)] transition-colors hover:bg-[#171A1F]", )} - title={preset.description} > - - - - {preset.label} - - +
+
+ +
+
+

+ {preset.label} +

+

+ {preset.description} +

+
+
+
+ {cadenceLabel(preset)} - + + Use template + +
) } @@ -873,8 +860,6 @@ export default function CompanyBrainAutomations() { const removeDraft = (key: number) => setDrafts((d) => d.filter((x) => x.key !== key)) - const { isAdmin } = useOrgMemberRole(isCompanyBrain) - const listQuery = useQuery({ queryKey: ["company-brain-automations", "list", org?.id], queryFn: async () => { @@ -926,71 +911,12 @@ export default function CompanyBrainAutomations() { queryKey: ["company-brain-automations", "list"], }) } - const showGallery = automations.length === 0 && drafts.length === 0 + const usedTitles = new Set(automations.map((a) => a.title)) + const availablePresets = presets.filter((p) => !usedTitles.has(p.label)) + const hasList = automations.length > 0 || drafts.length > 0 return (
-
-
- Channel automations - - - - - - addDraft(emptyDraft())} - className={menuItemClass} - > - - Blank automation - - {presets.map((p) => { - const Icon = p.icon - return ( - addDraft(presetToDraft(p))} - className={menuItemClass} - > - - {p.label} - - ) - })} - - -
- - - - Read-only scheduled summaries posted to a channel.{" "} - {isAdmin - ? "You manage all across the org." - : "You manage the ones you create."} - -
-
{automations.map((a) => openId === a.id ? ( @@ -1035,40 +961,37 @@ export default function CompanyBrainAutomations() { /> ))} - {showGallery ? ( -
-

- Start from a template: -

-
- {galleryPresets(presets).map((p) => ( - addDraft(presetToDraft(p))} - /> - ))} - -
-

- More templates in the New automation menu. -

-
+ {hasList ? ( +

+ Templates +

) : null} +
+ {availablePresets.map((p) => ( + addDraft(presetToDraft(p))} + /> + ))} + +
) diff --git a/apps/web/components/settings/company-brain-connections.tsx b/apps/web/components/settings/company-brain-connections.tsx index 556bc599..ae1489a4 100644 --- a/apps/web/components/settings/company-brain-connections.tsx +++ b/apps/web/components/settings/company-brain-connections.tsx @@ -2,8 +2,21 @@ import { useOrgMemberRole } from "@/hooks/use-org-member-role" import { cn } from "@lib/utils" -import { Loader2, Lock } from "lucide-react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { ChevronDown, Loader2, Plus, XIcon } from "lucide-react" import { useCallback, useEffect, useState } from "react" +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@ui/components/dialog" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@ui/components/dropdown-menu" import { toast } from "sonner" import { dmSans125ClassName } from "@/lib/fonts" import { useHasCompanyBrain } from "@/hooks/use-company-brain" @@ -32,7 +45,6 @@ type ConnRow = { userId: string | null } type SlackStatus = { connected: boolean; teamName: string | null } -type Scope = "org" | "user" function titleCase(s: string) { return s.replace(/\b\w/g, (c) => c.toUpperCase()) @@ -47,28 +59,20 @@ function slugifyMcpName(value: string) { .slice(0, 63) } -function SecondaryButton({ - children, - href, -}: { - children: React.ReactNode - href: string -}) { - return ( - - {children} - - ) -} +const pillLinkClass = cn( + "relative flex h-8 min-w-[94px] shrink-0 items-center justify-center gap-1.5 rounded-full bg-[#0D121A] px-3 sm:h-9 sm:min-w-[116px] sm:px-5", + "text-[12px] font-medium text-[#FAFAFA] sm:text-[14px]", + "shadow-[inset_1.5px_1.5px_4.5px_rgba(0,0,0,0.7)]", + "cursor-pointer transition-opacity hover:opacity-80", +) -function StatusDot({ connected }: { connected: boolean }) { +function ScopeChip({ + label, + connected, +}: { + label: string + connected: boolean +}) { return ( - {connected ? "Connected" : "Not connected"} + {label} ) } +const menuItemClass = + "gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer" + function AppCard({ name, subtitle, icon, - connected, - canConnect, - canDisconnect, - lockedHint, + userConnected, + orgConnected, + isAdmin, + personalOnly, busy, onConnect, onDisconnect, @@ -103,16 +110,20 @@ function AppCard({ name: string subtitle: string icon: React.ReactNode - connected: boolean - canConnect: boolean - canDisconnect: boolean - lockedHint?: string + userConnected: boolean + orgConnected: boolean + isAdmin: boolean + personalOnly?: boolean busy: boolean - onConnect: () => void - onDisconnect: () => void + onConnect: (shared: boolean) => void + onDisconnect: (shared: boolean) => void }) { + const anyConnected = userConnected || orgConnected + const showOrgChip = !personalOnly && (orgConnected || isAdmin) + const adminMenu = isAdmin && !personalOnly + return ( -
+
{icon} @@ -137,67 +148,154 @@ function AppCard({
- - {connected && canDisconnect ? ( - +
+ {personalOnly || !anyConnected ? ( + + ) : ( + <> + + {showOrgChip ? ( + + ) : null} + + )} +
+ {adminMenu ? ( + + + + + + + userConnected ? onDisconnect(false) : onConnect(false) + } + > + {userConnected ? "Disconnect my account" : "Connect my account"} + + + orgConnected ? onDisconnect(true) : onConnect(true) + } + > + {orgConnected + ? "Disconnect workspace" + : "Connect for workspace"} + + + + ) : userConnected ? ( + onDisconnect(false)} disabled={busy}> {busy && } Disconnect - ) : ( - !connected && - (canConnect ? ( - - {busy && } - Connect - - ) : lockedHint ? ( - - - {lockedHint} - - ) : null) + ) : personalOnly ? null : ( + onConnect(false)} disabled={busy}> + {busy && } + Connect + )}
) } -function ScopeToggle({ - scope, - onChange, +function SlackCard({ + status, + isAdmin, + installHref, }: { - scope: Scope - onChange: (s: Scope) => void + status: SlackStatus | null + isAdmin: boolean + installHref: string }) { - const items: { id: Scope; label: string }[] = [ - { id: "org", label: "Organization" }, - { id: "user", label: "Personal" }, - ] + const connected = status?.connected ?? false return ( -
- {items.map((it) => ( - - ))} +
+
+
+ +
+
+

+ Slack +

+

+ Messaging +

+
+
+
+ + {isAdmin ? ( + + {connected ? "Reconnect" : "Connect"} + + ) : null} +
) } function RowSkeleton() { return ( -
+
@@ -205,7 +303,7 @@ function RowSkeleton() {
-
+
) } @@ -218,7 +316,7 @@ export default function CompanyBrainConnections() { const [rows, setRows] = useState([]) const [slackStatus, setSlackStatus] = useState(null) const [busy, setBusy] = useState(null) - const [scope, setScope] = useState("user") + const [customOpen, setCustomOpen] = useState(false) const [customName, setCustomName] = useState("") const [customServerUrl, setCustomServerUrl] = useState("") @@ -379,8 +477,12 @@ export default function CompanyBrainConnections() { } if (data.authUrl) { window.open(data.authUrl, "_blank", "noopener") + setCustomOpen(false) + setCustomName("") + setCustomServerUrl("") } else if (data.ok) { toast.success(`${slug} connected.`) + setCustomOpen(false) setCustomName("") setCustomServerUrl("") await load() @@ -452,43 +554,9 @@ export default function CompanyBrainConnections() { !catalogSlugs.has(row.serverSlug), ) : [] - const shared = scope === "org" - const description = shared - ? "Connected by admins. Used for reads when you haven't connected your own." - : "Your personal accounts, used for your actions and your reads." - return (
-
- - {slackStatus?.connected && slackStatus.teamName ? ( -

- Slack · {slackStatus.teamName} -

- ) : null} - {isAdmin ? ( - - - Reconnect Slack - - ) : null} -
- -

- {description} -

- -
+
{loading ? ( <> @@ -497,100 +565,128 @@ export default function CompanyBrainConnections() { ) : ( <> - {!shared && isStaff ? ( -
-
-

- Custom MCP server -

-

- Add a personal OAuth MCP server by URL. -

-
-
- setCustomName(event.target.value)} - placeholder="Name" - className="h-8 w-full rounded-full border border-[#1E293B] bg-[#0D121A] px-3 text-[12px] font-medium text-[#FAFAFA] outline-none placeholder:text-[#5F6673] focus:border-[#334155]" - /> - setCustomServerUrl(event.target.value)} - placeholder="https://example.com/mcp" - className="h-8 w-full rounded-full border border-[#1E293B] bg-[#0D121A] px-3 text-[12px] font-medium text-[#FAFAFA] outline-none placeholder:text-[#5F6673] focus:border-[#334155]" - /> -
- - {busy?.startsWith("custom:") && ( - - )} - Connect - -
-
-
- ) : null} - + {apps.map((entry) => ( connect(entry, shared)} - onDisconnect={() => disconnect(entry, shared)} + userConnected={isConnected(entry.slug, false)} + orgConnected={isConnected(entry.slug, true)} + isAdmin={isAdmin} + busy={busy?.startsWith(`${entry.slug}:`) ?? false} + onConnect={(shared) => connect(entry, shared)} + onDisconnect={(shared) => disconnect(entry, shared)} /> ))} - {!shared && - customRows.map((row) => ( - {}} - onDisconnect={() => - disconnect( - { - slug: row.serverSlug, - name: titleCase(row.serverSlug.replace(/-/g, " ")), - category: "Custom OAuth MCP", - authType: "oauth", - }, - false, - ) - } - /> - ))} + {customRows.map((row) => ( + {}} + onDisconnect={() => + disconnect( + { + slug: row.serverSlug, + name: titleCase(row.serverSlug.replace(/-/g, " ")), + category: "Custom OAuth MCP", + authType: "oauth", + }, + false, + ) + } + /> + ))} + {isStaff ? ( + + ) : null} )}
+ + + +
+ + + Custom MCP server + +

+ Add a personal OAuth MCP server by URL. +

+
+ + + Close + +
+ +
+ setCustomName(event.target.value)} + placeholder="Name" + className="h-9 w-full rounded-full border border-[#1E293B] bg-[#0D121A] px-3.5 text-[13px] font-medium text-[#FAFAFA] outline-none placeholder:text-[#5F6673] focus:border-[#334155]" + /> + setCustomServerUrl(event.target.value)} + placeholder="https://example.com/mcp" + className="h-9 w-full rounded-full border border-[#1E293B] bg-[#0D121A] px-3.5 text-[13px] font-medium text-[#FAFAFA] outline-none placeholder:text-[#5F6673] focus:border-[#334155]" + /> +
+ + {busy?.startsWith("custom:") && ( + + )} + Connect + +
+
+
+
) } diff --git a/apps/web/components/settings/company-brain-models.tsx b/apps/web/components/settings/company-brain-models.tsx index 51b2bb9b..3bded837 100644 --- a/apps/web/components/settings/company-brain-models.tsx +++ b/apps/web/components/settings/company-brain-models.tsx @@ -75,7 +75,11 @@ function SectionTitle({ children }: { children: React.ReactNode }) { ) } -export default function CompanyBrainModels() { +export default function CompanyBrainModels({ + showHeading = true, +}: { + showHeading?: boolean +}) { const isCompanyBrain = useHasCompanyBrain() const { isAdmin } = useOrgMemberRole(isCompanyBrain) @@ -106,15 +110,17 @@ export default function CompanyBrainModels() { return (
-
- Models - - Choose which models Company Brain uses. Applies to this organization - only. - -
+ {showHeading ? ( +
+ Models + + Choose which models Company Brain uses. Applies to this organization + only. + +
+ ) : null} {modelsQuery.isLoading ? (
diff --git a/apps/web/components/settings/settings-content.tsx b/apps/web/components/settings/settings-content.tsx index dd8617d0..62d1e468 100644 --- a/apps/web/components/settings/settings-content.tsx +++ b/apps/web/components/settings/settings-content.tsx @@ -10,10 +10,6 @@ import Account from "@/components/settings/account" import Billing from "@/components/settings/billing" import Integrations from "@/components/settings/integrations" import ConnectionsMCP from "@/components/settings/connections-mcp" -import CompanyBrainConnections from "@/components/settings/company-brain-connections" -import { ProactivenessIcon } from "@/components/settings/proactiveness-icon" -import Proactiveness from "@/components/settings/proactiveness" -import CompanyBrainModels from "@/components/settings/company-brain-models" import Support from "@/components/settings/support" import { ErrorBoundary } from "@/components/error-boundary" import { useRouter } from "next/navigation" @@ -31,7 +27,6 @@ import { Zap, HelpCircle, CreditCard, - Cpu, ShieldAlert, ChevronRight, ArrowUpRight, @@ -56,9 +51,6 @@ export const TABS = [ "billing", "integrations", "connections", - "company-brain", - "company-brain-models", - "proactiveness", "support", ] as const export type SettingsTab = (typeof TABS)[number] @@ -95,24 +87,6 @@ const NAV_ITEMS: NavItem[] = [ description: "Drive, Notion, OneDrive, MCP", icon: , }, - { - id: "company-brain", - label: "Company Brain", - description: "Connect apps to your brain — org and personal", - icon: , - }, - { - id: "company-brain-models", - label: "Models", - description: "Choose the models your brain uses", - icon: , - }, - { - id: "proactiveness", - label: "Proactiveness", - description: "Scheduled digests and unprompted actions", - icon: , - }, { id: "support", label: "Support & Help", @@ -175,12 +149,12 @@ export function SettingsContent({ const { user, org, organizations, setActiveOrg, clearActiveOrg } = useAuth() const isCompanyBrain = useHasCompanyBrain() - // Company Brain orgs manage tools inside Company Brain; hide the generic tabs. + // Company Brain orgs manage tools in the Configure view; hide the generic tabs. const navItems = isCompanyBrain ? NAV_ITEMS.filter( (item) => item.id !== "integrations" && item.id !== "connections", ) - : NAV_ITEMS.filter((item) => item.id !== "company-brain-models") + : NAV_ITEMS const router = useRouter() const isMobile = useIsMobile() const localStorageUsername = useLocalStorageUsername() @@ -507,9 +481,6 @@ export function SettingsContent({ {activeTab === "billing" && } {activeTab === "integrations" && } {activeTab === "connections" && } - {activeTab === "company-brain" && } - {activeTab === "company-brain-models" && } - {activeTab === "proactiveness" && } {activeTab === "support" && }
diff --git a/apps/web/components/user-profile-menu.tsx b/apps/web/components/user-profile-menu.tsx index d4ba056f..b69701f2 100644 --- a/apps/web/components/user-profile-menu.tsx +++ b/apps/web/components/user-profile-menu.tsx @@ -15,10 +15,10 @@ import { useRouter } from "next/navigation" import { LogOut, Settings, + Settings2, RotateCcw, HelpCircle, LifeBuoy, - Building2, Sun, } from "lucide-react" import { cn } from "@lib/utils" @@ -26,7 +26,6 @@ import { dmSansClassName } from "@/lib/fonts" import { useOrgOnboarding } from "@hooks/use-org-onboarding" import { useTokenUsage } from "@/hooks/use-token-usage" import { useSettingsModal } from "@/components/settings/settings-modal" -import { ProactivenessIcon } from "@/components/settings/proactiveness-icon" import { useHasCompanyBrain } from "@/hooks/use-company-brain" import { useViewMode } from "@/lib/view-mode-context" @@ -165,20 +164,13 @@ export function UserProfileMenu({ Settings - openSettings("company-brain")} - className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer" - > - - Company Brain - {isCompanyBrain ? ( openSettings("proactiveness")} + onClick={() => void setViewMode("configure")} className="gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer" > - - Proactiveness + + Configure ) : null} {isCompanyBrain ? ( diff --git a/apps/web/lib/analytics.ts b/apps/web/lib/analytics.ts index 7b6369f3..eebd1697 100644 --- a/apps/web/lib/analytics.ts +++ b/apps/web/lib/analytics.ts @@ -222,15 +222,7 @@ export const analytics = { // settings / spaces / docs analytics settingsTabChanged: (props: { - tab: - | "account" - | "billing" - | "integrations" - | "connections" - | "company-brain" - | "company-brain-models" - | "proactiveness" - | "support" + tab: "account" | "billing" | "integrations" | "connections" | "support" }) => safeCapture("settings_tab_changed", props), spaceCreated: () => safeCapture("space_created"), diff --git a/apps/web/lib/search-params.ts b/apps/web/lib/search-params.ts index 9afb6eab..2d9f1688 100644 --- a/apps/web/lib/search-params.ts +++ b/apps/web/lib/search-params.ts @@ -28,6 +28,7 @@ const viewLiterals = [ "graph", "list", "integrations", + "configure", "chat", "digests", // Integration sub-views — each card is its own view diff --git a/bun.lock b/bun.lock index c6f5c06e..d6b4edf3 100644 --- a/bun.lock +++ b/bun.lock @@ -320,7 +320,7 @@ }, "packages/tools": { "name": "@supermemory/tools", - "version": "2.0.0", + "version": "2.1.1", "dependencies": { "@ai-sdk/anthropic": "^2.0.25", "@ai-sdk/openai": "^2.0.23",