Skip to content
Merged
18 changes: 17 additions & 1 deletion packages/ai-native/src/browser/chat/chat.feature.registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -14,6 +20,8 @@ export class ChatFeatureRegistry extends Disposable implements IChatFeatureRegis
private slashCommandsHandlerMap: Map<string, IChatSlashCommandHandler> = new Map();
private imageUploadProvider: IImageUploadProvider | undefined;

private messageSummaryProvider?: IMessageSummaryProvider;

public registerImageUploadProvider(provider: IImageUploadProvider): void {
this.imageUploadProvider = provider;
}
Expand All @@ -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<void> = new Emitter<void>();
Expand Down
24 changes: 21 additions & 3 deletions packages/ai-native/src/browser/chat/chat.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,7 @@ export function DefaultChatViewHeader({
}) {
const aiChatService = useInjectable<ChatInternalService>(IChatInternalService);
const messageService = useInjectable<IMessageService>(IMessageService);
const chatFeatureRegistry = useInjectable<ChatFeatureRegistry>(ChatFeatureRegistryToken);

const [historyList, setHistoryList] = React.useState<IChatHistoryItem[]>([]);
const [currentTitle, setCurrentTitle] = React.useState<string>('');
Expand Down Expand Up @@ -947,9 +948,26 @@ 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)
: '';
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 {
setCurrentTitle(currentTitle);
}
});
} else {
setCurrentTitle(currentTitle);
}

setHistoryList(
aiChatService.getSessions().map((session) => {
const history = session.history;
Expand Down
12 changes: 12 additions & 0 deletions packages/ai-native/src/browser/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -134,6 +135,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: {
Expand Down Expand Up @@ -298,6 +301,15 @@ export interface IImageUploadProvider {
imageUpload(file: File): Promise<DataContent | URL>;
}

export interface IMessageSummaryProvider {
getMessageSummary(
messages: Array<{
role: ChatMessageRole;
content: string;
}>,
): Promise<string | undefined>;
}

export const AINativeCoreContribution = Symbol('AINativeCoreContribution');

export interface AINativeCoreContribution {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
AIBackSerivcePath,
CancelResponse,
CancellationToken,
ChatMessageRole,
ChatResponse,
ChatServiceToken,
ErrorResponse,
Expand All @@ -57,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;

Expand Down Expand Up @@ -236,6 +241,49 @@ export class AINativeContribution implements AINativeCoreContribution {
imageUpload: imageToBase64,
});

registry.registerMessageSummaryProvider({
getMessageSummary: async (
messages: Array<{
role: ChatMessageRole;
content: string;
}>,
) => {
// ๅฆ‚ๆžœๆถˆๆฏๆ•ฐ้‡ๆœช่พพๅˆฐ้˜ˆๅ€ผไธ”ๅทฒๆœ‰ๆ‘˜่ฆ๏ผŒๅˆ™่ฟ”ๅ›žไธŠไธ€ๆฌก็š„ๆ‘˜่ฆ
this.messageCountSinceLastSummary++;
if (this.messageCountSinceLastSummary < this.SUMMARY_UPDATE_THRESHOLD && this.lastMessageSummary) {
return this.lastMessageSummary;
}

// ่ฃๅ‰ชๆœ€่ฟ‘็š„10ๆกๆถˆๆฏ๏ผŒ้ฟๅ…่ฟ‡้•ฟ็š„ๆถˆๆฏๅฏผ่‡ด่ฏทๆฑ‚ๅคฑ่ดฅ
const sliceMessages = messages.slice(-10);
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 this.lastMessageSummary || '';
}

if (result.errorCode !== 0) {
return this.lastMessageSummary || '';
}

// ๆ›ดๆ–ฐๆ‘˜่ฆๅ’Œ่ฎกๆ•ฐๅ™จ
this.lastMessageSummary = result.data || '';
this.messageCountSinceLastSummary = 0;
return this.lastMessageSummary;
},
});

// registry.registerSlashCommand(
// {
// name: 'Explain',
Expand Down
Loading