Skip to content

Commit

Permalink
Improve AI assistance for generating commit messages
Browse files Browse the repository at this point in the history
Implemented a new method in JeddictChatModel for generating commit message suggestions based on the provided git diff and initial commit message. Updated LearnFix and AskAIProjectAction classes to utilize this new functionality for improved AI-driven commit message assistance. Also added a user-friendly dialog in UIUtil for entering initial commit messages.
  • Loading branch information
jShiwaniGupta committed Oct 20, 2024
1 parent eba4136 commit 9f73575
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 6 deletions.
68 changes: 68 additions & 0 deletions src/main/java/io/github/jeddict/ai/JeddictChatModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,74 @@ public String enhanceExpressionStatement(String classContent, String parentConte
return enhanced;
}

public String generateCommitMessageSuggestions(String gitDiffOutput, String initialCommitMessage) {
StringBuilder prompt = new StringBuilder();
prompt.append("You are an API server that generates commit message suggestions based on the provided 'git diff' and 'git status' output in HTML format. ")
.append("Please provide various types of commit messages based on the changes: \n")
.append("- Very Short\n")
.append("- Short\n")
.append("- Medium\n")
.append("- Long\n")
.append("- Descriptive\n\n")
.append("Here is the 'git diff' and 'git status' output:\n")
.append(gitDiffOutput.replace("\n", "<br>")) // Use <br> for HTML rendering
.append("\n");

// Add initial commit message to the prompt if it is not empty or null
if (initialCommitMessage != null && !initialCommitMessage.isEmpty()) {
prompt.append("Initial Commit Message:\n").append(initialCommitMessage).append("<br>");
}

prompt.append("Please respond with the commit messages strictly in HTML format only. "
+ "Do not use any Markdown or code formatting (like backticks or triple backticks). "
+ "Ensure the HTML content is well-structured, styled using Bootstrap CSS, "
+ "and split any long lines (over 100 characters) with <br> tags for multiline display. "
+ "Your response should look like this: <br>"
+ "<h3>Very Short:</h3> Commit message here<br>"
+ "<h3>Short:</h3> Commit message here<br>"
+ "<h3>Medium:</h3> Commit message here<br>"
+ "<h3>Long:</h3> Commit message here<br>"
+ "<h3>Descriptive:</h3> Commit message here<br>"
+ "Do not include any other text or formatting outside of HTML.");

// Generate the commit message suggestions
String answer = generate(prompt.toString());

// Wrap long lines with <br> tags
answer = wrapLongLinesWithBr(removeCodeBlockMarkers(answer), 100);

return answer;
}

private String wrapLongLinesWithBr(String input, int maxLineLength) {
StringBuilder wrapped = new StringBuilder();
String[] lines = input.split("<br>"); // Split by existing line breaks

for (String line : lines) {
String[] words = line.split(" "); // Split line into words
StringBuilder currentLine = new StringBuilder();

for (String word : words) {
// Check if adding the next word exceeds the maximum line length
if (currentLine.length() + word.length() + 1 > maxLineLength) {
// If current line is not empty, append it to the result
if (currentLine.length() > 0) {
wrapped.append(currentLine.toString().trim()).append("<br>");
currentLine.setLength(0); // Reset current line
}
}
currentLine.append(word).append(" "); // Append the word with a space
}

// Append any remaining words in the current line
if (currentLine.length() > 0) {
wrapped.append(currentLine.toString().trim()).append("<br>");
}
}

return wrapped.toString();
}

