@opensumi/ide-ai-native 3.8.2-next-1741622293.0 → 3.8.3-next-1741661270.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/ai-core.contribution.d.ts.map +1 -1
- package/lib/browser/ai-core.contribution.js +20 -0
- package/lib/browser/ai-core.contribution.js.map +1 -1
- package/lib/browser/chat/chat-manager.service.d.ts +2 -1
- package/lib/browser/chat/chat-manager.service.d.ts.map +1 -1
- package/lib/browser/chat/chat-manager.service.js +26 -7
- package/lib/browser/chat/chat-manager.service.js.map +1 -1
- package/lib/browser/chat/chat-model.d.ts +3 -3
- package/lib/browser/chat/chat-model.d.ts.map +1 -1
- package/lib/browser/chat/chat-model.js +22 -9
- package/lib/browser/chat/chat-model.js.map +1 -1
- package/lib/browser/chat/chat-proxy.service.d.ts +1 -0
- package/lib/browser/chat/chat-proxy.service.d.ts.map +1 -1
- package/lib/browser/chat/chat-proxy.service.js +2 -0
- package/lib/browser/chat/chat-proxy.service.js.map +1 -1
- package/lib/browser/chat/chat.view.d.ts.map +1 -1
- package/lib/browser/chat/chat.view.js +61 -5
- package/lib/browser/chat/chat.view.js.map +1 -1
- package/lib/browser/components/ApplyStatus.d.ts +7 -0
- package/lib/browser/components/ApplyStatus.d.ts.map +1 -0
- package/lib/browser/components/ApplyStatus.js +32 -0
- package/lib/browser/components/ApplyStatus.js.map +1 -0
- package/lib/browser/components/ChangeList.d.ts +17 -0
- package/lib/browser/components/ChangeList.d.ts.map +1 -0
- package/lib/browser/components/ChangeList.js +72 -0
- package/lib/browser/components/ChangeList.js.map +1 -0
- package/lib/browser/components/ChatToolRender.d.ts.map +1 -1
- package/lib/browser/components/ChatToolRender.js +18 -12
- package/lib/browser/components/ChatToolRender.js.map +1 -1
- package/lib/browser/components/ChatToolRender.module.less +27 -15
- package/lib/browser/components/change-list.module.less +126 -0
- package/lib/browser/components/chat-history.module.less +1 -1
- package/lib/browser/components/components.module.less +14 -0
- package/lib/browser/contrib/inline-completions/prompt/matcher.js +2 -2
- package/lib/browser/contrib/inline-completions/prompt/similarSnippets.d.ts +1 -1
- package/lib/browser/contrib/inline-completions/prompt/similarSnippets.js +2 -2
- package/lib/browser/contrib/intelligent-completions/view/default.d.ts.map +1 -1
- package/lib/browser/contrib/intelligent-completions/view/default.js.map +1 -1
- package/lib/browser/index.d.ts.map +1 -1
- package/lib/browser/index.js +4 -4
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/mcp/base-apply.service.d.ts +14 -6
- package/lib/browser/mcp/base-apply.service.d.ts.map +1 -1
- package/lib/browser/mcp/base-apply.service.js +80 -52
- package/lib/browser/mcp/base-apply.service.js.map +1 -1
- package/lib/browser/mcp/config/components/mcp-config.view.d.ts.map +1 -1
- package/lib/browser/mcp/config/components/mcp-config.view.js +12 -8
- package/lib/browser/mcp/config/components/mcp-config.view.js.map +1 -1
- package/lib/browser/mcp/config/mcp-config.contribution.js.map +1 -1
- package/lib/browser/mcp/mcp-server.feature.registry.js +1 -1
- package/lib/browser/mcp/mcp-server.feature.registry.js.map +1 -1
- package/lib/browser/mcp/tools/components/EditFile.js +3 -24
- package/lib/browser/mcp/tools/components/EditFile.js.map +1 -1
- package/lib/browser/mcp/tools/components/ExpandableFileList.js +3 -2
- package/lib/browser/mcp/tools/components/ExpandableFileList.js.map +1 -1
- package/lib/browser/mcp/tools/createNewFileWithText.d.ts.map +1 -1
- package/lib/browser/mcp/tools/createNewFileWithText.js +1 -0
- package/lib/browser/mcp/tools/createNewFileWithText.js.map +1 -1
- package/lib/browser/mcp/tools/editFile.d.ts.map +1 -1
- package/lib/browser/mcp/tools/editFile.js +1 -0
- package/lib/browser/mcp/tools/editFile.js.map +1 -1
- package/lib/browser/mcp/tools/fileSearch.d.ts.map +1 -1
- package/lib/browser/mcp/tools/fileSearch.js +1 -0
- package/lib/browser/mcp/tools/fileSearch.js.map +1 -1
- package/lib/browser/mcp/tools/getDiagnosticsByPath.d.ts.map +1 -1
- package/lib/browser/mcp/tools/getDiagnosticsByPath.js +2 -1
- package/lib/browser/mcp/tools/getDiagnosticsByPath.js.map +1 -1
- package/lib/browser/mcp/tools/getOpenEditorFileDiagnostics.d.ts.map +1 -1
- package/lib/browser/mcp/tools/getOpenEditorFileDiagnostics.js +2 -0
- package/lib/browser/mcp/tools/getOpenEditorFileDiagnostics.js.map +1 -1
- package/lib/browser/mcp/tools/grepSearch.d.ts.map +1 -1
- package/lib/browser/mcp/tools/grepSearch.js +1 -0
- package/lib/browser/mcp/tools/grepSearch.js.map +1 -1
- package/lib/browser/mcp/tools/listDir.d.ts.map +1 -1
- package/lib/browser/mcp/tools/listDir.js +1 -0
- package/lib/browser/mcp/tools/listDir.js.map +1 -1
- package/lib/browser/mcp/tools/readFile.d.ts.map +1 -1
- package/lib/browser/mcp/tools/readFile.js +1 -0
- package/lib/browser/mcp/tools/readFile.js.map +1 -1
- package/lib/browser/mcp/tools/runTerminalCmd.d.ts.map +1 -1
- package/lib/browser/mcp/tools/runTerminalCmd.js +1 -0
- package/lib/browser/mcp/tools/runTerminalCmd.js.map +1 -1
- package/lib/browser/model/msg-history-manager.d.ts +0 -2
- package/lib/browser/model/msg-history-manager.d.ts.map +1 -1
- package/lib/browser/model/msg-history-manager.js +1 -6
- package/lib/browser/model/msg-history-manager.js.map +1 -1
- package/lib/browser/preferences/schema.d.ts.map +1 -1
- package/lib/browser/preferences/schema.js +8 -0
- package/lib/browser/preferences/schema.js.map +1 -1
- package/lib/browser/types.d.ts +1 -0
- package/lib/browser/types.d.ts.map +1 -1
- package/lib/browser/widget/inline-diff/inline-diff-manager.d.ts.map +1 -1
- package/lib/browser/widget/inline-diff/inline-diff-manager.js +2 -2
- package/lib/browser/widget/inline-diff/inline-diff-manager.js.map +1 -1
- package/lib/browser/widget/inline-diff/inline-diff.controller.d.ts +1 -1
- package/lib/browser/widget/inline-diff/inline-diff.controller.d.ts.map +1 -1
- package/lib/browser/widget/inline-diff/inline-diff.controller.js.map +1 -1
- package/lib/browser/widget/inline-diff/inline-diff.service.d.ts +3 -2
- package/lib/browser/widget/inline-diff/inline-diff.service.d.ts.map +1 -1
- package/lib/browser/widget/inline-diff/inline-diff.service.js.map +1 -1
- package/lib/browser/widget/inline-stream-diff/inline-stream-diff.handler.d.ts +1 -1
- package/lib/browser/widget/inline-stream-diff/inline-stream-diff.handler.d.ts.map +1 -1
- package/lib/browser/widget/inline-stream-diff/inline-stream-diff.handler.js.map +1 -1
- package/lib/browser/widget/inline-stream-diff/live-preview.component.d.ts +0 -33
- package/lib/browser/widget/inline-stream-diff/live-preview.component.d.ts.map +1 -1
- package/lib/browser/widget/inline-stream-diff/live-preview.component.js +1 -6
- package/lib/browser/widget/inline-stream-diff/live-preview.component.js.map +1 -1
- package/lib/browser/widget/inline-stream-diff/live-preview.decoration.d.ts.map +1 -1
- package/lib/browser/widget/inline-stream-diff/live-preview.decoration.js +15 -14
- package/lib/browser/widget/inline-stream-diff/live-preview.decoration.js.map +1 -1
- package/lib/common/index.d.ts +7 -2
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js +3 -2
- package/lib/common/index.js.map +1 -1
- package/lib/common/prompts/context-prompt-provider.d.ts.map +1 -1
- package/lib/common/prompts/context-prompt-provider.js +2 -4
- package/lib/common/prompts/context-prompt-provider.js.map +1 -1
- package/lib/common/types.d.ts +33 -0
- package/lib/common/types.d.ts.map +1 -1
- package/lib/common/types.js +6 -1
- package/lib/common/types.js.map +1 -1
- package/package.json +23 -23
- package/src/browser/ai-core.contribution.ts +28 -0
- package/src/browser/chat/chat-manager.service.ts +53 -31
- package/src/browser/chat/chat-model.ts +22 -8
- package/src/browser/chat/chat-proxy.service.ts +2 -0
- package/src/browser/chat/chat.view.tsx +80 -9
- package/src/browser/components/ApplyStatus.tsx +44 -0
- package/src/browser/components/ChangeList.tsx +131 -0
- package/src/browser/components/ChatToolRender.module.less +27 -15
- package/src/browser/components/ChatToolRender.tsx +14 -12
- package/src/browser/components/change-list.module.less +126 -0
- package/src/browser/components/chat-history.module.less +1 -1
- package/src/browser/components/components.module.less +14 -0
- package/src/browser/contrib/inline-completions/prompt/matcher.ts +2 -2
- package/src/browser/contrib/inline-completions/prompt/similarSnippets.ts +2 -2
- package/src/browser/contrib/intelligent-completions/view/default.ts +1 -0
- package/src/browser/index.ts +5 -4
- package/src/browser/mcp/base-apply.service.ts +84 -62
- package/src/browser/mcp/config/components/mcp-config.view.tsx +4 -0
- package/src/browser/mcp/config/mcp-config.contribution.ts +1 -1
- package/src/browser/mcp/mcp-server.feature.registry.ts +1 -1
- package/src/browser/mcp/tools/components/EditFile.tsx +3 -37
- package/src/browser/mcp/tools/components/ExpandableFileList.tsx +3 -1
- package/src/browser/mcp/tools/createNewFileWithText.ts +1 -0
- package/src/browser/mcp/tools/editFile.ts +1 -0
- package/src/browser/mcp/tools/fileSearch.ts +1 -0
- package/src/browser/mcp/tools/getDiagnosticsByPath.ts +2 -1
- package/src/browser/mcp/tools/getOpenEditorFileDiagnostics.ts +2 -0
- package/src/browser/mcp/tools/grepSearch.ts +1 -0
- package/src/browser/mcp/tools/listDir.ts +1 -0
- package/src/browser/mcp/tools/readFile.ts +1 -0
- package/src/browser/mcp/tools/runTerminalCmd.ts +1 -0
- package/src/browser/model/msg-history-manager.ts +1 -8
- package/src/browser/preferences/schema.ts +8 -0
- package/src/browser/types.ts +1 -0
- package/src/browser/widget/inline-diff/inline-diff-manager.tsx +3 -2
- package/src/browser/widget/inline-diff/inline-diff.controller.ts +2 -1
- package/src/browser/widget/inline-diff/inline-diff.service.ts +3 -2
- package/src/browser/widget/inline-stream-diff/inline-stream-diff.handler.tsx +4 -4
- package/src/browser/widget/inline-stream-diff/live-preview.component.tsx +0 -34
- package/src/browser/widget/inline-stream-diff/live-preview.decoration.tsx +8 -9
- package/src/common/index.ts +9 -2
- package/src/common/prompts/context-prompt-provider.ts +2 -6
- package/src/common/types.ts +35 -0
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { Autowired, INJECTOR_TOKEN, Injectable, Injector } from '@opensumi/di';
|
|
2
|
+
import { PreferenceService } from '@opensumi/ide-core-browser';
|
|
2
3
|
import {
|
|
4
|
+
AINativeSettingSectionsId,
|
|
3
5
|
CancellationToken,
|
|
4
6
|
CancellationTokenSource,
|
|
5
7
|
Disposable,
|
|
6
8
|
DisposableMap,
|
|
7
9
|
Emitter,
|
|
8
10
|
IChatProgress,
|
|
11
|
+
IDisposable,
|
|
9
12
|
IStorage,
|
|
13
|
+
LRUCache,
|
|
10
14
|
STORAGE_NAMESPACE,
|
|
11
15
|
StorageProvider,
|
|
12
16
|
debounce,
|
|
13
|
-
formatLocalize,
|
|
14
17
|
} from '@opensumi/ide-core-common';
|
|
15
18
|
import { IHistoryChatMessage } from '@opensumi/ide-core-common/lib/types/ai-native';
|
|
16
19
|
|
|
@@ -38,9 +41,26 @@ interface ISessionModel {
|
|
|
38
41
|
|
|
39
42
|
const MAX_SESSION_COUNT = 20;
|
|
40
43
|
|
|
44
|
+
class DisposableLRUCache<K, V extends IDisposable = IDisposable> extends LRUCache<K, V> implements IDisposable {
|
|
45
|
+
disposeKey(key: K): void {
|
|
46
|
+
const disposable = this.get(key);
|
|
47
|
+
if (disposable) {
|
|
48
|
+
disposable.dispose();
|
|
49
|
+
}
|
|
50
|
+
this.delete(key);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
dispose(): void {
|
|
54
|
+
this.forEach((disposable) => {
|
|
55
|
+
disposable.dispose();
|
|
56
|
+
});
|
|
57
|
+
this.clear();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
41
61
|
@Injectable()
|
|
42
62
|
export class ChatManagerService extends Disposable {
|
|
43
|
-
#sessionModels = this.registerDispose(new
|
|
63
|
+
#sessionModels = this.registerDispose(new DisposableLRUCache<string, ChatModel>(MAX_SESSION_COUNT));
|
|
44
64
|
#pendingRequests = this.registerDispose(new DisposableMap<string, CancellationTokenSource>());
|
|
45
65
|
private storageInitEmitter = new Emitter<void>();
|
|
46
66
|
public onStorageInit = this.storageInitEmitter.event;
|
|
@@ -54,35 +74,39 @@ export class ChatManagerService extends Disposable {
|
|
|
54
74
|
@Autowired(StorageProvider)
|
|
55
75
|
private storageProvider: StorageProvider;
|
|
56
76
|
|
|
77
|
+
@Autowired(PreferenceService)
|
|
78
|
+
private preferenceService: PreferenceService;
|
|
79
|
+
|
|
57
80
|
private _chatStorage: IStorage;
|
|
58
81
|
|
|
59
82
|
protected fromJSON(data: ISessionModel[]) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
83
|
+
return data
|
|
84
|
+
.filter((item) => item.history.messages.length > 0)
|
|
85
|
+
.map((item) => {
|
|
86
|
+
const model = new ChatModel({
|
|
87
|
+
sessionId: item.sessionId,
|
|
88
|
+
history: new MsgHistoryManager(item.history),
|
|
89
|
+
});
|
|
90
|
+
const requests = item.requests.map(
|
|
91
|
+
(request) =>
|
|
92
|
+
new ChatRequestModel(
|
|
93
|
+
request.requestId,
|
|
94
|
+
model,
|
|
95
|
+
request.message,
|
|
96
|
+
new ChatResponseModel(request.requestId, model, request.message.agentId, {
|
|
97
|
+
responseContents: request.response.responseContents,
|
|
98
|
+
isComplete: true,
|
|
99
|
+
responseText: request.response.responseText,
|
|
100
|
+
responseParts: request.response.responseParts,
|
|
101
|
+
errorDetails: request.response.errorDetails,
|
|
102
|
+
followups: request.response.followups,
|
|
103
|
+
isCanceled: request.response.isCanceled,
|
|
104
|
+
}),
|
|
105
|
+
),
|
|
106
|
+
);
|
|
107
|
+
model.restoreRequests(requests);
|
|
108
|
+
return model;
|
|
65
109
|
});
|
|
66
|
-
const requests = item.requests.map(
|
|
67
|
-
(request) =>
|
|
68
|
-
new ChatRequestModel(
|
|
69
|
-
request.requestId,
|
|
70
|
-
model,
|
|
71
|
-
request.message,
|
|
72
|
-
new ChatResponseModel(request.requestId, model, request.message.agentId, {
|
|
73
|
-
responseContents: request.response.responseContents,
|
|
74
|
-
isComplete: true,
|
|
75
|
-
responseText: request.response.responseText,
|
|
76
|
-
responseParts: request.response.responseParts,
|
|
77
|
-
errorDetails: request.response.errorDetails,
|
|
78
|
-
followups: request.response.followups,
|
|
79
|
-
isCanceled: request.response.isCanceled,
|
|
80
|
-
}),
|
|
81
|
-
),
|
|
82
|
-
);
|
|
83
|
-
model.restoreRequests(requests);
|
|
84
|
-
return model;
|
|
85
|
-
});
|
|
86
110
|
}
|
|
87
111
|
|
|
88
112
|
constructor() {
|
|
@@ -105,9 +129,6 @@ export class ChatManagerService extends Disposable {
|
|
|
105
129
|
}
|
|
106
130
|
|
|
107
131
|
startSession() {
|
|
108
|
-
if (this.#sessionModels.size >= MAX_SESSION_COUNT) {
|
|
109
|
-
throw new Error(formatLocalize('aiNative.chat.session.max', MAX_SESSION_COUNT.toString()));
|
|
110
|
-
}
|
|
111
132
|
const model = new ChatModel();
|
|
112
133
|
this.#sessionModels.set(model.sessionId, model);
|
|
113
134
|
this.listenSession(model);
|
|
@@ -155,7 +176,8 @@ export class ChatManagerService extends Disposable {
|
|
|
155
176
|
request.response.cancel();
|
|
156
177
|
});
|
|
157
178
|
|
|
158
|
-
const
|
|
179
|
+
const contextWindow = this.preferenceService.get<number>(AINativeSettingSectionsId.ContextWindow);
|
|
180
|
+
const history = model.getMessageHistory(contextWindow);
|
|
159
181
|
|
|
160
182
|
try {
|
|
161
183
|
const progressCallback = (progress: IChatProgress) => {
|
|
@@ -276,15 +276,12 @@ export class ChatRequestModel implements IChatRequestModel {
|
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
export class ChatModel extends Disposable implements IChatModel {
|
|
279
|
-
private
|
|
279
|
+
private requestIdPool = 0;
|
|
280
280
|
|
|
281
|
-
constructor(initParams?: { sessionId?: string; history?: MsgHistoryManager
|
|
281
|
+
constructor(initParams?: { sessionId?: string; history?: MsgHistoryManager }) {
|
|
282
282
|
super();
|
|
283
283
|
this.#sessionId = initParams?.sessionId ?? uuid();
|
|
284
284
|
this.history = initParams?.history ?? new MsgHistoryManager();
|
|
285
|
-
if (initParams?.requests) {
|
|
286
|
-
this.#requests = new Map(initParams.requests.map((r) => [r.requestId, r]));
|
|
287
|
-
}
|
|
288
285
|
}
|
|
289
286
|
|
|
290
287
|
#sessionId: string;
|
|
@@ -299,11 +296,18 @@ export class ChatModel extends Disposable implements IChatModel {
|
|
|
299
296
|
|
|
300
297
|
restoreRequests(requests: ChatRequestModel[]): void {
|
|
301
298
|
this.#requests = new Map(requests.map((r) => [r.requestId, r]));
|
|
299
|
+
this.requestIdPool = requests.length;
|
|
302
300
|
}
|
|
303
301
|
|
|
304
302
|
readonly history: MsgHistoryManager;
|
|
305
303
|
|
|
306
|
-
|
|
304
|
+
#slicedMessageCount = 0;
|
|
305
|
+
|
|
306
|
+
public get slicedMessageCount() {
|
|
307
|
+
return this.#slicedMessageCount;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
getMessageHistory(contextWindow?: number) {
|
|
307
311
|
const history: CoreMessage[] = [];
|
|
308
312
|
for (const request of this.requests) {
|
|
309
313
|
if (!request.response.isComplete) {
|
|
@@ -352,13 +356,23 @@ export class ChatModel extends Disposable implements IChatModel {
|
|
|
352
356
|
}
|
|
353
357
|
}
|
|
354
358
|
}
|
|
355
|
-
|
|
359
|
+
if (contextWindow) {
|
|
360
|
+
while (this.#slicedMessageCount < history.length) {
|
|
361
|
+
// 简单的使用 JSON.stringify 计算 token 数量
|
|
362
|
+
const tokenCount = JSON.stringify(history.slice(this.#slicedMessageCount)).length / 3;
|
|
363
|
+
if (tokenCount <= contextWindow) {
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
this.#slicedMessageCount++;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return history.slice(this.#slicedMessageCount);
|
|
356
370
|
}
|
|
357
371
|
|
|
358
372
|
addRequest(message: IChatRequestMessage): ChatRequestModel {
|
|
359
373
|
const msg = message;
|
|
360
374
|
|
|
361
|
-
const requestId = `${this.sessionId}_request_${
|
|
375
|
+
const requestId = `${this.sessionId}_request_${this.requestIdPool++}`;
|
|
362
376
|
const response = new ChatResponseModel(requestId, this, msg.agentId);
|
|
363
377
|
const request = new ChatRequestModel(requestId, this, msg, response);
|
|
364
378
|
|
|
@@ -92,6 +92,7 @@ export class ChatProxyService extends Disposable {
|
|
|
92
92
|
apiKey = this.preferenceService.get<string>(AINativeSettingSectionsId.OpenaiApiKey, '');
|
|
93
93
|
baseURL = this.preferenceService.get<string>(AINativeSettingSectionsId.OpenaiBaseURL, '');
|
|
94
94
|
}
|
|
95
|
+
const maxTokens = this.preferenceService.get<number>(AINativeSettingSectionsId.MaxTokens);
|
|
95
96
|
const agent = this.chatAgentService.getAgent(ChatProxyService.AGENT_ID);
|
|
96
97
|
return {
|
|
97
98
|
clientId: this.applicationService.clientId,
|
|
@@ -99,6 +100,7 @@ export class ChatProxyService extends Disposable {
|
|
|
99
100
|
modelId,
|
|
100
101
|
apiKey,
|
|
101
102
|
baseURL,
|
|
103
|
+
maxTokens,
|
|
102
104
|
system: agent?.metadata.systemPrompt,
|
|
103
105
|
};
|
|
104
106
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { MessageList } from 'react-chat-elements';
|
|
3
3
|
|
|
4
|
-
import { getIcon, useInjectable, useUpdateOnEvent } from '@opensumi/ide-core-browser';
|
|
4
|
+
import { AppConfig, getIcon, useInjectable, useUpdateOnEvent } from '@opensumi/ide-core-browser';
|
|
5
5
|
import { Popover, PopoverPosition } from '@opensumi/ide-core-browser/lib/components';
|
|
6
6
|
import { EnhanceIcon } from '@opensumi/ide-core-browser/lib/components/ai-native';
|
|
7
7
|
import {
|
|
@@ -19,15 +19,20 @@ import {
|
|
|
19
19
|
IAIReporter,
|
|
20
20
|
IChatComponent,
|
|
21
21
|
IChatContent,
|
|
22
|
+
URI,
|
|
22
23
|
formatLocalize,
|
|
23
24
|
localize,
|
|
25
|
+
path,
|
|
24
26
|
uuid,
|
|
25
27
|
} from '@opensumi/ide-core-common';
|
|
28
|
+
import { WorkbenchEditorService } from '@opensumi/ide-editor';
|
|
26
29
|
import { IMainLayoutService } from '@opensumi/ide-main-layout';
|
|
27
30
|
import { IMessageService } from '@opensumi/ide-overlay';
|
|
28
31
|
|
|
29
32
|
import 'react-chat-elements/dist/main.css';
|
|
30
33
|
import { AI_CHAT_VIEW_ID, IChatAgentService, IChatInternalService, IChatMessageStructure } from '../../common';
|
|
34
|
+
import { CodeBlockData } from '../../common/types';
|
|
35
|
+
import { FileChange, FileListDisplay } from '../components/ChangeList';
|
|
31
36
|
import { ChatContext } from '../components/ChatContext';
|
|
32
37
|
import { CodeBlockWrapperInput } from '../components/ChatEditor';
|
|
33
38
|
import ChatHistory, { IChatHistoryItem } from '../components/ChatHistory';
|
|
@@ -37,6 +42,7 @@ import { ChatNotify, ChatReply } from '../components/ChatReply';
|
|
|
37
42
|
import { SlashCustomRender } from '../components/SlashCustomRender';
|
|
38
43
|
import { MessageData, createMessageByAI, createMessageByUser } from '../components/utils';
|
|
39
44
|
import { WelcomeMessage } from '../components/WelcomeMsg';
|
|
45
|
+
import { BaseApplyService } from '../mcp/base-apply.service';
|
|
40
46
|
import { ChatViewHeaderRender, IMCPServerRegistry, TSlashCommandCustomRender, TokenMCPServerRegistry } from '../types';
|
|
41
47
|
|
|
42
48
|
import { ChatRequestModel, ChatSlashCommandItemModel } from './chat-model';
|
|
@@ -56,6 +62,41 @@ interface TDispatchAction {
|
|
|
56
62
|
|
|
57
63
|
const MAX_TITLE_LENGTH = 100;
|
|
58
64
|
|
|
65
|
+
const getFileChanges = (codeBlocks: CodeBlockData[]) =>
|
|
66
|
+
codeBlocks
|
|
67
|
+
.map((block) => {
|
|
68
|
+
const rangesFromDiffHunk =
|
|
69
|
+
block.applyResult?.diff.split('\n').reduce(
|
|
70
|
+
([del, add], line) => {
|
|
71
|
+
if (line.startsWith('-')) {
|
|
72
|
+
del += 1;
|
|
73
|
+
} else if (line.startsWith('+')) {
|
|
74
|
+
add += 1;
|
|
75
|
+
}
|
|
76
|
+
return [del, add];
|
|
77
|
+
},
|
|
78
|
+
[0, 0],
|
|
79
|
+
) || [];
|
|
80
|
+
return {
|
|
81
|
+
path: block.relativePath,
|
|
82
|
+
additions: rangesFromDiffHunk[1],
|
|
83
|
+
deletions: rangesFromDiffHunk[0],
|
|
84
|
+
status: block.status,
|
|
85
|
+
};
|
|
86
|
+
})
|
|
87
|
+
.reduce((acc, curr) => {
|
|
88
|
+
const existingFile = acc.find((file) => file.path === curr.path);
|
|
89
|
+
if (existingFile) {
|
|
90
|
+
existingFile.additions += curr.additions;
|
|
91
|
+
existingFile.deletions += curr.deletions;
|
|
92
|
+
// 使用最新的状态
|
|
93
|
+
existingFile.status = curr.status;
|
|
94
|
+
} else {
|
|
95
|
+
acc.push(curr);
|
|
96
|
+
}
|
|
97
|
+
return acc;
|
|
98
|
+
}, [] as FileChange[]);
|
|
99
|
+
|
|
59
100
|
export const AIChatView = () => {
|
|
60
101
|
const aiChatService = useInjectable<ChatInternalService>(IChatInternalService);
|
|
61
102
|
const chatApiService = useInjectable<ChatService>(ChatServiceToken);
|
|
@@ -70,9 +111,13 @@ export const AIChatView = () => {
|
|
|
70
111
|
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
71
112
|
const autoScroll = React.useRef<boolean>(true);
|
|
72
113
|
const chatInputRef = React.useRef<{ setInputValue: (v: string) => void } | null>(null);
|
|
73
|
-
|
|
114
|
+
const editorService = useInjectable<WorkbenchEditorService>(WorkbenchEditorService);
|
|
115
|
+
const appConfig = useInjectable<AppConfig>(AppConfig);
|
|
116
|
+
const applyService = useInjectable<BaseApplyService>(BaseApplyService);
|
|
74
117
|
const [shortcutCommands, setShortcutCommands] = React.useState<ChatSlashCommandItemModel[]>([]);
|
|
75
118
|
|
|
119
|
+
const [changeList, setChangeList] = React.useState<FileChange[]>(getFileChanges(applyService.getSessionCodeBlocks()));
|
|
120
|
+
|
|
76
121
|
const [messageListData, dispatchMessage] = React.useReducer((state: MessageData[], action: TDispatchAction) => {
|
|
77
122
|
switch (action.type) {
|
|
78
123
|
case 'add':
|
|
@@ -92,6 +137,18 @@ export const AIChatView = () => {
|
|
|
92
137
|
const [command, setCommand] = React.useState('');
|
|
93
138
|
const [theme, setTheme] = React.useState<string | null>(null);
|
|
94
139
|
|
|
140
|
+
React.useEffect(() => {
|
|
141
|
+
const disposer = new Disposable();
|
|
142
|
+
const doUpdate = () => {
|
|
143
|
+
const fileChanges = getFileChanges(applyService.getSessionCodeBlocks());
|
|
144
|
+
setChangeList(fileChanges);
|
|
145
|
+
};
|
|
146
|
+
disposer.addDispose(aiChatService.onChangeSession(doUpdate));
|
|
147
|
+
// TODO: 全量获取性能不好
|
|
148
|
+
disposer.addDispose(applyService.onCodeBlockUpdate(doUpdate));
|
|
149
|
+
return () => disposer.dispose();
|
|
150
|
+
}, []);
|
|
151
|
+
|
|
95
152
|
React.useEffect(() => {
|
|
96
153
|
const featureSlashCommands = chatFeatureRegistry.getAllShortcutSlashCommand();
|
|
97
154
|
|
|
@@ -691,10 +748,13 @@ export const AIChatView = () => {
|
|
|
691
748
|
dataSource={messageListData}
|
|
692
749
|
/>
|
|
693
750
|
</div>
|
|
694
|
-
{
|
|
751
|
+
{aiChatService.sessionModel.slicedMessageCount ? (
|
|
695
752
|
<div className={styles.chat_tips_text}>
|
|
696
753
|
<div className={styles.chat_tips_container}>
|
|
697
|
-
{formatLocalize(
|
|
754
|
+
{formatLocalize(
|
|
755
|
+
'aiNative.chat.ai.assistant.limit.message',
|
|
756
|
+
aiChatService.sessionModel.slicedMessageCount,
|
|
757
|
+
)}
|
|
698
758
|
</div>
|
|
699
759
|
</div>
|
|
700
760
|
) : null}
|
|
@@ -715,6 +775,20 @@ export const AIChatView = () => {
|
|
|
715
775
|
))}
|
|
716
776
|
</div>
|
|
717
777
|
</div>
|
|
778
|
+
{changeList.length > 0 && (
|
|
779
|
+
<FileListDisplay
|
|
780
|
+
files={changeList}
|
|
781
|
+
onFileClick={(filePath) => {
|
|
782
|
+
editorService.open(URI.file(path.join(appConfig.workspaceDir, filePath)));
|
|
783
|
+
}}
|
|
784
|
+
onRejectAll={() => {
|
|
785
|
+
applyService.processAll('reject');
|
|
786
|
+
}}
|
|
787
|
+
onAcceptAll={() => {
|
|
788
|
+
applyService.processAll('accept');
|
|
789
|
+
}}
|
|
790
|
+
/>
|
|
791
|
+
)}
|
|
718
792
|
<ChatInputWrapperRender
|
|
719
793
|
onSend={(value, agentId, command) =>
|
|
720
794
|
handleSend({
|
|
@@ -782,11 +856,8 @@ export function DefaultChatViewHeader({
|
|
|
782
856
|
React.useEffect(() => {
|
|
783
857
|
const getHistoryList = () => {
|
|
784
858
|
const currentMessages = aiChatService.sessionModel.history.getMessages();
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
? currentMessages[currentMessages.length - 1].content.slice(0, MAX_TITLE_LENGTH)
|
|
788
|
-
: '',
|
|
789
|
-
);
|
|
859
|
+
const latestUserMessage = currentMessages.findLast((m) => m.role === ChatMessageRole.User);
|
|
860
|
+
setCurrentTitle(latestUserMessage ? latestUserMessage.content.slice(0, MAX_TITLE_LENGTH) : '');
|
|
790
861
|
setHistoryList(
|
|
791
862
|
aiChatService.getSessions().map((session) => {
|
|
792
863
|
const history = session.history;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { Icon, Loading, Popover } from '@opensumi/ide-components';
|
|
4
|
+
|
|
5
|
+
import { CodeBlockStatus } from '../../common/types';
|
|
6
|
+
|
|
7
|
+
import styles from './components.module.less';
|
|
8
|
+
|
|
9
|
+
export const ApplyStatus = ({ status, error }: { status: CodeBlockStatus; error?: string }) => {
|
|
10
|
+
const renderStatus = () => {
|
|
11
|
+
switch (status) {
|
|
12
|
+
case 'generating':
|
|
13
|
+
return <Loading />;
|
|
14
|
+
case 'pending':
|
|
15
|
+
return (
|
|
16
|
+
<Popover title='Pending' id={'edit-file-tool-status-pending'}>
|
|
17
|
+
<Icon iconClass='codicon codicon-circle-large' />
|
|
18
|
+
</Popover>
|
|
19
|
+
);
|
|
20
|
+
case 'success':
|
|
21
|
+
return (
|
|
22
|
+
<Popover title='Success' id={'edit-file-tool-status-success'}>
|
|
23
|
+
<Icon iconClass='codicon codicon-pass' />
|
|
24
|
+
</Popover>
|
|
25
|
+
);
|
|
26
|
+
case 'failed':
|
|
27
|
+
return (
|
|
28
|
+
<Popover title={`Failed (${error || 'Unknown error'})`} id={'edit-file-tool-status-failed'}>
|
|
29
|
+
<Icon iconClass='codicon codicon-error' />
|
|
30
|
+
</Popover>
|
|
31
|
+
);
|
|
32
|
+
case 'cancelled':
|
|
33
|
+
return (
|
|
34
|
+
<Popover title='Cancelled' id={'edit-file-tool-status-cancelled'}>
|
|
35
|
+
<Icon iconClass='codicon codicon-circle-slash' style={{ color: 'var(--input-placeholderForeground)' }} />
|
|
36
|
+
</Popover>
|
|
37
|
+
);
|
|
38
|
+
default:
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return <span className={styles.status}>{renderStatus()}</span>;
|
|
44
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React, { useMemo, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Button, Icon } from '@opensumi/ide-components';
|
|
4
|
+
import { LabelService, URI, useInjectable } from '@opensumi/ide-core-browser';
|
|
5
|
+
|
|
6
|
+
import { CodeBlockStatus } from '../../common/types';
|
|
7
|
+
|
|
8
|
+
import { ApplyStatus } from './ApplyStatus';
|
|
9
|
+
import styles from './change-list.module.less';
|
|
10
|
+
|
|
11
|
+
export interface FileChange {
|
|
12
|
+
path: string;
|
|
13
|
+
additions: number;
|
|
14
|
+
deletions: number;
|
|
15
|
+
status: CodeBlockStatus;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface FileListDisplayProps {
|
|
19
|
+
files: FileChange[];
|
|
20
|
+
onFileClick: (path: string) => void;
|
|
21
|
+
onRejectAll: () => void;
|
|
22
|
+
onAcceptAll: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const FileListDisplay: React.FC<FileListDisplayProps> = (props) => {
|
|
26
|
+
const { files, onFileClick, onRejectAll, onAcceptAll } = props;
|
|
27
|
+
const [isExpanded, setIsExpanded] = useState(true);
|
|
28
|
+
const labelService = useInjectable<LabelService>(LabelService);
|
|
29
|
+
const fileIcons = useMemo(
|
|
30
|
+
() =>
|
|
31
|
+
files.map((file) => {
|
|
32
|
+
const uri = URI.parse(file.path);
|
|
33
|
+
const iconClass = labelService.getIcon(uri);
|
|
34
|
+
return <span className={iconClass}></span>;
|
|
35
|
+
}),
|
|
36
|
+
[files],
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const totalFiles = files.length;
|
|
40
|
+
const totalChanges = files.reduce(
|
|
41
|
+
(acc, file) => ({
|
|
42
|
+
additions: acc.additions + file.additions,
|
|
43
|
+
deletions: acc.deletions + file.deletions,
|
|
44
|
+
}),
|
|
45
|
+
{ additions: 0, deletions: 0 },
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const handleToggle = () => {
|
|
49
|
+
setIsExpanded(!isExpanded);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const renderChangeStats = (additions: number, deletions: number) => {
|
|
53
|
+
const parts: React.ReactNode[] = [];
|
|
54
|
+
|
|
55
|
+
if (additions > 0) {
|
|
56
|
+
parts.push(
|
|
57
|
+
<span key='add' className={styles.additions}>
|
|
58
|
+
+{additions}
|
|
59
|
+
</span>,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (deletions > 0) {
|
|
64
|
+
if (parts.length > 0) {
|
|
65
|
+
parts.push(' ');
|
|
66
|
+
}
|
|
67
|
+
parts.push(
|
|
68
|
+
<span key='del' className={styles.deletions}>
|
|
69
|
+
-{deletions}
|
|
70
|
+
</span>,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (parts.length === 0) {
|
|
75
|
+
parts.push(
|
|
76
|
+
<span key='no-change' className={styles.noChange}>
|
|
77
|
+
No changes
|
|
78
|
+
</span>,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return parts;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className={styles.container}>
|
|
87
|
+
<div className={styles.header} onClick={handleToggle}>
|
|
88
|
+
<span className={styles.title}>
|
|
89
|
+
<button className={styles.toggleButton}>{isExpanded ? <Icon icon='down' /> : <Icon icon='right' />}</button>
|
|
90
|
+
Changed Files ({totalFiles}) {renderChangeStats(totalChanges.additions, totalChanges.deletions)}
|
|
91
|
+
</span>
|
|
92
|
+
{files.some((file) => file.status === 'pending') && (
|
|
93
|
+
<div className={styles.actions}>
|
|
94
|
+
<Button
|
|
95
|
+
type='link'
|
|
96
|
+
size='small'
|
|
97
|
+
onClick={(e) => {
|
|
98
|
+
e.stopPropagation();
|
|
99
|
+
onRejectAll();
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
Reject
|
|
103
|
+
</Button>
|
|
104
|
+
<Button
|
|
105
|
+
size='small'
|
|
106
|
+
onClick={(e) => {
|
|
107
|
+
e.stopPropagation();
|
|
108
|
+
onAcceptAll();
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
Accept
|
|
112
|
+
</Button>
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<ul className={`${styles.fileList} ${!isExpanded ? styles.collapsed : ''}`}>
|
|
118
|
+
{files.map((file, index) => (
|
|
119
|
+
<li key={index} className={styles.fileItem} onClick={() => onFileClick(file.path)}>
|
|
120
|
+
<span className={styles.fileIcon}>{fileIcons[index] || '📄'}</span>
|
|
121
|
+
<div className={styles.fileInfo}>
|
|
122
|
+
<div className={styles.filePath}>{file.path}</div>
|
|
123
|
+
<div className={styles.fileStats}>{renderChangeStats(file.additions, file.deletions)}</div>
|
|
124
|
+
<ApplyStatus status={file.status} />
|
|
125
|
+
</div>
|
|
126
|
+
</li>
|
|
127
|
+
))}
|
|
128
|
+
</ul>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
.
|
|
1
|
+
.chat_tool_render {
|
|
2
2
|
margin: 8px 0;
|
|
3
3
|
border: 1px solid var(--design-borderColor);
|
|
4
4
|
border-radius: 6px;
|
|
5
5
|
overflow: hidden;
|
|
6
6
|
|
|
7
|
-
.
|
|
7
|
+
.tool_header {
|
|
8
8
|
display: flex;
|
|
9
9
|
align-items: center;
|
|
10
10
|
justify-content: space-between;
|
|
11
|
-
padding:
|
|
11
|
+
padding: 4px;
|
|
12
12
|
background-color: var(--design-block-background);
|
|
13
13
|
cursor: pointer;
|
|
14
14
|
user-select: none;
|
|
@@ -18,14 +18,25 @@
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
.
|
|
21
|
+
.tool_name {
|
|
22
|
+
font-size: 12px;
|
|
22
23
|
display: flex;
|
|
23
24
|
align-items: center;
|
|
24
25
|
font-weight: 500;
|
|
25
26
|
color: var(--design-text-foreground);
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
.
|
|
29
|
+
.tool_icon {
|
|
30
|
+
font-size: 12px !important;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.tool_label {
|
|
34
|
+
margin-left: 5px;
|
|
35
|
+
font-size: 11px;
|
|
36
|
+
color: var(--descriptionForeground);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.expand_icon {
|
|
29
40
|
display: inline-block;
|
|
30
41
|
margin-right: 8px;
|
|
31
42
|
transition: transform 0.2s;
|
|
@@ -36,29 +47,29 @@
|
|
|
36
47
|
}
|
|
37
48
|
}
|
|
38
49
|
|
|
39
|
-
.
|
|
50
|
+
.tool_state {
|
|
40
51
|
display: flex;
|
|
41
52
|
align-items: center;
|
|
42
53
|
font-size: 12px;
|
|
43
54
|
color: var(--design-text-placeholderForeground);
|
|
44
55
|
}
|
|
45
56
|
|
|
46
|
-
.
|
|
57
|
+
.state_icon {
|
|
47
58
|
display: flex;
|
|
48
59
|
align-items: center;
|
|
49
60
|
margin-right: 6px;
|
|
50
61
|
}
|
|
51
62
|
|
|
52
|
-
.
|
|
63
|
+
.loading_icon {
|
|
53
64
|
width: 12px;
|
|
54
65
|
height: 12px;
|
|
55
66
|
}
|
|
56
67
|
|
|
57
|
-
.
|
|
68
|
+
.state_label {
|
|
58
69
|
margin-left: 4px;
|
|
59
70
|
}
|
|
60
71
|
|
|
61
|
-
.
|
|
72
|
+
.tool_content {
|
|
62
73
|
max-height: 0;
|
|
63
74
|
overflow: hidden;
|
|
64
75
|
transition: max-height 0.3s ease-out;
|
|
@@ -69,18 +80,19 @@
|
|
|
69
80
|
}
|
|
70
81
|
}
|
|
71
82
|
|
|
72
|
-
.
|
|
73
|
-
.
|
|
74
|
-
|
|
83
|
+
.tool_arguments,
|
|
84
|
+
.tool_result {
|
|
85
|
+
font-size: 11px;
|
|
86
|
+
padding: 5px;
|
|
75
87
|
}
|
|
76
88
|
|
|
77
|
-
.
|
|
89
|
+
.section_label {
|
|
78
90
|
font-size: 12px;
|
|
79
91
|
color: var(--design-text-placeholderForeground);
|
|
80
92
|
margin-bottom: 8px;
|
|
81
93
|
}
|
|
82
94
|
|
|
83
|
-
.
|
|
95
|
+
.tool_result {
|
|
84
96
|
border-top: 1px solid var(--design-borderColor);
|
|
85
97
|
}
|
|
86
98
|
}
|