feat(navigation): migrate to TanStack Router and enhance navigation structure

- Replaced react-router with TanStack Router for improved routing capabilities.
- Updated navigation logic across various components to utilize the new navigation API.
- Refactored App.tsx to integrate AppShell as the main routing component, removing the deprecated Router.
- Enhanced navigation methods to support search parameters, improving URL handling for settings and provider navigation.

These changes streamline the routing architecture, enhancing the overall user experience and maintainability of the application.
This commit is contained in:
MyPrototypeWhat 2025-12-09 17:30:04 +08:00
parent 5ad5bd537b
commit b9a56fcec1
46 changed files with 979 additions and 313 deletions

View File

@ -7,15 +7,13 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/integration/react' import { PersistGate } from 'redux-persist/integration/react'
// TODO: 新路由系统入口,迁移完成后启用 import { AppShell } from './components/layout/AppShell'
// import { AppShell } from './components/layout/AppShell'
import TopViewContainer from './components/TopView' import TopViewContainer from './components/TopView'
import AntdProvider from './context/AntdProvider' import AntdProvider from './context/AntdProvider'
import { CodeStyleProvider } from './context/CodeStyleProvider' import { CodeStyleProvider } from './context/CodeStyleProvider'
import { NotificationProvider } from './context/NotificationProvider' import { NotificationProvider } from './context/NotificationProvider'
import StyleSheetManager from './context/StyleSheetManager' import StyleSheetManager from './context/StyleSheetManager'
import { ThemeProvider } from './context/ThemeProvider' import { ThemeProvider } from './context/ThemeProvider'
import Router from './Router'
const logger = loggerService.withContext('App.tsx') const logger = loggerService.withContext('App.tsx')
@ -44,8 +42,7 @@ function App(): React.ReactElement {
<CodeStyleProvider> <CodeStyleProvider>
<PersistGate loading={null} persistor={persistor}> <PersistGate loading={null} persistor={persistor}>
<TopViewContainer> <TopViewContainer>
{/* TODO: 迁移完成后切换到 <AppShell /> */} <AppShell />
<Router />
</TopViewContainer> </TopViewContainer>
</PersistGate> </PersistGate>
</CodeStyleProvider> </CodeStyleProvider>

View File

@ -1,67 +0,0 @@
import '@renderer/databases'
import type { FC } from 'react'
import { useMemo } from 'react'
import { HashRouter, Route, Routes } from 'react-router-dom'
import Sidebar from './components/app/Sidebar'
import { ErrorBoundary } from './components/ErrorBoundary'
import TabsContainer from './components/Tab/TabContainer'
import NavigationHandler from './handler/NavigationHandler'
import { useNavbarPosition } from './hooks/useNavbar'
import CodeToolsPage from './pages/code/CodeToolsPage'
import FilesPage from './pages/files/FilesPage'
import HomePage from './pages/home/HomePage'
import KnowledgePage from './pages/knowledge/KnowledgePage'
import LaunchpadPage from './pages/launchpad/LaunchpadPage'
import MinAppPage from './pages/minapps/MinAppPage'
import MinAppsPage from './pages/minapps/MinAppsPage'
import NotesPage from './pages/notes/NotesPage'
import PaintingsRoutePage from './pages/paintings/PaintingsRoutePage'
import SettingsPage from './pages/settings/SettingsPage'
import AssistantPresetsPage from './pages/store/assistants/presets/AssistantPresetsPage'
import TranslatePage from './pages/translate/TranslatePage'
const Router: FC = () => {
const { navbarPosition } = useNavbarPosition()
const routes = useMemo(() => {
return (
<ErrorBoundary>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/store" element={<AssistantPresetsPage />} />
<Route path="/paintings/*" element={<PaintingsRoutePage />} />
<Route path="/translate" element={<TranslatePage />} />
<Route path="/files" element={<FilesPage />} />
<Route path="/notes" element={<NotesPage />} />
<Route path="/knowledge" element={<KnowledgePage />} />
<Route path="/apps/:appId" element={<MinAppPage />} />
<Route path="/apps" element={<MinAppsPage />} />
<Route path="/code" element={<CodeToolsPage />} />
<Route path="/settings/*" element={<SettingsPage />} />
<Route path="/launchpad" element={<LaunchpadPage />} />
</Routes>
</ErrorBoundary>
)
}, [])
if (navbarPosition === 'left') {
return (
<HashRouter>
<Sidebar />
{routes}
<NavigationHandler />
</HashRouter>
)
}
return (
<HashRouter>
<NavigationHandler />
<TabsContainer>{routes}</TabsContainer>
</HashRouter>
)
}
export default Router

View File

@ -30,13 +30,13 @@ export const FreeTrialModelTag: FC<Props> = ({ model, showLabel = true }) => {
} }
const onSelectProvider = () => { const onSelectProvider = () => {
NavigationService.navigate!(`/settings/provider?id=${providerId}`) NavigationService.navigate!({ to: `/settings/provider`, search: { id: providerId } })
} }
const onNavigateProvider = (e: MouseEvent) => { const onNavigateProvider = (e: MouseEvent) => {
e.stopPropagation() e.stopPropagation()
SelectModelPopup.hide() SelectModelPopup.hide()
NavigationService.navigate!(`/settings/provider?id=${providerId}`) NavigationService.navigate?.({ to: '/settings/provider', search: { id: providerId } })
} }
if (!showLabel) { if (!showLabel) {

View File

@ -6,11 +6,11 @@ import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
import { useMinapps } from '@renderer/hooks/useMinapps' import { useMinapps } from '@renderer/hooks/useMinapps'
import { useNavbarPosition } from '@renderer/hooks/useNavbar' import { useNavbarPosition } from '@renderer/hooks/useNavbar'
import type { MinAppType } from '@renderer/types' import type { MinAppType } from '@renderer/types'
import { useNavigate } from '@tanstack/react-router'
import type { MenuProps } from 'antd' import type { MenuProps } from 'antd'
import { Dropdown } from 'antd' import { Dropdown } from 'antd'
import type { FC } from 'react' import type { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
interface Props { interface Props {
@ -47,7 +47,7 @@ const MinApp: FC<Props> = ({ app, onClick, size = 60, isLast }) => {
const handleClick = () => { const handleClick = () => {
if (isTopNavbar) { if (isTopNavbar) {
// 顶部导航栏:导航到小程序页面 // 顶部导航栏:导航到小程序页面
navigate(`/apps/${app.id}`) navigate({ to: `/apps/${app.id}` })
} else { } else {
// 侧边导航栏:保持原有弹窗行为 // 侧边导航栏:保持原有弹窗行为
openMinappKeepAlive(app) openMinappKeepAlive(app)

View File

@ -3,9 +3,9 @@ import WebviewContainer from '@renderer/components/MinApp/WebviewContainer'
import { useMinapps } from '@renderer/hooks/useMinapps' import { useMinapps } from '@renderer/hooks/useMinapps'
import { useNavbarPosition } from '@renderer/hooks/useNavbar' import { useNavbarPosition } from '@renderer/hooks/useNavbar'
import { getWebviewLoaded, setWebviewLoaded } from '@renderer/utils/webviewStateManager' import { getWebviewLoaded, setWebviewLoaded } from '@renderer/utils/webviewStateManager'
import { useLocation } from '@tanstack/react-router'
import type { WebviewTag } from 'electron' import type { WebviewTag } from 'electron'
import React, { useEffect, useRef } from 'react' import React, { useEffect, useRef } from 'react'
import { useLocation } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
/** /**

View File

@ -193,7 +193,7 @@ const PopupContainer: React.FC<Props> = ({ model, filter: baseFilter, showTagFil
e.stopPropagation() e.stopPropagation()
setOpen(false) setOpen(false)
resolve(undefined) resolve(undefined)
window.navigate(`/settings/provider?id=${p.id}`) window.navigate({ to: '/settings/provider', search: { id: p.id } })
}} }}
/> />
</Tooltip> </Tooltip>

View File

@ -17,6 +17,7 @@ import { addTab, removeTab, setActiveTab, setTabs } from '@renderer/store/tabs'
import type { MinAppType } from '@renderer/types' import type { MinAppType } from '@renderer/types'
import { classNames } from '@renderer/utils' import { classNames } from '@renderer/utils'
import { ThemeMode } from '@shared/data/preference/preferenceTypes' import { ThemeMode } from '@shared/data/preference/preferenceTypes'
import { useLocation, useNavigate } from '@tanstack/react-router'
import type { LRUCache } from 'lru-cache' import type { LRUCache } from 'lru-cache'
import { import {
FileSearch, FileSearch,
@ -37,7 +38,6 @@ import {
} from 'lucide-react' } from 'lucide-react'
import { useCallback, useEffect, useMemo } from 'react' import { useCallback, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useLocation, useNavigate } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import MinAppIcon from '../Icons/MinAppIcon' import MinAppIcon from '../Icons/MinAppIcon'
@ -203,17 +203,17 @@ const TabsContainer: React.FC<TabsContainerProps> = ({ children }) => {
const handleAddTab = () => { const handleAddTab = () => {
hideMinappPopup() hideMinappPopup()
navigate('/launchpad') navigate({ to: '/launchpad' })
} }
const handleSettingsClick = () => { const handleSettingsClick = () => {
hideMinappPopup() hideMinappPopup()
navigate(lastSettingsPath) navigate({ to: lastSettingsPath })
} }
const handleTabClick = (tab: Tab) => { const handleTabClick = (tab: Tab) => {
hideMinappPopup() hideMinappPopup()
navigate(tab.path) navigate({ to: tab.path })
} }
const visibleTabs = useMemo(() => tabs.filter((tab) => !specialTabs.includes(tab.id)), [tabs]) const visibleTabs = useMemo(() => tabs.filter((tab) => !specialTabs.includes(tab.id)), [tabs])

View File

@ -30,9 +30,9 @@ import {
} from 'lucide-react' } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useLocation, useNavigate } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import { useTabs } from '../../hooks/useTabs'
import UserPopup from '../Popups/UserPopup' import UserPopup from '../Popups/UserPopup'
import { SidebarOpenedMinappTabs, SidebarPinnedApps } from './PinnedMinapps' import { SidebarOpenedMinappTabs, SidebarPinnedApps } from './PinnedMinapps'
@ -40,9 +40,11 @@ const Sidebar: FC = () => {
const { hideMinappPopup } = useMinappPopup() const { hideMinappPopup } = useMinappPopup()
const { pinned, minappShow } = useMinapps() const { pinned, minappShow } = useMinapps()
const [visibleSidebarIcons] = usePreference('ui.sidebar.icons.visible') const [visibleSidebarIcons] = usePreference('ui.sidebar.icons.visible')
const { tabs, activeTabId, updateTab } = useTabs()
const { pathname } = useLocation() // 获取当前 Tab 的 URL 作为 pathname
const navigate = useNavigate() const activeTab = tabs.find((t) => t.id === activeTabId)
const pathname = activeTab?.url || '/'
const { theme, settedTheme, toggleTheme } = useTheme() const { theme, settedTheme, toggleTheme } = useTheme()
const avatar = useAvatar() const avatar = useAvatar()
@ -54,9 +56,12 @@ const Sidebar: FC = () => {
const showPinnedApps = pinned.length > 0 && visibleSidebarIcons.includes('minapp') const showPinnedApps = pinned.length > 0 && visibleSidebarIcons.includes('minapp')
// 在当前 Tab 内跳转
const to = async (path: string) => { const to = async (path: string) => {
await modelGenerating() await modelGenerating()
navigate(path) if (activeTabId) {
updateTab(activeTabId, { url: path })
}
} }
const isFullscreen = useFullscreen() const isFullscreen = useFullscreen()
@ -121,14 +126,16 @@ const Sidebar: FC = () => {
const MainMenus: FC = () => { const MainMenus: FC = () => {
const { hideMinappPopup } = useMinappPopup() const { hideMinappPopup } = useMinappPopup()
const { minappShow } = useMinapps() const { minappShow } = useMinapps()
const { tabs, activeTabId, updateTab } = useTabs()
// 获取当前 Tab 的 URL 作为 pathname
const activeTab = tabs.find((t) => t.id === activeTabId)
const pathname = activeTab?.url || '/'
const { pathname } = useLocation()
const [visibleSidebarIcons] = usePreference('ui.sidebar.icons.visible') const [visibleSidebarIcons] = usePreference('ui.sidebar.icons.visible')
const { defaultPaintingProvider } = useSettings() const { defaultPaintingProvider } = useSettings()
const navigate = useNavigate()
const { theme } = useTheme() const { theme } = useTheme()
const isRoute = (path: string): string => (pathname === path && !minappShow ? 'active' : '')
const isRoutes = (path: string): string => (pathname.startsWith(path) && !minappShow ? 'active' : '') const isRoutes = (path: string): string => (pathname.startsWith(path) && !minappShow ? 'active' : '')
const iconMap = { const iconMap = {
@ -144,7 +151,7 @@ const MainMenus: FC = () => {
} }
const pathMap = { const pathMap = {
assistants: '/', assistants: '/chat',
store: '/store', store: '/store',
paintings: `/paintings/${defaultPaintingProvider}`, paintings: `/paintings/${defaultPaintingProvider}`,
translate: '/translate', translate: '/translate',
@ -155,17 +162,24 @@ const MainMenus: FC = () => {
notes: '/notes' notes: '/notes'
} }
// 在当前 Tab 内跳转
const to = async (path: string) => {
await modelGenerating()
if (activeTabId) {
updateTab(activeTabId, { url: path })
}
}
return visibleSidebarIcons.map((icon) => { return visibleSidebarIcons.map((icon) => {
const path = pathMap[icon] const path = pathMap[icon]
const isActive = path === '/' ? isRoute(path) : isRoutes(path) const isActive = isRoutes(path)
return ( return (
<Tooltip key={icon} placement="right" content={getSidebarIconLabel(icon)} delay={800}> <Tooltip key={icon} placement="right" content={getSidebarIconLabel(icon)} delay={800}>
<StyledLink <StyledLink
onClick={async () => { onClick={async () => {
hideMinappPopup() hideMinappPopup()
await modelGenerating() await to(path)
navigate(path)
}}> }}>
<Icon theme={theme} className={isActive}> <Icon theme={theme} className={isActive}>
{iconMap[icon]} {iconMap[icon]}

View File

@ -1,3 +1,5 @@
import '@renderer/databases'
import { cn, Tabs, TabsList, TabsTrigger } from '@cherrystudio/ui' import { cn, Tabs, TabsList, TabsTrigger } from '@cherrystudio/ui'
import { Plus, X } from 'lucide-react' import { Plus, X } from 'lucide-react'
import { Activity } from 'react' import { Activity } from 'react'
@ -31,7 +33,7 @@ export const AppShell = () => {
id: uuid(), id: uuid(),
type: 'route', type: 'route',
url: '/', url: '/',
title: 'New Tab', title: 'New Tab'
}) })
} }

View File

@ -2,8 +2,8 @@
import type { PermissionUpdate } from '@anthropic-ai/claude-agent-sdk' import type { PermissionUpdate } from '@anthropic-ai/claude-agent-sdk'
import type { ToastUtilities } from '@cherrystudio/ui' import type { ToastUtilities } from '@cherrystudio/ui'
import type { UseNavigateResult } from '@tanstack/react-router'
import type { HookAPI } from 'antd/es/modal/useModal' import type { HookAPI } from 'antd/es/modal/useModal'
import type { NavigateFunction } from 'react-router-dom'
interface ImportMetaEnv { interface ImportMetaEnv {
VITE_RENDERER_INTEGRATED_MODEL: string VITE_RENDERER_INTEGRATED_MODEL: string
@ -18,7 +18,7 @@ declare global {
root: HTMLElement root: HTMLElement
modal: HookAPI modal: HookAPI
store: any store: any
navigate: NavigateFunction navigate: UseNavigateResult<string>
toast: ToastUtilities toast: ToastUtilities
agentTools: { agentTools: {
respondToPermission: (payload: { respondToPermission: (payload: {

View File

@ -1,8 +1,8 @@
import { useAppSelector } from '@renderer/store' import { useAppSelector } from '@renderer/store'
import { IpcChannel } from '@shared/IpcChannel' import { IpcChannel } from '@shared/IpcChannel'
import { useLocation, useNavigate } from '@tanstack/react-router'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
import { useLocation, useNavigate } from 'react-router-dom'
const NavigationHandler: React.FC = () => { const NavigationHandler: React.FC = () => {
const location = useLocation() const location = useLocation()
@ -17,7 +17,7 @@ const NavigationHandler: React.FC = () => {
if (location.pathname.startsWith('/settings')) { if (location.pathname.startsWith('/settings')) {
return return
} }
navigate('/settings/provider') navigate({ to: '/settings/provider' })
}, },
{ {
splitKey: '!', splitKey: '!',
@ -30,7 +30,7 @@ const NavigationHandler: React.FC = () => {
// Listen for navigate to About page event from macOS menu // Listen for navigate to About page event from macOS menu
useEffect(() => { useEffect(() => {
const handleNavigateToAbout = () => { const handleNavigateToAbout = () => {
navigate('/settings/about') navigate({ to: '/settings/about' })
} }
const removeListener = window.electron.ipcRenderer.on(IpcChannel.Windows_NavigateToAbout, handleNavigateToAbout) const removeListener = window.electron.ipcRenderer.on(IpcChannel.Windows_NavigateToAbout, handleNavigateToAbout)

View File

@ -64,7 +64,7 @@ export function useAppInit() {
useEffect(() => { useEffect(() => {
window.api.getDataPathFromArgs().then((dataPath) => { window.api.getDataPathFromArgs().then((dataPath) => {
if (dataPath) { if (dataPath) {
window.navigate('/settings/data', { replace: true }) window.navigate({ to: '/settings/data', replace: true })
} }
}) })
}, []) }, [])

View File

@ -13,8 +13,8 @@ window.electron.ipcRenderer.on(IpcChannel.Mcp_ServersChanged, (_event, servers)
window.electron.ipcRenderer.on(IpcChannel.Mcp_AddServer, (_event, server: MCPServer) => { window.electron.ipcRenderer.on(IpcChannel.Mcp_AddServer, (_event, server: MCPServer) => {
store.dispatch(addMCPServer(server)) store.dispatch(addMCPServer(server))
NavigationService.navigate?.('/settings/mcp') NavigationService.navigate?.({ to: '/settings/mcp' })
NavigationService.navigate?.(`/settings/mcp/settings/${encodeURIComponent(server.id)}`) NavigationService.navigate?.({ to: `/settings/mcp/settings/${encodeURIComponent(server.id)}` })
}) })
const selectMcpServers = (state: RootState) => state.mcp.servers const selectMcpServers = (state: RootState) => state.mcp.servers

View File

@ -186,7 +186,7 @@ export const useMinappPopup = () => {
// Then navigate to the app tab using NavigationService // Then navigate to the app tab using NavigationService
if (NavigationService.navigate) { if (NavigationService.navigate) {
NavigationService.navigate(`/apps/${config.id}`) NavigationService.navigate({ to: `/apps/${config.id}` })
} }
} else { } else {
// For side navbar, use the traditional popup system // For side navbar, use the traditional popup system

View File

@ -19,12 +19,12 @@ import { getClaudeSupportedProviders } from '@renderer/utils/provider'
import type { TerminalConfig } from '@shared/config/constant' import type { TerminalConfig } from '@shared/config/constant'
import { codeTools, terminalApps } from '@shared/config/constant' import { codeTools, terminalApps } from '@shared/config/constant'
import { isSiliconAnthropicCompatibleModel } from '@shared/config/providers' import { isSiliconAnthropicCompatibleModel } from '@shared/config/providers'
import { Link } from '@tanstack/react-router'
import { Alert, Checkbox, Input, Popover, Select, Space } from 'antd' import { Alert, Checkbox, Input, Popover, Select, Space } from 'antd'
import { ArrowUpRight, Download, FolderOpen, HelpCircle, Terminal, X } from 'lucide-react' import { ArrowUpRight, Download, FolderOpen, HelpCircle, Terminal, X } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react' import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import { import {

View File

@ -11,9 +11,9 @@ import { getTopicById } from '@renderer/hooks/useTopic'
import { getAssistantById } from '@renderer/services/AssistantService' import { getAssistantById } from '@renderer/services/AssistantService'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { locateToMessage } from '@renderer/services/MessagesService' import { locateToMessage } from '@renderer/services/MessagesService'
import NavigationService from '@renderer/services/NavigationService'
import type { Topic } from '@renderer/types' import type { Topic } from '@renderer/types'
import { classNames, runAsyncFunction } from '@renderer/utils' import { classNames, runAsyncFunction } from '@renderer/utils'
import { useNavigate } from '@tanstack/react-router'
import { Divider, Empty } from 'antd' import { Divider, Empty } from 'antd'
import { t } from 'i18next' import { t } from 'i18next'
import { Forward } from 'lucide-react' import { Forward } from 'lucide-react'
@ -27,7 +27,8 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
} }
const TopicMessages: FC<Props> = ({ topic: _topic, ...props }) => { const TopicMessages: FC<Props> = ({ topic: _topic, ...props }) => {
const navigate = NavigationService.navigate! const navigate = useNavigate()
const { handleScroll, containerRef } = useScrollPosition('TopicMessages') const { handleScroll, containerRef } = useScrollPosition('TopicMessages')
const [messageStyle] = usePreference('chat.message.style') const [messageStyle] = usePreference('chat.message.style')
const { setTimeoutTimer } = useTimer() const { setTimeoutTimer } = useTimer()
@ -53,7 +54,7 @@ const TopicMessages: FC<Props> = ({ topic: _topic, ...props }) => {
await modelGenerating() await modelGenerating()
SearchPopup.hide() SearchPopup.hide()
const assistant = getAssistantById(topic.assistantId) const assistant = getAssistantById(topic.assistantId)
navigate('/', { state: { assistant, topic } }) navigate({ to: '/chat', search: { assistantId: assistant?.id, topicId: topic.id } })
setTimeoutTimer('onContinueChat', () => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR), 100) setTimeoutTimer('onContinueChat', () => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR), 100)
} }

View File

@ -10,11 +10,11 @@ import { newMessagesActions } from '@renderer/store/newMessage'
import { setActiveAgentId, setActiveTopicOrSessionAction } from '@renderer/store/runtime' import { setActiveAgentId, setActiveTopicOrSessionAction } from '@renderer/store/runtime'
import type { Assistant, Topic } from '@renderer/types' import type { Assistant, Topic } from '@renderer/types'
import { MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH, SECOND_MIN_WINDOW_WIDTH } from '@shared/config/constant' import { MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH, SECOND_MIN_WINDOW_WIDTH } from '@shared/config/constant'
import { useNavigate, useSearch } from '@tanstack/react-router'
import { AnimatePresence, motion } from 'motion/react' import { AnimatePresence, motion } from 'motion/react'
import type { FC } from 'react' import type { FC } from 'react'
import { startTransition, useCallback, useEffect, useState } from 'react' import { startTransition, useCallback, useEffect, useState } from 'react'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { useLocation, useNavigate } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import Chat from './Chat' import Chat from './Chat'
@ -31,13 +31,23 @@ const HomePage: FC = () => {
// Initialize agent session hook // Initialize agent session hook
useAgentSessionInitializer() useAgentSessionInitializer()
const location = useLocation() const search = useSearch({ strict: false }) as { assistantId?: string; topicId?: string }
const state = location.state
// 根据 search params 中的 ID 查找对应的 assistant
const assistantFromSearch = search.assistantId
? assistants.find((a) => a.id === search.assistantId)
: undefined
const [activeAssistant, _setActiveAssistant] = useState<Assistant>( const [activeAssistant, _setActiveAssistant] = useState<Assistant>(
state?.assistant || _activeAssistant || assistants[0] assistantFromSearch || _activeAssistant || assistants[0]
) )
const { activeTopic, setActiveTopic: _setActiveTopic } = useActiveTopic(activeAssistant?.id ?? '', state?.topic)
// 根据 search params 中的 topicId 查找对应的 topic
const topicFromSearch = search.topicId
? activeAssistant?.topics?.find((t) => t.id === search.topicId)
: undefined
const { activeTopic, setActiveTopic: _setActiveTopic } = useActiveTopic(activeAssistant?.id ?? '', topicFromSearch)
const [showAssistants] = usePreference('assistant.tab.show') const [showAssistants] = usePreference('assistant.tab.show')
const [showTopics] = usePreference('topic.tab.show') const [showTopics] = usePreference('topic.tab.show')
const [topicPosition] = usePreference('topic.position') const [topicPosition] = usePreference('topic.position')
@ -80,10 +90,10 @@ const HomePage: FC = () => {
}, [navigate]) }, [navigate])
useEffect(() => { useEffect(() => {
state?.assistant && setActiveAssistant(state?.assistant) assistantFromSearch && setActiveAssistant(assistantFromSearch)
state?.topic && setActiveTopic(state?.topic) topicFromSearch && setActiveTopic(topicFromSearch)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [state]) }, [search.assistantId, search.topicId])
useEffect(() => { useEffect(() => {
const canMinimize = topicPosition == 'left' ? !showAssistants : !showAssistants && !showTopics const canMinimize = topicPosition == 'left' ? !showAssistants : !showAssistants && !showTopics

View File

@ -5,11 +5,11 @@ import { QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/Qu
import type { ToolQuickPanelApi } from '@renderer/pages/home/Inputbar/types' import type { ToolQuickPanelApi } from '@renderer/pages/home/Inputbar/types'
import { useAppSelector } from '@renderer/store' import { useAppSelector } from '@renderer/store'
import type { KnowledgeBase } from '@renderer/types' import type { KnowledgeBase } from '@renderer/types'
import { useNavigate } from '@tanstack/react-router'
import { CircleX, FileSearch, Plus } from 'lucide-react' import { CircleX, FileSearch, Plus } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { memo, useCallback, useEffect, useMemo, useRef } from 'react' import { memo, useCallback, useEffect, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router'
interface Props { interface Props {
quickPanel: ToolQuickPanelApi quickPanel: ToolQuickPanelApi
@ -54,7 +54,7 @@ const KnowledgeBaseButton: FC<Props> = ({ quickPanel, selectedBases, onSelect, d
items.push({ items.push({
label: t('knowledge.add.title') + '...', label: t('knowledge.add.title') + '...',
icon: <Plus />, icon: <Plus />,
action: () => navigate('/knowledge'), action: () => navigate({ to: '/knowledge' }),
isSelected: false isSelected: false
}) })

View File

@ -12,12 +12,12 @@ import { EventEmitter } from '@renderer/services/EventService'
import type { MCPPrompt, MCPResource, MCPServer } from '@renderer/types' import type { MCPPrompt, MCPResource, MCPServer } from '@renderer/types'
import { isToolUseModeFunction } from '@renderer/utils/assistant' import { isToolUseModeFunction } from '@renderer/utils/assistant'
import { isGeminiWebSearchProvider, isSupportUrlContextProvider } from '@renderer/utils/provider' import { isGeminiWebSearchProvider, isSupportUrlContextProvider } from '@renderer/utils/provider'
import { useNavigate } from '@tanstack/react-router'
import { Form, Input } from 'antd' import { Form, Input } from 'antd'
import { CircleX, Hammer, Plus } from 'lucide-react' import { CircleX, Hammer, Plus } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router'
interface Props { interface Props {
assistantId: string assistantId: string
@ -205,7 +205,7 @@ const MCPToolsButton: FC<Props> = ({ quickPanel, setInputValue, resizeTextArea,
newList.push({ newList.push({
label: t('settings.mcp.addServer.label') + '...', label: t('settings.mcp.addServer.label') + '...',
icon: <Plus />, icon: <Plus />,
action: () => navigate('/settings/mcp') action: () => navigate({ to: '/settings/mcp' })
}) })
newList.unshift({ newList.unshift({

View File

@ -9,6 +9,7 @@ import { getModelUniqId } from '@renderer/services/ModelService'
import type { FileType, Model } from '@renderer/types' import type { FileType, Model } from '@renderer/types'
import { FileTypes } from '@renderer/types' import { FileTypes } from '@renderer/types'
import { getFancyProviderName } from '@renderer/utils' import { getFancyProviderName } from '@renderer/utils'
import { useNavigate } from '@tanstack/react-router'
import { Avatar } from 'antd' import { Avatar } from 'antd'
import { useLiveQuery } from 'dexie-react-hooks' import { useLiveQuery } from 'dexie-react-hooks'
import { first, sortBy } from 'lodash' import { first, sortBy } from 'lodash'
@ -16,7 +17,6 @@ import { AtSign, CircleX, Plus } from 'lucide-react'
import type React from 'react' import type React from 'react'
import { useCallback, useEffect, useMemo, useRef } from 'react' import { useCallback, useEffect, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router'
import styled from 'styled-components' import styled from 'styled-components'
export type MentionTriggerInfo = { type: 'input' | 'button'; position?: number; originalText?: string } export type MentionTriggerInfo = { type: 'input' | 'button'; position?: number; originalText?: string }
@ -194,7 +194,7 @@ export const useMentionModelsPanel = (params: Params, role: 'button' | 'manager'
items.push({ items.push({
label: t('settings.models.add.add_model') + '...', label: t('settings.models.add.add_model') + '...',
icon: <Plus />, icon: <Plus />,
action: () => navigate('/settings/provider'), action: () => navigate({ to: '/settings/provider' }),
isSelected: false isSelected: false
}) })

View File

@ -3,7 +3,6 @@ import CodeViewer from '@renderer/components/CodeViewer'
import { useCodeStyle } from '@renderer/context/CodeStyleProvider' import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
import { useTimer } from '@renderer/hooks/useTimer' import { useTimer } from '@renderer/hooks/useTimer'
import { getHttpMessageLabel, getProviderLabel } from '@renderer/i18n/label' import { getHttpMessageLabel, getProviderLabel } from '@renderer/i18n/label'
import { getProviderById } from '@renderer/services/ProviderService'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { removeBlocksThunk } from '@renderer/store/thunk/messageThunk' import { removeBlocksThunk } from '@renderer/store/thunk/messageThunk'
import type { SerializedAiSdkError, SerializedAiSdkErrorUnion, SerializedError } from '@renderer/types/error' import type { SerializedAiSdkError, SerializedAiSdkErrorUnion, SerializedError } from '@renderer/types/error'
@ -33,10 +32,10 @@ import {
} from '@renderer/types/error' } from '@renderer/types/error'
import type { ErrorMessageBlock, Message } from '@renderer/types/newMessage' import type { ErrorMessageBlock, Message } from '@renderer/types/newMessage'
import { formatAiSdkError, formatError, safeToString } from '@renderer/utils/error' import { formatAiSdkError, formatError, safeToString } from '@renderer/utils/error'
import { Link } from '@tanstack/react-router'
import { Alert as AntdAlert, Modal } from 'antd' import { Alert as AntdAlert, Modal } from 'antd'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
const HTTP_ERROR_CODES = [400, 401, 403, 404, 429, 500, 502, 503, 504] const HTTP_ERROR_CODES = [400, 401, 403, 404, 429, 500, 502, 503, 504]
@ -71,8 +70,8 @@ const ErrorMessage: React.FC<{ block: ErrorMessageBlock }> = ({ block }) => {
provider: ( provider: (
<Link <Link
style={{ color: 'var(--color-link)' }} style={{ color: 'var(--color-link)' }}
to={`/settings/provider`} to="/settings/provider"
state={{ provider: getProviderById(providerId) }} search={{ id: providerId }}
/> />
) )
}} }}

View File

@ -16,6 +16,7 @@ import styled from 'styled-components'
import AssistantsDrawer from './components/AssistantsDrawer' import AssistantsDrawer from './components/AssistantsDrawer'
import UpdateAppButton from './components/UpdateAppButton' import UpdateAppButton from './components/UpdateAppButton'
interface Props { interface Props {
activeAssistant: Assistant activeAssistant: Assistant
activeTopic: Topic activeTopic: Topic

View File

@ -1,11 +1,11 @@
import App from '@renderer/components/MinApp/MinApp' import App from '@renderer/components/MinApp/MinApp'
import { useMinapps } from '@renderer/hooks/useMinapps' import { useMinapps } from '@renderer/hooks/useMinapps'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { useNavigate } from '@tanstack/react-router'
import { Code, FileSearch, Folder, Languages, LayoutGrid, NotepadText, Palette, Sparkle } from 'lucide-react' import { Code, FileSearch, Folder, Languages, LayoutGrid, NotepadText, Palette, Sparkle } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
const LaunchpadPage: FC = () => { const LaunchpadPage: FC = () => {
@ -87,7 +87,7 @@ const LaunchpadPage: FC = () => {
<SectionTitle>{t('launchpad.apps')}</SectionTitle> <SectionTitle>{t('launchpad.apps')}</SectionTitle>
<Grid> <Grid>
{appMenuItems.map((item) => ( {appMenuItems.map((item) => (
<AppIcon key={item.path} onClick={() => navigate(item.path)}> <AppIcon key={item.path} onClick={() => navigate({ to: item.path })}>
<IconContainer> <IconContainer>
<IconWrapper bgColor={item.bgColor}>{item.icon}</IconWrapper> <IconWrapper bgColor={item.bgColor}>{item.icon}</IconWrapper>
</IconContainer> </IconContainer>

View File

@ -6,10 +6,10 @@ import { useMinapps } from '@renderer/hooks/useMinapps'
import { useNavbarPosition } from '@renderer/hooks/useNavbar' import { useNavbarPosition } from '@renderer/hooks/useNavbar'
import TabsService from '@renderer/services/TabsService' import TabsService from '@renderer/services/TabsService'
import { getWebviewLoaded, onWebviewStateChange, setWebviewLoaded } from '@renderer/utils/webviewStateManager' import { getWebviewLoaded, onWebviewStateChange, setWebviewLoaded } from '@renderer/utils/webviewStateManager'
import { useNavigate, useParams } from '@tanstack/react-router'
import type { WebviewTag } from 'electron' import type { WebviewTag } from 'electron'
import type { FC } from 'react' import type { FC } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import BeatLoader from 'react-spinners/BeatLoader' import BeatLoader from 'react-spinners/BeatLoader'
import styled from 'styled-components' import styled from 'styled-components'
@ -20,7 +20,7 @@ import WebviewSearch from './components/WebviewSearch'
const logger = loggerService.withContext('MinAppPage') const logger = loggerService.withContext('MinAppPage')
const MinAppPage: FC = () => { const MinAppPage: FC = () => {
const { appId } = useParams<{ appId: string }>() const { appId } = useParams({ strict: false }) as { appId: string }
const { isTopNavbar } = useNavbarPosition() const { isTopNavbar } = useNavbarPosition()
const { openMinappKeepAlive, minAppsCache } = useMinappPopup() const { openMinappKeepAlive, minAppsCache } = useMinappPopup()
const { minapps } = useMinapps() const { minapps } = useMinapps()
@ -64,7 +64,7 @@ const MinAppPage: FC = () => {
useEffect(() => { useEffect(() => {
// If app not found, redirect to apps list // If app not found, redirect to apps list
if (!app) { if (!app) {
navigate('/apps') navigate({ to: '/apps' })
return return
} }
@ -72,7 +72,7 @@ const MinAppPage: FC = () => {
// Only check once and only if we haven't already redirected // Only check once and only if we haven't already redirected
if (!initialIsTopNavbar.current && !hasRedirected.current) { if (!initialIsTopNavbar.current && !hasRedirected.current) {
hasRedirected.current = true hasRedirected.current = true
navigate('/apps') navigate({ to: '/apps' })
// Open popup after navigation // Open popup after navigation
setTimeout(() => { setTimeout(() => {
openMinappKeepAlive(app) openMinappKeepAlive(app)

View File

@ -15,11 +15,11 @@ import { isDev } from '@renderer/config/constant'
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
import { useMinapps } from '@renderer/hooks/useMinapps' import { useMinapps } from '@renderer/hooks/useMinapps'
import type { MinAppType } from '@renderer/types' import type { MinAppType } from '@renderer/types'
import { useNavigate } from '@tanstack/react-router'
import type { WebviewTag } from 'electron' import type { WebviewTag } from 'electron'
import type { FC } from 'react' import type { FC } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react' import { useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
const logger = loggerService.withContext('MinimalToolbar') const logger = loggerService.withContext('MinimalToolbar')
@ -213,7 +213,7 @@ const MinimalToolbar: FC<Props> = ({ app, webviewRef, currentUrl, onReload, onOp
}, [app.id, webviewRef, scheduleNavigationUpdate]) }, [app.id, webviewRef, scheduleNavigationUpdate])
const handleMinimize = useCallback(() => { const handleMinimize = useCallback(() => {
navigate('/apps') navigate({ to: '/apps' })
}, [navigate]) }, [navigate])
const handleTogglePin = useCallback(() => { const handleTogglePin = useCallback(() => {

View File

@ -19,12 +19,12 @@ import { translateText } from '@renderer/services/TranslateService'
import type { FileMetadata } from '@renderer/types' import type { FileMetadata } from '@renderer/types'
import type { PaintingAction, PaintingsState } from '@renderer/types' import type { PaintingAction, PaintingsState } from '@renderer/types'
import { getErrorMessage, uuid } from '@renderer/utils' import { getErrorMessage, uuid } from '@renderer/utils'
import { useLocation, useNavigate } from '@tanstack/react-router'
import { Input, InputNumber, Radio, Segmented, Select, Slider, Upload } from 'antd' import { Input, InputNumber, Radio, Segmented, Select, Slider, Upload } from 'antd'
import TextArea from 'antd/es/input/TextArea' import TextArea from 'antd/es/input/TextArea'
import type { FC } from 'react' import type { FC } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useLocation, useNavigate } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import SendMessageButton from '../home/Inputbar/SendMessageButton' import SendMessageButton from '../home/Inputbar/SendMessageButton'
@ -667,7 +667,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
const handleProviderChange = (providerId: string) => { const handleProviderChange = (providerId: string) => {
const routeName = location.pathname.split('/').pop() const routeName = location.pathname.split('/').pop()
if (providerId !== routeName) { if (providerId !== routeName) {
navigate('../' + providerId, { replace: true }) navigate({ to: '../' + providerId, replace: true })
} }
} }

View File

@ -11,13 +11,13 @@ import { useAllProviders } from '@renderer/hooks/useProvider'
import FileManager from '@renderer/services/FileManager' import FileManager from '@renderer/services/FileManager'
import type { FileMetadata } from '@renderer/types' import type { FileMetadata } from '@renderer/types'
import { convertToBase64, uuid } from '@renderer/utils' import { convertToBase64, uuid } from '@renderer/utils'
import { useLocation, useNavigate } from '@tanstack/react-router'
import type { DmxapiPainting } from '@types' import type { DmxapiPainting } from '@types'
import { Input, InputNumber, Segmented, Select } from 'antd' import { Input, InputNumber, Segmented, Select } from 'antd'
import TextArea from 'antd/es/input/TextArea' import TextArea from 'antd/es/input/TextArea'
import type { FC } from 'react' import type { FC } from 'react'
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useLocation, useNavigate } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import { generationModeType } from '../../types' import { generationModeType } from '../../types'
@ -640,7 +640,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
const handleProviderChange = (providerId: string) => { const handleProviderChange = (providerId: string) => {
const routeName = location.pathname.split('/').pop() const routeName = location.pathname.split('/').pop()
if (providerId !== routeName) { if (providerId !== routeName) {
navigate('../' + providerId, { replace: true }) navigate({ to: '../' + providerId, replace: true })
} }
} }

View File

@ -29,13 +29,13 @@ import type { PaintingAction, PaintingsState } from '@renderer/types'
import type { FileMetadata } from '@renderer/types' import type { FileMetadata } from '@renderer/types'
import { getErrorMessage, uuid } from '@renderer/utils' import { getErrorMessage, uuid } from '@renderer/utils'
import { isNewApiProvider } from '@renderer/utils/provider' import { isNewApiProvider } from '@renderer/utils/provider'
import { useLocation, useNavigate } from '@tanstack/react-router'
import { Empty, InputNumber, Segmented, Select, Upload } from 'antd' import { Empty, InputNumber, Segmented, Select, Upload } from 'antd'
import TextArea from 'antd/es/input/TextArea' import TextArea from 'antd/es/input/TextArea'
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useLocation, useNavigate } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import SendMessageButton from '../home/Inputbar/SendMessageButton' import SendMessageButton from '../home/Inputbar/SendMessageButton'
@ -436,7 +436,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
const handleProviderChange = (providerId: string) => { const handleProviderChange = (providerId: string) => {
const routeName = location.pathname.split('/').pop() const routeName = location.pathname.split('/').pop()
if (providerId !== routeName) { if (providerId !== routeName) {
navigate('../' + providerId, { replace: true }) navigate({ to: '../' + providerId, replace: true })
} }
} }
@ -463,7 +463,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
// 当 modelOptions 为空时,引导用户跳转到 Provider 设置页面,新增 image-generation 端点模型 // 当 modelOptions 为空时,引导用户跳转到 Provider 设置页面,新增 image-generation 端点模型
const handleShowAddModelPopup = () => { const handleShowAddModelPopup = () => {
navigate(`/settings/provider?id=${newApiProvider.id}`) navigate({ to: `/settings/provider?id=${newApiProvider.id}` })
} }
useEffect(() => { useEffect(() => {

View File

@ -15,13 +15,13 @@ import FileManager from '@renderer/services/FileManager'
import { translateText } from '@renderer/services/TranslateService' import { translateText } from '@renderer/services/TranslateService'
import type { FileMetadata, OvmsPainting } from '@renderer/types' import type { FileMetadata, OvmsPainting } from '@renderer/types'
import { getErrorMessage, uuid } from '@renderer/utils' import { getErrorMessage, uuid } from '@renderer/utils'
import { useLocation, useNavigate } from '@tanstack/react-router'
import { Avatar, Input, InputNumber, Select, Slider } from 'antd' import { Avatar, Input, InputNumber, Select, Slider } from 'antd'
import TextArea from 'antd/es/input/TextArea' import TextArea from 'antd/es/input/TextArea'
import { Info } from 'lucide-react' import { Info } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useLocation, useNavigate } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import SendMessageButton from '../home/Inputbar/SendMessageButton' import SendMessageButton from '../home/Inputbar/SendMessageButton'
@ -330,7 +330,7 @@ const OvmsPage: FC<{ Options: string[] }> = ({ Options }) => {
const handleProviderChange = (providerId: string) => { const handleProviderChange = (providerId: string) => {
const routeName = location.pathname.split('/').pop() const routeName = location.pathname.split('/').pop()
if (providerId !== routeName) { if (providerId !== routeName) {
navigate('../' + providerId, { replace: true }) navigate({ to: '../' + providerId, replace: true })
} }
} }

View File

@ -5,9 +5,9 @@ import { setDefaultPaintingProvider } from '@renderer/store/settings'
import { updateTab } from '@renderer/store/tabs' import { updateTab } from '@renderer/store/tabs'
import type { PaintingProvider, SystemProviderId } from '@renderer/types' import type { PaintingProvider, SystemProviderId } from '@renderer/types'
import { isNewApiProvider } from '@renderer/utils/provider' import { isNewApiProvider } from '@renderer/utils/provider'
import { useParams } from '@tanstack/react-router'
import type { FC } from 'react' import type { FC } from 'react'
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import { Route, Routes, useParams } from 'react-router-dom'
import AihubmixPage from './AihubmixPage' import AihubmixPage from './AihubmixPage'
import DmxapiPage from './DmxapiPage' import DmxapiPage from './DmxapiPage'
@ -22,8 +22,8 @@ const logger = loggerService.withContext('PaintingsRoutePage')
const BASE_OPTIONS: SystemProviderId[] = ['zhipu', 'aihubmix', 'silicon', 'dmxapi', 'tokenflux', 'ovms'] const BASE_OPTIONS: SystemProviderId[] = ['zhipu', 'aihubmix', 'silicon', 'dmxapi', 'tokenflux', 'ovms']
const PaintingsRoutePage: FC = () => { const PaintingsRoutePage: FC = () => {
const params = useParams() const params = useParams({ strict: false }) as { _splat?: string }
const provider = params['*'] const provider = params._splat
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const providers = useAllProviders() const providers = useAllProviders()
const [ovmsStatus, setOvmsStatus] = useState<'not-installed' | 'not-running' | 'running'>('not-running') const [ovmsStatus, setOvmsStatus] = useState<'not-installed' | 'not-running' | 'running'>('not-running')
@ -49,22 +49,34 @@ const PaintingsRoutePage: FC = () => {
} }
}, [provider, dispatch, validOptions]) }, [provider, dispatch, validOptions])
return ( // 根据 provider 渲染对应的页面
<Routes> const renderPage = () => {
<Route path="*" element={<NewApiPage Options={validOptions} />} /> switch (provider) {
<Route path="/zhipu" element={<ZhipuPage Options={validOptions} />} /> case 'zhipu':
<Route path="/aihubmix" element={<AihubmixPage Options={validOptions} />} /> return <ZhipuPage Options={validOptions} />
<Route path="/silicon" element={<SiliconPage Options={validOptions} />} /> case 'aihubmix':
<Route path="/dmxapi" element={<DmxapiPage Options={validOptions} />} /> return <AihubmixPage Options={validOptions} />
<Route path="/tokenflux" element={<TokenFluxPage Options={validOptions} />} /> case 'silicon':
<Route path="/ovms" element={<OvmsPage Options={validOptions} />} /> return <SiliconPage Options={validOptions} />
<Route path="/new-api" element={<NewApiPage Options={validOptions} />} /> case 'dmxapi':
{/* new-api family providers are mounted dynamically below */} return <DmxapiPage Options={validOptions} />
{newApiProviders.map((p) => ( case 'tokenflux':
<Route key={p.id} path={`/${p.id}`} element={<NewApiPage Options={validOptions} />} /> return <TokenFluxPage Options={validOptions} />
))} case 'ovms':
</Routes> return <OvmsPage Options={validOptions} />
) case 'new-api':
return <NewApiPage Options={validOptions} />
default:
// 检查是否是 new-api 家族的 provider
if (provider && newApiProviders.some((p) => p.id === provider)) {
return <NewApiPage Options={validOptions} />
}
// 默认页面
return <NewApiPage Options={validOptions} />
}
}
return renderPage()
} }
export default PaintingsRoutePage export default PaintingsRoutePage

View File

@ -23,12 +23,12 @@ import FileManager from '@renderer/services/FileManager'
import { translateText } from '@renderer/services/TranslateService' import { translateText } from '@renderer/services/TranslateService'
import type { FileMetadata, Painting } from '@renderer/types' import type { FileMetadata, Painting } from '@renderer/types'
import { getErrorMessage, uuid } from '@renderer/utils' import { getErrorMessage, uuid } from '@renderer/utils'
import { useLocation, useNavigate } from '@tanstack/react-router'
import { Input, InputNumber, Radio, Select, Slider } from 'antd' import { Input, InputNumber, Radio, Select, Slider } from 'antd'
import TextArea from 'antd/es/input/TextArea' import TextArea from 'antd/es/input/TextArea'
import type { FC } from 'react' import type { FC } from 'react'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useLocation, useNavigate } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import SendMessageButton from '../home/Inputbar/SendMessageButton' import SendMessageButton from '../home/Inputbar/SendMessageButton'
@ -333,7 +333,7 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => {
const handleProviderChange = (providerId: string) => { const handleProviderChange = (providerId: string) => {
const routeName = location.pathname.split('/').pop() const routeName = location.pathname.split('/').pop()
if (providerId !== routeName) { if (providerId !== routeName) {
navigate('../' + providerId, { replace: true }) navigate({ to: '../' + providerId, replace: true })
} }
} }

View File

@ -15,12 +15,12 @@ import FileManager from '@renderer/services/FileManager'
import { translateText } from '@renderer/services/TranslateService' import { translateText } from '@renderer/services/TranslateService'
import type { TokenFluxPainting } from '@renderer/types' import type { TokenFluxPainting } from '@renderer/types'
import { getErrorMessage, uuid } from '@renderer/utils' import { getErrorMessage, uuid } from '@renderer/utils'
import { useLocation, useNavigate } from '@tanstack/react-router'
import { Select } from 'antd' import { Select } from 'antd'
import TextArea from 'antd/es/input/TextArea' import TextArea from 'antd/es/input/TextArea'
import type { FC } from 'react' import type { FC } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useLocation, useNavigate } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import SendMessageButton from '../home/Inputbar/SendMessageButton' import SendMessageButton from '../home/Inputbar/SendMessageButton'
@ -268,7 +268,7 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => {
const handleProviderChange = (providerId: string) => { const handleProviderChange = (providerId: string) => {
const routeName = location.pathname.split('/').pop() const routeName = location.pathname.split('/').pop()
if (providerId !== routeName) { if (providerId !== routeName) {
navigate('../' + providerId, { replace: true }) navigate({ to: '../' + providerId, replace: true })
} }
} }

View File

@ -12,12 +12,12 @@ import { usePaintings } from '@renderer/hooks/usePaintings'
import { useAllProviders } from '@renderer/hooks/useProvider' import { useAllProviders } from '@renderer/hooks/useProvider'
import FileManager from '@renderer/services/FileManager' import FileManager from '@renderer/services/FileManager'
import { getErrorMessage, uuid } from '@renderer/utils' import { getErrorMessage, uuid } from '@renderer/utils'
import { useLocation, useNavigate } from '@tanstack/react-router'
import { InputNumber, Radio, Select } from 'antd' import { InputNumber, Radio, Select } from 'antd'
import TextArea from 'antd/es/input/TextArea' import TextArea from 'antd/es/input/TextArea'
import type { FC } from 'react' import type { FC } from 'react'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useLocation, useNavigate } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import SendMessageButton from '../home/Inputbar/SendMessageButton' import SendMessageButton from '../home/Inputbar/SendMessageButton'
@ -260,7 +260,7 @@ const ZhipuPage: FC<{ Options: string[] }> = ({ Options }) => {
const handleProviderChange = (providerId: string) => { const handleProviderChange = (providerId: string) => {
const routeName = location.pathname.split('/').pop() const routeName = location.pathname.split('/').pop()
if (providerId !== routeName) { if (providerId !== routeName) {
navigate('../' + providerId, { replace: true }) navigate({ to: '../' + providerId, replace: true })
} }
} }

View File

@ -15,7 +15,7 @@ export function checkProviderEnabled(provider: Provider, t: TFunction): Promise<
closable: true, closable: true,
okText: t('common.go_to_settings'), okText: t('common.go_to_settings'),
onOk: () => { onOk: () => {
window.navigate?.(`/settings/provider?id=${provider.id}`) window.navigate?.({ to: `/settings/provider`, search: { id: provider.id } })
reject('Provider disabled') reject('Provider disabled')
}, },
onCancel: () => reject('Provider disabled') onCancel: () => reject('Provider disabled')

View File

@ -13,6 +13,7 @@ import i18n from '@renderer/i18n'
// import { setUpdateState as setAppUpdateState } from '@renderer/store/runtime' // import { setUpdateState as setAppUpdateState } from '@renderer/store/runtime'
import { runAsyncFunction } from '@renderer/utils' import { runAsyncFunction } from '@renderer/utils'
import { ThemeMode, UpgradeChannel } from '@shared/data/preference/preferenceTypes' import { ThemeMode, UpgradeChannel } from '@shared/data/preference/preferenceTypes'
import { Link } from '@tanstack/react-router'
import { Avatar, Progress, Radio, Row, Tag } from 'antd' import { Avatar, Progress, Radio, Row, Tag } from 'antd'
import { debounce } from 'lodash' import { debounce } from 'lodash'
import { Bug, Building2, Github, Globe, Mail, Rss } from 'lucide-react' import { Bug, Building2, Github, Globe, Mail, Rss } from 'lucide-react'
@ -21,7 +22,6 @@ import type { FC } from 'react'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Markdown from 'react-markdown' import Markdown from 'react-markdown'
import { Link } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingTitle } from '.' import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingTitle } from '.'

View File

@ -3,11 +3,11 @@ import { Center, ColFlex } from '@cherrystudio/ui'
import { Button } from '@cherrystudio/ui' import { Button } from '@cherrystudio/ui'
import { useAppDispatch, useAppSelector } from '@renderer/store' import { useAppDispatch, useAppSelector } from '@renderer/store'
import { setIsBunInstalled, setIsUvInstalled } from '@renderer/store/mcp' import { setIsBunInstalled, setIsUvInstalled } from '@renderer/store/mcp'
import { useNavigate } from '@tanstack/react-router'
import { Alert } from 'antd' import { Alert } from 'antd'
import type { FC } from 'react' import type { FC } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react' import { useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router'
import styled from 'styled-components' import styled from 'styled-components'
import { SettingDescription, SettingRow, SettingSubtitle } from '..' import { SettingDescription, SettingRow, SettingSubtitle } from '..'
@ -87,7 +87,7 @@ const InstallNpxUv: FC<Props> = ({ mini = false }) => {
<Button <Button
className="nodrag rounded-full" className="nodrag rounded-full"
variant={installed ? 'default' : 'destructive'} variant={installed ? 'default' : 'destructive'}
onClick={() => navigate('/settings/mcp/mcp-install')} onClick={() => navigate({ to: '/settings/mcp/mcp-install' })}
size="icon"> size="icon">
{installed ? <CheckCircleOutlined /> : <WarningOutlined />} {installed ? <CheckCircleOutlined /> : <WarningOutlined />}
</Button> </Button>

View File

@ -9,12 +9,12 @@ import { useMCPServerTrust } from '@renderer/hooks/useMCPServerTrust'
import type { MCPServer } from '@renderer/types' import type { MCPServer } from '@renderer/types'
import { formatMcpError } from '@renderer/utils/error' import { formatMcpError } from '@renderer/utils/error'
import { matchKeywordsInString } from '@renderer/utils/match' import { matchKeywordsInString } from '@renderer/utils/match'
import { useNavigate } from '@tanstack/react-router'
import { Button, Dropdown, Empty } from 'antd' import { Button, Dropdown, Empty } from 'antd'
import { Plus } from 'lucide-react' import { Plus } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { startTransition, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { startTransition, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router'
import styled from 'styled-components' import styled from 'styled-components'
import { SettingTitle } from '..' import { SettingTitle } from '..'
@ -115,7 +115,7 @@ const McpServersList: FC = () => {
isActive: false isActive: false
} }
addMCPServer(newServer) addMCPServer(newServer)
navigate(`/settings/mcp/settings/${encodeURIComponent(newServer.id)}`) navigate({ to: `/settings/mcp/settings/${encodeURIComponent(newServer.id)}` })
window.toast.success(t('settings.mcp.addSuccess')) window.toast.success(t('settings.mcp.addSuccess'))
}, [addMCPServer, navigate, t]) }, [addMCPServer, navigate, t])
@ -260,7 +260,7 @@ const McpServersList: FC = () => {
isLoading={loadingServerIds.has(server.id)} isLoading={loadingServerIds.has(server.id)}
onToggle={async (active) => await handleToggleActive(server, active)} onToggle={async (active) => await handleToggleActive(server, active)}
onDelete={() => onDeleteMcpServer(server)} onDelete={() => onDeleteMcpServer(server)}
onEdit={() => navigate(`/settings/mcp/settings/${encodeURIComponent(server.id)}`)} onEdit={() => navigate({ to: `/settings/mcp/settings/${encodeURIComponent(server.id)}` })}
onOpenUrl={(url) => window.open(url, '_blank')} onOpenUrl={(url) => window.open(url, '_blank')}
/> />
)} )}

