- 
                Notifications
    
You must be signed in to change notification settings  - Fork 273
 
feat(amazonQFlareChat): Set up Flare chat connection #5545
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
fe7738d
              c09368c
              9d037cf
              e259071
              bc6823b
              b833b6c
              fb9fd25
              46b9a82
              06dc03d
              0f16cd4
              21e4885
              06e0966
              ebba74e
              57600f5
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| 
          
            
          
           | 
    @@ -8,6 +8,7 @@ | |
| import com.intellij.openapi.util.Disposer | ||
| import com.intellij.ui.jcef.JBCefJSQuery | ||
| import org.cef.CefApp | ||
| import software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts.ArtifactHelper | ||
| import software.aws.toolkits.jetbrains.services.amazonq.util.HighlightCommand | ||
| import software.aws.toolkits.jetbrains.services.amazonq.util.createBrowser | ||
| import software.aws.toolkits.jetbrains.settings.MeetQSettings | ||
| 
          
            
          
           | 
    @@ -46,6 +47,13 @@ | |
| 
     | 
||
| fun component() = jcefBrowser.component | ||
| 
     | 
||
| fun postChat(message: String) { | ||
| jcefBrowser | ||
| .cefBrowser | ||
| .executeJavaScript("window.postMessage($message)", jcefBrowser.cefBrowser.url, 0) | ||
| } | ||
| 
     | 
||
| // TODO: Remove this once chat has been integrated with agents | ||
| fun post(message: String) = | ||
| jcefBrowser | ||
| .cefBrowser | ||
| 
          
            
          
           | 
    @@ -82,32 +90,86 @@ | |
| highlightCommand: HighlightCommand?, | ||
| ): String { | ||
| val postMessageToJavaJsCode = receiveMessageQuery.inject("JSON.stringify(message)") | ||
| 
     | 
||
| val jsScripts = """ | ||
| <script type="text/javascript" src="$WEB_SCRIPT_URI" defer onload="init()"></script> | ||
| <script type="text/javascript"> | ||
| const init = () => { | ||
| mynahUI.createMynahUI( | ||
| amazonQChat.createChat( | ||
| { | ||
| postMessage: message => { | ||
| $postMessageToJavaJsCode | ||
| } | ||
| }, | ||
| ${MeetQSettings.getInstance().reinvent2024OnboardingCount < MAX_ONBOARDING_PAGE_COUNT}, | ||
| ${MeetQSettings.getInstance().disclaimerAcknowledged}, | ||
| $isFeatureDevAvailable, // whether /dev is available | ||
| $isCodeTransformAvailable, // whether /transform is available | ||
| $isDocAvailable, // whether /doc is available | ||
| $isCodeScanAvailable, // whether /scan is available | ||
| $isCodeTestAvailable, // whether /test is available | ||
| ${OBJECT_MAPPER.writeValueAsString(highlightCommand)} | ||
| }, | ||
| { | ||
| quickActionCommands: [], | ||
| disclaimerAcknowledged: ${MeetQSettings.getInstance().disclaimerAcknowledged} | ||
| } | ||
| ); | ||
| } | ||
| </script> | ||
| """.trimIndent() | ||
| 
     | 
||
| addQuickActionCommands(isCodeTransformAvailable, isFeatureDevAvailable, isDocAvailable, isCodeTestAvailable, isCodeScanAvailable, highlightCommand) | ||
| return """ | ||
| <!DOCTYPE html> | ||
| <style> | ||
| body, | ||
| html { | ||
| background-color: var(--mynah-color-bg); | ||
| color: var(--mynah-color-text-default); | ||
| height: 100vh; | ||
| width: 100%%; | ||
| overflow: hidden; | ||
| margin: 0; | ||
| padding: 0; | ||
| } | ||
| .mynah-ui-icon-plus, | ||
| .mynah-ui-icon-cancel { | ||
| -webkit-mask-size: 155% !important; | ||
| mask-size: 155% !important; | ||
| mask-position: center; | ||
| scale: 60%; | ||
| } | ||
| .code-snippet-close-button i.mynah-ui-icon-cancel, | ||
| .mynah-chat-item-card-related-content-show-more i.mynah-ui-icon-down-open { | ||
| -webkit-mask-size: 195.5% !important; | ||
| mask-size: 195.5% !important; | ||
| mask-position: center; | ||
| aspect-ratio: 1/1; | ||
| width: 15px; | ||
| height: 15px; | ||
| scale: 50% | ||
| } | ||
| .mynah-ui-icon-tabs { | ||
| -webkit-mask-size: 102% !important; | ||
| mask-size: 102% !important; | ||
| mask-position: center; | ||
| } | ||
| textarea:placeholder-shown { | ||
| line-height: 1.5rem; | ||
| } | ||
| .mynah-ui-spinner-container { | ||
| contain: layout !important; | ||
| } | ||
| .mynah-ui-spinner-container > span.mynah-ui-spinner-logo-part { | ||
| position: static !important; | ||
| will-change: transform !important; | ||
| } | ||
| .mynah-ui-spinner-container, | ||
| .mynah-ui-spinner-container > span.mynah-ui-spinner-logo-part, | ||
| .mynah-ui-spinner-container > span.mynah-ui-spinner-logo-part > .mynah-ui-spinner-logo-mask.text { | ||
| border: 0 !important; | ||
| outline: none !important; | ||
| box-shadow: none !important; | ||
| border-radius: 0 !important; | ||
| } | ||
| .mynah-ui-spinner-container > span.mynah-ui-spinner-logo-part > .mynah-ui-spinner-logo-mask.text { | ||
| will-change: transform !important; | ||
| transform: translateZ(0) !important; | ||
| } | ||
| </style> | ||
| <html> | ||
| <head> | ||
| <title>AWS Q</title> | ||
| 
        
          
        
         | 
    @@ -119,8 +181,28 @@ | |
| """.trimIndent() | ||
| } | ||
| 
     | 
