@opensumi/ide-ai-native 3.8.1-next-1741182617.0 → 3.8.1-next-1741224137.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.
Files changed (45) hide show
  1. package/lib/browser/ai-core.contribution.d.ts.map +1 -1
  2. package/lib/browser/ai-core.contribution.js +3 -1
  3. package/lib/browser/ai-core.contribution.js.map +1 -1
  4. package/lib/browser/chat/chat.internal.service.d.ts +2 -0
  5. package/lib/browser/chat/chat.internal.service.d.ts.map +1 -1
  6. package/lib/browser/chat/chat.internal.service.js +3 -0
  7. package/lib/browser/chat/chat.internal.service.js.map +1 -1
  8. package/lib/browser/components/ChatInput.d.ts.map +1 -1
  9. package/lib/browser/components/ChatInput.js +5 -1
  10. package/lib/browser/components/ChatInput.js.map +1 -1
  11. package/lib/browser/components/ChatReply.d.ts.map +1 -1
  12. package/lib/browser/components/ChatReply.js +1 -1
  13. package/lib/browser/components/ChatReply.js.map +1 -1
  14. package/lib/browser/components/ChatThinking.d.ts +0 -2
  15. package/lib/browser/components/ChatThinking.d.ts.map +1 -1
  16. package/lib/browser/components/ChatThinking.js +2 -10
  17. package/lib/browser/components/ChatThinking.js.map +1 -1
  18. package/lib/browser/components/WelcomeMsg.js +1 -1
  19. package/lib/browser/components/WelcomeMsg.js.map +1 -1
  20. package/lib/browser/components/components.module.less +2 -0
  21. package/lib/browser/mcp/base-apply.service.d.ts +8 -5
  22. package/lib/browser/mcp/base-apply.service.d.ts.map +1 -1
  23. package/lib/browser/mcp/base-apply.service.js +81 -76
  24. package/lib/browser/mcp/base-apply.service.js.map +1 -1
  25. package/lib/browser/widget/inline-chat/inline-chat-controller.d.ts.map +1 -1
  26. package/lib/browser/widget/inline-chat/inline-chat-controller.js +6 -1
  27. package/lib/browser/widget/inline-chat/inline-chat-controller.js.map +1 -1
  28. package/lib/browser/widget/inline-diff/inline-diff-manager.js +1 -1
  29. package/lib/browser/widget/inline-diff/inline-diff-manager.js.map +1 -1
  30. package/lib/browser/widget/inline-diff/inline-diff-widget.module.less +3 -3
  31. package/lib/common/types.d.ts +1 -0
  32. package/lib/common/types.d.ts.map +1 -1
  33. package/package.json +23 -23
  34. package/src/browser/ai-core.contribution.ts +4 -1
  35. package/src/browser/chat/chat.internal.service.ts +4 -0
  36. package/src/browser/components/ChatInput.tsx +14 -2
  37. package/src/browser/components/ChatReply.tsx +1 -5
  38. package/src/browser/components/ChatThinking.tsx +3 -9
  39. package/src/browser/components/WelcomeMsg.tsx +1 -1
  40. package/src/browser/components/components.module.less +2 -0
  41. package/src/browser/mcp/base-apply.service.ts +89 -84
  42. package/src/browser/widget/inline-chat/inline-chat-controller.ts +5 -1
  43. package/src/browser/widget/inline-diff/inline-diff-manager.tsx +1 -1
  44. package/src/browser/widget/inline-diff/inline-diff-widget.module.less +3 -3
  45. package/src/common/types.ts +1 -0
