@theia/ai-chat-ui 1.65.0-next.6 → 1.65.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 (42) hide show
  1. package/lib/browser/ai-chat-ui-contribution.js +4 -4
  2. package/lib/browser/ai-chat-ui-contribution.js.map +1 -1
  3. package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -1
  4. package/lib/browser/ai-chat-ui-frontend-module.js +9 -1
  5. package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -1
  6. package/lib/browser/chat-input-history-contribution.d.ts +17 -0
  7. package/lib/browser/chat-input-history-contribution.d.ts.map +1 -0
  8. package/lib/browser/chat-input-history-contribution.js +157 -0
  9. package/lib/browser/chat-input-history-contribution.js.map +1 -0
  10. package/lib/browser/chat-input-history.d.ts +32 -0
  11. package/lib/browser/chat-input-history.d.ts.map +1 -0
  12. package/lib/browser/chat-input-history.js +125 -0
  13. package/lib/browser/chat-input-history.js.map +1 -0
  14. package/lib/browser/chat-input-widget.d.ts +22 -1
  15. package/lib/browser/chat-input-widget.d.ts.map +1 -1
  16. package/lib/browser/chat-input-widget.js +166 -8
  17. package/lib/browser/chat-input-widget.js.map +1 -1
  18. package/lib/browser/chat-response-renderer/tool-confirmation.d.ts +1 -1
  19. package/lib/browser/chat-response-renderer/tool-confirmation.d.ts.map +1 -1
  20. package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts +2 -1
  21. package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts.map +1 -1
  22. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js +46 -11
  23. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -1
  24. package/lib/browser/chat-view-commands.d.ts +2 -0
  25. package/lib/browser/chat-view-commands.d.ts.map +1 -1
  26. package/lib/browser/chat-view-commands.js +30 -23
  27. package/lib/browser/chat-view-commands.js.map +1 -1
  28. package/lib/browser/chat-view-widget.d.ts +3 -3
  29. package/lib/browser/chat-view-widget.d.ts.map +1 -1
  30. package/lib/browser/chat-view-widget.js +2 -4
  31. package/lib/browser/chat-view-widget.js.map +1 -1
  32. package/package.json +11 -12
  33. package/src/browser/ai-chat-ui-contribution.ts +4 -4
  34. package/src/browser/ai-chat-ui-frontend-module.ts +11 -2
  35. package/src/browser/chat-input-history-contribution.ts +166 -0
  36. package/src/browser/chat-input-history.ts +138 -0
  37. package/src/browser/chat-input-widget.tsx +197 -8
  38. package/src/browser/chat-response-renderer/tool-confirmation.tsx +1 -1
  39. package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +52 -12
  40. package/src/browser/chat-view-commands.ts +24 -17
  41. package/src/browser/chat-view-widget.tsx +3 -6
  42. package/src/browser/style/index.css +22 -16
@@ -15,14 +15,16 @@
15
15
  // *****************************************************************************
16
16
  import {
17
17
  ChangeSet, ChangeSetElement, ChatAgent, ChatChangeEvent, ChatHierarchyBranch,
18
- ChatModel, ChatRequestModel, ChatService, ChatSuggestion, EditableChatRequestModel
18
+ ChatModel, ChatRequestModel, ChatService, ChatSuggestion, EditableChatRequestModel,
19
+ ChatRequestParser
19
20
  } from '@theia/ai-chat';
20
21
  import { ChangeSetDecoratorService } from '@theia/ai-chat/lib/browser/change-set-decorator-service';
21
22
  import { ImageContextVariable } from '@theia/ai-chat/lib/common/image-context-variable';
22
23
  import { AIVariableResolutionRequest } from '@theia/ai-core';
23
24
  import { AgentCompletionNotificationService, FrontendVariableService, AIActivationService } from '@theia/ai-core/lib/browser';
24
- import { DisposableCollection, Emitter, InMemoryResources, URI, nls } from '@theia/core';
25
+ import { DisposableCollection, Emitter, InMemoryResources, URI, nls, Disposable } from '@theia/core';
25
26
  import { ContextMenuRenderer, LabelProvider, Message, OpenerService, ReactWidget } from '@theia/core/lib/browser';
