@theia/ai-chat-ui 1.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/lib/browser/ai-chat-ui-contribution.d.ts +24 -0
- package/lib/browser/ai-chat-ui-contribution.d.ts.map +1 -0
- package/lib/browser/ai-chat-ui-contribution.js +161 -0
- package/lib/browser/ai-chat-ui-contribution.js.map +1 -0
- package/lib/browser/ai-chat-ui-frontend-module.d.ts +5 -0
- package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -0
- package/lib/browser/ai-chat-ui-frontend-module.js +83 -0
- package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -0
- package/lib/browser/chat-input-widget.d.ts +32 -0
- package/lib/browser/chat-input-widget.d.ts.map +1 -0
- package/lib/browser/chat-input-widget.js +205 -0
- package/lib/browser/chat-input-widget.js.map +1 -0
- package/lib/browser/chat-node-toolbar-action-contribution.d.ts +47 -0
- package/lib/browser/chat-node-toolbar-action-contribution.d.ts.map +1 -0
- package/lib/browser/chat-node-toolbar-action-contribution.js +25 -0
- package/lib/browser/chat-node-toolbar-action-contribution.js.map +1 -0
- package/lib/browser/chat-response-part-renderer.d.ts +10 -0
- package/lib/browser/chat-response-part-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-part-renderer.js +20 -0
- package/lib/browser/chat-response-part-renderer.js.map +1 -0
- package/lib/browser/chat-response-renderer/ai-editor-manager.d.ts +36 -0
- package/lib/browser/chat-response-renderer/ai-editor-manager.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/ai-editor-manager.js +184 -0
- package/lib/browser/chat-response-renderer/ai-editor-manager.js.map +1 -0
- package/lib/browser/chat-response-renderer/code-part-renderer.d.ts +45 -0
- package/lib/browser/chat-response-renderer/code-part-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/code-part-renderer.js +186 -0
- package/lib/browser/chat-response-renderer/code-part-renderer.js.map +1 -0
- package/lib/browser/chat-response-renderer/command-part-renderer.d.ts +12 -0
- package/lib/browser/chat-response-renderer/command-part-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/command-part-renderer.js +70 -0
- package/lib/browser/chat-response-renderer/command-part-renderer.js.map +1 -0
- package/lib/browser/chat-response-renderer/error-part-renderer.d.ts +9 -0
- package/lib/browser/chat-response-renderer/error-part-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/error-part-renderer.js +40 -0
- package/lib/browser/chat-response-renderer/error-part-renderer.js.map +1 -0
- package/lib/browser/chat-response-renderer/horizontal-layout-part-renderer.d.ts +12 -0
- package/lib/browser/chat-response-renderer/horizontal-layout-part-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/horizontal-layout-part-renderer.js +54 -0
- package/lib/browser/chat-response-renderer/horizontal-layout-part-renderer.js.map +1 -0
- package/lib/browser/chat-response-renderer/index.d.ts +9 -0
- package/lib/browser/chat-response-renderer/index.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/index.js +27 -0
- package/lib/browser/chat-response-renderer/index.js.map +1 -0
- package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts +17 -0
- package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/markdown-part-renderer.js +70 -0
- package/lib/browser/chat-response-renderer/markdown-part-renderer.js.map +1 -0
- package/lib/browser/chat-response-renderer/text-part-renderer.d.ts +9 -0
- package/lib/browser/chat-response-renderer/text-part-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/text-part-renderer.js +41 -0
- package/lib/browser/chat-response-renderer/text-part-renderer.js.map +1 -0
- package/lib/browser/chat-response-renderer/text-part-renderer.spec.d.ts +2 -0
- package/lib/browser/chat-response-renderer/text-part-renderer.spec.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/text-part-renderer.spec.js +46 -0
- package/lib/browser/chat-response-renderer/text-part-renderer.spec.js.map +1 -0
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts +9 -0
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.js +49 -0
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -0
- package/lib/browser/chat-tree-view/chat-view-tree-container.d.ts +4 -0
- package/lib/browser/chat-tree-view/chat-view-tree-container.d.ts.map +1 -0
- package/lib/browser/chat-tree-view/chat-view-tree-container.js +33 -0
- package/lib/browser/chat-tree-view/chat-view-tree-container.js.map +1 -0
- package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts +53 -0
- package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts.map +1 -0
- package/lib/browser/chat-tree-view/chat-view-tree-widget.js +324 -0
- package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -0
- package/lib/browser/chat-tree-view/index.d.ts +3 -0
- package/lib/browser/chat-tree-view/index.d.ts.map +1 -0
- package/lib/browser/chat-tree-view/index.js +21 -0
- package/lib/browser/chat-tree-view/index.js.map +1 -0
- package/lib/browser/chat-view-commands.d.ts +8 -0
- package/lib/browser/chat-view-commands.d.ts.map +1 -0
- package/lib/browser/chat-view-commands.js +44 -0
- package/lib/browser/chat-view-commands.js.map +1 -0
- package/lib/browser/chat-view-contribution.d.ts +18 -0
- package/lib/browser/chat-view-contribution.d.ts.map +1 -0
- package/lib/browser/chat-view-contribution.js +153 -0
- package/lib/browser/chat-view-contribution.js.map +1 -0
- package/lib/browser/chat-view-language-contribution.d.ts +20 -0
- package/lib/browser/chat-view-language-contribution.d.ts.map +1 -0
- package/lib/browser/chat-view-language-contribution.js +98 -0
- package/lib/browser/chat-view-language-contribution.js.map +1 -0
- package/lib/browser/chat-view-widget-toolbar-contribution.d.ts +11 -0
- package/lib/browser/chat-view-widget-toolbar-contribution.d.ts.map +1 -0
- package/lib/browser/chat-view-widget-toolbar-contribution.js +65 -0
- package/lib/browser/chat-view-widget-toolbar-contribution.js.map +1 -0
- package/lib/browser/chat-view-widget.d.ts +41 -0
- package/lib/browser/chat-view-widget.d.ts.map +1 -0
- package/lib/browser/chat-view-widget.js +182 -0
- package/lib/browser/chat-view-widget.js.map +1 -0
- package/package.json +59 -0
- package/src/browser/ai-chat-ui-contribution.ts +171 -0
- package/src/browser/ai-chat-ui-frontend-module.ts +101 -0
- package/src/browser/chat-input-widget.tsx +247 -0
- package/src/browser/chat-node-toolbar-action-contribution.ts +63 -0
- package/src/browser/chat-response-part-renderer.ts +25 -0
- package/src/browser/chat-response-renderer/ai-editor-manager.ts +183 -0
- package/src/browser/chat-response-renderer/code-part-renderer.tsx +208 -0
- package/src/browser/chat-response-renderer/command-part-renderer.tsx +60 -0
- package/src/browser/chat-response-renderer/error-part-renderer.tsx +35 -0
- package/src/browser/chat-response-renderer/horizontal-layout-part-renderer.tsx +59 -0
- package/src/browser/chat-response-renderer/index.ts +23 -0
- package/src/browser/chat-response-renderer/markdown-part-renderer.tsx +71 -0
- package/src/browser/chat-response-renderer/text-part-renderer.spec.ts +50 -0
- package/src/browser/chat-response-renderer/text-part-renderer.tsx +35 -0
- package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +49 -0
- package/src/browser/chat-tree-view/chat-view-tree-container.ts +32 -0
- package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +410 -0
- package/src/browser/chat-tree-view/index.ts +18 -0
- package/src/browser/chat-view-commands.ts +45 -0
- package/src/browser/chat-view-contribution.ts +154 -0
- package/src/browser/chat-view-language-contribution.ts +141 -0
- package/src/browser/chat-view-widget-toolbar-contribution.tsx +54 -0
- package/src/browser/chat-view-widget.tsx +194 -0
- package/src/browser/style/index.css +328 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { CancellationToken, ContributionProvider, Prioritizeable, RecursivePartial, URI } from '@theia/core';
|
|
18
|
+
import { inject, injectable, named } from '@theia/core/shared/inversify';
|
|
19
|
+
import { EditorOpenerOptions, EditorWidget, Range } from '@theia/editor/lib/browser';
|
|
20
|
+
|
|
21
|
+
import { EditorPreviewManager } from '@theia/editor-preview/lib/browser/editor-preview-manager';
|
|
22
|
+
import { DocumentSymbol } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
|
|
23
|
+
import { TextModel } from '@theia/monaco-editor-core/esm/vs/editor/common/model/textModel';
|
|
24
|
+
import { ILanguageFeaturesService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/languageFeatures';
|
|
25
|
+
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
|
26
|
+
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
|
27
|
+
import { MonacoToProtocolConverter } from '@theia/monaco/lib/browser/monaco-to-protocol-converter';
|
|
28
|
+
|
|
29
|
+
/** Regex to match GitHub-style position and range declaration with line (L) and column (C) */
|
|
30
|
+
export const LOCATION_REGEX = /#L(\d+)?(?:C(\d+))?(?:-L(\d+)?(?:C(\d+))?)?$/;
|
|
31
|
+
|
|
32
|
+
export const AIEditorSelectionResolver = Symbol('AIEditorSelectionResolver');
|
|
33
|
+
export interface AIEditorSelectionResolver {
|
|
34
|
+
/**
|
|
35
|
+
* The priority of the resolver. A higher value resolver will be called before others.
|
|
36
|
+
*/
|
|
37
|
+
priority?: number;
|
|
38
|
+
resolveSelection(widget: EditorWidget, options: EditorOpenerOptions, uri?: URI): Promise<RecursivePartial<Range> | undefined>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@injectable()
|
|
42
|
+
export class GitHubSelectionResolver implements AIEditorSelectionResolver {
|
|
43
|
+
priority = 100;
|
|
44
|
+
|
|
45
|
+
async resolveSelection(widget: EditorWidget, options: EditorOpenerOptions, uri?: URI): Promise<RecursivePartial<Range> | undefined> {
|
|
46
|
+
if (!uri) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// We allow the GitHub syntax of selecting a range in markdown 'L1', 'L1-L2' 'L1-C1_L2-C2' (starting at line 1 and column 1)
|
|
50
|
+
const match = uri?.toString().match(LOCATION_REGEX);
|
|
51
|
+
if (!match) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// we need to adapt the position information from one-based (in GitHub) to zero-based (in Theia)
|
|
55
|
+
const startLine = match[1] ? parseInt(match[1], 10) - 1 : undefined;
|
|
56
|
+
// if no start column is given, we assume the start of the line
|
|
57
|
+
const startColumn = match[2] ? parseInt(match[2], 10) - 1 : 0;
|
|
58
|
+
const endLine = match[3] ? parseInt(match[3], 10) - 1 : undefined;
|
|
59
|
+
// if no end column is given, we assume the end of the line
|
|
60
|
+
const endColumn = match[4] ? parseInt(match[4], 10) - 1 : endLine ? widget.editor.document.getLineMaxColumn(endLine) : undefined;
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
start: { line: startLine, character: startColumn },
|
|
64
|
+
end: { line: endLine, character: endColumn }
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@injectable()
|
|
70
|
+
export class TypeDocSymbolSelectionResolver implements AIEditorSelectionResolver {
|
|
71
|
+
priority = 50;
|
|
72
|
+
|
|
73
|
+
@inject(MonacoToProtocolConverter) protected readonly m2p: MonacoToProtocolConverter;
|
|
74
|
+
|
|
75
|
+
async resolveSelection(widget: EditorWidget, options: EditorOpenerOptions, uri?: URI): Promise<RecursivePartial<Range> | undefined> {
|
|
76
|
+
if (!uri) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const editor = MonacoEditor.get(widget);
|
|
80
|
+
const monacoEditor = editor?.getControl();
|
|
81
|
+
if (!monacoEditor) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const symbolPath = this.findSymbolPath(uri);
|
|
85
|
+
if (!symbolPath) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const textModel = monacoEditor.getModel() as unknown as TextModel;
|
|
89
|
+
if (!textModel) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// try to find the symbol through the document symbol provider
|
|
94
|
+
// support referencing nested symbols by separating a dot path similar to TypeDoc
|
|
95
|
+
for (const provider of StandaloneServices.get(ILanguageFeaturesService).documentSymbolProvider.ordered(textModel)) {
|
|
96
|
+
const symbols = await provider.provideDocumentSymbols(textModel, CancellationToken.None);
|
|
97
|
+
const match = this.findSymbolByPath(symbols ?? [], symbolPath);
|
|
98
|
+
if (match) {
|
|
99
|
+
return this.m2p.asRange(match.selectionRange);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
protected findSymbolPath(uri: URI): string[] | undefined {
|
|
105
|
+
return uri.fragment.split('.');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
protected findSymbolByPath(symbols: DocumentSymbol[], symbolPath: string[]): DocumentSymbol | undefined {
|
|
109
|
+
if (!symbols || symbolPath.length === 0) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
let matchedSymbol: DocumentSymbol | undefined = undefined;
|
|
113
|
+
let currentSymbols = symbols;
|
|
114
|
+
for (const part of symbolPath) {
|
|
115
|
+
matchedSymbol = currentSymbols.find(symbol => symbol.name === part);
|
|
116
|
+
if (!matchedSymbol) {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
currentSymbols = matchedSymbol.children || [];
|
|
120
|
+
}
|
|
121
|
+
return matchedSymbol;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@injectable()
|
|
126
|
+
export class TextFragmentSelectionResolver implements AIEditorSelectionResolver {
|
|
127
|
+
async resolveSelection(widget: EditorWidget, options: EditorOpenerOptions, uri?: URI): Promise<RecursivePartial<Range> | undefined> {
|
|
128
|
+
if (!uri) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const fragment = this.findFragment(uri);
|
|
132
|
+
if (!fragment) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const matches = widget.editor.document.findMatches?.({ isRegex: false, matchCase: false, matchWholeWord: false, searchString: fragment }) ?? [];
|
|
136
|
+
if (matches.length > 0) {
|
|
137
|
+
return {
|
|
138
|
+
start: {
|
|
139
|
+
line: matches[0].range.start.line - 1,
|
|
140
|
+
character: matches[0].range.start.character - 1
|
|
141
|
+
},
|
|
142
|
+
end: {
|
|
143
|
+
line: matches[0].range.end.line - 1,
|
|
144
|
+
character: matches[0].range.end.character - 1
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
protected findFragment(uri: URI): string | undefined {
|
|
151
|
+
return uri.fragment;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@injectable()
|
|
156
|
+
export class AIEditorManager extends EditorPreviewManager {
|
|
157
|
+
@inject(ContributionProvider) @named(AIEditorSelectionResolver)
|
|
158
|
+
protected readonly resolvers: ContributionProvider<AIEditorSelectionResolver>;
|
|
159
|
+
|
|
160
|
+
protected override async revealSelection(widget: EditorWidget, options: EditorOpenerOptions = {}, uri?: URI): Promise<void> {
|
|
161
|
+
if (!options.selection) {
|
|
162
|
+
options.selection = await this.resolveSelection(options, widget, uri);
|
|
163
|
+
}
|
|
164
|
+
super.revealSelection(widget, options, uri);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
protected async resolveSelection(options: EditorOpenerOptions, widget: EditorWidget, uri: URI | undefined): Promise<RecursivePartial<Range> | undefined> {
|
|
168
|
+
if (!options.selection) {
|
|
169
|
+
const orderedResolvers = Prioritizeable.prioritizeAllSync(this.resolvers.getContributions(), resolver => resolver.priority ?? 1);
|
|
170
|
+
for (const linkResolver of orderedResolvers) {
|
|
171
|
+
try {
|
|
172
|
+
const selection = await linkResolver.value.resolveSelection(widget, options, uri);
|
|
173
|
+
if (selection) {
|
|
174
|
+
return selection;
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error(error);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
ChatResponseContent,
|
|
19
|
+
CodeChatResponseContent,
|
|
20
|
+
} from '@theia/ai-chat/lib/common';
|
|
21
|
+
import { UntitledResourceResolver, URI } from '@theia/core';
|
|
22
|
+
import { ContextMenuRenderer, TreeNode } from '@theia/core/lib/browser';
|
|
23
|
+
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
|
24
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
25
|
+
import * as React from '@theia/core/shared/react';
|
|
26
|
+
import { ReactNode } from '@theia/core/shared/react';
|
|
27
|
+
import { Position } from '@theia/core/shared/vscode-languageserver-protocol';
|
|
28
|
+
import { EditorManager, EditorWidget } from '@theia/editor/lib/browser';
|
|
29
|
+
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
|
30
|
+
import { MonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
|
|
31
|
+
import { MonacoLanguages } from '@theia/monaco/lib/browser/monaco-languages';
|
|
32
|
+
import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
|
|
33
|
+
import { ChatViewTreeWidget, ResponseNode } from '../chat-tree-view/chat-view-tree-widget';
|
|
34
|
+
import { IMouseEvent } from '@theia/monaco-editor-core';
|
|
35
|
+
|
|
36
|
+
@injectable()
|
|
37
|
+
export class CodePartRenderer
|
|
38
|
+
implements ChatResponsePartRenderer<CodeChatResponseContent> {
|
|
39
|
+
|
|
40
|
+
@inject(ClipboardService)
|
|
41
|
+
protected readonly clipboardService: ClipboardService;
|
|
42
|
+
@inject(EditorManager)
|
|
43
|
+
protected readonly editorManager: EditorManager;
|
|
44
|
+
@inject(UntitledResourceResolver)
|
|
45
|
+
protected readonly untitledResourceResolver: UntitledResourceResolver;
|
|
46
|
+
@inject(MonacoEditorProvider)
|
|
47
|
+
protected readonly editorProvider: MonacoEditorProvider;
|
|
48
|
+
@inject(MonacoLanguages)
|
|
49
|
+
protected readonly languageService: MonacoLanguages;
|
|
50
|
+
@inject(ContextMenuRenderer)
|
|
51
|
+
protected readonly contextMenuRenderer: ContextMenuRenderer;
|
|
52
|
+
|
|
53
|
+
canHandle(response: ChatResponseContent): number {
|
|
54
|
+
if (CodeChatResponseContent.is(response)) {
|
|
55
|
+
return 10;
|
|
56
|
+
}
|
|
57
|
+
return -1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
render(response: CodeChatResponseContent, parentNode: ResponseNode): ReactNode {
|
|
61
|
+
const language = response.language ? this.languageService.getExtension(response.language) : undefined;
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className="theia-CodePartRenderer-root">
|
|
65
|
+
<div className="theia-CodePartRenderer-top">
|
|
66
|
+
<div className="theia-CodePartRenderer-left">{this.renderTitle(response)}</div>
|
|
67
|
+
<div className="theia-CodePartRenderer-right">
|
|
68
|
+
<CopyToClipboardButton code={response.code} clipboardService={this.clipboardService} />
|
|
69
|
+
<InsertCodeAtCursorButton code={response.code} editorManager={this.editorManager} />
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
<div className="theia-CodePartRenderer-separator"></div>
|
|
73
|
+
<div className="theia-CodePartRenderer-bottom">
|
|
74
|
+
<CodeWrapper
|
|
75
|
+
content={response.code}
|
|
76
|
+
language={language}
|
|
77
|
+
editorProvider={this.editorProvider}
|
|
78
|
+
untitledResourceResolver={this.untitledResourceResolver}
|
|
79
|
+
contextMenuCallback={e => this.handleContextMenuEvent(parentNode, e, response.code)}></CodeWrapper>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
protected renderTitle(response: CodeChatResponseContent): ReactNode {
|
|
86
|
+
const uri = response.location?.uri;
|
|
87
|
+
const position = response.location?.position;
|
|
88
|
+
if (uri && position) {
|
|
89
|
+
return <a onClick={this.openFileAtPosition.bind(this, uri, position)}>{this.getTitle(response.location?.uri, response.language)}</a>;
|
|
90
|
+
}
|
|
91
|
+
return this.getTitle(response.location?.uri, response.language);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private getTitle(uri: URI | undefined, language: string | undefined): string {
|
|
95
|
+
// If there is a URI, use the file name as the title. Otherwise, use the language as the title.
|
|
96
|
+
// If there is no language, use a generic fallback title.
|
|
97
|
+
return uri?.path?.toString().split('/').pop() ?? language ?? 'Generated Code';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Opens a file and moves the cursor to the specified position.
|
|
102
|
+
*
|
|
103
|
+
* @param uri - The URI of the file to open.
|
|
104
|
+
* @param position - The position to move the cursor to, specified as {line, character}.
|
|
105
|
+
*/
|
|
106
|
+
async openFileAtPosition(uri: URI, position: Position): Promise<void> {
|
|
107
|
+
const editorWidget = await this.editorManager.open(uri) as EditorWidget;
|
|
108
|
+
if (editorWidget) {
|
|
109
|
+
const editor = editorWidget.editor;
|
|
110
|
+
editor.revealPosition(position);
|
|
111
|
+
editor.focus();
|
|
112
|
+
editor.cursor = position;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
protected handleContextMenuEvent(node: TreeNode | undefined, event: IMouseEvent, code: string): void {
|
|
117
|
+
this.contextMenuRenderer.render({
|
|
118
|
+
menuPath: ChatViewTreeWidget.CONTEXT_MENU,
|
|
119
|
+
anchor: { x: event.posx, y: event.posy },
|
|
120
|
+
args: [node, { code }]
|
|
121
|
+
});
|
|
122
|
+
event.preventDefault();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const CopyToClipboardButton = (props: { code: string, clipboardService: ClipboardService }) => {
|
|
127
|
+
const { code, clipboardService } = props;
|
|
128
|
+
const copyCodeToClipboard = React.useCallback(() => {
|
|
129
|
+
clipboardService.writeText(code);
|
|
130
|
+
}, [code, clipboardService]);
|
|
131
|
+
return <button className='theia-button main' onClick={copyCodeToClipboard}>Copy</button>;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const InsertCodeAtCursorButton = (props: { code: string, editorManager: EditorManager }) => {
|
|
135
|
+
const { code, editorManager } = props;
|
|
136
|
+
const insertCode = React.useCallback(() => {
|
|
137
|
+
const editor = editorManager.currentEditor;
|
|
138
|
+
if (editor) {
|
|
139
|
+
const currentEditor = editor.editor;
|
|
140
|
+
const selection = currentEditor.selection;
|
|
141
|
+
|
|
142
|
+
// Insert the text at the current cursor position
|
|
143
|
+
// If there is a selection, replace the selection with the text
|
|
144
|
+
currentEditor.executeEdits([{
|
|
145
|
+
range: {
|
|
146
|
+
start: selection.start,
|
|
147
|
+
end: selection.end
|
|
148
|
+
},
|
|
149
|
+
newText: code
|
|
150
|
+
}]);
|
|
151
|
+
}
|
|
152
|
+
}, [code, editorManager]);
|
|
153
|
+
return <button className='theia-button main' onClick={insertCode}>Insert at Cursor</button>;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Renders the given code within a Monaco Editor
|
|
158
|
+
*/
|
|
159
|
+
export const CodeWrapper = (props: {
|
|
160
|
+
content: string,
|
|
161
|
+
language?: string,
|
|
162
|
+
untitledResourceResolver: UntitledResourceResolver,
|
|
163
|
+
editorProvider: MonacoEditorProvider,
|
|
164
|
+
contextMenuCallback: (e: IMouseEvent) => void
|
|
165
|
+
}) => {
|
|
166
|
+
// eslint-disable-next-line no-null/no-null
|
|
167
|
+
const ref = React.useRef<HTMLDivElement | null>(null);
|
|
168
|
+
const editorRef = React.useRef<MonacoEditor | undefined>(undefined);
|
|
169
|
+
|
|
170
|
+
const createInputElement = async () => {
|
|
171
|
+
const resource = await props.untitledResourceResolver.createUntitledResource(undefined, props.language);
|
|
172
|
+
const editor = await props.editorProvider.createInline(resource.uri, ref.current!, {
|
|
173
|
+
readOnly: true,
|
|
174
|
+
autoSizing: true,
|
|
175
|
+
scrollBeyondLastLine: false,
|
|
176
|
+
scrollBeyondLastColumn: 0,
|
|
177
|
+
renderFinalNewline: 'on',
|
|
178
|
+
maxHeight: -1,
|
|
179
|
+
scrollbar: { vertical: 'hidden', horizontal: 'hidden' },
|
|
180
|
+
codeLens: false,
|
|
181
|
+
inlayHints: { enabled: 'off' },
|
|
182
|
+
hover: { enabled: false }
|
|
183
|
+
});
|
|
184
|
+
editor.document.textEditorModel.setValue(props.content);
|
|
185
|
+
editor.getControl().onContextMenu(e => props.contextMenuCallback(e.event));
|
|
186
|
+
editorRef.current = editor;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
React.useEffect(() => {
|
|
190
|
+
createInputElement();
|
|
191
|
+
return () => {
|
|
192
|
+
if (editorRef.current) {
|
|
193
|
+
editorRef.current.dispose();
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}, []);
|
|
197
|
+
|
|
198
|
+
React.useEffect(() => {
|
|
199
|
+
if (editorRef.current) {
|
|
200
|
+
editorRef.current.document.textEditorModel.setValue(props.content);
|
|
201
|
+
}
|
|
202
|
+
}, [props.content]);
|
|
203
|
+
|
|
204
|
+
editorRef.current?.resizeToFit();
|
|
205
|
+
|
|
206
|
+
return <div ref={ref}></div>;
|
|
207
|
+
};
|
|
208
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
|
|
18
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
19
|
+
import { ChatResponseContent, CommandChatResponseContent } from '@theia/ai-chat/lib/common';
|
|
20
|
+
import { ReactNode } from '@theia/core/shared/react';
|
|
21
|
+
import * as React from '@theia/core/shared/react';
|
|
22
|
+
import { CommandRegistry, CommandService } from '@theia/core';
|
|
23
|
+
|
|
24
|
+
@injectable()
|
|
25
|
+
export class CommandPartRenderer implements ChatResponsePartRenderer<CommandChatResponseContent> {
|
|
26
|
+
@inject(CommandService) private commandService: CommandService;
|
|
27
|
+
@inject(CommandRegistry) private commandRegistry: CommandRegistry;
|
|
28
|
+
canHandle(response: ChatResponseContent): number {
|
|
29
|
+
if (CommandChatResponseContent.is(response)) {
|
|
30
|
+
return 10;
|
|
31
|
+
}
|
|
32
|
+
return -1;
|
|
33
|
+
}
|
|
34
|
+
render(response: CommandChatResponseContent): ReactNode {
|
|
35
|
+
const label =
|
|
36
|
+
response.customCallback?.label ??
|
|
37
|
+
response.command?.label ??
|
|
38
|
+
response.command?.id
|
|
39
|
+
.split('-')
|
|
40
|
+
.map(s => s[0].toUpperCase() + s.substring(1))
|
|
41
|
+
.join(' ') ?? 'Execute';
|
|
42
|
+
if (!response.customCallback && response.command) {
|
|
43
|
+
const isCommandEnabled = this.commandRegistry.isEnabled(response.command.id);
|
|
44
|
+
if (!isCommandEnabled) {
|
|
45
|
+
return <div>The command has the id "{response.command.id}" but it is not executable from the Chat window.</div>;
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return <button className='theia-button main' onClick={this.onCommand.bind(this, response)}>{label}</button>;
|
|
50
|
+
}
|
|
51
|
+
private onCommand(arg: CommandChatResponseContent): void {
|
|
52
|
+
if (arg.customCallback) {
|
|
53
|
+
arg.customCallback.callback().catch(e => { console.error(e); });
|
|
54
|
+
} else if (arg.command) {
|
|
55
|
+
this.commandService.executeCommand(arg.command.id, ...(arg.arguments ?? [])).catch(e => { console.error(e); });
|
|
56
|
+
} else {
|
|
57
|
+
console.warn('No command or custom callback provided in command chat response content');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
|
|
18
|
+
import { injectable } from '@theia/core/shared/inversify';
|
|
19
|
+
import { ChatResponseContent, ErrorChatResponseContent } from '@theia/ai-chat/lib/common';
|
|
20
|
+
import { ReactNode } from '@theia/core/shared/react';
|
|
21
|
+
import * as React from '@theia/core/shared/react';
|
|
22
|
+
|
|
23
|
+
@injectable()
|
|
24
|
+
export class ErrorPartRenderer implements ChatResponsePartRenderer<ErrorChatResponseContent> {
|
|
25
|
+
canHandle(response: ChatResponseContent): number {
|
|
26
|
+
if (ErrorChatResponseContent.is(response)) {
|
|
27
|
+
return 10;
|
|
28
|
+
}
|
|
29
|
+
return -1;
|
|
30
|
+
}
|
|
31
|
+
render(response: ErrorChatResponseContent): ReactNode {
|
|
32
|
+
return <div className='theia-ChatPart-Error'><span className='codicon codicon-error' /><span>{response.error.message}</span></div>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
|
|
18
|
+
import { inject, injectable, named } from '@theia/core/shared/inversify';
|
|
19
|
+
import {
|
|
20
|
+
ChatResponseContent,
|
|
21
|
+
HorizontalLayoutChatResponseContent,
|
|
22
|
+
} from '@theia/ai-chat/lib/common';
|
|
23
|
+
import { ReactNode } from '@theia/core/shared/react';
|
|
24
|
+
import * as React from '@theia/core/shared/react';
|
|
25
|
+
import { ContributionProvider } from '@theia/core';
|
|
26
|
+
import { ResponseNode } from '../chat-tree-view/chat-view-tree-widget';
|
|
27
|
+
|
|
28
|
+
@injectable()
|
|
29
|
+
export class HorizontalLayoutPartRenderer
|
|
30
|
+
implements ChatResponsePartRenderer<ChatResponseContent> {
|
|
31
|
+
@inject(ContributionProvider)
|
|
32
|
+
@named(ChatResponsePartRenderer)
|
|
33
|
+
protected readonly chatResponsePartRenderers: ContributionProvider<
|
|
34
|
+
ChatResponsePartRenderer<ChatResponseContent>
|
|
35
|
+
>;
|
|
36
|
+
|
|
37
|
+
canHandle(response: ChatResponseContent): number {
|
|
38
|
+
if (HorizontalLayoutChatResponseContent.is(response)) {
|
|
39
|
+
return 10;
|
|
40
|
+
}
|
|
41
|
+
return -1;
|
|
42
|
+
}
|
|
43
|
+
render(response: HorizontalLayoutChatResponseContent, parentNode: ResponseNode): ReactNode {
|
|
44
|
+
const contributions = this.chatResponsePartRenderers.getContributions();
|
|
45
|
+
return (
|
|
46
|
+
<div className="ai-chat-horizontal-layout" style={{ display: 'flex', flexDirection: 'row', flexWrap: 'wrap' }}>
|
|
47
|
+
{response.content.map(content => {
|
|
48
|
+
const renderer = contributions
|
|
49
|
+
.map(c => ({
|
|
50
|
+
prio: c.canHandle(content),
|
|
51
|
+
renderer: c,
|
|
52
|
+
}))
|
|
53
|
+
.sort((a, b) => b.prio - a.prio)[0].renderer;
|
|
54
|
+
return renderer.render(content, parentNode);
|
|
55
|
+
})}
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
export * from './ai-editor-manager';
|
|
17
|
+
export * from './code-part-renderer';
|
|
18
|
+
export * from './command-part-renderer';
|
|
19
|
+
export * from './error-part-renderer';
|
|
20
|
+
export * from './horizontal-layout-part-renderer';
|
|
21
|
+
export * from './markdown-part-renderer';
|
|
22
|
+
export * from './text-part-renderer';
|
|
23
|
+
export * from './toolcall-part-renderer';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
|
|
18
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
19
|
+
import {
|
|
20
|
+
ChatResponseContent,
|
|
21
|
+
InformationalChatResponseContent,
|
|
22
|
+
MarkdownChatResponseContent,
|
|
23
|
+
} from '@theia/ai-chat/lib/common';
|
|
24
|
+
import { ReactNode, useEffect, useRef } from '@theia/core/shared/react';
|
|
25
|
+
import * as React from '@theia/core/shared/react';
|
|
26
|
+
import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
|
|
27
|
+
import { MarkdownRenderer } from '@theia/core/lib/browser/markdown-rendering/markdown-renderer';
|
|
28
|
+
|
|
29
|
+
@injectable()
|
|
30
|
+
export class MarkdownPartRenderer implements ChatResponsePartRenderer<MarkdownChatResponseContent | InformationalChatResponseContent> {
|
|
31
|
+
@inject(MarkdownRenderer) private renderer: MarkdownRenderer;
|
|
32
|
+
canHandle(response: ChatResponseContent): number {
|
|
33
|
+
if (MarkdownChatResponseContent.is(response)) {
|
|
34
|
+
return 10;
|
|
35
|
+
}
|
|
36
|
+
if (InformationalChatResponseContent.is(response)) {
|
|
37
|
+
return 10;
|
|
38
|
+
}
|
|
39
|
+
return -1;
|
|
40
|
+
}
|
|
41
|
+
private renderMarkdown(md: MarkdownString): HTMLElement {
|
|
42
|
+
return this.renderer.render(md).element;
|
|
43
|
+
}
|
|
44
|
+
render(response: MarkdownChatResponseContent | InformationalChatResponseContent): ReactNode {
|
|
45
|
+
// TODO let the user configure whether they want to see informational content
|
|
46
|
+
if (InformationalChatResponseContent.is(response)) {
|
|
47
|
+
// null is valid in React
|
|
48
|
+
// eslint-disable-next-line no-null/no-null
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return <MarkdownWrapper data={response.content} renderCallback={this.renderMarkdown.bind(this)}></MarkdownWrapper>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const MarkdownWrapper = (props: { data: MarkdownString, renderCallback: (md: MarkdownString) => HTMLElement }) => {
|
|
57
|
+
// eslint-disable-next-line no-null/no-null
|
|
58
|
+
const ref: React.MutableRefObject<HTMLDivElement | null> = useRef(null);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
const myDomElement = props.renderCallback(props.data);
|
|
62
|
+
|
|
63
|
+
while (ref?.current?.firstChild) {
|
|
64
|
+
ref.current.removeChild(ref.current.firstChild);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
ref?.current?.appendChild(myDomElement);
|
|
68
|
+
}, [props.data.value]);
|
|
69
|
+
|
|
70
|
+
return <div ref={ref}></div>;
|
|
71
|
+
};
|