@theia/ai-chat-ui 1.62.1 → 1.63.0-next.24

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 (40) hide show
  1. package/lib/browser/ai-chat-ui-contribution.js +2 -2
  2. package/lib/browser/ai-chat-ui-contribution.js.map +1 -1
  3. package/lib/browser/chat-input-widget.d.ts +3 -1
  4. package/lib/browser/chat-input-widget.d.ts.map +1 -1
  5. package/lib/browser/chat-input-widget.js +101 -90
  6. package/lib/browser/chat-input-widget.js.map +1 -1
  7. package/lib/browser/chat-response-renderer/index.d.ts +1 -0
  8. package/lib/browser/chat-response-renderer/index.d.ts.map +1 -1
  9. package/lib/browser/chat-response-renderer/index.js +1 -0
  10. package/lib/browser/chat-response-renderer/index.js.map +1 -1
  11. package/lib/browser/chat-response-renderer/tool-confirmation.d.ts +17 -0
  12. package/lib/browser/chat-response-renderer/tool-confirmation.d.ts.map +1 -0
  13. package/lib/browser/chat-response-renderer/tool-confirmation.js +120 -0
  14. package/lib/browser/chat-response-renderer/tool-confirmation.js.map +1 -0
  15. package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts +5 -1
  16. package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts.map +1 -1
  17. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js +83 -19
  18. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -1
  19. package/lib/browser/chat-tree-view/chat-view-tree-input-widget.d.ts +6 -1
  20. package/lib/browser/chat-tree-view/chat-view-tree-input-widget.d.ts.map +1 -1
  21. package/lib/browser/chat-tree-view/chat-view-tree-input-widget.js +9 -0
  22. package/lib/browser/chat-tree-view/chat-view-tree-input-widget.js.map +1 -1
  23. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts +6 -0
  24. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts.map +1 -1
  25. package/lib/browser/chat-tree-view/chat-view-tree-widget.js +30 -3
  26. package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -1
  27. package/lib/browser/chat-view-widget.d.ts +5 -2
  28. package/lib/browser/chat-view-widget.d.ts.map +1 -1
  29. package/lib/browser/chat-view-widget.js +19 -6
  30. package/lib/browser/chat-view-widget.js.map +1 -1
  31. package/package.json +11 -11
  32. package/src/browser/ai-chat-ui-contribution.ts +2 -2
  33. package/src/browser/chat-input-widget.tsx +171 -137
  34. package/src/browser/chat-response-renderer/index.ts +1 -0
  35. package/src/browser/chat-response-renderer/tool-confirmation.tsx +173 -0
  36. package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +115 -19
  37. package/src/browser/chat-tree-view/chat-view-tree-input-widget.tsx +16 -1
  38. package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +39 -5
  39. package/src/browser/chat-view-widget.tsx +23 -7
  40. package/src/browser/style/index.css +173 -0
@@ -15,15 +15,22 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
18
- import { injectable } from '@theia/core/shared/inversify';
18
+ import { inject, injectable } from '@theia/core/shared/inversify';
19
19
  import { ChatResponseContent, ToolCallChatResponseContent } from '@theia/ai-chat/lib/common';
20
20
  import { ReactNode } from '@theia/core/shared/react';
21
21
  import { nls } from '@theia/core/lib/common/nls';
22
+ import { codicon } from '@theia/core/lib/browser';
22
23
  import * as React from '@theia/core/shared/react';
24
+ import { ToolConfirmation, ToolConfirmationState } from './tool-confirmation';
25
+ import { ToolConfirmationManager, ToolConfirmationMode } from '@theia/ai-chat/lib/browser/chat-tool-preferences';
26
+ import { ResponseNode } from '../chat-tree-view';
23
27
 
24
28
  @injectable()