View File

@ -11,13 +11,13 @@ import MCPDescription from '@renderer/pages/settings/MCPSettings/McpDescription'
import type { MCPPrompt, MCPResource, MCPServer, MCPTool } from '@renderer/types' import type { MCPPrompt, MCPResource, MCPServer, MCPTool } from '@renderer/types'
import { parseKeyValueString } from '@renderer/utils/env' import { parseKeyValueString } from '@renderer/utils/env'
import { formatMcpError } from '@renderer/utils/error' import { formatMcpError } from '@renderer/utils/error'
import { useNavigate, useParams } from '@tanstack/react-router'
import type { TabsProps } from 'antd' import type { TabsProps } from 'antd'
import { Badge, Form, Input, Radio, Select, Tabs } from 'antd' import { Badge, Form, Input, Radio, Select, Tabs } from 'antd'
import TextArea from 'antd/es/input/TextArea' import TextArea from 'antd/es/input/TextArea'
import { ChevronDown, SaveIcon } from 'lucide-react' import { ChevronDown, SaveIcon } from 'lucide-react'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useNavigate, useParams } from 'react-router'
import styled from 'styled-components' import styled from 'styled-components'
import { SettingContainer, SettingDivider, SettingGroup, SettingTitle } from '..' import { SettingContainer, SettingDivider, SettingGroup, SettingTitle } from '..'
@ -68,7 +68,8 @@ type TabKey = 'settings' | 'description' | 'tools' | 'prompts' | 'resources'
const McpSettings: React.FC = () => { const McpSettings: React.FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { serverId } = useParams<{ serverId: string }>() const params = useParams({ strict: false }) as { serverId?: string }
const serverId = params.serverId
const decodedServerId = serverId ? decodeURIComponent(serverId) : '' const decodedServerId = serverId ? decodeURIComponent(serverId) : ''
const server = useMCPServer(decodedServerId).server as MCPServer const server = useMCPServer(decodedServerId).server as MCPServer
const { deleteMCPServer, updateMCPServer } = useMCPServers() const { deleteMCPServer, updateMCPServer } = useMCPServers()
@ -376,7 +377,7 @@ const McpSettings: React.FC = () => {
await window.api.mcp.removeServer(server) await window.api.mcp.removeServer(server)
deleteMCPServer(server.id) deleteMCPServer(server.id)
window.toast.success(t('settings.mcp.deleteSuccess')) window.toast.success(t('settings.mcp.deleteSuccess'))
navigate('/settings/mcp') navigate({ to: '/settings/mcp' })
} }
}) })
} catch (error: any) { } catch (error: any) {

View File

@ -8,30 +8,17 @@ import TokenFluxProviderLogo from '@renderer/assets/images/providers/tokenflux.p
import DividerWithText from '@renderer/components/DividerWithText' import DividerWithText from '@renderer/components/DividerWithText'
import ListItem from '@renderer/components/ListItem' import ListItem from '@renderer/components/ListItem'
import Scrollbar from '@renderer/components/Scrollbar' import Scrollbar from '@renderer/components/Scrollbar'
import { useTheme } from '@renderer/context/ThemeProvider' import { Link, Outlet, useLocation, useNavigate } from '@tanstack/react-router'
import { useMCPServers } from '@renderer/hooks/useMCPServers'
import { Button, Flex } from 'antd' import { Button, Flex } from 'antd'
import { FolderCog, Package, ShoppingBag } from 'lucide-react' import { FolderCog, Package, ShoppingBag } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Navigate, Route, Routes, useLocation, useNavigate } from 'react-router'
import { Link } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import { SettingContainer } from '..'
import BuiltinMCPServerList from './BuiltinMCPServerList'
import InstallNpxUv from './InstallNpxUv'
import McpMarketList from './McpMarketList'
import ProviderDetail from './McpProviderSettings'
import McpServersList from './McpServersList'
import McpSettings from './McpSettings'
import NpxSearch from './NpxSearch'
import { providers } from './providers/config' import { providers } from './providers/config'
const MCPSettings: FC = () => { const MCPSettings: FC = () => {
const { theme } = useTheme()
const { t } = useTranslation() const { t } = useTranslation()
const { mcpServers } = useMCPServers()
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation() const location = useLocation()
@ -84,7 +71,7 @@ const MCPSettings: FC = () => {
<ListItem <ListItem
title={t('settings.mcp.servers', 'MCP Servers')} title={t('settings.mcp.servers', 'MCP Servers')}
active={activeView === 'servers'} active={activeView === 'servers'}
onClick={() => navigate('/settings/mcp/servers')} onClick={() => navigate({ to: '/settings/mcp/servers' })}
icon={<FolderCog size={18} />} icon={<FolderCog size={18} />}
titleStyle={{ fontWeight: 500 }} titleStyle={{ fontWeight: 500 }}
/> />
@ -92,14 +79,14 @@ const MCPSettings: FC = () => {
<ListItem <ListItem
title={t('settings.mcp.builtinServers', 'Built-in Servers')} title={t('settings.mcp.builtinServers', 'Built-in Servers')}
active={activeView === 'builtin'} active={activeView === 'builtin'}
onClick={() => navigate('/settings/mcp/builtin')} onClick={() => navigate({ to: '/settings/mcp/builtin' })}
icon={<Package size={18} />} icon={<Package size={18} />}
titleStyle={{ fontWeight: 500 }} titleStyle={{ fontWeight: 500 }}
/> />
<ListItem <ListItem
title={t('settings.mcp.marketplaces', 'Marketplaces')} title={t('settings.mcp.marketplaces', 'Marketplaces')}
active={activeView === 'marketplaces'} active={activeView === 'marketplaces'}
onClick={() => navigate('/settings/mcp/marketplaces')} onClick={() => navigate({ to: '/settings/mcp/marketplaces' })}
icon={<ShoppingBag size={18} />} icon={<ShoppingBag size={18} />}
titleStyle={{ fontWeight: 500 }} titleStyle={{ fontWeight: 500 }}
/> />
@ -109,7 +96,7 @@ const MCPSettings: FC = () => {
key={provider.key} key={provider.key}
title={provider.name} title={provider.name}
active={activeView === provider.key} active={activeView === provider.key}
onClick={() => navigate(`/settings/mcp/${provider.key}`)} onClick={() => navigate({ to: `/settings/mcp/${provider.key}` })}
icon={providerIcons[provider.key] || <FolderCog size={16} />} icon={providerIcons[provider.key] || <FolderCog size={16} />}
titleStyle={{ fontWeight: 500 }} titleStyle={{ fontWeight: 500 }}
/> />
@ -125,50 +112,7 @@ const MCPSettings: FC = () => {
</Link> </Link>
</BackButtonContainer> </BackButtonContainer>
)} )}
<Routes> <Outlet />
<Route index element={<Navigate to="servers" replace />} />
<Route path="servers" element={<McpServersList />} />
<Route path="settings/:serverId" element={<McpSettings />} />
<Route
path="npx-search"
element={
<SettingContainer theme={theme}>
<NpxSearch />
</SettingContainer>
}
/>
<Route
path="mcp-install"
element={
<SettingContainer style={{ backgroundColor: 'inherit' }}>
<InstallNpxUv />
</SettingContainer>
}
/>
<Route
path="builtin"
element={
<ContentWrapper>
<BuiltinMCPServerList />
</ContentWrapper>
}
/>
<Route
path="marketplaces"
element={
<ContentWrapper>
<McpMarketList />
</ContentWrapper>
}
/>
{providers.map((provider) => (
<Route
key={provider.key}
path={provider.key}
element={<ProviderDetail provider={provider} existingServers={mcpServers} />}
/>
))}
</Routes>
</RightContainer> </RightContainer>
</MainContainer> </MainContainer>
</Container> </Container>
@ -212,12 +156,6 @@ const ProviderIcon = styled.img`
background-color: var(--color-background-soft); background-color: var(--color-background-soft);
` `
const ContentWrapper = styled.div`
padding: 20px;
overflow-y: auto;
height: 100%;
`
const BackButtonContainer = styled.div` const BackButtonContainer = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -14,13 +14,13 @@ import ImageStorage from '@renderer/services/ImageStorage'
import type { Provider, ProviderType } from '@renderer/types' import type { Provider, ProviderType } from '@renderer/types'
import { isSystemProvider } from '@renderer/types' import { isSystemProvider } from '@renderer/types'
import { getFancyProviderName, matchKeywordsInModel, matchKeywordsInProvider, uuid } from '@renderer/utils' import { getFancyProviderName, matchKeywordsInModel, matchKeywordsInProvider, uuid } from '@renderer/utils'
import { useLocation, useNavigate, useSearch } from '@tanstack/react-router'
import type { MenuProps } from 'antd' import type { MenuProps } from 'antd'
import { Dropdown, Input, Tag } from 'antd' import { Dropdown, Input, Tag } from 'antd'
import { GripVertical, PlusIcon, Search, UserPen } from 'lucide-react' import { GripVertical, PlusIcon, Search, UserPen } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { startTransition, useCallback, useEffect, useRef, useState } from 'react' import { startTransition, useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useSearchParams } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import AddProviderPopup from './AddProviderPopup' import AddProviderPopup from './AddProviderPopup'
@ -35,7 +35,9 @@ const systemType = await window.api.system.getDeviceType()
const cpuName = await window.api.system.getCpuName() const cpuName = await window.api.system.getCpuName()
const ProviderList: FC = () => { const ProviderList: FC = () => {
const [searchParams, setSearchParams] = useSearchParams() const search = useSearch({ strict: false }) as Record<string, string | undefined>
const navigate = useNavigate()
const location = useLocation()
const providers = useAllProviders() const providers = useAllProviders()
const { updateProviders, addProvider, removeProvider, updateProvider } = useProviders() const { updateProviders, addProvider, removeProvider, updateProvider } = useProviders()
const { setTimeoutTimer } = useTimer() const { setTimeoutTimer } = useTimer()
@ -72,8 +74,8 @@ const ProviderList: FC = () => {
}, [providers]) }, [providers])
useEffect(() => { useEffect(() => {
if (searchParams.get('id')) { if (search.id) {
const providerId = searchParams.get('id') const providerId = search.id
const provider = providers.find((p) => p.id === providerId) const provider = providers.find((p) => p.id === providerId)
if (provider) { if (provider) {
setSelectedProvider(provider) setSelectedProvider(provider)
@ -89,10 +91,17 @@ const ProviderList: FC = () => {
} else { } else {
setSelectedProvider(providers[0]) setSelectedProvider(providers[0])
} }
searchParams.delete('id') // 清除 id 参数
setSearchParams(searchParams) navigate({
to: location.pathname,
search: (prev) => {
const { id: _, ...rest } = prev as Record<string, unknown>
return rest
},
replace: true
})
} }
}, [providers, searchParams, setSearchParams, setSelectedProvider, setTimeoutTimer]) }, [providers, search.id, navigate, location.pathname, setSelectedProvider, setTimeoutTimer])
// Handle provider add key from URL schema // Handle provider add key from URL schema
useEffect(() => { useEffect(() => {
@ -106,7 +115,7 @@ const ProviderList: FC = () => {
const { id } = data const { id } = data
const { updatedProvider, isNew, displayName } = await UrlSchemaInfoPopup.show(data) const { updatedProvider, isNew, displayName } = await UrlSchemaInfoPopup.show(data)
window.navigate(`/settings/provider?id=${id}`) navigate({ to: '/settings/provider', search: { id } })
if (!updatedProvider) { if (!updatedProvider) {
return return
@ -123,7 +132,7 @@ const ProviderList: FC = () => {
} }
// 检查 URL 参数 // 检查 URL 参数
const addProviderData = searchParams.get('addProviderData') const addProviderData = search.addProviderData
if (!addProviderData) { if (!addProviderData) {
return return
} }
@ -132,17 +141,17 @@ const ProviderList: FC = () => {
const { id, apiKey: newApiKey, baseUrl, type, name } = JSON.parse(addProviderData) const { id, apiKey: newApiKey, baseUrl, type, name } = JSON.parse(addProviderData)
if (!id || !newApiKey || !baseUrl) { if (!id || !newApiKey || !baseUrl) {
window.toast.error(t('settings.models.provider_key_add_failed_by_invalid_data')) window.toast.error(t('settings.models.provider_key_add_failed_by_invalid_data'))
window.navigate('/settings/provider') navigate({ to: '/settings/provider' })
return return
} }
handleProviderAddKey({ id, apiKey: newApiKey, baseUrl, type, name }) handleProviderAddKey({ id, apiKey: newApiKey, baseUrl, type, name })
} catch (error) { } catch (error) {
window.toast.error(t('settings.models.provider_key_add_failed_by_invalid_data')) window.toast.error(t('settings.models.provider_key_add_failed_by_invalid_data'))
window.navigate('/settings/provider') navigate({ to: '/settings/provider' })
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams]) }, [search.addProviderData])
const onAddProvider = async () => { const onAddProvider = async () => {
const { name: providerName, type, logo } = await AddProviderPopup.show() const { name: providerName, type, logo } = await AddProviderPopup.show()

View File

@ -6,12 +6,12 @@ import { useTheme } from '@renderer/context/ThemeProvider'
import { getSelectionDescriptionLabel } from '@renderer/i18n/label' import { getSelectionDescriptionLabel } from '@renderer/i18n/label'
import SelectionToolbar from '@renderer/windows/selection/toolbar/SelectionToolbar' import SelectionToolbar from '@renderer/windows/selection/toolbar/SelectionToolbar'
import type { SelectionFilterMode, SelectionTriggerMode } from '@shared/data/preference/preferenceTypes' import type { SelectionFilterMode, SelectionTriggerMode } from '@shared/data/preference/preferenceTypes'
import { Link } from '@tanstack/react-router'
import { Row, Slider } from 'antd' import { Row, Slider } from 'antd'
import { CircleHelp, Edit2 } from 'lucide-react' import { CircleHelp, Edit2 } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import { import {

View File

@ -1,7 +1,7 @@
import { GlobalOutlined } from '@ant-design/icons' import { GlobalOutlined } from '@ant-design/icons'
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import Scrollbar from '@renderer/components/Scrollbar' import Scrollbar from '@renderer/components/Scrollbar'
import ModelSettings from '@renderer/pages/settings/ModelSettings/ModelSettings' import { Link, Outlet, useLocation } from '@tanstack/react-router'
import { Divider as AntDivider } from 'antd' import { Divider as AntDivider } from 'antd'
import { import {
Brain, Brain,
@ -22,27 +22,11 @@ import {
} from 'lucide-react' } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Link, Route, Routes, useLocation } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import AboutSettings from './AboutSettings'
import DataSettings from './DataSettings/DataSettings'
import DisplaySettings from './DisplaySettings/DisplaySettings'
import DocProcessSettings from './DocProcessSettings'
import GeneralSettings from './GeneralSettings'
import MCPSettings from './MCPSettings'
import MemorySettings from './MemorySettings'
import NotesSettings from './NotesSettings'
import { ProviderList } from './ProviderSettings'
import QuickAssistantSettings from './QuickAssistantSettings'
import QuickPhraseSettings from './QuickPhraseSettings'
import SelectionAssistantSettings from './SelectionAssistantSettings/SelectionAssistantSettings'
import ShortcutSettings from './ShortcutSettings'
import { ApiServerSettings } from './ToolSettings/ApiServerSettings'
import WebSearchSettings from './WebSearchSettings'
const SettingsPage: FC = () => { const SettingsPage: FC = () => {
const { pathname } = useLocation() const location = useLocation()
const { pathname } = location
const { t } = useTranslation() const { t } = useTranslation()
const isRoute = (path: string): string => (pathname.startsWith(path) ? 'active' : '') const isRoute = (path: string): string => (pathname.startsWith(path) ? 'active' : '')
@ -156,24 +140,7 @@ const SettingsPage: FC = () => {
</MenuItemLink> </MenuItemLink>
</SettingMenus> </SettingMenus>
<SettingContent> <SettingContent>
<Routes> <Outlet />
<Route path="provider" element={<ProviderList />} />
<Route path="model" element={<ModelSettings />} />
<Route path="websearch" element={<WebSearchSettings />} />
<Route path="api-server" element={<ApiServerSettings />} />
<Route path="docprocess" element={<DocProcessSettings />} />
<Route path="quickphrase" element={<QuickPhraseSettings />} />
<Route path="mcp/*" element={<MCPSettings />} />
<Route path="memory" element={<MemorySettings />} />
<Route path="general/*" element={<GeneralSettings />} />
<Route path="display" element={<DisplaySettings />} />
<Route path="shortcut" element={<ShortcutSettings />} />
<Route path="quickAssistant" element={<QuickAssistantSettings />} />
<Route path="selectionAssistant" element={<SelectionAssistantSettings />} />
<Route path="data" element={<DataSettings />} />
<Route path="notes" element={<NotesSettings />} />
<Route path="about" element={<AboutSettings />} />
</Routes>
</SettingContent> </SettingContent>
</ContentContainer> </ContentContainer>
</Container> </Container>

View File

@ -9,48 +9,510 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. // 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 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 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 IndexRouteImport } from './routes/index' 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'
import { Route as SettingsQuickphraseRouteImport } from './routes/settings/quickphrase'
import { Route as SettingsQuickAssistantRouteImport } from './routes/settings/quickAssistant'
import { Route as SettingsProviderRouteImport } from './routes/settings/provider'
import { Route as SettingsNotesRouteImport } from './routes/settings/notes'
import { Route as SettingsModelRouteImport } from './routes/settings/model'
import { Route as SettingsMemoryRouteImport } from './routes/settings/memory'
import { Route as SettingsMcpRouteImport } from './routes/settings/mcp'
import { Route as SettingsGeneralRouteImport } from './routes/settings/general'
import { Route as SettingsDocprocessRouteImport } from './routes/settings/docprocess'
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 SettingsMcpIndexRouteImport } from './routes/settings/mcp/index'
import { Route as SettingsMcpServersRouteImport } from './routes/settings/mcp/servers'
import { Route as SettingsMcpNpxSearchRouteImport } from './routes/settings/mcp/npx-search'
import { Route as SettingsMcpMcpInstallRouteImport } from './routes/settings/mcp/mcp-install'
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 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({ const SettingsRoute = SettingsRouteImport.update({
id: '/settings', id: '/settings',
path: '/settings', path: '/settings',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const NotesRoute = NotesRouteImport.update({
id: '/notes',
path: '/notes',
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',
getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({ const IndexRoute = IndexRouteImport.update({
id: '/', id: '/',
path: '/', path: '/',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const SettingsIndexRoute = SettingsIndexRouteImport.update({
id: '/',
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',
getParentRoute: () => SettingsRoute,
} as any)
const SettingsShortcutRoute = SettingsShortcutRouteImport.update({
id: '/shortcut',
path: '/shortcut',
getParentRoute: () => SettingsRoute,
} as any)
const SettingsSelectionAssistantRoute =
SettingsSelectionAssistantRouteImport.update({
id: '/selectionAssistant',
path: '/selectionAssistant',
getParentRoute: () => SettingsRoute,
} as any)
const SettingsQuickphraseRoute = SettingsQuickphraseRouteImport.update({
id: '/quickphrase',
path: '/quickphrase',
getParentRoute: () => SettingsRoute,
} as any)
const SettingsQuickAssistantRoute = SettingsQuickAssistantRouteImport.update({
id: '/quickAssistant',
path: '/quickAssistant',
getParentRoute: () => SettingsRoute,
} as any)
const SettingsProviderRoute = SettingsProviderRouteImport.update({
id: '/provider',
path: '/provider',
getParentRoute: () => SettingsRoute,
} as any)
const SettingsNotesRoute = SettingsNotesRouteImport.update({
id: '/notes',
path: '/notes',
getParentRoute: () => SettingsRoute,
} as any)
const SettingsModelRoute = SettingsModelRouteImport.update({
id: '/model',
path: '/model',
getParentRoute: () => SettingsRoute,
} as any)
const SettingsMemoryRoute = SettingsMemoryRouteImport.update({
id: '/memory',
path: '/memory',
getParentRoute: () => SettingsRoute,
} as any)
const SettingsMcpRoute = SettingsMcpRouteImport.update({
id: '/mcp',
path: '/mcp',
getParentRoute: () => SettingsRoute,
} as any)
const SettingsGeneralRoute = SettingsGeneralRouteImport.update({
id: '/general',
path: '/general',
getParentRoute: () => SettingsRoute,
} as any)
const SettingsDocprocessRoute = SettingsDocprocessRouteImport.update({
id: '/docprocess',
path: '/docprocess',
getParentRoute: () => SettingsRoute,
} as any)
const SettingsDisplayRoute = SettingsDisplayRouteImport.update({
id: '/display',
path: '/display',
getParentRoute: () => SettingsRoute,
} as any)
const SettingsDataRoute = SettingsDataRouteImport.update({
id: '/data',
path: '/data',
getParentRoute: () => SettingsRoute,
} as any)
const SettingsApiServerRoute = SettingsApiServerRouteImport.update({
id: '/api-server',
path: '/api-server',
getParentRoute: () => SettingsRoute,
} as any)
const SettingsAboutRoute = SettingsAboutRouteImport.update({
id: '/about',
path: '/about',
getParentRoute: () => SettingsRoute,
} as any)
const PaintingsSplatRoute = PaintingsSplatRouteImport.update({
id: '/paintings/$',
path: '/paintings/$',
getParentRoute: () => rootRouteImport,
} as any)
const AppsAppIdRoute = AppsAppIdRouteImport.update({
id: '/apps/$appId',
path: '/apps/$appId',
getParentRoute: () => rootRouteImport,
} as any)
const SettingsMcpIndexRoute = SettingsMcpIndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => SettingsMcpRoute,
} as any)
const SettingsMcpServersRoute = SettingsMcpServersRouteImport.update({
id: '/servers',
path: '/servers',
getParentRoute: () => SettingsMcpRoute,
} as any)
const SettingsMcpNpxSearchRoute = SettingsMcpNpxSearchRouteImport.update({
id: '/npx-search',
path: '/npx-search',
getParentRoute: () => SettingsMcpRoute,
} as any)
const SettingsMcpMcpInstallRoute = SettingsMcpMcpInstallRouteImport.update({
id: '/mcp-install',
path: '/mcp-install',
getParentRoute: () => SettingsMcpRoute,
} as any)
const SettingsMcpMarketplacesRoute = SettingsMcpMarketplacesRouteImport.update({
id: '/marketplaces',
path: '/marketplaces',
getParentRoute: () => SettingsMcpRoute,
} as any)
const SettingsMcpBuiltinRoute = SettingsMcpBuiltinRouteImport.update({
id: '/builtin',
path: '/builtin',
getParentRoute: () => SettingsMcpRoute,
} as any)
const SettingsMcpSplatRoute = SettingsMcpSplatRouteImport.update({
id: '/$',
path: '/$',
getParentRoute: () => SettingsMcpRoute,
} as any)
const SettingsMcpSettingsServerIdRoute =
SettingsMcpSettingsServerIdRouteImport.update({
id: '/settings/$serverId',
path: '/settings/$serverId',
getParentRoute: () => SettingsMcpRoute,
} as any)
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute '/': typeof IndexRoute
'/settings': typeof SettingsRoute '/chat': typeof ChatRoute
'/code': typeof CodeRoute
'/files': typeof FilesRoute
'/knowledge': typeof KnowledgeRoute
'/notes': typeof NotesRoute
'/settings': typeof SettingsRouteWithChildren
'/store': typeof StoreRoute
'/translate': typeof TranslateRoute
'/apps/$appId': typeof AppsAppIdRoute
'/paintings/$': typeof PaintingsSplatRoute
'/settings/about': typeof SettingsAboutRoute
'/settings/api-server': typeof SettingsApiServerRoute
'/settings/data': typeof SettingsDataRoute
'/settings/display': typeof SettingsDisplayRoute
'/settings/docprocess': typeof SettingsDocprocessRoute
'/settings/general': typeof SettingsGeneralRoute
'/settings/mcp': typeof SettingsMcpRouteWithChildren
'/settings/memory': typeof SettingsMemoryRoute
'/settings/model': typeof SettingsModelRoute
'/settings/notes': typeof SettingsNotesRoute
'/settings/provider': typeof SettingsProviderRoute
'/settings/quickAssistant': typeof SettingsQuickAssistantRoute
'/settings/quickphrase': typeof SettingsQuickphraseRoute
'/settings/selectionAssistant': typeof SettingsSelectionAssistantRoute
'/settings/shortcut': typeof SettingsShortcutRoute
'/settings/websearch': typeof SettingsWebsearchRoute
'/apps': typeof AppsIndexRoute
'/paintings': typeof PaintingsIndexRoute
'/settings/': typeof SettingsIndexRoute
'/settings/mcp/$': typeof SettingsMcpSplatRoute
'/settings/mcp/builtin': typeof SettingsMcpBuiltinRoute
'/settings/mcp/marketplaces': typeof SettingsMcpMarketplacesRoute
'/settings/mcp/mcp-install': typeof SettingsMcpMcpInstallRoute
'/settings/mcp/npx-search': typeof SettingsMcpNpxSearchRoute
'/settings/mcp/servers': typeof SettingsMcpServersRoute
'/settings/mcp/': typeof SettingsMcpIndexRoute
'/settings/mcp/settings/$serverId': typeof SettingsMcpSettingsServerIdRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/': typeof IndexRoute '/': typeof IndexRoute
'/settings': typeof SettingsRoute '/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
'/settings/about': typeof SettingsAboutRoute
'/settings/api-server': typeof SettingsApiServerRoute
'/settings/data': typeof SettingsDataRoute
'/settings/display': typeof SettingsDisplayRoute
'/settings/docprocess': typeof SettingsDocprocessRoute
'/settings/general': typeof SettingsGeneralRoute
'/settings/memory': typeof SettingsMemoryRoute
'/settings/model': typeof SettingsModelRoute
'/settings/notes': typeof SettingsNotesRoute
'/settings/provider': typeof SettingsProviderRoute
'/settings/quickAssistant': typeof SettingsQuickAssistantRoute
'/settings/quickphrase': typeof SettingsQuickphraseRoute
'/settings/selectionAssistant': typeof SettingsSelectionAssistantRoute
'/settings/shortcut': typeof SettingsShortcutRoute
'/settings/websearch': typeof SettingsWebsearchRoute
'/apps': typeof AppsIndexRoute
'/paintings': typeof PaintingsIndexRoute
'/settings': typeof SettingsIndexRoute
'/settings/mcp/$': typeof SettingsMcpSplatRoute
'/settings/mcp/builtin': typeof SettingsMcpBuiltinRoute
'/settings/mcp/marketplaces': typeof SettingsMcpMarketplacesRoute
'/settings/mcp/mcp-install': typeof SettingsMcpMcpInstallRoute
'/settings/mcp/npx-search': typeof SettingsMcpNpxSearchRoute
'/settings/mcp/servers': typeof SettingsMcpServersRoute
'/settings/mcp': typeof SettingsMcpIndexRoute
'/settings/mcp/settings/$serverId': typeof SettingsMcpSettingsServerIdRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRouteImport __root__: typeof rootRouteImport
'/': typeof IndexRoute '/': typeof IndexRoute
'/settings': typeof SettingsRoute '/chat': typeof ChatRoute
'/code': typeof CodeRoute
'/files': typeof FilesRoute
'/knowledge': typeof KnowledgeRoute
'/notes': typeof NotesRoute
'/settings': typeof SettingsRouteWithChildren
'/store': typeof StoreRoute
'/translate': typeof TranslateRoute
'/apps/$appId': typeof AppsAppIdRoute
'/paintings/$': typeof PaintingsSplatRoute
'/settings/about': typeof SettingsAboutRoute
'/settings/api-server': typeof SettingsApiServerRoute
'/settings/data': typeof SettingsDataRoute
'/settings/display': typeof SettingsDisplayRoute
'/settings/docprocess': typeof SettingsDocprocessRoute
'/settings/general': typeof SettingsGeneralRoute
'/settings/mcp': typeof SettingsMcpRouteWithChildren
'/settings/memory': typeof SettingsMemoryRoute
'/settings/model': typeof SettingsModelRoute
'/settings/notes': typeof SettingsNotesRoute
'/settings/provider': typeof SettingsProviderRoute
'/settings/quickAssistant': typeof SettingsQuickAssistantRoute
'/settings/quickphrase': typeof SettingsQuickphraseRoute
'/settings/selectionAssistant': typeof SettingsSelectionAssistantRoute
'/settings/shortcut': typeof SettingsShortcutRoute
'/settings/websearch': typeof SettingsWebsearchRoute
'/apps/': typeof AppsIndexRoute
'/paintings/': typeof PaintingsIndexRoute
'/settings/': typeof SettingsIndexRoute
'/settings/mcp/$': typeof SettingsMcpSplatRoute
'/settings/mcp/builtin': typeof SettingsMcpBuiltinRoute
'/settings/mcp/marketplaces': typeof SettingsMcpMarketplacesRoute
'/settings/mcp/mcp-install': typeof SettingsMcpMcpInstallRoute
'/settings/mcp/npx-search': typeof SettingsMcpNpxSearchRoute
'/settings/mcp/servers': typeof SettingsMcpServersRoute
'/settings/mcp/': typeof SettingsMcpIndexRoute
'/settings/mcp/settings/$serverId': typeof SettingsMcpSettingsServerIdRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/settings' fullPaths:
| '/'
| '/chat'
| '/code'
| '/files'
| '/knowledge'
| '/notes'
| '/settings'
| '/store'
| '/translate'
| '/apps/$appId'
| '/paintings/$'
| '/settings/about'
| '/settings/api-server'
| '/settings/data'
| '/settings/display'
| '/settings/docprocess'
| '/settings/general'
| '/settings/mcp'
| '/settings/memory'
| '/settings/model'
| '/settings/notes'
| '/settings/provider'
| '/settings/quickAssistant'
| '/settings/quickphrase'
| '/settings/selectionAssistant'
| '/settings/shortcut'
| '/settings/websearch'
| '/apps'
| '/paintings'
| '/settings/'
| '/settings/mcp/$'
| '/settings/mcp/builtin'
| '/settings/mcp/marketplaces'
| '/settings/mcp/mcp-install'
| '/settings/mcp/npx-search'
| '/settings/mcp/servers'
| '/settings/mcp/'
| '/settings/mcp/settings/$serverId'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: '/' | '/settings' to:
id: '__root__' | '/' | '/settings' | '/'
| '/chat'
| '/code'
| '/files'
| '/knowledge'
| '/notes'
| '/store'
| '/translate'
| '/apps/$appId'
| '/paintings/$'
| '/settings/about'
| '/settings/api-server'
| '/settings/data'
| '/settings/display'
| '/settings/docprocess'
| '/settings/general'
| '/settings/memory'
| '/settings/model'
| '/settings/notes'
| '/settings/provider'
| '/settings/quickAssistant'
| '/settings/quickphrase'
| '/settings/selectionAssistant'
| '/settings/shortcut'
| '/settings/websearch'
| '/apps'
| '/paintings'
| '/settings'
| '/settings/mcp/$'
| '/settings/mcp/builtin'
| '/settings/mcp/marketplaces'
| '/settings/mcp/mcp-install'
| '/settings/mcp/npx-search'
| '/settings/mcp/servers'
| '/settings/mcp'
| '/settings/mcp/settings/$serverId'
id:
| '__root__'
| '/'
| '/chat'
| '/code'
| '/files'
| '/knowledge'
| '/notes'
| '/settings'
| '/store'
| '/translate'
| '/apps/$appId'
| '/paintings/$'
| '/settings/about'
| '/settings/api-server'
| '/settings/data'
| '/settings/display'
| '/settings/docprocess'
| '/settings/general'
| '/settings/mcp'
| '/settings/memory'
| '/settings/model'
| '/settings/notes'
| '/settings/provider'
| '/settings/quickAssistant'
| '/settings/quickphrase'
| '/settings/selectionAssistant'
| '/settings/shortcut'
| '/settings/websearch'
| '/apps/'
| '/paintings/'
| '/settings/'
| '/settings/mcp/$'
| '/settings/mcp/builtin'
| '/settings/mcp/marketplaces'
| '/settings/mcp/mcp-install'
| '/settings/mcp/npx-search'
| '/settings/mcp/servers'
| '/settings/mcp/'
| '/settings/mcp/settings/$serverId'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
export interface RootRouteChildren { export interface RootRouteChildren {
IndexRoute: typeof IndexRoute IndexRoute: typeof IndexRoute
SettingsRoute: typeof SettingsRoute ChatRoute: typeof ChatRoute
CodeRoute: typeof CodeRoute
FilesRoute: typeof FilesRoute
KnowledgeRoute: typeof KnowledgeRoute
NotesRoute: typeof NotesRoute
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' { declare module '@tanstack/react-router' {
interface FileRoutesByPath { 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': { '/settings': {
id: '/settings' id: '/settings'
path: '/settings' path: '/settings'
@ -58,6 +520,41 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof SettingsRouteImport preLoaderRoute: typeof SettingsRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/notes': {
id: '/notes'
path: '/notes'
fullPath: '/notes'
preLoaderRoute: typeof NotesRouteImport
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
parentRoute: typeof rootRouteImport
}
'/': { '/': {
id: '/' id: '/'
path: '/' path: '/'
@ -65,12 +562,296 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexRouteImport preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/settings/': {
id: '/settings/'
path: '/'
fullPath: '/settings/'
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'
fullPath: '/settings/websearch'
preLoaderRoute: typeof SettingsWebsearchRouteImport
parentRoute: typeof SettingsRoute
}
'/settings/shortcut': {
id: '/settings/shortcut'
path: '/shortcut'
fullPath: '/settings/shortcut'
preLoaderRoute: typeof SettingsShortcutRouteImport
parentRoute: typeof SettingsRoute
}
'/settings/selectionAssistant': {
id: '/settings/selectionAssistant'
path: '/selectionAssistant'
fullPath: '/settings/selectionAssistant'
preLoaderRoute: typeof SettingsSelectionAssistantRouteImport
parentRoute: typeof SettingsRoute
}
'/settings/quickphrase': {
id: '/settings/quickphrase'
path: '/quickphrase'
fullPath: '/settings/quickphrase'
preLoaderRoute: typeof SettingsQuickphraseRouteImport
parentRoute: typeof SettingsRoute
}
'/settings/quickAssistant': {
id: '/settings/quickAssistant'
path: '/quickAssistant'
fullPath: '/settings/quickAssistant'
preLoaderRoute: typeof SettingsQuickAssistantRouteImport
parentRoute: typeof SettingsRoute
}
'/settings/provider': {
id: '/settings/provider'
path: '/provider'
fullPath: '/settings/provider'
preLoaderRoute: typeof SettingsProviderRouteImport
parentRoute: typeof SettingsRoute
}
'/settings/notes': {
id: '/settings/notes'
path: '/notes'
fullPath: '/settings/notes'
preLoaderRoute: typeof SettingsNotesRouteImport
parentRoute: typeof SettingsRoute
}
'/settings/model': {
id: '/settings/model'
path: '/model'
fullPath: '/settings/model'
preLoaderRoute: typeof SettingsModelRouteImport
parentRoute: typeof SettingsRoute
}
'/settings/memory': {
id: '/settings/memory'
path: '/memory'
fullPath: '/settings/memory'
preLoaderRoute: typeof SettingsMemoryRouteImport
parentRoute: typeof SettingsRoute
}
'/settings/mcp': {
id: '/settings/mcp'
path: '/mcp'
fullPath: '/settings/mcp'
preLoaderRoute: typeof SettingsMcpRouteImport
parentRoute: typeof SettingsRoute
}
'/settings/general': {
id: '/settings/general'
path: '/general'
fullPath: '/settings/general'
preLoaderRoute: typeof SettingsGeneralRouteImport
parentRoute: typeof SettingsRoute
}
'/settings/docprocess': {
id: '/settings/docprocess'
path: '/docprocess'
fullPath: '/settings/docprocess'
preLoaderRoute: typeof SettingsDocprocessRouteImport
parentRoute: typeof SettingsRoute
}
'/settings/display': {
id: '/settings/display'
path: '/display'
fullPath: '/settings/display'
preLoaderRoute: typeof SettingsDisplayRouteImport
parentRoute: typeof SettingsRoute
}
'/settings/data': {
id: '/settings/data'
path: '/data'
fullPath: '/settings/data'
preLoaderRoute: typeof SettingsDataRouteImport
parentRoute: typeof SettingsRoute
}
'/settings/api-server': {
id: '/settings/api-server'
path: '/api-server'
fullPath: '/settings/api-server'
preLoaderRoute: typeof SettingsApiServerRouteImport
parentRoute: typeof SettingsRoute
}
'/settings/about': {
id: '/settings/about'
path: '/about'
fullPath: '/settings/about'
preLoaderRoute: typeof SettingsAboutRouteImport
parentRoute: typeof SettingsRoute
}
'/paintings/$': {
id: '/paintings/$'
path: '/paintings/$'
fullPath: '/paintings/$'
preLoaderRoute: typeof PaintingsSplatRouteImport
parentRoute: typeof rootRouteImport
}
'/apps/$appId': {
id: '/apps/$appId'
path: '/apps/$appId'
fullPath: '/apps/$appId'
preLoaderRoute: typeof AppsAppIdRouteImport
parentRoute: typeof rootRouteImport
}
'/settings/mcp/': {
id: '/settings/mcp/'
path: '/'
fullPath: '/settings/mcp/'
preLoaderRoute: typeof SettingsMcpIndexRouteImport
parentRoute: typeof SettingsMcpRoute
}
'/settings/mcp/servers': {
id: '/settings/mcp/servers'
path: '/servers'
fullPath: '/settings/mcp/servers'
preLoaderRoute: typeof SettingsMcpServersRouteImport
parentRoute: typeof SettingsMcpRoute
}
'/settings/mcp/npx-search': {
id: '/settings/mcp/npx-search'
path: '/npx-search'
fullPath: '/settings/mcp/npx-search'
preLoaderRoute: typeof SettingsMcpNpxSearchRouteImport
parentRoute: typeof SettingsMcpRoute
}
'/settings/mcp/mcp-install': {
id: '/settings/mcp/mcp-install'
path: '/mcp-install'
fullPath: '/settings/mcp/mcp-install'
preLoaderRoute: typeof SettingsMcpMcpInstallRouteImport
parentRoute: typeof SettingsMcpRoute
}
'/settings/mcp/marketplaces': {
id: '/settings/mcp/marketplaces'
path: '/marketplaces'
fullPath: '/settings/mcp/marketplaces'
preLoaderRoute: typeof SettingsMcpMarketplacesRouteImport
parentRoute: typeof SettingsMcpRoute
}
'/settings/mcp/builtin': {
id: '/settings/mcp/builtin'
path: '/builtin'
fullPath: '/settings/mcp/builtin'
preLoaderRoute: typeof SettingsMcpBuiltinRouteImport
parentRoute: typeof SettingsMcpRoute
}
'/settings/mcp/$': {
id: '/settings/mcp/$'
path: '/$'
fullPath: '/settings/mcp/$'
preLoaderRoute: typeof SettingsMcpSplatRouteImport
parentRoute: typeof SettingsMcpRoute
}
'/settings/mcp/settings/$serverId': {
id: '/settings/mcp/settings/$serverId'
path: '/settings/$serverId'
fullPath: '/settings/mcp/settings/$serverId'
preLoaderRoute: typeof SettingsMcpSettingsServerIdRouteImport
parentRoute: typeof SettingsMcpRoute
}
} }
} }
interface SettingsMcpRouteChildren {
SettingsMcpSplatRoute: typeof SettingsMcpSplatRoute
SettingsMcpBuiltinRoute: typeof SettingsMcpBuiltinRoute
SettingsMcpMarketplacesRoute: typeof SettingsMcpMarketplacesRoute
SettingsMcpMcpInstallRoute: typeof SettingsMcpMcpInstallRoute
SettingsMcpNpxSearchRoute: typeof SettingsMcpNpxSearchRoute
SettingsMcpServersRoute: typeof SettingsMcpServersRoute
SettingsMcpIndexRoute: typeof SettingsMcpIndexRoute
SettingsMcpSettingsServerIdRoute: typeof SettingsMcpSettingsServerIdRoute
}
const SettingsMcpRouteChildren: SettingsMcpRouteChildren = {
SettingsMcpSplatRoute: SettingsMcpSplatRoute,
SettingsMcpBuiltinRoute: SettingsMcpBuiltinRoute,
SettingsMcpMarketplacesRoute: SettingsMcpMarketplacesRoute,
SettingsMcpMcpInstallRoute: SettingsMcpMcpInstallRoute,
SettingsMcpNpxSearchRoute: SettingsMcpNpxSearchRoute,
SettingsMcpServersRoute: SettingsMcpServersRoute,
SettingsMcpIndexRoute: SettingsMcpIndexRoute,
SettingsMcpSettingsServerIdRoute: SettingsMcpSettingsServerIdRoute,
}
const SettingsMcpRouteWithChildren = SettingsMcpRoute._addFileChildren(
SettingsMcpRouteChildren,
)
interface SettingsRouteChildren {
SettingsAboutRoute: typeof SettingsAboutRoute
SettingsApiServerRoute: typeof SettingsApiServerRoute
SettingsDataRoute: typeof SettingsDataRoute
SettingsDisplayRoute: typeof SettingsDisplayRoute
SettingsDocprocessRoute: typeof SettingsDocprocessRoute
SettingsGeneralRoute: typeof SettingsGeneralRoute
SettingsMcpRoute: typeof SettingsMcpRouteWithChildren
SettingsMemoryRoute: typeof SettingsMemoryRoute
SettingsModelRoute: typeof SettingsModelRoute
SettingsNotesRoute: typeof SettingsNotesRoute
SettingsProviderRoute: typeof SettingsProviderRoute
SettingsQuickAssistantRoute: typeof SettingsQuickAssistantRoute
SettingsQuickphraseRoute: typeof SettingsQuickphraseRoute
SettingsSelectionAssistantRoute: typeof SettingsSelectionAssistantRoute
SettingsShortcutRoute: typeof SettingsShortcutRoute
SettingsWebsearchRoute: typeof SettingsWebsearchRoute
SettingsIndexRoute: typeof SettingsIndexRoute
}
const SettingsRouteChildren: SettingsRouteChildren = {
SettingsAboutRoute: SettingsAboutRoute,
SettingsApiServerRoute: SettingsApiServerRoute,
SettingsDataRoute: SettingsDataRoute,
SettingsDisplayRoute: SettingsDisplayRoute,
SettingsDocprocessRoute: SettingsDocprocessRoute,
SettingsGeneralRoute: SettingsGeneralRoute,
SettingsMcpRoute: SettingsMcpRouteWithChildren,
SettingsMemoryRoute: SettingsMemoryRoute,
SettingsModelRoute: SettingsModelRoute,
SettingsNotesRoute: SettingsNotesRoute,
SettingsProviderRoute: SettingsProviderRoute,
SettingsQuickAssistantRoute: SettingsQuickAssistantRoute,
SettingsQuickphraseRoute: SettingsQuickphraseRoute,
SettingsSelectionAssistantRoute: SettingsSelectionAssistantRoute,
SettingsShortcutRoute: SettingsShortcutRoute,
SettingsWebsearchRoute: SettingsWebsearchRoute,
SettingsIndexRoute: SettingsIndexRoute,
}
const SettingsRouteWithChildren = SettingsRoute._addFileChildren(
SettingsRouteChildren,
)
const rootRouteChildren: RootRouteChildren = { const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute, IndexRoute: IndexRoute,
SettingsRoute: SettingsRoute, ChatRoute: ChatRoute,
CodeRoute: CodeRoute,
FilesRoute: FilesRoute,
KnowledgeRoute: KnowledgeRoute,
NotesRoute: NotesRoute,
SettingsRoute: SettingsRouteWithChildren,
StoreRoute: StoreRoute,
TranslateRoute: TranslateRoute,
AppsAppIdRoute: AppsAppIdRoute,
PaintingsSplatRoute: PaintingsSplatRoute,
AppsIndexRoute: AppsIndexRoute,
PaintingsIndexRoute: PaintingsIndexRoute,
} }
export const routeTree = rootRouteImport export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren) ._addFileChildren(rootRouteChildren)

View File

@ -23,9 +23,9 @@ import {
} from '@renderer/utils/messageUtils/create' } from '@renderer/utils/messageUtils/create'
import { filterContextMessages } from '@renderer/utils/messageUtils/filters' import { filterContextMessages } from '@renderer/utils/messageUtils/filters'
import { getMainTextContent } from '@renderer/utils/messageUtils/find' import { getMainTextContent } from '@renderer/utils/messageUtils/find'
import type { UseNavigateResult } from '@tanstack/react-router'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { t } from 'i18next' import { t } from 'i18next'
import type { NavigateFunction } from 'react-router'
import { getAssistantById, getAssistantProvider, getDefaultModel } from './AssistantService' import { getAssistantById, getAssistantProvider, getDefaultModel } from './AssistantService'
import { EVENT_NAMES, EventEmitter } from './EventService' import { EVENT_NAMES, EventEmitter } from './EventService'
@ -68,14 +68,14 @@ export function deleteMessageFiles(message: Message) {
}) })
} }
export async function locateToMessage(navigate: NavigateFunction, message: Message) { export async function locateToMessage(navigate: UseNavigateResult<string>, message: Message) {
await modelGenerating() await modelGenerating()
SearchPopup.hide() SearchPopup.hide()
const assistant = getAssistantById(message.assistantId) const assistant = getAssistantById(message.assistantId)
const topic = await getTopicById(message.topicId) const topic = await getTopicById(message.topicId)
navigate('/', { state: { assistant, topic } }) navigate({ to: '/chat', search: { assistantId: assistant?.id, topicId: topic?.id } })
setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR), 0) setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR), 0)
setTimeout(() => EventEmitter.emit(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id), 300) setTimeout(() => EventEmitter.emit(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id), 300)

View File

@ -1,14 +1,15 @@
import type { NavigateFunction } from 'react-router-dom' import type { UseNavigateResult } from '@tanstack/react-router'
// Tab 导航服务 - 用于在非 React 组件中进行路由导航
interface INavigationService { interface INavigationService {
navigate: NavigateFunction | null navigate: UseNavigateResult<string> | null
setNavigate: (navigateFunc: NavigateFunction) => void setNavigate: (navigateFunc: UseNavigateResult<string>) => void
} }
const NavigationService: INavigationService = { const NavigationService: INavigationService = {
navigate: null, navigate: null,
setNavigate: (navigateFunc: NavigateFunction): void => { setNavigate: (navigateFunc: UseNavigateResult<string>): void => {
NavigationService.navigate = navigateFunc NavigationService.navigate = navigateFunc
window.navigate = NavigationService.navigate window.navigate = NavigationService.navigate
} }

View File

@ -55,12 +55,12 @@ class TabsService {
// 使用 NavigationService 导航到新的标签页 // 使用 NavigationService 导航到新的标签页
if (NavigationService.navigate) { if (NavigationService.navigate) {
NavigationService.navigate(lastTab.path) NavigationService.navigate({ to: lastTab.path })
} else { } else {
logger.warn('Navigation service not ready, will navigate on next render') logger.warn('Navigation service not ready, will navigate on next render')
setTimeout(() => { setTimeout(() => {
if (NavigationService.navigate) { if (NavigationService.navigate) {
NavigationService.navigate(lastTab.path) NavigationService.navigate({ to: lastTab.path })
} }
}, 100) }, 100)
} }
@ -133,7 +133,7 @@ class TabsService {
// 导航到对应页面 // 导航到对应页面
if (NavigationService.navigate) { if (NavigationService.navigate) {
NavigationService.navigate(tab.path) NavigationService.navigate({ to: tab.path })
} }
return true return true