Skip to content

Commit

Permalink
fix(chat): move variable drop handler registration to the frontend
Browse files Browse the repository at this point in the history
In #14787 we've introduced a DOM type dependency to the common package, which prevents it from being properly used on the backend. This change moves the parts that depend on the DOM to the frontend.
  • Loading branch information
planger committed Feb 19, 2025
1 parent 0ff09c2 commit 263e868
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 46 deletions.
7 changes: 4 additions & 3 deletions packages/ai-chat-ui/src/browser/chat-input-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import { IMouseEvent } from '@theia/monaco-editor-core';
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
import { MonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
import { CHAT_VIEW_LANGUAGE_EXTENSION } from './chat-view-language-contribution';
import { AIVariableResolutionRequest, AIVariableService } from '@theia/ai-core';
import { AIVariableResolutionRequest } from '@theia/ai-core';
import { FrontendVariableService } from '@theia/ai-core/lib/browser';
import { ContextVariablePicker } from './context-variable-picker';

type Query = (query: string, context?: AIVariableResolutionRequest[]) => Promise<void>;
Expand Down Expand Up @@ -55,8 +56,8 @@ export class AIChatInputWidget extends ReactWidget {
@inject(AIChatInputConfiguration) @optional()
protected readonly configuration: AIChatInputConfiguration | undefined;

@inject(AIVariableService)
protected readonly variableService: AIVariableService;
@inject(FrontendVariableService)
protected readonly variableService: FrontendVariableService;

@inject(LabelProvider)
protected readonly labelProvider: LabelProvider;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { AIVariableContext, AIVariableContribution, AIVariableDropResult, AIVariableResolutionRequest, AIVariableService, PromptText } from '@theia/ai-core';
import { AIVariableContext, AIVariableResolutionRequest, PromptText } from '@theia/ai-core';
import { AIVariableDropResult, FrontendVariableContribution, FrontendVariableService } from '@theia/ai-core/lib/browser';
import { FILE_VARIABLE } from '@theia/ai-core/lib/browser/file-variable-contribution';
import { CancellationToken, QuickInputService, URI } from '@theia/core';
import { inject, injectable } from '@theia/core/shared/inversify';
Expand All @@ -24,7 +25,7 @@ import { WorkspaceService } from '@theia/workspace/lib/browser';
import { FileService } from '@theia/filesystem/lib/browser/file-service';

@injectable()
export class FileChatVariableContribution implements AIVariableContribution {
export class FileChatVariableContribution implements FrontendVariableContribution {
@inject(FileService)
protected readonly fileService: FileService;

Expand All @@ -37,7 +38,7 @@ export class FileChatVariableContribution implements AIVariableContribution {
@inject(QuickFileSelectService)
protected readonly quickFileSelectService: QuickFileSelectService;

registerVariables(service: AIVariableService): void {
registerVariables(service: FrontendVariableService): void {
service.registerArgumentPicker(FILE_VARIABLE, this.triggerArgumentPicker.bind(this));
service.registerArgumentCompletionProvider(FILE_VARIABLE, this.provideArgumentCompletionItems.bind(this));
service.registerDropHandler(this.handleDrop.bind(this));
Expand Down
5 changes: 3 additions & 2 deletions packages/ai-core/src/browser/ai-core-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import { AICoreFrontendApplicationContribution } from './ai-core-frontend-applic
import { bindAICorePreferences } from './ai-core-preferences';
import { AISettingsServiceImpl } from './ai-settings-service';
import { FrontendPromptCustomizationServiceImpl } from './frontend-prompt-customization-service';
import { FrontendVariableService } from './frontend-variable-service';
import { DefaultFrontendVariableService, FrontendVariableService } from './frontend-variable-service';
import { PromptTemplateContribution } from './prompttemplate-contribution';
import { FileVariableContribution } from './file-variable-contribution';
import { TheiaVariableContribution } from './theia-variable-contribution';
Expand Down Expand Up @@ -115,7 +115,8 @@ export default new ContainerModule(bind => {
bindViewContribution(bind, AIAgentConfigurationViewContribution);
bind(AISettingsService).to(AISettingsServiceImpl).inRequestScope();
bindContributionProvider(bind, AIVariableContribution);
bind(FrontendVariableService).toSelf().inSingletonScope();
bind(DefaultFrontendVariableService).toSelf().inSingletonScope();
bind(FrontendVariableService).toService(DefaultFrontendVariableService);
bind(AIVariableService).toService(FrontendVariableService);
bind(FrontendApplicationContribution).toService(FrontendVariableService);

Expand Down
51 changes: 48 additions & 3 deletions packages/ai-core/src/browser/frontend-variable-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,58 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { injectable } from '@theia/core/shared/inversify';
import { DefaultAIVariableService } from '../common';
import { Disposable } from '@theia/core';
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
import { injectable } from '@theia/core/shared/inversify';
import { AIVariableContext, AIVariableResolutionRequest, AIVariableService, DefaultAIVariableService } from '../common';

export type AIVariableDropHandler = (event: DragEvent, context: AIVariableContext) => Promise<AIVariableDropResult | undefined>;

export interface AIVariableDropResult {
variables: AIVariableResolutionRequest[],
text?: string
};

export const FrontendVariableService = Symbol('FrontendVariableService');
export interface FrontendVariableService extends AIVariableService {
registerDropHandler(handler: AIVariableDropHandler): Disposable;
unregisterDropHandler(handler: AIVariableDropHandler): void;
getDropResult(event: DragEvent, context: AIVariableContext): Promise<AIVariableDropResult>;
}

export interface FrontendVariableContribution {
registerVariables(service: FrontendVariableService): void;
}

@injectable()
export class FrontendVariableService extends DefaultAIVariableService implements FrontendApplicationContribution {
export class DefaultFrontendVariableService extends DefaultAIVariableService implements FrontendApplicationContribution {
protected dropHandlers = new Set<AIVariableDropHandler>();

onStart(): void {
this.initContributions();
}

registerDropHandler(handler: AIVariableDropHandler): Disposable {
this.dropHandlers.add(handler);
return Disposable.create(() => this.unregisterDropHandler(handler));
}

unregisterDropHandler(handler: AIVariableDropHandler): void {
this.dropHandlers.delete(handler);
}

async getDropResult(event: DragEvent, context: AIVariableContext): Promise<AIVariableDropResult> {
let text: string | undefined = undefined;
const variables: AIVariableResolutionRequest[] = [];
for (const handler of this.dropHandlers) {
const result = await handler(event, context);
if (result) {
variables.push(...result.variables);
if (text === undefined) {
text = result.text;
}
}
}
return { variables, text };
}
}
1 change: 1 addition & 0 deletions packages/ai-core/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ export * from './frontend-language-model-registry';
export * from './frontend-variable-service';
export * from './prompttemplate-contribution';
export * from './theia-variable-contribution';
export * from './frontend-variable-service';
36 changes: 1 addition & 35 deletions packages/ai-core/src/common/variable-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,16 +122,10 @@ export namespace AIVariableResolutionRequest {
export interface AIVariableContext {
}

export interface AIVariableDropResult {
variables: AIVariableResolutionRequest[],
text?: string
};

export type AIVariableArg = string | { variable: string, arg?: string } | AIVariableResolutionRequest;

export type AIVariableArgPicker = (context: AIVariableContext) => MaybePromise<string | undefined>;
export type AIVariableArgCompletionProvider = (model: monaco.editor.ITextModel, position: monaco.Position) => MaybePromise<monaco.languages.CompletionItem[] | undefined>;
export type AIVariableDropHandler = (event: DragEvent, context: AIVariableContext) => Promise<AIVariableDropResult | undefined>;

export interface AIVariableResolver {
canResolve(request: AIVariableResolutionRequest, context: AIVariableContext): MaybePromise<number>,
Expand Down Expand Up @@ -159,13 +153,10 @@ export interface AIVariableService {
unregisterArgumentCompletionProvider(variable: AIVariable, argPicker: AIVariableArgCompletionProvider): void;
getArgumentCompletionProvider(name: string): Promise<AIVariableArgCompletionProvider | undefined>;

registerDropHandler(handler: AIVariableDropHandler): Disposable;
unregisterDropHandler(handler: AIVariableDropHandler): void;
getDropResult(event: DragEvent, context: AIVariableContext): Promise<AIVariableDropResult>;

resolveVariable(variable: AIVariableArg, context: AIVariableContext): Promise<ResolvedAIVariable | undefined>;
}

/** Contributions on the frontend can optionally implement `FrontendVariableContribution`. */
export const AIVariableContribution = Symbol('AIVariableContribution');
export interface AIVariableContribution {
registerVariables(service: AIVariableService): void;
Expand All @@ -177,7 +168,6 @@ export class DefaultAIVariableService implements AIVariableService {
protected resolvers = new Map<string, AIVariableResolver[]>();
protected argPickers = new Map<string, AIVariableArgPicker>();
protected argCompletionProviders = new Map<string, AIVariableArgCompletionProvider>();
protected dropHandlers = new Set<AIVariableDropHandler>();

protected readonly onDidChangeVariablesEmitter = new Emitter<void>();
readonly onDidChangeVariables: Event<void> = this.onDidChangeVariablesEmitter.event;
Expand Down Expand Up @@ -301,30 +291,6 @@ export class DefaultAIVariableService implements AIVariableService {
return this.argCompletionProviders.get(this.getKey(name)) ?? undefined;
}

registerDropHandler(handler: AIVariableDropHandler): Disposable {
this.dropHandlers.add(handler);
return Disposable.create(() => this.unregisterDropHandler(handler));
}

unregisterDropHandler(handler: AIVariableDropHandler): void {
this.dropHandlers.delete(handler);
}

async getDropResult(event: DragEvent, context: AIVariableContext): Promise<AIVariableDropResult> {
let text: string | undefined = undefined;
const variables: AIVariableResolutionRequest[] = [];
for (const handler of this.dropHandlers) {
const result = await handler(event, context);
if (result) {
variables.push(...result.variables);
if (text === undefined) {
text = result.text;
}
}
}
return { variables, text };
}

async resolveVariable(request: AIVariableArg, context: AIVariableContext): Promise<ResolvedAIVariable | undefined> {
const variableName = typeof request === 'string' ? request : typeof request.variable === 'string' ? request.variable : request.variable.name;
const variable = this.getVariable(variableName);
Expand Down

0 comments on commit 263e868

Please sign in to comment.