@theia/ai-chat-ui 1.68.0-next.8 → 1.68.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/README.md +33 -0
- package/lib/browser/ai-chat-ui-contribution.d.ts +2 -1
- package/lib/browser/ai-chat-ui-contribution.d.ts.map +1 -1
- package/lib/browser/ai-chat-ui-contribution.js +41 -32
- 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 +15 -6
- package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -1
- package/lib/browser/change-set-actions/change-set-accept-action.d.ts +0 -1
- package/lib/browser/change-set-actions/change-set-accept-action.d.ts.map +1 -1
- package/lib/browser/change-set-actions/change-set-action-service.d.ts +0 -1
- package/lib/browser/change-set-actions/change-set-action-service.d.ts.map +1 -1
- package/lib/browser/change-set-actions/change-set-action-service.js +2 -2
- package/lib/browser/change-set-actions/change-set-action-service.js.map +1 -1
- package/lib/browser/chat-focus-contribution.d.ts +14 -0
- package/lib/browser/chat-focus-contribution.d.ts.map +1 -0
- package/lib/browser/chat-focus-contribution.js +102 -0
- package/lib/browser/chat-focus-contribution.js.map +1 -0
- package/lib/browser/chat-input-agent-suggestions.d.ts +0 -1
- package/lib/browser/chat-input-agent-suggestions.d.ts.map +1 -1
- package/lib/browser/chat-input-agent-suggestions.js +1 -1
- package/lib/browser/chat-input-agent-suggestions.js.map +1 -1
- package/lib/browser/chat-input-history-contribution.js +6 -10
- package/lib/browser/chat-input-history-contribution.js.map +1 -1
- package/lib/browser/chat-input-mode-contribution.js +1 -2
- package/lib/browser/chat-input-mode-contribution.js.map +1 -1
- package/lib/browser/chat-input-widget.d.ts +0 -1
- package/lib/browser/chat-input-widget.d.ts.map +1 -1
- package/lib/browser/chat-input-widget.js +106 -96
- package/lib/browser/chat-input-widget.js.map +1 -1
- package/lib/browser/chat-progress-message.d.ts +0 -1
- package/lib/browser/chat-progress-message.d.ts.map +1 -1
- package/lib/browser/chat-response-part-renderer.d.ts +0 -1
- package/lib/browser/chat-response-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/ai-selection-resolver.js +4 -5
- package/lib/browser/chat-response-renderer/ai-selection-resolver.js.map +1 -1
- package/lib/browser/chat-response-renderer/code-part-renderer.d.ts +0 -1
- package/lib/browser/chat-response-renderer/code-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/code-part-renderer.js +6 -9
- package/lib/browser/chat-response-renderer/code-part-renderer.js.map +1 -1
- package/lib/browser/chat-response-renderer/command-part-renderer.d.ts +0 -1
- package/lib/browser/chat-response-renderer/command-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/command-part-renderer.js +7 -4
- package/lib/browser/chat-response-renderer/command-part-renderer.js.map +1 -1
- package/lib/browser/chat-response-renderer/delegation-response-renderer.d.ts +0 -1
- package/lib/browser/chat-response-renderer/delegation-response-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/delegation-response-renderer.js +3 -4
- package/lib/browser/chat-response-renderer/delegation-response-renderer.js.map +1 -1
- package/lib/browser/chat-response-renderer/error-part-renderer.d.ts +0 -1
- package/lib/browser/chat-response-renderer/error-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/horizontal-layout-part-renderer.d.ts +0 -1
- package/lib/browser/chat-response-renderer/horizontal-layout-part-renderer.d.ts.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/markdown-part-renderer.d.ts +0 -1
- package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/markdown-part-renderer.js +7 -9
- package/lib/browser/chat-response-renderer/markdown-part-renderer.js.map +1 -1
- package/lib/browser/chat-response-renderer/not-available-toolcall-renderer.d.ts +17 -0
- package/lib/browser/chat-response-renderer/not-available-toolcall-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/not-available-toolcall-renderer.js +56 -0
- package/lib/browser/chat-response-renderer/not-available-toolcall-renderer.js.map +1 -0
- package/lib/browser/chat-response-renderer/progress-part-renderer.d.ts +0 -1
- package/lib/browser/chat-response-renderer/progress-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/question-part-renderer.d.ts +0 -1
- package/lib/browser/chat-response-renderer/question-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/question-part-renderer.js +6 -9
- package/lib/browser/chat-response-renderer/question-part-renderer.js.map +1 -1
- package/lib/browser/chat-response-renderer/text-part-renderer.d.ts +0 -1
- package/lib/browser/chat-response-renderer/text-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/thinking-part-renderer.d.ts +0 -1
- package/lib/browser/chat-response-renderer/thinking-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/tool-confirmation.d.ts +29 -10
- package/lib/browser/chat-response-renderer/tool-confirmation.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/tool-confirmation.js +219 -65
- package/lib/browser/chat-response-renderer/tool-confirmation.js.map +1 -1
- package/lib/browser/chat-response-renderer/tool-confirmation.spec.d.ts +2 -0
- package/lib/browser/chat-response-renderer/tool-confirmation.spec.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/tool-confirmation.spec.js +100 -0
- package/lib/browser/chat-response-renderer/tool-confirmation.spec.js.map +1 -0
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts +5 -3
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.js +21 -11
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -1
- package/lib/browser/chat-response-renderer/unknown-part-renderer.d.ts +0 -1
- package/lib/browser/chat-response-renderer/unknown-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/unknown-part-renderer.js +1 -2
- package/lib/browser/chat-response-renderer/unknown-part-renderer.js.map +1 -1
- package/lib/browser/chat-tree-view/chat-view-tree-container.js +1 -2
- package/lib/browser/chat-tree-view/chat-view-tree-container.js.map +1 -1
- package/lib/browser/chat-tree-view/chat-view-tree-input-widget.js +2 -3
- 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 +3 -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 +49 -42
- package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -1
- package/lib/browser/chat-tree-view/prompt-variant-badge.d.ts +0 -1
- package/lib/browser/chat-tree-view/prompt-variant-badge.d.ts.map +1 -1
- package/lib/browser/chat-tree-view/sub-chat-widget.d.ts +0 -1
- package/lib/browser/chat-tree-view/sub-chat-widget.d.ts.map +1 -1
- package/lib/browser/chat-view-commands.d.ts.map +1 -1
- package/lib/browser/chat-view-commands.js +2 -2
- package/lib/browser/chat-view-commands.js.map +1 -1
- package/lib/browser/chat-view-contribution.js +7 -7
- package/lib/browser/chat-view-contribution.js.map +1 -1
- package/lib/browser/chat-view-language-contribution.js +3 -4
- package/lib/browser/chat-view-language-contribution.js.map +1 -1
- package/lib/browser/chat-view-widget.js +9 -10
- package/lib/browser/chat-view-widget.js.map +1 -1
- package/lib/browser/context-variable-picker.js +9 -13
- package/lib/browser/context-variable-picker.js.map +1 -1
- package/lib/browser/session-storage-preference-renderer.d.ts +127 -0
- package/lib/browser/session-storage-preference-renderer.d.ts.map +1 -0
- package/lib/browser/session-storage-preference-renderer.js +490 -0
- package/lib/browser/session-storage-preference-renderer.js.map +1 -0
- package/package.json +12 -11
- package/src/browser/ai-chat-ui-contribution.ts +19 -7
- package/src/browser/ai-chat-ui-frontend-module.ts +13 -0
- package/src/browser/chat-focus-contribution.ts +102 -0
- package/src/browser/chat-input-widget.tsx +99 -27
- package/src/browser/chat-response-renderer/index.ts +1 -0
- package/src/browser/chat-response-renderer/not-available-toolcall-renderer.tsx +57 -0
- package/src/browser/chat-response-renderer/tool-confirmation.spec.ts +108 -0
- package/src/browser/chat-response-renderer/tool-confirmation.tsx +379 -95
- package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +27 -13
- package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +33 -5
- package/src/browser/chat-view-commands.ts +2 -2
- package/src/browser/session-storage-preference-renderer.ts +657 -0
- package/src/browser/style/index.css +73 -26
- package/src/browser/style/session-storage-preference.css +134 -0
|
@@ -20,6 +20,7 @@ import { bindViewContribution, FrontendApplicationContribution, WidgetFactory, K
|
|
|
20
20
|
import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
|
21
21
|
import { ContainerModule, interfaces } from '@theia/core/shared/inversify';
|
|
22
22
|
import { EditorSelectionResolver } from '@theia/editor/lib/browser/editor-manager';
|
|
23
|
+
import { PreferenceNodeRendererContribution } from '@theia/preferences/lib/browser/views/components/preference-node-renderer-creator';
|
|
23
24
|
import { AIChatContribution } from './ai-chat-ui-contribution';
|
|
24
25
|
import { AIChatInputConfiguration, AIChatInputWidget } from './chat-input-widget';
|
|
25
26
|
import { ChatNodeToolbarActionContribution, DefaultChatNodeToolbarActionContribution } from './chat-node-toolbar-action-contribution';
|
|
@@ -34,6 +35,7 @@ import {
|
|
|
34
35
|
InsertCodeAtCursorButtonAction,
|
|
35
36
|
MarkdownPartRenderer,
|
|
36
37
|
ToolCallPartRenderer,
|
|
38
|
+
NotAvailableToolCallRenderer,
|
|
37
39
|
ThinkingPartRenderer,
|
|
38
40
|
ProgressPartRenderer,
|
|
39
41
|
DelegationResponseRenderer,
|
|
@@ -60,6 +62,8 @@ import { SubChatWidget, SubChatWidgetFactory } from './chat-tree-view/sub-chat-w
|
|
|
60
62
|
import { ChatInputHistoryService } from './chat-input-history';
|
|
61
63
|
import { ChatInputHistoryContribution } from './chat-input-history-contribution';
|
|
62
64
|
import { ChatInputModeContribution } from './chat-input-mode-contribution';
|
|
65
|
+
import { ChatFocusContribution } from './chat-focus-contribution';
|
|
66
|
+
import { SessionStoragePreferenceRenderer, SessionStoragePreferenceRendererContribution } from './session-storage-preference-renderer';
|
|
63
67
|
|
|
64
68
|
export default new ContainerModule((bind, _unbind, _isBound, rebind) => {
|
|
65
69
|
bindViewContribution(bind, AIChatContribution);
|
|
@@ -74,6 +78,10 @@ export default new ContainerModule((bind, _unbind, _isBound, rebind) => {
|
|
|
74
78
|
bind(CommandContribution).toService(ChatInputModeContribution);
|
|
75
79
|
bind(KeybindingContribution).toService(ChatInputModeContribution);
|
|
76
80
|
|
|
81
|
+
bind(ChatFocusContribution).toSelf().inSingletonScope();
|
|
82
|
+
bind(CommandContribution).toService(ChatFocusContribution);
|
|
83
|
+
bind(KeybindingContribution).toService(ChatFocusContribution);
|
|
84
|
+
|
|
77
85
|
bindContributionProvider(bind, ChatResponsePartRenderer);
|
|
78
86
|
|
|
79
87
|
bindChatViewWidget(bind);
|
|
@@ -132,6 +140,7 @@ export default new ContainerModule((bind, _unbind, _isBound, rebind) => {
|
|
|
132
140
|
bind(ChatResponsePartRenderer).to(CodePartRenderer).inSingletonScope();
|
|
133
141
|
bind(ChatResponsePartRenderer).to(CommandPartRenderer).inSingletonScope();
|
|
134
142
|
bind(ChatResponsePartRenderer).to(ToolCallPartRenderer).inSingletonScope();
|
|
143
|
+
bind(ChatResponsePartRenderer).to(NotAvailableToolCallRenderer).inSingletonScope();
|
|
135
144
|
bind(ChatResponsePartRenderer).to(ErrorPartRenderer).inSingletonScope();
|
|
136
145
|
bind(ChatResponsePartRenderer).to(ThinkingPartRenderer).inSingletonScope();
|
|
137
146
|
bind(ChatResponsePartRenderer).to(QuestionPartRenderer).inSingletonScope();
|
|
@@ -173,6 +182,10 @@ export default new ContainerModule((bind, _unbind, _isBound, rebind) => {
|
|
|
173
182
|
return widget;
|
|
174
183
|
});
|
|
175
184
|
|
|
185
|
+
// Session storage preference renderer
|
|
186
|
+
bind(SessionStoragePreferenceRenderer).toSelf();
|
|
187
|
+
bind(PreferenceNodeRendererContribution).to(SessionStoragePreferenceRendererContribution).inSingletonScope();
|
|
188
|
+
|
|
176
189
|
});
|
|
177
190
|
|
|
178
191
|
function bindChatViewWidget(bind: interfaces.Bind): void {
|
|
@@ -0,0 +1,102 @@
|
|
|
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 { inject, injectable } from '@theia/core/shared/inversify';
|
|
20
|
+
import { ChatViewWidget } from './chat-view-widget';
|
|
21
|
+
import { ChatCommands } from './chat-view-commands';
|
|
22
|
+
|
|
23
|
+
export const CHAT_FOCUS_INPUT_COMMAND = Command.toLocalizedCommand({
|
|
24
|
+
id: 'ai-chat.focus-input',
|
|
25
|
+
category: ChatCommands.CHAT_CATEGORY,
|
|
26
|
+
label: 'Focus Chat Input'
|
|
27
|
+
}, 'theia/ai/chat-ui/focusInput', ChatCommands.CHAT_CATEGORY_KEY);
|
|
28
|
+
|
|
29
|
+
export const CHAT_FOCUS_RESPONSE_COMMAND = Command.toLocalizedCommand({
|
|
30
|
+
id: 'ai-chat.focus-response',
|
|
31
|
+
category: ChatCommands.CHAT_CATEGORY,
|
|
32
|
+
label: 'Focus Chat Response'
|
|
33
|
+
}, 'theia/ai/chat-ui/focusResponse', ChatCommands.CHAT_CATEGORY_KEY);
|
|
34
|
+
|
|
35
|
+
@injectable()
|
|
36
|
+
export class ChatFocusContribution implements CommandContribution, KeybindingContribution {
|
|
37
|
+
|
|
38
|
+
@inject(ApplicationShell)
|
|
39
|
+
protected readonly shell: ApplicationShell;
|
|
40
|
+
|
|
41
|
+
registerCommands(commands: CommandRegistry): void {
|
|
42
|
+
commands.registerCommand(CHAT_FOCUS_INPUT_COMMAND, {
|
|
43
|
+
execute: () => this.focusInput(),
|
|
44
|
+
isEnabled: () => this.findActiveChatViewWidget() !== undefined
|
|
45
|
+
});
|
|
46
|
+
commands.registerCommand(CHAT_FOCUS_RESPONSE_COMMAND, {
|
|
47
|
+
execute: () => this.focusResponse(),
|
|
48
|
+
isEnabled: () => this.findActiveChatViewWidget() !== undefined
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
registerKeybindings(keybindings: KeybindingRegistry): void {
|
|
53
|
+
keybindings.registerKeybinding({
|
|
54
|
+
command: CHAT_FOCUS_RESPONSE_COMMAND.id,
|
|
55
|
+
keybinding: 'ctrlcmd+up',
|
|
56
|
+
when: 'chatInputFocus && !suggestWidgetVisible'
|
|
57
|
+
});
|
|
58
|
+
keybindings.registerKeybinding({
|
|
59
|
+
command: CHAT_FOCUS_INPUT_COMMAND.id,
|
|
60
|
+
keybinding: 'ctrlcmd+down',
|
|
61
|
+
when: 'chatResponseFocus'
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
protected focusInput(): void {
|
|
66
|
+
const chatViewWidget = this.findActiveChatViewWidget();
|
|
67
|
+
if (chatViewWidget) {
|
|
68
|
+
chatViewWidget.inputWidget.activate();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
protected focusResponse(): void {
|
|
73
|
+
const chatViewWidget = this.findActiveChatViewWidget();
|
|
74
|
+
if (chatViewWidget) {
|
|
75
|
+
chatViewWidget.treeWidget.node.focus();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
protected findActiveChatViewWidget(): ChatViewWidget | undefined {
|
|
80
|
+
const activeWidget = this.shell.activeWidget;
|
|
81
|
+
if (activeWidget instanceof ChatViewWidget) {
|
|
82
|
+
return activeWidget;
|
|
83
|
+
}
|
|
84
|
+
// Also check if any part of the chat view has focus
|
|
85
|
+
const activeElement = document.activeElement;
|
|
86
|
+
if (activeElement instanceof HTMLElement) {
|
|
87
|
+
const widget = this.shell.findWidgetForElement(activeElement);
|
|
88
|
+
if (widget instanceof ChatViewWidget) {
|
|
89
|
+
return widget;
|
|
90
|
+
}
|
|
91
|
+
// Check parent widgets (e.g., when input widget has focus)
|
|
92
|
+
let parent = widget?.parent;
|
|
93
|
+
while (parent) {
|
|
94
|
+
if (parent instanceof ChatViewWidget) {
|
|
95
|
+
return parent;
|
|
96
|
+
}
|
|
97
|
+
parent = parent.parent;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -364,9 +364,12 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
364
364
|
// Only update and re-render when the agent changes
|
|
365
365
|
if (agent && agentId !== previousAgentId) {
|
|
366
366
|
const modes = agent.modes ?? [];
|
|
367
|
+
const defaultMode = modes.find(m => m.isDefault);
|
|
368
|
+
const initialModeId = defaultMode?.id;
|
|
367
369
|
this.receivingAgent = {
|
|
368
370
|
agentId: agentId,
|
|
369
|
-
modes
|
|
371
|
+
modes,
|
|
372
|
+
currentModeId: initialModeId
|
|
370
373
|
};
|
|
371
374
|
this.chatInputHasModesKey.set(modes.length > 1);
|
|
372
375
|
this.update();
|
|
@@ -494,6 +497,7 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
494
497
|
this.editorRef = editor;
|
|
495
498
|
this.setupEditorEventListeners();
|
|
496
499
|
this.editorReady.resolve();
|
|
500
|
+
this.scheduleUpdateReceivingAgent();
|
|
497
501
|
}}
|
|
498
502
|
showContext={this.configuration?.showContext}
|
|
499
503
|
showPinnedAgent={this.configuration?.showPinnedAgent}
|
|
@@ -785,6 +789,7 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
785
789
|
bracketPairColorization: { enabled: false },
|
|
786
790
|
wrappingStrategy: 'advanced',
|
|
787
791
|
stickyScroll: { enabled: false },
|
|
792
|
+
ariaLabel: nls.localize('theia/ai/chat-ui/chatInputAriaLabel', 'Type your message here'),
|
|
788
793
|
});
|
|
789
794
|
|
|
790
795
|
if (editorContainerRef.current) {
|
|
@@ -952,6 +957,7 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
952
957
|
setValue('');
|
|
953
958
|
if (editorRef.current && !editorRef.current.document.textEditorModel.isDisposed()) {
|
|
954
959
|
editorRef.current.document.textEditorModel.setValue('');
|
|
960
|
+
editorRef.current.focus();
|
|
955
961
|
}
|
|
956
962
|
}, [props.context, props.onQuery, props.modeSelectorProps.currentMode, setValue, shouldUseTaskPlaceholder, taskPlaceholder]);
|
|
957
963
|
|
|
@@ -1132,40 +1138,60 @@ const ChatInputOptions: React.FunctionComponent<ChatInputOptionsProps> = ({
|
|
|
1132
1138
|
isEnabled,
|
|
1133
1139
|
modeSelectorProps
|
|
1134
1140
|
}) => (
|
|
1141
|
+
// Right options are rendered first in DOM for tab order (send button first when enabled)
|
|
1142
|
+
// CSS order property positions them visually (left on left, right on right)
|
|
1135
1143
|
<div className="theia-ChatInputOptions">
|
|
1136
|
-
<div className="theia-ChatInputOptions-
|
|
1137
|
-
{
|
|
1144
|
+
<div className="theia-ChatInputOptions-right">
|
|
1145
|
+
{rightOptions.map((option, index) => (
|
|
1138
1146
|
<span
|
|
1139
1147
|
key={index}
|
|
1140
1148
|
className={`option${option.disabled ? ' disabled' : ''}${option.text?.align === 'right' ? ' reverse' : ''}`}
|
|
1141
1149
|
title={option.title}
|
|
1150
|
+
aria-label={option.title}
|
|
1151
|
+
role='button'
|
|
1152
|
+
tabIndex={option.disabled ? -1 : 0}
|
|
1142
1153
|
onClick={option.handler}
|
|
1154
|
+
onKeyDown={e => {
|
|
1155
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
1156
|
+
e.preventDefault();
|
|
1157
|
+
option.handler();
|
|
1158
|
+
}
|
|
1159
|
+
}}
|
|
1143
1160
|
>
|
|
1144
1161
|
<span>{option.text?.content}</span>
|
|
1145
1162
|
<span className={`codicon ${option.className}`} />
|
|
1146
1163
|
</span>
|
|
1147
1164
|
))}
|
|
1148
|
-
{modeSelectorProps.show && modeSelectorProps.modes && (
|
|
1149
|
-
<ChatModeSelector
|
|
1150
|
-
modes={modeSelectorProps.modes}
|
|
1151
|
-
currentMode={modeSelectorProps.currentMode}
|
|
1152
|
-
onModeChange={modeSelectorProps.onModeChange}
|
|
1153
|
-
disabled={!isEnabled}
|
|
1154
|
-
/>
|
|
1155
|
-
)}
|
|
1156
1165
|
</div>
|
|
1157
|
-
<div className="theia-ChatInputOptions-
|
|
1158
|
-
{
|
|
1166
|
+
<div className="theia-ChatInputOptions-left">
|
|
1167
|
+
{leftOptions.map((option, index) => (
|
|
1159
1168
|
<span
|
|
1160
1169
|
key={index}
|
|
1161
1170
|
className={`option${option.disabled ? ' disabled' : ''}${option.text?.align === 'right' ? ' reverse' : ''}`}
|
|
1162
1171
|
title={option.title}
|
|
1172
|
+
aria-label={option.title}
|
|
1173
|
+
role='button'
|
|
1174
|
+
tabIndex={option.disabled ? -1 : 0}
|
|
1163
1175
|
onClick={option.handler}
|
|
1176
|
+
onKeyDown={e => {
|
|
1177
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
1178
|
+
e.preventDefault();
|
|
1179
|
+
option.handler();
|
|
1180
|
+
}
|
|
1181
|
+
}}
|
|
1164
1182
|
>
|
|
1165
1183
|
<span>{option.text?.content}</span>
|
|
1166
1184
|
<span className={`codicon ${option.className}`} />
|
|
1167
1185
|
</span>
|
|
1168
1186
|
))}
|
|
1187
|
+
{modeSelectorProps.show && modeSelectorProps.modes && (
|
|
1188
|
+
<ChatModeSelector
|
|
1189
|
+
modes={modeSelectorProps.modes}
|
|
1190
|
+
currentMode={modeSelectorProps.currentMode}
|
|
1191
|
+
onModeChange={modeSelectorProps.onModeChange}
|
|
1192
|
+
disabled={!isEnabled}
|
|
1193
|
+
/>
|
|
1194
|
+
)}
|
|
1169
1195
|
</div>
|
|
1170
1196
|
</div>
|
|
1171
1197
|
);
|
|
@@ -1239,22 +1265,68 @@ interface ChangeSetUI {
|
|
|
1239
1265
|
}
|
|
1240
1266
|
|
|
1241
1267
|
/** Memo because the parent element rerenders on every key press in the chat widget. */
|
|
1242
|
-
const ChangeSetBox: React.FunctionComponent<{ changeSet: ChangeSetUI }> = React.memo(({ changeSet: { changeSet, title, deleteChangeSet, elements, actions } }) =>
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1268
|
+
const ChangeSetBox: React.FunctionComponent<{ changeSet: ChangeSetUI }> = React.memo(({ changeSet: { changeSet, title, deleteChangeSet, elements, actions } }) => {
|
|
1269
|
+
const [isCollapsed, setIsCollapsed] = React.useState(false);
|
|
1270
|
+
|
|
1271
|
+
const toggleCollapse = React.useCallback(() => {
|
|
1272
|
+
setIsCollapsed(prev => !prev);
|
|
1273
|
+
}, []);
|
|
1274
|
+
|
|
1275
|
+
const handleToggleKeyDown = React.useCallback(
|
|
1276
|
+
(e: React.KeyboardEvent) => {
|
|
1277
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
1278
|
+
e.preventDefault();
|
|
1279
|
+
toggleCollapse();
|
|
1280
|
+
}
|
|
1281
|
+
},
|
|
1282
|
+
[toggleCollapse]
|
|
1283
|
+
);
|
|
1284
|
+
|
|
1285
|
+
return (
|
|
1286
|
+
<div className='theia-ChatInput-ChangeSet-Box'>
|
|
1287
|
+
<div className='theia-ChatInput-ChangeSet-Header'>
|
|
1288
|
+
<div className='theia-ChatInput-ChangeSet-Header-Title'>
|
|
1289
|
+
<span
|
|
1290
|
+
className={`codicon ${isCollapsed ? 'codicon-chevron-right' : 'codicon-chevron-down'} theia-ChatInput-ChangeSet-Toggle`}
|
|
1291
|
+
onClick={toggleCollapse}
|
|
1292
|
+
onKeyDown={handleToggleKeyDown}
|
|
1293
|
+
role='button'
|
|
1294
|
+
tabIndex={0}
|
|
1295
|
+
aria-expanded={!isCollapsed}
|
|
1296
|
+
aria-label={
|
|
1297
|
+
isCollapsed
|
|
1298
|
+
? nls.localize('theia/ai/chat-ui/expandChangeSet', 'Expand Change Set')
|
|
1299
|
+
: nls.localize('theia/ai/chat-ui/collapseChangeSet', 'Collapse Change Set')
|
|
1300
|
+
}
|
|
1301
|
+
title={
|
|
1302
|
+
isCollapsed
|
|
1303
|
+
? nls.localize('theia/ai/chat-ui/expandChangeSet', 'Expand Change Set')
|
|
1304
|
+
: nls.localize('theia/ai/chat-ui/collapseChangeSet', 'Collapse Change Set')
|
|
1305
|
+
}
|
|
1306
|
+
/>
|
|
1307
|
+
<h3>{title}</h3>
|
|
1308
|
+
</div>
|
|
1309
|
+
<div className='theia-ChatInput-ChangeSet-Header-Actions'>
|
|
1310
|
+
{actions.map(action => (
|
|
1311
|
+
<div key={action.id} className='theia-changeSet-Action'>
|
|
1312
|
+
{action.render(changeSet)}
|
|
1313
|
+
</div>
|
|
1314
|
+
))}
|
|
1315
|
+
<span
|
|
1316
|
+
className='codicon codicon-close action'
|
|
1317
|
+
title={nls.localize('theia/ai/chat-ui/deleteChangeSet', 'Delete Change Set')}
|
|
1318
|
+
onClick={() => deleteChangeSet()}
|
|
1319
|
+
/>
|
|
1320
|
+
</div>
|
|
1321
|
+
</div>
|
|
1322
|
+
<div className={`theia-ChatInput-ChangeSet-List ${isCollapsed ? 'collapsed' : 'expanded'}`}>
|
|
1323
|
+
<ul>
|
|
1324
|
+
{elements.map(element => ChangeSetElement(element))}
|
|
1325
|
+
</ul>
|
|
1249
1326
|
</div>
|
|
1250
1327
|
</div>
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
{elements.map(element => ChangeSetElement(element))}
|
|
1254
|
-
</ul>
|
|
1255
|
-
</div>
|
|
1256
|
-
</div>
|
|
1257
|
-
));
|
|
1328
|
+
);
|
|
1329
|
+
});
|
|
1258
1330
|
|
|
1259
1331
|
function toUiElement(element: ChangeSetElement,
|
|
1260
1332
|
onDeleteChangeSetElement: (uri: URI) => void,
|
|
@@ -21,6 +21,7 @@ export * from './horizontal-layout-part-renderer';
|
|
|
21
21
|
export * from './markdown-part-renderer';
|
|
22
22
|
export * from './text-part-renderer';
|
|
23
23
|
export * from './toolcall-part-renderer';
|
|
24
|
+
export * from './not-available-toolcall-renderer';
|
|
24
25
|
export * from './thinking-part-renderer';
|
|
25
26
|
export * from './progress-part-renderer';
|
|
26
27
|
export * from './tool-confirmation';
|
|
@@ -0,0 +1,57 @@
|
|
|
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 { ChatResponsePartRenderer } from '../chat-response-part-renderer';
|
|
18
|
+
import { injectable } from '@theia/core/shared/inversify';
|
|
19
|
+
import { ChatResponseContent, ToolCallChatResponseContent } from '@theia/ai-chat/lib/common';
|
|
20
|
+
import { ReactNode } from '@theia/core/shared/react';
|
|
21
|
+
import { codicon } from '@theia/core/lib/browser';
|
|
22
|
+
import * as React from '@theia/core/shared/react';
|
|
23
|
+
import { ResponseNode } from '../chat-tree-view';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* High-priority renderer for tool calls that were not available.
|
|
27
|
+
*
|
|
28
|
+
* This handles cases where the LLM attempted to call a tool that was not
|
|
29
|
+
* made available to it in the request. This takes priority over all other
|
|
30
|
+
* tool renderers (including specialized ones like ShellExecutionToolRenderer)
|
|
31
|
+
* since unavailable tools should never be processed by tool-specific renderers.
|
|
32
|
+
*/
|
|
33
|
+
@injectable()
|
|
34
|
+
export class NotAvailableToolCallRenderer implements ChatResponsePartRenderer<ToolCallChatResponseContent> {
|
|
35
|
+
|
|
36
|
+
canHandle(response: ChatResponseContent): number {
|
|
37
|
+
if (ToolCallChatResponseContent.is(response) && response.finished) {
|
|
38
|
+
if (ToolCallChatResponseContent.isNotAvailableResult(response.result)) {
|
|
39
|
+
return 100;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return -1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
render(response: ToolCallChatResponseContent, _parentNode: ResponseNode): ReactNode {
|
|
46
|
+
const errorMessage = ToolCallChatResponseContent.getErrorMessage(response.result);
|
|
47
|
+
return (
|
|
48
|
+
<div className='theia-toolCall'>
|
|
49
|
+
<span className='theia-toolCall-unavailable'>
|
|
50
|
+
<span className={codicon('warning')}></span>
|
|
51
|
+
{' '}
|
|
52
|
+
{response.name}: {errorMessage}
|
|
53
|
+
</span>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
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 { expect } from 'chai';
|
|
18
|
+
import { ContextMenuRenderer } from '@theia/core/lib/browser';
|
|
19
|
+
import { ConfirmationScope, ToolConfirmationCallbacks, ToolConfirmationActionsProps, ToolConfirmationProps } from './tool-confirmation';
|
|
20
|
+
|
|
21
|
+
const mockContextMenuRenderer = {} as ContextMenuRenderer;
|
|
22
|
+
|
|
23
|
+
describe('Tool Confirmation Types', () => {
|
|
24
|
+
describe('ConfirmationScope', () => {
|
|
25
|
+
it('should accept valid scopes', () => {
|
|
26
|
+
const scopes: ConfirmationScope[] = ['once', 'session', 'forever'];
|
|
27
|
+
expect(scopes).to.have.length(3);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('ToolConfirmationCallbacks', () => {
|
|
32
|
+
it('should define required callback properties', () => {
|
|
33
|
+
const callbacks: ToolConfirmationCallbacks = {
|
|
34
|
+
onAllow: (_scope: ConfirmationScope) => { },
|
|
35
|
+
onDeny: (_scope: ConfirmationScope, _reason?: string) => { }
|
|
36
|
+
};
|
|
37
|
+
expect(callbacks.onAllow).to.be.a('function');
|
|
38
|
+
expect(callbacks.onDeny).to.be.a('function');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should allow optional toolRequest', () => {
|
|
42
|
+
const callbacks: ToolConfirmationCallbacks = {
|
|
43
|
+
toolRequest: { id: 'test', name: 'test', handler: async () => '', parameters: { type: 'object', properties: {} } },
|
|
44
|
+
onAllow: () => { },
|
|
45
|
+
onDeny: () => { }
|
|
46
|
+
};
|
|
47
|
+
expect(callbacks.toolRequest).to.exist;
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('ToolConfirmationActionsProps', () => {
|
|
52
|
+
it('should extend ToolConfirmationCallbacks with toolName', () => {
|
|
53
|
+
const props: ToolConfirmationActionsProps = {
|
|
54
|
+
toolName: 'testTool',
|
|
55
|
+
onAllow: () => { },
|
|
56
|
+
onDeny: () => { },
|
|
57
|
+
contextMenuRenderer: mockContextMenuRenderer
|
|
58
|
+
};
|
|
59
|
+
expect(props.toolName).to.equal('testTool');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should support confirmAlwaysAllow string in toolRequest', () => {
|
|
63
|
+
const props: ToolConfirmationActionsProps = {
|
|
64
|
+
toolName: 'dangerousTool',
|
|
65
|
+
toolRequest: {
|
|
66
|
+
id: 'test',
|
|
67
|
+
name: 'test',
|
|
68
|
+
handler: async () => '',
|
|
69
|
+
parameters: { type: 'object', properties: {} },
|
|
70
|
+
confirmAlwaysAllow: 'This tool can modify system files.'
|
|
71
|
+
},
|
|
72
|
+
onAllow: () => { },
|
|
73
|
+
onDeny: () => { },
|
|
74
|
+
contextMenuRenderer: mockContextMenuRenderer
|
|
75
|
+
};
|
|
76
|
+
expect(props.toolRequest?.confirmAlwaysAllow).to.equal('This tool can modify system files.');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should support confirmAlwaysAllow boolean in toolRequest', () => {
|
|
80
|
+
const props: ToolConfirmationActionsProps = {
|
|
81
|
+
toolName: 'dangerousTool',
|
|
82
|
+
toolRequest: {
|
|
83
|
+
id: 'test',
|
|
84
|
+
name: 'test',
|
|
85
|
+
handler: async () => '',
|
|
86
|
+
parameters: { type: 'object', properties: {} },
|
|
87
|
+
confirmAlwaysAllow: true
|
|
88
|
+
},
|
|
89
|
+
onAllow: () => { },
|
|
90
|
+
onDeny: () => { },
|
|
91
|
+
contextMenuRenderer: mockContextMenuRenderer
|
|
92
|
+
};
|
|
93
|
+
expect(props.toolRequest?.confirmAlwaysAllow).to.be.true;
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('ToolConfirmationProps', () => {
|
|
98
|
+
it('should pick toolRequest from ToolConfirmationCallbacks', () => {
|
|
99
|
+
const props: ToolConfirmationProps = {
|
|
100
|
+
response: { kind: 'toolCall', id: 'test', name: 'test' } as ToolConfirmationProps['response'],
|
|
101
|
+
onAllow: () => { },
|
|
102
|
+
onDeny: () => { },
|
|
103
|
+
contextMenuRenderer: mockContextMenuRenderer
|
|
104
|
+
};
|
|
105
|
+
expect(props.toolRequest).to.be.undefined;
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|