deploy: current vibn theia state
Some checks failed
Playwright Tests / Playwright Tests (ubuntu-22.04, Node.js 22.x) (push) Has been cancelled
3PP License Check / 3PP License Check (11, 22.x, ubuntu-22.04) (push) Has been cancelled
Publish packages to NPM / Perform Publishing (push) Has been cancelled

Made-with: Cursor
This commit is contained in:
2026-02-27 12:01:08 -08:00
commit 8bb5110148
3782 changed files with 640947 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
extends: [
'../../configs/build.eslintrc.json'
],
parserOptions: {
tsconfigRootDir: __dirname,
project: 'tsconfig.json'
}
};

View File

@@ -0,0 +1,31 @@
<div align='center'>
<br />
<img src='https://raw.githubusercontent.com/eclipse-theia/theia/master/logo/theia.svg?sanitize=true' alt='theia-ext-logo' width='100px' />
<h2>ECLIPSE THEIA - AI EDITOR EXTENSION</h2>
<hr />
</div>
## Description
The `@theia/ai-editor` extension brings AI-powered code actions and editor context interaction to Theia. It allows users to interact with AI directly in the editor for code explanations, fixes, and suggestions using AI agents.
## Additional Information
- [API documentation for `@theia/ai-editor`](https://eclipse-theia.github.io/theia/docs/next/modules/_theia_ai-editor.html)
- [Theia - GitHub](https://github.com/eclipse-theia/theia)
- [Theia - Website](https://theia-ide.org/)
## License
- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/)
- [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp)
## Trademark
"Theia" is a trademark of the Eclipse Foundation
<https://www.eclipse.org/theia>

View File

@@ -0,0 +1,57 @@
{
"name": "@theia/ai-editor",
"version": "1.68.0",
"description": "Theia - AI Editor",
"dependencies": {
"@theia/core": "1.68.0",
"@theia/editor": "1.68.0",
"@theia/filesystem": "1.68.0",
"@theia/monaco": "1.68.0",
"@theia/monaco-editor-core": "1.96.302",
"@theia/ai-core": "1.68.0",
"@theia/ai-chat": "1.68.0",
"@theia/ai-chat-ui": "1.68.0",
"@theia/workspace": "1.68.0"
},
"main": "lib/common",
"publishConfig": {
"access": "public"
},
"theiaExtensions": [
{
"frontend": "lib/browser/ai-editor-frontend-module"
}
],
"keywords": [
"theia-extension"
],
"license": "EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0",
"repository": {
"type": "git",
"url": "https://github.com/eclipse-theia/theia.git"
},
"bugs": {
"url": "https://github.com/eclipse-theia/theia/issues"
},
"homepage": "https://github.com/eclipse-theia/theia",
"files": [
"data",
"lib",
"src",
"style"
],
"scripts": {
"build": "theiaext build",
"clean": "theiaext clean",
"compile": "theiaext compile",
"lint": "theiaext lint",
"test": "theiaext test",
"watch": "theiaext watch"
},
"devDependencies": {
"@theia/ext-scripts": "1.68.0"
},
"nyc": {
"extends": "../../configs/nyc.json"
}
}

View File

@@ -0,0 +1,119 @@
// *****************************************************************************
// Copyright (C) 2025 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
import { CommandService } from '@theia/core/lib/common/command';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { inject, injectable } from '@theia/core/shared/inversify';
import * as monaco from '@theia/monaco-editor-core';
import { MonacoEditorService } from '@theia/monaco/lib/browser/monaco-editor-service';
import { AIActivationService } from '@theia/ai-core/lib/browser/ai-activation-service';
import { nls } from '@theia/core';
export const AI_EDITOR_SEND_TO_CHAT = {
id: 'ai-editor.sendToChat',
};
@injectable()
export class AICodeActionProvider implements FrontendApplicationContribution {
@inject(CommandService)
protected readonly commandService: CommandService;
@inject(MonacoEditorService)
protected readonly monacoEditorService: MonacoEditorService;
@inject(AIActivationService)
protected readonly activationService: AIActivationService;
protected readonly toDispose = new DisposableCollection();
onStart(): void {
this.registerCodeActionProvider();
// Listen to AI activation changes and re-register the provider
this.activationService.onDidChangeActiveStatus(() => {
this.toDispose.dispose();
this.registerCodeActionProvider();
});
}
dispose(): void {
this.toDispose.dispose();
}
protected registerCodeActionProvider(): void {
if (!this.activationService.isActive) {
// AI is disabled, don't register the provider
return;
}
const disposable = monaco.languages.registerCodeActionProvider('*', {
provideCodeActions: (model, range, context, token) => {
// Double-check activation status in the provider
if (!this.activationService.isActive) {
return { actions: [], dispose: () => { } };
}
// Filter for error markers only
const errorMarkers = context.markers.filter(marker =>
marker.severity === monaco.MarkerSeverity.Error);
if (errorMarkers.length === 0) {
return { actions: [], dispose: () => { } };
}
const actions: monaco.languages.CodeAction[] = [];
// Create code actions for each error marker: Fix with AI and Explain with AI
errorMarkers.forEach(marker => {
actions.push({
title: nls.localizeByDefault('Fix with AI'),
diagnostics: [marker],
isAI: true,
kind: 'quickfix',
command: {
id: AI_EDITOR_SEND_TO_CHAT.id,
title: nls.localizeByDefault('Fix with AI'),
arguments: [{
prompt: `@Coder ${nls.localize('theia/ai/editor/fixWithAI/prompt', 'Help to fix this error')}: "${marker.message}"`
}]
}
});
actions.push({
title: nls.localize('theia/ai/editor/explainWithAI/title', 'Explain with AI'),
diagnostics: [marker],
kind: 'quickfix',
isAI: true,
command: {
id: AI_EDITOR_SEND_TO_CHAT.id,
title: nls.localize('theia/ai/editor/explainWithAI/title', 'Explain with AI'),
arguments: [{
prompt: `@Architect ${nls.localize('theia/ai/editor/explainWithAI/prompt', 'Explain this error')}: "${marker.message}"`
}]
}
});
});
return {
actions: actions,
dispose: () => { }
};
}
});
this.toDispose.push(disposable);
}
}

View File

@@ -0,0 +1,161 @@
// *****************************************************************************
// Copyright (C) 2025 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { ChatAgentLocation, ChatRequest, ChatService } from '@theia/ai-chat';
import { AICommandHandlerFactory, ENABLE_AI_CONTEXT_KEY } from '@theia/ai-core/lib/browser';
import { isObject, isString, MenuContribution, MenuModelRegistry } from '@theia/core';
import { ApplicationShell, codicon, KeybindingContribution, KeybindingRegistry } from '@theia/core/lib/browser';
import { Command, CommandContribution, CommandRegistry } from '@theia/core/lib/common';
import { inject, injectable } from '@theia/core/shared/inversify';
import { EditorContextMenu, EditorWidget } from '@theia/editor/lib/browser';
import { MonacoCommandRegistry, MonacoEditorCommandHandler } from '@theia/monaco/lib/browser/monaco-command-registry';
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
import { AskAIInputMonacoZoneWidget } from './ask-ai-input-monaco-zone-widget';
import { AskAIInputFactory } from './ask-ai-input-widget';
export namespace AI_EDITOR_COMMANDS {
export const AI_EDITOR_ASK_AI: Command = Command.toLocalizedCommand({
id: 'ai-editor.contextAction',
label: 'Ask AI',
iconClass: codicon('sparkle')
}, 'theia/ai-editor/contextMenu');
export const AI_EDITOR_SEND_TO_CHAT: Command = Command.toLocalizedCommand({
id: 'ai-editor.sendToChat',
label: 'Send to AI Chat'
}, 'theia/ai-editor/sendToChat');
};
@injectable()
export class AiEditorCommandContribution implements CommandContribution, MenuContribution, KeybindingContribution {
@inject(MonacoCommandRegistry)
protected readonly monacoCommandRegistry: MonacoCommandRegistry;
@inject(ChatService)
protected readonly chatService: ChatService;
@inject(AICommandHandlerFactory)
protected readonly commandHandlerFactory: AICommandHandlerFactory;
@inject(AskAIInputFactory)
protected readonly askAIInputFactory: AskAIInputFactory;
@inject(ApplicationShell)
protected readonly shell: ApplicationShell;
protected askAiInputWidget: AskAIInputMonacoZoneWidget | undefined;
registerCommands(registry: CommandRegistry): void {
registry.registerCommand(AI_EDITOR_COMMANDS.AI_EDITOR_ASK_AI);
registry.registerCommand(AI_EDITOR_COMMANDS.AI_EDITOR_SEND_TO_CHAT);
this.monacoCommandRegistry.registerHandler(AI_EDITOR_COMMANDS.AI_EDITOR_ASK_AI.id, this.wrapMonacoHandler(this.showInputWidgetHandler()));
this.monacoCommandRegistry.registerHandler(AI_EDITOR_COMMANDS.AI_EDITOR_SEND_TO_CHAT.id, this.wrapMonacoHandler(this.sendToChatHandler()));
}
protected showInputWidgetHandler(): MonacoEditorCommandHandler {
return {
execute: (editor: MonacoEditor) => {
this.showInputWidget(editor);
},
isEnabled: (editor: MonacoEditor) =>
this.shell.currentWidget instanceof EditorWidget && (this.shell.currentWidget as EditorWidget).editor === editor
};
}
private showInputWidget(editor: MonacoEditor): void {
this.cleanupInputWidget();
// Create the input widget using the factory
this.askAiInputWidget = new AskAIInputMonacoZoneWidget(editor.getControl(), this.askAIInputFactory);
this.askAiInputWidget.onSubmit(request => {
this.createNewChatSession(request);
this.cleanupInputWidget();
});
const line = editor.getControl().getPosition()?.lineNumber ?? 1;
this.askAiInputWidget.showAtLine(line);
this.askAiInputWidget.onCancel(() => {
this.cleanupInputWidget();
});
}
private cleanupInputWidget(): void {
if (this.askAiInputWidget) {
this.askAiInputWidget.dispose();
this.askAiInputWidget = undefined;
}
}
protected sendToChatHandler(): MonacoEditorCommandHandler {
return {
execute: (_editor: MonacoEditor, ...args: unknown[]) => {
if (containsPrompt(args)) {
const prompt = args
.filter(isPromptArg)
.map(arg => arg.prompt)
.join();
this.createNewChatSession({ text: prompt } as ChatRequest);
}
},
isEnabled: (_editor: MonacoEditor, ...args: unknown[]) => containsPrompt(args)
};
}
private createNewChatSession(request: ChatRequest): void {
const session = this.chatService.createSession(ChatAgentLocation.Panel, { focus: true });
this.chatService.sendRequest(session.id, {
...request,
text: `${request.text} #editorContext`,
});
}
protected wrapMonacoHandler(handler: MonacoEditorCommandHandler): MonacoEditorCommandHandler {
const wrappedHandler = this.commandHandlerFactory(handler);
return {
execute: wrappedHandler.execute,
isEnabled: wrappedHandler.isEnabled
};
}
registerMenus(menus: MenuModelRegistry): void {
menus.registerMenuAction(EditorContextMenu.NAVIGATION, {
commandId: AI_EDITOR_COMMANDS.AI_EDITOR_ASK_AI.id,
label: AI_EDITOR_COMMANDS.AI_EDITOR_ASK_AI.label,
icon: AI_EDITOR_COMMANDS.AI_EDITOR_ASK_AI.iconClass,
when: ENABLE_AI_CONTEXT_KEY
});
}
registerKeybindings(registry: KeybindingRegistry): void {
registry.registerKeybinding({
command: AI_EDITOR_COMMANDS.AI_EDITOR_ASK_AI.id,
keybinding: 'ctrlcmd+i',
when: `${ENABLE_AI_CONTEXT_KEY} && editorFocus && !editorReadonly`
});
}
}
function containsPrompt(args: unknown[]): boolean {
return args.some(arg => isPromptArg(arg));
}
function isPromptArg(arg: unknown): arg is { prompt: string } {
return isObject(arg) && 'prompt' in arg && isString(arg.prompt);
}

View File

@@ -0,0 +1,191 @@
// *****************************************************************************
// Copyright (C) 2025 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { AIVariable, AIVariableContext, AIVariableContribution, AIVariableResolutionRequest, AIVariableResolver, ResolvedAIContextVariable } from '@theia/ai-core';
import { FrontendVariableService } from '@theia/ai-core/lib/browser';
import { inject, injectable } from '@theia/core/shared/inversify';
import { codiconArray } from '@theia/core/lib/browser';
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
import { MonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
import * as monaco from '@theia/monaco-editor-core';
import { nls } from '@theia/core';
export const EDITOR_CONTEXT_VARIABLE: AIVariable = {
id: 'editorContext',
description: nls.localize('theia/ai/editor/editorContextVariable/description', 'Resolves editor specific context information'),
name: 'editorContext',
label: nls.localize('theia/ai/editor/editorContextVariable/label', 'EditorContext'),
iconClasses: codiconArray('file'),
isContextVariable: true,
args: []
};
@injectable()
export class EditorContextVariableContribution implements AIVariableContribution, AIVariableResolver {
@inject(MonacoEditorProvider)
protected readonly monacoEditors: MonacoEditorProvider;
@inject(WorkspaceService)
protected readonly workspaceService: WorkspaceService;
registerVariables(service: FrontendVariableService): void {
service.registerResolver(EDITOR_CONTEXT_VARIABLE, this);
}
async canResolve(request: AIVariableResolutionRequest, _: AIVariableContext): Promise<number> {
return request.variable.name === EDITOR_CONTEXT_VARIABLE.name ? 1 : 0;
}
async resolve(request: AIVariableResolutionRequest, _: AIVariableContext): Promise<ResolvedAIContextVariable | undefined> {
const editor = this.monacoEditors.current;
if (!editor) {
return undefined;
}
const model = editor.getControl().getModel();
const selection = editor.getControl().getSelection();
if (!model || !selection) {
return undefined;
}
// Extract file information
const uri = editor.getResourceUri();
const languageId = model.getLanguageId();
// Extract selection information
const selectedText = model.getValueInRange(selection);
const hasSelection = !selection.isEmpty();
// Text position information
const position = editor.getControl().getPosition();
const lineNumber = position ? position.lineNumber : 0;
const column = position ? position.column : 0;
// Get workspace-relative path
const workspaceRelativePath = uri ? await this.workspaceService.getWorkspaceRelativePath(uri) : '';
// Create base context information
const baseContext = {
file: {
uri: workspaceRelativePath,
languageId,
fileName: uri ? uri.path.base : ''
},
selection: {
text: selectedText,
isEmpty: !hasSelection,
startLineNumber: selection.startLineNumber,
startColumn: selection.startColumn,
endLineNumber: selection.endLineNumber,
endColumn: selection.endColumn
},
position: {
lineNumber,
column,
lineContent: position ? model.getLineContent(position.lineNumber) : ''
}
};
const diagnostics = await this.getDiagnosticContext(editor);
const fullContext = {
...baseContext,
diagnostics
};
const contextValue = JSON.stringify(fullContext, undefined, 2);
return {
variable: request.variable,
value: contextValue, // Simplified visible value
contextValue // Full detailed context for AI processing
};
}
protected getDiagnosticContext(editor: MonacoEditor): Record<string, unknown> {
const model = editor.getControl().getModel();
if (!model) {
return {};
}
const markers = monaco.editor.getModelMarkers({ resource: model.uri });
if (markers.length === 0) {
return {
errorCount: 0,
warningCount: 0,
infoCount: 0,
hintCount: 0,
totalIssues: 0
};
}
const issues: Array<{
line: number;
column: number;
severity: string;
message: string;
source?: string;
}> = [];
const markerCounter = {
[monaco.MarkerSeverity.Error]: 0,
[monaco.MarkerSeverity.Warning]: 0,
[monaco.MarkerSeverity.Info]: 0,
[monaco.MarkerSeverity.Hint]: 0
};
markers.forEach(marker => {
markerCounter[marker.severity]++;
issues.push({
line: marker.startLineNumber,
column: marker.startColumn,
severity: this.severityToString(marker.severity),
message: marker.message,
source: marker.source
});
});
return {
diagnosticCounts: {
errorCount: markerCounter[monaco.MarkerSeverity.Error],
warningCount: markerCounter[monaco.MarkerSeverity.Warning],
infoCount: markerCounter[monaco.MarkerSeverity.Info],
hintCount: markerCounter[monaco.MarkerSeverity.Hint]
},
totalIssues: markers.length,
issues
};
}
protected severityToString(severity: monaco.MarkerSeverity): string {
switch (severity) {
case monaco.MarkerSeverity.Error:
return 'error';
case monaco.MarkerSeverity.Warning:
return 'warning';
case monaco.MarkerSeverity.Info:
return 'info';
case monaco.MarkerSeverity.Hint:
return 'hint';
default:
return 'unknown';
}
}
}

View File

@@ -0,0 +1,56 @@
// *****************************************************************************
// Copyright (C) 2025 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { AIVariableContribution } from '@theia/ai-core';
import { FrontendApplicationContribution, KeybindingContribution } from '@theia/core/lib/browser';
import { CommandContribution, MenuContribution } from '@theia/core/lib/common';
import { ContainerModule } from '@theia/core/shared/inversify';
import '../../style/ask-ai-input.css';
import { AICodeActionProvider } from './ai-code-action-provider';
import { AiEditorCommandContribution } from './ai-editor-command-contribution';
import { EditorContextVariableContribution } from './ai-editor-context-variable';
import {
AskAIInputArgs,
AskAIInputConfiguration,
AskAIInputFactory,
AskAIInputWidget
} from './ask-ai-input-widget';
export default new ContainerModule(bind => {
bind(AiEditorCommandContribution).toSelf().inSingletonScope();
bind(CommandContribution).toService(AiEditorCommandContribution);
bind(MenuContribution).toService(AiEditorCommandContribution);
bind(KeybindingContribution).toService(AiEditorCommandContribution);
bind(AIVariableContribution).to(EditorContextVariableContribution).inSingletonScope();
bind(AICodeActionProvider).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(AICodeActionProvider);
bind(AskAIInputFactory).toFactory(ctx => (args: AskAIInputArgs) => {
const container = ctx.container.createChild();
container.bind(AskAIInputArgs).toConstantValue(args);
container.bind(AskAIInputConfiguration).toConstantValue({
showContext: true,
showPinnedAgent: true,
showChangeSet: false,
showSuggestions: false
} satisfies AskAIInputConfiguration);
container.bind(AskAIInputWidget).toSelf().inSingletonScope();
return container.get(AskAIInputWidget);
});
});

View File

@@ -0,0 +1,114 @@
// *****************************************************************************
// Copyright (C) 2025 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { ChatRequest } from '@theia/ai-chat';
import { Disposable } from '@theia/core/lib/common/disposable';
import { Emitter, Event } from '@theia/core/lib/common/event';
import * as monaco from '@theia/monaco-editor-core';
import { MonacoEditorZoneWidget } from '@theia/monaco/lib/browser/monaco-editor-zone-widget';
import { AskAIInputFactory, AskAIInputWidget } from './ask-ai-input-widget';
/**
* A widget that shows the Ask AI input UI in a Monaco editor zone.
*/
export class AskAIInputMonacoZoneWidget extends MonacoEditorZoneWidget implements Disposable {
protected readonly inputWidget: AskAIInputWidget;
protected readonly onSubmitEmitter = new Emitter<ChatRequest>();
protected readonly onCancelEmitter = new Emitter<void>();
readonly onSubmit: Event<ChatRequest> = this.onSubmitEmitter.event;
readonly onCancel: Event<void> = this.onCancelEmitter.event;
constructor(
editorInstance: monaco.editor.ICodeEditor,
inputWidgetFactory: AskAIInputFactory
) {
super(editorInstance, false /* showArrow */);
this.containerNode.classList.add('ask-ai-input-monaco-zone-widget');
this.inputWidget = inputWidgetFactory({
onSubmit: event => this.handleSubmit(event),
onCancel: () => this.handleCancel()
});
this.inputWidget.onDidResize(() => this.adjustZoneHeight());
this.toDispose.pushAll([
this.onSubmitEmitter,
this.onCancelEmitter,
this.inputWidget,
]);
}
override show(options: MonacoEditorZoneWidget.Options): void {
super.show(options);
this.renderReactWidget();
this.registerListeners();
}
showAtLine(lineNumber: number): void {
const options: MonacoEditorZoneWidget.Options = {
afterLineNumber: lineNumber,
heightInLines: 5,
frameWidth: 1,
showFrame: true,
};
this.show(options);
}
protected renderReactWidget(): void {
this.containerNode.append(this.inputWidget.node);
this.inputWidget.activate();
this.inputWidget.update();
}
protected adjustZoneHeight(): void {
if (!this.viewZone) {
return;
}
const editorLineHeight = this.editor.getOption(monaco.editor.EditorOption.lineHeight);
const zoneWidgetHeight = this.inputWidget.node.parentElement ? this.inputWidget.node.parentElement.scrollHeight : this.inputWidget.node.scrollHeight;
const requiredLines = Math.max(5, Math.ceil(zoneWidgetHeight / editorLineHeight));
if (this.viewZone.heightInLines !== requiredLines) {
this.layout(requiredLines);
}
}
protected handleSubmit(request: ChatRequest): void {
this.onSubmitEmitter.fire(request);
this.hide();
}
protected handleCancel(): void {
this.onCancelEmitter.fire();
this.hide();
}
protected registerListeners(): void {
this.toHide.push(this.editor.onKeyDown(e => {
if (e.keyCode === monaco.KeyCode.Escape) {
this.handleCancel();
e.preventDefault();
e.stopPropagation();
}
}));
}
}

View File

@@ -0,0 +1,93 @@
// *****************************************************************************
// Copyright (C) 2025 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { ChatRequest, MutableChatModel } from '@theia/ai-chat';
import { AIChatInputConfiguration, AIChatInputWidget } from '@theia/ai-chat-ui/lib/browser/chat-input-widget';
import { CHAT_VIEW_LANGUAGE_EXTENSION } from '@theia/ai-chat-ui/lib/browser/chat-view-language-contribution';
import { generateUuid, URI } from '@theia/core';
import { inject, injectable, optional, postConstruct } from '@theia/core/shared/inversify';
export const AskAIInputConfiguration = Symbol('AskAIInputConfiguration');
export interface AskAIInputConfiguration extends AIChatInputConfiguration { }
export const AskAIInputArgs = Symbol('AskAIInputArgs');
export interface AskAIInputArgs {
onSubmit: (request: ChatRequest) => void;
onCancel: () => void;
}
export const AskAIInputFactory = Symbol('AskAIInputFactory');
export type AskAIInputFactory = (args: AskAIInputArgs) => AskAIInputWidget;
/**
* React input widget for Ask AI functionality, extending the AIChatInputWidget.
*/
@injectable()
export class AskAIInputWidget extends AIChatInputWidget {
public static override ID = 'ask-ai-input-widget';
@inject(AskAIInputArgs) @optional()
protected readonly args: AskAIInputArgs | undefined;
@inject(AskAIInputConfiguration) @optional()
protected override readonly configuration: AskAIInputConfiguration | undefined;
protected readonly resourceId = generateUuid();
protected override heightInLines = 3;
@postConstruct()
protected override init(): void {
super.init();
this.id = AskAIInputWidget.ID;
const noOp = () => { };
// We need to set those values here, otherwise the widget will throw an error
this.onUnpin = noOp;
this.onCancel = noOp;
this.onDeleteChangeSet = noOp;
this.onDeleteChangeSetElement = noOp;
// Create a temporary chat model for the widget
this.chatModel = new MutableChatModel();
this.setEnabled(true);
this.onQuery = this.handleSubmit.bind(this);
this.onCancel = this.handleCancel.bind(this);
}
protected override getResourceUri(): URI {
return new URI(`ask-ai:/input-${this.resourceId}.${CHAT_VIEW_LANGUAGE_EXTENSION}`);
}
protected handleSubmit(query: string): Promise<void> {
const userInput = query.trim();
if (userInput) {
const request: ChatRequest = { text: userInput, variables: this._chatModel.context.getVariables() };
this.args?.onSubmit(request);
}
return Promise.resolve();
}
protected handleCancel(): void {
this.args?.onCancel();
}
protected override onEscape(): void {
this.handleCancel();
}
}

View File

@@ -0,0 +1,18 @@
// *****************************************************************************
// Copyright (C) 2025 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
export * from './ai-editor-command-contribution';
export * from './ask-ai-input-widget';

View File

@@ -0,0 +1,28 @@
// *****************************************************************************
// Copyright (C) 2024 EclipseSource GmbH and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
/* note: this bogus test file is required so that
we are able to run mocha unit tests on this
package, without having any actual unit tests in it.
This way a coverage report will be generated,
showing 0% coverage, instead of no report.
This file can be removed once we have real unit
tests in place. */
describe('ai-editor package', () => {
it('support code coverage statistics', () => true);
});

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2025 EclipseSource GmbH.
*
* This program and the accompanying materials are made available under the terms
* of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
*/
.ask-ai-input-monaco-zone-widget {
background-color: var(--theia-editorWidget-background);
height: auto !important;
}
.ask-ai-input-monaco-zone-widget .theia-ChatInput {
margin-top: 10px;
margin-left: 55px;
width: 75%;
}

View File

@@ -0,0 +1,37 @@
{
"extends": "../../configs/base.tsconfig",
"compilerOptions": {
"composite": true,
"rootDir": "src",
"outDir": "lib"
},
"include": [
"src"
],
"references": [
{
"path": "../ai-chat"
},
{
"path": "../ai-chat-ui"
},
{
"path": "../ai-core"
},
{
"path": "../core"
},
{
"path": "../editor"
},
{
"path": "../filesystem"
},
{
"path": "../monaco"
},
{
"path": "../workspace"
}
]
}