Skip to content

Commit

Permalink
feat: add @umijs/copilot (still wip) (#10831)
Browse files Browse the repository at this point in the history
* feat: add @umijs/copilot (still wip)

* chore: update pnpm-lock.yaml

* chore: code style

* Update copilot/bin/umi-copilot.js

Co-authored-by: 咲奈Sakina <59400654+fz6m@users.noreply.github.com>

---------

Co-authored-by: 咲奈Sakina <59400654+fz6m@users.noreply.github.com>
  • Loading branch information
sorrycc and fz6m authored Mar 23, 2023
1 parent ad82b89 commit e516424
Show file tree
Hide file tree
Showing 17 changed files with 354 additions and 152 deletions.
3 changes: 3 additions & 0 deletions codemod/.fatherrc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
extends: '../.fatherrc.base.ts',
};
5 changes: 4 additions & 1 deletion codemod/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
"dist"
],
"scripts": {
"build": "pnpm tsc",
"build": "father build",
"dev": "father dev",
"doctor": "father doctor",
"release": "father doctor && esno ../scripts/release.ts --pkg codemod",
"test": "jest"
},
"dependencies": {
Expand Down
4 changes: 4 additions & 0 deletions copilot/.fatherrc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default {
extends: '../.fatherrc.base.ts',
prebundle: {},
};
5 changes: 5 additions & 0 deletions copilot/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/dist
/node_modules
/fixtures/*/node_modules
/fixtures/tmp
/fixtures/**/*.out.js
6 changes: 6 additions & 0 deletions copilot/bin/umi-copilot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env node

require('../dist/cli').main().catch((e) => {
console.error(e);
process.exit(1);
});
35 changes: 35 additions & 0 deletions copilot/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "@umijs/copilot",
"version": "0.0.0",
"homepage": "https://github.com/umijs/umi/tree/master/copilot",
"bugs": "https://github.com/umijs/umi/issues",
"repository": {
"type": "git",
"url": "https://github.com/umijs/umi"
},
"bin": {
"umi-copilot": "bin/umi-copilot.js"
},
"files": [
"bin",
"dist"
],
"scripts": {
"build": "father build",
"dev": "father dev",
"doctor": "father doctor",
"prebundle": "father prebundle",
"release": "father doctor && esno ../scripts/release.ts --pkg copilot",
"test": "jest"
},
"dependencies": {
"@umijs/utils": "workspace:*",
"zx": "^7.2.1"
},
"publishConfig": {
"access": "public"
},
"authors": [
"chencheng <sorrycc@gmail.com> (https://github.com/sorrycc)"
]
}
80 changes: 80 additions & 0 deletions copilot/src/chatgpt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { axios } from '@umijs/utils';

interface IMessage {
role: 'system' | 'user' | 'assistant';
content: string;
}

interface IChatGPTResponse {
error?: {
type: string;
code: string;
message: string;
};
created: number;
choices: {
index: number;
message: IMessage;
finish_reason: string;
}[];
usage: {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
};
}

const requestParamsBase = {
model: 'gpt-3.5-turbo',
temperature: 0.5,
top_p: 0.8,
presence_penalty: 1.0,
max_tokens: 500,
};

