jan/web-app/src/routes/__root.tsx
Dinh Long Nguyen 0771b998a5
Fix: Web Services Improvement
Fix: Web Services Improvement
2025-09-15 09:08:30 +07:00

224 lines
7.6 KiB
TypeScript

import { createRootRoute, Outlet, useRouterState } from '@tanstack/react-router'
// import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
import LeftPanel from '@/containers/LeftPanel'
import DialogAppUpdater from '@/containers/dialogs/AppUpdater'
import BackendUpdater from '@/containers/dialogs/BackendUpdater'
import { Fragment } from 'react/jsx-runtime'
import { AppearanceProvider } from '@/providers/AppearanceProvider'
import { ThemeProvider } from '@/providers/ThemeProvider'
import { KeyboardShortcutsProvider } from '@/providers/KeyboardShortcuts'
import { DataProvider } from '@/providers/DataProvider'
import { route } from '@/constants/routes'
import { ExtensionProvider } from '@/providers/ExtensionProvider'
import { ToasterProvider } from '@/providers/ToasterProvider'
import { useAnalytic } from '@/hooks/useAnalytic'
import { PromptAnalytic } from '@/containers/analytics/PromptAnalytic'
import { AnalyticProvider } from '@/providers/AnalyticProvider'
import { useLeftPanel } from '@/hooks/useLeftPanel'
import { cn } from '@/lib/utils'
import ToolApproval from '@/containers/dialogs/ToolApproval'
import { TranslationProvider } from '@/i18n/TranslationContext'
import OutOfContextPromiseModal from '@/containers/dialogs/OutOfContextDialog'
import LoadModelErrorDialog from '@/containers/dialogs/LoadModelErrorDialog'
import { useSmallScreen } from '@/hooks/useMediaQuery'
import {
ResizablePanelGroup,
ResizablePanel,
ResizableHandle,
} from '@/components/ui/resizable'
import { useCallback, useEffect } from 'react'
import GlobalError from '@/containers/GlobalError'
import { GlobalEventHandler } from '@/providers/GlobalEventHandler'
import ErrorDialog from '@/containers/dialogs/ErrorDialog'
import { ServiceHubProvider } from '@/providers/ServiceHubProvider'
import { PlatformFeatures } from '@/lib/platform/const'
import { PlatformFeature } from '@/lib/platform/types'
export const Route = createRootRoute({
component: RootLayout,
errorComponent: ({ error }) => <GlobalError error={error} />,
})
const AppLayout = () => {
const { productAnalyticPrompt } = useAnalytic()
const {
open: isLeftPanelOpen,
setLeftPanel,
size: leftPanelSize,
setLeftPanelSize,
} = useLeftPanel()
const isSmallScreen = useSmallScreen()
// Minimum width threshold for auto-close (10% of screen width)
const MIN_PANEL_WIDTH_THRESHOLD = 14
// Handle panel size changes
const handlePanelLayout = useCallback(
(sizes: number[]) => {
if (sizes.length > 0) {
const newSize = sizes[0]
// Close panel if resized below minimum threshold
if (newSize < MIN_PANEL_WIDTH_THRESHOLD) {
setLeftPanel(false)
} else {
setLeftPanelSize(newSize)
}
}
},
[setLeftPanelSize, setLeftPanel]
)
// Prevent default drag and drop behavior globally
useEffect(() => {
const preventDefaults = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
}
const handleGlobalDrop = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
// Only prevent if the target is not within a chat input or other valid drop zone
const target = e.target as Element
const isValidDropZone =
target?.closest('[data-drop-zone="true"]') ||
target?.closest('.chat-input-drop-zone') ||
target?.closest('[data-tauri-drag-region]')
if (!isValidDropZone) {
// Prevent the file from opening in the window
return false
}
}
// Add event listeners to prevent default drag/drop behavior
window.addEventListener('dragenter', preventDefaults)
window.addEventListener('dragover', preventDefaults)
window.addEventListener('drop', handleGlobalDrop)
return () => {
window.removeEventListener('dragenter', preventDefaults)
window.removeEventListener('dragover', preventDefaults)
window.removeEventListener('drop', handleGlobalDrop)
}
}, [])
return (
<Fragment>
<AnalyticProvider />
<KeyboardShortcutsProvider />
<main className="relative h-svh text-sm antialiased select-none bg-app">
{/* Fake absolute panel top to enable window drag */}
<div className="absolute w-full h-10 z-10" data-tauri-drag-region />
<DialogAppUpdater />
{PlatformFeatures[PlatformFeature.LOCAL_INFERENCE] && <BackendUpdater />}
{/* Use ResizablePanelGroup only on larger screens */}
{!isSmallScreen && isLeftPanelOpen ? (
<ResizablePanelGroup
direction="horizontal"
className="h-full"
onLayout={handlePanelLayout}
>
{/* Left Panel */}
<ResizablePanel
defaultSize={leftPanelSize}
minSize={MIN_PANEL_WIDTH_THRESHOLD}
maxSize={40}
collapsible
>
<div className="h-full p-1">
<LeftPanel />
</div>
</ResizablePanel>
{/* Resize Handle */}
<ResizableHandle withHandle />
{/* Main Content Panel */}
<ResizablePanel defaultSize={100 - leftPanelSize} minSize={60}>
<div className="h-full p-1 pl-0">
<div className="bg-main-view text-main-view-fg border border-main-view-fg/5 w-full h-full rounded-lg overflow-hidden">
<Outlet />
</div>
</div>
</ResizablePanel>
</ResizablePanelGroup>
) : (
<div className="flex h-full">
{/* left content panel - only show if not logs route */}
<LeftPanel />
{/* Main content panel */}
<div
className={cn(
'h-full flex w-full p-1 ',
isLeftPanelOpen && 'w-full md:w-[calc(100%-198px)]'
)}
>
<div className="bg-main-view text-main-view-fg border border-main-view-fg/5 w-full rounded-lg overflow-hidden">
<Outlet />
</div>
</div>
</div>
)}
</main>
{PlatformFeatures[PlatformFeature.ANALYTICS] && productAnalyticPrompt && (
<PromptAnalytic />
)}
</Fragment>
)
}
const LogsLayout = () => {
return (
<Fragment>
<main className="relative h-svh text-sm antialiased select-text bg-app">
<div className="flex h-full">
{/* Main content panel */}
<div className="h-full flex w-full">
<div className="bg-main-view text-main-view-fg border border-main-view-fg/5 w-full overflow-hidden">
<Outlet />
</div>
</div>
</div>
</main>
</Fragment>
)
}
function RootLayout() {
const router = useRouterState()
const isLocalAPIServerLogsRoute =
router.location.pathname === route.localApiServerlogs ||
router.location.pathname === route.systemMonitor ||
router.location.pathname === route.appLogs
return (
<Fragment>
<ServiceHubProvider>
<ThemeProvider />
<AppearanceProvider />
<ToasterProvider />
<TranslationProvider>
<ExtensionProvider>
<DataProvider />
<GlobalEventHandler />
{isLocalAPIServerLogsRoute ? <LogsLayout /> : <AppLayout />}
</ExtensionProvider>
{/* {isLocalAPIServerLogsRoute ? <LogsLayout /> : <AppLayout />} */}
{/* <TanStackRouterDevtools position="bottom-right" /> */}
<ToolApproval />
<LoadModelErrorDialog />
<ErrorDialog />
<OutOfContextPromiseModal />
</TranslationProvider>
</ServiceHubProvider>
</Fragment>
)
}