@theia/ai-chat-ui 1.61.0-next.8 → 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 (64) 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 +29 -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-response-renderer/code-part-renderer.js +1 -1
  21. package/lib/browser/chat-response-renderer/code-part-renderer.js.map +1 -1
  22. package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts +7 -1
  23. package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts.map +1 -1
  24. package/lib/browser/chat-response-renderer/markdown-part-renderer.js +14 -3
  25. package/lib/browser/chat-response-renderer/markdown-part-renderer.js.map +1 -1
  26. package/lib/browser/chat-tree-view/chat-view-tree-input-widget.d.ts +33 -0
  27. package/lib/browser/chat-tree-view/chat-view-tree-input-widget.d.ts.map +1 -0
  28. package/lib/browser/chat-tree-view/chat-view-tree-input-widget.js +79 -0
  29. package/lib/browser/chat-tree-view/chat-view-tree-input-widget.js.map +1 -0
  30. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts +20 -4
  31. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts.map +1 -1
  32. package/lib/browser/chat-tree-view/chat-view-tree-widget.js +180 -34
  33. package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -1
  34. package/lib/browser/chat-view-commands.d.ts +3 -0
  35. package/lib/browser/chat-view-commands.d.ts.map +1 -1
  36. package/lib/browser/chat-view-commands.js +15 -0
  37. package/lib/browser/chat-view-commands.js.map +1 -1
  38. package/lib/browser/chat-view-contribution.d.ts +1 -0
  39. package/lib/browser/chat-view-contribution.d.ts.map +1 -1
  40. package/lib/browser/chat-view-contribution.js +16 -14
  41. package/lib/browser/chat-view-contribution.js.map +1 -1
  42. package/lib/browser/chat-view-language-contribution.d.ts +3 -3
  43. package/lib/browser/chat-view-language-contribution.d.ts.map +1 -1
  44. package/lib/browser/chat-view-language-contribution.js +9 -22
  45. package/lib/browser/chat-view-language-contribution.js.map +1 -1
  46. package/lib/browser/chat-view-widget.d.ts +6 -2
  47. package/lib/browser/chat-view-widget.d.ts.map +1 -1
  48. package/lib/browser/chat-view-widget.js +36 -19
  49. package/lib/browser/chat-view-widget.js.map +1 -1
  50. package/package.json +12 -12
  51. package/src/browser/ai-chat-ui-contribution.ts +93 -6
  52. package/src/browser/ai-chat-ui-frontend-module.ts +31 -3
  53. package/src/browser/chat-input-agent-suggestions.tsx +85 -0
  54. package/src/browser/chat-input-widget.tsx +122 -32
  55. package/src/browser/chat-node-toolbar-action-contribution.ts +40 -1
  56. package/src/browser/chat-response-renderer/code-part-renderer.tsx +3 -3
  57. package/src/browser/chat-response-renderer/markdown-part-renderer.tsx +19 -2
  58. package/src/browser/chat-tree-view/chat-view-tree-input-widget.tsx +89 -0
  59. package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +199 -16
  60. package/src/browser/chat-view-commands.ts +18 -0
  61. package/src/browser/chat-view-contribution.ts +20 -16
  62. package/src/browser/chat-view-language-contribution.ts +10 -24
  63. package/src/browser/chat-view-widget.tsx +18 -5
  64. package/src/browser/style/index.css +58 -4
@@ -15,9 +15,13 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { inject, injectable } from '@theia/core/shared/inversify';
18
- import { CommandRegistry, isOSX, nls, QuickInputButton, QuickInputService, QuickPickItem } from '@theia/core';
18
+ import { CommandRegistry, Emitter, isOSX, MessageService, nls, QuickInputButton, QuickInputService, QuickPickItem } from '@theia/core';
19
19
  import { Widget } from '@theia/core/lib/browser';
20
- import { AI_CHAT_NEW_CHAT_WINDOW_COMMAND, AI_CHAT_SHOW_CHATS_COMMAND, ChatCommands } from './chat-view-commands';
20
+ import {
21
+ AI_CHAT_NEW_CHAT_WINDOW_COMMAND,
22
+ AI_CHAT_SHOW_CHATS_COMMAND,
23
+ ChatCommands
24
+ } from './chat-view-commands';
21
25
  import { ChatAgentLocation, ChatService } from '@theia/ai-chat';
