mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-19 17:58:39 +00:00
feat(chat): add status widget and input part widget controller for en… (#280269)
* feat(chat): add status widget and input part widget controller for enhanced chat functionality * update chat status widget to simplify entitlement checks to only show for internal users and improve button labeling
This commit is contained in:
@@ -89,6 +89,7 @@ import './agentSessions/agentSessionsView.js';
|
||||
import { IChatAccessibilityService, IChatCodeBlockContextProviderService, IChatWidgetService, IQuickChatService } from './chat.js';
|
||||
import { ChatAccessibilityService } from './chatAccessibilityService.js';
|
||||
import './chatAttachmentModel.js';
|
||||
import './chatStatusWidget.js';
|
||||
import { ChatAttachmentResolveService, IChatAttachmentResolveService } from './chatAttachmentResolveService.js';
|
||||
import { ChatMarkdownAnchorService, IChatMarkdownAnchorService } from './chatContentParts/chatMarkdownAnchorService.js';
|
||||
import { ChatContextPickService, IChatContextPickService } from './chatContextPickService.js';
|
||||
@@ -535,6 +536,16 @@ configurationRegistry.registerConfiguration({
|
||||
default: true,
|
||||
tags: ['experimental'],
|
||||
},
|
||||
['chat.statusWidget.enabled']: {
|
||||
type: 'boolean',
|
||||
description: nls.localize('chat.statusWidget.enabled.description', "Show the status widget in new chat sessions when quota is exceeded."),
|
||||
default: false,
|
||||
tags: ['experimental'],
|
||||
included: false,
|
||||
experiment: {
|
||||
mode: 'auto'
|
||||
}
|
||||
},
|
||||
[ChatConfiguration.AgentSessionsViewLocation]: {
|
||||
type: 'string',
|
||||
enum: ['disabled', 'view', 'single-view'], // TODO@bpasero remove this setting eventually
|
||||
|
||||
@@ -97,6 +97,7 @@ import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmen
|
||||
import { IDisposableReference } from './chatContentParts/chatCollections.js';
|
||||
import { CollapsibleListPool, IChatCollapsibleListItem } from './chatContentParts/chatReferencesContentPart.js';
|
||||
import { ChatTodoListWidget } from './chatContentParts/chatTodoListWidget.js';
|
||||
import { ChatInputPartWidgetController } from './chatInputPartWidgets.js';
|
||||
import { IChatContextService } from './chatContextService.js';
|
||||
import { ChatDragAndDrop } from './chatDragAndDrop.js';
|
||||
import { ChatEditingShowChangesAction, ViewPreviousEditsAction } from './chatEditing/chatEditingActions.js';
|
||||
@@ -234,6 +235,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
|
||||
private chatEditingSessionWidgetContainer!: HTMLElement;
|
||||
private chatInputTodoListWidgetContainer!: HTMLElement;
|
||||
private chatInputWidgetsContainer!: HTMLElement;
|
||||
private readonly _widgetController = this._register(new MutableDisposable<ChatInputPartWidgetController>());
|
||||
|
||||
private _inputPartHeight: number = 0;
|
||||
get inputPartHeight() {
|
||||
@@ -254,6 +257,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
return this.chatInputTodoListWidgetContainer.offsetHeight;
|
||||
}
|
||||
|
||||
get inputWidgetsHeight() {
|
||||
return this.chatInputWidgetsContainer?.offsetHeight ?? 0;
|
||||
}
|
||||
|
||||
get attachmentsHeight() {
|
||||
return this.attachmentsContainer.offsetHeight + (this.attachmentsContainer.checkVisibility() ? 6 : 0);
|
||||
}
|
||||
@@ -1312,6 +1319,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
if (this.options.renderStyle === 'compact') {
|
||||
elements = dom.h('.interactive-input-part', [
|
||||
dom.h('.interactive-input-and-edit-session', [
|
||||
dom.h('.chat-input-widgets-container@chatInputWidgetsContainer'),
|
||||
dom.h('.chat-todo-list-widget-container@chatInputTodoListWidgetContainer'),
|
||||
dom.h('.chat-editing-session@chatEditingSessionWidgetContainer'),
|
||||
dom.h('.interactive-input-and-side-toolbar@inputAndSideToolbar', [
|
||||
@@ -1331,6 +1339,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
} else {
|
||||
elements = dom.h('.interactive-input-part', [
|
||||
dom.h('.interactive-input-followups@followupsContainer'),
|
||||
dom.h('.chat-input-widgets-container@chatInputWidgetsContainer'),
|
||||
dom.h('.chat-todo-list-widget-container@chatInputTodoListWidgetContainer'),
|
||||
dom.h('.chat-editing-session@chatEditingSessionWidgetContainer'),
|
||||
dom.h('.interactive-input-and-side-toolbar@inputAndSideToolbar', [
|
||||
@@ -1362,6 +1371,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
const attachmentToolbarContainer = elements.attachmentToolbar;
|
||||
this.chatEditingSessionWidgetContainer = elements.chatEditingSessionWidgetContainer;
|
||||
this.chatInputTodoListWidgetContainer = elements.chatInputTodoListWidgetContainer;
|
||||
this.chatInputWidgetsContainer = elements.chatInputWidgetsContainer;
|
||||
|
||||
if (this.options.enableImplicitContext) {
|
||||
this._implicitContext = this._register(
|
||||
@@ -1374,6 +1384,9 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
}));
|
||||
}
|
||||
|
||||
this._widgetController.value = this.instantiationService.createInstance(ChatInputPartWidgetController, this.chatInputWidgetsContainer);
|
||||
this._register(this._widgetController.value.onDidChangeHeight(() => this._onDidChangeHeight.fire()));
|
||||
|
||||
this.renderAttachedContext();
|
||||
this._register(this._attachmentModel.onDidChange((e) => {
|
||||
if (e.added.length > 0) {
|
||||
@@ -2197,7 +2210,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
|
||||
get contentHeight(): number {
|
||||
const data = this.getLayoutData();
|
||||
return data.followupsHeight + data.inputPartEditorHeight + data.inputPartVerticalPadding + data.inputEditorBorder + data.attachmentsHeight + data.toolbarsHeight + data.chatEditingStateHeight + data.todoListWidgetContainerHeight;
|
||||
return data.followupsHeight + data.inputPartEditorHeight + data.inputPartVerticalPadding + data.inputEditorBorder + data.attachmentsHeight + data.toolbarsHeight + data.chatEditingStateHeight + data.todoListWidgetContainerHeight + data.inputWidgetsContainerHeight;
|
||||
}
|
||||
|
||||
layout(height: number, width: number) {
|
||||
@@ -2209,12 +2222,12 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
private previousInputEditorDimension: IDimension | undefined;
|
||||
private _layout(height: number, width: number, allowRecurse = true): void {
|
||||
const data = this.getLayoutData();
|
||||
const inputEditorHeight = Math.min(data.inputPartEditorHeight, height - data.followupsHeight - data.attachmentsHeight - data.inputPartVerticalPadding - data.toolbarsHeight - data.chatEditingStateHeight - data.todoListWidgetContainerHeight);
|
||||
const inputEditorHeight = Math.min(data.inputPartEditorHeight, height - data.followupsHeight - data.attachmentsHeight - data.inputPartVerticalPadding - data.toolbarsHeight - data.chatEditingStateHeight - data.todoListWidgetContainerHeight - data.inputWidgetsContainerHeight);
|
||||
|
||||
const followupsWidth = width - data.inputPartHorizontalPadding;
|
||||
this.followupsContainer.style.width = `${followupsWidth}px`;
|
||||
|
||||
this._inputPartHeight = data.inputPartVerticalPadding + data.followupsHeight + inputEditorHeight + data.inputEditorBorder + data.attachmentsHeight + data.toolbarsHeight + data.chatEditingStateHeight + data.todoListWidgetContainerHeight;
|
||||
this._inputPartHeight = data.inputPartVerticalPadding + data.followupsHeight + inputEditorHeight + data.inputEditorBorder + data.attachmentsHeight + data.toolbarsHeight + data.chatEditingStateHeight + data.todoListWidgetContainerHeight + data.inputWidgetsContainerHeight;
|
||||
this._followupsHeight = data.followupsHeight;
|
||||
this._editSessionWidgetHeight = data.chatEditingStateHeight;
|
||||
|
||||
@@ -2265,6 +2278,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
chatEditingStateHeight: this.chatEditingSessionWidgetContainer.offsetHeight,
|
||||
sideToolbarWidth: inputSideToolbarWidth > 0 ? inputSideToolbarWidth + 4 /*gap*/ : 0,
|
||||
todoListWidgetContainerHeight: this.chatInputTodoListWidgetContainer.offsetHeight,
|
||||
inputWidgetsContainerHeight: this.inputWidgetsHeight,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
144
src/vs/workbench/contrib/chat/browser/chatInputPartWidgets.ts
Normal file
144
src/vs/workbench/contrib/chat/browser/chatInputPartWidgets.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { ContextKeyExpression, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { BrandedService, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
|
||||
/**
|
||||
* A widget that can be rendered on top of the chat input part.
|
||||
*/
|
||||
export interface IChatInputPartWidget extends IDisposable {
|
||||
/**
|
||||
* The DOM node of the widget.
|
||||
*/
|
||||
readonly domNode: HTMLElement;
|
||||
|
||||
/**
|
||||
* Fired when the height of the widget changes.
|
||||
*/
|
||||
readonly onDidChangeHeight: Event<void>;
|
||||
|
||||
/**
|
||||
* The current height of the widget in pixels.
|
||||
*/
|
||||
readonly height: number;
|
||||
}
|
||||
|
||||
export interface IChatInputPartWidgetDescriptor<Services extends BrandedService[] = BrandedService[]> {
|
||||
readonly id: string;
|
||||
readonly when?: ContextKeyExpression;
|
||||
readonly ctor: new (...services: Services) => IChatInputPartWidget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registry for chat input part widgets.
|
||||
* Widgets register themselves and are instantiated by the controller based on context key conditions.
|
||||
*/
|
||||
export const ChatInputPartWidgetsRegistry = new class {
|
||||
readonly widgets: IChatInputPartWidgetDescriptor[] = [];
|
||||
|
||||
register<Services extends BrandedService[]>(id: string, ctor: new (...services: Services) => IChatInputPartWidget, when?: ContextKeyExpression): void {
|
||||
this.widgets.push({ id, ctor: ctor as IChatInputPartWidgetDescriptor['ctor'], when });
|
||||
}
|
||||
|
||||
getWidgets(): readonly IChatInputPartWidgetDescriptor[] {
|
||||
return this.widgets;
|
||||
}
|
||||
}();
|
||||
|
||||
interface IRenderedWidget {
|
||||
readonly descriptor: IChatInputPartWidgetDescriptor;
|
||||
readonly widget: IChatInputPartWidget;
|
||||
readonly disposables: DisposableStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controller that manages the rendering of widgets in the chat input part.
|
||||
* Widgets are shown/hidden based on context key conditions.
|
||||
*/
|
||||
export class ChatInputPartWidgetController extends Disposable {
|
||||
|
||||
private readonly _onDidChangeHeight = this._register(new Emitter<void>());
|
||||
readonly onDidChangeHeight: Event<void> = this._onDidChangeHeight.event;
|
||||
|
||||
private readonly renderedWidgets = new Map<string, IRenderedWidget>();
|
||||
|
||||
constructor(
|
||||
private readonly container: HTMLElement,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.update();
|
||||
|
||||
this._register(this.contextKeyService.onDidChangeContext(e => {
|
||||
const relevantKeys = new Set<string>();
|
||||
for (const descriptor of ChatInputPartWidgetsRegistry.getWidgets()) {
|
||||
if (descriptor.when) {
|
||||
for (const key of descriptor.when.keys()) {
|
||||
relevantKeys.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (e.affectsSome(relevantKeys)) {
|
||||
this.update();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private update(): void {
|
||||
const visibleIds = new Set<string>();
|
||||
for (const descriptor of ChatInputPartWidgetsRegistry.getWidgets()) {
|
||||
if (this.contextKeyService.contextMatchesRules(descriptor.when)) {
|
||||
visibleIds.add(descriptor.id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [id, rendered] of this.renderedWidgets) {
|
||||
if (!visibleIds.has(id)) {
|
||||
rendered.widget.domNode.remove();
|
||||
rendered.disposables.dispose();
|
||||
this.renderedWidgets.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const descriptor of ChatInputPartWidgetsRegistry.getWidgets()) {
|
||||
if (!visibleIds.has(descriptor.id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this.renderedWidgets.has(descriptor.id)) {
|
||||
const disposables = new DisposableStore();
|
||||
const widget = this.instantiationService.createInstance(descriptor.ctor);
|
||||
disposables.add(widget);
|
||||
disposables.add(widget.onDidChangeHeight(() => this._onDidChangeHeight.fire()));
|
||||
|
||||
this.renderedWidgets.set(descriptor.id, { descriptor, widget, disposables });
|
||||
this.container.appendChild(widget.domNode);
|
||||
}
|
||||
}
|
||||
|
||||
this._onDidChangeHeight.fire();
|
||||
}
|
||||
|
||||
get height(): number {
|
||||
let total = 0;
|
||||
for (const rendered of this.renderedWidgets.values()) {
|
||||
total += rendered.widget.height;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
for (const rendered of this.renderedWidgets.values()) {
|
||||
rendered.disposables.dispose();
|
||||
}
|
||||
this.renderedWidgets.clear();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
138
src/vs/workbench/contrib/chat/browser/chatStatusWidget.ts
Normal file
138
src/vs/workbench/contrib/chat/browser/chatStatusWidget.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import './media/chatStatusWidget.css';
|
||||
import * as dom from '../../../../base/browser/dom.js';
|
||||
import { Button } from '../../../../base/browser/ui/button/button.js';
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||
import { localize, localize2 } from '../../../../nls.js';
|
||||
import { Categories } from '../../../../platform/action/common/actionCommonCategories.js';
|
||||
import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
|
||||
import { ICommandService } from '../../../../platform/commands/common/commands.js';
|
||||
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
|
||||
import { IContextKeyService, ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { defaultButtonStyles } from '../../../../platform/theme/browser/defaultStyles.js';
|
||||
import { ChatEntitlement, ChatEntitlementContextKeys, IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js';
|
||||
import { ChatInputPartWidgetsRegistry, IChatInputPartWidget } from './chatInputPartWidgets.js';
|
||||
import { ChatContextKeys } from '../common/chatContextKeys.js';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
/**
|
||||
* Widget that displays a status message with an optional action button.
|
||||
* Only shown for free tier users when the setting is enabled (experiment controlled via onExP tag).
|
||||
*/
|
||||
export class ChatStatusWidget extends Disposable implements IChatInputPartWidget {
|
||||
|
||||
static readonly ID = 'chatStatusWidget';
|
||||
|
||||
readonly domNode: HTMLElement;
|
||||
|
||||
private readonly _onDidChangeHeight = this._register(new Emitter<void>());
|
||||
readonly onDidChangeHeight: Event<void> = this._onDidChangeHeight.event;
|
||||
|
||||
private messageElement: HTMLElement | undefined;
|
||||
private actionButton: Button | undefined;
|
||||
private _isEnabled = false;
|
||||
|
||||
constructor(
|
||||
@IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.domNode = $('.chat-status-widget');
|
||||
this.domNode.style.display = 'none';
|
||||
this.initializeIfEnabled();
|
||||
}
|
||||
|
||||
private initializeIfEnabled(): void {
|
||||
const isEnabled = this.configurationService.getValue<boolean>('chat.statusWidget.enabled');
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isEnabled = true;
|
||||
if (!this.chatEntitlementService.isInternal) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.createWidgetContent();
|
||||
this.updateContent();
|
||||
this.domNode.style.display = '';
|
||||
|
||||
this._register(this.chatEntitlementService.onDidChangeEntitlement(() => {
|
||||
this.updateContent();
|
||||
}));
|
||||
|
||||
this._onDidChangeHeight.fire();
|
||||
}
|
||||
|
||||
get height(): number {
|
||||
return this._isEnabled ? this.domNode.offsetHeight : 0;
|
||||
}
|
||||
|
||||
private createWidgetContent(): void {
|
||||
const contentContainer = $('.chat-status-content');
|
||||
this.messageElement = $('.chat-status-message');
|
||||
contentContainer.appendChild(this.messageElement);
|
||||
|
||||
const actionContainer = $('.chat-status-action');
|
||||
this.actionButton = this._register(new Button(actionContainer, {
|
||||
...defaultButtonStyles,
|
||||
supportIcons: true
|
||||
}));
|
||||
this.actionButton.element.classList.add('chat-status-button');
|
||||
|
||||
this._register(this.actionButton.onDidClick(async () => {
|
||||
const commandId = this.chatEntitlementService.entitlement === ChatEntitlement.Free
|
||||
? 'workbench.action.chat.upgradePlan'
|
||||
: 'workbench.action.chat.manageOverages';
|
||||
await this.commandService.executeCommand(commandId);
|
||||
}));
|
||||
|
||||
this.domNode.appendChild(contentContainer);
|
||||
this.domNode.appendChild(actionContainer);
|
||||
}
|
||||
|
||||
private updateContent(): void {
|
||||
if (!this.messageElement || !this.actionButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.messageElement.textContent = localize('chat.quotaExceeded.message', "Free tier chat message limit reached.");
|
||||
this.actionButton.label = localize('chat.quotaExceeded.increaseLimit', "Increase Limit");
|
||||
|
||||
this._onDidChangeHeight.fire();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO@bhavyaus remove this command after testing complete with team
|
||||
registerAction2(class ToggleChatQuotaExceededAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.chat.toggleStatusWidget',
|
||||
title: localize2('chat.toggleStatusWidget.label', "Toggle Chat Status Widget State"),
|
||||
f1: true,
|
||||
category: Categories.Developer,
|
||||
precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatEntitlementContextKeys.Entitlement.internal),
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor): void {
|
||||
const contextKeyService = accessor.get(IContextKeyService);
|
||||
const currentValue = ChatEntitlementContextKeys.chatQuotaExceeded.getValue(contextKeyService) ?? false;
|
||||
ChatEntitlementContextKeys.chatQuotaExceeded.bindTo(contextKeyService).set(!currentValue);
|
||||
}
|
||||
});
|
||||
|
||||
ChatInputPartWidgetsRegistry.register(
|
||||
ChatStatusWidget.ID,
|
||||
ChatStatusWidget,
|
||||
ContextKeyExpr.and(ChatContextKeys.chatQuotaExceeded, ChatContextKeys.chatSessionIsEmpty)
|
||||
);
|
||||
@@ -274,6 +274,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
};
|
||||
private readonly _lockedToCodingAgentContextKey: IContextKey<boolean>;
|
||||
private readonly _agentSupportsAttachmentsContextKey: IContextKey<boolean>;
|
||||
private readonly _sessionIsEmptyContextKey: IContextKey<boolean>;
|
||||
private _attachmentCapabilities: IChatAgentAttachmentCapabilities = supportsAllAttachments;
|
||||
|
||||
// Cache for prompt file descriptions to avoid async calls during rendering
|
||||
@@ -382,6 +383,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
|
||||
this._lockedToCodingAgentContextKey = ChatContextKeys.lockedToCodingAgent.bindTo(this.contextKeyService);
|
||||
this._agentSupportsAttachmentsContextKey = ChatContextKeys.agentSupportsAttachments.bindTo(this.contextKeyService);
|
||||
this._sessionIsEmptyContextKey = ChatContextKeys.chatSessionIsEmpty.bindTo(this.contextKeyService);
|
||||
|
||||
this.viewContext = viewContext ?? {};
|
||||
|
||||
@@ -1983,6 +1985,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
}));
|
||||
const inputState = model.inputModel.state.get();
|
||||
this.input.initForNewChatModel(inputState, model.getRequests().length === 0);
|
||||
this._sessionIsEmptyContextKey.set(model.getRequests().length === 0);
|
||||
|
||||
this.refreshParsedInput();
|
||||
this.viewModelDisposables.add(model.onDidChange((e) => {
|
||||
@@ -1993,11 +1996,13 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
}
|
||||
if (e.kind === 'addRequest') {
|
||||
this.inputPart.clearTodoListWidget(this.viewModel?.sessionResource, false);
|
||||
this._sessionIsEmptyContextKey.set(false);
|
||||
}
|
||||
// Hide widget on request removal
|
||||
if (e.kind === 'removeRequest') {
|
||||
this.inputPart.clearTodoListWidget(this.viewModel?.sessionResource, true);
|
||||
this.chatSuggestNextWidget.hide();
|
||||
this._sessionIsEmptyContextKey.set((this.viewModel?.model.getRequests().length ?? 0) === 0);
|
||||
}
|
||||
// Show next steps widget when response completes (not when request starts)
|
||||
if (e.kind === 'completedRequest') {
|
||||
|
||||
@@ -759,8 +759,9 @@ have to be updated for changes to the rules above, or to support more deeply nes
|
||||
}
|
||||
|
||||
.interactive-input-part:has(.chat-editing-session > .chat-editing-session-container) .chat-input-container,
|
||||
.interactive-input-part:has(.chat-todo-list-widget-container > .chat-todo-list-widget.has-todos) .chat-input-container {
|
||||
/* Remove top border radius when editing session or todo list is present */
|
||||
.interactive-input-part:has(.chat-todo-list-widget-container > .chat-todo-list-widget.has-todos) .chat-input-container,
|
||||
.interactive-input-part:has(.chat-input-widgets-container > .chat-status-widget:not([style*="display: none"])) .chat-input-container {
|
||||
/* Remove top border radius when editing session, todo list, or status widget is present */
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
@@ -1088,6 +1089,12 @@ have to be updated for changes to the rules above, or to support more deeply nes
|
||||
background-color: var(--vscode-toolbar-hoverBackground);
|
||||
}
|
||||
|
||||
.interactive-session .interactive-input-part > .chat-input-widgets-container {
|
||||
margin-bottom: -4px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Chat Todo List Widget Container - mirrors chat-editing-session styling */
|
||||
.interactive-session .interactive-input-part > .chat-todo-list-widget-container {
|
||||
margin-bottom: -4px;
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.interactive-session .interactive-input-part > .chat-input-widgets-container .chat-status-widget {
|
||||
padding: 6px 3px 6px 3px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid var(--vscode-input-border, transparent);
|
||||
background-color: var(--vscode-editor-background);
|
||||
border-bottom: none;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.interactive-session .interactive-input-part > .chat-input-widgets-container .chat-status-widget .chat-status-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.interactive-session .interactive-input-part > .chat-input-widgets-container .chat-status-widget .chat-status-message {
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
color: var(--vscode-foreground);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.interactive-session .interactive-input-part > .chat-input-widgets-container .chat-status-widget .chat-status-action {
|
||||
flex-shrink: 0;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.interactive-session .interactive-input-part > .chat-input-widgets-container .chat-status-widget .chat-status-button {
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
min-width: unset;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.interactive-session .interactive-input-part > .chat-input-widgets-container:has(.chat-status-widget:not([style*="display: none"])) + .chat-todo-list-widget-container .chat-todo-list-widget {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
.interactive-session .interactive-input-part > .chat-input-widgets-container:has(.chat-status-widget:not([style*="display: none"])) + .chat-todo-list-widget-container:not(:has(.chat-todo-list-widget.has-todos)) + .chat-editing-session .chat-editing-session-container {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
@@ -61,6 +61,7 @@ export namespace ChatContextKeys {
|
||||
export const location = new RawContextKey<ChatAgentLocation>('chatLocation', undefined);
|
||||
export const inQuickChat = new RawContextKey<boolean>('quickChatHasFocus', false, { type: 'boolean', description: localize('inQuickChat', "True when the quick chat UI has focus, false otherwise.") });
|
||||
export const hasFileAttachments = new RawContextKey<boolean>('chatHasFileAttachments', false, { type: 'boolean', description: localize('chatHasFileAttachments', "True when the chat has file attachments.") });
|
||||
export const chatSessionIsEmpty = new RawContextKey<boolean>('chatSessionIsEmpty', true, { type: 'boolean', description: localize('chatSessionIsEmpty', "True when the current chat session has no requests.") });
|
||||
|
||||
export const remoteJobCreating = new RawContextKey<boolean>('chatRemoteJobCreating', false, { type: 'boolean', description: localize('chatRemoteJobCreating', "True when a remote coding agent job is being created.") });
|
||||
export const hasRemoteCodingAgent = new RawContextKey<boolean>('hasRemoteCodingAgent', false, localize('hasRemoteCodingAgent', "Whether any remote coding agent is available"));
|
||||
|
||||
Reference in New Issue
Block a user