Skip to content

Commit f92fad4

Browse files
authored
Merge branch 'main' into add-chat-log
2 parents 670cc15 + b76a20e commit f92fad4

File tree

18 files changed

+287
-58
lines changed

18 files changed

+287
-58
lines changed

.changes/3.84.json

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

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# _3.84_ (2025-07-09)
2+
13
# _3.83_ (2025-07-07)
24
- **(Bug Fix)** Fix auto-suggestions being shown when suggestions are paused
35

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.84-SNAPSHOT
5+
toolkitVersion=3.85-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.idea.AppMode
78
import com.intellij.openapi.Disposable
89
import com.intellij.openapi.components.service
@@ -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
@@ -44,7 +47,12 @@ import software.aws.toolkits.jetbrains.services.amazonqDoc.auth.isDocAvailable
4447
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.auth.isFeatureDevAvailable
4548
import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable
4649
import software.aws.toolkits.resources.message
50+
import java.awt.datatransfer.DataFlavor
51+
import java.awt.dnd.DropTarget
52+
import java.awt.dnd.DropTargetDropEvent
53+
import java.io.File
4754
import java.util.concurrent.CompletableFuture
55+
import javax.imageio.ImageIO.read
4856
import javax.swing.JButton
4957

5058
class AmazonQPanel(val project: Project, private val scope: CoroutineScope) : Disposable {
@@ -122,12 +130,76 @@ class AmazonQPanel(val project: Project, private val scope: CoroutineScope) : Di
122130

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

128200
initConnections()
129-
connectUi(it)
130-
connectApps(it)
201+
connectUi(browserInstance)
202+
connectApps(browserInstance)
131203

132204
loadingPanel.stopLoading()
133205
}
@@ -211,6 +283,36 @@ class AmazonQPanel(val project: Project, private val scope: CoroutineScope) : Di
211283
}
212284
}
213285

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

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

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ class Browser(parent: Disposable, private val webUri: URI, val project: Project)
8585
// setup empty state. The message request handlers use this for storing state
8686
// that's persistent between page loads.
8787
jcefBrowser.setProperty("state", "")
88+
jcefBrowser.jbCefClient.addDragHandler({ browser, dragData, mask ->
89+
true // Allow drag operations
90+
}, jcefBrowser.cefBrowser)
8891
// load the web app
8992
jcefBrowser.loadHTML(
9093
getWebviewHTML(
@@ -122,7 +125,7 @@ class Browser(parent: Disposable, private val webUri: URI, val project: Project)
122125
<script type="text/javascript" charset="UTF-8" src="$webUri" defer onload="init()"></script>
123126
124127
<script type="text/javascript">
125-
128+
126129
const init = () => {
127130
const hybridChatConnector = connectorAdapter.initiateAdapter(
128131
${MeetQSettings.getInstance().reinvent2024OnboardingCount < MAX_ONBOARDING_PAGE_COUNT},
@@ -139,7 +142,7 @@ class Browser(parent: Disposable, private val webUri: URI, val project: Project)
139142
},
140143
141144
"${activeProfile?.profileName.orEmpty()}")
142-
amazonQChat.createChat(
145+
const qChat = amazonQChat.createChat(
143146
{
144147
postMessage: message => {
145148
$postMessageToJavaJsCode
@@ -155,6 +158,29 @@ class Browser(parent: Disposable, private val webUri: URI, val project: Project)
155158
hybridChatConnector,
156159
${CodeWhispererFeatureConfigService.getInstance().getFeatureConfigJsonString()}
157160
);
161+
162+
window.handleNativeDrop = function(filePath) {
163+
const parsedFilePath = JSON.parse(filePath);
164+
const contextArray = parsedFilePath.map(fullPath => {
165+
const fileName = fullPath.split(/[\\/]/).pop();
166+
return {
167+
command: fileName,
168+
label: 'image',
169+
route: [fullPath],
170+
description: fullPath
171+
};
172+
});
173+
qChat.addCustomContextToPrompt(qChat.getSelectedTabId(), contextArray);
174+
};
175+
176+
window.handleNativeNotify = function(errorMessages) {
177+
const messages = JSON.parse(errorMessages);
178+
messages.forEach(msg => {
179+
qChat.notify({
180+
content: msg
181+
})
182+
});
183+
};
158184
}
159185
</script>
160186
""".trimIndent()

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GetSe
7777
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.LIST_MCP_SERVERS_REQUEST_METHOD
7878
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.LIST_RULES_REQUEST_METHOD
7979
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.MCP_SERVER_CLICK_REQUEST_METHOD
80+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OPEN_FILE_DIALOG
81+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OPEN_FILE_DIALOG_REQUEST_METHOD
8082
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OPEN_SETTINGS
8183
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OPEN_WORKSPACE_SETTINGS_KEY
8284
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenSettingsNotification
@@ -490,6 +492,19 @@ class BrowserConnector(
490492
)
491493
}
492494
}
495+
496+
OPEN_FILE_DIALOG -> {
497+
handleChat(AmazonQChatServer.showOpenFileDialog, node)
498+
.whenComplete { response, _ ->
499+
browser.postChat(
500+
FlareUiMessage(
501+
command = OPEN_FILE_DIALOG_REQUEST_METHOD,
502+
params = response
503+
)
504+
)
505+
}
506+
}
507+
493508
LIST_RULES_REQUEST_METHOD -> {
494509
handleChat(AmazonQChatServer.listRules, node)
495510
.whenComplete { response, _ ->

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

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
@file:Suppress("BannedImports")
44
package software.aws.toolkits.jetbrains.services.cwc.commands
55

6-
import com.google.gson.Gson
76
import com.intellij.openapi.application.ApplicationManager
87
import com.intellij.openapi.project.Project
98
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -17,7 +16,6 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SEND_
1716
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SendToPromptParams
1817
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.TriggerType
1918
import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage
20-
import software.aws.toolkits.jetbrains.services.amazonqCodeTest.controller.TestCommandMessage
2119
import software.aws.toolkits.jetbrains.services.cwc.editor.context.ActiveFileContextExtractor
2220
import software.aws.toolkits.jetbrains.services.cwc.editor.context.ExtractionTriggerType
2321

@@ -28,25 +26,21 @@ class ActionRegistrar {
2826
val flow = _messages.asSharedFlow()
2927

3028
fun reportMessageClick(command: EditorContextCommand, project: Project) {
31-
if (command == EditorContextCommand.GenerateUnitTests) {
32-
AsyncChatUiListener.notifyPartialMessageUpdate(project, Gson().toJson(TestCommandMessage()))
33-
} else {
34-
// new agentic chat route
35-
ApplicationManager.getApplication().executeOnPooledThread {
36-
runBlocking {
37-
val contextExtractor = ActiveFileContextExtractor.create(fqnWebviewAdapter = null, project = project)
38-
val fileContext = contextExtractor.extractContextForTrigger(ExtractionTriggerType.ContextMenu)
39-
val codeSelection = "\n```\n${fileContext.focusAreaContext?.codeSelection?.trimIndent()?.trim()}\n```\n"
40-
var uiMessage: FlareUiMessage? = null
41-
if (command.verb != SEND_TO_PROMPT) {
42-
val params = GenericCommandParams(selection = codeSelection, triggerType = TriggerType.CONTEXT_MENU, genericCommand = command.name)
43-
uiMessage = FlareUiMessage(command = GENERIC_COMMAND, params = params)
44-
} else {
45-
val params = SendToPromptParams(selection = codeSelection, triggerType = TriggerType.CONTEXT_MENU)
46-
uiMessage = FlareUiMessage(command = SEND_TO_PROMPT, params = params)
47-
}
48-
AsyncChatUiListener.notifyPartialMessageUpdate(project, uiMessage)
29+
// new agentic chat route
30+
ApplicationManager.getApplication().executeOnPooledThread {
31+
runBlocking {
32+
val contextExtractor = ActiveFileContextExtractor.create(fqnWebviewAdapter = null, project = project)
33+
val fileContext = contextExtractor.extractContextForTrigger(ExtractionTriggerType.ContextMenu)
34+
val codeSelection = "\n```\n${fileContext.focusAreaContext?.codeSelection?.trimIndent()?.trim()}\n```\n"
35+
var uiMessage: FlareUiMessage? = null
36+
if (command.verb != SEND_TO_PROMPT) {
37+
val params = GenericCommandParams(selection = codeSelection, triggerType = TriggerType.CONTEXT_MENU, genericCommand = command.name)
38+
uiMessage = FlareUiMessage(command = GENERIC_COMMAND, params = params)
39+
} else {
40+
val params = SendToPromptParams(selection = codeSelection, triggerType = TriggerType.CONTEXT_MENU)
41+
uiMessage = FlareUiMessage(command = SEND_TO_PROMPT, params = params)
4942
}
43+
AsyncChatUiListener.notifyPartialMessageUpdate(project, uiMessage)
5044
}
5145
}
5246
}

plugins/amazonq/mynah-ui/src/mynah-ui/ui/quickActions/generator.ts

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ export class QuickActionGenerator {
3838

3939
const quickActionCommands = [
4040
{
41-
groupName: `Q Developer Agent for <b>Software Development</b>`,
4241
commands: [
4342
...(this.isFeatureDevEnabled
4443
? [
@@ -78,11 +77,6 @@ export class QuickActionGenerator {
7877
},
7978
]
8079
: []),
81-
],
82-
},
83-
{
84-
groupName: `Q Developer Agent for <b>Code Transformation</b>`,
85-
commands:[
8680
...(this.isCodeTransformEnabled
8781
? [
8882
{
@@ -93,22 +87,7 @@ export class QuickActionGenerator {
9387
]
9488
: []),
9589
],
96-
},
97-
{
98-
groupName: 'Quick Actions',
99-
commands: [
100-
{
101-
command: '/help',
102-
icon: MynahIcons.HELP,
103-
description: 'Learn more about Amazon Q',
104-
},
105-
{
106-
command: '/clear',
107-
icon: MynahIcons.TRASH,
108-
description: 'Clear this session',
109-
},
110-
],
111-
},
90+
}
11291
].filter((section) => section.commands.length > 0)
11392

11493
const commandUnavailability: Record<

0 commit comments

Comments
 (0)