mirror of
https://github.com/microsoft/vscode.git
synced 2026-08-05 21:07:16 +01:00
Agent hover basically works
This commit is contained in:
@@ -14,8 +14,16 @@ import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||
import { ChatRequestAgentPart, ChatRequestDynamicVariablePart, ChatRequestTextPart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||
import { contentRefUrl } from '../common/annotations';
|
||||
import { IHoverService } from 'vs/platform/hover/browser/hover';
|
||||
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory';
|
||||
import { h } from 'vs/base/browser/dom';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { ThemeIcon } from 'vs/base/common/themables';
|
||||
import { localize } from 'vs/nls';
|
||||
import { showExtensionsWithIdsCommandId } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
|
||||
const variableRefUrl = 'http://_vscodedecoration_';
|
||||
const agentRefUrl = 'http://_chatagent_';
|
||||
|
||||
export class ChatMarkdownDecorationsRenderer {
|
||||
constructor(
|
||||
@@ -23,6 +31,7 @@ export class ChatMarkdownDecorationsRenderer {
|
||||
@ILabelService private readonly labelService: ILabelService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IChatAgentService private readonly chatAgentService: IChatAgentService,
|
||||
@IHoverService private readonly hoverService: IHoverService,
|
||||
) { }
|
||||
|
||||
convertParsedRequestToMarkdown(parsedRequest: IParsedChatRequest): string {
|
||||
@@ -30,6 +39,14 @@ export class ChatMarkdownDecorationsRenderer {
|
||||
for (const part of parsedRequest.parts) {
|
||||
if (part instanceof ChatRequestTextPart) {
|
||||
result += part.text;
|
||||
} else if (part instanceof ChatRequestAgentPart) {
|
||||
let text = part.text;
|
||||
const isDupe = this.chatAgentService.getAgentsByName(part.agent.name).length > 1;
|
||||
if (isDupe) {
|
||||
text += ` (${part.agent.extensionPublisher})`;
|
||||
}
|
||||
|
||||
result += `[${text}](${agentRefUrl}?${encodeURIComponent(part.agent.id)})`;
|
||||
} else {
|
||||
const uri = part instanceof ChatRequestDynamicVariablePart && part.data.map(d => d.value).find((d): d is URI => d instanceof URI)
|
||||
|| undefined;
|
||||
@@ -37,14 +54,7 @@ export class ChatMarkdownDecorationsRenderer {
|
||||
part instanceof ChatRequestAgentPart ? part.agent.id :
|
||||
'';
|
||||
|
||||
let text = part.text;
|
||||
if (part instanceof ChatRequestAgentPart) {
|
||||
const isDupe = this.chatAgentService.getAgentsByName(part.agent.name).length > 1;
|
||||
if (isDupe) {
|
||||
text += ` (${part.agent.extensionPublisher})`;
|
||||
}
|
||||
}
|
||||
|
||||
const text = part.text;
|
||||
result += `[${text}](${variableRefUrl}?${title})`;
|
||||
}
|
||||
}
|
||||
@@ -56,7 +66,12 @@ export class ChatMarkdownDecorationsRenderer {
|
||||
element.querySelectorAll('a').forEach(a => {
|
||||
const href = a.getAttribute('data-href');
|
||||
if (href) {
|
||||
if (href.startsWith(variableRefUrl)) {
|
||||
if (href.startsWith(agentRefUrl)) {
|
||||
const title = decodeURIComponent(href.slice(agentRefUrl.length + 1));
|
||||
a.parentElement!.replaceChild(
|
||||
this.renderAgentWidget(a.textContent!, title),
|
||||
a);
|
||||
} else if (href.startsWith(variableRefUrl)) {
|
||||
const title = decodeURIComponent(href.slice(variableRefUrl.length + 1));
|
||||
a.parentElement!.replaceChild(
|
||||
this.renderResourceWidget(a.textContent!, title),
|
||||
@@ -70,6 +85,58 @@ export class ChatMarkdownDecorationsRenderer {
|
||||
});
|
||||
}
|
||||
|
||||
private renderAgentWidget(name: string, id: string): HTMLElement {
|
||||
const agent = this.chatAgentService.getAgent(id)!;
|
||||
|
||||
const container = dom.$('span.chat-resource-widget');
|
||||
const alias = dom.$('span', undefined, name);
|
||||
|
||||
const hoverElement = h(
|
||||
'.chat-agent-hover@root',
|
||||
[
|
||||
h('.chat-agent-hover-header', [
|
||||
h('.chat-agent-hover-icon@icon'),
|
||||
h('.chat-agent-hover-details', [
|
||||
h('.chat-agent-hover-name@name'),
|
||||
h('.chat-agent-hover-extension', [
|
||||
h('.chat-agent-hover-extension-name@extensionName'),
|
||||
h('.chat-agent-hover-separator@separator'),
|
||||
h('.chat-agent-hover-publisher@publisher'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
h('.chat-agent-hover-description@description'),
|
||||
]);
|
||||
|
||||
if (agent.metadata.icon instanceof URI) {
|
||||
const avatarIcon = dom.$<HTMLImageElement>('img.icon');
|
||||
avatarIcon.src = FileAccess.uriToBrowserUri(agent.metadata.icon).toString(true);
|
||||
hoverElement.icon.replaceChildren(dom.$('.avatar', undefined, avatarIcon));
|
||||
} else if (agent.metadata.themeIcon) {
|
||||
const avatarIcon = dom.$(ThemeIcon.asCSSSelector(agent.metadata.themeIcon));
|
||||
hoverElement.icon.replaceChildren(dom.$('.avatar.codicon-avatar', undefined, avatarIcon));
|
||||
}
|
||||
|
||||
hoverElement.name.textContent = `@${agent.name}`;
|
||||
hoverElement.extensionName.textContent = agent.extensionDisplayName;
|
||||
hoverElement.separator.textContent = ' | ';
|
||||
hoverElement.publisher.textContent = agent.extensionPublisher;
|
||||
|
||||
const description = agent.description && !agent.description.endsWith('.') ?
|
||||
`${agent.description}. ` :
|
||||
(agent.description || '');
|
||||
hoverElement.description.textContent = description;
|
||||
|
||||
const marketplaceLink = document.createElement('a');
|
||||
marketplaceLink.setAttribute('href', `command:${showExtensionsWithIdsCommandId}?${encodeURIComponent(JSON.stringify([agent.extensionId.value]))}`);
|
||||
marketplaceLink.textContent = localize('marketplaceLabel', "View in Marketplace") + '.';
|
||||
hoverElement.description.appendChild(marketplaceLink);
|
||||
|
||||
this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('element'), container, hoverElement.root);
|
||||
container.appendChild(alias);
|
||||
return container;
|
||||
}
|
||||
|
||||
private renderFileWidget(href: string, a: HTMLAnchorElement): void {
|
||||
// TODO this can be a nicer FileLabel widget with an icon. Do a simple link for now.
|
||||
const fullUri = URI.parse(href);
|
||||
|
||||
@@ -203,6 +203,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
|
||||
{
|
||||
extensionId: extension.description.identifier,
|
||||
extensionPublisher: extension.description.publisherDisplayName ?? extension.description.publisher, // May not be present in OSS
|
||||
extensionDisplayName: extension.description.displayName ?? extension.description.name, // ?
|
||||
id: providerDescriptor.id,
|
||||
description: providerDescriptor.description,
|
||||
metadata: {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { isEqual } from 'vs/base/common/resources';
|
||||
import { isDefined } from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import 'vs/css!./media/chat';
|
||||
import 'vs/css!./media/chatHover';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
outline: 1px solid var(--vscode-chat-requestBorder)
|
||||
outline: 1px solid var(--vscode-chat-requestBorder);
|
||||
}
|
||||
|
||||
.interactive-item-container .header .avatar.codicon-avatar {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.chat-agent-hover {
|
||||
line-height: unset;
|
||||
padding: 6px 0px;
|
||||
}
|
||||
|
||||
.chat-agent-hover-header {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.chat-agent-hover-icon img,
|
||||
.chat-agent-hover-icon .codicon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
outline: 1px solid var(--vscode-chat-requestBorder);
|
||||
}
|
||||
|
||||
.chat-agent-hover-icon .codicon {
|
||||
font-size: 23px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chat-agent-hover-header .chat-agent-hover-name {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.chat-agent-hover-extension {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.chat-agent-hover-separator {
|
||||
opacity: 0.7;
|
||||
margin: 0px 6px;
|
||||
}
|
||||
|
||||
.chat-agent-hover-description {
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -52,6 +52,7 @@ export interface IChatAgentData {
|
||||
description?: string;
|
||||
extensionId: ExtensionIdentifier;
|
||||
extensionPublisher: string;
|
||||
extensionDisplayName: string;
|
||||
/** The agent invoked when no agent is specified */
|
||||
isDefault?: boolean;
|
||||
metadata: IChatAgentMetadata;
|
||||
@@ -317,6 +318,7 @@ export class MergedChatAgent implements IChatAgent {
|
||||
get description(): string { return this.data.description ?? ''; }
|
||||
get extensionId(): ExtensionIdentifier { return this.data.extensionId; }
|
||||
get extensionPublisher(): string { return this.data.extensionPublisher; }
|
||||
get extensionDisplayName(): string { return this.data.extensionDisplayName; }
|
||||
get isDefault(): boolean | undefined { return this.data.isDefault; }
|
||||
get metadata(): IChatAgentMetadata { return this.data.metadata; }
|
||||
get slashCommands(): IChatAgentCommand[] { return this.data.slashCommands; }
|
||||
|
||||
@@ -2975,7 +2975,8 @@ CommandsRegistry.registerCommand('workbench.extensions.action.showExtensionsForL
|
||||
});
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand('workbench.extensions.action.showExtensionsWithIds', function (accessor: ServicesAccessor, extensionIds: string[]) {
|
||||
export const showExtensionsWithIdsCommandId = 'workbench.extensions.action.showExtensionsWithIds';
|
||||
CommandsRegistry.registerCommand(showExtensionsWithIdsCommandId, function (accessor: ServicesAccessor, extensionIds: string[]) {
|
||||
const paneCompositeService = accessor.get(IPaneCompositePartService);
|
||||
|
||||
return paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar, true)
|
||||
|
||||
Reference in New Issue
Block a user