public String generateHtmlDescriptionForProject(String projectContent, String query) {
String prompt = "You are an API server that provides answer to query of following project in HTML. "
+ "Do not include additional text or explanations outside of the HTML content.\n\n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
displayName = "#CTL_AskAIProjectAction", lazy = false, asynchronous = true)
@ActionReferences({
@ActionReference(path = "Projects/Actions", position = 100),})
@Messages({"CTL_AskAIProjectAction=Ask AI"})
@Messages({"CTL_AskAIProjectAction=AI Query Project"})
public final class AskAIProjectAction extends AbstractAction implements ContextAwareAction {

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package io.github.jeddict.ai.actions;

import io.github.jeddict.ai.hints.LearnFix;
import io.github.jeddict.ai.settings.PreferencesManager;
import io.github.jeddict.ai.util.UIUtil;
import java.awt.event.ActionEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.swing.AbstractAction;
import javax.swing.Action;
import org.netbeans.api.project.Project;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionRegistration;
import org.openide.awt.DynamicMenuContent;
import org.openide.filesystems.FileUtil;
import org.openide.util.ContextAwareAction;
import org.openide.util.Lookup;
import org.openide.util.NbBundle.Messages;

/**
*
* @author Shiwani Gupta
*/
@ActionID(
category = "Project",
id = "io.github.jeddict.ai.actions.GenerateCommitMessageAction")
@ActionRegistration(
displayName = "#CTL_GenerateCommitMessageAction", lazy = false, asynchronous = true)
@ActionReferences({
@ActionReference(path = "Projects/Actions", position = 100),})
@Messages({"CTL_GenerateCommitMessageAction=AI Commit Message"})
public final class GenerateCommitMessageAction extends AbstractAction implements ContextAwareAction {

@Override
public void actionPerformed(ActionEvent ev) {
// This can remain empty as the context action will handle the logic
}

@Override
public Action createContextAwareInstance(Lookup actionContext) {
if (actionContext != null) {
Project project = actionContext.lookup(Project.class);
boolean isGitProject = project != null && isGitRepository(project);
return new GenerateCommitMessageAction.ContextAction(
isGitProject && PreferencesManager.getInstance().isAiAssistantActivated(),
project
);
}
return new GenerateCommitMessageAction.ContextAction(false, null);
}

private boolean isGitRepository(Project project) {
File projectDir = FileUtil.toFile(project.getProjectDirectory()); // Get the project directory as a File
if (projectDir != null) {
File gitDir = new File(projectDir, ".git"); // Check for .git directory
return gitDir.exists() && gitDir.isDirectory(); // Ensure it exists and is a directory
}
return false;
}

private static final class ContextAction extends AbstractAction {

private final Project project;

private ContextAction(boolean enable, Project project) {
super(Bundle.CTL_GenerateCommitMessageAction());
this.putValue(DynamicMenuContent.HIDE_WHEN_DISABLED, true);
this.setEnabled(enable);
this.project = project;
}

@Override
public void actionPerformed(ActionEvent evt) {
StringBuilder sb = new StringBuilder();
sb.append("git diff \n\n");
String diffOutput = runGitCommand("diff");
sb.append(diffOutput);
sb.append("\n\n git status \n\n");
String statusOutput = runGitCommand("status");
sb.append(statusOutput);
String intitalCommitMessage = UIUtil.askForInitialCommitMessage();
LearnFix learnFix = new LearnFix(io.github.jeddict.ai.completion.Action.QUERY);
learnFix.askQueryForProjectCommit(project, sb.toString(), intitalCommitMessage);
}

private String runGitCommand(String command) {
StringBuilder output = new StringBuilder();
try {
ProcessBuilder processBuilder = new ProcessBuilder("git", command);
File projectDir = FileUtil.toFile(project.getProjectDirectory()); // Convert FileObject to File
processBuilder.directory(projectDir); // Set the working directory to the project root
Process process = processBuilder.start();

try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
}
process.waitFor();
} catch (IOException | InterruptedException e) {
e.printStackTrace(); // Handle exceptions appropriately in your production code
}
return output.toString();
}

}
}
21 changes: 16 additions & 5 deletions src/main/java/io/github/jeddict/ai/hints/LearnFix.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public class LearnFix extends JavaFix {
private int currentResponseIndex = -1;
private String javaCode = null;
private String projectContent;
private String commitChanges;
private PreferencesManager pm = PreferencesManager.getInstance();

public LearnFix(TreePathHandle tpHandle, Action action, TreePath treePath) {
Expand Down Expand Up @@ -148,7 +149,7 @@ protected void performRewrite(JavaFix.TransformationContext tc) throws Exception
name = ((ClassTree) leaf).getSimpleName().toString();

}
displayHtmlContent(removeCodeBlockMarkers(response), name);
displayHtmlContent(removeCodeBlockMarkers(response), name+ " AI Assistance");
}
}

