|  | 
|  | 1 | +import type { Session } from "core/index.js"; | 
| 1 | 2 | import { format, isThisWeek, isThisYear, isToday, isYesterday } from "date-fns"; | 
| 2 | 3 | import { Box, Text, useInput } from "ink"; | 
| 3 |  | -import React, { useMemo, useState } from "react"; | 
|  | 4 | +import React, { useEffect, useMemo, useState } from "react"; | 
| 4 | 5 | 
 | 
| 5 |  | -import { ExtendedSessionMetadata } from "../session.js"; | 
|  | 6 | +import { ExtendedSessionMetadata, loadSessionById } from "../session.js"; | 
| 6 | 7 | 
 | 
| 7 | 8 | import { useTerminalSize } from "./hooks/useTerminalSize.js"; | 
|  | 9 | +import { SessionPreview } from "./SessionPreview.js"; | 
| 8 | 10 | import { defaultBoxStyles } from "./styles.js"; | 
| 9 | 11 | 
 | 
| 10 | 12 | interface SessionSelectorProps { | 
| @@ -40,7 +42,19 @@ export function SessionSelector({ | 
| 40 | 42 |   onExit, | 
| 41 | 43 | }: SessionSelectorProps) { | 
| 42 | 44 |   const [selectedIndex, setSelectedIndex] = useState(0); | 
| 43 |  | -  const { rows: terminalHeight } = useTerminalSize(); | 
|  | 45 | +  const [previewSession, setPreviewSession] = useState<Session | null>(null); | 
|  | 46 | +  const { rows: terminalHeight, columns: terminalWidth } = useTerminalSize(); | 
|  | 47 | + | 
|  | 48 | +  // Load the selected session for preview | 
|  | 49 | +  useEffect(() => { | 
|  | 50 | +    const selectedSession = sessions[selectedIndex]; | 
|  | 51 | +    if (selectedSession && !selectedSession.isRemote) { | 
|  | 52 | +      const session = loadSessionById(selectedSession.sessionId); | 
|  | 53 | +      setPreviewSession(session); | 
|  | 54 | +    } else { | 
|  | 55 | +      setPreviewSession(null); | 
|  | 56 | +    } | 
|  | 57 | +  }, [selectedIndex, sessions]); | 
| 44 | 58 | 
 | 
| 45 | 59 |   // Calculate how many sessions we can display based on terminal height and scrolling | 
| 46 | 60 |   const { displaySessions, scrollOffset } = useMemo(() => { | 
| @@ -104,54 +118,90 @@ export function SessionSelector({ | 
| 104 | 118 |   const hasMoreAbove = scrollOffset > 0; | 
| 105 | 119 |   const hasMoreBelow = scrollOffset + displaySessions.length < sessions.length; | 
| 106 | 120 | 
 | 
|  | 121 | +  // Determine if we should show preview (only if terminal is wide enough) | 
|  | 122 | +  const showPreview = terminalWidth > 100; | 
|  | 123 | +  const listWidth = showPreview | 
|  | 124 | +    ? Math.floor(terminalWidth * 0.3) | 
|  | 125 | +    : terminalWidth; | 
|  | 126 | + | 
| 107 | 127 |   return ( | 
| 108 |  | -    <Box {...defaultBoxStyles("blue")}> | 
| 109 |  | -      <Text color="blue" bold> | 
| 110 |  | -        Recent Sessions{" "} | 
| 111 |  | -        {sessions.length > displaySessions.length && | 
| 112 |  | -          `(${selectedIndex + 1}/${sessions.length})`} | 
| 113 |  | -      </Text> | 
| 114 |  | -      <Text color="gray">↑/↓ to navigate, Enter to select, Esc to exit</Text> | 
| 115 |  | -      <Text> </Text> | 
| 116 |  | - | 
| 117 |  | -      {hasMoreAbove && ( | 
| 118 |  | -        <Text color="gray" italic> | 
| 119 |  | -          ⬆ {scrollOffset} more sessions above... | 
|  | 128 | +    <Box flexDirection="row" width={terminalWidth}> | 
|  | 129 | +      {/* Left side: Session list */} | 
|  | 130 | +      <Box {...defaultBoxStyles("blue")} width={listWidth}> | 
|  | 131 | +        <Text color="blue" bold> | 
|  | 132 | +          Recent Sessions{" "} | 
|  | 133 | +          {sessions.length > displaySessions.length && | 
|  | 134 | +            `(${selectedIndex + 1}/${sessions.length})`} | 
| 120 | 135 |         </Text> | 
| 121 |  | -      )} | 
|  | 136 | +        <Text color="gray">↑/↓ to navigate, Enter to select, Esc to exit</Text> | 
|  | 137 | +        <Text> </Text> | 
|  | 138 | + | 
|  | 139 | +        {hasMoreAbove && ( | 
|  | 140 | +          <Text color="gray" italic> | 
|  | 141 | +            ⬆ {scrollOffset} more sessions above... | 
|  | 142 | +          </Text> | 
|  | 143 | +        )} | 
|  | 144 | + | 
|  | 145 | +        {displaySessions.map((session, index) => { | 
|  | 146 | +          const globalIndex = index + scrollOffset; | 
|  | 147 | +          const isSelected = globalIndex === selectedIndex; | 
|  | 148 | +          const indicator = isSelected ? "➤ " : "  "; | 
|  | 149 | +          const color = isSelected ? "blue" : "white"; | 
|  | 150 | + | 
|  | 151 | +          return ( | 
|  | 152 | +            <Box key={session.sessionId} flexDirection="column"> | 
|  | 153 | +              <Box paddingRight={3}> | 
|  | 154 | +                <Text bold={isSelected} color={color} wrap="truncate-end"> | 
|  | 155 | +                  {indicator} | 
|  | 156 | +                  {formatMessage(session.title)} | 
|  | 157 | +                </Text> | 
|  | 158 | +              </Box> | 
|  | 159 | +              <Box marginLeft={2}> | 
|  | 160 | +                <Text color="gray"> | 
|  | 161 | +                  {formatTimestamp(new Date(session.dateCreated))} | 
|  | 162 | +                  {session.isRemote ? " (remote)" : " (local)"} | 
|  | 163 | +                </Text> | 
|  | 164 | +              </Box> | 
|  | 165 | +              {index < displaySessions.length - 1 && ( | 
|  | 166 | +                <Text key={`spacer-${session.sessionId}`}> </Text> | 
|  | 167 | +              )} | 
|  | 168 | +            </Box> | 
|  | 169 | +          ); | 
|  | 170 | +        })} | 
|  | 171 | + | 
|  | 172 | +        {hasMoreBelow && ( | 
|  | 173 | +          <Text color="gray" italic> | 
|  | 174 | +            ⬇ {sessions.length - scrollOffset - displaySessions.length} more | 
|  | 175 | +            sessions below... | 
|  | 176 | +          </Text> | 
|  | 177 | +        )} | 
|  | 178 | +      </Box> | 
| 122 | 179 | 
 | 
| 123 |  | -      {displaySessions.map((session, index) => { | 
| 124 |  | -        const globalIndex = index + scrollOffset; | 
| 125 |  | -        const isSelected = globalIndex === selectedIndex; | 
| 126 |  | -        const indicator = isSelected ? "➤ " : "  "; | 
| 127 |  | -        const color = isSelected ? "blue" : "white"; | 
| 128 |  | - | 
| 129 |  | -        return ( | 
| 130 |  | -          <Box key={session.sessionId} flexDirection="column"> | 
| 131 |  | -            <Box paddingRight={3}> | 
| 132 |  | -              <Text bold={isSelected} color={color} wrap="truncate-end"> | 
| 133 |  | -                {indicator} | 
| 134 |  | -                {formatMessage(session.title)} | 
|  | 180 | +      {/* Right side: Preview panel */} | 
|  | 181 | +      {showPreview && ( | 
|  | 182 | +        <Box marginLeft={1} flexGrow={1} width="100%"> | 
|  | 183 | +          {previewSession ? ( | 
|  | 184 | +            <SessionPreview | 
|  | 185 | +              chatHistory={previewSession.history} | 
|  | 186 | +              sessionTitle={previewSession.title} | 
|  | 187 | +            /> | 
|  | 188 | +          ) : ( | 
|  | 189 | +            <Box | 
|  | 190 | +              {...defaultBoxStyles("blue")} | 
|  | 191 | +              flexDirection="column" | 
|  | 192 | +              width="100%" | 
|  | 193 | +            > | 
|  | 194 | +              <Text color="blue" bold> | 
|  | 195 | +                Preview | 
| 135 | 196 |               </Text> | 
| 136 |  | -            </Box> | 
| 137 |  | -            <Box marginLeft={2}> | 
| 138 | 197 |               <Text color="gray"> | 
| 139 |  | -                {formatTimestamp(new Date(session.dateCreated))} | 
| 140 |  | -                {session.isRemote ? " (remote)" : " (local)"} | 
|  | 198 | +                {sessions[selectedIndex]?.isRemote | 
|  | 199 | +                  ? "(remote session preview not available)" | 
|  | 200 | +                  : "(loading...)"} | 
| 141 | 201 |               </Text> | 
| 142 | 202 |             </Box> | 
| 143 |  | -            {index < displaySessions.length - 1 && ( | 
| 144 |  | -              <Text key={`spacer-${session.sessionId}`}> </Text> | 
| 145 |  | -            )} | 
| 146 |  | -          </Box> | 
| 147 |  | -        ); | 
| 148 |  | -      })} | 
| 149 |  | - | 
| 150 |  | -      {hasMoreBelow && ( | 
| 151 |  | -        <Text color="gray" italic> | 
| 152 |  | -          ⬇ {sessions.length - scrollOffset - displaySessions.length} more | 
| 153 |  | -          sessions below... | 
| 154 |  | -        </Text> | 
|  | 203 | +          )} | 
|  | 204 | +        </Box> | 
| 155 | 205 |       )} | 
| 156 | 206 |     </Box> | 
| 157 | 207 |   ); | 
|  | 
0 commit comments