New Demo Example: Wechat Chatbot Powered by OpenAI ChatGPT (#284)

* feat: added wechaty chatgpt integration

* fix: correct formatting

---------

Co-authored-by: Daniel Zheng <daniel@cybever.com>
This commit is contained in:
Daniel Zheng
2023-03-08 11:00:57 -08:00
committed by GitHub
parent bd7de42e39
commit cecc9ee461
7 changed files with 267 additions and 1 deletions
@@ -0,0 +1,22 @@
# Wechaty ChatGPT
This is a wechaty implementation of ChatBot powered by OpenAI ChatGPT
## Setup
Modify `dotenv` file to add your openai_api_key as well as the service_token
```
OPENAI_API_KEY=""
WECHATY_PUPPET_SERVICE_TOKEN=""
```
```shell
npm install
mv dotenv .env
```
## Run
```shell
npm run start
```
+49
View File
@@ -0,0 +1,49 @@
// Importing the Wechaty npm package
import { Wechaty,WechatyBuilder, Contact, Message, ScanStatus, log } from "wechaty";
import { Configuration, OpenAIApi, ChatCompletionRequestMessage } from "openai";
import { PuppetPadlocal } from "wechaty-puppet-padlocal";
import { onScan, onLogin, onLogout, onMessage, onFriendship } from "./utils";
import dotenv from "dotenv";
dotenv.config();
console.log(process.env.OPENAI_API_KEY)
export function createBot(): Wechaty {
const token: string = process.env.WECHATY_PUPPET_SERVICE_TOKEN!
const puppet = new PuppetPadlocal({
token,
});
return WechatyBuilder.build({
name: "chatgpt-bot",
puppet,
});
}
// config openAI
const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
export const openai = new OpenAIApi(configuration);
// Initializing the bot
export const bot = createBot();
// Keep the conversation state
export const initState: Array<ChatCompletionRequestMessage> = new Array({ "role": "system", "content": "You are a helpful assistant." })
bot.on('scan', onScan)
bot.on('login', onLogin)
bot.on('logout', onLogout)
bot.on('message', onMessage)
bot.on('friendship', onFriendship)
bot.start()
.then(() => log.info('StarterBot', 'Starter Bot Started.'))
.catch(e => log.error('StarterBot', e))
bot.ready()
.then(() => log.info('StarterBot', 'Starter Bot Ready.'))
.catch(e => log.error('StarterBot', e))
+2
View File
@@ -0,0 +1,2 @@
OPENAI_API_KEY=""
WECHATY_PUPPET_SERVICE_TOKEN=""
@@ -0,0 +1,30 @@
{
"name": "wechaty-chatgpt",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "npx ts-node bot.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"dotenv": "^16.0.3",
"file-box": "^1.5.5",
"markdown-it": "^13.0.1",
"node-html-to-image": "^3.3.0",
"openai": "^3.2.1",
"qrcode-terminal": "^0.12.0",
"ts-node": "^10.9.1",
"wechaty": "^1.20.2",
"wechaty-puppet-padlocal": "^1.20.1",
"wechaty-puppet-service": "^1.19.9",
"wechaty-puppet-wechat": "^1.18.4"
},
"devDependencies": {
"@types/markdown-it": "^12.2.3",
"@types/qrcode-terminal": "^0.12.0"
}
}
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
export WECHATY_LOG=verbose
export WECHATY_PUPPET=wechaty-puppet-service
npm run start
+157
View File
@@ -0,0 +1,157 @@
import { Contact, Message, ScanStatus, log, Friendship } from "wechaty";
import { bot, openai, initState } from "./bot";
import markdownIt from 'markdown-it';
import { ChatCompletionRequestMessage } from "openai";
import { FileBox } from "file-box";
import qrTerm from "qrcode-terminal";
import nodeHtmlToImage from 'node-html-to-image';
const MEMORY_LIMIT = 50; // max memory
let conversation: Array<ChatCompletionRequestMessage> = new Array();
conversation.forEach(val => initState.push(Object.assign({}, val)));
function convertMarkdownToHtml(markdown: string): string {
const md = new markdownIt();
return md.render(markdown);
}
export function onScan (qrcode: string, status: ScanStatus) {
if (status === ScanStatus.Waiting || status === ScanStatus.Timeout) {
qrTerm.generate(qrcode, { small: true }) // show qrcode on console
const qrcodeImageUrl = [
'https://wechaty.js.org/qrcode/',
encodeURIComponent(qrcode),
].join('')
log.info('StarterBot', 'onScan: %s(%s) - %s', ScanStatus[status], status, qrcodeImageUrl)
} else {
log.info('StarterBot', 'onScan: %s(%s)', ScanStatus[status], status)
}
}
export function onLogin(user: Contact) {
log.info('StarterBot', '%s login', user)
}
export function onLogout(user: Contact) {
log.info('StarterBot', '%s logout', user)
}
export async function onMessage(msg: Message) {
log.info('StarterBot', msg.toString())
const contact = msg.talker();
const content = msg.text();
const isText = msg.type() === bot.Message.Type.Text;
if (msg.self() || !isText) { // msg.self() check if the message is sent from the bot itself
return;
}
if (content === 'ding') {
await contact.say('dong');
}
// return image
if (content.startsWith("/img ")) {
if (conversation.length === MEMORY_LIMIT) {
// reset to initial state when reach the memory limit
log.info("Resetting memory");
conversation = new Array();
conversation.forEach(val => initState.push(Object.assign({}, val)));
}
conversation.push({ "role": "user", "content": content.replace("/i", "") })
const response = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: conversation,
});
try {
const replyContent = response.data.choices[0].message!.content!
const html = convertMarkdownToHtml(replyContent);
await nodeHtmlToImage({
output: './output.png',
html: html
})
log.info('The image was created successfully!')
const fileBox = FileBox.fromFile("./output.png");
await contact.say(fileBox)
// record reply
const reply: ChatCompletionRequestMessage = { "role": "assistant", "content": replyContent };
conversation.push(reply);
} catch (e) {
console.error(e)
}
} else {
// return text if no slash command is specified
if (conversation.length === MEMORY_LIMIT) {
// reset to initial state when reach the memory limit
log.info("Resetting memory");
conversation = new Array();
conversation.forEach(val => initState.push(Object.assign({}, val)));
}
conversation.push({ "role": "user", "content": content.replace("/t", "") })
const response = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: conversation,
});
try {
const replyContent = response.data.choices[0].message!.content!
await contact.say(replyContent);
// record reply
const reply: ChatCompletionRequestMessage = { "role": "assistant", "content": replyContent };
conversation.push(reply);
} catch (e) {
console.error(e);
}
}
}
export async function onFriendship(friendship: Friendship) {
let logMsg;
try {
logMsg = "received `friend` event from " + friendship.contact().name();
log.info(logMsg);
switch (friendship.type()) {
/**
*
* 1. New Friend Request
*
* when request is set, we can get verify message from `request.hello`,
* and accept this request by `request.accept()`
*/
case bot.Friendship.Type.Receive:
logMsg = 'accepted automatically';
log.info("before accept");
await friendship.accept();
// if want to send msg , you need to delay sometimes
await new Promise((r) => setTimeout(r, 1000));
await friendship.contact().say(`Hi ${friendship.contact().name()} from FreeChatGPT, I am your person asistant!\n你好 ${friendship.contact().name()} 我是你的私人助理FreeChatGPT!`);
console.log("after accept");
break;
/**
*
* 2. Friend Ship Confirmed
*
*/
case bot.Friendship.Type.Confirm:
logMsg = "friendship confirmed with " + friendship.contact().name();
break;
default:
break;
}
} catch (e) {
console.error(e);
logMsg = "Friendship try catch failed";
}
log.info(logMsg);
}
+1 -1
View File
@@ -8,7 +8,7 @@ test('wechaty-puppet-wechat', async t => {
puppet: 'wechaty-puppet-wechat',
puppetOptions: {
launchOptions: {
ignoreDefaultArgs: ['--disable-extensions'],
ignoreDefaultArgs: [ '--disable-extensions' ],
},
},
} as const