10clouds / 10minions-vscode

Your Army of GPT-4 Powered Coding Buddies (Boost Your Productivity)
https://marketplace.visualstudio.com/items?itemName=10Clouds.10minions
MIT License
2 stars 0 forks source link

Replace failed #58

Closed cielecki closed 1 year ago

cielecki commented 1 year ago

//////////////////////////////////////////////////////////////////////////////// // Stage 1: Starting ... ////////////////////////////////////////////////////////////////////////////////

File: SideBarWebViewInnerComponent.tsx Task: Make text transparent (as it is now) but keep the cursor visible.

//////////////////////////////////////////////////////////////////////////////// // Stage 2: Understanding ... ////////////////////////////////////////////////////////////////////////////////

LocalChange

Classification: LocalChange

//////////////////////////////////////////////////////////////////////////////// // Stage 3: Conceptualising ... ////////////////////////////////////////////////////////////////////////////////

First, we need to understand the current implementation. The textarea has a transparent text color by setting the color property to "rgba(0,0,0,0)". This makes both the text and the cursor invisible.

To make the cursor visible while keeping the text transparent, we can change the color property of the ::-webkit-input-placeholder pseudo-element to transparent and remove the transparent color from the main textarea style. This way, the entered text will still be shown in the usual (visible) color, but the placeholder text will be transparent.

The CSS changes would look like this:

Step 1: Remove transparent color property from the main textarea style:

style={{
  height: "20rem",
  backgroundColor: "var(--vscode-editor-background)",
  borderColor: "var(--vscode-focusBorder)",
}}

Step 2: Add the transparent color property to ::-webkit-input-placeholder pseudo-element:

  <style>
    {`
    textarea::-webkit-input-placeholder {
      color: rgba(0,0,0,0);
    }
    `}
  </style>

Finally, we need to add the style tag with our new CSS rules near the top of the component render method, right after opening the root div. The modified component render code would look like this:

return (
    <div className="w-full">
      {/* Add a style tag for placeholder style */}
      <style>
        {`
        textarea::-webkit-input-placeholder {
          color: rgba(0,0,0,0);
        }
        `}
      </style>

      <div className="p-4 mb-6">
        {renderHeader(RobotIcon1, RobotIcon2)}

        {/* Rest of the component render code */}
        // ...
    </div>
);

Now, the cursor will be visible while the placeholder text stays transparent.

//////////////////////////////////////////////////////////////////////////////// // Stage 4: Preparing Changes ... ////////////////////////////////////////////////////////////////////////////////

I am going to make the cursor visible in the textarea while keeping the placeholder text transparent. I will remove the transparent color property from the main textarea style and add a new style tag with a transparent color property for the ::-webkit-input-placeholder pseudo-element. I will place the new style tag near the top of the component render method, right after opening the root div.

