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 && (
-
);
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 (