diff --git a/content.js b/content.js index fd1b362..6df866c 100644 --- a/content.js +++ b/content.js @@ -5,7 +5,7 @@ async function getOpenAIKey() { }); }); } -async function getOtherServiceUrl() { +async function getBigcodeServiceUrl() { return new Promise((resolve) => { chrome.runtime.sendMessage({ type: "getUrl" }, (response) => { resolve(response.otherServiceUrl); @@ -35,6 +35,19 @@ async function getHuggingfaceApiKey() { } +function removeOpenaiOutput(suggestion) { + let outPutIndex = suggestion.indexOf("\n\n§ Output") + + if (outPutIndex == -1){ + let outPutIndex = suggestion.indexOf("\n\n# Output") + + return outPutIndex == -1 ? suggestion:suggestion.substring(0, outPutIndex) + }else{ + return suggestion.substring(0, outPutIndex) + } + +} + // Function to send request to OpenAI API async function sendToOpenAI(prompt) { const apiKey = await getOpenAIKey(); @@ -70,18 +83,23 @@ async function sendToOpenAI(prompt) { // Remove invisible characters suggestion = suggestion.replace(/\u200B/g, ''); - // This is an example of a possible return: " print('Hello World!')\n\nhello_world()\n\n§ Output… : ['Hello World!\\n']\n\n \n§ Markdown\n\n### Exercise" - const outPutIndex = suggestion.indexOf("\n\n§ Output") - if(outPutIndex == -1){ - return suggestion - }else{ - return suggestion.substring(0,outPutIndex) + return removeOpenaiOutput(suggestion) +} + + +function removeJupyterOutput(str) { + const jupyterOutput = ''; + + if (str.endsWith(jupyterOutput)) { + return str.slice(0, -jupyterOutput.length); } + return str; } -async function sendToOtherService(code) { - const url = await getOtherServiceUrl(); + +async function sendToBigcode(code) { + const url = await getBigcodeServiceUrl(); const token = await getHuggingfaceApiKey(); if (!url) { @@ -117,19 +135,20 @@ async function sendToOtherService(code) { async function getCodeCompletion(code) { const checked = await getChecked(); + // If the user has not selected openai or bigcode if (!checked) { alert("The request method is not selected."); return; } - if (checked == "openaiApiKey") { - return await sendToOpenAI(code) - } else if (checked == "otherService") { - return await sendToOtherService(code) + switch(checked){ + case "openaiApiKey": return await sendToOpenAI(code); + case "otherService": return await sendToBigcode(code); + default: return "" } -} +} // Code to be filled in after request completion @@ -167,176 +186,227 @@ function insertSuggestion(suggestion) { } +const getActiveCellPointerCode = (activeCell, cellIndex) => { + let cellContent = [] -const getActiveCellPointerCode = (activeCell) => { - let leftContext = "" - let rightContext = "" - - // get cursor element - const cursorElement = activeCell.querySelector('div.CodeMirror-cursor') + // get cursor element + const cursorElement = activeCell.querySelector('div.CodeMirror-cursor') - const style = window.getComputedStyle(cursorElement); + const style = window.getComputedStyle(cursorElement); - // 指针所在位置的偏移量 - const cursorOffsetLeft = Math.round(parseFloat(style.getPropertyValue('left'))) + // 指针所在位置的偏移量 + const cursorOffsetLeft = Math.round(parseFloat(style.getPropertyValue('left'))) - // Which line - const lineIndex = Math.round(parseFloat(style.getPropertyValue('top')) / 17) - - // Obtain element for all line - const linesElement = activeCell.getElementsByClassName('CodeMirror-line') - // code dom element length in active line - const codeElementWdth = linesElement[lineIndex].querySelector("span").offsetWidth - - // Determine whether the pointer is at the end of a line, Because there is a left marring, so -4, but due to precision issues so -3 - if(cursorOffsetLeft - 3 < codeElementWdth){ - return [null, null] - } + // Which line + const lineIndex = Math.round(parseFloat(style.getPropertyValue('top')) / 17) - for (let i = 0; i < linesElement.length; i++) { + // Obtain element for all line + const linesElement = activeCell.getElementsByClassName('CodeMirror-line') + // code dom element length in active line + const codeElementWdth = linesElement[lineIndex].querySelector("span").offsetWidth - if(i < lineIndex) { - leftContext += linesElement[i].textContent + "\n" - }else if(i == lineIndex){ - leftContext += linesElement[i].textContent - }else{ - if(i == linesElement.length-1){ - rightContext += linesElement[i].textContent - }else{ - rightContext += linesElement[i].textContent + "\n" - } - } + // Determine whether the pointer is at the end of a line, Because there is a left marring, so -4, but due to precision issues so -3 + if (cursorOffsetLeft - 3 < codeElementWdth) { + return [null, null] + } + for (let i = 0; i < linesElement.length; i++) { + if (i == lineIndex) { + cellContent.push({ + "content": linesElement[i].textContent, + "cellIndex": cellIndex, + "isCursor": true, + "type": "code", + "lineIndex": i + }) + } else { + cellContent.push({ + "content": linesElement[i].textContent, + "cellIndex": cellIndex, + "isCursor": false, + "type": "code", + "lineIndex": i + }) } - - return [leftContext, rightContext] + } + const outputElement = activeCell.querySelector(`.${currctJupyterModel.requiredClassName.output}`); + if (outputElement && currentCellType == "code") { + cellContent.push({ + "content": linesElement[i].textContent, + "cellIndex": cellIndex, + "isCursor": false, + "type": "output", + "lineIndex": i + }) + } + return cellContent } -function getCellContentTextRequiredForOpenAI(activeCell) { - const cellElements = Array.from(document.querySelectorAll(`.${currctJupyterModel.requiredClassName.cell}`)); - const activeCellIndex = cellElements.findIndex(cell => cell.contains(activeCell)); - // Check if there are at least 3 cells before the active cell - let codeContent = ""; - - // LeftContext refers to the left side of the pointer, and vice versa, If both are null, it is determined that the pointer is not at the far right - const [leftContext, rightContext] = getActiveCellPointerCode(activeCell) - - if(!leftContext){ - return null - } +async function getCellContentText(activeCell){ + const result = await getChecked() - // Iterate through the last 3 cells before the active cell - const startIndex = 0; - for (let i = startIndex; i <= activeCellIndex; i++) { - if(i == activeCellIndex){ - codeContent += leftContext - break - }else{ - const cellElement = cellElements[i]; - if (cellElement.classList.contains(currctJupyterModel.requiredClassName.verify)) { - codeContent += extractTextFromCodeCell(cellElement); - } - } - codeContent += "\n" + if(result == "openaiApiKey"){ + return getCellContentTextRequiredForOpenAI(activeCell) + }else{ + return getCellContentTextRequiredForBigCode(activeCell) } - - return codeContent; } -function getCellContentTextRequiredForBigCode(activeCell) { - const cellElements = Array.from(document.querySelectorAll(`.${currctJupyterModel.requiredClassName.cell}`)); - const activeCellIndex = cellElements.findIndex(cell => cell.contains(activeCell)); - // Check if there are at least 3 cells before the active cell - let combinedContent = ""; - - // in active cell, LeftContext refers to the left side of the pointer, and vice versa, If both are null, it is determined that the pointer is not at the far right - const [leftContext, rightContext] = getActiveCellPointerCode(activeCell) - - if(!leftContext && !rightContext){ - return null - } +/* + Obtain information about the code in the cell and return it by line + Params: + cellElement: cell dom element + cellIndex: index corresponding to the cell in the context - TODO: "The following code needs to add 'leftContext' and 'rightContext'" - // Iterate through the last 3 cells before the active cell - const startIndex = activeCellIndex - 3 < 0 ? 0 : activeCellIndex - 3; + Return: + Example: + [{"content": def hello_world():,"cellIndex": 0,"isCursor": false,"type": "code","lineIndex": 0}, ...] + - content: content of a line + - cellIndex: index corresponding to the cell in the context + - isCursor: is the mouse pointer in this line + - type: code | text(markdown) | output + - lineIndex: line number in this cell + +*/ +const getCellCode = (cellElement, cellIndex) => { + const cellContent = [] + + if (cellElement.classList.contains(currctJupyterModel.requiredClassName.verify)) { + const codeLines = cellElement.querySelectorAll('.CodeMirror-line') + for (let i = 0; i < codeLines.length; i++) { + cellContent.push({ + "content": codeLines[i].textContent, + "cellIndex": cellIndex, + "isCursor": false, + "type": "code", + "lineIndex": i + }) + } - for (let i = startIndex; i <= activeCellIndex; i++) { - const cellElement = cellElements[i]; + const outputElement = cellElement.querySelector(`.${currctJupyterModel.requiredClassName.output}`); + if (outputElement && outputElement.textContent) { + cellContent.push({ + "content": outputElement.textContent, + "cellIndex": cellIndex, + "isCursor": false, + "type": "output", + "lineIndex": 0 + }) + } - if (cellElement.classList.contains(currctJupyterModel.requiredClassName.verify)) { - const code = extractTextFromCodeCell(cellElement); - - combinedContent += `${code}`; - const outputElement = cellElement.querySelector(`.${currctJupyterModel.requiredClassName.output}`); - if (outputElement) { - if (i !== activeCellIndex) { - combinedContent += ``; - combinedContent += outputElement.textContent; - } - } - } else if (cellElement.classList.contains(currctJupyterModel.requiredClassName.text)) { - const text = extractTextFromTextCell(cellElement); - combinedContent += `${text}`; + } else if (cellElement.classList.contains(currctJupyterModel.requiredClassName.text)) { + const textLines = cellElement.querySelectorAll(`.${currctJupyterModel.requiredClassName.textOutput} p`) + + for (let i = 0; i < textLines.length; i++) { + cellContent.push({ + "content": textLines[i].textContent, + "cellIndex": cellIndex, + "isCursor": false, + "type": "text", + "lineIndex": i + }) } + } else { + return [] } - return combinedContent; + return cellContent; } +/* + Get Cell Context, returns a total of 5 cell, symmetrically based on the cell of the current focus + + Return: + Example: + [{"content": def hello_world():,"cellIndex": 0,"isCursor": false,"type": "code","lineIndex": 0}, ...] + - content: content of a line + - cellIndex: index corresponding to the cell in the context + - isCursor: is the mouse pointer in this line + - type: code | text(markdown) | output + - lineIndex: line number in this cell + +*/ +const getActiveContext = () => { + // Obtain the current input box (cell) from the Textarea of the current input box + const activeCell = document.activeElement.parentElement.parentElement; -async function getCellContentText(activeCell){ - const result = await getChecked() - - if(result == "openaiApiKey"){ - return getCellContentTextRequiredForOpenAI(activeCell) - }else{ - return getCellContentTextRequiredForBigCode(activeCell) + const cellElements = Array.from(document.querySelectorAll(`.${currctJupyterModel.requiredClassName.cell}`)); + const activeCellIndex = cellElements.findIndex(cell => cell.contains(activeCell)); + let context = [] + for (let i = 0; i < cellElements.length; i++) { + if (i == activeCellIndex) { + const activeCellContent = getActiveCellPointerCode(activeCell, i) + context = [...context, ...activeCellContent] + } else if (i >= activeCellIndex - 2 || i <= activeCellIndex + 2) { + const cellContent = getCellCode(cellElements[i], i) + context = [...context, ...cellContent] + } } + console.log(context); + return context } +// prompt required by openai +function getCellContentTextRequiredForOpenAI() { + const context = getActiveContext() + let code = "" -function extractTextFromCodeCell(cell){ - const codeMirrorLines = cell.querySelectorAll('.CodeMirror-code pre'); + for(let index = 0; index < context.length; index++){ + const lineInformation = context[index] - const content = []; + if(lineInformation.type == "code"){ + if(index == context.length - 1 || lineInformation.isCursor){ + return code + lineInformation.content + }else{ + code += lineInformation.content += "\n" + } + } - codeMirrorLines.forEach((line) => { - content.push(line.textContent); - }); - const content_str = content.join('\n'); + } - return content_str; } +function getBigcodeFormattPrefix(typeStr){ + switch(typeStr){ + case "code": return ""; + case "text": return ""; + case "output": return ""; + } +} -function extractTextFromTextCell(cell) { - const codeMirrorLines = cell.querySelectorAll(`.${currctJupyterModel.requiredClassName.textOutput} p`); - - const content = []; - - codeMirrorLines.forEach((line) => { - content.push(line.textContent); - }); - const content_str = content.join('\n'); +// prompt required by bigcode +function getCellContentTextRequiredForBigCode() { + const context = getActiveContext() - return content_str; -} + let code = "" + let cellCode = getBigcodeFormattPrefix(context[0].type) + + for(let index = 0; index < context.length; index++){ + const lineInformation = context[index] + if(index == context.length - 1 || lineInformation.isCursor){ + code += cellCode + lineInformation.content + break + } + const nextLineInformation = context[index + 1] -function removeJupyterOutput(str) { - const jupyterOutput = ''; + if(lineInformation.cellIndex != nextLineInformation.cellIndex || lineInformation.type != nextLineInformation.type){ + code += cellCode + lineInformation.content + cellCode = getBigcodeFormattPrefix(nextLineInformation.type) + }else{ + cellCode += lineInformation.content + "\n" + } - if (str.endsWith(jupyterOutput)) { - return str.slice(0, -jupyterOutput.length); } - return str; + return code += "" } + // left animation css const loadCss = ` .before-content:before { @@ -453,6 +523,7 @@ const addFillCodeKeyListener = (event) => { } }; + const mainProcess = async () => { //Obtain the Textarea of the current input box const activeTextarea = document.activeElement; @@ -473,6 +544,7 @@ const mainProcess = async () => { isRequestInProgress = true let suggestion; + // Deal with a series of problems such as network try{ suggestion = await getCodeCompletion(code) @@ -493,6 +565,7 @@ const mainProcess = async () => { return } + if (suggestion) { clearInterval(animationInterval) // cancel animation element @@ -515,7 +588,7 @@ const montedEventListener = () => { if (event.ctrlKey && event.code === 'Space') { // Block default events event.preventDefault(); - + if (isRequestInProgress || isRequestSuccessful) { return }