Skip to content

Commit 8db2101

Browse files
authored
Add refresh button to the chat panel (aws#4687)
1 parent 95e0fc0 commit 8db2101

File tree

9 files changed

+144
-16
lines changed

9 files changed

+144
-16
lines changed

plugins/amazonq/chat/jetbrains-community/resources/META-INF/plugin-chat.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
</extensions>
2929

3030
<actions>
31+
<action id="aws.toolkit.open.q.window" class="software.aws.toolkits.jetbrains.services.amazonq.QRefreshPanelAction"/>
32+
<group id="aws.q.toolwindow.titleBar" popup="false" compact="true">
33+
<reference id="aws.toolkit.open.q.window"/>
34+
</group>
3135
<!-- TODO: q.openchat will eventually be in amazonq, aws.toolkit.q.sign.in will eventually be in core. -->
3236
<action id="q.openchat" class="software.aws.toolkits.jetbrains.services.amazonq.QOpenPanelAction"/>
3337

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

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.intellij.openapi.actionSystem.DataContext
99
import com.intellij.openapi.components.Service
1010
import com.intellij.openapi.components.service
1111
import com.intellij.openapi.project.Project
12+
import com.intellij.openapi.util.Disposer
1213
import com.intellij.ui.components.JBTextArea
1314
import com.intellij.ui.components.panels.Wrapper
1415
import com.intellij.ui.dsl.builder.panel
@@ -73,6 +74,19 @@ class QWebviewPanel private constructor(val project: Project) : Disposable {
7374
}
7475

7576
init {
77+
init()
78+
}
79+
80+
fun disposeAndRecreate() {
81+
webviewContainer.removeAll()
82+
val toDispose = browser
83+
init()
84+
if (toDispose != null) {
85+
Disposer.dispose(toDispose)
86+
}
87+
}
88+
89+
private fun init() {
7690
if (!JBCefApp.isSupported()) {
7791
// Fallback to an alternative browser-less solution
7892
webviewContainer.add(JBTextArea("JCEF not supported"))
@@ -92,11 +106,13 @@ class QWebviewPanel private constructor(val project: Project) : Disposable {
92106
}
93107
}
94108

95-
class QWebviewBrowser(val project: Project, private val parentDisposable: Disposable) : LoginBrowser(
96-
project,
97-
QWebviewBrowser.DOMAIN,
98-
QWebviewBrowser.WEB_SCRIPT_URI
99-
) {
109+
class QWebviewBrowser(val project: Project, private val parentDisposable: Disposable) :
110+
LoginBrowser(
111+
project,
112+
QWebviewBrowser.DOMAIN,
113+
QWebviewBrowser.WEB_SCRIPT_URI
114+
),
115+
Disposable {
100116
// TODO: confirm if we need such configuration or the default is fine
101117
override val jcefBrowser = createBrowser(parentDisposable)
102118
private val query = JBCefJSQuery.create(jcefBrowser)
@@ -117,6 +133,10 @@ class QWebviewBrowser(val project: Project, private val parentDisposable: Dispos
117133
query.addHandler(jcefHandler)
118134
}
119135

136+
override fun dispose() {
137+
Disposer.dispose(jcefBrowser)
138+
}
139+
120140
fun component(): JComponent? = jcefBrowser.component
121141

122142
override fun handleBrowserMessage(message: BrowserMessage?) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2024 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
5+
6+
import com.intellij.icons.AllIcons
7+
import com.intellij.openapi.actionSystem.ActionUpdateThread
8+
import com.intellij.openapi.actionSystem.AnActionEvent
9+
import com.intellij.openapi.application.ApplicationManager
10+
import com.intellij.openapi.project.DumbAwareAction
11+
import com.intellij.util.messages.Topic
12+
import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindow
13+
import software.aws.toolkits.resources.AmazonQBundle
14+
import software.aws.toolkits.resources.message
15+
import java.util.EventListener
16+
17+
class QRefreshPanelAction : DumbAwareAction(AmazonQBundle.message("amazonq.refresh.panel"), null, AllIcons.Actions.Refresh) {
18+
override fun actionPerformed(e: AnActionEvent) {
19+
val project = e.project ?: return
20+
// recreate chat browser
21+
AmazonQToolWindow.getInstance(project).disposeAndRecreate()
22+
// recreate signin browser
23+
QWebviewPanel.getInstance(project).disposeAndRecreate()
24+
RefreshQChatPanelButtonPressedListener.notifyRefresh()
25+
}
26+
27+
override fun getActionUpdateThread() = ActionUpdateThread.BGT
28+
}
29+
30+
interface RefreshQChatPanelButtonPressedListener : EventListener {
31+
fun onRefresh() {}
32+
33+
companion object {
34+
@Topic.AppLevel
35+
val TOPIC = Topic.create("Q Chat refreshed", RefreshQChatPanelButtonPressedListener::class.java)
36+
37+
fun notifyRefresh() {
38+
ApplicationManager.getApplication().messageBus.syncPublisher(TOPIC).onRefresh()
39+
}
40+
}
41+
}

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

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

66
import com.intellij.idea.AppMode
7-
import com.intellij.openapi.Disposable
7+
import com.intellij.openapi.util.Disposer
88
import com.intellij.ui.components.JBTextArea
99
import com.intellij.ui.components.panels.Wrapper
1010
import com.intellij.ui.dsl.builder.panel
@@ -16,9 +16,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.webview.Browser
1616
import java.awt.event.ActionListener
1717
import javax.swing.JButton
1818

19-
class AmazonQPanel(
20-
parent: Disposable,
21-
) {
19+
class AmazonQPanel {
2220
private val webviewContainer = Wrapper()
2321
var browser: Browser? = null
2422
private set
@@ -52,6 +50,19 @@ class AmazonQPanel(
5250
}
5351

5452
init {
53+
init()
54+
}
55+
56+
fun disposeAndRecreate() {
57+
webviewContainer.removeAll()
58+
val toDispose = browser
59+
init()
60+
if (toDispose != null) {
61+
Disposer.dispose(toDispose)
62+
}
63+
}
64+
65+
private fun init() {
5566
if (!JBCefApp.isSupported()) {
5667
// Fallback to an alternative browser-less solution
5768
if (AppMode.isRemoteDevHost()) {
@@ -61,7 +72,7 @@ class AmazonQPanel(
6172
}
6273
browser = null
6374
} else {
64-
browser = Browser(parent).also {
75+
browser = Browser().also {
6576
webviewContainer.add(it.component())
6677
}
6778
}

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33

44
package software.aws.toolkits.jetbrains.services.amazonq.toolwindow
55

6+
import com.intellij.ide.ui.LafManager
7+
import com.intellij.ide.ui.LafManagerListener
68
import com.intellij.openapi.Disposable
9+
import com.intellij.openapi.application.ApplicationManager
710
import com.intellij.openapi.application.runInEdt
811
import com.intellij.openapi.components.Service
912
import com.intellij.openapi.components.service
1013
import com.intellij.openapi.project.Project
1114
import com.intellij.openapi.util.Disposer
1215
import com.intellij.openapi.wm.ToolWindowManager
16+
import kotlinx.coroutines.CompletableDeferred
1317
import kotlinx.coroutines.CoroutineScope
1418
import kotlinx.coroutines.launch
1519
import software.aws.toolkits.jetbrains.services.amazonq.QWebviewPanel
@@ -36,7 +40,7 @@ class AmazonQToolWindow private constructor(
3640
private val browserConnector = BrowserConnector()
3741
private val editorThemeAdapter = EditorThemeAdapter()
3842

39-
private val chatPanel = AmazonQPanel(parent = this)
43+
private val chatPanel = AmazonQPanel()
4044

4145
val component: JComponent = chatPanel.component
4246

@@ -48,6 +52,18 @@ class AmazonQToolWindow private constructor(
4852
connectApps()
4953
}
5054

55+
fun disposeAndRecreate() {
56+
browserConnector.uiReady = CompletableDeferred()
57+
chatPanel.disposeAndRecreate()
58+
59+
appConnections.clear()
60+
initConnections()
61+
connectUi()
62+
connectApps()
63+
64+
ApplicationManager.getApplication().messageBus.syncPublisher(LafManagerListener.TOPIC).lookAndFeelChanged(LafManager.getInstance())
65+
}
66+
5167
private fun sendMessage(message: AmazonQMessage, tabType: String) {
5268
appConnections.filter { it.app.tabTypes.contains(tabType) }.forEach {
5369
scope.launch {

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

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33

44
package software.aws.toolkits.jetbrains.services.amazonq.toolwindow
55

6+
import com.intellij.openapi.actionSystem.ActionManager
67
import com.intellij.openapi.application.runInEdt
78
import com.intellij.openapi.project.DumbAware
89
import com.intellij.openapi.project.Project
910
import com.intellij.openapi.wm.ToolWindow
1011
import com.intellij.openapi.wm.ToolWindowFactory
1112
import com.intellij.openapi.wm.ex.ToolWindowEx
13+
import com.intellij.ui.content.Content
14+
import com.intellij.ui.content.ContentManager
1215
import software.aws.toolkits.core.utils.debug
1316
import software.aws.toolkits.core.utils.getLogger
1417
import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection
@@ -21,6 +24,7 @@ import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAu
2124
import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener
2225
import software.aws.toolkits.jetbrains.core.webview.BrowserState
2326
import software.aws.toolkits.jetbrains.services.amazonq.QWebviewPanel
27+
import software.aws.toolkits.jetbrains.services.amazonq.RefreshQChatPanelButtonPressedListener
2428
import software.aws.toolkits.jetbrains.services.amazonq.gettingstarted.openMeetQPage
2529
import software.aws.toolkits.jetbrains.services.amazonq.isQSupportedInThisVersion
2630
import software.aws.toolkits.jetbrains.utils.isQConnected
@@ -33,6 +37,10 @@ import java.awt.event.ComponentEvent
3337

3438
class AmazonQToolWindowFactory : ToolWindowFactory, DumbAware {
3539
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
40+
if (toolWindow is ToolWindowEx) {
41+
val actionManager = ActionManager.getInstance()
42+
toolWindow.setTitleActions(listOf(actionManager.getAction("aws.q.toolwindow.titleBar")))
43+
}
3644
val contentManager = toolWindow.contentManager
3745

3846
project.messageBus.connect().subscribe(
@@ -44,11 +52,24 @@ class AmazonQToolWindowFactory : ToolWindowFactory, DumbAware {
4452
}
4553
)
4654

55+
project.messageBus.connect().subscribe(
56+
RefreshQChatPanelButtonPressedListener.TOPIC,
57+
object : RefreshQChatPanelButtonPressedListener {
58+
override fun onRefresh() {
59+
runInEdt {
60+
contentManager.removeAllContents(true)
61+
prepareChatContent(project, contentManager)
62+
}
63+
}
64+
}
65+
)
66+
4767
project.messageBus.connect().subscribe(
4868
BearerTokenProviderListener.TOPIC,
4969
object : BearerTokenProviderListener {
5070
override fun onChange(providerId: String, newScopes: List<String>?) {
5171
if (ToolkitConnectionManager.getInstance(project).connectionStateForFeature(QConnection.getInstance()) == BearerTokenAuthState.AUTHORIZED) {
72+
contentManager.removeAllContents(true)
5273
val content = contentManager.factory.createContent(AmazonQToolWindow.getInstance(project).component, null, false).also {
5374
it.isCloseable = true
5475
it.isPinnable = true
@@ -63,6 +84,16 @@ class AmazonQToolWindowFactory : ToolWindowFactory, DumbAware {
6384
}
6485
)
6586

87+
val content = prepareChatContent(project, contentManager)
88+
89+
toolWindow.activate(null)
90+
contentManager.setSelectedContent(content)
91+
}
92+
93+
private fun prepareChatContent(
94+
project: Project,
95+
contentManager: ContentManager
96+
): Content {
6697
val component = if (isQConnected(project) && !isQExpired(project)) {
6798
AmazonQToolWindow.getInstance(project).component
6899
} else {
@@ -75,8 +106,7 @@ class AmazonQToolWindowFactory : ToolWindowFactory, DumbAware {
75106
it.isPinnable = true
76107
}
77108
contentManager.addContent(content)
78-
toolWindow.activate(null)
79-
contentManager.setSelectedContent(content)
109+
return content
80110
}
81111

82112
override fun init(toolWindow: ToolWindow) {

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

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

66
import com.intellij.openapi.Disposable
7+
import com.intellij.openapi.util.Disposer
78
import com.intellij.ui.jcef.JBCefJSQuery
89
import org.cef.CefApp
910
import software.aws.toolkits.jetbrains.services.amazonq.util.createBrowser
@@ -14,9 +15,9 @@ typealias MessageReceiver = Function<String, JBCefJSQuery.Response>
1415
/*
1516
Displays the web view for the Amazon Q tool window
1617
*/
17-
class Browser(parent: Disposable) {
18+
class Browser : Disposable {
1819

19-
val jcefBrowser = createBrowser(parent)
20+
val jcefBrowser = createBrowser(this)
2021

2122
val receiveMessageQuery = JBCefJSQuery.create(jcefBrowser)
2223

@@ -32,6 +33,10 @@ class Browser(parent: Disposable) {
3233
loadWebView(isCodeTransformAvailable, isFeatureDevAvailable)
3334
}
3435

36+
override fun dispose() {
37+
Disposer.dispose(jcefBrowser)
38+
}
39+
3540
fun component() = jcefBrowser.component
3641

3742
fun post(message: String) =

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class BrowserConnector(
2727
private val serializer: MessageSerializer = MessageSerializer.getInstance(),
2828
private val themeBrowserAdapter: ThemeBrowserAdapter = ThemeBrowserAdapter(),
2929
) {
30-
val uiReady = CompletableDeferred<Boolean>()
30+
var uiReady = CompletableDeferred<Boolean>()
3131

3232
suspend fun connect(
3333
browser: Browser,
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
action.q.hello.description=Hello description
2+
amazonq.refresh.panel=Refresh Chat Session
23
q.hello=Hello

0 commit comments

Comments
 (0)