25
29
  export class ToolCallPartRenderer implements ChatResponsePartRenderer<ToolCallChatResponseContent> {
26
30
 
31
+ @inject(ToolConfirmationManager)
32
+ protected toolConfirmationManager: ToolConfirmationManager;
33
+
27
34
  canHandle(response: ChatResponseContent): number {
28
35
  if (ToolCallChatResponseContent.is(response)) {
29
36
  return 10;
@@ -31,23 +38,20 @@ export class ToolCallPartRenderer implements ChatResponsePartRenderer<ToolCallCh
31
38
  return -1;
32
39
  }
33
40
 
34
- render(response: ToolCallChatResponseContent): ReactNode {
35
- return (
36
- <h4 className='theia-toolCall'>
37
- {response.finished ? (
38
- <details>
39
- <summary>{nls.localize('theia/ai/chat-ui/toolcall-part-renderer/finished', 'Ran')} {response.name}
40
- ({this.renderCollapsibleArguments(response.arguments)})
41
- </summary>
42
- <pre>{this.tryPrettyPrintJson(response)}</pre>
43
- </details>
44
- ) : (
45
- <span>
46
- <Spinner /> {nls.localizeByDefault('Running')} {response.name}({this.renderCollapsibleArguments(response.arguments)})
47
- </span>
48
- )}
49
- </h4>
50
- );
41
+ render(response: ToolCallChatResponseContent, parentNode: ResponseNode): ReactNode {
42
+ const chatId = parentNode.sessionId;
43
+ const confirmationMode = response.name ? this.getToolConfirmationSettings(response.name, chatId) : ToolConfirmationMode.DISABLED;
44
+ return <ToolCallContent
45
+ response={response}
46
+ confirmationMode={confirmationMode}
47
+ toolConfirmationManager={this.toolConfirmationManager}
48
+ chatId={chatId}
49
+ renderCollapsibleArguments={this.renderCollapsibleArguments.bind(this)}
50
+ tryPrettyPrintJson={this.tryPrettyPrintJson.bind(this)} />;
51
+ }
52
+
53
+ protected getToolConfirmationSettings(responseId: string, chatId: string): ToolConfirmationMode {
54
+ return this.toolConfirmationManager.getConfirmationMode(responseId, chatId);
51
55
  }
52
56
 
53
57
  protected renderCollapsibleArguments(args: string | undefined): ReactNode {
@@ -97,5 +101,97 @@ export class ToolCallPartRenderer implements ChatResponsePartRenderer<ToolCallCh
97
101
  }
98
102
 
99
103
  const Spinner = () => (
100
- <i className="fa fa-spinner fa-spin"></i>
104
+ <span className={codicon('loading')}></span>
101
105
  );
106
+
107
+ interface ToolCallContentProps {
108
+ response: ToolCallChatResponseContent;
109
+ confirmationMode: ToolConfirmationMode;
110
+ toolConfirmationManager: ToolConfirmationManager;
111
+ chatId: string;
112
+ renderCollapsibleArguments: (args: string | undefined) => ReactNode;
113
+ tryPrettyPrintJson: (response: ToolCallChatResponseContent) => string | undefined;
114
+ }
115
+
116
+ /**
117
+ * A function component to handle tool call rendering and confirmation
118
+ */
119
+ const ToolCallContent: React.FC<ToolCallContentProps> = ({ response, confirmationMode, toolConfirmationManager, chatId, tryPrettyPrintJson, renderCollapsibleArguments }) => {
120
+ const [confirmationState, setConfirmationState] = React.useState<ToolConfirmationState>('waiting');
121
+
122
+ React.useEffect(() => {
123
+ if (confirmationMode === ToolConfirmationMode.YOLO) {
124
+ response.confirm();
125
+ setConfirmationState('approved');
126
+ return;
127
+ } else if (confirmationMode === ToolConfirmationMode.DISABLED) {
128
+ response.deny();
129
+ setConfirmationState('denied');
130
+ return;
131
+ }
132
+ response.confirmed.then(
133
+ confirmed => {
134
+ if (confirmed === true) {
135
+ setConfirmationState('approved');
136
+ } else {
137
+ setConfirmationState('denied');
138
+ }
139
+ }
140
+ )
141
+ .catch(() => {
142
+ setConfirmationState('denied');
143
+ });
144
+ }, [response, confirmationMode]);
145
+
146
+ const handleApprove = React.useCallback((mode: 'once' | 'session' | 'forever' = 'once') => {
147
+ if (mode === 'forever' && response.name) {
148
+ toolConfirmationManager.setConfirmationMode(response.name, ToolConfirmationMode.YOLO);
149
+ } else if (mode === 'session' && response.name) {
150
+ toolConfirmationManager.setSessionConfirmationMode(response.name, ToolConfirmationMode.YOLO, chatId);
151
+ }
152
+ response.confirm();
153
+ }, [response, toolConfirmationManager, chatId]);
154
+
155
+ const handleDeny = React.useCallback((mode: 'once' | 'session' | 'forever' = 'once') => {
156
+ if (mode === 'forever' && response.name) {
157
+ toolConfirmationManager.setConfirmationMode(response.name, ToolConfirmationMode.DISABLED);
158
+ } else if (mode === 'session' && response.name) {
159
+ toolConfirmationManager.setSessionConfirmationMode(response.name, ToolConfirmationMode.DISABLED, chatId);
160
+ }
161
+ response.deny();
162
+ }, [response, toolConfirmationManager, chatId]);
163
+
164
+ return (
165
+ <div className='theia-toolCall'>
166
+ <h4>
167
+ {confirmationState === 'denied' ? (
168
+ <span className="theia-tool-denied">
169
+ <span className={codicon('error')}></span> {nls.localize('theia/ai/chat-ui/toolcall-part-renderer/denied', 'Execution denied')}: {response.name}
170
+ </span>
171
+ ) : response.finished ? (
172
+ <details>
173
+ <summary>{nls.localize('theia/ai/chat-ui/toolcall-part-renderer/finished', 'Ran')} {response.name}
174
+ ({renderCollapsibleArguments(response.arguments)})
175
+ </summary>
176
+ <pre>{tryPrettyPrintJson(response)}</pre>
177
+ </details>
178
+ ) : (
179
+ confirmationState === 'approved' && (
180
+ <span>
181
+ <Spinner /> {nls.localizeByDefault('Running')} {response.name}
182
+ </span>
183
+ )
184
+ )}
185
+ </h4>
186
+
187
+ {/* Show confirmation UI when waiting for approval */}
188
+ {confirmationState === 'waiting' && (
189
+ <ToolConfirmation
190
+ response={response}
191
+ onApprove={handleApprove}
192
+ onDeny={handleDeny}
193
+ />
194
+ )}
195
+ </div>
196
+ );
197
+ };
@@ -19,7 +19,7 @@ import { AIChatInputWidget, type AIChatInputConfiguration } from '../chat-input-
19
19
  import type { EditableRequestNode } from './chat-view-tree-widget';
20
20
  import { URI } from '@theia/core';
21
21
  import { CHAT_VIEW_LANGUAGE_EXTENSION } from '../chat-view-language-contribution';
22
- import type { ChatRequestModel, EditableChatRequestModel } from '@theia/ai-chat';
22
+ import type { ChatRequestModel, EditableChatRequestModel, ChatHierarchyBranch } from '@theia/ai-chat';
23
23
  import type { AIVariableResolutionRequest } from '@theia/ai-core';
24
24
  import { Key } from '@theia/core/lib/browser';
25
25
 
@@ -29,6 +29,10 @@ export interface AIChatTreeInputConfiguration extends AIChatInputConfiguration {
29
29
  export const AIChatTreeInputArgs = Symbol('AIChatTreeInputArgs');
30
30
  export interface AIChatTreeInputArgs {
31
31
  node: EditableRequestNode;
32
+ /**
33
+ * The branch of the chat tree for this request node (used by the input widget for state tracking).
34
+ */
35
+ branch?: ChatHierarchyBranch;
32
36
  initialValue?: string;
33
37
  onQuery: (query: string) => Promise<void>;
34
38
  onUnpin?: () => void;
@@ -60,6 +64,13 @@ export class AIChatTreeInputWidget extends AIChatInputWidget {
60
64
  @postConstruct()
61
65
  protected override init(): void {
62
66
  super.init();
67
+ this.updateBranch();
68
+
69
+ const request = this.requestNode.request;
70
+ this.toDispose.push(request.session.onDidChange(() => {
71
+ this.updateBranch();
72
+ }));
73
+
63
74
  this.addKeyListener(this.node, Key.ESCAPE, () => {
64
75
  this.request.cancelEdit();
65
76
  });
@@ -71,6 +82,10 @@ export class AIChatTreeInputWidget extends AIChatInputWidget {
71
82
  });
72
83
  }
73
84
 
85
+ protected updateBranch(): void {
86
+ this.branch = this.args.branch ?? this.requestNode.branch;
87
+ }
88
+
74
89
  protected override getResourceUri(): URI {
75
90
  return new URI(`ai-chat:/${this.requestNode.id}-input.${CHAT_VIEW_LANGUAGE_EXTENSION}`);
76
91
  }
@@ -64,7 +64,8 @@ import { AIChatTreeInputFactory, type AIChatTreeInputWidget } from './chat-view-
64
64
  // TODO Instead of directly operating on the ChatRequestModel we could use an intermediate view model
65
65
  export interface RequestNode extends TreeNode {
66
66
  request: ChatRequestModel,
67
- branch: ChatHierarchyBranch
67
+ branch: ChatHierarchyBranch,
68
+ sessionId: string
68
69
  }
69
70
  export const isRequestNode = (node: TreeNode): node is RequestNode => 'request' in node;
70
71
 
@@ -75,7 +76,8 @@ export const isEditableRequestNode = (node: TreeNode): node is EditableRequestNo
75
76
 
76
77
  // TODO Instead of directly operating on the ChatResponseModel we could use an intermediate view model
77
78
  export interface ResponseNode extends TreeNode {
78
- response: ChatResponseModel
79
+ response: ChatResponseModel,
80
+ sessionId: string
79
81
  }
80
82
  export const isResponseNode = (node: TreeNode): node is ResponseNode => 'response' in node;
81
83
 
@@ -136,6 +138,10 @@ export class ChatViewTreeWidget extends TreeWidget {
136
138
 
137
139
  protected isEnabled = false;
138
140
 
141
+ protected chatModelId: string;
142
+
143
+ onScrollLockChange?: (temporaryLocked: boolean) => void;
144
+
139
145
  set shouldScrollToEnd(shouldScrollToEnd: boolean) {
140
146
  this._shouldScrollToEnd = shouldScrollToEnd;
141
147
  this.shouldScrollToRow = this._shouldScrollToEnd;
@@ -178,6 +184,9 @@ export class ChatViewTreeWidget extends TreeWidget {
178
184
  widget.setEnabled(change);
179
185
  });
180
186
  this.update();
187
+ }),
188
+ this.onScroll(scrollEvent => {
189
+ this.handleScrollEvent(scrollEvent);
181
190
  })
182
191
  ]);
183
192
  }
@@ -187,6 +196,27 @@ export class ChatViewTreeWidget extends TreeWidget {
187
196
  this.update();
188
197
  }
189
198
 
199
+ protected handleScrollEvent(_scrollEvent: unknown): void {
200
+ // Check if we're at the bottom of the view
201
+ const isAtBottom = this.isScrolledToBottom();
202
+
203
+ // Only handle temporary scroll lock if auto-scroll is currently enabled
204
+ if (this.shouldScrollToEnd) {
205
+ if (!isAtBottom) {
206
+ // User scrolled away from bottom, enable temporary lock
207
+ this.setTemporaryScrollLock(true);
208
+ }
209
+ } else if (isAtBottom) {
210
+ // User scrolled back to bottom, disable temporary lock
211
+ this.setTemporaryScrollLock(false);
212
+ }
213
+ }
214
+
215
+ protected setTemporaryScrollLock(enabled: boolean): void {
216
+ // Immediately apply scroll lock changes without delay
217
+ this.onScrollLockChange?.(enabled);
218
+ }
219
+
190
220
  protected override renderTree(model: TreeModel): React.ReactNode {
191
221
  if (!this.isEnabled) {
192
222
  return this.renderDisabledMessage();
@@ -214,7 +244,8 @@ export class ChatViewTreeWidget extends TreeWidget {
214
244
  get request(): ChatRequestModel {
215
245
  return branch.get();
216
246
  },
217
- branch
247
+ branch,
248
+ sessionId: this.chatModelId
218
249
  };
219
250
  }
220
251
 
@@ -222,7 +253,8 @@ export class ChatViewTreeWidget extends TreeWidget {
222
253
  return {
223
254
  id: response.id,
224
255
  parent: this.model.root as CompositeTreeNode,
225
- response
256
+ response,
257
+ sessionId: this.chatModelId
226
258
  };
227
259
  }
228
260
 
@@ -292,6 +324,7 @@ export class ChatViewTreeWidget extends TreeWidget {
292
324
  protected async recreateModelTree(chatModel: ChatModel): Promise<void> {
293
325
  if (CompositeTreeNode.is(this.model.root)) {
294
326
  const nodes: TreeNode[] = [];
327
+ this.chatModelId = chatModel.id;
295
328
  chatModel.getBranches().forEach(branch => {
296
329
  const request = branch.get();
297
330
  nodes.push(this.mapRequestToNode(branch));
@@ -426,7 +459,8 @@ export class ChatViewTreeWidget extends TreeWidget {
426
459
  initialValue: editableNode.request.message.request.text,
427
460
  onQuery: async query => {
428
461
  editableNode.request.submitEdit({ text: query });
429
- }
462
+ },
463
+ branch: editableNode.branch
430
464
  });
431
465
 
432
466
  this.chatInputs.set(editableNode.id, widget);
@@ -13,7 +13,7 @@
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 } from '@theia/core';
16
+ import { CommandService, deepClone, Emitter, Event, MessageService, URI } from '@theia/core';
17
17
  import { ChatRequest, ChatRequestModel, ChatService, ChatSession, isActiveSessionChangedEvent, MutableChatModel } from '@theia/ai-chat';
18
18
  import { BaseWidget, codicon, ExtractableWidget, Message, PanelLayout, PreferenceService, StatefulWidget } from '@theia/core/lib/browser';
19
19
  import { nls } from '@theia/core/lib/common/nls';
@@ -28,6 +28,7 @@ import { FrontendVariableService } from '@theia/ai-core/lib/browser';
28
28
  export namespace ChatViewWidget {
29
29
  export interface State {
30
30
  locked?: boolean;
31
+ temporaryLocked?: boolean;
31
32
  }
32
33
  }
33
34
 
@@ -60,7 +61,7 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
60
61
 
61
62
  protected chatSession: ChatSession;
62
63
 
63
- protected _state: ChatViewWidget.State = { locked: false };
64
+ protected _state: ChatViewWidget.State = { locked: false, temporaryLocked: false };
64
65
  protected readonly onStateChangedEmitter = new Emitter<ChatViewWidget.State>();
65
66
 
66
67
  secondaryWindow: Window | undefined;
@@ -87,7 +88,8 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
87
88
  this.treeWidget,
88
89
  this.inputWidget,
89
90
  this.onStateChanged(newState => {
90
- this.treeWidget.shouldScrollToEnd = !newState.locked;
91
+ const shouldScrollToEnd = !newState.locked && !newState.temporaryLocked;
92
+ this.treeWidget.shouldScrollToEnd = shouldScrollToEnd;
91
93
  this.update();
92
94
  })
93
95
  ]);
@@ -107,6 +109,7 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
107
109
  this.inputWidget.onDeleteChangeSet = this.onDeleteChangeSet.bind(this);
108
110
  this.inputWidget.onDeleteChangeSetElement = this.onDeleteChangeSetElement.bind(this);
109
111
  this.treeWidget.trackChatModel(this.chatSession.model);
112
+ this.treeWidget.onScrollLockChange = this.onScrollLockChange.bind(this);
110
113
 
111
114
  this.initListeners();
112
115
 
@@ -161,6 +164,8 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
161
164
  if (oldState.locked) {
162
165
  copy.locked = oldState.locked;
163
166
  }
167
+ // Don't restore temporary lock state as it should reset on restart
168
+ copy.temporaryLocked = false;
164
169
  this.state = copy;
165
170
  }
166
171
 
@@ -210,16 +215,27 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
210
215
  this.chatService.deleteChangeSet(sessionId);
211
216
  }
