diff --git a/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/general/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/general/page.tsx new file mode 100644 index 000000000..ac50c654e --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/general/page.tsx @@ -0,0 +1,10 @@ +import { GeneralSettingsManager } from "@/components/settings/general-settings-manager"; + +export default async function Page({ + params, +}: { + params: Promise<{ search_space_id: string }>; +}) { + const { search_space_id } = await params; + return ; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/image-models/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/image-models/page.tsx new file mode 100644 index 000000000..22859859b --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/image-models/page.tsx @@ -0,0 +1,10 @@ +import { ImageModelManager } from "@/components/settings/image-model-manager"; + +export default async function Page({ + params, +}: { + params: Promise<{ search_space_id: string }>; +}) { + const { search_space_id } = await params; + return ; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/layout-shell.tsx b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/layout-shell.tsx new file mode 100644 index 000000000..96d77d131 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/layout-shell.tsx @@ -0,0 +1,179 @@ +"use client"; + +import { + BookText, + Bot, + Brain, + CircleUser, + Earth, + ImageIcon, + ListChecks, + ScanEye, + UserKey, +} from "lucide-react"; +import Link from "next/link"; +import { useSelectedLayoutSegment } from "next/navigation"; +import { useTranslations } from "next-intl"; +import type React from "react"; +import { useCallback, useMemo, useState } from "react"; +import { Separator } from "@/components/ui/separator"; +import { cn } from "@/lib/utils"; + +export type SearchSpaceSettingsTab = + | "general" + | "roles" + | "models" + | "image-models" + | "vision-models" + | "team-roles" + | "prompts" + | "team-memory" + | "public-links"; + +const DEFAULT_TAB: SearchSpaceSettingsTab = "general"; + +interface SearchSpaceSettingsLayoutShellProps { + searchSpaceId: string; + children: React.ReactNode; +} + +export function SearchSpaceSettingsLayoutShell({ + searchSpaceId, + children, +}: SearchSpaceSettingsLayoutShellProps) { + const t = useTranslations("searchSpaceSettings"); + const segment = useSelectedLayoutSegment(); + const [tabScrollPos, setTabScrollPos] = useState<"start" | "middle" | "end">("start"); + + const handleTabScroll = useCallback((e: React.UIEvent) => { + const el = e.currentTarget; + const atStart = el.scrollLeft <= 2; + const atEnd = el.scrollWidth - el.scrollLeft - el.clientWidth <= 2; + setTabScrollPos(atStart ? "start" : atEnd ? "end" : "middle"); + }, []); + + const navItems = useMemo( + () => [ + { + value: "general" as const, + label: t("nav_general"), + icon: , + }, + { + value: "roles" as const, + label: t("nav_role_assignments"), + icon: , + }, + { + value: "models" as const, + label: t("nav_agent_models"), + icon: , + }, + { + value: "image-models" as const, + label: t("nav_image_models"), + icon: , + }, + { + value: "vision-models" as const, + label: t("nav_vision_models"), + icon: , + }, + { + value: "team-roles" as const, + label: t("nav_team_roles"), + icon: , + }, + { + value: "prompts" as const, + label: t("nav_system_instructions"), + icon: , + }, + { + value: "team-memory" as const, + label: "Team Memory", + icon: , + }, + { + value: "public-links" as const, + label: t("nav_public_links"), + icon: , + }, + ], + [t] + ); + + const activeTab: SearchSpaceSettingsTab = + segment && navItems.some((item) => item.value === segment) + ? (segment as SearchSpaceSettingsTab) + : DEFAULT_TAB; + const selectedLabel = navItems.find((item) => item.value === activeTab)?.label ?? t("title"); + + const hrefFor = (tab: SearchSpaceSettingsTab) => + `/dashboard/${searchSpaceId}/search-space-settings/${tab}`; + + return ( +
+
+

{t("title")}

+ +
+
+ {navItems.map((item) => ( + + {item.icon} + {item.label} + + ))} +
+
+
+ +
+
+

{selectedLabel}

+ +
+
{children}
+
+
+ ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/layout.tsx b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/layout.tsx new file mode 100644 index 000000000..330158da7 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/layout.tsx @@ -0,0 +1,19 @@ +import type React from "react"; +import { use } from "react"; +import { SearchSpaceSettingsLayoutShell } from "./layout-shell"; + +export default function SearchSpaceSettingsLayout({ + params, + children, +}: { + params: Promise<{ search_space_id: string }>; + children: React.ReactNode; +}) { + const { search_space_id } = use(params); + + return ( + + {children} + + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/models/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/models/page.tsx new file mode 100644 index 000000000..b14a536ed --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/models/page.tsx @@ -0,0 +1,10 @@ +import { AgentModelManager } from "@/components/settings/agent-model-manager"; + +export default async function Page({ + params, +}: { + params: Promise<{ search_space_id: string }>; +}) { + const { search_space_id } = await params; + return ; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/page.tsx index 8dd7eb38e..27c59328b 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/page.tsx @@ -1,40 +1,10 @@ -import { - SearchSpaceSettingsPanel, - type SearchSpaceSettingsTab, -} from "@/components/settings/search-space-settings-panel"; - -const SEARCH_SPACE_SETTINGS_TABS = new Set([ - "general", - "roles", - "models", - "image-models", - "vision-models", - "team-roles", - "prompts", - "team-memory", - "public-links", -]); - -function getInitialTab(tab: string | string[] | undefined): SearchSpaceSettingsTab { - const value = Array.isArray(tab) ? tab[0] : tab; - return value && SEARCH_SPACE_SETTINGS_TABS.has(value) - ? (value as SearchSpaceSettingsTab) - : "general"; -} +import { redirect } from "next/navigation"; export default async function SearchSpaceSettingsPage({ params, - searchParams, }: { params: Promise<{ search_space_id: string }>; - searchParams: Promise<{ tab?: string | string[] }>; }) { - const [{ search_space_id }, resolvedSearchParams] = await Promise.all([params, searchParams]); - - return ( - - ); + const { search_space_id } = await params; + redirect(`/dashboard/${search_space_id}/search-space-settings/general`); } diff --git a/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/prompts/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/prompts/page.tsx new file mode 100644 index 000000000..fcdafdf2d --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/prompts/page.tsx @@ -0,0 +1,10 @@ +import { PromptConfigManager } from "@/components/settings/prompt-config-manager"; + +export default async function Page({ + params, +}: { + params: Promise<{ search_space_id: string }>; +}) { + const { search_space_id } = await params; + return ; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/public-links/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/public-links/page.tsx new file mode 100644 index 000000000..e4b5f35e7 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/public-links/page.tsx @@ -0,0 +1,10 @@ +import { PublicChatSnapshotsManager } from "@/components/public-chat-snapshots/public-chat-snapshots-manager"; + +export default async function Page({ + params, +}: { + params: Promise<{ search_space_id: string }>; +}) { + const { search_space_id } = await params; + return ; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/roles/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/roles/page.tsx new file mode 100644 index 000000000..8c43e7844 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/roles/page.tsx @@ -0,0 +1,10 @@ +import { LLMRoleManager } from "@/components/settings/llm-role-manager"; + +export default async function Page({ + params, +}: { + params: Promise<{ search_space_id: string }>; +}) { + const { search_space_id } = await params; + return ; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/team-memory/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/team-memory/page.tsx new file mode 100644 index 000000000..987b36513 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/team-memory/page.tsx @@ -0,0 +1,10 @@ +import { TeamMemoryManager } from "@/components/settings/team-memory-manager"; + +export default async function Page({ + params, +}: { + params: Promise<{ search_space_id: string }>; +}) { + const { search_space_id } = await params; + return ; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/team-roles/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/team-roles/page.tsx new file mode 100644 index 000000000..ca2108975 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/team-roles/page.tsx @@ -0,0 +1,10 @@ +import { RolesManager } from "@/components/settings/roles-manager"; + +export default async function Page({ + params, +}: { + params: Promise<{ search_space_id: string }>; +}) { + const { search_space_id } = await params; + return ; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/vision-models/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/vision-models/page.tsx new file mode 100644 index 000000000..8b8a2dc6d --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/search-space-settings/vision-models/page.tsx @@ -0,0 +1,10 @@ +import { VisionModelManager } from "@/components/settings/vision-model-manager"; + +export default async function Page({ + params, +}: { + params: Promise<{ search_space_id: string }>; +}) { + const { search_space_id } = await params; + return ; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx index cc83148a3..fadd617a5 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx @@ -545,7 +545,7 @@ function MemberRow({ - router.push(`/dashboard/${searchSpaceId}/search-space-settings?tab=team-roles`) + router.push(`/dashboard/${searchSpaceId}/search-space-settings/team-roles`) } > Manage Roles diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/agent-permissions/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/agent-permissions/page.tsx new file mode 100644 index 000000000..2b925bd29 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/agent-permissions/page.tsx @@ -0,0 +1,5 @@ +import { AgentPermissionsContent } from "../components/AgentPermissionsContent"; + +export default function Page() { + return ; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/agent-status/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/agent-status/page.tsx new file mode 100644 index 000000000..dc5c61d2a --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/agent-status/page.tsx @@ -0,0 +1,5 @@ +import { AgentStatusContent } from "../components/AgentStatusContent"; + +export default function Page() { + return ; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/api-key/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/api-key/page.tsx new file mode 100644 index 000000000..e59e50042 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/api-key/page.tsx @@ -0,0 +1,5 @@ +import { ApiKeyContent } from "../components/ApiKeyContent"; + +export default function Page() { + return ; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/community-prompts/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/community-prompts/page.tsx new file mode 100644 index 000000000..e962cbed2 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/community-prompts/page.tsx @@ -0,0 +1,5 @@ +import { CommunityPromptsContent } from "../components/CommunityPromptsContent"; + +export default function Page() { + return ; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/DesktopShortcutsContent.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/HotkeysContent.tsx similarity index 99% rename from surfsense_web/app/dashboard/[search_space_id]/user-settings/components/DesktopShortcutsContent.tsx rename to surfsense_web/app/dashboard/[search_space_id]/user-settings/components/HotkeysContent.tsx index 3abe46f6c..4f08eef3f 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/DesktopShortcutsContent.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/HotkeysContent.tsx @@ -122,7 +122,7 @@ function HotkeyRow({ ); } -export function DesktopShortcutsContent() { +export function HotkeysContent() { const api = useElectronAPI(); const [shortcuts, setShortcuts] = useState(DEFAULT_SHORTCUTS); const [shortcutsLoaded, setShortcutsLoaded] = useState(false); diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/desktop/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/desktop/page.tsx new file mode 100644 index 000000000..a7b0d1d27 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/desktop/page.tsx @@ -0,0 +1,5 @@ +import { DesktopContent } from "../components/DesktopContent"; + +export default function Page() { + return ; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/hotkeys/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/hotkeys/page.tsx new file mode 100644 index 000000000..8f776f738 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/hotkeys/page.tsx @@ -0,0 +1,5 @@ +import { HotkeysContent } from "../components/HotkeysContent"; + +export default function Page() { + return ; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/layout-shell.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/layout-shell.tsx new file mode 100644 index 000000000..e241dbf59 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/layout-shell.tsx @@ -0,0 +1,191 @@ +"use client"; + +import { + Brain, + CircleUser, + Globe, + Keyboard, + KeyRound, + Monitor, + ReceiptText, + ShieldCheck, + Sparkles, + Workflow, +} from "lucide-react"; +import Link from "next/link"; +import { useSelectedLayoutSegment } from "next/navigation"; +import { useTranslations } from "next-intl"; +import type React from "react"; +import { useCallback, useMemo, useState } from "react"; +import { Separator } from "@/components/ui/separator"; +import { usePlatform } from "@/hooks/use-platform"; +import { cn } from "@/lib/utils"; + +export type UserSettingsTab = + | "profile" + | "api-key" + | "prompts" + | "community-prompts" + | "memory" + | "agent-permissions" + | "agent-status" + | "purchases" + | "desktop" + | "hotkeys"; + +const DEFAULT_TAB: UserSettingsTab = "profile"; + +interface UserSettingsLayoutShellProps { + searchSpaceId: string; + children: React.ReactNode; +} + +export function UserSettingsLayoutShell({ + searchSpaceId, + children, +}: UserSettingsLayoutShellProps) { + const t = useTranslations("userSettings"); + const { isDesktop } = usePlatform(); + const segment = useSelectedLayoutSegment(); + const [tabScrollPos, setTabScrollPos] = useState<"start" | "middle" | "end">("start"); + + const handleTabScroll = useCallback((e: React.UIEvent) => { + const el = e.currentTarget; + const atStart = el.scrollLeft <= 2; + const atEnd = el.scrollWidth - el.scrollLeft - el.clientWidth <= 2; + setTabScrollPos(atStart ? "start" : atEnd ? "end" : "middle"); + }, []); + + const navItems = useMemo( + () => [ + { + value: "profile" as const, + label: t("profile_nav_label"), + icon: , + }, + { + value: "api-key" as const, + label: t("api_key_nav_label"), + icon: , + }, + { + value: "prompts" as const, + label: "My Prompts", + icon: , + }, + { + value: "community-prompts" as const, + label: "Community Prompts", + icon: , + }, + { + value: "memory" as const, + label: "Memory", + icon: , + }, + { + value: "agent-permissions" as const, + label: "Agent Permissions", + icon: , + }, + { + value: "agent-status" as const, + label: "Agent Status", + icon: , + }, + { + value: "purchases" as const, + label: "Purchase History", + icon: , + }, + ...(isDesktop + ? [ + { + value: "desktop" as const, + label: "App Preferences", + icon: , + }, + { + value: "hotkeys" as const, + label: "Hotkeys", + icon: , + }, + ] + : []), + ], + [t, isDesktop] + ); + + const activeTab: UserSettingsTab = + segment && navItems.some((item) => item.value === segment) + ? (segment as UserSettingsTab) + : DEFAULT_TAB; + const selectedLabel = navItems.find((item) => item.value === activeTab)?.label ?? t("title"); + + const hrefFor = (tab: UserSettingsTab) => `/dashboard/${searchSpaceId}/user-settings/${tab}`; + + return ( +
+
+

{t("title")}

+ +
+
+ {navItems.map((item) => ( + + {item.icon} + {item.label} + + ))} +
+
+
+ +
+
+

{selectedLabel}

+ +
+
{children}
+
+
+ ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/layout.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/layout.tsx new file mode 100644 index 000000000..e1f97a903 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/layout.tsx @@ -0,0 +1,17 @@ +import type React from "react"; +import { use } from "react"; +import { UserSettingsLayoutShell } from "./layout-shell"; + +export default function UserSettingsLayout({ + params, + children, +}: { + params: Promise<{ search_space_id: string }>; + children: React.ReactNode; +}) { + const { search_space_id } = use(params); + + return ( + {children} + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/memory/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/memory/page.tsx new file mode 100644 index 000000000..b10c5bce5 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/memory/page.tsx @@ -0,0 +1,5 @@ +import { MemoryContent } from "../components/MemoryContent"; + +export default function Page() { + return ; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/page.tsx index 48a5e7cde..dde643142 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/user-settings/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/page.tsx @@ -1,36 +1,10 @@ -import { UserSettingsPanel, type UserSettingsTab } from "@/components/settings/user-settings-panel"; - -const USER_SETTINGS_TABS = new Set([ - "profile", - "api-key", - "prompts", - "community-prompts", - "memory", - "agent-permissions", - "agent-status", - "purchases", - "desktop", - "desktop-shortcuts", -]); - -function getInitialTab(tab: string | string[] | undefined): UserSettingsTab { - const value = Array.isArray(tab) ? tab[0] : tab; - return value && USER_SETTINGS_TABS.has(value) ? (value as UserSettingsTab) : "profile"; -} +import { redirect } from "next/navigation"; export default async function UserSettingsPage({ params, - searchParams, }: { params: Promise<{ search_space_id: string }>; - searchParams: Promise<{ tab?: string | string[] }>; }) { - const [{ search_space_id }, resolvedSearchParams] = await Promise.all([params, searchParams]); - - return ( - - ); + const { search_space_id } = await params; + redirect(`/dashboard/${search_space_id}/user-settings/profile`); } diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/profile/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/profile/page.tsx new file mode 100644 index 000000000..9275ed26c --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/profile/page.tsx @@ -0,0 +1,5 @@ +import { ProfileContent } from "../components/ProfileContent"; + +export default function Page() { + return ; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/prompts/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/prompts/page.tsx new file mode 100644 index 000000000..559ce0a95 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/prompts/page.tsx @@ -0,0 +1,5 @@ +import { PromptsContent } from "../components/PromptsContent"; + +export default function Page() { + return ; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/purchases/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/purchases/page.tsx new file mode 100644 index 000000000..3fa08c278 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/purchases/page.tsx @@ -0,0 +1,5 @@ +import { PurchaseHistoryContent } from "../components/PurchaseHistoryContent"; + +export default function Page() { + return ; +} diff --git a/surfsense_web/components/assistant-ui/connector-popup.tsx b/surfsense_web/components/assistant-ui/connector-popup.tsx index 9150fb35f..6699a5567 100644 --- a/surfsense_web/components/assistant-ui/connector-popup.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup.tsx @@ -396,7 +396,7 @@ export const ConnectorIndicator = forwardRef { handleOpenChange(false); router.push( - `/dashboard/${searchSpaceId}/search-space-settings?tab=models` + `/dashboard/${searchSpaceId}/search-space-settings/models` ); }} > diff --git a/surfsense_web/components/assistant-ui/document-upload-popup.tsx b/surfsense_web/components/assistant-ui/document-upload-popup.tsx index 0067e25b6..88e99e67f 100644 --- a/surfsense_web/components/assistant-ui/document-upload-popup.tsx +++ b/surfsense_web/components/assistant-ui/document-upload-popup.tsx @@ -162,7 +162,7 @@ const DocumentUploadPopupContent: FC<{ variant="outline" onClick={() => { onOpenChange(false); - router.push(`/dashboard/${searchSpaceId}/search-space-settings?tab=models`); + router.push(`/dashboard/${searchSpaceId}/search-space-settings/models`); }} > diff --git a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx index d46895a0a..5d1f2566b 100644 --- a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx +++ b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx @@ -659,8 +659,8 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid // Detect if we're on the chat page (needs overflow-hidden for chat's own scroll) const isChatPage = pathname?.includes("/new-chat") ?? false; - const isUserSettingsPage = pathname?.endsWith("/user-settings") === true; - const isSearchSpaceSettingsPage = pathname?.endsWith("/search-space-settings") === true; + const isUserSettingsPage = pathname?.includes("/user-settings") === true; + const isSearchSpaceSettingsPage = pathname?.includes("/search-space-settings") === true; const useWorkspacePanel = pathname?.endsWith("/buy-more") === true || pathname?.endsWith("/more-pages") === true || diff --git a/surfsense_web/components/new-chat/chat-share-button.tsx b/surfsense_web/components/new-chat/chat-share-button.tsx index 3b4ff0654..101f73ade 100644 --- a/surfsense_web/components/new-chat/chat-share-button.tsx +++ b/surfsense_web/components/new-chat/chat-share-button.tsx @@ -149,7 +149,7 @@ export function ChatShareButton({ thread, onVisibilityChange, className }: ChatS size="icon" onClick={() => router.push( - `/dashboard/${thread.search_space_id}/search-space-settings?tab=public-links` + `/dashboard/${thread.search_space_id}/search-space-settings/public-links` ) } className="size-8 bg-muted/50 hover:bg-accent hover:text-accent-foreground" diff --git a/surfsense_web/components/new-chat/prompt-picker.tsx b/surfsense_web/components/new-chat/prompt-picker.tsx index 7700dd0b2..593e7e04f 100644 --- a/surfsense_web/components/new-chat/prompt-picker.tsx +++ b/surfsense_web/components/new-chat/prompt-picker.tsx @@ -72,7 +72,7 @@ export const PromptPicker = forwardRef(funct if (index === createPromptIndex) { onDone(); if (searchSpaceId) { - router.push(`/dashboard/${searchSpaceId}/user-settings?tab=prompts`); + router.push(`/dashboard/${searchSpaceId}/user-settings/prompts`); } return; } diff --git a/surfsense_web/components/settings/search-space-settings-panel.tsx b/surfsense_web/components/settings/search-space-settings-panel.tsx deleted file mode 100644 index 248b2c585..000000000 --- a/surfsense_web/components/settings/search-space-settings-panel.tsx +++ /dev/null @@ -1,245 +0,0 @@ -"use client"; - -import { - BookText, - Bot, - Brain, - CircleUser, - Earth, - ImageIcon, - ListChecks, - ScanEye, - UserKey, -} from "lucide-react"; -import dynamic from "next/dynamic"; -import { useRouter } from "next/navigation"; -import { useTranslations } from "next-intl"; -import type React from "react"; -import { useCallback, useEffect, useMemo, useState } from "react"; -import { Button } from "@/components/ui/button"; -import { Separator } from "@/components/ui/separator"; -import { cn } from "@/lib/utils"; - -const GeneralSettingsManager = dynamic( - () => - import("@/components/settings/general-settings-manager").then((m) => ({ - default: m.GeneralSettingsManager, - })), - { ssr: false } -); -const AgentModelManager = dynamic( - () => - import("@/components/settings/agent-model-manager").then((m) => ({ - default: m.AgentModelManager, - })), - { ssr: false } -); -const LLMRoleManager = dynamic( - () => - import("@/components/settings/llm-role-manager").then((m) => ({ default: m.LLMRoleManager })), - { ssr: false } -); -const ImageModelManager = dynamic( - () => - import("@/components/settings/image-model-manager").then((m) => ({ - default: m.ImageModelManager, - })), - { ssr: false } -); -const VisionModelManager = dynamic( - () => - import("@/components/settings/vision-model-manager").then((m) => ({ - default: m.VisionModelManager, - })), - { ssr: false } -); -const RolesManager = dynamic( - () => import("@/components/settings/roles-manager").then((m) => ({ default: m.RolesManager })), - { ssr: false } -); -const PromptConfigManager = dynamic( - () => - import("@/components/settings/prompt-config-manager").then((m) => ({ - default: m.PromptConfigManager, - })), - { ssr: false } -); -const PublicChatSnapshotsManager = dynamic( - () => - import("@/components/public-chat-snapshots/public-chat-snapshots-manager").then((m) => ({ - default: m.PublicChatSnapshotsManager, - })), - { ssr: false } -); -const TeamMemoryManager = dynamic( - () => - import("@/components/settings/team-memory-manager").then((m) => ({ - default: m.TeamMemoryManager, - })), - { ssr: false } -); - -export type SearchSpaceSettingsTab = - | "general" - | "roles" - | "models" - | "image-models" - | "vision-models" - | "team-roles" - | "prompts" - | "team-memory" - | "public-links"; - -interface SearchSpaceSettingsPanelProps { - searchSpaceId: string; - initialTab?: SearchSpaceSettingsTab; -} - -export function SearchSpaceSettingsPanel({ - searchSpaceId, - initialTab = "general", -}: SearchSpaceSettingsPanelProps) { - const t = useTranslations("searchSpaceSettings"); - const router = useRouter(); - const numericSearchSpaceId = Number(searchSpaceId); - const [activeTab, setActiveTab] = useState(initialTab); - const [tabScrollPos, setTabScrollPos] = useState<"start" | "middle" | "end">("start"); - - useEffect(() => { - setActiveTab(initialTab); - }, [initialTab]); - - const handleTabScroll = useCallback((e: React.UIEvent) => { - const el = e.currentTarget; - const atStart = el.scrollLeft <= 2; - const atEnd = el.scrollWidth - el.scrollLeft - el.clientWidth <= 2; - setTabScrollPos(atStart ? "start" : atEnd ? "end" : "middle"); - }, []); - - const navItems = useMemo( - () => [ - { value: "general", label: t("nav_general"), icon: }, - { value: "roles", label: t("nav_role_assignments"), icon: }, - { value: "models", label: t("nav_agent_models"), icon: }, - { - value: "image-models", - label: t("nav_image_models"), - icon: , - }, - { - value: "vision-models", - label: t("nav_vision_models"), - icon: , - }, - { value: "team-roles", label: t("nav_team_roles"), icon: }, - { - value: "prompts", - label: t("nav_system_instructions"), - icon: , - }, - { - value: "team-memory", - label: "Team Memory", - icon: , - }, - { value: "public-links", label: t("nav_public_links"), icon: }, - ], - [t] - ); - - const selectedTab = navItems.some((item) => item.value === activeTab) ? activeTab : "general"; - const selectedLabel = navItems.find((item) => item.value === selectedTab)?.label ?? t("title"); - - const handleItemChange = (tab: SearchSpaceSettingsTab) => { - setActiveTab(tab); - const suffix = tab === "general" ? "" : `?tab=${tab}`; - router.replace(`/dashboard/${searchSpaceId}/search-space-settings${suffix}`, { scroll: false }); - }; - - return ( -
-
-

{t("title")}

- -
-
- {navItems.map((item) => ( - - ))} -
-
-
- -
-
-

{selectedLabel}

- -
-
- {selectedTab === "general" && ( - - )} - {selectedTab === "models" && } - {selectedTab === "roles" && ( - - )} - {selectedTab === "image-models" && ( - - )} - {selectedTab === "vision-models" && ( - - )} - {selectedTab === "team-roles" && } - {selectedTab === "prompts" && ( - - )} - {selectedTab === "team-memory" && ( - - )} - {selectedTab === "public-links" && ( - - )} -
-
-
- ); -} diff --git a/surfsense_web/components/settings/user-settings-panel.tsx b/surfsense_web/components/settings/user-settings-panel.tsx deleted file mode 100644 index b4924c16a..000000000 --- a/surfsense_web/components/settings/user-settings-panel.tsx +++ /dev/null @@ -1,271 +0,0 @@ -"use client"; - -import { - Brain, - CircleUser, - Globe, - Keyboard, - KeyRound, - Monitor, - ReceiptText, - ShieldCheck, - Sparkles, - Workflow, -} from "lucide-react"; -import dynamic from "next/dynamic"; -import { useRouter } from "next/navigation"; -import { useTranslations } from "next-intl"; -import { useCallback, useEffect, useMemo, useState } from "react"; -import { Button } from "@/components/ui/button"; -import { Separator } from "@/components/ui/separator"; -import { usePlatform } from "@/hooks/use-platform"; -import { cn } from "@/lib/utils"; - -const ProfileContent = dynamic( - () => - import("@/app/dashboard/[search_space_id]/user-settings/components/ProfileContent").then( - (m) => ({ default: m.ProfileContent }) - ), - { ssr: false } -); -const ApiKeyContent = dynamic( - () => - import("@/app/dashboard/[search_space_id]/user-settings/components/ApiKeyContent").then( - (m) => ({ default: m.ApiKeyContent }) - ), - { ssr: false } -); -const PromptsContent = dynamic( - () => - import("@/app/dashboard/[search_space_id]/user-settings/components/PromptsContent").then( - (m) => ({ default: m.PromptsContent }) - ), - { ssr: false } -); -const CommunityPromptsContent = dynamic( - () => - import( - "@/app/dashboard/[search_space_id]/user-settings/components/CommunityPromptsContent" - ).then((m) => ({ default: m.CommunityPromptsContent })), - { ssr: false } -); -const PurchaseHistoryContent = dynamic( - () => - import( - "@/app/dashboard/[search_space_id]/user-settings/components/PurchaseHistoryContent" - ).then((m) => ({ default: m.PurchaseHistoryContent })), - { ssr: false } -); -const DesktopContent = dynamic( - () => - import("@/app/dashboard/[search_space_id]/user-settings/components/DesktopContent").then( - (m) => ({ default: m.DesktopContent }) - ), - { ssr: false } -); -const DesktopShortcutsContent = dynamic( - () => - import( - "@/app/dashboard/[search_space_id]/user-settings/components/DesktopShortcutsContent" - ).then((m) => ({ default: m.DesktopShortcutsContent })), - { ssr: false } -); -const MemoryContent = dynamic( - () => - import("@/app/dashboard/[search_space_id]/user-settings/components/MemoryContent").then( - (m) => ({ default: m.MemoryContent }) - ), - { ssr: false } -); -const AgentPermissionsContent = dynamic( - () => - import( - "@/app/dashboard/[search_space_id]/user-settings/components/AgentPermissionsContent" - ).then((m) => ({ default: m.AgentPermissionsContent })), - { ssr: false } -); -const AgentStatusContent = dynamic( - () => - import("@/app/dashboard/[search_space_id]/user-settings/components/AgentStatusContent").then( - (m) => ({ default: m.AgentStatusContent }) - ), - { ssr: false } -); - -export type UserSettingsTab = - | "profile" - | "api-key" - | "prompts" - | "community-prompts" - | "memory" - | "agent-permissions" - | "agent-status" - | "purchases" - | "desktop" - | "desktop-shortcuts"; - -interface UserSettingsPanelProps { - searchSpaceId: string; - initialTab?: UserSettingsTab; -} - -export function UserSettingsPanel({ - searchSpaceId, - initialTab = "profile", -}: UserSettingsPanelProps) { - const t = useTranslations("userSettings"); - const router = useRouter(); - const { isDesktop } = usePlatform(); - const [activeTab, setActiveTab] = useState(initialTab); - const [tabScrollPos, setTabScrollPos] = useState<"start" | "middle" | "end">("start"); - - useEffect(() => { - setActiveTab(initialTab); - }, [initialTab]); - - const handleTabScroll = useCallback((e: React.UIEvent) => { - const el = e.currentTarget; - const atStart = el.scrollLeft <= 2; - const atEnd = el.scrollWidth - el.scrollLeft - el.clientWidth <= 2; - setTabScrollPos(atStart ? "start" : atEnd ? "end" : "middle"); - }, []); - - const navItems = useMemo( - () => [ - { value: "profile", label: t("profile_nav_label"), icon: }, - { - value: "api-key", - label: t("api_key_nav_label"), - icon: , - }, - { - value: "prompts", - label: "My Prompts", - icon: , - }, - { - value: "community-prompts", - label: "Community Prompts", - icon: , - }, - { - value: "memory", - label: "Memory", - icon: , - }, - { - value: "agent-permissions", - label: "Agent Permissions", - icon: , - }, - { - value: "agent-status", - label: "Agent Status", - icon: , - }, - { - value: "purchases", - label: "Purchase History", - icon: , - }, - ...(isDesktop - ? [ - { - value: "desktop" as const, - label: "App Preferences", - icon: , - }, - { - value: "desktop-shortcuts" as const, - label: "Hotkeys", - icon: , - }, - ] - : []), - ], - [t, isDesktop] - ); - - const selectedTab = navItems.some((item) => item.value === activeTab) ? activeTab : "profile"; - const selectedLabel = navItems.find((item) => item.value === selectedTab)?.label ?? t("title"); - - const handleItemChange = (tab: UserSettingsTab) => { - setActiveTab(tab); - const suffix = tab === "profile" ? "" : `?tab=${tab}`; - router.replace(`/dashboard/${searchSpaceId}/user-settings${suffix}`, { scroll: false }); - }; - - return ( -
-
-

{t("title")}

- -
-
- {navItems.map((item) => ( - - ))} -
-
-
- -
-
-

{selectedLabel}

- -
-
- {selectedTab === "profile" && } - {selectedTab === "api-key" && } - {selectedTab === "prompts" && } - {selectedTab === "community-prompts" && } - {selectedTab === "memory" && } - {selectedTab === "agent-permissions" && } - {selectedTab === "agent-status" && } - {selectedTab === "purchases" && } - {selectedTab === "desktop" && } - {selectedTab === "desktop-shortcuts" && } -
-
-
- ); -}