Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
54c3375
feat(grading): add interactive AI comment selection and word-level di…
tzj04 Feb 25, 2026
8ee56da
feature(ai_grading): Rendering generate comments button based on xml …
leongyiquan Feb 25, 2026
e244d31
Merge remote-tracking branch 'yiquan/feat/generate-comments-toggling'…
tzj04 Feb 25, 2026
186f47c
feat(grading): Add LLM feedback submission for grading comments
tzj04 Mar 3, 2026
645fe92
feat(groundControl): Add LLM usage statistics monitoring
tzj04 Mar 3, 2026
8f89539
feat(core): Add LLM types and request handlers
tzj04 Mar 3, 2026
d598501
feat: 3 tiered prompt generation and prompt button adjustments
leongyiquan Mar 8, 2026
895e3a2
feat(grading): streamline AI comment selection with inline editor syn…
tzj04 Mar 16, 2026
28e3c3d
refactor(api): simplify saveChosenComments to answer-based endpoint
tzj04 Mar 16, 2026
7f2523d
Merge branch 'master' into feature-ai-comments
tzj04 Mar 16, 2026
43cdbe3
merge: resolve conflicts and integrate 3-tier prompting with ai comments
leongyiquan Mar 20, 2026
eacfe94
feat: implement AI token cost tracking and stats UI
leongyiquan Mar 20, 2026
9cfa21a
Added a new tab for coursewide summary for LLM stats
leongyiquan Mar 28, 2026
df6a1e1
Fixed fail save bug
leongyiquan Mar 28, 2026
518102c
prevent unsafe changes bug
leongyiquan Mar 28, 2026
0d4eb8c
fix(grading): prevent false unsaved state and guard save-and-continue…
tzj04 Mar 29, 2026
ffda3b2
fix(grading): preserve empty AI comment slots to keep index mapping s…
tzj04 Mar 29, 2026
e0fa5f7
fix: add timeout fallback for save controls unlock on grading failure
tzj04 Mar 29, 2026
cb0bc0d
feat(grading): make LLM feedback star rating keyboard-accessible and …
tzj04 Mar 29, 2026
58831f5
fix(grading): use typed selector hook in GradingEditor
tzj04 Mar 29, 2026
23979a1
fix(grading): use shared typed dispatch hook to avoid restricted reac…
tzj04 Mar 29, 2026
d04b4ec
fix(grading): gate is_llm using grading.enable_llm_grading instead of…
tzj04 Mar 30, 2026
4636129
fix(grading): keep LLM comment UX visible without prompts and gate pr…
tzj04 Mar 30, 2026
d1802d8
merge: resolve conflicts with upstream master
tzj04 Mar 30, 2026
9a9c51b
fix: Replaced direct yield of postGrading with a redux-saga CALL effect
tzj04 Mar 30, 2026
903368f
fix(navbar): remove stale hasLlmContent prop from academy navbar righ…
tzj04 Mar 30, 2026
426cdaf
chore(deps): update caniuse-lite browsers data
tzj04 Mar 30, 2026
c67418d
test(backend-saga): fix submit grading-and-continue navigation assert…
tzj04 Mar 30, 2026
fbe8b57
fix: sort imports in academyRoutes.test.ts
tzj04 Mar 30, 2026
d53877f
fix(types): add local declaration shim for @blueprintjs/core
tzj04 Mar 31, 2026
382fb50
fix(groundControl): resolve React 19 runtime compatibility issues
tzj04 Mar 31, 2026
3aaa053
fix(llm-stats): label feedback by task display order
tzj04 Mar 31, 2026
8672ad9
fix(grading): persist and rehydrate AI comment selection state; keep …
tzj04 Mar 31, 2026
2cd0377
fixed hard coded bug for llm in grading page
leongyiquan Mar 31, 2026
4e9ce4e
fixed data handling mismatch
leongyiquan Mar 31, 2026
9ec0c41
fixed formatting issue with \n
leongyiquan Mar 31, 2026
f1cc545
Merge branch 'master' into feature-ai-comments
martin-henz Apr 1, 2026
90f52f2
fix(ground-control): ensure action buttons remain visible on small sc…
tzj04 Apr 3, 2026
1beb5b1
fix(grading): refactor AI comment selection with unified UI and persi…
tzj04 Apr 3, 2026
71e04d9
fix(grading): refactor AI comment selection
tzj04 Apr 3, 2026
d2580aa
Update LLMStatsTab and LLMStatsPage
tzj04 Apr 3, 2026
1617adf
Merge branch 'tzj04-feature-ai-comments'
tzj04 Apr 3, 2026
0bf8aa0
Save LLMStats updates before merging master
tzj04 Apr 3, 2026
5fb13e2
Merge branch 'master' into feature-ai-comments
tzj04 Apr 3, 2026
c938620
Merge branch 'master' of https://github.com/source-academy/frontend i…
tzj04 Apr 3, 2026
608f723
fix(grading): persist cleared AI comment selection after re-generate …
tzj04 Apr 3, 2026
b71637f
Merge branch 'master' into feature-ai-comments
martin-henz Apr 6, 2026
e14306a
Fixed rollback logic for AI comments
leongyiquan Apr 7, 2026
047c2d5
Merge branch 'master' into feature-ai-comments
tzj04 Apr 7, 2026
a4b15f5
Merge branch 'master' into feature-ai-comments
tzj04 Apr 9, 2026
a16bfaa
fix(grading): prevent AI comment rollback corruption on regenerate+sa…
tzj04 Apr 9, 2026
efc78d9
resolve merge conlficts with 'master'
tzj04 Apr 9, 2026
1512451
Fix: resolve conflicts between local master and upstream
tzj04 Apr 13, 2026
e91be09
Merge branch 'master' into feature-ai-comments
tzj04 Apr 13, 2026
4febf6a
Fix incorrect past merge resolution
RichDom2185 May 7, 2026
f717559
Merge branch 'master' of https://github.com/source-academy/frontend i…
RichDom2185 May 7, 2026
4f1b9a3
Undo more incorrect changes
RichDom2185 May 7, 2026
80975b5
Remove unnecessary Vitest config change
RichDom2185 May 7, 2026
f5c2920
Merge branch 'master' of https://github.com/source-academy/frontend i…
RichDom2185 May 7, 2026
09c4815
Fix errors post-merge
RichDom2185 May 7, 2026
4186f20
Migrate changes post-merge
RichDom2185 May 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/commons/assessment/AssessmentTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export type AssessmentOverview = {
hasVotingFeatures: boolean;
hasTokenCounter?: boolean;
isVotingPublished?: boolean;
isLlmGraded?: boolean;
maxXp: number;
earlySubmissionXp: number;
number?: string; // For mission control
Expand Down
93 changes: 88 additions & 5 deletions src/commons/sagas/RequestsSaga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,7 @@
q.library.globals = Object.entries(q.library.globals as object).map(entry => {
try {
entry[1] = (window as any).eval(entry[1]);
} catch (e) {}

Check warning on line 717 in src/commons/sagas/RequestsSaga.ts

View workflow job for this annotation

GitHub Actions / lint (eslint)

Empty block statement
return entry;
});

Expand Down Expand Up @@ -1572,16 +1572,99 @@
return await resp.json();
};

