kopia lustrzana https://github.com/biobootloader/wolverine
Integrate openai into my editor
rodzic
81143a3852
commit
b3ed8de3b4
|
@ -0,0 +1,15 @@
|
|||
const countCharacters = (text) => text.replace(/\n/g, '').length;
|
||||
const countNewLines = (text) => text.match(/\n/g)?.length || 0;
|
||||
const countTextStream = (textStream) => {
|
||||
const numberOfCharacters = countCharacters(textStream);
|
||||
const numberofNewLines = countNewLines(textStream);
|
||||
return { numberOfCharacters, numberofNewLines };
|
||||
};
|
||||
|
||||
const main = () => {
|
||||
const textStream = "A B C D E F G H I J K L M N O P Q \n\n R S T U V";
|
||||
const result = countTextStream(textStream);
|
||||
console.log(result);
|
||||
};
|
||||
|
||||
main();
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/naming-convention": "warn",
|
||||
"@typescript-eslint/semi": "warn",
|
||||
"curly": "warn",
|
||||
"eqeqeq": "warn",
|
||||
"no-throw-literal": "warn",
|
||||
"semi": "off"
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"out",
|
||||
"dist",
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
out
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
|
@ -0,0 +1,13 @@
|
|||
.vscode/**
|
||||
.vscode-test/**
|
||||
out/**
|
||||
node_modules/**
|
||||
src/**
|
||||
.gitignore
|
||||
.yarnrc
|
||||
webpack.config.js
|
||||
vsc-extension-quickstart.md
|
||||
**/tsconfig.json
|
||||
**/.eslintrc.json
|
||||
**/*.map
|
||||
**/*.ts
|
|
@ -0,0 +1,9 @@
|
|||
# Change Log
|
||||
|
||||
All notable changes to the "wolverine" extension will be documented in this file.
|
||||
|
||||
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- Initial release
|
|
@ -0,0 +1,10 @@
|
|||
Hey chatGPT! This is some markdown, could you rewrite this to be a bit friendlier?
|
||||
|
||||
# wolverine README
|
||||
It's 2020 + 3. Let's stop tabbing away from our text editor, shall we?
|
||||
|
||||
#set up workspace settings
|
||||
|
||||
### Features
|
||||
- To auto-correct code, highlight the code and press ctrl+k ctrl+h, which will prompt GPT-3.5 and stream the fixed code back in.
|
||||
- If nothing is highlighted, the extension will fix the whole file.
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,79 @@
|
|||
{
|
||||
"name": "wolverine",
|
||||
"displayName": "wolverine",
|
||||
"description": "A toolkit to help you interact with your editor using LLMs",
|
||||
"repository": "https://github.com/biobootloader/wolverine",
|
||||
"version": "0.0.1",
|
||||
"engines": {
|
||||
"vscode": "^1.77.0"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [],
|
||||
"main": "./dist/extension.js",
|
||||
"contributes": {
|
||||
"configuration": {
|
||||
"title": "wolverine",
|
||||
"properties": {
|
||||
"wolverine.UNSAFE.OpenaiApiKeySetting": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "OpenAI api key. You shouldn't paste your key in raw like this, unless you know what you're doing. Set a limit, vscode is a highly suspect environment, and your tokens might get pwn'd. Only do this if you're okay with someone naughty running away with your key!!\n\nIf only we had the weights running locally... emad...."
|
||||
},
|
||||
"wolverine.model": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "model, openai only right now"
|
||||
},
|
||||
"wolverine.prompt": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The prompt. The selected text is immediately added after"
|
||||
}
|
||||
}
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"command": "wolverine.directedHeal",
|
||||
"title": "Directed Heal"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"vsce": "vsce package",
|
||||
"vscode:prepublish": "npm run esbuild-base -- --minify",
|
||||
"esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/main.js --external:vscode --format=cjs --platform=node",
|
||||
"esbuild": "npm run esbuild-base -- --sourcemap",
|
||||
"esbuild-watch": "npm run esbuild-base -- --sourcemap --watch",
|
||||
"test-compile": "tsc -p ./",
|
||||
"watch": "webpack --watch",
|
||||
"package": "webpack --mode production --devtool hidden-source-map",
|
||||
"compile-tests": "tsc -p . --outDir out",
|
||||
"watch-tests": "tsc -p . -w --outDir out",
|
||||
"pretest": "npm run compile-tests && npm run compile && npm run lint",
|
||||
"lint": "eslint src --ext ts",
|
||||
"test": "node ./out/test/runTest.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/glob": "^8.1.0",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@types/node": "16.x",
|
||||
"@types/vscode": "^1.77.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.56.0",
|
||||
"@typescript-eslint/parser": "^5.56.0",
|
||||
"@vscode/test-electron": "^2.3.0",
|
||||
"esbuild": "^0.17.17",
|
||||
"eslint": "^8.36.0",
|
||||
"glob": "^8.1.0",
|
||||
"mocha": "^10.2.0",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.5",
|
||||
"webpack": "^5.76.3",
|
||||
"webpack-cli": "^5.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.3.6",
|
||||
"vsce": "^2.15.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
import {
|
||||
ExtensionContext,
|
||||
workspace,
|
||||
Position,
|
||||
TextEditor,
|
||||
Range,
|
||||
commands,
|
||||
window
|
||||
} from 'vscode';
|
||||
import axios from 'axios';
|
||||
|
||||
let openaikey = '';
|
||||
|
||||
// See https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events
|
||||
// Responsiblity of caller to register event listener.
|
||||
const streamCompletion = async (prompt: string, onDataFunction: (chunk: any) => void): Promise<void> => {
|
||||
const messages: any[] = [
|
||||
{ role: 'user', content: prompt }
|
||||
];
|
||||
const headers = {
|
||||
['Content-Type']: 'application/json',
|
||||
['Authorization']: `Bearer ${openaikey}`,
|
||||
};
|
||||
|
||||
const configurationModel = await workspace.getConfiguration().get('wolverine.model');
|
||||
const data = {
|
||||
'model': configurationModel || 'gpt-3.5-turbo',
|
||||
'messages': messages,
|
||||
'temperature': 0.9,
|
||||
'stream': true,
|
||||
};
|
||||
return new Promise(async (resolve) => {
|
||||
const response = await axios({
|
||||
method: 'post',
|
||||
url: 'https://api.openai.com/v1/chat/completions',
|
||||
headers: headers,
|
||||
data: data,
|
||||
responseType: 'stream',
|
||||
});
|
||||
response.data.on('data', onDataFunction);
|
||||
response.data.on('end', () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const countCharacters = (text: string) => text.replace(/\n/g, '').length;
|
||||
const countNewLines = (text: string) => text.match(/\n/g)?.length || 0;
|
||||
const getNewCursorLocation = (textStream: string, currentLine: number, currentCharacter: number): { newCharacterLocation: number, newLineLocation: number } => {
|
||||
const numberOfNewLines = countNewLines(textStream);
|
||||
const newCharacterLocation = numberOfNewLines === 0 ? countCharacters(textStream) + currentCharacter : 0;
|
||||
const newLineLocation = numberOfNewLines + currentLine;
|
||||
return { newCharacterLocation, newLineLocation };
|
||||
};
|
||||
|
||||
const deleteRange = async (activeEditor: TextEditor, range: Range) => {
|
||||
await activeEditor.edit(editBuilder => {
|
||||
editBuilder.delete(range);
|
||||
});
|
||||
};
|
||||
|
||||
// Yacine threw most of the complexity into here.
|
||||
// Holds a buffer in a javascript array, registers a event listener on a server-sent events function, builds the buffer
|
||||
// Takes a position, and flushes the buffer on a preconfigured cron into the provided cursor position.
|
||||
const useBufferToUpdateTextContentsWhileStreamingOpenAIResponse = async (activeEditor: TextEditor, position: Position, prompt: string) => {
|
||||
let currentCharacter = position.character;
|
||||
let currentLine = position.line;
|
||||
let buffer: string[] = [];
|
||||
let doneStreaming = false;
|
||||
const onDataFunction = (word: any) => {
|
||||
const newContent = word.toString().split('data: ')
|
||||
.map((line: string) => line.trim())
|
||||
.filter((line: string) => line.length > 0)
|
||||
.map((line: string) => JSON.parse(line).choices[0].delta.content);
|
||||
buffer = [...buffer, ...newContent];
|
||||
};
|
||||
streamCompletion(prompt, onDataFunction).then(() => doneStreaming = true);
|
||||
while (!doneStreaming || buffer.length >= 0) {
|
||||
const word: string | undefined = buffer.shift();
|
||||
if (word) {
|
||||
let { newCharacterLocation, newLineLocation } = getNewCursorLocation(word, currentLine, currentCharacter);
|
||||
const position = new Position(currentLine, currentCharacter);
|
||||
await activeEditor.edit((editBuilder) => {
|
||||
editBuilder.insert(position, word);
|
||||
});
|
||||
currentCharacter = newCharacterLocation;
|
||||
currentLine = newLineLocation;
|
||||
}
|
||||
// TODO I should make this buffer flush configurable.
|
||||
await sleep(30);
|
||||
}
|
||||
};
|
||||
|
||||
const constructPrompt = async (text: string): Promise<string> => {
|
||||
const defaultPrompt = `
|
||||
INSTRUCTIONS:
|
||||
The text under 'CODE' has come straight from my text editor.
|
||||
When you respond, please only output valid code, and no raw english! If its english, put it under comments.
|
||||
We want your entire output to be valid codee.
|
||||
The code sent to you might have some instructions under comments. Follow those instructions.
|
||||
CODE:
|
||||
${text}
|
||||
NEW CODE:
|
||||
`;
|
||||
const configuredPrompt = await workspace.getConfiguration().get('wolverine.prompt');
|
||||
if (configuredPrompt) {
|
||||
return configuredPrompt + text;
|
||||
}
|
||||
return defaultPrompt;
|
||||
};
|
||||
|
||||
export async function activate(context: ExtensionContext) {
|
||||
|
||||
let disposable = commands.registerCommand('wolverine.directedHeal', async () => {
|
||||
// Forgive me father..
|
||||
openaikey = await workspace.getConfiguration().get('wolverine.UNSAFE.OpenaiApiKeySetting') || '';
|
||||
const activeEditor = window.activeTextEditor;
|
||||
if (!activeEditor) {
|
||||
window.showErrorMessage('No text editor is currently active.');
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = activeEditor.selection;
|
||||
let range: Range;
|
||||
if (!selection.isEmpty) {
|
||||
range = new Range(selection.start, selection.end);
|
||||
} else {
|
||||
range = activeEditor.document.validateRange(new Range(0, 0, Number.MAX_VALUE, Number.MAX_VALUE));
|
||||
}
|
||||
const text = activeEditor.document.getText(range);
|
||||
const prompt = await constructPrompt(text);
|
||||
await deleteRange(activeEditor, range);
|
||||
await useBufferToUpdateTextContentsWhileStreamingOpenAIResponse(activeEditor, range.start, prompt);
|
||||
await activeEditor.document.save();
|
||||
});
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
export function deactivate() { }
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import * as path from 'path';
|
||||
|
||||
import { runTests } from '@vscode/test-electron';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// The folder containing the Extension Manifest package.json
|
||||
// Passed to `--extensionDevelopmentPath`
|
||||
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
|
||||
|
||||
// The path to test runner
|
||||
// Passed to --extensionTestsPath
|
||||
const extensionTestsPath = path.resolve(__dirname, './suite/index');
|
||||
|
||||
// Download VS Code, unzip it and run the integration test
|
||||
await runTests({ extensionDevelopmentPath, extensionTestsPath });
|
||||
} catch (err) {
|
||||
console.error('Failed to run tests', err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
|
@ -0,0 +1,11 @@
|
|||
import * as assert from 'assert';
|
||||
import * as vscode from 'vscode';
|
||||
// import * as myExtension from '../../extension';
|
||||
|
||||
suite('Extension Test Suite', () => {
|
||||
vscode.window.showInformationMessage('Start all tests.');
|
||||
test('test stub', () => {
|
||||
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
|
||||
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
import * as path from 'path';
|
||||
import * as Mocha from 'mocha';
|
||||
import * as glob from 'glob';
|
||||
|
||||
export function run(): Promise<void> {
|
||||
// Create the mocha test
|
||||
const mocha = new Mocha({
|
||||
ui: 'tdd',
|
||||
color: true
|
||||
});
|
||||
|
||||
const testsRoot = path.resolve(__dirname, '..');
|
||||
|
||||
return new Promise((c, e) => {
|
||||
glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
|
||||
// Add files to the test suite
|
||||
files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
|
||||
|
||||
try {
|
||||
// Run the mocha test
|
||||
mocha.run(failures => {
|
||||
if (failures > 0) {
|
||||
e(new Error(`${failures} tests failed.`));
|
||||
} else {
|
||||
c();
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
e(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "esnext",
|
||||
"lib": [
|
||||
"ES2021",
|
||||
"esnext.asynciterable"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"rootDir": "src",
|
||||
"strict": true /* enable all strict type-checking options */
|
||||
/* Additional Checks */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
//@ts-check
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
//@ts-check
|
||||
/** @typedef {import('webpack').Configuration} WebpackConfig **/
|
||||
|
||||
/** @type WebpackConfig */
|
||||
const extensionConfig = {
|
||||
target: 'node', // VS Code extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
|
||||
mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
|
||||
|
||||
entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
|
||||
output: {
|
||||
// the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'extension.js',
|
||||
libraryTarget: 'commonjs2'
|
||||
},
|
||||
externals: {
|
||||
vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
|
||||
// modules added here also need to be added in the .vscodeignore file
|
||||
},
|
||||
resolve: {
|
||||
// support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
|
||||
extensions: ['.ts', '.js']
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: 'ts-loader'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
devtool: 'nosources-source-map',
|
||||
infrastructureLogging: {
|
||||
level: "log", // enables logging required for problem matchers
|
||||
},
|
||||
};
|
||||
module.exports = [ extensionConfig ];
|
Plik binarny nie jest wyświetlany.
Ładowanie…
Reference in New Issue