REPLACE style={{ height: "20rem", backgroundColor: "var(--vscode-editor-background)", color: "rgba(0,0,0,0)", // Transparent text color borderColor: "var(--vscode-focusBorder)", }} WITH style={{ height: "20rem", backgroundColor: "var(--vscode-editor-background)", borderColor: "var(--vscode-focusBorder)", }} END_REPLACE

REPLACE return (

WITH return (
{/* Add a style tag for placeholder style */} END_REPLACE//////////////////////////////////////////////////////////////////////////////// // Stage 5: Applying Changes ... //////////////////////////////////////////////////////////////////////////////// Error in applying consolidation: Error: adjustedReplaceText not found in currentCode. adjustedReplaceText: return (
//////////////////////////////////////////////////////////////////////////////// // Stage 6: Preparing Changes (retry) ... //////////////////////////////////////////////////////////////////////////////// I will make the cursor visible while keeping the placeholder text transparent by modifying the textarea style and adding a new style tag for the ::-webkit-input-placeholder pseudo-element. Here are the changes: REPLACE ``` style={{ height: "20rem", backgroundColor: "var(--vscode-editor-background)", color: "rgba(0,0,0,0)", // Transparent text color borderColor: "var(--vscode-focusBorder)", }} ``` WITH ``` style={{ height: "20rem", backgroundColor: "var(--vscode-editor-background)", borderColor: "var(--vscode-focusBorder)", }} ``` REPLACE ``` return (
``` WITH ``` return (
{/* Add a style tag for placeholder style */} ``` END_REPLACE//////////////////////////////////////////////////////////////////////////////// // Stage 7: Applying Changes (retry) ... //////////////////////////////////////////////////////////////////////////////// Error in applying consolidation: Error: adjustedReplaceText not found in currentCode. adjustedReplaceText: return (
//////////////////////////////////////////////////////////////////////////////// // Stage 8: Applying changes as comment (fall back) ... //////////////////////////////////////////////////////////////////////////////// PLAIN COMMENT FALLBACK //////////////////////////////////////////////////////////////////////////////// // Stage 9: Finishing ... //////////////////////////////////////////////////////////////////////////////// Finished (Execution Time: 00:02:06)
cielecki commented 1 year ago

Another one

//////////////////////////////////////////////////////////////////////////////// // Stage 1: Starting ... ////////////////////////////////////////////////////////////////////////////////

File: TenMinionsViewProvider.ts Task: Besides commandHistory store current executions in the global state

//////////////////////////////////////////////////////////////////////////////// // Stage 2: Understanding ... ////////////////////////////////////////////////////////////////////////////////

LocalChange

Classification: LocalChange

//////////////////////////////////////////////////////////////////////////////// // Stage 3: Conceptualising ... ////////////////////////////////////////////////////////////////////////////////

First, I will update the constructor to initialize executions from the global state. Then, I will create a method to save the executions to the global state after each change. Finally, I will call this method whenever the executions array is modified.

  1. Update the constructor to initialize executions from the global state:
constructor(private readonly _extensionUri: vscode.Uri, context: vscode.ExtensionContext) {
  this._context = context;
  this.commandHistory = this._context.globalState.get("10minions.commandHistory") || {
    // default command history
  };
  this.executions = this._context.globalState.get("10minions.executions") || []; // Initialize from global state
}
  1. Create a method to save the executions to the global state:
private async saveExecutions() {
  await this._context.globalState.update("10minions.executions", this.executions);
}
  1. Call the saveExecutions method whenever the executions array is modified:

3.1. In runMinionOnCurrentSelectionAndEditor method, after adding the new execution:

this.executions = [execution, ...this.executions];
await this.saveExecutions(); // Save executions to global state

3.2. In the case of "reRunExecution":

Replace the following line:

this.executions = [newExecution, ...this.executions.filter((e) => e.id !== executionId)];

With this code:

this.executions = [newExecution, ...this.executions.filter((e) => e.id !== executionId)];
await this.saveExecutions(); // Save executions to global state

3.3. In the case of "closeExecution":

Replace the following line:

this.executions = this.executions.filter((e) => e.id !== executionId);

With this code:

this.executions = this.executions.filter((e) => e.id !== executionId);
await this.saveExecutions(); // Save executions to global state

With these modifications, the current executions will be stored in the global state, and will persist across different sessions.

//////////////////////////////////////////////////////////////////////////////// // Stage 4: Preparing Changes ... ////////////////////////////////////////////////////////////////////////////////

I will update the constructor to initialize executions from the global state, create a method to save the executions to the global state, and call this method whenever the executions array is modified.

REPLACE constructor(private readonly _extensionUri: vscode.Uri, context: vscode.ExtensionContext) { this._context = context; } WITH constructor(private readonly _extensionUri: vscode.Uri, context: vscode.ExtensionContext) { this._context = context; this.commandHistory = this._context.globalState.get("10minions.commandHistory") || { // default command history }; this.executions = this._context.globalState.get("10minions.executions") || []; // Initialize from global state } END_REPLACE

REPLACE public async runMinionOnCurrentSelectionAndEditor(userQuery: string) { // ...rest of the code remains the same...

this.executions = [execution, ...this.executions]; } WITH private async saveExecutions() { await this._context.globalState.update("10minions.executions", this.executions); }

public async runMinionOnCurrentSelectionAndEditor(userQuery: string) { // ...rest of the code remains the same...

this.executions = [execution, ...this.executions]; await this.saveExecutions(); // Save executions to global state } END_REPLACE

REPLACE this.executions = [newExecution, ...this.executions.filter((e) => e.id !== executionId)]; WITH this.executions = [newExecution, ...this.executions.filter((e) => e.id !== executionId)]; await this.saveExecutions(); // Save executions to global state END_REPLACE

REPLACE this.executions = this.executions.filter((e) => e.id !== executionId); WITH this.executions = this.executions.filter((e) => e.id !== executionId); await this.saveExecutions(); // Save executions to global state END_REPLACE

//////////////////////////////////////////////////////////////////////////////// // Stage 5: Applying Changes ... ////////////////////////////////////////////////////////////////////////////////

Error in applying consolidation: Error: Failed replace

replaceText: public async runMinionOnCurrentSelectionAndEditor(userQuery: string) { // ...rest of the code remains the same...

this.executions = [execution, ...this.executions]; }

originalCode: import { encode } from "gpt-tokenizer"; import * as vscode from "vscode"; import { GPTExecution } from "./GPTExecution"; import { CANCELED_STAGE_NAME, ExecutionInfo } from "./ui/ExecutionInfo"; import { basename } from "path";

export class TenMinionsViewProvider implements vscode.WebviewViewProvider { public static readonly viewType = "10minions.chatView";

private executions: GPTExecution[] = []; private _view?: vscode.WebviewView;

private readonly _context: vscode.ExtensionContext;

constructor(private readonly _extensionUri: vscode.Uri, context: vscode.ExtensionContext) { this._context = context; }

private commandHistory: Record<string, { timeStamp: number }> = {};

private getCommandSuggestion(input: string) { if (!input) return "";

const ONE_DAY = 24 * 60 * 60 * 1000;

const suggestions = Object.keys(this.commandHistory)
  .filter((command) => command.toLowerCase().includes(input.toLowerCase()) && command.toLowerCase() !== input.toLowerCase())
  .map((command) => {
    const { timeStamp } = this.commandHistory[command];
    const daysOld = Math.floor((Date.now() - timeStamp) / ONE_DAY);
    return { command, weight: -daysOld, originalCommand: command };
  })
  .sort((a, b) => b.weight - a.weight);

if (suggestions.length === 0) return "";

const matchedCommand = suggestions[0].originalCommand;
const index = matchedCommand.toLowerCase().indexOf(input.toLowerCase());
const commandWithCorrectedCase = matchedCommand.slice(0, index) + input + matchedCommand.slice(index + input.length);
return commandWithCorrectedCase;

}

private async updateCommandHistory(prompt: string) { const newCommandHistory = { ...this.commandHistory };

if (newCommandHistory[prompt]) {
  newCommandHistory[prompt] = { timeStamp: Date.now() };
} else {
  newCommandHistory[prompt] = { timeStamp: Date.now() };
}

this.commandHistory = newCommandHistory;
await this._context.globalState.update("10minions.commandHistory", newCommandHistory);

}

public resolveWebviewView(webviewView: vscode.WebviewView, context: vscode.WebviewViewResolveContext, _token: vscode.CancellationToken) { this.commandHistory = this._context.globalState.get("10minions.commandHistory") || { "Refactor this": { timeStamp: Date.now() }, "Explain": { timeStamp: Date.now() }, "Make it pretty": { timeStamp: Date.now() }, "Rename this to something sensible": { timeStamp: Date.now() }, "Are there any bugs? Fix them": { timeStamp: Date.now() }, "Rework this so now it also does X": { timeStamp: Date.now() }, }; this._view = webviewView;

// set options for the webview, allow scripts
webviewView.webview.options = {
  enableScripts: true,
  localResourceRoots: [this._extensionUri],
};

const extractTextKey = (uri: vscode.Uri): string => uri.path.match(/^text\/([a-z\d\-]+)/)![1];

const self = this;

class ContentProvider implements vscode.TextDocumentContentProvider {
  constructor() {}

  provideTextDocumentContent(uri: vscode.Uri): string {
    console.log("CONTENT", uri);
    const textKey = extractTextKey(uri);
    const originalContent = self.executions.find((e) => e.id === textKey)?.fullContent;
    return originalContent || "";
  }
}

vscode.workspace.registerTextDocumentContentProvider("10minions", new ContentProvider());

// set the HTML for the webview
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);

// add an event listener for messages received by the webview
webviewView.webview.onDidReceiveMessage(async (data) => {
  console.log("CMD", data);
  const activeEditor = vscode.window.activeTextEditor;

  switch (data.type) {
    case "getTokenCount": {
      let tokenCount = activeEditor ? encode(activeEditor.document.getText()).length : 0;

      this._view?.webview.postMessage({
        type: "tokenCount",
        value: tokenCount,
      });

      break;
    }
    case "newExecution": {
      let prompt = data.value ? data.value : "Refactor this code";

      await this.updateCommandHistory(prompt);

      this.runMinionOnCurrentSelectionAndEditor(prompt);
      break;
    }
    case "openDocument": {
      //if it's open and focused close it

      let documentURI = vscode.Uri.parse(data.documentURI);
      await vscode.workspace.openTextDocument(documentURI);
      await vscode.window.showTextDocument(documentURI);

      break;
    }
    case "showDiff": {
      let executionId = data.executionId;
      let execution = this.executions.find((e) => e.id === executionId);

      if (execution) {
        const makeUriString = (textKey: string): string => `10minions:text/${textKey}`; // `_ts` to avoid cache

        const documentUri = vscode.Uri.parse(execution.documentURI);
        await vscode.commands.executeCommand(
          "vscode.diff",
          vscode.Uri.parse(makeUriString(executionId)),
          documentUri,
          `(original) ↔ ${basename(documentUri.fsPath)}`
        );
      }
      break;
    }
    case "reRunExecution": {
      let executionId = data.executionId;
      let oldExecutionMaybe = this.executions.find((e) => e.id === executionId);

      if (!oldExecutionMaybe) {
        vscode.window.showErrorMessage("No execution found for id", executionId);
        throw new Error(`No execution found for id ${executionId}`);
      }

      let oldExecution = oldExecutionMaybe;

      if (!oldExecution.stopped) {
        vscode.window.showErrorMessage("Execution is still running", executionId);
        break;
      }

      //remove old execution
      this.executions = this.executions.filter((e) => e.id !== executionId);

      //after 1 second add a new one
      setTimeout(async () => {
        let newExecution = await GPTExecution.create({
          userQuery: data.newUserQuery || oldExecution.userQuery,
          document: await oldExecution.document(),
          selection: oldExecution.selection,
          selectedText: oldExecution.selectedText,
          onChanged: async (important) => {
            if (important) {
              this.notifyExecutionsUpdatedImmediate();
            } else {
              this.notifyExecutionsUpdated();
            }
          },
        });

        this.executions = [newExecution, ...this.executions.filter((e) => e.id !== executionId)];

        await newExecution.run();
      }, 1000);

      break;
    }

    case "stopExecution": {
      let executionId = data.executionId;
      let execution = this.executions.find((e) => e.id === executionId);

      if (execution) {
        execution.stopExecution(CANCELED_STAGE_NAME);
      } else {
        console.error("No execution found for id", executionId);
      }

      break;
    }
    case "getSuggestions": {
      const input = data.input || "";
      const suggestion = this.getCommandSuggestion(input);
      console.log(`Suggestion: ${suggestion}`);
      this._view?.webview.postMessage({
        type: "suggestion",
        value: suggestion,
      });
      break;
    }
    case "closeExecution": {
      let executionId = data.executionId;

      let execution = this.executions.find((e) => e.id === executionId);

      if (execution) {
        execution.stopExecution(CANCELED_STAGE_NAME, false);
        this.executions = this.executions.filter((e) => e.id !== executionId);
      } else {
        vscode.window.showErrorMessage("No execution found for id", executionId);
      }

      //notify webview
      this.notifyExecutionsUpdatedImmediate();

      break;
    }
    case "readyForMessages": {
      this._view?.webview.postMessage({
        type: "apiKeySet",
        value: !!vscode.workspace.getConfiguration("10minions").get("apiKey"),
      });

      break;
    }
  }
});

//post message with update to set api key, each time appropriate config is updated
vscode.workspace.onDidChangeConfiguration((e) => {
  console.log(`Changed`);
  if (e.affectsConfiguration("10minions.apiKey")) {
    this._view?.webview.postMessage({
      type: "apiKeySet",
      value: !!vscode.workspace.getConfiguration("10minions").get("apiKey"),
    });
  }
});

}

private _isThrottled = false; private _pendingUpdate = false;

notifyExecutionsUpdated() { if (this._isThrottled) { this._pendingUpdate = true; return; }

this._isThrottled = true;
this.notifyExecutionsUpdatedImmediate();

setTimeout(() => {
  this._isThrottled = false;
  if (this._pendingUpdate) this.notifyExecutionsUpdatedImmediate();
  this._pendingUpdate = false;
}, 500);

}

notifyExecutionsUpdatedImmediate() { const executionInfo: ExecutionInfo[] = this.executions.map((e) => ({ id: e.id, fullContent: e.fullContent, userQuery: e.userQuery, executionStage: e.executionStage, documentName: e.baseName, documentURI: e.documentURI, logFileURI: e.workingDocumentURI, progress: e.progress, stopped: e.stopped, classification: e.classification, modificationDescription: e.modificationDescription, selectedText: e.selectedText, shortName: e.shortName, }));

this._view?.webview.postMessage({
  type: "executionsUpdated",
  executions: executionInfo,
});

}

async clearAndfocusOnInput() { //make sure that our extension bar is visible if (!this._view) { await vscode.commands.executeCommand("10minions.chatView.focus"); } else { this._view?.show?.(true); }

this._view?.webview.postMessage({
  type: "clearAndfocusOnInput",
});

}

async preFillPrompt(prompt: string) { //make sure that our extension bar is visible if (!this._view) { await vscode.commands.executeCommand("10minions.chatView.focus"); } else { this._view?.show?.(true); }

this.runMinionOnCurrentSelectionAndEditor(prompt);

}

public async runMinionOnCurrentSelectionAndEditor(userQuery: string) { const activeEditor = vscode.window.activeTextEditor;

if (!activeEditor) {
  vscode.window.showErrorMessage("Please open a file before running 10Minions");
  return;
}

if (activeEditor.document.fileName.endsWith(".log")) {
  vscode.window.showErrorMessage("Please open a file before running 10Minions");
  return;
}

const execution = await GPTExecution.create({
  userQuery,
  document: activeEditor.document,
  selection: activeEditor.selection,
  selectedText: activeEditor.document.getText(activeEditor.selection),
  onChanged: async (important) => {
    if (important) {
      this.notifyExecutionsUpdatedImmediate();
    } else {
      this.notifyExecutionsUpdated();
    }
  },
});

this.executions = [execution, ...this.executions];

await execution.run();

}

private _getHtmlForWebview(webview: vscode.Webview) { return `

<body>
  <div id="root"></div>
  <script src="${webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "dist", "sideBar.js"))}"></script>
</body>
</html>`;

} } //////////////////////////////////////////////////////////////////////////////// // Stage 6: Preparing Changes (retry) ... ////////////////////////////////////////////////////////////////////////////////

I will update the constructor to initialize executions from the global state, create a method to save the executions to the global state, and call this method whenever the executions array is modified.

REPLACE constructor(private readonly _extensionUri: vscode.Uri, context: vscode.ExtensionContext) { this._context = context; } WITH constructor(private readonly _extensionUri: vscode.Uri, context: vscode.ExtensionContext) { this._context = context; this.commandHistory = this._context.globalState.get("10minions.commandHistory") || { // default command history }; this.executions = this._context.globalState.get("10minions.executions") || []; // Initialize from global state } END_REPLACE

REPLACE public async runMinionOnCurrentSelectionAndEditor(userQuery: string) { const activeEditor = vscode.window.activeTextEditor;

if (!activeEditor) { vscode.window.showErrorMessage("Please open a file before running 10Minions"); return; }

if (activeEditor.document.fileName.endsWith(".log")) { vscode.window.showErrorMessage("Please open a file before running 10Minions"); return; }

const execution = await GPTExecution.create({ userQuery, document: activeEditor.document, selection: activeEditor.selection, selectedText: activeEditor.document.getText(activeEditor.selection), onChanged: async (important) => { if (important) { this.notifyExecutionsUpdatedImmediate(); } else { this.notifyExecutionsUpdated(); } }, });

this.executions = [execution, ...this.executions]; } WITH private async saveExecutions() { await this._context.globalState.update("10minions.executions", this.executions); }

public async runMinionOnCurrentSelectionAndEditor(userQuery: string) { const activeEditor = vscode.window.activeTextEditor;

if (!activeEditor) { vscode.window.showErrorMessage("Please open a file before running 10Minions"); return; }

if (activeEditor.document.fileName.endsWith(".log")) { vscode.window.showErrorMessage("Please open a file before running 10Minions"); return; }

const execution = await GPTExecution.create({ userQuery, document: activeEditor.document, selection: activeEditor.selection, selectedText: activeEditor.document.getText(activeEditor.selection), onChanged: async (important) => { if (important) { this.notifyExecutionsUpdatedImmediate(); } else { this.notifyExecutionsUpdated(); } }, });

this.executions = [execution, ...this.executions]; await this.saveExecutions(); // Save executions to global state } END_REPLACE

REPLACE this.executions = [newExecution, ...this.executions.filter((e) => e.id !== executionId)]; WITH this.executions = [newExecution, ...this.executions.filter((e) => e.id !== executionId)]; await this.saveExecutions(); // Save executions to global state END_REPLACE

REPLACE this.executions = this.executions.filter((e) => e.id !== executionId); WITH this.executions = this.executions.filter((e) => e.id !== executionId); await this.saveExecutions(); // Save executions to global state END_REPLACE

//////////////////////////////////////////////////////////////////////////////// // Stage 7: Applying Changes (retry) ... ////////////////////////////////////////////////////////////////////////////////

Error in applying consolidation: Error: Failed replace

replaceText: public async runMinionOnCurrentSelectionAndEditor(userQuery: string) { const activeEditor = vscode.window.activeTextEditor;

if (!activeEditor) { vscode.window.showErrorMessage("Please open a file before running 10Minions"); return; }

if (activeEditor.document.fileName.endsWith(".log")) { vscode.window.showErrorMessage("Please open a file before running 10Minions"); return; }

const execution = await GPTExecution.create({ userQuery, document: activeEditor.document, selection: activeEditor.selection, selectedText: activeEditor.document.getText(activeEditor.selection), onChanged: async (important) => { if (important) { this.notifyExecutionsUpdatedImmediate(); } else { this.notifyExecutionsUpdated(); } }, });

this.executions = [execution, ...this.executions]; }

originalCode: import { encode } from "gpt-tokenizer"; import * as vscode from "vscode"; import { GPTExecution } from "./GPTExecution"; import { CANCELED_STAGE_NAME, ExecutionInfo } from "./ui/ExecutionInfo"; import { basename } from "path";

export class TenMinionsViewProvider implements vscode.WebviewViewProvider { public static readonly viewType = "10minions.chatView";

private executions: GPTExecution[] = []; private _view?: vscode.WebviewView;

private readonly _context: vscode.ExtensionContext;

constructor(private readonly _extensionUri: vscode.Uri, context: vscode.ExtensionContext) { this._context = context; }

private commandHistory: Record<string, { timeStamp: number }> = {};

private getCommandSuggestion(input: string) { if (!input) return "";

const ONE_DAY = 24 * 60 * 60 * 1000;

const suggestions = Object.keys(this.commandHistory)
  .filter((command) => command.toLowerCase().includes(input.toLowerCase()) && command.toLowerCase() !== input.toLowerCase())
  .map((command) => {
    const { timeStamp } = this.commandHistory[command];
    const daysOld = Math.floor((Date.now() - timeStamp) / ONE_DAY);
    return { command, weight: -daysOld, originalCommand: command };
  })
  .sort((a, b) => b.weight - a.weight);

if (suggestions.length === 0) return "";

const matchedCommand = suggestions[0].originalCommand;
const index = matchedCommand.toLowerCase().indexOf(input.toLowerCase());
const commandWithCorrectedCase = matchedCommand.slice(0, index) + input + matchedCommand.slice(index + input.length);
return commandWithCorrectedCase;

}

private async updateCommandHistory(prompt: string) { const newCommandHistory = { ...this.commandHistory };

if (newCommandHistory[prompt]) {
  newCommandHistory[prompt] = { timeStamp: Date.now() };
} else {
  newCommandHistory[prompt] = { timeStamp: Date.now() };
}

this.commandHistory = newCommandHistory;
await this._context.globalState.update("10minions.commandHistory", newCommandHistory);

}

public resolveWebviewView(webviewView: vscode.WebviewView, context: vscode.WebviewViewResolveContext, _token: vscode.CancellationToken) { this.commandHistory = this._context.globalState.get("10minions.commandHistory") || { "Refactor this": { timeStamp: Date.now() }, "Explain": { timeStamp: Date.now() }, "Make it pretty": { timeStamp: Date.now() }, "Rename this to something sensible": { timeStamp: Date.now() }, "Are there any bugs? Fix them": { timeStamp: Date.now() }, "Rework this so now it also does X": { timeStamp: Date.now() }, }; this._view = webviewView;

// set options for the webview, allow scripts
webviewView.webview.options = {
  enableScripts: true,
  localResourceRoots: [this._extensionUri],
};

const extractTextKey = (uri: vscode.Uri): string => uri.path.match(/^text\/([a-z\d\-]+)/)![1];

const self = this;

class ContentProvider implements vscode.TextDocumentContentProvider {
  constructor() {}

  provideTextDocumentContent(uri: vscode.Uri): string {
    console.log("CONTENT", uri);
    const textKey = extractTextKey(uri);
    const originalContent = self.executions.find((e) => e.id === textKey)?.fullContent;
    return originalContent || "";
  }
}

vscode.workspace.registerTextDocumentContentProvider("10minions", new ContentProvider());

// set the HTML for the webview
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);

// add an event listener for messages received by the webview
webviewView.webview.onDidReceiveMessage(async (data) => {
  console.log("CMD", data);
  const activeEditor = vscode.window.activeTextEditor;

  switch (data.type) {
    case "getTokenCount": {
      let tokenCount = activeEditor ? encode(activeEditor.document.getText()).length : 0;

      this._view?.webview.postMessage({
        type: "tokenCount",
        value: tokenCount,
      });

      break;
    }
    case "newExecution": {
      let prompt = data.value ? data.value : "Refactor this code";

      await this.updateCommandHistory(prompt);

      this.runMinionOnCurrentSelectionAndEditor(prompt);
      break;
    }
    case "openDocument": {
      //if it's open and focused close it

      let documentURI = vscode.Uri.parse(data.documentURI);
      await vscode.workspace.openTextDocument(documentURI);
      await vscode.window.showTextDocument(documentURI);

      break;
    }
    case "showDiff": {
      let executionId = data.executionId;
      let execution = this.executions.find((e) => e.id === executionId);

      if (execution) {
        const makeUriString = (textKey: string): string => `10minions:text/${textKey}`; // `_ts` to avoid cache

        const documentUri = vscode.Uri.parse(execution.documentURI);
        await vscode.commands.executeCommand(
          "vscode.diff",
          vscode.Uri.parse(makeUriString(executionId)),
          documentUri,
          `(original) ↔ ${basename(documentUri.fsPath)}`
        );
      }
      break;
    }
    case "reRunExecution": {
      let executionId = data.executionId;
      let oldExecutionMaybe = this.executions.find((e) => e.id === executionId);

      if (!oldExecutionMaybe) {
        vscode.window.showErrorMessage("No execution found for id", executionId);
        throw new Error(`No execution found for id ${executionId}`);
      }

      let oldExecution = oldExecutionMaybe;

      if (!oldExecution.stopped) {
        vscode.window.showErrorMessage("Execution is still running", executionId);
        break;
      }

      //remove old execution
      this.executions = this.executions.filter((e) => e.id !== executionId);

      //after 1 second add a new one
      setTimeout(async () => {
        let newExecution = await GPTExecution.create({
          userQuery: data.newUserQuery || oldExecution.userQuery,
          document: await oldExecution.document(),
          selection: oldExecution.selection,
          selectedText: oldExecution.selectedText,
          onChanged: async (important) => {
            if (important) {
              this.notifyExecutionsUpdatedImmediate();
            } else {
              this.notifyExecutionsUpdated();
            }
          },
        });

        this.executions = [newExecution, ...this.executions.filter((e) => e.id !== executionId)];

        await newExecution.run();
      }, 1000);

      break;
    }

    case "stopExecution": {
      let executionId = data.executionId;
      let execution = this.executions.find((e) => e.id === executionId);

      if (execution) {
        execution.stopExecution(CANCELED_STAGE_NAME);
      } else {
        console.error("No execution found for id", executionId);
      }

      break;
    }
    case "getSuggestions": {
      const input = data.input || "";
      const suggestion = this.getCommandSuggestion(input);
      console.log(`Suggestion: ${suggestion}`);
      this._view?.webview.postMessage({
        type: "suggestion",
        value: suggestion,
      });
      break;
    }
    case "closeExecution": {
      let executionId = data.executionId;

      let execution = this.executions.find((e) => e.id === executionId);

      if (execution) {
        execution.stopExecution(CANCELED_STAGE_NAME, false);
        this.executions = this.executions.filter((e) => e.id !== executionId);
      } else {
        vscode.window.showErrorMessage("No execution found for id", executionId);
      }

      //notify webview
      this.notifyExecutionsUpdatedImmediate();

      break;
    }
    case "readyForMessages": {
      this._view?.webview.postMessage({
        type: "apiKeySet",
        value: !!vscode.workspace.getConfiguration("10minions").get("apiKey"),
      });

      break;
    }
  }
});

//post message with update to set api key, each time appropriate config is updated
vscode.workspace.onDidChangeConfiguration((e) => {
  console.log(`Changed`);
  if (e.affectsConfiguration("10minions.apiKey")) {
    this._view?.webview.postMessage({
      type: "apiKeySet",
      value: !!vscode.workspace.getConfiguration("10minions").get("apiKey"),
    });
  }
});

}

private _isThrottled = false; private _pendingUpdate = false;

notifyExecutionsUpdated() { if (this._isThrottled) { this._pendingUpdate = true; return; }

this._isThrottled = true;
this.notifyExecutionsUpdatedImmediate();

setTimeout(() => {
  this._isThrottled = false;
  if (this._pendingUpdate) this.notifyExecutionsUpdatedImmediate();
  this._pendingUpdate = false;
}, 500);

}

notifyExecutionsUpdatedImmediate() { const executionInfo: ExecutionInfo[] = this.executions.map((e) => ({ id: e.id, fullContent: e.fullContent, userQuery: e.userQuery, executionStage: e.executionStage, documentName: e.baseName, documentURI: e.documentURI, logFileURI: e.workingDocumentURI, progress: e.progress, stopped: e.stopped, classification: e.classification, modificationDescription: e.modificationDescription, selectedText: e.selectedText, shortName: e.shortName, }));

this._view?.webview.postMessage({
  type: "executionsUpdated",
  executions: executionInfo,
});

}

async clearAndfocusOnInput() { //make sure that our extension bar is visible if (!this._view) { await vscode.commands.executeCommand("10minions.chatView.focus"); } else { this._view?.show?.(true); }

this._view?.webview.postMessage({
  type: "clearAndfocusOnInput",
});

}

async preFillPrompt(prompt: string) { //make sure that our extension bar is visible if (!this._view) { await vscode.commands.executeCommand("10minions.chatView.focus"); } else { this._view?.show?.(true); }

this.runMinionOnCurrentSelectionAndEditor(prompt);

}

public async runMinionOnCurrentSelectionAndEditor(userQuery: string) { const activeEditor = vscode.window.activeTextEditor;

if (!activeEditor) {
  vscode.window.showErrorMessage("Please open a file before running 10Minions");
  return;
}

if (activeEditor.document.fileName.endsWith(".log")) {
  vscode.window.showErrorMessage("Please open a file before running 10Minions");
  return;
}

const execution = await GPTExecution.create({
  userQuery,
  document: activeEditor.document,
  selection: activeEditor.selection,
  selectedText: activeEditor.document.getText(activeEditor.selection),
  onChanged: async (important) => {
    if (important) {
      this.notifyExecutionsUpdatedImmediate();
    } else {
      this.notifyExecutionsUpdated();
    }
  },
});

this.executions = [execution, ...this.executions];

await execution.run();

}

private _getHtmlForWebview(webview: vscode.Webview) { return `

<body>
  <div id="root"></div>
  <script src="${webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "dist", "sideBar.js"))}"></script>
</body>
</html>`;

} } //////////////////////////////////////////////////////////////////////////////// // Stage 8: Applying changes as comment (fall back) ... ////////////////////////////////////////////////////////////////////////////////

PLAIN COMMENT FALLBACK //////////////////////////////////////////////////////////////////////////////// // Stage 9: Finishing ... ////////////////////////////////////////////////////////////////////////////////

Finished (Execution Time: 00:07:38)

cielecki commented 1 year ago

Another one

//////////////////////////////////////////////////////////////////////////////// // Stage 1: Starting ... ////////////////////////////////////////////////////////////////////////////////

File: GPTExecution.ts Task: Add those to constructor as optional, provide defaults

modificationDescription: string; modificationProcedure: string; modificationApplied: boolean; executionStage: string;

//////////////////////////////////////////////////////////////////////////////// // Stage 2: Understanding ... ////////////////////////////////////////////////////////////////////////////////

LocalChange

Classification: LocalChange

//////////////////////////////////////////////////////////////////////////////// // Stage 3: Conceptualising ... ////////////////////////////////////////////////////////////////////////////////

First, we will modify the constructor of the GPTExecution class to accept the new optional parameters and provide default values for them. Here's the updated constructor signature and implementation:

constructor({
  id,
  documentURI,
  workingDocumentURI,
  userQuery,
  selection,
  selectedText,
  fullContent,
  startTime,
  onChanged = async (important: boolean) => {},
  shortName = "",
  modificationDescription = "",
  modificationProcedure = "",
  modificationApplied = false,
  executionStage = "",
}: {
  id: string;
  documentURI: string;
  workingDocumentURI: string;
  userQuery: string;
  selection: vscode.Selection;
  selectedText: string;
  fullContent: string;
  startTime: number;
  onChanged?: (important: boolean) => Promise<void>;
  shortName?: string;
  modificationDescription?: string;
  modificationProcedure?: string;
  modificationApplied?: boolean;
  executionStage?: string;
}) {
  // Constructor implementation
}

Also, we will update the call to the constructor inside the static async create method to pass the new optional parameters with their default values:

new GPTExecution({
  id: executionId,
  documentURI: document.uri.toString(),
  workingDocumentURI: workingDocument.uri.toString(),
  userQuery,
  selection,
  selectedText,
  fullContent: await document.getText(),
  startTime: Date.now(),
  onChanged,
  shortName: "",
  modificationDescription: "",  // Added default value
  modificationProcedure: "",    // Added default value
  modificationApplied: false,   // Added default value
  executionStage: "",           // Added default value
});

With these changes, the constructor now accepts the new optional parameters and assigns default values if they are not provided.

//////////////////////////////////////////////////////////////////////////////// // Stage 4: Preparing Changes ... ////////////////////////////////////////////////////////////////////////////////

I will modify the GPTExecution class constructor to accept new optional parameters and provide default values for them. I will also update the call to the constructor inside the static async create method to pass the new optional parameters with their default values.

REPLACE constructor({ id, documentURI, workingDocumentURI, userQuery, selection, selectedText, fullContent, startTime, onChanged = async (important: boolean) => {}, shortName = "" }: { id: string; documentURI: string; workingDocumentURI: string; userQuery: string; selection: vscode.Selection; selectedText: string; fullContent: string; startTime: number; onChanged?: (important: boolean) => Promise; shortName?: string; }) { // Constructor implementation } WITH constructor({ id, documentURI, workingDocumentURI, userQuery, selection, selectedText, fullContent, startTime, onChanged = async (important: boolean) => {}, shortName = "", modificationDescription = "", modificationProcedure = "", modificationApplied = false, executionStage = "", }: { id: string; documentURI: string; workingDocumentURI: string; userQuery: string; selection: vscode.Selection; selectedText: string; fullContent: string; startTime: number; onChanged?: (important: boolean) => Promise; shortName?: string; modificationDescription?: string; modificationProcedure?: string; modificationApplied?: boolean; executionStage?: string; }) { // Constructor implementation }

REPLACE new GPTExecution({ id: executionId, documentURI: document.uri.toString(), workingDocumentURI: workingDocument.uri.toString(), userQuery, selection, selectedText, fullContent: await document.getText(), startTime: Date.now(), onChanged, shortName: "", }); WITH new GPTExecution({ id: executionId, documentURI: document.uri.toString(), workingDocumentURI: workingDocument.uri.toString(), userQuery, selection, selectedText, fullContent: await document.getText(), startTime: Date.now(), onChanged, shortName: "", modificationDescription: "", // Added default value modificationProcedure: "", // Added default value modificationApplied: false, // Added default value executionStage: "", // Added default value }); END_REPLACE

//////////////////////////////////////////////////////////////////////////////// // Stage 5: Applying Changes ... ////////////////////////////////////////////////////////////////////////////////

Error in applying consolidation: Error: Failed replace

replaceText: constructor({ id, documentURI, workingDocumentURI, userQuery, selection, selectedText, fullContent, startTime, onChanged = async (important: boolean) => {}, shortName = "" }: { id: string; documentURI: string; workingDocumentURI: string; userQuery: string; selection: vscode.Selection; selectedText: string; fullContent: string; startTime: number; onChanged?: (important: boolean) => Promise; shortName?: string; }) { // Constructor implementation }

originalCode: /* 10Minions: I was unable to modify the code myself, but you can do it yourself based on my remarks below:

First, I will describe the plan to implement this feature:

  1. Add a check to the run method of the GPTExecution class to see if any other execution is currently running on the same file.
  2. If there is already a running execution on the file, set the waiting flag to true and add it to a queue.
  3. When an execution completes, check the queue for any waiting executions and start the first one in the queue.

Now, I will elaborate on each step and provide the necessary modifications in the code.

Step 1: Add a check inside the run method to see if any other execution is currently running on the same file.

// Add a static property to GPTExecution class to keep track of running executions
static runningExecutions: Record<string, GPTExecution> = {};

public async run() {
  // Check if an execution is already running on the same file
  if (GPTExecution.runningExecutions[this.documentURI]) {
    this.waiting = true;
    // Add to queue (implementation in Step 2)
  } else {
    // Continue with current execution and add it to runningExecutions
    GPTExecution.runningExecutions[this.documentURI] = this;
    // Rest of the run method implementation...
  }
}

Step 2: Create a queue for waiting executions. If an execution is waiting, add it to the queue.

// Add a static property to GPTExecution class to keep track of waiting executions
static waitingExecutions: Record<string, GPTExecution[]> = {};

// Inside the run() method, where the waiting flag is set to true
if (this.waiting) {
  GPTExecution.waitingExecutions[this.documentURI] = GPTExecution.waitingExecutions[this.documentURI] ?? [];
  GPTExecution.waitingExecutions[this.documentURI].push(this);
}

Step 3: When an execution completes, check the queue for waiting executions and start the next one in the queue.

// Inside the stopExecution method, after setting this.stopped to true
if (this.stopped) {
  delete GPTExecution.runningExecutions[this.documentURI]; // Remove the current execution from runningExecutions
  const waitingExecution = GPTExecution.waitingExecutions[this.documentURI]?.shift();
  if (waitingExecution) {
    waitingExecution.waiting = false;
    waitingExecution.run();
  }
}

The final code modifications include the additions of runningExecutions and waitingExecutions, as well as changes to the run and stopExecution methods in the GPTExecution class. */

import { randomUUID } from "crypto"; import as path from "path"; import as vscode from "vscode"; import { STAGES, TOTAL_WEIGHTS as STAGES_TOTAL_WEIGHTS } from "./stages/config"; import { CANCELED_STAGE_NAME, FINISHED_STAGE_NAME, TASK_CLASSIFICATION_NAME } from "./ui/ExecutionInfo"; import { appendToFile } from "./utils/appendToFile"; import { calculateAndFormatExecutionTime } from "./utils/calculateAndFormatExecutionTime"; import { createWorkingdocument } from "./utils/createWorkingdocument"; import { gptExecute } from "./openai";

export type SerializedGPTExecution = { id: string; documentURI: string; workingDocumentURI: string; userQuery: string; selection: { startLine: number; startCharacter: number; endLine: number; endCharacter: number; }; selectedText: string; fullContent: string; startTime: number; shortName: string; modificationDescription: string; modificationProcedure: string; modificationApplied: boolean; executionStage: string;
classification?: TASK_CLASSIFICATION_NAME; };

export class GPTExecution { serialize(): SerializedGPTExecution { return { id: this.id, documentURI: this.documentURI, workingDocumentURI: this.workingDocumentURI, userQuery: this.userQuery, selection: { startLine: this.selection.start.line, startCharacter: this.selection.start.character, endLine: this.selection.end.line, endCharacter: this.selection.end.character, }, selectedText: this.selectedText, fullContent: this.fullContent, startTime: this.startTime, shortName: this.shortName, modificationDescription: this.modificationDescription, modificationProcedure: this.modificationProcedure, modificationApplied: this.modificationApplied, executionStage: this.executionStage, classification: this.classification, }; }

static deserialize(data: SerializedGPTExecution): GPTExecution { return new GPTExecution({ id: data.id, documentURI: data.documentURI, workingDocumentURI: data.workingDocumentURI, userQuery: data.userQuery, selection: new vscode.Selection( new vscode.Position(data.selection.startLine, data.selection.startCharacter), new vscode.Position(data.selection.endLine, data.selection.endCharacter) ), selectedText: data.selectedText, fullContent: data.fullContent, startTime: data.startTime, shortName: data.shortName, modificationDescription: data.modificationDescription, modificationProcedure: data.modificationProcedure, modificationApplied: data.modificationApplied, executionStage: data.executionStage, classification: data.classification, onChanged: async (important: boolean) => {}, }); }

readonly userQuery: string; readonly id: string;

readonly documentURI: string; readonly workingDocumentURI: string; readonly selection: vscode.Selection; readonly selectedText: string; readonly onChanged: (important: boolean) => Promise;

rejectProgress?: (error: string) => void; resolveProgress?: () => void;

// // tracking variables between stages // shortName: string; fullContent: string; currentStageIndex: number = 0; startTime: number; modificationDescription: string; modificationProcedure: string; modificationApplied: boolean; stopped: boolean = false; progress: number = 0; executionStage: string; classification?: TASK_CLASSIFICATION_NAME; waiting: boolean = false;

constructor({ id, documentURI, workingDocumentURI, userQuery, selection, selectedText, fullContent, startTime, onChanged = async (important: boolean) => {}, shortName = "" }: { id: string; documentURI: string; workingDocumentURI: string; userQuery: string; selection: vscode.Selection; selectedText: string; fullContent: string; startTime: number; onChanged?: (important: boolean) => Promise; shortName?: string; }) { this.id = id; this.documentURI = documentURI; this.workingDocumentURI = workingDocumentURI; this.userQuery = userQuery; this.selection = selection; this.selectedText = selectedText; this.fullContent = fullContent; this.startTime = startTime; this.onChanged = onChanged; this }

static async create({ userQuery, document, selection, selectedText, onChanged, }: { userQuery: string; document: vscode.TextDocument; selection: vscode.Selection; selectedText: string; onChanged: (important: boolean) => Promise; }): Promise { const executionId = randomUUID(); const workingDocument = await createWorkingdocument(executionId);

return new GPTExecution({
  id: executionId,
  documentURI: document.uri.toString(),
  workingDocumentURI: workingDocument.uri.toString(),
  userQuery,
  selection,
  selectedText,
  fullContent: await document.getText(),
  startTime: Date.now(),
  onChanged,
});

}

public async document() { let document = await vscode.workspace.openTextDocument(vscode.Uri.parse(this.documentURI)); return document; }

public async stopExecution(error?: string, important: boolean = true) { if (this.stopped) { return; }

this.stopped = true;
this.executionStage = error ? error : FINISHED_STAGE_NAME;

if (this.rejectProgress && error) this.rejectProgress(error);
else if (this.resolveProgress) this.resolveProgress();

this.rejectProgress = undefined;
this.resolveProgress = undefined;

//delete tmp file
//vscode.workspace.fs.delete(refDocument.uri);
await this.onChanged(important);

}

public reportSmallProgress(fractionOfBigTask: number = 0.005) { const weigtsNextStepTotal = STAGES.reduce((acc, stage, index) => { if (index > this.currentStageIndex) { return acc; } return acc + stage.weight; }, 0);

const remainingProgress = (1.0 * weigtsNextStepTotal) / STAGES_TOTAL_WEIGHTS;
const currentProgress = this.progress;

const totalPending = remainingProgress - currentProgress;
let increment = totalPending * fractionOfBigTask;
this.progress = this.progress + increment;
this.onChanged(false);

}

public async run() { return new Promise(async (resolve, reject) => { this.resolveProgress = resolve; this.rejectProgress = reject; this.currentStageIndex = 0;

  this.setShortName();

  try {
    while (this.currentStageIndex < STAGES.length && !this.stopped) {
      this.executionStage = STAGES[this.currentStageIndex].name;

      appendToFile(
        this.workingDocumentURI,
        [
          `////////////////////////////////////////////////////////////////////////////////`,
          `// Stage ${this.currentStageIndex + 1}: ${this.executionStage}`,
          `////////////////////////////////////////////////////////////////////////////////`,
        ].join("\n") + "\n\n"
      );

      await STAGES[this.currentStageIndex].execution.apply(this);

      if (this.stopped) {
        break;
      }

      const weigtsNextStepTotal = STAGES.reduce((acc, stage, index) => {
        if (index > this.currentStageIndex) {
          return acc;
        }
        return acc + stage.weight;
      }, 0);

      this.progress = weigtsNextStepTotal / STAGES_TOTAL_WEIGHTS;
      this.onChanged(false);
      this.currentStageIndex++;
    }
  } catch (error) {
    if (error !== CANCELED_STAGE_NAME) {
      vscode.window.showErrorMessage(`Error in execution: ${error}`);
      console.log("Error in execution", error);
    }

    this.stopExecution(String(error));
  } finally {
    const executionTime = Date.now() - this.startTime;
    const formattedExecutionTime = calculateAndFormatExecutionTime(executionTime);

    await appendToFile(this.workingDocumentURI, `${this.executionStage} (Execution Time: ${formattedExecutionTime})\n\n`);
    this.progress = 1;
  }
});

}

