@theia/ai-chat-ui 1.61.0-next.8 → 1.62.0-next.3
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 +29 -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-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/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-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 +180 -34
- 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 +31 -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-response-renderer/code-part-renderer.tsx +3 -3
- package/src/browser/chat-response-renderer/markdown-part-renderer.tsx +19 -2
- 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 +199 -16
- 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
|
@@ -20,10 +20,16 @@ import {
|
|
|
20
20
|
ChatRequestModel,
|
|
21
21
|
ChatResponseContent,
|
|
22
22
|
ChatResponseModel,
|
|
23
|
+
ChatService,
|
|
24
|
+
EditableChatRequestModel,
|
|
23
25
|
ParsedChatRequestAgentPart,
|
|
24
26
|
ParsedChatRequestVariablePart,
|
|
27
|
+
type ChatRequest,
|
|
28
|
+
type ChatHierarchyBranch,
|
|
25
29
|
} from '@theia/ai-chat';
|
|
26
|
-
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';
|
|
27
33
|
import {
|
|
28
34
|
codicon,
|
|
29
35
|
CompositeTreeNode,
|
|
@@ -37,7 +43,10 @@ import {
|
|
|
37
43
|
TreeNode,
|
|
38
44
|
TreeProps,
|
|
39
45
|
TreeWidget,
|
|
46
|
+
Widget,
|
|
47
|
+
type ReactWidget
|
|
40
48
|
} from '@theia/core/lib/browser';
|
|
49
|
+
import { nls } from '@theia/core/lib/common/nls';
|
|
41
50
|
import {
|
|
42
51
|
inject,
|
|
43
52
|
injectable,
|
|
@@ -46,20 +55,24 @@ import {
|
|
|
46
55
|
postConstruct
|
|
47
56
|
} from '@theia/core/shared/inversify';
|
|
48
57
|
import * as React from '@theia/core/shared/react';
|
|
49
|
-
import { nls } from '@theia/core/lib/common/nls';
|
|
50
|
-
|
|
51
58
|
import { ChatNodeToolbarActionContribution } from '../chat-node-toolbar-action-contribution';
|
|
52
59
|
import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
|
|
53
60
|
import { useMarkdownRendering } from '../chat-response-renderer/markdown-part-renderer';
|
|
54
|
-
import { AIVariableService } from '@theia/ai-core';
|
|
55
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
|
};
|
|
@@ -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)[] {
|
|
@@ -14,15 +14,15 @@
|
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
import { ChatAgentService } from '@theia/ai-chat';
|
|
17
|
-
import {
|
|
17
|
+
import { AIVariableService } from '@theia/ai-core/lib/common';
|
|
18
18
|
import { PromptText } from '@theia/ai-core/lib/common/prompt-text';
|
|
19
19
|
import { ToolInvocationRegistry } from '@theia/ai-core/lib/common/tool-invocation-registry';
|
|
20
20
|
import { MaybePromise, nls } from '@theia/core';
|
|
21
|
-
import {
|
|
21
|
+
import { FrontendApplication, FrontendApplicationContribution } from '@theia/core/lib/browser';
|
|
22
22
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
23
23
|
import * as monaco from '@theia/monaco-editor-core';
|
|
24
24
|
import { ProviderResult } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
|
|
25
|
-
import {
|
|
25
|
+
import { AIChatFrontendContribution, VARIABLE_ADD_CONTEXT_COMMAND } from '@theia/ai-chat/lib/browser/ai-chat-frontend-contribution';
|
|
26
26
|
|
|
27
27
|
export const CHAT_VIEW_LANGUAGE_ID = 'theia-ai-chat-view-language';
|
|
28
28
|
export const SETTINGS_LANGUAGE_ID = 'theia-ai-chat-settings-language';
|
|
@@ -30,7 +30,6 @@ export const CHAT_VIEW_LANGUAGE_EXTENSION = 'aichatviewlanguage';
|
|
|
30
30
|
|
|
31
31
|
const VARIABLE_RESOLUTION_CONTEXT = { context: 'chat-input-autocomplete' };
|
|
32
32
|
const VARIABLE_ARGUMENT_PICKER_COMMAND = 'trigger-variable-argument-picker';
|
|
33
|
-
const VARIABLE_ADD_CONTEXT_COMMAND = 'add-context-variable';
|
|
34
33
|
|
|
35
34
|
interface CompletionSource<T> {
|
|
36
35
|
triggerCharacter: string;
|
|
@@ -54,8 +53,8 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
|
|
|
54
53
|
@inject(ToolInvocationRegistry)
|
|
55
54
|
protected readonly toolInvocationRegistry: ToolInvocationRegistry;
|
|
56
55
|
|
|
57
|
-
@inject(
|
|
58
|
-
protected readonly
|
|
56
|
+
@inject(AIChatFrontendContribution)
|
|
57
|
+
protected readonly chatFrontendContribution: AIChatFrontendContribution;
|
|
59
58
|
|
|
60
59
|
onStart(_app: FrontendApplication): MaybePromise<void> {
|
|
61
60
|
monaco.languages.register({ id: CHAT_VIEW_LANGUAGE_ID, extensions: [CHAT_VIEW_LANGUAGE_EXTENSION] });
|
|
@@ -64,7 +63,6 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
|
|
|
64
63
|
this.registerCompletionProviders();
|
|
65
64
|
|
|
66
65
|
monaco.editor.registerCommand(VARIABLE_ARGUMENT_PICKER_COMMAND, this.triggerVariableArgumentPicker.bind(this));
|
|
67
|
-
monaco.editor.registerCommand(VARIABLE_ADD_CONTEXT_COMMAND, (_, ...args) => args.length > 1 ? this.addContextVariable(args[0], args[1]) : undefined);
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
protected registerCompletionProviders(): void {
|
|
@@ -205,12 +203,12 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
|
|
|
205
203
|
const items = await provider(model, position);
|
|
206
204
|
if (items) {
|
|
207
205
|
suggestions.push(...items.map(item => ({
|
|
208
|
-
...item,
|
|
209
206
|
command: {
|
|
210
|
-
title:
|
|
211
|
-
id: VARIABLE_ADD_CONTEXT_COMMAND,
|
|
207
|
+
title: VARIABLE_ADD_CONTEXT_COMMAND.label!,
|
|
208
|
+
id: VARIABLE_ADD_CONTEXT_COMMAND.id,
|
|
212
209
|
arguments: [variable.name, item.insertText]
|
|
213
|
-
}
|
|
210
|
+
},
|
|
211
|
+
...item,
|
|
214
212
|
})));
|
|
215
213
|
}
|
|
216
214
|
}
|
|
@@ -262,22 +260,10 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
|
|
|
262
260
|
text: arg
|
|
263
261
|
}]);
|
|
264
262
|
|
|
265
|
-
await this.addContextVariable(variableName, arg);
|
|
263
|
+
await this.chatFrontendContribution.addContextVariable(variableName, arg);
|
|
266
264
|
}
|
|
267
265
|
|
|
268
266
|
protected getCharacterBeforePosition(model: monaco.editor.ITextModel, position: monaco.Position): string {
|
|
269
267
|
return model.getLineContent(position.lineNumber)[position.column - 1 - 1];
|
|
270
268
|
}
|
|
271
|
-
|
|
272
|
-
protected async addContextVariable(variableName: string, arg: string | undefined): Promise<void> {
|
|
273
|
-
const variable = this.variableService.getVariable(variableName);
|
|
274
|
-
if (!variable || !AIContextVariable.is(variable)) {
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const widget = this.shell.getWidgetById(ChatViewWidget.ID);
|
|
279
|
-
if (widget instanceof ChatViewWidget) {
|
|
280
|
-
widget.addContext({ variable, arg });
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
269
|
}
|
|
@@ -22,6 +22,8 @@ import { AIChatInputWidget } from './chat-input-widget';
|
|
|
22
22
|
import { ChatViewTreeWidget } from './chat-tree-view/chat-view-tree-widget';
|
|
23
23
|
import { AIActivationService } from '@theia/ai-core/lib/browser/ai-activation-service';
|
|
24
24
|
import { AIVariableResolutionRequest } from '@theia/ai-core';
|
|
25
|
+
import { ProgressBarFactory } from '@theia/core/lib/browser/progress-bar-factory';
|
|
26
|
+
import { FrontendVariableService } from '@theia/ai-core/lib/browser';
|
|
25
27
|
|
|
26
28
|
export namespace ChatViewWidget {
|
|
27
29
|
export interface State {
|
|
@@ -50,6 +52,12 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
|
|
|
50
52
|
@inject(AIActivationService)
|
|
51
53
|
protected readonly activationService: AIActivationService;
|
|
52
54
|
|
|
55
|
+
@inject(FrontendVariableService)
|
|
56
|
+
protected readonly variableService: FrontendVariableService;
|
|
57
|
+
|
|
58
|
+
@inject(ProgressBarFactory)
|
|
59
|
+
protected readonly progressBarFactory: ProgressBarFactory;
|
|
60
|
+
|
|
53
61
|
protected chatSession: ChatSession;
|
|
54
62
|
|
|
55
63
|
protected _state: ChatViewWidget.State = { locked: false };
|
|
@@ -110,10 +118,11 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
|
|
|
110
118
|
this.inputWidget.setEnabled(change);
|
|
111
119
|
this.update();
|
|
112
120
|
});
|
|
121
|
+
this.toDispose.push(this.progressBarFactory({ container: this.node, insertMode: 'prepend', locationId: 'ai-chat' }));
|
|
113
122
|
}
|
|
114
123
|
|
|
115
124
|
protected initListeners(): void {
|
|
116
|
-
this.toDispose.
|
|
125
|
+
this.toDispose.pushAll([
|
|
117
126
|
this.chatService.onSessionEvent(event => {
|
|
118
127
|
if (!isActiveSessionChangedEvent(event)) {
|
|
119
128
|
return;
|
|
@@ -130,8 +139,12 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
|
|
|
130
139
|
} else {
|
|
131
140
|
console.warn(`Session with ${event.sessionId} not found.`);
|
|
132
141
|
}
|
|
142
|
+
}),
|
|
143
|
+
// The chat view needs to handle the submission of the edit request
|
|
144
|
+
this.treeWidget.onDidSubmitEdit(request => {
|
|
145
|
+
this.onQuery(request);
|
|
133
146
|
})
|
|
134
|
-
);
|
|
147
|
+
]);
|
|
135
148
|
}
|
|
136
149
|
|
|
137
150
|
protected override onActivateRequest(msg: Message): void {
|
|
@@ -164,10 +177,10 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
|
|
|
164
177
|
return this.onStateChangedEmitter.event;
|
|
165
178
|
}
|
|
166
179
|
|
|
167
|
-
protected async onQuery(query: string): Promise<void> {
|
|
168
|
-
|
|
180
|
+
protected async onQuery(query: string | ChatRequest): Promise<void> {
|
|
181
|
+
const chatRequest: ChatRequest = typeof query === 'string' ? { text: query } : { ...query };
|
|
182
|
+
if (chatRequest.text.length === 0) { return; }
|
|
169
183
|
|
|
170
|
-
const chatRequest: ChatRequest = { text: query };
|
|
171
184
|
const requestProgress = await this.chatService.sendRequest(this.chatSession.id, chatRequest);
|
|
172
185
|
requestProgress?.responseCompleted.then(responseModel => {
|
|
173
186
|
if (responseModel.isError) {
|