diff --git a/pom.xml b/pom.xml index 1104fcf..7721579 100644 --- a/pom.xml +++ b/pom.xml @@ -406,6 +406,11 @@ org-netbeans-modules-projectapi RELEASE230 + + org.netbeans.api + org-openide-dialogs + RELEASE230 + UTF-8 diff --git a/src/main/java/io/github/jeddict/ai/JeddictChatModel.java b/src/main/java/io/github/jeddict/ai/JeddictChatModel.java index 7527913..063a95d 100644 --- a/src/main/java/io/github/jeddict/ai/JeddictChatModel.java +++ b/src/main/java/io/github/jeddict/ai/JeddictChatModel.java @@ -62,11 +62,13 @@ public class JeddictChatModel { public JeddictChatModel() { if (null != preferencesManager.getModel()) { switch (preferencesManager.getProvider()) { - case GOOGLE -> model = GoogleAiGeminiChatModel.builder() + case GOOGLE -> + model = GoogleAiGeminiChatModel.builder() .apiKey(preferencesManager.getApiKey()) .modelName(preferencesManager.getModelName()) .build(); - case OPEN_AI -> model = OpenAiChatModel.builder() + case OPEN_AI -> + model = OpenAiChatModel.builder() .apiKey(preferencesManager.getApiKey()) .modelName(preferencesManager.getModelName()) .build(); @@ -76,23 +78,28 @@ public JeddictChatModel() { .apiKey(preferencesManager.getApiKey()) .modelName(preferencesManager.getModelName()) .build(); - case MISTRAL -> model = MistralAiChatModel.builder() - .apiKey(preferencesManager.getApiKey()) - .modelName(preferencesManager.getModelName()) - .build(); - case ANTHROPIC -> model = AnthropicChatModel.builder() - .apiKey(preferencesManager.getApiKey()) - .modelName(preferencesManager.getModelName()) - .build(); - case OLLAMA -> model = OllamaChatModel.builder() + case MISTRAL -> + model = MistralAiChatModel.builder() + .apiKey(preferencesManager.getApiKey()) + .modelName(preferencesManager.getModelName()) + .build(); + case ANTHROPIC -> + model = AnthropicChatModel.builder() + .apiKey(preferencesManager.getApiKey()) + .modelName(preferencesManager.getModelName()) + .build(); + case OLLAMA -> + model = OllamaChatModel.builder() .baseUrl(preferencesManager.getProviderLocation()) .modelName(preferencesManager.getModelName()) .build(); - case LM_STUDIO -> model = LMStudioChatModel.builder() + case LM_STUDIO -> + model = LMStudioChatModel.builder() .baseUrl(preferencesManager.getProviderLocation()) .modelName(preferencesManager.getModelName()) .build(); - case GPT4ALL -> model = LocalAiChatModel.builder() + case GPT4ALL -> + model = LocalAiChatModel.builder() .baseUrl(preferencesManager.getProviderLocation()) .modelName(preferencesManager.getModelName()) .build(); @@ -101,11 +108,11 @@ public JeddictChatModel() { } private String generate(String prompt) { - if(model == null) { - JOptionPane.showMessageDialog(null, - "AI assistance model not intitalized.", - "Error in AI Assistance", - JOptionPane.ERROR_MESSAGE); + if (model == null) { + JOptionPane.showMessageDialog(null, + "AI assistance model not intitalized.", + "Error in AI Assistance", + JOptionPane.ERROR_MESSAGE); } try { return model.generate(prompt); @@ -460,8 +467,8 @@ public List suggestNextLineCode(String classDatas, String classContent, + (preferencesManager.isDescriptionEnabled() ? jsonRequestWithDescription : jsonRequest) + "Java Class Content:\n" + classContent; } else if (path.getLeaf().getKind() == Tree.Kind.MODIFIERS - && path.getParentPath() != null - && path.getParentPath().getLeaf().getKind() == Tree.Kind.METHOD) { + && path.getParentPath() != null + && path.getParentPath().getLeaf().getKind() == Tree.Kind.METHOD) { prompt = "You are an API server that suggests Java code modifications for a method. " + "At the placeholder location ${SUGGEST_CODE_LIST}, suggest method-level modifiers such as 'public', 'protected', 'private', 'abstract', 'static', 'final', 'synchronized', or relevant method-level annotations. " + "Additionally, you may suggest method-specific annotations like '@Override', '@Deprecated', '@Transactional', etc. " @@ -485,8 +492,8 @@ public List suggestNextLineCode(String classDatas, String classContent, + (preferencesManager.isDescriptionEnabled() ? jsonRequestWithDescription : jsonRequest) + "Java Class Content:\n" + classContent; } else if (path.getLeaf().getKind() == Tree.Kind.PARENTHESIZED - && path.getParentPath() != null - && path.getParentPath().getLeaf().getKind() == Tree.Kind.IF) { + && path.getParentPath() != null + && path.getParentPath().getLeaf().getKind() == Tree.Kind.IF) { prompt = "You are an API server that suggests Java code to enhance an if-statement. " + "At the placeholder location ${SUGGEST_IF_CONDITIONS}, suggest additional conditional checks or actions within the if-statement. " + "Ensure that the suggestions are contextually appropriate for the condition. " @@ -509,7 +516,7 @@ public List suggestNextLineCode(String classDatas, String classContent, List nextLines = parseJsonToSnippets(jsonResponse); return nextLines; } - + public List suggestJavaComment(String classDatas, String classContent, String lineText) { String prompt = "You are an API server that suggests appropriate Java comments for a specific context in a given Java class at the placeholder location ${SUGGEST_JAVA_COMMENT}. " + "Based on the provided Java class content and the line of comment: \"" + lineText + " ${SUGGEST_JAVA_COMMENT} \", suggest relevant Java comment as appropriate for the context represented by the placeholder ${SUGGEST_JAVA_COMMENT} in the Java Class. " @@ -543,8 +550,7 @@ public List suggestJavadocOrComment(String classDatas, String classConte + "'imports' should be an array of required Java import statements (if no imports are required, return an empty array). " + "'snippet' should contain the suggested code as a text block, which may include multiple lines formatted as a single string using \\n for line breaks. " + "Make sure to escape any double quotes within the snippet using a backslash (\\) so that the JSON remains valid. \n\n"; - - + String jsonRequestWithDescription = "Return a JSON array with a few best suggestions without any additional text or explanation. Each element should be an object containing three fields: 'imports', 'snippet', and 'description'. " + "'imports' should be an array of required Java import statements (if no imports are required, return an empty array). " + "'snippet' should contain the suggested code as a text block, which may include multiple lines formatted as a single string using \\n for line breaks. " @@ -567,7 +573,7 @@ public List suggestAnnotations(String classDatas, String classContent, } public List parseJsonToSnippets(String jsonResponse) { - if(jsonResponse == null) { + if (jsonResponse == null) { return Collections.EMPTY_LIST; } List snippets = new ArrayList<>(); @@ -597,7 +603,7 @@ public List parseJsonToSnippets(String jsonResponse) { // Extract the "snippet" field String snippet = jsonObject.getString("snippet"); - if(jsonObject.has("description")){ + if (jsonObject.has("description")) { String descripion = jsonObject.getString("description"); Snippet snippetObj = new Snippet(snippet, descripion, importsList); snippets.add(snippetObj); @@ -638,7 +644,7 @@ private List parseJsonToList(String json) { private List parseJsonToListWithSplit(String json) { List variableNames = new ArrayList<>(); - if(json == null || json.isEmpty()){ + if (json == null || json.isEmpty()) { return variableNames; } @@ -715,9 +721,26 @@ public String enhanceExpressionStatement(String classContent, String parentConte return enhanced; } + 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" + + "Do not include text in block.\n\n" + + "If Full Java Class is in response then wrap it in
. " + + "If partial snippet of Java Class are in response then wrap it in
. " + + "Projects Content:\n" + projectContent + "\n\n" + + "Query:\n" + query; + + // Generate the HTML description + String answer = generate(prompt); + System.out.println(answer); + return answer; + } + public String generateHtmlDescriptionForClass(String classContent) { String prompt = "You are an API server that provides description of following class in HTML. " + "Do not include additional text or explanations outside of the HTML content.\n\n" + + "If Full Java Class is in response then wrap it in
. " + + "If partial snippet of Java Class are in response then wrap it in
. " + "Java Class Content:\n" + classContent; // Generate the HTML description @@ -725,10 +748,12 @@ public String generateHtmlDescriptionForClass(String classContent) { System.out.println(answer); return answer; } - + public String generateHtmlDescriptionForMethod(String methodContent) { String prompt = "You are an API server that provides description of following Method in HTML. " + "Do not include additional text or explanations outside of the HTML content.\n\n" + + "If Full Java Class is in response then wrap it in
. " + + "If partial snippet of Java Class are in response then wrap it in
. " + "Java Method Content:\n" + methodContent; // Generate the HTML description @@ -737,12 +762,19 @@ public String generateHtmlDescriptionForMethod(String methodContent) { return answer; } - public String generateHtmlDescription(String classContent, String methodContent, String previousChatResponse, String userQuery) { + public String generateHtmlDescription( + String projectContent, String classContent, String methodContent, + String previousChatResponse, String userQuery) { String prompt; String promptExtend; if (methodContent != null) { promptExtend = "Method Content:\n" + methodContent + "\n\n" + "Do not return complete Java Class, return only Method and wrap it in . \n"; + } else if (projectContent != null) { + promptExtend = "Project Full Content:\n" + classContent + "\n\n" + + "If Full Java Class is in response then wrap it in . " + + "If partial snippet of Java Class are in response then wrap it in . "; + } else { promptExtend = "Orignal Java Class Content:\n" + classContent + "\n\n" + "If Full Java Class is in response then wrap it in . " diff --git a/src/main/java/io/github/jeddict/ai/actions/AskAIPackageAction.java b/src/main/java/io/github/jeddict/ai/actions/AskAIPackageAction.java new file mode 100644 index 0000000..afe4444 --- /dev/null +++ b/src/main/java/io/github/jeddict/ai/actions/AskAIPackageAction.java @@ -0,0 +1,84 @@ +/* + * 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 static io.github.jeddict.ai.util.UIUtil.askQuery; +import java.awt.event.ActionEvent; +import java.util.Collection; +import javax.swing.AbstractAction; +import javax.swing.Action; +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.FileObject; +import org.openide.util.ContextAwareAction; +import org.openide.util.Lookup; +import org.openide.util.NbBundle.Messages; + +@ActionID( + category = "Project", + id = "io.github.jeddict.ai.actions.AskAIPackageAction") +@ActionRegistration( + displayName = "#CTL_AskAIPackageAction", lazy = false, asynchronous = true) +@ActionReference(path = "Projects/package/Actions", position = 100) +@Messages({"CTL_AskAIPackageAction=Ask AI"}) +public final class AskAIPackageAction extends AbstractAction implements ContextAwareAction { + + @Override + public void actionPerformed(ActionEvent ev) { + } + + @Override + public Action createContextAwareInstance(Lookup actionContext) { + if (actionContext != null) { + return new AskAIPackageAction.ContextAction( + PreferencesManager.getInstance().isAiAssistantActivated(), + actionContext.lookupAll(FileObject.class) + ); + } + return new AskAIPackageAction.ContextAction(false, null); + } + + private static final class ContextAction extends AbstractAction { + + private final Collection selectedPackages; + + private ContextAction(boolean enable, Collection selectedPackages) { + super(Bundle.CTL_AskAIPackageAction()); + this.putValue(DynamicMenuContent.HIDE_WHEN_DISABLED, true); + this.setEnabled(enable); + this.selectedPackages = selectedPackages; + } + + @Override + public void actionPerformed(ActionEvent evt) { + String query = askQuery(); + if (query == null) { + return; + } + LearnFix learnFix = new LearnFix(io.github.jeddict.ai.completion.Action.QUERY); + learnFix.askQueryForPackage(selectedPackages, query); + } + + } +} diff --git a/src/main/java/io/github/jeddict/ai/actions/AskAIProjectAction.java b/src/main/java/io/github/jeddict/ai/actions/AskAIProjectAction.java new file mode 100644 index 0000000..a50bede --- /dev/null +++ b/src/main/java/io/github/jeddict/ai/actions/AskAIProjectAction.java @@ -0,0 +1,84 @@ +/* + * 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 static io.github.jeddict.ai.util.UIUtil.askQuery; +import java.awt.event.ActionEvent; +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.util.ContextAwareAction; +import org.openide.util.Lookup; +import org.openide.util.NbBundle.Messages; + +@ActionID( + category = "Project", + id = "io.github.jeddict.ai.actions.AskAIProjectAction") +@ActionRegistration( + displayName = "#CTL_AskAIProjectAction", lazy = false, asynchronous = true) +@ActionReferences({ + @ActionReference(path = "Projects/Actions", position = 100),}) +@Messages({"CTL_AskAIProjectAction=Ask AI"}) +public final class AskAIProjectAction extends AbstractAction implements ContextAwareAction { + + @Override + public void actionPerformed(ActionEvent ev) { + } + + @Override + public Action createContextAwareInstance(Lookup actionContext) { + if (actionContext != null) { + return new AskAIProjectAction.ContextAction( + PreferencesManager.getInstance().isAiAssistantActivated(), + actionContext.lookup(Project.class) + ); + } + return new AskAIProjectAction.ContextAction(false, null); + } + + private static final class ContextAction extends AbstractAction { + + private final Project project; + + private ContextAction(boolean enable, Project project) { + super(Bundle.CTL_AskAIProjectAction()); + this.putValue(DynamicMenuContent.HIDE_WHEN_DISABLED, true); + this.setEnabled(enable); + this.project = project; + } + + @Override + public void actionPerformed(ActionEvent evt) { + String query = askQuery(); + if (query == null) { + return; + } + LearnFix learnFix = new LearnFix(io.github.jeddict.ai.completion.Action.QUERY); + learnFix.askQueryForProject(project, query); + } + + } +} diff --git a/src/main/java/io/github/jeddict/ai/completion/JeddictItem.java b/src/main/java/io/github/jeddict/ai/completion/JeddictItem.java index 1906666..4dcf678 100644 --- a/src/main/java/io/github/jeddict/ai/completion/JeddictItem.java +++ b/src/main/java/io/github/jeddict/ai/completion/JeddictItem.java @@ -4,6 +4,7 @@ */ package io.github.jeddict.ai.completion; +import io.github.jeddict.ai.components.AssistantTopComponent; import io.github.jeddict.ai.util.Utilities; import static io.github.jeddict.ai.util.Utilities.getHTMLColor; import io.github.jeddict.ai.util.SourceUtil; @@ -23,7 +24,6 @@ import org.netbeans.spi.editor.completion.CompletionDocumentation; import org.netbeans.spi.editor.completion.CompletionResultSet; import org.netbeans.spi.editor.completion.CompletionTask; -import org.openide.util.ImageUtilities; import org.openide.xml.XMLUtil; /** @@ -151,10 +151,7 @@ protected String getRightHtmlText() { @Override protected ImageIcon getIcon() { - if (icon == null) { - icon = ImageUtilities.loadImageIcon(LOCAL_VARIABLE, false); - } - return null; + return AssistantTopComponent.icon; } @Override diff --git a/src/main/java/io/github/jeddict/ai/components/AssistantTopComponent.java b/src/main/java/io/github/jeddict/ai/components/AssistantTopComponent.java index b8564dd..82b1b4b 100644 --- a/src/main/java/io/github/jeddict/ai/components/AssistantTopComponent.java +++ b/src/main/java/io/github/jeddict/ai/components/AssistantTopComponent.java @@ -20,16 +20,12 @@ import java.awt.BorderLayout; import java.awt.Desktop; -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; import java.util.prefs.Preferences; import javax.swing.BoxLayout; +import javax.swing.ImageIcon; import javax.swing.JEditorPane; import javax.swing.JPanel; import javax.swing.event.HyperlinkEvent; -import javax.swing.event.HyperlinkListener; import javax.swing.text.EditorKit; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.StyleSheet; @@ -43,6 +39,7 @@ */ public class AssistantTopComponent extends TopComponent { + public static final ImageIcon icon = new ImageIcon(AssistantTopComponent.class.getResource("/icons/logo16.png")); public static final String PREFERENCE_KEY = "AssistantTopComponentOpen"; private final JPanel parentPanel; private HTMLEditorKit editorKit; @@ -50,10 +47,10 @@ public class AssistantTopComponent extends TopComponent { public AssistantTopComponent(String name) { setName(name); setLayout(new BorderLayout()); + setIcon(icon.getImage()); parentPanel = new JPanel(); parentPanel.setLayout(new BoxLayout(parentPanel, BoxLayout.Y_AXIS)); - add(parentPanel, BorderLayout.CENTER); } diff --git a/src/main/java/io/github/jeddict/ai/hints/LearnFix.java b/src/main/java/io/github/jeddict/ai/hints/LearnFix.java index 449c30b..8568698 100644 --- a/src/main/java/io/github/jeddict/ai/hints/LearnFix.java +++ b/src/main/java/io/github/jeddict/ai/hints/LearnFix.java @@ -29,9 +29,9 @@ import io.github.jeddict.ai.completion.Action; import io.github.jeddict.ai.JeddictChatModel; import io.github.jeddict.ai.components.AssistantTopComponent; +import static io.github.jeddict.ai.util.ProjectUtils.getSourceFiles; import io.github.jeddict.ai.util.StringUtil; import static io.github.jeddict.ai.util.StringUtil.removeCodeBlockMarkers; -import static io.github.jeddict.ai.util.UIUtil.askQueryAboutClass; import java.awt.BorderLayout; import java.awt.Desktop; import java.awt.Dimension; @@ -47,8 +47,12 @@ import java.io.IOException; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; import java.util.List; import java.util.prefs.Preferences; +import java.util.stream.Collectors; import javax.swing.JButton; import javax.swing.JEditorPane; import javax.swing.JPanel; @@ -59,8 +63,15 @@ import org.netbeans.api.java.source.JavaSource; import org.netbeans.api.java.source.TreePathHandle; import org.netbeans.api.java.source.WorkingCopy; +import org.netbeans.api.project.FileOwnerQuery; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectInformation; +import org.netbeans.api.project.ProjectUtils; import org.netbeans.spi.java.hints.JavaFix; +import org.openide.filesystems.FileObject; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; +import static io.github.jeddict.ai.util.UIUtil.askQuery; /** * @@ -68,8 +79,20 @@ */ public class LearnFix extends JavaFix { - private final TreePath treePath; + private final List acceptedExtensions = Arrays.asList( + "java", "php", "jsf", "kt", "groovy", "scala", "xml", "json", "yaml", "yml", + "properties", "txt", "md", "js", "ts", "css", "scss", "html", "xhtml", "sh", + "bat", "sql", "jsp", "rb", "cs", "go", "swift", "rs", "c", "cpp", "h", "py" + ); + + private TreePath treePath; private final Action action; + private JButton prevButton, nextButton, saveButton; + private AssistantTopComponent topComponent; + private final List responseHistory = new ArrayList<>(); + private int currentResponseIndex = -1; + private String javaCode = null; + private String projectContent; public LearnFix(TreePathHandle tpHandle, Action action, TreePath treePath) { super(tpHandle); @@ -77,6 +100,11 @@ public LearnFix(TreePathHandle tpHandle, Action action, TreePath treePath) { this.action = action; } + public LearnFix(Action action) { + super(null); + this.action = action; + } + @Override protected String getText() { if (action == Action.LEARN) { @@ -101,13 +129,13 @@ protected void performRewrite(JavaFix.TransformationContext tc) throws Exception || leaf.getKind() == INTERFACE || leaf.getKind() == ENUM || leaf.getKind() == METHOD) { - String response = null; + String response; if (action == Action.QUERY) { - String query = askQueryAboutClass(); + String query = askQuery(); if (query == null) { return; } - response = new JeddictChatModel().generateHtmlDescription(treePath.getCompilationUnit().toString(), null, null, query); + response = new JeddictChatModel().generateHtmlDescription(null, treePath.getCompilationUnit().toString(), null, null, query); } else { if (leaf instanceof MethodTree) { response = new JeddictChatModel().generateHtmlDescriptionForMethod(leaf.toString()); @@ -123,17 +151,63 @@ protected void performRewrite(JavaFix.TransformationContext tc) throws Exception name = ((ClassTree) leaf).getSimpleName().toString(); } - displayHtmlContent(copy, tc.getPath(), removeCodeBlockMarkers(response), name); + displayHtmlContent(removeCodeBlockMarkers(response), name); } } - private JButton prevButton, nextButton, saveButton; - private AssistantTopComponent topComponent; - private final List responseHistory = new ArrayList<>(); - private int currentResponseIndex = -1; - String javaCode = null; + public void askQueryForProject(Project project, String userQuery) { - private void displayHtmlContent(WorkingCopy copy, TreePath tp, final String response, String title) { + ProjectInformation info = ProjectUtils.getInformation(project); + String projectName = info.getDisplayName(); + List sourceFiles = getSourceFiles(project); + + StringBuilder inputForAI = new StringBuilder(); + for (FileObject file : sourceFiles) { + try { + if (acceptedExtensions.contains(file.getExt())) { + inputForAI.append(file.asText()); + inputForAI.append("\n"); + } + } catch (Exception ex) { + Exceptions.printStackTrace(ex); + } + } + + projectContent = inputForAI.toString(); + String response = new JeddictChatModel().generateHtmlDescriptionForProject(projectContent, userQuery); + displayHtmlContent(removeCodeBlockMarkers(response), projectName); + } + + public void askQueryForPackage(Collection selectedPackages, String userQuery) { + Iterator selectedPackagesIterator = selectedPackages.iterator(); + if (selectedPackagesIterator.hasNext()) { + Project project = FileOwnerQuery.getOwner(selectedPackagesIterator.next()); + ProjectInformation info = ProjectUtils.getInformation(project); + String projectName = info.getDisplayName(); + + List sourceFiles = selectedPackages.stream() + .filter(FileObject::isFolder) + .flatMap(packageFolder -> Arrays.stream(packageFolder.getChildren()) + .filter(FileObject::isData) + .filter(file -> acceptedExtensions.contains(file.getExt()))) + .collect(Collectors.toList()); + + StringBuilder inputForAI = new StringBuilder(); + for (FileObject file : sourceFiles) { + try { + inputForAI.append(file.asText()); + inputForAI.append("\n"); + } catch (Exception ex) { + Exceptions.printStackTrace(ex); + } + } + projectContent = inputForAI.toString(); + String response = new JeddictChatModel().generateHtmlDescriptionForProject(projectContent, userQuery); + displayHtmlContent(removeCodeBlockMarkers(response), projectName); + } + } + + private void displayHtmlContent(final String response, String title) { SwingUtilities.invokeLater(() -> { try { File tempFile = File.createTempFile("tempHtml", ".html"); @@ -158,118 +232,10 @@ private void displayHtmlContent(WorkingCopy copy, TreePath tp, final String resp JScrollPane scrollPane = new JScrollPane(topComponent.getParentPanel()); topComponent.add(scrollPane, BorderLayout.CENTER); - // Create a panel for the text field and buttons - JPanel bottomPanel = new JPanel(new GridBagLayout()); - GridBagConstraints gbc = new GridBagConstraints(); - gbc.insets = new Insets(2, 2, 2, 2); - - // Previous Button - prevButton = new JButton("\u2190"); // Left arrow (←) - gbc.gridx = 0; - gbc.gridy = 0; - gbc.anchor = GridBagConstraints.WEST; - bottomPanel.add(prevButton, gbc); - - // Next Button - nextButton = new JButton("\u2192"); - gbc.gridx = 1; - gbc.gridy = 0; - gbc.anchor = GridBagConstraints.WEST; - bottomPanel.add(nextButton, gbc); - - // Text Field - JTextField questionField = new JTextField(); - questionField.setPreferredSize(new Dimension(300, questionField.getPreferredSize().height)); // Set preferred size - gbc.gridx = 2; - gbc.gridy = 0; - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.weightx = 1.0; // Allow text field to expand - bottomPanel.add(questionField, gbc); - - // Ask Button - JButton submitButton = new JButton("Ask"); - gbc.gridx = 3; - gbc.gridy = 0; - gbc.fill = GridBagConstraints.NONE; - gbc.weightx = 0; // No expansion - bottomPanel.add(submitButton, gbc); - - // Save Button - saveButton = new JButton("Copy"); - gbc.gridx = 4; - gbc.gridy = 0; - gbc.fill = GridBagConstraints.NONE; - gbc.weightx = 0; // No expansion - bottomPanel.add(saveButton, gbc); - - saveButton.addActionListener(e -> { - try { - StringSelection selection = new StringSelection(javaCode); - Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); - clipboard.setContents(selection, null); - } catch (Exception ex) { - ex.printStackTrace(); - } - }); - - JButton upButton = new JButton("\u2191"); // Up arrow (↑) - gbc.gridx = 5; - gbc.gridy = 0; - gbc.fill = GridBagConstraints.NONE; - gbc.weightx = 0; // No expansion - bottomPanel.add(upButton, gbc); - - upButton.addActionListener(e -> { - try { - File latestTempFile = File.createTempFile(title, ".html"); - latestTempFile.deleteOnExit(); - try (FileWriter writer = new FileWriter(latestTempFile)) { - writer.write(responseHistory.get(currentResponseIndex)); - } - if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { - Desktop.getDesktop().browse(latestTempFile.toURI()); - } - } catch (IOException ex) { - ex.printStackTrace(); - } - }); - - // Create a common action listener method - ActionListener submitActionListener = e -> { - String question = questionField.getText(); - if (!question.isEmpty()) { - submitButton.setText("Loading..."); - submitButton.setEnabled(false); - handleQuestion(question, submitButton); - } - }; - - submitButton.addActionListener(submitActionListener); - questionField.addActionListener(submitActionListener); - responseHistory.add(response); currentResponseIndex = responseHistory.size() - 1; - prevButton.addActionListener(e -> { - if (currentResponseIndex > 0) { - currentResponseIndex--; - String historyResponse = responseHistory.get(currentResponseIndex); - updateEditor(historyResponse); - updateNavigationButtons(prevButton, nextButton); - } - }); - nextButton.addActionListener(e -> { - if (currentResponseIndex < responseHistory.size() - 1) { - currentResponseIndex++; - String historyResponse = responseHistory.get(currentResponseIndex); - updateEditor(historyResponse); - updateNavigationButtons(prevButton, nextButton); - } - }); - - updateNavigationButtons(prevButton, nextButton); - - topComponent.add(bottomPanel, BorderLayout.SOUTH); + topComponent.add(createBottomPanel(title), BorderLayout.SOUTH); topComponent.open(); topComponent.requestActive(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { @@ -284,10 +250,121 @@ private void displayHtmlContent(WorkingCopy copy, TreePath tp, final String resp }); } + private JPanel createBottomPanel(String title) { + // Create a panel for the text field and buttons + JPanel bottomPanel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(2, 2, 2, 2); + + // Previous Button + prevButton = new JButton("\u2190"); // Left arrow (←) + gbc.gridx = 0; + gbc.gridy = 0; + gbc.anchor = GridBagConstraints.WEST; + bottomPanel.add(prevButton, gbc); + + // Next Button + nextButton = new JButton("\u2192"); + gbc.gridx = 1; + gbc.gridy = 0; + gbc.anchor = GridBagConstraints.WEST; + bottomPanel.add(nextButton, gbc); + + // Text Field + JTextField questionField = new JTextField(); + questionField.setPreferredSize(new Dimension(300, questionField.getPreferredSize().height)); + gbc.gridx = 2; + gbc.gridy = 0; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + bottomPanel.add(questionField, gbc); + + // Ask Button + JButton submitButton = new JButton("Ask"); + gbc.gridx = 3; + gbc.gridy = 0; + gbc.fill = GridBagConstraints.NONE; + gbc.weightx = 0; + bottomPanel.add(submitButton, gbc); + + // Save Button + saveButton = new JButton("Copy"); + gbc.gridx = 4; + gbc.gridy = 0; + gbc.fill = GridBagConstraints.NONE; + gbc.weightx = 0; + bottomPanel.add(saveButton, gbc); + + saveButton.addActionListener(e -> { + try { + StringSelection selection = new StringSelection(javaCode); + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(selection, null); + } catch (Exception ex) { + ex.printStackTrace(); + } + }); + + JButton upButton = new JButton("\u2191"); // Up arrow (↑) + gbc.gridx = 5; + gbc.gridy = 0; + gbc.fill = GridBagConstraints.NONE; + gbc.weightx = 0; // No expansion + bottomPanel.add(upButton, gbc); + + upButton.addActionListener(e -> { + try { + File latestTempFile = File.createTempFile(title, ".html"); + latestTempFile.deleteOnExit(); + try (FileWriter writer = new FileWriter(latestTempFile)) { + writer.write(responseHistory.get(currentResponseIndex)); + } + if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { + Desktop.getDesktop().browse(latestTempFile.toURI()); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + }); + + // Create a common action listener method + ActionListener submitActionListener = e -> { + String question = questionField.getText(); + if (!question.isEmpty()) { + submitButton.setText("Loading..."); + submitButton.setEnabled(false); + handleQuestion(question, submitButton); + } + }; + + submitButton.addActionListener(submitActionListener); + questionField.addActionListener(submitActionListener); + + prevButton.addActionListener(e -> { + if (currentResponseIndex > 0) { + currentResponseIndex--; + String historyResponse = responseHistory.get(currentResponseIndex); + updateEditor(historyResponse); + updateNavigationButtons(prevButton, nextButton); + } + }); + nextButton.addActionListener(e -> { + if (currentResponseIndex < responseHistory.size() - 1) { + currentResponseIndex++; + String historyResponse = responseHistory.get(currentResponseIndex); + updateEditor(historyResponse); + updateNavigationButtons(prevButton, nextButton); + } + }); + + updateNavigationButtons(prevButton, nextButton); + + return bottomPanel; + } + private JEditorPane updateEditor(String response) { JEditorPane editorPane = null; String[] parts = response.split("
|
|
|
|
"); -// String[] parts = response.split("
|
|
|
"); topComponent.clear(); for (int i = 0; i < parts.length; i++) { if (i % 2 == 1) { @@ -312,8 +389,12 @@ private void handleQuestion(String question, JButton submitButton) { prevChat = null; } } - - String response = new JeddictChatModel().generateHtmlDescription(treePath.getCompilationUnit().toString(), treePath.getLeaf() instanceof MethodTree ? treePath.getLeaf().toString() : null, prevChat, question); + String response; + 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); + } response = removeCodeBlockMarkers(response); if (responseHistory.isEmpty() || !response.equals(responseHistory.get(responseHistory.size() - 1))) { responseHistory.add(response); diff --git a/src/main/java/io/github/jeddict/ai/hints/MethodFix.java b/src/main/java/io/github/jeddict/ai/hints/MethodFix.java index 9b02bd6..f3352dd 100644 --- a/src/main/java/io/github/jeddict/ai/hints/MethodFix.java +++ b/src/main/java/io/github/jeddict/ai/hints/MethodFix.java @@ -15,7 +15,6 @@ import static io.github.jeddict.ai.util.FileUtil.saveOpenEditor; import static io.github.jeddict.ai.util.SourceUtil.geIndentaion; import static io.github.jeddict.ai.util.StringUtil.removeCodeBlockMarkers; -import static io.github.jeddict.ai.util.UIUtil.askQuery; import javax.lang.model.element.Element; import org.json.JSONArray; import org.json.JSONObject; @@ -26,6 +25,7 @@ import org.netbeans.api.java.source.WorkingCopy; import org.netbeans.spi.java.hints.JavaFix; import org.openide.util.NbBundle; +import static io.github.jeddict.ai.util.UIUtil.queryToEnhance; /** * @@ -85,7 +85,7 @@ protected void performRewrite(JavaFix.TransformationContext tc) throws Exception } else if (action == Action.ENHANCE) { content = new JeddictChatModel().enhanceMethodFromMethodContent(treePath.getParentPath().getLeaf().toString(), leaf.toString()); } else { - String query = askQuery(); + String query = queryToEnhance(); if (query == null) { return; } diff --git a/src/main/java/io/github/jeddict/ai/util/ProjectUtils.java b/src/main/java/io/github/jeddict/ai/util/ProjectUtils.java new file mode 100644 index 0000000..dd14594 --- /dev/null +++ b/src/main/java/io/github/jeddict/ai/util/ProjectUtils.java @@ -0,0 +1,40 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package io.github.jeddict.ai.util; + +import java.util.ArrayList; +import java.util.List; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.SourceGroup; +import org.openide.filesystems.FileObject; + +/** + * + * @author Shiwani Gupta + */ +public class ProjectUtils { + + public static List getSourceFiles(Project project) { + List sourceFiles = new ArrayList<>(); + SourceGroup[] sourceGroups = org.netbeans.api.project.ProjectUtils.getSources(project).getSourceGroups("java"); + for (SourceGroup sourceGroup : sourceGroups) { + if (sourceGroup.getRootFolder().getParent().getName().equals("test")) { + continue; + } + collectFiles(sourceGroup.getRootFolder(), sourceFiles); + } + return sourceFiles; + } + + public static void collectFiles(FileObject folder, List sourceFiles) { + for (FileObject file : folder.getChildren()) { + if (file.isFolder()) { + collectFiles(file, sourceFiles); + } else if (file.isData()) { + sourceFiles.add(file); + } + } + } +} diff --git a/src/main/java/io/github/jeddict/ai/util/UIUtil.java b/src/main/java/io/github/jeddict/ai/util/UIUtil.java index 64537b9..3102971 100644 --- a/src/main/java/io/github/jeddict/ai/util/UIUtil.java +++ b/src/main/java/io/github/jeddict/ai/util/UIUtil.java @@ -4,6 +4,7 @@ */ package io.github.jeddict.ai.util; +import io.github.jeddict.ai.components.AssistantTopComponent; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; @@ -15,7 +16,7 @@ */ public class UIUtil { - public static String askQuery() { + public static String queryToEnhance() { // Create a JTextArea for multiline input JTextArea textArea = new JTextArea(10, 30); // 10 rows, 30 columns textArea.setWrapStyleWord(true); @@ -34,7 +35,8 @@ public static String askQuery() { panel, "Please provide details about what to update in this method:", JOptionPane.OK_CANCEL_OPTION, - JOptionPane.PLAIN_MESSAGE + JOptionPane.PLAIN_MESSAGE, + AssistantTopComponent.icon ); // Check the user's choice @@ -56,7 +58,7 @@ public static String askQuery() { return query; } - public static String askQueryAboutClass() { + public static String askQuery() { // Create a JTextArea for multiline input JTextArea textArea = new JTextArea(10, 30); // 10 rows, 30 columns textArea.setWrapStyleWord(true); @@ -73,9 +75,10 @@ public static String askQueryAboutClass() { int option = JOptionPane.showConfirmDialog( null, panel, - "Please ask the query about this class.", + "Please ask the query.", JOptionPane.OK_CANCEL_OPTION, - JOptionPane.PLAIN_MESSAGE + JOptionPane.PLAIN_MESSAGE, + AssistantTopComponent.icon ); // Check the user's choice diff --git a/src/main/java/io/github/jeddict/ai/logo.png b/src/main/resources/icons/logo.png similarity index 100% rename from src/main/java/io/github/jeddict/ai/logo.png rename to src/main/resources/icons/logo.png diff --git a/src/main/resources/icons/logo16.png b/src/main/resources/icons/logo16.png new file mode 100644 index 0000000..8d6c44f Binary files /dev/null and b/src/main/resources/icons/logo16.png differ diff --git a/src/main/resources/icons/logo24.png b/src/main/resources/icons/logo24.png new file mode 100644 index 0000000..0c67aff Binary files /dev/null and b/src/main/resources/icons/logo24.png differ