Skip to content

Commit 2486239

Browse files
authored
chore: add github discussion auto-responder (#3525)
* chore: add github discussion auto-responder * fix: rewrite in deno * fix: improvements
1 parent 9c9f37b commit 2486239

File tree

3 files changed

+174
-1
lines changed

3 files changed

+174
-1
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Discussion auto-responder
2+
on:
3+
discussion:
4+
types: [created]
5+
env:
6+
GH_SERVICE_ACC_DISCUSSIONS_TOKEN: ${{ github.token }}
7+
API_SECRET: ${{ secrets.API_SECRET }}
8+
jobs:
9+
on_discussion_open:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
discussions: write
13+
steps:
14+
- name: Checkout repository
15+
uses: actions/checkout@v2
16+
- name: Set up Deno
17+
uses: denolib/setup-deno@v2
18+
with:
19+
deno-version: "v1.x"
20+
- name: Run Deno script
21+
run: npm run ci:autorespond
22+
env:
23+
DISCUSSION_NODE_ID: ${{ github.event.discussion.node_id }}
24+
DISCUSSION_BODY: ${{ github.event.discussion.body }}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@
8383
"changeset": "changeset",
8484
"create:package": "plop create-package",
8585
"token-usage": "tsx tools/build/token-usage-detector.ts",
86-
"nx": "nx"
86+
"nx": "nx",
87+
"ci:autorespond": "deno run --allow-all ./tools/github/autoresponder.ts"
8788
},
8889
"dependencies": {
8990
"@babel/cli": "^7.21.0",

tools/github/autoresponder.ts

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/* eslint-disable no-console,import/no-unresolved,import/extensions */
2+
// @ts-expect-error deno
3+
import { Octokit } from "https://esm.sh/octokit?dts";
4+
// @ts-expect-error deno
5+
const API_SECRET = Deno.env.get("API_SECRET");
6+
// @ts-expect-error deno
7+
const GH_SERVICE_ACC_DISCUSSIONS_TOKEN = Deno.env.get(
8+
"GH_SERVICE_ACC_DISCUSSIONS_TOKEN"
9+
);
10+
// @ts-expect-error deno
11+
const DISCUSSION_NODE_ID = Deno.env.get("DISCUSSION_NODE_ID");
12+
// @ts-expect-error deno
13+
const DISCUSSION_BODY = Deno.env.get("DISCUSSION_BODY");
14+
15+
console.log("discussion body:", DISCUSSION_BODY);
16+
console.log("discussion node_id:", DISCUSSION_NODE_ID);
17+
18+
const octokit = new Octokit({ auth: GH_SERVICE_ACC_DISCUSSIONS_TOKEN });
19+
20+
type Discussion = {
21+
path: string;
22+
content: string;
23+
similarity: number;
24+
source: string;
25+
type: string;
26+
meta: Record<string, unknown>;
27+
heading: string;
28+
slug: string;
29+
};
30+
31+
const getSimilarDiscussions = async (
32+
secret: string,
33+
question: string
34+
): Promise<string> => {
35+
try {
36+
const response = await fetch(
37+
"https://paste.twilio.design/api/discussions-search",
38+
{
39+
method: "POST",
40+
headers: {
41+
"Content-Type": "application/json",
42+
},
43+
body: JSON.stringify({
44+
secret,
45+
prompt: question,
46+
}),
47+
}
48+
);
49+
const responseJson = JSON.parse(await response.text());
50+
51+
return (
52+
// Get the top 3 results at most
53+
responseJson.data
54+
.slice(0, 3)
55+
// We only want results that are fairly similar, not guesses
56+
.filter((item: Discussion) => item.similarity > 0.78)
57+
// Convert to markdown
58+
.map(
59+
(item: Discussion) =>
60+
`- [${item.heading}](${item.path}) (updated: ${
61+
item.meta.updatedAt
62+
}, similarity score: ${item.similarity.toFixed(2)})`
63+
)
64+
// Convert to string
65+
.join("\n")
66+
);
67+
} catch (error) {
68+
console.log("Error fetching similar discussions", error);
69+
}
70+
return "No similar discussions found.";
71+
};
72+
73+
const getAnswerFromAi = async (
74+
secret: string,
75+
question: string
76+
): Promise<string> => {
77+
try {
78+
const response = await fetch("https://paste.twilio.design/api/ai", {
79+
method: "POST",
80+
headers: {
81+
"Content-Type": "application/json",
82+
},
83+
body: JSON.stringify({
84+
secret,
85+
prompt: question,
86+
}),
87+
});
88+
89+
return response.text();
90+
} catch (error) {
91+
console.log("Error fetching answer from AI", error);
92+
throw error;
93+
}
94+
};
95+
96+
const writeAnswerToDiscussion = async (
97+
discussionId: string,
98+
body: string
99+
): Promise<string> => {
100+
const mutation = `
101+
mutation comment($discussionId:ID!,$body:String!){
102+
addDiscussionComment(input: { discussionId:$discussionId,body:$body}) {
103+
comment {
104+
id
105+
}
106+
}
107+
}
108+
`;
109+
// console.log("\n\nThe graphQL query:", mutation);
110+
111+
try {
112+
const { addDiscussionComment } = await octokit.graphql(mutation, {
113+
discussionId,
114+
body,
115+
});
116+
return addDiscussionComment.comment.id;
117+
} catch (error) {
118+
console.log("Error writing answer to discussion", error);
119+
throw error;
120+
}
121+
};
122+
123+
const commentHeader = `
124+
**Disclaimer:** This is a very experimental bot using OpenAI's GPT-4. The answers may not be correct, a human will review the answer and update it if necessary.
125+
126+
---
127+
128+
`;
129+
// @ts-expect-error deno
130+
const similarDiscussions = await getSimilarDiscussions(
131+
API_SECRET,
132+
DISCUSSION_BODY
133+
);
134+
console.log("similar discussions:", similarDiscussions);
135+
136+
// @ts-expect-error deno
137+
const answerFromAi = await getAnswerFromAi(API_SECRET, DISCUSSION_BODY);
138+
console.log("response from AI:", answerFromAi.trim().slice(0, 300));
139+
140+
const fullBody = `${commentHeader}${answerFromAi}\n\n---\n\nHere are some similar discussions:\n\n${similarDiscussions}`;
141+
142+
// @ts-expect-error deno
143+
const commentId = await writeAnswerToDiscussion(DISCUSSION_NODE_ID, fullBody);
144+
console.log(`Comment added with ID: ${commentId}`);
145+
146+
// @ts-expect-error deno
147+
Deno.exit(0);
148+
/* eslint-enable no-console,import/no-unresolved,import/extensions */

0 commit comments

Comments
 (0)