Skip to content

Commit c361946

Browse files
committed
Merge remote-tracking branch 'origin/main' into HEAD
2 parents 882e6ff + c73ed14 commit c361946

File tree

27 files changed

+809
-138
lines changed

27 files changed

+809
-138
lines changed

.changes/3.85.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"date" : "2025-07-10",
3+
"version" : "3.85",
4+
"entries" : [ {
5+
"type" : "feature",
6+
"description" : "Amazon Q /test, /doc, and /dev capabilities integrated into Agentic coding."
7+
}, {
8+
"type" : "feature",
9+
"description" : "Add image context support"
10+
} ]
11+
}

.changes/3.86.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"date" : "2025-07-16",
3+
"version" : "3.86",
4+
"entries" : [ {
5+
"type" : "bugfix",
6+
"description" : "- Fixed \"Insert to Cursor\" button to correctly insert code blocks at the current cursor position in the active file"
7+
} ]
8+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "bugfix",
3+
"description" : "Suppress IDE error when current editor context is not valid for Amazon Q"
4+
}

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# _3.86_ (2025-07-16)
2+
- **(Bug Fix)** - Fixed "Insert to Cursor" button to correctly insert code blocks at the current cursor position in the active file
3+
4+
# _3.85_ (2025-07-10)
5+
- **(Feature)** Amazon Q /test, /doc, and /dev capabilities integrated into Agentic coding.
6+
- **(Feature)** Add image context support
7+
18
# _3.84_ (2025-07-09)
29

310
# _3.83_ (2025-07-07)

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# SPDX-License-Identifier: Apache-2.0
33

44
# Toolkit Version
5-
toolkitVersion=3.85-SNAPSHOT
5+
toolkitVersion=3.87-SNAPSHOT
66

77
# Publish Settings
88
publishToken=

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

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

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

6+
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
67
import com.intellij.openapi.Disposable
78
import com.intellij.openapi.components.service
89
import com.intellij.openapi.project.Project
@@ -20,6 +21,8 @@ import kotlinx.coroutines.CoroutineScope
2021
import kotlinx.coroutines.flow.first
2122
import kotlinx.coroutines.launch
2223
import kotlinx.coroutines.withContext
24+
import software.aws.toolkits.core.utils.error
25+
import software.aws.toolkits.core.utils.getLogger
2326
import software.aws.toolkits.jetbrains.core.coroutines.EDT
2427
import software.aws.toolkits.jetbrains.isDeveloperMode
2528
import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext
@@ -45,7 +48,12 @@ import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.auth.isFeature
4548
import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable
4649
import software.aws.toolkits.jetbrains.utils.isRunningOnRemoteBackend
4750
import software.aws.toolkits.resources.message
51+
import java.awt.datatransfer.DataFlavor
52+
import java.awt.dnd.DropTarget
53+
import java.awt.dnd.DropTargetDropEvent
54+
import java.io.File
4855
import java.util.concurrent.CompletableFuture
56+
import javax.imageio.ImageIO.read
4957
import javax.swing.JButton
5058