export const saveFinalComment = async (
export const saveChosenComments = async (
tokens: Tokens,
answer_id: number,
comment: string
answerId: number,
selectedIndices: number[],
edits: Record<number, string>
): Promise<Response | null> => {
const resp = await request(`${courseId()}/admin/save-final-comment/${answer_id}`, 'POST', {
body: { comment: comment },
const resp = await request(`${courseId()}/admin/save-chosen-comments/${answerId}`, 'POST', {
body: {
selected_indices: selectedIndices,
edits: edits
},
...tokens
});

return resp;
};

/**
* GET /courses/{courseId}/admin/llm-stats/{assessmentId}
* Fetches assessment-level LLM usage statistics with per-question breakdown.
*/
export const getLLMAssessmentStats = async (
tokens: Tokens,
assessmentId: number
): Promise<any | null> => {
const resp = await request(`${courseId()}/admin/llm-stats/${assessmentId}`, 'GET', {
...tokens
});
if (!resp || !resp.ok) {
return null;
}
return await resp.json();
};

/**
* GET /courses/{courseId}/admin/llm-stats/{assessmentId}/{questionId}
* Fetches question-level LLM usage statistics.
*/
export const getLLMQuestionStats = async (
tokens: Tokens,
assessmentId: number,
questionId: number
): Promise<any | null> => {
const resp = await request(`${courseId()}/admin/llm-stats/${assessmentId}/${questionId}`, 'GET', {
...tokens
});
if (!resp || !resp.ok) {
return null;
}
return await resp.json();
};

/**
* GET /courses/{courseId}/admin/llm-stats/{assessmentId}/feedback?question_id={questionId}
* Fetches LLM feedback for an assessment, optionally filtered by question.
*/
export const getLLMFeedback = async (
tokens: Tokens,
assessmentId: number,
questionId?: number
): Promise<any[] | null> => {
let url = `${courseId()}/admin/llm-stats/${assessmentId}/feedback`;
if (questionId) {
url += `?question_id=${questionId}`;
}
const resp = await request(url, 'GET', {
...tokens
});
if (!resp || !resp.ok) {
return null;
}
return await resp.json();
};

/**
* POST /courses/{courseId}/admin/llm-stats/{assessmentId}/feedback
* Submits feedback for the LLM feature on an assessment (optionally for a specific question).
*/
export const submitLLMFeedback = async (
tokens: Tokens,
assessmentId: number,
body: string,
rating?: number,
questionId?: number
): Promise<Response | null> => {
const resp = await request(`${courseId()}/admin/llm-stats/${assessmentId}/feedback`, 'POST', {
body: {
body,
rating,
question_id: questionId
},
...tokens
});
return resp;
};

Expand Down
32 changes: 20 additions & 12 deletions src/pages/academy/grading/subcomponents/GradingCommentSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { H5, NonIdealState, Spinner } from '@blueprintjs/core';
import { Checkbox, H5, NonIdealState, Spinner } from '@blueprintjs/core';
import React from 'react';
import styles from 'src/styles/GradingCommentSelector.module.scss';

type Props = {
comments: string[];
isLoading: boolean;
onSelect: (comment: string) => void;
selectedIndices: number[];
onToggle: (index: number) => void;
};

const GradingCommentSelector: React.FC<Props> = props => {
Expand All @@ -17,24 +18,31 @@ const GradingCommentSelector: React.FC<Props> = props => {
<NonIdealState icon={<Spinner />} />
) : (
<div>
{' '}
{props.comments.length > 0 ? (
props.comments.map((el, index) => {
props.comments.map((comment, index) => {
const isSelected = props.selectedIndices.includes(index);

return (
<button
<div
key={index}
className={styles['grading-comment-selector-item']}
onClick={() => {
props.onSelect(el);
}}
className={`${styles['grading-comment-selector-item']} ${isSelected ? styles['selected'] : ''}`}
>
{el}
</button>
<div className={styles['comment-header']}>
<Checkbox
checked={isSelected}
onChange={() => props.onToggle(index)}
className={styles['comment-checkbox']}
/>
<div className={styles['comment-text']} onClick={() => props.onToggle(index)}>
{comment}
</div>
</div>
</div>
Comment thread
tzj04 marked this conversation as resolved.
);
})
) : (
<span>No Comments Generated</span>
)}{' '}
)}
</div>
)}
</div>
Expand Down
Loading
Loading