@@ -30,6 +30,9 @@ export class ChatInternalService extends Disposable {
30
30
  private readonly _onCancelRequest = new Emitter<void>();
31
31
  public readonly onCancelRequest: Event<void> = this._onCancelRequest.event;
32
32
 
33
+ private readonly _onWillClearSession = new Emitter<string>();
34
+ public readonly onWillClearSession: Event<string> = this._onWillClearSession.event;
35
+
33
36
  private readonly _onRegenerateRequest = new Emitter<void>();
34
37
  public readonly onRegenerateRequest: Event<void> = this._onRegenerateRequest.event;
35
38
 
@@ -83,6 +86,7 @@ export class ChatInternalService extends Disposable {
83
86
 
84
87
  clearSessionModel(sessionId?: string) {
85
88
  sessionId = sessionId || this.#sessionModel.sessionId;
89
+ this._onWillClearSession.fire(sessionId);
86
90
  this.chatManagerService.clearSession(sessionId);
87
91
  if (sessionId === this.#sessionModel.sessionId) {
88
92
  this.#sessionModel = this.chatManagerService.startSession();
@@ -16,11 +16,18 @@ import { CommandService } from '@opensumi/ide-core-common/lib/command';
16
16
  import { MonacoCommandRegistry } from '@opensumi/ide-editor/lib/browser/monaco-contrib/command/command.service';
17
17
  import { IDialogService } from '@opensumi/ide-overlay';
18
18
 
19
- import { AT_SIGN_SYMBOL, IChatAgentService, SLASH_SYMBOL, TokenMCPServerProxyService } from '../../common';
19
+ import {
20
+ AT_SIGN_SYMBOL,
21
+ IChatAgentService,
22
+ IChatInternalService,
23
+ SLASH_SYMBOL,
24
+ TokenMCPServerProxyService,
25
+ } from '../../common';
20
26
  import { ChatAgentViewService } from '../chat/chat-agent.view.service';
21
27
  import { ChatSlashCommandItemModel } from '../chat/chat-model';
22
28
  import { ChatProxyService } from '../chat/chat-proxy.service';
23
29
  import { ChatFeatureRegistry } from '../chat/chat.feature.registry';
30
+ import { ChatInternalService } from '../chat/chat.internal.service';
24
31
  import { OPEN_MCP_CONFIG_COMMAND } from '../mcp/config/mcp-config.commands';
25
32
  import { MCPServerProxyService } from '../mcp/mcp-server-proxy.service';
26
33
  import { MCPToolsDialog } from '../mcp/mcp-tools-dialog.view';
@@ -204,7 +211,7 @@ export const ChatInput = React.forwardRef((props: IChatInputProps, ref) => {
204
211
  const [showExpand, setShowExpand] = useState(false);
205
212
  const [isExpand, setIsExpand] = useState(false);
206
213
  const [placeholder, setPlaceHolder] = useState(localize('aiNative.chat.input.placeholder.default'));
207
-
214
+ const aiChatService = useInjectable<ChatInternalService>(IChatInternalService);
208
215
  const dialogService = useInjectable<IDialogService>(IDialogService);
209
216
  const aiNativeConfigService = useInjectable<AINativeConfigService>(AINativeConfigService);
210
217
  const mcpServerProxyService = useInjectable<MCPServerProxyService>(TokenMCPServerProxyService);
@@ -324,6 +331,10 @@ export const ChatInput = React.forwardRef((props: IChatInputProps, ref) => {
324
331
  }
325
332
  }, []);
326
333
 
334
+ const handleStop = useCallback(() => {
335
+ aiChatService.cancelRequest();
336
+ }, []);
337
+
327
338
  const handleSend = useCallback(async () => {
328
339
  if (disabled) {
329
340
  return;
@@ -484,6 +495,7 @@ export const ChatInput = React.forwardRef((props: IChatInputProps, ref) => {
484
495
  disabled={disabled}
485
496
  className={styles.input_wrapper}
486
497
  onSend={handleSend}
498
+ onStop={handleStop}
487
499
  sendBtnClassName={sendBtnClassName}
488
500
  onHeightChange={handleHeightChange}
489
501
  height={inputHeight}
@@ -355,11 +355,7 @@ export const ChatReply = (props: IChatReplyProps) => {
355
355
  }, [request.response.followups]);
356
356
 
357
357
  if (!request.response.isComplete) {
358
- return (
359
- <ChatThinking message={request.response.responseText} onStop={onStop}>
360
- {contentNode}
361
- </ChatThinking>
362
- );
358
+ return <ChatThinking message={request.response.responseText}>{contentNode}</ChatThinking>;
363
359
  }
364
360
 
365
361
  return (
@@ -18,14 +18,12 @@ interface ITinkingProps {
18
18
  message?: string;
19
19
  onRegenerate?: () => void;
20
20
  requestId?: string;
21
- onStop?: () => void;
22
21
  thinkingText?: string;
23
- showStop?: boolean;
24
22
  showRegenerate?: boolean;
25
23
  }
26
24
 
27
25
  export const ChatThinking = (props: ITinkingProps) => {
28
- const { children, message, onStop, showStop = true, thinkingText } = props;
26
+ const { children, message, thinkingText } = props;
29
27
 
30
28
  const chatRenderRegistry = useInjectable<ChatRenderRegistry>(ChatRenderRegistryToken);
31
29
 
@@ -34,10 +32,6 @@ export const ChatThinking = (props: ITinkingProps) => {
34
32
  [chatRenderRegistry, chatRenderRegistry.chatThinkingRender],
35
33
  );
36
34
 
37
- const handlePause = useCallback(async () => {
38
- onStop && onStop();
39
- }, []);
40
-
41
35
  const renderContent = useCallback(() => {
42
36
  if (!children || !message?.trim()) {
43
37
  if (CustomThinkingRender) {
@@ -61,12 +55,12 @@ export const ChatThinking = (props: ITinkingProps) => {
61
55
  {!children && <Progress loading={true} wrapperClassName={styles.ai_native_progress_wrapper} />}
62
56
  </span>
63
57
  )}
64
- {showStop && (
58
+ {/* {showStop && (
65
59
  <div className={styles.block} onClick={handlePause} tabIndex={0} role='button'>
66
60
  <Icon className={getIcon('circle-pause')}></Icon>
67
61
  <span>{localize('aiNative.operate.stop.title')}</span>
68
62
  </div>
69
- )}
63
+ )} */}
70
64
  </div>
71
65
  </div>
72
66
  </>
@@ -62,7 +62,7 @@ export const WelcomeMessage = () => {
62
62
  }, []);
63
63
 
64
64
  if (!welcomeMessage) {
65
- return <ChatThinking showStop={false} thinkingText={localize('aiNative.chat.welcome.loading.text')} />;
65
+ return <ChatThinking thinkingText={localize('aiNative.chat.welcome.loading.text')} />;
66
66
  }
67
67
 
68
68
  const allSampleQuestions = React.useMemo(
@@ -284,6 +284,8 @@
284
284
  position: relative;
285
285
  min-width: 100px;
286
286
  margin-top: 4px;
287
+ max-height: 300px;
288
+ overflow: auto;
287
289
 
288
290
  :global {
289
291
  .hljs {
@@ -69,7 +69,17 @@ export abstract class BaseApplyService extends WithEventBus {
69
69
  super();
70
70
  this.addDispose(
71
71
  this.chatInternalService.onCancelRequest(() => {
72
- this.cancelAllApply();
72
+ const currentMessageId = this.chatInternalService.sessionModel.history.lastMessageId;
73
+ if (!currentMessageId) {
74
+ return;
75
+ }
76
+ const codeBlockMap = this.getMessageCodeBlocks(currentMessageId);
77
+ if (!codeBlockMap) {
78
+ return;
79
+ }
80
+ Object.values(codeBlockMap).forEach((blockData) => {
81
+ this.cancelApply(blockData);
82
+ });
73
83
  }),
74
84
  );
75
85
  this.currentSessionId = this.chatInternalService.sessionModel.sessionId;
@@ -94,6 +104,11 @@ export abstract class BaseApplyService extends WithEventBus {
94
104
  });
95
105
  }),
96
106
  );
107
+ this.addDispose(
108
+ this.chatInternalService.onWillClearSession((sessionId) => {
109
+ this.cancelAllApply(sessionId);
110
+ }),
111
+ );
97
112
  }
98
113
 
99
114
  private getMessageCodeBlocks(
@@ -109,27 +124,6 @@ export abstract class BaseApplyService extends WithEventBus {
109
124
  return message?.codeBlockMap;
110
125
  }
111
126
 
112
- private getSessionCodeBlocksForPath(relativePath: string, sessionId?: string) {
113
- sessionId = sessionId || this.chatInternalService.sessionModel.sessionId;
114
- const sessionModel = this.chatInternalService.getSession(sessionId);
115
- if (!sessionModel) {
116
- throw new Error(`Session ${sessionId} not found`);
117
- }
118
- const sessionAdditionals = sessionModel.history.sessionAdditionals;
119
- const codeBlocks: CodeBlockData[] = Array.from(sessionAdditionals.values())
120
- .map((additional) => additional.codeBlockMap as { [toolCallId: string]: CodeBlockData })
121
- .reduce((acc, cur) => {
122
- Object.values(cur).forEach((block) => {
123
- if (block.relativePath === relativePath) {
124
- acc.push(block);
125
- }
126
- });
127
- return acc;
128
- }, [] as CodeBlockData[])
129
- .sort((a, b) => b.version - a.version);
130
- return codeBlocks;
131
- }
132
-
133
127
  private activePreviewerMap = this.registerDispose(
134
128
  new DisposableMap<string, BaseInlineDiffPreviewer<BaseInlineStreamDiffHandler>>(),
135
129
  );
@@ -156,10 +150,9 @@ export abstract class BaseApplyService extends WithEventBus {
156
150
  ) {
157
151
  return;
158
152
  }
159
- const filePendingApplies = Object.values(
160
- this.getMessageCodeBlocks(this.chatInternalService.sessionModel.history.lastMessageId!) || {},
161
- ).filter((block) => block.relativePath === relativePath && block.status === 'pending');
162
- // TODO: 刷新后重新应用,事件无法恢复 & 恢复继续请求,需要改造成批量apply形式
153
+ const filePendingApplies =
154
+ this.getUriCodeBlocks(event.payload.resource.uri)?.filter((block) => block.status === 'pending') || [];
155
+ // 使用最后一个版本内容渲染 apply 内容
163
156
  if (filePendingApplies.length > 0 && filePendingApplies[0].updatedCode) {
164
157
  const editor = event.payload.group.codeEditor.monacoEditor;
165
158
  this.renderApplyResult(editor, filePendingApplies[0], filePendingApplies[0].updatedCode);
@@ -174,23 +167,23 @@ export abstract class BaseApplyService extends WithEventBus {
174
167
  return this.activePreviewerMap.get(path.relative(this.appConfig.workspaceDir, currentUri.path.toString()));
175
168
  }
176
169
 
177
- getUriPendingCodeBlock(uri: URI): CodeBlockData | undefined {
178
- const messageId = this.chatInternalService.sessionModel.history.lastMessageId;
179
- if (!messageId) {
180
- return undefined;
181
- }
182
- const codeBlockMap = this.getMessageCodeBlocks(messageId);
183
- if (!codeBlockMap) {
184
- return undefined;
185
- }
186
- return Object.values(codeBlockMap).find(
187
- (block) =>
188
- block.relativePath === path.relative(this.appConfig.workspaceDir, uri.path.toString()) &&
189
- block.status === 'pending',
190
- );
170
+ /**
171
+ * 获取指定uri的 code block,按version降序排序
172
+ */
173
+ getUriCodeBlocks(uri: URI): CodeBlockData[] | undefined {
174
+ const sessionCodeBlocks = this.getSessionCodeBlocks();
175
+ const relativePath = path.relative(this.appConfig.workspaceDir, uri.path.toString());
176
+ return sessionCodeBlocks
177
+ .filter((block) => block.relativePath === relativePath)
178
+ .sort((a, b) => b.version - a.version);
191
179
  }
192
180
 
193
181
  getPendingPaths(sessionId?: string): string[] {
182
+ const sessionCodeBlocks = this.getSessionCodeBlocks(sessionId);
183
+ return sessionCodeBlocks.filter((block) => block.status === 'pending').map((block) => block.relativePath);
184
+ }
185
+
186
+ protected getSessionCodeBlocks(sessionId?: string) {
194
187
  sessionId = sessionId || this.chatInternalService.sessionModel.sessionId;
195
188
  const sessionModel = this.chatInternalService.getSession(sessionId);
196
189
  if (!sessionModel) {
@@ -198,15 +191,13 @@ export abstract class BaseApplyService extends WithEventBus {
198
191
  }
199
192
  const sessionAdditionals = sessionModel.history.sessionAdditionals;
200
193
  return Array.from(sessionAdditionals.values())
201
- .map((additional) => additional.codeBlockMap as { [toolCallId: string]: CodeBlockData })
194
+ .map((additional) => (additional.codeBlockMap || {}) as { [toolCallId: string]: CodeBlockData })
202
195
  .reduce((acc, cur) => {
203
196
  Object.values(cur).forEach((block) => {
204
- if (block.status === 'pending' && !acc.includes(block.relativePath)) {
205
- acc.push(block.relativePath);
206
- }
197
+ acc.push(block);
207
198
  });
208
199
  return acc;
209
- }, [] as string[]);
200
+ }, [] as CodeBlockData[]);
210
201
  }
211
202
 
212
203
  getCodeBlock(toolCallId: string, messageId?: string): CodeBlockData | undefined {
@@ -221,11 +212,8 @@ export abstract class BaseApplyService extends WithEventBus {
221
212
  return codeBlockMap[toolCallId];
222
213
  }
223
214
 
224
- protected updateCodeBlock(codeBlock: CodeBlockData, messageId?: string) {
225
- messageId = messageId || this.chatInternalService.sessionModel.history.lastMessageId;
226
- if (!messageId) {
227
- throw new Error('Message ID is required');
228
- }
215
+ protected updateCodeBlock(codeBlock: CodeBlockData) {
216
+ const messageId = codeBlock.messageId;
229
217
  const codeBlockMap = this.getMessageCodeBlocks(messageId);
230
218
  if (!codeBlockMap) {
231
219
  throw new Error('Code block not found');
@@ -239,7 +227,7 @@ export abstract class BaseApplyService extends WithEventBus {
239
227
 
240
228
  async registerCodeBlock(relativePath: string, content: string, toolCallId: string): Promise<CodeBlockData> {
241
229
  const lastMessageId = this.chatInternalService.sessionModel.history.lastMessageId!;
242
- const savedCodeBlockMap = this.getMessageCodeBlocks(lastMessageId) || {};
230
+ const uriCodeBlocks = this.getUriCodeBlocks(URI.file(path.join(this.appConfig.workspaceDir, relativePath)));
243
231
  const originalModelRef = await this.editorDocumentModelService.createModelReference(
244
232
  URI.file(path.join(this.appConfig.workspaceDir, relativePath)),
245
233
  );
@@ -251,13 +239,13 @@ export abstract class BaseApplyService extends WithEventBus {
251
239
  version: 1,
252
240
  createdAt: Date.now(),
253
241
  toolCallId,
242
+ messageId: lastMessageId,
254
243
  // TODO: 支持range
255
244
  originalCode: originalModelRef.instance.getText(),
256
245
  };
257
- const samePathCodeBlocks = Object.values(savedCodeBlockMap).filter((block) => block.relativePath === relativePath);
258
- if (samePathCodeBlocks.length > 0) {
259
- newBlock.version = samePathCodeBlocks.length;
260
- for (const block of samePathCodeBlocks.sort((a, b) => a.version - b.version)) {
246
+ if (uriCodeBlocks?.length) {
247
+ newBlock.version = uriCodeBlocks.length;
248
+ for (const block of uriCodeBlocks) {
261
249
  // 如果连续的上一个同文件apply结果存在LintError,则iterationCount++
262
250
  if (block.relativePath === relativePath && block.applyResult?.diagnosticInfos?.length) {
263
251
  newBlock.iterationCount++;
@@ -266,6 +254,7 @@ export abstract class BaseApplyService extends WithEventBus {
266
254
  }
267
255
  }
268
256
  }
257
+ const savedCodeBlockMap = this.getMessageCodeBlocks(lastMessageId) || {};
269
258
  savedCodeBlockMap[toolCallId] = newBlock;
270
259
  this.chatInternalService.sessionModel.history.setMessageAdditional(lastMessageId, {
271
260
  codeBlockMap: savedCodeBlockMap,
@@ -296,9 +285,9 @@ export abstract class BaseApplyService extends WithEventBus {
296
285
  }
297
286
 
298
287
  if (this.activePreviewerMap.has(codeBlock.relativePath)) {
299
- this.activePreviewerMap.disposeKey(codeBlock.relativePath);
288
+ // 有正在进行的 apply,则取消(但不更新block状态,只清理副作用)
289
+ this.cancelApply(codeBlock, true);
300
290
  }
301
- // FIXME: 同一个bubble单个文件多次写入(如迭代)兼容
302
291
  // trigger diffPreivewer & return expected diff result directly
303
292
  const result = await this.editorService.open(
304
293
  URI.file(path.join(this.appConfig.workspaceDir, codeBlock.relativePath)),
@@ -306,6 +295,11 @@ export abstract class BaseApplyService extends WithEventBus {
306
295
  if (!result) {
307
296
  throw new Error('Failed to open file');
308
297
  }
298
+ if (typeof fastApplyFileResult.result === 'string') {
299
+ codeBlock.updatedCode = fastApplyFileResult.result;
300
+ codeBlock.status = 'pending';
301
+ this.updateCodeBlock(codeBlock);
302
+ }
309
303
  const applyResult = await this.renderApplyResult(
310
304
  result.group.codeEditor.monacoEditor,
311
305
  codeBlock,
@@ -340,16 +334,14 @@ export abstract class BaseApplyService extends WithEventBus {
340
334
 
341
335
  if (typeof updatedContentOrStream === 'string') {
342
336
  const editorCurrentContent = editor.getModel()!.getValue();
343
- const document = this.editorDocumentModelService.getModelReference(
344
- URI.file(path.join(this.appConfig.workspaceDir, codeBlock.relativePath)),
345
- );
337
+ const uri = URI.file(path.join(this.appConfig.workspaceDir, codeBlock.relativePath));
338
+ const document = this.editorDocumentModelService.getModelReference(uri);
346
339
  if (editorCurrentContent !== updatedContentOrStream || document?.instance.dirty) {
347
340
  editor.getModel()?.pushEditOperations([], [EditOperation.replace(range, updatedContentOrStream)], () => null);
348
- await this.editorService.save(URI.file(path.join(this.appConfig.workspaceDir, codeBlock.relativePath)));
341
+ await this.editorService.save(uri);
349
342
  }
350
- const earlistPendingCodeBlock = this.getSessionCodeBlocksForPath(codeBlock.relativePath).find(
351
- (block) => block.status === 'pending',
352
- );
343
+ const uriPendingCodeBlocks = this.getUriCodeBlocks(uri)?.filter((block) => block.status === 'pending');
344
+ const earlistPendingCodeBlock = uriPendingCodeBlocks?.[uriPendingCodeBlocks.length - 1];
353
345
  if ((earlistPendingCodeBlock?.originalCode || codeBlock.originalCode) === updatedContentOrStream) {
354
346
  codeBlock.status = 'cancelled';
355
347
  this.updateCodeBlock(codeBlock);
@@ -367,9 +359,6 @@ export abstract class BaseApplyService extends WithEventBus {
367
359
  },
368
360
  ) as LiveInlineDiffPreviewer;
369
361
  this.activePreviewerMap.set(codeBlock.relativePath, previewer);
370
- codeBlock.updatedCode = updatedContentOrStream;
371
- codeBlock.status = 'pending';
372
- this.updateCodeBlock(codeBlock);
373
362
  // 新建文件场景,为避免model为空,加一个空行
374
363
  previewer.setValue(earlistPendingCodeBlock?.originalCode || codeBlock.originalCode || '\n');
375
364
  // 强刷展示 manager 视图
@@ -385,7 +374,7 @@ export abstract class BaseApplyService extends WithEventBus {
385
374
 
386
375
  const { diff, rangesFromDiffHunk } = this.getDiffResult(
387
376
  codeBlock.originalCode,
388
- codeBlock.updatedCode,
377
+ codeBlock.updatedCode || updatedContentOrStream,
389
378
  codeBlock.relativePath,
390
379
  );
391
380
  const diagnosticInfos = this.getDiagnosticInfos(editor.getModel()!.uri.toString(), rangesFromDiffHunk);
@@ -405,6 +394,17 @@ export abstract class BaseApplyService extends WithEventBus {
405
394
  renderRemovedWidgetImmediately: false,
406
395
  },
407
396
  }) as LiveInlineDiffPreviewer;
397
+
398
+ this.addDispose(
399
+ controller.onError((err) => {
400
+ deferred.reject(err);
401
+ }),
402
+ );
403
+ this.addDispose(
404
+ controller.onAbort(() => {
405
+ deferred.reject(new Error('Apply aborted'));
406
+ }),
407
+ );
408
408
  this.addDispose(
409
409
  // 流式输出结束后,转为直接输出逻辑
410
410
  previewer.getNode()!.onDiffFinished(async (diffModel) => {
@@ -418,6 +418,7 @@ export abstract class BaseApplyService extends WithEventBus {
418
418
  deferred.reject(new Error('no changes applied'));
419
419
  return;
420
420
  }
421
+ codeBlock.status = 'pending';
421
422
  this.updateCodeBlock(codeBlock);
422
423
  previewer.dispose();
423
424
  const result = await this.renderApplyResult(editor, codeBlock, codeBlock.updatedCode);
@@ -432,8 +433,12 @@ export abstract class BaseApplyService extends WithEventBus {
432
433
  /**
433
434
  * Cancel an ongoing apply operation
434
435
  */
435
- cancelApply(blockData: CodeBlockData): void {
436
+ cancelApply(blockData: CodeBlockData, keepStatus?: boolean): void {
436
437
  if (blockData.status === 'generating' || blockData.status === 'pending') {
438
+ // 先取消掉相关的监听器
439
+ this.editorListenerMap.disposeKey(
440
+ URI.file(path.join(this.appConfig.workspaceDir, blockData.relativePath)).toString(),
441
+ );
437
442
  if (this.activePreviewerMap.has(blockData.relativePath)) {
438
443
  this.activePreviewerMap
439
444
  .get(blockData.relativePath)
@@ -441,19 +446,16 @@ export abstract class BaseApplyService extends WithEventBus {
441
446
  ?.livePreviewDiffDecorationModel.discardUnProcessed();
442
447
  this.activePreviewerMap.disposeKey(blockData.relativePath);
443
448
  }
444
- blockData.status = 'cancelled';
445
- this.updateCodeBlock(blockData);
449
+ if (!keepStatus) {
450
+ blockData.status = 'cancelled';
451
+ this.updateCodeBlock(blockData);
452
+ }
446
453
  }
447
454
  }
448
455
 
449
- // TODO: 目前的设计下,有一个工具 apply 没返回,是不会触发下一个的(cursor 是会全部自动 apply 的),所以这个方法目前还没有必要
450
- cancelAllApply(): void {
451
- const messageId = this.chatInternalService.sessionModel.history.lastMessageId!;
452
- const codeBlockMap = this.getMessageCodeBlocks(messageId);
453
- if (!codeBlockMap) {
454
- return;
455
- }
456
- Object.values(codeBlockMap).forEach((blockData) => {
456
+ cancelAllApply(sessionId?: string): void {
457
+ const sessionCodeBlocks = this.getSessionCodeBlocks(sessionId);
458
+ sessionCodeBlocks.forEach((blockData) => {
457
459
  this.cancelApply(blockData);
458
460
  });
459
461
  }
@@ -476,12 +478,12 @@ export abstract class BaseApplyService extends WithEventBus {
476
478
  }
477
479
 
478
480
  processAll(uri: URI, type: 'accept' | 'reject'): void {
479
- const codeBlock = this.getUriPendingCodeBlock(uri);
480
- if (!codeBlock) {
481
+ const codeBlocks = this.getUriCodeBlocks(uri)?.filter((block) => block.status === 'pending');
482
+ if (!codeBlocks?.length) {
481
483
  throw new Error('No pending code block found');
482
484
  }
483
485
  const decorationModel = this.activePreviewerMap
484
- .get(codeBlock.relativePath)
486
+ .get(codeBlocks[0].relativePath)
485
487
  ?.getNode()?.livePreviewDiffDecorationModel;
486
488
  if (!decorationModel) {
487
489
  throw new Error('No active previewer found');
@@ -492,8 +494,11 @@ export abstract class BaseApplyService extends WithEventBus {
492
494
  decorationModel.discardUnProcessed();
493
495
  }
494
496
  this.editorService.save(uri);
495
- codeBlock.status = type === 'accept' ? 'success' : 'cancelled';
496
- this.updateCodeBlock(codeBlock);
497
+ codeBlocks.forEach((codeBlock) => {
498
+ codeBlock.status = type === 'accept' ? 'success' : 'cancelled';
499
+ // TODO: 批量更新
500
+ this.updateCodeBlock(codeBlock);
501
+ });
497
502
  }
498
503
 
499
504
  protected listenPartialEdit(model: ITextModel, codeBlock: CodeBlockData) {
@@ -83,7 +83,11 @@ export class InlineChatController {
83
83
  this._onData.fire(reply);
84
84
  },
85
85
  onEnd: () => {
86
- this._onEnd.fire();
86
+ if (!wholeContent) {
87
+ this._onError.fire(new ErrorResponse(new Error('No content')));
88
+ } else {
89
+ this._onEnd.fire();
90
+ }
87
91
  },
88
92
  onError: (error) => {
89
93
  if (AbortError.is(error)) {
@@ -29,7 +29,7 @@ export const InlineDiffManager: React.FC<{ resource: IResource }> = (props) => {
29
29
  const [show, setShow] = useState(true);
30
30
  const [changesCount, setChangesCount] = useState(0);
31
31
  const [currentChangeIndex, setCurrentChangeIndex] = useState(0);
32
- const [filePaths, setFilePaths] = useState<string[]>([]);
32
+ const [filePaths, setFilePaths] = useState<string[]>(applyService.getPendingPaths());
33
33
 
34
34
  const currentFilePath = useMemo(
35
35
  () => path.relative(appConfig.workspaceDir, resource.uri.path.toString()),
@@ -22,7 +22,7 @@
22
22
 
23
23
  .inlineDiffManager {
24
24
  display: flex;
25
- border-radius: 13px;
25
+ border-radius: 14px;
26
26
  align-items: center;
27
27
  position: absolute;
28
28
  bottom: 18px;
@@ -38,7 +38,7 @@
38
38
  border-right: 1px solid var(--design-borderColor);
39
39
  }
40
40
  :global(.codicon) {
41
- font-size: 20px;
41
+ font-size: 16px;
42
42
  padding: 6px;
43
43
  color: var(--kt-primaryButton-foreground);
44
44
  background-color: var(--kt-primaryButton-hoverBackground);
@@ -48,7 +48,7 @@
48
48
  }
49
49
  }
50
50
  .disabled {
51
- // color: var(--kt-button-disableForeground) !important;
51
+ color: var(--kt-button-disableForeground) !important;
52
52
  background-color: var(--kt-button-disableBackground) !important;
53
53
  cursor: not-allowed;
54
54
  }
@@ -50,6 +50,7 @@ export interface MCPTool {
50
50
  }
51
51
 
52
52
  export interface CodeBlockData {
53
+ messageId: string;
53
54
  toolCallId: string;
54
55
  codeEdit: string;
55
56
  originalCode: string;