private async setShortName() { this.shortName = "..."; let context = this.selectedText ? ==== WHAT USER SELECTED ==== ${this.selectedText} .trim() : ==== WHAT IS THE NAME OF THE FILE ==== ${this.baseName} .trim();

await gptExecute({
  maxTokens: 20,
  fullPrompt: `
  Create a very short summary of a task. Maximum of 20 characters. You MUST not exceed this number. Try to combine info both from what user said and what user selected / file name. If a selected identifier is too long or file name is too long, just use some keywords from it.

  ==== WHAT USER SAID ====
  ${this.userQuery}

  ${context}

  `.trim(),
}).then((res) => {
  this.shortName = res || this.baseName;
  this.onChanged(true);
});

}

get baseName() { return path.basename(vscode.Uri.parse(this.documentURI).fsPath); } } //////////////////////////////////////////////////////////////////////////////// // Stage 6: Preparing Changes (retry) ... ////////////////////////////////////////////////////////////////////////////////

I will modify the constructor of the GPTExecution class to accept the new optional parameters and provide default values for them. I will also update the call to the constructor inside the static async create method to pass the new optional parameters with their default values.

REPLACE constructor({ id, documentURI, workingDocumentURI, userQuery, selection, selectedText, fullContent, startTime, onChanged = async (important: boolean) => {}, shortName = "" }: { WITH constructor({ id, documentURI, workingDocumentURI, userQuery, selection, selectedText, fullContent, startTime, onChanged = async (important: boolean) => {}, shortName = "", modificationDescription = "", modificationProcedure = "", modificationApplied = false, executionStage = "", }: { END_REPLACE

REPLACE new GPTExecution({ id: executionId, documentURI: document.uri.toString(), workingDocumentURI: workingDocument.uri.toString(), userQuery, selection, selectedText, fullContent: await document.getText(), startTime: Date.now(), onChanged, shortName: "" }); WITH new GPTExecution({ id: executionId, documentURI: document.uri.toString(), workingDocumentURI: workingDocument.uri.toString(), userQuery, selection, selectedText, fullContent: await document.getText(), startTime: Date.now(), onChanged, shortName: "", modificationDescription: "", // Added default value modificationProcedure: "", // Added default value modificationApplied: false, // Added default value executionStage: "", // Added default value }); END_REPLACE

//////////////////////////////////////////////////////////////////////////////// // Stage 7: Applying Changes (retry) ... ////////////////////////////////////////////////////////////////////////////////

Error in applying consolidation: Error: Failed replace

replaceText: new GPTExecution({ id: executionId, documentURI: document.uri.toString(), workingDocumentURI: workingDocument.uri.toString(), userQuery, selection, selectedText, fullContent: await document.getText(), startTime: Date.now(), onChanged, shortName: "" });

originalCode: /* 10Minions: I was unable to modify the code myself, but you can do it yourself based on my remarks below:

First, I will describe the plan to implement this feature:

  1. Add a check to the run method of the GPTExecution class to see if any other execution is currently running on the same file.
  2. If there is already a running execution on the file, set the waiting flag to true and add it to a queue.
  3. When an execution completes, check the queue for any waiting executions and start the first one in the queue.

Now, I will elaborate on each step and provide the necessary modifications in the code.

Step 1: Add a check inside the run method to see if any other execution is currently running on the same file.

// Add a static property to GPTExecution class to keep track of running executions
static runningExecutions: Record<string, GPTExecution> = {};

public async run() {
  // Check if an execution is already running on the same file
  if (GPTExecution.runningExecutions[this.documentURI]) {
    this.waiting = true;
    // Add to queue (implementation in Step 2)
  } else {
    // Continue with current execution and add it to runningExecutions
    GPTExecution.runningExecutions[this.documentURI] = this;
    // Rest of the run method implementation...
  }
}

Step 2: Create a queue for waiting executions. If an execution is waiting, add it to the queue.

// Add a static property to GPTExecution class to keep track of waiting executions
static waitingExecutions: Record<string, GPTExecution[]> = {};

// Inside the run() method, where the waiting flag is set to true
if (this.waiting) {
  GPTExecution.waitingExecutions[this.documentURI] = GPTExecution.waitingExecutions[this.documentURI] ?? [];
  GPTExecution.waitingExecutions[this.documentURI].push(this);
}

Step 3: When an execution completes, check the queue for waiting executions and start the next one in the queue.

// Inside the stopExecution method, after setting this.stopped to true
if (this.stopped) {
  delete GPTExecution.runningExecutions[this.documentURI]; // Remove the current execution from runningExecutions
  const waitingExecution = GPTExecution.waitingExecutions[this.documentURI]?.shift();
  if (waitingExecution) {
    waitingExecution.waiting = false;
    waitingExecution.run();
  }
}

The final code modifications include the additions of runningExecutions and waitingExecutions, as well as changes to the run and stopExecution methods in the GPTExecution class. */

import { randomUUID } from "crypto"; import as path from "path"; import as vscode from "vscode"; import { STAGES, TOTAL_WEIGHTS as STAGES_TOTAL_WEIGHTS } from "./stages/config"; import { CANCELED_STAGE_NAME, FINISHED_STAGE_NAME, TASK_CLASSIFICATION_NAME } from "./ui/ExecutionInfo"; import { appendToFile } from "./utils/appendToFile"; import { calculateAndFormatExecutionTime } from "./utils/calculateAndFormatExecutionTime"; import { createWorkingdocument } from "./utils/createWorkingdocument"; import { gptExecute } from "./openai";

export type SerializedGPTExecution = { id: string; documentURI: string; workingDocumentURI: string; userQuery: string; selection: { startLine: number; startCharacter: number; endLine: number; endCharacter: number; }; selectedText: string; fullContent: string; startTime: number; shortName: string; modificationDescription: string; modificationProcedure: string; modificationApplied: boolean; executionStage: string;
classification?: TASK_CLASSIFICATION_NAME; };

export class GPTExecution { serialize(): SerializedGPTExecution { return { id: this.id, documentURI: this.documentURI, workingDocumentURI: this.workingDocumentURI, userQuery: this.userQuery, selection: { startLine: this.selection.start.line, startCharacter: this.selection.start.character, endLine: this.selection.end.line, endCharacter: this.selection.end.character, }, selectedText: this.selectedText, fullContent: this.fullContent, startTime: this.startTime, shortName: this.shortName, modificationDescription: this.modificationDescription, modificationProcedure: this.modificationProcedure, modificationApplied: this.modificationApplied, executionStage: this.executionStage, classification: this.classification, }; }

static deserialize(data: SerializedGPTExecution): GPTExecution { return new GPTExecution({ id: data.id, documentURI: data.documentURI, workingDocumentURI: data.workingDocumentURI, userQuery: data.userQuery, selection: new vscode.Selection( new vscode.Position(data.selection.startLine, data.selection.startCharacter), new vscode.Position(data.selection.endLine, data.selection.endCharacter) ), selectedText: data.selectedText, fullContent: data.fullContent, startTime: data.startTime, shortName: data.shortName, modificationDescription: data.modificationDescription, modificationProcedure: data.modificationProcedure, modificationApplied: data.modificationApplied, executionStage: data.executionStage, classification: data.classification, onChanged: async (important: boolean) => {}, }); }

readonly userQuery: string; readonly id: string;

readonly documentURI: string; readonly workingDocumentURI: string; readonly selection: vscode.Selection; readonly selectedText: string; readonly onChanged: (important: boolean) => Promise;

rejectProgress?: (error: string) => void; resolveProgress?: () => void;

// // tracking variables between stages // shortName: string; fullContent: string; currentStageIndex: number = 0; startTime: number; modificationDescription: string; modificationProcedure: string; modificationApplied: boolean; stopped: boolean = false; progress: number = 0; executionStage: string; classification?: TASK_CLASSIFICATION_NAME; waiting: boolean = false;

constructor({ id, documentURI, workingDocumentURI, userQuery, selection, selectedText, fullContent, startTime, onChanged = async (important: boolean) => {}, shortName = "" }: { id: string; documentURI: string; workingDocumentURI: string; userQuery: string; selection: vscode.Selection; selectedText: string; fullContent: string; startTime: number; onChanged?: (important: boolean) => Promise; shortName?: string; }) { this.id = id; this.documentURI = documentURI; this.workingDocumentURI = workingDocumentURI; this.userQuery = userQuery; this.selection = selection; this.selectedText = selectedText; this.fullContent = fullContent; this.startTime = startTime; this.onChanged = onChanged; this }

static async create({ userQuery, document, selection, selectedText, onChanged, }: { userQuery: string; document: vscode.TextDocument; selection: vscode.Selection; selectedText: string; onChanged: (important: boolean) => Promise; }): Promise { const executionId = randomUUID(); const workingDocument = await createWorkingdocument(executionId);

return new GPTExecution({
  id: executionId,
  documentURI: document.uri.toString(),
  workingDocumentURI: workingDocument.uri.toString(),
  userQuery,
  selection,
  selectedText,
  fullContent: await document.getText(),
  startTime: Date.now(),
  onChanged,
});

}

public async document() { let document = await vscode.workspace.openTextDocument(vscode.Uri.parse(this.documentURI)); return document; }

public async stopExecution(error?: string, important: boolean = true) { if (this.stopped) { return; }

this.stopped = true;
this.executionStage = error ? error : FINISHED_STAGE_NAME;

if (this.rejectProgress && error) this.rejectProgress(error);
else if (this.resolveProgress) this.resolveProgress();

this.rejectProgress = undefined;
this.resolveProgress = undefined;

//delete tmp file
//vscode.workspace.fs.delete(refDocument.uri);
await this.onChanged(important);

}

public reportSmallProgress(fractionOfBigTask: number = 0.005) { const weigtsNextStepTotal = STAGES.reduce((acc, stage, index) => { if (index > this.currentStageIndex) { return acc; } return acc + stage.weight; }, 0);

const remainingProgress = (1.0 * weigtsNextStepTotal) / STAGES_TOTAL_WEIGHTS;
const currentProgress = this.progress;

const totalPending = remainingProgress - currentProgress;
let increment = totalPending * fractionOfBigTask;
this.progress = this.progress + increment;
this.onChanged(false);

}

public async run() { return new Promise(async (resolve, reject) => { this.resolveProgress = resolve; this.rejectProgress = reject; this.currentStageIndex = 0;

  this.setShortName();

  try {
    while (this.currentStageIndex < STAGES.length && !this.stopped) {
      this.executionStage = STAGES[this.currentStageIndex].name;

      appendToFile(
        this.workingDocumentURI,
        [
          `////////////////////////////////////////////////////////////////////////////////`,
          `// Stage ${this.currentStageIndex + 1}: ${this.executionStage}`,
          `////////////////////////////////////////////////////////////////////////////////`,
        ].join("\n") + "\n\n"
      );

      await STAGES[this.currentStageIndex].execution.apply(this);

      if (this.stopped) {
        break;
      }

      const weigtsNextStepTotal = STAGES.reduce((acc, stage, index) => {
        if (index > this.currentStageIndex) {
          return acc;
        }
        return acc + stage.weight;
      }, 0);

      this.progress = weigtsNextStepTotal / STAGES_TOTAL_WEIGHTS;
      this.onChanged(false);
      this.currentStageIndex++;
    }
  } catch (error) {
    if (error !== CANCELED_STAGE_NAME) {
      vscode.window.showErrorMessage(`Error in execution: ${error}`);
      console.log("Error in execution", error);
    }

    this.stopExecution(String(error));
  } finally {
    const executionTime = Date.now() - this.startTime;
    const formattedExecutionTime = calculateAndFormatExecutionTime(executionTime);

    await appendToFile(this.workingDocumentURI, `${this.executionStage} (Execution Time: ${formattedExecutionTime})\n\n`);
    this.progress = 1;
  }
});

}

private async setShortName() { this.shortName = "..."; let context = this.selectedText ? ==== WHAT USER SELECTED ==== ${this.selectedText} .trim() : ==== WHAT IS THE NAME OF THE FILE ==== ${this.baseName} .trim();

await gptExecute({
  maxTokens: 20,
  fullPrompt: `
  Create a very short summary of a task. Maximum of 20 characters. You MUST not exceed this number. Try to combine info both from what user said and what user selected / file name. If a selected identifier is too long or file name is too long, just use some keywords from it.

  ==== WHAT USER SAID ====
  ${this.userQuery}

  ${context}

  `.trim(),
}).then((res) => {
  this.shortName = res || this.baseName;
  this.onChanged(true);
});

}

get baseName() { return path.basename(vscode.Uri.parse(this.documentURI).fsPath); } } //////////////////////////////////////////////////////////////////////////////// // Stage 8: Applying changes as comment (fall back) ... ////////////////////////////////////////////////////////////////////////////////

PLAIN COMMENT FALLBACK //////////////////////////////////////////////////////////////////////////////// // Stage 9: Finishing ... ////////////////////////////////////////////////////////////////////////////////

Finished (Execution Time: 00:05:02)

cielecki commented 1 year ago

Next:

//////////////////////////////////////////////////////////////////////////////// // Stage 1: Starting ... ////////////////////////////////////////////////////////////////////////////////

File: blendColors.tsx Task: refactor this, provide more generic functions than getMinionBlendedColor

//////////////////////////////////////////////////////////////////////////////// // Stage 2: Understanding ... ////////////////////////////////////////////////////////////////////////////////

LocalChange

Classification: LocalChange

//////////////////////////////////////////////////////////////////////////////// // Stage 3: Conceptualising ... ////////////////////////////////////////////////////////////////////////////////

Firstly, I will change the structure of the code to have more generic function names and separate the existing logic into smaller, reusable functions. I will change the function getMinionBlendedColor to getBlendedColor, making it more generic by taking in a blendRatio as a parameter. I will also split the logic in the getOpacityAndColor function into getOpacity and getColor functions respectively. Finally, I will update getColor to accept a fallback color as a parameter.

Here is the refactored code:

export function getOpacity(execution: ExecutionInfo) {
  if (execution.executionStage === CANCELED_STAGE_NAME) {
    return "opacity-0";
  }

  return "opacity-1";
}

export function getColor(
  execution: ExecutionInfo,
  fallbackColor: string = BRAND_COLOR
) {
  if (execution.stopped) {
    const errorColor = getComputedStyle(
      document.documentElement
    ).getPropertyValue(
      "--vscode-terminalCommandDecoration-errorBackground"
    ).trim();

    if (execution.executionStage === FINISHED_STAGE_NAME) {
      return getComputedStyle(
        document.documentElement
      ).getPropertyValue(
        "--vscode-terminalCommandDecoration-successBackground"
      ).trim();
    }

    if (execution.executionStage === CANCELED_STAGE_NAME) {
      return (
        fallbackColor ||
        getComputedStyle(
          document.documentElement
        ).getPropertyValue("--vscode-sideBar-background").trim()
      );
    }

    return errorColor;
  }

  return fallbackColor;
}

export function getBlendedColor(
  execution: ExecutionInfo,
  blendRatio: number = 0.25
) {
  return blendColors(
    getComputedStyle(
      document.documentElement
    ).getPropertyValue("--vscode-sideBar-foreground").trim(),
    getColor(execution),
    blendRatio
  );
}

Now, the getOpacity function handles the opacity logic while getColor determines the appropriate color based on the execution stage. The getBlendedColor function is more generic and works with the provided blendRatio parameter.

//////////////////////////////////////////////////////////////////////////////// // Stage 4: Preparing Changes ... ////////////////////////////////////////////////////////////////////////////////

I will refactor the code by changing the structure to have more generic function names and separate the existing logic into smaller, reusable functions. I will change the function getMinionBlendedColor to getBlendedColor, making it more generic by taking in a blendRatio as a parameter. I will also split the logic in the getOpacityAndColor function into getOpacity and getColor functions respectively. Finally, I will update getColor to accept a fallback color as a parameter.

REPLACE export function getOpacityAndColor(execution: ExecutionInfo) { let opacity = "opacity-1"; let color = "#5e20e5";

if (execution.stopped) { color = getComputedStyle(document.documentElement).getPropertyValue('--vscode-terminalCommandDecoration-errorBackground').trim()

if (execution.executionStage === FINISHED_STAGE_NAME) {
  color = getComputedStyle(document.documentElement).getPropertyValue('--vscode-terminalCommandDecoration-successBackground').trim();
}

if (execution.executionStage === CANCELED_STAGE_NAME) {
  color = getComputedStyle(document.documentElement).getPropertyValue('--vscode-sideBar-background').trim();
  opacity = "opacity-0";
}

}

return { opacity, color }; }

export function getMinionBlendedColor(execution: ExecutionInfo) { return blendColors(getComputedStyle(document.documentElement).getPropertyValue('--vscode-sideBar-foreground').trim(), getOpacityAndColor(execution).color, 0.25); } WITH export function getOpacity(execution: ExecutionInfo) { if (execution.executionStage === CANCELED_STAGE_NAME) { return "opacity-0"; }

return "opacity-1"; }

export function getColor( execution: ExecutionInfo, fallbackColor: string = BRAND_COLOR ) { if (execution.stopped) { const errorColor = getComputedStyle( document.documentElement ).getPropertyValue( "--vscode-terminalCommandDecoration-errorBackground" ).trim();

if (execution.executionStage === FINISHED_STAGE_NAME) {
  return getComputedStyle(
    document.documentElement
  ).getPropertyValue(
    "--vscode-terminalCommandDecoration-successBackground"
  ).trim();
}

if (execution.executionStage === CANCELED_STAGE_NAME) {
  return (
    fallbackColor ||
    getComputedStyle(
      document.documentElement
    ).getPropertyValue("--vscode-sideBar-background").trim()
  );
}

return errorColor;

}

return fallbackColor; }

export function getBlendedColor( execution: ExecutionInfo, blendRatio: number = 0.25 ) { return blendColors( getComputedStyle( document.documentElement ).getPropertyValue("--vscode-sideBar-foreground").trim(), getColor(execution), blendRatio ); } END_REPLACE

//////////////////////////////////////////////////////////////////////////////// // Stage 5: Applying Changes ... ////////////////////////////////////////////////////////////////////////////////

Error in applying consolidation: Error: Failed replace

replaceText: export function getOpacityAndColor(execution: ExecutionInfo) { let opacity = "opacity-1"; let color = "#5e20e5";

if (execution.stopped) { color = getComputedStyle(document.documentElement).getPropertyValue('--vscode-terminalCommandDecoration-errorBackground').trim()

if (execution.executionStage === FINISHED_STAGE_NAME) {
  color = getComputedStyle(document.documentElement).getPropertyValue('--vscode-terminalCommandDecoration-successBackground').trim();
}

if (execution.executionStage === CANCELED_STAGE_NAME) {
  color = getComputedStyle(document.documentElement).getPropertyValue('--vscode-sideBar-background').trim();
  opacity = "opacity-0";
}

}

return { opacity, color }; }

export function getMinionBlendedColor(execution: ExecutionInfo) { return blendColors(getComputedStyle(document.documentElement).getPropertyValue('--vscode-sideBar-foreground').trim(), getOpacityAndColor(execution).color, 0.25); }

originalCode: import { CANCELED_STAGE_NAME, ExecutionInfo, FINISHED_STAGE_NAME } from "../ui/ExecutionInfo";

export const BRAND_COLOR = "#5e20e5"; export const ERROR_COLOR = "#D8595A"; export const SUCESS_COLOR = "#2AB678";

export function getOpacityAndColor(execution: ExecutionInfo) { let opacity = "opacity-1"; let color = "#5e20e5";

if (execution.stopped) { color = getComputedStyle(document.documentElement).getPropertyValue('--vscode-terminalCommandDecoration-errorBackground').trim()

if (execution.executionStage === FINISHED_STAGE_NAME) {
  color = getComputedStyle(document.documentElement).getPropertyValue('--vscode-terminalCommandDecoration-successBackground').trim();
}

if (execution.executionStage === CANCELED_STAGE_NAME) {
  color = getComputedStyle(document.documentElement).getPropertyValue('--vscode-sideBar-background').trim();
  opacity = "opacity-0";
}

}

return { opacity, color }; }

export function blendColors(color1: string, color2: string, blendRatio: number) { const r1 = parseInt(color1.substr(1, 2), 16); const g1 = parseInt(color1.substr(3, 2), 16); const b1 = parseInt(color1.substr(5, 2), 16);

const r2 = parseInt(color2.substr(1, 2), 16); const g2 = parseInt(color2.substr(3, 2), 16); const b2 = parseInt(color2.substr(5, 2), 16);

const r = Math.round(r1 blendRatio + r2 (1 - blendRatio)); const g = Math.round(g1 blendRatio + g2 (1 - blendRatio)); const b = Math.round(b1 blendRatio + b2 (1 - blendRatio));

return #${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}; }

export function getMinionBlendedColor(execution: ExecutionInfo) { return blendColors(getComputedStyle(document.documentElement).getPropertyValue('--vscode-sideBar-foreground').trim(), getOpacityAndColor(execution).color, 0.25); } //////////////////////////////////////////////////////////////////////////////// // Stage 6: Preparing Changes (retry) ... ////////////////////////////////////////////////////////////////////////////////

I will refactor the code by changing the structure to have more generic function names and separate the existing logic into smaller, reusable functions. I will change the function getMinionBlendedColor to getBlendedColor, making it more generic by taking in a blendRatio as a parameter. I will also split the logic in the getOpacityAndColor function into getOpacity and getColor functions respectively. Finally, I will update getColor to accept a fallback color as a parameter.

REPLACE export function getOpacityAndColor(execution: ExecutionInfo) { let opacity = "opacity-1"; let color = "#5e20e5";

if (execution.stopped) { color = getComputedStyle(document.documentElement).getPropertyValue('--vscode-terminalCommandDecoration-errorBackground').trim()

if (execution.executionStage === FINISHED_STAGE_NAME) {
  color = getComputedStyle(document.documentElement).getPropertyValue('--vscode-terminalCommandDecoration-successBackground').trim();
}

if (execution.executionStage === CANCELED_STAGE_NAME) {
  color = getComputedStyle(document.documentElement).getPropertyValue('--vscode-sideBar-background').trim();
  opacity = "opacity-0";
}

}

return { opacity, color }; }

export function getMinionBlendedColor(execution: ExecutionInfo) { return blendColors(getComputedStyle(document.documentElement).getPropertyValue('--vscode-sideBar-foreground').trim(), getOpacityAndColor(execution).color, 0.25); } WITH export function getOpacity(execution: ExecutionInfo) { if (execution.executionStage === CANCELED_STAGE_NAME) { return "opacity-0"; }

return "opacity-1"; }

export function getColor( execution: ExecutionInfo, fallbackColor: string = BRAND_COLOR ) { if (execution.stopped) { const errorColor = getComputedStyle( document.documentElement ).getPropertyValue( "--vscode-terminalCommandDecoration-errorBackground" ).trim();

if (execution.executionStage === FINISHED_STAGE_NAME) {
  return getComputedStyle(
    document.documentElement
  ).getPropertyValue(
    "--vscode-terminalCommandDecoration-successBackground"
  ).trim();
}

if (execution.executionStage === CANCELED_STAGE_NAME) {
  return (
    fallbackColor ||
    getComputedStyle(
      document.documentElement
    ).getPropertyValue("--vscode-sideBar-background").trim()
  );
}

return errorColor;

}

return fallbackColor; }

export function getBlendedColor( execution: ExecutionInfo, blendRatio: number = 0.25 ) { return blendColors( getComputedStyle( document.documentElement ).getPropertyValue("--vscode-sideBar-foreground").trim(), getColor(execution), blendRatio ); } END_REPLACE

//////////////////////////////////////////////////////////////////////////////// // Stage 7: Applying Changes (retry) ... ////////////////////////////////////////////////////////////////////////////////

Error in applying consolidation: Error: Failed replace

replaceText: export function getOpacityAndColor(execution: ExecutionInfo) { let opacity = "opacity-1"; let color = "#5e20e5";

if (execution.stopped) { color = getComputedStyle(document.documentElement).getPropertyValue('--vscode-terminalCommandDecoration-errorBackground').trim()

if (execution.executionStage === FINISHED_STAGE_NAME) {
  color = getComputedStyle(document.documentElement).getPropertyValue('--vscode-terminalCommandDecoration-successBackground').trim();
}

if (execution.executionStage === CANCELED_STAGE_NAME) {
  color = getComputedStyle(document.documentElement).getPropertyValue('--vscode-sideBar-background').trim();
  opacity = "opacity-0";
}

}

return { opacity, color }; }

export function getMinionBlendedColor(execution: ExecutionInfo) { return blendColors(getComputedStyle(document.documentElement).getPropertyValue('--vscode-sideBar-foreground').trim(), getOpacityAndColor(execution).color, 0.25); }

originalCode: import { CANCELED_STAGE_NAME, ExecutionInfo, FINISHED_STAGE_NAME } from "../ui/ExecutionInfo";

export const BRAND_COLOR = "#5e20e5"; export const ERROR_COLOR = "#D8595A"; export const SUCESS_COLOR = "#2AB678";

export function getOpacityAndColor(execution: ExecutionInfo) { let opacity = "opacity-1"; let color = "#5e20e5";

if (execution.stopped) { color = getComputedStyle(document.documentElement).getPropertyValue('--vscode-terminalCommandDecoration-errorBackground').trim()

if (execution.executionStage === FINISHED_STAGE_NAME) {
  color = getComputedStyle(document.documentElement).getPropertyValue('--vscode-terminalCommandDecoration-successBackground').trim();
}

if (execution.executionStage === CANCELED_STAGE_NAME) {
  color = getComputedStyle(document.documentElement).getPropertyValue('--vscode-sideBar-background').trim();
  opacity = "opacity-0";
}

}

return { opacity, color }; }

export function blendColors(color1: string, color2: string, blendRatio: number) { const r1 = parseInt(color1.substr(1, 2), 16); const g1 = parseInt(color1.substr(3, 2), 16); const b1 = parseInt(color1.substr(5, 2), 16);

const r2 = parseInt(color2.substr(1, 2), 16); const g2 = parseInt(color2.substr(3, 2), 16); const b2 = parseInt(color2.substr(5, 2), 16);

const r = Math.round(r1 blendRatio + r2 (1 - blendRatio)); const g = Math.round(g1 blendRatio + g2 (1 - blendRatio)); const b = Math.round(b1 blendRatio + b2 (1 - blendRatio));

return #${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}; }

export function getMinionBlendedColor(execution: ExecutionInfo) { return blendColors(getComputedStyle(document.documentElement).getPropertyValue('--vscode-sideBar-foreground').trim(), getOpacityAndColor(execution).color, 0.25); } //////////////////////////////////////////////////////////////////////////////// // Stage 8: Applying changes as comment (fall back) ... ////////////////////////////////////////////////////////////////////////////////

PLAIN COMMENT FALLBACK //////////////////////////////////////////////////////////////////////////////// // Stage 9: Finishing ... ////////////////////////////////////////////////////////////////////////////////

Finished (Execution Time: 00:03:42)

cielecki commented 1 year ago

Not enough data