Skip to content

Commit bc6823b

Browse files
committed
feedback
1 parent e259071 commit bc6823b

File tree

11 files changed

+81
-42
lines changed

11 files changed

+81
-42
lines changed

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/commands/MessageSerializer.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.fasterxml.jackson.databind.MapperFeature
1212
import com.fasterxml.jackson.databind.SerializationFeature
1313
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
1414
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
15+
import com.fasterxml.jackson.module.kotlin.treeToValue
1516
import org.jetbrains.annotations.VisibleForTesting
1617
import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage
1718
import software.aws.toolkits.jetbrains.services.amazonq.messages.UnknownMessageType
@@ -36,6 +37,9 @@ class MessageSerializer @VisibleForTesting constructor() {
3637

3738
fun serialize(value: Any): String = objectMapper.writeValueAsString(value)
3839

40+
fun <T> deserializeChatMessages(value: JsonNode, clazz: Class<T>): T =
41+
objectMapper.treeToValue(value, clazz)
42+
3943
// Provide singleton global access
4044
companion object {
4145
private val instance = MessageSerializer()

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ package software.aws.toolkits.jetbrains.services.amazonq.toolwindow
55

66
import com.intellij.idea.AppMode
77
import com.intellij.openapi.Disposable
8+
import com.intellij.openapi.application.ApplicationManager
9+
import com.intellij.openapi.application.runInEdt
810
import com.intellij.openapi.util.Disposer
11+
import com.intellij.ui.components.JBLoadingPanel
12+
import com.intellij.ui.components.JBPanelWithEmptyText
913
import com.intellij.ui.components.JBTextArea
1014
import com.intellij.ui.components.panels.Wrapper
1115
import com.intellij.ui.dsl.builder.Align
@@ -14,14 +18,15 @@ import com.intellij.ui.dsl.builder.AlignY
1418
import com.intellij.ui.dsl.builder.panel
1519
import com.intellij.ui.jcef.JBCefApp
1620
import software.aws.toolkits.jetbrains.isDeveloperMode
21+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts.ArtifactHelper
1722
import software.aws.toolkits.jetbrains.services.amazonq.webview.Browser
1823
import java.awt.event.ActionListener
24+
import java.util.concurrent.CompletableFuture
1925
import javax.swing.JButton
2026

2127
class AmazonQPanel(private val parent: Disposable) {
2228
private val webviewContainer = Wrapper()
23-
var browser: Browser? = null
24-
private set
29+
val browser = CompletableFuture<Browser>()
2530

2631
val component = panel {
2732
row {
@@ -39,7 +44,7 @@ class AmazonQPanel(private val parent: Disposable) {
3944
// Code to be executed when the button is clicked
4045
// Add your logic here
4146

42-
browser?.jcefBrowser?.openDevtools()
47+
browser.get().jcefBrowser.openDevtools()
4348
},
4449
)
4550
},
@@ -56,7 +61,7 @@ class AmazonQPanel(private val parent: Disposable) {
5661

5762
fun disposeAndRecreate() {
5863
webviewContainer.removeAll()
59-
val toDispose = browser
64+
val toDispose = browser.get()
6065
init()
6166
if (toDispose != null) {
6267
Disposer.dispose(toDispose)
@@ -71,10 +76,26 @@ class AmazonQPanel(private val parent: Disposable) {
7176
} else {
7277
webviewContainer.add(JBTextArea("JCEF not supported"))
7378
}
74-
browser = null
79+
browser.complete(null)
7580
} else {
76-
browser = Browser(parent).also {
77-
webviewContainer.add(it.component())
81+
val loadingPanel = JBLoadingPanel(null, parent, 0)
82+
val wrapper = Wrapper()
83+
loadingPanel.startLoading()
84+
85+
loadingPanel.add(JBPanelWithEmptyText().withEmptyText("Wait for chat to be ready"))
86+
webviewContainer.add(wrapper)
87+
wrapper.setContent(loadingPanel)
88+
89+
ApplicationManager.getApplication().executeOnPooledThread {
90+
val webUri = ArtifactHelper().getLatestLocalLspArtifact().resolve("amazonq-ui.js").toUri()
91+
loadingPanel.stopLoading()
92+
runInEdt {
93+
browser.complete(
94+
Browser(parent, webUri).also {
95+
wrapper.setContent(it.component())
96+
}
97+
)
98+
}
7899
}
79100
}
80101
}

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindow.kt

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import com.intellij.openapi.util.Disposer
1515
import com.intellij.openapi.wm.ToolWindowManager
1616
import kotlinx.coroutines.CompletableDeferred
1717
import kotlinx.coroutines.CoroutineScope
18+
import kotlinx.coroutines.future.await
1819
import kotlinx.coroutines.launch
1920
import software.aws.toolkits.jetbrains.services.amazonq.QWebviewPanel
2021
import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext
@@ -26,6 +27,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.messages.MessageConnecto
2627
import software.aws.toolkits.jetbrains.services.amazonq.onboarding.OnboardingPageInteraction
2728
import software.aws.toolkits.jetbrains.services.amazonq.onboarding.OnboardingPageInteractionType
2829
import software.aws.toolkits.jetbrains.services.amazonq.util.highlightCommand
30+
import software.aws.toolkits.jetbrains.services.amazonq.webview.Browser
2931
import software.aws.toolkits.jetbrains.services.amazonq.webview.BrowserConnector
3032
import software.aws.toolkits.jetbrains.services.amazonq.webview.FqnWebviewAdapter
3133
import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.EditorThemeAdapter
@@ -53,30 +55,43 @@ class AmazonQToolWindow private constructor(
5355
private val appConnections = mutableListOf<AppConnection>()
5456

5557
init {
56-
initConnections()
57-
connectUi()
58-
connectApps()
58+
prepareBrowser()
59+
60+
scope.launch {
61+
chatPanel.browser.await()
62+
}
5963

6064
project.messageBus.connect().subscribe(
6165
AsyncChatUiListener.TOPIC,
6266
object : AsyncChatUiListener {
6367
override fun onChange(message: String) {
6468
runInEdt {
65-
chatPanel.browser?.postChat(message)
69+
chatPanel.browser.get()?.postChat(message)
6670
}
6771
}
6872
}
6973
)
7074
}
7175

76+
private fun prepareBrowser() {
77+
chatPanel.browser.whenComplete { browser, ex ->
78+
if (ex != null) {
79+
return@whenComplete
80+
}
81+
82+
initConnections()
83+
connectUi(browser)
84+
connectApps(browser)
85+
}
86+
}
87+
7288
fun disposeAndRecreate() {
7389
browserConnector.uiReady = CompletableDeferred()
90+
7491
chatPanel.disposeAndRecreate()
7592

7693
appConnections.clear()
77-
initConnections()
78-
connectUi()
79-
connectApps()
94+
prepareBrowser()
8095

8196
ApplicationManager.getApplication().messageBus.syncPublisher(LafManagerListener.TOPIC).lookAndFeelChanged(LafManager.getInstance())
8297
}
@@ -109,9 +124,7 @@ class AmazonQToolWindow private constructor(
109124
}
110125
}
111126

112-
private fun connectApps() {
113-
val browser = chatPanel.browser ?: return
114-
127+
private fun connectApps(browser: Browser) {
115128
val fqnWebviewAdapter = FqnWebviewAdapter(browser.jcefBrowser, browserConnector)
116129

117130
appConnections.forEach { connection ->
@@ -129,11 +142,10 @@ class AmazonQToolWindow private constructor(
129142
}
130143
}
131144

132-
private fun connectUi() {
133-
val chatBrowser = chatPanel.browser ?: return
145+
private fun connectUi(browser: Browser) {
134146
val loginBrowser = QWebviewPanel.getInstance(project).browser ?: return
135147

136-
chatBrowser.init(
148+
browser.init(
137149
isCodeTransformAvailable = isCodeTransformAvailable(project),
138150
isFeatureDevAvailable = isFeatureDevAvailable(project),
139151
isCodeScanAvailable = isCodeScanAvailable(project),
@@ -145,15 +157,15 @@ class AmazonQToolWindow private constructor(
145157
scope.launch {
146158
// Pipe messages from the UI to the relevant apps and vice versa
147159
browserConnector.connect(
148-
browser = chatBrowser,
160+
browser = browser,
149161
connections = appConnections,
150162
)
151163
}
152164

153165
scope.launch {
154166
// Update the theme in the UI when the IDE theme changes
155167
browserConnector.connectTheme(
156-
chatBrowser = chatBrowser.jcefBrowser.cefBrowser,
168+
chatBrowser = browser.jcefBrowser.cefBrowser,
157169
loginBrowser = loginBrowser.jcefBrowser.cefBrowser,
158170
themeSource = editorThemeAdapter.onThemeChange(),
159171
)

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/Browser.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ import com.intellij.openapi.Disposable
88
import com.intellij.openapi.util.Disposer
99
import com.intellij.ui.jcef.JBCefJSQuery
1010
import org.cef.CefApp
11-
import software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts.ArtifactHelper
1211
import software.aws.toolkits.jetbrains.services.amazonq.util.HighlightCommand
1312
import software.aws.toolkits.jetbrains.services.amazonq.util.createBrowser
1413
import software.aws.toolkits.jetbrains.settings.MeetQSettings
14+
import java.net.URI
1515

1616
/*
1717
Displays the web view for the Amazon Q tool window
1818
*/
19-
class Browser(parent: Disposable) : Disposable {
19+
class Browser(parent: Disposable, private val webUri: URI) : Disposable {
2020

2121
val jcefBrowser = createBrowser(parent)
2222

@@ -91,7 +91,7 @@ class Browser(parent: Disposable) : Disposable {
9191
): String {
9292
val postMessageToJavaJsCode = receiveMessageQuery.inject("JSON.stringify(message)")
9393
val jsScripts = """
94-
<script type="text/javascript" src="$WEB_SCRIPT_URI" defer onload="init()"></script>
94+
<script type="text/javascript" src="$webUri" defer onload="init()"></script>
9595
<script type="text/javascript">
9696
const init = () => {
9797
amazonQChat.createChat(
@@ -201,8 +201,6 @@ class Browser(parent: Disposable) : Disposable {
201201
}
202202

203203
companion object {
204-
// TODO: Switch this to respect the overriden paths too
205-
private val WEB_SCRIPT_URI = ArtifactHelper().getLatestLocalLspArtifact().resolve("amazonq-ui.js").toUri()
206204
private const val MAX_ONBOARDING_PAGE_COUNT = 3
207205
private val OBJECT_MAPPER = jacksonObjectMapper()
208206
}

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
package software.aws.toolkits.jetbrains.services.amazonq.webview
55

66
import com.fasterxml.jackson.databind.JsonNode
7-
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
87
import com.intellij.ide.BrowserUtil
98
import com.intellij.ide.util.RunOnceUtil
109
import com.intellij.openapi.project.Project
@@ -32,6 +31,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ChatP
3231
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ChatPrompt
3332
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CursorState
3433
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.EncryptedChatParams
34+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SEND_CHAT_COMMAND_PROMPT
3535
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SendChatPromptRequest
3636
import software.aws.toolkits.jetbrains.services.amazonq.util.command
3737
import software.aws.toolkits.jetbrains.services.amazonq.util.tabType
@@ -144,10 +144,10 @@ class BrowserConnector(
144144
}
145145
}
146146

147-
private suspend fun handleFlareChatMessages(browser: Browser, node: JsonNode) {
147+
private fun handleFlareChatMessages(browser: Browser, node: JsonNode) {
148148
when (node.command) {
149-
"aws/chat/sendChatPrompt" -> {
150-
val requestFromUi = jacksonObjectMapper().readValue(node.toString(), SendChatPromptRequest::class.java)
149+
SEND_CHAT_COMMAND_PROMPT -> {
150+
val requestFromUi = serializer.deserializeChatMessages(node, SendChatPromptRequest::class.java)
151151
val chatPrompt = ChatPrompt(
152152
requestFromUi.params.prompt.prompt,
153153
requestFromUi.params.prompt.escapedPrompt,
@@ -173,7 +173,7 @@ class BrowserConnector(
173173
chatPrompt,
174174
textDocumentIdentifier,
175175
cursorState
176-
)
176+
)
177177

178178
var encryptionManager: JwtEncryptionManager? = null
179179
val result = AmazonQLspService.executeIfRunning(project) { server ->

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.ChatCommun
1919
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.ConnectionMetadata
2020
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.SsoProfileData
2121
import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings
22-
import software.aws.toolkits.jetbrains.utils.notifyInfo
2322
import java.util.concurrent.CompletableFuture
2423

2524
/**

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/ChatCommunicationManager.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.intellij.openapi.project.Project
99
import org.eclipse.lsp4j.ProgressParams
1010
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
1111
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.ProgressNotificationUtils.getObject
12+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SEND_CHAT_COMMAND_PROMPT
1213
import java.util.UUID
1314
import java.util.concurrent.ConcurrentHashMap
1415

@@ -42,11 +43,9 @@ class ChatCommunicationManager {
4243
val encryptedPartialChatResult = getObject(params, String::class.java)
4344
if (encryptedPartialChatResult != null) {
4445
val partialChatResult = AmazonQLspService.getInstance(project).encryptionManager.decrypt(encryptedPartialChatResult)
45-
// if (partialChatResult.body.isEmpty()) {
46-
// return
47-
// }
46+
4847
val uiMessage = convertToJsonToSendToChat(
49-
command = "aws/chat/sendChatPrompt",
48+
command = SEND_CHAT_COMMAND_PROMPT,
5049
tabId = tabId,
5150
params = partialChatResult,
5251
isPartialResult = true

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/ProgressNotificationUtils.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3-
3+
@file:Suppress("BannedImports")
44
package software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat
55

66
import com.google.gson.Gson

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatPrompt.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@ data class ChatPrompt(
1111

1212
data class SendChatPromptRequest(
1313
val command: String,
14-
val params: MidChatPrompt
14+
val params: MidChatPrompt,
1515
)
1616

1717
data class MidChatPrompt(
1818
val prompt: InnerChatPrompt,
19-
val tabId: String
19+
val tabId: String,
2020
)
2121

2222
data class InnerChatPrompt(
2323
val prompt: String,
2424
val escapedPrompt: String,
25-
val context: List<String>
25+
val context: List<String>,
2626
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat
5+
6+
const val SEND_CHAT_COMMAND_PROMPT = "aws/chat/sendChatPrompt"

0 commit comments

Comments
 (0)