Expand Down Expand Up @@ -176,7 +177,15 @@ public void askQueryForProject(Project project, String userQuery) {

projectContent = inputForAI.toString();
String response = new JeddictChatModel().generateHtmlDescriptionForProject(projectContent, userQuery);
displayHtmlContent(removeCodeBlockMarkers(response), projectName);
displayHtmlContent(removeCodeBlockMarkers(response), projectName+ " Projetc GenAI");
}

public void askQueryForProjectCommit(Project project, String commitChanges, String intitalCommitMessage) {
ProjectInformation info = ProjectUtils.getInformation(project);
String projectName = info.getDisplayName();
String response = new JeddictChatModel().generateCommitMessageSuggestions(commitChanges, intitalCommitMessage);
displayHtmlContent(removeCodeBlockMarkers(response), projectName+ " GenAI Commit");
this.commitChanges = commitChanges;
}

public void askQueryForPackage(Collection<? extends FileObject> selectedPackages, String userQuery) {
Expand Down Expand Up @@ -208,7 +217,7 @@ public void askQueryForPackage(Collection<? extends FileObject> selectedPackages
}
projectContent = inputForAI.toString();
String response = new JeddictChatModel().generateHtmlDescriptionForProject(projectContent, userQuery);
displayHtmlContent(removeCodeBlockMarkers(response), projectName);
displayHtmlContent(removeCodeBlockMarkers(response), projectName+ " Package GenAI");
}
}

Expand All @@ -223,7 +232,7 @@ private void displayHtmlContent(final String response, String title) {

Preferences prefs = Preferences.userNodeForPackage(AssistantTopComponent.class);
prefs.putBoolean(AssistantTopComponent.PREFERENCE_KEY, true);
topComponent = new AssistantTopComponent(title + " AI Assistance");
topComponent = new AssistantTopComponent(title);
updateEditor(response).addHyperlinkListener((HyperlinkEvent e) -> {
if (HyperlinkEvent.EventType.ACTIVATED.equals(e.getEventType())) {
try {
Expand Down Expand Up @@ -395,7 +404,9 @@ private void handleQuestion(String question, JButton submitButton) {
}
}
String response;
if (treePath == null && projectContent != null) {
if(commitChanges != null) {
response = new JeddictChatModel().generateCommitMessageSuggestions(commitChanges, question);
} else if (treePath == null || projectContent != null) {
response = new JeddictChatModel().generateHtmlDescription(projectContent, null, null, prevChat, question);
} else {
response = new JeddictChatModel().generateHtmlDescription(null, treePath.getCompilationUnit().toString(), treePath.getLeaf() instanceof MethodTree ? treePath.getLeaf().toString() : null, prevChat, question);
Expand Down
38 changes: 38 additions & 0 deletions src/main/java/io/github/jeddict/ai/util/UIUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,42 @@ public static String askQuery() {
}
return query;
}

public static String askForInitialCommitMessage() {
// Create a JTextArea for multiline input
JTextArea textArea = new JTextArea(10, 30); // 10 rows, 30 columns
textArea.setWrapStyleWord(true);
textArea.setLineWrap(true);

// Add the text area to a JScrollPane
JScrollPane scrollPane = new JScrollPane(textArea);

// Create a JPanel to hold the scroll pane
JPanel panel = new JPanel();
panel.add(scrollPane);

// Show the custom dialog
int option = JOptionPane.showConfirmDialog(
null,
panel,
"Please enter the initial commit message (optional).",
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.PLAIN_MESSAGE,
AssistantTopComponent.icon
);

// Check the user's choice
if (option != JOptionPane.OK_OPTION) {
return null; // Exit if the user cancels the input
}

String initialMessage = textArea.getText().trim();

// If the input is empty, return null to indicate no initial message
if (initialMessage.isEmpty()) {
return null; // No input provided
}

return initialMessage; // Return the provided initial commit message
}
}

0 comments on commit 9f73575

Please sign in to comment.