@@ -925,6 +935,7 @@ export default function Account() {
(null)
const [catalogLoaded, setCatalogLoaded] = useState(false)
const [rows, setRows] = useState
([])
@@ -368,9 +366,6 @@ export default function CompanyBrainConnections() {
(shared ? r.userId === null : r.userId !== null),
)
- const isStaff =
- user?.email?.toLowerCase().endsWith("@supermemory.com") ?? false
-
const connect = async (entry: CatalogEntry, shared: boolean) => {
const key = `${entry.slug}:${shared ? "org" : "user"}`
setBusy(key)
@@ -462,10 +457,6 @@ export default function CompanyBrainConnections() {
redirectUrl: window.location.href,
}),
})
- if (res.status === 403) {
- toast.error("Custom MCP URLs are staff-only.")
- return
- }
const data = (await res.json().catch(() => ({}))) as {
authUrl?: string
ok?: boolean
@@ -609,20 +600,18 @@ export default function CompanyBrainConnections() {
}
/>
))}
- {isStaff ? (
- setCustomOpen(true)}
- className={cn(
- dmSans125ClassName(),
- "flex min-h-[104px] cursor-pointer items-center justify-center gap-2 rounded-xl border border-[#2A313C] border-dashed",
- "text-[13px] font-medium text-[#737B87] transition-colors hover:border-[#3A4150] hover:text-[#FAFAFA]",
- )}
- >
-
- Add custom MCP
-
- ) : null}
+ setCustomOpen(true)}
+ className={cn(
+ dmSans125ClassName(),
+ "flex min-h-[104px] cursor-pointer items-center justify-center gap-2 rounded-xl border border-[#2A313C] border-dashed",
+ "text-[13px] font-medium text-[#737B87] transition-colors hover:border-[#3A4150] hover:text-[#FAFAFA]",
+ )}
+ >
+
+ Add custom MCP
+
>
)}
diff --git a/apps/web/components/settings/company-brain-proactivity.tsx b/apps/web/components/settings/company-brain-proactivity.tsx
new file mode 100644
index 00000000..4e17eaf4
--- /dev/null
+++ b/apps/web/components/settings/company-brain-proactivity.tsx
@@ -0,0 +1,292 @@
+"use client"
+
+import { useQuery } from "@tanstack/react-query"
+import { cn } from "@lib/utils"
+import { Check, Loader2, Lock, X } from "lucide-react"
+import { useAuth } from "@lib/auth-context"
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@ui/components/select"
+import {
+ type BrainChannelProactivity,
+ type BrainProactivityDefault,
+ useBrainSettings,
+ useUpdateBrainSettings,
+} from "@/hooks/use-brain-settings"
+import { useHasCompanyBrain } from "@/hooks/use-company-brain"
+import { useOrgMemberRole } from "@/hooks/use-org-member-role"
+import { dmSans125ClassName } from "@/lib/fonts"
+
+const BACKEND =
+ process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai"
+
+type Channel = { id: string; name: string; isPrivate: boolean }
+
+const HOME_CHANNEL_NAME = "company-brain"
+const ADD_PLACEHOLDER = "__add__"
+
+const MODES: {
+ id: BrainProactivityDefault
+ label: string
+ description: string
+}[] = [
+ {
+ id: "all_channels",
+ label: "All channels",
+ description: "Joins any conversation it's been added to when it can help.",
+ },
+ {
+ id: "own_channel_only",
+ label: "Only its own channel",
+ description: "Speaks only in #company-brain unless @mentioned or DMed.",
+ },
+]
+
+const fieldLabel = cn(
+ dmSans125ClassName(),
+ "text-[11px] font-medium uppercase tracking-[0.06em] text-[#5B6675]",
+)
+const controlClass = cn(
+ dmSans125ClassName(),
+ "h-9 w-full rounded-full border border-[#1E293B] bg-[#0D121A] px-3.5 text-[13px] text-[#FAFAFA] outline-none disabled:opacity-50",
+)
+const selectContentClass = cn(
+ dmSans125ClassName(),
+ "rounded-[10px] border-white/[0.08] bg-[#1B1F24] text-[#FAFAFA] shadow-[0px_8px_24px_rgba(0,0,0,0.5)]",
+)
+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"
+
+export default function CompanyBrainProactivity() {
+ const isCompanyBrain = useHasCompanyBrain()
+ const { isAdmin } = useOrgMemberRole(isCompanyBrain)
+ const { org } = useAuth()
+
+ const settingsQuery = useBrainSettings(isCompanyBrain)
+ const update = useUpdateBrainSettings()
+
+ const slackStatusQuery = useQuery({
+ queryKey: ["brain", "slack-status", org?.id],
+ queryFn: async () => {
+ const res = await fetch(`${BACKEND}/brain/slack/status`, {
+ credentials: "include",
+ })
+ if (!res.ok) throw new Error("Failed to load Slack status")
+ return (await res.json()) as { connected: boolean }
+ },
+ enabled: isCompanyBrain,
+ staleTime: 60_000,
+ })
+
+ // Same key + endpoint as the automations picker so react-query dedupes.
+ const channelsQuery = useQuery({
+ queryKey: ["company-brain-automations", "channels", org?.id],
+ queryFn: async () => {
+ const res = await fetch(`${BACKEND}/brain/automations/channels`, {
+ credentials: "include",
+ })
+ if (!res.ok) throw new Error("Failed to load channels")
+ return ((await res.json()) as { channels: Channel[] }).channels ?? []
+ },
+ enabled: isCompanyBrain,
+ staleTime: 60_000,
+ })
+
+ if (!isCompanyBrain) {
+ return (
+
+
+ Company Brain isn't enabled for this organization.
+
+
+ )
+ }
+
+ const proactivity = settingsQuery.data?.proactivity
+ const activeMode = proactivity?.default ?? "all_channels"
+ const overrides = proactivity?.channels ?? {}
+ const channels = channelsQuery.data ?? []
+ const channelName = (id: string) =>
+ channels.find((ch) => ch.id === id)?.name ?? id
+ const addable = channels.filter(
+ (ch) => !overrides[ch.id] && ch.name !== HOME_CHANNEL_NAME,
+ )
+ const disabled = !isAdmin || settingsQuery.isLoading || update.isPending
+
+ const setMode = (mode: BrainProactivityDefault) => {
+ if (mode === activeMode) return
+ update.mutate({ proactivity: { default: mode } })
+ }
+ const setOverride = (
+ channelId: string,
+ value: BrainChannelProactivity | null,
+ ) => {
+ update.mutate({ proactivity: { channels: { [channelId]: value } } })
+ }
+
+ return (
+
+ {settingsQuery.isLoading ? (
+
+
+ Loading proactivity…
+
+ ) : settingsQuery.isError ? (
+
+ Couldn't load proactivity settings.
+
+ ) : (
+
+
+ {MODES.map((mode) => {
+ const isActive = mode.id === activeMode
+ return (
+ setMode(mode.id)}
+ className={cn(
+ dmSans125ClassName(),
+ "flex min-w-0 cursor-pointer flex-col gap-1.5 rounded-xl p-4 text-left transition-colors disabled:cursor-not-allowed disabled:opacity-50",
+ "bg-[#14161A] shadow-[inset_2.42px_2.42px_4.263px_rgba(11,15,21,0.7)]",
+ isActive
+ ? "bg-[#10161f] ring-1 ring-[#2261CA]/45"
+ : "hover:bg-[#171A1F]",
+ )}
+ >
+
+
+ {mode.label}
+ {mode.id === "all_channels" ? (
+
+ Default
+
+ ) : null}
+
+ {isActive ? (
+
+ ) : null}
+
+
+ {mode.description}
+
+
+ )
+ })}
+
+
+
+
Channel exceptions
+ {Object.entries(overrides).map(([channelId, value]) => (
+
+
+ #{channelName(channelId)}
+
+
+
+ {(["proactive", "quiet"] as const).map((option) => (
+ {
+ if (value !== option) setOverride(channelId, option)
+ }}
+ className={cn(
+ dmSans125ClassName(),
+ "h-7 cursor-pointer rounded-full px-3 text-[11.5px] font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50",
+ value === option
+ ? "bg-white/[0.10] text-[#FAFAFA]"
+ : "text-[#8B929E] hover:text-[#FAFAFA]",
+ )}
+ >
+ {option === "proactive" ? "Proactive" : "Quiet"}
+
+ ))}
+
+
setOverride(channelId, null)}
+ className="cursor-pointer text-[#6B6B6B] transition-colors hover:text-[#FAFAFA] disabled:cursor-not-allowed disabled:opacity-50"
+ aria-label={`Remove exception for #${channelName(channelId)}`}
+ >
+
+
+
+
+ ))}
+ {addable.length > 0 ? (
+
{
+ if (channelId === ADD_PLACEHOLDER) return
+ setOverride(
+ channelId,
+ activeMode === "all_channels" ? "quiet" : "proactive",
+ )
+ }}
+ >
+
+
+
+
+
+ Add a channel exception…
+
+ {addable.map((ch) => (
+
+ {ch.isPrivate ? "🔒 " : "# "}
+ {ch.name}
+
+ ))}
+
+
+ ) : channelsQuery.isLoading || slackStatusQuery.isLoading ? null : (
+
+ {slackStatusQuery.data?.connected === false
+ ? "Connect Slack to set per-channel exceptions."
+ : "Invite Company Brain to a Slack channel to list it here."}
+
+ )}
+
+
+ {!isAdmin ? (
+
+
+ Only organization admins can change these.
+
+ ) : null}
+
+ )}
+
+ )
+}
diff --git a/apps/web/components/settings/proactiveness-icon.tsx b/apps/web/components/settings/proactiveness-icon.tsx
index 550730f1..d330f1e4 100644
--- a/apps/web/components/settings/proactiveness-icon.tsx
+++ b/apps/web/components/settings/proactiveness-icon.tsx
@@ -6,24 +6,16 @@ export function ProactivenessIcon({ className }: { className?: string }) {
fill="none"
stroke="currentColor"
strokeWidth={1.5}
+ strokeLinecap="round"
+ strokeLinejoin="round"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
-
-
-
+
+
+
+
+
)
}
diff --git a/apps/web/components/settings/settings-content.tsx b/apps/web/components/settings/settings-content.tsx
index 62d1e468..7694c03a 100644
--- a/apps/web/components/settings/settings-content.tsx
+++ b/apps/web/components/settings/settings-content.tsx
@@ -477,7 +477,9 @@ export function SettingsContent({
}
>
- {activeTab === "account" &&
}
+ {activeTab === "account" && (
+
+ )}
{activeTab === "billing" &&
}
{activeTab === "integrations" &&
}
{activeTab === "connections" &&
}
diff --git a/apps/web/components/settings/workspace-prompt.tsx b/apps/web/components/settings/workspace-prompt.tsx
new file mode 100644
index 00000000..18e30d5e
--- /dev/null
+++ b/apps/web/components/settings/workspace-prompt.tsx
@@ -0,0 +1,186 @@
+"use client"
+
+import { LoaderIcon } from "lucide-react"
+import { useState } from "react"
+import { useHasCompanyBrain } from "@/hooks/use-company-brain"
+import { useOrgMemberRole } from "@/hooks/use-org-member-role"
+import { useOrgSettings, useUpdateOrgSettings } from "@/hooks/use-org-settings"
+import { dmSans125ClassName, dmSansClassName } from "@/lib/fonts"
+import { cn } from "@lib/utils"
+
+const DESCRIPTION_ID = "workspace-prompt-description"
+const COUNTER_ID = "workspace-prompt-counter"
+const HEADING_ID = "workspace-prompt-heading"
+
+function SectionHeading({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ )
+}
+
+function PromptHeader() {
+ return (
+
+
Workspace Prompt
+
+ Set persistent guidance for how Company Brain works across your
+ workspace.
+
+
+ )
+}
+
+export function WorkspacePrompt({
+ showHeading = true,
+}: {
+ showHeading?: boolean
+}) {
+ const isCompanyBrain = useHasCompanyBrain()
+ const { isAdmin } = useOrgMemberRole(isCompanyBrain)
+ const settingsQuery = useOrgSettings()
+ const updateSettings = useUpdateOrgSettings()
+ const [draft, setDraft] = useState
(null)
+
+ const savedPrompt = settingsQuery.data?.workspacePrompt ?? ""
+ const prompt = draft ?? savedPrompt
+ const dirty = draft !== null && draft.trim() !== savedPrompt.trim()
+ const canClear = !dirty && savedPrompt.length > 0 && isAdmin
+
+ const handleSave = () => {
+ updateSettings.mutate(
+ {
+ workspacePrompt: prompt.trim() ? prompt.trim() : null,
+ },
+ { onSuccess: () => setDraft(null) },
+ )
+ }
+
+ if (!isCompanyBrain) return null
+
+ return (
+
+ {showHeading ? : null}
+
+ {settingsQuery.isLoading ? (
+
+
+ Loading workspace prompt…
+
+ ) : settingsQuery.isError ? (
+
+
+ Workspace prompt could not be loaded.
+
+
void settingsQuery.refetch()}
+ disabled={settingsQuery.isFetching}
+ className="inline-flex h-7 items-center gap-1.5 rounded-full bg-[#0D121A] px-3 text-[12px] font-semibold text-[#FAFAFA] shadow-[inset_1.5px_1.5px_4.5px_rgba(0,0,0,0.7)] transition-opacity hover:opacity-80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#4BA0FA] focus-visible:ring-offset-2 focus-visible:ring-offset-[#1B1F24] cursor-pointer disabled:cursor-not-allowed disabled:opacity-50"
+ >
+ {settingsQuery.isFetching && (
+
+ )}
+ Try again
+
+
+ ) : (
+
+ )}
+
+ )
+}
diff --git a/apps/web/components/share-modal.tsx b/apps/web/components/share-modal.tsx
index 877d82ac..091a9446 100644
--- a/apps/web/components/share-modal.tsx
+++ b/apps/web/components/share-modal.tsx
@@ -15,6 +15,7 @@ import { XIcon, Download, Copy, Check } from "lucide-react"
import { GradientLogo } from "@ui/assets/Logo"
import { useAuth } from "@lib/auth-context"
import { useLocalStorageUsername } from "@hooks/use-local-storage-username"
+import { useHasCompanyBrain } from "@/hooks/use-company-brain"
import { toast } from "sonner"
import * as htmlToImage from "html-to-image"
@@ -276,7 +277,8 @@ export function ShareModal({
onClose,
graphCanvasRef,
}: ShareModalProps) {
- const { user } = useAuth()
+ const { user, org } = useAuth()
+ const isCompanyBrain = useHasCompanyBrain()
const [selectedTheme, setSelectedTheme] =
useState("gradient")
const [isCopying, setIsCopying] = useState(false)
@@ -291,6 +293,15 @@ export function ShareModal({
user?.email?.split("@")[0] ||
""
const userName = displayName ? `${displayName.split(" ")[0]}'s` : "Your"
+ const orgLabel = org?.name.replace(/\s*organizations?\s*$/i, "").trim()
+ const ownerLabel = isCompanyBrain
+ ? orgLabel
+ ? /['’]s$/i.test(orgLabel)
+ ? orgLabel
+ : `${orgLabel}'s`
+ : "Your company's"
+ : userName
+ const productName = isCompanyBrain ? "Company Brain" : "supermemory"
const capturePreview = useCallback(async (): Promise => {
if (!previewRef.current) return null
@@ -439,10 +450,10 @@ export function ShareModal({
- {userName}
+ {ownerLabel}
- supermemory
+ {productName}
diff --git a/apps/web/components/user-profile-menu.tsx b/apps/web/components/user-profile-menu.tsx
index b69701f2..ea034d73 100644
--- a/apps/web/components/user-profile-menu.tsx
+++ b/apps/web/components/user-profile-menu.tsx
@@ -13,6 +13,7 @@ import {
import { authClient } from "@lib/auth"
import { useRouter } from "next/navigation"
import {
+ Brain,
LogOut,
Settings,
Settings2,
@@ -22,6 +23,7 @@ import {
Sun,
} from "lucide-react"
import { cn } from "@lib/utils"
+import { analytics } from "@/lib/analytics"
import { dmSansClassName } from "@/lib/fonts"
import { useOrgOnboarding } from "@hooks/use-org-onboarding"
import { useTokenUsage } from "@/hooks/use-token-usage"
@@ -164,6 +166,18 @@ export function UserProfileMenu({