From 11099695da34cbce5b36ccf2bcf576e7dfbfd4fd Mon Sep 17 00:00:00 2001 From: bkellam Date: Wed, 23 Jul 2025 14:45:55 -0700 Subject: [PATCH] fix: Move search mode selection into a cookie to avoid SSR flashes --- packages/web/src/actions.ts | 9 ++++++++- .../[domain]/chat/components/newChatPanel.tsx | 2 +- .../components/homepage/agenticSearch.tsx | 2 +- .../[domain]/components/homepage/index.tsx | 20 +++++++++++++------ packages/web/src/app/[domain]/page.tsx | 12 +++++++++++ packages/web/src/lib/constants.ts | 1 + 6 files changed, 37 insertions(+), 9 deletions(-) diff --git a/packages/web/src/actions.ts b/packages/web/src/actions.ts index ed73950a..7ac7c02d 100644 --- a/packages/web/src/actions.ts +++ b/packages/web/src/actions.ts @@ -25,7 +25,7 @@ import { auth } from "./auth"; import { getConnection } from "./data/connection"; import { IS_BILLING_ENABLED } from "./ee/features/billing/stripe"; import InviteUserEmail from "./emails/inviteUserEmail"; -import { MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME, SINGLE_TENANT_ORG_DOMAIN, SOURCEBOT_GUEST_USER_ID, SOURCEBOT_SUPPORT_EMAIL } from "./lib/constants"; +import { MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME, SEARCH_MODE_COOKIE_NAME, SINGLE_TENANT_ORG_DOMAIN, SOURCEBOT_GUEST_USER_ID, SOURCEBOT_SUPPORT_EMAIL } from "./lib/constants"; import { orgDomainSchema, orgNameSchema, repositoryQuerySchema } from "./lib/schemas"; import { TenancyMode, ApiKeyPayload } from "./lib/types"; import { decrementOrgSeatCount, getSubscriptionForOrg } from "./ee/features/billing/serverUtils"; @@ -1998,6 +1998,13 @@ export const setAnonymousAccessStatus = async (domain: string, enabled: boolean) }); }); +export async function setSearchModeCookie(searchMode: "precise" | "agentic") { + const cookieStore = await cookies(); + cookieStore.set(SEARCH_MODE_COOKIE_NAME, searchMode, { + httpOnly: false, // Allow client-side access + }); +} + ////// Helpers /////// const parseConnectionConfig = (config: string) => { diff --git a/packages/web/src/app/[domain]/chat/components/newChatPanel.tsx b/packages/web/src/app/[domain]/chat/components/newChatPanel.tsx index 1c5295b9..e8573864 100644 --- a/packages/web/src/app/[domain]/chat/components/newChatPanel.tsx +++ b/packages/web/src/app/[domain]/chat/components/newChatPanel.tsx @@ -22,7 +22,7 @@ export const NewChatPanel = ({ repos, order, }: NewChatPanelProps) => { - const [selectedRepos, setSelectedRepos] = useLocalStorage("selectedRepos", []); + const [selectedRepos, setSelectedRepos] = useLocalStorage("selectedRepos", [], { initializeWithValue: false }); const { createNewChatThread, isLoading } = useCreateNewChatThread(); const [isRepoSelectorOpen, setIsRepoSelectorOpen] = useState(false); diff --git a/packages/web/src/app/[domain]/components/homepage/agenticSearch.tsx b/packages/web/src/app/[domain]/components/homepage/agenticSearch.tsx index ed463120..957b6c5f 100644 --- a/packages/web/src/app/[domain]/components/homepage/agenticSearch.tsx +++ b/packages/web/src/app/[domain]/components/homepage/agenticSearch.tsx @@ -126,7 +126,7 @@ export const AgenticSearch = ({ const { createNewChatThread, isLoading } = useCreateNewChatThread(); const dropdownRef = useRef(null); const editor = useSlate(); - const [selectedRepos, setSelectedRepos] = useLocalStorage("selectedRepos", []); + const [selectedRepos, setSelectedRepos] = useLocalStorage("selectedRepos", [], { initializeWithValue: false }); const domain = useDomain(); const [isRepoSelectorOpen, setIsRepoSelectorOpen] = useState(false); diff --git a/packages/web/src/app/[domain]/components/homepage/index.tsx b/packages/web/src/app/[domain]/components/homepage/index.tsx index 972ca34d..6fa4784d 100644 --- a/packages/web/src/app/[domain]/components/homepage/index.tsx +++ b/packages/web/src/app/[domain]/components/homepage/index.tsx @@ -4,11 +4,12 @@ import { SourcebotLogo } from "@/app/components/sourcebotLogo"; import { LanguageModelInfo } from "@/features/chat/types"; import { RepositoryQuery } from "@/lib/types"; import { useHotkeys } from "react-hotkeys-hook"; -import { useLocalStorage } from "usehooks-ts"; import { AgenticSearch } from "./agenticSearch"; import { PreciseSearch } from "./preciseSearch"; import { SearchMode } from "./toolbar"; import { CustomSlateEditor } from "@/features/chat/customSlateEditor"; +import { setSearchModeCookie } from "@/actions"; +import { useCallback, useState } from "react"; interface HomepageProps { initialRepos: RepositoryQuery[]; @@ -18,6 +19,7 @@ interface HomepageProps { createdAt: Date; name: string | null; }[]; + initialSearchMode: SearchMode; } @@ -25,13 +27,19 @@ export const Homepage = ({ initialRepos, languageModels, chatHistory, + initialSearchMode, }: HomepageProps) => { - const [searchMode, setSearchMode] = useLocalStorage("search-mode", "precise", { initializeWithValue: false }); + const [searchMode, setSearchMode] = useState(initialSearchMode); const isAgenticSearchEnabled = languageModels.length > 0; + const onSearchModeChanged = useCallback(async (newMode: SearchMode) => { + setSearchMode(newMode); + await setSearchModeCookie(newMode); + }, [setSearchMode]); + useHotkeys("mod+i", (e) => { e.preventDefault(); - setSearchMode("agentic"); + onSearchModeChanged("agentic"); }, { enableOnFormTags: true, enableOnContentEditable: true, @@ -40,7 +48,7 @@ export const Homepage = ({ useHotkeys("mod+p", (e) => { e.preventDefault(); - setSearchMode("precise"); + onSearchModeChanged("precise"); }, { enableOnFormTags: true, enableOnContentEditable: true, @@ -61,7 +69,7 @@ export const Homepage = ({ searchModeSelectorProps={{ searchMode: "precise", isAgenticSearchEnabled, - onSearchModeChange: setSearchMode, + onSearchModeChange: onSearchModeChanged, }} /> ) : ( @@ -70,7 +78,7 @@ export const Homepage = ({ searchModeSelectorProps={{ searchMode: "agentic", isAgenticSearchEnabled, - onSearchModeChange: setSearchMode, + onSearchModeChange: onSearchModeChanged, }} languageModels={languageModels} repos={initialRepos} diff --git a/packages/web/src/app/[domain]/page.tsx b/packages/web/src/app/[domain]/page.tsx index fdecb105..607bc143 100644 --- a/packages/web/src/app/[domain]/page.tsx +++ b/packages/web/src/app/[domain]/page.tsx @@ -9,6 +9,8 @@ import { PageNotFound } from "./components/pageNotFound"; import { UpgradeToast } from "./components/upgradeToast"; import { ServiceErrorException } from "@/lib/serviceError"; import { auth } from "@/auth"; +import { cookies } from "next/headers"; +import { SEARCH_MODE_COOKIE_NAME } from "@/lib/constants"; export default async function Home({ params: { domain } }: { params: { domain: string } }) { const org = await getOrgFromDomain(domain); @@ -32,6 +34,15 @@ export default async function Home({ params: { domain } }: { params: { domain: s const indexedRepos = repos.filter((repo) => repo.indexedAt !== undefined); + // Read search mode from cookie, defaulting to agentic if not set + // (assuming a language model is configured). + const cookieStore = await cookies(); + const searchModeCookie = cookieStore.get(SEARCH_MODE_COOKIE_NAME); + const initialSearchMode = ( + searchModeCookie?.value === "agentic" || + searchModeCookie?.value === "precise" + ) ? searchModeCookie.value : models.length > 0 ? "agentic" : "precise"; + return (
diff --git a/packages/web/src/lib/constants.ts b/packages/web/src/lib/constants.ts index 19da86bd..4860f473 100644 --- a/packages/web/src/lib/constants.ts +++ b/packages/web/src/lib/constants.ts @@ -23,6 +23,7 @@ export const TEAM_FEATURES = [ ] export const MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME = 'sb.mobile-unsupported-splash-screen-dismissed'; +export const SEARCH_MODE_COOKIE_NAME = 'sb.search-mode'; // NOTE: changing SOURCEBOT_GUEST_USER_ID may break backwards compatibility since this value is used // to detect old guest users in the DB. If you change this value ensure it doesn't break upgrade flows