4
4
*--------------------------------------------------------------------------------------------*/
5
5
6
6
import * as dom from '../../../../../../base/browser/dom.js' ;
7
+ import { renderMarkdown } from '../../../../../../base/browser/markdownRenderer.js' ;
7
8
import { decodeBase64 } from '../../../../../../base/common/buffer.js' ;
8
9
import { CancellationTokenSource } from '../../../../../../base/common/cancellation.js' ;
9
10
import { Codicon } from '../../../../../../base/common/codicons.js' ;
10
11
import { ThemeIcon } from '../../../../../../base/common/themables.js' ;
12
+ import { generateUuid } from '../../../../../../base/common/uuid.js' ;
11
13
import { localize } from '../../../../../../nls.js' ;
12
14
import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js' ;
13
15
import { IChatToolInvocation , IChatToolInvocationSerialized , IToolResultOutputDetailsSerialized } from '../../../common/chatService.js' ;
16
+ import { IChatViewModel } from '../../../common/chatViewModel.js' ;
14
17
import { IToolResultOutputDetails } from '../../../common/languageModelToolsService.js' ;
15
18
import { IChatCodeBlockInfo , IChatWidgetService } from '../../chat.js' ;
16
19
import { IChatOutputRendererService } from '../../chatOutputItemRenderer.js' ;
17
20
import { IChatContentPartRenderContext } from '../chatContentParts.js' ;
18
21
import { ChatCustomProgressPart } from '../chatProgressContentPart.js' ;
19
22
import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js' ;
20
23
24
+ interface OutputState {
25
+ readonly webviewOrigin : string ;
26
+ height : number ;
27
+ }
28
+
21
29
// TODO: see if we can reuse existing types instead of adding ChatToolOutputSubPart
22
30
export class ChatToolOutputSubPart extends BaseChatToolInvocationSubPart {
31
+
32
+ /** Remembers cached state on re-render */
33
+ private static readonly _cachedStates = new WeakMap < IChatViewModel | IChatToolInvocationSerialized , Map < string , OutputState > > ( ) ;
34
+
23
35
public readonly domNode : HTMLElement ;
24
36
25
37
public override readonly codeblocks : IChatCodeBlockInfo [ ] = [ ] ;
@@ -45,26 +57,58 @@ export class ChatToolOutputSubPart extends BaseChatToolInvocationSubPart {
45
57
} ,
46
58
} ;
47
59
48
- this . domNode = this . createOutputPart ( details ) ;
60
+ this . domNode = dom . $ ( 'div.tool-output-part' ) ;
61
+
62
+ const titleEl = dom . $ ( '.output-title' ) ;
63
+ this . domNode . appendChild ( titleEl ) ;
64
+ if ( typeof toolInvocation . invocationMessage === 'string' ) {
65
+ titleEl . textContent = toolInvocation . invocationMessage ;
66
+ } else {
67
+ const md = this . _register ( renderMarkdown ( toolInvocation . invocationMessage ) ) ;
68
+ titleEl . appendChild ( md . element ) ;
69
+ }
70
+
71
+ this . domNode . appendChild ( this . createOutputPart ( toolInvocation , details ) ) ;
49
72
}
50
73
51
74
public override dispose ( ) : void {
52
75
this . _disposeCts . dispose ( true ) ;
53
76
super . dispose ( ) ;
54
77
}
55
78
56
- private createOutputPart ( details : IToolResultOutputDetails ) : HTMLElement {
79
+ private createOutputPart ( toolInvocation : IChatToolInvocation | IChatToolInvocationSerialized , details : IToolResultOutputDetails ) : HTMLElement {
80
+ const vm = this . chatWidgetService . getWidgetBySessionId ( this . context . element . sessionId ) ?. viewModel ;
81
+
57
82
const parent = dom . $ ( 'div.webview-output' ) ;
58
83
parent . style . maxHeight = '80vh' ;
59
- // TODO: we should cache the height when restoring to avoid extra layout shifts
84
+
85
+ let partState : OutputState = { height : 0 , webviewOrigin : generateUuid ( ) } ;
86
+ if ( vm ) {
87
+ let allStates = ChatToolOutputSubPart . _cachedStates . get ( vm ) ;
88
+ if ( ! allStates ) {
89
+ allStates = new Map < string , OutputState > ( ) ;
90
+ ChatToolOutputSubPart . _cachedStates . set ( vm , allStates ) ;
91
+ }
92
+
93
+ const cachedState = allStates . get ( toolInvocation . toolCallId ) ;
94
+ if ( cachedState ) {
95
+ partState = cachedState ;
96
+ } else {
97
+ allStates . set ( toolInvocation . toolCallId , partState ) ;
98
+ }
99
+ }
100
+
101
+ if ( partState . height ) {
102
+ parent . style . height = `${ partState . height } px` ;
103
+ }
60
104
61
105
const progressMessage = dom . $ ( 'span' ) ;
62
106
progressMessage . textContent = localize ( 'loading' , 'Rendering tool output...' ) ;
63
107
const progressPart = this . instantiationService . createInstance ( ChatCustomProgressPart , progressMessage , ThemeIcon . modify ( Codicon . loading , 'spin' ) ) ;
64
108
parent . appendChild ( progressPart . domNode ) ;
65
109
66
110
// TODO: we also need to show the tool output in the UI
67
- this . chatOutputItemRendererService . renderOutputPart ( details . output . mimeType , details . output . value . buffer , parent , this . _disposeCts . token ) . then ( ( renderedItem ) => {
111
+ this . chatOutputItemRendererService . renderOutputPart ( details . output . mimeType , details . output . value . buffer , parent , { origin : partState . webviewOrigin } , this . _disposeCts . token ) . then ( ( renderedItem ) => {
68
112
if ( this . _disposeCts . token . isCancellationRequested ) {
69
113
return ;
70
114
}
@@ -74,8 +118,9 @@ export class ChatToolOutputSubPart extends BaseChatToolInvocationSubPart {
74
118
progressPart . domNode . remove ( ) ;
75
119
76
120
this . _onDidChangeHeight . fire ( ) ;
77
- this . _register ( renderedItem . onDidChangeHeight ( ( ) => {
121
+ this . _register ( renderedItem . onDidChangeHeight ( newHeight => {
78
122
this . _onDidChangeHeight . fire ( ) ;
123
+ partState . height = newHeight ;
79
124
} ) ) ;
80
125
81
126
this . _register ( renderedItem . webview . onDidWheel ( e => {
@@ -85,6 +130,14 @@ export class ChatToolOutputSubPart extends BaseChatToolInvocationSubPart {
85
130
stopPropagation : ( ) => { }
86
131
} ) ;
87
132
} ) ) ;
133
+
134
+ // When the webview is disconnected from the DOM due to being hidden, we need to reload it when it is shown again.
135
+ const widget = this . chatWidgetService . getWidgetBySessionId ( this . context . element . sessionId ) ;
136
+ if ( widget ) {
137
+ this . _register ( widget ?. onDidShow ( ( ) => {
138
+ renderedItem . reinitialize ( ) ;
139
+ } ) ) ;
140
+ }
88
141
} , ( error ) => {
89
142
// TODO: show error in UI too
90
143
console . error ( 'Error rendering tool output:' , error ) ;
0 commit comments