-
Notifications
You must be signed in to change notification settings - Fork 1.2k
309 lines (268 loc) · 14.9 KB
/
auto-respond-issues.yml
File metadata and controls
309 lines (268 loc) · 14.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
name: Auto-respond to Issues
on:
issues:
types: [opened]
workflow_dispatch:
inputs:
issue_number:
description: 'Number of issue to handle'
required: true
type: string
permissions:
issues: write
contents: read
jobs:
auto-respond:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: |
npm install axios @octokit/rest
- name: Process issue and generate response
id: process-issue
uses: actions/github-script@v7
with:
script: |
const axios = require('axios');
// Get issue details - handle both automatic and manual triggers
let issue, issueTitle, issueBody, issueNumber, issueAuthor;
if (context.payload.inputs?.issue_number) {
// Manual trigger - fetch issue details
const issueResponse = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: parseInt(context.payload.inputs.issue_number)
});
issue = issueResponse.data;
issueTitle = issue.title;
issueBody = issue.body || '';
issueNumber = issue.number;
issueAuthor = issue.user.login;
} else {
// Automatic trigger - use context
issue = context.payload.issue;
issueTitle = issue.title;
issueBody = issue.body || '';
issueNumber = issue.number;
issueAuthor = issue.user.login;
}
console.log(`Processing issue #${issueNumber}: ${issueTitle}`);
// Skip if issue is from a maintainer/collaborator
const collaborators = ['pwizla'];
const isTesting = issueTitle.includes('[TEST]') || issueBody.includes('testing-auto-response');
if (collaborators.includes(issueAuthor) && !isTesting) {
console.log('Issue from maintainer, skipping auto-response');
return;
}
// Prepare the enhanced query for Kapa AI
const cleanedBody = issueBody
.replace(/<!--[\s\S]*?-->/g, '') // Remove HTML comments
.replace(/\n_No response_\s*$/gm, '') // Remove "No response" placeholders
.replace(/Automatic sync of commit from main/g, '') // Remove auto-sync indicators
.replace(/\n{3,}/g, '\n\n') // Clean up multiple newlines
.trim();
const finalBody = cleanedBody.length < 20 ?
`${cleanedBody}\n\n(Note: This is a brief issue description. The title "${issueTitle}" may contain additional context.)` :
cleanedBody;
const query = [
"I need to provide a helpful, friendly, and comprehensive response to a GitHub issue from the Strapi community.",
"",
"**Issue Title:** " + issueTitle,
"",
"**Issue Description:**",
finalBody,
"",
"Please provide a response that:",
"",
"1. **Tone & Approach:**",
" - Be warm, welcoming, and supportive - this represents the Strapi brand",
" - Show empathy for the user's situation and acknowledge their effort in reporting the issue",
" - Use friendly language that makes the user feel valued in our community",
" - Keep the introduction concise and professional - avoid overly lengthy greetings",
"2. **Technical Content:**",
" - Directly address the question or problem described",
" - Provide relevant code examples, configuration snippets, or step-by-step guidance when applicable",
" - Include links to official Strapi documentation that can help",
" - If the issue involves a bug, acknowledge it and provide workarounds if possible",
" - If it's a feature request, explain current alternatives or suggest next steps",
"",
"3. **Response Guidelines:**",
" - If you cannot find specific information to answer this question, be honest about it",
" - Suggest where the user might find more resources (Discord, documentation sections, etc.)",
" - For complex issues, break down the response into clear, actionable steps",
" - If the issue seems like it requires core team attention, indicate that appropriately",
" - If suggesting feature requests or improvements, direct users to https://feedback.strapi.io",
" - If the issue seems to be a product bug or core functionality issue rather than documentation, clearly mention this and format the username as `@pwizla` (with backticks) for potential transfer to strapi/strapi repository", "",
"Please craft a response that will be posted as an automated GitHub comment, so it should be complete and helpful on its own while being genuinely friendly and supportive."
].join('\n');
try {
// Call Kapa AI Chat API
const kapaApiUrl = `https://api.kapa.ai/query/v1/projects/${process.env.KAPA_PROJECT_ID}/chat/`;
console.log(`Calling Kapa Chat API: ${kapaApiUrl}`);
const kapaResponse = await axios.post(kapaApiUrl, {
query: query,
user_data: {
source: 'github-automation',
issue_number: issueNumber,
author: issueAuthor
}
}, {
headers: {
'X-API-Key': process.env.KAPA_API_TOKEN,
'Content-Type': 'application/json',
'User-Agent': 'Strapi-Docs-GitHub-Bot/1.0'
},
timeout: 120000 // 2-minute timeout
});
console.log('Kapa API response received');
// Extract response data - updated for actual Kapa response structure
const aiResponse = kapaResponse.data.answer;
const sources = kapaResponse.data.relevant_sources || [];
const isUncertain = kapaResponse.data.is_uncertain || false;
if (!aiResponse) {
throw new Error('No answer received from Kapa AI');
}
// Format the response
let responseBody = `🤖 I've analyzed your question and here's what I found:\n\n`;
// Add uncertainty warning if needed
if (isUncertain) {
responseBody += `⚠️ *Note: This response may not be completely accurate. Please verify the information.*\n\n`;
}
responseBody += `${aiResponse}\n\n`;
if (sources.length > 0) {
responseBody += `📚 **Relevant documentation:**\n`;
// Process and format sources
const formattedSources = sources
.filter(source => source.source_url && source.source_url.startsWith('http') && source.title !== 'Documentation')
.map(source => {
const url = source.source_url;
let title = source.title || 'Documentation';
// Handle pipe-separated title|subtitle format
if (title.includes('|')) {
const parts = title.split('|');
const pageTitle = parts[0].trim();
const sectionTitle = parts[1].trim();
// If section title is different from page title, format as "Page - Section"
if (sectionTitle && sectionTitle !== pageTitle) {
title = `${pageTitle} - ${sectionTitle}`;
} else {
title = pageTitle;
}
}
return { title, url };
})
.sort((a, b) => a.title.localeCompare(b.title)); // Sort alphabetically by title
// Remove duplicates (same title and URL)
const uniqueSources = formattedSources.filter((source, index, array) =>
index === array.findIndex(s => s.title === source.title && s.url === source.url)
);
uniqueSources.forEach(source => {
responseBody += `- [${source.title}](${source.url})\n`;
});
responseBody += `\n`;
}
responseBody += `---\n\n`;
responseBody += `ℹ️ This response was generated automatically. `;
responseBody += `If this doesn't fully answer your question or if you need further assistance, `;
responseBody += `please mention \`@pwizla\` in a comment and a human maintainer will help you.\n\n`;
responseBody += `You can also try our [interactive AI chat](https://docs.strapi.io) for more detailed assistance.\n\n`;
responseBody += `💡 For feature requests or product feedback, visit [feedback.strapi.io](https://feedback.strapi.io).`;
// Post the response
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: responseBody
});
// Add labels based on enhanced content analysis
const labels = [];
const titleLower = issueTitle.toLowerCase();
const bodyLower = issueBody.toLowerCase();
const combinedContent = `${titleLower} ${bodyLower}`;
// Enhanced label detection
if (combinedContent.includes('install') || combinedContent.includes('setup') || combinedContent.includes('getting started')) {
labels.push('installation');
}
if (combinedContent.includes('deploy') || combinedContent.includes('production') || combinedContent.includes('hosting')) {
labels.push('deployment');
}
if (combinedContent.includes('api') || combinedContent.includes('endpoint') || combinedContent.includes('rest') || combinedContent.includes('graphql')) {
labels.push('api');
}
if (combinedContent.includes('plugin') || combinedContent.includes('extension')) {
labels.push('plugins');
}
if (combinedContent.includes('documentation') || combinedContent.includes('docs') || titleLower.includes('[request]')) {
labels.push('documentation');
}
if (combinedContent.includes('migration') || combinedContent.includes('upgrade') || combinedContent.includes('v4') || combinedContent.includes('v5')) {
labels.push('migration');
}
if (combinedContent.includes('database') || combinedContent.includes('db') || combinedContent.includes('migration')) {
labels.push('database');
}
if (combinedContent.includes('admin panel') || combinedContent.includes('admin') || combinedContent.includes('customization')) {
labels.push('admin-panel');
}
if (combinedContent.includes('auth') || combinedContent.includes('permission') || combinedContent.includes('jwt') || combinedContent.includes('login')) {
labels.push('authentication');
}
if (combinedContent.includes('i18n') || combinedContent.includes('locale') || combinedContent.includes('translation')) {
labels.push('i18n');
}
if (combinedContent.includes('typo') || combinedContent.includes('error') || combinedContent.includes('bug') || combinedContent.includes('broken')) {
labels.push('bug');
}
if (titleLower.includes('[request]') || combinedContent.includes('feature') || combinedContent.includes('enhancement')) {
labels.push('enhancement');
}
if (titleLower.includes('[auto-sync]') || combinedContent.includes('automatic sync')) {
labels.push('auto-sync');
}
labels.push('auto-responded');
if (labels.length > 0) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: labels
});
}
console.log(`Successfully processed issue #${issueNumber}`);
} catch (error) {
console.error('Error calling Kapa AI:', error.message);
if (error.response) {
console.error(`HTTP Status: ${error.response.status}`);
console.error('Response data:', error.response.data);
}
// Fallback response - Fixed string concatenation
let fallbackResponse = `👋 Hello @${issueAuthor}! Thanks for opening this issue.\n\n`;
fallbackResponse += `🤖 I tried to analyze your question automatically but encountered a technical issue. `;
fallbackResponse += `A human maintainer will review this soon.\n\n`;
fallbackResponse += `In the meantime, you might find answers in our:\n`;
fallbackResponse += `- [Documentation](https://docs.strapi.io)\n`;
fallbackResponse += `- [Community Discord](https://discord.strapi.io)\n`;
fallbackResponse += `- [Interactive AI chat](https://docs.strapi.io) (click "Ask AI")\n\n`;
fallbackResponse += `@pwizla please review this issue.`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: fallbackResponse
});
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: ['needs-review', 'auto-response-failed']
});
}
env:
KAPA_API_TOKEN: ${{ secrets.KAPA_API_TOKEN }}
KAPA_PROJECT_ID: ${{ secrets.KAPA_PROJECT_ID }}