diff --git a/packages/ai-native/src/browser/ai-core.contribution.ts b/packages/ai-native/src/browser/ai-core.contribution.ts index e852d04458..5c554b647f 100644 --- a/packages/ai-native/src/browser/ai-core.contribution.ts +++ b/packages/ai-native/src/browser/ai-core.contribution.ts @@ -32,7 +32,6 @@ import { } from '@opensumi/ide-core-browser/lib/ai-native/command'; import { InlineChatIsVisible } from '@opensumi/ide-core-browser/lib/contextkey/ai-native'; import { DesignLayoutConfig } from '@opensumi/ide-core-browser/lib/layout/constants'; -import { TerminalRegistryToken } from '@opensumi/ide-core-common'; import { AI_NATIVE_SETTING_GROUP_TITLE, ChatFeatureRegistryToken, @@ -41,6 +40,7 @@ import { InlineChatFeatureRegistryToken, RenameCandidatesProviderRegistryToken, ResolveConflictRegistryToken, + TerminalRegistryToken, isUndefined, runWhenIdle, } from '@opensumi/ide-core-common'; @@ -63,12 +63,11 @@ import { AIEditorContribution } from './ai-editor.contribution'; import { AINativeService } from './ai-native.service'; import { ChatProxyService } from './chat/chat-proxy.service'; import { AIChatView } from './chat/chat.view'; -import { AIInlineCompletionsProvider } from './inline-completions/completeProvider'; -import { AICompletionsService } from './inline-completions/service/ai-completions.service'; +import { AIInlineCompletionsProvider } from './contrib/inline-completions/completeProvider'; +import { AICompletionsService } from './contrib/inline-completions/service/ai-completions.service'; +import { AIRunToolbar } from './contrib/run-toolbar/run-toolbar'; import { AIChatTabRenderer, AILeftTabRenderer, AIRightTabRenderer } from './layout/tabbar.view'; import { AIChatLogoAvatar } from './layout/view/avatar/avatar.view'; -import { OpenSumiLightBulbWidget } from './light-bulb-widget'; -import { AIRunToolbar } from './run/toolbar/run-toolbar'; import { AINativeCoreContribution, IChatFeatureRegistry, @@ -78,6 +77,7 @@ import { IResolveConflictRegistry, ITerminalProviderRegistry, } from './types'; +import { SumiLightBulbWidget } from './widget/light-bulb'; @Domain( ClientAppContribution, @@ -173,7 +173,7 @@ export class AINativeBrowserContribution registerEditorExtensionContribution(register: IEditorExtensionContribution): void { const { supportsInlineChat } = this.aiNativeConfigService.capabilities; if (supportsInlineChat) { - register(OpenSumiLightBulbWidget.ID, OpenSumiLightBulbWidget, EditorContributionInstantiation.Lazy); + register(SumiLightBulbWidget.ID, SumiLightBulbWidget, EditorContributionInstantiation.Lazy); } } diff --git a/packages/ai-native/src/browser/ai-editor.contribution.ts b/packages/ai-native/src/browser/ai-editor.contribution.ts index 1ffbbc0e1f..7c8843d4f4 100644 --- a/packages/ai-native/src/browser/ai-editor.contribution.ts +++ b/packages/ai-native/src/browser/ai-editor.contribution.ts @@ -1,143 +1,43 @@ -import debounce from 'lodash/debounce'; - -import { Autowired, INJECTOR_TOKEN, Injectable, Injector } from '@opensumi/di'; -import { AINativeConfigService, IAIInlineChatService, PreferenceService } from '@opensumi/ide-core-browser'; +import { Autowired, Injectable } from '@opensumi/di'; +import { AINativeConfigService } from '@opensumi/ide-core-browser'; import { IBrowserCtxMenu } from '@opensumi/ide-core-browser/lib/menu/next/renderer/ctxmenu/browser'; -import { - AIInlineChatContentWidgetId, - AINativeSettingSectionsId, - AISerivceType, - AbortError, - CancelResponse, - CancellationToken, - ChatResponse, - ContributionProvider, - Disposable, - ErrorResponse, - Event, - IAIReporter, - IDisposable, - IEventBus, - ILogServiceClient, - ILoggerManagerClient, - InlineChatFeatureRegistryToken, - MaybePromise, - ReplyResponse, - Schemes, - SupportLogNamespace, - getErrorMessage, - runWhenIdle, -} from '@opensumi/ide-core-common'; +import { ContributionProvider, Disposable, Event, IDisposable, Schemes } from '@opensumi/ide-core-common'; import { DesignBrowserCtxMenuService } from '@opensumi/ide-design/lib/browser/override/menu.service'; -import { WorkbenchEditorService } from '@opensumi/ide-editor'; -import { EditorSelectionChangeEvent, IEditor, IEditorFeatureContribution } from '@opensumi/ide-editor/lib/browser'; +import { IEditor, IEditorFeatureContribution } from '@opensumi/ide-editor/lib/browser'; import { BrowserCodeEditor } from '@opensumi/ide-editor/lib/browser/editor-collection.service'; -import { WorkbenchEditorServiceImpl } from '@opensumi/ide-editor/lib/browser/workbench-editor.service'; -import * as monaco from '@opensumi/ide-monaco'; -import { monaco as monacoApi } from '@opensumi/ide-monaco/lib/browser/monaco-api'; -import { languageFeaturesService } from '@opensumi/ide-monaco/lib/browser/monaco-api/languages'; -import { MonacoTelemetryService } from '@opensumi/ide-monaco/lib/browser/telemetry.service'; -import { listenReadable } from '@opensumi/ide-utils/lib/stream'; - -import { AI_DIFF_WIDGET_ID } from '../common'; -import { AINativeService } from './ai-native.service'; -import { AIInlineCompletionsProvider } from './inline-completions/completeProvider'; -import { AICompletionsService } from './inline-completions/service/ai-completions.service'; -import { LanguageParserService } from './languages/service'; -import { ICodeBlockInfo } from './languages/tree-sitter/language-facts/base'; -import { RenameSuggestionsService } from './rename/rename.service'; +import { CodeActionHandler } from './contrib/code-action/code-action.handler'; +import { InlineCompletionHandler } from './contrib/inline-completions/inline-completions.handler'; +import { RenameHandler } from './contrib/rename/rename.handler'; import { AINativeCoreContribution, IAIMiddleware } from './types'; -import { InlineChatController } from './widget/inline-chat/inline-chat-controller'; -import { InlineChatFeatureRegistry } from './widget/inline-chat/inline-chat.feature.registry'; -import { AIInlineChatService, EInlineChatStatus } from './widget/inline-chat/inline-chat.service'; -import { AIInlineContentWidget } from './widget/inline-chat/inline-content-widget'; -import { InlineDiffWidget } from './widget/inline-diff/inline-diff-widget'; +import { InlineChatHandler } from './widget/inline-chat/inline-chat.handler'; @Injectable() export class AIEditorContribution extends Disposable implements IEditorFeatureContribution { - @Autowired(INJECTOR_TOKEN) - private readonly injector: Injector; - @Autowired(AINativeConfigService) private readonly aiNativeConfigService: AINativeConfigService; - @Autowired(IAIInlineChatService) - private readonly aiInlineChatService: AIInlineChatService; - - @Autowired(AINativeService) - private readonly aiNativeService: AINativeService; - - @Autowired(ILoggerManagerClient) - private readonly loggerManagerClient: ILoggerManagerClient; - - @Autowired(PreferenceService) - private readonly preferenceService: PreferenceService; - @Autowired(IBrowserCtxMenu) private readonly ctxMenuRenderer: DesignBrowserCtxMenuService; - @Autowired(InlineChatFeatureRegistryToken) - private readonly inlineChatFeatureRegistry: InlineChatFeatureRegistry; - - @Autowired(IAIReporter) - private readonly aiReporter: IAIReporter; - @Autowired(AINativeCoreContribution) private readonly contributions: ContributionProvider; - @Autowired(AIInlineCompletionsProvider) - private readonly aiInlineCompletionsProvider: AIInlineCompletionsProvider; - - @Autowired(RenameSuggestionsService) - private readonly renameSuggestionService: RenameSuggestionsService; - - @Autowired(AICompletionsService) - private aiCompletionsService: AICompletionsService; - - @Autowired(IEventBus) - private eventBus: IEventBus; - - @Autowired(LanguageParserService) - private languageParserService: LanguageParserService; - - @Autowired(WorkbenchEditorService) - private workbenchEditorService: WorkbenchEditorServiceImpl; + @Autowired(InlineChatHandler) + private readonly inlineChatHandler: InlineChatHandler; - @Autowired() - private monacoTelemetryService: MonacoTelemetryService; + @Autowired(CodeActionHandler) + private readonly codeActionHandler: CodeActionHandler; - private latestMiddlewareCollector: IAIMiddleware; + @Autowired(InlineCompletionHandler) + private readonly inlineCompletionHandler: InlineCompletionHandler; - private logger: ILogServiceClient; - - constructor() { - super(); - - this.logger = this.loggerManagerClient.getLogger(SupportLogNamespace.Browser); - } - - private aiDiffWidget: InlineDiffWidget; - private aiInlineContentWidget: AIInlineContentWidget; - private aiInlineChatDisposed: Disposable = new Disposable(); - private aiInlineChatOperationDisposed: Disposable = new Disposable(); + @Autowired(RenameHandler) + private readonly renameHandler: RenameHandler; private modelSessionDisposable: Disposable; private initialized: boolean = false; - private disposeAllWidget() { - [ - this.aiDiffWidget, - this.aiInlineContentWidget, - this.aiInlineChatDisposed, - this.aiInlineChatOperationDisposed, - ].forEach((widget) => { - widget?.dispose(); - }); - - this.inlineChatInUsing = false; - } - dispose(): void { super.dispose(); this.initialized = false; @@ -159,786 +59,71 @@ export class AIEditorContribution extends Disposable implements IEditorFeatureCo } this.initialized = true; - this.contributeInlineCompletionFeature(editor); - this.contributeInlineChatFeature(editor); - this.registerLanguageFeatures(editor); - }), - ); - const { monacoEditor } = editor; - - this.disposables.push( - monacoEditor.onDidScrollChange(() => { - /** - * 其他的 ctxmenu 服务注册的菜单在 onHide 函数里会有其他逻辑处理,例如在 editor.context.ts 会在 hide 的时候 focus 编辑器,影响使用 - */ - this.ctxMenuRenderer.onHide = undefined; - this.ctxMenuRenderer.hide(true); - }), - ); - - return this; - } - - protected contributeInlineCompletionFeature(editor: IEditor): void { - const { monacoEditor } = editor; - // 判断用户是否选择了一块区域或者移动光标 取消掉请补全求 - const selectionChange = () => { - this.aiCompletionsService.hideStatusBarItem(); - const selection = monacoEditor.getSelection(); - if (!selection) { - return; - } - - // 判断是否选中区域 - if (selection.startLineNumber !== selection.endLineNumber || selection.startColumn !== selection.endColumn) { - this.aiInlineCompletionsProvider.cancelRequest(); - } - requestAnimationFrame(() => { - this.aiCompletionsService.setVisibleCompletion(false); - }); - }; - - const debouncedSelectionChange = debounce(selectionChange, 50, { - maxWait: 200, - leading: true, - trailing: true, - }); + this.addDispose( + Event.debounce( + monacoEditor.onWillChangeModel, + (_, e) => e, + 300, + )(() => { + this.registerLanguageFeatures(editor); + }), + ); + this.registerLanguageFeatures(editor); - this.disposables.push( - this.eventBus.on(EditorSelectionChangeEvent, (e) => { - if (e.payload.source === 'mouse') { - debouncedSelectionChange(); - } else { - debouncedSelectionChange.cancel(); - selectionChange(); - } - }), - monacoEditor.onDidChangeModelContent((e) => { - const changes = e.changes; - for (const change of changes) { - if (change.text === '') { - this.aiInlineCompletionsProvider.isDelEvent = true; - this.aiInlineCompletionsProvider.cancelRequest(); - } else { - this.aiInlineCompletionsProvider.isDelEvent = false; - } - } - }), - monacoEditor.onWillChangeModel(() => { - this.aiCompletionsService.hideStatusBarItem(); - this.disposeAllWidget(); - }), - monacoEditor.onDidBlurEditorText(() => { - this.aiCompletionsService.hideStatusBarItem(); - this.aiCompletionsService.setVisibleCompletion(false); + this.addDispose(this.inlineCompletionHandler.registerInlineCompletionFeature(editor)); + this.addDispose(this.inlineChatHandler.registerInlineChatFeature(editor)); }), ); - } - protected contributeInlineChatFeature(editor: IEditor): void { const { monacoEditor } = editor; this.disposables.push( - this.aiNativeService.onInlineChatVisible((value: boolean) => { - if (value) { - this.showInlineChat(editor); - } else { - this.aiNativeService.cancelToken(); - this.disposeAllWidget(); - } - }), - // 通过 code actions 来透出我们 inline chat 的功能 - this.inlineChatFeatureRegistry.onCodeActionRun(({ id, range }) => { - const currentEditor = this.workbenchEditorService.currentEditor; - - if (currentEditor?.currentUri !== editor.currentUri) { - return; - } - - monacoEditor.setSelection(range); - this.showInlineChat(editor); - if (this.aiInlineContentWidget) { - this.aiInlineContentWidget.clickActionId(id, 'codeAction'); - } - }), - ); - - let needShowInlineChat = false; - this.disposables.push( - monacoEditor.onMouseDown(() => { - needShowInlineChat = false; - }), - monacoEditor.onMouseUp((event) => { - const target = event.target; - const detail = (target as any).detail; - if (detail && typeof detail === 'string' && detail === AIInlineChatContentWidgetId) { - needShowInlineChat = false; - } else { - needShowInlineChat = true; - } - }), - ); - - let prefInlineChatAutoVisible = this.preferenceService.getValid( - AINativeSettingSectionsId.INLINE_CHAT_AUTO_VISIBLE, - true, - ); - this.disposables.push( - this.preferenceService.onSpecificPreferenceChange( - AINativeSettingSectionsId.INLINE_CHAT_AUTO_VISIBLE, - ({ newValue }) => { - prefInlineChatAutoVisible = newValue; - }, - ), - ); - - this.disposables.push( - Event.debounce( - Event.any(monacoEditor.onDidChangeCursorSelection, monacoEditor.onMouseUp), - (_, e) => e, - 100, - )(() => { - if (!prefInlineChatAutoVisible || !needShowInlineChat) { - return; - } - - if ( - this.aiInlineChatService.status !== EInlineChatStatus.READY && - this.aiInlineChatService.status !== EInlineChatStatus.ERROR - ) { - return; + monacoEditor.onDidScrollChange(() => { + if (this.ctxMenuRenderer.visible) { + this.ctxMenuRenderer.hide(true); } - - this.showInlineChat(editor); }), ); - } - - shouldAbortRequest(model: monaco.ITextModel) { - if (model.uri.scheme !== Schemes.file) { - return true; - } - return false; + return this; } - protected inlineChatInUsing = false; - protected async showInlineChat(editor: IEditor): Promise { - if (!this.aiNativeConfigService.capabilities.supportsInlineChat) { - return; - } - if (this.inlineChatInUsing) { - return; - } - - this.inlineChatInUsing = true; - - this.disposeAllWidget(); - + private async registerLanguageFeatures(editor: IEditor): Promise { const { monacoEditor } = editor; - const selection = monacoEditor.getSelection(); - - if (!selection || selection.isEmpty()) { - this.disposeAllWidget(); - return; - } - - this.aiInlineChatDisposed.addDispose(this.aiInlineChatService.launchChatStatus(EInlineChatStatus.READY)); - - this.aiInlineContentWidget = this.injector.get(AIInlineContentWidget, [monacoEditor]); - - this.aiInlineContentWidget.show({ - selection, - }); - - this.aiInlineChatDisposed.addDispose( - this.aiInlineContentWidget.onActionClick((action) => { - this.runInlineChatAction(action, monacoEditor); - }), - ); - } - - private async runInlineChatAction( - { - actionId: id, - source, - }: { - actionId: string; - source: string; - }, - monacoEditor: monaco.ICodeEditor, - ) { - const handler = this.inlineChatFeatureRegistry.getEditorHandler(id); - const action = this.inlineChatFeatureRegistry.getAction(id); - if (!handler || !action) { - return; - } - - const selection = monacoEditor.getSelection(); - if (!selection) { - this.logger.error('No selection found, aborting inline chat action.'); - return; - } - - const { execute, providerDiffPreviewStrategy } = handler; - - if (execute) { - await execute(monacoEditor); - this.disposeAllWidget(); - } - - if (providerDiffPreviewStrategy) { - const crossSelection = selection - .setStartPosition(selection.startLineNumber, 1) - .setEndPosition(selection.endLineNumber, Number.MAX_SAFE_INTEGER); - - const relationId = this.aiReporter.start(action.name, { - message: action.name, - type: AISerivceType.InlineChat, - source, - runByCodeAction: source === 'codeAction', - }); - - await this.handleDiffPreviewStrategy( - monacoEditor, - providerDiffPreviewStrategy, - crossSelection, - relationId, - false, - ); - - this.aiInlineChatDisposed.addDispose([ - this.aiInlineChatService.onDiscard(() => { - this.aiReporter.end(relationId, { message: 'discard', success: true, isDrop: true }); - this.disposeAllWidget(); - }), - this.aiInlineChatService.onRegenerate(async () => { - await this.handleDiffPreviewStrategy( - monacoEditor, - providerDiffPreviewStrategy, - crossSelection, - relationId, - true, - ); - }), - ]); + if (this.modelSessionDisposable) { + this.modelSessionDisposable.dispose(); } - } - private async handleDiffPreviewStrategy( - monacoEditor: monaco.ICodeEditor, - strategy: ( - editor: monaco.ICodeEditor, - cancelToken: CancellationToken, - ) => MaybePromise, - crossSelection: monaco.Selection, - relationId: string, - isRetry: boolean, - ): Promise { const model = monacoEditor.getModel(); - - this.aiDiffWidget?.dispose(); - this.aiInlineChatOperationDisposed.dispose(); - this.aiInlineChatDisposed.addDispose(this.aiInlineChatService.launchChatStatus(EInlineChatStatus.THINKING)); - - const startTime = Date.now(); - - if (this.aiNativeService.cancelIndicator.token.isCancellationRequested) { - this.convertInlineChatStatus(EInlineChatStatus.READY, { - relationId, - message: 'abort', - startTime, - isRetry, - isStop: true, - }); + if (!model) { return; } - const response = await strategy(monacoEditor, this.aiNativeService.cancelIndicator.token); + this.modelSessionDisposable = new Disposable(); + const languageId = model.getLanguageId(); - this.visibleDiffWidget( - monacoEditor, - { crossSelection, chatResponse: response }, - { relationId, startTime, isRetry }, - ); + if (this.aiNativeConfigService.capabilities.supportsInlineCompletion) { + let latestMiddlewareCollector: IAIMiddleware | undefined; - this.aiInlineChatOperationDisposed.addDispose([ - this.aiInlineChatService.onAccept(() => { - this.aiReporter.end(relationId, { message: 'accept', success: true, isReceive: true }); - const newValue = this.aiDiffWidget?.getModifiedModel()?.getValue() || ''; - - monacoEditor.getModel()?.pushEditOperations(null, [{ range: crossSelection, text: newValue }], () => null); - runWhenIdle(() => { - this.disposeAllWidget(); - }); - }), - this.aiInlineChatService.onThumbs((isLike: boolean) => { - this.aiReporter.end(relationId, { isLike }); - }), - this.aiDiffWidget.onMaxLineCount((count) => { - requestAnimationFrame(() => { - if (crossSelection.endLineNumber === model!.getLineCount()) { - // 如果用户是选中了最后一行,直接显示在最后一行 - const lineHeight = monacoEditor.getOption(monacoApi.editor.EditorOption.lineHeight); - this.aiInlineContentWidget.offsetTop(lineHeight * count + 12); - } - }); - }), - ]); - } - - private formatAnswer(answer: string, crossCode: string): string { - const leadingWhitespaceMatch = crossCode.match(/^\s*/); - const indent = leadingWhitespaceMatch ? leadingWhitespaceMatch[0] : ' '; - return answer - .split('\n') - .map((line) => `${indent}${line}`) - .join('\n'); - } - - private convertInlineChatStatus( - status: EInlineChatStatus, - reportInfo: { - relationId: string; - message: string; - startTime: number; - isRetry?: boolean; - isStop?: boolean; - }, - ): void { - const { relationId, message, startTime, isRetry, isStop } = reportInfo; - - this.aiInlineChatDisposed.addDispose(this.aiInlineChatService.launchChatStatus(status)); - this.aiReporter.end(relationId, { - message, - success: status !== EInlineChatStatus.ERROR, - replytime: Date.now() - startTime, - isStop, - isRetry, - }); - } - - private visibleDiffWidget( - monacoEditor: monaco.ICodeEditor, - options: { - crossSelection: monaco.Selection; - chatResponse?: ChatResponse | InlineChatController; - }, - reportInfo: { - relationId: string; - startTime: number; - isRetry: boolean; - }, - ): void { - const { crossSelection, chatResponse } = options; - const { relationId, startTime, isRetry } = reportInfo; - - this.aiDiffWidget = this.injector.get(InlineDiffWidget, [ - AI_DIFF_WIDGET_ID, - { - editor: monacoEditor, - selection: crossSelection, - }, - ]); - this.aiDiffWidget.create(); - this.aiDiffWidget.showByLine( - crossSelection.startLineNumber - 1, - crossSelection.endLineNumber - crossSelection.startLineNumber + 2, - ); - - if (InlineChatController.is(chatResponse)) { - const controller = chatResponse as InlineChatController; - - this.aiInlineChatOperationDisposed.addDispose( - this.aiDiffWidget.onReady(() => { - const modifiedModel = this.aiDiffWidget.getModifiedModel(); - if (!modifiedModel) { - return; - } - - let isAbort = false; - - this.aiInlineChatOperationDisposed.addDispose([ - controller.onData((data) => { - if (ReplyResponse.is(data)) { - isAbort = false; - const { message } = data; - - const lastLine = modifiedModel.getLineCount(); - const lastColumn = modifiedModel.getLineMaxColumn(lastLine); - - const range = new monaco.Range(lastLine, lastColumn, lastLine, lastColumn); - - const edit = { - range, - text: message || '', - }; - modifiedModel.pushEditOperations(null, [edit], () => null); - this.aiDiffWidget.layout(); - } - }), - controller.onError((error) => { - this.convertInlineChatStatus(EInlineChatStatus.ERROR, { - relationId, - message: error.message || '', - startTime, - isRetry, - }); - }), - controller.onAbort(() => { - this.convertInlineChatStatus(EInlineChatStatus.READY, { - relationId, - message: 'abort', - startTime, - isRetry, - isStop: true, - }); - }), - controller.onEnd(() => { - this.convertInlineChatStatus(EInlineChatStatus.DONE, { - relationId, - message: '', - startTime, - isRetry, - }); - }), - ]); - }), - ); - } else { - const model = monacoEditor.getModel(); - const crossCode = model!.getValueInRange(crossSelection); - - if (this.aiInlineChatDisposed.disposed || CancelResponse.is(chatResponse)) { - this.convertInlineChatStatus(EInlineChatStatus.READY, { - relationId, - message: (chatResponse as CancelResponse).message || '', - startTime, - isRetry, - isStop: true, - }); - return; - } - - if (ErrorResponse.is(chatResponse)) { - this.convertInlineChatStatus(EInlineChatStatus.ERROR, { - relationId, - message: (chatResponse as ErrorResponse).message || '', - startTime, - isRetry, - }); - return; - } - - this.convertInlineChatStatus(EInlineChatStatus.DONE, { - relationId, - message: '', - startTime, - isRetry, + this.contributions.getContributions().forEach((contribution) => { + if (contribution.middleware) { + latestMiddlewareCollector = contribution.middleware; + } }); - let answer = (chatResponse as ReplyResponse).message; - answer = this.formatAnswer(answer, crossCode); - - this.aiInlineChatOperationDisposed.addDispose( - this.aiDiffWidget.onReady(() => { - const modifiedModel = this.aiDiffWidget.getModifiedModel(); - if (!modifiedModel) { - return; - } - - modifiedModel.setValue(answer); - }), + this.modelSessionDisposable.addDispose( + this.inlineCompletionHandler.registerProvider(editor, languageId, latestMiddlewareCollector), ); } - this.aiInlineContentWidget?.setOptions({ - position: { - lineNumber: crossSelection.endLineNumber + 1, - column: 1, - }, - }); - this.aiInlineContentWidget?.layoutContentWidget(); - } - - private async registerLanguageFeatures(editor: IEditor): Promise { - const { monacoEditor } = editor; - - const doRegister = async () => { - if (this.modelSessionDisposable) { - this.modelSessionDisposable.dispose(); - } - - const model = monacoEditor.getModel(); - if (!model) { - return; - } - - this.modelSessionDisposable = new Disposable(); - - const languageId = model.getLanguageId(); - - if (this.aiNativeConfigService.capabilities.supportsInlineCompletion) { - this.contributions.getContributions().forEach((contribution) => { - if (contribution.middleware) { - this.latestMiddlewareCollector = contribution.middleware; - } - }); - - this.aiInlineCompletionsProvider.registerEditor(editor); - this.modelSessionDisposable.addDispose({ - dispose: () => { - this.aiInlineCompletionsProvider.dispose(); - }, - }); - this.modelSessionDisposable.addDispose( - monacoApi.languages.registerInlineCompletionsProvider(languageId, { - provideInlineCompletions: async (model, position, context, token) => { - if (this.shouldAbortRequest(model)) { - return; - } - - if (this.latestMiddlewareCollector?.language?.provideInlineCompletions) { - this.aiCompletionsService.setMiddlewareComplete( - this.latestMiddlewareCollector?.language?.provideInlineCompletions, - ); - } - - const list = await this.aiInlineCompletionsProvider.provideInlineCompletionItems( - model, - position, - context, - token, - ); - - this.logger.log( - 'provideInlineCompletions: ', - list.items.map((data) => data.insertText), - ); - - return list; - }, - freeInlineCompletions() {}, - handleItemDidShow: (completions) => { - if (completions.items.length > 0) { - this.aiCompletionsService.setVisibleCompletion(true); - } - }, - }), - ); - } - - if (this.aiNativeConfigService.capabilities.supportsRenameSuggestions) { - this.modelSessionDisposable.addDispose(this.contributeRenameFeature(languageId)); - } - - if (this.aiNativeConfigService.capabilities.supportsInlineChat) { - this.modelSessionDisposable.addDispose(this.contributeCodeActionFeature(languageId, editor)); - } - }; - - this.disposables.push(Event.debounce(monacoEditor.onWillChangeModel, (_, e) => e, 300)(doRegister.bind(this))); - - doRegister(); - } - - lastModelRequestRenameEndTime: number | undefined; - lastModelRequestRenameSessionId: string | undefined; - - protected contributeRenameFeature(languageId: string): IDisposable { - const disposable = new Disposable(); - - const provider = async (model: monaco.ITextModel, range: monaco.IRange, token: CancellationToken) => { - if (this.shouldAbortRequest(model)) { - return; - } - - this.lastModelRequestRenameSessionId = undefined; - - const startTime = +new Date(); - const relationId = this.aiReporter.start('rename', { - message: 'start', - type: AISerivceType.Rename, - modelRequestStartTime: startTime, - }); - this.lastModelRequestRenameSessionId = relationId; - - const toDispose = token.onCancellationRequested(() => { - const endTime = +new Date(); - - this.aiReporter.end(relationId, { - message: 'cancel', - success: false, - isCancel: true, - modelRequestStartTime: startTime, - modelRequestEndTime: endTime, - }); - - this.lastModelRequestRenameSessionId = undefined; - }); - - try { - const result = await this.renameSuggestionService.provideRenameSuggestions(model, range, token); - toDispose.dispose(); - this.lastModelRequestRenameEndTime = +new Date(); - return result; - } catch (error) { - const endTime = +new Date(); - this.aiReporter.end(relationId, { - message: 'error:' + getErrorMessage(error), - success: false, - modelRequestStartTime: startTime, - modelRequestEndTime: endTime, - }); - throw error; - } - }; - - disposable.addDispose([ - monacoApi.languages.registerNewSymbolNameProvider(languageId, { - provideNewSymbolNames: provider, - }), - this.monacoTelemetryService.onEventLog('renameInvokedEvent', (event) => { - if (this.lastModelRequestRenameSessionId) { - this.aiReporter.end(this.lastModelRequestRenameSessionId, { - message: 'done', - success: true, - modelRequestEndTime: this.lastModelRequestRenameEndTime, - ...event, - }); - } - }), - ]); - - return disposable; - } - - protected contributeCodeActionFeature(languageId: string, editor: IEditor): IDisposable { - const disposable = new Disposable(); - - let prefInlineChatActionEnabled = this.preferenceService.getValid( - AINativeSettingSectionsId.INLINE_CHAT_CODE_ACTION_ENABLED, - true, - ); - - if (!prefInlineChatActionEnabled) { - return disposable; + if (this.aiNativeConfigService.capabilities.supportsRenameSuggestions) { + this.modelSessionDisposable.addDispose(this.renameHandler.registerRenameFeature(languageId)); } - const { monacoEditor } = editor; - const { languageParserService, inlineChatFeatureRegistry, shouldAbortRequest } = this; - - let codeActionDispose: IDisposable | undefined; - - disposable.addDispose( - this.preferenceService.onSpecificPreferenceChange( - AINativeSettingSectionsId.INLINE_CHAT_CODE_ACTION_ENABLED, - ({ newValue }) => { - prefInlineChatActionEnabled = newValue; - if (newValue) { - register(); - } else { - if (codeActionDispose) { - codeActionDispose.dispose(); - codeActionDispose = undefined; - } - } - }, - ), - ); - - register(); - - return disposable; - - function register() { - if (codeActionDispose) { - codeActionDispose.dispose(); - codeActionDispose = undefined; - } - - codeActionDispose = languageFeaturesService.codeActionProvider.register(languageId, { - provideCodeActions: async (model) => { - if (shouldAbortRequest(model)) { - return; - } - - if (!prefInlineChatActionEnabled) { - return; - } - - const parser = languageParserService.createParser(languageId); - if (!parser) { - return; - } - const actions = inlineChatFeatureRegistry.getCodeActions(); - if (!actions || actions.length === 0) { - return; - } - - const cursorPosition = monacoEditor.getPosition(); - if (!cursorPosition) { - return; - } - - function constructCodeActions(info: ICodeBlockInfo) { - return { - actions: actions.map((v) => { - const command = {} as monaco.Command; - if (v.command) { - command.id = v.command.id; - command.arguments = [info.range]; - } - - let title = v.title; - - switch (info.infoCategory) { - case 'function': { - title = title + ` for Function: ${info.name}`; - } - } - - return { - ...v, - title, - ranges: [info.range], - command, - }; - }) as monaco.CodeAction[], - dispose() {}, - }; - } - - const info = await parser.provideCodeBlockInfo(model, cursorPosition); - if (info) { - return constructCodeActions(info); - } - - // check current line is empty - const currentLineLength = model.getLineLength(cursorPosition.lineNumber); - if (currentLineLength !== 0) { - return; - } - - // 获取视窗范围内的代码块 - const ranges = monacoEditor.getVisibleRanges(); - if (ranges.length === 0) { - return; - } - - // 查找从当前行至视窗最后一行的代码块中是否包含函数 - const newRange = new monaco.Range(cursorPosition.lineNumber, 0, ranges[0].endLineNumber + 1, 0); - - const rangeInfo = await parser.provideCodeBlockInfoInRange(model, newRange); - if (rangeInfo) { - return constructCodeActions(rangeInfo); - } - }, - }); - - disposable.addDispose(codeActionDispose); + if (this.aiNativeConfigService.capabilities.supportsInlineChat) { + this.modelSessionDisposable.addDispose(this.codeActionHandler.registerCodeActionFeature(languageId, editor)); } } } diff --git a/packages/ai-native/src/browser/contrib/code-action/code-action.handler.ts b/packages/ai-native/src/browser/contrib/code-action/code-action.handler.ts new file mode 100644 index 0000000000..442ed9225d --- /dev/null +++ b/packages/ai-native/src/browser/contrib/code-action/code-action.handler.ts @@ -0,0 +1,145 @@ +import { Autowired, Injectable } from '@opensumi/di'; +import { Disposable, IDisposable, PreferenceService } from '@opensumi/ide-core-browser'; +import { AINativeSettingSectionsId, InlineChatFeatureRegistryToken, Schemes } from '@opensumi/ide-core-common'; +import { IEditor } from '@opensumi/ide-editor/lib/browser'; +import * as monaco from '@opensumi/ide-monaco'; +import { languageFeaturesService } from '@opensumi/ide-monaco/lib/browser/monaco-api/languages'; + +import { LanguageParserService } from '../../languages/service'; +import { ICodeBlockInfo } from '../../languages/tree-sitter/language-facts/base'; +import { InlineChatFeatureRegistry } from '../../widget/inline-chat/inline-chat.feature.registry'; + +@Injectable() +export class CodeActionHandler extends Disposable { + @Autowired(InlineChatFeatureRegistryToken) + private readonly inlineChatFeatureRegistry: InlineChatFeatureRegistry; + + @Autowired(PreferenceService) + private readonly preferenceService: PreferenceService; + + @Autowired(LanguageParserService) + private readonly languageParserService: LanguageParserService; + + public registerCodeActionFeature(languageId: string, editor: IEditor): IDisposable { + const disposable = new Disposable(); + + let prefInlineChatActionEnabled = this.preferenceService.getValid( + AINativeSettingSectionsId.INLINE_CHAT_CODE_ACTION_ENABLED, + true, + ); + + if (!prefInlineChatActionEnabled) { + return disposable; + } + + const { monacoEditor } = editor; + const { languageParserService, inlineChatFeatureRegistry } = this; + + let codeActionDispose: IDisposable | undefined; + + disposable.addDispose( + this.preferenceService.onSpecificPreferenceChange( + AINativeSettingSectionsId.INLINE_CHAT_CODE_ACTION_ENABLED, + ({ newValue }) => { + prefInlineChatActionEnabled = newValue; + if (newValue) { + register(); + } else { + if (codeActionDispose) { + codeActionDispose.dispose(); + codeActionDispose = undefined; + } + } + }, + ), + ); + + register(); + + return disposable; + + function register() { + if (codeActionDispose) { + codeActionDispose.dispose(); + codeActionDispose = undefined; + } + + codeActionDispose = languageFeaturesService.codeActionProvider.register(languageId, { + provideCodeActions: async (model) => { + if (!prefInlineChatActionEnabled) { + return; + } + + const parser = languageParserService.createParser(languageId); + if (!parser) { + return; + } + const actions = inlineChatFeatureRegistry.getCodeActions(); + if (!actions || actions.length === 0) { + return; + } + + const cursorPosition = monacoEditor.getPosition(); + if (!cursorPosition) { + return; + } + + function constructCodeActions(info: ICodeBlockInfo) { + return { + actions: actions.map((v) => { + const command = {} as monaco.Command; + if (v.command) { + command.id = v.command.id; + command.arguments = [info.range]; + } + + let title = v.title; + + switch (info.infoCategory) { + case 'function': { + title = title + ` for Function: ${info.name}`; + } + } + + return { + ...v, + title, + ranges: [info.range], + command, + }; + }) as monaco.CodeAction[], + dispose() {}, + }; + } + + const info = await parser.provideCodeBlockInfo(model, cursorPosition); + if (info) { + return constructCodeActions(info); + } + + // check current line is empty + const currentLineLength = model.getLineLength(cursorPosition.lineNumber); + if (currentLineLength !== 0) { + return; + } + + // 获取视窗范围内的代码块 + const ranges = monacoEditor.getVisibleRanges(); + if (ranges.length === 0) { + return; + } + + // 查找从当前行至视窗最后一行的代码块中是否包含函数 + const newRange = new monaco.Range(cursorPosition.lineNumber, 0, ranges[0].endLineNumber + 1, 0); + + const rangeInfo = await parser.provideCodeBlockInfoInRange(model, newRange); + if (rangeInfo) { + return constructCodeActions(rangeInfo); + } + }, + }); + + disposable.addDispose(codeActionDispose); + } + } +} diff --git a/packages/ai-native/src/browser/inline-completions/completeProvider.ts b/packages/ai-native/src/browser/contrib/inline-completions/completeProvider.ts similarity index 99% rename from packages/ai-native/src/browser/inline-completions/completeProvider.ts rename to packages/ai-native/src/browser/contrib/inline-completions/completeProvider.ts index 7a7891811a..e392fc4d57 100644 --- a/packages/ai-native/src/browser/inline-completions/completeProvider.ts +++ b/packages/ai-native/src/browser/contrib/inline-completions/completeProvider.ts @@ -6,7 +6,7 @@ import { AISerivceType, IAIReporter } from '@opensumi/ide-core-common/lib/types/ import { IEditor } from '@opensumi/ide-editor'; import * as monaco from '@opensumi/ide-monaco'; -import { AINativeContextKey } from '../contextkey/ai-native.contextkey.service'; +import { AINativeContextKey } from '../../contextkey/ai-native.contextkey.service'; import { DEFAULT_COMPLECTION_MODEL } from './constants'; import { CompletionRequestBean, InlayList, InlineCompletionItem } from './model/competionModel'; diff --git a/packages/ai-native/src/browser/inline-completions/constants.ts b/packages/ai-native/src/browser/contrib/inline-completions/constants.ts similarity index 100% rename from packages/ai-native/src/browser/inline-completions/constants.ts rename to packages/ai-native/src/browser/contrib/inline-completions/constants.ts diff --git a/packages/ai-native/src/browser/contrib/inline-completions/inline-completions.handler.ts b/packages/ai-native/src/browser/contrib/inline-completions/inline-completions.handler.ts new file mode 100644 index 0000000000..f6ef32a969 --- /dev/null +++ b/packages/ai-native/src/browser/contrib/inline-completions/inline-completions.handler.ts @@ -0,0 +1,116 @@ +import debounce from 'lodash/debounce'; + +import { Autowired, Injectable } from '@opensumi/di'; +import { Disposable, IDisposable } from '@opensumi/ide-core-browser'; +import { IEventBus, Schemes } from '@opensumi/ide-core-common'; +import { EditorSelectionChangeEvent, IEditor } from '@opensumi/ide-editor/lib/browser'; +import * as monaco from '@opensumi/ide-monaco'; +import { monacoApi } from '@opensumi/ide-monaco/lib/browser/monaco-api'; + +import { IAIMiddleware } from '../../types'; + +import { AIInlineCompletionsProvider } from './completeProvider'; +import { AICompletionsService } from './service/ai-completions.service'; + +@Injectable() +export class InlineCompletionHandler extends Disposable { + @Autowired(IEventBus) + private eventBus: IEventBus; + + @Autowired(AIInlineCompletionsProvider) + private readonly aiInlineCompletionsProvider: AIInlineCompletionsProvider; + + @Autowired(AICompletionsService) + private aiCompletionsService: AICompletionsService; + + public registerInlineCompletionFeature(editor: IEditor): IDisposable { + const { monacoEditor } = editor; + // 判断用户是否选择了一块区域或者移动光标 取消掉请补全求 + const selectionChange = () => { + this.aiCompletionsService.hideStatusBarItem(); + const selection = monacoEditor.getSelection(); + if (!selection) { + return; + } + + // 判断是否选中区域 + if (selection.startLineNumber !== selection.endLineNumber || selection.startColumn !== selection.endColumn) { + this.aiInlineCompletionsProvider.cancelRequest(); + } + requestAnimationFrame(() => { + this.aiCompletionsService.setVisibleCompletion(false); + }); + }; + + const debouncedSelectionChange = debounce(selectionChange, 50, { + maxWait: 200, + leading: true, + trailing: true, + }); + + this.disposables.push( + this.eventBus.on(EditorSelectionChangeEvent, (e) => { + if (e.payload.source === 'mouse') { + debouncedSelectionChange(); + } else { + debouncedSelectionChange.cancel(); + selectionChange(); + } + }), + monacoEditor.onDidChangeModelContent((e) => { + const changes = e.changes; + for (const change of changes) { + if (change.text === '') { + this.aiInlineCompletionsProvider.isDelEvent = true; + this.aiInlineCompletionsProvider.cancelRequest(); + } else { + this.aiInlineCompletionsProvider.isDelEvent = false; + } + } + }), + monacoEditor.onWillChangeModel(() => { + this.aiCompletionsService.hideStatusBarItem(); + }), + monacoEditor.onDidBlurEditorText(() => { + this.aiCompletionsService.hideStatusBarItem(); + this.aiCompletionsService.setVisibleCompletion(false); + }), + ); + + return this; + } + + public registerProvider(editor: IEditor, languageId: string, middlewareCollector?: IAIMiddleware): IDisposable { + const disposable = new Disposable(); + + this.aiInlineCompletionsProvider.registerEditor(editor); + + disposable.addDispose(this.aiInlineCompletionsProvider); + disposable.addDispose( + monacoApi.languages.registerInlineCompletionsProvider(languageId, { + provideInlineCompletions: async (model, position, context, token) => { + if (middlewareCollector?.language?.provideInlineCompletions) { + this.aiCompletionsService.setMiddlewareComplete(middlewareCollector?.language?.provideInlineCompletions); + } + + const list = await this.aiInlineCompletionsProvider.provideInlineCompletionItems( + model, + position, + context, + token, + ); + + return list; + }, + freeInlineCompletions() {}, + handleItemDidShow: (completions) => { + if (completions.items.length > 0) { + this.aiCompletionsService.setVisibleCompletion(true); + } + }, + }), + ); + + return disposable; + } +} diff --git a/packages/ai-native/src/browser/inline-completions/model/competionModel.ts b/packages/ai-native/src/browser/contrib/inline-completions/model/competionModel.ts similarity index 100% rename from packages/ai-native/src/browser/inline-completions/model/competionModel.ts rename to packages/ai-native/src/browser/contrib/inline-completions/model/competionModel.ts diff --git a/packages/ai-native/src/browser/inline-completions/prompt/const.ts b/packages/ai-native/src/browser/contrib/inline-completions/prompt/const.ts similarity index 100% rename from packages/ai-native/src/browser/inline-completions/prompt/const.ts rename to packages/ai-native/src/browser/contrib/inline-completions/prompt/const.ts diff --git a/packages/ai-native/src/browser/inline-completions/prompt/importedFiles.ts b/packages/ai-native/src/browser/contrib/inline-completions/prompt/importedFiles.ts similarity index 97% rename from packages/ai-native/src/browser/inline-completions/prompt/importedFiles.ts rename to packages/ai-native/src/browser/contrib/inline-completions/prompt/importedFiles.ts index fb8e459ea1..52db61a781 100644 --- a/packages/ai-native/src/browser/inline-completions/prompt/importedFiles.ts +++ b/packages/ai-native/src/browser/contrib/inline-completions/prompt/importedFiles.ts @@ -4,7 +4,7 @@ import { IFileServiceClient } from '@opensumi/ide-file-service'; import { Path } from '@opensumi/ide-utils/lib/path'; import { IWorkspaceService } from '@opensumi/ide-workspace'; -import { LanguageParserService } from '../../languages/service'; +import { LanguageParserService } from '../../../languages/service'; import { ICompletionContext, ImportedFileOptions, ResourceDocument } from '../types'; import { LANGUAGE_TO_SUFFIX } from './languages'; diff --git a/packages/ai-native/src/browser/inline-completions/prompt/jaccardMatcher.ts b/packages/ai-native/src/browser/contrib/inline-completions/prompt/jaccardMatcher.ts similarity index 100% rename from packages/ai-native/src/browser/inline-completions/prompt/jaccardMatcher.ts rename to packages/ai-native/src/browser/contrib/inline-completions/prompt/jaccardMatcher.ts diff --git a/packages/ai-native/src/browser/inline-completions/prompt/languages.ts b/packages/ai-native/src/browser/contrib/inline-completions/prompt/languages.ts similarity index 100% rename from packages/ai-native/src/browser/inline-completions/prompt/languages.ts rename to packages/ai-native/src/browser/contrib/inline-completions/prompt/languages.ts diff --git a/packages/ai-native/src/browser/inline-completions/prompt/matcher.ts b/packages/ai-native/src/browser/contrib/inline-completions/prompt/matcher.ts similarity index 100% rename from packages/ai-native/src/browser/inline-completions/prompt/matcher.ts rename to packages/ai-native/src/browser/contrib/inline-completions/prompt/matcher.ts diff --git a/packages/ai-native/src/browser/inline-completions/prompt/prompt.ts b/packages/ai-native/src/browser/contrib/inline-completions/prompt/prompt.ts similarity index 98% rename from packages/ai-native/src/browser/inline-completions/prompt/prompt.ts rename to packages/ai-native/src/browser/contrib/inline-completions/prompt/prompt.ts index 7b8a31047b..68b8574f31 100644 --- a/packages/ai-native/src/browser/inline-completions/prompt/prompt.ts +++ b/packages/ai-native/src/browser/contrib/inline-completions/prompt/prompt.ts @@ -3,8 +3,8 @@ import { Tiktoken } from 'js-tiktoken'; import { Injector } from '@opensumi/di'; -import { LanguageParser } from '../../languages/parser'; -import { LanguageParserService } from '../../languages/service'; +import { LanguageParser } from '../../../languages/parser'; +import { LanguageParserService } from '../../../languages/service'; import { StrategyType, WishListAttributeName } from '../types'; import { LANGUAGE_COMMENT_MARKERS } from './const'; diff --git a/packages/ai-native/src/browser/inline-completions/prompt/similarSnippets.ts b/packages/ai-native/src/browser/contrib/inline-completions/prompt/similarSnippets.ts similarity index 98% rename from packages/ai-native/src/browser/inline-completions/prompt/similarSnippets.ts rename to packages/ai-native/src/browser/contrib/inline-completions/prompt/similarSnippets.ts index 08433c2094..c8613d67e1 100644 --- a/packages/ai-native/src/browser/inline-completions/prompt/similarSnippets.ts +++ b/packages/ai-native/src/browser/contrib/inline-completions/prompt/similarSnippets.ts @@ -3,7 +3,7 @@ import { Injector } from '@opensumi/di'; import { IEditorDocumentModel, WorkbenchEditorService } from '@opensumi/ide-editor'; import { IWorkspaceService } from '@opensumi/ide-workspace'; -import { isDocumentValid } from '../../../common/utils'; +import { isDocumentValid } from '../../../../common/utils'; import { ICompletionContext, MatchSimilarSnippet, diff --git a/packages/ai-native/src/browser/inline-completions/prompt/tokenizer.ts b/packages/ai-native/src/browser/contrib/inline-completions/prompt/tokenizer.ts similarity index 100% rename from packages/ai-native/src/browser/inline-completions/prompt/tokenizer.ts rename to packages/ai-native/src/browser/contrib/inline-completions/prompt/tokenizer.ts diff --git a/packages/ai-native/src/browser/inline-completions/promptCache.ts b/packages/ai-native/src/browser/contrib/inline-completions/promptCache.ts similarity index 100% rename from packages/ai-native/src/browser/inline-completions/promptCache.ts rename to packages/ai-native/src/browser/contrib/inline-completions/promptCache.ts diff --git a/packages/ai-native/src/browser/inline-completions/provider.ts b/packages/ai-native/src/browser/contrib/inline-completions/provider.ts similarity index 100% rename from packages/ai-native/src/browser/inline-completions/provider.ts rename to packages/ai-native/src/browser/contrib/inline-completions/provider.ts diff --git a/packages/ai-native/src/browser/inline-completions/service/ai-completions.service.ts b/packages/ai-native/src/browser/contrib/inline-completions/service/ai-completions.service.ts similarity index 98% rename from packages/ai-native/src/browser/inline-completions/service/ai-completions.service.ts rename to packages/ai-native/src/browser/contrib/inline-completions/service/ai-completions.service.ts index 409a9b7b4a..01d956787a 100644 --- a/packages/ai-native/src/browser/inline-completions/service/ai-completions.service.ts +++ b/packages/ai-native/src/browser/contrib/inline-completions/service/ai-completions.service.ts @@ -10,7 +10,7 @@ import { } from '@opensumi/ide-core-common'; import { CompletionRT, IAIReporter } from '@opensumi/ide-core-common/lib/types/ai-native/reporter'; -import { IProvideInlineCompletionsSignature } from '../../types'; +import { IProvideInlineCompletionsSignature } from '../../../types'; import { CompletionRequestBean } from '../model/competionModel'; @Injectable() diff --git a/packages/ai-native/src/browser/inline-completions/types.ts b/packages/ai-native/src/browser/contrib/inline-completions/types.ts similarity index 100% rename from packages/ai-native/src/browser/inline-completions/types.ts rename to packages/ai-native/src/browser/contrib/inline-completions/types.ts diff --git a/packages/ai-native/src/browser/interface-navigation/interface-navigation.contribution.ts b/packages/ai-native/src/browser/contrib/interface-navigation/interface-navigation.contribution.ts similarity index 98% rename from packages/ai-native/src/browser/interface-navigation/interface-navigation.contribution.ts rename to packages/ai-native/src/browser/contrib/interface-navigation/interface-navigation.contribution.ts index 100da009ad..02ae0f4362 100644 --- a/packages/ai-native/src/browser/interface-navigation/interface-navigation.contribution.ts +++ b/packages/ai-native/src/browser/contrib/interface-navigation/interface-navigation.contribution.ts @@ -23,8 +23,8 @@ import { MouseTargetType } from '@opensumi/ide-monaco/lib/browser/monaco-exports import { IIconService, IThemeService, IconType } from '@opensumi/ide-theme'; import { IModelDeltaDecoration } from '@opensumi/monaco-editor-core/esm/vs/editor/common/model'; -import { LanguageParserService } from '../languages/service'; -import { toMonacoRange } from '../languages/tree-sitter/common'; +import { LanguageParserService } from '../../languages/service'; +import { toMonacoRange } from '../../languages/tree-sitter/common'; import styles from './interface-navigation.module.less'; diff --git a/packages/ai-native/src/browser/interface-navigation/interface-navigation.module.less b/packages/ai-native/src/browser/contrib/interface-navigation/interface-navigation.module.less similarity index 100% rename from packages/ai-native/src/browser/interface-navigation/interface-navigation.module.less rename to packages/ai-native/src/browser/contrib/interface-navigation/interface-navigation.module.less diff --git a/packages/ai-native/src/browser/merge-conflict/index.ts b/packages/ai-native/src/browser/contrib/merge-conflict/index.ts similarity index 100% rename from packages/ai-native/src/browser/merge-conflict/index.ts rename to packages/ai-native/src/browser/contrib/merge-conflict/index.ts diff --git a/packages/ai-native/src/browser/merge-conflict/merge-conflict.feature.registry.ts b/packages/ai-native/src/browser/contrib/merge-conflict/merge-conflict.feature.registry.ts similarity index 94% rename from packages/ai-native/src/browser/merge-conflict/merge-conflict.feature.registry.ts rename to packages/ai-native/src/browser/contrib/merge-conflict/merge-conflict.feature.registry.ts index 23b42a5c78..899568460d 100644 --- a/packages/ai-native/src/browser/merge-conflict/merge-conflict.feature.registry.ts +++ b/packages/ai-native/src/browser/contrib/merge-conflict/merge-conflict.feature.registry.ts @@ -6,7 +6,7 @@ import { MergeConflictEditorMode, } from '@opensumi/ide-core-common'; -import { IResolveConflictRegistry } from '../types'; +import { IResolveConflictRegistry } from '../../types'; @Injectable() export class ResolveConflictRegistry diff --git a/packages/ai-native/src/browser/merge-conflict/override-resolve-result-widget.tsx b/packages/ai-native/src/browser/contrib/merge-conflict/override-resolve-result-widget.tsx similarity index 98% rename from packages/ai-native/src/browser/merge-conflict/override-resolve-result-widget.tsx rename to packages/ai-native/src/browser/contrib/merge-conflict/override-resolve-result-widget.tsx index a8005c92d7..12b6096e5c 100644 --- a/packages/ai-native/src/browser/merge-conflict/override-resolve-result-widget.tsx +++ b/packages/ai-native/src/browser/contrib/merge-conflict/override-resolve-result-widget.tsx @@ -20,7 +20,7 @@ import { WapperAIInlineResult, } from '@opensumi/ide-monaco/lib/browser/contrib/merge-editor/widget/resolve-result-widget'; -import { InlineDiffWidget } from '../widget/inline-diff/inline-diff-widget'; +import { InlineDiffWidget } from '../../widget/inline-diff/inline-diff-widget'; @Injectable({ multiple: true }) export class DiffResolveResultWidget extends ResolveResultWidget { diff --git a/packages/ai-native/src/browser/rename/rename.feature.registry.ts b/packages/ai-native/src/browser/contrib/rename/rename.feature.registry.ts similarity index 95% rename from packages/ai-native/src/browser/rename/rename.feature.registry.ts rename to packages/ai-native/src/browser/contrib/rename/rename.feature.registry.ts index 71cafe1fec..8c934cdbe1 100644 --- a/packages/ai-native/src/browser/rename/rename.feature.registry.ts +++ b/packages/ai-native/src/browser/contrib/rename/rename.feature.registry.ts @@ -1,6 +1,6 @@ import { Injectable } from '@opensumi/di'; -import { IRenameCandidatesProviderRegistry, NewSymbolNamesProviderFn } from '../types'; +import { IRenameCandidatesProviderRegistry, NewSymbolNamesProviderFn } from '../../types'; @Injectable() export class RenameCandidatesProviderRegistry implements IRenameCandidatesProviderRegistry { diff --git a/packages/ai-native/src/browser/contrib/rename/rename.handler.ts b/packages/ai-native/src/browser/contrib/rename/rename.handler.ts new file mode 100644 index 0000000000..c5b200c43f --- /dev/null +++ b/packages/ai-native/src/browser/contrib/rename/rename.handler.ts @@ -0,0 +1,87 @@ +import { Autowired, Injectable } from '@opensumi/di'; +import { Disposable, IDisposable } from '@opensumi/ide-core-browser'; +import { AISerivceType, CancellationToken, IAIReporter, Schemes, getErrorMessage } from '@opensumi/ide-core-common'; +import * as monaco from '@opensumi/ide-monaco'; +import { monaco as monacoApi } from '@opensumi/ide-monaco/lib/browser/monaco-api'; +import { MonacoTelemetryService } from '@opensumi/ide-monaco/lib/browser/telemetry.service'; + +import { RenameSuggestionsService } from './rename.service'; + +@Injectable() +export class RenameHandler extends Disposable { + @Autowired(RenameSuggestionsService) + private readonly renameSuggestionService: RenameSuggestionsService; + + @Autowired(IAIReporter) + private readonly aiReporter: IAIReporter; + + @Autowired() + private monacoTelemetryService: MonacoTelemetryService; + + private lastModelRequestRenameEndTime: number | undefined; + private lastModelRequestRenameSessionId: string | undefined; + + public registerRenameFeature(languageId: string): IDisposable { + const disposable = new Disposable(); + + const provider = async (model: monaco.ITextModel, range: monaco.IRange, token: CancellationToken) => { + this.lastModelRequestRenameSessionId = undefined; + + const startTime = +new Date(); + const relationId = this.aiReporter.start('rename', { + message: 'start', + type: AISerivceType.Rename, + modelRequestStartTime: startTime, + }); + this.lastModelRequestRenameSessionId = relationId; + + const toDispose = token.onCancellationRequested(() => { + const endTime = +new Date(); + + this.aiReporter.end(relationId, { + message: 'cancel', + success: false, + isCancel: true, + modelRequestStartTime: startTime, + modelRequestEndTime: endTime, + }); + + this.lastModelRequestRenameSessionId = undefined; + }); + + try { + const result = await this.renameSuggestionService.provideRenameSuggestions(model, range, token); + toDispose.dispose(); + this.lastModelRequestRenameEndTime = +new Date(); + return result; + } catch (error) { + const endTime = +new Date(); + this.aiReporter.end(relationId, { + message: 'error:' + getErrorMessage(error), + success: false, + modelRequestStartTime: startTime, + modelRequestEndTime: endTime, + }); + throw error; + } + }; + + disposable.addDispose([ + monacoApi.languages.registerNewSymbolNameProvider(languageId, { + provideNewSymbolNames: provider, + }), + this.monacoTelemetryService.onEventLog('renameInvokedEvent', (event) => { + if (this.lastModelRequestRenameSessionId) { + this.aiReporter.end(this.lastModelRequestRenameSessionId, { + message: 'done', + success: true, + modelRequestEndTime: this.lastModelRequestRenameEndTime, + ...event, + }); + } + }), + ]); + + return disposable; + } +} diff --git a/packages/ai-native/src/browser/rename/rename.service.ts b/packages/ai-native/src/browser/contrib/rename/rename.service.ts similarity index 92% rename from packages/ai-native/src/browser/rename/rename.service.ts rename to packages/ai-native/src/browser/contrib/rename/rename.service.ts index 7ad4ddb83e..2ed788643a 100644 --- a/packages/ai-native/src/browser/rename/rename.service.ts +++ b/packages/ai-native/src/browser/contrib/rename/rename.service.ts @@ -2,7 +2,7 @@ import { Autowired, Injectable } from '@opensumi/di'; import { CancellationToken, RenameCandidatesProviderRegistryToken } from '@opensumi/ide-core-common'; import { IRange, ITextModel, NewSymbolName } from '@opensumi/ide-monaco'; -import { IRenameCandidatesProviderRegistry } from '../types'; +import { IRenameCandidatesProviderRegistry } from '../../types'; @Injectable() export class RenameSuggestionsService { diff --git a/packages/ai-native/src/browser/run/toolbar/run-toolbar.module.less b/packages/ai-native/src/browser/contrib/run-toolbar/run-toolbar.module.less similarity index 100% rename from packages/ai-native/src/browser/run/toolbar/run-toolbar.module.less rename to packages/ai-native/src/browser/contrib/run-toolbar/run-toolbar.module.less diff --git a/packages/ai-native/src/browser/run/toolbar/run-toolbar.tsx b/packages/ai-native/src/browser/contrib/run-toolbar/run-toolbar.tsx similarity index 100% rename from packages/ai-native/src/browser/run/toolbar/run-toolbar.tsx rename to packages/ai-native/src/browser/contrib/run-toolbar/run-toolbar.tsx diff --git a/packages/ai-native/src/browser/ai-terminal/ai-terminal.service.ts b/packages/ai-native/src/browser/contrib/terminal/ai-terminal.service.ts similarity index 98% rename from packages/ai-native/src/browser/ai-terminal/ai-terminal.service.ts rename to packages/ai-native/src/browser/contrib/terminal/ai-terminal.service.ts index 6718e99864..fa526d139e 100644 --- a/packages/ai-native/src/browser/ai-terminal/ai-terminal.service.ts +++ b/packages/ai-native/src/browser/contrib/terminal/ai-terminal.service.ts @@ -5,7 +5,7 @@ import { AIActionItem } from '@opensumi/ide-core-browser/lib/components/ai-nativ import { Disposable, InlineChatFeatureRegistryToken } from '@opensumi/ide-core-common'; import { ITerminalController } from '@opensumi/ide-terminal-next'; -import { InlineChatFeatureRegistry } from '../widget/inline-chat/inline-chat.feature.registry'; +import { InlineChatFeatureRegistry } from '../../widget/inline-chat/inline-chat.feature.registry'; import { AITerminalDecorationService } from './decoration/terminal-decoration'; import { BaseTerminalDetectionLineMatcher, LineRecord, MatcherType } from './matcher'; diff --git a/packages/ai-native/src/browser/ai-terminal/component/terminal-command-suggest-controller.module.less b/packages/ai-native/src/browser/contrib/terminal/component/terminal-command-suggest-controller.module.less similarity index 100% rename from packages/ai-native/src/browser/ai-terminal/component/terminal-command-suggest-controller.module.less rename to packages/ai-native/src/browser/contrib/terminal/component/terminal-command-suggest-controller.module.less diff --git a/packages/ai-native/src/browser/ai-terminal/component/terminal-command-suggest-controller.tsx b/packages/ai-native/src/browser/contrib/terminal/component/terminal-command-suggest-controller.tsx similarity index 99% rename from packages/ai-native/src/browser/ai-terminal/component/terminal-command-suggest-controller.tsx rename to packages/ai-native/src/browser/contrib/terminal/component/terminal-command-suggest-controller.tsx index 9b5918c700..c2beb87f96 100644 --- a/packages/ai-native/src/browser/ai-terminal/component/terminal-command-suggest-controller.tsx +++ b/packages/ai-native/src/browser/contrib/terminal/component/terminal-command-suggest-controller.tsx @@ -5,7 +5,7 @@ import { Emitter, localize } from '@opensumi/ide-core-browser'; import { Input, getIcon } from '@opensumi/ide-core-browser/lib/components'; import { EnhanceIcon } from '@opensumi/ide-core-browser/lib/components/ai-native'; -import { ITerminalCommandSuggestionDesc } from '../../../common'; +import { ITerminalCommandSuggestionDesc } from '../../../../common'; import styles from './terminal-command-suggest-controller.module.less'; diff --git a/packages/ai-native/src/browser/ai-terminal/component/terminal-inline-chat-controller.module.less b/packages/ai-native/src/browser/contrib/terminal/component/terminal-inline-chat-controller.module.less similarity index 100% rename from packages/ai-native/src/browser/ai-terminal/component/terminal-inline-chat-controller.module.less rename to packages/ai-native/src/browser/contrib/terminal/component/terminal-inline-chat-controller.module.less diff --git a/packages/ai-native/src/browser/ai-terminal/component/terminal-inline-chat-controller.tsx b/packages/ai-native/src/browser/contrib/terminal/component/terminal-inline-chat-controller.tsx similarity index 100% rename from packages/ai-native/src/browser/ai-terminal/component/terminal-inline-chat-controller.tsx rename to packages/ai-native/src/browser/contrib/terminal/component/terminal-inline-chat-controller.tsx diff --git a/packages/ai-native/src/browser/ai-terminal/decoration/terminal-decoration.tsx b/packages/ai-native/src/browser/contrib/terminal/decoration/terminal-decoration.tsx similarity index 97% rename from packages/ai-native/src/browser/ai-terminal/decoration/terminal-decoration.tsx rename to packages/ai-native/src/browser/contrib/terminal/decoration/terminal-decoration.tsx index f21f5e7a3b..2c86b97d45 100644 --- a/packages/ai-native/src/browser/ai-terminal/decoration/terminal-decoration.tsx +++ b/packages/ai-native/src/browser/contrib/terminal/decoration/terminal-decoration.tsx @@ -7,7 +7,7 @@ import { AIActionItem } from '@opensumi/ide-core-browser/lib/components/ai-nativ import { Disposable, InlineChatFeatureRegistryToken, runWhenIdle } from '@opensumi/ide-core-common'; import { ITerminalController } from '@opensumi/ide-terminal-next'; -import { InlineChatFeatureRegistry } from '../../widget/inline-chat/inline-chat.feature.registry'; +import { InlineChatFeatureRegistry } from '../../../widget/inline-chat/inline-chat.feature.registry'; import { TerminalInlineWidgetForDetection, TerminalInlineWidgetForSelection, diff --git a/packages/ai-native/src/browser/ai-terminal/index.ts b/packages/ai-native/src/browser/contrib/terminal/index.ts similarity index 100% rename from packages/ai-native/src/browser/ai-terminal/index.ts rename to packages/ai-native/src/browser/contrib/terminal/index.ts diff --git a/packages/ai-native/src/browser/ai-terminal/matcher/base.ts b/packages/ai-native/src/browser/contrib/terminal/matcher/base.ts similarity index 100% rename from packages/ai-native/src/browser/ai-terminal/matcher/base.ts rename to packages/ai-native/src/browser/contrib/terminal/matcher/base.ts diff --git a/packages/ai-native/src/browser/ai-terminal/matcher/index.ts b/packages/ai-native/src/browser/contrib/terminal/matcher/index.ts similarity index 100% rename from packages/ai-native/src/browser/ai-terminal/matcher/index.ts rename to packages/ai-native/src/browser/contrib/terminal/matcher/index.ts diff --git a/packages/ai-native/src/browser/ai-terminal/matcher/java.ts b/packages/ai-native/src/browser/contrib/terminal/matcher/java.ts similarity index 100% rename from packages/ai-native/src/browser/ai-terminal/matcher/java.ts rename to packages/ai-native/src/browser/contrib/terminal/matcher/java.ts diff --git a/packages/ai-native/src/browser/ai-terminal/matcher/node.ts b/packages/ai-native/src/browser/contrib/terminal/matcher/node.ts similarity index 100% rename from packages/ai-native/src/browser/ai-terminal/matcher/node.ts rename to packages/ai-native/src/browser/contrib/terminal/matcher/node.ts diff --git a/packages/ai-native/src/browser/ai-terminal/matcher/npm.ts b/packages/ai-native/src/browser/contrib/terminal/matcher/npm.ts similarity index 100% rename from packages/ai-native/src/browser/ai-terminal/matcher/npm.ts rename to packages/ai-native/src/browser/contrib/terminal/matcher/npm.ts diff --git a/packages/ai-native/src/browser/ai-terminal/matcher/shell.ts b/packages/ai-native/src/browser/contrib/terminal/matcher/shell.ts similarity index 100% rename from packages/ai-native/src/browser/ai-terminal/matcher/shell.ts rename to packages/ai-native/src/browser/contrib/terminal/matcher/shell.ts diff --git a/packages/ai-native/src/browser/ai-terminal/matcher/tsc.ts b/packages/ai-native/src/browser/contrib/terminal/matcher/tsc.ts similarity index 100% rename from packages/ai-native/src/browser/ai-terminal/matcher/tsc.ts rename to packages/ai-native/src/browser/contrib/terminal/matcher/tsc.ts diff --git a/packages/ai-native/src/browser/ai-terminal/ps1-terminal.service.tsx b/packages/ai-native/src/browser/contrib/terminal/ps1-terminal.service.tsx similarity index 99% rename from packages/ai-native/src/browser/ai-terminal/ps1-terminal.service.tsx rename to packages/ai-native/src/browser/contrib/terminal/ps1-terminal.service.tsx index 7382cd98b4..37df4f341e 100644 --- a/packages/ai-native/src/browser/ai-terminal/ps1-terminal.service.tsx +++ b/packages/ai-native/src/browser/contrib/terminal/ps1-terminal.service.tsx @@ -9,7 +9,7 @@ import { CancellationTokenSource, Disposable, IAIReporter, TerminalRegistryToken import { ITerminalConnection, ITerminalController } from '@opensumi/ide-terminal-next'; import { listenReadable } from '@opensumi/ide-utils/lib/stream'; -import { ITerminalCommandSuggestionDesc } from '../../common'; +import { ITerminalCommandSuggestionDesc } from '../../../common'; import { AITerminalPrompt } from './component/terminal-command-suggest-controller'; import { TerminalFeatureRegistry } from './terminal.feature.registry'; diff --git a/packages/ai-native/src/browser/ai-terminal/terminal-ai.contributon.ts b/packages/ai-native/src/browser/contrib/terminal/terminal-ai.contributon.ts similarity index 100% rename from packages/ai-native/src/browser/ai-terminal/terminal-ai.contributon.ts rename to packages/ai-native/src/browser/contrib/terminal/terminal-ai.contributon.ts diff --git a/packages/ai-native/src/browser/ai-terminal/terminal.feature.registry.ts b/packages/ai-native/src/browser/contrib/terminal/terminal.feature.registry.ts similarity index 95% rename from packages/ai-native/src/browser/ai-terminal/terminal.feature.registry.ts rename to packages/ai-native/src/browser/contrib/terminal/terminal.feature.registry.ts index 35ae768ffc..eaebb66181 100644 --- a/packages/ai-native/src/browser/ai-terminal/terminal.feature.registry.ts +++ b/packages/ai-native/src/browser/contrib/terminal/terminal.feature.registry.ts @@ -2,12 +2,12 @@ import { Injectable } from '@opensumi/di'; import { CancellationToken, Disposable } from '@opensumi/ide-core-common'; import { IReadableStream, isReadableStream, listenGroupReadable, listenReadable } from '@opensumi/ide-utils/lib/stream'; -import { ITerminalCommandSuggestionDesc } from '../../common/index'; +import { ITerminalCommandSuggestionDesc } from '../../../common/index'; import { ITerminalProviderRegistry, TTerminalCommandSuggestionsProviderFn, TerminalSuggestionReadableStream, -} from '../types'; +} from '../../types'; @Injectable() export class TerminalFeatureRegistry extends Disposable implements ITerminalProviderRegistry { diff --git a/packages/ai-native/src/browser/ai-terminal/utils/ansi-parser.ts b/packages/ai-native/src/browser/contrib/terminal/utils/ansi-parser.ts similarity index 100% rename from packages/ai-native/src/browser/ai-terminal/utils/ansi-parser.ts rename to packages/ai-native/src/browser/contrib/terminal/utils/ansi-parser.ts diff --git a/packages/ai-native/src/browser/index.ts b/packages/ai-native/src/browser/index.ts index 62ae79a0bf..6e2639f3b3 100644 --- a/packages/ai-native/src/browser/index.ts +++ b/packages/ai-native/src/browser/index.ts @@ -24,8 +24,6 @@ import { import { AINativeBrowserContribution } from './ai-core.contribution'; import { AINativeService } from './ai-native.service'; -import { TerminalAIContribution } from './ai-terminal/terminal-ai.contributon'; -import { TerminalFeatureRegistry } from './ai-terminal/terminal.feature.registry'; import { ChatAgentService } from './chat/chat-agent.service'; import { ChatAgentViewService } from './chat/chat-agent.view.service'; import { ChatManagerService } from './chat/chat-manager.service'; @@ -34,11 +32,13 @@ import { ChatService } from './chat/chat.api.service'; import { ChatFeatureRegistry } from './chat/chat.feature.registry'; import { ChatInternalService } from './chat/chat.internal.service'; import { ChatRenderRegistry } from './chat/chat.render.registry'; -import { InterfaceNavigationContribution } from './interface-navigation/interface-navigation.contribution'; +import { InterfaceNavigationContribution } from './contrib/interface-navigation/interface-navigation.contribution'; +import { MergeConflictContribution } from './contrib/merge-conflict'; +import { ResolveConflictRegistry } from './contrib/merge-conflict/merge-conflict.feature.registry'; +import { RenameCandidatesProviderRegistry } from './contrib/rename/rename.feature.registry'; +import { TerminalAIContribution } from './contrib/terminal/terminal-ai.contributon'; +import { TerminalFeatureRegistry } from './contrib/terminal/terminal.feature.registry'; import { LanguageParserService } from './languages/service'; -import { MergeConflictContribution } from './merge-conflict'; -import { ResolveConflictRegistry } from './merge-conflict/merge-conflict.feature.registry'; -import { RenameCandidatesProviderRegistry } from './rename/rename.feature.registry'; import { AINativeCoreContribution } from './types'; import { InlineChatFeatureRegistry } from './widget/inline-chat/inline-chat.feature.registry'; import { AIInlineChatService } from './widget/inline-chat/inline-chat.service'; diff --git a/packages/ai-native/src/browser/types.ts b/packages/ai-native/src/browser/types.ts index a1c20d6c13..e1f091ad3e 100644 --- a/packages/ai-native/src/browser/types.ts +++ b/packages/ai-native/src/browser/types.ts @@ -21,8 +21,8 @@ import { SumiReadableStream, listenReadable } from '@opensumi/ide-utils/lib/stre import { IChatWelcomeMessageContent, ISampleQuestions, ITerminalCommandSuggestionDesc } from '../common'; -import { BaseTerminalDetectionLineMatcher } from './ai-terminal/matcher'; -import { CompletionRequestBean } from './inline-completions/model/competionModel'; +import { CompletionRequestBean } from './contrib/inline-completions/model/competionModel'; +import { BaseTerminalDetectionLineMatcher } from './contrib/terminal/matcher'; import { InlineChatController } from './widget/inline-chat/inline-chat-controller'; export interface IEditorInlineChatHandler { diff --git a/packages/ai-native/src/browser/widget/inline-chat/inline-chat-actions.tsx b/packages/ai-native/src/browser/widget/inline-chat/inline-chat-actions.tsx deleted file mode 100644 index 3cf64833df..0000000000 --- a/packages/ai-native/src/browser/widget/inline-chat/inline-chat-actions.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; - -import { IAIInlineChatService, useInjectable } from '@opensumi/ide-core-browser'; -import { AIAction, AIInlineResult, EnhancePopover } from '@opensumi/ide-core-browser/lib/components/ai-native'; -import { ContentWidgetContainerPanel } from '@opensumi/ide-core-browser/lib/components/ai-native/content-widget/containerPanel'; -import { MenuNode } from '@opensumi/ide-core-browser/lib/menu/next/base'; -import { InlineChatFeatureRegistryToken, localize } from '@opensumi/ide-core-common'; - -import { Loading } from '../../components/Loading'; - -import { InlineChatFeatureRegistry } from './inline-chat.feature.registry'; -import styles from './inline-chat.module.less'; -import { AIInlineChatService, EInlineChatStatus } from './inline-chat.service'; - -export interface IAIInlineOperationProps { - handleActions: (id: string) => void; - onClose?: () => void; -} - -const AIInlineOperation = (props: IAIInlineOperationProps) => { - const { handleActions, onClose } = props; - const inlineChatFeatureRegistry: InlineChatFeatureRegistry = useInjectable(InlineChatFeatureRegistryToken); - - const operationList = useMemo(() => inlineChatFeatureRegistry.getEditorActionButtons(), [inlineChatFeatureRegistry]); - - const handleClickActions = useCallback( - (id: string) => { - handleActions(id); - }, - [handleActions], - ); - - const handleClose = useCallback(() => { - if (onClose) { - onClose(); - } - }, [onClose]); - - const moreOperation = useMemo( - () => - inlineChatFeatureRegistry.getEditorActionMenus().map( - (data) => - new MenuNode({ - id: `ai.menu.operation.${data.id}`, - label: data.name, - className: styles.more_operation_menu_item, - execute: () => { - handleClickActions(data.id); - }, - }), - ), - [inlineChatFeatureRegistry], - ); - - if (operationList.length === 0 && moreOperation.length === 0) { - return null; - } - - return ( - - ); -}; - -export interface IAIInlineChatControllerProps { - onClickActions: (id: string) => void; - onClose?: () => void; -} - -export const AIInlineChatController = (props: IAIInlineChatControllerProps) => { - const { onClickActions, onClose } = props; - const aiInlineChatService: AIInlineChatService = useInjectable(IAIInlineChatService); - const [status, setStatus] = useState(EInlineChatStatus.READY); - - useEffect(() => { - const dis = aiInlineChatService.onChatStatus((status) => { - setStatus(status); - }); - - return () => { - dis.dispose(); - }; - }, []); - - useEffect(() => { - if (status === EInlineChatStatus.ERROR) { - if (onClose) { - onClose(); - } - } - }, [status, onClose]); - - const isLoading = useMemo(() => status === EInlineChatStatus.THINKING, [status]); - const isDone = useMemo(() => status === EInlineChatStatus.DONE, [status]); - const isError = useMemo(() => status === EInlineChatStatus.ERROR, [status]); - - const iconResultItems = useMemo( - () => [ - { - icon: 'check', - text: localize('aiNative.inline.chat.operate.check.title'), - onClick: () => { - aiInlineChatService._onAccept.fire(); - }, - }, - { - icon: 'discard', - text: localize('aiNative.operate.discard.title'), - onClick: () => { - aiInlineChatService._onDiscard.fire(); - }, - }, - { - icon: 'afresh', - text: localize('aiNative.operate.afresh.title'), - onClick: () => { - aiInlineChatService._onRegenerate.fire(); - }, - }, - ], - [], - ); - - const handleClickActions = useCallback( - (id: string) => { - if (onClickActions) { - onClickActions(id); - } - }, - [onClickActions], - ); - - const translateY: React.CSSProperties | undefined = useMemo(() => { - if (isDone) { - return { - transform: 'translateY(-15px)', - }; - } - return undefined; - }, [isDone]); - - const renderContent = useCallback(() => { - if (isError) { - return null; - } - - if (isDone) { - return ( - - - - ); - } - - if (isLoading) { - return ( - - - - - - ); - } - - return ; - }, [status]); - - return
{renderContent()}
; -}; diff --git a/packages/ai-native/src/browser/widget/inline-chat/inline-chat.handler.ts b/packages/ai-native/src/browser/widget/inline-chat/inline-chat.handler.ts new file mode 100644 index 0000000000..90bd4e7c47 --- /dev/null +++ b/packages/ai-native/src/browser/widget/inline-chat/inline-chat.handler.ts @@ -0,0 +1,522 @@ +import { Autowired, INJECTOR_TOKEN, Injectable, Injector } from '@opensumi/di'; +import { AINativeConfigService, IAIInlineChatService, PreferenceService } from '@opensumi/ide-core-browser'; +import { + AIInlineChatContentWidgetId, + AINativeSettingSectionsId, + AISerivceType, + CancelResponse, + CancellationToken, + ChatResponse, + Disposable, + ErrorResponse, + Event, + IAIReporter, + IDisposable, + ILogServiceClient, + ILoggerManagerClient, + InlineChatFeatureRegistryToken, + MaybePromise, + ReplyResponse, + SupportLogNamespace, + runWhenIdle, +} from '@opensumi/ide-core-common'; +import { WorkbenchEditorService } from '@opensumi/ide-editor'; +import { IEditor, IEditorFeatureContribution } from '@opensumi/ide-editor/lib/browser'; +import { WorkbenchEditorServiceImpl } from '@opensumi/ide-editor/lib/browser/workbench-editor.service'; +import * as monaco from '@opensumi/ide-monaco'; +import { monacoApi } from '@opensumi/ide-monaco/lib/browser/monaco-api'; + +import { AI_DIFF_WIDGET_ID } from '../../../common'; +import { AINativeService } from '../../ai-native.service'; +import { InlineDiffWidget } from '../inline-diff/inline-diff-widget'; + +import { InlineChatController } from './inline-chat-controller'; +import { InlineChatFeatureRegistry } from './inline-chat.feature.registry'; +import { AIInlineChatService, EInlineChatStatus } from './inline-chat.service'; +import { AIInlineContentWidget } from './inline-content-widget'; + +@Injectable() +export class InlineChatHandler extends Disposable { + @Autowired(INJECTOR_TOKEN) + private readonly injector: Injector; + + @Autowired(AINativeService) + private readonly aiNativeService: AINativeService; + + @Autowired(AINativeConfigService) + private readonly aiNativeConfigService: AINativeConfigService; + + @Autowired(IAIInlineChatService) + private readonly aiInlineChatService: AIInlineChatService; + + @Autowired(InlineChatFeatureRegistryToken) + private readonly inlineChatFeatureRegistry: InlineChatFeatureRegistry; + + @Autowired(PreferenceService) + private readonly preferenceService: PreferenceService; + + @Autowired(IAIReporter) + private readonly aiReporter: IAIReporter; + + @Autowired(WorkbenchEditorService) + private readonly workbenchEditorService: WorkbenchEditorServiceImpl; + + @Autowired(ILoggerManagerClient) + private readonly loggerManagerClient: ILoggerManagerClient; + + private logger: ILogServiceClient; + + private aiDiffWidget: InlineDiffWidget; + private aiInlineContentWidget: AIInlineContentWidget; + private aiInlineChatDisposed: Disposable = new Disposable(); + private aiInlineChatOperationDisposed: Disposable = new Disposable(); + + constructor() { + super(); + + this.logger = this.loggerManagerClient.getLogger(SupportLogNamespace.Browser); + } + + contribute(editor: IEditor): IDisposable { + this.registerInlineChatFeature(editor); + return this; + } + + private disposeAllWidget() { + [ + this.aiDiffWidget, + this.aiInlineContentWidget, + this.aiInlineChatDisposed, + this.aiInlineChatOperationDisposed, + ].forEach((widget) => { + widget?.dispose(); + }); + + this.inlineChatInUsing = false; + } + + protected inlineChatInUsing = false; + + public registerInlineChatFeature(editor: IEditor): IDisposable { + const { monacoEditor } = editor; + + this.disposables.push( + this.aiNativeService.onInlineChatVisible((value: boolean) => { + if (value) { + this.showInlineChat(editor); + } else { + this.aiNativeService.cancelToken(); + this.disposeAllWidget(); + } + }), + // 通过 code actions 来透出我们 inline chat 的功能 + this.inlineChatFeatureRegistry.onCodeActionRun(({ id, range }) => { + const currentEditor = this.workbenchEditorService.currentEditor; + + if (currentEditor?.currentUri !== editor.currentUri) { + return; + } + + monacoEditor.setSelection(range); + this.showInlineChat(editor); + if (this.aiInlineContentWidget) { + this.aiInlineContentWidget.clickActionId(id, 'codeAction'); + } + }), + monacoEditor.onWillChangeModel(() => { + this.disposeAllWidget(); + }), + ); + + let needShowInlineChat = false; + this.disposables.push( + monacoEditor.onMouseDown(() => { + needShowInlineChat = false; + }), + monacoEditor.onMouseUp((event) => { + const target = event.target; + const detail = (target as any).detail; + if (detail && typeof detail === 'string' && detail === AIInlineChatContentWidgetId) { + needShowInlineChat = false; + } else { + needShowInlineChat = true; + } + }), + ); + + let prefInlineChatAutoVisible = this.preferenceService.getValid( + AINativeSettingSectionsId.INLINE_CHAT_AUTO_VISIBLE, + true, + ); + this.disposables.push( + this.preferenceService.onSpecificPreferenceChange( + AINativeSettingSectionsId.INLINE_CHAT_AUTO_VISIBLE, + ({ newValue }) => { + prefInlineChatAutoVisible = newValue; + }, + ), + ); + + this.disposables.push( + Event.debounce( + Event.any(monacoEditor.onDidChangeCursorSelection, monacoEditor.onMouseUp), + (_, e) => e, + 100, + )(() => { + if (!prefInlineChatAutoVisible || !needShowInlineChat) { + return; + } + + if ( + this.aiInlineChatService.status !== EInlineChatStatus.READY && + this.aiInlineChatService.status !== EInlineChatStatus.ERROR + ) { + return; + } + + this.showInlineChat(editor); + }), + ); + + return this; + } + + protected async showInlineChat(editor: IEditor): Promise { + if (!this.aiNativeConfigService.capabilities.supportsInlineChat) { + return; + } + if (this.inlineChatInUsing) { + return; + } + + this.inlineChatInUsing = true; + + this.disposeAllWidget(); + + const { monacoEditor } = editor; + + const selection = monacoEditor.getSelection(); + + if (!selection || selection.isEmpty()) { + this.disposeAllWidget(); + return; + } + + this.aiInlineChatDisposed.addDispose(this.aiInlineChatService.launchChatStatus(EInlineChatStatus.READY)); + + this.aiInlineContentWidget = this.injector.get(AIInlineContentWidget, [monacoEditor]); + + this.aiInlineContentWidget.show({ + selection, + }); + + this.aiInlineChatDisposed.addDispose( + this.aiInlineContentWidget.onActionClick((action) => { + this.runInlineChatAction(action, monacoEditor); + }), + ); + } + + private formatAnswer(answer: string, crossCode: string): string { + const leadingWhitespaceMatch = crossCode.match(/^\s*/); + const indent = leadingWhitespaceMatch ? leadingWhitespaceMatch[0] : ' '; + return answer + .split('\n') + .map((line) => `${indent}${line}`) + .join('\n'); + } + + private convertInlineChatStatus( + status: EInlineChatStatus, + reportInfo: { + relationId: string; + message: string; + startTime: number; + isRetry?: boolean; + isStop?: boolean; + }, + ): void { + const { relationId, message, startTime, isRetry, isStop } = reportInfo; + + this.aiInlineChatDisposed.addDispose(this.aiInlineChatService.launchChatStatus(status)); + this.aiReporter.end(relationId, { + message, + success: status !== EInlineChatStatus.ERROR, + replytime: Date.now() - startTime, + isStop, + isRetry, + }); + } + + private visibleDiffWidget( + monacoEditor: monaco.ICodeEditor, + options: { + crossSelection: monaco.Selection; + chatResponse?: ChatResponse | InlineChatController; + }, + reportInfo: { + relationId: string; + startTime: number; + isRetry: boolean; + }, + ): void { + const { crossSelection, chatResponse } = options; + const { relationId, startTime, isRetry } = reportInfo; + + this.aiDiffWidget = this.injector.get(InlineDiffWidget, [ + AI_DIFF_WIDGET_ID, + { + editor: monacoEditor, + selection: crossSelection, + }, + ]); + this.aiDiffWidget.create(); + this.aiDiffWidget.showByLine( + crossSelection.startLineNumber - 1, + crossSelection.endLineNumber - crossSelection.startLineNumber + 2, + ); + + if (InlineChatController.is(chatResponse)) { + const controller = chatResponse as InlineChatController; + + this.aiInlineChatOperationDisposed.addDispose( + this.aiDiffWidget.onReady(() => { + const modifiedModel = this.aiDiffWidget.getModifiedModel(); + if (!modifiedModel) { + return; + } + + let isAbort = false; + + this.aiInlineChatOperationDisposed.addDispose([ + controller.onData((data) => { + if (ReplyResponse.is(data)) { + isAbort = false; + const { message } = data; + + const lastLine = modifiedModel.getLineCount(); + const lastColumn = modifiedModel.getLineMaxColumn(lastLine); + + const range = new monaco.Range(lastLine, lastColumn, lastLine, lastColumn); + + const edit = { + range, + text: message || '', + }; + modifiedModel.pushEditOperations(null, [edit], () => null); + this.aiDiffWidget.layout(); + } + }), + controller.onError((error) => { + this.convertInlineChatStatus(EInlineChatStatus.ERROR, { + relationId, + message: error.message || '', + startTime, + isRetry, + }); + }), + controller.onAbort(() => { + this.convertInlineChatStatus(EInlineChatStatus.READY, { + relationId, + message: 'abort', + startTime, + isRetry, + isStop: true, + }); + }), + controller.onEnd(() => { + this.convertInlineChatStatus(EInlineChatStatus.DONE, { + relationId, + message: '', + startTime, + isRetry, + }); + }), + ]); + }), + ); + } else { + const model = monacoEditor.getModel(); + const crossCode = model!.getValueInRange(crossSelection); + + if (this.aiInlineChatDisposed.disposed || CancelResponse.is(chatResponse)) { + this.convertInlineChatStatus(EInlineChatStatus.READY, { + relationId, + message: (chatResponse as CancelResponse).message || '', + startTime, + isRetry, + isStop: true, + }); + return; + } + + if (ErrorResponse.is(chatResponse)) { + this.convertInlineChatStatus(EInlineChatStatus.ERROR, { + relationId, + message: (chatResponse as ErrorResponse).message || '', + startTime, + isRetry, + }); + return; + } + + this.convertInlineChatStatus(EInlineChatStatus.DONE, { + relationId, + message: '', + startTime, + isRetry, + }); + + let answer = (chatResponse as ReplyResponse).message; + answer = this.formatAnswer(answer, crossCode); + + this.aiInlineChatOperationDisposed.addDispose( + this.aiDiffWidget.onReady(() => { + const modifiedModel = this.aiDiffWidget.getModifiedModel(); + if (!modifiedModel) { + return; + } + + modifiedModel.setValue(answer); + }), + ); + } + + this.aiInlineContentWidget?.setOptions({ + position: { + lineNumber: crossSelection.endLineNumber + 1, + column: 1, + }, + }); + this.aiInlineContentWidget?.layoutContentWidget(); + } + + private async handleDiffPreviewStrategy( + monacoEditor: monaco.ICodeEditor, + strategy: ( + editor: monaco.ICodeEditor, + cancelToken: CancellationToken, + ) => MaybePromise, + crossSelection: monaco.Selection, + relationId: string, + isRetry: boolean, + ): Promise { + const model = monacoEditor.getModel(); + + this.aiDiffWidget?.dispose(); + this.aiInlineChatOperationDisposed.dispose(); + this.aiInlineChatDisposed.addDispose(this.aiInlineChatService.launchChatStatus(EInlineChatStatus.THINKING)); + + const startTime = Date.now(); + + if (this.aiNativeService.cancelIndicator.token.isCancellationRequested) { + this.convertInlineChatStatus(EInlineChatStatus.READY, { + relationId, + message: 'abort', + startTime, + isRetry, + isStop: true, + }); + return; + } + + const response = await strategy(monacoEditor, this.aiNativeService.cancelIndicator.token); + + this.visibleDiffWidget( + monacoEditor, + { crossSelection, chatResponse: response }, + { relationId, startTime, isRetry }, + ); + + this.aiInlineChatOperationDisposed.addDispose([ + this.aiInlineChatService.onAccept(() => { + this.aiReporter.end(relationId, { message: 'accept', success: true, isReceive: true }); + const newValue = this.aiDiffWidget?.getModifiedModel()?.getValue() || ''; + + monacoEditor.getModel()?.pushEditOperations(null, [{ range: crossSelection, text: newValue }], () => null); + runWhenIdle(() => { + this.disposeAllWidget(); + }); + }), + this.aiInlineChatService.onThumbs((isLike: boolean) => { + this.aiReporter.end(relationId, { isLike }); + }), + this.aiDiffWidget.onMaxLineCount((count) => { + requestAnimationFrame(() => { + if (crossSelection.endLineNumber === model!.getLineCount()) { + // 如果用户是选中了最后一行,直接显示在最后一行 + const lineHeight = monacoEditor.getOption(monacoApi.editor.EditorOption.lineHeight); + this.aiInlineContentWidget.offsetTop(lineHeight * count + 12); + } + }); + }), + ]); + } + + private async runInlineChatAction( + { + actionId: id, + source, + }: { + actionId: string; + source: string; + }, + monacoEditor: monaco.ICodeEditor, + ) { + const handler = this.inlineChatFeatureRegistry.getEditorHandler(id); + const action = this.inlineChatFeatureRegistry.getAction(id); + if (!handler || !action) { + return; + } + + const selection = monacoEditor.getSelection(); + if (!selection) { + this.logger.error('No selection found, aborting inline chat action.'); + return; + } + + const { execute, providerDiffPreviewStrategy } = handler; + + if (execute) { + await execute(monacoEditor); + this.disposeAllWidget(); + } + + if (providerDiffPreviewStrategy) { + const crossSelection = selection + .setStartPosition(selection.startLineNumber, 1) + .setEndPosition(selection.endLineNumber, Number.MAX_SAFE_INTEGER); + + const relationId = this.aiReporter.start(action.name, { + message: action.name, + type: AISerivceType.InlineChat, + source, + runByCodeAction: source === 'codeAction', + }); + + await this.handleDiffPreviewStrategy( + monacoEditor, + providerDiffPreviewStrategy, + crossSelection, + relationId, + false, + ); + + this.aiInlineChatDisposed.addDispose([ + this.aiInlineChatService.onDiscard(() => { + this.aiReporter.end(relationId, { message: 'discard', success: true, isDrop: true }); + this.disposeAllWidget(); + }), + this.aiInlineChatService.onRegenerate(async () => { + await this.handleDiffPreviewStrategy( + monacoEditor, + providerDiffPreviewStrategy, + crossSelection, + relationId, + true, + ); + }), + ]); + } + } +} diff --git a/packages/ai-native/src/browser/widget/inline-chat/inline-content-widget.tsx b/packages/ai-native/src/browser/widget/inline-chat/inline-content-widget.tsx index 533b4d873b..5bfe1ea46b 100644 --- a/packages/ai-native/src/browser/widget/inline-chat/inline-content-widget.tsx +++ b/packages/ai-native/src/browser/widget/inline-chat/inline-content-widget.tsx @@ -1,8 +1,16 @@ -import React from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Autowired, INJECTOR_TOKEN, Injectable, Injector } from '@opensumi/di'; -import { IAIInlineChatService, StackingLevelStr } from '@opensumi/ide-core-browser'; -import { AIInlineChatContentWidgetId, Emitter } from '@opensumi/ide-core-common'; +import { IAIInlineChatService, StackingLevelStr, useInjectable } from '@opensumi/ide-core-browser'; +import { AIAction, AIInlineResult, EnhancePopover } from '@opensumi/ide-core-browser/lib/components/ai-native'; +import { ContentWidgetContainerPanel } from '@opensumi/ide-core-browser/lib/components/ai-native/content-widget/containerPanel'; +import { MenuNode } from '@opensumi/ide-core-browser/lib/menu/next/base'; +import { + AIInlineChatContentWidgetId, + Emitter, + InlineChatFeatureRegistryToken, + localize, +} from '@opensumi/ide-core-common'; import * as monaco from '@opensumi/ide-monaco'; import { monacoBrowser } from '@opensumi/ide-monaco/lib/browser'; import { @@ -10,13 +18,176 @@ import { ShowAIContentOptions, } from '@opensumi/ide-monaco/lib/browser/ai-native/BaseInlineContentWidget'; +import { Loading } from '../../components/Loading'; import { AINativeContextKey } from '../../contextkey/ai-native.contextkey.service'; -import { AIInlineChatController } from './inline-chat-actions'; +import { InlineChatFeatureRegistry } from './inline-chat.feature.registry'; +import styles from './inline-chat.module.less'; import { AIInlineChatService, EInlineChatStatus } from './inline-chat.service'; import type { ICodeEditor as IMonacoCodeEditor } from '@opensumi/ide-monaco/lib/browser/monaco-api/types'; + +export interface IAIInlineOperationProps { + handleActions: (id: string) => void; + onClose?: () => void; +} + +const AIInlineOperation = (props: IAIInlineOperationProps) => { + const { handleActions, onClose } = props; + const inlineChatFeatureRegistry: InlineChatFeatureRegistry = useInjectable(InlineChatFeatureRegistryToken); + + const operationList = useMemo(() => inlineChatFeatureRegistry.getEditorActionButtons(), [inlineChatFeatureRegistry]); + + const handleClickActions = useCallback( + (id: string) => { + handleActions(id); + }, + [handleActions], + ); + + const handleClose = useCallback(() => { + if (onClose) { + onClose(); + } + }, [onClose]); + + const moreOperation = useMemo( + () => + inlineChatFeatureRegistry.getEditorActionMenus().map( + (data) => + new MenuNode({ + id: `ai.menu.operation.${data.id}`, + label: data.name, + className: styles.more_operation_menu_item, + execute: () => { + handleClickActions(data.id); + }, + }), + ), + [inlineChatFeatureRegistry], + ); + + if (operationList.length === 0 && moreOperation.length === 0) { + return null; + } + + return ( + + ); +}; + +export interface IAIInlineChatControllerProps { + onClickActions: (id: string) => void; + onClose?: () => void; +} + +export const AIInlineChatController = (props: IAIInlineChatControllerProps) => { + const { onClickActions, onClose } = props; + const aiInlineChatService: AIInlineChatService = useInjectable(IAIInlineChatService); + const [status, setStatus] = useState(EInlineChatStatus.READY); + + useEffect(() => { + const dis = aiInlineChatService.onChatStatus((status) => { + setStatus(status); + }); + + return () => { + dis.dispose(); + }; + }, []); + + useEffect(() => { + if (status === EInlineChatStatus.ERROR) { + if (onClose) { + onClose(); + } + } + }, [status, onClose]); + + const isLoading = useMemo(() => status === EInlineChatStatus.THINKING, [status]); + const isDone = useMemo(() => status === EInlineChatStatus.DONE, [status]); + const isError = useMemo(() => status === EInlineChatStatus.ERROR, [status]); + + const iconResultItems = useMemo( + () => [ + { + icon: 'check', + text: localize('aiNative.inline.chat.operate.check.title'), + onClick: () => { + aiInlineChatService._onAccept.fire(); + }, + }, + { + icon: 'discard', + text: localize('aiNative.operate.discard.title'), + onClick: () => { + aiInlineChatService._onDiscard.fire(); + }, + }, + { + icon: 'afresh', + text: localize('aiNative.operate.afresh.title'), + onClick: () => { + aiInlineChatService._onRegenerate.fire(); + }, + }, + ], + [], + ); + + const handleClickActions = useCallback( + (id: string) => { + if (onClickActions) { + onClickActions(id); + } + }, + [onClickActions], + ); + + const translateY: React.CSSProperties | undefined = useMemo(() => { + if (isDone) { + return { + transform: 'translateY(-15px)', + }; + } + return undefined; + }, [isDone]); + + const renderContent = useCallback(() => { + if (isError) { + return null; + } + + if (isDone) { + return ( + + + + ); + } + + if (isLoading) { + return ( + + + + + + ); + } + + return ; + }, [status]); + + return
{renderContent()}
; +}; + @Injectable({ multiple: true }) export class AIInlineContentWidget extends ReactInlineContentWidget { @Autowired(INJECTOR_TOKEN) diff --git a/packages/ai-native/src/browser/light-bulb-widget/index.ts b/packages/ai-native/src/browser/widget/light-bulb/index.ts similarity index 95% rename from packages/ai-native/src/browser/light-bulb-widget/index.ts rename to packages/ai-native/src/browser/widget/light-bulb/index.ts index 4b075a86fb..ce1756a791 100644 --- a/packages/ai-native/src/browser/light-bulb-widget/index.ts +++ b/packages/ai-native/src/browser/widget/light-bulb/index.ts @@ -7,7 +7,7 @@ import { LightBulbWidget, } from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/codeAction/browser/lightBulbWidget'; -export class OpenSumiLightBulbWidget extends LightBulbWidget { +export class SumiLightBulbWidget extends LightBulbWidget { protected override _updateLightBulbTitleAndIcon(): void { this._domNode.classList.remove(...this._iconClasses); this._iconClasses = []; diff --git a/packages/editor/src/browser/editor.contribution.ts b/packages/editor/src/browser/editor.contribution.ts index 2bc08eb8d1..19e6f6db49 100644 --- a/packages/editor/src/browser/editor.contribution.ts +++ b/packages/editor/src/browser/editor.contribution.ts @@ -1,6 +1,5 @@ import { Autowired, INJECTOR_TOKEN, Injector } from '@opensumi/di'; import { - AINativeConfigService, AppConfig, ClientAppContribution, CommandContribution, @@ -229,9 +228,6 @@ export class EditorContribution @Autowired(ICtxMenuRenderer) private readonly contextMenuRenderer: ICtxMenuRenderer; - @Autowired(AINativeConfigService) - private readonly aiNativeConfigService: AINativeConfigService; - registerMonacoDefaultFormattingSelector(register: (selector: IFormattingEditProviderSelector) => IDisposable): void { const formatSelector = this.injector.get(FormattingSelector); this.addDispose(register(formatSelector.selectFormatter.bind(formatSelector))); diff --git a/packages/editor/src/browser/light-bulb-widget/index.ts b/packages/editor/src/browser/light-bulb-widget/index.ts deleted file mode 100644 index 4b075a86fb..0000000000 --- a/packages/editor/src/browser/light-bulb-widget/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { asClassNameArrayWrapper } from '@opensumi/ide-core-browser'; -import { Sumicon } from '@opensumi/ide-core-common/lib/codicons'; -import { Codicon } from '@opensumi/monaco-editor-core/esm/vs/base/common/codicons'; -import { ThemeIcon } from '@opensumi/monaco-editor-core/esm/vs/base/common/themables'; -import { - LightBulbState, - LightBulbWidget, -} from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/codeAction/browser/lightBulbWidget'; - -export class OpenSumiLightBulbWidget extends LightBulbWidget { - protected override _updateLightBulbTitleAndIcon(): void { - this._domNode.classList.remove(...this._iconClasses); - this._iconClasses = []; - if (this.state.type !== LightBulbState.Type.Showing) { - return; - } - let icon: ThemeIcon; - let autoRun = false; - if (this.state.actions.allAIFixes) { - icon = Sumicon.magicWand; - if (this.state.actions.validActions.length === 1) { - autoRun = true; - } - } else if (this.state.actions.hasAutoFix) { - if (this.state.actions.hasAIFix) { - // icon = Codicon.lightbulbSparkleAutofix; - icon = Sumicon.magicWand; - } else { - icon = Codicon.lightbulbAutofix; - } - } else if (this.state.actions.hasAIFix) { - // icon = Codicon.lightbulbSparkle; - icon = Sumicon.magicWand; - } else { - icon = Codicon.lightBulb; - } - this._updateLightbulbTitle(this.state.actions.hasAutoFix, autoRun); - this._iconClasses = asClassNameArrayWrapper(icon); - this._domNode.classList.add(...this._iconClasses); - } -} diff --git a/packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts b/packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts index bd8e199dea..3f3869018b 100644 --- a/packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts +++ b/packages/startup/entry/sample-modules/ai-native/ai-native.contribution.ts @@ -1,4 +1,5 @@ import { Autowired } from '@opensumi/di'; +import { ChatService } from '@opensumi/ide-ai-native/lib/browser/chat/chat.api.service'; import { BaseTerminalDetectionLineMatcher, JavaMatcher, @@ -6,9 +7,8 @@ import { NodeMatcher, ShellMatcher, TSCMatcher, -} from '@opensumi/ide-ai-native/lib/browser/ai-terminal/matcher'; -import { TextWithStyle } from '@opensumi/ide-ai-native/lib/browser/ai-terminal/utils/ansi-parser'; -import { ChatService } from '@opensumi/ide-ai-native/lib/browser/chat/chat.api.service'; +} from '@opensumi/ide-ai-native/lib/browser/contrib/terminal/matcher'; +import { TextWithStyle } from '@opensumi/ide-ai-native/lib/browser/contrib/terminal/utils/ansi-parser'; import { AINativeCoreContribution, IChatFeatureRegistry, @@ -26,19 +26,15 @@ import { TerminalDetectionPromptManager } from '@opensumi/ide-ai-native/lib/comm import { Domain, getIcon } from '@opensumi/ide-core-browser'; import { AIBackSerivcePath, - AbortError, CancelResponse, ChatServiceToken, ErrorResponse, IAIBackService, - IChatContent, - IChatProgress, MergeConflictEditorMode, ReplyResponse, getDebugLogger, } from '@opensumi/ide-core-common'; import { ICodeEditor, NewSymbolName, NewSymbolNameTag } from '@opensumi/ide-monaco'; -import { listenReadable } from '@opensumi/ide-utils/lib/stream'; import { MarkdownString } from '@opensumi/monaco-editor-core/esm/vs/base/common/htmlContent'; import { SlashCommand } from './SlashCommand'; diff --git a/packages/startup/entry/sample-modules/ai-native/ai.back.service.ts b/packages/startup/entry/sample-modules/ai-native/ai.back.service.ts index e9f5b97d3a..3d03a56c3c 100644 --- a/packages/startup/entry/sample-modules/ai-native/ai.back.service.ts +++ b/packages/startup/entry/sample-modules/ai-native/ai.back.service.ts @@ -1,6 +1,7 @@ import { Readable } from 'stream'; import { Autowired, Injectable } from '@opensumi/di'; +import { IAICompletionOption } from '@opensumi/ide-core-common'; import { CancellationToken, ChatReadableStream, @@ -49,6 +50,13 @@ export class AIBackService implements IAIBackService