2023-05-22 13:14:51 +08:00

281 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// devchat.ts
import * as vscode from 'vscode';
import * as dotenv from 'dotenv';
import * as path from 'path';
import * as fs from 'fs';
import { logger } from '../util/logger';
import { CommandRun } from "../util/commonUtil";
import ExtensionContextHolder from '../util/extensionContext';
const envPath = path.join(__dirname, '..', '.env');
dotenv.config({ path: envPath });
export interface ChatOptions {
parent?: string;
reference?: string[];
header?: string[];
context?: string[];
}
export interface LogOptions {
skip?: number;
maxCount?: number;
}
export interface LogEntry {
hash: string;
user: string;
date: string;
request: string;
response: string;
context: Array<{
content: string;
role: string;
}>;
}
export interface ChatResponse {
"prompt-hash": string;
user: string;
date: string;
response: string;
isError: boolean;
}
class DevChat {
private commandRun: CommandRun;
constructor() {
this.commandRun = new CommandRun();
}
public stop() {
this.commandRun.stop();
}
async chat(content: string, options: ChatOptions = {}, onData: (data: ChatResponse) => void): Promise<ChatResponse> {
let args = ["prompt"];
if (options.reference) {
for (const reference of options.reference) {
args.push("-r", reference);
}
}
if (options.header) {
for (const header of options.header) {
args.push("-i", header);
}
}
if (options.context) {
for (const context of options.context) {
args.push("-c", context);
}
}
args.push(content)
const workspaceDir = vscode.workspace.workspaceFolders?.[0].uri.fsPath;
const secretStorage: vscode.SecretStorage = ExtensionContextHolder.context!.secrets;
let openaiApiKey = await secretStorage.get("devchat_OPENAI_API_KEY");
if (!openaiApiKey) {
openaiApiKey = vscode.workspace.getConfiguration('DevChat').get('OpenAI.apiKey');
}
if (!openaiApiKey) {
openaiApiKey = process.env.OPENAI_API_KEY;
}
if (!openaiApiKey) {
logger.channel()?.error('openAI key is invalid!');
logger.channel()?.show();
}
const openAiApiBase = vscode.workspace.getConfiguration('DevChat').get('OpenAI.EndPoint');
const openAiApiBaseObject = openAiApiBase ? { OPENAI_API_BASE: openAiApiBase } : {};
const openaiModel = vscode.workspace.getConfiguration('DevChat').get('OpenAI.model');
const openaiTemperature = vscode.workspace.getConfiguration('DevChat').get('OpenAI.temperature');
const openaiStream = vscode.workspace.getConfiguration('DevChat').get('OpenAI.stream');
const llmModel = vscode.workspace.getConfiguration('DevChat').get('llmModel');
const tokensPerPrompt = vscode.workspace.getConfiguration('DevChat').get('OpenAI.tokensPerPrompt');
let devChat: string | undefined = vscode.workspace.getConfiguration('DevChat').get('DevChatPath');
if (!devChat) {
devChat = 'devchat';
}
if (options.parent) {
args.push("-p", options.parent);
}
const devchatConfig = {
model: openaiModel,
provider: llmModel,
"tokens-per-prompt": tokensPerPrompt,
OpenAI: {
temperature: openaiTemperature,
stream: openaiStream,
}
}
// write to config file
const configPath = path.join(workspaceDir!, '.chat', 'config.json');
// write devchatConfig to configPath
const configJson = JSON.stringify(devchatConfig, null, 2);
fs.writeFileSync(configPath, configJson);
try {
const parseOutData = (stdout: string, isPartial: boolean) => {
const responseLines = stdout.trim().split("\n");
if (responseLines.length < 2) {
return {
"prompt-hash": "",
user: "",
date: "",
response: "",
isError: isPartial ? false : true,
};
}
const userLine = responseLines.shift()!;
const user = (userLine.match(/User: (.+)/)?.[1]) ?? "";
const dateLine = responseLines.shift()!;
const date = (dateLine.match(/Date: (.+)/)?.[1]) ?? "";
let promptHashLine = "";
for (let i = responseLines.length - 1; i >= 0; i--) {
if (responseLines[i].startsWith("prompt")) {
promptHashLine = responseLines[i];
responseLines.splice(i, 1);
break;
}
}
if (!promptHashLine) {
return {
"prompt-hash": "",
user: user,
date: date,
response: responseLines.join("\n"),
isError: isPartial ? false : true,
};
}
const promptHash = promptHashLine.split(" ")[1];
const response = responseLines.join("\n");
return {
"prompt-hash": promptHash,
user,
date,
response,
isError: false,
};
};
let receviedStdout = "";
const onStdoutPartial = (stdout: string) => {
receviedStdout += stdout;
const data = parseOutData(receviedStdout, true);
onData(data);
};
logger.channel()?.info(`Running devchat with args: ${args.join(" ")}`);
const { exitCode: code, stdout, stderr } = await this.commandRun.spawnAsync(devChat, args, {
maxBuffer: 10 * 1024 * 1024, // Set maxBuffer to 10 MB
cwd: workspaceDir,
env: {
...process.env,
OPENAI_API_KEY: openaiApiKey,
...openAiApiBaseObject
},
}, onStdoutPartial, undefined, undefined, undefined);
if (stderr) {
const errorMessage = stderr.trim().match(/Error(.+)/)?.[1];
return {
"prompt-hash": "",
user: "",
date: "",
response: errorMessage ? `Error: ${errorMessage}` : "Unknown error",
isError: true,
};
}
const response = parseOutData(stdout, false);
return response;
} catch (error: any) {
return {
"prompt-hash": "",
user: "",
date: "",
response: `Error: ${error.stderr}\nExit code: ${error.code}`,
isError: true,
};
}
}
async log(options: LogOptions = {}): Promise<LogEntry[]> {
const args = this.buildLogArgs(options);
const devChat = this.getDevChatPath();
const workspaceDir = vscode.workspace.workspaceFolders?.[0].uri.fsPath;
const openaiApiKey = process.env.OPENAI_API_KEY;
try {
logger.channel()?.info(`Running devchat with args: ${args.join(" ")}`);
const { exitCode: code, stdout, stderr } = await this.commandRun.spawnAsync(devChat, args, {
maxBuffer: 10 * 1024 * 1024, // Set maxBuffer to 10 MB
cwd: workspaceDir,
env: {
...process.env,
OPENAI_API_KEY: openaiApiKey,
},
}, undefined, undefined, undefined, undefined);
logger.channel()?.info(`Finish devchat with args: ${args.join(" ")}`);
if (stderr) {
logger.channel()?.error(`Error getting log: ${stderr}`);
logger.channel()?.show();
return [];
}
return JSON.parse(stdout.trim()).reverse();
} catch (error) {
logger.channel()?.error(`Error getting log: ${error}`);
logger.channel()?.show();
return [];
}
}
private buildLogArgs(options: LogOptions): string[] {
let args = ["log"];
if (options.skip) {
args.push('--skip', `${options.skip}`);
}
if (options.maxCount) {
args.push('--max-count', `${options.maxCount}`);
} else {
const maxLogCount = vscode.workspace.getConfiguration('DevChat').get('maxLogCount');
args.push('--max-count', `${maxLogCount}`);
}
return args;
}
private getDevChatPath(): string {
let devChat: string | undefined = vscode.workspace.getConfiguration('DevChat').get('DevChatPath');
if (!devChat) {
devChat = 'devchat';
}
return devChat;
}
}
export default DevChat;