mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 08:47:44 +00:00
🚀 Add Todo Write Tool for Task Management and Progress Tracking (#478)
This commit is contained in:
@@ -12,6 +12,8 @@ import { Colors } from '../../colors.js';
|
||||
import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js';
|
||||
import { GeminiRespondingSpinner } from '../GeminiRespondingSpinner.js';
|
||||
import { MaxSizedBox } from '../shared/MaxSizedBox.js';
|
||||
import { TodoDisplay } from '../TodoDisplay.js';
|
||||
import { TodoResultDisplay } from '@qwen-code/qwen-code-core';
|
||||
|
||||
const STATIC_HEIGHT = 1;
|
||||
const RESERVED_LINE_COUNT = 5; // for tool name, status, padding etc.
|
||||
@@ -23,6 +25,116 @@ const MIN_LINES_SHOWN = 2; // show at least this many lines
|
||||
const MAXIMUM_RESULT_DISPLAY_CHARACTERS = 1000000;
|
||||
export type TextEmphasis = 'high' | 'medium' | 'low';
|
||||
|
||||
type DisplayRendererResult =
|
||||
| { type: 'none' }
|
||||
| { type: 'todo'; data: TodoResultDisplay }
|
||||
| { type: 'string'; data: string }
|
||||
| { type: 'diff'; data: { fileDiff: string; fileName: string } };
|
||||
|
||||
/**
|
||||
* Custom hook to determine the type of result display and return appropriate rendering info
|
||||
*/
|
||||
const useResultDisplayRenderer = (
|
||||
resultDisplay: unknown,
|
||||
): DisplayRendererResult =>
|
||||
React.useMemo(() => {
|
||||
if (!resultDisplay) {
|
||||
return { type: 'none' };
|
||||
}
|
||||
|
||||
// Check for TodoResultDisplay
|
||||
if (
|
||||
typeof resultDisplay === 'object' &&
|
||||
resultDisplay !== null &&
|
||||
'type' in resultDisplay &&
|
||||
resultDisplay.type === 'todo_list'
|
||||
) {
|
||||
return {
|
||||
type: 'todo',
|
||||
data: resultDisplay as TodoResultDisplay,
|
||||
};
|
||||
}
|
||||
|
||||
// Check for FileDiff
|
||||
if (
|
||||
typeof resultDisplay === 'object' &&
|
||||
resultDisplay !== null &&
|
||||
'fileDiff' in resultDisplay
|
||||
) {
|
||||
return {
|
||||
type: 'diff',
|
||||
data: resultDisplay as { fileDiff: string; fileName: string },
|
||||
};
|
||||
}
|
||||
|
||||
// Default to string
|
||||
return {
|
||||
type: 'string',
|
||||
data: resultDisplay as string,
|
||||
};
|
||||
}, [resultDisplay]);
|
||||
|
||||
/**
|
||||
* Component to render todo list results
|
||||
*/
|
||||
const TodoResultRenderer: React.FC<{ data: TodoResultDisplay }> = ({
|
||||
data,
|
||||
}) => <TodoDisplay todos={data.todos} />;
|
||||
|
||||
/**
|
||||
* Component to render string results (markdown or plain text)
|
||||
*/
|
||||
const StringResultRenderer: React.FC<{
|
||||
data: string;
|
||||
renderAsMarkdown: boolean;
|
||||
availableHeight?: number;
|
||||
childWidth: number;
|
||||
}> = ({ data, renderAsMarkdown, availableHeight, childWidth }) => {
|
||||
let displayData = data;
|
||||
|
||||
// Truncate if too long
|
||||
if (displayData.length > MAXIMUM_RESULT_DISPLAY_CHARACTERS) {
|
||||
displayData = '...' + displayData.slice(-MAXIMUM_RESULT_DISPLAY_CHARACTERS);
|
||||
}
|
||||
|
||||
if (renderAsMarkdown) {
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<MarkdownDisplay
|
||||
text={displayData}
|
||||
isPending={false}
|
||||
availableTerminalHeight={availableHeight}
|
||||
terminalWidth={childWidth}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MaxSizedBox maxHeight={availableHeight} maxWidth={childWidth}>
|
||||
<Box>
|
||||
<Text wrap="wrap">{displayData}</Text>
|
||||
</Box>
|
||||
</MaxSizedBox>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Component to render diff results
|
||||
*/
|
||||
const DiffResultRenderer: React.FC<{
|
||||
data: { fileDiff: string; fileName: string };
|
||||
availableHeight?: number;
|
||||
childWidth: number;
|
||||
}> = ({ data, availableHeight, childWidth }) => (
|
||||
<DiffRenderer
|
||||
diffContent={data.fileDiff}
|
||||
filename={data.fileName}
|
||||
availableTerminalHeight={availableHeight}
|
||||
terminalWidth={childWidth}
|
||||
/>
|
||||
);
|
||||
|
||||
export interface ToolMessageProps extends IndividualToolCallDisplay {
|
||||
availableTerminalHeight?: number;
|
||||
terminalWidth: number;
|
||||
@@ -55,13 +167,10 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
|
||||
}
|
||||
|
||||
const childWidth = terminalWidth - 3; // account for padding.
|
||||
if (typeof resultDisplay === 'string') {
|
||||
if (resultDisplay.length > MAXIMUM_RESULT_DISPLAY_CHARACTERS) {
|
||||
// Truncate the result display to fit within the available width.
|
||||
resultDisplay =
|
||||
'...' + resultDisplay.slice(-MAXIMUM_RESULT_DISPLAY_CHARACTERS);
|
||||
}
|
||||
}
|
||||
|
||||
// Use the custom hook to determine the display type
|
||||
const displayRenderer = useResultDisplayRenderer(resultDisplay);
|
||||
|
||||
return (
|
||||
<Box paddingX={1} paddingY={0} flexDirection="column">
|
||||
<Box minHeight={1}>
|
||||
@@ -74,32 +183,25 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
|
||||
/>
|
||||
{emphasis === 'high' && <TrailingIndicator />}
|
||||
</Box>
|
||||
{resultDisplay && (
|
||||
{displayRenderer.type !== 'none' && (
|
||||
<Box paddingLeft={STATUS_INDICATOR_WIDTH} width="100%" marginTop={1}>
|
||||
<Box flexDirection="column">
|
||||
{typeof resultDisplay === 'string' && renderOutputAsMarkdown && (
|
||||
<Box flexDirection="column">
|
||||
<MarkdownDisplay
|
||||
text={resultDisplay}
|
||||
isPending={false}
|
||||
availableTerminalHeight={availableHeight}
|
||||
terminalWidth={childWidth}
|
||||
/>
|
||||
</Box>
|
||||
{displayRenderer.type === 'todo' && (
|
||||
<TodoResultRenderer data={displayRenderer.data} />
|
||||
)}
|
||||
{typeof resultDisplay === 'string' && !renderOutputAsMarkdown && (
|
||||
<MaxSizedBox maxHeight={availableHeight} maxWidth={childWidth}>
|
||||
<Box>
|
||||
<Text wrap="wrap">{resultDisplay}</Text>
|
||||
</Box>
|
||||
</MaxSizedBox>
|
||||
{displayRenderer.type === 'string' && (
|
||||
<StringResultRenderer
|
||||
data={displayRenderer.data}
|
||||
renderAsMarkdown={renderOutputAsMarkdown}
|
||||
availableHeight={availableHeight}
|
||||
childWidth={childWidth}
|
||||
/>
|
||||
)}
|
||||
{typeof resultDisplay !== 'string' && (
|
||||
<DiffRenderer
|
||||
diffContent={resultDisplay.fileDiff}
|
||||
filename={resultDisplay.fileName}
|
||||
availableTerminalHeight={availableHeight}
|
||||
terminalWidth={childWidth}
|
||||
{displayRenderer.type === 'diff' && (
|
||||
<DiffResultRenderer
|
||||
data={displayRenderer.data}
|
||||
availableHeight={availableHeight}
|
||||
childWidth={childWidth}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user