5159
class AmazonQPanel(val project: Project, private val scope: CoroutineScope) : Disposable {
@@ -130,12 +138,76 @@ class AmazonQPanel(val project: Project, private val scope: CoroutineScope) : Di
130138

131139
withContext(EDT) {
132140
browser.complete(
133-
Browser(this@AmazonQPanel, mynahAsset, project).also {
134-
wrapper.setContent(it.component())
141+
Browser(this@AmazonQPanel, mynahAsset, project).also { browserInstance ->
142+
wrapper.setContent(browserInstance.component())
143+
144+
// Add DropTarget to the browser component
145+
// JCEF does not propagate OS-level dragenter, dragOver and drop into DOM.
146+
// As an alternative, enabling the native drag in JCEF,
147+
// and let the native handling the drop event, and update the UI through JS bridge.
148+
val dropTarget = object : DropTarget() {
149+
override fun drop(dtde: DropTargetDropEvent) {
150+
try {
151+
dtde.acceptDrop(dtde.dropAction)
152+
val transferable = dtde.transferable
153+
if (transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
154+
val fileList = transferable.getTransferData(DataFlavor.javaFileListFlavor) as List<*>
155+
156+
val errorMessages = mutableListOf<String>()
157+
val validImages = mutableListOf<File>()
158+
val allowedTypes = setOf("jpg", "jpeg", "png", "gif", "webp")
159+
val maxFileSize = 3.75 * 1024 * 1024 // 3.75MB in bytes
160+
val maxDimension = 8000
161+
162+
for (file in fileList as List<File>) {
163+
val validationResult = validateImageFile(file, allowedTypes, maxFileSize, maxDimension)
164+
if (validationResult != null) {
165+
errorMessages.add(validationResult)
166+
} else {
167+
validImages.add(file)
168+
}
169+
}
170+
171+
// File count restriction
172+
if (validImages.size > 20) {
173+
errorMessages.add("A maximum of 20 images can be added to a single message.")
174+
validImages.subList(20, validImages.size).clear()
175+
}
176+
177+
val json = OBJECT_MAPPER.writeValueAsString(validImages)
178+
browserInstance.jcefBrowser.cefBrowser.executeJavaScript(
179+
"window.handleNativeDrop('$json')",
180+
browserInstance.jcefBrowser.cefBrowser.url,
181+
0
182+
)
183+
184+
val errorJson = OBJECT_MAPPER.writeValueAsString(errorMessages)
185+
browserInstance.jcefBrowser.cefBrowser.executeJavaScript(
186+
"window.handleNativeNotify('$errorJson')",
187+
browserInstance.jcefBrowser.cefBrowser.url,
188+
0
189+
)
190+
dtde.dropComplete(true)
191+
} else {
192+
dtde.dropComplete(false)
193+
}
194+
} catch (e: Exception) {
195+
LOG.error { "Failed to handle file drop operation: ${e.message}" }
196+
dtde.dropComplete(false)
197+
}
198+
}
199+
}
200+
201+
// Set DropTarget on the browser component and its children
202+
browserInstance.component()?.let { component ->
203+
component.dropTarget = dropTarget
204+
// Also try setting on parent if needed
205+
component.parent?.dropTarget = dropTarget
206+
}
135207

136208
initConnections()
137-
connectUi(it)
138-
connectApps(it)
209+
connectUi(browserInstance)
210+
connectApps(browserInstance)
139211

140212
loadingPanel.stopLoading()
141213
}
@@ -219,6 +291,36 @@ class AmazonQPanel(val project: Project, private val scope: CoroutineScope) : Di
219291
}
220292
}
221293

294+
private fun validateImageFile(file: File, allowedTypes: Set<String>, maxFileSize: Double, maxDimension: Int): String? {
295+
val fileName = file.name
296+
val ext = fileName.substringAfterLast('.', "").lowercase()
297+
298+
if (ext !in allowedTypes) {
299+
return "$fileName: File must be an image in JPEG, PNG, GIF, or WebP format."
300+
}
301+
302+
if (file.length() > maxFileSize) {
303+
return "$fileName: Image must be no more than 3.75MB in size."
304+
}
305+
306+
return try {
307+
val img = read(file)
308+
when {
309+
img == null -> "$fileName: File could not be read as an image."
310+
img.width > maxDimension -> "$fileName: Image must be no more than 8,000px in width."
311+
img.height > maxDimension -> "$fileName: Image must be no more than 8,000px in height."
312+
else -> null
313+
}
314+
} catch (e: Exception) {
315+
"$fileName: File could not be read as an image."
316+
}
317+
}
318+
319+
companion object {
320+
private val LOG = getLogger<AmazonQPanel>()
321+
private val OBJECT_MAPPER = jacksonObjectMapper()
322+
}
323+
222324
override fun dispose() {
223325
}
224326
}

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

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ class Browser(parent: Disposable, private val mynahAsset: Path, val project: Pro
8989
// setup empty state. The message request handlers use this for storing state
9090
// that's persistent between page loads.
9191
jcefBrowser.setProperty("state", "")
92-
92+
jcefBrowser.jbCefClient.addDragHandler({ browser, dragData, mask ->
93+
true // Allow drag operations
94+
}, jcefBrowser.cefBrowser)
9395
// load the web app
9496
jcefBrowser.loadURL(
9597
assetRequestHandler.createResource(
@@ -130,7 +132,7 @@ class Browser(parent: Disposable, private val mynahAsset: Path, val project: Pro
130132
<script type="text/javascript" charset="UTF-8" src="$connectorAdapterPath"></script>
131133
<script type="text/javascript" charset="UTF-8" src="$mynahResource" defer onload="init()"></script>
132134
<script type="text/javascript">
133-
135+
134136
const init = () => {
135137
const hybridChatConnector = connectorAdapter.initiateAdapter(
136138
${MeetQSettings.getInstance().reinvent2024OnboardingCount < MAX_ONBOARDING_PAGE_COUNT},
@@ -147,7 +149,7 @@ class Browser(parent: Disposable, private val mynahAsset: Path, val project: Pro
147149
},
148150
149151
"${activeProfile?.profileName.orEmpty()}")
150-
amazonQChat.createChat(
152+
const qChat = amazonQChat.createChat(
151153
{
152154
postMessage: message => {
153155
$postMessageToJavaJsCode
@@ -163,6 +165,29 @@ class Browser(parent: Disposable, private val mynahAsset: Path, val project: Pro
163165
hybridChatConnector,
164166
${CodeWhispererFeatureConfigService.getInstance().getFeatureConfigJsonString()}
165167
);
168+
169+
window.handleNativeDrop = function(filePath) {
170+
const parsedFilePath = JSON.parse(filePath);
171+
const contextArray = parsedFilePath.map(fullPath => {
172+
const fileName = fullPath.split(/[\\/]/).pop();
173+
return {
174+
command: fileName,
175+
label: 'image',
176+
route: [fullPath],
177+
description: fullPath
178+
};
179+
});
180+
qChat.addCustomContextToPrompt(qChat.getSelectedTabId(), contextArray);
181+
};
182+
183+
window.handleNativeNotify = function(errorMessages) {
184+
const messages = JSON.parse(errorMessages);
185+
messages.forEach(msg => {
186+
qChat.notify({
187+
content: msg
188+
})
189+
});
190+
};
166191
}
167192
</script>
168193
""".trimIndent()

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

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import org.eclipse.lsp4j.jsonrpc.ResponseErrorException
3131
import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode
3232
import software.aws.toolkits.core.utils.error
3333
import software.aws.toolkits.core.utils.getLogger
34+
import software.aws.toolkits.core.utils.info
3435
import software.aws.toolkits.core.utils.warn
3536
import software.aws.toolkits.jetbrains.services.amazonq.apps.AppConnection
3637
import software.aws.toolkits.jetbrains.services.amazonq.commands.MessageSerializer
@@ -74,9 +75,12 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.Encry
7475
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.EncryptedQuickActionChatParams
7576
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GET_SERIALIZED_CHAT_REQUEST_METHOD
7677
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GetSerializedChatResponse
78+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.LIST_AVAILABLE_MODELS
7779
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.LIST_MCP_SERVERS_REQUEST_METHOD
7880
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.LIST_RULES_REQUEST_METHOD
7981
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.MCP_SERVER_CLICK_REQUEST_METHOD
82+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OPEN_FILE_DIALOG
83+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OPEN_FILE_DIALOG_REQUEST_METHOD
8084
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OPEN_SETTINGS
8185
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OPEN_WORKSPACE_SETTINGS_KEY
8286
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenSettingsNotification
@@ -301,6 +305,7 @@ class BrowserConnector(
301305
}
302306

303307
CHAT_READY -> {
308+
LOG.info { "Amazon Q Chat UI loaded and ready for input" }
304309
handleChat(AmazonQChatServer.chatReady, node) { params, invoke ->
305310
uiReady.complete(true)
306311
chatCommunicationManager.setUiReady()
@@ -351,7 +356,22 @@ class BrowserConnector(
351356
}
352357

353358
CHAT_INSERT_TO_CURSOR -> {
354-
handleChat(AmazonQChatServer.insertToCursorPosition, node)
359+
val editor = FileEditorManager.getInstance(project).selectedTextEditor
360+
val textDocumentIdentifier = editor?.let { TextDocumentIdentifier(toUriString(it.virtualFile)) }
361+
val cursorPosition = editor?.let { LspEditorUtil.getCursorPosition(it) }
362+
363+
val enrichmentParams = mapOf(
364+
"textDocument" to textDocumentIdentifier,
365+
"cursorPosition" to cursorPosition,
366+
)
367+
368+
val insertToCursorPositionParams: ObjectNode = (node.params as ObjectNode)
369+
.setAll(serializer.objectMapper.valueToTree<ObjectNode>(enrichmentParams))
370+
val enrichedNode = (node as ObjectNode).apply {
371+
set<JsonNode>("params", insertToCursorPositionParams)
372+
}
373+
374+
handleChat(AmazonQChatServer.insertToCursorPosition, enrichedNode)
355375
}
356376

357377
CHAT_LINK_CLICK -> {
@@ -489,6 +509,19 @@ class BrowserConnector(
489509
)
490510
}
491511
}
512+
513+
OPEN_FILE_DIALOG -> {
514+
handleChat(AmazonQChatServer.showOpenFileDialog, node)
515+
.whenComplete { response, _ ->
516+
browser.postChat(
517+
FlareUiMessage(
518+
command = OPEN_FILE_DIALOG_REQUEST_METHOD,
519+
params = response
520+
)
521+
)
522+
}
523+
}
524+
492525
LIST_RULES_REQUEST_METHOD -> {
493526
handleChat(AmazonQChatServer.listRules, node)
494527
.whenComplete { response, _ ->
@@ -517,6 +550,17 @@ class BrowserConnector(
517550
CHAT_PINNED_CONTEXT_REMOVE -> {
518551
handleChat(AmazonQChatServer.pinnedContextRemove, node)
519552
}
553+
LIST_AVAILABLE_MODELS -> {
554+
handleChat(AmazonQChatServer.listAvailableModels, node)
555+
.whenComplete { response, _ ->
556+
browser.postChat(
557+
FlareUiMessage(
558+
command = LIST_AVAILABLE_MODELS,
559+
params = response
560+
)
561+
)
562+
}
563+
}
520564
}
521565
}
522566

0 commit comments

Comments
 (0)