@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.
Files changed (75) hide show
  1. package/lib/browser/ai-chat-ui-contribution.d.ts.map +1 -1
  2. package/lib/browser/ai-chat-ui-contribution.js +7 -7
  3. package/lib/browser/ai-chat-ui-contribution.js.map +1 -1
  4. package/lib/browser/ai-chat-ui-frontend-module.d.ts +1 -1
  5. package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -1
  6. package/lib/browser/ai-chat-ui-frontend-module.js +17 -13
  7. package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -1
  8. package/lib/browser/change-set-actions/change-set-accept-action.d.ts +10 -0
  9. package/lib/browser/change-set-actions/change-set-accept-action.d.ts.map +1 -0
  10. package/lib/browser/change-set-actions/change-set-accept-action.js +47 -0
  11. package/lib/browser/change-set-actions/change-set-accept-action.js.map +1 -0
  12. package/lib/browser/change-set-actions/change-set-action-service.d.ts +31 -0
  13. package/lib/browser/change-set-actions/change-set-action-service.d.ts.map +1 -0
  14. package/lib/browser/change-set-actions/change-set-action-service.js +57 -0
  15. package/lib/browser/change-set-actions/change-set-action-service.js.map +1 -0
  16. package/lib/browser/chat-input-widget.d.ts +22 -3
  17. package/lib/browser/chat-input-widget.d.ts.map +1 -1
  18. package/lib/browser/chat-input-widget.js +209 -56
  19. package/lib/browser/chat-input-widget.js.map +1 -1
  20. package/lib/browser/chat-response-renderer/ai-selection-resolver.d.ts +23 -0
  21. package/lib/browser/chat-response-renderer/ai-selection-resolver.d.ts.map +1 -0
  22. package/lib/browser/chat-response-renderer/{ai-editor-manager.js → ai-selection-resolver.js} +2 -38
  23. package/lib/browser/chat-response-renderer/ai-selection-resolver.js.map +1 -0
  24. package/lib/browser/chat-response-renderer/code-part-renderer.d.ts.map +1 -1
  25. package/lib/browser/chat-response-renderer/code-part-renderer.js +4 -3
  26. package/lib/browser/chat-response-renderer/code-part-renderer.js.map +1 -1
  27. package/lib/browser/chat-response-renderer/index.d.ts +1 -1
  28. package/lib/browser/chat-response-renderer/index.d.ts.map +1 -1
  29. package/lib/browser/chat-response-renderer/index.js +1 -1
  30. package/lib/browser/chat-response-renderer/index.js.map +1 -1
  31. package/lib/browser/chat-response-renderer/text-part-renderer.d.ts.map +1 -1
  32. package/lib/browser/chat-response-renderer/text-part-renderer.js +3 -1
  33. package/lib/browser/chat-response-renderer/text-part-renderer.js.map +1 -1
  34. package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts.map +1 -1
  35. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js +7 -3
  36. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -1
  37. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts.map +1 -1
  38. package/lib/browser/chat-tree-view/chat-view-tree-widget.js +25 -16
  39. package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -1
  40. package/lib/browser/chat-view-contribution.d.ts +2 -2
  41. package/lib/browser/chat-view-contribution.d.ts.map +1 -1
  42. package/lib/browser/chat-view-contribution.js +6 -6
  43. package/lib/browser/chat-view-contribution.js.map +1 -1
  44. package/lib/browser/chat-view-language-contribution.d.ts +10 -5
  45. package/lib/browser/chat-view-language-contribution.d.ts.map +1 -1
  46. package/lib/browser/chat-view-language-contribution.js +94 -14
  47. package/lib/browser/chat-view-language-contribution.js.map +1 -1
  48. package/lib/browser/chat-view-widget.d.ts +3 -0
  49. package/lib/browser/chat-view-widget.d.ts.map +1 -1
  50. package/lib/browser/chat-view-widget.js +15 -5
  51. package/lib/browser/chat-view-widget.js.map +1 -1
  52. package/lib/browser/context-variable-picker.d.ts +9 -0
  53. package/lib/browser/context-variable-picker.d.ts.map +1 -0
  54. package/lib/browser/context-variable-picker.js +86 -0
  55. package/lib/browser/context-variable-picker.js.map +1 -0
  56. package/package.json +11 -11
  57. package/src/browser/ai-chat-ui-contribution.ts +8 -8
  58. package/src/browser/ai-chat-ui-frontend-module.ts +18 -16
  59. package/src/browser/change-set-actions/change-set-accept-action.tsx +52 -0
  60. package/src/browser/change-set-actions/change-set-action-service.ts +65 -0
  61. package/src/browser/chat-input-widget.tsx +307 -75
  62. package/src/browser/chat-response-renderer/{ai-editor-manager.ts → ai-selection-resolver.ts} +6 -45
  63. package/src/browser/chat-response-renderer/code-part-renderer.tsx +4 -3
  64. package/src/browser/chat-response-renderer/index.ts +1 -1
  65. package/src/browser/chat-response-renderer/text-part-renderer.tsx +4 -1
  66. package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +9 -3
  67. package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +26 -16
  68. package/src/browser/chat-view-contribution.ts +6 -6
  69. package/src/browser/chat-view-language-contribution.ts +103 -19
  70. package/src/browser/chat-view-widget.tsx +19 -6
  71. package/src/browser/context-variable-picker.ts +85 -0
  72. package/src/browser/style/index.css +110 -12
  73. package/lib/browser/chat-response-renderer/ai-editor-manager.d.ts +0 -36
  74. package/lib/browser/chat-response-renderer/ai-editor-manager.d.ts.map +0 -1
  75. package/lib/browser/chat-response-renderer/ai-editor-manager.js.map +0 -1
