@theia/ai-chat-ui 1.58.3 → 1.59.0-next.72
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/lib/browser/ai-chat-ui-contribution.d.ts.map +1 -1
- package/lib/browser/ai-chat-ui-contribution.js +7 -7
- package/lib/browser/ai-chat-ui-contribution.js.map +1 -1
- package/lib/browser/ai-chat-ui-frontend-module.d.ts +1 -1
- package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-chat-ui-frontend-module.js +17 -13
- package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -1
- package/lib/browser/change-set-actions/change-set-accept-action.d.ts +10 -0
- package/lib/browser/change-set-actions/change-set-accept-action.d.ts.map +1 -0
- package/lib/browser/change-set-actions/change-set-accept-action.js +47 -0
- package/lib/browser/change-set-actions/change-set-accept-action.js.map +1 -0
- package/lib/browser/change-set-actions/change-set-action-service.d.ts +31 -0
- package/lib/browser/change-set-actions/change-set-action-service.d.ts.map +1 -0
- package/lib/browser/change-set-actions/change-set-action-service.js +57 -0
- package/lib/browser/change-set-actions/change-set-action-service.js.map +1 -0
- package/lib/browser/chat-input-widget.d.ts +22 -3
- package/lib/browser/chat-input-widget.d.ts.map +1 -1
- package/lib/browser/chat-input-widget.js +209 -56
- package/lib/browser/chat-input-widget.js.map +1 -1
- package/lib/browser/chat-response-renderer/ai-selection-resolver.d.ts +23 -0
- package/lib/browser/chat-response-renderer/ai-selection-resolver.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/{ai-editor-manager.js → ai-selection-resolver.js} +2 -38
- package/lib/browser/chat-response-renderer/ai-selection-resolver.js.map +1 -0
- package/lib/browser/chat-response-renderer/code-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/code-part-renderer.js +4 -3
- package/lib/browser/chat-response-renderer/code-part-renderer.js.map +1 -1
- package/lib/browser/chat-response-renderer/index.d.ts +1 -1
- package/lib/browser/chat-response-renderer/index.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/index.js +1 -1
- package/lib/browser/chat-response-renderer/index.js.map +1 -1
- package/lib/browser/chat-response-renderer/text-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/text-part-renderer.js +3 -1
- package/lib/browser/chat-response-renderer/text-part-renderer.js.map +1 -1
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.js +7 -3
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -1
- package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts.map +1 -1
- package/lib/browser/chat-tree-view/chat-view-tree-widget.js +25 -16
- package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -1
- package/lib/browser/chat-view-contribution.d.ts +2 -2
- package/lib/browser/chat-view-contribution.d.ts.map +1 -1
- package/lib/browser/chat-view-contribution.js +6 -6
- package/lib/browser/chat-view-contribution.js.map +1 -1
- package/lib/browser/chat-view-language-contribution.d.ts +10 -5
- package/lib/browser/chat-view-language-contribution.d.ts.map +1 -1
- package/lib/browser/chat-view-language-contribution.js +94 -14
- package/lib/browser/chat-view-language-contribution.js.map +1 -1
- package/lib/browser/chat-view-widget.d.ts +3 -0
- package/lib/browser/chat-view-widget.d.ts.map +1 -1
- package/lib/browser/chat-view-widget.js +15 -5
- package/lib/browser/chat-view-widget.js.map +1 -1
- package/lib/browser/context-variable-picker.d.ts +9 -0
- package/lib/browser/context-variable-picker.d.ts.map +1 -0
- package/lib/browser/context-variable-picker.js +86 -0
- package/lib/browser/context-variable-picker.js.map +1 -0
- package/package.json +11 -11
- package/src/browser/ai-chat-ui-contribution.ts +8 -8
- package/src/browser/ai-chat-ui-frontend-module.ts +18 -16
- package/src/browser/change-set-actions/change-set-accept-action.tsx +52 -0
- package/src/browser/change-set-actions/change-set-action-service.ts +65 -0
- package/src/browser/chat-input-widget.tsx +307 -75
- package/src/browser/chat-response-renderer/{ai-editor-manager.ts → ai-selection-resolver.ts} +6 -45
- package/src/browser/chat-response-renderer/code-part-renderer.tsx +4 -3
- package/src/browser/chat-response-renderer/index.ts +1 -1
- package/src/browser/chat-response-renderer/text-part-renderer.tsx +4 -1
- package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +9 -3
- package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +26 -16
- package/src/browser/chat-view-contribution.ts +6 -6
- package/src/browser/chat-view-language-contribution.ts +103 -19
- package/src/browser/chat-view-widget.tsx +19 -6
- package/src/browser/context-variable-picker.ts +85 -0
- package/src/browser/style/index.css +110 -12
- package/lib/browser/chat-response-renderer/ai-editor-manager.d.ts +0 -36
- package/lib/browser/chat-response-renderer/ai-editor-manager.d.ts.map +0 -1
- package/lib/browser/chat-response-renderer/ai-editor-manager.js.map +0 -1
package/src/browser/chat-response-renderer/{ai-editor-manager.ts → ai-selection-resolver.ts}
RENAMED
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
|
-
import { CancellationToken,
|
|
18
|
-
import { inject, injectable
|
|
17
|
+
import { CancellationToken, RecursivePartial, URI } from '@theia/core';
|
|
18
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
19
19
|
import { EditorOpenerOptions, EditorWidget, Range } from '@theia/editor/lib/browser';
|
|
20
20
|
|
|
21
|
-
import {
|
|
21
|
+
import { EditorSelectionResolver } from '@theia/editor/lib/browser/editor-manager';
|
|
22
22
|
import { DocumentSymbol } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
|
|
23
23
|
import { TextModel } from '@theia/monaco-editor-core/esm/vs/editor/common/model/textModel';
|
|
24
24
|
import { ILanguageFeaturesService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/languageFeatures';
|
|
@@ -29,17 +29,8 @@ import { MonacoToProtocolConverter } from '@theia/monaco/lib/browser/monaco-to-p
|
|
|
29
29
|
/** Regex to match GitHub-style position and range declaration with line (L) and column (C) */
|
|
30
30
|
export const LOCATION_REGEX = /#L(\d+)?(?:C(\d+))?(?:-L(\d+)?(?:C(\d+))?)?$/;
|
|
31
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
32
|
@injectable()
|
|
42
|
-
export class GitHubSelectionResolver implements
|
|
33
|
+
export class GitHubSelectionResolver implements EditorSelectionResolver {
|
|
43
34
|
priority = 100;
|
|
44
35
|
|
|
45
36
|
async resolveSelection(widget: EditorWidget, options: EditorOpenerOptions, uri?: URI): Promise<RecursivePartial<Range> | undefined> {
|
|
@@ -67,7 +58,7 @@ export class GitHubSelectionResolver implements AIEditorSelectionResolver {
|
|
|
67
58
|
}
|
|
68
59
|
|
|
69
60
|
@injectable()
|
|
70
|
-
export class TypeDocSymbolSelectionResolver implements
|
|
61
|
+
export class TypeDocSymbolSelectionResolver implements EditorSelectionResolver {
|
|
71
62
|
priority = 50;
|
|
72
63
|
|
|
73
64
|
@inject(MonacoToProtocolConverter) protected readonly m2p: MonacoToProtocolConverter;
|
|
@@ -123,7 +114,7 @@ export class TypeDocSymbolSelectionResolver implements AIEditorSelectionResolver
|
|
|
123
114
|
}
|
|
124
115
|
|
|
125
116
|
@injectable()
|
|
126
|
-
export class TextFragmentSelectionResolver implements
|
|
117
|
+
export class TextFragmentSelectionResolver implements EditorSelectionResolver {
|
|
127
118
|
async resolveSelection(widget: EditorWidget, options: EditorOpenerOptions, uri?: URI): Promise<RecursivePartial<Range> | undefined> {
|
|
128
119
|
if (!uri) {
|
|
129
120
|
return;
|
|
@@ -151,33 +142,3 @@ export class TextFragmentSelectionResolver implements AIEditorSelectionResolver
|
|
|
151
142
|
return uri.fragment;
|
|
152
143
|
}
|
|
153
144
|
}
|
|
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
|
-
}
|
|
@@ -23,6 +23,7 @@ import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
|
|
23
23
|
import { inject, injectable, named } from '@theia/core/shared/inversify';
|
|
24
24
|
import * as React from '@theia/core/shared/react';
|
|
25
25
|
import { ReactNode } from '@theia/core/shared/react';
|
|
26
|
+
import { nls } from '@theia/core/lib/common/nls';
|
|
26
27
|
import { Position } from '@theia/core/shared/vscode-languageserver-protocol';
|
|
27
28
|
import { EditorManager, EditorWidget } from '@theia/editor/lib/browser';
|
|
28
29
|
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
|
@@ -113,7 +114,7 @@ export class CodePartRenderer
|
|
|
113
114
|
private getTitle(uri: URI | undefined, language: string | undefined): string {
|
|
114
115
|
// If there is a URI, use the file name as the title. Otherwise, use the language as the title.
|
|
115
116
|
// If there is no language, use a generic fallback title.
|
|
116
|
-
return uri?.path?.toString().split('/').pop() ?? language ?? 'Generated Code';
|
|
117
|
+
return uri?.path?.toString().split('/').pop() ?? language ?? nls.localize('theia/ai/chat-ui/code-part-renderer/generatedCode', 'Generated Code');
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
/**
|
|
@@ -157,7 +158,7 @@ const CopyToClipboardButton = (props: { code: string, clipboardService: Clipboar
|
|
|
157
158
|
const copyCodeToClipboard = React.useCallback(() => {
|
|
158
159
|
clipboardService.writeText(code);
|
|
159
160
|
}, [code, clipboardService]);
|
|
160
|
-
return <div className='button codicon codicon-copy' title='Copy' role='button' onClick={copyCodeToClipboard}></div>;
|
|
161
|
+
return <div className='button codicon codicon-copy' title={nls.localizeByDefault('Copy')} role='button' onClick={copyCodeToClipboard}></div>;
|
|
161
162
|
};
|
|
162
163
|
|
|
163
164
|
@injectable()
|
|
@@ -189,7 +190,7 @@ const InsertCodeAtCursorButton = (props: { code: string, editorManager: EditorMa
|
|
|
189
190
|
}]);
|
|
190
191
|
}
|
|
191
192
|
}, [code, editorManager]);
|
|
192
|
-
return <div className='button codicon codicon-insert' title='Insert at Cursor' role='button' onClick={insertCode}></div>;
|
|
193
|
+
return <div className='button codicon codicon-insert' title={nls.localizeByDefault('Insert at Cursor')} role='button' onClick={insertCode}></div>;
|
|
193
194
|
};
|
|
194
195
|
|
|
195
196
|
/**
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
//
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
|
-
export * from './ai-
|
|
16
|
+
export * from './ai-selection-resolver';
|
|
17
17
|
export * from './code-part-renderer';
|
|
18
18
|
export * from './command-part-renderer';
|
|
19
19
|
export * from './error-part-renderer';
|
|
@@ -18,6 +18,7 @@ import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
|
|
|
18
18
|
import { injectable } from '@theia/core/shared/inversify';
|
|
19
19
|
import { ChatResponseContent } from '@theia/ai-chat/lib/common';
|
|
20
20
|
import { ReactNode } from '@theia/core/shared/react';
|
|
21
|
+
import { nls } from '@theia/core/lib/common/nls';
|
|
21
22
|
import * as React from '@theia/core/shared/react';
|
|
22
23
|
|
|
23
24
|
@injectable()
|
|
@@ -30,6 +31,8 @@ export class TextPartRenderer implements ChatResponsePartRenderer<ChatResponseCo
|
|
|
30
31
|
if (response && ChatResponseContent.hasAsString(response)) {
|
|
31
32
|
return <span>{response.asString()}</span>;
|
|
32
33
|
}
|
|
33
|
-
return <span>
|
|
34
|
+
return <span>
|
|
35
|
+
{nls.localize('theia/ai/chat-ui/text-part-renderer/cantDisplay',
|
|
36
|
+
"Can't display response, please check your ChatResponsePartRenderers!")} {JSON.stringify(response)}</span>;
|
|
34
37
|
}
|
|
35
38
|
}
|
|
@@ -18,6 +18,7 @@ import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
|
|
|
18
18
|
import { injectable } from '@theia/core/shared/inversify';
|
|
19
19
|
import { ChatResponseContent, ToolCallChatResponseContent } from '@theia/ai-chat/lib/common';
|
|
20
20
|
import { ReactNode } from '@theia/core/shared/react';
|
|
21
|
+
import { nls } from '@theia/core/lib/common/nls';
|
|
21
22
|
import * as React from '@theia/core/shared/react';
|
|
22
23
|
|
|
23
24
|
@injectable()
|
|
@@ -35,14 +36,14 @@ export class ToolCallPartRenderer implements ChatResponsePartRenderer<ToolCallCh
|
|
|
35
36
|
<h4 className='theia-toolCall'>
|
|
36
37
|
{response.finished ? (
|
|
37
38
|
<details>
|
|
38
|
-
<summary>Ran {response.name}
|
|
39
|
+
<summary>{nls.localize('theia/ai/chat-ui/toolcall-part-renderer/finished', 'Ran')} {response.name}
|
|
39
40
|
({this.renderCollapsibleArguments(response.arguments)})
|
|
40
41
|
</summary>
|
|
41
42
|
<pre>{this.tryPrettyPrintJson(response)}</pre>
|
|
42
43
|
</details>
|
|
43
44
|
) : (
|
|
44
45
|
<span>
|
|
45
|
-
<Spinner /> Running {response.name}({this.renderCollapsibleArguments(response.arguments)})
|
|
46
|
+
<Spinner /> {nls.localizeByDefault('Running')} {response.name}({this.renderCollapsibleArguments(response.arguments)})
|
|
46
47
|
</span>
|
|
47
48
|
)}
|
|
48
49
|
</h4>
|
|
@@ -82,7 +83,12 @@ export class ToolCallPartRenderer implements ChatResponsePartRenderer<ToolCallCh
|
|
|
82
83
|
}
|
|
83
84
|
} catch (e) {
|
|
84
85
|
if (typeof responseContent !== 'string') {
|
|
85
|
-
responseContent =
|
|
86
|
+
responseContent = nls.localize(
|
|
87
|
+
'theia/ai/chat-ui/toolcall-part-renderer/prettyPrintError',
|
|
88
|
+
"The content could not be converted to string: '{0}'. This is the original content: '{1}'.",
|
|
89
|
+
e.message,
|
|
90
|
+
responseContent
|
|
91
|
+
);
|
|
86
92
|
}
|
|
87
93
|
// fall through
|
|
88
94
|
}
|
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
postConstruct
|
|
48
48
|
} from '@theia/core/shared/inversify';
|
|
49
49
|
import * as React from '@theia/core/shared/react';
|
|
50
|
+
import { nls } from '@theia/core/lib/common/nls';
|
|
50
51
|
|
|
51
52
|
import { ChatNodeToolbarActionContribution } from '../chat-node-toolbar-action-contribution';
|
|
52
53
|
import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
|
|
@@ -151,43 +152,51 @@ export class ChatViewTreeWidget extends TreeWidget {
|
|
|
151
152
|
return <div className={'theia-ResponseNode'}>
|
|
152
153
|
<div className='theia-ResponseNode-Content' key={'disabled-message'}>
|
|
153
154
|
<div className="disable-message">
|
|
154
|
-
<span className="section-header">
|
|
155
|
+
<span className="section-header">{
|
|
156
|
+
nls.localize('theia/ai/chat-ui/chat-view-tree-widget/aiFeatureHeader', '🚀 AI Features Available (Alpha Version)!')}
|
|
157
|
+
</span>
|
|
155
158
|
<div className="section-title">
|
|
156
|
-
<p><code>Currently, all AI Features are disabled
|
|
159
|
+
<p><code>{nls.localize('theia/ai/chat-ui/chat-view-tree-widget/featuresDisabled', 'Currently, all AI Features are disabled!')}</code></p>
|
|
157
160
|
</div>
|
|
158
161
|
<div className="section-title">
|
|
159
|
-
<p>How to Enable
|
|
162
|
+
<p>{nls.localize('theia/ai/chat-ui/chat-view-tree-widget/howToEnable', 'How to Enable the AI Features:')}</p>
|
|
160
163
|
</div>
|
|
161
164
|
<div className="section-content">
|
|
162
|
-
<p>To enable the
|
|
163
|
-
{this.renderLinkButton('the settings menu', CommonCommands.OPEN_PREFERENCES.id)}
|
|
165
|
+
<p>To enable the AI features, please go to
|
|
166
|
+
{this.renderLinkButton(nls.localize('theia/ai/chat-ui/chat-view-tree-widget/settingsMenu', 'the settings menu'), CommonCommands.OPEN_PREFERENCES.id)}
|
|
164
167
|
and locate the <strong>AI Features</strong> section.</p>
|
|
165
168
|
<ol>
|
|
166
|
-
<li>Toggle the switch for <strong>'Ai-features: Enable'</strong>.</li>
|
|
169
|
+
<li>Toggle the switch for <strong>{nls.localize('theia/ai/chat-ui/chat-view-tree-widget/aiFeaturesEnable', 'Ai-features: Enable')}</strong>.</li>
|
|
167
170
|
<li>Provide at least one LLM provider (e.g. OpenAI), also see <a href="https://theia-ide.org/docs/user_ai/" target="_blank">the documentation</a>
|
|
168
171
|
for more information.</li>
|
|
169
172
|
</ol>
|
|
170
|
-
<p>This will activate the
|
|
173
|
+
<p>This will activate the AI capabilities in the app. Please remember, these features are <strong>in an alpha state</strong>,
|
|
174
|
+
so they may change and we are working on improving them 🚧.<br></br>
|
|
175
|
+
Please support us by <a href="https://github.com/eclipse-theia/theia">providing feedback
|
|
176
|
+
</a>!</p>
|
|
171
177
|
</div>
|
|
172
178
|
|
|
173
179
|
<div className="section-title">
|
|
174
180
|
<p>Currently Supported Views and Features:</p>
|
|
175
181
|
</div>
|
|
176
182
|
<div className="section-content">
|
|
177
|
-
<p>Once the
|
|
183
|
+
<p>Once the AI features are enabled, you can access the following views and features:</p>
|
|
178
184
|
<ul>
|
|
179
185
|
<li>Code Completion</li>
|
|
180
186
|
<li>Terminal Assistance (via CTRL+I in a terminal)</li>
|
|
181
187
|
<li>This Chat View (features the following agents):
|
|
182
188
|
<ul>
|
|
183
189
|
<li>Universal Chat Agent</li>
|
|
184
|
-
<li>
|
|
190
|
+
<li>Coder Chat Agent</li>
|
|
191
|
+
<li>Architect Chat Agent</li>
|
|
185
192
|
<li>Command Chat Agent</li>
|
|
186
193
|
<li>Orchestrator Chat Agent</li>
|
|
187
194
|
</ul>
|
|
188
195
|
</li>
|
|
189
|
-
<li>{this.renderLinkButton('AI History View', 'aiHistory:open')}</li>
|
|
190
|
-
<li>{this.renderLinkButton(
|
|
196
|
+
<li>{this.renderLinkButton(nls.localize('theia/ai/chat-ui/chat-view-tree-widget/aiHistoryView', 'AI History View'), 'aiHistory:open')}</li>
|
|
197
|
+
<li>{this.renderLinkButton(
|
|
198
|
+
nls.localize('theia/ai/chat-ui/chat-view-tree-widget/aiConfigurationView', 'AI Configuration View'), 'aiConfiguration:open')}
|
|
199
|
+
</li>
|
|
191
200
|
</ul>
|
|
192
201
|
<p>See <a href="https://theia-ide.org/docs/user_ai/" target="_blank">the documentation</a> for more information.</p>
|
|
193
202
|
</div>
|
|
@@ -307,8 +316,9 @@ export class ChatViewTreeWidget extends TreeWidget {
|
|
|
307
316
|
}}>
|
|
308
317
|
{this.getAgentLabel(node)}
|
|
309
318
|
</h3>
|
|
310
|
-
{inProgress && !waitingForInput && <span className='theia-ChatContentInProgress'>Generating</span>}
|
|
311
|
-
{inProgress && waitingForInput && <span className='theia-ChatContentInProgress'>
|
|
319
|
+
{inProgress && !waitingForInput && <span className='theia-ChatContentInProgress'>{nls.localizeByDefault('Generating')}</span>}
|
|
320
|
+
{inProgress && waitingForInput && <span className='theia-ChatContentInProgress'>{
|
|
321
|
+
nls.localize('theia/ai/chat-ui/chat-view-tree-widget/waitingForInput', 'Waiting for input')}</span>}
|
|
312
322
|
<div className='theia-ChatNodeToolbar'>
|
|
313
323
|
{!inProgress &&
|
|
314
324
|
toolbarContributions.length > 0 &&
|
|
@@ -338,9 +348,9 @@ export class ChatViewTreeWidget extends TreeWidget {
|
|
|
338
348
|
private getAgentLabel(node: RequestNode | ResponseNode): string {
|
|
339
349
|
if (isRequestNode(node)) {
|
|
340
350
|
// TODO find user name
|
|
341
|
-
return 'You';
|
|
351
|
+
return nls.localize('theia/ai/chat-ui/chat-view-tree-widget/you', 'You');
|
|
342
352
|
}
|
|
343
|
-
return this.getAgent(node)?.name ?? 'AI';
|
|
353
|
+
return this.getAgent(node)?.name ?? nls.localize('theia/ai/chat-ui/chat-view-tree-widget/ai', 'AI');
|
|
344
354
|
}
|
|
345
355
|
|
|
346
356
|
private getAgent(node: RequestNode | ResponseNode): ChatAgent | undefined {
|
|
@@ -420,7 +430,7 @@ export class ChatViewTreeWidget extends TreeWidget {
|
|
|
420
430
|
[-1, undefined])[1];
|
|
421
431
|
if (!renderer) {
|
|
422
432
|
console.error('No renderer found for content', content);
|
|
423
|
-
return <div>Error: No renderer found</div>;
|
|
433
|
+
return <div>{nls.localize('theia/ai/chat-ui/chat-view-tree-widget/noRenderer', 'Error: No renderer found')}</div>;
|
|
424
434
|
}
|
|
425
435
|
return renderer.render(content, node);
|
|
426
436
|
}
|
|
@@ -69,7 +69,7 @@ export class ChatViewMenuContribution implements MenuContribution, CommandContri
|
|
|
69
69
|
const parent = extractRequestOrResponseNodes(args).find(arg => arg.parent)?.parent;
|
|
70
70
|
const text = parent?.children
|
|
71
71
|
.filter(isRequestOrResponseNode)
|
|
72
|
-
.map(child => this.
|
|
72
|
+
.map(child => this.getCopyText(child))
|
|
73
73
|
.join('\n\n---\n\n');
|
|
74
74
|
if (text) {
|
|
75
75
|
this.clipboardService.writeText(text);
|
|
@@ -93,19 +93,19 @@ export class ChatViewMenuContribution implements MenuContribution, CommandContri
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
protected copyMessage(args: (RequestNode | ResponseNode)[]): void {
|
|
96
|
-
const text = this.
|
|
96
|
+
const text = this.getCopyTextAndJoin(args);
|
|
97
97
|
this.clipboardService.writeText(text);
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
protected
|
|
101
|
-
return args !== undefined ? args.map(arg => this.
|
|
100
|
+
protected getCopyTextAndJoin(args: (RequestNode | ResponseNode)[] | undefined): string {
|
|
101
|
+
return args !== undefined ? args.map(arg => this.getCopyText(arg)).join() : '';
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
protected
|
|
104
|
+
protected getCopyText(arg: RequestNode | ResponseNode): string {
|
|
105
105
|
if (isRequestNode(arg)) {
|
|
106
106
|
return arg.request.request.text;
|
|
107
107
|
} else if (isResponseNode(arg)) {
|
|
108
|
-
return arg.response.response.
|
|
108
|
+
return arg.response.response.asDisplayString();
|
|
109
109
|
}
|
|
110
110
|
return '';
|
|
111
111
|
}
|
|
@@ -13,18 +13,24 @@
|
|
|
13
13
|
//
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { ChatAgentService } from '@theia/ai-chat';
|
|
17
|
+
import { AIContextVariable, AIVariableService } from '@theia/ai-core/lib/common';
|
|
18
|
+
import { PromptText } from '@theia/ai-core/lib/common/prompt-text';
|
|
19
|
+
import { ToolInvocationRegistry } from '@theia/ai-core/lib/common/tool-invocation-registry';
|
|
20
|
+
import { MaybePromise, nls } from '@theia/core';
|
|
21
|
+
import { ApplicationShell, FrontendApplication, FrontendApplicationContribution } from '@theia/core/lib/browser';
|
|
22
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
18
23
|
import * as monaco from '@theia/monaco-editor-core';
|
|
19
|
-
import { ContributionProvider, MaybePromise } from '@theia/core';
|
|
20
24
|
import { ProviderResult } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
|
|
21
|
-
import {
|
|
22
|
-
import { AIVariableService } from '@theia/ai-core/lib/common';
|
|
23
|
-
import { ToolProvider } from '@theia/ai-core/lib/common/tool-invocation-registry';
|
|
25
|
+
import { ChatViewWidget } from './chat-view-widget';
|
|
24
26
|
|
|
25
27
|
export const CHAT_VIEW_LANGUAGE_ID = 'theia-ai-chat-view-language';
|
|
26
28
|
export const CHAT_VIEW_LANGUAGE_EXTENSION = 'aichatviewlanguage';
|
|
27
29
|
|
|
30
|
+
const VARIABLE_RESOLUTION_CONTEXT = { context: 'chat-input-autocomplete' };
|
|
31
|
+
const VARIABLE_ARGUMENT_PICKER_COMMAND = 'trigger-variable-argument-picker';
|
|
32
|
+
const VARIABLE_ADD_CONTEXT_COMMAND = 'add-context-variable';
|
|
33
|
+
|
|
28
34
|
@injectable()
|
|
29
35
|
export class ChatViewLanguageContribution implements FrontendApplicationContribution {
|
|
30
36
|
|
|
@@ -34,25 +40,34 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
|
|
|
34
40
|
@inject(AIVariableService)
|
|
35
41
|
protected readonly variableService: AIVariableService;
|
|
36
42
|
|
|
37
|
-
@inject(
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
@inject(ToolInvocationRegistry)
|
|
44
|
+
protected readonly toolInvocationRegistry: ToolInvocationRegistry;
|
|
45
|
+
|
|
46
|
+
@inject(ApplicationShell)
|
|
47
|
+
protected readonly shell: ApplicationShell;
|
|
40
48
|
|
|
41
49
|
onStart(_app: FrontendApplication): MaybePromise<void> {
|
|
42
50
|
monaco.languages.register({ id: CHAT_VIEW_LANGUAGE_ID, extensions: [CHAT_VIEW_LANGUAGE_EXTENSION] });
|
|
43
51
|
|
|
44
52
|
monaco.languages.registerCompletionItemProvider(CHAT_VIEW_LANGUAGE_ID, {
|
|
45
|
-
triggerCharacters: [
|
|
53
|
+
triggerCharacters: [PromptText.AGENT_CHAR],
|
|
46
54
|
provideCompletionItems: (model, position, _context, _token): ProviderResult<monaco.languages.CompletionList> => this.provideAgentCompletions(model, position),
|
|
47
55
|
});
|
|
48
56
|
monaco.languages.registerCompletionItemProvider(CHAT_VIEW_LANGUAGE_ID, {
|
|
49
|
-
triggerCharacters: [
|
|
57
|
+
triggerCharacters: [PromptText.VARIABLE_CHAR],
|
|
50
58
|
provideCompletionItems: (model, position, _context, _token): ProviderResult<monaco.languages.CompletionList> => this.provideVariableCompletions(model, position),
|
|
51
59
|
});
|
|
52
60
|
monaco.languages.registerCompletionItemProvider(CHAT_VIEW_LANGUAGE_ID, {
|
|
53
|
-
triggerCharacters: [
|
|
61
|
+
triggerCharacters: [PromptText.VARIABLE_CHAR, PromptText.VARIABLE_SEPARATOR_CHAR],
|
|
62
|
+
provideCompletionItems: (model, position, _context, _token): ProviderResult<monaco.languages.CompletionList> => this.provideVariableWithArgCompletions(model, position),
|
|
63
|
+
});
|
|
64
|
+
monaco.languages.registerCompletionItemProvider(CHAT_VIEW_LANGUAGE_ID, {
|
|
65
|
+
triggerCharacters: [PromptText.FUNCTION_CHAR],
|
|
54
66
|
provideCompletionItems: (model, position, _context, _token): ProviderResult<monaco.languages.CompletionList> => this.provideToolCompletions(model, position),
|
|
55
67
|
});
|
|
68
|
+
|
|
69
|
+
monaco.editor.registerCommand(VARIABLE_ARGUMENT_PICKER_COMMAND, this.triggerVariableArgumentPicker.bind(this));
|
|
70
|
+
monaco.editor.registerCommand(VARIABLE_ADD_CONTEXT_COMMAND, (_, ...args) => args.length > 1 ? this.addContextVariable(args[0], args[1]) : undefined);
|
|
56
71
|
}
|
|
57
72
|
|
|
58
73
|
getCompletionRange(model: monaco.editor.ITextModel, position: monaco.Position, triggerCharacter: string): monaco.Range | undefined {
|
|
@@ -65,7 +80,7 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
|
|
|
65
80
|
return undefined;
|
|
66
81
|
}
|
|
67
82
|
|
|
68
|
-
// Calculate the range from the position of the
|
|
83
|
+
// Calculate the range from the position of the trigger character
|
|
69
84
|
const wordInfo = model.getWordUntilPosition(position);
|
|
70
85
|
return new monaco.Range(
|
|
71
86
|
position.lineNumber,
|
|
@@ -83,7 +98,8 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
|
|
|
83
98
|
kind: monaco.languages.CompletionItemKind,
|
|
84
99
|
getId: (item: T) => string,
|
|
85
100
|
getName: (item: T) => string,
|
|
86
|
-
getDescription: (item: T) => string
|
|
101
|
+
getDescription: (item: T) => string,
|
|
102
|
+
command?: monaco.languages.Command
|
|
87
103
|
): ProviderResult<monaco.languages.CompletionList> {
|
|
88
104
|
const completionRange = this.getCompletionRange(model, position, triggerChar);
|
|
89
105
|
if (completionRange === undefined) {
|
|
@@ -95,6 +111,7 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
|
|
|
95
111
|
label: getName(item),
|
|
96
112
|
range: completionRange,
|
|
97
113
|
detail: getDescription(item),
|
|
114
|
+
command
|
|
98
115
|
}));
|
|
99
116
|
return { suggestions };
|
|
100
117
|
}
|
|
@@ -103,7 +120,7 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
|
|
|
103
120
|
return this.getSuggestions(
|
|
104
121
|
model,
|
|
105
122
|
position,
|
|
106
|
-
|
|
123
|
+
PromptText.AGENT_CHAR,
|
|
107
124
|
this.agentService.getAgents(),
|
|
108
125
|
monaco.languages.CompletionItemKind.Value,
|
|
109
126
|
agent => agent.id,
|
|
@@ -116,25 +133,92 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
|
|
|
116
133
|
return this.getSuggestions(
|
|
117
134
|
model,
|
|
118
135
|
position,
|
|
119
|
-
|
|
136
|
+
PromptText.VARIABLE_CHAR,
|
|
120
137
|
this.variableService.getVariables(),
|
|
121
138
|
monaco.languages.CompletionItemKind.Variable,
|
|
122
139
|
variable => variable.name,
|
|
123
140
|
variable => variable.name,
|
|
124
|
-
variable => variable.description
|
|
141
|
+
variable => variable.description,
|
|
142
|
+
{
|
|
143
|
+
title: nls.localize('theia/ai/chat-ui/selectVariableArguments', 'Select variable arguments'),
|
|
144
|
+
id: VARIABLE_ARGUMENT_PICKER_COMMAND,
|
|
145
|
+
}
|
|
125
146
|
);
|
|
126
147
|
}
|
|
127
148
|
|
|
149
|
+
async provideVariableWithArgCompletions(model: monaco.editor.ITextModel, position: monaco.Position): Promise<monaco.languages.CompletionList> {
|
|
150
|
+
const variables = this.variableService.getVariables();
|
|
151
|
+
const suggestions: monaco.languages.CompletionItem[] = [];
|
|
152
|
+
for (const variable of variables) {
|
|
153
|
+
const provider = await this.variableService.getArgumentCompletionProvider(variable.name);
|
|
154
|
+
if (provider) {
|
|
155
|
+
const items = await provider(model, position);
|
|
156
|
+
if (items) {
|
|
157
|
+
suggestions.push(...items.map(item => ({
|
|
158
|
+
...item,
|
|
159
|
+
// trigger command to check if we should add a context variable
|
|
160
|
+
command: {
|
|
161
|
+
title: nls.localize('theia/ai/chat-ui/addContextVariable', 'Add context variable'),
|
|
162
|
+
id: VARIABLE_ADD_CONTEXT_COMMAND,
|
|
163
|
+
arguments: [variable.name, item.insertText]
|
|
164
|
+
}
|
|
165
|
+
})));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return { suggestions };
|
|
170
|
+
}
|
|
171
|
+
|
|
128
172
|
provideToolCompletions(model: monaco.editor.ITextModel, position: monaco.Position): ProviderResult<monaco.languages.CompletionList> {
|
|
129
173
|
return this.getSuggestions(
|
|
130
174
|
model,
|
|
131
175
|
position,
|
|
132
|
-
|
|
133
|
-
this.
|
|
176
|
+
PromptText.FUNCTION_CHAR,
|
|
177
|
+
this.toolInvocationRegistry.getAllFunctions(),
|
|
134
178
|
monaco.languages.CompletionItemKind.Function,
|
|
135
179
|
tool => tool.id,
|
|
136
180
|
tool => tool.name,
|
|
137
181
|
tool => tool.description ?? ''
|
|
138
182
|
);
|
|
139
183
|
}
|
|
184
|
+
|
|
185
|
+
protected async triggerVariableArgumentPicker(): Promise<void> {
|
|
186
|
+
const inputEditor = monaco.editor.getEditors().find(editor => editor.hasTextFocus());
|
|
187
|
+
if (!inputEditor) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const model = inputEditor.getModel();
|
|
191
|
+
const position = inputEditor.getPosition();
|
|
192
|
+
if (!model || !position) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const variableName = model.getWordAtPosition(position)?.word;
|
|
196
|
+
if (!variableName) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const provider = await this.variableService.getArgumentPicker(variableName, VARIABLE_RESOLUTION_CONTEXT);
|
|
200
|
+
if (!provider) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const arg = await provider(VARIABLE_RESOLUTION_CONTEXT);
|
|
204
|
+
if (!arg) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
inputEditor.executeEdits('variable-argument-picker', [{
|
|
208
|
+
range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column),
|
|
209
|
+
text: PromptText.VARIABLE_SEPARATOR_CHAR + arg
|
|
210
|
+
}]);
|
|
211
|
+
await this.addContextVariable(variableName, arg);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
protected async addContextVariable(variableName: string, arg: string | undefined): Promise<void> {
|
|
215
|
+
const variable = this.variableService.getVariable(variableName);
|
|
216
|
+
if (!variable || !AIContextVariable.is(variable)) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const widget = this.shell.getWidgetById(ChatViewWidget.ID);
|
|
220
|
+
if (widget instanceof ChatViewWidget) {
|
|
221
|
+
widget.addContext({ variable, arg });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
140
224
|
}
|
|
@@ -21,6 +21,7 @@ import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'
|
|
|
21
21
|
import { AIChatInputWidget } from './chat-input-widget';
|
|
22
22
|
import { ChatViewTreeWidget } from './chat-tree-view/chat-view-tree-widget';
|
|
23
23
|
import { AIActivationService } from '@theia/ai-core/lib/browser/ai-activation-service';
|
|
24
|
+
import { AIVariableResolutionRequest } from '@theia/ai-core';
|
|
24
25
|
|
|
25
26
|
export namespace ChatViewWidget {
|
|
26
27
|
export interface State {
|
|
@@ -32,7 +33,7 @@ export namespace ChatViewWidget {
|
|
|
32
33
|
export class ChatViewWidget extends BaseWidget implements ExtractableWidget, StatefulWidget {
|
|
33
34
|
|
|
34
35
|
public static ID = 'chat-view-widget';
|
|
35
|
-
static LABEL =
|
|
36
|
+
static LABEL = `${nls.localizeByDefault('Chat')}`;
|
|
36
37
|
|
|
37
38
|
@inject(ChatService)
|
|
38
39
|
protected chatService: ChatService;
|
|
@@ -91,8 +92,10 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
|
|
|
91
92
|
this.chatSession = this.chatService.createSession();
|
|
92
93
|
|
|
93
94
|
this.inputWidget.onQuery = this.onQuery.bind(this);
|
|
95
|
+
this.inputWidget.onUnpin = this.onUnpin.bind(this);
|
|
94
96
|
this.inputWidget.onCancel = this.onCancel.bind(this);
|
|
95
97
|
this.inputWidget.chatModel = this.chatSession.model;
|
|
98
|
+
this.inputWidget.pinnedAgent = this.chatSession.pinnedAgent;
|
|
96
99
|
this.inputWidget.onDeleteChangeSet = this.onDeleteChangeSet.bind(this);
|
|
97
100
|
this.inputWidget.onDeleteChangeSetElement = this.onDeleteChangeSetElement.bind(this);
|
|
98
101
|
this.treeWidget.trackChatModel(this.chatSession.model);
|
|
@@ -117,6 +120,7 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
|
|
|
117
120
|
this.chatSession = session;
|
|
118
121
|
this.treeWidget.trackChatModel(this.chatSession.model);
|
|
119
122
|
this.inputWidget.chatModel = this.chatSession.model;
|
|
123
|
+
this.inputWidget.pinnedAgent = this.chatSession.pinnedAgent;
|
|
120
124
|
if (event.focus) {
|
|
121
125
|
this.show();
|
|
122
126
|
}
|
|
@@ -160,15 +164,15 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
|
|
|
160
164
|
protected async onQuery(query: string): Promise<void> {
|
|
161
165
|
if (query.length === 0) { return; }
|
|
162
166
|
|
|
163
|
-
const chatRequest: ChatRequest = {
|
|
164
|
-
text: query
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
+
const chatRequest: ChatRequest = { text: query };
|
|
167
168
|
const requestProgress = await this.chatService.sendRequest(this.chatSession.id, chatRequest);
|
|
168
169
|
requestProgress?.responseCompleted.then(responseModel => {
|
|
169
170
|
if (responseModel.isError) {
|
|
170
|
-
this.messageService.error(responseModel.errorObject?.message ??
|
|
171
|
+
this.messageService.error(responseModel.errorObject?.message ??
|
|
172
|
+
nls.localize('theia/ai/chat-ui/errorChatInvocation', 'An error occurred during chat service invocation.'));
|
|
171
173
|
}
|
|
174
|
+
}).finally(() => {
|
|
175
|
+
this.inputWidget.pinnedAgent = this.chatSession.pinnedAgent;
|
|
172
176
|
});
|
|
173
177
|
if (!requestProgress) {
|
|
174
178
|
this.messageService.error(`Was not able to send request "${chatRequest.text}" to session ${this.chatSession.id}`);
|
|
@@ -177,6 +181,11 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
|
|
|
177
181
|
// Tree Widget currently tracks the ChatModel itself. Therefore no notification necessary.
|
|
178
182
|
}
|
|
179
183
|
|
|
184
|
+
protected onUnpin(): void {
|
|
185
|
+
this.chatSession.pinnedAgent = undefined;
|
|
186
|
+
this.inputWidget.pinnedAgent = this.chatSession.pinnedAgent;
|
|
187
|
+
}
|
|
188
|
+
|
|
180
189
|
protected onCancel(requestModel: ChatRequestModel): void {
|
|
181
190
|
this.chatService.cancelRequest(requestModel.session.id, requestModel.id);
|
|
182
191
|
}
|
|
@@ -204,4 +213,8 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
|
|
|
204
213
|
get isExtractable(): boolean {
|
|
205
214
|
return this.secondaryWindow === undefined;
|
|
206
215
|
}
|
|
216
|
+
|
|
217
|
+
addContext(variable: AIVariableResolutionRequest): void {
|
|
218
|
+
this.inputWidget.addContext(variable);
|
|
219
|
+
}
|
|
207
220
|
}
|