@@ -37,6 +37,7 @@ import {
3737 RuleClickResult ,
3838 SourceLinkClickParams ,
3939 ListAvailableModelsResult ,
40+ ExecuteShellCommandParams ,
4041} from '@aws/language-server-runtimes-types'
4142import {
4243 ChatItem ,
@@ -94,6 +95,7 @@ export interface InboundChatApi {
9495 openTab ( requestId : string , params : OpenTabParams ) : void
9596 sendContextCommands ( params : ContextCommandParams ) : void
9697 listConversations ( params : ListConversationsResult ) : void
98+ executeShellCommandShortCut ( params : ExecuteShellCommandParams ) : void
9799 listRules ( params : ListRulesResult ) : void
98100 conversationClicked ( params : ConversationClickResult ) : void
99101 ruleClicked ( params : RuleClickResult ) : void
@@ -313,7 +315,8 @@ export const createMynahUi = (
313315 customChatClientAdapter ?: ChatClientAdapter ,
314316 featureConfig ?: Map < string , any > ,
315317 agenticMode ?: boolean ,
316- stringOverrides ?: Partial < ConfigTexts >
318+ stringOverrides ?: Partial < ConfigTexts > ,
319+ os ?: string
317320) : [ MynahUI , InboundChatApi ] => {
318321 let disclaimerCardActive = ! disclaimerAcknowledged
319322 let programmingModeCardActive = ! pairProgrammingCardAcknowledged
@@ -690,24 +693,7 @@ export const createMynahUi = (
690693 }
691694 } ,
692695 onStopChatResponse : tabId => {
693- messager . onStopChatResponse ( tabId )
694-
695- // Reset loading state
696- mynahUi . updateStore ( tabId , {
697- loadingChat : false ,
698- cancelButtonWhenLoading : true ,
699- promptInputDisabledState : false ,
700- } )
701-
702- // Add a small delay before adding the chat item
703- setTimeout ( ( ) => {
704- // Add cancellation message when stop button is clicked
705- mynahUi . addChatItem ( tabId , {
706- type : ChatItemType . DIRECTIVE ,
707- messageId : 'stopped' + Date . now ( ) ,
708- body : 'You stopped your current work, please provide additional examples or ask another question.' ,
709- } )
710- } , 500 ) // 500ms delay
696+ handleUIStopChatResponse ( messager , mynahUi , tabId )
711697 } ,
712698 onOpenFileDialogClick : ( tabId , fileType , insertPosition ) => {
713699 const imageContext = getImageContextCount ( tabId )
@@ -818,6 +804,7 @@ export const createMynahUi = (
818804 dragOverlayText : 'Add image to context' ,
819805 // Fallback to original texts in non-agentic chat mode
820806 stopGenerating : agenticMode ? uiComponentsTexts . stopGenerating : 'Stop generating' ,
807+ stopGeneratingTooltip : getStopGeneratingToolTipText ( os , agenticMode ) ,
821808 spinnerText : agenticMode ? uiComponentsTexts . spinnerText : 'Generating your answer...' ,
822809 ...stringOverrides ,
823810 } ,
@@ -1459,6 +1446,55 @@ ${params.message}`,
14591446 messager . onError ( params )
14601447 }
14611448
1449+ const executeShellCommandShortCut = ( params : ExecuteShellCommandParams ) => {
1450+ const activeElement = document . activeElement as HTMLElement
1451+
1452+ const tabId = mynahUi . getSelectedTabId ( )
1453+ if ( ! tabId ) return
1454+
1455+ const chatItems = mynahUi . getTabData ( tabId ) ?. getStore ( ) ?. chatItems || [ ]
1456+ const buttonId = params . id
1457+
1458+ let messageId
1459+ for ( const item of chatItems ) {
1460+ if ( buttonId === 'stop-shell-command' && item . buttons && item . buttons . some ( b => b . id === buttonId ) ) {
1461+ messageId = item . messageId
1462+ break
1463+ }
1464+ if ( item . header ?. buttons && item . header . buttons . some ( b => b . id === buttonId ) ) {
1465+ messageId = item . messageId
1466+ break
1467+ }
1468+ }
1469+
1470+ if ( messageId ) {
1471+ const payload : ButtonClickParams = {
1472+ tabId,
1473+ messageId,
1474+ buttonId,
1475+ }
1476+ messager . onButtonClick ( payload )
1477+ if ( buttonId === 'stop-shell-command' ) {
1478+ handleUIStopChatResponse ( messager , mynahUi , tabId )
1479+ }
1480+ } else {
1481+ // handle global stop
1482+ const isLoading = mynahUi . getTabData ( tabId ) ?. getStore ( ) ?. loadingChat
1483+ if ( isLoading && buttonId === 'stop-shell-command' ) {
1484+ handleUIStopChatResponse ( messager , mynahUi , tabId )
1485+ }
1486+ }
1487+ // this is a short-term solution to re-gain focus after executing a shortcut
1488+ // current behavior will emit exitFocus telemetry immediadately.
1489+ // use this to re-gain focus, so that user can use shortcut after shortcut
1490+ // without manually re-gain focus.
1491+ setTimeout ( ( ) => {
1492+ if ( activeElement && activeElement . focus ) {
1493+ activeElement . focus ( )
1494+ }
1495+ } , 100 )
1496+ }
1497+
14621498 const openTab = ( requestId : string , params : OpenTabParams ) => {
14631499 if ( params . tabId ) {
14641500 if ( params . tabId !== mynahUi . getSelectedTabId ( ) ) {
@@ -1684,6 +1720,7 @@ ${params.message}`,
16841720 openTab : openTab ,
16851721 sendContextCommands : sendContextCommands ,
16861722 sendPinnedContext : sendPinnedContext ,
1723+ executeShellCommandShortCut : executeShellCommandShortCut ,
16871724 listConversations : listConversations ,
16881725 listRules : listRules ,
16891726 conversationClicked : conversationClicked ,
@@ -1730,4 +1767,37 @@ export const uiComponentsTexts = {
17301767 noMoreTabsTooltip : 'You can only open ten conversation tabs at a time.' ,
17311768 codeSuggestionWithReferenceTitle : 'Some suggestions contain code with references.' ,
17321769 spinnerText : 'Working...' ,
1770+ macStopButtonShortcut : '⇧ ⌘ ⌫' ,
1771+ windowStopButtonShortcut : 'Ctrl + ⇧ + ⌫' ,
1772+ }
1773+
1774+ const getStopGeneratingToolTipText = ( os : string | undefined , agenticMode : boolean | undefined ) : string => {
1775+ if ( agenticMode && os ) {
1776+ return os === 'darwin'
1777+ ? `Stop: ${ uiComponentsTexts . macStopButtonShortcut } `
1778+ : `Stop: ${ uiComponentsTexts . windowStopButtonShortcut } `
1779+ }
1780+
1781+ return agenticMode ? uiComponentsTexts . stopGenerating : 'Stop generating'
1782+ }
1783+
1784+ const handleUIStopChatResponse = ( messenger : Messager , mynahUi : MynahUI , tabId : string ) => {
1785+ messenger . onStopChatResponse ( tabId )
1786+
1787+ // Reset loading state
1788+ mynahUi . updateStore ( tabId , {
1789+ loadingChat : false ,
1790+ cancelButtonWhenLoading : true ,
1791+ promptInputDisabledState : false ,
1792+ } )
1793+
1794+ // Add a small delay before adding the chat item
1795+ setTimeout ( ( ) => {
1796+ // Add cancellation message when stop button is clicked
1797+ mynahUi . addChatItem ( tabId , {
1798+ type : ChatItemType . DIRECTIVE ,
1799+ messageId : 'stopped' + Date . now ( ) ,
1800+ body : 'You stopped your current work, please provide additional examples or ask another question.' ,
1801+ } )
1802+ } , 500 ) // 500ms delay
17331803}
0 commit comments