diff --git a/.github/workflows/generateUpdatesXml.py b/.github/workflows/generateUpdatesXml.py index 153a0a3b6c1..4e252e21432 100644 --- a/.github/workflows/generateUpdatesXml.py +++ b/.github/workflows/generateUpdatesXml.py @@ -21,12 +21,7 @@ versionRegex = r'.*?\-(\d.*[.-]\d{3})\.zip$' for asset in data['assets']: name = asset['name'] - if ('plugin-amazonq' in name): - plugin = 'amazon.q' - elif ('plugin-core' in name): - plugin = 'aws.toolkit.core' - else: - plugin = 'aws.toolkit' + plugin = 'aws.toolkit' build = re.match(buildRegex, name) if build == None: continue diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 32bd1956813..7bfda86778b 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -17,8 +17,8 @@ jobs: generate_artifact: strategy: matrix: - build_target: [ ':plugin-core:buildPlugin', ':plugin-toolkit:intellij-standalone:buildPlugin', ':plugin-amazonq:buildPlugin' ] - version: [ '2024.3', '2025.1', '2025.2', '2025.3' ] + build_target: [ ':plugin-toolkit:intellij-standalone:buildPlugin' ] + version: [ '2025.1', '2025.2', '2025.3' ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.run/Run All - Community [2024.3].run.xml b/.run/Run All - Community [2024.3].run.xml deleted file mode 100644 index 988ece48c18..00000000000 --- a/.run/Run All - Community [2024.3].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run All - Community [2025.1].run.xml b/.run/Run All - Community [2025.1].run.xml deleted file mode 100644 index 3d992a0a127..00000000000 --- a/.run/Run All - Community [2025.1].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run All - Community [2025.2].run.xml b/.run/Run All - Community [2025.2].run.xml deleted file mode 100644 index baa08804312..00000000000 --- a/.run/Run All - Community [2025.2].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run All - Rider [2024.3].run.xml b/.run/Run All - Rider [2024.3].run.xml deleted file mode 100644 index ba5df9f5ab5..00000000000 --- a/.run/Run All - Rider [2024.3].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run All - Rider [2025.1].run.xml b/.run/Run All - Rider [2025.1].run.xml deleted file mode 100644 index c6e195c57a3..00000000000 --- a/.run/Run All - Rider [2025.1].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run All - Rider [2025.2].run.xml b/.run/Run All - Rider [2025.2].run.xml deleted file mode 100644 index dfbf6e33121..00000000000 --- a/.run/Run All - Rider [2025.2].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run All - Ultimate [2024.3].run.xml b/.run/Run All - Ultimate [2024.3].run.xml deleted file mode 100644 index 49f02c1a438..00000000000 --- a/.run/Run All - Ultimate [2024.3].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run All - Ultimate [2025.1].run.xml b/.run/Run All - Ultimate [2025.1].run.xml deleted file mode 100644 index 4285e179a54..00000000000 --- a/.run/Run All - Ultimate [2025.1].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run All - Ultimate [2025.2].run.xml b/.run/Run All - Ultimate [2025.2].run.xml deleted file mode 100644 index 4dbdd4dc4e0..00000000000 --- a/.run/Run All - Ultimate [2025.2].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run Amazon Q - Community [2024.3].run.xml b/.run/Run Amazon Q - Community [2024.3].run.xml deleted file mode 100644 index 4f6b86d32f1..00000000000 --- a/.run/Run Amazon Q - Community [2024.3].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run Amazon Q - Community [2025.1].run.xml b/.run/Run Amazon Q - Community [2025.1].run.xml deleted file mode 100644 index ed23a45a50e..00000000000 --- a/.run/Run Amazon Q - Community [2025.1].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run Amazon Q - Community [2025.2].run.xml b/.run/Run Amazon Q - Community [2025.2].run.xml deleted file mode 100644 index 7652b369b5f..00000000000 --- a/.run/Run Amazon Q - Community [2025.2].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run Amazon Q - Rider [2024.3].run.xml b/.run/Run Amazon Q - Rider [2024.3].run.xml deleted file mode 100644 index 9e64b94c3e3..00000000000 --- a/.run/Run Amazon Q - Rider [2024.3].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run Amazon Q - Rider [2025.1].run.xml b/.run/Run Amazon Q - Rider [2025.1].run.xml deleted file mode 100644 index e94c11fe07f..00000000000 --- a/.run/Run Amazon Q - Rider [2025.1].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run Amazon Q - Rider [2025.2].run.xml b/.run/Run Amazon Q - Rider [2025.2].run.xml deleted file mode 100644 index b44257e0f62..00000000000 --- a/.run/Run Amazon Q - Rider [2025.2].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run Amazon Q - Ultimate [2024.3].run.xml b/.run/Run Amazon Q - Ultimate [2024.3].run.xml deleted file mode 100644 index 6891367056b..00000000000 --- a/.run/Run Amazon Q - Ultimate [2024.3].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run Amazon Q - Ultimate [2025.1].run.xml b/.run/Run Amazon Q - Ultimate [2025.1].run.xml deleted file mode 100644 index 8d363e8ad4a..00000000000 --- a/.run/Run Amazon Q - Ultimate [2025.1].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/.run/Run Amazon Q - Ultimate [2025.2].run.xml b/.run/Run Amazon Q - Ultimate [2025.2].run.xml deleted file mode 100644 index 95c4963df05..00000000000 --- a/.run/Run Amazon Q - Ultimate [2025.2].run.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - false - true - false - false - - - \ No newline at end of file diff --git a/REPOLAYOUT.md b/REPOLAYOUT.md index ecc5c12748a..b7bb78e84f9 100644 --- a/REPOLAYOUT.md +++ b/REPOLAYOUT.md @@ -51,26 +51,6 @@ aws-toolkit-jetbrains │ │ │ └── build.gradle.kts │ │ └── build.gradle.kts │ │ -│ ├── amazonq :plugin-amazonq -│ │ ├── codewhisperer :plugin-amazonq:codewhisperer -│ │ │ ├── community :plugin-amazonq:codewhisperer:community -│ │ │ │ ├── ⋮ -│ │ │ │ └── build.gradle.kts -│ │ │ ├── ultimate :plugin-amazonq:codewhisperer:ultimate -│ │ │ │ ├── ⋮ -│ │ │ │ └── build.gradle.kts -│ │ │ └── build.gradle.kts -│ │ ├── codemodernizer :plugin-amazonq:codemodernizer -│ │ │ ├── community :plugin-amazonq:codemodernizer:community -│ │ │ │ ├── ⋮ -│ │ │ │ └── build.gradle.kts -│ │ │ └── build.gradle.kts -│ │ ├── ⋮ -│ │ ├── mynah-ui :plugin-amazonq:mynah-ui -│ │ │ ├── ⋮ -│ │ │ └── build.gradle.kts -│ │ └── build.gradle.kts -│ │ │ └── toolbox :plugin-toolbox │ ├── ⋮ │ └── build.gradle.kts @@ -139,17 +119,13 @@ Full documentation on the supported folder patterns can be found on the `findFol For artifacts where major functional logic is developed by multiple independent teams, we optionally support another layer of subprojects to increase code locality during development Below is a sample of the layout and sourceset interactions for the given artifacts: -* `plugin-core.zip` * `plugin-toolkit.zip` -* `plugin-amazonq.zip` ```mermaid flowchart LR common - common <-. depends/input for .-> plugin-core common <-. depends/input for .-> plugin-toolkit - common <-. depends/input for .-> plugin-amazonq subgraph plugin-core resources-core[resources] @@ -210,54 +186,5 @@ flowchart LR style plugintoolkitzip text-align:left end - - subgraph plugin-amazonq - direction LR - mynah-ui - - subgraph codewhisperer[codewhisperer: intellij platform sourcesets] - community-codewhisperer[community] - ultimate-codewhisperer[ultimate] - - ultimate-codewhisperer --depends--> community-codewhisperer - - community-codewhisperer -. input for .-> instrument-codewhisperer - ultimate-codewhisperer -. input for .-> instrument-codewhisperer - instrument-codewhisperer[[InstrumentJarTask]] - end - - subgraph amazonq[amazonq shared: intellij platform sourcesets] - community-amazonq[community] - ultimate-amazonq[ultimate] - - ultimate-amazonq --depends--> community-amazonq - - community-amazonq -. input for .-> instrument-amazonq - ultimate-amazonq -. input for .-> instrument-amazonq - instrument-amazonq[[InstrumentJarTask]] - end - - subgraph codemodernizer[codemodernizer: intellij platform sourcesets] - community-codemodernizer[community] - - community-codemodernizer -. input for .-> instrument-codemodernizer - instrument-codemodernizer[[InstrumentJarTask]] - end - - codewhisperer --depends---> amazonq - codemodernizer --depends---> amazonq - - mynah-ui -. input for .-> build-amazonq - instrument-amazonq -. input for .-> build-amazonq - instrument-codewhisperer -. input for .-> build-amazonq - instrument-codemodernizer -. input for .-> build-amazonq - build-amazonq[[PrepareSandbox + BuildPlugin]] - - build-amazonq -- emits --> pluginamazonqzip - pluginamazonqzip[plugin-amazonq.zip\n* mynah-ui.jar\n* instrumented-amazonqshared-community.jar\n* instrumented-amazonqshared-ultimate.jar\n* instrumented-codewhisperer-community.jar\n* instrumented-codewhisperer-ultimate.jar\n* instrumented-chat-community.jar\n* instrumented-codemodernizer-community.jar\n* common.jar] - style pluginamazonqzip text-align:left - end - plugin-toolkit -. runtime dependency on API .-> plugincorezip - plugin-amazonq -. runtime dependency on API .-> plugincorezip ``` diff --git a/build.gradle.kts b/build.gradle.kts index 778a136b053..9dc691999d5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,7 +33,6 @@ tasks.createRelease.configure { dependencies { aggregateCoverage(project(":plugin-toolkit:intellij-standalone")) aggregateCoverage(project(":plugin-core")) - aggregateCoverage(project(":plugin-amazonq")) project.findProject(":plugin-toolkit:jetbrains-gateway")?.let { aggregateCoverage(it) diff --git a/buildspec/linuxTestsForAmazonQ.yml b/buildspec/linuxTestsForAmazonQ.yml deleted file mode 100644 index 737681deb84..00000000000 --- a/buildspec/linuxTestsForAmazonQ.yml +++ /dev/null @@ -1,71 +0,0 @@ -version: 0.2 - -cache: - paths: - # - '/root/.gradle/caches/**/*' - - '/root/.gradle/wrapper/**/*' - -env: - variables: - CI: true - LOCAL_ENV_RUN: true - -phases: - install: - commands: - - useradd codebuild-user - - dnf install -y acl - - chown -R codebuild-user:codebuild-user /codebuild/output - - chown -R codebuild-user:codebuild-user /codebuild/local-cache - - setfacl -m d:o::rwx,o::rwx /root - # (CVE-2022-24765) fatal: detected dubious ownership in repository - - su codebuild-user -c "git config --global --add safe.directory \"$CODEBUILD_SRC_DIR\"" - - build: - commands: - - | - if [ "$CODEARTIFACT_DOMAIN_NAME" ] && [ "$CODEARTIFACT_REPO_NAME" ]; then - CODEARTIFACT_URL=$(aws codeartifact get-repository-endpoint --domain $CODEARTIFACT_DOMAIN_NAME --repository $CODEARTIFACT_REPO_NAME --format maven --query repositoryEndpoint --output text) - # CODEARTIFACT_NUGET_URL=$(aws codeartifact get-repository-endpoint --domain $CODEARTIFACT_DOMAIN_NAME --repository $CODEARTIFACT_REPO_NAME --format nuget --query repositoryEndpoint --output text) - CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token --domain $CODEARTIFACT_DOMAIN_NAME --query authorizationToken --output text --duration-seconds 3600) - su codebuild-user -c "dotnet codeartifact-creds install" - fi - - - chmod +x gradlew - - su codebuild-user -c "./gradlew -PideProfileName=$ALTERNATIVE_IDE_PROFILE_NAME :plugin-amazonq:check coverageReport --info --console plain --continue" - - ./gradlew -PideProfileName=$ALTERNATIVE_IDE_PROFILE_NAME :plugin-amazonq:buildPlugin - - VCS_COMMIT_ID="${CODEBUILD_RESOLVED_SOURCE_VERSION}" - - CI_BUILD_URL=$(echo $CODEBUILD_BUILD_URL | sed 's/#/%23/g') # Encode `#` in the URL because otherwise the url is clipped in the Codecov.io site - - CI_BUILD_ID="${CODEBUILD_BUILD_ID}" - - test -n "$CODE_COV_TOKEN" && curl -Os https://uploader.codecov.io/latest/linux/codecov && chmod +x codecov || true # this sometimes times out but we don't want to fail the build - - test -n "$CODE_COV_TOKEN" && test -n "$CODEBUILD_BUILD_SUCCEEDING" && ./codecov -t $CODE_COV_TOKEN -F unittest || true - - test -n "$CODE_COV_TOKEN" && test -n "$CODEBUILD_BUILD_SUCCEEDING" && ./codecov -t $CODE_COV_TOKEN -F codewhisperer || true - - post_build: - commands: - - BUILD_ARTIFACTS="/tmp/buildArtifacts" - - TEST_ARTIFACTS="/tmp/testArtifacts" - - mkdir -p $TEST_ARTIFACTS/test-reports - - mkdir -p $BUILD_ARTIFACTS - - rsync -rmq --include='*/' --include '**/build/idea-sandbox/**/log*/**' --exclude='*' . $TEST_ARTIFACTS/ || true - - rsync -rmq --include='*/' --include '**/build/reports/**' --exclude='*' . $TEST_ARTIFACTS/ || true - - rsync -rmq --include='*/' --include '**/test-results/**/*.xml' --exclude='*' . $TEST_ARTIFACTS/test-reports || true - - cp -r ./plugins/amazonq/build/distributions/*.zip $BUILD_ARTIFACTS/ || touch $BUILD_ARTIFACTS/build_failed - -reports: - unit-test: - files: - - "**/*" - base-directory: /tmp/testArtifacts/test-reports - discard-paths: yes - -artifacts: - files: - - "**/*" - base-directory: /tmp/testArtifacts - secondary-artifacts: - plugin: - files: - - /tmp/buildArtifacts/* - discard-paths: yes - name: plugin.zip diff --git a/buildspec/linuxTestsForCore.yml b/buildspec/linuxTestsForCore.yml deleted file mode 100644 index 40f8ff01e51..00000000000 --- a/buildspec/linuxTestsForCore.yml +++ /dev/null @@ -1,71 +0,0 @@ -version: 0.2 - -cache: - paths: - # - '/root/.gradle/caches/**/*' - - '/root/.gradle/wrapper/**/*' - -env: - variables: - CI: true - LOCAL_ENV_RUN: true - -phases: - install: - commands: - - useradd codebuild-user - - dnf install -y acl - - chown -R codebuild-user:codebuild-user /codebuild/output - - chown -R codebuild-user:codebuild-user /codebuild/local-cache - - setfacl -m d:o::rwx,o::rwx /root - # (CVE-2022-24765) fatal: detected dubious ownership in repository - - su codebuild-user -c "git config --global --add safe.directory \"$CODEBUILD_SRC_DIR\"" - - build: - commands: - - | - if [ "$CODEARTIFACT_DOMAIN_NAME" ] && [ "$CODEARTIFACT_REPO_NAME" ]; then - CODEARTIFACT_URL=$(aws codeartifact get-repository-endpoint --domain $CODEARTIFACT_DOMAIN_NAME --repository $CODEARTIFACT_REPO_NAME --format maven --query repositoryEndpoint --output text) - # CODEARTIFACT_NUGET_URL=$(aws codeartifact get-repository-endpoint --domain $CODEARTIFACT_DOMAIN_NAME --repository $CODEARTIFACT_REPO_NAME --format nuget --query repositoryEndpoint --output text) - CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token --domain $CODEARTIFACT_DOMAIN_NAME --query authorizationToken --output text --duration-seconds 3600) - su codebuild-user -c "dotnet codeartifact-creds install" - fi - - - chmod +x gradlew - - su codebuild-user -c "./gradlew -PideProfileName=$ALTERNATIVE_IDE_PROFILE_NAME :plugin-core:check coverageReport --info --console plain --continue" - - ./gradlew -PideProfileName=$ALTERNATIVE_IDE_PROFILE_NAME :plugin-core:buildPlugin - - VCS_COMMIT_ID="${CODEBUILD_RESOLVED_SOURCE_VERSION}" - - CI_BUILD_URL=$(echo $CODEBUILD_BUILD_URL | sed 's/#/%23/g') # Encode `#` in the URL because otherwise the url is clipped in the Codecov.io site - - CI_BUILD_ID="${CODEBUILD_BUILD_ID}" - - test -n "$CODE_COV_TOKEN" && curl -Os https://uploader.codecov.io/latest/linux/codecov && chmod +x codecov || true # this sometimes times out but we don't want to fail the build - - test -n "$CODE_COV_TOKEN" && test -n "$CODEBUILD_BUILD_SUCCEEDING" && ./codecov -t $CODE_COV_TOKEN -F unittest || true - - test -n "$CODE_COV_TOKEN" && test -n "$CODEBUILD_BUILD_SUCCEEDING" && ./codecov -t $CODE_COV_TOKEN -F codewhisperer || true - - post_build: - commands: - - BUILD_ARTIFACTS="/tmp/buildArtifacts" - - TEST_ARTIFACTS="/tmp/testArtifacts" - - mkdir -p $TEST_ARTIFACTS/test-reports - - mkdir -p $BUILD_ARTIFACTS - - rsync -rmq --include='*/' --include '**/build/idea-sandbox/**/log*/**' --exclude='*' . $TEST_ARTIFACTS/ || true - - rsync -rmq --include='*/' --include '**/build/reports/**' --exclude='*' . $TEST_ARTIFACTS/ || true - - rsync -rmq --include='*/' --include '**/test-results/**/*.xml' --exclude='*' . $TEST_ARTIFACTS/test-reports || true - - cp -r ./plugins/core/build/distributions/*.zip $BUILD_ARTIFACTS/ || touch $BUILD_ARTIFACTS/build_failed - -reports: - unit-test: - files: - - "**/*" - base-directory: /tmp/testArtifacts/test-reports - discard-paths: yes - -artifacts: - files: - - "**/*" - base-directory: /tmp/testArtifacts - secondary-artifacts: - plugin: - files: - - /tmp/buildArtifacts/* - discard-paths: yes - name: plugin.zip diff --git a/buildspec/linuxTestsForToolkit.yml b/buildspec/linuxTestsForToolkit.yml deleted file mode 100644 index 0bf0473f6f5..00000000000 --- a/buildspec/linuxTestsForToolkit.yml +++ /dev/null @@ -1,71 +0,0 @@ -version: 0.2 - -cache: - paths: - # - '/root/.gradle/caches/**/*' - - '/root/.gradle/wrapper/**/*' - -env: - variables: - CI: true - LOCAL_ENV_RUN: true - -phases: - install: - commands: - - useradd codebuild-user - - dnf install -y acl - - chown -R codebuild-user:codebuild-user /codebuild/output - - chown -R codebuild-user:codebuild-user /codebuild/local-cache - - setfacl -m d:o::rwx,o::rwx /root - # (CVE-2022-24765) fatal: detected dubious ownership in repository - - su codebuild-user -c "git config --global --add safe.directory \"$CODEBUILD_SRC_DIR\"" - - build: - commands: - - | - if [ "$CODEARTIFACT_DOMAIN_NAME" ] && [ "$CODEARTIFACT_REPO_NAME" ]; then - CODEARTIFACT_URL=$(aws codeartifact get-repository-endpoint --domain $CODEARTIFACT_DOMAIN_NAME --repository $CODEARTIFACT_REPO_NAME --format maven --query repositoryEndpoint --output text) - # CODEARTIFACT_NUGET_URL=$(aws codeartifact get-repository-endpoint --domain $CODEARTIFACT_DOMAIN_NAME --repository $CODEARTIFACT_REPO_NAME --format nuget --query repositoryEndpoint --output text) - CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token --domain $CODEARTIFACT_DOMAIN_NAME --query authorizationToken --output text --duration-seconds 3600) - su codebuild-user -c "dotnet codeartifact-creds install" - fi - - - chmod +x gradlew - - su codebuild-user -c "./gradlew -PideProfileName=$ALTERNATIVE_IDE_PROFILE_NAME :plugin-toolkit:intellij-standalone:check coverageReport --info --console plain --continue" - - ./gradlew -PideProfileName=$ALTERNATIVE_IDE_PROFILE_NAME :plugin-toolkit:intellij-standalone:buildPlugin - - VCS_COMMIT_ID="${CODEBUILD_RESOLVED_SOURCE_VERSION}" - - CI_BUILD_URL=$(echo $CODEBUILD_BUILD_URL | sed 's/#/%23/g') # Encode `#` in the URL because otherwise the url is clipped in the Codecov.io site - - CI_BUILD_ID="${CODEBUILD_BUILD_ID}" - - test -n "$CODE_COV_TOKEN" && curl -Os https://uploader.codecov.io/latest/linux/codecov && chmod +x codecov || true # this sometimes times out but we don't want to fail the build - - test -n "$CODE_COV_TOKEN" && test -n "$CODEBUILD_BUILD_SUCCEEDING" && ./codecov -t $CODE_COV_TOKEN -F unittest || true - - test -n "$CODE_COV_TOKEN" && test -n "$CODEBUILD_BUILD_SUCCEEDING" && ./codecov -t $CODE_COV_TOKEN -F codewhisperer || true - - post_build: - commands: - - BUILD_ARTIFACTS="/tmp/buildArtifacts" - - TEST_ARTIFACTS="/tmp/testArtifacts" - - mkdir -p $TEST_ARTIFACTS/test-reports - - mkdir -p $BUILD_ARTIFACTS - - rsync -rmq --include='*/' --include '**/build/idea-sandbox/**/log*/**' --exclude='*' . $TEST_ARTIFACTS/ || true - - rsync -rmq --include='*/' --include '**/build/reports/**' --exclude='*' . $TEST_ARTIFACTS/ || true - - rsync -rmq --include='*/' --include '**/test-results/**/*.xml' --exclude='*' . $TEST_ARTIFACTS/test-reports || true - - cp -r ./plugins/toolkit/intellij-standalone/build/distributions/*.zip $BUILD_ARTIFACTS/ || touch $BUILD_ARTIFACTS/build_failed - -reports: - unit-test: - files: - - "**/*" - base-directory: /tmp/testArtifacts/test-reports - discard-paths: yes - -artifacts: - files: - - "**/*" - base-directory: /tmp/testArtifacts - secondary-artifacts: - plugin: - files: - - /tmp/buildArtifacts/* - discard-paths: yes - name: plugin.zip diff --git a/buildspec/windowsTestsForAmazonQ.yml b/buildspec/windowsTestsForAmazonQ.yml deleted file mode 100644 index 70c6c2056f4..00000000000 --- a/buildspec/windowsTestsForAmazonQ.yml +++ /dev/null @@ -1,89 +0,0 @@ -version: 0.2 - -env: - variables: - CI: true - LOCAL_ENV_RUN: true - -phases: - install: - commands: - # force install java21 while we work through path issues - - | - $javaName = "C:\Program Files\Amazon Corretto" | ForEach-Object { - ls $_ | Where-Object {$_ -Like "jdk*"} | Sort-Object -Descending -Property Name | Select-Object -first 1 -expandproperty Name - } - $JAVA_HOME = "C:\Program Files\Amazon Corretto\$javaName" - - | - if(-Not($Env:CODE_COV_TOKEN -eq $null)) { - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; - Invoke-WebRequest -Uri https://uploader.codecov.io/latest/windows/codecov.exe -Outfile codecov.exe - } - - dotnet --list-sdks - - build: - commands: - - | - # See https://github.com/NuGet/NuGet.Client/pull/4259 - $Env:NUGET_EXPERIMENTAL_CHAIN_BUILD_RETRY_POLICY = "3,1000" - - $Env:JAVA_HOME = $JAVA_HOME - - if ($Env:CODEARTIFACT_DOMAIN_NAME -and $Env:CODEARTIFACT_REPO_NAME) { - $Env:CODEARTIFACT_URL=aws codeartifact get-repository-endpoint --domain $Env:CODEARTIFACT_DOMAIN_NAME --repository $Env:CODEARTIFACT_REPO_NAME --format maven --query repositoryEndpoint --output text - # $Env:CODEARTIFACT_NUGET_URL=aws codeartifact get-repository-endpoint --domain $Env:CODEARTIFACT_DOMAIN_NAME --repository $Env:CODEARTIFACT_REPO_NAME --format nuget --query repositoryEndpoint --output text - $Env:CODEARTIFACT_AUTH_TOKEN=aws codeartifact get-authorization-token --domain $Env:CODEARTIFACT_DOMAIN_NAME --query authorizationToken --output text --duration-seconds 3600 - } - - # Rider is very expensive (spikes our CI jobs to 50% CPU, so let it do the prep work in parallel, but run tests later - ./gradlew -PideProfileName="$Env:ALTERNATIVE_IDE_PROFILE_NAME" :plugin-amazonq:check --info --console plain --continue - if ($LastExitCode -ne 0) { - Write-Host "Command failed with exit code $LastExitCode" - exit -1 - } - # ./gradlew -PideProfileName="$Env:ALTERNATIVE_IDE_PROFILE_NAME" :plugin-toolkit:jetbrains-rider:check coverageReport --info --console plain - - post_build: - commands: - - | - $script:TEST_ARTIFACTS=Join-Path $env:TEMP testArtifacts - $script:TEST_REPORTS=Join-Path $script:TEST_ARTIFACTS test-reports - - function copyFolder($basedir, $subdir, $destdir) { - $src = Join-Path "." -ChildPath $basedir | Join-Path -ChildPath $subdir - $dest = Join-Path $destdir -ChildPath $basedir | Join-Path -ChildPath $subDir - if( (Get-ChildItem $src -ErrorAction SilentlyContinue | Measure-Object).Count -ne 0) { - Copy-Item $src $dest -Recurse -Force -ErrorAction SilentlyContinue - } - } - - function copyArtifacts($root) { - copyFolder $root "build/reports/" $script:TEST_ARTIFACTS - copyFolder $root "build/idea-sandbox/system-test/log/" $script:TEST_ARTIFACTS - copyFolder $root "build/test-results/test/" $script:TEST_REPORTS - } - - copyArtifacts "." - Get-ChildItem -Directory | ForEach-Object { copyArtifacts $_.Name } - - if(-Not($Env:CODEBUILD_BUILD_SUCCEEDING -eq "0" -Or $Env:CODE_COV_TOKEN -eq $null)) { - $env:VCS_COMMIT_ID=$Env:CODEBUILD_RESOLVED_SOURCE_VERSION; - $env:CI_BUILD_URL=[uri]::EscapeUriString($Env:CODEBUILD_BUILD_URL); - $env:CI_BUILD_ID=$Env:CODEBUILD_BUILD_ID; - .\codecov.exe -t $Env:CODE_COV_TOKEN ` - --flags unittest ` - -f "build/reports/jacoco/coverageReport/coverageReport.xml" ` - -c $Env:CODEBUILD_RESOLVED_SOURCE_VERSION - } - -reports: - unit-test: - files: - - "**/*" - base-directory: "$env:TEMP/testArtifacts/test-reports" - discard-paths: yes - -artifacts: - base-directory: "$env:TEMP/testArtifacts" - files: - - "**/*" diff --git a/buildspec/windowsTestsForCore.yml b/buildspec/windowsTestsForCore.yml deleted file mode 100644 index 1d195706057..00000000000 --- a/buildspec/windowsTestsForCore.yml +++ /dev/null @@ -1,89 +0,0 @@ -version: 0.2 - -env: - variables: - CI: true - LOCAL_ENV_RUN: true - -phases: - install: - commands: - # force install java21 while we work through path issues - - | - $javaName = "C:\Program Files\Amazon Corretto" | ForEach-Object { - ls $_ | Where-Object {$_ -Like "jdk*"} | Sort-Object -Descending -Property Name | Select-Object -first 1 -expandproperty Name - } - $JAVA_HOME = "C:\Program Files\Amazon Corretto\$javaName" - - | - if(-Not($Env:CODE_COV_TOKEN -eq $null)) { - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; - Invoke-WebRequest -Uri https://uploader.codecov.io/latest/windows/codecov.exe -Outfile codecov.exe - } - - dotnet --list-sdks - - build: - commands: - - | - # See https://github.com/NuGet/NuGet.Client/pull/4259 - $Env:NUGET_EXPERIMENTAL_CHAIN_BUILD_RETRY_POLICY = "3,1000" - - $Env:JAVA_HOME = $JAVA_HOME - - if ($Env:CODEARTIFACT_DOMAIN_NAME -and $Env:CODEARTIFACT_REPO_NAME) { - $Env:CODEARTIFACT_URL=aws codeartifact get-repository-endpoint --domain $Env:CODEARTIFACT_DOMAIN_NAME --repository $Env:CODEARTIFACT_REPO_NAME --format maven --query repositoryEndpoint --output text - # $Env:CODEARTIFACT_NUGET_URL=aws codeartifact get-repository-endpoint --domain $Env:CODEARTIFACT_DOMAIN_NAME --repository $Env:CODEARTIFACT_REPO_NAME --format nuget --query repositoryEndpoint --output text - $Env:CODEARTIFACT_AUTH_TOKEN=aws codeartifact get-authorization-token --domain $Env:CODEARTIFACT_DOMAIN_NAME --query authorizationToken --output text --duration-seconds 3600 - } - - # Rider is very expensive (spikes our CI jobs to 50% CPU, so let it do the prep work in parallel, but run tests later - ./gradlew -PideProfileName="$Env:ALTERNATIVE_IDE_PROFILE_NAME" :plugin-core:check --info --console plain --continue - if ($LastExitCode -ne 0) { - Write-Host "Command failed with exit code $LastExitCode" - exit -1 - } - # ./gradlew -PideProfileName="$Env:ALTERNATIVE_IDE_PROFILE_NAME" :plugin-toolkit:jetbrains-rider:check coverageReport --info --console plain - - post_build: - commands: - - | - $script:TEST_ARTIFACTS=Join-Path $env:TEMP testArtifacts - $script:TEST_REPORTS=Join-Path $script:TEST_ARTIFACTS test-reports - - function copyFolder($basedir, $subdir, $destdir) { - $src = Join-Path "." -ChildPath $basedir | Join-Path -ChildPath $subdir - $dest = Join-Path $destdir -ChildPath $basedir | Join-Path -ChildPath $subDir - if( (Get-ChildItem $src -ErrorAction SilentlyContinue | Measure-Object).Count -ne 0) { - Copy-Item $src $dest -Recurse -Force -ErrorAction SilentlyContinue - } - } - - function copyArtifacts($root) { - copyFolder $root "build/reports/" $script:TEST_ARTIFACTS - copyFolder $root "build/idea-sandbox/system-test/log/" $script:TEST_ARTIFACTS - copyFolder $root "build/test-results/test/" $script:TEST_REPORTS - } - - copyArtifacts "." - Get-ChildItem -Directory | ForEach-Object { copyArtifacts $_.Name } - - if(-Not($Env:CODEBUILD_BUILD_SUCCEEDING -eq "0" -Or $Env:CODE_COV_TOKEN -eq $null)) { - $env:VCS_COMMIT_ID=$Env:CODEBUILD_RESOLVED_SOURCE_VERSION; - $env:CI_BUILD_URL=[uri]::EscapeUriString($Env:CODEBUILD_BUILD_URL); - $env:CI_BUILD_ID=$Env:CODEBUILD_BUILD_ID; - .\codecov.exe -t $Env:CODE_COV_TOKEN ` - --flags unittest ` - -f "build/reports/jacoco/coverageReport/coverageReport.xml" ` - -c $Env:CODEBUILD_RESOLVED_SOURCE_VERSION - } - -reports: - unit-test: - files: - - "**/*" - base-directory: "$env:TEMP/testArtifacts/test-reports" - discard-paths: yes - -artifacts: - base-directory: "$env:TEMP/testArtifacts" - files: - - "**/*" diff --git a/buildspec/windowsTestsForToolkit.yml b/buildspec/windowsTestsForToolkit.yml deleted file mode 100644 index f96035a79c4..00000000000 --- a/buildspec/windowsTestsForToolkit.yml +++ /dev/null @@ -1,89 +0,0 @@ -version: 0.2 - -env: - variables: - CI: true - LOCAL_ENV_RUN: true - -phases: - install: - commands: - # force install java21 while we work through path issues - - | - $javaName = "C:\Program Files\Amazon Corretto" | ForEach-Object { - ls $_ | Where-Object {$_ -Like "jdk*"} | Sort-Object -Descending -Property Name | Select-Object -first 1 -expandproperty Name - } - $JAVA_HOME = "C:\Program Files\Amazon Corretto\$javaName" - - | - if(-Not($Env:CODE_COV_TOKEN -eq $null)) { - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; - Invoke-WebRequest -Uri https://uploader.codecov.io/latest/windows/codecov.exe -Outfile codecov.exe - } - - dotnet --list-sdks - - build: - commands: - - | - # See https://github.com/NuGet/NuGet.Client/pull/4259 - $Env:NUGET_EXPERIMENTAL_CHAIN_BUILD_RETRY_POLICY = "3,1000" - - $Env:JAVA_HOME = $JAVA_HOME - - if ($Env:CODEARTIFACT_DOMAIN_NAME -and $Env:CODEARTIFACT_REPO_NAME) { - $Env:CODEARTIFACT_URL=aws codeartifact get-repository-endpoint --domain $Env:CODEARTIFACT_DOMAIN_NAME --repository $Env:CODEARTIFACT_REPO_NAME --format maven --query repositoryEndpoint --output text - # $Env:CODEARTIFACT_NUGET_URL=aws codeartifact get-repository-endpoint --domain $Env:CODEARTIFACT_DOMAIN_NAME --repository $Env:CODEARTIFACT_REPO_NAME --format nuget --query repositoryEndpoint --output text - $Env:CODEARTIFACT_AUTH_TOKEN=aws codeartifact get-authorization-token --domain $Env:CODEARTIFACT_DOMAIN_NAME --query authorizationToken --output text --duration-seconds 3600 - } - - # Rider is very expensive (spikes our CI jobs to 50% CPU, so let it do the prep work in parallel, but run tests later - ./gradlew -PideProfileName="$Env:ALTERNATIVE_IDE_PROFILE_NAME" :plugin-toolkit:intellij-standalone:check :plugin-toolkit:jetbrains-rider:compileTestKotlin -x :plugin-toolkit:jetbrains-rider:check --info --console plain --continue - if ($LastExitCode -ne 0) { - Write-Host "Command failed with exit code $LastExitCode" - exit -1 - } - # ./gradlew -PideProfileName="$Env:ALTERNATIVE_IDE_PROFILE_NAME" :plugin-toolkit:jetbrains-rider:check coverageReport --info --console plain - - post_build: - commands: - - | - $script:TEST_ARTIFACTS=Join-Path $env:TEMP testArtifacts - $script:TEST_REPORTS=Join-Path $script:TEST_ARTIFACTS test-reports - - function copyFolder($basedir, $subdir, $destdir) { - $src = Join-Path "." -ChildPath $basedir | Join-Path -ChildPath $subdir - $dest = Join-Path $destdir -ChildPath $basedir | Join-Path -ChildPath $subDir - if( (Get-ChildItem $src -ErrorAction SilentlyContinue | Measure-Object).Count -ne 0) { - Copy-Item $src $dest -Recurse -Force -ErrorAction SilentlyContinue - } - } - - function copyArtifacts($root) { - copyFolder $root "build/reports/" $script:TEST_ARTIFACTS - copyFolder $root "build/idea-sandbox/system-test/log/" $script:TEST_ARTIFACTS - copyFolder $root "build/test-results/test/" $script:TEST_REPORTS - } - - copyArtifacts "." - Get-ChildItem -Directory | ForEach-Object { copyArtifacts $_.Name } - - if(-Not($Env:CODEBUILD_BUILD_SUCCEEDING -eq "0" -Or $Env:CODE_COV_TOKEN -eq $null)) { - $env:VCS_COMMIT_ID=$Env:CODEBUILD_RESOLVED_SOURCE_VERSION; - $env:CI_BUILD_URL=[uri]::EscapeUriString($Env:CODEBUILD_BUILD_URL); - $env:CI_BUILD_ID=$Env:CODEBUILD_BUILD_ID; - .\codecov.exe -t $Env:CODE_COV_TOKEN ` - --flags unittest ` - -f "build/reports/jacoco/coverageReport/coverageReport.xml" ` - -c $Env:CODEBUILD_RESOLVED_SOURCE_VERSION - } - -reports: - unit-test: - files: - - "**/*" - base-directory: "$env:TEMP/testArtifacts/test-reports" - discard-paths: yes - -artifacts: - base-directory: "$env:TEMP/testArtifacts" - files: - - "**/*" diff --git a/plugins/amazonq/build.gradle.kts b/plugins/amazonq/build.gradle.kts deleted file mode 100644 index 65a5aaa7cd5..00000000000 --- a/plugins/amazonq/build.gradle.kts +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import de.undercouch.gradle.tasks.download.Download -import org.jetbrains.intellij.platform.gradle.tasks.PrepareSandboxTask -import software.aws.toolkits.gradle.changelog.tasks.GeneratePluginChangeLog - -plugins { - id("toolkit-publishing-conventions") - id("toolkit-publish-root-conventions") - id("toolkit-jvm-conventions") - id("toolkit-testing") - id("de.undercouch.download") -} - -buildscript { - dependencies { - classpath(libs.bundles.jackson) - } -} - -val changelog = tasks.register("pluginChangeLog") { - includeUnreleased.set(true) - changeLogFile.value(layout.buildDirectory.file("changelog/change-notes.xml")) -} - -tasks.jar { - dependsOn(changelog) - from(changelog) { - into("META-INF") - } -} - -dependencies { - implementation(project(":plugin-core-q")) - implementation(project(":plugin-amazonq:chat")) - implementation(project(":plugin-amazonq:codetransform")) - implementation(project(":plugin-amazonq:codewhisperer")) - implementation(project(":plugin-amazonq:mynah-ui")) - implementation(project(":plugin-amazonq:shared")) - implementation(libs.bundles.jackson) - implementation(libs.lsp4j) - - testImplementation(project(":plugin-core-q")) -} - -tasks.check { - val serviceSubdirs = project(":plugin-amazonq").subprojects - serviceSubdirs.forEach { serviceSubDir -> - val subDirs = serviceSubDir.subprojects - subDirs.forEach { insideService-> - dependsOn(":plugin-amazonq:${serviceSubDir.name}:${insideService.name}:check") - } - } -} - -val downloadFlareManifest by tasks.registering(Download::class) { - src("https://aws-toolkit-language-servers.amazonaws.com/qAgenticChatServer/0/manifest.json") - dest(layout.buildDirectory.file("flare/manifest.json")) - onlyIfModified(true) - useETag(true) -} - -data class FlareManifest( - val versions: List, -) - -data class FlareVersion( - val serverVersion: String, - val thirdPartyLicenses: String, - val targets: List, -) - -data class FlareTarget( - val platform: String, - val arch: String, - val contents: List -) - -data class FlareContent( - val url: String, -) - -val downloadFlareArtifacts by tasks.registering(Download::class) { - dependsOn(downloadFlareManifest) - inputs.files(downloadFlareManifest) - - val manifestFile = downloadFlareManifest.map { it.outputFiles.first() } - val manifest = manifestFile.map { jacksonObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).readValue(it.readText(), FlareManifest::class.java) } - - // use darwin-aarch64 because its the smallest and we're going to throw away everything platform specific - val latest = manifest.map { it.versions.first() } - val latestVersion = latest.map { it.serverVersion } - val licensesUrl = latest.map { it.thirdPartyLicenses } - val darwin = latest.map { it.targets.first { target -> target.platform == "darwin" && target.arch == "arm64" } } - val contentUrls = darwin.map { it.contents.map { content -> content.url } } - - val destination = layout.buildDirectory.dir(latestVersion.map { "flare/$it" }) - outputs.dir(destination) - - src(contentUrls.zip(licensesUrl) { left, right -> left + right}) - dest(destination) - onlyIfModified(true) - useETag(true) -} - -val prepareBundledFlare by tasks.registering(Copy::class) { - dependsOn(downloadFlareArtifacts) - inputs.files(downloadFlareArtifacts) - - val dest = layout.buildDirectory.dir("tmp/extractFlare") - into(dest) - from(downloadFlareArtifacts.map { it.outputFiles.filterNot { file -> file.name.endsWith(".zip") } }) - - doLast { - copy { - into(dest) - includeEmptyDirs = false - downloadFlareArtifacts.get().outputFiles.filter { it.name.endsWith(".zip") }.forEach { - dest.get().file(it.parentFile.name).asFile.createNewFile() - from(zipTree(it)) { - include("*.js") - include("*.txt") - } - } - } - } -} - -tasks.withType().configureEach { - from(file("contrib/QCT-Maven-1-0-156-0.jar")) { - into(intellijPlatform.projectName.map { "$it/lib" }) - } - from(prepareBundledFlare) { - into(intellijPlatform.projectName.map { "$it/flare" }) - } -} diff --git a/plugins/amazonq/chat/build.gradle.kts b/plugins/amazonq/chat/build.gradle.kts deleted file mode 100644 index a801996a7a6..00000000000 --- a/plugins/amazonq/chat/build.gradle.kts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -plugins { - id("toolkit-jvm-conventions") -} - -dependencies { - implementation(project(":plugin-amazonq:chat:jetbrains-community")) -} diff --git a/plugins/amazonq/chat/jetbrains-community/build.gradle.kts b/plugins/amazonq/chat/jetbrains-community/build.gradle.kts deleted file mode 100644 index 927bbf26216..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/build.gradle.kts +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import software.aws.toolkits.gradle.intellij.IdeFlavor - -plugins { - id("toolkit-intellij-subplugin") -} - -intellijToolkit { - ideFlavor.set(IdeFlavor.IC) -} - -dependencies { - implementation(project(":plugin-core-q")) - - implementation(project(":plugin-amazonq:shared:jetbrains-community")) - // everything references codewhisperer, which is not ideal - implementation(project(":plugin-amazonq:codewhisperer:jetbrains-community")) - implementation(libs.diff.util) - implementation(libs.commons.text) - - compileOnly(project(":plugin-core-q:jetbrains-community")) - - testImplementation(testFixtures(project(":plugin-core-q:jetbrains-community"))) -} - -// hack because our test structure currently doesn't make complete sense -tasks.prepareTestSandbox { - val pluginXmlJar = project(":plugin-amazonq").tasks.jar - - dependsOn(pluginXmlJar) - from(pluginXmlJar) { - into(intellijPlatform.projectName.map { "$it/lib" }) - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/resources/META-INF/plugin-chat.xml b/plugins/amazonq/chat/jetbrains-community/resources/META-INF/plugin-chat.xml deleted file mode 100644 index d1197e69b67..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/resources/META-INF/plugin-chat.xml +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/common/util/FileUtils.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/common/util/FileUtils.kt deleted file mode 100644 index 6cf8cc24769..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/common/util/FileUtils.kt +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.common.util - -import com.github.difflib.DiffUtils -import com.github.difflib.patch.DeltaType -import com.intellij.openapi.fileChooser.FileChooser -import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory -import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.CharsetToolkit -import com.intellij.openapi.vfs.VirtualFile -import java.io.File -import java.nio.charset.Charset -import java.nio.file.Path -import kotlin.io.path.createDirectories -import kotlin.io.path.deleteIfExists -import kotlin.io.path.writeBytes - -fun resolveAndCreateOrUpdateFile(projectRootPath: Path, relativeFilePath: String, fileContent: String) { - val filePath = projectRootPath.resolve(relativeFilePath) - filePath.parent.createDirectories() // Create directories if needed - filePath.writeBytes(fileContent.toByteArray(Charsets.UTF_8)) -} - -fun resolveAndDeleteFile(projectRootPath: Path, relativePath: String) { - val filePath = projectRootPath.resolve(relativePath) - filePath.deleteIfExists() -} - -fun selectFolder(project: Project, openOn: VirtualFile): VirtualFile? { - val fileChooserDescriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor() - return FileChooser.chooseFile(fileChooserDescriptor, project, openOn) -} - -fun readFileToString(file: File): String { - val charsetToolkit = CharsetToolkit(file.readBytes(), Charset.forName("UTF-8"), false) - val charset = charsetToolkit.guessEncoding(4096) - return file.readText(charset) -} - -/** - * Calculates the number of added characters and lines between existing content and LLM response - * - * @param existingContent The original text content before changes - * @param llmResponse The new text content from the LLM - * @return A Map containing: - * - "addedChars": Total number of new characters added - * - "addedLines": Total number of new lines added - */ -data class DiffResult(val addedChars: Int, val addedLines: Int) - -fun getDiffCharsAndLines( - existingContent: String, - llmResponse: String, -): DiffResult { - var addedChars = 0 - var addedLines = 0 - - val existingLines = existingContent.lines() - val llmLines = llmResponse.lines() - - val patch = DiffUtils.diff(existingLines, llmLines) - - for (delta in patch.deltas) { - when (delta.type) { - DeltaType.INSERT -> { - addedChars += delta.target.lines.sumOf { it.length } - addedLines += delta.target.lines.size - } - - DeltaType.CHANGE -> { - addedChars += delta.target.lines.sumOf { it.length } - addedLines += delta.target.lines.size - } - - else -> {} // Do nothing for DELETE - } - } - - return DiffResult(addedChars, addedLines) -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/GetAmazonQLogsAction.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/GetAmazonQLogsAction.kt deleted file mode 100644 index f9ca023248a..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/GetAmazonQLogsAction.kt +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq - -import com.intellij.icons.AllIcons -import com.intellij.ide.actions.RevealFileAction -import com.intellij.ide.logsUploader.LogPacker -import com.intellij.openapi.actionSystem.ActionUpdateThread -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.project.DumbAwareAction -import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.Messages -import com.intellij.openapi.util.IconLoader -import com.intellij.ui.ColorUtil -import com.intellij.ui.JBColor -import com.intellij.util.IconUtil -import com.intellij.util.ui.UIUtil -import kotlinx.coroutines.runBlocking -import software.amazon.q.jetbrains.utils.notifyInfo -import software.amazon.q.jetbrains.utils.runUnderProgressIfNeeded -import software.amazon.q.resources.AwsCoreBundle -import software.aws.toolkits.resources.AmazonQBundle.message - -class GetAmazonQLogsAction : DumbAwareAction(message("amazonq.getLogs.tooltip.text")) { - private val baseIcon = IconLoader.getIcon("/icons/file.svg", GetAmazonQLogsAction::class.java) - - private val lightIcon by lazy { - IconUtil.colorize(baseIcon, ColorUtil.brighter(UIUtil.getLabelForeground(), 2)) - } - - override fun update(e: AnActionEvent) { - e.presentation.icon = if (!JBColor.isBright()) { - baseIcon - } else { - lightIcon - } - } - - override fun getActionUpdateThread() = ActionUpdateThread.BGT - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - showLogCollectionWarningGetLogs(project) - } - - companion object { - fun showLogCollectionWarningGetLogs(project: Project) { - if (Messages.showOkCancelDialog( - message("amazonq.logs.warning"), - message("amazonq.getLogs"), - AwsCoreBundle.message("general.ok"), - AwsCoreBundle.message("general.cancel"), - AllIcons.General.Warning - ) == 0 - ) { - runUnderProgressIfNeeded(project, message("amazonq.getLogs"), cancelable = true) { - runBlocking { - try { - RevealFileAction.openFile(LogPacker.packLogs(project)) - } catch (_: Exception) { - notifyInfo(message("amazonq.getLogs"), message("amazonq.logs.error"), project) - } - } - } - } - } - } -} 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 deleted file mode 100644 index 8ac30cbf887..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt +++ /dev/null @@ -1,381 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq - -import com.intellij.ide.BrowserUtil -import com.intellij.openapi.Disposable -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.DataContext -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.runInEdt -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Disposer -import com.intellij.ui.components.JBTextArea -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 kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import software.amazon.q.core.utils.debug -import software.amazon.q.core.utils.error -import software.amazon.q.core.utils.getLogger -import software.amazon.q.core.utils.warn -import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection -import software.amazon.q.jetbrains.core.credentials.ToolkitAuthManager -import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager -import software.amazon.q.jetbrains.core.credentials.actions.SsoLogoutAction -import software.amazon.q.jetbrains.core.credentials.pinning.QConnection -import software.amazon.q.jetbrains.core.credentials.sono.Q_SCOPES -import software.amazon.q.jetbrains.core.credentials.sono.isSono -import software.amazon.q.jetbrains.core.region.AwsRegionProvider -import software.amazon.q.jetbrains.core.webview.BrowserMessage -import software.amazon.q.jetbrains.core.webview.BrowserState -import software.amazon.q.jetbrains.core.webview.LocalAssetJBCefRequestHandler -import software.amazon.q.jetbrains.core.webview.LoginBrowser -import software.amazon.q.jetbrains.isDeveloperMode -import software.amazon.q.jetbrains.utils.isQConnected -import software.amazon.q.jetbrains.utils.isQExpired -import software.amazon.q.jetbrains.utils.isQWebviewsAvailable -import software.aws.toolkits.jetbrains.services.amazonq.profile.QProfileSwitchIntent -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager -import software.aws.toolkits.jetbrains.services.amazonq.util.createBrowser -import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.EditorThemeAdapter -import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.ThemeBrowserAdapter -import software.aws.toolkits.telemetry.FeatureId -import software.aws.toolkits.telemetry.MetricResult -import software.aws.toolkits.telemetry.Telemetry -import software.aws.toolkits.telemetry.UiTelemetry -import software.aws.toolkits.telemetry.WebviewTelemetry -import java.awt.event.ActionListener -import java.net.URI -import javax.swing.JButton -import javax.swing.JComponent - -@Service(Service.Level.PROJECT) -class QWebviewPanel private constructor(val project: Project, private val scope: CoroutineScope) : Disposable { - private val webviewContainer = Wrapper() - var browser: QWebviewBrowser? = null - private set - - val component = panel { - row { - cell(webviewContainer) - .align(Align.FILL) - }.resizableRow() - - if (isDeveloperMode()) { - row { - cell( - JButton("Show Web Debugger").apply { - addActionListener( - ActionListener { - browser?.jcefBrowser?.openDevtools() - }, - ) - }, - ) - .align(Align.FILL) - } - } - } - - init { - init() - } - - fun disposeAndRecreate() { - webviewContainer.removeAll() - val toDispose = browser - init() - if (toDispose != null) { - Disposer.dispose(toDispose) - } - } - - private fun init() { - if (!isQWebviewsAvailable()) { - // Fallback to an alternative browser-less solution - webviewContainer.add(JBTextArea("JCEF not supported")) - browser = null - } else { - browser = QWebviewBrowser(project, this).also { - webviewContainer.add(it.component()) - - val themeBrowserAdapter = ThemeBrowserAdapter() - EditorThemeAdapter().onThemeChange() - .distinctUntilChanged() - .onEach { theme -> - themeBrowserAdapter.updateLoginThemeInBrowser(it.jcefBrowser.cefBrowser, theme) - } - .launchIn(scope) - } - } - } - - companion object { - fun getInstance(project: Project) = project.service() - } - - override fun dispose() { - } -} - -class QWebviewBrowser(val project: Project, private val parentDisposable: Disposable) : - LoginBrowser( - project, - ), - 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 { - loadWebView(query) - - query.addHandler(jcefHandler) - } - - override fun dispose() { - Disposer.dispose(jcefBrowser) - } - - fun component(): JComponent? = jcefBrowser.component - - override fun handleBrowserMessage(message: BrowserMessage?) { - if (message == null) { - return - } - - when (message) { - is BrowserMessage.PrepareUi -> { - this.prepareBrowser(BrowserState(FeatureId.AmazonQ, false)) - WebviewTelemetry.amazonqSignInOpened( - project, - reAuth = isQExpired(project) - ) - } - - is BrowserMessage.SelectConnection -> { - this.selectionSettings[message.connectionId]?.let { settings -> - settings.onChange(settings.currentSelection) - } - } - - is BrowserMessage.LoginBuilderId -> { - loginBuilderId(Q_SCOPES) - } - - is BrowserMessage.LoginIdC -> { - val awsRegion = AwsRegionProvider.getInstance()[message.region] ?: error("unknown region returned from Q browser") - loginIdC(message.url, awsRegion, Q_SCOPES) - } - - is BrowserMessage.CancelLogin -> { - cancelLogin() - } - - is BrowserMessage.Signout -> { - ( - ToolkitConnectionManager.getInstance(project) - .activeConnectionForFeature(QConnection.getInstance()) as? AwsBearerTokenConnection - )?.let { connection -> - runInEdt { - SsoLogoutAction(connection).actionPerformed( - AnActionEvent.createFromDataContext( - "qBrowser", - null, - DataContext.EMPTY_CONTEXT - ) - ) - } - } - } - - is BrowserMessage.Reauth -> { - reauth(ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance())) - } - - is BrowserMessage.LoginIAM, is BrowserMessage.ToggleBrowser -> { - error("QBrowser doesn't support the provided command ${message::class.simpleName}") - } - - is BrowserMessage.SendUiClickTelemetry -> { - val signInOption = message.signInOptionClicked - if (signInOption.isNullOrEmpty()) { - LOG.warn { "Unknown sign in option" } - } else { - UiTelemetry.click(project, signInOption) - } - } - - is BrowserMessage.SwitchProfile -> { - QRegionProfileManager.getInstance().switchProfile( - project, - QRegionProfile(profileName = message.profileName, arn = message.arn), - intent = QProfileSwitchIntent.Auth - ) - } - - is BrowserMessage.ListProfiles -> { - handleListProfilesMessage() - } - - is BrowserMessage.PublishWebviewTelemetry -> { - publishTelemetry(message) - } - - is BrowserMessage.OpenUrl -> { - BrowserUtil.browse(URI(message.externalLink)) - } - } - } - - override fun prepareBrowser(state: BrowserState) { - // TODO: duplicate code in ToolkitLoginWebview - selectionSettings.clear() - - if (!isQConnected(project)) { - // existing connections - // TODO: filter "active"(state == 'AUTHENTICATED') connection only maybe? - val bearerCreds = ToolkitAuthManager.getInstance().listConnections().filterIsInstance().associate { - it.id to BearerConnectionSelectionSettings(it) { conn -> - if (conn.isSono()) { - loginBuilderId(Q_SCOPES) - } else { - // TODO: rewrite scope logic, it's short term solution only - AwsRegionProvider.getInstance()[conn.region]?.let { region -> - loginIdC(conn.startUrl, region, Q_SCOPES) - } - } - } - } - - selectionSettings.putAll(bearerCreds) - } - - // previous login - val lastLoginIdcInfo = ToolkitAuthManager.getInstance().getLastLoginIdcInfo().apply { - // set default option as us-east-1 - if (this.region.isBlank()) { - this.region = AwsRegionProvider.getInstance().defaultRegion().id - } - } - - // available regions - val regions = AwsRegionProvider.getInstance().allRegionsForService("sso").values.let { - writeValueAsString(it) - } - - val stage = if (isQExpired(project)) { - "REAUTH" - } else if (isQConnected(project) && QRegionProfileManager.getInstance().isPendingProfileSelection(project)) { - "PROFILE_SELECT" - } else { - "START" - } - - when (stage) { - "PROFILE_SELECT" -> { - val jsonData = """ - { - stage: '$stage', - status: 'pending' - } - """.trimIndent() - executeJS("window.ideClient.prepareUi($jsonData)") - } - - else -> { - val jsonData = """ - { - stage: '$stage', - regions: $regions, - idcInfo: { - profileName: '${lastLoginIdcInfo.profileName}', - startUrl: '${lastLoginIdcInfo.startUrl}', - region: '${lastLoginIdcInfo.region}' - }, - cancellable: ${state.browserCancellable}, - feature: '${state.feature}', - existConnections: ${writeValueAsString(selectionSettings.values.map { it.currentSelection }.toList())}, - } - """.trimIndent() - - executeJS("window.ideClient.prepareUi($jsonData)") - } - } - } - - override fun loginIAM(profileName: String, accessKey: String, secretKey: String) { - LOG.error { "IAM is not supported by Q" } - return - } - - override fun loadWebView(query: JBCefJSQuery) { - val webScriptUri = assetHandler.createResource( - WEB_SCRIPT, - QWebviewBrowser::class.java.getResourceAsStream("/webview/assets/$WEB_SCRIPT") - ) - - jcefBrowser.loadURL(assetHandler.createResource("content.html", getWebviewHTML(webScriptUri, query))) - } - - private fun handleListProfilesMessage() { - ApplicationManager.getApplication().executeOnPooledThread { - var errorMessage = "" - val profiles = try { - QRegionProfileManager.getInstance().listRegionProfiles(project) - } catch (e: Exception) { - e.message?.let { - errorMessage = it - } - LOG.warn { "Failed to call listRegionProfiles API: $errorMessage" } - val qConn = ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance()) - Telemetry.amazonq.didSelectProfile.use { span -> - span.source(QProfileSwitchIntent.Auth.value) - .amazonQProfileRegion(QRegionProfileManager.getInstance().activeProfile(project)?.region ?: "not-set") - .ssoRegion((qConn as? AwsBearerTokenConnection)?.region) - .credentialStartUrl((qConn as? AwsBearerTokenConnection)?.startUrl) - .result(MetricResult.Failed) - .reason(e.message) - } - - null - } - - // auto-select the profile if users only have 1 and don't show the UI - if (profiles?.size == 1) { - LOG.debug { "User only have access to 1 Q profile, auto-selecting profile ${profiles.first().profileName} for ${project.name}" } - QRegionProfileManager.getInstance().switchProfile(project, profiles.first(), QProfileSwitchIntent.Update) - return@executeOnPooledThread - } - - // required EDT as this entire block is executed on thread pool - runInEdt { - val jsonData = """ - { - stage: 'PROFILE_SELECT', - status: '${if (profiles != null) "succeeded" else "failed"}', - profiles: ${writeValueAsString(profiles ?: "")}, - errorMessage: '$errorMessage' - } - """.trimIndent() - - executeJS("window.ideClient.prepareUi($jsonData)") - } - } - } - - companion object { - private val LOG = getLogger() - private const val WEB_SCRIPT = "js/getStart.js" - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QMigrationNotificationAction.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QMigrationNotificationAction.kt deleted file mode 100644 index b438425a123..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QMigrationNotificationAction.kt +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq - -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.ui.popup.JBPopup -import com.intellij.openapi.wm.StatusBarWidget -import com.intellij.openapi.wm.WindowManager -import com.intellij.openapi.wm.impl.status.IdeStatusBarImpl -import com.intellij.openapi.wm.impl.status.TextPanel -import com.intellij.ui.awt.RelativePoint -import com.intellij.ui.popup.AbstractPopup -import icons.AwsIcons -import software.aws.toolkits.jetbrains.services.codewhisperer.status.CodeWhispererStatusBarWidget -import software.aws.toolkits.resources.message -import java.awt.Dimension -import java.awt.Point -import javax.swing.JPanel - -class QMigrationNotificationAction : AnAction(message("q.migration.notification.title"), null, AwsIcons.Misc.NEW) { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - val statusBar = WindowManager.getInstance().getStatusBar(project) - val widget = statusBar.getWidget(CodeWhispererStatusBarWidget.ID) ?: return - val statusBarComponent = statusBar.component as IdeStatusBarImpl? ?: return - - // This is kinda ugly, but it works. We want to display the popup at the status bar component but it's a private - // field. So we have to search from its parent all the way down that matches Q status bar characteristics. - val component = statusBarComponent.components.flatMap { - (it as JPanel).components.filterIsInstance() - }.firstOrNull { it.text?.startsWith("Amazon Q") ?: false } ?: return - val presentation = widget.getPresentation() as StatusBarWidget.MultipleTextValuesPresentation - val popup = presentation.getPopup() ?: return - val dimension = getSizeFor(popup) - val at = Point(0, -dimension.height) - popup.show(RelativePoint(component, at)) - } - - private fun getSizeFor(popup: JBPopup): Dimension = - if (popup is AbstractPopup) popup.sizeForPositioning else popup.content.preferredSize -} 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 deleted file mode 100644 index cab5e06d312..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QOpenPanelAction.kt +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq - -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.CommonDataKeys -import com.intellij.openapi.wm.ToolWindowManager -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.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) { - val project = e.getRequiredData(CommonDataKeys.PROJECT) - UiTelemetry.click(project, "q_openChat") - ToolWindowManager.getInstance(project).getToolWindow(AMAZON_Q_WINDOW_ID)?.activate(null, true) - if (e.getData(runScanKey) == true) { - 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/QRefreshPanelAction.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QRefreshPanelAction.kt deleted file mode 100644 index 52ed6897ec3..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QRefreshPanelAction.kt +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq - -import com.intellij.icons.AllIcons -import com.intellij.openapi.actionSystem.ActionUpdateThread -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.progress.currentThreadCoroutineScope -import com.intellij.openapi.project.DumbAwareAction -import com.intellij.util.messages.Topic -import kotlinx.coroutines.launch -import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService -import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.ChatCommunicationManager -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_REMOVE -import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindow -import software.aws.toolkits.resources.AmazonQBundle -import java.util.EventListener - -class QRefreshPanelAction : DumbAwareAction(AmazonQBundle.message("amazonq.refresh.panel"), null, AllIcons.Actions.Refresh) { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - - // Notify LSP server about all open tabs being removed - val chatManager = ChatCommunicationManager.getInstance(project) - currentThreadCoroutineScope().launch { - chatManager.getAllTabIds().forEach { tabId -> - AmazonQLspService.executeAsyncIfRunning(project) { - rawEndpoint.notify(CHAT_TAB_REMOVE, mapOf("tabId" to tabId)) - } - } - } - - // recreate chat browser - AmazonQToolWindow.getInstance(project).disposeAndRecreate() - // recreate signin browser - QWebviewPanel.getInstance(project).disposeAndRecreate() - RefreshQChatPanelButtonPressedListener.notifyRefresh() - } - - override fun getActionUpdateThread() = ActionUpdateThread.BGT -} - -interface RefreshQChatPanelButtonPressedListener : EventListener { - fun onRefresh() {} - - companion object { - @Topic.AppLevel - val TOPIC = Topic.create("Q Chat refreshed", RefreshQChatPanelButtonPressedListener::class.java) - - fun notifyRefresh() { - ApplicationManager.getApplication().messageBus.syncPublisher(TOPIC).onRefresh() - } - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQApp.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQApp.kt deleted file mode 100644 index 814dcca1211..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQApp.kt +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.apps - -import com.intellij.openapi.Disposable - -/** - * Base interface for the entry point for "apps" that are built using AmazonQ. - * - * Apps should implement this interface, and then register the implementing class in plugin.xml as an extension: - * - * - * - * - */ -interface AmazonQApp : Disposable { - - /** - * The types of tabs supported by this app. Messages will only be received by the app if they have a tabType that is contained in this list. - */ - val tabTypes: List - - /** - * This initializer function is called when the tool window is being setup. The app is passed an instance of [AmazonQAppInitContext], which contains the - * connections needed to communicate with the Amazon Q UI. - */ - fun init(context: AmazonQAppInitContext) -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQAppFactory.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQAppFactory.kt deleted file mode 100644 index 1bf8143a88f..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQAppFactory.kt +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.apps - -import com.intellij.openapi.project.Project - -interface AmazonQAppFactory { - fun createApp(project: Project): AmazonQApp -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQAppInitContext.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQAppInitContext.kt deleted file mode 100644 index ec9405cab18..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AmazonQAppInitContext.kt +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.apps - -import com.intellij.openapi.project.Project -import software.aws.toolkits.jetbrains.services.amazonq.commands.MessageTypeRegistry -import software.aws.toolkits.jetbrains.services.amazonq.messages.MessageListener -import software.aws.toolkits.jetbrains.services.amazonq.messages.MessagePublisher -import software.aws.toolkits.jetbrains.services.amazonq.webview.FqnWebviewAdapter - -/** - * Context object that is passed to each [AmazonQApp] during initialization. Contains the connections needed to communicate with the Amazon Q UI. - */ -data class AmazonQAppInitContext( - val project: Project, - val messagesFromAppToUi: MessagePublisher, - val messagesFromUiToApp: MessageListener, - val messageTypeRegistry: MessageTypeRegistry, - val fqnWebviewAdapter: FqnWebviewAdapter, -) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AppConnection.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AppConnection.kt deleted file mode 100644 index f3f2ea4ad96..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/apps/AppConnection.kt +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.apps - -import software.aws.toolkits.jetbrains.services.amazonq.commands.MessageTypeRegistry -import software.aws.toolkits.jetbrains.services.amazonq.messages.MessageConnector - -data class AppConnection( - val app: AmazonQApp, - val messagesFromAppToUi: MessageConnector, - val messagesFromUiToApp: MessageConnector, - val messageTypeRegistry: MessageTypeRegistry, -) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthController.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthController.kt deleted file mode 100644 index af2bf3efd9a..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthController.kt +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.auth - -import com.intellij.openapi.application.runInEdt -import com.intellij.openapi.project.Project -import software.amazon.q.core.utils.getLogger -import software.amazon.q.core.utils.warn -import software.amazon.q.jetbrains.core.gettingstarted.editor.ActiveConnection -import software.amazon.q.jetbrains.core.gettingstarted.editor.ActiveConnectionType -import software.amazon.q.jetbrains.core.gettingstarted.editor.BearerTokenFeatureSet -import software.amazon.q.jetbrains.core.gettingstarted.editor.checkBearerConnectionValidity -import software.amazon.q.jetbrains.core.gettingstarted.reauthenticateWithQ -import software.amazon.q.jetbrains.core.gettingstarted.requestCredentialsForQ -import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.TelemetryHelper -import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl -import software.aws.toolkits.resources.message -import software.aws.toolkits.telemetry.CwsprChatCommandType -import software.aws.toolkits.telemetry.UiTelemetry - -class AuthController { - /** - * Check the state of the Q connection. If the connection is valid then null is returned, otherwise it returns a [AuthNeededState] - * holding a message indicating the problem and what type of authentication is needed to resolve. - */ - fun getAuthNeededStates(project: Project): AuthNeededStates { - val connectionState = checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q) - - // CW chat is enabled for Builder and IDC users, same for Amazon Q - return AuthNeededStates( - chat = getAuthNeededState(connectionState), - amazonQ = getAuthNeededState(connectionState) - ) - } - - private fun getAuthNeededState( - amazonqConnectionState: ActiveConnection, - onlyIamIdcConnection: Boolean = false, - ): AuthNeededState? = - when (amazonqConnectionState) { - ActiveConnection.NotConnected -> { - AuthNeededState( - message = message("q.connection.disconnected"), - authType = AuthFollowUpType.FullAuth, - ) - } - - is ActiveConnection.ValidBearer -> { - if (onlyIamIdcConnection && amazonqConnectionState.connectionType != ActiveConnectionType.IAM_IDC) { - AuthNeededState( - message = message("q.connection.need_scopes"), - authType = AuthFollowUpType.Unsupported, - ) - } else { - null - } - } - - is ActiveConnection.ExpiredBearer -> AuthNeededState( - message = message("q.connection.expired"), - authType = AuthFollowUpType.ReAuth, - ) - // Not a bearer connection. This should not happen, but if it does, we treat it as a full-auth scenario - else -> { - logger.warn { "Received non-bearer connection for Q" } - AuthNeededState( - message = message("q.connection.invalid"), - authType = AuthFollowUpType.FullAuth, - ) - } - } - - fun handleAuth(project: Project, type: AuthFollowUpType) { - when (type) { - AuthFollowUpType.MissingScopes, - AuthFollowUpType.Unsupported, - AuthFollowUpType.FullAuth, - -> runInEdt { - UiTelemetry.click(project, "amazonq_chatAuthenticate") - requestCredentialsForQ(project, connectionInitiatedFromQChatPanel = true, isReauth = false) - } - - AuthFollowUpType.ReAuth, - -> runInEdt { - reauthenticateWithQ(project) - } - } - - TelemetryHelper.recordTelemetryChatRunCommand(CwsprChatCommandType.Auth, type.name, getStartUrl(project)) - } - - companion object { - private val logger = getLogger() - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthFollowUpType.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthFollowUpType.kt deleted file mode 100644 index 9c5d8377eda..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthFollowUpType.kt +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.auth - -import com.fasterxml.jackson.annotation.JsonValue - -enum class AuthFollowUpType( - @field:JsonValue val json: String, -) { - FullAuth("full-auth"), - ReAuth("re-auth"), - MissingScopes("missing_scopes"), - Unsupported("use-supported-auth"), -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthNeededState.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthNeededState.kt deleted file mode 100644 index e034717adf9..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/auth/AuthNeededState.kt +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.auth - -data class AuthNeededState( - val message: String, - val authType: AuthFollowUpType, -) - -data class AuthNeededStates( - val chat: AuthNeededState? = null, - val amazonQ: AuthNeededState? = null, -) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/commands/MessageSerializer.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/commands/MessageSerializer.kt deleted file mode 100644 index 29b7213a76b..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/commands/MessageSerializer.kt +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.commands - -import com.fasterxml.jackson.annotation.JsonAutoDetect -import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.annotation.PropertyAccessor -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.MapperFeature -import com.fasterxml.jackson.databind.SerializationFeature -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.treeToValue -import org.jetbrains.annotations.VisibleForTesting -import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage -import software.aws.toolkits.jetbrains.services.amazonq.messages.UnknownMessageType -import software.aws.toolkits.jetbrains.services.amazonq.util.command - -class MessageSerializer @VisibleForTesting constructor() { - - val objectMapper = jacksonObjectMapper() - .registerModule(JavaTimeModule()) - .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) - .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) - .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) - .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - - fun toNode(json: String) = objectMapper.readTree(json) - - fun deserialize(node: JsonNode, registeredTypes: MessageTypeRegistry): AmazonQMessage { - val type = registeredTypes.get(node.command) ?: return UnknownMessageType(node.asText()) - return objectMapper.treeToValue(node, type.java) - } - - fun serialize(value: Any): String = objectMapper.writeValueAsString(value) - - inline fun deserializeChatMessages(value: JsonNode): T = - objectMapper.treeToValue(value) - - inline fun deserializeChatMessages(value: JsonNode, clazz: Class): T = - objectMapper.treeToValue(value, clazz) - - // Provide singleton global access - companion object { - private val instance = MessageSerializer() - - fun getInstance() = instance - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/commands/MessageTypeRegistry.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/commands/MessageTypeRegistry.kt deleted file mode 100644 index 9547312a24d..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/commands/MessageTypeRegistry.kt +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.commands - -import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage -import kotlin.reflect.KClass - -private typealias MessageClass = KClass - -/** - * This class allows an App to register the target class to use for deserialization of a particular command from the TypeScript code. - * Messages from TypeScript arrive as a JSON object that always has a "command" field. Apps can register the specific class an object with a particular command - * should deserialize as before they are sent out to the app's MessageListener. - */ -class MessageTypeRegistry { - private val registry = mutableMapOf() - - fun register(command: String, type: MessageClass) = registry.put(command, type) - fun register(vararg entries: Pair) = registry.putAll(entries) - - fun remove(command: String) = registry.remove(command) - fun get(command: String) = registry[command] -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/explorerActions/ReauthenticateWithQ.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/explorerActions/ReauthenticateWithQ.kt deleted file mode 100644 index 066b3bda8d0..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/explorerActions/ReauthenticateWithQ.kt +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.explorerActions - -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import software.amazon.q.jetbrains.core.gettingstarted.reauthenticateWithQ -import software.aws.toolkits.resources.message - -class ReauthenticateWithQ : AnAction(message("q.reauthenticate")) { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - reauthenticateWithQ(project) - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/explorerActions/SignInToQAction.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/explorerActions/SignInToQAction.kt deleted file mode 100644 index ed364f873d1..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/explorerActions/SignInToQAction.kt +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.explorerActions - -import com.intellij.icons.AllIcons -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.application.runInEdt -import com.intellij.openapi.project.DumbAwareAction -import com.intellij.openapi.wm.ToolWindowManager -import software.amazon.q.jetbrains.core.credentials.ReauthSource -import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager -import software.amazon.q.jetbrains.core.credentials.pinning.QConnection -import software.amazon.q.jetbrains.core.credentials.reauthConnectionIfNeeded -import software.amazon.q.jetbrains.core.gettingstarted.requestCredentialsForQ -import software.amazon.q.jetbrains.utils.isQWebviewsAvailable -import software.aws.toolkits.jetbrains.services.amazonq.gettingstarted.openMeetQPage -import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindowFactory -import software.aws.toolkits.resources.message -import software.aws.toolkits.telemetry.UiTelemetry - -class SignInToQAction : SignInToQActionBase(message("q.sign.in")) { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - UiTelemetry.click(project, "auth_start_Q") - - if (!isQWebviewsAvailable()) { - requestCredentialsForQ(project, isReauth = false) - } else { - ToolWindowManager.getInstance(project).getToolWindow(AmazonQToolWindowFactory.WINDOW_ID)?.show() - } - } -} - -class EnableQAction : SignInToQActionBase(message("q.enable.text")) - -abstract class SignInToQActionBase(actionName: String) : DumbAwareAction(actionName, null, AllIcons.CodeWithMe.CwmAccess) { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - UiTelemetry.click(project, "auth_start_Q") - val connectionManager = ToolkitConnectionManager.getInstance(project) - connectionManager.activeConnectionForFeature(QConnection.getInstance())?.let { - reauthConnectionIfNeeded(project, it, isReAuth = true, reauthSource = ReauthSource.CODEWHISPERER_STATUSBAR) - } ?: run { - runInEdt { - if (requestCredentialsForQ(project, isReauth = false)) { - if (!openMeetQPage(project)) { - return@runInEdt - } - } - } - } - } -} 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 deleted file mode 100644 index 8c65f81aef7..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedContent.kt +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.gettingstarted - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.intellij.openapi.Disposable -import com.intellij.openapi.project.Project -import com.intellij.ui.JBColor -import com.intellij.ui.jcef.JBCefBrowserBase -import com.intellij.ui.jcef.JBCefJSQuery -import com.intellij.ui.jcef.JCEFHtmlPanel -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch -import org.cef.browser.CefBrowser -import org.cef.browser.CefFrame -import org.cef.handler.CefLoadHandlerAdapter -import software.amazon.q.jetbrains.core.coroutines.disposableCoroutineScope -import software.amazon.q.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 -import software.aws.toolkits.telemetry.UiTelemetry -import java.util.Base64 -import java.util.function.Function - -class QGettingStartedContent(val project: Project) : Disposable { - val jcefBrowser: JBCefBrowserBase = JCEFHtmlPanel("about:blank") - val receiveMessageQuery = JBCefJSQuery.create(jcefBrowser) - - init { - jcefBrowser.jbCefClient.addLoadHandler( - object : CefLoadHandlerAdapter() { - override fun onLoadEnd(browser: CefBrowser, frame: CefFrame, httpStatusCode: Int) { - // only needs to be done once - jcefBrowser.jbCefClient.removeLoadHandler(this, browser) - - disposableCoroutineScope(this@QGettingStartedContent).launch { - EditorThemeAdapter().onThemeChange() - .distinctUntilChanged() - .onEach { - val js = if (it.darkMode) { - "document.body.classList.add('$darkThemeClass');document.body.classList.remove('$lightThemeClass');" - } else { - "document.body.classList.add('$lightThemeClass');document.body.classList.remove('$darkThemeClass');" - } - browser.executeJavaScript(js, browser.url, 0) - } - .launchIn(this) - } - } - }, - jcefBrowser.cefBrowser - ) - loadWebView() - val handler = Function { - val command = jacksonObjectMapper().readTree(it).get("command").asText() - when (command) { - "sendToQ" -> { - UiTelemetry.click(project, "amazonq_meet_askq") - AmazonQToolWindow.getStarted(project) - } - } - - JBCefJSQuery.Response(null) - } - receiveMessageQuery.addHandler(handler) - } - - fun component() = jcefBrowser.component - - private fun loadWebView() { - // load the web app - jcefBrowser.loadURL(LocalAssetJBCefRequestHandler(jcefBrowser).createResource("content.html", getWebviewHTML())) - } - - private fun getWebviewHTML(): String { - val colorMode = if (JBColor.isBright()) lightThemeClass else darkThemeClass - val bgLogoDark = getBase64EncodedImageString("/icons/logos/Amazon-Q-Icon_White_Medium.svg") - val qLogo = getBase64EncodedImageString("/icons/logos/Amazon-Q-Icon_Gradient_Medium.svg") - val bgLogoLight = getBase64EncodedImageString("/icons/logos/Amazon-Q-Icon_Squid-Ink_Medium.svg") - val cwLogoDark = getBase64EncodedImageString("/icons/logos/CW_InlineSuggestions_dark.svg") - val cwLogoLight = getBase64EncodedImageString("/icons/logos/CW_InlineSuggestions_light.svg") - val postMessageToJavaJsCode = receiveMessageQuery.inject("JSON.stringify(message)") - - return """ - - - - - - - - - - -
-
- -

${message("q.onboarding.description")}

-
- -
-
-
- - - - """.trimIndent() - } - - private fun getBase64EncodedImageString(imageLocation: String) = QGettingStartedContent::class.java.getResourceAsStream(imageLocation).use { - Base64.getEncoder().encodeToString(it?.readAllBytes() ?: return@use null) - } - - private fun getImageSourceFromEncodedString(imageName: String?) = "data:image/svg+xml;base64,$imageName" - - override fun dispose() { - } - - companion object { - private const val darkThemeClass = "dark" - private const val lightThemeClass = "light" - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedEditor.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedEditor.kt deleted file mode 100644 index 4f7e4291005..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedEditor.kt +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.gettingstarted - -import com.intellij.openapi.fileEditor.FileEditor -import com.intellij.openapi.fileEditor.FileEditorState -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Disposer -import com.intellij.openapi.util.UserDataHolderBase -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.ui.components.JBScrollPane -import java.beans.PropertyChangeListener -import javax.swing.JComponent - -class QGettingStartedEditor( - private val project: Project, - private val file: VirtualFile, -) : - UserDataHolderBase(), FileEditor { - override fun dispose() { - } - - override fun getComponent(): JComponent { - val panel = QGettingStartedPanel(project) - Disposer.register(this, panel) - - return JBScrollPane(panel.component) - } - - override fun getFile(): VirtualFile = file - - override fun getName(): String = file.name - - override fun getPreferredFocusedComponent(): JComponent? = null - - override fun setState(state: FileEditorState) {} - - override fun isModified(): Boolean = false - - override fun isValid(): Boolean = true - - override fun addPropertyChangeListener(listener: PropertyChangeListener) { - } - - override fun removePropertyChangeListener(listener: PropertyChangeListener) { - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedEditorProvider.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedEditorProvider.kt deleted file mode 100644 index dde52f78fe2..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedEditorProvider.kt +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.gettingstarted - -import com.intellij.openapi.fileEditor.FileEditor -import com.intellij.openapi.fileEditor.FileEditorPolicy -import com.intellij.openapi.fileEditor.FileEditorProvider -import com.intellij.openapi.project.DumbAware -import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.VirtualFile - -class QGettingStartedEditorProvider : FileEditorProvider, DumbAware { - override fun accept(project: Project, file: VirtualFile) = file is QGettingStartedVirtualFile - - override fun createEditor(project: Project, file: VirtualFile): FileEditor { - file as QGettingStartedVirtualFile - return QGettingStartedEditor(project, file) - } - - override fun getEditorTypeId() = EDITOR_TYPE - - override fun getPolicy() = FileEditorPolicy.HIDE_DEFAULT_EDITOR - - companion object { - const val EDITOR_TYPE = "QGettingStartedUxMainPanel" - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedPanel.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedPanel.kt deleted file mode 100644 index d5e15052f00..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/gettingstarted/QGettingStartedPanel.kt +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.gettingstarted - -import com.intellij.openapi.Disposable -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Disposer -import com.intellij.ui.components.JBTextArea -import com.intellij.ui.components.panels.Wrapper -import com.intellij.ui.dsl.builder.Align -import com.intellij.ui.dsl.builder.panel -import software.amazon.q.jetbrains.utils.isQWebviewsAvailable - -class QGettingStartedPanel( - val project: Project, -) : Disposable { - private val webviewContainer = Wrapper() - var browser: QGettingStartedContent? = null - private set - - val component = panel { - row { - cell(webviewContainer) - .align(Align.FILL) - }.resizableRow() - } - - init { - if (!isQWebviewsAvailable()) { - // Fallback to an alternative browser-less solution - webviewContainer.add(JBTextArea("JCEF not supported")) - browser = null - } else { - browser = QGettingStartedContent(project).also { - webviewContainer.add(it.component()) - } - } - } - - override fun dispose() { - browser?.let { - Disposer.dispose(it) - } - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/messages/AmazonQMessages.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/messages/AmazonQMessages.kt deleted file mode 100644 index 70c0e1641f0..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/messages/AmazonQMessages.kt +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.messages - -interface AmazonQMessage - -/** - * Message that is sent when a command is received that does not have a registered deserialization class. The content is the plain-text representation of the - * received JSON. - */ -data class UnknownMessageType(val content: String) : AmazonQMessage diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/messages/MessagePublisher.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/messages/MessagePublisher.kt deleted file mode 100644 index 62b350c56f7..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/messages/MessagePublisher.kt +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.messages - -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.asSharedFlow - -/** - * MessagePublisher is used for sending outbound messages - */ -interface MessagePublisher { - suspend fun publish(message: AmazonQMessage) -} - -/** - * MessageListener is used to receive inbound messages - */ -interface MessageListener { - val flow: Flow -} - -/** - * A MessageConnector is a uni-directional channel for passing messages between Amazon Q and App implementations. It is provided as either a MessagePublisher or - * MessageListener depending on the intended direction of communication. - */ -class MessageConnector : MessagePublisher, MessageListener { - private val _messages = MutableSharedFlow(extraBufferCapacity = 10, replay = 10) - override val flow = _messages.asSharedFlow() - - override suspend fun publish(message: AmazonQMessage) { - _messages.emit(message) - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/onboarding/OnboardingPageInteraction.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/onboarding/OnboardingPageInteraction.kt deleted file mode 100644 index 6a57c9ec73a..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/onboarding/OnboardingPageInteraction.kt +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.onboarding - -import com.fasterxml.jackson.annotation.JsonValue -import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage - -enum class OnboardingPageInteractionType( - @field:JsonValue val json: String, -) { - CwcButtonClick("onboarding-page-cwc-button-clicked"), -} - -data class OnboardingPageInteraction( - val type: OnboardingPageInteractionType, -) : AmazonQMessage diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/startup/AmazonQStartupActivity.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/startup/AmazonQStartupActivity.kt deleted file mode 100644 index 6dd45965158..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/startup/AmazonQStartupActivity.kt +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.startup - -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.runInEdt -import com.intellij.openapi.project.Project -import com.intellij.openapi.startup.ProjectActivity -import com.intellij.openapi.wm.ToolWindowManager -import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection -import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager -import software.amazon.q.jetbrains.core.credentials.pinning.QConnection -import software.amazon.q.jetbrains.core.gettingstarted.emitUserState -import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService -import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager -import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindow -import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindowFactory -import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager -import software.aws.toolkits.jetbrains.services.cwc.inline.InlineChatController -import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings -import java.util.concurrent.atomic.AtomicBoolean - -class AmazonQStartupActivity : ProjectActivity { - private val runOnce = AtomicBoolean(false) - - override suspend fun execute(project: Project) { - if (ApplicationManager.getApplication().isUnitTestMode) return - - ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance())?.let { - if (it is AwsBearerTokenConnection && CodeWhispererFeatureConfigService.getInstance().getChatWSContext()) { - CodeWhispererSettings.getInstance().toggleProjectContextEnabled(value = true, passive = true) - } - } - - // initialize html contents in BGT so users don't have to wait when they open the tool window - AmazonQToolWindow.getInstance(project) - InlineChatController.getInstance(project) - - if (CodeWhispererExplorerActionManager.getInstance().getIsFirstRestartAfterQInstall()) { - runInEdt { - val toolWindow = ToolWindowManager.getInstance(project).getToolWindow(AmazonQToolWindowFactory.WINDOW_ID) ?: return@runInEdt - toolWindow.show() - CodeWhispererExplorerActionManager.getInstance().setIsFirstRestartAfterQInstall(false) - } - } - - QRegionProfileManager.getInstance().validateProfile(project) - - AmazonQLspService.getInstance(project) - if (runOnce.get()) return - emitUserState(project) - runOnce.set(true) - } -} 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 deleted file mode 100644 index cc09349dcca..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt +++ /dev/null @@ -1,335 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.toolwindow - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.intellij.openapi.Disposable -import com.intellij.openapi.components.service -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Disposer -import com.intellij.ui.components.JBLoadingPanel -import com.intellij.ui.components.JBTextArea -import com.intellij.ui.components.ProgressBarLoadingDecorator -import com.intellij.ui.components.panels.Wrapper -import com.intellij.ui.dsl.builder.Align -import com.intellij.ui.dsl.builder.AlignX -import com.intellij.ui.dsl.builder.AlignY -import com.intellij.ui.dsl.builder.panel -import com.intellij.ui.jcef.JBCefApp -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import software.amazon.q.core.utils.error -import software.amazon.q.core.utils.getLogger -import software.amazon.q.jetbrains.core.coroutines.EDT -import software.amazon.q.jetbrains.isDeveloperMode -import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend -import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext -import software.aws.toolkits.jetbrains.services.amazonq.apps.AppConnection -import software.aws.toolkits.jetbrains.services.amazonq.commands.MessageTypeRegistry -import software.aws.toolkits.jetbrains.services.amazonq.isQSupportedInThisVersion -import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService -import software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts.ArtifactManager -import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.ChatCommunicationManager -import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage -import software.aws.toolkits.jetbrains.services.amazonq.messages.MessageConnector -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager -import software.aws.toolkits.jetbrains.services.amazonq.util.highlightCommand -import software.aws.toolkits.jetbrains.services.amazonq.webview.Browser -import software.aws.toolkits.jetbrains.services.amazonq.webview.BrowserConnector -import software.aws.toolkits.jetbrains.services.amazonq.webview.FqnWebviewAdapter -import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.EditorThemeAdapter -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.auth.isCodeScanAvailable -import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable -import software.aws.toolkits.resources.message -import java.awt.datatransfer.DataFlavor -import java.awt.dnd.DropTarget -import java.awt.dnd.DropTargetDragEvent -import java.awt.dnd.DropTargetDropEvent -import java.awt.dnd.DropTargetEvent -import java.io.File -import java.util.concurrent.CompletableFuture -import javax.imageio.ImageIO.read -import javax.swing.JButton - -class AmazonQPanel(val project: Project, private val scope: CoroutineScope) : Disposable { - private val browser = CompletableFuture() - private val webviewContainer = Wrapper() - private val appSource = AppSource() - private val browserConnector = BrowserConnector(project = project) - private val editorThemeAdapter = EditorThemeAdapter() - private val appConnections = mutableListOf() - - val component = panel { - row { - cell(webviewContainer) - .align(Align.FILL) - }.resizableRow() - - // Button to show the web debugger for debugging the UI: - if (isDeveloperMode()) { - row { - cell( - JButton("Show Web Debugger").apply { - addActionListener { - // Code to be executed when the button is clicked - // Add your logic here - - browser.get().jcefBrowser.openDevtools() - } - }, - ) - .align(AlignX.CENTER) - .align(AlignY.BOTTOM) - } - } - } - - init { - if (!JBCefApp.isSupported()) { - // Fallback to an alternative browser-less solution - 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")) - } - browser.complete(null) - } else if (!isQSupportedInThisVersion()) { - webviewContainer.add(JBTextArea("${message("q.unavailable")}\n ${message("q.unavailable.node")}")) - browser.complete(null) - } else { - val loadingPanel = if (isRunningOnRemoteBackend()) { - JBLoadingPanel(null) { - ProgressBarLoadingDecorator(it, this, -1) - } - } else { - JBLoadingPanel(null, this) - } - - val wrapper = Wrapper() - loadingPanel.startLoading() - - webviewContainer.add(wrapper) - wrapper.setContent(loadingPanel) - - scope.launch { - val mynahAsset = service().fetchArtifact(project).resolve("amazonq-ui.js") - // wait for server to be running - AmazonQLspService.getInstance(project).instanceFlow.first() - - withContext(EDT) { - browser.complete( - Browser(this@AmazonQPanel, mynahAsset, project).also { browserInstance -> - wrapper.setContent(browserInstance.component()) - - // Register direct callback instead of using message bus - ChatCommunicationManager.getInstance(project).setChatUpdateCallback { message -> - browserInstance.postChat(message) - } - - // Add DropTarget to the browser component - // JCEF does not propagate OS-level dragenter, dragOver and drop into DOM. - // As an alternative, enabling the native drag in JCEF, - // and let the native handling the drop event, and update the UI through JS bridge. - val dropTarget = object : DropTarget() { - override fun dragEnter(dtde: DropTargetDragEvent) { - setDragAndDropOverlayVisible(browserInstance, true) - } - override fun dragOver(dtde: DropTargetDragEvent) { - setDragAndDropOverlayVisible(browserInstance, true) - } - override fun dragExit(dte: DropTargetEvent) { - setDragAndDropOverlayVisible(browserInstance, false) - } - override fun drop(dtde: DropTargetDropEvent) { - try { - dtde.acceptDrop(dtde.dropAction) - val transferable = dtde.transferable - if (transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { - val fileList = transferable.getTransferData(DataFlavor.javaFileListFlavor) as List<*> - - val errorMessages = mutableListOf() - val validImages = mutableListOf() - val allowedTypes = setOf("jpg", "jpeg", "png", "gif", "webp") - val maxFileSize = 3.75 * 1024 * 1024 // 3.75MB in bytes - val maxDimension = 8000 - - for (file in fileList as List) { - val validationResult = validateImageFile(file, allowedTypes, maxFileSize, maxDimension) - if (validationResult != null) { - errorMessages.add(validationResult) - } else { - validImages.add(file) - } - } - - // File count restriction - if (validImages.size > 20) { - errorMessages.add("A maximum of 20 images can be added to a single message.") - validImages.subList(20, validImages.size).clear() - } - - executeJavaScript(browserInstance, "window.resetTopBarClicked()") - - setDragAndDropOverlayVisible(browserInstance, false) - - val json = OBJECT_MAPPER.writeValueAsString(validImages) - executeJavaScript(browserInstance, "window.handleNativeDrop('$json')") - - if (errorMessages.isNotEmpty()) { - val errorJson = OBJECT_MAPPER.writeValueAsString(errorMessages) - executeJavaScript(browserInstance, "window.handleNativeNotify('$errorJson')") - } - - dtde.dropComplete(true) - } else { - dtde.dropComplete(false) - } - } catch (e: Exception) { - LOG.error { "Failed to handle file drop operation: ${e.message}" } - dtde.dropComplete(false) - } - } - } - - // Set DropTarget on the browser component and its children - browserInstance.component()?.let { component -> - component.dropTarget = dropTarget - // Also try setting on parent if needed - component.parent?.dropTarget = dropTarget - } - - initConnections() - connectUi(browserInstance) - connectApps(browserInstance) - - loadingPanel.stopLoading() - } - ) - } - } - } - } - - fun sendMessage(message: AmazonQMessage, tabType: String) { - appConnections.filter { it.app.tabTypes.contains(tabType) }.forEach { - scope.launch { - it.messagesFromUiToApp.publish(message) - } - } - } - - fun sendMessageAppToUi(message: AmazonQMessage, tabType: String) { - appConnections.filter { it.app.tabTypes.contains(tabType) }.forEach { - scope.launch { - it.messagesFromAppToUi.publish(message) - } - } - } - - private fun initConnections() { - val apps = appSource.getApps(project) - apps.forEach { app -> - appConnections += AppConnection( - app = app, - messagesFromAppToUi = MessageConnector(), - messagesFromUiToApp = MessageConnector(), - messageTypeRegistry = MessageTypeRegistry(), - ) - } - } - - private fun connectApps(browser: Browser) { - val fqnWebviewAdapter = FqnWebviewAdapter(browser.jcefBrowser, browserConnector) - - appConnections.forEach { connection -> - val initContext = AmazonQAppInitContext( - project = project, - messagesFromAppToUi = connection.messagesFromAppToUi, - messagesFromUiToApp = connection.messagesFromUiToApp, - messageTypeRegistry = connection.messageTypeRegistry, - fqnWebviewAdapter = fqnWebviewAdapter, - ) - // Connect the app to the UI - connection.app.init(initContext) - // Dispose of the app when the tool window is disposed. - Disposer.register(this, connection.app) - } - } - - private fun connectUi(browser: Browser) { - browser.init( - isCodeTransformAvailable = isCodeTransformAvailable(project), - isCodeScanAvailable = isCodeScanAvailable(project), - highlightCommand = highlightCommand(), - activeProfile = QRegionProfileManager.getInstance().takeIf { it.shouldDisplayProfileInfo(project) }?.activeProfile(project) - ) - - scope.launch { - // Pipe messages from the UI to the relevant apps and vice versa - browserConnector.connect( - browser = browser, - connections = appConnections, - ) - } - - scope.launch { - // Update the theme in the UI when the IDE theme changes - browserConnector.connectTheme( - chatBrowser = browser.jcefBrowser.cefBrowser, - themeSource = editorThemeAdapter.onThemeChange(), - ) - } - } - - private fun executeJavaScript(browserInstance: Browser, jsCommand: String) { - try { - browserInstance.jcefBrowser.cefBrowser.executeJavaScript( - jsCommand, - browserInstance.jcefBrowser.cefBrowser.url, - 0 - ) - } catch (e: Exception) { - LOG.error { "Failed to execute JavaScript: $jsCommand - ${e.message}" } - } - } - - private fun setDragAndDropOverlayVisible(browserInstance: Browser, visible: Boolean) { - executeJavaScript(browserInstance, "window.setDragAndDropVisible('$visible')") - } - - private fun validateImageFile(file: File, allowedTypes: Set, maxFileSize: Double, maxDimension: Int): String? { - val fileName = file.name - val ext = fileName.substringAfterLast('.', "").lowercase() - - if (ext !in allowedTypes) { - return "$fileName: File must be an image in JPEG, PNG, GIF, or WebP format." - } - - if (file.length() > maxFileSize) { - return "$fileName: Image must be no more than 3.75MB in size." - } - - return try { - val img = read(file) - when { - img == null -> "$fileName: File could not be read as an image." - img.width > maxDimension -> "$fileName: Image must be no more than 8,000px in width." - img.height > maxDimension -> "$fileName: Image must be no more than 8,000px in height." - else -> null - } - } catch (e: Exception) { - "$fileName: File could not be read as an image." - } - } - - companion object { - private val LOG = getLogger() - private val OBJECT_MAPPER = jacksonObjectMapper() - } - - override fun dispose() { - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindow.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindow.kt deleted file mode 100644 index 5df972bba09..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindow.kt +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.toolwindow - -import com.intellij.openapi.Disposable -import com.intellij.openapi.application.runInEdt -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Disposer -import com.intellij.openapi.wm.ToolWindowManager -import kotlinx.coroutines.CoroutineScope -import software.aws.toolkits.jetbrains.services.amazonq.onboarding.OnboardingPageInteraction -import software.aws.toolkits.jetbrains.services.amazonq.onboarding.OnboardingPageInteractionType -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.runCodeScanMessage - -@Service(Service.Level.PROJECT) -class AmazonQToolWindow private constructor( - private val project: Project, - private val scope: CoroutineScope, -) : Disposable { - private var chatPanel = AmazonQPanel(project, scope) - - val component - get() = chatPanel.component - fun disposeAndRecreate() { - Disposer.dispose(chatPanel) - chatPanel = AmazonQPanel(project, scope) - } - - companion object { - fun getInstance(project: Project): AmazonQToolWindow = project.service() - - private fun showChatWindow(project: Project) = runInEdt { - val toolWindow = ToolWindowManager.getInstance(project).getToolWindow(AmazonQToolWindowFactory.WINDOW_ID) - toolWindow?.show() - } - - fun getStarted(project: Project) { - // Make sure the window is shown - showChatWindow(project) - - // Send the interaction message - val window = getInstance(project) - window.chatPanel.sendMessage(OnboardingPageInteraction(OnboardingPageInteractionType.CwcButtonClick), "cwc") - } - - fun openScanTab(project: Project) { - showChatWindow(project) - val window = getInstance(project) - window.chatPanel.sendMessageAppToUi(runCodeScanMessage, tabType = "codescan") - } - } - - override fun dispose() { - // Nothing to do - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowFactory.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowFactory.kt deleted file mode 100644 index 6a8e02dfdfa..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowFactory.kt +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.toolwindow - -import com.intellij.openapi.actionSystem.ActionManager -import com.intellij.openapi.application.runInEdt -import com.intellij.openapi.project.DumbAware -import com.intellij.openapi.project.Project -import com.intellij.openapi.wm.ToolWindow -import com.intellij.openapi.wm.ToolWindowFactory -import com.intellij.openapi.wm.ex.ToolWindowEx -import com.intellij.ui.components.panels.Wrapper -import com.intellij.util.ui.components.BorderLayoutPanel -import software.amazon.q.core.utils.debug -import software.amazon.q.core.utils.getLogger -import software.amazon.q.jetbrains.core.credentials.ToolkitConnection -import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager -import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerListener -import software.amazon.q.jetbrains.core.credentials.pinning.QConnection -import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState -import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener -import software.amazon.q.jetbrains.core.notifications.BannerContent -import software.amazon.q.jetbrains.core.notifications.NotificationDismissalState -import software.amazon.q.jetbrains.core.notifications.NotificationPanel -import software.amazon.q.jetbrains.core.notifications.ProcessNotificationsBase -import software.amazon.q.jetbrains.core.webview.BrowserState -import software.amazon.q.jetbrains.utils.isQConnected -import software.amazon.q.jetbrains.utils.isQExpired -import software.amazon.q.jetbrains.utils.isQWebviewsAvailable -import software.amazon.q.jetbrains.utils.isRunningOnRemoteBackend -import software.aws.toolkits.jetbrains.services.amazonq.QWebviewPanel -import software.aws.toolkits.jetbrains.services.amazonq.RefreshQChatPanelButtonPressedListener -import software.aws.toolkits.jetbrains.services.amazonq.gettingstarted.openMeetQPage -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileSelectedListener -import software.aws.toolkits.resources.AmazonQBundle -import software.aws.toolkits.resources.message -import software.aws.toolkits.telemetry.FeatureId -import java.awt.event.ComponentAdapter -import java.awt.event.ComponentEvent - -class AmazonQToolWindowFactory : ToolWindowFactory, DumbAware { - - override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { - val mainPanel = BorderLayoutPanel() - val qPanel = Wrapper() - val notificationPanel = NotificationPanel().apply { - if (isRunningOnRemoteBackend() && !NotificationDismissalState.getInstance().isDismissed(REMOTE_RESIZE_NOTIFICATION_ID)) { - updateNotificationPanel( - BannerContent( - REMOTE_RESIZE_NOTIFICATION_ID, - AmazonQBundle.message("amazonq.resize.panel"), - // message is not used for banner - "", - ) - ) - } - } - - mainPanel.addToTop(notificationPanel) - mainPanel.add(qPanel) - val notifListener = ProcessNotificationsBase.getInstance(project) - notifListener.addListenerForNotification { bannerContent -> - runInEdt { - notificationPanel.updateNotificationPanel(bannerContent) - } - } - - if (toolWindow is ToolWindowEx) { - val actionManager = ActionManager.getInstance() - toolWindow.setTitleActions(listOf(actionManager.getAction("aws.q.toolwindow.titleBar"))) - } - val contentManager = toolWindow.contentManager - - project.messageBus.connect(toolWindow.disposable).subscribe( - ToolkitConnectionManagerListener.TOPIC, - object : ToolkitConnectionManagerListener { - override fun activeConnectionChanged(newConnection: ToolkitConnection?) { - ToolkitConnectionManager.getInstance(project).activeConnectionForFeature(QConnection.getInstance())?.let { qConn -> - openMeetQPage(project) - } - preparePanelContent(project, qPanel) - } - } - ) - - project.messageBus.connect(toolWindow.disposable).subscribe( - RefreshQChatPanelButtonPressedListener.TOPIC, - object : RefreshQChatPanelButtonPressedListener { - override fun onRefresh() { - preparePanelContent(project, qPanel) - } - } - ) - - project.messageBus.connect(toolWindow.disposable).subscribe( - BearerTokenProviderListener.TOPIC, - object : BearerTokenProviderListener { - override fun onProviderChange(providerId: String, newScopes: List?) { - if (ToolkitConnectionManager.getInstance(project).connectionStateForFeature(QConnection.getInstance()) == BearerTokenAuthState.AUTHORIZED) { - preparePanelContent(project, qPanel) - } - } - } - ) - - project.messageBus.connect(toolWindow.disposable).subscribe( - QRegionProfileSelectedListener.TOPIC, - object : QRegionProfileSelectedListener { - // note we name myProject intentionally ow it will shadow the "project" provided by the IDE - override fun onProfileSelected(myProject: Project, profile: QRegionProfile?) { - if (project.isDisposed) return - preparePanelContent(project, qPanel) - } - } - ) - - preparePanelContent(project, qPanel) - - val content = contentManager.factory.createContent(mainPanel, null, false).also { - it.isCloseable = true - it.isPinnable = true - } - toolWindow.activate(null) - contentManager.addContent(content) - } - - private fun preparePanelContent( - project: Project, - qPanel: Wrapper, - ) { - /** - * only render Q Chat when - * 1. There is a Q connection - * 2. Q connection is not expired - * 3. User is not pending region profile selection - */ - val component = if (isQConnected(project) && !isQExpired(project) && !QRegionProfileManager.getInstance().isPendingProfileSelection(project)) { - AmazonQToolWindow.getInstance(project).component - } else { - QWebviewPanel.getInstance(project).browser?.prepareBrowser(BrowserState(FeatureId.AmazonQ)) - QWebviewPanel.getInstance(project).component - } - runInEdt { - qPanel.setContent(component) - } - } - - /** - * Only applies to local - * On remote, since we are using PROJECTOR_INSTANCING, this will never run - */ - override fun init(toolWindow: ToolWindow) { - toolWindow.stripeTitle = message("toolwindow.stripe.amazon.q.window") - toolWindow.component.addComponentListener( - object : ComponentAdapter() { - override fun componentResized(e: ComponentEvent) { - val newWidth = e.component.width - if (newWidth >= MINIMUM_TOOLWINDOW_WIDTH) return - LOG.debug { - "Amazon Q Tool window stretched to a width less than the minimum allowed width, resizing to the minimum allowed width" - } - - // can't implement equivalent on remote as stretchWidth impl is noop - (toolWindow as ToolWindowEx).stretchWidth(MINIMUM_TOOLWINDOW_WIDTH - newWidth) - } - } - ) - } - - override fun shouldBeAvailable(project: Project): Boolean = isQWebviewsAvailable() - - companion object { - private val LOG = getLogger() - const val WINDOW_ID = AMAZON_Q_WINDOW_ID - private const val MINIMUM_TOOLWINDOW_WIDTH = 325 - private const val REMOTE_RESIZE_NOTIFICATION_ID = "resize.amazon.q.toolwindow.if.remote" - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowListener.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowListener.kt deleted file mode 100644 index b201519e6f1..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowListener.kt +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.toolwindow - -import com.intellij.openapi.wm.ToolWindow -import com.intellij.openapi.wm.ToolWindowManager -import com.intellij.openapi.wm.ex.ToolWindowManagerListener -import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.TelemetryHelper - -class AmazonQToolWindowListener : ToolWindowManagerListener { - - override fun toolWindowShown(toolWindow: ToolWindow) { - if (toolWindow.id == AmazonQToolWindowFactory.WINDOW_ID) { - TelemetryHelper.recordOpenChat(toolWindow.project) - } - } - - override fun stateChanged( - toolWindowManager: ToolWindowManager, - toolWindow: ToolWindow, - changeType: ToolWindowManagerListener.ToolWindowManagerEventType, - ) { - if (toolWindow.id != AmazonQToolWindowFactory.WINDOW_ID) { - return - } - - if (changeType == ToolWindowManagerListener.ToolWindowManagerEventType.HideToolWindow) { - TelemetryHelper.recordCloseChat(toolWindow.project) - } - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AppSource.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AppSource.kt deleted file mode 100644 index d54fed75552..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AppSource.kt +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.toolwindow - -import com.intellij.openapi.extensions.ExtensionPointName -import com.intellij.openapi.project.Project -import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppFactory - -class AppSource { - private val extensionPointName = ExtensionPointName.create("amazon.q.appFactory") - fun getApps(project: Project) = buildList { - extensionPointName.forEachExtensionSafe { - add(it.createApp(project)) - } - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/HighlightCommand.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/HighlightCommand.kt deleted file mode 100644 index 5019d051c1b..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/HighlightCommand.kt +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.util - -import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService - -data class HighlightCommand(val command: String, val description: String) - -fun highlightCommand(): HighlightCommand? { - val feature = CodeWhispererFeatureConfigService.getInstance().getHighlightCommandFeature() - - if (feature == null || feature.value.stringValue().isEmpty()) return null - - return HighlightCommand(feature.value.stringValue(), feature.variation) -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/JcefBrowserUtil.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/JcefBrowserUtil.kt deleted file mode 100644 index fd86513a863..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/JcefBrowserUtil.kt +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.util - -import com.intellij.openapi.Disposable -import com.intellij.openapi.util.Disposer -import com.intellij.ui.jcef.JBCefApp -import com.intellij.ui.jcef.JBCefBrowserBase -import com.intellij.ui.jcef.JBCefBrowserBuilder -import com.intellij.ui.jcef.JBCefClient - -fun createBrowser(parent: Disposable): JBCefBrowserBase { - val client = JBCefApp.getInstance().createClient().apply { - setProperty(JBCefClient.Properties.JS_QUERY_POOL_SIZE, 5) - } - - Disposer.register(parent, client) - - return JBCefBrowserBuilder() - .setClient(client) - .setOffScreenRendering(true) - .build() -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/JsonUtil.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/JsonUtil.kt deleted file mode 100644 index 673396fa7e9..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/util/JsonUtil.kt +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.util - -import com.fasterxml.jackson.databind.JsonNode - -val JsonNode.command - get() = get("command").asText() - -val JsonNode.tabType - get() = get("tabType")?.asText() 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 deleted file mode 100644 index ee3053b1c2e..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/Browser.kt +++ /dev/null @@ -1,577 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -@file:Suppress("BannedImports") -package software.aws.toolkits.jetbrains.services.amazonq.webview - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.google.gson.Gson -import com.intellij.openapi.Disposable -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Disposer -import com.intellij.ui.jcef.JBCefJSQuery -import software.amazon.q.core.utils.inputStream -import software.amazon.q.jetbrains.core.webview.LocalAssetJBCefRequestHandler -import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService -import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.FlareUiMessage -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile -import software.aws.toolkits.jetbrains.services.amazonq.util.HighlightCommand -import software.aws.toolkits.jetbrains.services.amazonq.util.createBrowser -import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.EditorThemeAdapter -import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.ThemeBrowserAdapter -import software.aws.toolkits.jetbrains.settings.MeetQSettings -import java.nio.file.Path -import java.nio.file.Paths - -/** - * Displays the web view for the Amazon Q tool window - */ -class Browser(parent: Disposable, private val mynahAsset: Path, val project: Project) : Disposable { - - val jcefBrowser = createBrowser(parent) - - 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, - isCodeScanAvailable: Boolean, - highlightCommand: HighlightCommand?, - activeProfile: QRegionProfile?, - ) { - loadWebView( - isCodeTransformAvailable, - isCodeScanAvailable, - highlightCommand, - activeProfile - ) - } - - override fun dispose() { - Disposer.dispose(jcefBrowser) - } - - fun component() = jcefBrowser.component - - fun postChat(command: FlareUiMessage) = postChat(Gson().toJson(command)) - - @Deprecated("shouldn't need this version") - fun postChat(message: String) { - jcefBrowser - .cefBrowser - .executeJavaScript("window.postMessage($message)", jcefBrowser.cefBrowser.url, 0) - } - - // Load the chat web app into the jcefBrowser - private fun loadWebView( - isCodeTransformAvailable: Boolean, - isCodeScanAvailable: Boolean, - highlightCommand: HighlightCommand?, - activeProfile: QRegionProfile?, - ) { - // setup empty state. The message request handlers use this for storing state - // that's persistent between page loads. - jcefBrowser.setProperty("state", "") - jcefBrowser.jbCefClient.addDragHandler({ browser, dragData, mask -> - true // Allow drag operations - }, jcefBrowser.cefBrowser) - // load the web app - jcefBrowser.loadURL( - assetRequestHandler.createResource( - "webview/chat.html", - getWebviewHTML( - isCodeTransformAvailable, - isCodeScanAvailable, - highlightCommand, - activeProfile, - ) - ) - ) - } - - /** - * Generate index.html for the web view - * @return HTML source - */ - private fun getWebviewHTML( - isCodeTransformAvailable: Boolean, - isCodeScanAvailable: Boolean, - highlightCommand: HighlightCommand?, - activeProfile: QRegionProfile?, - ): String { - val postMessageToJavaJsCode = receiveMessageQuery.inject("JSON.stringify(message)") - val connectorAdapterPath = "${LocalAssetJBCefRequestHandler.PROTOCOL}://${LocalAssetJBCefRequestHandler.AUTHORITY}/mynah/js/connectorAdapter.js" - val mynahResource = assetRequestHandler.createResource(mynahAsset.fileName.toString(), mynahAsset.inputStream()) - - // https://github.com/highlightjs/highlight.js/issues/1387 - // language=HTML - val jsScripts = """ - - - - """.trimIndent() - - // language=HTML - return """ - - - - - AWS Q - $jsScripts - - -
Amazon Q is loading...
- - - - - """.trimIndent() - } - - 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 deleted file mode 100644 index 21c6a9dbb83..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt +++ /dev/null @@ -1,792 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -@file:Suppress("BannedImports") -package software.aws.toolkits.jetbrains.services.amazonq.webview - -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.node.ObjectNode -import com.fasterxml.jackson.module.kotlin.readValue -import com.fasterxml.jackson.module.kotlin.treeToValue -import com.google.gson.Gson -import com.intellij.ide.BrowserUtil -import com.intellij.ide.util.RunOnceUtil -import com.intellij.openapi.application.runInEdt -import com.intellij.openapi.application.runReadAction -import com.intellij.openapi.fileEditor.FileDocumentManager -import com.intellij.openapi.fileEditor.FileEditorManager -import com.intellij.openapi.options.ShowSettingsUtil -import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.LocalFileSystem -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.ui.jcef.JBCefJSQuery.Response -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch -import org.cef.browser.CefBrowser -import org.eclipse.lsp4j.TextDocumentIdentifier -import org.eclipse.lsp4j.jsonrpc.ResponseErrorException -import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode -import org.intellij.lang.annotations.Language -import software.amazon.q.core.utils.error -import software.amazon.q.core.utils.getLogger -import software.amazon.q.core.utils.info -import software.amazon.q.core.utils.warn -import software.aws.toolkits.jetbrains.services.amazonq.GetAmazonQLogsAction -import software.aws.toolkits.jetbrains.services.amazonq.apps.AppConnection -import software.aws.toolkits.jetbrains.services.amazonq.commands.MessageSerializer -import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQChatServer -import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService -import software.aws.toolkits.jetbrains.services.amazonq.lsp.JsonRpcMethod -import software.aws.toolkits.jetbrains.services.amazonq.lsp.JsonRpcNotification -import software.aws.toolkits.jetbrains.services.amazonq.lsp.JsonRpcRequest -import software.aws.toolkits.jetbrains.services.amazonq.lsp.encryption.JwtEncryptionManager -import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.ChatCommunicationManager -import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.FlareUiMessage -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.AUTH_FOLLOW_UP_CLICKED -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.AuthFollowUpClickNotification -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ButtonClickResult -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_BUTTON_CLICK -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_CONVERSATION_CLICK -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_COPY_CODE_TO_CLIPBOARD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_CREATE_PROMPT -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_DISCLAIMER_ACKNOWLEDGED -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_FEEDBACK -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_FILE_CLICK -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_FOLLOW_UP_CLICK -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_INFO_LINK_CLICK -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_INSERT_TO_CURSOR -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_LINK_CLICK -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_LIST_CONVERSATIONS -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_OPEN_TAB -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_PINNED_CONTEXT_ADD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_PINNED_CONTEXT_REMOVE -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_PROMPT_OPTION_ACKNOWLEDGED -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_QUICK_ACTION -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_READY -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_SOURCE_LINK_CLICK -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_ADD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_BAR_ACTIONS -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_CHANGE -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_REMOVE -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.EncryptedChatParams -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.EncryptedQuickActionChatParams -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GET_SERIALIZED_CHAT_REQUEST_METHOD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GetSerializedChatResponse -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.LIST_AVAILABLE_MODELS -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.LIST_MCP_SERVERS_REQUEST_METHOD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.LIST_RULES_REQUEST_METHOD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.MCP_SERVER_CLICK_REQUEST_METHOD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OPEN_FILE_DIALOG -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OPEN_FILE_DIALOG_REQUEST_METHOD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OPEN_SETTINGS -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OPEN_WORKSPACE_SETTINGS_KEY -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenSettingsNotification -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenTabResponse -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenTabResult -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenTabResultError -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenTabResultSuccess -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.PROMPT_INPUT_OPTIONS_CHANGE -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.QuickChatActionRequest -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.RULE_CLICK_REQUEST_METHOD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SEND_CHAT_COMMAND_PROMPT -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.STOP_CHAT_RESPONSE -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SendChatPromptRequest -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.StopResponseMessage -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.TELEMETRY_EVENT -import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.LspEditorUtil -import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.LspEditorUtil.toUriString -import software.aws.toolkits.jetbrains.services.amazonq.util.command -import software.aws.toolkits.jetbrains.services.amazonq.util.tabType -import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.AmazonQTheme -import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.ThemeBrowserAdapter -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.auth.isCodeScanAvailable -import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanIssue -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanManager -import software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhispererConfigurable -import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants -import software.aws.toolkits.jetbrains.settings.MeetQSettings -import software.aws.toolkits.telemetry.MetricResult -import software.aws.toolkits.telemetry.Telemetry -import java.nio.file.Path -import java.util.concurrent.CompletableFuture -import java.util.concurrent.CompletionException -import java.util.function.Function - -class BrowserConnector( - private val serializer: MessageSerializer = MessageSerializer.getInstance(), - private val themeBrowserAdapter: ThemeBrowserAdapter = ThemeBrowserAdapter(), - private val project: Project, -) { - val uiReady = CompletableDeferred() - private val chatCommunicationManager = ChatCommunicationManager.getInstance(project) - - suspend fun connect( - browser: Browser, - connections: List, - ) = coroutineScope { - // Send browser messages to the outbound publisher - addMessageHook(browser) - .onEach { json -> - val node = serializer.toNode(json) - when (node.command) { - // this is sent when the named agents UI is ready - "ui-is-ready" -> { - uiReady.complete(true) - chatCommunicationManager.setUiReady() - RunOnceUtil.runOnceForApp("AmazonQ-UI-Ready") { - MeetQSettings.getInstance().reinvent2024OnboardingCount += 1 - } - } - CHAT_DISCLAIMER_ACKNOWLEDGED -> { - MeetQSettings.getInstance().disclaimerAcknowledged = true - } - - // some weird issue preventing deserialization from working - "open-user-guide" -> { - BrowserUtil.browse(node.get("userGuideLink").asText()) - } - "send-telemetry" -> { - val source = node.get("source") - val module = node.get("module") - val trigger = node.get("trigger") - - if (source != null) { - Telemetry.ui.click.use { - it.elementId(source.asText()) - } - } else if (module != null && trigger != null) { - Telemetry.toolkit.willOpenModule.use { - it.module(module.asText()) - it.source(trigger.asText()) - it.result(MetricResult.Succeeded) - } - } - } - } - - val tabType = node.tabType - if (tabType == null || tabType == "cwc") { - handleFlareChatMessages(browser, node) - } else { - connections.filter { connection -> connection.app.tabTypes.contains(tabType) }.forEach { connection -> - launch { - val message = serializer.deserialize(node, connection.messageTypeRegistry) - connection.messagesFromUiToApp.publish(message) - } - } - } - } - .launchIn(this) - - // Wait for UI ready before starting to send messages to the UI. - uiReady.await() - - // Chat options including history and quick actions need to be sent in once UI is ready - updateQuickActionsInBrowser(browser) - - // Send inbound messages to the browser - val inboundMessages = connections.map { it.messagesFromAppToUi.flow }.merge() - inboundMessages - .onEach { browser.postChat(serializer.serialize(it)) } - .launchIn(this) - } - - suspend fun connectTheme( - chatBrowser: CefBrowser, - themeSource: Flow, - ) = coroutineScope { - themeSource - .distinctUntilChanged() - .onEach { - uiReady.await() - themeBrowserAdapter.updateThemeInBrowser(chatBrowser, it) - } - .launchIn(this) - } - - private fun addMessageHook(browser: Browser) = callbackFlow { - val handler = Function { - trySend(it) - Response(null) - } - - browser.receiveMessageQuery.addHandler(handler) - - awaitClose { - browser.receiveMessageQuery.removeHandler(handler) - } - } - - private suspend fun handleFlareChatMessages(browser: Browser, node: JsonNode) { - when (node.command) { - SEND_CHAT_COMMAND_PROMPT -> { - val requestFromUi = serializer.deserializeChatMessages(node) - val editor = FileEditorManager.getInstance(project).selectedTextEditor - val textDocumentIdentifier = editor?.let { e -> - e.virtualFile?.let { - TextDocumentIdentifier(toUriString(it)) - } - } - val cursorState = editor?.let { LspEditorUtil.getCursorState(it) } - - val enrichmentParams = mapOf( - "textDocument" to textDocumentIdentifier, - "cursorState" to cursorState?.let { listOf(it) }, - ) - - val serializedEnrichmentParams = serializer.objectMapper.valueToTree(enrichmentParams) - val chatParams: ObjectNode = (node.params as ObjectNode) - .setAll(serializedEnrichmentParams) - - val tabId = requestFromUi.params.tabId - val partialResultToken = chatCommunicationManager.addPartialChatMessage(tabId) - - var encryptionManager: JwtEncryptionManager? = null - val result = AmazonQLspService.executeAsyncIfRunning(project) { server -> - encryptionManager = this.encryptionManager - - val encryptedParams = EncryptedChatParams(this.encryptionManager.encrypt(chatParams), partialResultToken) - rawEndpoint.request(SEND_CHAT_COMMAND_PROMPT, encryptedParams) as CompletableFuture - } ?: (CompletableFuture.failedFuture(IllegalStateException("LSP Server not running"))) - - // We assume there is only one outgoing request per tab because the input is - // blocked when there is an outgoing request - chatCommunicationManager.setInflightRequestForTab(tabId, result) - showResult(result, partialResultToken, tabId, encryptionManager, browser) - } - - CHAT_QUICK_ACTION -> { - val requestFromUi = serializer.deserializeChatMessages(node) - val tabId = requestFromUi.params.tabId - val quickActionParams = node.params ?: error("empty payload") - val partialResultToken = chatCommunicationManager.addPartialChatMessage(tabId) - var encryptionManager: JwtEncryptionManager? = null - val result = AmazonQLspService.executeAsyncIfRunning(project) { server -> - encryptionManager = this.encryptionManager - - val encryptedParams = EncryptedQuickActionChatParams(this.encryptionManager.encrypt(quickActionParams), partialResultToken) - rawEndpoint.request(CHAT_QUICK_ACTION, encryptedParams) as CompletableFuture - } ?: (CompletableFuture.failedFuture(IllegalStateException("LSP Server not running"))) - - // We assume there is only one outgoing request per tab because the input is - // blocked when there is an outgoing request - chatCommunicationManager.setInflightRequestForTab(tabId, result) - - showResult(result, partialResultToken, tabId, encryptionManager, browser) - } - - CHAT_LIST_CONVERSATIONS -> { - handleChat(AmazonQChatServer.listConversations, node) - .whenComplete { response, _ -> - browser.postChat( - FlareUiMessage( - command = CHAT_LIST_CONVERSATIONS, - params = response - ) - ) - } - } - - CHAT_CONVERSATION_CLICK -> { - handleChat(AmazonQChatServer.conversationClick, node) - .whenComplete { response, _ -> - browser.postChat( - FlareUiMessage( - command = CHAT_CONVERSATION_CLICK, - params = response - ) - ) - } - } - - CHAT_FEEDBACK -> { - handleChat(AmazonQChatServer.feedback, node) - } - - CHAT_READY -> { - LOG.info { "Amazon Q Chat UI loaded and ready for input" } - handleChat(AmazonQChatServer.chatReady, node) { params, invoke -> - uiReady.complete(true) - chatCommunicationManager.setUiReady() - RunOnceUtil.runOnceForApp("AmazonQ-UI-Ready") { - MeetQSettings.getInstance().reinvent2024OnboardingCount += 1 - } - - invoke() - } - } - - CHAT_TAB_ADD -> { - handleChat(AmazonQChatServer.tabAdd, node) { params, invoke -> - // Track the tab ID when a tab is added - chatCommunicationManager.addTabId(params.tabId) - invoke() - } - } - - CHAT_TAB_REMOVE -> { - handleChat(AmazonQChatServer.tabRemove, node) { params, invoke -> - chatCommunicationManager.removePartialChatMessage(params.tabId) - cancelInflightRequests(params.tabId) - // Remove the tab ID from tracking when a tab is removed - chatCommunicationManager.removeTabId(params.tabId) - invoke() - } - } - - CHAT_TAB_CHANGE -> { - handleChat(AmazonQChatServer.tabChange, node) - } - - CHAT_OPEN_TAB -> { - val response = serializer.deserializeChatMessages(node) - val future = chatCommunicationManager.removeTabOpenRequest(response.requestId) ?: return - try { - val id = serializer.deserializeChatMessages(node.params).result.tabId - future.complete(OpenTabResult(id)) - } catch (e: Exception) { - try { - val err = serializer.deserializeChatMessages(node.params) - future.complete(err.error) - } catch (_: Exception) { - future.completeExceptionally(e) - } - } - } - - CHAT_INSERT_TO_CURSOR -> { - val editor = FileEditorManager.getInstance(project).selectedTextEditor - val textDocumentIdentifier = editor?.let { e -> - e.virtualFile?.let { - TextDocumentIdentifier(toUriString(it)) - } - } - val cursorPosition = editor?.let { LspEditorUtil.getCursorPosition(it) } - - val enrichmentParams = mapOf( - "textDocument" to textDocumentIdentifier, - "cursorPosition" to cursorPosition, - ) - - val insertToCursorPositionParams: ObjectNode = (node.params as ObjectNode) - .setAll(serializer.objectMapper.valueToTree(enrichmentParams)) - val enrichedNode = (node as ObjectNode).apply { - set("params", insertToCursorPositionParams) - } - - handleChat(AmazonQChatServer.insertToCursorPosition, enrichedNode) - } - - CHAT_LINK_CLICK -> { - handleChat(AmazonQChatServer.linkClick, node) - } - - CHAT_INFO_LINK_CLICK -> { - handleChat(AmazonQChatServer.infoLinkClick, node) - } - - CHAT_SOURCE_LINK_CLICK -> { - handleChat(AmazonQChatServer.sourceLinkClick, node) - } - - CHAT_FILE_CLICK -> { - handleChat(AmazonQChatServer.fileClick, node) - } - - PROMPT_INPUT_OPTIONS_CHANGE -> { - handleChat(AmazonQChatServer.promptInputOptionsChange, node) - } - - CHAT_FOLLOW_UP_CLICK -> { - handleChat(AmazonQChatServer.followUpClick, node) - } - - CHAT_BUTTON_CLICK -> { - handleChat(AmazonQChatServer.buttonClick, node).thenApply { response -> - if (response is ButtonClickResult && !response.success) { - LOG.warn { "Failed to execute action associated with button with reason: ${response.failureReason}" } - } - } - } - - CHAT_COPY_CODE_TO_CLIPBOARD -> { - handleChat(AmazonQChatServer.copyCodeToClipboard, node) - } - - GET_SERIALIZED_CHAT_REQUEST_METHOD -> { - val response = serializer.deserializeChatMessages(node) - chatCommunicationManager.completeSerializedChatResponse( - response.requestId, - response.params.result.content - ) - } - - CHAT_TAB_BAR_ACTIONS -> { - val action = node.params.get("action") - if (action.textValue() == "show_logs") { - runInEdt { - GetAmazonQLogsAction.showLogCollectionWarningGetLogs(project) - } - } else { - handleChat(AmazonQChatServer.tabBarActions, node) { params, invoke -> - invoke() - .whenComplete { actions, error -> - try { - if (error != null) { - throw error - } - - browser.postChat( - FlareUiMessage( - command = CHAT_TAB_BAR_ACTIONS, - params = actions - ) - ) - } catch (e: Exception) { - val cause = if (e is CompletionException) e.cause else e - - // dont post error to UI if user cancels export - if (cause is ResponseErrorException && cause.responseError.code == ResponseErrorCode.RequestCancelled.getValue()) { - return@whenComplete - } - LOG.error { "Failed to perform chat tab bar action $e" } - params.tabId?.let { - browser.postChat(chatCommunicationManager.getErrorUiMessage(it, e, null)) - } - } - } - } - } - } - - CHAT_CREATE_PROMPT -> { - handleChat(AmazonQChatServer.createPrompt, node) - } - - STOP_CHAT_RESPONSE -> { - val stopResponseRequest = serializer.deserializeChatMessages(node) - if (!chatCommunicationManager.hasInflightRequest(stopResponseRequest.params.tabId)) { - return - } - cancelInflightRequests(stopResponseRequest.params.tabId) - chatCommunicationManager.removePartialChatMessage(stopResponseRequest.params.tabId) - } - - AUTH_FOLLOW_UP_CLICKED -> { - val message = serializer.deserializeChatMessages(node) - chatCommunicationManager.handleAuthFollowUpClicked( - project, - message.params - ) - } - - CHAT_PROMPT_OPTION_ACKNOWLEDGED -> { - val acknowledgedMessage = node.params?.get("messageId") - if (acknowledgedMessage?.asText() == "programmerModeCardId") { - MeetQSettings.getInstance().pairProgrammingAcknowledged = true - } - } - - OPEN_SETTINGS -> { - val openSettingsNotification = serializer.deserializeChatMessages(node) - if (openSettingsNotification.params.settingKey != OPEN_WORKSPACE_SETTINGS_KEY) return - runInEdt { - ShowSettingsUtil.getInstance().showSettingsDialog(browser.project, CodeWhispererConfigurable::class.java) - } - } - TELEMETRY_EVENT -> { - handleChat(AmazonQChatServer.telemetryEvent, node) - } - LIST_MCP_SERVERS_REQUEST_METHOD -> { - handleChat(AmazonQChatServer.listMcpServers, node) - .whenComplete { response, _ -> - browser.postChat( - FlareUiMessage( - command = LIST_MCP_SERVERS_REQUEST_METHOD, - params = response - ) - ) - } - } - MCP_SERVER_CLICK_REQUEST_METHOD -> { - handleChat(AmazonQChatServer.mcpServerClick, node) - .whenComplete { response, _ -> - browser.postChat( - FlareUiMessage( - command = MCP_SERVER_CLICK_REQUEST_METHOD, - params = response - ) - ) - } - } - - OPEN_FILE_DIALOG -> { - handleChat(AmazonQChatServer.showOpenFileDialog, node) - .whenComplete { response, _ -> - browser.postChat( - FlareUiMessage( - command = OPEN_FILE_DIALOG_REQUEST_METHOD, - params = response - ) - ) - } - } - - LIST_RULES_REQUEST_METHOD -> { - handleChat(AmazonQChatServer.listRules, node) - .whenComplete { response, _ -> - browser.postChat( - FlareUiMessage( - command = LIST_RULES_REQUEST_METHOD, - params = response - ) - ) - } - } - RULE_CLICK_REQUEST_METHOD -> { - handleChat(AmazonQChatServer.ruleClick, node) - .whenComplete { response, _ -> - browser.postChat( - FlareUiMessage( - command = RULE_CLICK_REQUEST_METHOD, - params = response - ) - ) - } - } - CHAT_PINNED_CONTEXT_ADD -> { - handleChat(AmazonQChatServer.pinnedContextAdd, node) - } - CHAT_PINNED_CONTEXT_REMOVE -> { - handleChat(AmazonQChatServer.pinnedContextRemove, node) - } - LIST_AVAILABLE_MODELS -> { - handleChat(AmazonQChatServer.listAvailableModels, node) - .whenComplete { response, _ -> - browser.postChat( - FlareUiMessage( - command = LIST_AVAILABLE_MODELS, - params = response - ) - ) - } - } - } - } - - private fun showResult( - result: CompletableFuture, - partialResultToken: String, - tabId: String, - encryptionManager: JwtEncryptionManager?, - browser: Browser, - ) { - result.whenComplete { value, error -> - try { - if (error != null) { - throw error - } - chatCommunicationManager.removePartialChatMessage(partialResultToken) - val decryptedMessage = value?.let { encryptionManager?.decrypt(it) }.orEmpty() - val filteredMessage = parseFindingsMessages(decryptedMessage) - - val messageToChat = ChatCommunicationManager.convertToJsonToSendToChat( - SEND_CHAT_COMMAND_PROMPT, - tabId, - filteredMessage, - isPartialResult = false - ) - browser.postChat(messageToChat) - chatCommunicationManager.removeInflightRequestForTab(tabId) - } catch (e: CancellationException) { - LOG.warn { "Cancelled chat generation" } - } catch (e: Exception) { - LOG.warn(e) { "Failed to send chat message" } - browser.postChat(chatCommunicationManager.getErrorUiMessage(tabId, e, partialResultToken)) - } - } - } - - fun deserializeFindings(@Language("JSON") responsePayload: String): List { - val additionalMessages = serializer.objectMapper.readValue(responsePayload).additionalMessages - ?: return emptyList() - - return additionalMessages - } - - fun parseFindingsMessages(@Language("JSON") responsePayload: String): String { - try { - val findings = deserializeFindings(responsePayload) - val scannedFiles = mutableListOf() - val mappedFindings = buildList { - for (finding in findings) { - for (aggregatedIssue in finding.body) { - val file = LocalFileSystem.getInstance().findFileByIoFile( - Path.of(aggregatedIssue.filePath).toFile() - ) - if (file?.isDirectory == false) { - scannedFiles.add(file) - runReadAction { - FileDocumentManager.getInstance().getDocument(file) - }?.let { document -> - for (issue in aggregatedIssue.issues) { - val endLineInDocument = minOf(maxOf(0, issue.endLine - 1), document.lineCount - 1) - val endCol = document.getLineEndOffset(endLineInDocument) - document.getLineStartOffset(endLineInDocument) + 1 - val isIssueIgnored = CodeWhispererCodeScanManager.getInstance(project) - .isIgnoredIssue(issue.title, document, file, issue.startLine - 1) - if (isIssueIgnored) { - continue - } - - add( - CodeWhispererCodeScanIssue( - startLine = issue.startLine, - startCol = 1, - endLine = issue.endLine, - endCol = endCol, - file = file, - project = project, - title = issue.title, - description = issue.description, - detectorId = issue.detectorId, - detectorName = issue.detectorName, - findingId = issue.findingId, - ruleId = issue.ruleId, - relatedVulnerabilities = issue.relatedVulnerabilities, - severity = issue.severity, - recommendation = issue.recommendation, - suggestedFixes = issue.suggestedFixes, - codeSnippet = emptyList(), - isVisible = true, - autoDetected = issue.autoDetected, - scanJobId = issue.scanJobId, - ), - ) - } - } - } - } - } - } - - if (mappedFindings.isNotEmpty()) { - CodeWhispererCodeScanManager.getInstance(project) - .addOnDemandIssues( - mappedFindings, - scannedFiles, - CodeWhispererConstants.CodeAnalysisScope.AGENTIC - ) - CodeWhispererCodeScanManager.getInstance(project).showCodeScanUI() - } - // Remove findings messages from response payload - val rootNode = serializer.objectMapper.readTree(responsePayload) as ObjectNode - rootNode.remove("additionalMessages") - return serializer.objectMapper.writeValueAsString(rootNode) - } catch (e: Exception) { - LOG.error(e) { "Failed to parse findings message" } - return responsePayload - } - } - - private suspend fun updateQuickActionsInBrowser(browser: Browser) { - val isCodeTransformAvailable = isCodeTransformAvailable(project) - val isCodeScanAvailable = isCodeScanAvailable(project) - - val serverCapabilities = AmazonQLspService.getInstance(project).instanceFlow.first().initializeResult.await().awsServerCapabilities - - // language=JavaScript - val script = """ - try { - // hack to create the list of actions across all tab types - const tempConnector = connectorAdapter.initiateAdapter( - false, - true, // the two values are not used here, needed for constructor - $isCodeTransformAvailable, - $isCodeScanAvailable, - { postMessage: () => {} }, - ); - - const commands = tempConnector.initialQuickActions?.slice(0, 2) || []; - const options = ${Gson().toJson(serverCapabilities.chatOptions)}; - options.quickActions.quickActionsCommandGroups = [ - ...commands, - ...options.quickActions.quickActionsCommandGroups - ]; - - window.postMessage({ - command: "chatOptions", - params: options - }); - } catch (e) { - console.error("Error updating quick actions:", e); - } - """.trimIndent() - - browser.jcefBrowser.cefBrowser.executeJavaScript(script, browser.jcefBrowser.cefBrowser.url, 0) - } - - private fun cancelInflightRequests(tabId: String) { - chatCommunicationManager.getInflightRequestForTab(tabId)?.let { request -> - request.cancel(true) - chatCommunicationManager.removeInflightRequestForTab(tabId) - } - } - - private suspend inline fun handleChat( - lspMethod: JsonRpcMethod, - node: JsonNode, - crossinline serverAction: (params: Request, invokeService: () -> CompletableFuture) -> CompletableFuture, - ): CompletableFuture { - val requestFromUi = if (node.params == null) { - Unit as Request - } else { - serializer.deserializeChatMessages(node.params, lspMethod.params) - } - - return AmazonQLspService.executeAsyncIfRunning(project) { _ -> - val invokeService = when (lspMethod) { - is JsonRpcNotification -> { - // notify is Unit - { CompletableFuture.completedFuture(rawEndpoint.notify(lspMethod.name, node.params?.let { serializer.objectMapper.treeToValue(it) })) } - } - - is JsonRpcRequest -> { - { - rawEndpoint.request(lspMethod.name, node.params?.let { serializer.objectMapper.treeToValue(it) }).thenApply { - serializer.objectMapper.readValue( - Gson().toJson(it), - lspMethod.response - ) - } - } - } - } as () -> CompletableFuture - serverAction(requestFromUi, invokeService) - } ?: CompletableFuture.failedFuture(IllegalStateException("LSP Server not running")) - } - - private suspend inline fun handleChat( - lspMethod: JsonRpcMethod, - node: JsonNode, - ): CompletableFuture = handleChat( - lspMethod, - node, - ) { _, invokeService -> invokeService() } - - private val JsonNode.params - get() = get("params") - - companion object { - private val LOG = getLogger() - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/FlareAdditionalFindings.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/FlareAdditionalFindings.kt deleted file mode 100644 index 6485c0e285d..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/FlareAdditionalFindings.kt +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -// TODO: move to software.aws.toolkits.jetbrains.services.amazonq.lsp.model -package software.aws.toolkits.jetbrains.services.amazonq.webview - -import com.fasterxml.jackson.annotation.JsonSetter -import com.fasterxml.jackson.annotation.Nulls -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.Description -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.Recommendation -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.SuggestedFix - -/** - * Solely used to extract the aggregated findings from the response - */ -data class FlareAdditionalMessages( - @get:JsonDeserialize(contentUsing = FlareAggregatedFindingsDeserializer::class) - @get:JsonSetter(contentNulls = Nulls.SKIP) - val additionalMessages: List?, -) - -data class FlareAggregatedFindings( - val messageId: String, - val body: List, -) - -data class FlareCodeScanIssue( - val startLine: Int, - val endLine: Int, - val comment: String?, - val title: String, - val description: Description, - val detectorId: String, - val detectorName: String, - val findingId: String, - val ruleId: String?, - val relatedVulnerabilities: List, - val severity: String, - val recommendation: Recommendation, - val suggestedFixes: List, - val scanJobId: String, - val language: String, - val autoDetected: Boolean, - val filePath: String, - val findingContext: String?, -) - -data class AggregatedCodeScanIssue( - val filePath: String, - val issues: List, -) - -class FlareAggregatedFindingsDeserializer : JsonDeserializer() { - private val objectMapper = jacksonObjectMapper() - - override fun deserialize(p: JsonParser, ctxt: DeserializationContext): FlareAggregatedFindings? = - try { - val node = p.readValueAsTree() - val messageId = node.get("messageId")?.asText() ?: return null - val bodyNode = node.get("body") ?: return null - - val body = objectMapper.readValue>(bodyNode.asText()) - - FlareAggregatedFindings(messageId, body) - } catch (_: Exception) { - null - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/FqnWebviewAdapter.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/FqnWebviewAdapter.kt deleted file mode 100644 index beb808b9eff..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/FqnWebviewAdapter.kt +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.webview - -import com.intellij.ui.jcef.JBCefBrowserBase -import com.intellij.ui.jcef.JBCefJSQuery -import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.time.withTimeout -import software.amazon.q.core.utils.getLogger -import software.amazon.q.core.utils.warn -import java.time.Duration - -/** - * Exposes the FQN (Fully Qualified Name) and import reading capabilities that are implemented in TypeScript via a Kotlin API. - */ -class FqnWebviewAdapter( - private val jcefBrowser: JBCefBrowserBase, - private val browserConnector: BrowserConnector, -) { - - private val namesExtractionResponses: Channel = Channel() - private val receiveNames: String - - init { - val receiveNamesFromBrowser = JBCefJSQuery.create(jcefBrowser) - receiveNamesFromBrowser.addHandler { names: String -> - namesExtractionResponses.trySend(names) - null - } - receiveNames = receiveNamesFromBrowser.inject("names") - } - - @Throws(TimeoutCancellationException::class) - suspend fun readImports(args: String): String { - browserConnector.uiReady.await() - - return try { - withTimeout(Duration.ofMillis(1250)) { - jcefBrowser.cefBrowser.executeJavaScript( - """ - window.fqnExtractor.readImports($args.fileContent, $args.language).then(result => { - const names = JSON.stringify(Array.from(result)); - $receiveNames - }); - """.trimIndent(), - jcefBrowser.cefBrowser.url, - 0, - ) - namesExtractionResponses.receive() - } - } catch (e: TimeoutCancellationException) { - logger.warn(e) { "Failed to read imports" } - "[]" - } - } - - suspend fun extractNames(args: String): String { - browserConnector.uiReady.await() - - return try { - withTimeout(Duration.ofMillis(1250)) { - jcefBrowser.cefBrowser.executeJavaScript( - """ - window.fqnExtractor.extractCodeQuery($args.fileContent, $args.language, $args.codeSelection).then(({codeQuery, namesWereTruncated}) => { - const names = codeQuery === undefined ? `{"simpleNames": [], "fullyQualifiedNames": {"used": []}}` : JSON.stringify(codeQuery); - $receiveNames - }); - """.trimIndent(), - jcefBrowser.cefBrowser.url, - 0, - ) - namesExtractionResponses.receive() - } - } catch (e: TimeoutCancellationException) { - logger.warn(e) { "Failed to extract fully qualified names" } - "{\"simpleNames\": [], \"fullyQualifiedNames\": {\"used\": []}}" - } - } - - companion object { - private val logger = getLogger() - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/AmazonQTheme.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/AmazonQTheme.kt deleted file mode 100644 index bbf449eac59..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/AmazonQTheme.kt +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.webview.theme - -import java.awt.Color -import java.awt.Font - -/** - * Data class that encapsulates the theme values we extract from the IDE. - */ -data class AmazonQTheme( - val darkMode: Boolean, - val font: Font, - - val defaultText: Color, - val inactiveText: Color, - val linkText: Color, - val lightText: Color, - val emptyText: Color, - - val background: Color, - val border: Color, - val activeTab: Color, - - val checkboxBackground: Color, - val checkboxForeground: Color, - - val textFieldBackground: Color, - val textFieldForeground: Color, - - val buttonForeground: Color, - val buttonBackground: Color, - val secondaryButtonForeground: Color, - val secondaryButtonBackground: Color, - val inputBorderFocused: Color, - val inputBorderUnfocused: Color, - - val info: Color, - val success: Color, - val warning: Color, - val error: Color, - - val editorFont: Font, - val editorBackground: Color, - val editorForeground: Color, - val editorVariable: Color, - val editorOperator: Color, - val editorFunction: Color, - val editorComment: Color, - val editorKeyword: Color, - val editorString: Color, - val editorProperty: Color, - val editorClassName: Color, -) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/CssVariable.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/CssVariable.kt deleted file mode 100644 index 793a75052fb..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/CssVariable.kt +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.webview.theme - -/** - * Enumeration of CSS variables that used by MynahUi to theme the chat experience. - */ -enum class CssVariable( - val varName: String, -) { - FontSize("--vscode-font-size"), - FontFamily("--mynah-font-family"), - - TextColorDefault("--mynah-color-text-default"), - TextColorAlt("--mynah-color-text-alternate"), - TextColorStrong("--mynah-color-text-strong"), - TextColorWeak("--mynah-color-text-weak"), - TextColorLight("--mynah-color-light"), - TextColorLink("--mynah-color-text-link"), - TextColorInput("--mynah-color-text-input"), - TextColorDisabled("--mynah-color-text-disabled"), - - Background("--mynah-color-bg"), - BackgroundAlt("--mynah-color-bg-alt"), - TabActive("--mynah-color-tab-active"), - - ColorDeep("--mynah-color-deep"), - ColorDeepReverse("--mynah-color-deep-reverse"), - BorderDefault("--mynah-color-border-default"), - BorderFocused("--mynah-color-text-input-border-focused"), - BorderUnfocused("--mynah-color-text-input-border"), - InputBackground("--mynah-input-bg"), - - SyntaxBackground("--mynah-color-syntax-bg"), - SyntaxVariable("--mynah-color-syntax-variable"), - SyntaxFunction("--mynah-color-syntax-function"), - SyntaxOperator("--mynah-color-syntax-operator"), - SyntaxAttributeValue("--mynah-color-syntax-attr-value"), - SyntaxAttribute("--mynah-color-syntax-attr"), - SyntaxProperty("--mynah-color-syntax-property"), - SyntaxComment("--mynah-color-syntax-comment"), - SyntaxCode("--mynah-color-syntax-code"), - SyntaxKeyword("--mynah-color-syntax-keyword"), - SyntaxString("--mynah-color-syntax-string"), - SyntaxClassName("--mynah-color-syntax-class-name"), - SyntaxCodeFontFamily("--mynah-syntax-code-font-family"), - SyntaxCodeFontSize("--mynah-syntax-code-font-size"), - - StatusInfo("--mynah-color-status-info"), - StatusSuccess("--mynah-color-status-success"), - StatusWarning("--mynah-color-status-warning"), - StatusError("--mynah-color-status-error"), - - ButtonBackground("--mynah-color-button"), - ButtonForeground("--mynah-color-button-reverse"), - - SecondaryButtonBackground("--mynah-color-alternate"), - SecondaryButtonForeground("--mynah-color-alternate-reverse"), - - MainBackground("--mynah-color-main"), - MainForeground("--mynah-color-main-reverse"), - - CardBackground("--mynah-card-bg"), - CardBackgroundAlt("--mynah-card-bg-alternate"), -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/EditorThemeAdapter.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/EditorThemeAdapter.kt deleted file mode 100644 index 6aa7ad9729a..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/EditorThemeAdapter.kt +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.webview.theme - -import com.intellij.ide.ui.LafManagerListener -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.editor.DefaultLanguageHighlighterColors -import com.intellij.openapi.editor.colors.ColorKey -import com.intellij.openapi.editor.colors.EditorColorsListener -import com.intellij.openapi.editor.colors.EditorColorsManager -import com.intellij.openapi.editor.colors.EditorColorsScheme -import com.intellij.openapi.editor.colors.EditorFontType -import com.intellij.openapi.editor.colors.TextAttributesKey -import com.intellij.ui.JBColor -import com.intellij.util.ui.UIUtil -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.callbackFlow -import software.amazon.q.core.utils.error -import software.amazon.q.core.utils.getLogger -import java.awt.Color - -/** - * Helper class that returns a Flow of [AmazonQTheme] instances based on the current IDE theme. - */ -class EditorThemeAdapter { - private val logger = getLogger() - - /** - * Returns a flow of [AmazonQTheme] instances. The current theme is emitted immediately, - * and a new theme is emitted whenever the look-and-feel of the IDE changes. - */ - fun onThemeChange() = callbackFlow { - // Register a listener for changes to the IDE LaF - val messageBus = ApplicationManager.getApplication().messageBus - val connection = messageBus.connect() - - // Listen to LaF changes (the overall IDE theme changes) - connection.subscribe( - LafManagerListener.TOPIC, - LafManagerListener { - // It's important to not throw exceptions from this listener. Throwing here will prevent the user's theme from being properly applied in the IDE - try { - trySend(getThemeFromIde()) - } catch (e: Exception) { - logger.error(e) { "Cannot construct Amazon Q theme from IDE colors" } - } - }, - ) - - // Also listen to EditorColors changes. This will be triggered if the editor's colors or fonts change. - connection.subscribe( - EditorColorsManager.TOPIC, - EditorColorsListener { - // It's important to not throw exceptions from this listener. Throwing here will prevent the user's theme from being properly applied in the IDE - try { - trySend(getThemeFromIde()) - } catch (e: Exception) { - logger.error(e) { "Cannot construct Amazon Q theme from IDE colors" } - } - }, - ) - - // Send an initial value for the current theme - send(getThemeFromIde()) - // Disconnect from the message bus when the flow collection is cancelled - awaitClose { connection.disconnect() } - } - - companion object { - // Returns a theme constructed from the current look-and-feel of the IDE - fun getThemeFromIde(): AmazonQTheme { - val currentScheme = EditorColorsManager.getInstance().schemeForCurrentUITheme - - val cardBackground = currentScheme.defaultBackground - val text = currentScheme.defaultForeground - val chatBackground = tryFindDifferentColor( - cardBackground, - "Panel.background", - "EditorPane.background", - "EditorPane.inactiveBackground", - "Editor.background", - "Content.background", - default = 0xF2F2F2, - darkDefault = 0x3C3F41, - ) - - return AmazonQTheme( - darkMode = !JBColor.isBright(), - font = UIUtil.getFont(UIUtil.FontSize.NORMAL, null), - - defaultText = text, - inactiveText = themeColor("TextField.inactiveForeground", default = 0x8C8C8C, darkDefault = 0x808080), - linkText = themeColor("link.foreground", "link", "Link.activeForeground", default = 0x589DF6), - - background = chatBackground, - border = getBorderColor(currentScheme), - activeTab = themeColor("EditorTabs.underlinedTabBackground", default = 0xFFFFFF, darkDefault = 0x4E5254), - - checkboxBackground = themeColor("CheckBox.background", default = 0xF2F2F2, darkDefault = 0x3C3F41), - checkboxForeground = themeColor("CheckBox.foreground", default = 0x000000, darkDefault = 0xBBBBBB), - - textFieldBackground = themeColor("TextField.background", default = 0xFFFFFF, darkDefault = 0x45494A), - textFieldForeground = themeColor("TextField.foreground", default = 0x000000, darkDefault = 0xBBBBBB), - - buttonBackground = themeColor("Button.default.startBackground", default = 0x528CC7, darkDefault = 0x365880), - buttonForeground = themeColor("Button.default.foreground", default = 0xFFFFFF, darkDefault = 0xBBBBBB), - secondaryButtonBackground = themeColor("Button.startBackground", default = 0xFFFFFF, darkDefault = 0x4C5052), - secondaryButtonForeground = themeColor("Button.foreground", default = 0x000000, darkDefault = 0xBBBBBB), - - info = themeColor("ProgressBar.progressColor", default = 0x1E82E6, darkDefault = 0x1E82E6), - success = themeColor("ProgressBar.passedColor", default = 0x34B171, darkDefault = 0x008F50), - warning = themeColor("Component.warningFocusColor", default = 0xE2A53A), - error = themeColor("ProgressBar.failedColor", default = 0xD64F4F, darkDefault = 0xE74848), - - editorFont = currentScheme.getFont(EditorFontType.PLAIN), - editorBackground = currentScheme.defaultBackground, - editorForeground = currentScheme.defaultForeground, - editorVariable = currentScheme.foregroundColor(DefaultLanguageHighlighterColors.LOCAL_VARIABLE), - editorOperator = currentScheme.foregroundColor(DefaultLanguageHighlighterColors.OPERATION_SIGN), - editorFunction = currentScheme.foregroundColor(DefaultLanguageHighlighterColors.FUNCTION_DECLARATION), - editorComment = currentScheme.foregroundColor(DefaultLanguageHighlighterColors.LINE_COMMENT), - editorKeyword = currentScheme.foregroundColor(DefaultLanguageHighlighterColors.KEYWORD), - editorString = currentScheme.foregroundColor(DefaultLanguageHighlighterColors.STRING), - editorProperty = currentScheme.foregroundColor(DefaultLanguageHighlighterColors.INSTANCE_FIELD), - editorClassName = currentScheme.foregroundColor(DefaultLanguageHighlighterColors.CLASS_NAME), - lightText = themeColor("TextField.inactiveForeground", default = 0xA8ADBD, darkDefault = 0x5A5D63), - emptyText = themeColor("TextField.inactiveForeground", default = 0xA8ADBD, darkDefault = 0x5A5D63), - inputBorderFocused = themeColor("ActionButton.focusedBorderColor", default = 0x4682FA, darkDefault = 0x3574f0), - inputBorderUnfocused = themeColor("TextField.borderColor", default = 0xEBECF0, darkDefault = 0x4E5157), - ) - } - - private fun themeColor(name: String, default: Int, darkDefault: Int = default) = JBColor.namedColor(name, JBColor(default, darkDefault)) - - private fun themeColor(name: String, vararg backups: String, default: Int, darkDefault: Int = default): Color { - var defaultColor = JBColor(default, darkDefault) - for (i in backups.indices.reversed()) { - defaultColor = JBColor.namedColor(backups[i], defaultColor) - } - return JBColor.namedColor(name, defaultColor) - } - - private fun getBorderColor(currentScheme: EditorColorsScheme) = currentScheme.getColor(ColorKey.find("INDENT_GUIDE")) ?: themeColor( - "Borders.color", - "Component.borderColor", - "EditorTabs.borderColor", - default = 0xC4C4C4, - darkDefault = 0x646464, - ) - - private fun tryFindDifferentColor(color: Color, vararg choices: String, default: Int, darkDefault: Int): Color { - for (choice in choices) { - val themeColor = JBColor.namedColor(choice) - if (themeColor != color) { - return themeColor - } - } - // None of them are different so just take the first defined value - return themeColor(choices.first(), *choices, default = default, darkDefault = darkDefault) - } - - // Not all values may be set in the current scheme. Use the default foreground color if not specified. - private fun EditorColorsScheme.foregroundColor(key: TextAttributesKey) = getAttributes(key).foregroundColor ?: defaultForeground - } -} 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 deleted file mode 100644 index b42c96a01e8..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/theme/ThemeBrowserAdapter.kt +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.webview.theme - -import org.cef.browser.CefBrowser -import java.awt.Color -import java.awt.Font - -// The className must match what's in the mynah-ui package. -const val DARK_MODE_CLASS = "vscode-dark" - -/** - * Takes a [AmazonQTheme] instance and uses it to update CSS variables in the Webview UI. - */ -class ThemeBrowserAdapter { - fun updateLoginThemeInBrowser(browser: CefBrowser, theme: AmazonQTheme) { - browser.executeJavaScript("window.changeTheme(${theme.darkMode})", browser.url, 0) - } - - fun updateThemeInBrowser(browser: CefBrowser, theme: AmazonQTheme) { - val codeToUpdateTheme = buildJsCodeToUpdateTheme(theme) - browser.executeJavaScript(codeToUpdateTheme, browser.url, 0) - } - - fun buildJsCodeToUpdateTheme(theme: AmazonQTheme) = buildString { - val (bg, altBg, inputBg) = determineInputAndBgColor(theme) - appendDarkMode(theme.darkMode) - - append("{\n") - append("const rootElement = document.querySelector(':root');\n") - - append(CssVariable.FontSize, theme.font.toCssSize()) - append(CssVariable.FontFamily, theme.font.toCssFontFamily()) - - append(CssVariable.TextColorDefault, theme.defaultText) - append(CssVariable.TextColorAlt, theme.defaultText) - append(CssVariable.TextColorStrong, theme.textFieldForeground) - append(CssVariable.TextColorInput, theme.textFieldForeground) - append(CssVariable.TextColorLink, theme.linkText) - append(CssVariable.TextColorWeak, theme.emptyText) - append(CssVariable.TextColorLight, theme.emptyText) - append(CssVariable.TextColorDisabled, theme.inactiveText) - - append(CssVariable.Background, theme.background) - append(CssVariable.BackgroundAlt, altBg) - append(CssVariable.CardBackground, bg) - append(CssVariable.CardBackgroundAlt, theme.editorBackground) - append(CssVariable.BorderDefault, theme.border) - append(CssVariable.BorderFocused, theme.inputBorderFocused) - append(CssVariable.BorderUnfocused, theme.inputBorderUnfocused) - append(CssVariable.TabActive, theme.activeTab) - - append(CssVariable.InputBackground, inputBg) - - append(CssVariable.ButtonBackground, theme.buttonBackground) - append(CssVariable.ButtonForeground, theme.buttonForeground) - append(CssVariable.SecondaryButtonBackground, theme.secondaryButtonBackground) - append(CssVariable.SecondaryButtonForeground, theme.secondaryButtonForeground) - - append(CssVariable.StatusInfo, theme.info) - append(CssVariable.StatusSuccess, theme.success) - append(CssVariable.StatusWarning, theme.warning) - append(CssVariable.StatusError, theme.error) - - append(CssVariable.ColorDeep, theme.checkboxBackground) - append(CssVariable.ColorDeepReverse, theme.checkboxForeground) - - append(CssVariable.SyntaxCodeFontFamily, theme.editorFont.toCssFontFamily("monospace")) - append(CssVariable.SyntaxCodeFontSize, theme.editorFont.toCssSize()) - append(CssVariable.SyntaxCode, theme.editorForeground) - append(CssVariable.SyntaxBackground, theme.editorBackground) - append(CssVariable.SyntaxVariable, theme.editorVariable) - append(CssVariable.SyntaxOperator, theme.editorOperator) - append(CssVariable.SyntaxFunction, theme.editorFunction) - append(CssVariable.SyntaxComment, theme.editorComment) - append(CssVariable.SyntaxAttributeValue, theme.editorKeyword) - append(CssVariable.SyntaxAttribute, theme.editorString) - append(CssVariable.SyntaxProperty, theme.editorProperty) - append(CssVariable.SyntaxKeyword, theme.editorKeyword) - append(CssVariable.SyntaxString, theme.editorString) - append(CssVariable.SyntaxClassName, theme.editorClassName) - - append(CssVariable.MainBackground, theme.buttonBackground) - append(CssVariable.MainForeground, theme.buttonForeground) - - append("}") - } - - private fun StringBuilder.append(variable: CssVariable, value: Color) = append(variable, value.toCss()) - - private fun StringBuilder.append(variable: CssVariable, value: String) { - append("rootElement.style.setProperty('") - append(variable.varName) - append("', '") - append(value) - append("');\n") - } - - private fun StringBuilder.appendDarkMode(isDarkMode: Boolean) { - if (isDarkMode) { - // classList acts as a set, so we don't need to worry about calling add multiple times - append("document.body.classList.add('$DARK_MODE_CLASS');\n") - } else { - append("document.body.classList.remove('$DARK_MODE_CLASS');\n") - } - } - - private fun Color.toCss() = "rgba($red,$green,$blue,$alpha)" - - private fun Font.toCssSize() = "${size}px" - - // Some font names have characters that require them to be wrapped in quotes in the CSS variable, for example if they have spaces or a period. - private fun Font.toCssFontFamily(fallback: String = "system-ui") = "\"$family\", $fallback" - - // darkest = bg, second darkest is alt bg, lightest is input bg - private fun determineInputAndBgColor(theme: AmazonQTheme): Triple { - val colors = arrayOf(theme.editorBackground, theme.background, theme.textFieldBackground).sortedWith( - Comparator.comparing { - // luma calculation for brightness - (0.2126 * it.red) + (0.7152 * it.green) + (0.0722 * it.blue) - } - ) - return Triple(colors[0], colors[1], colors[2]) - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatApp.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatApp.kt deleted file mode 100644 index 3b5b9c0be7c..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatApp.kt +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan - -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.components.service -import com.intellij.openapi.project.Project -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.launch -import software.amazon.q.jetbrains.core.credentials.AwsBearerTokenConnection -import software.amazon.q.jetbrains.core.credentials.ToolkitConnection -import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManager -import software.amazon.q.jetbrains.core.credentials.ToolkitConnectionManagerListener -import software.amazon.q.jetbrains.core.credentials.pinning.QConnection -import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState -import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProvider -import software.amazon.q.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener -import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQApp -import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext -import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthController -import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile -import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileSelectedListener -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.auth.isCodeScanAvailable -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.commands.CodeScanActionMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.commands.CodeScanMessageListener -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.controller.CodeScanChatController -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.AuthenticationNeededExceptionMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.AuthenticationUpdateMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.CODE_SCAN_TAB_NAME -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.IncomingCodeScanMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.storage.ChatSessionStorage -import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable -import java.util.concurrent.atomic.AtomicBoolean - -private enum class CodeScanMessageTypes(val type: String) { - ClearChat("clear"), - Help("help"), - TabCreated("new-tab-was-created"), - TabRemoved("tab-was-removed"), - Scan("scan"), - StartProjectScan("codescan_start_project_scan"), - StartFileScan("codescan_start_file_scan"), - StopFileScan("codescan_stop_file_scan"), - StopProjectScan("codescan_stop_project_scan"), - OpenIssuesPanel("codescan_open_issues"), - ResponseBodyLinkClicked("response-body-link-click"), -} - -class CodeScanChatApp(private val scope: CoroutineScope) : AmazonQApp { - private val isProcessingAuthChanged = AtomicBoolean(false) - override val tabTypes = listOf(CODE_SCAN_TAB_NAME) - override fun init(context: AmazonQAppInitContext) { - val chatSessionStorage = ChatSessionStorage() - val inboundAppMessagesHandler: InboundAppMessagesHandler = CodeScanChatController(context, chatSessionStorage) - - context.messageTypeRegistry.register( - CodeScanMessageTypes.ClearChat.type to IncomingCodeScanMessage.ClearChat::class, - CodeScanMessageTypes.Help.type to IncomingCodeScanMessage.Help::class, - CodeScanMessageTypes.TabCreated.type to IncomingCodeScanMessage.TabCreated::class, - CodeScanMessageTypes.TabRemoved.type to IncomingCodeScanMessage.TabRemoved::class, - CodeScanMessageTypes.Scan.type to IncomingCodeScanMessage.Scan::class, - CodeScanMessageTypes.StartProjectScan.type to IncomingCodeScanMessage.StartProjectScan::class, - CodeScanMessageTypes.StartFileScan.type to IncomingCodeScanMessage.StartFileScan::class, - CodeScanMessageTypes.StopProjectScan.type to IncomingCodeScanMessage.StopProjectScan::class, - CodeScanMessageTypes.StopFileScan.type to IncomingCodeScanMessage.StopFileScan::class, - CodeScanMessageTypes.ResponseBodyLinkClicked.type to IncomingCodeScanMessage.ResponseBodyLinkClicked::class, - CodeScanMessageTypes.OpenIssuesPanel.type to IncomingCodeScanMessage.OpenIssuesPanel::class - ) - - scope.launch { - merge(service().flow, context.messagesFromUiToApp.flow).collect { message -> - // Launch a new coroutine to handle each message - scope.launch { handleMessage(message, inboundAppMessagesHandler) } - } - } - - fun authChanged() { - val isAnotherThreadProcessing = !isProcessingAuthChanged.compareAndSet(false, true) - if (isAnotherThreadProcessing) return - scope.launch { - val authController = AuthController() - val credentialState = authController.getAuthNeededStates(context.project).amazonQ - if (credentialState == null) { - // Notify tabs about restoring authentication - context.messagesFromAppToUi.publish( - AuthenticationUpdateMessage( - codeTransformEnabled = isCodeTransformAvailable(context.project), - codeScanEnabled = isCodeScanAvailable(context.project), - authenticatingTabIDs = chatSessionStorage.getAuthenticatingSessions().map { it.tabId } - ) - ) - - chatSessionStorage.changeAuthenticationNeeded(false) - chatSessionStorage.changeAuthenticationNeededNotified(false) - } else { - chatSessionStorage.changeAuthenticationNeeded(true) - - // Ask for reauth - chatSessionStorage.getAuthenticatingSessions().filter { !it.authNeededNotified }.forEach { - context.messagesFromAppToUi.publish( - AuthenticationNeededExceptionMessage( - tabId = it.tabId, - authType = credentialState.authType, - message = credentialState.message - ) - ) - } - - // Prevent multiple calls to activeConnectionChanged - chatSessionStorage.changeAuthenticationNeededNotified(true) - } - isProcessingAuthChanged.set(false) - } - } - - ApplicationManager.getApplication().messageBus.connect(this).subscribe( - BearerTokenProviderListener.TOPIC, - object : BearerTokenProviderListener { - override fun onProviderChange(providerId: String, newScopes: List?) { - val qProvider = getQTokenProvider(context.project) - val isQ = qProvider?.id == providerId - val isAuthorized = qProvider?.state() == BearerTokenAuthState.AUTHORIZED - if (!isQ || !isAuthorized) return - authChanged() - } - } - ) - - ApplicationManager.getApplication().messageBus.connect(this).subscribe( - ToolkitConnectionManagerListener.TOPIC, - object : ToolkitConnectionManagerListener { - override fun activeConnectionChanged(newConnection: ToolkitConnection?) { - authChanged() - } - } - ) - - context.project.messageBus.connect(this).subscribe( - QRegionProfileSelectedListener.TOPIC, - object : QRegionProfileSelectedListener { - override fun onProfileSelected(project: Project, profile: QRegionProfile?) { - chatSessionStorage.deleteAllSessions() - } - } - ) - } - - private fun getQTokenProvider(project: Project) = ( - ToolkitConnectionManager - .getInstance(project) - .activeConnectionForFeature(QConnection.getInstance()) as? AwsBearerTokenConnection - ) - ?.getConnectionSettings() - ?.tokenProvider - ?.delegate as? BearerTokenProvider - - private suspend fun handleMessage(message: AmazonQMessage, inboundAppMessagesHandler: InboundAppMessagesHandler) { - when (message) { - is IncomingCodeScanMessage.ClearChat -> inboundAppMessagesHandler.processClearQuickAction(message) - is IncomingCodeScanMessage.Help -> inboundAppMessagesHandler.processHelpQuickAction(message) - is IncomingCodeScanMessage.TabCreated -> inboundAppMessagesHandler.processTabCreated(message) - is IncomingCodeScanMessage.TabRemoved -> inboundAppMessagesHandler.processTabRemoved(message) - is IncomingCodeScanMessage.Scan -> inboundAppMessagesHandler.processScanQuickAction(message) - is IncomingCodeScanMessage.StartProjectScan -> inboundAppMessagesHandler.processStartProjectScan(message) - is IncomingCodeScanMessage.StartFileScan -> inboundAppMessagesHandler.processStartFileScan(message) - is CodeScanActionMessage -> inboundAppMessagesHandler.processCodeScanCommand(message) - is IncomingCodeScanMessage.StopProjectScan -> inboundAppMessagesHandler.processStopProjectScan(message) - is IncomingCodeScanMessage.StopFileScan -> inboundAppMessagesHandler.processStopFileScan(message) - is IncomingCodeScanMessage.ResponseBodyLinkClicked -> inboundAppMessagesHandler.processResponseBodyLinkClicked(message) - is IncomingCodeScanMessage.OpenIssuesPanel -> inboundAppMessagesHandler.processOpenIssuesPanel(message) - } - } - - override fun dispose() { - // nothing to do - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatAppFactory.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatAppFactory.kt deleted file mode 100644 index 2ac70b38b0d..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatAppFactory.kt +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan - -import com.intellij.openapi.project.Project -import kotlinx.coroutines.CoroutineScope -import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppFactory - -class CodeScanChatAppFactory(private val cs: CoroutineScope) : AmazonQAppFactory { - override fun createApp(project: Project) = CodeScanChatApp(cs) -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatItems.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatItems.kt deleted file mode 100644 index ee2dc09e3c5..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanChatItems.kt +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan - -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.Button -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.CodeScanButtonId -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.CodeScanChatMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.CodeScanChatMessageContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.ProgressField -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.PromptProgressMessage -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanIssue -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.utils.IssueSeverity -import software.aws.toolkits.jetbrains.services.cwc.controller.chat.StaticPrompt -import software.aws.toolkits.jetbrains.services.cwc.controller.chat.StaticTextResponse -import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessageType -import software.aws.toolkits.resources.message -import java.util.UUID - -private val projectScanButton = Button( - id = CodeScanButtonId.StartProjectScan.id, - text = message("codescan.chat.message.button.projectScan") -) - -private val fileScanButton = Button( - id = CodeScanButtonId.StartFileScan.id, - text = message("codescan.chat.message.button.fileScan") -) - -private val openIssuesPanelButton = Button( - id = CodeScanButtonId.OpenIssuesPanel.id, - text = message("codescan.chat.message.button.openIssues"), - keepCardAfterClick = true -) - -fun buildStartNewScanChatContent() = CodeScanChatMessageContent( - type = ChatMessageType.Answer, - message = message("codescan.chat.new_scan.input.message"), - buttons = listOf( - fileScanButton, - projectScanButton - ), - canBeVoted = false -) - -// TODO: Replace StaticPrompt and StaticTextResponse message according to Fnf -fun buildHelpChatPromptContent() = CodeScanChatMessageContent( - type = ChatMessageType.Prompt, - message = StaticPrompt.Help.message, - canBeVoted = false -) - -fun buildHelpChatAnswerContent() = CodeScanChatMessageContent( - type = ChatMessageType.Answer, - message = StaticTextResponse.Help.message, - canBeVoted = false -) - -fun buildUserSelectionProjectScanChatContent() = CodeScanChatMessageContent( - type = ChatMessageType.Prompt, - message = message("codescan.chat.message.button.projectScan"), - canBeVoted = false -) - -fun buildUserSelectionFileScanChatContent() = CodeScanChatMessageContent( - type = ChatMessageType.Prompt, - message = message("codescan.chat.message.button.fileScan"), - canBeVoted = false -) - -fun buildNotInGitRepoChatContent() = CodeScanChatMessageContent( - type = ChatMessageType.Answer, - message = message("codescan.chat.message.not_git_repo"), - canBeVoted = false -) - -fun buildScanInProgressChatContent(currentStep: Int, isProject: Boolean) = CodeScanChatMessageContent( - type = ChatMessageType.AnswerPart, - message = buildString { - appendLine(if (isProject) message("codescan.chat.message.scan_begin_project") else message("codescan.chat.message.scan_begin_file")) - appendLine("") - appendLine(message("codescan.chat.message.scan_begin_wait_time")) - appendLine("") - appendLine("${getIconForStep(0, currentStep)} " + message("codescan.chat.message.scan_step_1")) - appendLine("${getIconForStep(1, currentStep)} " + message("codescan.chat.message.scan_step_2")) - appendLine("${getIconForStep(2, currentStep)} " + message("codescan.chat.message.scan_step_3")) - } -) - -val cancellingProgressField = ProgressField( - status = "warning", - text = message("general.canceling"), - value = -1, - actions = emptyList() -) - -fun buildScanCompleteChatContent(issues: List, isProject: Boolean = false): CodeScanChatMessageContent { - val issueCountMap = IssueSeverity.entries.associate { it.displayName to 0 }.toMutableMap() - val aggregatedIssues = issues.groupBy { it.severity } - aggregatedIssues.forEach { (key, list) -> if (list.isNotEmpty()) issueCountMap[key] = list.size } - - val message = buildString { - appendLine(if (isProject) message("codewhisperer.codescan.scan_complete_project") else message("codewhisperer.codescan.scan_complete_file")) - issueCountMap.entries.forEach { (severity, count) -> - appendLine(message("codewhisperer.codescan.scan_complete_count", severity, count)) - } - } - - return CodeScanChatMessageContent( - type = ChatMessageType.Answer, - message = message, - buttons = listOf( - openIssuesPanelButton - ), - ) -} - -fun buildPromptProgressMessage(tabId: String, isProject: Boolean = false, isCanceling: Boolean = false) = PromptProgressMessage( - progressField = when { - isCanceling -> cancellingProgressField - isProject -> projectScanProgressField - else -> fileScanProgressField - }, - tabId = tabId -) - -fun buildClearPromptProgressMessage(tabId: String) = PromptProgressMessage( - tabId = tabId -) - -val runCodeScanMessage - get() = CodeScanChatMessage(messageType = ChatMessageType.Prompt, command = "review", tabId = UUID.randomUUID().toString()) - -val cancelFileScanButton = Button( - id = CodeScanButtonId.StopFileScan.id, - text = message("general.cancel"), - icon = "cancel" -) - -val cancelProjectScanButton = cancelFileScanButton.copy( - id = CodeScanButtonId.StopProjectScan.id -) - -val fileScanProgressField = ProgressField( - status = "default", - text = message("codescan.chat.message.scan_file_in_progress"), - value = -1, - actions = listOf(cancelFileScanButton) -) - -val projectScanProgressField = fileScanProgressField.copy( - text = message("codescan.chat.message.scan_project_in_progress"), - actions = listOf(cancelProjectScanButton) -) - -fun buildProjectScanFailedChatContent(errorMessage: String?) = CodeScanChatMessageContent( - type = ChatMessageType.Answer, - message = errorMessage ?: message("codescan.chat.message.project_scan_failed") -) - -fun getIconForStep(targetStep: Int, currentStep: Int) = when { - currentStep == targetStep -> "☐" - currentStep > targetStep -> "☑" - else -> "☐" -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanConstants.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanConstants.kt deleted file mode 100644 index c27b7ef4169..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/CodeScanConstants.kt +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan - -const val FEATURE_NAME = "Amazon Q Code Scan" diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/InboundAppMessagesHandler.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/InboundAppMessagesHandler.kt deleted file mode 100644 index 31ca09503df..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/InboundAppMessagesHandler.kt +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan - -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.commands.CodeScanActionMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.IncomingCodeScanMessage - -interface InboundAppMessagesHandler { - suspend fun processScanQuickAction(message: IncomingCodeScanMessage.Scan) - - suspend fun processStartProjectScan(message: IncomingCodeScanMessage.StartProjectScan) - - suspend fun processStartFileScan(message: IncomingCodeScanMessage.StartFileScan) - - suspend fun processStopProjectScan(message: IncomingCodeScanMessage.StopProjectScan) - - suspend fun processStopFileScan(message: IncomingCodeScanMessage.StopFileScan) - - suspend fun processTabCreated(message: IncomingCodeScanMessage.TabCreated) - - suspend fun processClearQuickAction(message: IncomingCodeScanMessage.ClearChat) - - suspend fun processHelpQuickAction(message: IncomingCodeScanMessage.Help) - - suspend fun processTabRemoved(message: IncomingCodeScanMessage.TabRemoved) - - suspend fun processCodeScanCommand(message: CodeScanActionMessage) - - suspend fun processResponseBodyLinkClicked(message: IncomingCodeScanMessage.ResponseBodyLinkClicked) - - suspend fun processOpenIssuesPanel(message: IncomingCodeScanMessage.OpenIssuesPanel) -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/auth/CodeScanAuthUtils.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/auth/CodeScanAuthUtils.kt deleted file mode 100644 index 8720f036d45..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/auth/CodeScanAuthUtils.kt +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan.auth - -import com.intellij.openapi.project.Project -import software.amazon.q.jetbrains.core.gettingstarted.editor.ActiveConnection -import software.amazon.q.jetbrains.core.gettingstarted.editor.BearerTokenFeatureSet -import software.amazon.q.jetbrains.core.gettingstarted.editor.checkBearerConnectionValidity - -fun isCodeScanAvailable(project: Project): Boolean = checkBearerConnectionValidity(project, BearerTokenFeatureSet.Q) is ActiveConnection.ValidBearer diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanActionMessage.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanActionMessage.kt deleted file mode 100644 index 01532c154f3..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanActionMessage.kt +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan.commands - -import com.intellij.openapi.project.Project -import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeScanResponse -import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants - -data class CodeScanActionMessage( - val command: CodeScanCommand, - val project: Project, - val scanResult: CodeScanResponse? = null, - val scope: CodeWhispererConstants.CodeAnalysisScope, -) : AmazonQMessage diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanCommand.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanCommand.kt deleted file mode 100644 index f7b16705b14..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanCommand.kt +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan.commands - -enum class CodeScanCommand { - ScanComplete, -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanMessageListener.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanMessageListener.kt deleted file mode 100644 index 832741a1fc4..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/commands/CodeScanMessageListener.kt +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan.commands - -import com.intellij.openapi.components.Service -import com.intellij.openapi.project.Project -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.asSharedFlow -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeScanResponse -import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants - -@Service -class CodeScanMessageListener { - private val _messages by lazy { MutableSharedFlow(extraBufferCapacity = 10) } - val flow = _messages.asSharedFlow() - - fun onScanResult(result: CodeScanResponse?, scope: CodeWhispererConstants.CodeAnalysisScope, project: Project) { - _messages.tryEmit(CodeScanActionMessage(CodeScanCommand.ScanComplete, scanResult = result, scope = scope, project = project)) - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/controller/CodeScanChatController.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/controller/CodeScanChatController.kt deleted file mode 100644 index 8349a218979..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/controller/CodeScanChatController.kt +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan.controller - -import com.intellij.ide.BrowserUtil -import software.amazon.q.core.utils.debug -import software.amazon.q.core.utils.getLogger -import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext -import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthController -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.FEATURE_NAME -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.InboundAppMessagesHandler -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildHelpChatAnswerContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildHelpChatPromptContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildNotInGitRepoChatContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildProjectScanFailedChatContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildScanCompleteChatContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildScanInProgressChatContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildStartNewScanChatContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildUserSelectionFileScanChatContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildUserSelectionProjectScanChatContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.commands.CodeScanActionMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.commands.CodeScanCommand -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.AuthenticationNeededExceptionMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.CodeScanChatMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.IncomingCodeScanMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.storage.ChatSessionStorage -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeScanResponse -import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanManager -import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService -import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants -import software.aws.toolkits.jetbrains.services.codewhisperer.util.getAuthType -import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessageType -import software.aws.toolkits.resources.message - -class CodeScanChatController( - private val context: AmazonQAppInitContext, - private val chatSessionStorage: ChatSessionStorage, - private val authController: AuthController = AuthController(), -) : InboundAppMessagesHandler { - - private val messenger = context.messagesFromAppToUi - private val codeScanManager = CodeWhispererCodeScanManager.getInstance(context.project) - private val codeScanChatHelper = CodeScanChatHelper(context.messagesFromAppToUi, chatSessionStorage) - private val scanInProgressMessageId = "scanProgressMessage" - - override suspend fun processScanQuickAction(message: IncomingCodeScanMessage.Scan) { - // TODO: telemetry - - if (!checkForAuth(message.tabId)) { - return - } - if (message.tabId != codeScanChatHelper.getActiveCodeScanTabId()) return - - codeScanChatHelper.setActiveCodeScanTabId(message.tabId) - codeScanChatHelper.addNewMessage(buildStartNewScanChatContent()) - codeScanChatHelper.sendChatInputEnabledMessage(false) - } - - override suspend fun processStartProjectScan(message: IncomingCodeScanMessage.StartProjectScan) { - if (message.tabId != codeScanChatHelper.getActiveCodeScanTabId()) return - codeScanChatHelper.addNewMessage(buildUserSelectionProjectScanChatContent()) - if (!codeScanManager.isInsideWorkTree()) { - codeScanChatHelper.addNewMessage(buildNotInGitRepoChatContent()) - } - codeScanChatHelper.addNewMessage(buildScanInProgressChatContent(currentStep = 1, isProject = true), messageIdOverride = scanInProgressMessageId) - codeScanManager.runCodeScan(CodeWhispererConstants.CodeAnalysisScope.PROJECT, initiatedByChat = true) - codeScanChatHelper.updateProgress(isProject = true, isCanceling = false) - } - - override suspend fun processStopProjectScan(message: IncomingCodeScanMessage.StopProjectScan) { - if (message.tabId != codeScanChatHelper.getActiveCodeScanTabId()) return - codeScanChatHelper.updateProgress(isProject = true, isCanceling = true) - codeScanManager.stopCodeScan(CodeWhispererConstants.CodeAnalysisScope.PROJECT) - } - - override suspend fun processStopFileScan(message: IncomingCodeScanMessage.StopFileScan) { - if (message.tabId != codeScanChatHelper.getActiveCodeScanTabId()) return - codeScanChatHelper.updateProgress(isProject = false, isCanceling = true) - codeScanManager.stopCodeScan(CodeWhispererConstants.CodeAnalysisScope.FILE) - } - - override suspend fun processStartFileScan(message: IncomingCodeScanMessage.StartFileScan) { - if (message.tabId != codeScanChatHelper.getActiveCodeScanTabId()) return - codeScanChatHelper.addNewMessage(buildUserSelectionFileScanChatContent()) - codeScanChatHelper.addNewMessage(buildScanInProgressChatContent(currentStep = 1, isProject = false), messageIdOverride = scanInProgressMessageId) - codeScanManager.runCodeScan(CodeWhispererConstants.CodeAnalysisScope.FILE, initiatedByChat = true) - codeScanChatHelper.updateProgress(isProject = false, isCanceling = false) - } - - override suspend fun processTabCreated(message: IncomingCodeScanMessage.TabCreated) { - logger.debug { "$FEATURE_NAME: New tab created: $message" } - codeScanChatHelper.setActiveCodeScanTabId(message.tabId) - CodeWhispererTelemetryService.getInstance().sendCodeScanNewTabEvent(getAuthType(context.project)) - } - - override suspend fun processClearQuickAction(message: IncomingCodeScanMessage.ClearChat) { - chatSessionStorage.deleteSession(message.tabId) - } - - override suspend fun processHelpQuickAction(message: IncomingCodeScanMessage.Help) { - if (message.tabId != codeScanChatHelper.getActiveCodeScanTabId()) return - codeScanChatHelper.addNewMessage(buildHelpChatPromptContent()) - codeScanChatHelper.addNewMessage(buildHelpChatAnswerContent()) - } - - override suspend fun processTabRemoved(message: IncomingCodeScanMessage.TabRemoved) { - chatSessionStorage.deleteSession(message.tabId) - } - - override suspend fun processResponseBodyLinkClicked(message: IncomingCodeScanMessage.ResponseBodyLinkClicked) { - BrowserUtil.browse(message.link) - } - - override suspend fun processCodeScanCommand(message: CodeScanActionMessage) { - if (message.project != context.project) return - val isProject = message.scope == CodeWhispererConstants.CodeAnalysisScope.PROJECT - when (message.command) { - CodeScanCommand.ScanComplete -> { - codeScanChatHelper.addNewMessage( - buildScanInProgressChatContent(currentStep = 2, isProject = isProject), - messageIdOverride = scanInProgressMessageId - ) - val result = message.scanResult - if (result != null) { - handleCodeScanResult(result, message.scope) - } else { - codeScanChatHelper.addNewMessage(buildProjectScanFailedChatContent("Cancelled")) - codeScanChatHelper.clearProgress() - } - } - } - } - - private suspend fun handleCodeScanResult(result: CodeScanResponse, scope: CodeWhispererConstants.CodeAnalysisScope) { - val isProject = scope == CodeWhispererConstants.CodeAnalysisScope.PROJECT - when (result) { - is CodeScanResponse.Success -> { - codeScanChatHelper.addNewMessage( - buildScanInProgressChatContent(currentStep = 3, isProject = isProject), - messageIdOverride = scanInProgressMessageId - ) - codeScanChatHelper.addNewMessage(buildScanCompleteChatContent(result.issues, isProject = isProject)) - codeScanChatHelper.clearProgress() - } - is CodeScanResponse.Failure -> { - codeScanChatHelper.addNewMessage(buildScanInProgressChatContent(3, isProject = isProject), messageIdOverride = scanInProgressMessageId) - codeScanChatHelper.addNewMessage(buildProjectScanFailedChatContent(result.failureReason.message)) - codeScanChatHelper.clearProgress() - } - } - } - - /** - * Return true if authenticated, else show authentication message and return false - * // TODO: Refactor this to avoid code duplication with other controllers - */ - private suspend fun checkForAuth(tabId: String): Boolean { - try { - val session = chatSessionStorage.getSession(tabId) - logger.debug { "$FEATURE_NAME: Session created with id: ${session.tabId}" } - - val credentialState = authController.getAuthNeededStates(context.project).amazonQ - if (credentialState != null) { - messenger.publish( - AuthenticationNeededExceptionMessage( - tabId = session.tabId, - authType = credentialState.authType, - message = credentialState.message - ) - ) - session.isAuthenticating = true - return false - } - } catch (err: Exception) { - messenger.publish( - CodeScanChatMessage( - tabId = tabId, - messageType = ChatMessageType.Answer, - message = message("codescan.chat.message.error_request") - ) - ) - return false - } - - return true - } - - override suspend fun processOpenIssuesPanel(message: IncomingCodeScanMessage.OpenIssuesPanel) { - if (message.tabId != codeScanChatHelper.getActiveCodeScanTabId()) return - codeScanManager.showCodeScanUI() - } - - companion object { - private val logger = getLogger() - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/controller/CodeScanChatHelper.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/controller/CodeScanChatHelper.kt deleted file mode 100644 index 920bebb5109..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/controller/CodeScanChatHelper.kt +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan.controller - -import software.aws.toolkits.jetbrains.services.amazonq.messages.MessagePublisher -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildClearPromptProgressMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.buildPromptProgressMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.ChatInputEnabledMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.CodeScanChatMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.CodeScanChatMessageContent -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages.UpdatePlaceholderMessage -import software.aws.toolkits.jetbrains.services.amazonqCodeScan.storage.ChatSessionStorage -import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessageType -import java.util.UUID - -class CodeScanChatHelper( - private val messagePublisher: MessagePublisher, - private val chatSessionStorage: ChatSessionStorage, -) { - private var activeCodeScanTabId: String? = null - - fun setActiveCodeScanTabId(tabId: String) { - activeCodeScanTabId = tabId - } - - fun getActiveCodeScanTabId(): String? = activeCodeScanTabId - - private fun isInValidSession() = activeCodeScanTabId == null || chatSessionStorage.getSession(activeCodeScanTabId as String).isAuthenticating - - suspend fun addNewMessage( - content: CodeScanChatMessageContent, - messageIdOverride: String? = null, - clearPreviousItemButtons: Boolean? = false, - ) { - if (isInValidSession()) return - messagePublisher.publish( - CodeScanChatMessage( - tabId = activeCodeScanTabId as String, - messageId = messageIdOverride ?: UUID.randomUUID().toString(), - messageType = content.type, - message = content.message, - buttons = content.buttons, - formItems = content.formItems, - followUps = content.followUps, - canBeVoted = content.canBeVoted, - isLoading = content.type == ChatMessageType.AnswerPart, - clearPreviousItemButtons = clearPreviousItemButtons as Boolean - ) - ) - } - - suspend fun updateProgress(isProject: Boolean = false, isCanceling: Boolean = false) { - if (isInValidSession()) return - messagePublisher.publish(buildPromptProgressMessage(activeCodeScanTabId as String, isProject, isCanceling)) - sendChatInputEnabledMessage(false) - } - - suspend fun clearProgress() { - if (isInValidSession()) return - messagePublisher.publish(buildClearPromptProgressMessage(activeCodeScanTabId as String)) - sendChatInputEnabledMessage(true) - } - - suspend fun sendChatInputEnabledMessage(isEnabled: Boolean) { - if (isInValidSession()) return - messagePublisher.publish(ChatInputEnabledMessage(activeCodeScanTabId as String, enabled = isEnabled)) - } - - suspend fun updatePlaceholder(newPlaceholder: String) { - if (isInValidSession()) return - - messagePublisher.publish( - UpdatePlaceholderMessage( - tabId = activeCodeScanTabId as String, - newPlaceholder = newPlaceholder - ) - ) - } -} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/messages/CodeScanMessage.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/messages/CodeScanMessage.kt deleted file mode 100644 index 51fffcdb53d..00000000000 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeScan/messages/CodeScanMessage.kt +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonqCodeScan.messages - -import com.fasterxml.jackson.annotation.JsonProperty -import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthFollowUpType -import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage -import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessageType -import software.aws.toolkits.jetbrains.services.cwc.messages.FollowUp -import java.time.Instant -import java.util.UUID - -const val CODE_SCAN_TAB_NAME = "codescan" - -enum class CodeScanButtonId(val id: String) { - StartProjectScan("codescan_start_project_scan"), - StartFileScan("codescan_start_file_scan"), - StopProjectScan("codescan_stop_project_scan"), - StopFileScan("codescan_stop_file_scan"), - OpenIssuesPanel("codescan_open_issues"), -} - -data class Button( - val id: String, - val text: String, - val description: String? = null, - val icon: String? = null, - val keepCardAfterClick: Boolean? = false, - val disabled: Boolean? = false, - val waitMandatoryFormItems: Boolean? = false, -) -data class ProgressField( - val title: String? = null, - val value: Int? = null, - val status: String? = null, - val actions: List