@theia/ai-chat-ui 1.72.2 → 1.72.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-chat-ui-frontend-module.js +4 -0
- package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -1
- package/lib/browser/chat-response-renderer/delegation-tool-renderer.d.ts +8 -1
- package/lib/browser/chat-response-renderer/delegation-tool-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/delegation-tool-renderer.js +30 -1
- package/lib/browser/chat-response-renderer/delegation-tool-renderer.js.map +1 -1
- package/lib/browser/chat-response-renderer/tool-confirmation.d.ts +15 -1
- package/lib/browser/chat-response-renderer/tool-confirmation.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/tool-confirmation.js +60 -17
- package/lib/browser/chat-response-renderer/tool-confirmation.js.map +1 -1
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts +8 -1
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.js +30 -4
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -1
- package/lib/browser/chat-view-widget.d.ts +1 -0
- package/lib/browser/chat-view-widget.d.ts.map +1 -1
- package/lib/browser/chat-view-widget.js +3 -0
- package/lib/browser/chat-view-widget.js.map +1 -1
- package/lib/browser/tool-confirmation-keybinding-contribution.d.ts +25 -0
- package/lib/browser/tool-confirmation-keybinding-contribution.d.ts.map +1 -0
- package/lib/browser/tool-confirmation-keybinding-contribution.js +106 -0
- package/lib/browser/tool-confirmation-keybinding-contribution.js.map +1 -0
- package/package.json +12 -12
- package/src/browser/ai-chat-ui-frontend-module.ts +5 -0
- package/src/browser/chat-response-renderer/delegation-tool-renderer.tsx +34 -3
- package/src/browser/chat-response-renderer/tool-confirmation.tsx +122 -30
- package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +49 -4
- package/src/browser/chat-view-widget.tsx +4 -0
- package/src/browser/style/index.css +9 -0
- package/src/browser/tool-confirmation-keybinding-contribution.ts +104 -0
|
@@ -16,13 +16,14 @@
|
|
|
16
16
|
|
|
17
17
|
import * as React from '@theia/core/shared/react';
|
|
18
18
|
import { nls } from '@theia/core/lib/common/nls';
|
|
19
|
-
import { codicon, ContextMenuRenderer, OpenerService } from '@theia/core/lib/browser';
|
|
19
|
+
import { codicon, ContextMenuRenderer, LocalizedMarkdown, MarkdownRenderer, OpenerService } from '@theia/core/lib/browser';
|
|
20
20
|
import { ToolCallChatResponseContent } from '@theia/ai-chat/lib/common';
|
|
21
21
|
import { ToolRequest } from '@theia/ai-core';
|
|
22
22
|
import { CommandMenu, ContextExpressionMatcher, MenuPath } from '@theia/core/lib/common/menu';
|
|
23
23
|
import { GroupImpl } from '@theia/core/lib/browser/menu/composite-menu-node';
|
|
24
24
|
import { ToolConfirmationMode as ToolConfirmationPreferenceMode } from '@theia/ai-chat/lib/common/chat-tool-preferences';
|
|
25
25
|
import { ToolConfirmationManager } from '@theia/ai-chat/lib/browser/chat-tool-preference-bindings';
|
|
26
|
+
import { PendingToolConfirmationTracker } from '@theia/ai-chat/lib/browser/pending-tool-confirmation-tracker';
|
|
26
27
|
import { MarkdownRender } from './markdown-part-renderer';
|
|
27
28
|
import { condenseArguments, formatArgsForTooltip } from './toolcall-utils';
|
|
28
29
|
|
|
@@ -160,9 +161,16 @@ export interface ToolConfirmationCallbacks {
|
|
|
160
161
|
onDeny: (scope: ConfirmationScope, reason?: string) => void;
|
|
161
162
|
}
|
|
162
163
|
|
|
164
|
+
export interface ToolConfirmationKeybindingHints {
|
|
165
|
+
allow?: string;
|
|
166
|
+
deny?: string;
|
|
167
|
+
}
|
|
168
|
+
|
|
163
169
|
export interface ToolConfirmationActionsProps extends ToolConfirmationCallbacks {
|
|
164
170
|
toolName: string;
|
|
165
171
|
contextMenuRenderer: ContextMenuRenderer;
|
|
172
|
+
autoFocus?: boolean;
|
|
173
|
+
keybindingHints?: ToolConfirmationKeybindingHints;
|
|
166
174
|
}
|
|
167
175
|
|
|
168
176
|
export class InlineActionMenuNode implements CommandMenu {
|
|
@@ -196,7 +204,9 @@ export const ToolConfirmationActions: React.FC<ToolConfirmationActionsProps> = (
|
|
|
196
204
|
toolRequest,
|
|
197
205
|
onAllow,
|
|
198
206
|
onDeny,
|
|
199
|
-
contextMenuRenderer
|
|
207
|
+
contextMenuRenderer,
|
|
208
|
+
autoFocus,
|
|
209
|
+
keybindingHints
|
|
200
210
|
}) => {
|
|
201
211
|
const [allowScope, setAllowScope] = React.useState<ConfirmationScope>('once');
|
|
202
212
|
const [denyScope, setDenyScope] = React.useState<ConfirmationScope>('once');
|
|
@@ -205,6 +215,14 @@ export const ToolConfirmationActions: React.FC<ToolConfirmationActionsProps> = (
|
|
|
205
215
|
const [denyReason, setDenyReason] = React.useState('');
|
|
206
216
|
// eslint-disable-next-line no-null/no-null
|
|
207
217
|
const denyReasonInputRef = React.useRef<HTMLInputElement>(null);
|
|
218
|
+
// eslint-disable-next-line no-null/no-null
|
|
219
|
+
const allowButtonRef = React.useRef<HTMLButtonElement>(null);
|
|
220
|
+
|
|
221
|
+
React.useEffect(() => {
|
|
222
|
+
if (autoFocus && allowButtonRef.current) {
|
|
223
|
+
allowButtonRef.current.focus();
|
|
224
|
+
}
|
|
225
|
+
}, [autoFocus]);
|
|
208
226
|
|
|
209
227
|
const handleAllow = React.useCallback(() => {
|
|
210
228
|
if ((allowScope === 'forever' || allowScope === 'session') && toolRequest?.confirmAlwaysAllow) {
|
|
@@ -334,6 +352,9 @@ export const ToolConfirmationActions: React.FC<ToolConfirmationActionsProps> = (
|
|
|
334
352
|
const selectedScope = type === 'allow' ? allowScope : denyScope;
|
|
335
353
|
const setScope = type === 'allow' ? setAllowScope : setDenyScope;
|
|
336
354
|
const handleMain = type === 'allow' ? handleAllow : handleDeny;
|
|
355
|
+
const keybindingHint = type === 'allow' ? keybindingHints?.allow : keybindingHints?.deny;
|
|
356
|
+
const mainLabel = scopeLabel(type, selectedScope);
|
|
357
|
+
const mainTitle = keybindingHint && selectedScope === 'once' ? `${mainLabel} (${keybindingHint})` : undefined;
|
|
337
358
|
|
|
338
359
|
return (
|
|
339
360
|
<div
|
|
@@ -341,10 +362,12 @@ export const ToolConfirmationActions: React.FC<ToolConfirmationActionsProps> = (
|
|
|
341
362
|
style={{ display: 'inline-flex', position: 'relative' }}
|
|
342
363
|
>
|
|
343
364
|
<button
|
|
365
|
+
ref={type === 'allow' ? allowButtonRef : undefined}
|
|
344
366
|
className={`theia-button ${type === 'allow' ? 'main' : 'secondary'} theia-tool-confirmation-main-btn`}
|
|
345
367
|
onClick={handleMain}
|
|
368
|
+
title={mainTitle}
|
|
346
369
|
>
|
|
347
|
-
{
|
|
370
|
+
{mainLabel}
|
|
348
371
|
</button>
|
|
349
372
|
<button
|
|
350
373
|
className={`theia-button ${type === 'allow' ? 'main' : 'secondary'} theia-tool-confirmation-chevron-btn`}
|
|
@@ -433,10 +456,45 @@ export interface ToolConfirmationProps extends Pick<ToolConfirmationCallbacks, '
|
|
|
433
456
|
onDeny: (scope?: ConfirmationScope, reason?: string) => void;
|
|
434
457
|
contextMenuRenderer: ContextMenuRenderer;
|
|
435
458
|
openerService: OpenerService;
|
|
459
|
+
pendingTracker?: PendingToolConfirmationTracker;
|
|
460
|
+
keybindingHints?: ToolConfirmationKeybindingHints;
|
|
461
|
+
chatId?: string;
|
|
462
|
+
markdownRenderer?: MarkdownRenderer;
|
|
436
463
|
}
|
|
437
464
|
|
|
438
|
-
|
|
465
|
+
/**
|
|
466
|
+
* Command that opens the AI Configuration view on the Tools tab; its id is duplicated here as a
|
|
467
|
+
* string constant to keep ai-chat-ui free of an ai-ide dependency.
|
|
468
|
+
*/
|
|
469
|
+
const OPEN_TOOLS_CONFIGURATION_COMMAND_ID = 'aiConfiguration:openTools';
|
|
470
|
+
|
|
471
|
+
const ToolConfirmationIntro: React.FC<{ markdownRenderer: MarkdownRenderer }> = ({ markdownRenderer }) => (
|
|
472
|
+
<div className="theia-tool-confirmation-intro">
|
|
473
|
+
<LocalizedMarkdown
|
|
474
|
+
localizationKey="theia/ai/chat-ui/toolconfirmation/intro"
|
|
475
|
+
defaultMarkdown={
|
|
476
|
+
'AI agents may want to use tools to act on your workspace. ' +
|
|
477
|
+
'By default each tool call needs your confirmation. ' +
|
|
478
|
+
'You can change this default or pre-approve individual tools in the [Tools configuration view]({0}).'
|
|
479
|
+
}
|
|
480
|
+
args={[`command:${OPEN_TOOLS_CONFIGURATION_COMMAND_ID}`]}
|
|
481
|
+
markdownRenderer={markdownRenderer}
|
|
482
|
+
markdownOptions={{
|
|
483
|
+
isTrusted: { enabledCommands: [OPEN_TOOLS_CONFIGURATION_COMMAND_ID] }
|
|
484
|
+
}}
|
|
485
|
+
/>
|
|
486
|
+
</div>
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
export const ToolConfirmation: React.FC<ToolConfirmationProps> = ({
|
|
490
|
+
response, toolRequest, onAllow, onDeny, contextMenuRenderer, openerService, pendingTracker, keybindingHints, chatId, markdownRenderer
|
|
491
|
+
}) => {
|
|
439
492
|
const [state, setState] = React.useState<ToolConfirmationState>('waiting');
|
|
493
|
+
// Pure initializer (no side effects): decide whether the intro should render. Marking the chat
|
|
494
|
+
// as "intro shown" happens in the effect below to keep the initializer pure for StrictMode.
|
|
495
|
+
const [showIntro] = React.useState<boolean>(
|
|
496
|
+
() => !!(pendingTracker && chatId && markdownRenderer && !pendingTracker.hasShownIntro(chatId))
|
|
497
|
+
);
|
|
440
498
|
|
|
441
499
|
const handleAllow = React.useCallback((scope: ConfirmationScope) => {
|
|
442
500
|
setState('allowed');
|
|
@@ -448,6 +506,25 @@ export const ToolConfirmation: React.FC<ToolConfirmationProps> = ({ response, to
|
|
|
448
506
|
onDeny(scope, reason);
|
|
449
507
|
}, [onDeny]);
|
|
450
508
|
|
|
509
|
+
React.useEffect(() => {
|
|
510
|
+
if (showIntro && pendingTracker && chatId) {
|
|
511
|
+
pendingTracker.markIntroShown(chatId);
|
|
512
|
+
}
|
|
513
|
+
}, []);
|
|
514
|
+
|
|
515
|
+
React.useEffect(() => {
|
|
516
|
+
if (!pendingTracker || !chatId || state !== 'waiting') {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
const disposable = pendingTracker.register({
|
|
520
|
+
chatId,
|
|
521
|
+
response,
|
|
522
|
+
allow: () => handleAllow('once'),
|
|
523
|
+
deny: () => handleDeny('once')
|
|
524
|
+
});
|
|
525
|
+
return () => disposable.dispose();
|
|
526
|
+
}, [pendingTracker, chatId, response, handleAllow, handleDeny, state]);
|
|
527
|
+
|
|
451
528
|
if (state === 'allowed') {
|
|
452
529
|
return (
|
|
453
530
|
<div className="theia-tool-confirmation-status allowed">
|
|
@@ -472,32 +549,37 @@ export const ToolConfirmation: React.FC<ToolConfirmationProps> = ({ response, to
|
|
|
472
549
|
);
|
|
473
550
|
|
|
474
551
|
return (
|
|
475
|
-
|
|
476
|
-
<
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
<
|
|
484
|
-
{
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
552
|
+
<>
|
|
553
|
+
{showIntro && markdownRenderer && <ToolConfirmationIntro markdownRenderer={markdownRenderer} />}
|
|
554
|
+
<div className="theia-tool-confirmation">
|
|
555
|
+
<div className="theia-tool-confirmation-header">
|
|
556
|
+
<span className={codicon('shield')}></span> {nls.localize('theia/ai/chat-ui/toolconfirmation/header', 'Confirm Tool Execution')}
|
|
557
|
+
</div>
|
|
558
|
+
<div className="theia-tool-confirmation-info">
|
|
559
|
+
{toolRequest?.description ? (
|
|
560
|
+
<details className="theia-tool-confirmation-name">
|
|
561
|
+
<summary>{toolNameContent}</summary>
|
|
562
|
+
<div className="theia-tool-confirmation-description">
|
|
563
|
+
{toolRequest.description}
|
|
564
|
+
</div>
|
|
565
|
+
</details>
|
|
566
|
+
) : (
|
|
567
|
+
<div className="theia-tool-confirmation-name">{toolNameContent}</div>
|
|
568
|
+
)}
|
|
569
|
+
<ToolArgsDisplay args={response.arguments} openerService={openerService} />
|
|
570
|
+
</div>
|
|
571
|
+
<ToolConfirmationActions
|
|
572
|
+
toolName={response.name ?? 'unknown'}
|
|
573
|
+
toolRequest={toolRequest}
|
|
574
|
+
onAllow={handleAllow}
|
|
575
|
+
onDeny={handleDeny}
|
|
576
|
+
contextMenuRenderer={contextMenuRenderer}
|
|
577
|
+
autoFocus={true}
|
|
578
|
+
keybindingHints={keybindingHints}
|
|
579
|
+
/>
|
|
580
|
+
<CountdownTimer response={response} />
|
|
491
581
|
</div>
|
|
492
|
-
|
|
493
|
-
toolName={response.name ?? 'unknown'}
|
|
494
|
-
toolRequest={toolRequest}
|
|
495
|
-
onAllow={handleAllow}
|
|
496
|
-
onDeny={handleDeny}
|
|
497
|
-
contextMenuRenderer={contextMenuRenderer}
|
|
498
|
-
/>
|
|
499
|
-
<CountdownTimer response={response} />
|
|
500
|
-
</div>
|
|
582
|
+
</>
|
|
501
583
|
);
|
|
502
584
|
};
|
|
503
585
|
|
|
@@ -539,6 +621,9 @@ export interface WithToolCallConfirmationProps {
|
|
|
539
621
|
requestCanceled: boolean;
|
|
540
622
|
contextMenuRenderer: ContextMenuRenderer;
|
|
541
623
|
openerService: OpenerService;
|
|
624
|
+
pendingTracker?: PendingToolConfirmationTracker;
|
|
625
|
+
keybindingHints?: ToolConfirmationKeybindingHints;
|
|
626
|
+
markdownRenderer?: MarkdownRenderer;
|
|
542
627
|
}
|
|
543
628
|
|
|
544
629
|
export function withToolCallConfirmation<P extends object>(
|
|
@@ -559,7 +644,10 @@ export function withToolCallConfirmation<P extends object>(
|
|
|
559
644
|
showArgsTooltip,
|
|
560
645
|
requestCanceled,
|
|
561
646
|
contextMenuRenderer,
|
|
562
|
-
openerService
|
|
647
|
+
openerService,
|
|
648
|
+
pendingTracker,
|
|
649
|
+
keybindingHints,
|
|
650
|
+
markdownRenderer
|
|
563
651
|
} = toolConfirmation;
|
|
564
652
|
|
|
565
653
|
const { confirmationState } = useToolConfirmationState(response, confirmationMode);
|
|
@@ -622,6 +710,10 @@ export function withToolCallConfirmation<P extends object>(
|
|
|
622
710
|
onDeny={handleDeny}
|
|
623
711
|
contextMenuRenderer={contextMenuRenderer}
|
|
624
712
|
openerService={openerService}
|
|
713
|
+
pendingTracker={pendingTracker}
|
|
714
|
+
keybindingHints={keybindingHints}
|
|
715
|
+
chatId={chatId}
|
|
716
|
+
markdownRenderer={markdownRenderer}
|
|
625
717
|
/>
|
|
626
718
|
);
|
|
627
719
|
}
|
|
@@ -19,14 +19,19 @@ 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, ContextMenuRenderer, HoverService, OpenerService } from '@theia/core/lib/browser';
|
|
22
|
+
import { codicon, ContextMenuRenderer, HoverService, KeybindingRegistry, MarkdownRenderer, OpenerService } from '@theia/core/lib/browser';
|
|
23
23
|
import * as React from '@theia/core/shared/react';
|
|
24
|
-
import { createConfirmationHandlers, ToolConfirmation, useToolConfirmationState } from './tool-confirmation';
|
|
24
|
+
import { createConfirmationHandlers, ToolConfirmation, ToolConfirmationKeybindingHints, useToolConfirmationState } from './tool-confirmation';
|
|
25
25
|
import { ToolConfirmationMode } from '@theia/ai-chat/lib/common/chat-tool-preferences';
|
|
26
26
|
import { ResponseNode } from '../chat-tree-view';
|
|
27
27
|
import { MarkdownRender } from './markdown-part-renderer';
|
|
28
28
|
import { isToolCallContent, ToolCallResult, ToolInvocationRegistry, ToolRequest } from '@theia/ai-core';
|
|
29
29
|
import { ToolConfirmationManager } from '@theia/ai-chat/lib/browser/chat-tool-preference-bindings';
|
|
30
|
+
import { PendingToolConfirmationTracker } from '@theia/ai-chat/lib/browser/pending-tool-confirmation-tracker';
|
|
31
|
+
import {
|
|
32
|
+
APPROVE_LATEST_TOOL_CONFIRMATION_COMMAND,
|
|
33
|
+
DENY_LATEST_TOOL_CONFIRMATION_COMMAND
|
|
34
|
+
} from '../tool-confirmation-keybinding-contribution';
|
|
30
35
|
import { condenseArguments, formatArgsForTooltip } from './toolcall-utils';
|
|
31
36
|
|
|
32
37
|
@injectable()
|
|
@@ -47,6 +52,15 @@ export class ToolCallPartRenderer implements ChatResponsePartRenderer<ToolCallCh
|
|
|
47
52
|
@inject(ContextMenuRenderer)
|
|
48
53
|
protected contextMenuRenderer: ContextMenuRenderer;
|
|
49
54
|
|
|
55
|
+
@inject(PendingToolConfirmationTracker)
|
|
56
|
+
protected pendingToolConfirmationTracker: PendingToolConfirmationTracker;
|
|
57
|
+
|
|
58
|
+
@inject(KeybindingRegistry)
|
|
59
|
+
protected keybindingRegistry: KeybindingRegistry;
|
|
60
|
+
|
|
61
|
+
@inject(MarkdownRenderer)
|
|
62
|
+
protected markdownRenderer: MarkdownRenderer;
|
|
63
|
+
|
|
50
64
|
canHandle(response: ChatResponseContent): number {
|
|
51
65
|
if (ToolCallChatResponseContent.is(response)) {
|
|
52
66
|
return 10;
|
|
@@ -68,6 +82,10 @@ export class ToolCallPartRenderer implements ChatResponsePartRenderer<ToolCallCh
|
|
|
68
82
|
onDeny={handleDeny}
|
|
69
83
|
contextMenuRenderer={this.contextMenuRenderer}
|
|
70
84
|
openerService={this.openerService}
|
|
85
|
+
pendingTracker={this.pendingToolConfirmationTracker}
|
|
86
|
+
keybindingHints={this.getKeybindingHints()}
|
|
87
|
+
chatId={chatId}
|
|
88
|
+
markdownRenderer={this.markdownRenderer}
|
|
71
89
|
/>;
|
|
72
90
|
}
|
|
73
91
|
|
|
@@ -86,7 +104,24 @@ export class ToolCallPartRenderer implements ChatResponsePartRenderer<ToolCallCh
|
|
|
86
104
|
responseRenderer={this.renderResult.bind(this)}
|
|
87
105
|
requestCanceled={parentNode.response.isCanceled}
|
|
88
106
|
contextMenuRenderer={this.contextMenuRenderer}
|
|
89
|
-
openerService={this.openerService}
|
|
107
|
+
openerService={this.openerService}
|
|
108
|
+
pendingTracker={this.pendingToolConfirmationTracker}
|
|
109
|
+
keybindingHints={this.getKeybindingHints()}
|
|
110
|
+
markdownRenderer={this.markdownRenderer} />;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
protected getKeybindingHints(): ToolConfirmationKeybindingHints {
|
|
114
|
+
const allow = this.formatKeybinding(APPROVE_LATEST_TOOL_CONFIRMATION_COMMAND.id);
|
|
115
|
+
const deny = this.formatKeybinding(DENY_LATEST_TOOL_CONFIRMATION_COMMAND.id);
|
|
116
|
+
return { allow, deny };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
protected formatKeybinding(commandId: string): string | undefined {
|
|
120
|
+
const bindings = this.keybindingRegistry.getKeybindingsForCommand(commandId);
|
|
121
|
+
if (!bindings.length) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
return this.keybindingRegistry.acceleratorFor(bindings[0], '+').join('+');
|
|
90
125
|
}
|
|
91
126
|
|
|
92
127
|
protected renderResult(response: ToolCallChatResponseContent): ReactNode {
|
|
@@ -190,6 +225,9 @@ interface ToolCallContentProps {
|
|
|
190
225
|
requestCanceled: boolean;
|
|
191
226
|
contextMenuRenderer: ContextMenuRenderer;
|
|
192
227
|
openerService: OpenerService;
|
|
228
|
+
pendingTracker?: PendingToolConfirmationTracker;
|
|
229
|
+
keybindingHints?: ToolConfirmationKeybindingHints;
|
|
230
|
+
markdownRenderer?: MarkdownRenderer;
|
|
193
231
|
}
|
|
194
232
|
|
|
195
233
|
/**
|
|
@@ -206,7 +244,10 @@ const ToolCallContent: React.FC<ToolCallContentProps> = ({
|
|
|
206
244
|
requestCanceled,
|
|
207
245
|
showArgsTooltip,
|
|
208
246
|
contextMenuRenderer,
|
|
209
|
-
openerService
|
|
247
|
+
openerService,
|
|
248
|
+
pendingTracker,
|
|
249
|
+
keybindingHints,
|
|
250
|
+
markdownRenderer
|
|
210
251
|
}) => {
|
|
211
252
|
const { confirmationState, rejectionReason } = useToolConfirmationState(response, confirmationMode);
|
|
212
253
|
const summaryRef = React.useRef<HTMLElement | undefined>(undefined);
|
|
@@ -297,6 +338,10 @@ const ToolCallContent: React.FC<ToolCallContentProps> = ({
|
|
|
297
338
|
onDeny={handleDeny}
|
|
298
339
|
contextMenuRenderer={contextMenuRenderer}
|
|
299
340
|
openerService={openerService}
|
|
341
|
+
pendingTracker={pendingTracker}
|
|
342
|
+
keybindingHints={keybindingHints}
|
|
343
|
+
chatId={chatId}
|
|
344
|
+
markdownRenderer={markdownRenderer}
|
|
300
345
|
/>
|
|
301
346
|
</span>
|
|
302
347
|
)}
|
|
@@ -341,6 +341,10 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
|
|
|
341
341
|
getSettings(): ChatSessionSettings | undefined {
|
|
342
342
|
return this.chatSession.model.settings;
|
|
343
343
|
}
|
|
344
|
+
|
|
345
|
+
get sessionId(): string {
|
|
346
|
+
return this.chatSession.id;
|
|
347
|
+
}
|
|
344
348
|
}
|
|
345
349
|
|
|
346
350
|
export namespace ChatViewWidget {
|
|
@@ -991,6 +991,15 @@ div:last-child>.theia-ChatNode {
|
|
|
991
991
|
cursor: default;
|
|
992
992
|
}
|
|
993
993
|
|
|
994
|
+
.theia-tool-confirmation-intro {
|
|
995
|
+
margin: calc(var(--theia-ui-padding) * 2) 0;
|
|
996
|
+
line-height: var(--theia-content-line-height);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
.theia-tool-confirmation-intro p {
|
|
1000
|
+
margin: 0;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
994
1003
|
.theia-tool-confirmation-header {
|
|
995
1004
|
font-weight: bold;
|
|
996
1005
|
margin-bottom: 8px;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 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 { Command, CommandContribution, CommandRegistry } from '@theia/core';
|
|
18
|
+
import { ApplicationShell, KeybindingContribution, KeybindingRegistry } from '@theia/core/lib/browser';
|
|
19
|
+
import { ContextKey, ContextKeyService } from '@theia/core/lib/browser/context-key-service';
|
|
20
|
+
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
|
21
|
+
import { PendingToolConfirmation, PendingToolConfirmationTracker } from '@theia/ai-chat/lib/browser/pending-tool-confirmation-tracker';
|
|
22
|
+
import { ChatViewWidget } from './chat-view-widget';
|
|
23
|
+
|
|
24
|
+
export const HAS_PENDING_TOOL_CONFIRMATION_CONTEXT_KEY = 'theiaAi.hasPendingToolConfirmation';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Only fire the approve/deny shortcuts while the chat view is focused, so e.g. `Ctrl+Enter` in an
|
|
28
|
+
* editor is never shadowed by a confirmation pending in some background chat.
|
|
29
|
+
*/
|
|
30
|
+
const CHAT_VIEW_FOCUS_WHEN = '(chatInputFocus || chatResponseFocus)';
|
|
31
|
+
|
|
32
|
+
export const APPROVE_LATEST_TOOL_CONFIRMATION_COMMAND = Command.toLocalizedCommand({
|
|
33
|
+
id: 'theia.ai.chat.approveLatestToolConfirmation',
|
|
34
|
+
label: 'Approve Latest Tool Confirmation'
|
|
35
|
+
}, 'theia/ai/chat-ui/toolconfirmation/approveLatest');
|
|
36
|
+
|
|
37
|
+
export const DENY_LATEST_TOOL_CONFIRMATION_COMMAND = Command.toLocalizedCommand({
|
|
38
|
+
id: 'theia.ai.chat.denyLatestToolConfirmation',
|
|
39
|
+
label: 'Deny Latest Tool Confirmation'
|
|
40
|
+
}, 'theia/ai/chat-ui/toolconfirmation/denyLatest');
|
|
41
|
+
|
|
42
|
+
@injectable()
|
|
43
|
+
export class ToolConfirmationKeybindingContribution implements CommandContribution, KeybindingContribution {
|
|
44
|
+
|
|
45
|
+
@inject(PendingToolConfirmationTracker)
|
|
46
|
+
protected readonly pendingTracker: PendingToolConfirmationTracker;
|
|
47
|
+
|
|
48
|
+
@inject(ContextKeyService)
|
|
49
|
+
protected readonly contextKeyService: ContextKeyService;
|
|
50
|
+
|
|
51
|
+
@inject(ApplicationShell)
|
|
52
|
+
protected readonly shell: ApplicationShell;
|
|
53
|
+
|
|
54
|
+
protected hasPendingKey: ContextKey<boolean>;
|
|
55
|
+
|
|
56
|
+
@postConstruct()
|
|
57
|
+
protected init(): void {
|
|
58
|
+
this.hasPendingKey = this.contextKeyService.createKey<boolean>(HAS_PENDING_TOOL_CONFIRMATION_CONTEXT_KEY, false);
|
|
59
|
+
this.pendingTracker.onChanged(() => this.hasPendingKey.set(this.pendingTracker.hasPending()));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
registerCommands(commands: CommandRegistry): void {
|
|
63
|
+
commands.registerCommand(APPROVE_LATEST_TOOL_CONFIRMATION_COMMAND, {
|
|
64
|
+
isEnabled: () => this.hasPendingInActiveChat(),
|
|
65
|
+
execute: () => this.getLatestInActiveChat()?.allow()
|
|
66
|
+
});
|
|
67
|
+
commands.registerCommand(DENY_LATEST_TOOL_CONFIRMATION_COMMAND, {
|
|
68
|
+
isEnabled: () => this.hasPendingInActiveChat(),
|
|
69
|
+
execute: () => this.getLatestInActiveChat()?.deny()
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
registerKeybindings(keybindings: KeybindingRegistry): void {
|
|
74
|
+
keybindings.registerKeybinding({
|
|
75
|
+
command: APPROVE_LATEST_TOOL_CONFIRMATION_COMMAND.id,
|
|
76
|
+
keybinding: 'ctrlcmd+enter',
|
|
77
|
+
when: `${HAS_PENDING_TOOL_CONFIRMATION_CONTEXT_KEY} && ${CHAT_VIEW_FOCUS_WHEN}`
|
|
78
|
+
});
|
|
79
|
+
keybindings.registerKeybinding({
|
|
80
|
+
command: DENY_LATEST_TOOL_CONFIRMATION_COMMAND.id,
|
|
81
|
+
keybinding: 'ctrlcmd+shift+backspace',
|
|
82
|
+
when: `${HAS_PENDING_TOOL_CONFIRMATION_CONTEXT_KEY} && ${CHAT_VIEW_FOCUS_WHEN}`
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The id of the chat the user is currently interacting with, or `undefined` if no chat view is
|
|
88
|
+
* focused. Used to target the shortcuts at the visible confirmation rather than the globally
|
|
89
|
+
* newest one.
|
|
90
|
+
*/
|
|
91
|
+
protected getActiveChatId(): string | undefined {
|
|
92
|
+
return ChatViewWidget.findActive(this.shell)?.sessionId;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
protected hasPendingInActiveChat(): boolean {
|
|
96
|
+
const chatId = this.getActiveChatId();
|
|
97
|
+
return chatId !== undefined && this.pendingTracker.hasPending(chatId);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
protected getLatestInActiveChat(): PendingToolConfirmation | undefined {
|
|
101
|
+
const chatId = this.getActiveChatId();
|
|
102
|
+
return chatId === undefined ? undefined : this.pendingTracker.getLatest(chatId);
|
|
103
|
+
}
|
|
104
|
+
}
|