feat(routes): restructure app routing and enhance navigation

- Introduced new routes for the app section, including `/app`, `/app/minapp`, and various sub-routes for features like chat, knowledge, and files.
- Updated navigation logic across components to reflect the new route structure, ensuring consistent access to app features.
- Refactored existing components to utilize the new routing paths, improving overall navigation experience.
- Enhanced route title management to align with the new structure, providing better localization support.

This commit significantly improves the organization and accessibility of app-related features within the application.
This commit is contained in:
MyPrototypeWhat 2026-01-05 17:06:07 +08:00
parent 945b4de42c
commit b2976373de
27 changed files with 393 additions and 318 deletions

View File

@ -47,7 +47,7 @@ const MinApp: FC<Props> = ({ app, onClick, size = 60, isLast }) => {
const handleClick = () => {
if (isTopNavbar) {
// 顶部导航栏:导航到小程序页面
navigate({ to: `/apps/${app.id}` })
navigate({ to: '/app/minapp/$appId', params: { appId: app.id } })
} else {
// 侧边导航栏:保持原有弹窗行为
openMinappKeepAlive(app)

View File

@ -31,10 +31,10 @@ const MinAppTabsPool: React.FC = () => {
// 使用集中工具进行更稳健的路由判断
const isAppDetail = (() => {
const pathname = location.pathname
if (pathname === '/apps') return false
if (!pathname.startsWith('/apps/')) return false
const parts = pathname.split('/').filter(Boolean) // ['apps', '<id>', ...]
return parts.length >= 2
if (pathname === '/app/minapp') return false
if (!pathname.startsWith('/app/minapp/')) return false
const parts = pathname.split('/').filter(Boolean) // ['app', 'minapp', '<id>', ...]
return parts.length >= 3
})()
const shouldShow = isTopNavbar && isAppDetail

View File

@ -149,15 +149,15 @@ const MainMenus: FC = () => {
}
const pathMap = {
assistants: '/chat',
store: '/store',
paintings: `/paintings/${defaultPaintingProvider}`,
translate: '/translate',
minapp: '/apps',
knowledge: '/knowledge',
files: '/files',
code_tools: '/code',
notes: '/notes'
assistants: '/app/chat',
store: '/app/assistant',
paintings: `/app/paintings/${defaultPaintingProvider}`,
translate: '/app/translate',
minapp: '/app/minapp',
knowledge: '/app/knowledge',
files: '/app/files',
code_tools: '/app/code',
notes: '/app/notes'
}
// 在当前 Tab 内跳转

View File

@ -15,8 +15,8 @@ const logger = loggerService.withContext('useTabs')
const DEFAULT_TAB: Tab = {
id: 'home',
type: 'route',
url: '/',
title: getDefaultRouteTitle('/'),
url: '/home',
title: getDefaultRouteTitle('/home'),
lastAccessTime: Date.now(),
isDormant: false
}

View File

@ -54,7 +54,7 @@ const TopicMessages: FC<Props> = ({ topic: _topic, ...props }) => {
await modelGenerating()
SearchPopup.hide()
const assistant = getAssistantById(topic.assistantId)
navigate({ to: '/chat', search: { assistantId: assistant?.id, topicId: topic.id } })
navigate({ to: '/app/chat', search: { assistantId: assistant?.id, topicId: topic.id } })
setTimeoutTimer('onContinueChat', () => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR), 100)
}

View File

@ -54,7 +54,7 @@ const KnowledgeBaseButton: FC<Props> = ({ quickPanel, selectedBases, onSelect, d
items.push({
label: t('knowledge.add.title') + '...',
icon: <Plus />,
action: () => navigate({ to: '/knowledge' }),
action: () => navigate({ to: '/app/knowledge' }),
isSelected: false
})

View File

@ -18,49 +18,49 @@ const LaunchpadPage: FC = () => {
{
icon: <LayoutGrid size={32} className="icon" />,
text: t('title.apps'),
path: '/apps',
path: '/app/minapp',
bgColor: 'linear-gradient(135deg, #8B5CF6, #A855F7)' // 小程序:紫色,代表多功能和灵活性
},
{
icon: <FileSearch size={32} className="icon" />,
text: t('title.knowledge'),
path: '/knowledge',
path: '/app/knowledge',
bgColor: 'linear-gradient(135deg, #10B981, #34D399)' // 知识库:翠绿色,代表生长和知识
},
{
icon: <Palette size={32} className="icon" />,
text: t('title.paintings'),
path: `/paintings/${defaultPaintingProvider}`,
path: `/app/paintings/${defaultPaintingProvider}`,
bgColor: 'linear-gradient(135deg, #EC4899, #F472B6)' // 绘画:活力粉色,代表创造力和艺术
},
{
icon: <Sparkle size={32} className="icon" />,
text: t('title.store'),
path: '/store',
path: '/app/assistant',
bgColor: 'linear-gradient(135deg, #6366F1, #4F46E5)' // AI助手靛蓝渐变代表智能和科技
},
{
icon: <Languages size={32} className="icon" />,
text: t('title.translate'),
path: '/translate',
path: '/app/translate',
bgColor: 'linear-gradient(135deg, #06B6D4, #0EA5E9)' // 翻译:明亮的青蓝色,代表沟通和流畅
},
{
icon: <Folder size={32} className="icon" />,
text: t('title.files'),
path: '/files',
path: '/app/files',
bgColor: 'linear-gradient(135deg, #F59E0B, #FBBF24)' // 文件:金色,代表资源和重要性
},
{
icon: <Code size={32} className="icon" />,
text: t('title.code'),
path: '/code',
path: '/app/code',
bgColor: 'linear-gradient(135deg, #1F2937, #374151)' // Code CLI高级暗黑色代表专业和技术
},
{
icon: <NotepadText size={32} className="icon" />,
text: t('title.notes'),
path: '/notes',
path: '/app/notes',
bgColor: 'linear-gradient(135deg, #F97316, #FB923C)' // 笔记:橙色,代表活力和清晰思路
}
]

View File

@ -64,7 +64,7 @@ const MinAppPage: FC = () => {
useEffect(() => {
// If app not found, redirect to apps list
if (!app) {
navigate({ to: '/apps' })
navigate({ to: '/app/minapp' })
return
}
@ -72,7 +72,7 @@ const MinAppPage: FC = () => {
// Only check once and only if we haven't already redirected
if (!initialIsTopNavbar.current && !hasRedirected.current) {
hasRedirected.current = true
navigate({ to: '/apps' })
navigate({ to: '/app/minapp' })
// Open popup after navigation
setTimeout(() => {
openMinappKeepAlive(app)

View File

@ -213,7 +213,7 @@ const MinimalToolbar: FC<Props> = ({ app, webviewRef, currentUrl, onReload, onOp
}, [app.id, webviewRef, scheduleNavigationUpdate])
const handleMinimize = useCallback(() => {
navigate({ to: '/apps' })
navigate({ to: '/app/minapp' })
}, [navigate])
const handleTogglePin = useCallback(() => {

View File

@ -9,18 +9,11 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
import { Route as TranslateRouteImport } from './routes/translate'
import { Route as StoreRouteImport } from './routes/store'
import { Route as SettingsRouteImport } from './routes/settings'
import { Route as NotesRouteImport } from './routes/notes'
import { Route as KnowledgeRouteImport } from './routes/knowledge'
import { Route as FilesRouteImport } from './routes/files'
import { Route as CodeRouteImport } from './routes/code'
import { Route as ChatRouteImport } from './routes/chat'
import { Route as HomeRouteImport } from './routes/home'
import { Route as AppRouteImport } from './routes/app'
import { Route as IndexRouteImport } from './routes/index'
import { Route as SettingsIndexRouteImport } from './routes/settings/index'
import { Route as PaintingsIndexRouteImport } from './routes/paintings/index'
import { Route as AppsIndexRouteImport } from './routes/apps/index'
import { Route as SettingsWebsearchRouteImport } from './routes/settings/websearch'
import { Route as SettingsShortcutRouteImport } from './routes/settings/shortcut'
import { Route as SettingsSelectionAssistantRouteImport } from './routes/settings/selectionAssistant'
@ -37,10 +30,17 @@ import { Route as SettingsDisplayRouteImport } from './routes/settings/display'
import { Route as SettingsDataRouteImport } from './routes/settings/data'
import { Route as SettingsApiServerRouteImport } from './routes/settings/api-server'
import { Route as SettingsAboutRouteImport } from './routes/settings/about'
import { Route as PaintingsSplatRouteImport } from './routes/paintings/$'
import { Route as AppsAppIdRouteImport } from './routes/apps/$appId'
import { Route as AppTranslateRouteImport } from './routes/app/translate'
import { Route as AppNotesRouteImport } from './routes/app/notes'
import { Route as AppKnowledgeRouteImport } from './routes/app/knowledge'
import { Route as AppFilesRouteImport } from './routes/app/files'
import { Route as AppCodeRouteImport } from './routes/app/code'
import { Route as AppChatRouteImport } from './routes/app/chat'
import { Route as AppAssistantRouteImport } from './routes/app/assistant'
import { Route as SettingsWebsearchIndexRouteImport } from './routes/settings/websearch/index'
import { Route as SettingsMcpIndexRouteImport } from './routes/settings/mcp/index'
import { Route as AppPaintingsIndexRouteImport } from './routes/app/paintings/index'
import { Route as AppMinappIndexRouteImport } from './routes/app/minapp/index'
import { Route as SettingsWebsearchGeneralRouteImport } from './routes/settings/websearch/general'
import { Route as SettingsMcpServersRouteImport } from './routes/settings/mcp/servers'
import { Route as SettingsMcpNpxSearchRouteImport } from './routes/settings/mcp/npx-search'
@ -48,47 +48,24 @@ import { Route as SettingsMcpMcpInstallRouteImport } from './routes/settings/mcp
import { Route as SettingsMcpMarketplacesRouteImport } from './routes/settings/mcp/marketplaces'
import { Route as SettingsMcpBuiltinRouteImport } from './routes/settings/mcp/builtin'
import { Route as SettingsMcpSplatRouteImport } from './routes/settings/mcp/$'
import { Route as AppPaintingsSplatRouteImport } from './routes/app/paintings/$'
import { Route as AppMinappAppIdRouteImport } from './routes/app/minapp/$appId'
import { Route as SettingsWebsearchProviderProviderIdRouteImport } from './routes/settings/websearch/provider.$providerId'
import { Route as SettingsMcpSettingsServerIdRouteImport } from './routes/settings/mcp/settings.$serverId'
const TranslateRoute = TranslateRouteImport.update({
id: '/translate',
path: '/translate',
getParentRoute: () => rootRouteImport,
} as any)
const StoreRoute = StoreRouteImport.update({
id: '/store',
path: '/store',
getParentRoute: () => rootRouteImport,
} as any)
const SettingsRoute = SettingsRouteImport.update({
id: '/settings',
path: '/settings',
getParentRoute: () => rootRouteImport,
} as any)
const NotesRoute = NotesRouteImport.update({
id: '/notes',
path: '/notes',
const HomeRoute = HomeRouteImport.update({
id: '/home',
path: '/home',
getParentRoute: () => rootRouteImport,
} as any)
const KnowledgeRoute = KnowledgeRouteImport.update({
id: '/knowledge',
path: '/knowledge',
getParentRoute: () => rootRouteImport,
} as any)
const FilesRoute = FilesRouteImport.update({
id: '/files',
path: '/files',
getParentRoute: () => rootRouteImport,
} as any)
const CodeRoute = CodeRouteImport.update({
id: '/code',
path: '/code',
getParentRoute: () => rootRouteImport,
} as any)
const ChatRoute = ChatRouteImport.update({
id: '/chat',
path: '/chat',
const AppRoute = AppRouteImport.update({
id: '/app',
path: '/app',
getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({
@ -101,16 +78,6 @@ const SettingsIndexRoute = SettingsIndexRouteImport.update({
path: '/',
getParentRoute: () => SettingsRoute,
} as any)
const PaintingsIndexRoute = PaintingsIndexRouteImport.update({
id: '/paintings/',
path: '/paintings/',
getParentRoute: () => rootRouteImport,
} as any)
const AppsIndexRoute = AppsIndexRouteImport.update({
id: '/apps/',
path: '/apps/',
getParentRoute: () => rootRouteImport,
} as any)
const SettingsWebsearchRoute = SettingsWebsearchRouteImport.update({
id: '/websearch',
path: '/websearch',
@ -192,15 +159,40 @@ const SettingsAboutRoute = SettingsAboutRouteImport.update({
path: '/about',
getParentRoute: () => SettingsRoute,
} as any)
const PaintingsSplatRoute = PaintingsSplatRouteImport.update({
id: '/paintings/$',
path: '/paintings/$',
getParentRoute: () => rootRouteImport,
const AppTranslateRoute = AppTranslateRouteImport.update({
id: '/translate',
path: '/translate',
getParentRoute: () => AppRoute,
} as any)
const AppsAppIdRoute = AppsAppIdRouteImport.update({
id: '/apps/$appId',
path: '/apps/$appId',
getParentRoute: () => rootRouteImport,
const AppNotesRoute = AppNotesRouteImport.update({
id: '/notes',
path: '/notes',
getParentRoute: () => AppRoute,
} as any)
const AppKnowledgeRoute = AppKnowledgeRouteImport.update({
id: '/knowledge',
path: '/knowledge',
getParentRoute: () => AppRoute,
} as any)
const AppFilesRoute = AppFilesRouteImport.update({
id: '/files',
path: '/files',
getParentRoute: () => AppRoute,
} as any)
const AppCodeRoute = AppCodeRouteImport.update({
id: '/code',
path: '/code',
getParentRoute: () => AppRoute,
} as any)
const AppChatRoute = AppChatRouteImport.update({
id: '/chat',
path: '/chat',
getParentRoute: () => AppRoute,
} as any)
const AppAssistantRoute = AppAssistantRouteImport.update({
id: '/assistant',
path: '/assistant',
getParentRoute: () => AppRoute,
} as any)
const SettingsWebsearchIndexRoute = SettingsWebsearchIndexRouteImport.update({
id: '/',
@ -212,6 +204,16 @@ const SettingsMcpIndexRoute = SettingsMcpIndexRouteImport.update({
path: '/',
getParentRoute: () => SettingsMcpRoute,
} as any)
const AppPaintingsIndexRoute = AppPaintingsIndexRouteImport.update({
id: '/paintings/',
path: '/paintings/',
getParentRoute: () => AppRoute,
} as any)
const AppMinappIndexRoute = AppMinappIndexRouteImport.update({
id: '/minapp/',
path: '/minapp/',
getParentRoute: () => AppRoute,
} as any)
const SettingsWebsearchGeneralRoute =
SettingsWebsearchGeneralRouteImport.update({
id: '/general',
@ -248,6 +250,16 @@ const SettingsMcpSplatRoute = SettingsMcpSplatRouteImport.update({
path: '/$',
getParentRoute: () => SettingsMcpRoute,
} as any)
const AppPaintingsSplatRoute = AppPaintingsSplatRouteImport.update({
id: '/paintings/$',
path: '/paintings/$',
getParentRoute: () => AppRoute,
} as any)
const AppMinappAppIdRoute = AppMinappAppIdRouteImport.update({
id: '/minapp/$appId',
path: '/minapp/$appId',
getParentRoute: () => AppRoute,
} as any)
const SettingsWebsearchProviderProviderIdRoute =
SettingsWebsearchProviderProviderIdRouteImport.update({
id: '/provider/$providerId',
@ -263,16 +275,16 @@ const SettingsMcpSettingsServerIdRoute =
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/chat': typeof ChatRoute
'/code': typeof CodeRoute
'/files': typeof FilesRoute
'/knowledge': typeof KnowledgeRoute
'/notes': typeof NotesRoute
'/app': typeof AppRouteWithChildren
'/home': typeof HomeRoute
'/settings': typeof SettingsRouteWithChildren
'/store': typeof StoreRoute
'/translate': typeof TranslateRoute
'/apps/$appId': typeof AppsAppIdRoute
'/paintings/$': typeof PaintingsSplatRoute
'/app/assistant': typeof AppAssistantRoute
'/app/chat': typeof AppChatRoute
'/app/code': typeof AppCodeRoute
'/app/files': typeof AppFilesRoute
'/app/knowledge': typeof AppKnowledgeRoute
'/app/notes': typeof AppNotesRoute
'/app/translate': typeof AppTranslateRoute
'/settings/about': typeof SettingsAboutRoute
'/settings/api-server': typeof SettingsApiServerRoute
'/settings/data': typeof SettingsDataRoute
@ -289,9 +301,9 @@ export interface FileRoutesByFullPath {
'/settings/selectionAssistant': typeof SettingsSelectionAssistantRoute
'/settings/shortcut': typeof SettingsShortcutRoute
'/settings/websearch': typeof SettingsWebsearchRouteWithChildren
'/apps': typeof AppsIndexRoute
'/paintings': typeof PaintingsIndexRoute
'/settings/': typeof SettingsIndexRoute
'/app/minapp/$appId': typeof AppMinappAppIdRoute
'/app/paintings/$': typeof AppPaintingsSplatRoute
'/settings/mcp/$': typeof SettingsMcpSplatRoute
'/settings/mcp/builtin': typeof SettingsMcpBuiltinRoute
'/settings/mcp/marketplaces': typeof SettingsMcpMarketplacesRoute
@ -299,6 +311,8 @@ export interface FileRoutesByFullPath {
'/settings/mcp/npx-search': typeof SettingsMcpNpxSearchRoute
'/settings/mcp/servers': typeof SettingsMcpServersRoute
'/settings/websearch/general': typeof SettingsWebsearchGeneralRoute
'/app/minapp': typeof AppMinappIndexRoute
'/app/paintings': typeof AppPaintingsIndexRoute
'/settings/mcp/': typeof SettingsMcpIndexRoute
'/settings/websearch/': typeof SettingsWebsearchIndexRoute
'/settings/mcp/settings/$serverId': typeof SettingsMcpSettingsServerIdRoute
@ -306,15 +320,15 @@ export interface FileRoutesByFullPath {
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/chat': typeof ChatRoute
'/code': typeof CodeRoute
'/files': typeof FilesRoute
'/knowledge': typeof KnowledgeRoute
'/notes': typeof NotesRoute
'/store': typeof StoreRoute
'/translate': typeof TranslateRoute
'/apps/$appId': typeof AppsAppIdRoute
'/paintings/$': typeof PaintingsSplatRoute
'/app': typeof AppRouteWithChildren
'/home': typeof HomeRoute
'/app/assistant': typeof AppAssistantRoute
'/app/chat': typeof AppChatRoute
'/app/code': typeof AppCodeRoute
'/app/files': typeof AppFilesRoute
'/app/knowledge': typeof AppKnowledgeRoute
'/app/notes': typeof AppNotesRoute
'/app/translate': typeof AppTranslateRoute
'/settings/about': typeof SettingsAboutRoute
'/settings/api-server': typeof SettingsApiServerRoute
'/settings/data': typeof SettingsDataRoute
@ -329,9 +343,9 @@ export interface FileRoutesByTo {
'/settings/quickphrase': typeof SettingsQuickphraseRoute
'/settings/selectionAssistant': typeof SettingsSelectionAssistantRoute
'/settings/shortcut': typeof SettingsShortcutRoute
'/apps': typeof AppsIndexRoute
'/paintings': typeof PaintingsIndexRoute
'/settings': typeof SettingsIndexRoute
'/app/minapp/$appId': typeof AppMinappAppIdRoute
'/app/paintings/$': typeof AppPaintingsSplatRoute
'/settings/mcp/$': typeof SettingsMcpSplatRoute
'/settings/mcp/builtin': typeof SettingsMcpBuiltinRoute
'/settings/mcp/marketplaces': typeof SettingsMcpMarketplacesRoute
@ -339,6 +353,8 @@ export interface FileRoutesByTo {
'/settings/mcp/npx-search': typeof SettingsMcpNpxSearchRoute
'/settings/mcp/servers': typeof SettingsMcpServersRoute
'/settings/websearch/general': typeof SettingsWebsearchGeneralRoute
'/app/minapp': typeof AppMinappIndexRoute
'/app/paintings': typeof AppPaintingsIndexRoute
'/settings/mcp': typeof SettingsMcpIndexRoute
'/settings/websearch': typeof SettingsWebsearchIndexRoute
'/settings/mcp/settings/$serverId': typeof SettingsMcpSettingsServerIdRoute
@ -347,16 +363,16 @@ export interface FileRoutesByTo {
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/chat': typeof ChatRoute
'/code': typeof CodeRoute
'/files': typeof FilesRoute
'/knowledge': typeof KnowledgeRoute
'/notes': typeof NotesRoute
'/app': typeof AppRouteWithChildren
'/home': typeof HomeRoute
'/settings': typeof SettingsRouteWithChildren
'/store': typeof StoreRoute
'/translate': typeof TranslateRoute
'/apps/$appId': typeof AppsAppIdRoute
'/paintings/$': typeof PaintingsSplatRoute
'/app/assistant': typeof AppAssistantRoute
'/app/chat': typeof AppChatRoute
'/app/code': typeof AppCodeRoute
'/app/files': typeof AppFilesRoute
'/app/knowledge': typeof AppKnowledgeRoute
'/app/notes': typeof AppNotesRoute
'/app/translate': typeof AppTranslateRoute
'/settings/about': typeof SettingsAboutRoute
'/settings/api-server': typeof SettingsApiServerRoute
'/settings/data': typeof SettingsDataRoute
@ -373,9 +389,9 @@ export interface FileRoutesById {
'/settings/selectionAssistant': typeof SettingsSelectionAssistantRoute
'/settings/shortcut': typeof SettingsShortcutRoute
'/settings/websearch': typeof SettingsWebsearchRouteWithChildren
'/apps/': typeof AppsIndexRoute
'/paintings/': typeof PaintingsIndexRoute
'/settings/': typeof SettingsIndexRoute
'/app/minapp/$appId': typeof AppMinappAppIdRoute
'/app/paintings/$': typeof AppPaintingsSplatRoute
'/settings/mcp/$': typeof SettingsMcpSplatRoute
'/settings/mcp/builtin': typeof SettingsMcpBuiltinRoute
'/settings/mcp/marketplaces': typeof SettingsMcpMarketplacesRoute
@ -383,6 +399,8 @@ export interface FileRoutesById {
'/settings/mcp/npx-search': typeof SettingsMcpNpxSearchRoute
'/settings/mcp/servers': typeof SettingsMcpServersRoute
'/settings/websearch/general': typeof SettingsWebsearchGeneralRoute
'/app/minapp/': typeof AppMinappIndexRoute
'/app/paintings/': typeof AppPaintingsIndexRoute
'/settings/mcp/': typeof SettingsMcpIndexRoute
'/settings/websearch/': typeof SettingsWebsearchIndexRoute
'/settings/mcp/settings/$serverId': typeof SettingsMcpSettingsServerIdRoute
@ -392,16 +410,16 @@ export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| '/chat'
| '/code'
| '/files'
| '/knowledge'
| '/notes'
| '/app'
| '/home'
| '/settings'
| '/store'
| '/translate'
| '/apps/$appId'
| '/paintings/$'
| '/app/assistant'
| '/app/chat'
| '/app/code'
| '/app/files'
| '/app/knowledge'
| '/app/notes'
| '/app/translate'
| '/settings/about'
| '/settings/api-server'
| '/settings/data'
@ -418,9 +436,9 @@ export interface FileRouteTypes {
| '/settings/selectionAssistant'
| '/settings/shortcut'
| '/settings/websearch'
| '/apps'
| '/paintings'
| '/settings/'
| '/app/minapp/$appId'
| '/app/paintings/$'
| '/settings/mcp/$'
| '/settings/mcp/builtin'
| '/settings/mcp/marketplaces'
@ -428,6 +446,8 @@ export interface FileRouteTypes {
| '/settings/mcp/npx-search'
| '/settings/mcp/servers'
| '/settings/websearch/general'
| '/app/minapp'
| '/app/paintings'
| '/settings/mcp/'
| '/settings/websearch/'
| '/settings/mcp/settings/$serverId'
@ -435,15 +455,15 @@ export interface FileRouteTypes {
fileRoutesByTo: FileRoutesByTo
to:
| '/'
| '/chat'
| '/code'
| '/files'
| '/knowledge'
| '/notes'
| '/store'
| '/translate'
| '/apps/$appId'
| '/paintings/$'
| '/app'
| '/home'
| '/app/assistant'
| '/app/chat'
| '/app/code'
| '/app/files'
| '/app/knowledge'
| '/app/notes'
| '/app/translate'
| '/settings/about'
| '/settings/api-server'
| '/settings/data'
@ -458,9 +478,9 @@ export interface FileRouteTypes {
| '/settings/quickphrase'
| '/settings/selectionAssistant'
| '/settings/shortcut'
| '/apps'
| '/paintings'
| '/settings'
| '/app/minapp/$appId'
| '/app/paintings/$'
| '/settings/mcp/$'
| '/settings/mcp/builtin'
| '/settings/mcp/marketplaces'
@ -468,6 +488,8 @@ export interface FileRouteTypes {
| '/settings/mcp/npx-search'
| '/settings/mcp/servers'
| '/settings/websearch/general'
| '/app/minapp'
| '/app/paintings'
| '/settings/mcp'
| '/settings/websearch'
| '/settings/mcp/settings/$serverId'
@ -475,16 +497,16 @@ export interface FileRouteTypes {
id:
| '__root__'
| '/'
| '/chat'
| '/code'
| '/files'
| '/knowledge'
| '/notes'
| '/app'
| '/home'
| '/settings'
| '/store'
| '/translate'
| '/apps/$appId'
| '/paintings/$'
| '/app/assistant'
| '/app/chat'
| '/app/code'
| '/app/files'
| '/app/knowledge'
| '/app/notes'
| '/app/translate'
| '/settings/about'
| '/settings/api-server'
| '/settings/data'
@ -501,9 +523,9 @@ export interface FileRouteTypes {
| '/settings/selectionAssistant'
| '/settings/shortcut'
| '/settings/websearch'
| '/apps/'
| '/paintings/'
| '/settings/'
| '/app/minapp/$appId'
| '/app/paintings/$'
| '/settings/mcp/$'
| '/settings/mcp/builtin'
| '/settings/mcp/marketplaces'
@ -511,6 +533,8 @@ export interface FileRouteTypes {
| '/settings/mcp/npx-search'
| '/settings/mcp/servers'
| '/settings/websearch/general'
| '/app/minapp/'
| '/app/paintings/'
| '/settings/mcp/'
| '/settings/websearch/'
| '/settings/mcp/settings/$serverId'
@ -519,36 +543,13 @@ export interface FileRouteTypes {
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
ChatRoute: typeof ChatRoute
CodeRoute: typeof CodeRoute
FilesRoute: typeof FilesRoute
KnowledgeRoute: typeof KnowledgeRoute
NotesRoute: typeof NotesRoute
AppRoute: typeof AppRouteWithChildren
HomeRoute: typeof HomeRoute
SettingsRoute: typeof SettingsRouteWithChildren
StoreRoute: typeof StoreRoute
TranslateRoute: typeof TranslateRoute
AppsAppIdRoute: typeof AppsAppIdRoute
PaintingsSplatRoute: typeof PaintingsSplatRoute
AppsIndexRoute: typeof AppsIndexRoute
PaintingsIndexRoute: typeof PaintingsIndexRoute
}
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/translate': {
id: '/translate'
path: '/translate'
fullPath: '/translate'
preLoaderRoute: typeof TranslateRouteImport
parentRoute: typeof rootRouteImport
}
'/store': {
id: '/store'
path: '/store'
fullPath: '/store'
preLoaderRoute: typeof StoreRouteImport
parentRoute: typeof rootRouteImport
}
'/settings': {
id: '/settings'
path: '/settings'
@ -556,39 +557,18 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof SettingsRouteImport
parentRoute: typeof rootRouteImport
}
'/notes': {
id: '/notes'
path: '/notes'
fullPath: '/notes'
preLoaderRoute: typeof NotesRouteImport
'/home': {
id: '/home'
path: '/home'
fullPath: '/home'
preLoaderRoute: typeof HomeRouteImport
parentRoute: typeof rootRouteImport
}
'/knowledge': {
id: '/knowledge'
path: '/knowledge'
fullPath: '/knowledge'
preLoaderRoute: typeof KnowledgeRouteImport
parentRoute: typeof rootRouteImport
}
'/files': {
id: '/files'
path: '/files'
fullPath: '/files'
preLoaderRoute: typeof FilesRouteImport
parentRoute: typeof rootRouteImport
}
'/code': {
id: '/code'
path: '/code'
fullPath: '/code'
preLoaderRoute: typeof CodeRouteImport
parentRoute: typeof rootRouteImport
}
'/chat': {
id: '/chat'
path: '/chat'
fullPath: '/chat'
preLoaderRoute: typeof ChatRouteImport
'/app': {
id: '/app'
path: '/app'
fullPath: '/app'
preLoaderRoute: typeof AppRouteImport
parentRoute: typeof rootRouteImport
}
'/': {
@ -605,20 +585,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof SettingsIndexRouteImport
parentRoute: typeof SettingsRoute
}
'/paintings/': {
id: '/paintings/'
path: '/paintings'
fullPath: '/paintings'
preLoaderRoute: typeof PaintingsIndexRouteImport
parentRoute: typeof rootRouteImport
}
'/apps/': {
id: '/apps/'
path: '/apps'
fullPath: '/apps'
preLoaderRoute: typeof AppsIndexRouteImport
parentRoute: typeof rootRouteImport
}
'/settings/websearch': {
id: '/settings/websearch'
path: '/websearch'
@ -731,19 +697,54 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof SettingsAboutRouteImport
parentRoute: typeof SettingsRoute
}
'/paintings/$': {
id: '/paintings/$'
path: '/paintings/$'
fullPath: '/paintings/$'
preLoaderRoute: typeof PaintingsSplatRouteImport
parentRoute: typeof rootRouteImport
'/app/translate': {
id: '/app/translate'
path: '/translate'
fullPath: '/app/translate'
preLoaderRoute: typeof AppTranslateRouteImport
parentRoute: typeof AppRoute
}
'/apps/$appId': {
id: '/apps/$appId'
path: '/apps/$appId'
fullPath: '/apps/$appId'
preLoaderRoute: typeof AppsAppIdRouteImport
parentRoute: typeof rootRouteImport
'/app/notes': {
id: '/app/notes'
path: '/notes'
fullPath: '/app/notes'
preLoaderRoute: typeof AppNotesRouteImport
parentRoute: typeof AppRoute
}
'/app/knowledge': {
id: '/app/knowledge'
path: '/knowledge'
fullPath: '/app/knowledge'
preLoaderRoute: typeof AppKnowledgeRouteImport
parentRoute: typeof AppRoute
}
'/app/files': {
id: '/app/files'
path: '/files'
fullPath: '/app/files'
preLoaderRoute: typeof AppFilesRouteImport
parentRoute: typeof AppRoute
}
'/app/code': {
id: '/app/code'
path: '/code'
fullPath: '/app/code'
preLoaderRoute: typeof AppCodeRouteImport
parentRoute: typeof AppRoute
}
'/app/chat': {
id: '/app/chat'
path: '/chat'
fullPath: '/app/chat'
preLoaderRoute: typeof AppChatRouteImport
parentRoute: typeof AppRoute
}
'/app/assistant': {
id: '/app/assistant'
path: '/assistant'
fullPath: '/app/assistant'
preLoaderRoute: typeof AppAssistantRouteImport
parentRoute: typeof AppRoute
}
'/settings/websearch/': {
id: '/settings/websearch/'
@ -759,6 +760,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof SettingsMcpIndexRouteImport
parentRoute: typeof SettingsMcpRoute
}
'/app/paintings/': {
id: '/app/paintings/'
path: '/paintings'
fullPath: '/app/paintings'
preLoaderRoute: typeof AppPaintingsIndexRouteImport
parentRoute: typeof AppRoute
}
'/app/minapp/': {
id: '/app/minapp/'
path: '/minapp'
fullPath: '/app/minapp'
preLoaderRoute: typeof AppMinappIndexRouteImport
parentRoute: typeof AppRoute
}
'/settings/websearch/general': {
id: '/settings/websearch/general'
path: '/general'
@ -808,6 +823,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof SettingsMcpSplatRouteImport
parentRoute: typeof SettingsMcpRoute
}
'/app/paintings/$': {
id: '/app/paintings/$'
path: '/paintings/$'
fullPath: '/app/paintings/$'
preLoaderRoute: typeof AppPaintingsSplatRouteImport
parentRoute: typeof AppRoute
}
'/app/minapp/$appId': {
id: '/app/minapp/$appId'
path: '/minapp/$appId'
fullPath: '/app/minapp/$appId'
preLoaderRoute: typeof AppMinappAppIdRouteImport
parentRoute: typeof AppRoute
}
'/settings/websearch/provider/$providerId': {
id: '/settings/websearch/provider/$providerId'
path: '/provider/$providerId'
@ -825,6 +854,36 @@ declare module '@tanstack/react-router' {
}
}
interface AppRouteChildren {
AppAssistantRoute: typeof AppAssistantRoute
AppChatRoute: typeof AppChatRoute
AppCodeRoute: typeof AppCodeRoute
AppFilesRoute: typeof AppFilesRoute
AppKnowledgeRoute: typeof AppKnowledgeRoute
AppNotesRoute: typeof AppNotesRoute
AppTranslateRoute: typeof AppTranslateRoute
AppMinappAppIdRoute: typeof AppMinappAppIdRoute
AppPaintingsSplatRoute: typeof AppPaintingsSplatRoute
AppMinappIndexRoute: typeof AppMinappIndexRoute
AppPaintingsIndexRoute: typeof AppPaintingsIndexRoute
}
const AppRouteChildren: AppRouteChildren = {
AppAssistantRoute: AppAssistantRoute,
AppChatRoute: AppChatRoute,
AppCodeRoute: AppCodeRoute,
AppFilesRoute: AppFilesRoute,
AppKnowledgeRoute: AppKnowledgeRoute,
AppNotesRoute: AppNotesRoute,
AppTranslateRoute: AppTranslateRoute,
AppMinappAppIdRoute: AppMinappAppIdRoute,
AppPaintingsSplatRoute: AppPaintingsSplatRoute,
AppMinappIndexRoute: AppMinappIndexRoute,
AppPaintingsIndexRoute: AppPaintingsIndexRoute,
}
const AppRouteWithChildren = AppRoute._addFileChildren(AppRouteChildren)
interface SettingsMcpRouteChildren {
SettingsMcpSplatRoute: typeof SettingsMcpSplatRoute
SettingsMcpBuiltinRoute: typeof SettingsMcpBuiltinRoute
@ -913,18 +972,9 @@ const SettingsRouteWithChildren = SettingsRoute._addFileChildren(
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
ChatRoute: ChatRoute,
CodeRoute: CodeRoute,
FilesRoute: FilesRoute,
KnowledgeRoute: KnowledgeRoute,
NotesRoute: NotesRoute,
AppRoute: AppRouteWithChildren,
HomeRoute: HomeRoute,
SettingsRoute: SettingsRouteWithChildren,
StoreRoute: StoreRoute,
TranslateRoute: TranslateRoute,
AppsAppIdRoute: AppsAppIdRoute,
PaintingsSplatRoute: PaintingsSplatRoute,
AppsIndexRoute: AppsIndexRoute,
PaintingsIndexRoute: PaintingsIndexRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)

View File

@ -0,0 +1,5 @@
import { createFileRoute, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/app')({
component: () => <Outlet />
})

View File

@ -1,6 +1,6 @@
import AssistantPresetsPage from '@renderer/pages/store/assistants/presets/AssistantPresetsPage'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/store')({
export const Route = createFileRoute('/app/assistant')({
component: AssistantPresetsPage
})

View File

@ -1,6 +1,6 @@
import HomePage from '@renderer/pages/home/HomePage'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/chat')({
export const Route = createFileRoute('/app/chat')({
component: HomePage
})

View File

@ -1,6 +1,6 @@
import CodeToolsPage from '@renderer/pages/code/CodeToolsPage'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/code')({
export const Route = createFileRoute('/app/code')({
component: CodeToolsPage
})

View File

@ -1,6 +1,6 @@
import FilesPage from '@renderer/pages/files/FilesPage'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/files')({
export const Route = createFileRoute('/app/files')({
component: FilesPage
})

View File

@ -1,6 +1,6 @@
import KnowledgePage from '@renderer/pages/knowledge/KnowledgePage'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/knowledge')({
export const Route = createFileRoute('/app/knowledge')({
component: KnowledgePage
})

View File

@ -1,6 +1,6 @@
import MinAppPage from '@renderer/pages/minapps/MinAppPage'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/apps/$appId')({
export const Route = createFileRoute('/app/minapp/$appId')({
component: MinAppPage
})

View File

@ -1,6 +1,6 @@
import MinAppsPage from '@renderer/pages/minapps/MinAppsPage'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/apps/')({
export const Route = createFileRoute('/app/minapp/')({
component: MinAppsPage
})

View File

@ -1,6 +1,6 @@
import NotesPage from '@renderer/pages/notes/NotesPage'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/notes')({
export const Route = createFileRoute('/app/notes')({
component: NotesPage
})

View File

@ -1,7 +1,7 @@
import PaintingsRoutePage from '@renderer/pages/paintings/PaintingsRoutePage'
import { createFileRoute } from '@tanstack/react-router'
// 通配符路由:捕获 /paintings/* 所有子路径
export const Route = createFileRoute('/paintings/$')({
// 通配符路由:捕获 /app/paintings/* 所有子路径
export const Route = createFileRoute('/app/paintings/$')({
component: PaintingsRoutePage
})

View File

@ -1,6 +1,6 @@
import PaintingsRoutePage from '@renderer/pages/paintings/PaintingsRoutePage'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/paintings/')({
export const Route = createFileRoute('/app/paintings/')({
component: PaintingsRoutePage
})

View File

@ -1,6 +1,6 @@
import TranslatePage from '@renderer/pages/translate/TranslatePage'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/translate')({
export const Route = createFileRoute('/app/translate')({
component: TranslatePage
})

View File

@ -0,0 +1,6 @@
import LaunchpadPage from '@renderer/pages/launchpad/LaunchpadPage'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/home')({
component: LaunchpadPage
})

View File

@ -1,6 +1,7 @@
import LaunchpadPage from '@renderer/pages/launchpad/LaunchpadPage'
import { createFileRoute } from '@tanstack/react-router'
import { createFileRoute, redirect } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
component: LaunchpadPage
beforeLoad: () => {
throw redirect({ to: '/home' })
}
})

View File

@ -75,7 +75,7 @@ export async function locateToMessage(navigate: UseNavigateResult<string>, messa
const assistant = getAssistantById(message.assistantId)
const topic = await getTopicById(message.topicId)
navigate({ to: '/chat', search: { assistantId: assistant?.id, topicId: topic?.id } })
navigate({ to: '/app/chat', search: { assistantId: assistant?.id, topicId: topic?.id } })
setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR), 0)
setTimeout(() => EventEmitter.emit(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id), 300)

View File

@ -5,17 +5,17 @@ vi.mock('@renderer/i18n', () => ({
default: {
t: vi.fn((key: string) => {
const translations: Record<string, string> = {
'tab.new': '新标签页',
'assistants.title': '助手',
'assistants.presets.title': '预设助手',
'paintings.title': '绘图',
'translate.title': '翻译',
'minapp.title': '小程序',
'knowledge.title': '知识库',
'files.title': '文件',
'code.title': '代码',
'notes.title': '笔记',
'settings.title': '设置'
'title.home': '首页',
'common.chat': '聊天',
'title.store': '助手库',
'title.paintings': '绘画',
'title.translate': '翻译',
'title.apps': '小程序',
'title.knowledge': '知识库',
'title.files': '文件',
'title.code': 'Code',
'title.notes': '笔记',
'title.settings': '设置'
}
return translations[key] || key
})
@ -32,16 +32,17 @@ describe('routeTitle', () => {
describe('getDefaultRouteTitle', () => {
describe('exact route matches', () => {
it.each([
['/', '新标签页'],
['/chat', '助手'],
['/store', '预设助手'],
['/paintings', '绘图'],
['/translate', '翻译'],
['/apps', '小程序'],
['/knowledge', '知识库'],
['/files', '文件'],
['/code', '代码'],
['/notes', '笔记'],
['/', '首页'],
['/home', '首页'],
['/app/chat', '聊天'],
['/app/assistant', '助手库'],
['/app/paintings', '绘画'],
['/app/translate', '翻译'],
['/app/minapp', '小程序'],
['/app/knowledge', '知识库'],
['/app/files', '文件'],
['/app/code', 'Code'],
['/app/notes', '笔记'],
['/settings', '设置']
])('should return correct title for %s', (url, expectedTitle) => {
expect(getDefaultRouteTitle(url)).toBe(expectedTitle)
@ -50,25 +51,25 @@ describe('routeTitle', () => {
describe('nested route matches', () => {
it('should match base path for nested routes', () => {
expect(getDefaultRouteTitle('/chat/topic-123')).toBe('助手')
expect(getDefaultRouteTitle('/app/chat/topic-123')).toBe('聊天')
expect(getDefaultRouteTitle('/settings/provider')).toBe('设置')
expect(getDefaultRouteTitle('/settings/mcp/servers')).toBe('设置')
expect(getDefaultRouteTitle('/paintings/zhipu')).toBe('绘')
expect(getDefaultRouteTitle('/app/paintings/zhipu')).toBe('绘')
})
})
describe('URL with query params and hash', () => {
it('should handle URLs with query parameters', () => {
expect(getDefaultRouteTitle('/chat?topicId=123')).toBe('助手')
expect(getDefaultRouteTitle('/app/chat?topicId=123')).toBe('聊天')
expect(getDefaultRouteTitle('/settings/provider?id=openai')).toBe('设置')
})
it('should handle URLs with hash', () => {
expect(getDefaultRouteTitle('/knowledge#section1')).toBe('知识库')
expect(getDefaultRouteTitle('/app/knowledge#section1')).toBe('知识库')
})
it('should handle URLs with both query and hash', () => {
expect(getDefaultRouteTitle('/chat?id=1#message-5')).toBe('助手')
expect(getDefaultRouteTitle('/app/chat?id=1#message-5')).toBe('聊天')
})
})
@ -85,20 +86,20 @@ describe('routeTitle', () => {
describe('edge cases', () => {
it('should handle trailing slashes', () => {
expect(getDefaultRouteTitle('/chat/')).toBe('助手')
expect(getDefaultRouteTitle('/app/chat/')).toBe('聊天')
expect(getDefaultRouteTitle('/settings/')).toBe('设置')
})
it('should handle double slashes (protocol-relative URL)', () => {
// '//chat' is a protocol-relative URL, so 'chat' becomes the hostname
// This is expected behavior per URL standard
expect(getDefaultRouteTitle('//chat')).toBe('新标签页')
expect(getDefaultRouteTitle('//chat')).toBe('页')
})
it('should handle relative-like paths', () => {
// URL constructor with base will normalize these
expect(getDefaultRouteTitle('chat')).toBe('助手')
expect(getDefaultRouteTitle('./chat')).toBe('助手')
expect(getDefaultRouteTitle('app/chat')).toBe('聊天')
expect(getDefaultRouteTitle('./app/chat')).toBe('聊天')
})
})
})
@ -106,10 +107,10 @@ describe('routeTitle', () => {
describe('getRouteTitleKey', () => {
describe('exact matches', () => {
it.each([
['/', 'tab.new'],
['/chat', 'assistants.title'],
['/store', 'assistants.presets.title'],
['/settings', 'settings.title']
['/', 'title.home'],
['/app/chat', 'common.chat'],
['/app/assistant', 'title.store'],
['/settings', 'title.settings']
])('should return i18n key for %s', (url, expectedKey) => {
expect(getRouteTitleKey(url)).toBe(expectedKey)
})
@ -117,8 +118,8 @@ describe('routeTitle', () => {
describe('base path matches', () => {
it('should return base path key for nested routes', () => {
expect(getRouteTitleKey('/chat/topic-123')).toBe('assistants.title')
expect(getRouteTitleKey('/settings/provider')).toBe('settings.title')
expect(getRouteTitleKey('/app/chat/topic-123')).toBe('common.chat')
expect(getRouteTitleKey('/settings/provider')).toBe('title.settings')
})
})

View File

@ -7,28 +7,42 @@ const BASE_URL = 'https://www.cherry-ai.com/'
* Route to i18n key mapping for default tab titles
*/
const routeTitleKeys: Record<string, string> = {
'/': 'tab.new',
'/chat': 'assistants.title',
'/store': 'assistants.presets.title',
'/paintings': 'paintings.title',
'/translate': 'translate.title',
'/apps': 'minapp.title',
'/knowledge': 'knowledge.title',
'/files': 'files.title',
'/code': 'code.title',
'/notes': 'notes.title',
'/settings': 'settings.title'
'/': 'title.home',
'/home': 'title.home',
'/app/chat': 'common.chat',
'/app/assistant': 'title.store',
'/app/paintings': 'title.paintings',
'/app/translate': 'title.translate',
'/app/minapp': 'title.apps',
'/app/knowledge': 'title.knowledge',
'/app/files': 'title.files',
'/app/code': 'title.code',
'/app/notes': 'title.notes',
'/settings': 'title.settings'
}
/**
* Get the base path for route matching
* For /app/* routes, returns first two segments (e.g., '/app/chat')
* For other routes, returns first segment (e.g., '/settings')
*/
function getBasePath(pathname: string): string {
const segments = pathname.split('/').filter(Boolean)
if (segments[0] === 'app' && segments.length >= 2) {
return '/' + segments.slice(0, 2).join('/')
}
return '/' + (segments[0] || '')
}
/**
* Get the default title for a route URL
*
* @param url - Route URL (e.g., '/settings', '/chat/123')
* @param url - Route URL (e.g., '/settings', '/app/chat/123')
* @returns Translated title or URL path fallback
*
* @example
* getDefaultRouteTitle('/settings') // '设置'
* getDefaultRouteTitle('/chat/abc123') // '助手'
* getDefaultRouteTitle('/app/chat/abc123') // '助手'
* getDefaultRouteTitle('/unknown') // 'unknown'
*/
export function getDefaultRouteTitle(url: string): string {
@ -40,9 +54,8 @@ export function getDefaultRouteTitle(url: string): string {
return i18n.t(exactKey)
}
// Try matching base path (e.g., '/chat/123' -> '/chat')
const basePath = '/' + sanitizedUrl.split('/').filter(Boolean)[0]
const baseKey = routeTitleKeys[basePath]
// Try matching base path
const baseKey = routeTitleKeys[getBasePath(sanitizedUrl)]
if (baseKey) {
return i18n.t(baseKey)
}
@ -61,6 +74,5 @@ export function getRouteTitleKey(url: string): string | undefined {
const exactKey = routeTitleKeys[sanitizedUrl]
if (exactKey) return exactKey
const basePath = '/' + sanitizedUrl.split('/').filter(Boolean)[0]
return routeTitleKeys[basePath]
return routeTitleKeys[getBasePath(sanitizedUrl)]
}