@@ -10,6 +10,7 @@ import { Action, Separator } from '../../../../../base/common/actions.js';
1010import { Emitter , Event } from '../../../../../base/common/event.js' ;
1111import { IMarkdownString , MarkdownString } from '../../../../../base/common/htmlContent.js' ;
1212import { Disposable , DisposableStore , MutableDisposable } from '../../../../../base/common/lifecycle.js' ;
13+ import type { ThemeIcon } from '../../../../../base/common/themables.js' ;
1314import { IMarkdownRenderResult , MarkdownRenderer , openLinkFromMarkdown } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js' ;
1415import { localize } from '../../../../../nls.js' ;
1516import { MenuWorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js' ;
@@ -106,7 +107,7 @@ export class ChatQueryTitlePart extends Disposable {
106107 }
107108}
108109
109- abstract class BaseChatConfirmationWidget extends Disposable {
110+ abstract class BaseSimpleChatConfirmationWidget extends Disposable {
110111 private _onDidClick = this . _register ( new Emitter < IChatConfirmationButton > ( ) ) ;
111112 get onDidClick ( ) : Event < IChatConfirmationButton > { return this . _onDidClick . event ; }
112113
@@ -269,7 +270,9 @@ abstract class BaseChatConfirmationWidget extends Disposable {
269270 }
270271 }
271272}
272- export class ChatConfirmationWidget extends BaseChatConfirmationWidget {
273+
274+ /** @deprecated Use ChatConfirmationWidget instead */
275+ export class SimpleChatConfirmationWidget extends BaseSimpleChatConfirmationWidget {
273276 private _renderedMessage : HTMLElement | undefined ;
274277
275278 constructor (
@@ -297,10 +300,231 @@ export class ChatConfirmationWidget extends BaseChatConfirmationWidget {
297300 }
298301}
299302
303+ export interface IChatConfirmationWidget2Options {
304+ title : string | IMarkdownString ;
305+ icon ?: ThemeIcon ;
306+ subtitle ?: string | IMarkdownString ;
307+ buttons : IChatConfirmationButton [ ] ;
308+ toolbarData ?: { arg : any ; partType : string ; partSource ?: string } ;
309+ }
310+
311+ abstract class BaseChatConfirmationWidget extends Disposable {
312+ private _onDidClick = this . _register ( new Emitter < IChatConfirmationButton > ( ) ) ;
313+ get onDidClick ( ) : Event < IChatConfirmationButton > { return this . _onDidClick . event ; }
314+
315+ protected _onDidChangeHeight = this . _register ( new Emitter < void > ( ) ) ;
316+ get onDidChangeHeight ( ) : Event < void > { return this . _onDidChangeHeight . event ; }
317+
318+ private _domNode : HTMLElement ;
319+ get domNode ( ) : HTMLElement {
320+ return this . _domNode ;
321+ }
322+
323+ private get showingButtons ( ) {
324+ return ! this . domNode . classList . contains ( 'hideButtons' ) ;
325+ }
326+
327+ setShowButtons ( showButton : boolean ) : void {
328+ this . domNode . classList . toggle ( 'hideButtons' , ! showButton ) ;
329+ }
330+
331+ private readonly messageElement : HTMLElement ;
332+ protected readonly markdownRenderer : MarkdownRenderer ;
333+ private readonly title : string | IMarkdownString ;
334+
335+ private readonly notification = this . _register ( new MutableDisposable < DisposableStore > ( ) ) ;
336+
337+ constructor (
338+ options : IChatConfirmationWidget2Options ,
339+ @IInstantiationService protected readonly instantiationService : IInstantiationService ,
340+ @IContextMenuService contextMenuService : IContextMenuService ,
341+ @IConfigurationService private readonly _configurationService : IConfigurationService ,
342+ @IHostService private readonly _hostService : IHostService ,
343+ @IViewsService private readonly _viewsService : IViewsService ,
344+ @IContextKeyService contextKeyService : IContextKeyService ,
345+ ) {
346+ super ( ) ;
347+
348+ const { title, subtitle, buttons, icon } = options ;
349+ this . title = title ;
350+
351+ const elements = dom . h ( '.chat-confirmation-widget2@root' , [
352+ dom . h ( '.chat-confirmation-widget-title' , [
353+ dom . h ( '.chat-title@title' ) ,
354+ dom . h ( '.chat-buttons@buttons' ) ,
355+ ] ) ,
356+ dom . h ( '.chat-confirmation-widget-message@message' ) ,
357+ dom . h ( '.chat-buttons-container@buttonsContainer' , [
358+ dom . h ( '.chat-toolbar@toolbar' ) ,
359+ ] ) ,
360+ ] ) ;
361+ this . _domNode = elements . root ;
362+ this . markdownRenderer = this . instantiationService . createInstance ( MarkdownRenderer , { } ) ;
363+
364+ const titlePart = this . _register ( instantiationService . createInstance (
365+ ChatQueryTitlePart ,
366+ elements . title ,
367+ new MarkdownString ( icon ? `$(${ icon . id } ) ${ typeof title === 'string' ? title : title . value } ` : typeof title === 'string' ? title : title . value ) ,
368+ subtitle ,
369+ this . markdownRenderer ,
370+ ) ) ;
371+
372+ this . _register ( titlePart . onDidChangeHeight ( ( ) => this . _onDidChangeHeight . fire ( ) ) ) ;
373+
374+ this . messageElement = elements . message ;
375+
376+ for ( const buttonData of buttons ) {
377+ const buttonOptions : IButtonOptions = { ...defaultButtonStyles , secondary : buttonData . isSecondary , title : buttonData . tooltip , disabled : buttonData . disabled } ;
378+
379+ let button : IButton ;
380+ if ( buttonData . moreActions ) {
381+ button = new ButtonWithDropdown ( elements . buttons , {
382+ ...buttonOptions ,
383+ contextMenuProvider : contextMenuService ,
384+ addPrimaryActionToDropdown : false ,
385+ actions : buttonData . moreActions . map ( action => {
386+ if ( action instanceof Separator ) {
387+ return action ;
388+ }
389+ return this . _register ( new Action (
390+ action . label ,
391+ action . label ,
392+ undefined ,
393+ ! action . disabled ,
394+ ( ) => {
395+ this . _onDidClick . fire ( action ) ;
396+ return Promise . resolve ( ) ;
397+ } ,
398+ ) ) ;
399+ } ) ,
400+ } ) ;
401+ } else {
402+ button = new Button ( elements . buttons , buttonOptions ) ;
403+ }
404+
405+ this . _register ( button ) ;
406+ button . label = buttonData . label ;
407+ this . _register ( button . onDidClick ( ( ) => this . _onDidClick . fire ( buttonData ) ) ) ;
408+ if ( buttonData . onDidChangeDisablement ) {
409+ this . _register ( buttonData . onDidChangeDisablement ( disabled => button . enabled = ! disabled ) ) ;
410+ }
411+ }
412+
413+ // Create toolbar if actions are provided
414+ if ( options ?. toolbarData ) {
415+ const overlay = contextKeyService . createOverlay ( [
416+ [ 'chatConfirmationPartType' , options . toolbarData . partType ] ,
417+ [ 'chatConfirmationPartSource' , options . toolbarData . partSource ] ,
418+ ] ) ;
419+ const nestedInsta = this . _register ( instantiationService . createChild ( new ServiceCollection ( [ IContextKeyService , overlay ] ) ) ) ;
420+ this . _register ( nestedInsta . createInstance (
421+ MenuWorkbenchToolBar ,
422+ elements . toolbar ,
423+ MenuId . ChatConfirmationMenu ,
424+ {
425+ // buttonConfigProvider: () => ({ showLabel: false, showIcon: true }),
426+ menuOptions : {
427+ arg : options . toolbarData . arg ,
428+ shouldForwardArgs : true ,
429+ }
430+ }
431+ ) ) ;
432+ }
433+ }
434+
435+
436+ // protected renderMessage(element: HTMLElement, listContainer: HTMLElement): void {
437+ // this.messageElement.append(element);
438+
439+ // if (this.showingButtons && this._configurationService.getValue<boolean>('chat.notifyWindowOnConfirmation')) {
440+ // const targetWindow = dom.getWindow(listContainer);
441+ // if (!targetWindow.document.hasFocus()) {
442+ // this.notifyConfirmationNeeded(targetWindow);
443+ // }
444+ // }
445+ // }
446+ protected renderMessage ( element : HTMLElement | IMarkdownString | string , listContainer : HTMLElement ) : void {
447+ if ( ! dom . isHTMLElement ( element ) ) {
448+ const messageElement = this . _register ( this . markdownRenderer . render (
449+ typeof element === 'string' ? new MarkdownString ( element ) : element ,
450+ { asyncRenderCallback : ( ) => this . _onDidChangeHeight . fire ( ) }
451+ ) ) ;
452+ element = messageElement . element ;
453+ }
454+
455+ for ( const child of this . messageElement . children ) {
456+ child . remove ( ) ;
457+ }
458+ this . messageElement . append ( element ) ;
459+
460+ if ( this . showingButtons && this . _configurationService . getValue < boolean > ( 'chat.notifyWindowOnConfirmation' ) ) {
461+ const targetWindow = dom . getWindow ( listContainer ) ;
462+ if ( ! targetWindow . document . hasFocus ( ) ) {
463+ this . notifyConfirmationNeeded ( targetWindow ) ;
464+ }
465+ }
466+ }
467+
468+ private async notifyConfirmationNeeded ( targetWindow : Window ) : Promise < void > {
469+
470+ // Focus Window
471+ this . _hostService . focus ( targetWindow , { mode : FocusMode . Notify } ) ;
472+
473+ // Notify
474+ const title = renderAsPlaintext ( this . title ) ;
475+ const notification = await dom . triggerNotification ( title ? localize ( 'notificationTitle' , "Chat: {0}" , title ) : localize ( 'defaultTitle' , "Chat: Confirmation Required" ) ,
476+ {
477+ detail : localize ( 'notificationDetail' , "The current chat session requires your confirmation to proceed." )
478+ }
479+ ) ;
480+ if ( notification ) {
481+ const disposables = this . notification . value = new DisposableStore ( ) ;
482+ disposables . add ( notification ) ;
483+
484+ disposables . add ( Event . once ( notification . onClick ) ( ( ) => {
485+ this . _hostService . focus ( targetWindow , { mode : FocusMode . Force } ) ;
486+ showChatView ( this . _viewsService ) ;
487+ } ) ) ;
488+
489+ disposables . add ( this . _hostService . onDidChangeFocus ( focus => {
490+ if ( focus ) {
491+ disposables . dispose ( ) ;
492+ }
493+ } ) ) ;
494+ }
495+ }
496+ }
497+ export class ChatConfirmationWidget extends BaseChatConfirmationWidget {
498+ private _renderedMessage : HTMLElement | undefined ;
499+
500+ constructor (
501+ private readonly _container : HTMLElement ,
502+ options : IChatConfirmationWidget2Options & { message : HTMLElement | IMarkdownString | string } ,
503+ @IInstantiationService instantiationService : IInstantiationService ,
504+ @IContextMenuService contextMenuService : IContextMenuService ,
505+ @IConfigurationService configurationService : IConfigurationService ,
506+ @IHostService hostService : IHostService ,
507+ @IViewsService viewsService : IViewsService ,
508+ @IContextKeyService contextKeyService : IContextKeyService ,
509+ ) {
510+ super ( options , instantiationService , contextMenuService , configurationService , hostService , viewsService , contextKeyService ) ;
511+ this . renderMessage ( options . message , this . _container ) ;
512+ }
513+
514+ public updateMessage ( message : string | IMarkdownString ) : void {
515+ this . _renderedMessage ?. remove ( ) ;
516+ const renderedMessage = this . _register ( this . markdownRenderer . render (
517+ typeof message === 'string' ? new MarkdownString ( message ) : message ,
518+ { asyncRenderCallback : ( ) => this . _onDidChangeHeight . fire ( ) }
519+ ) ) ;
520+ this . renderMessage ( renderedMessage . element , this . _container ) ;
521+ this . _renderedMessage = renderedMessage . element ;
522+ }
523+ }
300524export class ChatCustomConfirmationWidget extends BaseChatConfirmationWidget {
301525 constructor (
302526 container : HTMLElement ,
303- options : IChatConfirmationWidgetOptions & { message : HTMLElement } ,
527+ options : IChatConfirmationWidget2Options & { message : HTMLElement | IMarkdownString | string } ,
304528 @IInstantiationService instantiationService : IInstantiationService ,
305529 @IContextMenuService contextMenuService : IContextMenuService ,
306530 @IConfigurationService configurationService : IConfigurationService ,
0 commit comments