Skip to content

Commit 286a971

Browse files
bpaserobhavyaus
andauthored
chat - mention ToS in welcome for first time anonymous usage (microsoft#266202)
* chat - mention ToS in welcome for first time anonymous usage * tests * briefer warning * refactor: update chat widget to replace experimental terminology with new user terminology --------- Co-authored-by: Bhavya U <[email protected]>
1 parent f51beb9 commit 286a971

File tree

5 files changed

+99
-39
lines changed

5 files changed

+99
-39
lines changed

src/vs/workbench/contrib/chat/browser/chatWidget.ts

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,17 @@ import { ChatViewPane } from './chatViewPane.js';
8383
import { IViewsService } from '../../../services/views/common/viewsService.js';
8484
import { ICommandService } from '../../../../platform/commands/common/commands.js';
8585
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
86+
import product from '../../../../platform/product/common/product.js';
87+
import { ChatEntitlement, IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js';
8688

8789
const $ = dom.$;
8890

91+
const defaultChat = {
92+
provider: product.defaultChatAgent?.provider ?? { default: { id: '', name: '' }, enterprise: { id: '', name: '' }, apple: { id: '', name: '' }, google: { id: '', name: '' } },
93+
termsStatementUrl: product.defaultChatAgent?.termsStatementUrl ?? '',
94+
privacyStatementUrl: product.defaultChatAgent?.privacyStatementUrl ?? ''
95+
};
96+
8997
export interface IChatViewState {
9098
inputValue?: string;
9199
inputState?: IChatInputState;
@@ -421,7 +429,8 @@ export class ChatWidget extends Disposable implements IChatWidget {
421429
@IChatModeService private readonly chatModeService: IChatModeService,
422430
@IHoverService private readonly hoverService: IHoverService,
423431
@IChatTodoListService private readonly chatTodoListService: IChatTodoListService,
424-
@IChatLayoutService private readonly chatLayoutService: IChatLayoutService
432+
@IChatLayoutService private readonly chatLayoutService: IChatLayoutService,
433+
@IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService,
425434
) {
426435
super();
427436
this._lockedToCodingAgentContextKey = ChatContextKeys.lockedToCodingAgent.bindTo(this.contextKeyService);
@@ -452,6 +461,16 @@ export class ChatWidget extends Disposable implements IChatWidget {
452461
}));
453462
this.updateEmptyStateWithHistoryContext();
454463

464+
// Update welcome view content when `anonymous` entitlement changes
465+
let anonymousUsage = this.chatEntitlementService.entitlement === ChatEntitlement.Unknown;
466+
this._register(this.chatEntitlementService.onDidChangeEntitlement(() => {
467+
const newAnonymousUsage = this.chatEntitlementService.entitlement === ChatEntitlement.Unknown;
468+
if (newAnonymousUsage !== anonymousUsage) {
469+
anonymousUsage = newAnonymousUsage;
470+
this.renderWelcomeViewContentIfNeeded();
471+
}
472+
}));
473+
455474
this._register(bindContextKey(decidedChatEditingResourceContextKey, contextKeyService, (reader) => {
456475
const currentSession = this._editingSession.read(reader);
457476
if (!currentSession) {
@@ -609,8 +628,8 @@ export class ChatWidget extends Disposable implements IChatWidget {
609628
ChatContextKeys.Entitlement.canSignUp.key
610629
]))) {
611630
// reset the input in welcome view if it was rendered in experimental mode
612-
if (this.container.classList.contains('experimental-welcome-view') && !this.contextKeyService.contextMatchesRules(this.chatSetupTriggerContext)) {
613-
this.container.classList.remove('experimental-welcome-view');
631+
if (this.container.classList.contains('new-welcome-view') && !this.contextKeyService.contextMatchesRules(this.chatSetupTriggerContext)) {
632+
this.container.classList.remove('new-welcome-view');
614633
const renderFollowups = this.viewOptions.renderFollowups ?? false;
615634
const renderStyle = this.viewOptions.renderStyle;
616635
this.createInput(this.container, { renderFollowups, renderStyle });
@@ -863,8 +882,8 @@ export class ChatWidget extends Disposable implements IChatWidget {
863882

864883

865884
// reset the input in welcome view if it was rendered in experimental mode
866-
if (this.container.classList.contains('experimental-welcome-view') && this.viewModel?.getItems().length) {
867-
this.container.classList.remove('experimental-welcome-view');
885+
if (this.container.classList.contains('new-welcome-view') && this.viewModel?.getItems().length) {
886+
this.container.classList.remove('new-welcome-view');
868887
const renderFollowups = this.viewOptions.renderFollowups ?? false;
869888
const renderStyle = this.viewOptions.renderStyle;
870889
this.createInput(this.container, { renderFollowups, renderStyle });
@@ -936,8 +955,8 @@ export class ChatWidget extends Disposable implements IChatWidget {
936955
), { isTrusted: { enabledCommands: [generateInstructionsCommand] } });
937956
}
938957
if (this.contextKeyService.contextMatchesRules(this.chatSetupTriggerContext)) {
939-
welcomeContent = this.getExpWelcomeViewContent();
940-
this.container.classList.add('experimental-welcome-view');
958+
welcomeContent = this.getNewWelcomeViewContent();
959+
this.container.classList.add('new-welcome-view');
941960
}
942961
else if (expEmptyState) {
943962
welcomeContent = this.getWelcomeViewContent(additionalMessage, expEmptyState);
@@ -1194,20 +1213,27 @@ export class ChatWidget extends Disposable implements IChatWidget {
11941213
}
11951214
}
11961215

1197-
private getExpWelcomeViewContent(): IChatViewWelcomeContent {
1216+
private getNewWelcomeViewContent(): IChatViewWelcomeContent {
1217+
let additionalMessage: string | IMarkdownString | undefined = undefined;
1218+
if (this.chatEntitlementService.entitlement === ChatEntitlement.Unknown) {
1219+
additionalMessage = new MarkdownString(localize({ key: 'settings', comment: ['{Locked="]({2})"}', '{Locked="]({3})"}'] }, "AI responses may be inaccurate.\nBy continuing with {0} Copilot, you agree to {1}'s [Terms]({2}) and [Privacy Statement]({3}).", defaultChat.provider.default.name, defaultChat.provider.default.name, defaultChat.termsStatementUrl, defaultChat.privacyStatementUrl), { isTrusted: true });
1220+
} else {
1221+
additionalMessage = localize('expChatAdditionalMessage', "AI responses may be inaccurate.");
1222+
}
1223+
11981224
const welcomeContent: IChatViewWelcomeContent = {
11991225
title: localize('expChatTitle', 'Welcome to Copilot'),
12001226
message: new MarkdownString(localize('expchatMessage', "Let's get started")),
12011227
icon: Codicon.copilotLarge,
12021228
inputPart: this.inputPart.element,
1203-
additionalMessage: localize('expChatAdditionalMessage', "Review AI output carefully before use."),
1204-
isExperimental: true,
1205-
suggestedPrompts: this.getExpSuggestedPrompts(),
1229+
additionalMessage,
1230+
isNew: true,
1231+
suggestedPrompts: this.getNewSuggestedPrompts(),
12061232
};
12071233
return welcomeContent;
12081234
}
12091235

1210-
private getExpSuggestedPrompts(): IChatSuggestedPrompts[] {
1236+
private getNewSuggestedPrompts(): IChatSuggestedPrompts[] {
12111237
// Check if the workbench is empty
12121238
const isEmpty = this.contextService.getWorkbenchState() === WorkbenchState.EMPTY;
12131239
if (isEmpty) {
@@ -2368,7 +2394,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
23682394
this.inlineInputPart?.layout(layoutHeight, width);
23692395
}
23702396

2371-
if (this.container.classList.contains('experimental-welcome-view')) {
2397+
if (this.container.classList.contains('new-welcome-view')) {
23722398
this.inputPart.layout(layoutHeight, Math.min(width, 650));
23732399
}
23742400
else {

src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
/* Container for chat widget welcome message and interactive session variants */
1717
.interactive-session {
18-
&.experimental-welcome-view {
18+
&.new-welcome-view {
1919
.interactive-input-part {
2020
.dropdown-action-container { display: none; }
2121
.chat-attachments-container { display: none; }
@@ -40,7 +40,7 @@
4040
div.chat-welcome-view > .chat-welcome-view-title { display: none; }
4141
.chat-welcome-view-icon .codicon { line-height: 48px; }
4242
div.chat-welcome-view { align-self: center; margin-top: auto; margin-bottom: auto; }
43-
.chat-welcome-view-message.experimental-empty-state,
43+
.chat-welcome-view-message.empty-state,
4444
.chat-welcome-view-disclaimer {
4545
position: absolute;
4646
left: 0;
@@ -54,7 +54,7 @@
5454
}
5555
}
5656

57-
.experimental-welcome-view & > .chat-welcome-view-input-part {
57+
.new-welcome-view & > .chat-welcome-view-input-part {
5858
max-width: 650px;
5959
margin-bottom: 48px;
6060
}
@@ -110,7 +110,7 @@ div.chat-welcome-view {
110110
}
111111
}
112112

113-
& > .chat-welcome-view-message.experimental-empty-state {
113+
& > .chat-welcome-view-message.empty-state {
114114
position: relative;
115115
text-align: center;
116116
max-width: 100%;
@@ -154,7 +154,7 @@ div.chat-welcome-view {
154154
}
155155
}
156156

157-
& > .chat-welcome-experimental-view-message {
157+
& > .chat-welcome-new-view-message {
158158
text-align: center;
159159
max-width: 350px;
160160
padding: 0 20px 32px;
@@ -165,11 +165,16 @@ div.chat-welcome-view {
165165
}
166166
}
167167

168-
& > .chat-welcome-view-experimental-additional-message {
168+
& > .chat-welcome-view-additional-message {
169169
color: var(--vscode-input-placeholderForeground);
170170
text-align: center;
171171
max-width: 400px;
172172
margin-top: 8px;
173+
padding: 0 12px;
174+
font-size: 12px;
175+
a {
176+
color: var(--vscode-textLink-foreground);
177+
}
173178
}
174179

175180
/* Dedicated disclaimer container appended at root in TS */

src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export interface IChatViewWelcomeContent {
111111
readonly additionalMessage?: string | IMarkdownString;
112112
tips?: IMarkdownString;
113113
readonly inputPart?: HTMLElement;
114-
readonly isExperimental?: boolean;
114+
readonly isNew?: boolean;
115115
readonly suggestedPrompts?: readonly IChatSuggestedPrompts[];
116116
}
117117

@@ -157,37 +157,37 @@ export class ChatViewWelcomePart extends Disposable {
157157
const title = dom.append(this.element, $('.chat-welcome-view-title'));
158158
title.textContent = content.title;
159159

160-
// Preview indicator
160+
// Preview indicator (no experimental variants)
161161
const expEmptyState = this.configurationService.getValue<boolean>('chat.emptyChatState.enabled');
162162
if (typeof content.message !== 'function' && options?.isWidgetAgentWelcomeViewContent && !expEmptyState) {
163163
const container = dom.append(this.element, $('.chat-welcome-view-indicator-container'));
164164
dom.append(container, $('.chat-welcome-view-subtitle', undefined, localize('agentModeSubtitle', "Agent Mode")));
165165
}
166166

167167
// Message
168-
const message = dom.append(this.element, content.isExperimental ? $('.chat-welcome-experimental-view-message') : $('.chat-welcome-view-message'));
169-
message.classList.toggle('experimental-empty-state', expEmptyState);
168+
const message = dom.append(this.element, content.isNew ? $('.chat-welcome-new-view-message') : $('.chat-welcome-view-message'));
169+
message.classList.toggle('empty-state', expEmptyState);
170170

171171
const messageResult = this.renderMarkdownMessageContent(renderer, content.message, options);
172172
dom.append(message, messageResult.element);
173173

174-
if (content.isExperimental && content.inputPart) {
174+
if (content.isNew && content.inputPart) {
175175
content.inputPart.querySelector('.chat-attachments-container')?.remove();
176176
dom.append(this.element, content.inputPart);
177177
}
178-
if (!content.isExperimental) {
179-
// Additional message
178+
179+
// Additional message (new user mode)
180+
if (!content.isNew && content.additionalMessage) {
181+
const disclaimers = dom.append(this.element, $('.chat-welcome-view-disclaimer'));
180182
if (typeof content.additionalMessage === 'string') {
181-
const disclaimers = dom.append(this.element, $('.chat-welcome-view-disclaimer'));
182183
disclaimers.textContent = content.additionalMessage;
183-
} else if (content.additionalMessage) {
184-
const disclaimers = dom.append(this.element, $('.chat-welcome-view-disclaimer'));
184+
} else {
185185
const additionalMessageResult = this.renderMarkdownMessageContent(renderer, content.additionalMessage, options);
186186
disclaimers.appendChild(additionalMessageResult.element);
187187
}
188188
}
189189

190-
// Render suggested prompts for both experimental and regular modes
190+
// Render suggested prompts for both new user and regular modes
191191
if (content.suggestedPrompts && content.suggestedPrompts.length) {
192192
const suggestedPromptsContainer = dom.append(this.element, $('.chat-welcome-view-suggested-prompts'));
193193
for (const prompt of content.suggestedPrompts) {
@@ -246,23 +246,26 @@ export class ChatViewWelcomePart extends Disposable {
246246
tips.appendChild(tipsResult.element);
247247
}
248248

249-
// In experimental mode, render the additional message after suggested prompts (deferred)
250-
if (content.isExperimental && typeof content.additionalMessage === 'string') {
251-
const additionalMsg = $('.chat-welcome-view-experimental-additional-message');
252-
additionalMsg.textContent = content.additionalMessage;
253-
dom.append(this.element, additionalMsg);
249+
// In new user mode, render the additional message after suggested prompts (deferred)
250+
if (content.isNew && content.additionalMessage) {
251+
const additionalMsg = dom.append(this.element, $('.chat-welcome-view-additional-message'));
252+
if (typeof content.additionalMessage === 'string') {
253+
additionalMsg.textContent = content.additionalMessage;
254+
} else {
255+
const additionalMessageResult = this.renderMarkdownMessageContent(renderer, content.additionalMessage, options);
256+
additionalMsg.appendChild(additionalMessageResult.element);
257+
}
254258
}
255-
256259
} catch (err) {
257260
this.logService.error('Failed to render chat view welcome content', err);
258261
}
259262
}
260263

261264
public needsRerender(content: IChatViewWelcomeContent): boolean {
262265
// Heuristic based on content that changes between states
263-
return !!(content.isExperimental ||
266+
return !!(content.isNew ||
264267
this.content.title !== content.title ||
265-
this.content.isExperimental !== content.isExperimental ||
268+
this.content.isNew !== content.isNew ||
266269
this.content.message.value !== content.message.value ||
267270
this.content.additionalMessage !== content.additionalMessage ||
268271
this.content.tips?.value !== content.tips?.value ||

src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import { NullWorkbenchAssignmentService } from '../../../../services/assignment/
4646
import { IExtensionService, nullExtensionDescription } from '../../../../services/extensions/common/extensions.js';
4747
import { TextModelResolverService } from '../../../../services/textmodelResolver/common/textModelResolverService.js';
4848
import { IViewsService } from '../../../../services/views/common/viewsService.js';
49-
import { TestViewsService, workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js';
49+
import { TestChatEntitlementService, TestViewsService, workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js';
5050
import { TestContextService, TestExtensionService } from '../../../../test/common/workbenchTestServices.js';
5151
import { AccessibilityVerbositySettingId } from '../../../accessibility/browser/accessibilityConfiguration.js';
5252
import { IChatAccessibilityService, IChatWidget, IChatWidgetService } from '../../../chat/browser/chat.js';
@@ -223,6 +223,7 @@ suite('InlineChatController', function () {
223223
override getTodos(sessionId: string): IChatTodo[] { return []; }
224224
override setTodos(sessionId: string, todos: IChatTodo[]): void { }
225225
}],
226+
[IChatEntitlementService, new SyncDescriptor(TestChatEntitlementService)],
226227
);
227228

228229
instaService = store.add((store.add(workbenchInstantiationService(undefined, store))).createChild(serviceCollection));

src/vs/workbench/test/browser/workbenchTestServices.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ import { TerminalEditorInput } from '../../contrib/terminal/browser/terminalEdit
137137
import { IEnvironmentVariableService } from '../../contrib/terminal/common/environmentVariable.js';
138138
import { EnvironmentVariableService } from '../../contrib/terminal/common/environmentVariableService.js';
139139
import { IRegisterContributedProfileArgs, IShellLaunchConfigResolveOptions, ITerminalProfileProvider, ITerminalProfileResolverService, ITerminalProfileService, type ITerminalConfiguration } from '../../contrib/terminal/common/terminal.js';
140+
import { ChatEntitlement, IChatEntitlementService } from '../../services/chat/common/chatEntitlementService.js';
140141
import { IDecoration, IDecorationData, IDecorationsProvider, IDecorationsService, IResourceDecorationChangeEvent } from '../../services/decorations/common/decorations.js';
141142
import { CodeEditorService } from '../../services/editor/browser/codeEditorService.js';
142143
import { EditorPaneService } from '../../services/editor/browser/editorPaneService.js';
@@ -368,6 +369,7 @@ export function workbenchInstantiationService(
368369
instantiationService.stub(IRemoteSocketFactoryService, new RemoteSocketFactoryService());
369370
instantiationService.stub(ICustomEditorLabelService, disposables.add(new CustomEditorLabelService(configService, workspaceContextService)));
370371
instantiationService.stub(IHoverService, NullHoverService);
372+
instantiationService.stub(IChatEntitlementService, new TestChatEntitlementService());
371373

372374
return instantiationService;
373375
}
@@ -2164,3 +2166,26 @@ export async function workbenchTeardown(instantiationService: IInstantiationServ
21642166
}
21652167
});
21662168
}
2169+
2170+
export class TestChatEntitlementService implements IChatEntitlementService {
2171+
2172+
_serviceBrand: undefined;
2173+
2174+
readonly organisations: undefined;
2175+
readonly isInternal = false;
2176+
readonly sku = undefined;
2177+
2178+
readonly onDidChangeQuotaExceeded = Event.None;
2179+
readonly onDidChangeQuotaRemaining = Event.None;
2180+
readonly quotas = {};
2181+
2182+
update(token: CancellationToken): Promise<void> {
2183+
throw new Error('Method not implemented.');
2184+
}
2185+
2186+
readonly onDidChangeSentiment = Event.None;
2187+
readonly sentiment = {};
2188+
2189+
readonly onDidChangeEntitlement = Event.None;
2190+
entitlement: ChatEntitlement = ChatEntitlement.Unknown;
2191+
}

0 commit comments

Comments
 (0)