@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.
Files changed (79) hide show
  1. package/lib/browser/ai-chat-ui-contribution.d.ts +5 -1
  2. package/lib/browser/ai-chat-ui-contribution.d.ts.map +1 -1
  3. package/lib/browser/ai-chat-ui-contribution.js +102 -4
  4. package/lib/browser/ai-chat-ui-contribution.js.map +1 -1
  5. package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -1
  6. package/lib/browser/ai-chat-ui-frontend-module.js +30 -1
  7. package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -1
  8. package/lib/browser/chat-input-agent-suggestions.d.ts +11 -0
  9. package/lib/browser/chat-input-agent-suggestions.d.ts.map +1 -0
  10. package/lib/browser/chat-input-agent-suggestions.js +76 -0
  11. package/lib/browser/chat-input-agent-suggestions.js.map +1 -0
  12. package/lib/browser/chat-input-widget.d.ts +17 -6
  13. package/lib/browser/chat-input-widget.d.ts.map +1 -1
  14. package/lib/browser/chat-input-widget.js +72 -22
  15. package/lib/browser/chat-input-widget.js.map +1 -1
  16. package/lib/browser/chat-node-toolbar-action-contribution.d.ts +8 -0
  17. package/lib/browser/chat-node-toolbar-action-contribution.d.ts.map +1 -1
  18. package/lib/browser/chat-node-toolbar-action-contribution.js +55 -1
  19. package/lib/browser/chat-node-toolbar-action-contribution.js.map +1 -1
  20. package/lib/browser/chat-progress-message.d.ts +7 -0
  21. package/lib/browser/chat-progress-message.d.ts.map +1 -0
  22. package/lib/browser/chat-progress-message.js +33 -0
  23. package/lib/browser/chat-progress-message.js.map +1 -0
  24. package/lib/browser/chat-response-renderer/code-part-renderer.js +1 -1
  25. package/lib/browser/chat-response-renderer/code-part-renderer.js.map +1 -1
  26. package/lib/browser/chat-response-renderer/index.d.ts +1 -0
  27. package/lib/browser/chat-response-renderer/index.d.ts.map +1 -1
  28. package/lib/browser/chat-response-renderer/index.js +1 -0
  29. package/lib/browser/chat-response-renderer/index.js.map +1 -1
  30. package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts +7 -1
  31. package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts.map +1 -1
  32. package/lib/browser/chat-response-renderer/markdown-part-renderer.js +14 -3
  33. package/lib/browser/chat-response-renderer/markdown-part-renderer.js.map +1 -1
  34. package/lib/browser/chat-response-renderer/progress-part-renderer.d.ts +9 -0
  35. package/lib/browser/chat-response-renderer/progress-part-renderer.d.ts.map +1 -0
  36. package/lib/browser/chat-response-renderer/progress-part-renderer.js +39 -0
  37. package/lib/browser/chat-response-renderer/progress-part-renderer.js.map +1 -0
  38. package/lib/browser/chat-tree-view/chat-view-tree-input-widget.d.ts +33 -0
  39. package/lib/browser/chat-tree-view/chat-view-tree-input-widget.d.ts.map +1 -0
  40. package/lib/browser/chat-tree-view/chat-view-tree-input-widget.js +79 -0
  41. package/lib/browser/chat-tree-view/chat-view-tree-input-widget.js.map +1 -0
  42. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts +20 -4
  43. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts.map +1 -1
  44. package/lib/browser/chat-tree-view/chat-view-tree-widget.js +184 -48
  45. package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -1
  46. package/lib/browser/chat-view-commands.d.ts +3 -0
  47. package/lib/browser/chat-view-commands.d.ts.map +1 -1
  48. package/lib/browser/chat-view-commands.js +15 -0
  49. package/lib/browser/chat-view-commands.js.map +1 -1
  50. package/lib/browser/chat-view-contribution.d.ts +1 -0
  51. package/lib/browser/chat-view-contribution.d.ts.map +1 -1
  52. package/lib/browser/chat-view-contribution.js +16 -14
  53. package/lib/browser/chat-view-contribution.js.map +1 -1
  54. package/lib/browser/chat-view-language-contribution.d.ts +3 -3
  55. package/lib/browser/chat-view-language-contribution.d.ts.map +1 -1
  56. package/lib/browser/chat-view-language-contribution.js +9 -22
  57. package/lib/browser/chat-view-language-contribution.js.map +1 -1
  58. package/lib/browser/chat-view-widget.d.ts +6 -2
  59. package/lib/browser/chat-view-widget.d.ts.map +1 -1
  60. package/lib/browser/chat-view-widget.js +36 -19
  61. package/lib/browser/chat-view-widget.js.map +1 -1
  62. package/package.json +12 -12
  63. package/src/browser/ai-chat-ui-contribution.ts +93 -6
  64. package/src/browser/ai-chat-ui-frontend-module.ts +33 -3
  65. package/src/browser/chat-input-agent-suggestions.tsx +85 -0
  66. package/src/browser/chat-input-widget.tsx +122 -32
  67. package/src/browser/chat-node-toolbar-action-contribution.ts +40 -1
  68. package/src/browser/chat-progress-message.tsx +40 -0
  69. package/src/browser/chat-response-renderer/code-part-renderer.tsx +3 -3
  70. package/src/browser/chat-response-renderer/index.ts +1 -0
  71. package/src/browser/chat-response-renderer/markdown-part-renderer.tsx +19 -2
  72. package/src/browser/chat-response-renderer/progress-part-renderer.tsx +40 -0
  73. package/src/browser/chat-tree-view/chat-view-tree-input-widget.tsx +89 -0
  74. package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +200 -37
  75. package/src/browser/chat-view-commands.ts +18 -0
  76. package/src/browser/chat-view-contribution.ts +20 -16
  77. package/src/browser/chat-view-language-contribution.ts +10 -24
  78. package/src/browser/chat-view-widget.tsx +18 -5
  79. 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 { CommandRegistry, ContributionProvider } from '@theia/core';
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 { AIVariableService } from '@theia/ai-core';
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(request: ChatRequestModel): RequestNode {
208
+ protected mapRequestToNode(branch: ChatHierarchyBranch): RequestNode {
172
209
  return {
173
- id: request.id,
174
210
  parent: this.model.root as CompositeTreeNode,
175
- request
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.toDispose.push(
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.getRequests().forEach(request => {
219
- nodes.push(this.mapRequestToNode(request));
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
- // maintain the leading and trailing spaces with explicit `&nbsp;`, otherwise they would get trimmed by the markdown renderer
438
- const ref = useMarkdownRendering(part.text.replace(/^\s|\s$/g, '&nbsp;'), openerService, true);
614
+ const ref = useMarkdownRendering(
615
+ part.text
616
+ .replace(/^[\r\n]+|[\r\n]+$/g, '') // remove excessive new lines
617
+ .replace(/(^ )/g, '&nbsp;'), // 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 { ChatViewTreeWidget, isRequestNode, isResponseNode, RequestNode, ResponseNode } from './chat-tree-view/chat-view-tree-widget';
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 hasEditorAsFirstArg(args: unknown[]): args is [MonacoEditor, ...unknown[]] {
149
- return args.length > 0 && args[0] instanceof MonacoEditor;
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)[] {