diff --git a/apps/sim/app/o/[organizationId]/search/search.tsx b/apps/sim/app/o/[organizationId]/search/search.tsx index 6e92e7e5f29..5dce1e9991e 100644 --- a/apps/sim/app/o/[organizationId]/search/search.tsx +++ b/apps/sim/app/o/[organizationId]/search/search.tsx @@ -5,11 +5,12 @@ import { Chip, ComposerActionButton, cn } from '@sim/emcn' import { ArrowUp, Plus, Search, Sparkles, StopFilled } from '@sim/emcn/icons' import { generateId } from '@sim/utils/id' import { useIsFetching, useQueryClient } from '@tanstack/react-query' -import { usePathname, useRouter } from 'next/navigation' +import { usePathname, useRouter, useSearchParams } from 'next/navigation' import { useQueryStates } from 'nuqs' import { HEADER_ACTION_CLUSTER } from '@/components/page-header-bar' import type { WorkspaceSearchFilters } from '@/lib/api/contracts/knowledge' import { resourceScopeKey } from '@/lib/core/resource-scope' +import { BrowserStorage } from '@/lib/core/utils/browser-storage' import { organizationRoutes } from '@/lib/navigation/paths' import type { OrganizationSummaryRequest } from '@/app/o/[organizationId]/components/organization-conversation' import { SearchInputBar } from '@/app/o/[organizationId]/components/search-input-bar' @@ -166,6 +167,7 @@ function OrganizationSearchContent({ userId, chatId }: OrganizationSearchProps) const { recordQuery } = useSearchHistoryActions() const router = useRouter() const pathname = usePathname() + const hasSearchParams = useSearchParams().size > 0 const routes = organizationRoutes(organization.id) const hasConversation = Boolean(chatId) || pathname.startsWith(`${routes.search}/chat/`) const queryClient = useQueryClient() @@ -173,10 +175,9 @@ function OrganizationSearchContent({ userId, chatId }: OrganizationSearchProps) organizationSearchParsers, organizationSearchUrlKeys ) - const activeMode = - mothershipAvailable && (mode ?? (hasConversation ? 'assistant' : 'search')) === 'assistant' - ? 'assistant' - : 'search' + const routeMode = mode ?? (hasConversation ? 'assistant' : hasSearchParams ? 'search' : null) + const activeMode = mothershipAvailable ? routeMode : 'search' + const preferenceKey = `organization-search-mode:${userId}:${organization.id}` const [assistantOpened, setAssistantOpened] = useState(activeMode === 'assistant') const [conversationInstance, setConversationInstance] = useState(0) const [isStartingNew, startNewTransition] = useTransition() @@ -185,8 +186,20 @@ function OrganizationSearchContent({ userId, chatId }: OrganizationSearchProps) (id: string) => setSummaries((current) => current.filter((request) => request.id !== id)), [] ) + useEffect(() => { + if (!mothershipAvailable) return + if (routeMode) { + BrowserStorage.setItem(preferenceKey, routeMode) + return + } + const savedMode = BrowserStorage.getItem(preferenceKey, null) + // Pin the mode to this history entry so Back survives a later preference change. + void setParams({ mode: savedMode === 'assistant' ? 'assistant' : 'search' }) + }, [mothershipAvailable, preferenceKey, routeMode, setParams]) + + if (activeMode === 'assistant' && !assistantOpened) setAssistantOpened(true) + const changeMode = (next: 'search' | 'assistant') => { - if (next === 'assistant') setAssistantOpened(true) void setParams({ mode: next }, { history: 'push' }) } const startNew = () => { @@ -201,7 +214,7 @@ function OrganizationSearchContent({ userId, chatId }: OrganizationSearchProps) } startNewTransition(() => { setConversationInstance((current) => current + 1) - router.push(activeMode === 'assistant' ? `${routes.search}?mode=assistant` : routes.search, { + router.push(mothershipAvailable ? `${routes.search}?mode=${activeMode}` : routes.search, { scroll: false, }) }) @@ -263,28 +276,28 @@ function OrganizationSearchContent({ userId, chatId }: OrganizationSearchProps) return (
- {(assistantOpened || hasConversation || activeMode === 'assistant') && - mothershipAvailable && ( -
- }> - - -
- )} + {(assistantOpened || hasConversation) && mothershipAvailable && ( +
+ }> + + +
+ )} + {!activeMode && } {activeMode === 'search' && ( <>
diff --git a/apps/sim/scripts/test-assistant-actions-e2e.ts b/apps/sim/scripts/test-assistant-actions-e2e.ts index 17410913a2b..35570e5637f 100644 --- a/apps/sim/scripts/test-assistant-actions-e2e.ts +++ b/apps/sim/scripts/test-assistant-actions-e2e.ts @@ -1488,7 +1488,7 @@ try { await reviewCards.getByRole('button', { name: 'Cancel', exact: true }).focus() await page.keyboard.press('Enter') await expect.poll(() => decisionPersisted).toBe(true) - await page.goto(`${baseUrl.origin}${root}/search`) + await page.goto(`${baseUrl.origin}${root}/search?mode=search`) const search = page.getByRole('textbox', { name: 'Search your sources', exact: true }) await search.fill('Continue searching') releaseResponse.resolve() diff --git a/apps/sim/scripts/test-organization-search-e2e.ts b/apps/sim/scripts/test-organization-search-e2e.ts index 0e5a9666a93..4400d641a49 100644 --- a/apps/sim/scripts/test-organization-search-e2e.ts +++ b/apps/sim/scripts/test-organization-search-e2e.ts @@ -412,6 +412,21 @@ try { await expect(assistantInput).toHaveValue('Summarize launch readiness') } ) + await check('Returning to Search restores the last selected view', async () => { + await page.goto(`${baseUrl.origin}${root}/search`) + const views = page.getByRole('navigation', { name: 'Search views' }) + for (const mode of ['Assistant', 'Search'] as const) { + const tab = views.getByRole('button', { name: mode, exact: true }) + await tab.click() + await expect(tab).toHaveAttribute('aria-current', 'page') + await page.getByRole('link', { name: 'Home', exact: true }).click() + await expect(page).toHaveURL(`${baseUrl.origin}${root}/home`) + await page.getByRole('link', { name: 'Search', exact: true }).click() + await expect(tab).toHaveAttribute('aria-current', 'page') + await page.goto(`${baseUrl.origin}${root}/search`) + await expect(tab).toHaveAttribute('aria-current', 'page') + } + }) await check( 'Traditional search submits the organization query and keeps results in Search', async () => { @@ -750,7 +765,7 @@ try { await input.fill('Edited launch query') await expect(input).toHaveValue('Edited launch query') await page.getByRole('button', { name: 'New search', exact: true }).click() - await expect(page).toHaveURL(`${baseUrl.origin}${root}/search`) + await expect(page).toHaveURL(`${baseUrl.origin}${root}/search?mode=search`) await expect(input).toHaveValue('') await page.goBack() await expect(input).toHaveValue('Edited launch query') @@ -1001,7 +1016,7 @@ try { values (${generateId()}, ${organizationId}, 'Search only', ${sql.json({ disableWorkspaceCreation: true })}, ${userId}, true)` await page.goto(`${baseUrl.origin}${root}/home`) - await expect(page).toHaveURL(`${baseUrl.origin}${root}/search`) + await expect(page).toHaveURL((url) => url.pathname === `${root}/search`) await page.goto(`${baseUrl.origin}${root}/search/chat/${assistantId}`) await expect(page.getByText(answer, { exact: true })).toBeVisible() }