@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.
Files changed (118) hide show
  1. package/README.md +32 -0
  2. package/lib/browser/ai-chat-ui-contribution.d.ts +24 -0
  3. package/lib/browser/ai-chat-ui-contribution.d.ts.map +1 -0
  4. package/lib/browser/ai-chat-ui-contribution.js +161 -0
  5. package/lib/browser/ai-chat-ui-contribution.js.map +1 -0
  6. package/lib/browser/ai-chat-ui-frontend-module.d.ts +5 -0
  7. package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -0
  8. package/lib/browser/ai-chat-ui-frontend-module.js +83 -0
  9. package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -0
  10. package/lib/browser/chat-input-widget.d.ts +32 -0
  11. package/lib/browser/chat-input-widget.d.ts.map +1 -0
  12. package/lib/browser/chat-input-widget.js +205 -0
  13. package/lib/browser/chat-input-widget.js.map +1 -0
  14. package/lib/browser/chat-node-toolbar-action-contribution.d.ts +47 -0
  15. package/lib/browser/chat-node-toolbar-action-contribution.d.ts.map +1 -0
  16. package/lib/browser/chat-node-toolbar-action-contribution.js +25 -0
  17. package/lib/browser/chat-node-toolbar-action-contribution.js.map +1 -0
  18. package/lib/browser/chat-response-part-renderer.d.ts +10 -0
  19. package/lib/browser/chat-response-part-renderer.d.ts.map +1 -0
  20. package/lib/browser/chat-response-part-renderer.js +20 -0
  21. package/lib/browser/chat-response-part-renderer.js.map +1 -0
  22. package/lib/browser/chat-response-renderer/ai-editor-manager.d.ts +36 -0
  23. package/lib/browser/chat-response-renderer/ai-editor-manager.d.ts.map +1 -0
  24. package/lib/browser/chat-response-renderer/ai-editor-manager.js +184 -0
  25. package/lib/browser/chat-response-renderer/ai-editor-manager.js.map +1 -0
  26. package/lib/browser/chat-response-renderer/code-part-renderer.d.ts +45 -0
  27. package/lib/browser/chat-response-renderer/code-part-renderer.d.ts.map +1 -0
  28. package/lib/browser/chat-response-renderer/code-part-renderer.js +186 -0
  29. package/lib/browser/chat-response-renderer/code-part-renderer.js.map +1 -0
  30. package/lib/browser/chat-response-renderer/command-part-renderer.d.ts +12 -0
  31. package/lib/browser/chat-response-renderer/command-part-renderer.d.ts.map +1 -0
  32. package/lib/browser/chat-response-renderer/command-part-renderer.js +70 -0
  33. package/lib/browser/chat-response-renderer/command-part-renderer.js.map +1 -0
  34. package/lib/browser/chat-response-renderer/error-part-renderer.d.ts +9 -0
  35. package/lib/browser/chat-response-renderer/error-part-renderer.d.ts.map +1 -0
  36. package/lib/browser/chat-response-renderer/error-part-renderer.js +40 -0
  37. package/lib/browser/chat-response-renderer/error-part-renderer.js.map +1 -0
  38. package/lib/browser/chat-response-renderer/horizontal-layout-part-renderer.d.ts +12 -0
  39. package/lib/browser/chat-response-renderer/horizontal-layout-part-renderer.d.ts.map +1 -0
  40. package/lib/browser/chat-response-renderer/horizontal-layout-part-renderer.js +54 -0
  41. package/lib/browser/chat-response-renderer/horizontal-layout-part-renderer.js.map +1 -0
  42. package/lib/browser/chat-response-renderer/index.d.ts +9 -0
  43. package/lib/browser/chat-response-renderer/index.d.ts.map +1 -0
  44. package/lib/browser/chat-response-renderer/index.js +27 -0
  45. package/lib/browser/chat-response-renderer/index.js.map +1 -0
  46. package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts +17 -0
  47. package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts.map +1 -0
  48. package/lib/browser/chat-response-renderer/markdown-part-renderer.js +70 -0
  49. package/lib/browser/chat-response-renderer/markdown-part-renderer.js.map +1 -0
  50. package/lib/browser/chat-response-renderer/text-part-renderer.d.ts +9 -0
  51. package/lib/browser/chat-response-renderer/text-part-renderer.d.ts.map +1 -0
  52. package/lib/browser/chat-response-renderer/text-part-renderer.js +41 -0
  53. package/lib/browser/chat-response-renderer/text-part-renderer.js.map +1 -0
  54. package/lib/browser/chat-response-renderer/text-part-renderer.spec.d.ts +2 -0
  55. package/lib/browser/chat-response-renderer/text-part-renderer.spec.d.ts.map +1 -0
  56. package/lib/browser/chat-response-renderer/text-part-renderer.spec.js +46 -0
  57. package/lib/browser/chat-response-renderer/text-part-renderer.spec.js.map +1 -0
  58. package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts +9 -0
  59. package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts.map +1 -0
  60. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js +49 -0
  61. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -0
  62. package/lib/browser/chat-tree-view/chat-view-tree-container.d.ts +4 -0
  63. package/lib/browser/chat-tree-view/chat-view-tree-container.d.ts.map +1 -0
  64. package/lib/browser/chat-tree-view/chat-view-tree-container.js +33 -0
  65. package/lib/browser/chat-tree-view/chat-view-tree-container.js.map +1 -0
  66. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts +53 -0
  67. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts.map +1 -0
  68. package/lib/browser/chat-tree-view/chat-view-tree-widget.js +324 -0
  69. package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -0
  70. package/lib/browser/chat-tree-view/index.d.ts +3 -0
  71. package/lib/browser/chat-tree-view/index.d.ts.map +1 -0
  72. package/lib/browser/chat-tree-view/index.js +21 -0
  73. package/lib/browser/chat-tree-view/index.js.map +1 -0
  74. package/lib/browser/chat-view-commands.d.ts +8 -0
  75. package/lib/browser/chat-view-commands.d.ts.map +1 -0
  76. package/lib/browser/chat-view-commands.js +44 -0
  77. package/lib/browser/chat-view-commands.js.map +1 -0
  78. package/lib/browser/chat-view-contribution.d.ts +18 -0
  79. package/lib/browser/chat-view-contribution.d.ts.map +1 -0
  80. package/lib/browser/chat-view-contribution.js +153 -0
  81. package/lib/browser/chat-view-contribution.js.map +1 -0
  82. package/lib/browser/chat-view-language-contribution.d.ts +20 -0
  83. package/lib/browser/chat-view-language-contribution.d.ts.map +1 -0
  84. package/lib/browser/chat-view-language-contribution.js +98 -0
  85. package/lib/browser/chat-view-language-contribution.js.map +1 -0
  86. package/lib/browser/chat-view-widget-toolbar-contribution.d.ts +11 -0
  87. package/lib/browser/chat-view-widget-toolbar-contribution.d.ts.map +1 -0
  88. package/lib/browser/chat-view-widget-toolbar-contribution.js +65 -0
  89. package/lib/browser/chat-view-widget-toolbar-contribution.js.map +1 -0
  90. package/lib/browser/chat-view-widget.d.ts +41 -0
  91. package/lib/browser/chat-view-widget.d.ts.map +1 -0
  92. package/lib/browser/chat-view-widget.js +182 -0
  93. package/lib/browser/chat-view-widget.js.map +1 -0
  94. package/package.json +59 -0
  95. package/src/browser/ai-chat-ui-contribution.ts +171 -0
  96. package/src/browser/ai-chat-ui-frontend-module.ts +101 -0
  97. package/src/browser/chat-input-widget.tsx +247 -0
  98. package/src/browser/chat-node-toolbar-action-contribution.ts +63 -0
  99. package/src/browser/chat-response-part-renderer.ts +25 -0
  100. package/src/browser/chat-response-renderer/ai-editor-manager.ts +183 -0
  101. package/src/browser/chat-response-renderer/code-part-renderer.tsx +208 -0
  102. package/src/browser/chat-response-renderer/command-part-renderer.tsx +60 -0
  103. package/src/browser/chat-response-renderer/error-part-renderer.tsx +35 -0
  104. package/src/browser/chat-response-renderer/horizontal-layout-part-renderer.tsx +59 -0
  105. package/src/browser/chat-response-renderer/index.ts +23 -0
  106. package/src/browser/chat-response-renderer/markdown-part-renderer.tsx +71 -0
  107. package/src/browser/chat-response-renderer/text-part-renderer.spec.ts +50 -0
  108. package/src/browser/chat-response-renderer/text-part-renderer.tsx +35 -0
  109. package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +49 -0
  110. package/src/browser/chat-tree-view/chat-view-tree-container.ts +32 -0
  111. package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +410 -0
  112. package/src/browser/chat-tree-view/index.ts +18 -0
  113. package/src/browser/chat-view-commands.ts +45 -0
  114. package/src/browser/chat-view-contribution.ts +154 -0
  115. package/src/browser/chat-view-language-contribution.ts +141 -0
  116. package/src/browser/chat-view-widget-toolbar-contribution.tsx +54 -0
  117. package/src/browser/chat-view-widget.tsx +194 -0
  118. package/src/browser/style/index.css +328 -0
