Skip to content

Commit 196b103

Browse files
committed
Updated classroom.yml
1 parent 8e9bf3a commit 196b103

File tree

1 file changed

+174
-0
lines changed

1 file changed

+174
-0
lines changed

.github/workflows/classroom.yml

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ permissions:
77
checks: write
88
actions: read
99
contents: read
10+
pull-requests: write
1011

1112
jobs:
1213
run-autograding-tests:
14+
name: Autograding
1315
runs-on: ubuntu-latest
1416
if: github.actor != 'github-classroom[bot]'
1517
steps:
@@ -57,3 +59,175 @@ jobs:
5759
EDGE-CASE-TESTS_RESULTS: "${{steps.edge-case-tests.outputs.result}}"
5860
with:
5961
runners: compilation-check,basic-tests,edge-case-tests
62+
ai_feedback:
63+
name: AI-Powered Feedback
64+
needs: autograding
65+
if: ${{ needs.autograding.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+
}

0 commit comments

Comments
 (0)