Skip to content

Commit

Permalink
feat: support inline ai widget
Browse files Browse the repository at this point in the history
  • Loading branch information
coetzeexu committed Dec 10, 2024
1 parent 7087a58 commit 86fcd53
Show file tree
Hide file tree
Showing 20 changed files with 955 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { LibroCellView } from '@difizen/libro-jupyter';
import type { CommandRegistry } from '@difizen/mana-app';
import { CommandContribution, inject, singleton } from '@difizen/mana-app';
import { l10n } from '@difizen/mana-l10n';

import { LibroAINativeService } from '../ai-native-service.js';

import { AIWidgetCommandRegister } from './ai-widget-command-register.js';
import { AIWidgetCommands } from './command.js';

@singleton({ contrib: CommandContribution })
export class AIWidgetCommandContribution implements CommandContribution {
@inject(AIWidgetCommandRegister)
protected readonly widgetCommandRegister: AIWidgetCommandRegister;

@inject(LibroAINativeService) libroAINativeService: LibroAINativeService;

registerCommands(command: CommandRegistry) {
this.widgetCommandRegister.registerAIWidgetCommand(
command,
AIWidgetCommands['Optimize'],
{
execute: async (code, cell, libro) => {
if (!cell || !(cell instanceof LibroCellView)) {
return;
}
const libroAINativeForCellView =
await this.libroAINativeService.getOrCreateLibroAINativeForCellView(
cell.id,
cell,
);
libroAINativeForCellView.showAI = true;

const inCode =
l10n.getLang() === 'en-US'
? `Could you please optimize this piece of code?:${code}`
: `帮忙优化一下这段代码:${code}`;
libroAINativeForCellView.chatStream({
content: inCode,
});
},
},
);
this.widgetCommandRegister.registerAIWidgetCommand(
command,
AIWidgetCommands['Explain'],
{
execute: async (code, cell) => {
if (!cell || !(cell instanceof LibroCellView)) {
return;
}
const libroAINativeForCellView =
await this.libroAINativeService.getOrCreateLibroAINativeForCellView(
cell.id,
cell,
);
libroAINativeForCellView.showAI = true;

const inCode =
l10n.getLang() === 'en-US'
? `Could you please optimize this piece of code?:${code}`
: `帮忙解释一下这段代码:${code}`;
libroAINativeForCellView.chatStream({
content: inCode,
});
},
},
);
}
}
137 changes: 137 additions & 0 deletions packages/libro-ai-native/src/ai-widget/ai-widget-command-register.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { LibroService } from '@difizen/libro-jupyter';
import type { CellView, NotebookView } from '@difizen/libro-jupyter';
import type {
Command,
CommandHandler,
CommandRegistry,
CommandHandlerWithContext,
} from '@difizen/mana-app';
import { inject, singleton } from '@difizen/mana-app';

export interface GeneralAIWidgetCommandHandler extends CommandHandler {
execute: (
code?: string,
cell?: CellView,
libro?: NotebookView,
position?: string,
options?: any,
) => void;
isVisible?: (
code?: string,
cell?: CellView,
libro?: NotebookView,
position?: string,
options?: any,
) => boolean;
isEnabled?: (
code?: string,
cell?: CellView,
libro?: NotebookView,
position?: string,
options?: any,
) => boolean;
isActive?: (
code?: string,
cell?: CellView,
libro?: NotebookView,
position?: string,
options?: any,
) => boolean;
}

@singleton()
export class AIWidgetCommandRegister {
@inject(LibroService) protected readonly libroService: LibroService;

toGeneralCommandArgs = (
ctx: AIWidgetCommandRegister,
code?: string,
cell?: CellView,
libro?: NotebookView,
position?: string,
options?: any,
): [
string | undefined,
CellView | undefined,
NotebookView | undefined,
string | undefined,
any,
] => {
const libroView = libro || ctx.libroService.active;
const cellView = cell || libroView?.model?.active;
return [code, cellView, libroView, position, options];
};

registerAIWidgetCommand(
registry: CommandRegistry,
command: Command,
handler: GeneralAIWidgetCommandHandler,
) {
const commandHandler: CommandHandlerWithContext<AIWidgetCommandRegister> = {
execute: (
ctx,
code?: string,
cell?: CellView,
libro?: NotebookView,
position?: string,
options?: any,
) => {
return handler.execute(
...this.toGeneralCommandArgs(ctx, code, cell, libro, position, options),
);
},
};
if (handler.isEnabled) {
commandHandler.isEnabled = (
ctx,
code?: string,
cell?: CellView,
libro?: NotebookView,
position?: string,
options?: any,
) => {
if (!handler.isEnabled) {
return true;
}
return handler.isEnabled(
...this.toGeneralCommandArgs(ctx, code, cell, libro, position, options),
);
};
}
if (handler.isVisible) {
commandHandler.isVisible = (
ctx,
code?: string,
cell?: CellView,
libro?: NotebookView,
position?: string,
options?: any,
) => {
if (!handler.isVisible) {
return true;
}
return handler.isVisible(
...this.toGeneralCommandArgs(ctx, code, cell, libro, position, options),
);
};
}
if (handler.isActive) {
commandHandler.isActive = (
ctx,
code?: string,
cell?: CellView,
libro?: NotebookView,
position?: string,
options?: any,
) => {
if (!handler.isActive) {
return false;
}
return handler.isActive(
...this.toGeneralCommandArgs(ctx, code, cell, libro, position, options),
);
};
}
registry.registerCommandWithContext(command, this, commandHandler);
}
}
125 changes: 125 additions & 0 deletions packages/libro-ai-native/src/ai-widget/ai-widget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import type {
WidgetActionItem,
WidgetActionHandlerItem,
IEditor,
} from '@difizen/libro-code-editor';
import { EditorWidgetContribution } from '@difizen/libro-code-editor';
import { CommandRegistry } from '@difizen/mana-app';
import { inject, singleton } from '@difizen/mana-app';

