import { CLIService, Config } from "../type"; import { Logger } from "../common/logger"; import APIClient from "../common/http"; import { Command } from "commander"; import { Session } from "../session"; import ConfigHelper from "../common/config-helper"; import { Configuration, OpenAIApi } from "openai"; import * as process from "process"; const inquirer = require("inquirer"); import clipboard from "clipboardy"; export default class AssistantService extends CLIService { logger: Logger; currentSession: Session; config: Config; openai: OpenAIApi; constructor(private readonly client: APIClient, private readonly program: Command, private readonly assistantSteamHandler?: (chunk: any, ...args: any) => {}) { super(); this.logger = new Logger(); this.config = ConfigHelper.getConfig(); this.openai = new OpenAIApi(new Configuration({ apiKey: this.config.openaiKey })); program.command("assistant").alias("a") .option("-q, --question ", "submit an prompt question, and keep the session.") .option("-c, --continue", "as continue to answer question.") .option("-r, --re-generate", "ask chat-gpt to re-generate answer.") .option("-s, --start-new-session", "clean the current session message histories to start new session context.") .action(async (opts: any) => await this.commandActionHandler(opts)); } async commandActionHandler(opts: any, args?: any): Promise { this.logger.info("assistant command action handler"); try { if (opts.question) { await this.askQuestion(opts.question, this.assistantSteamHandler); return await this.keepingSession(); } else if (opts.continue) { return await this.continueAnswer(); } else if (opts.reGenerate) { return await this.reGenerateAnswer(); } else if (opts.startNewSession) { return await this.startNewSession(); } else { this.logger.warn("No action specified, please use -h to see the usage."); } } catch (e) { this.logger.error(e); } } async askQuestion(question: string, streamHandler?: any): Promise { try { if (!this.currentSession) { this.currentSession = new Session(); await this.currentSession.start(); } await this.currentSession.userPrompt(question); const { data: chat } = await this.openai.createChatCompletion({ model: this.config.model, messages: this.currentSession.contextMessages, stream: true }, { responseType: "stream", timeout: 60e3 }); const answer = await this.handleChatStream(chat, streamHandler); this.logger.debug("save assistant answer" + answer); await this.currentSession.assistantAnswer(answer); return Promise.resolve(this.currentSession.histories); } catch (e) { this.logger.error(e); return Promise.reject(e); } } async keepingSession(): Promise { let inquirerMode = true; while (inquirerMode) { const questions = [ { type: "list", name: "action", message: "What do you want to do?", choices: ["continue send prompt", "re-generate", "continue, let chat-gpt continue to generate content", "start new session", "exit"] } ]; const userAnswer = await inquirer.prompt(questions); switch (userAnswer.action) { case "continue send prompt": const question = await inquirer.prompt([{ type: "input", name: "question", message: "please input your question:" }]); await this.askQuestion(question.question); break; case "continue, let chat-gpt continue to generate content": await this.continueAnswer(); break; case "re-generate": await this.reGenerateAnswer(); break; case "start new session": await this.startNewSession(); case "exit": default: inquirerMode = false; } } } async continueAnswer(): Promise { if (!this.currentSession) { this.logger.info("No session started, please start a new session first."); return; } return this.askQuestion("继续"); } async reGenerateAnswer(): Promise { if (!this.currentSession) { this.logger.info("No session started, please start a new session first."); return; } return this.askQuestion("再试试"); } async startNewSession(): Promise { if (!this.currentSession) { this.logger.info("No session started, please start a new session first."); return; } this.currentSession = new Session(); await this.currentSession.start(); } async handleChatStream(chat: any, streamHandler?: (chunk: any, ...args: any) => {}): Promise { const assistantStreamStartMessage = `\x1b[33m\x1b[89m [${this.currentSession.id}] assistant: `; const emit = streamHandler ? streamHandler : (chunk: string) => { // process.stdout.write(`\x1b[33m\x1b[89m${chunk}`, "utf-8"); clipboard.writeSync(chunk); }; return await new Promise((resolve, reject) => { try { emit(assistantStreamStartMessage); let result = ""; (chat).on("data", (data: any) => { const lines = data?.toString()?.split("\n").filter((line) => line.trim() !== ""); for (const line of lines) { const message = line.replace(/^data: /, ""); if (message == "[DONE]") { emit(`\n`); return resolve(result); } try { let token = JSON.parse(message)?.choices?.[0]?.delta.content; if (token) { result += token; emit(`${token?.replace(/(\n|\r)/g, "")}`); } } catch (e) { this.logger.error(e.message, e); reject(e); } } }); } catch (e) { this.logger.error(e.message, e); reject(e); } }); } }