Skip to content

Commit c7cdc0b

Browse files
committed
Improved diagram generation, updated docs
1 parent c6420ef commit c7cdc0b

File tree

6 files changed

+58
-20
lines changed

6 files changed

+58
-20
lines changed

README.md

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# A ChatGPT-powered Chatbot for Mattermost
22

3+
![A chat window in Mattermost showing the chat between the OpenAI bot and "yGuy"](./mattermost-chat.png)
4+
35
Here's how to get the bot running - it's easy if you have a Docker server.
46

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

17-
| Name | Required | Example Value | Description |
18-
|---------------------|----------|-----------------------------|----------------------------------------------------------------------------------------------|
19-
| MATTERMOST_URL | yes | `https://mattermost.server` | The URL to the server. This is used for connecting the bot to the Mattermost API |
20-
| MATTERMOST_TOKEN | yes | `abababacdcdcd` | The authentication token from the logged in mattermost bot |
21-
| OPENAI_API_KEY | yes | `sk-234234234234234234` | The OpenAI API key to authenticate with OpenAI |
22-
| 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 |
23-
| MATTERMOST_BOTNAME | no | `"@chatgpt"` | the name of the bot user in Mattermost, defaults to '@chatgpt' |
24-
| DEBUG_LEVEL | no | `TRACE` | a debug level used for logging activity, defaults to `INFO` |
25-
19+
| Name | Required | Example Value | Description |
20+
|---------------------|----------|-----------------------------|---------------------------------------------------------------------------------------------|
21+
| MATTERMOST_URL | yes | `https://mattermost.server` | The URL to the server. This is used for connecting the bot to the Mattermost API |
22+
| MATTERMOST_TOKEN | yes | `abababacdcdcd` | The authentication token from the logged in mattermost bot |
23+
| OPENAI_API_KEY | yes | `sk-234234234234234234` | The OpenAI API key to authenticate with OpenAI |
24+
| OPENAI_MODEL_NAME | no | `gpt-3.5-turbo` | The OpenAI language model to use, defaults to `gpt-3.5-turbo` |
25+
| OPENAI_MAX_TOKENS | no | `2000` | The maximum number of tokens to pass to the OpenAI API, defaults to 2000 |
26+
| YFILES_SERVER_URL | no | `http://localhost:3835` | The URL to the yFiles graph service for embedding auto-generated diagrams. |
27+
| 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 |
28+
| MATTERMOST_BOTNAME | no | `"@chatgpt"` | the name of the bot user in Mattermost, defaults to '@chatgpt' |
29+
| DEBUG_LEVEL | no | `TRACE` | a debug level used for logging activity, defaults to `INFO` |
30+
31+
> **Note**
32+
> The `YFILES_SERVER_URL` is used for automatically converting text information created by the bot into diagrams.
33+
> This is currently in development. You can see it in action, here:
34+
> [LinkedIn Post](https://www.linkedin.com/posts/yguy_chatgpt-yfiles-diagramming-activity-7046713027005407232-2bKH)
35+
> If you are interested in getting your hands on the plugin, please contact [yWorks](https://www.yworks.com)!
2636
2737
## Using the ready-made image
2838

mattermost-chat.png

218 KB
Loading

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "chatgpt-mattermost-bot",
3-
"version": "1.2.0",
3+
"version": "1.3.0",
44
"private": true,
55
"scripts": {
66
"start": "node ./src/botservice.js"

src/botservice.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,18 @@ mmClient.getMe().then(me => meId = me.id)
2020

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

23-
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.`
23+
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. " +
24+
"The JSON object has four properties: `nodes`, `edges`, and optionally `types` and `layout`. " +
25+
"Each `nodes` object has an `id`, `label`, and an optional `type` property. " +
26+
"Each `edges` object has `from`, `to`, an optional `label` and an optional `type` property. " +
27+
"For every `type` you use, there must be a matching entry in the top-level `types` array. " +
28+
"Entries have a corresponding `name` property and optional properties that describe the graphical attributes: " +
29+
"'shape' (one of rectangle, ellipse, hexagon, triangle, pill), 'color', 'thickness' and 'size' (as a number). " +
30+
"You may use the 'layout' property to specify the arrangement ('hierarchic', 'circular', 'organic', 'tree') when the user asks you to. " +
31+
"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. " +
32+
"So do not explain or mention the JSON. Instead, pretend that the user can see the diagram. Hence, when the above conditions apply, " +
33+
"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. " +
34+
"You may explain what you added in the diagram, but not how you constructed the JSON."
2435

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

@@ -50,7 +61,7 @@ wsClient.addMessageListener(async function (event) {
5061
posts.forEach(threadPost => {
5162
log.trace({msg: threadPost})
5263
if (threadPost.user_id === meId) {
53-
chatmessages.push({role: "assistant", content: threadPost.message})
64+
chatmessages.push({role: "assistant", content: threadPost.props.originalMessage ?? threadPost.message})
5465
assistantCount++
5566
} else {
5667
if (threadPost.message.includes(name)){
@@ -68,15 +79,19 @@ wsClient.addMessageListener(async function (event) {
6879
}
6980

7081
// see if we are actually part of the conversation -
71-
// ignore conversations where were never mentioned or participated.
82+
// ignore conversations where we were never mentioned or participated.
7283
if (assistantCount > 0){
73-
wsClient.userTyping(post.channel_id, post.id)
74-
wsClient.userUpdateActiveStatus(true, true)
84+
wsClient.userTyping(post.channel_id, post.id ?? "")
85+
log.trace({chatmessages})
7586
const answer = await continueThread(chatmessages)
76-
const { message, fileId } = await processGraphResponse(answer, post.channel_id)
87+
log.trace({answer})
88+
wsClient.userTyping(post.channel_id, post.id ?? "")
89+
const { message, fileId, props } = await processGraphResponse(answer, post.channel_id)
90+
wsClient.userTyping(post.channel_id, post.id ?? "")
7791
const newPost = await mmClient.createPost({
7892
message: message,
7993
channel_id: post.channel_id,
94+
props,
8095
root_id: post.root_id || post.id,
8196
file_ids: fileId ? [fileId] : undefined
8297
})

src/openai-thread-completion.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ const configuration = new Configuration({
33
apiKey: process.env["OPENAI_API_KEY"]
44
});
55
const openai = new OpenAIApi(configuration);
6-
async function continueThread(messages){
6+
7+
const model = process.env["OPENAI_MODEL_NAME"] ?? 'gpt-3.5-turbo'
8+
const max_tokens = Number(process.env["OPENAI_MAX_TOKENS"] ?? 2000)
9+
10+
async function continueThread(messages) {
711
const response = await openai.createChatCompletion({
812
messages: messages,
9-
model: "gpt-3.5-turbo",
10-
max_tokens: 1000
13+
model,
14+
max_tokens
1115
});
1216
return response.data?.choices?.[0]?.message?.content
1317
}

src/process-graph-response.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,14 @@ async function processGraphResponse (content, channelId) {
3232
const pre = content.substring(0, replaceStart)
3333
const post = content.substring(replaceEnd)
3434

35-
result.message = `${pre} [see attached image] ${post}`
35+
if (post.trim().length < 1){
36+
result.message = pre
37+
} else {
38+
result.message = `${pre} [see attached image] ${post}`
39+
}
40+
41+
result.props = {originalMessage: content}
42+
3643
result.fileId = fileId
3744
} catch (e) {
3845
log.error(e)
@@ -64,7 +71,9 @@ async function jsonToFileId (jsonString, channelId) {
6471
const form = new FormData()
6572
form.append('channel_id', channelId);
6673
form.append('files', Buffer.from(svgString), 'diagram.svg');
74+
log.trace('Appending Diagram SVG', svgString)
6775
const response = await mmClient.uploadFile(form)
76+
log.trace('Uploaded a file with id', response.file_infos[0].id)
6877
return response.file_infos[0].id
6978
}
7079

0 commit comments

Comments
 (0)