diff --git a/packages/ui/src/components/layout/Header.tsx b/packages/ui/src/components/layout/Header.tsx index 32e184ea..18f2f20d 100644 --- a/packages/ui/src/components/layout/Header.tsx +++ b/packages/ui/src/components/layout/Header.tsx @@ -4,8 +4,8 @@ import { TooltipContent, TooltipTrigger, } from '@/components/ui/tooltip'; -import { Button } from '@/components/ui/button'; -import { RiArrowDownSLine, RiArrowUpSLine, RiChat4Line, RiCodeLine, RiGitBranchLine, RiLayoutLeftLine, RiPlayListAddLine, RiTerminalBoxLine, type RemixiconComponentType } from '@remixicon/react'; + +import { RiChat4Line, RiCodeLine, RiGitBranchLine, RiLayoutLeftLine, RiPlayListAddLine, RiTerminalBoxLine, type RemixiconComponentType } from '@remixicon/react'; import { useUIStore, type MainTab } from '@/stores/useUIStore'; import { useConfigStore } from '@/stores/useConfigStore'; import { useSessionStore } from '@/stores/useSessionStore'; @@ -121,7 +121,6 @@ export const Header: React.FC = () => { const contextLimit = (limit && typeof limit.context === 'number' ? limit.context : 0); const outputLimit = (limit && typeof limit.output === 'number' ? limit.output : 0); const contextUsage = getContextUsage(contextLimit, outputLimit); - const [isMobileDetailsOpen, setIsMobileDetailsOpen] = React.useState(false); const isSessionSwitcherOpen = useUIStore((state) => state.isSessionSwitcherOpen); const handleOpenSessionSwitcher = React.useCallback(() => { @@ -182,17 +181,7 @@ export const Header: React.FC = () => { useEffect(() => { updateHeaderHeight(); - }, [updateHeaderHeight, isMobile, isMobileDetailsOpen]); - - const formatTokenValue = React.useCallback((tokens: number) => { - if (tokens >= 1_000_000) { - return `${(tokens / 1_000_000).toFixed(1)}M`; - } - if (tokens >= 1_000) { - return `${(tokens / 1_000).toFixed(1)}K`; - } - return tokens.toFixed(1).replace(/\.0$/, ''); - }, []); + }, [updateHeaderHeight, isMobile]); const handleDragStart = React.useCallback(async (e: React.MouseEvent) => { @@ -368,101 +357,62 @@ export const Header: React.FC = () => { ); const renderMobile = () => ( -
-
-
- - {contextUsage && contextUsage.totalTokens > 0 && activeMainTab === 'chat' && ( - - )} -
- -
- {} -
- {tabs.map((tab) => { - const isActive = activeMainTab === tab.id; - const Icon = tab.icon; - return ( - - - - - -

{tab.label}

-
-
- ); - })} -
- -
+
+
+ + {contextUsage && contextUsage.totalTokens > 0 && activeMainTab === 'chat' && ( + + )}
- {isMobileDetailsOpen && ( -
-
- {contextUsage && contextUsage.totalTokens > 0 && ( -
- Context usage -
-

- Used tokens: {formatTokenValue(contextUsage.totalTokens)} -

-

- Context limit: {formatTokenValue(contextUsage.contextLimit)} -

-

- Output limit: {formatTokenValue(contextUsage.outputLimit ?? 0)} -

-
-
- )} -
+
+
+ {tabs.map((tab) => { + const isActive = activeMainTab === tab.id; + const Icon = tab.icon; + return ( + + + + + +

{tab.label}

+
+
+ ); + })}
- )} +
); diff --git a/packages/ui/src/components/ui/ContextUsageDisplay.tsx b/packages/ui/src/components/ui/ContextUsageDisplay.tsx index ed216880..baf25bb2 100644 --- a/packages/ui/src/components/ui/ContextUsageDisplay.tsx +++ b/packages/ui/src/components/ui/ContextUsageDisplay.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { RiDonutChartLine } from '@remixicon/react'; import { cn } from '@/lib/utils'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; +import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel'; interface ContextUsageDisplayProps { totalTokens: number; @@ -9,6 +10,7 @@ interface ContextUsageDisplayProps { contextLimit: number; outputLimit?: number; size?: 'default' | 'compact'; + isMobile?: boolean; } export const ContextUsageDisplay: React.FC = ({ @@ -17,7 +19,11 @@ export const ContextUsageDisplay: React.FC = ({ contextLimit, outputLimit, size = 'default', + isMobile = false, }) => { + const [mobileTooltipOpen, setMobileTooltipOpen] = React.useState(false); + const longPressTimerRef = React.useRef(undefined); + const formatTokens = (tokens: number) => { if (tokens >= 1_000_000) { return `${(tokens / 1_000_000).toFixed(1)}M`; @@ -28,9 +34,9 @@ export const ContextUsageDisplay: React.FC = ({ return tokens.toFixed(1).replace(/\.0$/, ''); }; - const getPercentageColor = (percentage: number) => { - if (percentage >= 90) return 'text-status-error'; - if (percentage >= 75) return 'text-status-warning'; + const getPercentageColor = (pct: number) => { + if (pct >= 90) return 'text-status-error'; + if (pct >= 75) return 'text-status-warning'; return 'text-status-success'; }; @@ -41,6 +47,29 @@ export const ContextUsageDisplay: React.FC = ({ `Output limit: ${formatTokens(safeOutputLimit)}`, ]; + const handleLongPressStart = React.useCallback(() => { + if (longPressTimerRef.current) { + clearTimeout(longPressTimerRef.current); + } + longPressTimerRef.current = setTimeout(() => { + setMobileTooltipOpen(true); + }, 500); + }, []); + + const handleLongPressEnd = React.useCallback(() => { + if (longPressTimerRef.current) { + clearTimeout(longPressTimerRef.current); + } + }, []); + + React.useEffect(() => { + return () => { + if (longPressTimerRef.current) { + clearTimeout(longPressTimerRef.current); + } + }; + }, []); + const contextElement = (
= ({ size === 'compact' ? 'typography-micro' : 'typography-meta', )} aria-label="Context usage" + onTouchStart={isMobile ? handleLongPressStart : undefined} + onTouchEnd={isMobile ? handleLongPressEnd : undefined} > @@ -56,6 +87,42 @@ export const ContextUsageDisplay: React.FC = ({
); + if (isMobile) { + return ( + <> + {contextElement} + setMobileTooltipOpen(false)} + title="Context Usage" + > +
+
+
+ Used tokens + {formatTokens(totalTokens)} +
+
+ Context limit + {formatTokens(contextLimit)} +
+
+ Output limit + {formatTokens(safeOutputLimit)} +
+
+ Usage + + {Math.min(percentage, 999).toFixed(1)}% + +
+
+
+
+ + ); + } + return ( {contextElement} diff --git a/packages/ui/src/components/views/DiffView.tsx b/packages/ui/src/components/views/DiffView.tsx index 172a2a17..a897e5ec 100644 --- a/packages/ui/src/components/views/DiffView.tsx +++ b/packages/ui/src/components/views/DiffView.tsx @@ -64,14 +64,14 @@ const FileSelector = React.memo(({ onSelectFile, isMobile, }) => { - if (changedFiles.length === 0) return null; - const getLabel = React.useCallback((path: string) => { if (!isMobile) return path; const lastSlash = path.lastIndexOf('/'); return lastSlash >= 0 ? path.slice(lastSlash + 1) : path; }, [isMobile]); + if (changedFiles.length === 0) return null; + return (