Skip to content

Commit bcaeba4

Browse files
committed
Add reaction to issue comments and simplify review comment handling
1 parent 29a3fac commit bcaeba4

3 files changed

Lines changed: 136 additions & 24 deletions

File tree

โ€Žhandlers/webhooks.jsโ€Ž

Lines changed: 115 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ export async function handleWebhook(request, env) {
5656
case "issue_comment":
5757
return handleIssueCommentEvent(payload, env);
5858

59+
case "pull_request_review_comment":
60+
return handlePullRequestReviewCommentEvent(payload, env);
61+
5962
default:
6063
console.log(`Unhandled event type: ${eventType}`);
6164
return corsResponse({ message: `Ignored: ${eventType}` });
@@ -274,15 +277,20 @@ async function handleIssueCommentEvent(payload, env) {
274277
const prNumber = issue.number;
275278

276279
// ๋ฉ˜์…˜ ๊ฐ์ง€: @dalestudy๋งŒ ์ฒดํฌ
277-
const commentBody = comment.body.toLowerCase();
278-
const isMentioned = commentBody.includes("@dalestudy");
280+
const commentBody = comment.body;
281+
const lowerBody = commentBody.toLowerCase();
282+
const isMentioned = lowerBody.includes("@dalestudy");
279283

280284
if (!isMentioned) {
281285
console.log("Ignoring: bot not mentioned");
282286
return corsResponse({ message: "Ignored: not mentioned" });
283287
}
284288

285-
console.log(`AI review requested for PR #${prNumber}`);
289+
// ๋ฉ˜์…˜ ๋’ค์˜ ํ…์ŠคํŠธ ์ถ”์ถœ
290+
const mentionMatch = commentBody.match(/@dalestudy\s*(.*)/i);
291+
const userRequest = mentionMatch && mentionMatch[1].trim() ? mentionMatch[1].trim() : null;
292+
293+
console.log(`AI review requested for PR #${prNumber}${userRequest ? ` - Request: ${userRequest}` : ""}`);
286294

287295
// OPENAI_API_KEY ํ™•์ธ
288296
if (!env.OPENAI_API_KEY) {
@@ -294,15 +302,24 @@ async function handleIssueCommentEvent(payload, env) {
294302
try {
295303
const appToken = await generateGitHubAppToken(env);
296304

305+
// ๐Ÿ‘€ reaction ์ถ”๊ฐ€ (๋ฆฌ๋ทฐ ์‹œ์ž‘ ์•Œ๋ฆผ)
306+
await fetch(
307+
`https://api.github.com/repos/${repoOwner}/${repoName}/issues/comments/${comment.id}/reactions`,
308+
{
309+
method: "POST",
310+
headers: getGitHubHeaders(appToken),
311+
body: JSON.stringify({ content: "eyes" }),
312+
}
313+
);
314+
297315
await performAIReview(
298316
repoOwner,
299317
repoName,
300318
prNumber,
301319
issue.title,
302320
issue.body,
303321
appToken,
304-
env.OPENAI_API_KEY,
305-
comment.id // ์›๋ณธ ๋Œ“๊ธ€ ID ์ „๋‹ฌ
322+
env.OPENAI_API_KEY
306323
);
307324

308325
console.log(`AI review completed for PR #${prNumber}`);
@@ -316,3 +333,96 @@ async function handleIssueCommentEvent(payload, env) {
316333
return errorResponse(`AI review failed: ${error.message}`, 500);
317334
}
318335
}
336+
337+
/**
338+
* Pull Request Review Comment ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ (์ฝ”๋“œ ๋ผ์ธ ๋Œ“๊ธ€์— ๋Œ€ํ•œ AI ๋ฆฌ๋ทฐ)
339+
*/
340+
async function handlePullRequestReviewCommentEvent(payload, env) {
341+
const action = payload.action;
342+
343+
// created ์•ก์…˜๋งŒ ์ฒ˜๋ฆฌ
344+
if (action !== "created") {
345+
console.log(`Ignoring pull_request_review_comment action: ${action}`);
346+
return corsResponse({ message: `Ignored: ${action}` });
347+
}
348+
349+
console.log(`Processing pull_request_review_comment action: ${action}`);
350+
351+
const comment = payload.comment;
352+
const pullRequest = payload.pull_request;
353+
const repoOwner = payload.repository.owner.login;
354+
const repoName = payload.repository.name;
355+
const prNumber = pullRequest.number;
356+
357+
// ๋ฉ˜์…˜ ๊ฐ์ง€: @dalestudy๋งŒ ์ฒดํฌ
358+
const commentBody = comment.body.toLowerCase();
359+
const isMentioned = commentBody.includes("@dalestudy");
360+
361+
if (!isMentioned) {
362+
console.log("Ignoring: bot not mentioned");
363+
return corsResponse({ message: "Ignored: not mentioned" });
364+
}
365+
366+
console.log(`AI review requested for PR #${prNumber} (review comment)`);
367+
368+
// OPENAI_API_KEY ํ™•์ธ
369+
if (!env.OPENAI_API_KEY) {
370+
console.log("OPENAI_API_KEY not configured");
371+
return corsResponse({ message: "AI review not configured" });
372+
}
373+
374+
// AI ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์‹คํ–‰ (์Šค๋ ˆ๋“œ ๋‹ต๋ณ€)
375+
try {
376+
const appToken = await generateGitHubAppToken(env);
377+
378+
// ๐Ÿ‘€ reaction ์ถ”๊ฐ€
379+
await fetch(
380+
`https://api.github.com/repos/${repoOwner}/${repoName}/pulls/comments/${comment.id}/reactions`,
381+
{
382+
method: "POST",
383+
headers: getGitHubHeaders(appToken),
384+
body: JSON.stringify({ content: "eyes" }),
385+
}
386+
);
387+
388+
// AI ๋ฆฌ๋ทฐ ์ƒ์„ฑ
389+
const { generateCodeReview } = await import("../utils/openai.js");
390+
const { getPRDiff } = await import("../utils/prReview.js");
391+
392+
const prDiff = await getPRDiff(repoOwner, repoName, prNumber, appToken);
393+
394+
// diff๊ฐ€ ๋„ˆ๋ฌด ํฌ๋ฉด ์Šคํ‚ต
395+
const diffLines = prDiff.split("\n").length;
396+
if (diffLines > 1000) {
397+
console.log(`Skipping AI review: diff too large (${diffLines} lines)`);
398+
return corsResponse({ message: "Diff too large" });
399+
}
400+
401+
const reviewContent = await generateCodeReview(
402+
prDiff,
403+
pullRequest.title,
404+
pullRequest.body,
405+
env.OPENAI_API_KEY
406+
);
407+
408+
// ์Šค๋ ˆ๋“œ ๋‹ต๋ณ€์œผ๋กœ ์ž‘์„ฑ
409+
await fetch(
410+
`https://api.github.com/repos/${repoOwner}/${repoName}/pulls/${prNumber}/comments/${comment.id}/replies`,
411+
{
412+
method: "POST",
413+
headers: getGitHubHeaders(appToken),
414+
body: JSON.stringify({ body: reviewContent }),
415+
}
416+
);
417+
418+
console.log(`AI review completed for PR #${prNumber} (thread reply)`);
419+
420+
return corsResponse({
421+
message: "AI review posted as thread reply",
422+
pr: prNumber,
423+
});
424+
} catch (error) {
425+
console.error(`AI review failed for PR #${prNumber}:`, error);
426+
return errorResponse(`AI review failed: ${error.message}`, 500);
427+
}
428+
}

โ€Žutils/openai.jsโ€Ž

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99
* @param {string} prTitle - PR ์ œ๋ชฉ
1010
* @param {string} prBody - PR ๋ณธ๋ฌธ
1111
* @param {string} apiKey - OpenAI API ํ‚ค
12+
* @param {string} userRequest - ์‚ฌ์šฉ์ž์˜ ๊ตฌ์ฒด์ ์ธ ์š”์ฒญ (์„ ํƒ์‚ฌํ•ญ)
1213
* @returns {Promise<string>} AI๊ฐ€ ์ƒ์„ฑํ•œ ๋ฆฌ๋ทฐ ๋Œ“๊ธ€ (๋งˆํฌ๋‹ค์šด)
1314
*/
14-
export async function generateCodeReview(prDiff, prTitle, prBody, apiKey) {
15+
export async function generateCodeReview(prDiff, prTitle, prBody, apiKey, userRequest = null) {
1516
const systemPrompt = `๋‹น์‹ ์€ ๋ฆฌํŠธ์ฝ”๋“œ ์Šคํ„ฐ๋”” ๊ทธ๋ฃน์˜ AI ์ฝ”์น˜์ž…๋‹ˆ๋‹ค.
1617
์•„๋ž˜ ์ฝ”๋“œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ฆฌ๋ทฐํ•˜๊ณ  ๊ฑด์„ค์ ์ธ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•˜์„ธ์š”.
1718
@@ -22,9 +23,12 @@ export async function generateCodeReview(prDiff, prTitle, prBody, apiKey) {
2223
โ€ข ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ ๋ฐ ์Šคํƒ€์ผ, ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค ์ค€์ˆ˜ ์—ฌ๋ถ€
2324
โ€ข ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ ๋˜๋Š” ๊ฐœ์„  ๊ฐ€๋Šฅ์„ฑ
2425
25-
๋‹จ์ˆœํžˆ ์ง€์ ๋งŒ ํ•˜์ง€ ๋ง๊ณ , ๊ฒฉ๋ ค์™€ ํ•™์Šต์ด ๋˜๋Š” ํ”ผ๋“œ๋ฐฑ์„ ํ•จ๊ป˜ ์ฃผ์„ธ์š”. ํ•ด๋‹น ์‚ฌํ•ญ์—†๋Š” ์„น์…˜์€ ์ƒ๋žตํ•˜๊ณ  ์„น์…˜์„ ๋‚˜๋ˆ„์ง€ ๋ง๊ณ  ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ž‘์„ฑํ•˜์„ธ์š”.`;
26+
๋‹จ์ˆœํžˆ ์ง€์ ๋งŒ ํ•˜์ง€ ๋ง๊ณ , ๊ฒฉ๋ ค์™€ ํ•™์Šต์ด ๋˜๋Š” ํ”ผ๋“œ๋ฐฑ์„ ํ•จ๊ป˜ ์ฃผ์„ธ์š”.
27+
ํ•ด๋‹น ์‚ฌํ•ญ์—†๋Š” ํ•ญ๋ชฉ์€ ์ƒ๋žตํ•˜๊ณ  ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ž‘์„ฑํ•˜์„ธ์š”.
28+
300 ๊ธ€์ž๋ฅผ ์ดˆ๊ณผํ•˜์ง€ ๋ง์•„์ฃผ์„ธ์š”.
29+
`;
2630

27-
const userPrompt = `# PR Title
31+
let userPrompt = `# PR Title
2832
${prTitle}
2933
3034
# PR Description
@@ -34,8 +38,13 @@ ${prBody || "No description provided"}
3438
\`\`\`diff
3539
${prDiff}
3640
\`\`\`
41+
`;
3742

38-
Please review this pull request.`;
43+
if (userRequest) {
44+
userPrompt += `\n# User's Specific Request\n${userRequest}\n\nPlease review this pull request, focusing on the user's specific request.`;
45+
} else {
46+
userPrompt += `\nPlease review this pull request.`;
47+
}
3948

4049
const response = await fetch("https://api.openai.com/v1/chat/completions", {
4150
method: "POST",

โ€Žutils/prReview.jsโ€Ž

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { generateCodeReview } from "./openai.js";
1414
* @param {string} githubToken - GitHub ํ† ํฐ
1515
* @returns {Promise<string>} PR diff ๋‚ด์šฉ
1616
*/
17-
async function getPRDiff(repoOwner, repoName, prNumber, githubToken) {
17+
export async function getPRDiff(repoOwner, repoName, prNumber, githubToken) {
1818
const response = await fetch(
1919
`https://api.github.com/repos/${repoOwner}/${repoName}/pulls/${prNumber}`,
2020
{
@@ -33,35 +33,29 @@ async function getPRDiff(repoOwner, repoName, prNumber, githubToken) {
3333
}
3434

3535
/**
36-
* AI ์ฝ”๋“œ ๋ฆฌ๋ทฐ ๋Œ“๊ธ€ ์ž‘์„ฑ (์›๋ณธ ๋Œ“๊ธ€์— ๋‹ต๋ณ€)
36+
* AI ์ฝ”๋“œ ๋ฆฌ๋ทฐ ๋Œ“๊ธ€ ์ž‘์„ฑ
3737
*
3838
* @param {string} repoOwner - ์ €์žฅ์†Œ ์†Œ์œ ์ž
3939
* @param {string} repoName - ์ €์žฅ์†Œ ์ด๋ฆ„
4040
* @param {number} prNumber - PR ๋ฒˆํ˜ธ
4141
* @param {string} reviewContent - ๋ฆฌ๋ทฐ ๋‚ด์šฉ (๋งˆํฌ๋‹ค์šด)
4242
* @param {string} githubToken - GitHub ํ† ํฐ
43-
* @param {number} inReplyTo - ๋‹ต๋ณ€ํ•  ๋Œ“๊ธ€ ID (์„ ํƒ์‚ฌํ•ญ)
4443
*/
4544
async function postReviewComment(
4645
repoOwner,
4746
repoName,
4847
prNumber,
4948
reviewContent,
50-
githubToken,
51-
inReplyTo = null
49+
githubToken
5250
) {
5351
const commentBody = `${reviewContent}`;
5452

55-
const body = inReplyTo
56-
? { body: commentBody, in_reply_to: inReplyTo }
57-
: { body: commentBody };
58-
5953
await fetch(
6054
`https://api.github.com/repos/${repoOwner}/${repoName}/issues/${prNumber}/comments`,
6155
{
6256
method: "POST",
6357
headers: getGitHubHeaders(githubToken),
64-
body: JSON.stringify(body),
58+
body: JSON.stringify({ body: commentBody }),
6559
}
6660
);
6761
}
@@ -76,7 +70,7 @@ async function postReviewComment(
7670
* @param {string} prBody - PR ๋ณธ๋ฌธ
7771
* @param {string} githubToken - GitHub ํ† ํฐ
7872
* @param {string} openaiApiKey - OpenAI API ํ‚ค
79-
* @param {number} commentId - ๋‹ต๋ณ€ํ•  ๋Œ“๊ธ€ ID (์„ ํƒ์‚ฌํ•ญ)
73+
* @param {string} userRequest - ์‚ฌ์šฉ์ž์˜ ๊ตฌ์ฒด์ ์ธ ์š”์ฒญ (์„ ํƒ์‚ฌํ•ญ)
8074
*/
8175
export async function performAIReview(
8276
repoOwner,
@@ -86,7 +80,7 @@ export async function performAIReview(
8680
prBody,
8781
githubToken,
8882
openaiApiKey,
89-
commentId = null
83+
userRequest = null
9084
) {
9185
console.log(`Starting AI review for PR #${prNumber}`);
9286

@@ -108,14 +102,13 @@ export async function performAIReview(
108102
openaiApiKey
109103
);
110104

111-
// ๋ฆฌ๋ทฐ ๋Œ“๊ธ€ ์ž‘์„ฑ (์›๋ณธ ๋Œ“๊ธ€์— ๋‹ต๋ณ€)
105+
// ๋ฆฌ๋ทฐ ๋Œ“๊ธ€ ์ž‘์„ฑ
112106
await postReviewComment(
113107
repoOwner,
114108
repoName,
115109
prNumber,
116110
reviewContent,
117-
githubToken,
118-
commentId
111+
githubToken
119112
);
120113

121114
console.log(`AI review posted for PR #${prNumber}`);

0 commit comments

Comments
ย (0)