@theia/ai-chat-ui 1.60.2 → 1.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/ai-chat-ui-contribution.d.ts +5 -1
- package/lib/browser/ai-chat-ui-contribution.d.ts.map +1 -1
- package/lib/browser/ai-chat-ui-contribution.js +102 -4
- package/lib/browser/ai-chat-ui-contribution.js.map +1 -1
- package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-chat-ui-frontend-module.js +30 -1
- package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -1
- package/lib/browser/chat-input-agent-suggestions.d.ts +11 -0
- package/lib/browser/chat-input-agent-suggestions.d.ts.map +1 -0
- package/lib/browser/chat-input-agent-suggestions.js +76 -0
- package/lib/browser/chat-input-agent-suggestions.js.map +1 -0
- package/lib/browser/chat-input-widget.d.ts +17 -6
- package/lib/browser/chat-input-widget.d.ts.map +1 -1
- package/lib/browser/chat-input-widget.js +72 -22
- package/lib/browser/chat-input-widget.js.map +1 -1
- package/lib/browser/chat-node-toolbar-action-contribution.d.ts +8 -0
- package/lib/browser/chat-node-toolbar-action-contribution.d.ts.map +1 -1
- package/lib/browser/chat-node-toolbar-action-contribution.js +55 -1
- package/lib/browser/chat-node-toolbar-action-contribution.js.map +1 -1
- package/lib/browser/chat-progress-message.d.ts +7 -0
- package/lib/browser/chat-progress-message.d.ts.map +1 -0
- package/lib/browser/chat-progress-message.js +33 -0
- package/lib/browser/chat-progress-message.js.map +1 -0
- package/lib/browser/chat-response-renderer/code-part-renderer.js +1 -1
- package/lib/browser/chat-response-renderer/code-part-renderer.js.map +1 -1
- package/lib/browser/chat-response-renderer/index.d.ts +1 -0
- package/lib/browser/chat-response-renderer/index.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/index.js +1 -0
- package/lib/browser/chat-response-renderer/index.js.map +1 -1
- package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts +7 -1
- package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/markdown-part-renderer.js +14 -3
- package/lib/browser/chat-response-renderer/markdown-part-renderer.js.map +1 -1
- package/lib/browser/chat-response-renderer/progress-part-renderer.d.ts +9 -0
- package/lib/browser/chat-response-renderer/progress-part-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/progress-part-renderer.js +39 -0
- package/lib/browser/chat-response-renderer/progress-part-renderer.js.map +1 -0
- package/lib/browser/chat-tree-view/chat-view-tree-input-widget.d.ts +33 -0
- package/lib/browser/chat-tree-view/chat-view-tree-input-widget.d.ts.map +1 -0
- package/lib/browser/chat-tree-view/chat-view-tree-input-widget.js +79 -0
- package/lib/browser/chat-tree-view/chat-view-tree-input-widget.js.map +1 -0
- package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts +20 -4
- package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts.map +1 -1
- package/lib/browser/chat-tree-view/chat-view-tree-widget.js +184 -48
- package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -1
- package/lib/browser/chat-view-commands.d.ts +3 -0
- package/lib/browser/chat-view-commands.d.ts.map +1 -1
- package/lib/browser/chat-view-commands.js +15 -0
- package/lib/browser/chat-view-commands.js.map +1 -1
- package/lib/browser/chat-view-contribution.d.ts +1 -0
- package/lib/browser/chat-view-contribution.d.ts.map +1 -1
- package/lib/browser/chat-view-contribution.js +16 -14
- package/lib/browser/chat-view-contribution.js.map +1 -1
- package/lib/browser/chat-view-language-contribution.d.ts +3 -3
- package/lib/browser/chat-view-language-contribution.d.ts.map +1 -1
- package/lib/browser/chat-view-language-contribution.js +9 -22
- package/lib/browser/chat-view-language-contribution.js.map +1 -1
- package/lib/browser/chat-view-widget.d.ts +6 -2
- package/lib/browser/chat-view-widget.d.ts.map +1 -1
- package/lib/browser/chat-view-widget.js +36 -19
- package/lib/browser/chat-view-widget.js.map +1 -1
- package/package.json +12 -12
- package/src/browser/ai-chat-ui-contribution.ts +93 -6
- package/src/browser/ai-chat-ui-frontend-module.ts +33 -3
- package/src/browser/chat-input-agent-suggestions.tsx +85 -0
- package/src/browser/chat-input-widget.tsx +122 -32
- package/src/browser/chat-node-toolbar-action-contribution.ts +40 -1
- package/src/browser/chat-progress-message.tsx +40 -0
- package/src/browser/chat-response-renderer/code-part-renderer.tsx +3 -3
- package/src/browser/chat-response-renderer/index.ts +1 -0
- package/src/browser/chat-response-renderer/markdown-part-renderer.tsx +19 -2
- package/src/browser/chat-response-renderer/progress-part-renderer.tsx +40 -0
- package/src/browser/chat-tree-view/chat-view-tree-input-widget.tsx +89 -0
- package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +200 -37
- package/src/browser/chat-view-commands.ts +18 -0
- package/src/browser/chat-view-contribution.ts +20 -16
- package/src/browser/chat-view-language-contribution.ts +10 -24
- package/src/browser/chat-view-widget.tsx +18 -5
- package/src/browser/style/index.css +58 -4
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 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, ProgressChatResponseContent } from '@theia/ai-chat/lib/common';
|
|
20
|
+
import { ReactNode } from '@theia/core/shared/react';
|
|
21
|
+
import * as React from '@theia/core/shared/react';
|
|
22
|
+
import { ProgressMessage } from '../chat-progress-message';
|
|
23
|
+
|
|
24
|
+
@injectable()
|
|
25
|
+
export class ProgressPartRenderer implements ChatResponsePartRenderer<ProgressChatResponseContent> {
|
|
26
|
+
|
|
27
|
+
canHandle(response: ChatResponseContent): number {
|
|
28
|
+
if (ProgressChatResponseContent.is(response)) {
|
|
29
|
+
return 10;
|
|
30
|
+
}
|
|
31
|
+
return -1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
render(response: ProgressChatResponseContent): ReactNode {
|
|
35
|
+
return (
|
|
36
|
+
<ProgressMessage content={response.message} status='inProgress' />
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 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 { inject, injectable, optional, postConstruct } from '@theia/core/shared/inversify';
|
|
18
|
+
import { AIChatInputWidget, type AIChatInputConfiguration } from '../chat-input-widget';
|
|
19
|
+
import type { EditableRequestNode } from './chat-view-tree-widget';
|
|
20
|
+
import { URI } from '@theia/core';
|
|
21
|
+
import { CHAT_VIEW_LANGUAGE_EXTENSION } from '../chat-view-language-contribution';
|
|
22
|
+
import type { ChatRequestModel, EditableChatRequestModel } from '@theia/ai-chat';
|
|
23
|
+
import type { AIVariableResolutionRequest } from '@theia/ai-core';
|
|
24
|
+
import { Key } from '@theia/core/lib/browser';
|
|
25
|
+
|
|
26
|
+
export const AIChatTreeInputConfiguration = Symbol('AIChatTreeInputConfiguration');
|
|
27
|
+
export interface AIChatTreeInputConfiguration extends AIChatInputConfiguration { }
|
|
28
|
+
|
|
29
|
+
export const AIChatTreeInputArgs = Symbol('AIChatTreeInputArgs');
|
|
30
|
+
export interface AIChatTreeInputArgs {
|
|
31
|
+
node: EditableRequestNode;
|
|
32
|
+
initialValue?: string;
|
|
33
|
+
onQuery: (query: string) => Promise<void>;
|
|
34
|
+
onUnpin?: () => void;
|
|
35
|
+
onCancel?: (requestModel: ChatRequestModel) => void;
|
|
36
|
+
onDeleteChangeSet?: (requestModel: ChatRequestModel) => void;
|
|
37
|
+
onDeleteChangeSetElement?: (requestModel: ChatRequestModel, index: number) => void;
|
|
38
|
+
}
|
|
39
|
+
export const AIChatTreeInputFactory = Symbol('AIChatTreeInputFactory');
|
|
40
|
+
export type AIChatTreeInputFactory = (args: AIChatTreeInputArgs) => AIChatTreeInputWidget;
|
|
41
|
+
|
|
42
|
+
@injectable()
|
|
43
|
+
export class AIChatTreeInputWidget extends AIChatInputWidget {
|
|
44
|
+
public static override ID = 'chat-tree-input-widget';
|
|
45
|
+
|
|
46
|
+
@inject(AIChatTreeInputArgs)
|
|
47
|
+
protected readonly args: AIChatTreeInputArgs;
|
|
48
|
+
|
|
49
|
+
@inject(AIChatTreeInputConfiguration) @optional()
|
|
50
|
+
protected override readonly configuration: AIChatTreeInputConfiguration | undefined;
|
|
51
|
+
|
|
52
|
+
get requestNode(): EditableRequestNode {
|
|
53
|
+
return this.args.node;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get request(): EditableChatRequestModel {
|
|
57
|
+
return this.requestNode.request;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@postConstruct()
|
|
61
|
+
protected override init(): void {
|
|
62
|
+
super.init();
|
|
63
|
+
this.addKeyListener(this.node, Key.ESCAPE, () => {
|
|
64
|
+
this.request.cancelEdit();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
this.editorReady.promise.then(() => {
|
|
68
|
+
if (this.editorRef) {
|
|
69
|
+
this.editorRef.focus();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
protected override getResourceUri(): URI {
|
|
75
|
+
return new URI(`ai-chat:/${this.requestNode.id}-input.${CHAT_VIEW_LANGUAGE_EXTENSION}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
override addContext(variable: AIVariableResolutionRequest): void {
|
|
79
|
+
this.request.editContextManager.addVariables(variable);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
protected override getContext(): readonly AIVariableResolutionRequest[] {
|
|
83
|
+
return this.request.editContextManager.getVariables();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
protected override deleteContextElement(index: number): void {
|
|
87
|
+
this.request.editContextManager.deleteVariables(index);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -17,14 +17,19 @@ import {
|
|
|
17
17
|
ChatAgent,
|
|
18
18
|
ChatAgentService,
|
|
19
19
|
ChatModel,
|
|
20
|
-
ChatProgressMessage,
|
|
21
20
|
ChatRequestModel,
|
|
22
21
|
ChatResponseContent,
|
|
23
22
|
ChatResponseModel,
|
|
23
|
+
ChatService,
|
|
24
|
+
EditableChatRequestModel,
|
|
24
25
|
ParsedChatRequestAgentPart,
|
|
25
26
|
ParsedChatRequestVariablePart,
|
|
27
|
+
type ChatRequest,
|
|
28
|
+
type ChatHierarchyBranch,
|
|
26
29
|
} from '@theia/ai-chat';
|
|
27
|
-
import {
|
|
30
|
+
import { AIVariableService } from '@theia/ai-core';
|
|
31
|
+
import { AIActivationService } from '@theia/ai-core/lib/browser';
|
|
32
|
+
import { CommandRegistry, ContributionProvider, Disposable, DisposableCollection, Emitter } from '@theia/core';
|
|
28
33
|
import {
|
|
29
34
|
codicon,
|
|
30
35
|
CompositeTreeNode,
|
|
@@ -38,7 +43,10 @@ import {
|
|
|
38
43
|
TreeNode,
|
|
39
44
|
TreeProps,
|
|
40
45
|
TreeWidget,
|
|
46
|
+
Widget,
|
|
47
|
+
type ReactWidget
|
|
41
48
|
} from '@theia/core/lib/browser';
|
|
49
|
+
import { nls } from '@theia/core/lib/common/nls';
|
|
42
50
|
import {
|
|
43
51
|
inject,
|
|
44
52
|
injectable,
|
|
@@ -47,19 +55,24 @@ import {
|
|
|
47
55
|
postConstruct
|
|
48
56
|
} from '@theia/core/shared/inversify';
|
|
49
57
|
import * as React from '@theia/core/shared/react';
|
|
50
|
-
import { nls } from '@theia/core/lib/common/nls';
|
|
51
|
-
|
|
52
58
|
import { ChatNodeToolbarActionContribution } from '../chat-node-toolbar-action-contribution';
|
|
53
59
|
import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
|
|
54
60
|
import { useMarkdownRendering } from '../chat-response-renderer/markdown-part-renderer';
|
|
55
|
-
import {
|
|
61
|
+
import { ProgressMessage } from '../chat-progress-message';
|
|
62
|
+
import { AIChatTreeInputFactory, type AIChatTreeInputWidget } from './chat-view-tree-input-widget';
|
|
56
63
|
|
|
57
64
|
// TODO Instead of directly operating on the ChatRequestModel we could use an intermediate view model
|
|
58
65
|
export interface RequestNode extends TreeNode {
|
|
59
|
-
request: ChatRequestModel
|
|
66
|
+
request: ChatRequestModel,
|
|
67
|
+
branch: ChatHierarchyBranch
|
|
60
68
|
}
|
|
61
69
|
export const isRequestNode = (node: TreeNode): node is RequestNode => 'request' in node;
|
|
62
70
|
|
|
71
|
+
export interface EditableRequestNode extends RequestNode {
|
|
72
|
+
request: EditableChatRequestModel
|
|
73
|
+
}
|
|
74
|
+
export const isEditableRequestNode = (node: TreeNode): node is EditableRequestNode => isRequestNode(node) && EditableChatRequestModel.is(node.request);
|
|
75
|
+
|
|
63
76
|
// TODO Instead of directly operating on the ChatResponseModel we could use an intermediate view model
|
|
64
77
|
export interface ResponseNode extends TreeNode {
|
|
65
78
|
response: ChatResponseModel
|
|
@@ -105,6 +118,20 @@ export class ChatViewTreeWidget extends TreeWidget {
|
|
|
105
118
|
@inject(ChatWelcomeMessageProvider) @optional()
|
|
106
119
|
protected welcomeMessageProvider?: ChatWelcomeMessageProvider;
|
|
107
120
|
|
|
121
|
+
@inject(AIChatTreeInputFactory)
|
|
122
|
+
protected inputWidgetFactory: AIChatTreeInputFactory;
|
|
123
|
+
|
|
124
|
+
@inject(AIActivationService)
|
|
125
|
+
protected readonly activationService: AIActivationService;
|
|
126
|
+
|
|
127
|
+
@inject(ChatService)
|
|
128
|
+
protected readonly chatService: ChatService;
|
|
129
|
+
|
|
130
|
+
protected readonly onDidSubmitEditEmitter = new Emitter<ChatRequest>();
|
|
131
|
+
onDidSubmitEdit = this.onDidSubmitEditEmitter.event;
|
|
132
|
+
|
|
133
|
+
protected readonly chatInputs: Map<string, AIChatTreeInputWidget> = new Map();
|
|
134
|
+
|
|
108
135
|
protected _shouldScrollToEnd = true;
|
|
109
136
|
|
|
110
137
|
protected isEnabled = false;
|
|
@@ -143,6 +170,16 @@ export class ChatViewTreeWidget extends TreeWidget {
|
|
|
143
170
|
|
|
144
171
|
this.id = ChatViewTreeWidget.ID + '-treeContainer';
|
|
145
172
|
this.addClass('treeContainer');
|
|
173
|
+
|
|
174
|
+
this.toDispose.pushAll([
|
|
175
|
+
this.toDisposeOnChatModelChange,
|
|
176
|
+
this.activationService.onDidChangeActiveStatus(change => {
|
|
177
|
+
this.chatInputs.forEach(widget => {
|
|
178
|
+
widget.setEnabled(change);
|
|
179
|
+
});
|
|
180
|
+
this.update();
|
|
181
|
+
})
|
|
182
|
+
]);
|
|
146
183
|
}
|
|
147
184
|
|
|
148
185
|
public setEnabled(enabled: boolean): void {
|
|
@@ -168,11 +205,16 @@ export class ChatViewTreeWidget extends TreeWidget {
|
|
|
168
205
|
return this.welcomeMessageProvider?.renderWelcomeMessage?.() ?? <></>;
|
|
169
206
|
}
|
|
170
207
|
|
|
171
|
-
protected mapRequestToNode(
|
|
208
|
+
protected mapRequestToNode(branch: ChatHierarchyBranch): RequestNode {
|
|
172
209
|
return {
|
|
173
|
-
id: request.id,
|
|
174
210
|
parent: this.model.root as CompositeTreeNode,
|
|
175
|
-
|
|
211
|
+
get id(): string {
|
|
212
|
+
return this.request.id;
|
|
213
|
+
},
|
|
214
|
+
get request(): ChatRequestModel {
|
|
215
|
+
return branch.get();
|
|
216
|
+
},
|
|
217
|
+
branch
|
|
176
218
|
};
|
|
177
219
|
}
|
|
178
220
|
|
|
@@ -184,25 +226,60 @@ export class ChatViewTreeWidget extends TreeWidget {
|
|
|
184
226
|
};
|
|
185
227
|
}
|
|
186
228
|
|
|
229
|
+
protected readonly toDisposeOnChatModelChange = new DisposableCollection();
|
|
187
230
|
/**
|
|
188
231
|
* Tracks the ChatModel handed over.
|
|
189
232
|
* Tracking multiple chat models will result in a weird UI
|
|
190
233
|
*/
|
|
191
234
|
public trackChatModel(chatModel: ChatModel): void {
|
|
235
|
+
this.toDisposeOnChatModelChange.dispose();
|
|
192
236
|
this.recreateModelTree(chatModel);
|
|
193
237
|
chatModel.getRequests().forEach(request => {
|
|
194
238
|
if (!request.response.isComplete) {
|
|
195
239
|
request.response.onDidChange(() => this.scheduleUpdateScrollToRow());
|
|
196
240
|
}
|
|
197
241
|
});
|
|
198
|
-
this.
|
|
242
|
+
this.toDisposeOnChatModelChange.pushAll([
|
|
243
|
+
Disposable.create(() => {
|
|
244
|
+
this.chatInputs.forEach(widget => widget.dispose());
|
|
245
|
+
this.chatInputs.clear();
|
|
246
|
+
}),
|
|
199
247
|
chatModel.onDidChange(event => {
|
|
248
|
+
if (event.kind === 'enableEdit') {
|
|
249
|
+
this.scrollToRow = this.rows.get(event.request.id)?.index;
|
|
250
|
+
this.update();
|
|
251
|
+
return;
|
|
252
|
+
} else if (event.kind === 'cancelEdit') {
|
|
253
|
+
this.disposeChatInputWidget(event.request);
|
|
254
|
+
this.scrollToRow = undefined;
|
|
255
|
+
this.update();
|
|
256
|
+
return;
|
|
257
|
+
} else if (event.kind === 'changeHierarchyBranch') {
|
|
258
|
+
this.scrollToRow = undefined;
|
|
259
|
+
}
|
|
260
|
+
|
|
200
261
|
this.recreateModelTree(chatModel);
|
|
262
|
+
|
|
201
263
|
if (event.kind === 'addRequest' && !event.request.response.isComplete) {
|
|
202
264
|
event.request.response.onDidChange(() => this.scheduleUpdateScrollToRow());
|
|
265
|
+
} else if (event.kind === 'submitEdit') {
|
|
266
|
+
event.branch.succeedingBranches().forEach(branch => {
|
|
267
|
+
this.disposeChatInputWidget(branch.get());
|
|
268
|
+
});
|
|
269
|
+
this.onDidSubmitEditEmitter.fire(
|
|
270
|
+
event.newRequest,
|
|
271
|
+
);
|
|
203
272
|
}
|
|
204
273
|
})
|
|
205
|
-
);
|
|
274
|
+
]);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
protected disposeChatInputWidget(request: ChatRequestModel): void {
|
|
278
|
+
const widget = this.chatInputs.get(request.id);
|
|
279
|
+
if (widget) {
|
|
280
|
+
widget.dispose();
|
|
281
|
+
this.chatInputs.delete(request.id);
|
|
282
|
+
}
|
|
206
283
|
}
|
|
207
284
|
|
|
208
285
|
protected override getScrollToRow(): number | undefined {
|
|
@@ -215,8 +292,9 @@ export class ChatViewTreeWidget extends TreeWidget {
|
|
|
215
292
|
protected async recreateModelTree(chatModel: ChatModel): Promise<void> {
|
|
216
293
|
if (CompositeTreeNode.is(this.model.root)) {
|
|
217
294
|
const nodes: TreeNode[] = [];
|
|
218
|
-
chatModel.
|
|
219
|
-
|
|
295
|
+
chatModel.getBranches().forEach(branch => {
|
|
296
|
+
const request = branch.get();
|
|
297
|
+
nodes.push(this.mapRequestToNode(branch));
|
|
220
298
|
nodes.push(this.mapResponseToNode(request.response));
|
|
221
299
|
});
|
|
222
300
|
this.model.root.children = nodes;
|
|
@@ -338,6 +416,32 @@ export class ChatViewTreeWidget extends TreeWidget {
|
|
|
338
416
|
chatAgentService={this.chatAgentService}
|
|
339
417
|
variableService={this.variableService}
|
|
340
418
|
openerService={this.openerService}
|
|
419
|
+
provideChatInputWidget={() => {
|
|
420
|
+
const editableNode = node;
|
|
421
|
+
if (isEditableRequestNode(editableNode)) {
|
|
422
|
+
let widget = this.chatInputs.get(editableNode.id);
|
|
423
|
+
if (!widget) {
|
|
424
|
+
widget = this.inputWidgetFactory({
|
|
425
|
+
node: editableNode,
|
|
426
|
+
initialValue: editableNode.request.message.request.text,
|
|
427
|
+
onQuery: async query => {
|
|
428
|
+
editableNode.request.submitEdit({ text: query });
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
this.chatInputs.set(editableNode.id, widget);
|
|
433
|
+
|
|
434
|
+
widget.disposed.connect(() => {
|
|
435
|
+
this.chatInputs.delete(editableNode.id);
|
|
436
|
+
editableNode.request.cancelEdit();
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return widget;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return;
|
|
444
|
+
}}
|
|
341
445
|
/>;
|
|
342
446
|
}
|
|
343
447
|
|
|
@@ -397,19 +501,92 @@ export class ChatViewTreeWidget extends TreeWidget {
|
|
|
397
501
|
});
|
|
398
502
|
event.preventDefault();
|
|
399
503
|
}
|
|
504
|
+
|
|
505
|
+
protected override handleSpace(event: KeyboardEvent): boolean {
|
|
506
|
+
// We need to return false to prevent the handler within
|
|
507
|
+
// packages/core/src/browser/widgets/widget.ts
|
|
508
|
+
// Otherwise, the space key will never be handled by the monaco editor
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
interface WidgetContainerProps {
|
|
514
|
+
widget: ReactWidget;
|
|
400
515
|
}
|
|
401
516
|
|
|
517
|
+
const WidgetContainer: React.FC<WidgetContainerProps> = ({ widget }) => {
|
|
518
|
+
// eslint-disable-next-line no-null/no-null
|
|
519
|
+
const containerRef = React.useRef<HTMLDivElement | null>(null);
|
|
520
|
+
|
|
521
|
+
React.useEffect(() => {
|
|
522
|
+
if (containerRef.current && !widget.isAttached) {
|
|
523
|
+
Widget.attach(widget, containerRef.current);
|
|
524
|
+
}
|
|
525
|
+
}, [containerRef.current]);
|
|
526
|
+
|
|
527
|
+
// Clean up
|
|
528
|
+
React.useEffect(() =>
|
|
529
|
+
() => {
|
|
530
|
+
setTimeout(() => {
|
|
531
|
+
// Delay clean up to allow react to finish its rendering cycle
|
|
532
|
+
widget.clearFlag(Widget.Flag.IsAttached);
|
|
533
|
+
widget.dispose();
|
|
534
|
+
});
|
|
535
|
+
}, []);
|
|
536
|
+
|
|
537
|
+
return <div ref={containerRef} />;
|
|
538
|
+
};
|
|
539
|
+
|
|
402
540
|
const ChatRequestRender = (
|
|
403
541
|
{
|
|
404
|
-
node, hoverService, chatAgentService, variableService, openerService
|
|
542
|
+
node, hoverService, chatAgentService, variableService, openerService,
|
|
543
|
+
provideChatInputWidget
|
|
405
544
|
}: {
|
|
406
545
|
node: RequestNode,
|
|
407
546
|
hoverService: HoverService,
|
|
408
547
|
chatAgentService: ChatAgentService,
|
|
409
548
|
variableService: AIVariableService,
|
|
410
|
-
openerService: OpenerService
|
|
549
|
+
openerService: OpenerService,
|
|
550
|
+
provideChatInputWidget: () => ReactWidget | undefined,
|
|
411
551
|
}) => {
|
|
412
552
|
const parts = node.request.message.parts;
|
|
553
|
+
if (EditableChatRequestModel.isEditing(node.request)) {
|
|
554
|
+
const widget = provideChatInputWidget();
|
|
555
|
+
if (widget) {
|
|
556
|
+
return <div className="theia-RequestNode">
|
|
557
|
+
<WidgetContainer widget={widget}></WidgetContainer>
|
|
558
|
+
</div>;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const renderFooter = () => {
|
|
563
|
+
if (node.branch.items.length < 2) {
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const isFirst = node.branch.activeBranchIndex === 0;
|
|
568
|
+
const isLast = node.branch.activeBranchIndex === node.branch.items.length - 1;
|
|
569
|
+
|
|
570
|
+
return (
|
|
571
|
+
<div className='theia-RequestNode-Footer'>
|
|
572
|
+
<div className={`item ${isFirst ? '' : 'enabled'}`}>
|
|
573
|
+
<div className="codicon codicon-chevron-left action-label" title="Previous" onClick={() => {
|
|
574
|
+
node.branch.enablePrevious();
|
|
575
|
+
}}></div>
|
|
576
|
+
</div>
|
|
577
|
+
<small>
|
|
578
|
+
<span>{node.branch.activeBranchIndex + 1}/</span>
|
|
579
|
+
<span>{node.branch.items.length}</span>
|
|
580
|
+
</small>
|
|
581
|
+
<div className={`item ${isLast ? '' : 'enabled'}`}>
|
|
582
|
+
<div className='codicon codicon-chevron-right action-label' title="Next" onClick={() => {
|
|
583
|
+
node.branch.enableNext();
|
|
584
|
+
}}></div>
|
|
585
|
+
</div>
|
|
586
|
+
</div>
|
|
587
|
+
);
|
|
588
|
+
};
|
|
589
|
+
|
|
413
590
|
return (
|
|
414
591
|
<div className="theia-RequestNode">
|
|
415
592
|
<p>
|
|
@@ -434,14 +611,20 @@ const ChatRequestRender = (
|
|
|
434
611
|
/>
|
|
435
612
|
);
|
|
436
613
|
} else {
|
|
437
|
-
|
|
438
|
-
|
|
614
|
+
const ref = useMarkdownRendering(
|
|
615
|
+
part.text
|
|
616
|
+
.replace(/^[\r\n]+|[\r\n]+$/g, '') // remove excessive new lines
|
|
617
|
+
.replace(/(^ )/g, ' '), // enforce keeping space before
|
|
618
|
+
openerService,
|
|
619
|
+
true
|
|
620
|
+
);
|
|
439
621
|
return (
|
|
440
622
|
<span key={index} ref={ref}></span>
|
|
441
623
|
);
|
|
442
624
|
}
|
|
443
625
|
})}
|
|
444
626
|
</p>
|
|
627
|
+
{renderFooter()}
|
|
445
628
|
</div>
|
|
446
629
|
);
|
|
447
630
|
};
|
|
@@ -474,23 +657,3 @@ const HoverableLabel = (
|
|
|
474
657
|
</span>
|
|
475
658
|
);
|
|
476
659
|
};
|
|
477
|
-
|
|
478
|
-
const ProgressMessage = (c: ChatProgressMessage) => (
|
|
479
|
-
<div className='theia-ResponseNode-ProgressMessage'>
|
|
480
|
-
<Indicator {...c} /> {c.content}
|
|
481
|
-
</div>
|
|
482
|
-
);
|
|
483
|
-
|
|
484
|
-
const Indicator = (progressMessage: ChatProgressMessage) => (
|
|
485
|
-
<span className='theia-ResponseNode-ProgressMessage-Indicator'>
|
|
486
|
-
{progressMessage.status === 'inProgress' &&
|
|
487
|
-
<i className={'fa fa-spinner fa-spin ' + progressMessage.status}></i>
|
|
488
|
-
}
|
|
489
|
-
{progressMessage.status === 'completed' &&
|
|
490
|
-
<i className={'fa fa-check ' + progressMessage.status}></i>
|
|
491
|
-
}
|
|
492
|
-
{progressMessage.status === 'failed' &&
|
|
493
|
-
<i className={'fa fa-warning ' + progressMessage.status}></i>
|
|
494
|
-
}
|
|
495
|
-
</span>
|
|
496
|
-
);
|
|
@@ -38,6 +38,24 @@ export namespace ChatCommands {
|
|
|
38
38
|
category: CHAT_CATEGORY,
|
|
39
39
|
iconClass: codicon('bracket')
|
|
40
40
|
}, 'Set Session Settings', CHAT_CATEGORY_KEY);
|
|
41
|
+
|
|
42
|
+
export const AI_CHAT_NEW_WITH_TASK_CONTEXT: Command = {
|
|
43
|
+
id: 'ai-chat.new-with-task-context',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const AI_CHAT_SUMMARIZE_CURRENT_SESSION = Command.toLocalizedCommand({
|
|
47
|
+
id: 'ai-chat-summary-current-session',
|
|
48
|
+
iconClass: codicon('go-to-editing-session'),
|
|
49
|
+
label: 'Summarize Current Session',
|
|
50
|
+
category: CHAT_CATEGORY
|
|
51
|
+
}, undefined, CHAT_CATEGORY_KEY);
|
|
52
|
+
|
|
53
|
+
export const AI_CHAT_OPEN_SUMMARY_FOR_CURRENT_SESSION = Command.toLocalizedCommand({
|
|
54
|
+
id: 'ai-chat-open-current-session-summary',
|
|
55
|
+
iconClass: codicon('note'),
|
|
56
|
+
label: 'Open Current Session Summary',
|
|
57
|
+
category: CHAT_CATEGORY
|
|
58
|
+
}, undefined, CHAT_CATEGORY_KEY);
|
|
41
59
|
}
|
|
42
60
|
|
|
43
61
|
export const AI_CHAT_NEW_CHAT_WINDOW_COMMAND: Command = {
|
|
@@ -17,9 +17,11 @@ import { Command, CommandContribution, CommandRegistry, CommandService, isObject
|
|
|
17
17
|
import { CommonCommands, TreeNode } from '@theia/core/lib/browser';
|
|
18
18
|
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
|
19
19
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
ChatViewTreeWidget, isEditableRequestNode, isRequestNode,
|
|
22
|
+
isResponseNode, RequestNode, ResponseNode, type EditableRequestNode
|
|
23
|
+
} from './chat-tree-view/chat-view-tree-widget';
|
|
21
24
|
import { AIChatInputWidget } from './chat-input-widget';
|
|
22
|
-
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
|
23
25
|
|
|
24
26
|
export namespace ChatViewCommands {
|
|
25
27
|
export const COPY_MESSAGE = Command.toDefaultLocalizedCommand({
|
|
@@ -34,6 +36,10 @@ export namespace ChatViewCommands {
|
|
|
34
36
|
id: 'chat.copy.code',
|
|
35
37
|
label: 'Copy Code Block'
|
|
36
38
|
}, 'theia/ai/chat-ui/copyCodeBlock');
|
|
39
|
+
export const EDIT = Command.toLocalizedCommand({
|
|
40
|
+
id: 'chat.edit.request',
|
|
41
|
+
label: 'Edit'
|
|
42
|
+
}, 'theia/ai/chat-ui/editRequest');
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
@injectable()
|
|
@@ -56,17 +62,6 @@ export class ChatViewMenuContribution implements MenuContribution, CommandContri
|
|
|
56
62
|
},
|
|
57
63
|
isEnabled: (...args: unknown[]) => containsRequestOrResponseNode(args)
|
|
58
64
|
});
|
|
59
|
-
commands.registerHandler(CommonCommands.PASTE.id, {
|
|
60
|
-
execute: async (...args) => {
|
|
61
|
-
if (hasEditorAsFirstArg(args)) {
|
|
62
|
-
const editor = args[0];
|
|
63
|
-
const range = editor.selection;
|
|
64
|
-
const newText = await this.clipboardService.readText();
|
|
65
|
-
editor.executeEdits([{ range, newText }]);
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
isEnabled: (...args) => hasEditorAsFirstArg(args)
|
|
69
|
-
});
|
|
70
65
|
commands.registerCommand(ChatViewCommands.COPY_MESSAGE, {
|
|
71
66
|
execute: (...args: unknown[]) => {
|
|
72
67
|
if (containsRequestOrResponseNode(args)) {
|
|
@@ -102,6 +97,13 @@ export class ChatViewMenuContribution implements MenuContribution, CommandContri
|
|
|
102
97
|
},
|
|
103
98
|
isEnabled: (...args: unknown[]) => containsRequestOrResponseNode(args) && containsCode(args)
|
|
104
99
|
});
|
|
100
|
+
commands.registerCommand(ChatViewCommands.EDIT, {
|
|
101
|
+
execute: (...args: [EditableRequestNode, ...unknown[]]) => {
|
|
102
|
+
args[0].request.enableEdit();
|
|
103
|
+
},
|
|
104
|
+
isEnabled: (...args: unknown[]) => hasAsFirstArg(args, isEditableRequestNode) && !args[0].request.isEditing,
|
|
105
|
+
isVisible: (...args: unknown[]) => hasAsFirstArg(args, isEditableRequestNode) && !args[0].request.isEditing
|
|
106
|
+
});
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
protected copyMessage(args: (RequestNode | ResponseNode)[]): void {
|
|
@@ -135,6 +137,9 @@ export class ChatViewMenuContribution implements MenuContribution, CommandContri
|
|
|
135
137
|
menus.registerMenuAction([...ChatViewTreeWidget.CONTEXT_MENU, '_1'], {
|
|
136
138
|
commandId: ChatViewCommands.COPY_CODE.id
|
|
137
139
|
});
|
|
140
|
+
menus.registerMenuAction([...ChatViewTreeWidget.CONTEXT_MENU, '_1'], {
|
|
141
|
+
commandId: ChatViewCommands.EDIT.id
|
|
142
|
+
});
|
|
138
143
|
menus.registerMenuAction([...AIChatInputWidget.CONTEXT_MENU, '_1'], {
|
|
139
144
|
commandId: CommonCommands.COPY.id
|
|
140
145
|
});
|
|
@@ -142,11 +147,10 @@ export class ChatViewMenuContribution implements MenuContribution, CommandContri
|
|
|
142
147
|
commandId: CommonCommands.PASTE.id
|
|
143
148
|
});
|
|
144
149
|
}
|
|
145
|
-
|
|
146
150
|
}
|
|
147
151
|
|
|
148
|
-
function
|
|
149
|
-
return args.length > 0 && args[0]
|
|
152
|
+
function hasAsFirstArg<T>(args: unknown[], guard: (arg: unknown) => arg is T): args is [T, ...unknown[]] {
|
|
153
|
+
return args.length > 0 && guard(args[0]);
|
|
150
154
|
}
|
|
151
155
|
|
|
152
156
|
function extractRequestOrResponseNodes(args: unknown[]): (RequestNode | ResponseNode)[] {
|