@theia/ai-chat-ui 1.71.0-next.8 → 1.71.1
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-contribution.d.ts.map +1 -1
- package/lib/browser/ai-chat-ui-contribution.js +10 -9
- package/lib/browser/ai-chat-ui-contribution.js.map +1 -1
- package/lib/browser/ai-chat-ui-frontend-module.d.ts +1 -0
- package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-chat-ui-frontend-module.js +3 -0
- package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -1
- package/lib/browser/chat-input-mode-contribution.js +1 -1
- package/lib/browser/chat-input-mode-contribution.js.map +1 -1
- package/lib/browser/chat-input-widget.d.ts +57 -3
- package/lib/browser/chat-input-widget.d.ts.map +1 -1
- package/lib/browser/chat-input-widget.js +382 -21
- package/lib/browser/chat-input-widget.js.map +1 -1
- package/lib/browser/chat-response-part-renderer.d.ts +6 -0
- package/lib/browser/chat-response-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/delegation-tool-renderer.d.ts +2 -0
- package/lib/browser/chat-response-renderer/delegation-tool-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/delegation-tool-renderer.js +76 -5
- package/lib/browser/chat-response-renderer/delegation-tool-renderer.js.map +1 -1
- package/lib/browser/chat-response-renderer/index.d.ts +1 -0
- package/lib/browser/chat-response-renderer/index.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/index.js +1 -0
- package/lib/browser/chat-response-renderer/index.js.map +1 -1
- package/lib/browser/chat-response-renderer/question-part-renderer.d.ts +3 -0
- package/lib/browser/chat-response-renderer/question-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/question-part-renderer.js +19 -6
- package/lib/browser/chat-response-renderer/question-part-renderer.js.map +1 -1
- package/lib/browser/chat-response-renderer/tool-call-rendering.d.ts +21 -0
- package/lib/browser/chat-response-renderer/tool-call-rendering.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/tool-call-rendering.js +53 -0
- package/lib/browser/chat-response-renderer/tool-call-rendering.js.map +1 -0
- package/lib/browser/chat-response-renderer/tool-confirmation.d.ts +4 -0
- package/lib/browser/chat-response-renderer/tool-confirmation.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/tool-confirmation.js +22 -0
- package/lib/browser/chat-response-renderer/tool-confirmation.js.map +1 -1
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts +1 -0
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.js +7 -18
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -1
- package/lib/browser/chat-response-renderer/toolcall-utils.d.ts +1 -0
- package/lib/browser/chat-response-renderer/toolcall-utils.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/toolcall-utils.js +3 -0
- package/lib/browser/chat-response-renderer/toolcall-utils.js.map +1 -1
- package/lib/browser/chat-token-usage-indicator-util.d.ts +35 -0
- package/lib/browser/chat-token-usage-indicator-util.d.ts.map +1 -0
- package/lib/browser/chat-token-usage-indicator-util.js +138 -0
- package/lib/browser/chat-token-usage-indicator-util.js.map +1 -0
- package/lib/browser/chat-token-usage-indicator-util.spec.d.ts +2 -0
- package/lib/browser/chat-token-usage-indicator-util.spec.d.ts.map +1 -0
- package/lib/browser/chat-token-usage-indicator-util.spec.js +226 -0
- package/lib/browser/chat-token-usage-indicator-util.spec.js.map +1 -0
- package/lib/browser/chat-tree-view/chat-view-tree-input-widget.d.ts +6 -1
- package/lib/browser/chat-tree-view/chat-view-tree-input-widget.d.ts.map +1 -1
- package/lib/browser/chat-tree-view/chat-view-tree-input-widget.js +7 -0
- package/lib/browser/chat-tree-view/chat-view-tree-input-widget.js.map +1 -1
- package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts.map +1 -1
- package/lib/browser/chat-tree-view/chat-view-tree-widget.js +20 -3
- package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -1
- package/lib/browser/chat-view-preferences.d.ts +18 -0
- package/lib/browser/chat-view-preferences.d.ts.map +1 -0
- package/lib/browser/chat-view-preferences.js +69 -0
- package/lib/browser/chat-view-preferences.js.map +1 -0
- package/lib/browser/chat-view-widget-toolbar-contribution.d.ts.map +1 -1
- package/lib/browser/chat-view-widget-toolbar-contribution.js +3 -2
- package/lib/browser/chat-view-widget-toolbar-contribution.js.map +1 -1
- package/lib/browser/chat-view-widget.d.ts +1 -2
- package/lib/browser/chat-view-widget.d.ts.map +1 -1
- package/lib/browser/chat-view-widget.js +3 -7
- package/lib/browser/chat-view-widget.js.map +1 -1
- package/lib/browser/session-settings-dialog.d.ts +2 -5
- package/lib/browser/session-settings-dialog.d.ts.map +1 -1
- package/lib/browser/session-settings-dialog.js +15 -33
- package/lib/browser/session-settings-dialog.js.map +1 -1
- package/lib/common/toolcall-utils.d.ts +6 -0
- package/lib/common/toolcall-utils.d.ts.map +1 -0
- package/lib/common/toolcall-utils.js +40 -0
- package/lib/common/toolcall-utils.js.map +1 -0
- package/lib/common/toolcall-utils.spec.d.ts +2 -0
- package/lib/common/toolcall-utils.spec.d.ts.map +1 -0
- package/lib/common/toolcall-utils.spec.js +102 -0
- package/lib/common/toolcall-utils.spec.js.map +1 -0
- package/package.json +12 -12
- package/src/browser/ai-chat-ui-contribution.ts +10 -9
- package/src/browser/ai-chat-ui-frontend-module.ts +4 -0
- package/src/browser/chat-input-mode-contribution.ts +1 -1
- package/src/browser/chat-input-widget.tsx +481 -14
- package/src/browser/chat-response-part-renderer.ts +6 -0
- package/src/browser/chat-response-renderer/delegation-tool-renderer.tsx +105 -7
- package/src/browser/chat-response-renderer/index.ts +1 -0
- package/src/browser/chat-response-renderer/question-part-renderer.tsx +24 -8
- package/src/browser/chat-response-renderer/tool-call-rendering.tsx +89 -0
- package/src/browser/chat-response-renderer/tool-confirmation.tsx +26 -0
- package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +21 -18
- package/src/browser/chat-response-renderer/toolcall-utils.ts +2 -0
- package/src/browser/chat-token-usage-indicator-util.spec.ts +262 -0
- package/src/browser/chat-token-usage-indicator-util.ts +151 -0
- package/src/browser/chat-tree-view/chat-view-tree-input-widget.tsx +9 -1
- package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +22 -3
- package/src/browser/chat-view-preferences.ts +87 -0
- package/src/browser/chat-view-widget-toolbar-contribution.tsx +3 -2
- package/src/browser/chat-view-widget.tsx +4 -7
- package/src/browser/session-settings-dialog.tsx +15 -97
- package/src/browser/style/index.css +164 -54
- package/src/browser/style/tool-call-rendering.css +327 -0
- package/src/common/toolcall-utils.spec.ts +118 -0
- package/src/common/toolcall-utils.ts +36 -0
|
@@ -22,4 +22,10 @@ export const ChatResponsePartRenderer = Symbol('ChatResponsePartRenderer');
|
|
|
22
22
|
export interface ChatResponsePartRenderer<T extends ChatResponseContent> {
|
|
23
23
|
canHandle(response: ChatResponseContent): number;
|
|
24
24
|
render(response: T, parentNode: ResponseNode): ReactNode;
|
|
25
|
+
/**
|
|
26
|
+
* Optional method to render a compact confirmation/interaction view for this content type.
|
|
27
|
+
* Used by the delegation summary to show tool-specific confirmation UI in collapsed state.
|
|
28
|
+
* If not implemented, a generic fallback confirmation UI is used.
|
|
29
|
+
*/
|
|
30
|
+
renderConfirmation?(response: T, parentNode: ResponseNode): ReactNode;
|
|
25
31
|
}
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
|
-
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
18
|
-
import { ChatRequestInvocation, ChatResponseContent, ChatResponseModel, ToolCallChatResponseContent } from '@theia/ai-chat';
|
|
17
|
+
import { inject, injectable, named } from '@theia/core/shared/inversify';
|
|
18
|
+
import { ChatRequestInvocation, ChatResponseContent, ChatResponseModel, InteractiveContent, ToolCallChatResponseContent } from '@theia/ai-chat';
|
|
19
19
|
import { ChatAgentService } from '@theia/ai-chat/lib/common/chat-agent-service';
|
|
20
20
|
import { ToolConfirmationManager } from '@theia/ai-chat/lib/browser/chat-tool-preference-bindings';
|
|
21
21
|
import { AGENT_DELEGATION_FUNCTION_ID } from '@theia/ai-core/lib/common/tool-constants';
|
|
@@ -25,8 +25,9 @@ import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
|
|
|
25
25
|
import { ResponseNode } from '../chat-tree-view';
|
|
26
26
|
import { SubChatWidgetFactory } from '../chat-tree-view/sub-chat-widget';
|
|
27
27
|
import { withToolCallConfirmation } from './tool-confirmation';
|
|
28
|
+
import { extractJsonStringField } from './toolcall-utils';
|
|
28
29
|
import { CompositeTreeNode, ContextMenuRenderer } from '@theia/core/lib/browser';
|
|
29
|
-
import { DisposableCollection, nls } from '@theia/core';
|
|
30
|
+
import { ContributionProvider, DisposableCollection, nls } from '@theia/core';
|
|
30
31
|
import * as React from '@theia/core/shared/react';
|
|
31
32
|
|
|
32
33
|
@injectable()
|
|
@@ -50,6 +51,9 @@ export class DelegationToolRenderer implements ChatResponsePartRenderer<ToolCall
|
|
|
50
51
|
@inject(ContextMenuRenderer)
|
|
51
52
|
protected contextMenuRenderer: ContextMenuRenderer;
|
|
52
53
|
|
|
54
|
+
@inject(ContributionProvider) @named(ChatResponsePartRenderer)
|
|
55
|
+
protected chatResponsePartRenderers: ContributionProvider<ChatResponsePartRenderer<ChatResponseContent>>;
|
|
56
|
+
|
|
53
57
|
canHandle(response: ChatResponseContent): number {
|
|
54
58
|
if (ToolCallChatResponseContent.is(response) && response.name === AGENT_DELEGATION_FUNCTION_ID) {
|
|
55
59
|
return 20;
|
|
@@ -72,7 +76,14 @@ export class DelegationToolRenderer implements ChatResponsePartRenderer<ToolCall
|
|
|
72
76
|
prompt = args.prompt;
|
|
73
77
|
}
|
|
74
78
|
} catch {
|
|
75
|
-
|
|
79
|
+
const partialAgentId = extractJsonStringField(response.arguments, 'agentId');
|
|
80
|
+
if (partialAgentId) {
|
|
81
|
+
agentName = this.chatAgentService.getAgent(partialAgentId)?.name ?? partialAgentId;
|
|
82
|
+
}
|
|
83
|
+
const partialPrompt = extractJsonStringField(response.arguments, 'prompt');
|
|
84
|
+
if (partialPrompt) {
|
|
85
|
+
prompt = partialPrompt;
|
|
86
|
+
}
|
|
76
87
|
}
|
|
77
88
|
}
|
|
78
89
|
|
|
@@ -87,17 +98,23 @@ export class DelegationToolRenderer implements ChatResponsePartRenderer<ToolCall
|
|
|
87
98
|
finished={response.finished}
|
|
88
99
|
parentNode={parentNode}
|
|
89
100
|
subChatWidgetFactory={this.subChatWidgetFactory}
|
|
101
|
+
contextMenuRenderer={this.contextMenuRenderer}
|
|
102
|
+
chatResponsePartRenderers={this.chatResponsePartRenderers}
|
|
90
103
|
response={response}
|
|
91
104
|
confirmationMode={confirmationMode}
|
|
92
105
|
toolConfirmationManager={this.toolConfirmationManager}
|
|
93
106
|
toolRequest={toolRequest}
|
|
94
107
|
chatId={chatId}
|
|
95
108
|
requestCanceled={parentNode.response.isCanceled}
|
|
96
|
-
contextMenuRenderer={this.contextMenuRenderer}
|
|
97
109
|
/>;
|
|
98
110
|
}
|
|
99
111
|
}
|
|
100
112
|
|
|
113
|
+
interface PendingInteraction {
|
|
114
|
+
contentPart: InteractiveContent & ChatResponseContent;
|
|
115
|
+
id: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
101
118
|
interface DelegatedChatProps {
|
|
102
119
|
invocation?: ChatRequestInvocation;
|
|
103
120
|
agentName: string;
|
|
@@ -105,19 +122,28 @@ interface DelegatedChatProps {
|
|
|
105
122
|
finished?: boolean;
|
|
106
123
|
parentNode: ResponseNode;
|
|
107
124
|
subChatWidgetFactory: SubChatWidgetFactory;
|
|
125
|
+
contextMenuRenderer: ContextMenuRenderer;
|
|
126
|
+
chatResponsePartRenderers: ContributionProvider<ChatResponsePartRenderer<ChatResponseContent>>;
|
|
108
127
|
}
|
|
109
128
|
|
|
110
129
|
interface DelegatedChatState {
|
|
111
130
|
node?: ResponseNode;
|
|
131
|
+
isOpen: boolean;
|
|
132
|
+
pendingInteractions: PendingInteraction[];
|
|
112
133
|
}
|
|
113
134
|
|
|
114
135
|
class DelegatedChat extends React.Component<DelegatedChatProps, DelegatedChatState> {
|
|
115
136
|
private widget: ReturnType<SubChatWidgetFactory>;
|
|
116
137
|
private toDispose = new DisposableCollection();
|
|
138
|
+
private trackedInteractionIds = new Set<string>();
|
|
117
139
|
|
|
118
140
|
constructor(props: DelegatedChatProps) {
|
|
119
141
|
super(props);
|
|
120
|
-
this.state = {
|
|
142
|
+
this.state = {
|
|
143
|
+
node: undefined,
|
|
144
|
+
isOpen: false,
|
|
145
|
+
pendingInteractions: []
|
|
146
|
+
};
|
|
121
147
|
this.widget = props.subChatWidgetFactory();
|
|
122
148
|
}
|
|
123
149
|
|
|
@@ -134,6 +160,7 @@ class DelegatedChat extends React.Component<DelegatedChatProps, DelegatedChatSta
|
|
|
134
160
|
private subscribeToInvocation(invocation?: ChatRequestInvocation): void {
|
|
135
161
|
this.toDispose.dispose();
|
|
136
162
|
this.toDispose = new DisposableCollection();
|
|
163
|
+
this.trackedInteractionIds.clear();
|
|
137
164
|
if (!invocation) {
|
|
138
165
|
return;
|
|
139
166
|
}
|
|
@@ -143,9 +170,25 @@ class DelegatedChat extends React.Component<DelegatedChatProps, DelegatedChatSta
|
|
|
143
170
|
this.setState({ node });
|
|
144
171
|
|
|
145
172
|
const changeListener = () => {
|
|
173
|
+
this.removeResolvedInteractions();
|
|
146
174
|
this.forceUpdate();
|
|
147
175
|
};
|
|
148
176
|
this.toDispose.push(chatModel.onDidChange(changeListener));
|
|
177
|
+
|
|
178
|
+
// Subscribe to interactionNeeded for push-based interaction tracking
|
|
179
|
+
this.toDispose.push(chatModel.onInteractionNeeded(contentPart => {
|
|
180
|
+
const id = contentPart.interactionId;
|
|
181
|
+
if (id && !this.trackedInteractionIds.has(id)
|
|
182
|
+
&& this.findConfirmationRenderer(contentPart)) {
|
|
183
|
+
this.trackedInteractionIds.add(id);
|
|
184
|
+
this.setState(prevState => ({
|
|
185
|
+
pendingInteractions: [
|
|
186
|
+
...prevState.pendingInteractions,
|
|
187
|
+
{ contentPart, id }
|
|
188
|
+
]
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
191
|
+
}));
|
|
149
192
|
}).catch(error => {
|
|
150
193
|
console.error('Failed to create delegated chat response:', error);
|
|
151
194
|
});
|
|
@@ -160,6 +203,42 @@ class DelegatedChat extends React.Component<DelegatedChatProps, DelegatedChatSta
|
|
|
160
203
|
|
|
161
204
|
override componentWillUnmount(): void {
|
|
162
205
|
this.toDispose.dispose();
|
|
206
|
+
this.trackedInteractionIds.clear();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private removeResolvedInteractions(): void {
|
|
210
|
+
this.setState(prevState => ({
|
|
211
|
+
pendingInteractions: prevState.pendingInteractions.filter(p => !p.contentPart.isResolved)
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private findConfirmationRenderer(contentPart: ChatResponseContent): ChatResponsePartRenderer<ChatResponseContent> | undefined {
|
|
216
|
+
const renderer = this.props.chatResponsePartRenderers.getContributions().reduce<[number, ChatResponsePartRenderer<ChatResponseContent> | undefined]>(
|
|
217
|
+
(prev, current) => {
|
|
218
|
+
const prio = current.canHandle(contentPart);
|
|
219
|
+
if (prio > prev[0]) {
|
|
220
|
+
return [prio, current];
|
|
221
|
+
}
|
|
222
|
+
return prev;
|
|
223
|
+
},
|
|
224
|
+
[-1, undefined])[1];
|
|
225
|
+
if (renderer && renderer.renderConfirmation) {
|
|
226
|
+
return renderer;
|
|
227
|
+
}
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private handleToggle = (event: React.SyntheticEvent<HTMLDetailsElement>): void => {
|
|
232
|
+
const details = event.currentTarget;
|
|
233
|
+
this.setState({ isOpen: details.open });
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
private renderInteractionConfirmation(contentPart: ChatResponseContent, id: string): React.ReactNode {
|
|
237
|
+
const renderer = this.findConfirmationRenderer(contentPart);
|
|
238
|
+
if (renderer && this.state.node) {
|
|
239
|
+
return <React.Fragment key={id}>{renderer.renderConfirmation!(contentPart, this.state.node)}</React.Fragment>;
|
|
240
|
+
}
|
|
241
|
+
return undefined;
|
|
163
242
|
}
|
|
164
243
|
|
|
165
244
|
override render(): React.ReactNode {
|
|
@@ -193,19 +272,38 @@ class DelegatedChat extends React.Component<DelegatedChatProps, DelegatedChatSta
|
|
|
193
272
|
statusText = nls.localize('theia/ai/chat-ui/delegation-response-renderer/status/starting', 'starting...');
|
|
194
273
|
}
|
|
195
274
|
|
|
275
|
+
const { isOpen, pendingInteractions } = this.state;
|
|
276
|
+
const showInteractionsInSummary = !isOpen && pendingInteractions.length > 0;
|
|
277
|
+
|
|
196
278
|
return (
|
|
197
279
|
<div className='theia-delegation-container'>
|
|
198
|
-
<details className='delegation-response-details'>
|
|
280
|
+
<details className='delegation-response-details' onToggle={this.handleToggle}>
|
|
199
281
|
<summary className='delegation-summary'>
|
|
200
282
|
<div className='delegation-header'>
|
|
201
283
|
<span className='delegation-agent'>
|
|
202
284
|
<span className='codicon codicon-copilot-large' /> {agentName}
|
|
203
285
|
</span>
|
|
204
286
|
<span className='delegation-status'>
|
|
287
|
+
{showInteractionsInSummary && (
|
|
288
|
+
<span className='delegation-interaction-badge' title={nls.localize(
|
|
289
|
+
'theia/ai/chat-ui/delegation-response-renderer/interactionNeeded',
|
|
290
|
+
'User interaction needed'
|
|
291
|
+
)}>
|
|
292
|
+
<span className='codicon codicon-warning'></span>
|
|
293
|
+
</span>
|
|
294
|
+
)}
|
|
205
295
|
<span className={`codicon ${statusIcon} delegation-status-icon`}></span>
|
|
206
296
|
<span className='delegation-status-text'>{statusText}</span>
|
|
207
297
|
</span>
|
|
298
|
+
<span className={`delegation-toggle-arrow${isOpen ? ' open' : ''}`} />
|
|
208
299
|
</div>
|
|
300
|
+
{showInteractionsInSummary && (
|
|
301
|
+
<div className='delegation-pending-confirmations'>
|
|
302
|
+
{pendingInteractions.map(({ contentPart, id }) =>
|
|
303
|
+
this.renderInteractionConfirmation(contentPart, id)
|
|
304
|
+
)}
|
|
305
|
+
</div>
|
|
306
|
+
)}
|
|
209
307
|
</summary>
|
|
210
308
|
<div className='delegation-content'>
|
|
211
309
|
<div className='delegation-prompt-section'>
|
|
@@ -15,17 +15,20 @@
|
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
import { ChatResponseContent, MultiSelectQuestionResponseHandler, QuestionResponseContent, QuestionResponseHandler } from '@theia/ai-chat';
|
|
17
17
|
import { nls } from '@theia/core';
|
|
18
|
-
import { codicon } from '@theia/core/lib/browser';
|
|
19
|
-
import { injectable } from '@theia/core/shared/inversify';
|
|
18
|
+
import { codicon, OpenerService } from '@theia/core/lib/browser';
|
|
19
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
20
20
|
import * as React from '@theia/core/shared/react';
|
|
21
21
|
import { ReactNode } from '@theia/core/shared/react';
|
|
22
22
|
import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
|
|
23
23
|
import { ResponseNode } from '../chat-tree-view';
|
|
24
|
+
import { useMarkdownRendering } from './markdown-part-renderer';
|
|
24
25
|
|
|
25
26
|
@injectable()
|
|
26
27
|
export class QuestionPartRenderer
|
|
27
28
|
implements ChatResponsePartRenderer<QuestionResponseContent> {
|
|
28
29
|
|
|
30
|
+
@inject(OpenerService) protected readonly openerService: OpenerService;
|
|
31
|
+
|
|
29
32
|
canHandle(response: ChatResponseContent): number {
|
|
30
33
|
if (QuestionResponseContent.is(response)) {
|
|
31
34
|
return 10;
|
|
@@ -35,9 +38,16 @@ export class QuestionPartRenderer
|
|
|
35
38
|
|
|
36
39
|
render(question: QuestionResponseContent, node: ResponseNode): ReactNode {
|
|
37
40
|
if (question.multiSelect) {
|
|
38
|
-
return <MultiSelectQuestion question={question} node={node} />;
|
|
41
|
+
return <MultiSelectQuestion question={question} node={node} openerService={this.openerService} />;
|
|
42
|
+
}
|
|
43
|
+
return <SingleSelectQuestion question={question} node={node} openerService={this.openerService} />;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
renderConfirmation(question: QuestionResponseContent, node: ResponseNode): ReactNode {
|
|
47
|
+
if (question.multiSelect) {
|
|
48
|
+
return <MultiSelectQuestion question={question} node={node} openerService={this.openerService} />;
|
|
39
49
|
}
|
|
40
|
-
return <SingleSelectQuestion question={question} node={node} />;
|
|
50
|
+
return <SingleSelectQuestion question={question} node={node} openerService={this.openerService} />;
|
|
41
51
|
}
|
|
42
52
|
|
|
43
53
|
}
|
|
@@ -77,15 +87,18 @@ function DismissButton({ question, disabled }: { question: QuestionResponseConte
|
|
|
77
87
|
);
|
|
78
88
|
}
|
|
79
89
|
|
|
80
|
-
function SingleSelectQuestion({ question, node
|
|
90
|
+
function SingleSelectQuestion({ question, node, openerService }: {
|
|
91
|
+
question: QuestionResponseContent, node: ResponseNode, openerService: OpenerService
|
|
92
|
+
}): React.JSX.Element {
|
|
81
93
|
const isDisabled = question.isReadOnly || isResolved(question) || !node.response.isWaitingForInput;
|
|
82
94
|
const hasDescriptions = question.options.some(option => option.description);
|
|
95
|
+
const questionRef = useMarkdownRendering(question.question, openerService);
|
|
83
96
|
|
|
84
97
|
return (
|
|
85
98
|
<div className="theia-QuestionPartRenderer-root">
|
|
86
99
|
{question.onSkip && <DismissButton question={question} disabled={isDisabled} />}
|
|
87
100
|
{question.header && <div className="theia-QuestionPartRenderer-header">{question.header}</div>}
|
|
88
|
-
<div className="theia-QuestionPartRenderer-question"
|
|
101
|
+
<div className="theia-QuestionPartRenderer-question" ref={questionRef} />
|
|
89
102
|
<div className={`theia-QuestionPartRenderer-options ${hasDescriptions ? 'has-descriptions' : ''}`}>
|
|
90
103
|
{
|
|
91
104
|
question.options.map((option, index) => (
|
|
@@ -112,7 +125,10 @@ function SingleSelectQuestion({ question, node }: { question: QuestionResponseCo
|
|
|
112
125
|
);
|
|
113
126
|
}
|
|
114
127
|
|
|
115
|
-
function MultiSelectQuestion({ question, node
|
|
128
|
+
function MultiSelectQuestion({ question, node, openerService }: {
|
|
129
|
+
question: QuestionResponseContent, node: ResponseNode, openerService: OpenerService
|
|
130
|
+
}): React.JSX.Element {
|
|
131
|
+
const questionRef = useMarkdownRendering(question.question, openerService);
|
|
116
132
|
const restoredIndices = React.useMemo(() => {
|
|
117
133
|
if (question.selectedOptions && question.selectedOptions.length > 0) {
|
|
118
134
|
const indices = new Set<number>();
|
|
@@ -165,7 +181,7 @@ function MultiSelectQuestion({ question, node }: { question: QuestionResponseCon
|
|
|
165
181
|
<div className="theia-QuestionPartRenderer-root">
|
|
166
182
|
<DismissButton question={question} disabled={isDisabled} />
|
|
167
183
|
{question.header && <div className="theia-QuestionPartRenderer-header">{question.header}</div>}
|
|
168
|
-
<div className="theia-QuestionPartRenderer-question"
|
|
184
|
+
<div className="theia-QuestionPartRenderer-question" ref={questionRef} />
|
|
169
185
|
<div className={`theia-QuestionPartRenderer-options ${hasDescriptions ? 'has-descriptions' : ''}`}>
|
|
170
186
|
{question.options.map((option, index) => (
|
|
171
187
|
<button
|
|
@@ -0,0 +1,89 @@
|
|
|
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 * as React from '@theia/core/shared/react';
|
|
18
|
+
import { nls } from '@theia/core/lib/common';
|
|
19
|
+
import { codicon } from '@theia/core/lib/browser';
|
|
20
|
+
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
|
21
|
+
|
|
22
|
+
export interface CopyButtonProps {
|
|
23
|
+
text: string;
|
|
24
|
+
clipboardService: ClipboardService;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const CopyButton: React.FC<CopyButtonProps> = ({ text, clipboardService }) => {
|
|
28
|
+
const handleCopy = React.useCallback(() => {
|
|
29
|
+
clipboardService.writeText(text);
|
|
30
|
+
}, [text, clipboardService]);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<button
|
|
34
|
+
className="tool-call copy-button"
|
|
35
|
+
onClick={handleCopy}
|
|
36
|
+
title={nls.localizeByDefault('Copy')}
|
|
37
|
+
>
|
|
38
|
+
<span className={codicon('copy')} />
|
|
39
|
+
</button>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export interface MetaRowProps {
|
|
44
|
+
icon: string;
|
|
45
|
+
label: string;
|
|
46
|
+
children: React.ReactNode;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const MetaRow: React.FC<MetaRowProps> = ({ icon, label, children }) => (
|
|
50
|
+
<div className="tool-call meta-row" title={label}>
|
|
51
|
+
<span className={codicon(icon)} />
|
|
52
|
+
<span>{children}</span>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
export interface OutputBoxProps {
|
|
57
|
+
title: string;
|
|
58
|
+
output?: string;
|
|
59
|
+
clipboardService: ClipboardService;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const OutputBox: React.FC<OutputBoxProps> = ({ title, output, clipboardService }) => (
|
|
63
|
+
<div className="tool-call output-box">
|
|
64
|
+
<div className="tool-call output-header">
|
|
65
|
+
<span className={codicon('output')} />
|
|
66
|
+
{title}
|
|
67
|
+
{output && <CopyButton text={output} clipboardService={clipboardService} />}
|
|
68
|
+
</div>
|
|
69
|
+
{output ? (
|
|
70
|
+
<pre className="tool-call output">{output}</pre>
|
|
71
|
+
) : (
|
|
72
|
+
<div className="tool-call no-output">
|
|
73
|
+
{nls.localize('theia/ai-chat-ui/noOutput', 'No output')}
|
|
74
|
+
</div>
|
|
75
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
export function formatDuration(ms: number): string {
|
|
80
|
+
if (ms < 1000) {
|
|
81
|
+
return `${ms}ms`;
|
|
82
|
+
}
|
|
83
|
+
if (ms < 60000) {
|
|
84
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
85
|
+
}
|
|
86
|
+
const minutes = Math.floor(ms / 60000);
|
|
87
|
+
const seconds = Math.floor((ms % 60000) / 1000);
|
|
88
|
+
return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
|
|
89
|
+
}
|
|
@@ -126,6 +126,32 @@ export function useToolConfirmationState(
|
|
|
126
126
|
return { confirmationState, rejectionReason };
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
export function createConfirmationHandlers(
|
|
130
|
+
toolId: string | undefined,
|
|
131
|
+
response: ToolCallChatResponseContent,
|
|
132
|
+
toolConfirmationManager: ToolConfirmationManager,
|
|
133
|
+
chatId: string,
|
|
134
|
+
toolRequest?: ToolRequest
|
|
135
|
+
): { handleAllow: (scope: ConfirmationScope) => void; handleDeny: (scope: ConfirmationScope, reason?: string) => void } {
|
|
136
|
+
const handleAllow = (scope: ConfirmationScope): void => {
|
|
137
|
+
if (scope === 'forever' && toolId) {
|
|
138
|
+
toolConfirmationManager.setConfirmationMode(toolId, ToolConfirmationPreferenceMode.ALWAYS_ALLOW, toolRequest);
|
|
139
|
+
} else if (scope === 'session' && toolId) {
|
|
140
|
+
toolConfirmationManager.setSessionConfirmationMode(toolId, ToolConfirmationPreferenceMode.ALWAYS_ALLOW, chatId);
|
|
141
|
+
}
|
|
142
|
+
response.confirm();
|
|
143
|
+
};
|
|
144
|
+
const handleDeny = (scope: ConfirmationScope, reason?: string): void => {
|
|
145
|
+
if (scope === 'forever' && toolId) {
|
|
146
|
+
toolConfirmationManager.setConfirmationMode(toolId, ToolConfirmationPreferenceMode.DISABLED);
|
|
147
|
+
} else if (scope === 'session' && toolId) {
|
|
148
|
+
toolConfirmationManager.setSessionConfirmationMode(toolId, ToolConfirmationPreferenceMode.DISABLED, chatId);
|
|
149
|
+
}
|
|
150
|
+
response.deny(reason);
|
|
151
|
+
};
|
|
152
|
+
return { handleAllow, handleDeny };
|
|
153
|
+
}
|
|
154
|
+
|
|
129
155
|
export interface ToolConfirmationCallbacks {
|
|
130
156
|
toolRequest?: ToolRequest;
|
|
131
157
|
onAllow: (scope: ConfirmationScope) => void;
|
|
@@ -21,7 +21,7 @@ import { ReactNode } from '@theia/core/shared/react';
|
|
|
21
21
|
import { nls } from '@theia/core/lib/common/nls';
|
|
22
22
|
import { codicon, ContextMenuRenderer, HoverService, OpenerService } from '@theia/core/lib/browser';
|
|
23
23
|
import * as React from '@theia/core/shared/react';
|
|
24
|
-
import { ToolConfirmation, useToolConfirmationState } from './tool-confirmation';
|
|
24
|
+
import { createConfirmationHandlers, ToolConfirmation, 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 { useMarkdownRendering } from './markdown-part-renderer';
|
|
@@ -54,6 +54,22 @@ export class ToolCallPartRenderer implements ChatResponsePartRenderer<ToolCallCh
|
|
|
54
54
|
return -1;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
renderConfirmation(response: ToolCallChatResponseContent, parentNode: ResponseNode): ReactNode {
|
|
58
|
+
const chatId = parentNode.sessionId;
|
|
59
|
+
const toolRequest = response.name ? this.toolInvocationRegistry.getFunction(response.name) : undefined;
|
|
60
|
+
const { handleAllow, handleDeny } = createConfirmationHandlers(
|
|
61
|
+
response.name, response, this.toolConfirmationManager, chatId, toolRequest
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
return <ToolConfirmation
|
|
65
|
+
response={response}
|
|
66
|
+
toolRequest={toolRequest}
|
|
67
|
+
onAllow={handleAllow}
|
|
68
|
+
onDeny={handleDeny}
|
|
69
|
+
contextMenuRenderer={this.contextMenuRenderer}
|
|
70
|
+
/>;
|
|
71
|
+
}
|
|
72
|
+
|
|
57
73
|
render(response: ToolCallChatResponseContent, parentNode: ResponseNode): ReactNode {
|
|
58
74
|
const chatId = parentNode.sessionId;
|
|
59
75
|
const toolRequest = response.name ? this.toolInvocationRegistry.getFunction(response.name) : undefined;
|
|
@@ -210,23 +226,10 @@ const ToolCallContent: React.FC<ToolCallContentProps> = ({
|
|
|
210
226
|
}
|
|
211
227
|
};
|
|
212
228
|
|
|
213
|
-
const handleAllow = React.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
toolConfirmationManager.setSessionConfirmationMode(response.name, ToolConfirmationMode.ALWAYS_ALLOW, chatId);
|
|
218
|
-
}
|
|
219
|
-
response.confirm();
|
|
220
|
-
}, [response, toolConfirmationManager, chatId, toolRequest]);
|
|
221
|
-
|
|
222
|
-
const handleDeny = React.useCallback((mode: 'once' | 'session' | 'forever' = 'once', reason?: string) => {
|
|
223
|
-
if (mode === 'forever' && response.name) {
|
|
224
|
-
toolConfirmationManager.setConfirmationMode(response.name, ToolConfirmationMode.DISABLED);
|
|
225
|
-
} else if (mode === 'session' && response.name) {
|
|
226
|
-
toolConfirmationManager.setSessionConfirmationMode(response.name, ToolConfirmationMode.DISABLED, chatId);
|
|
227
|
-
}
|
|
228
|
-
response.deny(reason);
|
|
229
|
-
}, [response, toolConfirmationManager, chatId]);
|
|
229
|
+
const { handleAllow, handleDeny } = React.useMemo(
|
|
230
|
+
() => createConfirmationHandlers(response.name, response, toolConfirmationManager, chatId, toolRequest),
|
|
231
|
+
[response, toolConfirmationManager, chatId, toolRequest]
|
|
232
|
+
);
|
|
230
233
|
|
|
231
234
|
const reasonText = formatReason(rejectionReason);
|
|
232
235
|
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
|
|
17
17
|
import { MarkdownStringImpl } from '@theia/core/lib/common/markdown-rendering/markdown-string';
|
|
18
18
|
|
|
19
|
+
export { extractJsonStringField } from '../../common/toolcall-utils';
|
|
20
|
+
|
|
19
21
|
const SHORT_STRING_THRESHOLD = 50;
|
|
20
22
|
const MAX_CONDENSED_LENGTH = 80;
|
|
21
23
|
const MAX_CONDENSED_VALUE_LENGTH = 30;
|