mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
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:
parent
20e585cd00
commit
9824f0c9cd
3 changed files with 393 additions and 80 deletions
|
|
@ -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,
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue