From 0549957f717d7d3a0706f5c5df0b7390e41df18f Mon Sep 17 00:00:00 2001 From: Carl-Robert Linnupuu Date: Mon, 27 Jan 2025 14:12:27 +0000 Subject: [PATCH] feat: support DeepSeek R1 and V3 --- src/main/cpp/llama.cpp | 2 +- .../ee/carlrobert/codegpt/CodeGPTPlugin.java | 14 ++- .../codegpt/completions/HuggingFaceModel.java | 11 ++ .../codegpt/completions/llama/LlamaModel.java | 14 +++ .../completions/llama/LlamaServerAgent.java | 117 ++++++++++++------ .../completions/llama/PromptTemplate.java | 18 +++ .../chat/ChatToolWindowTabPanel.java | 1 - .../chat/ui/ChatMessageResponseBody.java | 52 ++++++-- .../factory/OpenAIRequestFactory.kt | 10 +- .../service/codegpt/CodeGPTAvailableModels.kt | 12 +- .../toolwindow/chat/ThinkingOutputParser.kt | 39 ++++++ .../codegpt/toolwindow/ui/UserMessagePanel.kt | 2 +- .../codegpt/ui/ThoughtProcessPanel.kt | 78 ++++++++++++ 13 files changed, 305 insertions(+), 65 deletions(-) create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/ThinkingOutputParser.kt create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/ui/ThoughtProcessPanel.kt diff --git a/src/main/cpp/llama.cpp b/src/main/cpp/llama.cpp index 0541f0629..d6d24cd9e 160000 --- a/src/main/cpp/llama.cpp +++ b/src/main/cpp/llama.cpp @@ -1 +1 @@ -Subproject commit 0541f06296753dbc59a57379eb54cec865a4c9f9 +Subproject commit d6d24cd9ed6d0b9558643dcc28f2124bef488c52 diff --git a/src/main/java/ee/carlrobert/codegpt/CodeGPTPlugin.java b/src/main/java/ee/carlrobert/codegpt/CodeGPTPlugin.java index 7b54b955e..3691adf5d 100644 --- a/src/main/java/ee/carlrobert/codegpt/CodeGPTPlugin.java +++ b/src/main/java/ee/carlrobert/codegpt/CodeGPTPlugin.java @@ -1,12 +1,12 @@ package ee.carlrobert.codegpt; +import static java.io.File.separator; import static java.util.Objects.requireNonNull; import com.intellij.ide.plugins.PluginManagerCore; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.project.Project; -import java.io.File; import java.nio.file.Path; import org.jetbrains.annotations.NotNull; @@ -26,18 +26,22 @@ private CodeGPTPlugin() { } public static @NotNull String getPluginOptionsPath() { - return PathManager.getOptionsPath() + File.separator + "CodeGPT"; + return PathManager.getOptionsPath() + separator + "CodeGPT"; } public static @NotNull String getIndexStorePath() { - return getPluginOptionsPath() + File.separator + "indexes"; + return getPluginOptionsPath() + separator + "indexes"; } public static @NotNull String getLlamaSourcePath() { - return getPluginBasePath() + File.separator + "llama.cpp"; + return getPluginBasePath() + separator + "llama.cpp"; + } + + public static @NotNull String getLlamaServerSourcePath() { + return getPluginBasePath() + separator + "llama.cpp" + separator + "build" + separator + "bin"; } public static @NotNull String getProjectIndexStorePath(@NotNull Project project) { - return getIndexStorePath() + File.separator + project.getName(); + return getIndexStorePath() + separator + project.getName(); } } diff --git a/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java b/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java index c5e3d8a96..1e257140b 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/HuggingFaceModel.java @@ -46,6 +46,17 @@ public enum HuggingFaceModel { DEEPSEEK_CODER_33B_Q5(33, 5, "deepseek-coder-33b-instruct-GGUF", "deepseek-coder-33b-instruct.Q5_K_M.gguf", 23.5), + DEEPSEEK_R1_1_5B_Q6(1, 6, "DeepSeek-R1-Distill-Qwen-1.5B-GGUF", + "DeepSeek-R1-Distill-Qwen-1.5B-Q6_K.gguf", "bartowski", 1.89), + DEEPSEEK_R1_7B_Q4(7, 4, "DeepSeek-R1-Distill-Qwen-7B-GGUF", + "DeepSeek-R1-Distill-Qwen-7B-Q4_K_M.gguf", "bartowski", 4.68), + DEEPSEEK_R1_7B_Q6(7, 6, "DeepSeek-R1-Distill-Qwen-7B-GGUF", + "DeepSeek-R1-Distill-Qwen-7B-Q6_K.gguf", "bartowski", 6.25), + DEEPSEEK_R1_14B_Q4(14, 4, "DeepSeek-R1-Distill-Qwen-14B-GGUF", + "DeepSeek-R1-Distill-Qwen-14B-Q4_K_M.gguf", "bartowski", 8.99), + DEEPSEEK_R1_14B_Q6(14, 6, "DeepSeek-R1-Distill-Qwen-14B-GGUF", + "DeepSeek-R1-Distill-Qwen-14B-Q6_K.gguf", "bartowski", 12.12), + PHIND_CODE_LLAMA_34B_Q3(34, 3, "Phind-CodeLlama-34B-v2-GGUF", "phind-codellama-34b-v2.Q3_K_M.gguf"), PHIND_CODE_LLAMA_34B_Q4(34, 4, "Phind-CodeLlama-34B-v2-GGUF", diff --git a/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java b/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java index 2f63ab09c..9b161250e 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaModel.java @@ -64,6 +64,20 @@ public enum LlamaModel { HuggingFaceModel.DEEPSEEK_CODER_33B_Q3, HuggingFaceModel.DEEPSEEK_CODER_33B_Q4, HuggingFaceModel.DEEPSEEK_CODER_33B_Q5)), + DEEPSEEK_R1( + "Deepseek R1", + "DeepSeek-R1-Zero, a model trained via large-scale reinforcement learning (RL) " + + "without supervised fine-tuning (SFT) as a preliminary step, demonstrated remarkable " + + "performance on reasoning. DeepSeek-R1 achieves performance comparable to OpenAI-o1 " + + "across math, code, and reasoning tasks.", + PromptTemplate.DEEPSEEK_R1, + InfillPromptTemplate.DEEPSEEK_CODER, + List.of( + HuggingFaceModel.DEEPSEEK_R1_1_5B_Q6, + HuggingFaceModel.DEEPSEEK_R1_7B_Q4, + HuggingFaceModel.DEEPSEEK_R1_7B_Q6, + HuggingFaceModel.DEEPSEEK_R1_14B_Q4, + HuggingFaceModel.DEEPSEEK_R1_14B_Q6)), PHIND_CODE_LLAMA( "Phind Code Llama", "This model is fine-tuned from Phind-CodeLlama-34B-v1 on an additional 1.5B tokens " diff --git a/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaServerAgent.java b/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaServerAgent.java index 863dc2d2b..ebf51491e 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaServerAgent.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/llama/LlamaServerAgent.java @@ -33,7 +33,8 @@ public final class LlamaServerAgent implements Disposable { private static final Logger LOG = Logger.getInstance(LlamaServerAgent.class); - private @Nullable OSProcessHandler makeProcessHandler; + private @Nullable OSProcessHandler makeSetupProcessHandler; + private @Nullable OSProcessHandler makeBuildProcessHandler; private @Nullable OSProcessHandler startServerProcessHandler; private ServerProgressPanel activeServerProgressPanel; private boolean stoppedByUser; @@ -49,11 +50,44 @@ public void startAgent( stoppedByUser = false; serverProgressPanel.displayText( CodeGPTBundle.get("llamaServerAgent.buildingProject.description")); - makeProcessHandler = new OSProcessHandler( - getMakeCommandLine(params)); - makeProcessHandler.addProcessListener( - getMakeProcessListener(params, onSuccess, onServerStopped)); - makeProcessHandler.startNotify(); + + makeSetupProcessHandler = new OSProcessHandler(getCMakeSetupCommandLine(params)); + makeSetupProcessHandler.addProcessListener(new ProcessAdapter() { + private final List errorLines = new CopyOnWriteArrayList<>(); + + @Override + public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) { + if (ProcessOutputType.isStderr(outputType)) { + errorLines.add(event.getText()); + return; + } + LOG.info(event.getText()); + } + + @Override + public void processTerminated(@NotNull ProcessEvent event) { + int exitCode = event.getExitCode(); + LOG.info(format("CMake setup exited with code %d", exitCode)); + if (stoppedByUser) { + onServerStopped.accept(activeServerProgressPanel); + return; + } + if (exitCode != 0) { + showServerError(String.join(",", errorLines), onServerStopped); + return; + } + + try { + makeBuildProcessHandler = new OSProcessHandler(getCMakeBuildCommandLine(params)); + makeBuildProcessHandler.addProcessListener( + getMakeProcessListener(params, onSuccess, onServerStopped)); + makeBuildProcessHandler.startNotify(); + } catch (ExecutionException e) { + showServerError(e.getMessage(), onServerStopped); + } + } + }); + makeSetupProcessHandler.startNotify(); } catch (ExecutionException e) { showServerError(e.getMessage(), onServerStopped); } @@ -62,8 +96,8 @@ public void startAgent( public void stopAgent() { stoppedByUser = true; - if (makeProcessHandler != null) { - makeProcessHandler.destroyProcess(); + if (makeSetupProcessHandler != null) { + makeSetupProcessHandler.destroyProcess(); } if (startServerProcessHandler != null) { startServerProcessHandler.destroyProcess(); @@ -71,9 +105,9 @@ public void stopAgent() { } public boolean isServerRunning() { - return (makeProcessHandler != null - && makeProcessHandler.isStartNotified() - && !makeProcessHandler.isProcessTerminated()) + return (makeSetupProcessHandler != null + && makeSetupProcessHandler.isStartNotified() + && !makeSetupProcessHandler.isProcessTerminated()) || (startServerProcessHandler != null && startServerProcessHandler.isStartNotified() && !startServerProcessHandler.isProcessTerminated()); @@ -147,25 +181,14 @@ public void processTerminated(@NotNull ProcessEvent event) { @Override public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) { - if (ProcessOutputType.isStderr(outputType)) { - errorLines.add(event.getText()); - } - - if (ProcessOutputType.isStdout(outputType)) { - LOG.info(event.getText()); + LOG.info(event.getText()); - try { - var serverMessage = objectMapper.readValue(event.getText(), LlamaServerMessage.class); - // hack - if ("HTTP server listening".equals(serverMessage.msg())) { - LOG.info("Server up and running!"); + // TODO: Use proper successful boot up validation + if (event.getText().contains("server is listening")) { + LOG.info("Server up and running!"); - LlamaSettings.getCurrentState().setServerPort(port); - onSuccess.run(); - } - } catch (Exception ignore) { - // ignore - } + LlamaSettings.getCurrentState().setServerPort(port); + onSuccess.run(); } } }; @@ -177,21 +200,33 @@ private void showServerError(String errorText, Consumer onS OverlayUtil.showClosableBalloon(errorText, MessageType.ERROR, activeServerProgressPanel); } - private static GeneralCommandLine getMakeCommandLine(LlamaServerStartupParams params) { - GeneralCommandLine commandLine = new GeneralCommandLine().withCharset(StandardCharsets.UTF_8); - commandLine.setExePath("make"); - commandLine.withWorkDirectory(CodeGPTPlugin.getLlamaSourcePath()); - commandLine.addParameters("-j"); - commandLine.addParameters(params.additionalBuildParameters()); - commandLine.withEnvironment(params.additionalEnvironmentVariables()); - commandLine.setRedirectErrorStream(false); - return commandLine; + private static GeneralCommandLine getCMakeSetupCommandLine(LlamaServerStartupParams params) { + GeneralCommandLine cmakeSetupCommand = new GeneralCommandLine().withCharset( + StandardCharsets.UTF_8); + cmakeSetupCommand.setExePath("cmake"); + cmakeSetupCommand.withWorkDirectory(CodeGPTPlugin.getLlamaSourcePath()); + cmakeSetupCommand.addParameters("-B", "build"); + cmakeSetupCommand.withEnvironment(params.additionalEnvironmentVariables()); + cmakeSetupCommand.setRedirectErrorStream(false); + return cmakeSetupCommand; + } + + private static GeneralCommandLine getCMakeBuildCommandLine(LlamaServerStartupParams params) { + GeneralCommandLine cmakeBuildCommand = new GeneralCommandLine().withCharset( + StandardCharsets.UTF_8); + cmakeBuildCommand.setExePath("cmake"); + cmakeBuildCommand.withWorkDirectory(CodeGPTPlugin.getLlamaSourcePath()); + cmakeBuildCommand.addParameters("--build", "build", "--config", "Release", "-t", "llama-server", + "-j", "4"); + cmakeBuildCommand.withEnvironment(params.additionalEnvironmentVariables()); + cmakeBuildCommand.setRedirectErrorStream(false); + return cmakeBuildCommand; } private GeneralCommandLine getServerCommandLine(LlamaServerStartupParams params) { GeneralCommandLine commandLine = new GeneralCommandLine().withCharset(StandardCharsets.UTF_8); - commandLine.setExePath("./server"); - commandLine.withWorkDirectory(CodeGPTPlugin.getLlamaSourcePath()); + commandLine.setExePath("./llama-server"); + commandLine.withWorkDirectory(CodeGPTPlugin.getLlamaServerSourcePath()); commandLine.addParameters( "-m", params.modelPath(), "-c", String.valueOf(params.contextLength()), @@ -210,8 +245,8 @@ public void setActiveServerProgressPanel( @Override public void dispose() { - if (makeProcessHandler != null && !makeProcessHandler.isProcessTerminated()) { - makeProcessHandler.destroyProcess(); + if (makeSetupProcessHandler != null && !makeSetupProcessHandler.isProcessTerminated()) { + makeSetupProcessHandler.destroyProcess(); } if (startServerProcessHandler != null && !startServerProcessHandler.isProcessTerminated()) { startServerProcessHandler.destroyProcess(); diff --git a/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java b/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java index 7bfa5a252..d81284a77 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/llama/PromptTemplate.java @@ -4,6 +4,7 @@ import ee.carlrobert.codegpt.conversations.message.Message; import java.util.List; +import java.util.stream.Collectors; public enum PromptTemplate { @@ -237,6 +238,23 @@ public String buildPrompt(String systemPrompt, String userPrompt, List .toString(); } }, + DEEPSEEK_R1("DeepSeek R1") { + @Override + public String buildPrompt(String systemPrompt, String userPrompt, List history) { + var historyString = history.stream() + .map(it -> { + String response = it.getResponse(); + if (response.startsWith("")) { + response = response.replaceAll("(?s).*?", "").trim(); + } + return String.format("User:\n%s\n\nAssistant:\n%s", it.getPrompt(), response); + }) + .collect(Collectors.joining("\n\n")); + + return "<|begin▁of▁sentence|>%s<|User|>History:\n%s\n\nUser:\n%s<|Assistant|>" + .formatted(systemPrompt, historyString, userPrompt); + } + }, DEEPSEEK_CODER("DeepSeek Coder") { @Override public String buildPrompt(String systemPrompt, String userPrompt, List history) { diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java index 487ad6e89..3eff42759 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java @@ -225,7 +225,6 @@ private ResponseMessagePanel createResponseMessagePanel(ChatCompletionParameters panel.addCopyAction(() -> CopyAction.copyToClipboard(message.getResponse())); panel.addContent(new ChatMessageResponseBody( project, - true, false, message.isWebSearchIncluded(), fileContextIncluded || message.getDocumentationDetails() != null, diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java index 53065f5af..53a6d7d2b 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java @@ -37,17 +37,18 @@ import ee.carlrobert.codegpt.settings.GeneralSettingsConfigurable; import ee.carlrobert.codegpt.telemetry.TelemetryAction; import ee.carlrobert.codegpt.toolwindow.chat.StreamParser; +import ee.carlrobert.codegpt.toolwindow.chat.ThinkingOutputParser; import ee.carlrobert.codegpt.toolwindow.chat.editor.ResponseEditorPanel; import ee.carlrobert.codegpt.toolwindow.chat.editor.actions.CopyAction; import ee.carlrobert.codegpt.toolwindow.ui.ResponseBodyProgressPanel; import ee.carlrobert.codegpt.toolwindow.ui.WebpageList; -import ee.carlrobert.codegpt.ui.OverlayUtil; +import ee.carlrobert.codegpt.ui.ThoughtProcessPanel; import ee.carlrobert.codegpt.ui.UIUtil; import ee.carlrobert.codegpt.util.EditorUtil; import ee.carlrobert.codegpt.util.MarkdownUtil; import java.awt.BorderLayout; -import java.awt.event.MouseEvent; import java.util.Objects; +import java.util.stream.Stream; import javax.swing.BoxLayout; import javax.swing.DefaultListModel; import javax.swing.JEditorPane; @@ -62,6 +63,7 @@ public class ChatMessageResponseBody extends JPanel { private final Project project; private final Disposable parentDisposable; private final StreamParser streamParser; + private final ThinkingOutputParser thinkingOutputParser; private final boolean readOnly; private final DefaultListModel webpageListModel = new DefaultListModel<>(); private final WebpageList webpageList = new WebpageList(webpageListModel); @@ -71,12 +73,11 @@ public class ChatMessageResponseBody extends JPanel { private JPanel webpageListPanel; public ChatMessageResponseBody(Project project, Disposable parentDisposable) { - this(project, false, false, false, false, parentDisposable); + this(project, false, false, false, parentDisposable); } public ChatMessageResponseBody( Project project, - boolean withGhostText, boolean readOnly, boolean webSearchIncluded, boolean withProgress, @@ -84,6 +85,7 @@ public ChatMessageResponseBody( this.project = project; this.parentDisposable = parentDisposable; this.streamParser = new StreamParser(); + this.thinkingOutputParser = new ThinkingOutputParser(); this.readOnly = readOnly; setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); setOpaque(false); @@ -96,12 +98,6 @@ public ChatMessageResponseBody( webpageListPanel = createWebpageListPanel(webpageList); add(webpageListPanel); } - - if (withGhostText) { - prepareProcessingText(!readOnly); - currentlyProcessedTextPane.setText( - "

"); - } } public ChatMessageResponseBody withResponse(@NotNull String response) { @@ -119,6 +115,29 @@ public ChatMessageResponseBody withResponse(@NotNull String response) { } public void updateMessage(String partialMessage) { + thinkingOutputParser.processChunk(partialMessage); + + var thoughtProcessPanel = (ThoughtProcessPanel) Stream.of(getComponents()) + .filter(it -> it instanceof ThoughtProcessPanel) + .findFirst() + .orElse(null); + + if (thinkingOutputParser.isThinking()) { + progressPanel.setVisible(false); + + if (thoughtProcessPanel == null) { + thoughtProcessPanel = new ThoughtProcessPanel(); + add(thoughtProcessPanel); + } else { + thoughtProcessPanel.updateText(thinkingOutputParser.getThoughtProcess()); + } + return; + } + + if (thoughtProcessPanel != null && !thoughtProcessPanel.getFinished()) { + thoughtProcessPanel.setFinished(); + } + for (var item : streamParser.parse(partialMessage)) { processResponse(item.response(), CODE.equals(item.type()), true); } @@ -240,6 +259,19 @@ private void processResponse(String markdownInput, boolean codeResponse, boolean } } + private void processThinkingOutput(String thoughtProcess) { + Stream.of(getComponents()) + .filter(it -> it instanceof ThoughtProcessPanel) + .findFirst() + .ifPresentOrElse(thoughtProcessPanel -> { + ((ThoughtProcessPanel) thoughtProcessPanel).updateText(thoughtProcess); + }, () -> { + add(new ThoughtProcessPanel()); + revalidate(); + repaint(); + }); + } + private void processCode(String markdownCode) { var document = Parser.builder().build().parse(markdownCode); var child = document.getChildOfType(FencedCodeBlock.class); diff --git a/src/main/kotlin/ee/carlrobert/codegpt/completions/factory/OpenAIRequestFactory.kt b/src/main/kotlin/ee/carlrobert/codegpt/completions/factory/OpenAIRequestFactory.kt index 72b177f77..0b0dce4e2 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/completions/factory/OpenAIRequestFactory.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/completions/factory/OpenAIRequestFactory.kt @@ -219,8 +219,16 @@ class OpenAIRequestFactory : CompletionRequestFactory { } else { messages.add(OpenAIChatCompletionStandardMessage("user", prevMessage.prompt)) } + + var response = prevMessage.response ?: "" + if (response.startsWith("")) { + response = response + .replace("(?s).*?".toRegex(), "") + .trim { it <= ' ' } + } + messages.add( - OpenAIChatCompletionStandardMessage("assistant", prevMessage.response) + OpenAIChatCompletionStandardMessage("assistant", response) ) } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTAvailableModels.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTAvailableModels.kt index 640a9335e..09716dccf 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTAvailableModels.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/service/codegpt/CodeGPTAvailableModels.kt @@ -17,9 +17,8 @@ object CodeGPTAvailableModels { CodeGPTModel("o1-mini", "o1-mini", Icons.OpenAI, INDIVIDUAL), CodeGPTModel("GPT-4o", "gpt-4o", Icons.OpenAI, INDIVIDUAL), CodeGPTModel("Claude 3.5 Sonnet", "claude-3.5-sonnet", Icons.Anthropic, INDIVIDUAL), + CodeGPTModel("DeepSeek R1", "deepseek-r1", Icons.DeepSeek, INDIVIDUAL), CodeGPTModel("Gemini 1.5 Pro", "gemini-pro-1.5", Icons.Google, INDIVIDUAL), - CodeGPTModel("Qwen 2.5 Coder (32B)", "qwen-2.5-32b-chat", Icons.Qwen, FREE), - CodeGPTModel("Llama 3.1 (405B)", "llama-3.1-405b", Icons.Meta, FREE), CodeGPTModel("DeepSeek Coder V2 - FREE", "deepseek-coder-v2", Icons.DeepSeek, ANONYMOUS), CodeGPTModel("GPT-4o mini - FREE", "gpt-4o-mini", Icons.OpenAI, ANONYMOUS), ) @@ -28,7 +27,8 @@ object CodeGPTAvailableModels { CodeGPTModel("o1-mini", "o1-mini", Icons.OpenAI, INDIVIDUAL), CodeGPTModel("GPT-4o", "gpt-4o", Icons.OpenAI, INDIVIDUAL), CodeGPTModel("Claude 3.5 Sonnet", "claude-3.5-sonnet", Icons.Anthropic, INDIVIDUAL), - CodeGPTModel("Gemini 1.5 Pro", "gemini-pro-1.5", Icons.Google, INDIVIDUAL), + CodeGPTModel("DeepSeek R1", "deepseek-r1", Icons.DeepSeek, INDIVIDUAL), + CodeGPTModel("DeepSeek V3", "deepseek-v3", Icons.DeepSeek, FREE), CodeGPTModel("Qwen 2.5 Coder (32B)", "qwen-2.5-32b-chat", Icons.Qwen, FREE), CodeGPTModel("Llama 3.1 (405B)", "llama-3.1-405b", Icons.Meta, FREE), CodeGPTModel("DeepSeek Coder V2", "deepseek-coder-v2", Icons.DeepSeek, ANONYMOUS), @@ -38,9 +38,9 @@ object CodeGPTAvailableModels { CodeGPTModel("o1-mini", "o1-mini", Icons.OpenAI, INDIVIDUAL), CodeGPTModel("GPT-4o", "gpt-4o", Icons.OpenAI, INDIVIDUAL), CodeGPTModel("Claude 3.5 Sonnet", "claude-3.5-sonnet", Icons.Anthropic, INDIVIDUAL), + CodeGPTModel("DeepSeek R1", "deepseek-r1", Icons.DeepSeek, INDIVIDUAL), + CodeGPTModel("DeepSeek V3", "deepseek-v3", Icons.DeepSeek, FREE), CodeGPTModel("Gemini 1.5 Pro", "gemini-pro-1.5", Icons.Google, INDIVIDUAL), - CodeGPTModel("Qwen 2.5 Coder (32B)", "qwen-2.5-32b-chat", Icons.Qwen, FREE), - CodeGPTModel("Llama 3.1 (405B)", "llama-3.1-405b", Icons.Meta, FREE), ) } } @@ -54,6 +54,8 @@ object CodeGPTAvailableModels { CodeGPTModel("Gemini 1.5 Pro", "gemini-pro-1.5", Icons.Google, INDIVIDUAL), CodeGPTModel("Qwen 2.5 Coder (32B)", "qwen-2.5-32b-chat", Icons.Qwen, FREE), CodeGPTModel("Llama 3.1 (405B)", "llama-3.1-405b", Icons.Meta, FREE), + CodeGPTModel("DeepSeek R1", "deepseek-r1", Icons.DeepSeek, INDIVIDUAL), + CodeGPTModel("DeepSeek V3", "deepseek-v3", Icons.DeepSeek, FREE), CodeGPTModel("DeepSeek Coder V2", "deepseek-coder-v2", Icons.DeepSeek, FREE), ) diff --git a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/ThinkingOutputParser.kt b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/ThinkingOutputParser.kt new file mode 100644 index 000000000..e79e28bb9 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/ThinkingOutputParser.kt @@ -0,0 +1,39 @@ +package ee.carlrobert.codegpt.toolwindow.chat + +import java.util.regex.Pattern + +class ThinkingOutputParser { + + private val buffer = StringBuilder() + + var isThinking: Boolean = false + private set + + var isFinished: Boolean = false + private set + + var thoughtProcess: String = "" + private set + + fun processChunk(chunk: String) { + if (isFinished) { + return + } + + buffer.append(chunk) + + val thinkPattern = Pattern.compile("(.*?)", Pattern.DOTALL) + val matcher = thinkPattern.matcher(buffer.toString()) + if (matcher.find()) { + isFinished = true + isThinking = false + thoughtProcess = matcher.group(1).trim { it <= ' ' } + } else if (buffer.isNotBlank() && "".contains(buffer)) { + thoughtProcess = "" + isThinking = true + } else if (buffer.toString().startsWith("")) { + thoughtProcess = buffer.toString().replaceFirst("".toRegex(), "") + isThinking = true + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/UserMessagePanel.kt b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/UserMessagePanel.kt index e096fab68..c36d8725c 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/UserMessagePanel.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/UserMessagePanel.kt @@ -118,7 +118,7 @@ class UserMessagePanel( private fun setupResponseBody() { addContent( - ChatMessageResponseBody(project, false, true, false, false, parentDisposable) + ChatMessageResponseBody(project, true, false, false, parentDisposable) .withResponse(message.prompt) ) } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/ThoughtProcessPanel.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/ThoughtProcessPanel.kt new file mode 100644 index 000000000..562f3172a --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/ThoughtProcessPanel.kt @@ -0,0 +1,78 @@ +package ee.carlrobert.codegpt.ui + +import com.intellij.icons.AllIcons +import com.intellij.ui.JBColor +import com.intellij.util.ui.JBUI +import com.intellij.util.ui.components.BorderLayoutPanel +import ee.carlrobert.codegpt.util.MarkdownUtil +import java.awt.BorderLayout +import java.awt.event.ItemEvent +import javax.swing.* + +class ThoughtProcessPanel : JPanel(BorderLayout()) { + + var finished: Boolean = false + private set + + private val responseBodyContent = UIUtil.createTextPane("", false) + private val contentPanel = createContentPanel() + private val toggleButton: JToggleButton = createToggleButton() + + init { + isOpaque = false + + add(toggleButton, BorderLayout.NORTH) + add(contentPanel, BorderLayout.CENTER) + } + + fun setFinished() { + toggleButton.text = "Thought Process" + toggleButton.isSelected = false + finished = true + + contentPanel.add(Box.createVerticalStrut(8)) + contentPanel.add( + BorderLayoutPanel().withBorder( + JBUI.Borders.compound( + JBUI.Borders.customLine(JBColor.border(), 1, 0, 0, 0), + JBUI.Borders.empty(8, 0) + ) + ) + ) + } + + fun updateText(text: String) { + responseBodyContent.text = MarkdownUtil.convertMdToHtml(text) + } + + private fun createContentPanel(): JPanel { + val panel = JPanel().apply { + isOpaque = false + isVisible = true + layout = BoxLayout(this, BoxLayout.Y_AXIS) + border = JBUI.Borders.empty(0, 0) + } + + panel.add(responseBodyContent) + panel.add(Box.createVerticalStrut(4)) + return panel + } + + private fun createToggleButton(): JToggleButton { + return JToggleButton("Thinking...", AllIcons.General.ArrowUp) + .apply { + isFocusPainted = false + isContentAreaFilled = false + background = background + selectedIcon = AllIcons.General.ArrowDown + border = null + isSelected = true + horizontalAlignment = SwingConstants.LEFT + horizontalTextPosition = SwingConstants.RIGHT + iconTextGap = 4 + addItemListener { e: ItemEvent -> + contentPanel.isVisible = e.stateChange == ItemEvent.SELECTED + } + } + } +} \ No newline at end of file