||
| fun addQuickActionCommands( | ||
| 
         Check notice on line 184 in plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/Browser.kt 
    
   | 
||
                
       | 
||
| isCodeTransformAvailable: Boolean, | ||
| isFeatureDevAvailable: Boolean, | ||
| isDocAvailable: Boolean, | ||
| isCodeTestAvailable: Boolean, | ||
| isCodeScanAvailable: Boolean, | ||
| highlightCommand: HighlightCommand?, | ||
| ) { | ||
| // TODO: Remove this once chat has been integrated with agents. This is added temporarily to keep detekt happy. | ||
| isCodeScanAvailable | ||
| isCodeTestAvailable | ||
| isDocAvailable | ||
| isFeatureDevAvailable | ||
| isCodeTransformAvailable | ||
| MAX_ONBOARDING_PAGE_COUNT | ||
| OBJECT_MAPPER | ||
| highlightCommand | ||
| } | ||
| 
     | 
||
| companion object { | ||
| private const val WEB_SCRIPT_URI = "http://mynah/js/mynah-ui.js" | ||
| // TODO: Switch this to respect the overriden paths too | ||
| private val WEB_SCRIPT_URI = ArtifactHelper().getLatestLocalLspArtifact().resolve("amazonq-ui.js").toUri() | ||
                
       | 
||
| private const val MAX_ONBOARDING_PAGE_COUNT = 3 | ||
| private val OBJECT_MAPPER = jacksonObjectMapper() | ||
| } | ||
| 
          
            
          
           | 
    ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| 
          
            
          
           | 
    @@ -3,8 +3,11 @@ | |
| 
     | 
||
| package software.aws.toolkits.jetbrains.services.amazonq.webview | ||
| 
     | 
||
| import com.fasterxml.jackson.databind.JsonNode | ||
| import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper | ||
| import com.intellij.ide.BrowserUtil | ||
| import com.intellij.ide.util.RunOnceUtil | ||
| import com.intellij.openapi.project.Project | ||
| import com.intellij.ui.jcef.JBCefJSQuery.Response | ||
| import kotlinx.coroutines.CompletableDeferred | ||
| import kotlinx.coroutines.channels.awaitClose | ||
| 
        
          
        
         | 
    @@ -17,22 +20,36 @@ | |
| import kotlinx.coroutines.flow.onEach | ||
| import kotlinx.coroutines.launch | ||
| import org.cef.browser.CefBrowser | ||
| import org.eclipse.lsp4j.Position | ||
| import org.eclipse.lsp4j.Range | ||
| import software.aws.toolkits.jetbrains.services.amazonq.apps.AppConnection | ||
| import software.aws.toolkits.jetbrains.services.amazonq.commands.MessageSerializer | ||
| import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService | ||
| import software.aws.toolkits.jetbrains.services.amazonq.lsp.encryption.JwtEncryptionManager | ||
| import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.ChatCommunicationManager | ||
| import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.getTextDocumentIdentifier | ||
| import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ChatParams | ||
| import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ChatPrompt | ||
| import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CursorState | ||
| import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.EncryptedChatParams | ||
| import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SendChatPromptRequest | ||
| import software.aws.toolkits.jetbrains.services.amazonq.util.command | ||
| import software.aws.toolkits.jetbrains.services.amazonq.util.tabType | ||
| import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.AmazonQTheme | ||
| import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.ThemeBrowserAdapter | ||
| import software.aws.toolkits.jetbrains.settings.MeetQSettings | ||
| import software.aws.toolkits.telemetry.MetricResult | ||
| import software.aws.toolkits.telemetry.Telemetry | ||
| import java.util.concurrent.CompletableFuture | ||
| import java.util.function.Function | ||
| 
     | 
