├── .gitignore ├── .idea └── gradle.xml ├── .run ├── Run IDE for UI Tests.run.xml ├── Run IDE with Plugin.run.xml ├── Run Plugin Tests.run.xml ├── Run Plugin Verification.run.xml └── Run Qodana.run.xml ├── CHANGELOG.md ├── README.md ├── build.gradle.kts ├── coreys-chatgpt-intellij-plugin-0.0.1.zip ├── docs ├── explain-action.gif ├── install-from-disk.png ├── open-dialog.png ├── panel-icon.png ├── plugin-panel.png ├── refactor-action.gif └── settings.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── qodana.yml ├── settings.gradle.kts └── src └── main ├── kotlin └── com │ └── github │ └── cspeisman │ └── chatgptintellijplugin │ ├── chatbot │ ├── ChatBotToolWindow.kt │ ├── actions │ │ ├── ChatBotActionService.kt │ │ ├── ChatBotBaseAction.kt │ │ ├── ChatBotExplainAction.kt │ │ └── ChatBotRefactorAction.kt │ └── chatgpt │ │ ├── ChatGptRepository.kt │ │ └── ChatGptService.kt │ ├── settings │ ├── AppSettingsComponent.kt │ ├── AppSettingsConfigurable.kt │ └── AppSettingsState.kt │ └── ui │ ├── ContentPanelComponent.kt │ ├── MessageComponent.kt │ └── PromptFormatter.kt └── resources ├── META-INF ├── plugin.xml └── pluginIcon.svg ├── icons └── toolWindow.svg └── messages └── MyBundle.properties /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | .qodana 4 | build 5 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.run/Run IDE for UI Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 15 | 17 | true 18 | true 19 | false 20 | 21 | 22 | -------------------------------------------------------------------------------- /.run/Run IDE with Plugin.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 24 | -------------------------------------------------------------------------------- /.run/Run Plugin Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.run/Run Plugin Verification.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 25 | 26 | -------------------------------------------------------------------------------- /.run/Run Qodana.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 16 | 19 | 21 | true 22 | true 23 | false 24 | 25 | 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # chatgpt-intellij-plugin Changelog 4 | 5 | ## [Unreleased] 6 | ### Added 7 | - Initial scaffold created from [IntelliJ Platform Plugin Template](https://github.com/JetBrains/intellij-platform-plugin-template) 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Corey's ChatBot Intellij Plugin 2 | 3 | ## Overview 4 | 5 | The ChatBot plugin uses ChatGPT underneath the hood to assist you in all (okay, some) of your coding needs. 6 | You can interact with the ChatBot and ask it any questions you would like, even those not coding related. 7 | Additionally, you can fire intellij actions to `Ask Chatbot about this code` which will explain your highlighted block of code 8 | or `Ask Chatbot to refactor this code` which will offer up a refactor suggestion for your highlighted block of code. 9 | 10 | 11 | ## Installation 12 | 1. Download the plugin's zip file [coreys-chatgpt-intellij-plugin-0.0.1.zip](coreys-chatgpt-intellij-plugin-0.0.1.zip) in the repository 13 | 2. Go to your plugins planel in the settings modal 14 | 15 | ![plugin panel](docs/plugin-panel.png) 16 | 3. Click on the Gear Icon in the right most tab and select "Install Plugin from Disk..." 17 | 18 | ![install from disk](docs/install-from-disk.png) 19 | 20 | 4. From there you can upload the downloaded zip file 21 | 5. Confirm that you see the ChatBot Icon in the right panel and you are good to go! 22 | 23 | ![panel icon](docs/panel-icon.png) 24 | 25 | ## Setup 26 | Now that the plugin is installed you need you add your ChatGPT api key 27 | 28 | - Navigate to the settings page at **Tools -> ChatBot: ChatGpt Settings** and paste in your api key from [OpenAI](https://platform.openai.com/account/api-keys) 29 | - **OPTIONAL:** If you have a preferred model you would like to use, you can include it here. Otherwise ChatBot will use `gpt-3.5-turbo` by default 30 | 31 | ![settings](docs/settings.png) 32 | --- 33 | 34 | ## Features 35 | **General Questions:** Clicking on the ChatBot icon on the right side toolbar will open the ChatBot panel where you can have open dialog with the chatbot using the text field 36 | 37 | ![open dialog](docs/open-dialog.png) 38 | 39 | **Ask ChatBot To Explain:** You can highlight a block of code and open the actions window and type "Ask ChatBot To Explain". ChatBot will take the highlighted code and give its best explanation 40 | 41 | ![explain action](docs/explain-action.gif) 42 | 43 | **Ask ChatBot To Refactor:** You can highlight a block of code and open the actions window and type "Ask ChatBot To Refactor". ChatBot will take the highlighted code and respond with a refactored solution. You can use the "Replace Selection" button to replace your highlighted code with the refactored suggestion 44 | 45 | ![refactor action](docs/refactor-action.gif) 46 | 47 | 48 | --- 49 | Plugin based on the [IntelliJ Platform Plugin Template][template]. 50 | 51 | [template]: https://github.com/JetBrains/intellij-platform-plugin-template 52 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.changelog.Changelog 2 | import org.jetbrains.changelog.markdownToHTML 3 | 4 | fun properties(key: String) = project.findProperty(key).toString() 5 | 6 | plugins { 7 | // Java support 8 | id("java") 9 | // Kotlin support 10 | id("org.jetbrains.kotlin.jvm") version "1.8.0" 11 | // Gradle IntelliJ Plugin 12 | id("org.jetbrains.intellij") version "1.12.0" 13 | // Gradle Changelog Plugin 14 | id("org.jetbrains.changelog") version "2.0.0" 15 | // // Gradle Qodana Plugin 16 | // id("org.jetbrains.qodana") version "0.1.13" 17 | // // Gradle Kover Plugin 18 | // id("org.jetbrains.kotlinx.kover") version "0.6.1" 19 | } 20 | 21 | group = properties("pluginGroup") 22 | version = properties("pluginVersion") 23 | 24 | // Configure project's dependencies 25 | repositories { 26 | mavenCentral() 27 | } 28 | 29 | // Set the JVM language level used to build the project. Use Java 11 for 2020.3+, and Java 17 for 2022.2+. 30 | kotlin { 31 | jvmToolchain(11) 32 | } 33 | 34 | // Configure Gradle IntelliJ Plugin - read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html 35 | intellij { 36 | pluginName.set(properties("pluginName")) 37 | version.set(properties("platformVersion")) 38 | type.set(properties("platformType")) 39 | 40 | // Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file. 41 | plugins.set(properties("platformPlugins").split(',').map(String::trim).filter(String::isNotEmpty)) 42 | } 43 | 44 | // Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin 45 | changelog { 46 | groups.set(emptyList()) 47 | repositoryUrl.set(properties("pluginRepositoryUrl")) 48 | } 49 | 50 | // Configure Gradle Qodana Plugin - read more: https://github.com/JetBrains/gradle-qodana-plugin 51 | //qodana { 52 | // cachePath.set(file(".qodana").canonicalPath) 53 | // reportPath.set(file("build/reports/inspections").canonicalPath) 54 | // saveReport.set(true) 55 | // showReport.set(System.getenv("QODANA_SHOW_REPORT")?.toBoolean() ?: false) 56 | //} 57 | 58 | // Configure Gradle Kover Plugin - read more: https://github.com/Kotlin/kotlinx-kover#configuration 59 | //kover.xmlReport { 60 | // onCheck.set(true) 61 | //} 62 | 63 | tasks { 64 | wrapper { 65 | gradleVersion = properties("gradleVersion") 66 | } 67 | 68 | patchPluginXml { 69 | version.set(properties("pluginVersion")) 70 | sinceBuild.set(properties("pluginSinceBuild")) 71 | untilBuild.set(properties("pluginUntilBuild")) 72 | 73 | // Extract the section from README.md and provide for the plugin's manifest 74 | pluginDescription.set( 75 | file("README.md").readText().lines().run { 76 | val start = "" 77 | val end = "" 78 | 79 | if (!containsAll(listOf(start, end))) { 80 | throw GradleException("Plugin description section not found in README.md:\n$start ... $end") 81 | } 82 | subList(indexOf(start) + 1, indexOf(end)) 83 | }.joinToString("\n").let { markdownToHTML(it) } 84 | ) 85 | 86 | // Get the latest available change notes from the changelog file 87 | changeNotes.set(provider { 88 | with(changelog) { 89 | renderItem( 90 | getOrNull(properties("pluginVersion")) 91 | ?: runCatching { getLatest() }.getOrElse { getUnreleased() }, 92 | Changelog.OutputType.HTML, 93 | ) 94 | } 95 | }) 96 | } 97 | 98 | // Configure UI tests plugin 99 | // Read more: https://github.com/JetBrains/intellij-ui-test-robot 100 | runIdeForUiTests { 101 | systemProperty("robot-server.port", "8082") 102 | systemProperty("ide.mac.message.dialogs.as.sheets", "false") 103 | systemProperty("jb.privacy.policy.text", "") 104 | systemProperty("jb.consents.confirmation.enabled", "false") 105 | } 106 | 107 | signPlugin { 108 | certificateChain.set(System.getenv("CERTIFICATE_CHAIN")) 109 | privateKey.set(System.getenv("PRIVATE_KEY")) 110 | password.set(System.getenv("PRIVATE_KEY_PASSWORD")) 111 | } 112 | 113 | publishPlugin { 114 | dependsOn("patchChangelog") 115 | token.set(System.getenv("PUBLISH_TOKEN")) 116 | // The pluginVersion is based on the SemVer (https://semver.org) and supports pre-release labels, like 2.1.7-alpha.3 117 | // Specify pre-release label to publish the plugin in a custom Release Channel automatically. Read more: 118 | // https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel 119 | channels.set(listOf(properties("pluginVersion").split('-').getOrElse(1) { "default" }.split('.').first())) 120 | } 121 | } 122 | 123 | dependencies { 124 | implementation("org.slf4j:slf4j-simple:2.0.5") 125 | testImplementation("io.mockk:mockk:1.13.4") 126 | testImplementation("org.slf4j:slf4j-nop:1.7.21") 127 | testImplementation(kotlin("test")) 128 | 129 | implementation("com.squareup.okhttp3:okhttp:4.9.2") 130 | implementation("com.google.code.gson:gson:2.8.9") 131 | implementation("org.commonmark:commonmark:0.20.0") 132 | } 133 | -------------------------------------------------------------------------------- /coreys-chatgpt-intellij-plugin-0.0.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cspeisman/chatgpt-intellij-plugin/31cbd87cb43a8da901c807f676f8bb08f909bd91/coreys-chatgpt-intellij-plugin-0.0.1.zip -------------------------------------------------------------------------------- /docs/explain-action.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cspeisman/chatgpt-intellij-plugin/31cbd87cb43a8da901c807f676f8bb08f909bd91/docs/explain-action.gif -------------------------------------------------------------------------------- /docs/install-from-disk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cspeisman/chatgpt-intellij-plugin/31cbd87cb43a8da901c807f676f8bb08f909bd91/docs/install-from-disk.png -------------------------------------------------------------------------------- /docs/open-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cspeisman/chatgpt-intellij-plugin/31cbd87cb43a8da901c807f676f8bb08f909bd91/docs/open-dialog.png -------------------------------------------------------------------------------- /docs/panel-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cspeisman/chatgpt-intellij-plugin/31cbd87cb43a8da901c807f676f8bb08f909bd91/docs/panel-icon.png -------------------------------------------------------------------------------- /docs/plugin-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cspeisman/chatgpt-intellij-plugin/31cbd87cb43a8da901c807f676f8bb08f909bd91/docs/plugin-panel.png -------------------------------------------------------------------------------- /docs/refactor-action.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cspeisman/chatgpt-intellij-plugin/31cbd87cb43a8da901c807f676f8bb08f909bd91/docs/refactor-action.gif -------------------------------------------------------------------------------- /docs/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cspeisman/chatgpt-intellij-plugin/31cbd87cb43a8da901c807f676f8bb08f909bd91/docs/settings.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # IntelliJ Platform Artifacts Repositories -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html 2 | 3 | pluginGroup = com.github.cspeisman.chatgptintellijplugin 4 | pluginName = coreys-chatgpt-intellij-plugin 5 | pluginRepositoryUrl = https://github.com/Cspeisman/chatgpt-intellij-plugin 6 | # SemVer format -> https://semver.org 7 | pluginVersion = 0.0.1 8 | 9 | # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html 10 | pluginSinceBuild = 212.* 11 | pluginUntilBuild = 223.* 12 | 13 | # IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension 14 | platformType = IU 15 | platformVersion = 2021.3.3 16 | pluginVerifierIdeVersions = 2021.2 17 | 18 | # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html 19 | # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 20 | platformPlugins = 21 | 22 | # Gradle Releases -> https://github.com/gradle/gradle/releases 23 | gradleVersion = 7.6 24 | 25 | # Opt-out flag for bundling Kotlin standard library -> https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library 26 | # suppress inspection "UnusedProperty" 27 | kotlin.stdlib.default.dependency = false 28 | 29 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html 30 | # suppress inspection "UnusedProperty" 31 | org.gradle.unsafe.configuration-cache = true 32 | 33 | # Java language level used to compile sources and to generate the files for - Java 11 is required since 2020.3 34 | javaVersion = 11 35 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cspeisman/chatgpt-intellij-plugin/31cbd87cb43a8da901c807f676f8bb08f909bd91/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | # Collect all arguments for the java command; 201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 202 | # shell script including quotes and variable substitutions, so put them in 203 | # double quotes to make sure that they get re-expanded; and 204 | # * put everything else in single quotes, so that it's not re-expanded. 205 | 206 | set -- \ 207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 208 | -classpath "$CLASSPATH" \ 209 | org.gradle.wrapper.GradleWrapperMain \ 210 | "$@" 211 | 212 | # Stop when "xargs" is not available. 213 | if ! command -v xargs >/dev/null 2>&1 214 | then 215 | die "xargs is not available" 216 | fi 217 | 218 | # Use "xargs" to parse quoted args. 219 | # 220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 221 | # 222 | # In Bash we could simply go: 223 | # 224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 225 | # set -- "${ARGS[@]}" "$@" 226 | # 227 | # but POSIX shell has neither arrays nor command substitution, so instead we 228 | # post-process each arg (as a line of input to sed) to backslash-escape any 229 | # character that might be a shell metacharacter, then use eval to reverse 230 | # that process (while maintaining the separation between arguments), and wrap 231 | # the whole thing up as a single "set" statement. 232 | # 233 | # This will of course break if any of these variables contains a newline or 234 | # an unmatched quote. 235 | # 236 | 237 | eval "set -- $( 238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 239 | xargs -n1 | 240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 241 | tr '\n' ' ' 242 | )" '"$@"' 243 | 244 | exec "$JAVACMD" "$@" 245 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /qodana.yml: -------------------------------------------------------------------------------- 1 | # Qodana configuration: 2 | # https://www.jetbrains.com/help/qodana/qodana-yaml.html 3 | 4 | version: 1.0 5 | profile: 6 | name: qodana.recommended 7 | exclude: 8 | - name: All 9 | paths: 10 | - .qodana 11 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "chatgpt-intellij-plugin" 2 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/cspeisman/chatgptintellijplugin/chatbot/ChatBotToolWindow.kt: -------------------------------------------------------------------------------- 1 | package com.github.cspeisman.chatgptintellijplugin.chatbot 2 | 3 | import com.github.cspeisman.chatgptintellijplugin.chatbot.actions.ChatBotActionService 4 | import com.github.cspeisman.chatgptintellijplugin.chatbot.actions.ChatBotActionType 5 | import com.github.cspeisman.chatgptintellijplugin.ui.ContentPanelComponent 6 | import com.intellij.openapi.project.Project 7 | import com.intellij.openapi.wm.ToolWindow 8 | import com.intellij.openapi.wm.ToolWindowFactory 9 | import com.intellij.ui.content.ContentFactory 10 | 11 | 12 | class ChatBotToolWindow : ToolWindowFactory { 13 | override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { 14 | val contentFactory = ContentFactory.SERVICE.getInstance() 15 | val chatBotActionService = ChatBotActionService(ChatBotActionType.EXPLAIN) 16 | val contentPanel = ContentPanelComponent(chatBotActionService) 17 | val createContent = contentFactory?.createContent(contentPanel, "ChatBot", false) 18 | toolWindow.contentManager.addContent(createContent!!) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/cspeisman/chatgptintellijplugin/chatbot/actions/ChatBotActionService.kt: -------------------------------------------------------------------------------- 1 | package com.github.cspeisman.chatgptintellijplugin.chatbot.actions 2 | 3 | import ChatBot 4 | import ChatCompletionRequest 5 | import ChatGptHttp 6 | import com.github.cspeisman.chatgptintellijplugin.settings.AppSettingsState 7 | import com.github.cspeisman.chatgptintellijplugin.ui.ContentPanelComponent 8 | import com.github.cspeisman.chatgptintellijplugin.ui.PromptFormatter 9 | import com.intellij.openapi.application.ApplicationManager 10 | 11 | 12 | class ChatBotActionService(private var actionType: ChatBotActionType) { 13 | val action = if (actionType == ChatBotActionType.EXPLAIN) "explain" else "refactor" 14 | 15 | fun setActionType(actionType: ChatBotActionType) { 16 | this.actionType = actionType 17 | } 18 | 19 | fun getLabel(): String { 20 | val capitalizedAction = action.capitalize() 21 | return "$capitalizedAction Code" 22 | } 23 | 24 | 25 | private fun getCodeSection(content: String): String { 26 | val pattern = "```(.+?)```".toRegex(RegexOption.DOT_MATCHES_ALL) 27 | val match = pattern.find(content) 28 | 29 | if (match != null) return match.groupValues[1].trim() 30 | return "" 31 | } 32 | 33 | 34 | private fun makeChatBotRequest(prompt: String): String { 35 | val apiKey = AppSettingsState.instance.apiKey 36 | val model = AppSettingsState.instance.model.ifEmpty { "gpt-3.5-turbo" } 37 | 38 | if (apiKey.isEmpty()) { 39 | return "Please add an API Key in the ChatBot settings" 40 | } 41 | 42 | val chatbot = ChatBot(ChatGptHttp(apiKey)) 43 | val system = "Be as helpful as possible and concise with your response" 44 | val request = ChatCompletionRequest(model, system) 45 | request.addMessage(prompt) 46 | val generateResponse = chatbot.generateResponse(request) 47 | return generateResponse.choices[0].message.content 48 | } 49 | 50 | fun handlePromptAndResponse( 51 | ui: ContentPanelComponent, 52 | prompt: PromptFormatter, 53 | replaceSelectedText: ((response: String) -> Unit)? = null 54 | ) { 55 | ui.add(prompt.getUIPrompt(), true) 56 | ui.add("Loading...") 57 | 58 | ApplicationManager.getApplication().executeOnPooledThread { 59 | val response = this.makeChatBotRequest(prompt.getRequestPrompt()) 60 | ApplicationManager.getApplication().invokeLater { 61 | when { 62 | actionType === ChatBotActionType.REFACTOR -> ui.updateReplaceableContent(response) { 63 | replaceSelectedText?.invoke(getCodeSection(response)) 64 | } 65 | else -> ui.updateMessage(response) 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | enum class ChatBotActionType { 73 | REFACTOR, 74 | EXPLAIN 75 | } 76 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/cspeisman/chatgptintellijplugin/chatbot/actions/ChatBotBaseAction.kt: -------------------------------------------------------------------------------- 1 | package com.github.cspeisman.chatgptintellijplugin.chatbot.actions 2 | 3 | import com.github.cspeisman.chatgptintellijplugin.ui.ActionPromptFormatter 4 | import com.github.cspeisman.chatgptintellijplugin.ui.ContentPanelComponent 5 | import com.intellij.openapi.actionSystem.AnAction 6 | import com.intellij.openapi.actionSystem.AnActionEvent 7 | import com.intellij.openapi.actionSystem.CommonDataKeys 8 | import com.intellij.openapi.wm.ToolWindowManager 9 | 10 | abstract class ChatBotBaseAction : AnAction() { 11 | override fun actionPerformed(event: AnActionEvent) { 12 | val project = event.project 13 | val toolWindowManager = ToolWindowManager.getInstance(project!!).getToolWindow("ChatBot") 14 | val contentManager = toolWindowManager?.contentManager 15 | 16 | val caretModel = event.getData(CommonDataKeys.EDITOR)?.caretModel 17 | val selectedText = caretModel?.currentCaret?.selectedText ?: "" 18 | val lang = event.getData(CommonDataKeys.PSI_FILE)?.language?.displayName ?: "" 19 | 20 | val chatBotActionService = ChatBotActionService(getActionType()) 21 | val contentPanel = ContentPanelComponent(chatBotActionService) 22 | val content = contentManager?.factory?.createContent(contentPanel, chatBotActionService.getLabel(), false) 23 | contentManager?.removeAllContents(true) 24 | contentManager?.addContent(content!!) 25 | toolWindowManager?.activate(null) 26 | 27 | chatBotActionService.handlePromptAndResponse( 28 | contentPanel, 29 | ActionPromptFormatter(chatBotActionService.action, lang, selectedText), getReplaceableAction(event) 30 | ) 31 | 32 | } 33 | 34 | open fun getReplaceableAction(event: AnActionEvent): ((response: String) -> Unit)? { 35 | return null 36 | } 37 | 38 | abstract fun getActionType(): ChatBotActionType 39 | } 40 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/cspeisman/chatgptintellijplugin/chatbot/actions/ChatBotExplainAction.kt: -------------------------------------------------------------------------------- 1 | package com.github.cspeisman.chatgptintellijplugin.chatbot.actions 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent 4 | 5 | 6 | class ChatBotExplainAction : ChatBotBaseAction() { 7 | override fun actionPerformed(event: AnActionEvent) { 8 | super.actionPerformed(event) 9 | } 10 | 11 | override fun getActionType(): ChatBotActionType { 12 | return ChatBotActionType.EXPLAIN 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/cspeisman/chatgptintellijplugin/chatbot/actions/ChatBotRefactorAction.kt: -------------------------------------------------------------------------------- 1 | package com.github.cspeisman.chatgptintellijplugin.chatbot.actions 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent 4 | import com.intellij.openapi.actionSystem.CommonDataKeys 5 | import com.intellij.openapi.command.WriteCommandAction 6 | 7 | class ChatBotRefactorAction : ChatBotBaseAction() { 8 | 9 | override fun actionPerformed(event: AnActionEvent) { 10 | super.actionPerformed(event) 11 | } 12 | 13 | override fun getActionType(): ChatBotActionType { 14 | return ChatBotActionType.REFACTOR 15 | 16 | } 17 | 18 | override fun getReplaceableAction(event: AnActionEvent): (response: String) -> Unit { 19 | val editor = event.getRequiredData(CommonDataKeys.EDITOR) 20 | val project = event.getRequiredData(CommonDataKeys.PROJECT) 21 | val document = editor.document 22 | 23 | val primaryCaret = editor.caretModel.primaryCaret; 24 | val start = primaryCaret.selectionStart; 25 | val end = primaryCaret.selectionEnd 26 | 27 | return {response -> WriteCommandAction.runWriteCommandAction(project) { 28 | document.replaceString(start, end, response) 29 | }} 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/cspeisman/chatgptintellijplugin/chatbot/chatgpt/ChatGptRepository.kt: -------------------------------------------------------------------------------- 1 | import com.google.gson.GsonBuilder 2 | import com.google.gson.JsonParser 3 | import com.google.gson.annotations.SerializedName 4 | import okhttp3.MediaType 5 | import okhttp3.MediaType.Companion.toMediaType 6 | import okhttp3.OkHttpClient 7 | import okhttp3.Request 8 | import okhttp3.RequestBody 9 | import okhttp3.RequestBody.Companion.toRequestBody 10 | import java.lang.IllegalArgumentException 11 | import java.util.concurrent.TimeUnit 12 | 13 | interface ChatGptRepository { 14 | fun getCompletionResponse(request: ChatCompletionRequest): ChatCompletionResponse 15 | } 16 | class ChatGptHttp(private val apiKey: String): ChatGptRepository { 17 | private val client = OkHttpClient.Builder() 18 | .connectTimeout(60, TimeUnit.SECONDS) 19 | .writeTimeout(60, TimeUnit.SECONDS) 20 | .readTimeout(60, TimeUnit.SECONDS) 21 | .build() 22 | private val mediaType: MediaType = "application/json; charset=utf-8".toMediaType() 23 | private val gson = GsonBuilder().create() 24 | override fun getCompletionResponse(request: ChatCompletionRequest): ChatCompletionResponse { 25 | val json = gson.toJson(request) 26 | val body: RequestBody = json.toRequestBody(mediaType) 27 | 28 | val httpRequest: Request = Request.Builder() 29 | .url("https://api.openai.com/v1/chat/completions") 30 | .addHeader("Content-Type", "application/json") 31 | .addHeader("Authorization", "Bearer $apiKey") 32 | .post(body) 33 | .build() 34 | 35 | // Block the thread and wait for OpenAI's json response 36 | val response = client.newCall(httpRequest).execute() 37 | val jsonResponse = response.body!!.string() 38 | val rootObject = JsonParser.parseString(jsonResponse).asJsonObject 39 | 40 | // Usually happens if you give improper arguments (either an unrecognized argument or bad argument value) 41 | if (rootObject.has("error")) 42 | throw IllegalArgumentException(rootObject["error"].asJsonObject["message"].asString) 43 | 44 | return ChatCompletionResponse(rootObject) 45 | } 46 | } 47 | 48 | class ChatGptFake: ChatGptRepository { 49 | data class ChatMessagePayload(val content: String, val role: String) 50 | data class ChatCompletionChoicePayload(val index: Int, val message: ChatMessagePayload, @SerializedName("finish_reason") val finishReason: String) 51 | data class ChatCompletionResponsePayload(val id: String, val created: String, val choices: List) 52 | override fun getCompletionResponse(request: ChatCompletionRequest): ChatCompletionResponse { 53 | return ChatCompletionResponse("id-1234", 1, listOf(ChatCompletionChoice( 54 | 1, 55 | ChatMessage("response for ${request.messages[1].content}"), 56 | "completed" 57 | ))) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/cspeisman/chatgptintellijplugin/chatbot/chatgpt/ChatGptService.kt: -------------------------------------------------------------------------------- 1 | import com.google.gson.JsonObject 2 | import com.google.gson.annotations.SerializedName 3 | 4 | 5 | 6 | data class ChatMessage(val content: String, val role: String = "user") { 7 | constructor(json: JsonObject) : this( 8 | json["content"].asString ?: "Sorry, no answer found", 9 | json["role"].asString ?: "system" 10 | ) 11 | } 12 | 13 | data class ChatCompletionChoice( 14 | val index: Int, 15 | val message: ChatMessage, 16 | val finishReason: String 17 | ) { 18 | 19 | constructor(json: JsonObject) : this( 20 | json["index"].asInt, 21 | ChatMessage(json["message"].asJsonObject), 22 | if (json["finish_reason"].isJsonNull) "" else json["finish_reason"].asString 23 | ) 24 | } 25 | 26 | data class ChatCompletionResponse( 27 | val id: String, 28 | val created: Long, 29 | val choices: List, 30 | ) { 31 | constructor(json: JsonObject) : this( 32 | json["id"].asString, 33 | json["created"].asLong, 34 | json["choices"].asJsonArray.map { ChatCompletionChoice(it.asJsonObject) }, 35 | ) 36 | } 37 | 38 | data class ChatCompletionRequest( 39 | val model: String, // recommend: "gpt-3.5-turbo" 40 | val messages: MutableList, 41 | private val temperature: Float = 0.0f, 42 | @SerializedName("top_p") val topP: Float = 0.0f, 43 | val n: Int = 1, 44 | val stream: Boolean = false, 45 | val stop: String? = null, 46 | @SerializedName("max_tokens") val maxTokens: Int? = null, // default is 4096 47 | @SerializedName("presence_penalty") val presencePenalty: Float = 0.0f, 48 | @SerializedName("frequency_penalty") val frequencyPenalty: Float = 0.0f, 49 | @SerializedName("logit_bias") val logitBias: JsonObject? = null, 50 | val user: String? = null 51 | ) { 52 | constructor(model: String, systemContent: String) : this( 53 | model, 54 | arrayListOf(ChatMessage(systemContent, "system")) 55 | ) 56 | 57 | fun addMessage(prompt: String) { 58 | this.messages.add(ChatMessage(prompt)) 59 | } 60 | } 61 | 62 | open class ChatBot(private val chatGptRepository: ChatGptRepository) { 63 | 64 | fun generateResponse(request: ChatCompletionRequest): ChatCompletionResponse { 65 | return chatGptRepository.getCompletionResponse(request) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/cspeisman/chatgptintellijplugin/settings/AppSettingsComponent.kt: -------------------------------------------------------------------------------- 1 | package com.github.cspeisman.chatgptintellijplugin.settings 2 | 3 | import com.intellij.ui.components.JBLabel 4 | import com.intellij.ui.components.JBTextField 5 | import com.intellij.util.ui.FormBuilder 6 | import javax.swing.JComponent 7 | import javax.swing.JPanel 8 | 9 | 10 | class AppSettingsComponent { 11 | private val myMainPanel: JPanel 12 | private val myApiKey = JBTextField() 13 | private val myChatGptModel = JBTextField() 14 | 15 | init { 16 | myMainPanel = FormBuilder.createFormBuilder() 17 | .addLabeledComponent(JBLabel("ChatGpt Api Key:"), myApiKey, 1, false) 18 | .addLabeledComponent(JBLabel("ChatGpt Model: "), myChatGptModel, 1, false) 19 | .addComponentFillVertically(JPanel(), 0) 20 | .panel 21 | } 22 | 23 | val panel: JPanel 24 | get() = myMainPanel 25 | val preferredFocusedComponent: JComponent 26 | get() = myApiKey 27 | 28 | var apiKey: String? 29 | get() = myApiKey.text 30 | set(newText) { 31 | myApiKey.text = newText 32 | } 33 | var chatGptModel: String? 34 | get() = myChatGptModel.text 35 | set(newText) { 36 | myChatGptModel.text = newText 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/cspeisman/chatgptintellijplugin/settings/AppSettingsConfigurable.kt: -------------------------------------------------------------------------------- 1 | package com.github.cspeisman.chatgptintellijplugin.settings 2 | 3 | import com.intellij.openapi.options.Configurable 4 | import javax.swing.JComponent 5 | 6 | class AppSettingsConfigurable : Configurable { 7 | private var mySettingsComponent: AppSettingsComponent? = null 8 | 9 | 10 | override fun createComponent(): JComponent { 11 | mySettingsComponent = AppSettingsComponent() 12 | return mySettingsComponent!!.panel 13 | } 14 | 15 | override fun isModified(): Boolean { 16 | val settings: AppSettingsState = AppSettingsState.instance 17 | var modified = mySettingsComponent!!.apiKey != settings.apiKey 18 | modified = modified or (mySettingsComponent!!.chatGptModel != settings.model) 19 | return modified 20 | } 21 | 22 | 23 | override fun apply() { 24 | val settings: AppSettingsState = AppSettingsState.instance 25 | settings.apiKey = mySettingsComponent!!.apiKey!! 26 | settings.model = mySettingsComponent!!.chatGptModel!! 27 | } 28 | 29 | override fun reset() { 30 | val settings: AppSettingsState = AppSettingsState.instance 31 | mySettingsComponent!!.apiKey = settings.apiKey 32 | mySettingsComponent!!.chatGptModel = settings.model 33 | } 34 | 35 | override fun disposeUIResources() { 36 | mySettingsComponent = null 37 | } 38 | 39 | override fun getDisplayName(): String { 40 | return "ChatBot: ChatGpt Settings" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/cspeisman/chatgptintellijplugin/settings/AppSettingsState.kt: -------------------------------------------------------------------------------- 1 | package com.github.cspeisman.chatgptintellijplugin.settings; 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import com.intellij.openapi.components.PersistentStateComponent 5 | import com.intellij.openapi.components.State 6 | import com.intellij.openapi.components.Storage 7 | 8 | import com.intellij.util.xmlb.XmlSerializerUtil 9 | 10 | 11 | @State( 12 | name = "com.github.cspeisman.chatgptintellijplugin.settings.AppSettingsState", 13 | storages = [Storage("ChatGPTSettingsPlugin.xml")] 14 | ) 15 | class AppSettingsState : PersistentStateComponent { 16 | var apiKey = "" 17 | var model = "" 18 | 19 | 20 | override fun getState(): AppSettingsState { 21 | return this 22 | } 23 | 24 | override fun loadState(state: AppSettingsState) { 25 | XmlSerializerUtil.copyBean(state, this) 26 | } 27 | 28 | companion object { 29 | val instance: AppSettingsState 30 | get() = ApplicationManager.getApplication().getService(AppSettingsState::class.java) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/cspeisman/chatgptintellijplugin/ui/ContentPanelComponent.kt: -------------------------------------------------------------------------------- 1 | package com.github.cspeisman.chatgptintellijplugin.ui 2 | 3 | import com.github.cspeisman.chatgptintellijplugin.chatbot.actions.ChatBotActionService 4 | import com.github.cspeisman.chatgptintellijplugin.chatbot.actions.ChatBotActionType 5 | import com.intellij.openapi.ui.NullableComponent 6 | import com.intellij.ui.Gray 7 | import com.intellij.ui.JBColor 8 | import com.intellij.ui.OnePixelSplitter 9 | import com.intellij.ui.components.JBLabel 10 | import com.intellij.ui.components.JBPanel 11 | import com.intellij.ui.components.JBScrollPane 12 | import com.intellij.ui.components.labels.LinkLabel 13 | import com.intellij.ui.components.panels.VerticalLayout 14 | import com.intellij.util.ui.JBEmptyBorder 15 | import com.intellij.util.ui.JBFont 16 | import com.intellij.util.ui.JBUI 17 | import com.intellij.util.ui.UIUtil 18 | import java.awt.BorderLayout 19 | import java.awt.event.ActionEvent 20 | import java.awt.event.ActionListener 21 | import java.awt.event.MouseAdapter 22 | import java.awt.event.MouseEvent 23 | import javax.swing.* 24 | 25 | 26 | class ContentPanelComponent(private val chatBotActionService: ChatBotActionService) : 27 | JBPanel(), NullableComponent { 28 | 29 | private var progressBar: JProgressBar 30 | private val myTitle = JBLabel("Conversation") 31 | private val myList = JPanel(VerticalLayout(JBUI.scale(10))) 32 | private val mainPanel = JPanel(BorderLayout(0, JBUI.scale(8))) 33 | private val myScrollPane = JBScrollPane( 34 | myList, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, 35 | ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER 36 | ) 37 | 38 | init { 39 | val splitter = OnePixelSplitter(true, .98f) 40 | splitter.dividerWidth = 2 41 | 42 | myTitle.foreground = JBColor.namedColor("Label.infoForeground", JBColor(Gray.x80, Gray.x8C)) 43 | myTitle.font = JBFont.label() 44 | 45 | layout = BorderLayout(JBUI.scale(7), 0) 46 | background = UIUtil.getListBackground() 47 | mainPanel.isOpaque = false 48 | add(mainPanel, BorderLayout.CENTER) 49 | 50 | myList.isOpaque = true 51 | myList.background = UIUtil.getListBackground() 52 | myScrollPane.border = JBEmptyBorder(10, 15, 10, 15) 53 | 54 | splitter.firstComponent = myScrollPane 55 | 56 | progressBar = JProgressBar() 57 | splitter.secondComponent = progressBar 58 | mainPanel.add(splitter) 59 | myScrollPane.verticalScrollBar.autoscrolls = true 60 | addQuestionArea() 61 | } 62 | 63 | fun add(message: String, isMe: Boolean = false) { 64 | val messageComponent = MessageComponent(message, isMe) 65 | val jbScrollPane = JBScrollPane( 66 | messageComponent, ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER, 67 | ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED 68 | ) 69 | 70 | myList.add(jbScrollPane) 71 | progressBar.isIndeterminate = true 72 | updateUI() 73 | } 74 | 75 | fun updateMessage(message: String) { 76 | myList.remove(myList.componentCount - 1) 77 | val messageComponent = MessageComponent(message, false) 78 | myList.add(messageComponent) 79 | progressBar.isIndeterminate = false 80 | progressBar.isVisible = false 81 | updateUI() 82 | } 83 | 84 | override fun isNull(): Boolean { 85 | return !isVisible 86 | } 87 | 88 | fun updateReplaceableContent(content: String, replaceSelectedText: () -> Unit) { 89 | myList.remove(myList.componentCount - 1) 90 | val messageComponent = MessageComponent(content, false) 91 | 92 | val jButton = JButton("Replace Selection") 93 | val listener = ActionListener { 94 | replaceSelectedText() 95 | myList.remove(myList.componentCount - 1) 96 | } 97 | jButton.addActionListener(listener) 98 | myList.add(messageComponent) 99 | myList.add(jButton) 100 | progressBar.isIndeterminate = false 101 | progressBar.isVisible = false 102 | updateUI() 103 | } 104 | 105 | private fun addQuestionArea() { 106 | val actionPanel = JPanel(BorderLayout()) 107 | 108 | val searchTextArea = JTextField() 109 | 110 | val listener: (ActionEvent) -> Unit = { 111 | val prompt = searchTextArea.text 112 | searchTextArea.text = "" 113 | chatBotActionService.setActionType(ChatBotActionType.EXPLAIN) 114 | chatBotActionService.handlePromptAndResponse(this, object : PromptFormatter { 115 | override fun getUIPrompt() = prompt 116 | override fun getRequestPrompt() = prompt 117 | }) 118 | } 119 | searchTextArea.addActionListener(listener) 120 | actionPanel.add(searchTextArea, BorderLayout.CENTER) 121 | 122 | val actionButtons = JPanel(BorderLayout()) 123 | val clearChat = LinkLabel("Clear Chat", null) 124 | clearChat.addMouseListener(object : MouseAdapter() { 125 | override fun mouseClicked(e: MouseEvent?) { 126 | myList.removeAll() 127 | updateUI() 128 | } 129 | }) 130 | clearChat.border = JBEmptyBorder(5, 5, 5, 5) 131 | 132 | val button = JButton("Send") 133 | button.addActionListener(listener) 134 | 135 | actionButtons.add(button, BorderLayout.NORTH) 136 | actionButtons.add(clearChat, BorderLayout.SOUTH) 137 | actionPanel.add(actionButtons, BorderLayout.EAST) 138 | 139 | mainPanel.add(actionPanel, BorderLayout.SOUTH) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/cspeisman/chatgptintellijplugin/ui/MessageComponent.kt: -------------------------------------------------------------------------------- 1 | package com.github.cspeisman.chatgptintellijplugin.ui 2 | 3 | import com.intellij.ui.JBColor 4 | import com.intellij.util.ui.JBEmptyBorder 5 | import com.intellij.util.ui.UIUtil 6 | import org.commonmark.node.Node 7 | import org.commonmark.parser.Parser 8 | import org.commonmark.renderer.html.HtmlRenderer 9 | import javax.swing.JEditorPane 10 | import javax.swing.text.SimpleAttributeSet 11 | import javax.swing.text.StyleConstants 12 | import javax.swing.text.StyledEditorKit 13 | 14 | fun parseMarkdown(markdown: String): String { 15 | val parser = Parser.builder().build() 16 | val document: Node = parser.parse(markdown) 17 | val htmlRenderer = HtmlRenderer.builder().build() 18 | return htmlRenderer.render(document) 19 | } 20 | 21 | class MessageComponent(question: String, isPrompt: Boolean = false) : JEditorPane() { 22 | init { 23 | this.contentType = "text/html;charset=UTF-8" 24 | this.putClientProperty(HONOR_DISPLAY_PROPERTIES, true) 25 | this.font = UIUtil.getMenuFont() 26 | this.isEditable = false 27 | this.background = if (isPrompt) JBColor(0xEAEEF7, 0x45494A) else JBColor(0xE0EEF7, 0x2d2f30) 28 | this.border = JBEmptyBorder(10) 29 | this.text = if (isPrompt) question else parseMarkdown(question) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/cspeisman/chatgptintellijplugin/ui/PromptFormatter.kt: -------------------------------------------------------------------------------- 1 | package com.github.cspeisman.chatgptintellijplugin.ui 2 | 3 | interface PromptFormatter { 4 | fun getUIPrompt(): String 5 | 6 | fun getRequestPrompt(): String 7 | } 8 | 9 | class ActionPromptFormatter(private val action: String, private val lang: String, private val selectedText: String): 10 | PromptFormatter { 11 | override fun getUIPrompt(): String { 12 | return """$action this $lang code: 13 |
$selectedText
14 | """.trimMargin() 15 | } 16 | 17 | override fun getRequestPrompt(): String { 18 | return """$action this $lang code: 19 | $selectedText 20 | """.trimMargin() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.github.cspeisman.chatgptintellijplugin 4 | Corey's ChatBot 5 | cspeisman 6 | 7 | com.intellij.modules.platform 8 | 9 | 10 | 11 | 13 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/resources/icons/toolWindow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/resources/messages/MyBundle.properties: -------------------------------------------------------------------------------- 1 | name=My Plugin 2 | applicationService=Application service 3 | projectService=Project service: {0} 4 | --------------------------------------------------------------------------------