Merge remote-tracking branch 'origin/master' into platform/2024.1
4
.github/dependabot.yml
vendored
|
|
@ -11,11 +11,11 @@ updates:
|
|||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "gradle"
|
||||
directory: "/buildSrc"
|
||||
directory: "/buildSrc/src/main/kotlin" # /buildSrc and /codegpt-telemetry use only references
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "gradle"
|
||||
directory: "/codegpt-core"
|
||||
directory: "/codegpt-treesitter"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
|
|
|||
23
CHANGELOG.md
|
|
@ -6,10 +6,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [2.6.0-233] - 2024-04-07
|
||||
|
||||
### Added
|
||||
|
||||
- Vision support (image understanding) for OpenAI GPT-4 and Anthropic Claude models
|
||||
- Total token panel for all providers
|
||||
- Support for configuring code completions via settings
|
||||
- Autofocus for UserTextArea when the tool window is visible
|
||||
|
||||
### Fixed
|
||||
|
||||
- Git commit message generation
|
||||
- Fixed several UI/UX issues related to code completions for IDE versions starting from 233
|
||||
- Error when adding a single file to the context
|
||||
- Several IntelliJ platform warnings
|
||||
|
||||
### Removed
|
||||
|
||||
- Azure custom configuration (use OpenAI-compatible service to override the default configuration)
|
||||
|
||||
### Changed
|
||||
|
||||
- Supported minimum IDE build from 213 to 222
|
||||
|
||||
## [2.5.1] - 2024-03-14
|
||||
|
||||
### Added
|
||||
|
|
@ -396,7 +416,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- `OPENAI_API_KEY` persistence, key is saved in the OS password safe from now on
|
||||
|
||||
[Unreleased]: https://github.com/carlrobertoh/CodeGPT/compare/v2.5.1...HEAD
|
||||
[Unreleased]: https://github.com/carlrobertoh/CodeGPT/compare/v2.6.0-233...HEAD
|
||||
[2.6.0-233]: https://github.com/carlrobertoh/CodeGPT/compare/v2.5.1...v2.6.0-233
|
||||
[2.5.1]: https://github.com/carlrobertoh/CodeGPT/compare/v2.5.0...v2.5.1
|
||||
[2.5.0]: https://github.com/carlrobertoh/CodeGPT/compare/v2.4.0...v2.5.0
|
||||
[2.4.0]: https://github.com/carlrobertoh/CodeGPT/compare/v2.3.1...v2.4.0
|
||||
|
|
|
|||
102
DESCRIPTION.md
|
|
@ -2,104 +2,68 @@
|
|||
|
||||
## Introducing CodeGPT: Your Free, Open-Source AI Copilot for Coding
|
||||
|
||||
CodeGPT is your go-to AI assistant, designed to enhance your coding skills and optimize your programming time.
|
||||
Access state-of-the-art LLMs like GPT-4, Claude 3, Code LLama and more, all for free.
|
||||
CodeGPT is your go-to AI coding assistant, offering assistance throughout your entire software development journey while keeping privacy in mind. Access state-of-the-art large language models from leading providers such as OpenAI, Anthropic, Azure, Mistral, and others, or connect to a locally hosted model for a completely offline and transparent development experience.
|
||||
|
||||
## Quick Start Guide
|
||||
## Core Features
|
||||
|
||||
1. **Download the Plugin**
|
||||
Leveraging large language models, CodeGPT offers a wide range of features to enhance your coding experience, including, but not limited to:
|
||||
|
||||
2. **Choose Your Preferred Service**
|
||||
### Code Completions
|
||||
|
||||
a) **OpenAI** - Requires authentication via OpenAI API key.
|
||||
Receive single-line or whole-function autocomplete suggestions as you type.
|
||||
|
||||
b) **Custom OpenAI-compatible service** - Choose between multiple different providers, such as Together, Anyscale, Groq, Ollama and many more.
|
||||
|
||||
c) **Anthropic** - Requires authentication via API key.
|
||||

|
||||
|
||||
d) **Azure** - Requires authentication via Active Directory or API key.
|
||||
> Completions are currently supported only for OpenAI GPT-3.5 and locally hosted models.
|
||||
|
||||
e) **You.com** - A free, web-connected service with an optional upgrade to You⚡Pro for enhanced features.
|
||||
### Chat (with Vision)
|
||||
|
||||
f) **LLaMA C/C++ Port** - Run Code Llama, WizardCoder, Deepseek Coder, and other state-of-the-art models locally for free.
|
||||
Get instant coding advice through a ChatGPT-like interface. Ask questions, seek explanations, or get guidance on your projects without leaving your IDE.
|
||||
|
||||
3. **Start Using the Features**
|
||||
CodeGPT also supports vision models and image understanding, allowing you to attach images for more context-aware assistance. It can detect new screenshots automatically, saving you time by eliminating the need to manually upload images each time you take a screenshot.
|
||||
|
||||
### OpenAI
|
||||

|
||||
|
||||
After successful installation, configure your API key. Navigate to the plugin's settings via **File | Settings/Preferences | Tools | CodeGPT**. Paste your OpenAI API key into the field and click `Apply/OK`.
|
||||
### Commit Message Generation
|
||||
|
||||
### Azure OpenAI
|
||||
CodeGPT can generate meaningful commit messages based on the changes made in your codebase. It analyzes the diff of your staged changes and suggests concise and descriptive commit messages, saving you time and effort.
|
||||
|
||||
For Azure OpenAI services, you'll need to input three additional fields:
|
||||

|
||||
|
||||
- **Resource name**: The name of your Azure OpenAI Cognitive Services.
|
||||
- **Deployment ID**: The name of your Deployment.
|
||||
- **API version**: The most recent non-preview version.
|
||||
### Reference Files
|
||||
|
||||
Also, input one of the two provided API keys.
|
||||
CodeGPT allows you to reference specific files or documentation during your chat sessions, ensuring that responses are always relevant and accurate.
|
||||
|
||||
### You.com (Free)
|
||||

|
||||
|
||||
**You**.com is a search engine that summarizes the best parts of the internet for **you**, with private ads and with privacy options.
|
||||
### Name Suggestions
|
||||
|
||||
**You⚡Pro**
|
||||
Stuck on naming a method or variable? CodeGPT offers context-aware suggestions, helping you adhere to best practices and maintain readability in your codebase.
|
||||
|
||||
Use the **CodeGPT** coupon for a free month of unlimited GPT-4 usage.
|
||||

|
||||
|
||||
Check out the full [feature list](https://about.you.com/hc/youpro/what-features-are-included-in-youpro/) for more details.
|
||||
### Offline Development Support
|
||||
|
||||
### LLaMA C/C++ Port (Free, Local)
|
||||
CodeGPT supports a completely offline development workflow by allowing you to connect to a locally hosted language model. This ensures that your code and data remain private and secure within your local environment, eliminating the need for an internet connection or sharing sensitive information with third-party servers.
|
||||
|
||||
> **Note**: This feature is currently supported only on Linux and MacOS.
|
||||

|
||||
|
||||
The main goal of `llama.cpp` is to run the LLaMA model using 4-bit integer quantization on a MacBook.
|
||||
## Privacy
|
||||
|
||||
#### Getting Started
|
||||
**Your data stays yours.** CodeGPT **does not** collect or store any kind of sensitive information.
|
||||
|
||||
1. **Select the Model**: Depending on your hardware capabilities, choose the appropriate model from the provided list. Once selected, click on the `Download Model` link. A progress bar will appear, indicating the download process.
|
||||
However, with users' consent, we do collect anonymous usage data, which we use to understand how users interact with the extension, including the most-used features and preferred providers.
|
||||
|
||||
2. **Start the Server**: After successfully downloading the model, initiate the server by clicking on the `Start Server` button. A status message will be displayed, indicating that the server is starting up.
|
||||
## License
|
||||
|
||||
3. **Apply Settings**: With the server running, you can now apply the settings to start using the features. Click on the `Apply/OK` button to save your settings and start using the application.
|
||||
CodeGPT's code is open source under the Apache License 2.0.
|
||||
|
||||
<img alt="animated" style="max-width: 100%; width: 600px;" src="https://github.com/carlrobertoh/CodeGPT-docs/blob/main/images/llama-settings.png?raw=true" />
|
||||
## Feedback
|
||||
|
||||
> **Note**: If you're already running a server and wish to configure the plugin against that, then simply select the port and click `Apply/OK`.
|
||||
Your input helps us grow. Reach out through:
|
||||
|
||||
## Features
|
||||
|
||||
The plugin provides several key features, such as:
|
||||
|
||||
### Chat with AI
|
||||
|
||||
Ask anything you'd like.
|
||||
|
||||
<img alt="animated" style="max-width: 100%; width: 800px;" src="https://github.com/carlrobertoh/CodeGPT-docs/blob/main/gif/ask-anything.gif?raw=true" />
|
||||
|
||||
### Select and Ask
|
||||
|
||||
Ask anything related to your selected code.
|
||||
|
||||
<img alt="animated" style="max-width: 100%; width: 800px;" src="https://github.com/carlrobertoh/CodeGPT-docs/blob/main/gif/custom-prompt.gif?raw=true" />
|
||||
|
||||
### Replace Generated Code
|
||||
|
||||
Instantly replace a selected code block in the editor with suggested code generated by AI.
|
||||
|
||||
<img alt="animated" style="max-width: 100%; width: 800px;" src="https://github.com/carlrobertoh/CodeGPT-docs/blob/main/gif/replace-code.gif?raw=true" />
|
||||
|
||||
### Regenerate Response
|
||||
|
||||
Expected a different answer? Re-generate any response of your choosing.
|
||||
|
||||
<img alt="animated" style="max-width: 100%; width: 800px;" src="https://github.com/carlrobertoh/CodeGPT-docs/blob/main/gif/regenerate.gif?raw=true" />
|
||||
|
||||
## Other features
|
||||
|
||||
- **Conversation History** - View recent conversation history and restore previous sessions, making it easy to pick up where you left off
|
||||
- **Concurrent conversations** - Chat with the AI in multiple tabs simultaneously
|
||||
- **Seamless conversations** - Chat with the AI regardless of the maximum token limitations
|
||||
- **Predefined Actions** - Create your own editor actions or override the existing ones, saving time rewriting the same prompt repeatedly
|
||||
- [Issue Tracker](https://github.com/carlrobertoh/CodeGPT/issues)
|
||||
- [Discord](https://discord.gg/8dTGGrwcnR)
|
||||
- [Email](mailto:carlrobertoh@gmail.com)
|
||||
|
||||
<!-- Plugin description end -->
|
||||
|
|
|
|||
86
GETTING_STARTED.md
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
## Getting Started
|
||||
|
||||
1. **Download the Plugin**
|
||||
|
||||
2. **Choose Your Preferred Service**:
|
||||
|
||||
a) **OpenAI** - Requires authentication via OpenAI API key.
|
||||
|
||||
b) **Azure** - Requires authentication via Active Directory or API key.
|
||||
|
||||
c) **Custom OpenAI-compatible service** - Choose between multiple different providers, such as Together, Anyscale,
|
||||
Groq, Ollama and many more.
|
||||
|
||||
d) **Anthropic** - Requires authentication via API key.
|
||||
|
||||
e) **You.com** - A free, web-connected service with an optional upgrade to You⚡Pro for enhanced features.
|
||||
|
||||
f) **LLaMA C/C++ Port** - Recommended to have a decent computer to handle the computational requirements of running
|
||||
inference.
|
||||
> **Note**: Currently supported only on Linux and MacOS.
|
||||
|
||||
3. **Start Using the Features**
|
||||
|
||||
### Installation
|
||||
|
||||
The plugin is available from [JetBrains Marketplace][plugin-repo].
|
||||
You can install it directly from your IDE via the `File | Settings/Preferences | Plugins` screen.
|
||||
On the `Marketplace` tab simply search for `codegpt` and select the `CodeGPT` suggestion:
|
||||
|
||||
![marketplace][marketplace-img]
|
||||
|
||||
### OpenAI
|
||||
|
||||
After successful installation, configure your API key. Navigate to the plugin's settings via **File |
|
||||
Settings/Preferences | Tools | CodeGPT**. Paste your OpenAI API key into the field and click `Apply/OK`.
|
||||
|
||||
### Azure OpenAI
|
||||
|
||||
For Azure OpenAI services, you'll need to input three additional fields:
|
||||
|
||||
- **Resource name**: The name of your Azure OpenAI Cognitive Services. It's the first part of the url you're provided to
|
||||
use the service: "https://**my-resource-name**.openai.azure.com/". You can find it in your Azure Cognitive Services
|
||||
page, under `Resource Management` → `Resource Management` → `Keys and Endpoints`.
|
||||
- **Deployment ID**: The name of your Deployment. You can find it in the Azure AI Studio,
|
||||
under `Management` → `Deployment` → `Deployment Name` column in the table.
|
||||
- **API version**: The most recent non-preview version.
|
||||
|
||||
In addition to these, you need to input one of the two API Keys provided, found along with the `Resource Name`.
|
||||
|
||||
### You.com (Free)
|
||||
|
||||
**You.com** is a search engine that summarizes the best parts of the internet for **you**, with private ads and with
|
||||
privacy options.
|
||||
|
||||
**You⚡Pro**
|
||||
|
||||
Use the **CodeGPT** coupon for a free month of unlimited GPT-4 usage.
|
||||
|
||||
Check out the full [feature list](https://about.you.com/hc/youpro/what-features-are-included-in-youpro/) for more
|
||||
details.
|
||||
|
||||
### LLaMA C/C++ Port (Free, Local)
|
||||
|
||||
> **Note**: Currently supported only on Linux and MacOS.
|
||||
|
||||
The main goal of `llama.cpp` is to run the LLaMA model using 4-bit integer quantization on a MacBook.
|
||||
|
||||
#### Getting Started
|
||||
|
||||
1. **Select the Model**: Depending on your hardware capabilities, choose the appropriate model from the provided list.
|
||||
Once selected, click on the `Download Model` link. A progress bar will appear, indicating the download process.
|
||||
|
||||
2. **Start the Server**: After successfully downloading the model, initiate the server by clicking on the `Start Server`
|
||||
button. A status message will be displayed, indicating that the server is starting up.
|
||||
|
||||
3. **Apply Settings**: With the server running, you can now apply the settings to start using the features. Click on
|
||||
the `Apply/OK` button to save your settings and start using the application.
|
||||
|
||||
<img alt="animated" style="max-width: 100%; width: 600px;" src="https://github.com/carlrobertoh/CodeGPT-docs/blob/main/images/llama-settings.png?raw=true" />
|
||||
|
||||
> **Note**: If you're already running a server and wish to configure the plugin against that, then simply select the
|
||||
> port and click `Apply/OK`.
|
||||
|
||||
[marketplace-img]: https://github.com/carlrobertoh/CodeGPT-docs/blob/main/images/marketplace.png?raw=true
|
||||
|
||||
[plugin-repo]: https://plugins.jetbrains.com/plugin/21056-codegpt
|
||||
132
README.md
|
|
@ -38,122 +38,53 @@
|
|||
|
||||
## About The Project
|
||||
|
||||
This is an extension for JetBrains IDEs that integrates AI into your coding environment.
|
||||
By leveraging the power of Large Language Models (LLMs), this makes it an invaluable tool for developers looking to streamline their workflow and gain a deeper understanding of the code they're working on.
|
||||
CodeGPT is your go-to AI coding assistant, offering assistance throughout your entire software development journey while keeping privacy in mind. Access state-of-the-art large language models from leading providers such as OpenAI, Anthropic, Azure, Mistral, and others, or connect to a locally hosted model for a completely offline and transparent development experience.
|
||||
|
||||
## Features
|
||||
## Core Features
|
||||
|
||||
The plugin provides several key features, such as:
|
||||
Leveraging large language models, CodeGPT offers a wide range of features to enhance your coding experience, including, but not limited to:
|
||||
|
||||
### Chat with AI
|
||||
### Code Completions
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/carlrobertoh/CodeGPT-docs/blob/main/images/chat-with-ai.png?raw=true" alt="animated" />
|
||||
</p>
|
||||
Receive single-line or whole-function autocomplete suggestions as you type.
|
||||
|
||||
### Chat With Multiple Files
|
||||

|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/carlrobertoh/CodeGPT-docs/blob/main/images/include-in-context.png?raw=true" alt="animated" />
|
||||
</p>
|
||||
> **Note**: Currently supported only on GPT-3.5 and locally-hosted models.
|
||||
|
||||
### Choose Between Different Providers
|
||||
### Chat (with Vision)
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/carlrobertoh/CodeGPT-docs/blob/main/images/model-selection.png?raw=true" />
|
||||
</p>
|
||||
Get instant coding advice through a ChatGPT-like interface. Ask questions, seek explanations, or get guidance on your projects without leaving your IDE.
|
||||
|
||||
### Method Name Suggestions
|
||||
CodeGPT also supports vision models and image understanding, allowing you to attach images for more context-aware assistance. It can detect new screenshots automatically, saving you time by eliminating the need to manually upload images each time you take a screenshot.
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/carlrobertoh/CodeGPT-docs/blob/main/images/method-name-suggestions.png?raw=true" />
|
||||
</p>
|
||||

|
||||
|
||||
### Generate Commit Messages
|
||||
### Commit Message Generation
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/carlrobertoh/CodeGPT-docs/blob/main/images/generate-commit-message.png?raw=true" />
|
||||
</p>
|
||||
CodeGPT can generate meaningful commit messages based on the changes made in your codebase. It analyzes the diff of your staged changes and suggests concise and descriptive commit messages, saving you time and effort.
|
||||
|
||||
### Other features
|
||||

|
||||
|
||||
- **Conversation History** - View recent conversation history and restore previous sessions, making it easy to pick up where you left off
|
||||
- **Concurrent conversations** - Chat with AI in multiple tabs simultaneously
|
||||
- **Seamless conversations** - Chat with AI regardless of the maximum token limitations
|
||||
- **Predefined Prompts** - Create your own editor prompt or override the existing ones
|
||||
### Reference Files
|
||||
|
||||
## Getting Started
|
||||
CodeGPT allows you to reference specific files or documentation during your chat sessions, ensuring that responses are always relevant and accurate.
|
||||
|
||||
1. **Download the Plugin**
|
||||

|
||||
|
||||
2. **Choose Your Preferred Service**:
|
||||
### Name Suggestions
|
||||
|
||||
a) **OpenAI** - Requires authentication via OpenAI API key.
|
||||
Stuck on naming a method or variable? CodeGPT offers context-aware suggestions, helping you adhere to best practices and maintain readability in your codebase.
|
||||
|
||||
b) **Azure** - Requires authentication via Active Directory or API key.
|
||||

|
||||
|
||||
c) **Custom OpenAI-compatible service** - Choose between multiple different providers, such as Together, Anyscale, Groq, Ollama and many more.
|
||||
### Offline Development Support
|
||||
|
||||
d) **Anthropic** - Requires authentication via API key.
|
||||
CodeGPT supports a completely offline development workflow by allowing you to connect to a locally hosted language model. This ensures that your code and data remain private and secure within your local environment, eliminating the need for an internet connection or sharing sensitive information with third-party servers.
|
||||
|
||||
e) **You.com** - A free, web-connected service with an optional upgrade to You⚡Pro for enhanced features.
|
||||