function getApiUrl(proxyUrl?: string) {
if (proxyUrl) {
proxyUrl = proxyUrl.replace(/^https?:\/\//, '').replace(/\/$/, '');
}
return `https://${proxyUrl || 'api.openai.com'}/v1/chat/completions`;
}

export async function sendMessage(opts: {
messages: IMessage[];
token: string;
proxyUrl?: string;
controller?: AbortController;
timeout?: number;
}) {
const apiUrl = getApiUrl(opts.proxyUrl);
let res: any = null;
try {
res = await axios.post<IChatGPTResponse>(
apiUrl,
{
...requestParamsBase,
messages: opts.messages,
stream: false,
},
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${opts.token}`,
},
validateStatus: () => true,
signal: opts.controller?.signal,
timeout: opts.timeout || 20000,
},
);
} catch (e: any) {
if (opts.controller?.signal.aborted === true) {
// 用户手动取消了请求
} else {
throw new Error('Network Error');
}
}
if (res.data.error) {
throw new Error(res.data.error.message);
}
return res;
}
90 changes: 90 additions & 0 deletions copilot/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { chalk, clackPrompts, logger, resolve, yParser } from '@umijs/utils';
import { sendMessage } from './chatgpt';
import { SYSTEM_PROMPT } from './constants';
import { printHelp } from './printHelp';
const { confirm, spinner } = clackPrompts;

export async function main() {
logger.info("🚧 It's in beta, please use it carefully.");
const args = yParser(process.argv.slice(2), {
alias: {
help: ['h'],
},
});
const cwd = args.cwd || process.cwd();

if (args.help) {
printHelp();
return;
}

// --token
const token = process.env.OPENAI_API_KEY || args.token;
if (!token) {
throw new Error('Please set OPENAI_API_KEY or --token');
}

const message = args._.join(' ');
const s = spinner();
s.start('🕖 Hold on, asking OpenAI...');
const res = await sendMessage({
messages: [
{
role: 'system',
content: SYSTEM_PROMPT,
},
{
role: 'user',
content: message,
},
],
token,
// --proxy-url
proxyUrl: args.proxyUrl,
});
s.stop();
const command = res.data.choices[0].message.content;
logger.info('The suggested command is:');
logger.info(chalk.green(command));

const shouldRunCommand = await confirm({
message: `Run the command?`,
});
// why equals true?
// since if user press ctrl+c, shouldRunCommand will not be falsy
if (shouldRunCommand === true) {
logger.info('✅ Running the command...');
process.env.FORCE_COLOR = '1';
const commandPath = await findUmiCommand({ cwd });
logger.info(`Command Path: ${commandPath}`);
// why split?
// since zx.$ has problem when parsing command with template string
const { $ } = await import('zx');
await $`${command.replace('umi', commandPath).split(' ')}`;
}
}

async function findUmiCommand(opts: { cwd: string }) {
let ret;
// max > umi > global max > global umi
if (!ret) ret = resolveSyncSilently('max/bin/max.js', opts.cwd);
if (!ret) ret = resolveSyncSilently('umi/bin/umi.js', opts.cwd);
const { $ } = await import('zx');
try {
if (!ret) ret = (await $`which max`).stdout.trim();
} catch (_e) {}
try {
if (!ret) ret = (await $`which umi`).stdout.trim();
} catch (_e) {}
return ret;
}

function resolveSyncSilently(path: string, cwd: string) {
try {
return resolve.sync(path, {
basedir: cwd,
});
} catch (e) {
return null;
}
}
16 changes: 16 additions & 0 deletions copilot/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// TODO: IMPROVE THIS
export const SYSTEM_PROMPT = `
Umi 框架有一些命令如下:
umi dev
umi build
umi dev 可以通过 PORT 环境变量指定端口号。
umi build 可以通过设置 COMPRESS 环境变量为 none 来让构建不进行压缩。
环境变量的使用方法是在命令之前加入环境变量,如:
PORT=1234 umi dev
COMPRESS=none umi build
基于以上知识,请基于我的要求给出 umi 相关可执行的命令。只给可执行的命令行,不要做任何额外解释。
`;
16 changes: 16 additions & 0 deletions copilot/src/printHelp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export function printHelp() {
console.log(`
Usage: umi-copilot [message]
Options:
--token OpenAI API key
--proxy-url Proxy URL for OpenAI API
--cwd Working directory
--timeout Timeout for OpenAI API, Default: 20000
--help Show help
Examples:
$ umi-copilot "I want to"
`);
}
2 changes: 2 additions & 0 deletions did-you-know/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"scripts": {
"build": "father build",
"dev": "father dev",
"doctor": "father doctor",
"release": "father doctor && esno ../scripts/release.ts --pkg did-you-know",
"test": "jest"
},
"devDependencies": {
Expand Down
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@
"codemod:build": "pnpm --filter @umijs/codemod build",
"codemod:dev": "pnpm --filter @umijs/codemod build --watch",
"codemod:jest": "cd codemod && jest",
"codemod:release": "umi-scripts releaseCodemod",
"codemod:release": "pnpm --filter @umijs/codemod release",
"codemod:run": "BACON=none GIT_CHECK=none ./codemod/bin/umi-codemod.js --cwd codemod/fixtures/tmp",
"codemod:test": "BACON=none GIT_CHECK=none umi-scripts testCodemod",
"copilot:build": "pnpm --filter @umijs/copilot build",
"copilot:dev": "pnpm --filter @umijs/copilot dev",
"copilot:release": "pnpm --filter @umijs/copilot release",
"cov": "jest --coverage",
"dep:update": "pnpm up --interactive --latest --recursive",
"dev": "umi-scripts turbo dev --parallel",
"didyouknow:build": "pnpm --filter @umijs/did-you-know build",
"didyouknow:dev": "pnpm --filter @umijs/did-you-know dev",
"didyouknow:release": "umi-scripts releaseDidYouKnow",
"didyouknow:release": "pnpm --filter @umijs/did-you-know release",
"doc:build": "pnpm doc:deps && umi build",
"doc:deps": "pnpm doc:deps-ts && pnpm doc:deps-extra",
"doc:deps-extra": "umi-scripts turbo build:extra --filter @umijs/plugin-docs...",
Expand Down
Loading

0 comments on commit e516424

Please sign in to comment.