feat: support DeepSeek R1 and V3 models

This commit is contained in:
Carl-Robert Linnupuu 2025-01-27 14:12:27 +00:00
parent 89a3b669c5
commit 4e149c54de
13 changed files with 300 additions and 64 deletions

@ -1 +1 @@
Subproject commit 0541f06296753dbc59a57379eb54cec865a4c9f9
Subproject commit d6d24cd9ed6d0b9558643dcc28f2124bef488c52

View file

@ -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,18 @@ public final class 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 getProjectIndexStorePath(@NotNull Project project) {
return getIndexStorePath() + File.separator + project.getName();
return getIndexStorePath() + separator + project.getName();
}
}

View file

@ -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",

View file

@ -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 "

View file

@ -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 final class LlamaServerAgent implements Disposable {
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<String> 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 final class LlamaServerAgent implements Disposable {
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 final class LlamaServerAgent implements Disposable {
}
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 final class LlamaServerAgent implements Disposable {
@Override
public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) {
if (ProcessOutputType.isStderr(outputType)) {
errorLines.add(event.getText());
}
LOG.info(event.getText());
if (ProcessOutputType.isStdout(outputType)) {
LOG.info(event.getText());
// TODO: Use proper successful boot up validation
if (event.getText().contains("server is listening")) {
LOG.info("Server up and running!");
try {
var serverMessage = objectMapper.readValue(event.getText(), LlamaServerMessage.class);
// hack
if ("HTTP server listening".equals(serverMessage.msg())) {
LOG.info("Server up and running!");
LlamaSettings.getCurrentState().setServerPort(port);
onSuccess.run();
}
} catch (Exception ignore) {
// ignore
}
LlamaSettings.getCurrentState().setServerPort(port);
onSuccess.run();
}
}
};
@ -177,20 +200,32 @@ public final class LlamaServerAgent implements Disposable {
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.setExePath("./build/bin/llama-server");
commandLine.withWorkDirectory(CodeGPTPlugin.getLlamaSourcePath());
commandLine.addParameters(
"-m", params.modelPath(),
@ -210,8 +245,8 @@ public final class LlamaServerAgent implements Disposable {
@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();

View file

@ -4,6 +4,7 @@ import static java.util.Collections.emptyList;
import ee.carlrobert.codegpt.conversations.message.Message;
import java.util.List;
import java.util.stream.Collectors;
public enum PromptTemplate {
@ -237,6 +238,23 @@ public enum PromptTemplate {
.toString();
}
},
DEEPSEEK_R1("DeepSeek R1") {
@Override
public String buildPrompt(String systemPrompt, String userPrompt, List<Message> history) {
var historyString = history.stream()
.map(it -> {
String response = it.getResponse();
if (response.startsWith("<think>")) {
response = response.replaceAll("(?s)<think>.*?</think>", "").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<Message> history) {

View file

@ -225,7 +225,6 @@ public class ChatToolWindowTabPanel implements Disposable {
panel.addCopyAction(() -> CopyAction.copyToClipboard(message.getResponse()));
panel.addContent(new ChatMessageResponseBody(
project,
true,
false,
message.isWebSearchIncluded(),
fileContextIncluded || message.getDocumentationDetails() != null,

View file

@ -37,17 +37,18 @@ import ee.carlrobert.codegpt.events.WebSearchEventDetails;
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<WebSearchEventDetails> 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 class ChatMessageResponseBody extends JPanel {
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 class ChatMessageResponseBody extends JPanel {
webpageListPanel = createWebpageListPanel(webpageList);
add(webpageListPanel);
}
if (withGhostText) {
prepareProcessingText(!readOnly);
currentlyProcessedTextPane.setText(
"<html><p style=\"margin-top: 4px; margin-bottom: 8px;\">&#8205;</p></html>");
}
}
public ChatMessageResponseBody withResponse(@NotNull String response) {
@ -119,6 +115,29 @@ public class ChatMessageResponseBody extends JPanel {
}
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 @@ public class ChatMessageResponseBody extends JPanel {
}
}
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);

View file

@ -219,8 +219,16 @@ class OpenAIRequestFactory : CompletionRequestFactory {
} else {
messages.add(OpenAIChatCompletionStandardMessage("user", prevMessage.prompt))
}
var response = prevMessage.response ?: ""
if (response.startsWith("<think>")) {
response = response
.replace("(?s)<think>.*?</think>".toRegex(), "")
.trim { it <= ' ' }
}
messages.add(
OpenAIChatCompletionStandardMessage("assistant", prevMessage.response)
OpenAIChatCompletionStandardMessage("assistant", response)
)
}

View file

@ -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),
)

View file

@ -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("<think>(.*?)</think>", 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() && "<think>".contains(buffer)) {
thoughtProcess = ""
isThinking = true
} else if (buffer.toString().startsWith("<think>")) {
thoughtProcess = buffer.toString().replaceFirst("<think>".toRegex(), "")
isThinking = true
}
}
}

View file

@ -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)
)
}

View file

@ -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
}
}
}
}