Skip to content

Commit

Permalink
here have some code
Browse files Browse the repository at this point in the history
  • Loading branch information
doug-wade committed Jun 12, 2024
1 parent fee0ebe commit b9b2ea1
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 106 deletions.
12 changes: 0 additions & 12 deletions .eslintrc.js

This file was deleted.

28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,34 @@ eleventyConfig.addPlugin(gpt4AllPlugin, {
});
```

### prompts

Used to customize prompts.

#### initialCode

Provide the prompt for the initial code generation.

```javascript
eleventyConfig.addPlugin(gpt4AllPlugin, {
prompts: {
initialCode: (prompt, lang) => `Please generate code matching the prompt ${prompt} in the language ${lang}`
}
});
```

#### codeReview

Provide the prompt for the code review.

```javascript
eleventyConfig.addPlugin(gpt4AllPlugin, {
prompts: {
codeReview: (lang, code) => `Please perform a code review of the code ${code} in the language ${lang}`
}
});
```

## Disclaimer

Please review the code generated by GPT4All before deploying to production.
18 changes: 18 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

import js from "@eslint/js";
import globals from "globals";

export default [
js.configs.recommended,
{
languageOptions: {
parserOptions: {
ecmaVersion: 2020,
},
globals: {
...globals.node,
...globals.es2020,
}
},
}
]
98 changes: 5 additions & 93 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
const path = require('node:path');
const fs = require('node:fs/promises');
const { JSDOM } = require('jsdom');
const { createCompletion, loadModel } = require('gpt4all');
const { loadModel } = require('gpt4all');
const { mkdirp } = require('mkdirp');

const systemPromptTemplate = `
You are an expert web developer, who conforms
to the latest best practices, and has access to all knowledge necessary
to complete the tasks I assign to you. Please think carefully before
giving me an answer, and check your work for correctness. Consider
accessibility, seo, and performance when designing solutions, and take
as much time as you need to come to a good solution.
`;
const generatePageFromPrompt = require('./src/generatePageFromPrompt');

const defaultInitialCode = (prompt, lang) => `
We are going to write a web page that matches the following description,
Expand All @@ -37,84 +27,6 @@ ${code}
"""
`;

const generateFiles = async ({ model, prompt, initialCode, codeReview }) => {
const getInitialCode = async (model, prompt, lang) => {
const response = await createCompletion(model, [
{ role: 'user', content: initialCode(prompt, lang) }
], { systemPromptTemplate });
return response.choices[0].message.content;
}

const doCodeReview = async (model, code, lang) => {
const response = await createCompletion(model, [
{ role: 'user', content: codeReview(lang, code) }
], { systemPromptTemplate });
return response.choices[0].message.content;
};

const htmlResponse = await getInitialCode(model, prompt, 'html');
const cssResponse = await getInitialCode(model, prompt, 'css');
const jsResponse = await getInitialCode(model, prompt, 'javascript');

const reviewedHtml = await doCodeReview(model, htmlResponse, 'html');
const reviewedCss = await doCodeReview(model, cssResponse, 'css');
const reviewedJs = await doCodeReview(model, jsResponse, 'javascript');

return {
html: reviewedHtml,
js: reviewedJs,
css: reviewedCss,
};
}

const generatePageFromPrompt = async ({ prompt, model, outputPath, verbose, initialCode, codeReview }) => {
const log = verbose ? console.log : () => {};
const pageName = path.basename(outputPath, '.html');
const dirName = path.dirname(outputPath);

log(`Generating page ${dirName}/${pageName} from prompt`);

// Call gpt4all with the prompt and get the statics
const { html, css, js } = await generateFiles({ model, prompt, initialCode, codeReview });

log(`Generated html, css, and js for ${dirName}/${pageName}`);

// Parse the html into a DOM for us to operate on.
// I checked to make sure that jsdom doesn't execute script
// tags, but if it does, we'll need to find a way to sandbox
// it, because we can't have ai-generated code running on our
// build boxes.
const dom = new JSDOM(html);
const document = dom.window.document;

// Generate the css and js files
const jsFileName = path.join(dirName, `${pageName}.js`);
const cssFileName = path.join(dirName, `${pageName}.css`);

await Promise.all([
fs.writeFile(jsFileName, js),
fs.writeFile(cssFileName, css),
]);

log(`Wrote ${jsFileName} and ${cssFileName}`);

// Add the script tag to the body
const scriptTag = document.createElement('script');
scriptTag.setAttribute('src', `${jsFileName}`);
document.body.appendChild(scriptTag);

// Add the style tag to the body
const styleTag = document.createElement('style');
styleTag.setAttribute('type', 'text/css');
styleTag.setAttribute('rel', 'stylesheet');
styleTag.setAttribute('href', `${cssFileName}`);
document.body.appendChild(styleTag);

log(`Added script and style tags to ${pageName}`);

return `<!DOCTYPE html>${document.documentElement.outerHTML}`;
};

module.exports = function eleventyPluginGPT4All(eleventyConfig, options = {}) {
const modelName = options.modelName || 'starcoder-newbpe-q4_0.gguf';
const verbose = options.verbose || false;
Expand All @@ -123,14 +35,14 @@ module.exports = function eleventyPluginGPT4All(eleventyConfig, options = {}) {
const codeReview = prompts.codeReview || defaultCodeReview;
const modelPromise = loadModel(modelName, { verbose });

eleventyConfig.addTransform('gpt4all-prompt', async (content, outputPath) => {
eleventyConfig.addTransform('gpt4all-prompt', async (prompt, outputPath) => {
const model = await modelPromise;

if (outputPath && outputPath.endsWith(".html")) {
await mkdirp(path.dirname(outputPath));
return await generatePageFromPrompt({ content, model, outputPath, verbose, initialCode, codeReview });
return await generatePageFromPrompt({ prompt, model, outputPath, verbose, initialCode, codeReview });
}

return content;
return prompt;
});
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
},
"devDependencies": {
"@types/node": "20.14.2",
"eslint": "^9.4.0"
"eslint": "^9.4.0",
"globals": "^15.4.0"
},
"files": [
"index.js",
Expand Down
42 changes: 42 additions & 0 deletions src/generateFiles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const { createCompletion } = require('gpt4all');

const systemPromptTemplate = `
You are an expert web developer, who conforms
to the latest best practices, and has access to all knowledge necessary
to complete the tasks I assign to you. Please think carefully before
giving me an answer, and check your work for correctness. Consider
accessibility, seo, and performance when designing solutions, and take
as much time as you need to come to a good solution.
`;

const generateFiles = async ({ model, prompt, initialCode, codeReview }) => {
const getInitialCode = async (model, prompt, lang) => {
const response = await createCompletion(model, [
{ role: 'user', content: initialCode(prompt, lang) }
], { systemPromptTemplate });
return response.choices[0].message.content;
}

const doCodeReview = async (model, code, lang) => {
const response = await createCompletion(model, [
{ role: 'user', content: codeReview(lang, code) }
], { systemPromptTemplate });
return response.choices[0].message.content;
};

const htmlResponse = await getInitialCode(model, prompt, 'html');
const cssResponse = await getInitialCode(model, prompt, 'css');
const jsResponse = await getInitialCode(model, prompt, 'javascript');

const reviewedHtml = await doCodeReview(model, htmlResponse, 'html');
const reviewedCss = await doCodeReview(model, cssResponse, 'css');
const reviewedJs = await doCodeReview(model, jsResponse, 'javascript');

return {
html: reviewedHtml,
js: reviewedJs,
css: reviewedCss,
};
}

module.exports = generateFiles;
54 changes: 54 additions & 0 deletions src/generatePageFromPrompt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const path = require('node:path');
const fs = require('fs/promises');
const { JSDOM } = require('jsdom');
const generateFiles = require('./generateFiles');

const generatePageFromPrompt = async ({ prompt, model, outputPath, verbose, initialCode, codeReview }) => {
const log = verbose ? console.log : () => {};
const pageName = path.basename(outputPath, '.html');
const dirName = path.dirname(outputPath);

log(`Generating page ${dirName}/${pageName} from prompt`);

// Call gpt4all with the prompt and get the statics
const { html, css, js } = await generateFiles({ model, prompt, initialCode, codeReview });

log(`Generated html, css, and js for ${dirName}/${pageName}`);

// Parse the html into a DOM for us to operate on.
// I checked to make sure that jsdom doesn't execute script
// tags, but if it does, we'll need to find a way to sandbox
// it, because we can't have ai-generated code running on our
// build boxes.
const dom = new JSDOM(html);
const document = dom.window.document;

// Generate the css and js files
const jsFileName = path.join(dirName, `${pageName}.js`);
const cssFileName = path.join(dirName, `${pageName}.css`);

await Promise.all([
fs.writeFile(jsFileName, js),
fs.writeFile(cssFileName, css),
]);

log(`Wrote ${jsFileName} and ${cssFileName}`);

// Add the script tag to the body
const scriptTag = document.createElement('script');
scriptTag.setAttribute('src', `${jsFileName}`);
document.body.appendChild(scriptTag);

// Add the style tag to the body
const styleTag = document.createElement('style');
styleTag.setAttribute('type', 'text/css');
styleTag.setAttribute('rel', 'stylesheet');
styleTag.setAttribute('href', `${cssFileName}`);
document.body.appendChild(styleTag);

log(`Added script and style tags to ${pageName}`);

return `<!DOCTYPE html>${document.documentElement.outerHTML}`;
};

module.exports = generatePageFromPrompt;
32 changes: 32 additions & 0 deletions tst/generateFiles.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const { afterEach, describe, it, mock } = require('node:test');
const assert = require('node:assert');

const gpt4all = require('gpt4all');
mock.method(gpt4all, 'createCompletion', async () => ({
choices: [{ message: { content: 'mock content' }}],
}));

const generateFiles = require('../src/generateFiles');

describe('generateFiles', () => {
afterEach(() => {
mock.reset();
});

it('should call createCompletion six times', async () => {
await generateFiles({ model: {}, prompt: 'prompt', initialCode: mock.fn(), codeReview: mock.fn() });
assert.strictEqual(gpt4all.createCompletion.mock.calls.length, 6);
});

it('should call initialCode three times', async () => {
const initialCode = mock.fn();
await generateFiles({ model: {}, prompt: 'prompt', initialCode, codeReview: mock.fn() });
assert.strictEqual(initialCode.mock.calls.length, 3);
});

it('should call codeReview three times', async () => {
const codeReview = mock.fn();
await generateFiles({ model: {}, prompt: 'prompt', initialCode: mock.fn(), codeReview });
assert.strictEqual(codeReview.mock.calls.length, 3);
});
});
49 changes: 49 additions & 0 deletions tst/generatePageFromPrompt.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const { afterEach, describe, it, mock } = require('node:test');
const assert = require('node:assert');

const fs = require('fs/promises');
mock.method(fs, 'writeFile', async () => ({}));
const gpt4all = require('gpt4all');
mock.method(gpt4all, 'createCompletion', async () => ({
choices: [{ message: { content: 'mock content' }}],
}));


describe('generatePageFromPrompt', () => {
afterEach(() => {
mock.reset();
});

it('should call createChatCompletion six times', async () => {
const generatePageFromPrompt = require('../src/generatePageFromPrompt');
const initialCode = mock.fn();
const codeReview = mock.fn();
await generatePageFromPrompt({
prompt: 'prompt',
client: {},
outputPath: 'outputPath',
verbose: false,
initialCode,
codeReview
});
assert.strictEqual(gpt4all.createCompletion.mock.calls.length, 6);
assert.strictEqual(initialCode.mock.calls.length, 3);
assert.strictEqual(codeReview.mock.calls.length, 3);
});

it('should call writeFile three times', async () => {
const generatePageFromPrompt = require('../src/generatePageFromPrompt');
const initialCode = mock.fn();
const codeReview = mock.fn();
await generatePageFromPrompt({
prompt: 'prompt',
client: {},
outputPath: 'outputPath',
verbose: false,
initialCode,
codeReview
});
assert.strictEqual(fs.promises.writeFile.mock.calls.length, 3);
});

});
Loading

0 comments on commit b9b2ea1

Please sign in to comment.