@@ -5,23 +5,67 @@ package software.aws.toolkits.jetbrains.services.amazonq.toolwindow
5
5
6
6
import com.intellij.idea.AppMode
7
7
import com.intellij.openapi.Disposable
8
+ import com.intellij.openapi.application.ApplicationManager
9
+ import com.intellij.openapi.application.runInEdt
10
+ import com.intellij.openapi.components.service
11
+ import com.intellij.openapi.project.Project
8
12
import com.intellij.openapi.util.Disposer
13
+ import com.intellij.ui.components.JBLoadingPanel
9
14
import com.intellij.ui.components.JBTextArea
10
15
import com.intellij.ui.components.panels.Wrapper
11
16
import com.intellij.ui.dsl.builder.Align
12
17
import com.intellij.ui.dsl.builder.AlignX
13
18
import com.intellij.ui.dsl.builder.AlignY
14
19
import com.intellij.ui.dsl.builder.panel
15
20
import com.intellij.ui.jcef.JBCefApp
21
+ import kotlinx.coroutines.CoroutineScope
22
+ import kotlinx.coroutines.launch
23
+ import kotlinx.coroutines.runBlocking
16
24
import software.aws.toolkits.jetbrains.isDeveloperMode
25
+ import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext
26
+ import software.aws.toolkits.jetbrains.services.amazonq.apps.AppConnection
27
+ import software.aws.toolkits.jetbrains.services.amazonq.commands.MessageTypeRegistry
28
+ import software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts.ArtifactManager
29
+ import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.AsyncChatUiListener
30
+ import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.FlareUiMessage
31
+ import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage
32
+ import software.aws.toolkits.jetbrains.services.amazonq.messages.MessageConnector
33
+ import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager
34
+ import software.aws.toolkits.jetbrains.services.amazonq.util.highlightCommand
17
35
import software.aws.toolkits.jetbrains.services.amazonq.webview.Browser
18
- import java.awt.event.ActionListener
36
+ import software.aws.toolkits.jetbrains.services.amazonq.webview.BrowserConnector
37
+ import software.aws.toolkits.jetbrains.services.amazonq.webview.FqnWebviewAdapter
38
+ import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.EditorThemeAdapter
39
+ import software.aws.toolkits.jetbrains.services.amazonqCodeScan.auth.isCodeScanAvailable
40
+ import software.aws.toolkits.jetbrains.services.amazonqCodeTest.auth.isCodeTestAvailable
41
+ import software.aws.toolkits.jetbrains.services.amazonqDoc.auth.isDocAvailable
42
+ import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.auth.isFeatureDevAvailable
43
+ import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable
44
+ import java.util.concurrent.CompletableFuture
19
45
import javax.swing.JButton
20
46
21
- class AmazonQPanel (private val parent : Disposable ) {
47
+ class AmazonQPanel (val project : Project , private val scope : CoroutineScope ) : Disposable {
48
+ private val browser = CompletableFuture <Browser >()
22
49
private val webviewContainer = Wrapper ()
23
- var browser: Browser ? = null
24
- private set
50
+ private val appSource = AppSource ()
51
+ private val browserConnector = BrowserConnector (project = project)
52
+ private val editorThemeAdapter = EditorThemeAdapter ()
53
+ private val appConnections = mutableListOf<AppConnection >()
54
+
55
+ init {
56
+ project.messageBus.connect().subscribe(
57
+ AsyncChatUiListener .TOPIC ,
58
+ object : AsyncChatUiListener {
59
+ override fun onChange (command : String ) {
60
+ browser.get()?.postChat(command)
61
+ }
62
+
63
+ override fun onChange (command : FlareUiMessage ) {
64
+ browser.get()?.postChat(command)
65
+ }
66
+ }
67
+ )
68
+ }
25
69
26
70
val component = panel {
27
71
row {
@@ -34,14 +78,12 @@ class AmazonQPanel(private val parent: Disposable) {
34
78
row {
35
79
cell(
36
80
JButton (" Show Web Debugger" ).apply {
37
- addActionListener(
38
- ActionListener {
39
- // Code to be executed when the button is clicked
40
- // Add your logic here
41
-
42
- browser?.jcefBrowser?.openDevtools()
43
- },
44
- )
81
+ addActionListener {
82
+ // Code to be executed when the button is clicked
83
+ // Add your logic here
84
+
85
+ browser.get().jcefBrowser.openDevtools()
86
+ }
45
87
},
46
88
)
47
89
.align(AlignX .CENTER )
@@ -51,31 +93,114 @@ class AmazonQPanel(private val parent: Disposable) {
51
93
}
52
94
53
95
init {
54
- init ()
55
- }
56
-
57
- fun disposeAndRecreate () {
58
- webviewContainer.removeAll()
59
- val toDispose = browser
60
- init ()
61
- if (toDispose != null ) {
62
- Disposer .dispose(toDispose)
63
- }
64
- }
65
-
66
- private fun init () {
67
96
if (! JBCefApp .isSupported()) {
68
97
// Fallback to an alternative browser-less solution
69
98
if (AppMode .isRemoteDevHost()) {
70
99
webviewContainer.add(JBTextArea (" Amazon Q chat is not supported in remote dev environment." ))
71
100
} else {
72
101
webviewContainer.add(JBTextArea (" JCEF not supported" ))
73
102
}
74
- browser = null
103
+ browser.complete( null )
75
104
} else {
76
- browser = Browser (parent).also {
77
- webviewContainer.add(it.component())
105
+ val loadingPanel = JBLoadingPanel (null , this )
106
+ val wrapper = Wrapper ()
107
+ loadingPanel.startLoading()
108
+
109
+ webviewContainer.add(wrapper)
110
+ wrapper.setContent(loadingPanel)
111
+
112
+ ApplicationManager .getApplication().executeOnPooledThread {
113
+ val webUri = runBlocking { service<ArtifactManager >().fetchArtifact(project).resolve(" amazonq-ui.js" ).toUri() }
114
+ loadingPanel.stopLoading()
115
+ runInEdt {
116
+ browser.complete(
117
+ Browser (this , webUri, project).also {
118
+ wrapper.setContent(it.component())
119
+
120
+ initConnections()
121
+ connectUi(it)
122
+ connectApps(it)
123
+ }
124
+ )
125
+ }
78
126
}
79
127
}
80
128
}
129
+
130
+ fun sendMessage (message : AmazonQMessage , tabType : String ) {
131
+ appConnections.filter { it.app.tabTypes.contains(tabType) }.forEach {
132
+ scope.launch {
133
+ it.messagesFromUiToApp.publish(message)
134
+ }
135
+ }
136
+ }
137
+
138
+ fun sendMessageAppToUi (message : AmazonQMessage , tabType : String ) {
139
+ appConnections.filter { it.app.tabTypes.contains(tabType) }.forEach {
140
+ scope.launch {
141
+ it.messagesFromAppToUi.publish(message)
142
+ }
143
+ }
144
+ }
145
+
146
+ private fun initConnections () {
147
+ val apps = appSource.getApps(project)
148
+ apps.forEach { app ->
149
+ appConnections + = AppConnection (
150
+ app = app,
151
+ messagesFromAppToUi = MessageConnector (),
152
+ messagesFromUiToApp = MessageConnector (),
153
+ messageTypeRegistry = MessageTypeRegistry (),
154
+ )
155
+ }
156
+ }
157
+
158
+ private fun connectApps (browser : Browser ) {
159
+ val fqnWebviewAdapter = FqnWebviewAdapter (browser.jcefBrowser, browserConnector)
160
+
161
+ appConnections.forEach { connection ->
162
+ val initContext = AmazonQAppInitContext (
163
+ project = project,
164
+ messagesFromAppToUi = connection.messagesFromAppToUi,
165
+ messagesFromUiToApp = connection.messagesFromUiToApp,
166
+ messageTypeRegistry = connection.messageTypeRegistry,
167
+ fqnWebviewAdapter = fqnWebviewAdapter,
168
+ )
169
+ // Connect the app to the UI
170
+ connection.app.init (initContext)
171
+ // Dispose of the app when the tool window is disposed.
172
+ Disposer .register(this , connection.app)
173
+ }
174
+ }
175
+
176
+ private fun connectUi (browser : Browser ) {
177
+ browser.init (
178
+ isCodeTransformAvailable = isCodeTransformAvailable(project),
179
+ isFeatureDevAvailable = isFeatureDevAvailable(project),
180
+ isCodeScanAvailable = isCodeScanAvailable(project),
181
+ isCodeTestAvailable = isCodeTestAvailable(project),
182
+ isDocAvailable = isDocAvailable(project),
183
+ highlightCommand = highlightCommand(),
184
+ activeProfile = QRegionProfileManager .getInstance().takeIf { it.shouldDisplayProfileInfo(project) }?.activeProfile(project)
185
+ )
186
+
187
+ scope.launch {
188
+ // Pipe messages from the UI to the relevant apps and vice versa
189
+ browserConnector.connect(
190
+ browser = browser,
191
+ connections = appConnections,
192
+ )
193
+ }
194
+
195
+ scope.launch {
196
+ // Update the theme in the UI when the IDE theme changes
197
+ browserConnector.connectTheme(
198
+ chatBrowser = browser.jcefBrowser.cefBrowser,
199
+ themeSource = editorThemeAdapter.onThemeChange(),
200
+ )
201
+ }
202
+ }
203
+
204
+ override fun dispose () {
205
+ }
81
206
}
0 commit comments