Refactor chat widget welcome view to include additional messages and improve styling (#253720)

* Refactor chat widget welcome view to include additional messages and improve styling

* Update experiment configurations to adjust target percentages and group allocations
This commit is contained in:
Bhavya U
2025-07-03 08:57:30 -07:00
committed by GitHub
parent 8693de592f
commit 6ac7f77fdf
3 changed files with 56 additions and 124 deletions
@@ -11,7 +11,7 @@ import { Codicon } from '../../../../base/common/codicons.js';
import { toErrorMessage } from '../../../../base/common/errorMessage.js';
import { Emitter, Event } from '../../../../base/common/event.js';
import { FuzzyScore } from '../../../../base/common/filters.js';
import { MarkdownString } from '../../../../base/common/htmlContent.js';
import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js';
import { Iterable } from '../../../../base/common/iterator.js';
import { combinedDisposable, Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
import { ResourceSet } from '../../../../base/common/map.js';
@@ -64,7 +64,7 @@ import { ChatEditorOptions } from './chatOptions.js';
import './media/chat.css';
import './media/chatAgentHover.css';
import './media/chatViewWelcome.css';
import { ChatViewWelcomePart, IChatSuggestedPrompts, IChatViewWelcomeContent } from './viewsWelcome/chatViewWelcomeController.js';
import { ChatViewWelcomePart, IChatViewWelcomeContent } from './viewsWelcome/chatViewWelcomeController.js';
import { MicrotaskDelay } from '../../../../base/common/symbols.js';
import { IChatRequestVariableEntry, ChatRequestVariableSet as ChatRequestVariableSet, isPromptFileVariableEntry, toPromptFileVariableEntry, PromptFileVariableKind } from '../common/chatVariableEntries.js';
import { PromptsConfig } from '../common/promptSyntax/config/config.js';
@@ -734,9 +734,6 @@ export class ChatWidget extends Disposable implements IChatWidget {
const numItems = this.viewModel?.getItems().length ?? 0;
if (!numItems) {
dom.clearNode(this.welcomeMessageContainer);
const defaultAgent = this.chatAgentService.getDefaultAgent(this.location, this.input.currentModeKind);
const additionalMessage = defaultAgent?.metadata.additionalWelcomeMessage;
const startupExpValue = startupExpContext.getValue(this.contextKeyService);
let welcomeContent: IChatViewWelcomeContent;
if (startupExpValue === StartupExperimentGroup.MaximizedChat
@@ -746,15 +743,17 @@ export class ChatWidget extends Disposable implements IChatWidget {
this.container.classList.add('experimental-welcome-view');
}
else {
const defaultAgent = this.chatAgentService.getDefaultAgent(this.location, this.input.currentModeKind);
const additionalMessage = defaultAgent?.metadata.additionalWelcomeMessage;
const tips = this.input.currentModeKind === ChatModeKind.Ask
? new MarkdownString(localize('chatWidget.tips', "{0} or type {1} to attach context\n\n{2} to chat with extensions\n\nType {3} to use commands", '$(attach)', '#', '$(mention)', '/'), { supportThemeIcons: true })
: new MarkdownString(localize('chatWidget.tips.withoutParticipants', "{0} or type {1} to attach context", '$(attach)', '#'), { supportThemeIcons: true });
welcomeContent = this.getWelcomeViewContent();
welcomeContent = this.getWelcomeViewContent(additionalMessage);
welcomeContent.tips = tips;
}
this.welcomePart.value = this.instantiationService.createInstance(
ChatViewWelcomePart,
{ ...welcomeContent, additionalMessage },
welcomeContent,
{
location: this.location,
isWidgetAgentWelcomeViewContent: this.input?.currentModeKind === ChatModeKind.Agent
@@ -769,58 +768,44 @@ export class ChatWidget extends Disposable implements IChatWidget {
}
}
private getWelcomeViewContent(): IChatViewWelcomeContent {
private getWelcomeViewContent(additionalMessage: string | IMarkdownString | undefined): IChatViewWelcomeContent {
const baseMessage = localize('chatMessage', "Copilot is powered by AI, so mistakes are possible. Review output carefully before use.");
if (this.input.currentModeKind === ChatModeKind.Ask) {
return {
title: localize('chatDescription', "Ask Copilot"),
message: new MarkdownString(baseMessage),
icon: Codicon.copilotLarge
icon: Codicon.copilotLarge,
additionalMessage,
};
} else if (this.input.currentModeKind === ChatModeKind.Edit) {
return {
title: localize('editsTitle', "Edit with Copilot"),
message: new MarkdownString(localize('editsMessage', "Start your editing session by defining a set of files that you want to work with. Then ask Copilot for the changes you want to make.") + `\n\n${baseMessage}`),
icon: Codicon.copilotLarge
icon: Codicon.copilotLarge,
additionalMessage
};
} else {
return {
title: localize('editsTitle', "Edit with Copilot"),
message: new MarkdownString(localize('agentMessage', "Ask Copilot to edit your files in [agent mode]({0}). Copilot will automatically use multiple requests to pick files to edit, run terminal commands, and iterate on errors.", 'https://aka.ms/vscode-copilot-agent') + `\n\n${baseMessage}`),
icon: Codicon.copilotLarge
icon: Codicon.copilotLarge,
additionalMessage
};
}
}
private getExpWelcomeViewContent(): IChatViewWelcomeContent {
const baseMessage = localize('chatMessage', "Copilot is powered by AI, so mistakes are possible. Review output carefully before use.");
const welcomeContent = {
title: localize('expChatTitle', 'Get Started with VS Code'),
message: new MarkdownString(baseMessage),
const welcomeContent: IChatViewWelcomeContent = {
title: localize('expChatTitle', 'Welcome to Copilot'),
message: new MarkdownString(localize('expchatMessage', "Let's get started")),
icon: Codicon.copilotLarge,
suggestedPrompts: this.getExpSuggestedPrompts(),
inputPart: this.inputPart.element,
additionalMessage: localize('expChatAdditionalMessage', "Review output carefully before use."),
isExperimental: true
};
return welcomeContent;
}
private getExpSuggestedPrompts(): IChatSuggestedPrompts[] {
return [
{
icon: Codicon.vscode,
label: localize('chatWidget.suggestedPrompts.gettingStarted', "Ask @vscode"),
prompt: '@vscode Help me get started with VS Code?',
},
{
icon: Codicon.newFolder,
label: localize('chatWidget.suggestedPrompts.newProject', "Create a #new Project"),
prompt: '#new Create a new project for me',
}
];
}
private async renderChatEditingSessionState() {
if (!this.input) {
return;
@@ -23,6 +23,11 @@
.interactive-session .experimental-welcome-view & > .chat-welcome-view-input-part {
max-width: 650px;
margin-bottom: 48px;
}
.interactive-session.experimental-welcome-view .chat-input-toolbars > .chat-input-toolbar > div {
display: none;
}
/* Container for ChatViewPane welcome view */
@@ -102,44 +107,21 @@ div.chat-welcome-view {
}
}
& > .chat-welcome-view-suggested-prompts {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
margin-top: 15px;
& > .chat-welcome-experimental-view-message {
text-align: center;
max-width: 350px;
padding: 0 20px 32px;
font-size: 16px;
> .chat-welcome-view-suggested-prompt {
display: flex;
align-items: center;
padding: 0 4px;
border-radius: 8px;
background-color: var(--vscode-editorWidget-background);
cursor: pointer;
border: 1px solid var(--vscode-chat-requestBorder, var(--vscode-input-background, transparent));
border-radius: 4px;
gap: 2px;
max-width: 100%;
width: fit-content;
> .chat-welcome-view-suggested-prompt-icon {
display: flex;
align-items: center;
margin-right: 8px;
font-size: 4px;
color: var(--vscode-icon-foreground) !important;
align-items: center;
}
> .chat-welcome-view-suggested-prompt-label {
font-size: 14px;
color: var(--vscode-editorWidget-foreground);
}
}
> .chat-welcome-view-suggested-prompt:hover {
background-color: var(--vscode-list-hoverBackground);
border-color: var(--vscode-focusBorder);
a {
color: var(--vscode-descriptionForeground);
}
}
& > .chat-welcome-view-experimental-additional-message {
font-size: 12px;
color: var(--vscode-disabledForeground);
text-align: center;
max-width: 400px;
}
}
@@ -19,8 +19,6 @@ import { IOpenerService } from '../../../../../platform/opener/common/opener.js'
import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js';
import { ChatAgentLocation } from '../../common/constants.js';
import { chatViewsWelcomeRegistry, IChatViewsWelcomeDescriptor } from './chatViewsWelcome.js';
import { IChatWidgetService } from '../chat.js';
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
const $ = dom.$;
@@ -115,13 +113,7 @@ export interface IChatViewWelcomeContent {
additionalMessage?: string | IMarkdownString;
tips?: IMarkdownString;
inputPart?: HTMLElement;
suggestedPrompts?: IChatSuggestedPrompts[];
}
export interface IChatSuggestedPrompts {
icon?: ThemeIcon;
label: string;
prompt: string;
isExperimental?: boolean;
}
export interface IChatViewWelcomeRenderOptions {
@@ -139,8 +131,6 @@ export class ChatViewWelcomePart extends Disposable {
@IOpenerService private openerService: IOpenerService,
@IInstantiationService private instantiationService: IInstantiationService,
@ILogService private logService: ILogService,
@IChatWidgetService private chatWidgetService: IChatWidgetService,
@ITelemetryService private telemetryService: ITelemetryService,
) {
super();
this.element = dom.$('.chat-welcome-view');
@@ -165,57 +155,32 @@ export class ChatViewWelcomePart extends Disposable {
}
// Message
const message = dom.append(this.element, $('.chat-welcome-view-message'));
const message = dom.append(this.element, content.isExperimental ? $('.chat-welcome-experimental-view-message') : $('.chat-welcome-view-message'));
if (typeof content.message === 'function') {
dom.append(message, content.message(this._register(new DisposableStore())));
} else {
const messageResult = this.renderMarkdownMessageContent(renderer, content.message, options);
dom.append(message, messageResult.element);
}
// Additional message
if (typeof content.additionalMessage === 'string') {
const element = $('');
element.textContent = content.additionalMessage;
dom.append(message, element);
} else if (content.additionalMessage) {
const additionalMessageResult = this.renderMarkdownMessageContent(renderer, content.additionalMessage, options);
dom.append(message, additionalMessageResult.element);
}
if (content.inputPart) {
if (content.isExperimental && content.inputPart) {
content.inputPart.querySelector('.chat-attachments-container')?.remove();
dom.append(this.element, content.inputPart);
}
if (content.suggestedPrompts && content.suggestedPrompts.length) {
// create a tile with icon and label for each suggested promot
const suggestedPromptsContainer = dom.append(this.element, $('.chat-welcome-view-suggested-prompts'));
for (const prompt of content.suggestedPrompts) {
const promptElement = dom.append(suggestedPromptsContainer, $('.chat-welcome-view-suggested-prompt'));
if (prompt.icon) {
const iconElement = dom.append(promptElement, $('.chat-welcome-view-suggested-prompt-icon'));
iconElement.appendChild(renderIcon(prompt.icon));
}
const labelElement = dom.append(promptElement, $('.chat-welcome-view-suggested-prompt-label'));
labelElement.textContent = prompt.label;
this._register(dom.addDisposableListener(promptElement, dom.EventType.CLICK, () => {
type SuggestedPromptClickEvent = { suggestedPrompt: string };
type SuggestedPromptClickData = {
owner: 'bhavyaus';
comment: 'Event used to gain insights into when suggested prompts are clicked.';
suggestedPrompt: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The suggested prompt clicked.' };
};
this.telemetryService.publicLog2<SuggestedPromptClickEvent, SuggestedPromptClickData>('chat.clickedSuggestedPrompt', {
suggestedPrompt: prompt.prompt,
});
this.chatWidgetService.lastFocusedWidget?.setInput(prompt.prompt);
}));
if (typeof content.additionalMessage === 'string') {
const additionalMsg = $('.chat-welcome-view-experimental-additional-message');
additionalMsg.textContent = content.additionalMessage;
dom.append(this.element, additionalMsg);
}
// also append telemetry message if available
} else {
// Additional message
if (typeof content.additionalMessage === 'string') {
const element = $('');
element.textContent = content.additionalMessage;
dom.append(message, element);
} else if (content.additionalMessage) {
const additionalMessageResult = this.renderMarkdownMessageContent(renderer, content.additionalMessage, options);
dom.append(message, additionalMessageResult.element);
}
}