||
| class BrowserConnector( | ||
| private val serializer: MessageSerializer = MessageSerializer.getInstance(), | ||
| private val themeBrowserAdapter: ThemeBrowserAdapter = ThemeBrowserAdapter(), | ||
| private val project: Project, | ||
| ) { | ||
| var uiReady = CompletableDeferred<Boolean>() | ||
| val chatCommunicationManager = ChatCommunicationManager.getInstance(project) | ||
| 
         Check notice on line 52 in plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt 
    
   | 
||
                
       | 
||
| 
     | 
||
| suspend fun connect( | ||
| browser: Browser, | ||
| 
          
            
          
           | 
    @@ -77,7 +94,10 @@ | |
| } | ||
| } | ||
| 
     | 
||
| val tabType = node.tabType ?: return@onEach | ||
| val tabType = node.tabType | ||
| if (tabType == null) { | ||
| handleFlareChatMessages(browser, node) | ||
| } | ||
| connections.filter { connection -> connection.app.tabTypes.contains(tabType) }.forEach { connection -> | ||
| launch { | ||
| val message = serializer.deserialize(node, connection.messageTypeRegistry) | ||
| 
          
            
          
           | 
    @@ -123,4 +143,56 @@ | |
| browser.receiveMessageQuery.removeHandler(handler) | ||
| } | ||
| } | ||
| 
     | 
||
| private suspend fun handleFlareChatMessages(browser: Browser, node: JsonNode) { | ||
                
       | 
||
| when (node.command) { | ||
| "aws/chat/sendChatPrompt" -> { | ||
                
      
                  manodnyab marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| val requestFromUi = jacksonObjectMapper().readValue(node.toString(), SendChatPromptRequest::class.java) | ||
                
      
                  manodnyab marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| val chatPrompt = ChatPrompt( | ||
| requestFromUi.params.prompt.prompt, | ||
| requestFromUi.params.prompt.escapedPrompt, | ||
| node.command | ||
| ) | ||
| val textDocumentIdentifier = getTextDocumentIdentifier(project) | ||
| val cursorState = CursorState( | ||
| Range( | ||
| Position( | ||
| 0, | ||
| 0 | ||
| ), | ||
| Position( | ||
| 1, | ||
| 1 | ||
| ) | ||
| 
         
      Comment on lines
    
      +158
     to 
      +166
    
   
  There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use real values? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding this as a follow-up  | 
||
| ) | ||
| ) | ||
| 
     | 
||
| val partialResultToken = chatCommunicationManager.addPartialChatMessage(requestFromUi.params.tabId) | ||
| val chatParams = ChatParams( | ||
| requestFromUi.params.tabId, | ||
| chatPrompt, | ||
| textDocumentIdentifier, | ||
| cursorState | ||
| ) | ||
| 
     | 
||
| var encryptionManager: JwtEncryptionManager? = null | ||
| val result = AmazonQLspService.executeIfRunning(project) { server -> | ||
| encryptionManager = this.encryptionManager | ||
| server.sendChatPrompt(EncryptedChatParams(encryptionManager!!.encrypt(chatParams), partialResultToken)) | ||
| } ?: (CompletableFuture.failedFuture(IllegalStateException("LSP Server not running"))) | ||
| 
     | 
||
| result.whenComplete { | ||
| value, error -> | ||
| chatCommunicationManager.removePartialChatMessage(partialResultToken) | ||
| val messageToChat = ChatCommunicationManager.convertToJsonToSendToChat( | ||
| node.command, | ||
| requestFromUi.params.tabId, | ||
| encryptionManager?.decrypt(value) ?: "", | ||
| isPartialResult = false | ||
| ) | ||
| browser.postChat(messageToChat) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dont need messagebus for this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed offline. This will involve moving the toolwindow to shared. Will address this as a part of a new PR