From d0fcaa84830b4727ce65cf3476a2ba8143f13336 Mon Sep 17 00:00:00 2001 From: liuqian Date: Wed, 28 May 2025 16:55:34 +0800 Subject: [PATCH 1/4] feat(chat): add message summary provider to ChatFeatureRegistry --- .../src/browser/chat/chat.feature.registry.ts | 18 +++++++++++++++- .../ai-native/src/browser/chat/chat.view.tsx | 21 ++++++++++++++++--- packages/ai-native/src/browser/types.ts | 9 ++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/packages/ai-native/src/browser/chat/chat.feature.registry.ts b/packages/ai-native/src/browser/chat/chat.feature.registry.ts index e3b57b52b6..95319ff8f4 100644 --- a/packages/ai-native/src/browser/chat/chat.feature.registry.ts +++ b/packages/ai-native/src/browser/chat/chat.feature.registry.ts @@ -2,7 +2,13 @@ import { Injectable } from '@opensumi/di'; import { Disposable, Emitter, Event, getDebugLogger } from '@opensumi/ide-core-common'; import { IChatWelcomeMessageContent, ISampleQuestions, SLASH_SYMBOL } from '../../common'; -import { IChatFeatureRegistry, IChatSlashCommandHandler, IChatSlashCommandItem, IImageUploadProvider } from '../types'; +import { + IChatFeatureRegistry, + IChatSlashCommandHandler, + IChatSlashCommandItem, + IImageUploadProvider, + IMessageSummaryProvider, +} from '../types'; import { ChatSlashCommandItemModel, ChatWelcomeMessageModel } from './chat-model'; import { ChatProxyService } from './chat-proxy.service'; @@ -14,6 +20,8 @@ export class ChatFeatureRegistry extends Disposable implements IChatFeatureRegis private slashCommandsHandlerMap: Map = new Map(); private imageUploadProvider: IImageUploadProvider | undefined; + private messageSummaryProvider?: IMessageSummaryProvider; + public registerImageUploadProvider(provider: IImageUploadProvider): void { this.imageUploadProvider = provider; } @@ -22,6 +30,14 @@ export class ChatFeatureRegistry extends Disposable implements IChatFeatureRegis return this.imageUploadProvider; } + public registerMessageSummaryProvider(provider: IMessageSummaryProvider): void { + this.messageSummaryProvider = provider; + } + + public getMessageSummaryProvider(): IMessageSummaryProvider | undefined { + return this.messageSummaryProvider; + } + public chatWelcomeMessageModel?: ChatWelcomeMessageModel; private _onDidWelcomeMessageChange: Emitter = new Emitter(); diff --git a/packages/ai-native/src/browser/chat/chat.view.tsx b/packages/ai-native/src/browser/chat/chat.view.tsx index 212e2a7eaa..f33fb95bdb 100644 --- a/packages/ai-native/src/browser/chat/chat.view.tsx +++ b/packages/ai-native/src/browser/chat/chat.view.tsx @@ -918,6 +918,7 @@ export function DefaultChatViewHeader({ }) { const aiChatService = useInjectable(IChatInternalService); const messageService = useInjectable(IMessageService); + const chatFeatureRegistry = useInjectable(ChatFeatureRegistryToken); const [historyList, setHistoryList] = React.useState([]); const [currentTitle, setCurrentTitle] = React.useState(''); @@ -947,9 +948,23 @@ export function DefaultChatViewHeader({ const getHistoryList = () => { const currentMessages = aiChatService.sessionModel.history.getMessages(); const latestUserMessage = currentMessages.findLast((m) => m.role === ChatMessageRole.User); - setCurrentTitle( - latestUserMessage ? cleanAttachedTextWrapper(latestUserMessage.content).slice(0, MAX_TITLE_LENGTH) : '', - ); + const summaryProvider = chatFeatureRegistry.getMessageSummaryProvider(); + const currentTitle = latestUserMessage + ? cleanAttachedTextWrapper(latestUserMessage.content).slice(0, MAX_TITLE_LENGTH) + : ''; + + if (summaryProvider && aiChatService.sessionModel.sessionId) { + summaryProvider.getMessageSummary(aiChatService.sessionModel.sessionId).then((summary) => { + if (summary) { + setCurrentTitle(summary.slice(0, MAX_TITLE_LENGTH)); + } else { + setCurrentTitle(currentTitle); + } + }); + } else { + setCurrentTitle(currentTitle); + } + setHistoryList( aiChatService.getSessions().map((session) => { const history = session.history; diff --git a/packages/ai-native/src/browser/types.ts b/packages/ai-native/src/browser/types.ts index 24f554254f..0e2536f61b 100644 --- a/packages/ai-native/src/browser/types.ts +++ b/packages/ai-native/src/browser/types.ts @@ -134,6 +134,8 @@ export interface IChatFeatureRegistry { registerImageUploadProvider(provider: IImageUploadProvider): void; registerWelcome(content: IChatWelcomeMessageContent | React.ReactNode, sampleQuestions?: ISampleQuestions[]): void; registerSlashCommand(command: IChatSlashCommandItem, handler: IChatSlashCommandHandler): void; + + registerMessageSummaryProvider(provider: IMessageSummaryProvider): void; } export type ChatWelcomeRender = (props: { @@ -298,6 +300,13 @@ export interface IImageUploadProvider { imageUpload(file: File): Promise; } +export interface IMessageSummaryProvider { + /** + * @param sessionId 会话 ID + */ + getMessageSummary(sessionId: string): Promise; +} + export const AINativeCoreContribution = Symbol('AINativeCoreContribution'); export interface AINativeCoreContribution { From 32cf2d6575eceb652a657e3f30981b1e0ab485f1 Mon Sep 17 00:00:00 2001 From: liuqian Date: Wed, 28 May 2025 17:44:26 +0800 Subject: [PATCH 2/4] feat(chat): enhance message summary provider to accept message history for summarization --- .../ai-native/src/browser/chat/chat.view.tsx | 9 ++-- packages/ai-native/src/browser/types.ts | 11 +++-- .../ai-native/ai-native.contribution.ts | 45 +++++++++++++++++++ 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/packages/ai-native/src/browser/chat/chat.view.tsx b/packages/ai-native/src/browser/chat/chat.view.tsx index f33fb95bdb..aac3e2cbc9 100644 --- a/packages/ai-native/src/browser/chat/chat.view.tsx +++ b/packages/ai-native/src/browser/chat/chat.view.tsx @@ -952,9 +952,12 @@ export function DefaultChatViewHeader({ const currentTitle = latestUserMessage ? cleanAttachedTextWrapper(latestUserMessage.content).slice(0, MAX_TITLE_LENGTH) : ''; - - if (summaryProvider && aiChatService.sessionModel.sessionId) { - summaryProvider.getMessageSummary(aiChatService.sessionModel.sessionId).then((summary) => { + const messages = aiChatService.sessionModel.history.getMessages().map((msg) => ({ + role: msg.role, + content: msg.content, + })); + if (messages.length > 2 && summaryProvider && aiChatService.sessionModel.sessionId) { + summaryProvider.getMessageSummary(messages).then((summary) => { if (summary) { setCurrentTitle(summary.slice(0, MAX_TITLE_LENGTH)); } else { diff --git a/packages/ai-native/src/browser/types.ts b/packages/ai-native/src/browser/types.ts index 0e2536f61b..548f312e89 100644 --- a/packages/ai-native/src/browser/types.ts +++ b/packages/ai-native/src/browser/types.ts @@ -5,6 +5,7 @@ import { ZodSchema } from 'zod'; import { AIActionItem } from '@opensumi/ide-core-browser/lib/components/ai-native/index'; import { CancellationToken, + ChatMessageRole, ChatResponse, Deferred, IAICompletionOption, @@ -301,10 +302,12 @@ export interface IImageUploadProvider { } export interface IMessageSummaryProvider { - /** - * @param sessionId 会话 ID - */ - getMessageSummary(sessionId: string): Promise; + getMessageSummary( + messages: Array<{ + role: ChatMessageRole; + content: string; + }>, + ): Promise; } export const AINativeCoreContribution = Symbol('AINativeCoreContribution'); diff --git a/packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts b/packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts index ad3b80a750..5db5ff197c 100644 --- a/packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts +++ b/packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts @@ -35,6 +35,7 @@ import { AIBackSerivcePath, CancelResponse, CancellationToken, + ChatMessageRole, ChatResponse, ChatServiceToken, ErrorResponse, @@ -219,6 +220,15 @@ export class AINativeContribution implements AINativeCoreContribution { ); } + private async getSummary( + messages: Array<{ + role: ChatMessageRole; + content: string; + }>, + ): Promise { + const latestUserMessage = messages.findLast((m) => m.role === ChatMessageRole.User); + } + registerChatFeature(registry: IChatFeatureRegistry): void { registry.registerWelcome( new MarkdownString(` @@ -236,6 +246,41 @@ export class AINativeContribution implements AINativeCoreContribution { imageUpload: imageToBase64, }); + registry.registerMessageSummaryProvider({ + getMessageSummary: async ( + messages: Array<{ + role: ChatMessageRole; + content: string; + }>, + ) => { + // 裁剪最近的10条消息,避免过长的消息导致请求失败 + const sliceMessages = messages.slice(-10); + // TODO: summarize the messages + const prompt = `You are a helpful AI assistant. Summarize in ONE clear statement (12-15 words). +Requirements: +- Include what happened +- No meta-descriptions +- No interpretations + +Bad: "The discussion was about ENI limits" +Good: "Instance network interfaces exceeded system limit"`; + const result = await this.aiBackService.request(prompt, { + type: '', + messages: sliceMessages, + }); + + if (result.isCancel) { + return ''; + } + + if (result.errorCode !== 0) { + return ''; + } + + return result.data || ''; + }, + }); + // registry.registerSlashCommand( // { // name: 'Explain', From 0721634ad73ef25b3eaf7d64eb7f6bd2640a25a3 Mon Sep 17 00:00:00 2001 From: liuqian Date: Thu, 29 May 2025 10:53:12 +0800 Subject: [PATCH 3/4] feat(chat): implement message summary caching and update logic based on message count --- .../ai-native/ai-native.contribution.ts | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts b/packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts index 5db5ff197c..c2259a9d52 100644 --- a/packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts +++ b/packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts @@ -58,6 +58,10 @@ export class AINativeContribution implements AINativeCoreContribution { @Autowired(AIBackSerivcePath) private readonly aiBackService: IAIBackService; + private lastMessageSummary: string = ''; + private messageCountSinceLastSummary: number = 0; + private readonly SUMMARY_UPDATE_THRESHOLD = 10; // 每10条消息更新一次摘要 + @Autowired(TerminalDetectionPromptManager) terminalDetectionPromptManager: TerminalDetectionPromptManager; @@ -220,15 +224,6 @@ export class AINativeContribution implements AINativeCoreContribution { ); } - private async getSummary( - messages: Array<{ - role: ChatMessageRole; - content: string; - }>, - ): Promise { - const latestUserMessage = messages.findLast((m) => m.role === ChatMessageRole.User); - } - registerChatFeature(registry: IChatFeatureRegistry): void { registry.registerWelcome( new MarkdownString(` @@ -253,9 +248,14 @@ export class AINativeContribution implements AINativeCoreContribution { content: string; }>, ) => { + // 如果消息数量未达到阈值且已有摘要,则返回上一次的摘要 + this.messageCountSinceLastSummary++; + if (this.messageCountSinceLastSummary < this.SUMMARY_UPDATE_THRESHOLD && this.lastMessageSummary) { + return this.lastMessageSummary; + } + // 裁剪最近的10条消息,避免过长的消息导致请求失败 const sliceMessages = messages.slice(-10); - // TODO: summarize the messages const prompt = `You are a helpful AI assistant. Summarize in ONE clear statement (12-15 words). Requirements: - Include what happened @@ -270,14 +270,17 @@ Good: "Instance network interfaces exceeded system limit"`; }); if (result.isCancel) { - return ''; + return this.lastMessageSummary || ''; } if (result.errorCode !== 0) { - return ''; + return this.lastMessageSummary || ''; } - return result.data || ''; + // 更新摘要和计数器 + this.lastMessageSummary = result.data || ''; + this.messageCountSinceLastSummary = 0; + return this.lastMessageSummary; }, }); From f95d604745e7a1dd1ac55e450f877336dbea7aa6 Mon Sep 17 00:00:00 2001 From: liuqian Date: Tue, 3 Jun 2025 15:10:34 +0800 Subject: [PATCH 4/4] chore: remove unused test case --- .../browser/extension-service/extension.service.test.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/extension/__tests__/browser/extension-service/extension.service.test.ts b/packages/extension/__tests__/browser/extension-service/extension.service.test.ts index a6dfa47b2e..12e2ba2318 100644 --- a/packages/extension/__tests__/browser/extension-service/extension.service.test.ts +++ b/packages/extension/__tests__/browser/extension-service/extension.service.test.ts @@ -99,11 +99,6 @@ describe('Extension service', () => { expect(exts).toEqual(MOCK_EXTENSIONS); }); - it('should return all mock extensions JSON', async () => { - const jsons = await extensionManagementService.getAllExtensionJson(); - expect(jsons).toEqual(MOCK_EXTENSIONS.map((e) => e.toJSON())); - }); - it('should return specified extension props', async () => { const extensionMetadata = await extensionManagementService.getExtensionProps(MOCK_EXTENSIONS[0].path, { readme: './README.md',