feat(web): configure Company Brain reasoning effort (#1307)

## Stack context

Stacks on #1306, which moves Company Brain settings into the revamped Configure page.

Backend contract and runtime support: supermemoryai/mono#2581. The UI safely hides any effort controls omitted by an older backend response.

## What changed

- Adds independent Low, Medium, High, and Extra high reasoning controls for Main, Triage, and Research.
- Saves model and reasoning edits together through the existing partial PATCH.
- Keeps controls visible but disabled for non-admin members.
- Explains that Extra high maps to High for Grok and GPT providers.

## Validation

- `bunx biome check apps/web/hooks/use-brain-models.ts apps/web/components/settings/company-brain-models.tsx`
- `bun run build` in `apps/web`
- `git diff --check`

The standalone web TypeScript command still reports pre-existing unrelated errors outside these files.

---
**Session Details**
- Session: [View Session](https://supermemory.us1.vorflux.com/agent-sessions/1c4f3ec7-a536-4105-bbe7-8b19e61f245f)
- Requested by: Sreeram Sreedhar (sreeram@supermemory.com)
- Address comments on this PR. Add `(aside)` to your comment to have me ignore it.
This commit is contained in:
sreedharsreeram 2026-07-18 19:12:45 +00:00
parent 20e585cd00
commit 9824f0c9cd
3 changed files with 393 additions and 80 deletions

View file

@ -27,7 +27,8 @@ const SECTIONS: {
{
id: "models",
label: "Models",
description: "Choose the models used for reasoning, triage, and research.",
description:
"Pick how fast or thorough your brain should be. Fine-tune each task under Advanced.",
icon: Cpu,
},
{

View file

@ -1,7 +1,7 @@
"use client"
import { cn } from "@lib/utils"
import { Loader2, Lock } from "lucide-react"
import { Check, ChevronDown, Loader2, Lock } from "lucide-react"
import { useMemo, useState } from "react"
import {
Select,
@ -11,13 +11,17 @@ import {
SelectValue,
} from "@ui/components/select"
import {
type BrainModelConfig,
type BrainModelRole,
type BrainReasoningEffort,
type BrainReasoningKey,
useBrainModels,
useUpdateBrainModels,
} from "@/hooks/use-brain-models"
import { useHasCompanyBrain } from "@/hooks/use-company-brain"
import { useOrgMemberRole } from "@/hooks/use-org-member-role"
import { dmSans125ClassName } from "@/lib/fonts"
import { PillButton } from "../integrations/install-steps"
const MODEL_LABELS: Record<string, string> = {
"claude-sonnet-5": "Sonnet 5",
@ -33,27 +37,143 @@ const MODEL_LABELS: Record<string, string> = {
const labelFor = (id: string) => MODEL_LABELS[id] ?? id
const ROWS: { role: BrainModelRole; title: string; help: string }[] = [
// One-line personality tags so non-experts can tell models apart.
const MODEL_TAGS: Record<string, string> = {
"claude-sonnet-5": "balanced",
"claude-opus-4.8": "most capable, slower",
"claude-sonnet-4.6": "balanced",
"claude-haiku-4.5": "fast and light",
"grok-4.5": "sharp all-rounder",
"grok-4.3": "capable",
"grok-4-fast": "fastest",
"gpt-5.6": "capable",
"gpt-5.5": "capable",
}
const EFFORT_LABELS: Record<BrainReasoningEffort, string> = {
low: "Low",
medium: "Medium",
high: "High",
xhigh: "Extra high",
}
const ROWS: {
role: BrainModelRole
effortKey: BrainReasoningKey
title: string
help: string
effortHelp: string
}[] = [
{
role: "main",
title: "Main model",
help: "Reasoning, tool use, and the final Slack answer.",
effortKey: "mainEffort",
title: "Answers",
help: "Writes the replies your brain sends in Slack.",
effortHelp: "Deeper thinking gives better answers but takes longer.",
},
{
role: "triage",
title: "Triage model",
help: "Decides whether and how the brain replies to a message.",
effortKey: "triageEffort",
title: "When to reply",
help: "Decides whether and how the brain responds to a message.",
effortHelp:
"Deeper thinking routes messages more carefully but takes longer.",
},
{
role: "research",
title: "Research model",
help: "Grounded web research during company research.",
effortKey: "researchEffort",
title: "Web research",
help: "Looks things up on the web when researching your company.",
effortHelp: "Deeper research per web search, at the cost of speed.",
},
]
type FullConfig = Required<BrainModelConfig>
type PresetDef = {
id: string
label: string
description: string
build: (
defaults: BrainModelConfig,
choices: { main: string[] } & Partial<
Record<BrainReasoningKey, BrainReasoningEffort[]>
>,
) => FullConfig
}
const pickEffort = (
options: BrainReasoningEffort[] | undefined,
wanted: BrainReasoningEffort,
fallback: BrainReasoningEffort,
): BrainReasoningEffort =>
!options || options.length === 0 || options.includes(wanted)
? wanted
: fallback
const PRESETS: PresetDef[] = [
{
id: "fast",
label: "Fastest",
description: "Snappy replies, lighter on credits. Best for quick lookups.",
build: (defaults, choices) => ({
main: choices.main.includes("grok-4-fast")
? "grok-4-fast"
: defaults.main,
triage: defaults.triage,
research: defaults.research,
mainEffort: pickEffort(choices.mainEffort, "low", "low"),
triageEffort: pickEffort(choices.triageEffort, "low", "low"),
researchEffort: pickEffort(choices.researchEffort, "low", "low"),
}),
},
{
id: "balanced",
label: "Balanced",
description: "Our recommended mix of speed and answer quality.",
build: (defaults) => ({
main: defaults.main,
triage: defaults.triage,
research: defaults.research,
mainEffort: defaults.mainEffort ?? "high",
triageEffort: defaults.triageEffort ?? "low",
researchEffort: defaults.researchEffort ?? "high",
}),
},
{
id: "thorough",
label: "Most thorough",
description: "Deepest answers and research. Slower, uses more credits.",
build: (defaults, choices) => ({
main: defaults.main,
triage: defaults.triage,
research: defaults.research,
mainEffort: pickEffort(choices.mainEffort, "xhigh", "high"),
triageEffort: pickEffort(choices.triageEffort, "medium", "low"),
researchEffort: pickEffort(choices.researchEffort, "xhigh", "high"),
}),
},
]
const CONFIG_KEYS = [
"main",
"triage",
"research",
"mainEffort",
"triageEffort",
"researchEffort",
] as const
const extraHighIsBounded = (model: string): boolean =>
model.startsWith("grok-") || model.startsWith("gpt-")
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",
"h-9 w-full rounded-full border border-[#1E293B] bg-[#0D121A] px-3.5 text-[13px] text-[#FAFAFA] outline-none disabled:opacity-50",
)
const fieldLabel = cn(
dmSans125ClassName(),
"text-[11px] font-medium uppercase tracking-[0.06em] text-[#5B6675]",
)
const selectContentClass = cn(
dmSans125ClassName(),
@ -86,9 +206,8 @@ export default function CompanyBrainModels({
const modelsQuery = useBrainModels(isCompanyBrain)
const update = useUpdateBrainModels()
const [draft, setDraft] = useState<Partial<Record<BrainModelRole, string>>>(
{},
)
const [draft, setDraft] = useState<Partial<BrainModelConfig>>({})
const [advancedOpen, setAdvancedOpen] = useState(false)
const resolved = modelsQuery.data?.resolved
const defaults = modelsQuery.data?.defaults
@ -96,14 +215,40 @@ export default function CompanyBrainModels({
const valueFor = (role: BrainModelRole): string =>
draft[role] ?? resolved?.[role] ?? ""
const effortFor = (key: BrainReasoningKey): BrainReasoningEffort | "" =>
draft[key] ?? resolved?.[key] ?? defaults?.[key] ?? ""
const dirty = useMemo(() => {
if (!resolved) return false
return ROWS.some(
({ role }) => draft[role] && draft[role] !== resolved[role],
)
return ROWS.some(({ role, effortKey }) => {
const modelChanged =
draft[role] !== undefined && draft[role] !== resolved[role]
const effortChanged =
draft[effortKey] !== undefined &&
draft[effortKey] !== resolved[effortKey]
return modelChanged || effortChanged
})
}, [draft, resolved])
const presets = useMemo(() => {
if (!defaults || !choices) return []
return PRESETS.map((p) => ({ ...p, config: p.build(defaults, choices) }))
}, [defaults, choices])
// Preset whose full config matches what's currently on screen (draft over saved).
const activePresetId = useMemo(() => {
if (!resolved) return null
const current: Record<string, string | undefined> = {}
for (const key of CONFIG_KEYS) {
current[key] = draft[key] ?? resolved[key] ?? defaults?.[key]
}
return (
presets.find((p) =>
CONFIG_KEYS.every((key) => current[key] === p.config[key]),
)?.id ?? null
)
}, [presets, draft, resolved, defaults])
if (!isCompanyBrain) return null
const disabled = !isAdmin || modelsQuery.isLoading || update.isPending
@ -133,63 +278,212 @@ export default function CompanyBrainModels({
</p>
) : (
<div className="flex flex-col gap-4">
{ROWS.map(({ role, title, help }) => {
const options = choices?.[role] ?? []
const current = valueFor(role)
return (
<div key={role} className="flex flex-col gap-1.5">
<div className="flex items-center justify-between gap-3">
<span
className={cn(
dmSans125ClassName(),
"text-[13px] font-medium text-[#FAFAFA]",
)}
>
{title}
</span>
{defaults?.[role] === current ? (
<span
className={cn(
dmSans125ClassName(),
"text-[11px] text-[#737373]",
)}
>
Default
</span>
) : null}
</div>
<Select
value={current}
<div className="grid gap-3 sm:grid-cols-3">
{presets.map((preset) => {
const isActive = preset.id === activePresetId
return (
<button
key={preset.id}
type="button"
disabled={disabled}
onValueChange={(v) => setDraft((d) => ({ ...d, [role]: v }))}
>
<SelectTrigger className={controlClass}>
<SelectValue placeholder="Select a model…" />
</SelectTrigger>
<SelectContent className={selectContentClass}>
{options.map((id) => (
<SelectItem
key={id}
value={id}
className={selectItemClass}
>
{labelFor(id)}
{defaults?.[role] === id ? " (default)" : ""}
</SelectItem>
))}
</SelectContent>
</Select>
<span
aria-pressed={isActive}
onClick={() => {
if (!resolved) return
setDraft({ ...preset.config })
}}
className={cn(
dmSans125ClassName(),
"text-[12px] text-[#9A9A9A]",
"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]",
)}
>
{help}
</span>
</div>
)
})}
<span className="flex items-center justify-between gap-2">
<span className="truncate font-semibold text-[14px] tracking-[-0.15px] text-[#FAFAFA]">
{preset.label}
{preset.id === "balanced" ? (
<span className="ml-1.5 text-[11px] font-medium text-[#737B87]">
Recommended
</span>
) : null}
</span>
{isActive ? (
<Check className="size-4 shrink-0 text-[#6BB0FF]" />
) : null}
</span>
<span className="text-[12px] font-medium leading-[1.5] text-[#737373]">
{preset.description}
</span>
</button>
)
})}
</div>
<button
type="button"
onClick={() => setAdvancedOpen((open) => !open)}
className={cn(
dmSans125ClassName(),
"flex w-fit cursor-pointer items-center gap-1.5 rounded-full px-1 py-1 text-[12px] font-medium text-[#8B929E] transition-colors hover:text-[#FAFAFA]",
)}
>
<ChevronDown
className={cn(
"size-3.5 transition-transform",
(advancedOpen || activePresetId === null) && "rotate-180",
)}
/>
Advanced
{activePresetId === null ? (
<span className="text-[11px] text-[#5F6673]">
· custom settings in use
</span>
) : null}
</button>
{advancedOpen || activePresetId === null ? (
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{ROWS.map(({ role, effortKey, title, help, effortHelp }) => {
const options = choices?.[role] ?? []
const current = valueFor(role)
const effortOptions = choices?.[effortKey] ?? []
const currentEffort = effortFor(effortKey)
const isDefault =
defaults?.[role] === current &&
(effortOptions.length === 0 ||
defaults?.[effortKey] === currentEffort)
return (
<div
key={role}
className="flex min-w-0 flex-col gap-5 rounded-xl bg-[#14161A] p-5 shadow-[inset_2.42px_2.42px_4.263px_rgba(11,15,21,0.7)]"
>
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<p
className={cn(
dmSans125ClassName(),
"truncate font-semibold text-[15px] tracking-[-0.15px] text-[#FAFAFA]",
)}
>
{title}
</p>
<p
className={cn(
dmSans125ClassName(),
"mt-1.5 min-h-[2lh] text-[12px] font-medium leading-[1.55] text-[#737373]",
)}
>
{help}
</p>
</div>
{isDefault ? (
<span
className={cn(
dmSans125ClassName(),
"shrink-0 rounded-full border border-white/[0.08] px-2 py-0.5 text-[10px] font-medium text-[#737B87]",
)}
>
Default
</span>
) : null}
</div>
<div className="flex flex-col gap-2">
<span className={fieldLabel}>Model</span>
<Select
value={current}
disabled={disabled}
onValueChange={(v) =>
setDraft((d) => ({ ...d, [role]: v }))
}
>
<SelectTrigger className={controlClass}>
<SelectValue placeholder="Select a model…" />
</SelectTrigger>
<SelectContent className={selectContentClass}>
{options.map((id) => (
<SelectItem
key={id}
value={id}
className={selectItemClass}
>
{labelFor(id)}
{MODEL_TAGS[id] ? (
<span className="text-[#737B87]">
{" "}
· {MODEL_TAGS[id]}
</span>
) : null}
{defaults?.[role] === id ? " (default)" : ""}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{effortOptions.length > 0 ? (
<div className="flex flex-col gap-2">
<span className={fieldLabel}>Thinking depth</span>
<div className="flex w-full items-center gap-0.5 rounded-full bg-[#0D121A] p-0.5 shadow-[inset_1.5px_1.5px_4.5px_rgba(0,0,0,0.5)]">
{effortOptions.map((effort) => {
const isOn = currentEffort === effort
return (
<button
key={effort}
type="button"
aria-pressed={isOn}
disabled={disabled}
onClick={() =>
setDraft((currentDraft) => ({
...currentDraft,
[effortKey]: effort,
}))
}
className={cn(
dmSans125ClassName(),
"h-7 min-w-0 flex-1 cursor-pointer rounded-full px-1 text-[11.5px] font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50",
isOn
? "bg-white/[0.10] text-[#FAFAFA]"
: "text-[#8B929E] hover:text-[#FAFAFA]",
)}
>
{EFFORT_LABELS[effort]}
</button>
)
})}
</div>
<div className="flex items-center justify-between px-1.5 text-[10px] font-medium text-[#4A5260]">
<span>Faster</span>
<span>Smarter</span>
</div>
<p
className={cn(
dmSans125ClassName(),
"text-[11px] leading-[1.5] text-[#5F6673]",
)}
>
{effortHelp}
</p>
{currentEffort === "xhigh" &&
extraHighIsBounded(current) ? (
<p
className={cn(
dmSans125ClassName(),
"text-[11px] text-amber-300/80",
)}
>
Extra high maps to High for this model provider.
</p>
) : null}
</div>
) : null}
</div>
)
})}
</div>
) : null}
{!isAdmin ? (
<div className="flex items-center gap-1.5 text-[12px] text-[#737373]">
@ -197,29 +491,43 @@ export default function CompanyBrainModels({
Only organization admins can change these.
</div>
) : (
<div className="flex justify-end">
<button
type="button"
<div className="flex items-center justify-end gap-3">
{dirty ? (
<button
type="button"
onClick={() => setDraft({})}
disabled={update.isPending}
className={cn(
dmSans125ClassName(),
"h-9 rounded-full px-3 text-[13px] font-medium text-[#8B929E] transition-colors hover:text-[#FAFAFA] disabled:opacity-45",
)}
>
Reset
</button>
) : null}
<PillButton
disabled={disabled || !dirty}
onClick={() => {
const patch: Partial<Record<BrainModelRole, string>> = {}
for (const { role } of ROWS) {
const patch: Partial<BrainModelConfig> = {}
for (const { role, effortKey } of ROWS) {
if (draft[role] && draft[role] !== resolved?.[role]) {
patch[role] = draft[role]
}
if (
draft[effortKey] &&
draft[effortKey] !== resolved?.[effortKey]
) {
patch[effortKey] = draft[effortKey]
}
}
update.mutate(patch, { onSuccess: () => setDraft({}) })
}}
className={cn(
dmSans125ClassName(),
"inline-flex h-8 items-center gap-1.5 rounded-full bg-white px-4 text-[12px] font-medium text-black transition-opacity hover:opacity-90 disabled:opacity-40",
)}
>
{update.isPending ? (
<Loader2 className="size-3.5 animate-spin" />
) : null}
Save
</button>
</PillButton>
</div>
)}
</div>

View file

@ -7,13 +7,17 @@ const BACKEND =
const BASE = `${BACKEND}/brain/models`
export type BrainModelRole = "main" | "triage" | "research"
export type BrainReasoningEffort = "low" | "medium" | "high" | "xhigh"
export type BrainReasoningKey = "mainEffort" | "triageEffort" | "researchEffort"
export type BrainModelConfig = Record<BrainModelRole, string>
export type BrainModelConfig = Record<BrainModelRole, string> &
Partial<Record<BrainReasoningKey, BrainReasoningEffort>>
export type BrainModelsResponse = {
resolved: BrainModelConfig
defaults: BrainModelConfig
choices: Record<BrainModelRole, string[]>
choices: Record<BrainModelRole, string[]> &
Partial<Record<BrainReasoningKey, BrainReasoningEffort[]>>
}
export function useBrainModels(enabled: boolean) {