22
26
  import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
23
27
  import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
@@ -28,6 +32,10 @@ import { formatDistance } from 'date-fns';
28
32
  import * as locales from 'date-fns/locale';
29
33
  import { AI_SHOW_SETTINGS_COMMAND } from '@theia/ai-core/lib/browser';
30
34
  import { OPEN_AI_HISTORY_VIEW } from '@theia/ai-history/lib/browser/ai-history-contribution';
35
+ import { ChatNodeToolbarCommands } from './chat-node-toolbar-action-contribution';
36
+ import { isEditableRequestNode, type EditableRequestNode } from './chat-tree-view';
37
+ import { TASK_CONTEXT_VARIABLE } from '@theia/ai-chat/lib/browser/task-context-variable';
38
+ import { TaskContextService } from '@theia/ai-chat/lib/browser/task-context-service';
31
39
 
32
40
  export const AI_CHAT_TOGGLE_COMMAND_ID = 'aiChat:toggle';
33
41
 
@@ -38,6 +46,10 @@ export class AIChatContribution extends AbstractViewContribution<ChatViewWidget>
38
46
  protected readonly chatService: ChatService;
39
47
  @inject(QuickInputService)
40
48
  protected readonly quickInputService: QuickInputService;
49
+ @inject(TaskContextService)
50
+ protected readonly taskContextService: TaskContextService;
51
+ @inject(MessageService)
52
+ protected readonly messageService: MessageService;
41
53
 
42
54
  protected static readonly RENAME_CHAT_BUTTON: QuickInputButton = {
43
55
  iconClass: 'codicon-edit',
@@ -83,14 +95,66 @@ export class AIChatContribution extends AbstractViewContribution<ChatViewWidget>
83
95
  })
84
96
  });
85
97
  registry.registerCommand(AI_CHAT_NEW_CHAT_WINDOW_COMMAND, {
86
- execute: () => this.chatService.createSession(ChatAgentLocation.Panel, { focus: true }),
87
- isEnabled: widget => this.withWidget(widget, () => true),
98
+ execute: () => this.openView().then(() => this.chatService.createSession(ChatAgentLocation.Panel, { focus: true })),
88
99
  isVisible: widget => this.withWidget(widget, () => true),
89
100
  });
