@theia/ai-chat-ui 1.63.0-next.0 → 1.63.0-next.52
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 +29 -1
- package/lib/browser/ai-chat-ui-contribution.d.ts.map +1 -1
- package/lib/browser/ai-chat-ui-contribution.js +158 -2
- 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 +8 -0
- package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -1
- package/lib/browser/chat-input-widget.d.ts +9 -6
- package/lib/browser/chat-input-widget.d.ts.map +1 -1
- package/lib/browser/chat-input-widget.js +181 -111
- package/lib/browser/chat-input-widget.js.map +1 -1
- package/lib/browser/chat-node-toolbar-action-contribution.d.ts +1 -0
- package/lib/browser/chat-node-toolbar-action-contribution.d.ts.map +1 -1
- package/lib/browser/chat-node-toolbar-action-contribution.js +13 -0
- package/lib/browser/chat-node-toolbar-action-contribution.js.map +1 -1
- package/lib/browser/chat-response-renderer/delegation-response-renderer.d.ts +14 -0
- package/lib/browser/chat-response-renderer/delegation-response-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/delegation-response-renderer.js +144 -0
- package/lib/browser/chat-response-renderer/delegation-response-renderer.js.map +1 -0
- package/lib/browser/chat-response-renderer/index.d.ts +2 -0
- package/lib/browser/chat-response-renderer/index.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/index.js +2 -0
- package/lib/browser/chat-response-renderer/index.js.map +1 -1
- package/lib/browser/chat-response-renderer/tool-confirmation.d.ts +17 -0
- package/lib/browser/chat-response-renderer/tool-confirmation.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/tool-confirmation.js +120 -0
- package/lib/browser/chat-response-renderer/tool-confirmation.js.map +1 -0
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts +5 -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 +83 -19
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -1
- package/lib/browser/chat-tree-view/chat-view-tree-input-widget.d.ts +6 -1
- package/lib/browser/chat-tree-view/chat-view-tree-input-widget.d.ts.map +1 -1
- package/lib/browser/chat-tree-view/chat-view-tree-input-widget.js +9 -0
- package/lib/browser/chat-tree-view/chat-view-tree-input-widget.js.map +1 -1
- package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts +8 -0
- 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 +72 -3
- package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -1
- package/lib/browser/chat-tree-view/sub-chat-widget.d.ts +22 -0
- package/lib/browser/chat-tree-view/sub-chat-widget.d.ts.map +1 -0
- package/lib/browser/chat-tree-view/sub-chat-widget.js +92 -0
- package/lib/browser/chat-tree-view/sub-chat-widget.js.map +1 -0
- package/lib/browser/chat-view-commands.d.ts +1 -0
- package/lib/browser/chat-view-commands.d.ts.map +1 -1
- package/lib/browser/chat-view-commands.js +5 -0
- package/lib/browser/chat-view-commands.js.map +1 -1
- package/lib/browser/chat-view-contribution.js +2 -1
- package/lib/browser/chat-view-contribution.js.map +1 -1
- package/lib/browser/chat-view-widget.d.ts +6 -3
- package/lib/browser/chat-view-widget.d.ts.map +1 -1
- package/lib/browser/chat-view-widget.js +20 -10
- package/lib/browser/chat-view-widget.js.map +1 -1
- package/package.json +10 -10
- package/src/browser/ai-chat-ui-contribution.ts +166 -5
- package/src/browser/ai-chat-ui-frontend-module.ts +11 -0
- package/src/browser/chat-input-widget.tsx +280 -170
- package/src/browser/chat-node-toolbar-action-contribution.ts +14 -0
- package/src/browser/chat-response-renderer/delegation-response-renderer.tsx +177 -0
- package/src/browser/chat-response-renderer/index.ts +2 -0
- package/src/browser/chat-response-renderer/tool-confirmation.tsx +173 -0
- package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +115 -19
- package/src/browser/chat-tree-view/chat-view-tree-input-widget.tsx +16 -1
- package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +89 -5
- package/src/browser/chat-tree-view/sub-chat-widget.tsx +101 -0
- package/src/browser/chat-view-commands.ts +6 -0
- package/src/browser/chat-view-contribution.ts +1 -1
- package/src/browser/chat-view-widget.tsx +25 -12
- package/src/browser/style/index.css +350 -2
|
@@ -64,7 +64,8 @@ import { AIChatTreeInputFactory, type AIChatTreeInputWidget } from './chat-view-
|
|
|
64
64
|
// TODO Instead of directly operating on the ChatRequestModel we could use an intermediate view model
|
|
65
65
|
export interface RequestNode extends TreeNode {
|
|
66
66
|
request: ChatRequestModel,
|
|
67
|
-
branch: ChatHierarchyBranch
|
|
67
|
+
branch: ChatHierarchyBranch,
|
|
68
|
+
sessionId: string
|
|
68
69
|
}
|
|
69
70
|
export const isRequestNode = (node: TreeNode): node is RequestNode => 'request' in node;
|
|
70
71
|
|
|
@@ -75,7 +76,8 @@ export const isEditableRequestNode = (node: TreeNode): node is EditableRequestNo
|
|
|
75
76
|
|
|
76
77
|
// TODO Instead of directly operating on the ChatResponseModel we could use an intermediate view model
|
|
77
78
|
export interface ResponseNode extends TreeNode {
|
|
78
|
-
response: ChatResponseModel
|
|
79
|
+
response: ChatResponseModel,
|
|
80
|
+
sessionId: string
|
|
79
81
|
}
|
|
80
82
|
export const isResponseNode = (node: TreeNode): node is ResponseNode => 'response' in node;
|
|
81
83
|
|
|
@@ -136,6 +138,12 @@ export class ChatViewTreeWidget extends TreeWidget {
|
|
|
136
138
|
|
|
137
139
|
protected isEnabled = false;
|
|
138
140
|
|
|
141
|
+
protected chatModelId: string;
|
|
142
|
+
|
|
143
|
+
onScrollLockChange?: (temporaryLocked: boolean) => void;
|
|
144
|
+
|
|
145
|
+
protected lastScrollTop = 0;
|
|
146
|
+
|
|
139
147
|
set shouldScrollToEnd(shouldScrollToEnd: boolean) {
|
|
140
148
|
this._shouldScrollToEnd = shouldScrollToEnd;
|
|
141
149
|
this.shouldScrollToRow = this._shouldScrollToEnd;
|
|
@@ -178,8 +186,14 @@ export class ChatViewTreeWidget extends TreeWidget {
|
|
|
178
186
|
widget.setEnabled(change);
|
|
179
187
|
});
|
|
180
188
|
this.update();
|
|
189
|
+
}),
|
|
190
|
+
this.onScroll(scrollEvent => {
|
|
191
|
+
this.handleScrollEvent(scrollEvent);
|
|
181
192
|
})
|
|
182
193
|
]);
|
|
194
|
+
|
|
195
|
+
// Initialize lastScrollTop with current scroll position
|
|
196
|
+
this.lastScrollTop = this.getCurrentScrollTop(undefined);
|
|
183
197
|
}
|
|
184
198
|
|
|
185
199
|
public setEnabled(enabled: boolean): void {
|
|
@@ -187,6 +201,72 @@ export class ChatViewTreeWidget extends TreeWidget {
|
|
|
187
201
|
this.update();
|
|
188
202
|
}
|
|
189
203
|
|
|
204
|
+
protected handleScrollEvent(scrollEvent: unknown): void {
|
|
205
|
+
// Get current scroll position
|
|
206
|
+
const currentScrollTop = this.getCurrentScrollTop(scrollEvent);
|
|
207
|
+
const isAtBottom = this.isScrolledToBottom();
|
|
208
|
+
|
|
209
|
+
// Determine scroll direction
|
|
210
|
+
const isScrollingUp = currentScrollTop < this.lastScrollTop;
|
|
211
|
+
const isScrollingDown = currentScrollTop > this.lastScrollTop;
|
|
212
|
+
|
|
213
|
+
// Handle scroll lock logic based on direction and position
|
|
214
|
+
// The key insight is that we only enable temporary lock when scrolling UP,
|
|
215
|
+
// and only disable it when scrolling DOWN to the bottom. This prevents
|
|
216
|
+
// the jitter when users try to scroll up by just a few pixels from the bottom.
|
|
217
|
+
if (this.shouldScrollToEnd) {
|
|
218
|
+
// Auto-scroll is enabled, check if we need to enable temporary lock
|
|
219
|
+
if (isScrollingUp) {
|
|
220
|
+
// User is scrolling up and not at bottom - enable temporary lock
|
|
221
|
+
this.setTemporaryScrollLock(true);
|
|
222
|
+
}
|
|
223
|
+
// Note: We don't disable temporary lock when scrolling down while shouldScrollToEnd is true
|
|
224
|
+
// because that would cause jitter. The lock will be disabled when user reaches bottom.
|
|
225
|
+
} else {
|
|
226
|
+
// Temporary lock is active, check if we should disable it
|
|
227
|
+
if (isScrollingDown && isAtBottom) {
|
|
228
|
+
// User scrolled back to bottom - disable temporary lock
|
|
229
|
+
this.setTemporaryScrollLock(false);
|
|
230
|
+
}
|
|
231
|
+
// Note: We don't change the lock state when scrolling up while locked,
|
|
232
|
+
// as the user is intentionally scrolling away from auto-scroll behavior.
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Update last scroll position for next comparison
|
|
236
|
+
this.lastScrollTop = currentScrollTop;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
protected setTemporaryScrollLock(enabled: boolean): void {
|
|
240
|
+
// Immediately apply scroll lock changes without delay
|
|
241
|
+
this.onScrollLockChange?.(enabled);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
protected getCurrentScrollTop(scrollEvent: unknown): number {
|
|
245
|
+
// For virtualized trees, try to get scroll position from the virtualized view
|
|
246
|
+
if (this.props.virtualized !== false && this.view) {
|
|
247
|
+
const scrollState = this.getVirtualizedScrollState();
|
|
248
|
+
if (scrollState !== undefined) {
|
|
249
|
+
return scrollState.scrollTop;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Try to extract scroll position from the scroll event
|
|
254
|
+
if (scrollEvent && typeof scrollEvent === 'object' && 'scrollTop' in scrollEvent) {
|
|
255
|
+
const scrollEventWithScrollTop = scrollEvent as { scrollTop: unknown };
|
|
256
|
+
const scrollTop = scrollEventWithScrollTop.scrollTop;
|
|
257
|
+
if (typeof scrollTop === 'number' && !isNaN(scrollTop)) {
|
|
258
|
+
return scrollTop;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Last resort: use DOM scroll position
|
|
263
|
+
if (this.node && typeof this.node.scrollTop === 'number') {
|
|
264
|
+
return this.node.scrollTop;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
269
|
+
|
|
190
270
|
protected override renderTree(model: TreeModel): React.ReactNode {
|
|
191
271
|
if (!this.isEnabled) {
|
|
192
272
|
return this.renderDisabledMessage();
|
|
@@ -214,7 +294,8 @@ export class ChatViewTreeWidget extends TreeWidget {
|
|
|
214
294
|
get request(): ChatRequestModel {
|
|
215
295
|
return branch.get();
|
|
216
296
|
},
|
|
217
|
-
branch
|
|
297
|
+
branch,
|
|
298
|
+
sessionId: this.chatModelId
|
|
218
299
|
};
|
|
219
300
|
}
|
|
220
301
|
|
|
@@ -222,7 +303,8 @@ export class ChatViewTreeWidget extends TreeWidget {
|
|
|
222
303
|
return {
|
|
223
304
|
id: response.id,
|
|
224
305
|
parent: this.model.root as CompositeTreeNode,
|
|
225
|
-
response
|
|
306
|
+
response,
|
|
307
|
+
sessionId: this.chatModelId
|
|
226
308
|
};
|
|
227
309
|
}
|
|
228
310
|
|
|
@@ -292,6 +374,7 @@ export class ChatViewTreeWidget extends TreeWidget {
|
|
|
292
374
|
protected async recreateModelTree(chatModel: ChatModel): Promise<void> {
|
|
293
375
|
if (CompositeTreeNode.is(this.model.root)) {
|
|
294
376
|
const nodes: TreeNode[] = [];
|
|
377
|
+
this.chatModelId = chatModel.id;
|
|
295
378
|
chatModel.getBranches().forEach(branch => {
|
|
296
379
|
const request = branch.get();
|
|
297
380
|
nodes.push(this.mapRequestToNode(branch));
|
|
@@ -426,7 +509,8 @@ export class ChatViewTreeWidget extends TreeWidget {
|
|
|
426
509
|
initialValue: editableNode.request.message.request.text,
|
|
427
510
|
onQuery: async query => {
|
|
428
511
|
editableNode.request.submitEdit({ text: query });
|
|
429
|
-
}
|
|
512
|
+
},
|
|
513
|
+
branch: editableNode.branch
|
|
430
514
|
});
|
|
431
515
|
|
|
432
516
|
this.chatInputs.set(editableNode.id, widget);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 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 { inject, injectable, named } from '@theia/core/shared/inversify';
|
|
18
|
+
import { ProgressMessage } from '../chat-progress-message';
|
|
19
|
+
import { ChatViewTreeWidget, ResponseNode } from './chat-view-tree-widget';
|
|
20
|
+
import * as React from '@theia/core/shared/react';
|
|
21
|
+
import { ContributionProvider } from '@theia/core';
|
|
22
|
+
import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
|
|
23
|
+
import { ChatNodeToolbarActionContribution } from '../chat-node-toolbar-action-contribution';
|
|
24
|
+
import { ChatResponseContent } from '@theia/ai-chat';
|
|
25
|
+
import { ContextMenuRenderer, TreeNode } from '@theia/core/lib/browser';
|
|
26
|
+
import { nls } from '@theia/core/lib/common/nls';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Subset of the ChatViewTreeWidget used to render ResponseNodes for delegated prompts.
|
|
30
|
+
*/
|
|
31
|
+
@injectable()
|
|
32
|
+
export class SubChatWidget {
|
|
33
|
+
|
|
34
|
+
@inject(ContributionProvider) @named(ChatResponsePartRenderer)
|
|
35
|
+
protected readonly chatResponsePartRenderers: ContributionProvider<ChatResponsePartRenderer<ChatResponseContent>>;
|
|
36
|
+
|
|
37
|
+
@inject(ContributionProvider) @named(ChatNodeToolbarActionContribution)
|
|
38
|
+
protected readonly chatNodeToolbarActionContributions: ContributionProvider<ChatNodeToolbarActionContribution>;
|
|
39
|
+
|
|
40
|
+
@inject(ContextMenuRenderer) contextMenuRenderer: ContextMenuRenderer;
|
|
41
|
+
|
|
42
|
+
renderChatResponse(node: ResponseNode): React.ReactNode {
|
|
43
|
+
return (
|
|
44
|
+
<div className={'theia-ResponseNode'}>
|
|
45
|
+
{!node.response.isComplete
|
|
46
|
+
&& node.response.response.content.length === 0
|
|
47
|
+
&& node.response.progressMessages
|
|
48
|
+
.filter(c => c.show === 'untilFirstContent')
|
|
49
|
+
.map((c, i) =>
|
|
50
|
+
<ProgressMessage {...c} key={`${node.id}-progress-untilFirstContent-${i}`} />
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
{node.response.response.content.map((c, i) =>
|
|
54
|
+
<div className='theia-ResponseNode-Content' key={`${node.id}-content-${i}`}>{this.getChatResponsePartRenderer(c, node)}</div>
|
|
55
|
+
)}
|
|
56
|
+
{!node.response.isComplete
|
|
57
|
+
&& node.response.progressMessages
|
|
58
|
+
.filter(c => c.show === 'whileIncomplete')
|
|
59
|
+
.map((c, i) =>
|
|
60
|
+
<ProgressMessage {...c} key={`${node.id}-progress-whileIncomplete-${i}`} />
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
{node.response.progressMessages
|
|
64
|
+
.filter(c => c.show === 'forever')
|
|
65
|
+
.map((c, i) =>
|
|
66
|
+
<ProgressMessage {...c} key={`${node.id}-progress-afterComplete-${i}`} />
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
protected getChatResponsePartRenderer(content: ChatResponseContent, node: ResponseNode): React.ReactNode {
|
|
74
|
+
const renderer = this.chatResponsePartRenderers.getContributions().reduce<[number, ChatResponsePartRenderer<ChatResponseContent> | undefined]>(
|
|
75
|
+
(prev, current) => {
|
|
76
|
+
const prio = current.canHandle(content);
|
|
77
|
+
if (prio > prev[0]) {
|
|
78
|
+
return [prio, current];
|
|
79
|
+
} return prev;
|
|
80
|
+
},
|
|
81
|
+
[-1, undefined])[1];
|
|
82
|
+
if (!renderer) {
|
|
83
|
+
console.error('No renderer found for content', content);
|
|
84
|
+
return <div>{nls.localize('theia/ai/chat-ui/chat-view-tree-widget/noRenderer', 'Error: No renderer found')}</div>;
|
|
85
|
+
}
|
|
86
|
+
return renderer.render(content, node);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
protected handleContextMenu(node: TreeNode | undefined, event: React.MouseEvent<HTMLElement>): void {
|
|
90
|
+
this.contextMenuRenderer.render({
|
|
91
|
+
menuPath: ChatViewTreeWidget.CONTEXT_MENU,
|
|
92
|
+
anchor: { x: event.clientX, y: event.clientY },
|
|
93
|
+
args: [node],
|
|
94
|
+
context: event.currentTarget
|
|
95
|
+
});
|
|
96
|
+
event.preventDefault();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const SubChatWidgetFactory = Symbol('SubChatWidgetFactory');
|
|
101
|
+
export type SubChatWidgetFactory = () => SubChatWidget;
|
|
@@ -43,6 +43,12 @@ export namespace ChatCommands {
|
|
|
43
43
|
id: 'ai-chat.new-with-task-context',
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
export const AI_CHAT_INITIATE_SESSION_WITH_TASK_CONTEXT = Command.toLocalizedCommand({
|
|
47
|
+
id: 'ai-chat.initiate-session-with-task-context',
|
|
48
|
+
label: 'Task Context: Initiate Session',
|
|
49
|
+
category: CHAT_CATEGORY
|
|
50
|
+
}, undefined, CHAT_CATEGORY_KEY);
|
|
51
|
+
|
|
46
52
|
export const AI_CHAT_SUMMARIZE_CURRENT_SESSION = Command.toLocalizedCommand({
|
|
47
53
|
id: 'ai-chat-summary-current-session',
|
|
48
54
|
iconClass: codicon('go-to-editing-session'),
|
|
@@ -117,7 +117,7 @@ export class ChatViewMenuContribution implements MenuContribution, CommandContri
|
|
|
117
117
|
|
|
118
118
|
protected getCopyText(arg: RequestNode | ResponseNode): string {
|
|
119
119
|
if (isRequestNode(arg)) {
|
|
120
|
-
return arg.request.request.text;
|
|
120
|
+
return arg.request.request.text ?? '';
|
|
121
121
|
} else if (isResponseNode(arg)) {
|
|
122
122
|
return arg.response.response.asDisplayString();
|
|
123
123
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
//
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
|
-
import { CommandService, deepClone, Emitter, Event, MessageService } from '@theia/core';
|
|
16
|
+
import { CommandService, deepClone, Emitter, Event, MessageService, URI } from '@theia/core';
|
|
17
17
|
import { ChatRequest, ChatRequestModel, ChatService, ChatSession, isActiveSessionChangedEvent, MutableChatModel } from '@theia/ai-chat';
|
|
18
18
|
import { BaseWidget, codicon, ExtractableWidget, Message, PanelLayout, PreferenceService, StatefulWidget } from '@theia/core/lib/browser';
|
|
19
19
|
import { nls } from '@theia/core/lib/common/nls';
|
|
@@ -28,6 +28,7 @@ import { FrontendVariableService } from '@theia/ai-core/lib/browser';
|
|
|
28
28
|
export namespace ChatViewWidget {
|
|
29
29
|
export interface State {
|
|
30
30
|
locked?: boolean;
|
|
31
|
+
temporaryLocked?: boolean;
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -60,7 +61,7 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
|
|
|
60
61
|
|
|
61
62
|
protected chatSession: ChatSession;
|
|
62
63
|
|
|
63
|
-
protected _state: ChatViewWidget.State = { locked: false };
|
|
64
|
+
protected _state: ChatViewWidget.State = { locked: false, temporaryLocked: false };
|
|
64
65
|
protected readonly onStateChangedEmitter = new Emitter<ChatViewWidget.State>();
|
|
65
66
|
|
|
66
67
|
secondaryWindow: Window | undefined;
|
|
@@ -87,7 +88,8 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
|
|
|
87
88
|
this.treeWidget,
|
|
88
89
|
this.inputWidget,
|
|
89
90
|
this.onStateChanged(newState => {
|
|
90
|
-
|
|
91
|
+
const shouldScrollToEnd = !newState.locked && !newState.temporaryLocked;
|
|
92
|
+
this.treeWidget.shouldScrollToEnd = shouldScrollToEnd;
|
|
91
93
|
this.update();
|
|
92
94
|
})
|
|
93
95
|
]);
|
|
@@ -107,6 +109,7 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
|
|
|
107
109
|
this.inputWidget.onDeleteChangeSet = this.onDeleteChangeSet.bind(this);
|
|
108
110
|
this.inputWidget.onDeleteChangeSetElement = this.onDeleteChangeSetElement.bind(this);
|
|
109
111
|
this.treeWidget.trackChatModel(this.chatSession.model);
|
|
112
|
+
this.treeWidget.onScrollLockChange = this.onScrollLockChange.bind(this);
|
|
110
113
|
|
|
111
114
|
this.initListeners();
|
|
112
115
|
|
|
@@ -133,9 +136,6 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
|
|
|
133
136
|
this.treeWidget.trackChatModel(this.chatSession.model);
|
|
134
137
|
this.inputWidget.chatModel = this.chatSession.model;
|
|
135
138
|
this.inputWidget.pinnedAgent = this.chatSession.pinnedAgent;
|
|
136
|
-
if (event.focus) {
|
|
137
|
-
this.show();
|
|
138
|
-
}
|
|
139
139
|
} else {
|
|
140
140
|
console.warn(`Session with ${event.sessionId} not found.`);
|
|
141
141
|
}
|
|
@@ -161,6 +161,8 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
|
|
|
161
161
|
if (oldState.locked) {
|
|
162
162
|
copy.locked = oldState.locked;
|
|
163
163
|
}
|
|
164
|
+
// Don't restore temporary lock state as it should reset on restart
|
|
165
|
+
copy.temporaryLocked = false;
|
|
164
166
|
this.state = copy;
|
|
165
167
|
}
|
|
166
168
|
|
|
@@ -177,8 +179,8 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
|
|
|
177
179
|
return this.onStateChangedEmitter.event;
|
|
178
180
|
}
|
|
179
181
|
|
|
180
|
-
protected async onQuery(query
|
|
181
|
-
const chatRequest: ChatRequest = typeof query === 'string' ? { text: query } : { ...query };
|
|
182
|
+
protected async onQuery(query?: string | ChatRequest): Promise<void> {
|
|
183
|
+
const chatRequest: ChatRequest = !query ? { text: '' } : typeof query === 'string' ? { text: query } : { ...query };
|
|
182
184
|
if (chatRequest.text.length === 0) { return; }
|
|
183
185
|
|
|
184
186
|
const requestProgress = await this.chatService.sendRequest(this.chatSession.id, chatRequest);
|
|
@@ -210,16 +212,27 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
|
|
|
210
212
|
this.chatService.deleteChangeSet(sessionId);
|
|
211
213
|
}
|
|
212
214
|
|
|
213
|
-
protected onDeleteChangeSetElement(sessionId: string,
|
|
214
|
-
this.chatService.deleteChangeSetElement(sessionId,
|
|
215
|
+
protected onDeleteChangeSetElement(sessionId: string, uri: URI): void {
|
|
216
|
+
this.chatService.deleteChangeSetElement(sessionId, uri);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
protected onScrollLockChange(temporaryLocked: boolean): void {
|
|
220
|
+
this.setTemporaryLock(temporaryLocked);
|
|
215
221
|
}
|
|
216
222
|
|
|
217
223
|
lock(): void {
|
|
218
|
-
this.state = { ...deepClone(this.state), locked: true };
|
|
224
|
+
this.state = { ...deepClone(this.state), locked: true, temporaryLocked: false };
|
|
219
225
|
}
|
|
220
226
|
|
|
221
227
|
unlock(): void {
|
|
222
|
-
this.state = { ...deepClone(this.state), locked: false };
|
|
228
|
+
this.state = { ...deepClone(this.state), locked: false, temporaryLocked: false };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
setTemporaryLock(locked: boolean): void {
|
|
232
|
+
// Only set temporary lock if not permanently locked
|
|
233
|
+
if (!this.state.locked) {
|
|
234
|
+
this.state = { ...deepClone(this.state), temporaryLocked: locked };
|
|
235
|
+
}
|
|
223
236
|
}
|
|
224
237
|
|
|
225
238
|
get isLocked(): boolean {
|