-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: add example "mandelbrot-animated"
- Loading branch information
1 parent
31aae4c
commit fc5b7a1
Showing
11 changed files
with
1,496 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import process from "node:process"; | ||
import fs from "node:fs/promises"; | ||
import path from "node:path"; | ||
import { config } from "dotenv"; | ||
import meow from "meow"; | ||
import { Configuration, OpenAIApi } from "openai"; | ||
import ora from "ora"; | ||
import pkg from "./package.json" assert { type: "json" }; | ||
|
||
config(); | ||
|
||
const { flags } = meow("", { | ||
importMeta: import.meta, | ||
flags: { | ||
goal: { | ||
type: "string", | ||
alias: "G", | ||
default: "extend the code", | ||
}, | ||
generations: { | ||
type: "number", | ||
alias: "g", | ||
default: 1, | ||
}, | ||
persona: { | ||
type: "string", | ||
alias: "p", | ||
default: "expert node.js developer, creative, interaction expert, cli expert", | ||
}, | ||
temperature: { | ||
type: "number", | ||
alias: "t", | ||
default: 0.2, | ||
}, | ||
}, | ||
}); | ||
|
||
const spinner = ora("Evolving"); | ||
|
||
const configuration = new Configuration({ | ||
apiKey: process.env.OPENAI_SECRET, | ||
}); | ||
|
||
export const openai = new OpenAIApi(configuration); | ||
|
||
const instructions = ` | ||
Extend the code but the RULES can NEVER be changed and must be respected AT ALL TIMES. | ||
The code should INCREASE in logic. | ||
The GOAL must be completed. | ||
GOAL: ${flags.goal} | ||
ALLOWED NPM PACKAGES: ${Object.entries(pkg.dependencies) | ||
.map(([name, version]) => `${name}@${version}`) | ||
.join(", ")} | ||
RULES: | ||
- Pay special attention TO ALL UPPERCASE words | ||
- KEEP the existing code, only ADD new code or improve the code you added since "Generation 0" | ||
- increment the generation constant ONCE per generation | ||
- Keep track of changes, EXTEND the CHANGELOG | ||
- Use es module syntax (with imports) | ||
- NEVER use "require" | ||
- NEVER explain anything | ||
- VERY IMPORTANT: output the javaScript only (this is the most important rule, the entire answer has to be valid javascript) | ||
`; | ||
|
||
const persona = `programmer with the following characteristics: ${flags.persona}`; | ||
|
||
export const generations = flags.generations; | ||
const maxSpawns = 3; | ||
let spawns = maxSpawns; | ||
|
||
export async function evolve(generation) { | ||
if (spawns <= 0) { | ||
spinner.fail("Maximum retries reached"); | ||
return; | ||
} | ||
const nextGeneration = generation + 1; | ||
|
||
try { | ||
const filename = buildFilename(generation); | ||
const code = await fs.readFile(filename, "utf-8"); | ||
spinner.start(`Generation ${generation} | ${spawns} spawns left`); | ||
|
||
const completion = await openai.createChatCompletion({ | ||
// model: "gpt-4", | ||
model: "gpt-3.5-turbo", | ||
messages: [ | ||
{ | ||
role: "system", | ||
content: `You are a: ${persona} extend or fix my code based on these instructions: ${instructions}`, | ||
}, | ||
{ | ||
role: "user", | ||
content: code, | ||
}, | ||
], | ||
max_tokens: 2048, | ||
temperature: flags.temperature, | ||
}); | ||
spinner.stop(); | ||
const { content } = completion.data.choices[0].message; | ||
const nextFilename = buildFilename(nextGeneration); | ||
await fs.writeFile(nextFilename, content); | ||
spinner.succeed(`Generation ${generation} | ${spawns} spawns left`); | ||
await import(`./${nextFilename}`); | ||
} catch (error) { | ||
spawns--; | ||
spinner.fail(`Generation ${generation} | ${spawns} spawns left`); | ||
|
||
await handleError(error, generation); | ||
} | ||
} | ||
|
||
export function pad(n) { | ||
return n.toString().padStart(3, "0"); | ||
} | ||
|
||
export function buildFilename(currentGeneration) { | ||
return path.join(".", `generation-${pad(currentGeneration)}.js`); | ||
} | ||
|
||
export async function handleError(error, generation) { | ||
const message = ( | ||
error.response?.data?.error.message ?? | ||
error.message ?? | ||
"unknown error" | ||
).trim(); | ||
|
||
const code = error.response?.status ?? error.code ?? "UNKOWN_CODE"; | ||
|
||
if (code === "ERR_MODULE_NOT_FOUND") { | ||
console.error(message); | ||
return; | ||
} | ||
|
||
// The AI might increment too often | ||
if (message.startsWith("ENOENT") && generation > 1) { | ||
await evolve(generation - 1); | ||
} | ||
|
||
// Errors in the API | ||
if (error.response && code !== 200) { | ||
console.error(`${code}: ${message}`); | ||
|
||
if (code === 401) { | ||
console.error( | ||
"Please make sure to use a valid API key and that you have set OPENAI_SECRET in .env" | ||
); | ||
} | ||
|
||
return; | ||
} | ||
|
||
console.error(error); | ||
throw error; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/** | ||
* CHANGELOG: | ||
* Generation 0: implement base script | ||
*/ | ||
import readline from "node:readline"; | ||
import logUpdate from "log-update"; | ||
import fs from "node:fs/promises"; | ||
import { generations, evolve } from "./base.js"; | ||
|
||
const generation = 0; | ||
|
||
async function run() { | ||
// App logic | ||
} | ||
|
||
if (generation < generations) { | ||
await evolve(generation); | ||
} else { | ||
await run(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/** | ||
* CHANGELOG: | ||
* Generation 0: implement base script | ||
* Generation 1: implement mandelbrot algorithm and ascii console output | ||
*/ | ||
|
||
import readline from "node:readline"; | ||
import logUpdate from "log-update"; | ||
import fs from "node:fs/promises"; | ||
import { generations, evolve } from "./base.js"; | ||
|
||
const generation = 1; | ||
|
||
async function run() { | ||
const width = process.stdout.columns; | ||
const height = process.stdout.rows - 2; | ||
|
||
const xMin = -2; | ||
const xMax = 1; | ||
const yMin = -1; | ||
const yMax = 1; | ||
|
||
const maxIterations = 100; | ||
|
||
let output = ""; | ||
|
||
for (let y = 0; y < height; y++) { | ||
for (let x = 0; x < width; x++) { | ||
const a = xMin + (xMax - xMin) * (x / width); | ||
const b = yMin + (yMax - yMin) * (y / height); | ||
|
||
let real = a; | ||
let imaginary = b; | ||
|
||
let iterations = 0; | ||
|
||
while (iterations < maxIterations) { | ||
const tempReal = real * real - imaginary * imaginary + a; | ||
const tempImaginary = 2 * real * imaginary + b; | ||
|
||
real = tempReal; | ||
imaginary = tempImaginary; | ||
|
||
if (real * real + imaginary * imaginary > 4) { | ||
break; | ||
} | ||
|
||
iterations++; | ||
} | ||
|
||
const char = | ||
iterations === maxIterations ? " " : String.fromCharCode(97 + (iterations % 26)); | ||
|
||
output += char; | ||
} | ||
|
||
output += "\n"; | ||
} | ||
|
||
logUpdate(output); | ||
} | ||
|
||
if (generation < generations) { | ||
await evolve(generation); | ||
} else { | ||
await run(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/** | ||
* CHANGELOG: | ||
* Generation 0: implement base script | ||
* Generation 1: implement mandelbrot algorithm and ascii console output | ||
* Generation 2: add ability to save output to file | ||
*/ | ||
|
||
import readline from "node:readline"; | ||
import logUpdate from "log-update"; | ||
import fs from "node:fs/promises"; | ||
import { generations, evolve } from "./base.js"; | ||
|
||
const generation = 2; | ||
|
||
async function run() { | ||
const width = process.stdout.columns; | ||
const height = process.stdout.rows - 2; | ||
|
||
const xMin = -2; | ||
const xMax = 1; | ||
const yMin = -1; | ||
const yMax = 1; | ||
|
||
const maxIterations = 100; | ||
|
||
let output = ""; | ||
|
||
for (let y = 0; y < height; y++) { | ||
for (let x = 0; x < width; x++) { | ||
const a = xMin + (xMax - xMin) * (x / width); | ||
const b = yMin + (yMax - yMin) * (y / height); | ||
|
||
let real = a; | ||
let imaginary = b; | ||
|
||
let iterations = 0; | ||
|
||
while (iterations < maxIterations) { | ||
const tempReal = real * real - imaginary * imaginary + a; | ||
const tempImaginary = 2 * real * imaginary + b; | ||
|
||
real = tempReal; | ||
imaginary = tempImaginary; | ||
|
||
if (real * real + imaginary * imaginary > 4) { | ||
break; | ||
} | ||
|
||
iterations++; | ||
} | ||
|
||
const char = | ||
iterations === maxIterations ? " " : String.fromCharCode(97 + (iterations % 26)); | ||
|
||
output += char; | ||
} | ||
|
||
output += "\n"; | ||
} | ||
|
||
logUpdate(output); | ||
|
||
const rl = readline.createInterface({ | ||
input: process.stdin, | ||
output: process.stdout, | ||
}); | ||
|
||
rl.question("Do you want to save the output to a file? (y/n) ", async answer => { | ||
if (answer === "y") { | ||
const filename = `mandelbrot_${Date.now()}.txt`; | ||
await fs.writeFile(filename, output); | ||
console.log(`Output saved to ${filename}`); | ||
} | ||
|
||
rl.close(); | ||
}); | ||
} | ||
|
||
if (generation < generations) { | ||
await evolve(generation); | ||
} else { | ||
await run(); | ||
} |
Oops, something went wrong.