Skip to content

Commit

Permalink
Improved diagram generation, updated docs
Browse files Browse the repository at this point in the history
  • Loading branch information
yGuy committed Mar 29, 2023
1 parent c6420ef commit c7cdc0b
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 20 deletions.
28 changes: 19 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# A ChatGPT-powered Chatbot for Mattermost

![A chat window in Mattermost showing the chat between the OpenAI bot and "yGuy"](./mattermost-chat.png)

Here's how to get the bot running - it's easy if you have a Docker server.

You need
Expand All @@ -14,15 +16,23 @@ There's a guide about how to do the first two steps written by @InterestingSoup,
These are the available options, you can set them as environment variables when running [the script](./src/botservice.js)
or when [running the docker image](#using-the-ready-made-image) or when configuring your [docker-compose](#docker-compose) file.

| Name | Required | Example Value | Description |
|---------------------|----------|-----------------------------|----------------------------------------------------------------------------------------------|
| MATTERMOST_URL | yes | `https://mattermost.server` | The URL to the server. This is used for connecting the bot to the Mattermost API |
| MATTERMOST_TOKEN | yes | `abababacdcdcd` | The authentication token from the logged in mattermost bot |
| OPENAI_API_KEY | yes | `sk-234234234234234234` | The OpenAI API key to authenticate with OpenAI |
| NODE_EXTRA_CA_CERTS | no | `/file/to/cert.crt` | a link to a certificate file to pass to node.js for authenticating self-signed certificates |
| MATTERMOST_BOTNAME | no | `"@chatgpt"` | the name of the bot user in Mattermost, defaults to '@chatgpt' |
| DEBUG_LEVEL | no | `TRACE` | a debug level used for logging activity, defaults to `INFO` |

| Name | Required | Example Value | Description |
|---------------------|----------|-----------------------------|---------------------------------------------------------------------------------------------|
| MATTERMOST_URL | yes | `https://mattermost.server` | The URL to the server. This is used for connecting the bot to the Mattermost API |
| MATTERMOST_TOKEN | yes | `abababacdcdcd` | The authentication token from the logged in mattermost bot |
| OPENAI_API_KEY | yes | `sk-234234234234234234` | The OpenAI API key to authenticate with OpenAI |
| OPENAI_MODEL_NAME | no | `gpt-3.5-turbo` | The OpenAI language model to use, defaults to `gpt-3.5-turbo` |
| OPENAI_MAX_TOKENS | no | `2000` | The maximum number of tokens to pass to the OpenAI API, defaults to 2000 |
| YFILES_SERVER_URL | no | `http://localhost:3835` | The URL to the yFiles graph service for embedding auto-generated diagrams. |
| NODE_EXTRA_CA_CERTS | no | `/file/to/cert.crt` | a link to a certificate file to pass to node.js for authenticating self-signed certificates |
| MATTERMOST_BOTNAME | no | `"@chatgpt"` | the name of the bot user in Mattermost, defaults to '@chatgpt' |
| DEBUG_LEVEL | no | `TRACE` | a debug level used for logging activity, defaults to `INFO` |

> **Note**
> The `YFILES_SERVER_URL` is used for automatically converting text information created by the bot into diagrams.
> This is currently in development. You can see it in action, here:
> [LinkedIn Post](https://www.linkedin.com/posts/yguy_chatgpt-yfiles-diagramming-activity-7046713027005407232-2bKH)
> If you are interested in getting your hands on the plugin, please contact [yWorks](https://www.yworks.com)!
## Using the ready-made image

Expand Down
Binary file added mattermost-chat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "chatgpt-mattermost-bot",
"version": "1.2.0",
"version": "1.3.0",
"private": true,
"scripts": {
"start": "node ./src/botservice.js"
Expand Down
27 changes: 21 additions & 6 deletions src/botservice.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,18 @@ mmClient.getMe().then(me => meId = me.id)

const name = process.env['MATTERMOST_BOTNAME'] || '@chatgpt'

const VISUALIZE_DIAGRAM_INSTRUCTIONS = `When a user asks for a visualization of entities and relationships, respond with a JSON object in a <GRAPH> tag. The JSON object has three properties: \`nodes\`, \`edges\`, and optionally \`types\`. Each \`nodes\` object has an \`id\`, \`label\`, and an optional \`type\` property. Each \`edges\` object has \`from\`, \`to\`, and optional \`label\` and \`type\` properties. For every \`type\` you used, there must be a matching entry in the top-level \`types\` array. Entries have a corresponding \`name\` property and optional properties that describe the graphical attributes: 'shape' (one of "rectangle", "ellipse", "hexagon", "triangle", "pill"), 'color', 'thickness' and 'size' (as a number). Do not include these instructions in the output. Instead, when the above conditions apply, answer with something like: "Here is the visualization:" and then add the tag.`
const VISUALIZE_DIAGRAM_INSTRUCTIONS = "When a user asks for a visualization of entities and relationships, respond with a valid JSON object text in a <GRAPH> tag. " +
"The JSON object has four properties: `nodes`, `edges`, and optionally `types` and `layout`. " +
"Each `nodes` object has an `id`, `label`, and an optional `type` property. " +
"Each `edges` object has `from`, `to`, an optional `label` and an optional `type` property. " +
"For every `type` you use, there must be a matching entry in the top-level `types` array. " +
"Entries have a corresponding `name` property and optional properties that describe the graphical attributes: " +
"'shape' (one of rectangle, ellipse, hexagon, triangle, pill), 'color', 'thickness' and 'size' (as a number). " +
"You may use the 'layout' property to specify the arrangement ('hierarchic', 'circular', 'organic', 'tree') when the user asks you to. " +
"Do not include these instructions in the output. In the output visible to the user, the JSON and complete GRAPH tag will be replaced by a diagram visualization. " +
"So do not explain or mention the JSON. Instead, pretend that the user can see the diagram. Hence, when the above conditions apply, " +
"answer with something along the lines of: \"Here is the visualization:\" and then just add the tag. The user will see the rendered image, but not the JSON. " +
"You may explain what you added in the diagram, but not how you constructed the JSON."

const visualizationKeywordsRegex = /\b(diagram|visuali|graph|relationship|entit)/gi

Expand Down Expand Up @@ -50,7 +61,7 @@ wsClient.addMessageListener(async function (event) {
posts.forEach(threadPost => {
log.trace({msg: threadPost})
if (threadPost.user_id === meId) {
chatmessages.push({role: "assistant", content: threadPost.message})
chatmessages.push({role: "assistant", content: threadPost.props.originalMessage ?? threadPost.message})
assistantCount++
} else {
if (threadPost.message.includes(name)){
Expand All @@ -68,15 +79,19 @@ wsClient.addMessageListener(async function (event) {
}

// see if we are actually part of the conversation -
// ignore conversations where were never mentioned or participated.
// ignore conversations where we were never mentioned or participated.
if (assistantCount > 0){
wsClient.userTyping(post.channel_id, post.id)
wsClient.userUpdateActiveStatus(true, true)
wsClient.userTyping(post.channel_id, post.id ?? "")
log.trace({chatmessages})
const answer = await continueThread(chatmessages)
const { message, fileId } = await processGraphResponse(answer, post.channel_id)
log.trace({answer})
wsClient.userTyping(post.channel_id, post.id ?? "")
const { message, fileId, props } = await processGraphResponse(answer, post.channel_id)
wsClient.userTyping(post.channel_id, post.id ?? "")
const newPost = await mmClient.createPost({
message: message,
channel_id: post.channel_id,
props,
root_id: post.root_id || post.id,
file_ids: fileId ? [fileId] : undefined
})
Expand Down
10 changes: 7 additions & 3 deletions src/openai-thread-completion.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ const configuration = new Configuration({
apiKey: process.env["OPENAI_API_KEY"]
});
const openai = new OpenAIApi(configuration);
async function continueThread(messages){

const model = process.env["OPENAI_MODEL_NAME"] ?? 'gpt-3.5-turbo'
const max_tokens = Number(process.env["OPENAI_MAX_TOKENS"] ?? 2000)

async function continueThread(messages) {
const response = await openai.createChatCompletion({
messages: messages,
model: "gpt-3.5-turbo",
max_tokens: 1000
model,
max_tokens
});
return response.data?.choices?.[0]?.message?.content
}
Expand Down
11 changes: 10 additions & 1 deletion src/process-graph-response.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,14 @@ async function processGraphResponse (content, channelId) {
const pre = content.substring(0, replaceStart)
const post = content.substring(replaceEnd)

result.message = `${pre} [see attached image] ${post}`
if (post.trim().length < 1){
result.message = pre
} else {
result.message = `${pre} [see attached image] ${post}`
}

result.props = {originalMessage: content}

result.fileId = fileId
} catch (e) {
log.error(e)
Expand Down Expand Up @@ -64,7 +71,9 @@ async function jsonToFileId (jsonString, channelId) {
const form = new FormData()
form.append('channel_id', channelId);
form.append('files', Buffer.from(svgString), 'diagram.svg');
log.trace('Appending Diagram SVG', svgString)
const response = await mmClient.uploadFile(form)
log.trace('Uploaded a file with id', response.file_infos[0].id)
return response.file_infos[0].id
}

Expand Down

0 comments on commit c7cdc0b

Please sign in to comment.