feat: add /chat summary command

This commit is contained in:
pomelo-nwu
2025-09-08 20:43:38 +08:00
parent 57c6b25e61
commit c33e709802
3 changed files with 208 additions and 3 deletions

View File

@@ -90,7 +90,7 @@ describe('chatCommand', () => {
it('should have the correct main command definition', () => {
expect(chatCommand.name).toBe('chat');
expect(chatCommand.description).toBe('Manage conversation history.');
expect(chatCommand.subCommands).toHaveLength(4);
expect(chatCommand.subCommands).toHaveLength(5);
});
describe('list subcommand', () => {

View File

@@ -15,7 +15,10 @@ import {
CommandKind,
SlashCommandActionReturn,
} from './types.js';
import { decodeTagName } from '@qwen-code/qwen-code-core';
import {
decodeTagName,
getProjectSummaryPrompt,
} from '@qwen-code/qwen-code-core';
import path from 'path';
import { HistoryItemWithoutId, MessageType } from '../types.js';
@@ -272,9 +275,181 @@ const deleteCommand: SlashCommand = {
},
};
const summaryCommand: SlashCommand = {
name: 'summary',
description:
'Generate a project summary and save it to .qwen/PROJECT_SUMMARY.md',
kind: CommandKind.BUILT_IN,
action: async (context): Promise<SlashCommandActionReturn> => {
const { config } = context.services;
const { ui } = context;
if (!config) {
return {
type: 'message',
messageType: 'error',
content: 'Config not loaded.',
};
}
const geminiClient = config.getGeminiClient();
if (!geminiClient) {
return {
type: 'message',
messageType: 'error',
content: 'No chat client available to generate summary.',
};
}
// Check if already generating summary
if (ui.pendingItem) {
ui.addItem(
{
type: 'error' as const,
text: 'Already generating summary, wait for previous request to complete',
},
Date.now(),
);
return {
type: 'message',
messageType: 'error',
content:
'Already generating summary, wait for previous request to complete',
};
}
try {
// Get the current chat history
const chat = geminiClient.getChat();
const history = chat.getHistory();
if (history.length <= 2) {
return {
type: 'message',
messageType: 'info',
content: 'No conversation found to summarize.',
};
}
// Show loading state
const pendingMessage = {
type: 'info' as const,
text: '🔄 Generating project summary...',
};
ui.setPendingItem(pendingMessage);
// Build the conversation context for summary generation
const conversationContext = history.map((message) => ({
role: message.role,
parts: message.parts,
}));
// Use generateContent with chat history as context
const response = await geminiClient.generateContent(
[
...conversationContext,
{
role: 'user',
parts: [
{
text: '' + getProjectSummaryPrompt(),
},
],
},
],
{},
new AbortController().signal,
);
// Extract text from response
const parts = response.candidates?.[0]?.content?.parts;
const markdownSummary =
parts
?.map((part) => part.text)
.filter((text): text is string => typeof text === 'string')
.join('') || '';
if (!markdownSummary) {
throw new Error(
'Failed to generate summary - no text content received from LLM response',
);
}
// Update loading message to show saving progress
ui.setPendingItem({
type: 'info' as const,
text: '💾 Saving project summary...',
});
// Ensure .qwen directory exists
const projectRoot = config.getProjectRoot();
const qwenDir = path.join(projectRoot, '.qwen');
try {
await fsPromises.mkdir(qwenDir, { recursive: true });
} catch (_err) {
// Directory might already exist, ignore error
}
// Save the summary to PROJECT_SUMMARY.md
const summaryPath = path.join(qwenDir, 'PROJECT_SUMMARY.md');
const summaryContent = `${markdownSummary}
---
## Summary Metadata
**Generated**: ${new Date().toISOString()} This summary was automatically generated from the conversation history and is designed to provide comprehensive context for future development sessions.*
`;
await fsPromises.writeFile(summaryPath, summaryContent, 'utf8');
// 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',
},
Date.now(),
);
return {
type: 'message',
messageType: 'info',
content: `Project summary generated and saved to .qwen/PROJECT_SUMMARY.md`,
};
} catch (error) {
// Clear pending item on error
ui.setPendingItem(null);
ui.addItem(
{
type: 'error' as const,
text: `❌ Failed to generate project context summary: ${
error instanceof Error ? error.message : String(error)
}`,
},
Date.now(),
);
return {
type: 'message',
messageType: 'error',
content: `Failed to generate project context summary: ${
error instanceof Error ? error.message : String(error)
}`,
};
}
},
};
export const chatCommand: SlashCommand = {
name: 'chat',
description: 'Manage conversation history.',
kind: CommandKind.BUILT_IN,
subCommands: [listCommand, saveCommand, resumeCommand, deleteCommand],
subCommands: [
listCommand,
saveCommand,
resumeCommand,
deleteCommand,
summaryCommand,
],
};