From 84840c5fbcf24bfaab1d81724c1526546460b911 Mon Sep 17 00:00:00 2001 From: Andrew Charneski Date: Wed, 15 Mar 2023 23:53:02 -0400 Subject: [PATCH] 1.0.16 --- .gitignore | 3 + CHANGELOG.md | 10 +- build.gradle.kts | 8 + gradle.properties | 2 +- .../aicoder/config/AppSettingsComponent.java | 97 - .../config/AppSettingsConfigurable.java | 80 - .../aicoder/config/AppSettingsState.java | 141 - .../simiacryptus/aicoder/config/Name.java | 9 - .../simiacryptus/aicoder/openai/ApiError.java | 25 - .../simiacryptus/aicoder/openai/Choice.java | 24 - .../aicoder/openai/CompletionRequest.java | 148 -- .../aicoder/openai/CompletionResponse.java | 39 - .../aicoder/openai/EditRequest.java | 137 - .../simiacryptus/aicoder/openai/Engine.java | 37 - .../openai/InteractiveCompletionRequest.java | 69 - .../openai/InteractiveEditRequest.java | 64 - .../simiacryptus/aicoder/openai/LogProbs.java | 27 - .../aicoder/openai/ModerationException.java | 7 - .../simiacryptus/aicoder/openai/Response.java | 19 - .../simiacryptus/aicoder/openai/Usage.java | 14 - .../aicoder/actions/code/CommentsAction.kt | 27 +- .../aicoder/actions/code/ConvertFileTo.kt | 5 +- .../actions/code/ConvertFileToLanguage.kt | 6 +- .../aicoder/actions/code/CustomEditAction.kt | 12 +- .../aicoder/actions/code/DescribeAction.kt | 12 +- .../aicoder/actions/code/DocAction.kt | 7 +- .../actions/code/FromHumanLanguageAction.kt | 8 +- .../aicoder/actions/code/ImplementAction.kt | 9 +- .../aicoder/actions/code/PasteAction.kt | 5 +- .../actions/code/PsiClassContextAction.kt | 6 +- .../aicoder/actions/code/QuestionAction.kt | 22 +- .../actions/code/RecentCodeEditsAction.kt | 9 +- .../actions/code/RenameVariablesAction.kt | 13 +- .../actions/code/RewordCommentAction.kt | 12 +- .../actions/code/ToHumanLanguageAction.kt | 8 +- .../actions/code/TranslateCommentAction.kt | 6 +- .../actions/dev/GenerateProjectAction.kt | 61 + .../actions/dev/MarkdownContextAction.kt | 12 +- .../aicoder/actions/dev/PrintTreeAction.kt | 6 +- .../dev/RecursiveToStatementListAction.kt | 44 +- .../aicoder/actions/generic/AppendAction.kt | 6 +- .../actions/generic/DictationAction.kt | 26 +- .../aicoder/actions/generic/EditAction.kt | 4 +- .../aicoder/actions/generic/InsertAction.kt | 4 +- .../actions/generic/RecentTextEditsAction.kt | 7 +- .../aicoder/actions/generic/RedoLast.kt | 2 +- .../actions/generic/ReplaceOptionsAction.kt | 9 +- .../actions/markdown/AnnotateTextAction.kt | 13 +- .../markdown/FactCheckLinkedListAction.kt | 6 +- .../markdown/MarkdownImplementAction.kt | 4 +- .../markdown/MarkdownImplementActionGroup.kt | 4 +- .../actions/markdown/MarkdownListAction.kt | 4 +- .../markdown/MarkdownNewTableColAction.kt | 8 +- .../markdown/MarkdownNewTableColsAction.kt | 8 +- .../markdown/MarkdownNewTableRowsAction.kt | 4 +- .../actions/markdown/ToStatementListAction.kt | 4 +- .../actions/markdown/WikiLinksAction.kt | 11 +- .../aicoder/config/AppSettingsComponent.kt | 111 + .../aicoder/config/AppSettingsConfigurable.kt | 61 + .../aicoder/config/AppSettingsState.kt | 156 ++ .../simiacryptus/aicoder/config/Name.kt | 7 + .../simiacryptus/aicoder/openai/OpenAI_API.kt | 645 ----- .../aicoder/openai/async/AsyncAPI.kt | 279 ++ .../aicoder/openai/async/AsyncAPIImpl.kt | 34 + .../aicoder/openai/core/ApiError.kt | 26 + .../aicoder/openai/core/ChatChoice.kt | 11 + .../aicoder/openai/core/ChatMessage.kt | 17 + .../aicoder/openai/core/ChatRequest.kt | 9 + .../aicoder/openai/core/ChatResponse.kt | 32 + .../aicoder/openai/core/CompletionChoice.kt | 25 + .../aicoder/openai/core/CompletionRequest.kt | 107 + .../aicoder/openai/core/CompletionResponse.kt | 48 + .../aicoder/openai/core/CoreAPI.kt | 425 +++ .../aicoder/openai/core/EditRequest.kt | 121 + .../aicoder/openai/core/Engine.kt | 51 + .../aicoder/openai/core/LogProbs.kt | 33 + .../aicoder/openai/core/ModelMaxException.kt | 10 + .../openai/core/ModerationException.kt | 3 + .../aicoder/openai/core/Response.kt | 18 + .../simiacryptus/aicoder/openai/core/Usage.kt | 12 + .../aicoder/openai/proxy/ChatProxy.kt | 87 + .../aicoder/openai/proxy/CompletionProxy.kt | 46 + .../aicoder/openai/proxy/GPTProxyBase.kt | 210 ++ .../aicoder/openai/proxy/SoftwareProjectAI.kt | 76 + .../translate/BaseTranslationRequest.kt | 1 - .../openai/translate/GPTInterfaceProxy.kt | 118 - .../openai/translate/TranslationRequest.kt | 8 +- .../translate/TranslationRequest_XML.kt | 5 +- .../openai/ui/CompletionRequestWithModel.kt | 26 + .../aicoder/openai/ui/CoreAPIImpl.kt | 17 + .../openai/ui/InteractiveCompletionRequest.kt | 73 + .../openai/ui/InteractiveEditRequest.kt | 68 + .../aicoder/openai/ui/OpenAI_API.kt | 174 ++ .../aicoder/util/AudioRecorder.kt | 6 +- .../aicoder/util/ComputerLanguage.kt | 2 +- .../aicoder/util/LoudnessWindowBuffer.kt | 6 +- .../simiacryptus/aicoder/util/StringTools.kt | 13 +- .../simiacryptus/aicoder/util/StyleUtil.kt | 12 +- .../simiacryptus/aicoder/util/UITools.kt | 393 ++- .../aicoder/util/psi/PsiClassContext.kt | 20 +- .../aicoder/util/psi/PsiMarkdownContext.kt | 1 - .../util/psi/PsiTranslationSkeleton.kt | 12 +- .../simiacryptus/aicoder/util/psi/PsiUtil.kt | 8 +- src/main/resources/META-INF/plugin.xml | 8 + src/main/resources/META-INF/pluginIcon.svg | 2321 +++++++++-------- .../com/github/simiacryptus/aicoder/DocGen.kt | 1 - .../simiacryptus/aicoder/ProxyPlay.ws.kts | 22 + .../github/simiacryptus/aicoder/ProxyTest.kt | 84 - .../github/simiacryptus/aicoder/UITestUtil.kt | 8 +- .../simiacryptus/aicoder/proxy/AutoDevelop.kt | 101 + .../simiacryptus/aicoder/proxy/AutoNews.kt | 141 + .../simiacryptus/aicoder/proxy/ComicBook.kt | 149 ++ .../simiacryptus/aicoder/proxy/DebateJudge.kt | 128 + .../aicoder/proxy/GenerationReportBase.kt | 54 + .../simiacryptus/aicoder/proxy/ImageTest.kt | 17 + .../simiacryptus/aicoder/proxy/ProxyTest.kt | 106 + .../simiacryptus/aicoder/proxy/QATest.kt | 26 + .../aicoder/proxy/ScreenPlayWriter.kt | 138 + .../simiacryptus/aicoder/proxy/ShortStory.kt | 120 + .../simiacryptus/aicoder/proxy/VideoGame.kt | 167 ++ 120 files changed, 5390 insertions(+), 3198 deletions(-) delete mode 100644 src/main/java/com/github/simiacryptus/aicoder/config/AppSettingsComponent.java delete mode 100644 src/main/java/com/github/simiacryptus/aicoder/config/AppSettingsConfigurable.java delete mode 100644 src/main/java/com/github/simiacryptus/aicoder/config/AppSettingsState.java delete mode 100644 src/main/java/com/github/simiacryptus/aicoder/config/Name.java delete mode 100644 src/main/java/com/github/simiacryptus/aicoder/openai/ApiError.java delete mode 100644 src/main/java/com/github/simiacryptus/aicoder/openai/Choice.java delete mode 100644 src/main/java/com/github/simiacryptus/aicoder/openai/CompletionRequest.java delete mode 100644 src/main/java/com/github/simiacryptus/aicoder/openai/CompletionResponse.java delete mode 100644 src/main/java/com/github/simiacryptus/aicoder/openai/EditRequest.java delete mode 100644 src/main/java/com/github/simiacryptus/aicoder/openai/Engine.java delete mode 100644 src/main/java/com/github/simiacryptus/aicoder/openai/InteractiveCompletionRequest.java delete mode 100644 src/main/java/com/github/simiacryptus/aicoder/openai/InteractiveEditRequest.java delete mode 100644 src/main/java/com/github/simiacryptus/aicoder/openai/LogProbs.java delete mode 100644 src/main/java/com/github/simiacryptus/aicoder/openai/ModerationException.java delete mode 100644 src/main/java/com/github/simiacryptus/aicoder/openai/Response.java delete mode 100644 src/main/java/com/github/simiacryptus/aicoder/openai/Usage.java create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/GenerateProjectAction.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsComponent.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsConfigurable.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsState.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/config/Name.kt delete mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/OpenAI_API.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/async/AsyncAPI.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/async/AsyncAPIImpl.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ApiError.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ChatChoice.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ChatMessage.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ChatRequest.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ChatResponse.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/CompletionChoice.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/CompletionRequest.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/CompletionResponse.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/CoreAPI.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/EditRequest.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/Engine.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/LogProbs.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ModelMaxException.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ModerationException.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/Response.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/Usage.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/proxy/ChatProxy.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/proxy/CompletionProxy.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/proxy/GPTProxyBase.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/proxy/SoftwareProjectAI.kt delete mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/translate/GPTInterfaceProxy.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/CompletionRequestWithModel.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/CoreAPIImpl.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/InteractiveCompletionRequest.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/InteractiveEditRequest.kt create mode 100644 src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/OpenAI_API.kt create mode 100644 src/test/kotlin/com/github/simiacryptus/aicoder/ProxyPlay.ws.kts delete mode 100644 src/test/kotlin/com/github/simiacryptus/aicoder/ProxyTest.kt create mode 100644 src/test/kotlin/com/github/simiacryptus/aicoder/proxy/AutoDevelop.kt create mode 100644 src/test/kotlin/com/github/simiacryptus/aicoder/proxy/AutoNews.kt create mode 100644 src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ComicBook.kt create mode 100644 src/test/kotlin/com/github/simiacryptus/aicoder/proxy/DebateJudge.kt create mode 100644 src/test/kotlin/com/github/simiacryptus/aicoder/proxy/GenerationReportBase.kt create mode 100644 src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ImageTest.kt create mode 100644 src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ProxyTest.kt create mode 100644 src/test/kotlin/com/github/simiacryptus/aicoder/proxy/QATest.kt create mode 100644 src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ScreenPlayWriter.kt create mode 100644 src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ShortStory.kt create mode 100644 src/test/kotlin/com/github/simiacryptus/aicoder/proxy/VideoGame.kt diff --git a/.gitignore b/.gitignore index e1b6f5d9..2a8b33f6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ openai.key .run chain.crt *.pem +api.log.json +*.log.java +*.log diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e7f43c4..c36bbd11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,18 @@ ### Added - +## [1.0.16] + +### Added +- Completed transition to Kotlin +- Improved "max tokens" logic +- Added new proxy api feature +- Added "Generate Project" action (beta) + ## [1.0.15] ### Added -- Improved logic for dictation action, particularly silence detection and prefix handling- +- Improved logic for dictation action, particularly silence detection and prefix handling- ## [1.0.14] diff --git a/build.gradle.kts b/build.gradle.kts index 295c20e7..15632859 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { testImplementation("com.intellij.remoterobot:remote-robot:0.11.16") // https://packages.jetbrains.team/maven/p/ij/intellij-dependencies/com/intellij/remoterobot/remote-robot/ testImplementation("com.intellij.remoterobot:remote-fixtures:0.11.16") testImplementation("com.squareup.okhttp3:okhttp:3.14.9") + testImplementation(kotlin("script-runtime")) } @@ -42,6 +43,7 @@ kotlin { // jvmToolchain(17) } + // Configure Gradle IntelliJ Plugin - read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html intellij { pluginName.set(properties("pluginName")) @@ -72,6 +74,12 @@ kover.xmlReport { } tasks { + compileTestKotlin { + kotlinOptions { + javaParameters = true + } + } + wrapper { gradleVersion = properties("gradleVersion") } diff --git a/gradle.properties b/gradle.properties index c93de159..203cbae6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ pluginGroup = com.github.simiacryptus pluginName = intellij-aicoder pluginRepositoryUrl = https://github.com/SimiaCryptus/intellij-aicoder # SemVer format -> https://semver.org -pluginVersion = 1.0.15 +pluginVersion = 1.0.16 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 203 diff --git a/src/main/java/com/github/simiacryptus/aicoder/config/AppSettingsComponent.java b/src/main/java/com/github/simiacryptus/aicoder/config/AppSettingsComponent.java deleted file mode 100644 index d20f6913..00000000 --- a/src/main/java/com/github/simiacryptus/aicoder/config/AppSettingsComponent.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.github.simiacryptus.aicoder.config; - -import com.github.simiacryptus.aicoder.openai.OpenAI_API; -import com.github.simiacryptus.aicoder.util.StyleUtil; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.ui.ComboBox; -import com.intellij.ui.components.JBCheckBox; -import com.intellij.ui.components.JBPasswordField; -import com.intellij.ui.components.JBTextField; -import com.jetbrains.rd.util.LogLevel; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.awt.event.ActionEvent; -import java.util.Arrays; - -public class AppSettingsComponent { - private static final Logger log = Logger.getInstance(AppSettingsComponent.class); - - - @Name("Style") - public final JBTextField style = new JBTextField(); - @SuppressWarnings("unused") - public final JButton randomizeStyle = new JButton(new AbstractAction("Randomize Style") { - @Override - public void actionPerformed(ActionEvent e) { - style.setText(StyleUtil.INSTANCE.randomStyle()); - } - }); - - @SuppressWarnings("unused") - public final JButton testStyle = new JButton(new AbstractAction("Test Style") { - @Override - public void actionPerformed(ActionEvent e) { - StyleUtil.INSTANCE.demoStyle(style.getText()); - } - }); - - @Name("Token Counter") - public final JBTextField tokenCounter = new JBTextField(); - @SuppressWarnings("unused") - public final JButton clearCounter = new JButton(new AbstractAction("Clear Token Counter") { - @Override - public void actionPerformed(ActionEvent e) { - tokenCounter.setText("0"); - } - }); - - @SuppressWarnings("unused") - @Name("Human Language") - public final JBTextField humanLanguage = new JBTextField(); - @SuppressWarnings("unused") - @Name("History Limit") - public final JBTextField historyLimit = new JBTextField(); - @SuppressWarnings("unused") - @Name("Developer Tools") - public final JBCheckBox devActions = new JBCheckBox(); - @SuppressWarnings("unused") - @Name("Suppress Progress (UNSAFE)") - public final JBCheckBox suppressProgress = new JBCheckBox(); - @SuppressWarnings("unused") - @Name("API Log Level") - public final ComboBox apiLogLevel = new ComboBox<>(Arrays.stream(LogLevel.values()).map(Enum::name).toArray(String[]::new)); - - @SuppressWarnings("unused") - @Name("Temperature") - public final JBTextField temperature = new JBTextField(); - @SuppressWarnings("unused") - @Name("Max Tokens") - public final JBTextField maxTokens = new JBTextField(); - @SuppressWarnings("unused") - @Name("Max Prompt (Characters)") - public final JBTextField maxPrompt = new JBTextField(); - @SuppressWarnings("unused") - @Name("Completion Model") - public final JComponent model_completion = OpenAI_API.INSTANCE.getModelSelector(); - @Name("Edit Model") - public final JComponent model_edit = OpenAI_API.INSTANCE.getModelSelector(); - - @Name("API Threads") - public final JBTextField apiThreads = new JBTextField(); - - @Name("API Key") - public final JBPasswordField apiKey = new JBPasswordField(); - @SuppressWarnings("unused") - @Name("API Base") - public final JBTextField apiBase = new JBTextField(); - - public AppSettingsComponent() { - tokenCounter.setEditable(false); - } - - public @NotNull JComponent getPreferredFocusedComponent() { - return apiKey; - } - -} \ No newline at end of file diff --git a/src/main/java/com/github/simiacryptus/aicoder/config/AppSettingsConfigurable.java b/src/main/java/com/github/simiacryptus/aicoder/config/AppSettingsConfigurable.java deleted file mode 100644 index 98f121ff..00000000 --- a/src/main/java/com/github/simiacryptus/aicoder/config/AppSettingsConfigurable.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.github.simiacryptus.aicoder.config; - -import com.github.simiacryptus.aicoder.util.UITools; -import com.intellij.openapi.options.Configurable; -import com.intellij.util.ui.FormBuilder; -import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.util.Objects; - -/** - * Provides controller functionality for application settings. - */ -public class AppSettingsConfigurable implements Configurable { - - @Nullable AppSettingsComponent settingsComponent; - private volatile @Nullable JPanel mainPanel = null; - - public AppSettingsConfigurable() { - // A default constructor with no arguments is required because this implementation is registered as an applicationConfigurable EP - } - - @Nls(capitalization = Nls.Capitalization.Title) - @Override - public @NotNull String getDisplayName() { - return "AICoder Settings"; - } - - @Override - public JComponent getPreferredFocusedComponent() { - return Objects.requireNonNull(settingsComponent).getPreferredFocusedComponent(); - } - - @Nullable - @Override - public JComponent createComponent() { - if (null == mainPanel) { - synchronized (this) { - if (null == mainPanel) { - @NotNull FormBuilder formBuilder = FormBuilder.createFormBuilder(); - settingsComponent = new AppSettingsComponent(); - UITools.INSTANCE.addFields(settingsComponent, formBuilder); - mainPanel = formBuilder.addComponentFillVertically(new JPanel(), 0).getPanel(); - } - } - } - return mainPanel; - } - - @Override - public boolean isModified() { - @NotNull AppSettingsState buffer = new AppSettingsState(); - if (this.settingsComponent != null) { - UITools.INSTANCE.readUI(this.settingsComponent, buffer); - } - return !buffer.equals(AppSettingsState.getInstance()); - } - - @Override - public void apply() { - if (this.settingsComponent != null) { - UITools.INSTANCE.readUI(this.settingsComponent, AppSettingsState.getInstance()); - } - } - - @Override - public void reset() { - if (settingsComponent != null) { - UITools.INSTANCE.writeUI(settingsComponent, AppSettingsState.getInstance()); - } - } - - @Override - public void disposeUIResources() { - settingsComponent = null; - } - -} \ No newline at end of file diff --git a/src/main/java/com/github/simiacryptus/aicoder/config/AppSettingsState.java b/src/main/java/com/github/simiacryptus/aicoder/config/AppSettingsState.java deleted file mode 100644 index 1a81e488..00000000 --- a/src/main/java/com/github/simiacryptus/aicoder/config/AppSettingsState.java +++ /dev/null @@ -1,141 +0,0 @@ -package com.github.simiacryptus.aicoder.config; - -import com.github.simiacryptus.aicoder.openai.CompletionRequest; -import com.github.simiacryptus.aicoder.openai.EditRequest; -import com.github.simiacryptus.aicoder.openai.translate.TranslationRequest; -import com.github.simiacryptus.aicoder.openai.translate.TranslationRequestTemplate; -import com.intellij.openapi.application.Application; -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 com.jetbrains.rd.util.LogLevel; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; -import java.util.stream.Collectors; - -/** - * Supports storing the application settings in a persistent way. - * The {@link State} and {@link Storage} annotations define the name of the data and the file name where - * these persistent application settings are stored. - */ -@State( - name = "org.intellij.sdk.settings.AppSettingsState", - storages = @Storage("SdkSettingsPlugin.xml") -) -public class AppSettingsState implements PersistentStateComponent { - - public @NotNull String apiBase = "https://api.openai.com/v1"; - public @NotNull String apiKey = ""; - public @NotNull String model_completion = "text-davinci-003"; - public @NotNull String model_edit = "text-davinci-edit-001"; - public int maxTokens = 1000; - public double temperature = 0.1; - public @NotNull String style = ""; - @SuppressWarnings("unused") - public int tokenCounter = 0; - private final @NotNull Map mostUsedHistory = new HashMap<>(); - private final @NotNull List mostRecentHistory = new ArrayList<>(); - public int historyLimit = 10; - public @NotNull String humanLanguage = "English"; - public int maxPrompt = 5000; - public @NotNull TranslationRequestTemplate translationRequestTemplate = TranslationRequestTemplate.XML; - public @NotNull LogLevel apiLogLevel = LogLevel.Debug; - public boolean devActions = false; - public boolean suppressProgress = false; - public int apiThreads = 4; - - public AppSettingsState() { - } - - @NotNull public static AppSettingsState getInstance() { - Application application = ApplicationManager.getApplication(); - return null==application?new AppSettingsState():application.getService(AppSettingsState.class); - } - - public TranslationRequest createTranslationRequest() { - return translationRequestTemplate.get(this); - } - - public @NotNull CompletionRequest createCompletionRequest() { - return new CompletionRequest(this); - } - - public @NotNull EditRequest createEditRequest() { - return new EditRequest(this); - } - - @Nullable - @Override - public AppSettingsState getState() { - return this; - } - - @Override - public void loadState(@NotNull AppSettingsState state) { - XmlSerializerUtil.copyBean(state, this); - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - @NotNull AppSettingsState that = (AppSettingsState) o; - if (maxTokens != that.maxTokens) return false; - if (maxPrompt != that.maxPrompt) return false; - if (Double.compare(that.temperature, temperature) != 0) return false; - if (!Objects.equals(humanLanguage, that.humanLanguage)) return false; - if (!Objects.equals(apiBase, that.apiBase)) return false; - if (!Objects.equals(apiKey, that.apiKey)) return false; - if (!Objects.equals(model_completion, that.model_completion)) return false; - if (!Objects.equals(model_edit, that.model_edit)) return false; - if (!Objects.equals(translationRequestTemplate, that.translationRequestTemplate)) return false; - if (!Objects.equals(apiLogLevel, that.apiLogLevel)) return false; - if (!Objects.equals(devActions, that.devActions)) return false; - if (!Objects.equals(suppressProgress, that.suppressProgress)) return false; - return Objects.equals(style, that.style); - } - - @Override - public int hashCode() { - return Objects.hash(apiBase, apiKey, model_completion, model_edit, maxTokens, temperature, translationRequestTemplate, apiLogLevel, devActions, suppressProgress, style); - } - - public void addInstructionToHistory(@NotNull CharSequence instruction) { - synchronized (mostRecentHistory) { - mostRecentHistory.add(instruction.toString()); - while(mostRecentHistory.size() > historyLimit) { - mostRecentHistory.remove(0); - } - } - synchronized (mostUsedHistory) { - mostUsedHistory.put(instruction.toString(), mostUsedHistory.getOrDefault(instruction, 0) + 1); - } - - // If the instruction history is bigger than the history limit, - // We'll make a set of strings to retain, - // We'll sort the instruction history by its value, - // And limit it to the history limit, - // Then we'll map the entry key and collect it in a set, - // Then we'll make a new hash set of the instruction history keys, - // And remove all the ones we want to retain, - // Then we'll remove all the ones we don't want to keep, - // And that's how we'll make sure the instruction history is neat! - if (mostUsedHistory.size() > historyLimit) { - @NotNull List retain = mostUsedHistory.entrySet().stream() - .sorted(Map.Entry.comparingByValue().reversed()) - .limit(historyLimit).map(Map.Entry::getKey).collect(Collectors.toList()); - @NotNull HashSet toRemove = new HashSet<>(mostUsedHistory.keySet()); - toRemove.removeAll(retain); - toRemove.removeAll(mostRecentHistory); - toRemove.forEach(mostUsedHistory::remove); - } - } - - public @NotNull Set getEditHistory() { - return mostUsedHistory.keySet(); - } -} \ No newline at end of file diff --git a/src/main/java/com/github/simiacryptus/aicoder/config/Name.java b/src/main/java/com/github/simiacryptus/aicoder/config/Name.java deleted file mode 100644 index d0c4690d..00000000 --- a/src/main/java/com/github/simiacryptus/aicoder/config/Name.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.github.simiacryptus.aicoder.config; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -@Retention(RetentionPolicy.RUNTIME) -public @interface Name { - String value(); -} diff --git a/src/main/java/com/github/simiacryptus/aicoder/openai/ApiError.java b/src/main/java/com/github/simiacryptus/aicoder/openai/ApiError.java deleted file mode 100644 index 2d922498..00000000 --- a/src/main/java/com/github/simiacryptus/aicoder/openai/ApiError.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.github.simiacryptus.aicoder.openai; - -public class ApiError { - @SuppressWarnings("unused") - public String message; - @SuppressWarnings("unused") - public String type; - @SuppressWarnings("unused") - public String param; - @SuppressWarnings("unused") - public Double code; - - @SuppressWarnings("unused") - public ApiError() { - } - - @SuppressWarnings("unused") - public ApiError(String message, String type, String param, Double code) { - this.message = message; - this.type = type; - this.param = param; - this.code = code; - } - -} diff --git a/src/main/java/com/github/simiacryptus/aicoder/openai/Choice.java b/src/main/java/com/github/simiacryptus/aicoder/openai/Choice.java deleted file mode 100644 index bdf7d9c9..00000000 --- a/src/main/java/com/github/simiacryptus/aicoder/openai/Choice.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.github.simiacryptus.aicoder.openai; - -public class Choice { - public String text; - @SuppressWarnings("unused") - public int index; - @SuppressWarnings("unused") - public LogProbs logprobs; - @SuppressWarnings("unused") - public String finish_reason; - - @SuppressWarnings("unused") - public Choice() { - } - - @SuppressWarnings("unused") - public Choice(String text, int index, LogProbs logprobs, String finish_reason) { - this.text = text; - this.index = index; - this.logprobs = logprobs; - this.finish_reason = finish_reason; - } - -} diff --git a/src/main/java/com/github/simiacryptus/aicoder/openai/CompletionRequest.java b/src/main/java/com/github/simiacryptus/aicoder/openai/CompletionRequest.java deleted file mode 100644 index db8bef2d..00000000 --- a/src/main/java/com/github/simiacryptus/aicoder/openai/CompletionRequest.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.github.simiacryptus.aicoder.openai; - -import com.github.simiacryptus.aicoder.config.AppSettingsState; -import com.github.simiacryptus.aicoder.util.UITools; -import com.intellij.util.ui.FormBuilder; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.util.ArrayList; -import java.util.Arrays; - -/** - * The CompletionRequest class is used to create a request for completion of a given prompt. - */ -public class CompletionRequest { - - public CompletionRequest(@NotNull AppSettingsState config) { - this("", config.temperature, config.maxTokens, null); - } - - @NotNull - public CompletionRequest.CompletionRequestWithModel uiIntercept() { - CompletionRequestWithModel withModel; - if (!(this instanceof CompletionRequestWithModel)) { - AppSettingsState settingsState = AppSettingsState.getInstance(); - if (!settingsState.devActions) { - withModel = new CompletionRequestWithModel(this, settingsState.model_completion); - } else { - withModel = showModelEditDialog(); - } - } else { - withModel = (CompletionRequestWithModel) this; - } - return withModel; - } - - public static class CompletionRequestWithModel extends CompletionRequest { - - public String model; - - public CompletionRequestWithModel(String prompt, double temperature, int max_tokens, Integer logprobs, boolean echo, String model, CharSequence... stop) { - super(prompt, temperature, max_tokens, logprobs, stop); - this.model = model; - } - - public CompletionRequestWithModel(@NotNull CompletionRequestWithModel other) { - super(other); - this.model = other.model; - } - - public CompletionRequestWithModel(@NotNull CompletionRequest other, String model) { - super(other); - this.model = model; - } - - public void fixup(@NotNull AppSettingsState settings) { - if (null != this.suffix) { - if (this.suffix.trim().length() == 0) { - setSuffix(null); - } else { - this.echo = false; - } - } - if (null != this.stop && this.stop.length == 0) { - this.stop = null; - } - if (this.prompt.length() > settings.maxPrompt) - throw new IllegalArgumentException("Prompt too long:" + this.prompt.length() + " chars"); - } - } - - public String prompt; - public @Nullable String suffix = null; - @SuppressWarnings("unused") - public double temperature; - @SuppressWarnings("unused") - public int max_tokens; - public CharSequence @Nullable [] stop; - @SuppressWarnings("unused") - public Integer logprobs; - @SuppressWarnings("unused") - public boolean echo; - - @SuppressWarnings("unused") - public CompletionRequest() { - } - - public CompletionRequest(String prompt, double temperature, int max_tokens, Integer logprobs, CharSequence... stop) { - this.prompt = prompt; - this.temperature = temperature; - this.max_tokens = max_tokens; - this.stop = stop; - this.logprobs = logprobs; - this.echo = false; - } - - public CompletionRequest(@NotNull CompletionRequest other) { - this.prompt = other.prompt; - this.temperature = other.temperature; - this.max_tokens = other.max_tokens; - this.stop = other.stop; - this.logprobs = other.logprobs; - this.echo = other.echo; - } - - - public @NotNull CompletionRequest appendPrompt(CharSequence prompt) { - this.prompt = this.prompt + prompt; - return this; - } - - public @NotNull CompletionRequest addStops(@NotNull CharSequence @NotNull ... newStops) { - @NotNull ArrayList stops = new ArrayList<>(); - for (@NotNull CharSequence x : newStops) { - if (x.length() > 0) { - stops.add(x); - } - } - if (!stops.isEmpty()) { - if (null != this.stop) Arrays.stream(this.stop).forEach(stops::add); - this.stop = stops.stream().distinct().toArray(CharSequence[]::new); - } - return this; - } - - public @NotNull CompletionRequest setSuffix(@Nullable CharSequence suffix) { - this.suffix = null == suffix ? null : suffix.toString(); - return this; - } - - public @NotNull CompletionRequestWithModel showModelEditDialog() { - @NotNull FormBuilder formBuilder = FormBuilder.createFormBuilder(); - AppSettingsState instance = AppSettingsState.getInstance(); - @NotNull CompletionRequestWithModel withModel = new CompletionRequestWithModel(this, instance.model_completion); - @NotNull InteractiveCompletionRequest ui = new InteractiveCompletionRequest(withModel); - UITools.INSTANCE.addFields(ui, formBuilder); - UITools.INSTANCE.writeUI(ui, withModel); - JPanel mainPanel = formBuilder.getPanel(); - if (UITools.INSTANCE.showOptionDialog(mainPanel, new Object[]{"OK"}, "Completion Request") == 0) { - UITools.INSTANCE.readUI(ui, withModel); - return withModel; - } else { - return withModel; - } - } - -} diff --git a/src/main/java/com/github/simiacryptus/aicoder/openai/CompletionResponse.java b/src/main/java/com/github/simiacryptus/aicoder/openai/CompletionResponse.java deleted file mode 100644 index 71ae6452..00000000 --- a/src/main/java/com/github/simiacryptus/aicoder/openai/CompletionResponse.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.github.simiacryptus.aicoder.openai; - -import org.jetbrains.annotations.NotNull; - -import java.util.Arrays; -import java.util.Optional; - -public class CompletionResponse { - @SuppressWarnings("unused") - public String id; - @SuppressWarnings("unused") - public String object; - @SuppressWarnings("unused") - public int created; - @SuppressWarnings("unused") - public String model; - public Choice[] choices; - @SuppressWarnings("unused") - public ApiError error; - - @SuppressWarnings("unused") - public Usage usage; - - public CompletionResponse() { - } - - public CompletionResponse(String id, String object, int created, String model, Choice[] choices, ApiError error) { - this.id = id; - this.object = object; - this.created = created; - this.model = model; - this.choices = choices; - this.error = error; - } - - public @NotNull Optional getFirstChoice() { - return Optional.ofNullable(this.choices).flatMap(choices -> Arrays.stream(choices).findFirst()).map(choice -> choice.text.trim()); - } -} diff --git a/src/main/java/com/github/simiacryptus/aicoder/openai/EditRequest.java b/src/main/java/com/github/simiacryptus/aicoder/openai/EditRequest.java deleted file mode 100644 index 27a9d60e..00000000 --- a/src/main/java/com/github/simiacryptus/aicoder/openai/EditRequest.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.github.simiacryptus.aicoder.openai; - -import com.github.simiacryptus.aicoder.config.AppSettingsState; -import com.github.simiacryptus.aicoder.util.UITools; -import com.intellij.util.ui.FormBuilder; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -/** - * The CompletionRequest class is used to create a request for completion of a given prompt. - */ -public class EditRequest { - - @NotNull - public String model; - @Nullable - public String input = null; - @NotNull - public String instruction; - @SuppressWarnings("unused") - @Nullable - public Double temperature; - @SuppressWarnings("unused") - @Nullable - public Integer n = null; - @Nullable - public Double top_p = null; - - @SuppressWarnings("unused") - public EditRequest() { - } - - public EditRequest(@NotNull AppSettingsState settingsState) { - this.setInstruction(""); - this.setModel(settingsState.model_edit); - this.setTemperature(settingsState.temperature); - } - - public EditRequest(@NotNull String instruction) { - this.setInstruction(instruction); - this.setModel(AppSettingsState.getInstance().model_edit); - this.setTemperature(AppSettingsState.getInstance().temperature); - } - - public EditRequest(@NotNull String instruction, @Nullable String input) { - this.setInput(input); - this.setInstruction(instruction); - this.setModel(AppSettingsState.getInstance().model_edit); - this.setTemperature(AppSettingsState.getInstance().temperature); - } - - public EditRequest(@NotNull String model, @Nullable String input, @NotNull String instruction, @Nullable Double temperature) { - this.setModel(model); - this.setInput(input); - this.setInstruction(instruction); - this.setTemperature(temperature); - } - - public EditRequest(@NotNull EditRequest obj) { - this.model = obj.model; - this.top_p = obj.top_p; - this.input = obj.input; - this.instruction = obj.instruction; - this.temperature = obj.temperature; - this.n = obj.n; - } - - public @NotNull EditRequest setModel(@NotNull String model) { - this.model = model; - return this; - } - - public @NotNull EditRequest setInput(String input) { - this.input = input; - return this; - } - - public @NotNull EditRequest setInstruction(@NotNull String instruction) { - this.instruction = instruction; - return this; - } - - public @NotNull EditRequest setTemperature(Double temperature) { - this.top_p = null; - this.temperature = temperature; - return this; - } - - public @NotNull EditRequest setN(Integer n) { - this.n = n; - return this; - } - - public @NotNull EditRequest setTop_p(Double top_p) { - this.temperature = null; - this.top_p = top_p; - return this; - } - - @Override - public @NotNull String toString() { - @NotNull String sb = "EditRequest{" + "model='" + model + '\'' + - ", input='" + input + '\'' + - ", instruction='" + instruction + '\'' + - ", temperature=" + temperature + - ", n=" + n + - ", top_p=" + top_p + - '}'; - return sb; - } - - public @NotNull EditRequest showModelEditDialog() { - @NotNull FormBuilder formBuilder = FormBuilder.createFormBuilder(); - @NotNull EditRequest withModel = new EditRequest(this); - @NotNull InteractiveEditRequest ui = new InteractiveEditRequest(withModel); - UITools.INSTANCE.addFields(ui, formBuilder); - UITools.INSTANCE.writeUI(ui, withModel); - JPanel mainPanel = formBuilder.getPanel(); - if (UITools.INSTANCE.showOptionDialog(mainPanel, new Object[]{"OK"}, "Completion Request") == 0) { - UITools.INSTANCE.readUI(ui, withModel); - return withModel; - } else { - return withModel; - } - } - - @NotNull - public EditRequest uiIntercept() { - if (AppSettingsState.getInstance().devActions) { - return showModelEditDialog(); - } else { - return this; - } - } -} diff --git a/src/main/java/com/github/simiacryptus/aicoder/openai/Engine.java b/src/main/java/com/github/simiacryptus/aicoder/openai/Engine.java deleted file mode 100644 index b68260ed..00000000 --- a/src/main/java/com/github/simiacryptus/aicoder/openai/Engine.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.github.simiacryptus.aicoder.openai; - -public class Engine { - @SuppressWarnings("unused") - public String id; - @SuppressWarnings("unused") - public boolean ready; - @SuppressWarnings("unused") - public String owner; - @SuppressWarnings("unused") - public String object; - @SuppressWarnings("unused") - public Integer created; - @SuppressWarnings("unused") - public String permissions; - @SuppressWarnings("unused") - public Integer replicas; - @SuppressWarnings("unused") - public Integer max_replicas; - - @SuppressWarnings("unused") - public Engine() { - } - - @SuppressWarnings("unused") - public Engine(String id, boolean ready, String owner, String object, Integer created, String permissions, Integer replicas, Integer max_replicas) { - this.id = id; - this.ready = ready; - this.owner = owner; - this.object = object; - this.created = created; - this.permissions = permissions; - this.replicas = replicas; - this.max_replicas = max_replicas; - } - -} diff --git a/src/main/java/com/github/simiacryptus/aicoder/openai/InteractiveCompletionRequest.java b/src/main/java/com/github/simiacryptus/aicoder/openai/InteractiveCompletionRequest.java deleted file mode 100644 index db3871d1..00000000 --- a/src/main/java/com/github/simiacryptus/aicoder/openai/InteractiveCompletionRequest.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.github.simiacryptus.aicoder.openai; - -import com.github.simiacryptus.aicoder.config.AppSettingsState; -import com.github.simiacryptus.aicoder.config.Name; -import com.github.simiacryptus.aicoder.util.UITools; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.intellij.ui.components.JBScrollPane; -import com.intellij.ui.components.JBTextArea; -import com.intellij.ui.components.JBTextField; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.awt.event.ActionEvent; -import java.util.Arrays; - -public class InteractiveCompletionRequest { - @SuppressWarnings("unused") - @Name("Prompt") - public final JBScrollPane prompt; - @SuppressWarnings("unused") - @Name("Suffix") - public final JBTextArea suffix; - @SuppressWarnings("unused") - @Name("Model") - public final JComponent model = OpenAI_API.INSTANCE.getModelSelector(); - @SuppressWarnings("unused") - @Name("Temperature") - public final JBTextField temperature = new JBTextField(8); - @SuppressWarnings("unused") - @Name("Max Tokens") - public final JBTextField max_tokens = new JBTextField(8); - public final @NotNull JButton testRequest; - - public InteractiveCompletionRequest(@NotNull CompletionRequest parent) { - testRequest = new JButton(new AbstractAction("Test Request") { - @Override - public void actionPerformed(ActionEvent e) { - CompletionRequest.@NotNull CompletionRequestWithModel withModel = new CompletionRequest.CompletionRequestWithModel(parent, AppSettingsState.getInstance().model_completion); - UITools.INSTANCE.readUI(InteractiveCompletionRequest.this, withModel); - @NotNull ListenableFuture future = OpenAI_API.INSTANCE.complete(null, withModel, ""); - testRequest.setEnabled(false); - Futures.addCallback(future, new FutureCallback<>() { - @Override - public void onSuccess(@NotNull CharSequence result) { - testRequest.setEnabled(true); - @NotNull String text = result.toString(); - int rows = Math.min(50, text.split("\n").length); - int columns = Math.min(200, Arrays.stream(text.split("\n")).mapToInt(String::length).max().getAsInt()); - @NotNull JBTextArea area = new JBTextArea(rows, columns); - area.setText(text); - area.setEditable(false); - JOptionPane.showMessageDialog(null, area, "Test Output", JOptionPane.PLAIN_MESSAGE); - } - - @Override - public void onFailure(Throwable t) { - testRequest.setEnabled(true); - UITools.INSTANCE.handle(t); - } - }, OpenAI_API.getPool()); - } - }); - suffix = UITools.INSTANCE.configureTextArea(new JBTextArea(1, 120)); - prompt = UITools.INSTANCE.wrapScrollPane((new JBTextArea(10, 120))); - } - -} diff --git a/src/main/java/com/github/simiacryptus/aicoder/openai/InteractiveEditRequest.java b/src/main/java/com/github/simiacryptus/aicoder/openai/InteractiveEditRequest.java deleted file mode 100644 index b7fc7f18..00000000 --- a/src/main/java/com/github/simiacryptus/aicoder/openai/InteractiveEditRequest.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.github.simiacryptus.aicoder.openai; - -import com.github.simiacryptus.aicoder.config.Name; -import com.github.simiacryptus.aicoder.util.UITools; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.intellij.ui.components.JBScrollPane; -import com.intellij.ui.components.JBTextArea; -import com.intellij.ui.components.JBTextField; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.awt.event.ActionEvent; -import java.util.Arrays; - -public class InteractiveEditRequest { - @SuppressWarnings("unused") - @Name("Input") - public final @NotNull JBScrollPane input; - @SuppressWarnings("unused") - @Name("Instruction") - public final JBTextArea instruction; - @SuppressWarnings("unused") - @Name("Model") - public final JComponent model = OpenAI_API.INSTANCE.getModelSelector(); - @SuppressWarnings("unused") - @Name("Temperature") - public final JBTextField temperature = new JBTextField(8); - public final @NotNull JButton testRequest; - - public InteractiveEditRequest(@NotNull EditRequest parent) { - testRequest = new JButton(new AbstractAction("Test Request") { - @Override - public void actionPerformed(ActionEvent e) { - @NotNull EditRequest request = new EditRequest(); - UITools.INSTANCE.readUI(InteractiveEditRequest.this, request); - @NotNull ListenableFuture future = OpenAI_API.INSTANCE.edit(null, request, ""); - testRequest.setEnabled(false); - Futures.addCallback(future, new FutureCallback<>() { - @Override - public void onSuccess(@NotNull CharSequence result) { - testRequest.setEnabled(true); - @NotNull String text = result.toString(); - int rows = Math.min(50, text.split("\n").length); - int columns = Math.min(200, Arrays.stream(text.split("\n")).mapToInt(String::length).max().getAsInt()); - @NotNull JBTextArea area = new JBTextArea(rows, columns); - area.setText(text); - area.setEditable(false); - JOptionPane.showMessageDialog(null, area, "Test Output", JOptionPane.PLAIN_MESSAGE); - } - - @Override - public void onFailure(Throwable t) { - testRequest.setEnabled(true); - UITools.INSTANCE.handle(t); - } - }, OpenAI_API.getPool()); - } - }); - input = UITools.INSTANCE.wrapScrollPane(new JBTextArea(10, 120)); - instruction = UITools.INSTANCE.configureTextArea(new JBTextArea(1, 120)); - } -} diff --git a/src/main/java/com/github/simiacryptus/aicoder/openai/LogProbs.java b/src/main/java/com/github/simiacryptus/aicoder/openai/LogProbs.java deleted file mode 100644 index b49c61fa..00000000 --- a/src/main/java/com/github/simiacryptus/aicoder/openai/LogProbs.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.github.simiacryptus.aicoder.openai; - -import com.fasterxml.jackson.databind.node.ObjectNode; - -public class LogProbs { - @SuppressWarnings("unused") - public CharSequence[] tokens; - @SuppressWarnings("unused") - public double[] token_logprobs; - @SuppressWarnings("unused") - public ObjectNode[] top_logprobs; - @SuppressWarnings("unused") - public int[] text_offset; - - @SuppressWarnings("unused") - public LogProbs() { - } - - @SuppressWarnings("unused") - public LogProbs(CharSequence[] tokens, double[] token_logprobs, ObjectNode[] top_logprobs, int[] text_offset) { - this.tokens = tokens; - this.token_logprobs = token_logprobs; - this.top_logprobs = top_logprobs; - this.text_offset = text_offset; - } - -} diff --git a/src/main/java/com/github/simiacryptus/aicoder/openai/ModerationException.java b/src/main/java/com/github/simiacryptus/aicoder/openai/ModerationException.java deleted file mode 100644 index d62ce1ef..00000000 --- a/src/main/java/com/github/simiacryptus/aicoder/openai/ModerationException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.simiacryptus.aicoder.openai; - -public class ModerationException extends Exception { - public ModerationException(String message) { - super(message); - } -} diff --git a/src/main/java/com/github/simiacryptus/aicoder/openai/Response.java b/src/main/java/com/github/simiacryptus/aicoder/openai/Response.java deleted file mode 100644 index 96862660..00000000 --- a/src/main/java/com/github/simiacryptus/aicoder/openai/Response.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.github.simiacryptus.aicoder.openai; - -public class Response { - @SuppressWarnings("unused") - public String object; - @SuppressWarnings("unused") - private Engine[] data; - - @SuppressWarnings("unused") - public Response() { - } - - @SuppressWarnings("unused") - public Response(String object, Engine[] data) { - this.object = object; - this.data = data; - } - -} diff --git a/src/main/java/com/github/simiacryptus/aicoder/openai/Usage.java b/src/main/java/com/github/simiacryptus/aicoder/openai/Usage.java deleted file mode 100644 index ae08ccbf..00000000 --- a/src/main/java/com/github/simiacryptus/aicoder/openai/Usage.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.simiacryptus.aicoder.openai; - -public class Usage { - @SuppressWarnings("unused") - public int prompt_tokens; - @SuppressWarnings("unused") - public int completion_tokens; - @SuppressWarnings("unused") - public int total_tokens; - - @SuppressWarnings("unused") - public Usage() { - } -} diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/CommentsAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/CommentsAction.kt index d02d65c8..d26eb062 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/CommentsAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/CommentsAction.kt @@ -27,30 +27,31 @@ class CommentsAction : AnAction() { val selectionStart = primaryCaret.selectionStart val selectionEnd = primaryCaret.selectionEnd val selectedText = primaryCaret.selectedText - val outputHumanLanguage = AppSettingsState.getInstance().humanLanguage + val outputHumanLanguage = AppSettingsState.instance.humanLanguage val language = ComputerLanguage.getComputerLanguage(e) - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val request = settings.createTranslationRequest() - .setInputType(Objects.requireNonNull(language)!!.name) - .setOutputType(language!!.name) - .setInstruction(UITools.getInstruction("Rewrite to include detailed $outputHumanLanguage code comments for every line")) - .setInputAttribute("type", "commented") - .setOutputAttrute("type", "uncommented") - .setOutputAttrute("style", settings.style) - .setInputText(selectedText) - .buildCompletionRequest() + .setInputType(Objects.requireNonNull(language)!!.name) + .setOutputType(language!!.name) + .setInstruction(UITools.getInstruction("Rewrite to include detailed $outputHumanLanguage code comments for every line")) + .setInputAttribute("type", "commented") + .setOutputAttrute("type", "uncommented") + .setOutputAttrute("style", settings.style) + .setInputText(selectedText) + .buildCompletionRequest() val caret = e.getData(CommonDataKeys.CARET) val indent = UITools.getIndent(caret) - UITools.redoableRequest(request, indent, e + UITools.redoableRequest( + request, indent, e ) { newText: CharSequence? -> UITools.replaceString(editor.document, selectionStart, selectionEnd, newText!!) } } companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false if (!UITools.hasSelection(e)) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false - if(computerLanguage == ComputerLanguage.Text) return false + if (computerLanguage == ComputerLanguage.Text) return false return true } } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/ConvertFileTo.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/ConvertFileTo.kt index 6becd824..e7baaa19 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/ConvertFileTo.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/ConvertFileTo.kt @@ -6,7 +6,6 @@ import com.intellij.openapi.actionSystem.ActionGroup import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.diagnostic.Logger -import java.util.* /** * The ConvertFileTo ActionGroup provides a way to quickly convert a file from one language to another. @@ -30,9 +29,9 @@ class ConvertFileTo : ActionGroup() { } private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false - if(computerLanguage == ComputerLanguage.Text) return false + if (computerLanguage == ComputerLanguage.Text) return false return supportedLanguages.contains(computerLanguage) } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/ConvertFileToLanguage.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/ConvertFileToLanguage.kt index 0ed1118c..6978207b 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/ConvertFileToLanguage.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/ConvertFileToLanguage.kt @@ -1,7 +1,7 @@ package com.github.simiacryptus.aicoder.actions.code -import com.github.simiacryptus.aicoder.openai.OpenAI_API.pool import com.github.simiacryptus.aicoder.config.AppSettingsState +import com.github.simiacryptus.aicoder.openai.async.AsyncAPI import com.github.simiacryptus.aicoder.util.ComputerLanguage import com.github.simiacryptus.aicoder.util.UITools.getIndent import com.github.simiacryptus.aicoder.util.psi.PsiTranslationSkeleton @@ -41,7 +41,7 @@ class ConvertFileToLanguage(private val targetLanguage: ComputerLanguage) : AnAc indent: CharSequence, root: PsiTranslationSkeleton ) { - val future: ListenableFuture<*> = if (AppSettingsState.getInstance().apiThreads > 1) { + val future: ListenableFuture<*> = if (AppSettingsState.instance.apiThreads > 1) { root.parallelTranslate(event.project!!, indent, sourceLanguage!!, targetLanguage) } else { root.sequentialTranslate(event.project!!, indent, sourceLanguage!!, targetLanguage)!! @@ -68,7 +68,7 @@ class ConvertFileToLanguage(private val targetLanguage: ComputerLanguage) : AnAc override fun onFailure(e: Throwable) { log.error("Error translating file", e) } - }, pool) + }, AsyncAPI.pool) } companion object { diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/CustomEditAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/CustomEditAction.kt index 6031ee25..2a38fa55 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/CustomEditAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/CustomEditAction.kt @@ -7,7 +7,6 @@ import com.github.simiacryptus.aicoder.util.UITools import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys -import java.util.* import javax.swing.JOptionPane /** @@ -34,9 +33,10 @@ class CustomEditAction : AnAction() { val selectionEnd = primaryCaret.selectionEnd val selectedText = primaryCaret.selectedText val computerLanguage = ComputerLanguage.getComputerLanguage(e)!!.name - val instruction = JOptionPane.showInputDialog(null, "Instruction:", "Edit Code", JOptionPane.QUESTION_MESSAGE) ?: return - if(instruction.isBlank()) return - val settings = AppSettingsState.getInstance() + val instruction = + JOptionPane.showInputDialog(null, "Instruction:", "Edit Code", JOptionPane.QUESTION_MESSAGE) ?: return + if (instruction.isBlank()) return + val settings = AppSettingsState.instance settings.addInstructionToHistory(instruction) val request = settings.createTranslationRequest() .setInputType(computerLanguage) @@ -60,10 +60,10 @@ class CustomEditAction : AnAction() { companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false if (!UITools.hasSelection(e)) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false - if(computerLanguage == ComputerLanguage.Text) return false + if (computerLanguage == ComputerLanguage.Text) return false return true } } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/DescribeAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/DescribeAction.kt index 120980ac..ed2aed76 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/DescribeAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/DescribeAction.kt @@ -1,7 +1,10 @@ package com.github.simiacryptus.aicoder.actions.code import com.github.simiacryptus.aicoder.config.AppSettingsState -import com.github.simiacryptus.aicoder.util.* +import com.github.simiacryptus.aicoder.util.ComputerLanguage +import com.github.simiacryptus.aicoder.util.IndentedText +import com.github.simiacryptus.aicoder.util.StringTools +import com.github.simiacryptus.aicoder.util.UITools import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys @@ -19,6 +22,7 @@ class DescribeAction : AnAction() { e.presentation.isEnabledAndVisible = isEnabled(e) super.update(e) } + override fun actionPerformed(event: AnActionEvent) { val editor = event.getRequiredData(CommonDataKeys.EDITOR) val caretModel = editor.caretModel @@ -39,12 +43,14 @@ class DescribeAction : AnAction() { } actionPerformed(event, editor, selectionStart, selectionEnd, selectedText, language) } + private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false if (computerLanguage == ComputerLanguage.Text) return false return true } + private fun actionPerformed( event: AnActionEvent, editor: Editor, @@ -54,7 +60,7 @@ class DescribeAction : AnAction() { language: ComputerLanguage ) { val indent = UITools.getIndent(event) - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val request = settings.createTranslationRequest() .setInputType(language.name) .setOutputType(settings.humanLanguage) diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/DocAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/DocAction.kt index 556694bc..a3635720 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/DocAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/DocAction.kt @@ -12,7 +12,6 @@ import com.github.simiacryptus.aicoder.util.psi.PsiUtil import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys -import java.util.* /** * The DocAction is an IntelliJ action that enables users to add detailed documentation to their code. @@ -39,7 +38,7 @@ class DocAction : AnAction() { val smallestIntersectingMethod = PsiUtil.getSmallestIntersectingMajorCodeElement(psiFile, caret!!) ?: return - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val code = smallestIntersectingMethod.text val indentedInput = IndentedText.fromString(code) val indent = indentedInput.indent @@ -72,9 +71,9 @@ class DocAction : AnAction() { companion object { private fun isEnabled(event: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false val computerLanguage = ComputerLanguage.getComputerLanguage(event) ?: return false - if(computerLanguage == ComputerLanguage.Text) return false + if (computerLanguage == ComputerLanguage.Text) return false if (computerLanguage.docStyle.isEmpty()) return false val psiFile = event.getRequiredData(CommonDataKeys.PSI_FILE) val caret = event.getData(CommonDataKeys.CARET) diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/FromHumanLanguageAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/FromHumanLanguageAction.kt index 80095136..5b1d36f7 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/FromHumanLanguageAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/FromHumanLanguageAction.kt @@ -26,8 +26,8 @@ class FromHumanLanguageAction : AnAction() { val selectionStart = primaryCaret.selectionStart val selectionEnd = primaryCaret.selectionEnd val selectedText = primaryCaret.selectedText - val request = AppSettingsState.getInstance().createTranslationRequest() - .setInputType(AppSettingsState.getInstance().humanLanguage.lowercase(Locale.getDefault())) + val request = AppSettingsState.instance.createTranslationRequest() + .setInputType(AppSettingsState.instance.humanLanguage.lowercase(Locale.getDefault())) .setOutputType(ComputerLanguage.getComputerLanguage(event)!!.name) .setInstruction("Implement this specification") .setInputAttribute("type", "input") @@ -48,10 +48,10 @@ class FromHumanLanguageAction : AnAction() { companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false if (!UITools.hasSelection(e)) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false - if(computerLanguage == ComputerLanguage.Text) return false + if (computerLanguage == ComputerLanguage.Text) return false return true } } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/ImplementAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/ImplementAction.kt index 5fef4c2f..5be51131 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/ImplementAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/ImplementAction.kt @@ -7,7 +7,6 @@ import com.github.simiacryptus.aicoder.util.StringTools import com.github.simiacryptus.aicoder.util.UITools import com.github.simiacryptus.aicoder.util.UITools.redoableRequest import com.github.simiacryptus.aicoder.util.UITools.replaceString -import com.github.simiacryptus.aicoder.util.psi.PsiUtil import com.github.simiacryptus.aicoder.util.psi.PsiUtil.getCode import com.github.simiacryptus.aicoder.util.psi.PsiUtil.getDocComment import com.github.simiacryptus.aicoder.util.psi.PsiUtil.getSmallestIntersectingMajorCodeElement @@ -31,7 +30,7 @@ class ImplementAction : AnAction() { val psiFile = event.getRequiredData(CommonDataKeys.PSI_FILE) val smallestIntersectingMethod = getSmallestIntersectingMajorCodeElement(psiFile, caret!!) ?: return val computerLanguage = ComputerLanguage.getComputerLanguage(event) ?: return - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val code = smallestIntersectingMethod.text val indentedInput = IndentedText.fromString(code) var declaration: CharSequence = smallestIntersectingMethod.text @@ -64,10 +63,10 @@ class ImplementAction : AnAction() { companion object { private fun isEnabled(event: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false - if (!AppSettingsState.getInstance().devActions) return false + if (UITools.isSanctioned()) return false + if (!AppSettingsState.instance.devActions) return false val computerLanguage = ComputerLanguage.getComputerLanguage(event) ?: return false - if(computerLanguage == ComputerLanguage.Text) return false + if (computerLanguage == ComputerLanguage.Text) return false val caret = event.getData(CommonDataKeys.CARET) val psiFile = event.getRequiredData(CommonDataKeys.PSI_FILE) val smallestIntersectingMethod = diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/PasteAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/PasteAction.kt index ce2ecc77..fe945544 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/PasteAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/PasteAction.kt @@ -8,7 +8,6 @@ import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.ide.CopyPasteManager import java.awt.datatransfer.DataFlavor -import java.util.* /** * The PasteAction class is an action that is used to paste text into an IntelliJ editor with GPT translation. @@ -34,7 +33,7 @@ class PasteAction : AnAction() { val text = CopyPasteManager.getInstance().getContents(DataFlavor.stringFlavor)!!.toString() .trim { it <= ' ' } - val request = AppSettingsState.getInstance().createTranslationRequest() + val request = AppSettingsState.instance.createTranslationRequest() .setInputType("source") .setOutputType("translated") .setInstruction("Translate this input into $language") @@ -60,7 +59,7 @@ class PasteAction : AnAction() { companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false return if (CopyPasteManager.getInstance() .getContents(DataFlavor.stringFlavor) == null ) false else { diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/PsiClassContextAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/PsiClassContextAction.kt index f570c0b0..209fea53 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/PsiClassContextAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/PsiClassContextAction.kt @@ -20,13 +20,13 @@ class PsiClassContextAction : AnAction() { } override fun actionPerformed(event: AnActionEvent) { - val humanLanguage = AppSettingsState.getInstance().humanLanguage + val humanLanguage = AppSettingsState.instance.humanLanguage val computerLanguage = ComputerLanguage.getComputerLanguage(event) val psiClassContextActionParams = getPsiClassContextActionParams(event).get() val editor = event.getRequiredData(CommonDataKeys.EDITOR) val caretModel = editor.caretModel val primaryCaret = caretModel.primaryCaret - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance var instruct = psiClassContextActionParams.largestIntersectingComment.text.trim { it <= ' ' } if (primaryCaret.selectionEnd > primaryCaret.selectionStart) { val selectedText = Objects.requireNonNull(primaryCaret.selectedText) @@ -89,7 +89,7 @@ class PsiClassContextAction : AnAction() { companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false if (computerLanguage == ComputerLanguage.Text) return false return getPsiClassContextActionParams(e).isPresent diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/QuestionAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/QuestionAction.kt index 89de913c..b13abac8 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/QuestionAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/QuestionAction.kt @@ -1,12 +1,12 @@ package com.github.simiacryptus.aicoder.actions.code import com.github.simiacryptus.aicoder.config.AppSettingsState -import com.github.simiacryptus.aicoder.util.* +import com.github.simiacryptus.aicoder.util.ComputerLanguage +import com.github.simiacryptus.aicoder.util.UITools import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.editor.Editor -import java.util.* import javax.swing.JOptionPane /** @@ -20,12 +20,14 @@ class QuestionAction : AnAction() { e.presentation.isEnabledAndVisible = isEnabled(e) super.update(e) } + private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false if (computerLanguage == ComputerLanguage.Text) return false return true } + override fun actionPerformed(event: AnActionEvent) { val editor = event.getRequiredData(CommonDataKeys.EDITOR) val caretModel = editor.caretModel @@ -46,6 +48,7 @@ class QuestionAction : AnAction() { } actionPerformed(event, editor, selectionStart, selectionEnd, selectedText, language) } + private fun actionPerformed( event: AnActionEvent, editor: Editor, @@ -55,11 +58,13 @@ class QuestionAction : AnAction() { language: ComputerLanguage ) { val indent = UITools.getIndent(event) - val settings = AppSettingsState.getInstance() - val question = JOptionPane.showInputDialog(null, "Question:", "Question", JOptionPane.QUESTION_MESSAGE) ?: return - if(question.isBlank()) return + val settings = AppSettingsState.instance + val question = + JOptionPane.showInputDialog(null, "Question:", "Question", JOptionPane.QUESTION_MESSAGE) ?: return + if (question.isBlank()) return val request = settings.createCompletionRequest() - .appendPrompt(""" + .appendPrompt( + """ Analyze the following code to answer the question "$question" ```$language ${selectedText.replace("\n", "\n ")} @@ -67,7 +72,8 @@ class QuestionAction : AnAction() { Question: $question Answer: - """.trimIndent()) + """.trimIndent() + ) UITools.redoableRequest(request, indent, event, { newText -> var text = """ diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/RecentCodeEditsAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/RecentCodeEditsAction.kt index 5196f0ff..da4b6410 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/RecentCodeEditsAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/RecentCodeEditsAction.kt @@ -8,7 +8,6 @@ import com.intellij.openapi.actionSystem.ActionGroup import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys -import java.util.* /** * The RecentCodeEditsAction is an IntelliJ action that allows users to quickly access and apply recent code edits. @@ -25,7 +24,7 @@ class RecentCodeEditsAction : ActionGroup() { override fun getChildren(e: AnActionEvent?): Array { val children = ArrayList() - for (instruction in AppSettingsState.getInstance().editHistory) { + for (instruction in AppSettingsState.instance.editHistory) { val id = children.size + 1 var text: String text = if (id < 10) { @@ -42,7 +41,7 @@ class RecentCodeEditsAction : ActionGroup() { val selectionStart = primaryCaret.selectionStart val selectionEnd = primaryCaret.selectionEnd val selectedText = primaryCaret.selectedText - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance settings.addInstructionToHistory(instruction) val request = settings.createTranslationRequest() .setInputType(computerLanguage) @@ -71,11 +70,11 @@ class RecentCodeEditsAction : ActionGroup() { companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false if (!UITools.hasSelection(e)) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) if (null == computerLanguage) return false - if(computerLanguage == ComputerLanguage.Text) return false + if (computerLanguage == ComputerLanguage.Text) return false return null != computerLanguage } } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/RenameVariablesAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/RenameVariablesAction.kt index a453398f..affe4144 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/RenameVariablesAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/RenameVariablesAction.kt @@ -4,21 +4,17 @@ import com.github.simiacryptus.aicoder.config.AppSettingsState import com.github.simiacryptus.aicoder.util.* import com.github.simiacryptus.aicoder.util.UITools.replaceString import com.github.simiacryptus.aicoder.util.UITools.showCheckboxDialog -import com.github.simiacryptus.aicoder.util.UITools.showOptionDialog import com.github.simiacryptus.aicoder.util.psi.PsiUtil import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.refactoring.suggested.endOffset import com.intellij.refactoring.suggested.startOffset -import com.intellij.util.ui.FormBuilder import org.intellij.lang.annotations.Language import org.jetbrains.annotations.NotNull import org.jetbrains.annotations.Nullable import java.util.* import java.util.stream.Collectors -import javax.swing.Icon -import javax.swing.JCheckBox class RenameVariablesAction : AnAction() { @@ -28,7 +24,7 @@ class RenameVariablesAction : AnAction() { } private fun isEnabled(@NotNull e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false if (computerLanguage == ComputerLanguage.Text) return false return true @@ -38,11 +34,12 @@ class RenameVariablesAction : AnAction() { @NotNull val textEditor = actionEvent.getRequiredData(CommonDataKeys.EDITOR) @NotNull val caretModel = textEditor.caretModel @NotNull val mainCursor = caretModel.primaryCaret - @NotNull val outputLanguage = AppSettingsState.getInstance().humanLanguage + @NotNull val outputLanguage = AppSettingsState.instance.humanLanguage val sourceFile = actionEvent.getRequiredData(CommonDataKeys.PSI_FILE) - val codeElement = PsiUtil.getSmallestIntersectingMajorCodeElement(sourceFile, mainCursor) ?: throw IllegalStateException() + val codeElement = + PsiUtil.getSmallestIntersectingMajorCodeElement(sourceFile, mainCursor) ?: throw IllegalStateException() @NotNull val programmingLanguage = ComputerLanguage.getComputerLanguage(actionEvent) - val appSettings = AppSettingsState.getInstance() + val appSettings = AppSettingsState.instance @Language("Markdown") @NotNull val completionRequest = appSettings.createCompletionRequest() diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/RewordCommentAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/RewordCommentAction.kt index 46bb98aa..26465a98 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/RewordCommentAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/RewordCommentAction.kt @@ -24,11 +24,11 @@ class RewordCommentAction : AnAction() { } override fun actionPerformed(event: AnActionEvent) { - val humanLanguage = AppSettingsState.getInstance().humanLanguage + val humanLanguage = AppSettingsState.instance.humanLanguage val computerLanguage = ComputerLanguage.getComputerLanguage(event) val rewordCommentParams = getRewordCommentParams(event) val editor = event.getRequiredData(CommonDataKeys.EDITOR) - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val text = Objects.requireNonNull(rewordCommentParams)!!.largestIntersectingComment.text val commentModel = computerLanguage!!.getCommentModel(text) val commentText = Objects.requireNonNull(commentModel)!!.fromString(text.trim { it <= ' ' })!! @@ -57,12 +57,12 @@ class RewordCommentAction : AnAction() { val document = editor.document UITools.redoableRequest(request, "", event, { newText -> - indent.toString() + commentModel!!.fromString( + indent.toString() + commentModel.fromString( StringTools.lineWrapping( newText, 120 ) )!!.withIndent(indent) - },{ newText -> + }, { newText -> UITools.replaceString( document, startOffset, endOffset, newText @@ -74,9 +74,9 @@ class RewordCommentAction : AnAction() { class RewordCommentParams constructor(val caret: Caret, val largestIntersectingComment: PsiElement) companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false - if(computerLanguage == ComputerLanguage.Text) return false + if (computerLanguage == ComputerLanguage.Text) return false return null != getRewordCommentParams(e) } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/ToHumanLanguageAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/ToHumanLanguageAction.kt index 021b25e5..4ac2c01b 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/ToHumanLanguageAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/ToHumanLanguageAction.kt @@ -29,13 +29,13 @@ class ToHumanLanguageAction : AnAction() { val selectedText = primaryCaret.selectedText val language = ComputerLanguage.getComputerLanguage(event) val computerLanguage = language!!.name - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val request = settings.createTranslationRequest() .setInstruction(UITools.getInstruction("Describe this code")) .setInputText(selectedText) .setInputType(computerLanguage) .setInputAttribute("type", "input") - .setOutputType(AppSettingsState.getInstance().humanLanguage.lowercase(Locale.getDefault())) + .setOutputType(AppSettingsState.instance.humanLanguage.lowercase(Locale.getDefault())) .setOutputAttrute("type", "output") .setOutputAttrute("style", settings.style) .buildCompletionRequest() @@ -54,10 +54,10 @@ class ToHumanLanguageAction : AnAction() { companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false if (!UITools.hasSelection(e)) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false - if(computerLanguage == ComputerLanguage.Text) return false + if (computerLanguage == ComputerLanguage.Text) return false return true } } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/TranslateCommentAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/TranslateCommentAction.kt index 6d46a4bd..49da37d1 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/TranslateCommentAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/code/TranslateCommentAction.kt @@ -23,11 +23,11 @@ class TranslateCommentAction : AnAction() { } override fun actionPerformed(event: AnActionEvent) { - val humanLanguage = AppSettingsState.getInstance().humanLanguage + val humanLanguage = AppSettingsState.instance.humanLanguage val computerLanguage = Objects.requireNonNull(ComputerLanguage.getComputerLanguage(event)) val rewordCommentParams = Objects.requireNonNull(getRewordCommentParams(event))!! val editor = event.getRequiredData(CommonDataKeys.EDITOR) - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val largestIntersectingComment = rewordCommentParams.largestIntersectingComment val indent = UITools.getIndent(rewordCommentParams.caret) val text = largestIntersectingComment.text @@ -61,7 +61,7 @@ class TranslateCommentAction : AnAction() { class RewordCommentParams constructor(val caret: Caret, val largestIntersectingComment: PsiElement) companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false if (computerLanguage == ComputerLanguage.Text) return false return null != getRewordCommentParams(e) diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/GenerateProjectAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/GenerateProjectAction.kt new file mode 100644 index 00000000..4945899e --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/GenerateProjectAction.kt @@ -0,0 +1,61 @@ +package com.github.simiacryptus.aicoder.actions.dev + +import com.github.simiacryptus.aicoder.config.AppSettingsState +import com.github.simiacryptus.aicoder.openai.proxy.ChatProxy +import com.github.simiacryptus.aicoder.openai.proxy.SoftwareProjectAI +import com.github.simiacryptus.aicoder.util.UITools +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import java.io.File +import javax.swing.JTextArea + +class GenerateProjectAction : AnAction() { + + fun createProjectFiles(e: AnActionEvent, description: String) { + val outputDir = File(UITools.getSelectedFolder(e)!!.canonicalPath) + val api = ChatProxy(apiKey = AppSettingsState.instance.apiKey, base = AppSettingsState.instance.apiBase).create( + SoftwareProjectAI::class.java + ) + val project = api.newProject(description) + val requirements = api.getProjectStatements(project) + val projectDesign = api.buildProjectDesign(project, requirements) + val files = api.buildProjectFileSpecifications(project, requirements, projectDesign) + for (file in files.files) { + val sourceCode = api.implement( + project, + files.files.map { it.location }.filter { file.requires.contains(it) }.toList(), + file + ) + val outFile = + outputDir.resolve(file.location.path.replace('\\','/').trimEnd('/') + "/${file.location.name}.${file.location.extension}") + outFile.parentFile.mkdirs() + outFile.writeText(sourceCode.code) + log.warn("Wrote ${outFile.canonicalPath}") + } + + } + + override fun update(e: AnActionEvent) { + e.presentation.isEnabledAndVisible = isEnabled(e) + super.update(e) + } + + data class SettingsUI(val description: JTextArea = JTextArea()) + data class Settings(var description: String = "") + + override fun actionPerformed(e: AnActionEvent) { + UITools.showDialog(e, SettingsUI::class.java, Settings::class.java) { config -> + createProjectFiles(e, config.description) + } + } + + private fun isEnabled(e: AnActionEvent): Boolean { + if (UITools.isSanctioned()) return false + if (!AppSettingsState.instance.devActions) return false + return true + } + + companion object { + private val log = org.slf4j.LoggerFactory.getLogger(GenerateProjectAction::class.java) + } +} diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/MarkdownContextAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/MarkdownContextAction.kt index 2a4d2a6e..9f80d219 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/MarkdownContextAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/MarkdownContextAction.kt @@ -31,9 +31,9 @@ class MarkdownContextAction : AnAction() { val selectionStart = primaryCaret.selectionStart val selectionEnd = primaryCaret.selectionEnd val selectedText = primaryCaret.selectedText - val humanLanguage = AppSettingsState.getInstance().humanLanguage + val humanLanguage = AppSettingsState.instance.humanLanguage val markdownContextParams = getMarkdownContextParams(event, humanLanguage) - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val psiFile = event.getRequiredData(CommonDataKeys.PSI_FILE) var context = PsiMarkdownContext.getContext( psiFile, @@ -74,13 +74,13 @@ class MarkdownContextAction : AnAction() { companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false - if (!AppSettingsState.getInstance().devActions) return false + if (UITools.isSanctioned()) return false + if (!AppSettingsState.instance.devActions) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false - if(computerLanguage == ComputerLanguage.Text) return false + if (computerLanguage == ComputerLanguage.Text) return false return if (ComputerLanguage.Markdown != computerLanguage) false else null != getMarkdownContextParams( e, - AppSettingsState.getInstance().humanLanguage + AppSettingsState.instance.humanLanguage ) } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/PrintTreeAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/PrintTreeAction.kt index 50f07570..8f74267a 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/PrintTreeAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/PrintTreeAction.kt @@ -27,8 +27,10 @@ class PrintTreeAction : AnAction() { companion object { val log = Logger.getInstance(PrintTreeAction::class.java) private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false - return if (!AppSettingsState.getInstance().devActions) false else null != PsiUtil.getLargestContainedEntity(e) + if (UITools.isSanctioned()) return false + return if (!AppSettingsState.instance.devActions) false else null != PsiUtil.getLargestContainedEntity( + e + ) } } } \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/RecursiveToStatementListAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/RecursiveToStatementListAction.kt index 36ba1ccb..f9445f0c 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/RecursiveToStatementListAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/dev/RecursiveToStatementListAction.kt @@ -1,7 +1,8 @@ package com.github.simiacryptus.aicoder.actions.dev import com.github.simiacryptus.aicoder.config.AppSettingsState -import com.github.simiacryptus.aicoder.openai.OpenAI_API +import com.github.simiacryptus.aicoder.openai.async.AsyncAPI +import com.github.simiacryptus.aicoder.openai.ui.OpenAI_API import com.github.simiacryptus.aicoder.util.ComputerLanguage import com.github.simiacryptus.aicoder.util.UITools import com.github.simiacryptus.aicoder.util.UITools.getInstruction @@ -26,7 +27,7 @@ class RecursiveToStatementListAction : AnAction() { } override fun actionPerformed(event: AnActionEvent) { - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val caret = event.getRequiredData(CommonDataKeys.EDITOR).caretModel.primaryCaret val languageName = ComputerLanguage.getComputerLanguage(event)!!.name val endOffset: Int @@ -44,7 +45,7 @@ class RecursiveToStatementListAction : AnAction() { } val progressIndicator = startProgress() transform( - OpenAI_API.complete(event.project, topicsRequest(settings, languageName, text), ""), + OpenAI_API.getCompletion(event.project, topicsRequest(settings, languageName, text), ""), { topicsTxt -> val topics: List = topicsTxt.replace("\"".toRegex(), "").split("\n\\d+\\.\\s+".toRegex()) var future = expand(settings, event, languageName, text!!, topics) @@ -66,9 +67,9 @@ class RecursiveToStatementListAction : AnAction() { progressIndicator?.cancel() handle(t) } - }, OpenAI_API.pool) + }, AsyncAPI.pool) }, - OpenAI_API.pool + AsyncAPI.pool ) } @@ -80,12 +81,13 @@ class RecursiveToStatementListAction : AnAction() { topics: List, cache: ConcurrentHashMap>> = ConcurrentHashMap>>() ): ListenableFuture> = cache.computeIfAbsent(text) { - var rawFuture = OpenAI_API.complete(event.project, completionRequest(settings, languageName, text, topics), "") - rawFuture = transform(rawFuture, { result -> "1. $result" }, OpenAI_API.pool) + var rawFuture = + OpenAI_API.getCompletion(event.project, completionRequest(settings, languageName, text, topics), "") + rawFuture = transform(rawFuture, { result -> "1. $result" }, AsyncAPI.pool) var listFuture = transform( rawFuture, { it!!.split("(? ) = settings.createTranslationRequest() - .setInstruction(getInstruction(""" + .setInstruction( + getInstruction( + """ Transform into a list of independent statements of fact. Resolve all pronouns and fully qualify each item. - """.trimIndent().trim())) + """.trimIndent().trim() + ) + ) .setInputType(languageName) .setInputAttribute("type", "before") .setInputText(text) @@ -148,9 +154,13 @@ class RecursiveToStatementListAction : AnAction() { languageName: String, text: @NlsSafe String? ) = settings.createTranslationRequest() - .setInstruction(getInstruction(""" + .setInstruction( + getInstruction( + """ Describe the context of this text by listing terms and topics. - """.trimIndent())) + """.trimIndent() + ) + ) .setInputType(languageName) .setInputAttribute("type", "before") .setInputText(text) @@ -164,8 +174,8 @@ class RecursiveToStatementListAction : AnAction() { companion object { val log = org.slf4j.LoggerFactory.getLogger(RecursiveToStatementListAction::class.java)!! private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false - if (!AppSettingsState.getInstance().devActions) return false + if (UITools.isSanctioned()) return false + if (!AppSettingsState.instance.devActions) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false if (ComputerLanguage.Markdown != computerLanguage) return false val caret = e.getData(CommonDataKeys.CARET) ?: return false diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/AppendAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/AppendAction.kt index f6458768..2721295c 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/AppendAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/AppendAction.kt @@ -24,8 +24,8 @@ class AppendAction : AnAction() { override fun actionPerformed(event: AnActionEvent) { val caret = event.getData(CommonDataKeys.CARET) val before: CharSequence? = Objects.requireNonNull(caret)!!.selectedText - val settings = AppSettingsState.getInstance() - val completionRequest = settings.createCompletionRequest().appendPrompt(before) + val settings = AppSettingsState.instance + val completionRequest = settings.createCompletionRequest().appendPrompt(before ?: "") val document = event.getRequiredData(CommonDataKeys.EDITOR).document val selectionEnd = caret!!.selectionEnd redoableRequest( @@ -41,7 +41,7 @@ class AppendAction : AnAction() { companion object { @Suppress("unused") private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false return hasSelection(e) } } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/DictationAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/DictationAction.kt index eb908d09..77feeb96 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/DictationAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/DictationAction.kt @@ -1,17 +1,21 @@ package com.github.simiacryptus.aicoder.actions.generic -import com.github.simiacryptus.aicoder.openai.OpenAI_API -import com.github.simiacryptus.aicoder.util.LoudnessWindowBuffer +import com.github.simiacryptus.aicoder.openai.ui.OpenAI_API import com.github.simiacryptus.aicoder.util.AudioRecorder +import com.github.simiacryptus.aicoder.util.LoudnessWindowBuffer import com.github.simiacryptus.aicoder.util.UITools -import com.intellij.openapi.actionSystem.* +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.diagnostic.Logger import java.util.* import java.util.concurrent.ConcurrentLinkedDeque import java.util.concurrent.atomic.AtomicInteger -import javax.sound.sampled.* -import javax.swing.* +import javax.sound.sampled.AudioSystem +import javax.swing.JFrame +import javax.swing.JLabel class DictationAction : AnAction() { override fun update(e: AnActionEvent) { @@ -46,7 +50,7 @@ class DictationAction : AnAction() { val caretModel = event.getRequiredData(CommonDataKeys.EDITOR).caretModel val primaryCaret = caretModel.primaryCaret - val dictationPump = if(primaryCaret.hasSelection()) { + val dictationPump = if (primaryCaret.hasSelection()) { DictationPump(event, wavBuffer, continueFn, primaryCaret.selectionEnd, primaryCaret.selectedText ?: "") } else { DictationPump(event, wavBuffer, continueFn, caretModel.offset) @@ -82,9 +86,11 @@ class DictationAction : AnAction() { var text = OpenAI_API.text_to_speech(recordAudio, prompt) if (prompt.isNotEmpty()) text = " " + text val newPrompt = (prompt + text).split(" ").takeLast(32).joinToString(" ") - log.warn("""Speech-To-Text Complete + log.warn( + """Speech-To-Text Complete | Prompt: $prompt - | Result: $text""".trimMargin()) + | Result: $text""".trimMargin() + ) prompt = newPrompt WriteCommandAction.runWriteCommandAction(event.project) { val editor = event.getRequiredData(CommonDataKeys.EDITOR) @@ -99,7 +105,7 @@ class DictationAction : AnAction() { private fun statusDialog(e1: AnActionEvent): JFrame { val dialog = JFrame("Dictation") val jLabel = JLabel("Close this window to stop recording and dictation") - jLabel.setFont(jLabel.getFont().deriveFont(48f)) + jLabel.font = jLabel.font.deriveFont(48f) dialog.add(jLabel) dialog.pack() dialog.location = e1.getData(PlatformDataKeys.CONTEXT_COMPONENT)?.locationOnScreen!! @@ -109,7 +115,7 @@ class DictationAction : AnAction() { } private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false try { AudioSystem.getTargetDataLine(AudioRecorder.audioFormat) } catch (e: Exception) { diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/EditAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/EditAction.kt index 449ad040..6e40b599 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/EditAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/EditAction.kt @@ -27,7 +27,7 @@ class EditAction : AnAction() { } override fun actionPerformed(event: AnActionEvent) { - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val instruction = JOptionPane.showInputDialog(null, "Instruction:", "Edit Text", JOptionPane.QUESTION_MESSAGE) settings.addInstructionToHistory(instruction) val caret = event.getData(CommonDataKeys.CARET) @@ -51,7 +51,7 @@ class EditAction : AnAction() { companion object { @Suppress("unused") private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false return UITools.hasSelection(e) } } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/InsertAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/InsertAction.kt index 3598b99c..a96f73de 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/InsertAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/InsertAction.kt @@ -21,7 +21,7 @@ class InsertAction : AnAction() { val caretPosition = caret.offset val before = StringTools.getSuffixForContext(document.getText(TextRange(0, caretPosition)), 32) val after = StringTools.getPrefixForContext(document.getText(TextRange(caretPosition, document.textLength)), 32) - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val completionRequest = settings.createCompletionRequest() .appendPrompt(before) .setSuffix(after) @@ -38,7 +38,7 @@ class InsertAction : AnAction() { companion object { @Suppress("unused") private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false return !UITools.hasSelection(e) } } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/RecentTextEditsAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/RecentTextEditsAction.kt index 386e4455..e182a5f1 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/RecentTextEditsAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/RecentTextEditsAction.kt @@ -7,7 +7,6 @@ import com.intellij.openapi.actionSystem.ActionGroup import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys -import java.util.ArrayList /** * The RecentTextEditsAction is an IntelliJ action that allows users to quickly access and apply recent text edits. @@ -23,7 +22,7 @@ class RecentTextEditsAction : ActionGroup() { override fun getChildren(e: AnActionEvent?): Array { val children = ArrayList() - for (instruction in AppSettingsState.getInstance().editHistory) { + for (instruction in AppSettingsState.instance.editHistory) { val id = children.size + 1 var text: String text = if (id < 10) { @@ -39,7 +38,7 @@ class RecentTextEditsAction : ActionGroup() { val selectionStart = primaryCaret.selectionStart val selectionEnd = primaryCaret.selectionEnd val selectedText = primaryCaret.selectedText - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance settings.addInstructionToHistory(instruction) val request = settings.createEditRequest() .setInstruction(instruction) @@ -63,7 +62,7 @@ class RecentTextEditsAction : ActionGroup() { companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false return UITools.hasSelection(e) } } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/RedoLast.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/RedoLast.kt index c535f93c..cb5fbca3 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/RedoLast.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/RedoLast.kt @@ -23,7 +23,7 @@ class RedoLast : AnAction() { companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false return null != retry[e.getRequiredData(CommonDataKeys.EDITOR).document] } } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/ReplaceOptionsAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/ReplaceOptionsAction.kt index cfc69c20..360cbad8 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/ReplaceOptionsAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/ReplaceOptionsAction.kt @@ -20,7 +20,8 @@ class ReplaceOptionsAction : AnAction() { val caret = event.getData(CommonDataKeys.CARET) val document = caret!!.editor.document val selectedText = caret.selectedText - val idealLength = Math.pow(2.0, 2 + Math.ceil(Math.log(selectedText!!.length.toDouble()) / Math.log(2.0))).toInt() + val idealLength = + Math.pow(2.0, 2 + Math.ceil(Math.log(selectedText!!.length.toDouble()) / Math.log(2.0))).toInt() val newlines = "\n".toRegex() val selectionStart = caret.selectionStart val allBefore = document.getText(TextRange(0, selectionStart)) @@ -28,7 +29,7 @@ class ReplaceOptionsAction : AnAction() { val allAfter = document.getText(TextRange(selectionEnd, document.textLength)) val before = StringTools.getSuffixForContext(allBefore, idealLength).replace(newlines, " ") val after = StringTools.getPrefixForContext(allAfter, idealLength).replace(newlines, " ") - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val completionRequest = settings.createCompletionRequest() .appendPrompt( """ @@ -44,7 +45,7 @@ class ReplaceOptionsAction : AnAction() { { newText: CharSequence? -> val options = newText!!.split("\n") .map { it.trim().replace("^\\d+\\. ".toRegex(), "").trim() }.toTypedArray() - showRadioButtonDialog("Select an option to fill in the blank:", *options)?:selectedText + showRadioButtonDialog("Select an option to fill in the blank:", *options) ?: selectedText }, { newText: CharSequence? -> UITools.replaceString(document, selectionStart, selectionEnd, newText!!) }) @@ -52,7 +53,7 @@ class ReplaceOptionsAction : AnAction() { @Suppress("unused") private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false return UITools.hasSelection(e) } } \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/AnnotateTextAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/AnnotateTextAction.kt index ec9e27c8..1cc72678 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/AnnotateTextAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/AnnotateTextAction.kt @@ -8,7 +8,6 @@ import com.intellij.openapi.actionSystem.ActionGroup import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys -import java.util.* /** * The RecentCodeEditsAction is an IntelliJ action that allows users to quickly access and apply recent code edits. @@ -41,9 +40,9 @@ class AnnotateTextAction : ActionGroup() { val selectionStart = primaryCaret.selectionStart val selectionEnd = primaryCaret.selectionEnd val selectedText = primaryCaret.selectedText - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance settings.addInstructionToHistory(encoding) - val humanLanguage = AppSettingsState.getInstance().humanLanguage + val humanLanguage = AppSettingsState.instance.humanLanguage val request = settings.createTranslationRequest() .setInstruction("Parse and output as $encoding") .setInputType("text") @@ -68,13 +67,15 @@ class AnnotateTextAction : ActionGroup() { } return children.toTypedArray() } + private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false if (!UITools.hasSelection(e)) return false - if(!setOf( + if (!setOf( ComputerLanguage.Markdown, ComputerLanguage.Text - ).contains(ComputerLanguage.getComputerLanguage(e))) return false + ).contains(ComputerLanguage.getComputerLanguage(e)) + ) return false return null != ComputerLanguage.getComputerLanguage(e) } } \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/FactCheckLinkedListAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/FactCheckLinkedListAction.kt index f3f9b72c..2c99bd6a 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/FactCheckLinkedListAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/FactCheckLinkedListAction.kt @@ -21,7 +21,7 @@ class FactCheckLinkedListAction : AnAction() { } override fun actionPerformed(event: AnActionEvent) { - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val caret = event.getRequiredData(CommonDataKeys.EDITOR).caretModel.primaryCaret val languageName = ComputerLanguage.getComputerLanguage(event)!!.name val psiFile = PsiUtil.getPsiFile(event)!! @@ -34,7 +34,7 @@ class FactCheckLinkedListAction : AnAction() { .setInstruction(getInstruction("Translate each item into a search query that can be used to fact check each item with a search engine")) .setInputType(languageName) .setInputAttribute("type", "before") - .setInputText(elementText.mapIndexed { index, s -> "${index+1}. $s" }.joinToString("\n")) + .setInputText(elementText.mapIndexed { index, s -> "${index + 1}. $s" }.joinToString("\n")) .setOutputType(languageName) .setOutputAttrute("type", "after") .buildCompletionRequest() @@ -59,7 +59,7 @@ class FactCheckLinkedListAction : AnAction() { companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false if (ComputerLanguage.Markdown != computerLanguage) return false val caret = e.getData(CommonDataKeys.CARET) ?: return false diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownImplementAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownImplementAction.kt index 8445f9cc..478e819f 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownImplementAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownImplementAction.kt @@ -28,7 +28,7 @@ class MarkdownImplementAction(private val language: String) : AnAction( } override fun actionPerformed(event: AnActionEvent) { - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val caret = event.getData(CommonDataKeys.CARET) val selectedText = caret!!.selectedText val endOffset = caret.selectionEnd @@ -54,7 +54,7 @@ class MarkdownImplementAction(private val language: String) : AnAction( companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false if (ComputerLanguage.Markdown != computerLanguage) return false return hasSelection(e) diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownImplementActionGroup.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownImplementActionGroup.kt index 90c6dcd9..fceea6c1 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownImplementActionGroup.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownImplementActionGroup.kt @@ -50,9 +50,9 @@ class MarkdownImplementActionGroup : ActionGroup() { } private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false - if(computerLanguage == ComputerLanguage.Text) return false + if (computerLanguage == ComputerLanguage.Text) return false if (ComputerLanguage.Markdown != computerLanguage) return false return hasSelection(e) } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownListAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownListAction.kt index eb14050a..ca3a18a4 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownListAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownListAction.kt @@ -34,7 +34,7 @@ class MarkdownListAction : AnAction() { override fun actionPerformed(event: AnActionEvent) { val markdownListParams = getMarkdownListParams(event) - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val items = StringTools.trim( getAll( Objects.requireNonNull(markdownListParams)!!.list, "MarkdownListItemImpl" @@ -83,7 +83,7 @@ class MarkdownListAction : AnAction() { class MarkdownListParams constructor(val caret: Caret, val list: PsiElement) companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false return if (ComputerLanguage.Markdown != computerLanguage) false else null != getMarkdownListParams(e) } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownNewTableColAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownNewTableColAction.kt index e9004687..de5baef7 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownNewTableColAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownNewTableColAction.kt @@ -1,7 +1,7 @@ package com.github.simiacryptus.aicoder.actions.markdown import com.github.simiacryptus.aicoder.config.AppSettingsState -import com.github.simiacryptus.aicoder.openai.CompletionRequest +import com.github.simiacryptus.aicoder.openai.core.CompletionRequest import com.github.simiacryptus.aicoder.util.ComputerLanguage import com.github.simiacryptus.aicoder.util.StringTools import com.github.simiacryptus.aicoder.util.UITools @@ -24,7 +24,7 @@ class MarkdownNewTableColAction : AnAction() { override fun actionPerformed(event: AnActionEvent) { val markdownNewTableColParams = getMarkdownNewTableColParams(event) - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val columnName: CharSequence = JOptionPane.showInputDialog(null, "Column Name:", "Add Column", JOptionPane.QUESTION_MESSAGE) .trim { it <= ' ' } @@ -38,7 +38,7 @@ class MarkdownNewTableColAction : AnAction() { val endOffset = textRange.endOffset UITools.redoableRequest(request, "", event, { transformCompletion(markdownNewTableColParams, it, columnName) }, - { UITools.replaceString(document, startOffset, endOffset, it!!) } + { UITools.replaceString(document, startOffset, endOffset, it) } ) } @@ -51,7 +51,7 @@ class MarkdownNewTableColAction : AnAction() { companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false return if (ComputerLanguage.Markdown !== computerLanguage) false else null != getMarkdownNewTableColParams(e) } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownNewTableColsAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownNewTableColsAction.kt index ba461fa9..8cbc0fe9 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownNewTableColsAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownNewTableColsAction.kt @@ -22,7 +22,7 @@ class MarkdownNewTableColsAction : AnAction() { override fun actionPerformed(event: AnActionEvent) { val markdownNewTableColsParams = getMarkdownNewTableColsParams(event) - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val indent = UITools.getIndent(Objects.requireNonNull(markdownNewTableColsParams)!!.caret) val request = MarkdownNewTableColAction.newRowsRequest( settings, @@ -56,11 +56,11 @@ class MarkdownNewTableColsAction : AnAction() { companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false return if (ComputerLanguage.Markdown !== computerLanguage) false else null != getMarkdownNewTableColsParams( - e - ) + e + ) } fun getMarkdownNewTableColsParams(e: AnActionEvent): MarkdownNewTableColsParams? { diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownNewTableRowsAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownNewTableRowsAction.kt index d9c245db..afe575ee 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownNewTableRowsAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/MarkdownNewTableRowsAction.kt @@ -27,7 +27,7 @@ class MarkdownNewTableRowsAction : AnAction() { ).stream().map { obj: PsiElement -> obj.text!! }.collect(Collectors.toList()), 10, true ) val n: CharSequence = Integer.toString(rows.size * 2) - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val endOffset = markdownNewTableRowsParams.table.textRange.endOffset val document = event.getRequiredData(CommonDataKeys.EDITOR).document UITools.redoableRequest( @@ -41,7 +41,7 @@ class MarkdownNewTableRowsAction : AnAction() { class MarkdownNewTableRowsParams constructor(val caret: Caret, val table: PsiElement) companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false return if (ComputerLanguage.Markdown !== computerLanguage) false else null != getMarkdownNewTableRowsParams( e diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/ToStatementListAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/ToStatementListAction.kt index ac2cc04d..0e516c20 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/ToStatementListAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/ToStatementListAction.kt @@ -18,7 +18,7 @@ class ToStatementListAction : AnAction() { } override fun actionPerformed(event: AnActionEvent) { - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val caret = event.getRequiredData(CommonDataKeys.EDITOR).caretModel.primaryCaret val languageName = ComputerLanguage.getComputerLanguage(event)!!.name val endOffset: Int @@ -53,7 +53,7 @@ class ToStatementListAction : AnAction() { companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false if (ComputerLanguage.Markdown != computerLanguage) return false val caret = e.getData(CommonDataKeys.CARET) ?: return false diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/WikiLinksAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/WikiLinksAction.kt index 85499d21..696b0cc1 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/WikiLinksAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/markdown/WikiLinksAction.kt @@ -1,7 +1,7 @@ package com.github.simiacryptus.aicoder.actions.markdown import com.github.simiacryptus.aicoder.config.AppSettingsState -import com.github.simiacryptus.aicoder.openai.OpenAI_API +import com.github.simiacryptus.aicoder.openai.ui.OpenAI_API import com.github.simiacryptus.aicoder.util.ComputerLanguage import com.github.simiacryptus.aicoder.util.StringTools.replaceAllNonOverlapping import com.github.simiacryptus.aicoder.util.UITools @@ -22,7 +22,7 @@ class WikiLinksAction : AnAction() { } override fun actionPerformed(event: AnActionEvent) { - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val caret = event.getRequiredData(CommonDataKeys.EDITOR).caretModel.primaryCaret val languageName = ComputerLanguage.getComputerLanguage(event)!!.name val endOffset: Int @@ -30,7 +30,7 @@ class WikiLinksAction : AnAction() { val psiFile = PsiUtil.getPsiFile(event)!! val elements = PsiUtil.getAllIntersecting(psiFile, caret.selectionStart, caret.selectionEnd, "ListItem") val elementText: CharSequence - if(elements.isEmpty()) { + if (elements.isEmpty()) { elementText = caret.selectedText!! startOffset = caret.selectionStart endOffset = caret.selectionEnd @@ -70,15 +70,16 @@ class WikiLinksAction : AnAction() { .map { it.trim() }.filter { it.isNotBlank() } (extraWords + listOf(mainTerm)).distinct().map { it to "[$it]($linkTarget)" } } - replaceAllNonOverlapping(replaceString, *replacements.toTypedArray() ) + replaceAllNonOverlapping(replaceString, *replacements.toTypedArray()) }, { replaceString(document, startOffset, endOffset, it) }, OpenAI_API.filterStringResult(stripUnbalancedTerminators = false) ) } + companion object { private fun isEnabled(e: AnActionEvent): Boolean { - if(UITools.isSanctioned()) return false + if (UITools.isSanctioned()) return false val computerLanguage = ComputerLanguage.getComputerLanguage(e) ?: return false if (ComputerLanguage.Markdown != computerLanguage) return false val caret = e.getData(CommonDataKeys.CARET) ?: return false diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsComponent.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsComponent.kt new file mode 100644 index 00000000..066f42cf --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsComponent.kt @@ -0,0 +1,111 @@ +package com.github.simiacryptus.aicoder.config + +import com.github.simiacryptus.aicoder.openai.ui.OpenAI_API +import com.github.simiacryptus.aicoder.util.StyleUtil +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.ui.ComboBox +import com.intellij.ui.components.JBCheckBox +import com.intellij.ui.components.JBPasswordField +import com.intellij.ui.components.JBTextField +import com.jetbrains.rd.util.LogLevel +import java.awt.event.ActionEvent +import java.util.* +import javax.swing.AbstractAction +import javax.swing.JButton +import javax.swing.JComponent + +class AppSettingsComponent { + /* + Question: Why is the name annotation missing at runtime from the public field? + Answer: The annotation removes the name annotation from the public field at runtime. This allows the field to be accessed directly from Java code without the need for getter and setter methods. + */ + @Name("Style") + val style = JBTextField() + + @Suppress("unused") + val randomizeStyle = JButton(object : AbstractAction("Randomize Style") { + override fun actionPerformed(e: ActionEvent) { + style.text = StyleUtil.randomStyle() + } + }) + + @Suppress("unused") + val testStyle = JButton(object : AbstractAction("Test Style") { + override fun actionPerformed(e: ActionEvent) { + StyleUtil.demoStyle(style.text) + } + }) + + @Name("Token Counter") + val tokenCounter = JBTextField() + + @Suppress("unused") + val clearCounter = JButton(object : AbstractAction("Clear Token Counter") { + override fun actionPerformed(e: ActionEvent) { + tokenCounter.text = "0" + } + }) + + @Suppress("unused") + @Name("Human Language") + val humanLanguage = JBTextField() + + @Suppress("unused") + @Name("History Limit") + val historyLimit = JBTextField() + + @Suppress("unused") + @Name("Developer Tools") + val devActions = JBCheckBox() + + @Suppress("unused") + @Name("Suppress Progress (UNSAFE)") + val suppressProgress = JBCheckBox() + + @Suppress("unused") + @Name("API Log Level") + val apiLogLevel = ComboBox(Arrays.stream(LogLevel.values()).map { obj: LogLevel -> obj.name } + .toArray { size: Int -> arrayOfNulls(size) }) + + @Suppress("unused") + @Name("Temperature") + val temperature = JBTextField() + + @Suppress("unused") + @Name("Max Tokens") + val maxTokens = JBTextField() + + @Suppress("unused") + @Name("Max Prompt (Characters)") + val maxPrompt = JBTextField() + + @Suppress("unused") + @Name("Completion Model") + val model_completion = OpenAI_API.modelSelector + + @Name("Edit Model") + val model_edit = OpenAI_API.modelSelector + + @Name("API Threads") + val apiThreads = JBTextField() + + @Name("API Key") + val apiKey = JBPasswordField() + + @Suppress("unused") + @Name("API Base") + val apiBase = JBTextField() + + init { + tokenCounter.isEditable = false + } + + val preferredFocusedComponent: JComponent + get() = apiKey + + companion object { + private val log = Logger.getInstance( + AppSettingsComponent::class.java + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsConfigurable.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsConfigurable.kt new file mode 100644 index 00000000..ec55d73d --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsConfigurable.kt @@ -0,0 +1,61 @@ +package com.github.simiacryptus.aicoder.config + +import com.github.simiacryptus.aicoder.util.UITools +import com.intellij.openapi.options.Configurable +import com.intellij.util.ui.FormBuilder +import org.jetbrains.annotations.Nls +import java.util.* +import javax.swing.JComponent +import javax.swing.JPanel + +class AppSettingsConfigurable : Configurable { + var settingsComponent: AppSettingsComponent? = null + + @Volatile + private var mainPanel: JPanel? = null + override fun getDisplayName(): @Nls(capitalization = Nls.Capitalization.Title) String { + return "AICoder Settings" + } + + override fun getPreferredFocusedComponent(): JComponent? { + return Objects.requireNonNull(settingsComponent)?.preferredFocusedComponent + } + + override fun createComponent(): JComponent? { + if (null == mainPanel) { + synchronized(this) { + if (null == mainPanel) { + settingsComponent = AppSettingsComponent() + mainPanel = UITools.build(settingsComponent!!) + } + } + } + return mainPanel + } + + + override fun isModified(): Boolean { + val buffer = AppSettingsState() + if (settingsComponent != null) { + UITools.readKotlinUI(settingsComponent!!, buffer) + } + return buffer != AppSettingsState.instance + } + + override fun apply() { + if (settingsComponent != null) { + UITools.readKotlinUI(settingsComponent!!, AppSettingsState.instance) + } + } + + override fun reset() { + if (settingsComponent != null) { + UITools.writeKotlinUI(settingsComponent!!, AppSettingsState.instance) + } + } + + override fun disposeUIResources() { + settingsComponent = null + } +} + diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsState.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsState.kt new file mode 100644 index 00000000..3f068cdd --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsState.kt @@ -0,0 +1,156 @@ +package com.github.simiacryptus.aicoder.config + +import com.github.simiacryptus.aicoder.openai.core.CompletionRequest +import com.github.simiacryptus.aicoder.openai.core.EditRequest +import com.github.simiacryptus.aicoder.openai.translate.TranslationRequest +import com.github.simiacryptus.aicoder.openai.translate.TranslationRequestTemplate +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 com.jetbrains.rd.util.LogLevel +import java.util.* +import java.util.Map +import java.util.stream.Collectors +import kotlin.collections.ArrayList +import kotlin.collections.HashMap +import kotlin.collections.HashSet +import kotlin.collections.MutableList +import kotlin.collections.MutableMap +import kotlin.collections.Set +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.forEach +import kotlin.collections.getOrDefault +import kotlin.collections.remove + +@State(name = "org.intellij.sdk.settings.AppSettingsState", storages = [Storage("SdkSettingsPlugin.xml")]) +class AppSettingsState : PersistentStateComponent { + var apiBase = "https://api.openai.com/v1" + var apiKey = "" + var model_completion = "text-davinci-003" + var model_edit = "text-davinci-edit-001" + var maxTokens = 1000 + var temperature = 0.1 + var style = "" + + @Suppress("unused") + var tokenCounter = 0 + private val mostUsedHistory: MutableMap = HashMap() + private val mostRecentHistory: MutableList = ArrayList() + var historyLimit = 10 + var humanLanguage = "English" + var maxPrompt = 5000 + var translationRequestTemplate = TranslationRequestTemplate.XML + var apiLogLevel = LogLevel.Debug + var devActions = false + var suppressProgress = false + var apiThreads = 4 + fun createTranslationRequest(): TranslationRequest { + return translationRequestTemplate[this] + } + + fun createCompletionRequest(): CompletionRequest { + return CompletionRequest(this) + } + + fun createEditRequest(): EditRequest { + return EditRequest(this) + } + + override fun getState(): AppSettingsState { + return this + } + + override fun loadState(state: AppSettingsState) { + XmlSerializerUtil.copyBean(state, this) + } + + override fun equals(o: Any?): Boolean { + if (this === o) return true + if (o == null || javaClass != o.javaClass) return false + val that = o as AppSettingsState + if (maxTokens != that.maxTokens) return false + if (maxPrompt != that.maxPrompt) return false + if (java.lang.Double.compare(that.temperature, temperature) != 0) return false + if (humanLanguage != that.humanLanguage) return false + if (apiBase != that.apiBase) return false + if (apiKey != that.apiKey) return false + if (model_completion != that.model_completion) return false + if (model_edit != that.model_edit) return false + if (translationRequestTemplate != that.translationRequestTemplate) return false + if (apiLogLevel != that.apiLogLevel) return false + if (devActions != that.devActions) return false + return if (suppressProgress != that.suppressProgress) false else style == that.style + } + + override fun hashCode(): Int { + return Objects.hash( + apiBase, + apiKey, + model_completion, + model_edit, + maxTokens, + temperature, + translationRequestTemplate, + apiLogLevel, + devActions, + suppressProgress, + style + ) + } + + fun addInstructionToHistory(instruction: CharSequence) { + synchronized(mostRecentHistory) { + mostRecentHistory.add(instruction.toString()) + while (mostRecentHistory.size > historyLimit) { + mostRecentHistory.removeAt(0) + } + } + synchronized(mostUsedHistory) { + mostUsedHistory.put( + instruction.toString(), + mostUsedHistory.getOrDefault(instruction, 0) + 1 + ) + } + + // If the instruction history is bigger than the history limit, + // We'll make a set of strings to retain, + // We'll sort the instruction history by its value, + // And limit it to the history limit, + // Then we'll map the entry key and collect it in a set, + // Then we'll make a new hash set of the instruction history keys, + // And remove all the ones we want to retain, + // Then we'll remove all the ones we don't want to keep, + // And that's how we'll make sure the instruction history is neat! + if (mostUsedHistory.size > historyLimit) { + val retain = mostUsedHistory.entries.stream() + .sorted(Map.Entry.comparingByValue().reversed()) + .limit(historyLimit.toLong()) + .map { (key, _) -> key }.collect( + Collectors.toList() + ) + val toRemove = HashSet(mostUsedHistory.keys) + toRemove.removeAll(retain) + toRemove.removeAll(mostRecentHistory) + toRemove.forEach { key: CharSequence? -> + mostUsedHistory.remove( + key + ) + } + } + } + + val editHistory: Set + get() = mostUsedHistory.keys + + companion object { + @JvmStatic + val instance: AppSettingsState + get() { + val application = ApplicationManager.getApplication() + return if (null == application) AppSettingsState() else application.getService(AppSettingsState::class.java) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/config/Name.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/config/Name.kt new file mode 100644 index 00000000..7a74dc73 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/config/Name.kt @@ -0,0 +1,7 @@ +package com.github.simiacryptus.aicoder.config + +import java.util.* + +@Retention(AnnotationRetention.RUNTIME) +annotation class Name(val value: String) + diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/OpenAI_API.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/OpenAI_API.kt deleted file mode 100644 index 5912693d..00000000 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/OpenAI_API.kt +++ /dev/null @@ -1,645 +0,0 @@ -package com.github.simiacryptus.aicoder.openai - -import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.core.JsonProcessingException -import com.fasterxml.jackson.databind.MapperFeature -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.SerializationFeature -import com.fasterxml.jackson.databind.node.ObjectNode -import com.github.simiacryptus.aicoder.config.AppSettingsState -import com.github.simiacryptus.aicoder.util.IndentedText -import com.github.simiacryptus.aicoder.util.StringTools -import com.github.simiacryptus.aicoder.util.StringTools.restrictCharacterSet -import com.github.simiacryptus.aicoder.util.UITools -import com.google.common.util.concurrent.* -import com.google.gson.Gson -import com.google.gson.JsonObject -import com.intellij.openapi.diagnostic.Logger -import com.intellij.openapi.progress.ProgressIndicator -import com.intellij.openapi.progress.ProgressManager -import com.intellij.openapi.progress.Task -import com.intellij.openapi.progress.util.AbstractProgressIndicatorBase -import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.ComboBox -import com.intellij.ui.components.JBTextField -import com.jetbrains.rd.util.AtomicReference -import com.jetbrains.rd.util.LogLevel -import org.apache.http.HttpResponse -import org.apache.http.client.methods.HttpGet -import org.apache.http.client.methods.HttpPost -import org.apache.http.client.methods.HttpRequestBase -import org.apache.http.entity.ContentType -import org.apache.http.entity.StringEntity -import org.apache.http.entity.mime.HttpMultipartMode -import org.apache.http.entity.mime.MultipartEntityBuilder -import org.apache.http.impl.client.CloseableHttpClient -import org.apache.http.impl.client.HttpClientBuilder -import org.apache.http.util.EntityUtils -import java.io.IOException -import java.nio.charset.Charset -import java.util.* -import java.util.Map -import java.util.concurrent.* -import java.util.function.Consumer -import java.util.stream.Collectors -import java.util.stream.IntStream -import javax.swing.JComponent -import kotlin.collections.MutableMap -import kotlin.collections.set -import kotlin.collections.toTypedArray - -object OpenAI_API { - - @JvmStatic - private val threadFactory: ThreadFactory = ThreadFactoryBuilder().setNameFormat("API Thread %d").build() - private val allowedCharset = Charset.forName("ASCII") - - @JvmStatic - val pool: ListeningExecutorService = MoreExecutors.listeningDecorator( - ThreadPoolExecutor( - settingsState?.apiThreads ?: 1, - settingsState?.apiThreads ?: 1, - 0L, TimeUnit.MILLISECONDS, - LinkedBlockingQueue(), - threadFactory, - ThreadPoolExecutor.AbortPolicy() - ) - ) - - @JvmStatic - val scheduledPool: ListeningScheduledExecutorService = - MoreExecutors.listeningDecorator(ScheduledThreadPoolExecutor(1, threadFactory)) - - private val log = Logger.getInstance(OpenAI_API::class.java) - private val activeModelUI = WeakHashMap, Any>() - - @Transient - var settings: AppSettingsState? = null - - @Transient - private var comboBox: ComboBox? = null - val modelSelector: JComponent - get() { - if (null != comboBox) { - val element = ComboBox( - (IntStream.range(0, comboBox!!.itemCount) - .mapToObj(comboBox!!::getItemAt)).collect(Collectors.toList()).toTypedArray() - ) - activeModelUI[element] = Any() - return element - } - val apiKey: CharSequence = settingsState!!.apiKey - if (apiKey.toString().trim { it <= ' ' }.length > 0) { - try { - comboBox = ComboBox(arrayOf( - settingsState!!.model_completion, - settingsState!!.model_edit - )) - activeModelUI[comboBox] = Any() - onSuccess( - engines - ) { engines: ObjectNode -> - val data = engines["data"] - val items = - arrayOfNulls(data.size()) - for (i in 0 until data.size()) { - items[i] = data[i]["id"].asText() - } - Arrays.sort(items) - activeModelUI.keys.forEach(Consumer { ui: ComboBox -> - Arrays.stream(items).forEach(ui::addItem) - }) - } - return comboBox!! - } catch (e: Throwable) { - log.warn(e) - } - } - return JBTextField() - } - - fun complete(project: Project?, request: CompletionRequest, indent: CharSequence): ListenableFuture { - return complete(project, request, filterStringResult(indent)) - } - - fun complete( - project: Project?, - request: CompletionRequest, - filter: (CharSequence) -> CharSequence - ): ListenableFuture { - return map(complete(project, request)) { it.firstChoice.map(filter).orElse("")} - } - - fun edit(project: Project, request: EditRequest, indent: CharSequence): ListenableFuture { - return edit(project, request, filterStringResult(indent)) - } - - fun edit( - project: Project, - request: EditRequest, - filter: (CharSequence) -> CharSequence - ): ListenableFuture { - return map(edit(project, request)) { it.firstChoice.map(filter).orElse("") } - } - - var lastFetchedSettingsState = 0L - open val settingsState: AppSettingsState? - get() { - if (null == settings || lastFetchedSettingsState < System.currentTimeMillis() - (TimeUnit.SECONDS.toMillis(1))) { - lastFetchedSettingsState = System.currentTimeMillis() - settings = AppSettingsState.getInstance() - } - return settings - } - val engines: ListenableFuture - get() = pool.submit { - mapper.readValue( - get(settingsState!!.apiBase + "/engines"), - ObjectNode::class.java - ) - } - - @Throws(IOException::class, InterruptedException::class) - private fun post(url: String, body: String): String { - return post(url, body, 1) - } - - private fun complete( - project: Project?, - completionRequest: CompletionRequest - ): ListenableFuture { - val settings = settingsState - val withModel = completionRequest.uiIntercept() - withModel.fixup(settings!!) - return complete(project, CompletionRequest(withModel), settings, withModel.model) - } - - private fun edit(project: Project, request: EditRequest): ListenableFuture { - return edit(project, request, settingsState!!) - } - - private fun edit( - project: Project, - editRequest: EditRequest, - settings: AppSettingsState - ): ListenableFuture { - return map(moderateAsync(project, editRequest.toString())) { _: Any? -> - try { - val task: Task.WithResult = - object : Task.WithResult( - project, - "Text Completion", - false - ) { - override fun compute(indicator: ProgressIndicator): CompletionResponse { - try { - if (editRequest.input == null) { - log( - settings.apiLogLevel, String.format( - "Text Edit Request\nInstruction:\n\t%s\n", - editRequest.instruction.replace("\n", "\n\t") - ) - ) - } else { - log( - settings.apiLogLevel, String.format( - "Text Edit Request\nInstruction:\n\t%s\nInput:\n\t%s\n", - editRequest.instruction.replace("\n", "\n\t"), - editRequest.input!!.replace("\n", "\n\t") - ) - ) - } - val request: String = restrictCharacterSet(mapper.writeValueAsString(editRequest), allowedCharset) - val result = post(settings.apiBase + "/edits", request) - val completionResponse = processResponse(result, settings) - logComplete( - completionResponse.firstChoice.orElse("").toString().trim { it <= ' ' }, - settings - ) - return completionResponse - } catch (e: IOException) { - throw RuntimeException(e) - } catch (e: InterruptedException) { - throw RuntimeException(e) - } - } - } - if (null != project && !settingsState!!.suppressProgress) { - return@map ProgressManager.getInstance() - .run(task) - } else { - task.run(AbstractProgressIndicatorBase()) - return@map task.result - } - } catch (e: RuntimeException) { - throw e - } catch (e: Exception) { - throw RuntimeException(e) - } - } - } - - /** - * Processes the response from the server. - * - * @param result The response from the server. - * @param settings The application settings. - * @return The completion response. - * @throws IOException If an error occurs while processing the response. - */ - @Throws(IOException::class) - private fun processResponse(result: String, settings: AppSettingsState): CompletionResponse { - val jsonObject = Gson().fromJson( - result, - JsonObject::class.java - ) - if (jsonObject.has("error")) { - val errorObject = jsonObject.getAsJsonObject("error") - val errorMessage = errorObject["message"].asString - throw IOException(errorMessage) - } - val completionResponse = mapper.readValue( - result, - CompletionResponse::class.java - ) - if (completionResponse.usage != null) { - settings.tokenCounter += completionResponse.usage.total_tokens - } - return completionResponse - } - - private fun complete( - project: Project?, - completionRequest: CompletionRequest, - settings: AppSettingsState, - model: String - ): ListenableFuture { - val canBeCancelled = - true // Cancel doesn't seem to work; the cancel event is only dispatched after the request completes. - return map(moderateAsync(project, restrictCharacterSet(completionRequest.prompt, allowedCharset))) { _: Any? -> - run( - project, - object : Task.WithResult( - project, - "Text Completion", - canBeCancelled - ) { - var threadRef = - AtomicReference(null) - - override fun compute(indicator: ProgressIndicator): CompletionResponse { - val cancelMonitor = - scheduledPool.scheduleAtFixedRate( - { checkCanceled(indicator, threadRef) }, - 0, - 100, - TimeUnit.MILLISECONDS - ) - threadRef.getAndSet(Thread.currentThread()) - try { - logStart(completionRequest, settings) - val request: String = restrictCharacterSet(mapper.writeValueAsString(completionRequest), allowedCharset) - val result = - post(settings.apiBase + "/engines/" + model + "/completions", request) - val completionResponse = processResponse(result, settings) - val completionResult = StringTools.stripPrefix( - completionResponse.firstChoice.orElse("").toString().trim { it <= ' ' }, - completionRequest.prompt.trim { it <= ' ' }) - logComplete(completionResult, settings) - return completionResponse - } catch (e: IOException) { - log.error(e) - throw RuntimeException(e) - } catch (e: InterruptedException) { - throw RuntimeException(e) - } finally { - threadRef.getAndSet(null) - cancelMonitor.cancel(true) - } - } - - override fun onCancel() { - val thread = threadRef.get() - if (null != thread) { - log.warn(Arrays.stream( - thread.stackTrace - ).map(StackTraceElement::toString) - .collect(Collectors.joining("\n")) - ) - thread.interrupt() - } - super.onCancel() - } - }, 3 - ) - } - } - - private fun checkCanceled(indicator: ProgressIndicator, threadRef: AtomicReference) { - if (indicator.isCanceled) { - val thread = threadRef.get() - if (null != thread) { - thread.interrupt() - try { - clients[thread]!!.close() - } catch (e: IOException) { - log.warn("Error closing client: " + e.message) - } - } - } - } - - private fun logComplete(completionResult: CharSequence, settings: AppSettingsState) { - log( - settings.apiLogLevel, String.format( - "Text Completion Completion:\n\t%s", - completionResult.toString().replace("\n", "\n\t") - ) - ) - } - - private fun logStart(completionRequest: CompletionRequest, settings: AppSettingsState) { - if (completionRequest.suffix == null) { - log( - settings.apiLogLevel, String.format( - "Text Completion Request\nPrefix:\n\t%s\n", - completionRequest.prompt.replace("\n", "\n\t") - ) - ) - } else { - log( - settings.apiLogLevel, String.format( - "Text Completion Request\nPrefix:\n\t%s\nSuffix:\n\t%s\n", - completionRequest.prompt.replace("\n", "\n\t"), - completionRequest.suffix!!.replace("\n", "\n\t") - ) - ) - } - } - - private fun log(level: LogLevel, msg: String) { - val message = msg.trim { it <= ' ' }.replace("\n", "\n\t") - when (level) { - LogLevel.Error -> log.error(message) - LogLevel.Warn -> log.warn(message) - LogLevel.Info -> log.info(message) - else -> log.debug(message) - } - } - - private fun moderateAsync(project: Project?, text: String): ListenableFuture<*> { - return run( - project, - object : Task.WithResult, Exception?>(project, "Moderation", false) { - override fun compute(indicator: ProgressIndicator): ListenableFuture<*> { - return pool.submit { - val body: String - body = try { - mapper.writeValueAsString( - Map.of( - "input", - restrictCharacterSet(text, allowedCharset) - ) - ) - } catch (e: JsonProcessingException) { - throw RuntimeException(e) - } - val settings1: AppSettingsState = settingsState!! - val result: String - result = try { - post(settings1.apiBase + "/moderations", body) - } catch (e: IOException) { - throw RuntimeException(e) - } catch (e: InterruptedException) { - throw RuntimeException(e) - } - val jsonObject = - Gson().fromJson( - result, - JsonObject::class.java - ) - if (jsonObject.has("error")) { - val errorObject = jsonObject.getAsJsonObject("error") - throw RuntimeException(IOException(errorObject["message"].asString)) - } - val moderationResult = - jsonObject.getAsJsonArray("results")[0].asJsonObject - log( - LogLevel.Debug, - String.format( - "Moderation Request\nText:\n%s\n\nResult:\n%s", - text.replace("\n", "\n\t"), - result - ) - ) - if (moderationResult["flagged"].asBoolean) { - val categoriesObj = - moderationResult["categories"].asJsonObject - throw RuntimeException( - ModerationException( - "Moderation flagged this request due to " + categoriesObj.keySet() - .stream().filter { c: String? -> - categoriesObj[c].asBoolean - }.reduce { a: String, b: String -> "$a, $b" } - .orElse("???") - ) - ) - } - } - } - }, - 0 - ) - } - - private val clients: MutableMap = ConcurrentHashMap() - - /** - * Posts a request to the given URL with the given JSON body and retries if an IOException is thrown. - * - * @param url The URL to post the request to. - * @param json The JSON body of the request. - * @param retries The number of times to retry the request if an IOException is thrown. - * @return The response from the request. - * @throws IOException If an IOException is thrown and the number of retries is exceeded. - * @throws InterruptedException If the thread is interrupted while sleeping. - */ - @Throws(IOException::class, InterruptedException::class) - private fun post(url: String, json: String, retries: Int): String { - return post(jsonRequest(url, json), retries) - } - - private fun post( - request: HttpPost, - retries: Int - ): String { - try { - val client = HttpClientBuilder.create() - try { - client.build().use { httpClient -> - clients[Thread.currentThread()] = httpClient - val response: HttpResponse = httpClient.execute(request) - val entity = response.entity - return EntityUtils.toString(entity) - } - } finally { - clients.remove(Thread.currentThread()) - } - } catch (e: IOException) { - if (retries > 0) { - log.warn("Error posting request, retrying in 15 seconds", e) - Thread.sleep(15000) - return post(request, retries - 1) - } - throw e - } - } - - private fun jsonRequest(url: String, json: String): HttpPost { - val request = HttpPost(url) - request.addHeader("Content-Type", "application/json") - request.addHeader("Accept", "application/json") - authorize(request) - request.entity = StringEntity(json) - return request - } - - private val mapper: ObjectMapper - get() { - val mapper = ObjectMapper() - mapper - .enable(SerializationFeature.INDENT_OUTPUT) - .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS) - .enable(MapperFeature.USE_STD_BEAN_NAMING) - .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .activateDefaultTyping(mapper.polymorphicTypeValidator) - return mapper - } - - @Throws(IOException::class) - private fun authorize(request: HttpRequestBase) { - val settingsState = settingsState - var apiKey: CharSequence = settingsState!!.apiKey - if (apiKey.length == 0) { - synchronized(settingsState) { - apiKey = settingsState.apiKey - if (apiKey.length == 0) { - apiKey = UITools.queryAPIKey()!! - settingsState.apiKey = apiKey.toString() - } - } - } - request.addHeader("Authorization", "Bearer $apiKey") - } - - /** - * Gets the response from the given URL. - * - * @param url The URL to GET the response from. - * @return The response from the given URL. - * @throws IOException If an I/O error occurs. - */ - @Throws(IOException::class) - operator fun get(url: String?): String { - val client = HttpClientBuilder.create() - val request = HttpGet(url) - request.addHeader("Content-Type", "application/json") - request.addHeader("Accept", "application/json") - authorize(request) - client.build().use { httpClient -> - val response: HttpResponse = httpClient.execute(request) - val entity = response.entity - return EntityUtils.toString(entity) - } - } - - fun filterStringResult(indent: CharSequence = "", stripUnbalancedTerminators: Boolean = true): (CharSequence) -> CharSequence { - return { text -> - var result : CharSequence = text.toString().trim { it <= ' ' } - if (stripUnbalancedTerminators) { - result = StringTools.stripUnbalancedTerminators(result) - } - result = IndentedText.fromString2(result).withIndent(indent).toString() - indent.toString() + result - } - } - - private fun run(project: Project?, task: Task.WithResult, retries: Int): T { - return try { - if (null != project && !settingsState!!.suppressProgress) { - ProgressManager.getInstance().run(task) - } else { - task.run(AbstractProgressIndicatorBase()) - task.result - } - } catch (e: RuntimeException) { - if (isInterruptedException(e)) throw e - if (retries > 0) { - log.warn("Retrying request", e) - run(project, task, retries - 1) - } else { - throw e - } - } catch (e: InterruptedException) { - throw RuntimeException(e) - } catch (e: Exception) { - if (isInterruptedException(e)) throw RuntimeException(e) - if (retries > 0) { - log.warn("Retrying request", e) - try { - Thread.sleep(15000) - } catch (ex: InterruptedException) { - Thread.currentThread().interrupt() - } - run(project, task, retries - 1) - } else { - throw RuntimeException(e) - } - } - } - - private fun isInterruptedException(e: Throwable?): Boolean { - if (e is InterruptedException) return true - return if (e!!.cause != null && e.cause !== e) isInterruptedException( - e.cause - ) else false - } - - fun map( - moderateAsync: ListenableFuture, - o: com.google.common.base.Function - ): ListenableFuture = Futures.transform(moderateAsync, o, pool) - - fun onSuccess(moderateAsync: ListenableFuture, o: Consumer) { - Futures.addCallback(moderateAsync, object : FutureCallback { - override fun onSuccess(result: I) { - o.accept(result) - } - - override fun onFailure(t: Throwable) { - UITools.handle(t) - } - }, pool) - } - - fun text_to_speech(wavAudio: ByteArray, prompt: String = ""): String { - val url = settingsState!!.apiBase + "/audio/transcriptions" - val request = HttpPost(url) - request.addHeader("Accept", "application/json") - authorize(request) - val entity = MultipartEntityBuilder.create() - entity.setMode(HttpMultipartMode.RFC6532) - entity.addBinaryBody("file", wavAudio, ContentType.create("audio/x-wav"), "audio.wav") - entity.addTextBody("model", "whisper-1") - if(!prompt.isEmpty()) entity.addTextBody("prompt", prompt) - request.entity = entity.build() - val response = post(request, 3) - val jsonObject = Gson().fromJson(response, JsonObject::class.java) - if (jsonObject.has("error")) { - val errorObject = jsonObject.getAsJsonObject("error") - throw RuntimeException(IOException(errorObject["message"].asString)) - } - return jsonObject.get("text").asString!! - } - -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/async/AsyncAPI.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/async/AsyncAPI.kt new file mode 100644 index 00000000..6bb3954d --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/async/AsyncAPI.kt @@ -0,0 +1,279 @@ +package com.github.simiacryptus.aicoder.openai.async + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.databind.MapperFeature +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializationFeature +import com.github.simiacryptus.aicoder.config.AppSettingsState +import com.github.simiacryptus.aicoder.openai.core.CompletionRequest +import com.github.simiacryptus.aicoder.openai.core.CompletionResponse +import com.github.simiacryptus.aicoder.openai.core.CoreAPI +import com.github.simiacryptus.aicoder.openai.core.EditRequest +import com.github.simiacryptus.aicoder.util.StringTools +import com.github.simiacryptus.aicoder.util.UITools +import com.google.common.util.concurrent.* +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.progress.Task +import com.intellij.openapi.progress.util.AbstractProgressIndicatorBase +import com.intellij.openapi.project.Project +import com.jetbrains.rd.util.AtomicReference +import com.jetbrains.rd.util.LogLevel +import java.io.IOException +import java.nio.charset.Charset +import java.util.* +import java.util.concurrent.* +import java.util.function.Consumer +import java.util.stream.Collectors + +open class AsyncAPI( + val coreAPI: CoreAPI, + private val suppressProgress: Boolean = false +) { + + open fun checkCanceled(indicator: ProgressIndicator, threadRef: AtomicReference) {} + + fun edit( + project: Project?, + editRequest: EditRequest, + settings: AppSettingsState + ): ListenableFuture { + return map(moderateAsync(project, editRequest.toString())) { _: Any? -> + try { + val task: Task.WithResult = + object : Task.WithResult( + project, + "Text Completion", + false + ) { + override fun compute(indicator: ProgressIndicator): CompletionResponse { + try { + if (editRequest.input == null) { + log( + settings.apiLogLevel, String.format( + "Text Edit Request\nInstruction:\n\t%s\n", + editRequest.instruction.replace("\n", "\n\t") + ) + ) + } else { + log( + settings.apiLogLevel, String.format( + "Text Edit Request\nInstruction:\n\t%s\nInput:\n\t%s\n", + editRequest.instruction.replace("\n", "\n\t"), + editRequest.input!!.replace("\n", "\n\t") + ) + ) + } + val request: String = + StringTools.restrictCharacterSet( + mapper.writeValueAsString(editRequest), + allowedCharset + ) + val result = coreAPI.post(settings.apiBase + "/edits", request) + val completionResponse = coreAPI.processCompletionResponse(result) + coreAPI.logComplete( + completionResponse.firstChoice.orElse("").toString().trim { it <= ' ' } + ) + return completionResponse + } catch (e: IOException) { + throw RuntimeException(e) + } catch (e: InterruptedException) { + throw RuntimeException(e) + } + } + } + if (!suppressProgress) { + return@map ProgressManager.getInstance().run(task) + } else { + task.run(AbstractProgressIndicatorBase()) + return@map task.result + } + } catch (e: RuntimeException) { + throw e + } catch (e: Exception) { + throw RuntimeException(e) + } + } + } + + fun complete( + project: Project?, + completionRequest: CompletionRequest, + model: String + ): ListenableFuture { + val canBeCancelled = + true // Cancel doesn't seem to work; the cancel event is only dispatched after the request completes. + return map( + moderateAsync( + project, + StringTools.restrictCharacterSet(completionRequest.prompt, allowedCharset) + ) + ) { _: Any? -> + run( + object : Task.WithResult( + project, + "Text Completion", + canBeCancelled + ) { + var threadRef = + AtomicReference(null) + + override fun compute(indicator: ProgressIndicator): CompletionResponse { + val cancelMonitor = + scheduledPool.scheduleAtFixedRate( + { checkCanceled(indicator, threadRef) }, + 0, + 100, + TimeUnit.MILLISECONDS + ) + threadRef.getAndSet(Thread.currentThread()) + try { + return coreAPI.complete(completionRequest, model) + } catch (e: IOException) { + log.error(e) + throw RuntimeException(e) + } catch (e: InterruptedException) { + throw RuntimeException(e) + } finally { + threadRef.getAndSet(null) + cancelMonitor.cancel(true) + } + } + + override fun onCancel() { + val thread = threadRef.get() + if (null != thread) { + log.warn( + Arrays.stream( + thread.stackTrace + ).map(StackTraceElement::toString) + .collect(Collectors.joining("\n")) + ) + thread.interrupt() + } + super.onCancel() + } + }, + 3 + ) + } + } + + + private fun moderateAsync(project: Project?, text: String): ListenableFuture<*> { + return run( + object : Task.WithResult, Exception?>(project, "Moderation", false) { + override fun compute(indicator: ProgressIndicator): ListenableFuture<*> { + return pool.submit { + coreAPI.moderate(text) + } + } + }, + 0 + ) + } + + fun run(task: Task.WithResult, retries: Int): T { + return try { + if (!suppressProgress) { + ProgressManager.getInstance().run(task) + } else { + task.run(AbstractProgressIndicatorBase()) + task.result + } + } catch (e: RuntimeException) { + if (isInterruptedException(e)) throw e + if (retries > 0) { + log.warn("Retrying request", e) + run(task, retries - 1) + } else { + throw e + } + } catch (e: InterruptedException) { + throw RuntimeException(e) + } catch (e: Exception) { + if (isInterruptedException(e)) throw RuntimeException(e) + if (retries > 0) { + log.warn("Retrying request", e) + try { + Thread.sleep(15000) + } catch (ex: InterruptedException) { + Thread.currentThread().interrupt() + } + run(task, retries - 1) + } else { + throw RuntimeException(e) + } + } + } + + private fun isInterruptedException(e: Throwable?): Boolean { + if (e is InterruptedException) return true + return if (e!!.cause != null && e.cause !== e) isInterruptedException( + e.cause + ) else false + } + + companion object { + private val apiThreads = AppSettingsState.instance.apiThreads + val log = Logger.getInstance(AsyncAPI::class.java) + + fun log(level: LogLevel, msg: String) { + val message = msg.trim { it <= ' ' }.replace("\n", "\n\t") + when (level) { + LogLevel.Error -> CoreAPI.log.error(message) + LogLevel.Warn -> CoreAPI.log.warn(message) + LogLevel.Info -> CoreAPI.log.info(message) + else -> CoreAPI.log.debug(message) + } + } + + fun map( + moderateAsync: ListenableFuture, + o: com.google.common.base.Function + ): ListenableFuture = Futures.transform(moderateAsync, o, pool) + + val mapper: ObjectMapper + get() { + val mapper = ObjectMapper() + mapper + .enable(SerializationFeature.INDENT_OUTPUT) + .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS) + .enable(MapperFeature.USE_STD_BEAN_NAMING) + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .activateDefaultTyping(mapper.polymorphicTypeValidator) + return mapper + } + + val threadFactory: ThreadFactory = ThreadFactoryBuilder().setNameFormat("API Thread %d").build() + + val pool: ListeningExecutorService = MoreExecutors.listeningDecorator( + ThreadPoolExecutor( + apiThreads, + apiThreads, + 0L, TimeUnit.MILLISECONDS, + LinkedBlockingQueue(), + threadFactory, + ThreadPoolExecutor.AbortPolicy() + ) + ) + val scheduledPool: ListeningScheduledExecutorService = + MoreExecutors.listeningDecorator(ScheduledThreadPoolExecutor(1, threadFactory)) + + fun onSuccess(moderateAsync: ListenableFuture, o: Consumer) { + Futures.addCallback(moderateAsync, object : FutureCallback { + override fun onSuccess(result: I) { + o.accept(result) + } + + override fun onFailure(t: Throwable) { + UITools.handle(t) + } + }, pool) + } + } + + val allowedCharset: Charset = Charset.forName("ASCII") + +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/async/AsyncAPIImpl.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/async/AsyncAPIImpl.kt new file mode 100644 index 00000000..c9380f22 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/async/AsyncAPIImpl.kt @@ -0,0 +1,34 @@ +package com.github.simiacryptus.aicoder.openai.async + +import com.github.simiacryptus.aicoder.config.AppSettingsState +import com.github.simiacryptus.aicoder.openai.ui.OpenAI_API +import com.github.simiacryptus.aicoder.openai.core.CoreAPI +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.progress.ProgressIndicator +import com.jetbrains.rd.util.AtomicReference +import java.io.IOException + +class AsyncAPIImpl(core: CoreAPI, appSettingsState: AppSettingsState) : AsyncAPI( + core, + appSettingsState.suppressProgress +) { + @Override + override fun checkCanceled(indicator: ProgressIndicator, threadRef: AtomicReference) { + if (indicator.isCanceled) { + val thread = threadRef.get() + if (null != thread) { + thread.interrupt() + try { + coreAPI.clients[thread]!!.close() + } catch (e: IOException) { + log.warn("Error closing client: " + e.message) + } + } + } + } + + companion object { + @JvmStatic + private val log = Logger.getInstance(OpenAI_API::class.java) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ApiError.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ApiError.kt new file mode 100644 index 00000000..8e417cf3 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ApiError.kt @@ -0,0 +1,26 @@ +package com.github.simiacryptus.aicoder.openai.core + +class ApiError { + @Suppress("unused") + var message: String? = null + + @Suppress("unused") + var type: String? = null + + @Suppress("unused") + var param: String? = null + + @Suppress("unused") + var code: Double? = null + + @Suppress("unused") + constructor() + + @Suppress("unused") + constructor(message: String?, type: String?, param: String?, code: Double?) { + this.message = message + this.type = type + this.param = param + this.code = code + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ChatChoice.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ChatChoice.kt new file mode 100644 index 00000000..8f135643 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ChatChoice.kt @@ -0,0 +1,11 @@ +package com.github.simiacryptus.aicoder.openai.core + +class ChatChoice @Suppress("unused") constructor() { + var message: ChatMessage? = null + + @Suppress("unused") + var index = 0 + + @Suppress("unused") + var finish_reason: String? = null +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ChatMessage.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ChatMessage.kt new file mode 100644 index 00000000..018376b5 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ChatMessage.kt @@ -0,0 +1,17 @@ +package com.github.simiacryptus.aicoder.openai.core + +class ChatMessage { + enum class Role { + assistant, user, system + } + + var role: Role? = null + var content: String? = null + + @Suppress("unused") + constructor() + constructor(role: Role?, content: String?) { + this.role = role + this.content = content + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ChatRequest.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ChatRequest.kt new file mode 100644 index 00000000..f15f56fa --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ChatRequest.kt @@ -0,0 +1,9 @@ +package com.github.simiacryptus.aicoder.openai.core + +class ChatRequest @Suppress("unused") constructor() { + var messages = arrayOf() + var model: String? = null + var temperature = 0.0 + var max_tokens = 0 + var stop: Array? = null +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ChatResponse.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ChatResponse.kt new file mode 100644 index 00000000..9243412b --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ChatResponse.kt @@ -0,0 +1,32 @@ +package com.github.simiacryptus.aicoder.openai.core + +import java.util.* + +class ChatResponse { + @Suppress("unused") + var id: String? = null + + @Suppress("unused") + var `object`: String? = null + + @Suppress("unused") + var created: Long = 0 + + @Suppress("unused") + var model: String? = null + var choices: Array = arrayOf() + + @Suppress("unused") + var error: ApiError? = null + + @Suppress("unused") + var usage: Usage? = null + val response: Optional + get() = Optional.ofNullable(choices).flatMap( + { choices: Array? -> + Arrays.stream( + choices + ).findFirst() + }).map( + { choice: ChatChoice -> choice.message!!.content!!.trim { it <= ' ' } }) +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/CompletionChoice.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/CompletionChoice.kt new file mode 100644 index 00000000..c3f3a705 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/CompletionChoice.kt @@ -0,0 +1,25 @@ +package com.github.simiacryptus.aicoder.openai.core + +class CompletionChoice { + var text: String? = null + + @Suppress("unused") + var index = 0 + + @Suppress("unused") + var logprobs: LogProbs? = null + + @Suppress("unused") + var finish_reason: String? = null + + @Suppress("unused") + constructor() + + @Suppress("unused") + constructor(text: String?, index: Int, logprobs: LogProbs?, finish_reason: String?) { + this.text = text + this.index = index + this.logprobs = logprobs + this.finish_reason = finish_reason + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/CompletionRequest.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/CompletionRequest.kt new file mode 100644 index 00000000..6b6fa4f1 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/CompletionRequest.kt @@ -0,0 +1,107 @@ +package com.github.simiacryptus.aicoder.openai.core + +import com.github.simiacryptus.aicoder.config.AppSettingsState +import com.github.simiacryptus.aicoder.openai.ui.CompletionRequestWithModel +import com.github.simiacryptus.aicoder.openai.ui.InteractiveCompletionRequest +import com.github.simiacryptus.aicoder.util.UITools +import com.intellij.util.ui.FormBuilder +import java.util.* + +open class CompletionRequest { + constructor(config: AppSettingsState) : this("", config.temperature, config.maxTokens, null) + + fun uiIntercept(): CompletionRequestWithModel { + val withModel: CompletionRequestWithModel + withModel = if (this !is CompletionRequestWithModel) { + val settingsState = AppSettingsState.instance + if (!settingsState.devActions) { + CompletionRequestWithModel(this, settingsState.model_completion) + } else { + showModelEditDialog() + } + } else { + this + } + return withModel + } + + var prompt: String = "" + var suffix: String? = null + + @Suppress("unused") + var temperature = 0.0 + + @Suppress("unused") + var max_tokens = 0 + var stop: Array? = null + + @Suppress("unused") + var logprobs: Int? = null + + @Suppress("unused") + var echo = false + + @Suppress("unused") + constructor() + constructor(prompt: String, temperature: Double, max_tokens: Int, logprobs: Int?, vararg stop: CharSequence) { + this.prompt = prompt + this.temperature = temperature + this.max_tokens = max_tokens + this.stop = stop.map { it }.toTypedArray() + this.logprobs = logprobs + echo = false + } + + constructor(other: CompletionRequest) { + prompt = other.prompt + temperature = other.temperature + max_tokens = other.max_tokens + stop = other.stop + logprobs = other.logprobs + echo = other.echo + } + + fun appendPrompt(prompt: CharSequence): CompletionRequest { + this.prompt = this.prompt + prompt + return this + } + + fun addStops(vararg newStops: CharSequence): CompletionRequest { + val stops = ArrayList() + for (x in newStops) { + if (x.length > 0) { + stops.add(x) + } + } + if (!stops.isEmpty()) { + if (null != stop) Arrays.stream(stop).forEach { e: CharSequence -> + stops.add( + e + ) + } + stop = stops.stream().distinct().toArray { size: Int -> arrayOfNulls(size) } + } + return this + } + + fun setSuffix(suffix: CharSequence?): CompletionRequest { + this.suffix = suffix?.toString() + return this + } + + fun showModelEditDialog(): CompletionRequestWithModel { + val formBuilder = FormBuilder.createFormBuilder() + val instance = AppSettingsState.instance + val withModel = CompletionRequestWithModel(this, instance.model_completion) + val ui = InteractiveCompletionRequest(withModel) + UITools.addKotlinFields(ui, formBuilder) + UITools.writeKotlinUI(ui, withModel) + val mainPanel = formBuilder.panel + return if (UITools.showOptionDialog(mainPanel, arrayOf("OK"), title = "Completion Request") == 0) { + UITools.readKotlinUI(ui, withModel) + withModel + } else { + withModel + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/CompletionResponse.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/CompletionResponse.kt new file mode 100644 index 00000000..8c3241a1 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/CompletionResponse.kt @@ -0,0 +1,48 @@ +package com.github.simiacryptus.aicoder.openai.core + +import java.util.* + +class CompletionResponse { + @Suppress("unused") + var id: String? = null + + @Suppress("unused") + var `object`: String? = null + + @Suppress("unused") + var created = 0 + + @Suppress("unused") + var model: String? = null + var choices: Array = arrayOf() + + @Suppress("unused") + var error: ApiError? = null + + @Suppress("unused") + var usage: Usage? = null + + constructor() + constructor( + id: String?, + `object`: String?, + created: Int, + model: String?, + choices: Array, + error: ApiError? + ) { + this.id = id + this.`object` = `object` + this.created = created + this.model = model + this.choices = choices + this.error = error + } + + val firstChoice: Optional + get() = Optional.ofNullable(choices).flatMap { choices: Array? -> + Arrays.stream( + choices + ).findFirst() + }.map { choice: CompletionChoice -> choice.text!!.trim { it <= ' ' } } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/CoreAPI.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/CoreAPI.kt new file mode 100644 index 00000000..04e1a1f4 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/CoreAPI.kt @@ -0,0 +1,425 @@ +package com.github.simiacryptus.aicoder.openai.core + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.core.JsonProcessingException +import com.fasterxml.jackson.databind.MapperFeature +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializationFeature +import com.fasterxml.jackson.databind.node.ObjectNode +import com.github.simiacryptus.aicoder.openai.async.AsyncAPI +import com.github.simiacryptus.aicoder.openai.core.* +import com.github.simiacryptus.aicoder.openai.ui.OpenAI_API +import com.github.simiacryptus.aicoder.util.StringTools +import com.github.simiacryptus.aicoder.util.UITools +import com.google.gson.Gson +import com.google.gson.JsonObject +import com.intellij.openapi.diagnostic.Logger +import com.jetbrains.rd.util.LogLevel +import org.apache.http.HttpResponse +import org.apache.http.client.methods.HttpGet +import org.apache.http.client.methods.HttpPost +import org.apache.http.client.methods.HttpRequestBase +import org.apache.http.entity.ContentType +import org.apache.http.entity.StringEntity +import org.apache.http.entity.mime.HttpMultipartMode +import org.apache.http.entity.mime.MultipartEntityBuilder +import org.apache.http.impl.client.CloseableHttpClient +import org.apache.http.impl.client.HttpClientBuilder +import org.apache.http.util.EntityUtils +import java.awt.image.BufferedImage +import java.io.IOException +import java.net.URL +import java.nio.charset.Charset +import java.util.* +import java.util.Map +import java.util.concurrent.ConcurrentHashMap +import java.util.regex.Pattern +import javax.imageio.ImageIO +import kotlin.collections.MutableMap +import kotlin.collections.first +import kotlin.collections.joinToString +import kotlin.collections.map +import kotlin.collections.set + +open class CoreAPI( + val apiBase: String, + var key: String, + val logLevel: LogLevel +) { + fun getEngines(): Array { + val engines = mapper.readValue( + get(OpenAI_API.settingsState!!.apiBase + "/engines"), + ObjectNode::class.java + ) + val data = engines["data"] + val items = + arrayOfNulls(data.size()) + for (i in 0 until data.size()) { + items[i] = data[i]["id"].asText() + } + Arrays.sort(items) + return items + } + + val clients: MutableMap = ConcurrentHashMap() + + @Throws(IOException::class, InterruptedException::class) + fun post(url: String, body: String): String { + return post(url, body, 3) + } + + + fun logComplete(completionResult: CharSequence) { + log( + logLevel, String.format( + "Text Completion Completion:\n\t%s", + completionResult.toString().replace("\n", "\n\t") + ) + ) + } + + fun logStart(completionRequest: CompletionRequest) { + if (completionRequest.suffix == null) { + log( + logLevel, String.format( + "Text Completion Request\nPrefix:\n\t%s\n", + completionRequest.prompt.replace("\n", "\n\t") + ) + ) + } else { + log( + logLevel, String.format( + "Text Completion Request\nPrefix:\n\t%s\nSuffix:\n\t%s\n", + completionRequest.prompt.replace("\n", "\n\t"), + completionRequest.suffix!!.replace("\n", "\n\t") + ) + ) + } + } + + @Throws(IOException::class, InterruptedException::class) + fun post(url: String, json: String, retries: Int): String { + return post(jsonRequest(url, json), retries) + } + + fun post( + request: HttpPost, + retries: Int + ): String { + try { + val client = HttpClientBuilder.create() + try { + client.build().use { httpClient -> + clients[Thread.currentThread()] = httpClient + val response: HttpResponse = httpClient.execute(request) + val entity = response.entity + return EntityUtils.toString(entity) + } + } finally { + clients.remove(Thread.currentThread()) + } + } catch (e: IOException) { + if (retries > 0) { + log.warn("Error posting request, retrying in 15 seconds", e) + Thread.sleep(15000) + return post(request, retries - 1) + } + throw e + } + } + + fun jsonRequest(url: String, json: String): HttpPost { + val request = HttpPost(url) + request.addHeader("Content-Type", "application/json") + request.addHeader("Accept", "application/json") + authorize(request) + request.entity = StringEntity(json) + return request + } + + @Throws(IOException::class) + fun authorize(request: HttpRequestBase) { + var apiKey: CharSequence = key + if (apiKey.length == 0) { + synchronized(OpenAI_API.javaClass) { + apiKey = key + if (apiKey.length == 0) { + apiKey = UITools.queryAPIKey()!! + key = apiKey.toString() + } + } + } + request.addHeader("Authorization", "Bearer $apiKey") + } + + /** + * Gets the response from the given URL. + * + * @param url The URL to GET the response from. + * @return The response from the given URL. + * @throws IOException If an I/O error occurs. + */ + @Throws(IOException::class) + operator fun get(url: String?): String { + val client = HttpClientBuilder.create() + val request = HttpGet(url) + request.addHeader("Content-Type", "application/json") + request.addHeader("Accept", "application/json") + authorize(request) + client.build().use { httpClient -> + val response: HttpResponse = httpClient.execute(request) + val entity = response.entity + return EntityUtils.toString(entity) + } + } + + fun text_to_speech(wavAudio: ByteArray, prompt: String = ""): String { + val url = apiBase + "/audio/transcriptions" + val request = HttpPost(url) + request.addHeader("Accept", "application/json") + authorize(request) + val entity = MultipartEntityBuilder.create() + entity.setMode(HttpMultipartMode.RFC6532) + entity.addBinaryBody("file", wavAudio, ContentType.create("audio/x-wav"), "audio.wav") + entity.addTextBody("model", "whisper-1") + if (!prompt.isEmpty()) entity.addTextBody("prompt", prompt) + request.entity = entity.build() + val response = post(request, 3) + val jsonObject = Gson().fromJson(response, JsonObject::class.java) + if (jsonObject.has("error")) { + val errorObject = jsonObject.getAsJsonObject("error") + throw RuntimeException(IOException(errorObject["message"].asString)) + } + return jsonObject.get("text").asString!! + } + + fun text_to_image(prompt: String = "", resolution: Int = 1024, count: Int = 1): List { + val url = apiBase + "/images/generations" + val request = HttpPost(url) + request.addHeader("Accept", "application/json") + request.addHeader("Content-Type", "application/json") + authorize(request) + val jsonObject = JsonObject() + jsonObject.addProperty("prompt", prompt) + jsonObject.addProperty("n", count) + jsonObject.addProperty("size", "${resolution}x$resolution") + request.entity = StringEntity(jsonObject.toString()) + val response = post(request, 3) + val jsonObject2 = Gson().fromJson(response, JsonObject::class.java) + if (jsonObject2.has("error")) { + val errorObject = jsonObject2.getAsJsonObject("error") + throw RuntimeException(IOException(errorObject["message"].asString)) + } + val dataArray = jsonObject2.getAsJsonArray("data") + val images = ArrayList() + for (i in 0 until dataArray.size()) { + images.add(ImageIO.read(URL(dataArray[i].asJsonObject.get("url").asString))) + } + return images + } + + @Throws(IOException::class) + fun processCompletionResponse(result: String): CompletionResponse { + checkError(result) + val response = mapper.readValue( + result, + CompletionResponse::class.java + ) + if (response.usage != null) { + incrementTokens(response.usage!!.total_tokens) + } + return response + } + + @Throws(IOException::class) + fun processChatResponse(result: String): ChatResponse { + checkError(result) + val response = mapper.readValue( + result, + ChatResponse::class.java + ) + if (response.usage != null) { + incrementTokens(response.usage!!.total_tokens) + } + return response + } + + private val maxTokenErrorMessage = Pattern.compile( + """This model's maximum context length is (\d+) tokens. However, you requested (\d+) tokens \((\d+) in the messages, (\d+) in the completion\). Please reduce the length of the messages or completion.""" + ) + + private fun checkError(result: String) { + try { + val jsonObject = Gson().fromJson( + result, + JsonObject::class.java + ) + if (jsonObject.has("error")) { + val errorObject = jsonObject.getAsJsonObject("error") + val errorMessage = errorObject["message"].asString + val matcher = maxTokenErrorMessage.matcher(errorMessage) + if (matcher.find()) { + val modelMax = matcher.group(1).toInt() + val request = matcher.group(2).toInt() + val messages = matcher.group(3).toInt() + val completion = matcher.group(4).toInt() + throw ModelMaxException(modelMax, request, messages, completion) + } + throw IOException(errorMessage) + } + } catch (e: com.google.gson.JsonSyntaxException) { + throw IOException("Invalid JSON response: $result") + } + } + + open fun incrementTokens(totalTokens: Int) {} + + companion object { + val log = Logger.getInstance(CoreAPI::class.java) + + fun log(level: LogLevel, msg: String) { + val message = msg.trim { it <= ' ' }.replace("\n", "\n\t") + when (level) { + LogLevel.Error -> log.error(message) + LogLevel.Warn -> log.warn(message) + LogLevel.Info -> log.info(message) + else -> log.debug(message) + } + } + } + + val allowedCharset = Charset.forName("ASCII") + val mapper: ObjectMapper + get() { + val mapper = ObjectMapper() + mapper + .enable(SerializationFeature.INDENT_OUTPUT) + .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS) + .enable(MapperFeature.USE_STD_BEAN_NAMING) + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .activateDefaultTyping(mapper.polymorphicTypeValidator) + return mapper + } + + fun complete( + completionRequest: CompletionRequest, + model: String + ): CompletionResponse { + logStart(completionRequest) + val completionResponse = try { + val request: String = + StringTools.restrictCharacterSet( + AsyncAPI.mapper.writeValueAsString(completionRequest), + allowedCharset + ) + val result = + post(apiBase + "/engines/" + model + "/completions", request) + processCompletionResponse(result) + } catch (e: ModelMaxException) { + completionRequest.max_tokens = (e.modelMax - e.messages) - 1 + val request: String = + StringTools.restrictCharacterSet( + AsyncAPI.mapper.writeValueAsString(completionRequest), + allowedCharset + ) + val result = + post(apiBase + "/engines/" + model + "/completions", request) + processCompletionResponse(result) + } + val completionResult = StringTools.stripPrefix( + completionResponse.firstChoice.orElse("").toString().trim { it <= ' ' }, + completionRequest.prompt.trim { it <= ' ' }) + logComplete(completionResult) + return completionResponse + } + + fun chat( + completionRequest: ChatRequest + ): ChatResponse { + logStart(completionRequest) + val url = apiBase + "/chat/completions" + val completionResponse = try { + val result = post( + url, StringTools.restrictCharacterSet( + AsyncAPI.mapper.writeValueAsString(completionRequest), + allowedCharset + ) + ) + processChatResponse(result) + } catch (e: ModelMaxException) { + completionRequest.max_tokens = (e.modelMax - e.messages) - 1 + val result = post( + url, StringTools.restrictCharacterSet( + AsyncAPI.mapper.writeValueAsString(completionRequest), + allowedCharset + ) + ) + processChatResponse(result) + } + val completionResult = completionResponse.choices.first().message!!.content!!.trim { it <= ' ' } + logComplete(completionResult) + return completionResponse + } + + private fun logStart(completionRequest: ChatRequest) { + log( + logLevel, String.format( + "Text Completion Request\nPrefix:\n\t%s\n", + completionRequest.messages.map { it.content }.joinToString { "\n" }.replace("\n", "\n\t") + ) + ) + } + + fun moderate(text: String) { + val body: String = try { + AsyncAPI.mapper.writeValueAsString( + Map.of( + "input", + StringTools.restrictCharacterSet(text, allowedCharset) + ) + ) + } catch (e: JsonProcessingException) { + throw RuntimeException(e) + } + val result: String = try { + this.post(apiBase + "/moderations", body) + } catch (e: IOException) { + throw RuntimeException(e) + } catch (e: InterruptedException) { + throw RuntimeException(e) + } + val jsonObject = + Gson().fromJson( + result, + JsonObject::class.java + ) + if (jsonObject.has("error")) { + val errorObject = jsonObject.getAsJsonObject("error") + throw RuntimeException(IOException(errorObject["message"].asString)) + } + val moderationResult = + jsonObject.getAsJsonArray("results")[0].asJsonObject + AsyncAPI.log( + LogLevel.Debug, + String.format( + "Moderation Request\nText:\n%s\n\nResult:\n%s", + text.replace("\n", "\n\t"), + result + ) + ) + if (moderationResult["flagged"].asBoolean) { + val categoriesObj = + moderationResult["categories"].asJsonObject + throw RuntimeException( + ModerationException( + "Moderation flagged this request due to " + categoriesObj.keySet() + .stream().filter { c: String? -> + categoriesObj[c].asBoolean + }.reduce { a: String, b: String -> "$a, $b" } + .orElse("???") + ) + ) + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/EditRequest.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/EditRequest.kt new file mode 100644 index 00000000..6169982b --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/EditRequest.kt @@ -0,0 +1,121 @@ +package com.github.simiacryptus.aicoder.openai.core + +import com.github.simiacryptus.aicoder.config.AppSettingsState +import com.github.simiacryptus.aicoder.openai.ui.InteractiveEditRequest +import com.github.simiacryptus.aicoder.util.UITools +import com.intellij.util.ui.FormBuilder + +class EditRequest { + var model: String = "" + var input: String? = null + var instruction: String = "" + + @Suppress("unused") + var temperature: Double? = null + + @Suppress("unused") + var n: Int? = null + var top_p: Double? = null + + @Suppress("unused") + constructor() + constructor(settingsState: AppSettingsState) { + setInstruction("") + setModel(settingsState.model_edit) + setTemperature(settingsState.temperature) + } + + constructor(instruction: String) { + setInstruction(instruction) + setModel(AppSettingsState.instance.model_edit) + setTemperature(AppSettingsState.instance.temperature) + } + + constructor(instruction: String, input: String?) { + setInput(input) + setInstruction(instruction) + setModel(AppSettingsState.instance.model_edit) + setTemperature(AppSettingsState.instance.temperature) + } + + constructor(model: String, input: String?, instruction: String, temperature: Double?) { + setModel(model) + setInput(input) + setInstruction(instruction) + setTemperature(temperature) + } + + constructor(obj: EditRequest) { + model = obj.model + top_p = obj.top_p + input = obj.input + instruction = obj.instruction + temperature = obj.temperature + n = obj.n + } + + fun setModel(model: String): EditRequest { + this.model = model + return this + } + + fun setInput(input: String?): EditRequest { + this.input = input + return this + } + + fun setInstruction(instruction: String): EditRequest { + this.instruction = instruction + return this + } + + fun setTemperature(temperature: Double?): EditRequest { + top_p = null + this.temperature = temperature + return this + } + + fun setN(n: Int?): EditRequest { + this.n = n + return this + } + + fun setTop_p(top_p: Double?): EditRequest { + temperature = null + this.top_p = top_p + return this + } + + override fun toString(): String { + return "EditRequest{" + "model='" + model + '\'' + + ", input='" + input + '\'' + + ", instruction='" + instruction + '\'' + + ", temperature=" + temperature + + ", n=" + n + + ", top_p=" + top_p + + '}' + } + + fun showModelEditDialog(): EditRequest { + val formBuilder = FormBuilder.createFormBuilder() + val withModel = EditRequest(this) + val ui = InteractiveEditRequest(withModel) + UITools.addKotlinFields(ui, formBuilder) + UITools.writeKotlinUI(ui, withModel) + val mainPanel = formBuilder.panel + return if (UITools.showOptionDialog(mainPanel, arrayOf("OK"), title = "Completion Request") == 0) { + UITools.readKotlinUI(ui, withModel) + withModel + } else { + withModel + } + } + + fun uiIntercept(): EditRequest { + return if (AppSettingsState.instance.devActions) { + showModelEditDialog() + } else { + this + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/Engine.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/Engine.kt new file mode 100644 index 00000000..de1c9ab7 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/Engine.kt @@ -0,0 +1,51 @@ +package com.github.simiacryptus.aicoder.openai.core + +class Engine { + @Suppress("unused") + var id: String? = null + + @Suppress("unused") + var ready = false + + @Suppress("unused") + var owner: String? = null + + @Suppress("unused") + var `object`: String? = null + + @Suppress("unused") + var created: Int? = null + + @Suppress("unused") + var permissions: String? = null + + @Suppress("unused") + var replicas: Int? = null + + @Suppress("unused") + var max_replicas: Int? = null + + @Suppress("unused") + constructor() + + @Suppress("unused") + constructor( + id: String?, + ready: Boolean, + owner: String?, + `object`: String?, + created: Int?, + permissions: String?, + replicas: Int?, + max_replicas: Int? + ) { + this.id = id + this.ready = ready + this.owner = owner + this.`object` = `object` + this.created = created + this.permissions = permissions + this.replicas = replicas + this.max_replicas = max_replicas + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/LogProbs.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/LogProbs.kt new file mode 100644 index 00000000..28339670 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/LogProbs.kt @@ -0,0 +1,33 @@ +package com.github.simiacryptus.aicoder.openai.core + +import com.fasterxml.jackson.databind.node.ObjectNode + +class LogProbs { + @Suppress("unused") + var tokens: Array = arrayOf() + + @Suppress("unused") + var token_logprobs: DoubleArray = DoubleArray(0) + + @Suppress("unused") + var top_logprobs: Array = arrayOf() + + @Suppress("unused") + var text_offset: IntArray = IntArray(0) + + @Suppress("unused") + constructor() + + @Suppress("unused") + constructor( + tokens: Array, + token_logprobs: DoubleArray, + top_logprobs: Array, + text_offset: IntArray + ) { + this.tokens = tokens + this.token_logprobs = token_logprobs + this.top_logprobs = top_logprobs + this.text_offset = text_offset + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ModelMaxException.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ModelMaxException.kt new file mode 100644 index 00000000..154283f0 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ModelMaxException.kt @@ -0,0 +1,10 @@ +package com.github.simiacryptus.aicoder.openai.core + +import java.io.IOException + +class ModelMaxException( + val modelMax: Int, + val request: Int, + val messages: Int, + val completion: Int +) : IOException("Model max exceeded: $modelMax, request: $request, messages: $messages, completion: $completion") \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ModerationException.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ModerationException.kt new file mode 100644 index 00000000..e9bde0f0 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/ModerationException.kt @@ -0,0 +1,3 @@ +package com.github.simiacryptus.aicoder.openai.core + +class ModerationException(message: String?) : Exception(message) \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/Response.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/Response.kt new file mode 100644 index 00000000..db648da5 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/Response.kt @@ -0,0 +1,18 @@ +package com.github.simiacryptus.aicoder.openai.core + +class Response { + @Suppress("unused") + var `object`: String? = null + + @Suppress("unused") + private var data: Array = arrayOf() + + @Suppress("unused") + constructor() + + @Suppress("unused") + constructor(`object`: String?, data: Array) { + this.`object` = `object` + this.data = data + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/Usage.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/Usage.kt new file mode 100644 index 00000000..41ce403a --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/core/Usage.kt @@ -0,0 +1,12 @@ +package com.github.simiacryptus.aicoder.openai.core + +class Usage @Suppress("unused") constructor() { + @Suppress("unused") + var prompt_tokens = 0 + + @Suppress("unused") + var completion_tokens = 0 + + @Suppress("unused") + var total_tokens = 0 +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/proxy/ChatProxy.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/proxy/ChatProxy.kt new file mode 100644 index 00000000..e50ae166 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/proxy/ChatProxy.kt @@ -0,0 +1,87 @@ +package com.github.simiacryptus.aicoder.openai.proxy + +import com.github.simiacryptus.aicoder.openai.core.ChatMessage +import com.github.simiacryptus.aicoder.openai.core.ChatRequest +import com.github.simiacryptus.aicoder.openai.core.CoreAPI +import com.jetbrains.rd.util.LogLevel +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +class ChatProxy( + apiKey: String, + private val model: String = "gpt-3.5-turbo", + private val maxTokens: Int = 3500, + private val temperature: Double = 0.7, + private val verbose: Boolean = false, + base: String = "https://api.openai.com/v1", + apiLog: String = "api.${ + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss")) + }.log.json" +) : GPTProxyBase(apiLog) { + val api: CoreAPI + + init { + api = CoreAPI(base, apiKey, LogLevel.Debug) + } + + override fun complete(prompt: ProxyRequest, vararg examples: ProxyRecord): String { + if (verbose) println(prompt) + val request = ChatRequest() + request.messages = ( + listOf( + ChatMessage( + ChatMessage.Role.system, """ + |You are a JSON-RPC Service serving the following method: + |${prompt.methodName} + |Requests contain the following arguments: + |${prompt.argList.keys.joinToString("\n ")} + |Responses are of type: + |${prompt.responseType} + |Responses are expected to be a single JSON object + |All input arguments are optional + |""".trimMargin().trim() + ) + ) + + examples.flatMap { + listOf( + ChatMessage(ChatMessage.Role.user, argsToString(it.argList)), + ChatMessage(ChatMessage.Role.assistant, it.response) + ) + } + + listOf(ChatMessage(ChatMessage.Role.user, argsToString(prompt.argList))) + ).toTypedArray() + request.model = model + request.max_tokens = maxTokens + request.temperature = temperature + val completion = api.chat(request).response.get().toString() + if (verbose) println(completion) + val trimPrefix = trimPrefix(completion) + val trimSuffix = trimSuffix(trimPrefix.first) + return trimSuffix.first + } + + private fun trimPrefix(completion: String): Pair { + val start = completion.indexOf('{') + if (start < 0) { + return completion to "" + } else { + val substring = completion.substring(start) + return substring to completion.substring(0, start) + } + } + + private fun trimSuffix(completion: String): Pair { + val end = completion.lastIndexOf('}') + if (end < 0) { + return completion to "" + } else { + val substring = completion.substring(0, end + 1) + return substring to completion.substring(end + 1) + } + } + + private fun argsToString(argList: Map) = + "{" + argList.entries.joinToString(",\n", transform = { (argName, argValue) -> + """"$argName": $argValue""" + }) + "}" +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/proxy/CompletionProxy.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/proxy/CompletionProxy.kt new file mode 100644 index 00000000..897a49ea --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/proxy/CompletionProxy.kt @@ -0,0 +1,46 @@ +package com.github.simiacryptus.aicoder.openai.proxy + +import com.github.simiacryptus.aicoder.openai.core.CompletionRequest +import com.github.simiacryptus.aicoder.openai.core.CoreAPI +import com.jetbrains.rd.util.LogLevel + +class CompletionProxy( + apiKey: String, + private val model: String = "text-davinci-003", + private val maxTokens: Int = 1000, + private val temperature: Double = 0.7, + private val verbose: Boolean = false, + base: String = "https://api.openai.com/v1", + apiLog: String +) : GPTProxyBase(apiLog) { + val api: CoreAPI + + init { + api = CoreAPI(base, apiKey, LogLevel.Debug) + } + + override fun complete(prompt: ProxyRequest, vararg examples: ProxyRecord): String { + if(verbose) println(prompt) + val request = CompletionRequest() + val argList = prompt.argList + val methodName = prompt.methodName + val responseType = prompt.responseType + request.prompt = """ + Method: $methodName + Response Type: + ${responseType.replace("\n", "\n ")} + Request: + { + ${argList.entries.joinToString(",\n", transform = { (argName, argValue) -> + """"$argName": $argValue""" + }).replace("\n", "\n ")} + } + Response: + {""".trimIndent() + request.max_tokens = maxTokens + request.temperature = temperature + val completion = api.complete(request, model).firstChoice.get().toString() + if(verbose) println(completion) + return "{$completion" + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/proxy/GPTProxyBase.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/proxy/GPTProxyBase.kt new file mode 100644 index 00000000..f595e6c5 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/proxy/GPTProxyBase.kt @@ -0,0 +1,210 @@ +package com.github.simiacryptus.aicoder.openai.proxy + +import com.fasterxml.jackson.core.JsonParseException +import com.fasterxml.jackson.core.json.JsonReadFeature +import com.fasterxml.jackson.databind.ObjectMapper +import com.github.simiacryptus.aicoder.util.StringTools.indentJoin +import java.io.BufferedWriter +import java.io.File +import java.io.FileWriter +import java.lang.reflect.* + +abstract class GPTProxyBase( + apiLogFile: String +) { + abstract fun complete(prompt: ProxyRequest, vararg examples: ProxyRecord): String + + fun create(clazz: Class): T { + return Proxy.newProxyInstance(clazz.classLoader, arrayOf(clazz)) { proxy, method, args -> + if (method.name == "toString") return@newProxyInstance clazz.simpleName + val prompt = ProxyRequest( + method.name, + typeToName(method.returnType), + (args ?: arrayOf()).zip(method.parameters) + .filter> { (arg: Any?, _) -> arg != null } + .map, Pair> { (arg, param) -> + param.name to toJson(arg!!) + }.toMap()) + for (retry in 0 until 3) { + val result = complete(prompt, *examples[method.name]?.toTypedArray() ?: arrayOf()) + writeToJsonLog(ProxyRecord(prompt.methodName, prompt.argList, result)) + try { + return@newProxyInstance fromJson(result, method.returnType) + } catch (e: JsonParseException) { + println("Failed to parse response: $result") + println("Retrying...") + } + } + } as T + } + + private val apiLog = openApiLog(apiLogFile) + private val examples = HashMap>() + private fun loadExamples(file: File = File("api.examples.json")) : List { + if (!file.exists()) return listOf() + val json = file.readText() + return fromJson(json, object : ArrayList() {}.javaClass) + } + fun addExamples(file: File) { + examples.putAll(loadExamples(file).groupBy { it.methodName }) + } + + private fun openApiLog(file: String): BufferedWriter { + val writer = BufferedWriter(FileWriter(File(file))) + writer.write("[") + writer.newLine() + writer.flush() + return writer + } + private fun writeToJsonLog(record: ProxyRecord) { + apiLog.write(toJson(record)) + apiLog.write(",") + apiLog.newLine() + apiLog.flush() + } + + open fun toJson(data: Any): String { + return objectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(data) + } + + open fun fromJson(data: String, type: Class): T { + if (data.isNotEmpty()) try { + if (type.isAssignableFrom(String::class.java)) return data as T + return objectMapper().readValue(data, type) + } + catch (e: JsonParseException) { +// log.error("Error parsing JSON", e) + throw e + } + catch (e: Exception) { + log.error("Error parsing JSON", e) + } + return type.getConstructor().newInstance() + } + + open fun objectMapper(): ObjectMapper { + return ObjectMapper() + .enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature()) + .configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + } + + data class ProxyRequest( + val methodName: String = "", + val responseType: String = "", + val argList: Map = mapOf() + ) + + data class ProxyRecord( + val methodName: String = "", + val argList: Map = mapOf(), + val response: String = "" + ) + + companion object { + val log = org.slf4j.LoggerFactory.getLogger(GPTProxyBase::class.java) + + fun typeToName(type: Class<*>?): String { + // Convert a type to API documentation including type name and type structure, recusively expanding child types + if (type == null) { + return "null" + } + if (type.isPrimitive) { + return type.simpleName + } + if (type.isArray) { + return "Array<${typeToName(type.componentType)}>" + } + if (type.isEnum) { + return type.simpleName + } + if (type.isAssignableFrom(List::class.java)) { + if (type.isAssignableFrom(ParameterizedType::class.java)) { + val genericType = (type as ParameterizedType).actualTypeArguments[0] + return "List<${typeToName(genericType as Class<*>)}>" + } else { + return "List" + } + } + if (type.isAssignableFrom(Map::class.java)) { + val keyType = (type as ParameterizedType).actualTypeArguments[0] + val valueType = (type as ParameterizedType).actualTypeArguments[1] + return "Map<${typeToName(keyType as Class<*>)}, ${typeToName(valueType as Class<*>)}>" + } + if (type.getPackage()?.name?.startsWith("java") == true) { + return type.simpleName + } + val typeDescription = typeDescription(type) + return typeDescription.toString() + } + + private fun typeDescription(clazz: Class<*>): TypeDescription { + val apiDocumentation = if (clazz.isArray) { + return TypeDescription("Array<${typeDescription(clazz.componentType)}>") + } else if (clazz.isAssignableFrom(List::class.java)) { + if (clazz.isAssignableFrom(ParameterizedType::class.java)) { + val genericType = (clazz as ParameterizedType).actualTypeArguments[0] + return TypeDescription("List<${typeDescription(genericType as Class<*>)}>") + } else { + return TypeDescription("List") + } + } else if (clazz.isAssignableFrom(Map::class.java)) { + val keyType = (clazz as ParameterizedType).actualTypeArguments[0] + val valueType = (clazz as ParameterizedType).actualTypeArguments[1] + return TypeDescription("Map<${typeDescription(keyType as Class<*>)}, ${typeDescription(valueType as Class<*>)}}>") + } else if (clazz == String::class.java) { + return TypeDescription(clazz.simpleName) + } else { + TypeDescription(clazz.simpleName) + } + if (clazz.isPrimitive) return apiDocumentation + if (clazz.isEnum) return apiDocumentation + + for (field in clazz.declaredFields) { + if (field.name.startsWith("\$")) continue + // Get ParameterizedType for field + val type = field.genericType + if (type is ParameterizedType) { + // Get raw type + val rawType = type.rawType as Class<*> + if (rawType.isAssignableFrom(List::class.java)) { + // Get type of list elements + val elementType = type.actualTypeArguments[0] as Class<*> + apiDocumentation.fields.add( + FieldData( + field.name, + TypeDescription("List<${typeDescription(elementType)}>") + ) + ) + continue + } + } + apiDocumentation.fields.add(FieldData(field.name, typeDescription(field.type))) + } + return apiDocumentation + } + + private fun typeDescription(clazz: Type): TypeDescription { + if (clazz is Class<*>) return typeDescription(clazz) + if (clazz is ParameterizedType) { + val rawType = clazz.rawType as Class<*> + if (rawType.isAssignableFrom(List::class.java)) { + // Get type of list elements + val elementType = clazz.actualTypeArguments[0] as Class<*> + return TypeDescription("List<${typeDescription(elementType)}>") + } + } + return TypeDescription(clazz.typeName) + } + + class TypeDescription(val name: String) { + val fields: ArrayList = ArrayList() + override fun toString(): String { + return if (fields.isEmpty()) name else indentJoin(fields) + } + } + + class FieldData(val name: String, val type: TypeDescription) { + override fun toString(): String = """"$name": $type""" + } + } +} diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/proxy/SoftwareProjectAI.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/proxy/SoftwareProjectAI.kt new file mode 100644 index 00000000..0d4005e5 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/proxy/SoftwareProjectAI.kt @@ -0,0 +1,76 @@ +package com.github.simiacryptus.aicoder.openai.proxy + +interface SoftwareProjectAI { + fun newProject(description: String): Project + + data class Project( + val name: String = "", + val description: String = "", + val language: String = "", + val libraries: List = listOf(), + val buildTools: List = listOf(), + ) + + fun getProjectStatements(project: Project): ProjectStatements + + data class ProjectStatements( + val assumptions: List = listOf(), + val requirements: List = listOf(), + val risks: List = listOf(), + ) + + fun buildProjectDesign(project: Project, requirements: ProjectStatements): ProjectDesign + + data class ProjectDesign( + val designDetails: List = listOf(), + val tests: List = listOf(), + ) + + fun buildProjectFileSpecifications(project: Project, requirements: ProjectStatements, design: ProjectDesign, recursive: Boolean = true): FileList + + data class FileList( + val files: List = listOf(), + ) + + data class FileSpecification( + val location: FilePath = FilePath(), + val description: String = "", + val requires: List = listOf(), + val publicProperties: List = listOf(), + val publicMethodSignatures: List = listOf(), + ) + + data class FilePath( + val path: String = "", + val name: String = "", + val extension: String = "", + ) { + override fun toString(): String { + return "${path.trimEnd('/')}/$name.$extension" + } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as FilePath + if (path != other.path) return false + if (name != other.name) return false + if (extension != other.extension) return false + return true + } + override fun hashCode(): Int { + var result = path.hashCode() + result = 31 * result + name.hashCode() + result = 31 * result + extension.hashCode() + return result + } + + } + + fun implement(project: Project, imports: List, specification: FileSpecification): SourceCode + + data class SourceCode( + val language: String = "", + val code: String = "", + ) + +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/translate/BaseTranslationRequest.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/translate/BaseTranslationRequest.kt index 4e7230f8..0101df8d 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/translate/BaseTranslationRequest.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/translate/BaseTranslationRequest.kt @@ -1,7 +1,6 @@ package com.github.simiacryptus.aicoder.openai.translate import com.github.simiacryptus.aicoder.config.AppSettingsState -import java.util.* abstract class BaseTranslationRequest>(settings: AppSettingsState) : TranslationRequest { init { diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/translate/GPTInterfaceProxy.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/translate/GPTInterfaceProxy.kt deleted file mode 100644 index a92eb86b..00000000 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/translate/GPTInterfaceProxy.kt +++ /dev/null @@ -1,118 +0,0 @@ -package com.github.simiacryptus.aicoder.openai.translate - -import com.fasterxml.jackson.databind.ObjectMapper -import java.lang.reflect.Method -import java.lang.reflect.ParameterizedType -import java.lang.reflect.Proxy - -abstract class GPTInterfaceProxy { - abstract fun complete(prompt: String): String - - fun proxy(clazz: Class): T { - return Proxy.newProxyInstance(clazz.classLoader, arrayOf(clazz)) { proxy, method, args -> - if (method.name == "toString") return@newProxyInstance clazz.simpleName - val prompt = getPrompt(method, args) - val result = complete(prompt) - return@newProxyInstance fromJson(result, method.returnType) - } as T - } - - open fun toJson(data: Any): String { - return objectMapper() - .writerWithDefaultPrettyPrinter() - .writeValueAsString(data) - } - - open fun fromJson(data: String, type: Class): T { - if (data.isNotEmpty()) try { - if (type.isAssignableFrom(String::class.java)) return data as T - return objectMapper().readValue(data, type) - } catch (e: Exception) { - Companion.log.error("Error parsing JSON", e) - } - return type.getConstructor().newInstance() - } - - open fun objectMapper() = ObjectMapper() - - private fun getPrompt(method: Method, args: Array): String = """ - Method: ${method.name} - Response Type: - ${typeToName(method.returnType).replace("\n", "\n ")} - Request: - ${args.joinToString("\n", transform = ::toJson).replace("\n", "\n ")} - Response:""".trimIndent() - - companion object { - val log = org.slf4j.LoggerFactory.getLogger(GPTInterfaceProxy::class.java) - - fun typeToName(type: Class<*>?): String { - // Convert a type to API documentation including type name and type structure, recusively expanding child types - if (type == null) { - return "null" - } - if (type.isPrimitive) { - return type.simpleName - } - if (type.isArray) { - return "Array<${typeToName(type.componentType)}>" - } - if (type.isEnum) { - return type.simpleName - } - if (type.isAssignableFrom(List::class.java)) { - val genericType = (type as ParameterizedType).actualTypeArguments[0] - return "List<${typeToName(genericType as Class<*>)}>" - } - if (type.isAssignableFrom(Map::class.java)) { - val keyType = (type as ParameterizedType).actualTypeArguments[0] - val valueType = (type as ParameterizedType).actualTypeArguments[1] - return "Map<${typeToName(keyType as Class<*>)}, ${typeToName(valueType as Class<*>)}>" - } - if (type.getPackage()?.name?.startsWith("java") == true) { - return type.simpleName - } - val typeDescription = typeDescription(type) - return typeDescription.toString() - } - - private fun typeDescription(clazz: Class<*>): TypeDescription { - val apiDocumentation = if (clazz.isArray) { - return TypeDescription("Array<${typeDescription(clazz.componentType)}>") - } else if (clazz.isAssignableFrom(List::class.java)) { - if (clazz.isAssignableFrom(ParameterizedType::class.java)) { - val genericType = (clazz as ParameterizedType).actualTypeArguments[0] - return TypeDescription("List<${typeDescription(genericType as Class<*>)}}>") - } else { - return TypeDescription("List") - } - } else if (clazz.isAssignableFrom(Map::class.java)) { - val keyType = (clazz as ParameterizedType).actualTypeArguments[0] - val valueType = (clazz as ParameterizedType).actualTypeArguments[1] - return TypeDescription("Map<${typeDescription(keyType as Class<*>)}, ${typeDescription(valueType as Class<*>)}}>") - } else if (clazz == String::class.java) { - return TypeDescription(clazz.simpleName) - } else { - TypeDescription(clazz.simpleName) - } - if (clazz.isPrimitive) return apiDocumentation - if (clazz.isEnum) return apiDocumentation - - for (field in clazz.declaredFields) { - if (field.name.startsWith("\$")) continue - apiDocumentation.fields.add(FieldData(field.name, typeDescription(field.type))) - } - return apiDocumentation - } - - class TypeDescription(val name: String) { - val fields: ArrayList = ArrayList() - override fun toString(): String = - if (fields.isEmpty()) name else "{\n\t" + fields.joinToString("\n\t") + "\n}" - } - - class FieldData(val name: String, val type: TypeDescription) { - override fun toString(): String = """"$name": $type""" - } - } -} diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/translate/TranslationRequest.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/translate/TranslationRequest.kt index d2b52465..d62541ab 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/translate/TranslationRequest.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/translate/TranslationRequest.kt @@ -1,8 +1,7 @@ package com.github.simiacryptus.aicoder.openai.translate -import com.github.simiacryptus.aicoder.openai.CompletionRequest +import com.github.simiacryptus.aicoder.openai.core.CompletionRequest import java.util.* -import kotlin.collections.HashMap interface TranslationRequest { @@ -37,7 +36,10 @@ interface TranslationRequest { fun setInstruction(instruction: CharSequence?): TranslationRequest fun setInputAttribute(key: CharSequence?, value: CharSequence?): TranslationRequest fun setOutputAttrute(key: CharSequence?, value: CharSequence?): TranslationRequest - fun addExample(exampleText: CharSequence, attributes: Map = HashMap()): TranslationRequest + fun addExample( + exampleText: CharSequence, + attributes: Map = HashMap() + ): TranslationRequest fun setInputText(originalText: CharSequence?): TranslationRequest diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/translate/TranslationRequest_XML.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/translate/TranslationRequest_XML.kt index b6131eac..1de7f486 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/translate/TranslationRequest_XML.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/translate/TranslationRequest_XML.kt @@ -1,7 +1,7 @@ package com.github.simiacryptus.aicoder.openai.translate import com.github.simiacryptus.aicoder.config.AppSettingsState -import com.github.simiacryptus.aicoder.openai.CompletionRequest +import com.github.simiacryptus.aicoder.openai.core.CompletionRequest import java.util.* import java.util.stream.Collectors @@ -18,7 +18,8 @@ class TranslationRequest_XML(settings: AppSettingsState) : val inputTagTxt = inputTag.toString().lowercase(Locale.getDefault()) val outputTagTxt = outputTag.toString().lowercase(Locale.getDefault()) val inputText = originalText.toString().trim { it <= ' ' } - return CompletionRequest(""" + return CompletionRequest( + """ <$inputTagTxt$inputAttrStr>$inputText $exampleStr diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/CompletionRequestWithModel.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/CompletionRequestWithModel.kt new file mode 100644 index 00000000..38ea5907 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/CompletionRequestWithModel.kt @@ -0,0 +1,26 @@ +package com.github.simiacryptus.aicoder.openai.ui + +import com.github.simiacryptus.aicoder.config.AppSettingsState +import com.github.simiacryptus.aicoder.openai.core.CompletionRequest + +class CompletionRequestWithModel : CompletionRequest { + var model: String + + constructor(other: CompletionRequest, model: String) : super(other) { + this.model = model + } + + fun fixup(settings: AppSettingsState) { + if (null != suffix) { + if (suffix!!.trim { it <= ' ' }.length == 0) { + setSuffix(null) + } else { + echo = false + } + } + if (null != stop && stop!!.size == 0) { + stop = null + } + require(prompt.length <= settings.maxPrompt) { "Prompt too long:" + prompt.length + " chars" } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/CoreAPIImpl.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/CoreAPIImpl.kt new file mode 100644 index 00000000..88b3881a --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/CoreAPIImpl.kt @@ -0,0 +1,17 @@ +package com.github.simiacryptus.aicoder.openai.ui + +import com.github.simiacryptus.aicoder.config.AppSettingsState +import com.github.simiacryptus.aicoder.openai.core.CoreAPI + +class CoreAPIImpl( + private val appSettingsState: AppSettingsState +) : CoreAPI( + appSettingsState.apiBase, + appSettingsState.apiKey, + appSettingsState.apiLogLevel +) { + + override fun incrementTokens(totalTokens: Int) { + appSettingsState.tokenCounter += totalTokens + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/InteractiveCompletionRequest.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/InteractiveCompletionRequest.kt new file mode 100644 index 00000000..c0d47cbf --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/InteractiveCompletionRequest.kt @@ -0,0 +1,73 @@ +package com.github.simiacryptus.aicoder.openai.ui + +import com.github.simiacryptus.aicoder.config.AppSettingsState +import com.github.simiacryptus.aicoder.config.Name +import com.github.simiacryptus.aicoder.openai.async.AsyncAPI +import com.github.simiacryptus.aicoder.openai.core.CompletionRequest +import com.github.simiacryptus.aicoder.util.UITools +import com.google.common.util.concurrent.FutureCallback +import com.google.common.util.concurrent.Futures +import com.intellij.ui.components.JBScrollPane +import com.intellij.ui.components.JBTextArea +import com.intellij.ui.components.JBTextField +import java.awt.event.ActionEvent +import java.util.* +import javax.swing.AbstractAction +import javax.swing.JButton +import javax.swing.JOptionPane + +class InteractiveCompletionRequest(parent: CompletionRequest) { + @Suppress("unused") + @Name("Prompt") + val prompt: JBScrollPane + + @Suppress("unused") + @Name("Suffix") + val suffix: JBTextArea + + @Suppress("unused") + @Name("Model") + val model = OpenAI_API.modelSelector + + @Suppress("unused") + @Name("Temperature") + val temperature = JBTextField(8) + + @Suppress("unused") + @Name("Max Tokens") + val max_tokens = JBTextField(8) + val testRequest: JButton + + init { + testRequest = JButton(object : AbstractAction("Test Request") { + override fun actionPerformed(e: ActionEvent) { + val withModel = CompletionRequestWithModel(parent, AppSettingsState.instance.model_completion) + UITools.readKotlinUI(this@InteractiveCompletionRequest, withModel) + val future = OpenAI_API.getCompletion(null, withModel, "") + isEnabled = false + Futures.addCallback(future, object : FutureCallback { + override fun onSuccess(result: CharSequence) { + isEnabled = true + val text = result.toString() + val rows = Math.min(50, text.split("\n".toRegex()).dropLastWhile { it.isEmpty() } + .toTypedArray().size) + val columns = + Math.min(200, Arrays.stream(text.split("\n".toRegex()).dropLastWhile { it.isEmpty() } + .toTypedArray()).mapToInt { obj: String -> obj.length }.max().asInt) + val area = JBTextArea(rows, columns) + area.text = text + area.isEditable = false + JOptionPane.showMessageDialog(null, area, "Test Output", JOptionPane.PLAIN_MESSAGE) + } + + override fun onFailure(t: Throwable) { + isEnabled = true + UITools.handle(t) + } + }, AsyncAPI.pool) + } + }) + suffix = UITools.configureTextArea(JBTextArea(1, 120)) + prompt = UITools.wrapScrollPane(JBTextArea(10, 120)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/InteractiveEditRequest.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/InteractiveEditRequest.kt new file mode 100644 index 00000000..bf8030c5 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/InteractiveEditRequest.kt @@ -0,0 +1,68 @@ +package com.github.simiacryptus.aicoder.openai.ui + +import com.github.simiacryptus.aicoder.config.Name +import com.github.simiacryptus.aicoder.openai.async.AsyncAPI +import com.github.simiacryptus.aicoder.openai.core.EditRequest +import com.github.simiacryptus.aicoder.util.UITools +import com.google.common.util.concurrent.FutureCallback +import com.google.common.util.concurrent.Futures +import com.intellij.ui.components.JBScrollPane +import com.intellij.ui.components.JBTextArea +import com.intellij.ui.components.JBTextField +import java.awt.event.ActionEvent +import java.util.* +import javax.swing.AbstractAction +import javax.swing.JButton +import javax.swing.JOptionPane + +class InteractiveEditRequest(parent: EditRequest) { + @Suppress("unused") + @Name("Input") + val input: JBScrollPane + + @Suppress("unused") + @Name("Instruction") + val instruction: JBTextArea + + @Suppress("unused") + @Name("Model") + val model = OpenAI_API.modelSelector + + @Suppress("unused") + @Name("Temperature") + val temperature = JBTextField(8) + val testRequest: JButton + + init { + testRequest = JButton(object : AbstractAction("Test Request") { + override fun actionPerformed(e: ActionEvent) { + val request = EditRequest() + UITools.readKotlinUI(this@InteractiveEditRequest, request) + val future = OpenAI_API.edit(null, request, "") + isEnabled = false + Futures.addCallback(future, object : FutureCallback { + override fun onSuccess(result: CharSequence?) { + isEnabled = true + val text = result.toString() + val rows = Math.min(50, text.split("\n".toRegex()).dropLastWhile { it.isEmpty() } + .toTypedArray().size) + val columns = + Math.min(200, Arrays.stream(text.split("\n".toRegex()).dropLastWhile { it.isEmpty() } + .toTypedArray()).mapToInt { obj: String -> obj.length }.max().asInt) + val area = JBTextArea(rows, columns) + area.text = text + area.isEditable = false + JOptionPane.showMessageDialog(null, area, "Test Output", JOptionPane.PLAIN_MESSAGE) + } + + override fun onFailure(t: Throwable) { + isEnabled = true + UITools.handle(t) + } + }, AsyncAPI.pool) + } + }) + input = UITools.wrapScrollPane(JBTextArea(10, 120)) + instruction = UITools.configureTextArea(JBTextArea(1, 120)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/OpenAI_API.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/OpenAI_API.kt new file mode 100644 index 00000000..490234df --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/openai/ui/OpenAI_API.kt @@ -0,0 +1,174 @@ +package com.github.simiacryptus.aicoder.openai.ui + +import com.fasterxml.jackson.databind.node.ObjectNode +import com.github.simiacryptus.aicoder.config.AppSettingsState +import com.github.simiacryptus.aicoder.openai.async.AsyncAPI +import com.github.simiacryptus.aicoder.openai.async.AsyncAPI.Companion.map +import com.github.simiacryptus.aicoder.openai.async.AsyncAPIImpl +import com.github.simiacryptus.aicoder.openai.core.CompletionRequest +import com.github.simiacryptus.aicoder.openai.core.CompletionResponse +import com.github.simiacryptus.aicoder.openai.core.CoreAPI +import com.github.simiacryptus.aicoder.openai.core.EditRequest +import com.github.simiacryptus.aicoder.util.IndentedText +import com.github.simiacryptus.aicoder.util.StringTools +import com.google.common.util.concurrent.ListenableFuture +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.ComboBox +import com.intellij.ui.components.JBTextField +import java.util.* +import java.util.function.Consumer +import java.util.stream.Collectors +import java.util.stream.IntStream +import javax.swing.JComponent +import kotlin.collections.set + +object OpenAI_API { + + @JvmStatic + private val log = Logger.getInstance(OpenAI_API::class.java) + + @JvmStatic + val coreAPI: CoreAPI = CoreAPIImpl(settingsState!!) + + @JvmStatic + val asyncAPI = AsyncAPIImpl(coreAPI, settingsState!!) + + private val activeModelUI = WeakHashMap, Any>() + + @Transient + var settings: AppSettingsState? = null + + @Transient + private var comboBox: ComboBox? = null + val modelSelector: JComponent + get() { + if (null != comboBox) { + val element = ComboBox( + (IntStream.range(0, comboBox!!.itemCount) + .mapToObj(comboBox!!::getItemAt)).collect(Collectors.toList()).toTypedArray() + ) + activeModelUI[element] = Any() + return element + } + val apiKey: CharSequence = settingsState!!.apiKey + if (apiKey.toString().trim { it <= ' ' }.length > 0) { + try { + comboBox = ComboBox( + arrayOf( + settingsState!!.model_completion, + settingsState!!.model_edit + ) + ) + activeModelUI[comboBox] = Any() + AsyncAPI.onSuccess( + engines + ) { engines: ObjectNode -> + val data = engines["data"] + val items = + arrayOfNulls(data.size()) + for (i in 0 until data.size()) { + items[i] = data[i]["id"].asText() + } + Arrays.sort(items) + activeModelUI.keys.forEach(Consumer { ui: ComboBox -> + Arrays.stream(items).forEach(ui::addItem) + }) + } + return comboBox!! + } catch (e: Throwable) { + log.warn(e) + } + } + return JBTextField() + } + + fun getCompletion( + project: Project?, + request: CompletionRequest, + indent: CharSequence + ): ListenableFuture { + return getCompletion(project, request, filterStringResult(indent)) + } + + fun getCompletion( + project: Project?, + request: CompletionRequest, + filter: (CharSequence) -> CharSequence + ): ListenableFuture { + return map(complete(project, request)) { it.firstChoice.map(filter).orElse("") } + } + + fun edit(project: Project?, request: EditRequest, indent: CharSequence): ListenableFuture { + return edit(project, request, filterStringResult(indent)) + } + + fun edit( + project: Project?, + request: EditRequest, + filter: (CharSequence) -> CharSequence + ): ListenableFuture { + return map(edit(project, request)) { it.firstChoice.map(filter).orElse("") } + } + + val settingsState: AppSettingsState? + get() { + if (null == settings) { + settings = AppSettingsState.instance + } + return settings + } + private val engines: ListenableFuture + get() = AsyncAPI.pool.submit { + coreAPI.mapper.readValue( + coreAPI.get(settingsState!!.apiBase + "/engines"), + ObjectNode::class.java + ) + } + + fun complete( + project: Project?, + completionRequest: CompletionRequest + ): ListenableFuture { + val settings = settingsState + val withModel = completionRequest.uiIntercept() + withModel.fixup(settings!!) + val newRequest = CompletionRequest(withModel) + return complete(project, newRequest, settings, withModel.model) + } + + private fun edit(project: Project?, request: EditRequest): ListenableFuture { + return edit(project, request, settingsState!!) + } + + private fun edit( + project: Project?, + editRequest: EditRequest, + settings: AppSettingsState + ): ListenableFuture = asyncAPI.edit(project, editRequest, settings) + + fun complete( + project: Project?, + completionRequest: CompletionRequest, + settings: AppSettingsState, + model: String + ): ListenableFuture = asyncAPI.complete(project, completionRequest, model) + + + fun filterStringResult( + indent: CharSequence = "", + stripUnbalancedTerminators: Boolean = true + ): (CharSequence) -> CharSequence { + return { text -> + var result: CharSequence = text.toString().trim { it <= ' ' } + if (stripUnbalancedTerminators) { + result = StringTools.stripUnbalancedTerminators(result) + } + result = IndentedText.fromString2(result).withIndent(indent).toString() + indent.toString() + result + } + } + + fun text_to_speech(wavAudio: ByteArray, prompt: String = ""): String = coreAPI.text_to_speech(wavAudio, prompt) + +} diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/util/AudioRecorder.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/util/AudioRecorder.kt index d6269f4b..66ec63f1 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/util/AudioRecorder.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/util/AudioRecorder.kt @@ -3,7 +3,9 @@ package com.github.simiacryptus.aicoder.util import com.intellij.openapi.diagnostic.Logger import org.apache.commons.io.input.buffer.CircularByteBuffer import java.util.* -import javax.sound.sampled.* +import javax.sound.sampled.AudioFormat +import javax.sound.sampled.AudioSystem +import javax.sound.sampled.TargetDataLine class AudioRecorder( private val audioBuffer: Deque, @@ -22,7 +24,7 @@ class AudioRecorder( while (bytesRead != -1 && System.currentTimeMillis() < endTime) { bytesRead = targetDataLine.read(buffer, 0, buffer.size) circularBuffer.add(buffer, 0, bytesRead) - while(circularBuffer.currentNumberOfBytes >= packetLength) { + while (circularBuffer.currentNumberOfBytes >= packetLength) { val array = ByteArray(packetLength) circularBuffer.read(array, 0, packetLength) audioBuffer.add(array) diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/util/ComputerLanguage.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/util/ComputerLanguage.kt index d2bd5b58..48c93456 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/util/ComputerLanguage.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/util/ComputerLanguage.kt @@ -408,7 +408,7 @@ enum class ComputerLanguage(configuration: Configuration) { docComment.blockSuffix } else null - fun getCommentModel(text: String?): TextBlockFactory<*>? { + fun getCommentModel(text: String?): TextBlockFactory<*> { if (Objects.requireNonNull(docComment)!!.looksLike(text)) return docComment return if (Objects.requireNonNull(blockComment)!!.looksLike(text)) blockComment else lineComment } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/util/LoudnessWindowBuffer.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/util/LoudnessWindowBuffer.kt index 6d583292..72848be5 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/util/LoudnessWindowBuffer.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/util/LoudnessWindowBuffer.kt @@ -6,8 +6,10 @@ import edu.emory.mathcs.jtransforms.fft.FloatFFT_1D import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.util.* -import javax.sound.sampled.* -import kotlin.collections.ArrayList +import javax.sound.sampled.AudioFileFormat +import javax.sound.sampled.AudioFormat +import javax.sound.sampled.AudioInputStream +import javax.sound.sampled.AudioSystem import kotlin.math.ln import kotlin.math.pow import kotlin.math.sqrt diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/util/StringTools.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/util/StringTools.kt index 36c91a04..925b8b09 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/util/StringTools.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/util/StringTools.kt @@ -9,6 +9,14 @@ import java.util.stream.Collectors import java.util.stream.Stream object StringTools { + + fun indentJoin(fields: List, indent: String = "\t"): String { + val joinToString = fields + .map { it.toString().replace("\n", "\n$indent") } + .joinToString("\n$indent") + return "{\n$indent$joinToString\n}" + } + fun stripUnbalancedTerminators(input: CharSequence): CharSequence { var openCount = 0 var inQuotes = false @@ -258,7 +266,8 @@ object StringTools { ): String { val joinedPattern = replacements.map { Pattern.quote(it.first) }.joinToString("|").toRegex() return joinedPattern.replace(replaceString) { result -> - val charSequence: CharSequence = replacements.find { it.first.compareTo(result.value, true) == 0 }?.second ?: result.value + val charSequence: CharSequence = + replacements.find { it.first.compareTo(result.value, true) == 0 }?.second ?: result.value charSequence } } @@ -272,7 +281,7 @@ object StringTools { * @param delimiters The delimiters to use when splitting the text. * @return The suffix for the given context. */ - fun getSuffixForContext(text: String, idealLength: Int, vararg delimiters: CharSequence?): CharSequence { + fun getSuffixForContext(text: String, idealLength: Int, vararg delimiters: CharSequence?): CharSequence { // Create a list of candidates by splitting the text by each of the delimiters val candidates = Stream.of(*delimiters).flatMap { d: CharSequence? -> // Create a string builder to store the split strings diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/util/StyleUtil.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/util/StyleUtil.kt index 2b976761..384447a7 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/util/StyleUtil.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/util/StyleUtil.kt @@ -1,9 +1,9 @@ package com.github.simiacryptus.aicoder.util -import com.github.simiacryptus.aicoder.openai.OpenAI_API.complete -import com.github.simiacryptus.aicoder.openai.OpenAI_API.map -import com.github.simiacryptus.aicoder.openai.OpenAI_API.onSuccess import com.github.simiacryptus.aicoder.config.AppSettingsState +import com.github.simiacryptus.aicoder.openai.async.AsyncAPI +import com.github.simiacryptus.aicoder.openai.async.AsyncAPI.Companion.map +import com.github.simiacryptus.aicoder.openai.ui.OpenAI_API.getCompletion import com.github.simiacryptus.aicoder.util.StringTools.lineWrapping import com.google.common.util.concurrent.ListenableFuture import com.intellij.openapi.diagnostic.Logger @@ -126,7 +126,7 @@ object StyleUtil { "int randomIndex = rand.nextInt(items.size());\n" + "String randomItem = items.get(randomIndex);" ) { - onSuccess( + AsyncAPI.onSuccess( describeTest(style, language, code) ) { description: CharSequence -> val message: CharSequence = String.format( @@ -152,7 +152,7 @@ object StyleUtil { * @return A description of the test in the specified style and language. */ fun describeTest(style: CharSequence?, language: ComputerLanguage, code: String?): ListenableFuture { - val settings = AppSettingsState.getInstance() + val settings = AppSettingsState.instance val completionRequest = settings.createTranslationRequest() .setInstruction(String.format("Explain this %s in %s (%s)", language.name, settings.humanLanguage, style)) .setInputText(IndentedText.fromString((code)!!).textBlock.toString().trim()) @@ -162,7 +162,7 @@ object StyleUtil { .setOutputAttrute("type", "description") .setOutputAttrute("style", style) .buildCompletionRequest() - val future = complete(null, completionRequest, "") + val future = getCompletion(null, completionRequest, "") return map(future) { x: CharSequence? -> lineWrapping( x.toString().trim { it <= ' ' }, diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/util/UITools.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/util/UITools.kt index 20008b5f..4107cefd 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/util/UITools.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/util/UITools.kt @@ -2,25 +2,33 @@ package com.github.simiacryptus.aicoder.util -import com.github.simiacryptus.aicoder.openai.OpenAI_API import com.github.simiacryptus.aicoder.config.AppSettingsState import com.github.simiacryptus.aicoder.config.Name -import com.github.simiacryptus.aicoder.openai.CompletionRequest -import com.github.simiacryptus.aicoder.openai.EditRequest -import com.github.simiacryptus.aicoder.openai.ModerationException +import com.github.simiacryptus.aicoder.openai.* +import com.github.simiacryptus.aicoder.openai.async.AsyncAPI +import com.github.simiacryptus.aicoder.openai.core.CompletionRequest +import com.github.simiacryptus.aicoder.openai.core.EditRequest +import com.github.simiacryptus.aicoder.openai.core.ModerationException +import com.github.simiacryptus.aicoder.openai.ui.OpenAI_API import com.google.common.util.concurrent.FutureCallback import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Document +import com.intellij.openapi.fileChooser.FileChooser +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory +import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.ui.ComboBox +import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.util.TextRange +import com.intellij.openapi.vfs.VirtualFile import com.intellij.ui.components.JBLabel import com.intellij.ui.components.JBScrollPane import com.intellij.ui.components.JBTextArea @@ -42,17 +50,28 @@ import java.util.function.Function import java.util.stream.Collectors import javax.swing.* import javax.swing.text.JTextComponent +import kotlin.reflect.KMutableProperty +import kotlin.reflect.KProperty1 +import kotlin.reflect.full.memberProperties +import kotlin.reflect.jvm.isAccessible +import kotlin.reflect.jvm.javaField +import kotlin.reflect.jvm.javaType object UITools { private val log = Logger.getInstance(UITools::class.java) val retry = WeakHashMap() - fun redoableRequest(request: CompletionRequest, indent: CharSequence, event: AnActionEvent, action: Function) { + fun redoableRequest( + request: CompletionRequest, + indent: CharSequence, + event: AnActionEvent, + action: Function + ) { redoableRequest(request, indent, event, { x: CharSequence -> x }, action) } fun startProgress(): ProgressIndicator? { - if(1==1) return null; - if (AppSettingsState.getInstance().suppressProgress) return null + if (1 == 1) return null + if (AppSettingsState.instance.suppressProgress) return null val progressIndicator = ProgressManager.getInstance().progressIndicator if (null != progressIndicator) { progressIndicator.isIndeterminate = true @@ -60,14 +79,23 @@ object UITools { } return progressIndicator } + fun redoableRequest( request: CompletionRequest, indent: CharSequence, event: AnActionEvent, transformCompletion: Function, action: Function, - postFilter: (CharSequence) -> CharSequence) { - redoableRequest(request, indent, event, transformCompletion, action, OpenAI_API.complete(event.project!!, request, postFilter)) + postFilter: (CharSequence) -> CharSequence + ) { + redoableRequest( + request, + indent, + event, + transformCompletion, + action, + OpenAI_API.getCompletion(event.project!!, request, postFilter) + ) } /** @@ -85,7 +113,7 @@ object UITools { event: AnActionEvent, transformCompletion: Function, action: Function, - resultFuture: ListenableFuture = OpenAI_API.complete(event.project!!, request, indent), + resultFuture: ListenableFuture = OpenAI_API.getCompletion(event.project!!, request, indent), progressIndicator: ProgressIndicator? = startProgress() ) { Futures.addCallback(resultFuture, object : FutureCallback { @@ -112,7 +140,7 @@ object UITools { progressIndicator?.cancel() handle(t) } - }, OpenAI_API.pool) + }, AsyncAPI.pool) } /** @@ -131,12 +159,19 @@ object UITools { * @param transformCompletion * @return a [Runnable] that will attempt to complete the given [CompletionRequest] */ - fun getRetry(request: CompletionRequest, indent: CharSequence?, event: AnActionEvent, action: Function, undo: Runnable, transformCompletion: Function): Runnable { + fun getRetry( + request: CompletionRequest, + indent: CharSequence?, + event: AnActionEvent, + action: Function, + undo: Runnable, + transformCompletion: Function + ): Runnable { val document = Objects.requireNonNull(event.getData(CommonDataKeys.EDITOR))!!.document return Runnable { val progressIndicator = startProgress() Futures.addCallback( - OpenAI_API.complete(event.project!!, request, indent!!), + OpenAI_API.getCompletion(event.project!!, request, indent!!), object : FutureCallback { override fun onSuccess(result: CharSequence?) { progressIndicator?.cancel() @@ -149,7 +184,8 @@ object UITools { ) ) } - retry[document] = getRetry(request, indent, event, action, nextUndo.get()!!, transformCompletion) + retry[document] = + getRetry(request, indent, event, action, nextUndo.get()!!, transformCompletion) } override fun onFailure(t: Throwable) { @@ -157,16 +193,27 @@ object UITools { handle(t) } }, - OpenAI_API.pool + AsyncAPI.pool ) } } - fun redoableRequest(request: EditRequest, indent: CharSequence, event: AnActionEvent, action: Function) { + fun redoableRequest( + request: EditRequest, + indent: CharSequence, + event: AnActionEvent, + action: Function + ) { redoableRequest(request, indent, event, { x: CharSequence -> x }, action) } - fun redoableRequest(request: EditRequest, indent: CharSequence, event: AnActionEvent, transformCompletion: Function, action: Function) { + fun redoableRequest( + request: EditRequest, + indent: CharSequence, + event: AnActionEvent, + transformCompletion: Function, + action: Function + ) { val editor = event.getData(CommonDataKeys.EDITOR) val document = Objects.requireNonNull(editor)!!.document val progressIndicator = startProgress() @@ -175,7 +222,15 @@ object UITools { override fun onSuccess(result: CharSequence?) { progressIndicator?.cancel() val undo = AtomicReference() - WriteCommandAction.runWriteCommandAction(event.project) { undo.set(action.apply(transformCompletion.apply(result.toString()))) } + WriteCommandAction.runWriteCommandAction(event.project) { + undo.set( + action.apply( + transformCompletion.apply( + result.toString() + ) + ) + ) + } retry[document] = getRetry(request, indent, event, action, undo.get()) } @@ -183,10 +238,16 @@ object UITools { progressIndicator?.cancel() handle(t) } - }, OpenAI_API.pool) + }, AsyncAPI.pool) } - private fun getRetry(request: EditRequest, indent: CharSequence?, event: AnActionEvent, action: Function, undo: Runnable): Runnable { + private fun getRetry( + request: EditRequest, + indent: CharSequence?, + event: AnActionEvent, + action: Function, + undo: Runnable + ): Runnable { val document = Objects.requireNonNull(event.getData(CommonDataKeys.EDITOR))!!.document return Runnable { val progressIndicator = startProgress() @@ -204,7 +265,7 @@ object UITools { progressIndicator?.cancel() handle(t) } - }, OpenAI_API.pool) + }, AsyncAPI.pool) } } @@ -215,7 +276,7 @@ object UITools { * @return A string containing the instruction and the style */ fun getInstruction(instruction: String): String { - val style: CharSequence = AppSettingsState.getInstance().style + val style: CharSequence = AppSettingsState.instance.style return if (style.length == 0) instruction else String.format("%s (%s)", instruction, style) } @@ -231,15 +292,39 @@ object UITools { fun replaceString(document: Document, startOffset: Int, endOffset: Int, newText: CharSequence): Runnable { val oldText: CharSequence = document.getText(TextRange(startOffset, endOffset)) document.replaceString(startOffset, endOffset, newText) - logEdit(String.format("FWD replaceString from %s to %s (%s->%s): %s", startOffset, endOffset, endOffset - startOffset, newText.length, newText)) + logEdit( + String.format( + "FWD replaceString from %s to %s (%s->%s): %s", + startOffset, + endOffset, + endOffset - startOffset, + newText.length, + newText + ) + ) return Runnable { val verifyTxt = document.getText(TextRange(startOffset, startOffset + newText.length)) if (verifyTxt != newText) { - val msg = String.format("The text range from %d to %d does not match the expected text \"%s\" and is instead \"%s\"", startOffset, startOffset + newText.length, newText, verifyTxt) + val msg = String.format( + "The text range from %d to %d does not match the expected text \"%s\" and is instead \"%s\"", + startOffset, + startOffset + newText.length, + newText, + verifyTxt + ) throw IllegalStateException(msg) } document.replaceString(startOffset, startOffset + newText.length, oldText) - logEdit(String.format("REV replaceString from %s to %s (%s->%s): %s", startOffset, startOffset + newText.length, newText.length, oldText.length, oldText)) + logEdit( + String.format( + "REV replaceString from %s to %s (%s->%s): %s", + startOffset, + startOffset + newText.length, + newText.length, + oldText.length, + oldText + ) + ) } } @@ -257,7 +342,13 @@ object UITools { return Runnable { val verifyTxt = document.getText(TextRange(startOffset, startOffset + newText.length)) if (verifyTxt != newText) { - val message = String.format("The text range from %d to %d does not match the expected text \"%s\" and is instead \"%s\"", startOffset, startOffset + newText.length, newText, verifyTxt) + val message = String.format( + "The text range from %d to %d does not match the expected text \"%s\" and is instead \"%s\"", + startOffset, + startOffset + newText.length, + newText, + verifyTxt + ) throw AssertionError(message) } document.deleteString(startOffset, startOffset + newText.length) @@ -285,7 +376,7 @@ object UITools { val documentText = document.text val lineNumber = document.getLineNumber(caret.selectionStart) val lines = documentText.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - if(lines.isEmpty()) return "" + if (lines.isEmpty()) return "" return IndentedText.fromString(lines[Math.min(Math.max(lineNumber, 0), lines.size - 1)]).indent } @@ -319,14 +410,16 @@ object UITools { panel.add(pass) val options = arrayOf("OK", "Cancel") return if (JOptionPane.showOptionDialog( - null, - panel, - "API Key", - JOptionPane.NO_OPTION, - JOptionPane.PLAIN_MESSAGE, - null, - options, - options[1]) == JOptionPane.OK_OPTION) { + null, + panel, + "API Key", + JOptionPane.NO_OPTION, + JOptionPane.PLAIN_MESSAGE, + null, + options, + options[1] + ) == JOptionPane.OK_OPTION + ) { val password = pass.password java.lang.String(password) } else { @@ -334,10 +427,12 @@ object UITools { } } - fun readUI(component: Any, settings: T) { + fun readJavaUI(component: Any, settings: T) { val componentClass: Class<*> = component.javaClass - val declaredUIFields = Arrays.stream(componentClass.fields).map { obj: Field -> obj.name }.collect(Collectors.toSet()) + val declaredUIFields = + Arrays.stream(componentClass.fields).map { obj: Field -> obj.name }.collect(Collectors.toSet()) for (settingsField in settings.javaClass.fields) { + if (Modifier.isStatic(settingsField.modifiers)) continue settingsField.isAccessible = true val settingsFieldName = settingsField.name try { @@ -377,7 +472,8 @@ object UITools { if (uiVal is ComboBox<*>) { val comboBox = uiVal val item = comboBox.item - newSettingsValue = java.lang.Enum.valueOf(settingsField.type as Class?>, item.toString()) + newSettingsValue = + java.lang.Enum.valueOf(settingsField.type as Class?>, item.toString()) } } } @@ -388,9 +484,74 @@ object UITools { } } - fun writeUI(component: Any, settings: T) { + fun readKotlinUI(component: R, settings: T) { val componentClass: Class<*> = component.javaClass - val declaredUIFields = Arrays.stream(componentClass.fields).map { obj: Field -> obj.name }.collect(Collectors.toSet()) + val declaredUIFields = + componentClass.kotlin.memberProperties.map { it.name }.toSet() + for (settingsField in settings.javaClass.kotlin.memberProperties) { + if (settingsField is KMutableProperty<*>) { + settingsField.isAccessible = true + val settingsFieldName = settingsField.name + try { + var newSettingsValue: Any? = null + if (!declaredUIFields.contains(settingsFieldName)) continue + val uiField: KProperty1 = + (componentClass.kotlin.memberProperties.find { it.name.equals(settingsFieldName) } as KProperty1?)!! + var uiVal = uiField.get(component) + if (uiVal is JScrollPane) { + uiVal = uiVal.viewport.view + } + val name = settingsField.returnType.javaType.typeName + when (name) { + "java.lang.String" -> if (uiVal is JTextComponent) { + newSettingsValue = uiVal.text + } else if (uiVal is ComboBox<*>) { + newSettingsValue = uiVal.item + } + + "int", "java.lang.Integer" -> if (uiVal is JTextComponent) { + newSettingsValue = uiVal.text.toInt() + } + + "long" -> if (uiVal is JTextComponent) { + newSettingsValue = uiVal.text.toLong() + } + + "double", "java.lang.Double" -> if (uiVal is JTextComponent) { + newSettingsValue = uiVal.text.toDouble() + } + + "boolean" -> if (uiVal is JCheckBox) { + newSettingsValue = uiVal.isSelected + } else if (uiVal is JTextComponent) { + newSettingsValue = java.lang.Boolean.parseBoolean(uiVal.text) + } + + else -> + if (Enum::class.java.isAssignableFrom(settingsField.returnType.javaType as Class<*>)) { + if (uiVal is ComboBox<*>) { + val comboBox = uiVal + val item = comboBox.item + newSettingsValue = + java.lang.Enum.valueOf( + settingsField.returnType.javaType as Class?>, + item.toString() + ) + } + } + } + settingsField.setter.call(settings, newSettingsValue) + } catch (e: Throwable) { + RuntimeException("Error processing $settingsField", e).printStackTrace() + } + } + } + } + + fun writeJavaUI(component: Any, settings: T) { + val componentClass: Class<*> = component.javaClass + val declaredUIFields = + Arrays.stream(componentClass.fields).map { obj: Field -> obj.name }.collect(Collectors.toSet()) for (settingsField in settings.javaClass.fields) { val fieldName = settingsField.name try { @@ -405,7 +566,58 @@ object UITools { "java.lang.String" -> if (uiVal is JTextComponent) { uiVal.text = settingsVal.toString() } else if (uiVal is ComboBox<*>) { - (uiVal as ComboBox).item = settingsVal.toString() + uiVal.item = settingsVal.toString() + } + + "int", "java.lang.Integer" -> if (uiVal is JTextComponent) { + uiVal.text = (settingsVal as Int).toString() + } + + "long" -> if (uiVal is JTextComponent) { + uiVal.text = (settingsVal as Int).toLong().toString() + } + + "boolean" -> if (uiVal is JCheckBox) { + uiVal.isSelected = (settingsVal as Boolean) + } else if (uiVal is JTextComponent) { + uiVal.text = java.lang.Boolean.toString((settingsVal as Boolean)) + } + + "double", "java.lang.Double" -> if (uiVal is JTextComponent) { + uiVal.text = java.lang.Double.toString((settingsVal as Double)) + } + + else -> if (uiVal is ComboBox<*>) { + uiVal.item = settingsVal.toString() + } + } + } catch (e: Throwable) { + RuntimeException("Error processing $settingsField", e).printStackTrace() + } + } + } + + fun writeKotlinUI(component: R, settings: T) { + val componentClass: Class<*> = component.javaClass + val declaredUIFields = + componentClass.kotlin.memberProperties.map { it.name }.toSet() + for (settingsField in settings.javaClass.kotlin.memberProperties) { + val fieldName = settingsField.name + try { + if (!declaredUIFields.contains(fieldName)) continue + val uiField: KProperty1 = + (componentClass.kotlin.memberProperties.find { it.name.equals(fieldName) } as KProperty1?)!! + val settingsVal = settingsField.get(settings) ?: continue + var uiVal = uiField.get(component) + if (uiVal is JScrollPane) { + uiVal = uiVal.viewport.view + } + val name = settingsField.returnType.javaType.typeName + when (name) { + "java.lang.String" -> if (uiVal is JTextComponent) { + uiVal.text = settingsVal.toString() + } else if (uiVal is ComboBox<*>) { + uiVal.item = settingsVal.toString() } "int", "java.lang.Integer" -> if (uiVal is JTextComponent) { @@ -427,7 +639,7 @@ object UITools { } else -> if (uiVal is ComboBox<*>) { - (uiVal as ComboBox).item = settingsVal.toString() + uiVal.item = settingsVal.toString() } } } catch (e: Throwable) { @@ -436,7 +648,7 @@ object UITools { } } - fun addFields(ui: Any, formBuilder: FormBuilder) { + fun addJavaFields(ui: Any, formBuilder: FormBuilder) { var first = true for (field in ui.javaClass.fields) { if (Modifier.isStatic(field.modifiers)) continue @@ -461,6 +673,31 @@ object UITools { } } + fun addKotlinFields(ui: T, formBuilder: FormBuilder) { + var first = true + for (field in ui.javaClass.kotlin.memberProperties) { + if (field.javaField == null) continue + try { + val nameAnnotation = field.annotations.find { it is Name } as Name? + val component = field.get(ui) as JComponent + if (nameAnnotation != null) { + if (first) { + first = false + formBuilder.addLabeledComponentFillVertically(nameAnnotation.value + ": ", component) + } else { + formBuilder.addLabeledComponent(JBLabel(nameAnnotation.value + ": "), component, 1, false) + } + } else { + formBuilder.addComponentToRightColumn(component, 1) + } + } catch (e: IllegalAccessException) { + throw RuntimeException(e) + } catch (e: Throwable) { + log.warn("Error processing " + field.name, e) + } + } + } + fun getMaximumSize(factor: Double): Dimension { val screenSize = Toolkit.getDefaultToolkit().screenSize return Dimension((screenSize.getWidth() * factor).toInt(), (screenSize.getHeight() * factor).toInt()) @@ -468,9 +705,10 @@ object UITools { fun showOptionDialog(mainPanel: JPanel?, vararg options: Any, title: String): Int { val pane = JOptionPane( - mainPanel, JOptionPane.PLAIN_MESSAGE, - JOptionPane.NO_OPTION, null, - options, options[0]) + mainPanel, JOptionPane.PLAIN_MESSAGE, + JOptionPane.NO_OPTION, null, + options, options[0] + ) pane.initialValue = options[0] pane.componentOrientation = JOptionPane.getRootFrame().componentOrientation val dialog: JDialog @@ -547,7 +785,10 @@ object UITools { fun configureTextArea(textArea: JBTextArea): JBTextArea { val font = textArea.font val fontMetrics = textArea.getFontMetrics(font) - textArea.preferredSize = Dimension((fontMetrics.charWidth(' ') * textArea.columns * 1.2).toInt(), (fontMetrics.height * textArea.rows * 1.2).toInt()) + textArea.preferredSize = Dimension( + (fontMetrics.charWidth(' ') * textArea.columns * 1.2).toInt(), + (fontMetrics.height * textArea.rows * 1.2).toInt() + ) textArea.autoscrolls = true return textArea } @@ -629,11 +870,11 @@ object UITools { // Slava Ukraini! val locale = Locale.getDefault() // ISO 3166 - Russia - if(locale.country.compareTo("RU", true) == 0) return true + if (locale.country.compareTo("RU", true) == 0) return true // ISO 3166 - Belarus - if(locale.country.compareTo("BY", true) == 0) return true + if (locale.country.compareTo("BY", true) == 0) return true // ISO 639 - Russian - if(locale.language.compareTo("ru", true) == 0) { + if (locale.language.compareTo("ru", true) == 0) { // ISO 3166 - Ukraine if (locale.country.compareTo("UA", true) == 0) return false // ISO 3166 - United States @@ -676,4 +917,56 @@ object UITools { } return false } + + fun build( + component: T, + formBuilder: FormBuilder = FormBuilder.createFormBuilder() + ): JPanel? { + addKotlinFields(component, formBuilder) + return formBuilder.addComponentFillVertically(JPanel(), 0).panel + } + + fun showDialog(e: AnActionEvent, uiClass: Class, configClass: Class, onComplete: (C) -> Unit) { + val project = e.project + val component = uiClass.getConstructor().newInstance() + val config = configClass.getConstructor().newInstance() + val dialog = object : DialogWrapper(project) { + init { + this.init() + this.title = "Generate Project" + this.setOKButtonText("Generate") + this.setCancelButtonText("Cancel") + this.setResizable(true) + //this.setPreferredFocusedComponent(this) + //this.setContent(this) + } + + override fun createCenterPanel(): JComponent? { + return build(component) + } + } + dialog.show() + if (dialog.isOK) { + readKotlinUI(component, config) + onComplete(config) + } + } + + fun getSelectedFolder(e: AnActionEvent): VirtualFile? { + val project = e.project + val dataContext = e.dataContext + val data = PlatformDataKeys.VIRTUAL_FILE.getData(dataContext) + if (data != null && data.isDirectory) { + return data + } + val editor = PlatformDataKeys.EDITOR.getData(dataContext) + if (editor != null) { + val file = FileDocumentManager.getInstance().getFile(editor.document) + if (file != null) { + return file.parent + } + } + return project?.baseDir + } + } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/util/psi/PsiClassContext.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/util/psi/PsiClassContext.kt index 897bc81e..2f812b94 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/util/psi/PsiClassContext.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/util/psi/PsiClassContext.kt @@ -4,7 +4,6 @@ import com.github.simiacryptus.aicoder.util.ComputerLanguage import com.intellij.psi.PsiElement import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiFile -import java.util.ArrayList class PsiClassContext(val text: String, val isPrior: Boolean, val isOverlap: Boolean, val language: ComputerLanguage) { val children = ArrayList() @@ -40,7 +39,14 @@ class PsiClassContext(val text: String, val isPrior: Boolean, val isOverlap: Boo currentContext.children.add(PsiClassContext(text.trim { it <= ' ' }, isPrior, isOverlap, language)) } else if (PsiUtil.matchesType(element, "Comment", "DocComment")) { if (within) { - currentContext.children.add(PsiClassContext(indent + text.trim { it <= ' ' }, false, true, language)) + currentContext.children.add( + PsiClassContext( + indent + text.trim { it <= ' ' }, + false, + true, + language + ) + ) } } else if (PsiUtil.matchesType(element, "Field")) { processChildren( @@ -61,7 +67,8 @@ class PsiClassContext(val text: String, val isPrior: Boolean, val isOverlap: Boo self, isPrior, isOverlap, - indent + PsiUtil.getDeclaration(element).trim { it <= ' ' } + (if (isOverlap) " {" else methodTerminator)) + indent + PsiUtil.getDeclaration(element) + .trim { it <= ' ' } + (if (isOverlap) " {" else methodTerminator)) } else if (PsiUtil.matchesType(element, "LocalVariable")) { currentContext.children.add(PsiClassContext(indent + text.trim { it <= ' ' } + ";", isPrior, @@ -125,7 +132,12 @@ class PsiClassContext(val text: String, val isPrior: Boolean, val isOverlap: Boo } companion object { - fun getContext(psiFile: PsiFile, selectionStart: Int, selectionEnd: Int, language: ComputerLanguage): PsiClassContext { + fun getContext( + psiFile: PsiFile, + selectionStart: Int, + selectionEnd: Int, + language: ComputerLanguage + ): PsiClassContext { return PsiClassContext("", false, true, language).init(psiFile, selectionStart, selectionEnd) } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/util/psi/PsiMarkdownContext.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/util/psi/PsiMarkdownContext.kt index 8b8f1e9a..ea5d1095 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/util/psi/PsiMarkdownContext.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/util/psi/PsiMarkdownContext.kt @@ -3,7 +3,6 @@ package com.github.simiacryptus.aicoder.util.psi import com.intellij.psi.PsiElement import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiFile -import java.util.ArrayList import java.util.concurrent.atomic.AtomicReference class PsiMarkdownContext(private val parent: PsiMarkdownContext?, val text: String, private val start: Int) { diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/util/psi/PsiTranslationSkeleton.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/util/psi/PsiTranslationSkeleton.kt index 00e99c8e..34b435aa 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/util/psi/PsiTranslationSkeleton.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/util/psi/PsiTranslationSkeleton.kt @@ -1,8 +1,8 @@ package com.github.simiacryptus.aicoder.util.psi -import com.github.simiacryptus.aicoder.openai.OpenAI_API.complete -import com.github.simiacryptus.aicoder.openai.OpenAI_API.pool import com.github.simiacryptus.aicoder.config.AppSettingsState +import com.github.simiacryptus.aicoder.openai.async.AsyncAPI +import com.github.simiacryptus.aicoder.openai.ui.OpenAI_API.getCompletion import com.github.simiacryptus.aicoder.util.ComputerLanguage import com.google.common.collect.Streams import com.google.common.util.concurrent.Futures @@ -56,7 +56,7 @@ class PsiTranslationSkeleton(private val stubId: String?, text: String?, private regex = if (Pattern.compile(regex).matcher(translatedOuter).find()) regex else stubId } } - return translatedOuter.toString().replace(regex.toRegex(), translatedInner.toString()) + return translatedOuter.toString().replace(regex.toRegex(), translatedInner.toString().replace("\$", "\\\$")) } fun translate( @@ -68,8 +68,8 @@ class PsiTranslationSkeleton(private val stubId: String?, text: String?, private if (null == translateFuture) { synchronized(this) { if (null == translateFuture) { - translateFuture = complete( - project, AppSettingsState.getInstance() + translateFuture = getCompletion( + project, AppSettingsState.instance .createTranslationRequest() .setInstruction( String.format( @@ -110,7 +110,7 @@ class PsiTranslationSkeleton(private val stubId: String?, text: String?, private future = Futures.transformAsync( future, { _: Any? -> stub.sequentialTranslate(project, indent, sourceLanguage, targetLanguage) }, - pool + AsyncAPI.pool ) } return future diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/util/psi/PsiUtil.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/util/psi/PsiUtil.kt index 6eb499f5..45ed42f6 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/util/psi/PsiUtil.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/util/psi/PsiUtil.kt @@ -112,7 +112,9 @@ object PsiUtil { val textRange = element.textRange if (intersects(TextRange(selectionStart, selectionEnd), textRange)) { if (matchesType(element, *types)) { - largest.updateAndGet { s: PsiElement? -> if ((s?.text?.length ?: Int.MAX_VALUE) < element.text.length) s else element } + largest.updateAndGet { s: PsiElement? -> + if ((s?.text?.length ?: Int.MAX_VALUE) < element.text.length) s else element + } } } //System.out.printf("%s : %s%n", simpleName, element.getText()); @@ -149,7 +151,7 @@ object PsiUtil { return elements } - private fun within(textRange: TextRange, vararg offset: Int) : Boolean = + private fun within(textRange: TextRange, vararg offset: Int): Boolean = offset.filter { it in textRange.startOffset..textRange.endOffset }.isNotEmpty() private fun intersects(a: TextRange, b: TextRange): Boolean { @@ -333,7 +335,7 @@ object PsiUtil { return fileFromText.get() } - fun getPsiFile(e: AnActionEvent) : PsiFile? { + fun getPsiFile(e: AnActionEvent): PsiFile? { return e.getData(CommonDataKeys.PSI_FILE) } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index f4665c09..19371b3b 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -279,6 +279,14 @@ + + + + image/svg+xml + rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/DocGen.kt b/src/test/kotlin/com/github/simiacryptus/aicoder/DocGen.kt index a7bd630c..3f379da5 100644 --- a/src/test/kotlin/com/github/simiacryptus/aicoder/DocGen.kt +++ b/src/test/kotlin/com/github/simiacryptus/aicoder/DocGen.kt @@ -105,7 +105,6 @@ class DocGen { writer.println("| Text | Description |") writer.println("| --- | --- |") actions.forEach { action -> - val file = "src/main/kotlin/${action["id"].toString().replace(".", "/")}.kt" val uiText = action["text"].toString().replace("_", "") writer.println("| $uiText | ${action["long_description"]} |") } diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/ProxyPlay.ws.kts b/src/test/kotlin/com/github/simiacryptus/aicoder/ProxyPlay.ws.kts new file mode 100644 index 00000000..96699fbd --- /dev/null +++ b/src/test/kotlin/com/github/simiacryptus/aicoder/ProxyPlay.ws.kts @@ -0,0 +1,22 @@ +import com.github.simiacryptus.aicoder.proxy.ProxyTest +import com.github.simiacryptus.aicoder.openai.proxy.ChatProxy +import com.github.simiacryptus.aicoder.openai.proxy.CompletionProxy +import com.intellij.openapi.util.io.FileUtil +import java.io.File + +val keyFile = File("C:\\Users\\andre\\code\\all-projects\\openai.key") +val chatProxy = ChatProxy(apiKey = FileUtil.loadFile(keyFile).trim(), apiLog = "api.log.json") +val completionProxy = CompletionProxy( + apiKey = FileUtil.loadFile(keyFile).trim(), + apiLog = FileUtil.loadFile(keyFile).trim() +) + +println(completionProxy.api.getEngines().joinToString("\n")) +val statement = "The meaning of life is to live a life of meaning." +val proxyFactory = chatProxy +val proxy = proxyFactory.create(ProxyTest.EssayAPI::class.java) +val essayOutline = proxy.essayOutline( + ProxyTest.EssayAPI.Thesis(statement), + "5000 words" +) +println(essayOutline.introduction!!.thesis.statement) diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/ProxyTest.kt b/src/test/kotlin/com/github/simiacryptus/aicoder/ProxyTest.kt deleted file mode 100644 index d941b338..00000000 --- a/src/test/kotlin/com/github/simiacryptus/aicoder/ProxyTest.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.github.simiacryptus.aicoder - -import com.github.simiacryptus.aicoder.config.AppSettingsState -import com.github.simiacryptus.aicoder.openai.CompletionRequest -import com.github.simiacryptus.aicoder.openai.OpenAI_API -import com.github.simiacryptus.aicoder.openai.translate.GPTInterfaceProxy -import com.intellij.openapi.util.io.FileUtil -import org.junit.Test -import java.io.File - - -class ProxyTest { - - interface TestAPI { - fun simpleQuestion(question: SimpleQuestion): SimpleResponse - - class SimpleQuestion(var question: String = "What is the answer to life, the universe, and everything?") - - class SimpleResponse { - var answer: String = "42" - var why: String = "Because it is the answer to life, the universe, and everything." - } - - fun topTen(question: TopTenQuestion): TopTenResponse - - class TopTenQuestion(var question: String = "What are the top ten answers to life, the universe, and everything?") - - class TopTenResponse { - var answers: List = listOf( - "42", - "Love", - "Happiness", - "Peace", - "Friendship", - "Family", - "Kindness", - "Compassion", - "Understanding", - "Knowledge" - ) - } - - } - - @Test - fun test_simpleQuestion() { - if (!keyFile.exists()) return - val proxyFactory = TestGPTInterfaceProxy() - val proxy = proxyFactory.proxy(TestAPI::class.java) - println(proxy.simpleQuestion(TestAPI.SimpleQuestion()).answer) - } - - @Test - fun test_topTen() { - if (!keyFile.exists()) return - val proxyFactory = TestGPTInterfaceProxy() - val proxy = proxyFactory.proxy(TestAPI::class.java) - println(proxy.topTen(TestAPI.TopTenQuestion()).answers.joinToString("\n")) - } - - companion object { - val keyFile = File("C:\\Users\\andre\\code\\all-projects\\openai.key") - - class TestGPTInterfaceProxy : GPTInterfaceProxy() { - init { - val settings = AppSettingsState() - settings.apiKey = FileUtil.loadFile(keyFile).trim() - OpenAI_API.lastFetchedSettingsState = Long.MAX_VALUE - OpenAI_API.settings = settings - } - - override fun complete(prompt: String): String { - println(prompt) - val request = CompletionRequest().appendPrompt(prompt) - request.max_tokens = 1000 - request.temperature = 0.7 - val completion = OpenAI_API.complete(null, request, "").get().toString() - println(completion) - return completion - } - } - - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/UITestUtil.kt b/src/test/kotlin/com/github/simiacryptus/aicoder/UITestUtil.kt index 5c52fe10..abe188ef 100644 --- a/src/test/kotlin/com/github/simiacryptus/aicoder/UITestUtil.kt +++ b/src/test/kotlin/com/github/simiacryptus/aicoder/UITestUtil.kt @@ -66,7 +66,7 @@ class UITestUtil { private fun isStillRunning(): Boolean { val resultText = - componentText("//div[contains(@accessiblename.key, 'editor.accessible.name')]") ?: return false + componentText("//div[contains(@accessiblename.key, 'editor.accessible.name')]") return !(resultText.contains("Process finished")) } @@ -127,12 +127,12 @@ class UITestUtil { } fun clickText(element: ComponentFixture, text: String) { - val (leftPoint, rightPoint) = findText(element, text) ?: throw Exception("Could not find text $text") + val (leftPoint, _) = findText(element, text) ?: throw Exception("Could not find text $text") element.runJs("robot.click(component, new Point(${leftPoint.x}, ${leftPoint.y}))") } fun rightClickText(element: ComponentFixture, text: String) { - val (leftPoint, rightPoint) = findText(element, text) ?: throw Exception("Could not find text $text") + val (leftPoint, _) = findText(element, text) ?: throw Exception("Could not find text $text") element.runJs("robot.rightClick(component, new Point(${leftPoint.x}, ${leftPoint.y}))") } @@ -829,7 +829,7 @@ class UITestUtil { fun getComponent(vararg paths: String) = paths.flatMap { path -> try { - listOf(robot.find(ComponentFixture::class.java, byXpath(path))).filter { it != null } + listOf(robot.find(ComponentFixture::class.java, byXpath(path))) } catch (ex: Throwable) { listOf() } diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/AutoDevelop.kt b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/AutoDevelop.kt new file mode 100644 index 00000000..aec5851d --- /dev/null +++ b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/AutoDevelop.kt @@ -0,0 +1,101 @@ +package com.github.simiacryptus.aicoder.proxy + +import com.github.simiacryptus.aicoder.openai.proxy.SoftwareProjectAI +import org.junit.Test +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream + +/** + * AutoDevelop takes a software project description and generates a software project with all the necessary files. + */ +class AutoDevelop : GenerationReportBase() { + + @Test + fun softwareProject() { + runReport("SoftwareProject", SoftwareProjectAI::class) { api, logJson, out -> + report(api, logJson, out) + } + } + + private fun report( + api: SoftwareProjectAI, + logJson: (Any) -> Unit, + out: (Any) -> Unit + ) { + val project = api.newProject( + """ + | + |Slack bot to monitor a support alias and automatically respond to common questions + | + |Language: Kotlin + | + """.trimMargin().trim() + ) + logJson(project) + out( + """ + | + |# ${project.name} + | + |${project.description} + | + |Language: ${project.language} + | + |Libraries: ${project.libraries.joinToString(", ")} + | + |Build Tools: ${project.buildTools.joinToString(", ")} + | + |""".trimMargin() + ) + val requirements = api.getProjectStatements(project) + logJson(requirements) + + val projectDesign = api.buildProjectDesign(project, requirements) + logJson(projectDesign) + + val files = api.buildProjectFileSpecifications(project, requirements, projectDesign) + logJson(files) + + + val zipArchiveFile = outputDir.resolve("projects/${project.name}.zip") + zipArchiveFile.parentFile.mkdirs() + out( + """ + | + |## Project Files + | + |[Download](projects/${project.name}.zip) + | + |""".trimMargin() + ) + ZipOutputStream(zipArchiveFile.outputStream()).use { zip -> + for (file in files.files) { + } + for (file in files.files) { + val sourceCode = api.implement( + project, + files.files.map { it.location }.filter { file.requires.contains(it) }.toList(), + file + ) + zip.putNextEntry(ZipEntry(file.location.toString())) + zip.write(sourceCode.code.toByteArray()) + zip.closeEntry() + out( + """ + | + |## ${file.location.name}.${file.location.extension} + | + |${file.description} + | + |```${sourceCode.language} + |${sourceCode.code} + |``` + | + |""".trimMargin() + ) + } + } + } + + +} \ No newline at end of file diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/AutoNews.kt b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/AutoNews.kt new file mode 100644 index 00000000..051f2eab --- /dev/null +++ b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/AutoNews.kt @@ -0,0 +1,141 @@ +package com.github.simiacryptus.aicoder.proxy + +import org.junit.Test + +/** + * FakeNews is a class that implements the News interface. + * It provides methods to get a publication, get stories, + * cover a story, and generate a report. + */ +class AutoNews : GenerationReportBase() { + interface News { + + fun getPublication(publicationName: String): Publication + + data class Publication( + val name: String = "", + val description: String = "", + val tags: List = listOf(), + val publishing_date: String? = null, + ) + + fun getStories( + publication: Publication, + category: String, + storyCount: Int = 3, + funny: Boolean = true + ): StoryList + + data class StoryList( + val stories: List = listOf(), + val category: String = "", + ) + + data class Story( + val who: String = "", + val what: String = "", + val `when`: String = "", + val where: String = "", + val why: String = "", + val punchline: String = "", + val commentary: String = "", + val image: ImageDescription? = null, + val isFunny: Boolean = false, + ) + + data class ImageDescription( + val style: String = "", + val subject: String = "", + val background: String = "", + val detailedCaption: String = "", + ) + + fun coverStory(publication: Publication, story: Story, paragraphCount: Int = 10): Article + + data class Article( + val title: String = "", + val author: String = "", + val keywords: String = "", + val content: List = listOf(), + ) + + } + + @Test + fun newsWebsite() { + runReport("News", News::class) { api, logJson, out -> + val categories = listOf( + "politics", + "science", + "technology", + "entertainment", + "sports", + "business", + "health", + "travel", + "food", + "weather", + "fashion", + "lifestyle", + "finance", + "education", + "environment", + "religion", + ) + val publication = News.Publication( + description = "A humorous exploration of the unknown", + tags = listOf("satire", "funny", "science fiction", "future"), + name = "Beyond Imagination", + publishing_date = "2023-07-04", + ) + logJson(publication) + out( + """ + | + |# ${publication.name} + | + |${publication.description} + | + |Tags: ${publication.tags.joinToString(", ")} + | + |""".trimMargin() + ) + for (category in categories) { + out( + """ + | + |## ${category.capitalize()} + | + |""".trimMargin() + ) + val stories = api.getStories(publication, category) + logJson(stories) + for (story in stories.stories) { + val article = api.coverStory(publication, story) + logJson(article) + out( + """ + | + |### ${article.title} + | + |![${story.image!!.detailedCaption}](${ + writeImage( + proxy.api.text_to_image( + story.image.detailedCaption, + resolution = 512 + )[0] + ) + }) + | + |${article.content.joinToString("\n\n")} + | + |Keywords: ${article.keywords} + | + |""".trimMargin() + ) + } + } + } + } +} + diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ComicBook.kt b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ComicBook.kt new file mode 100644 index 00000000..c2210d4f --- /dev/null +++ b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ComicBook.kt @@ -0,0 +1,149 @@ +package com.github.simiacryptus.aicoder.proxy + +import org.junit.Test + +/** + * ComicBook creates a comic book from an idea. + */ +class ComicBook: GenerationReportBase() { + interface Comic { + + fun getCharacters( + characterCount: Int = 3, + gender: String? = null, + age: Int? = null, + species: String? = null + ): CharacterList + + data class CharacterList( + val characters: List = listOf(), + ) + + data class Character( + val name: String = "", + val gender: String = "", + val age: Int = 0, + val species: String = "", + val description: String = "", + val image: ImageDescription? = null, + ) + + data class ImageDescription( + val style: String = "", + val subject: String = "", + val background: String = "", + val detailedCaption: String = "", + ) + + fun getStory( + characters: CharacterList, + plot: String, + setting: String, + pageCount: Int = 10 + ): Story + + data class Story( + val title: String = "", + val author: String = "", + val characters: List = listOf(), + val plot: String = "", + val setting: String = "", + val pages: List = listOf(), + ) + + data class Page( + val image: ImageDescription? = null, + val text: String = "", + ) + + } + + @Test + fun comicBook() { + runReport("ComicBook", Comic::class) { api, logJson, out -> + val characters = api.getCharacters( + characterCount = 3, + gender = "male", + age = 20, + species = "human" + ) + logJson(characters) + out( + """ + | + |# Characters + | + |""".trimMargin() + ) + for (character in characters.characters) { + logJson(character) + out( + """ + | + |## ${character.name} + | + |![${character.image!!.detailedCaption}](${ + writeImage( + proxy.api.text_to_image( + character.image.detailedCaption, + resolution = 512 + )[0] + ) + }) + | + |Gender: ${character.gender} + | + |Age: ${character.age} + | + |Species: ${character.species} + | + |${character.description} + | + |""".trimMargin() + ) + } + val story = api.getStory( + characters = characters, + plot = "The characters must save the world from an alien invasion.", + setting = "A post-apocalyptic world.", + ) + logJson(story) + out( + """ + | + |# ${story.title} + | + |Author: ${story.author} + | + |Characters: ${story.characters.joinToString(", ")} + | + |Plot: ${story.plot} + | + |Setting: ${story.setting} + | + |""".trimMargin() + ) + for (page in story.pages) { + logJson(page) + out( + """ + | + |## Page ${story.pages.indexOf(page) + 1} + | + |![${page.image!!.detailedCaption}](${ + writeImage( + proxy.api.text_to_image( + page.image.detailedCaption, + resolution = 512 + )[0] + ) + }) + | + |${page.text} + | + |""".trimMargin() + ) + } + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/DebateJudge.kt b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/DebateJudge.kt new file mode 100644 index 00000000..7cc84815 --- /dev/null +++ b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/DebateJudge.kt @@ -0,0 +1,128 @@ +package com.github.simiacryptus.aicoder.proxy + +import org.junit.Test +import java.io.BufferedWriter +import java.io.File +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +class DebateJudge { + interface Debate { + fun newRandomDebate( + participantNames: List = listOf(""), + topic: String = "", + questionCount: Int = 5, + ): DebateSummary + + data class DebateSummary( + val topic: String = "", + val participants: List = listOf(), + val questions: List = listOf() + ) + + data class DebateParticipant( + val name: String = "", + val positions: List = listOf(), + val writingStyle: String = "", + ) + + data class DebateJudgement( + val winner: String = "", val reasoning: String = "", val pointsAwarded: Int = 0 + ) + + fun judgeDebate(debate: DebateSummary, dialog: DebateArguments): DebateJudgement + fun poseQuestion(debate: DebateSummary, question: String): DebateArgument + fun rebuttal( + debate: DebateSummary, question: String, speaker: String, argument: DebateArguments + ): DebateArgument + + data class DebateArguments( + val lines: List = listOf() + ) + + data class DebateArgument( + val speaker: String = "", + val summary: String = "", + val supportingDetails: List = listOf(), + ) + + fun writeArgumentText(debate: DebateSummary, argument: DebateArgument): ArgumentText + + data class ArgumentText( + val speaker: String = "", + val text: String = "", + ) + } + + @Test + fun judgeDebate() { + if (!ProxyTest.keyFile.exists()) return + val outputDir = File(".") + val markdownOutputFile = File( + outputDir, "Debate_${LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"))}.md" + ) + BufferedWriter(markdownOutputFile.writer()).use { writer -> + fun out(s: Any) { + println(s.toString()) + writer.write(s.toString()) + writer.newLine() + } + + val proxy = ProxyTest.chatProxy() + fun logJson( + obj: Any + ) { + out( + """ + |```json + |${proxy.toJson(obj)} + |``` + |""".trimMargin() + ) + } + + val file = File("debate.examples.json") + if (file.exists()) proxy.addExamples(file) + val debateApi = proxy.create(Debate::class.java) + val debate = debateApi.newRandomDebate( +// topic = "What is the secret to a happy life?", + topic = "What is the best way to solve a problem?", + participantNames = listOf("Socrates", "Buddha", "Jesus", "Confucius", "Nietzsche"), + ) + logJson(debate) + for (question in debate.questions) { + val argument = debateApi.poseQuestion(debate, question) + val dialog = listOf(argument).toMutableList() + logJson(argument) + debateApi.writeArgumentText(debate, argument).let { spokenText -> + out( + """ + | + |${spokenText.speaker}: ${spokenText.text} + | + |""".trimMargin() + ) + } + debate.participants.map { it.name }.filter { it != argument.speaker }.shuffled().forEach { speaker -> + val rebuttal = + debateApi.rebuttal(debate, question, speaker, Debate.DebateArguments(dialog.takeLast(1))) + logJson(rebuttal) + debateApi.writeArgumentText(debate, rebuttal).let { spokenText -> + out( + """ + | + |${spokenText.speaker}: ${spokenText.text} + | + |""".trimMargin() + ) + } + dialog.add(rebuttal) + } + logJson(debateApi.judgeDebate(debate, Debate.DebateArguments(dialog))) + } + } + } + +} + + diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/GenerationReportBase.kt b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/GenerationReportBase.kt new file mode 100644 index 00000000..4f53450a --- /dev/null +++ b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/GenerationReportBase.kt @@ -0,0 +1,54 @@ +package com.github.simiacryptus.aicoder.proxy + +import java.awt.image.BufferedImage +import java.io.BufferedWriter +import java.io.File +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import javax.imageio.ImageIO +import kotlin.reflect.KClass + +open class GenerationReportBase { + val proxy = ProxyTest.chatProxy( + "api.${ + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss")) + }.log.json" + ) + val outputDir = File("../intellij-aicoder-docs") + fun runReport(prefix: String, kClass: KClass, fn: (T, (Any) -> Unit, (Any) -> Unit) -> Unit) { + if (!ProxyTest.keyFile.exists()) return + val markdownOutputFile = File( + outputDir, + "${prefix}_${LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"))}.md" + ) + BufferedWriter(markdownOutputFile.writer()).use { writer -> + fun out(s: Any) { + println(s.toString()) + writer.write(s.toString()) + writer.newLine() + } + + fun logJson(obj: Any) { + out( + """ + |```json + |${proxy.toJson(obj)} + |``` + |""".trimMargin() + ) + } + + val file = File("$prefix.examples.json") + if (file.exists()) proxy.addExamples(file) + fn(proxy.create(kClass.java), ::logJson, ::out) + } + } + + fun writeImage(image: BufferedImage): String { + val imageDir = File(outputDir, "images") + imageDir.mkdirs() + val file = File.createTempFile("image", ".png", imageDir) + ImageIO.write(image, "png", file) + return file.toRelativeString(outputDir) + } +} diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ImageTest.kt b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ImageTest.kt new file mode 100644 index 00000000..ccb938f8 --- /dev/null +++ b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ImageTest.kt @@ -0,0 +1,17 @@ +package com.github.simiacryptus.aicoder.proxy + +import org.junit.Test + +import java.io.File + +class ImageTest: GenerationReportBase() { + @Test + fun imageGenerationTest() { + val image = proxy.api.text_to_image("Hello World")[0] + // Write the image to a file + val file = File.createTempFile("image", ".png") + javax.imageio.ImageIO.write(image, "png", file) + // Open the file + java.awt.Desktop.getDesktop().open(file) + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ProxyTest.kt b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ProxyTest.kt new file mode 100644 index 00000000..6457a993 --- /dev/null +++ b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ProxyTest.kt @@ -0,0 +1,106 @@ +package com.github.simiacryptus.aicoder.proxy + +import com.github.simiacryptus.aicoder.openai.proxy.ChatProxy +import com.github.simiacryptus.aicoder.openai.proxy.CompletionProxy +import com.intellij.openapi.util.io.FileUtil +import org.junit.Test +import java.io.File + +class ProxyTest { + companion object { + val keyFile = File("C:\\Users\\andre\\code\\all-projects\\openai.key") + fun chatProxy(apiLog: String = "api.log.json"): ChatProxy = ChatProxy( + apiKey = FileUtil.loadFile(keyFile).trim(), + apiLog = apiLog + ) + fun completionProxy(apiLog: String = "api.log.json"): CompletionProxy = CompletionProxy( + apiKey = FileUtil.loadFile(keyFile).trim(), + apiLog = apiLog + ) + } + + interface TestAPI { + fun simpleQuestion(question: SimpleQuestion): SimpleResponse + + class SimpleQuestion(var question: String = "") + + class SimpleResponse { + var answer: String = "" + var why: String = "" + } + + fun topTen(question: TopTenQuestion): TopTenResponse + + class TopTenQuestion(var question: String = "") + + class TopTenResponse { + var answers: List = listOf() + } + } + + interface EssayAPI { + fun essayOutline(thesis: Thesis = Thesis(), essayLength: String = ""): EssayOutline + + class Thesis(var statement: String = "") + + class EssayOutline( + var introduction: Introduction = Introduction(), + var bodyParagraphs: List = listOf(), + var conclusion: Conclusion = Conclusion(), + var topics: List = listOf() + ) + + class Introduction(var thesis: Thesis = Thesis("")) + + class BodyParagraph( + var topicSentence: TopicSentence = TopicSentence(), + var supportingDetails: List = listOf() + ) + + class TopicSentence(var sentence: String = "") + + class SupportingDetail(var detail: String = "") { + fun examples(api: EssayAPI): List { + return api.findExamples(this) + } + } + + class Conclusion(var thesis: Thesis = Thesis()) + + fun findExamples(paragraph: SupportingDetail = SupportingDetail()): List + } + + @Test + fun test_essayOutline() { + if (!keyFile.exists()) return + //println(TestGPTInterfaceProxy().api.getEngines().joinToString("\n")) + val statement = "The meaning of life is to live a life of meaning." + for (proxyFactory in listOf(completionProxy(), chatProxy())) { + val proxy = proxyFactory.create(EssayAPI::class.java) + val essayOutline = proxy.essayOutline( + EssayAPI.Thesis(statement), "5000 words" + ) + println(essayOutline.introduction!!.thesis.statement) + } + } + + @Test + fun test_simpleQuestion() { + if (!keyFile.exists()) return + val question = "What is the meaning of life?" + for (proxyFactory in listOf(completionProxy(), chatProxy())) { + val proxy = proxyFactory.create(TestAPI::class.java) + println(proxy.simpleQuestion(TestAPI.SimpleQuestion(question)).answer) + } + } + + + @Test + fun test_topTen() { + if (!keyFile.exists()) return + val proxy = chatProxy().create(TestAPI::class.java) + println(proxy.topTen(TestAPI.TopTenQuestion()).answers.joinToString("\n")) + } + + +} \ No newline at end of file diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/QATest.kt b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/QATest.kt new file mode 100644 index 00000000..bc484fc5 --- /dev/null +++ b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/QATest.kt @@ -0,0 +1,26 @@ +package com.github.simiacryptus.aicoder.proxy + +import org.junit.Test + +class QATest { + + interface QA { + fun simpleQuestion(question: SimpleQuestion): SimpleResponse + + class SimpleQuestion(var question: String = "") + + class SimpleResponse { + var answer: String = "" + var why: String = "" + } + } + + @Test + fun test_question() { + if (!ProxyTest.keyFile.exists()) return + val proxy = ProxyTest.completionProxy().create(QA::class.java) + println(proxy.simpleQuestion(QA.SimpleQuestion("What is the meaning of life?")).answer) + } + + +} \ No newline at end of file diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ScreenPlayWriter.kt b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ScreenPlayWriter.kt new file mode 100644 index 00000000..6205160c --- /dev/null +++ b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ScreenPlayWriter.kt @@ -0,0 +1,138 @@ +package com.github.simiacryptus.aicoder.proxy + +import org.junit.Test +import java.io.BufferedWriter +import java.io.File +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +class ScreenPlayWriter { + interface StoryTelling { + fun newStory( + moral: String = "", + themes: List = listOf("") + ): StorySummary + + data class StorySummary( + val title: String = "", + val description: String = "", + val characters: List = listOf(), + val setting: String = "", + val genre: String = "", + val theme: String = "", + val conflict: String = "", + val resolution: String = "", + val moral: String = "", + val tone: String = "", + val style: String = "" + ) + + data class Character( + val name: String = "", + val personality: String = "", + val backstory: String = "", + val motivation: String = "" + ) + + fun actSummaries(story: StorySummary): ActSummaryList + data class ActSummaryList(val acts: List = listOf()) + data class ActSummary( + val title: String = "", + val index: Int = 0, + val description: String = "" + ) { + override fun toString(): String = """ + |# Act ${this.index} - ${this.title} + |${this.description} + |""".trimMargin() + } + + fun sceneSummaries(story: StorySummary, act: ActSummary, previousActScene: SceneSummary?): SceneSummaryList + data class SceneSummaryList(val scenes: List = listOf()) + data class SceneSummary( + val title: String = "", + val index: Int = 0, + val description: String = "", + val characters: List = listOf(), + val setting: String = "", + val dialogLines: Int = 10, + ) { + override fun toString(): String = """ + |## Scene ${this.index} - ${this.title} + |${this.description} + | + |Setting: ${this.setting} + |""".trimMargin() + } + + fun sceneToScreenplay( + story: StorySummary, + act: ActSummary, + currentScene: SceneSummary, + previousSceneSummary: Screenplay? + ): Screenplay + + data class Screenplay(val segments: List = listOf()) { + override fun toString(): String = segments.joinToString("\n\n") { segment -> + when (segment.character) { + "Narrator" -> segment.text + else -> "${segment.character}: ${segment.text}" + } + } + } + + data class ScreenplaySegment( + val character: String = "", + val text: String = "" + ) + } + + @Test + fun writeNewStory() { + if (!ProxyTest.keyFile.exists()) return + val outputDir = File(".") + val markdownOutputFile = File( + outputDir, + "Story_${LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"))}.md" + ) + BufferedWriter(markdownOutputFile.writer()).use { writer -> + fun out(s: Any) { + println(s.toString()) + writer.write(s.toString()) + writer.newLine() + } + + val proxy = ProxyTest.chatProxy() + val file = File("story.examples.json") + if (file.exists()) proxy.addExamples(file) + val storyApi = proxy.create(StoryTelling::class.java) + val story = + storyApi.newStory( + themes = listOf("Children's Book", "Dark", "Funny For Adults"), + moral = "Never try to steal from a wizard" + ) + out("```json\n" + proxy.toJson(story) + "\n```\n") + val acts = storyApi.actSummaries(story) + var previousActScene: StoryTelling.SceneSummary? = null + val sceneMap = acts.acts.associateWith { act -> + out("```json\n" + proxy.toJson(act) + "\n```\n") + val scenes = storyApi.sceneSummaries(story, act, previousActScene) + previousActScene = scenes.scenes.lastOrNull() + out("```json\n" + proxy.toJson(scenes) + "\n```\n") + scenes + } + var previousScene: StoryTelling.Screenplay? = null + for ((act, scenes) in sceneMap) { + out(act) + for (scene in scenes.scenes) { + out(scene) + val screenplay: StoryTelling.Screenplay = + storyApi.sceneToScreenplay(story, act, scene, previousScene) + previousScene = screenplay + out(screenplay) + } + } + } + } +} + diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ShortStory.kt b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ShortStory.kt new file mode 100644 index 00000000..d34f301b --- /dev/null +++ b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/ShortStory.kt @@ -0,0 +1,120 @@ +package com.github.simiacryptus.aicoder.proxy + +import org.junit.Test + +/** + * Create an entertaining fictional story with several characters and a plot, with a twist ending and a moral + */ +class ShortStory : GenerationReportBase() { + interface Story { + + fun getCharacters(characterCount: Int = 5): CharacterList + + data class CharacterList( + val characters: List = listOf(), + ) + + data class Character( + val name: String = "", + val age: Int = 0, + val occupation: String = "", + val description: String = "", + val traits: List = listOf(), + val goals: List = listOf(), + val secrets: List = listOf(), + ) + + fun getPlot( + characters: List = listOf(), + setting: String = "", + plotPoints: Int = 5 + ): Plot + + data class Plot( + val characters: List = listOf(), + val setting: String = "", + val plotPoints: List = listOf(), + ) + + fun getTwistEnding(plot: Plot): String + + fun getMoral(plot: Plot): String + } + + @Test + fun shortStory() { + runReport("Short Story", Story::class) { api, logJson, out -> + val characters = api.getCharacters() + logJson(characters) + out( + """ + | + |# Characters + | + |""".trimMargin() + ) + for (character in characters.characters) { + out( + """ + | + |## ${character.name} + | + |Age: ${character.age} + | + |Occupation: ${character.occupation} + | + |Description: ${character.description} + | + |Traits: ${character.traits.joinToString(", ")} + | + |Goals: ${character.goals.joinToString(", ")} + | + |Secrets: ${character.secrets.joinToString(", ")} + | + |""".trimMargin() + ) + } + val setting = "A small town in the middle of nowhere" + out( + """ + | + |# Plot + | + |Setting: $setting + | + |""".trimMargin() + ) + val plot = api.getPlot(characters.characters, setting) + logJson(plot) + for (plotPoint in plot.plotPoints) { + out( + """ + | + |* $plotPoint + | + |""".trimMargin() + ) + } + val twistEnding = api.getTwistEnding(plot) + out( + """ + | + |# Twist Ending + | + |$twistEnding + | + |""".trimMargin() + ) + val moral = api.getMoral(plot) + out( + """ + | + |# Moral + | + |$moral + | + |""".trimMargin() + ) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/VideoGame.kt b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/VideoGame.kt new file mode 100644 index 00000000..3cd1ee49 --- /dev/null +++ b/src/test/kotlin/com/github/simiacryptus/aicoder/proxy/VideoGame.kt @@ -0,0 +1,167 @@ +package com.github.simiacryptus.aicoder.proxy + +import org.junit.Test + +/** + * VideoGame creates a video game from an idea. + */ +class VideoGame : GenerationReportBase() { + interface Game { + + fun getCharacters( + characterCount: Int = 3, + gender: String? = null, + age: Int? = null, + species: String? = null + ): CharacterList + + data class CharacterList( + val characters: List = listOf(), + ) + + data class Character( + val name: String = "", + val gender: String = "", + val age: Int = 0, + val species: String = "", + val description: String = "", + val image: ImageDescription? = null, + ) + + data class ImageDescription( + val style: String = "", + val subject: String = "", + val background: String = "", + val detailedCaption: String = "", + ) + + fun getGame( + characters: CharacterList, + plot: String, + setting: String, + levelCount: Int = 10 + ): Game + + data class Game( + val title: String = "", + val author: String = "", + val characters: List = listOf(), + val plot: String = "", + val setting: String = "", + val levels: List = listOf(), + ) + + data class Level( + val image: ImageDescription? = null, + val description: String = "", + val objectives: List = listOf(), + ) + + data class Objective( + val description: String = "", + val reward: String = "", + ) + + } + + @Test + fun videoGame() { + runReport("Video Game", Game::class) { api, logJson, out -> + val characters = api.getCharacters( + characterCount = 3, + gender = "male", + age = 20, + species = "human" + ) + logJson(characters) + out( + """ + | + |# Characters + | + |""".trimMargin() + ) + for (character in characters.characters) { + logJson(character) + out( + """ + | + |## ${character.name} + | + |![${character.image!!.detailedCaption}](${ + writeImage( + proxy.api.text_to_image( + character.image.detailedCaption, + resolution = 512 + )[0] + ) + }) + | + |Gender: ${character.gender} + | + |Age: ${character.age} + | + |Species: ${character.species} + | + |${character.description} + | + |""".trimMargin() + ) + } + val game = api.getGame( + characters = characters, + plot = "The characters must save the world from an alien invasion.", + setting = "A post-apocalyptic world.", + ) + logJson(game) + out( + """ + | + |# ${game.title} + | + |Author: ${game.author} + | + |Characters: ${game.characters.joinToString(", ")} + | + |Plot: ${game.plot} + | + |Setting: ${game.setting} + | + |""".trimMargin() + ) + for (level in game.levels) { + logJson(level) + out( + """ + | + |## Level ${game.levels.indexOf(level) + 1} + | + |![${level.image!!.detailedCaption}](${ + writeImage( + proxy.api.text_to_image( + level.image.detailedCaption, + resolution = 512 + )[0] + ) + }) + | + |${level.description} + | + |Objectives: + | + |""".trimMargin() + ) + for (objective in level.objectives) { + logJson(objective) + out( + """ + | + |- ${objective.description} (Reward: ${objective.reward}) + | + |""".trimMargin() + ) + } + } + } + } +} \ No newline at end of file