From 37af74ebdf78c72cbd932c2cd91d5637d759f04e Mon Sep 17 00:00:00 2001 From: Carl-Robert Date: Thu, 14 Sep 2023 14:52:18 +0300 Subject: [PATCH] You API integration (#203) * Ability to configure custom service * Add example preset templates, rename module * Custom service client impl * Add YOU API integration * Remove/ignore generated antlr classes * Remove text completion models(deprecated) * Remove unused code, fix settings state sync * Display model name/icon in the tool window * Update chat history UI * Fix model/service sync * Clear plugin state * Fix minor bugs, add settings sync tests * UI changes * Separate model configuration * Add support for overriding the completion path * Update Find Bugs prompt --- .github/dependabot.yml | 2 +- .gitignore | 12 +- CHANGELOG.md | 24 ++ build.gradle.kts | 4 +- .../codegpt.java-conventions.gradle.kts | 2 +- codegpt-core/build.gradle.kts | 19 + .../src/main/antlr/JSON.g4 | 0 .../src/main/antlr/JavaLexer.g4 | 0 .../src/main/antlr/JavaParser.g4 | 0 .../src/main/antlr/KotlinLexer.g4 | 0 .../src/main/antlr/KotlinParser.g4 | 0 .../src/main/antlr/PythonLexer.g4 | 0 .../src/main/antlr/PythonParser.g4 | 0 .../src/main/antlr/TypeScriptLexer.g4 | 0 .../src/main/antlr/TypeScriptParser.g4 | 0 .../src/main/antlr/UnicodeClasses.g4 | 0 .../ee/carlrobert/embedding}/CheckedFile.java | 2 +- .../embedding}/EmbeddingsService.java | 51 ++- .../embedding}/GeneratedContextDetails.java | 2 +- .../ee/carlrobert}/splitter/CodeSplitter.java | 2 +- .../splitter/JavaCodeSplitter.java | 2 +- .../ee/carlrobert}/splitter/JsonSplitter.java | 2 +- .../splitter/PythonCodeSplitter.java | 2 +- .../ee/carlrobert}/splitter/Splitter.java | 2 +- .../carlrobert}/splitter/SplitterFactory.java | 2 +- .../splitter/TypeScriptCodeSplitter.java | 2 +- .../ee/carlrobert/vector}/VectorStore.java | 2 +- .../main/java/ee/carlrobert/vector}/Word.java | 2 +- .../main/java/grammar/PythonLexerBase.java | 0 .../main/java/grammar/PythonParserBase.java | 0 .../src/main/java/grammar/PythonVersion.java | 0 .../java/grammar/TypeScriptLexerBase.java | 0 .../java/grammar/TypeScriptParserBase.java | 0 .../resources/prompts/prompt-with-context.txt | 0 .../main/resources/prompts/text-generator.txt | 0 .../src/test/resources/indexes/hnsw.index | Bin 0 -> 13801 bytes embeddings/build.gradle.kts | 19 - gradle.properties | 2 +- settings.gradle.kts | 4 +- .../ee/carlrobert/codegpt/CodeGPTPlugin.java | 4 - .../carlrobert/codegpt/EncodingManager.java | 13 +- .../java/ee/carlrobert/codegpt/Icons.java | 3 + .../codegpt/PluginStartupActivity.java | 19 +- .../actions/editor/EditorActionsUtil.java | 2 +- .../DeleteAllConversationsAction.java | 2 +- .../toolwindow/DeleteConversationAction.java | 8 +- .../actions/toolwindow/MoveUpAction.java | 2 +- .../completions/CompletionClientProvider.java | 47 ++- .../completions/CompletionRequestHandler.java | 186 +++++++--- .../CompletionRequestProvider.java | 125 +++---- .../codegpt/completions/SerpResult.java | 40 +++ .../codegpt/conversations/Conversation.java | 7 +- .../conversations/ConversationService.java | 41 ++- .../conversations/ConversationsContainer.java | 7 +- .../conversations/ConversationsState.java | 8 +- .../conversations/message/Message.java | 11 + .../credentials/AzureCredentialsManager.java | 11 +- .../credentials/OpenAICredentialsManager.java | 11 +- .../credentials/UserCredentialsManager.java | 12 +- .../codegpt/indexes/CodebaseIndexingTask.java | 15 +- .../indexes/FolderStructureTreePanel.java | 2 +- ...eModelComboBox.java => ModelComboBox.java} | 6 +- .../codegpt/settings/ModelSelectionForm.java | 142 -------- .../settings/ServiceChangeNotifier.java | 2 + .../settings/ServiceSelectionForm.java | 327 +++++++++++++----- .../codegpt/settings/SettingsComponent.java | 14 +- .../settings/SettingsConfigurable.java | 133 ++----- .../settings/UserDetailsSettingsPanel.java | 75 ++-- .../advanced/AdvancedSettingsComponent.java | 1 - .../advanced/AdvancedSettingsState.java | 2 +- .../configuration/ConfigurationComponent.java | 1 - .../configuration/ConfigurationState.java | 5 +- .../settings/state/AzureSettingsState.java | 67 +++- .../settings/state/ModelSettingsState.java | 82 ----- .../settings/state/OpenAISettingsState.java | 52 ++- .../codegpt/settings/state/SettingsState.java | 38 +- .../codegpt/toolwindow/ModelIconLabel.java | 36 ++ .../toolwindow/ProjectToolWindowFactory.java | 1 + .../chat/BaseChatToolWindowTabPanel.java | 97 +++++- .../chat/ChatToolWindowTabPanelEditor.java | 14 +- .../ChatMessageResponseBody.java | 35 +- .../chat/{ => components}/ResponsePanel.java | 18 +- .../{ => components}/UserMessagePanel.java | 2 +- .../UserPromptTextArea.java} | 12 +- .../ContextualChatToolWindowLandingPanel.java | 6 +- .../ContextualChatToolWindowTabPanel.java | 8 +- .../StandardChatToolWindowContentManager.java | 16 +- .../StandardChatToolWindowLandingPanel.java | 2 +- .../standard/StandardChatToolWindowPanel.java | 24 +- .../StandardChatToolWindowTabPanel.java | 27 +- .../StandardChatToolWindowTabbedPane.java | 5 +- .../conversations/ConversationPanel.java | 119 ++++--- .../ConversationsToolWindow.form | 21 -- .../ConversationsToolWindow.java | 66 +--- .../conversations/RootConversationPanel.java | 43 --- .../ee/carlrobert/codegpt/user/ApiClient.java | 77 ++--- .../carlrobert/codegpt/user/UserManager.java | 30 +- .../user/auth/AuthenticationError.java | 24 ++ .../user/auth/AuthenticationHandler.java | 8 +- .../user/auth/AuthenticationService.java | 92 ++--- .../carlrobert/codegpt/user/auth/Session.java | 43 --- .../codegpt/user/auth/SessionUser.java | 34 -- .../user/auth/SessionVerificationJob.java | 5 - .../auth/response/AuthenticationResponse.java | 18 + .../response/AuthenticationResponseData.java | 40 +++ .../codegpt/user/auth/response/Email.java | 33 ++ .../codegpt/user/auth/response/Name.java | 33 ++ .../codegpt/user/auth/response/Session.java | 47 +++ .../codegpt/user/auth/response/User.java | 34 ++ .../user/subscription/Subscription.java | 47 --- .../user/subscription/SubscriptionPrice.java | 40 --- .../subscription/SubscriptionProduct.java | 40 --- .../carlrobert/codegpt/util/EditorUtils.java | 10 + .../carlrobert/codegpt/util/SwingUtils.java | 11 +- src/main/resources/META-INF/plugin.xml | 1 + src/main/resources/icons/azure.svg | 8 + src/main/resources/icons/openai.svg | 2 + src/main/resources/icons/openai_dark.svg | 2 + src/main/resources/icons/you.svg | 22 ++ .../resources/messages/codegpt.properties | 5 +- .../CompletionRequestProviderTest.java | 104 ++---- .../DefaultCompletionRequestHandlerTest.java | 70 +--- .../conversations/ConversationsStateTest.java | 19 +- .../settings/state/SettingsStateTest.java | 55 +++ src/test/resources/indexes/hnsw.index | Bin 13801 -> 13779 bytes 125 files changed, 1663 insertions(+), 1527 deletions(-) create mode 100644 codegpt-core/build.gradle.kts rename {embeddings => codegpt-core}/src/main/antlr/JSON.g4 (100%) rename {embeddings => codegpt-core}/src/main/antlr/JavaLexer.g4 (100%) rename {embeddings => codegpt-core}/src/main/antlr/JavaParser.g4 (100%) rename {embeddings => codegpt-core}/src/main/antlr/KotlinLexer.g4 (100%) rename {embeddings => codegpt-core}/src/main/antlr/KotlinParser.g4 (100%) rename {embeddings => codegpt-core}/src/main/antlr/PythonLexer.g4 (100%) rename {embeddings => codegpt-core}/src/main/antlr/PythonParser.g4 (100%) rename {embeddings => codegpt-core}/src/main/antlr/TypeScriptLexer.g4 (100%) rename {embeddings => codegpt-core}/src/main/antlr/TypeScriptParser.g4 (100%) rename {embeddings => codegpt-core}/src/main/antlr/UnicodeClasses.g4 (100%) rename {embeddings/src/main/java/ee/carlrobert/codegpt/embeddings => codegpt-core/src/main/java/ee/carlrobert/embedding}/CheckedFile.java (95%) rename {embeddings/src/main/java/ee/carlrobert/codegpt/embeddings => codegpt-core/src/main/java/ee/carlrobert/embedding}/EmbeddingsService.java (68%) rename {embeddings/src/main/java/ee/carlrobert/codegpt/embeddings => codegpt-core/src/main/java/ee/carlrobert/embedding}/GeneratedContextDetails.java (90%) rename {embeddings/src/main/java/ee/carlrobert/codegpt/embeddings => codegpt-core/src/main/java/ee/carlrobert}/splitter/CodeSplitter.java (95%) rename {embeddings/src/main/java/ee/carlrobert/codegpt/embeddings => codegpt-core/src/main/java/ee/carlrobert}/splitter/JavaCodeSplitter.java (94%) rename {embeddings/src/main/java/ee/carlrobert/codegpt/embeddings => codegpt-core/src/main/java/ee/carlrobert}/splitter/JsonSplitter.java (94%) rename {embeddings/src/main/java/ee/carlrobert/codegpt/embeddings => codegpt-core/src/main/java/ee/carlrobert}/splitter/PythonCodeSplitter.java (94%) rename {embeddings/src/main/java/ee/carlrobert/codegpt/embeddings => codegpt-core/src/main/java/ee/carlrobert}/splitter/Splitter.java (68%) rename {embeddings/src/main/java/ee/carlrobert/codegpt/embeddings => codegpt-core/src/main/java/ee/carlrobert}/splitter/SplitterFactory.java (90%) rename {embeddings/src/main/java/ee/carlrobert/codegpt/embeddings => codegpt-core/src/main/java/ee/carlrobert}/splitter/TypeScriptCodeSplitter.java (92%) rename {embeddings/src/main/java/ee/carlrobert/codegpt/embeddings => codegpt-core/src/main/java/ee/carlrobert/vector}/VectorStore.java (97%) rename {embeddings/src/main/java/ee/carlrobert/codegpt/embeddings => codegpt-core/src/main/java/ee/carlrobert/vector}/Word.java (94%) rename {embeddings => codegpt-core}/src/main/java/grammar/PythonLexerBase.java (100%) rename {embeddings => codegpt-core}/src/main/java/grammar/PythonParserBase.java (100%) rename {embeddings => codegpt-core}/src/main/java/grammar/PythonVersion.java (100%) rename {embeddings => codegpt-core}/src/main/java/grammar/TypeScriptLexerBase.java (100%) rename {embeddings => codegpt-core}/src/main/java/grammar/TypeScriptParserBase.java (100%) rename src/main/resources/prompts/retrieval-prompt.txt => codegpt-core/src/main/resources/prompts/prompt-with-context.txt (100%) rename {src => codegpt-core/src}/main/resources/prompts/text-generator.txt (100%) create mode 100644 codegpt-core/src/test/resources/indexes/hnsw.index delete mode 100644 embeddings/build.gradle.kts create mode 100644 src/main/java/ee/carlrobert/codegpt/completions/SerpResult.java rename src/main/java/ee/carlrobert/codegpt/settings/{BaseModelComboBox.java => ModelComboBox.java} (78%) delete mode 100644 src/main/java/ee/carlrobert/codegpt/settings/ModelSelectionForm.java create mode 100644 src/main/java/ee/carlrobert/codegpt/settings/ServiceChangeNotifier.java delete mode 100644 src/main/java/ee/carlrobert/codegpt/settings/state/ModelSettingsState.java create mode 100644 src/main/java/ee/carlrobert/codegpt/toolwindow/ModelIconLabel.java rename src/main/java/ee/carlrobert/codegpt/toolwindow/chat/{ => components}/ChatMessageResponseBody.java (85%) rename src/main/java/ee/carlrobert/codegpt/toolwindow/chat/{ => components}/ResponsePanel.java (83%) rename src/main/java/ee/carlrobert/codegpt/toolwindow/chat/{ => components}/UserMessagePanel.java (96%) rename src/main/java/ee/carlrobert/codegpt/toolwindow/chat/{UserTextArea.java => components/UserPromptTextArea.java} (93%) delete mode 100644 src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/ConversationsToolWindow.form delete mode 100644 src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/RootConversationPanel.java create mode 100644 src/main/java/ee/carlrobert/codegpt/user/auth/AuthenticationError.java delete mode 100644 src/main/java/ee/carlrobert/codegpt/user/auth/Session.java delete mode 100644 src/main/java/ee/carlrobert/codegpt/user/auth/SessionUser.java create mode 100644 src/main/java/ee/carlrobert/codegpt/user/auth/response/AuthenticationResponse.java create mode 100644 src/main/java/ee/carlrobert/codegpt/user/auth/response/AuthenticationResponseData.java create mode 100644 src/main/java/ee/carlrobert/codegpt/user/auth/response/Email.java create mode 100644 src/main/java/ee/carlrobert/codegpt/user/auth/response/Name.java create mode 100644 src/main/java/ee/carlrobert/codegpt/user/auth/response/Session.java create mode 100644 src/main/java/ee/carlrobert/codegpt/user/auth/response/User.java delete mode 100644 src/main/java/ee/carlrobert/codegpt/user/subscription/Subscription.java delete mode 100644 src/main/java/ee/carlrobert/codegpt/user/subscription/SubscriptionPrice.java delete mode 100644 src/main/java/ee/carlrobert/codegpt/user/subscription/SubscriptionProduct.java create mode 100644 src/main/resources/icons/azure.svg create mode 100644 src/main/resources/icons/openai.svg create mode 100644 src/main/resources/icons/openai_dark.svg create mode 100644 src/main/resources/icons/you.svg create mode 100644 src/test/java/ee/carlrobert/codegpt/settings/state/SettingsStateTest.java diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 31e3b840d..95500085f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -16,6 +16,6 @@ updates: interval: "daily" - package-ecosystem: "gradle" - directory: "/embeddings" + directory: "/codegpt-core" schedule: interval: "daily" diff --git a/.gitignore b/.gitignore index 4cd4cab48..78533cf29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,9 @@ -embeddings/src/main/java/grammar/* -!embeddings/src/main/java/grammar/PythonLexerBase.java -!embeddings/src/main/java/grammar/PythonParserBase.java -!embeddings/src/main/java/grammar/PythonVersion.java -!embeddings/src/main/java/grammar/TypeScriptLexerBase.java -!embeddings/src/main/java/grammar/TypeScriptParserBase.java +codegpt-core/src/main/java/grammar/* +!codegpt-core/src/main/java/grammar/PythonLexerBase.java +!codegpt-core/src/main/java/grammar/PythonParserBase.java +!codegpt-core/src/main/java/grammar/PythonVersion.java +!codegpt-core/src/main/java/grammar/TypeScriptLexerBase.java +!codegpt-core/src/main/java/grammar/TypeScriptParserBase.java .gradle build/ diff --git a/CHANGELOG.md b/CHANGELOG.md index fea91b79c..c93cf7d08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Changelog + All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). @@ -8,21 +9,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [2.0.6] - 2023-08-29 ### Removed + - Functionality to fetch/use OpenAI account name ### Fixed + - Rendering user input's newlines ### Added + - Support for closing other tabs (#172) - Support for configuring custom hosts for OpenAI and Azure services ## [2.0.5] - 2023-06-12 ### Fixed + - Tool window not opening on editor actions (#157) ### Added + - Support for changing the editor action behaviour (#157) - Support for overriding completions request parameters (#152) - User text area autofocus on creating a new chat (#155) @@ -31,10 +37,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [2.0.4] - 2023-05-27 ### Fixed + - TypeScript and C# code highlighting + - ToolWindow usability when virtual space option turned ON (#125) ### Added + - ToolWindow code editor copy/replace header actions - Custom prompt main editor action (#144) - Support for 2023.2 EAP builds (#149) @@ -42,56 +51,71 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [2.0.3] - 2023-05-18 ### Fixed + - Empty editor context menu item text (#137) - Temp file path resolving (#130) ### Improved + - Response streaming ### Added + - Reset chat window toolbar action (#138) ## [2.0.2] - 2023-05-16 ### Fixed + - Settings deserialization error ### Improved + - Memory consumption by disposing unused editors which are no longer needed ### Removed + - Main editor focus stealing on response streaming ## [2.0.1] - 2023-05-14 ### Added + - New GPT-3.5-16k model ### Fixed + - NPE when `displayName` couldn't be fetched ### Improved + - Proxy support by disabling the default trustmanager ### Removed + - Off-screen rendering setting option - Automatic textarea focus on stream completion (#126) ## [2.0.0] - 2023-05-03 ### Added + - Automatic retry logic on stream timeouts ### Improved + - Input prompt text field UI/UX (height grows with the content) ### Removed + - Custom prompt editor action (users can now provide custom actions within the chat window itself) ### Replaced + - ToolWindow HTML content with native Swing components ### Secured + - `OPENAI_API_KEY` persistence, key is saved in the OS password safe from now on [Unreleased]: https://github.com/carlrobertoh/CodeGPT/compare/v2.0.6...HEAD diff --git a/build.gradle.kts b/build.gradle.kts index 6ddafb11d..0e044c6b9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -31,8 +31,7 @@ changelog { } dependencies { - implementation(project("embeddings", "instrumentedJar")) - implementation(project(mapOf("path" to ":embeddings"))) + implementation(project(":codegpt-core")) implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.15.2") implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2") @@ -47,6 +46,7 @@ dependencies { testImplementation("org.assertj:assertj-core:3.24.2") testImplementation("org.awaitility:awaitility:4.2.0") + testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.2") testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.0") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.0") testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.10.0") diff --git a/buildSrc/src/main/kotlin/codegpt.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/codegpt.java-conventions.gradle.kts index 3b4d0dd35..29d6ad2db 100644 --- a/buildSrc/src/main/kotlin/codegpt.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/codegpt.java-conventions.gradle.kts @@ -18,7 +18,7 @@ intellij { } dependencies { - implementation("ee.carlrobert:openai-client:1.2.1") + implementation("ee.carlrobert:llm-client:0.0.3") } tasks { diff --git a/codegpt-core/build.gradle.kts b/codegpt-core/build.gradle.kts new file mode 100644 index 000000000..c5e313acb --- /dev/null +++ b/codegpt-core/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + id("codegpt.java-conventions") + id("antlr") +} + +dependencies { + antlr("org.antlr:antlr4:4.13.1") + api("com.github.jelmerk:hnswlib-core:1.0.1") + api("com.github.jelmerk:hnswlib-utils:1.0.1") + implementation("org.antlr:antlr4-runtime:4.13.1") + implementation("org.json:json:20230618") +} + +tasks { + generateGrammarSource { + outputDirectory = file("src/main/java/grammar") + arguments = arguments + listOf("-package", "grammar") + } +} \ No newline at end of file diff --git a/embeddings/src/main/antlr/JSON.g4 b/codegpt-core/src/main/antlr/JSON.g4 similarity index 100% rename from embeddings/src/main/antlr/JSON.g4 rename to codegpt-core/src/main/antlr/JSON.g4 diff --git a/embeddings/src/main/antlr/JavaLexer.g4 b/codegpt-core/src/main/antlr/JavaLexer.g4 similarity index 100% rename from embeddings/src/main/antlr/JavaLexer.g4 rename to codegpt-core/src/main/antlr/JavaLexer.g4 diff --git a/embeddings/src/main/antlr/JavaParser.g4 b/codegpt-core/src/main/antlr/JavaParser.g4 similarity index 100% rename from embeddings/src/main/antlr/JavaParser.g4 rename to codegpt-core/src/main/antlr/JavaParser.g4 diff --git a/embeddings/src/main/antlr/KotlinLexer.g4 b/codegpt-core/src/main/antlr/KotlinLexer.g4 similarity index 100% rename from embeddings/src/main/antlr/KotlinLexer.g4 rename to codegpt-core/src/main/antlr/KotlinLexer.g4 diff --git a/embeddings/src/main/antlr/KotlinParser.g4 b/codegpt-core/src/main/antlr/KotlinParser.g4 similarity index 100% rename from embeddings/src/main/antlr/KotlinParser.g4 rename to codegpt-core/src/main/antlr/KotlinParser.g4 diff --git a/embeddings/src/main/antlr/PythonLexer.g4 b/codegpt-core/src/main/antlr/PythonLexer.g4 similarity index 100% rename from embeddings/src/main/antlr/PythonLexer.g4 rename to codegpt-core/src/main/antlr/PythonLexer.g4 diff --git a/embeddings/src/main/antlr/PythonParser.g4 b/codegpt-core/src/main/antlr/PythonParser.g4 similarity index 100% rename from embeddings/src/main/antlr/PythonParser.g4 rename to codegpt-core/src/main/antlr/PythonParser.g4 diff --git a/embeddings/src/main/antlr/TypeScriptLexer.g4 b/codegpt-core/src/main/antlr/TypeScriptLexer.g4 similarity index 100% rename from embeddings/src/main/antlr/TypeScriptLexer.g4 rename to codegpt-core/src/main/antlr/TypeScriptLexer.g4 diff --git a/embeddings/src/main/antlr/TypeScriptParser.g4 b/codegpt-core/src/main/antlr/TypeScriptParser.g4 similarity index 100% rename from embeddings/src/main/antlr/TypeScriptParser.g4 rename to codegpt-core/src/main/antlr/TypeScriptParser.g4 diff --git a/embeddings/src/main/antlr/UnicodeClasses.g4 b/codegpt-core/src/main/antlr/UnicodeClasses.g4 similarity index 100% rename from embeddings/src/main/antlr/UnicodeClasses.g4 rename to codegpt-core/src/main/antlr/UnicodeClasses.g4 diff --git a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/CheckedFile.java b/codegpt-core/src/main/java/ee/carlrobert/embedding/CheckedFile.java similarity index 95% rename from embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/CheckedFile.java rename to codegpt-core/src/main/java/ee/carlrobert/embedding/CheckedFile.java index 097c0eef0..76b0841df 100644 --- a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/CheckedFile.java +++ b/codegpt-core/src/main/java/ee/carlrobert/embedding/CheckedFile.java @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.embeddings; +package ee.carlrobert.embedding; import java.io.File; import java.io.IOException; diff --git a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/EmbeddingsService.java b/codegpt-core/src/main/java/ee/carlrobert/embedding/EmbeddingsService.java similarity index 68% rename from embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/EmbeddingsService.java rename to codegpt-core/src/main/java/ee/carlrobert/embedding/EmbeddingsService.java index 34cb24f4e..4b4c8c4a8 100644 --- a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/EmbeddingsService.java +++ b/codegpt-core/src/main/java/ee/carlrobert/embedding/EmbeddingsService.java @@ -1,26 +1,24 @@ -package ee.carlrobert.codegpt.embeddings; +package ee.carlrobert.embedding; import static com.github.jelmerk.knn.util.VectorUtils.normalize; import static java.util.stream.Collectors.toList; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import com.github.jelmerk.knn.Item; import com.github.jelmerk.knn.SearchResult; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; -import ee.carlrobert.codegpt.embeddings.splitter.SplitterFactory; -import ee.carlrobert.openai.client.completion.CompletionClient; -import ee.carlrobert.openai.client.completion.chat.ChatCompletionModel; -import ee.carlrobert.openai.client.completion.chat.request.ChatCompletionMessage; -import ee.carlrobert.openai.client.completion.chat.request.ChatCompletionRequest; -import ee.carlrobert.openai.client.completion.chat.response.ChatCompletionResponse; -import ee.carlrobert.openai.client.embeddings.EmbeddingsClient; +import ee.carlrobert.llm.client.openai.OpenAIClient; +import ee.carlrobert.llm.client.openai.completion.chat.OpenAIChatCompletionModel; +import ee.carlrobert.llm.client.openai.completion.chat.request.OpenAIChatCompletionMessage; +import ee.carlrobert.llm.client.openai.completion.chat.request.OpenAIChatCompletionRequest; +import ee.carlrobert.splitter.SplitterFactory; +import ee.carlrobert.vector.VectorStore; +import ee.carlrobert.vector.Word; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Objects; @@ -32,22 +30,20 @@ public class EmbeddingsService { private static final Logger LOG = Logger.getInstance(EmbeddingsService.class); private final VectorStore vectorStore; - private final EmbeddingsClient embeddingsClient; - private final CompletionClient completionClient; + private final OpenAIClient openAIClient; - public EmbeddingsService(EmbeddingsClient embeddingsClient, CompletionClient completionClient, Path pluginBasePath) { - this.embeddingsClient = embeddingsClient; - this.completionClient = completionClient; + public EmbeddingsService(OpenAIClient openAIClient, Path pluginBasePath) { + this.openAIClient = openAIClient; this.vectorStore = VectorStore.getInstance(pluginBasePath); } public List getEmbeddings(List chunks) { - return embeddingsClient.getEmbeddings(chunks); + return openAIClient.getEmbeddings(chunks); } - - public GeneratedContextDetails buildRelevantContext(String prompt) { + + public String buildPromptWithContext(String prompt) { try { - var inputEmbedding = embeddingsClient.getEmbedding(getSearchQuery(prompt)); + var inputEmbedding = openAIClient.getEmbedding(getSearchQuery(prompt)); var sortedResult = vectorStore.loadIndex() .findNearest(normalize(inputEmbedding), 10) .stream() @@ -58,10 +54,12 @@ public GeneratedContextDetails buildRelevantContext(String prompt) { var context = sortedResult.stream().map(Word::id).collect(Collectors.joining()); var fileNames = sortedResult.stream().map(Word::getMeta).collect(Collectors.toSet()); - return new GeneratedContextDetails(context, fileNames); + return getResourceContent("/prompts/prompt-with-context.txt") + .replace("{prompt}", prompt) + .replace("{context}", new GeneratedContextDetails(context, fileNames).getContext()); } catch (IOException e) { LOG.error("Unable to load vector index", e); - return new GeneratedContextDetails(prompt, Collections.emptySet()); + return prompt; } } @@ -83,16 +81,15 @@ public List> createEmbeddings(List checkedFi } private String getSearchQuery(String userPrompt) throws JsonProcessingException { - var message = new ChatCompletionMessage("user", getResourceContent("/prompts/text-generator.txt").replace("{prompt}", userPrompt)); - var request = new ChatCompletionRequest.Builder(List.of(message)) - .setModel(ChatCompletionModel.GPT_4) + var message = new OpenAIChatCompletionMessage("user", getResourceContent("/prompts/text-generator.txt").replace("{prompt}", userPrompt)); + var request = new OpenAIChatCompletionRequest.Builder(List.of(message)) + .setModel(OpenAIChatCompletionModel.GPT_4) .setMaxTokens(400) .setTemperature(0.1) .setStream(false) .build(); - return new ObjectMapper() - .readValue(completionClient.call(request), ChatCompletionResponse.class) + return openAIClient.getChatCompletion(request) .getChoices() .get(0) .getMessage() @@ -104,7 +101,7 @@ private void addEmbeddings(CheckedFile checkedFile, List> var codeSplitter = SplitterFactory.getCodeSplitter(fileExtension); if (codeSplitter != null) { var chunks = codeSplitter.split(checkedFile.getFileName(), checkedFile.getFileContent()); - var embeddings = embeddingsClient.getEmbeddings(chunks); + var embeddings = openAIClient.getEmbeddings(chunks); for (int i = 0; i < chunks.size(); i++) { prevEmbeddings.add(new Word(chunks.get(i), checkedFile.getFileName(), normalize(embeddings.get(i)))); } diff --git a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/GeneratedContextDetails.java b/codegpt-core/src/main/java/ee/carlrobert/embedding/GeneratedContextDetails.java similarity index 90% rename from embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/GeneratedContextDetails.java rename to codegpt-core/src/main/java/ee/carlrobert/embedding/GeneratedContextDetails.java index b3573f935..2ef1d0b90 100644 --- a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/GeneratedContextDetails.java +++ b/codegpt-core/src/main/java/ee/carlrobert/embedding/GeneratedContextDetails.java @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.embeddings; +package ee.carlrobert.embedding; import java.util.Set; diff --git a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/CodeSplitter.java b/codegpt-core/src/main/java/ee/carlrobert/splitter/CodeSplitter.java similarity index 95% rename from embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/CodeSplitter.java rename to codegpt-core/src/main/java/ee/carlrobert/splitter/CodeSplitter.java index 84c6692e2..3837346df 100644 --- a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/CodeSplitter.java +++ b/codegpt-core/src/main/java/ee/carlrobert/splitter/CodeSplitter.java @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.embeddings.splitter; +package ee.carlrobert.splitter; import java.util.ArrayList; import java.util.List; diff --git a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/JavaCodeSplitter.java b/codegpt-core/src/main/java/ee/carlrobert/splitter/JavaCodeSplitter.java similarity index 94% rename from embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/JavaCodeSplitter.java rename to codegpt-core/src/main/java/ee/carlrobert/splitter/JavaCodeSplitter.java index 2e4f51b81..99e409fe4 100644 --- a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/JavaCodeSplitter.java +++ b/codegpt-core/src/main/java/ee/carlrobert/splitter/JavaCodeSplitter.java @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.embeddings.splitter; +package ee.carlrobert.splitter; import grammar.JavaLexer; import grammar.JavaParser; diff --git a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/JsonSplitter.java b/codegpt-core/src/main/java/ee/carlrobert/splitter/JsonSplitter.java similarity index 94% rename from embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/JsonSplitter.java rename to codegpt-core/src/main/java/ee/carlrobert/splitter/JsonSplitter.java index 6af51f550..475ecdfd9 100644 --- a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/JsonSplitter.java +++ b/codegpt-core/src/main/java/ee/carlrobert/splitter/JsonSplitter.java @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.embeddings.splitter; +package ee.carlrobert.splitter; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/PythonCodeSplitter.java b/codegpt-core/src/main/java/ee/carlrobert/splitter/PythonCodeSplitter.java similarity index 94% rename from embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/PythonCodeSplitter.java rename to codegpt-core/src/main/java/ee/carlrobert/splitter/PythonCodeSplitter.java index 143624519..0a766d4a6 100644 --- a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/PythonCodeSplitter.java +++ b/codegpt-core/src/main/java/ee/carlrobert/splitter/PythonCodeSplitter.java @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.embeddings.splitter; +package ee.carlrobert.splitter; import grammar.PythonLexer; import grammar.PythonParser; diff --git a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/Splitter.java b/codegpt-core/src/main/java/ee/carlrobert/splitter/Splitter.java similarity index 68% rename from embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/Splitter.java rename to codegpt-core/src/main/java/ee/carlrobert/splitter/Splitter.java index 2d2e74aa1..7e545dc84 100644 --- a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/Splitter.java +++ b/codegpt-core/src/main/java/ee/carlrobert/splitter/Splitter.java @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.embeddings.splitter; +package ee.carlrobert.splitter; import java.util.List; diff --git a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/SplitterFactory.java b/codegpt-core/src/main/java/ee/carlrobert/splitter/SplitterFactory.java similarity index 90% rename from embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/SplitterFactory.java rename to codegpt-core/src/main/java/ee/carlrobert/splitter/SplitterFactory.java index be347bf7d..092cd4199 100644 --- a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/SplitterFactory.java +++ b/codegpt-core/src/main/java/ee/carlrobert/splitter/SplitterFactory.java @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.embeddings.splitter; +package ee.carlrobert.splitter; import org.jetbrains.annotations.Nullable; diff --git a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/TypeScriptCodeSplitter.java b/codegpt-core/src/main/java/ee/carlrobert/splitter/TypeScriptCodeSplitter.java similarity index 92% rename from embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/TypeScriptCodeSplitter.java rename to codegpt-core/src/main/java/ee/carlrobert/splitter/TypeScriptCodeSplitter.java index 2f87524c5..e14ebf76f 100644 --- a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/splitter/TypeScriptCodeSplitter.java +++ b/codegpt-core/src/main/java/ee/carlrobert/splitter/TypeScriptCodeSplitter.java @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.embeddings.splitter; +package ee.carlrobert.splitter; import grammar.TypeScriptLexer; import grammar.TypeScriptParser; diff --git a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/VectorStore.java b/codegpt-core/src/main/java/ee/carlrobert/vector/VectorStore.java similarity index 97% rename from embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/VectorStore.java rename to codegpt-core/src/main/java/ee/carlrobert/vector/VectorStore.java index 5cacfb1f2..c4b97aeb2 100644 --- a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/VectorStore.java +++ b/codegpt-core/src/main/java/ee/carlrobert/vector/VectorStore.java @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.embeddings; +package ee.carlrobert.vector; import com.github.jelmerk.knn.DistanceFunctions; import com.github.jelmerk.knn.Item; diff --git a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/Word.java b/codegpt-core/src/main/java/ee/carlrobert/vector/Word.java similarity index 94% rename from embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/Word.java rename to codegpt-core/src/main/java/ee/carlrobert/vector/Word.java index 33d69765f..c972fcef6 100644 --- a/embeddings/src/main/java/ee/carlrobert/codegpt/embeddings/Word.java +++ b/codegpt-core/src/main/java/ee/carlrobert/vector/Word.java @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.embeddings; +package ee.carlrobert.vector; import com.github.jelmerk.knn.Item; import java.util.Arrays; diff --git a/embeddings/src/main/java/grammar/PythonLexerBase.java b/codegpt-core/src/main/java/grammar/PythonLexerBase.java similarity index 100% rename from embeddings/src/main/java/grammar/PythonLexerBase.java rename to codegpt-core/src/main/java/grammar/PythonLexerBase.java diff --git a/embeddings/src/main/java/grammar/PythonParserBase.java b/codegpt-core/src/main/java/grammar/PythonParserBase.java similarity index 100% rename from embeddings/src/main/java/grammar/PythonParserBase.java rename to codegpt-core/src/main/java/grammar/PythonParserBase.java diff --git a/embeddings/src/main/java/grammar/PythonVersion.java b/codegpt-core/src/main/java/grammar/PythonVersion.java similarity index 100% rename from embeddings/src/main/java/grammar/PythonVersion.java rename to codegpt-core/src/main/java/grammar/PythonVersion.java diff --git a/embeddings/src/main/java/grammar/TypeScriptLexerBase.java b/codegpt-core/src/main/java/grammar/TypeScriptLexerBase.java similarity index 100% rename from embeddings/src/main/java/grammar/TypeScriptLexerBase.java rename to codegpt-core/src/main/java/grammar/TypeScriptLexerBase.java diff --git a/embeddings/src/main/java/grammar/TypeScriptParserBase.java b/codegpt-core/src/main/java/grammar/TypeScriptParserBase.java similarity index 100% rename from embeddings/src/main/java/grammar/TypeScriptParserBase.java rename to codegpt-core/src/main/java/grammar/TypeScriptParserBase.java diff --git a/src/main/resources/prompts/retrieval-prompt.txt b/codegpt-core/src/main/resources/prompts/prompt-with-context.txt similarity index 100% rename from src/main/resources/prompts/retrieval-prompt.txt rename to codegpt-core/src/main/resources/prompts/prompt-with-context.txt diff --git a/src/main/resources/prompts/text-generator.txt b/codegpt-core/src/main/resources/prompts/text-generator.txt similarity index 100% rename from src/main/resources/prompts/text-generator.txt rename to codegpt-core/src/main/resources/prompts/text-generator.txt diff --git a/codegpt-core/src/test/resources/indexes/hnsw.index b/codegpt-core/src/test/resources/indexes/hnsw.index new file mode 100644 index 0000000000000000000000000000000000000000..ef62213439033fb818dfe64717b31317057127d3 GIT binary patch literal 13801 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 literal 0 HcmV?d00001 diff --git a/embeddings/build.gradle.kts b/embeddings/build.gradle.kts deleted file mode 100644 index 36dbde845..000000000 --- a/embeddings/build.gradle.kts +++ /dev/null @@ -1,19 +0,0 @@ -plugins { - id("codegpt.java-conventions") - id("antlr") -} - -dependencies { - antlr("org.antlr:antlr4:4.13.1") - implementation("org.antlr:antlr4-runtime:4.13.1") - implementation("org.json:json:20230618") - implementation("com.github.jelmerk:hnswlib-core:1.1.0") - implementation("com.github.jelmerk:hnswlib-utils:1.1.0") -} - -tasks { - generateGrammarSource { - outputDirectory = file("src/main/java/grammar") - arguments = arguments + listOf("-package", "grammar") - } -} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index bfa429f23..96874fd63 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ pluginGroup = ee.carlrobert pluginName = CodeGPT pluginRepositoryUrl = https://github.com/carlrobertoh/CodeGPT # SemVer format -> https://semver.org -pluginVersion = 2.0.6 +pluginVersion = 2.1.0 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 213 diff --git a/settings.gradle.kts b/settings.gradle.kts index bd26a20dd..f6e33381a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,3 @@ rootProject.name = "CodeGPT" -include(":embeddings") -project(":embeddings").projectDir = File("embeddings") \ No newline at end of file +include("codegpt-core") +project(":codegpt-core").projectDir = File("codegpt-core") \ No newline at end of file diff --git a/src/main/java/ee/carlrobert/codegpt/CodeGPTPlugin.java b/src/main/java/ee/carlrobert/codegpt/CodeGPTPlugin.java index c03801095..4e8ad07ad 100644 --- a/src/main/java/ee/carlrobert/codegpt/CodeGPTPlugin.java +++ b/src/main/java/ee/carlrobert/codegpt/CodeGPTPlugin.java @@ -32,8 +32,4 @@ private CodeGPTPlugin() { public static @NotNull String getProjectIndexStorePath(@NotNull Project project) { return getIndexStorePath() + File.separator + project.getName(); } - - public static @NotNull String getProjectIndexPath(@NotNull Project project) { - return getProjectIndexStorePath(project) + File.separator + "hnsw.index"; - } } diff --git a/src/main/java/ee/carlrobert/codegpt/EncodingManager.java b/src/main/java/ee/carlrobert/codegpt/EncodingManager.java index 4121eb85c..0d21525d4 100644 --- a/src/main/java/ee/carlrobert/codegpt/EncodingManager.java +++ b/src/main/java/ee/carlrobert/codegpt/EncodingManager.java @@ -5,28 +5,23 @@ import com.knuddels.jtokkit.Encodings; import com.knuddels.jtokkit.api.Encoding; import com.knuddels.jtokkit.api.EncodingRegistry; -import ee.carlrobert.codegpt.settings.state.ModelSettingsState; -import ee.carlrobert.openai.client.completion.chat.request.ChatCompletionMessage; +import com.knuddels.jtokkit.api.EncodingType; +import ee.carlrobert.llm.client.openai.completion.chat.request.OpenAIChatCompletionMessage; @Service public final class EncodingManager { private final EncodingRegistry registry = Encodings.newDefaultEncodingRegistry(); - private Encoding encoding; + private final Encoding encoding = registry.getEncoding(EncodingType.CL100K_BASE); private EncodingManager() { - setEncoding(ModelSettingsState.getInstance().getCompletionModel()); } public static EncodingManager getInstance() { return ApplicationManager.getApplication().getService(EncodingManager.class); } - public void setEncoding(String modelName) { - this.encoding = registry.getEncodingForModel(modelName).orElseThrow(); - } - - public int countMessageTokens(ChatCompletionMessage message) { + public int countMessageTokens(OpenAIChatCompletionMessage message) { var tokensPerMessage = 4; // every message follows <|start|>{role/name}\n{content}<|end|>\n return encoding.countTokens(message.getRole() + message.getContent()) + tokensPerMessage; } diff --git a/src/main/java/ee/carlrobert/codegpt/Icons.java b/src/main/java/ee/carlrobert/codegpt/Icons.java index 2157343fa..e1166273b 100644 --- a/src/main/java/ee/carlrobert/codegpt/Icons.java +++ b/src/main/java/ee/carlrobert/codegpt/Icons.java @@ -10,6 +10,9 @@ public final class Icons { public static final Icon DefaultIcon = IconLoader.getIcon("/icons/codegpt.svg", Icons.class); public static final Icon DefaultSmallIcon = IconLoader.getIcon("/icons/codegpt-small.svg", Icons.class); public static final Icon SendIcon = IconLoader.getIcon("/icons/send.svg", Icons.class); + public static final Icon OpenAIIcon = IconLoader.getIcon("/icons/openai.svg", Icons.class); + public static final Icon AzureIcon = IconLoader.getIcon("/icons/azure.svg", Icons.class); + public static final Icon YouIcon = IconLoader.getIcon("/icons/you.svg", Icons.class); public static final ImageIcon DefaultImageIcon = getImageIcon("/icons/chatgpt.png"); private static ImageIcon getImageIcon(String path) { diff --git a/src/main/java/ee/carlrobert/codegpt/PluginStartupActivity.java b/src/main/java/ee/carlrobert/codegpt/PluginStartupActivity.java index 6cdeb60cf..78740ec53 100644 --- a/src/main/java/ee/carlrobert/codegpt/PluginStartupActivity.java +++ b/src/main/java/ee/carlrobert/codegpt/PluginStartupActivity.java @@ -10,9 +10,11 @@ import ee.carlrobert.codegpt.credentials.UserCredentialsManager; import ee.carlrobert.codegpt.settings.state.SettingsState; import ee.carlrobert.codegpt.user.UserManager; +import ee.carlrobert.codegpt.user.auth.AuthenticationError; import ee.carlrobert.codegpt.user.auth.AuthenticationHandler; import ee.carlrobert.codegpt.user.auth.AuthenticationService; import ee.carlrobert.codegpt.user.auth.SessionVerificationJob; +import ee.carlrobert.codegpt.user.auth.response.AuthenticationResponse; import ee.carlrobert.codegpt.util.OverlayUtils; import org.jetbrains.annotations.NotNull; import org.quartz.JobBuilder; @@ -31,9 +33,8 @@ public class PluginStartupActivity implements StartupActivity { public void runActivity(@NotNull Project project) { EditorActionsUtil.refreshActions(); - var userManager = UserManager.getInstance(); - var session = userManager.getSession(); - if (session == null || session.isExpired()) { + var authenticationResponse = UserManager.getInstance().getAuthenticationResponse(); + if (authenticationResponse == null) { handleAuthentication(); } else { startSessionVerificationJob(); @@ -78,7 +79,7 @@ private void startSessionVerificationJob() { private void handleAuthentication() { var settings = SettingsState.getInstance(); - if (settings == null || !settings.isPreviouslySignedIn()) { + if (!settings.isPreviouslySignedIn()) { return; } @@ -87,19 +88,19 @@ private void handleAuthentication() { AuthenticationService.getInstance() .signInAsync(settings.getEmail(), password, new AuthenticationHandler() { @Override - public void handleAuthenticated() { + public void handleAuthenticated(AuthenticationResponse authenticationResponse) { OverlayUtils.showNotification("Authentication successful.", NotificationType.INFORMATION); startSessionVerificationJob(); } @Override - public void handleInvalidCredentials() { - OverlayUtils.showNotification("Authentication unsuccessful. Invalid credentials provided.", NotificationType.ERROR); + public void handleGenericError() { + OverlayUtils.showNotification("Something went wrong while trying to authenticate.", NotificationType.ERROR); } @Override - public void handleGenericError() { - OverlayUtils.showNotification("Something went wrong while trying to authenticate.", NotificationType.ERROR); + public void handleError(AuthenticationError authenticationError) { + OverlayUtils.showNotification(authenticationError.getErrorMessage(), NotificationType.ERROR); } }); } diff --git a/src/main/java/ee/carlrobert/codegpt/actions/editor/EditorActionsUtil.java b/src/main/java/ee/carlrobert/codegpt/actions/editor/EditorActionsUtil.java index 4dff373e8..4a85eaccb 100644 --- a/src/main/java/ee/carlrobert/codegpt/actions/editor/EditorActionsUtil.java +++ b/src/main/java/ee/carlrobert/codegpt/actions/editor/EditorActionsUtil.java @@ -21,7 +21,7 @@ public class EditorActionsUtil { public static Map DEFAULT_ACTIONS = new LinkedHashMap<>(Map.of( - "Find Bugs", "Find bugs in the selected code {{selectedCode}}", + "Find Bugs", "Find bugs and output code with bugs fixed in the following code: {{selectedCode}}", "Write Tests", "Write Tests for the selected code {{selectedCode}}", "Explain", "Explain the selected code {{selectedCode}}", "Refactor", "Refactor the selected code {{selectedCode}}", diff --git a/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/DeleteAllConversationsAction.java b/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/DeleteAllConversationsAction.java index 32e0c7f70..7408e9830 100644 --- a/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/DeleteAllConversationsAction.java +++ b/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/DeleteAllConversationsAction.java @@ -37,7 +37,7 @@ public void actionPerformed(@NotNull AnActionEvent event) { var project = event.getProject(); if (project != null) { ConversationService.getInstance().clearAll(); - StandardChatToolWindowContentManager.getInstance(project).resetTabbedPane(); + StandardChatToolWindowContentManager.getInstance(project).resetActiveTab(); } this.onRefresh.run(); } diff --git a/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/DeleteConversationAction.java b/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/DeleteConversationAction.java index 3bf84f817..02d617218 100644 --- a/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/DeleteConversationAction.java +++ b/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/DeleteConversationAction.java @@ -12,11 +12,11 @@ public class DeleteConversationAction extends AnAction { - private final Runnable onRefresh; + private final Runnable onDelete; - public DeleteConversationAction(Runnable onRefresh) { + public DeleteConversationAction(Runnable onDelete) { super("Delete Conversation", "Delete single conversation", AllIcons.General.Remove); - this.onRefresh = onRefresh; + this.onDelete = onDelete; EditorActionsUtil.registerOrReplaceAction(this); } @@ -31,7 +31,7 @@ public void actionPerformed(@NotNull AnActionEvent event) { var project = event.getProject(); if (project != null) { ConversationService.getInstance().deleteSelectedConversation(); - onRefresh.run(); + onDelete.run(); } } } diff --git a/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/MoveUpAction.java b/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/MoveUpAction.java index dac5db4ec..d3b6b28b8 100644 --- a/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/MoveUpAction.java +++ b/src/main/java/ee/carlrobert/codegpt/actions/toolwindow/MoveUpAction.java @@ -23,6 +23,6 @@ public void update(@NotNull AnActionEvent event) { @Override protected Optional getConversation(@NotNull Project project) { - return project.getService(ConversationService.class).getPreviousConversation(); + return ConversationService.getInstance().getPreviousConversation(); } } diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java index 1895fdc70..4dea2a85b 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionClientProvider.java @@ -2,42 +2,31 @@ import ee.carlrobert.codegpt.credentials.AzureCredentialsManager; import ee.carlrobert.codegpt.credentials.OpenAICredentialsManager; +import ee.carlrobert.codegpt.settings.advanced.AdvancedSettingsState; import ee.carlrobert.codegpt.settings.state.AzureSettingsState; import ee.carlrobert.codegpt.settings.state.OpenAISettingsState; -import ee.carlrobert.codegpt.settings.state.SettingsState; -import ee.carlrobert.codegpt.settings.advanced.AdvancedSettingsState; -import ee.carlrobert.openai.client.AzureClient; -import ee.carlrobert.openai.client.Client; -import ee.carlrobert.openai.client.OpenAIClient; -import ee.carlrobert.openai.client.ProxyAuthenticator; -import ee.carlrobert.openai.client.azure.AzureClientRequestParams; -import ee.carlrobert.openai.client.completion.CompletionClient; -import ee.carlrobert.openai.client.embeddings.EmbeddingsClient; +import ee.carlrobert.llm.client.Client; +import ee.carlrobert.llm.client.ProxyAuthenticator; +import ee.carlrobert.llm.client.azure.AzureClient; +import ee.carlrobert.llm.client.azure.AzureCompletionRequestParams; +import ee.carlrobert.llm.client.openai.OpenAIClient; +import ee.carlrobert.llm.client.you.YouClient; import java.net.InetSocketAddress; import java.net.Proxy; import java.util.concurrent.TimeUnit; public class CompletionClientProvider { - public static EmbeddingsClient getEmbeddingsClient() { - if (SettingsState.getInstance().isUseOpenAIService()) { - return getOpenAIClientBuilder().buildEmbeddingsClient(); - } - // TODO - return null; - } - - public static CompletionClient getChatCompletionClient(SettingsState settings) { - return getClientBuilder(settings).buildChatCompletionClient(); + public static OpenAIClient getOpenAIClient() { + return getOpenAIClientBuilder().build(); } - @Deprecated - public static CompletionClient getTextCompletionClient(SettingsState settings) { - return getClientBuilder(settings).buildTextCompletionClient(); + public static AzureClient getAzureClient() { + return getAzureClientBuilder().build(); } - public static Client.Builder getClientBuilder(SettingsState settings) { - return settings.isUseAzureService() ? getAzureClientBuilder() : getOpenAIClientBuilder(); + public static YouClient getYouClient(String sessionId, String accessToken) { + return new YouClient.Builder(sessionId, accessToken).build(); } private static OpenAIClient.Builder getOpenAIClientBuilder() { @@ -50,12 +39,20 @@ private static OpenAIClient.Builder getOpenAIClientBuilder() { private static AzureClient.Builder getAzureClientBuilder() { var settings = AzureSettingsState.getInstance(); - var params = new AzureClientRequestParams(settings.getResourceName(), settings.getDeploymentId(), settings.getApiVersion()); + var params = new AzureCompletionRequestParams(settings.getResourceName(), settings.getDeploymentId(), settings.getApiVersion()); var builder = new AzureClient.Builder(AzureCredentialsManager.getInstance().getSecret(), params) .setActiveDirectoryAuthentication(settings.isUseAzureActiveDirectoryAuthentication()); return (AzureClient.Builder) addDefaultClientParams(builder).setHost(settings.getBaseHost()); } + private static YouClient.Builder getYouClientBuilder() { + var settings = OpenAISettingsState.getInstance(); + var builder = new OpenAIClient + .Builder(OpenAICredentialsManager.getInstance().getApiKey()) + .setOrganization(settings.getOrganization()); + return (YouClient.Builder) addDefaultClientParams(builder).setHost(settings.getBaseHost()); + } + private static Client.Builder addDefaultClientParams(Client.Builder builder) { var advancedSettings = AdvancedSettingsState.getInstance(); var proxyHost = advancedSettings.getProxyHost(); diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java index d4d1df80d..5bf80eb80 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java @@ -2,10 +2,13 @@ import ee.carlrobert.codegpt.conversations.Conversation; import ee.carlrobert.codegpt.conversations.message.Message; -import ee.carlrobert.codegpt.settings.state.ModelSettingsState; +import ee.carlrobert.codegpt.settings.state.AzureSettingsState; +import ee.carlrobert.codegpt.settings.state.OpenAISettingsState; import ee.carlrobert.codegpt.settings.state.SettingsState; -import ee.carlrobert.openai.client.completion.CompletionEventListener; -import ee.carlrobert.openai.client.completion.ErrorDetails; +import ee.carlrobert.llm.client.openai.completion.ErrorDetails; +import ee.carlrobert.llm.client.you.completion.YouCompletionEventListener; +import ee.carlrobert.llm.client.you.completion.YouSerpResult; +import ee.carlrobert.llm.completion.CompletionEventListener; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -22,6 +25,7 @@ public class CompletionRequestHandler { private @Nullable Consumer messageListener; private @Nullable BiConsumer errorListener; private @Nullable Consumer completedListener; + private @Nullable Consumer> serpResultsListener; private @Nullable Runnable tokensExceededListener; private boolean useContextualSearch; @@ -46,48 +50,12 @@ public void addTokensExceededListener(Runnable tokensExceededListener) { this.tokensExceededListener = tokensExceededListener; } - public void call(Conversation conversation, Message message, boolean isRetry) { - swingWorker = new SwingWorker<>() { - protected Void doInBackground() { - try { - eventSource = startCall(conversation, message, isRetry, new CompletionEventListener() { - @Override - public void onMessage(String message) { - publish(message); - } - - @Override - public void onComplete(StringBuilder messageBuilder) { - if (completedListener != null) { - completedListener.accept(messageBuilder.toString()); - } - } - - @Override - public void onError(ErrorDetails error, Throwable ex) { - if (errorListener != null) { - errorListener.accept(error, ex); - } - } - }); - } catch (TotalUsageExceededException e) { - if (tokensExceededListener != null) { - tokensExceededListener.run(); - } - } - return null; - } + public void addSerpResultsListener(Consumer> serpResultsListener) { + this.serpResultsListener = serpResultsListener; + } - protected void process(List chunks) { - message.setResponse(messageBuilder.toString()); - for (String text : chunks) { - messageBuilder.append(text); - if (messageListener != null) { - messageListener.accept(text); - } - } - } - }; + public void call(Conversation conversation, Message message, boolean isRetry) { + swingWorker = new CompletionRequestWorker(conversation, message, isRetry); swingWorker.execute(); } @@ -104,14 +72,132 @@ private EventSource startCall( boolean isRetry, CompletionEventListener eventListener) { var settings = SettingsState.getInstance(); - var modelSettings = ModelSettingsState.getInstance(); var requestProvider = new CompletionRequestProvider(conversation); - if (modelSettings.isUseChatCompletion()) { - return CompletionClientProvider.getChatCompletionClient(settings).stream( - requestProvider.buildChatCompletionRequest(modelSettings.getChatCompletionModel(), message, isRetry, useContextualSearch), eventListener); + try { + if (settings.isUseYouService()) { + return CompletionClientProvider.getYouClient("", "") + .getChatCompletion(requestProvider.buildYouCompletionRequest(message), eventListener); + } + + if (settings.isUseAzureService()) { + var azureSettings = AzureSettingsState.getInstance(); + return CompletionClientProvider.getAzureClient().getChatCompletion( + requestProvider.buildOpenAIChatCompletionRequest( + azureSettings.getModel(), + message, + isRetry, + useContextualSearch, + azureSettings.isUsingCustomPath() ? azureSettings.getPath() : null), + eventListener); + } + + var openAISettings = OpenAISettingsState.getInstance(); + return CompletionClientProvider.getOpenAIClient().getChatCompletion( + requestProvider.buildOpenAIChatCompletionRequest( + openAISettings.getModel(), + message, + isRetry, + useContextualSearch, + openAISettings.isUsingCustomPath() ? openAISettings.getPath() : null), + eventListener); + } catch (Throwable t) { + if (errorListener != null) { + errorListener.accept(new ErrorDetails("Something went wrong"), t); + } + throw t; + } + } + + private class CompletionRequestWorker extends SwingWorker { + + private final Conversation conversation; + private final Message message; + private final boolean isRetry; + + public CompletionRequestWorker(Conversation conversation, Message message, boolean isRetry) { + this.conversation = conversation; + this.message = message; + this.isRetry = isRetry; + } + + protected Void doInBackground() { + try { + eventSource = startCall( + conversation, + message, + isRetry, + SettingsState.getInstance().isUseYouService() ? getYouCompletionEventListener() : getCompletionEventListener()); + } catch (TotalUsageExceededException e) { + if (tokensExceededListener != null) { + tokensExceededListener.run(); + } + } + return null; + } + + protected void process(List chunks) { + message.setResponse(messageBuilder.toString()); + for (String text : chunks) { + messageBuilder.append(text); + if (messageListener != null) { + messageListener.accept(text); + } + } + } + + private CompletionEventListener getCompletionEventListener() { + return new CompletionEventListener() { + @Override + public void onMessage(String message) { + publish(message); + } + + @Override + public void onComplete(StringBuilder messageBuilder) { + if (completedListener != null) { + completedListener.accept(messageBuilder.toString()); + } + } + + @Override + public void onError(ErrorDetails error, Throwable ex) { + if (errorListener != null) { + errorListener.accept(error, ex); + } + } + }; + } + + // TODO: Refactor + private YouCompletionEventListener getYouCompletionEventListener() { + return new YouCompletionEventListener() { + @Override + public void onMessage(String message) { + publish(message); + } + + @Override + public void onComplete(StringBuilder messageBuilder) { + if (completedListener != null) { + completedListener.accept(messageBuilder.toString()); + } + } + + @Override + public void onError(ErrorDetails error, Throwable ex) { + if (errorListener != null) { + errorListener.accept(error, ex); + } + } + + @Override + public void onSerpResults(List results) { + if (serpResultsListener != null) { + serpResultsListener.accept(results); + } + } + }; } - return CompletionClientProvider.getTextCompletionClient(settings).stream( - requestProvider.buildTextCompletionRequest(modelSettings.getTextCompletionModel(), message, isRetry), eventListener); } } diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java index 82fcc1380..ab1c501ba 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java @@ -8,18 +8,19 @@ import ee.carlrobert.codegpt.conversations.Conversation; import ee.carlrobert.codegpt.conversations.ConversationsState; import ee.carlrobert.codegpt.conversations.message.Message; -import ee.carlrobert.codegpt.embeddings.EmbeddingsService; -import ee.carlrobert.codegpt.settings.state.SettingsState; import ee.carlrobert.codegpt.settings.configuration.ConfigurationState; -import ee.carlrobert.codegpt.util.FileUtils; -import ee.carlrobert.openai.client.completion.chat.ChatCompletionModel; -import ee.carlrobert.openai.client.completion.chat.request.ChatCompletionMessage; -import ee.carlrobert.openai.client.completion.chat.request.ChatCompletionRequest; -import ee.carlrobert.openai.client.completion.text.TextCompletionModel; -import ee.carlrobert.openai.client.completion.text.request.TextCompletionRequest; +import ee.carlrobert.codegpt.settings.state.SettingsState; +import ee.carlrobert.embedding.EmbeddingsService; +import ee.carlrobert.llm.client.openai.completion.chat.OpenAIChatCompletionModel; +import ee.carlrobert.llm.client.openai.completion.chat.request.OpenAIChatCompletionMessage; +import ee.carlrobert.llm.client.openai.completion.chat.request.OpenAIChatCompletionRequest; +import ee.carlrobert.llm.client.you.completion.YouCompletionRequest; +import ee.carlrobert.llm.client.you.completion.YouCompletionRequestMessage; import java.util.ArrayList; import java.util.List; +import java.util.NoSuchElementException; import java.util.Objects; +import org.jetbrains.annotations.Nullable; public class CompletionRequestProvider { @@ -48,72 +49,85 @@ public class CompletionRequestProvider { private final Conversation conversation; public CompletionRequestProvider(Conversation conversation) { - this.embeddingsService = new EmbeddingsService( - CompletionClientProvider.getEmbeddingsClient(), - CompletionClientProvider.getChatCompletionClient(SettingsState.getInstance()), - CodeGPTPlugin.getPluginBasePath()); + this.embeddingsService = new EmbeddingsService(CompletionClientProvider.getOpenAIClient(), CodeGPTPlugin.getPluginBasePath()); this.conversation = conversation; } - public ChatCompletionRequest buildChatCompletionRequest(String model, Message message, boolean isRetry) { - return buildChatCompletionRequest(model, message, isRetry, false); + public YouCompletionRequest buildYouCompletionRequest(Message message) { + return new YouCompletionRequest.Builder(message.getPrompt()) + .setChatHistory(conversation.getMessages().stream() + .map(prevMessage -> new YouCompletionRequestMessage(prevMessage.getPrompt(), prevMessage.getResponse())) + .collect(toList())) + .build(); } - public ChatCompletionRequest buildChatCompletionRequest(String model, Message message, boolean isRetry, boolean useContextualSearch) { - return (ChatCompletionRequest) new ChatCompletionRequest.Builder(buildMessages(model, message, isRetry, useContextualSearch)) - .setModel(model) - .setMaxTokens(ConfigurationState.getInstance().getMaxTokens()) - .setTemperature(ConfigurationState.getInstance().getTemperature()) - .build(); + public OpenAIChatCompletionRequest buildOpenAIChatCompletionRequest(String model, Message message, boolean isRetry) { + return buildOpenAIChatCompletionRequest(model, message, isRetry, false, null); } - public TextCompletionRequest buildTextCompletionRequest(String model, Message message, boolean isRetry) { - return (TextCompletionRequest) new TextCompletionRequest.Builder(buildPrompt(model, message, isRetry)) - .setStop(List.of(" Human:", " AI:")) + public OpenAIChatCompletionRequest buildOpenAIChatCompletionRequest( + String model, + Message message, + boolean isRetry, + boolean useContextualSearch, + @Nullable String overriddenPath) { + var builder = new OpenAIChatCompletionRequest.Builder(buildMessages(model, message, isRetry, useContextualSearch)) .setModel(model) .setMaxTokens(ConfigurationState.getInstance().getMaxTokens()) - .setTemperature(ConfigurationState.getInstance().getTemperature()) - .build(); + .setTemperature(ConfigurationState.getInstance().getTemperature()); + + if (overriddenPath != null) { + builder.setOverriddenPath(overriddenPath); + } + + return (OpenAIChatCompletionRequest) builder.build(); } - private List buildMessages(String model, Message message, boolean isRetry, boolean useContextualSearch) { - var messages = new ArrayList(); + private List buildMessages(String model, Message message, boolean isRetry, boolean useContextualSearch) { + var messages = new ArrayList(); if (useContextualSearch) { - var context = embeddingsService.buildRelevantContext(message.getPrompt()); - var prompt = FileUtils.getResourceContent("/prompts/retrieval-prompt.txt") - .replace("{prompt}", message.getPrompt()) - .replace("{context}", context.getContext()); - + var prompt = embeddingsService.buildPromptWithContext(message.getPrompt()); LOG.info("Retrieved context:\n" + prompt); - messages.add(new ChatCompletionMessage("user", prompt)); + messages.add(new OpenAIChatCompletionMessage("user", prompt)); } else { var systemPrompt = ConfigurationState.getInstance().getSystemPrompt(); - messages.add(new ChatCompletionMessage("system", + messages.add(new OpenAIChatCompletionMessage("system", systemPrompt.isEmpty() ? COMPLETION_SYSTEM_PROMPT : systemPrompt)); for (var prevMessage : conversation.getMessages()) { if (isRetry && prevMessage.getId().equals(message.getId())) { break; } - messages.add(new ChatCompletionMessage("user", prevMessage.getPrompt())); - messages.add(new ChatCompletionMessage("assistant", prevMessage.getResponse())); + messages.add(new OpenAIChatCompletionMessage("user", prevMessage.getPrompt())); + messages.add(new OpenAIChatCompletionMessage("assistant", prevMessage.getResponse())); } - messages.add(new ChatCompletionMessage("user", message.getPrompt())); + messages.add(new OpenAIChatCompletionMessage("user", message.getPrompt())); + } + + if (SettingsState.getInstance().isUseYouService()) { + return messages; } int totalUsage = messages.parallelStream() .mapToInt(encodingManager::countMessageTokens) .sum() + ConfigurationState.getInstance().getMaxTokens(); - int modelMaxTokens = ChatCompletionModel.findByCode(model).getMaxTokens(); + int modelMaxTokens; + try { + modelMaxTokens = OpenAIChatCompletionModel.findByCode(model).getMaxTokens(); - if (totalUsage <= modelMaxTokens) { + if (totalUsage <= modelMaxTokens) { + return messages; + } + } catch (NoSuchElementException ex) { return messages; } - return tryReducingMessagesOrThrow(messages, totalUsage, modelMaxTokens); } - private List tryReducingMessagesOrThrow(List messages, int totalUsage, int modelMaxTokens) { + private List tryReducingMessagesOrThrow( + List messages, + int totalUsage, + int modelMaxTokens) { if (!ConversationsState.getInstance().discardAllTokenLimits) { if (!conversation.isDiscardTokenLimit()) { throw new TotalUsageExceededException(); @@ -132,33 +146,4 @@ private List tryReducingMessagesOrThrow(List - basePrompt.append("Human: ") - .append(prevMessage.getPrompt()) - .append("\n") - .append("AI: ") - .append(prevMessage.getResponse()) - .append("\n")); - basePrompt.append("Human: ") - .append(message.getPrompt()) - .append("\n") - .append("AI: ") - .append("\n"); - return basePrompt.toString(); - } } diff --git a/src/main/java/ee/carlrobert/codegpt/completions/SerpResult.java b/src/main/java/ee/carlrobert/codegpt/completions/SerpResult.java new file mode 100644 index 000000000..183825136 --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/completions/SerpResult.java @@ -0,0 +1,40 @@ +package ee.carlrobert.codegpt.completions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class SerpResult { + + private final String url; + private final String name; + private final String snippet; + private final String snippetSource; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public SerpResult( + @JsonProperty("url") String url, + @JsonProperty("name") String name, + @JsonProperty("snippet") String snippet, + @JsonProperty("snippetSource") String snippetSource) { + this.url = url; + this.name = name; + this.snippet = snippet; + this.snippetSource = snippetSource; + } + + public String getUrl() { + return url; + } + + public String getName() { + return name; + } + + public String getSnippet() { + return snippet; + } + + public String getSnippetSource() { + return snippetSource; + } +} diff --git a/src/main/java/ee/carlrobert/codegpt/conversations/Conversation.java b/src/main/java/ee/carlrobert/codegpt/conversations/Conversation.java index 10ccb20de..0aba2cbce 100644 --- a/src/main/java/ee/carlrobert/codegpt/conversations/Conversation.java +++ b/src/main/java/ee/carlrobert/codegpt/conversations/Conversation.java @@ -3,7 +3,6 @@ import static java.util.stream.Collectors.toList; import ee.carlrobert.codegpt.conversations.message.Message; -import ee.carlrobert.openai.client.ClientCode; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -13,7 +12,7 @@ public class Conversation { private UUID id; private List messages = new ArrayList<>(); - private ClientCode clientCode; + private String clientCode; private String model; private LocalDateTime createdOn; private LocalDateTime updatedOn; @@ -35,11 +34,11 @@ public void setMessages(List messages) { this.messages = messages; } - public ClientCode getClientCode() { + public String getClientCode() { return clientCode; } - public void setClientCode(ClientCode clientCode) { + public void setClientCode(String clientCode) { this.clientCode = clientCode; } diff --git a/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java b/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java index 538870dcf..7e4ecbaaa 100644 --- a/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java +++ b/src/main/java/ee/carlrobert/codegpt/conversations/ConversationService.java @@ -2,9 +2,12 @@ import static java.util.stream.Collectors.toList; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.Service; import ee.carlrobert.codegpt.conversations.message.Message; -import ee.carlrobert.codegpt.settings.state.ModelSettingsState; -import ee.carlrobert.openai.client.ClientCode; +import ee.carlrobert.codegpt.settings.state.AzureSettingsState; +import ee.carlrobert.codegpt.settings.state.OpenAISettingsState; +import ee.carlrobert.codegpt.settings.state.SettingsState; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Comparator; @@ -13,19 +16,16 @@ import java.util.UUID; import org.jetbrains.annotations.NotNull; -public class ConversationService { +@Service +public final class ConversationService { - private static ConversationService instance; private final ConversationsState conversationState = ConversationsState.getInstance(); private ConversationService() { } public static ConversationService getInstance() { - if (instance == null) { - instance = new ConversationService(); - } - return instance; + return ApplicationManager.getApplication().getService(ConversationService.class); } public List getSortedConversations() { @@ -37,11 +37,18 @@ public List getSortedConversations() { .collect(toList()); } - public Conversation createConversation(ClientCode clientCode) { + public Conversation createConversation(String clientCode) { + var settings = SettingsState.getInstance(); var conversation = new Conversation(); conversation.setId(UUID.randomUUID()); conversation.setClientCode(clientCode); - conversation.setModel(ModelSettingsState.getInstance().getCompletionModel()); + if (settings.isUseYouService()) { + conversation.setModel("YouCode"); + } else if (settings.isUseAzureService()) { + conversation.setModel(AzureSettingsState.getInstance().getModel()); + } else { + conversation.setModel(OpenAISettingsState.getInstance().getModel()); + } conversation.setCreatedOn(LocalDateTime.now()); conversation.setUpdatedOn(LocalDateTime.now()); return conversation; @@ -107,9 +114,19 @@ public void saveConversation(Conversation conversation) { conversationState.setCurrentConversation(conversation); } + private String getClientCode() { + var settings = SettingsState.getInstance(); + if (settings.isUseOpenAIService()) { + return "chat.completion"; + } + if (settings.isUseAzureService()) { + return "azure.chat.completion"; + } + return "you.chat.completion"; + } + public Conversation startConversation() { - var currentClientCode = ModelSettingsState.getInstance().isUseChatCompletion() ? ClientCode.CHAT_COMPLETION : ClientCode.TEXT_COMPLETION; - var conversation = createConversation(currentClientCode); + var conversation = createConversation(getClientCode()); conversationState.setCurrentConversation(conversation); addConversation(conversation); return conversation; diff --git a/src/main/java/ee/carlrobert/codegpt/conversations/ConversationsContainer.java b/src/main/java/ee/carlrobert/codegpt/conversations/ConversationsContainer.java index 8157c2ec1..f6c52e73c 100644 --- a/src/main/java/ee/carlrobert/codegpt/conversations/ConversationsContainer.java +++ b/src/main/java/ee/carlrobert/codegpt/conversations/ConversationsContainer.java @@ -1,19 +1,18 @@ package ee.carlrobert.codegpt.conversations; -import ee.carlrobert.openai.client.ClientCode; import java.util.HashMap; import java.util.List; import java.util.Map; public class ConversationsContainer { - private Map> conversationsMapping = new HashMap<>(); + private Map> conversationsMapping = new HashMap<>(); - public Map> getConversationsMapping() { + public Map> getConversationsMapping() { return conversationsMapping; } - public void setConversationsMapping(Map> conversationsMapping) { + public void setConversationsMapping(Map> conversationsMapping) { this.conversationsMapping = conversationsMapping; } } diff --git a/src/main/java/ee/carlrobert/codegpt/conversations/ConversationsState.java b/src/main/java/ee/carlrobert/codegpt/conversations/ConversationsState.java index ba3598d8c..0fb9d9028 100644 --- a/src/main/java/ee/carlrobert/codegpt/conversations/ConversationsState.java +++ b/src/main/java/ee/carlrobert/codegpt/conversations/ConversationsState.java @@ -8,16 +8,12 @@ import com.intellij.util.xmlb.annotations.OptionTag; import ee.carlrobert.codegpt.conversations.converter.ConversationConverter; import ee.carlrobert.codegpt.conversations.converter.ConversationsConverter; -import ee.carlrobert.openai.client.ClientCode; import java.util.List; import java.util.Map; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -@State( - name = "ee.carlrobert.codegpt.state.conversations.ConversationsState", - storages = @Storage("ChatGPTConversations_170.xml") -) +@State(name = "ee.carlrobert.codegpt.state.conversations.ConversationsState", storages = @Storage("ChatGPTConversations_170.xml")) public class ConversationsState implements PersistentStateComponent { @OptionTag(converter = ConversationsConverter.class) @@ -55,7 +51,7 @@ public void setCurrentConversation(@Nullable Conversation conversation) { return getInstance().currentConversation; } - public Map> getConversationsMapping() { + public Map> getConversationsMapping() { return conversationsContainer.getConversationsMapping(); } } diff --git a/src/main/java/ee/carlrobert/codegpt/conversations/message/Message.java b/src/main/java/ee/carlrobert/codegpt/conversations/message/Message.java index 941529c07..b3ef58b5c 100644 --- a/src/main/java/ee/carlrobert/codegpt/conversations/message/Message.java +++ b/src/main/java/ee/carlrobert/codegpt/conversations/message/Message.java @@ -2,6 +2,8 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import ee.carlrobert.codegpt.completions.SerpResult; +import java.util.List; import java.util.Objects; import java.util.UUID; @@ -11,6 +13,7 @@ public class Message { private final String prompt; private String response; private String userMessage; + private List serpResults; public Message(String prompt, String response) { this(prompt); @@ -47,6 +50,14 @@ public void setUserMessage(String userMessage) { this.userMessage = userMessage; } + public List getSerpResults() { + return serpResults; + } + + public void setSerpResults(List serpResults) { + this.serpResults = serpResults; + } + @Override public boolean equals(Object obj) { if (obj == this) { diff --git a/src/main/java/ee/carlrobert/codegpt/credentials/AzureCredentialsManager.java b/src/main/java/ee/carlrobert/codegpt/credentials/AzureCredentialsManager.java index 8ca42fc0b..883de476c 100644 --- a/src/main/java/ee/carlrobert/codegpt/credentials/AzureCredentialsManager.java +++ b/src/main/java/ee/carlrobert/codegpt/credentials/AzureCredentialsManager.java @@ -1,16 +1,18 @@ package ee.carlrobert.codegpt.credentials; import com.intellij.credentialStore.CredentialAttributes; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.Service; import ee.carlrobert.codegpt.settings.state.AzureSettingsState; import org.jetbrains.annotations.Nullable; -public class AzureCredentialsManager { +@Service +public final class AzureCredentialsManager { private static final CredentialAttributes azureOpenAIApiKeyCredentialAttributes = CredentialsUtil.createCredentialAttributes("AZURE_OPENAI_API_KEY"); private static final CredentialAttributes azureActiveDirectoryTokenCredentialAttributes = CredentialsUtil.createCredentialAttributes("AZURE_ACTIVE_DIRECTORY_TOKEN"); - private static AzureCredentialsManager instance; private String azureOpenAIApiKey; private String azureActiveDirectoryToken; @@ -21,10 +23,7 @@ private AzureCredentialsManager() { } public static AzureCredentialsManager getInstance() { - if (instance == null) { - instance = new AzureCredentialsManager(); - } - return instance; + return ApplicationManager.getApplication().getService(AzureCredentialsManager.class); } public String getSecret() { diff --git a/src/main/java/ee/carlrobert/codegpt/credentials/OpenAICredentialsManager.java b/src/main/java/ee/carlrobert/codegpt/credentials/OpenAICredentialsManager.java index aee0216fe..71c4b362e 100644 --- a/src/main/java/ee/carlrobert/codegpt/credentials/OpenAICredentialsManager.java +++ b/src/main/java/ee/carlrobert/codegpt/credentials/OpenAICredentialsManager.java @@ -1,12 +1,14 @@ package ee.carlrobert.codegpt.credentials; import com.intellij.credentialStore.CredentialAttributes; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.Service; import org.jetbrains.annotations.Nullable; -public class OpenAICredentialsManager { +@Service +public final class OpenAICredentialsManager { private static final CredentialAttributes openAIApiKeyCredentialAttributes = CredentialsUtil.createCredentialAttributes("OPENAI_API_KEY"); - private static OpenAICredentialsManager instance; private String openAIApiKey; @@ -15,10 +17,7 @@ private OpenAICredentialsManager() { } public static OpenAICredentialsManager getInstance() { - if (instance == null) { - instance = new OpenAICredentialsManager(); - } - return instance; + return ApplicationManager.getApplication().getService(OpenAICredentialsManager.class); } public boolean isApiKeySet() { diff --git a/src/main/java/ee/carlrobert/codegpt/credentials/UserCredentialsManager.java b/src/main/java/ee/carlrobert/codegpt/credentials/UserCredentialsManager.java index fdf6ba298..bc14d7af8 100644 --- a/src/main/java/ee/carlrobert/codegpt/credentials/UserCredentialsManager.java +++ b/src/main/java/ee/carlrobert/codegpt/credentials/UserCredentialsManager.java @@ -1,14 +1,15 @@ package ee.carlrobert.codegpt.credentials; import com.intellij.credentialStore.CredentialAttributes; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.Service; import org.jetbrains.annotations.Nullable; -public class UserCredentialsManager { +@Service +public final class UserCredentialsManager { private static final CredentialAttributes accountPasswordCredentialAttributes = CredentialsUtil.createCredentialAttributes("ACCOUNT_PASSWORD"); - private static UserCredentialsManager instance; - private String accountPassword; private UserCredentialsManager() { @@ -16,10 +17,7 @@ private UserCredentialsManager() { } public static UserCredentialsManager getInstance() { - if (instance == null) { - instance = new UserCredentialsManager(); - } - return instance; + return ApplicationManager.getApplication().getService(UserCredentialsManager.class); } public @Nullable String getAccountPassword() { diff --git a/src/main/java/ee/carlrobert/codegpt/indexes/CodebaseIndexingTask.java b/src/main/java/ee/carlrobert/codegpt/indexes/CodebaseIndexingTask.java index 131ed0bb2..9ca7f6f3d 100644 --- a/src/main/java/ee/carlrobert/codegpt/indexes/CodebaseIndexingTask.java +++ b/src/main/java/ee/carlrobert/codegpt/indexes/CodebaseIndexingTask.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.jelmerk.knn.Item; import com.intellij.notification.NotificationType; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; @@ -13,12 +14,11 @@ import ee.carlrobert.codegpt.CodeGPTBundle; import ee.carlrobert.codegpt.CodeGPTPlugin; import ee.carlrobert.codegpt.completions.CompletionClientProvider; -import ee.carlrobert.codegpt.embeddings.EmbeddingsService; -import ee.carlrobert.codegpt.embeddings.VectorStore; -import ee.carlrobert.codegpt.embeddings.CheckedFile; -import ee.carlrobert.codegpt.settings.state.SettingsState; import ee.carlrobert.codegpt.util.FileUtils; import ee.carlrobert.codegpt.util.OverlayUtils; +import ee.carlrobert.embedding.CheckedFile; +import ee.carlrobert.embedding.EmbeddingsService; +import ee.carlrobert.vector.VectorStore; import java.util.List; import java.util.Map; import org.jetbrains.annotations.NotNull; @@ -34,10 +34,7 @@ public CodebaseIndexingTask(Project project, List checkedFiles) { super(project, CodeGPTBundle.get("codebaseIndexing.task.title"), true); this.project = project; this.checkedFiles = checkedFiles; - this.embeddingsService = new EmbeddingsService( - CompletionClientProvider.getEmbeddingsClient(), - CompletionClientProvider.getChatCompletionClient(SettingsState.getInstance()), - CodeGPTPlugin.getPluginBasePath()); + this.embeddingsService = new EmbeddingsService(CompletionClientProvider.getOpenAIClient(), CodeGPTPlugin.getPluginBasePath()); } public void run() { @@ -63,7 +60,7 @@ public void run(@NotNull ProgressIndicator indicator) { try { indicator.setFraction(0); - var embeddings = embeddingsService.createEmbeddings(checkedFiles, indicator); + List> embeddings = embeddingsService.createEmbeddings(checkedFiles, indicator); VectorStore.getInstance(CodeGPTPlugin.getPluginBasePath()).save(embeddings); OverlayUtils.showNotification("Indexing completed", NotificationType.INFORMATION); diff --git a/src/main/java/ee/carlrobert/codegpt/indexes/FolderStructureTreePanel.java b/src/main/java/ee/carlrobert/codegpt/indexes/FolderStructureTreePanel.java index 97271c620..316ee715e 100644 --- a/src/main/java/ee/carlrobert/codegpt/indexes/FolderStructureTreePanel.java +++ b/src/main/java/ee/carlrobert/codegpt/indexes/FolderStructureTreePanel.java @@ -19,7 +19,7 @@ import com.intellij.ui.components.JBLabel; import com.intellij.util.ui.AsyncProcessIcon; import com.intellij.util.ui.JBUI; -import ee.carlrobert.codegpt.embeddings.CheckedFile; +import ee.carlrobert.embedding.CheckedFile; import ee.carlrobert.codegpt.util.FileUtils; import java.awt.BorderLayout; import java.awt.FlowLayout; diff --git a/src/main/java/ee/carlrobert/codegpt/settings/BaseModelComboBox.java b/src/main/java/ee/carlrobert/codegpt/settings/ModelComboBox.java similarity index 78% rename from src/main/java/ee/carlrobert/codegpt/settings/BaseModelComboBox.java rename to src/main/java/ee/carlrobert/codegpt/settings/ModelComboBox.java index 293d2d7ff..7c1cf2ebf 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/BaseModelComboBox.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/ModelComboBox.java @@ -1,14 +1,14 @@ package ee.carlrobert.codegpt.settings; import com.intellij.openapi.ui.ComboBox; -import ee.carlrobert.openai.client.completion.CompletionModel; +import ee.carlrobert.llm.completion.CompletionModel; import java.awt.Component; import javax.swing.JList; import javax.swing.plaf.basic.BasicComboBoxRenderer; -public class BaseModelComboBox extends ComboBox { +public class ModelComboBox extends ComboBox { - public BaseModelComboBox(CompletionModel[] options, CompletionModel selectedModel) { + public ModelComboBox(CompletionModel[] options, CompletionModel selectedModel) { super(options); setSelectedItem(selectedModel); setRenderer(getBasicComboBoxRenderer()); diff --git a/src/main/java/ee/carlrobert/codegpt/settings/ModelSelectionForm.java b/src/main/java/ee/carlrobert/codegpt/settings/ModelSelectionForm.java deleted file mode 100644 index a91a322bb..000000000 --- a/src/main/java/ee/carlrobert/codegpt/settings/ModelSelectionForm.java +++ /dev/null @@ -1,142 +0,0 @@ -package ee.carlrobert.codegpt.settings; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.ui.ComboBox; -import com.intellij.ui.components.JBRadioButton; -import com.intellij.util.ui.FormBuilder; -import com.intellij.util.ui.JBUI; -import ee.carlrobert.codegpt.CodeGPTBundle; -import ee.carlrobert.codegpt.settings.state.ModelSettingsState; -import ee.carlrobert.codegpt.util.SwingUtils; -import ee.carlrobert.openai.client.completion.CompletionModel; -import ee.carlrobert.openai.client.completion.chat.ChatCompletionModel; -import ee.carlrobert.openai.client.completion.text.TextCompletionModel; -import java.util.NoSuchElementException; -import javax.swing.ButtonGroup; -import javax.swing.JPanel; - -public class ModelSelectionForm { - - private static final Logger LOG = Logger.getInstance(ModelSelectionForm.class); - - private static final String modelSelectionLabel = CodeGPTBundle.get("settingsConfigurable.section.model.selectionFieldLabel"); - - private final ComboBox chatCompletionBaseModelComboBox; - private final ComboBox textCompletionBaseModelComboBox; - private final JBRadioButton useChatCompletionRadioButton; - private final JBRadioButton useTextCompletionRadioButton; - private final JPanel chatCompletionModelsPanel; - private final JPanel textCompletionModelsPanel; - - private CompletionModel findChatCompletionModelOrGetDefault(ModelSettingsState settings) { - try { - return ChatCompletionModel.findByCode(settings.getChatCompletionModel()); - } catch (NoSuchElementException e) { - LOG.warn("Couldn't find completion model with code: " + settings.getChatCompletionModel()); - return ChatCompletionModel.GPT_3_5; - } - } - - private CompletionModel findTextCompletionModelOrGetDefault(ModelSettingsState settings) { - try { - return TextCompletionModel.findByCode(settings.getTextCompletionModel()); - } catch (NoSuchElementException e) { - LOG.warn("Couldn't find completion model with code: " + settings.getTextCompletionModel()); - return TextCompletionModel.DAVINCI; - } - } - - public ModelSelectionForm() { - var settings = ModelSettingsState.getInstance(); - chatCompletionBaseModelComboBox = new BaseModelComboBox( - new ChatCompletionModel[] { - ChatCompletionModel.GPT_3_5, - ChatCompletionModel.GPT_3_5_16k, - ChatCompletionModel.GPT_4, - ChatCompletionModel.GPT_4_32k - }, - findChatCompletionModelOrGetDefault(settings)); - chatCompletionModelsPanel = SwingUtils.createPanel( - chatCompletionBaseModelComboBox, modelSelectionLabel, false); - chatCompletionModelsPanel.setBorder(JBUI.Borders.emptyLeft(16)); - textCompletionBaseModelComboBox = new BaseModelComboBox( - new TextCompletionModel[] { - TextCompletionModel.DAVINCI, - TextCompletionModel.CURIE, - TextCompletionModel.BABBAGE, - TextCompletionModel.ADA, - }, - findTextCompletionModelOrGetDefault(settings)); - textCompletionModelsPanel = SwingUtils.createPanel(textCompletionBaseModelComboBox, modelSelectionLabel); - textCompletionModelsPanel.setBorder(JBUI.Borders.emptyLeft(16)); - useChatCompletionRadioButton = new JBRadioButton( - CodeGPTBundle.get("settingsConfigurable.section.model.useChatCompletionRadioButtonLabel"), - settings.isUseChatCompletion()); - useTextCompletionRadioButton = new JBRadioButton( - CodeGPTBundle.get("settingsConfigurable.section.model.useTextCompletionRadioButtonLabel"), - settings.isUseTextCompletion()); - - registerFields(); - registerRadioButtons(); - } - - public JPanel getForm() { - var form = FormBuilder.createFormBuilder() - .addComponent(useChatCompletionRadioButton) - .addComponent(chatCompletionModelsPanel) - .addComponent(useTextCompletionRadioButton) - .addComponent(textCompletionModelsPanel) - .getPanel(); - form.setBorder(JBUI.Borders.emptyLeft(16)); - return form; - } - - public boolean isChatCompletionOptionSelected() { - return useChatCompletionRadioButton.isSelected(); - } - - public void setUseChatCompletionSelected(boolean isSelected) { - useChatCompletionRadioButton.setSelected(isSelected); - } - - public boolean isTextCompletionOptionSelected() { - return useTextCompletionRadioButton.isSelected(); - } - - public void setUseTextCompletionSelected(boolean isSelected) { - useTextCompletionRadioButton.setSelected(isSelected); - } - - public TextCompletionModel getTextCompletionBaseModel() { - return (TextCompletionModel) textCompletionBaseModelComboBox.getSelectedItem(); - } - - public void setTextCompletionBaseModel(String modelCode) { - textCompletionBaseModelComboBox.setSelectedItem(TextCompletionModel.findByCode(modelCode)); - } - - public ChatCompletionModel getChatCompletionBaseModel() { - return (ChatCompletionModel) chatCompletionBaseModelComboBox.getSelectedItem(); - } - - public void setChatCompletionBaseModel(String modelCode) { - chatCompletionBaseModelComboBox.setSelectedItem(ChatCompletionModel.findByCode(modelCode)); - } - - private void registerRadioButtons() { - var completionButtonGroup = new ButtonGroup(); - completionButtonGroup.add(useChatCompletionRadioButton); - completionButtonGroup.add(useTextCompletionRadioButton); - useChatCompletionRadioButton.addActionListener(e -> enableModelFields(true)); - useTextCompletionRadioButton.addActionListener(e -> enableModelFields(false)); - } - - private void registerFields() { - enableModelFields(useChatCompletionRadioButton.isSelected()); - } - - private void enableModelFields(boolean isChatCompletionModel) { - chatCompletionBaseModelComboBox.setEnabled(isChatCompletionModel); - textCompletionBaseModelComboBox.setEnabled(!isChatCompletionModel); - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/settings/ServiceChangeNotifier.java b/src/main/java/ee/carlrobert/codegpt/settings/ServiceChangeNotifier.java new file mode 100644 index 000000000..fd729cecb --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/settings/ServiceChangeNotifier.java @@ -0,0 +1,2 @@ +package ee.carlrobert.codegpt.settings;public class ServiceChangeNotifier { +} diff --git a/src/main/java/ee/carlrobert/codegpt/settings/ServiceSelectionForm.java b/src/main/java/ee/carlrobert/codegpt/settings/ServiceSelectionForm.java index e8a0ac054..7762fa920 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/ServiceSelectionForm.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/ServiceSelectionForm.java @@ -1,6 +1,10 @@ package ee.carlrobert.codegpt.settings; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.openapi.util.Disposer; import com.intellij.ui.TitledSeparator; +import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.components.JBPasswordField; import com.intellij.ui.components.JBRadioButton; import com.intellij.ui.components.JBTextField; @@ -13,20 +17,37 @@ import ee.carlrobert.codegpt.settings.state.AzureSettingsState; import ee.carlrobert.codegpt.settings.state.OpenAISettingsState; import ee.carlrobert.codegpt.settings.state.SettingsState; +import ee.carlrobert.codegpt.user.UserManager; +import ee.carlrobert.codegpt.user.auth.AuthenticationNotifier; import ee.carlrobert.codegpt.util.SwingUtils; +import ee.carlrobert.llm.client.openai.completion.chat.OpenAIChatCompletionModel; +import ee.carlrobert.llm.completion.CompletionModel; +import java.awt.FlowLayout; +import java.util.List; import java.util.Map; +import javax.swing.Box; import javax.swing.ButtonGroup; +import javax.swing.JComponent; import javax.swing.JPanel; public class ServiceSelectionForm { + private static final OpenAIChatCompletionModel[] DEFAULT_OPENAI_MODELS = new OpenAIChatCompletionModel[] { + OpenAIChatCompletionModel.GPT_3_5, + OpenAIChatCompletionModel.GPT_3_5_16k, + OpenAIChatCompletionModel.GPT_4, + OpenAIChatCompletionModel.GPT_4_32k + }; + private final JBRadioButton useOpenAIServiceRadioButton; private final JBRadioButton useAzureServiceRadioButton; - private final JBPasswordField openAIApiKey; + private final JBPasswordField openAIApiKeyField; private final JBTextField openAIBaseHostField; + private final JBTextField openAIPathField; private final JBTextField openAIOrganizationField; private final JPanel openAIServiceSectionPanel; + private final ComboBox openAICompletionModelComboBox; private final JBRadioButton useAzureApiKeyAuthenticationRadioButton; private final JBPasswordField azureApiKeyField; @@ -35,17 +56,23 @@ public class ServiceSelectionForm { private final JBPasswordField azureActiveDirectoryTokenField; private final JPanel azureActiveDirectoryTokenFieldPanel; private final JBTextField azureBaseHostField; + private final JBTextField azurePathField; private final JBTextField azureResourceNameField; private final JBTextField azureDeploymentIdField; private final JBTextField azureApiVersionField; private final JPanel azureServiceSectionPanel; + private final ComboBox azureCompletionModelComboBox; + + private final JBRadioButton useYouServiceRadioButton; + private final JPanel youServiceSectionPanel; + private final JBCheckBox displayWebSearchResultsCheckBox; public ServiceSelectionForm(SettingsState settings) { var openAISettings = OpenAISettingsState.getInstance(); var azureSettings = AzureSettingsState.getInstance(); - openAIApiKey = new JBPasswordField(); - openAIApiKey.setColumns(30); - openAIApiKey.setText(OpenAICredentialsManager.getInstance().getApiKey()); + openAIApiKeyField = new JBPasswordField(); + openAIApiKeyField.setColumns(30); + openAIApiKeyField.setText(OpenAICredentialsManager.getInstance().getApiKey()); azureApiKeyField = new JBPasswordField(); azureApiKeyField.setColumns(30); @@ -76,31 +103,188 @@ public ServiceSelectionForm(SettingsState settings) { CodeGPTBundle.get("settingsConfigurable.section.service.useOpenAIServiceRadioButtonLabel"), settings.isUseOpenAIService()); useAzureServiceRadioButton = new JBRadioButton( CodeGPTBundle.get("settingsConfigurable.section.service.useAzureServiceRadioButtonLabel"), settings.isUseAzureService()); + useYouServiceRadioButton = new JBRadioButton( + CodeGPTBundle.get("settingsConfigurable.section.service.useYouServiceRadioButtonLabel"), settings.isUseYouService()); openAIBaseHostField = new JBTextField(openAISettings.getBaseHost(), 30); + openAIPathField = new JBTextField(openAISettings.getPath(), 30); openAIOrganizationField = new JBTextField(openAISettings.getOrganization(), 30); + openAICompletionModelComboBox = new ModelComboBox(DEFAULT_OPENAI_MODELS, OpenAIChatCompletionModel.findByCode(openAISettings.getModel())); - azureBaseHostField = new JBTextField(azureSettings.getBaseHost(), 30); - azureResourceNameField = new JBTextField(azureSettings.getResourceName(), 30); - azureDeploymentIdField = new JBTextField(azureSettings.getDeploymentId(), 30); - azureApiVersionField = new JBTextField(azureSettings.getApiVersion(), 30); + azureBaseHostField = new JBTextField(azureSettings.getBaseHost(), 35); + azurePathField = new JBTextField(azureSettings.getPath(), 35); + azureResourceNameField = new JBTextField(azureSettings.getResourceName(), 35); + azureDeploymentIdField = new JBTextField(azureSettings.getDeploymentId(), 35); + azureApiVersionField = new JBTextField(azureSettings.getApiVersion(), 35); + azureCompletionModelComboBox = new ModelComboBox(DEFAULT_OPENAI_MODELS, OpenAIChatCompletionModel.findByCode(azureSettings.getModel())); + azureCompletionModelComboBox.getEditor().getEditorComponent().setMaximumSize(azureBaseHostField.getPreferredSize()); + + displayWebSearchResultsCheckBox = new JBCheckBox("Display web search results", settings.isDisplayWebSearchResults()); + displayWebSearchResultsCheckBox.setEnabled(UserManager.getInstance().isAuthenticated()); openAIServiceSectionPanel = createOpenAIServiceSectionPanel(); azureServiceSectionPanel = createAzureServiceSectionPanel(); + youServiceSectionPanel = createYouServiceSectionPanel(); registerPanelsVisibility(settings, azureSettings); registerRadioButtons(); + + ApplicationManager.getApplication() + .getMessageBus() + .connect() + .subscribe(AuthenticationNotifier.AUTHENTICATION_TOPIC, (AuthenticationNotifier) () -> displayWebSearchResultsCheckBox.setEnabled(true)); } public JPanel getForm() { - var form = FormBuilder.createFormBuilder() - .addComponent(useOpenAIServiceRadioButton) + var panel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0)); + panel.add(useOpenAIServiceRadioButton); + // flow layout's horizontal gap adds annoying horizontal padding on each sides + panel.add(Box.createHorizontalStrut(16)); + panel.add(useAzureServiceRadioButton); + panel.add(Box.createHorizontalStrut(16)); + panel.add(useYouServiceRadioButton); + + return FormBuilder.createFormBuilder() + .addComponent(withEmptyLeftBorder(panel)) .addComponent(openAIServiceSectionPanel) - .addComponent(useAzureServiceRadioButton) .addComponent(azureServiceSectionPanel) + .addComponent(youServiceSectionPanel) + .getPanel(); + } + + private JPanel createOpenAIServiceSectionPanel() { + var requestConfigurationPanel = UI.PanelFactory.grid() + .add(UI.PanelFactory.panel(openAIOrganizationField) + .withLabel(CodeGPTBundle.get("settingsConfigurable.section.service.openai.organizationField.label")) + .resizeX(false) + .withComment(CodeGPTBundle.get("settingsConfigurable.section.service.openai.organizationField.comment"))) + .add(UI.PanelFactory.panel(openAIBaseHostField) + .withLabel("Base host:") + .resizeX(false)) + .add(UI.PanelFactory.panel(openAIPathField) + .withLabel("Path:") + .resizeX(false)) + .add(UI.PanelFactory.panel(openAICompletionModelComboBox) + .withLabel("Model:") + .resizeX(false)) + .createPanel(); + + var apiKeyFieldPanel = UI.PanelFactory.panel(openAIApiKeyField) + .withLabel(CodeGPTBundle.get("settingsConfigurable.section.integration.apiKeyField.label")) + .resizeX(false) + .withComment(CodeGPTBundle.get("settingsConfigurable.section.integration.apiKeyField.comment")) + .withCommentHyperlinkListener(SwingUtils::handleHyperlinkClicked) + .createPanel(); + + return FormBuilder.createFormBuilder() + .addComponent(new TitledSeparator("Authentication")) + .addComponent(withEmptyLeftBorder(apiKeyFieldPanel)) + .addComponent(new TitledSeparator("Request Configuration")) + .addComponent(withEmptyLeftBorder(requestConfigurationPanel)) + .addComponentFillVertically(new JPanel(), 0) + .getPanel(); + } + + private JPanel createAzureServiceSectionPanel() { + var authPanel = withEmptyLeftBorder(FormBuilder.createFormBuilder() + .addComponent(UI.PanelFactory + .panel(useAzureApiKeyAuthenticationRadioButton) + .resizeX(false) + .createPanel()) + .addComponent(withEmptyLeftBorder(azureApiKeyFieldPanel)) + .addComponent(UI.PanelFactory + .panel(useAzureActiveDirectoryAuthenticationRadioButton) + .resizeX(false) + .createPanel()) + .addComponent(withEmptyLeftBorder(azureActiveDirectoryTokenFieldPanel)) + .getPanel()); + + var configPanel = withEmptyLeftBorder(UI.PanelFactory.grid() + .add(UI.PanelFactory.panel(azureResourceNameField) + .withLabel(CodeGPTBundle.get("settingsConfigurable.section.service.azure.resourceNameField.label")) + .resizeX(false) + .withComment(CodeGPTBundle.get("settingsConfigurable.section.service.azure.resourceNameField.comment"))) + .add(UI.PanelFactory.panel(azureDeploymentIdField) + .withLabel(CodeGPTBundle.get("settingsConfigurable.section.service.azure.deploymentIdField.label")) + .resizeX(false) + .withComment(CodeGPTBundle.get("settingsConfigurable.section.service.azure.deploymentIdField.comment"))) + .add(UI.PanelFactory.panel(azureApiVersionField) + .withLabel(CodeGPTBundle.get("settingsConfigurable.section.service.azure.apiVersionField.label")) + .resizeX(false) + .withComment(CodeGPTBundle.get("settingsConfigurable.section.service.azure.apiVersionField.comment"))) + .add(UI.PanelFactory.panel(azureBaseHostField) + .withLabel("Base host:") + .resizeX(false)) + .add(UI.PanelFactory.panel(azurePathField) + .withLabel("Path:") + .resizeX(false)) + .add(UI.PanelFactory.panel(azureCompletionModelComboBox) + .withLabel("Model:") + .resizeX(false)) + .createPanel()); + + return FormBuilder.createFormBuilder() + .addComponent(new TitledSeparator("Authentication")) + .addComponent(authPanel) + .addComponent(new TitledSeparator("Request Configuration")) + .addComponent(configPanel) + .getPanel(); + } + + private JPanel createYouServiceSectionPanel() { + return FormBuilder.createFormBuilder() + .addComponent(new UserDetailsSettingsPanel(Disposer.newDisposable())) + .addComponent(new TitledSeparator("Chat Preferences")) + .addComponent(withEmptyLeftBorder(displayWebSearchResultsCheckBox)) .getPanel(); - form.setBorder(JBUI.Borders.emptyLeft(16)); - return form; + } + + private JComponent withEmptyLeftBorder(JComponent component) { + component.setBorder(JBUI.Borders.emptyLeft(16)); + return component; + } + + private void registerPanelsVisibility(SettingsState settings, AzureSettingsState azureSettings) { + openAIServiceSectionPanel.setVisible(settings.isUseOpenAIService()); + azureServiceSectionPanel.setVisible(settings.isUseAzureService()); + azureApiKeyFieldPanel.setVisible(azureSettings.isUseAzureApiKeyAuthentication()); + azureActiveDirectoryTokenFieldPanel.setVisible(azureSettings.isUseAzureActiveDirectoryAuthentication()); + youServiceSectionPanel.setVisible(settings.isUseYouService()); + } + + private void registerRadioButtons() { + registerRadioButtons( + List.of( + Map.entry(useOpenAIServiceRadioButton, openAIServiceSectionPanel), + Map.entry(useAzureServiceRadioButton, azureServiceSectionPanel), + Map.entry(useYouServiceRadioButton, youServiceSectionPanel))); + registerRadioButtons( + List.of( + Map.entry(useAzureApiKeyAuthenticationRadioButton, azureApiKeyFieldPanel), + Map.entry(useAzureActiveDirectoryAuthenticationRadioButton, azureActiveDirectoryTokenFieldPanel))); + } + + private void registerRadioButtons(List> entries) { + var buttonGroup = new ButtonGroup(); + entries.forEach(entry -> buttonGroup.add(entry.getKey())); + + entries.forEach(entry -> entry.getKey().addActionListener((e) -> { + for (Map.Entry innerEntry : entries) { + innerEntry.getValue().setVisible(innerEntry.equals(entry)); + } + })); + } + + public OpenAIChatCompletionModel getSelectedCompletionModel() { + return (OpenAIChatCompletionModel) (isOpenAIServiceSelected() ? + openAICompletionModelComboBox.getSelectedItem() : + azureCompletionModelComboBox.getSelectedItem()); + } + + public void setSelectedChatCompletionModel(OpenAIChatCompletionModel chatCompletionModel) { + if (isOpenAIServiceSelected()) { + openAICompletionModelComboBox.setSelectedItem(chatCompletionModel); + } } public void setOpenAIServiceSelected(boolean selected) { @@ -119,12 +303,20 @@ public boolean isAzureServiceSelected() { return useAzureServiceRadioButton.isSelected(); } + public boolean isYouServiceSelected() { + return useYouServiceRadioButton.isSelected(); + } + + public void setYouServiceSelected(boolean selected) { + useYouServiceRadioButton.setSelected(selected); + } + public void setOpenAIApiKey(String apiKey) { - openAIApiKey.setText(apiKey); + openAIApiKeyField.setText(apiKey); } public String getOpenAIApiKey() { - return new String(openAIApiKey.getPassword()); + return new String(openAIApiKeyField.getPassword()); } public void setOpenAIBaseHost(String baseHost) { @@ -143,6 +335,14 @@ public String getOpenAIOrganization() { return openAIOrganizationField.getText(); } + public void setOpenAIModel(String model) { + openAICompletionModelComboBox.setSelectedItem(OpenAIChatCompletionModel.findByCode(model)); + } + + public String getOpenAIModel() { + return ((OpenAIChatCompletionModel) (openAICompletionModelComboBox.getModel().getSelectedItem())).getCode(); + } + public void setAzureActiveDirectoryAuthenticationSelected(boolean selected) { useAzureActiveDirectoryAuthenticationRadioButton.setSelected(selected); } @@ -207,94 +407,35 @@ public String getAzureBaseHost() { return azureBaseHostField.getText(); } - private JPanel createOpenAIServiceSectionPanel() { - var panel = UI.PanelFactory.grid() - .add(UI.PanelFactory.panel(openAIApiKey) - .withLabel(CodeGPTBundle.get("settingsConfigurable.section.integration.apiKeyField.label")) - .resizeX(false) - .withComment(CodeGPTBundle.get("settingsConfigurable.section.integration.apiKeyField.comment")) - .withCommentHyperlinkListener(SwingUtils::handleHyperlinkClicked)) - .add(UI.PanelFactory.panel(openAIOrganizationField) - .withLabel(CodeGPTBundle.get("settingsConfigurable.section.service.openai.organizationField.label")) - .resizeX(false) - .withComment(CodeGPTBundle.get("settingsConfigurable.section.service.openai.organizationField.comment"))) - .add(UI.PanelFactory.panel(openAIBaseHostField) - .withLabel("Base host:") - .resizeX(false)) - .createPanel(); - panel.setBorder(JBUI.Borders.emptyLeft(16)); - return panel; + public void setAzureModel(String model) { + azureCompletionModelComboBox.setSelectedItem(OpenAIChatCompletionModel.findByCode(model)); } - private JPanel createAzureServiceSectionPanel() { - var gridPanel = UI.PanelFactory.grid() - .add(UI.PanelFactory.panel(azureResourceNameField) - .withLabel(CodeGPTBundle.get("settingsConfigurable.section.service.azure.resourceNameField.label")) - .resizeX(false) - .withComment(CodeGPTBundle.get("settingsConfigurable.section.service.azure.resourceNameField.comment"))) - .add(UI.PanelFactory.panel(azureDeploymentIdField) - .withLabel(CodeGPTBundle.get("settingsConfigurable.section.service.azure.deploymentIdField.label")) - .resizeX(false) - .withComment(CodeGPTBundle.get("settingsConfigurable.section.service.azure.deploymentIdField.comment"))) - .add(UI.PanelFactory.panel(azureApiVersionField) - .withLabel(CodeGPTBundle.get("settingsConfigurable.section.service.azure.apiVersionField.label")) - .resizeX(false) - .withComment(CodeGPTBundle.get("settingsConfigurable.section.service.azure.apiVersionField.comment"))) - .add(UI.PanelFactory.panel(azureBaseHostField) - .withLabel("Base host:") - .resizeX(false)) - .createPanel(); - gridPanel.setBorder(JBUI.Borders.emptyLeft(16)); + public String getAzureModel() { + return ((OpenAIChatCompletionModel) (azureCompletionModelComboBox.getModel().getSelectedItem())).getCode(); + } - azureApiKeyFieldPanel.setBorder(JBUI.Borders.emptyLeft(16)); - azureActiveDirectoryTokenFieldPanel.setBorder(JBUI.Borders.emptyLeft(16)); + public void setDisplayWebSearchResults(boolean displayWebSearchResults) { + displayWebSearchResultsCheckBox.setSelected(displayWebSearchResults); + } - var authenticationPanel = FormBuilder.createFormBuilder() - .addComponent(UI.PanelFactory.panel(useAzureApiKeyAuthenticationRadioButton).resizeX(false).createPanel()) - .addComponent(azureApiKeyFieldPanel) - .addComponent(UI.PanelFactory.panel(useAzureActiveDirectoryAuthenticationRadioButton).resizeX(false).createPanel()) - .addComponent(azureActiveDirectoryTokenFieldPanel) - .getPanel(); - authenticationPanel.setBorder(JBUI.Borders.emptyLeft(16)); + public boolean isDisplayWebSearchResults() { + return displayWebSearchResultsCheckBox.isSelected(); + } - var form = FormBuilder.createFormBuilder() - .addComponent(new TitledSeparator("Authentication")) - .addComponent(authenticationPanel) - .addComponent(new TitledSeparator("Request Configuration")) - .addComponent(gridPanel) - .getPanel(); - form.setBorder(JBUI.Borders.emptyLeft(16)); - return form; + public void setOpenAIPath(String path) { + openAIPathField.setText(path); } - private void registerPanelsVisibility(SettingsState settings, AzureSettingsState azureSettings) { - openAIServiceSectionPanel.setVisible(settings.isUseOpenAIService()); - azureServiceSectionPanel.setVisible(settings.isUseAzureService()); - azureApiKeyFieldPanel.setVisible(azureSettings.isUseAzureApiKeyAuthentication()); - azureActiveDirectoryTokenFieldPanel.setVisible(azureSettings.isUseAzureActiveDirectoryAuthentication()); + public String getOpenAIPath() { + return openAIPathField.getText(); } - private void registerRadioButtons() { - registerRadioButtons( - Map.entry(useOpenAIServiceRadioButton, openAIServiceSectionPanel), - Map.entry(useAzureServiceRadioButton, azureServiceSectionPanel)); - registerRadioButtons( - Map.entry(useAzureApiKeyAuthenticationRadioButton, azureApiKeyFieldPanel), - Map.entry(useAzureActiveDirectoryAuthenticationRadioButton, azureActiveDirectoryTokenFieldPanel)); + public void setAzurePath(String path) { + azurePathField.setText(path); } - private void registerRadioButtons(Map.Entry firstEntry, Map.Entry secondEntry) { - var buttonGroup = new ButtonGroup(); - buttonGroup.add(firstEntry.getKey()); - buttonGroup.add(secondEntry.getKey()); - - firstEntry.getKey().addActionListener(e -> { - firstEntry.getValue().setVisible(true); - secondEntry.getValue().setVisible(false); - }); - secondEntry.getKey().addActionListener(e -> { - firstEntry.getValue().setVisible(false); - secondEntry.getValue().setVisible(true); - }); + public String getAzurePath() { + return azurePathField.getText(); } } diff --git a/src/main/java/ee/carlrobert/codegpt/settings/SettingsComponent.java b/src/main/java/ee/carlrobert/codegpt/settings/SettingsComponent.java index 5c3c6fba0..1c8bbccfd 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/SettingsComponent.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/SettingsComponent.java @@ -15,19 +15,13 @@ public class SettingsComponent { private final JPanel mainPanel; private final JBTextField displayNameField; private final ServiceSelectionForm serviceSelectionForm; - private final ModelSelectionForm modelSelectionForm; private final UserDetailsSettingsPanel userDetailsSettingsPanel; public SettingsComponent(Disposable parentDisposable, SettingsState settings) { - modelSelectionForm = new ModelSelectionForm(); serviceSelectionForm = new ServiceSelectionForm(settings); - displayNameField = new JBTextField(settings.getDisplayName(), 20); - - userDetailsSettingsPanel = new UserDetailsSettingsPanel(parentDisposable, settings); - + userDetailsSettingsPanel = new UserDetailsSettingsPanel(parentDisposable); mainPanel = FormBuilder.createFormBuilder() - // .addComponent(userDetailsSettingsPanel) .addComponent(UI.PanelFactory.panel(displayNameField) .withLabel(CodeGPTBundle.get("settingsConfigurable.section.integration.displayNameFieldLabel")) .resizeX(false) @@ -35,8 +29,6 @@ public SettingsComponent(Disposable parentDisposable, SettingsState settings) { .addComponent(new TitledSeparator(CodeGPTBundle.get("settingsConfigurable.section.service.title"))) .addComponent(serviceSelectionForm.getForm()) .addVerticalGap(8) - .addComponent(new TitledSeparator(CodeGPTBundle.get("settingsConfigurable.section.model.title"))) - .addComponent(modelSelectionForm.getForm()) .addComponentFillVertically(new JPanel(), 0) .getPanel(); } @@ -65,10 +57,6 @@ public ServiceSelectionForm getServiceSelectionForm() { return serviceSelectionForm; } - public ModelSelectionForm getModelSelectionForm() { - return modelSelectionForm; - } - public String getDisplayName() { return displayNameField.getText(); } diff --git a/src/main/java/ee/carlrobert/codegpt/settings/SettingsConfigurable.java b/src/main/java/ee/carlrobert/codegpt/settings/SettingsConfigurable.java index dc33d2134..243528332 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/SettingsConfigurable.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/SettingsConfigurable.java @@ -3,17 +3,13 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.options.Configurable; import ee.carlrobert.codegpt.CodeGPTBundle; -import ee.carlrobert.codegpt.EncodingManager; import ee.carlrobert.codegpt.conversations.ConversationsState; import ee.carlrobert.codegpt.credentials.AzureCredentialsManager; import ee.carlrobert.codegpt.credentials.OpenAICredentialsManager; -import ee.carlrobert.codegpt.credentials.UserCredentialsManager; import ee.carlrobert.codegpt.settings.state.AzureSettingsState; -import ee.carlrobert.codegpt.settings.state.ModelSettingsState; import ee.carlrobert.codegpt.settings.state.OpenAISettingsState; import ee.carlrobert.codegpt.settings.state.SettingsState; import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowContentManager; -import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowTabPanel; import ee.carlrobert.codegpt.util.ApplicationUtils; import javax.swing.JComponent; import org.jetbrains.annotations.Nls; @@ -47,90 +43,41 @@ public boolean isModified() { var settings = SettingsState.getInstance(); var openAISettings = OpenAISettingsState.getInstance(); var azureSettings = AzureSettingsState.getInstance(); - var modelSettings = ModelSettingsState.getInstance(); var serviceSelectionForm = settingsComponent.getServiceSelectionForm(); - return !settingsComponent.getEmail().equals(settings.getEmail()) || - !settingsComponent.getDisplayName().equals(settings.getDisplayName()) || - - serviceSelectionForm.isOpenAIServiceSelected() != settings.isUseOpenAIService() || - serviceSelectionForm.isAzureServiceSelected() != settings.isUseAzureService() || - !serviceSelectionForm.getOpenAIApiKey().equals(OpenAICredentialsManager.getInstance().getApiKey()) || - !serviceSelectionForm.getOpenAIOrganization().equals(openAISettings.getOrganization()) || - !serviceSelectionForm.getOpenAIBaseHost().equals(openAISettings.getBaseHost()) || - - serviceSelectionForm.isAzureActiveDirectoryAuthenticationSelected() != azureSettings.isUseAzureActiveDirectoryAuthentication() || - serviceSelectionForm.isAzureApiKeyAuthenticationSelected() != azureSettings.isUseAzureApiKeyAuthentication() || - !serviceSelectionForm.getAzureActiveDirectoryToken().equals(AzureCredentialsManager.getInstance().getAzureActiveDirectoryToken()) || - !serviceSelectionForm.getAzureOpenAIApiKey().equals(AzureCredentialsManager.getInstance().getAzureOpenAIApiKey()) || - !serviceSelectionForm.getAzureResourceName().equals(azureSettings.getResourceName()) || - !serviceSelectionForm.getAzureDeploymentId().equals(azureSettings.getDeploymentId()) || - !serviceSelectionForm.getAzureApiVersion().equals(azureSettings.getApiVersion()) || - !serviceSelectionForm.getAzureBaseHost().equals(azureSettings.getBaseHost()) || - - isModelChanged(modelSettings) || - isCompletionOptionChanged(modelSettings); + return !settingsComponent.getDisplayName().equals(settings.getDisplayName()) || + isServiceChanged(serviceSelectionForm, settings) || + openAISettings.isModified(serviceSelectionForm) || + azureSettings.isModified(serviceSelectionForm) || + serviceSelectionForm.isDisplayWebSearchResults() != settings.isDisplayWebSearchResults(); } @Override public void apply() { + var serviceSelectionForm = settingsComponent.getServiceSelectionForm(); var settings = SettingsState.getInstance(); var openAISettings = OpenAISettingsState.getInstance(); var azureSettings = AzureSettingsState.getInstance(); - var modelSettings = ModelSettingsState.getInstance(); - var isModelChanged = isModelChanged(modelSettings); - - if (isModelChanged) { - EncodingManager.getInstance().setEncoding(modelSettings.isUseChatCompletion() ? - modelSettings.getChatCompletionModel() : - modelSettings.getTextCompletionModel()); - } + var serviceChanged = isServiceChanged(serviceSelectionForm, settings); + var modelChanged = openAISettings.getModel().equals(serviceSelectionForm.getOpenAIModel()) || + azureSettings.getModel().equals(serviceSelectionForm.getAzureModel()); - if (isCompletionOptionChanged(modelSettings) || isModelChanged) { - ConversationsState.getInstance().setCurrentConversation(null); - var project = ApplicationUtils.findCurrentProject(); - if (project == null) { - throw new RuntimeException("Could not find current project."); - } - - StandardChatToolWindowContentManager.getInstance(project) - .tryFindChatTabbedPane() - .ifPresent(tabbedPane -> { - tabbedPane.clearAll(); - var tabPanel = new StandardChatToolWindowTabPanel(project); - tabPanel.displayLandingView(); - tabbedPane.addNewTab(tabPanel); - }); - } - - var serviceSelectionForm = settingsComponent.getServiceSelectionForm(); - var modelSelectionForm = settingsComponent.getModelSelectionForm(); - - UserCredentialsManager.getInstance().setAccountPassword(settingsComponent.getPassword()); OpenAICredentialsManager.getInstance().setApiKey(serviceSelectionForm.getOpenAIApiKey()); AzureCredentialsManager.getInstance().setApiKey(serviceSelectionForm.getAzureOpenAIApiKey()); AzureCredentialsManager.getInstance().setAzureActiveDirectoryToken(serviceSelectionForm.getAzureActiveDirectoryToken()); - settings.setEmail(settingsComponent.getEmail()); settings.setDisplayName(settingsComponent.getDisplayName()); - settings.setUseOpenAIService(serviceSelectionForm.isOpenAIServiceSelected()); settings.setUseAzureService(serviceSelectionForm.isAzureServiceSelected()); + settings.setUseYouService(serviceSelectionForm.isYouServiceSelected()); + settings.setDisplayWebSearchResults(serviceSelectionForm.isDisplayWebSearchResults()); - openAISettings.setOrganization(serviceSelectionForm.getOpenAIOrganization()); - openAISettings.setBaseHost(serviceSelectionForm.getOpenAIBaseHost()); + openAISettings.apply(serviceSelectionForm); + azureSettings.apply(serviceSelectionForm); - azureSettings.setUseAzureActiveDirectoryAuthentication(serviceSelectionForm.isAzureActiveDirectoryAuthenticationSelected()); - azureSettings.setUseAzureApiKeyAuthentication(serviceSelectionForm.isAzureApiKeyAuthenticationSelected()); - azureSettings.setResourceName(serviceSelectionForm.getAzureResourceName()); - azureSettings.setDeploymentId(serviceSelectionForm.getAzureDeploymentId()); - azureSettings.setApiVersion(serviceSelectionForm.getAzureApiVersion()); - azureSettings.setBaseHost(serviceSelectionForm.getAzureBaseHost()); - - modelSettings.setUseChatCompletion(modelSelectionForm.isChatCompletionOptionSelected()); - modelSettings.setUseTextCompletion(modelSelectionForm.isTextCompletionOptionSelected()); - modelSettings.setChatCompletionModel(modelSelectionForm.getChatCompletionBaseModel().getCode()); - modelSettings.setTextCompletionModel(modelSelectionForm.getTextCompletionBaseModel().getCode()); + if (serviceChanged || modelChanged) { + resetActiveTab(); + } } @Override @@ -138,33 +85,19 @@ public void reset() { var settings = SettingsState.getInstance(); var openAISettings = OpenAISettingsState.getInstance(); var azureSettings = AzureSettingsState.getInstance(); - var modelSettings = ModelSettingsState.getInstance(); var serviceSelectionForm = settingsComponent.getServiceSelectionForm(); - var modelSelectionForm = settingsComponent.getModelSelectionForm(); settingsComponent.setEmail(settings.getEmail()); settingsComponent.setDisplayName(settings.getDisplayName()); serviceSelectionForm.setOpenAIServiceSelected(settings.isUseOpenAIService()); serviceSelectionForm.setAzureServiceSelected(settings.isUseAzureService()); + serviceSelectionForm.setYouServiceSelected(settings.isUseYouService()); + + openAISettings.reset(serviceSelectionForm); + azureSettings.reset(serviceSelectionForm); - serviceSelectionForm.setOpenAIApiKey(OpenAICredentialsManager.getInstance().getApiKey()); - serviceSelectionForm.setOpenAIOrganization(openAISettings.getOrganization()); - serviceSelectionForm.setOpenAIBaseHost(openAISettings.getBaseHost()); - - serviceSelectionForm.setAzureApiKeyAuthenticationSelected(azureSettings.isUseAzureApiKeyAuthentication()); - serviceSelectionForm.setAzureApiKey(AzureCredentialsManager.getInstance().getAzureOpenAIApiKey()); - serviceSelectionForm.setAzureActiveDirectoryAuthenticationSelected(azureSettings.isUseAzureActiveDirectoryAuthentication()); - serviceSelectionForm.setAzureActiveDirectoryToken(AzureCredentialsManager.getInstance().getAzureActiveDirectoryToken()); - serviceSelectionForm.setAzureResourceName(azureSettings.getResourceName()); - serviceSelectionForm.setAzureDeploymentId(azureSettings.getDeploymentId()); - serviceSelectionForm.setAzureApiVersion(azureSettings.getApiVersion()); - serviceSelectionForm.setAzureBaseHost(azureSettings.getBaseHost()); - - modelSelectionForm.setUseChatCompletionSelected(modelSettings.isUseChatCompletion()); - modelSelectionForm.setUseTextCompletionSelected(modelSettings.isUseTextCompletion()); - modelSelectionForm.setChatCompletionBaseModel(modelSettings.getChatCompletionModel()); - modelSelectionForm.setTextCompletionBaseModel(modelSettings.getTextCompletionModel()); + serviceSelectionForm.setDisplayWebSearchResults(settings.isDisplayWebSearchResults()); } @Override @@ -172,19 +105,23 @@ public void disposeUIResources() { settingsComponent = null; } - private boolean isCompletionOptionChanged(ModelSettingsState settings) { - var modelSelectionForm = settingsComponent.getModelSelectionForm(); - return modelSelectionForm.isChatCompletionOptionSelected() != settings.isUseChatCompletion() || - modelSelectionForm.isTextCompletionOptionSelected() != settings.isUseTextCompletion(); + @Override + public void dispose() { } - private boolean isModelChanged(ModelSettingsState settings) { - var modelSelectionForm = settingsComponent.getModelSelectionForm(); - return !modelSelectionForm.getChatCompletionBaseModel().getCode().equals(settings.getChatCompletionModel()) || - !modelSelectionForm.getTextCompletionBaseModel().getCode().equals(settings.getTextCompletionModel()); + private boolean isServiceChanged(ServiceSelectionForm serviceSelectionForm, SettingsState settings) { + return serviceSelectionForm.isOpenAIServiceSelected() != settings.isUseOpenAIService() || + serviceSelectionForm.isAzureServiceSelected() != settings.isUseAzureService() || + serviceSelectionForm.isYouServiceSelected() != settings.isUseYouService(); } - @Override - public void dispose() { + private void resetActiveTab() { + ConversationsState.getInstance().setCurrentConversation(null); + var project = ApplicationUtils.findCurrentProject(); + if (project == null) { + throw new RuntimeException("Could not find current project."); + } + + StandardChatToolWindowContentManager.getInstance(project).resetActiveTab(); } } diff --git a/src/main/java/ee/carlrobert/codegpt/settings/UserDetailsSettingsPanel.java b/src/main/java/ee/carlrobert/codegpt/settings/UserDetailsSettingsPanel.java index f7da5c9b0..550c51e0f 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/UserDetailsSettingsPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/UserDetailsSettingsPanel.java @@ -1,5 +1,6 @@ package ee.carlrobert.codegpt.settings; +import com.intellij.notification.NotificationType; import com.intellij.openapi.Disposable; import com.intellij.openapi.ui.ComponentValidator; import com.intellij.openapi.ui.ValidationInfo; @@ -17,17 +18,23 @@ import ee.carlrobert.codegpt.credentials.UserCredentialsManager; import ee.carlrobert.codegpt.settings.state.SettingsState; import ee.carlrobert.codegpt.user.UserManager; +import ee.carlrobert.codegpt.user.auth.AuthenticationError; import ee.carlrobert.codegpt.user.auth.AuthenticationHandler; import ee.carlrobert.codegpt.user.auth.AuthenticationService; +import ee.carlrobert.codegpt.user.auth.response.AuthenticationResponse; +import ee.carlrobert.codegpt.user.auth.response.User; +import ee.carlrobert.codegpt.util.OverlayUtils; import ee.carlrobert.codegpt.util.SwingUtils; import java.awt.BorderLayout; import java.awt.FlowLayout; +import java.util.regex.Pattern; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JPasswordField; import javax.swing.JTextPane; import javax.swing.SwingUtilities; +import org.jetbrains.annotations.Nullable; public class UserDetailsSettingsPanel extends JPanel { @@ -37,8 +44,9 @@ public class UserDetailsSettingsPanel extends JPanel { private final JTextPane signUpTextPane; private final AsyncProcessIcon loadingSpinner; - public UserDetailsSettingsPanel(Disposable parentDisposable, SettingsState settings) { + public UserDetailsSettingsPanel(Disposable parentDisposable) { super(new BorderLayout()); + var settings = SettingsState.getInstance(); emailField = new JBTextField(settings.getEmail(), 25); passwordField = new JBPasswordField(); passwordField.setColumns(25); @@ -65,10 +73,10 @@ public UserDetailsSettingsPanel(Disposable parentDisposable, SettingsState setti } }); - if (UserManager.getInstance().getSession() == null) { - add(createUserAuthenticationPanel(emailField, passwordField, false)); + if (UserManager.getInstance().getAuthenticationResponse() == null) { + add(createUserAuthenticationPanel(emailField, passwordField, null)); } else { - add(createUserInformationPanel()); + add(createUserInformationPanel(UserManager.getInstance().getAuthenticationResponse().getData().getUser())); } } @@ -90,6 +98,9 @@ private ComponentValidator createInputValidator(Disposable parentDisposable, JCo String value; if (component instanceof JBTextField) { value = ((JBTextField) component).getText(); + if (!isValidEmail(value)) { + return new ValidationInfo("The email you entered is invalid.", component).withOKEnabled(); + } } else { value = new String(((JPasswordField) component).getPassword()); } @@ -106,9 +117,15 @@ private ComponentValidator createInputValidator(Disposable parentDisposable, JCo return validator; } + private boolean isValidEmail(String email) { + // RFC 5322 + return Pattern.compile("^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$") + .matcher(email) + .matches(); + } + private JTextPane createSignUpTextPane() { - var textPane = createTextPane( - "Don't have an account? Sign up"); + var textPane = createTextPane("Don't have an account? Sign up"); textPane.setBorder(JBUI.Borders.emptyLeft(4)); return textPane; } @@ -132,17 +149,16 @@ private JPanel createFooterPanel() { return panel; } - private JPanel createUserAuthenticationPanel(JBTextField emailAddressField, JBPasswordField passwordField, boolean withInvalidCredentials) { + private JPanel createUserAuthenticationPanel(JBTextField emailAddressField, JBPasswordField passwordField, @Nullable AuthenticationError error) { var contentPanelBuilder = FormBuilder.createFormBuilder() - .addVerticalGap(8) .addLabeledComponent("Email address:", emailAddressField) .addLabeledComponent("Password:", passwordField) .addVerticalGap(4) .addComponentToRightColumn(createFooterPanel()) .addVerticalGap(4); - if (withInvalidCredentials) { - var invalidCredentialsLabel = new JBLabel("Invalid login credentials"); + if (error != null) { + var invalidCredentialsLabel = new JBLabel(error.getErrorMessage()); invalidCredentialsLabel.setForeground(JBColor.red); invalidCredentialsLabel.setBorder(JBUI.Borders.emptyLeft(4)); @@ -157,29 +173,15 @@ private JPanel createUserAuthenticationPanel(JBTextField emailAddressField, JBPa .getPanel(); } - private JPanel createUserInformationPanel() { + private JPanel createUserInformationPanel(User user) { var userManager = UserManager.getInstance(); var contentPanelBuilder = FormBuilder.createFormBuilder() - .addLabeledComponent("Email address:", - new JBLabel(userManager.getSession() - .getUser() - .getEmail()).withFont(JBFont.label().asBold())); - - if (userManager.isSubscribed()) { - contentPanelBuilder.addLabeledComponent("Subscription:", - new JBLabel(userManager.getSubscription() - .getPrices() - .getProducts() - .getName()).withFont(JBFont.label().asBold())); - } else { - contentPanelBuilder.addComponent(createTextPane( - "You haven't subscribed to any plan yet. Subscribe now.")); - } + .addLabeledComponent("Email address:", new JBLabel(user.getEmails().get(0).getEmail()).withFont(JBFont.label().asBold())); var signOutButton = new JButton("Sign Out"); signOutButton.addActionListener(e -> { userManager.clearSession(); - refreshView(createUserAuthenticationPanel(emailField, passwordField, false)); + refreshView(createUserAuthenticationPanel(emailField, passwordField, null)); }); return FormBuilder.createFormBuilder() @@ -196,18 +198,25 @@ private JPanel createUserInformationPanel() { class UserAuthenticationHandler implements AuthenticationHandler { @Override - public void handleAuthenticated() { - SwingUtilities.invokeLater(() -> refreshView(createUserInformationPanel())); + public void handleAuthenticated(AuthenticationResponse authenticationResponse) { + SwingUtilities.invokeLater(() -> { + var email = emailField.getText(); + var password = passwordField.getPassword(); + SettingsState.getInstance().setEmail(email); + UserCredentialsManager.getInstance().setAccountPassword(new String(password)); + refreshView(createUserInformationPanel(authenticationResponse.getData().getUser())); + }); } @Override - public void handleInvalidCredentials() { - SwingUtilities.invokeLater(() -> refreshView(createUserAuthenticationPanel(emailField, passwordField, true))); + public void handleGenericError() { + SwingUtilities.invokeLater(() -> refreshView( + createUserAuthenticationPanel(emailField, passwordField, new AuthenticationError("unknown", "Something went wrong.")))); } @Override - public void handleGenericError() { - // TODO + public void handleError(AuthenticationError error) { + SwingUtilities.invokeLater(() -> refreshView(createUserAuthenticationPanel(emailField, passwordField, error))); } } diff --git a/src/main/java/ee/carlrobert/codegpt/settings/advanced/AdvancedSettingsComponent.java b/src/main/java/ee/carlrobert/codegpt/settings/advanced/AdvancedSettingsComponent.java index 3499b1aa4..92151c053 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/advanced/AdvancedSettingsComponent.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/advanced/AdvancedSettingsComponent.java @@ -63,7 +63,6 @@ public AdvancedSettingsComponent(AdvancedSettingsState advancedSettings) { private JPanel createConnectionSettingsForm() { var panel = FormBuilder.createFormBuilder() - .addVerticalGap(4) .addLabeledComponent("Connection timeout (s):", connectionTimeoutField) .addLabeledComponent("Read timeout (s):", readTimeoutField) .getPanel(); diff --git a/src/main/java/ee/carlrobert/codegpt/settings/advanced/AdvancedSettingsState.java b/src/main/java/ee/carlrobert/codegpt/settings/advanced/AdvancedSettingsState.java index cdd5160fc..1d52ccd5a 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/advanced/AdvancedSettingsState.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/advanced/AdvancedSettingsState.java @@ -9,7 +9,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -@State(name = "CodeGPT_AdvancedSettings", storages = @Storage("CodeGPT_AdvancedSettings.xml")) +@State(name = "CodeGPT_AdvancedSettings_210", storages = @Storage("CodeGPT_AdvancedSettings_210.xml")) public class AdvancedSettingsState implements PersistentStateComponent { private String proxyHost = ""; diff --git a/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationComponent.java b/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationComponent.java index 800a3fc48..46e97e80a 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationComponent.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationComponent.java @@ -100,7 +100,6 @@ public void changedUpdate(DocumentEvent e) { .addComponent(openNewTabCheckBox) .addVerticalGap(4) .addComponent(new TitledSeparator(CodeGPTBundle.get("configurationConfigurable.section.assistant.title"))) - .addVerticalGap(4) .addComponent(createAssistantConfigurationForm()) .addComponentFillVertically(new JPanel(), 0) .getPanel(); diff --git a/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationState.java b/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationState.java index 04f8e28b4..f5bee3eae 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationState.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/configuration/ConfigurationState.java @@ -10,10 +10,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -@State( - name = "ee.carlrobert.codegpt.state.settings.configuration.ConfigurationState", - storages = @Storage("CodeGPTConfiguration.xml") -) +@State(name = "CodeGPT_ConfigurationSettings_210", storages = @Storage("CodeGPT_ConfigurationSettings_210.xml")) public class ConfigurationState implements PersistentStateComponent { private String systemPrompt = ""; diff --git a/src/main/java/ee/carlrobert/codegpt/settings/state/AzureSettingsState.java b/src/main/java/ee/carlrobert/codegpt/settings/state/AzureSettingsState.java index 01708d65d..44b16edba 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/state/AzureSettingsState.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/state/AzureSettingsState.java @@ -5,15 +5,22 @@ import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.util.xmlb.XmlSerializerUtil; +import ee.carlrobert.codegpt.credentials.AzureCredentialsManager; +import ee.carlrobert.codegpt.settings.ServiceSelectionForm; +import ee.carlrobert.llm.client.openai.completion.chat.OpenAIChatCompletionModel; import org.jetbrains.annotations.NotNull; -@State(name = "CodeGPT_AzureSettings", storages = @Storage("CodeGPT_AzureSettings.xml")) +@State(name = "CodeGPT_AzureSettings_210", storages = @Storage("CodeGPT_AzureSettings_210.xml")) public class AzureSettingsState implements PersistentStateComponent { + private final String BASE_PATH = "/openai/deployments/%s/chat/completions?api-version=%s"; + private String resourceName = ""; private String deploymentId = ""; private String apiVersion = ""; private String baseHost = "https://%s.openai.azure.com"; + private String path = BASE_PATH; + private String model = OpenAIChatCompletionModel.GPT_3_5.getCode(); private boolean useAzureApiKeyAuthentication = true; private boolean useAzureActiveDirectoryAuthentication; @@ -31,6 +38,48 @@ public void loadState(@NotNull AzureSettingsState state) { XmlSerializerUtil.copyBean(state, this); } + public boolean isModified(ServiceSelectionForm serviceSelectionForm) { + return serviceSelectionForm.isAzureActiveDirectoryAuthenticationSelected() != isUseAzureActiveDirectoryAuthentication() || + serviceSelectionForm.isAzureApiKeyAuthenticationSelected() != isUseAzureApiKeyAuthentication() || + !serviceSelectionForm.getAzureActiveDirectoryToken().equals(AzureCredentialsManager.getInstance().getAzureActiveDirectoryToken()) || + !serviceSelectionForm.getAzureOpenAIApiKey().equals(AzureCredentialsManager.getInstance().getAzureOpenAIApiKey()) || + !serviceSelectionForm.getAzureResourceName().equals(resourceName) || + !serviceSelectionForm.getAzureDeploymentId().equals(deploymentId) || + !serviceSelectionForm.getAzureApiVersion().equals(apiVersion) || + !serviceSelectionForm.getAzureBaseHost().equals(baseHost) || + !serviceSelectionForm.getAzurePath().equals(path) || + !serviceSelectionForm.getAzureModel().equals(model); + } + + public void apply(ServiceSelectionForm serviceSelectionForm) { + useAzureActiveDirectoryAuthentication = serviceSelectionForm.isAzureActiveDirectoryAuthenticationSelected(); + useAzureApiKeyAuthentication = serviceSelectionForm.isAzureApiKeyAuthenticationSelected(); + + resourceName = serviceSelectionForm.getAzureResourceName(); + deploymentId = serviceSelectionForm.getAzureDeploymentId(); + apiVersion = serviceSelectionForm.getAzureApiVersion(); + baseHost = serviceSelectionForm.getAzureBaseHost(); + path = serviceSelectionForm.getAzurePath(); + model = serviceSelectionForm.getAzureModel(); + } + + public void reset(ServiceSelectionForm serviceSelectionForm) { + serviceSelectionForm.setAzureApiKey(AzureCredentialsManager.getInstance().getAzureOpenAIApiKey()); + serviceSelectionForm.setAzureActiveDirectoryToken(AzureCredentialsManager.getInstance().getAzureActiveDirectoryToken()); + serviceSelectionForm.setAzureApiKeyAuthenticationSelected(useAzureApiKeyAuthentication); + serviceSelectionForm.setAzureActiveDirectoryAuthenticationSelected(useAzureActiveDirectoryAuthentication); + serviceSelectionForm.setAzureResourceName(resourceName); + serviceSelectionForm.setAzureDeploymentId(deploymentId); + serviceSelectionForm.setAzureApiVersion(apiVersion); + serviceSelectionForm.setAzureBaseHost(baseHost); + serviceSelectionForm.setAzurePath(path); + serviceSelectionForm.setAzureModel(serviceSelectionForm.getAzureModel()); + } + + public boolean isUsingCustomPath() { + return !BASE_PATH.equals(path); + } + public String getResourceName() { return resourceName; } @@ -63,6 +112,22 @@ public void setBaseHost(String baseHost) { this.baseHost = baseHost; } + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + public boolean isUseAzureApiKeyAuthentication() { return useAzureApiKeyAuthentication; } diff --git a/src/main/java/ee/carlrobert/codegpt/settings/state/ModelSettingsState.java b/src/main/java/ee/carlrobert/codegpt/settings/state/ModelSettingsState.java deleted file mode 100644 index a1eaec6f2..000000000 --- a/src/main/java/ee/carlrobert/codegpt/settings/state/ModelSettingsState.java +++ /dev/null @@ -1,82 +0,0 @@ -package ee.carlrobert.codegpt.settings.state; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.PersistentStateComponent; -import com.intellij.openapi.components.State; -import com.intellij.openapi.components.Storage; -import com.intellij.util.xmlb.XmlSerializerUtil; -import ee.carlrobert.codegpt.conversations.Conversation; -import ee.carlrobert.openai.client.ClientCode; -import ee.carlrobert.openai.client.completion.chat.ChatCompletionModel; -import ee.carlrobert.openai.client.completion.text.TextCompletionModel; -import org.jetbrains.annotations.NotNull; - -@State(name = "CodeGPT_ModelSettings", storages = @Storage("CodeGPT_ModelSettings.xml")) -public class ModelSettingsState implements PersistentStateComponent { - - private String textCompletionModel = TextCompletionModel.DAVINCI.getCode(); - private String chatCompletionModel = ChatCompletionModel.GPT_3_5.getCode(); - private boolean useChatCompletion = true; - private boolean useTextCompletion; - - public static ModelSettingsState getInstance() { - return ApplicationManager.getApplication().getService(ModelSettingsState.class); - } - - @Override - public ModelSettingsState getState() { - return this; - } - - @Override - public void loadState(@NotNull ModelSettingsState state) { - XmlSerializerUtil.copyBean(state, this); - } - - public void sync(Conversation conversation) { - var isChatCompletion = ClientCode.CHAT_COMPLETION.equals(conversation.getClientCode()); - if (isChatCompletion) { - chatCompletionModel = conversation.getModel(); - } else { - textCompletionModel = conversation.getModel(); - } - useChatCompletion = isChatCompletion; - useTextCompletion = !isChatCompletion; - } - - public String getCompletionModel() { - return useChatCompletion ? chatCompletionModel : textCompletionModel; - } - - public String getTextCompletionModel() { - return textCompletionModel; - } - - public void setTextCompletionModel(String textCompletionModel) { - this.textCompletionModel = textCompletionModel; - } - - public String getChatCompletionModel() { - return chatCompletionModel; - } - - public void setChatCompletionModel(String chatCompletionModel) { - this.chatCompletionModel = chatCompletionModel; - } - - public boolean isUseChatCompletion() { - return useChatCompletion; - } - - public void setUseChatCompletion(boolean useChatCompletion) { - this.useChatCompletion = useChatCompletion; - } - - public boolean isUseTextCompletion() { - return useTextCompletion; - } - - public void setUseTextCompletion(boolean useTextCompletion) { - this.useTextCompletion = useTextCompletion; - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/settings/state/OpenAISettingsState.java b/src/main/java/ee/carlrobert/codegpt/settings/state/OpenAISettingsState.java index e316981a7..1eaa927f5 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/state/OpenAISettingsState.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/state/OpenAISettingsState.java @@ -5,13 +5,20 @@ import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.util.xmlb.XmlSerializerUtil; +import ee.carlrobert.codegpt.credentials.OpenAICredentialsManager; +import ee.carlrobert.codegpt.settings.ServiceSelectionForm; +import ee.carlrobert.llm.client.openai.completion.chat.OpenAIChatCompletionModel; import org.jetbrains.annotations.NotNull; -@State(name = "CodeGPT_OpenAISettings", storages = @Storage("CodeGPT_OpenAISettings.xml")) +@State(name = "CodeGPT_OpenAISettings_210", storages = @Storage("CodeGPT_OpenAISettings_210.xml")) public class OpenAISettingsState implements PersistentStateComponent { + private final String BASE_PATH = "/v1/chat/completions"; + private String organization = ""; private String baseHost = "https://api.openai.com"; + private String path = BASE_PATH; + private String model = OpenAIChatCompletionModel.GPT_3_5.getCode(); public static OpenAISettingsState getInstance() { return ApplicationManager.getApplication().getService(OpenAISettingsState.class); @@ -27,6 +34,33 @@ public void loadState(@NotNull OpenAISettingsState state) { XmlSerializerUtil.copyBean(state, this); } + public boolean isModified(ServiceSelectionForm serviceSelectionForm) { + return !serviceSelectionForm.getOpenAIApiKey().equals(OpenAICredentialsManager.getInstance().getApiKey()) || + !serviceSelectionForm.getOpenAIOrganization().equals(organization) || + !serviceSelectionForm.getOpenAIBaseHost().equals(baseHost) || + !serviceSelectionForm.getOpenAIPath().equals(path) || + !serviceSelectionForm.getOpenAIModel().equals(model); + } + + public void apply(ServiceSelectionForm serviceSelectionForm) { + organization = serviceSelectionForm.getOpenAIOrganization(); + baseHost = serviceSelectionForm.getOpenAIBaseHost(); + path = serviceSelectionForm.getOpenAIPath(); + model = serviceSelectionForm.getOpenAIModel(); + } + + public void reset(ServiceSelectionForm serviceSelectionForm) { + serviceSelectionForm.setOpenAIApiKey(OpenAICredentialsManager.getInstance().getApiKey()); + serviceSelectionForm.setOpenAIOrganization(organization); + serviceSelectionForm.setOpenAIBaseHost(baseHost); + serviceSelectionForm.setOpenAIPath(path); + serviceSelectionForm.setOpenAIModel(model); + } + + public boolean isUsingCustomPath() { + return !BASE_PATH.equals(path); + } + public String getOrganization() { return organization; } @@ -42,4 +76,20 @@ public String getBaseHost() { public void setBaseHost(String openAIBaseHost) { this.baseHost = openAIBaseHost; } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } } diff --git a/src/main/java/ee/carlrobert/codegpt/settings/state/SettingsState.java b/src/main/java/ee/carlrobert/codegpt/settings/state/SettingsState.java index 773e04a76..2a99f4c11 100644 --- a/src/main/java/ee/carlrobert/codegpt/settings/state/SettingsState.java +++ b/src/main/java/ee/carlrobert/codegpt/settings/state/SettingsState.java @@ -5,12 +5,10 @@ import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.util.xmlb.XmlSerializerUtil; +import ee.carlrobert.codegpt.conversations.Conversation; import org.jetbrains.annotations.NotNull; -@State( - name = "ee.carlrobert.codegpt.state.settings.SettingsState", - storages = @Storage("CodeGPTSettings_210.xml") -) +@State(name = "CodeGPT_GeneralSettings_210", storages = @Storage("CodeGPT_GeneralSettings_210.xml")) public class SettingsState implements PersistentStateComponent { private String email = ""; @@ -18,6 +16,8 @@ public class SettingsState implements PersistentStateComponent { private boolean previouslySignedIn; private boolean useOpenAIService = true; private boolean useAzureService; + private boolean useYouService; + private boolean displayWebSearchResults = true; public SettingsState() { } @@ -36,6 +36,20 @@ public void loadState(@NotNull SettingsState state) { XmlSerializerUtil.copyBean(state, this); } + public void sync(Conversation conversation) { + var clientCode = conversation.getClientCode(); + if ("chat.completion".equals(clientCode)) { + OpenAISettingsState.getInstance().setModel(conversation.getModel()); + } + if ("azure.chat.completion".equals(clientCode)) { + AzureSettingsState.getInstance().setModel(conversation.getModel()); + } + + setUseOpenAIService("chat.completion".equals(clientCode)); + setUseAzureService("azure.chat.completion".equals(clientCode)); + setUseYouService("you.chat.completion".equals(clientCode)); + } + public String getEmail() { return email; } @@ -82,4 +96,20 @@ public boolean isUseAzureService() { public void setUseAzureService(boolean useAzureService) { this.useAzureService = useAzureService; } + + public boolean isUseYouService() { + return useYouService; + } + + public void setUseYouService(boolean useYouService) { + this.useYouService = useYouService; + } + + public boolean isDisplayWebSearchResults() { + return displayWebSearchResults; + } + + public void setDisplayWebSearchResults(boolean displayWebSearchResults) { + this.displayWebSearchResults = displayWebSearchResults; + } } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/ModelIconLabel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/ModelIconLabel.java new file mode 100644 index 000000000..f307be8ce --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/ModelIconLabel.java @@ -0,0 +1,36 @@ +package ee.carlrobert.codegpt.toolwindow; + +import com.intellij.ui.components.JBLabel; +import com.intellij.util.ui.JBFont; +import ee.carlrobert.codegpt.Icons; +import ee.carlrobert.llm.client.openai.completion.chat.OpenAIChatCompletionModel; +import java.util.NoSuchElementException; +import javax.swing.SwingConstants; + +public class ModelIconLabel extends JBLabel { + + public ModelIconLabel(String clientCode, String modelCode) { + if ("you.chat.completion".equals(clientCode)) { + setIcon(Icons.YouIcon); + return; + } + + if ("chat.completion".equals(clientCode)) { + setIcon(Icons.OpenAIIcon); + } + if ("azure.chat.completion".equals(clientCode)) { + setIcon(Icons.AzureIcon); + } + setText(formatModelName(modelCode)); + setFont(JBFont.small().asBold()); + setHorizontalAlignment(SwingConstants.LEADING); + } + + private String formatModelName(String modelCode) { + try { + return OpenAIChatCompletionModel.findByCode(modelCode).getDescription(); + } catch (NoSuchElementException e) { + return modelCode; + } + } +} diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/ProjectToolWindowFactory.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/ProjectToolWindowFactory.java index da67c80f4..b4363f5c4 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/ProjectToolWindowFactory.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/ProjectToolWindowFactory.java @@ -6,6 +6,7 @@ import com.intellij.openapi.wm.ToolWindowFactory; import com.intellij.ui.content.ContentManagerEvent; import com.intellij.ui.content.ContentManagerListener; +import ee.carlrobert.codegpt.toolwindow.chat.contextual.ContextualChatToolWindowPanel; import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowPanel; import ee.carlrobert.codegpt.toolwindow.conversations.ConversationsToolWindow; import javax.swing.JComponent; diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/BaseChatToolWindowTabPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/BaseChatToolWindowTabPanel.java index b1f3de349..5d9c1d620 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/BaseChatToolWindowTabPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/BaseChatToolWindowTabPanel.java @@ -3,6 +3,7 @@ import static com.intellij.openapi.ui.Messages.OK; import static ee.carlrobert.codegpt.util.ThemeUtils.getPanelBackgroundColor; import static java.lang.String.format; +import static java.util.stream.Collectors.toList; import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.project.Project; @@ -11,12 +12,21 @@ import com.intellij.ui.components.JBScrollPane; import com.intellij.util.ui.JBUI; import ee.carlrobert.codegpt.completions.CompletionRequestHandler; +import ee.carlrobert.codegpt.completions.SerpResult; import ee.carlrobert.codegpt.conversations.Conversation; import ee.carlrobert.codegpt.conversations.ConversationService; import ee.carlrobert.codegpt.conversations.message.Message; import ee.carlrobert.codegpt.credentials.AzureCredentialsManager; import ee.carlrobert.codegpt.credentials.OpenAICredentialsManager; +import ee.carlrobert.codegpt.settings.state.AzureSettingsState; +import ee.carlrobert.codegpt.settings.state.OpenAISettingsState; import ee.carlrobert.codegpt.settings.state.SettingsState; +import ee.carlrobert.codegpt.toolwindow.ModelIconLabel; +import ee.carlrobert.codegpt.toolwindow.chat.components.ChatMessageResponseBody; +import ee.carlrobert.codegpt.toolwindow.chat.components.ResponsePanel; +import ee.carlrobert.codegpt.toolwindow.chat.components.UserMessagePanel; +import ee.carlrobert.codegpt.toolwindow.chat.components.UserPromptTextArea; +import ee.carlrobert.codegpt.user.UserManager; import ee.carlrobert.codegpt.util.EditorUtils; import ee.carlrobert.codegpt.util.FileUtils; import ee.carlrobert.codegpt.util.OverlayUtils; @@ -25,6 +35,7 @@ import java.awt.GridBagLayout; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; import javax.swing.BoxLayout; @@ -41,9 +52,10 @@ public abstract class BaseChatToolWindowTabPanel implements ChatToolWindowTabPan private final JPanel rootPanel; private final ScrollablePanel scrollablePanel; private final Map visibleMessagePanels = new HashMap<>(); + private final Map> serpResultsMapping = new HashMap<>(); protected final Project project; - protected final UserTextArea userTextArea; + protected final UserPromptTextArea userPromptTextArea; protected final ConversationService conversationService; protected @Nullable Conversation conversation; @@ -55,12 +67,12 @@ public BaseChatToolWindowTabPanel(@NotNull Project project, boolean useContextua this.conversationService = ConversationService.getInstance(); this.rootPanel = new JPanel(new GridBagLayout()); this.scrollablePanel = new ScrollablePanel(); - this.userTextArea = new UserTextArea(this::handleSubmit); + this.userPromptTextArea = new UserPromptTextArea(this::handleSubmit); init(); } public void requestFocusForTextArea() { - userTextArea.focus(); + userPromptTextArea.focus(); } @Override @@ -113,6 +125,9 @@ public void dispose() { } private boolean isCredentialSet() { + if (SettingsState.getInstance().isUseYouService()) { + return UserManager.getInstance().isAuthenticated(); + } if (SettingsState.getInstance().isUseAzureService()) { return AzureCredentialsManager.getInstance().isCredentialSet(); } @@ -141,6 +156,20 @@ private void call(Conversation conversation, Message message, ResponsePanel resp responsePanel.enableActions(); conversationService.saveMessage(completeMessage, message, conversation, isRetry); stopStreaming(responseContainer); + + var serpResults = serpResultsMapping.get(message.getId()); + var containsResults = serpResults != null && !serpResults.isEmpty(); + if (SettingsState.getInstance().isDisplayWebSearchResults()) { + if (containsResults) { + responseContainer.displaySerpResults(serpResults); + } + } + + if (containsResults) { + message.setSerpResults(serpResults.stream() + .map(result -> new SerpResult(result.getUrl(), result.getName(), result.getSnippet(), result.getSnippetSource())) + .collect(toList())); + } }); requestHandler.addTokensExceededListener(() -> SwingUtilities.invokeLater(() -> { var answer = OverlayUtils.showTokenLimitExceededDialog(); @@ -156,8 +185,11 @@ private void call(Conversation conversation, Message message, ResponsePanel resp responseContainer.displayError(error.getMessage()); stopStreaming(responseContainer); }); - userTextArea.setRequestHandler(requestHandler); - userTextArea.setSubmitEnabled(false); + requestHandler.addSerpResultsListener(serpResults -> serpResultsMapping.put(message.getId(), serpResults.stream() + .map(result -> new SerpResult(result.getUrl(), result.getName(), result.getSnippet(), result.getSnippetSource())) + .collect(toList()))); + userPromptTextArea.setRequestHandler(requestHandler); + userPromptTextArea.setSubmitEnabled(false); requestHandler.call(conversation, message, isRetry); } @@ -215,7 +247,7 @@ protected void clearWindow() { private void stopStreaming(ChatMessageResponseBody responseContainer) { SwingUtilities.invokeLater(() -> { - userTextArea.setSubmitEnabled(true); + userPromptTextArea.setSubmitEnabled(true); responseContainer.hideCarets(); }); } @@ -261,15 +293,52 @@ private void init() { gbc.fill = GridBagConstraints.HORIZONTAL; gbc.gridy = 1; - // JBUI.Panels.simplePanel(8, 0).add(); - JPanel chatTextAreaWrapper = new JPanel(new BorderLayout()); - chatTextAreaWrapper.setBorder(JBUI.Borders.compound( + + var model = getModel(); + var modelIconWrapper = JBUI.Panels.simplePanel( + new ModelIconLabel(getClientCode(), model)).withBorder(JBUI.Borders.empty(0, 0, 8, 4)); + modelIconWrapper.setBackground(getPanelBackgroundColor()); + + var wrapper = new JPanel(new BorderLayout()); + wrapper.setBorder(JBUI.Borders.compound( JBUI.Borders.customLine(JBColor.border(), 1, 0, 0, 0), JBUI.Borders.empty(8))); - chatTextAreaWrapper.setBackground(getPanelBackgroundColor()); - chatTextAreaWrapper.add(userTextArea, BorderLayout.SOUTH); - rootPanel.add(chatTextAreaWrapper, gbc); - userTextArea.requestFocusInWindow(); - userTextArea.requestFocus(); + wrapper.setBackground(getPanelBackgroundColor()); + wrapper.add(userPromptTextArea, BorderLayout.SOUTH); + if (model != null) { + wrapper.add(modelIconWrapper, BorderLayout.LINE_END); + } + rootPanel.add(wrapper, gbc); + userPromptTextArea.requestFocusInWindow(); + userPromptTextArea.requestFocus(); + } + + private String getClientCode() { + var settings = SettingsState.getInstance(); + if (settings.isUseOpenAIService()) { + return "chat.completion"; + } + if (settings.isUseAzureService()) { + return "azure.chat.completion"; + } + if (settings.isUseYouService()) { + return "you.chat.completion"; + } + return null; + } + + private @Nullable String getModel() { + var settings = SettingsState.getInstance(); + if (settings.isUseOpenAIService()) { + return OpenAISettingsState.getInstance().getModel(); + } + if (settings.isUseAzureService()) { + return AzureSettingsState.getInstance().getModel(); + } + if (settings.isUseYouService()) { + return "YouCode"; + } + + return null; } } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanelEditor.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanelEditor.java index 626827e04..0f9224444 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanelEditor.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanelEditor.java @@ -2,7 +2,6 @@ import static ee.carlrobert.codegpt.util.FileUtils.findFileNameExtensionMapping; -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.icons.AllIcons; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.ActionGroup; @@ -11,7 +10,6 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.EditorKind; @@ -21,7 +19,6 @@ import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; -import com.intellij.psi.PsiDocumentManager; import com.intellij.testFramework.LightVirtualFile; import com.intellij.ui.JBColor; import com.intellij.ui.components.JBLabel; @@ -46,12 +43,10 @@ public class ChatToolWindowTabPanelEditor implements Disposable { - private final Project project; private final Editor editor; private final Map.Entry fileNameExtensionMapping; public ChatToolWindowTabPanelEditor(Project project, String code, String language, Disposable disposableParent) { - this.project = project; this.fileNameExtensionMapping = findFileNameExtensionMapping(language); var fileName = "temp_" + DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now()) + fileNameExtensionMapping.getValue(); @@ -60,7 +55,7 @@ public ChatToolWindowTabPanelEditor(Project project, String code, String languag if (document == null) { document = EditorFactory.getInstance().createDocument(code); } - disableHighlighting(document); + EditorUtils.disableHighlighting(project, document); editor = EditorFactory.getInstance().createEditor(document, project, lightVirtualFile, true, EditorKind.UNTYPED); String originalGroupId = ((EditorEx) editor).getContextMenuGroupId(); AnAction originalGroup = originalGroupId == null ? null : ActionManager.getInstance().getAction(originalGroupId); @@ -95,13 +90,6 @@ public Editor getEditor() { return editor; } - private void disableHighlighting(Document document) { - var psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); - if (psiFile != null) { - DaemonCodeAnalyzer.getInstance(project).setHighlightingEnabled(psiFile, false); - } - } - private JPanel createHeaderComponent(String language) { var headerComponent = new JPanel(new BorderLayout()); headerComponent.setBorder(JBUI.Borders.compound( diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatMessageResponseBody.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/ChatMessageResponseBody.java similarity index 85% rename from src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatMessageResponseBody.java rename to src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/ChatMessageResponseBody.java index a70932811..cf52268d7 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatMessageResponseBody.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/ChatMessageResponseBody.java @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.toolwindow.chat; +package ee.carlrobert.codegpt.toolwindow.chat.components; import static ee.carlrobert.codegpt.util.ThemeUtils.getPanelBackgroundColor; import static java.lang.String.format; @@ -19,12 +19,20 @@ import com.vladsch.flexmark.html.HtmlRenderer; import com.vladsch.flexmark.parser.Parser; import com.vladsch.flexmark.util.data.MutableDataSet; +import ee.carlrobert.codegpt.completions.SerpResult; import ee.carlrobert.codegpt.settings.SettingsConfigurable; +import ee.carlrobert.codegpt.settings.state.SettingsState; +import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowTabPanelEditor; +import ee.carlrobert.codegpt.toolwindow.chat.ResponseNodeRenderer; +import ee.carlrobert.codegpt.toolwindow.chat.StreamParser; +import ee.carlrobert.codegpt.toolwindow.chat.StreamResponseType; import ee.carlrobert.codegpt.util.MarkdownUtils; import ee.carlrobert.codegpt.util.SwingUtils; import java.awt.BorderLayout; import java.awt.Color; +import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import javax.swing.BoxLayout; import javax.swing.JPanel; import javax.swing.JTextPane; @@ -84,8 +92,10 @@ public void update(String partialMessage) { } public void displayMissingCredential() { - currentlyProcessedTextPane.setText( - "

API key not provided. Open Settings to set one.

"); + var message = SettingsState.getInstance().isUseYouService() ? + "Please log in to access the chat feature." : + "API key not provided. Open Settings to set one."; + currentlyProcessedTextPane.setText(String.format("

%s

", message)); currentlyProcessedTextPane.addHyperlinkListener(e -> { if (e.getEventType() == ACTIVATED) { ShowSettingsUtil.getInstance().showSettingsDialog(project, SettingsConfigurable.class); @@ -104,7 +114,6 @@ public void hideCarets() { } } - public void displayError(String message) { var errorText = format("

%s

", message); if (responseReceived) { @@ -120,6 +129,24 @@ public void displayDefaultError() { displayError("Something went wrong."); } + public void displaySerpResults(List serpResults) { + var titles = serpResults.stream() + .map(result -> format("
  • %s
  • ", result.getUrl(), result.getName())) + .collect(Collectors.joining()); + var html = format( + "" + + "

    Search results:

    " + + "
      %s
    " + + "", titles); + if (responseReceived) { + var textPane = createTextPane(); + textPane.setText(html); + add(new ResponseWrapper().add(textPane)); + } else { + currentlyProcessedTextPane.setText(html); + } + } + public void clear() { removeAll(); diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ResponsePanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/ResponsePanel.java similarity index 83% rename from src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ResponsePanel.java rename to src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/ResponsePanel.java index 37a5ff15b..abdd6db27 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ResponsePanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/ResponsePanel.java @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.toolwindow.chat; +package ee.carlrobert.codegpt.toolwindow.chat.components; import static ee.carlrobert.codegpt.util.ThemeUtils.getPanelBackgroundColor; @@ -13,6 +13,7 @@ import ee.carlrobert.codegpt.toolwindow.IconActionButton; import java.awt.BorderLayout; import java.awt.FlowLayout; +import javax.swing.Box; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.SwingConstants; @@ -29,7 +30,7 @@ public ResponsePanel() { header = new Header(); body = new Body(); add(header, BorderLayout.NORTH); - add(body, BorderLayout.SOUTH); + add(body, BorderLayout.CENTER); } public void enableActions() { @@ -69,7 +70,7 @@ static class Header extends JPanel { setBorder(JBUI.Borders.empty(12, 8, 4, 8)); add(getIconLabel(), BorderLayout.LINE_START); - iconsWrapper = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0)); + iconsWrapper = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0)); iconsWrapper.setBackground(getBackground()); add(iconsWrapper, BorderLayout.LINE_END); } @@ -81,7 +82,7 @@ public void enableActions(boolean enabled) { } public void addReloadAction(Runnable onReload) { - iconsWrapper.add(new IconActionButton("Reload response", Actions.Refresh, new AnAction() { + addIconActionButton(new IconActionButton("Reload response", Actions.Refresh, new AnAction() { @Override public void actionPerformed(@NotNull AnActionEvent e) { enableActions(false); @@ -91,7 +92,7 @@ public void actionPerformed(@NotNull AnActionEvent e) { } public void addDeleteAction(Runnable onDelete) { - iconsWrapper.add(new IconActionButton("Delete response", Actions.GC, new AnAction() { + addIconActionButton(new IconActionButton("Delete response", Actions.GC, new AnAction() { @Override public void actionPerformed(@NotNull AnActionEvent e) { onDelete.run(); @@ -99,6 +100,13 @@ public void actionPerformed(@NotNull AnActionEvent e) { })); } + private void addIconActionButton(IconActionButton iconActionButton) { + if (iconsWrapper.getComponents() != null && iconsWrapper.getComponents().length > 0) { + iconsWrapper.add(Box.createHorizontalStrut(8)); + } + iconsWrapper.add(iconActionButton); + } + private JBLabel getIconLabel() { return new JBLabel("CodeGPT", Icons.DefaultIcon, SwingConstants.LEADING) .setAllowAutoWrapping(true) diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/UserMessagePanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/UserMessagePanel.java similarity index 96% rename from src/main/java/ee/carlrobert/codegpt/toolwindow/chat/UserMessagePanel.java rename to src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/UserMessagePanel.java index abb15ec07..17045496b 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/UserMessagePanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/UserMessagePanel.java @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.toolwindow.chat; +package ee.carlrobert.codegpt.toolwindow.chat.components; import com.intellij.openapi.Disposable; import com.intellij.openapi.project.Project; diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/UserTextArea.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/UserPromptTextArea.java similarity index 93% rename from src/main/java/ee/carlrobert/codegpt/toolwindow/chat/UserTextArea.java rename to src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/UserPromptTextArea.java index d7d70d932..ef3d89415 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/UserTextArea.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/components/UserPromptTextArea.java @@ -1,4 +1,4 @@ -package ee.carlrobert.codegpt.toolwindow.chat; +package ee.carlrobert.codegpt.toolwindow.chat.components; import com.intellij.icons.AllIcons; import com.intellij.openapi.editor.ex.util.EditorUtil; @@ -33,7 +33,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class UserTextArea extends JPanel { +public class UserPromptTextArea extends JPanel { private static final String TEXT_SUBMIT = "text-submit"; private static final String INSERT_BREAK = "insert-break"; @@ -47,7 +47,7 @@ public class UserTextArea extends JPanel { private JPanel iconsPanel; private boolean submitEnabled = true; - public UserTextArea(Consumer onSubmit) { + public UserPromptTextArea(Consumer onSubmit) { this.onSubmit = onSubmit; textArea = new JBTextArea(); @@ -68,12 +68,12 @@ public void actionPerformed(ActionEvent e) { textArea.addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent e) { - UserTextArea.super.paintBorder(UserTextArea.super.getGraphics()); + UserPromptTextArea.super.paintBorder(UserPromptTextArea.super.getGraphics()); } @Override public void focusLost(FocusEvent e) { - UserTextArea.super.paintBorder(UserTextArea.super.getGraphics()); + UserPromptTextArea.super.paintBorder(UserPromptTextArea.super.getGraphics()); } }); textArea.getDocument().addDocumentListener(new DocumentListener() { @@ -139,7 +139,7 @@ public void setTextAreaEnabled(boolean textAreaEnabled) { } private void handleSubmit() { - if (submitEnabled && textArea.getText().length() > 0) { + if (submitEnabled && !textArea.getText().isEmpty()) { // Replacing each newline with two newlines to ensure proper Markdown formatting var text = textArea.getText().replace("\n", "\n\n"); onSubmit.accept(text); diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowLandingPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowLandingPanel.java index 27eb83c49..c850a6f9c 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowLandingPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowLandingPanel.java @@ -13,13 +13,13 @@ import ee.carlrobert.codegpt.indexes.CodebaseIndexingTask; import ee.carlrobert.codegpt.indexes.FolderStructureTreePanel; import ee.carlrobert.codegpt.settings.SettingsConfigurable; -import ee.carlrobert.codegpt.toolwindow.chat.ResponsePanel; +import ee.carlrobert.codegpt.toolwindow.chat.components.ResponsePanel; import ee.carlrobert.codegpt.user.UserManager; import ee.carlrobert.codegpt.user.auth.AuthenticationNotifier; import ee.carlrobert.codegpt.user.auth.SignedOutNotifier; import ee.carlrobert.codegpt.util.OverlayUtils; import ee.carlrobert.codegpt.util.SwingUtils; -import ee.carlrobert.codegpt.embeddings.VectorStore; +import ee.carlrobert.vector.VectorStore; import javax.swing.JTextPane; import javax.swing.event.HyperlinkEvent; @@ -53,7 +53,7 @@ private JTextPane createContent() { var description = createTextPane(); var userManager = UserManager.getInstance(); - if (userManager.getSession() == null) { + if (userManager.getAuthenticationResponse() == null) { description.setText("" + "

    It looks like you haven't logged in. Please log in to use the feature.

    " + ""); diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowTabPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowTabPanel.java index 12c0c02a1..6e27419e3 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowTabPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/contextual/ContextualChatToolWindowTabPanel.java @@ -17,17 +17,17 @@ public class ContextualChatToolWindowTabPanel extends BaseChatToolWindowTabPanel public ContextualChatToolWindowTabPanel(@NotNull Project project) { super(project, true); displayLandingView(); - userTextArea.setTextAreaEnabled(UserManager.getInstance().isSubscribed()); + userPromptTextArea.setTextAreaEnabled(UserManager.getInstance().isSubscribed()); project.getMessageBus() .connect() .subscribe(CodebaseIndexingCompletedNotifier.INDEXING_COMPLETED_TOPIC, - (CodebaseIndexingCompletedNotifier) () -> userTextArea.setTextAreaEnabled(UserManager.getInstance().isSubscribed())); + (CodebaseIndexingCompletedNotifier) () -> userPromptTextArea.setTextAreaEnabled(UserManager.getInstance().isSubscribed())); var messageBusConnection = ApplicationManager.getApplication().getMessageBus().connect(); messageBusConnection.subscribe(AuthenticationNotifier.AUTHENTICATION_TOPIC, - (AuthenticationNotifier) () -> userTextArea.setTextAreaEnabled(UserManager.getInstance().isSubscribed())); - messageBusConnection.subscribe(SignedOutNotifier.SIGNED_OUT_TOPIC, (SignedOutNotifier) () -> userTextArea.setTextAreaEnabled(false)); + (AuthenticationNotifier) () -> userPromptTextArea.setTextAreaEnabled(UserManager.getInstance().isSubscribed())); + messageBusConnection.subscribe(SignedOutNotifier.SIGNED_OUT_TOPIC, (SignedOutNotifier) () -> userPromptTextArea.setTextAreaEnabled(false)); } @Override diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowContentManager.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowContentManager.java index 3a1925ebc..cdc02387c 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowContentManager.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowContentManager.java @@ -6,6 +6,7 @@ import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.ui.content.Content; +import ee.carlrobert.codegpt.conversations.Conversation; import ee.carlrobert.codegpt.conversations.ConversationsState; import ee.carlrobert.codegpt.conversations.message.Message; import ee.carlrobert.codegpt.settings.configuration.ConfigurationState; @@ -55,6 +56,15 @@ public void sendMessage(ChatToolWindowTabPanel toolWindowTabPanel, Message messa } } + public void displayConversation(Conversation conversation) { + displayChatTab(); + tryFindChatTabbedPane() + .ifPresent(tabbedPane -> tabbedPane.tryFindActiveConversationTitle(conversation.getId()) + .ifPresentOrElse( + title -> tabbedPane.setSelectedIndex(tabbedPane.indexOfTab(title)), + () -> tabbedPane.addNewTab(new StandardChatToolWindowTabPanel(project, conversation)))); + } + public StandardChatToolWindowTabPanel createNewTabPanel() { displayChatTab(); var tabbedPane = tryFindChatTabbedPane(); @@ -90,12 +100,10 @@ public Optional tryFindChatTabbedPane() { return Optional.empty(); } - public void resetTabbedPane() { + public void resetActiveTab() { tryFindChatTabbedPane().ifPresent(tabbedPane -> { tabbedPane.clearAll(); - var tabPanel = new StandardChatToolWindowTabPanel(project); - tabPanel.displayLandingView(); - tabbedPane.addNewTab(tabPanel); + tabbedPane.addNewTab(new StandardChatToolWindowTabPanel(project)); }); } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowLandingPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowLandingPanel.java index a94a405e2..80923b7fb 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowLandingPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowLandingPanel.java @@ -5,7 +5,7 @@ import com.intellij.openapi.diagnostic.Logger; import ee.carlrobert.codegpt.settings.state.SettingsState; -import ee.carlrobert.codegpt.toolwindow.chat.ResponsePanel; +import ee.carlrobert.codegpt.toolwindow.chat.components.ResponsePanel; import ee.carlrobert.codegpt.util.SwingUtils; import java.awt.event.MouseEvent; import javax.swing.JTextPane; diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowPanel.java index ba8053bb1..bc04ce9c2 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowPanel.java @@ -3,7 +3,9 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.ActionToolbar; +import com.intellij.openapi.actionSystem.Constraints; import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.actionSystem.DefaultCompactActionGroup; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.SimpleToolWindowPanel; import com.intellij.openapi.util.Disposer; @@ -11,6 +13,7 @@ import ee.carlrobert.codegpt.actions.toolwindow.CreateNewConversationAction; import ee.carlrobert.codegpt.actions.toolwindow.OpenInEditorAction; import ee.carlrobert.codegpt.conversations.ConversationsState; +import java.awt.FlowLayout; import org.jetbrains.annotations.NotNull; public class StandardChatToolWindowPanel extends SimpleToolWindowPanel { @@ -21,27 +24,22 @@ public StandardChatToolWindowPanel(@NotNull Project project, @NotNull Disposable } private void initialize(Project project, Disposable parentDisposable) { - var tabPanel = new StandardChatToolWindowTabPanel(project); var conversation = ConversationsState.getCurrentConversation(); - if (conversation == null) { - tabPanel.displayLandingView(); - } else { - tabPanel.displayConversation(conversation); - } - + var tabPanel = new StandardChatToolWindowTabPanel(project, conversation); var tabbedPane = createTabbedPane(tabPanel, parentDisposable); - setToolbar(createActionToolbar(project, tabbedPane).getComponent()); + var toolbarComponent = createActionToolbar(project, tabbedPane).getComponent(); + toolbarComponent.setLayout(new FlowLayout()); + + setToolbar(toolbarComponent); setContent(tabbedPane); Disposer.register(parentDisposable, tabPanel); } private ActionToolbar createActionToolbar(Project project, StandardChatToolWindowTabbedPane tabbedPane) { - var actionGroup = new DefaultActionGroup("TOOLBAR_ACTION_GROUP", false); + var actionGroup = new DefaultCompactActionGroup("TOOLBAR_ACTION_GROUP", false); actionGroup.add(new CreateNewConversationAction(() -> { - var panel = new StandardChatToolWindowTabPanel(project); - panel.displayLandingView(); - tabbedPane.addNewTab(panel); + tabbedPane.addNewTab(new StandardChatToolWindowTabPanel(project)); repaint(); revalidate(); })); @@ -50,7 +48,7 @@ private ActionToolbar createActionToolbar(Project project, StandardChatToolWindo actionGroup.add(new OpenInEditorAction()); var toolbar = ActionManager.getInstance() - .createActionToolbar("NAVIGATION_BAR_TOOLBAR", actionGroup, false); + .createActionToolbar("NAVIGATION_BAR_TOOLBAR", actionGroup, true); toolbar.setTargetComponent(this); return toolbar; } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabPanel.java index 4e1ff95e4..f71d1b93f 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabPanel.java @@ -6,20 +6,31 @@ import com.intellij.openapi.project.Project; import ee.carlrobert.codegpt.conversations.Conversation; import ee.carlrobert.codegpt.conversations.message.Message; +import ee.carlrobert.codegpt.settings.state.SettingsState; import ee.carlrobert.codegpt.toolwindow.chat.BaseChatToolWindowTabPanel; -import ee.carlrobert.codegpt.toolwindow.chat.ChatMessageResponseBody; -import ee.carlrobert.codegpt.toolwindow.chat.ResponsePanel; -import ee.carlrobert.codegpt.toolwindow.chat.UserMessagePanel; +import ee.carlrobert.codegpt.toolwindow.chat.components.ChatMessageResponseBody; +import ee.carlrobert.codegpt.toolwindow.chat.components.ResponsePanel; +import ee.carlrobert.codegpt.toolwindow.chat.components.UserMessagePanel; import ee.carlrobert.codegpt.util.EditorUtils; import ee.carlrobert.codegpt.util.FileUtils; import ee.carlrobert.codegpt.util.OverlayUtils; import javax.swing.JComponent; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class StandardChatToolWindowTabPanel extends BaseChatToolWindowTabPanel { public StandardChatToolWindowTabPanel(@NotNull Project project) { + this(project, null); + } + + public StandardChatToolWindowTabPanel(@NotNull Project project, @Nullable Conversation conversation) { super(project, false); + if (conversation == null) { + displayLandingView(); + } else { + displayConversation(conversation); + } } @Override @@ -51,12 +62,20 @@ protected JComponent getLandingView() { public void displayConversation(@NotNull Conversation conversation) { clearWindow(); conversation.getMessages().forEach(message -> { + var messageResponseBody = new ChatMessageResponseBody(project, this) + .withResponse(message.getResponse()); + + var serpResults = message.getSerpResults(); + if (SettingsState.getInstance().isDisplayWebSearchResults() && serpResults != null && !serpResults.isEmpty()) { + messageResponseBody.displaySerpResults(serpResults); + } + var messageWrapper = createNewMessageWrapper(message.getId()); messageWrapper.add(new UserMessagePanel(project, message, false, this)); messageWrapper.add(new ResponsePanel() .withReloadAction(() -> reloadMessage(message, conversation)) .withDeleteAction(() -> deleteMessage(message.getId(), messageWrapper, conversation)) - .addContent(new ChatMessageResponseBody(project, this).withResponse(message.getResponse()))); + .addContent(messageResponseBody)); }); setConversation(conversation); } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabbedPane.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabbedPane.java index 780289ab9..8e7dafa19 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabbedPane.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/standard/StandardChatToolWindowTabbedPane.java @@ -8,7 +8,7 @@ import com.intellij.ui.components.JBTabbedPane; import com.intellij.util.ui.JBUI; import ee.carlrobert.codegpt.conversations.ConversationsState; -import ee.carlrobert.codegpt.settings.state.ModelSettingsState; +import ee.carlrobert.codegpt.settings.state.SettingsState; import java.awt.Component; import java.awt.Dimension; import java.awt.event.ActionEvent; @@ -36,7 +36,6 @@ public StandardChatToolWindowTabbedPane(Disposable parentDisposable) { this.parentDisposable = parentDisposable; setTabComponentInsets(null); setComponentPopupMenu(new TabPopupMenu()); - addChangeListener(e -> refreshTabState()); } @@ -104,7 +103,7 @@ private void refreshTabState() { var conversation = toolWindowPanel.getConversation(); if (conversation != null) { ConversationsState.getInstance().setCurrentConversation(conversation); - ModelSettingsState.getInstance().sync(conversation); + SettingsState.getInstance().sync(conversation); } } } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/ConversationPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/ConversationPanel.java index 8f0fbac81..e395533cc 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/ConversationPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/ConversationPanel.java @@ -1,94 +1,111 @@ package ee.carlrobert.codegpt.toolwindow.conversations; -import static ee.carlrobert.codegpt.util.SwingUtils.justifyLeft; +import static ee.carlrobert.codegpt.util.ThemeUtils.getPanelBackgroundColor; import com.intellij.icons.AllIcons; +import com.intellij.openapi.project.Project; import com.intellij.ui.JBColor; +import com.intellij.ui.components.JBLabel; +import com.intellij.util.ui.JBFont; import com.intellij.util.ui.JBUI; +import ee.carlrobert.codegpt.actions.toolwindow.DeleteConversationAction; import ee.carlrobert.codegpt.conversations.Conversation; +import ee.carlrobert.codegpt.conversations.ConversationsState; +import ee.carlrobert.codegpt.settings.state.SettingsState; +import ee.carlrobert.codegpt.toolwindow.IconActionButton; +import ee.carlrobert.codegpt.toolwindow.ModelIconLabel; +import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowContentManager; +import java.awt.BorderLayout; import java.awt.Cursor; -import java.awt.Font; +import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.time.format.DateTimeFormatter; -import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JLabel; import javax.swing.JPanel; +import org.jetbrains.annotations.NotNull; class ConversationPanel extends JPanel { - private final Conversation conversation; - - ConversationPanel(Conversation conversation, boolean isSelected) { - this.conversation = conversation; - addStyles(isSelected); - - var constraints = new GridBagConstraints(); - constraints.insets = JBUI.insets(0, 10); - addChatIcon(constraints); - addTextPanel(constraints); + ConversationPanel(@NotNull Project project, @NotNull Conversation conversation, @NotNull Runnable onDelete) { + super(new BorderLayout()); + setBackground(JBColor.background()); + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + SettingsState.getInstance().sync(conversation); + StandardChatToolWindowContentManager.getInstance(project).displayConversation(conversation); + } + }); + addStyles(isSelected(conversation)); + addTextPanel(conversation, onDelete); setCursor(new Cursor(Cursor.HAND_CURSOR)); } + private boolean isSelected(Conversation conversation) { + var currentConversation = ConversationsState.getCurrentConversation(); + return currentConversation != null && currentConversation.getId().equals(conversation.getId()); + } + private void addStyles(boolean isSelected) { - setBackground(JBColor.background().darker()); - if (isSelected) { - setBorder(BorderFactory.createCompoundBorder( - BorderFactory.createMatteBorder(4, 4, 4, 4, JBColor.green), - JBUI.Borders.empty(10))); - } else { - setBorder(JBUI.Borders.empty(10)); - } + var border = isSelected ? + JBUI.Borders.customLine(JBUI.CurrentTheme.ActionButton.focusedBorder(), 2, 2, 2, 2) : + JBUI.Borders.customLine(JBColor.border(), 1, 0, 1, 0); + setBackground(getPanelBackgroundColor()); + setBorder(JBUI.Borders.compound(border, JBUI.Borders.empty(8))); setLayout(new GridBagLayout()); setCursor(new Cursor(Cursor.HAND_CURSOR)); } - private void addChatIcon(GridBagConstraints constraints) { - constraints.gridx = 0; - constraints.gridy = 0; - constraints.weightx = 0.0; - constraints.fill = GridBagConstraints.NONE; - add(new JLabel(AllIcons.Actions.Annotate), constraints); - } - - private void addTextPanel(GridBagConstraints constraints) { + private void addTextPanel(Conversation conversation, Runnable onDelete) { + var constraints = new GridBagConstraints(); constraints.gridx = 1; constraints.weightx = 1.0; constraints.fill = GridBagConstraints.HORIZONTAL; - add(createTextPanel(), constraints); + add(createTextPanel(conversation, onDelete), constraints); } - private JPanel createTextPanel() { - var title = new JLabel(getFirstPrompt()); - title.setBorder(JBUI.Borders.emptyBottom(8)); - title.setFont(title.getFont().deriveFont(title.getFont().getStyle() | Font.BOLD)); + private JPanel createTextPanel(Conversation conversation, Runnable onDelete) { + var headerPanel = new JPanel(new GridBagLayout()); + headerPanel.setBorder(JBUI.Borders.emptyBottom(12)); - var textPanel = new JPanel(); - textPanel.setBackground(getBackground()); - textPanel.setLayout(new BoxLayout(textPanel, BoxLayout.PAGE_AXIS)); - textPanel.add(justifyLeft(title)); + var gbc = new GridBagConstraints(); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + gbc.gridx = 0; - var bottomPanel = new JPanel(); - bottomPanel.setBackground(getBackground()); - bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.X_AXIS)); + headerPanel.setBackground(getPanelBackgroundColor()); + headerPanel.add(new JBLabel(getFirstPrompt(conversation)) + .withFont(JBFont.label().asBold()), gbc); + + gbc.gridx = 1; + gbc.weightx = 0; + headerPanel.add(new IconActionButton("Delete conversation", AllIcons.Actions.GC, new DeleteConversationAction(onDelete)), gbc); + + var bottomPanel = new JPanel(new BorderLayout()); + bottomPanel.setBackground(getPanelBackgroundColor()); bottomPanel.add(new JLabel(conversation.getUpdatedOn() - .format(DateTimeFormatter.ofPattern("M/d/yyyy, h:mm:ss a")))); - bottomPanel.add(Box.createHorizontalGlue()); + .format(DateTimeFormatter.ofPattern("M/d/yyyy, h:mm:ss a"))), BorderLayout.WEST); if (conversation.getModel() != null) { - bottomPanel.add(new JLabel(conversation.getModel())); + bottomPanel.add(new ModelIconLabel(conversation.getClientCode(), conversation.getModel()), BorderLayout.EAST); } - textPanel.add(bottomPanel); + + var textPanel = new JPanel(new BorderLayout()); + textPanel.setBackground(getPanelBackgroundColor()); + textPanel.add(headerPanel, BorderLayout.NORTH); + textPanel.add(bottomPanel, BorderLayout.SOUTH); return textPanel; } - private String getFirstPrompt() { + private String getFirstPrompt(Conversation conversation) { var messages = conversation.getMessages(); - var prompt = ""; - if (!messages.isEmpty()) { - prompt = conversation.getMessages().get(0).getPrompt(); + if (messages.isEmpty()) { + return ""; } - return prompt; + return messages.get(0).getPrompt(); } } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/ConversationsToolWindow.form b/src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/ConversationsToolWindow.form deleted file mode 100644 index 5f778b1e7..000000000 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/ConversationsToolWindow.form +++ /dev/null @@ -1,21 +0,0 @@ - -
    - - - - - - - - - - - - - - - - - - -
    diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/ConversationsToolWindow.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/ConversationsToolWindow.java index e4cf372fc..bae18a652 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/ConversationsToolWindow.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/ConversationsToolWindow.java @@ -9,15 +9,10 @@ import com.intellij.util.ui.JBFont; import com.intellij.util.ui.JBUI; import ee.carlrobert.codegpt.actions.toolwindow.DeleteAllConversationsAction; -import ee.carlrobert.codegpt.actions.toolwindow.DeleteConversationAction; import ee.carlrobert.codegpt.actions.toolwindow.MoveDownAction; import ee.carlrobert.codegpt.actions.toolwindow.MoveUpAction; -import ee.carlrobert.codegpt.conversations.Conversation; import ee.carlrobert.codegpt.conversations.ConversationService; -import ee.carlrobert.codegpt.conversations.ConversationsState; -import ee.carlrobert.codegpt.settings.state.ModelSettingsState; -import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowContentManager; -import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowTabPanel; +import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JLabel; import javax.swing.JPanel; @@ -25,29 +20,35 @@ import javax.swing.ScrollPaneConstants; import org.jetbrains.annotations.NotNull; -public class ConversationsToolWindow { +public class ConversationsToolWindow extends JPanel { private final Project project; private final ConversationService conversationService; - private JPanel conversationsToolWindowContent; - private JScrollPane scrollPane; - private ScrollablePanel scrollablePanel; + private final ScrollablePanel scrollablePanel; + private final JScrollPane scrollPane; public ConversationsToolWindow(@NotNull Project project) { this.project = project; this.conversationService = ConversationService.getInstance(); + scrollablePanel = new ScrollablePanel(); + scrollablePanel.setLayout(new BoxLayout(scrollablePanel, BoxLayout.Y_AXIS)); + + scrollPane = new JBScrollPane(); + scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + scrollPane.setViewportView(scrollablePanel); + scrollPane.setBorder(null); + scrollPane.setViewportBorder(null); refresh(); } public JPanel getContent() { SimpleToolWindowPanel panel = new SimpleToolWindowPanel(true); - panel.setContent(conversationsToolWindowContent); + panel.setContent(scrollPane); var actionGroup = new DefaultActionGroup("TOOLBAR_ACTION_GROUP", false); actionGroup.add(new MoveDownAction(this::refresh)); actionGroup.add(new MoveUpAction(this::refresh)); actionGroup.addSeparator(); - actionGroup.add(new DeleteConversationAction(this::refresh)); actionGroup.add(new DeleteAllConversationsAction(this::refresh)); var toolbar = ActionManager.getInstance() @@ -67,46 +68,13 @@ public void refresh() { emptyLabel.setBorder(JBUI.Borders.empty(8)); scrollablePanel.add(emptyLabel); } else { - sortedConversations.forEach(this::addContent); + sortedConversations.forEach(conversation -> { + scrollablePanel.add(Box.createVerticalStrut(8)); + scrollablePanel.add(new ConversationPanel(project, conversation, this::refresh)); + }); } scrollablePanel.revalidate(); scrollablePanel.repaint(); } - - private void addContent(Conversation conversation) { - var mainPanel = new RootConversationPanel(() -> { - ModelSettingsState.getInstance().sync(conversation); - - var toolWindowContentManager = StandardChatToolWindowContentManager.getInstance(project); - toolWindowContentManager.displayChatTab(); - toolWindowContentManager.tryFindChatTabbedPane() - .ifPresent(tabbedPane -> tabbedPane.tryFindActiveConversationTitle(conversation.getId()) - .ifPresentOrElse( - title -> tabbedPane.setSelectedIndex(tabbedPane.indexOfTab(title)), - () -> { - var panel = new StandardChatToolWindowTabPanel(project); - panel.displayConversation(conversation); - tabbedPane.addNewTab(panel); - })); - }); - - var currentConversation = ConversationsState.getCurrentConversation(); - var isSelected = - currentConversation != null && currentConversation.getId().equals(conversation.getId()); - mainPanel.add(new ConversationPanel(conversation, isSelected)); - mainPanel.setBackground(conversationsToolWindowContent.getBackground()); - scrollablePanel.add(mainPanel); - } - - private void createUIComponents() { - scrollablePanel = new ScrollablePanel(); - scrollablePanel.setLayout(new BoxLayout(scrollablePanel, BoxLayout.Y_AXIS)); - - scrollPane = new JBScrollPane(); - scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - scrollPane.setViewportView(scrollablePanel); - scrollPane.setBorder(null); - scrollPane.setViewportBorder(null); - } } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/RootConversationPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/RootConversationPanel.java deleted file mode 100644 index a1d7b1047..000000000 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/conversations/RootConversationPanel.java +++ /dev/null @@ -1,43 +0,0 @@ -package ee.carlrobert.codegpt.toolwindow.conversations; - -import com.intellij.ui.JBColor; -import com.intellij.util.ui.JBUI; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import javax.swing.BoxLayout; -import javax.swing.JPanel; - -class RootConversationPanel extends JPanel { - - RootConversationPanel(Runnable onClick) { - setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - setBorder(JBUI.Borders.empty(10, 20)); - setBackground(JBColor.background()); - addMouseListener(getMouseListener(onClick)); - } - - private MouseListener getMouseListener(Runnable onClick) { - return new MouseListener() { - @Override - public void mouseClicked(MouseEvent mouseEvent) { - onClick.run(); - } - - @Override - public void mousePressed(MouseEvent mouseEvent) { - } - - @Override - public void mouseReleased(MouseEvent mouseEvent) { - } - - @Override - public void mouseEntered(MouseEvent mouseEvent) { - } - - @Override - public void mouseExited(MouseEvent mouseEvent) { - } - }; - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/user/ApiClient.java b/src/main/java/ee/carlrobert/codegpt/user/ApiClient.java index f0260d703..e4139e22e 100644 --- a/src/main/java/ee/carlrobert/codegpt/user/ApiClient.java +++ b/src/main/java/ee/carlrobert/codegpt/user/ApiClient.java @@ -1,84 +1,51 @@ package ee.carlrobert.codegpt.user; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.Service; -import ee.carlrobert.codegpt.user.subscription.Subscription; -import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.Map; import okhttp3.Callback; -import okhttp3.MediaType; +import okhttp3.Headers; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; -import org.jetbrains.annotations.Nullable; @Service public final class ApiClient { - private static final String API_BASE_URL = "http://localhost:8000/api"; - private final OkHttpClient httpClient; - - private ApiClient() { - httpClient = new OkHttpClient(); - } + private static final String API_BASE_URL = "https://web.stytch.com/sdk"; + private static final String publicToken = "public-token-live-507a52ad-7e69-496b-aee0-1c9863c7c819"; public static ApiClient getInstance() { return ApplicationManager.getApplication().getService(ApiClient.class); } - public @Nullable Subscription getSubscription(String accessToken) { - try { - var body = httpClient.newCall(new Request.Builder() - .url(API_BASE_URL + "/subscriptions") - .header("Authorization", accessToken) - .get() - .build()) - .execute() - .body(); - if (body == null) { - return null; - } - return new ObjectMapper().readValue(body.string(), new TypeReference<>() {}); - } catch (IOException e) { - throw new RuntimeException("Unable to obtain subscriptions", e); - } - } - public void authenticate(String email, String password, Callback callback) { try { - httpClient.newCall(new Request.Builder() - .url(API_BASE_URL + "/auth") - .post(RequestBody.create( - new ObjectMapper() - .writerWithDefaultPrettyPrinter() - .writeValueAsString(Map.of( - "email", email, - "password", password)), - MediaType.parse("application/json"))) + new OkHttpClient() + .newCall(new Request.Builder() + .url(API_BASE_URL + "/v1/passwords/authenticate") + .headers(Headers.of( + "content-type", "application/json", + "authority", "web.stytch.com", + "authorization", "Basic " + Base64.getEncoder().encodeToString((publicToken + ":" + publicToken).getBytes()), + "x-sdk-client", "eyJldmVudF9pZCI6ImV2ZW50LWlkLWY5YmU4YWU5LWE3MjctNGFlYy1hNzY0LTk4NDg1NDFkZjcwYSIsImFwcF9zZXNzaW9uX2lkIjoiYXBwLXNlc3Npb24taWQtYjY1NzcwZjMtMWFkMy00YjlhLWFjYzctMzJjNWQyMGMxNGU0IiwicGVyc2lzdGVudF9pZCI6InBlcnNpc3RlbnQtaWQtYzY0M2M0YTMtZDg5MC00ZGJkLTk3YjQtMjY0MmFlODdkMTZhIiwiY2xpZW50X3NlbnRfYXQiOiIyMDIzLTA5LTAxVDIyOjMwOjU1LjIzNFoiLCJ0aW1lem9uZSI6IkV1cm9wZS9UYWxsaW5uIiwiYXBwIjp7ImlkZW50aWZpZXIiOiJ5b3UuY29tIn0sInNkayI6eyJpZGVudGlmaWVyIjoiU3R5dGNoLmpzIEphdmFzY3JpcHQgU0RLIC0gWU9VLkNPTSBERUJVRyBCVUlMRCIsInZlcnNpb24iOiI0LjAuMCJ9fQ==", + "x-sdk-parent-host", "https://you.com" + )) + .post(RequestBody.create(new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(Map.of( + "email", email, + "password", password, + "session_duration_minutes", 129_600)) + .getBytes(StandardCharsets.UTF_8))) .build()) .enqueue(callback); } catch (JsonProcessingException e) { throw new RuntimeException("Could not process request", e); } } - - public void refreshToken(String refreshToken, Callback callback) { - try { - httpClient.newCall(new Request.Builder() - .url(API_BASE_URL + "/refresh-token") - .patch(RequestBody.create( - new ObjectMapper() - .writerWithDefaultPrettyPrinter() - .writeValueAsString(Map.of( - "refreshToken", refreshToken)), - MediaType.parse("application/json"))) - .build()) - .enqueue(callback); - } catch (JsonProcessingException e) { - throw new RuntimeException("Unable to refresh token", e); - } - } } diff --git a/src/main/java/ee/carlrobert/codegpt/user/UserManager.java b/src/main/java/ee/carlrobert/codegpt/user/UserManager.java index 41ddee3f2..45419fc51 100644 --- a/src/main/java/ee/carlrobert/codegpt/user/UserManager.java +++ b/src/main/java/ee/carlrobert/codegpt/user/UserManager.java @@ -2,16 +2,13 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.Service; -import ee.carlrobert.codegpt.user.auth.Session; import ee.carlrobert.codegpt.user.auth.SignedOutNotifier; -import ee.carlrobert.codegpt.user.subscription.Subscription; -import org.jetbrains.annotations.Nullable; +import ee.carlrobert.codegpt.user.auth.response.AuthenticationResponse; @Service public final class UserManager { - private Session session; - private Subscription subscription; + private AuthenticationResponse authenticationResponse; private UserManager() { } @@ -20,32 +17,27 @@ public static UserManager getInstance() { return ApplicationManager.getApplication().getService(UserManager.class); } - public @Nullable Session getSession() { - return session; + public AuthenticationResponse getAuthenticationResponse() { + return authenticationResponse; } - public void setSession(@Nullable Session session) { - this.session = session; + public void setAuthenticationResponse(AuthenticationResponse authenticationResponse) { + this.authenticationResponse = authenticationResponse; } public void clearSession() { - session = null; - subscription = null; + authenticationResponse = null; ApplicationManager.getApplication().getMessageBus() .syncPublisher(SignedOutNotifier.SIGNED_OUT_TOPIC) .signedOut(); } - public @Nullable Subscription getSubscription() { - return subscription; - } - - public void setSubscription(@Nullable Subscription subscription) { - this.subscription = subscription; + public boolean isSubscribed() { + return true; // TODO } - public boolean isSubscribed() { - return session != null && subscription != null; + public boolean isAuthenticated() { + return authenticationResponse != null; } } diff --git a/src/main/java/ee/carlrobert/codegpt/user/auth/AuthenticationError.java b/src/main/java/ee/carlrobert/codegpt/user/auth/AuthenticationError.java new file mode 100644 index 000000000..faee2da21 --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/user/auth/AuthenticationError.java @@ -0,0 +1,24 @@ +package ee.carlrobert.codegpt.user.auth; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class AuthenticationError { + + private final String errorType; + private final String errorMessage; + + public AuthenticationError(@JsonProperty("error_type") String errorType, @JsonProperty("error_message") String errorMessage) { + this.errorType = errorType; + this.errorMessage = errorMessage; + } + + public String getErrorType() { + return errorType; + } + + public String getErrorMessage() { + return errorMessage; + } +} diff --git a/src/main/java/ee/carlrobert/codegpt/user/auth/AuthenticationHandler.java b/src/main/java/ee/carlrobert/codegpt/user/auth/AuthenticationHandler.java index 1b2241a98..437fa9eab 100644 --- a/src/main/java/ee/carlrobert/codegpt/user/auth/AuthenticationHandler.java +++ b/src/main/java/ee/carlrobert/codegpt/user/auth/AuthenticationHandler.java @@ -1,10 +1,12 @@ package ee.carlrobert.codegpt.user.auth; -public interface AuthenticationHandler { +import ee.carlrobert.codegpt.user.auth.response.AuthenticationResponse; - void handleAuthenticated(); +public interface AuthenticationHandler { - void handleInvalidCredentials(); + void handleAuthenticated(AuthenticationResponse authenticationResponse); void handleGenericError(); + + void handleError(AuthenticationError authenticationError); } diff --git a/src/main/java/ee/carlrobert/codegpt/user/auth/AuthenticationService.java b/src/main/java/ee/carlrobert/codegpt/user/auth/AuthenticationService.java index b394fb1c8..af5f1ebb6 100644 --- a/src/main/java/ee/carlrobert/codegpt/user/auth/AuthenticationService.java +++ b/src/main/java/ee/carlrobert/codegpt/user/auth/AuthenticationService.java @@ -5,10 +5,9 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.Service; import com.intellij.openapi.diagnostic.Logger; -import ee.carlrobert.codegpt.credentials.UserCredentialsManager; -import ee.carlrobert.codegpt.settings.state.SettingsState; import ee.carlrobert.codegpt.user.ApiClient; import ee.carlrobert.codegpt.user.UserManager; +import ee.carlrobert.codegpt.user.auth.response.AuthenticationResponse; import ee.carlrobert.codegpt.util.OverlayUtils; import java.io.IOException; import okhttp3.Call; @@ -33,56 +32,7 @@ public void signInAsync(String email, String password, AuthenticationHandler aut client.authenticate(email, password, new AuthenticationCallback(authenticationHandler, email, password)); } - public void refreshToken() { - var userManager = UserManager.getInstance(); - var session = userManager.getSession(); - - if (session == null) { - throw new IllegalStateException("Tried to revalidate unauthenticated user"); - } - - client.refreshToken(session.getRefreshToken(), new Callback() { - @Override - public void onFailure(@NotNull Call call, @NotNull IOException e) { - userManager.clearSession(); - } - - @Override - public void onResponse(@NotNull Call call, @NotNull Response response) { - if (response.code() == 200) { - var body = response.body(); - if (body != null) { - try { - handleSuccessfulAuthentication(new ObjectMapper().readValue(body.string(), Session.class)); - return; - } catch (IOException e) { - throw new RuntimeException("Unable to deserialize session", e); - } - } - } - - userManager.clearSession(); - throw new RuntimeException("Internal server error. " + response.message()); - } - }); - } - - private void handleSuccessfulAuthentication(Session session) { - SettingsState.getInstance().setPreviouslySignedIn(true); - - var userManager = UserManager.getInstance(); - userManager.setSession(session); - - var subscription = client.getSubscription(session.getAccessToken()); - if (subscription != null) { - userManager.setSubscription(subscription); - } - ApplicationManager.getApplication().getMessageBus() - .syncPublisher(AuthenticationNotifier.AUTHENTICATION_TOPIC) - .authenticationSuccessful(); - } - - class AuthenticationCallback implements Callback { + static class AuthenticationCallback implements Callback { private final AuthenticationHandler authenticationHandler; private final String email; @@ -102,27 +52,33 @@ public void onFailure(@NotNull Call call, @NotNull IOException e) { @Override public void onResponse(@NotNull Call call, @NotNull Response response) { - if (response.code() == 401) { - authenticationHandler.handleInvalidCredentials(); + var body = response.body(); + if (body == null) { + authenticationHandler.handleGenericError(); + return; } + if (response.code() == 200) { - var body = response.body(); - if (body != null) { - try { - var session = new ObjectMapper().readValue(body.string(), Session.class); - handleSuccessfulAuthentication(session); - SettingsState.getInstance().setEmail(email); - UserCredentialsManager.getInstance().setAccountPassword(password); - authenticationHandler.handleAuthenticated(); - return; - } catch (IOException e) { - throw new RuntimeException("Unable to deserialize session", e); - } + try { + var authenticationResponse = new ObjectMapper().readValue(body.string(), AuthenticationResponse.class); + UserManager.getInstance().setAuthenticationResponse(authenticationResponse); + authenticationHandler.handleAuthenticated(authenticationResponse); + + ApplicationManager.getApplication().getMessageBus() + .syncPublisher(AuthenticationNotifier.AUTHENTICATION_TOPIC) + .authenticationSuccessful(); + return; + } catch (IOException e) { + throw new RuntimeException("Unable to deserialize session", e); } } - authenticationHandler.handleGenericError(); - throw new RuntimeException("Internal server error. " + response.message()); + try { + authenticationHandler.handleError(new ObjectMapper().readValue(body.string(), AuthenticationError.class)); + } catch (Throwable ex) { + authenticationHandler.handleGenericError(); + throw new RuntimeException(ex); + } } } } diff --git a/src/main/java/ee/carlrobert/codegpt/user/auth/Session.java b/src/main/java/ee/carlrobert/codegpt/user/auth/Session.java deleted file mode 100644 index 1b8a09349..000000000 --- a/src/main/java/ee/carlrobert/codegpt/user/auth/Session.java +++ /dev/null @@ -1,43 +0,0 @@ -package ee.carlrobert.codegpt.user.auth; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import java.time.LocalDateTime; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class Session { - - private final String accessToken; - private final String refreshToken; - private final SessionUser user; - private final long expiresIn; - private final LocalDateTime createdAt; - - public Session( - @JsonProperty("access_token") String accessToken, - @JsonProperty("refresh_token") String refreshToken, - @JsonProperty("user") SessionUser user, - @JsonProperty("expires_in") long expiresIn) { - this.accessToken = accessToken; - this.refreshToken = refreshToken; - this.user = user; - this.expiresIn = expiresIn; - this.createdAt = LocalDateTime.now(); - } - - public String getAccessToken() { - return accessToken; - } - - public String getRefreshToken() { - return refreshToken; - } - - public SessionUser getUser() { - return user; - } - - public boolean isExpired() { - return LocalDateTime.now().isAfter(createdAt.plusSeconds(expiresIn)); - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/user/auth/SessionUser.java b/src/main/java/ee/carlrobert/codegpt/user/auth/SessionUser.java deleted file mode 100644 index be3411aca..000000000 --- a/src/main/java/ee/carlrobert/codegpt/user/auth/SessionUser.java +++ /dev/null @@ -1,34 +0,0 @@ -package ee.carlrobert.codegpt.user.auth; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.UUID; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class SessionUser { - - private final UUID id; - private final String role; - private final String email; - - public SessionUser( - @JsonProperty("id") UUID id, - @JsonProperty("role") String role, - @JsonProperty("email") String email) { - this.id = id; - this.role = role; - this.email = email; - } - - public UUID getId() { - return id; - } - - public String getRole() { - return role; - } - - public String getEmail() { - return email; - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/user/auth/SessionVerificationJob.java b/src/main/java/ee/carlrobert/codegpt/user/auth/SessionVerificationJob.java index d431a995d..d6b75e8a2 100644 --- a/src/main/java/ee/carlrobert/codegpt/user/auth/SessionVerificationJob.java +++ b/src/main/java/ee/carlrobert/codegpt/user/auth/SessionVerificationJob.java @@ -13,10 +13,5 @@ public class SessionVerificationJob implements Job { @Override public void execute(JobExecutionContext context) { LOG.info("Refreshing token: " + LocalDateTime.now()); - - var session = UserManager.getInstance().getSession(); - if (session != null && session.isExpired()) { - AuthenticationService.getInstance().refreshToken(); - } } } diff --git a/src/main/java/ee/carlrobert/codegpt/user/auth/response/AuthenticationResponse.java b/src/main/java/ee/carlrobert/codegpt/user/auth/response/AuthenticationResponse.java new file mode 100644 index 000000000..67f567d27 --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/user/auth/response/AuthenticationResponse.java @@ -0,0 +1,18 @@ +package ee.carlrobert.codegpt.user.auth.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class AuthenticationResponse { + + private final AuthenticationResponseData data; + + public AuthenticationResponse(@JsonProperty("data") AuthenticationResponseData data) { + this.data = data; + } + + public AuthenticationResponseData getData() { + return data; + } +} diff --git a/src/main/java/ee/carlrobert/codegpt/user/auth/response/AuthenticationResponseData.java b/src/main/java/ee/carlrobert/codegpt/user/auth/response/AuthenticationResponseData.java new file mode 100644 index 000000000..f206ee540 --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/user/auth/response/AuthenticationResponseData.java @@ -0,0 +1,40 @@ +package ee.carlrobert.codegpt.user.auth.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class AuthenticationResponseData { + + private final Session session; + private final String sessionJwt; + private final String sessionToken; + private final User user; + + public AuthenticationResponseData( + @JsonProperty("session") Session session, + @JsonProperty("session_jwt") String sessionJwt, + @JsonProperty("session_token") String sessionToken, + @JsonProperty("user") User user) { + this.session = session; + this.sessionJwt = sessionJwt; + this.sessionToken = sessionToken; + this.user = user; + } + + public Session getSession() { + return session; + } + + public String getSessionJwt() { + return sessionJwt; + } + + public String getSessionToken() { + return sessionToken; + } + + public User getUser() { + return user; + } +} diff --git a/src/main/java/ee/carlrobert/codegpt/user/auth/response/Email.java b/src/main/java/ee/carlrobert/codegpt/user/auth/response/Email.java new file mode 100644 index 000000000..bf6696dc7 --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/user/auth/response/Email.java @@ -0,0 +1,33 @@ +package ee.carlrobert.codegpt.user.auth.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Email { + + private final String email; + private final String emailId; + private final boolean verified; + + public Email( + @JsonProperty("email") String email, + @JsonProperty("email_id") String emailId, + @JsonProperty("verified") boolean verified) { + this.email = email; + this.emailId = emailId; + this.verified = verified; + } + + public String getEmail() { + return email; + } + + public String getEmailId() { + return emailId; + } + + public boolean isVerified() { + return verified; + } +} diff --git a/src/main/java/ee/carlrobert/codegpt/user/auth/response/Name.java b/src/main/java/ee/carlrobert/codegpt/user/auth/response/Name.java new file mode 100644 index 000000000..107d25432 --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/user/auth/response/Name.java @@ -0,0 +1,33 @@ +package ee.carlrobert.codegpt.user.auth.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Name { + + private final String firstName; + private final String middleName; + private final String lastName; + + public Name( + @JsonProperty("first_name") String firstName, + @JsonProperty("middle_name") String middleName, + @JsonProperty("last_name") String lastName) { + this.firstName = firstName; + this.middleName = middleName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public String getMiddleName() { + return middleName; + } + + public String getLastName() { + return lastName; + } +} diff --git a/src/main/java/ee/carlrobert/codegpt/user/auth/response/Session.java b/src/main/java/ee/carlrobert/codegpt/user/auth/response/Session.java new file mode 100644 index 000000000..bf563a1f7 --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/user/auth/response/Session.java @@ -0,0 +1,47 @@ +package ee.carlrobert.codegpt.user.auth.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Session { + + private final String expiresAt; + private final String lastAccessedAt; + private final String sessionId; + private final String startedAt; + private final String userId; + + public Session( + @JsonProperty("expires_at") String expiresAt, + @JsonProperty("last_accessed_at") String lastAccessedAt, + @JsonProperty("session_id") String sessionId, + @JsonProperty("started_at") String startedAt, + @JsonProperty("user_id") String userId) { + this.expiresAt = expiresAt; + this.lastAccessedAt = lastAccessedAt; + this.sessionId = sessionId; + this.startedAt = startedAt; + this.userId = userId; + } + + public String getExpiresAt() { + return expiresAt; + } + + public String getLastAccessedAt() { + return lastAccessedAt; + } + + public String getSessionId() { + return sessionId; + } + + public String getStartedAt() { + return startedAt; + } + + public String getUserId() { + return userId; + } +} diff --git a/src/main/java/ee/carlrobert/codegpt/user/auth/response/User.java b/src/main/java/ee/carlrobert/codegpt/user/auth/response/User.java new file mode 100644 index 000000000..6abcdf1e7 --- /dev/null +++ b/src/main/java/ee/carlrobert/codegpt/user/auth/response/User.java @@ -0,0 +1,34 @@ +package ee.carlrobert.codegpt.user.auth.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class User { + + private final List emails; + private final Name name; + private final String userId; + + public User( + @JsonProperty("emails") List emails, + @JsonProperty("name") Name name, + @JsonProperty("user_id") String userId) { + this.emails = emails; + this.name = name; + this.userId = userId; + } + + public List getEmails() { + return emails; + } + + public Name getName() { + return name; + } + + public String getUserId() { + return userId; + } +} diff --git a/src/main/java/ee/carlrobert/codegpt/user/subscription/Subscription.java b/src/main/java/ee/carlrobert/codegpt/user/subscription/Subscription.java deleted file mode 100644 index ec63bf88f..000000000 --- a/src/main/java/ee/carlrobert/codegpt/user/subscription/Subscription.java +++ /dev/null @@ -1,47 +0,0 @@ -package ee.carlrobert.codegpt.user.subscription; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class Subscription { - - private final String id; - private final String userId; - private final String status; - private final String priceId; - private final SubscriptionPrice prices; - - public Subscription( - @JsonProperty("id") String id, - @JsonProperty("user_id") String userId, - @JsonProperty("status") String status, - @JsonProperty("price_id") String priceId, - @JsonProperty("prices") SubscriptionPrice prices) { - this.id = id; - this.userId = userId; - this.status = status; - this.priceId = priceId; - this.prices = prices; - } - - public String getId() { - return id; - } - - public String getUserId() { - return userId; - } - - public String getStatus() { - return status; - } - - public String getPriceId() { - return priceId; - } - - public SubscriptionPrice getPrices() { - return prices; - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/user/subscription/SubscriptionPrice.java b/src/main/java/ee/carlrobert/codegpt/user/subscription/SubscriptionPrice.java deleted file mode 100644 index 17d7f47ee..000000000 --- a/src/main/java/ee/carlrobert/codegpt/user/subscription/SubscriptionPrice.java +++ /dev/null @@ -1,40 +0,0 @@ -package ee.carlrobert.codegpt.user.subscription; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class SubscriptionPrice { - - private final String id; - private final String productId; - private final boolean active; - private final SubscriptionProduct products; - - public SubscriptionPrice( - @JsonProperty("id") String id, - @JsonProperty("product_id") String productId, - @JsonProperty("active") boolean active, - @JsonProperty("products") SubscriptionProduct products) { - this.id = id; - this.productId = productId; - this.active = active; - this.products = products; - } - - public String getId() { - return id; - } - - public String getProductId() { - return productId; - } - - public boolean isActive() { - return active; - } - - public SubscriptionProduct getProducts() { - return products; - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/user/subscription/SubscriptionProduct.java b/src/main/java/ee/carlrobert/codegpt/user/subscription/SubscriptionProduct.java deleted file mode 100644 index 0b83c2f1c..000000000 --- a/src/main/java/ee/carlrobert/codegpt/user/subscription/SubscriptionProduct.java +++ /dev/null @@ -1,40 +0,0 @@ -package ee.carlrobert.codegpt.user.subscription; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class SubscriptionProduct { - - private final String id; - private final boolean active; - private final String name; - private final String description; - - public SubscriptionProduct( - @JsonProperty("id") String id, - @JsonProperty("active") boolean active, - @JsonProperty("name") String name, - @JsonProperty("description") String description) { - this.id = id; - this.active = active; - this.name = name; - this.description = description; - } - - public String getId() { - return id; - } - - public boolean isActive() { - return active; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/util/EditorUtils.java b/src/main/java/ee/carlrobert/codegpt/util/EditorUtils.java index 7a7e25fea..83ba71865 100644 --- a/src/main/java/ee/carlrobert/codegpt/util/EditorUtils.java +++ b/src/main/java/ee/carlrobert/codegpt/util/EditorUtils.java @@ -1,10 +1,13 @@ package ee.carlrobert.codegpt.util; +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocumentManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -36,4 +39,11 @@ public static void replaceMainEditorSelection(@NotNull Project project, @NotNull } }))); } + + public static void disableHighlighting(@NotNull Project project, Document document) { + var psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); + if (psiFile != null) { + DaemonCodeAnalyzer.getInstance(project).setHighlightingEnabled(psiFile, false); + } + } } diff --git a/src/main/java/ee/carlrobert/codegpt/util/SwingUtils.java b/src/main/java/ee/carlrobert/codegpt/util/SwingUtils.java index 72c42a959..e0cd01a30 100644 --- a/src/main/java/ee/carlrobert/codegpt/util/SwingUtils.java +++ b/src/main/java/ee/carlrobert/codegpt/util/SwingUtils.java @@ -2,6 +2,7 @@ import static javax.swing.event.HyperlinkEvent.EventType.ACTIVATED; +import com.intellij.ide.BrowserUtil; import com.intellij.util.ui.UI; import java.awt.Component; import java.awt.Desktop; @@ -59,12 +60,10 @@ public static JPanel createPanel(JComponent component, String label, boolean res public static void handleHyperlinkClicked(HyperlinkEvent event) { if (ACTIVATED.equals(event.getEventType()) && event.getURL() != null) { - if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { - try { - Desktop.getDesktop().browse(event.getURL().toURI()); - } catch (IOException | URISyntaxException e) { - throw new RuntimeException("Couldn't open the browser.", e); - } + try { + BrowserUtil.browse(event.getURL().toURI()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); } } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index b109be733..520bfdb8a 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -15,6 +15,7 @@ + diff --git a/src/main/resources/icons/azure.svg b/src/main/resources/icons/azure.svg new file mode 100644 index 000000000..b71230253 --- /dev/null +++ b/src/main/resources/icons/azure.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/icons/openai.svg b/src/main/resources/icons/openai.svg new file mode 100644 index 000000000..0d851038b --- /dev/null +++ b/src/main/resources/icons/openai.svg @@ -0,0 +1,2 @@ + +OpenAI icon \ No newline at end of file diff --git a/src/main/resources/icons/openai_dark.svg b/src/main/resources/icons/openai_dark.svg new file mode 100644 index 000000000..d033fb74f --- /dev/null +++ b/src/main/resources/icons/openai_dark.svg @@ -0,0 +1,2 @@ + +OpenAI icon \ No newline at end of file diff --git a/src/main/resources/icons/you.svg b/src/main/resources/icons/you.svg new file mode 100644 index 000000000..7b017a396 --- /dev/null +++ b/src/main/resources/icons/you.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/messages/codegpt.properties b/src/main/resources/messages/codegpt.properties index edb5bf4d6..9719c3b6b 100644 --- a/src/main/resources/messages/codegpt.properties +++ b/src/main/resources/messages/codegpt.properties @@ -1,7 +1,7 @@ action.editor.group.EditorActionGroup=CodeGPT notification.group.name=CodeGPT notification group settings.displayName=CodeGPT: Settings -settingsConfigurable.section.userAuthentication.title=User Authentication +settingsConfigurable.section.userAuthentication.title=Authentication settingsConfigurable.section.userAuthentication.signIn.label=Sign In settingsConfigurable.section.userInformation.title=User Information settingsConfigurable.section.integration.apiKeyField.label=API key: @@ -10,7 +10,7 @@ settingsConfigurable.section.integration.displayNameFieldLabel=Display name: settingsConfigurable.section.service.title=Service Preference settingsConfigurable.section.service.useOpenAIServiceRadioButtonLabel=Use OpenAI API settingsConfigurable.section.service.useAzureServiceRadioButtonLabel=Use Azure OpenAI API -settingsConfigurable.section.service.useCustomServiceRadioButtonLabel=Use custom service +settingsConfigurable.section.service.useYouServiceRadioButtonLabel=Use You API settingsConfigurable.section.service.useActiveDirectoryAuthenticationCheckBoxLabel=Use Azure active directory authentication settingsConfigurable.section.service.openai.organizationField.label=Organization: settingsConfigurable.section.service.openai.organizationField.comment=Useful when you are part of multiple organizations optional @@ -25,7 +25,6 @@ settingsConfigurable.section.service.custom.hostField.comment=Example: h settingsConfigurable.section.model.title=Model Preference settingsConfigurable.section.model.selectionFieldLabel=Model: settingsConfigurable.section.model.useChatCompletionRadioButtonLabel=Use chat completion -settingsConfigurable.section.model.useTextCompletionRadioButtonLabel=Use text completion codebaseIndexing.task.title=Indexing codebase codebaseIndexing.task.completed.title=Indexing completed codebaseIndexing.task.completed.description=Creating embeddings completed diff --git a/src/test/java/ee/carlrobert/codegpt/completions/CompletionRequestProviderTest.java b/src/test/java/ee/carlrobert/codegpt/completions/CompletionRequestProviderTest.java index c9f95eb5c..e08bca5c2 100644 --- a/src/test/java/ee/carlrobert/codegpt/completions/CompletionRequestProviderTest.java +++ b/src/test/java/ee/carlrobert/codegpt/completions/CompletionRequestProviderTest.java @@ -1,34 +1,28 @@ package ee.carlrobert.codegpt.completions; import static ee.carlrobert.codegpt.completions.CompletionRequestProvider.COMPLETION_SYSTEM_PROMPT; -import static ee.carlrobert.openai.util.JSONUtil.e; -import static ee.carlrobert.openai.util.JSONUtil.jsonArray; -import static ee.carlrobert.openai.util.JSONUtil.jsonMap; -import static ee.carlrobert.openai.util.JSONUtil.jsonMapResponse; +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 org.apache.http.HttpHeaders.AUTHORIZATION; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; import com.intellij.testFramework.fixtures.BasePlatformTestCase; -import ee.carlrobert.codegpt.conversations.Conversation; import ee.carlrobert.codegpt.conversations.ConversationService; import ee.carlrobert.codegpt.conversations.message.Message; import ee.carlrobert.codegpt.credentials.OpenAICredentialsManager; -import ee.carlrobert.codegpt.settings.state.ModelSettingsState; +import ee.carlrobert.codegpt.settings.configuration.ConfigurationState; import ee.carlrobert.codegpt.settings.state.OpenAISettingsState; import ee.carlrobert.codegpt.settings.state.SettingsState; -import ee.carlrobert.codegpt.settings.configuration.ConfigurationState; -import ee.carlrobert.openai.client.ClientCode; -import ee.carlrobert.openai.client.completion.chat.ChatCompletionModel; -import ee.carlrobert.openai.client.completion.text.TextCompletionModel; -import ee.carlrobert.openai.http.LocalCallbackServer; -import ee.carlrobert.openai.http.ResponseEntity; -import ee.carlrobert.openai.http.exchange.BasicHttpExchange; -import ee.carlrobert.openai.http.expectation.BasicExpectation; -import java.time.LocalDateTime; +import ee.carlrobert.llm.client.http.LocalCallbackServer; +import ee.carlrobert.llm.client.http.ResponseEntity; +import ee.carlrobert.llm.client.http.exchange.BasicHttpExchange; +import ee.carlrobert.llm.client.http.expectation.BasicExpectation; +import ee.carlrobert.llm.client.openai.completion.chat.OpenAIChatCompletionModel; import java.util.List; import java.util.Map; -import java.util.UUID; public class CompletionRequestProviderTest extends BasePlatformTestCase { @@ -49,46 +43,6 @@ protected void tearDown() throws Exception { super.tearDown(); } - public void testTextCompletionRequestWithSystemPromptOverride() { - ConfigurationState.getInstance().setSystemPrompt("TEST_SYSTEM_PROMPT"); - var conversation = createConversation(ClientCode.TEXT_COMPLETION); - var firstMsg = new Message("TEST_PROMPT", "TEST_RESPONSE"); - conversation.addMessage(firstMsg); - conversation.addMessage(new Message("TEST_PROMPT_2", "TEST_RESPONSE_2")); - - var request = new CompletionRequestProvider(conversation) - .buildTextCompletionRequest(TextCompletionModel.DAVINCI.getCode(), new Message("TEST_TEXT_COMPLETION_PROMPT"), false); - - assertThat(request.getPrompt()) - .isEqualTo("TEST_SYSTEM_PROMPT\n" - + "Human: TEST_PROMPT\n" - + "AI: TEST_RESPONSE\n" - + "Human: TEST_PROMPT_2\n" - + "AI: TEST_RESPONSE_2\n" - + "Human: TEST_TEXT_COMPLETION_PROMPT\n" - + "AI: \n"); - } - - public void testTextCompletionRequestWithoutSystemPromptOverride() { - var conversation = createConversation(ClientCode.TEXT_COMPLETION); - var firstMsg = new Message("TEST_PROMPT", "TEST_RESPONSE"); - conversation.addMessage(firstMsg); - conversation.addMessage(new Message("TEST_PROMPT_2", "TEST_RESPONSE_2")); - - var request = new CompletionRequestProvider(conversation) - .buildTextCompletionRequest(TextCompletionModel.DAVINCI.getCode(), new Message("TEST_TEXT_COMPLETION_PROMPT"), false); - - assertThat(request.getPrompt()) - .isEqualTo("You are ChatGPT, a large language model trained by OpenAI.\n" - + "Answer in a markdown language, code blocks should contain language whenever possible.\n" - + "Human: TEST_PROMPT\n" - + "AI: TEST_RESPONSE\n" - + "Human: TEST_PROMPT_2\n" - + "AI: TEST_RESPONSE_2\n" - + "Human: TEST_TEXT_COMPLETION_PROMPT\n" - + "AI: \n"); - } - public void testChatCompletionRequestWithSystemPromptOverride() { ConfigurationState.getInstance().setSystemPrompt("TEST_SYSTEM_PROMPT"); var conversation = ConversationService.getInstance().startConversation(); @@ -98,7 +52,11 @@ public void testChatCompletionRequestWithSystemPromptOverride() { conversation.addMessage(secondMessage); var request = new CompletionRequestProvider(conversation) - .buildChatCompletionRequest(ChatCompletionModel.GPT_3_5.getCode(), new Message("TEST_CHAT_COMPLETION_PROMPT"), false, false); + .buildOpenAIChatCompletionRequest(OpenAIChatCompletionModel.GPT_3_5.getCode(), + new Message("TEST_CHAT_COMPLETION_PROMPT"), + false, + false, + null); assertThat(request.getMessages()) .extracting("role", "content") @@ -119,7 +77,7 @@ public void testChatCompletionRequestWithoutSystemPromptOverride() { conversation.addMessage(secondMessage); var request = new CompletionRequestProvider(conversation) - .buildChatCompletionRequest(ChatCompletionModel.GPT_3_5.getCode(), new Message("TEST_CHAT_COMPLETION_PROMPT"), false); + .buildOpenAIChatCompletionRequest(OpenAIChatCompletionModel.GPT_3_5.getCode(), new Message("TEST_CHAT_COMPLETION_PROMPT"), false); assertThat(request.getMessages()) .extracting("role", "content") @@ -140,7 +98,7 @@ public void testChatCompletionRequestRetry() { conversation.addMessage(secondMessage); var request = new CompletionRequestProvider(conversation) - .buildChatCompletionRequest(ChatCompletionModel.GPT_3_5.getCode(), secondMessage, true); + .buildOpenAIChatCompletionRequest(OpenAIChatCompletionModel.GPT_3_5.getCode(), secondMessage, true); assertThat(request.getMessages()) .extracting("role", "content") @@ -162,7 +120,7 @@ public void testReducedChatCompletionRequest() { conversation.discardTokenLimits(); var request = new CompletionRequestProvider(conversation) - .buildChatCompletionRequest(ChatCompletionModel.GPT_3_5.getCode(), new Message("TEST_CHAT_COMPLETION_PROMPT"), false); + .buildOpenAIChatCompletionRequest(OpenAIChatCompletionModel.GPT_3_5.getCode(), new Message("TEST_CHAT_COMPLETION_PROMPT"), false); assertThat(request.getMessages()) .extracting("role", "content") @@ -181,15 +139,12 @@ public void testTotalUsageExceededException() { assertThrows(TotalUsageExceededException.class, () -> new CompletionRequestProvider(conversation) - .buildChatCompletionRequest(ChatCompletionModel.GPT_3_5.getCode(), createDummyMessage(100), false)); + .buildOpenAIChatCompletionRequest(OpenAIChatCompletionModel.GPT_3_5.getCode(), createDummyMessage(100), false)); } public void testContextualSearch() { var conversation = ConversationService.getInstance().startConversation(); var settings = SettingsState.getInstance(); - var modelSettings = ModelSettingsState.getInstance(); - modelSettings.setUseTextCompletion(false); - modelSettings.setUseChatCompletion(true); settings.setUseOpenAIService(true); settings.setUseAzureService(false); expectRequest("/v1/chat/completions", request -> { @@ -199,10 +154,8 @@ public void testContextualSearch() { .extracting("model", "messages") .containsExactly("gpt-4", List.of(Map.of( - "role", - "user", - "content", - "You are Text Generator, a helpful expert of generating natural language into semantically comparable search query.\n" + + "role", "user", + "content", "You are Text Generator, a helpful expert of generating natural language into semantically comparable search query.\n" + "\n" + "Text: List all the dependencies that the project uses\n" + "AI: project dependencies, development dependencies, versions, libraries, frameworks, packages\n" + @@ -226,7 +179,7 @@ public void testContextualSearch() { }); var request = new CompletionRequestProvider(conversation) - .buildChatCompletionRequest(ChatCompletionModel.GPT_3_5.getCode(), new Message("TEST_CHAT_COMPLETION_PROMPT"), false, true); + .buildOpenAIChatCompletionRequest(OpenAIChatCompletionModel.GPT_3_5.getCode(), new Message("TEST_CHAT_COMPLETION_PROMPT"), false, true, null); assertThat(request.getModel()).isEqualTo("gpt-3.5-turbo"); assertThat(request.getMessages().size()).isEqualTo(1); @@ -244,19 +197,6 @@ public void testContextualSearch() { "Helpful answer in Markdown format:"); } - private Conversation createConversation(ClientCode clientCode) { - var modelSettings = ModelSettingsState.getInstance(); - var conversation = new Conversation(); - conversation.setId(UUID.randomUUID()); - conversation.setClientCode(clientCode); - conversation.setModel(modelSettings.isUseChatCompletion() ? - modelSettings.getChatCompletionModel() : - modelSettings.getTextCompletionModel()); - conversation.setCreatedOn(LocalDateTime.now()); - conversation.setUpdatedOn(LocalDateTime.now()); - return conversation; - } - private Message createDummyMessage(int tokenSize) { return createDummyMessage("TEST_PROMPT", tokenSize); } diff --git a/src/test/java/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.java b/src/test/java/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.java index aced2cc1d..65dc609c1 100644 --- a/src/test/java/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.java +++ b/src/test/java/ee/carlrobert/codegpt/completions/DefaultCompletionRequestHandlerTest.java @@ -1,9 +1,9 @@ package ee.carlrobert.codegpt.completions; import static ee.carlrobert.codegpt.completions.CompletionRequestProvider.COMPLETION_SYSTEM_PROMPT; -import static ee.carlrobert.openai.util.JSONUtil.jsonArray; -import static ee.carlrobert.openai.util.JSONUtil.jsonMap; -import static ee.carlrobert.openai.util.JSONUtil.jsonMapResponse; +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; @@ -14,16 +14,14 @@ import ee.carlrobert.codegpt.conversations.message.Message; import ee.carlrobert.codegpt.credentials.AzureCredentialsManager; import ee.carlrobert.codegpt.credentials.OpenAICredentialsManager; +import ee.carlrobert.codegpt.settings.configuration.ConfigurationState; import ee.carlrobert.codegpt.settings.state.AzureSettingsState; -import ee.carlrobert.codegpt.settings.state.ModelSettingsState; import ee.carlrobert.codegpt.settings.state.OpenAISettingsState; import ee.carlrobert.codegpt.settings.state.SettingsState; -import ee.carlrobert.codegpt.settings.configuration.ConfigurationState; -import ee.carlrobert.openai.client.completion.chat.ChatCompletionModel; -import ee.carlrobert.openai.client.completion.text.TextCompletionModel; -import ee.carlrobert.openai.http.LocalCallbackServer; -import ee.carlrobert.openai.http.exchange.StreamHttpExchange; -import ee.carlrobert.openai.http.expectation.StreamExpectation; +import ee.carlrobert.llm.client.http.LocalCallbackServer; +import ee.carlrobert.llm.client.http.exchange.StreamHttpExchange; +import ee.carlrobert.llm.client.http.expectation.StreamExpectation; +import ee.carlrobert.llm.client.openai.completion.chat.OpenAIChatCompletionModel; import java.util.List; import java.util.Map; @@ -54,9 +52,6 @@ public void testChatCompletionCall() { var requestHandler = new CompletionRequestHandler(); requestHandler.addRequestCompletedListener(message::setResponse); var settings = SettingsState.getInstance(); - var modelSettings = ModelSettingsState.getInstance(); - modelSettings.setUseChatCompletion(true); - modelSettings.setUseTextCompletion(false); settings.setUseOpenAIService(true); settings.setUseAzureService(false); expectStreamRequest("/v1/chat/completions", request -> { @@ -84,63 +79,22 @@ public void testChatCompletionCall() { await().atMost(5, SECONDS).until(() -> "Hello!".equals(message.getResponse())); } - public void testTextCompletionCall() { - var message = new Message("TEST_PROMPT"); - var conversation = ConversationService.getInstance().startConversation(); - var requestHandler = new CompletionRequestHandler(); - requestHandler.addRequestCompletedListener(message::setResponse); - var settings = SettingsState.getInstance(); - var modelSettings = ModelSettingsState.getInstance(); - modelSettings.setUseTextCompletion(true); - modelSettings.setUseChatCompletion(false); - modelSettings.setTextCompletionModel(TextCompletionModel.CURIE.getCode()); - settings.setUseOpenAIService(true); - settings.setUseAzureService(false); - OpenAISettingsState.getInstance().setOrganization("TEST_ORGANIZATION"); - expectStreamRequest("/v1/completions", request -> { - var headers = request.getHeaders(); - assertThat(headers.get("Authorization").get(0)).isEqualTo("Bearer TEST_API_KEY"); - assertThat(headers.get("Openai-organization").get(0)).isEqualTo("TEST_ORGANIZATION"); - assertThat(request.getBody()) - .extracting( - "model", - "prompt") - .containsExactly( - "text-curie-001", - "The following is a conversation with an AI assistant. " - + "The assistant is helpful, creative, clever, and very friendly.\n\n" - + "Human: TEST_PROMPT\n" - + "AI: \n"); - return List.of( - jsonMapResponse("choices", jsonArray(jsonMap("text", "He"))), - jsonMapResponse("choices", jsonArray(jsonMap("text", "llo"))), - jsonMapResponse("choices", jsonArray(jsonMap("text", "!")))); - }); - - requestHandler.call(conversation, message, false); - - await().atMost(5, SECONDS).until(() -> "Hello!".equals(message.getResponse())); - } - public void testAzureChatCompletionCall() { - var message = new Message("TEST_PROMPT"); + AzureSettingsState.getInstance().setModel(OpenAIChatCompletionModel.GPT_3_5.getCode()); var settings = SettingsState.getInstance(); - var modelSettings = ModelSettingsState.getInstance(); - var azureSettings = AzureSettingsState.getInstance(); - modelSettings.setUseTextCompletion(false); - modelSettings.setUseChatCompletion(true); - modelSettings.setChatCompletionModel(ChatCompletionModel.GPT_3_5.getCode()); settings.setUseOpenAIService(false); settings.setUseAzureService(true); + var azureSettings = AzureSettingsState.getInstance(); azureSettings.setResourceName("TEST_RESOURCE_NAME"); azureSettings.setApiVersion("TEST_API_VERSION"); azureSettings.setDeploymentId("TEST_DEPLOYMENT_ID"); var conversationService = ConversationService.getInstance(); - var conversation = conversationService.startConversation(); var requestHandler = new CompletionRequestHandler(); + var message = new Message("TEST_PROMPT"); requestHandler.addRequestCompletedListener(message::setResponse); var prevMessage = new Message("TEST_PREV_PROMPT"); prevMessage.setResponse("TEST_PREV_RESPONSE"); + var conversation = conversationService.startConversation(); conversation.addMessage(prevMessage); conversationService.saveConversation(conversation); expectStreamRequest("/openai/deployments/TEST_DEPLOYMENT_ID/chat/completions", request -> { diff --git a/src/test/java/ee/carlrobert/codegpt/conversations/ConversationsStateTest.java b/src/test/java/ee/carlrobert/codegpt/conversations/ConversationsStateTest.java index a1a71710b..3f1c1fb01 100644 --- a/src/test/java/ee/carlrobert/codegpt/conversations/ConversationsStateTest.java +++ b/src/test/java/ee/carlrobert/codegpt/conversations/ConversationsStateTest.java @@ -4,29 +4,30 @@ import com.intellij.testFramework.fixtures.BasePlatformTestCase; import ee.carlrobert.codegpt.conversations.message.Message; -import ee.carlrobert.codegpt.settings.state.ModelSettingsState; -import ee.carlrobert.openai.client.ClientCode; -import ee.carlrobert.openai.client.completion.chat.ChatCompletionModel; +import ee.carlrobert.codegpt.settings.state.OpenAISettingsState; +import ee.carlrobert.codegpt.settings.state.SettingsState; +import ee.carlrobert.llm.client.openai.completion.chat.OpenAIChatCompletionModel; public class ConversationsStateTest extends BasePlatformTestCase { public void testStartNewDefaultConversation() { - var modelSettings = ModelSettingsState.getInstance(); - modelSettings.setUseChatCompletion(true); - modelSettings.setUseTextCompletion(false); - modelSettings.setChatCompletionModel(ChatCompletionModel.GPT_3_5.getCode()); + var settings = SettingsState.getInstance(); + settings.setUseOpenAIService(true); + settings.setUseAzureService(false); + settings.setUseYouService(false); + OpenAISettingsState.getInstance().setModel(OpenAIChatCompletionModel.GPT_3_5.getCode()); var conversation = ConversationService.getInstance().startConversation(); assertThat(conversation).isEqualTo(ConversationsState.getCurrentConversation()); assertThat(conversation) .extracting("clientCode", "model") - .containsExactly(ClientCode.CHAT_COMPLETION, "gpt-3.5-turbo"); + .containsExactly("chat.completion", "gpt-3.5-turbo"); } public void testSaveConversation() { var service = ConversationService.getInstance(); - var conversation = service.createConversation(ClientCode.CHAT_COMPLETION); + var conversation = service.createConversation("chat.completion"); service.addConversation(conversation); var message = new Message("TEST_PROMPT"); message.setResponse("TEST_RESPONSE"); diff --git a/src/test/java/ee/carlrobert/codegpt/settings/state/SettingsStateTest.java b/src/test/java/ee/carlrobert/codegpt/settings/state/SettingsStateTest.java new file mode 100644 index 000000000..cb2a3c90f --- /dev/null +++ b/src/test/java/ee/carlrobert/codegpt/settings/state/SettingsStateTest.java @@ -0,0 +1,55 @@ +package ee.carlrobert.codegpt.settings.state; + + +import static org.assertj.core.api.Assertions.assertThat; + +import com.intellij.testFramework.fixtures.BasePlatformTestCase; +import ee.carlrobert.codegpt.conversations.Conversation; + +public class SettingsStateTest extends BasePlatformTestCase { + + public void testOpenAISettingsSync() { + var settings = SettingsState.getInstance(); + var openAISettings = OpenAISettingsState.getInstance(); + openAISettings.setModel("gpt-3.5-turbo"); + var conversation = new Conversation(); + conversation.setModel("gpt-4"); + conversation.setClientCode("chat.completion"); + + settings.sync(conversation); + + assertThat(settings) + .extracting("useOpenAIService", "useAzureService", "useYouService") + .containsExactly(true, false, false); + assertThat(openAISettings.getModel()).isEqualTo("gpt-4"); + } + + public void testAzureSettingsSync() { + var settings = SettingsState.getInstance(); + var azureSettings = AzureSettingsState.getInstance(); + azureSettings.setModel("gpt-3.5-turbo"); + var conversation = new Conversation(); + conversation.setModel("gpt-4"); + conversation.setClientCode("azure.chat.completion"); + + settings.sync(conversation); + + assertThat(settings) + .extracting("useOpenAIService", "useAzureService", "useYouService") + .containsExactly(false, true, false); + assertThat(azureSettings.getModel()).isEqualTo("gpt-4"); + } + + public void testYouSettingsSync() { + var settings = SettingsState.getInstance(); + var conversation = new Conversation(); + conversation.setModel("YouCode"); + conversation.setClientCode("you.chat.completion"); + + settings.sync(conversation); + + assertThat(settings) + .extracting("useOpenAIService", "useAzureService", "useYouService") + .containsExactly(false, false, true); + } +} diff --git a/src/test/resources/indexes/hnsw.index b/src/test/resources/indexes/hnsw.index index ef62213439033fb818dfe64717b31317057127d3..d8d1dab43fcba4f888318b445cfb2504e52e6b95 100644 GIT binary patch literal 13779 zcmbVz2{cvh_x~lOK@%yI5SmSe25B2olu9KH#*2$<9|uX)UrMh!}_g(RBm zZK4R3G><9`KXuOWe*eGq|E~34e{1#LUfXS*N3SvV zaCh8F_tY|`iP|%LSaf&0(FePE(!J>{4{sK2wy`takFMg&V!EpQ`~B$EGpMUK`nvyp z5{tIPcy#eph{aPO7f)q$@l-aDH>NHf9X^@i&hid4@nE{MShVH;ZQ!3(R~cJ%g>9p& zGCFn=BYF)MSdpe8Z!=fqtw;TPPx*n6Y zi^2Ny-j1#wwscox4?7nYZRvk3j-7|Qov*j|=(JQ^NB`)fvXk*=igfqU6}EF39XW$J zx?x84yBOX~x+^n?F}k?_b-{?O`S;S%;nV&M|Mwd&S{QAjF>U6^;mV?^{I|FH zyNjC`?xU-3N38Cj0a^i`{^JE`vcfylKn-oEzmok5&G6%03F)=qz7vy6bjtdxrPF-;;xBqN9VoY194=b)~yI zDjT@_y8Y)LNB480(Y9Tty4&;(Mqhc^e|hE2qtE`|G4y}DpwFN95%*s*`sL{KX_Lu+ zv!JUpSbX8rspRkNqo02-5Ma?nObvFKTI+A$W@@n4)L-JyU&x<7clh)3_jB~EBpD24 zJG!^4w}sZ(Y!9uen{aRTLI{8h&KjT7q%8e)ZKe;?O7S#Qgnx-a+(8+_QD3k7LoK!2JWJ zt{6&O2j22EVeW&XP;{j-PWRCkh#KC!{@%MCkYWG4V)6AtjHxiOjhEhovE%M0wP;u) z+_@ZavEVhPDm}H3NRL9z#0eYMeEo@mTT_~T%@#n8{)4W=-!DRNU9qZx)&i{7SS;Xs z>Jiw#)E{9P*kESPyT+bpd%jvtrn`xEDor3&|>at#?6yzFi(2nYohJc{CjlL~sFzed4Zy#s% zV?MrVEjT;~UcwV)B`!rny76MRdLRp8K5{eGnaW`nbH~%tY(>mnB6nt}_#^Osf0z~T zvke_n_4|r`yJB|G-1b&}HP{Gt#Cm&)V)RD`k=>U4m^=_?`RuhK@+v~U7s!O7aZS#X zz8gBo*)sA%#(x{IUTzTF6}ktk)V{didtV0uwE4Y@a#S%dvZc^hssf4yJE8>tnT;Vy zqKea{!yweXU8%e26BO;ga%i^sKJf41%cxti&;^^vZ8aZ8-E_k)&yQ}9X@bdH9Unnd zM_RVDdJ?ADzKQvHQW1i*{Yvh1WT2yLVEjOvek6-%g>R+m}Ccp;QYX{;D*XJ zD!x4i6{?(HAFnz?VrTp654;h~?y5b{->?aM#4@c7@(+x~QHIl3_8ZT6m{_TD?4Df( zCinVTUsS(~JYqb?i6~Bcoxcv!S2p9PExUljDvwBXe~1CNP8HednC?`h!?S)0r7Pds z6z_7!wB8@)sn!!9d8fk3Nll4xU?lEQ`LT20f7^bI>mz4OFG zNk_xf1v;XzMCJH}TN&e!w|hx_jzSP7ypp$~ugpYtc|k+)(>YKfyHzh$yc^QUbD7n6 zTE2Zt02%sa4E^*S254PG!Ax`Lc9t@_j28%;)x4QOM#X})jKlnc9z2q^pANCuR`b9_ou2?zI3I*Kr@#WKIPu;QeAF;$G&*EJ$ zdOc2(ZN4Q9W$Uzmrd?T#38@959plboUgFb)#Bd8p6iQIN?p6fx#QA`19yfkrm=q=v z>yMF->z+n7O+r3SXULi#MJn#{>#o!-SF^^Hjq_vl8$yBCDW37n>KtaeX)f8N84R)Y zHyh4nZHLG=BRM;~O2Ixf@W(W}m*CA-%X!IB!gwPS(fT1;PIATdj*O3CP>< zbimzw8am##-?VJ&9^h!55ojy5-Y zz`m)|3+cD^>OM@E3~|rTPCaX_2sIjPy{m1rfPc?-p-sXKOkBe_?iINLVu<*|)Yg4M zbNt+)YL-vg#_58X{qjrMJIOqx&T~Bd)YHW~P668{)ZwVG1msT@vRv1f1Z77g?w{Kx zg)Rmc6@IIEf=AMj{H{~uHP#`pGOsP_ZGmL);p`7FX( zG5He2t$Ua5_B9+*^NyN_c@IMpUxJ>&2>=(PyYjLxnlRU>Ixc@p0mheJv{6Z01wm!= z4-9KDF{NEwGk0PadRDW4o`3F*Sqz=$5k3E4oPlV{aprvFk#R}oonRtwK;`vaT3ZyQ zfZrl(rziFcb1i?ZNj?>eYcb0@;czcF-yYJ?6wbl)$4Qd?xeKu%YF^LPrQJ{|vFF*B z!+W9Nj;HExwabv*Vmz{Ewjy$>BL$X6xuKo!vK01#j}Rd?&Ds2*D_WRo6r|kvjG2K< z`7_qe7%qNj#cM_a=4qF0s#;eCg*(G0q;(&}tXyfQ1)rR-2K8#XC$9=Th0v~ z6s+du4O&3q&yA5?`X*!bI@@iqLCy9S_#B)_V;E&aqV4omN8J8_N&>Gyh;Hwr$HVh6 zhRC0g@piHOJAO2j<+RRU`B)oW@lE?wqkargbXKXE#{9`O|{)#xz2USoo?3HG))UDiTnbwQ@%0|rY?t&x+|?iC0ik@e_O-M z#M2o6bxF!1%@QbSo@FO-_8{foNl6?gHTGKA@^Rb_~+hpf?H{1SJ6JJQ2aem_cuydF~*8htBUOnOTj%6?4puIdT>Y_WKyxt~9~C zu-f8Xwq}@FD9|8KSP87XPpo&xp8&sk>$LvalZ_GOp6|O+ox)vs#JL~W2;Bpgg3wd$ zq1ou(y7%Ym_5R>v|2*@1%PH8aI%MA75Q1^@oSsTBs*p#lE5@E~dh+?xEsV-(70Jz*9y7V9Msd;3Am^zQnE z)h@`bmytMXIUTt+2d94!Uko869u7_0-gwIA9cJ!*;-KDC4Y8`}GvprZ!btfV_T%WO zSaEw*$(}BKj7fNq{&ndO$tLq3IbX2775!40F9n&?kI^LNdqY9fT#MRc z(;-;*q4VPIy=!^|-pTtehk+;%0XKTKsM=GOVd2`Y(VzEGddH?P-N=>F;A z`u;~JgQs|7HT79ba1?FjKLTSa0POheC*OO{XU zN+Gtf@7$xUMacMlF{LDQ1|}-4s1wYvBznE&dY3}YQt$i4o+P|tiNvJR?eO+MNRIbA*Rn$Alw&|N(KM*K-ACGZ0HlJB1# zw){Zi>V?QAaNbzmiQ8>v*S#VUUAKvyzoesv+>atvB^nPQhljFS5xN+9ezFy3xDjGh z?HwL>>I3WARbYP~g4DtHM^Z0jLABC^`NMnWK?Z^Ipj=iqJm&Ko;D^yfd^Kt4OX6xO z-;n1UJsa+}ZvF-s^?ZWU;e}5zm&_+jVqd|dop+!>=q>~i^#t657gZTmEyZlh@=3PG zR|B8G!9d~D=>7#_*Fq{FU)pfNn&N2?e`1o@FP$40H*3=rHIXi4%ecL)yv)aH1DE%V z^hJ;@5nbYVX$OSAR$0mSTmU+AWIQZ#k7I;jhu59UN|tZ50E;KDI||I5&87Y z=+RH`>d>T%ecgnSzaiq_#$k+h)09wL%Ect_@Va>hJ0N#!y&e74TqvmfX5#@Kz$N#C z6aB)me}gJUXcYQ52xMai-SJ@!$2U1`cE7M-jfylJ~$pJ#F2K^IUYF>0UWL zTnM746p1N#iy%jOb<3_E8|3EjL*wqs0mWZ91m6aiyd@5q5eRI-;kk3}X+y@XmS$&in7e^T zx8Rm)@E}imZlXfLAjDY)XtyMpf!&;P-63BIq}H9n2P8kk;*~rZL;Y2lJX3s)VAMqP zJD56dYE2_VPBCaI-2Do|?>}T$PFRDHWc}wr`|rX1hUmFzXy|9-Ph?b>m{&cu29p0G zOXlNq&ySao%^ojsH+2F8ui>uV8{h%dJoPOumYo)xi2EaXzax6%F`nEXD$e+B=N0GK z)Ii7`{kZe;XEE)}&f3rBX;7?|!Y`)Jg-GMwl19OKm~XYGK<7~!=ASs%u{!!2L<^^i z3!Xg>xz1_}T5cs{VfgC&dC9S$wz9eHc9apN^I+8UD5r7rwjiHt+Iyky9Oio;va#Mp zADe#}!N11xa3QI~8AwbIa`W ziy>|2!K1f^>fyjk{a^i0%z=B&=hu><6A-xjXsc{vHuAl$%CDCgp>zz$7%^Y>t*Qi5 z--{i*wPGbz*U(aYXRo6X6SH&6>|V|1kMVAN0622wI?P7Qw=)g7EVUp#R8j9Ug>YMxoUV~m#%CcOKAM`pU0Z#^yy{Fj%%AD)tg zNlUibDjgjMQ6%ojtV_Z|v(sQa8Y~yl$ZjY+*P=;vRCd^Ks1D+_rPm&xkcQUdop$%zFGO2H-$Gx@`M5BwR_>Wa>Q#&-<`4RB7hBD=P5~|vuMn>1@SsX=8`>P^jyx{9fO!taJ!0w_5Ike> zfaSc$7_Rtk8Ea!3<{P{}dw`#ZiSBx`PZPT^&pW(*^A4>sou-gqXlYSj1rfiV!A{L4 z$l)GYAbIHnMv!?bOQ%YEQsxmXChKivU;70^n^;VHF<-;C?iP}|2_%qsBZH_{WA)L8 zZDmyxCJ6^S+jSH2-+_<%+@IZ;3hBQ^2eUTZfJ6<`Qn`k2WBHWA3moy;FI;0Y8ovlX zx7~hcGiLA0_%8aIhJFmU!br>8827ScO4l3~Miu-c*2v*Qq{?F#@e(miC-oQ#M|g^S z>T#|R#;9huMvX26{kE_c%_~CSIh1a{FmxIwlKS9o-{{uFC#%pe^tRoKMei^xroCJ! zj0ZNfW?_2f5N1;Qv#R2Q-=iid3TGHt=kk!MGYQ1H0XJb+rKQMeia%kQ{qutK%LgHu z;6IoaY^pi^t`#b%8)Em*P>g^6^z;um4W!T6@^eA^GmJ9fHXYF&fmlKh1unslFoMv9 zsCq+#z%$bQ)w?e6TkQ_=c$tw;py|F(7AFv6lJFB~+Y3iQw>ZgZ_n(5#yn2VwQrz zVYOw=zjh+|{t$Nma7&xX1~ehhho4t*(vsPCpfXnO(Z%@tzz+HNA@=e$j7XWH+mZhR zNSz&RojSXvehGua?Ny^yY7?fBai3KiwKiSZ8*&U>dRN7)hsv&4g-AVFOd#`VT9n_4 zT^oO3x{TYyz=k837|EYtBM}baay?2rQ~lBGNa4LtvkWk;HRkaC=rD9YDK4w5Jqx3V zx;e(fDE;!l-Y1p?^`{{8#9(xF;RgtHs8Gu>*$sJvE+zb=5bz@THp{SpGig{GNqz_X z#1#W!o91Djv*jc8S24i$ydV{NrWONIJ(K#UhG6;W%tg@;A3%m!rh~$rJPeEtUfp-_ z^_c$W?%dzN)_ed_4{vi0h+l^4iEef~X(E`b8<({A;V<;(FIg^Sc`{yG}}e+xUr?ajm%0^=k`85%nGt30?stPC?sc zSJ~;DDvTp>rd{AVTc@c()P7>9_KyUbaSvK0xy0Q5cz9IelPF~B1RwasjYoI?+4;=ySKwaR zr)%&@5DUe_bMM>PLVCL43l%$aOxdhLpJe$Rd2f%bx2%2w*`#itdvr%2=U5{0mmfN( zuOf!ovK7JGW#@y-piAMRw-Hbv7jwpQe+)#G56=)3ScNG|Do>v1l!r2se^T*ItzTwa z4I{U7J|w1Ie#~5wPt|MW5PBMrbpg1f{+UhiK&&9~RwTju!NTBuQ`GegO2@>Cmj$-j zlkCB^yu-TZoC<9FrosN!B?NI5)!bUS17Jt$dL>gNWj<`ZiA*xi3u@Zp9h7%r6v;n4 zS>;jRC*Os*9R`g0ptTsNyT4ET${S47_B)!fmW}CRYv1b?SA(b6G`kRQ7Vt`4egr!z zW7y#P;u||Eac`Z}!iEwd%*J&x>AqE9Pvk}95a$vrhkx8UJ+BH222Khu8PR|u$p$^` zB^NM~;G399;3~{yDz39m6sPb#lDq_SZZ`zAzAgs4=}~m~fq&0WIlHc+uWci`-dK^4 z>*9>L+=unb9g>j#q35^Nq7Dr88=?(7>432A#tVZJGm#T=X}{N{N{lOvJJZpA4|r~A z!4G!kVd|BjN58IXK?Msr3;{jwJ<)Cy>KKA*=N@*JmJ*`|K(!ZOU$J!x?H(=en@kzAax>mn8% zQyp42?GPksmuUv6BF1ysi9#1Uk-s>vLC&uQ3N?i{DScT3@uc1qzr$N?U$HcBHVr+v zeqt76r3`T-`;8%n@E5SK=A+&{L*p^ss=RA?|5CLV7)|a|I)VSN{P)F}$^LUOWs>Yf ztJTI>Na}EQksAuKoYd*g?@S+{ks$6?g6@3S?(+=jpt8a)LL zwPU=uq%U7${7ygQu?N-Iu^XTJI>AJ4*L9dl__M;!V=+$ayPO@^>&gkd$DfG zgjIcou<+GI$&1-=g?2MwhanprUKW_2x#^Cf;UYr9iPlg`>QeMJMcWTB48>bU?!9-h zALEhHq>g1mo~vTQw}Mnkx2Ww-nX{RTVdJ$fX#X4mI>8?xip1r?q#s1pX_6OX={Whv zGQ-QTWJF-dCz%aqv!tA^YBp260_-;=M=!ZP>O+${c`1Q^An4x6{CWu?T9d*v zXiy4v6H+Dt^M~WYt0|(ehgq|F@ks-WCiFFM9f-4UQ=A7>yl0TURZ3CDF-z%HFuVTd zg)ZsMK;n3acvH$;n=zT>wE=QsG)X4`LnF+`}{&Ri1kRC*(q-$qfS`Oy!?)aef z8%9j%Vk@LLK)QXZ9;-47Q)s`G{dywMKC9^_}Px=4_&UGs1CYD3F zy6>)A{?))E^iFWon;N%ZyfWDJeGXqTI|!)r?jwJH-S$FZq;vr??m^QF zW^NAz5b=$^>b_QTTk?>KGpq4h^;T`;Fqf!X5Kt#2^O!jmskqKn(?38Pe-IML`sTgp ze3Zxiy%<~SIBv&WTU7&&jZ@F_nR z?_?syGkojI7ibPEVPuMZrpeiP7)Rz^%8xJf8DIOT4I?nTZw} zhkt!JG6hqkjA~p>5Uu0l*01zfi;G5kt#%(7k7bVsLix?AP)z!lBoD@XzJbz;)BBM} z@{{~-2E8mK4&!@T>d*Y(VOm=C@-rSUF_4F9%;cqz{Z6=a=FK6@TE}1iyxSU6N&UW9 z_h#5PO%L=Z_$82df2=3P?Pd_FhNtx%z(OkbKkkAbtJThC5JbmL_yO z^BaiZ&yaM{Ai|K}AI5(K+ivX%$X8kdt1tQTGvEZj7X5EFd4@UJVg0DiH+j+4!rp=hK_vHEawK)*4P?uOZU5Syx(dfk;tl$=M4_CphQ0G$%^2QWBn4OUW%DhiWGK+eugxX&yhYNa!7r} zmGGm`ro?967qcV4Cw(wVpP~9b*re`NU}kIX9{U24J{2Zjt72mY=|eN>%jfPvO-v#3 zH~P3uiVAOaL`tW1vx?T(-E$h#2)_tIySYkwrA%NF`ZSah>k59)lkNR>h(iY1&qd-j zj8NQ<{O!Mx3p2alMTy@`(4sW7ni=%C2SNf#)mb=j%MC zd@-o#aZXJAY6MQiIRRhNw*A&}Y>YU2aWH1@1u%0<3paejhb+QZ z!E~a30Q1Om?_J%c7oBkcV+j9u%=gWw-Hcgz(-Xo!?@)Qz+k|9)2`2VRtK_N1LzU`~ zZRKYlEIs^6r((-TOd}mJ>tu!$I1e%TX~~UqJNB4KrrG-T*w(r{HGA zax#~kL#p47)DOWUoT2;nSKgRE9~U`uGVjfHAbspHeLvTBx?y)9Vj-dDf(fBlQaB&I z$UZ5`7bD*b_++1*Pv7U{=dn&m<@fkHDN&hkGRTRzzc(ZN3sC+wr9)A;%9Zf>fvq)= z({(@w3%a@UX7M7%`k)M%_uejDp*QAt#PwInUaUo+@ z4!kvso_v@J)On92_nqX8sIgd&{k1j*xH7pF68#(~Ci^PL_(sa-_ns4wUFkRp{HA=h z>NL{;FTr8C;pyr?`pb|t=T~`Q!2&{|4g--vCIR1&fHjHncf)vb$V0dW`>5{ZH{kU59Xf$L}W21>_&;480V) zA1VI7iKs)6yiM%X;6yD5A${nAcI{2=vsxi7E>5>>iY658JXm<(@lE75sxJC1X9%{# z(q+4LDg%lC#`=*+KMZ4a?mROtZGq@1^c(Vg-zWGFkkz6 zPs94_m^ZQQ(uBq0;2HgbE+n!V^Ceel7<{Y(Uh14R7Vj+}jp&;NlE*-FeLzZjx&Wm3 z4GFhOUO+cer_)l@j&+(Ai{X+D>#wG?VNT^Z;mqAUj3oOT(@7kt!Wb}OwJ{uH{p-N|(GCtZszEbie zuCLvgUPtzoVhPcQ3^BgZ4K>xeWBRfe*s~&CF znvd}$Z?PlmrP+*0&NX4)$R&N=U{co*c~xzYe5x1A1G8G~9b=Hv8s><@pF+Ava`gPJ8!9FY|{b7FRs2t_miMbB48-J~sHr9`ub1AX( zTz3pmelWM>tW&|-dl>d!tn9;g?J@pL_3u$U(p7Gse8;DKkWTu{*_SN81zht5iq~?= zy~a)MoDVsP$*Y9y)}fZ<!(l1FT^c+ZfmvVH{L?Ngk-}iu~__1j1LTL_chooa{YZs{VuTZiICS} c8rTs!4P=Tn{aSOk8OZ)V|M7qSKZAh(0ax(n@Bjb+ literal 13801 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