27
+ import { ContextKey, ContextKeyService } from '@theia/core/lib/browser/context-key-service';
26
28
  import { Deferred } from '@theia/core/lib/common/promise-util';
27
29
  import { inject, injectable, optional, postConstruct } from '@theia/core/shared/inversify';
28
30
  import * as React from '@theia/core/shared/react';
@@ -35,6 +37,8 @@ import { CHAT_VIEW_LANGUAGE_EXTENSION } from './chat-view-language-contribution'
35
37
  import { ContextVariablePicker } from './context-variable-picker';
36
38
  import { TASK_CONTEXT_VARIABLE } from '@theia/ai-chat/lib/browser/task-context-variable';
37
39
  import { IModelDeltaDecoration } from '@theia/monaco-editor-core/esm/vs/editor/common/model';
40
+ import { EditorOption } from '@theia/monaco-editor-core/esm/vs/editor/common/config/editorOptions';
41
+ import { ChatInputHistoryService, ChatInputNavigationState } from './chat-input-history';
38
42
 
39
43
  type Query = (query: string) => Promise<void>;
40
44
  type Unpin = () => void;
@@ -49,6 +53,7 @@ export interface AIChatInputConfiguration {
49
53
  showPinnedAgent?: boolean;
50
54
  showChangeSet?: boolean;
51
55
  showSuggestions?: boolean;
56
+ enablePromptHistory?: boolean;
52
57
  }
53
58
 
54
59
  @injectable()
@@ -95,12 +100,52 @@ export class AIChatInputWidget extends ReactWidget {
95
100
  @inject(AIActivationService)
96
101
  protected readonly aiActivationService: AIActivationService;
97
102
 
103
+ @inject(ChatInputHistoryService)
104
+ protected readonly historyService: ChatInputHistoryService;
105
+
106
+ @inject(ChatRequestParser)
107
+ protected readonly chatRequestParser: ChatRequestParser;
108
+
109
+ protected navigationState: ChatInputNavigationState;
110
+
111
+ @inject(ContextKeyService)
112
+ protected readonly contextKeyService: ContextKeyService;
113
+
98
114
  protected editorRef: SimpleMonacoEditor | undefined = undefined;
99
115
  protected readonly editorReady = new Deferred<void>();
100
116
 
117
+ get editor(): SimpleMonacoEditor | undefined {
118
+ return this.editorRef;
119
+ }
120
+
121
+ get inputConfiguration(): AIChatInputConfiguration | undefined {
122
+ return this.configuration;
123
+ }
124
+
125
+ getPreviousPrompt(currentInput: string): string | undefined {
126
+ if (!this.navigationState) {
127
+ return undefined;
128
+ }
129
+ return this.navigationState.getPreviousPrompt(currentInput);
130
+ }
131
+
132
+ getNextPrompt(): string | undefined {
133
+ if (!this.navigationState) {
134
+ return undefined;
135
+ }
136
+ return this.navigationState.getNextPrompt();
137
+ }
138
+
139
+ protected chatInputFocusKey: ContextKey<boolean>;
140
+ protected chatInputFirstLineKey: ContextKey<boolean>;
141
+ protected chatInputLastLineKey: ContextKey<boolean>;
142
+ protected chatInputReceivingAgentKey: ContextKey<string>;
143
+
101
144
  protected isEnabled = false;
102
145
  protected heightInLines = 12;
103
146
 
147
+ protected updateReceivingAgentTimeout: number | undefined;
148
+
104
149
  protected _branch?: ChatHierarchyBranch;
105
150
  set branch(branch: ChatHierarchyBranch | undefined) {
106
151
  if (this._branch !== branch) {
@@ -111,7 +156,13 @@ export class AIChatInputWidget extends ReactWidget {
111
156
 
112
157
  protected _onQuery: Query;
113
158
  set onQuery(query: Query) {
114
- this._onQuery = query;
159
+ this._onQuery = (prompt: string) => {
160
+ if (this.configuration?.enablePromptHistory !== false && prompt.trim()) {
161
+ this.historyService.addToHistory(prompt);
162
+ this.navigationState.stopNavigation();
163
+ }
164
+ return query(prompt);
165
+ };
115
166
  }
116
167
  protected _onUnpin: Unpin;
117
168
  set onUnpin(unpin: Unpin) {
@@ -146,11 +197,13 @@ export class AIChatInputWidget extends ReactWidget {
146
197
  }
147
198
  }));
148
199
  this._chatModel = chatModel;
200
+ this.scheduleUpdateReceivingAgent();
149
201
  this.update();
150
202
  }
151
203
  protected _pinnedAgent: ChatAgent | undefined;
152
204
  set pinnedAgent(pinnedAgent: ChatAgent | undefined) {
153
205
  this._pinnedAgent = pinnedAgent;
206
+ this.scheduleUpdateReceivingAgent();
154
207
  this.update();
155
208
  }
156
209
 
@@ -166,10 +219,134 @@ export class AIChatInputWidget extends ReactWidget {
166
219
  this.setEnabled(this.aiActivationService.isActive);
167
220
  }));
