From 6caa149146402e77013eae2a3de1e08ba00678b1 Mon Sep 17 00:00:00 2001 From: Richard Li <742829+rli@users.noreply.github.com> Date: Thu, 6 Mar 2025 11:32:32 -0800 Subject: [PATCH 01/12] feat(amazonq): Add support for Amazon Q chat on remote 242+ (#4825) --- ...oolkit-publish-root-conventions.gradle.kts | 6 + plugins/amazonq/build.gradle.kts | 4 - .../services/amazonq/QLoginWebview.kt | 25 +- .../services/amazonq/QOpenPanelAction.kt | 6 +- .../gettingstarted/QGettingStartedContent.kt | 3 +- .../amazonq/toolwindow/AmazonQPanel.kt | 6 +- .../services/amazonq/webview/Browser.kt | 33 +- .../controller/CodeTestChatController.kt | 2 +- .../controller/FeatureDevController.kt | 2 +- .../services/cwc/controller/ChatController.kt | 2 +- .../context/file/FileContextExtractor.kt | 2 +- .../focusArea/FocusAreaContextExtractor.kt | 4 +- .../listeners/InlineChatFileListener.kt | 2 +- .../listeners/InlineChatSelectionListener.kt | 4 +- .../META-INF/codetransform-ext-java.xml | 1 - .../codemodernizer/CodeModernizerManager.kt | 9 - .../plan/CodeModernizerPlanEditorProvider.kt | 3 +- .../summary/CodeModernizerSummaryEditor.kt | 535 ------------------ .../CodeModernizerSummaryEditorProvider.kt | 48 -- .../CodeModernizerSummaryVirtualFile.kt | 20 - .../explorer/actions/ActionFactory.kt | 5 +- .../inlay/CodeWhispererInlayManager.kt | 6 +- .../inlay/CodeWhispererInlayManagerNew.kt | 6 +- .../learn/LearnCodeWhispererEditorProvider.kt | 4 +- .../popup/CodeWhispererPopupComponents.kt | 6 +- .../popup/CodeWhispererPopupManager.kt | 9 +- .../popup/CodeWhispererPopupManagerNew.kt | 6 +- .../status/CodeWhispererStatusBarWidget.kt | 4 +- ...WhispererCodeReferenceToolWindowFactory.kt | 3 +- .../utils/CodeTransformSharedUtils.kt | 2 - .../impl/jcef/JBCefLocalRequestHandler.kt | 87 +++ .../impl/jcef/JBCefStreamResourceHandler.kt | 78 +++ .../webview/LocalAssetJBCefRequestHandler.kt | 83 +++ .../jetbrains/core/webview/LoginBrowser.kt | 2 - .../core/webview/WebviewResourceHandler.kt | 88 --- .../jetbrains/utils/RemoteEnvUtils.kt | 2 +- .../jetbrains/core/BrowserMessageTest.kt | 2 +- .../explorer/webview/ToolkitLoginWebview.kt | 27 +- .../editor/GettingStartedPanel.kt | 11 +- 39 files changed, 336 insertions(+), 812 deletions(-) delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/summary/CodeModernizerSummaryEditor.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/summary/CodeModernizerSummaryEditorProvider.kt delete mode 100644 plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/summary/CodeModernizerSummaryVirtualFile.kt create mode 100644 plugins/core/jetbrains-community/src/contrib/org/intellij/images/editor/impl/jcef/JBCefLocalRequestHandler.kt create mode 100644 plugins/core/jetbrains-community/src/contrib/org/intellij/images/editor/impl/jcef/JBCefStreamResourceHandler.kt create mode 100644 plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/webview/LocalAssetJBCefRequestHandler.kt delete mode 100644 plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/webview/WebviewResourceHandler.kt diff --git a/buildSrc/src/main/kotlin/toolkit-publish-root-conventions.gradle.kts b/buildSrc/src/main/kotlin/toolkit-publish-root-conventions.gradle.kts index 2e4d45b570e..d7b079b1160 100644 --- a/buildSrc/src/main/kotlin/toolkit-publish-root-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/toolkit-publish-root-conventions.gradle.kts @@ -3,6 +3,7 @@ import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType import org.jetbrains.intellij.platform.gradle.tasks.PatchPluginXmlTask +import org.jetbrains.intellij.platform.gradle.tasks.aware.SplitModeAware import software.aws.toolkits.gradle.intellij.IdeFlavor import software.aws.toolkits.gradle.intellij.toolkitIntelliJ @@ -75,3 +76,8 @@ tasks.runIde { systemProperty("user.home", home) environment("HOME", home) } + +val runSplitIde by intellijPlatformTesting.runIde.registering { + splitMode = true + splitModeTarget = SplitModeAware.SplitModeTarget.BACKEND +} diff --git a/plugins/amazonq/build.gradle.kts b/plugins/amazonq/build.gradle.kts index f3caa6e678e..4985f69e765 100644 --- a/plugins/amazonq/build.gradle.kts +++ b/plugins/amazonq/build.gradle.kts @@ -1,11 +1,7 @@ // Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType import software.aws.toolkits.gradle.changelog.tasks.GeneratePluginChangeLog -import software.aws.toolkits.gradle.intellij.IdeFlavor -import software.aws.toolkits.gradle.intellij.IdeVersions -import software.aws.toolkits.gradle.intellij.toolkitIntelliJ plugins { id("toolkit-publishing-conventions") diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt index bc594209fba..a314ea8d49c 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt @@ -16,7 +16,6 @@ import com.intellij.ui.components.panels.Wrapper import com.intellij.ui.dsl.builder.Align import com.intellij.ui.dsl.builder.panel import com.intellij.ui.jcef.JBCefJSQuery -import org.cef.CefApp import software.aws.toolkits.core.utils.error import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.core.utils.warn @@ -30,8 +29,8 @@ import software.aws.toolkits.jetbrains.core.credentials.sono.isSono import software.aws.toolkits.jetbrains.core.region.AwsRegionProvider import software.aws.toolkits.jetbrains.core.webview.BrowserMessage import software.aws.toolkits.jetbrains.core.webview.BrowserState +import software.aws.toolkits.jetbrains.core.webview.LocalAssetJBCefRequestHandler import software.aws.toolkits.jetbrains.core.webview.LoginBrowser -import software.aws.toolkits.jetbrains.core.webview.WebviewResourceHandlerFactory import software.aws.toolkits.jetbrains.isDeveloperMode import software.aws.toolkits.jetbrains.services.amazonq.util.createBrowser import software.aws.toolkits.jetbrains.utils.isQConnected @@ -108,25 +107,14 @@ class QWebviewPanel private constructor(val project: Project) : Disposable { class QWebviewBrowser(val project: Project, private val parentDisposable: Disposable) : LoginBrowser( project, - QWebviewBrowser.DOMAIN, - QWebviewBrowser.WEB_SCRIPT_URI ), Disposable { // TODO: confirm if we need such configuration or the default is fine override val jcefBrowser = createBrowser(parentDisposable) private val query = JBCefJSQuery.create(jcefBrowser) + private val assetHandler = LocalAssetJBCefRequestHandler(jcefBrowser) init { - CefApp.getInstance() - .registerSchemeHandlerFactory( - "http", - domain, - WebviewResourceHandlerFactory( - domain = "http://$domain/", - assetUri = "/webview/assets/" - ), - ) - loadWebView(query) query.addHandler(jcefHandler) @@ -273,12 +261,15 @@ class QWebviewBrowser(val project: Project, private val parentDisposable: Dispos } override fun loadWebView(query: JBCefJSQuery) { - jcefBrowser.loadHTML(getWebviewHTML(webScriptUri, query)) + val webScriptUri = assetHandler.createResource( + "js/getStart.js", + QWebviewBrowser::class.java.getResourceAsStream("/webview/assets/js/getStart.js") + ) + + jcefBrowser.loadURL(assetHandler.createResource("content.html", getWebviewHTML(webScriptUri, query))) } companion object { private val LOG = getLogger() - private const val WEB_SCRIPT_URI = "http://webview/js/getStart.js" - private const val DOMAIN = "webview" } } diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QOpenPanelAction.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QOpenPanelAction.kt index de01e99bade..cab5e06d312 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QOpenPanelAction.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QOpenPanelAction.kt @@ -11,13 +11,11 @@ import icons.AwsIcons import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AMAZON_Q_WINDOW_ID import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindow import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.runScanKey -import software.aws.toolkits.jetbrains.utils.isRunningOnRemoteBackend import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.UiTelemetry class QOpenPanelAction : AnAction(message("action.q.openchat.text"), null, AwsIcons.Logos.AWS_Q) { override fun actionPerformed(e: AnActionEvent) { - if (isRunningOnRemoteBackend()) return val project = e.getRequiredData(CommonDataKeys.PROJECT) UiTelemetry.click(project, "q_openChat") ToolWindowManager.getInstance(project).getToolWindow(AMAZON_Q_WINDOW_ID)?.activate(null, true) @@ -25,4 +23,8 @@ class QOpenPanelAction : AnAction(message("action.q.openchat.text"), null, AwsIc AmazonQToolWindow.openScanTab(project) } } + + override fun update(e: AnActionEvent) { + e.presentation.isEnabled = e.getData(CommonDataKeys.PROJECT) != null + } } diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedContent.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedContent.kt index afb490b9f19..3e81b2c836d 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedContent.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedContent.kt @@ -18,6 +18,7 @@ import org.cef.browser.CefBrowser import org.cef.browser.CefFrame import org.cef.handler.CefLoadHandlerAdapter import software.aws.toolkits.jetbrains.core.coroutines.disposableCoroutineScope +import software.aws.toolkits.jetbrains.core.webview.LocalAssetJBCefRequestHandler import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindow import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.EditorThemeAdapter import software.aws.toolkits.resources.message @@ -72,7 +73,7 @@ class QGettingStartedContent(val project: Project) : Disposable { private fun loadWebView() { // load the web app - jcefBrowser.loadHTML(getWebviewHTML()) + jcefBrowser.loadURL(LocalAssetJBCefRequestHandler(jcefBrowser).createResource("content.html", getWebviewHTML())) } private fun getWebviewHTML(): String { diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt index 396057bc9a9..a0e18e10382 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt @@ -3,7 +3,6 @@ package software.aws.toolkits.jetbrains.services.amazonq.toolwindow -import com.intellij.idea.AppMode import com.intellij.openapi.Disposable import com.intellij.openapi.util.Disposer import com.intellij.ui.components.JBTextArea @@ -15,6 +14,7 @@ import com.intellij.ui.dsl.builder.panel import com.intellij.ui.jcef.JBCefApp import software.aws.toolkits.jetbrains.isDeveloperMode import software.aws.toolkits.jetbrains.services.amazonq.webview.Browser +import software.aws.toolkits.jetbrains.utils.isRunningOnRemoteBackend import java.awt.event.ActionListener import javax.swing.JButton @@ -66,8 +66,8 @@ class AmazonQPanel(private val parent: Disposable) { private fun init() { if (!JBCefApp.isSupported()) { // Fallback to an alternative browser-less solution - if (AppMode.isRemoteDevHost()) { - webviewContainer.add(JBTextArea("Amazon Q chat is not supported in remote dev environment.")) + if (isRunningOnRemoteBackend()) { + webviewContainer.add(JBTextArea("Amazon Q chat is not supported in this remote dev environment because it lacks JCEF webview support.")) } else { webviewContainer.add(JBTextArea("JCEF not supported")) } diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/Browser.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/Browser.kt index cc39985dd36..c08ed48c989 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/Browser.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/Browser.kt @@ -7,10 +7,11 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.intellij.openapi.Disposable import com.intellij.openapi.util.Disposer import com.intellij.ui.jcef.JBCefJSQuery -import org.cef.CefApp +import software.aws.toolkits.jetbrains.core.webview.LocalAssetJBCefRequestHandler import software.aws.toolkits.jetbrains.services.amazonq.util.HighlightCommand import software.aws.toolkits.jetbrains.services.amazonq.util.createBrowser import software.aws.toolkits.jetbrains.settings.MeetQSettings +import java.nio.file.Paths /* Displays the web view for the Amazon Q tool window @@ -21,6 +22,17 @@ class Browser(parent: Disposable) : Disposable { val receiveMessageQuery = JBCefJSQuery.create(jcefBrowser) + private val assetRequestHandler = LocalAssetJBCefRequestHandler(jcefBrowser) + + init { + assetRequestHandler.addWildcardHandler("mynah") { path -> + val asset = path.replaceFirst("mynah/", "/mynah-ui/assets/") + Paths.get(asset).normalize().toString().replace("\\", "/").let { + this::class.java.getResourceAsStream(it) + } + } + } + fun init( isCodeTransformAvailable: Boolean, isFeatureDevAvailable: Boolean, @@ -29,14 +41,6 @@ class Browser(parent: Disposable) : Disposable { isCodeTestAvailable: Boolean, highlightCommand: HighlightCommand?, ) { - // register the scheme handler to route http://mynah/ URIs to the resources/assets directory on classpath - CefApp.getInstance() - .registerSchemeHandlerFactory( - "http", - "mynah", - AssetResourceHandler.AssetResourceHandlerFactory(), - ) - loadWebView(isCodeTransformAvailable, isFeatureDevAvailable, isDocAvailable, isCodeScanAvailable, isCodeTestAvailable, highlightCommand) } @@ -63,9 +67,13 @@ class Browser(parent: Disposable) : Disposable { // setup empty state. The message request handlers use this for storing state // that's persistent between page loads. jcefBrowser.setProperty("state", "") + // load the web app - jcefBrowser.loadHTML( - getWebviewHTML(isCodeTransformAvailable, isFeatureDevAvailable, isDocAvailable, isCodeScanAvailable, isCodeTestAvailable, highlightCommand) + jcefBrowser.loadURL( + assetRequestHandler.createResource( + "webview/chat.html", + getWebviewHTML(isCodeTransformAvailable, isFeatureDevAvailable, isDocAvailable, isCodeScanAvailable, isCodeTestAvailable, highlightCommand) + ) ) } @@ -84,7 +92,7 @@ class Browser(parent: Disposable) : Disposable { val postMessageToJavaJsCode = receiveMessageQuery.inject("JSON.stringify(message)") val jsScripts = """ - + + - - + + """.trimIndent() - addQuickActionCommands( - isCodeTransformAvailable, - isFeatureDevAvailable, - isDocAvailable, - isCodeTestAvailable, - isCodeScanAvailable, - highlightCommand, - activeProfile - ) + // language=HTML return """ - + AWS Q $jsScripts +
Amazon Q is loading...
+ """.trimIndent() } - private fun addQuickActionCommands( - isCodeTransformAvailable: Boolean, - isFeatureDevAvailable: Boolean, - isDocAvailable: Boolean, - isCodeTestAvailable: Boolean, - isCodeScanAvailable: Boolean, - highlightCommand: HighlightCommand?, - activeProfile: QRegionProfile?, - ) { - // TODO: Remove this once chat has been integrated with agents. This is added temporarily to keep detekt happy. - isCodeScanAvailable - isCodeTestAvailable - isDocAvailable - isFeatureDevAvailable - isCodeTransformAvailable - MAX_ONBOARDING_PAGE_COUNT - OBJECT_MAPPER - highlightCommand - activeProfile - } - companion object { private const val MAX_ONBOARDING_PAGE_COUNT = 3 private val OBJECT_MAPPER = jacksonObjectMapper() diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt index 0f994cfb35e..f0c725e8a48 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt @@ -201,7 +201,8 @@ class BrowserConnector( themeSource .distinctUntilChanged() .onEach { - themeBrowserAdapter.updateThemeInBrowser(chatBrowser, it, uiReady) + uiReady.await() + themeBrowserAdapter.updateThemeInBrowser(chatBrowser, it) } .launchIn(this) } @@ -619,7 +620,7 @@ class BrowserConnector( $isDocAvailable, $isCodeScanAvailable, $isCodeTestAvailable, - { postMessage: () => {} } + { postMessage: () => {} }, ); const commands = tempConnector.initialQuickActions?.slice(0, 2) || []; diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/ThemeBrowserAdapter.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/ThemeBrowserAdapter.kt index b3bb829f4ba..b42c96a01e8 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/ThemeBrowserAdapter.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/ThemeBrowserAdapter.kt @@ -3,7 +3,6 @@ package software.aws.toolkits.jetbrains.services.amazonq.webview.theme -import kotlinx.coroutines.CompletableDeferred import org.cef.browser.CefBrowser import java.awt.Color import java.awt.Font @@ -19,13 +18,12 @@ class ThemeBrowserAdapter { browser.executeJavaScript("window.changeTheme(${theme.darkMode})", browser.url, 0) } - suspend fun updateThemeInBrowser(browser: CefBrowser, theme: AmazonQTheme, uiReady: CompletableDeferred) { - uiReady.await() + fun updateThemeInBrowser(browser: CefBrowser, theme: AmazonQTheme) { val codeToUpdateTheme = buildJsCodeToUpdateTheme(theme) browser.executeJavaScript(codeToUpdateTheme, browser.url, 0) } - private fun buildJsCodeToUpdateTheme(theme: AmazonQTheme) = buildString { + fun buildJsCodeToUpdateTheme(theme: AmazonQTheme) = buildString { val (bg, altBg, inputBg) = determineInputAndBgColor(theme) appendDarkMode(theme.darkMode) diff --git a/plugins/amazonq/mynah-ui/src/mynah-ui/connectorAdapter.ts b/plugins/amazonq/mynah-ui/src/mynah-ui/connectorAdapter.ts index 0015e5a940c..c89f92d0a01 100644 --- a/plugins/amazonq/mynah-ui/src/mynah-ui/connectorAdapter.ts +++ b/plugins/amazonq/mynah-ui/src/mynah-ui/connectorAdapter.ts @@ -7,15 +7,6 @@ import { isTabType } from './ui/storages/tabsStorage' import { WebviewUIHandler } from './ui/main' import { TabDataGenerator } from './ui/tabs/generator' import { ChatClientAdapter, ChatEventHandler } from '@aws/chat-client' -import { FqnExtractor } from "./fqn/extractor"; - -export * from "./ui/main"; - -declare global { - interface Window { fqnExtractor: FqnExtractor; } -} - -window.fqnExtractor = new FqnExtractor(); export const initiateAdapter = (showWelcomePage: boolean, disclaimerAcknowledged: boolean, @@ -25,11 +16,12 @@ export const initiateAdapter = (showWelcomePage: boolean, isCodeScanEnabled: boolean, isCodeTestEnabled: boolean, ideApiPostMessage: (message: any) => void, - profileName?: string) : HybridChatAdapter => { - return new HybridChatAdapter(showWelcomePage, disclaimerAcknowledged, isFeatureDevEnabled, isCodeTransformEnabled, isDocEnabled, isCodeScanEnabled, isCodeTestEnabled, ideApiPostMessage, profileName) + highlightCommand?: QuickActionCommand, + profileName?: string, +) : HybridChatAdapter => { + return new HybridChatAdapter(showWelcomePage, disclaimerAcknowledged, isFeatureDevEnabled, isCodeTransformEnabled, isDocEnabled, isCodeScanEnabled, isCodeTestEnabled, ideApiPostMessage, highlightCommand, profileName) } - // Ref: https://github.com/aws/aws-toolkit-vscode/blob/e9ea8082ffe0b9968a873437407d0b6b31b9e1a5/packages/core/src/amazonq/webview/ui/connectorAdapter.ts#L14 export class HybridChatAdapter implements ChatClientAdapter { private uiHandler?: WebviewUIHandler @@ -46,6 +38,7 @@ export class HybridChatAdapter implements ChatClientAdapter { private isCodeScanEnabled: boolean, private isCodeTestEnabled: boolean, private ideApiPostMessage: (message: any) => void, + private highlightCommand?: QuickActionCommand, private profileName?: string, ) {} @@ -67,6 +60,7 @@ export class HybridChatAdapter implements ChatClientAdapter { isDocEnabled: this.isDocEnabled, isCodeScanEnabled: this.isCodeScanEnabled, isCodeTestEnabled: this.isCodeTestEnabled, + highlightCommand: this.highlightCommand, profileName: this.profileName, hybridChat: true, }) diff --git a/plugins/amazonq/mynah-ui/src/mynah-ui/fqn/extractor.ts b/plugins/amazonq/mynah-ui/src/mynah-ui/fqn/extractor.ts deleted file mode 100644 index 2c598c087c1..00000000000 --- a/plugins/amazonq/mynah-ui/src/mynah-ui/fqn/extractor.ts +++ /dev/null @@ -1,133 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -// @ts-ignore -import {findNames, findNamesWithInExtent} from './find-names'; -import {extractContextFromJavaImports} from "./java-import-reader"; - - -export interface FullyQualifiedName { - source: string[], - symbol: string[], -} - -export interface FullyQualifiedNames { - used: FullyQualifiedName[]; -} - -export interface CodeQuery { - simpleNames: string[]; - fullyQualifiedNames: FullyQualifiedNames; -} - -export interface CodeSelection { - selectedCode: string; - file?: { - range: { - start: { - row: string; - column: string; - }; - end: { - row: string; - column: string; - }; - }; - name: string; - }; -} - -export class FqnExtractor { - - async readImports(fileText: string, languageId: string): Promise> { - const names = await findNames(fileText, languageId); - - if (names.fullyQualified === undefined) { - return new Set() - } - - if (languageId === 'java') { - return new Set(extractContextFromJavaImports(names)); - } - - return new Set(names.fullyQualified?.declaredSymbols - .map((symbol: { source: string[] }): string => { - return symbol.source[0].replace('@', '') - }) - .filter((source: string) => source.length !== 0)) - } - - async extractCodeQuery(fileText: string, languageId: string, selection: CodeSelection): Promise<{ codeQuery: CodeQuery | undefined, namesWereTruncated: boolean }> { - const names = selection === undefined ? await findNames(fileText, languageId) : - await findNamesWithInExtent(fileText, languageId, selection.file?.range.start.row, selection.file?.range.start.column, selection.file?.range.end.row, selection.file?.range.end.column); - if (names === undefined || Object.keys(names).length === 0) { - return {codeQuery: undefined, namesWereTruncated: false} - } - - const {simpleNames, simpleNamesListWasLongerThanMaxLength} = this.prepareSimpleNames(names); - - const {usedFullyQualifiedNames, namesWereTruncated} = this.prepareFqns(names); - - return { - codeQuery: { - simpleNames: simpleNames, - fullyQualifiedNames: { used: Array.from(usedFullyQualifiedNames) } - }, - namesWereTruncated: simpleNamesListWasLongerThanMaxLength || namesWereTruncated - } - } - - private prepareSimpleNames(names: any): { simpleNames: string[], simpleNamesListWasLongerThanMaxLength: boolean } { - let simpleNames: string[] = names.simple.usedSymbols - .concat(names.simple.declaredSymbols) - .map((elem: any) => elem.symbol.trim()) - .filter((name: string) => name.length < 129 && name.length > 1); - - const maxSimpleNames = 100; - let simpleNamesListWasLongerThanMaxLength = false; - - if (simpleNames.length > maxSimpleNames) { - simpleNamesListWasLongerThanMaxLength = true - - simpleNames = [...new Set(simpleNames)] - - if (simpleNames.length > maxSimpleNames) { - simpleNames = simpleNames.sort((a, b) => a.length - b.length) - simpleNames.splice(0, simpleNames.length - maxSimpleNames) - } - } - - return {simpleNames, simpleNamesListWasLongerThanMaxLength} - } - - private prepareFqns(names: any): { - readonly usedFullyQualifiedNames: Set - readonly namesWereTruncated: boolean - } { - const usedFullyQualifiedNames: Set = new Set( - names.fullyQualified.usedSymbols.map((name: any) => ({ source: name.source, symbol: name.symbol })) - ) - - const maxUsedFullyQualifiedNamesLength = 25 - - if (usedFullyQualifiedNames.size > maxUsedFullyQualifiedNamesLength) { - const usedFullyQualifiedNamesSorted = Array.from(usedFullyQualifiedNames).sort( - (name, other) => name.source.length + name.symbol.length - (other.source.length + other.symbol.length) - ) - return { - usedFullyQualifiedNames: new Set( - usedFullyQualifiedNamesSorted.slice(0, maxUsedFullyQualifiedNamesLength) - ), - namesWereTruncated: true, - } - } - - return { - usedFullyQualifiedNames, - namesWereTruncated: false, - } - } - -} diff --git a/plugins/amazonq/mynah-ui/src/mynah-ui/fqn/find-names.js b/plugins/amazonq/mynah-ui/src/mynah-ui/fqn/find-names.js deleted file mode 100644 index e14e589dab2..00000000000 --- a/plugins/amazonq/mynah-ui/src/mynah-ui/fqn/find-names.js +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { Java, Python, TypeScript, Tsx } from '@aws/fully-qualified-names' - -export async function findNames(inputCode, languageId) { - const fqn = await import('@aws/fully-qualified-names') - switch (languageId) { - case 'java': - return await fqn.Java.findNames(inputCode); - case 'javascript': - case 'javascriptreact': - case 'typescriptreact': - return await fqn.Tsx.findNames(inputCode); - case 'python': - return await fqn.Python.findNames(inputCode); - case 'typescript': - return await fqn.TypeScript.findNames(inputCode); - default: - return {} - } -} - -export class Selection { - startLine; - startColumn; - endLine; - endColumn; - -} - - -export async function findNamesWithInExtent(fileText, languageId, startLine,startColumn, endLine, endColumn ){ - const fqn = await import('@aws/fully-qualified-names') - - const startLocation = new fqn.Location(startLine, startColumn) - const endLocation = new fqn.Location(endLine, endColumn) - const extent = new fqn.Extent(startLocation, endLocation) - - switch (languageId) { - case 'java': - return await fqn.Java.findNamesWithInExtent(fileText, extent) - case 'javascript': - case 'javascriptreact': - case 'typescriptreact': - return await fqn.Tsx.findNamesWithInExtent(fileText, extent) - case 'python': - return await fqn.Python.findNamesWithInExtent(fileText, extent) - case 'typescript': - return await fqn.TypeScript.findNamesWithInExtent(fileText, extent) - default: - return {} - } -} diff --git a/plugins/amazonq/mynah-ui/src/mynah-ui/fqn/java-import-reader.ts b/plugins/amazonq/mynah-ui/src/mynah-ui/fqn/java-import-reader.ts deleted file mode 100644 index 8209a444db3..00000000000 --- a/plugins/amazonq/mynah-ui/src/mynah-ui/fqn/java-import-reader.ts +++ /dev/null @@ -1,58 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -export interface JavaImport { - tld: string - organisation?: string - packages?: string[] -} - -export function extractContextFromJavaImports(names: any): string[] { - return names.fullyQualified?.declaredSymbols - .map((symbol: any): JavaImport => { - const sourcesCount = symbol.source.length - return { - tld: symbol.source[0], - organisation: sourcesCount > 1 ? symbol.source[1] : undefined, - packages: sourcesCount > 2 ? symbol.source.slice(2) : undefined, - } - }) - .map((javaImport: JavaImport): string => { - const importStatement = toString(javaImport) - if (commonJavaImportsPrefixesRegex.test(importStatement)) { - return '' - } else if (importStatement.startsWith(awsJavaSdkV1Prefix)) { - //@ts-ignore - return javaImport.packages?.at(1) ?? '' - } else if (importStatement.startsWith(awsJavaSdkV2Prefix)) { - //@ts-ignore - return javaImport.packages?.at(2) ?? '' - } else { - //@ts-ignore - return javaImport.packages?.at(0) ?? javaImport.organisation ?? javaImport.tld - } - }) - .filter((context: string) => context !== '') -} - -function toString(javaImport: JavaImport): string { - let importSegments: string[] = [] - importSegments.push(javaImport.tld) - if (javaImport.organisation !== undefined) { - importSegments.push(javaImport.organisation) - } - if (javaImport.packages !== undefined) { - importSegments = importSegments.concat(javaImport.packages) - } - return importSegments.join('.') + '.' -} - -const commonJavaImportsPrefixesRegex = new RegExp( - '^(java.|javax.|org.slf4j.|org.apache.log4j.|org.apache.logging.log4j.|org.junit.|org.testng.)' -) - -const awsJavaSdkV1Prefix = 'com.amazonaws.services' - -const awsJavaSdkV2Prefix = 'software.amazon.awssdk.services' diff --git a/plugins/amazonq/mynah-ui/src/mynah-ui/index.ts b/plugins/amazonq/mynah-ui/src/mynah-ui/index.ts deleted file mode 100644 index 1658ec7e5b8..00000000000 --- a/plugins/amazonq/mynah-ui/src/mynah-ui/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import { FqnExtractor } from "./fqn/extractor"; - -export * from "./ui/main"; - -declare global { - interface Window { fqnExtractor: FqnExtractor; } -} - -window.fqnExtractor = new FqnExtractor(); diff --git a/plugins/amazonq/mynah-ui/webpack.media.config.js b/plugins/amazonq/mynah-ui/webpack.media.config.js index c11715ddf97..60bd564b664 100644 --- a/plugins/amazonq/mynah-ui/webpack.media.config.js +++ b/plugins/amazonq/mynah-ui/webpack.media.config.js @@ -3,60 +3,9 @@ 'use strict'; const path = require('path'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); /**@type {import('webpack').Configuration}*/ -const config = { - plugins: [ - new CopyWebpackPlugin({ - patterns: [ - { from: './node_modules/web-tree-sitter/tree-sitter.wasm', to: ''} - ] - }) - ], - target: 'web', - entry: './src/mynah-ui/index.ts', - output: { - path: path.resolve(__dirname, 'build/assets/js'), - filename: 'mynah-ui.js', - library: 'mynahUI', - libraryTarget: 'var', - devtoolModuleFilenameTemplate: '../[resource-path]', - }, - devtool: 'source-map', - resolve: { - extensions: ['.ts', '.js', '.wasm'], - fallback: { - fs: false, - path: false, - util: false - } - }, - experiments: { asyncWebAssembly: true }, - module: { - rules: [ - {test: /\.(sa|sc|c)ss$/, use: ['style-loader', 'css-loader', 'sass-loader']}, - { - test: /\.ts$/, - exclude: /node_modules/, - use: [ - { - loader: 'ts-loader', - }, - ], - }, - ], - }, -}; - const connectorAdapter = { - plugins: [ - new CopyWebpackPlugin({ - patterns: [ - { from: './node_modules/web-tree-sitter/tree-sitter.wasm', to: ''} - ] - }) - ], target: 'web', entry: './src/mynah-ui/connectorAdapter.ts', output: { @@ -73,7 +22,7 @@ const connectorAdapter = { fs: false, path: false, util: false - } + }, }, experiments: { asyncWebAssembly: true }, module: { @@ -91,4 +40,5 @@ const connectorAdapter = { ], }, }; -module.exports = [config, connectorAdapter]; + +module.exports = [connectorAdapter]; diff --git a/plugins/amazonq/shared/jetbrains-community/build.gradle.kts b/plugins/amazonq/shared/jetbrains-community/build.gradle.kts index bb04cda998e..205c6806b86 100644 --- a/plugins/amazonq/shared/jetbrains-community/build.gradle.kts +++ b/plugins/amazonq/shared/jetbrains-community/build.gradle.kts @@ -1,6 +1,7 @@ // Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +import org.jetbrains.intellij.platform.gradle.models.Coordinates import software.aws.toolkits.gradle.intellij.IdeFlavor plugins { @@ -14,6 +15,7 @@ intellijToolkit { dependencies { intellijPlatform { localPlugin(project(":plugin-core")) + platformDependency(Coordinates(groupId = "com.jetbrains.intellij.rd", artifactId = "rd-platform")) } compileOnlyApi(project(":plugin-core:jetbrains-community")) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt index 34a87e04737..9a211e8b7f6 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt @@ -126,7 +126,13 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC } override fun showMessage(messageParams: MessageParams) { - notify(messageParams.type.toNotificationType(), message("q.window.title"), getCleanedContent(messageParams.message, true), project, emptyList()) + notify( + messageParams.type.toNotificationType(), + message("toolwindow.stripe.amazon.q.window"), + getCleanedContent(messageParams.message, true), + project, + emptyList() + ) } override fun showMessageRequest(requestParams: ShowMessageRequestParams): CompletableFuture { @@ -137,7 +143,7 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC notify( requestParams.type.toNotificationType(), - message("q.window.title"), + message("toolwindow.stripe.amazon.q.window"), getCleanedContent(requestParams.message, true), project, requestParams.actions.map { item -> diff --git a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties index 122ee65b787..991ca2e98d5 100644 --- a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties +++ b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties @@ -1664,7 +1664,6 @@ q.sign.in=Get Started q.ui.prompt.transform=/transform q.unavailable=\ Amazon Q Chat is not supported in IDE versions <= v2024.2.1 q.unavailable.node=Please update to the latest IDE version -q.window.title=Amazon Q Chat rds.aurora=Aurora rds.iam_config=Connect with IAM... rds.iam_connection_display_name=AWS IAM @@ -2118,5 +2117,6 @@ toolkit.sso_expire.dialog.no_button=Don't show again toolkit.sso_expire.dialog.title=Connection Expired toolkit.sso_expire.dialog.yes_button=Re-authenticate toolkit.sso_expire.dialog_message=Your Amazon Q connection has expired. Please re-authenticate. +toolwindow.stripe.amazon.q.window=Amazon Q Chat toolwindow.stripe.aws.codewhisperer.codereference=Code Reference Log toolwindow.stripe.aws.codewhisperer.codetransform=Transformation Hub From 7335f81073a9bd80509aac6d1a30a6b7bde790d2 Mon Sep 17 00:00:00 2001 From: Richard Li <742829+rli@users.noreply.github.com> Date: Tue, 26 Aug 2025 16:32:30 -0700 Subject: [PATCH 12/12] feat(amazonq): limit remote to amzn-internal compute (#5993) Per internal discussion, feature deemed not ready for external users due to https://youtrack.jetbrains.com/issue/IJPL-203169 --- .../jetbrains/utils/RemoteEnvUtils.kt | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/utils/RemoteEnvUtils.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/utils/RemoteEnvUtils.kt index d955effc1b4..4a57bbd9813 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/utils/RemoteEnvUtils.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/utils/RemoteEnvUtils.kt @@ -5,6 +5,10 @@ package software.aws.toolkits.jetbrains.utils import com.intellij.idea.AppMode import com.intellij.ui.jcef.JBCefApp +import software.aws.toolkits.core.utils.exists +import software.aws.toolkits.core.utils.tryOrNull +import software.aws.toolkits.jetbrains.isDeveloperMode +import java.nio.file.Paths /** * @return true if running in any type of remote environment @@ -16,4 +20,20 @@ fun isRunningOnRemoteBackend() = AppMode.isRemoteDevHost() */ fun isCodeCatalystDevEnv() = System.getenv("__DEV_ENVIRONMENT_ID") != null -fun isQWebviewsAvailable() = JBCefApp.isSupported() +/** + * @return low fidelity "is internal compute". is not exact and may fail at any time + */ +private val isInternalAmznLinuxCompute by lazy { + tryOrNull { + Paths.get("/apollo").exists() + } ?: false +} + +/** + * On remote, only enabled experimentally and for internal + */ +fun isQWebviewsAvailable() = JBCefApp.isSupported() && if (!isRunningOnRemoteBackend()) { + true +} else { + isDeveloperMode() || isInternalAmznLinuxCompute +}