101
+ registry.registerCommand(ChatCommands.AI_CHAT_NEW_WITH_TASK_CONTEXT, {
102
+ execute: async () => {
103
+ const activeSession = this.chatService.getActiveSession();
104
+ const id = await this.summarizeActiveSession();
105
+ if (!id || !activeSession) { return; }
106
+ const newSession = this.chatService.createSession(ChatAgentLocation.Panel, { focus: true }, activeSession.pinnedAgent);
107
+ const summaryVariable = { variable: TASK_CONTEXT_VARIABLE, arg: id };
108
+ newSession.model.context.addVariables(summaryVariable);
109
+ },
110
+ isVisible: () => false
111
+ });
112
+ registry.registerCommand(ChatCommands.AI_CHAT_SUMMARIZE_CURRENT_SESSION, {
113
+ execute: async () => this.summarizeActiveSession(),
114
+ isVisible: widget => {
115
+ if (widget && !this.withWidget(widget)) { return false; }
116
+ const activeSession = this.chatService.getActiveSession();
117
+ return activeSession?.model.location === ChatAgentLocation.Panel
118
+ && !this.taskContextService.hasSummary(activeSession);
119
+ },
120
+ isEnabled: widget => {
121
+ if (widget && !this.withWidget(widget)) { return false; }
122
+ const activeSession = this.chatService.getActiveSession();
123
+ return activeSession?.model.location === ChatAgentLocation.Panel
124
+ && !activeSession.model.isEmpty()
125
+ && !this.taskContextService.hasSummary(activeSession);
126
+ }
127
+ });
128
+ registry.registerCommand(ChatCommands.AI_CHAT_OPEN_SUMMARY_FOR_CURRENT_SESSION, {
129
+ execute: async () => {
130
+ const id = await this.summarizeActiveSession();
131
+ if (!id) { return; }
132
+ await this.taskContextService.open(id);
133
+ },
134
+ isVisible: widget => {
135
+ if (widget && !this.withWidget(widget)) { return false; }
136
+ const activeSession = this.chatService.getActiveSession();
137
+ return !!activeSession && this.taskContextService.hasSummary(activeSession);
138
+ }
139
+ });
90
140
  registry.registerCommand(AI_CHAT_SHOW_CHATS_COMMAND, {
91
141
  execute: () => this.selectChat(),
92
- isEnabled: widget => this.withWidget(widget, () => true) && this.chatService.getSessions().length > 1,
93
- isVisible: widget => this.withWidget(widget, () => true)
142
+ isEnabled: widget => this.withWidget(widget) && this.chatService.getSessions().length > 1,
143
+ isVisible: widget => this.withWidget(widget)
144
+ });
145
+ registry.registerCommand(ChatNodeToolbarCommands.EDIT, {
146
+ isEnabled: node => isEditableRequestNode(node) && !node.request.isEditing,
147
+ isVisible: node => isEditableRequestNode(node) && !node.request.isEditing,
148
+ execute: (node: EditableRequestNode) => {
149
+ node.request.enableEdit();
150
+ }
151
+ });
152
+ registry.registerCommand(ChatNodeToolbarCommands.CANCEL, {
153
+ isEnabled: node => isEditableRequestNode(node) && node.request.isEditing,
154
+ isVisible: node => isEditableRequestNode(node) && node.request.isEditing,
155
+ execute: (node: EditableRequestNode) => {
156
+ node.request.cancelEdit();
157
+ }
94
158
  });
95
159
  }
96
160
 
@@ -123,6 +187,19 @@ export class AIChatContribution extends AbstractViewContribution<ChatViewWidget>
123
187
  priority: 1,
124
188
  isVisible: widget => this.withWidget(widget),
125
189
  });
190
+ const sessionSummarizibilityChangedEmitter = new Emitter<void>();
191
+ this.taskContextService.onDidChange(() => sessionSummarizibilityChangedEmitter.fire());
192
+ this.chatService.onSessionEvent(event => event.type === 'activeChange' && sessionSummarizibilityChangedEmitter.fire());
193
+ registry.registerItem({
194
+ id: 'chat-view.' + ChatCommands.AI_CHAT_SUMMARIZE_CURRENT_SESSION.id,
195
+ command: ChatCommands.AI_CHAT_SUMMARIZE_CURRENT_SESSION.id,
196
+ onDidChange: sessionSummarizibilityChangedEmitter.event
197
+ });
198
+ registry.registerItem({
199
+ id: 'chat-view.' + ChatCommands.AI_CHAT_OPEN_SUMMARY_FOR_CURRENT_SESSION.id,
200
+ command: ChatCommands.AI_CHAT_OPEN_SUMMARY_FOR_CURRENT_SESSION.id,
201
+ onDidChange: sessionSummarizibilityChangedEmitter.event
202
+ });
126
203
  }
127
204
 
128
205
  protected async selectChat(sessionId?: string): Promise<void> {
@@ -211,6 +288,16 @@ export class AIChatContribution extends AbstractViewContribution<ChatViewWidget>
211
288
  canExtractChatView(chatView: ChatViewWidget): boolean {
212
289
  return !chatView.secondaryWindow;
213
290
  }
291
+
292
+ protected async summarizeActiveSession(): Promise<string | undefined> {
293
+ const activeSession = this.chatService.getActiveSession();
294
+ if (!activeSession) { return; }
295
+ return this.taskContextService.summarize(activeSession).catch(err => {
296
+ console.warn('Error while summarizing session:', err);
297
+ this.messageService.error('Unable to summarize current session. Please confirm that the summary agent is not disabled.');
298
+ return undefined;
299
+ });
300
+ }
214
301
  }
215
302
 
