diff --git a/src/main/java/com/devoxx/genie/model/Constant.java b/src/main/java/com/devoxx/genie/model/Constant.java index bcfab66e..07149990 100644 --- a/src/main/java/com/devoxx/genie/model/Constant.java +++ b/src/main/java/com/devoxx/genie/model/Constant.java @@ -24,6 +24,7 @@ private Constant() { public static final String FIND_PROMPT = "Perform semantic search on the project files using RAG and show matching files. (NOTE: The /find command requires RAG to be enabled in settings)"; public static final String HELP_PROMPT = "Display help and available commands for the Genie Devoxx Plugin"; + public static final String COMMAND_PREFIX = "/"; public static final String TEST_COMMAND = "test"; public static final String FIND_COMMAND = "find"; public static final String REVIEW_COMMAND = "review"; diff --git a/src/main/java/com/devoxx/genie/model/request/ChatMessageContext.java b/src/main/java/com/devoxx/genie/model/request/ChatMessageContext.java index 1c9a7e25..8c669a0c 100644 --- a/src/main/java/com/devoxx/genie/model/request/ChatMessageContext.java +++ b/src/main/java/com/devoxx/genie/model/request/ChatMessageContext.java @@ -7,14 +7,15 @@ import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.chat.StreamingChatLanguageModel; import dev.langchain4j.model.output.TokenUsage; -import lombok.Builder; -import lombok.Data; -import lombok.Getter; -import lombok.Setter; +import lombok.*; import java.time.LocalDateTime; import java.util.List; +/** + * Represents the context of a chat message with all the necessary information. + */ +@ToString @Data @Builder public class ChatMessageContext { @@ -22,11 +23,11 @@ public class ChatMessageContext { private String id; private Project project; private Integer timeout; - private String userPrompt; - private UserMessage userMessage; - private AiMessage aiMessage; - private String context; - private EditorInfo editorInfo; + private String userPrompt; // The user prompt + private UserMessage userMessage; // The user message + private AiMessage aiMessage; // The LLM response message + private String context; // The context of the prompt + private EditorInfo editorInfo; // The editor info private LanguageModel languageModel; private ChatLanguageModel chatLanguageModel; private StreamingChatLanguageModel streamingChatLanguageModel; diff --git a/src/main/java/com/devoxx/genie/service/ChatMemoryService.java b/src/main/java/com/devoxx/genie/service/ChatMemoryService.java index 93516af3..5d11ec28 100644 --- a/src/main/java/com/devoxx/genie/service/ChatMemoryService.java +++ b/src/main/java/com/devoxx/genie/service/ChatMemoryService.java @@ -9,10 +9,7 @@ import com.devoxx.genie.util.ChatMessageContextUtil; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; -import dev.langchain4j.data.message.AiMessage; -import dev.langchain4j.data.message.ChatMessage; -import dev.langchain4j.data.message.SystemMessage; -import dev.langchain4j.data.message.UserMessage; +import dev.langchain4j.data.message.*; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.store.memory.chat.InMemoryChatMemoryStore; import org.jetbrains.annotations.NotNull; @@ -36,6 +33,8 @@ public void init(@NotNull Project project) { createChangeListener(); } + // TODO - This method is currently not used anywhere in the codebase + // TODO - Should be triggered when user changes the chat memory size in the settings private void createChangeListener() { ApplicationManager.getApplication().getMessageBus() .connect() @@ -102,9 +101,9 @@ public void restoreConversation(@NotNull Project project, @NotNull Conversation clear(project); for (com.devoxx.genie.model.conversation.ChatMessage message : conversation.getMessages()) { if (message.isUser()) { - add(project, new UserMessage(message.getContent())); + add(project, UserMessage.from(new TextContent(message.getContent()))); } else { - add(project, new AiMessage(message.getContent())); + add(project, AiMessage.from(message.getContent())); } } } diff --git a/src/main/java/com/devoxx/genie/service/ChatPromptExecutor.java b/src/main/java/com/devoxx/genie/service/ChatPromptExecutor.java index dea33aa6..b69f0e36 100644 --- a/src/main/java/com/devoxx/genie/service/ChatPromptExecutor.java +++ b/src/main/java/com/devoxx/genie/service/ChatPromptExecutor.java @@ -27,8 +27,7 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentHashMap; -import static com.devoxx.genie.model.Constant.FIND_COMMAND; -import static com.devoxx.genie.model.Constant.HELP_COMMAND; +import static com.devoxx.genie.model.Constant.*; public class ChatPromptExecutor { @@ -182,16 +181,20 @@ public void stopPromptExecution(Project project) { * @param promptOutputPanel the prompt output panel * @return the command */ - private Optional<String> getCommandFromPrompt(@NotNull ChatMessageContext chatMessageContext, + public Optional<String> getCommandFromPrompt(@NotNull ChatMessageContext chatMessageContext, PromptOutputPanel promptOutputPanel) { String prompt = chatMessageContext.getUserPrompt().trim(); + + // Early exit if not a command + if (!prompt.startsWith(COMMAND_PREFIX)) { + return Optional.of(prompt); + } + DevoxxGenieSettingsService settings = DevoxxGenieStateService.getInstance(); List<CustomPrompt> customPrompts = settings.getCustomPrompts(); Optional<CustomPrompt> matchingPrompt = customPrompts.stream() - .filter(customPrompt -> - prompt.equalsIgnoreCase("/" + customPrompt.getName()) - ) + .filter(customPrompt -> prompt.equalsIgnoreCase(COMMAND_PREFIX + customPrompt.getName())) .findFirst(); // if OK diff --git a/src/main/java/com/devoxx/genie/service/MessageCreationService.java b/src/main/java/com/devoxx/genie/service/MessageCreationService.java index f7a30439..570e9c94 100644 --- a/src/main/java/com/devoxx/genie/service/MessageCreationService.java +++ b/src/main/java/com/devoxx/genie/service/MessageCreationService.java @@ -9,10 +9,12 @@ import com.devoxx.genie.ui.util.NotificationUtil; import com.devoxx.genie.util.ChatMessageContextUtil; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; +import dev.langchain4j.data.message.TextContent; import dev.langchain4j.data.message.UserMessage; import org.jetbrains.annotations.NotNull; @@ -24,7 +26,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.logging.Logger; import java.util.stream.Collectors; import static com.devoxx.genie.action.AddSnippetAction.SELECTED_TEXT_KEY; @@ -34,9 +35,8 @@ * Here's where also the basic prompt "engineering" is happening, including calling the AST magic. */ public class MessageCreationService { - private static final Logger LOG = Logger.getLogger(MessageCreationService.class.getName()); - public static final String CONTEXT_PROMPT = "Context: \n"; + private static final Logger LOG = Logger.getInstance(MessageCreationService.class.getName()); private static final String GIT_DIFF_INSTRUCTIONS = """ Please analyze the code and provide ONLY the modified code in your response. @@ -61,23 +61,18 @@ public static MessageCreationService getInstance() { /** * Create user message. * @param chatMessageContext the chat message context - * @return the user message */ - @NotNull - public UserMessage createUserMessage(@NotNull ChatMessageContext chatMessageContext) { - UserMessage userMessage; + public void addUserMessageToContext(@NotNull ChatMessageContext chatMessageContext) { String context = chatMessageContext.getContext(); - if (context != null && !context.isEmpty()) { - userMessage = constructUserMessageWithFullContext(chatMessageContext, context); + constructUserMessageWithFullContext(chatMessageContext, context); } else { - userMessage = constructUserMessageWithCombinedContext(chatMessageContext); + constructUserMessageWithCombinedContext(chatMessageContext); } - - return userMessage; } - private @NotNull UserMessage constructUserMessageWithCombinedContext(@NotNull ChatMessageContext chatMessageContext) { + private void constructUserMessageWithCombinedContext(@NotNull ChatMessageContext chatMessageContext) { + LOG.debug("Constructing user message with combined context"); StringBuilder stringBuilder = new StringBuilder(); @@ -107,14 +102,10 @@ public UserMessage createUserMessage(@NotNull ChatMessageContext chatMessageCont // Add editor content or selected text String editorContent = getEditorContentOrSelectedText(chatMessageContext); if (!editorContent.isEmpty()) { - stringBuilder.append("<EditorContext>\n"); stringBuilder.append(editorContent); - stringBuilder.append("\n</EditorContext>\n\n"); } - UserMessage userMessage = new UserMessage(stringBuilder.toString()); - chatMessageContext.setUserMessage(userMessage); - return userMessage; + chatMessageContext.setUserMessage(UserMessage.from(new TextContent(stringBuilder.toString()))); } /** @@ -123,6 +114,8 @@ public UserMessage createUserMessage(@NotNull ChatMessageContext chatMessageCont * @return the user message */ private @NotNull String addSemanticSearchResults(@NotNull ChatMessageContext chatMessageContext) { + LOG.debug("Adding semantic search results to user message"); + StringBuilder contextBuilder = new StringBuilder(); try { @@ -157,7 +150,7 @@ public UserMessage createUserMessage(@NotNull ChatMessageContext chatMessageCont ); } } catch (Exception e) { - LOG.warning("Failed to get semantic search results: " + e.getMessage()); + LOG.warn("Failed to get semantic search results: " + e.getMessage()); } return contextBuilder.toString(); @@ -227,10 +220,10 @@ public static List<SemanticFile> extractFileReferences(@NotNull Map<String, Sear * * @param chatMessageContext the chat message context * @param context the context - * @return the user message */ - private @NotNull UserMessage constructUserMessageWithFullContext(@NotNull ChatMessageContext chatMessageContext, - String context) { + private void constructUserMessageWithFullContext(@NotNull ChatMessageContext chatMessageContext, + String context) { + LOG.debug("Constructing user message with full context"); StringBuilder stringBuilder = new StringBuilder(); // If git diff is enabled, add special instructions at the beginning @@ -251,9 +244,7 @@ public static List<SemanticFile> extractFileReferences(@NotNull Map<String, Sear stringBuilder.append(chatMessageContext.getUserPrompt()); stringBuilder.append("</UserPrompt>"); - UserMessage userMessage = new UserMessage("user_message", stringBuilder.toString()); - chatMessageContext.setUserMessage(userMessage); - return userMessage; + chatMessageContext.setUserMessage(UserMessage.from(new TextContent(stringBuilder.toString()))); } /** diff --git a/src/main/java/com/devoxx/genie/service/PromptExecutionService.java b/src/main/java/com/devoxx/genie/service/PromptExecutionService.java index 82b93231..af43136f 100644 --- a/src/main/java/com/devoxx/genie/service/PromptExecutionService.java +++ b/src/main/java/com/devoxx/genie/service/PromptExecutionService.java @@ -10,14 +10,13 @@ import com.devoxx.genie.util.ChatMessageContextUtil; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; -import dev.langchain4j.data.message.AiMessage; -import dev.langchain4j.data.message.SystemMessage; -import dev.langchain4j.data.message.UserMessage; +import dev.langchain4j.data.message.*; import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.output.Response; import lombok.Getter; import org.jetbrains.annotations.NotNull; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -47,30 +46,28 @@ static PromptExecutionService getInstance() { * @return the response */ public @NotNull CompletableFuture<Response<AiMessage>> executeQuery(@NotNull ChatMessageContext chatMessageContext) { - LOG.info("Execute query : " + chatMessageContext); + LOG.debug("Execute query : " + chatMessageContext); queryLock.lock(); try { if (isCanceled()) return CompletableFuture.completedFuture(null); - MessageCreationService messageCreationService = MessageCreationService.getInstance(); + ChatMemoryService chatMemoryService = ChatMemoryService.getInstance(); + // Add System Message if ChatMemoryService is empty if (ChatMemoryService.getInstance().isEmpty(chatMessageContext.getProject())) { - LOG.info("ChatMemoryService is empty, adding a new SystemMessage"); + LOG.debug("ChatMemoryService is empty, adding a new SystemMessage"); if (!ChatMessageContextUtil.isOpenAIo1Model(chatMessageContext.getLanguageModel())) { - ChatMemoryService - .getInstance() - .add(chatMessageContext.getProject(), - new SystemMessage(DevoxxGenieStateService.getInstance().getSystemPrompt() + Constant.MARKDOWN) - ); + String systemPrompt = DevoxxGenieStateService.getInstance().getSystemPrompt() + Constant.MARKDOWN; + chatMemoryService.add(chatMessageContext.getProject(), SystemMessage.from(systemPrompt)); } } - UserMessage userMessage = messageCreationService.createUserMessage(chatMessageContext); - LOG.info("Created UserMessage: " + userMessage); + // Add User message to context + MessageCreationService.getInstance().addUserMessageToContext(chatMessageContext); - ChatMemoryService.getInstance().add(chatMessageContext.getProject(), userMessage); + // chatMemoryService.add(chatMessageContext.getProject(), chatMessageContext.getUserMessage()); long startTime = System.currentTimeMillis(); @@ -117,12 +114,17 @@ private boolean isCanceled() { private @NotNull Response<AiMessage> processChatMessage(ChatMessageContext chatMessageContext) { try { ChatLanguageModel chatLanguageModel = chatMessageContext.getChatLanguageModel(); - Response<AiMessage> response = - chatLanguageModel - .generate(ChatMemoryService.getInstance().messages(chatMessageContext.getProject())); - ChatMemoryService.getInstance().add(chatMessageContext.getProject(), response.content()); + + ChatMemoryService chatMemoryService = ChatMemoryService.getInstance(); + List<ChatMessage> messages = chatMemoryService.messages(chatMessageContext.getProject()); + + Response<AiMessage> response = chatLanguageModel.generate(messages); + + chatMemoryService.add(chatMessageContext.getProject(), response.content()); + return response; } catch (Exception e) { + LOG.error("Error occurred while processing chat message", e); if (chatMessageContext.getLanguageModel().getProvider().equals(ModelProvider.Jan)) { throw new ModelNotActiveException("Selected Jan model is not active. Download and make it active or add API Key in Jan settings."); } diff --git a/src/main/java/com/devoxx/genie/service/chromadb/ChromaDockerService.java b/src/main/java/com/devoxx/genie/service/chromadb/ChromaDockerService.java index 131e94ad..f6532066 100644 --- a/src/main/java/com/devoxx/genie/service/chromadb/ChromaDockerService.java +++ b/src/main/java/com/devoxx/genie/service/chromadb/ChromaDockerService.java @@ -45,7 +45,7 @@ public void startChromaDB(Project project, ChromaDBStatusCallback callback) { try { // We assume Docker is already installed and running if (isChromaDBRunning()) { - LOG.info("ChromaDB is already running"); + LOG.debug("ChromaDB is already running"); callback.onSuccess(); return; } @@ -182,7 +182,7 @@ public void deleteCollectionData(@NotNull Project project, @NotNull String colle Path collectionPath = volumePath.resolve(collectionName); if (!Files.exists(collectionPath)) { - LOG.info("Collection directory does not exist: " + collectionPath); + LOG.debug("Collection directory does not exist: " + collectionPath); return; } @@ -197,7 +197,7 @@ private void deleteCollectionData(@NotNull Path collectionPath, @NotNull String .forEach(path -> { try { Files.delete(path); - LOG.info("Deleted: " + path); + LOG.debug("Deleted: " + path); } catch (IOException e) { LOG.warn("Failed to delete: " + path, e); } diff --git a/src/main/java/com/devoxx/genie/service/projectscanner/ProjectScannerService.java b/src/main/java/com/devoxx/genie/service/projectscanner/ProjectScannerService.java index 84a3fa20..5ac36b27 100644 --- a/src/main/java/com/devoxx/genie/service/projectscanner/ProjectScannerService.java +++ b/src/main/java/com/devoxx/genie/service/projectscanner/ProjectScannerService.java @@ -187,7 +187,7 @@ private StringBuilder getContentFromModules(Project project, readFileContent(file, fullContent, scanContentResult); } else { scanContentResult.incrementSkippedFileCount(); - LOG.info("Skipping file: " + file.getPath() + " (excluded by settings or .gitignore)"); + LOG.debug("Skipping file: " + file.getPath() + " (excluded by settings or .gitignore)"); } return fullContent; } @@ -210,7 +210,7 @@ public boolean visitFile(@NotNull VirtualFile file) { readFileContent(file, fullContent, scanContentResult); } else { scanContentResult.incrementSkippedFileCount(); - LOG.info("Skipping file: " + file.getPath() + " (not in content, excluded by settings, or .gitignore)"); + LOG.debug("Skipping file: " + file.getPath() + " (not in content, excluded by settings, or .gitignore)"); } } return true; diff --git a/src/main/java/com/devoxx/genie/service/rag/ProjectIndexerService.java b/src/main/java/com/devoxx/genie/service/rag/ProjectIndexerService.java index 4b9d477f..9aff204f 100644 --- a/src/main/java/com/devoxx/genie/service/rag/ProjectIndexerService.java +++ b/src/main/java/com/devoxx/genie/service/rag/ProjectIndexerService.java @@ -123,7 +123,7 @@ public void indexFiles(Project project, VirtualFile baseDir = LocalFileSystem.getInstance().findFileByPath(basePath); if (baseDir == null) { - LOG.info("Could not find base directory: " + basePath); + LOG.debug("Could not find base directory: " + basePath); return; } @@ -151,17 +151,17 @@ public void indexFiles(Project project, * @param filePath Path to the file to index */ private void indexSingleFile(Path filePath) { - LOG.info("Indexing file: " + filePath); + LOG.debug("Indexing file: " + filePath); try { if (isFileIndexed(filePath)) { - LOG.info("File already indexed: " + filePath); + LOG.debug("File already indexed: " + filePath); return; } processPath(filePath); - LOG.info("File successfully indexed: " + filePath); + LOG.debug("File successfully indexed: " + filePath); } catch (Exception e) { - LOG.error("Error indexing file: " + filePath + " - " + e.getMessage()); + LOG.warn("Error indexing file: " + filePath + " - " + e.getMessage()); } } @@ -208,7 +208,7 @@ private void markFileAsIndexed(Path filePath, TextSegment segment) { private void processPath(Path path) { try { - LOG.info("Processing file: " + path); + LOG.debug("Processing file: " + path); String content = Files.readString(path); if (content.isBlank()) { diff --git a/src/main/java/com/devoxx/genie/service/streaming/StreamingPromptExecutor.java b/src/main/java/com/devoxx/genie/service/streaming/StreamingPromptExecutor.java index 00f71100..fad70ed2 100644 --- a/src/main/java/com/devoxx/genie/service/streaming/StreamingPromptExecutor.java +++ b/src/main/java/com/devoxx/genie/service/streaming/StreamingPromptExecutor.java @@ -7,7 +7,6 @@ import com.devoxx.genie.ui.settings.DevoxxGenieStateService; import com.devoxx.genie.ui.util.NotificationUtil; import dev.langchain4j.data.message.SystemMessage; -import dev.langchain4j.data.message.UserMessage; import dev.langchain4j.model.chat.StreamingChatLanguageModel; import org.jetbrains.annotations.NotNull; @@ -60,8 +59,8 @@ private void prepareMemory(@NotNull ChatMessageContext chatMessageContext) { ); } - UserMessage userMessage = messageCreationService.createUserMessage(chatMessageContext); - chatMemoryService.add(chatMessageContext.getProject(), userMessage); + messageCreationService.addUserMessageToContext(chatMessageContext); + chatMemoryService.add(chatMessageContext.getProject(), chatMessageContext.getUserMessage()); } /** diff --git a/src/main/java/com/devoxx/genie/service/websearch/WebSearchService.java b/src/main/java/com/devoxx/genie/service/websearch/WebSearchService.java index 26c5cc07..a49ed914 100644 --- a/src/main/java/com/devoxx/genie/service/websearch/WebSearchService.java +++ b/src/main/java/com/devoxx/genie/service/websearch/WebSearchService.java @@ -92,7 +92,7 @@ interface SearchWebsite { .csi(stateService.getGoogleCSIKey()) .build(); } - LOG.info("Web search engine not found or all disabled"); + LOG.debug("Web search engine not found or all disabled"); return null; } } diff --git a/src/main/java/com/devoxx/genie/ui/EditorFileButtonManager.java b/src/main/java/com/devoxx/genie/ui/EditorFileButtonManager.java index b1855205..9b3583f2 100644 --- a/src/main/java/com/devoxx/genie/ui/EditorFileButtonManager.java +++ b/src/main/java/com/devoxx/genie/ui/EditorFileButtonManager.java @@ -1,6 +1,5 @@ package com.devoxx.genie.ui; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.FileEditorManagerListener; @@ -14,17 +13,19 @@ public class EditorFileButtonManager { + private final Project project; private final FileEditorManager fileEditorManager; private final JButton addFileBtn; public EditorFileButtonManager(Project project, JButton addFileBtn) { + this.project = project; this.fileEditorManager = FileEditorManager.getInstance(project); this.addFileBtn = addFileBtn; handleFileOpenClose(); } private void handleFileOpenClose() { - ApplicationManager.getApplication().getMessageBus().connect().subscribe( + project.getMessageBus().connect().subscribe( FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() { @Override public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) { diff --git a/src/main/java/com/devoxx/genie/ui/component/input/CommandAutoCompleteTextField.java b/src/main/java/com/devoxx/genie/ui/component/input/CommandAutoCompleteTextField.java index d7556616..f864cf74 100644 --- a/src/main/java/com/devoxx/genie/ui/component/input/CommandAutoCompleteTextField.java +++ b/src/main/java/com/devoxx/genie/ui/component/input/CommandAutoCompleteTextField.java @@ -32,14 +32,13 @@ public class CommandAutoCompleteTextField extends JBTextArea implements CustomPr private boolean isAutoCompleting = false; private String placeholder = ""; - public CommandAutoCompleteTextField(Project project) { + public CommandAutoCompleteTextField(@NotNull Project project) { super(); this.project = project; setDocument(new CommandDocument()); addKeyListener(new CommandKeyListener()); - ApplicationManager.getApplication() - .getMessageBus() + project.getMessageBus() .connect() .subscribe(AppTopics.CUSTOM_PROMPT_CHANGED_TOPIC, this); @@ -48,15 +47,15 @@ public CommandAutoCompleteTextField(Project project) { private void initializeCommands() { commands.clear(); - commands.add("/" + TEST_COMMAND); - commands.add("/" + EXPLAIN_COMMAND); - commands.add("/" + REVIEW_COMMAND); - commands.add("/" + TDG_COMMAND); - commands.add("/" + HELP_COMMAND); + commands.add(COMMAND_PREFIX + TEST_COMMAND); + commands.add(COMMAND_PREFIX + EXPLAIN_COMMAND); + commands.add(COMMAND_PREFIX + REVIEW_COMMAND); + commands.add(COMMAND_PREFIX + TDG_COMMAND); + commands.add(COMMAND_PREFIX + HELP_COMMAND); DevoxxGenieSettingsService stateService = DevoxxGenieStateService.getInstance(); for (CustomPrompt customPrompt : stateService.getCustomPrompts()) { - commands.add("/" + customPrompt.getName()); + commands.add(COMMAND_PREFIX + customPrompt.getName()); } } @@ -84,7 +83,7 @@ private void sendPrompt() { private void autoComplete() { String text = getText(); String[] lines = text.split("\n"); - if (lines.length > 0 && lines[lines.length - 1].startsWith("/")) { + if (lines.length > 0 && lines[lines.length - 1].startsWith(COMMAND_PREFIX)) { String currentLine = lines[lines.length - 1]; for (String command : commands) { if (command.startsWith(currentLine)) { @@ -142,7 +141,7 @@ private String getCurrentLine(@NotNull String text) { } private boolean shouldAttemptAutoComplete(@NotNull String currentLine) { - return currentLine.startsWith("/") && currentLine.length() > 1; + return currentLine.startsWith(COMMAND_PREFIX) && currentLine.length() > 1; } private void attemptAutoComplete(String currentLine) throws BadLocationException { diff --git a/src/main/java/com/devoxx/genie/ui/component/input/PromptInputArea.java b/src/main/java/com/devoxx/genie/ui/component/input/PromptInputArea.java index 82911a80..c331a3d5 100644 --- a/src/main/java/com/devoxx/genie/ui/component/input/PromptInputArea.java +++ b/src/main/java/com/devoxx/genie/ui/component/input/PromptInputArea.java @@ -19,7 +19,7 @@ public class PromptInputArea extends JPanel { private final transient Project project; private final transient ResourceBundle resourceBundle; - public PromptInputArea(@NotNull ResourceBundle resourceBundle, Project project) { + public PromptInputArea(Project project, @NotNull ResourceBundle resourceBundle) { super(new BorderLayout()); this.resourceBundle = resourceBundle; diff --git a/src/main/java/com/devoxx/genie/ui/listener/ChatMemorySizeListener.java b/src/main/java/com/devoxx/genie/ui/listener/ChatMemorySizeListener.java index aab36225..0658e021 100644 --- a/src/main/java/com/devoxx/genie/ui/listener/ChatMemorySizeListener.java +++ b/src/main/java/com/devoxx/genie/ui/listener/ChatMemorySizeListener.java @@ -1,5 +1,8 @@ package com.devoxx.genie.ui.listener; public interface ChatMemorySizeListener { + + // TODO - This method is currently not used anywhere in the codebase + // TODO - Should be triggered when user changes the chat memory size in the settings void onChatMemorySizeChanged(int chatMemorySize); } diff --git a/src/main/java/com/devoxx/genie/ui/panel/SubmitPanel.java b/src/main/java/com/devoxx/genie/ui/panel/SubmitPanel.java index 4aab17af..06f3ed7a 100644 --- a/src/main/java/com/devoxx/genie/ui/panel/SubmitPanel.java +++ b/src/main/java/com/devoxx/genie/ui/panel/SubmitPanel.java @@ -38,7 +38,7 @@ public SubmitPanel(@NotNull DevoxxGenieToolWindowContent toolWindowContent) { this.project = toolWindowContent.getProject(); ResourceBundle resourceBundle = toolWindowContent.getResourceBundle(); - promptInputArea = new PromptInputArea(resourceBundle, project); + promptInputArea = new PromptInputArea(project, resourceBundle); actionButtonsPanel = createActionButtonsPanel(); add(createSubmitPanel(actionButtonsPanel), BorderLayout.CENTER); diff --git a/src/main/java/com/devoxx/genie/ui/panel/WelcomePanel.java b/src/main/java/com/devoxx/genie/ui/panel/WelcomePanel.java index 4ded5fbe..9eb44a2e 100644 --- a/src/main/java/com/devoxx/genie/ui/panel/WelcomePanel.java +++ b/src/main/java/com/devoxx/genie/ui/panel/WelcomePanel.java @@ -48,7 +48,7 @@ public WelcomePanel(ResourceBundle resourceBundle) { try { Desktop.getDesktop().browse(new URI(e.getURL().toString())); } catch (Exception ex) { - LOG.info("Error opening browser link: " + e.getURL().toString(), ex); + LOG.warn("Error opening browser link: " + e.getURL().toString(), ex); } } } diff --git a/src/main/java/com/devoxx/genie/ui/topic/AppTopics.java b/src/main/java/com/devoxx/genie/ui/topic/AppTopics.java index 3a257c1d..9be38ee6 100644 --- a/src/main/java/com/devoxx/genie/ui/topic/AppTopics.java +++ b/src/main/java/com/devoxx/genie/ui/topic/AppTopics.java @@ -8,6 +8,8 @@ public class AppTopics { public static final Topic<SettingsChangeListener> SETTINGS_CHANGED_TOPIC = Topic.create("SettingsChanged", SettingsChangeListener.class); + // TODO - This method is currently not used anywhere in the codebase + // TODO - Should be triggered when user changes the chat memory size in the settings public static final Topic<ChatMemorySizeListener> CHAT_MEMORY_SIZE_TOPIC = new Topic<>("chatMemorySizeChanged", ChatMemorySizeListener.class); diff --git a/src/main/java/com/devoxx/genie/util/ChatMessageContextUtil.java b/src/main/java/com/devoxx/genie/util/ChatMessageContextUtil.java index bb808524..ed3f4e17 100644 --- a/src/main/java/com/devoxx/genie/util/ChatMessageContextUtil.java +++ b/src/main/java/com/devoxx/genie/util/ChatMessageContextUtil.java @@ -46,7 +46,6 @@ private ChatMessageContextUtil() { .project(project) .id(String.valueOf(System.currentTimeMillis())) .userPrompt(userPromptText) - .userMessage(UserMessage.userMessage(userPromptText)) .languageModel(languageModel) .webSearchRequested(stateService.getWebSearchActivated() && (stateService.isGoogleSearchEnabled() || stateService.isTavilySearchEnabled())) .totalFileCount(FileListManager.getInstance().size()) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d2315be0..51bb1235 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1,2 @@ -#Mon Dec 16 13:59:22 CET 2024 +#Tue Dec 17 13:03:41 CET 2024 version=0.4.6 diff --git a/src/test/java/com/devoxx/genie/service/ChatPromptExecutorIT.java b/src/test/java/com/devoxx/genie/service/ChatPromptExecutorIT.java new file mode 100644 index 00000000..192e2b75 --- /dev/null +++ b/src/test/java/com/devoxx/genie/service/ChatPromptExecutorIT.java @@ -0,0 +1,115 @@ +package com.devoxx.genie.service; + +import com.devoxx.genie.chatmodel.AbstractLightPlatformTestCase; +import com.devoxx.genie.model.request.ChatMessageContext; +import com.devoxx.genie.ui.component.input.PromptInputArea; +import com.devoxx.genie.ui.panel.PromptOutputPanel; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.extensions.ExtensionsArea; +import com.intellij.util.messages.MessageBusConnection; +import dev.langchain4j.data.message.UserMessage; + +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.util.messages.MessageBus; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.Optional; +import java.util.ResourceBundle; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ChatPromptExecutorIT extends AbstractLightPlatformTestCase { + + ResourceBundle resourceBundle = ResourceBundle.getBundle("messages"); + PromptInputArea promptInputArea; + PromptOutputPanel promptOutputPanel; + + ChatPromptExecutor chatPromptExecutor; + + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + Application application = mock(Application.class); + MessageBus messageBus = mock(MessageBus.class); + when(application.getMessageBus()).thenReturn(messageBus); + + // Mockito.doNothing().when(messageBus).syncPublisher(Mockito.any()); // If needed + MessageBusConnection messageBusConnection = mock(MessageBusConnection.class); + when(messageBus.connect()).thenReturn(messageBusConnection); + + ExtensionsArea extensionsArea = mock(ExtensionsArea.class); + when(application.getExtensionArea()).thenReturn(extensionsArea); + when(extensionsArea.hasExtensionPoint(Mockito.any(ExtensionPointName.class))).thenReturn(true); + + // Use reflection or a dedicated method to set the application mock + setApplication(application); + + promptInputArea = new PromptInputArea(getProject(), resourceBundle); + promptOutputPanel = new PromptOutputPanel(getProject(), resourceBundle); + chatPromptExecutor = new ChatPromptExecutor(promptInputArea); + } + + @Test + public void testGetCommandFromPrompt_validInput() { + + var chatMessageContext = ChatMessageContext + .builder() + .userMessage(UserMessage.from("/review")) + .build(); + + Optional<String> command = chatPromptExecutor.getCommandFromPrompt(chatMessageContext, promptOutputPanel); + + assertEquals("add", command.get()); + } + + private void setApplication(Application application) throws Exception { + java.lang.reflect.Field applicationField = ApplicationManager.class.getDeclaredField("ourApplication"); + applicationField.setAccessible(true); + applicationField.set(null, application); + } + +// +// @Test +// public void testGetCommandFromPrompt_emptyInput() { +// // Simulate empty user input (just pressing Enter) +// String input = "\n"; +// InputStream in = new ByteArrayInputStream(input.getBytes()); +// System.setIn(in); +// +// YourClass yourClassInstance = new YourClass(); +// String command = yourClassInstance.getCommandFromPrompt(); +// +// assertEquals("", command); // Or assertNull(command) if that's the expected behavior for empty input +// } +// +// @Test +// public void testGetCommandFromPrompt_multipleSpaces() { +// // Simulate user input with multiple spaces +// String input = " delete \n"; +// InputStream in = new ByteArrayInputStream(input.getBytes()); +// System.setIn(in); +// +// YourClass yourClassInstance = new YourClass(); +// String command = yourClassInstance.getCommandFromPrompt(); +// +// assertEquals("delete", command); // Assuming the method trims whitespace +// } +// +// @Test +// public void testGetCommandFromPrompt_mixedCase() { +// // Simulate user input with mixed case +// String input = "UpDaTe\n"; +// InputStream in = new ByteArrayInputStream(input.getBytes()); +// System.setIn(in); +// +// YourClass yourClassInstance = new YourClass(); +// String command = yourClassInstance.getCommandFromPrompt(); +// +// assertEquals("update", command); // Assuming the method converts to lowercase +// } +}