Skip to content

Commit 44b56d6

Browse files
authored
refactor: inline chat & input and support config (#4339)
* feat: support inline chat config * refactor: inline chat & inline input * feat: improve inline input regenerate * feat: improve code * fix: ci * feat: support cancel streaming * feat: support close file inline input disposed * fix: ci
1 parent 0581def commit 44b56d6

24 files changed

+794
-565
lines changed

packages/ai-native/src/browser/ai-core.contextkeys.ts

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
InlineCompletionIsTrigger,
66
InlineDiffPartialEditsIsVisible,
77
InlineHintWidgetIsVisible,
8+
InlineInputWidgetIsStreaming,
89
InlineInputWidgetIsVisible,
910
MultiLineEditsIsVisible,
1011
} from '@opensumi/ide-core-browser/lib/contextkey/ai-native';
@@ -22,6 +23,7 @@ export class AINativeContextKey {
2223
public readonly inlineCompletionIsTrigger: IContextKey<boolean>;
2324
public readonly inlineHintWidgetIsVisible: IContextKey<boolean>;
2425
public readonly inlineInputWidgetIsVisible: IContextKey<boolean>;
26+
public readonly inlineInputWidgetIsStreaming: IContextKey<boolean>;
2527
public readonly inlineDiffPartialEditsIsVisible: IContextKey<boolean>;
2628
public readonly multiLineEditsIsVisible: IContextKey<boolean>;
2729
public get contextKeyService() {
@@ -34,6 +36,7 @@ export class AINativeContextKey {
3436
this.inlineCompletionIsTrigger = InlineCompletionIsTrigger.bind(this._contextKeyService);
3537
this.inlineHintWidgetIsVisible = InlineHintWidgetIsVisible.bind(this._contextKeyService);
3638
this.inlineInputWidgetIsVisible = InlineInputWidgetIsVisible.bind(this._contextKeyService);
39+
this.inlineInputWidgetIsStreaming = InlineInputWidgetIsStreaming.bind(this._contextKeyService);
3740
this.inlineDiffPartialEditsIsVisible = InlineDiffPartialEditsIsVisible.bind(this._contextKeyService);
3841
this.multiLineEditsIsVisible = MultiLineEditsIsVisible.bind(this._contextKeyService);
3942
}

packages/ai-native/src/browser/ai-core.contribution.ts

+63-16
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
} from '@opensumi/ide-core-browser';
2828
import {
2929
AI_CHAT_VISIBLE,
30+
AI_INLINE_CHAT_INTERACTIVE_INPUT_CANCEL,
3031
AI_INLINE_CHAT_INTERACTIVE_INPUT_VISIBLE,
3132
AI_INLINE_CHAT_VISIBLE,
3233
AI_INLINE_COMPLETION_REPORTER,
@@ -37,6 +38,7 @@ import {
3738
InlineChatIsVisible,
3839
InlineDiffPartialEditsIsVisible,
3940
InlineHintWidgetIsVisible,
41+
InlineInputWidgetIsStreaming,
4042
InlineInputWidgetIsVisible,
4143
} from '@opensumi/ide-core-browser/lib/contextkey/ai-native';
4244
import { DesignLayoutConfig } from '@opensumi/ide-core-browser/lib/layout/constants';
@@ -56,8 +58,9 @@ import {
5658
runWhenIdle,
5759
} from '@opensumi/ide-core-common';
5860
import { DESIGN_MENU_BAR_RIGHT } from '@opensumi/ide-design';
59-
import { IEditor } from '@opensumi/ide-editor';
61+
import { IEditor, WorkbenchEditorService } from '@opensumi/ide-editor';
6062
import { BrowserEditorContribution, IEditorFeatureRegistry } from '@opensumi/ide-editor/lib/browser';
63+
import { WorkbenchEditorServiceImpl } from '@opensumi/ide-editor/lib/browser/workbench-editor.service';
6164
import { IMainLayoutService } from '@opensumi/ide-main-layout';
6265
import { ISettingRegistry, SettingContribution } from '@opensumi/ide-preferences';
6366
import { EditorContributionInstantiation } from '@opensumi/monaco-editor-core/esm/vs/editor/browser/editorExtensions';
@@ -101,11 +104,11 @@ import {
101104
} from './types';
102105
import { InlineChatEditorController } from './widget/inline-chat/inline-chat-editor.controller';
103106
import { InlineChatFeatureRegistry } from './widget/inline-chat/inline-chat.feature.registry';
104-
import { AIInlineChatService } from './widget/inline-chat/inline-chat.service';
107+
import { InlineChatService } from './widget/inline-chat/inline-chat.service';
105108
import { InlineDiffController } from './widget/inline-diff/inline-diff.controller';
106109
import { InlineHintController } from './widget/inline-hint/inline-hint.controller';
107110
import { InlineInputController } from './widget/inline-input/inline-input.controller';
108-
import { InlineInputChatService } from './widget/inline-input/inline-input.service';
111+
import { InlineInputService } from './widget/inline-input/inline-input.service';
109112
import { InlineStreamDiffService } from './widget/inline-stream-diff/inline-stream-diff.service';
110113
import { SumiLightBulbWidget } from './widget/light-bulb';
111114

@@ -191,10 +194,10 @@ export class AINativeBrowserContribution
191194
private readonly chatProxyService: ChatProxyService;
192195

193196
@Autowired(IAIInlineChatService)
194-
private readonly aiInlineChatService: AIInlineChatService;
197+
private readonly aiInlineChatService: InlineChatService;
195198

196-
@Autowired(InlineInputChatService)
197-
private readonly inlineInputChatService: InlineInputChatService;
199+
@Autowired(InlineInputService)
200+
private readonly inlineInputService: InlineInputService;
198201

199202
@Autowired(InlineStreamDiffService)
200203
private readonly inlineStreamDiffService: InlineStreamDiffService;
@@ -205,6 +208,9 @@ export class AINativeBrowserContribution
205208
@Autowired(CodeActionSingleHandler)
206209
private readonly codeActionSingleHandler: CodeActionSingleHandler;
207210

211+
@Autowired(WorkbenchEditorService)
212+
private readonly workbenchEditorService: WorkbenchEditorServiceImpl;
213+
208214
constructor() {
209215
this.registerFeature();
210216
}
@@ -237,7 +243,7 @@ export class AINativeBrowserContribution
237243
EditorContributionInstantiation.BeforeFirstInteraction,
238244
);
239245

240-
if (this.inlineChatFeatureRegistry.getInteractiveInputHandler()) {
246+
if (this.inlineInputService.getInteractiveInputHandler()) {
241247
register(
242248
InlineHintController.ID,
243249
new SyncDescriptor(InlineHintController, [this.injector]),
@@ -418,14 +424,48 @@ export class AINativeBrowserContribution
418424
});
419425

420426
commands.registerCommand(AI_INLINE_CHAT_INTERACTIVE_INPUT_VISIBLE, {
421-
execute: (isVisible: boolean) => {
422-
if (isVisible) {
423-
this.inlineInputChatService.visible();
424-
} else {
425-
this.inlineInputChatService.hide();
427+
execute: async (isVisible: boolean) => {
428+
if (!isVisible) {
429+
this.inlineInputService.hide();
430+
return;
431+
}
432+
433+
// 每次在展示 inline input 的时候,先隐藏 inline chat
434+
this.commandService.executeCommand(AI_INLINE_CHAT_VISIBLE.id, false);
435+
436+
const editor = this.workbenchEditorService.currentCodeEditor;
437+
if (!editor) {
438+
return;
439+
}
440+
441+
const position = editor.monacoEditor.getPosition();
442+
if (!position) {
443+
return;
444+
}
445+
446+
const selection = editor.monacoEditor.getSelection();
447+
const isEmptyLine = position ? editor.monacoEditor.getModel()?.getLineLength(position.lineNumber) === 0 : false;
448+
449+
if (isEmptyLine) {
450+
this.inlineInputService.visibleByPosition(position);
451+
return;
426452
}
427453

428-
this.aiInlineChatService._onInteractiveInputVisible.fire(isVisible);
454+
if (selection && !selection.isEmpty()) {
455+
this.inlineInputService.visibleBySelection(selection);
456+
return;
457+
}
458+
459+
this.inlineInputService.visibleByNearestCodeBlock(position, editor.monacoEditor);
460+
},
461+
});
462+
463+
commands.registerCommand(AI_INLINE_CHAT_INTERACTIVE_INPUT_CANCEL, {
464+
execute: () => {
465+
const editor = this.workbenchEditorService.currentCodeEditor;
466+
if (editor) {
467+
InlineInputController.get(editor.monacoEditor)?.cancelToken();
468+
}
429469
},
430470
});
431471

@@ -516,12 +556,12 @@ export class AINativeBrowserContribution
516556
when: `editorFocus && ${InlineChatIsVisible.raw}`,
517557
});
518558

519-
if (this.inlineChatFeatureRegistry.getInteractiveInputHandler()) {
559+
if (this.inlineInputService.getInteractiveInputHandler()) {
520560
// 当 Inline Chat (浮动组件)展示时,通过 CMD K 唤起 Inline Input
521561
keybindings.registerKeybinding(
522562
{
523563
command: AI_INLINE_CHAT_INTERACTIVE_INPUT_VISIBLE.id,
524-
keybinding: 'ctrlcmd+k',
564+
keybinding: this.aiNativeConfigService.inlineChat.inputKeybinding,
525565
args: true,
526566
priority: 0,
527567
when: `editorFocus && (${InlineChatIsVisible.raw} || inlineSuggestionVisible)`,
@@ -536,11 +576,18 @@ export class AINativeBrowserContribution
536576
priority: 0,
537577
when: `editorFocus && ${InlineInputWidgetIsVisible.raw}`,
538578
});
579+
// 当 Inline Input 流式编辑时,通过 ESC 退出
580+
keybindings.registerKeybinding({
581+
command: AI_INLINE_CHAT_INTERACTIVE_INPUT_CANCEL.id,
582+
keybinding: 'esc',
583+
priority: 1,
584+
when: `editorFocus && ${InlineInputWidgetIsStreaming.raw}`,
585+
});
539586
// 当出现 CMD K 展示信息时,通过快捷键快速唤起 Inline Input
540587
keybindings.registerKeybinding(
541588
{
542589
command: AI_INLINE_CHAT_INTERACTIVE_INPUT_VISIBLE.id,
543-
keybinding: 'ctrlcmd+k',
590+
keybinding: this.aiNativeConfigService.inlineChat.inputKeybinding,
544591
args: true,
545592
priority: 0,
546593
when: `editorFocus && ${InlineHintWidgetIsVisible.raw} && ${InlineChatIsVisible.not}`,

packages/ai-native/src/browser/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import { LanguageParserService } from './languages/service';
4646
import { AINativePreferencesContribution } from './preferences';
4747
import { AINativeCoreContribution } from './types';
4848
import { InlineChatFeatureRegistry } from './widget/inline-chat/inline-chat.feature.registry';
49-
import { AIInlineChatService } from './widget/inline-chat/inline-chat.service';
49+
import { InlineChatService } from './widget/inline-chat/inline-chat.service';
5050
import { InlineDiffService } from './widget/inline-diff';
5151

5252
@Injectable()
@@ -90,7 +90,7 @@ export class AINativeModule extends BrowserModule {
9090
},
9191
{
9292
token: IAIInlineChatService,
93-
useClass: AIInlineChatService,
93+
useClass: InlineChatService,
9494
},
9595
{
9696
token: IChatManagerService,

packages/ai-native/src/browser/widget/inline-chat/inline-chat-editor.controller.ts

+20-54
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
Disposable,
1616
ErrorResponse,
1717
Event,
18+
FRAME_THREE,
1819
IAIReporter,
1920
IDisposable,
2021
ILogServiceClient,
@@ -30,14 +31,14 @@ import * as monaco from '@opensumi/ide-monaco';
3031
import { ICodeEditor } from '@opensumi/ide-monaco';
3132
import { monacoApi } from '@opensumi/ide-monaco/lib/browser/monaco-api';
3233

34+
import { AINativeContextKey } from '../../ai-core.contextkeys';
3335
import { BaseAIMonacoEditorController } from '../../contrib/base';
3436
import { CodeActionService } from '../../contrib/code-action/code-action.service';
35-
import { ERunStrategy } from '../../types';
3637
import { InlineDiffController } from '../inline-diff/inline-diff.controller';
3738

3839
import { InlineChatController } from './inline-chat-controller';
3940
import { InlineChatFeatureRegistry } from './inline-chat.feature.registry';
40-
import { AIInlineChatService, EInlineChatStatus, EResultKind } from './inline-chat.service';
41+
import { EInlineChatStatus, EResultKind, InlineChatService } from './inline-chat.service';
4142
import { AIInlineContentWidget } from './inline-content-widget';
4243

4344
export class InlineChatEditorController extends BaseAIMonacoEditorController {
@@ -51,7 +52,7 @@ export class InlineChatEditorController extends BaseAIMonacoEditorController {
5152
return this.injector.get(AINativeConfigService);
5253
}
5354

54-
private get aiInlineChatService(): AIInlineChatService {
55+
private get aiInlineChatService(): InlineChatService {
5556
return this.injector.get(IAIInlineChatService);
5657
}
5758

@@ -86,11 +87,13 @@ export class InlineChatEditorController extends BaseAIMonacoEditorController {
8687
private aiInlineContentWidget: AIInlineContentWidget;
8788
private aiInlineChatDisposable: Disposable = new Disposable();
8889
private aiInlineChatOperationDisposable: Disposable = new Disposable();
90+
private aiNativeContextKey: AINativeContextKey;
8991
private inlineChatInUsing = false;
9092

9193
private inlineDiffController: InlineDiffController;
9294

9395
mount(): IDisposable {
96+
this.aiNativeContextKey = this.injector.get(AINativeContextKey, [this.monacoEditor.contextKeyService]);
9497
this.inlineDiffController = InlineDiffController.get(this.monacoEditor)!;
9598

9699
if (!this.monacoEditor) {
@@ -178,7 +181,7 @@ export class InlineChatEditorController extends BaseAIMonacoEditorController {
178181
Event.debounce(
179182
Event.any<any>(this.monacoEditor.onDidChangeCursorSelection, this.monacoEditor.onMouseUp),
180183
(_, e) => e,
181-
100,
184+
FRAME_THREE,
182185
)(() => {
183186
if (!prefInlineChatAutoVisible || !needShowInlineChat) {
184187
return;
@@ -197,7 +200,7 @@ export class InlineChatEditorController extends BaseAIMonacoEditorController {
197200
return this.featureDisposable;
198201
}
199202

200-
private disposeAllWidget() {
203+
public disposeAllWidget() {
201204
[this.aiInlineContentWidget, this.aiInlineChatDisposable, this.aiInlineChatOperationDisposable].forEach(
202205
(widget) => {
203206
widget?.dispose();
@@ -224,9 +227,21 @@ export class InlineChatEditorController extends BaseAIMonacoEditorController {
224227
return;
225228
}
226229

230+
// 如果 inline input 正在展示,则不展示 inline chat
231+
const isInlineInputVisible = this.aiNativeContextKey.inlineInputWidgetIsVisible.get();
232+
if (isInlineInputVisible) {
233+
return;
234+
}
235+
236+
const isInlineStreaming = this.aiNativeContextKey.inlineInputWidgetIsStreaming.get();
237+
if (isInlineStreaming) {
238+
return;
239+
}
240+
227241
if (!this.aiNativeConfigService.capabilities.supportsInlineChat) {
228242
return;
229243
}
244+
230245
if (this.inlineChatInUsing) {
231246
return;
232247
}
@@ -243,12 +258,6 @@ export class InlineChatEditorController extends BaseAIMonacoEditorController {
243258

244259
this.showInlineContentWidget(monacoEditor, selection);
245260

246-
this.aiInlineChatDisposable.addDispose(
247-
this.inlineChatFeatureRegistry.onChatClick(() => {
248-
this.aiInlineChatService.launchInputVisible(true);
249-
}),
250-
);
251-
252261
this.aiInlineChatDisposable.addDispose(
253262
this.aiInlineContentWidget.onActionClick(({ actionId, source }) => {
254263
const handler = this.inlineChatFeatureRegistry.getEditorHandler(actionId);
@@ -297,49 +306,6 @@ export class InlineChatEditorController extends BaseAIMonacoEditorController {
297306
});
298307
}),
299308
);
300-
301-
this.aiInlineChatDisposable.addDispose(
302-
this.aiInlineContentWidget.onInteractiveInputValue(async (value) => {
303-
const handler = this.inlineChatFeatureRegistry.getInteractiveInputHandler();
304-
305-
if (!handler) {
306-
return;
307-
}
308-
309-
const strategy = await this.inlineChatFeatureRegistry.getInteractiveInputStrategyHandler()(monacoEditor, value);
310-
311-
const crossSelection = this.getCrossSelection(monacoEditor);
312-
if (!crossSelection) {
313-
return;
314-
}
315-
316-
this.runAction({
317-
monacoEditor,
318-
crossSelection,
319-
reporterFn: () => {
320-
const relationId = this.aiReporter.start(AIServiceType.InlineChatInput, {
321-
message: value,
322-
type: AIServiceType.InlineChatInput,
323-
source: 'input',
324-
actionSource: ActionSourceEnum.InlineChatInput,
325-
});
326-
return relationId;
327-
},
328-
execute:
329-
handler.execute && strategy === ERunStrategy.EXECUTE
330-
? handler.execute!.bind(this, monacoEditor, value, this.token)
331-
: undefined,
332-
providerPreview:
333-
handler.providePreviewStrategy && strategy === ERunStrategy.PREVIEW
334-
? handler.providePreviewStrategy.bind(this, monacoEditor, value, this.token)
335-
: undefined,
336-
extraData: {
337-
actionSource: ActionSourceEnum.InlineChatInput,
338-
actionType: strategy,
339-
},
340-
});
341-
}),
342-
);
343309
}
344310

345311
private getCrossSelection(monacoEditor: monaco.ICodeEditor) {

0 commit comments

Comments
 (0)