feat: add initDB

This commit is contained in:
WJG 2024-02-27 23:35:00 +08:00
parent 9e1e9a3973
commit 413a80cbf8
No known key found for this signature in database
GPG Key ID: 258474EF8590014A
11 changed files with 204 additions and 51 deletions

View File

@ -20,19 +20,21 @@
],
"scripts": {
"build": "tsup",
"db:gen": "npx prisma migrate dev --name hello",
"db:gen": "npx prisma migrate dev --name init",
"db:reset": "rm .bot.json && npx prisma migrate reset",
"prepublish": "yarn db:gen && npm run build"
"prepublish": "npm run db:gen && npm run build"
},
"dependencies": {
"@prisma/client": "^5.8.1",
"axios": "^1.6.5",
"fs-extra": "^11.2.0",
"https-proxy-agent": "^7.0.4",
"mi-service-lite": "^2.0.0",
"openai": "^4.28.0",
"prisma": "^5.8.1"
},
"devDependencies": {
"@types/fs-extra": "^11.0.4",
"@types/node": "^20.4.9",
"dotenv": "^16.3.2",
"ts-node": "^10.9.2",

View File

@ -7,7 +7,7 @@ generator client {
datasource db {
provider = "sqlite"
url = "file:./hello.db"
url = "file:../.mi-gpt.db"
}
model User {

View File

@ -1,6 +1,6 @@
import { AISpeaker, AISpeakerConfig } from "./services/speaker/ai";
import { MyBot, MyBotConfig } from "./services/bot";
import { runWithDB } from "./services/db";
import { initDB, runWithDB } from "./services/db";
import { kBannerASCII } from "./utils/string";
export type MiGPTConfig = Omit<MyBotConfig, "speaker"> & {
@ -35,7 +35,7 @@ export class MiGPT {
}
async start() {
// todo init DB
await initDB(".mi-gpt.db");
const main = () => {
console.log(kBannerASCII);
return this.ai.run();

View File

@ -1,4 +1,3 @@
import { runWithDB } from "./services/db";
import { println } from "./utils/base";
import { kBannerASCII } from "./utils/string";
@ -6,4 +5,4 @@ async function main() {
println(kBannerASCII);
}
runWithDB(main);
main();

View File

@ -1,13 +1,15 @@
import { PrismaClient } from "@prisma/client";
import { Logger } from "../../utils/log";
import { deleteFile, exists } from "../../utils/io";
import { Shell } from "../../utils/shell";
export const k404 = -404;
export const kPrisma = new PrismaClient();
export const kDBLogger = Logger.create({ tag: "DB" });
export const kDBLogger = Logger.create({ tag: "database" });
export function runWithDB(main: () => Promise<void>) {
main()
return main()
.then(async () => {
await kPrisma.$disconnect();
})
@ -24,3 +26,16 @@ export function getSkipWithCursor(skip: number, cursorId: any) {
cursor: cursorId ? { id: cursorId } : undefined,
};
}
export async function initDB(dbPath: string) {
if (!exists(dbPath)) {
const isExternal = exists("node_modules/mi-gpt/prisma");
const withSchema = isExternal
? "--schema node_modules/mi-gpt/prisma/schema.prisma"
: "";
await deleteFile(".bot.json");
await Shell.run(`npx prisma migrate dev --name init ${withSchema}`);
}
const success = exists(dbPath);
kDBLogger.assert(success, "初始化数据库失败!");
}

View File

@ -25,16 +25,22 @@ class OpenAIClient {
traceOutput = true;
private _logger = Logger.create({ tag: "Open AI" });
private _client = new OpenAI({
httpAgent: kProxyAgent,
apiKey: kEnvs.OPENAI_API_KEY!,
});
private _client?: OpenAI;
private _init() {
if (!this._client) {
this._client = new OpenAI({
httpAgent: kProxyAgent,
apiKey: kEnvs.OPENAI_API_KEY!,
});
}
}
private _abortCallbacks: Record<string, VoidFunction> = {
// requestId: abortStreamCallback
};
abort(requestId: string) {
this._init();
if (this._abortCallbacks[requestId]) {
this._abortCallbacks[requestId]();
delete this._abortCallbacks[requestId];
@ -42,6 +48,7 @@ class OpenAIClient {
}
async chat(options: ChatOptions) {
this._init();
let {
user,
system,
@ -65,20 +72,18 @@ class OpenAIClient {
this._abortCallbacks[requestId] = () => controller.abort();
signal = controller.signal;
}
const chatCompletion = await this._client.chat.completions
.create(
{
model,
tools,
messages: [...systemMsg, { role: "user", content: user }],
response_format: jsonMode ? { type: "json_object" } : undefined,
},
{ signal }
)
.catch((e) => {
this._logger.error("openai chat failed", e);
return null;
});
const chatCompletion = await this._client!.chat.completions.create(
{
model,
tools,
messages: [...systemMsg, { role: "user", content: user }],
response_format: jsonMode ? { type: "json_object" } : undefined,
},
{ signal }
).catch((e) => {
this._logger.error("openai chat failed", e);
return null;
});
const message = chatCompletion?.choices?.[0]?.message;
if (trace && this.traceOutput) {
this._logger.log(`✅ Answer: ${message?.content ?? "None"}`.trim());
@ -91,6 +96,7 @@ class OpenAIClient {
onStream?: (text: string) => void;
}
) {
this._init();
let {
user,
system,
@ -109,18 +115,16 @@ class OpenAIClient {
const systemMsg: ChatCompletionMessageParam[] = system
? [{ role: "system", content: system }]
: [];
const stream = await this._client.chat.completions
.create({
model,
tools,
stream: true,
messages: [...systemMsg, { role: "user", content: user }],
response_format: jsonMode ? { type: "json_object" } : undefined,
})
.catch((e) => {
this._logger.error("❌ openai chat failed", e);
return null;
});
const stream = await this._client!.chat.completions.create({
model,
tools,
stream: true,
messages: [...systemMsg, { role: "user", content: user }],
response_format: jsonMode ? { type: "json_object" } : undefined,
}).catch((e) => {
this._logger.error("❌ openai chat failed", e);
return null;
});
if (!stream) {
return;
}

View File

@ -1,5 +1,5 @@
import * as fs from "fs";
import * as path from "path";
import fs from "fs-extra";
import path from "path";
import { jsonDecode, jsonEncode } from "./base";
@ -7,14 +7,7 @@ export const kRoot = process.cwd();
export const exists = (filePath: string) => fs.existsSync(filePath);
export const deleteFile = (filePath: string) => {
try {
fs.rmSync(filePath);
return true;
} catch {
return false;
}
};
export const getFullPath = (filePath: string) => path.resolve(filePath);
export const getFiles = (dir: string) => {
return new Promise<string[]>((resolve) => {
@ -89,3 +82,87 @@ export const readJSONSync = (filePath: string) =>
export const writeJSON = (filePath: string, content: any) =>
writeFile(filePath, jsonEncode(content) ?? "", "utf8");
export const deleteFile = (filePath: string) => {
try {
fs.rmSync(filePath);
return true;
} catch {
return false;
}
};
export const copyFile = (
from: string,
to: string,
mode?: number | undefined
) => {
if (!fs.existsSync(from)) {
return false;
}
const dirname = path.dirname(to);
if (!fs.existsSync(dirname)) {
fs.mkdirSync(dirname, { recursive: true });
}
return new Promise<boolean>((resolve) => {
const callback = (err: any) => {
resolve(err ? false : true);
};
if (mode) {
fs.copyFile(from, to, mode, callback);
} else {
fs.copyFile(from, to, callback);
}
});
};
export const copyFileSync = (
from: string,
to: string,
mode?: number | undefined
) => {
if (!fs.existsSync(from)) {
return false;
}
const dirname = path.dirname(to);
if (!fs.existsSync(dirname)) {
fs.mkdirSync(dirname, { recursive: true });
}
try {
fs.copyFileSync(from, to, mode);
return true;
} catch {
return false;
}
};
export const moveFile = (from: string, to: string) => {
if (!fs.existsSync(from)) {
return false;
}
const dirname = path.dirname(to);
if (!fs.existsSync(dirname)) {
fs.mkdirSync(dirname, { recursive: true });
}
return new Promise<boolean>((resolve) => {
fs.rename(from, to, (err) => {
resolve(err ? false : true);
});
});
};
export const moveFileSync = (from: string, to: string) => {
if (!fs.existsSync(from)) {
return false;
}
const dirname = path.dirname(to);
if (!fs.existsSync(dirname)) {
fs.mkdirSync(dirname, { recursive: true });
}
try {
fs.renameSync(from, to);
return true;
} catch {
return false;
}
};

14
src/utils/shell.ts Normal file
View File

@ -0,0 +1,14 @@
import { exec as execSync } from "child_process";
import { promisify } from "util";
const exec = promisify(execSync);
export class Shell {
static async run(command: string) {
return exec(command);
}
static get args() {
return process.argv.slice(2);
}
}

View File

@ -1,7 +1,6 @@
import dotenv from "dotenv";
import { println } from "../src/utils/base";
import { kBannerASCII } from "../src/utils/string";
import { runWithDB } from "../src/services/db";
import { testDB } from "./db";
import { testSpeaker } from "./speaker";
import { testOpenAI } from "./openai";
@ -21,4 +20,4 @@ async function main() {
testMiGPT();
}
runWithDB(main);
main();

View File

@ -333,6 +333,21 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
"@types/fs-extra@^11.0.4":
version "11.0.4"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-11.0.4.tgz#e16a863bb8843fba8c5004362b5a73e17becca45"
integrity sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==
dependencies:
"@types/jsonfile" "*"
"@types/node" "*"
"@types/jsonfile@*":
version "6.1.4"
resolved "https://registry.yarnpkg.com/@types/jsonfile/-/jsonfile-6.1.4.tgz#614afec1a1164e7d670b4a7ad64df3e7beb7b702"
integrity sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==
dependencies:
"@types/node" "*"
"@types/node-fetch@^2.6.4":
version "2.6.11"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.11.tgz#9b39b78665dae0e82a08f02f4967d62c66f95d24"
@ -717,6 +732,15 @@ formdata-node@^4.3.2:
node-domexception "1.0.0"
web-streams-polyfill "4.0.0-beta.3"
fs-extra@^11.2.0:
version "11.2.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b"
integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
universalify "^2.0.0"
fsevents@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
@ -757,6 +781,11 @@ globby@^11.0.3:
merge2 "^1.4.1"
slash "^3.0.0"
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
https-proxy-agent@^7.0.4:
version "7.0.4"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz#8e97b841a029ad8ddc8731f26595bad868cb4168"
@ -840,6 +869,15 @@ joycon@^3.0.1:
resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03"
integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==
jsonfile@^6.0.1:
version "6.1.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
dependencies:
universalify "^2.0.0"
optionalDependencies:
graceful-fs "^4.1.6"
lilconfig@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.0.0.tgz#f8067feb033b5b74dab4602a5f5029420be749bc"
@ -1291,6 +1329,11 @@ undici-types@~5.26.4:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
universalify@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
v8-compile-cache-lib@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"