|
||||
|
||||
f) **LLaMA C/C++ Port** - Recommended to have a decent computer to handle the computational requirements of running inference.
|
||||
> **Note**: Currently supported only on Linux and MacOS.
|
||||
|
||||
3. **Start Using the Features**
|
||||
|
||||
### Installation
|
||||
|
||||
The plugin is available from [JetBrains Marketplace][plugin-repo].
|
||||
You can install it directly from your IDE via the `File | Settings/Preferences | Plugins` screen.
|
||||
On the `Marketplace` tab simply search for `codegpt` and select the `CodeGPT` suggestion:
|
||||
|
||||
![marketplace][marketplace-img]
|
||||
|
||||
### OpenAI
|
||||
|
||||
After successful installation, configure your API key. Navigate to the plugin's settings via **File | Settings/Preferences | Tools | CodeGPT**. Paste your OpenAI API key into the field and click `Apply/OK`.
|
||||
|
||||
### Azure OpenAI
|
||||
|
||||
For Azure OpenAI services, you'll need to input three additional fields:
|
||||
|
||||
- **Resource name**: The name of your Azure OpenAI Cognitive Services. It's the first part of the url you're provided to use the service: "https://**my-resource-name**.openai.azure.com/". You can find it in your Azure Cognitive Services page, under `Resource Management` → `Resource Management` → `Keys and Endpoints`.
|
||||
- **Deployment ID**: The name of your Deployment. You can find it in the Azure AI Studio, under `Management` → `Deployment` → `Deployment Name` column in the table.
|
||||
- **API version**: The most recent non-preview version.
|
||||
|
||||
In addition to these, you need to input one of the two API Keys provided, found along with the `Resource Name`.
|
||||
|
||||
### You.com (Free)
|
||||
|
||||
**You.com** is a search engine that summarizes the best parts of the internet for **you**, with private ads and with privacy options.
|
||||
|
||||
**You⚡Pro**
|
||||
|
||||
Use the **CodeGPT** coupon for a free month of unlimited GPT-4 usage.
|
||||
|
||||
Check out the full [feature list](https://about.you.com/hc/youpro/what-features-are-included-in-youpro/) for more details.
|
||||
|
||||
### LLaMA C/C++ Port (Free, Local)
|
||||
|
||||
> **Note**: Currently supported only on Linux and MacOS.
|
||||
|
||||
The main goal of `llama.cpp` is to run the LLaMA model using 4-bit integer quantization on a MacBook.
|
||||
|
||||
#### Getting Started
|
||||
|
||||
1. **Select the Model**: Depending on your hardware capabilities, choose the appropriate model from the provided list. Once selected, click on the `Download Model` link. A progress bar will appear, indicating the download process.
|
||||
|
||||
2. **Start the Server**: After successfully downloading the model, initiate the server by clicking on the `Start Server` button. A status message will be displayed, indicating that the server is starting up.
|
||||
|
||||
3. **Apply Settings**: With the server running, you can now apply the settings to start using the features. Click on the `Apply/OK` button to save your settings and start using the application.
|
||||
|
||||
<img alt="animated" style="max-width: 100%; width: 600px;" src="https://github.com/carlrobertoh/CodeGPT-docs/blob/main/images/llama-settings.png?raw=true" />
|
||||
|
||||
> **Note**: If you're already running a server and wish to configure the plugin against that, then simply select the port and click `Apply/OK`.
|
||||
|
||||
### Running locally
|
||||
## Running locally
|
||||
|
||||
**Linux or macOS**
|
||||
```shell
|
||||
|
|
@ -173,9 +104,19 @@ git submodule update
|
|||
tail -f build/idea-sandbox/system/log/idea.log
|
||||
```
|
||||
|
||||
## Issues
|
||||
## Privacy
|
||||
|
||||
See the [open issues][open-issues] for a full list of proposed features (and known issues).
|
||||
**Your data stays yours.** CodeGPT **does not** collect or store any kind of sensitive information.
|
||||
|
||||
However, with users' consent, we do collect anonymous usage data, which we use to understand how users interact with the extension, including the most-used features and preferred providers.
|
||||
|
||||
## Feedback
|
||||
|
||||
Your input helps us grow. Reach out through:
|
||||
|
||||
- [Issue Tracker](https://github.com/carlrobertoh/CodeGPT/issues)
|
||||
- [Discord](https://discord.gg/8dTGGrwcnR)
|
||||
- [Email](mailto:carlrobertoh@gmail.com)
|
||||
|
||||
## License
|
||||
|
||||
|
|
@ -184,7 +125,6 @@ Apache 2.0 © [Carl-Robert Linnupuu][portfolio]
|
|||
If you found this project interesting, kindly rate it on the marketplace and don't forget to give it a star. Thanks again!
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
|
||||
<!-- MARKDOWN LINKS & IMAGES -->
|
||||
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
|
||||
|
||||
|
|
|
|||
|
|
@ -23,12 +23,16 @@ fun environment(key: String) = providers.environmentVariable(key)
|
|||
|
||||
plugins {
|
||||
id("codegpt.java-conventions")
|
||||
id("org.jetbrains.changelog") version "2.2.0"
|
||||
alias(libs.plugins.changelog)
|
||||
}
|
||||
|
||||
group = properties("pluginGroup").get()
|
||||
version = properties("pluginVersion").get() + "-" + properties("pluginSinceBuild").get()
|
||||
|
||||
checkstyle {
|
||||
toolVersion = libs.versions.checkstyle.get()
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
|
|
@ -50,17 +54,16 @@ dependencies {
|
|||
implementation(project(":codegpt-telemetry"))
|
||||
implementation(project(":codegpt-treesitter"))
|
||||
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.16.1")
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.16.2")
|
||||
implementation("com.vladsch.flexmark:flexmark-all:0.64.8") {
|
||||
implementation(enforcedPlatform(libs.jackson.bom))
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8")
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
|
||||
implementation(libs.flexmark.all) {
|
||||
// vulnerable transitive dependency
|
||||
exclude(group = "org.jsoup", module = "jsoup")
|
||||
}
|
||||
implementation("org.jsoup:jsoup:1.17.2")
|
||||
implementation("org.apache.commons:commons-text:1.11.0")
|
||||
implementation("com.knuddels:jtokkit:1.0.0")
|
||||
|
||||
testImplementation("org.awaitility:awaitility:4.2.0")
|
||||
implementation(libs.jsoup)
|
||||
implementation(libs.commons.text)
|
||||
implementation(libs.jtokkit)
|
||||
}
|
||||
|
||||
tasks.register<Exec>("updateSubmodules") {
|
||||
|
|
@ -156,4 +159,4 @@ tasks {
|
|||
showStandardStreams = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,6 @@ repositories {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.intellij.plugins", "gradle-intellij-plugin", "1.17.2")
|
||||
implementation("org.jetbrains.kotlin", "kotlin-gradle-plugin", "1.9.22")
|
||||
implementation(libs.gradle.intellij.plugin)
|
||||
implementation(libs.kotlin.gradle.plugin)
|
||||
}
|
||||
|
|
|
|||
7
buildSrc/settings.gradle.kts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
create("libs") {
|
||||
from(files("../gradle/libs.versions.toml")) // Allow references
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,17 +22,18 @@ intellij {
|
|||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "10.12.5"
|
||||
toolVersion = "10.15.0"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("ee.carlrobert:llm-client:0.6.2")
|
||||
implementation("ee.carlrobert:llm-client:0.7.0")
|
||||
|
||||
testImplementation(enforcedPlatform("org.junit:junit-bom:5.10.2"))
|
||||
testImplementation("org.assertj:assertj-core:3.25.3")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.2")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.2")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.2")
|
||||
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.10.2")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-params")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
|
||||
testRuntimeOnly("org.junit.vintage:junit-vintage-engine")
|
||||
}
|
||||
|
||||
tasks {
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@ plugins {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.rudderstack.sdk.java.analytics:analytics:3.0.0")
|
||||
}
|
||||
implementation(libs.analytics)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ plugins {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation("io.github.bonede:tree-sitter:0.21.0")
|
||||
implementation(libs.tree.sitter)
|
||||
implementation("io.github.bonede:tree-sitter-erlang:0.1.0")
|
||||
implementation("io.github.bonede:tree-sitter-elixir:0.1.1")
|
||||
implementation("io.github.bonede:tree-sitter-dockerfile:0.1.2")
|
||||
|
|
@ -37,4 +37,4 @@ dependencies {
|
|||
implementation("io.github.bonede:tree-sitter-php:0.20.0")
|
||||
implementation("io.github.bonede:tree-sitter-typescript:0.20.3")
|
||||
implementation("io.github.bonede:tree-sitter-query:0.1.0")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ pluginGroup = ee.carlrobert
|
|||
pluginName = CodeGPT
|
||||
pluginRepositoryUrl = https://github.com/carlrobertoh/CodeGPT
|
||||
# SemVer format -> https://semver.org
|
||||
pluginVersion = 2.5.1
|
||||
pluginVersion = 2.6.0
|
||||
|
||||
# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
|
||||
pluginSinceBuild = 241
|
||||
|
|
@ -21,7 +21,7 @@ platformPlugins =
|
|||
javaVersion = 17
|
||||
|
||||
# Gradle Releases -> https://github.com/gradle/gradle/releases
|
||||
gradleVersion = 8.5
|
||||
gradleVersion = 8.7
|
||||
|
||||
# Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib
|
||||
kotlin.stdlib.default.dependency = false
|
||||
|
|
@ -38,4 +38,4 @@ org.gradle.caching = true
|
|||
systemProp.org.gradle.unsafe.kotlin.assignment = true
|
||||
|
||||
# Temporary workaround for Kotlin Compiler OutOfMemoryError -> https://jb.gg/intellij-platform-kotlin-oom
|
||||
kotlin.incremental.useClasspathSnapshot = false
|
||||
kotlin.incremental.useClasspathSnapshot = false
|
||||
|
|
|
|||
29
gradle/libs.versions.toml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
[versions]
|
||||
analytics = "3.0.0"
|
||||
changelog = "2.2.0"
|
||||
checkstyle = "10.15.0"
|
||||
commons-text = "1.11.0"
|
||||
flexmark = "0.64.8"
|
||||
gradle-intellij-plugin-version = "1.17.3"
|
||||
jackson = "2.17.0"
|
||||
jsoup = "1.17.2"
|
||||
jtokkit = "1.0.0"
|
||||
junit = "5.10.2"
|
||||
kotlin = "1.9.23"
|
||||
llm-client = "0.7.0"
|
||||
tree-sitter = "0.22.2"
|
||||
|
||||
[libraries]
|
||||
analytics = { module = "com.rudderstack.sdk.java.analytics:analytics", version.ref = "analytics" }
|
||||
commons-text = { module = "org.apache.commons:commons-text", version.ref = "commons-text" }
|
||||
flexmark-all = { module = "com.vladsch.flexmark:flexmark-all", version.ref = "flexmark" }
|
||||
gradle-intellij-plugin = { module = "org.jetbrains.intellij.plugins:gradle-intellij-plugin", version.ref = "gradle-intellij-plugin-version" }
|
||||
jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" }
|
||||
jtokkit = { module = "com.knuddels:jtokkit", version.ref = "jtokkit" }
|
||||
jackson-bom = { module = "com.fasterxml.jackson:jackson-bom", version.ref = "jackson" }
|
||||
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||
llm-client = { module = "ee.carlrobert:llm-client", version.ref = "llm-client" }
|
||||
tree-sitter = { module = "io.github.bonede:tree-sitter", version.ref = "tree-sitter" }
|
||||
|
||||
[plugins]
|
||||
changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" }
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,6 +1,6 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
|
|||
14
gradlew
vendored
|
|
@ -145,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
|
|
@ -153,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
|
|
@ -202,11 +202,11 @@ fi
|
|||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
|
|
|
|||
|
|
@ -9,4 +9,6 @@ public class CodeGPTKeys {
|
|||
Key.create("codegpt.editor.inlay.prev-value");
|
||||
public static final Key<List<ReferencedFile>> SELECTED_FILES =
|
||||
Key.create("codegpt.selectedFiles");
|
||||
public static final Key<String> IMAGE_ATTACHMENT_FILE_PATH =
|
||||
Key.create("codegpt.imageAttachmentFilePath");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ import com.knuddels.jtokkit.api.EncodingRegistry;
|
|||
import com.knuddels.jtokkit.api.EncodingType;
|
||||
import com.knuddels.jtokkit.api.IntArrayList;
|
||||
import ee.carlrobert.codegpt.conversations.Conversation;
|
||||
import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionDetailedMessage;
|
||||
import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionMessage;
|
||||
import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionStandardMessage;
|
||||
import ee.carlrobert.llm.client.openai.completion.request.OpenAIMessageTextContent;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
|
|
@ -38,7 +41,16 @@ public final class EncodingManager {
|
|||
}
|
||||
|
||||
public int countMessageTokens(OpenAIChatCompletionMessage message) {
|
||||
return countMessageTokens(message.getRole(), message.getContent());
|
||||
if (message instanceof OpenAIChatCompletionStandardMessage standardMessage) {
|
||||
return countMessageTokens(standardMessage.getRole(), standardMessage.getContent());
|
||||
}
|
||||
|
||||
return ((OpenAIChatCompletionDetailedMessage) message).getContent().stream()
|
||||
.filter(it -> it instanceof OpenAIMessageTextContent)
|
||||
.map(it -> countMessageTokens(
|
||||
((OpenAIChatCompletionDetailedMessage) message).getRole(),
|
||||
((OpenAIMessageTextContent) it).getText()))
|
||||
.reduce(0, Integer::sum);
|
||||
}
|
||||
|
||||
public int countMessageTokens(String role, String content) {
|
||||
|
|
|
|||
|
|
@ -17,4 +17,5 @@ public final class Icons {
|
|||
public static final Icon You = IconLoader.getIcon("/icons/you.svg", Icons.class);
|
||||
public static final Icon YouSmall = IconLoader.getIcon("/icons/you_small.png", Icons.class);
|
||||
public static final Icon User = IconLoader.getIcon("/icons/user.svg", Icons.class);
|
||||
public static final Icon Upload = IconLoader.getIcon("/icons/upload.svg", Icons.class);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import com.intellij.openapi.project.Project;
|
|||
import ee.carlrobert.codegpt.completions.CompletionRequestProvider;
|
||||
import ee.carlrobert.codegpt.conversations.message.Message;
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowContentManager;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowContentManager;
|
||||
import ee.carlrobert.codegpt.ui.OverlayUtil;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -50,10 +50,10 @@ public class ProjectCompilationStatusListener implements CompilationStatusListen
|
|||
NotificationType.INFORMATION)
|
||||
.addAction(NotificationAction.createSimpleExpiring(
|
||||
CodeGPTBundle.get("notification.compilationError.okLabel"),
|
||||
() -> project.getService(StandardChatToolWindowContentManager.class)
|
||||
() -> project.getService(ChatToolWindowContentManager.class)
|
||||
.sendMessage(getMultiFileMessage(compileContext), FIX_COMPILE_ERRORS)))
|
||||
.addAction(NotificationAction.createSimpleExpiring(
|
||||
CodeGPTBundle.get("checkForUpdatesTask.notification.hideButton"),
|
||||
CodeGPTBundle.get("shared.notification.doNotShowAgain"),
|
||||
() -> ConfigurationSettings.getCurrentState().setCaptureCompileErrors(false)))
|
||||
.notify(project);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
package ee.carlrobert.codegpt.actions;
|
||||
|
||||
import com.intellij.util.messages.Topic;
|
||||
import com.intellij.util.messages.Topic.BroadcastDirection;
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationState;
|
||||
import java.util.EventListener;
|
||||
|
||||
/**
|
||||
* {@link EventListener} for changes of {@link ConfigurationState#isCodeCompletionsEnabled()}.
|
||||
*
|
||||
* @see EnableCompletionsAction
|
||||
* @see DisableCompletionsAction
|
||||
*/
|
||||
public interface CodeCompletionEnabledListener extends EventListener {
|
||||
|
||||
/**
|
||||
* Topic for subscribing to {@link ConfigurationState#isCodeCompletionsEnabled()} changes.<br/>
|
||||
* Broadcasts from Application-Level to all projects.
|
||||
*/
|
||||
@Topic.AppLevel
|
||||
Topic<CodeCompletionEnabledListener> TOPIC = new Topic<>(CodeCompletionEnabledListener.class,
|
||||
BroadcastDirection.TO_DIRECT_CHILDREN);
|
||||
|
||||
void onCodeCompletionsEnabledChange(boolean codeCompletionsEnabled);
|
||||
}
|
||||
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
package ee.carlrobert.codegpt.actions;
|
||||
|
||||
import static ee.carlrobert.codegpt.settings.service.ServiceType.LLAMA_CPP;
|
||||
import static ee.carlrobert.codegpt.settings.service.ServiceType.OPENAI;
|
||||
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings;
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings;
|
||||
import java.util.List;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Disables code-completion.<br/> Publishes message to {@link CodeCompletionEnabledListener#TOPIC}
|
||||
*/
|
||||
public class DisableCompletionsAction extends AnAction {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
ConfigurationSettings.getCurrentState().setCodeCompletionsEnabled(false);
|
||||
ApplicationManager.getApplication()
|
||||
.getMessageBus().syncPublisher(CodeCompletionEnabledListener.TOPIC)
|
||||
.onCodeCompletionsEnabledChange(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull AnActionEvent e) {
|
||||
var selectedService = GeneralSettings.getCurrentState().getSelectedService();
|
||||
var codeCompletionEnabled = ConfigurationSettings.getCurrentState().isCodeCompletionsEnabled();
|
||||
e.getPresentation().setEnabled(codeCompletionEnabled);
|
||||
e.getPresentation()
|
||||
.setVisible(codeCompletionEnabled && List.of(OPENAI, LLAMA_CPP).contains(selectedService));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ActionUpdateThread getActionUpdateThread() {
|
||||
return ActionUpdateThread.BGT;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
package ee.carlrobert.codegpt.actions;
|
||||
|
||||
import static ee.carlrobert.codegpt.settings.service.ServiceType.LLAMA_CPP;
|
||||
import static ee.carlrobert.codegpt.settings.service.ServiceType.OPENAI;
|
||||
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings;
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings;
|
||||
import java.util.List;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Enables code-completion.<br/> Publishes message to {@link CodeCompletionEnabledListener#TOPIC}
|
||||
*/
|
||||
public class EnableCompletionsAction extends AnAction {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
ConfigurationSettings.getCurrentState().setCodeCompletionsEnabled(true);
|
||||
ApplicationManager.getApplication()
|
||||
.getMessageBus().syncPublisher(CodeCompletionEnabledListener.TOPIC)
|
||||
.onCodeCompletionsEnabledChange(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull AnActionEvent e) {
|
||||
var selectedService = GeneralSettings.getCurrentState().getSelectedService();
|
||||
var codeCompletionEnabled = ConfigurationSettings.getCurrentState().isCodeCompletionsEnabled();
|
||||
e.getPresentation().setEnabled(!codeCompletionEnabled);
|
||||
e.getPresentation()
|
||||
.setVisible(!codeCompletionEnabled && List.of(OPENAI, LLAMA_CPP).contains(selectedService));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ActionUpdateThread getActionUpdateThread() {
|
||||
return ActionUpdateThread.BGT;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ import static com.intellij.openapi.ui.Messages.OK;
|
|||
import static com.intellij.util.ObjectUtils.tryCast;
|
||||
import static ee.carlrobert.codegpt.settings.service.ServiceType.YOU;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import com.intellij.notification.Notification;
|
||||
import com.intellij.notification.NotificationType;
|
||||
|
|
@ -20,11 +19,8 @@ import com.intellij.openapi.editor.ex.EditorEx;
|
|||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.vcs.FilePath;
|
||||
import com.intellij.openapi.vcs.VcsDataKeys;
|
||||
import com.intellij.openapi.vcs.changes.Change;
|
||||
import com.intellij.openapi.vcs.changes.ui.ChangesBrowserBase;
|
||||
import com.intellij.openapi.vcs.changes.ui.CommitDialogChangesBrowser;
|
||||
import com.intellij.openapi.vcs.ui.CommitMessage;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.vcs.commit.CommitWorkflowUi;
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle;
|
||||
import ee.carlrobert.codegpt.EncodingManager;
|
||||
import ee.carlrobert.codegpt.Icons;
|
||||
|
|
@ -37,11 +33,13 @@ import java.io.BufferedReader;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import okhttp3.sse.EventSource;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
|
@ -61,19 +59,17 @@ public class GenerateGitCommitMessageAction extends AnAction {
|
|||
|
||||
@Override
|
||||
public void update(@NotNull AnActionEvent event) {
|
||||
var commitWorkflowUi = event.getData(VcsDataKeys.COMMIT_WORKFLOW_UI);
|
||||
var selectedService = GeneralSettings.getCurrentState().getSelectedService();
|
||||
if (selectedService == YOU) {
|
||||
if (selectedService == YOU || commitWorkflowUi == null) {
|
||||
event.getPresentation().setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var includedChangesFilePaths = getIncludedChangesFilePaths(event);
|
||||
var includedUnversionedChangesFilePaths = getIncludedUnversionedFilePaths(event);
|
||||
var filesSelected =
|
||||
!includedChangesFilePaths.isEmpty() || !includedUnversionedChangesFilePaths.isEmpty();
|
||||
var callAllowed = CompletionRequestService.isRequestAllowed(
|
||||
GeneralSettings.getCurrentState().getSelectedService());
|
||||
event.getPresentation().setEnabled(callAllowed && filesSelected);
|
||||
event.getPresentation().setEnabled(callAllowed
|
||||
&& new CommitWorkflowChanges(commitWorkflowUi).isFilesSelected());
|
||||
event.getPresentation().setText(CodeGPTBundle.get(callAllowed
|
||||
? "action.generateCommitMessage.title"
|
||||
: "action.generateCommitMessage.missingCredentials"));
|
||||
|
|
@ -86,11 +82,7 @@ public class GenerateGitCommitMessageAction extends AnAction {
|
|||
return;
|
||||
}
|
||||
|
||||
var gitDiff = getGitDiff(
|
||||
project,
|
||||
getIncludedChangesFilePaths(event),
|
||||
getIncludedUnversionedFilePaths(event));
|
||||
|
||||
var gitDiff = getGitDiff(event, project);
|
||||
var tokenCount = encodingManager.countTokens(gitDiff);
|
||||
if (tokenCount > MAX_TOKEN_COUNT_WARNING
|
||||
&& OverlayUtil.showTokenSoftLimitWarningDialog(tokenCount) != OK) {
|
||||
|
|
@ -142,21 +134,48 @@ public class GenerateGitCommitMessageAction extends AnAction {
|
|||
return commitMessage != null ? commitMessage.getEditorField().getEditor() : null;
|
||||
}
|
||||
|
||||
private String getGitDiff(
|
||||
Project project,
|
||||
List<String> includedChangesFilePaths,
|
||||
List<String> includedUnversionedFilePaths) {
|
||||
private String getGitDiff(AnActionEvent event, Project project) {
|
||||
var commitWorkflowUi = Optional.ofNullable(event.getData(VcsDataKeys.COMMIT_WORKFLOW_UI))
|
||||
.orElseThrow(() -> new IllegalStateException("Could not retrieve commit workflow ui."));
|
||||
var changes = new CommitWorkflowChanges(commitWorkflowUi);
|
||||
var projectBasePath = project.getBasePath();
|
||||
var gitDiff = getGitDiff(projectBasePath, changes.getIncludedVersionedFilePaths(), false);
|
||||
var stagedGitDiff = getGitDiff(projectBasePath, changes.getIncludedVersionedFilePaths(), true);
|
||||
var newFilesContent =
|
||||
getNewFilesDiff(projectBasePath, changes.getIncludedUnversionedFilePaths());
|
||||
|
||||
return Stream.of(
|
||||
new AbstractMap.SimpleEntry<>(includedChangesFilePaths, true),
|
||||
new AbstractMap.SimpleEntry<>(includedUnversionedFilePaths, false))
|
||||
.filter(entry -> !entry.getKey().isEmpty())
|
||||
.map(entry -> {
|
||||
var process =
|
||||
createGitDiffProcess(project.getBasePath(), entry.getKey(), entry.getValue());
|
||||
return new BufferedReader(new InputStreamReader(process.getInputStream()))
|
||||
.lines()
|
||||
.collect(joining("\n"));
|
||||
new AbstractMap.SimpleEntry<>("Git diff", gitDiff),
|
||||
new AbstractMap.SimpleEntry<>("Staged git diff", stagedGitDiff),
|
||||
new AbstractMap.SimpleEntry<>("New files", newFilesContent))
|
||||
.filter(entry -> !entry.getValue().isEmpty())
|
||||
.map(entry -> "%s:\n%s".formatted(entry.getKey(), entry.getValue()))
|
||||
.collect(joining("\n\n"));
|
||||
}
|
||||
|
||||
private String getGitDiff(String projectPath, List<String> filePaths, boolean cached) {
|
||||
if (filePaths.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
var process = createGitDiffProcess(projectPath, filePaths, cached);
|
||||
return new BufferedReader(new InputStreamReader(process.getInputStream()))
|
||||
.lines()
|
||||
.collect(joining("\n"));
|
||||
}
|
||||
|
||||
private String getNewFilesDiff(String projectPath, List<String> filePaths) {
|
||||
return filePaths.stream()
|
||||
.map(pathString -> {
|
||||
var filePath = Path.of(pathString);
|
||||
var relativePath = Path.of(projectPath).relativize(filePath);
|
||||
try {
|
||||
return "New file '" + relativePath + "' content:\n" + Files.readString(filePath);
|
||||
} catch (IOException ignored) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(joining("\n"));
|
||||
}
|
||||
|
||||
|
|
@ -178,29 +197,31 @@ public class GenerateGitCommitMessageAction extends AnAction {
|
|||
}
|
||||
}
|
||||
|
||||
private @NotNull List<String> getFilePaths(
|
||||
AnActionEvent event,
|
||||
Function<CommitDialogChangesBrowser, Stream<?>> extractor) {
|
||||
var changesBrowserBase = event.getData(ChangesBrowserBase.DATA_KEY);
|
||||
if (changesBrowserBase == null) {
|
||||
return List.of();
|
||||
static class CommitWorkflowChanges {
|
||||
|
||||
private final List<String> includedVersionedFilePaths;
|
||||
private final List<String> includedUnversionedFilePaths;
|
||||
|
||||
CommitWorkflowChanges(CommitWorkflowUi commitWorkflowUi) {
|
||||
includedVersionedFilePaths = commitWorkflowUi.getIncludedChanges().stream()
|
||||
.map(it -> it.getVirtualFile() == null ? null : it.getVirtualFile().getPath())
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
includedUnversionedFilePaths = commitWorkflowUi.getIncludedUnversionedFiles().stream()
|
||||
.map(FilePath::getPath)
|
||||
.toList();
|
||||
}
|
||||
|
||||
return extractor.apply((CommitDialogChangesBrowser) changesBrowserBase)
|
||||
.map(obj -> obj instanceof Change
|
||||
? ((Change) obj).getVirtualFile()
|
||||
: ((FilePath) obj).getVirtualFile())
|
||||
.filter(Objects::nonNull)
|
||||
.map(VirtualFile::getPath)
|
||||
.distinct()
|
||||
.collect(toList());
|
||||
}
|
||||
public List<String> getIncludedVersionedFilePaths() {
|
||||
return includedVersionedFilePaths;
|
||||
}
|
||||
|
||||
private @NotNull List<String> getIncludedChangesFilePaths(AnActionEvent event) {
|
||||
return getFilePaths(event, browser -> browser.getIncludedChanges().stream());
|
||||
}
|
||||
public List<String> getIncludedUnversionedFilePaths() {
|
||||
return includedUnversionedFilePaths;
|
||||
}
|
||||
|
||||
private @NotNull List<String> getIncludedUnversionedFilePaths(AnActionEvent event) {
|
||||
return getFilePaths(event, browser -> browser.getIncludedUnversionedFiles().stream());
|
||||
public boolean isFilesSelected() {
|
||||
return !includedVersionedFilePaths.isEmpty() || !includedUnversionedFilePaths.isEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import com.intellij.openapi.actionSystem.AnAction;
|
|||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import ee.carlrobert.codegpt.Icons;
|
||||
import ee.carlrobert.codegpt.conversations.ConversationsState;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowContentManager;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowContentManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class AskAction extends AnAction {
|
||||
|
|
@ -26,7 +26,7 @@ public class AskAction extends AnAction {
|
|||
if (project != null) {
|
||||
ConversationsState.getInstance().setCurrentConversation(null);
|
||||
var tabPanel =
|
||||
project.getService(StandardChatToolWindowContentManager.class).createNewTabPanel();
|
||||
project.getService(ChatToolWindowContentManager.class).createNewTabPanel();
|
||||
if (tabPanel != null) {
|
||||
tabPanel.displayLandingView();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import com.intellij.util.ui.FormBuilder;
|
|||
import com.intellij.util.ui.JBUI;
|
||||
import com.intellij.util.ui.UI;
|
||||
import ee.carlrobert.codegpt.conversations.message.Message;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowContentManager;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowContentManager;
|
||||
import ee.carlrobert.codegpt.ui.UIUtil;
|
||||
import ee.carlrobert.codegpt.util.file.FileUtil;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
|
@ -42,7 +42,7 @@ public class CustomPromptAction extends BaseEditorAction {
|
|||
format("%s\n```%s\n%s\n```", previousUserPrompt, fileExtension, selectedText));
|
||||
message.setUserMessage(previousUserPrompt);
|
||||
SwingUtilities.invokeLater(() ->
|
||||
project.getService(StandardChatToolWindowContentManager.class).sendMessage(message));
|
||||
project.getService(ChatToolWindowContentManager.class).sendMessage(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import ee.carlrobert.codegpt.CodeGPTKeys;
|
|||
import ee.carlrobert.codegpt.ReferencedFile;
|
||||
import ee.carlrobert.codegpt.conversations.message.Message;
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowContentManager;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowContentManager;
|
||||
import ee.carlrobert.codegpt.util.file.FileUtil;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
|
|
@ -65,7 +65,7 @@ public class EditorActionsUtil {
|
|||
format("\n```%s\n%s\n```", fileExtension, selectedText)));
|
||||
message.setUserMessage(prompt.replace("{{selectedCode}}", ""));
|
||||
var toolWindowContentManager =
|
||||
project.getService(StandardChatToolWindowContentManager.class);
|
||||
project.getService(ChatToolWindowContentManager.class);
|
||||
toolWindowContentManager.getToolWindow().show();
|
||||
|
||||
message.setReferencedFilePaths(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import ee.carlrobert.codegpt.actions.ActionType;
|
|||
import ee.carlrobert.codegpt.actions.editor.EditorActionsUtil;
|
||||
import ee.carlrobert.codegpt.conversations.ConversationService;
|
||||
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowContentManager;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowContentManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class DeleteAllConversationsAction extends AnAction {
|
||||
|
|
@ -44,7 +44,7 @@ public class DeleteAllConversationsAction extends AnAction {
|
|||
if (project != null) {
|
||||
try {
|
||||
ConversationService.getInstance().clearAll();
|
||||
project.getService(StandardChatToolWindowContentManager.class).resetAll();
|
||||
project.getService(ChatToolWindowContentManager.class).resetAll();
|
||||
} finally {
|
||||
TelemetryAction.IDE_ACTION.createActionMessage()
|
||||
.property("action", ActionType.DELETE_ALL_CONVERSATIONS.name())
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package ee.carlrobert.codegpt.completions;
|
|||
|
||||
import ee.carlrobert.codegpt.conversations.Conversation;
|
||||
import ee.carlrobert.codegpt.conversations.message.Message;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class CallParameters {
|
||||
|
||||
|
|
@ -9,6 +10,8 @@ public class CallParameters {
|
|||
private final ConversationType conversationType;
|
||||
private final Message message;
|
||||
private final boolean retry;
|
||||
private @Nullable String imageMediaType;
|
||||
private byte[] imageData;
|
||||
|
||||
public CallParameters(Conversation conversation, Message message) {
|
||||
this(conversation, ConversationType.DEFAULT, message, false);
|
||||
|
|
@ -40,4 +43,20 @@ public class CallParameters {
|
|||
public boolean isRetry() {
|
||||
return retry;
|
||||
}
|
||||
|
||||
public String getImageMediaType() {
|
||||
return imageMediaType;
|
||||
}
|
||||
|
||||
public void setImageMediaType(String imageMediaType) {
|
||||
this.imageMediaType = imageMediaType;
|
||||
}
|
||||
|
||||
public byte[] getImageData() {
|
||||
return imageData;
|
||||
}
|
||||
|
||||
public void setImageData(byte[] imageData) {
|
||||
this.imageData = imageData;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,15 +30,29 @@ import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings;
|
|||
import ee.carlrobert.codegpt.settings.service.you.YouSettings;
|
||||
import ee.carlrobert.codegpt.telemetry.core.configuration.TelemetryConfiguration;
|
||||
import ee.carlrobert.codegpt.telemetry.core.service.UserId;
|
||||
import ee.carlrobert.codegpt.util.file.FileUtil;
|
||||
import ee.carlrobert.llm.client.anthropic.completion.ClaudeBase64Source;
|
||||
import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionDetailedMessage;
|
||||
import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionMessage;
|
||||
import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionRequest;
|
||||
import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionRequestMessage;
|
||||
import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionStandardMessage;
|
||||
import ee.carlrobert.llm.client.anthropic.completion.ClaudeMessageImageContent;
|
||||
import ee.carlrobert.llm.client.anthropic.completion.ClaudeMessageTextContent;
|
||||
import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest;
|
||||
import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionModel;
|
||||
import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionDetailedMessage;
|
||||
import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionMessage;
|
||||
import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionRequest;
|
||||
import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionStandardMessage;
|
||||
import ee.carlrobert.llm.client.openai.completion.request.OpenAIImageUrl;
|
||||
import ee.carlrobert.llm.client.openai.completion.request.OpenAIMessageImageURLContent;
|
||||
import ee.carlrobert.llm.client.openai.completion.request.OpenAIMessageTextContent;
|
||||
import ee.carlrobert.llm.client.you.completion.YouCompletionRequest;
|
||||
import ee.carlrobert.llm.client.you.completion.YouCompletionRequestMessage;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -91,9 +105,10 @@ public class CompletionRequestProvider {
|
|||
public static OpenAIChatCompletionRequest buildOpenAILookupCompletionRequest(String context) {
|
||||
return new OpenAIChatCompletionRequest.Builder(
|
||||
List.of(
|
||||
new OpenAIChatCompletionMessage("system",
|
||||
new OpenAIChatCompletionStandardMessage(
|
||||
"system",
|
||||
getResourceContent("/prompts/method-name-generator.txt")),
|
||||
new OpenAIChatCompletionMessage("user", context)))
|
||||
new OpenAIChatCompletionStandardMessage("user", context)))
|
||||
.setModel(OpenAISettings.getCurrentState().getModel())
|
||||
.setStream(false)
|
||||
.build();
|
||||
|
|
@ -103,8 +118,8 @@ public class CompletionRequestProvider {
|
|||
return buildCustomOpenAIChatCompletionRequest(
|
||||
CustomServiceSettings.getCurrentState(),
|
||||
List.of(
|
||||
new OpenAIChatCompletionMessage("system", system),
|
||||
new OpenAIChatCompletionMessage("user", context)),
|
||||
new OpenAIChatCompletionStandardMessage("system", system),
|
||||
new OpenAIChatCompletionStandardMessage("user", context)),
|
||||
true);
|
||||
}
|
||||
|
||||
|
|
@ -112,10 +127,10 @@ public class CompletionRequestProvider {
|
|||
return buildCustomOpenAIChatCompletionRequest(
|
||||
CustomServiceSettings.getCurrentState(),
|
||||
List.of(
|
||||
new OpenAIChatCompletionMessage(
|
||||
new OpenAIChatCompletionStandardMessage(
|
||||
"system",
|
||||
getResourceContent("/prompts/method-name-generator.txt")),
|
||||
new OpenAIChatCompletionMessage("user", context)),
|
||||
new OpenAIChatCompletionStandardMessage("user", context)),
|
||||
false);
|
||||
}
|
||||
|
||||
|
|
@ -201,10 +216,10 @@ public class CompletionRequestProvider {
|
|||
List<OpenAIChatCompletionMessage> messages,
|
||||
boolean streamRequest) {
|
||||
var requestBuilder = new Request.Builder().url(customConfiguration.getUrl().trim());
|
||||
var credential = CredentialsStore.INSTANCE.getCredential(CUSTOM_SERVICE_API_KEY);
|
||||
for (var entry : customConfiguration.getHeaders().entrySet()) {
|
||||
String value = entry.getValue();
|
||||
var credential = CredentialsStore.INSTANCE.getCredential(CUSTOM_SERVICE_API_KEY);
|
||||
if (value.contains("$CUSTOM_SERVICE_API_KEY") && credential != null) {
|
||||
if (credential != null && value.contains("$CUSTOM_SERVICE_API_KEY")) {
|
||||
value = value.replace("$CUSTOM_SERVICE_API_KEY", credential);
|
||||
}
|
||||
requestBuilder.addHeader(entry.getKey(), value);
|
||||
|
|
@ -246,15 +261,25 @@ public class CompletionRequestProvider {
|
|||
request.setMaxTokens(configuration.getMaxTokens());
|
||||
request.setStream(true);
|
||||
request.setSystem(COMPLETION_SYSTEM_PROMPT);
|
||||
var messages = conversation.getMessages().stream()
|
||||
List<ClaudeCompletionMessage> messages = conversation.getMessages().stream()
|
||||
.filter(prevMessage -> prevMessage.getResponse() != null
|
||||
&& !prevMessage.getResponse().isEmpty())
|
||||
.flatMap(prevMessage -> Stream.of(
|
||||
new ClaudeCompletionRequestMessage("user", prevMessage.getPrompt()),
|
||||
new ClaudeCompletionRequestMessage("assistant", prevMessage.getResponse())))
|
||||
new ClaudeCompletionStandardMessage("user", prevMessage.getPrompt()),
|
||||
new ClaudeCompletionStandardMessage("assistant", prevMessage.getResponse())))
|
||||
.collect(toList());
|
||||
messages.add(
|
||||
new ClaudeCompletionRequestMessage("user", callParameters.getMessage().getPrompt()));
|
||||
|
||||
if (callParameters.getImageMediaType() != null && callParameters.getImageData().length > 0) {
|
||||
messages.add(new ClaudeCompletionDetailedMessage("user",
|
||||
List.of(
|
||||
new ClaudeMessageImageContent(new ClaudeBase64Source(
|
||||
callParameters.getImageMediaType(),
|
||||
callParameters.getImageData())),
|
||||
new ClaudeMessageTextContent(callParameters.getMessage().getPrompt()))));
|
||||
} else {
|
||||
messages.add(
|
||||
new ClaudeCompletionStandardMessage("user", callParameters.getMessage().getPrompt()));
|
||||
}
|
||||
request.setMessages(messages);
|
||||
return request;
|
||||
}
|
||||
|
|
@ -263,22 +288,48 @@ public class CompletionRequestProvider {
|
|||
var message = callParameters.getMessage();
|
||||
var messages = new ArrayList<OpenAIChatCompletionMessage>();
|
||||
if (callParameters.getConversationType() == ConversationType.DEFAULT) {
|
||||
messages.add(new OpenAIChatCompletionMessage(
|
||||
messages.add(new OpenAIChatCompletionStandardMessage(
|
||||
"system",
|
||||
ConfigurationSettings.getCurrentState().getSystemPrompt()));
|
||||
}
|
||||
if (callParameters.getConversationType() == ConversationType.FIX_COMPILE_ERRORS) {
|
||||
messages.add(new OpenAIChatCompletionMessage("system", FIX_COMPILE_ERRORS_SYSTEM_PROMPT));
|
||||
messages.add(
|
||||
new OpenAIChatCompletionStandardMessage("system", FIX_COMPILE_ERRORS_SYSTEM_PROMPT));
|
||||
}
|
||||
|
||||
for (var prevMessage : conversation.getMessages()) {
|
||||
if (callParameters.isRetry() && prevMessage.getId().equals(message.getId())) {
|
||||
break;
|
||||
}
|
||||
messages.add(new OpenAIChatCompletionMessage("user", prevMessage.getPrompt()));
|
||||
messages.add(new OpenAIChatCompletionMessage("assistant", prevMessage.getResponse()));
|
||||
var prevMessageImageFilePath = prevMessage.getImageFilePath();
|
||||
if (prevMessageImageFilePath != null && !prevMessageImageFilePath.isEmpty()) {
|
||||
try {
|
||||
var imageFilePath = Path.of(prevMessageImageFilePath);
|
||||
var imageData = Files.readAllBytes(imageFilePath);
|
||||
var imageMediaType = FileUtil.getImageMediaType(imageFilePath.getFileName().toString());
|
||||
messages.add(new OpenAIChatCompletionDetailedMessage("user",
|
||||
List.of(
|
||||
new OpenAIMessageImageURLContent(new OpenAIImageUrl(imageMediaType, imageData)),
|
||||
new OpenAIMessageTextContent(prevMessage.getPrompt()))));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
messages.add(new OpenAIChatCompletionStandardMessage("user", prevMessage.getPrompt()));
|
||||
}
|
||||
messages.add(new OpenAIChatCompletionStandardMessage("assistant", prevMessage.getResponse()));
|
||||
}
|
||||
|
||||
if (callParameters.getImageMediaType() != null && callParameters.getImageData().length > 0) {
|
||||
messages.add(new OpenAIChatCompletionDetailedMessage("user",
|
||||
List.of(
|
||||
new OpenAIMessageImageURLContent(
|
||||
new OpenAIImageUrl(callParameters.getImageMediaType(),
|
||||
callParameters.getImageData())),
|
||||
new OpenAIMessageTextContent(message.getPrompt()))));
|
||||
} else {
|
||||
messages.add(new OpenAIChatCompletionStandardMessage("user", message.getPrompt()));
|
||||
}
|
||||
messages.add(new OpenAIChatCompletionMessage("user", message.getPrompt()));
|
||||
return messages;
|
||||
}
|
||||
|
||||
|
|
@ -324,8 +375,11 @@ public class CompletionRequestProvider {
|
|||
break;
|
||||
}
|
||||
|
||||
totalUsage -= encodingManager.countMessageTokens(messages.get(i));
|
||||
messages.set(i, null);
|
||||
var message = messages.get(i);
|
||||
if (message instanceof OpenAIChatCompletionStandardMessage) {
|
||||
totalUsage -= encodingManager.countMessageTokens(message);
|
||||
messages.set(i, null);
|
||||
}
|
||||
}
|
||||
|
||||
return messages.stream().filter(Objects::nonNull).collect(toList());
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
|
|||
import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings;
|
||||
import ee.carlrobert.llm.client.DeserializationUtil;
|
||||
import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionRequest;
|
||||
import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionRequestMessage;
|
||||
import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionStandardMessage;
|
||||
import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest;
|
||||
import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionEventSourceListener;
|
||||
import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionMessage;
|
||||
import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionRequest;
|
||||
import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionStandardMessage;
|
||||
import ee.carlrobert.llm.client.openai.completion.response.OpenAIChatCompletionResponse;
|
||||
import ee.carlrobert.llm.completion.CompletionEventListener;
|
||||
import java.io.IOException;
|
||||
|
|
@ -117,8 +117,8 @@ public final class CompletionRequestService {
|
|||
var configuration = ConfigurationSettings.getCurrentState();
|
||||
var commitMessagePrompt = configuration.getCommitMessagePrompt();
|
||||
var openaiRequest = new OpenAIChatCompletionRequest.Builder(List.of(
|
||||
new OpenAIChatCompletionMessage("system", commitMessagePrompt),
|
||||
new OpenAIChatCompletionMessage("user", prompt)))
|
||||
new OpenAIChatCompletionStandardMessage("system", commitMessagePrompt),
|
||||
new OpenAIChatCompletionStandardMessage("user", prompt)))
|
||||
.setModel(OpenAISettings.getCurrentState().getModel())
|
||||
.build();
|
||||
var selectedService = GeneralSettings.getCurrentState().getSelectedService();
|
||||
|
|
@ -142,8 +142,7 @@ public final class CompletionRequestService {
|
|||
claudeRequest.setStream(true);
|
||||
claudeRequest.setMaxTokens(configuration.getMaxTokens());
|
||||
claudeRequest.setModel(anthropicSettings.getModel());
|
||||
claudeRequest.setMessages(
|
||||
List.of(new ClaudeCompletionRequestMessage("user", prompt)));
|
||||
claudeRequest.setMessages(List.of(new ClaudeCompletionStandardMessage("user", prompt)));
|
||||
CompletionClientProvider.getClaudeClient()
|
||||
.getCompletionAsync(claudeRequest, eventListener);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ public final class YouAuthenticationService {
|
|||
|
||||
if (response.code() == 200) {
|
||||
try {
|
||||
var messageBus = ApplicationManager.getApplication().getMessageBus();
|
||||
var userManager = YouUserManager.getInstance();
|
||||
|
||||
var authenticationResponse =
|
||||
|
|
@ -72,6 +71,7 @@ public final class YouAuthenticationService {
|
|||
YouApiClient.getInstance().getSubscription(authenticationResponse);
|
||||
var subscribed = subscription != null && "youpro".equals(subscription.getService());
|
||||
userManager.setSubscribed(subscribed);
|
||||
var messageBus = ApplicationManager.getApplication().getMessageBus();
|
||||
if (subscribed) {
|
||||
messageBus.syncPublisher(YouSubscriptionNotifier.SUBSCRIPTION_TOPIC).subscribed();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import ee.carlrobert.llm.client.you.completion.YouSerpResult;
|
|||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class Message {
|
||||
|
||||
|
|
@ -15,6 +16,7 @@ public class Message {
|
|||
private String userMessage;
|
||||
private List<YouSerpResult> serpResults;
|
||||
private List<String> referencedFilePaths;
|
||||
private @Nullable String imageFilePath;
|
||||
|
||||
public Message(String prompt, String response) {
|
||||
this(prompt);
|
||||
|
|
@ -71,6 +73,14 @@ public class Message {
|
|||
this.referencedFilePaths = referencedFilePaths;
|
||||
}
|
||||
|
||||
public @Nullable String getImageFilePath() {
|
||||
return imageFilePath;
|
||||
}
|
||||
|
||||
public void setImageFilePath(@Nullable String imageFilePath) {
|
||||
this.imageFilePath = imageFilePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import ee.carlrobert.codegpt.settings.service.openai.OpenAISettingsForm;
|
|||
import ee.carlrobert.codegpt.settings.service.you.YouSettings;
|
||||
import ee.carlrobert.codegpt.settings.service.you.YouSettingsForm;
|
||||
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowContentManager;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowContentManager;
|
||||
import ee.carlrobert.codegpt.util.ApplicationUtil;
|
||||
import javax.swing.JComponent;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
|
|
@ -160,6 +160,6 @@ public class GeneralSettingsConfigurable implements Configurable {
|
|||
throw new RuntimeException("Could not find current project.");
|
||||
}
|
||||
|
||||
project.getService(StandardChatToolWindowContentManager.class).resetAll();
|
||||
project.getService(ChatToolWindowContentManager.class).resetAll();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ public class ConfigurationComponent {
|
|||
private final JPanel mainPanel;
|
||||
private final JBTable table;
|
||||
private final JBCheckBox checkForPluginUpdatesCheckBox;
|
||||
private final JBCheckBox checkForNewScreenshotsCheckBox;
|
||||
private final JBCheckBox openNewTabCheckBox;
|
||||
private final JBCheckBox methodNameGenerationCheckBox;
|
||||
private final JBCheckBox autoFormattingCheckBox;
|
||||
|
|
@ -111,6 +112,9 @@ public class ConfigurationComponent {
|
|||
|
||||
checkForPluginUpdatesCheckBox = new JBCheckBox(
|
||||
CodeGPTBundle.get("configurationConfigurable.checkForPluginUpdates.label"),
|
||||
configuration.isCheckForNewScreenshots());
|
||||
checkForNewScreenshotsCheckBox = new JBCheckBox(
|
||||
CodeGPTBundle.get("configurationConfigurable.checkForNewScreenshots.label"),
|
||||
configuration.isCheckForPluginUpdates());
|
||||
openNewTabCheckBox = new JBCheckBox(
|
||||
CodeGPTBundle.get("configurationConfigurable.openNewTabCheckBox.label"),
|
||||
|
|
@ -126,6 +130,7 @@ public class ConfigurationComponent {
|
|||
.addComponent(tablePanel)
|
||||
.addVerticalGap(4)
|
||||
.addComponent(checkForPluginUpdatesCheckBox)
|
||||
.addComponent(checkForNewScreenshotsCheckBox)
|
||||
.addComponent(openNewTabCheckBox)
|
||||
.addComponent(methodNameGenerationCheckBox)
|
||||
.addComponent(autoFormattingCheckBox)
|
||||
|
|
@ -152,11 +157,10 @@ public class ConfigurationComponent {
|
|||
state.setSystemPrompt(systemPromptTextArea.getText());
|
||||
state.setCommitMessagePrompt(commitMessagePromptTextArea.getText());
|
||||
state.setCheckForPluginUpdates(checkForPluginUpdatesCheckBox.isSelected());
|
||||
state.setCheckForNewScreenshots(checkForNewScreenshotsCheckBox.isSelected());
|
||||
state.setCreateNewChatOnEachAction(openNewTabCheckBox.isSelected());
|
||||
state.setMethodNameGenerationEnabled(methodNameGenerationCheckBox.isSelected());
|
||||
state.setAutoFormattingEnabled(autoFormattingCheckBox.isSelected());
|
||||
state.setCodeCompletionsEnabled(
|
||||
ConfigurationSettings.getCurrentState().isCodeCompletionsEnabled());
|
||||
return state;
|
||||
}
|
||||
|
||||
|
|
@ -168,6 +172,7 @@ public class ConfigurationComponent {
|
|||
systemPromptTextArea.setText(configuration.getSystemPrompt());
|
||||
commitMessagePromptTextArea.setText(configuration.getCommitMessagePrompt());
|
||||
checkForPluginUpdatesCheckBox.setSelected(configuration.isCheckForPluginUpdates());
|
||||
checkForNewScreenshotsCheckBox.setSelected(configuration.isCheckForNewScreenshots());
|
||||
openNewTabCheckBox.setSelected(configuration.isCreateNewChatOnEachAction());
|
||||
methodNameGenerationCheckBox.setSelected(configuration.isMethodNameGenerationEnabled());
|
||||
autoFormattingCheckBox.setSelected(configuration.isAutoFormattingEnabled());
|
||||
|
|
|
|||
|
|
@ -14,12 +14,12 @@ public class ConfigurationState {
|
|||
private int maxTokens = 1000;
|
||||
private double temperature = 0.1;
|
||||
private boolean checkForPluginUpdates = true;
|
||||
private boolean checkForNewScreenshots = true;
|
||||
private boolean createNewChatOnEachAction;
|
||||
private boolean ignoreGitCommitTokenLimit;
|
||||
private boolean methodNameGenerationEnabled = true;
|
||||
private boolean captureCompileErrors = true;
|
||||
private boolean autoFormattingEnabled = true;
|
||||
private boolean codeCompletionsEnabled;
|
||||
private Map<String, String> tableData = EditorActionsUtil.DEFAULT_ACTIONS;
|
||||
|
||||
public String getSystemPrompt() {
|
||||
|
|
@ -62,6 +62,14 @@ public class ConfigurationState {
|
|||
this.createNewChatOnEachAction = createNewChatOnEachAction;
|
||||
}
|
||||
|
||||
public boolean isCheckForNewScreenshots() {
|
||||
return checkForNewScreenshots;
|
||||
}
|
||||
|
||||
public void setCheckForNewScreenshots(boolean checkForNewScreenshots) {
|
||||
this.checkForNewScreenshots = checkForNewScreenshots;
|
||||
}
|
||||
|
||||
public Map<String, String> getTableData() {
|
||||
return tableData;
|
||||
}
|
||||
|
|
@ -110,14 +118,6 @@ public class ConfigurationState {
|
|||
this.autoFormattingEnabled = autoFormattingEnabled;
|
||||
}
|
||||
|
||||
public boolean isCodeCompletionsEnabled() {
|
||||
return codeCompletionsEnabled;
|
||||
}
|
||||
|
||||
public void setCodeCompletionsEnabled(boolean codeCompletionsEnabled) {
|
||||
this.codeCompletionsEnabled = codeCompletionsEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
|
|
@ -135,7 +135,6 @@ public class ConfigurationState {
|
|||
&& methodNameGenerationEnabled == that.methodNameGenerationEnabled
|
||||
&& captureCompileErrors == that.captureCompileErrors
|
||||
&& autoFormattingEnabled == that.autoFormattingEnabled
|
||||
&& codeCompletionsEnabled == that.codeCompletionsEnabled
|
||||
&& Objects.equals(systemPrompt, that.systemPrompt)
|
||||
&& Objects.equals(commitMessagePrompt, that.commitMessagePrompt)
|
||||
&& Objects.equals(tableData, that.tableData);
|
||||
|
|
@ -145,7 +144,6 @@ public class ConfigurationState {
|
|||
public int hashCode() {
|
||||
return Objects.hash(systemPrompt, commitMessagePrompt, maxTokens, temperature,
|
||||
checkForPluginUpdates, createNewChatOnEachAction, ignoreGitCommitTokenLimit,
|
||||
methodNameGenerationEnabled, captureCompileErrors, autoFormattingEnabled,
|
||||
codeCompletionsEnabled, tableData);
|
||||
methodNameGenerationEnabled, captureCompileErrors, autoFormattingEnabled, tableData);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ public class LlamaSettingsState {
|
|||
private double topP = 0.9;
|
||||
private double minP = 0.05;
|
||||
private double repeatPenalty = 1.1;
|
||||
private boolean codeCompletionsEnabled = true;
|
||||
private int codeCompletionMaxTokens = 128;
|
||||
|
||||
public boolean isUseCustomModel() {
|
||||
return useCustomModel;
|
||||
|
|
@ -168,6 +170,22 @@ public class LlamaSettingsState {
|
|||
this.repeatPenalty = repeatPenalty;
|
||||
}
|
||||
|
||||
public boolean isCodeCompletionsEnabled() {
|
||||
return codeCompletionsEnabled;
|
||||
}
|
||||
|
||||
public void setCodeCompletionsEnabled(boolean codeCompletionsEnabled) {
|
||||
this.codeCompletionsEnabled = codeCompletionsEnabled;
|
||||
}
|
||||
|
||||
public int getCodeCompletionMaxTokens() {
|
||||
return codeCompletionMaxTokens;
|
||||
}
|
||||
|
||||
public void setCodeCompletionMaxTokens(int codeCompletionMaxTokens) {
|
||||
this.codeCompletionMaxTokens = codeCompletionMaxTokens;
|
||||
}
|
||||
|
||||
private static Integer getRandomAvailablePortOrDefault() {
|
||||
try (ServerSocket socket = new ServerSocket(0)) {
|
||||
return socket.getLocalPort();
|
||||
|
|
@ -201,7 +219,9 @@ public class LlamaSettingsState {
|
|||
&& remoteModelInfillPromptTemplate == that.remoteModelInfillPromptTemplate
|
||||
&& Objects.equals(baseHost, that.baseHost)
|
||||
&& Objects.equals(serverPort, that.serverPort)
|
||||
&& Objects.equals(additionalParameters, that.additionalParameters);
|
||||
&& Objects.equals(additionalParameters, that.additionalParameters)
|
||||
&& codeCompletionsEnabled == that.codeCompletionsEnabled
|
||||
&& codeCompletionMaxTokens == that.codeCompletionMaxTokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -209,6 +229,7 @@ public class LlamaSettingsState {
|
|||
return Objects.hash(runLocalServer, useCustomModel, customLlamaModelPath, huggingFaceModel,
|
||||
localModelPromptTemplate, remoteModelPromptTemplate, localModelInfillPromptTemplate,
|
||||
remoteModelInfillPromptTemplate, baseHost, serverPort, contextSize, threads,
|
||||
additionalParameters, topK, topP, minP, repeatPenalty);
|
||||
additionalParameters, topK, topP, minP, repeatPenalty, codeCompletionsEnabled,
|
||||
codeCompletionMaxTokens);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import static ee.carlrobert.codegpt.ui.UIUtil.withEmptyLeftBorder;
|
|||
import com.intellij.ui.TitledSeparator;
|
||||
import com.intellij.util.ui.FormBuilder;
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle;
|
||||
import ee.carlrobert.codegpt.settings.service.CodeCompletionConfigurationForm;
|
||||
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
|
||||
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettingsState;
|
||||
import java.awt.BorderLayout;
|
||||
|
|
@ -14,10 +15,14 @@ public class LlamaSettingsForm extends JPanel {
|
|||
|
||||
private final LlamaServerPreferencesForm llamaServerPreferencesForm;
|
||||
private final LlamaRequestPreferencesForm llamaRequestPreferencesForm;
|
||||
private final CodeCompletionConfigurationForm codeCompletionConfigurationForm;
|
||||
|
||||
public LlamaSettingsForm(LlamaSettingsState settings) {
|
||||
llamaServerPreferencesForm = new LlamaServerPreferencesForm(settings);
|
||||
llamaRequestPreferencesForm = new LlamaRequestPreferencesForm(settings);
|
||||
codeCompletionConfigurationForm = new CodeCompletionConfigurationForm(
|
||||
settings.isCodeCompletionsEnabled(),
|
||||
settings.getCodeCompletionMaxTokens());
|
||||
init();
|
||||
}
|
||||
|
||||
|
|
@ -44,6 +49,8 @@ public class LlamaSettingsForm extends JPanel {
|
|||
state.setLocalModelPromptTemplate(modelPreferencesForm.getPromptTemplate());
|
||||
state.setLocalModelInfillPromptTemplate(modelPreferencesForm.getInfillPromptTemplate());
|
||||
|
||||
state.setCodeCompletionsEnabled(codeCompletionConfigurationForm.isCodeCompletionsEnabled());
|
||||
state.setCodeCompletionMaxTokens(codeCompletionConfigurationForm.getMaxTokens());
|
||||
return state;
|
||||
}
|
||||
|
||||
|
|
@ -51,6 +58,8 @@ public class LlamaSettingsForm extends JPanel {
|
|||
var state = LlamaSettings.getCurrentState();
|
||||
llamaServerPreferencesForm.resetForm(state);
|
||||
llamaRequestPreferencesForm.resetForm(state);
|
||||
codeCompletionConfigurationForm.setCodeCompletionsEnabled(state.isCodeCompletionsEnabled());
|
||||
codeCompletionConfigurationForm.setMaxTokens(state.getCodeCompletionMaxTokens());
|
||||
}
|
||||
|
||||
public LlamaServerPreferencesForm getLlamaServerPreferencesForm() {
|
||||
|
|
@ -60,6 +69,8 @@ public class LlamaSettingsForm extends JPanel {
|
|||
private void init() {
|
||||
setLayout(new BorderLayout());
|
||||
add(FormBuilder.createFormBuilder()
|
||||
.addComponent(new TitledSeparator("Code Completions"))
|
||||
.addComponent(withEmptyLeftBorder(codeCompletionConfigurationForm.getForm()))
|
||||
.addComponent(new TitledSeparator(
|
||||
CodeGPTBundle.get("settingsConfigurable.service.llama.serverPreferences.title")))
|
||||
.addComponent(llamaServerPreferencesForm.getForm())
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.intellij.openapi.components.PersistentStateComponent;
|
|||
import com.intellij.openapi.components.State;
|
||||
import com.intellij.openapi.components.Storage;
|
||||
import ee.carlrobert.codegpt.credentials.CredentialsStore;
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.intellij.util.ui.FormBuilder;
|
|||
import com.intellij.util.ui.UI;
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle;
|
||||
import ee.carlrobert.codegpt.credentials.CredentialsStore;
|
||||
import ee.carlrobert.codegpt.settings.service.CodeCompletionConfigurationForm;
|
||||
import ee.carlrobert.codegpt.ui.UIUtil;
|
||||
import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionModel;
|
||||
import javax.swing.JPanel;
|
||||
|
|
@ -22,6 +23,7 @@ public class OpenAISettingsForm {
|
|||
private final JBPasswordField apiKeyField;
|
||||
private final JBTextField organizationField;
|
||||
private final ComboBox<OpenAIChatCompletionModel> completionModelComboBox;
|
||||
private final CodeCompletionConfigurationForm codeCompletionConfigurationForm;
|
||||
|
||||
public OpenAISettingsForm(OpenAISettingsState settings) {
|
||||
apiKeyField = new JBPasswordField();
|
||||
|
|
@ -32,6 +34,9 @@ public class OpenAISettingsForm {
|
|||
new EnumComboBoxModel<>(OpenAIChatCompletionModel.class));
|
||||
completionModelComboBox.setSelectedItem(
|
||||
OpenAIChatCompletionModel.findByCode(settings.getModel()));
|
||||
codeCompletionConfigurationForm = new CodeCompletionConfigurationForm(
|
||||
settings.isCodeCompletionsEnabled(),
|
||||
settings.getCodeCompletionMaxTokens());
|
||||
}
|
||||
|
||||
public JPanel getForm() {
|
||||
|
|
@ -52,6 +57,8 @@ public class OpenAISettingsForm {
|
|||
.createPanel();
|
||||
|
||||
return FormBuilder.createFormBuilder()
|
||||
.addComponent(new TitledSeparator(CodeGPTBundle.get("shared.codeCompletions")))
|
||||
.addComponent(withEmptyLeftBorder(codeCompletionConfigurationForm.getForm()))
|
||||
.addComponent(new TitledSeparator(CodeGPTBundle.get("shared.configuration")))
|
||||
.addComponent(withEmptyLeftBorder(configurationGrid))
|
||||
.addComponentFillVertically(new JPanel(), 0)
|
||||
|
|
@ -73,6 +80,8 @@ public class OpenAISettingsForm {
|
|||
var state = new OpenAISettingsState();
|
||||
state.setModel(getModel());
|
||||
state.setOrganization(organizationField.getText());
|
||||
state.setCodeCompletionsEnabled(codeCompletionConfigurationForm.isCodeCompletionsEnabled());
|
||||
state.setCodeCompletionMaxTokens(codeCompletionConfigurationForm.getMaxTokens());
|
||||
return state;
|
||||
}
|
||||
|
||||
|
|
@ -82,5 +91,7 @@ public class OpenAISettingsForm {
|
|||
completionModelComboBox.setSelectedItem(
|
||||
OpenAIChatCompletionModel.findByCode(state.getModel()));
|
||||
organizationField.setText(state.getOrganization());
|
||||
codeCompletionConfigurationForm.setCodeCompletionsEnabled(state.isCodeCompletionsEnabled());
|
||||
codeCompletionConfigurationForm.setMaxTokens(state.getCodeCompletionMaxTokens());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ public class OpenAISettingsState {
|
|||
|
||||
private String organization = "";
|
||||
private String model = OpenAIChatCompletionModel.GPT_3_5_0125_16k.getCode();
|
||||
private boolean codeCompletionsEnabled = true;
|
||||
private int codeCompletionMaxTokens = 128;
|
||||
|
||||
public String getOrganization() {
|
||||
return organization;
|
||||
|
|
@ -24,6 +26,22 @@ public class OpenAISettingsState {
|
|||
this.model = model;
|
||||
}
|
||||
|
||||
public boolean isCodeCompletionsEnabled() {
|
||||
return codeCompletionsEnabled;
|
||||
}
|
||||
|
||||
public void setCodeCompletionsEnabled(boolean codeCompletionsEnabled) {
|
||||
this.codeCompletionsEnabled = codeCompletionsEnabled;
|
||||
}
|
||||
|
||||
public int getCodeCompletionMaxTokens() {
|
||||
return codeCompletionMaxTokens;
|
||||
}
|
||||
|
||||
public void setCodeCompletionMaxTokens(int codeCompletionMaxTokens) {
|
||||
this.codeCompletionMaxTokens = codeCompletionMaxTokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
|
|
@ -33,11 +51,14 @@ public class OpenAISettingsState {
|
|||
return false;
|
||||
}
|
||||
OpenAISettingsState that = (OpenAISettingsState) o;
|
||||
return Objects.equals(organization, that.organization) && Objects.equals(model, that.model);
|
||||
return Objects.equals(organization, that.organization)
|
||||
&& Objects.equals(model, that.model)
|
||||
&& codeCompletionsEnabled == that.codeCompletionsEnabled
|
||||
&& codeCompletionMaxTokens == that.codeCompletionMaxTokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(organization, model);
|
||||
return Objects.hash(organization, model, codeCompletionsEnabled, codeCompletionMaxTokens);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import com.intellij.openapi.wm.ToolWindow;
|
|||
import com.intellij.openapi.wm.ToolWindowFactory;
|
||||
import com.intellij.ui.content.ContentManagerEvent;
|
||||
import com.intellij.ui.content.ContentManagerListener;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowPanel;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowPanel;
|
||||
import ee.carlrobert.codegpt.toolwindow.conversations.ConversationsToolWindow;
|
||||
import javax.swing.JComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
|
@ -14,7 +14,7 @@ import org.jetbrains.annotations.NotNull;
|
|||
public class ProjectToolWindowFactory implements ToolWindowFactory, DumbAware {
|
||||
|
||||
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
|
||||
var chatToolWindowPanel = new StandardChatToolWindowPanel(project, toolWindow.getDisposable());
|
||||
var chatToolWindowPanel = new ChatToolWindowPanel(project, toolWindow.getDisposable());
|
||||
var conversationsToolWindow = new ConversationsToolWindow(project);
|
||||
|
||||
addContent(toolWindow, chatToolWindowPanel, "Chat");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.standard;
|
||||
package ee.carlrobert.codegpt.toolwindow.chat;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
|
|
@ -22,11 +22,11 @@ import java.util.Optional;
|
|||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Service(Service.Level.PROJECT)
|
||||
public final class StandardChatToolWindowContentManager {
|
||||
public final class ChatToolWindowContentManager {
|
||||
|
||||
private final Project project;
|
||||
|
||||
public StandardChatToolWindowContentManager(Project project) {
|
||||
public ChatToolWindowContentManager(Project project) {
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
|
|
@ -55,15 +55,14 @@ public final class StandardChatToolWindowContentManager {
|
|||
.ifPresent(tabbedPane -> tabbedPane.tryFindTabTitle(conversation.getId())
|
||||
.ifPresentOrElse(
|
||||
title -> tabbedPane.setSelectedIndex(tabbedPane.indexOfTab(title)),
|
||||
() -> tabbedPane.addNewTab(
|
||||
new StandardChatToolWindowTabPanel(project, conversation))));
|
||||
() -> tabbedPane.addNewTab(new ChatToolWindowTabPanel(project, conversation))));
|
||||
}
|
||||
|
||||
public StandardChatToolWindowTabPanel createNewTabPanel() {
|
||||
public ChatToolWindowTabPanel createNewTabPanel() {
|
||||
displayChatTab();
|
||||
return tryFindChatTabbedPane()
|
||||
.map(item -> {
|
||||
var panel = new StandardChatToolWindowTabPanel(
|
||||
var panel = new ChatToolWindowTabPanel(
|
||||
project,
|
||||
ConversationService.getInstance().startConversation());
|
||||
item.addNewTab(panel);
|
||||
|
|
@ -83,26 +82,26 @@ public final class StandardChatToolWindowContentManager {
|
|||
);
|
||||
}
|
||||
|
||||
public Optional<StandardChatToolWindowTabbedPane> tryFindChatTabbedPane() {
|
||||
public Optional<ChatToolWindowTabbedPane> tryFindChatTabbedPane() {
|
||||
var chatTabContent = tryFindFirstChatTabContent();
|
||||
if (chatTabContent.isPresent()) {
|
||||
var chatToolWindowPanel = (StandardChatToolWindowPanel) chatTabContent.get().getComponent();
|
||||
var chatToolWindowPanel = (ChatToolWindowPanel) chatTabContent.get().getComponent();
|
||||
return Optional.of(chatToolWindowPanel.getChatTabbedPane());
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<StandardChatToolWindowPanel> tryFindChatToolWindowPanel() {
|
||||
public Optional<ChatToolWindowPanel> tryFindChatToolWindowPanel() {
|
||||
return tryFindFirstChatTabContent()
|
||||
.map(ComponentContainer::getComponent)
|
||||
.filter(component -> component instanceof StandardChatToolWindowPanel)
|
||||
.map(component -> (StandardChatToolWindowPanel) component);
|
||||
.filter(component -> component instanceof ChatToolWindowPanel)
|
||||
.map(component -> (ChatToolWindowPanel) component);
|
||||
}
|
||||
|
||||
public void resetAll() {
|
||||
tryFindChatTabbedPane().ifPresent(tabbedPane -> {
|
||||
tabbedPane.clearAll();
|
||||
tabbedPane.addNewTab(new StandardChatToolWindowTabPanel(
|
||||
tabbedPane.addNewTab(new ChatToolWindowTabPanel(
|
||||
project,
|
||||
ConversationService.getInstance().startConversation()));
|
||||
});
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.actionSystem.ActionManager;
|
||||
import com.intellij.openapi.actionSystem.ActionToolbar;
|
||||
import com.intellij.openapi.actionSystem.DefaultCompactActionGroup;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.SimpleToolWindowPanel;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import ee.carlrobert.codegpt.CodeGPTKeys;
|
||||
import ee.carlrobert.codegpt.ReferencedFile;
|
||||
import ee.carlrobert.codegpt.actions.IncludeFilesInContextNotifier;
|
||||
import ee.carlrobert.codegpt.actions.toolwindow.ClearChatWindowAction;
|
||||
import ee.carlrobert.codegpt.actions.toolwindow.CreateNewConversationAction;
|
||||
import ee.carlrobert.codegpt.actions.toolwindow.OpenInEditorAction;
|
||||
import ee.carlrobert.codegpt.conversations.ConversationService;
|
||||
import ee.carlrobert.codegpt.conversations.ConversationsState;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.ToolWindowFooterNotification;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.AttachImageNotifier;
|
||||
import java.awt.BorderLayout;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JPanel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ChatToolWindowPanel extends SimpleToolWindowPanel {
|
||||
|
||||
private final ToolWindowFooterNotification selectedFilesNotification;
|
||||
private final ToolWindowFooterNotification imageFileAttachmentNotification;
|
||||
private ChatToolWindowTabbedPane tabbedPane;
|
||||
|
||||
public ChatToolWindowPanel(
|
||||
@NotNull Project project,
|
||||
@NotNull Disposable parentDisposable) {
|
||||
super(true);
|
||||
selectedFilesNotification = new ToolWindowFooterNotification(
|
||||
() -> clearSelectedFilesNotification(project));
|
||||
imageFileAttachmentNotification = new ToolWindowFooterNotification(() ->
|
||||
project.putUserData(CodeGPTKeys.IMAGE_ATTACHMENT_FILE_PATH, ""));
|
||||
init(project, parentDisposable);
|
||||
|
||||
project.getMessageBus()
|
||||
.connect()
|
||||
.subscribe(IncludeFilesInContextNotifier.FILES_INCLUDED_IN_CONTEXT_TOPIC,
|
||||
(IncludeFilesInContextNotifier) this::displaySelectedFilesNotification);
|
||||
project.getMessageBus()
|
||||
.connect()
|
||||
.subscribe(AttachImageNotifier.IMAGE_ATTACHMENT_FILE_PATH_TOPIC,
|
||||
(AttachImageNotifier) filePath -> imageFileAttachmentNotification.show(
|
||||
Path.of(filePath).getFileName().toString(),
|
||||
"File path: " + filePath));
|
||||
}
|
||||
|
||||
public ChatToolWindowTabbedPane getChatTabbedPane() {
|
||||
return tabbedPane;
|
||||
}
|
||||
|
||||
public void displaySelectedFilesNotification(List<ReferencedFile> referencedFiles) {
|
||||
if (referencedFiles.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var referencedFilePaths = referencedFiles.stream()
|
||||
.map(ReferencedFile::getFilePath)
|
||||
.collect(Collectors.toList());
|
||||
selectedFilesNotification.show(
|
||||
referencedFiles.size() + " files selected",
|
||||
selectedFilesNotificationDescription(referencedFilePaths));
|
||||
}
|
||||
|
||||
private String selectedFilesNotificationDescription(List<String> referencedFilePaths) {
|
||||
var html = referencedFilePaths.stream()
|
||||
.map(filePath -> format("<li>%s</li>", Paths.get(filePath).getFileName().toString()))
|
||||
.collect(Collectors.joining());
|
||||
return format("<ul style=\"margin: 4px 12px;\">%s</ul>", html);
|
||||
}
|
||||
|
||||
public void clearNotifications(Project project) {
|
||||
selectedFilesNotification.hideNotification();
|
||||
imageFileAttachmentNotification.hideNotification();
|
||||
|
||||
project.putUserData(CodeGPTKeys.IMAGE_ATTACHMENT_FILE_PATH, "");
|
||||
project.putUserData(CodeGPTKeys.SELECTED_FILES, emptyList());
|
||||
}
|
||||
|
||||
private void init(Project project, Disposable parentDisposable) {
|
||||
var conversation = ConversationsState.getCurrentConversation();
|
||||
if (conversation == null) {
|
||||
conversation = ConversationService.getInstance().startConversation();
|
||||
}
|
||||
|
||||
var tabPanel = new ChatToolWindowTabPanel(project, conversation);
|
||||
tabbedPane = createTabbedPane(tabPanel, parentDisposable);
|
||||
Runnable onAddNewTab = () -> {
|
||||
tabbedPane.addNewTab(new ChatToolWindowTabPanel(
|
||||
project,
|
||||
ConversationService.getInstance().startConversation()));
|
||||
repaint();
|
||||
revalidate();
|
||||
};
|
||||
var actionToolbarPanel = new JPanel(new BorderLayout());
|
||||
actionToolbarPanel.add(
|
||||
createActionToolbar(project, tabbedPane, onAddNewTab).getComponent(),
|
||||
BorderLayout.LINE_START);
|
||||
|
||||
setToolbar(actionToolbarPanel);
|
||||
var notificationContainer = new JPanel(new BorderLayout());
|
||||
notificationContainer.setLayout(new BoxLayout(notificationContainer, BoxLayout.PAGE_AXIS));
|
||||
notificationContainer.add(selectedFilesNotification);
|
||||
notificationContainer.add(imageFileAttachmentNotification);
|
||||
setContent(JBUI.Panels.simplePanel(tabbedPane).addToBottom(notificationContainer));
|
||||
|
||||
Disposer.register(parentDisposable, tabPanel);
|
||||
}
|
||||
|
||||
private ActionToolbar createActionToolbar(
|
||||
Project project,
|
||||
ChatToolWindowTabbedPane tabbedPane,
|
||||
Runnable onAddNewTab) {
|
||||
var actionGroup = new DefaultCompactActionGroup("TOOLBAR_ACTION_GROUP", false);
|
||||
actionGroup.add(new CreateNewConversationAction(onAddNewTab));
|
||||
actionGroup.add(
|
||||
new ClearChatWindowAction(() -> tabbedPane.resetCurrentlyActiveTabPanel(project)));
|
||||
actionGroup.addSeparator();
|
||||
actionGroup.add(new OpenInEditorAction());
|
||||
|
||||
var toolbar = ActionManager.getInstance()
|
||||
.createActionToolbar("NAVIGATION_BAR_TOOLBAR", actionGroup, true);
|
||||
toolbar.setTargetComponent(this);
|
||||
return toolbar;
|
||||
}
|
||||
|
||||
private ChatToolWindowTabbedPane createTabbedPane(
|
||||
ChatToolWindowTabPanel tabPanel,
|
||||
Disposable parentDisposable) {
|
||||
var tabbedPane = new ChatToolWindowTabbedPane(parentDisposable);
|
||||
tabbedPane.addNewTab(tabPanel);
|
||||
return tabbedPane;
|
||||
}
|
||||
|
||||
public void clearSelectedFilesNotification(Project project) {
|
||||
project.putUserData(CodeGPTKeys.SELECTED_FILES, emptyList());
|
||||
project.getMessageBus()
|
||||
.syncPublisher(IncludeFilesInContextNotifier.FILES_INCLUDED_IN_CONTEXT_TOPIC)
|
||||
.filesIncluded(emptyList());
|
||||
}
|
||||
}
|
||||
|
|
@ -6,12 +6,13 @@ import static java.lang.String.format;
|
|||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.actionSystem.ActionPlaces;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.impl.EditorImpl;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import com.intellij.util.ui.JBUI.Borders;
|
||||
import ee.carlrobert.codegpt.CodeGPTKeys;
|
||||
import ee.carlrobert.codegpt.EncodingManager;
|
||||
import ee.carlrobert.codegpt.ReferencedFile;
|
||||
|
|
@ -25,47 +26,47 @@ import ee.carlrobert.codegpt.conversations.ConversationService;
|
|||
import ee.carlrobert.codegpt.conversations.message.Message;
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings;
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType;
|
||||
import ee.carlrobert.codegpt.settings.service.you.YouSettings;
|
||||
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowContentManager;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowPanel;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.ChatMessageResponseBody;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.ChatToolWindowScrollablePanel;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.ResponsePanel;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.UserMessagePanel;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.ModelComboBoxAction;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.TotalTokensDetails;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.TotalTokensPanel;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.UserPromptTextArea;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.UserPromptTextAreaHeader;
|
||||
import ee.carlrobert.codegpt.toolwindow.ui.ChatToolWindowLandingPanel;
|
||||
import ee.carlrobert.codegpt.ui.OverlayUtil;
|
||||
import ee.carlrobert.codegpt.util.EditorUtil;
|
||||
import ee.carlrobert.codegpt.util.file.FileUtil;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.UUID;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingUtilities;
|
||||
import kotlin.Unit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public abstract class ChatToolWindowTabPanel implements Disposable {
|
||||
public class ChatToolWindowTabPanel implements Disposable {
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(ChatToolWindowTabPanel.class);
|
||||
|
||||
private final Project project;
|
||||
private final JPanel rootPanel;
|
||||
private final Conversation conversation;
|
||||
private final UserPromptTextArea userPromptTextArea;
|
||||
private final ConversationService conversationService;
|
||||
private final TotalTokensPanel totalTokensPanel;
|
||||
private final ChatToolWindowScrollablePanel toolWindowScrollablePanel;
|
||||
|
||||
protected final Project project;
|
||||
protected final TotalTokensPanel totalTokensPanel;
|
||||
protected final ChatToolWindowScrollablePanel toolWindowScrollablePanel;
|
||||
|
||||
protected abstract JComponent getLandingView();
|
||||
|
||||
public ChatToolWindowTabPanel(
|
||||
@NotNull Project project,
|
||||
@NotNull Conversation conversation,
|
||||
boolean useContextualSearch) {
|
||||
public ChatToolWindowTabPanel(@NotNull Project project, @NotNull Conversation conversation) {
|
||||
this.project = project;
|
||||
this.conversation = conversation;
|
||||
conversationService = ConversationService.getInstance();
|
||||
|
|
@ -79,6 +80,12 @@ public abstract class ChatToolWindowTabPanel implements Disposable {
|
|||
rootPanel = createRootPanel();
|
||||
userPromptTextArea.requestFocusInWindow();
|
||||
userPromptTextArea.requestFocus();
|
||||
|
||||
if (conversation.getMessages().isEmpty()) {
|
||||
displayLandingView();
|
||||
} else {
|
||||
displayConversation(conversation);
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
|
|
@ -93,58 +100,6 @@ public abstract class ChatToolWindowTabPanel implements Disposable {
|
|||
return conversation;
|
||||
}
|
||||
|
||||
public void sendMessage(Message message) {
|
||||
sendMessage(message, ConversationType.DEFAULT);
|
||||
}
|
||||
|
||||
public void sendMessage(Message message, ConversationType conversationType) {
|
||||
Runnable runnable = () -> {
|
||||
var referencedFiles = project.getUserData(CodeGPTKeys.SELECTED_FILES);
|
||||
if (referencedFiles != null && !referencedFiles.isEmpty()) {
|
||||
var referencedFilePaths = referencedFiles.stream()
|
||||
.map(ReferencedFile::getFilePath)
|
||||
.collect(toList());
|
||||
message.setReferencedFilePaths(referencedFilePaths);
|
||||
message.setUserMessage(message.getPrompt());
|
||||
message.setPrompt(getPromptWithContext(referencedFiles, message.getPrompt()));
|
||||
|
||||
totalTokensPanel.updateReferencedFilesTokens(referencedFiles);
|
||||
|
||||
project.getService(StandardChatToolWindowContentManager.class)
|
||||
.tryFindChatToolWindowPanel()
|
||||
.ifPresent(StandardChatToolWindowPanel::clearSelectedFilesNotification);
|
||||
}
|
||||
|
||||
var messagePanel = toolWindowScrollablePanel.addMessage(message.getId());
|
||||
messagePanel.add(new UserMessagePanel(project, message, this));
|
||||
var responsePanel = createResponsePanel(message, conversationType);
|
||||
messagePanel.add(responsePanel);
|
||||
|
||||
updateTotalTokens(message);
|
||||
|
||||
call(message, conversationType, responsePanel, false);
|
||||
};
|
||||
// TODO
|
||||
if (ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
runnable.run();
|
||||
} else {
|
||||
SwingUtilities.invokeLater(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTotalTokens(Message message) {
|
||||
int userPromptTokens = EncodingManager.getInstance().countTokens(message.getPrompt());
|
||||
int conversationTokens = EncodingManager.getInstance().countConversationTokens(conversation);
|
||||
totalTokensPanel.updateConversationTokens(conversationTokens + userPromptTokens);
|
||||
}
|
||||
|
||||
private ResponsePanel createResponsePanel(Message message, ConversationType conversationType) {
|
||||
return new ResponsePanel()
|
||||
.withReloadAction(() -> reloadMessage(message, conversation, conversationType))
|
||||
.withDeleteAction(() -> removeMessage(message.getId(), conversation))
|
||||
.addContent(new ChatMessageResponseBody(project, true, this));
|
||||
}
|
||||
|
||||
public TotalTokensDetails getTokenDetails() {
|
||||
return totalTokensPanel.getTokenDetails();
|
||||
}
|
||||
|
|
@ -158,7 +113,77 @@ public abstract class ChatToolWindowTabPanel implements Disposable {
|
|||
totalTokensPanel.updateConversationTokens(conversation);
|
||||
}
|
||||
|
||||
protected void reloadMessage(
|
||||
public void sendMessage(Message message) {
|
||||
sendMessage(message, ConversationType.DEFAULT);
|
||||
}
|
||||
|
||||
public void sendMessage(Message message, ConversationType conversationType) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
var referencedFiles = project.getUserData(CodeGPTKeys.SELECTED_FILES);
|
||||
var chatToolWindowPanel = project.getService(ChatToolWindowContentManager.class)
|
||||
.tryFindChatToolWindowPanel();
|
||||
if (referencedFiles != null && !referencedFiles.isEmpty()) {
|
||||
var referencedFilePaths = referencedFiles.stream()
|
||||
.map(ReferencedFile::getFilePath)
|
||||
.collect(toList());
|
||||
message.setReferencedFilePaths(referencedFilePaths);
|
||||
message.setUserMessage(message.getPrompt());
|
||||
message.setPrompt(getPromptWithContext(referencedFiles, message.getPrompt()));
|
||||
|
||||
totalTokensPanel.updateReferencedFilesTokens(referencedFiles);
|
||||
|
||||
chatToolWindowPanel.ifPresent(panel -> panel.clearNotifications(project));
|
||||
}
|
||||
|
||||
var userMessagePanel = new UserMessagePanel(project, message, this);
|
||||
var attachedFilePath = CodeGPTKeys.IMAGE_ATTACHMENT_FILE_PATH.get(project);
|
||||
var callParameters = getCallParameters(conversationType, message, attachedFilePath);
|
||||
if (callParameters.getImageData() != null) {
|
||||
message.setImageFilePath(attachedFilePath);
|
||||
chatToolWindowPanel.ifPresent(panel -> panel.clearNotifications(project));
|
||||
userMessagePanel.displayImage(attachedFilePath);
|
||||
}
|
||||
|
||||
var messagePanel = toolWindowScrollablePanel.addMessage(message.getId());
|
||||
messagePanel.add(userMessagePanel);
|
||||
|
||||
var responsePanel = createResponsePanel(message, conversationType);
|
||||
messagePanel.add(responsePanel);
|
||||
updateTotalTokens(message);
|
||||
call(callParameters, responsePanel);
|
||||
});
|
||||
}
|
||||
|
||||
private CallParameters getCallParameters(
|
||||
ConversationType conversationType,
|
||||
Message message,
|
||||
@Nullable String attachedFilePath) {
|
||||
var callParameters = new CallParameters(conversation, conversationType, message, false);
|
||||
if (attachedFilePath != null && !attachedFilePath.isEmpty()) {
|
||||
try {
|
||||
callParameters.setImageData(Files.readAllBytes(Path.of(attachedFilePath)));
|
||||
callParameters.setImageMediaType(FileUtil.getImageMediaType(attachedFilePath));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return callParameters;
|
||||
}
|
||||
|
||||
private void updateTotalTokens(Message message) {
|
||||
int userPromptTokens = EncodingManager.getInstance().countTokens(message.getPrompt());
|
||||
int conversationTokens = EncodingManager.getInstance().countConversationTokens(conversation);
|
||||
totalTokensPanel.updateConversationTokens(conversationTokens + userPromptTokens);
|
||||
}
|
||||
|
||||
private ResponsePanel createResponsePanel(Message message, ConversationType conversationType) {
|
||||
return new ResponsePanel()
|
||||
.withReloadAction(() -> reloadMessage(message, conversation, conversationType))
|
||||
.withDeleteAction(() -> removeMessage(message.getId(), conversation))
|
||||
.addContent(new ChatMessageResponseBody(project, true, this));
|
||||
}
|
||||
|
||||
private void reloadMessage(
|
||||
Message message,
|
||||
Conversation conversation,
|
||||
ConversationType conversationType) {
|
||||
|
|
@ -175,7 +200,7 @@ public abstract class ChatToolWindowTabPanel implements Disposable {
|
|||
if (responsePanel != null) {
|
||||
message.setResponse("");
|
||||
conversationService.saveMessage(conversation, message);
|
||||
call(message, conversationType, responsePanel, true);
|
||||
call(new CallParameters(conversation, conversationType, message, true), responsePanel);
|
||||
}
|
||||
|
||||
totalTokensPanel.updateConversationTokens(conversation);
|
||||
|
|
@ -186,7 +211,7 @@ public abstract class ChatToolWindowTabPanel implements Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
protected void removeMessage(UUID messageId, Conversation conversation) {
|
||||
private void removeMessage(UUID messageId, Conversation conversation) {
|
||||
toolWindowScrollablePanel.removeMessage(messageId);
|
||||
conversation.removeMessage(messageId);
|
||||
conversationService.saveConversation(conversation);
|
||||
|
|
@ -197,16 +222,12 @@ public abstract class ChatToolWindowTabPanel implements Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
protected void clearWindow() {
|
||||
private void clearWindow() {
|
||||
toolWindowScrollablePanel.clearAll();
|
||||
totalTokensPanel.updateConversationTokens(conversation);
|
||||
}
|
||||
|
||||
private void call(
|
||||
Message message,
|
||||
ConversationType conversationType,
|
||||
ResponsePanel responsePanel,
|
||||
boolean retry) {
|
||||
private void call(CallParameters callParameters, ResponsePanel responsePanel) {
|
||||
var responseContainer = (ChatMessageResponseBody) responsePanel.getContent();
|
||||
|
||||
if (!CompletionRequestService.getInstance().isRequestAllowed()) {
|
||||
|
|
@ -222,13 +243,13 @@ public abstract class ChatToolWindowTabPanel implements Disposable {
|
|||
userPromptTextArea) {
|
||||
@Override
|
||||
public void handleTokensExceededPolicyAccepted() {
|
||||
call(message, conversationType, responsePanel, true);
|
||||
call(callParameters, responsePanel);
|
||||
}
|
||||
});
|
||||
userPromptTextArea.setRequestHandler(requestHandler);
|
||||
userPromptTextArea.setSubmitEnabled(false);
|
||||
|
||||
requestHandler.call(new CallParameters(conversation, conversationType, message, retry));
|
||||
requestHandler.call(callParameters);
|
||||
}
|
||||
|
||||
private void handleSubmit(String text) {
|
||||
|
|
@ -253,15 +274,79 @@ public abstract class ChatToolWindowTabPanel implements Disposable {
|
|||
panel.setBorder(JBUI.Borders.compound(
|
||||
JBUI.Borders.customLine(JBColor.border(), 1, 0, 0, 0),
|
||||
JBUI.Borders.empty(8)));
|
||||
var contentManager = project.getService(StandardChatToolWindowContentManager.class);
|
||||
panel.add(JBUI.Panels.simplePanel(new UserPromptTextAreaHeader(
|
||||
var contentManager = project.getService(ChatToolWindowContentManager.class);
|
||||
panel.add(JBUI.Panels.simplePanel(createUserPromptTextAreaHeader(
|
||||
selectedService,
|
||||
totalTokensPanel,
|
||||
contentManager::createNewTabPanel)), BorderLayout.NORTH);
|
||||
() -> {
|
||||
ConversationService.getInstance().startConversation();
|
||||
contentManager.createNewTabPanel();
|
||||
})), BorderLayout.NORTH);
|
||||
panel.add(JBUI.Panels.simplePanel(userPromptTextArea), BorderLayout.CENTER);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JPanel createUserPromptTextAreaHeader(
|
||||
ServiceType selectedService,
|
||||
Runnable onModelChange) {
|
||||
return JBUI.Panels.simplePanel()
|
||||
.withBorder(Borders.emptyBottom(8))
|
||||
.andTransparent()
|
||||
.addToLeft(totalTokensPanel)
|
||||
.addToRight(new ModelComboBoxAction(onModelChange, selectedService)
|
||||
.createCustomComponent(ActionPlaces.UNKNOWN));
|
||||
}
|
||||
|
||||
private JComponent getLandingView() {
|
||||
return new ChatToolWindowLandingPanel((action, locationOnScreen) -> {
|
||||
var editor = EditorUtil.getSelectedEditor(project);
|
||||
if (editor == null || !editor.getSelectionModel().hasSelection()) {
|
||||
OverlayUtil.showWarningBalloon(
|
||||
editor == null ? "Unable to locate a selected editor"
|
||||
: "Please select a target code before proceeding",
|
||||
locationOnScreen);
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
var fileExtension = FileUtil.getFileExtension(
|
||||
((EditorImpl) editor).getVirtualFile().getName());
|
||||
var message = new Message(action.getPrompt().replace(
|
||||
"{{selectedCode}}",
|
||||
format("\n```%s\n%s\n```", fileExtension, editor.getSelectionModel().getSelectedText())));
|
||||
message.setUserMessage(action.getUserMessage());
|
||||
|
||||
sendMessage(message, ConversationType.DEFAULT);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
|
||||
private void displayConversation(@NotNull Conversation conversation) {
|
||||
clearWindow();
|
||||
conversation.getMessages().forEach(message -> {
|
||||
var messageResponseBody =
|
||||
new ChatMessageResponseBody(project, this).withResponse(message.getResponse());
|
||||
|
||||
var serpResults = message.getSerpResults();
|
||||
if (YouSettings.getCurrentState().isDisplayWebSearchResults()
|
||||
&& serpResults != null && !serpResults.isEmpty()) {
|
||||
messageResponseBody.displaySerpResults(serpResults);
|
||||
}
|
||||
messageResponseBody.hideCaret();
|
||||
|
||||
var userMessagePanel = new UserMessagePanel(project, message, this);
|
||||
var imageFilePath = message.getImageFilePath();
|
||||
if (imageFilePath != null && !imageFilePath.isEmpty()) {
|
||||
userMessagePanel.displayImage(imageFilePath);
|
||||
}
|
||||
|
||||
var messagePanel = toolWindowScrollablePanel.addMessage(message.getId());
|
||||
messagePanel.add(userMessagePanel);
|
||||
messagePanel.add(new ResponsePanel()
|
||||
.withReloadAction(() -> reloadMessage(message, conversation, ConversationType.DEFAULT))
|
||||
.withDeleteAction(() -> removeMessage(message.getId(), conversation))
|
||||
.addContent(messageResponseBody));
|
||||
});
|
||||
}
|
||||
|
||||
private JPanel createRootPanel() {
|
||||
var gbc = new GridBagConstraints();
|
||||
gbc.fill = GridBagConstraints.BOTH;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.standard;
|
||||
package ee.carlrobert.codegpt.toolwindow.chat;
|
||||
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.openapi.Disposable;
|
||||
|
|
@ -25,9 +25,9 @@ import javax.swing.JPanel;
|
|||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
public class StandardChatToolWindowTabbedPane extends JBTabbedPane {
|
||||
public class ChatToolWindowTabbedPane extends JBTabbedPane {
|
||||
|
||||
private final Map<String, StandardChatToolWindowTabPanel> activeTabMapping = new TreeMap<>(
|
||||
private final Map<String, ChatToolWindowTabPanel> activeTabMapping = new TreeMap<>(
|
||||
(o1, o2) -> {
|
||||
int n1 = Integer.parseInt(o1.replaceAll("\\D", ""));
|
||||
int n2 = Integer.parseInt(o2.replaceAll("\\D", ""));
|
||||
|
|
@ -35,18 +35,18 @@ public class StandardChatToolWindowTabbedPane extends JBTabbedPane {
|
|||
});
|
||||
private final Disposable parentDisposable;
|
||||
|
||||
public StandardChatToolWindowTabbedPane(Disposable parentDisposable) {
|
||||
public ChatToolWindowTabbedPane(Disposable parentDisposable) {
|
||||
this.parentDisposable = parentDisposable;
|
||||
setTabComponentInsets(null);
|
||||
setComponentPopupMenu(new TabPopupMenu());
|
||||
addChangeListener(e -> refreshTabState());
|
||||
}
|
||||
|
||||
public Map<String, StandardChatToolWindowTabPanel> getActiveTabMapping() {
|
||||
public Map<String, ChatToolWindowTabPanel> getActiveTabMapping() {
|
||||
return activeTabMapping;
|
||||
}
|
||||
|
||||
public void addNewTab(StandardChatToolWindowTabPanel toolWindowPanel) {
|
||||
public void addNewTab(ChatToolWindowTabPanel toolWindowPanel) {
|
||||
var tabIndices = activeTabMapping.keySet().toArray(new String[0]);
|
||||
var nextIndex = 0;
|
||||
for (String title : tabIndices) {
|
||||
|
|
@ -81,7 +81,7 @@ public class StandardChatToolWindowTabbedPane extends JBTabbedPane {
|
|||
.map(Map.Entry::getKey);
|
||||
}
|
||||
|
||||
public Optional<StandardChatToolWindowTabPanel> tryFindActiveTabPanel() {
|
||||
public Optional<ChatToolWindowTabPanel> tryFindActiveTabPanel() {
|
||||
var selectedIndex = getSelectedIndex();
|
||||
if (selectedIndex == -1) {
|
||||
return Optional.empty();
|
||||
|
|
@ -116,7 +116,7 @@ public class StandardChatToolWindowTabbedPane extends JBTabbedPane {
|
|||
Disposer.dispose(tabPanel);
|
||||
activeTabMapping.remove(getTitleAt(getSelectedIndex()));
|
||||
removeTabAt(getSelectedIndex());
|
||||
addNewTab(new StandardChatToolWindowTabPanel(
|
||||
addNewTab(new ChatToolWindowTabPanel(
|
||||
project,
|
||||
ConversationService.getInstance().startConversation()));
|
||||
repaint();
|
||||
|
|
@ -180,8 +180,8 @@ public class StandardChatToolWindowTabbedPane extends JBTabbedPane {
|
|||
|
||||
@Override
|
||||
public void show(Component invoker, int x, int y) {
|
||||
selectedPopupTabIndex = StandardChatToolWindowTabbedPane.this.getUI()
|
||||
.tabForCoordinate(StandardChatToolWindowTabbedPane.this, x, y);
|
||||
selectedPopupTabIndex = ChatToolWindowTabbedPane.this.getUI()
|
||||
.tabForCoordinate(ChatToolWindowTabbedPane.this, x, y);
|
||||
if (selectedPopupTabIndex > 0) {
|
||||
super.show(invoker, x, y);
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.standard;
|
||||
|
||||
enum EditorAction {
|
||||
FIND_BUGS(
|
||||
"Find Bugs",
|
||||
"Find bugs in the selected code",
|
||||
"Find bugs and output code with bugs fixed in the selected code: {{selectedCode}}"),
|
||||
WRITE_TESTS(
|
||||
"Write Tests",
|
||||
"Write unit tests for the selected code",
|
||||
"Write unit tests for the selected code: {{selectedCode}}"),
|
||||
EXPLAIN(
|
||||
"Explain",
|
||||
"Explain the selected code",
|
||||
"Explain the selected code: {{selectedCode}}"),
|
||||
REFACTOR(
|
||||
"Refactor",
|
||||
"Refactor the selected code",
|
||||
"Refactor the selected code: {{selectedCode}}"),
|
||||
OPTIMIZE(
|
||||
"Optimize",
|
||||
"Optimize the selected code",
|
||||
"Optimize the selected code: {{selectedCode}}");
|
||||
|
||||
private final String label;
|
||||
private final String userMessage;
|
||||
private final String prompt;
|
||||
|
||||
EditorAction(String label, String userMessage, String prompt) {
|
||||
this.label = label;
|
||||
this.userMessage = userMessage;
|
||||
this.prompt = prompt;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public String getPrompt() {
|
||||
return prompt;
|
||||
}
|
||||
|
||||
public String getUserMessage() {
|
||||
return userMessage;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.standard;
|
||||
|
||||
import java.awt.Point;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface EditorActionEvent {
|
||||
|
||||
void handleAction(EditorAction action, Point locationOnScreen);
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.standard;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
import com.intellij.ui.components.ActionLink;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import ee.carlrobert.codegpt.Icons;
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.ResponsePanel;
|
||||
import ee.carlrobert.codegpt.ui.UIUtil;
|
||||
import java.awt.BorderLayout;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
class StandardChatToolWindowLandingPanel extends ResponsePanel {
|
||||
|
||||
StandardChatToolWindowLandingPanel(EditorActionEvent onAction) {
|
||||
addContent(createContent(onAction));
|
||||
}
|
||||
|
||||
private ActionLink createEditorActionLink(EditorAction action, EditorActionEvent onAction) {
|
||||
var link = new ActionLink(action.getUserMessage(), event -> {
|
||||
onAction.handleAction(action, ((ActionLink) event.getSource()).getLocationOnScreen());
|
||||
});
|
||||
link.setIcon(Icons.Sparkle);
|
||||
return link;
|
||||
}
|
||||
|
||||
private JPanel createContent(EditorActionEvent onAction) {
|
||||
var panel = new JPanel(new BorderLayout());
|
||||
panel.add(UIUtil.createTextPane(
|
||||
"<html>"
|
||||
+ format(
|
||||
"<p style=\"margin-top: 4px; margin-bottom: 4px;\">"
|
||||
+ "Welcome <strong>%s</strong>, I'm your intelligent code companion, here to be"
|
||||
+ " your partner-in-crime for getting things done in a flash."
|
||||
+ "</p>", GeneralSettings.getCurrentState().getDisplayName())
|
||||
+ "<p style=\"margin-top: 4px; margin-bottom: 4px;\">"
|
||||
+ "Feel free to ask me anything you'd like, but my true superpower lies in assisting "
|
||||
+ "you with your code! Here are a few examples of how I can assist you:"
|
||||
+ "</p>"
|
||||
+ "</html>",
|
||||
false), BorderLayout.NORTH);
|
||||
panel.add(createEditorActionsListPanel(onAction), BorderLayout.CENTER);
|
||||
panel.add(UIUtil.createTextPane(
|
||||
"<html>"
|
||||
+ "<p style=\"margin-top: 4px; margin-bottom: 4px;\">"
|
||||
+ "Being an AI-powered assistant, I may occasionally have surprises or make mistakes. "
|
||||
+ "Therefore, it's wise to double-check any code or suggestions I provide."
|
||||
+ "</p>"
|
||||
+ "</html>",
|
||||
false), BorderLayout.SOUTH);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JPanel createEditorActionsListPanel(EditorActionEvent onAction) {
|
||||
var listPanel = new JPanel();
|
||||
listPanel.setLayout(new BoxLayout(listPanel, BoxLayout.PAGE_AXIS));
|
||||
listPanel.setBorder(JBUI.Borders.emptyLeft(4));
|
||||
listPanel.add(Box.createVerticalStrut(4));
|
||||
listPanel.add(createEditorActionLink(EditorAction.WRITE_TESTS, onAction));
|
||||
listPanel.add(Box.createVerticalStrut(4));
|
||||
listPanel.add(createEditorActionLink(EditorAction.EXPLAIN, onAction));
|
||||
listPanel.add(Box.createVerticalStrut(4));
|
||||
listPanel.add(createEditorActionLink(EditorAction.FIND_BUGS, onAction));
|
||||
listPanel.add(Box.createVerticalStrut(4));
|
||||
return listPanel;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.standard;
|
||||
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.actionSystem.ActionManager;
|
||||
import com.intellij.openapi.actionSystem.ActionToolbar;
|
||||
import com.intellij.openapi.actionSystem.DefaultCompactActionGroup;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.SimpleToolWindowPanel;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import ee.carlrobert.codegpt.ReferencedFile;
|
||||
import ee.carlrobert.codegpt.actions.IncludeFilesInContextNotifier;
|
||||
import ee.carlrobert.codegpt.actions.toolwindow.ClearChatWindowAction;
|
||||
import ee.carlrobert.codegpt.actions.toolwindow.CreateNewConversationAction;
|
||||
import ee.carlrobert.codegpt.actions.toolwindow.OpenInEditorAction;
|
||||
import ee.carlrobert.codegpt.conversations.ConversationService;
|
||||
import ee.carlrobert.codegpt.conversations.ConversationsState;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.SelectedFilesNotification;
|
||||
import java.awt.BorderLayout;
|
||||
import java.util.List;
|
||||
import javax.swing.JPanel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class StandardChatToolWindowPanel extends SimpleToolWindowPanel {
|
||||
|
||||
private final SelectedFilesNotification selectedFilesNotification;
|
||||
private StandardChatToolWindowTabbedPane tabbedPane;
|
||||
|
||||
public StandardChatToolWindowPanel(
|
||||
@NotNull Project project,
|
||||
@NotNull Disposable parentDisposable) {
|
||||
super(true);
|
||||
selectedFilesNotification = new SelectedFilesNotification(project);
|
||||
init(project, selectedFilesNotification, parentDisposable);
|
||||
|
||||
project.getMessageBus()
|
||||
.connect()
|
||||
.subscribe(IncludeFilesInContextNotifier.FILES_INCLUDED_IN_CONTEXT_TOPIC,
|
||||
(IncludeFilesInContextNotifier) this::displaySelectedFilesNotification);
|
||||
}
|
||||
|
||||
public void displaySelectedFilesNotification(List<ReferencedFile> referencedFiles) {
|
||||
selectedFilesNotification.displaySelectedFilesNotification(referencedFiles);
|
||||
}
|
||||
|
||||
public void clearSelectedFilesNotification() {
|
||||
selectedFilesNotification.clearSelectedFilesNotification();
|
||||
}
|
||||
|
||||
private void init(
|
||||
Project project,
|
||||
SelectedFilesNotification selectedFilesNotification,
|
||||
Disposable parentDisposable) {
|
||||
var conversation = ConversationsState.getCurrentConversation();
|
||||
if (conversation == null) {
|
||||
conversation = ConversationService.getInstance().startConversation();
|
||||
}
|
||||
|
||||
var tabPanel = new StandardChatToolWindowTabPanel(project, conversation);
|
||||
tabbedPane = createTabbedPane(tabPanel, parentDisposable);
|
||||
Runnable onAddNewTab = () -> {
|
||||
tabbedPane.addNewTab(new StandardChatToolWindowTabPanel(
|
||||
project,
|
||||
ConversationService.getInstance().startConversation()));
|
||||
repaint();
|
||||
revalidate();
|
||||
};
|
||||
var actionToolbarPanel = new JPanel(new BorderLayout());
|
||||
actionToolbarPanel.add(
|
||||
createActionToolbar(project, tabbedPane, onAddNewTab).getComponent(),
|
||||
BorderLayout.LINE_START);
|
||||
|
||||
setToolbar(actionToolbarPanel);
|
||||
setContent(
|
||||
JBUI.Panels.simplePanel(tabbedPane).addToBottom(selectedFilesNotification));
|
||||
|
||||
Disposer.register(parentDisposable, tabPanel);
|
||||
}
|
||||
|
||||
private ActionToolbar createActionToolbar(
|
||||
Project project,
|
||||
StandardChatToolWindowTabbedPane tabbedPane,
|
||||
Runnable onAddNewTab) {
|
||||
var actionGroup = new DefaultCompactActionGroup("TOOLBAR_ACTION_GROUP", false);
|
||||
actionGroup.add(new CreateNewConversationAction(onAddNewTab));
|
||||
actionGroup.add(
|
||||
new ClearChatWindowAction(() -> tabbedPane.resetCurrentlyActiveTabPanel(project)));
|
||||
actionGroup.addSeparator();
|
||||
actionGroup.add(new OpenInEditorAction());
|
||||
|
||||
var toolbar = ActionManager.getInstance()
|
||||
.createActionToolbar("NAVIGATION_BAR_TOOLBAR", actionGroup, true);
|
||||
toolbar.setTargetComponent(this);
|
||||
return toolbar;
|
||||
}
|
||||
|
||||
private StandardChatToolWindowTabbedPane createTabbedPane(
|
||||
StandardChatToolWindowTabPanel tabPanel,
|
||||
Disposable parentDisposable) {
|
||||
var tabbedPane = new StandardChatToolWindowTabbedPane(parentDisposable);
|
||||
tabbedPane.addNewTab(tabPanel);
|
||||
return tabbedPane;
|
||||
}
|
||||
|
||||
public StandardChatToolWindowTabbedPane getChatTabbedPane() {
|
||||
return tabbedPane;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.standard;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
import com.intellij.openapi.editor.impl.EditorImpl;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import ee.carlrobert.codegpt.completions.ConversationType;
|
||||
import ee.carlrobert.codegpt.conversations.Conversation;
|
||||
import ee.carlrobert.codegpt.conversations.message.Message;
|
||||
import ee.carlrobert.codegpt.settings.service.you.YouSettings;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowTabPanel;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.ChatMessageResponseBody;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.ResponsePanel;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.UserMessagePanel;
|
||||
import ee.carlrobert.codegpt.ui.OverlayUtil;
|
||||
import ee.carlrobert.codegpt.util.EditorUtil;
|
||||
import ee.carlrobert.codegpt.util.file.FileUtil;
|
||||
import javax.swing.JComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class StandardChatToolWindowTabPanel extends ChatToolWindowTabPanel {
|
||||
|
||||
public StandardChatToolWindowTabPanel(
|
||||
@NotNull Project project,
|
||||
@NotNull Conversation conversation) {
|
||||
super(project, conversation, false);
|
||||
if (conversation.getMessages().isEmpty()) {
|
||||
displayLandingView();
|
||||
} else {
|
||||
displayConversation(conversation);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent getLandingView() {
|
||||
return new StandardChatToolWindowLandingPanel((action, locationOnScreen) -> {
|
||||
var editor = EditorUtil.getSelectedEditor(project);
|
||||
if (editor == null || !editor.getSelectionModel().hasSelection()) {
|
||||
OverlayUtil.showWarningBalloon(
|
||||
editor == null ? "Unable to locate a selected editor"
|
||||
: "Please select a target code before proceeding",
|
||||
locationOnScreen);
|
||||
return;
|
||||
}
|
||||
|
||||
var fileExtension = FileUtil.getFileExtension(
|
||||
((EditorImpl) editor).getVirtualFile().getName());
|
||||
var message = new Message(action.getPrompt().replace(
|
||||
"{{selectedCode}}",
|
||||
format("\n```%s\n%s\n```", fileExtension, editor.getSelectionModel().getSelectedText())));
|
||||
message.setUserMessage(action.getUserMessage());
|
||||
|
||||
sendMessage(message, ConversationType.DEFAULT);
|
||||
});
|
||||
}
|
||||
|
||||
private void displayConversation(@NotNull Conversation conversation) {
|
||||
clearWindow();
|
||||
conversation.getMessages().forEach(message -> {
|
||||
var messageResponseBody =
|
||||
new ChatMessageResponseBody(project, this).withResponse(message.getResponse());
|
||||
|
||||
var serpResults = message.getSerpResults();
|
||||
if (YouSettings.getCurrentState().isDisplayWebSearchResults()
|
||||
&& serpResults != null && !serpResults.isEmpty()) {
|
||||
messageResponseBody.displaySerpResults(serpResults);
|
||||
}
|
||||
messageResponseBody.hideCaret();
|
||||
|
||||
var messagePanel = toolWindowScrollablePanel.addMessage(message.getId());
|
||||
messagePanel.add(new UserMessagePanel(project, message, this));
|
||||
messagePanel.add(new ResponsePanel()
|
||||
.withReloadAction(() -> reloadMessage(message, conversation, ConversationType.DEFAULT))
|
||||
.withDeleteAction(() -> removeMessage(message.getId(), conversation))
|
||||
.addContent(messageResponseBody));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.ui;
|
||||
|
||||
import static com.intellij.util.ui.JBUI.Panels.simplePanel;
|
||||
|
||||
import com.intellij.icons.AllIcons.General;
|
||||
import com.intellij.ui.components.JBLabel;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JToggleButton;
|
||||
import javax.swing.SwingConstants;
|
||||
|
||||
public class ImageAccordion extends JPanel {
|
||||
|
||||
public ImageAccordion(String fileName, byte[] imageData) {
|
||||
super(new BorderLayout());
|
||||
setOpaque(false);
|
||||
|
||||
var contentPanel = createContentPanel(fileName, imageData);
|
||||
add(createToggleButton(contentPanel), BorderLayout.NORTH);
|
||||
add(contentPanel, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
private JPanel createContentPanel(String fileName, byte[] imageData) {
|
||||
var panel = new JPanel();
|
||||
panel.setOpaque(false);
|
||||
panel.setVisible(true);
|
||||
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
||||
panel.setBorder(JBUI.Borders.empty(4, 0));
|
||||
try {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(imageData);
|
||||
BufferedImage originalImage = ImageIO.read(inputStream);
|
||||
int maxHeight = 80;
|
||||
BufferedImage resizedImage = resizeImage(originalImage, maxHeight);
|
||||
panel.add(simplePanel()
|
||||
.andTransparent()
|
||||
.addToTop(
|
||||
new JBLabel("<html><small><strong>%s</strong></small></html>".formatted(fileName))
|
||||
.withBorder(JBUI.Borders.emptyBottom(4)))
|
||||
.addToLeft(new JBLabel(new ImageIcon(resizedImage))), BorderLayout.LINE_START);
|
||||
} catch (IOException e) {
|
||||
panel.add(new JBLabel("ERROR: Something went wrong while reading the image"));
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JToggleButton createToggleButton(JPanel contentPane) {
|
||||
var accordionToggle = new JToggleButton(
|
||||
CodeGPTBundle.get("imageAccordion.title"),
|
||||
General.ArrowDown);
|
||||
accordionToggle.setFocusPainted(false);
|
||||
accordionToggle.setContentAreaFilled(false);
|
||||
accordionToggle.setBackground(getBackground());
|
||||
accordionToggle.setSelectedIcon(General.ArrowUp);
|
||||
accordionToggle.setBorder(null);
|
||||
accordionToggle.setSelected(true);
|
||||
accordionToggle.setHorizontalAlignment(SwingConstants.LEADING);
|
||||
accordionToggle.setHorizontalTextPosition(SwingConstants.LEADING);
|
||||
accordionToggle.addItemListener(e ->
|
||||
contentPane.setVisible(e.getStateChange() == ItemEvent.SELECTED));
|
||||
return accordionToggle;
|
||||
}
|
||||
|
||||
private BufferedImage resizeImage(BufferedImage originalImage, int maxHeight) {
|
||||
double aspectRatio = (double) originalImage.getWidth() / originalImage.getHeight();
|
||||
int newWidth = (int) (maxHeight * aspectRatio);
|
||||
Image resizedImage = originalImage.getScaledInstance(newWidth, maxHeight, Image.SCALE_SMOOTH);
|
||||
BufferedImage bufferedResizedImage = new BufferedImage(newWidth, maxHeight,
|
||||
BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g2d = bufferedResizedImage.createGraphics();
|
||||
g2d.drawImage(resizedImage, 0, 0, null);
|
||||
g2d.dispose();
|
||||
return bufferedResizedImage;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.ui;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
import com.intellij.icons.AllIcons.General;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.ui.components.ActionLink;
|
||||
import com.intellij.ui.components.JBLabel;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import com.intellij.util.ui.JBUI.CurrentTheme.NotificationInfo;
|
||||
import ee.carlrobert.codegpt.CodeGPTKeys;
|
||||
import ee.carlrobert.codegpt.ReferencedFile;
|
||||
import ee.carlrobert.codegpt.actions.IncludeFilesInContextNotifier;
|
||||
import java.awt.BorderLayout;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingConstants;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class SelectedFilesNotification extends JPanel {
|
||||
|
||||
private final Project project;
|
||||
private final JBLabel label;
|
||||
|
||||
public SelectedFilesNotification(@NotNull Project project) {
|
||||
super(new BorderLayout());
|
||||
this.project = project;
|
||||
this.label = new JBLabel(
|
||||
getSelectedFilesLabel(),
|
||||
General.BalloonInformation,
|
||||
SwingConstants.LEADING);
|
||||
|
||||
setVisible(false);
|
||||
setBorder(JBUI.Borders.compound(
|
||||
JBUI.Borders.customLine(JBColor.border(), 1, 0, 0, 0),
|
||||
JBUI.Borders.empty(8, 12)));
|
||||
|
||||
setBackground(NotificationInfo.backgroundColor());
|
||||
setForeground(NotificationInfo.foregroundColor());
|
||||
add(label, BorderLayout.LINE_START);
|
||||
add(new ActionLink("Remove", (event) -> {
|
||||
clearSelectedFilesNotification();
|
||||
}), BorderLayout.LINE_END);
|
||||
}
|
||||
|
||||
public void displaySelectedFilesNotification(@NotNull List<ReferencedFile> referencedFiles) {
|
||||
if (referencedFiles.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
label.setText(referencedFiles.size() + " files selected");
|
||||
var referencedFilePaths = referencedFiles.stream()
|
||||
.map(ReferencedFile::getFilePath)
|
||||
.collect(Collectors.toList());
|
||||
label.setToolTipText(getHtml(referencedFilePaths));
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
public void clearSelectedFilesNotification() {
|
||||
project.putUserData(CodeGPTKeys.SELECTED_FILES, emptyList());
|
||||
project.getMessageBus()
|
||||
.syncPublisher(IncludeFilesInContextNotifier.FILES_INCLUDED_IN_CONTEXT_TOPIC)
|
||||
.filesIncluded(emptyList());
|
||||
|
||||
label.setText("0 files selected");
|
||||
label.setToolTipText(null);
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
private String getHtml(List<String> referencedFilePaths) {
|
||||
var html = referencedFilePaths.stream()
|
||||
.map(filePath -> format("<li>%s</li>", Paths.get(filePath).getFileName().toString()))
|
||||
.collect(Collectors.joining());
|
||||
return format("<ul style=\"margin: 4px 12px;\">%s</ul>", html);
|
||||
}
|
||||
|
||||
private String getSelectedFilesLabel() {
|
||||
var selectedFiles = project.getUserData(CodeGPTKeys.SELECTED_FILES);
|
||||
var fileCount = selectedFiles == null ? 0 : selectedFiles.size();
|
||||
return fileCount + " files selected";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.ui;
|
||||
|
||||
import com.intellij.icons.AllIcons.General;
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.ui.components.ActionLink;
|
||||
import com.intellij.ui.components.JBLabel;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import com.intellij.util.ui.JBUI.CurrentTheme.NotificationInfo;
|
||||
import java.awt.BorderLayout;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingConstants;
|
||||
|
||||
public class ToolWindowFooterNotification extends JPanel {
|
||||
|
||||
private final JBLabel label;
|
||||
|
||||
public ToolWindowFooterNotification(Runnable onRemove) {
|
||||
this("", onRemove);
|
||||
}
|
||||
|
||||
public ToolWindowFooterNotification(String text, Runnable onRemove) {
|
||||
super(new BorderLayout());
|
||||
this.label = new JBLabel(text, General.BalloonInformation, SwingConstants.LEADING);
|
||||
|
||||
setVisible(false);
|
||||
setBorder(JBUI.Borders.compound(
|
||||
JBUI.Borders.customLine(JBColor.border(), 1, 0, 0, 0),
|
||||
JBUI.Borders.empty(8, 12)));
|
||||
|
||||
setBackground(NotificationInfo.backgroundColor());
|
||||
setForeground(NotificationInfo.foregroundColor());
|
||||
add(label, BorderLayout.LINE_START);
|
||||
add(new ActionLink("Remove", (event) -> {
|
||||
hideNotification();
|
||||
onRemove.run();
|
||||
}), BorderLayout.LINE_END);
|
||||
}
|
||||
|
||||
public void show(String text, String toolTipText) {
|
||||
label.setText(text);
|
||||
label.setToolTipText(toolTipText);
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
public void hideNotification() {
|
||||
label.setText("");
|
||||
label.setToolTipText(null);
|
||||
setVisible(false);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.ui;
|
||||
|
||||
import com.intellij.icons.AllIcons.General;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.ui.ColorUtil;
|
||||
|
|
@ -11,6 +12,9 @@ import ee.carlrobert.codegpt.Icons;
|
|||
import ee.carlrobert.codegpt.conversations.message.Message;
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings;
|
||||
import java.awt.BorderLayout;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingConstants;
|
||||
|
||||
|
|
@ -39,6 +43,18 @@ public class UserMessagePanel extends JPanel {
|
|||
}
|
||||
}
|
||||
|
||||
public void displayImage(String imageFilePath) {
|
||||
try {
|
||||
var path = Paths.get(imageFilePath);
|
||||
add(new ImageAccordion(path.getFileName().toString(), Files.readAllBytes(path)));
|
||||
} catch (IOException e) {
|
||||
add(new JBLabel(
|
||||
"<html><small>Unable to load image %s</small></html>".formatted(imageFilePath),
|
||||
General.Error,
|
||||
SwingConstants.LEFT));
|
||||
}
|
||||
}
|
||||
|
||||
private ChatMessageResponseBody createResponseBody(
|
||||
Project project,
|
||||
String prompt,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.ui.textarea;
|
||||
|
||||
import com.intellij.util.messages.Topic;
|
||||
|
||||
public interface AttachImageNotifier {
|
||||
|
||||
Topic<AttachImageNotifier> IMAGE_ATTACHMENT_FILE_PATH_TOPIC =
|
||||
Topic.create("imageAttachmentFilePath", AttachImageNotifier.class);
|
||||
|
||||
void imageAttached(String filePath);
|
||||
}
|
||||
|
|
@ -18,8 +18,6 @@ import ee.carlrobert.codegpt.Icons;
|
|||
import ee.carlrobert.codegpt.completions.llama.LlamaModel;
|
||||
import ee.carlrobert.codegpt.completions.you.YouUserManager;
|
||||
import ee.carlrobert.codegpt.completions.you.auth.SignedOutNotifier;
|
||||
import ee.carlrobert.codegpt.conversations.ConversationService;
|
||||
import ee.carlrobert.codegpt.conversations.ConversationsState;
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings;
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettingsState;
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType;
|
||||
|
|
@ -39,13 +37,13 @@ import org.jetbrains.annotations.NotNull;
|
|||
|
||||
public class ModelComboBoxAction extends ComboBoxAction {
|
||||
|
||||
private final Runnable onAddNewTab;
|
||||
private final Runnable onModelChange;
|
||||
private final GeneralSettingsState settings;
|
||||
private final OpenAISettingsState openAISettings;
|
||||
private final YouSettingsState youSettings;
|
||||
|
||||
public ModelComboBoxAction(Runnable onAddNewTab, ServiceType selectedService) {
|
||||
this.onAddNewTab = onAddNewTab;
|
||||
public ModelComboBoxAction(Runnable onModelChange, ServiceType selectedService) {
|
||||
this.onModelChange = onModelChange;
|
||||
settings = GeneralSettings.getCurrentState();
|
||||
openAISettings = OpenAISettings.getCurrentState();
|
||||
youSettings = YouSettings.getCurrentState();
|
||||
|
|
@ -74,6 +72,7 @@ public class ModelComboBoxAction extends ComboBoxAction {
|
|||
var actionGroup = new DefaultActionGroup();
|
||||
actionGroup.addSeparator("OpenAI");
|
||||
List.of(
|
||||
OpenAIChatCompletionModel.GPT_4_VISION_PREVIEW,
|
||||
OpenAIChatCompletionModel.GPT_4_0125_128k,
|
||||
OpenAIChatCompletionModel.GPT_3_5_0125_16k,
|
||||
OpenAIChatCompletionModel.GPT_4_32k,
|
||||
|
|
@ -210,7 +209,7 @@ public class ModelComboBoxAction extends ComboBoxAction {
|
|||
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
handleProviderChange(serviceType, label, icon, comboBoxPresentation);
|
||||
handleModelChange(serviceType, label, icon, comboBoxPresentation);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -220,7 +219,7 @@ public class ModelComboBoxAction extends ComboBoxAction {
|
|||
};
|
||||
}
|
||||
|
||||
private void handleProviderChange(
|
||||
private void handleModelChange(
|
||||
ServiceType serviceType,
|
||||
String label,
|
||||
Icon icon,
|
||||
|
|
@ -228,13 +227,7 @@ public class ModelComboBoxAction extends ComboBoxAction {
|
|||
settings.setSelectedService(serviceType);
|
||||
comboBoxPresentation.setIcon(icon);
|
||||
comboBoxPresentation.setText(label);
|
||||
|
||||
var currentConversation = ConversationsState.getCurrentConversation();
|
||||
if (currentConversation != null && !currentConversation.getMessages().isEmpty()) {
|
||||
onAddNewTab.run();
|
||||
} else {
|
||||
ConversationService.getInstance().startConversation();
|
||||
}
|
||||
onModelChange.run();
|
||||
}
|
||||
|
||||
private AnAction createOpenAIModelAction(
|
||||
|
|
@ -252,7 +245,7 @@ public class ModelComboBoxAction extends ComboBoxAction {
|
|||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
openAISettings.setModel(model.getCode());
|
||||
handleProviderChange(
|
||||
handleModelChange(
|
||||
OPENAI,
|
||||
model.getDescription(),
|
||||
Icons.OpenAI,
|
||||
|
|
@ -281,7 +274,7 @@ public class ModelComboBoxAction extends ComboBoxAction {
|
|||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
youSettings.setChatMode(mode);
|
||||
handleProviderChange(
|
||||
handleModelChange(
|
||||
YOU,
|
||||
mode.getDescription(),
|
||||
Icons.YouSmall,
|
||||
|
|
@ -311,7 +304,7 @@ public class ModelComboBoxAction extends ComboBoxAction {
|
|||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
youSettings.setCustomModel(model);
|
||||
youSettings.setChatMode(YouCompletionMode.CUSTOM);
|
||||
handleProviderChange(
|
||||
handleModelChange(
|
||||
YOU,
|
||||
model.getDescription(),
|
||||
Icons.YouSmall,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import ee.carlrobert.codegpt.EncodingManager;
|
|||
import ee.carlrobert.codegpt.ReferencedFile;
|
||||
import ee.carlrobert.codegpt.actions.IncludeFilesInContextNotifier;
|
||||
import ee.carlrobert.codegpt.conversations.Conversation;
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings;
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
|
@ -154,12 +156,28 @@ public class TotalTokensPanel extends JPanel {
|
|||
entry.getKey(),
|
||||
entry.getValue()))
|
||||
.collect(Collectors.joining());
|
||||
iconLabel.setToolTipText("<html>" + html + "</html>");
|
||||
iconLabel.setToolTipText(getIconToolTipText(html));
|
||||
}
|
||||
});
|
||||
return iconLabel;
|
||||
}
|
||||
|
||||
private String getIconToolTipText(String html) {
|
||||
if (GeneralSettings.getCurrentState().getSelectedService() != ServiceType.OPENAI) {
|
||||
return """
|
||||
<html
|
||||
<p style="margin: 4px 0;">
|
||||
<small>
|
||||
<strong>ⓘ Keep in mind that the output values might vary across different
|
||||
large language models due to variations in their encoding methods.</strong>
|
||||
</small>
|
||||
</p>
|
||||
%s
|
||||
</html>""".formatted(html);
|
||||
}
|
||||
return "<html" + html + "</html>";
|
||||
}
|
||||
|
||||
private String getLabelHtml(int total) {
|
||||
return format("<html><small>Tokens: <strong>%d</strong></small></html>", total);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.ui.textarea;
|
||||
|
||||
import static ee.carlrobert.codegpt.settings.service.ServiceType.ANTHROPIC;
|
||||
import static ee.carlrobert.codegpt.settings.service.ServiceType.OPENAI;
|
||||
import static ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionModel.GPT_4_VISION_PREVIEW;
|
||||
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.ex.util.EditorUtil;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
|
|
@ -10,11 +16,14 @@ import com.intellij.ui.components.JBTextArea;
|
|||
import com.intellij.util.ui.JBUI;
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle;
|
||||
import ee.carlrobert.codegpt.Icons;
|
||||
import ee.carlrobert.codegpt.actions.AttachImageAction;
|
||||
import ee.carlrobert.codegpt.completions.CompletionRequestHandler;
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings;
|
||||
import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings;
|
||||
import ee.carlrobert.codegpt.ui.IconActionButton;
|
||||
import ee.carlrobert.codegpt.ui.UIUtil;
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
|
|
@ -23,16 +32,14 @@ import java.awt.RenderingHints;
|
|||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.FocusListener;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class UserPromptTextArea extends JPanel {
|
||||
|
||||
|
|
@ -41,11 +48,12 @@ public class UserPromptTextArea extends JPanel {
|
|||
private static final JBColor BACKGROUND_COLOR = JBColor.namedColor(
|
||||
"Editor.SearchField.background", com.intellij.util.ui.UIUtil.getTextFieldBackground());
|
||||
|
||||
private final AtomicReference<CompletionRequestHandler> requestHandlerRef =
|
||||
new AtomicReference<>();
|
||||
private final JBTextArea textArea;
|
||||
|
||||
private final int textAreaRadius = 16;
|
||||
private final Consumer<String> onSubmit;
|
||||
private JButton stopButton;
|
||||
private IconActionButton stopButton;
|
||||
private JPanel iconsPanel;
|
||||
private boolean submitEnabled = true;
|
||||
|
||||
|
|
@ -82,12 +90,6 @@ public class UserPromptTextArea extends JPanel {
|
|||
UserPromptTextArea.super.paintBorder(UserPromptTextArea.super.getGraphics());
|
||||
}
|
||||
});
|
||||
textArea.getDocument().addDocumentListener(new DocumentAdapter() {
|
||||
@Override
|
||||
protected void textChanged(@NotNull DocumentEvent e) {
|
||||
iconsPanel.getComponents()[0].setEnabled(e.getDocument().getLength() > 0);
|
||||
}
|
||||
});
|
||||
updateFont();
|
||||
init();
|
||||
}
|
||||
|
|
@ -150,6 +152,10 @@ public class UserPromptTextArea extends JPanel {
|
|||
stopButton.setEnabled(!submitEnabled);
|
||||
}
|
||||
|
||||
public void setRequestHandler(@NotNull CompletionRequestHandler handler) {
|
||||
requestHandlerRef.set(handler);
|
||||
}
|
||||
|
||||
private void handleSubmit() {
|
||||
if (submitEnabled && !textArea.getText().isEmpty()) {
|
||||
// Replacing each newline with two newlines to ensure proper Markdown formatting
|
||||
|
|
@ -163,12 +169,34 @@ public class UserPromptTextArea extends JPanel {
|
|||
setOpaque(false);
|
||||
add(textArea, BorderLayout.CENTER);
|
||||
|
||||
stopButton = createIconButton(AllIcons.Actions.Suspend, null);
|
||||
stopButton = new IconActionButton(
|
||||
new AnAction("Stop", "Stop current inference", AllIcons.Actions.Suspend) {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
var handler = requestHandlerRef.get();
|
||||
if (handler != null) {
|
||||
handler.cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
stopButton.setEnabled(false);
|
||||
|
||||
var flowLayout = new FlowLayout(FlowLayout.RIGHT);
|
||||
flowLayout.setHgap(8);
|
||||
iconsPanel = new JPanel(flowLayout);
|
||||
iconsPanel.add(createIconButton(Icons.Send, this::handleSubmit));
|
||||
iconsPanel.add(new IconActionButton(
|
||||
new AnAction("Send Message", "Send message", Icons.Send) {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
handleSubmit();
|
||||
}
|
||||
}));
|
||||
var selectedService = GeneralSettings.getCurrentState().getSelectedService();
|
||||
if (selectedService == ANTHROPIC
|
||||
|| (selectedService == OPENAI
|
||||
&& GPT_4_VISION_PREVIEW.getCode().equals(OpenAISettings.getCurrentState().getModel()))) {
|
||||
iconsPanel.add(new IconActionButton(new AttachImageAction()));
|
||||
}
|
||||
iconsPanel.add(stopButton);
|
||||
add(iconsPanel, BorderLayout.EAST);
|
||||
}
|
||||
|
|
@ -180,19 +208,4 @@ public class UserPromptTextArea extends JPanel {
|
|||
textArea.setFont(UIManager.getFont("TextField.font"));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: IconActionButton?
|
||||
private JButton createIconButton(Icon icon, @Nullable Runnable submitListener) {
|
||||
var button = UIUtil.createIconButton(icon);
|
||||
if (submitListener != null) {
|
||||
button.addActionListener((e) -> handleSubmit());
|
||||
}
|
||||
button.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||
button.setEnabled(false);
|
||||
return button;
|
||||
}
|
||||
|
||||
public void setRequestHandler(@NotNull CompletionRequestHandler requestService) {
|
||||
stopButton.addActionListener(e -> requestService.cancel());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.ui.textarea;
|
||||
|
||||
import com.intellij.openapi.actionSystem.ActionPlaces;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.ui.components.JBCheckBox;
|
||||
import com.intellij.util.messages.MessageBusConnection;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import ee.carlrobert.codegpt.completions.you.YouSubscriptionNotifier;
|
||||
import ee.carlrobert.codegpt.completions.you.auth.SignedOutNotifier;
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType;
|
||||
import java.awt.BorderLayout;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
public class UserPromptTextAreaHeader extends JPanel {
|
||||
|
||||
public UserPromptTextAreaHeader(
|
||||
ServiceType selectedService,
|
||||
TotalTokensPanel totalTokensPanel,
|
||||
Runnable onAddNewTab) {
|
||||
super(new BorderLayout());
|
||||
setOpaque(false);
|
||||
setBorder(JBUI.Borders.emptyBottom(8));
|
||||
switch (selectedService) {
|
||||
case OPENAI:
|
||||
case AZURE:
|
||||
add(totalTokensPanel, BorderLayout.LINE_START);
|
||||
break;
|
||||
case YOU:
|
||||
break;
|
||||
default:
|
||||
}
|
||||
add(new ModelComboBoxAction(onAddNewTab, selectedService)
|
||||
.createCustomComponent(ActionPlaces.UNKNOWN), BorderLayout.LINE_END);
|
||||
}
|
||||
|
||||
private void subscribeToYouTopics(JBCheckBox gpt4CheckBox) {
|
||||
var messageBusConnection = ApplicationManager.getApplication().getMessageBus().connect();
|
||||
subscribeToYouSubscriptionTopic(messageBusConnection, gpt4CheckBox);
|
||||
subscribeToSignedOutTopic(messageBusConnection, gpt4CheckBox);
|
||||
}
|
||||
|
||||
private void subscribeToSignedOutTopic(
|
||||
MessageBusConnection messageBusConnection,
|
||||
JBCheckBox gpt4CheckBox) {
|
||||
messageBusConnection.subscribe(
|
||||
SignedOutNotifier.SIGNED_OUT_TOPIC,
|
||||
(SignedOutNotifier) () -> gpt4CheckBox.setEnabled(false));
|
||||
}
|
||||
|
||||
private void subscribeToYouSubscriptionTopic(
|
||||
MessageBusConnection messageBusConnection,
|
||||
JBCheckBox gpt4CheckBox) {
|
||||
messageBusConnection.subscribe(
|
||||
YouSubscriptionNotifier.SUBSCRIPTION_TOPIC,
|
||||
(YouSubscriptionNotifier) () -> gpt4CheckBox.setEnabled(true));
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import ee.carlrobert.codegpt.actions.toolwindow.DeleteConversationAction;
|
|||
import ee.carlrobert.codegpt.conversations.Conversation;
|
||||
import ee.carlrobert.codegpt.conversations.ConversationsState;
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowContentManager;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowContentManager;
|
||||
import ee.carlrobert.codegpt.ui.IconActionButton;
|
||||
import ee.carlrobert.codegpt.ui.ModelIconLabel;
|
||||
import java.awt.BorderLayout;
|
||||
|
|
@ -30,12 +30,12 @@ class ConversationPanel extends JPanel {
|
|||
@NotNull Conversation conversation,
|
||||
@NotNull Runnable onDelete) {
|
||||
super(new BorderLayout());
|
||||
var toolWindowContentManager = project.getService(StandardChatToolWindowContentManager.class);
|
||||
var toolWindowContentManager = project.getService(ChatToolWindowContentManager.class);
|
||||
init(toolWindowContentManager, conversation, onDelete);
|
||||
}
|
||||
|
||||
private void init(
|
||||
StandardChatToolWindowContentManager toolWindowContentManager,
|
||||
ChatToolWindowContentManager toolWindowContentManager,
|
||||
Conversation conversation,
|
||||
Runnable onDelete) {
|
||||
setBackground(JBColor.background());
|
||||
|
|
|
|||
|
|
@ -141,6 +141,15 @@ public class FileUtil {
|
|||
}
|
||||
}
|
||||
|
||||
public static String getImageMediaType(String fileName) {
|
||||
var fileExtension = getFileExtension(fileName);
|
||||
return switch (fileExtension) {
|
||||
case "png" -> "image/png";
|
||||
case "jpg", "jpeg" -> "image/jpeg";
|
||||
default -> throw new IllegalArgumentException("Unsupported image type: " + fileExtension);
|
||||
};
|
||||
}
|
||||
|
||||
public static String getResourceContent(String name) {
|
||||
try (var stream = Objects.requireNonNull(FileUtil.class.getResourceAsStream(name))) {
|
||||
return new String(stream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package ee.carlrobert.codegpt
|
||||
|
||||
import com.intellij.notification.NotificationAction
|
||||
import com.intellij.notification.NotificationType
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.startup.ProjectActivity
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import ee.carlrobert.codegpt.actions.editor.EditorActionsUtil
|
||||
import ee.carlrobert.codegpt.completions.you.YouUserManager
|
||||
import ee.carlrobert.codegpt.completions.you.auth.AuthenticationHandler
|
||||
|
|
@ -13,8 +15,11 @@ import ee.carlrobert.codegpt.completions.you.auth.response.YouAuthenticationResp
|
|||
import ee.carlrobert.codegpt.credentials.CredentialsStore
|
||||
import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey
|
||||
import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
|
||||
import ee.carlrobert.codegpt.settings.service.you.YouSettings
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.AttachImageNotifier
|
||||
import ee.carlrobert.codegpt.ui.OverlayUtil
|
||||
import java.nio.file.Paths
|
||||
|
||||
class CodeGPTProjectActivity : ProjectActivity {
|
||||
|
||||
|
|
@ -23,12 +28,24 @@ class CodeGPTProjectActivity : ProjectActivity {
|
|||
CredentialsStore.loadAll()
|
||||
|
||||
if (YouUserManager.getInstance().authenticationResponse == null) {
|
||||
ApplicationManager.getApplication()
|
||||
.executeOnPooledThread { this.handleYouServiceAuthentication() }
|
||||
handleYouServiceAuthenticationAsync()
|
||||
}
|
||||
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode
|
||||
&& ConfigurationSettings.getCurrentState().isCheckForNewScreenshots
|
||||
) {
|
||||
val pathToWatch = Paths.get(System.getProperty("user.home"), "Desktop")
|
||||
val fileWatcher = FileWatcher(pathToWatch)
|
||||
fileWatcher.watch {
|
||||
if (listOf("jpg", "jpeg", "png").contains(it.extension)) {
|
||||
showImageAttachmentNotification(project, it.absolutePath)
|
||||
}
|
||||
}
|
||||
Disposer.register(project, fileWatcher)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleYouServiceAuthentication() {
|
||||
private fun handleYouServiceAuthenticationAsync() {
|
||||
val settings = YouSettings.getCurrentState()
|
||||
val password = getCredential(CredentialKey.YOU_ACCOUNT_PASSWORD)
|
||||
if (settings.email.isNotEmpty() && !password.isNullOrEmpty()) {
|
||||
|
|
@ -57,4 +74,27 @@ class CodeGPTProjectActivity : ProjectActivity {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun showImageAttachmentNotification(project: Project, filePath: String) {
|
||||
OverlayUtil.getDefaultNotification(
|
||||
CodeGPTBundle.get("imageAttachmentNotification.content"),
|
||||
NotificationType.INFORMATION
|
||||
)
|
||||
.addAction(NotificationAction.createSimpleExpiring(
|
||||
CodeGPTBundle.get("imageAttachmentNotification.action")
|
||||
) {
|
||||
CodeGPTKeys.IMAGE_ATTACHMENT_FILE_PATH.set(project, filePath)
|
||||
project.messageBus
|
||||
.syncPublisher<AttachImageNotifier>(
|
||||
AttachImageNotifier.IMAGE_ATTACHMENT_FILE_PATH_TOPIC
|
||||
)
|
||||
.imageAttached(filePath)
|
||||
})
|
||||
.addAction(NotificationAction.createSimpleExpiring(
|
||||
CodeGPTBundle.get("shared.notification.doNotShowAgain")
|
||||
) {
|
||||
ConfigurationSettings.getCurrentState().isCheckForNewScreenshots = false
|
||||
})
|
||||
.notify(project)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package ee.carlrobert.codegpt;
|
||||
package ee.carlrobert.codegpt
|
||||
|
||||
import com.intellij.ide.plugins.InstalledPluginsState
|
||||
import com.intellij.notification.NotificationAction
|
||||
|
|
@ -39,7 +39,7 @@ class CodeGPTUpdateActivity : ProjectActivity {
|
|||
Task.Backgroundable(project, CodeGPTBundle.get("checkForUpdatesTask.title"), true) {
|
||||
override fun run(indicator: ProgressIndicator) {
|
||||
val isLatestVersion =
|
||||
!InstalledPluginsState.getInstance().hasNewerVersion(CodeGPTPlugin.CODEGPT_ID);
|
||||
!InstalledPluginsState.getInstance().hasNewerVersion(CodeGPTPlugin.CODEGPT_ID)
|
||||
if (project.isDisposed || isLatestVersion) {
|
||||
return
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@ class CodeGPTUpdateActivity : ProjectActivity {
|
|||
.executeOnPooledThread { installCodeGPTUpdate(project) }
|
||||
})
|
||||
.addAction(NotificationAction.createSimpleExpiring(
|
||||
CodeGPTBundle.get("checkForUpdatesTask.notification.hideButton")
|
||||
CodeGPTBundle.get("shared.notification.doNotShowAgain")
|
||||
) {
|
||||
ConfigurationSettings.getCurrentState().isCheckForPluginUpdates = false
|
||||
})
|
||||
|
|
|
|||
29
src/main/kotlin/ee/carlrobert/codegpt/FileWatcher.kt
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package ee.carlrobert.codegpt
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor
|
||||
import org.apache.commons.io.monitor.FileAlterationMonitor
|
||||
import org.apache.commons.io.monitor.FileAlterationObserver
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
|
||||
class FileWatcher(private val pathToWatch: Path) : Disposable {
|
||||
|
||||
private val fileMonitor =
|
||||
FileAlterationMonitor(500, FileAlterationObserver(pathToWatch.toFile()))
|
||||
|
||||
fun watch(onFileCreated: (File) -> Unit) {
|
||||
val observer = FileAlterationObserver(pathToWatch.toFile())
|
||||
observer.addListener(object : FileAlterationListenerAdaptor() {
|
||||
override fun onFileCreate(file: File) {
|
||||
onFileCreated(file)
|
||||
}
|
||||
})
|
||||
fileMonitor.addObserver(observer)
|
||||
fileMonitor.start()
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
fileMonitor.stop()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package ee.carlrobert.codegpt.actions
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.fileChooser.FileChooser
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptor
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle
|
||||
import ee.carlrobert.codegpt.CodeGPTKeys
|
||||
import ee.carlrobert.codegpt.Icons
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.AttachImageNotifier
|
||||
|
||||
class AttachImageAction : AnAction(
|
||||
CodeGPTBundle.get("action.attachImage"),
|
||||
CodeGPTBundle.get("action.attachImageDescription"),
|
||||
Icons.Upload
|
||||
) {
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
FileChooser.chooseFiles(createSingleImageFileDescriptor(), e.project, null).also { files ->
|
||||
if (files.isNotEmpty()) {
|
||||
check(files.size == 1) { "Expected exactly one file to be selected" }
|
||||
e.project?.let { project ->
|
||||
CodeGPTKeys.IMAGE_ATTACHMENT_FILE_PATH[project] = files.first().path
|
||||
project.messageBus
|
||||
.syncPublisher(AttachImageNotifier.IMAGE_ATTACHMENT_FILE_PATH_TOPIC)
|
||||
.imageAttached(files.first().path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSingleImageFileDescriptor() = FileChooserDescriptor(
|
||||
true, false, false, false, false, false
|
||||
).apply {
|
||||
withFileFilter { file ->
|
||||
file.extension in listOf("jpg", "jpeg", "png")
|
||||
}
|
||||
withTitle(CodeGPTBundle.get("imageFileChooser.title"))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package ee.carlrobert.codegpt.actions
|
||||
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType.LLAMA_CPP
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType.OPENAI
|
||||
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings
|
||||
import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings
|
||||
|
||||
abstract class CodeCompletionFeatureToggleActions(
|
||||
private val enableFeatureAction: Boolean
|
||||
) : DumbAwareAction() {
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
GeneralSettings.getCurrentState().selectedService
|
||||
.takeIf { it in listOf(OPENAI, LLAMA_CPP) }
|
||||
?.also { selectedService ->
|
||||
if (OPENAI == selectedService) {
|
||||
OpenAISettings.getCurrentState().isCodeCompletionsEnabled = enableFeatureAction
|
||||
} else {
|
||||
LlamaSettings.getCurrentState().isCodeCompletionsEnabled = enableFeatureAction
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val selectedService = GeneralSettings.getCurrentState().selectedService
|
||||
val codeCompletionEnabled = isCodeCompletionsEnabled(selectedService)
|
||||
e.presentation.isEnabled = codeCompletionEnabled != enableFeatureAction
|
||||
e.presentation.isVisible =
|
||||
e.presentation.isEnabled && listOf(OPENAI, LLAMA_CPP).contains(
|
||||
selectedService
|
||||
)
|
||||
}
|
||||
|
||||
override fun getActionUpdateThread(): ActionUpdateThread {
|
||||
return ActionUpdateThread.BGT
|
||||
}
|
||||
|
||||
private fun isCodeCompletionsEnabled(serviceType: ServiceType): Boolean {
|
||||
return when (serviceType) {
|
||||
OPENAI -> OpenAISettings.getCurrentState().isCodeCompletionsEnabled
|
||||
LLAMA_CPP -> LlamaSettings.getCurrentState().isCodeCompletionsEnabled
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EnableCompletionsAction : CodeCompletionFeatureToggleActions(true)
|
||||
|
||||
class DisableCompletionsAction : CodeCompletionFeatureToggleActions(false)
|
||||
|
|
@ -2,34 +2,34 @@ package ee.carlrobert.codegpt.codecompletions
|
|||
|
||||
import ee.carlrobert.codegpt.completions.llama.LlamaModel
|
||||
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings
|
||||
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettingsState
|
||||
import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings
|
||||
import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest
|
||||
import ee.carlrobert.llm.client.openai.completion.request.OpenAITextCompletionRequest
|
||||
|
||||
object CodeCompletionRequestFactory {
|
||||
private const val MAX_TOKENS = 128
|
||||
|
||||
fun buildOpenAIRequest(details: InfillRequestDetails): OpenAITextCompletionRequest {
|
||||
return OpenAITextCompletionRequest.Builder(details.prefix)
|
||||
.setSuffix(details.suffix)
|
||||
.setStream(true)
|
||||
.setMaxTokens(MAX_TOKENS)
|
||||
.setMaxTokens(OpenAISettings.getCurrentState().codeCompletionMaxTokens)
|
||||
.setTemperature(0.4)
|
||||
.build()
|
||||
}
|
||||
|
||||
fun buildLlamaRequest(details: InfillRequestDetails): LlamaCompletionRequest {
|
||||
val promptTemplate = getLlamaInfillPromptTemplate()
|
||||
val settings = LlamaSettings.getCurrentState()
|
||||
val promptTemplate = getLlamaInfillPromptTemplate(settings)
|
||||
val prompt = promptTemplate.buildPrompt(details.prefix, details.suffix)
|
||||
return LlamaCompletionRequest.Builder(prompt)
|
||||
.setN_predict(MAX_TOKENS)
|
||||
.setN_predict(settings.codeCompletionMaxTokens)
|
||||
.setStream(true)
|
||||
.setTemperature(0.4)
|
||||
.setStop(promptTemplate.stopTokens)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun getLlamaInfillPromptTemplate(): InfillPromptTemplate {
|
||||
val settings = LlamaSettings.getCurrentState()
|
||||
private fun getLlamaInfillPromptTemplate(settings: LlamaSettingsState): InfillPromptTemplate {
|
||||
if (!settings.isRunLocalServer) {
|
||||
return settings.remoteModelInfillPromptTemplate
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import com.intellij.codeInsight.inline.completion.InlineCompletionEvent
|
|||
import com.intellij.codeInsight.inline.completion.InlineCompletionProvider
|
||||
import com.intellij.codeInsight.inline.completion.InlineCompletionProviderID
|
||||
import com.intellij.codeInsight.inline.completion.InlineCompletionRequest
|
||||
import com.intellij.codeInsight.inline.completion.elements.InlineCompletionElement
|
||||
import com.intellij.codeInsight.inline.completion.elements.InlineCompletionGrayTextElement
|
||||
import com.intellij.codeInsight.inline.completion.suggestion.InlineCompletionSingleSuggestion
|
||||
import com.intellij.codeInsight.inline.completion.suggestion.InlineCompletionSuggestionUpdateManager
|
||||
|
|
@ -15,14 +14,18 @@ import com.intellij.codeInsight.inline.completion.suggestion.InlineCompletionVar
|
|||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import ee.carlrobert.codegpt.completions.CompletionRequestService
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType
|
||||
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings
|
||||
import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings
|
||||
import ee.carlrobert.codegpt.treesitter.CodeCompletionParserFactory
|
||||
import ee.carlrobert.llm.completion.CompletionEventListener
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.ProducerScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.sse.EventSource
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
|
|
@ -33,7 +36,6 @@ class CodeGPTInlineCompletionProvider : InlineCompletionProvider {
|
|||
}
|
||||
|
||||
private val currentCall = AtomicReference<EventSource>(null)
|
||||
private val providerScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
|
||||
override val id: InlineCompletionProviderID
|
||||
get() = InlineCompletionProviderID("CodeGPTInlineCompletionProvider")
|
||||
|
|
@ -51,11 +53,18 @@ class CodeGPTInlineCompletionProvider : InlineCompletionProvider {
|
|||
val infillRequest = withContext(Dispatchers.EDT) {
|
||||
InfillRequestDetails.fromInlineCompletionRequest(request)
|
||||
}
|
||||
cancelCurrentCall()
|
||||
currentCall.set(
|
||||
CompletionRequestService.getInstance().getCodeCompletionAsync(
|
||||
infillRequest,
|
||||
getCodeCompletionEventListener(infillRequest)
|
||||
CodeCompletionEventListener(infillRequest) {
|
||||
launch {
|
||||
try {
|
||||
trySend(InlineCompletionGrayTextElement(it))
|
||||
} catch (e: Exception) {
|
||||
LOG.error("Failed to send inline completion suggestion", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
awaitClose { cancelCurrentCall() }
|
||||
|
|
@ -63,20 +72,13 @@ class CodeGPTInlineCompletionProvider : InlineCompletionProvider {
|
|||
}
|
||||
|
||||
override fun isEnabled(event: InlineCompletionEvent): Boolean {
|
||||
return event is InlineCompletionEvent.DocumentChange
|
||||
&& ConfigurationSettings.getCurrentState().isCodeCompletionsEnabled
|
||||
}
|
||||
|
||||
private fun ProducerScope<InlineCompletionElement>.getCodeCompletionEventListener(
|
||||
infillRequest: InfillRequestDetails
|
||||
) = CodeCompletionEventListener(infillRequest) {
|
||||
providerScope.launch {
|
||||
try {
|
||||
send(InlineCompletionGrayTextElement(it))
|
||||
} catch (e: Exception) {
|
||||
LOG.error("Failed to send inline completion suggestion", e)
|
||||
}
|
||||
val selectedService = GeneralSettings.getCurrentState().selectedService
|
||||
val codeCompletionsEnabled = when (selectedService) {
|
||||
ServiceType.OPENAI -> OpenAISettings.getCurrentState().isCodeCompletionsEnabled
|
||||
ServiceType.LLAMA_CPP -> LlamaSettings.getCurrentState().isCodeCompletionsEnabled
|
||||
else -> false
|
||||
}
|
||||
return event is InlineCompletionEvent.DocumentChange && codeCompletionsEnabled
|
||||
}
|
||||
|
||||
private fun cancelCurrentCall() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package ee.carlrobert.codegpt.settings.service
|
||||
|
||||
import com.intellij.openapi.ui.panel.ComponentPanelBuilder
|
||||
import com.intellij.ui.components.JBCheckBox
|
||||
import com.intellij.ui.components.fields.IntegerField
|
||||
import com.intellij.util.ui.FormBuilder
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle
|
||||
import javax.swing.JPanel
|
||||
|
||||
class CodeCompletionConfigurationForm(codeCompletionsEnabled: Boolean, maxTokens: Int) {
|
||||
|
||||
private val codeCompletionsEnabledCheckBox = JBCheckBox(
|
||||
CodeGPTBundle.get("codeCompletionsForm.enableFeatureText"),
|
||||
codeCompletionsEnabled
|
||||
)
|
||||
private val codeCompletionMaxTokensField =
|
||||
IntegerField("completion_max_tokens", 8, 4096).apply {
|
||||
columns = 12
|
||||
value = maxTokens
|
||||
}
|
||||
|
||||
fun getForm(): JPanel {
|
||||
return FormBuilder.createFormBuilder()
|
||||
.addComponent(codeCompletionsEnabledCheckBox)
|
||||
.addVerticalGap(4)
|
||||
.addLabeledComponent(
|
||||
CodeGPTBundle.get("codeCompletionsForm.maxTokensLabel"),
|
||||
codeCompletionMaxTokensField
|
||||
)
|
||||
.addComponentToRightColumn(
|
||||
ComponentPanelBuilder.createCommentComponent(
|
||||
CodeGPTBundle.get("codeCompletionsForm.maxTokensComment"), true, 48, true
|
||||
)
|
||||
)
|
||||
.panel
|
||||
}
|
||||
|
||||
var isCodeCompletionsEnabled: Boolean
|
||||
get() = codeCompletionsEnabledCheckBox.isSelected
|
||||
set(enabled) {
|
||||
codeCompletionsEnabledCheckBox.isSelected = enabled
|
||||
}
|
||||
|
||||
var maxTokens: Int
|
||||
get() = codeCompletionMaxTokensField.value
|
||||
set(maxTokens) {
|
||||
codeCompletionMaxTokensField.value = maxTokens
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.wm.ToolWindow
|
||||
import com.intellij.openapi.wm.ex.ToolWindowManagerListener
|
||||
|
||||
class ChatToolWindowListener : ToolWindowManagerListener {
|
||||
|
||||
override fun toolWindowShown(toolWindow: ToolWindow) {
|
||||
if ("CodeGPT" == toolWindow.id) {
|
||||
requestFocusForTextArea(toolWindow.project)
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestFocusForTextArea(project: Project) {
|
||||
val contentManager = project.getService(ChatToolWindowContentManager::class.java)
|
||||
contentManager.tryFindChatTabbedPane().ifPresent { tabbedPane ->
|
||||
tabbedPane.tryFindActiveTabPanel().ifPresent { tabPanel ->
|
||||
tabPanel.requestFocusForTextArea()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.ui
|
||||
|
||||
import com.intellij.ui.components.ActionLink
|
||||
import com.intellij.util.ui.JBUI
|
||||
import ee.carlrobert.codegpt.Icons
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.ResponsePanel
|
||||
import ee.carlrobert.codegpt.ui.UIUtil.createTextPane
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Point
|
||||
import java.awt.event.ActionListener
|
||||
import javax.swing.Box
|
||||
import javax.swing.BoxLayout
|
||||
import javax.swing.JPanel
|
||||
|
||||
class ChatToolWindowLandingPanel(onAction: (LandingPanelAction, Point) -> Unit) : ResponsePanel() {
|
||||
|
||||
init {
|
||||
addContent(createContent(onAction))
|
||||
}
|
||||
|
||||
private fun createContent(onAction: (LandingPanelAction, Point) -> Unit): JPanel {
|
||||
return JPanel(BorderLayout()).apply {
|
||||
add(createTextPane(getWelcomeMessage(), false), BorderLayout.NORTH)
|
||||
add(createActionsListPanel(onAction), BorderLayout.CENTER)
|
||||
add(createTextPane(getCautionMessage(), false), BorderLayout.SOUTH)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createActionsListPanel(onAction: (LandingPanelAction, Point) -> Unit): JPanel {
|
||||
val listPanel = JPanel()
|
||||
listPanel.layout = BoxLayout(listPanel, BoxLayout.PAGE_AXIS)
|
||||
listPanel.border = JBUI.Borders.emptyLeft(4)
|
||||
listPanel.add(Box.createVerticalStrut(4))
|
||||
listPanel.add(createEditorActionLink(LandingPanelAction.WRITE_TESTS, onAction))
|
||||
listPanel.add(Box.createVerticalStrut(4))
|
||||
listPanel.add(createEditorActionLink(LandingPanelAction.EXPLAIN, onAction))
|
||||
listPanel.add(Box.createVerticalStrut(4))
|
||||
listPanel.add(createEditorActionLink(LandingPanelAction.FIND_BUGS, onAction))
|
||||
listPanel.add(Box.createVerticalStrut(4))
|
||||
return listPanel
|
||||
}
|
||||
|
||||
private fun createEditorActionLink(
|
||||
action: LandingPanelAction,
|
||||
onAction: (LandingPanelAction, Point) -> Unit
|
||||
): ActionLink {
|
||||
return ActionLink(action.userMessage, ActionListener { event ->
|
||||
onAction(action, (event.source as ActionLink).locationOnScreen)
|
||||
}).apply {
|
||||
icon = Icons.Sparkle
|
||||
}
|
||||
}
|
||||
|
||||
private fun getWelcomeMessage(): String {
|
||||
return """
|
||||
<html>
|
||||
<p style="margin-top: 4px; margin-bottom: 4px;">
|
||||
Welcome <strong>${GeneralSettings.getCurrentState().displayName}</strong>, I'm your intelligent code companion, here to be your partner-in-crime for getting things done in a flash.
|
||||
</p>
|
||||
<p style="margin-top: 4px; margin-bottom: 4px;">
|
||||
Feel free to ask me anything you'd like, but my true superpower lies in assisting you with your code! Here are a few examples of how I can assist you:
|
||||
</p>
|
||||
</html>
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
private fun getCautionMessage(): String {
|
||||
return """
|
||||
<html>
|
||||
<p style="margin-top: 4px; margin-bottom: 4px;">
|
||||
Being an AI-powered assistant, I may occasionally have surprises or make mistakes. Therefore, it's wise to double-check any code or suggestions I provide.
|
||||
</p>
|
||||
</html>
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
enum class LandingPanelAction(
|
||||
val label: String,
|
||||
val userMessage: String,
|
||||
val prompt: String
|
||||
) {
|
||||
FIND_BUGS(
|
||||
"Find Bugs",
|
||||
"Find bugs in the selected code",
|
||||
"Find bugs and output code with bugs fixed in the selected code: {{selectedCode}}"
|
||||
),
|
||||
WRITE_TESTS(
|
||||
"Write Tests",
|
||||
"Write unit tests for the selected code",
|
||||
"Write unit tests for the selected code: {{selectedCode}}"
|
||||
),
|
||||
EXPLAIN(
|
||||
"Explain",
|
||||
"Explain the selected code",
|
||||
"Explain the selected code: {{selectedCode}}"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -9,6 +9,8 @@
|
|||
<projectListeners>
|
||||
<listener topic="com.intellij.codeInsight.lookup.LookupManagerListener"
|
||||
class="ee.carlrobert.codegpt.completions.MethodNameLookupListener"/>
|
||||
<listener class="ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowListener"
|
||||
topic="com.intellij.openapi.wm.ex.ToolWindowManagerListener"/>
|
||||
</projectListeners>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
|
||||
<svg width="20px" height="20px" viewBox="0 0 24.00 24.00" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#7a8183">
|
||||
<svg width="18px" height="18px" viewBox="0 0 24.00 24.00" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#7a8183">
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
|
||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<g id="SVGRepo_iconCarrier"> <path d="M10.5004 11.9998H5.00043M4.91577 12.2913L2.58085 19.266C2.39742 19.8139 2.3057 20.0879 2.37152 20.2566C2.42868 20.4031 2.55144 20.5142 2.70292 20.5565C2.87736 20.6052 3.14083 20.4866 3.66776 20.2495L20.3792 12.7293C20.8936 12.4979 21.1507 12.3822 21.2302 12.2214C21.2993 12.0817 21.2993 11.9179 21.2302 11.7782C21.1507 11.6174 20.8936 11.5017 20.3792 11.2703L3.66193 3.74751C3.13659 3.51111 2.87392 3.39291 2.69966 3.4414C2.54832 3.48351 2.42556 3.59429 2.36821 3.74054C2.30216 3.90893 2.3929 4.18231 2.57437 4.72906L4.91642 11.7853C4.94759 11.8792 4.96317 11.9262 4.96933 11.9742C4.97479 12.0168 4.97473 12.0599 4.96916 12.1025C4.96289 12.1506 4.94718 12.1975 4.91577 12.2913Z" stroke="#7a8183" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/> </g>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
|
@ -1,6 +1,6 @@
|
|||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
|
||||
<svg width="20px" height="20px" viewBox="0 0 24.00 24.00" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#ffffff">
|
||||
<svg width="18px" height="18px" viewBox="0 0 24.00 24.00" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#ffffff">
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
|
||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<g id="SVGRepo_iconCarrier"> <path d="M10.5004 11.9998H5.00043M4.91577 12.2913L2.58085 19.266C2.39742 19.8139 2.3057 20.0879 2.37152 20.2566C2.42868 20.4031 2.55144 20.5142 2.70292 20.5565C2.87736 20.6052 3.14083 20.4866 3.66776 20.2495L20.3792 12.7293C20.8936 12.4979 21.1507 12.3822 21.2302 12.2214C21.2993 12.0817 21.2993 11.9179 21.2302 11.7782C21.1507 11.6174 20.8936 11.5017 20.3792 11.2703L3.66193 3.74751C3.13659 3.51111 2.87392 3.39291 2.69966 3.4414C2.54832 3.48351 2.42556 3.59429 2.36821 3.74054C2.30216 3.90893 2.3929 4.18231 2.57437 4.72906L4.91642 11.7853C4.94759 11.8792 4.96317 11.9262 4.96933 11.9742C4.97479 12.0168 4.97473 12.0599 4.96916 12.1025C4.96289 12.1506 4.94718 12.1975 4.91577 12.2913Z" stroke="#ffffff" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/> </g>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
7
src/main/resources/icons/upload.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.8535 4.6463C11.0488 4.84156 11.0488 5.15814 10.8536 5.35341C10.6583 5.54868 10.3417 5.54868 10.1465 5.35343L8.49972 3.70677L8.49972 10.5142C8.49972 10.7903 8.27586 11.0142 7.99972 11.0142C7.72358 11.0142 7.49972 10.7903 7.49972 10.5142L7.49972 3.70729L5.85343 5.35356C5.65816 5.54882 5.34158 5.54881 5.14632 5.35355C4.95106 5.15829 4.95106 4.8417 5.14633 4.64644L7.64642 2.1464C7.84167 1.95114 8.15825 1.95114 8.35351 2.14639L10.8535 4.6463Z" fill="#6C707E"/>
|
||||
<rect width="12" height="1" rx="0.5" transform="matrix(1 0 0 -1 2 14)" fill="#6C707E"/>
|
||||
<rect x="2" y="10" width="1" height="4" rx="0.5" fill="#6C707E"/>
|
||||
<rect x="13" y="10" width="1" height="4" rx="0.5" fill="#6C707E"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 964 B |
7
src/main/resources/icons/upload_dark.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.8535 4.6463C11.0488 4.84156 11.0488 5.15814 10.8536 5.35341C10.6583 5.54868 10.3417 5.54868 10.1465 5.35343L8.49972 3.70677L8.49972 10.5142C8.49972 10.7903 8.27586 11.0142 7.99972 11.0142C7.72358 11.0142 7.49972 10.7903 7.49972 10.5142L7.49972 3.70729L5.85343 5.35356C5.65816 5.54882 5.34158 5.54881 5.14632 5.35355C4.95106 5.15829 4.95106 4.8417 5.14633 4.64644L7.64642 2.1464C7.84167 1.95114 8.15825 1.95114 8.35351 2.14639L10.8535 4.6463Z" fill="#CED0D6"/>
|
||||
<rect width="12" height="1" rx="0.5" transform="matrix(1 0 0 -1 2 14)" fill="#CED0D6"/>
|
||||
<rect x="2" y="10" width="1" height="4" rx="0.5" fill="#CED0D6"/>
|
||||
<rect x="13" y="10" width="1" height="4" rx="0.5" fill="#CED0D6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 964 B |
|
|
@ -87,6 +87,7 @@ configurationConfigurable.table.header.promptColumnLabel=Prompt
|
|||
configurationConfigurable.table.action.revertToDefaults.text=Revert to Defaults
|
||||
configurationConfigurable.table.action.addKeymap.text=Add Shortcut
|
||||
configurationConfigurable.checkForPluginUpdates.label=Check for plugin updates automatically
|
||||
configurationConfigurable.checkForNewScreenshots.label=Check for new screenshots automatically
|
||||
configurationConfigurable.openNewTabCheckBox.label=Open a new chat on each action
|
||||
configurationConfigurable.enableMethodNameGeneration.label=Enable method name lookup suggestions
|
||||
configurationConfigurable.autoFormatting.label=Enable automatic code formatting
|
||||
|
|
@ -178,7 +179,6 @@ validation.error.mustBeGreaterThanZero=Value must be greater than 0
|
|||
checkForUpdatesTask.title=Checking for CodeGPT update...
|
||||
checkForUpdatesTask.notification.message=An update for CodeGPT is available.
|
||||
checkForUpdatesTask.notification.installButton=Install update
|
||||
checkForUpdatesTask.notification.hideButton=Do not show again
|
||||
llamaServerAgent.buildingProject.description=Building llama.cpp...
|
||||
llamaServerAgent.serverBootup.description=Booting up server...
|
||||
notification.compilationError.description=CodeGPT has detected a compilation error. Would you like assistance in resolving it?
|
||||
|
|
@ -190,4 +190,15 @@ shared.infillPromptTemplate=Infill template:
|
|||
shared.apiVersion=API version:
|
||||
shared.configuration=Configuration
|
||||
shared.port=Port:
|
||||
codeCompletion.progress.title=Code completion in progress
|
||||
shared.notification.doNotShowAgain=Do not show again
|
||||
codeCompletion.progress.title=Code completion in progress
|
||||
imageAttachmentNotification.content=New image detected on desktop. Would you like to attach it to your current conversation?
|
||||
imageAttachmentNotification.action=Attach image
|
||||
action.attachImage=Attach Image
|
||||
action.attachImageDescription=Attach an image
|
||||
imageFileChooser.title=Select Image
|
||||
imageAccordion.title=Attached image
|
||||
shared.codeCompletions=Code Completions
|
||||
codeCompletionsForm.enableFeatureText=Enable code completions
|
||||
codeCompletionsForm.maxTokensLabel=Max tokens:
|
||||
codeCompletionsForm.maxTokensComment=The maximum number of tokens that can be generated in the code completion.
|
||||
|
|
|
|||
|
|
@ -6,10 +6,8 @@ import static ee.carlrobert.llm.client.util.JSONUtil.e;
|
|||
import static ee.carlrobert.llm.client.util.JSONUtil.jsonArray;
|
||||
import static ee.carlrobert.llm.client.util.JSONUtil.jsonMap;
|
||||
import static ee.carlrobert.llm.client.util.JSONUtil.jsonMapResponse;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.apache.http.HttpHeaders.AUTHORIZATION;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
|
||||
import ee.carlrobert.codegpt.CodeGPTPlugin;
|
||||
import ee.carlrobert.codegpt.conversations.ConversationService;
|
||||
|
|
@ -50,7 +48,7 @@ public class DefaultCompletionRequestHandlerTest extends IntegrationTest {
|
|||
|
||||
requestHandler.call(new CallParameters(conversation, ConversationType.DEFAULT, message, false));
|
||||
|
||||
await().atMost(5, SECONDS).until(() -> "Hello!".equals(message.getResponse()));
|
||||
waitExpecting(() -> "Hello!".equals(message.getResponse()));
|
||||
}
|
||||
|
||||
public void testAzureChatCompletionCall() {
|
||||
|
|
@ -86,7 +84,7 @@ public class DefaultCompletionRequestHandlerTest extends IntegrationTest {
|
|||
|
||||
requestHandler.call(new CallParameters(conversation, ConversationType.DEFAULT, message, false));
|
||||
|
||||
await().atMost(5, SECONDS).until(() -> "Hello!".equals(message.getResponse()));
|
||||
waitExpecting(() -> "Hello!".equals(message.getResponse()));
|
||||
}
|
||||
|
||||
public void testYouChatCompletionCall() {
|
||||
|
|
@ -137,7 +135,7 @@ public class DefaultCompletionRequestHandlerTest extends IntegrationTest {
|
|||
|
||||
requestHandler.call(new CallParameters(conversation, ConversationType.DEFAULT, message, false));
|
||||
|
||||
await().atMost(5, SECONDS).until(() -> "Hello!".equals(message.getResponse()));
|
||||
waitExpecting(() -> "Hello!".equals(message.getResponse()));
|
||||
}
|
||||
|
||||
public void testLlamaChatCompletionCall() {
|
||||
|
|
@ -171,7 +169,7 @@ public class DefaultCompletionRequestHandlerTest extends IntegrationTest {
|
|||
|
||||
requestHandler.call(new CallParameters(conversation, ConversationType.DEFAULT, message, false));
|
||||
|
||||
await().atMost(5, SECONDS).until(() -> "Hello!".equals(message.getResponse()));
|
||||
waitExpecting(() -> "Hello!".equals(message.getResponse()));
|
||||
}
|
||||
|
||||
private CompletionResponseEventListener getRequestEventListener(Message message) {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,9 @@ import static ee.carlrobert.llm.client.util.JSONUtil.e;
|
|||
import static ee.carlrobert.llm.client.util.JSONUtil.jsonArray;
|
||||
import static ee.carlrobert.llm.client.util.JSONUtil.jsonMap;
|
||||
import static ee.carlrobert.llm.client.util.JSONUtil.jsonMapResponse;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.apache.http.HttpHeaders.AUTHORIZATION;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
|
||||
import ee.carlrobert.codegpt.CodeGPTKeys;
|
||||
import ee.carlrobert.codegpt.EncodingManager;
|
||||
|
|
@ -21,20 +20,23 @@ import ee.carlrobert.codegpt.conversations.ConversationService;
|
|||
import ee.carlrobert.codegpt.conversations.message.Message;
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings;
|
||||
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowTabPanel;
|
||||
import ee.carlrobert.llm.client.http.exchange.StreamHttpExchange;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import testsupport.IntegrationTest;
|
||||
|
||||
public class StandardChatToolWindowTabPanelTest extends IntegrationTest {
|
||||
public class ChatToolWindowTabPanelTest extends IntegrationTest {
|
||||
|
||||
public void testSendingOpenAIMessage() {
|
||||
useOpenAIService();
|
||||
ConfigurationSettings.getCurrentState().setSystemPrompt(COMPLETION_SYSTEM_PROMPT);
|
||||
var message = new Message("Hello!");
|
||||
var conversation = ConversationService.getInstance().startConversation();
|
||||
var panel = new StandardChatToolWindowTabPanel(getProject(), conversation);
|
||||
var panel = new ChatToolWindowTabPanel(getProject(), conversation);
|
||||
expectOpenAI((StreamHttpExchange) request -> {
|
||||
assertThat(request.getUri().getPath()).isEqualTo("/v1/chat/completions");
|
||||
assertThat(request.getMethod()).isEqualTo("POST");
|
||||
|
|
@ -57,11 +59,10 @@ public class StandardChatToolWindowTabPanelTest extends IntegrationTest {
|
|||
|
||||
panel.sendMessage(message);
|
||||
|
||||
await().atMost(5, SECONDS)
|
||||
.until(() -> {
|
||||
var messages = conversation.getMessages();
|
||||
return !messages.isEmpty() && "Hello!".equals(messages.get(0).getResponse());
|
||||
});
|
||||
waitExpecting(() -> {
|
||||
var messages = conversation.getMessages();
|
||||
return !messages.isEmpty() && "Hello!".equals(messages.get(0).getResponse());
|
||||
});
|
||||
var encodingManager = EncodingManager.getInstance();
|
||||
assertThat(panel.getTokenDetails()).extracting(
|
||||
"systemPromptTokens",
|
||||
|
|
@ -100,7 +101,7 @@ public class StandardChatToolWindowTabPanelTest extends IntegrationTest {
|
|||
message.setReferencedFilePaths(
|
||||
List.of("TEST_FILE_PATH_1", "TEST_FILE_PATH_2", "TEST_FILE_PATH_3"));
|
||||
var conversation = ConversationService.getInstance().startConversation();
|
||||
var panel = new StandardChatToolWindowTabPanel(getProject(), conversation);
|
||||
var panel = new ChatToolWindowTabPanel(getProject(), conversation);
|
||||
expectOpenAI((StreamHttpExchange) request -> {
|
||||
assertThat(request.getUri().getPath()).isEqualTo("/v1/chat/completions");
|
||||
assertThat(request.getMethod()).isEqualTo("POST");
|
||||
|
|
@ -114,23 +115,28 @@ public class StandardChatToolWindowTabPanelTest extends IntegrationTest {
|
|||
List.of(
|
||||
Map.of("role", "system", "content", COMPLETION_SYSTEM_PROMPT),
|
||||
Map.of("role", "user", "content",
|
||||
"Use the following context to answer question at the end:\n\n"
|
||||
+ "File Path: TEST_FILE_PATH_1\n"
|
||||
+ "File Content:\n"
|
||||
+ "```TEST_FILE_NAME_1\n"
|
||||
+ "TEST_FILE_CONTENT_1\n"
|
||||
+ "```\n\n"
|
||||
+ "File Path: TEST_FILE_PATH_2\n"
|
||||
+ "File Content:\n"
|
||||
+ "```TEST_FILE_NAME_2\n"
|
||||
+ "TEST_FILE_CONTENT_2\n"
|
||||
+ "```\n\n"
|
||||
+ "File Path: TEST_FILE_PATH_3\n"
|
||||
+ "File Content:\n"
|
||||
+ "```TEST_FILE_NAME_3\n"
|
||||
+ "TEST_FILE_CONTENT_3\n"
|
||||
+ "```\n\n"
|
||||
+ "Question: TEST_MESSAGE")));
|
||||
"""
|
||||
Use the following context to answer question at the end:
|
||||
|
||||
File Path: TEST_FILE_PATH_1
|
||||
File Content:
|
||||
```TEST_FILE_NAME_1
|
||||
TEST_FILE_CONTENT_1
|
||||
```
|
||||
|
||||
File Path: TEST_FILE_PATH_2
|
||||
File Content:
|
||||
```TEST_FILE_NAME_2
|
||||
TEST_FILE_CONTENT_2
|
||||
```
|
||||
|
||||
File Path: TEST_FILE_PATH_3
|
||||
File Content:
|
||||
```TEST_FILE_NAME_3
|
||||
TEST_FILE_CONTENT_3
|
||||
```
|
||||
|
||||
Question: TEST_MESSAGE""")));
|
||||
return List.of(
|
||||
jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("role", "assistant")))),
|
||||
jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "Hel")))),
|
||||
|
|
@ -140,11 +146,10 @@ public class StandardChatToolWindowTabPanelTest extends IntegrationTest {
|
|||
|
||||
panel.sendMessage(message);
|
||||
|
||||
await().atMost(5, SECONDS)
|
||||
.until(() -> {
|
||||
var messages = conversation.getMessages();
|
||||
return !messages.isEmpty() && "Hello!".equals(messages.get(0).getResponse());
|
||||
});
|
||||
waitExpecting(() -> {
|
||||
var messages = conversation.getMessages();
|
||||
return !messages.isEmpty() && "Hello!".equals(messages.get(0).getResponse());
|
||||
});
|
||||
var encodingManager = EncodingManager.getInstance();
|
||||
assertThat(panel.getTokenDetails()).extracting(
|
||||
"systemPromptTokens",
|
||||
|
|
@ -175,6 +180,79 @@ public class StandardChatToolWindowTabPanelTest extends IntegrationTest {
|
|||
List.of("TEST_FILE_PATH_1", "TEST_FILE_PATH_2", "TEST_FILE_PATH_3"));
|
||||
}
|
||||
|
||||
public void testSendingOpenAIMessageWithImage() {
|
||||
var testImagePath = requireNonNull(getClass().getResource("/images/test-image.png")).getPath();
|
||||
getProject().putUserData(CodeGPTKeys.IMAGE_ATTACHMENT_FILE_PATH, testImagePath);
|
||||
useOpenAIService("gpt-4-vision-preview");
|
||||
ConfigurationSettings.getCurrentState().setSystemPrompt(COMPLETION_SYSTEM_PROMPT);
|
||||
var message = new Message("TEST_MESSAGE");
|
||||
var conversation = ConversationService.getInstance().startConversation();
|
||||
var panel = new ChatToolWindowTabPanel(getProject(), conversation);
|
||||
expectOpenAI((StreamHttpExchange) request -> {
|
||||
assertThat(request.getUri().getPath()).isEqualTo("/v1/chat/completions");
|
||||
assertThat(request.getMethod()).isEqualTo("POST");
|
||||
assertThat(request.getHeaders().get(AUTHORIZATION).get(0)).isEqualTo("Bearer TEST_API_KEY");
|
||||
try {
|
||||
var testImageUrl = "data:image/png;base64,"
|
||||
+ Base64.getEncoder().encodeToString(Files.readAllBytes(Path.of(testImagePath)));
|
||||
assertThat(request.getBody())
|
||||
.extracting("model", "messages")
|
||||
.containsExactly(
|
||||
"gpt-4-vision-preview",
|
||||
List.of(
|
||||
Map.of("role", "system", "content", COMPLETION_SYSTEM_PROMPT),
|
||||
Map.of("role", "user", "content", List.of(
|
||||
Map.of(
|
||||
"type", "image_url",
|
||||
"image_url", Map.of("url", testImageUrl)),
|
||||
Map.of("type", "text", "text", "TEST_MESSAGE")
|
||||
))));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return List.of(
|
||||
jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("role", "assistant")))),
|
||||
jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "Hel")))),
|
||||
jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "lo")))),
|
||||
jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "!")))));
|
||||
});
|
||||
|
||||
panel.sendMessage(message);
|
||||
|
||||
waitExpecting(() -> {
|
||||
var messages = conversation.getMessages();
|
||||
return !messages.isEmpty() && "Hello!".equals(messages.get(0).getResponse());
|
||||
});
|
||||
var encodingManager = EncodingManager.getInstance();
|
||||
assertThat(panel.getTokenDetails()).extracting(
|
||||
"systemPromptTokens",
|
||||
"conversationTokens",
|
||||
"userPromptTokens",
|
||||
"highlightedTokens")
|
||||
.containsExactly(
|
||||
encodingManager.countTokens(COMPLETION_SYSTEM_PROMPT),
|
||||
encodingManager.countTokens(message.getPrompt()),
|
||||
0,
|
||||
0);
|
||||
assertThat(panel.getConversation())
|
||||
.isNotNull()
|
||||
.extracting("id", "model", "clientCode", "discardTokenLimit")
|
||||
.containsExactly(
|
||||
conversation.getId(),
|
||||
conversation.getModel(),
|
||||
conversation.getClientCode(),
|
||||
false);
|
||||
var messages = panel.getConversation().getMessages();
|
||||
assertThat(messages.size()).isOne();
|
||||
assertThat(messages.get(0))
|
||||
.extracting("id", "prompt", "response", "imageFilePath")
|
||||
.containsExactly(
|
||||
message.getId(),
|
||||
message.getPrompt(),
|
||||
message.getResponse(),
|
||||
message.getImageFilePath());
|
||||
}
|
||||
|
||||
public void testFixCompileErrorsWithOpenAIService() {
|
||||
getProject().putUserData(CodeGPTKeys.SELECTED_FILES, List.of(
|
||||
new ReferencedFile("TEST_FILE_NAME_1", "TEST_FILE_PATH_1", "TEST_FILE_CONTENT_1"),
|
||||
|
|
@ -187,7 +265,7 @@ public class StandardChatToolWindowTabPanelTest extends IntegrationTest {
|
|||
message.setReferencedFilePaths(
|
||||
List.of("TEST_FILE_PATH_1", "TEST_FILE_PATH_2", "TEST_FILE_PATH_3"));
|
||||
var conversation = ConversationService.getInstance().startConversation();
|
||||
var panel = new StandardChatToolWindowTabPanel(getProject(), conversation);
|
||||
var panel = new ChatToolWindowTabPanel(getProject(), conversation);
|
||||
expectOpenAI((StreamHttpExchange) request -> {
|
||||
assertThat(request.getUri().getPath()).isEqualTo("/v1/chat/completions");
|
||||
assertThat(request.getMethod()).isEqualTo("POST");
|
||||
|
|
@ -201,23 +279,28 @@ public class StandardChatToolWindowTabPanelTest extends IntegrationTest {
|
|||
List.of(
|
||||
Map.of("role", "system", "content", FIX_COMPILE_ERRORS_SYSTEM_PROMPT),
|
||||
Map.of("role", "user", "content",
|
||||
"Use the following context to answer question at the end:\n\n"
|
||||
+ "File Path: TEST_FILE_PATH_1\n"
|
||||
+ "File Content:\n"
|
||||
+ "```TEST_FILE_NAME_1\n"
|
||||
+ "TEST_FILE_CONTENT_1\n"
|
||||
+ "```\n\n"
|
||||
+ "File Path: TEST_FILE_PATH_2\n"
|
||||
+ "File Content:\n"
|
||||
+ "```TEST_FILE_NAME_2\n"
|
||||
+ "TEST_FILE_CONTENT_2\n"
|
||||
+ "```\n\n"
|
||||
+ "File Path: TEST_FILE_PATH_3\n"
|
||||
+ "File Content:\n"
|
||||
+ "```TEST_FILE_NAME_3\n"
|
||||
+ "TEST_FILE_CONTENT_3\n"
|
||||
+ "```\n\n"
|
||||
+ "Question: TEST_MESSAGE")));
|
||||
"""
|
||||
Use the following context to answer question at the end:
|
||||
|
||||
File Path: TEST_FILE_PATH_1
|
||||
File Content:
|
||||
```TEST_FILE_NAME_1
|
||||
TEST_FILE_CONTENT_1
|
||||
```
|
||||
|
||||
File Path: TEST_FILE_PATH_2
|
||||
File Content:
|
||||
```TEST_FILE_NAME_2
|
||||
TEST_FILE_CONTENT_2
|
||||
```
|
||||
|
||||
File Path: TEST_FILE_PATH_3
|
||||
File Content:
|
||||
```TEST_FILE_NAME_3
|
||||
TEST_FILE_CONTENT_3
|
||||
```
|
||||
|
||||
Question: TEST_MESSAGE""")));
|
||||
return List.of(
|
||||
jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("role", "assistant")))),
|
||||
jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "Hel")))),
|
||||
|
|
@ -227,11 +310,10 @@ public class StandardChatToolWindowTabPanelTest extends IntegrationTest {
|
|||
|
||||
panel.sendMessage(message, ConversationType.FIX_COMPILE_ERRORS);
|
||||
|
||||
await().atMost(5, SECONDS)
|
||||
.until(() -> {
|
||||
var messages = conversation.getMessages();
|
||||
return !messages.isEmpty() && "Hello!".equals(messages.get(0).getResponse());
|
||||
});
|
||||
waitExpecting(() -> {
|
||||
var messages = conversation.getMessages();
|
||||
return !messages.isEmpty() && "Hello!".equals(messages.get(0).getResponse());
|
||||
});
|
||||
var encodingManager = EncodingManager.getInstance();
|
||||
assertThat(panel.getTokenDetails()).extracting(
|
||||
"systemPromptTokens",
|
||||
|
|
@ -277,7 +359,7 @@ public class StandardChatToolWindowTabPanelTest extends IntegrationTest {
|
|||
llamaSettings.setRepeatPenalty(1.3);
|
||||
var message = new Message("TEST_PROMPT");
|
||||
var conversation = ConversationService.getInstance().startConversation();
|
||||
var panel = new StandardChatToolWindowTabPanel(getProject(), conversation);
|
||||
var panel = new ChatToolWindowTabPanel(getProject(), conversation);
|
||||
expectLlama((StreamHttpExchange) request -> {
|
||||
assertThat(request.getUri().getPath()).isEqualTo("/completion");
|
||||
assertThat(request.getBody())
|
||||
|
|
@ -312,11 +394,10 @@ public class StandardChatToolWindowTabPanelTest extends IntegrationTest {
|
|||
|
||||
panel.sendMessage(message, ConversationType.DEFAULT);
|
||||
|
||||
await().atMost(5, SECONDS)
|
||||
.until(() -> {
|
||||
var messages = conversation.getMessages();
|
||||
return !messages.isEmpty() && "Hello!".equals(messages.get(0).getResponse());
|
||||
});
|
||||
waitExpecting(() -> {
|
||||
var messages = conversation.getMessages();
|
||||
return !messages.isEmpty() && "Hello!".equals(messages.get(0).getResponse());
|
||||
});
|
||||
assertThat(panel.getConversation())
|
||||
.isNotNull()
|
||||
.extracting("id", "model", "clientCode", "discardTokenLimit")
|
||||
|
|
@ -6,13 +6,11 @@ import com.intellij.openapi.util.Disposer;
|
|||
import com.intellij.testFramework.fixtures.BasePlatformTestCase;
|
||||
import ee.carlrobert.codegpt.conversations.ConversationService;
|
||||
import ee.carlrobert.codegpt.conversations.message.Message;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowTabPanel;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowTabbedPane;
|
||||
|
||||
public class StandardChatToolWindowTabbedPaneTest extends BasePlatformTestCase {
|
||||
public class ChatToolWindowTabbedPaneTest extends BasePlatformTestCase {
|
||||
|
||||
public void testClearAllTabs() {
|
||||
var tabbedPane = new StandardChatToolWindowTabbedPane(Disposer.newDisposable());
|
||||
var tabbedPane = new ChatToolWindowTabbedPane(Disposer.newDisposable());
|
||||
tabbedPane.addNewTab(createNewTabPanel());
|
||||
|
||||
tabbedPane.clearAll();
|
||||
|
|
@ -22,7 +20,7 @@ public class StandardChatToolWindowTabbedPaneTest extends BasePlatformTestCase {
|
|||
|
||||
|
||||
public void testAddingNewTabs() {
|
||||
var tabbedPane = new StandardChatToolWindowTabbedPane(Disposer.newDisposable());
|
||||
var tabbedPane = new ChatToolWindowTabbedPane(Disposer.newDisposable());
|
||||
|
||||
tabbedPane.addNewTab(createNewTabPanel());
|
||||
tabbedPane.addNewTab(createNewTabPanel());
|
||||
|
|
@ -33,10 +31,10 @@ public class StandardChatToolWindowTabbedPaneTest extends BasePlatformTestCase {
|
|||
}
|
||||
|
||||
public void testResetCurrentlyActiveTabPanel() {
|
||||
var tabbedPane = new StandardChatToolWindowTabbedPane(Disposer.newDisposable());
|
||||
var tabbedPane = new ChatToolWindowTabbedPane(Disposer.newDisposable());
|
||||
var conversation = ConversationService.getInstance().startConversation();
|
||||
conversation.addMessage(new Message("TEST_PROMPT", "TEST_RESPONSE"));
|
||||
tabbedPane.addNewTab(new StandardChatToolWindowTabPanel(getProject(), conversation));
|
||||
tabbedPane.addNewTab(new ChatToolWindowTabPanel(getProject(), conversation));
|
||||
|
||||
tabbedPane.resetCurrentlyActiveTabPanel(getProject());
|
||||
|
||||
|
|
@ -44,8 +42,8 @@ public class StandardChatToolWindowTabbedPaneTest extends BasePlatformTestCase {
|
|||
assertThat(tabPanel.getConversation().getMessages()).isEmpty();
|
||||
}
|
||||
|
||||
private StandardChatToolWindowTabPanel createNewTabPanel() {
|
||||
return new StandardChatToolWindowTabPanel(
|
||||
private ChatToolWindowTabPanel createNewTabPanel() {
|
||||
return new ChatToolWindowTabPanel(
|
||||
getProject(),
|
||||
ConversationService.getInstance().startConversation());
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package testsupport;
|
||||
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase;
|
||||
import ee.carlrobert.codegpt.CodeGPTKeys;
|
||||
import ee.carlrobert.llm.client.mixin.ExternalServiceTestMixin;
|
||||
|
|
@ -17,7 +18,17 @@ public class IntegrationTest extends BasePlatformTestCase implements
|
|||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
ExternalServiceTestMixin.clearAll();
|
||||
getProject().putUserData(CodeGPTKeys.SELECTED_FILES, Collections.emptyList());
|
||||
clearKeys();
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
private void clearKeys() {
|
||||
putUserData(CodeGPTKeys.SELECTED_FILES, Collections.emptyList());
|
||||
putUserData(CodeGPTKeys.PREVIOUS_INLAY_TEXT, "");
|
||||
putUserData(CodeGPTKeys.IMAGE_ATTACHMENT_FILE_PATH, "");
|
||||
}
|
||||
|
||||
private <T> void putUserData(Key<T> key, T value) {
|
||||
getProject().putUserData(key, value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,19 +3,25 @@ package testsupport.mixin;
|
|||
import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.AZURE_OPENAI_API_KEY;
|
||||
import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.OPENAI_API_KEY;
|
||||
|
||||
import com.intellij.testFramework.PlatformTestUtil;
|
||||
import ee.carlrobert.codegpt.credentials.CredentialsStore;
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings;
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType;
|
||||
import ee.carlrobert.codegpt.settings.service.azure.AzureSettings;
|
||||
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
|
||||
import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
public interface ShortcutsTestMixin {
|
||||
|
||||
default void useOpenAIService() {
|
||||
useOpenAIService("gpt-4");
|
||||
}
|
||||
|
||||
default void useOpenAIService(String model) {
|
||||
GeneralSettings.getCurrentState().setSelectedService(ServiceType.OPENAI);
|
||||
CredentialsStore.INSTANCE.setCredential(OPENAI_API_KEY, "TEST_API_KEY");
|
||||
OpenAISettings.getCurrentState().setModel("gpt-4");
|
||||
OpenAISettings.getCurrentState().setModel(model);
|
||||
}
|
||||
|
||||
default void useAzureService() {
|
||||
|
|
@ -35,4 +41,11 @@ public interface ShortcutsTestMixin {
|
|||
GeneralSettings.getCurrentState().setSelectedService(ServiceType.LLAMA_CPP);
|
||||
LlamaSettings.getCurrentState().setServerPort(null);
|
||||
}
|
||||
|
||||
default void waitExpecting(BooleanSupplier condition) {
|
||||
PlatformTestUtil.waitWithEventsDispatching(
|
||||
"Waiting for message response timed out or did not meet expected conditions",
|
||||
condition,
|
||||
5);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
src/test/resources/images/test-image.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |