diff --git a/src/renderer/src/components/layout/AppShell.tsx b/src/renderer/src/components/layout/AppShell.tsx index dcecbe61b3..37cd987f4a 100644 --- a/src/renderer/src/components/layout/AppShell.tsx +++ b/src/renderer/src/components/layout/AppShell.tsx @@ -23,7 +23,7 @@ const WebviewContainer = ({ url, isActive }: { url: string; isActive: boolean }) export const AppShell = () => { const { tabs, activeTabId, setActiveTab, closeTab, updateTab, addTab } = useTabs() - // Sync internal navigation back to tab state with default title + // Sync internal navigation back to tab state with default title (url may include search/hash) const handleUrlChange = (tabId: string, url: string) => { updateTab(tabId, { url, title: getDefaultRouteTitle(url) }) } diff --git a/src/renderer/src/components/layout/TabRouter.tsx b/src/renderer/src/components/layout/TabRouter.tsx index 8e2e372dbd..9c2f5df258 100644 --- a/src/renderer/src/components/layout/TabRouter.tsx +++ b/src/renderer/src/components/layout/TabRouter.tsx @@ -24,20 +24,20 @@ export const TabRouter = ({ tab, isActive, onUrlChange }: TabRouterProps) => { return createRouter({ routeTree, history }) // eslint-disable-next-line react-hooks/exhaustive-deps }, [tab.id]) - // Sync internal navigation back to tab state useEffect(() => { return router.subscribe('onResolved', ({ toLocation }) => { - if (toLocation.pathname !== tab.url) { - onUrlChange(toLocation.pathname) + const nextHref = toLocation.href + if (nextHref !== tab.url) { + onUrlChange(nextHref) } }) }, [router, tab.url, onUrlChange]) // Navigate when tab.url changes externally (e.g., from Sidebar) useEffect(() => { - const currentPath = router.state.location.pathname - if (tab.url !== currentPath) { + const currentHref = router.state.location.href + if (tab.url !== currentHref) { router.navigate({ to: tab.url }) } }, [router, tab.url]) diff --git a/src/renderer/src/hooks/useTabs.ts b/src/renderer/src/hooks/useTabs.ts index faba51aed3..cebe77c0c3 100644 --- a/src/renderer/src/hooks/useTabs.ts +++ b/src/renderer/src/hooks/useTabs.ts @@ -179,7 +179,7 @@ export function useTabs() { return } - // 添加 LRU 字段 + // 添加 LRU 字段,保留完整 URL(含 search/hash) const newTab: Tab = { ...tab, lastAccessTime: Date.now(), @@ -255,7 +255,7 @@ export function useTabs() { const newTab: Tab = { id: id || uuid(), type, - url, + url, // full URL including search/hash title: title || getDefaultRouteTitle(url), lastAccessTime: Date.now(), isDormant: false diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx index 6bb0a9f93d..220613f703 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx @@ -94,8 +94,8 @@ const ProviderList: FC = () => { // 清除 id 参数 navigate({ to: location.pathname, - search: (prev) => { - const { id: _, ...rest } = prev as Record + search: ({ id, ...rest }) => { + void id return rest }, replace: true diff --git a/src/renderer/src/routes/__root.tsx b/src/renderer/src/routes/__root.tsx index d1a273acb7..07dcf9524c 100644 --- a/src/renderer/src/routes/__root.tsx +++ b/src/renderer/src/routes/__root.tsx @@ -1,5 +1,11 @@ +import NavigationHandler from '@renderer/handler/NavigationHandler' import { createRootRoute, Outlet } from '@tanstack/react-router' export const Route = createRootRoute({ - component: () => + component: () => ( + <> + + + + ) }) diff --git a/src/renderer/src/utils/routeTitle.ts b/src/renderer/src/utils/routeTitle.ts index b880683c01..5873f2713e 100644 --- a/src/renderer/src/utils/routeTitle.ts +++ b/src/renderer/src/utils/routeTitle.ts @@ -1,5 +1,8 @@ import i18n from '@renderer/i18n' +/** Base URL for parsing relative route paths */ +const BASE_URL = 'https://www.cherry-ai.com/' + /** * Route to i18n key mapping for default tab titles */ @@ -29,30 +32,35 @@ const routeTitleKeys: Record = { * getDefaultRouteTitle('/unknown') // 'unknown' */ export function getDefaultRouteTitle(url: string): string { + const sanitizedUrl = new URL(url, BASE_URL).pathname + // Try exact match first - const exactKey = routeTitleKeys[url] + const exactKey = routeTitleKeys[sanitizedUrl] if (exactKey) { return i18n.t(exactKey) } // Try matching base path (e.g., '/chat/123' -> '/chat') - const basePath = '/' + url.split('/').filter(Boolean)[0] + const basePath = '/' + sanitizedUrl.split('/').filter(Boolean)[0] const baseKey = routeTitleKeys[basePath] if (baseKey) { return i18n.t(baseKey) } - // Fallback to URL path - return url.split('/').pop() || url + // Fallback to last segment of pathname + const segments = sanitizedUrl.split('/').filter(Boolean) + return segments.pop() || sanitizedUrl } /** * Get the i18n key for a route (without translating) */ export function getRouteTitleKey(url: string): string | undefined { - const exactKey = routeTitleKeys[url] + const sanitizedUrl = new URL(url, BASE_URL).pathname + + const exactKey = routeTitleKeys[sanitizedUrl] if (exactKey) return exactKey - const basePath = '/' + url.split('/').filter(Boolean)[0] + const basePath = '/' + sanitizedUrl.split('/').filter(Boolean)[0] return routeTitleKeys[basePath] }