@@ -14,37 +14,47 @@ import { ILogService } from 'vs/platform/log/common/log';
14
14
import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents' ;
15
15
import { ChatRequestAgentPart , ChatRequestDynamicVariablePart , ChatRequestTextPart , IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes' ;
16
16
import { contentRefUrl } from '../common/annotations' ;
17
+ import { IHoverService } from 'vs/platform/hover/browser/hover' ;
18
+ import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory' ;
19
+ import { h } from 'vs/base/browser/dom' ;
20
+ import { FileAccess } from 'vs/base/common/network' ;
21
+ import { ThemeIcon } from 'vs/base/common/themables' ;
22
+ import { localize } from 'vs/nls' ;
23
+ import { showExtensionsWithIdsCommandId } from 'vs/workbench/contrib/extensions/browser/extensionsActions' ;
17
24
18
25
const variableRefUrl = 'http://_vscodedecoration_' ;
26
+ const agentRefUrl = 'http://_chatagent_' ;
19
27
20
28
export class ChatMarkdownDecorationsRenderer {
21
29
constructor (
22
30
@IKeybindingService private readonly keybindingService : IKeybindingService ,
23
31
@ILabelService private readonly labelService : ILabelService ,
24
32
@ILogService private readonly logService : ILogService ,
25
33
@IChatAgentService private readonly chatAgentService : IChatAgentService ,
34
+ @IHoverService private readonly hoverService : IHoverService ,
26
35
) { }
27
36
28
37
convertParsedRequestToMarkdown ( parsedRequest : IParsedChatRequest ) : string {
29
38
let result = '' ;
30
39
for ( const part of parsedRequest . parts ) {
31
40
if ( part instanceof ChatRequestTextPart ) {
32
41
result += part . text ;
42
+ } else if ( part instanceof ChatRequestAgentPart ) {
43
+ let text = part . text ;
44
+ const isDupe = this . chatAgentService . getAgentsByName ( part . agent . name ) . length > 1 ;
45
+ if ( isDupe ) {
46
+ text += ` (${ part . agent . extensionPublisher } )` ;
47
+ }
48
+
49
+ result += `[${ text } ](${ agentRefUrl } ?${ encodeURIComponent ( part . agent . id ) } )` ;
33
50
} else {
34
51
const uri = part instanceof ChatRequestDynamicVariablePart && part . data . map ( d => d . value ) . find ( ( d ) : d is URI => d instanceof URI )
35
52
|| undefined ;
36
53
const title = uri ? encodeURIComponent ( this . labelService . getUriLabel ( uri , { relative : true } ) ) :
37
54
part instanceof ChatRequestAgentPart ? part . agent . id :
38
55
'' ;
39
56
40
- let text = part . text ;
41
- if ( part instanceof ChatRequestAgentPart ) {
42
- const isDupe = this . chatAgentService . getAgentsByName ( part . agent . name ) . length > 1 ;
43
- if ( isDupe ) {
44
- text += ` (${ part . agent . extensionPublisher } )` ;
45
- }
46
- }
47
-
57
+ const text = part . text ;
48
58
result += `[${ text } ](${ variableRefUrl } ?${ title } )` ;
49
59
}
50
60
}
@@ -56,7 +66,12 @@ export class ChatMarkdownDecorationsRenderer {
56
66
element . querySelectorAll ( 'a' ) . forEach ( a => {
57
67
const href = a . getAttribute ( 'data-href' ) ;
58
68
if ( href ) {
59
- if ( href . startsWith ( variableRefUrl ) ) {
69
+ if ( href . startsWith ( agentRefUrl ) ) {
70
+ const title = decodeURIComponent ( href . slice ( agentRefUrl . length + 1 ) ) ;
71
+ a . parentElement ! . replaceChild (
72
+ this . renderAgentWidget ( a . textContent ! , title ) ,
73
+ a ) ;
74
+ } else if ( href . startsWith ( variableRefUrl ) ) {
60
75
const title = decodeURIComponent ( href . slice ( variableRefUrl . length + 1 ) ) ;
61
76
a . parentElement ! . replaceChild (
62
77
this . renderResourceWidget ( a . textContent ! , title ) ,
@@ -70,6 +85,58 @@ export class ChatMarkdownDecorationsRenderer {
70
85
} ) ;
71
86
}
72
87
88
+ private renderAgentWidget ( name : string , id : string ) : HTMLElement {
89
+ const agent = this . chatAgentService . getAgent ( id ) ! ;
90
+
91
+ const container = dom . $ ( 'span.chat-resource-widget' ) ;
92
+ const alias = dom . $ ( 'span' , undefined , name ) ;
93
+
94
+ const hoverElement = h (
95
+ '.chat-agent-hover@root' ,
96
+ [
97
+ h ( '.chat-agent-hover-header' , [
98
+ h ( '.chat-agent-hover-icon@icon' ) ,
99
+ h ( '.chat-agent-hover-details' , [
100
+ h ( '.chat-agent-hover-name@name' ) ,
101
+ h ( '.chat-agent-hover-extension' , [
102
+ h ( '.chat-agent-hover-extension-name@extensionName' ) ,
103
+ h ( '.chat-agent-hover-separator@separator' ) ,
104
+ h ( '.chat-agent-hover-publisher@publisher' ) ,
105
+ ] ) ,
106
+ ] ) ,
107
+ ] ) ,
108
+ h ( '.chat-agent-hover-description@description' ) ,
109
+ ] ) ;
110
+
111
+ if ( agent . metadata . icon instanceof URI ) {
112
+ const avatarIcon = dom . $ < HTMLImageElement > ( 'img.icon' ) ;
113
+ avatarIcon . src = FileAccess . uriToBrowserUri ( agent . metadata . icon ) . toString ( true ) ;
114
+ hoverElement . icon . replaceChildren ( dom . $ ( '.avatar' , undefined , avatarIcon ) ) ;
115
+ } else if ( agent . metadata . themeIcon ) {
116
+ const avatarIcon = dom . $ ( ThemeIcon . asCSSSelector ( agent . metadata . themeIcon ) ) ;
117
+ hoverElement . icon . replaceChildren ( dom . $ ( '.avatar.codicon-avatar' , undefined , avatarIcon ) ) ;
118
+ }
119
+
120
+ hoverElement . name . textContent = `@${ agent . name } ` ;
121
+ hoverElement . extensionName . textContent = agent . extensionDisplayName ;
122
+ hoverElement . separator . textContent = ' | ' ;
123
+ hoverElement . publisher . textContent = agent . extensionPublisher ;
124
+
125
+ const description = agent . description && ! agent . description . endsWith ( '.' ) ?
126
+ `${ agent . description } . ` :
127
+ ( agent . description || '' ) ;
128
+ hoverElement . description . textContent = description ;
129
+
130
+ const marketplaceLink = document . createElement ( 'a' ) ;
131
+ marketplaceLink . setAttribute ( 'href' , `command:${ showExtensionsWithIdsCommandId } ?${ encodeURIComponent ( JSON . stringify ( [ agent . extensionId . value ] ) ) } ` ) ;
132
+ marketplaceLink . textContent = localize ( 'marketplaceLabel' , "View in Marketplace" ) + '.' ;
133
+ hoverElement . description . appendChild ( marketplaceLink ) ;
134
+
135
+ this . hoverService . setupUpdatableHover ( getDefaultHoverDelegate ( 'element' ) , container , hoverElement . root ) ;
136
+ container . appendChild ( alias ) ;
137
+ return container ;
138
+ }
139
+
73
140
private renderFileWidget ( href : string , a : HTMLAnchorElement ) : void {
74
141
// TODO this can be a nicer FileLabel widget with an icon. Do a simple link for now.
75
142
const fullUri = URI . parse ( href ) ;
0 commit comments