@theia/ai-chat-ui 1.58.3 → 1.59.0-next.62
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.map +1 -1
- package/lib/browser/ai-chat-ui-frontend-module.js +7 -4
- package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -1
- package/lib/browser/chat-input-widget.d.ts +21 -4
- package/lib/browser/chat-input-widget.d.ts.map +1 -1
- package/lib/browser/chat-input-widget.js +177 -37
- package/lib/browser/chat-input-widget.js.map +1 -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 +4 -3
- package/lib/browser/chat-response-renderer/code-part-renderer.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 +4 -1
- package/lib/browser/chat-view-widget.d.ts.map +1 -1
- package/lib/browser/chat-view-widget.js +18 -7
- 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 +8 -4
- package/src/browser/chat-input-widget.tsx +255 -49
- package/src/browser/chat-response-renderer/code-part-renderer.tsx +4 -3
- 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 +22 -8
- package/src/browser/context-variable-picker.ts +85 -0
- package/src/browser/style/index.css +95 -9
|
@@ -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, ChangeSetElement, ChatChangeEvent, ChatModel, ChatRequestModel } from '@theia/ai-chat';
|
|
17
|
-
import { Disposable,
|
|
16
|
+
import { ChangeSet, ChangeSetElement, ChatAgent, ChatChangeEvent, ChatModel, ChatRequestModel } from '@theia/ai-chat';
|
|
17
|
+
import { Disposable, 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,12 @@ 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';
|
|
26
29
|
|
|
27
|
-
type Query = (query: string) => Promise<void>;
|
|
30
|
+
type Query = (query: string, context?: AIVariableResolutionRequest[]) => Promise<void>;
|
|
31
|
+
type Unpin = () => void;
|
|
28
32
|
type Cancel = (requestModel: ChatRequestModel) => void;
|
|
29
33
|
type DeleteChangeSet = (requestModel: ChatRequestModel) => void;
|
|
30
34
|
type DeleteChangeSetElement = (requestModel: ChatRequestModel, index: number) => void;
|
|
@@ -32,6 +36,7 @@ type DeleteChangeSetElement = (requestModel: ChatRequestModel, index: number) =>
|
|
|
32
36
|
export const AIChatInputConfiguration = Symbol('AIChatInputConfiguration');
|
|
33
37
|
export interface AIChatInputConfiguration {
|
|
34
38
|
showContext?: boolean;
|
|
39
|
+
showPinnedAgent?: boolean;
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
@injectable()
|
|
@@ -42,8 +47,8 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
42
47
|
@inject(MonacoEditorProvider)
|
|
43
48
|
protected readonly editorProvider: MonacoEditorProvider;
|
|
44
49
|
|
|
45
|
-
@inject(
|
|
46
|
-
protected readonly
|
|
50
|
+
@inject(InMemoryResources)
|
|
51
|
+
protected readonly resources: InMemoryResources;
|
|
47
52
|
|
|
48
53
|
@inject(ContextMenuRenderer)
|
|
49
54
|
protected readonly contextMenuRenderer: ContextMenuRenderer;
|
|
@@ -51,18 +56,30 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
51
56
|
@inject(AIChatInputConfiguration) @optional()
|
|
52
57
|
protected readonly configuration: AIChatInputConfiguration | undefined;
|
|
53
58
|
|
|
59
|
+
@inject(FrontendVariableService)
|
|
60
|
+
protected readonly variableService: FrontendVariableService;
|
|
61
|
+
|
|
54
62
|
@inject(LabelProvider)
|
|
55
63
|
protected readonly labelProvider: LabelProvider;
|
|
56
64
|
|
|
65
|
+
@inject(ContextVariablePicker)
|
|
66
|
+
protected readonly contextVariablePicker: ContextVariablePicker;
|
|
67
|
+
|
|
57
68
|
protected editorRef: MonacoEditor | undefined = undefined;
|
|
58
69
|
private editorReady = new Deferred<void>();
|
|
59
70
|
|
|
60
71
|
protected isEnabled = false;
|
|
61
72
|
|
|
73
|
+
protected context: AIVariableResolutionRequest[] = [];
|
|
74
|
+
|
|
62
75
|
private _onQuery: Query;
|
|
63
76
|
set onQuery(query: Query) {
|
|
64
77
|
this._onQuery = query;
|
|
65
78
|
}
|
|
79
|
+
private _onUnpin: Unpin;
|
|
80
|
+
set onUnpin(unpin: Unpin) {
|
|
81
|
+
this._onUnpin = unpin;
|
|
82
|
+
}
|
|
66
83
|
private _onCancel: Cancel;
|
|
67
84
|
set onCancel(cancel: Cancel) {
|
|
68
85
|
this._onCancel = cancel;
|
|
@@ -75,11 +92,17 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
75
92
|
set onDeleteChangeSetElement(deleteChangeSetElement: DeleteChangeSetElement) {
|
|
76
93
|
this._onDeleteChangeSetElement = deleteChangeSetElement;
|
|
77
94
|
}
|
|
95
|
+
|
|
78
96
|
private _chatModel: ChatModel;
|
|
79
97
|
set chatModel(chatModel: ChatModel) {
|
|
80
98
|
this._chatModel = chatModel;
|
|
81
99
|
this.update();
|
|
82
100
|
}
|
|
101
|
+
private _pinnedAgent: ChatAgent | undefined;
|
|
102
|
+
set pinnedAgent(pinnedAgent: ChatAgent | undefined) {
|
|
103
|
+
this._pinnedAgent = pinnedAgent;
|
|
104
|
+
this.update();
|
|
105
|
+
}
|
|
83
106
|
|
|
84
107
|
@postConstruct()
|
|
85
108
|
protected init(): void {
|
|
@@ -101,12 +124,19 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
101
124
|
return (
|
|
102
125
|
<ChatInput
|
|
103
126
|
onQuery={this._onQuery.bind(this)}
|
|
127
|
+
onUnpin={this._onUnpin.bind(this)}
|
|
104
128
|
onCancel={this._onCancel.bind(this)}
|
|
129
|
+
onDragOver={this.onDragOver.bind(this)}
|
|
130
|
+
onDrop={this.onDrop.bind(this)}
|
|
105
131
|
onDeleteChangeSet={this._onDeleteChangeSet.bind(this)}
|
|
106
132
|
onDeleteChangeSetElement={this._onDeleteChangeSetElement.bind(this)}
|
|
133
|
+
onAddContextElement={this.addContextElement.bind(this)}
|
|
134
|
+
onDeleteContextElement={this.deleteContextElement.bind(this)}
|
|
135
|
+
context={this.context}
|
|
107
136
|
chatModel={this._chatModel}
|
|
137
|
+
pinnedAgent={this._pinnedAgent}
|
|
108
138
|
editorProvider={this.editorProvider}
|
|
109
|
-
|
|
139
|
+
resources={this.resources}
|
|
110
140
|
contextMenuCallback={this.handleContextMenu.bind(this)}
|
|
111
141
|
isEnabled={this.isEnabled}
|
|
112
142
|
setEditorRef={editor => {
|
|
@@ -114,16 +144,64 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
114
144
|
this.editorReady.resolve();
|
|
115
145
|
}}
|
|
116
146
|
showContext={this.configuration?.showContext}
|
|
147
|
+
showPinnedAgent={this.configuration?.showPinnedAgent}
|
|
117
148
|
labelProvider={this.labelProvider}
|
|
118
149
|
/>
|
|
119
150
|
);
|
|
120
151
|
}
|
|
121
152
|
|
|
153
|
+
protected onDragOver(event: React.DragEvent): void {
|
|
154
|
+
event.preventDefault();
|
|
155
|
+
event.stopPropagation();
|
|
156
|
+
this.node.classList.add('drag-over');
|
|
157
|
+
if (event.dataTransfer?.types.includes('text/plain')) {
|
|
158
|
+
event.dataTransfer!.dropEffect = 'copy';
|
|
159
|
+
} else {
|
|
160
|
+
event.dataTransfer!.dropEffect = 'link';
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
protected onDrop(event: React.DragEvent): void {
|
|
165
|
+
event.preventDefault();
|
|
166
|
+
event.stopPropagation();
|
|
167
|
+
this.node.classList.remove('drag-over');
|
|
168
|
+
const dataTransferText = event.dataTransfer?.getData('text/plain');
|
|
169
|
+
const position = this.editorRef?.getControl().getTargetAtClientPoint(event.clientX, event.clientY)?.position;
|
|
170
|
+
this.variableService.getDropResult(event.nativeEvent, { type: 'ai-chat-input-widget' }).then(result => {
|
|
171
|
+
result.variables.forEach(variable => this.addContext(variable));
|
|
172
|
+
const text = result.text ?? dataTransferText;
|
|
173
|
+
if (position && text) {
|
|
174
|
+
this.editorRef?.getControl().executeEdits('drag-and-drop', [{
|
|
175
|
+
range: {
|
|
176
|
+
startLineNumber: position.lineNumber,
|
|
177
|
+
startColumn: position.column,
|
|
178
|
+
endLineNumber: position.lineNumber,
|
|
179
|
+
endColumn: position.column
|
|
180
|
+
},
|
|
181
|
+
text
|
|
182
|
+
}]);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
122
187
|
public setEnabled(enabled: boolean): void {
|
|
123
188
|
this.isEnabled = enabled;
|
|
124
189
|
this.update();
|
|
125
190
|
}
|
|
126
191
|
|
|
192
|
+
protected addContextElement(): void {
|
|
193
|
+
this.contextVariablePicker.pickContextVariable().then(contextElement => {
|
|
194
|
+
if (contextElement) {
|
|
195
|
+
this.addContext(contextElement);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
protected deleteContextElement(index: number): void {
|
|
201
|
+
this.context.splice(index, 1);
|
|
202
|
+
this.update();
|
|
203
|
+
}
|
|
204
|
+
|
|
127
205
|
protected handleContextMenu(event: IMouseEvent): void {
|
|
128
206
|
this.contextMenuRenderer.render({
|
|
129
207
|
menuPath: AIChatInputWidget.CONTEXT_MENU,
|
|
@@ -132,20 +210,35 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
132
210
|
event.preventDefault();
|
|
133
211
|
}
|
|
134
212
|
|
|
213
|
+
addContext(variableRequest: AIVariableResolutionRequest): void {
|
|
214
|
+
if (this.context.some(existing => existing.variable.id === variableRequest.variable.id && existing.arg === variableRequest.arg)) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
this.context.push(variableRequest);
|
|
218
|
+
this.update();
|
|
219
|
+
}
|
|
135
220
|
}
|
|
136
221
|
|
|
137
222
|
interface ChatInputProperties {
|
|
138
223
|
onCancel: (requestModel: ChatRequestModel) => void;
|
|
139
|
-
onQuery: (query: string) => void;
|
|
224
|
+
onQuery: (query: string, context?: AIVariableResolutionRequest[]) => void;
|
|
225
|
+
onUnpin: () => void;
|
|
226
|
+
onDragOver: (event: React.DragEvent) => void;
|
|
227
|
+
onDrop: (event: React.DragEvent) => void;
|
|
140
228
|
onDeleteChangeSet: (sessionId: string) => void;
|
|
141
229
|
onDeleteChangeSetElement: (sessionId: string, index: number) => void;
|
|
230
|
+
onAddContextElement: () => void;
|
|
231
|
+
onDeleteContextElement: (index: number) => void;
|
|
232
|
+
context?: AIVariableResolutionRequest[];
|
|
142
233
|
isEnabled?: boolean;
|
|
143
234
|
chatModel: ChatModel;
|
|
235
|
+
pinnedAgent?: ChatAgent;
|
|
144
236
|
editorProvider: MonacoEditorProvider;
|
|
145
|
-
|
|
237
|
+
resources: InMemoryResources;
|
|
146
238
|
contextMenuCallback: (event: IMouseEvent) => void;
|
|
147
239
|
setEditorRef: (editor: MonacoEditor | undefined) => void;
|
|
148
240
|
showContext?: boolean;
|
|
241
|
+
showPinnedAgent?: boolean;
|
|
149
242
|
labelProvider: LabelProvider;
|
|
150
243
|
}
|
|
151
244
|
|
|
@@ -166,12 +259,13 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
166
259
|
const editorRef = React.useRef<MonacoEditor | undefined>(undefined);
|
|
167
260
|
|
|
168
261
|
React.useEffect(() => {
|
|
262
|
+
const uri = new URI(`ai-chat:/input.${CHAT_VIEW_LANGUAGE_EXTENSION}`);
|
|
263
|
+
const resource = props.resources.add(uri, '');
|
|
169
264
|
const createInputElement = async () => {
|
|
170
265
|
const paddingTop = 6;
|
|
171
266
|
const lineHeight = 20;
|
|
172
267
|
const maxHeight = 240;
|
|
173
|
-
const
|
|
174
|
-
const editor = await props.editorProvider.createInline(resource.uri, editorContainerRef.current!, {
|
|
268
|
+
const editor = await props.editorProvider.createInline(uri, editorContainerRef.current!, {
|
|
175
269
|
language: CHAT_VIEW_LANGUAGE_EXTENSION,
|
|
176
270
|
// Disable code lens, inlay hints and hover support to avoid console errors from other contributions
|
|
177
271
|
codeLens: false,
|
|
@@ -234,7 +328,9 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
234
328
|
props.setEditorRef(editor);
|
|
235
329
|
};
|
|
236
330
|
createInputElement();
|
|
331
|
+
|
|
237
332
|
return () => {
|
|
333
|
+
resource.dispose();
|
|
238
334
|
props.setEditorRef(undefined);
|
|
239
335
|
if (editorRef.current) {
|
|
240
336
|
editorRef.current.dispose();
|
|
@@ -277,7 +373,7 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
277
373
|
return;
|
|
278
374
|
}
|
|
279
375
|
setInProgress(true);
|
|
280
|
-
props.onQuery(value);
|
|
376
|
+
props.onQuery(value, props.context);
|
|
281
377
|
if (editorRef.current) {
|
|
282
378
|
editorRef.current.document.textEditorModel.setValue('');
|
|
283
379
|
}
|
|
@@ -319,15 +415,46 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
319
415
|
}
|
|
320
416
|
};
|
|
321
417
|
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
418
|
+
const handlePin = () => {
|
|
419
|
+
if (editorRef.current) {
|
|
420
|
+
editorRef.current.getControl().getModel()?.applyEdits([{
|
|
421
|
+
range: {
|
|
422
|
+
startLineNumber: 1,
|
|
423
|
+
startColumn: 1,
|
|
424
|
+
endLineNumber: 1,
|
|
425
|
+
endColumn: 1
|
|
426
|
+
},
|
|
427
|
+
text: '@ ',
|
|
428
|
+
}]);
|
|
429
|
+
editorRef.current.getControl().setPosition({ lineNumber: 1, column: 2 });
|
|
430
|
+
editorRef.current.getControl().getAction('editor.action.triggerSuggest')?.run();
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
const leftOptions = [
|
|
435
|
+
...(props.showContext
|
|
436
|
+
? [{
|
|
437
|
+
title: nls.localize('theia/ai/chat-ui/attachToContext', 'Attach elements to context'),
|
|
438
|
+
handler: () => props.onAddContextElement(),
|
|
439
|
+
className: 'codicon-add'
|
|
440
|
+
}]
|
|
441
|
+
: []),
|
|
442
|
+
...(props.showPinnedAgent
|
|
443
|
+
? [{
|
|
444
|
+
title: props.pinnedAgent ? nls.localize('theia/ai/chat-ui/unpinAgent', 'Unpin Agent') : nls.localize('theia/ai/chat-ui/pinAgent', 'Pin Agent'),
|
|
445
|
+
handler: props.pinnedAgent ? props.onUnpin : handlePin,
|
|
446
|
+
className: 'at-icon',
|
|
447
|
+
text: {
|
|
448
|
+
align: 'right',
|
|
449
|
+
content: props.pinnedAgent && props.pinnedAgent.name
|
|
450
|
+
},
|
|
451
|
+
}]
|
|
452
|
+
: []),
|
|
453
|
+
] as Option[];
|
|
327
454
|
|
|
328
455
|
const rightOptions = inProgress
|
|
329
456
|
? [{
|
|
330
|
-
title: 'Cancel (Esc)',
|
|
457
|
+
title: nls.localize('theia/ai/chat-ui/cancel', 'Cancel (Esc)'),
|
|
331
458
|
handler: () => {
|
|
332
459
|
const latestRequest = getLatestRequest(props.chatModel);
|
|
333
460
|
if (latestRequest) {
|
|
@@ -338,7 +465,7 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
338
465
|
className: 'codicon-stop-circle'
|
|
339
466
|
}]
|
|
340
467
|
: [{
|
|
341
|
-
title: 'Send (Enter)',
|
|
468
|
+
title: nls.localize('theia/ai/chat-ui/send', 'Send (Enter)'),
|
|
342
469
|
handler: () => {
|
|
343
470
|
if (props.isEnabled) {
|
|
344
471
|
submit(editorRef.current?.document.textEditorModel.getValue() || '');
|
|
@@ -348,14 +475,19 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
348
475
|
disabled: isInputEmpty || !props.isEnabled
|
|
349
476
|
}];
|
|
350
477
|
|
|
351
|
-
|
|
478
|
+
const contextUI = buildContextUI(props.context, props.labelProvider, props.onDeleteContextElement);
|
|
479
|
+
|
|
480
|
+
return <div className='theia-ChatInput' onDragOver={props.onDragOver} onDrop={props.onDrop} >
|
|
352
481
|
{changeSetUI?.elements &&
|
|
353
482
|
<ChangeSetBox changeSet={changeSetUI} />
|
|
354
483
|
}
|
|
355
484
|
<div className='theia-ChatInput-Editor-Box'>
|
|
356
485
|
<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>
|
|
486
|
+
<div ref={placeholderRef} className='theia-ChatInput-Editor-Placeholder'>{nls.localizeByDefault('Ask a question')}</div>
|
|
358
487
|
</div>
|
|
488
|
+
{props.context && props.context.length > 0 &&
|
|
489
|
+
<ChatContext context={contextUI.context} />
|
|
490
|
+
}
|
|
359
491
|
<ChatInputOptions leftOptions={leftOptions} rightOptions={rightOptions} />
|
|
360
492
|
</div>
|
|
361
493
|
</div>;
|
|
@@ -369,7 +501,7 @@ const noPropagation = (handler: () => void) => (e: React.MouseEvent) => {
|
|
|
369
501
|
const buildChangeSetUI = (changeSet: ChangeSet, labelProvider: LabelProvider, onDeleteChangeSet: () => void, onDeleteChangeSetElement: (index: number) => void): ChangeSetUI => ({
|
|
370
502
|
title: changeSet.title,
|
|
371
503
|
disabled: !hasPendingElementsToAccept(changeSet),
|
|
372
|
-
|
|
504
|
+
applyAllPendingElements: () => applyAllPendingElements(changeSet),
|
|
373
505
|
delete: () => onDeleteChangeSet(),
|
|
374
506
|
elements: changeSet.getElements().map(element => ({
|
|
375
507
|
open: element?.open?.bind(element),
|
|
@@ -378,8 +510,8 @@ const buildChangeSetUI = (changeSet: ChangeSet, labelProvider: LabelProvider, on
|
|
|
378
510
|
name: element.name ?? labelProvider.getName(element.uri),
|
|
379
511
|
additionalInfo: element.additionalInfo ?? labelProvider.getDetails(element.uri),
|
|
380
512
|
openChange: element?.openChange?.bind(element),
|
|
381
|
-
|
|
382
|
-
|
|
513
|
+
apply: element.state !== 'applied' ? element?.apply?.bind(element) : undefined,
|
|
514
|
+
revert: element.state === 'applied' || element.state === 'stale' ? element?.revert?.bind(element) : undefined,
|
|
383
515
|
delete: () => onDeleteChangeSetElement(changeSet.getElements().indexOf(element))
|
|
384
516
|
}))
|
|
385
517
|
});
|
|
@@ -391,15 +523,15 @@ interface ChangeSetUIElement {
|
|
|
391
523
|
additionalInfo: string;
|
|
392
524
|
open?: () => void;
|
|
393
525
|
openChange?: () => void;
|
|
394
|
-
|
|
395
|
-
|
|
526
|
+
apply?: () => void;
|
|
527
|
+
revert?: () => void;
|
|
396
528
|
delete: () => void;
|
|
397
529
|
}
|
|
398
530
|
|
|
399
531
|
interface ChangeSetUI {
|
|
400
532
|
title: string;
|
|
401
533
|
disabled: boolean;
|
|
402
|
-
|
|
534
|
+
applyAllPendingElements: () => void;
|
|
403
535
|
delete: () => void;
|
|
404
536
|
elements: ChangeSetUIElement[];
|
|
405
537
|
}
|
|
@@ -412,30 +544,47 @@ const ChangeSetBox: React.FunctionComponent<{ changeSet: ChangeSetUI }> = ({ cha
|
|
|
412
544
|
<button
|
|
413
545
|
className='theia-button'
|
|
414
546
|
disabled={changeSet.disabled}
|
|
415
|
-
title='
|
|
416
|
-
onClick={() => changeSet.
|
|
547
|
+
title={nls.localize('theia/ai/chat-ui/applyAllTitle', 'Apply all pending suggestions')}
|
|
548
|
+
onClick={() => changeSet.applyAllPendingElements()}
|
|
417
549
|
>
|
|
418
|
-
|
|
550
|
+
{nls.localize('theia/ai/chat-ui/acceptAll', 'Apply All')}
|
|
419
551
|
</button>
|
|
420
|
-
<span className='codicon codicon-close action' title='Delete Change Set' onClick={() => changeSet.delete()} />
|
|
552
|
+
<span className='codicon codicon-close action' title={nls.localize('theia/ai/chat-ui/deleteChangeSet', 'Delete Change Set')} onClick={() => changeSet.delete()} />
|
|
421
553
|
</div>
|
|
422
554
|
</div>
|
|
423
555
|
<div className='theia-ChatInput-ChangeSet-List'>
|
|
424
556
|
<ul>
|
|
425
557
|
{changeSet.elements.map((element, index) => (
|
|
426
|
-
<li key={index} title='Open Diff' onClick={() => element.openChange?.()}>
|
|
558
|
+
<li key={index} title={nls.localize('theia/ai/chat-ui/openDiff', 'Open Diff')} onClick={() => element.openChange?.()}>
|
|
427
559
|
<div className={`theia-ChatInput-ChangeSet-Icon ${element.iconClass}`} />
|
|
428
|
-
<span className=
|
|
429
|
-
{element.
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
560
|
+
<span className='theia-ChatInput-ChangeSet-labelParts'>
|
|
561
|
+
<span className={`theia-ChatInput-ChangeSet-title ${element.nameClass}`}>
|
|
562
|
+
{element.name}
|
|
563
|
+
</span>
|
|
564
|
+
<span className='theia-ChatInput-ChangeSet-additionalInfo'>
|
|
565
|
+
{element.additionalInfo}
|
|
566
|
+
</span>
|
|
433
567
|
</span>
|
|
434
568
|
<div className='theia-ChatInput-ChangeSet-Actions'>
|
|
435
|
-
{element.open && (
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
569
|
+
{element.open && (
|
|
570
|
+
<span
|
|
571
|
+
className='codicon codicon-file action'
|
|
572
|
+
title={nls.localize('theia/ai/chat-ui/openOriginalFile', 'Open Original File')}
|
|
573
|
+
onClick={noPropagation(() => element.open!())}
|
|
574
|
+
/>)}
|
|
575
|
+
{element.revert && (
|
|
576
|
+
<span
|
|
577
|
+
className='codicon codicon-discard action'
|
|
578
|
+
title={nls.localizeByDefault('Revert')}
|
|
579
|
+
onClick={noPropagation(() => element.revert!())}
|
|
580
|
+
/>)}
|
|
581
|
+
{element.apply && (
|
|
582
|
+
<span
|
|
583
|
+
className='codicon codicon-check action'
|
|
584
|
+
title={nls.localizeByDefault('Apply')}
|
|
585
|
+
onClick={noPropagation(() => element.apply!())}
|
|
586
|
+
/>)}
|
|
587
|
+
<span className='codicon codicon-close action' title={nls.localizeByDefault('Delete')} onClick={noPropagation(() => element.delete())} />
|
|
439
588
|
</div>
|
|
440
589
|
</li>
|
|
441
590
|
))}
|
|
@@ -454,6 +603,10 @@ interface Option {
|
|
|
454
603
|
handler: () => void;
|
|
455
604
|
className: string;
|
|
456
605
|
disabled?: boolean;
|
|
606
|
+
text?: {
|
|
607
|
+
align?: 'left' | 'right';
|
|
608
|
+
content: string;
|
|
609
|
+
};
|
|
457
610
|
}
|
|
458
611
|
|
|
459
612
|
const ChatInputOptions: React.FunctionComponent<ChatInputOptionsProps> = ({ leftOptions, rightOptions }) => (
|
|
@@ -462,38 +615,91 @@ const ChatInputOptions: React.FunctionComponent<ChatInputOptionsProps> = ({ left
|
|
|
462
615
|
{leftOptions.map((option, index) => (
|
|
463
616
|
<span
|
|
464
617
|
key={index}
|
|
465
|
-
className={`
|
|
618
|
+
className={`option ${option.disabled ? 'disabled' : ''} ${option.text?.align === 'right' ? 'reverse' : ''}`}
|
|
466
619
|
title={option.title}
|
|
467
620
|
onClick={option.handler}
|
|
468
|
-
|
|
621
|
+
>
|
|
622
|
+
<span>{option.text?.content}</span>
|
|
623
|
+
<span className={`codicon ${option.className}`} />
|
|
624
|
+
</span>
|
|
469
625
|
))}
|
|
470
626
|
</div>
|
|
471
627
|
<div className="theia-ChatInputOptions-right">
|
|
472
628
|
{rightOptions.map((option, index) => (
|
|
473
629
|
<span
|
|
474
630
|
key={index}
|
|
475
|
-
className={`
|
|
631
|
+
className={`option ${option.disabled ? 'disabled' : ''} ${option.text?.align === 'right' ? 'reverse' : ''}`}
|
|
476
632
|
title={option.title}
|
|
477
633
|
onClick={option.handler}
|
|
478
|
-
|
|
634
|
+
>
|
|
635
|
+
<span>{option.text?.content}</span>
|
|
636
|
+
<span className={`codicon ${option.className}`} />
|
|
637
|
+
</span>
|
|
479
638
|
))}
|
|
480
639
|
</div>
|
|
481
640
|
</div>
|
|
482
641
|
);
|
|
483
642
|
|
|
484
|
-
function
|
|
485
|
-
|
|
643
|
+
function applyAllPendingElements(changeSet: ChangeSet): void {
|
|
644
|
+
getPendingElements(changeSet).forEach(e => e.apply!());
|
|
486
645
|
}
|
|
487
646
|
|
|
488
647
|
function hasPendingElementsToAccept(changeSet: ChangeSet): boolean | undefined {
|
|
489
|
-
return
|
|
648
|
+
return getPendingElements(changeSet).length > 0;
|
|
490
649
|
}
|
|
491
650
|
|
|
492
|
-
function
|
|
493
|
-
return changeSet.getElements().filter(e => e.
|
|
651
|
+
function getPendingElements(changeSet: ChangeSet): ChangeSetElement[] {
|
|
652
|
+
return changeSet.getElements().filter(e => e.apply && (e.state === undefined || e.state === 'pending'));
|
|
494
653
|
}
|
|
495
654
|
|
|
496
655
|
function getLatestRequest(chatModel: ChatModel): ChatRequestModel | undefined {
|
|
497
656
|
const requests = chatModel.getRequests();
|
|
498
657
|
return requests.length > 0 ? requests[requests.length - 1] : undefined;
|
|
499
658
|
}
|
|
659
|
+
|
|
660
|
+
function buildContextUI(context: AIVariableResolutionRequest[] | undefined, labelProvider: LabelProvider, onDeleteContextElement: (index: number) => void): ChatContextUI {
|
|
661
|
+
if (!context) {
|
|
662
|
+
return { context: [] };
|
|
663
|
+
}
|
|
664
|
+
return {
|
|
665
|
+
context: context.map((element, index) => ({
|
|
666
|
+
name: labelProvider.getName(element),
|
|
667
|
+
iconClass: labelProvider.getIcon(element),
|
|
668
|
+
nameClass: element.variable.name,
|
|
669
|
+
additionalInfo: labelProvider.getDetails(element),
|
|
670
|
+
details: labelProvider.getLongName(element),
|
|
671
|
+
delete: () => onDeleteContextElement(index),
|
|
672
|
+
}))
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
interface ChatContextUI {
|
|
677
|
+
context: {
|
|
678
|
+
name: string;
|
|
679
|
+
iconClass: string;
|
|
680
|
+
nameClass: string;
|
|
681
|
+
additionalInfo?: string;
|
|
682
|
+
details?: string;
|
|
683
|
+
delete: () => void;
|
|
684
|
+
open?: () => void;
|
|
685
|
+
}[];
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const ChatContext: React.FunctionComponent<ChatContextUI> = ({ context }) => (
|
|
689
|
+
<div className="theia-ChatInput-ChatContext">
|
|
690
|
+
<ul>
|
|
691
|
+
{context.map((element, index) => (
|
|
692
|
+
<li key={index} className="theia-ChatInput-ChatContext-Element" title={element.details} onClick={() => element.open?.()}>
|
|
693
|
+
<div className={`theia-ChatInput-ChatContext-Icon ${element.iconClass}`} />
|
|
694
|
+
<span className={`theia-ChatInput-ChatContext-title ${element.nameClass}`}>
|
|
695
|
+
{element.name}
|
|
696
|
+
</span>
|
|
697
|
+
<span className='theia-ChatInput-ChatContext-additionalInfo'>
|
|
698
|
+
{element.additionalInfo}
|
|
699
|
+
</span>
|
|
700
|
+
<span className="codicon codicon-close action" title={nls.localizeByDefault('Delete')} onClick={() => element.delete()} />
|
|
701
|
+
</li>
|
|
702
|
+
))}
|
|
703
|
+
</ul>
|
|
704
|
+
</div>
|
|
705
|
+
);
|
|
@@ -23,6 +23,7 @@ import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
|
|
23
23
|
import { inject, injectable, named } from '@theia/core/shared/inversify';
|
|
24
24
|
import * as React from '@theia/core/shared/react';
|
|
25
25
|
import { ReactNode } from '@theia/core/shared/react';
|
|
26
|
+
import { nls } from '@theia/core/lib/common/nls';
|
|
26
27
|
import { Position } from '@theia/core/shared/vscode-languageserver-protocol';
|
|
27
28
|
import { EditorManager, EditorWidget } from '@theia/editor/lib/browser';
|
|
28
29
|
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
|
@@ -113,7 +114,7 @@ export class CodePartRenderer
|
|
|
113
114
|
private getTitle(uri: URI | undefined, language: string | undefined): string {
|
|
114
115
|
// If there is a URI, use the file name as the title. Otherwise, use the language as the title.
|
|
115
116
|
// If there is no language, use a generic fallback title.
|
|
116
|
-
return uri?.path?.toString().split('/').pop() ?? language ?? 'Generated Code';
|
|
117
|
+
return uri?.path?.toString().split('/').pop() ?? language ?? nls.localize('theia/ai/chat-ui/code-part-renderer/generatedCode', 'Generated Code');
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
/**
|
|
@@ -157,7 +158,7 @@ const CopyToClipboardButton = (props: { code: string, clipboardService: Clipboar
|
|
|
157
158
|
const copyCodeToClipboard = React.useCallback(() => {
|
|
158
159
|
clipboardService.writeText(code);
|
|
159
160
|
}, [code, clipboardService]);
|
|
160
|
-
return <div className='button codicon codicon-copy' title='Copy' role='button' onClick={copyCodeToClipboard}></div>;
|
|
161
|
+
return <div className='button codicon codicon-copy' title={nls.localizeByDefault('Copy')} role='button' onClick={copyCodeToClipboard}></div>;
|
|
161
162
|
};
|
|
162
163
|
|
|
163
164
|
@injectable()
|
|
@@ -189,7 +190,7 @@ const InsertCodeAtCursorButton = (props: { code: string, editorManager: EditorMa
|
|
|
189
190
|
}]);
|
|
190
191
|
}
|
|
191
192
|
}, [code, editorManager]);
|
|
192
|
-
return <div className='button codicon codicon-insert' title='Insert at Cursor' role='button' onClick={insertCode}></div>;
|
|
193
|
+
return <div className='button codicon codicon-insert' title={nls.localizeByDefault('Insert at Cursor')} role='button' onClick={insertCode}></div>;
|
|
193
194
|
};
|
|
194
195
|
|
|
195
196
|
/**
|
|
@@ -18,6 +18,7 @@ import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
|
|
|
18
18
|
import { injectable } from '@theia/core/shared/inversify';
|
|
19
19
|
import { ChatResponseContent } from '@theia/ai-chat/lib/common';
|
|
20
20
|
import { ReactNode } from '@theia/core/shared/react';
|
|
21
|
+
import { nls } from '@theia/core/lib/common/nls';
|
|
21
22
|
import * as React from '@theia/core/shared/react';
|
|
22
23
|
|
|
23
24
|
@injectable()
|
|
@@ -30,6 +31,8 @@ export class TextPartRenderer implements ChatResponsePartRenderer<ChatResponseCo
|
|
|
30
31
|
if (response && ChatResponseContent.hasAsString(response)) {
|
|
31
32
|
return <span>{response.asString()}</span>;
|
|
32
33
|
}
|
|
33
|
-
return <span>
|
|
34
|
+
return <span>
|
|
35
|
+
{nls.localize('theia/ai/chat-ui/text-part-renderer/cantDisplay',
|
|
36
|
+
"Can't display response, please check your ChatResponsePartRenderers!")} {JSON.stringify(response)}</span>;
|
|
34
37
|
}
|
|
35
38
|
}
|
|
@@ -18,6 +18,7 @@ import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
|
|
|
18
18
|
import { injectable } from '@theia/core/shared/inversify';
|
|
19
19
|
import { ChatResponseContent, ToolCallChatResponseContent } from '@theia/ai-chat/lib/common';
|
|
20
20
|
import { ReactNode } from '@theia/core/shared/react';
|
|
21
|
+
import { nls } from '@theia/core/lib/common/nls';
|
|
21
22
|
import * as React from '@theia/core/shared/react';
|
|
22
23
|
|
|
23
24
|
@injectable()
|
|
@@ -35,14 +36,14 @@ export class ToolCallPartRenderer implements ChatResponsePartRenderer<ToolCallCh
|
|
|
35
36
|
<h4 className='theia-toolCall'>
|
|
36
37
|
{response.finished ? (
|
|
37
38
|
<details>
|
|
38
|
-
<summary>Ran {response.name}
|
|
39
|
+
<summary>{nls.localize('theia/ai/chat-ui/toolcall-part-renderer/finished', 'Ran')} {response.name}
|
|
39
40
|
({this.renderCollapsibleArguments(response.arguments)})
|
|
40
41
|
</summary>
|
|
41
42
|
<pre>{this.tryPrettyPrintJson(response)}</pre>
|
|
42
43
|
</details>
|
|
43
44
|
) : (
|
|
44
45
|
<span>
|
|
45
|
-
<Spinner /> Running {response.name}({this.renderCollapsibleArguments(response.arguments)})
|
|
46
|
+
<Spinner /> {nls.localizeByDefault('Running')} {response.name}({this.renderCollapsibleArguments(response.arguments)})
|
|
46
47
|
</span>
|
|
47
48
|
)}
|
|
48
49
|
</h4>
|
|
@@ -82,7 +83,12 @@ export class ToolCallPartRenderer implements ChatResponsePartRenderer<ToolCallCh
|
|
|
82
83
|
}
|
|
83
84
|
} catch (e) {
|
|
84
85
|
if (typeof responseContent !== 'string') {
|
|
85
|
-
responseContent =
|
|
86
|
+
responseContent = nls.localize(
|
|
87
|
+
'theia/ai/chat-ui/toolcall-part-renderer/prettyPrintError',
|
|
88
|
+
"The content could not be converted to string: '{0}'. This is the original content: '{1}'.",
|
|
89
|
+
e.message,
|
|
90
|
+
responseContent
|
|
91
|
+
);
|
|
86
92
|
}
|
|
87
93
|
// fall through
|
|
88
94
|
}
|