@theia/ai-chat-ui 1.46.0-next.241

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 (123) hide show
  1. package/README.md +33 -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 +87 -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 +217 -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 +190 -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 +25 -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 +89 -0
  49. package/lib/browser/chat-response-renderer/markdown-part-renderer.js.map +1 -0
  50. package/lib/browser/chat-response-renderer/question-part-renderer.d.ts +10 -0
  51. package/lib/browser/chat-response-renderer/question-part-renderer.d.ts.map +1 -0
  52. package/lib/browser/chat-response-renderer/question-part-renderer.js +43 -0
  53. package/lib/browser/chat-response-renderer/question-part-renderer.js.map +1 -0
  54. package/lib/browser/chat-response-renderer/text-part-renderer.d.ts +9 -0
  55. package/lib/browser/chat-response-renderer/text-part-renderer.d.ts.map +1 -0
  56. package/lib/browser/chat-response-renderer/text-part-renderer.js +41 -0
  57. package/lib/browser/chat-response-renderer/text-part-renderer.js.map +1 -0
  58. package/lib/browser/chat-response-renderer/text-part-renderer.spec.d.ts +2 -0
  59. package/lib/browser/chat-response-renderer/text-part-renderer.spec.d.ts.map +1 -0
  60. package/lib/browser/chat-response-renderer/text-part-renderer.spec.js +46 -0
  61. package/lib/browser/chat-response-renderer/text-part-renderer.spec.js.map +1 -0
  62. package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts +12 -0
  63. package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts.map +1 -0
  64. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js +81 -0
  65. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -0
  66. package/lib/browser/chat-tree-view/chat-view-tree-container.d.ts +4 -0
  67. package/lib/browser/chat-tree-view/chat-view-tree-container.d.ts.map +1 -0
  68. package/lib/browser/chat-tree-view/chat-view-tree-container.js +33 -0
  69. package/lib/browser/chat-tree-view/chat-view-tree-container.js.map +1 -0
  70. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts +56 -0
  71. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts.map +1 -0
  72. package/lib/browser/chat-tree-view/chat-view-tree-widget.js +388 -0
  73. package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -0
  74. package/lib/browser/chat-tree-view/index.d.ts +3 -0
  75. package/lib/browser/chat-tree-view/index.d.ts.map +1 -0
  76. package/lib/browser/chat-tree-view/index.js +21 -0
  77. package/lib/browser/chat-tree-view/index.js.map +1 -0
  78. package/lib/browser/chat-view-commands.d.ts +8 -0
  79. package/lib/browser/chat-view-commands.d.ts.map +1 -0
  80. package/lib/browser/chat-view-commands.js +44 -0
  81. package/lib/browser/chat-view-commands.js.map +1 -0
  82. package/lib/browser/chat-view-contribution.d.ts +18 -0
  83. package/lib/browser/chat-view-contribution.d.ts.map +1 -0
  84. package/lib/browser/chat-view-contribution.js +153 -0
  85. package/lib/browser/chat-view-contribution.js.map +1 -0
  86. package/lib/browser/chat-view-language-contribution.d.ts +20 -0
  87. package/lib/browser/chat-view-language-contribution.d.ts.map +1 -0
  88. package/lib/browser/chat-view-language-contribution.js +98 -0
  89. package/lib/browser/chat-view-language-contribution.js.map +1 -0
  90. package/lib/browser/chat-view-widget-toolbar-contribution.d.ts +11 -0
  91. package/lib/browser/chat-view-widget-toolbar-contribution.d.ts.map +1 -0
  92. package/lib/browser/chat-view-widget-toolbar-contribution.js +65 -0
  93. package/lib/browser/chat-view-widget-toolbar-contribution.js.map +1 -0
  94. package/lib/browser/chat-view-widget.d.ts +41 -0
  95. package/lib/browser/chat-view-widget.d.ts.map +1 -0
  96. package/lib/browser/chat-view-widget.js +182 -0
  97. package/lib/browser/chat-view-widget.js.map +1 -0
  98. package/package.json +59 -0
  99. package/src/browser/ai-chat-ui-contribution.ts +171 -0
  100. package/src/browser/ai-chat-ui-frontend-module.ts +105 -0
  101. package/src/browser/chat-input-widget.tsx +262 -0
  102. package/src/browser/chat-node-toolbar-action-contribution.ts +63 -0
  103. package/src/browser/chat-response-part-renderer.ts +25 -0
  104. package/src/browser/chat-response-renderer/ai-editor-manager.ts +183 -0
  105. package/src/browser/chat-response-renderer/code-part-renderer.tsx +211 -0
  106. package/src/browser/chat-response-renderer/command-part-renderer.tsx +60 -0
  107. package/src/browser/chat-response-renderer/error-part-renderer.tsx +35 -0
  108. package/src/browser/chat-response-renderer/horizontal-layout-part-renderer.tsx +59 -0
  109. package/src/browser/chat-response-renderer/index.ts +23 -0
  110. package/src/browser/chat-response-renderer/markdown-part-renderer.tsx +92 -0
  111. package/src/browser/chat-response-renderer/question-part-renderer.tsx +59 -0
  112. package/src/browser/chat-response-renderer/text-part-renderer.spec.ts +50 -0
  113. package/src/browser/chat-response-renderer/text-part-renderer.tsx +35 -0
  114. package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +89 -0
  115. package/src/browser/chat-tree-view/chat-view-tree-container.ts +32 -0
  116. package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +526 -0
  117. package/src/browser/chat-tree-view/index.ts +18 -0
  118. package/src/browser/chat-view-commands.ts +45 -0
  119. package/src/browser/chat-view-contribution.ts +154 -0
  120. package/src/browser/chat-view-language-contribution.ts +141 -0
  121. package/src/browser/chat-view-widget-toolbar-contribution.tsx +54 -0
  122. package/src/browser/chat-view-widget.tsx +194 -0
  123. package/src/browser/style/index.css +415 -0
