Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions packages/ai-native/src/browser/chat/chat.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getIcon, useInjectable, useUpdateOnEvent } from '@opensumi/ide-core-bro
import { Popover, PopoverPosition } from '@opensumi/ide-core-browser/lib/components';
import { EnhanceIcon } from '@opensumi/ide-core-browser/lib/components/ai-native';
import {
AISerivceType,
AIServiceType,
ActionSourceEnum,
ActionTypeEnum,
CancellationToken,
Expand Down Expand Up @@ -198,7 +198,7 @@ export const AIChatView = observer(() => {
disposer.addDispose(
chatApiService.onChatReplyMessageLaunch((data) => {
if (data.kind === 'content') {
const relationId = aiReporter.start(AISerivceType.CustomReplay, {
const relationId = aiReporter.start(AIServiceType.CustomReplay, {
message: data.content,
});
msgHistoryManager.addAssistantMessage({
Expand All @@ -207,7 +207,7 @@ export const AIChatView = observer(() => {
});
renderSimpleMarkdownReply({ chunk: data.content, relationId });
} else {
const relationId = aiReporter.start(AISerivceType.CustomReplay, {
const relationId = aiReporter.start(AIServiceType.CustomReplay, {
message: 'component#' + data.component,
});
msgHistoryManager.addAssistantMessage({
Expand All @@ -228,7 +228,7 @@ export const AIChatView = observer(() => {
list.forEach((item) => {
const { role } = item;

const relationId = aiReporter.start(AISerivceType.Chat, {
const relationId = aiReporter.start(AIServiceType.Chat, {
message: '',
});
Comment thread
Ricbet marked this conversation as resolved.

Expand Down Expand Up @@ -290,7 +290,7 @@ export const AIChatView = observer(() => {
disposer.addDispose(
chatAgentService.onDidSendMessage((chunk) => {
const newChunk = chunk as IChatComponent | IChatContent;
const relationId = aiReporter.start(AISerivceType.Agent, {
const relationId = aiReporter.start(AIServiceType.Agent, {
message: '',
});

Comment thread
Ricbet marked this conversation as resolved.
Expand Down Expand Up @@ -495,7 +495,7 @@ export const AIChatView = observer(() => {
aiChatService.setLatestRequestId(request.requestId);

const startTime = Date.now();
const reportType = ChatProxyService.AGENT_ID === agentId ? AISerivceType.Chat : AISerivceType.Agent;
const reportType = ChatProxyService.AGENT_ID === agentId ? AIServiceType.Chat : AIServiceType.Agent;
const relationId = aiReporter.start(command || reportType, {
message,
agentId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { empty } from '@opensumi/ide-utils/lib/strings';
import { InlineCompletionContextKeys } from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys';

import { IAIInlineCompletionsProvider } from '../../../common';
import { AINativeContextKey } from '../../contextkey/ai-native.contextkey.service';
import { AINativeContextKey } from '../../ai-core.contextkeys';
import { BaseAIMonacoEditorController } from '../base';
import { IIntelligentCompletionsResult } from '../intelligent-completions';
import { IntelligentCompletionsRegistry } from '../intelligent-completions/intelligent-completions.feature.registry';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
URI,
uuid,
} from '@opensumi/ide-core-common';
import { AISerivceType, IAIReporter } from '@opensumi/ide-core-common/lib/types/ai-native/reporter';
import { AIServiceType, IAIReporter } from '@opensumi/ide-core-common/lib/types/ai-native/reporter';
import { WorkbenchEditorService } from '@opensumi/ide-editor';
import { WorkbenchEditorServiceImpl } from '@opensumi/ide-editor/lib/browser/workbench-editor.service';
import * as monaco from '@opensumi/ide-monaco';
Expand Down Expand Up @@ -165,7 +165,7 @@ export class InlineCompletionRequestTask extends Disposable {
let completeResult: IIntelligentCompletionsResult | undefined;
const cacheData = this.promptCache.getCache(requestBean);
const relationId =
cacheData?.relationId || this.aiReporter.start(AISerivceType.Completion, { message: AISerivceType.Completion });
cacheData?.relationId || this.aiReporter.start(AIServiceType.Completion, { message: AIServiceType.Completion });
Comment thread
Ricbet marked this conversation as resolved.

this.aiCompletionsService.setLastRelationId(relationId);
// 如果存在缓存
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Disposable } from '@opensumi/ide-core-common';
import { IRange, InlineCompletion } from '@opensumi/ide-monaco';
import { Disposable, ECodeEditsSourceTyping } from '@opensumi/ide-core-common';
import { IPosition, IRange, InlineCompletion } from '@opensumi/ide-monaco';

import type { ILineChangeData } from './source/line-change.source';
import type { ILinterErrorData } from './source/lint-error.source';
Expand All @@ -12,14 +12,9 @@ export interface IIntelligentCompletionsResult<T = any> {
extra?: T;
}

export enum ECodeEditsSource {
LinterErrors = 'lint_errors',
LineChange = 'line_change',
}

export type ICodeEditsContextBean =
| { typing: ECodeEditsSource.LinterErrors; data: ILinterErrorData }
| { typing: ECodeEditsSource.LineChange; data: ILineChangeData };
| { typing: ECodeEditsSourceTyping.LinterErrors; position: IPosition; data: ILinterErrorData }
| { typing: ECodeEditsSourceTyping.LineChange; position: IPosition; data: ILineChangeData };

export interface ICodeEdit {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from '@opensumi/ide-core-browser';
import {
AI_MULTI_LINE_COMPLETION_ACCEPT,
AI_MULTI_LINE_COMPLETION_HIDE,
AI_MULTI_LINE_COMPLETION_DISCARD,
} from '@opensumi/ide-core-browser/lib/ai-native/command';
import { MultiLineEditsIsVisible } from '@opensumi/ide-core-browser/lib/contextkey/ai-native';
import { CommandContribution, CommandRegistry, Domain } from '@opensumi/ide-core-common';
Expand All @@ -23,11 +23,11 @@ export class IntelligentCompletionsContribution implements KeybindingContributio
private readonly workbenchEditorService: WorkbenchEditorServiceImpl;

registerCommands(commands: CommandRegistry): void {
commands.registerCommand(AI_MULTI_LINE_COMPLETION_HIDE, {
commands.registerCommand(AI_MULTI_LINE_COMPLETION_DISCARD, {
execute: () => {
const editor = this.workbenchEditorService.currentCodeEditor;
if (editor) {
IntelligentCompletionsController.get(editor.monacoEditor)?.hide();
IntelligentCompletionsController.get(editor.monacoEditor)?.discard.get();
}
},
});
Expand All @@ -36,15 +36,15 @@ export class IntelligentCompletionsContribution implements KeybindingContributio
execute: () => {
const editor = this.workbenchEditorService.currentCodeEditor;
if (editor) {
IntelligentCompletionsController.get(editor.monacoEditor)?.accept();
IntelligentCompletionsController.get(editor.monacoEditor)?.accept.get();
}
},
});
}

registerKeybindings(keybindings: KeybindingRegistry): void {
keybindings.registerKeybinding({
command: AI_MULTI_LINE_COMPLETION_HIDE.id,
command: AI_MULTI_LINE_COMPLETION_DISCARD.id,
keybinding: Key.ESCAPE.code,
when: MultiLineEditsIsVisible.raw,
priority: 100,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,25 @@ import { Key, KeybindingRegistry, KeybindingScope, PreferenceService } from '@op
import { MultiLineEditsIsVisible } from '@opensumi/ide-core-browser/lib/contextkey/ai-native';
import {
AINativeSettingSectionsId,
CodeEditsRT,
Disposable,
Event,
IDisposable,
ILogger,
IntelligentCompletionsRegistryToken,
runWhenIdle,
} from '@opensumi/ide-core-common';
import { ICodeEditor, ICursorPositionChangedEvent, IRange, ITextModel, Range } from '@opensumi/ide-monaco';
import {
ISettableObservable,
ObservableValue,
autorun,
autorunWithStoreHandleChanges,
derived,
observableValue,
transaction,
} from '@opensumi/ide-monaco/lib/common/observable';
import { empty } from '@opensumi/ide-utils/lib/strings';
import { autorun, transaction } from '@opensumi/monaco-editor-core/esm/vs/base/common/observable';
import { ObservableValue } from '@opensumi/monaco-editor-core/esm/vs/base/common/observableInternal/base';
import { EditorContextKeys } from '@opensumi/monaco-editor-core/esm/vs/editor/common/editorContextKeys';
import { inlineSuggestCommitId } from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/inlineCompletions/browser/commandIds';
import { InlineCompletionContextKeys } from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys';
Expand All @@ -23,7 +32,7 @@ import {
import { SuggestController } from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/suggest/browser/suggestController';
import { ContextKeyExpr } from '@opensumi/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey';

import { AINativeContextKey } from '../../contextkey/ai-native.contextkey.service';
import { AINativeContextKey } from '../../ai-core.contextkeys';
import { REWRITE_DECORATION_INLINE_ADD, RewriteWidget } from '../../widget/rewrite/rewrite-widget';
import { BaseAIMonacoEditorController } from '../base';

Expand Down Expand Up @@ -65,19 +74,31 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
return this.injector.get(IntelligentCompletionsRegistryToken);
}

private get logger(): ILogger {
return this.injector.get(ILogger);
}

private codeEditsResult: ISettableObservable<CodeEditsResultValue | undefined>;
private multiLineDecorationModel: MultiLineDecorationModel;
private additionsDeletionsDecorationModel: AdditionsDeletionsDecorationModel;
private codeEditsSourceCollection: CodeEditsSourceCollection;
private aiNativeContextKey: AINativeContextKey;
private rewriteWidget: RewriteWidget | null;
private whenMultiLineEditsVisibleDisposable: Disposable;

public mount(): IDisposable {
this.handlerAlwaysVisiblePreference();

this.codeEditsResult = observableValue<CodeEditsResultValue | undefined>(this, undefined);

this.whenMultiLineEditsVisibleDisposable = new Disposable();
this.multiLineDecorationModel = new MultiLineDecorationModel(this.monacoEditor);
this.additionsDeletionsDecorationModel = new AdditionsDeletionsDecorationModel(this.monacoEditor);
this.aiNativeContextKey = this.injector.get(AINativeContextKey, [this.monacoEditor.contextKeyService]);
this.codeEditsSourceCollection = this.injector.get(CodeEditsSourceCollection, [
[LintErrorCodeEditsSource, LineChangeCodeEditsSource],
this.monacoEditor,
]);

this.registerFeature(this.monacoEditor);
return this.featureDisposable;
Expand Down Expand Up @@ -219,15 +240,15 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
const maxWordChanges = 20;

if (
position &&
range &&
isOnlyAddingToEachWord &&
charChanges.length <= maxCharChanges &&
wordChanges.length <= maxWordChanges
) {
const modificationsResult = this.multiLineDecorationModel.applyInlineDecorations(
this.monacoEditor,
mergeMultiLineDiffChanges(singleLineCharChanges, eol),
position.lineNumber,
range.startLineNumber,
position,
);

Expand All @@ -248,15 +269,15 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
if (this.whenMultiLineEditsVisibleDisposable.disposed) {
this.whenMultiLineEditsVisibleDisposable = new Disposable();
}
// 监听当前光标位置的变化,如果超出 range 区域则取消 multiLine edits
// 监听当前光标位置的变化,如果超出 range 区域则表示弃用
this.whenMultiLineEditsVisibleDisposable.addDispose(
this.monacoEditor.onDidChangeCursorPosition((event: ICursorPositionChangedEvent) => {
const isVisible = this.aiNativeContextKey.multiLineEditsIsVisible.get();
if (isVisible) {
const position = event.position;
if (position.lineNumber < range.startLineNumber || position.lineNumber > range.endLineNumber) {
runWhenIdle(() => {
this.hide();
this.discard.get();
});
}
} else {
Expand Down Expand Up @@ -319,7 +340,46 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
this.destroyRewriteWidget();
}

public accept() {
private readonly reportData = derived(this, (reader) => {
const contextBean = this.codeEditsSourceCollection.codeEditsContextBean.read(reader);
const codeEditsResult = this.codeEditsResult.read(reader);
if (contextBean && codeEditsResult) {
const { range, insertText } = codeEditsResult.items[0];
const newCode = insertText;
const originCode = this.model.getValueInRange(range);
return (type: keyof Pick<CodeEditsRT, 'accept' | 'discard' | 'isCancel'>) => {
contextBean.reporterEnd({
[type]: true,
code: newCode,
originCode,
});
};
}
});

private lastVisibleTime = derived(this, (reader) => {
const isVisible = this.aiNativeContextKey.multiLineEditsIsVisible.get();
return isVisible ? Date.now() : undefined;
});

public discard = derived(this, (reader) => {
const lastVisibleTime = this.lastVisibleTime.read(reader);
const report = this.reportData.read(reader);

// 在可见的情况下超过 750ms 弃用才算有效数据,否则视为取消
if (lastVisibleTime && Date.now() - lastVisibleTime > 750) {
report?.('discard');
} else {
report?.('isCancel');
}

this.hide();
});

public accept = derived(this, (reader) => {
const report = this.reportData.read(reader);
report?.('accept');

this.multiLineDecorationModel.accept();

if (this.rewriteWidget) {
Expand All @@ -342,7 +402,7 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
}

this.hide();
}
});

private registerFeature(monacoEditor: ICodeEditor): void {
this.featureDisposable.addDispose(
Expand Down Expand Up @@ -379,38 +439,56 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
}),
);

const codeEditsSourceCollection = this.injector.get(CodeEditsSourceCollection, [
[LintErrorCodeEditsSource, LineChangeCodeEditsSource],
this.monacoEditor,
]);
this.featureDisposable.addDispose(
autorunWithStoreHandleChanges(
{
createEmptyChangeSummary: () => ({}),
handleChange: (context, changeSummary) => {
if (context.didChange(this.codeEditsSourceCollection.codeEditsContextBean)) {
// 如果上一次补全结果还在,则不重复请求
const isVisible = this.aiNativeContextKey.multiLineEditsIsVisible.get();
return !isVisible;
}
return false;
},
},
async (reader, _, store) => {
const context = this.codeEditsSourceCollection.codeEditsContextBean.read(reader);

codeEditsSourceCollection.mount();
const provider = this.intelligentCompletionsRegistry.getCodeEditsProvider();
if (context && provider) {
// 新的请求进来且上一次的请求还在继续时,则取消掉上一次的请求
store.add(Disposable.create(() => context.cancelToken()));

this.featureDisposable.addDispose(
autorun(async (reader) => {
const context = codeEditsSourceCollection.codeEditsContextBean.read(reader);
context.reporterStart();

// 如果上一次补全结果还在,则不重复请求
const isVisible = this.aiNativeContextKey.multiLineEditsIsVisible.get();
if (isVisible) {
const result = await provider(this.monacoEditor, context.position, context.bean, context.token);

if (result && result.items.length > 0) {
transaction((tx) => {
this.codeEditsResult.set(new CodeEditsResultValue(result), tx);
});
}
}
},
),
);

this.featureDisposable.addDispose(
autorun((reader) => {
const completionModel = this.codeEditsResult.read(reader);
if (!completionModel) {
return;
}

const provider = this.intelligentCompletionsRegistry.getCodeEditsProvider();
if (context && provider) {
const result = await provider(
this.monacoEditor,
this.monacoEditor.getPosition()!,
context.bean,
codeEditsSourceCollection.token,
);
if (result && result.items.length > 0) {
this.applyInlineDecorations(new CodeEditsResultValue(result));
}
try {
this.applyInlineDecorations(completionModel);
} catch (error) {
this.logger.warn('IntelligentCompletionsController applyInlineDecorations error', error);
}
}),
);

this.featureDisposable.addDispose(codeEditsSourceCollection);
this.featureDisposable.addDispose(this.codeEditsSourceCollection);
Comment thread
Ricbet marked this conversation as resolved.
}
}
Loading