212
217
 
213
- protected onDeleteChangeSetElement(sessionId: string, index: number): void {
214
- this.chatService.deleteChangeSetElement(sessionId, index);
218
+ protected onDeleteChangeSetElement(sessionId: string, uri: URI): void {
219
+ this.chatService.deleteChangeSetElement(sessionId, uri);
220
+ }
221
+
222
+ protected onScrollLockChange(temporaryLocked: boolean): void {
223
+ this.setTemporaryLock(temporaryLocked);
215
224
  }
216
225
 
217
226
  lock(): void {
218
- this.state = { ...deepClone(this.state), locked: true };
227
+ this.state = { ...deepClone(this.state), locked: true, temporaryLocked: false };
219
228
  }
220
229
 
221
230
  unlock(): void {
222
- this.state = { ...deepClone(this.state), locked: false };
231
+ this.state = { ...deepClone(this.state), locked: false, temporaryLocked: false };
232
+ }
233
+
234
+ setTemporaryLock(locked: boolean): void {
235
+ // Only set temporary lock if not permanently locked
236
+ if (!this.state.locked) {
237
+ this.state = { ...deepClone(this.state), temporaryLocked: locked };
238
+ }
223
239
  }
224
240
 
225
241
  get isLocked(): boolean {
@@ -624,6 +624,179 @@ div:last-child > .theia-ChatNode {
624
624
  cursor: pointer;
625
625
  }
626
626
 
627
+ /* Tool confirmation styles */
628
+ .theia-tool-confirmation {
629
+ margin: 10px 0;
630
+ padding: 12px;
631
+ border: 1px solid var(--theia-dropdown-border);
632
+ border-radius: 4px;
633
+ background-color: var(--theia-editorWidget-background);
634
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.14);
635
+ }
636
+
637
+ .theia-tool-confirmation-header {
638
+ font-weight: bold;
639
+ margin-bottom: 8px;
640
+ color: var(--theia-foreground);
641
+ display: flex;
642
+ align-items: center;
643
+ gap: 6px;
644
+ }
645
+
646
+ .theia-tool-confirmation-info {
647
+ margin-bottom: 12px;
648
+ }
649
+
650
+ .theia-tool-confirmation-name,
651
+ .theia-tool-confirmation-args {
652
+ margin-bottom: 4px;
653
+ }
654
+
655
+ .theia-tool-confirmation-name .label,
656
+ .theia-tool-confirmation-args .label {
657
+ font-weight: bold;
658
+ margin-right: 6px;
659
+ }
660
+
661
+ .theia-tool-confirmation-args pre.value {
662
+ margin: 6px 0;
663
+ padding: 8px;
664
+ background-color: var(--theia-editor-background);
665
+ border-radius: 3px;
666
+ max-height: 150px;
667
+ overflow: auto;
668
+ }
669
+
670
+ .theia-tool-confirmation-actions {
671
+ display: flex;
672
+ justify-content: flex-end;
673
+ gap: 0;
674
+ }
675
+
676
+ .theia-tool-confirmation-split-button {
677
+ display: inline-flex;
678
+ position: relative;
679
+ }
680
+
681
+ .theia-tool-confirmation-main-btn {
682
+ border-top-right-radius: 0 !important;
683
+ border-bottom-right-radius: 0 !important;
684
+ margin-right: 0 !important;
685
+ }
686
+
687
+ .theia-tool-confirmation-chevron-btn {
688
+ border-top-left-radius: 0 !important;
689
+ border-bottom-left-radius: 0 !important;
690
+ width: 24px !important;
691
+ min-width: 24px !important;
692
+ max-width: 24px !important;
693
+ padding: 0 !important;
694
+ margin-left: 0 !important;
695
+ display: flex !important;
696
+ align-items: center !important;
697
+ justify-content: center !important;
698
+ }
699
+
700
+ .theia-tool-confirmation-dropdown-menu {
701
+ position: absolute;
702
+ top: 100%;
703
+ left: 0;
704
+ z-index: 10;
705
+ min-width: 180px;
706
+ background: var(--theia-menu-background);
707
+ border: 1px solid var(--theia-menu-border);
708
+ border-radius: 4px;
709
+ box-shadow: 0 2px 8px rgba(0,0,0,0.15);
710
+ margin: 0;
711
+ padding: 0;
712
+ list-style: none;
713
+ }
714
+
715
+ .theia-tool-confirmation-dropdown-item {
716
+ padding: 6px 16px;
717
+ cursor: pointer;
718
+ white-space: nowrap;
719
+ }
720
+
721
+ .theia-tool-confirmation-dropdown-item:hover {
722
+ background: var(--theia-list-hoverBackground, #e5e5e5);
723
+ }
724
+
725
+ .theia-tool-confirmation-status {
726
+ margin: 10px 0;
727
+ padding: 12px;
728
+ border: 1px solid var(--theia-dropdown-border);
729
+ border-radius: 4px;
730
+ background-color: var(--theia-editorWidget-background);
731
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.14);
732
+ }
733
+
734
+ .theia-tool-confirmation-header {
735
+ font-weight: bold;
736
+ margin-bottom: 8px;
737
+ color: var(--theia-foreground);
738
+ display: flex;
739
+ align-items: center;
740
+ gap: 6px;
741
+ }
742
+
743
+ .theia-tool-confirmation-info {
744
+ margin-bottom: 12px;
745
+ }
746
+
747
+ .theia-tool-confirmation-name,
748
+ .theia-tool-confirmation-args {
749
+ margin-bottom: 4px;
750
+ }
751
+
752
+ .theia-tool-confirmation-name .label,
753
+ .theia-tool-confirmation-args .label {
754
+ font-weight: bold;
755
+ margin-right: 6px;
756
+ }
757
+
758
+ .theia-tool-confirmation-args pre.value {
759
+ margin: 6px 0;
760
+ padding: 8px;
761
+ background-color: var(--theia-editor-background);
762
+ border-radius: 3px;
763
+ max-height: 150px;
764
+ overflow: auto;
765
+ }
766
+
767
+ .theia-tool-confirmation-actions {
768
+ display: flex;
769
+ justify-content: flex-end;
770
+ gap: 8px;
771
+ }
772
+
773
+ .theia-tool-confirmation-status {
774
+ padding: 8px;
775
+ margin: 10px 0;
776
+ border-radius: 4px;
777
+ display: flex;
778
+ align-items: center;
779
+ gap: 6px;
780
+ }
781
+
782
+ .theia-tool-confirmation-status.approved {
783
+ background-color: var(--theia-successBackground);
784
+ color: var(--theia-successForeground);
785
+ }
786
+
787
+ .theia-tool-confirmation-status.denied {
788
+ background-color: var(--theia-errorBackground);
789
+ color: var(--theia-errorForeground);
790
+ }
791
+
792
+ .theia-tool-pending {
793
+ color: var(--theia-descriptionForeground);
794
+ }
795
+
796
+ .theia-tool-denied {
797
+ color: var(--theia-errorForeground);
798
+ }
799
+
627
800
  .theia-toolCall .fa,
628
801
  .theia-toolCall details summary::marker,
629
802
  .theia-thinking .fa,