Skip to content

Commit

Permalink
Support to generate command using ChatGPT
Browse files Browse the repository at this point in the history
  • Loading branch information
ryo-ma committed Feb 6, 2023
1 parent 4ad5294 commit b111fb9
Show file tree
Hide file tree
Showing 7 changed files with 396 additions and 1 deletion.
2 changes: 2 additions & 0 deletions deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export { Sha1 } from "https://deno.land/std@0.146.0/hash/sha1.ts";

export { parse } from "https://deno.land/std@0.174.0/encoding/csv.ts";

export { Configuration, OpenAIApi } from "npm:openai";

export const zipWrapper = {
decompress: _decompress,
};
Expand Down
18 changes: 18 additions & 0 deletions dim.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Command, CompletionsCommand, GithubProvider, HelpCommand, UpgradeCommand } from "./deps.ts";
import {
CleanAction,
GenerateAction,
InitAction,
InstallAction,
ListAction,
Expand Down Expand Up @@ -129,6 +130,23 @@ await new Command()
.description("Search data from package_search CKAN API")
.action(new SearchAction().execute),
)
.command(
"generate",
new Command()
.option(
"-t, --target <target:string>",
"Specify the target data name or file path to send to ChatGPT.",
)
.option(
"-o, --output <output:string>",
"Specify to output file path of generated post-process.",
)
.arguments("<prompt:string>")
.description(
"Auto-generate code about target data using ChatGPT. \nFor example, conversion processing, visualization processing, etc.",
)
.action(new GenerateAction().execute),
)
.command("help", new HelpCommand())
.command("complete", new CompletionsCommand())
.parse(Deno.args);
70 changes: 69 additions & 1 deletion libs/actions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Colors } from "../deps.ts";
import { Colors, Confirm, Input } from "../deps.ts";
import { DEFAULT_DATAFILES_PATH, DEFAULT_DIM_FILE_PATH } from "./consts.ts";
import { DimFileAccessor, DimLockFileAccessor } from "./accessor.ts";
import { CkanApiClient } from "./ckan_api_client.ts";
import { createDataFilesDir, initDimFile, initDimLockFile } from "./action_helper/initializer.ts";
import { installFromDimFile, installFromURL, interactiveInstall, parseHeader } from "./action_helper/installer.ts";
import { ChatGPTClient } from "./chatgpt_client.ts";
import { ConsoleAnimation } from "./console_animation.ts";

export class InitAction {
async execute() {
Expand Down Expand Up @@ -363,3 +365,69 @@ export class SearchAction {
);
}
}

export class GenerateAction {
async execute(
options: { target?: string; output?: string },
prompt: string,
) {
const dimLockFileAccessor = new DimLockFileAccessor();
let target = options.target;
const dataNameList = dimLockFileAccessor.getContents().map((c) => c.name);
if (!target) {
target = await Input.prompt({
message: "Enter the target data name or file path to send to ChatGPT.",
hint: `${dataNameList.join(", ").slice(0, 50)}... or target file path])`,
suggestions: dataNameList,
validate: (text) => {
return text !== "";
},
});
}
const content = dimLockFileAccessor.getContents().find((c) => c.name === target);
let targetData;
try {
if (content) {
targetData = Deno.readTextFileSync(content.path);
} else {
targetData = Deno.readTextFileSync(options.target!);
}
} catch {
console.log(
Colors.red("Not found a target file."),
);
Deno.exit(1);
}
const consoleAnimation = new ConsoleAnimation(
["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
`Generate a code ...`,
);
consoleAnimation.start(100);
const response = await new ChatGPTClient().request(`${targetData}\n${prompt}`);
if (!response) {
return;
}
const code = response.data.choices[0].text;
consoleAnimation.stop();
console.log(`${code}\n\n`);

const isHit = await Confirm.prompt({
message: "Hit to save the file.",
default: true,
});
if (!isHit) {
return;
}
if (!options.output) {
const output = await Input.prompt({
message: "Enter a output file path.",
validate: (text) => {
return text !== "";
},
});
Deno.writeTextFileSync(output, code!);
} else {
Deno.writeTextFileSync(options.output!, code!);
}
}
}
35 changes: 35 additions & 0 deletions libs/chatgpt_client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Colors, Configuration, OpenAIApi } from "../deps.ts";

export class ChatGPTClient {
private openai;

constructor() {
const apiKey = Deno.env.get("OPENAI_API_KEY")!;
if (!apiKey) {
console.log(
Colors.red("Not set environment variable of 'OPENAI_API_KEY'\n"),
);
Deno.exit(1);
}
const configuration = new Configuration({ apiKey });
this.openai = new OpenAIApi(configuration);
}
async request(prompt: string) {
try {
const gptResponse = await this.openai.createCompletion({
model: "text-davinci-003",
prompt,
max_tokens: 1024,
temperature: 0,
});
return gptResponse;
} catch (error) {
console.log(
Colors.red(`\n${error}`),
//Colors.red(`\n${error.response.data.error.message}`),
Colors.yellow(`\nThe problem may be improved by temporarily reducing the number of target data.`),
);
Deno.exit(1);
}
}
}
24 changes: 24 additions & 0 deletions tests/helper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Stub, stub } from "https://deno.land/std@0.152.0/testing/mock.ts";
import { resolve } from "https://deno.land/std@0.152.0/path/mod.ts";
import { ky } from "../deps.ts";
import axios, { AxiosResponse } from "npm:axios@0.26";

const currentDirectory = new URL(".", import.meta.url).pathname;
export const temporaryDirectory = resolve(currentDirectory, "temporary") + "/";
Expand All @@ -22,6 +23,29 @@ export const createKyGetStub = (

return stub(ky, "get", mockedKy.get);
};
export const createAxiosStub = () => {
const generatedCode = Deno.readTextFileSync("../test_data/generated_code.py");
const response: AxiosResponse = {
data: { id: "", object: "", created: 0, model: "", choices: [{ text: generatedCode }] },
status: 200,
statusText: "",
headers: {},
config: {},
};
return stub(axios.default, "request", () => Promise.resolve(response));
};

export const createErrorAxiosStub = () => {
const generatedCode = Deno.readTextFileSync("../test_data/generated_code.py");
const response: AxiosResponse = {
data: { id: "", object: "", created: 0, model: "", choices: [{ text: generatedCode }] },
status: 200,
statusText: "",
headers: {},
config: {},
};
return stub(axios.default, "request", () => Promise.reject(response));
};

export const removeTemporaryFiles = () => {
for (const path of Deno.readDirSync(temporaryDirectory)) {
Expand Down
Loading

0 comments on commit b111fb9

Please sign in to comment.