diff --git a/packages/ui/src/components/chat/MobileSessionStatusBar.tsx b/packages/ui/src/components/chat/MobileSessionStatusBar.tsx index 4ec54224..630cfd86 100644 --- a/packages/ui/src/components/chat/MobileSessionStatusBar.tsx +++ b/packages/ui/src/components/chat/MobileSessionStatusBar.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { useSessionUIStore } from '@/sync/session-ui-store'; -import { useSelectionStore } from '@/sync/selection-store'; import { useAllSessionStatuses, useAllLiveSessions } from '@/sync/sync-context'; import { useGlobalSessionsStore, ensureGlobalSessionsLoaded, refreshGlobalSessions } from '@/stores/useGlobalSessionsStore'; import { useConfigStore } from '@/stores/useConfigStore'; @@ -9,7 +8,6 @@ import { useProjectsStore } from '@/stores/useProjectsStore'; import type { Session } from '@opencode-ai/sdk/v2'; import type { ProjectEntry } from '@/lib/api/types'; import { cn, formatDirectoryName } from '@/lib/utils'; -import { getAgentColor } from '@/lib/agentColors'; import type { SessionContextUsage } from '@/stores/types/sessionTypes'; import { PROJECT_ICON_MAP, PROJECT_COLOR_MAP, ProjectIconImage } from '@/lib/projectMeta'; import { useDirectoryStore } from '@/stores/useDirectoryStore'; @@ -172,17 +170,7 @@ function useSessionGrouping( return { sessions: processedSessions, totalRunning, totalUnread, totalCount: processedSessions.length }; } -function useSessionHelpers(agents: Array<{ name: string }>) { - const getSessionAgentName = React.useCallback((session: Session): string => { - const agent = (session as { agent?: string }).agent; - if (agent) return agent; - - const sessionAgentSelection = useSelectionStore.getState().getSessionAgentSelection(session.id); - if (sessionAgentSelection) return sessionAgentSelection; - - return agents[0]?.name ?? 'agent'; - }, [agents]); - +function useSessionHelpers() { const getSessionTitle = React.useCallback((session: Session): string => { const title = session.title; if (title && title.trim()) return title; @@ -194,7 +182,7 @@ function useSessionHelpers(agents: Array<{ name: string }>) { return (unseenCounts[sessionId] ?? 0) > 0; }, [unseenCounts]); - return { getSessionAgentName, getSessionTitle, needsAttention }; + return { getSessionTitle, needsAttention }; } // Per-project status indicators (running / unread) for the filter chips. @@ -300,14 +288,29 @@ function UnreadIndicator({ count }: { count: number }) { function TokenUsageIndicator({ contextUsage }: { contextUsage: SessionContextUsage | null }) { if (!contextUsage || contextUsage.totalTokens === 0) return null; - const percentage = Math.min(contextUsage.percentage, 999); + // Recompute with a fraction: contextUsage.percentage is rounded to an integer + // in the store, which would always render as "X.0%". + const percentage = contextUsage.contextLimit > 0 + ? Math.min((contextUsage.totalTokens / contextUsage.contextLimit) * 100, 999) + : 0; const colorClass = percentage >= 90 ? 'text-[var(--status-error)]' : percentage >= 75 ? 'text-[var(--status-warning)]' : 'text-[var(--status-success)]'; + const formatTokens = (value: number): string => { + if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`; + if (value >= 1_000) return `${(value / 1_000).toFixed(1)}K`; + return String(value); + }; + + const tokens = contextUsage.contextLimit > 0 + ? `${formatTokens(contextUsage.totalTokens)}/${formatTokens(contextUsage.contextLimit)}` + : formatTokens(contextUsage.totalTokens); + return ( - - {percentage.toFixed(1)}% + + {percentage.toFixed(1)}% + {tokens} ); } @@ -316,20 +319,16 @@ function TokenUsageIndicator({ contextUsage }: { contextUsage: SessionContextUsa function SessionItem({ session, isCurrent, - getSessionAgentName, getSessionTitle, onClick, needsAttention, }: { session: SessionWithStatus; isCurrent: boolean; - getSessionAgentName: (s: Session) => string; getSessionTitle: (s: Session) => string; onClick: () => void; needsAttention: (sessionId: string) => boolean; }) { - const agentName = getSessionAgentName(session); - const agentColor = getAgentColor(agentName); const attention = needsAttention(session.id); return ( @@ -346,11 +345,6 @@ function SessionItem({ - - = ({ const currentSessionId = useSessionUIStore((state) => state.currentSessionId); const sessionStatus = useAllSessionStatuses(); const setCurrentSession = useSessionUIStore((state) => state.setCurrentSession); + const openNewSessionDraft = useSessionUIStore((state) => state.openNewSessionDraft); const getContextUsage = useSessionUIStore((state) => state.getContextUsage); - const agents = useConfigStore((state) => state.agents); const getCurrentModel = useConfigStore((state) => state.getCurrentModel); const isMobile = useUIStore((state) => state.isMobile); const showMobileSessionStatusBar = useUIStore((state) => state.showMobileSessionStatusBar); @@ -512,7 +506,7 @@ export const MobileSessionStatusBar: React.FC = ({ const homeDirectory = useDirectoryStore((state) => state.homeDirectory); const { sessions: sortedSessions, totalRunning, totalUnread } = useSessionGrouping(sessions, sessionStatus); - const { getSessionAgentName, getSessionTitle, needsAttention } = useSessionHelpers(agents); + const { getSessionTitle, needsAttention } = useSessionHelpers(); const getProjectStatus = useProjectStatus(sessions, sessionStatus, currentSessionId); const resolveProjectRoots = useProjectRootsResolver(); @@ -572,7 +566,28 @@ export const MobileSessionStatusBar: React.FC = ({ setOpen(false); }; - const renderHeader = React.useCallback((closeButton: React.ReactNode) => ( + // "+" — start a new session draft. Target the project selected in the filter; + // for "All", use the most recently active session's directory, falling back to + // the store's own default target when there are no sessions. + const handleNewChat = React.useCallback(() => { + setOpen(false); + if (filterProjectId) { + const project = projects.find((p) => p.id === filterProjectId); + if (project) { + openNewSessionDraft({ selectedProjectId: project.id, directoryOverride: project.path }); + return; + } + } + const mostRecent = [...sessions].sort((a, b) => { + const aTime = (a as { time?: { updated?: number } }).time?.updated ?? 0; + const bTime = (b as { time?: { updated?: number } }).time?.updated ?? 0; + return bTime - aTime; + })[0]; + const directory = mostRecent ? sessionDirectory(mostRecent) : ''; + openNewSessionDraft(directory ? { directoryOverride: directory } : undefined); + }, [filterProjectId, projects, sessions, openNewSessionDraft, setOpen]); + + const renderHeader = React.useCallback(() => ( @@ -586,7 +601,23 @@ export const MobileSessionStatusBar: React.FC = ({ - {closeButton} + + + + setOpen(false)} + className="flex size-8 items-center justify-center rounded-full text-[var(--surface-mutedForeground)] transition-colors hover:bg-[var(--interactive-hover)] hover:text-[var(--surface-foreground)]" + style={{ touchAction: 'manipulation' }} + > + + @@ -620,7 +651,7 @@ export const MobileSessionStatusBar: React.FC = ({ )} - ), [t, totalRunning, totalUnread, contextUsage, projects, filterProjectId, setFilterProjectId, formatProjectLabel, currentTheme, getProjectStatus]); + ), [t, totalRunning, totalUnread, contextUsage, projects, filterProjectId, setFilterProjectId, formatProjectLabel, currentTheme, getProjectStatus, handleNewChat, setOpen]); if (!isMobile || !showMobileSessionStatusBar) { return null; @@ -646,7 +677,6 @@ export const MobileSessionStatusBar: React.FC = ({ key={session.id} session={session} isCurrent={session.id === currentSessionId} - getSessionAgentName={getSessionAgentName} getSessionTitle={getSessionTitle} onClick={() => handleSessionClick(session)} needsAttention={needsAttention}