feat: add loading for summary

This commit is contained in:
pomelo-nwu
2025-09-08 23:09:00 +08:00
parent 28896b4bf8
commit ac2aeaea45
5 changed files with 103 additions and 13 deletions

View File

@@ -20,7 +20,11 @@ import {
getProjectSummaryPrompt,
} from '@qwen-code/qwen-code-core';
import path from 'path';
import { HistoryItemWithoutId, MessageType } from '../types.js';
import {
HistoryItemWithoutId,
HistoryItemSummary,
MessageType,
} from '../types.js';
interface ChatDetail {
name: string;
@@ -331,9 +335,12 @@ const summaryCommand: SlashCommand = {
}
// Show loading state
const pendingMessage = {
type: 'info' as const,
text: '🔄 Generating project summary...',
const pendingMessage: HistoryItemSummary = {
type: 'summary',
summary: {
isPending: true,
stage: 'generating',
},
};
ui.setPendingItem(pendingMessage);
@@ -377,8 +384,11 @@ const summaryCommand: SlashCommand = {
// Update loading message to show saving progress
ui.setPendingItem({
type: 'info' as const,
text: '💾 Saving project summary...',
type: 'summary',
summary: {
isPending: true,
stage: 'saving',
},
});
// Ensure .qwen directory exists
@@ -404,13 +414,14 @@ const summaryCommand: SlashCommand = {
// Clear pending item and show success message
ui.setPendingItem(null);
ui.addItem(
{
type: 'info' as const,
text: '✅ Project summary generated and saved to .qwen/PROJECT_SUMMARY.md',
const completedSummaryItem: HistoryItemSummary = {
type: 'summary',
summary: {
isPending: false,
stage: 'completed',
},
Date.now(),
);
};
ui.addItem(completedSummaryItem, Date.now());
return {
type: 'message',

View File

@@ -14,6 +14,7 @@ import { ErrorMessage } from './messages/ErrorMessage.js';
import { ToolGroupMessage } from './messages/ToolGroupMessage.js';
import { GeminiMessageContent } from './messages/GeminiMessageContent.js';
import { CompressionMessage } from './messages/CompressionMessage.js';
import { SummaryMessage } from './messages/SummaryMessage.js';
import { Box } from 'ink';
import { AboutBox } from './AboutBox.js';
import { StatsDisplay } from './StatsDisplay.js';
@@ -97,5 +98,6 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
{item.type === 'compression' && (
<CompressionMessage compression={item.compression} />
)}
{item.type === 'summary' && <SummaryMessage summary={item.summary} />}
</Box>
);

View File

@@ -0,0 +1,55 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import React from 'react';
import { Box, Text } from 'ink';
import { SummaryProps } from '../../types.js';
import Spinner from 'ink-spinner';
import { Colors } from '../../colors.js';
export interface SummaryDisplayProps {
summary: SummaryProps;
}
/*
* Summary messages appear when the /chat summary command is run, and show a loading spinner
* while summary generation is in progress, followed up by success confirmation.
*/
export const SummaryMessage: React.FC<SummaryDisplayProps> = ({ summary }) => {
const getText = () => {
if (summary.isPending) {
switch (summary.stage) {
case 'generating':
return 'Generating project summary...';
case 'saving':
return 'Saving project summary...';
default:
return 'Processing summary...';
}
}
return 'Project summary generated and saved successfully!';
};
const getIcon = () => {
if (summary.isPending) {
return <Spinner type="dots" />;
}
return <Text color={Colors.AccentGreen}></Text>;
};
return (
<Box flexDirection="row">
<Box marginRight={1}>{getIcon()}</Box>
<Box>
<Text
color={summary.isPending ? Colors.AccentPurple : Colors.AccentGreen}
>
{getText()}
</Text>
</Box>
</Box>
);
};

View File

@@ -157,6 +157,11 @@ export const useSlashCommandProcessor = (
type: 'compression',
compression: message.compression,
};
} else if (message.type === MessageType.SUMMARY) {
historyItemContent = {
type: 'summary',
summary: message.summary,
};
} else {
historyItemContent = {
type: message.type,

View File

@@ -58,6 +58,11 @@ export interface CompressionProps {
newTokenCount: number | null;
}
export interface SummaryProps {
isPending: boolean;
stage: 'generating' | 'saving' | 'completed';
}
export interface HistoryItemBase {
text?: string; // Text content for user/gemini/info/error messages
}
@@ -141,6 +146,11 @@ export type HistoryItemCompression = HistoryItemBase & {
compression: CompressionProps;
};
export type HistoryItemSummary = HistoryItemBase & {
type: 'summary';
summary: SummaryProps;
};
// Using Omit<HistoryItem, 'id'> seems to have some issues with typescript's
// type inference e.g. historyItem.type === 'tool_group' isn't auto-inferring that
// 'tools' in historyItem.
@@ -160,7 +170,8 @@ export type HistoryItemWithoutId =
| HistoryItemToolStats
| HistoryItemQuit
| HistoryItemQuitConfirmation
| HistoryItemCompression;
| HistoryItemCompression
| HistoryItemSummary;
export type HistoryItem = HistoryItemWithoutId & { id: number };
@@ -178,6 +189,7 @@ export enum MessageType {
QUIT_CONFIRMATION = 'quit_confirmation',
GEMINI = 'gemini',
COMPRESSION = 'compression',
SUMMARY = 'summary',
}
// Simplified message structure for internal feedback
@@ -236,6 +248,11 @@ export type Message =
type: MessageType.COMPRESSION;
compression: CompressionProps;
timestamp: Date;
}
| {
type: MessageType.SUMMARY;
summary: SummaryProps;
timestamp: Date;
};
export interface ConsoleMessageItem {