Skip to content

Commit

Permalink
add continuous dialogue feat
Browse files Browse the repository at this point in the history
  • Loading branch information
RealTong committed Mar 17, 2023
1 parent b47c448 commit 8f2909d
Show file tree
Hide file tree
Showing 8 changed files with 467 additions and 166 deletions.
351 changes: 191 additions & 160 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"async-retry": "^1.3.3",
"dotenv": "^16.0.3",
"execa": "^6.1.0",
"openai": "^3.2.1",
"qrcode": "^1.5.1",
"uuid": "^9.0.0",
"wechaty": "^1.20.2",
Expand Down
58 changes: 52 additions & 6 deletions src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { config } from "./config.js";
import { ContactInterface, RoomInterface } from "wechaty/impls";
import { Message } from "wechaty";
import {sendMessage} from "./chatgpt.js";
import {getCompletion} from "./openai.js";
import {addSessionByUsername, setPromptByUsername} from "./data.js";
enum MessageType {
Unknown = 0,

Expand Down Expand Up @@ -44,7 +46,36 @@ export class ChatGPTBot {
}
return regEx
}
async command(): Promise<void> {}
async command(talker:any, text:string, privateChat:boolean): Promise<void> {
// 找到第一个空格之前的字符串
const command = text.split(" ")[0];
console.log(`command: ${command}`);
switch (command) {
case "help":
await this.trySay(talker,"========\n" +
"/cmd prompt <PROMPT>\n" +
"# 设置当前会话的prompt\n" +
"/cmd clear\n" +
"# 清除自上次启动以来的所有会话\n" +
"========");
break;
case "prompt":
let prompt = text.slice(command.length+1);
console.log(`Prompt: ${prompt}`);
if (privateChat){
setPromptByUsername(talker.name(), prompt);
await this.trySay(talker,"设置成功");
}else{
setPromptByUsername(talker, prompt);
await this.trySay(talker,"设置成功");
}
break;
case "clear":
console.log("清除会话");
await this.trySay(talker,"清除成功");
break;
}
}
// remove more times conversation and mention
cleanMessage(rawText: string, privateChat: boolean = false): string {
let text = rawText;
Expand All @@ -64,8 +95,10 @@ export class ChatGPTBot {
// remove more text via - - - - - - - - - - - - - - -
return text
}
async getGPTMessage(text: string): Promise<string> {
return await sendMessage(text);
async getGPTMessage(talkerName: string,text: string): Promise<string> {
let gptMessage = await getCompletion(talkerName,text);
addSessionByUsername(talkerName, {assistantMsg:gptMessage});
return gptMessage;
}
// Check if the message returned by chatgpt contains masked words]
checkChatGPTBlockWords(message: string): boolean {
Expand Down Expand Up @@ -145,7 +178,7 @@ export class ChatGPTBot {
}

async onPrivateMessage(talker: ContactInterface, text: string) {
const gptMessage = await this.getGPTMessage(text);
const gptMessage = await this.getGPTMessage(talker.name(),text);
await this.trySay(talker, gptMessage);
}

Expand All @@ -154,20 +187,33 @@ export class ChatGPTBot {
text: string,
room: RoomInterface
) {
const gptMessage = await this.getGPTMessage(text);
const gptMessage = await this.getGPTMessage(talker.name(),text);
const result = `@${talker.name()} ${text}\n\n------ ${gptMessage}`;
await this.trySay(room, result);
}
async onMessage(message: Message) {
console.log(`🎯 ${message.date()} Message: ${message}`);
const talker = message.talker();
const rawText = message.text();
const room = message.room();
const messageType = message.type();
const privateChat = !room;
if (privateChat) {
console.log(`🤵Contact: ${talker.name()} 💬Text: ${rawText}`)
} else {
const topic = await room.topic()
console.log(`🚪Room: ${topic} 🤵Contact: ${talker.name()} 💬Text: ${rawText}`)
}
if (this.isNonsense(talker, messageType, rawText)) {
return;
}
if (rawText.startsWith("/cmd ")){
const text = rawText.slice(5)
if (privateChat){
return await this.command(talker, text, !privateChat);
}else{
return await this.command(room, text,privateChat);
}
}
if (this.triggerGPTMessage(rawText, privateChat)) {
const text = this.cleanMessage(rawText, privateChat);
if (privateChat) {
Expand Down
Empty file added src/command.ts
Empty file.
73 changes: 73 additions & 0 deletions src/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* 使用内存作为数据库
*/
type session = {
userMsg?: string,
assistantMsg?: string
}
type user = {
username: string,
prompt: string,
session: session[]
}
type data = user[]
// Initialize data
const data: data = []

/**
* Add user
* @param username
* @param prompt default: ""
*/
function addUser(username: string, prompt: string = ""): user {
const user = {
username: username,
prompt: prompt,
session: [{
userMsg: "",
assistantMsg: ""
}]
}
data.push(user)
return data.find(user => user.username === username) as user;
}

function addSessionByUsername(
username: string,
{userMsg = "", assistantMsg = ""}: session
): void {
const user = getUserByUsername(username)
if (user) {
user.session.push({
userMsg: userMsg,
assistantMsg: assistantMsg
})
}
}

/**
* Get user by username
* @param username
*/
function getUserByUsername(username: string): user | undefined {
let user = data.find(user => user.username === username);
return user
}
function getSessionByUsername(username: string): session[] | undefined {
const user = getUserByUsername(username)
if (user) {
return user.session
}
}
function getAllData(): data {
return data
}
function setPromptByUsername(username: string, prompt: string): void {
const user = getUserByUsername(username)
if (user) {
user.prompt = prompt
}else{
addUser(username,prompt).prompt= prompt
}
}
export {addUser, addSessionByUsername,getUserByUsername, getSessionByUsername,getAllData,setPromptByUsername}
52 changes: 52 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,28 @@ import { WechatyBuilder } from "wechaty";
import QRCode from "qrcode";
import { ChatGPTBot } from "./bot.js";
import {config} from "./config.js";
import {voiceToText} from "./whisper.js";
import {getAllData} from "./data.js";

const chatGPTBot = new ChatGPTBot();

const bot = WechatyBuilder.build({
name: "wechat-assistant", // generate xxxx.memory-card.json and save login data for the next login
puppet: "wechaty-puppet-wechat",
puppetOptions: {
uos:true
}
});
async function main() {
// 启动一个计时器, 每20秒打印一个数据库
setInterval(() => {
const data = getAllData()
if (data){
data.map((item) => {
console.log("item: ",item)
})
}
}, 20000)
const initializedAt = Date.now()
bot
.on("scan", async (qrcode, status) => {
Expand All @@ -32,11 +48,47 @@ async function main() {
await message.say("pong");
return;
}
if (message.type() === bot.Message.Type.Audio) {
console.log("收到语音消息");
// const fileBox = FileBox.fromFile("/Users/RealTong/Pictures/Snipaste_2022-07-29_17-38-50.png")
// message.say(fileBox)
// const urlLink = new UrlLink({
// description: 'Wechaty is a Bot SDK for Wechat Individual Account which can help you create a bot in 6 lines of javascript, with cross-platform support including Linux, Windows, Darwin(OSX/Mac) and Docker.',
// thumbnailUrl: 'https://camo.githubusercontent.com/f310a2097d4aa79d6db2962fa42bb3bb2f6d43df/68747470733a2f2f6368617469652e696f2f776563686174792f696d616765732f776563686174792d6c6f676f2d656e2e706e67',
// title: 'Wechaty',
// url: 'https://github.com/wechaty/wechaty',
// });
//
// await message.say();
const media = await message.toFileBox();
const name = media.name;
console.log(`收到语音消息: ${name}`);
media.toFile("/Users/RealTong/Desktop/Medias/"+name, true);

message.toFileBox().then((fileBox) => {
// 保存文件
fileBox.toFile("/Users/RealTong/Desktop/Medias/"+fileBox.name, true);
// Whisper
voiceToText("/Users/RealTong/Desktop/Medias/"+fileBox.name).then((text) => {
console.log("语音转文字: ",text);
})
})
return;
}
if(message.type() === bot.Message.Type.Post){
console.log("收到群消息");
return;
}


try {
await chatGPTBot.onMessage(message);
} catch (e) {
console.error(e);
}
})
.on("error",()=>{
console.log("ERROR !!!");
});
try {
await bot.start();
Expand Down
64 changes: 64 additions & 0 deletions src/openai.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {Configuration, OpenAIApi, ChatCompletionRequestMessageRoleEnum} from "openai";
import {addSessionByUsername, getUserByUsername} from "./data.js";
import {ChatCompletionRequestMessage} from "openai/api";


const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);

/**
* Get completion from OpenAI
* @param username
* @param message
*/
async function getCompletion(username:string,message: string): Promise<string> {
// 先将用户输入的消息添加到数据库中
let userData = getUserByUsername(username)
console.log("数据库返回: ", userData)
const messages:ChatCompletionRequestMessage[] = [];
if (userData) {
// 添加用户输入的消息
console.log(`${username}的session:`, userData.session)
addSessionByUsername(username, {userMsg: message})
console.log("Database: ", getUserByUsername(username))
// 填充prompt
if(userData.prompt!==""){
messages.push({
role: ChatCompletionRequestMessageRoleEnum.System,
content: userData.prompt
})
}
// 填充messages
userData.session.map((item) => {
if (item.userMsg!=="") {
messages.push({
role: ChatCompletionRequestMessageRoleEnum.User,
content: item.userMsg as string
})
}
if (item.assistantMsg!=="") {
messages.push({
role: ChatCompletionRequestMessageRoleEnum.Assistant,
content: item.assistantMsg as string
})
}
})
}else{
return "请先执行/cmd prompt命令. \n EXAMPLE: /cmd prompt 你的prompt"
}
console.log("ChatGPT MESSages: ", messages)
const response = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: messages,
temperature: 0.6
}).then((res) => res.data);
if (response.choices[0].message) {
return response.choices[0].message.content.replace(/^\n+|\n+$/g, "");
} else {
return "Something went wrong"
}
}

export {getCompletion};
34 changes: 34 additions & 0 deletions src/whisper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {config} from "./config.js";
// const fs = require('fs');
import fs from 'fs';

let api = config.api;
let apiKey = config.openai_api_key;
const voiceToText = async (path:string) => {
const formData = new FormData();
formData.append("model", "whisper-1");
// 根据文件路径读取文件
const fileContent = fs.readFileSync(path)
// @ts-ignore
formData.append("file",fileContent);
try {
const response = await fetch(`${api}/v1/audio/transcriptions`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "multipart/form-data",
},
body: formData
}).then((res) => res.json());
if (response.error?.message) {
console.log("OpenAI API ERROR: ",response.error.message)
// throw new Error(`OpenAI API ${response.error.message}`);
}
return response.text;
} catch (e) {
console.error(e)
return "Something went wrong"
}
}

export {voiceToText};

0 comments on commit 8f2909d

Please sign in to comment.