@theia/ai-chat-ui 1.58.3 → 1.59.0-next.72
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 +7 -7
- package/lib/browser/ai-chat-ui-contribution.js.map +1 -1
- package/lib/browser/ai-chat-ui-frontend-module.d.ts +1 -1
- package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-chat-ui-frontend-module.js +17 -13
- package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -1
- package/lib/browser/change-set-actions/change-set-accept-action.d.ts +10 -0
- package/lib/browser/change-set-actions/change-set-accept-action.d.ts.map +1 -0
- package/lib/browser/change-set-actions/change-set-accept-action.js +47 -0
- package/lib/browser/change-set-actions/change-set-accept-action.js.map +1 -0
- package/lib/browser/change-set-actions/change-set-action-service.d.ts +31 -0
- package/lib/browser/change-set-actions/change-set-action-service.d.ts.map +1 -0
- package/lib/browser/change-set-actions/change-set-action-service.js +57 -0
- package/lib/browser/change-set-actions/change-set-action-service.js.map +1 -0
- package/lib/browser/chat-input-widget.d.ts +22 -3
- package/lib/browser/chat-input-widget.d.ts.map +1 -1
- package/lib/browser/chat-input-widget.js +209 -56
- package/lib/browser/chat-input-widget.js.map +1 -1
- package/lib/browser/chat-response-renderer/ai-selection-resolver.d.ts +23 -0
- package/lib/browser/chat-response-renderer/ai-selection-resolver.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/{ai-editor-manager.js → ai-selection-resolver.js} +2 -38
- package/lib/browser/chat-response-renderer/ai-selection-resolver.js.map +1 -0
- package/lib/browser/chat-response-renderer/code-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/code-part-renderer.js +4 -3
- package/lib/browser/chat-response-renderer/code-part-renderer.js.map +1 -1
- package/lib/browser/chat-response-renderer/index.d.ts +1 -1
- package/lib/browser/chat-response-renderer/index.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/index.js +1 -1
- package/lib/browser/chat-response-renderer/index.js.map +1 -1
- package/lib/browser/chat-response-renderer/text-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/text-part-renderer.js +3 -1
- package/lib/browser/chat-response-renderer/text-part-renderer.js.map +1 -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 +7 -3
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.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 +25 -16
- package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -1
- package/lib/browser/chat-view-contribution.d.ts +2 -2
- package/lib/browser/chat-view-contribution.d.ts.map +1 -1
- package/lib/browser/chat-view-contribution.js +6 -6
- package/lib/browser/chat-view-contribution.js.map +1 -1
- package/lib/browser/chat-view-language-contribution.d.ts +10 -5
- package/lib/browser/chat-view-language-contribution.d.ts.map +1 -1
- package/lib/browser/chat-view-language-contribution.js +94 -14
- package/lib/browser/chat-view-language-contribution.js.map +1 -1
- package/lib/browser/chat-view-widget.d.ts +3 -0
- package/lib/browser/chat-view-widget.d.ts.map +1 -1
- package/lib/browser/chat-view-widget.js +15 -5
- package/lib/browser/chat-view-widget.js.map +1 -1
- package/lib/browser/context-variable-picker.d.ts +9 -0
- package/lib/browser/context-variable-picker.d.ts.map +1 -0
- package/lib/browser/context-variable-picker.js +86 -0
- package/lib/browser/context-variable-picker.js.map +1 -0
- package/package.json +11 -11
- package/src/browser/ai-chat-ui-contribution.ts +8 -8
- package/src/browser/ai-chat-ui-frontend-module.ts +18 -16
- package/src/browser/change-set-actions/change-set-accept-action.tsx +52 -0
- package/src/browser/change-set-actions/change-set-action-service.ts +65 -0
- package/src/browser/chat-input-widget.tsx +307 -75
- package/src/browser/chat-response-renderer/{ai-editor-manager.ts → ai-selection-resolver.ts} +6 -45
- package/src/browser/chat-response-renderer/code-part-renderer.tsx +4 -3
- package/src/browser/chat-response-renderer/index.ts +1 -1
- package/src/browser/chat-response-renderer/text-part-renderer.tsx +4 -1
- package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +9 -3
- package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +26 -16
- package/src/browser/chat-view-contribution.ts +6 -6
- package/src/browser/chat-view-language-contribution.ts +103 -19
- package/src/browser/chat-view-widget.tsx +19 -6
- package/src/browser/context-variable-picker.ts +85 -0
- package/src/browser/style/index.css +110 -12
- package/lib/browser/chat-response-renderer/ai-editor-manager.d.ts +0 -36
- package/lib/browser/chat-response-renderer/ai-editor-manager.d.ts.map +0 -1
- package/lib/browser/chat-response-renderer/ai-editor-manager.js.map +0 -1
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
//
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
|
-
import { ChangeSet,
|
|
17
|
-
import { Disposable,
|
|
16
|
+
import { ChangeSet, ChatAgent, ChatChangeEvent, ChatModel, ChatRequestModel } from '@theia/ai-chat';
|
|
17
|
+
import { Disposable, DisposableCollection, InMemoryResources, URI, nls } from '@theia/core';
|
|
18
18
|
import { ContextMenuRenderer, LabelProvider, Message, ReactWidget } from '@theia/core/lib/browser';
|
|
19
19
|
import { Deferred } from '@theia/core/lib/common/promise-util';
|
|
20
20
|
import { inject, injectable, optional, postConstruct } from '@theia/core/shared/inversify';
|
|
@@ -23,8 +23,13 @@ import { IMouseEvent } from '@theia/monaco-editor-core';
|
|
|
23
23
|
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
|
24
24
|
import { MonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
|
|
25
25
|
import { CHAT_VIEW_LANGUAGE_EXTENSION } from './chat-view-language-contribution';
|
|
26
|
+
import { AIVariableResolutionRequest } from '@theia/ai-core';
|
|
27
|
+
import { FrontendVariableService } from '@theia/ai-core/lib/browser';
|
|
28
|
+
import { ContextVariablePicker } from './context-variable-picker';
|
|
29
|
+
import { ChangeSetActionRenderer, ChangeSetActionService } from './change-set-actions/change-set-action-service';
|
|
26
30
|
|
|
27
31
|
type Query = (query: string) => Promise<void>;
|
|
32
|
+
type Unpin = () => void;
|
|
28
33
|
type Cancel = (requestModel: ChatRequestModel) => void;
|
|
29
34
|
type DeleteChangeSet = (requestModel: ChatRequestModel) => void;
|
|
30
35
|
type DeleteChangeSetElement = (requestModel: ChatRequestModel, index: number) => void;
|
|
@@ -32,6 +37,7 @@ type DeleteChangeSetElement = (requestModel: ChatRequestModel, index: number) =>
|
|
|
32
37
|
export const AIChatInputConfiguration = Symbol('AIChatInputConfiguration');
|
|
33
38
|
export interface AIChatInputConfiguration {
|
|
34
39
|
showContext?: boolean;
|
|
40
|
+
showPinnedAgent?: boolean;
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
@injectable()
|
|
@@ -42,8 +48,8 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
42
48
|
@inject(MonacoEditorProvider)
|
|
43
49
|
protected readonly editorProvider: MonacoEditorProvider;
|
|
44
50
|
|
|
45
|
-
@inject(
|
|
46
|
-
protected readonly
|
|
51
|
+
@inject(InMemoryResources)
|
|
52
|
+
protected readonly resources: InMemoryResources;
|
|
47
53
|
|
|
48
54
|
@inject(ContextMenuRenderer)
|
|
49
55
|
protected readonly contextMenuRenderer: ContextMenuRenderer;
|
|
@@ -51,9 +57,18 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
51
57
|
@inject(AIChatInputConfiguration) @optional()
|
|
52
58
|
protected readonly configuration: AIChatInputConfiguration | undefined;
|
|
53
59
|
|
|
60
|
+
@inject(FrontendVariableService)
|
|
61
|
+
protected readonly variableService: FrontendVariableService;
|
|
62
|
+
|
|
54
63
|
@inject(LabelProvider)
|
|
55
64
|
protected readonly labelProvider: LabelProvider;
|
|
56
65
|
|
|
66
|
+
@inject(ContextVariablePicker)
|
|
67
|
+
protected readonly contextVariablePicker: ContextVariablePicker;
|
|
68
|
+
|
|
69
|
+
@inject(ChangeSetActionService)
|
|
70
|
+
protected readonly changeSetActionService: ChangeSetActionService;
|
|
71
|
+
|
|
57
72
|
protected editorRef: MonacoEditor | undefined = undefined;
|
|
58
73
|
private editorReady = new Deferred<void>();
|
|
59
74
|
|
|
@@ -63,6 +78,10 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
63
78
|
set onQuery(query: Query) {
|
|
64
79
|
this._onQuery = query;
|
|
65
80
|
}
|
|
81
|
+
private _onUnpin: Unpin;
|
|
82
|
+
set onUnpin(unpin: Unpin) {
|
|
83
|
+
this._onUnpin = unpin;
|
|
84
|
+
}
|
|
66
85
|
private _onCancel: Cancel;
|
|
67
86
|
set onCancel(cancel: Cancel) {
|
|
68
87
|
this._onCancel = cancel;
|
|
@@ -75,11 +94,25 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
75
94
|
set onDeleteChangeSetElement(deleteChangeSetElement: DeleteChangeSetElement) {
|
|
76
95
|
this._onDeleteChangeSetElement = deleteChangeSetElement;
|
|
77
96
|
}
|
|
97
|
+
|
|
98
|
+
protected onDisposeForChatModel = new DisposableCollection();
|
|
78
99
|
private _chatModel: ChatModel;
|
|
79
100
|
set chatModel(chatModel: ChatModel) {
|
|
101
|
+
this.onDisposeForChatModel.dispose();
|
|
102
|
+
this.onDisposeForChatModel = new DisposableCollection();
|
|
103
|
+
this.onDisposeForChatModel.push(chatModel.onDidChange(event => {
|
|
104
|
+
if (event.kind === 'addVariable' || event.kind === 'removeVariable') {
|
|
105
|
+
this.update();
|
|
106
|
+
}
|
|
107
|
+
}));
|
|
80
108
|
this._chatModel = chatModel;
|
|
81
109
|
this.update();
|
|
82
110
|
}
|
|
111
|
+
private _pinnedAgent: ChatAgent | undefined;
|
|
112
|
+
set pinnedAgent(pinnedAgent: ChatAgent | undefined) {
|
|
113
|
+
this._pinnedAgent = pinnedAgent;
|
|
114
|
+
this.update();
|
|
115
|
+
}
|
|
83
116
|
|
|
84
117
|
@postConstruct()
|
|
85
118
|
protected init(): void {
|
|
@@ -101,12 +134,19 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
101
134
|
return (
|
|
102
135
|
<ChatInput
|
|
103
136
|
onQuery={this._onQuery.bind(this)}
|
|
137
|
+
onUnpin={this._onUnpin.bind(this)}
|
|
104
138
|
onCancel={this._onCancel.bind(this)}
|
|
139
|
+
onDragOver={this.onDragOver.bind(this)}
|
|
140
|
+
onDrop={this.onDrop.bind(this)}
|
|
105
141
|
onDeleteChangeSet={this._onDeleteChangeSet.bind(this)}
|
|
106
142
|
onDeleteChangeSetElement={this._onDeleteChangeSetElement.bind(this)}
|
|
143
|
+
onAddContextElement={this.addContextElement.bind(this)}
|
|
144
|
+
onDeleteContextElement={this.deleteContextElement.bind(this)}
|
|
145
|
+
context={this._chatModel.context.getVariables()}
|
|
107
146
|
chatModel={this._chatModel}
|
|
147
|
+
pinnedAgent={this._pinnedAgent}
|
|
108
148
|
editorProvider={this.editorProvider}
|
|
109
|
-
|
|
149
|
+
resources={this.resources}
|
|
110
150
|
contextMenuCallback={this.handleContextMenu.bind(this)}
|
|
111
151
|
isEnabled={this.isEnabled}
|
|
112
152
|
setEditorRef={editor => {
|
|
@@ -114,16 +154,64 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
114
154
|
this.editorReady.resolve();
|
|
115
155
|
}}
|
|
116
156
|
showContext={this.configuration?.showContext}
|
|
157
|
+
showPinnedAgent={this.configuration?.showPinnedAgent}
|
|
117
158
|
labelProvider={this.labelProvider}
|
|
159
|
+
actionService={this.changeSetActionService}
|
|
118
160
|
/>
|
|
119
161
|
);
|
|
120
162
|
}
|
|
121
163
|
|
|
164
|
+
protected onDragOver(event: React.DragEvent): void {
|
|
165
|
+
event.preventDefault();
|
|
166
|
+
event.stopPropagation();
|
|
167
|
+
this.node.classList.add('drag-over');
|
|
168
|
+
if (event.dataTransfer?.types.includes('text/plain')) {
|
|
169
|
+
event.dataTransfer!.dropEffect = 'copy';
|
|
170
|
+
} else {
|
|
171
|
+
event.dataTransfer!.dropEffect = 'link';
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
protected onDrop(event: React.DragEvent): void {
|
|
176
|
+
event.preventDefault();
|
|
177
|
+
event.stopPropagation();
|
|
178
|
+
this.node.classList.remove('drag-over');
|
|
179
|
+
const dataTransferText = event.dataTransfer?.getData('text/plain');
|
|
180
|
+
const position = this.editorRef?.getControl().getTargetAtClientPoint(event.clientX, event.clientY)?.position;
|
|
181
|
+
this.variableService.getDropResult(event.nativeEvent, { type: 'ai-chat-input-widget' }).then(result => {
|
|
182
|
+
result.variables.forEach(variable => this.addContext(variable));
|
|
183
|
+
const text = result.text ?? dataTransferText;
|
|
184
|
+
if (position && text) {
|
|
185
|
+
this.editorRef?.getControl().executeEdits('drag-and-drop', [{
|
|
186
|
+
range: {
|
|
187
|
+
startLineNumber: position.lineNumber,
|
|
188
|
+
startColumn: position.column,
|
|
189
|
+
endLineNumber: position.lineNumber,
|
|
190
|
+
endColumn: position.column
|
|
191
|
+
},
|
|
192
|
+
text
|
|
193
|
+
}]);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
122
198
|
public setEnabled(enabled: boolean): void {
|
|
123
199
|
this.isEnabled = enabled;
|
|
124
200
|
this.update();
|
|
125
201
|
}
|
|
126
202
|
|
|
203
|
+
protected addContextElement(): void {
|
|
204
|
+
this.contextVariablePicker.pickContextVariable().then(contextElement => {
|
|
205
|
+
if (contextElement) {
|
|
206
|
+
this._chatModel.context.addVariables(contextElement);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
protected deleteContextElement(index: number): void {
|
|
212
|
+
this._chatModel.context.deleteVariables(index);
|
|
213
|
+
}
|
|
214
|
+
|
|
127
215
|
protected handleContextMenu(event: IMouseEvent): void {
|
|
128
216
|
this.contextMenuRenderer.render({
|
|
129
217
|
menuPath: AIChatInputWidget.CONTEXT_MENU,
|
|
@@ -132,21 +220,33 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
132
220
|
event.preventDefault();
|
|
133
221
|
}
|
|
134
222
|
|
|
223
|
+
addContext(variable: AIVariableResolutionRequest): void {
|
|
224
|
+
this._chatModel.context.addVariables(variable);
|
|
225
|
+
}
|
|
135
226
|
}
|
|
136
227
|
|
|
137
228
|
interface ChatInputProperties {
|
|
138
229
|
onCancel: (requestModel: ChatRequestModel) => void;
|
|
139
230
|
onQuery: (query: string) => void;
|
|
231
|
+
onUnpin: () => void;
|
|
232
|
+
onDragOver: (event: React.DragEvent) => void;
|
|
233
|
+
onDrop: (event: React.DragEvent) => void;
|
|
140
234
|
onDeleteChangeSet: (sessionId: string) => void;
|
|
141
235
|
onDeleteChangeSetElement: (sessionId: string, index: number) => void;
|
|
236
|
+
onAddContextElement: () => void;
|
|
237
|
+
onDeleteContextElement: (index: number) => void;
|
|
238
|
+
context?: readonly AIVariableResolutionRequest[];
|
|
142
239
|
isEnabled?: boolean;
|
|
143
240
|
chatModel: ChatModel;
|
|
241
|
+
pinnedAgent?: ChatAgent;
|
|
144
242
|
editorProvider: MonacoEditorProvider;
|
|
145
|
-
|
|
243
|
+
resources: InMemoryResources;
|
|
146
244
|
contextMenuCallback: (event: IMouseEvent) => void;
|
|
147
245
|
setEditorRef: (editor: MonacoEditor | undefined) => void;
|
|
148
246
|
showContext?: boolean;
|
|
247
|
+
showPinnedAgent?: boolean;
|
|
149
248
|
labelProvider: LabelProvider;
|
|
249
|
+
actionService: ChangeSetActionService;
|
|
150
250
|
}
|
|
151
251
|
|
|
152
252
|
const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInputProperties) => {
|
|
@@ -156,7 +256,15 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
156
256
|
const [inProgress, setInProgress] = React.useState(false);
|
|
157
257
|
const [isInputEmpty, setIsInputEmpty] = React.useState(true);
|
|
158
258
|
const [changeSetUI, setChangeSetUI] = React.useState(
|
|
159
|
-
() => props.chatModel.changeSet
|
|
259
|
+
() => props.chatModel.changeSet
|
|
260
|
+
? buildChangeSetUI(
|
|
261
|
+
props.chatModel.changeSet,
|
|
262
|
+
props.labelProvider,
|
|
263
|
+
props.actionService.getActionsForChangeset(props.chatModel.changeSet),
|
|
264
|
+
onDeleteChangeSet,
|
|
265
|
+
onDeleteChangeSetElement
|
|
266
|
+
)
|
|
267
|
+
: undefined
|
|
160
268
|
);
|
|
161
269
|
|
|
162
270
|
// eslint-disable-next-line no-null/no-null
|
|
@@ -166,12 +274,13 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
166
274
|
const editorRef = React.useRef<MonacoEditor | undefined>(undefined);
|
|
167
275
|
|
|
168
276
|
React.useEffect(() => {
|
|
277
|
+
const uri = new URI(`ai-chat:/input.${CHAT_VIEW_LANGUAGE_EXTENSION}`);
|
|
278
|
+
const resource = props.resources.add(uri, '');
|
|
169
279
|
const createInputElement = async () => {
|
|
170
280
|
const paddingTop = 6;
|
|
171
281
|
const lineHeight = 20;
|
|
172
282
|
const maxHeight = 240;
|
|
173
|
-
const
|
|
174
|
-
const editor = await props.editorProvider.createInline(resource.uri, editorContainerRef.current!, {
|
|
283
|
+
const editor = await props.editorProvider.createInline(uri, editorContainerRef.current!, {
|
|
175
284
|
language: CHAT_VIEW_LANGUAGE_EXTENSION,
|
|
176
285
|
// Disable code lens, inlay hints and hover support to avoid console errors from other contributions
|
|
177
286
|
codeLens: false,
|
|
@@ -234,7 +343,9 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
234
343
|
props.setEditorRef(editor);
|
|
235
344
|
};
|
|
236
345
|
createInputElement();
|
|
346
|
+
|
|
237
347
|
return () => {
|
|
348
|
+
resource.dispose();
|
|
238
349
|
props.setEditorRef(undefined);
|
|
239
350
|
if (editorRef.current) {
|
|
240
351
|
editorRef.current.dispose();
|
|
@@ -257,14 +368,28 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
257
368
|
setInProgress(ChatRequestModel.isInProgress(event.request))
|
|
258
369
|
);
|
|
259
370
|
} else if (ChatChangeEvent.isChangeSetEvent(event)) {
|
|
260
|
-
if (event.
|
|
261
|
-
setChangeSetUI(buildChangeSetUI(event.changeSet, props.labelProvider, onDeleteChangeSet, onDeleteChangeSetElement));
|
|
262
|
-
} else {
|
|
371
|
+
if (event.kind === 'removeChangeSet') {
|
|
263
372
|
setChangeSetUI(undefined);
|
|
373
|
+
} else if (event.kind === 'setChangeSet' || 'updateChangeSet') {
|
|
374
|
+
setChangeSetUI(buildChangeSetUI(
|
|
375
|
+
event.changeSet,
|
|
376
|
+
props.labelProvider,
|
|
377
|
+
props.actionService.getActionsForChangeset(event.changeSet),
|
|
378
|
+
onDeleteChangeSet,
|
|
379
|
+
onDeleteChangeSetElement
|
|
380
|
+
));
|
|
264
381
|
}
|
|
265
382
|
}
|
|
266
383
|
});
|
|
267
|
-
setChangeSetUI(props.chatModel.changeSet
|
|
384
|
+
setChangeSetUI(props.chatModel.changeSet
|
|
385
|
+
? buildChangeSetUI(
|
|
386
|
+
props.chatModel.changeSet,
|
|
387
|
+
props.labelProvider,
|
|
388
|
+
props.actionService.getActionsForChangeset(props.chatModel.changeSet),
|
|
389
|
+
onDeleteChangeSet,
|
|
390
|
+
onDeleteChangeSetElement
|
|
391
|
+
)
|
|
392
|
+
: undefined);
|
|
268
393
|
return () => {
|
|
269
394
|
listener?.dispose();
|
|
270
395
|
responseListenerRef.current?.dispose();
|
|
@@ -272,7 +397,16 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
272
397
|
};
|
|
273
398
|
}, [props.chatModel]);
|
|
274
399
|
|
|
275
|
-
|
|
400
|
+
React.useEffect(() => {
|
|
401
|
+
const disposable = props.actionService.onDidChange(() => {
|
|
402
|
+
if (!props.chatModel.changeSet) { return; }
|
|
403
|
+
const newActions = props.actionService.getActionsForChangeset(props.chatModel.changeSet);
|
|
404
|
+
setChangeSetUI(current => !current ? current : { ...current, actions: newActions });
|
|
405
|
+
});
|
|
406
|
+
return () => disposable.dispose();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const submit = React.useCallback(function submit(value: string): void {
|
|
276
410
|
if (!value || value.trim().length === 0) {
|
|
277
411
|
return;
|
|
278
412
|
}
|
|
@@ -281,7 +415,7 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
281
415
|
if (editorRef.current) {
|
|
282
416
|
editorRef.current.document.textEditorModel.setValue('');
|
|
283
417
|
}
|
|
284
|
-
}
|
|
418
|
+
}, [props.context, props.onQuery, editorRef]);
|
|
285
419
|
|
|
286
420
|
const onKeyDown = React.useCallback((event: React.KeyboardEvent) => {
|
|
287
421
|
if (!props.isEnabled) {
|
|
@@ -291,7 +425,7 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
291
425
|
event.preventDefault();
|
|
292
426
|
submit(editorRef.current?.document.textEditorModel.getValue() || '');
|
|
293
427
|
}
|
|
294
|
-
}, [props.isEnabled]);
|
|
428
|
+
}, [props.isEnabled, submit]);
|
|
295
429
|
|
|
296
430
|
const handleInputFocus = () => {
|
|
297
431
|
hidePlaceholderIfEditorFilled();
|
|
@@ -319,15 +453,46 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
319
453
|
}
|
|
320
454
|
};
|
|
321
455
|
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
456
|
+
const handlePin = () => {
|
|
457
|
+
if (editorRef.current) {
|
|
458
|
+
editorRef.current.getControl().getModel()?.applyEdits([{
|
|
459
|
+
range: {
|
|
460
|
+
startLineNumber: 1,
|
|
461
|
+
startColumn: 1,
|
|
462
|
+
endLineNumber: 1,
|
|
463
|
+
endColumn: 1
|
|
464
|
+
},
|
|
465
|
+
text: '@ ',
|
|
466
|
+
}]);
|
|
467
|
+
editorRef.current.getControl().setPosition({ lineNumber: 1, column: 2 });
|
|
468
|
+
editorRef.current.getControl().getAction('editor.action.triggerSuggest')?.run();
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
const leftOptions = [
|
|
473
|
+
...(props.showContext
|
|
474
|
+
? [{
|
|
475
|
+
title: nls.localize('theia/ai/chat-ui/attachToContext', 'Attach elements to context'),
|
|
476
|
+
handler: () => props.onAddContextElement(),
|
|
477
|
+
className: 'codicon-add'
|
|
478
|
+
}]
|
|
479
|
+
: []),
|
|
480
|
+
...(props.showPinnedAgent
|
|
481
|
+
? [{
|
|
482
|
+
title: props.pinnedAgent ? nls.localize('theia/ai/chat-ui/unpinAgent', 'Unpin Agent') : nls.localize('theia/ai/chat-ui/pinAgent', 'Pin Agent'),
|
|
483
|
+
handler: props.pinnedAgent ? props.onUnpin : handlePin,
|
|
484
|
+
className: 'at-icon',
|
|
485
|
+
text: {
|
|
486
|
+
align: 'right',
|
|
487
|
+
content: props.pinnedAgent && props.pinnedAgent.name
|
|
488
|
+
},
|
|
489
|
+
}]
|
|
490
|
+
: []),
|
|
491
|
+
] as Option[];
|
|
327
492
|
|
|
328
493
|
const rightOptions = inProgress
|
|
329
494
|
? [{
|
|
330
|
-
title: 'Cancel (Esc)',
|
|
495
|
+
title: nls.localize('theia/ai/chat-ui/cancel', 'Cancel (Esc)'),
|
|
331
496
|
handler: () => {
|
|
332
497
|
const latestRequest = getLatestRequest(props.chatModel);
|
|
333
498
|
if (latestRequest) {
|
|
@@ -338,7 +503,7 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
338
503
|
className: 'codicon-stop-circle'
|
|
339
504
|
}]
|
|
340
505
|
: [{
|
|
341
|
-
title: 'Send (Enter)',
|
|
506
|
+
title: nls.localize('theia/ai/chat-ui/send', 'Send (Enter)'),
|
|
342
507
|
handler: () => {
|
|
343
508
|
if (props.isEnabled) {
|
|
344
509
|
submit(editorRef.current?.document.textEditorModel.getValue() || '');
|
|
@@ -348,14 +513,19 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
348
513
|
disabled: isInputEmpty || !props.isEnabled
|
|
349
514
|
}];
|
|
350
515
|
|
|
351
|
-
|
|
516
|
+
const contextUI = buildContextUI(props.context, props.labelProvider, props.onDeleteContextElement);
|
|
517
|
+
|
|
518
|
+
return <div className='theia-ChatInput' onDragOver={props.onDragOver} onDrop={props.onDrop} >
|
|
352
519
|
{changeSetUI?.elements &&
|
|
353
520
|
<ChangeSetBox changeSet={changeSetUI} />
|
|
354
521
|
}
|
|
355
522
|
<div className='theia-ChatInput-Editor-Box'>
|
|
356
523
|
<div className='theia-ChatInput-Editor' ref={editorContainerRef} onKeyDown={onKeyDown} onFocus={handleInputFocus} onBlur={handleInputBlur}>
|
|
357
|
-
<div ref={placeholderRef} className='theia-ChatInput-Editor-Placeholder'>Ask a question</div>
|
|
524
|
+
<div ref={placeholderRef} className='theia-ChatInput-Editor-Placeholder'>{nls.localizeByDefault('Ask a question')}</div>
|
|
358
525
|
</div>
|
|
526
|
+
{props.context && props.context.length > 0 &&
|
|
527
|
+
<ChatContext context={contextUI.context} />
|
|
528
|
+
}
|
|
359
529
|
<ChatInputOptions leftOptions={leftOptions} rightOptions={rightOptions} />
|
|
360
530
|
</div>
|
|
361
531
|
</div>;
|
|
@@ -366,22 +536,28 @@ const noPropagation = (handler: () => void) => (e: React.MouseEvent) => {
|
|
|
366
536
|
e.stopPropagation();
|
|
367
537
|
};
|
|
368
538
|
|
|
369
|
-
const buildChangeSetUI = (
|
|
539
|
+
const buildChangeSetUI = (
|
|
540
|
+
changeSet: ChangeSet,
|
|
541
|
+
labelProvider: LabelProvider,
|
|
542
|
+
actions: ChangeSetActionRenderer[],
|
|
543
|
+
onDeleteChangeSet: () => void,
|
|
544
|
+
onDeleteChangeSetElement: (index: number) => void
|
|
545
|
+
): ChangeSetUI => ({
|
|
370
546
|
title: changeSet.title,
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
delete: () => onDeleteChangeSet(),
|
|
547
|
+
changeSet,
|
|
548
|
+
deleteChangeSet: onDeleteChangeSet,
|
|
374
549
|
elements: changeSet.getElements().map(element => ({
|
|
375
|
-
open: element
|
|
550
|
+
open: element.open?.bind(element),
|
|
376
551
|
iconClass: element.icon ?? labelProvider.getIcon(element.uri) ?? labelProvider.fileIcon,
|
|
377
552
|
nameClass: `${element.type} ${element.state}`,
|
|
378
553
|
name: element.name ?? labelProvider.getName(element.uri),
|
|
379
554
|
additionalInfo: element.additionalInfo ?? labelProvider.getDetails(element.uri),
|
|
380
555
|
openChange: element?.openChange?.bind(element),
|
|
381
|
-
|
|
382
|
-
|
|
556
|
+
apply: element.state !== 'applied' ? element?.apply?.bind(element) : undefined,
|
|
557
|
+
revert: element.state === 'applied' || element.state === 'stale' ? element?.revert?.bind(element) : undefined,
|
|
383
558
|
delete: () => onDeleteChangeSetElement(changeSet.getElements().indexOf(element))
|
|
384
|
-
}))
|
|
559
|
+
})),
|
|
560
|
+
actions
|
|
385
561
|
});
|
|
386
562
|
|
|
387
563
|
interface ChangeSetUIElement {
|
|
@@ -391,58 +567,69 @@ interface ChangeSetUIElement {
|
|
|
391
567
|
additionalInfo: string;
|
|
392
568
|
open?: () => void;
|
|
393
569
|
openChange?: () => void;
|
|
394
|
-
|
|
395
|
-
|
|
570
|
+
apply?: () => void;
|
|
571
|
+
revert?: () => void;
|
|
396
572
|
delete: () => void;
|
|
397
573
|
}
|
|
398
574
|
|
|
399
575
|
interface ChangeSetUI {
|
|
576
|
+
changeSet: ChangeSet;
|
|
400
577
|
title: string;
|
|
401
|
-
|
|
402
|
-
acceptAllPendingElements: () => void;
|
|
403
|
-
delete: () => void;
|
|
578
|
+
deleteChangeSet: () => void;
|
|
404
579
|
elements: ChangeSetUIElement[];
|
|
580
|
+
actions: ChangeSetActionRenderer[];
|
|
405
581
|
}
|
|
406
582
|
|
|
407
|
-
|
|
583
|
+
/** Memo because the parent element rerenders on every key press in the chat widget. */
|
|
584
|
+
const ChangeSetBox: React.FunctionComponent<{ changeSet: ChangeSetUI }> = React.memo(({ changeSet: { changeSet, title, deleteChangeSet, elements, actions } }) => (
|
|
408
585
|
<div className='theia-ChatInput-ChangeSet-Box'>
|
|
409
586
|
<div className='theia-ChatInput-ChangeSet-Header'>
|
|
410
|
-
<h3>{
|
|
587
|
+
<h3>{title}</h3>
|
|
411
588
|
<div className='theia-ChatInput-ChangeSet-Header-Actions'>
|
|
412
|
-
<
|
|
413
|
-
|
|
414
|
-
disabled={changeSet.disabled}
|
|
415
|
-
title='Accept all pending changes'
|
|
416
|
-
onClick={() => changeSet.acceptAllPendingElements()}
|
|
417
|
-
>
|
|
418
|
-
Accept
|
|
419
|
-
</button>
|
|
420
|
-
<span className='codicon codicon-close action' title='Delete Change Set' onClick={() => changeSet.delete()} />
|
|
589
|
+
{actions.map(action => <div key={action.id} className='theia-changeSet-Action'>{action.render(changeSet)}</div>)}
|
|
590
|
+
<span className='codicon codicon-close action' title={nls.localize('theia/ai/chat-ui/deleteChangeSet', 'Delete Change Set')} onClick={() => deleteChangeSet()} />
|
|
421
591
|
</div>
|
|
422
592
|
</div>
|
|
423
593
|
<div className='theia-ChatInput-ChangeSet-List'>
|
|
424
594
|
<ul>
|
|
425
|
-
{
|
|
426
|
-
<li key={index} title='Open Diff' onClick={() => element.openChange?.()}>
|
|
595
|
+
{elements.map((element, index) => (
|
|
596
|
+
<li key={index} title={nls.localize('theia/ai/chat-ui/openDiff', 'Open Diff')} onClick={() => element.openChange?.()}>
|
|
427
597
|
<div className={`theia-ChatInput-ChangeSet-Icon ${element.iconClass}`} />
|
|
428
|
-
<span className=
|
|
429
|
-
{element.
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
598
|
+
<span className='theia-ChatInput-ChangeSet-labelParts'>
|
|
599
|
+
<span className={`theia-ChatInput-ChangeSet-title ${element.nameClass}`}>
|
|
600
|
+
{element.name}
|
|
601
|
+
</span>
|
|
602
|
+
<span className='theia-ChatInput-ChangeSet-additionalInfo'>
|
|
603
|
+
{element.additionalInfo}
|
|
604
|
+
</span>
|
|
433
605
|
</span>
|
|
434
606
|
<div className='theia-ChatInput-ChangeSet-Actions'>
|
|
435
|
-
{element.open && (
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
607
|
+
{element.open && (
|
|
608
|
+
<span
|
|
609
|
+
className='codicon codicon-file action'
|
|
610
|
+
title={nls.localize('theia/ai/chat-ui/openOriginalFile', 'Open Original File')}
|
|
611
|
+
onClick={noPropagation(() => element.open!())}
|
|
612
|
+
/>)}
|
|
613
|
+
{element.revert && (
|
|
614
|
+
<span
|
|
615
|
+
className='codicon codicon-discard action'
|
|
616
|
+
title={nls.localizeByDefault('Revert')}
|
|
617
|
+
onClick={noPropagation(() => element.revert!())}
|
|
618
|
+
/>)}
|
|
619
|
+
{element.apply && (
|
|
620
|
+
<span
|
|
621
|
+
className='codicon codicon-check action'
|
|
622
|
+
title={nls.localizeByDefault('Apply')}
|
|
623
|
+
onClick={noPropagation(() => element.apply!())}
|
|
624
|
+
/>)}
|
|
625
|
+
<span className='codicon codicon-close action' title={nls.localizeByDefault('Delete')} onClick={noPropagation(() => element.delete())} />
|
|
439
626
|
</div>
|
|
440
627
|
</li>
|
|
441
628
|
))}
|
|
442
629
|
</ul>
|
|
443
630
|
</div>
|
|
444
631
|
</div>
|
|
445
|
-
);
|
|
632
|
+
));
|
|
446
633
|
|
|
447
634
|
interface ChatInputOptionsProps {
|
|
448
635
|
leftOptions: Option[];
|
|
@@ -454,6 +641,10 @@ interface Option {
|
|
|
454
641
|
handler: () => void;
|
|
455
642
|
className: string;
|
|
456
643
|
disabled?: boolean;
|
|
644
|
+
text?: {
|
|
645
|
+
align?: 'left' | 'right';
|
|
646
|
+
content: string;
|
|
647
|
+
};
|
|
457
648
|
}
|
|
458
649
|
|
|
459
650
|
const ChatInputOptions: React.FunctionComponent<ChatInputOptionsProps> = ({ leftOptions, rightOptions }) => (
|
|
@@ -462,38 +653,79 @@ const ChatInputOptions: React.FunctionComponent<ChatInputOptionsProps> = ({ left
|
|
|
462
653
|
{leftOptions.map((option, index) => (
|
|
463
654
|
<span
|
|
464
655
|
key={index}
|
|
465
|
-
className={`
|
|
656
|
+
className={`option ${option.disabled ? 'disabled' : ''} ${option.text?.align === 'right' ? 'reverse' : ''}`}
|
|
466
657
|
title={option.title}
|
|
467
658
|
onClick={option.handler}
|
|
468
|
-
|
|
659
|
+
>
|
|
660
|
+
<span>{option.text?.content}</span>
|
|
661
|
+
<span className={`codicon ${option.className}`} />
|
|
662
|
+
</span>
|
|
469
663
|
))}
|
|
470
664
|
</div>
|
|
471
665
|
<div className="theia-ChatInputOptions-right">
|
|
472
666
|
{rightOptions.map((option, index) => (
|
|
473
667
|
<span
|
|
474
668
|
key={index}
|
|
475
|
-
className={`
|
|
669
|
+
className={`option ${option.disabled ? 'disabled' : ''} ${option.text?.align === 'right' ? 'reverse' : ''}`}
|
|
476
670
|
title={option.title}
|
|
477
671
|
onClick={option.handler}
|
|
478
|
-
|
|
672
|
+
>
|
|
673
|
+
<span>{option.text?.content}</span>
|
|
674
|
+
<span className={`codicon ${option.className}`} />
|
|
675
|
+
</span>
|
|
479
676
|
))}
|
|
480
677
|
</div>
|
|
481
678
|
</div>
|
|
482
679
|
);
|
|
483
680
|
|
|
484
|
-
function
|
|
485
|
-
|
|
681
|
+
function getLatestRequest(chatModel: ChatModel): ChatRequestModel | undefined {
|
|
682
|
+
const requests = chatModel.getRequests();
|
|
683
|
+
return requests.length > 0 ? requests[requests.length - 1] : undefined;
|
|
486
684
|
}
|
|
487
685
|
|
|
488
|
-
function
|
|
489
|
-
|
|
686
|
+
function buildContextUI(context: readonly AIVariableResolutionRequest[] | undefined, labelProvider: LabelProvider, onDeleteContextElement: (index: number) => void): ChatContextUI {
|
|
687
|
+
if (!context) {
|
|
688
|
+
return { context: [] };
|
|
689
|
+
}
|
|
690
|
+
return {
|
|
691
|
+
context: context.map((element, index) => ({
|
|
692
|
+
name: labelProvider.getName(element),
|
|
693
|
+
iconClass: labelProvider.getIcon(element),
|
|
694
|
+
nameClass: element.variable.name,
|
|
695
|
+
additionalInfo: labelProvider.getDetails(element),
|
|
696
|
+
details: labelProvider.getLongName(element),
|
|
697
|
+
delete: () => onDeleteContextElement(index),
|
|
698
|
+
}))
|
|
699
|
+
};
|
|
490
700
|
}
|
|
491
701
|
|
|
492
|
-
|
|
493
|
-
|
|
702
|
+
interface ChatContextUI {
|
|
703
|
+
context: {
|
|
704
|
+
name: string;
|
|
705
|
+
iconClass: string;
|
|
706
|
+
nameClass: string;
|
|
707
|
+
additionalInfo?: string;
|
|
708
|
+
details?: string;
|
|
709
|
+
delete: () => void;
|
|
710
|
+
open?: () => void;
|
|
711
|
+
}[];
|
|
494
712
|
}
|
|
495
713
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
714
|
+
const ChatContext: React.FunctionComponent<ChatContextUI> = ({ context }) => (
|
|
715
|
+
<div className="theia-ChatInput-ChatContext">
|
|
716
|
+
<ul>
|
|
717
|
+
{context.map((element, index) => (
|
|
718
|
+
<li key={index} className="theia-ChatInput-ChatContext-Element" title={element.details} onClick={() => element.open?.()}>
|
|
719
|
+
<div className={`theia-ChatInput-ChatContext-Icon ${element.iconClass}`} />
|
|
720
|
+
<span className={`theia-ChatInput-ChatContext-title ${element.nameClass}`}>
|
|
721
|
+
{element.name}
|
|
722
|
+
</span>
|
|
723
|
+
<span className='theia-ChatInput-ChatContext-additionalInfo'>
|
|
724
|
+
{element.additionalInfo}
|
|
725
|
+
</span>
|
|
726
|
+
<span className="codicon codicon-close action" title={nls.localizeByDefault('Delete')} onClick={() => element.delete()} />
|
|
727
|
+
</li>
|
|
728
|
+
))}
|
|
729
|
+
</ul>
|
|
730
|
+
</div>
|
|
731
|
+
);
|