mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-17 23:35:54 +01:00
Update chat input: hide attachments bar, move/restyle context window hint (#296390)
* Simplify chat input: hide attachments bar, move context usage widget - Hide attachments bar by default, only show with manual attachments - Remove Add Files button entirely - Stop rendering implicit context pill (data still collected/sent) - Move context usage pie widget from top-right to bottom toolbar - Make input editor 2 lines tall by default (non-compact mode) * fix: chat input toolbar icons use icon-foreground instead of foreground - Move Add Context action to ChatInput menu alongside tools button - Fix theme-2026 styles.css override that stomped toolbar codicon color with --vscode-foreground; now uses --vscode-icon-foreground - Add matching icon-foreground override in chat.css for specificity safety - Switch context usage indicator from pie chart to stroke-based ring - Account for context usage widget width in toolbar layout calculation - Set explicit ordering for toolbar sections (input, context usage, execute) * fix: remove unused isAttachmentAlreadyAttached method and dead imports * fix: derive input min height from editor options instead of hardcoded values * fix: remove dead ChatInputAttachmentToolbar menu registration * fix: show attach context in execute toolbar for quick chat, not input toolbar
This commit is contained in:
@@ -492,10 +492,10 @@
|
||||
color: var(--vscode-icon-foreground) !important;
|
||||
}
|
||||
|
||||
/* Chat input toolbar icons should use proper foreground color, not the muted icon.foreground */
|
||||
/* Chat input toolbar icons should follow icon foreground token */
|
||||
.monaco-workbench .interactive-session .chat-input-toolbars .monaco-action-bar .action-item .codicon,
|
||||
.monaco-workbench .interactive-session .chat-input-toolbars .action-label .codicon {
|
||||
color: var(--vscode-foreground) !important;
|
||||
color: var(--vscode-icon-foreground) !important;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
|
||||
@@ -465,15 +465,16 @@ export class AttachContextAction extends Action2 {
|
||||
super({
|
||||
id: 'workbench.action.chat.attachContext',
|
||||
title: localize2('workbench.action.chat.attachContext.label.2', "Add Context..."),
|
||||
icon: Codicon.attach,
|
||||
icon: Codicon.add,
|
||||
category: CHAT_CATEGORY,
|
||||
keybinding: {
|
||||
when: ContextKeyExpr.and(ChatContextKeys.inChatInput, ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat)),
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Slash,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menu: {
|
||||
menu: [{
|
||||
when: ContextKeyExpr.and(
|
||||
ChatContextKeys.inQuickChat.negate(),
|
||||
ContextKeyExpr.or(
|
||||
ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat),
|
||||
ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditorInline), CTX_INLINE_CHAT_V2_ENABLED)
|
||||
@@ -483,10 +484,21 @@ export class AttachContextAction extends Action2 {
|
||||
ChatContextKeys.agentSupportsAttachments
|
||||
)
|
||||
),
|
||||
id: MenuId.ChatInputAttachmentToolbar,
|
||||
id: MenuId.ChatInput,
|
||||
group: 'navigation',
|
||||
order: 3
|
||||
},
|
||||
order: 101
|
||||
}, {
|
||||
when: ContextKeyExpr.and(
|
||||
ChatContextKeys.inQuickChat,
|
||||
ContextKeyExpr.or(
|
||||
ChatContextKeys.lockedToCodingAgent.negate(),
|
||||
ChatContextKeys.agentSupportsAttachments
|
||||
)
|
||||
),
|
||||
id: MenuId.ChatExecute,
|
||||
group: 'navigation',
|
||||
order: -1
|
||||
}],
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import * as aria from '../../../../../../base/browser/ui/aria/aria.js';
|
||||
import { ButtonWithIcon } from '../../../../../../base/browser/ui/button/button.js';
|
||||
import { createInstantHoverDelegate } from '../../../../../../base/browser/ui/hover/hoverDelegateFactory.js';
|
||||
import { HoverPosition } from '../../../../../../base/browser/ui/hover/hoverWidget.js';
|
||||
import { renderLabelWithIcons } from '../../../../../../base/browser/ui/iconLabel/iconLabels.js';
|
||||
import { IAction } from '../../../../../../base/common/actions.js';
|
||||
import { equals as arraysEqual } from '../../../../../../base/common/arrays.js';
|
||||
import { DeferredPromise, RunOnceScheduler } from '../../../../../../base/common/async.js';
|
||||
@@ -32,15 +31,13 @@ import { autorun, derived, derivedOpts, IObservable, ISettableObservable, observ
|
||||
import { isMacintosh } from '../../../../../../base/common/platform.js';
|
||||
import { isEqual } from '../../../../../../base/common/resources.js';
|
||||
import { ScrollbarVisibility } from '../../../../../../base/common/scrollable.js';
|
||||
import { assertType } from '../../../../../../base/common/types.js';
|
||||
import { URI } from '../../../../../../base/common/uri.js';
|
||||
import { IEditorConstructionOptions } from '../../../../../../editor/browser/config/editorConfiguration.js';
|
||||
import { EditorExtensionsRegistry } from '../../../../../../editor/browser/editorExtensions.js';
|
||||
import { CodeEditorWidget } from '../../../../../../editor/browser/widget/codeEditor/codeEditorWidget.js';
|
||||
import { EditorLayoutInfo, EditorOptions, IEditorOptions } from '../../../../../../editor/common/config/editorOptions.js';
|
||||
import { EditorLayoutInfo, EditorOption, EditorOptions, IEditorOptions } from '../../../../../../editor/common/config/editorOptions.js';
|
||||
import { IDimension } from '../../../../../../editor/common/core/2d/dimension.js';
|
||||
import { IPosition } from '../../../../../../editor/common/core/position.js';
|
||||
import { IRange, Range } from '../../../../../../editor/common/core/range.js';
|
||||
import { isLocation } from '../../../../../../editor/common/languages.js';
|
||||
import { ITextModel } from '../../../../../../editor/common/model.js';
|
||||
import { IModelService } from '../../../../../../editor/common/services/model.js';
|
||||
@@ -83,7 +80,7 @@ import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions, setupSimpleEd
|
||||
import { InlineChatConfigKeys } from '../../../../inlineChat/common/inlineChat.js';
|
||||
import { IChatViewTitleActionContext } from '../../../common/actions/chatActions.js';
|
||||
import { ChatContextKeys } from '../../../common/actions/chatContextKeys.js';
|
||||
import { ChatRequestVariableSet, IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeRangeVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry, isStringVariableEntry } from '../../../common/attachments/chatVariableEntries.js';
|
||||
import { ChatRequestVariableSet, IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeRangeVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry } from '../../../common/attachments/chatVariableEntries.js';
|
||||
import { ChatMode, IChatMode, IChatModeService } from '../../../common/chatModes.js';
|
||||
import { IChatFollowup, IChatQuestionCarousel, IChatService, IChatSessionContext } from '../../../common/chatService/chatService.js';
|
||||
import { agentOptionId, IChatSessionProviderOptionGroup, IChatSessionProviderOptionItem, IChatSessionsService, isIChatSessionFileChange2, localChatSessionType } from '../../../common/chatSessionsService.js';
|
||||
@@ -104,7 +101,6 @@ import { ChatAttachmentModel } from '../../attachments/chatAttachmentModel.js';
|
||||
import { IChatAttachmentWidgetRegistry } from '../../attachments/chatAttachmentWidgetRegistry.js';
|
||||
import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, SCMHistoryItemChangeAttachmentWidget, SCMHistoryItemChangeRangeAttachmentWidget, TerminalCommandAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from '../../attachments/chatAttachmentWidgets.js';
|
||||
import { ChatImplicitContexts } from '../../attachments/chatImplicitContext.js';
|
||||
import { ImplicitContextAttachmentWidget } from '../../attachments/implicitContextAttachment.js';
|
||||
import { IChatWidget, IChatWidgetViewModelChangeEvent, ISessionTypePickerDelegate, isIChatResourceViewContext, isIChatViewViewContext, IWorkspacePickerDelegate } from '../../chat.js';
|
||||
import { ChatEditingShowChangesAction, ViewAllSessionChangesAction, ViewPreviousEditsAction } from '../../chatEditing/chatEditingActions.js';
|
||||
import { resizeImage } from '../../chatImageUtils.js';
|
||||
@@ -133,6 +129,7 @@ import { EnhancedModelPickerActionItem } from './modelPickerActionItem2.js';
|
||||
const $ = dom.$;
|
||||
|
||||
const INPUT_EDITOR_MAX_HEIGHT = 250;
|
||||
const INPUT_EDITOR_MIN_VISIBLE_LINES = 2;
|
||||
const CachedLanguageModelsKey = 'chat.cachedLanguageModels.v2';
|
||||
|
||||
export interface IChatInputStyles {
|
||||
@@ -235,8 +232,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
private _onDidClickOverlay = this._register(new Emitter<void>());
|
||||
readonly onDidClickOverlay: Event<void> = this._onDidClickOverlay.event;
|
||||
|
||||
private readonly _implicitContextWidget: MutableDisposable<ImplicitContextAttachmentWidget> = this._register(new MutableDisposable<ImplicitContextAttachmentWidget>());
|
||||
|
||||
private readonly _attachmentModel: ChatAttachmentModel;
|
||||
private _widget?: IChatWidget;
|
||||
public get attachmentModel(): ChatAttachmentModel {
|
||||
@@ -332,8 +327,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
private executeToolbar!: MenuWorkbenchToolBar;
|
||||
private inputActionsToolbar!: MenuWorkbenchToolBar;
|
||||
|
||||
private addFilesToolbar: MenuWorkbenchToolBar | undefined;
|
||||
private addFilesButton: AddFilesButton | undefined;
|
||||
|
||||
|
||||
get inputEditor() {
|
||||
return this._inputEditor;
|
||||
@@ -1944,13 +1938,13 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
dom.h('.chat-getting-started-tip-container@chatGettingStartedTipContainer'),
|
||||
dom.h('.interactive-input-and-side-toolbar@inputAndSideToolbar', [
|
||||
dom.h('.chat-input-container@inputContainer', [
|
||||
dom.h('.chat-context-usage-container@contextUsageWidgetContainer'),
|
||||
dom.h('.chat-editor-container@editorContainer'),
|
||||
dom.h('.chat-input-toolbars@inputToolbars'),
|
||||
dom.h('.chat-input-toolbars@inputToolbars', [
|
||||
dom.h('.chat-context-usage-container@contextUsageWidgetContainer'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
dom.h('.chat-attachments-container@attachmentsContainer', [
|
||||
dom.h('.chat-attachment-toolbar@attachmentToolbar'),
|
||||
dom.h('.chat-attached-context@attachedContextContainer'),
|
||||
]),
|
||||
dom.h('.interactive-input-followups@followupsContainer'),
|
||||
@@ -1966,13 +1960,13 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
dom.h('.chat-getting-started-tip-container@chatGettingStartedTipContainer'),
|
||||
dom.h('.interactive-input-and-side-toolbar@inputAndSideToolbar', [
|
||||
dom.h('.chat-input-container@inputContainer', [
|
||||
dom.h('.chat-context-usage-container@contextUsageWidgetContainer'),
|
||||
dom.h('.chat-attachments-container@attachmentsContainer', [
|
||||
dom.h('.chat-attachment-toolbar@attachmentToolbar'),
|
||||
dom.h('.chat-attached-context@attachedContextContainer'),
|
||||
]),
|
||||
dom.h('.chat-editor-container@editorContainer'),
|
||||
dom.h('.chat-input-toolbars@inputToolbars'),
|
||||
dom.h('.chat-input-toolbars@inputToolbars', [
|
||||
dom.h('.chat-context-usage-container@contextUsageWidgetContainer'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
@@ -1995,7 +1989,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
this.attachmentsContainer = elements.attachmentsContainer;
|
||||
this.attachedContextContainer = elements.attachedContextContainer;
|
||||
const toolbarsContainer = elements.inputToolbars;
|
||||
const attachmentToolbarContainer = elements.attachmentToolbar;
|
||||
this.chatEditingSessionWidgetContainer = elements.chatEditingSessionWidgetContainer;
|
||||
this.chatInputTodoListWidgetContainer = elements.chatInputTodoListWidgetContainer;
|
||||
this.chatGettingStartedTipContainer = elements.chatGettingStartedTipContainer;
|
||||
@@ -2004,7 +1997,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
this.chatInputWidgetsContainer = elements.chatInputWidgetsContainer;
|
||||
this.contextUsageWidgetContainer = elements.contextUsageWidgetContainer;
|
||||
|
||||
// Context usage widget
|
||||
// Context usage widget — will be positioned in the toolbar after toolbars are created
|
||||
this.contextUsageWidget = this._register(this.instantiationService.createInstance(ChatContextUsageWidget));
|
||||
this.contextUsageWidgetContainer.appendChild(this.contextUsageWidget.domNode);
|
||||
|
||||
@@ -2337,23 +2330,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
this.renderAttachedContext();
|
||||
}));
|
||||
|
||||
this.addFilesToolbar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, attachmentToolbarContainer, MenuId.ChatInputAttachmentToolbar, {
|
||||
telemetrySource: this.options.menus.telemetrySource,
|
||||
label: true,
|
||||
menuOptions: { shouldForwardArgs: true, renderShortTitle: true },
|
||||
hiddenItemStrategy: HiddenItemStrategy.NoHide,
|
||||
hoverDelegate,
|
||||
actionViewItemProvider: (action, options) => {
|
||||
if (action.id === 'workbench.action.chat.attachContext') {
|
||||
const viewItem = this.instantiationService.createInstance(AddFilesButton, this._attachmentModel, action, options);
|
||||
viewItem.setShowLabel(this._attachmentModel.size === 0 && !this._implicitContextWidget.value?.hasRenderedContexts);
|
||||
this.addFilesButton = viewItem;
|
||||
return this.addFilesButton;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}));
|
||||
this.addFilesToolbar.context = { widget, placeholder: localize('chatAttachFiles', 'Search for files and context to add to your request') };
|
||||
this.renderAttachedContext();
|
||||
|
||||
const inputResizeObserver = this._register(new dom.DisposableResizeObserver(() => {
|
||||
@@ -2400,15 +2376,14 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
}));
|
||||
|
||||
const attachments = [...this.attachmentModel.attachments.entries()];
|
||||
const hasAttachments = Boolean(attachments.length) || Boolean(this.implicitContext?.hasValue);
|
||||
dom.setVisibility(Boolean(this.options.renderInputToolbarBelowInput || hasAttachments || (this.addFilesToolbar && !this.addFilesToolbar.isEmpty())), this.attachmentsContainer);
|
||||
const hasAttachments = Boolean(attachments.length);
|
||||
dom.setVisibility(Boolean(this.options.renderInputToolbarBelowInput || hasAttachments), this.attachmentsContainer);
|
||||
dom.setVisibility(hasAttachments, this.attachedContextContainer);
|
||||
if (!attachments.length) {
|
||||
this._indexOfLastAttachedContextDeletedWithKeyboard = -1;
|
||||
this._indexOfLastOpenedContext = -1;
|
||||
}
|
||||
|
||||
const isSuggestedEnabled = this.configurationService.getValue<boolean>('chat.implicitContext.suggestedContext');
|
||||
|
||||
for (const [index, attachment] of attachments) {
|
||||
const resource = URI.isUri(attachment.value) ? attachment.value : isLocation(attachment.value) ? attachment.value.uri : undefined;
|
||||
@@ -2465,54 +2440,9 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
}));
|
||||
}
|
||||
|
||||
if (isSuggestedEnabled && this.implicitContext?.hasValue) {
|
||||
this._implicitContextWidget.value = this.instantiationService.createInstance(ImplicitContextAttachmentWidget, () => this._widget, (targetUri: URI | undefined, targetRange: IRange | undefined, targetHandle: number | undefined) => this.isAttachmentAlreadyAttached(targetUri, targetRange, targetHandle, attachments.map(([, a]) => a)), this.implicitContext, this._contextResourceLabels, this._attachmentModel, container);
|
||||
} else {
|
||||
this._implicitContextWidget.clear();
|
||||
}
|
||||
|
||||
this.addFilesButton?.setShowLabel(this._attachmentModel.size === 0 && !this._implicitContextWidget.value?.hasRenderedContexts);
|
||||
|
||||
this._indexOfLastOpenedContext = -1;
|
||||
}
|
||||
|
||||
private isAttachmentAlreadyAttached(targetUri: URI | undefined, targetRange: IRange | undefined, targetHandle: number | undefined, attachments: IChatRequestVariableEntry[]): boolean {
|
||||
return attachments.some((attachment) => {
|
||||
let uri: URI | undefined;
|
||||
let range: IRange | undefined;
|
||||
let handle: number | undefined;
|
||||
|
||||
if (URI.isUri(attachment.value)) {
|
||||
uri = attachment.value;
|
||||
} else if (isLocation(attachment.value)) {
|
||||
uri = attachment.value.uri;
|
||||
range = attachment.value.range;
|
||||
} else if (isStringVariableEntry(attachment)) {
|
||||
uri = attachment.uri;
|
||||
handle = attachment.handle;
|
||||
}
|
||||
|
||||
if ((handle !== undefined && targetHandle === undefined) || (handle === undefined && targetHandle !== undefined)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (handle !== undefined && targetHandle !== undefined && handle !== targetHandle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!uri || !isEqual(uri, targetUri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if the exact range is already attached
|
||||
if (targetRange) {
|
||||
return range && Range.equalsRange(range, targetRange);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private handleAttachmentDeletion(e: KeyboardEvent | unknown, index: number, attachment: IChatRequestVariableEntry) {
|
||||
// Set focus to the next attached context item if deletion was triggered by a keystroke (vs a mouse click)
|
||||
if (dom.isKeyboardEvent(e)) {
|
||||
@@ -2555,20 +2485,12 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const toolbar = this.addFilesToolbar?.getElement().querySelector('.action-label');
|
||||
if (!toolbar) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const attachments = Array.from(this.attachedContextContainer.querySelectorAll('.chat-attached-context-attachment'));
|
||||
if (!attachments.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
attachments.unshift(toolbar);
|
||||
|
||||
const activeElement = dom.getWindow(this.attachmentsContainer).document.activeElement;
|
||||
const currentIndex = attachments.findIndex(attachment => attachment === activeElement);
|
||||
let newIndex = currentIndex;
|
||||
@@ -3029,7 +2951,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
|
||||
const initialEditorScrollWidth = this._inputEditor.getScrollWidth();
|
||||
const newEditorWidth = width - data.inputPartHorizontalPadding - data.editorBorder - data.inputPartHorizontalPaddingInside - data.toolbarsWidth - data.sideToolbarWidth;
|
||||
const inputEditorHeight = Math.min(this._inputEditor.getContentHeight(), this.inputEditorMaxHeight);
|
||||
const lineHeight = this._inputEditor.getOption(EditorOption.lineHeight);
|
||||
const { top, bottom } = this._inputEditor.getOption(EditorOption.padding);
|
||||
const inputEditorMinHeight = this.options.renderStyle === 'compact' ? 0 : (lineHeight * INPUT_EDITOR_MIN_VISIBLE_LINES) + top + bottom;
|
||||
const inputEditorHeight = Math.max(inputEditorMinHeight, Math.min(this._inputEditor.getContentHeight(), this.inputEditorMaxHeight));
|
||||
const newDimension = { width: newEditorWidth, height: inputEditorHeight };
|
||||
if (!this.previousInputEditorDimension || (this.previousInputEditorDimension.width !== newDimension.width || this.previousInputEditorDimension.height !== newDimension.height)) {
|
||||
// This layout call has side-effects that are hard to understand. eg if we are calling this inside a onDidChangeContent handler, this can trigger the next onDidChangeContent handler
|
||||
@@ -3063,7 +2988,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
const inputToolbarWidth = this.cachedInputToolbarWidth = this.inputActionsToolbar.getItemsWidth();
|
||||
const executeToolbarPadding = (this.executeToolbar.getItemsLength() - 1) * 4;
|
||||
const inputToolbarPadding = this.inputActionsToolbar.getItemsLength() ? (this.inputActionsToolbar.getItemsLength() - 1) * 4 : 0;
|
||||
return executeToolbarWidth + executeToolbarPadding + (this.options.renderInputToolbarBelowInput ? 0 : inputToolbarWidth + inputToolbarPadding);
|
||||
const contextUsageWidth = dom.getTotalWidth(this.contextUsageWidgetContainer);
|
||||
return executeToolbarWidth + executeToolbarPadding + contextUsageWidth + (this.options.renderInputToolbarBelowInput ? 0 : inputToolbarWidth + inputToolbarPadding);
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -3152,37 +3078,3 @@ class ChatSessionPickersContainerActionItem extends ActionViewItem {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class AddFilesButton extends ActionViewItem {
|
||||
private showLabel: boolean | undefined;
|
||||
|
||||
constructor(context: unknown, action: IAction, options: IActionViewItemOptions) {
|
||||
super(context, action, {
|
||||
...options,
|
||||
icon: false,
|
||||
label: true,
|
||||
keybindingNotRenderedWithLabel: true,
|
||||
});
|
||||
}
|
||||
|
||||
public setShowLabel(show: boolean): void {
|
||||
this.showLabel = show;
|
||||
this.updateLabel();
|
||||
}
|
||||
|
||||
override render(container: HTMLElement): void {
|
||||
container.classList.add('chat-attachment-button');
|
||||
super.render(container);
|
||||
this.updateLabel();
|
||||
}
|
||||
|
||||
protected override updateLabel(): void {
|
||||
if (!this.label) {
|
||||
return;
|
||||
}
|
||||
assertType(this.label);
|
||||
this.label.classList.toggle('has-label', this.showLabel);
|
||||
const message = this.showLabel ? `$(attach) ${this.action.label}` : `$(attach)`;
|
||||
dom.reset(this.label, ...renderLabelWithIcons(message));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -804,17 +804,16 @@ have to be updated for changes to the rules above, or to support more deeply nes
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Context usage widget container - positioned at top right of chat input */
|
||||
.interactive-session .chat-input-container .chat-context-usage-container {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 6px;
|
||||
z-index: 1;
|
||||
/* Context usage widget container - positioned in the bottom toolbar */
|
||||
.interactive-session .chat-input-toolbars .chat-context-usage-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
/* Hide context usage widget in compact mode (quick chat) */
|
||||
.interactive-session .interactive-input-part.compact .chat-context-usage-container {
|
||||
display: none;
|
||||
.interactive-session .chat-input-toolbars > .chat-execute-toolbar {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.interactive-input-part:has(.chat-editing-session > .chat-editing-session-container) .chat-input-container,
|
||||
@@ -1007,7 +1006,6 @@ have to be updated for changes to the rules above, or to support more deeply nes
|
||||
flex-direction: row;
|
||||
gap: 4px;
|
||||
margin-top: 6px;
|
||||
margin-right: 20px;
|
||||
flex-wrap: wrap;
|
||||
cursor: default;
|
||||
}
|
||||
@@ -1302,6 +1300,8 @@ have to be updated for changes to the rules above, or to support more deeply nes
|
||||
|
||||
.interactive-session .chat-input-toolbars {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.interactive-session .chat-input-toolbars :not(.responsive.chat-input-toolbar) .actions-container:first-child {
|
||||
@@ -1321,9 +1321,15 @@ have to be updated for changes to the rules above, or to support more deeply nes
|
||||
}
|
||||
|
||||
.interactive-session .chat-input-toolbars > .chat-input-toolbar {
|
||||
order: 0;
|
||||
overflow: hidden;
|
||||
min-width: 0px;
|
||||
width: 100%;
|
||||
color: var(--vscode-icon-foreground);
|
||||
|
||||
.monaco-action-bar .action-item .codicon {
|
||||
color: var(--vscode-icon-foreground);
|
||||
}
|
||||
|
||||
.chat-input-picker-item {
|
||||
min-width: 0px;
|
||||
@@ -1382,7 +1388,7 @@ have to be updated for changes to the rules above, or to support more deeply nes
|
||||
padding: 3px 0px 3px 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--vscode-foreground);
|
||||
color: var(--vscode-icon-foreground);
|
||||
}
|
||||
|
||||
.monaco-workbench .interactive-session .chat-input-toolbar .chat-input-picker-item .action-label .codicon-chevron-down,
|
||||
@@ -1401,6 +1407,16 @@ have to be updated for changes to the rules above, or to support more deeply nes
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .interactive-session .chat-input-toolbars .monaco-action-bar .action-item .codicon,
|
||||
.monaco-workbench .interactive-session .chat-input-toolbars .action-label .codicon {
|
||||
color: var(--vscode-icon-foreground) !important;
|
||||
}
|
||||
|
||||
.monaco-workbench .interactive-session .chat-input-toolbars .monaco-action-bar .action-item.disabled .codicon,
|
||||
.monaco-workbench .interactive-session .chat-input-toolbars .action-label.disabled .codicon {
|
||||
color: var(--vscode-disabledForeground) !important;
|
||||
}
|
||||
|
||||
.action-widget .monaco-list-row.chat-model-picker-unavailable .description a,
|
||||
.action-widget .monaco-list-row.chat-model-picker-unavailable .description a:visited {
|
||||
color: var(--vscode-textLink-foreground);
|
||||
@@ -1593,7 +1609,6 @@ have to be updated for changes to the rules above, or to support more deeply nes
|
||||
padding: 8px 0 0 0
|
||||
}
|
||||
|
||||
.action-item.chat-attachment-button .action-label,
|
||||
.interactive-session .chat-attached-context .chat-attached-context-attachment {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
@@ -1606,40 +1621,11 @@ have to be updated for changes to the rules above, or to support more deeply nes
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.action-item.chat-attachment-button .action-label {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.interactive-session .interactive-list .chat-attached-context .chat-attached-context-attachment {
|
||||
font-family: var(--vscode-chat-font-family, inherit);
|
||||
font-size: var(--vscode-chat-font-size-body-xs);
|
||||
}
|
||||
|
||||
.action-item.chat-attachment-button > .action-label > .codicon {
|
||||
font-size: 14px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.interactive-session .action-item.chat-attachment-button .action-label:not(.has-label) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2px 4px 2px 4px;
|
||||
height: fit-content;
|
||||
gap: 0;
|
||||
border: 1px solid var(--vscode-chat-requestBorder, var(--vscode-input-background, transparent));
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.codicon {
|
||||
width: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-item.chat-attachment-button .codicon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.action-item.chat-mcp {
|
||||
min-width: 22px !important;
|
||||
|
||||
@@ -1742,11 +1728,6 @@ have to be updated for changes to the rules above, or to support more deeply nes
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.interactive-session .chat-attachment-toolbar .actions-container {
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.interactive-session .interactive-input-part.compact .chat-attached-context {
|
||||
padding-bottom: 0px;
|
||||
display: flex;
|
||||
|
||||
@@ -27,71 +27,55 @@ import { KeyCode } from '../../../../../../base/common/keyCodes.js';
|
||||
const $ = dom.$;
|
||||
|
||||
/**
|
||||
* A reusable circular progress indicator that displays a pie chart.
|
||||
* The pie fills clockwise from the top based on the percentage value.
|
||||
* A reusable circular progress indicator that displays a ring.
|
||||
* The ring fills clockwise from the top based on the percentage value.
|
||||
*/
|
||||
export class CircularProgressIndicator {
|
||||
|
||||
readonly domNode: SVGSVGElement;
|
||||
|
||||
private readonly progressPie: SVGPathElement;
|
||||
private readonly progressCircle: SVGCircleElement;
|
||||
private readonly circumference: number;
|
||||
|
||||
private static readonly CENTER_X = 18;
|
||||
private static readonly CENTER_Y = 18;
|
||||
private static readonly RADIUS = 16;
|
||||
private static readonly RADIUS = 14;
|
||||
|
||||
constructor() {
|
||||
const r = CircularProgressIndicator.RADIUS;
|
||||
this.circumference = 2 * Math.PI * r;
|
||||
|
||||
this.domNode = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
this.domNode.setAttribute('viewBox', '0 0 36 36');
|
||||
this.domNode.classList.add('circular-progress');
|
||||
|
||||
// Background circle (outline only)
|
||||
// Background circle
|
||||
const bgCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
||||
bgCircle.setAttribute('cx', String(CircularProgressIndicator.CENTER_X));
|
||||
bgCircle.setAttribute('cy', String(CircularProgressIndicator.CENTER_Y));
|
||||
bgCircle.setAttribute('r', String(CircularProgressIndicator.RADIUS));
|
||||
bgCircle.setAttribute('r', String(r));
|
||||
bgCircle.classList.add('progress-bg');
|
||||
this.domNode.appendChild(bgCircle);
|
||||
|
||||
// Progress pie (filled arc)
|
||||
this.progressPie = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
this.progressPie.classList.add('progress-pie');
|
||||
this.domNode.appendChild(this.progressPie);
|
||||
// Progress arc (stroke-based ring)
|
||||
this.progressCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
||||
this.progressCircle.setAttribute('cx', String(CircularProgressIndicator.CENTER_X));
|
||||
this.progressCircle.setAttribute('cy', String(CircularProgressIndicator.CENTER_Y));
|
||||
this.progressCircle.setAttribute('r', String(r));
|
||||
this.progressCircle.classList.add('progress-arc');
|
||||
this.progressCircle.setAttribute('stroke-dasharray', String(this.circumference));
|
||||
this.progressCircle.setAttribute('stroke-dashoffset', String(this.circumference));
|
||||
this.domNode.appendChild(this.progressCircle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the pie chart to display the given percentage (0-100).
|
||||
* @param percentage The percentage of the pie to fill (clamped to 0-100)
|
||||
* Updates the ring to display the given percentage (0-100).
|
||||
* @param percentage The percentage of the ring to fill (clamped to 0-100)
|
||||
*/
|
||||
setProgress(percentage: number): void {
|
||||
const cx = CircularProgressIndicator.CENTER_X;
|
||||
const cy = CircularProgressIndicator.CENTER_Y;
|
||||
const r = CircularProgressIndicator.RADIUS;
|
||||
|
||||
if (percentage >= 100) {
|
||||
// Full circle - use a circle element's path equivalent
|
||||
this.progressPie.setAttribute('d', `M ${cx} ${cy - r} A ${r} ${r} 0 1 1 ${cx - 0.001} ${cy - r} Z`);
|
||||
} else if (percentage <= 0) {
|
||||
// Empty - no path
|
||||
this.progressPie.setAttribute('d', '');
|
||||
} else {
|
||||
// Calculate the arc endpoint
|
||||
const angle = (percentage / 100) * 360;
|
||||
const radians = (angle - 90) * (Math.PI / 180); // Start from top (-90 degrees)
|
||||
const x = cx + r * Math.cos(radians);
|
||||
const y = cy + r * Math.sin(radians);
|
||||
const largeArcFlag = angle > 180 ? 1 : 0;
|
||||
|
||||
// Create pie slice path: move to center, line to top, arc to endpoint, close
|
||||
const d = [
|
||||
`M ${cx} ${cy}`, // Move to center
|
||||
`L ${cx} ${cy - r}`, // Line to top
|
||||
`A ${r} ${r} 0 ${largeArcFlag} 1 ${x} ${y}`, // Arc to endpoint
|
||||
'Z' // Close path back to center
|
||||
].join(' ');
|
||||
|
||||
this.progressPie.setAttribute('d', d);
|
||||
}
|
||||
const clamped = Math.max(0, Math.min(100, percentage));
|
||||
const offset = this.circumference - (clamped / 100) * this.circumference;
|
||||
this.progressCircle.setAttribute('stroke-dashoffset', String(offset));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
padding: 3px;
|
||||
@@ -19,8 +20,8 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -46,22 +47,26 @@
|
||||
}
|
||||
|
||||
.chat-context-usage-widget .progress-bg {
|
||||
fill: transparent;
|
||||
stroke: var(--vscode-icon-foreground);
|
||||
stroke-width: 2;
|
||||
opacity: 0.4;
|
||||
fill: none;
|
||||
stroke: var(--vscode-disabledForeground);
|
||||
stroke-width: 4;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.chat-context-usage-widget .progress-pie {
|
||||
fill: var(--vscode-icon-foreground);
|
||||
opacity: 0.8;
|
||||
.chat-context-usage-widget .progress-arc {
|
||||
fill: none;
|
||||
stroke: var(--vscode-descriptionForeground);
|
||||
stroke-width: 4;
|
||||
stroke-linecap: round;
|
||||
transform: rotate(-90deg);
|
||||
transform-origin: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.chat-context-usage-widget.warning .progress-pie {
|
||||
fill: var(--vscode-editorWarning-foreground);
|
||||
.chat-context-usage-widget.warning .progress-arc {
|
||||
stroke: var(--vscode-editorWarning-foreground);
|
||||
}
|
||||
|
||||
.chat-context-usage-widget.error .progress-pie {
|
||||
fill: var(--vscode-editorError-foreground);
|
||||
.chat-context-usage-widget.error .progress-arc {
|
||||
stroke: var(--vscode-editorError-foreground);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user