) : activeSection === "workspace-prompt" ? (
+ ) : activeSection === "proactivity" ? (
+
) : (
)}
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/hooks/use-brain-settings.ts b/apps/web/hooks/use-brain-settings.ts
new file mode 100644
index 00000000..8d0f4a9b
--- /dev/null
+++ b/apps/web/hooks/use-brain-settings.ts
@@ -0,0 +1,76 @@
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
+import { toast } from "sonner"
+import { useAuth } from "@lib/auth-context"
+
+const BACKEND =
+ process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai"
+const BASE = `${BACKEND}/brain/settings`
+
+export type BrainProactivityDefault = "all_channels" | "own_channel_only"
+export type BrainChannelProactivity = "proactive" | "quiet"
+
+export type BrainSettingsResponse = {
+ proactivity: {
+ default: BrainProactivityDefault
+ channels: Record
+ }
+ choices: {
+ proactivityDefault: BrainProactivityDefault[]
+ channelProactivity: BrainChannelProactivity[]
+ }
+}
+
+// null clears: default -> reset, channels[id] -> remove that override.
+export type BrainSettingsPatch = {
+ proactivity?: {
+ default?: BrainProactivityDefault | null
+ channels?: Record | null
+ } | null
+}
+
+export function useBrainSettings(enabled: boolean) {
+ const { org } = useAuth()
+ return useQuery({
+ queryKey: ["brain", "settings", org?.id],
+ queryFn: async (): Promise => {
+ const res = await fetch(`${BASE}/`, { credentials: "include" })
+ if (!res.ok) throw new Error("Failed to load settings")
+ return res.json()
+ },
+ enabled,
+ staleTime: 60_000,
+ })
+}
+
+export function useUpdateBrainSettings() {
+ const { org } = useAuth()
+ const queryClient = useQueryClient()
+ return useMutation({
+ mutationFn: async (patch: BrainSettingsPatch) => {
+ const res = await fetch(`${BASE}/`, {
+ method: "PATCH",
+ credentials: "include",
+ headers: { "Content-Type": "application/json", "X-App-Source": "nova" },
+ body: JSON.stringify(patch),
+ })
+ if (res.status === 403)
+ throw new Error("Only admins can change these settings.")
+ if (!res.ok) {
+ const b = (await res.json().catch(() => ({}))) as {
+ message?: string
+ error?: string
+ }
+ throw new Error(b.message ?? b.error ?? "Failed to save settings")
+ }
+ return res.json() as Promise
+ },
+ onSuccess: (data) => {
+ queryClient.setQueryData(["brain", "settings", org?.id], data)
+ toast.success("Proactivity saved")
+ },
+ onError: (err) =>
+ toast.error(
+ err instanceof Error ? err.message : "Failed to save settings",
+ ),
+ })
+}