@studious-lms/server 1.25.0 → 1.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +3 -1
- package/.github/workflows/npm-publish.yml +1 -0
- package/AGENTS.md +84 -0
- package/dist/models/cloudStorage.d.ts +11 -0
- package/dist/models/cloudStorage.d.ts.map +1 -1
- package/dist/models/cloudStorage.js +18 -2
- package/dist/models/cloudStorage.js.map +1 -1
- package/dist/models/conversation.d.ts +1 -0
- package/dist/models/conversation.d.ts.map +1 -1
- package/dist/models/labChat.d.ts +40 -0
- package/dist/models/labChat.d.ts.map +1 -1
- package/dist/models/labChat.js +32 -2
- package/dist/models/labChat.js.map +1 -1
- package/dist/models/marketplace.d.ts +35 -0
- package/dist/models/marketplace.d.ts.map +1 -1
- package/dist/models/marketplace.js +9 -2
- package/dist/models/marketplace.js.map +1 -1
- package/dist/models/message.d.ts +3 -0
- package/dist/models/message.d.ts.map +1 -1
- package/dist/models/module.d.ts +228 -0
- package/dist/models/module.d.ts.map +1 -0
- package/dist/models/module.js +69 -0
- package/dist/models/module.js.map +1 -0
- package/dist/pipelines/aiLabChat.d.ts +2 -0
- package/dist/pipelines/aiLabChat.d.ts.map +1 -1
- package/dist/pipelines/aiLabChat.js +60 -16
- package/dist/pipelines/aiLabChat.js.map +1 -1
- package/dist/pipelines/aiLabChatContract.d.ts +170 -2
- package/dist/pipelines/aiLabChatContract.d.ts.map +1 -1
- package/dist/pipelines/aiLabChatContract.js +30 -2
- package/dist/pipelines/aiLabChatContract.js.map +1 -1
- package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
- package/dist/pipelines/aiNewtonChat.js +30 -5
- package/dist/pipelines/aiNewtonChat.js.map +1 -1
- package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
- package/dist/pipelines/gradeWorksheet.js +4 -2
- package/dist/pipelines/gradeWorksheet.js.map +1 -1
- package/dist/pipelines/labChatPrompt.d.ts +1 -1
- package/dist/pipelines/labChatPrompt.d.ts.map +1 -1
- package/dist/pipelines/labChatPrompt.js +13 -14
- package/dist/pipelines/labChatPrompt.js.map +1 -1
- package/dist/routers/_app.d.ts +1185 -215
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +4 -2
- package/dist/routers/_app.js.map +1 -1
- package/dist/routers/admin.d.ts +0 -1
- package/dist/routers/admin.d.ts.map +1 -1
- package/dist/routers/conversation.d.ts +1 -0
- package/dist/routers/conversation.d.ts.map +1 -1
- package/dist/routers/labChat.d.ts +10 -0
- package/dist/routers/labChat.d.ts.map +1 -1
- package/dist/routers/labChat.js +6 -3
- package/dist/routers/labChat.js.map +1 -1
- package/dist/routers/marketplace.d.ts +41 -0
- package/dist/routers/marketplace.d.ts.map +1 -1
- package/dist/routers/marketplace.js +13 -3
- package/dist/routers/marketplace.js.map +1 -1
- package/dist/routers/message.d.ts +37 -4
- package/dist/routers/message.d.ts.map +1 -1
- package/dist/routers/message.js +24 -7
- package/dist/routers/message.js.map +1 -1
- package/dist/routers/module.d.ts +345 -0
- package/dist/routers/module.d.ts.map +1 -0
- package/dist/routers/module.js +82 -0
- package/dist/routers/module.js.map +1 -0
- package/dist/routers/parent.d.ts +18 -0
- package/dist/routers/parent.d.ts.map +1 -1
- package/dist/routers/parent.js +10 -3
- package/dist/routers/parent.js.map +1 -1
- package/dist/routers/studentProgress.d.ts +44 -0
- package/dist/routers/studentProgress.d.ts.map +1 -1
- package/dist/routers/studentProgress.js +12 -3
- package/dist/routers/studentProgress.js.map +1 -1
- package/dist/routes/aiStream.js +32 -2
- package/dist/routes/aiStream.js.map +1 -1
- package/dist/seedDatabase.d.ts.map +1 -1
- package/dist/seedDatabase.js +107 -3
- package/dist/seedDatabase.js.map +1 -1
- package/dist/services/adminUser.d.ts +0 -1
- package/dist/services/adminUser.d.ts.map +1 -1
- package/dist/services/adminUser.js +7 -4
- package/dist/services/adminUser.js.map +1 -1
- package/dist/services/aiChatStream.d.ts +15 -0
- package/dist/services/aiChatStream.d.ts.map +1 -1
- package/dist/services/aiChatStream.js +144 -74
- package/dist/services/aiChatStream.js.map +1 -1
- package/dist/services/aiGeneration.d.ts +4 -0
- package/dist/services/aiGeneration.d.ts.map +1 -0
- package/dist/services/aiGeneration.js +88 -0
- package/dist/services/aiGeneration.js.map +1 -0
- package/dist/services/aiProposalTools.d.ts +4 -1
- package/dist/services/aiProposalTools.d.ts.map +1 -1
- package/dist/services/aiProposalTools.js +128 -3
- package/dist/services/aiProposalTools.js.map +1 -1
- package/dist/services/aiProposals.d.ts +70 -0
- package/dist/services/aiProposals.d.ts.map +1 -0
- package/dist/services/aiProposals.js +363 -0
- package/dist/services/aiProposals.js.map +1 -0
- package/dist/services/assignment.d.ts.map +1 -1
- package/dist/services/assignment.js +8 -3
- package/dist/services/assignment.js.map +1 -1
- package/dist/services/conversation.d.ts +1 -0
- package/dist/services/conversation.d.ts.map +1 -1
- package/dist/services/labChat.d.ts +10 -0
- package/dist/services/labChat.d.ts.map +1 -1
- package/dist/services/labChat.js +52 -3
- package/dist/services/labChat.js.map +1 -1
- package/dist/services/marketplace.d.ts +36 -0
- package/dist/services/marketplace.d.ts.map +1 -1
- package/dist/services/marketplace.js +30 -3
- package/dist/services/marketplace.js.map +1 -1
- package/dist/services/message.d.ts +24 -8
- package/dist/services/message.d.ts.map +1 -1
- package/dist/services/message.js +170 -117
- package/dist/services/message.js.map +1 -1
- package/dist/services/module.d.ts +288 -0
- package/dist/services/module.d.ts.map +1 -0
- package/dist/services/module.js +518 -0
- package/dist/services/module.js.map +1 -0
- package/dist/services/parentPortal.d.ts +14 -0
- package/dist/services/parentPortal.d.ts.map +1 -1
- package/dist/services/parentPortal.js +72 -2
- package/dist/services/parentPortal.js.map +1 -1
- package/dist/services/school.d.ts.map +1 -1
- package/dist/services/school.js +17 -5
- package/dist/services/school.js.map +1 -1
- package/dist/services/studentProgress.d.ts +50 -0
- package/dist/services/studentProgress.d.ts.map +1 -1
- package/dist/services/studentProgress.js +107 -2
- package/dist/services/studentProgress.js.map +1 -1
- package/dist/utils/inference.d.ts +10 -0
- package/dist/utils/inference.d.ts.map +1 -1
- package/dist/utils/inference.js +77 -2
- package/dist/utils/inference.js.map +1 -1
- package/dist/workers/processors/createRunJobWorker.js +17 -16
- package/dist/workers/processors/createRunJobWorker.js.map +1 -1
- package/package.json +1 -1
- package/prisma/migrations/20260708013000_add_modules/migration.sql +100 -0
- package/prisma/migrations/20260811130000_add_ai_message_feedback/migration.sql +7 -0
- package/prisma/schema.prisma +101 -0
- package/src/models/cloudStorage.ts +19 -0
- package/src/models/labChat.ts +34 -0
- package/src/models/marketplace.ts +16 -0
- package/src/models/module.ts +77 -0
- package/src/pipelines/aiLabChat.ts +79 -18
- package/src/pipelines/aiLabChatContract.ts +35 -0
- package/src/pipelines/aiNewtonChat.ts +34 -2
- package/src/pipelines/gradeWorksheet.ts +6 -0
- package/src/pipelines/labChatPrompt.ts +11 -12
- package/src/routers/_app.ts +2 -0
- package/src/routers/labChat.ts +5 -0
- package/src/routers/marketplace.ts +16 -0
- package/src/routers/message.ts +34 -5
- package/src/routers/module.ts +118 -0
- package/src/routers/parent.ts +11 -0
- package/src/routers/studentProgress.ts +15 -0
- package/src/routes/aiStream.ts +32 -0
- package/src/seedDatabase.ts +107 -2
- package/src/services/adminUser.ts +5 -2
- package/src/services/aiChatStream.ts +83 -7
- package/src/services/aiGeneration.ts +92 -0
- package/src/services/aiProposalTools.ts +166 -1
- package/src/services/aiProposals.ts +428 -0
- package/src/services/assignment.ts +12 -1
- package/src/services/labChat.ts +65 -0
- package/src/services/marketplace.ts +39 -0
- package/src/services/message.ts +205 -142
- package/src/services/module.ts +683 -0
- package/src/services/parentPortal.ts +85 -0
- package/src/services/school.ts +15 -3
- package/src/services/studentProgress.ts +135 -0
- package/src/utils/inference.ts +85 -0
- package/src/workers/processors/createRunJobWorker.ts +17 -17
- package/tests/unit/services/adminUser.test.ts +75 -0
- package/tests/unit/utils/adminPanelAccess.test.ts +26 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI creation proposals – defer-and-confirm flow for Lab chat.
|
|
3
|
+
*
|
|
4
|
+
* The lab pipeline records every `propose_*` tool call as a pending creation
|
|
5
|
+
* on the AI message's meta (`meta.pendingCreations`). Nothing is created at
|
|
6
|
+
* generation time: the teacher approves or dismisses each proposal from the
|
|
7
|
+
* chat, and approval creates the real entity server-side.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="3505287f-3d05-5c3f-a2c7-fe3fe40b3803")}catch(e){}}();
|
|
11
|
+
import { TRPCError } from "@trpc/server";
|
|
12
|
+
import { v4 } from "uuid";
|
|
13
|
+
import { prisma } from "../lib/prisma.js";
|
|
14
|
+
import { chatChannel, pusher } from "../lib/pusher.js";
|
|
15
|
+
import { logger } from "../utils/logger.js";
|
|
16
|
+
import { findConversationMembership, findMessageByIdMinimal, messageFileSelect, } from "../models/message.js";
|
|
17
|
+
import { createAssignmentRecord, updateAssignmentRecord } from "./assignment.js";
|
|
18
|
+
import { createWorksheetRecord, addQuestionToWorksheet, updateWorksheetRecord, } from "./worksheet.js";
|
|
19
|
+
import { createSectionRecord, updateSectionRecord } from "./section.js";
|
|
20
|
+
const PROPOSAL_TOOL_KIND = {
|
|
21
|
+
propose_assignment: "assignment",
|
|
22
|
+
propose_worksheet: "worksheet",
|
|
23
|
+
propose_section: "section",
|
|
24
|
+
propose_update_assignment: "assignmentUpdate",
|
|
25
|
+
propose_update_worksheet: "worksheetUpdate",
|
|
26
|
+
propose_update_section: "sectionUpdate",
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Turn the stream's accumulated proposals into pending creations and stamp
|
|
30
|
+
* each `propose_*` tool part with its proposal id so the client can render
|
|
31
|
+
* the confirmation card inline at the tool-call position.
|
|
32
|
+
*/
|
|
33
|
+
export function buildPendingCreations(proposals, parts) {
|
|
34
|
+
const pendingCreations = [
|
|
35
|
+
...proposals.assignments.map((payload) => ({
|
|
36
|
+
id: v4(), kind: "assignment", title: payload.title, payload, status: "pending",
|
|
37
|
+
})),
|
|
38
|
+
...proposals.worksheets.map((payload) => ({
|
|
39
|
+
id: v4(), kind: "worksheet", title: payload.title, payload, status: "pending",
|
|
40
|
+
})),
|
|
41
|
+
...proposals.sections.map((payload) => ({
|
|
42
|
+
id: v4(), kind: "section", title: payload.name, payload, status: "pending",
|
|
43
|
+
})),
|
|
44
|
+
...proposals.assignmentUpdates.map((payload) => ({
|
|
45
|
+
id: v4(), kind: "assignmentUpdate", title: payload.targetTitle, payload, status: "pending",
|
|
46
|
+
})),
|
|
47
|
+
...proposals.worksheetUpdates.map((payload) => ({
|
|
48
|
+
id: v4(), kind: "worksheetUpdate", title: payload.targetTitle, payload, status: "pending",
|
|
49
|
+
})),
|
|
50
|
+
...proposals.sectionUpdates.map((payload) => ({
|
|
51
|
+
id: v4(), kind: "sectionUpdate", title: payload.targetTitle, payload, status: "pending",
|
|
52
|
+
})),
|
|
53
|
+
];
|
|
54
|
+
const remaining = new Map();
|
|
55
|
+
for (const creation of pendingCreations) {
|
|
56
|
+
const list = remaining.get(creation.kind) ?? [];
|
|
57
|
+
list.push(creation);
|
|
58
|
+
remaining.set(creation.kind, list);
|
|
59
|
+
}
|
|
60
|
+
const stamped = parts.map((part) => {
|
|
61
|
+
if (part.type !== "tool")
|
|
62
|
+
return part;
|
|
63
|
+
const kind = PROPOSAL_TOOL_KIND[part.toolName];
|
|
64
|
+
if (!kind)
|
|
65
|
+
return part;
|
|
66
|
+
const list = remaining.get(kind);
|
|
67
|
+
if (!list || list.length === 0)
|
|
68
|
+
return part;
|
|
69
|
+
// Prefer a title match; fall back to stream order.
|
|
70
|
+
const idx = part.title
|
|
71
|
+
? list.findIndex((c) => c.title.trim().toLowerCase() === part.title.trim().toLowerCase())
|
|
72
|
+
: 0;
|
|
73
|
+
const creation = list.splice(idx >= 0 ? idx : 0, 1)[0];
|
|
74
|
+
return { ...part, proposalId: creation.id };
|
|
75
|
+
});
|
|
76
|
+
return { pendingCreations, parts: stamped };
|
|
77
|
+
}
|
|
78
|
+
const RESOLVE_MAX_RETRIES = 5;
|
|
79
|
+
/**
|
|
80
|
+
* Compare-and-swap a proposal's status inside message meta. Returns the new
|
|
81
|
+
* meta on success, or null when the proposal was not in `fromStatus`.
|
|
82
|
+
*/
|
|
83
|
+
async function casProposalStatus(messageId, proposalId, fromStatus, patch) {
|
|
84
|
+
for (let attempt = 0; attempt < RESOLVE_MAX_RETRIES; attempt++) {
|
|
85
|
+
const msg = await prisma.message.findUnique({
|
|
86
|
+
where: { id: messageId },
|
|
87
|
+
select: { meta: true },
|
|
88
|
+
});
|
|
89
|
+
if (!msg) {
|
|
90
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Message not found" });
|
|
91
|
+
}
|
|
92
|
+
const currentMeta = msg.meta ?? {};
|
|
93
|
+
const creations = Array.isArray(currentMeta.pendingCreations)
|
|
94
|
+
? currentMeta.pendingCreations
|
|
95
|
+
: [];
|
|
96
|
+
const target = creations.find((c) => c.id === proposalId);
|
|
97
|
+
if (!target) {
|
|
98
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Proposal not found" });
|
|
99
|
+
}
|
|
100
|
+
if (target.status !== fromStatus)
|
|
101
|
+
return null;
|
|
102
|
+
const newMeta = {
|
|
103
|
+
...currentMeta,
|
|
104
|
+
pendingCreations: creations.map((c) => c.id === proposalId ? { ...c, ...patch } : c),
|
|
105
|
+
};
|
|
106
|
+
const newMetaJson = JSON.stringify(newMeta);
|
|
107
|
+
const currentMetaJson = msg.meta === null ? null : JSON.stringify(msg.meta);
|
|
108
|
+
const updatedCount = currentMetaJson === null
|
|
109
|
+
? await prisma.$executeRaw `
|
|
110
|
+
UPDATE "Message" SET meta = ${newMetaJson}::jsonb
|
|
111
|
+
WHERE id = ${messageId} AND meta IS NULL
|
|
112
|
+
`
|
|
113
|
+
: await prisma.$executeRaw `
|
|
114
|
+
UPDATE "Message" SET meta = ${newMetaJson}::jsonb
|
|
115
|
+
WHERE id = ${messageId} AND meta = ${currentMetaJson}::jsonb
|
|
116
|
+
`;
|
|
117
|
+
if (Number(updatedCount) > 0)
|
|
118
|
+
return newMeta;
|
|
119
|
+
}
|
|
120
|
+
throw new TRPCError({
|
|
121
|
+
code: "CONFLICT",
|
|
122
|
+
message: "Failed to update proposal after retries due to concurrent updates",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
async function broadcastMessageMeta(messageId, conversationId) {
|
|
126
|
+
const updated = await prisma.message.findUnique({
|
|
127
|
+
where: { id: messageId },
|
|
128
|
+
include: {
|
|
129
|
+
sender: {
|
|
130
|
+
select: {
|
|
131
|
+
id: true,
|
|
132
|
+
username: true,
|
|
133
|
+
profile: { select: { displayName: true, profilePicture: true } },
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
attachments: { select: messageFileSelect },
|
|
137
|
+
mentions: { select: { userId: true } },
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
if (!updated)
|
|
141
|
+
return;
|
|
142
|
+
try {
|
|
143
|
+
await pusher.trigger(chatChannel(conversationId), "message-updated", {
|
|
144
|
+
id: updated.id,
|
|
145
|
+
content: updated.content,
|
|
146
|
+
senderId: updated.senderId,
|
|
147
|
+
conversationId: updated.conversationId,
|
|
148
|
+
status: updated.status,
|
|
149
|
+
createdAt: updated.createdAt,
|
|
150
|
+
sender: updated.sender,
|
|
151
|
+
attachments: updated.attachments ?? [],
|
|
152
|
+
meta: updated.meta,
|
|
153
|
+
mentionedUserIds: updated.mentions.map((m) => m.userId),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
logger.error("Failed to broadcast proposal resolution via Pusher", {
|
|
158
|
+
error,
|
|
159
|
+
messageId,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/** Create or update the proposed entity; returns the affected entity's id. */
|
|
164
|
+
async function createProposedEntity(userId, classId, proposal) {
|
|
165
|
+
switch (proposal.kind) {
|
|
166
|
+
case "sectionUpdate": {
|
|
167
|
+
const payload = proposal.payload;
|
|
168
|
+
await updateSectionRecord(userId, {
|
|
169
|
+
id: payload.id,
|
|
170
|
+
classId,
|
|
171
|
+
name: payload.name,
|
|
172
|
+
color: payload.color ?? undefined,
|
|
173
|
+
});
|
|
174
|
+
return payload.id;
|
|
175
|
+
}
|
|
176
|
+
case "worksheetUpdate": {
|
|
177
|
+
const payload = proposal.payload;
|
|
178
|
+
if (payload.name) {
|
|
179
|
+
await updateWorksheetRecord(payload.id, userId, { name: payload.name });
|
|
180
|
+
}
|
|
181
|
+
for (const question of payload.addQuestions ?? []) {
|
|
182
|
+
await addQuestionToWorksheet(payload.id, userId, {
|
|
183
|
+
question: question.question,
|
|
184
|
+
answer: question.answer,
|
|
185
|
+
points: question.points,
|
|
186
|
+
options: question.options,
|
|
187
|
+
markScheme: question.markScheme,
|
|
188
|
+
type: question.type,
|
|
189
|
+
order: question.order,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return payload.id;
|
|
193
|
+
}
|
|
194
|
+
case "assignmentUpdate": {
|
|
195
|
+
const payload = proposal.payload;
|
|
196
|
+
await updateAssignmentRecord(userId, {
|
|
197
|
+
id: payload.id,
|
|
198
|
+
classId,
|
|
199
|
+
title: payload.title,
|
|
200
|
+
instructions: payload.instructions,
|
|
201
|
+
dueDate: payload.dueDate,
|
|
202
|
+
type: payload.type,
|
|
203
|
+
maxGrade: payload.maxGrade,
|
|
204
|
+
acceptFiles: payload.acceptFiles,
|
|
205
|
+
acceptExtendedResponse: payload.acceptExtendedResponse,
|
|
206
|
+
acceptWorksheet: payload.acceptWorksheet,
|
|
207
|
+
sectionId: payload.sectionId,
|
|
208
|
+
});
|
|
209
|
+
return payload.id;
|
|
210
|
+
}
|
|
211
|
+
case "section": {
|
|
212
|
+
const section = await createSectionRecord(userId, {
|
|
213
|
+
classId,
|
|
214
|
+
name: proposal.payload.name,
|
|
215
|
+
color: proposal.payload.color ?? undefined,
|
|
216
|
+
});
|
|
217
|
+
return section.id;
|
|
218
|
+
}
|
|
219
|
+
case "worksheet": {
|
|
220
|
+
const worksheet = await createWorksheetRecord(classId, proposal.payload.title);
|
|
221
|
+
for (const question of proposal.payload.questions) {
|
|
222
|
+
await addQuestionToWorksheet(worksheet.id, userId, {
|
|
223
|
+
question: question.question,
|
|
224
|
+
answer: question.answer,
|
|
225
|
+
points: question.points,
|
|
226
|
+
options: question.options,
|
|
227
|
+
markScheme: question.markScheme,
|
|
228
|
+
type: question.type,
|
|
229
|
+
order: question.order,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return worksheet.id;
|
|
233
|
+
}
|
|
234
|
+
case "assignment": {
|
|
235
|
+
const payload = proposal.payload;
|
|
236
|
+
const assignment = await createAssignmentRecord(userId, {
|
|
237
|
+
classId,
|
|
238
|
+
title: payload.title,
|
|
239
|
+
instructions: payload.instructions,
|
|
240
|
+
dueDate: payload.dueDate,
|
|
241
|
+
type: payload.type,
|
|
242
|
+
maxGrade: payload.maxGrade,
|
|
243
|
+
acceptFiles: payload.acceptFiles,
|
|
244
|
+
acceptExtendedResponse: payload.acceptExtendedResponse,
|
|
245
|
+
acceptWorksheet: payload.acceptWorksheet,
|
|
246
|
+
gradingBoundaryId: payload.gradingBoundaryId ?? undefined,
|
|
247
|
+
markSchemeId: payload.markSchemeId ?? undefined,
|
|
248
|
+
worksheetIds: payload.worksheetIds,
|
|
249
|
+
studentIds: payload.studentIds,
|
|
250
|
+
sectionId: payload.sectionId ?? undefined,
|
|
251
|
+
existingFileIds: payload.attachments.map((a) => a.id),
|
|
252
|
+
});
|
|
253
|
+
return assignment.id;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Approve or dismiss a pending AI creation proposal. Approval creates the
|
|
259
|
+
* entity server-side; both outcomes persist to message meta and broadcast a
|
|
260
|
+
* `message-updated` event so every client (and reloads) see the final state.
|
|
261
|
+
*/
|
|
262
|
+
export async function resolveAiProposal(userId, input) {
|
|
263
|
+
const { messageId, proposalId, action } = input;
|
|
264
|
+
const message = await findMessageByIdMinimal(messageId);
|
|
265
|
+
if (!message) {
|
|
266
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Message not found" });
|
|
267
|
+
}
|
|
268
|
+
const membership = await findConversationMembership(message.conversationId, userId);
|
|
269
|
+
if (!membership) {
|
|
270
|
+
throw new TRPCError({
|
|
271
|
+
code: "FORBIDDEN",
|
|
272
|
+
message: "Not a member of this conversation",
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
const labChat = await prisma.labChat.findFirst({
|
|
276
|
+
where: { conversationId: message.conversationId },
|
|
277
|
+
select: { classId: true },
|
|
278
|
+
});
|
|
279
|
+
if (!labChat) {
|
|
280
|
+
throw new TRPCError({
|
|
281
|
+
code: "BAD_REQUEST",
|
|
282
|
+
message: "Proposals can only be resolved in lab chats",
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
const teaches = await prisma.class.findFirst({
|
|
286
|
+
where: { id: labChat.classId, teachers: { some: { id: userId } } },
|
|
287
|
+
select: { id: true },
|
|
288
|
+
});
|
|
289
|
+
if (!teaches) {
|
|
290
|
+
throw new TRPCError({
|
|
291
|
+
code: "FORBIDDEN",
|
|
292
|
+
message: "Only class teachers can resolve proposals",
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
const currentMeta = await prisma.message.findUnique({
|
|
296
|
+
where: { id: messageId },
|
|
297
|
+
select: { meta: true },
|
|
298
|
+
});
|
|
299
|
+
const creations = Array.isArray(currentMeta?.meta?.pendingCreations)
|
|
300
|
+
? currentMeta.meta
|
|
301
|
+
.pendingCreations
|
|
302
|
+
: [];
|
|
303
|
+
const proposal = creations.find((c) => c.id === proposalId);
|
|
304
|
+
if (!proposal) {
|
|
305
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Proposal not found" });
|
|
306
|
+
}
|
|
307
|
+
if (proposal.status !== "pending") {
|
|
308
|
+
return {
|
|
309
|
+
status: proposal.status,
|
|
310
|
+
createdId: proposal.createdId ?? null,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
if (action === "dismiss") {
|
|
314
|
+
const meta = await casProposalStatus(messageId, proposalId, "pending", {
|
|
315
|
+
status: "dismissed",
|
|
316
|
+
});
|
|
317
|
+
if (meta) {
|
|
318
|
+
await broadcastMessageMeta(messageId, message.conversationId);
|
|
319
|
+
}
|
|
320
|
+
return { status: "dismissed", createdId: null };
|
|
321
|
+
}
|
|
322
|
+
// Approve: claim the proposal first so concurrent approvals create at most
|
|
323
|
+
// one entity, then create and record the created id (roll back on failure).
|
|
324
|
+
const claimed = await casProposalStatus(messageId, proposalId, "pending", {
|
|
325
|
+
status: "approved",
|
|
326
|
+
});
|
|
327
|
+
if (!claimed) {
|
|
328
|
+
const msg = await prisma.message.findUnique({
|
|
329
|
+
where: { id: messageId },
|
|
330
|
+
select: { meta: true },
|
|
331
|
+
});
|
|
332
|
+
const latest = (msg?.meta?.pendingCreations ??
|
|
333
|
+
[]).find((c) => c.id === proposalId);
|
|
334
|
+
return {
|
|
335
|
+
status: latest?.status ?? "pending",
|
|
336
|
+
createdId: latest?.createdId ?? null,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
try {
|
|
340
|
+
const createdId = await createProposedEntity(userId, labChat.classId, proposal);
|
|
341
|
+
await casProposalStatus(messageId, proposalId, "approved", { createdId });
|
|
342
|
+
await broadcastMessageMeta(messageId, message.conversationId);
|
|
343
|
+
return { status: "approved", createdId };
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
logger.error("Failed to create approved AI proposal", {
|
|
347
|
+
error,
|
|
348
|
+
messageId,
|
|
349
|
+
proposalId,
|
|
350
|
+
kind: proposal.kind,
|
|
351
|
+
});
|
|
352
|
+
await casProposalStatus(messageId, proposalId, "approved", {
|
|
353
|
+
status: "pending",
|
|
354
|
+
}).catch(() => null);
|
|
355
|
+
await broadcastMessageMeta(messageId, message.conversationId);
|
|
356
|
+
throw new TRPCError({
|
|
357
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
358
|
+
message: "Failed to create the approved item",
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
//# sourceMappingURL=aiProposals.js.map
|
|
363
|
+
//# debugId=3505287f-3d05-5c3f-a2c7-fe3fe40b3803
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aiProposals.js","sources":["services/aiProposals.ts"],"sourceRoot":"/","sourcesContent":["/**\n * AI creation proposals – defer-and-confirm flow for Lab chat.\n *\n * The lab pipeline records every `propose_*` tool call as a pending creation\n * on the AI message's meta (`meta.pendingCreations`). Nothing is created at\n * generation time: the teacher approves or dismisses each proposal from the\n * chat, and approval creates the real entity server-side.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport { v4 } from \"uuid\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { chatChannel, pusher } from \"../lib/pusher.js\";\nimport { logger } from \"../utils/logger.js\";\nimport {\n findConversationMembership,\n findMessageByIdMinimal,\n messageFileSelect,\n} from \"../models/message.js\";\nimport { createAssignmentRecord, updateAssignmentRecord } from \"./assignment.js\";\nimport {\n createWorksheetRecord,\n addQuestionToWorksheet,\n updateWorksheetRecord,\n} from \"./worksheet.js\";\nimport { createSectionRecord, updateSectionRecord } from \"./section.js\";\nimport type { StreamPart } from \"./aiChatStream.js\";\nimport type { LabProposals } from \"./aiProposalTools.js\";\nimport type {\n AssignmentToCreate,\n WorksheetToCreate,\n SectionToCreate,\n AssignmentUpdate,\n WorksheetUpdate,\n SectionUpdate,\n} from \"../pipelines/aiLabChatContract.js\";\n\nexport type PendingCreationStatus = \"pending\" | \"approved\" | \"dismissed\";\n\nexport type PendingCreation =\n | { id: string; kind: \"assignment\"; title: string; payload: AssignmentToCreate; status: PendingCreationStatus; createdId?: string }\n | { id: string; kind: \"worksheet\"; title: string; payload: WorksheetToCreate; status: PendingCreationStatus; createdId?: string }\n | { id: string; kind: \"section\"; title: string; payload: SectionToCreate; status: PendingCreationStatus; createdId?: string }\n | { id: string; kind: \"assignmentUpdate\"; title: string; payload: AssignmentUpdate; status: PendingCreationStatus; createdId?: string }\n | { id: string; kind: \"worksheetUpdate\"; title: string; payload: WorksheetUpdate; status: PendingCreationStatus; createdId?: string }\n | { id: string; kind: \"sectionUpdate\"; title: string; payload: SectionUpdate; status: PendingCreationStatus; createdId?: string };\n\nconst PROPOSAL_TOOL_KIND: Record<string, PendingCreation[\"kind\"]> = {\n propose_assignment: \"assignment\",\n propose_worksheet: \"worksheet\",\n propose_section: \"section\",\n propose_update_assignment: \"assignmentUpdate\",\n propose_update_worksheet: \"worksheetUpdate\",\n propose_update_section: \"sectionUpdate\",\n};\n\n/**\n * Turn the stream's accumulated proposals into pending creations and stamp\n * each `propose_*` tool part with its proposal id so the client can render\n * the confirmation card inline at the tool-call position.\n */\nexport function buildPendingCreations(\n proposals: LabProposals,\n parts: StreamPart[],\n): { pendingCreations: PendingCreation[]; parts: StreamPart[] } {\n const pendingCreations: PendingCreation[] = [\n ...proposals.assignments.map((payload): PendingCreation => ({\n id: v4(), kind: \"assignment\", title: payload.title, payload, status: \"pending\",\n })),\n ...proposals.worksheets.map((payload): PendingCreation => ({\n id: v4(), kind: \"worksheet\", title: payload.title, payload, status: \"pending\",\n })),\n ...proposals.sections.map((payload): PendingCreation => ({\n id: v4(), kind: \"section\", title: payload.name, payload, status: \"pending\",\n })),\n ...proposals.assignmentUpdates.map((payload): PendingCreation => ({\n id: v4(), kind: \"assignmentUpdate\", title: payload.targetTitle, payload, status: \"pending\",\n })),\n ...proposals.worksheetUpdates.map((payload): PendingCreation => ({\n id: v4(), kind: \"worksheetUpdate\", title: payload.targetTitle, payload, status: \"pending\",\n })),\n ...proposals.sectionUpdates.map((payload): PendingCreation => ({\n id: v4(), kind: \"sectionUpdate\", title: payload.targetTitle, payload, status: \"pending\",\n })),\n ];\n\n const remaining = new Map<PendingCreation[\"kind\"], PendingCreation[]>();\n for (const creation of pendingCreations) {\n const list = remaining.get(creation.kind) ?? [];\n list.push(creation);\n remaining.set(creation.kind, list);\n }\n\n const stamped = parts.map((part) => {\n if (part.type !== \"tool\") return part;\n const kind = PROPOSAL_TOOL_KIND[part.toolName];\n if (!kind) return part;\n const list = remaining.get(kind);\n if (!list || list.length === 0) return part;\n // Prefer a title match; fall back to stream order.\n const idx = part.title\n ? list.findIndex((c) => c.title.trim().toLowerCase() === part.title!.trim().toLowerCase())\n : 0;\n const creation = list.splice(idx >= 0 ? idx : 0, 1)[0];\n return { ...part, proposalId: creation.id };\n });\n\n return { pendingCreations, parts: stamped };\n}\n\nconst RESOLVE_MAX_RETRIES = 5;\n\n/**\n * Compare-and-swap a proposal's status inside message meta. Returns the new\n * meta on success, or null when the proposal was not in `fromStatus`.\n */\nasync function casProposalStatus(\n messageId: string,\n proposalId: string,\n fromStatus: PendingCreationStatus,\n patch: Partial<Pick<PendingCreation, \"status\" | \"createdId\">>,\n): Promise<Record<string, unknown> | null> {\n for (let attempt = 0; attempt < RESOLVE_MAX_RETRIES; attempt++) {\n const msg = await prisma.message.findUnique({\n where: { id: messageId },\n select: { meta: true },\n });\n if (!msg) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Message not found\" });\n }\n\n const currentMeta = (msg.meta as Record<string, unknown>) ?? {};\n const creations = Array.isArray(currentMeta.pendingCreations)\n ? (currentMeta.pendingCreations as PendingCreation[])\n : [];\n const target = creations.find((c) => c.id === proposalId);\n if (!target) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Proposal not found\" });\n }\n if (target.status !== fromStatus) return null;\n\n const newMeta = {\n ...currentMeta,\n pendingCreations: creations.map((c) =>\n c.id === proposalId ? { ...c, ...patch } : c,\n ),\n };\n\n const newMetaJson = JSON.stringify(newMeta);\n const currentMetaJson = msg.meta === null ? null : JSON.stringify(msg.meta);\n const updatedCount =\n currentMetaJson === null\n ? await prisma.$executeRaw`\n UPDATE \"Message\" SET meta = ${newMetaJson}::jsonb\n WHERE id = ${messageId} AND meta IS NULL\n `\n : await prisma.$executeRaw`\n UPDATE \"Message\" SET meta = ${newMetaJson}::jsonb\n WHERE id = ${messageId} AND meta = ${currentMetaJson}::jsonb\n `;\n\n if (Number(updatedCount) > 0) return newMeta;\n }\n\n throw new TRPCError({\n code: \"CONFLICT\",\n message: \"Failed to update proposal after retries due to concurrent updates\",\n });\n}\n\nasync function broadcastMessageMeta(messageId: string, conversationId: string) {\n const updated = await prisma.message.findUnique({\n where: { id: messageId },\n include: {\n sender: {\n select: {\n id: true,\n username: true,\n profile: { select: { displayName: true, profilePicture: true } },\n },\n },\n attachments: { select: messageFileSelect },\n mentions: { select: { userId: true } },\n },\n });\n if (!updated) return;\n\n try {\n await pusher.trigger(chatChannel(conversationId), \"message-updated\", {\n id: updated.id,\n content: updated.content,\n senderId: updated.senderId,\n conversationId: updated.conversationId,\n status: updated.status,\n createdAt: updated.createdAt,\n sender: updated.sender,\n attachments: updated.attachments ?? [],\n meta: updated.meta,\n mentionedUserIds: updated.mentions.map((m) => m.userId),\n });\n } catch (error) {\n logger.error(\"Failed to broadcast proposal resolution via Pusher\", {\n error,\n messageId,\n });\n }\n}\n\n/** Create or update the proposed entity; returns the affected entity's id. */\nasync function createProposedEntity(\n userId: string,\n classId: string,\n proposal: PendingCreation,\n): Promise<string> {\n switch (proposal.kind) {\n case \"sectionUpdate\": {\n const payload = proposal.payload;\n await updateSectionRecord(userId, {\n id: payload.id,\n classId,\n name: payload.name,\n color: payload.color ?? undefined,\n });\n return payload.id;\n }\n case \"worksheetUpdate\": {\n const payload = proposal.payload;\n if (payload.name) {\n await updateWorksheetRecord(payload.id, userId, { name: payload.name });\n }\n for (const question of payload.addQuestions ?? []) {\n await addQuestionToWorksheet(payload.id, userId, {\n question: question.question,\n answer: question.answer,\n points: question.points,\n options: question.options,\n markScheme: question.markScheme,\n type: question.type,\n order: question.order,\n });\n }\n return payload.id;\n }\n case \"assignmentUpdate\": {\n const payload = proposal.payload;\n await updateAssignmentRecord(userId, {\n id: payload.id,\n classId,\n title: payload.title,\n instructions: payload.instructions,\n dueDate: payload.dueDate,\n type: payload.type,\n maxGrade: payload.maxGrade,\n acceptFiles: payload.acceptFiles,\n acceptExtendedResponse: payload.acceptExtendedResponse,\n acceptWorksheet: payload.acceptWorksheet,\n sectionId: payload.sectionId,\n });\n return payload.id;\n }\n case \"section\": {\n const section = await createSectionRecord(userId, {\n classId,\n name: proposal.payload.name,\n color: proposal.payload.color ?? undefined,\n });\n return section.id;\n }\n case \"worksheet\": {\n const worksheet = await createWorksheetRecord(classId, proposal.payload.title);\n for (const question of proposal.payload.questions) {\n await addQuestionToWorksheet(worksheet.id, userId, {\n question: question.question,\n answer: question.answer,\n points: question.points,\n options: question.options,\n markScheme: question.markScheme,\n type: question.type,\n order: question.order,\n });\n }\n return worksheet.id;\n }\n case \"assignment\": {\n const payload = proposal.payload;\n const assignment = await createAssignmentRecord(userId, {\n classId,\n title: payload.title,\n instructions: payload.instructions,\n dueDate: payload.dueDate,\n type: payload.type,\n maxGrade: payload.maxGrade,\n acceptFiles: payload.acceptFiles,\n acceptExtendedResponse: payload.acceptExtendedResponse,\n acceptWorksheet: payload.acceptWorksheet,\n gradingBoundaryId: payload.gradingBoundaryId ?? undefined,\n markSchemeId: payload.markSchemeId ?? undefined,\n worksheetIds: payload.worksheetIds,\n studentIds: payload.studentIds,\n sectionId: payload.sectionId ?? undefined,\n existingFileIds: payload.attachments.map((a) => a.id),\n });\n return assignment.id;\n }\n }\n}\n\n/**\n * Approve or dismiss a pending AI creation proposal. Approval creates the\n * entity server-side; both outcomes persist to message meta and broadcast a\n * `message-updated` event so every client (and reloads) see the final state.\n */\nexport async function resolveAiProposal(\n userId: string,\n input: { messageId: string; proposalId: string; action: \"approve\" | \"dismiss\" },\n) {\n const { messageId, proposalId, action } = input;\n\n const message = await findMessageByIdMinimal(messageId);\n if (!message) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Message not found\" });\n }\n\n const membership = await findConversationMembership(\n message.conversationId,\n userId,\n );\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n const labChat = await prisma.labChat.findFirst({\n where: { conversationId: message.conversationId },\n select: { classId: true },\n });\n if (!labChat) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Proposals can only be resolved in lab chats\",\n });\n }\n\n const teaches = await prisma.class.findFirst({\n where: { id: labChat.classId, teachers: { some: { id: userId } } },\n select: { id: true },\n });\n if (!teaches) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can resolve proposals\",\n });\n }\n\n const currentMeta = await prisma.message.findUnique({\n where: { id: messageId },\n select: { meta: true },\n });\n const creations = Array.isArray(\n (currentMeta?.meta as Record<string, unknown> | null)?.pendingCreations,\n )\n ? ((currentMeta!.meta as Record<string, unknown>)\n .pendingCreations as PendingCreation[])\n : [];\n const proposal = creations.find((c) => c.id === proposalId);\n if (!proposal) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Proposal not found\" });\n }\n if (proposal.status !== \"pending\") {\n return {\n status: proposal.status,\n createdId: proposal.createdId ?? null,\n };\n }\n\n if (action === \"dismiss\") {\n const meta = await casProposalStatus(messageId, proposalId, \"pending\", {\n status: \"dismissed\",\n });\n if (meta) {\n await broadcastMessageMeta(messageId, message.conversationId);\n }\n return { status: \"dismissed\" as const, createdId: null };\n }\n\n // Approve: claim the proposal first so concurrent approvals create at most\n // one entity, then create and record the created id (roll back on failure).\n const claimed = await casProposalStatus(messageId, proposalId, \"pending\", {\n status: \"approved\",\n });\n if (!claimed) {\n const msg = await prisma.message.findUnique({\n where: { id: messageId },\n select: { meta: true },\n });\n const latest = (\n ((msg?.meta as Record<string, unknown> | null)?.pendingCreations ??\n []) as PendingCreation[]\n ).find((c) => c.id === proposalId);\n return {\n status: latest?.status ?? \"pending\",\n createdId: latest?.createdId ?? null,\n };\n }\n\n try {\n const createdId = await createProposedEntity(userId, labChat.classId, proposal);\n await casProposalStatus(messageId, proposalId, \"approved\", { createdId });\n await broadcastMessageMeta(messageId, message.conversationId);\n return { status: \"approved\" as const, createdId };\n } catch (error) {\n logger.error(\"Failed to create approved AI proposal\", {\n error,\n messageId,\n proposalId,\n kind: proposal.kind,\n });\n await casProposalStatus(messageId, proposalId, \"approved\", {\n status: \"pending\",\n }).catch(() => null);\n await broadcastMessageMeta(messageId, message.conversationId);\n throw new TRPCError({\n code: \"INTERNAL_SERVER_ERROR\",\n message: \"Failed to create the approved item\",\n });\n }\n}\n"],"names":[],"mappings":"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","debug_id":"3505287f-3d05-5c3f-a2c7-fe3fe40b3803"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assignment.d.ts","sourceRoot":"/","sources":["services/assignment.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"assignment.d.ts","sourceRoot":"/","sources":["services/assignment.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Assignment service – create, read, update, delete assignments and submissions.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
5
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="206c04a8-baef-526c-ae19-93de3e7e4cab")}catch(e){}}();
|
|
6
6
|
import { TRPCError } from "@trpc/server";
|
|
7
7
|
import { prisma } from "../lib/prisma.js";
|
|
8
8
|
import { findAssignmentById, findAssignmentWithDetails, findAssignmentWithClassForTeacher, findAssignmentForDelete, findAssignmentsDueToday, findSubmissionByAssignmentAndStudent, findSubmissionByIdForClassMember, findSubmissionForUpdate, findSubmissionForTeacherUpdate, findSubmissionsByAssignmentForTeacher, createSubmission, findSectionsByClassId, findAvailableEventsForAssignment, attachAssignmentToEvent, detachAssignmentFromEvent, duplicateAssignment, } from "../models/assignment.js";
|
|
@@ -13,6 +13,7 @@ import { enqueueRunJob } from "../workers/queues.js";
|
|
|
13
13
|
import { logger } from "../utils/logger.js";
|
|
14
14
|
import { invalidateStudentProgressInferenceCache } from "./studentProgressInferenceCache.js";
|
|
15
15
|
import { enqueueEmbedding } from "./embedding.js";
|
|
16
|
+
import { syncAssignmentModuleProgress } from "./module.js";
|
|
16
17
|
import { resolveAssignmentBehavior } from "../lib/assignmentTypeDefaults.js";
|
|
17
18
|
export async function assignmentExists(id) {
|
|
18
19
|
const assignment = await findAssignmentById(id);
|
|
@@ -584,7 +585,7 @@ export async function updateSubmissionRecord(userId, input) {
|
|
|
584
585
|
}
|
|
585
586
|
}
|
|
586
587
|
}
|
|
587
|
-
|
|
588
|
+
const updatedSubmission = await prisma.submission.update({
|
|
588
589
|
where: { id: submission.id },
|
|
589
590
|
data: {
|
|
590
591
|
submitted: !submission.submitted,
|
|
@@ -602,6 +603,8 @@ export async function updateSubmissionRecord(userId, input) {
|
|
|
602
603
|
},
|
|
603
604
|
},
|
|
604
605
|
});
|
|
606
|
+
await syncAssignmentModuleProgress(userId, submission.assignmentId);
|
|
607
|
+
return updatedSubmission;
|
|
605
608
|
}
|
|
606
609
|
const isLateUpdate = submission.assignment.dueDate < new Date();
|
|
607
610
|
if (isLateUpdate &&
|
|
@@ -735,6 +738,7 @@ export async function updateSubmissionAsTeacherRecord(teacherId, input) {
|
|
|
735
738
|
data: { returned: !submission.returned },
|
|
736
739
|
include: submissionTeacherInclude,
|
|
737
740
|
});
|
|
741
|
+
await syncAssignmentModuleProgress(submission.studentId, submission.assignmentId);
|
|
738
742
|
if (updated.returned) {
|
|
739
743
|
sendNotification({
|
|
740
744
|
receiverId: submission.studentId,
|
|
@@ -793,6 +797,7 @@ export async function updateSubmissionAsTeacherRecord(teacherId, input) {
|
|
|
793
797
|
});
|
|
794
798
|
if (gradeReceived !== undefined) {
|
|
795
799
|
invalidateStudentProgressInferenceCache(submission.assignment.classId, submission.studentId);
|
|
800
|
+
await syncAssignmentModuleProgress(submission.studentId, submission.assignmentId);
|
|
796
801
|
}
|
|
797
802
|
// Index submission for RAG search after teacher grades/feedback
|
|
798
803
|
enqueueEmbedding(input.classId, 'SUBMISSION', submissionId);
|
|
@@ -1090,4 +1095,4 @@ export async function moveAssignmentRecord(teacherId, input) {
|
|
|
1090
1095
|
}, { maxWait: 5000, timeout: 10000 });
|
|
1091
1096
|
}
|
|
1092
1097
|
//# sourceMappingURL=assignment.js.map
|
|
1093
|
-
//# debugId=
|
|
1098
|
+
//# debugId=206c04a8-baef-526c-ae19-93de3e7e4cab
|