Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/contrib/chat/browser/newChatInSessionViewPane.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import './media/chatWidget.css';
7
import './media/newChatInSession.css';
8
import * as dom from '../../../../base/browser/dom.js';
9
import { Codicon } from '../../../../base/common/codicons.js';
10
import { Disposable, DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js';
11
import { derived } from '../../../../base/common/observable.js';
12
import { Gesture, EventType as TouchEventType } from '../../../../base/browser/touch.js';
13
import { URI } from '../../../../base/common/uri.js';
14
import { localize } from '../../../../nls.js';
15
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
16
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
17
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
18
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
19
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
20
import { ILogService } from '../../../../platform/log/common/log.js';
21
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
22
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
23
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
24
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
25
import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js';
26
import { ISessionsManagementService } from '../../../services/sessions/common/sessionsManagement.js';
27
import { IViewDescriptorService } from '../../../../workbench/common/views.js';
28
import { IViewPaneOptions, ViewPane } from '../../../../workbench/browser/parts/views/viewPane.js';
29
import { NewChatInputWidget } from './newChatInput.js';
30
import { IChatRequestVariableEntry } from '../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js';
31
32
// #region --- New Chat In Session Widget ---
33
34
const STORAGE_KEY_SUB_SESSION_TIP_DISMISSED = 'sessions.subSessionTipDismissed';
35
36
/**
37
* A widget for composing a secondary chat within an existing session.
38
* Reuses {@link NewChatInputWidget} but without workspace/session type pickers,
39
* since the session already exists.
40
*/
41
class NewChatInSessionWidget extends Disposable {
42
43
private readonly _newChatInput: NewChatInputWidget;
44
private readonly _tipDisposable = this._register(new MutableDisposable());
45
46
constructor(
47
@IInstantiationService private readonly instantiationService: IInstantiationService,
48
@ILogService private readonly logService: ILogService,
49
@ISessionsManagementService private readonly sessionsManagementService: ISessionsManagementService,
50
@IStorageService private readonly storageService: IStorageService,
51
) {
52
super();
53
54
const canSendRequest = derived(reader => {
55
const session = this.sessionsManagementService.activeSession.read(reader);
56
return !!session;
57
});
58
59
const loading = derived(_reader => false);
60
61
this._newChatInput = this._register(this.instantiationService.createInstance(NewChatInputWidget, {
62
getContextFolderUri: () => this._getContextFolderUri(),
63
sendRequest: async (text: string, attachedContext?: IChatRequestVariableEntry[]) => this._send(text, attachedContext),
64
canSendRequest,
65
loading,
66
minEditorHeight: 64,
67
placeholder: localize('newChatInSessionPlaceholder', 'Ask a follow-up question or start a new topic within this session...'),
68
}));
69
}
70
71
// --- Rendering ---
72
73
render(parent: HTMLElement): void {
74
const element = dom.append(parent, dom.$('.sessions-chat-widget.new-chat-in-session'));
75
const chatWidgetContainer = dom.append(element, dom.$('.new-chat-widget-container'));
76
const chatWidgetContent = dom.append(chatWidgetContainer, dom.$('.new-chat-widget-content'));
77
78
this._renderSubSessionTip(chatWidgetContent);
79
this._newChatInput.render(chatWidgetContent, parent);
80
81
chatWidgetContainer.classList.add('revealed');
82
}
83
84
private _renderSubSessionTip(container: HTMLElement): void {
85
if (this.storageService.getBoolean(STORAGE_KEY_SUB_SESSION_TIP_DISMISSED, StorageScope.PROFILE, false)) {
86
return;
87
}
88
89
const tipContainer = dom.append(container, dom.$('.sub-session-tip-container'));
90
const tipWidget = dom.append(tipContainer, dom.$('.sub-session-tip-widget'));
91
tipWidget.setAttribute('role', 'status');
92
tipWidget.setAttribute('aria-label', localize('subSessionTip.ariaLabel', "Sub-session tip"));
93
94
// Tip icon
95
const iconEl = dom.append(tipWidget, renderIcon(Codicon.lightbulb));
96
iconEl.classList.add('sub-session-tip-icon');
97
98
// Tip text
99
const textEl = dom.append(tipWidget, dom.$('span.sub-session-tip-text'));
100
textEl.textContent = localize(
101
'subSessionTip.message',
102
"This is a sub-session, a new chat in the same workspace. Use it to ask questions, run tasks, or explore ideas with fresh context."
103
);
104
105
// Dismiss button
106
const dismissBtn = dom.append(tipWidget, dom.$('button.sub-session-tip-dismiss')) as HTMLButtonElement;
107
dismissBtn.type = 'button';
108
dismissBtn.setAttribute('aria-label', localize('subSessionTip.dismiss', "Dismiss tip"));
109
dom.append(dismissBtn, renderIcon(Codicon.close));
110
111
const dismiss = () => {
112
this.storageService.store(STORAGE_KEY_SUB_SESSION_TIP_DISMISSED, true, StorageScope.PROFILE, StorageTarget.USER);
113
tipContainer.remove();
114
this._tipDisposable.clear();
115
};
116
117
const handleDismiss = (e: Event) => {
118
dom.EventHelper.stop(e, true);
119
dismiss();
120
};
121
122
const store = new DisposableStore();
123
store.add(Gesture.addTarget(dismissBtn));
124
store.add(dom.addDisposableListener(dismissBtn, dom.EventType.CLICK, handleDismiss));
125
store.add(dom.addDisposableListener(dismissBtn, TouchEventType.Tap, handleDismiss));
126
this._tipDisposable.value = store;
127
}
128
129
/**
130
* Returns the workspace URI from the active session's workspace.
131
*/
132
private _getContextFolderUri(): URI | undefined {
133
const session = this.sessionsManagementService.activeSession.get();
134
const workspace = session?.workspace.get();
135
return workspace?.repositories[0]?.workingDirectory ?? workspace?.repositories[0]?.uri;
136
}
137
138
// --- Send ---
139
140
private async _send(query: string, attachedContext?: IChatRequestVariableEntry[]): Promise<void> {
141
const activeSession = this.sessionsManagementService.activeSession.get();
142
if (!activeSession) {
143
return;
144
}
145
const activeChat = activeSession.activeChat.get();
146
try {
147
await this.sessionsManagementService.sendRequest(activeSession, activeChat, { query, attachedContext });
148
} catch (e) {
149
this.logService.error('Failed to send secondary chat request:', e);
150
}
151
}
152
153
layout(height: number, width: number): void {
154
this._newChatInput.layout(height, width);
155
}
156
157
focusInput(): void {
158
this._newChatInput.focus();
159
}
160
}
161
162
// #endregion
163
164
// #region --- New Chat In Session View Pane ---
165
166
export const NewChatInSessionViewId = 'workbench.view.sessions.newChatInSession';
167
168
/**
169
* A view pane that hosts the new-chat-in-session widget.
170
* Shown when the user wants to compose a secondary chat within the active session.
171
*/
172
export class NewChatInSessionViewPane extends ViewPane {
173
174
private _widget: NewChatInSessionWidget | undefined;
175
176
constructor(
177
options: IViewPaneOptions,
178
@IKeybindingService keybindingService: IKeybindingService,
179
@IContextMenuService contextMenuService: IContextMenuService,
180
@IConfigurationService configurationService: IConfigurationService,
181
@IContextKeyService contextKeyService: IContextKeyService,
182
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
183
@IInstantiationService instantiationService: IInstantiationService,
184
@IOpenerService openerService: IOpenerService,
185
@IThemeService themeService: IThemeService,
186
@IHoverService hoverService: IHoverService,
187
) {
188
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
189
}
190
191
protected override renderBody(container: HTMLElement): void {
192
super.renderBody(container);
193
194
this._widget = this._register(this.instantiationService.createInstance(
195
NewChatInSessionWidget,
196
));
197
198
this._widget.render(container);
199
this._widget.focusInput();
200
}
201
202
protected override layoutBody(height: number, width: number): void {
203
super.layoutBody(height, width);
204
this._widget?.layout(height, width);
205
}
206
207
override focus(): void {
208
super.focus();
209
this._widget?.focusInput();
210
}
211
212
override setVisible(visible: boolean): void {
213
super.setVisible(visible);
214
if (visible) {
215
this._widget?.focusInput();
216
}
217
}
218
}
219
220
// #endregion
221
222