diff --git a/pom.xml b/pom.xml
index 6b786e3f..55ee47fb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -209,15 +209,11 @@
- com.theokanning.openai-gpt3-java
- client
- 0.14.0
-
-
- com.theokanning.openai-gpt3-java
- service
- 0.14.0
+ org.json
+ json
+ 20230227
+
diff --git a/src/main/java/org/scijava/ui/swing/script/ClaudeApiClient.java b/src/main/java/org/scijava/ui/swing/script/ClaudeApiClient.java
new file mode 100644
index 00000000..f7f13b7c
--- /dev/null
+++ b/src/main/java/org/scijava/ui/swing/script/ClaudeApiClient.java
@@ -0,0 +1,84 @@
+package org.scijava.ui.swing.script;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+
+public class ClaudeApiClient {
+
+ public static String prompt(String prompt, String model, String apikey, String apiurl) throws IOException {
+
+ if (apiurl == null) {
+ apiurl = "https://api.anthropic.com/v1/messages";
+ }
+ if (apikey == null) {
+ apikey = System.getenv("ANTHROPIC_API_KEY");
+ }
+
+ System.out.println("API KEY:" + apikey);
+
+ URL url = new URL(apiurl);
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.setRequestMethod("POST");
+ connection.setRequestProperty("x-api-key", apikey);
+ connection.setRequestProperty("anthropic-version", "2023-06-01");
+ connection.setRequestProperty("content-type", "application/json");
+ connection.setDoOutput(true);
+
+ JSONObject requestBody = new JSONObject();
+ requestBody.put("model", model);
+ requestBody.put("max_tokens", 8192);
+ requestBody.put("messages", new JSONObject[]{
+ new JSONObject().put("role", "user").put("content", prompt)
+ });
+
+
+ // Send request
+ try (OutputStream os = connection.getOutputStream()) {
+ byte[] input = requestBody.toString().getBytes("utf-8");
+ os.write(input, 0, input.length);
+ }
+
+ int responseCode = connection.getResponseCode();
+
+ StringBuilder response = new StringBuilder();
+ try (BufferedReader br = new BufferedReader(
+ new InputStreamReader(
+ responseCode >= 400 ? connection.getErrorStream() : connection.getInputStream(),
+ StandardCharsets.UTF_8
+ )
+ )) {
+ String responseLine;
+ while ((responseLine = br.readLine()) != null) {
+ response.append(responseLine.trim());
+ }
+ }
+
+ //System.out.println("Response Code: " + responseCode);
+ //System.out.println("Full response: " + response.toString());
+
+ if (responseCode >= 400) {
+ return "Error: " + response.toString();
+ }
+
+ try {
+ JSONObject jsonObject = new JSONObject(response.toString());
+ String content = jsonObject.getJSONArray("content").getJSONObject(0).getString("text");
+ return content;
+ } catch (Exception e) {
+ System.out.println("Error parsing JSON: " + e.getMessage());
+ return null;
+ }
+ }
+
+ public static void main(String[] args) throws IOException {
+ String input = "Hello, Claude! How are you today?";
+ String response = prompt(input, "claude-3-5-sonnet-20240620", null, null);
+ System.out.println("Claude's response: " + response);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/scijava/ui/swing/script/OpenAIOptions.java b/src/main/java/org/scijava/ui/swing/script/LLMServicesOptions.java
similarity index 60%
rename from src/main/java/org/scijava/ui/swing/script/OpenAIOptions.java
rename to src/main/java/org/scijava/ui/swing/script/LLMServicesOptions.java
index d7da558f..60734128 100644
--- a/src/main/java/org/scijava/ui/swing/script/OpenAIOptions.java
+++ b/src/main/java/org/scijava/ui/swing/script/LLMServicesOptions.java
@@ -38,28 +38,43 @@
*
* @author Curtis Rueden
*/
-@Plugin(type = OptionsPlugin.class, menuPath = "Edit>Options>OpenAI...")
-public class OpenAIOptions extends OptionsPlugin {
+@Plugin(type = OptionsPlugin.class, menuPath = "Edit>Options>LLM Service Options...")
+public class LLMServicesOptions extends OptionsPlugin {
- @Parameter(label = "OpenAI key", required = false)
- private String openAIKey;
+ @Parameter(label = "OpenAI API key", required = false)
+ private String openAIAPIKey;
+
+ @Parameter(label = "Anthropic API key", required = false)
+ private String anthropicAPIKey;
@Parameter(label = "OpenAI Model name")
- private String modelName = "gpt-3.5-turbo-0613";
+ private String openAIModelName = "gpt-4o-2024-08-06";
+
+ @Parameter(label = "Anthropic Model name")
+ private String anthropicModelName = "claude-3-5-sonnet-20240620";
@Parameter(label = "Prompt prefix (added before custom prompt)", style = "text area")
- private String promptPrefix = "Write code in {programming_language}.\n" +
+ private String promptPrefix =
+ "You are an extremely talented Bio-image Analyst and programmer.\n" +
+ "You write code in {programming_language}.\n" +
"Write concise and high quality code for ImageJ/Fiji.\n" +
"Put minimal comments explaining what the code does.\n" +
- "The code should do the following:\n" +
+ "Your task is the following:\n" +
"{custom_prompt}";
- public String getOpenAIKey() {
- return openAIKey;
+ public String getOpenAIAPIKey() {
+ return openAIAPIKey;
}
- public void setOpenAIKey(final String openAIKey) {
- this.openAIKey = openAIKey;
+ public String getAnthropicAPIKey() {
+ return anthropicAPIKey;
+ }
+
+ public void setOpenAIAPIKey(final String openAIAPIKey) {
+ this.openAIAPIKey = openAIAPIKey;
+ }
+ public void setAnthropicAPIKey(final String anthropicAPIKey) {
+ this.anthropicAPIKey = anthropicAPIKey;
}
public String getPromptPrefix() {
@@ -70,12 +85,20 @@ public void setPromptPrefix(final String promptPrefix) {
this.promptPrefix = promptPrefix;
}
- public String getModelName() {
- return modelName;
+ public String getOpenAIModelName() {
+ return openAIModelName;
+ }
+
+ public void setOpenAIModelName(final String openAIModelName) {
+ this.openAIModelName = openAIModelName;
+ }
+
+ public String getAnthropidModelName() {
+ return anthropicModelName;
}
- public void setModelName(final String modelName) {
- this.modelName = modelName;
+ public void setAnthropidModelName(final String anthropicModelName) {
+ this.anthropicModelName = anthropicModelName;
}
diff --git a/src/main/java/org/scijava/ui/swing/script/OpenAIClient.java b/src/main/java/org/scijava/ui/swing/script/OpenAIClient.java
new file mode 100644
index 00000000..472fc4b1
--- /dev/null
+++ b/src/main/java/org/scijava/ui/swing/script/OpenAIClient.java
@@ -0,0 +1,68 @@
+package org.scijava.ui.swing.script;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public class OpenAIClient {
+
+ public static void main(String[] args) {
+ String prompt = "Hello, GPT-4!";
+ try {
+ String response = prompt(prompt, "gpt-4o-2024-08-06", null, null);
+ System.out.println("GPT-4 Response: " + response);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static String prompt(String prompt, String model, String apikey, String apiurl) throws Exception {
+
+ if (apiurl == null) {
+ apiurl = "https://api.openai.com/v1/chat/completions";
+ }
+ if (apikey == null) {
+ apikey = System.getenv("OPENAI_API_KEY");
+ }
+
+ URL url = new URL(apiurl);
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.setRequestMethod("POST");
+ connection.setRequestProperty("Authorization", "Bearer " + apikey);
+ connection.setRequestProperty("Content-Type", "application/json");
+ connection.setDoOutput(true);
+
+ // Create JSON request body
+ JSONObject requestBody = new JSONObject();
+ requestBody.put("model", model);
+ requestBody.put("messages", new JSONObject[]{
+ new JSONObject().put("role", "user").put("content", prompt)
+ });
+
+ System.out.println(connection);
+ System.out.println(requestBody);
+
+ // Send request
+ try (OutputStream os = connection.getOutputStream()) {
+ byte[] input = requestBody.toString().getBytes("utf-8");
+ os.write(input, 0, input.length);
+ }
+
+ // Read response
+ StringBuilder response = new StringBuilder();
+ try (BufferedReader br = new BufferedReader(
+ new InputStreamReader(connection.getInputStream(), "utf-8"))) {
+ String responseLine;
+ while ((responseLine = br.readLine()) != null) {
+ response.append(responseLine.trim());
+ }
+ }
+
+ // Parse JSON response
+ JSONObject jsonResponse = new JSONObject(response.toString());
+ return jsonResponse.getJSONArray("choices").getJSONObject(0).getJSONObject("message").getString("content");
+ }
+}
diff --git a/src/main/java/org/scijava/ui/swing/script/TextEditor.java b/src/main/java/org/scijava/ui/swing/script/TextEditor.java
index 6b6232ce..a2cff1a1 100644
--- a/src/main/java/org/scijava/ui/swing/script/TextEditor.java
+++ b/src/main/java/org/scijava/ui/swing/script/TextEditor.java
@@ -107,7 +107,6 @@
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import javax.swing.*;
-import javax.swing.border.BevelBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
@@ -118,10 +117,6 @@
import javax.swing.text.Position;
import javax.swing.tree.TreePath;
-import com.theokanning.openai.completion.chat.ChatCompletionRequest;
-import com.theokanning.openai.completion.chat.ChatMessage;
-import com.theokanning.openai.completion.chat.ChatMessageRole;
-import com.theokanning.openai.service.OpenAiService;
import org.fife.ui.rsyntaxtextarea.AbstractTokenMakerFactory;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.Theme;
@@ -220,7 +215,7 @@ public class TextEditor extends JFrame implements ActionListener,
removeTrailingWhitespace, findNext, findPrevious, openHelp, addImport,
nextError, previousError, openHelpWithoutFrames, nextTab,
previousTab, runSelection, extractSourceJar, askChatGPTtoGenerateCode,
- openSourceForClass,
+ askClaudetoGenerateCode, openSourceForClass,
//openSourceForMenuItem, // this never had an actionListener!??
openMacroFunctions, decreaseFontSize, increaseFontSize, chooseFontSize,
chooseTabSize, gitGrep, replaceTabsWithSpaces,
@@ -518,8 +513,9 @@ public TextEditor(final Context context) {
//openSourceForMenuItem = addToMenu(toolsMenu, "Open Java File for Menu Item...", 0, 0);
//openSourceForMenuItem.setMnemonic(KeyEvent.VK_M);
- GuiUtils.addMenubarSeparator(toolsMenu, "chatGPT");
- askChatGPTtoGenerateCode = addToMenu(toolsMenu, "Ask chatGPT...", 0, 0);
+ GuiUtils.addMenubarSeparator(toolsMenu, "AI assistance");
+ askChatGPTtoGenerateCode = addToMenu(toolsMenu, "Ask OpenAI's chatGPT...", 0, 0);
+ askClaudetoGenerateCode = addToMenu(toolsMenu, "Ask Anthropic's Claude...", 0, 0 );
addScritpEditorMacroCommands(toolsMenu);
@@ -1627,6 +1623,7 @@ else if (source == chooseTabSize) {
else if (source == openClassOrPackageHelp) openClassOrPackageHelp(null);
else if (source == extractSourceJar) extractSourceJar();
else if (source == askChatGPTtoGenerateCode) askChatGPTtoGenerateCode();
+ else if (source == askClaudetoGenerateCode) askClaudetoGenerateCode();
else if (source == openSourceForClass) {
final String className = getSelectedClassNameOrAsk("Class (fully qualified name):", "Which Class?");
if (className != null) {
@@ -3244,17 +3241,41 @@ public void extractSourceJar() {
}
public void askChatGPTtoGenerateCode() {
+ askLLMServiceProviderToGenerateCode("openai");
+ }
+ public void askClaudetoGenerateCode() {
+ askLLMServiceProviderToGenerateCode("anthropic");
+ }
+
+ public void askLLMServiceProviderToGenerateCode(String service_provider) {
SwingUtilities.invokeLater(() -> {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ String text = getTextArea().getSelectedText();
+ if (text == null) {
+ text = getTextArea().getText();
+ }
+
// setup default prompt
String prompt =
promptPrefix()
.replace("{programming_language}", getCurrentLanguage().getLanguageName() )
- .replace("{custom_prompt}", getTextArea().getSelectedText());
+ .replace("{custom_prompt}", text);
- String answer = askChatGPT(prompt);
+ String answer = null;
+
+ try {
+ if (service_provider.equals("openai")) {
+ answer = new OpenAIClient().prompt(prompt, openAIModelName(), openaiApiKey(), null);
+ } else if (service_provider.equals("anthropic")) {
+ answer = new ClaudeApiClient().prompt(prompt, anthropicModelName(), anthropicApiKey(), null);
+ } else {
+ throw new RuntimeException("LLM Service provider " + service_provider + " is not implemented.");
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
if (answer.contains("```")) {
// clean answer by removing blabla outside the code block
@@ -3264,6 +3285,8 @@ public void askChatGPTtoGenerateCode() {
answer = answer.replace("```jython", "```");
answer = answer.replace("```macro", "```");
answer = answer.replace("```groovy", "```");
+ answer = answer.replace("```ijm", "```");
+ answer = answer.replace("```imagej", "```");
String[] temp = answer.split("```");
answer = temp[1];
@@ -3275,47 +3298,48 @@ public void askChatGPTtoGenerateCode() {
});
}
- private String askChatGPT(String text) {
- // Modified from: https://github.com/TheoKanning/openai-java/blob/main/example/src/main/java/example/OpenAiApiFunctionsExample.java
- String token = apiKey();
-
- OpenAiService service = new OpenAiService(token);
-
- List messages = new ArrayList<>();
- ChatMessage userMessage = new ChatMessage(ChatMessageRole.USER.value(), text);
- messages.add(userMessage);
-
- ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest
- .builder()
- .model(modelName())
- .messages(messages).build();
-
- ChatMessage responseMessage = service.createChatCompletion(chatCompletionRequest).getChoices().get(0).getMessage();
- messages.add(responseMessage);
-
- String result = responseMessage.getContent();
- System.out.println(result);
- return result;
+ private String anthropicApiKey() {
+ if (optionsService != null) {
+ final LLMServicesOptions llmOptions =
+ optionsService.getOptions(LLMServicesOptions.class);
+ if (llmOptions != null) {
+ final String key = llmOptions.getAnthropicAPIKey();
+ if (key != null && !key.isEmpty()) return key;
+ }
+ }
+ return System.getenv("ANTHROPIC_API_KEY");
}
- private String apiKey() {
+ private String openaiApiKey() {
if (optionsService != null) {
- final OpenAIOptions openAIOptions =
- optionsService.getOptions(OpenAIOptions.class);
- if (openAIOptions != null) {
- final String key = openAIOptions.getOpenAIKey();
+ final LLMServicesOptions llmOptions =
+ optionsService.getOptions(LLMServicesOptions.class);
+ if (llmOptions != null) {
+ final String key = llmOptions.getOpenAIAPIKey();
if (key != null && !key.isEmpty()) return key;
}
}
return System.getenv("OPENAI_API_KEY");
}
- private String modelName() {
+ private String openAIModelName() {
if (optionsService != null) {
- final OpenAIOptions openAIOptions =
- optionsService.getOptions(OpenAIOptions.class);
- if (openAIOptions != null) {
- final String key = openAIOptions.getModelName();
+ final LLMServicesOptions llmOptions =
+ optionsService.getOptions(LLMServicesOptions.class);
+ if (llmOptions != null) {
+ final String key = llmOptions.getOpenAIModelName();
+ if (key != null && !key.isEmpty()) return key;
+ }
+ }
+ return null;
+ }
+
+ private String anthropicModelName() {
+ if (optionsService != null) {
+ final LLMServicesOptions llmOptions =
+ optionsService.getOptions(LLMServicesOptions.class);
+ if (llmOptions != null) {
+ final String key = llmOptions.getAnthropidModelName();
if (key != null && !key.isEmpty()) return key;
}
}
@@ -3324,8 +3348,8 @@ private String modelName() {
private String promptPrefix() {
if (optionsService != null) {
- final OpenAIOptions openAIOptions =
- optionsService.getOptions(OpenAIOptions.class);
+ final LLMServicesOptions openAIOptions =
+ optionsService.getOptions(LLMServicesOptions.class);
if (openAIOptions != null) {
final String promptPrefix = openAIOptions.getPromptPrefix();
if (promptPrefix != null && !promptPrefix.isEmpty()) return promptPrefix;