diff --git a/package.json b/package.json index eb2a9b7..b7d98ae 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "antlr4": "4.7.2", "bootstrap": "4.4.1", "chroma-js": "2.1.0", - "diff": "4.0.2", + "diff": "^4.0.2", + "diff2html": "^3.4.48", "elementtree": "0.1.7", "marked": "4.0.12", "monaco-editor": "0.21.2", @@ -16,6 +17,7 @@ "openai": "^4.30.0", "path": "0.12.7", "pluralize": "7.0.0", + "prismjs": "^1.29.0", "rc-slider": "8.7.1", "rc-tooltip": "4.0.0-alpha.3", "react": "16.12.0", diff --git a/src/activeLLM/suggestFix.js b/src/activeLLM/suggestFix.js index 039df69..f1a5c22 100644 --- a/src/activeLLM/suggestFix.js +++ b/src/activeLLM/suggestFix.js @@ -6,38 +6,41 @@ export async function suggestFix( violation, exampleFilePath, violationFilePath, + violationFileContent, setState, ) { - const prompt = `Here is a design rule and its description: ${rule} - Here is a code example that follows this design rule: ${example} - The example file path is ${exampleFilePath} - Now, here is a code snippet that violates this design rule: ${violation} - The violated code's file path is ${violationFilePath} - Can you suggest a fix to make this violation follow the given design rule? - Generate code with surrounding code included that follows the design rule. - Be sure to maintain proper whitespace with \\t and \\n. - Give a brief explanation of your fix as well. - Ensure to include the fileName of where to insert the fix in the format Example.java. - Strictly output in JSON format. The JSON should have the following format:{"code": "...", "explanation": "...", "fileName": "..."}`; - - //the following prompt is an older version. It is commented out because the one used right now is more - //concise and produces better output - /*const prompt = `Here is a design rule and its description: ${rule} - Here is a code example that follows this design rule: ${example} - The example file path is ${exampleFilePath} - Now, here is a code snippet that violates this design rule. ${violation} - The violated code's file path is ${violationFilePath} - Suggest a fix to make this violation follow the given design rule? - Generate code with surrounding code included that follows the design rule. - Be sure to maintain proper whitespace with \\t and \\n. - Give a brief explanation of your fix as well. Strictly output in JSON format. - Ensure that you include the fileName where the fix should be inserted at. - This should just be in the format Example.java - The JSON should have the following format:{"code": "...", "explanation": "...", "fileName": "..."}`;*/ + const prompt = `You are assisting with enforcing the following design rule: +${rule} + +Here is a code example that follows the rule: +${example} +Example file path: ${exampleFilePath} + +<<>> +${violationFilePath} +<<>> + +<<>> +${violationFileContent} +<<>> + +<<>> +${violation} +<<>> + +Rewrite the original file so it satisfies the rule while preserving every unrelated line verbatim. Constraints: +- Copy every existing package declaration, import statement, comment, Javadoc, and formatting exactly as provided unless a specific line must change to satisfy the rule. +- Do NOT delete or reorder imports; append new imports after the existing block if required. +- When you modify a line, change only the minimal portion needed; leave all other lines identical. +- Preserve indentation and whitespace on all untouched lines. + +Respond strictly as JSON with the structure {\"modifiedFileContent\":\"...\", \"explanation\":\"...\", \"fileName\":\"...\"}.`; let attempt = 1; let success = false; + console.log("violation codde sent to chatGPT:"); + console.log(violationFileContent); while (attempt <= 3 && !success) { try { @@ -60,9 +63,12 @@ export async function suggestFix( const stripped = suggestedSnippet.replace(/^`json|`json$/g, "").trim(); const parsedJSON = JSON.parse(stripped); + console.log("Solution from chatGPT:"); + console.log(parsedJSON); + // sets the relevant state in the React component that made the request // see ../ui/rulePanel.js for more details - setState({suggestedSnippet: parsedJSON["code"]}); + setState({suggestedSnippet: parsedJSON["modifiedFileContent"]}); setState({snippetExplanation: parsedJSON["explanation"]}); setState({suggestionFileName: parsedJSON["fileName"]}); @@ -71,8 +77,9 @@ export async function suggestFix( data: { filePath: `${violationFilePath}`, fileToChange: `${parsedJSON["fileName"]}`, - modifiedFileContent: `${parsedJSON["code"]}`, - explanation: `${parsedJSON["explanation"]}`, + modifiedFileContent: parsedJSON["modifiedFileContent"], + explanation: parsedJSON["explanation"], + originalFileContent: violationFileContent, }, }; @@ -80,6 +87,84 @@ export async function suggestFix( setState({llmModifiedFileContent: llmModifiedFileContent}); success = true; + return llmModifiedFileContent; + } catch (error) { + console.log(error); + success = false; + attempt++; + } + } +} + + + +export async function editFix(fileContentToSendToGPT,conversationHistory,setState) { + + console.log("CAME TO EDIT FIX"); + //console.log(fileContentToSendToGPT); + //conversationHistory = {role:'user',content:conversationHistory}; + + // Create the additional prompt using the projectPath + const additionalPrompt = `You previously suggested the following fix (JSON snippet below). Integrate it into the provided file without removing unrelated lines. + +<<>> +${conversationHistory} +<<>> + +<<>> +${fileContentToSendToGPT} +<<>> + +Rewrite the file so it satisfies the rule while preserving every existing package, import, comment, and formatting exactly as provided unless a line must change to satisfy the rule. Do not delete or reorder imports; only append new ones if required. Modify only the minimal code needed and keep all untouched lines verbatim. +Respond strictly as JSON with the structure {\"modifiedFileContent\":\"...\", \"explanation\":\"...\", \"fileName\":\"...\"}.`; + const continuedConversation = [ { role: "user", content: additionalPrompt }]; + + let attempt = 1; + let success = false; + + while (attempt <= 3 && !success) { + try { + const apiKey = localStorage.getItem("OPENAI_API_KEY"); + const openai = new OpenAI({ + apiKey, + dangerouslyAllowBrowser: true, + }); + + // Send the continued conversation to OpenAI + const chatCompletion = await openai.chat.completions.create({ + model: "gpt-3.5-turbo", + temperature: 0.75, + messages: continuedConversation, + }); + + const suggestedSnippet = chatCompletion.choices[0].message.content; + const stripped = suggestedSnippet.replace(/^`json|`json$/g, "").trim(); + const parsedJSON = JSON.parse(stripped); + + console.log(parsedJSON); + + // Update state with new suggested snippet, explanation, and file name + setState((prevState) => ({ + suggestedSnippet: parsedJSON["modifiedFileContent"], + snippetExplanation: parsedJSON["explanation"], + suggestionFileName: parsedJSON["fileName"], + llmModifiedFileContent: { + command: "LLM_MODIFIED_FILE_CONTENT", + data: { + filePath: `${parsedJSON["fileName"]}`, // Assuming the initial prompt contains the file path + fileToChange: `${parsedJSON["fileName"]}`, + modifiedFileContent: parsedJSON["modifiedFileContent"], + explanation: parsedJSON["explanation"], + originalFileContent: prevState?.originalFileContent ?? '', + }, + }, + })); + + // Update conversation history in session storage + //saveConversationToSessionStorage(key, [...continuedConversation, { role: "assistant", content: suggestedSnippet }]); + + success = true; + console.log("got second data from chatGPT"); } catch (error) { console.log(error); success = false; diff --git a/src/core/coreConstants.js b/src/core/coreConstants.js index 2817b0d..5dda3cc 100644 --- a/src/core/coreConstants.js +++ b/src/core/coreConstants.js @@ -1,11 +1,14 @@ export const webSocketSendMessage = { + send_info_for_edit_fix:"SEND_INFO_FOR_EDIT_FIX", modified_rule_msg: "MODIFIED_RULE", modified_tag_msg: "MODIFIED_TAG", snippet_xml_msg: "XML_RESULT", + llm_modified_file_content: "LLM_MODIFIED_FILE_CONTENT", converted_java_snippet_msg: "CONVERTED_JAVA_SNIPPET", code_to_xml_msg: "EXPR_STMT", new_rule_msg: "NEW_RULE", new_tag_msg: "NEW_TAG", + send_llm_snippet_msg: "LLM_SNIPPET", open_file_msg: "OPEN_FILE", @@ -18,6 +21,7 @@ export const webSocketSendMessage = { }; export const webSocketReceiveMessage = { + receive_content_for_edit_fix: "RECEIVE_CONTENT_FOR_EDIT_FIX", xml_files_msg: "XML", rule_table_msg: "RULE_TABLE", tag_table_msg: "TAG_TABLE", diff --git a/src/core/utilities.js b/src/core/utilities.js index 15d38d6..7ddb5c4 100644 --- a/src/core/utilities.js +++ b/src/core/utilities.js @@ -30,6 +30,40 @@ class Utilities { tagInfo: data }; break; + + case webSocketSendMessage.send_llm_snippet_msg: + console.log("In command"); + console.log(data); + messageJson.data={ + code:data.suggestedSnippet, + explanation:data.snippetExplanation + } + break; + + case webSocketSendMessage.send_info_for_edit_fix: + messageJson.data={ + filePathOfSuggestedFix:data.data.fileToChange, + filePathOfViolation:data.data.filePath, + modifiedFileContent:data.data.modifiedFileContent + } + break; + + + case webSocketSendMessage.llm_modified_file_content: + if (!data.llmModifiedFileContent) { + console.warn('No LLM modified file content to send.'); + return; + } + + messageJson.data = { + filePath: data.llmModifiedFileContent.data.filePath, + explanation: data.llmModifiedFileContent.data.explanation, + fileToChange: data.llmModifiedFileContent.data.fileToChange, + modifiedFileContent: data.llmModifiedFileContent.data.modifiedFileContent, + originalFileContent: data.originalFileContent || data.llmModifiedFileContent.data.originalFileContent || '', + } + + break; case webSocketSendMessage.snippet_xml_msg: messageJson.data = { fileName: data.fileName, @@ -246,4 +280,4 @@ class Utilities { } -export default Utilities; \ No newline at end of file +export default Utilities; diff --git a/src/core/webSocketManager.js b/src/core/webSocketManager.js index 15254cd..cb0ff07 100644 --- a/src/core/webSocketManager.js +++ b/src/core/webSocketManager.js @@ -5,6 +5,7 @@ import {Component} from "react"; import {connect} from "react-redux"; + import { receiveExpressionStatementXML, ignoreFileChange, updateFilePath, @@ -51,6 +52,7 @@ class WebSocketManager extends Component { switch (message.command) { + case webSocketReceiveMessage.enter_chat_msg: this.props.onLoadingGif(true); break; @@ -170,17 +172,25 @@ class WebSocketManager extends Component { case webSocketReceiveMessage.file_change_in_ide_msg: // data: "filePath" + let focusedFilePath = message.data; - if (!this.props.ignoreFileChange) { - this.props.onFilePathChange(focusedFilePath); - window.location.hash = `#/${hashConst.rulesForFile}/` + focusedFilePath.replace(/\//g, "%2F"); - } else { - counter--; - if (counter === 0) { - this.props.onFalsifyIgnoreFile(); - counter = 3; - } + console.log("File name: "); + console.log(focusedFilePath); + focusedFilePath = String(focusedFilePath); + + if(!focusedFilePath.includes("edit_fix_window")){ + if (!this.props.ignoreFileChange) { + this.props.onFilePathChange(focusedFilePath); + window.location.hash = `#/${hashConst.rulesForFile}/` + focusedFilePath.replace(/\//g, "%2F"); + } else { + counter--; + if (counter === 0) { + this.props.onFalsifyIgnoreFile(); + counter = 3; + } + } } + break; /* Mining Rules */ diff --git a/src/prism-vs.css b/src/prism-vs.css new file mode 100644 index 0000000..54377df --- /dev/null +++ b/src/prism-vs.css @@ -0,0 +1,168 @@ +/** + * VS theme by Andrew Lock (https://andrewlock.net) + * Inspired by Visual Studio syntax coloring + */ + +code[class*="language-"], +pre[class*="language-"] { + color: #393A34; + font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + font-size: .9em; + line-height: 1.2em; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre > code[class*="language-"] { + font-size: 1em; +} + +pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { + background: #C1DEF1; +} + +pre[class*="language-"]::selection, pre[class*="language-"] ::selection, +code[class*="language-"]::selection, code[class*="language-"] ::selection { + background: #C1DEF1; +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; + border: 1px solid #dddddd; + background-color: white; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .2em; + padding-top: 1px; + padding-bottom: 1px; + background: #f8f8f8; + border: 1px solid #dddddd; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #008000; + font-style: italic; +} + +.token.namespace { + opacity: .7; +} + +.token.string { + color: #A31515; +} + +.token.punctuation, +.token.operator { + color: #393A34; /* no highlight */ +} + +.token.url, +.token.symbol, +.token.number, +.token.boolean, +.token.variable, +.token.constant, +.token.inserted { + color: #36acaa; +} + +.token.atrule, +.token.keyword, +.token.attr-value, +.language-autohotkey .token.selector, +.language-json .token.boolean, +.language-json .token.number, +code[class*="language-css"] { + color: #0000ff; +} + +.token.function { + color: #393A34; +} + +.token.deleted, +.language-autohotkey .token.tag { + color: #9a050f; +} + +.token.selector, +.language-autohotkey .token.keyword { + color: #00009f; +} + +.token.important { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} + +.token.italic { + font-style: italic; +} + +.token.class-name, +.language-json .token.property { + color: #2B91AF; +} + +.token.tag, +.token.selector { + color: #800000; +} + +.token.attr-name, +.token.property, +.token.regex, +.token.entity { + color: #ff0000; +} + +.token.directive.tag .tag { + background: #ffff00; + color: #393A34; +} + +/* overrides color-values for the Line Numbers plugin + * http://prismjs.com/plugins/line-numbers/ + */ +.line-numbers.line-numbers .line-numbers-rows { + border-right-color: #a5a5a5; +} + +.line-numbers .line-numbers-rows > span:before { + color: #2B91AF; +} + +/* overrides color-values for the Line Highlight plugin +* http://prismjs.com/plugins/line-highlight/ +*/ +.line-highlight.line-highlight { + background: rgba(193, 222, 241, 0.2); + background: -webkit-linear-gradient(left, rgba(193, 222, 241, 0.2) 70%, rgba(221, 222, 241, 0)); + background: linear-gradient(to right, rgba(193, 222, 241, 0.2) 70%, rgba(221, 222, 241, 0)); +} diff --git a/src/prism-vsc-dark-plus.css b/src/prism-vsc-dark-plus.css new file mode 100644 index 0000000..d3bd501 --- /dev/null +++ b/src/prism-vsc-dark-plus.css @@ -0,0 +1,275 @@ +pre[class*="language-"], +code[class*="language-"] { + color: #d4d4d4; + font-size: 13px; + text-shadow: none; + font-family: Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + line-height: 1.5; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::selection, +code[class*="language-"]::selection, +pre[class*="language-"] *::selection, +code[class*="language-"] *::selection { + text-shadow: none; + background: #264F78; +} + +@media print { + pre[class*="language-"], + code[class*="language-"] { + text-shadow: none; + } +} + +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; + background: #1e1e1e; +} + +:not(pre) > code[class*="language-"] { + padding: .1em .3em; + border-radius: .3em; + color: #db4c69; + background: #1e1e1e; +} +/********************************************************* +* Tokens +*/ +.namespace { + opacity: .7; +} + +.token.doctype .token.doctype-tag { + color: #569CD6; +} + +.token.doctype .token.name { + color: #9cdcfe; +} + +.token.comment, +.token.prolog { + color: #6a9955; +} + +.token.punctuation, +.language-html .language-css .token.punctuation, +.language-html .language-javascript .token.punctuation { + color: #d4d4d4; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.inserted, +.token.unit { + color: #b5cea8; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.deleted { + color: #ce9178; +} + +.language-css .token.string.url { + text-decoration: underline; +} + +.token.operator, +.token.entity { + color: #d4d4d4; +} + +.token.operator.arrow { + color: #569CD6; +} + +.token.atrule { + color: #ce9178; +} + +.token.atrule .token.rule { + color: #c586c0; +} + +.token.atrule .token.url { + color: #9cdcfe; +} + +.token.atrule .token.url .token.function { + color: #dcdcaa; +} + +.token.atrule .token.url .token.punctuation { + color: #d4d4d4; +} + +.token.keyword { + color: #569CD6; +} + +.token.keyword.module, +.token.keyword.control-flow { + color: #c586c0; +} + +.token.function, +.token.function .token.maybe-class-name { + color: #dcdcaa; +} + +.token.regex { + color: #d16969; +} + +.token.important { + color: #569cd6; +} + +.token.italic { + font-style: italic; +} + +.token.constant { + color: #9cdcfe; +} + +.token.class-name, +.token.maybe-class-name { + color: #4ec9b0; +} + +.token.console { + color: #9cdcfe; +} + +.token.parameter { + color: #9cdcfe; +} + +.token.interpolation { + color: #9cdcfe; +} + +.token.punctuation.interpolation-punctuation { + color: #569cd6; +} + +.token.boolean { + color: #569cd6; +} + +.token.property, +.token.variable, +.token.imports .token.maybe-class-name, +.token.exports .token.maybe-class-name { + color: #9cdcfe; +} + +.token.selector { + color: #d7ba7d; +} + +.token.escape { + color: #d7ba7d; +} + +.token.tag { + color: #569cd6; +} + +.token.tag .token.punctuation { + color: #808080; +} + +.token.cdata { + color: #808080; +} + +.token.attr-name { + color: #9cdcfe; +} + +.token.attr-value, +.token.attr-value .token.punctuation { + color: #ce9178; +} + +.token.attr-value .token.punctuation.attr-equals { + color: #d4d4d4; +} + +.token.entity { + color: #569cd6; +} + +.token.namespace { + color: #4ec9b0; +} +/********************************************************* +* Language Specific +*/ + +pre[class*="language-javascript"], +code[class*="language-javascript"], +pre[class*="language-jsx"], +code[class*="language-jsx"], +pre[class*="language-typescript"], +code[class*="language-typescript"], +pre[class*="language-tsx"], +code[class*="language-tsx"] { + color: #9cdcfe; +} + +pre[class*="language-css"], +code[class*="language-css"] { + color: #ce9178; +} + +pre[class*="language-html"], +code[class*="language-html"] { + color: #d4d4d4; +} + +.language-regex .token.anchor { + color: #dcdcaa; +} + +.language-html .token.punctuation { + color: #808080; +} +/********************************************************* +* Line highlighting +*/ +pre[class*="language-"] > code[class*="language-"] { + position: relative; + z-index: 1; +} + +.line-highlight.line-highlight { + background: #f7ebc6; + box-shadow: inset 5px 0 0 #f7d87c; + z-index: 0; +} diff --git a/src/ui/rulePanel.js b/src/ui/rulePanel.js index d10a6d4..e594eae 100644 --- a/src/ui/rulePanel.js +++ b/src/ui/rulePanel.js @@ -8,7 +8,7 @@ import { connect } from "react-redux"; import "../index.css"; import "../App.css"; import { - Tab, Tabs, Badge, FormGroup, ControlLabel, Label, Collapse + Tab, Tabs, Badge, FormGroup, ControlLabel, Label, Collapse, Button } from "react-bootstrap"; import { FaCaretDown, FaCaretUp } from "react-icons/fa"; import { MdEdit } from "react-icons/md"; @@ -21,7 +21,14 @@ import { webSocketSendMessage } from "../core/coreConstants"; import { relatives } from "../core/ruleExecutorConstants"; import { hashConst, none_filePath } from "./uiConstants"; -import { suggestFix } from "../activeLLM/suggestFix"; +import { suggestFix, editFix } from "../activeLLM/suggestFix"; +import Prism from 'prismjs'; +import '../../src/prism-vs.css'; // Choose any theme you like + +// Import the language syntax for Java +import 'prismjs/components/prism-java'; + +import WebSocketManager from "../core/webSocketManager"; class RulePanel extends Component { @@ -387,6 +394,7 @@ class RulePanel extends Component { return list.map((d, i) => { return ( { + sessionStorage.setItem(key, JSON.stringify(conversationHistory)); } + getConversationFromSessionStorage = (key) => { + const history = sessionStorage.getItem(key); + return history ? JSON.parse(history) : []; + } + + clearConversationFromSessionStorage = (key) => { + sessionStorage.removeItem(key); + } + + handleSuggestion = async ( rule, example, snippet, exampleFilePath, violationFilePath, + key ) => { const parsedSnippet = Utilities.removeSrcmlAnnotations(snippet); const parsedExample = Utilities.removeSrcmlAnnotations(example); + + const normalizePath = (filePath) => filePath.replace(/\\/g, '/'); + const targetPath = normalizePath(violationFilePath); + let violationFileContent = ''; + if (this.props.xmlFiles && this.props.xmlFiles.length > 0) { + const matchingFile = this.props.xmlFiles.find((file) => normalizePath(file.filePath) === targetPath); + if (matchingFile) { + violationFileContent = Utilities.removeSrcmlAnnotations(matchingFile.xml); + } else { + const targetFileName = targetPath.split('/').pop(); + const fallbackMatch = this.props.xmlFiles.find((file) => normalizePath(file.filePath).endsWith(`/${targetFileName}`)); + if (fallbackMatch) { + violationFileContent = Utilities.removeSrcmlAnnotations(fallbackMatch.xml); + } + } + } + + this.setState({ fixButtonClicked: true, originalFileContent: violationFileContent }); + // prevent multiple calls to suggestFix if (!this.state.suggestionCreated) { - suggestFix( + const conversationHistory = await suggestFix( rule, parsedExample, parsedSnippet, exampleFilePath, violationFilePath, + violationFileContent, this.setState.bind(this), ); + + this.saveConversationToSessionStorage(key, conversationHistory); + // notify the component that this snippet now has a suggested fix this.setState({ suggestionCreated: true }); } }; + + + handleEditFix = async (suggestionFileName, uniqueKey) => { + + const convHistory = this.getConversationFromSessionStorage(uniqueKey); + //console.log("convHistory"); + //console.log(convHistory); + + + const xmlFiles = this.props.xmlFiles; + + + + // Function to extract the file name from the filePath + const extractFileName = (filePath) => { + const parts = filePath.split('/'); + return parts[parts.length - 1]; + }; + + // Iterate over xmlFiles to find the matching file + const matchingFile = xmlFiles.find(file => extractFileName(file.filePath) === suggestionFileName); + let codeOfSuggestionFile = ''; + + if (matchingFile) { + console.log("Found matching file:"); + //console.log(matchingFile.xml); + codeOfSuggestionFile = Utilities.removeSrcmlAnnotations(matchingFile.xml); + // Do something with the matchingFile.xml here + + //console.log("before getting into normalizeFunction"); + //console.log(codeOfSuggestionFile); + + //console.log("after normalizeFunction"); + //console.log(codeOfSuggestionFile); + } else { + console.log("No matching file found"); + } + //console.log("Matching file content"); + //console.log(codeOfSuggestionFile); + const originalCode = Utilities.removeSrcmlAnnotations(this.state.d.surroundingNodes); + const modifiedCode = convHistory.data.modifiedFileContent; + const diff = this.generateDiff(originalCode, modifiedCode); + //console.log("THE DIFF"); + //console.log(diff); + + + + // Define the processDiff function inside handleEditFix + const processDiff = (diffArray) => { + return diffArray.map(diff => { + let message = ''; + if (diff.type === 'added') { + message = 'This line was added by you as part of solution: '; + } else if (diff.type === 'removed') { + message = 'This line was removed by you as part of solution: '; + } + return message + diff.text; + }); + }; + + const processedDiff = processDiff(diff); + + // Join all the elements into a single string with each element on a separate line + const resultText = processedDiff.join('\n'); + + console.log(resultText); + + const response = await editFix(codeOfSuggestionFile, resultText, this.setState.bind(this)); + } + + generateDiff = (originalCode, modifiedCode) => { + const normalizeForComparison = (line) => line.replace(/^\s+/, '').replace(/\s+$/, ''); + const originalLines = originalCode.split('\n'); + const modifiedLines = modifiedCode.split('\n'); + + const originalCounts = new Map(); + originalLines.forEach((line) => { + const key = normalizeForComparison(line); + originalCounts.set(key, (originalCounts.get(key) || 0) + 1); + }); + + const remainingOriginalCounts = new Map(originalCounts); + const diff = []; + + modifiedLines.forEach((line) => { + const key = normalizeForComparison(line); + const remaining = remainingOriginalCounts.get(key) || 0; + if (remaining > 0) { + remainingOriginalCounts.set(key, remaining - 1); + } else { + diff.push({ type: 'added', text: line }); + } + }); + + originalLines.forEach((line) => { + const key = normalizeForComparison(line); + const remaining = remainingOriginalCounts.get(key) || 0; + if (remaining > 0) { + diff.push({ type: 'removed', text: line }); + remainingOriginalCounts.set(key, remaining - 1); + } + }); + + console.log('DIFF'); + console.log(diff); + + return diff; + }; + + renderDiff = () => { + const originalCode = this.state.originalFileContent; + const modifiedCode = this.state.suggestedSnippet || ''; + const diff = this.generateDiff(originalCode, modifiedCode); + + const highlightCode = (code) => { + return Prism.highlight(code, Prism.languages.java, 'java'); + }; + + return ( +
+ {diff.map((line, index) => ( +
+ + {line.type === 'added' ? '+' : '-'} + + +
+ ))} +
+ ); + }; + + render() { - // NOTE: These styles can be moved to index.css in the future. - // There was an issue with that, so this is a quick fix + const uniqueKey = this.state.d.filePath; + const apiKey = localStorage.getItem("OPENAI_API_KEY"); + const titleStyle = { color: "#333", fontSize: "1.10em", @@ -555,14 +744,42 @@ class SnippetView extends Component { }; const buttonParent = { - position: "absolute", - top: "0", - right: "0", + position: "relative", + //top: "0", + //right: "0", zIndex: "1", }; - // Store the API key in a variable - const apiKey = localStorage.getItem("OPENAI_API_KEY"); + const containerStyle = { + display: "flex", + flexDirection: "column", + width: "100%", + padding: "10px", + border: "1px solid grey", + marginTop: "2px", + borderRadius: "5px" + }; + + const paneStyle = { + padding: "10px", + borderBottom: "1px solid grey", + marginTop: "2px", + borderRadius: "0px" + }; + + const highlightCode = (code) => { + return Prism.highlight(code, Prism.languages.java, 'java'); + }; + + const wrapperStyle = { + display: 'flex', + alignItems: 'center', + width: '100%', + }; + + const contentStyle = { + flex: 1, + }; return (
@@ -573,6 +790,7 @@ class SnippetView extends Component { >
{ this.props.onIgnoreFile(true); Utilities.sendToServer( @@ -582,81 +800,158 @@ class SnippetView extends Component { ); }} > -
-
-                        
-                            {/* render the following IF this is a violation of a rule and there is no fix yet */}
-                            {this.state.snippetGroup === "violated" &&
-                                // Use the apiKey variable in the conditional rendering check
-                                apiKey !== null &&
-                                apiKey !== "" &&
-                                !this.state.suggestedSnippet && (
-                                    
-                                )}
-                        
+                        

Violated Code Snippet

+
+
+
+
+
+                            
+                                {/* render the following IF this is a violation of a rule and there is no fix yet */}
+                                {this.state.snippetGroup === "violated" &&
+                                    apiKey !== null &&
+                                    apiKey !== "" &&
+                                    !this.state.suggestedSnippet && (
+                                        
+                                    )}
+                            
+                        
- {this.state.suggestionCreated && !this.state.suggestedSnippet && ( -

Loading Fix...

+ {!this.state.suggestionCreated && this.state.fixButtonClicked && ( +

Loading Fix...

)} + {this.state.suggestionCreated && this.state.suggestedSnippet && ( +
+ {/*
*/} + {/*

Suggested Fix:

+
*/}
+
+                            
+ +

Suggestion Location:

+

+

+ + +
+

Suggested Fix:

+ {this.renderDiff()} +
+ +
+

Explanation:

+

+

+ + + +
+ + + + + + +
- {/* render the following IF the component state has received snippet */} - {this.state.suggestedSnippet && ( -
-

Suggested Fix:

-
-                            

Suggestion Location:

-

-

Explanation:

-

- -

)}
@@ -672,4 +967,4 @@ class SnippetView extends Component { exampleFilePath: nextProps.exampleFilePath }); } -} \ No newline at end of file +}