@opensumi/ide-ai-native 3.8.1-next-1740571693.0 → 3.8.1-next-1740625266.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 +4 -1
- package/lib/browser/ai-core.contribution.d.ts.map +1 -1
- package/lib/browser/ai-core.contribution.js +20 -1
- package/lib/browser/ai-core.contribution.js.map +1 -1
- package/lib/browser/chat/chat-manager.service.d.ts +1 -0
- package/lib/browser/chat/chat-manager.service.d.ts.map +1 -1
- package/lib/browser/chat/chat-manager.service.js +13 -0
- package/lib/browser/chat/chat-manager.service.js.map +1 -1
- package/lib/browser/chat/chat.internal.service.d.ts +1 -0
- package/lib/browser/chat/chat.internal.service.d.ts.map +1 -1
- package/lib/browser/chat/chat.internal.service.js +3 -0
- package/lib/browser/chat/chat.internal.service.js.map +1 -1
- package/lib/browser/components/ChatHistory.d.ts +0 -1
- package/lib/browser/components/ChatHistory.d.ts.map +1 -1
- package/lib/browser/components/ChatHistory.js +14 -14
- package/lib/browser/components/ChatHistory.js.map +1 -1
- package/lib/browser/mcp/base-apply.service.d.ts +31 -40
- package/lib/browser/mcp/base-apply.service.d.ts.map +1 -1
- package/lib/browser/mcp/base-apply.service.js +233 -167
- package/lib/browser/mcp/base-apply.service.js.map +1 -1
- package/lib/browser/mcp/tools/components/EditFile.d.ts.map +1 -1
- package/lib/browser/mcp/tools/components/EditFile.js +55 -41
- package/lib/browser/mcp/tools/components/EditFile.js.map +1 -1
- package/lib/browser/mcp/tools/components/index.module.less +22 -3
- package/lib/browser/mcp/tools/editFile.js +1 -1
- package/lib/browser/mcp/tools/editFile.js.map +1 -1
- package/lib/browser/mcp/tools/handlers/EditFile.d.ts +5 -1
- package/lib/browser/mcp/tools/handlers/EditFile.d.ts.map +1 -1
- package/lib/browser/mcp/tools/handlers/EditFile.js +4 -4
- package/lib/browser/mcp/tools/handlers/EditFile.js.map +1 -1
- package/lib/browser/model/msg-history-manager.d.ts +1 -0
- package/lib/browser/model/msg-history-manager.d.ts.map +1 -1
- package/lib/browser/model/msg-history-manager.js +12 -2
- package/lib/browser/model/msg-history-manager.js.map +1 -1
- package/lib/browser/types.d.ts +1 -1
- package/lib/browser/types.d.ts.map +1 -1
- package/lib/browser/widget/inline-diff/inline-diff-manager.d.ts +6 -0
- package/lib/browser/widget/inline-diff/inline-diff-manager.d.ts.map +1 -0
- package/lib/browser/widget/inline-diff/inline-diff-manager.js +27 -0
- package/lib/browser/widget/inline-diff/inline-diff-manager.js.map +1 -0
- package/lib/browser/widget/inline-diff/inline-diff-widget.module.less +12 -0
- package/lib/browser/widget/inline-stream-diff/inline-stream-diff.handler.d.ts +2 -0
- 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 +11 -4
- package/lib/browser/widget/inline-stream-diff/inline-stream-diff.handler.js.map +1 -1
- package/lib/common/types.d.ts +17 -0
- package/lib/common/types.d.ts.map +1 -1
- package/lib/common/types.js.map +1 -1
- package/lib/node/base-language-model.d.ts +1 -1
- package/lib/node/base-language-model.d.ts.map +1 -1
- package/lib/node/base-language-model.js +54 -3
- package/lib/node/base-language-model.js.map +1 -1
- package/package.json +23 -23
- package/src/browser/ai-core.contribution.ts +25 -1
- package/src/browser/chat/chat-manager.service.ts +12 -0
- package/src/browser/chat/chat.internal.service.ts +4 -0
- package/src/browser/components/ChatHistory.tsx +21 -15
- package/src/browser/mcp/base-apply.service.ts +266 -213
- package/src/browser/mcp/tools/components/EditFile.tsx +82 -60
- package/src/browser/mcp/tools/components/index.module.less +22 -3
- package/src/browser/mcp/tools/editFile.ts +2 -2
- package/src/browser/mcp/tools/handlers/EditFile.ts +4 -4
- package/src/browser/model/msg-history-manager.ts +12 -2
- package/src/browser/types.ts +1 -1
- package/src/browser/widget/inline-diff/inline-diff-manager.tsx +38 -0
- package/src/browser/widget/inline-diff/inline-diff-widget.module.less +12 -0
- package/src/browser/widget/inline-stream-diff/inline-stream-diff.handler.tsx +13 -4
- package/src/common/types.ts +20 -0
- package/src/node/base-language-model.ts +63 -1
- /package/lib/browser/components/{chat-history.css → chat-history.module.less} +0 -0
- /package/src/browser/components/{chat-history.css → chat-history.module.less} +0 -0
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { createPatch } from 'diff';
|
|
2
2
|
|
|
3
3
|
import { Autowired } from '@opensumi/di';
|
|
4
|
-
import { AppConfig,
|
|
4
|
+
import { AppConfig, IChatProgress, IMarker, MarkerSeverity, OnEvent, WithEventBus } from '@opensumi/ide-core-browser';
|
|
5
5
|
import { WorkbenchEditorService } from '@opensumi/ide-editor';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
EditorGroupCloseEvent,
|
|
8
|
+
EditorGroupOpenEvent,
|
|
9
|
+
RegisterEditorSideComponentEvent,
|
|
10
|
+
} from '@opensumi/ide-editor/lib/browser';
|
|
7
11
|
import { IMarkerService } from '@opensumi/ide-markers';
|
|
8
|
-
import { Position, Range, Selection, SelectionDirection } from '@opensumi/ide-monaco';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
12
|
+
import { ICodeEditor, Position, Range, Selection, SelectionDirection } from '@opensumi/ide-monaco';
|
|
13
|
+
import { Deferred, Emitter, URI, path } from '@opensumi/ide-utils';
|
|
14
|
+
import { SumiReadableStream } from '@opensumi/ide-utils/lib/stream';
|
|
11
15
|
|
|
12
16
|
import { IChatInternalService } from '../../common';
|
|
17
|
+
import { CodeBlockData, CodeBlockStatus } from '../../common/types';
|
|
13
18
|
import { ChatInternalService } from '../chat/chat.internal.service';
|
|
19
|
+
import { InlineChatController } from '../widget/inline-chat/inline-chat-controller';
|
|
14
20
|
import {
|
|
15
21
|
BaseInlineDiffPreviewer,
|
|
16
22
|
InlineDiffController,
|
|
@@ -41,6 +47,9 @@ export abstract class BaseApplyService extends WithEventBus {
|
|
|
41
47
|
@Autowired(IMarkerService)
|
|
42
48
|
private readonly markerService: IMarkerService;
|
|
43
49
|
|
|
50
|
+
private onCodeBlockUpdateEmitter = new Emitter<CodeBlockData>();
|
|
51
|
+
public onCodeBlockUpdate = this.onCodeBlockUpdateEmitter.event;
|
|
52
|
+
|
|
44
53
|
constructor() {
|
|
45
54
|
super();
|
|
46
55
|
this.addDispose(
|
|
@@ -52,23 +61,32 @@ export abstract class BaseApplyService extends WithEventBus {
|
|
|
52
61
|
this.chatInternalService.onRegenerateRequest(() => {
|
|
53
62
|
const messages = this.chatInternalService.sessionModel.history.getMessages();
|
|
54
63
|
const messageId = messages[messages.length - 1].id;
|
|
55
|
-
|
|
64
|
+
const codeBlockMap = this.getMessageCodeBlocks(messageId);
|
|
65
|
+
if (!codeBlockMap) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
Object.values(codeBlockMap).forEach((blockData) => {
|
|
69
|
+
this.cancelApply(blockData);
|
|
70
|
+
});
|
|
56
71
|
}),
|
|
57
72
|
);
|
|
58
73
|
}
|
|
59
74
|
|
|
60
|
-
|
|
75
|
+
private getMessageCodeBlocks(
|
|
76
|
+
messageId: string,
|
|
77
|
+
sessionId?: string,
|
|
78
|
+
): { [toolCallId: string]: CodeBlockData } | undefined {
|
|
79
|
+
sessionId = sessionId || this.chatInternalService.sessionModel.sessionId;
|
|
80
|
+
const sessionModel = this.chatInternalService.getSession(sessionId);
|
|
81
|
+
if (!sessionModel) {
|
|
82
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
83
|
+
}
|
|
84
|
+
const message = sessionModel.history.getMessageAdditional(messageId);
|
|
85
|
+
return message?.codeBlockMap;
|
|
86
|
+
}
|
|
61
87
|
|
|
62
88
|
private activePreviewer: BaseInlineDiffPreviewer<InlineStreamDiffHandler> | undefined;
|
|
63
89
|
|
|
64
|
-
private pendingApplyParams:
|
|
65
|
-
| {
|
|
66
|
-
relativePath: string;
|
|
67
|
-
newContent: string;
|
|
68
|
-
range?: Range;
|
|
69
|
-
}
|
|
70
|
-
| undefined;
|
|
71
|
-
|
|
72
90
|
@OnEvent(EditorGroupCloseEvent)
|
|
73
91
|
onEditorGroupClose(event: EditorGroupCloseEvent) {
|
|
74
92
|
if (this.activePreviewer?.getNode()?.uri.path.toString() === event.payload.resource.uri.path.toString()) {
|
|
@@ -77,187 +95,195 @@ export abstract class BaseApplyService extends WithEventBus {
|
|
|
77
95
|
}
|
|
78
96
|
}
|
|
79
97
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
98
|
+
@OnEvent(EditorGroupOpenEvent)
|
|
99
|
+
async onEditorGroupOpen(event: EditorGroupOpenEvent) {
|
|
100
|
+
if (!this.chatInternalService.sessionModel.history.getMessages().length) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const relativePath = path.relative(this.appConfig.workspaceDir, event.payload.resource.uri.path.toString());
|
|
104
|
+
const filePendingApplies = Object.values(
|
|
105
|
+
this.getMessageCodeBlocks(this.chatInternalService.sessionModel.history.lastMessageId!) || {},
|
|
106
|
+
).filter((block) => block.relativePath === relativePath && block.status === 'pending');
|
|
107
|
+
// TODO: 刷新后重新应用,事件无法恢复 & 恢复继续请求,需要改造成批量apply形式
|
|
108
|
+
// TODO: 暂时只支持 pending 串行的 apply,后续支持批量apply后统一accept
|
|
109
|
+
if (filePendingApplies.length > 0) {
|
|
110
|
+
this.renderApplyResult(filePendingApplies[0], filePendingApplies[0].updatedCode!);
|
|
86
111
|
}
|
|
87
|
-
const blockId = this.generateBlockId(relativeOrAbsolutePath, messageId);
|
|
88
|
-
return this.codeBlockMapObservable.get().get(blockId);
|
|
89
112
|
}
|
|
90
113
|
|
|
91
|
-
|
|
92
|
-
|
|
114
|
+
getUriPendingCodeBlock(uri: URI): CodeBlockData | undefined {
|
|
115
|
+
const messageId = this.chatInternalService.sessionModel.history.lastMessageId;
|
|
116
|
+
if (!messageId) {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
const codeBlockMap = this.getMessageCodeBlocks(messageId);
|
|
120
|
+
if (!codeBlockMap) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
return Object.values(codeBlockMap).find(
|
|
124
|
+
(block) =>
|
|
125
|
+
block.relativePath === path.relative(this.appConfig.workspaceDir, uri.path.toString()) &&
|
|
126
|
+
block.status === 'pending',
|
|
127
|
+
);
|
|
93
128
|
}
|
|
94
129
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
130
|
+
getCodeBlock(toolCallId: string, messageId?: string): CodeBlockData | undefined {
|
|
131
|
+
messageId = messageId || this.chatInternalService.sessionModel.history.lastMessageId;
|
|
132
|
+
if (!messageId) {
|
|
133
|
+
throw new Error('Message ID is required');
|
|
134
|
+
}
|
|
135
|
+
const codeBlockMap = this.getMessageCodeBlocks(messageId);
|
|
136
|
+
if (!codeBlockMap) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
return codeBlockMap[toolCallId];
|
|
101
140
|
}
|
|
102
141
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const blockId = this.generateBlockId(relativePath);
|
|
108
|
-
|
|
109
|
-
if (!this.codeBlockMapObservable.get().has(blockId)) {
|
|
110
|
-
this.codeBlockMapObservable.get().set(blockId, {
|
|
111
|
-
id: blockId,
|
|
112
|
-
content,
|
|
113
|
-
relativePath,
|
|
114
|
-
status: 'generating',
|
|
115
|
-
iterationCount: 0,
|
|
116
|
-
createdAt: Date.now(),
|
|
117
|
-
});
|
|
142
|
+
protected updateCodeBlock(codeBlock: CodeBlockData, messageId?: string) {
|
|
143
|
+
messageId = messageId || this.chatInternalService.sessionModel.history.lastMessageId;
|
|
144
|
+
if (!messageId) {
|
|
145
|
+
throw new Error('Message ID is required');
|
|
118
146
|
}
|
|
119
|
-
|
|
120
|
-
|
|
147
|
+
const codeBlockMap = this.getMessageCodeBlocks(messageId);
|
|
148
|
+
if (!codeBlockMap) {
|
|
149
|
+
throw new Error('Code block not found');
|
|
150
|
+
}
|
|
151
|
+
codeBlockMap[codeBlock.toolCallId] = codeBlock;
|
|
152
|
+
this.chatInternalService.sessionModel.history.setMessageAdditional(messageId, {
|
|
153
|
+
codeBlockMap,
|
|
154
|
+
});
|
|
155
|
+
this.onCodeBlockUpdateEmitter.fire(codeBlock);
|
|
121
156
|
}
|
|
122
157
|
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
158
|
+
registerCodeBlock(relativePath: string, content: string, toolCallId: string): CodeBlockData {
|
|
159
|
+
const lastMessageId = this.chatInternalService.sessionModel.history.lastMessageId!;
|
|
160
|
+
const savedCodeBlockMap = this.getMessageCodeBlocks(lastMessageId) || {};
|
|
161
|
+
const newBlock: CodeBlockData = {
|
|
162
|
+
codeEdit: content,
|
|
163
|
+
relativePath,
|
|
164
|
+
status: 'generating' as CodeBlockStatus,
|
|
165
|
+
iterationCount: 1,
|
|
166
|
+
version: 1,
|
|
167
|
+
createdAt: Date.now(),
|
|
168
|
+
toolCallId,
|
|
169
|
+
};
|
|
170
|
+
const samePathCodeBlocks = Object.values(savedCodeBlockMap).filter((block) => block.relativePath === relativePath);
|
|
171
|
+
if (samePathCodeBlocks.length > 0) {
|
|
172
|
+
newBlock.version = samePathCodeBlocks.length;
|
|
173
|
+
for (const block of samePathCodeBlocks.sort((a, b) => b.version - a.version)) {
|
|
174
|
+
// 如果连续的上一个同文件apply结果存在LintError,则iterationCount++
|
|
175
|
+
if (block.relativePath === relativePath && block.applyResult?.diagnosticInfos?.length) {
|
|
176
|
+
newBlock.iterationCount++;
|
|
177
|
+
} else {
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
127
181
|
}
|
|
182
|
+
savedCodeBlockMap[toolCallId] = newBlock;
|
|
183
|
+
this.chatInternalService.sessionModel.history.setMessageAdditional(lastMessageId, {
|
|
184
|
+
codeBlockMap: savedCodeBlockMap,
|
|
185
|
+
});
|
|
186
|
+
this.onCodeBlockUpdateEmitter.fire(newBlock);
|
|
187
|
+
return newBlock;
|
|
128
188
|
}
|
|
129
189
|
|
|
130
190
|
/**
|
|
131
191
|
* Apply changes of a code block
|
|
132
192
|
*/
|
|
133
|
-
async apply(
|
|
134
|
-
const blockData = this.getCodeBlock(relativePath);
|
|
135
|
-
if (!blockData) {
|
|
136
|
-
throw new Error('Code block not found');
|
|
137
|
-
}
|
|
193
|
+
async apply(codeBlock: CodeBlockData): Promise<CodeBlockData> {
|
|
138
194
|
try {
|
|
139
|
-
if (
|
|
140
|
-
throw new Error('
|
|
195
|
+
if (codeBlock.iterationCount > 3) {
|
|
196
|
+
throw new Error('Lint error max iteration count exceeded');
|
|
141
197
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
198
|
+
const fastApplyFileResult = await this.doApply(codeBlock);
|
|
199
|
+
if (!fastApplyFileResult.stream && !fastApplyFileResult.result) {
|
|
200
|
+
throw new Error('No apply content provided');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// trigger diffPreivewer & return expected diff result directly
|
|
204
|
+
const applyResult = await this.renderApplyResult(
|
|
205
|
+
codeBlock,
|
|
206
|
+
(fastApplyFileResult.result || fastApplyFileResult.stream)!,
|
|
207
|
+
fastApplyFileResult.range,
|
|
208
|
+
);
|
|
209
|
+
if (applyResult) {
|
|
210
|
+
// 用户实际接受的 apply 结果
|
|
211
|
+
codeBlock.applyResult = applyResult;
|
|
212
|
+
this.updateCodeBlock(codeBlock);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return codeBlock;
|
|
149
216
|
} catch (err) {
|
|
150
|
-
|
|
151
|
-
this.updateCodeBlock(
|
|
217
|
+
codeBlock.status = 'failed';
|
|
218
|
+
this.updateCodeBlock(codeBlock);
|
|
152
219
|
throw err;
|
|
153
220
|
}
|
|
154
221
|
}
|
|
155
222
|
|
|
156
|
-
async reRenderPendingApply() {
|
|
157
|
-
if (!this.pendingApplyParams) {
|
|
158
|
-
throw new Error('No pending apply params');
|
|
159
|
-
}
|
|
160
|
-
const result = await this.renderApplyResult(
|
|
161
|
-
this.pendingApplyParams.relativePath,
|
|
162
|
-
this.pendingApplyParams.newContent,
|
|
163
|
-
this.pendingApplyParams.range,
|
|
164
|
-
);
|
|
165
|
-
if (result) {
|
|
166
|
-
const blockData = this.getCodeBlock(this.pendingApplyParams.relativePath)!;
|
|
167
|
-
blockData.applyResult = result;
|
|
168
|
-
this.updateCodeBlock(blockData);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
223
|
async renderApplyResult(
|
|
173
|
-
|
|
174
|
-
|
|
224
|
+
codeBlock: CodeBlockData,
|
|
225
|
+
updatedContentOrStream: string | SumiReadableStream<IChatProgress>,
|
|
175
226
|
range?: Range,
|
|
176
227
|
): Promise<{ diff: string; diagnosticInfos: IMarker[] } | undefined> {
|
|
177
|
-
|
|
178
|
-
this.pendingApplyParams = {
|
|
179
|
-
relativePath,
|
|
180
|
-
newContent,
|
|
181
|
-
range,
|
|
182
|
-
};
|
|
183
|
-
const blockData = this.getCodeBlock(relativePath);
|
|
184
|
-
if (!blockData) {
|
|
185
|
-
throw new Error('Code block not found');
|
|
186
|
-
}
|
|
228
|
+
const { relativePath } = codeBlock;
|
|
187
229
|
const openResult = await this.editorService.open(URI.file(path.join(this.appConfig.workspaceDir, relativePath)));
|
|
188
230
|
if (!openResult) {
|
|
189
231
|
throw new Error('Failed to open editor');
|
|
190
232
|
}
|
|
191
233
|
const editor = openResult.group.codeEditor.monacoEditor;
|
|
192
234
|
const inlineDiffController = InlineDiffController.get(editor)!;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
// Create diff previewer
|
|
198
|
-
const previewer = inlineDiffController.createDiffPreviewer(
|
|
199
|
-
editor,
|
|
200
|
-
Selection.fromRange(range, SelectionDirection.LTR),
|
|
201
|
-
{
|
|
202
|
-
disposeWhenEditorClosed: true,
|
|
203
|
-
renderRemovedWidgetImmediately: true,
|
|
204
|
-
},
|
|
205
|
-
) as LiveInlineDiffPreviewer;
|
|
206
|
-
this.activePreviewer = previewer;
|
|
235
|
+
codeBlock.status = 'pending';
|
|
236
|
+
// 强刷展示 manager 视图
|
|
237
|
+
this.eventBus.fire(new RegisterEditorSideComponentEvent());
|
|
238
|
+
this.updateCodeBlock(codeBlock);
|
|
207
239
|
|
|
208
240
|
const fullOriginalContent = editor.getModel()!.getValue();
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
241
|
+
range = range || editor.getModel()?.getFullModelRange()!;
|
|
242
|
+
// const savedRangeContent = editor.getModel()!.getValueInRange(range);
|
|
243
|
+
|
|
244
|
+
if (typeof updatedContentOrStream === 'string') {
|
|
245
|
+
// Create diff previewer
|
|
246
|
+
const previewer = inlineDiffController.createDiffPreviewer(
|
|
247
|
+
editor,
|
|
248
|
+
Selection.fromRange(range, SelectionDirection.LTR),
|
|
249
|
+
{
|
|
250
|
+
disposeWhenEditorClosed: true,
|
|
251
|
+
renderRemovedWidgetImmediately: true,
|
|
252
|
+
},
|
|
253
|
+
) as LiveInlineDiffPreviewer;
|
|
254
|
+
// TODO: 支持多个diffPreviewer
|
|
255
|
+
this.activePreviewer = previewer;
|
|
256
|
+
codeBlock.updatedCode = updatedContentOrStream;
|
|
257
|
+
previewer.setValue(updatedContentOrStream);
|
|
214
258
|
} else {
|
|
215
|
-
|
|
259
|
+
const controller = new InlineChatController();
|
|
260
|
+
controller.mountReadable(updatedContentOrStream);
|
|
261
|
+
const inlineDiffHandler = InlineDiffController.get(editor)!;
|
|
262
|
+
|
|
263
|
+
this.activePreviewer = inlineDiffHandler.showPreviewerByStream(editor, {
|
|
264
|
+
crossSelection: Selection.fromRange(range, SelectionDirection.LTR),
|
|
265
|
+
chatResponse: controller,
|
|
266
|
+
previewerOptions: {
|
|
267
|
+
disposeWhenEditorClosed: true,
|
|
268
|
+
renderRemovedWidgetImmediately: false,
|
|
269
|
+
},
|
|
270
|
+
}) as LiveInlineDiffPreviewer;
|
|
216
271
|
this.addDispose(
|
|
217
|
-
this.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (event.acceptPartialEditCount > 0) {
|
|
221
|
-
blockData.status = 'success';
|
|
222
|
-
const appliedResult = editor.getModel()!.getValue();
|
|
223
|
-
const diffResult = createPatch(relativePath, fullOriginalContent, appliedResult)
|
|
224
|
-
.split('\n')
|
|
225
|
-
.slice(4)
|
|
226
|
-
.join('\n');
|
|
227
|
-
const rangesFromDiffHunk = diffResult
|
|
228
|
-
.split('\n')
|
|
229
|
-
.map((line) => {
|
|
230
|
-
if (line.startsWith('@@')) {
|
|
231
|
-
const [, , , start, end] = line.match(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/)!;
|
|
232
|
-
return new Range(parseInt(start, 10), 0, parseInt(end, 10), 0);
|
|
233
|
-
}
|
|
234
|
-
return null;
|
|
235
|
-
})
|
|
236
|
-
.filter((range) => range !== null);
|
|
237
|
-
const diagnosticInfos = this.getdiagnosticInfos(editor.getModel()!.uri.toString(), rangesFromDiffHunk);
|
|
238
|
-
// 移除开头的几个固定信息,避免浪费 tokens
|
|
239
|
-
deferred.resolve({
|
|
240
|
-
diff: diffResult,
|
|
241
|
-
diagnosticInfos,
|
|
242
|
-
});
|
|
243
|
-
} else {
|
|
244
|
-
// 用户全部取消
|
|
245
|
-
blockData.status = 'cancelled';
|
|
246
|
-
deferred.resolve();
|
|
247
|
-
}
|
|
248
|
-
}
|
|
272
|
+
this.activePreviewer.getNode()!.onDiffFinished((diffModel) => {
|
|
273
|
+
codeBlock.updatedCode = diffModel.newFullRangeTextLines.join('\n');
|
|
274
|
+
this.updateCodeBlock(codeBlock);
|
|
249
275
|
}),
|
|
250
276
|
);
|
|
251
277
|
}
|
|
252
|
-
|
|
278
|
+
|
|
279
|
+
return this.listenPartialEdit(editor, codeBlock, fullOriginalContent);
|
|
253
280
|
}
|
|
254
281
|
|
|
255
282
|
/**
|
|
256
283
|
* Cancel an ongoing apply operation
|
|
257
284
|
*/
|
|
258
|
-
cancelApply(
|
|
259
|
-
|
|
260
|
-
if (blockData && (blockData.status === 'generating' || blockData.status === 'pending')) {
|
|
285
|
+
cancelApply(blockData: CodeBlockData): void {
|
|
286
|
+
if (blockData.status === 'generating' || blockData.status === 'pending') {
|
|
261
287
|
if (this.activePreviewer) {
|
|
262
288
|
this.activePreviewer.getNode()?.livePreviewDiffDecorationModel.discardUnProcessed();
|
|
263
289
|
this.activePreviewer.dispose();
|
|
@@ -267,62 +293,105 @@ export abstract class BaseApplyService extends WithEventBus {
|
|
|
267
293
|
}
|
|
268
294
|
}
|
|
269
295
|
|
|
296
|
+
// TODO: 目前的设计下,有一个工具 apply 没返回,是不会触发下一个的(cursor 是会全部自动 apply 的),所以这个方法目前还没有必要
|
|
270
297
|
cancelAllApply(): void {
|
|
271
|
-
this.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
298
|
+
const messageId = this.chatInternalService.sessionModel.history.lastMessageId!;
|
|
299
|
+
const codeBlockMap = this.getMessageCodeBlocks(messageId);
|
|
300
|
+
if (!codeBlockMap) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
Object.values(codeBlockMap).forEach((blockData) => {
|
|
304
|
+
this.cancelApply(blockData);
|
|
275
305
|
});
|
|
276
306
|
}
|
|
277
307
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
308
|
+
revealApplyPosition(blockData: CodeBlockData): void {
|
|
309
|
+
const hunkInfo = blockData.applyResult?.diff.split('\n').find((line) => line.startsWith('@@'));
|
|
310
|
+
let startLine = 0;
|
|
311
|
+
let endLine = 0;
|
|
312
|
+
if (hunkInfo) {
|
|
313
|
+
// 取改动后的区间
|
|
314
|
+
const [, , , start, end] = hunkInfo.match(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/)!;
|
|
315
|
+
startLine = parseInt(start, 10) - 1;
|
|
316
|
+
endLine = parseInt(end, 10) - 1;
|
|
317
|
+
}
|
|
318
|
+
this.editorService.open(URI.file(path.join(this.appConfig.workspaceDir, blockData.relativePath)));
|
|
319
|
+
const editor = this.editorService.currentEditor;
|
|
320
|
+
if (editor) {
|
|
321
|
+
editor.setSelection(new Selection(startLine, 0, endLine, 0));
|
|
322
|
+
}
|
|
287
323
|
}
|
|
288
324
|
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
if (
|
|
292
|
-
|
|
293
|
-
let startLine = 0;
|
|
294
|
-
let endLine = 0;
|
|
295
|
-
if (hunkInfo) {
|
|
296
|
-
// 取改动后的区间
|
|
297
|
-
const [, , , start, end] = hunkInfo.match(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/)!;
|
|
298
|
-
startLine = parseInt(start, 10) - 1;
|
|
299
|
-
endLine = parseInt(end, 10) - 1;
|
|
300
|
-
}
|
|
301
|
-
this.editorService.open(URI.file(path.join(this.appConfig.workspaceDir, blockData.relativePath)));
|
|
302
|
-
const editor = this.editorService.currentEditor;
|
|
303
|
-
if (editor) {
|
|
304
|
-
editor.setSelection(new Selection(startLine, 0, endLine, 0));
|
|
305
|
-
}
|
|
325
|
+
processAll(uri: URI, type: 'accept' | 'reject'): void {
|
|
326
|
+
const codeBlock = this.getUriPendingCodeBlock(uri);
|
|
327
|
+
if (!codeBlock) {
|
|
328
|
+
throw new Error('No pending code block found');
|
|
306
329
|
}
|
|
330
|
+
const decorationModel = this.activePreviewer?.getNode()?.livePreviewDiffDecorationModel;
|
|
331
|
+
if (!decorationModel) {
|
|
332
|
+
throw new Error('No active previewer found');
|
|
333
|
+
}
|
|
334
|
+
if (type === 'accept') {
|
|
335
|
+
decorationModel.acceptUnProcessed();
|
|
336
|
+
} else {
|
|
337
|
+
decorationModel.discardUnProcessed();
|
|
338
|
+
}
|
|
339
|
+
this.editorService.save(uri);
|
|
340
|
+
codeBlock.status = type === 'accept' ? 'success' : 'cancelled';
|
|
341
|
+
this.updateCodeBlock(codeBlock);
|
|
307
342
|
}
|
|
308
343
|
|
|
309
|
-
protected
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
344
|
+
protected listenPartialEdit(editor: ICodeEditor, codeBlock: CodeBlockData, fullOriginalContent: string) {
|
|
345
|
+
const deferred = new Deferred<{ diff: string; diagnosticInfos: IMarker[] }>();
|
|
346
|
+
const toDispose = this.inlineDiffService.onPartialEdit((event) => {
|
|
347
|
+
// TODO 支持自动保存
|
|
348
|
+
if (event.totalPartialEditCount === event.resolvedPartialEditCount) {
|
|
349
|
+
if (event.acceptPartialEditCount > 0) {
|
|
350
|
+
codeBlock.status = 'success';
|
|
351
|
+
const appliedResult = editor.getModel()!.getValue();
|
|
352
|
+
const diffResult = createPatch(codeBlock.relativePath, fullOriginalContent, appliedResult)
|
|
353
|
+
.split('\n')
|
|
354
|
+
.slice(4)
|
|
355
|
+
.join('\n');
|
|
356
|
+
const rangesFromDiffHunk = diffResult
|
|
357
|
+
.split('\n')
|
|
358
|
+
.map((line) => {
|
|
359
|
+
if (line.startsWith('@@')) {
|
|
360
|
+
const [, , , start, end] = line.match(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/)!;
|
|
361
|
+
return new Range(parseInt(start, 10), 0, parseInt(end, 10), 0);
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
})
|
|
365
|
+
.filter((range) => range !== null);
|
|
366
|
+
const diagnosticInfos = this.getDiagnosticInfos(editor.getModel()!.uri.toString(), rangesFromDiffHunk);
|
|
367
|
+
// 移除开头的几个固定信息,避免浪费 tokens
|
|
368
|
+
deferred.resolve({
|
|
369
|
+
diff: diffResult,
|
|
370
|
+
diagnosticInfos,
|
|
371
|
+
});
|
|
372
|
+
} else {
|
|
373
|
+
// 用户全部取消
|
|
374
|
+
codeBlock.status = 'cancelled';
|
|
375
|
+
deferred.resolve();
|
|
376
|
+
}
|
|
377
|
+
toDispose.dispose();
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
return deferred.promise;
|
|
323
381
|
}
|
|
324
382
|
|
|
325
|
-
|
|
383
|
+
/**
|
|
384
|
+
* Apply changes of a code block, return stream to render inline diff in stream mode, result to render inline diff directly
|
|
385
|
+
* range is optional, if not provided, the result will be applied to the the full file
|
|
386
|
+
*/
|
|
387
|
+
protected abstract doApply(codeBlock: CodeBlockData): Promise<{
|
|
388
|
+
range?: Range;
|
|
389
|
+
stream?: SumiReadableStream<IChatProgress, Error>;
|
|
390
|
+
result?: string;
|
|
391
|
+
}>;
|
|
392
|
+
|
|
393
|
+
// TODO: 支持使用内存中的document获取诊断信息,实现并行apply accept
|
|
394
|
+
protected getDiagnosticInfos(uri: string, ranges: Range[]) {
|
|
326
395
|
const markers = this.markerService.getManager().getMarkers({ resource: uri });
|
|
327
396
|
return markers.filter(
|
|
328
397
|
(marker) =>
|
|
@@ -331,19 +400,3 @@ export abstract class BaseApplyService extends WithEventBus {
|
|
|
331
400
|
);
|
|
332
401
|
}
|
|
333
402
|
}
|
|
334
|
-
|
|
335
|
-
export interface CodeBlockData {
|
|
336
|
-
id: string;
|
|
337
|
-
initToolCallId?: string;
|
|
338
|
-
content: string;
|
|
339
|
-
relativePath: string;
|
|
340
|
-
status: CodeBlockStatus;
|
|
341
|
-
iterationCount: number;
|
|
342
|
-
createdAt: number;
|
|
343
|
-
applyResult?: {
|
|
344
|
-
diff: string;
|
|
345
|
-
diagnosticInfos: IMarker[];
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
export type CodeBlockStatus = 'generating' | 'pending' | 'success' | 'rejected' | 'failed' | 'cancelled';
|