@@ -0,0 +1,50 @@
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 { TextPartRenderer } from './text-part-renderer';
18
+ import { expect } from 'chai';
19
+ import { ChatResponseContent } from '@theia/ai-chat';
20
+
21
+ describe('TextPartRenderer', () => {
22
+
23
+ it('accepts all parts', () => {
24
+ const renderer = new TextPartRenderer();
25
+ expect(renderer.canHandle({ kind: 'text' })).to.be.greaterThan(0);
26
+ expect(renderer.canHandle({ kind: 'code' })).to.be.greaterThan(0);
27
+ expect(renderer.canHandle({ kind: 'command' })).to.be.greaterThan(0);
28
+ expect(renderer.canHandle({ kind: 'error' })).to.be.greaterThan(0);
29
+ expect(renderer.canHandle({ kind: 'horizontal' })).to.be.greaterThan(0);
30
+ expect(renderer.canHandle({ kind: 'informational' })).to.be.greaterThan(0);
31
+ expect(renderer.canHandle({ kind: 'markdownContent' })).to.be.greaterThan(0);
32
+ expect(renderer.canHandle({ kind: 'toolCall' })).to.be.greaterThan(0);
33
+ expect(renderer.canHandle(undefined as unknown as ChatResponseContent)).to.be.greaterThan(0);
34
+ });
35
+
36
+ it('renders text correctly', () => {
37
+ const renderer = new TextPartRenderer();
38
+ const part = { kind: 'text', asString: () => 'Hello, World!' };
39
+ const node = renderer.render(part);
40
+ expect(JSON.stringify(node)).to.contain('Hello, World!');
41
+ });
42
+
43
+ it('handles undefined content gracefully', () => {
44
+ const renderer = new TextPartRenderer();
45
+ const part = undefined as unknown as ChatResponseContent;
46
+ const node = renderer.render(part);
47
+ expect(node).to.exist;
48
+ });
49
+
50
+ });
@@ -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 } 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 TextPartRenderer implements ChatResponsePartRenderer<ChatResponseContent> {
25
+ canHandle(_reponse: ChatResponseContent): number {
26
+ // this is the fallback renderer
27
+ return 1;
28
+ }
29
+ render(response: ChatResponseContent): ReactNode {
30
+ if (response && ChatResponseContent.hasAsString(response)) {
31
+ return <span>{response.asString()}</span>;
32
+ }
33
+ return <span>Can't display response, please check your ChatResponsePartRenderers! {JSON.stringify(response)}</span>;
34
+ }
35
+ }
@@ -0,0 +1,49 @@
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, ToolCallChatResponseContent } 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 ToolCallPartRenderer implements ChatResponsePartRenderer<ToolCallChatResponseContent> {
25
+
26
+ canHandle(response: ChatResponseContent): number {
27
+ if (ToolCallChatResponseContent.is(response)) {
28
+ return 10;
29
+ }
30
+ return -1;
31
+ }
32
+ render(response: ToolCallChatResponseContent): ReactNode {
33
+ return <h4 className='theia-toolCall'>
34
+ {response.finished ?
35
+ <details>
36
+ <summary>Ran {response.name}</summary>
37
+ <p>{response.result}</p>
38
+ </details>
39
+ : <span><Spinner /> Running [{response.name}]</span>
40
+ }
41
+ </h4>;
42
+
43
+ }
44
+
45
+ }
46
+
47
+ const Spinner = () => (
48
+ <i className="fa fa-spinner fa-spin"></i>
49
+ );
@@ -0,0 +1,32 @@
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 { createTreeContainer, TreeProps } from '@theia/core/lib/browser';
18
+ import { interfaces } from '@theia/core/shared/inversify';
19
+ import { ChatViewTreeWidget } from './chat-view-tree-widget';
20
+
21
+ const CHAT_VIEW_TREE_PROPS = {
22
+ multiSelect: false,
23
+ search: false,
24
+ } as TreeProps;
25
+
26
+ export function createChatViewTreeWidget(parent: interfaces.Container): ChatViewTreeWidget {
27
+ const child = createTreeContainer(parent, {
28
+ props: CHAT_VIEW_TREE_PROPS,
29
+ widget: ChatViewTreeWidget,
30
+ });
31
+ return child.get(ChatViewTreeWidget);
32
+ }
@@ -0,0 +1,410 @@
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
+ import {
17
+ ChatAgentService,
18
+ ChatModel,
19
+ ChatProgressMessage,
20
+ ChatRequestModel,
21
+ ChatResponseContent,
22
+ ChatResponseModel,
23
+ } from '@theia/ai-chat';
24
+ import { CommandRegistry, ContributionProvider } from '@theia/core';
25
+ import {
26
+ codicon,
27
+ CommonCommands,
28
+ CompositeTreeNode,
29
+ ContextMenuRenderer,
30
+ Key,
31
+ KeyCode,
32
+ NodeProps,
33
+ TreeModel,
34
+ TreeNode,
35
+ TreeProps,
36
+ TreeWidget,
37
+ } from '@theia/core/lib/browser';
38
+ import { MarkdownStringImpl } from '@theia/core/lib/common/markdown-rendering/markdown-string';
39
+ import {
40
+ inject,
41
+ injectable,
42
+ named,
43
+ postConstruct
44
+ } from '@theia/core/shared/inversify';
45
+ import * as React from '@theia/core/shared/react';
46
+
47
+ import { MarkdownRenderer } from '@theia/core/lib/browser/markdown-rendering/markdown-renderer';
48
+ import { ChatNodeToolbarActionContribution } from '../chat-node-toolbar-action-contribution';
49
+ import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
50
+ import { MarkdownWrapper } from '../chat-response-renderer/markdown-part-renderer';
51
+
52
+ // TODO Instead of directly operating on the ChatRequestModel we could use an intermediate view model
53
+ export interface RequestNode extends TreeNode {
54
+ request: ChatRequestModel
55
+ }
56
+ export const isRequestNode = (node: TreeNode): node is RequestNode => 'request' in node;
57
+
58
+ // TODO Instead of directly operating on the ChatResponseModel we could use an intermediate view model
59
+ export interface ResponseNode extends TreeNode {
60
+ response: ChatResponseModel
61
+ }
62
+ export const isResponseNode = (node: TreeNode): node is ResponseNode => 'response' in node;
63
+
64
+ function isEnterKey(e: React.KeyboardEvent): boolean {
65
+ return Key.ENTER.keyCode === KeyCode.createKeyCode(e.nativeEvent).key?.keyCode;
66
+ }
67
+
68
+ @injectable()
69
+ export class ChatViewTreeWidget extends TreeWidget {
70
+ static readonly ID = 'chat-tree-widget';
71
+ static readonly CONTEXT_MENU = ['chat-tree-context-menu'];
72
+
73
+ @inject(ContributionProvider) @named(ChatResponsePartRenderer)
74
+ protected readonly chatResponsePartRenderers: ContributionProvider<ChatResponsePartRenderer<ChatResponseContent>>;
75
+
76
+ @inject(ContributionProvider) @named(ChatNodeToolbarActionContribution)
77
+ protected readonly chatNodeToolbarActionContributions: ContributionProvider<ChatNodeToolbarActionContribution>;
78
+
79
+ @inject(MarkdownRenderer)
80
+ private renderer: MarkdownRenderer;
81
+
82
+ @inject(ChatAgentService)
83
+ protected chatAgentService: ChatAgentService;
84
+
85
+ @inject(CommandRegistry)
86
+ private commandRegistry: CommandRegistry;
87
+
88
+ protected _shouldScrollToEnd = true;
89
+
90
+ protected isEnabled = false;
91
+
92
+ set shouldScrollToEnd(shouldScrollToEnd: boolean) {
93
+ this._shouldScrollToEnd = shouldScrollToEnd;
94
+ this.shouldScrollToRow = this._shouldScrollToEnd;
95
+ }
96
+
97
+ get shouldScrollToEnd(): boolean {
98
+ return this._shouldScrollToEnd;
99
+ }
100
+
101
+ constructor(
102
+ @inject(TreeProps) props: TreeProps,
103
+ @inject(TreeModel) model: TreeModel,
104
+ @inject(ContextMenuRenderer) contextMenuRenderer: ContextMenuRenderer
105
+ ) {
106
+ super(props, model, contextMenuRenderer);
107
+
108
+ this.id = ChatViewTreeWidget.ID;
109
+ this.title.closable = false;
110
+
111
+ model.root = {
112
+ id: 'ChatTree',
113
+ name: 'ChatRootNode',
114
+ parent: undefined,
115
+ visible: false,
116
+ children: [],
117
+ } as CompositeTreeNode;
118
+ }
119
+
120
+ @postConstruct()
121
+ protected override init(): void {
122
+ super.init();
123
+
124
+ this.id = ChatViewTreeWidget.ID + '-treeContainer';
125
+ this.addClass('treeContainer');
126
+ }
127
+
128
+ public setEnabled(enabled: boolean): void {
129
+ this.isEnabled = enabled;
130
+ this.update();
131
+ }
132
+
133
+ protected override renderTree(model: TreeModel): React.ReactNode {
134
+ if (this.isEnabled) {
135
+ return super.renderTree(model);
136
+ }
137
+ return this.renderDisabledMessage();
138
+ }
139
+
140
+ private renderDisabledMessage(): React.ReactNode {
141
+ return <div className={'theia-ResponseNode'}>
142
+ <div className='theia-ResponseNode-Content' key={'disabled-message'}>
143
+ <div className="disable-message">
144
+ <span className="section-header"> 🚀 Experimental AI Feature Available!</span>
145
+ <div className="section-title">
146
+ <p><code>Currently, all AI Features are disabled!</code></p>
147
+ </div>
148
+ <div className="section-title">
149
+ <p>How to Enable Experimental AI Features:</p>
150
+ </div>
151
+ <div className="section-content">
152
+ <p>To enable the experimental AI features, please go to &nbsp;
153
+ {this.renderLinkButton('the settings menu', CommonCommands.OPEN_PREFERENCES.id)}
154
+ &nbsp;and locate the <strong>AI Features</strong> section.</p>
155
+ <ol>
156
+ <li>Toggle the switch for <strong>'Ai-features: Enable'</strong>.</li>
157
+ <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>
158
+ for more information.</li>
159
+ </ol>
160
+ <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>
161
+ </div>
162
+
163
+ <div className="section-title">
164
+ <p>Currently Supported Views and Features:</p>
165
+ </div>
166
+ <div className="section-content">
167
+ <p>Once the experimental AI features are enabled, you can access the following views and features:</p>
168
+ <ul>
169
+ <li>Code Completion</li>
170
+ <li>Terminal Assistance (via CTRL+I in a terminal)</li>
171
+ <li>This Chat View (features the following agents):
172
+ <ul>
173
+ <li>Universal Chat Agent</li>
174
+ <li>Workspace Chat Agent</li>
175
+ <li>Command Chat Agent</li>
176
+ <li>Orchestrator Chat Agent</li>
177
+ </ul>
178
+ </li>
179
+ <li>{this.renderLinkButton('AI History View', 'aiHistory:open')}</li>
180
+ <li>{this.renderLinkButton('AI Configuration View', 'aiConfiguration:open')}</li>
181
+ </ul>
182
+ <p>See <a href="https://theia-ide.org/docs/user_ai/" target="_blank">the documentation</a> for more information.</p>
183
+ </div>
184
+ </div>
185
+ </div>
186
+ </div >;
187
+ }
188
+
189
+ private renderLinkButton(title: string, openCommandId: string): React.ReactNode {
190
+ return <a
191
+ role={'button'}
192
+ tabIndex={0}
193
+ onClick={() => this.commandRegistry.executeCommand(openCommandId)}
194
+ onKeyDown={e => isEnterKey(e) && this.commandRegistry.executeCommand(openCommandId)}>
195
+ {title}
196
+ </a>;
197
+ }
198
+
199
+ private mapRequestToNode(request: ChatRequestModel): RequestNode {
200
+ return {
201
+ id: request.id,
202
+ parent: this.model.root as CompositeTreeNode,
203
+ request
204
+ };
205
+ }
206
+
207
+ private mapResponseToNode(response: ChatResponseModel): ResponseNode {
208
+ return {
209
+ id: response.id,
210
+ parent: this.model.root as CompositeTreeNode,
211
+ response
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Tracks the ChatModel handed over.
217
+ * Tracking multiple chat models will result in a weird UI
218
+ */
219
+ public trackChatModel(chatModel: ChatModel): void {
220
+ this.recreateModelTree(chatModel);
221
+ chatModel.getRequests().forEach(request => {
222
+ if (!request.response.isComplete) {
223
+ request.response.onDidChange(() => this.scheduleUpdateScrollToRow());
224
+ }
225
+ });
226
+ this.toDispose.push(
227
+ chatModel.onDidChange(event => {
228
+ this.recreateModelTree(chatModel);
229
+ if (event.kind === 'addRequest' && !event.request.response.isComplete) {
230
+ event.request.response.onDidChange(() => this.scheduleUpdateScrollToRow());
231
+ }
232
+ })
233
+ );
234
+ }
235
+
236
+ protected override getScrollToRow(): number | undefined {
237
+ if (this.shouldScrollToEnd) {
238
+ return this.rows.size;
239
+ }
240
+ return super.getScrollToRow();
241
+ }
242
+
243
+ private async recreateModelTree(chatModel: ChatModel): Promise<void> {
244
+ if (CompositeTreeNode.is(this.model.root)) {
245
+ const nodes: TreeNode[] = [];
246
+ chatModel.getRequests().forEach(request => {
247
+ nodes.push(this.mapRequestToNode(request));
248
+ nodes.push(this.mapResponseToNode(request.response));
249
+ });
250
+ this.model.root.children = nodes;
251
+ this.model.refresh();
252
+ }
253
+ }
254
+
255
+ protected override renderNode(
256
+ node: TreeNode,
257
+ props: NodeProps
258
+ ): React.ReactNode {
259
+ if (!TreeNode.isVisible(node)) {
260
+ return undefined;
261
+ }
262
+ if (!(isRequestNode(node) || isResponseNode(node))) {
263
+ return super.renderNode(node, props);
264
+ }
265
+ return <React.Fragment key={node.id}>
266
+ <div className='theia-ChatNode' onContextMenu={e => this.handleContextMenu(node, e)}>
267
+ {this.renderAgent(node)}
268
+ {this.renderDetail(node)}
269
+ </div>
270
+ </React.Fragment>;
271
+ }
272
+
273
+ private renderAgent(node: RequestNode | ResponseNode): React.ReactNode {
274
+ const inProgress = isResponseNode(node) && !node.response.isComplete && !node.response.isCanceled && !node.response.isError;
275
+ const toolbarContributions = !inProgress
276
+ ? this.chatNodeToolbarActionContributions.getContributions()
277
+ .flatMap(c => c.getToolbarActions(node))
278
+ .filter(action => this.commandRegistry.isEnabled(action.commandId, node))
279
+ .sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0))
280
+ : [];
281
+ return <React.Fragment>
282
+ <div className='theia-ChatNodeHeader'>
283
+ <div className={`theia-AgentAvatar ${this.getAgentIconClassName(node)}`}></div>
284
+ <h3 className='theia-AgentLabel'>{this.getAgentLabel(node)}</h3>
285
+ {inProgress && <span className='theia-ChatContentInProgress'>Generating</span>}
286
+ <div className='theia-ChatNodeToolbar'>
287
+ {!inProgress &&
288
+ toolbarContributions.length > 0 &&
289
+ toolbarContributions.map(action =>
290
+ <span
291
+ className={`theia-ChatNodeToolbarAction ${action.icon}`}
292
+ title={action.tooltip}
293
+ onClick={e => {
294
+ e.stopPropagation();
295
+ this.commandRegistry.executeCommand(action.commandId, node);
296
+ }}
297
+ onKeyDown={e => {
298
+ if (isEnterKey(e)) {
299
+ e.stopPropagation();
300
+ this.commandRegistry.executeCommand(action.commandId, node);
301
+ }
302
+ }}
303
+ role='button'
304
+ ></span>
305
+ )}
306
+ </div>
307
+ </div>
308
+ </React.Fragment>;
309
+ }
310
+
311
+ private getAgentLabel(node: RequestNode | ResponseNode): string {
312
+ if (isRequestNode(node)) {
313
+ // TODO find user name
314
+ return 'You';
315
+ }
316
+ const agent = node.response.agentId ? this.chatAgentService.getAgent(node.response.agentId) : undefined;
317
+ return agent?.name ?? 'AI';
318
+ }
319
+
320
+ private getAgentIconClassName(node: RequestNode | ResponseNode): string | undefined {
321
+ if (isRequestNode(node)) {
322
+ return codicon('account');
323
+ }
324
+
325
+ const agent = node.response.agentId ? this.chatAgentService.getAgent(node.response.agentId) : undefined;
326
+ return agent?.iconClass ?? codicon('copilot');
327
+ }
328
+
329
+ private renderDetail(node: RequestNode | ResponseNode): React.ReactNode {
330
+ if (isRequestNode(node)) {
331
+ return this.renderChatRequest(node);
332
+ }
333
+ if (isResponseNode(node)) {
334
+ return this.renderChatResponse(node);
335
+ };
336
+ }
337
+
338
+ private renderChatRequest(node: RequestNode): React.ReactNode {
339
+ const text = node.request.request.displayText ?? node.request.request.text;
340
+ const markdownString = new MarkdownStringImpl(text, { supportHtml: true, isTrusted: true });
341
+ return (
342
+ <div className={'theia-RequestNode'}>
343
+ {<MarkdownWrapper
344
+ data={markdownString}
345
+ renderCallback={() => this.renderer.render(markdownString).element}
346
+ ></MarkdownWrapper>}
347
+ </div>
348
+ );
349
+ }
350
+
351
+ private renderChatResponse(node: ResponseNode): React.ReactNode {
352
+ return (
353
+ <div className={'theia-ResponseNode'}>
354
+ {!node.response.isComplete
355
+ && node.response.response.content.length === 0
356
+ && node.response.progressMessages.map((c, i) =>
357
+ <ProgressMessage {...c} key={`${node.id}-progress-${i}`} />
358
+ )}
359
+ {node.response.response.content.map((c, i) =>
360
+ <div className='theia-ResponseNode-Content' key={`${node.id}-content-${i}`}>{this.getChatResponsePartRenderer(c, node)}</div>
361
+ )}
362
+ </div>
363
+ );
364
+ }
365
+
366
+ private getChatResponsePartRenderer(content: ChatResponseContent, node: ResponseNode): React.ReactNode {
367
+ const renderer = this.chatResponsePartRenderers.getContributions().reduce<[number, ChatResponsePartRenderer<ChatResponseContent> | undefined]>(
368
+ (prev, current) => {
369
+ const prio = current.canHandle(content);
370
+ if (prio > prev[0]) {
371
+ return [prio, current];
372
+ } return prev;
373
+ },
374
+ [-1, undefined])[1];
375
+ if (!renderer) {
376
+ console.error('No renderer found for content', content);
377
+ return <div>Error: No renderer found</div>;
378
+ }
379
+ return renderer.render(content, node);
380
+ }
381
+
382
+ protected handleContextMenu(node: TreeNode | undefined, event: React.MouseEvent<HTMLElement>): void {
383
+ this.contextMenuRenderer.render({
384
+ menuPath: ChatViewTreeWidget.CONTEXT_MENU,
385
+ anchor: { x: event.clientX, y: event.clientY },
386
+ args: [node]
387
+ });
388
+ event.preventDefault();
389
+ }
390
+ }
391
+
392
+ const ProgressMessage = (c: ChatProgressMessage) => (
393
+ <div className='theia-ResponseNode-ProgressMessage'>
394
+ <Indicator {...c} /> {c.content}
395
+ </div>
396
+ );
397
+
398
+ const Indicator = (progressMessage: ChatProgressMessage) => (
399
+ <span className='theia-ResponseNode-ProgressMessage-Indicator'>
400
+ {progressMessage.status === 'inProgress' &&
401
+ <i className={'fa fa-spinner fa-spin ' + progressMessage.status}></i>
402
+ }
403
+ {progressMessage.status === 'completed' &&
404
+ <i className={'fa fa-check ' + progressMessage.status}></i>
405
+ }
406
+ {progressMessage.status === 'failed' &&
407
+ <i className={'fa fa-warning ' + progressMessage.status}></i>
408
+ }
409
+ </span>
410
+ );
@@ -0,0 +1,18 @@
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
+ export * from './chat-view-tree-container';
18
+ export * from './chat-view-tree-widget';
@@ -0,0 +1,45 @@
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 { Command, nls } from '@theia/core';
18
+ import { codicon } from '@theia/core/lib/browser';
19
+
20
+ export namespace ChatCommands {
21
+ const CHAT_CATEGORY = 'Chat';
22
+ const CHAT_CATEGORY_KEY = nls.getDefaultKey(CHAT_CATEGORY);
23
+
24
+ export const SCROLL_LOCK_WIDGET = Command.toLocalizedCommand({
25
+ id: 'chat:widget:lock',
26
+ category: CHAT_CATEGORY,
27
+ iconClass: codicon('unlock')
28
+ }, '', CHAT_CATEGORY_KEY);
29
+
30
+ export const SCROLL_UNLOCK_WIDGET = Command.toLocalizedCommand({
31
+ id: 'chat:widget:unlock',
32
+ category: CHAT_CATEGORY,
33
+ iconClass: codicon('lock')
34
+ }, '', CHAT_CATEGORY_KEY);
35
+ }
36
+
37
+ export const AI_CHAT_NEW_CHAT_WINDOW_COMMAND: Command = {
38
+ id: 'ai-chat-ui.new-chat',
39
+ iconClass: codicon('add')
40
+ };
41
+
42
+ export const AI_CHAT_SHOW_CHATS_COMMAND: Command = {
43
+ id: 'ai-chat-ui.show-chats',
44
+ iconClass: codicon('history')
45
+ };