@@ -0,0 +1,89 @@
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
+
33
+ render(response: ToolCallChatResponseContent): ReactNode {
34
+ return (
35
+ <h4 className='theia-toolCall'>
36
+ {response.finished ? (
37
+ <details>
38
+ <summary>Ran {response.name}
39
+ ({this.renderCollapsibleArguments(response.arguments)})
40
+ </summary>
41
+ <pre>{this.tryPrettyPrintJson(response)}</pre>
42
+ </details>
43
+ ) : (
44
+ <span>
45
+ <Spinner /> Running {response.name}({this.renderCollapsibleArguments(response.arguments)})
46
+ </span>
47
+ )}
48
+ </h4>
49
+ );
50
+ }
51
+
52
+ protected renderCollapsibleArguments(args: string | undefined): ReactNode {
53
+ if (!args || !args.trim() || args.trim() === '{}') {
54
+ return undefined;
55
+ }
56
+
57
+ return (
58
+ <details className="collapsible-arguments">
59
+ <summary className="collapsible-arguments-summary">...</summary>
60
+ <span>{this.prettyPrintArgs(args)}</span>
61
+ </details>
62
+ );
63
+ }
64
+
65
+ private prettyPrintArgs(args: string): string {
66
+ try {
67
+ return JSON.stringify(JSON.parse(args), undefined, 2);
68
+ } catch (e) {
69
+ // fall through
70
+ return args;
71
+ }
72
+ }
73
+
74
+ private tryPrettyPrintJson(response: ToolCallChatResponseContent): string | undefined {
75
+ let responseContent = response.result;
76
+ try {
77
+ if (response.result) {
78
+ responseContent = JSON.stringify(JSON.parse(response.result), undefined, 2);
79
+ }
80
+ } catch (e) {
81
+ // fall through
82
+ }
83
+ return responseContent;
84
+ }
85
+ }
86
+
87
+ const Spinner = () => (
88
+ <i className="fa fa-spinner fa-spin"></i>
89
+ );
@@ -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,526 @@
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
+ ChatAgent,
18
+ ChatAgentService,
19
+ ChatModel,
20
+ ChatProgressMessage,
21
+ ChatRequestModel,
22
+ ChatResponseContent,
23
+ ChatResponseModel,
24
+ ParsedChatRequestAgentPart,
25
+ ParsedChatRequestVariablePart,
26
+ } from '@theia/ai-chat';
27
+ import { CommandRegistry, ContributionProvider } from '@theia/core';
28
+ import {
29
+ codicon,
30
+ CommonCommands,
31
+ CompositeTreeNode,
32
+ ContextMenuRenderer,
33
+ HoverService,
34
+ Key,
35
+ KeyCode,
36
+ NodeProps,
37
+ TreeModel,
38
+ TreeNode,
39
+ TreeProps,
40
+ TreeWidget,
41
+ } from '@theia/core/lib/browser';
42
+ import {
43
+ inject,
44
+ injectable,
45
+ named,
46
+ postConstruct
47
+ } from '@theia/core/shared/inversify';
48
+ import * as React from '@theia/core/shared/react';
49
+
50
+ import { ChatNodeToolbarActionContribution } from '../chat-node-toolbar-action-contribution';
51
+ import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
52
+ import { useMarkdownRendering } from '../chat-response-renderer/markdown-part-renderer';
53
+ import { AIVariableService } from '@theia/ai-core';
54
+
55
+ // TODO Instead of directly operating on the ChatRequestModel we could use an intermediate view model
56
+ export interface RequestNode extends TreeNode {
57
+ request: ChatRequestModel
58
+ }
59
+ export const isRequestNode = (node: TreeNode): node is RequestNode => 'request' in node;
60
+
61
+ // TODO Instead of directly operating on the ChatResponseModel we could use an intermediate view model
62
+ export interface ResponseNode extends TreeNode {
63
+ response: ChatResponseModel
64
+ }
65
+ export const isResponseNode = (node: TreeNode): node is ResponseNode => 'response' in node;
66
+
67
+ function isEnterKey(e: React.KeyboardEvent): boolean {
68
+ return Key.ENTER.keyCode === KeyCode.createKeyCode(e.nativeEvent).key?.keyCode;
69
+ }
70
+
71
+ @injectable()
72
+ export class ChatViewTreeWidget extends TreeWidget {
73
+ static readonly ID = 'chat-tree-widget';
74
+ static readonly CONTEXT_MENU = ['chat-tree-context-menu'];
75
+
76
+ @inject(ContributionProvider) @named(ChatResponsePartRenderer)
77
+ protected readonly chatResponsePartRenderers: ContributionProvider<ChatResponsePartRenderer<ChatResponseContent>>;
78
+
79
+ @inject(ContributionProvider) @named(ChatNodeToolbarActionContribution)
80
+ protected readonly chatNodeToolbarActionContributions: ContributionProvider<ChatNodeToolbarActionContribution>;
81
+
82
+ @inject(ChatAgentService)
83
+ protected chatAgentService: ChatAgentService;
84
+
85
+ @inject(AIVariableService)
86
+ protected readonly variableService: AIVariableService;
87
+
88
+ @inject(CommandRegistry)
89
+ private commandRegistry: CommandRegistry;
90
+
91
+ @inject(HoverService)
92
+ private hoverService: HoverService;
93
+
94
+ protected _shouldScrollToEnd = true;
95
+
96
+ protected isEnabled = false;
97
+
98
+ set shouldScrollToEnd(shouldScrollToEnd: boolean) {
99
+ this._shouldScrollToEnd = shouldScrollToEnd;
100
+ this.shouldScrollToRow = this._shouldScrollToEnd;
101
+ }
102
+
103
+ get shouldScrollToEnd(): boolean {
104
+ return this._shouldScrollToEnd;
105
+ }
106
+
107
+ constructor(
108
+ @inject(TreeProps) props: TreeProps,
109
+ @inject(TreeModel) model: TreeModel,
110
+ @inject(ContextMenuRenderer) contextMenuRenderer: ContextMenuRenderer
111
+ ) {
112
+ super(props, model, contextMenuRenderer);
113
+
114
+ this.id = ChatViewTreeWidget.ID;
115
+ this.title.closable = false;
116
+
117
+ model.root = {
118
+ id: 'ChatTree',
119
+ name: 'ChatRootNode',
120
+ parent: undefined,
121
+ visible: false,
122
+ children: [],
123
+ } as CompositeTreeNode;
124
+ }
125
+
126
+ @postConstruct()
127
+ protected override init(): void {
128
+ super.init();
129
+
130
+ this.id = ChatViewTreeWidget.ID + '-treeContainer';
131
+ this.addClass('treeContainer');
132
+ }
133
+
134
+ public setEnabled(enabled: boolean): void {
135
+ this.isEnabled = enabled;
136
+ this.update();
137
+ }
138
+
139
+ protected override renderTree(model: TreeModel): React.ReactNode {
140
+ if (this.isEnabled) {
141
+ return super.renderTree(model);
142
+ }
143
+ return this.renderDisabledMessage();
144
+ }
145
+
146
+ private renderDisabledMessage(): React.ReactNode {
147
+ return <div className={'theia-ResponseNode'}>
148
+ <div className='theia-ResponseNode-Content' key={'disabled-message'}>
149
+ <div className="disable-message">
150
+ <span className="section-header"> 🚀 Experimental AI Feature Available!</span>
151
+ <div className="section-title">
152
+ <p><code>Currently, all AI Features are disabled!</code></p>
153
+ </div>
154
+ <div className="section-title">
155
+ <p>How to Enable Experimental AI Features:</p>
156
+ </div>
157
+ <div className="section-content">
158
+ <p>To enable the experimental AI features, please go to &nbsp;
159
+ {this.renderLinkButton('the settings menu', CommonCommands.OPEN_PREFERENCES.id)}
160
+ &nbsp;and locate the <strong>AI Features</strong> section.</p>
161
+ <ol>
162
+ <li>Toggle the switch for <strong>'Ai-features: Enable'</strong>.</li>
163
+ <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>
164
+ for more information.</li>
165
+ </ol>
166
+ <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>
167
+ </div>
168
+
169
+ <div className="section-title">
170
+ <p>Currently Supported Views and Features:</p>
171
+ </div>
172
+ <div className="section-content">
173
+ <p>Once the experimental AI features are enabled, you can access the following views and features:</p>
174
+ <ul>
175
+ <li>Code Completion</li>
176
+ <li>Terminal Assistance (via CTRL+I in a terminal)</li>
177
+ <li>This Chat View (features the following agents):
178
+ <ul>
179
+ <li>Universal Chat Agent</li>
180
+ <li>Workspace Chat Agent</li>
181
+ <li>Command Chat Agent</li>
182
+ <li>Orchestrator Chat Agent</li>
183
+ </ul>
184
+ </li>
185
+ <li>{this.renderLinkButton('AI History View', 'aiHistory:open')}</li>
186
+ <li>{this.renderLinkButton('AI Configuration View', 'aiConfiguration:open')}</li>
187
+ </ul>
188
+ <p>See <a href="https://theia-ide.org/docs/user_ai/" target="_blank">the documentation</a> for more information.</p>
189
+ </div>
190
+ </div>
191
+ </div>
192
+ </div >;
193
+ }
194
+
195
+ private renderLinkButton(title: string, openCommandId: string): React.ReactNode {
196
+ return <a
197
+ role={'button'}
198
+ tabIndex={0}
199
+ onClick={() => this.commandRegistry.executeCommand(openCommandId)}
200
+ onKeyDown={e => isEnterKey(e) && this.commandRegistry.executeCommand(openCommandId)}>
201
+ {title}
202
+ </a>;
203
+ }
204
+
205
+ private mapRequestToNode(request: ChatRequestModel): RequestNode {
206
+ return {
207
+ id: request.id,
208
+ parent: this.model.root as CompositeTreeNode,
209
+ request
210
+ };
211
+ }
212
+
213
+ private mapResponseToNode(response: ChatResponseModel): ResponseNode {
214
+ return {
215
+ id: response.id,
216
+ parent: this.model.root as CompositeTreeNode,
217
+ response
218
+ };
219
+ }
220
+
221
+ /**
222
+ * Tracks the ChatModel handed over.
223
+ * Tracking multiple chat models will result in a weird UI
224
+ */
225
+ public trackChatModel(chatModel: ChatModel): void {
226
+ this.recreateModelTree(chatModel);
227
+ chatModel.getRequests().forEach(request => {
228
+ if (!request.response.isComplete) {
229
+ request.response.onDidChange(() => this.scheduleUpdateScrollToRow());
230
+ }
231
+ });
232
+ this.toDispose.push(
233
+ chatModel.onDidChange(event => {
234
+ this.recreateModelTree(chatModel);
235
+ if (event.kind === 'addRequest' && !event.request.response.isComplete) {
236
+ event.request.response.onDidChange(() => this.scheduleUpdateScrollToRow());
237
+ }
238
+ })
239
+ );
240
+ }
241
+
242
+ protected override getScrollToRow(): number | undefined {
243
+ if (this.shouldScrollToEnd) {
244
+ return this.rows.size;
245
+ }
246
+ return super.getScrollToRow();
247
+ }
248
+
249
+ private async recreateModelTree(chatModel: ChatModel): Promise<void> {
250
+ if (CompositeTreeNode.is(this.model.root)) {
251
+ const nodes: TreeNode[] = [];
252
+ chatModel.getRequests().forEach(request => {
253
+ nodes.push(this.mapRequestToNode(request));
254
+ nodes.push(this.mapResponseToNode(request.response));
255
+ });
256
+ this.model.root.children = nodes;
257
+ this.model.refresh();
258
+ }
259
+ }
260
+
261
+ protected override renderNode(
262
+ node: TreeNode,
263
+ props: NodeProps
264
+ ): React.ReactNode {
265
+ if (!TreeNode.isVisible(node)) {
266
+ return undefined;
267
+ }
268
+ if (!(isRequestNode(node) || isResponseNode(node))) {
269
+ return super.renderNode(node, props);
270
+ }
271
+ return <React.Fragment key={node.id}>
272
+ <div className='theia-ChatNode' onContextMenu={e => this.handleContextMenu(node, e)}>
273
+ {this.renderAgent(node)}
274
+ {this.renderDetail(node)}
275
+ </div>
276
+ </React.Fragment>;
277
+ }
278
+
279
+ private renderAgent(node: RequestNode | ResponseNode): React.ReactNode {
280
+ const inProgress = isResponseNode(node) && !node.response.isComplete && !node.response.isCanceled && !node.response.isError;
281
+ const waitingForInput = isResponseNode(node) && node.response.isWaitingForInput;
282
+ const toolbarContributions = !inProgress
283
+ ? this.chatNodeToolbarActionContributions.getContributions()
284
+ .flatMap(c => c.getToolbarActions(node))
285
+ .filter(action => this.commandRegistry.isEnabled(action.commandId, node))
286
+ .sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0))
287
+ : [];
288
+ const agentLabel = React.createRef<HTMLHeadingElement>();
289
+ const agentDescription = this.getAgent(node)?.description;
290
+ return <React.Fragment>
291
+ <div className='theia-ChatNodeHeader'>
292
+ <div className={`theia-AgentAvatar ${this.getAgentIconClassName(node)}`}></div>
293
+ <h3 ref={agentLabel}
294
+ className='theia-AgentLabel'
295
+ onMouseEnter={() => {
296
+ if (agentDescription) {
297
+ this.hoverService.requestHover({
298
+ content: agentDescription,
299
+ target: agentLabel.current!,
300
+ position: 'right'
301
+ });
302
+ }
303
+ }}>
304
+ {this.getAgentLabel(node)}
305
+ </h3>
306
+ {inProgress && <span className='theia-ChatContentInProgress'>Generating</span>}
307
+ {inProgress && waitingForInput && <span className='theia-ChatContentInProgress'>Waiting for input</span>}
308
+ <div className='theia-ChatNodeToolbar'>
309
+ {!inProgress &&
310
+ toolbarContributions.length > 0 &&
311
+ toolbarContributions.map(action =>
312
+ <span
313
+ key={action.commandId}
314
+ className={`theia-ChatNodeToolbarAction ${action.icon}`}
315
+ title={action.tooltip}
316
+ onClick={e => {
317
+ e.stopPropagation();
318
+ this.commandRegistry.executeCommand(action.commandId, node);
319
+ }}
320
+ onKeyDown={e => {
321
+ if (isEnterKey(e)) {
322
+ e.stopPropagation();
323
+ this.commandRegistry.executeCommand(action.commandId, node);
324
+ }
325
+ }}
326
+ role='button'
327
+ ></span>
328
+ )}
329
+ </div>
330
+ </div>
331
+ </React.Fragment>;
332
+ }
333
+
334
+ private getAgentLabel(node: RequestNode | ResponseNode): string {
335
+ if (isRequestNode(node)) {
336
+ // TODO find user name
337
+ return 'You';
338
+ }
339
+ return this.getAgent(node)?.name ?? 'AI';
340
+ }
341
+
342
+ private getAgent(node: RequestNode | ResponseNode): ChatAgent | undefined {
343
+ if (isRequestNode(node)) {
344
+ return undefined;
345
+ }
346
+ return node.response.agentId ? this.chatAgentService.getAgent(node.response.agentId) : undefined;
347
+ }
348
+
349
+ private getAgentIconClassName(node: RequestNode | ResponseNode): string | undefined {
350
+ if (isRequestNode(node)) {
351
+ return codicon('account');
352
+ }
353
+
354
+ const agent = node.response.agentId ? this.chatAgentService.getAgent(node.response.agentId) : undefined;
355
+ return agent?.iconClass ?? codicon('copilot');
356
+ }
357
+
358
+ private renderDetail(node: RequestNode | ResponseNode): React.ReactNode {
359
+ if (isRequestNode(node)) {
360
+ return this.renderChatRequest(node);
361
+ }
362
+ if (isResponseNode(node)) {
363
+ return this.renderChatResponse(node);
364
+ };
365
+ }
366
+
367
+ private renderChatRequest(node: RequestNode): React.ReactNode {
368
+ return <ChatRequestRender
369
+ node={node}
370
+ hoverService={this.hoverService}
371
+ chatAgentService={this.chatAgentService}
372
+ variableService={this.variableService}
373
+ />;
374
+ }
375
+
376
+ private renderChatResponse(node: ResponseNode): React.ReactNode {
377
+ return (
378
+ <div className={'theia-ResponseNode'}>
379
+ {!node.response.isComplete
380
+ && node.response.response.content.length === 0
381
+ && node.response.progressMessages
382
+ .filter(c => c.show === 'untilFirstContent')
383
+ .map((c, i) =>
384
+ <ProgressMessage {...c} key={`${node.id}-progress-untilFirstContent-${i}`} />
385
+ )
386
+ }
387
+ {node.response.response.content.map((c, i) =>
388
+ <div className='theia-ResponseNode-Content' key={`${node.id}-content-${i}`}>{this.getChatResponsePartRenderer(c, node)}</div>
389
+ )}
390
+ {!node.response.isComplete
391
+ && node.response.progressMessages
392
+ .filter(c => c.show === 'whileIncomplete')
393
+ .map((c, i) =>
394
+ <ProgressMessage {...c} key={`${node.id}-progress-whileIncomplete-${i}`} />
395
+ )
396
+ }
397
+ {node.response.progressMessages
398
+ .filter(c => c.show === 'forever')
399
+ .map((c, i) =>
400
+ <ProgressMessage {...c} key={`${node.id}-progress-afterComplete-${i}`} />
401
+ )
402
+ }
403
+ </div>
404
+ );
405
+ }
406
+
407
+ private getChatResponsePartRenderer(content: ChatResponseContent, node: ResponseNode): React.ReactNode {
408
+ const renderer = this.chatResponsePartRenderers.getContributions().reduce<[number, ChatResponsePartRenderer<ChatResponseContent> | undefined]>(
409
+ (prev, current) => {
410
+ const prio = current.canHandle(content);
411
+ if (prio > prev[0]) {
412
+ return [prio, current];
413
+ } return prev;
414
+ },
415
+ [-1, undefined])[1];
416
+ if (!renderer) {
417
+ console.error('No renderer found for content', content);
418
+ return <div>Error: No renderer found</div>;
419
+ }
420
+ return renderer.render(content, node);
421
+ }
422
+
423
+ protected handleContextMenu(node: TreeNode | undefined, event: React.MouseEvent<HTMLElement>): void {
424
+ this.contextMenuRenderer.render({
425
+ menuPath: ChatViewTreeWidget.CONTEXT_MENU,
426
+ anchor: { x: event.clientX, y: event.clientY },
427
+ args: [node]
428
+ });
429
+ event.preventDefault();
430
+ }
431
+ }
432
+
433
+ const ChatRequestRender = (
434
+ {
435
+ node, hoverService, chatAgentService, variableService
436
+ }: {
437
+ node: RequestNode,
438
+ hoverService: HoverService,
439
+ chatAgentService: ChatAgentService,
440
+ variableService: AIVariableService
441
+ }) => {
442
+ const parts = node.request.message.parts;
443
+ return (
444
+ <div className="theia-RequestNode">
445
+ <p>
446
+ {parts.map((part, index) => {
447
+ if (part instanceof ParsedChatRequestAgentPart || part instanceof ParsedChatRequestVariablePart) {
448
+ let description = undefined;
449
+ let className = '';
450
+ if (part instanceof ParsedChatRequestAgentPart) {
451
+ description = chatAgentService.getAgent(part.agentId)?.description;
452
+ className = 'theia-RequestNode-AgentLabel';
453
+ } else if (part instanceof ParsedChatRequestVariablePart) {
454
+ description = variableService.getVariable(part.variableName)?.description;
455
+ className = 'theia-RequestNode-VariableLabel';
456
+ }
457
+ return (
458
+ <HoverableLabel
459
+ key={index}
460
+ text={part.text}
461
+ description={description}
462
+ hoverService={hoverService}
463
+ className={className}
464
+ />
465
+ );
466
+ } else {
467
+ // maintain the leading and trailing spaces with explicit `&nbsp;`, otherwise they would get trimmed by the markdown renderer
468
+ const ref = useMarkdownRendering(part.text.replace(/^\s|\s$/g, '&nbsp;'), true);
469
+ return (
470
+ <span key={index} ref={ref}></span>
471
+ );
472
+ }
473
+ })}
474
+ </p>
475
+ </div>
476
+ );
477
+ };
478
+
479
+ const HoverableLabel = (
480
+ {
481
+ text, description, hoverService, className
482
+ }: {
483
+ text: string,
484
+ description?: string,
485
+ hoverService: HoverService,
486
+ className: string
487
+ }) => {
488
+ const spanRef = React.createRef<HTMLSpanElement>();
489
+ return (
490
+ <span
491
+ className={className}
492
+ ref={spanRef}
493
+ onMouseEnter={() => {
494
+ if (description) {
495
+ hoverService.requestHover({
496
+ content: description,
497
+ target: spanRef.current!,
498
+ position: 'right'
499
+ });
500
+ }
501
+ }}
502
+ >
503
+ {text}
504
+ </span>
505
+ );
506
+ };
507
+
508
+ const ProgressMessage = (c: ChatProgressMessage) => (
509
+ <div className='theia-ResponseNode-ProgressMessage'>
510
+ <Indicator {...c} /> {c.content}
511
+ </div>
512
+ );
513
+
514
+ const Indicator = (progressMessage: ChatProgressMessage) => (
515
+ <span className='theia-ResponseNode-ProgressMessage-Indicator'>
516
+ {progressMessage.status === 'inProgress' &&
517
+ <i className={'fa fa-spinner fa-spin ' + progressMessage.status}></i>
518
+ }
519
+ {progressMessage.status === 'completed' &&
520
+ <i className={'fa fa-check ' + progressMessage.status}></i>
521
+ }
522
+ {progressMessage.status === 'failed' &&
523
+ <i className={'fa fa-warning ' + progressMessage.status}></i>
524
+ }
525
+ </span>
526
+ );
@@ -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';