@theia/ai-chat-ui 1.65.0-next.6 → 1.66.0-next.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.
- package/lib/browser/ai-chat-ui-contribution.js +4 -4
- package/lib/browser/ai-chat-ui-contribution.js.map +1 -1
- package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-chat-ui-frontend-module.js +9 -1
- package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -1
- package/lib/browser/chat-input-history-contribution.d.ts +17 -0
- package/lib/browser/chat-input-history-contribution.d.ts.map +1 -0
- package/lib/browser/chat-input-history-contribution.js +157 -0
- package/lib/browser/chat-input-history-contribution.js.map +1 -0
- package/lib/browser/chat-input-history.d.ts +32 -0
- package/lib/browser/chat-input-history.d.ts.map +1 -0
- package/lib/browser/chat-input-history.js +125 -0
- package/lib/browser/chat-input-history.js.map +1 -0
- package/lib/browser/chat-input-widget.d.ts +22 -1
- package/lib/browser/chat-input-widget.d.ts.map +1 -1
- package/lib/browser/chat-input-widget.js +166 -8
- package/lib/browser/chat-input-widget.js.map +1 -1
- package/lib/browser/chat-response-renderer/tool-confirmation.d.ts +1 -1
- package/lib/browser/chat-response-renderer/tool-confirmation.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts +2 -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 +46 -11
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -1
- package/lib/browser/chat-view-commands.d.ts +2 -0
- package/lib/browser/chat-view-commands.d.ts.map +1 -1
- package/lib/browser/chat-view-commands.js +30 -23
- package/lib/browser/chat-view-commands.js.map +1 -1
- package/lib/browser/chat-view-widget.d.ts +3 -3
- package/lib/browser/chat-view-widget.d.ts.map +1 -1
- package/lib/browser/chat-view-widget.js +2 -4
- package/lib/browser/chat-view-widget.js.map +1 -1
- package/package.json +11 -12
- package/src/browser/ai-chat-ui-contribution.ts +4 -4
- package/src/browser/ai-chat-ui-frontend-module.ts +11 -2
- package/src/browser/chat-input-history-contribution.ts +166 -0
- package/src/browser/chat-input-history.ts +138 -0
- package/src/browser/chat-input-widget.tsx +197 -8
- package/src/browser/chat-response-renderer/tool-confirmation.tsx +1 -1
- package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +52 -12
- package/src/browser/chat-view-commands.ts +24 -17
- package/src/browser/chat-view-widget.tsx +3 -6
- 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 =
|
|
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
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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 {
|
|
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> = ({
|
|
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
|
|
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
|
-
|
|
169
|
-
setConfirmationState('
|
|
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 === '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
|
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
|
|
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,
|
|
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
|
}
|