216
303
  function getDateFnsLocale(): locales.Locale {
@@ -22,7 +22,7 @@ import { ContainerModule, interfaces } from '@theia/core/shared/inversify';
22
22
  import { EditorSelectionResolver } from '@theia/editor/lib/browser/editor-manager';
23
23
  import { AIChatContribution } from './ai-chat-ui-contribution';
24
24
  import { AIChatInputConfiguration, AIChatInputWidget } from './chat-input-widget';
25
- import { ChatNodeToolbarActionContribution } from './chat-node-toolbar-action-contribution';
25
+ import { ChatNodeToolbarActionContribution, DefaultChatNodeToolbarActionContribution } from './chat-node-toolbar-action-contribution';
26
26
  import { ChatResponsePartRenderer } from './chat-response-part-renderer';
27
27
  import {
28
28
  CodePartRenderer,
@@ -52,6 +52,7 @@ import { ChatViewWidgetToolbarContribution } from './chat-view-widget-toolbar-co
52
52
  import { ContextVariablePicker } from './context-variable-picker';
53
53
  import { ChangeSetActionRenderer, ChangeSetActionService } from './change-set-actions/change-set-action-service';
54
54
  import { ChangeSetAcceptAction } from './change-set-actions/change-set-accept-action';
55
+ import { AIChatTreeInputArgs, AIChatTreeInputConfiguration, AIChatTreeInputFactory, AIChatTreeInputWidget } from './chat-tree-view/chat-view-tree-input-widget';
55
56
 
56
57
  export default new ContainerModule((bind, _unbind, _isBound, rebind) => {
57
58
  bindViewContribution(bind, AIChatContribution);
@@ -64,8 +65,9 @@ export default new ContainerModule((bind, _unbind, _isBound, rebind) => {
64
65
  bind(AIChatInputWidget).toSelf();
65
66
  bind(AIChatInputConfiguration).toConstantValue({
66
67
  showContext: true,
67
- showPinnedAgent: true
68
- });
68
+ showPinnedAgent: true,
69
+ showChangeSet: true
70
+ } satisfies AIChatInputConfiguration);
69
71
  bind(WidgetFactory).toDynamicValue(({ container }) => ({
70
72
  id: AIChatInputWidget.ID,
71
73
  createWidget: () => container.get(AIChatInputWidget)
@@ -79,6 +81,30 @@ export default new ContainerModule((bind, _unbind, _isBound, rebind) => {
79
81
  createWidget: () => container.get(ChatViewTreeWidget)
80
82
  })).inSingletonScope();
81
83
 
84
+ bind(AIChatTreeInputFactory).toFactory(ctx => (args: AIChatTreeInputArgs) => {
85
+ const container = ctx.container.createChild();
86
+ container.bind(AIChatTreeInputArgs).toConstantValue(args);
87
+ container.bind(AIChatTreeInputConfiguration).toConstantValue({
88
+ showContext: true,
89
+ showPinnedAgent: true,
90
+ showChangeSet: false
91
+ } satisfies AIChatInputConfiguration);
92
+ container.bind(AIChatTreeInputWidget).toSelf().inSingletonScope();
93
+ const widget = container.get(AIChatTreeInputWidget);
94
+ const noOp = () => { };
95
+ widget.node.classList.add('chat-input-widget');
96
+ widget.chatModel = args.node.request.session;
97
+ widget.initialValue = args.initialValue;
98
+ widget.setEnabled(true);
99
+ widget.onQuery = args.onQuery;
100
+ // We need to set those values here, otherwise the widget will throw an error
101
+ widget.onUnpin = args.onUnpin ?? noOp;
102
+ widget.onCancel = args.onCancel ?? noOp;
103
+ widget.onDeleteChangeSet = args.onDeleteChangeSet ?? noOp;
104
+ widget.onDeleteChangeSetElement = args.onDeleteChangeSetElement ?? noOp;
105
+ return widget;
106
+ });
107
+
82
108
  bind(ContextVariablePicker).toSelf().inSingletonScope();
83
109
 
84
110
  bind(ChatResponsePartRenderer).to(HorizontalLayoutPartRenderer).inSingletonScope();
@@ -115,6 +141,8 @@ export default new ContainerModule((bind, _unbind, _isBound, rebind) => {
115
141
  bind(ChangeSetActionRenderer).toService(ChangeSetAcceptAction);
116
142
 
117
143
  bindContributionProvider(bind, ChatNodeToolbarActionContribution);
144
+ bind(DefaultChatNodeToolbarActionContribution).toSelf().inSingletonScope();
145
+ bind(ChatNodeToolbarActionContribution).toService(DefaultChatNodeToolbarActionContribution);
118
146
  });
119
147
 
120
148
  function bindChatViewWidget(bind: interfaces.Bind): void {
@@ -0,0 +1,85 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2025 EclipseSource GmbH and others.
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 * as React from '@theia/core/shared/react';
18
+ import { DeclaredEventsEventListenerObject, useMarkdownRendering } from './chat-response-renderer/markdown-part-renderer';
19
+ import { OpenerService } from '@theia/core/lib/browser';
20
+ import { ChatSuggestion, ChatSuggestionCallback } from '@theia/ai-chat';
21
+ import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
22
+
23
+ interface ChatInputAgentSuggestionsProps {
24
+ suggestions: readonly ChatSuggestion[];
25
+ opener: OpenerService;
26
+ }
27
+
28
+ function getText(suggestion: ChatSuggestion): string {
29
+ if (typeof suggestion === 'string') { return suggestion; }
30
+ if ('value' in suggestion) { return suggestion.value; }
31
+ if (typeof suggestion.content === 'string') { return suggestion.content; }
32
+ return suggestion.content.value;
33
+ }
34
+
35
+ function getContent(suggestion: ChatSuggestion): string | MarkdownString {
36
+ if (typeof suggestion === 'string') { return suggestion; }
37
+ if ('value' in suggestion) { return suggestion; }
38
+ return suggestion.content;
39
+ }
40
+
41
+ export const ChatInputAgentSuggestions: React.FC<ChatInputAgentSuggestionsProps> = ({ suggestions, opener }) => (
42
+ !!suggestions?.length && <div className="chat-agent-suggestions">
43
+ {suggestions.map(suggestion => <ChatInputAgentSuggestion
44
+ key={getText(suggestion)}
45
+ suggestion={suggestion}
46
+ opener={opener}
47
+ handler={ChatSuggestionCallback.is(suggestion) ? new ChatSuggestionClickHandler(suggestion) : undefined}
48
+ />)}
49
+ </div>
50
+ );
51
+
52
+ interface ChatInputAgestSuggestionProps {
53
+ suggestion: ChatSuggestion;
54
+ opener: OpenerService;
55
+ handler?: DeclaredEventsEventListenerObject;
56
+ }
57
+
58
+ const ChatInputAgentSuggestion: React.FC<ChatInputAgestSuggestionProps> = ({ suggestion, opener, handler }) => {
59
+ const ref = useMarkdownRendering(getContent(suggestion), opener, true, handler);
60
+ return <div className="chat-agent-suggestion" style={(!handler || ChatSuggestionCallback.containsCallbackLink(suggestion)) ? undefined : { cursor: 'pointer' }} ref={ref} />;
61
+ };
62
+
63
+ class ChatSuggestionClickHandler implements DeclaredEventsEventListenerObject {
64
+ constructor(protected readonly suggestion: ChatSuggestionCallback) { }
65
+ handleEvent(event: Event): boolean {
66
+ const { target, currentTarget } = event;
67
+ if (event.type !== 'click' || !(target instanceof Element)) { return false; }
68
+ const link = target.closest('a[href^="_callback"]');
69
+ if (link) {
70
+ this.suggestion.callback();
71
+ return true;
72
+ }
73
+ if (!(currentTarget instanceof Element)) {
74
+ this.suggestion.callback();
75
+ return true;
76
+ }
77
+ const containedLink = currentTarget.querySelector('a[href^="_callback"]');
78
+ // Whole body should count.
79
+ if (!containedLink) {
80
+ this.suggestion.callback();
81
+ return true;
82
+ }
83
+ return false;
84
+ }
85
+ }