@@ -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, ContributionProvider, Prioritizeable, RecursivePartial, URI } from '@theia/core';
18
- import { inject, injectable, named } from '@theia/core/shared/inversify';
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 { EditorPreviewManager } from '@theia/editor-preview/lib/browser/editor-preview-manager';
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 AIEditorSelectionResolver {
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 AIEditorSelectionResolver {
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 AIEditorSelectionResolver {
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-editor-manager';
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>Can't display response, please check your ChatResponsePartRenderers! {JSON.stringify(response)}</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 = `The content could not be converted to string: '${e.message}'. This is the original content: '${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"> 🚀 Experimental AI Feature Available!</span>
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!</code></p>
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 Experimental AI Features:</p>
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 experimental AI features, please go to &nbsp;
163
- {this.renderLinkButton('the settings menu', CommonCommands.OPEN_PREFERENCES.id)}
165
+ <p>To enable the AI features, please go to &nbsp;
166
+ {this.renderLinkButton(nls.localize('theia/ai/chat-ui/chat-view-tree-widget/settingsMenu', 'the settings menu'), CommonCommands.OPEN_PREFERENCES.id)}
164
167
  &nbsp;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 new AI capabilities in the app. Please remember, these features are still in development, so they may change or be unstable. 🚧</p>
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 experimental AI features are enabled, you can access the following views and features:</p>
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>Workspace Chat Agent</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('AI Configuration View', 'aiConfiguration:open')}</li>
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'>Waiting for input</span>}
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.getText(child))
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.getTextAndJoin(args);
96
+ const text = this.getCopyTextAndJoin(args);
97
97
  this.clipboardService.writeText(text);
98
98
  }
99
99
 
100
- protected getTextAndJoin(args: (RequestNode | ResponseNode)[] | undefined): string {
101
- return args !== undefined ? args.map(arg => this.getText(arg)).join() : '';
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 getText(arg: RequestNode | ResponseNode): string {
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.asString();
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 { inject, injectable, named } from '@theia/core/shared/inversify';
17
- import { FrontendApplication, FrontendApplicationContribution } from '@theia/core/lib/browser';
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 { ChatAgentService } from '@theia/ai-chat';
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(ContributionProvider)
38
- @named(ToolProvider)
39
- private providers: ContributionProvider<ToolProvider>;
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 '@' character
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.providers.getContributions().map(provider => provider.getTool()),
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 = `✨ ${nls.localizeByDefault('Chat')} [Experimental]`;
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 ?? 'An error occurred during chat service invocation.');
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
  }