mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-21 17:27:54 +00:00
feat: add loading for summary
This commit is contained in:
@@ -20,7 +20,11 @@ import {
|
|||||||
getProjectSummaryPrompt,
|
getProjectSummaryPrompt,
|
||||||
} from '@qwen-code/qwen-code-core';
|
} from '@qwen-code/qwen-code-core';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { HistoryItemWithoutId, MessageType } from '../types.js';
|
import {
|
||||||
|
HistoryItemWithoutId,
|
||||||
|
HistoryItemSummary,
|
||||||
|
MessageType,
|
||||||
|
} from '../types.js';
|
||||||
|
|
||||||
interface ChatDetail {
|
interface ChatDetail {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -331,9 +335,12 @@ const summaryCommand: SlashCommand = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Show loading state
|
// Show loading state
|
||||||
const pendingMessage = {
|
const pendingMessage: HistoryItemSummary = {
|
||||||
type: 'info' as const,
|
type: 'summary',
|
||||||
text: '🔄 Generating project summary...',
|
summary: {
|
||||||
|
isPending: true,
|
||||||
|
stage: 'generating',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
ui.setPendingItem(pendingMessage);
|
ui.setPendingItem(pendingMessage);
|
||||||
|
|
||||||
@@ -377,8 +384,11 @@ const summaryCommand: SlashCommand = {
|
|||||||
|
|
||||||
// Update loading message to show saving progress
|
// Update loading message to show saving progress
|
||||||
ui.setPendingItem({
|
ui.setPendingItem({
|
||||||
type: 'info' as const,
|
type: 'summary',
|
||||||
text: '💾 Saving project summary...',
|
summary: {
|
||||||
|
isPending: true,
|
||||||
|
stage: 'saving',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure .qwen directory exists
|
// Ensure .qwen directory exists
|
||||||
@@ -404,13 +414,14 @@ const summaryCommand: SlashCommand = {
|
|||||||
|
|
||||||
// Clear pending item and show success message
|
// Clear pending item and show success message
|
||||||
ui.setPendingItem(null);
|
ui.setPendingItem(null);
|
||||||
ui.addItem(
|
const completedSummaryItem: HistoryItemSummary = {
|
||||||
{
|
type: 'summary',
|
||||||
type: 'info' as const,
|
summary: {
|
||||||
text: '✅ Project summary generated and saved to .qwen/PROJECT_SUMMARY.md',
|
isPending: false,
|
||||||
|
stage: 'completed',
|
||||||
},
|
},
|
||||||
Date.now(),
|
};
|
||||||
);
|
ui.addItem(completedSummaryItem, Date.now());
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { ErrorMessage } from './messages/ErrorMessage.js';
|
|||||||
import { ToolGroupMessage } from './messages/ToolGroupMessage.js';
|
import { ToolGroupMessage } from './messages/ToolGroupMessage.js';
|
||||||
import { GeminiMessageContent } from './messages/GeminiMessageContent.js';
|
import { GeminiMessageContent } from './messages/GeminiMessageContent.js';
|
||||||
import { CompressionMessage } from './messages/CompressionMessage.js';
|
import { CompressionMessage } from './messages/CompressionMessage.js';
|
||||||
|
import { SummaryMessage } from './messages/SummaryMessage.js';
|
||||||
import { Box } from 'ink';
|
import { Box } from 'ink';
|
||||||
import { AboutBox } from './AboutBox.js';
|
import { AboutBox } from './AboutBox.js';
|
||||||
import { StatsDisplay } from './StatsDisplay.js';
|
import { StatsDisplay } from './StatsDisplay.js';
|
||||||
@@ -97,5 +98,6 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
|
|||||||
{item.type === 'compression' && (
|
{item.type === 'compression' && (
|
||||||
<CompressionMessage compression={item.compression} />
|
<CompressionMessage compression={item.compression} />
|
||||||
)}
|
)}
|
||||||
|
{item.type === 'summary' && <SummaryMessage summary={item.summary} />}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
55
packages/cli/src/ui/components/messages/SummaryMessage.tsx
Normal file
55
packages/cli/src/ui/components/messages/SummaryMessage.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -157,6 +157,11 @@ export const useSlashCommandProcessor = (
|
|||||||
type: 'compression',
|
type: 'compression',
|
||||||
compression: message.compression,
|
compression: message.compression,
|
||||||
};
|
};
|
||||||
|
} else if (message.type === MessageType.SUMMARY) {
|
||||||
|
historyItemContent = {
|
||||||
|
type: 'summary',
|
||||||
|
summary: message.summary,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
historyItemContent = {
|
historyItemContent = {
|
||||||
type: message.type,
|
type: message.type,
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ export interface CompressionProps {
|
|||||||
newTokenCount: number | null;
|
newTokenCount: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SummaryProps {
|
||||||
|
isPending: boolean;
|
||||||
|
stage: 'generating' | 'saving' | 'completed';
|
||||||
|
}
|
||||||
|
|
||||||
export interface HistoryItemBase {
|
export interface HistoryItemBase {
|
||||||
text?: string; // Text content for user/gemini/info/error messages
|
text?: string; // Text content for user/gemini/info/error messages
|
||||||
}
|
}
|
||||||
@@ -141,6 +146,11 @@ export type HistoryItemCompression = HistoryItemBase & {
|
|||||||
compression: CompressionProps;
|
compression: CompressionProps;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type HistoryItemSummary = HistoryItemBase & {
|
||||||
|
type: 'summary';
|
||||||
|
summary: SummaryProps;
|
||||||
|
};
|
||||||
|
|
||||||
// Using Omit<HistoryItem, 'id'> seems to have some issues with typescript's
|
// 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
|
// type inference e.g. historyItem.type === 'tool_group' isn't auto-inferring that
|
||||||
// 'tools' in historyItem.
|
// 'tools' in historyItem.
|
||||||
@@ -160,7 +170,8 @@ export type HistoryItemWithoutId =
|
|||||||
| HistoryItemToolStats
|
| HistoryItemToolStats
|
||||||
| HistoryItemQuit
|
| HistoryItemQuit
|
||||||
| HistoryItemQuitConfirmation
|
| HistoryItemQuitConfirmation
|
||||||
| HistoryItemCompression;
|
| HistoryItemCompression
|
||||||
|
| HistoryItemSummary;
|
||||||
|
|
||||||
export type HistoryItem = HistoryItemWithoutId & { id: number };
|
export type HistoryItem = HistoryItemWithoutId & { id: number };
|
||||||
|
|
||||||
@@ -178,6 +189,7 @@ export enum MessageType {
|
|||||||
QUIT_CONFIRMATION = 'quit_confirmation',
|
QUIT_CONFIRMATION = 'quit_confirmation',
|
||||||
GEMINI = 'gemini',
|
GEMINI = 'gemini',
|
||||||
COMPRESSION = 'compression',
|
COMPRESSION = 'compression',
|
||||||
|
SUMMARY = 'summary',
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simplified message structure for internal feedback
|
// Simplified message structure for internal feedback
|
||||||
@@ -236,6 +248,11 @@ export type Message =
|
|||||||
type: MessageType.COMPRESSION;
|
type: MessageType.COMPRESSION;
|
||||||
compression: CompressionProps;
|
compression: CompressionProps;
|
||||||
timestamp: Date;
|
timestamp: Date;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: MessageType.SUMMARY;
|
||||||
|
summary: SummaryProps;
|
||||||
|
timestamp: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ConsoleMessageItem {
|
export interface ConsoleMessageItem {
|
||||||
|
|||||||
Reference in New Issue
Block a user