Просмотр исходного кода

feat(tui): add conversation scroll with Shift+Up/Down

Add scrollOffset to the conversation area so users can scroll back
through message history. Shift+Up scrolls up 3 messages, Shift+Down
scrolls back down. Scroll resets to bottom on new message submit.

Also reverts the Terminal.app light palette color change (keep
original yellow/blue).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fanghanjun 7 месяцев назад
Родитель
Сommit
9c1388bbdd

+ 8 - 3
packages/cli/src/tui/dashboard-model.ts

@@ -49,6 +49,7 @@ export interface BuildDashboardViewModelParams {
   readonly lastError?: string;
   readonly sinceTimestamp?: number;
   readonly terminalRows?: number;
+  readonly scrollOffset?: number;
 }
 
 export function buildDashboardViewModel(params: BuildDashboardViewModelParams): DashboardViewModel {
@@ -62,9 +63,13 @@ export function buildDashboardViewModel(params: BuildDashboardViewModelParams):
   const terminalRows = params.terminalRows ?? process.stdout.rows ?? 24;
   const conversationLimit = Math.max(4, terminalRows - 10);
 
-  const messageRows = params.session.messages
-    .filter((message) => message.timestamp >= sinceTimestamp)
-    .slice(-conversationLimit)
+  const filteredMessages = params.session.messages
+    .filter((message) => message.timestamp >= sinceTimestamp);
+  const scrollOffset = params.scrollOffset ?? 0;
+  const endIndex = filteredMessages.length - scrollOffset;
+  const startIndex = Math.max(0, endIndex - conversationLimit);
+  const messageRows = filteredMessages
+    .slice(startIndex, endIndex > 0 ? endIndex : undefined)
     .map((message, index) => ({
       key: `${message.timestamp}-${index}`,
       label: roleLabel(message.role, params.copy),

+ 14 - 0
packages/cli/src/tui/dashboard.tsx

@@ -46,6 +46,7 @@ export interface InkTuiDashboardProps {
   readonly slashSuggestions?: ReadonlyArray<string>;
   readonly selectedSlashIndex?: number;
   readonly showComposerCursor?: boolean;
+  readonly scrollOffset?: number;
   readonly onInputChange?: (value: string) => void;
   readonly onSubmit?: (value: string) => void;
 }
@@ -78,6 +79,7 @@ export function InkTuiDashboard(props: InkTuiDashboardProps): React.JSX.Element
     isSubmitting: props.isSubmitting,
     lastError: props.lastError,
     sinceTimestamp: props.sinceTimestamp,
+    scrollOffset: props.scrollOffset,
   });
   const activeAccent = props.isSubmitting ? WARM_ACCENT : statusColor(model.executionStatus);
   const composer = renderComposerDisplay(props.inputValue, model.composerPlaceholder, props.showComposerCursor ?? false);
@@ -170,6 +172,7 @@ export function InkTuiApp(props: InkTuiAppProps): React.JSX.Element {
   const [lastError, setLastError] = useState<string | undefined>();
   const [sinceTimestamp, setSinceTimestamp] = useState<number | undefined>();
   const [selectedSlashIndex, setSelectedSlashIndex] = useState(0);
+  const [scrollOffset, setScrollOffset] = useState(0);
   const [historyState, setHistoryState] = useState<{ cursor: number | null; draft: string }>({
     cursor: null,
     draft: "",
@@ -251,6 +254,10 @@ export function InkTuiApp(props: InkTuiAppProps): React.JSX.Element {
     }
 
     if (key.downArrow) {
+      if (key.shift) {
+        setScrollOffset((cur) => Math.max(0, cur - 3));
+        return;
+      }
       const next = moveHistoryCursor(inputHistory, historyState, inputValue, "down");
       setHistoryState(next.state);
       setInputValue(next.value);
@@ -258,6 +265,11 @@ export function InkTuiApp(props: InkTuiAppProps): React.JSX.Element {
     }
 
     if (key.upArrow) {
+      if (key.shift) {
+        const maxOffset = Math.max(0, session.messages.length - 4);
+        setScrollOffset((cur) => Math.min(maxOffset, cur + 3));
+        return;
+      }
       const next = moveHistoryCursor(inputHistory, historyState, inputValue, "up");
       setHistoryState(next.state);
       setInputValue(next.value);
@@ -348,6 +360,7 @@ export function InkTuiApp(props: InkTuiAppProps): React.JSX.Element {
       setIsSubmitting(true);
       setLastError(undefined);
       setInputValue("");
+      setScrollOffset(0);
       setHistoryState({ cursor: null, draft: "" });
       setSession((current) => createOptimisticUserMessageSession(current, input, userTimestamp));
 
@@ -412,6 +425,7 @@ export function InkTuiApp(props: InkTuiAppProps): React.JSX.Element {
       slashSuggestions={slashSuggestions}
       selectedSlashIndex={selectedSlashIndex}
       showComposerCursor={composerCaret.visible}
+      scrollOffset={scrollOffset}
       onInputChange={(value) => {
         setInputValue(value);
         setSelectedSlashIndex(0);

+ 2 - 2
packages/cli/src/tui/theme.ts

@@ -96,9 +96,9 @@ const appleTerminalDarkPalette: ThemePalette = {
 };
 
 const appleTerminalLightPalette: ThemePalette = {
-  warmAccent: "gray",
+  warmAccent: "yellow",
   warmMuted: "gray",
-  warmReply: "black",
+  warmReply: "blue",
   warmBorder: "gray",
   statusSuccess: "green",
   statusError: "red",