diff --git a/apps/web/components/settings/account.tsx b/apps/web/components/settings/account.tsx
index 9d61067b..ffea566c 100644
--- a/apps/web/components/settings/account.tsx
+++ b/apps/web/components/settings/account.tsx
@@ -39,6 +39,7 @@ import { useQueryState } from "nuqs"
import { useEffect, useMemo, useRef, useState } from "react"
import { toast } from "sonner"
import { useContainerTags } from "@/hooks/use-container-tags"
+import { useHasCompanyBrain } from "@/hooks/use-company-brain"
import { PopoverAnchor } from "@ui/components/popover"
import { WorkspacePersona } from "@/components/settings/workspace-persona"
import { OrgPlanBadge } from "@/components/org-plan-badge"
@@ -135,6 +136,7 @@ function isPendingInvitation(invitation: {
export default function Account() {
const { user, org, refetchActiveOrg, refetchOrganizations } = useAuth()
const autumn = useCustomer()
+ const isCompanyBrain = useHasCompanyBrain()
const { currentPlan, searchesUsed } = useTokenUsage(autumn)
const { data: orgSummaries } = useOrgSummaries()
const orgSummary = orgSummaries?.find((s) => s.orgId === org?.id)
@@ -614,7 +616,7 @@ export default function Account() {
- {canManageTeam && }
+ {canManageTeam && isCompanyBrain && }
diff --git a/apps/web/components/settings/workspace-persona.tsx b/apps/web/components/settings/workspace-persona.tsx
index c1f29f5f..d26e17d8 100644
--- a/apps/web/components/settings/workspace-persona.tsx
+++ b/apps/web/components/settings/workspace-persona.tsx
@@ -1,36 +1,70 @@
"use client"
import { LoaderIcon } from "lucide-react"
-import { useEffect, useState } from "react"
+import { useEffect, useRef, useState } from "react"
import { useOrgSettings, useUpdateOrgSettings } from "@/hooks/use-org-settings"
import { dmSans125ClassName, dmSansClassName } from "@/lib/fonts"
import { cn } from "@lib/utils"
+import { useAuth } from "@lib/auth-context"
-function SectionTitle({ children }: { children: React.ReactNode }) {
+const DESCRIPTION_ID = "workspace-persona-description"
+const COUNTER_ID = "workspace-persona-counter"
+const HEADING_ID = "workspace-persona-heading"
+
+function SectionHeading({ children }: { children: React.ReactNode }) {
return (
-
{children}
-
+
+ )
+}
+
+function PersonaHeader() {
+ return (
+
+
Workspace Persona
+
+ Guide how Company Brain communicates and works with your workspace.
+
+
)
}
export function WorkspacePersona() {
- const { data: settings, isLoading, isError } = useOrgSettings()
+ const { org } = useAuth()
+ const settingsQuery = useOrgSettings()
const updateSettings = useUpdateOrgSettings()
const [persona, setPersona] = useState("")
+ const syncedPersona = useRef<{ orgId: string; value: string } | null>(null)
- const savedPersona = settings?.workspacePersona ?? ""
+ const savedPersona = settingsQuery.data?.workspacePersona ?? ""
const dirty = persona.trim() !== savedPersona.trim()
- const settingsReady = !isLoading && !isError
useEffect(() => {
- setPersona(settings?.workspacePersona ?? "")
- }, [settings?.workspacePersona])
+ if (!settingsQuery.data) return
+
+ const nextPersona = settingsQuery.data.workspacePersona ?? ""
+ const orgId = org?.id ?? ""
+ const previous = syncedPersona.current
+
+ setPersona((current) => {
+ if (!previous || previous.orgId !== orgId) return nextPersona
+ return current === previous.value ? nextPersona : current
+ })
+ syncedPersona.current = { orgId, value: nextPersona }
+ }, [org?.id, settingsQuery.data])
const handleSave = () => {
updateSettings.mutate({
@@ -39,63 +73,92 @@ export function WorkspacePersona() {
}
return (
-
-
-
Workspace Persona
-
+
+
+ {settingsQuery.isLoading ? (
+
- Guide how Company Brain communicates and works with your workspace.
-
-
-
-
+ ) : settingsQuery.isError ? (
+
+
+ Workspace persona could not be loaded.
+
+
+
+ ) : (
+
+ )}
)
}
diff --git a/apps/web/hooks/use-org-settings.ts b/apps/web/hooks/use-org-settings.ts
index 39f21d3c..af752ec5 100644
--- a/apps/web/hooks/use-org-settings.ts
+++ b/apps/web/hooks/use-org-settings.ts
@@ -3,8 +3,6 @@ import { toast } from "sonner"
import { $fetch } from "@lib/api"
import { useAuth } from "@lib/auth-context"
-const API_BASE = `${process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai"}/v3`
-
export type OrgSettings = {
shouldLLMFilter: boolean
filterPrompt: string | null
@@ -52,25 +50,19 @@ export function useUpdateOrgSettings() {
return useMutation({
mutationFn: async (settings: Partial
) => {
- const res = await fetch(`${API_BASE}/settings`, {
- method: "PATCH",
- credentials: "include",
- headers: {
- "Content-Type": "application/json",
- "X-App-Source": "nova",
- },
- body: JSON.stringify(settings),
+ const response = await $fetch("@patch/settings", {
+ body: settings,
})
- if (!res.ok) {
- const body = (await res.json().catch(() => ({}))) as {
- message?: string
- }
- throw new Error(body?.message || "Failed to save settings")
+ if (response.error) {
+ throw new Error(response.error.message || "Failed to save settings")
}
- return res.json()
+ return response.data
},
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ["settings", "org", orgId] })
+ onSuccess: (_data, settings) => {
+ queryClient.setQueryData(
+ ["settings", "org", orgId],
+ (current) => (current ? { ...current, ...settings } : current),
+ )
toast.success("Settings saved")
},
onError: (error) => {
diff --git a/packages/lib/api.ts b/packages/lib/api.ts
index 42901c0b..4dc73327 100644
--- a/packages/lib/api.ts
+++ b/packages/lib/api.ts
@@ -33,10 +33,11 @@ import {
const SettingsResponseSchema = z.object({
message: z.string(),
settings: z.object({
- excludeItems: z.array(z.string().min(1).max(20)).optional(),
- filterPrompt: z.string().min(1).max(750).optional(),
- includeItems: z.array(z.string().min(1).max(20)).optional(),
+ excludeItems: z.array(z.string().min(1).max(20)).nullable().optional(),
+ filterPrompt: z.string().min(1).max(750).nullable().optional(),
+ includeItems: z.array(z.string().min(1).max(20)).nullable().optional(),
shouldLLMFilter: z.boolean().optional(),
+ workspacePersona: z.string().max(1500).nullable().optional(),
}),
})
diff --git a/packages/validation/api.ts b/packages/validation/api.ts
index 8579ca40..dd4f83e8 100644
--- a/packages/validation/api.ts
+++ b/packages/validation/api.ts
@@ -801,7 +801,7 @@ export const SettingsRequestSchema = OrganizationSettingsSchema.omit({
id: true,
orgId: true,
updatedAt: true,
-})
+}).partial()
export const ConnectionResponseSchema = z.object({
createdAt: z.string().datetime(),
diff --git a/packages/validation/schemas.ts b/packages/validation/schemas.ts
index 0bd73a1e..298b020b 100644
--- a/packages/validation/schemas.ts
+++ b/packages/validation/schemas.ts
@@ -315,6 +315,7 @@ export const OrganizationSettingsSchema = z.object({
filterPrompt: z.string().nullable().optional(),
includeItems: z.array(z.string()).nullable().optional(),
excludeItems: z.array(z.string()).nullable().optional(),
+ workspacePersona: z.string().max(1500).nullable().optional(),
// Google Drive custom keys
googleDriveCustomKeyEnabled: z.boolean().default(false),