mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 16:57:46 +00:00
Refactor: Improve console error/log display in CLI (#486)
This commit is contained in:
@@ -4,27 +4,19 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, Key } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { useEffect } from 'react';
|
||||
import util from 'util';
|
||||
import { ConsoleMessageItem } from '../types.js';
|
||||
|
||||
interface ConsoleMessage {
|
||||
id: Key;
|
||||
type: 'log' | 'warn' | 'error' | 'debug';
|
||||
content: string;
|
||||
}
|
||||
|
||||
// Using a module-level counter for unique IDs.
|
||||
// This ensures IDs are unique across messages.
|
||||
let messageIdCounter = 0;
|
||||
|
||||
interface ConsoleOutputProps {
|
||||
interface UseConsolePatcherParams {
|
||||
onNewMessage: (message: Omit<ConsoleMessageItem, 'id'>) => void;
|
||||
debugMode: boolean;
|
||||
}
|
||||
|
||||
export const ConsoleOutput: React.FC<ConsoleOutputProps> = ({ debugMode }) => {
|
||||
const [messages, setMessages] = useState<ConsoleMessage[]>([]);
|
||||
|
||||
export const useConsolePatcher = ({
|
||||
onNewMessage,
|
||||
debugMode,
|
||||
}: UseConsolePatcherParams): void => {
|
||||
useEffect(() => {
|
||||
const originalConsoleLog = console.log;
|
||||
const originalConsoleWarn = console.warn;
|
||||
@@ -32,25 +24,30 @@ export const ConsoleOutput: React.FC<ConsoleOutputProps> = ({ debugMode }) => {
|
||||
const originalConsoleDebug = console.debug;
|
||||
|
||||
const formatArgs = (args: unknown[]): string => util.format(...args);
|
||||
const addMessage = (
|
||||
type: 'log' | 'warn' | 'error' | 'debug',
|
||||
args: unknown[],
|
||||
) => {
|
||||
setMessages((prevMessages) => [
|
||||
...prevMessages,
|
||||
{
|
||||
id: `console-msg-${messageIdCounter++}`,
|
||||
type,
|
||||
content: formatArgs(args),
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
// It's patching time
|
||||
console.log = (...args: unknown[]) => addMessage('log', args);
|
||||
console.warn = (...args: unknown[]) => addMessage('warn', args);
|
||||
console.error = (...args: unknown[]) => addMessage('error', args);
|
||||
console.debug = (...args: unknown[]) => addMessage('debug', args);
|
||||
const patchConsoleMethod =
|
||||
(
|
||||
type: 'log' | 'warn' | 'error' | 'debug',
|
||||
originalMethod: (...args: unknown[]) => void,
|
||||
) =>
|
||||
(...args: unknown[]) => {
|
||||
if (debugMode) {
|
||||
originalMethod.apply(console, args);
|
||||
}
|
||||
|
||||
// Then, if it's not a debug message or debugMode is on, pass to onNewMessage
|
||||
if (type !== 'debug' || debugMode) {
|
||||
onNewMessage({
|
||||
type,
|
||||
content: formatArgs(args),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
console.log = patchConsoleMethod('log', originalConsoleLog);
|
||||
console.warn = patchConsoleMethod('warn', originalConsoleWarn);
|
||||
console.error = patchConsoleMethod('error', originalConsoleError);
|
||||
console.debug = patchConsoleMethod('debug', originalConsoleDebug);
|
||||
|
||||
return () => {
|
||||
console.log = originalConsoleLog;
|
||||
@@ -58,46 +55,5 @@ export const ConsoleOutput: React.FC<ConsoleOutputProps> = ({ debugMode }) => {
|
||||
console.error = originalConsoleError;
|
||||
console.debug = originalConsoleDebug;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
{messages.map((msg) => {
|
||||
if (msg.type === 'debug' && !debugMode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const textProps: { color?: string } = {};
|
||||
let prefix = '';
|
||||
|
||||
switch (msg.type) {
|
||||
case 'warn':
|
||||
textProps.color = 'yellow';
|
||||
prefix = 'WARN: ';
|
||||
break;
|
||||
case 'error':
|
||||
textProps.color = 'red';
|
||||
prefix = 'ERROR: ';
|
||||
break;
|
||||
case 'debug':
|
||||
textProps.color = 'gray';
|
||||
prefix = 'DEBUG: ';
|
||||
break;
|
||||
case 'log':
|
||||
default:
|
||||
prefix = 'LOG: ';
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box key={msg.id}>
|
||||
<Text {...textProps}>
|
||||
{prefix}
|
||||
{msg.content}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
}, [onNewMessage, debugMode]);
|
||||
};
|
||||
|
||||
35
packages/cli/src/ui/components/ConsoleSummaryDisplay.tsx
Normal file
35
packages/cli/src/ui/components/ConsoleSummaryDisplay.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { Colors } from '../colors.js';
|
||||
|
||||
interface ConsoleSummaryDisplayProps {
|
||||
errorCount: number;
|
||||
// logCount is not currently in the plan to be displayed in summary
|
||||
}
|
||||
|
||||
export const ConsoleSummaryDisplay: React.FC<ConsoleSummaryDisplayProps> = ({
|
||||
errorCount,
|
||||
}) => {
|
||||
if (errorCount === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const errorIcon = '\u2716'; // Heavy multiplication x (✖)
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{errorCount > 0 && (
|
||||
<Text color={Colors.AccentRed}>
|
||||
{errorIcon} {errorCount} error{errorCount > 1 ? 's' : ''}{' '}
|
||||
<Text color={Colors.SubtleComment}>(CTRL-D for details)</Text>
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
73
packages/cli/src/ui/components/DetailedMessagesDisplay.tsx
Normal file
73
packages/cli/src/ui/components/DetailedMessagesDisplay.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { Colors } from '../colors.js';
|
||||
import { ConsoleMessageItem } from '../types.js';
|
||||
|
||||
interface DetailedMessagesDisplayProps {
|
||||
messages: ConsoleMessageItem[];
|
||||
// debugMode is not needed here if App.tsx filters debug messages before passing them.
|
||||
// If DetailedMessagesDisplay should handle filtering, add debugMode prop.
|
||||
}
|
||||
|
||||
export const DetailedMessagesDisplay: React.FC<
|
||||
DetailedMessagesDisplayProps
|
||||
> = ({ messages }) => {
|
||||
if (messages.length === 0) {
|
||||
return null; // Don't render anything if there are no messages
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
flexDirection="column"
|
||||
marginTop={1}
|
||||
borderStyle="round"
|
||||
borderColor={Colors.SubtleComment}
|
||||
paddingX={1}
|
||||
>
|
||||
<Box marginBottom={1}>
|
||||
<Text bold color={Colors.Foreground}>
|
||||
Debug Console{' '}
|
||||
<Text color={Colors.SubtleComment}>(CTRL-D to close)</Text>
|
||||
</Text>
|
||||
</Box>
|
||||
{messages.map((msg, index) => {
|
||||
let textColor = Colors.Foreground;
|
||||
let icon = '\u2139'; // Information source (ℹ)
|
||||
|
||||
switch (msg.type) {
|
||||
case 'warn':
|
||||
textColor = Colors.AccentYellow;
|
||||
icon = '\u26A0'; // Warning sign (⚠)
|
||||
break;
|
||||
case 'error':
|
||||
textColor = Colors.AccentRed;
|
||||
icon = '\u2716'; // Heavy multiplication x (✖)
|
||||
break;
|
||||
case 'debug':
|
||||
textColor = Colors.SubtleComment; // Or Colors.Gray
|
||||
icon = '\u1F50D'; // Left-pointing magnifying glass (????)
|
||||
break;
|
||||
case 'log':
|
||||
default:
|
||||
// Default textColor and icon are already set
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box key={index} flexDirection="row">
|
||||
<Text color={textColor}>{icon} </Text>
|
||||
<Text color={textColor} wrap="wrap">
|
||||
{msg.content}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -8,6 +8,7 @@ import React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { Colors } from '../colors.js';
|
||||
import { shortenPath, tildeifyPath, Config } from '@gemini-code/server';
|
||||
import { ConsoleSummaryDisplay } from './ConsoleSummaryDisplay.js';
|
||||
|
||||
interface FooterProps {
|
||||
config: Config;
|
||||
@@ -15,6 +16,8 @@ interface FooterProps {
|
||||
debugMessage: string;
|
||||
cliVersion: string;
|
||||
corgiMode: boolean;
|
||||
errorCount: number;
|
||||
showErrorDetails: boolean;
|
||||
}
|
||||
|
||||
export const Footer: React.FC<FooterProps> = ({
|
||||
@@ -23,8 +26,10 @@ export const Footer: React.FC<FooterProps> = ({
|
||||
debugMessage,
|
||||
cliVersion,
|
||||
corgiMode,
|
||||
errorCount,
|
||||
showErrorDetails,
|
||||
}) => (
|
||||
<Box marginTop={1}>
|
||||
<Box marginTop={1} justifyContent="space-between" width="100%">
|
||||
<Box>
|
||||
<Text color={Colors.LightBlue}>
|
||||
{shortenPath(tildeifyPath(config.getTargetDir()), 70)}
|
||||
@@ -56,8 +61,8 @@ export const Footer: React.FC<FooterProps> = ({
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Right Section: Gemini Label */}
|
||||
<Box>
|
||||
{/* Right Section: Gemini Label and Console Summary */}
|
||||
<Box alignItems="center">
|
||||
<Text color={Colors.AccentBlue}> {config.getModel()} </Text>
|
||||
<Text color={Colors.SubtleComment}>| CLI {cliVersion} </Text>
|
||||
{corgiMode && (
|
||||
@@ -70,6 +75,12 @@ export const Footer: React.FC<FooterProps> = ({
|
||||
<Text color={Colors.AccentRed}>▼ </Text>
|
||||
</Text>
|
||||
)}
|
||||
{!showErrorDetails && errorCount > 0 && (
|
||||
<Box>
|
||||
<Text color={Colors.SubtleComment}>| </Text>
|
||||
<ConsoleSummaryDisplay errorCount={errorCount} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user