import { LibroAINativeService } from '../ai-native-service.js';

import { AIWidgetCommands } from './command.js';

@singleton({ contrib: [EditorWidgetContribution] })
export class AIWidget implements EditorWidgetContribution {
private actionsMap: Map<string, WidgetActionItem> = new Map();
private handlerMap: Map<string, WidgetActionHandlerItem> = new Map();

canHandle = () => {
return 100;
};

@inject(LibroAINativeService) libroAINativeService: LibroAINativeService;
@inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry;

constructor() {
this.registerEditorInlineChat(
{
id: 'ai-comments',
name: 'Comments',
title: 'add comments(readable stream example)',
renderType: 'button',
codeAction: {
isPreferred: true,
kind: 'refactor.rewrite',
},
},
{
execute: async (editor: IEditor) => {
const selection = editor.getSelection();
if (!selection) {
return;
}
const code = editor.getModel()?.getValueInRange(selection);
this.commandRegistry.executeCommand(AIWidgetCommands['Explain'].id, code);
},
},
);
this.registerEditorInlineChat(
{
id: 'ai-optimize',
name: 'Optimize',
renderType: 'button',
codeAction: {
isPreferred: true,
kind: 'refactor.rewrite',
},
},
{
execute: async (editor: IEditor) => {
const selection = editor.getSelection();
if (!selection) {
return;
}
const code = editor.getModel()?.getValueInRange(selection);
this.commandRegistry.executeCommand(AIWidgetCommands['Optimize'].id, code);
},
},
);
}
public getAction(id: string): WidgetActionItem | undefined {
return this.actionsMap.get(id);
}

public registerEditorInlineChat(
operational: WidgetActionItem,
handler: WidgetActionHandlerItem,
) {
const isCollect = this.collectActions(operational);

if (isCollect) {
this.handlerMap.set(operational.id, handler);
}
}

private collectActions(operational: WidgetActionItem): boolean {
const { id } = operational;

if (this.actionsMap.has(id)) {
return false;
}

if (!operational.renderType) {
operational.renderType = 'button';
}

if (!operational.order) {
operational.order = 0;
}

this.actionsMap.set(id, operational);

return true;
}

// show & hide
show: () => void;
hide: () => void;

public getActionButtons(): WidgetActionItem[] {
const actions: WidgetActionItem[] = Array.from(this.handlerMap.keys())
.filter((id) => {
const actions_find = this.actionsMap.get(id);
return actions_find && actions_find.renderType === 'button';
})
.map((id) => this.actionsMap.get(id))
.sort((a, b) => (a?.order ?? 0) - (b?.order ?? 0));

return actions;
}

getActionHandler(actionId: string) {
return this.handlerMap.get(actionId);
}
}
12 changes: 12 additions & 0 deletions packages/libro-ai-native/src/ai-widget/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { Command } from '@difizen/mana-app';

export const AIWidgetCommands: Record<string, Command & { keybind?: string }> = {
Explain: {
id: 'ai-widget:explain',
label: 'EXPLAIN',
},
Optimize: {
id: 'ai-widget:optimize',
label: 'Optimize',
},
};
3 changes: 3 additions & 0 deletions packages/libro-ai-native/src/ai-widget/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './ai-widget-command-contribution.js';
export * from './ai-widget-command-register.js';
export * from './command.js';
10 changes: 10 additions & 0 deletions packages/libro-ai-native/src/ai-widget/module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { CodeEditorModule } from '@difizen/libro-code-editor';
import { ManaModule } from '@difizen/mana-app';

import { AIWidgetCommandContribution } from './ai-widget-command-contribution.js';
import { AIWidgetCommandRegister } from './ai-widget-command-register.js';
import { AIWidget } from './ai-widget.js';

export const LibroAIWidgetModule = ManaModule.create()
.register(AIWidget, AIWidgetCommandRegister, AIWidgetCommandContribution)
.dependOn(CodeEditorModule);
1 change: 1 addition & 0 deletions packages/libro-ai-native/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './chat-view.js';
export * from './error-output-model.js';
export * from './libro-ai-native-color-registry.js';
export * from './ai-inline-completions/index.js';
export * from './ai-widget/index.js';
8 changes: 7 additions & 1 deletion packages/libro-ai-native/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { LibroAINativeCommandContribution } from './ai-native-command-contributi
import { LibroAINativeForCellView } from './ai-native-for-cell-view.js';
import { LibroAINativeCellTopBlank } from './ai-native-output-top.js';
import { LibroAINativeService } from './ai-native-service.js';
import { LibroAIWidgetModule } from './ai-widget/module.js';
import { LibroAIChatSlotContribution } from './chat-slot-contribution.js';
import { LibroChatView } from './chat-view.js';
import { AIErrorOutputModel } from './error-output-model.js';
Expand Down Expand Up @@ -48,4 +49,9 @@ export const LibroAINativeModule = ManaModule.create()
},
)
.canload(() => Promise.resolve(LibroAINativeModuleSetting.loadable))
.dependOn(LibroChatModule, CodeEditorModule, LibroAICompletionModule);
.dependOn(
LibroAIWidgetModule,
LibroChatModule,
CodeEditorModule,
LibroAICompletionModule,
);
Loading

0 comments on commit 86fcd53

Please sign in to comment.