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