Skip to content

Commit fbbbabb

Browse files
committed
Updated classroom.yml and move feedback to its own file
1 parent 6fc55e8 commit fbbbabb

File tree

2 files changed

+179
-172
lines changed

2 files changed

+179
-172
lines changed

.github/workflows/classroom.yml

Lines changed: 0 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -59,175 +59,3 @@ jobs:
5959
EDGE-CASE-TESTS_RESULTS: "${{steps.edge-case-tests.outputs.result}}"
6060
with:
6161
runners: compilation-check,basic-tests,edge-case-tests
62-
ai_feedback:
63-
name: AI-Powered Feedback
64-
needs: run-autograding-tests
65-
if: ${{ needs.run-autograding-tests.result == 'success' }}
66-
runs-on: ubuntu-latest
67-
permissions:
68-
pull-requests: write
69-
env:
70-
OPENROUTER_MODEL: ${{ vars.OPENROUTER_MODEL }}
71-
SYSTEM_PROMPT: ${{ vars.SYSTEM_PROMPT }}
72-
steps:
73-
- name: Checkout repository
74-
uses: actions/checkout@v5
75-
76-
- name: Read assignment instructions
77-
id: instructions
78-
run: |
79-
# Reads the content of the README.md file into an output variable.
80-
# The `EOF` marker is used to handle multi-line file content.
81-
echo "instructions=$(cat README.md | sed 's/\"/\\\"/g' | sed 's/$/\\n/' | tr -d '\n' | sed 's/\\n/\\\\n/g')" >> $GITHUB_OUTPUT
82-
83-
- name: Read source code
84-
id: source_code
85-
run: |
86-
{
87-
echo 'source_code<<EOF'
88-
find src/main/java -type f -name "*.java" | while read -r file; do
89-
echo "=== File: $file ==="
90-
cat "$file"
91-
echo
92-
done
93-
echo 'EOF'
94-
} >> "$GITHUB_OUTPUT"
95-
96-
- name: Read test code
97-
id: test_code
98-
run: |
99-
{
100-
echo 'test_code<<EOF'
101-
if [ -d "src/test/java" ]; then
102-
find src/test/java -type f -name "*.java" | while read -r file; do
103-
echo "=== File: $file ==="
104-
cat "$file"
105-
echo
106-
done
107-
else
108-
echo "No test code found."
109-
fi
110-
echo 'EOF'
111-
} >> "$GITHUB_OUTPUT"
112-
- name: Generate AI Feedback
113-
id: ai_feedback
114-
run: |
115-
# This step sends the collected data to the OpenRouter API.
116-
INSTRUCTIONS=$(jq -Rs . <<'EOF'
117-
${{ steps.instructions.outputs.instructions }}
118-
EOF
119-
)
120-
SOURCE_CODE=$(jq -Rs . <<'EOF'
121-
${{ steps.source_code.outputs.source_code }}
122-
EOF
123-
)
124-
TEST_CODE=$(jq -Rs . <<'EOF'
125-
${{ steps.test_code.outputs.test_code }}
126-
EOF
127-
)
128-
129-
if [ -z "$INSTRUCTIONS" ] || [ -z "$SOURCE_CODE" ] || [ -z "$TEST_CODE" ]; then
130-
echo "Error: One or more required variables are not set."
131-
exit 1
132-
fi
133-
134-
# Assigning to USER_CONTENT with variable expansion
135-
PAYLOAD="Please provide feedback on the following Java assignment.
136-
137-
--- Assignment Instructions ---
138-
${INSTRUCTIONS}
139-
140-
--- Source files ---
141-
${SOURCE_CODE}
142-
143-
--- Test files ---
144-
${TEST_CODE}"
145-
146-
JSON_CONTENT=$(jq -n \
147-
--argjson model "$OPENROUTER_MODEL" \
148-
--arg system_prompt "$SYSTEM_PROMPT" \
149-
--arg payload "$PAYLOAD" \
150-
'{
151-
models: $model,
152-
messages: [
153-
{role: "system", content: $system_prompt},
154-
{role: "user", content: $payload}
155-
]
156-
}')
157-
158-
echo "$JSON_CONTENT"
159-
160-
API_RESPONSE=$(echo "$JSON_CONTENT" | curl https://openrouter.ai/api/v1/chat/completions \
161-
-H "Authorization: Bearer ${{ secrets.OPENROUTER_API_KEY }}" \
162-
-H "Content-Type: application/json" \
163-
-d @-)
164-
165-
echo "$API_RESPONSE"
166-
167-
FEEDBACK_CONTENT=$(echo "$API_RESPONSE" | jq -r '.choices[0].message.content')
168-
echo "feedback<<EOF" >> $GITHUB_OUTPUT
169-
echo "$FEEDBACK_CONTENT" >> $GITHUB_OUTPUT
170-
echo "EOF" >> $GITHUB_OUTPUT
171-
- name: Post Feedback as PR Comment ✍️
172-
uses: actions/github-script@v7
173-
env:
174-
FEEDBACK_BODY: ${{ steps.ai_feedback.outputs.feedback }}
175-
with:
176-
github-token: ${{ secrets.GITHUB_TOKEN }}
177-
script: |
178-
const { owner, repo } = context.repo;
179-
const targetTitle = "Feedback";
180-
const signature = "🤖 AI Feedback";
181-
182-
const { data: pullRequests } = await github.rest.pulls.list({
183-
owner,
184-
repo,
185-
state: "open",
186-
per_page: 100
187-
});
188-
189-
const matchingPR = pullRequests.find(pr => pr.title.trim().toLowerCase() === targetTitle.toLowerCase());
190-
if (!matchingPR) {
191-
throw new Error(`No open pull request found with title '${targetTitle}'`);
192-
}
193-
194-
const prNumber = matchingPR.number;
195-
196-
const { data: comments } = await github.rest.issues.listComments({
197-
owner,
198-
repo,
199-
issue_number: prNumber,
200-
per_page: 100
201-
});
202-
203-
const existing = comments.find(c =>
204-
c.user?.login === "github-actions[bot]" &&
205-
c.body?.includes(signature)
206-
);
207-
208-
const timestamp = new Date().toISOString();
209-
const newEntry = `🕒 _Posted on ${timestamp}_\n\n${process.env.FEEDBACK_BODY}\n\n---\n`;
210-
211-
if (existing) {
212-
// Extract previous entries and wrap them in a collapsible block
213-
const previousContent = existing.body.replace(/^### 🤖 AI Feedback\s*/, '').trim();
214-
const collapsed = `<details><summary>Previous Feedback</summary>\n\n${previousContent}\n</details>`;
215-
216-
const updatedBody = `### ${signature}\n\n${newEntry}${collapsed}`;
217-
await github.rest.issues.updateComment({
218-
owner,
219-
repo,
220-
comment_id: existing.id,
221-
body: updatedBody
222-
});
223-
console.log(`🔄 Updated existing comment on PR #${prNumber}`);
224-
} else {
225-
const body = `### ${signature}\n\n${newEntry}`;
226-
await github.rest.issues.createComment({
227-
owner,
228-
repo,
229-
issue_number: prNumber,
230-
body
231-
});
232-
console.log(`🆕 Posted new comment on PR #${prNumber}`);
233-
}

.github/workflows/feedback.yml

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
name: AI-Powered Feedback
2+
3+
on:
4+
pull_request:
5+
types: [ opened, synchronize ]
6+
branches:
7+
- feedback
8+
9+
jobs:
10+
ai_feedback:
11+
name: AI-Powered Feedback
12+
runs-on: ubuntu-latest
13+
permissions:
14+
pull-requests: write
15+
env:
16+
OPENROUTER_MODEL: ${{ vars.OPENROUTER_MODEL }}
17+
SYSTEM_PROMPT: ${{ vars.SYSTEM_PROMPT }}
18+
steps:
19+
- name: Checkout repository
20+
uses: actions/checkout@v5
21+
22+
- name: Read assignment instructions
23+
id: instructions
24+
run: |
25+
# Reads the content of the README.md file into an output variable.
26+
# The `EOF` marker is used to handle multi-line file content.
27+
echo "instructions=$(cat README.md | sed 's/\"/\\\"/g' | sed 's/$/\\n/' | tr -d '\n' | sed 's/\\n/\\\\n/g')" >> $GITHUB_OUTPUT
28+
29+
- name: Read source code
30+
id: source_code
31+
run: |
32+
{
33+
echo 'source_code<<EOF'
34+
find src/main/java -type f -name "*.java" | while read -r file; do
35+
echo "=== File: $file ==="
36+
cat "$file"
37+
echo
38+
done
39+
echo 'EOF'
40+
} >> "$GITHUB_OUTPUT"
41+
42+
- name: Read test code
43+
id: test_code
44+
run: |
45+
{
46+
echo 'test_code<<EOF'
47+
if [ -d "src/test/java" ]; then
48+
find src/test/java -type f -name "*.java" | while read -r file; do
49+
echo "=== File: $file ==="
50+
cat "$file"
51+
echo
52+
done
53+
else
54+
echo "No test code found."
55+
fi
56+
echo 'EOF'
57+
} >> "$GITHUB_OUTPUT"
58+
- name: Generate AI Feedback
59+
id: ai_feedback
60+
run: |
61+
# This step sends the collected data to the OpenRouter API.
62+
INSTRUCTIONS=$(jq -Rs . <<'EOF'
63+
${{ steps.instructions.outputs.instructions }}
64+
EOF
65+
)
66+
SOURCE_CODE=$(jq -Rs . <<'EOF'
67+
${{ steps.source_code.outputs.source_code }}
68+
EOF
69+
)
70+
TEST_CODE=$(jq -Rs . <<'EOF'
71+
${{ steps.test_code.outputs.test_code }}
72+
EOF
73+
)
74+
75+
if [ -z "$INSTRUCTIONS" ] || [ -z "$SOURCE_CODE" ] || [ -z "$TEST_CODE" ]; then
76+
echo "Error: One or more required variables are not set."
77+
exit 1
78+
fi
79+
80+
# Assigning to USER_CONTENT with variable expansion
81+
PAYLOAD="Please provide feedback on the following Java assignment.
82+
83+
--- Assignment Instructions ---
84+
${INSTRUCTIONS}
85+
86+
--- Source files ---
87+
${SOURCE_CODE}
88+
89+
--- Test files ---
90+
${TEST_CODE}"
91+
92+
JSON_CONTENT=$(jq -n \
93+
--argjson model "$OPENROUTER_MODEL" \
94+
--arg system_prompt "$SYSTEM_PROMPT" \
95+
--arg payload "$PAYLOAD" \
96+
'{
97+
models: $model,
98+
messages: [
99+
{role: "system", content: $system_prompt},
100+
{role: "user", content: $payload}
101+
]
102+
}')
103+
104+
echo "$JSON_CONTENT"
105+
106+
API_RESPONSE=$(echo "$JSON_CONTENT" | curl https://openrouter.ai/api/v1/chat/completions \
107+
-H "Authorization: Bearer ${{ secrets.OPENROUTER_API_KEY }}" \
108+
-H "Content-Type: application/json" \
109+
-d @-)
110+
111+
echo "$API_RESPONSE"
112+
113+
FEEDBACK_CONTENT=$(echo "$API_RESPONSE" | jq -r '.choices[0].message.content')
114+
echo "feedback<<EOF" >> $GITHUB_OUTPUT
115+
echo "$FEEDBACK_CONTENT" >> $GITHUB_OUTPUT
116+
echo "EOF" >> $GITHUB_OUTPUT
117+
- name: Post Feedback as PR Comment ✍️
118+
uses: actions/github-script@v7
119+
env:
120+
FEEDBACK_BODY: ${{ steps.ai_feedback.outputs.feedback }}
121+
with:
122+
github-token: ${{ secrets.GITHUB_TOKEN }}
123+
script: |
124+
const { owner, repo } = context.repo;
125+
const targetTitle = "Feedback";
126+
const signature = "🤖 AI Feedback";
127+
128+
const { data: pullRequests } = await github.rest.pulls.list({
129+
owner,
130+
repo,
131+
state: "open",
132+
per_page: 100
133+
});
134+
135+
const matchingPR = pullRequests.find(pr => pr.title.trim().toLowerCase() === targetTitle.toLowerCase());
136+
if (!matchingPR) {
137+
throw new Error(`No open pull request found with title '${targetTitle}'`);
138+
}
139+
140+
const prNumber = matchingPR.number;
141+
142+
const { data: comments } = await github.rest.issues.listComments({
143+
owner,
144+
repo,
145+
issue_number: prNumber,
146+
per_page: 100
147+
});
148+
149+
const existing = comments.find(c =>
150+
c.user?.login === "github-actions[bot]" &&
151+
c.body?.includes(signature)
152+
);
153+
154+
const timestamp = new Date().toISOString();
155+
const newEntry = `🕒 _Posted on ${timestamp}_\n\n${process.env.FEEDBACK_BODY}\n\n---\n`;
156+
157+
if (existing) {
158+
// Extract previous entries and wrap them in a collapsible block
159+
const previousContent = existing.body.replace(/^### 🤖 AI Feedback\s*/, '').trim();
160+
const collapsed = `<details><summary>Previous Feedback</summary>\n\n${previousContent}\n</details>`;
161+
162+
const updatedBody = `### ${signature}\n\n${newEntry}${collapsed}`;
163+
await github.rest.issues.updateComment({
164+
owner,
165+
repo,
166+
comment_id: existing.id,
167+
body: updatedBody
168+
});
169+
console.log(`🔄 Updated existing comment on PR #${prNumber}`);
170+
} else {
171+
const body = `### ${signature}\n\n${newEntry}`;
172+
await github.rest.issues.createComment({
173+
owner,
174+
repo,
175+
issue_number: prNumber,
176+
body
177+
});
178+
console.log(`🆕 Posted new comment on PR #${prNumber}`);
179+
}

0 commit comments

Comments
 (0)