168
221
  this.toDispose.push(this.onDidResizeEmitter);
222
+ this.toDispose.push(Disposable.create(() => {
223
+ if (this.updateReceivingAgentTimeout !== undefined) {
224
+ clearTimeout(this.updateReceivingAgentTimeout);
225
+ this.updateReceivingAgentTimeout = undefined;
226
+ }
227
+ }));
169
228
  this.setEnabled(this.aiActivationService.isActive);
229
+ this.historyService.init().then(() => {
230
+ this.navigationState = new ChatInputNavigationState(this.historyService);
231
+ });
232
+ this.initializeContextKeys();
170
233
  this.update();
171
234
  }
172
235
 
236
+ protected initializeContextKeys(): void {
237
+ this.chatInputFocusKey = this.contextKeyService.createKey<boolean>('chatInputFocus', false);
238
+ this.chatInputFirstLineKey = this.contextKeyService.createKey<boolean>('chatInputFirstLine', false);
239
+ this.chatInputLastLineKey = this.contextKeyService.createKey<boolean>('chatInputLastLine', false);
240
+ this.chatInputReceivingAgentKey = this.contextKeyService.createKey<string>('chatInputReceivingAgent', '');
241
+ }
242
+
243
+ updateCursorPositionKeys(): void {
244
+ if (!this.editorRef) {
245
+ this.chatInputFirstLineKey.set(false);
246
+ this.chatInputLastLineKey.set(false);
247
+ return;
248
+ }
249
+
250
+ const editor = this.editorRef.getControl();
251
+ const position = editor.getPosition();
252
+ const model = editor.getModel();
253
+
254
+ if (!position || !model) {
255
+ this.chatInputFirstLineKey.set(false);
256
+ this.chatInputLastLineKey.set(false);
257
+ return;
258
+ }
259
+
260
+ const line = position.lineNumber;
261
+ const col = position.column;
262
+
263
+ const topAtPos = editor.getTopForPosition(line, col);
264
+ const topAtLineStart = editor.getTopForLineNumber(line);
265
+ const topAtLineEnd = editor.getTopForPosition(line, model.getLineMaxColumn(line));
266
+ const lineHeight = editor.getOption(EditorOption.lineHeight);
267
+ const toleranceValue = 0.5;
268
+
269
+ const isFirstVisualOfThisLine = Math.abs(topAtPos - topAtLineStart) < toleranceValue;
270
+ const isLastVisualOfThisLine = Math.abs(topAtPos - topAtLineEnd) < toleranceValue || (topAtPos > topAtLineEnd - lineHeight + toleranceValue);
271
+ const isFirstVisualOverall = line === 1 && isFirstVisualOfThisLine;
272
+
273
+ const isLastVisualOverall = line === model.getLineCount() && isLastVisualOfThisLine;
274
+
275
+ this.chatInputFirstLineKey.set(isFirstVisualOverall);
276
+ this.chatInputLastLineKey.set(isLastVisualOverall);
277
+ }
278
+
279
+ protected scheduleUpdateReceivingAgent(): void {
280
+ if (this.updateReceivingAgentTimeout !== undefined) {
281
+ clearTimeout(this.updateReceivingAgentTimeout);
282
+ }
283
+ this.updateReceivingAgentTimeout = window.setTimeout(() => {
284
+ this.updateReceivingAgent();
285
+ this.updateReceivingAgentTimeout = undefined;
286
+ }, 200);
287
+ }
288
+
289
+ protected async updateReceivingAgent(): Promise<void> {
290
+ if (!this.editorRef || !this._chatModel) {
291
+ this.chatInputReceivingAgentKey.set('');
292
+ return;
293
+ }
294
+
295
+ try {
296
+ const inputText = this.editorRef.getControl().getValue();
297
+ const request = { text: inputText };
298
+ const resolvedContext = { variables: [] };
299
+ const parsedRequest = await this.chatRequestParser.parseChatRequest(request, this._chatModel.location, resolvedContext);
300
+ const session = this.chatService.getSessions().find(s => s.model.id === this._chatModel.id);
301
+ if (session) {
302
+ const agent = this.chatService.getAgent(parsedRequest, session);
303
+ this.chatInputReceivingAgentKey.set(agent?.id ?? '');
304
+ } else {
305
+ this.chatInputReceivingAgentKey.set('');
306
+ }
307
+ } catch (error) {
308
+ console.warn('Failed to determine receiving agent:', error);
309
+ this.chatInputReceivingAgentKey.set('');
310
+ }
311
+ }
312
+
313
+ protected setupEditorEventListeners(): void {
314
+ if (!this.editorRef) {
315
+ return;
316
+ }
317
+
318
+ const editor = this.editorRef.getControl();
319
+
320
+ this.toDispose.push(editor.onDidFocusEditorWidget(() => {
321
+ this.chatInputFocusKey.set(true);
322
+ this.updateCursorPositionKeys();
323
+ }));
324
+
325
+ this.toDispose.push(editor.onDidBlurEditorWidget(() => {
326
+ this.chatInputFocusKey.set(false);
327
+ this.chatInputFirstLineKey.set(false);
328
+ this.chatInputLastLineKey.set(false);
329
+ }));
330
+
331
+ this.toDispose.push(editor.onDidChangeCursorPosition(() => {
332
+ if (editor.hasWidgetFocus()) {
333
+ this.updateCursorPositionKeys();
334
+ }
335
+ }));
336
+
337
+ this.toDispose.push(editor.onDidChangeModelContent(() => {
338
+ if (editor.hasWidgetFocus()) {
339
+ this.updateCursorPositionKeys();
340
+ }
341
+ this.scheduleUpdateReceivingAgent();
342
+ }));
343
+
344
+ if (editor.hasWidgetFocus()) {
345
+ this.chatInputFocusKey.set(true);
346
+ this.updateCursorPositionKeys();
347
+ }
348
+ }
349
+
173
350
  protected override onActivateRequest(msg: Message): void {
174
351
  super.onActivateRequest(msg);
175
352
  this.editorReady.promise.then(() => {
@@ -232,12 +409,14 @@ export class AIChatInputWidget extends ReactWidget {
232
409
  isEnabled={this.isEnabled}
233
410
  setEditorRef={editor => {
234
411
  this.editorRef = editor;
412
+ this.setupEditorEventListeners();
235
413
  this.editorReady.resolve();
236
414
  }}
237
415
  showContext={this.configuration?.showContext}
238
416
  showPinnedAgent={this.configuration?.showPinnedAgent}
239
417
  showChangeSet={this.configuration?.showChangeSet}
240
418
  showSuggestions={this.configuration?.showSuggestions}
419
+ hasPromptHistory={this.configuration?.enablePromptHistory}
241
420
  labelProvider={this.labelProvider}
242
421
  actionService={this.changeSetActionService}
243
422
  decoratorService={this.changeSetDecoratorService}
@@ -388,6 +567,7 @@ interface ChatInputProperties {
388
567
  showPinnedAgent?: boolean;
389
568
  showChangeSet?: boolean;
390
569
  showSuggestions?: boolean;
570
+ hasPromptHistory?: boolean;
391
571
  labelProvider: LabelProvider;
392
572
  actionService: ChangeSetActionService;
393
573
  decoratorService: ChangeSetDecoratorService;
@@ -412,6 +592,8 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
412
592
  const onDeleteChangeSetElement = (uri: URI) => props.onDeleteChangeSetElement(props.chatModel.id, uri);
413
593
 
414
594
  const [isInputEmpty, setIsInputEmpty] = React.useState(true);
595
+ const [isInputFocused, setIsInputFocused] = React.useState(false);
596
+ const [placeholderText, setPlaceholderText] = React.useState('');
415
597
  const [changeSetUI, setChangeSetUI] = React.useState(
416
598
  () => buildChangeSetUI(
417
599
  props.chatModel.changeSet,
@@ -435,11 +617,16 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
435
617
  const isFirstRequest = props.chatModel.getRequests().length === 0;
436
618
  const shouldUseTaskPlaceholder = isFirstRequest && props.pinnedAgent && hasTaskContext(props.chatModel);
437
619
  const taskPlaceholder = nls.localize('theia/ai/chat-ui/performThisTask', 'Perform this task.');
438
- const placeholderText = !props.isEnabled
439
- ? nls.localize('theia/ai/chat-ui/aiDisabled', 'AI features are disabled')
440
- : shouldUseTaskPlaceholder
441
- ? taskPlaceholder
442
- : nls.localizeByDefault('Ask a question');
620
+
621
+ // Update placeholder text when focus state or other dependencies change
622
+ React.useEffect(() => {
623
+ const newPlaceholderText = !props.isEnabled
624
+ ? nls.localize('theia/ai/chat-ui/aiDisabled', 'AI features are disabled')
625
+ : shouldUseTaskPlaceholder
626
+ ? taskPlaceholder
627
+ : nls.localizeByDefault('Ask a question') + (props.hasPromptHistory && isInputFocused ? nls.localizeByDefault(' ({0} for history)', '⇅') : '');
628
+ setPlaceholderText(newPlaceholderText);
629
+ }, [props.isEnabled, shouldUseTaskPlaceholder, taskPlaceholder, props.hasPromptHistory, isInputFocused]);
443
630
 
444
631
  // Handle paste events on the container
445
632
  const handlePaste = React.useCallback((event: ClipboardEvent) => {
@@ -680,6 +867,7 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
680
867
  }, [props.isEnabled, submit]);
681
868
 
682
869
  const handleInputFocus = () => {
870
+ setIsInputFocused(true);
683
871
  hidePlaceholderIfEditorFilled();
684
872
  };
685
873
 
@@ -689,6 +877,7 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
689
877
  };
690
878
 
691
879
  const handleInputBlur = () => {
880
+ setIsInputFocused(false);
692
881
  showPlaceholderIfEditorEmpty();
693
882
  };
694
883
 
@@ -22,7 +22,7 @@ import { ToolCallChatResponseContent } from '@theia/ai-chat/lib/common';
22
22
  /**
23
23
  * States the tool confirmation component can be in
24
24
  */
25
- export type ToolConfirmationState = 'waiting' | 'allowed' | 'denied';
25
+ export type ToolConfirmationState = 'waiting' | 'allowed' | 'denied' | 'rejected';
26
26
 
27
27
  export interface ToolConfirmationProps {
28
28
  response: ToolCallChatResponseContent;
@@ -22,10 +22,11 @@ import { nls } from '@theia/core/lib/common/nls';
22
22
  import { codicon, OpenerService } from '@theia/core/lib/browser';
23
23
  import * as React from '@theia/core/shared/react';
24
24
  import { ToolConfirmation, ToolConfirmationState } from './tool-confirmation';
25
- import { ToolConfirmationManager, ToolConfirmationMode } from '@theia/ai-chat/lib/browser/chat-tool-preferences';
25
+ import { ToolConfirmationMode } from '@theia/ai-chat/lib/common/chat-tool-preferences';
26
26
  import { ResponseNode } from '../chat-tree-view';
27
27
  import { useMarkdownRendering } from './markdown-part-renderer';
28
28
  import { ToolCallResult } from '@theia/ai-core';
29
+ import { ToolConfirmationManager } from '@theia/ai-chat/lib/browser/chat-tool-preference-bindings';
29
30
 
30
31
  @injectable()
31
32
  export class ToolCallPartRenderer implements ChatResponsePartRenderer<ToolCallChatResponseContent> {
@@ -52,7 +53,8 @@ export class ToolCallPartRenderer implements ChatResponsePartRenderer<ToolCallCh
52
53
  toolConfirmationManager={this.toolConfirmationManager}
53
54
  chatId={chatId}
54
55
  renderCollapsibleArguments={this.renderCollapsibleArguments.bind(this)}
55
- responseRenderer={this.renderResult.bind(this)} />;
56
+ responseRenderer={this.renderResult.bind(this)}
57
+ requestCanceled={parentNode.response.isCanceled} />;
56
58
  }
57
59
 
58
60
  protected renderResult(response: ToolCallChatResponseContent): ReactNode {
@@ -138,13 +140,40 @@ interface ToolCallContentProps {
138
140
  chatId: string;
139
141
  renderCollapsibleArguments: (args: string | undefined) => ReactNode;
140
142
  responseRenderer: (response: ToolCallChatResponseContent) => ReactNode | undefined;
143
+ requestCanceled: boolean;
141
144
  }
142
145
 
143
146
  /**
144
147
  * A function component to handle tool call rendering and confirmation
145
148
  */
146
- const ToolCallContent: React.FC<ToolCallContentProps> = ({ response, confirmationMode, toolConfirmationManager, chatId, responseRenderer, renderCollapsibleArguments }) => {
149
+ const ToolCallContent: React.FC<ToolCallContentProps> = ({
150
+ response,
151
+ confirmationMode,
152
+ toolConfirmationManager,
153
+ chatId,
154
+ responseRenderer,
155
+ renderCollapsibleArguments,
156
+ requestCanceled
157
+ }) => {
147
158
  const [confirmationState, setConfirmationState] = React.useState<ToolConfirmationState>('waiting');
159
+ const [rejectionReason, setRejectionReason] = React.useState<unknown>(undefined);
160
+
161
+ const formatReason = (reason: unknown): string => {
162
+ if (!reason) {
163
+ return '';
164
+ }
165
+ if (reason instanceof Error) {
166
+ return reason.message;
167
+ }
168
+ if (typeof reason === 'string') {
169
+ return reason;
170
+ }
171
+ try {
172
+ return JSON.stringify(reason);
173
+ } catch (e) {
174
+ return String(reason);
175
+ }
176
+ };
148
177
 
149
178
  React.useEffect(() => {
150
179
  if (confirmationMode === ToolConfirmationMode.ALWAYS_ALLOW) {
@@ -156,17 +185,17 @@ const ToolCallContent: React.FC<ToolCallContentProps> = ({ response, confirmatio
156
185
  setConfirmationState('denied');
157
186
  return;
158
187
  }
159
- response.confirmed.then(
160
- confirmed => {
188
+ response.confirmed
189
+ .then(confirmed => {
161
190
  if (confirmed === true) {
162
191
  setConfirmationState('allowed');
163
192
  } else {
164
193
  setConfirmationState('denied');
165
194
  }
166
- }
167
- )
168
- .catch(() => {
169
- setConfirmationState('denied');
195
+ })
196
+ .catch(reason => {
197
+ setRejectionReason(reason);
198
+ setConfirmationState('rejected');
170
199
  });
171
200
  }, [response, confirmationMode]);
172
201
 
@@ -188,9 +217,20 @@ const ToolCallContent: React.FC<ToolCallContentProps> = ({ response, confirmatio
188
217
  response.deny();
189
218
  }, [response, toolConfirmationManager, chatId]);
190
219
 
220
+ const reasonText = formatReason(rejectionReason);
221
+
191
222
  return (
192
223
  <div className='theia-toolCall'>
193
- {confirmationState === 'denied' ? (
224
+ {confirmationState === 'rejected' ? (
225
+ <span className='theia-toolCall-rejected'>
226
+ <span className={codicon('error')}></span> {nls.localize('theia/ai/chat-ui/toolcall-part-renderer/rejected', 'Execution canceled')}: {response.name}
227
+ {reasonText ? <span> — {reasonText}</span> : undefined}
228
+ </span>
229
+ ) : requestCanceled && !response.finished ? (
230
+ <span className='theia-toolCall-rejected'>
231
+ <span className={codicon('error')}></span> {nls.localize('theia/ai/chat-ui/toolcall-part-renderer/rejected', 'Execution canceled')}: {response.name}
232
+ </span>
233
+ ) : confirmationState === 'denied' ? (
194
234
  <span className='theia-toolCall-denied'>
195
235
  <span className={codicon('error')}></span> {nls.localize('theia/ai/chat-ui/toolcall-part-renderer/denied', 'Execution denied')}: {response.name}
196
236
  </span>
@@ -205,7 +245,7 @@ const ToolCallContent: React.FC<ToolCallContentProps> = ({ response, confirmatio
205
245
  </div>
206
246
  </details>
207
247
  ) : (
208
- confirmationState === 'allowed' && (
248
+ confirmationState === 'allowed' && !requestCanceled && (
209
249
  <span className='theia-toolCall-allowed'>
210
250
  <Spinner /> {nls.localizeByDefault('Running')} {response.name}
211
251
  </span>
@@ -213,7 +253,7 @@ const ToolCallContent: React.FC<ToolCallContentProps> = ({ response, confirmatio
213
253
  )}
214
254
 
215
255
  {/* Show confirmation UI when waiting for allow */}
216
- {confirmationState === 'waiting' && (
256
+ {confirmationState === 'waiting' && !requestCanceled && (
217
257
  <span className='theia-toolCall-waiting'>
218
258
  <ToolConfirmation
219
259
  response={response}
@@ -18,26 +18,29 @@ import { Command, nls } from '@theia/core';
18
18
  import { codicon } from '@theia/core/lib/browser';
19
19
 
20
20
  export namespace ChatCommands {
21
- const CHAT_CATEGORY = 'Chat';
22
- const CHAT_CATEGORY_KEY = nls.getDefaultKey(CHAT_CATEGORY);
21
+ export const CHAT_CATEGORY = 'Chat';
22
+ export const CHAT_CATEGORY_KEY = nls.getDefaultKey(CHAT_CATEGORY);
23
23
 
24
24
  export const SCROLL_LOCK_WIDGET = Command.toLocalizedCommand({
25
25
  id: 'chat:widget:lock',
26
26
  category: CHAT_CATEGORY,
27
- iconClass: codicon('unlock')
28
- }, '', CHAT_CATEGORY_KEY);
27
+ iconClass: codicon('unlock'),
28
+ label: 'Lock Scroll'
29
+ }, 'theia/ai-chat-ui/scroll-lock', CHAT_CATEGORY_KEY);
29
30
 
30
31
  export const SCROLL_UNLOCK_WIDGET = Command.toLocalizedCommand({
31
32
  id: 'chat:widget:unlock',
32
33
  category: CHAT_CATEGORY,
33
- iconClass: codicon('lock')
34
- }, '', CHAT_CATEGORY_KEY);
34
+ iconClass: codicon('lock'),
35
+ label: 'Unlock Scroll'
36
+ }, 'theia/ai-chat-ui/scroll-unlock', CHAT_CATEGORY_KEY);
35
37
 
36
38
  export const EDIT_SESSION_SETTINGS = Command.toLocalizedCommand({
37
39
  id: 'chat:widget:session-settings',
38
40
  category: CHAT_CATEGORY,
39
- iconClass: codicon('bracket')
40
- }, 'Set Session Settings', CHAT_CATEGORY_KEY);
41
+ iconClass: codicon('bracket'),
42
+ label: 'Set Session Settings'
43
+ }, 'theia/ai-chat-ui/session-settings', CHAT_CATEGORY_KEY);
41
44
 
42
45
  export const AI_CHAT_NEW_WITH_TASK_CONTEXT: Command = {
43
46
  id: 'ai-chat.new-with-task-context',
@@ -47,29 +50,33 @@ export namespace ChatCommands {
47
50
  id: 'ai-chat.initiate-session-with-task-context',
48
51
  label: 'Task Context: Initiate Session',
49
52
  category: CHAT_CATEGORY
50
- }, undefined, CHAT_CATEGORY_KEY);
53
+ }, 'theia/ai-chat-ui/initiate-session-task-context', CHAT_CATEGORY_KEY);
51
54
 
52
55
  export const AI_CHAT_SUMMARIZE_CURRENT_SESSION = Command.toLocalizedCommand({
53
56
  id: 'ai-chat-summary-current-session',
54
57
  iconClass: codicon('go-to-editing-session'),
55
58
  label: 'Summarize Current Session',
56
59
  category: CHAT_CATEGORY
57
- }, undefined, CHAT_CATEGORY_KEY);
60
+ }, 'theia/ai-chat-ui/summarize-current-session', CHAT_CATEGORY_KEY);
58
61
 
59
62
  export const AI_CHAT_OPEN_SUMMARY_FOR_CURRENT_SESSION = Command.toLocalizedCommand({
60
63
  id: 'ai-chat-open-current-session-summary',
61
64
  iconClass: codicon('note'),
62
65
  label: 'Open Current Session Summary',
63
66
  category: CHAT_CATEGORY
64
- }, undefined, CHAT_CATEGORY_KEY);
67
+ }, 'theia/ai-chat-ui/open-current-session-summary', CHAT_CATEGORY_KEY);
65
68
  }
66
69
 
67
- export const AI_CHAT_NEW_CHAT_WINDOW_COMMAND: Command = {
70
+ export const AI_CHAT_NEW_CHAT_WINDOW_COMMAND = Command.toDefaultLocalizedCommand({
68
71
  id: 'ai-chat-ui.new-chat',
69
- iconClass: codicon('add')
70
- };
72
+ iconClass: codicon('add'),
73
+ category: ChatCommands.CHAT_CATEGORY,
74
+ label: 'New Chat'
75
+ });
71
76
 
72
- export const AI_CHAT_SHOW_CHATS_COMMAND: Command = {
77
+ export const AI_CHAT_SHOW_CHATS_COMMAND = Command.toDefaultLocalizedCommand({
73
78
  id: 'ai-chat-ui.show-chats',
74
- iconClass: codicon('history')
75
- };
79
+ iconClass: codicon('history'),
80
+ category: ChatCommands.CHAT_CATEGORY,
81
+ label: 'Show Chats...'
82
+ });
@@ -13,9 +13,9 @@
13
13
  //
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
- import { CommandService, deepClone, Emitter, Event, MessageService, URI } from '@theia/core';
16
+ import { CommandService, deepClone, Emitter, Event, MessageService, PreferenceService, URI } from '@theia/core';
17
17
  import { ChatRequest, ChatRequestModel, ChatService, ChatSession, isActiveSessionChangedEvent, MutableChatModel } from '@theia/ai-chat';
18
- import { BaseWidget, codicon, ExtractableWidget, Message, PanelLayout, PreferenceService, StatefulWidget } from '@theia/core/lib/browser';
18
+ import { BaseWidget, codicon, ExtractableWidget, Message, PanelLayout, StatefulWidget } from '@theia/core/lib/browser';
19
19
  import { nls } from '@theia/core/lib/common/nls';
20
20
  import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
21
21
  import { AIChatInputWidget } from './chat-input-widget';
@@ -64,6 +64,7 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
64
64
  protected _state: ChatViewWidget.State = { locked: false, temporaryLocked: false };
65
65
  protected readonly onStateChangedEmitter = new Emitter<ChatViewWidget.State>();
66
66
 
67
+ isExtractable = true;
67
68
  secondaryWindow: Window | undefined;
68
69
 
69
70
  constructor(
@@ -239,10 +240,6 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
239
240
  return !!this.state.locked;
240
241
  }
241
242
 
242
- get isExtractable(): boolean {
243
- return this.secondaryWindow === undefined;
244
- }
245
-
246
243
  addContext(variable: AIVariableResolutionRequest): void {
247
244
  this.inputWidget.addContext(variable);
248
245
  }