Path: blob/main/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as dom from '../../../../../base/browser/dom.js';6import { createInstantHoverDelegate } from '../../../../../base/browser/ui/hover/hoverDelegateFactory.js';7import { Emitter } from '../../../../../base/common/event.js';8import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js';9import { basename } from '../../../../../base/common/path.js';10import { URI } from '../../../../../base/common/uri.js';11import { Range } from '../../../../../editor/common/core/range.js';12import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';13import { ResourceLabels } from '../../../../browser/labels.js';14import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemVariableEntry, OmittedState } from '../../common/chatVariableEntries.js';15import { ChatResponseReferencePartStatusKind, IChatContentReference } from '../../common/chatService.js';16import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from '../chatAttachmentWidgets.js';1718export class ChatAttachmentsContentPart extends Disposable {19private readonly attachedContextDisposables = this._register(new DisposableStore());2021private readonly _onDidChangeVisibility = this._register(new Emitter<boolean>());22private readonly _contextResourceLabels: ResourceLabels;2324public contextMenuHandler?: (attachment: IChatRequestVariableEntry, event: MouseEvent) => void;2526constructor(27private readonly variables: IChatRequestVariableEntry[],28private readonly contentReferences: ReadonlyArray<IChatContentReference> = [],29public readonly domNode: HTMLElement | undefined = dom.$('.chat-attached-context'),30@IInstantiationService private readonly instantiationService: IInstantiationService,31) {32super();33this._contextResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this._onDidChangeVisibility.event }));3435this.initAttachedContext(domNode);36if (!domNode.childElementCount) {37this.domNode = undefined;38}39}4041private initAttachedContext(container: HTMLElement) {42dom.clearNode(container);43this.attachedContextDisposables.clear();44const hoverDelegate = this.attachedContextDisposables.add(createInstantHoverDelegate());4546for (const attachment of this.variables) {47const resource = URI.isUri(attachment.value) ? attachment.value : attachment.value && typeof attachment.value === 'object' && 'uri' in attachment.value && URI.isUri(attachment.value.uri) ? attachment.value.uri : undefined;48const range = attachment.value && typeof attachment.value === 'object' && 'range' in attachment.value && Range.isIRange(attachment.value.range) ? attachment.value.range : undefined;49const correspondingContentReference = this.contentReferences.find((ref) => (typeof ref.reference === 'object' && 'variableName' in ref.reference && ref.reference.variableName === attachment.name) || (URI.isUri(ref.reference) && basename(ref.reference.path) === attachment.name));50const isAttachmentOmitted = correspondingContentReference?.options?.status?.kind === ChatResponseReferencePartStatusKind.Omitted;51const isAttachmentPartialOrOmitted = isAttachmentOmitted || correspondingContentReference?.options?.status?.kind === ChatResponseReferencePartStatusKind.Partial;5253let widget;54if (attachment.kind === 'tool' || attachment.kind === 'toolset') {55widget = this.instantiationService.createInstance(ToolSetOrToolItemAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate);56} else if (isElementVariableEntry(attachment)) {57widget = this.instantiationService.createInstance(ElementChatAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate);58} else if (isImageVariableEntry(attachment)) {59attachment.omittedState = isAttachmentPartialOrOmitted ? OmittedState.Full : attachment.omittedState;60widget = this.instantiationService.createInstance(ImageAttachmentWidget, resource, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate);61} else if (isPromptFileVariableEntry(attachment)) {62if (attachment.automaticallyAdded) {63continue;64}65widget = this.instantiationService.createInstance(PromptFileAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate);66} else if (isPromptTextVariableEntry(attachment)) {67if (attachment.automaticallyAdded) {68continue;69}70widget = this.instantiationService.createInstance(PromptTextAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate);71} else if (resource && (attachment.kind === 'file' || attachment.kind === 'directory')) {72widget = this.instantiationService.createInstance(FileAttachmentWidget, resource, range, attachment, correspondingContentReference, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate);73} else if (isPasteVariableEntry(attachment)) {74widget = this.instantiationService.createInstance(PasteAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate);75} else if (resource && isNotebookOutputVariableEntry(attachment)) {76widget = this.instantiationService.createInstance(NotebookCellOutputChatAttachmentWidget, resource, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate);77} else if (isSCMHistoryItemVariableEntry(attachment)) {78widget = this.instantiationService.createInstance(SCMHistoryItemAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate);79} else {80widget = this.instantiationService.createInstance(DefaultChatAttachmentWidget, resource, range, attachment, correspondingContentReference, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate);81}8283let ariaLabel: string | null = null;8485if (isAttachmentPartialOrOmitted) {86widget.element.classList.add('warning');87}88const description = correspondingContentReference?.options?.status?.description;89if (isAttachmentPartialOrOmitted) {90ariaLabel = `${ariaLabel}${description ? ` ${description}` : ''}`;91for (const selector of ['.monaco-icon-suffix-container', '.monaco-icon-name-container']) {92const element = widget.label.element.querySelector(selector);93if (element) {94element.classList.add('warning');95}96}97}9899this._register(dom.addDisposableListener(widget.element, 'contextmenu', e => this.contextMenuHandler?.(attachment, e)));100101if (this.attachedContextDisposables.isDisposed) {102widget.dispose();103return;104}105106if (ariaLabel) {107widget.element.ariaLabel = ariaLabel;108}109110this.attachedContextDisposables.add(widget);111}112}113}114115116