diff --git a/apps/web/components/configure-view.tsx b/apps/web/components/configure-view.tsx index 0b75762e..c36ea5e4 100644 --- a/apps/web/components/configure-view.tsx +++ b/apps/web/components/configure-view.tsx @@ -1,15 +1,21 @@ "use client" import { cn } from "@lib/utils" -import { Blocks, CalendarClock, Cpu } from "lucide-react" +import { Blocks, CalendarClock, Cpu, ScrollText } from "lucide-react" import { useState } from "react" import CompanyBrainConnections from "@/components/settings/company-brain-connections" import CompanyBrainModels from "@/components/settings/company-brain-models" import Proactiveness from "@/components/settings/proactiveness" +import { WorkspacePrompt } from "@/components/settings/workspace-prompt" import { ErrorBoundary } from "@/components/error-boundary" +import { useAuth } from "@lib/auth-context" import { dmSans125ClassName } from "@/lib/fonts" -type ConfigureSection = "company-brain" | "models" | "automations" +type ConfigureSection = + | "company-brain" + | "models" + | "workspace-prompt" + | "automations" const SECTIONS: { id: ConfigureSection @@ -31,6 +37,13 @@ const SECTIONS: { "Pick how fast or thorough your brain should be. Fine-tune each task under Advanced.", icon: Cpu, }, + { + id: "workspace-prompt", + label: "Workspace Prompt", + description: + "Persistent guidance for how your brain works across the workspace. Fixed safety, access, and approval constraints still apply.", + icon: ScrollText, + }, { id: "automations", label: "Automations", @@ -41,6 +54,7 @@ const SECTIONS: { ] export function ConfigureView() { + const { org } = useAuth() const [activeSection, setActiveSection] = useState("company-brain") const active = SECTIONS.find((section) => section.id === activeSection) @@ -118,6 +132,8 @@ export function ConfigureView() { ) : activeSection === "models" ? ( + ) : activeSection === "workspace-prompt" ? ( + ) : ( )} 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 ? ( + + + ) : settingsQuery.isError ? ( +
+

+ Workspace prompt could not be loaded. +

+ +
+ ) : ( +
+