From 7e4624aae39dee40cc818cb22ebcf5a0be86855a Mon Sep 17 00:00:00 2001 From: wadhia-yash Date: Tue, 20 Feb 2024 13:22:35 +0530 Subject: [PATCH 01/17] feat(Slash commands execution): we can use '/' to invoke commands and added each set of different functionalities for commands execution --- media/onboarding/onboarding.css | 30 +++--- media/onboarding/onboarding.html | 14 +-- media/onboarding/onboarding.js | 175 +++++++++++++++++++++++-------- 3 files changed, 157 insertions(+), 62 deletions(-) diff --git a/media/onboarding/onboarding.css b/media/onboarding/onboarding.css index 885e7c09..c0e38876 100644 --- a/media/onboarding/onboarding.css +++ b/media/onboarding/onboarding.css @@ -47,14 +47,14 @@ ul { } .menu { - position: absolute; z-index: 99; - border-radius: 7px; background-color: var(--vscode-activityBar-background); + border-top-left-radius: 7px; + border-top-right-radius: 7px; } .menu-item { - cursor: default; + cursor: pointer; padding: .5rem 1rem; border-radius: 7px; } @@ -65,8 +65,8 @@ ul { border-radius: 7px; } -.menu-item:hover:not(.selected) { - background-color: #fafafa; +.menu-item:hover { + background-color: var(--vscode-editor-background); border-radius: 7px; } @@ -163,15 +163,19 @@ p[contenteditable="true"] { align-items: center; border-radius: 7px; } -.close-icon { - display: none; - position: absolute; - top: -5px; - right: -5px; -} -.chips:hover .close-icon { - display: inline-block; + +.chips-reference { + color: #ea580c; + outline: .5px solid #ea580c; + padding: 3px 5px; + margin: 2px; + position: relative; + font-size: 11px; + display: inline-flex; + align-items: center; + border-radius: 7px; } + #agents{ color: var(--vscode-input-placeholderForeground); } diff --git a/media/onboarding/onboarding.html b/media/onboarding/onboarding.html index 4dbfed33..bb9a4205 100644 --- a/media/onboarding/onboarding.html +++ b/media/onboarding/onboarding.html @@ -6,7 +6,7 @@ - + @@ -63,8 +63,8 @@
-
+
@@ -72,10 +72,14 @@

# Ask FlutterGPT

-

@

+

+ / +

-

- -

- +
diff --git a/media/onboarding/onboarding.js b/media/onboarding/onboarding.js index cbd5e64c..2d390743 100644 --- a/media/onboarding/onboarding.js +++ b/media/onboarding/onboarding.js @@ -178,7 +178,9 @@ const commandsExecution = { setCaretToEnd(textRefactor); - adjustHeight(); + setTimeout(() => { + adjustHeight(); + }, 0); } } }; @@ -408,6 +410,7 @@ class CommandDeck { this.menuRef.style.left = this.left + 'px'; this.menuRef.style.top = (this.top - this.menuRef.offsetHeight - caretHeight) + 'px'; this.menuRef.innerHTML = ''; + this.menuRef.classList.add("p-1"); this.options.forEach((option, idx) => { const trigger = this.ref.textContent[this.triggerIdx]; From 3824e08742a6c185332d28e3a0967a9e88c34512 Mon Sep 17 00:00:00 2001 From: wadhia-yash Date: Fri, 23 Feb 2024 14:13:41 +0530 Subject: [PATCH 03/17] fix: show focus on refactor command and ui issue fixes --- media/onboarding/onboarding.css | 45 ++++++++++-- media/onboarding/onboarding.html | 8 ++- media/onboarding/onboarding.js | 115 +++++++++++++++++++++++++++++-- 3 files changed, 152 insertions(+), 16 deletions(-) diff --git a/media/onboarding/onboarding.css b/media/onboarding/onboarding.css index c0e38876..dc9b56ce 100644 --- a/media/onboarding/onboarding.css +++ b/media/onboarding/onboarding.css @@ -7,6 +7,7 @@ body { justify-content: flex-end; height: 100vh; } + ol, ul { padding-left: 17px !important; @@ -20,29 +21,30 @@ ol { ul { list-style-type: disc !important; } -.list-section > ul { + +.list-section>ul { list-style-type: none; margin: 0 !important; padding: 0 !important; } -.list-section > ul > li { +.list-section>ul>li { position: relative; display: flex; align-items: center; font-weight: bold; } -.list-section > ul > li::before { +.list-section>ul>li::before { padding-right: 3px; margin-top: 3px; } -.list-section > ul > li.valid::before { +.list-section>ul>li.valid::before { content: url("data:image/svg+xml,%3Csvg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6 0C2.685 0 0 2.685 0 6C0 9.315 2.685 12 6 12C9.315 12 12 9.315 12 6C12 2.685 9.315 0 6 0ZM9 2.67L10.08 3.75L5.25 8.58L2.67 6L3.75 4.92L5.25 6.42L9 2.67Z' fill='%233079D8'/%3E%3C/svg%3E"); } -.list-section > ul > li.invalid::before { +.list-section>ul>li.invalid::before { content: url("data:image/svg+xml,%3Csvg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6 0C2.685 0 0 2.685 0 6C0 9.315 2.685 12 6 12C9.315 12 12 9.315 12 6C12 2.685 9.315 0 6 0ZM3.75 2.67L6 4.92L8.25 2.67L9.33 3.75L7.08 6L9.33 8.25L8.25 9.33L6 7.08L3.75 9.33L2.67 8.25L4.92 6L2.67 3.75L3.75 2.67Z' fill='%23CB4343'/%3E%3C/svg%3E"); } @@ -97,19 +99,23 @@ ul { left: 50%; transform: translateX(-50%); } + p[contenteditable="true"] { outline: none; max-height: 270px; overflow: auto; white-space: pre-wrap; } + #text-input { color: var(--vscode-editor-foreground); } + .user-message { background-color: var(--vscode-panel-background); color: var(--vscode-sideBar-foreground); } + .typing-loader { width: 4px; height: 4px; @@ -117,6 +123,7 @@ p[contenteditable="true"] { animation: loading 1s linear infinite alternate; margin-bottom: 4px; } + @keyframes loading { 0% { background-color: var(--vscode-progressBar-background); @@ -147,10 +154,12 @@ p[contenteditable="true"] { .placeholder { color: var(--vscode-input-placeholderForeground) !important; } + .disabled { pointer-events: none; opacity: .9; } + .chips { color: var(--vscode-editor-foreground); outline: .5px solid var(--vscode-editor-foreground); @@ -176,16 +185,19 @@ p[contenteditable="true"] { border-radius: 7px; } -#agents{ +#agents { color: var(--vscode-input-placeholderForeground); } + #bottom-text-input-container { background-color: var(--vscode-editor-background); border-color: var(--vscode-sideBar-foreground); } + #text-input-container { border-color: var(--vscode-editor-foreground); } + #clear-chat-button { position: fixed; top: 8px; @@ -198,4 +210,25 @@ p[contenteditable="true"] { opacity: 0.8; cursor: pointer; z-index: 98; +} + +.tippy-box[data-theme~='flutter-blue'] { + background-color: #287CEB; + color: white; +} + +.tippy-box[data-theme~='flutter-blue'][data-placement^='top']>.tippy-arrow::before { + border-top-color: #287CEB; +} + +.tippy-box[data-theme~='flutter-blue'][data-placement^='bottom']>.tippy-arrow::before { + border-bottom-color: #287CEB; +} + +.tippy-box[data-theme~='flutter-blue'][data-placement^='left']>.tippy-arrow::before { + border-left-color: #287CEB; +} + +.tippy-box[data-theme~='flutter-blue'][data-placement^='right']>.tippy-arrow::before { + border-right-color: #287CEB; } \ No newline at end of file diff --git a/media/onboarding/onboarding.html b/media/onboarding/onboarding.html index d3cfe742..ba8e9477 100644 --- a/media/onboarding/onboarding.html +++ b/media/onboarding/onboarding.html @@ -77,11 +77,11 @@ @

+ title="Slash commands" id="slash-commands"> /

+ title="Use 'Add to Reference' in menu to attach selected code to chat" id="dart-add-reference"> @@ -134,7 +134,7 @@ -

+ + diff --git a/media/onboarding/onboarding.js b/media/onboarding/onboarding.js index 2d390743..883c7a1f 100644 --- a/media/onboarding/onboarding.js +++ b/media/onboarding/onboarding.js @@ -160,27 +160,105 @@ const commandsExecution = { 'exe': (input) => { commandEnable = true; input.textContent = ''; + + let isChipsFocused = false; + let isTextRefactorInputFocused = false; + const command = document.createElement('span'); - const textRefactor = document.createElement('span'); + const textRefactorInput = document.createElement('span'); const refactor = document.createElement('span'); + const referenceText = document.createElement('span'); + const refactorTextNode = document.createTextNode('/refactor\u00A0'); + const referenceIdSpan = document.createElement('span'); + + referenceIdSpan.id = "reference-id"; + referenceIdSpan.contentEditable = "false"; + referenceIdSpan.appendChild(document.createTextNode('\u00A0')); + + referenceText.id = "add-reference-text"; + referenceText.contentEditable = "false"; + referenceText.classList.add("bg-black", "text-white", "mb-1", "px-[7px]", "inline-block", "border", "cursor-pointer", "border-transparent"); + referenceText.textContent = "Add reference"; + referenceText.addEventListener("click", function (event) { + isChipsFocused = !isChipsFocused; + isChipsFocused ? referenceText.classList.add("border-orange-500") : referenceText.classList.remove("border-orange-500"); + if (isChipsFocused) { + isTextRefactorInputFocused = false; + } + }); - command.innerHTML = `/refactor Add reference `; + command.appendChild(refactorTextNode); + command.appendChild(referenceText); + command.appendChild(referenceIdSpan); + refactor.id = "text-refactor-container"; refactor.innerHTML = `Text to refactor`; refactor.classList.add("inline-block"); - textRefactor.contentEditable = "true"; - textRefactor.classList.add("bg-slate-700", "px-2"); + textRefactorInput.id = "text-refactor-input"; + textRefactorInput.contentEditable = "true"; + textRefactorInput.tabIndex = "0"; + textRefactorInput.classList.add("bg-slate-700", "px-2"); + textRefactorInput.addEventListener("focus", function(event) { + if (isTextRefactorInputFocused) { + isChipsFocused = false; + } + referenceText.classList.remove("border-orange-500"); + isTextRefactorInputFocused = !isTextRefactorInputFocused; + }); + + textRefactorInput.appendChild(document.createTextNode("\u00A0")); - refactor.appendChild(textRefactor); + refactor.appendChild(textRefactorInput); command.appendChild(refactor); input.appendChild(command); - setCaretToEnd(textRefactor); + setCaretToEnd(textRefactorInput); + + tippy('#add-reference-text', { + content: "Add reference", + theme: "flutter-blue" + }); + + input.addEventListener('keydown', function(event) { + let keyCaught = false; + switch (event.key) { + case "Tab": + if (isTextRefactorInputFocused) { + isTextRefactorInputFocused = false; + isChipsFocused = true; + referenceText.classList.add("border-orange-500"); + textRefactorInput.blur(); + } else { + isChipsFocused = false; + isTextRefactorInputFocused = true; + textRefactorInput.focus(); + setCaretToEnd(textRefactorInput); + } + keyCaught = true; + break; + + case "Backspace": + if (textRefactorInput.textContent.trim() === "" && textRefactorInput.innerText.trim() === "") { + // Clear the text + input.removeChild(command); + setTimeout(() => { + input.focus(); + adjustHeight(); + }, 0); + } + break; + } + if (keyCaught) { + event.preventDefault(); + } + }); setTimeout(() => { adjustHeight(); + textRefactorInput.focus(); }, 0); + } } }; @@ -475,8 +553,29 @@ class CommandDeck { textInput.addEventListener("dragover", dragOver); textInput.addEventListener("drop", drop); + //adding tooltips to the elements + addToolTipsById(); + })(); +function addToolTipsById() { + + tippy('#agents', { + content: "Specialized agents", + theme: "flutter-blue" + }); + + tippy('#slash-commands', { + content: "Slash commands", + theme: "flutter-blue" + }); + + tippy('#dart-add-reference', { + content: "Use 'Add to Reference' in menu to attach selected code to chat", + theme: "flutter-blue" + }); +} + function handleSubmit(event) { const resolveFn = async (query, type) => { // Array to store possible matches @@ -716,7 +815,9 @@ function readTriggeredMessage() { case 'addToReference': removePlaceholder(); createReferenceChips(JSON.parse(message.value)); - adjustHeight(); + setTimeout(() => + adjustHeight(), + 0); break; } }); From ea37f11266284798610b5ef02f12442edc6bf135 Mon Sep 17 00:00:00 2001 From: wadhia-yash Date: Sat, 24 Feb 2024 12:30:23 +0530 Subject: [PATCH 04/17] feat(Dash AI Message Tile): Added new message role when dash is present it will change the message bubble/tile behaviour --- .DS_Store | Bin 0 -> 6148 bytes media/onboarding/onboarding.js | 37 ++++++++++++++++++++++------ package-lock.json | 22 ++++++++--------- resources/.DS_Store | Bin 0 -> 6148 bytes src/providers/chat_view_provider.ts | 17 ++++++++----- 5 files changed, 52 insertions(+), 24 deletions(-) create mode 100644 .DS_Store create mode 100644 resources/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..59a9184f2a6da364cde3ce5294d004b2833ff26f GIT binary patch literal 6148 zcmeHK%}T>S5Z<+|O({YS3Oz1(E!bKqikA@U3mDOZN=-=6V45vWY7eE5v%Zi|;`2DO zyMY#iM-e*%yWi~m>}Ed5{xHV4I}iJe*^Ds@8X`xfLeO04+A+b1T+QJJi)pQRquM&4!|OBp>xd|z<68nz7<3F48o>j?bt<4v z<>raObvoFEiE|7V8g)A3YGs(mtXw``xLO_TLWMK#Xr!JPAO=AGL?_~ z%@i6D1H`~TV}RF3-pGZb%-Q;_JUnX!v`1(t7+0VI0{X@!00y{^bd*#31?mvz7%Vj6 TENEBhfOHX1giuEe`~m}CU;9d@ literal 0 HcmV?d00001 diff --git a/media/onboarding/onboarding.js b/media/onboarding/onboarding.js index 883c7a1f..34160644 100644 --- a/media/onboarding/onboarding.js +++ b/media/onboarding/onboarding.js @@ -993,13 +993,14 @@ function displayMessages() { conversationHistory.forEach((message) => { const messageElement = document.createElement("div"); - const userElement = document.createElement("p"); + const roleElement = document.createElement("p"); const contentElement = document.createElement("p"); + const buttonContainer = document.createElement("p"); if (message.role === "model") { modelCount++; - userElement.innerHTML = `
${flutterGPT}FlutterGPT
`; - userElement.classList.add("block", "w-full", "px-2.5", "py-1.5", "bg-[#3079D8]/[.2]"); + roleElement.innerHTML = `
${flutterGPT}FlutterGPT
`; + roleElement.classList.add("block", "w-full", "px-2.5", "py-1.5", "bg-[#3079D8]/[.2]"); contentElement.classList.add("text-sm", "block", "px-2.5", "py-1.5", "pt-2", "break-words", "leading-relaxed", "bg-[#3079D8]/[.2]"); contentElement.innerHTML = markdownToPlain(message.parts); if (modelCount === 1 && !stepOneCompleted) { @@ -1018,21 +1019,43 @@ function displayMessages() { onboardingText.classList.add("hidden"); tryFlutterText.classList.add("hidden"); } - } else { - userElement.innerHTML = "You"; - userElement.classList.add("block", "w-full", "px-2.5", "py-1.5", "user-message"); + } else if (message.role === "user") { + roleElement.innerHTML = "You"; + roleElement.classList.add("block", "w-full", "px-2.5", "py-1.5", "user-message"); contentElement.classList.add("text-sm", "block", "w-full", "px-2.5", "py-1.5", "break-words", "user-message"); contentElement.innerHTML = markdownToPlain(message.parts); + } else if (message.role === "dash") { + //UI implementation + roleElement.innerHTML = "Dash AI"; + roleElement.classList.add("block", "w-full", "px-2.5", "py-1.5", "bg-orange-500"); + contentElement.classList.add("text-sm", "block", "w-full", "px-2.5", "py-1.5", "break-words", "bg-orange-500", "text-white"); + contentElement.innerHTML = markdownToPlain(message.parts); + buttonContainer.classList.add("inline-flex", "w-full", "px-2.5", "py-1.5", "bg-orange-500"); + message?.buttons.forEach((type) => { + const button = document.createElement("div"); + button.classList.add("px-2.5", "py-1.5", "bg-black", "text-xs", "text-white", "uppercase", "mr-1", "rounded-[2px]", "cursor-pointer"); + button.textContent = type; + button.addEventListener("click", () => handleButtonEvent(message.agent, message.data, message.messageId, type)); + buttonContainer.appendChild(button); + }); } messageElement.classList.add("mt-1"); - messageElement.appendChild(userElement); + messageElement.appendChild(roleElement); messageElement.appendChild(contentElement); + messageElement.appendChild(buttonContainer); responseContainer.appendChild(messageElement); scrollToBottom(); }); setResponse(); } +function handleButtonEvent(agent, data, messageId, buttonType) { + vscode.postMessage({ + type: "dashResponse", + value: JSON.stringify({agent, data, messageId, buttonType}) + }); +} + function setResponse() { const preCodeBlocks = document.querySelectorAll("code"); preCodeBlocks.forEach((_preCodeBlock) => { diff --git a/package-lock.json b/package-lock.json index 3aae5564..f0e7b6b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "fluttergpt", - "version": "0.2.0", + "version": "0.2.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "fluttergpt", - "version": "0.2.0", + "version": "0.2.3", "dependencies": { "@google/generative-ai": "^0.1.1", "@vscode/extension-telemetry": "^0.8.1", @@ -1532,15 +1532,6 @@ "semver": "bin/semver" } }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -2643,6 +2634,15 @@ "url": "https://opencollective.com/mochajs" } }, + "node_modules/mocha/node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/mocha/node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", diff --git a/resources/.DS_Store b/resources/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c01421a75298b8ce8c754191f6778d80ae6ebbb5 GIT binary patch literal 6148 zcmeHK%}T>S5Z-O0O({YS3Oz1(E!bKqikDF93mDOZN=-=7(3mYrY7V84v%Zi|;`2DO zyAg}^Dq?3~_nY6{><8H&#uy*Y!yaP}W6Xwz$Wd7%=w2JDnPfzcV+2Ju3t}07{bpi+ z9q`*N7BZiuZ2I;4gIOG9dAIw{YjtaDyJ0u%mVM_v%EHV2e4Y*c^aiaC6iPYMBxNdZm**xl*LfalQ2`Yo(|YeyE%5+i$(vq+Yv{D-m)VWCx@u_ z2g_yC-rGMoy%SSAnPtMaQnLSldzAO?tm&1Jxx z33h99DWH`T1H`}&4B-ACpdq>jOO0ylfDW(E7`G5nK*zTPqA=(hEHy#|gzHj3UCPZ9 zgX?ne3zO#>EH&zK#?{O)j+wc7yl^!;_=QSm+|@`uF+dEgGEmp1gXjM_{4#4F`Ku*l z5d*})KVyJfBX2Z>MVYhp+w$ = []; - private _privateConversationHistory: Array<{ role: string, parts: string }> = []; + private _publicConversationHistory: Array<{ role: string, parts: string, messageId?: string, data?: any }> = []; + private _privateConversationHistory: Array<{ role: string, parts: string, messageId?: string, data?: any }> = []; private async getResponse(prompt: string) { if (!this._view) { From 3e21e46461f67941e96e5be6f02975b941c6faaf Mon Sep 17 00:00:00 2001 From: Keval Prajapati Date: Sun, 25 Feb 2024 12:53:28 +0530 Subject: [PATCH 05/17] WIP: refactor action from command dash --- media/onboarding/onboarding.js | 23 +++-- src/action-managers/refactor-agent.ts | 19 ++++ src/extension.ts | 10 +- src/providers/chat_view_provider.ts | 97 ++++++++++++------- .../refactor/refactor_from_instructions.ts | 14 +-- src/tools/reference/add_reference.ts | 61 +++++++----- src/utilities/command-manager.ts | 4 +- 7 files changed, 146 insertions(+), 82 deletions(-) create mode 100644 src/action-managers/refactor-agent.ts diff --git a/media/onboarding/onboarding.js b/media/onboarding/onboarding.js index 2d390743..3bc02014 100644 --- a/media/onboarding/onboarding.js +++ b/media/onboarding/onboarding.js @@ -534,16 +534,21 @@ function handleSubmit(event) { if (event.key === "Enter" && !event.shiftKey && commandDeck.menuRef?.hidden) { event.preventDefault(); let prompt = textInput.textContent; - - for (const chip in chipsData) { - if (prompt.includes(chip)) { - prompt = prompt.replace(chip, chipsData[chip].referenceContent); - } - } - + // for (const chip in chipsData) { + // if (prompt.includes(chip)) { + // prompt = prompt.replace(chip, chipsData[chip].referenceContent); + // } + // } + console.log({ + 'message': prompt, + 'chipsData': chipsData, + }); vscode.postMessage({ - type: "prompt", - value: prompt, + type: "action", + value: JSON.stringify({ + 'message': prompt, + 'chipsData': chipsData, + }), }); textInput.textContent = ""; diff --git a/src/action-managers/refactor-agent.ts b/src/action-managers/refactor-agent.ts new file mode 100644 index 00000000..2e5dd157 --- /dev/null +++ b/src/action-managers/refactor-agent.ts @@ -0,0 +1,19 @@ +import * as vscode from 'vscode'; +import { GeminiRepository } from '../repository/gemini-repository'; +import { refactorCode } from '../tools/refactor/refactor_from_instructions'; +import { ILspAnalyzer } from '../shared/types/LspAnalyzer'; +import { dartCodeExtensionIdentifier } from '../shared/types/constants'; +export class RefactorActionManager { + + constructor() { } + + static async handleRequest(chipsData: any, data: any, aiRepo: GeminiRepository, context: vscode.ExtensionContext, analyzer: ILspAnalyzer) { + const chip = chipsData.next().value; + const editorUri = chip.referenceData.editor; + const editor = vscode.window.visibleTextEditors.find(e => e.document.uri.toString() === editorUri); + const selection = chip.referenceData.selection; + const range: vscode.Range = new vscode.Range(new vscode.Position(selection.start.line, selection.start.character), new vscode.Position(selection.end.line, selection.end.character)); + await refactorCode(aiRepo!, context.globalState, range, analyzer, undefined, context, editor, data.message); + return { role: "dash", parts: "", messageId: "", data: chip, buttons: ["accept", "decline"], agent: "refactor", }; + } +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 82423572..8570238d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -40,7 +40,7 @@ export async function activate(context: vscode.ExtensionContext) { const apiKey = config.get('apiKey'); if (!apiKey || isOldOpenAIKey(apiKey)) { initWebview(context); - + // Prompt the user to update their settings vscode.window.showErrorMessage( 'Please update your API key to Gemini in the settings.', @@ -71,7 +71,7 @@ export async function activate(context: vscode.ExtensionContext) { } const analyzer: ILspAnalyzer = dartExt?.exports._privateApi.analyzer; - + try { let geminiRepo = initGemini(); initFlutterExtension(context, geminiRepo, analyzer); @@ -99,9 +99,9 @@ function isOldOpenAIKey(apiKey: string): boolean { return apiKey.startsWith('sk-'); } -function initWebview(context: vscode.ExtensionContext, geminiRepo?: GeminiRepository) { +function initWebview(context: vscode.ExtensionContext, geminiRepo?: GeminiRepository, analyzer?: ILspAnalyzer) { // Create a new FlutterGPTViewProvider instance and register it with the extension's context - const chatProvider = new FlutterGPTViewProvider(context.extensionUri, context, geminiRepo); + const chatProvider = new FlutterGPTViewProvider(context.extensionUri, context, geminiRepo, analyzer); // Register the provider with the extension's context context.subscriptions.push( vscode.window.registerWebviewViewProvider(FlutterGPTViewProvider.viewType, chatProvider, @@ -122,7 +122,7 @@ function initFlutterExtension(context: vscode.ExtensionContext, geminiRepo: Gemi const hoverProvider = new AIHoverProvider(geminiRepo, analyzer); context.subscriptions.push(vscode.languages.registerHoverProvider(activeFileFilters, hoverProvider)); - const flutterChatProvider = initWebview(context, geminiRepo); + const flutterChatProvider = initWebview(context, geminiRepo, analyzer); const errorActionProvider = new ErrorCodeActionProvider(analyzer, geminiRepo, context); context.subscriptions.push(vscode.languages.registerCodeActionsProvider(activeFileFilters, errorActionProvider)); diff --git a/src/providers/chat_view_provider.ts b/src/providers/chat_view_provider.ts index 189eb396..2c9a3883 100644 --- a/src/providers/chat_view_provider.ts +++ b/src/providers/chat_view_provider.ts @@ -3,35 +3,40 @@ import * as fs from 'fs'; import { GeminiRepository } from "../repository/gemini-repository"; import { dartCodeExtensionIdentifier } from "../shared/types/constants"; import { logError, logEvent } from "../utilities/telemetry-reporter"; - +import { refactorCode } from "../tools/refactor/refactor_from_instructions"; +import { ILspAnalyzer } from "../shared/types/LspAnalyzer"; +import { RefactorActionManager } from "../action-managers/refactor-agent"; export class FlutterGPTViewProvider implements vscode.WebviewViewProvider { public static readonly viewType = "fluttergpt.chatView"; private _view?: vscode.WebviewView; private _currentMessageNumber = 0; aiRepo?: GeminiRepository; + analyzer?: ILspAnalyzer; // In the constructor, we store the URI of the extension constructor(private readonly _extensionUri: vscode.Uri, private context: vscode.ExtensionContext, aiRepo?: GeminiRepository, + analyzer?: ILspAnalyzer, ) { this.aiRepo = aiRepo; + this.analyzer = analyzer; + } + + // Public method to post a message to the webview + public postMessageToWebview(message: any): void { + if (this._view) { + this._view.webview.postMessage(message); + } } - - // Public method to post a message to the webview - public postMessageToWebview(message: any): void { - if (this._view) { - this._view.webview.postMessage(message); - } - } - - public resolveWebviewView( - webviewView: vscode.WebviewView, - context: vscode.WebviewViewResolveContext, - _token: vscode.CancellationToken, - ) { - this._view = webviewView; + + public resolveWebviewView( + webviewView: vscode.WebviewView, + context: vscode.WebviewViewResolveContext, + _token: vscode.CancellationToken, + ) { + this._view = webviewView; // set options for the webview, allow scripts webviewView.webview.options = { @@ -52,6 +57,12 @@ export class FlutterGPTViewProvider implements vscode.WebviewViewProvider { { break; } + case "action": + { + this.handleAction(data.value); + break; + } + case "prompt": { this.getResponse(data.value); @@ -70,12 +81,12 @@ export class FlutterGPTViewProvider implements vscode.WebviewViewProvider { } }); } - logEvent('merge-code', {from: 'command-deck'}); + logEvent('merge-code', { from: 'command-deck' }); break; } - case "copyCode": + case "copyCode": { - logEvent('copy-code', {from: 'command-deck'}); + logEvent('copy-code', { from: 'command-deck' }); break; } case "clearChat": @@ -85,11 +96,11 @@ export class FlutterGPTViewProvider implements vscode.WebviewViewProvider { } case "validate": { - webviewView.webview.postMessage({type: "showValidationLoader"}); + webviewView.webview.postMessage({ type: "showValidationLoader" }); this.aiRepo = this.initGemini(data.value); await this._validateApiKey(data.value); await this._validateFlutterExtension(); - webviewView.webview.postMessage({type: "hideValidationLoader"}); + webviewView.webview.postMessage({ type: "hideValidationLoader" }); break; } case "updateSettings": @@ -98,36 +109,48 @@ export class FlutterGPTViewProvider implements vscode.WebviewViewProvider { vscode.window.showInformationMessage(`Settings updated: Gemini API Key set`); break; } - case "checkKeyIfExists": - { - this._checkIfKeyExists(); - break; - } + case "checkKeyIfExists": + { + this._checkIfKeyExists(); + break; + } } }); - webviewView.onDidChangeVisibility(() => { - if (webviewView.visible && this._view) { - this._view?.webview.postMessage({ type: 'focusChatInput' }); - } - }); + webviewView.onDidChangeVisibility(() => { + if (webviewView.visible && this._view) { + this._view?.webview.postMessage({ type: 'focusChatInput' }); + } + }); - vscode.window.onDidChangeActiveColorTheme(() => { - webviewView.webview.postMessage({ type: 'updateTheme' }); - }); + vscode.window.onDidChangeActiveColorTheme(() => { + webviewView.webview.postMessage({ type: 'updateTheme' }); + }); - logEvent('new-chat-start', {from: 'command-deck'}); - } + logEvent('new-chat-start', { from: 'command-deck' }); + } private _checkIfKeyExists() { const config = vscode.workspace.getConfiguration('fluttergpt'); const apiKey = config.get('apiKey'); if (apiKey) { - this._view?.webview.postMessage({type: "keyExists"}); + this._view?.webview.postMessage({ type: "keyExists" }); } } + private async handleAction(input: string) { + const data = JSON.parse(input); + const actionType = data.message.startsWith('/') ? data.message.split(' ')[0].substring(1) : ''; + const chipsData = new Map(Object.entries(data.chipsData)).values(); + data.message = data.message.replace(`/${actionType}`, '').trim(); + if (actionType === 'refactor') { + const result = await RefactorActionManager.handleRequest(chipsData, data, this.aiRepo!, this.context, this.analyzer!); + this._publicConversationHistory.push({ role: 'modal', parts: data.message }); + } + } + + private _getHtmlForWebview(webview: vscode.Webview) { const onboardingHtmlPath = vscode.Uri.joinPath(this._extensionUri, 'media', 'onboarding', 'onboarding.html'); const onboardingHtml = fs.readFileSync(onboardingHtmlPath.fsPath, 'utf8'); @@ -256,7 +279,7 @@ export class FlutterGPTViewProvider implements vscode.WebviewViewProvider { this._privateConversationHistory.push({ role: 'model', parts: response }); this._publicConversationHistory.push({ role: 'model', parts: response }); this._view?.webview.postMessage({ type: 'displayMessages', value: this._publicConversationHistory }); - logEvent('follow-up-message', {from: 'command-deck'}); + logEvent('follow-up-message', { from: 'command-deck' }); this._view?.webview.postMessage({ type: 'stepLoader', value: { creatingResultLoader: true } }); this._view?.webview.postMessage({ type: 'addResponse', value: '' }); diff --git a/src/tools/refactor/refactor_from_instructions.ts b/src/tools/refactor/refactor_from_instructions.ts index c76552ac..e39d43ed 100644 --- a/src/tools/refactor/refactor_from_instructions.ts +++ b/src/tools/refactor/refactor_from_instructions.ts @@ -6,10 +6,10 @@ import { ContextualCodeProvider } from '../../utilities/contextual-code'; import { handleDiffViewAndMerge } from '../../utilities/diff-utils'; import { GenerationRepository } from '../../repository/generation-repository'; -export async function refactorCode(generationRepository: GenerationRepository, globalState: vscode.Memento, range: vscode.Range | undefined, analyzer: ILspAnalyzer, elementname: string | undefined, context: vscode.ExtensionContext) { +export async function refactorCode(generationRepository: GenerationRepository, globalState: vscode.Memento, range: vscode.Range | undefined, analyzer: ILspAnalyzer, elementname: string | undefined, context: vscode.ExtensionContext, usedEditor: vscode.TextEditor | undefined, instructions: string | undefined) { logEvent('refactor-code', { 'type': 'refractor' }); try { - const editor = vscode.window.activeTextEditor; + const editor = usedEditor === undefined ? vscode.window.activeTextEditor : usedEditor; if (!editor) { vscode.window.showErrorMessage('No active editor'); return; @@ -40,12 +40,12 @@ export async function refactorCode(generationRepository: GenerationRepository, g // Construct the final string with highlighted selected code const finalString = `${docStart}${highlightStart}${selectedText}${highlightEnd}${docEnd}`; - - const instructions = await vscode.window.showInputBox({ prompt: "Enter refactor instructions" }); if (!instructions) { - return; + instructions = await vscode.window.showInputBox({ prompt: "Enter refactor instructions" }); + if (!instructions) { + return; + } } - let documentRefactoredText = editor.document.getText(); // Get the entire document text await vscode.window.withProgress({ @@ -63,7 +63,7 @@ export async function refactorCode(generationRepository: GenerationRepository, g }, 200); let contextualCode = await new ContextualCodeProvider().getContextualCode(editor.document, editor.selection, analyzer, elementname); - const result = await generationRepository.refactorCode(finalString, contextualCode, instructions, globalState); + const result = await generationRepository.refactorCode(finalString, contextualCode, instructions!, globalState); console.log(result); if (!result) { vscode.window.showErrorMessage('Failed to refactor code. Please try again.'); diff --git a/src/tools/reference/add_reference.ts b/src/tools/reference/add_reference.ts index 19340433..101638ec 100644 --- a/src/tools/reference/add_reference.ts +++ b/src/tools/reference/add_reference.ts @@ -5,27 +5,44 @@ import { logEvent } from '../../utilities/telemetry-reporter'; import { FlutterGPTViewProvider } from '../../providers/chat_view_provider'; export async function addToReference(globalState: vscode.Memento, flutterGPTViewProvider: FlutterGPTViewProvider) { - logEvent('add-to-reference', { 'type': 'reference' }); - - const editor = vscode.window.activeTextEditor; - if (!editor) { - vscode.window.showErrorMessage('Please open a file first.'); - return; - } - - const referenceContent = editor.document.getText(editor.selection); - const startLineNumber = editor.selection.start.line + 1; - const endLineNumber = editor.selection.end.line + 1; - - - const workspaceFolders = vscode.workspace.workspaceFolders; - const fileName = path.basename(editor.document.fileName); - let relativePath = editor.document.fileName; - if (workspaceFolders && workspaceFolders.length > 0) { - const workspaceRoot = workspaceFolders[0].uri.fsPath; - relativePath = path.relative(workspaceRoot, editor.document.fileName); - } - - flutterGPTViewProvider.postMessageToWebview({type: 'addToReference', value: JSON.stringify({relativePath: relativePath.trim(), referenceContent: `\`\n${relativePath.trim()}\n\`\n\`\`\`\n${referenceContent.toString()}\n\`\`\`\n`, startLineNumber, endLineNumber, fileName})}); + logEvent('add-to-reference', { 'type': 'reference' }); + + const editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showErrorMessage('Please open a file first.'); + return; } + const referenceContent = editor.document.getText(editor.selection); + const startLineNumber = editor.selection.start.line + 1; + const endLineNumber = editor.selection.end.line + 1; + + + const workspaceFolders = vscode.workspace.workspaceFolders; + const fileName = path.basename(editor.document.fileName); + let relativePath = editor.document.fileName; + if (workspaceFolders && workspaceFolders.length > 0) { + const workspaceRoot = workspaceFolders[0].uri.fsPath; + relativePath = path.relative(workspaceRoot, editor.document.fileName); + } + + flutterGPTViewProvider.postMessageToWebview({ + type: 'addToReference', value: JSON.stringify({ + relativePath: relativePath.trim(), referenceContent: `\`\n${relativePath.trim()}\n\`\n\`\`\`\n${referenceContent.toString()}\n\`\`\`\n`, referenceData: { + 'selection': { + 'start': { + 'line': startLineNumber, + 'character': 0 + }, + 'end': { + 'line': endLineNumber, + 'character': 0 + } + }, + 'editor': editor.document.uri.toString(), + }, startLineNumber, endLineNumber, fileName + }), + + }); +} + diff --git a/src/utilities/command-manager.ts b/src/utilities/command-manager.ts index 841c2599..0d08a8ea 100644 --- a/src/utilities/command-manager.ts +++ b/src/utilities/command-manager.ts @@ -76,11 +76,11 @@ export function initCommands(context: vscode.ExtensionContext, geminiRepo: any, { name: 'fluttergpt.createWidget', handler: async () => createWidgetFromDescription(geminiRepo, context.globalState), options: { isCommand: true, isMenu: true, isShortcut: false } }, { name: 'fluttergpt.createCodeFromBlueprint', handler: () => createCodeFromBlueprint(generationRepository, context.globalState), options: { isCommand: true, isMenu: true, isShortcut: false } }, { name: 'fluttergpt.createCodeFromDescription', handler: () => createCodeFromDescription(generationRepository, context.globalState), options: { isCommand: true, isMenu: true, isShortcut: false } }, - { name: 'fluttergpt.refactorCode', handler: (aiRepo: GenerationRepository, globalState: vscode.Memento, range: vscode.Range, anlyzer: ILspAnalyzer, elementName: string | undefined) => refactorCode(generationRepository, context.globalState, range, analyzer, elementName, context), options: { isCommand: true, isMenu: false, isShortcut: false } }, + { name: 'fluttergpt.refactorCode', handler: (aiRepo: GenerationRepository, globalState: vscode.Memento, range: vscode.Range, anlyzer: ILspAnalyzer, elementName: string | undefined) => refactorCode(generationRepository, context.globalState, range, analyzer, elementName, context, undefined, undefined), options: { isCommand: true, isMenu: false, isShortcut: false } }, { name: 'fluttergpt.fixErrors', handler: (aiRepo: GenerationRepository, errors: vscode.Diagnostic[], globalState: vscode.Memento, range: vscode.Range, anlyzer: ILspAnalyzer, elementName: string | undefined) => fixErrors(generationRepository, errors, context.globalState, range, analyzer, elementName, context), options: { isCommand: true, isMenu: false, isShortcut: false } }, { name: 'fluttergpt.optimizeCode', handler: (aiRepo: GenerationRepository, globalState: vscode.Memento, range: vscode.Range, anlyzer: ILspAnalyzer, elementName: string | undefined) => optimizeCode(generationRepository, context.globalState, range, anlyzer, elementName, context), options: { isCommand: true, isMenu: false, isShortcut: false } }, { name: 'fluttergpt.createInlineCodeCompletion', handler: () => createInlineCodeCompletion(geminiRepo), options: { isCommand: true, isMenu: true, isShortcut: true } }, - { name: 'fluttergpt.clearChat', handler: () => flutterGPTViewProvider?.postMessageToWebview({type: 'clearCommandDeck'}), options: {isCommand: true, isMenu: true, isShortcut: false} } + { name: 'fluttergpt.clearChat', handler: () => flutterGPTViewProvider?.postMessageToWebview({ type: 'clearCommandDeck' }), options: { isCommand: true, isMenu: true, isShortcut: false } } // Add more commands as needed. ]; From 78bbca63c1ff3546fe89cc13e91999b29fcdd7f2 Mon Sep 17 00:00:00 2001 From: wadhia-yash Date: Mon, 26 Feb 2024 11:46:33 +0530 Subject: [PATCH 06/17] fix: Adding space after special agents --- media/onboarding/onboarding.js | 35 +++++++++------------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/media/onboarding/onboarding.js b/media/onboarding/onboarding.js index 34160644..70587198 100644 --- a/media/onboarding/onboarding.js +++ b/media/onboarding/onboarding.js @@ -352,40 +352,21 @@ class CommandDeck { return () => { const option = this.options[active]; - const isOptionAvailable = commandsExecution.hasOwnProperty(option); + const isSlashOptionAvailable = commandsExecution.hasOwnProperty(option); - if (isOptionAvailable) { + if (isSlashOptionAvailable) { commandsExecution[option].exe(this.ref); } else { - - // this.ref is a contenteditable element - const selection = window.getSelection(); - const range = selection.getRangeAt(0); - const preMention = this.ref.textContent.substring(0, this.triggerIdx); - const postMention = this.ref.textContent.substring(range.endOffset); - const trigger = this.ref.textContent[this.triggerIdx]; - // Replace the mention with the selected option along with '@' + this.ref.textContent = ""; const mentionNode = document.createElement("span"); mentionNode.id = "special-commands"; mentionNode.classList.add("text-blue-500", "inline-block"); - mentionNode.contentEditable = "false"; - mentionNode.textContent = `${trigger}${option}`; - this.ref.textContent = ''; // Clear existing content - this.ref.appendChild(document.createTextNode(preMention)); + mentionNode.contentEditable = false; + mentionNode.textContent = `${trigger}${option}\u200B`; this.ref.appendChild(mentionNode); - this.ref.appendChild(document.createTextNode(postMention)); - - // Add   only if it's not already present - if (this.ref.lastChild && this.ref.lastChild.nodeValue !== '\u00A0') { - this.ref.appendChild(document.createTextNode('\u00A0')); - } - - // Move the cursor to the end of the mention - range.setStart(mentionNode.firstChild, option.length + 1); // +1 for '@' - range.collapse(true); - selection.removeAllRanges(); - selection.addRange(range); + this.ref.appendChild(document.createTextNode("\u00A0")); + setCaretToEnd(this.ref); } this.ref.focus(); @@ -630,6 +611,8 @@ function handleSubmit(event) { menuItemFn ); + console.log('commandDeck.active', commandDeck.active, commandDeck.triggerIdx, commandDeck.options); + if (event.key === "Enter" && !event.shiftKey && commandDeck.menuRef?.hidden) { event.preventDefault(); let prompt = textInput.textContent; From 628b0259daa39f9bbedf4a38053fea0abf380558 Mon Sep 17 00:00:00 2001 From: wadhia-yash Date: Mon, 26 Feb 2024 11:54:08 +0530 Subject: [PATCH 07/17] fix: Changed the color of '/refactor' slash command --- media/onboarding/onboarding.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/media/onboarding/onboarding.js b/media/onboarding/onboarding.js index 70587198..bc2a738e 100644 --- a/media/onboarding/onboarding.js +++ b/media/onboarding/onboarding.js @@ -100,7 +100,7 @@ const codeSnippetButton = document.getElementById("code-snippets"); let isApiKeyValid = false; let areDependenciesInstalled = false; let conversationHistory = []; -let chipsData = {}; // {'lib/main.dart [1-2]': {code: 'console.log('hello')'}} +let chipsData = {}; let stepOneCompleted = false; let onboardingCompleted = false; let activeAgent; @@ -168,13 +168,16 @@ const commandsExecution = { const textRefactorInput = document.createElement('span'); const refactor = document.createElement('span'); const referenceText = document.createElement('span'); - const refactorTextNode = document.createTextNode('/refactor\u00A0'); + const refactorTextNode = document.createElement('span'); const referenceIdSpan = document.createElement('span'); referenceIdSpan.id = "reference-id"; referenceIdSpan.contentEditable = "false"; referenceIdSpan.appendChild(document.createTextNode('\u00A0')); + refactorTextNode.textContent = "/refactor\u00A0"; + refactorTextNode.classList.add("text-pink-600"); + referenceText.id = "add-reference-text"; referenceText.contentEditable = "false"; referenceText.classList.add("bg-black", "text-white", "mb-1", "px-[7px]", "inline-block", "border", "cursor-pointer", "border-transparent"); @@ -611,8 +614,6 @@ function handleSubmit(event) { menuItemFn ); - console.log('commandDeck.active', commandDeck.active, commandDeck.triggerIdx, commandDeck.options); - if (event.key === "Enter" && !event.shiftKey && commandDeck.menuRef?.hidden) { event.preventDefault(); let prompt = textInput.textContent; From dae5dbbb95c8cea1efcf54a2bee1430849526bfd Mon Sep 17 00:00:00 2001 From: Keval Prajapati Date: Mon, 26 Feb 2024 14:58:42 +0530 Subject: [PATCH 08/17] refactor code uses refactor action provider --- media/onboarding/onboarding.js | 56 ++++++++-------- src/action-managers/diff-view-agent.ts | 48 ++++++++++++++ src/action-managers/refactor-agent.ts | 14 +++- src/providers/chat_view_provider.ts | 23 +++++-- .../refactor/refactor_from_instructions.ts | 55 ++++++++-------- src/utilities/diff-utils.ts | 64 ++++++++++--------- 6 files changed, 169 insertions(+), 91 deletions(-) create mode 100644 src/action-managers/diff-view-agent.ts diff --git a/media/onboarding/onboarding.js b/media/onboarding/onboarding.js index db6ce82b..1cc80d01 100644 --- a/media/onboarding/onboarding.js +++ b/media/onboarding/onboarding.js @@ -199,14 +199,14 @@ const commandsExecution = { textRefactorInput.contentEditable = "true"; textRefactorInput.tabIndex = "0"; textRefactorInput.classList.add("bg-slate-700", "px-2"); - textRefactorInput.addEventListener("focus", function(event) { + textRefactorInput.addEventListener("focus", function (event) { if (isTextRefactorInputFocused) { isChipsFocused = false; } referenceText.classList.remove("border-orange-500"); isTextRefactorInputFocused = !isTextRefactorInputFocused; }); - + textRefactorInput.appendChild(document.createTextNode("\u00A0")); refactor.appendChild(textRefactorInput); @@ -220,7 +220,7 @@ const commandsExecution = { theme: "flutter-blue" }); - input.addEventListener('keydown', function(event) { + input.addEventListener('keydown', function (event) { let keyCaught = false; switch (event.key) { case "Tab": @@ -237,7 +237,7 @@ const commandsExecution = { } keyCaught = true; break; - + case "Backspace": if (textRefactorInput.textContent.trim() === "" && textRefactorInput.innerText.trim() === "") { // Clear the text @@ -633,23 +633,24 @@ function handleSubmit(event) { if (event.key === "Enter" && !event.shiftKey && commandDeck.menuRef?.hidden) { event.preventDefault(); let prompt = textInput.textContent; - // for (const chip in chipsData) { - // if (prompt.includes(chip)) { - // prompt = prompt.replace(chip, chipsData[chip].referenceContent); - // } - // } - console.log({ - 'message': prompt, - 'chipsData': chipsData, - }); - vscode.postMessage({ - type: "action", - value: JSON.stringify({ - 'message': prompt, - 'chipsData': chipsData, - }), - }); - + if (!prompt.startsWith('/')) { + for (const chip in chipsData) { + if (prompt.includes(chip)) { + prompt = prompt.replace(chip, chipsData[chip].referenceContent); + } + } + } + if (!prompt.startsWith('/')) { + vscode.postMessage({ type: "prompt", value: prompt }); + } else { + vscode.postMessage({ + type: "action", + value: JSON.stringify({ + 'message': prompt, + 'chipsData': chipsData, + }), + }); + } textInput.textContent = ""; adjustHeight(); } @@ -820,9 +821,9 @@ function readTriggeredMessage() { case 'addToReference': removePlaceholder(); createReferenceChips(JSON.parse(message.value)); - setTimeout(() => - adjustHeight(), - 0); + setTimeout(() => + adjustHeight(), + 0); break; } }); @@ -835,7 +836,7 @@ function createReferenceChips(references) { const chip = document.createElement("span"); const chipId = `${truncateText(references.fileName)}:[${references.startLineNumber} - ${references.endLineNumber}]`; - + references.chipId = chipId; if (document.getElementById(chipId)) { return; } @@ -1036,11 +1037,12 @@ function displayMessages() { contentElement.classList.add("text-sm", "block", "w-full", "px-2.5", "py-1.5", "break-words", "bg-orange-500", "text-white"); contentElement.innerHTML = markdownToPlain(message.parts); buttonContainer.classList.add("inline-flex", "w-full", "px-2.5", "py-1.5", "bg-orange-500"); + const messageIndex = conversationHistory.indexOf(message); message?.buttons.forEach((type) => { const button = document.createElement("div"); button.classList.add("px-2.5", "py-1.5", "bg-black", "text-xs", "text-white", "uppercase", "mr-1", "rounded-[2px]", "cursor-pointer"); button.textContent = type; - button.addEventListener("click", () => handleButtonEvent(message.agent, message.data, message.messageId, type)); + button.addEventListener("click", () => handleButtonEvent(message.agent, message.data, messageIndex, type)); buttonContainer.appendChild(button); }); } @@ -1057,7 +1059,7 @@ function displayMessages() { function handleButtonEvent(agent, data, messageId, buttonType) { vscode.postMessage({ type: "dashResponse", - value: JSON.stringify({agent, data, messageId, buttonType}) + value: JSON.stringify({ agent, data, messageId, buttonType }) }); } diff --git a/src/action-managers/diff-view-agent.ts b/src/action-managers/diff-view-agent.ts new file mode 100644 index 00000000..940543a2 --- /dev/null +++ b/src/action-managers/diff-view-agent.ts @@ -0,0 +1,48 @@ +import * as vscode from 'vscode'; + +export class DiffViewAgent { + static async handleResponse(userChoice: string, data: any, messageId: string) { + const chip = data.chip; + const optimizedCode = data.optimizedCode; + const originalCodeUri = data.originalCodeUri; + const editorUri = chip.referenceData.editor; + const document = vscode.workspace.textDocuments.find(function (e) { + console.log(e.uri.toString(), editorUri); + return e.uri.toString() === editorUri; + }); + + const selection = chip.referenceData.selection; + const range: vscode.Range = new vscode.Range(new vscode.Position(selection.start.line, selection.start.character), new vscode.Position(selection.end.line, selection.end.character)); + if (!document) { + return; + } + if (userChoice === 'accept') { + + vscode.commands.executeCommand('workbench.action.closeActiveEditor'); // assuming apply edit time will be enough for the diff to close so user doesn't see a jank. + // Apply the optimized code + const workspaceEdit = new vscode.WorkspaceEdit(); + const entireDocumentRange = new vscode.Range( + document.positionAt(0), + document.positionAt(document.getText().length) + ); + workspaceEdit.replace(originalCodeUri, entireDocumentRange, optimizedCode); + if (await vscode.workspace.applyEdit(workspaceEdit)) { + let openDocument = await vscode.workspace.openTextDocument(document.uri); + await vscode.window.showTextDocument(openDocument, { + viewColumn: range.start.character, + preserveFocus: false, + selection: range, + }); + } + } else { + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + let openDocument = await vscode.workspace.openTextDocument(document.uri); + await vscode.window.showTextDocument(openDocument, { + viewColumn: range.start.character, + preserveFocus: false, + selection: range, + }); + } + return { role: "dash", parts: 'Code refactored successfully!', messageId: messageId, data: {}, buttons: [], agent: "messageView" }; + } +} \ No newline at end of file diff --git a/src/action-managers/refactor-agent.ts b/src/action-managers/refactor-agent.ts index 2e5dd157..68bced6a 100644 --- a/src/action-managers/refactor-agent.ts +++ b/src/action-managers/refactor-agent.ts @@ -8,12 +8,20 @@ export class RefactorActionManager { constructor() { } static async handleRequest(chipsData: any, data: any, aiRepo: GeminiRepository, context: vscode.ExtensionContext, analyzer: ILspAnalyzer) { - const chip = chipsData.next().value; + + const chip = Object.values(chipsData).values().next().value; + data.message = data.message.replace(chip.chipId, ''); const editorUri = chip.referenceData.editor; const editor = vscode.window.visibleTextEditors.find(e => e.document.uri.toString() === editorUri); const selection = chip.referenceData.selection; const range: vscode.Range = new vscode.Range(new vscode.Position(selection.start.line, selection.start.character), new vscode.Position(selection.end.line, selection.end.character)); - await refactorCode(aiRepo!, context.globalState, range, analyzer, undefined, context, editor, data.message); - return { role: "dash", parts: "", messageId: "", data: chip, buttons: ["accept", "decline"], agent: "refactor", }; + const optimizedCode = await refactorCode(aiRepo!, context.globalState, range, analyzer, undefined, context, editor, data.message, false); + return { + role: "dash", parts: 'Do you want to merge these changes?', messageId: "", data: { + 'chip': chip, + 'optimizedCode': optimizedCode, + 'originalCodeUri': editorUri, + }, buttons: ["accept", "decline"], agent: "diffView", + }; } } \ No newline at end of file diff --git a/src/providers/chat_view_provider.ts b/src/providers/chat_view_provider.ts index c7235368..5c46bcf0 100644 --- a/src/providers/chat_view_provider.ts +++ b/src/providers/chat_view_provider.ts @@ -6,6 +6,7 @@ import { logError, logEvent } from "../utilities/telemetry-reporter"; import { refactorCode } from "../tools/refactor/refactor_from_instructions"; import { ILspAnalyzer } from "../shared/types/LspAnalyzer"; import { RefactorActionManager } from "../action-managers/refactor-agent"; +import { DiffViewAgent } from "../action-managers/diff-view-agent"; export class FlutterGPTViewProvider implements vscode.WebviewViewProvider { public static readonly viewType = "fluttergpt.chatView"; @@ -118,6 +119,14 @@ export class FlutterGPTViewProvider implements vscode.WebviewViewProvider { { const { agent, data: _data, messageId, buttonType } = JSON.parse(data.value); console.log('agent', buttonType, _data, messageId, agent); + if (agent === "diffView") { + const updatedMessage = await DiffViewAgent.handleResponse(buttonType, _data, messageId); + // update the message with messageId + if (updatedMessage) { + this._publicConversationHistory[messageId] = updatedMessage; + this._view?.webview.postMessage({ type: 'displayMessages', value: this._publicConversationHistory }); + } + } } } @@ -143,15 +152,19 @@ export class FlutterGPTViewProvider implements vscode.WebviewViewProvider { this._view?.webview.postMessage({ type: "keyExists" }); } } - private async handleAction(input: string) { const data = JSON.parse(input); - const actionType = data.message.startsWith('/') ? data.message.split(' ')[0].substring(1) : ''; - const chipsData = new Map(Object.entries(data.chipsData)).values(); + const actionType = data.message.startsWith('/') ? data.message.split('\u00A0')[0].substring(1) : ''; + const chipsData = data.chipsData; data.message = data.message.replace(`/${actionType}`, '').trim(); if (actionType === 'refactor') { + this._view?.webview.postMessage({ type: 'showLoadingIndicator' }); const result = await RefactorActionManager.handleRequest(chipsData, data, this.aiRepo!, this.context, this.analyzer!); - this._publicConversationHistory.push({ role: 'modal', parts: data.message }); + this._view?.webview.postMessage({ type: 'hideLoadingIndicator' }); + this._publicConversationHistory.push(result); + this._view?.webview.postMessage({ type: 'displayMessages', value: this._publicConversationHistory }); + this._view?.webview.postMessage({ type: 'setPrompt', value: '' }); + } } @@ -228,7 +241,7 @@ export class FlutterGPTViewProvider implements vscode.WebviewViewProvider { return new GeminiRepository(apiKey); } - private _publicConversationHistory: Array<{ role: string, parts: string, messageId?: string, data?: any }> = []; + private _publicConversationHistory: Array<{ role: string, parts: string, messageId?: string, data?: any, buttons?: string[], agent?: string, }> = []; private _privateConversationHistory: Array<{ role: string, parts: string, messageId?: string, data?: any }> = []; private async getResponse(prompt: string) { diff --git a/src/tools/refactor/refactor_from_instructions.ts b/src/tools/refactor/refactor_from_instructions.ts index e39d43ed..3c0a0c95 100644 --- a/src/tools/refactor/refactor_from_instructions.ts +++ b/src/tools/refactor/refactor_from_instructions.ts @@ -6,7 +6,7 @@ import { ContextualCodeProvider } from '../../utilities/contextual-code'; import { handleDiffViewAndMerge } from '../../utilities/diff-utils'; import { GenerationRepository } from '../../repository/generation-repository'; -export async function refactorCode(generationRepository: GenerationRepository, globalState: vscode.Memento, range: vscode.Range | undefined, analyzer: ILspAnalyzer, elementname: string | undefined, context: vscode.ExtensionContext, usedEditor: vscode.TextEditor | undefined, instructions: string | undefined) { +export async function refactorCode(generationRepository: GenerationRepository, globalState: vscode.Memento, range: vscode.Range | undefined, analyzer: ILspAnalyzer, elementname: string | undefined, context: vscode.ExtensionContext, usedEditor: vscode.TextEditor | undefined, instructions: string | undefined, showLoadingIndicator: boolean = true): Promise { logEvent('refactor-code', { 'type': 'refractor' }); try { const editor = usedEditor === undefined ? vscode.window.activeTextEditor : usedEditor; @@ -47,21 +47,7 @@ export async function refactorCode(generationRepository: GenerationRepository, g } } let documentRefactoredText = editor.document.getText(); // Get the entire document text - - await vscode.window.withProgress({ - location: vscode.ProgressLocation.Notification, - title: "Refactoring Code", - cancellable: false - }, async (progress) => { - let progressPercentage = 0; - let prevProgressPercentage = 0; - const progressInterval = setInterval(() => { - prevProgressPercentage = progressPercentage; - progressPercentage = (progressPercentage + 10) % 100; - const increment = progressPercentage - prevProgressPercentage; - progress.report({ increment }); - }, 200); - + const refactorCodeWithoutProgress = async () => { let contextualCode = await new ContextualCodeProvider().getContextualCode(editor.document, editor.selection, analyzer, elementname); const result = await generationRepository.refactorCode(finalString, contextualCode, instructions!, globalState); console.log(result); @@ -70,9 +56,6 @@ export async function refactorCode(generationRepository: GenerationRepository, g return; } - clearInterval(progressInterval); - progress.report({ increment: 100 }); - let refactoredCode = extractDartCode(result, false); if (!refactoredCode) { vscode.window.showErrorMessage('Failed to refactor code. Please try again.'); @@ -82,17 +65,39 @@ export async function refactorCode(generationRepository: GenerationRepository, g refactoredCode = filterSurroundingCode(editor.document.getText(), refactoredCode, replaceRange.start.line, replaceRange.end.line); console.log("Refactored code:", refactoredCode); - // Modify the documentText string instead of the document directly const startOffset = editor.document.offsetAt(replaceRange.start); const endOffset = editor.document.offsetAt(replaceRange.end); documentRefactoredText = documentRefactoredText.substring(0, startOffset) + refactoredCode + documentRefactoredText.substring(endOffset); - }); - - vscode.window.showInformationMessage('Code refactored successfully!'); + }; + if (showLoadingIndicator) { + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: "Refactoring Code", + cancellable: false + }, async (progress) => { + let progressPercentage = 0; + let prevProgressPercentage = 0; + const progressInterval = setInterval(() => { + prevProgressPercentage = progressPercentage; + progressPercentage = (progressPercentage + 10) % 100; + const increment = progressPercentage - prevProgressPercentage; + progress.report({ increment }); + }, 200); + try { + await refactorCodeWithoutProgress(); + vscode.window.showInformationMessage('Code refactored successfully!'); + } finally { + clearInterval(progressInterval); + } + }); + } + else { + await refactorCodeWithoutProgress(); + } // Pass the current editor, current document uri and optimized code respectively. - await handleDiffViewAndMerge(editor, editor.document.uri, editor.document.getText(), documentRefactoredText, context); - + handleDiffViewAndMerge(editor, editor.document.uri, editor.document.getText(), documentRefactoredText, context, showLoadingIndicator); + return documentRefactoredText; } catch (error: Error | unknown) { logError('refactor-from-instructions-error', error); if (error instanceof Error) { diff --git a/src/utilities/diff-utils.ts b/src/utilities/diff-utils.ts index 2807c029..178043e8 100644 --- a/src/utilities/diff-utils.ts +++ b/src/utilities/diff-utils.ts @@ -7,6 +7,7 @@ export async function handleDiffViewAndMerge( orignalCode: string, optimizedCode: string, context: vscode.ExtensionContext, + showMergeConfimation: boolean = true ): Promise { const posAtTime = editor.selection.active; @@ -28,42 +29,43 @@ export async function handleDiffViewAndMerge( rhsUri, "Current Code ↔ Updated Code", ); + if (showMergeConfimation) { + // Ask user if they want to merge changes + let userChoice = await vscode.window.showInformationMessage( + 'Do you want to merge these changes?', + 'Yes', 'No' + ); - // Ask user if they want to merge changes - let userChoice = await vscode.window.showInformationMessage( - 'Do you want to merge these changes?', - 'Yes', 'No' - ); + if (!userChoice) { + return; + } - if (!userChoice){ - return ; - } + let reopenEditor = async () => { + let document = await vscode.workspace.openTextDocument(originalCodeUri); + await vscode.window.showTextDocument(document, { + viewColumn: editor.viewColumn, + preserveFocus: false, + selection: new vscode.Range(posAtTime, posAtTime) + }); + }; - let reopenEditor = async () => { - let document = await vscode.workspace.openTextDocument(originalCodeUri); - await vscode.window.showTextDocument(document, { - viewColumn: editor.viewColumn, - preserveFocus: false, - selection: new vscode.Range(posAtTime, posAtTime) - }); - }; - - - if (userChoice === 'Yes') { - vscode.commands.executeCommand('workbench.action.closeActiveEditor'); // assuming apply edit time will be enough for the diff to close so user doesn't see a jank. - // Apply the optimized code - const workspaceEdit = new vscode.WorkspaceEdit(); - const entireDocumentRange = new vscode.Range( - editor.document.positionAt(0), - editor.document.positionAt(editor.document.getText().length) - ); - workspaceEdit.replace(originalCodeUri, entireDocumentRange, optimizedCode); - if (await vscode.workspace.applyEdit(workspaceEdit)) { + + if (userChoice === 'Yes') { + vscode.commands.executeCommand('workbench.action.closeActiveEditor'); // assuming apply edit time will be enough for the diff to close so user doesn't see a jank. + // Apply the optimized code + const workspaceEdit = new vscode.WorkspaceEdit(); + const entireDocumentRange = new vscode.Range( + editor.document.positionAt(0), + editor.document.positionAt(editor.document.getText().length) + ); + workspaceEdit.replace(originalCodeUri, entireDocumentRange, optimizedCode); + if (await vscode.workspace.applyEdit(workspaceEdit)) { + await reopenEditor(); + } + } else { + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); await reopenEditor(); } - } else { - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - await reopenEditor(); } // remove the temporary documents From 796b140d297e44c11655bf6666c5952ee22d5911 Mon Sep 17 00:00:00 2001 From: wadhia-yash Date: Tue, 27 Feb 2024 09:55:18 +0530 Subject: [PATCH 09/17] fix: formatted text of refactor handleAction --- media/onboarding/onboarding.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/media/onboarding/onboarding.js b/media/onboarding/onboarding.js index 1cc80d01..2e8ea6cf 100644 --- a/media/onboarding/onboarding.js +++ b/media/onboarding/onboarding.js @@ -187,12 +187,13 @@ const commandsExecution = { } }); + command.id = "command-span"; command.appendChild(refactorTextNode); command.appendChild(referenceText); command.appendChild(referenceIdSpan); refactor.id = "text-refactor-container"; - refactor.innerHTML = `Text to refactor`; + refactor.innerHTML = `Text to refactor`; refactor.classList.add("inline-block"); textRefactorInput.id = "text-refactor-input"; @@ -632,6 +633,10 @@ function handleSubmit(event) { if (event.key === "Enter" && !event.shiftKey && commandDeck.menuRef?.hidden) { event.preventDefault(); + const textRefactor = document.getElementById("text-to-refactor-span"); + if (textRefactor) { + textRefactor.remove(); + } let prompt = textInput.textContent; if (!prompt.startsWith('/')) { for (const chip in chipsData) { @@ -643,6 +648,7 @@ function handleSubmit(event) { if (!prompt.startsWith('/')) { vscode.postMessage({ type: "prompt", value: prompt }); } else { + debugger; vscode.postMessage({ type: "action", value: JSON.stringify({ @@ -926,9 +932,10 @@ function insertAtReference(chip) { const referenceChip = document.getElementById("reference-id"); const referenceText = document.getElementById("add-reference-text"); - referenceText.classList.add("hidden"); + referenceText.remove(); referenceChip.innerHTML = ""; referenceChip.appendChild(chip); + referenceChip.appendChild(document.createTextNode("\u00A0")); } function debounce(func, wait, immediate = false) { From 7e6ccb103790d43330a04baa1e30e3baa539f0b8 Mon Sep 17 00:00:00 2001 From: Keval Prajapati Date: Tue, 27 Feb 2024 14:42:00 +0530 Subject: [PATCH 10/17] feat: refactor from quick-actions --- media/onboarding/onboarding.js | 13 ++++-- src/action-managers/refactor-agent.ts | 5 ++- src/providers/chat_view_provider.ts | 7 +++- .../refactor/refactor_from_instructions.ts | 41 +++++++++++++++++-- src/utilities/command-manager.ts | 2 +- 5 files changed, 57 insertions(+), 11 deletions(-) diff --git a/media/onboarding/onboarding.js b/media/onboarding/onboarding.js index 1cc80d01..6cd482ec 100644 --- a/media/onboarding/onboarding.js +++ b/media/onboarding/onboarding.js @@ -825,6 +825,12 @@ function readTriggeredMessage() { adjustHeight(), 0); break; + case 'setInput': + textInput.textContent = message.value; + if (message.value.startsWith('/')) { + const action = message.value.split(' ')[0].slice(1); + commandsExecution[action].exe(textInput); + } } }); } @@ -1033,10 +1039,11 @@ function displayMessages() { } else if (message.role === "dash") { //UI implementation roleElement.innerHTML = "Dash AI"; - roleElement.classList.add("block", "w-full", "px-2.5", "py-1.5", "bg-orange-500"); - contentElement.classList.add("text-sm", "block", "w-full", "px-2.5", "py-1.5", "break-words", "bg-orange-500", "text-white"); + roleElement.classList.add("block", "w-full", "px-2.5", "py-1.5", "bg-[#d66ab1]"); + contentElement.classList.add("text-sm", "block", "w-full", "px-2.5", "py-1.5", "break-words", "bg-[#d66ab1]", "text-white"); contentElement.innerHTML = markdownToPlain(message.parts); - buttonContainer.classList.add("inline-flex", "w-full", "px-2.5", "py-1.5", "bg-orange-500"); + buttonContainer.classList.add("inline-flex", "w-full", "px-2.5", "py-1.5", + "bg-[#d66ab1]"); const messageIndex = conversationHistory.indexOf(message); message?.buttons.forEach((type) => { const button = document.createElement("div"); diff --git a/src/action-managers/refactor-agent.ts b/src/action-managers/refactor-agent.ts index 68bced6a..4ca619e7 100644 --- a/src/action-managers/refactor-agent.ts +++ b/src/action-managers/refactor-agent.ts @@ -3,11 +3,12 @@ import { GeminiRepository } from '../repository/gemini-repository'; import { refactorCode } from '../tools/refactor/refactor_from_instructions'; import { ILspAnalyzer } from '../shared/types/LspAnalyzer'; import { dartCodeExtensionIdentifier } from '../shared/types/constants'; +import { FlutterGPTViewProvider } from '../providers/chat_view_provider'; export class RefactorActionManager { constructor() { } - static async handleRequest(chipsData: any, data: any, aiRepo: GeminiRepository, context: vscode.ExtensionContext, analyzer: ILspAnalyzer) { + static async handleRequest(chipsData: any, data: any, aiRepo: GeminiRepository, context: vscode.ExtensionContext, analyzer: ILspAnalyzer, flutterGPTViewProvider: FlutterGPTViewProvider) { const chip = Object.values(chipsData).values().next().value; data.message = data.message.replace(chip.chipId, ''); @@ -15,7 +16,7 @@ export class RefactorActionManager { const editor = vscode.window.visibleTextEditors.find(e => e.document.uri.toString() === editorUri); const selection = chip.referenceData.selection; const range: vscode.Range = new vscode.Range(new vscode.Position(selection.start.line, selection.start.character), new vscode.Position(selection.end.line, selection.end.character)); - const optimizedCode = await refactorCode(aiRepo!, context.globalState, range, analyzer, undefined, context, editor, data.message, false); + const optimizedCode = await refactorCode(aiRepo!, context.globalState, range, analyzer, undefined, context, flutterGPTViewProvider, editor, data.message, false); return { role: "dash", parts: 'Do you want to merge these changes?', messageId: "", data: { 'chip': chip, diff --git a/src/providers/chat_view_provider.ts b/src/providers/chat_view_provider.ts index 5c46bcf0..f7cf7381 100644 --- a/src/providers/chat_view_provider.ts +++ b/src/providers/chat_view_provider.ts @@ -159,7 +159,7 @@ export class FlutterGPTViewProvider implements vscode.WebviewViewProvider { data.message = data.message.replace(`/${actionType}`, '').trim(); if (actionType === 'refactor') { this._view?.webview.postMessage({ type: 'showLoadingIndicator' }); - const result = await RefactorActionManager.handleRequest(chipsData, data, this.aiRepo!, this.context, this.analyzer!); + const result = await RefactorActionManager.handleRequest(chipsData, data, this.aiRepo!, this.context, this.analyzer!, this); this._view?.webview.postMessage({ type: 'hideLoadingIndicator' }); this._publicConversationHistory.push(result); this._view?.webview.postMessage({ type: 'displayMessages', value: this._publicConversationHistory }); @@ -244,6 +244,11 @@ export class FlutterGPTViewProvider implements vscode.WebviewViewProvider { private _publicConversationHistory: Array<{ role: string, parts: string, messageId?: string, data?: any, buttons?: string[], agent?: string, }> = []; private _privateConversationHistory: Array<{ role: string, parts: string, messageId?: string, data?: any }> = []; + public addMessageToPublicConversationHistory(message: { role: string, parts: string, messageId?: string, data?: any, buttons?: string[], agent?: string, }) { + this._publicConversationHistory.push(message); + this._view?.webview.postMessage({ type: 'displayMessages', value: this._publicConversationHistory }); + } + private async getResponse(prompt: string) { if (!this._view) { await vscode.commands.executeCommand('fluttergpt.chatView.focus'); diff --git a/src/tools/refactor/refactor_from_instructions.ts b/src/tools/refactor/refactor_from_instructions.ts index 3c0a0c95..416d0bdb 100644 --- a/src/tools/refactor/refactor_from_instructions.ts +++ b/src/tools/refactor/refactor_from_instructions.ts @@ -5,8 +5,10 @@ import { ILspAnalyzer } from '../../shared/types/LspAnalyzer'; import { ContextualCodeProvider } from '../../utilities/contextual-code'; import { handleDiffViewAndMerge } from '../../utilities/diff-utils'; import { GenerationRepository } from '../../repository/generation-repository'; +import { FlutterGPTViewProvider } from '../../providers/chat_view_provider'; +import * as path from 'path'; -export async function refactorCode(generationRepository: GenerationRepository, globalState: vscode.Memento, range: vscode.Range | undefined, analyzer: ILspAnalyzer, elementname: string | undefined, context: vscode.ExtensionContext, usedEditor: vscode.TextEditor | undefined, instructions: string | undefined, showLoadingIndicator: boolean = true): Promise { +export async function refactorCode(generationRepository: GenerationRepository, globalState: vscode.Memento, range: vscode.Range | undefined, analyzer: ILspAnalyzer, elementname: string | undefined, context: vscode.ExtensionContext, flutterGPTViewProvider: FlutterGPTViewProvider, usedEditor: vscode.TextEditor | undefined, instructions: string | undefined, showLoadingIndicator: boolean = true): Promise { logEvent('refactor-code', { 'type': 'refractor' }); try { const editor = usedEditor === undefined ? vscode.window.activeTextEditor : usedEditor; @@ -41,10 +43,41 @@ export async function refactorCode(generationRepository: GenerationRepository, g const finalString = `${docStart}${highlightStart}${selectedText}${highlightEnd}${docEnd}`; if (!instructions) { - instructions = await vscode.window.showInputBox({ prompt: "Enter refactor instructions" }); - if (!instructions) { - return; + + const workspaceFolders = vscode.workspace.workspaceFolders; + let relativePath = editor.document.fileName; + if (workspaceFolders && workspaceFolders.length > 0) { + const workspaceRoot = workspaceFolders[0].uri.fsPath; + relativePath = path.relative(workspaceRoot, editor.document.fileName); } + const startLineNumber = replaceRange.start.line; + const endLineNumber = replaceRange.end.line; + const fileName = path.basename(editor.document.fileName); + + // focus chatView + vscode.commands.executeCommand('fluttergpt.chatView.focus'); + flutterGPTViewProvider.postMessageToWebview({ + type: 'setInput', + value: "/refactor" + }); + flutterGPTViewProvider.postMessageToWebview({ + type: 'addToReference', value: JSON.stringify({ + relativePath: relativePath.trim(), referenceContent: '', referenceData: { + 'selection': { + 'start': { + 'line': replaceRange.start.line, + 'character': replaceRange.start.character + }, + 'end': { + 'line': replaceRange.end.line, + 'character': replaceRange.end.character, + } + }, + 'editor': editor.document.uri.toString(), + }, startLineNumber, endLineNumber, fileName + }), + }); + return; } let documentRefactoredText = editor.document.getText(); // Get the entire document text const refactorCodeWithoutProgress = async () => { diff --git a/src/utilities/command-manager.ts b/src/utilities/command-manager.ts index 0d08a8ea..3248b7d2 100644 --- a/src/utilities/command-manager.ts +++ b/src/utilities/command-manager.ts @@ -76,7 +76,7 @@ export function initCommands(context: vscode.ExtensionContext, geminiRepo: any, { name: 'fluttergpt.createWidget', handler: async () => createWidgetFromDescription(geminiRepo, context.globalState), options: { isCommand: true, isMenu: true, isShortcut: false } }, { name: 'fluttergpt.createCodeFromBlueprint', handler: () => createCodeFromBlueprint(generationRepository, context.globalState), options: { isCommand: true, isMenu: true, isShortcut: false } }, { name: 'fluttergpt.createCodeFromDescription', handler: () => createCodeFromDescription(generationRepository, context.globalState), options: { isCommand: true, isMenu: true, isShortcut: false } }, - { name: 'fluttergpt.refactorCode', handler: (aiRepo: GenerationRepository, globalState: vscode.Memento, range: vscode.Range, anlyzer: ILspAnalyzer, elementName: string | undefined) => refactorCode(generationRepository, context.globalState, range, analyzer, elementName, context, undefined, undefined), options: { isCommand: true, isMenu: false, isShortcut: false } }, + { name: 'fluttergpt.refactorCode', handler: (aiRepo: GenerationRepository, globalState: vscode.Memento, range: vscode.Range, anlyzer: ILspAnalyzer, elementName: string | undefined) => refactorCode(generationRepository, context.globalState, range, analyzer, elementName, context, flutterGPTViewProvider, undefined, undefined,), options: { isCommand: true, isMenu: false, isShortcut: false } }, { name: 'fluttergpt.fixErrors', handler: (aiRepo: GenerationRepository, errors: vscode.Diagnostic[], globalState: vscode.Memento, range: vscode.Range, anlyzer: ILspAnalyzer, elementName: string | undefined) => fixErrors(generationRepository, errors, context.globalState, range, analyzer, elementName, context), options: { isCommand: true, isMenu: false, isShortcut: false } }, { name: 'fluttergpt.optimizeCode', handler: (aiRepo: GenerationRepository, globalState: vscode.Memento, range: vscode.Range, anlyzer: ILspAnalyzer, elementName: string | undefined) => optimizeCode(generationRepository, context.globalState, range, anlyzer, elementName, context), options: { isCommand: true, isMenu: false, isShortcut: false } }, { name: 'fluttergpt.createInlineCodeCompletion', handler: () => createInlineCodeCompletion(geminiRepo), options: { isCommand: true, isMenu: true, isShortcut: true } }, From 91f8e7ad0983d4ad463eddb844ada6b6639dc5c8 Mon Sep 17 00:00:00 2001 From: Keval Prajapati Date: Tue, 27 Feb 2024 16:28:05 +0530 Subject: [PATCH 11/17] fix: fixes code not replaced for refactor --- .../src}/action-managers/diff-view-agent.ts | 29 +++++++++++------- .../src}/action-managers/refactor-agent.ts | 0 vscode/src/extension.ts | 30 ++++++++++--------- 3 files changed, 35 insertions(+), 24 deletions(-) rename {src => vscode/src}/action-managers/diff-view-agent.ts (71%) rename {src => vscode/src}/action-managers/refactor-agent.ts (100%) diff --git a/src/action-managers/diff-view-agent.ts b/vscode/src/action-managers/diff-view-agent.ts similarity index 71% rename from src/action-managers/diff-view-agent.ts rename to vscode/src/action-managers/diff-view-agent.ts index 940543a2..fdbed285 100644 --- a/src/action-managers/diff-view-agent.ts +++ b/vscode/src/action-managers/diff-view-agent.ts @@ -6,6 +6,7 @@ export class DiffViewAgent { const optimizedCode = data.optimizedCode; const originalCodeUri = data.originalCodeUri; const editorUri = chip.referenceData.editor; + const document = vscode.workspace.textDocuments.find(function (e) { console.log(e.uri.toString(), editorUri); return e.uri.toString() === editorUri; @@ -25,23 +26,31 @@ export class DiffViewAgent { document.positionAt(0), document.positionAt(document.getText().length) ); - workspaceEdit.replace(originalCodeUri, entireDocumentRange, optimizedCode); + workspaceEdit.replace(document.uri, entireDocumentRange, optimizedCode); + await vscode.workspace.applyEdit(workspaceEdit); if (await vscode.workspace.applyEdit(workspaceEdit)) { - let openDocument = await vscode.workspace.openTextDocument(document.uri); + if (vscode.window.activeTextEditor?.document.uri.toString() !== document.uri.toString()) { + let openDocument = await vscode.workspace.openTextDocument(document.uri); + await vscode.window.showTextDocument(openDocument, { + viewColumn: range.start.character, + preserveFocus: false, + selection: range, + }); + } + } + + } else { + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + let openDocument = await vscode.workspace.openTextDocument(document.uri); + // show text document if the document is not open + if (vscode.window.activeTextEditor?.document.uri.toString() !== document.uri.toString()) { await vscode.window.showTextDocument(openDocument, { viewColumn: range.start.character, preserveFocus: false, selection: range, }); } - } else { - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - let openDocument = await vscode.workspace.openTextDocument(document.uri); - await vscode.window.showTextDocument(openDocument, { - viewColumn: range.start.character, - preserveFocus: false, - selection: range, - }); + } return { role: "dash", parts: 'Code refactored successfully!', messageId: messageId, data: {}, buttons: [], agent: "messageView" }; } diff --git a/src/action-managers/refactor-agent.ts b/vscode/src/action-managers/refactor-agent.ts similarity index 100% rename from src/action-managers/refactor-agent.ts rename to vscode/src/action-managers/refactor-agent.ts diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts index 911dad94..f2aabff2 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -35,19 +35,7 @@ export async function activate(context: vscode.ExtensionContext) { // Activate inline hints activateInlineHints(cacheManager); - // Check if the Gemini API key is set - const config = vscode.workspace.getConfiguration('fluttergpt'); - const apiKey = config.get('apiKey'); - if (!apiKey || isOldOpenAIKey(apiKey)) { - var chatViewProvider = initWebview(context); - showMissingApiKey(); - } - console.log('Congratulations, "fluttergpt" is now active!'); - dotenv.config({ path: path.join(__dirname, '../.env') }); - activateTelemetry(context); - logEvent('activated'); - - // Dart-code extenstion stuff + // Get analyzer from Dart extension const dartExt = vscode.extensions.getExtension(dartCodeExtensionIdentifier); if (!dartExt) { // This should not happen since the FlutterGPT extension has a dependency on the Dart one @@ -61,6 +49,20 @@ export async function activate(context: vscode.ExtensionContext) { } const analyzer: ILspAnalyzer = dartExt?.exports._privateApi.analyzer; + // Check if the Gemini API key is set + const config = vscode.workspace.getConfiguration('fluttergpt'); + const apiKey = config.get('apiKey'); + if (!apiKey || isOldOpenAIKey(apiKey)) { + var chatViewProvider = initWebview(context, undefined, analyzer); + showMissingApiKey(); + } + console.log('Congratulations, "fluttergpt" is now active!'); + dotenv.config({ path: path.join(__dirname, '../.env') }); + activateTelemetry(context); + logEvent('activated'); + + // Dart-code extenstion stuff + var _inlineErrorCommand: vscode.Disposable; try { let geminiRepo = initGemini(); @@ -124,7 +126,7 @@ function initFlutterExtension(context: vscode.ExtensionContext, geminiRepo: Gemi const hoverProvider = new AIHoverProvider(geminiRepo, analyzer); context.subscriptions.push(vscode.languages.registerHoverProvider(activeFileFilters, hoverProvider)); if (!chatViewProvider) { - chatViewProvider = initWebview(context, geminiRepo); + chatViewProvider = initWebview(context, geminiRepo, analyzer); } const errorActionProvider = new ErrorCodeActionProvider(analyzer, geminiRepo, context); From a896f226e76134977a29b965e750ef78df0d9b7a Mon Sep 17 00:00:00 2001 From: wadhia-yash Date: Tue, 27 Feb 2024 19:56:25 +0530 Subject: [PATCH 12/17] feat(Refactor user message bubble): When user uses refactoring command we can show the prompt which user has entered in the user message bubbles --- user_config.json | 1 + vscode/media/onboarding/onboarding.js | 89 ++++++++++--------- vscode/src/providers/chat_view_provider.ts | 2 + .../refactor/refactor_from_instructions.ts | 3 +- vscode/src/tools/reference/add_reference.ts | 1 + 5 files changed, 55 insertions(+), 41 deletions(-) create mode 100644 user_config.json diff --git a/user_config.json b/user_config.json new file mode 100644 index 00000000..04b6dbd1 --- /dev/null +++ b/user_config.json @@ -0,0 +1 @@ +{"version":"0.2.1"} \ No newline at end of file diff --git a/vscode/media/onboarding/onboarding.js b/vscode/media/onboarding/onboarding.js index 3a39ec8b..f0f014ba 100644 --- a/vscode/media/onboarding/onboarding.js +++ b/vscode/media/onboarding/onboarding.js @@ -509,20 +509,21 @@ class CommandDeck { }); sendButton.addEventListener("click", (event) => { - let prompt = textInput.textContent; - - for (const chip in chipsData) { - if (prompt.includes(chip)) { - prompt = prompt.replace(chip, chipsData[chip].referenceContent); - } - } - - vscode.postMessage({ type: "prompt", value: prompt }); - googleApiKeyHeader.classList.add("hidden"); - if (onboardingCompleted) { - textInput.textContent = ''; - } - adjustHeight(); + // let prompt = textInput.textContent; + + // for (const chip in chipsData) { + // if (prompt.includes(chip)) { + // prompt = prompt.replace(chip, chipsData[chip].referenceContent); + // } + // } + + // vscode.postMessage({ type: "prompt", value: prompt }); + // googleApiKeyHeader.classList.add("hidden"); + // if (onboardingCompleted) { + // textInput.textContent = ''; + // } + // adjustHeight(); + submitResponse(); }); textInput.addEventListener("paste", (event) => { @@ -561,6 +562,39 @@ function addToolTipsById() { }); } +function submitResponse() { + const textRefactor = document.getElementById("text-to-refactor-span"); + if (textRefactor) { + textRefactor.remove(); + } + let prompt = textInput.textContent; + if (!prompt.startsWith('/')) { + for (const chip in chipsData) { + if (prompt.includes(chip)) { + prompt = prompt.replace(chip, chipsData[chip].referenceContent); + } + } + } + if (!prompt.startsWith('/')) { + vscode.postMessage({ type: "prompt", value: prompt }); + } else { + for (const chip in chipsData) { + if (prompt.includes(chip)) { + prompt = prompt.replace(chip, chipsData[chip].referenceContent); + } + } + vscode.postMessage({ + type: "action", + value: JSON.stringify({ + 'message': prompt, + 'chipsData': chipsData, + }), + }); + } + textInput.textContent = ""; + adjustHeight(); +} + function handleSubmit(event) { const resolveFn = async (query, type) => { // Array to store possible matches @@ -617,32 +651,7 @@ function handleSubmit(event) { if (event.key === "Enter" && !event.shiftKey && commandDeck.menuRef?.hidden) { event.preventDefault(); - const textRefactor = document.getElementById("text-to-refactor-span"); - if (textRefactor) { - textRefactor.remove(); - } - let prompt = textInput.textContent; - if (!prompt.startsWith('/')) { - for (const chip in chipsData) { - if (prompt.includes(chip)) { - prompt = prompt.replace(chip, chipsData[chip].referenceContent); - } - } - } - if (!prompt.startsWith('/')) { - vscode.postMessage({ type: "prompt", value: prompt }); - } else { - debugger; - vscode.postMessage({ - type: "action", - value: JSON.stringify({ - 'message': prompt, - 'chipsData': chipsData, - }), - }); - } - textInput.textContent = ""; - adjustHeight(); + submitResponse(); } if (event.key === "Backspace") { diff --git a/vscode/src/providers/chat_view_provider.ts b/vscode/src/providers/chat_view_provider.ts index f7cf7381..0235486a 100644 --- a/vscode/src/providers/chat_view_provider.ts +++ b/vscode/src/providers/chat_view_provider.ts @@ -158,6 +158,8 @@ export class FlutterGPTViewProvider implements vscode.WebviewViewProvider { const chipsData = data.chipsData; data.message = data.message.replace(`/${actionType}`, '').trim(); if (actionType === 'refactor') { + this._publicConversationHistory.push({ role: 'user', parts: data.message }); + this._view?.webview.postMessage({ type: 'displayMessages', value: this._publicConversationHistory }); this._view?.webview.postMessage({ type: 'showLoadingIndicator' }); const result = await RefactorActionManager.handleRequest(chipsData, data, this.aiRepo!, this.context, this.analyzer!, this); this._view?.webview.postMessage({ type: 'hideLoadingIndicator' }); diff --git a/vscode/src/tools/refactor/refactor_from_instructions.ts b/vscode/src/tools/refactor/refactor_from_instructions.ts index 416d0bdb..5548bbde 100644 --- a/vscode/src/tools/refactor/refactor_from_instructions.ts +++ b/vscode/src/tools/refactor/refactor_from_instructions.ts @@ -46,6 +46,7 @@ export async function refactorCode(generationRepository: GenerationRepository, g const workspaceFolders = vscode.workspace.workspaceFolders; let relativePath = editor.document.fileName; + const referenceContent = editor.document.getText(editor.selection); if (workspaceFolders && workspaceFolders.length > 0) { const workspaceRoot = workspaceFolders[0].uri.fsPath; relativePath = path.relative(workspaceRoot, editor.document.fileName); @@ -62,7 +63,7 @@ export async function refactorCode(generationRepository: GenerationRepository, g }); flutterGPTViewProvider.postMessageToWebview({ type: 'addToReference', value: JSON.stringify({ - relativePath: relativePath.trim(), referenceContent: '', referenceData: { + relativePath: relativePath.trim(), referenceContent: `\`\n${relativePath.trim()}\n\`\n\`\`\`\n${referenceContent.toString()}\n\`\`\`\n`, referenceData: { 'selection': { 'start': { 'line': replaceRange.start.line, diff --git a/vscode/src/tools/reference/add_reference.ts b/vscode/src/tools/reference/add_reference.ts index 101638ec..59b94664 100644 --- a/vscode/src/tools/reference/add_reference.ts +++ b/vscode/src/tools/reference/add_reference.ts @@ -26,6 +26,7 @@ export async function addToReference(globalState: vscode.Memento, flutterGPTView relativePath = path.relative(workspaceRoot, editor.document.fileName); } + debugger; flutterGPTViewProvider.postMessageToWebview({ type: 'addToReference', value: JSON.stringify({ relativePath: relativePath.trim(), referenceContent: `\`\n${relativePath.trim()}\n\`\n\`\`\`\n${referenceContent.toString()}\n\`\`\`\n`, referenceData: { From 01b6b096df45456d29ab8faf3add01d0c587eb83 Mon Sep 17 00:00:00 2001 From: Keval Prajapati Date: Tue, 27 Feb 2024 23:00:08 +0530 Subject: [PATCH 13/17] fix: fixes prompt for refactor && code empty for refactor from quick-action --- vscode/media/onboarding/onboarding.js | 5 ----- vscode/src/tools/refactor/refactor_from_instructions.ts | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/vscode/media/onboarding/onboarding.js b/vscode/media/onboarding/onboarding.js index f0f014ba..1f2a1bf7 100644 --- a/vscode/media/onboarding/onboarding.js +++ b/vscode/media/onboarding/onboarding.js @@ -578,11 +578,6 @@ function submitResponse() { if (!prompt.startsWith('/')) { vscode.postMessage({ type: "prompt", value: prompt }); } else { - for (const chip in chipsData) { - if (prompt.includes(chip)) { - prompt = prompt.replace(chip, chipsData[chip].referenceContent); - } - } vscode.postMessage({ type: "action", value: JSON.stringify({ diff --git a/vscode/src/tools/refactor/refactor_from_instructions.ts b/vscode/src/tools/refactor/refactor_from_instructions.ts index 5548bbde..bf31a644 100644 --- a/vscode/src/tools/refactor/refactor_from_instructions.ts +++ b/vscode/src/tools/refactor/refactor_from_instructions.ts @@ -63,7 +63,7 @@ export async function refactorCode(generationRepository: GenerationRepository, g }); flutterGPTViewProvider.postMessageToWebview({ type: 'addToReference', value: JSON.stringify({ - relativePath: relativePath.trim(), referenceContent: `\`\n${relativePath.trim()}\n\`\n\`\`\`\n${referenceContent.toString()}\n\`\`\`\n`, referenceData: { + relativePath: relativePath.trim(), referenceContent: `\`\n${relativePath.trim()}\n\`\n\`\`\`\n${selectedText.toString()}\n\`\`\`\n`, referenceData: { 'selection': { 'start': { 'line': replaceRange.start.line, From c09dbbbbfe2c41a2834a5d32060a6813952fe3ed Mon Sep 17 00:00:00 2001 From: wadhia-yash Date: Wed, 28 Feb 2024 17:54:37 +0530 Subject: [PATCH 14/17] fix: ui fixes in prompt and passing instructions to refactor --- vscode/media/onboarding/onboarding.css | 7 +++-- vscode/media/onboarding/onboarding.js | 31 ++++++++++++++----- .../refactor/refactor_from_instructions.ts | 1 + vscode/src/tools/reference/add_reference.ts | 1 - 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/vscode/media/onboarding/onboarding.css b/vscode/media/onboarding/onboarding.css index dc9b56ce..80e7f4e2 100644 --- a/vscode/media/onboarding/onboarding.css +++ b/vscode/media/onboarding/onboarding.css @@ -174,8 +174,7 @@ p[contenteditable="true"] { } .chips-reference { - color: #ea580c; - outline: .5px solid #ea580c; + outline: .5px solid #db2777; padding: 3px 5px; margin: 2px; position: relative; @@ -189,6 +188,10 @@ p[contenteditable="true"] { color: var(--vscode-input-placeholderForeground); } +#slash-commands { + color: var(--vscode-input-placeholderForeground); +} + #bottom-text-input-container { background-color: var(--vscode-editor-background); border-color: var(--vscode-sideBar-foreground); diff --git a/vscode/media/onboarding/onboarding.js b/vscode/media/onboarding/onboarding.js index 1f2a1bf7..c70ade5f 100644 --- a/vscode/media/onboarding/onboarding.js +++ b/vscode/media/onboarding/onboarding.js @@ -176,11 +176,11 @@ const commandsExecution = { referenceIdSpan.appendChild(document.createTextNode('\u00A0')); refactorTextNode.textContent = "/refactor\u00A0"; - refactorTextNode.classList.add("text-pink-600"); + refactorTextNode.classList.add("text-pink-400"); referenceText.id = "add-reference-text"; referenceText.contentEditable = "false"; - referenceText.classList.add("bg-black", "text-white", "mb-1", "px-[7px]", "inline-block", "border", "cursor-pointer", "border-transparent"); + referenceText.classList.add("mb-1", "px-[7px]", "inline-block", "border", "cursor-pointer", "border-pink-400"); referenceText.textContent = "Add reference"; referenceText.addEventListener("click", function (event) { isChipsFocused = !isChipsFocused; @@ -202,7 +202,7 @@ const commandsExecution = { textRefactorInput.id = "text-refactor-input"; textRefactorInput.contentEditable = "true"; textRefactorInput.tabIndex = "0"; - textRefactorInput.classList.add("bg-slate-700", "px-2"); + textRefactorInput.classList.add("px-2", "border", "border-pink-400", "inline-block"); textRefactorInput.addEventListener("focus", function (event) { if (isTextRefactorInputFocused) { isChipsFocused = false; @@ -578,13 +578,29 @@ function submitResponse() { if (!prompt.startsWith('/')) { vscode.postMessage({ type: "prompt", value: prompt }); } else { + debugger; + const chipId = []; + const instructions = prompt; + for (const chip in chipsData) { + if (prompt.includes(chip)) { + prompt = prompt.replace(chip, chipsData[chip].referenceContent); + chipId.push(chip); + } + } + vscode.postMessage({ type: "action", value: JSON.stringify({ 'message': prompt, 'chipsData': chipsData, + 'chipId': chipId, + 'instructions': instructions, }), }); + + if (commandEnable) { + commandEnable = false; + } } textInput.textContent = ""; adjustHeight(); @@ -817,7 +833,7 @@ function readTriggeredMessage() { createReferenceChips(JSON.parse(message.value)); setTimeout(() => adjustHeight(), - 0); + 0); break; case 'setInput': textInput.textContent = message.value; @@ -829,9 +845,6 @@ function readTriggeredMessage() { }); } -// command to open command dash -//fluttergpt.chatView.focus - function createReferenceChips(references) { const chip = document.createElement("span"); @@ -926,7 +939,9 @@ function insertAtReference(chip) { const referenceChip = document.getElementById("reference-id"); const referenceText = document.getElementById("add-reference-text"); - referenceText.remove(); + if (referenceText) { + referenceText.remove(); + } referenceChip.innerHTML = ""; referenceChip.appendChild(chip); referenceChip.appendChild(document.createTextNode("\u00A0")); diff --git a/vscode/src/tools/refactor/refactor_from_instructions.ts b/vscode/src/tools/refactor/refactor_from_instructions.ts index bf31a644..118ce6d3 100644 --- a/vscode/src/tools/refactor/refactor_from_instructions.ts +++ b/vscode/src/tools/refactor/refactor_from_instructions.ts @@ -61,6 +61,7 @@ export async function refactorCode(generationRepository: GenerationRepository, g type: 'setInput', value: "/refactor" }); + console.log('selected text', selectedText); flutterGPTViewProvider.postMessageToWebview({ type: 'addToReference', value: JSON.stringify({ relativePath: relativePath.trim(), referenceContent: `\`\n${relativePath.trim()}\n\`\n\`\`\`\n${selectedText.toString()}\n\`\`\`\n`, referenceData: { diff --git a/vscode/src/tools/reference/add_reference.ts b/vscode/src/tools/reference/add_reference.ts index 59b94664..101638ec 100644 --- a/vscode/src/tools/reference/add_reference.ts +++ b/vscode/src/tools/reference/add_reference.ts @@ -26,7 +26,6 @@ export async function addToReference(globalState: vscode.Memento, flutterGPTView relativePath = path.relative(workspaceRoot, editor.document.fileName); } - debugger; flutterGPTViewProvider.postMessageToWebview({ type: 'addToReference', value: JSON.stringify({ relativePath: relativePath.trim(), referenceContent: `\`\n${relativePath.trim()}\n\`\n\`\`\`\n${referenceContent.toString()}\n\`\`\`\n`, referenceData: { From f4d38b2cda01cd5ed38c5fa5476d48707e1e2090 Mon Sep 17 00:00:00 2001 From: Keval Prajapati Date: Wed, 28 Feb 2024 21:04:01 +0530 Subject: [PATCH 15/17] fix: updates refactor to accept instructions --- vscode/src/action-managers/diff-view-agent.ts | 14 +++++++----- vscode/src/action-managers/refactor-agent.ts | 22 ++++++++++++++----- vscode/src/providers/chat_view_provider.ts | 6 +++-- vscode/src/repository/gemini-repository.ts | 8 +++++-- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/vscode/src/action-managers/diff-view-agent.ts b/vscode/src/action-managers/diff-view-agent.ts index fdbed285..5ad683e3 100644 --- a/vscode/src/action-managers/diff-view-agent.ts +++ b/vscode/src/action-managers/diff-view-agent.ts @@ -5,17 +5,21 @@ export class DiffViewAgent { const chip = data.chip; const optimizedCode = data.optimizedCode; const originalCodeUri = data.originalCodeUri; - const editorUri = chip.referenceData.editor; - const document = vscode.workspace.textDocuments.find(function (e) { - console.log(e.uri.toString(), editorUri); - return e.uri.toString() === editorUri; + let document = vscode.workspace.textDocuments.find(function (e) { + console.log(e.uri.toString(), originalCodeUri); + return e.uri.toString() === originalCodeUri; }); const selection = chip.referenceData.selection; const range: vscode.Range = new vscode.Range(new vscode.Position(selection.start.line, selection.start.character), new vscode.Position(selection.end.line, selection.end.character)); if (!document) { - return; + // if document is not founds, open the document + let uri = vscode.Uri.parse(originalCodeUri); + document = await vscode.workspace.openTextDocument(uri); + if (!document) { + return; + } } if (userChoice === 'accept') { diff --git a/vscode/src/action-managers/refactor-agent.ts b/vscode/src/action-managers/refactor-agent.ts index 4ca619e7..f15c71f5 100644 --- a/vscode/src/action-managers/refactor-agent.ts +++ b/vscode/src/action-managers/refactor-agent.ts @@ -8,15 +8,25 @@ export class RefactorActionManager { constructor() { } - static async handleRequest(chipsData: any, data: any, aiRepo: GeminiRepository, context: vscode.ExtensionContext, analyzer: ILspAnalyzer, flutterGPTViewProvider: FlutterGPTViewProvider) { - - const chip = Object.values(chipsData).values().next().value; - data.message = data.message.replace(chip.chipId, ''); + static async handleRequest(chips: any, chipIds: string[], data: any, aiRepo: GeminiRepository, context: vscode.ExtensionContext, analyzer: ILspAnalyzer, flutterGPTViewProvider: FlutterGPTViewProvider) { + var instructions = data.instructions as string; + for (const chip of chipIds) { + if (instructions.includes(chip)) { + instructions = instructions.replace(chip, ''); + } + } + const chip = Object.values(chips).values().next().value; + // Assuming first chip is code to refactor const editorUri = chip.referenceData.editor; - const editor = vscode.window.visibleTextEditors.find(e => e.document.uri.toString() === editorUri); + let editor = vscode.window.visibleTextEditors.find(e => e.document.uri.toString() === editorUri); + if (!editor) { + let uri = vscode.Uri.parse(editorUri); + let document = await vscode.workspace.openTextDocument(uri); + editor = await vscode.window.showTextDocument(document); + } const selection = chip.referenceData.selection; const range: vscode.Range = new vscode.Range(new vscode.Position(selection.start.line, selection.start.character), new vscode.Position(selection.end.line, selection.end.character)); - const optimizedCode = await refactorCode(aiRepo!, context.globalState, range, analyzer, undefined, context, flutterGPTViewProvider, editor, data.message, false); + const optimizedCode = await refactorCode(aiRepo!, context.globalState, range, analyzer, undefined, context, flutterGPTViewProvider, editor, instructions.trim(), false); return { role: "dash", parts: 'Do you want to merge these changes?', messageId: "", data: { 'chip': chip, diff --git a/vscode/src/providers/chat_view_provider.ts b/vscode/src/providers/chat_view_provider.ts index 0235486a..7303c06f 100644 --- a/vscode/src/providers/chat_view_provider.ts +++ b/vscode/src/providers/chat_view_provider.ts @@ -155,13 +155,15 @@ export class FlutterGPTViewProvider implements vscode.WebviewViewProvider { private async handleAction(input: string) { const data = JSON.parse(input); const actionType = data.message.startsWith('/') ? data.message.split('\u00A0')[0].substring(1) : ''; - const chipsData = data.chipsData; + const chipsData: object = data.chipsData; data.message = data.message.replace(`/${actionType}`, '').trim(); + data.instructions = data.instructions.replace(`/${actionType}`, '').trim(); + const chipIds: string[] = data.chipId; if (actionType === 'refactor') { this._publicConversationHistory.push({ role: 'user', parts: data.message }); this._view?.webview.postMessage({ type: 'displayMessages', value: this._publicConversationHistory }); this._view?.webview.postMessage({ type: 'showLoadingIndicator' }); - const result = await RefactorActionManager.handleRequest(chipsData, data, this.aiRepo!, this.context, this.analyzer!, this); + const result = await RefactorActionManager.handleRequest(chipsData, chipIds, data, this.aiRepo!, this.context, this.analyzer!, this); this._view?.webview.postMessage({ type: 'hideLoadingIndicator' }); this._publicConversationHistory.push(result); this._view?.webview.postMessage({ type: 'displayMessages', value: this._publicConversationHistory }); diff --git a/vscode/src/repository/gemini-repository.ts b/vscode/src/repository/gemini-repository.ts index 16b8ccad..1426a59b 100644 --- a/vscode/src/repository/gemini-repository.ts +++ b/vscode/src/repository/gemini-repository.ts @@ -399,7 +399,7 @@ export class GeminiRepository extends GenerationRepository { public async refactorCode(finalstring: string, contextualCode: string | undefined, instructions: string, globalState: vscode.Memento): Promise { let referenceEditor = getReferenceEditor(globalState); let prompt = 'You are a Flutter/Dart assistant helping user modify code within their editor window.'; - prompt += `Modification instructions from user: ${instructions}. Please find the editor file code. To represent the selected code, we have it highlighted with ..... .\n` + '```\n' + finalstring + '\n```\n'; + prompt += `Modification instructions from user:\n${instructions}.\n\nPlease find the editor file code. To represent the selected code, we have it highlighted with ..... .\n` + '```\n' + finalstring + '\n```\n'; prompt = appendReferences(referenceEditor, prompt); if (contextualCode) { @@ -409,7 +409,11 @@ export class GeminiRepository extends GenerationRepository { 1. Describe the selected piece of code. 2. What is the intent of user's modification? 3. How do you plan to achieve that? [Don't output code yet] - 4. Output the modified code to be be programatically replaced in the editor in place of the CURSOR_SELECTION. Since this is without human review, you need to output the precise CURSOR_SELECTION`; + 4. Output the modified code to be be programatically replaced in the editor in place of the CURSOR_SELECTION. Since this is without human review, you need to output the precise CURSOR_SELECTION + + IMPORTANT NOTE: Please make sure to output the modified code in a single code block. + Do not just give explanation prose but also give the final code at last. + `; console.log(prompt); const result = await this.getCompletion([{ 'role': 'user', From 8d229236f62b816aea1638ca73e870c617a9ad56 Mon Sep 17 00:00:00 2001 From: Keval Prajapati Date: Thu, 29 Feb 2024 11:38:09 +0530 Subject: [PATCH 16/17] fix: fixes wrong reference for refactor --- vscode/src/action-managers/diff-view-agent.ts | 2 +- vscode/src/action-managers/refactor-agent.ts | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/vscode/src/action-managers/diff-view-agent.ts b/vscode/src/action-managers/diff-view-agent.ts index 5ad683e3..0c2bc955 100644 --- a/vscode/src/action-managers/diff-view-agent.ts +++ b/vscode/src/action-managers/diff-view-agent.ts @@ -30,7 +30,7 @@ export class DiffViewAgent { document.positionAt(0), document.positionAt(document.getText().length) ); - workspaceEdit.replace(document.uri, entireDocumentRange, optimizedCode); + workspaceEdit.replace(vscode.Uri.parse(originalCodeUri), entireDocumentRange, optimizedCode); await vscode.workspace.applyEdit(workspaceEdit); if (await vscode.workspace.applyEdit(workspaceEdit)) { if (vscode.window.activeTextEditor?.document.uri.toString() !== document.uri.toString()) { diff --git a/vscode/src/action-managers/refactor-agent.ts b/vscode/src/action-managers/refactor-agent.ts index f15c71f5..e82157d3 100644 --- a/vscode/src/action-managers/refactor-agent.ts +++ b/vscode/src/action-managers/refactor-agent.ts @@ -10,13 +10,16 @@ export class RefactorActionManager { static async handleRequest(chips: any, chipIds: string[], data: any, aiRepo: GeminiRepository, context: vscode.ExtensionContext, analyzer: ILspAnalyzer, flutterGPTViewProvider: FlutterGPTViewProvider) { var instructions = data.instructions as string; - for (const chip of chipIds) { - if (instructions.includes(chip)) { - instructions = instructions.replace(chip, ''); + var chip; + for (const chipId of chipIds) { + if (instructions.includes(chipId)) { + instructions = instructions.replace(chipId, ''); + // Assuming first chip is code to refactor + if (!chip) { + chip = chips[chipId]; + } } } - const chip = Object.values(chips).values().next().value; - // Assuming first chip is code to refactor const editorUri = chip.referenceData.editor; let editor = vscode.window.visibleTextEditors.find(e => e.document.uri.toString() === editorUri); if (!editor) { From b2b100b8183cbef8b6c81e09e7f81c1449968a04 Mon Sep 17 00:00:00 2001 From: Keval Prajapati Date: Thu, 29 Feb 2024 21:33:03 +0530 Subject: [PATCH 17/17] fix: fixes incorrect merge --- vscode/src/action-managers/diff-view-agent.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/vscode/src/action-managers/diff-view-agent.ts b/vscode/src/action-managers/diff-view-agent.ts index 0c2bc955..61d30bd2 100644 --- a/vscode/src/action-managers/diff-view-agent.ts +++ b/vscode/src/action-managers/diff-view-agent.ts @@ -6,13 +6,13 @@ export class DiffViewAgent { const optimizedCode = data.optimizedCode; const originalCodeUri = data.originalCodeUri; + let document = vscode.workspace.textDocuments.find(function (e) { console.log(e.uri.toString(), originalCodeUri); return e.uri.toString() === originalCodeUri; }); const selection = chip.referenceData.selection; - const range: vscode.Range = new vscode.Range(new vscode.Position(selection.start.line, selection.start.character), new vscode.Position(selection.end.line, selection.end.character)); if (!document) { // if document is not founds, open the document let uri = vscode.Uri.parse(originalCodeUri); @@ -31,14 +31,13 @@ export class DiffViewAgent { document.positionAt(document.getText().length) ); workspaceEdit.replace(vscode.Uri.parse(originalCodeUri), entireDocumentRange, optimizedCode); - await vscode.workspace.applyEdit(workspaceEdit); - if (await vscode.workspace.applyEdit(workspaceEdit)) { + const isSuccess = await vscode.workspace.applyEdit(workspaceEdit); + if (isSuccess) { if (vscode.window.activeTextEditor?.document.uri.toString() !== document.uri.toString()) { - let openDocument = await vscode.workspace.openTextDocument(document.uri); - await vscode.window.showTextDocument(openDocument, { - viewColumn: range.start.character, + await vscode.window.showTextDocument(document, { + viewColumn: selection.start.character, preserveFocus: false, - selection: range, + selection: new vscode.Range(new vscode.Position(selection.start.line, selection.start.character), new vscode.Position(selection.start.line, selection.start.character)) }); } } @@ -49,9 +48,9 @@ export class DiffViewAgent { // show text document if the document is not open if (vscode.window.activeTextEditor?.document.uri.toString() !== document.uri.toString()) { await vscode.window.showTextDocument(openDocument, { - viewColumn: range.start.character, + viewColumn: selection.start.character, preserveFocus: false, - selection: range, + selection: new vscode.Range(new vscode.Position(selection.start.line, selection.start.character), new vscode.Position(selection.start.line, selection.start.character)) }); }