@studious-lms/server 1.3.0 → 1.4.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/dist/models/class.d.ts +24 -2
- package/dist/models/class.d.ts.map +1 -1
- package/dist/models/class.js +180 -81
- package/dist/models/class.js.map +1 -1
- package/dist/models/worksheet.d.ts +34 -34
- package/dist/pipelines/aiLabChat.d.ts +57 -2
- package/dist/pipelines/aiLabChat.d.ts.map +1 -1
- package/dist/pipelines/aiLabChat.js +252 -113
- package/dist/pipelines/aiLabChat.js.map +1 -1
- package/dist/pipelines/gradeWorksheet.d.ts +4 -4
- package/dist/routers/_app.d.ts +138 -56
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/class.d.ts +24 -3
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +3 -3
- package/dist/routers/class.js.map +1 -1
- package/dist/routers/labChat.d.ts +10 -1
- 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/message.d.ts +11 -0
- package/dist/routers/message.d.ts.map +1 -1
- package/dist/routers/message.js +10 -3
- package/dist/routers/message.js.map +1 -1
- package/dist/routers/worksheet.d.ts +24 -24
- package/dist/services/class.d.ts +24 -2
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/class.js +18 -6
- package/dist/services/class.js.map +1 -1
- package/dist/services/labChat.d.ts +5 -1
- package/dist/services/labChat.d.ts.map +1 -1
- package/dist/services/labChat.js +96 -4
- package/dist/services/labChat.js.map +1 -1
- package/dist/services/message.d.ts +8 -0
- package/dist/services/message.d.ts.map +1 -1
- package/dist/services/message.js +74 -2
- package/dist/services/message.js.map +1 -1
- package/dist/services/worksheet.d.ts +18 -18
- package/package.json +1 -1
- package/prisma/schema.prisma +1 -1
- package/src/models/class.ts +189 -84
- package/src/pipelines/aiLabChat.ts +291 -118
- package/src/routers/class.ts +1 -1
- package/src/routers/labChat.ts +7 -0
- package/src/routers/message.ts +13 -0
- package/src/services/class.ts +14 -7
- package/src/services/labChat.ts +108 -2
- package/src/services/message.ts +93 -0
package/src/services/labChat.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* trigger AI responses, and broadcast via Pusher.
|
|
4
4
|
*/
|
|
5
5
|
import { TRPCError } from "@trpc/server";
|
|
6
|
+
import { GenerationStatus } from "@prisma/client";
|
|
6
7
|
import { prisma } from "../lib/prisma.js";
|
|
7
8
|
import { chatChannel, teacherChannel, pusher } from "../lib/pusher.js";
|
|
8
9
|
import {
|
|
@@ -166,7 +167,20 @@ export async function getLabChat(userId: string, labChatId: string) {
|
|
|
166
167
|
});
|
|
167
168
|
}
|
|
168
169
|
|
|
169
|
-
|
|
170
|
+
const pendingMessage = await prisma.message.findFirst({
|
|
171
|
+
where: {
|
|
172
|
+
conversationId: labChat.conversationId,
|
|
173
|
+
status: GenerationStatus.PENDING,
|
|
174
|
+
senderId: { not: "AI_ASSISTANT" },
|
|
175
|
+
},
|
|
176
|
+
orderBy: { createdAt: "desc" },
|
|
177
|
+
select: { id: true },
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
...labChat,
|
|
182
|
+
pendingGenerationMessageId: pendingMessage?.id ?? null,
|
|
183
|
+
};
|
|
170
184
|
}
|
|
171
185
|
|
|
172
186
|
export async function listLabChats(userId: string, classId: string) {
|
|
@@ -282,10 +296,24 @@ export async function postToLabChat(
|
|
|
282
296
|
}
|
|
283
297
|
|
|
284
298
|
if (!isAIUser(userId)) {
|
|
299
|
+
await prisma.message.update({
|
|
300
|
+
where: { id: result.id },
|
|
301
|
+
data: { status: GenerationStatus.PENDING },
|
|
302
|
+
});
|
|
303
|
+
try {
|
|
304
|
+
await pusher.trigger(teacherChannel(labChat.classId), "lab-response-pending", {
|
|
305
|
+
labChatId,
|
|
306
|
+
messageId: result.id,
|
|
307
|
+
conversationId: labChat.conversationId,
|
|
308
|
+
});
|
|
309
|
+
} catch (error) {
|
|
310
|
+
console.error("Failed to broadcast lab response pending:", error);
|
|
311
|
+
}
|
|
285
312
|
generateAndSendLabResponse(
|
|
286
313
|
labChatId,
|
|
287
314
|
content,
|
|
288
|
-
labChat.conversationId
|
|
315
|
+
labChat.conversationId,
|
|
316
|
+
{ classId: labChat.classId, messageId: result.id }
|
|
289
317
|
);
|
|
290
318
|
}
|
|
291
319
|
|
|
@@ -350,3 +378,81 @@ export async function deleteLabChat(userId: string, labChatId: string) {
|
|
|
350
378
|
|
|
351
379
|
return { success: true };
|
|
352
380
|
}
|
|
381
|
+
|
|
382
|
+
/** Rerun the last AI response for a lab chat. Deletes the last AI message and regenerates. */
|
|
383
|
+
export async function rerunLastResponse(userId: string, labChatId: string) {
|
|
384
|
+
const labChat = await findLabChatForPost(labChatId, userId);
|
|
385
|
+
if (!labChat) {
|
|
386
|
+
throw new TRPCError({
|
|
387
|
+
code: "FORBIDDEN",
|
|
388
|
+
message: "Lab chat not found or access denied",
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const messages = await prisma.message.findMany({
|
|
393
|
+
where: { conversationId: labChat.conversationId },
|
|
394
|
+
orderBy: { createdAt: "desc" },
|
|
395
|
+
take: 10,
|
|
396
|
+
select: { id: true, content: true, senderId: true, createdAt: true },
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
const lastMessage = messages[0];
|
|
400
|
+
if (!lastMessage) {
|
|
401
|
+
throw new TRPCError({
|
|
402
|
+
code: "BAD_REQUEST",
|
|
403
|
+
message: "No messages to rerun",
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (!isAIUser(lastMessage.senderId)) {
|
|
408
|
+
throw new TRPCError({
|
|
409
|
+
code: "BAD_REQUEST",
|
|
410
|
+
message: "Last message is not from AI – nothing to rerun",
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const teacherMessage = messages.find((m) => !isAIUser(m.senderId));
|
|
415
|
+
if (!teacherMessage) {
|
|
416
|
+
throw new TRPCError({
|
|
417
|
+
code: "BAD_REQUEST",
|
|
418
|
+
message: "No teacher message found to rerun from",
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
await prisma.message.delete({
|
|
423
|
+
where: { id: lastMessage.id },
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
try {
|
|
427
|
+
await pusher.trigger(chatChannel(labChat.conversationId), "message-deleted", {
|
|
428
|
+
messageId: lastMessage.id,
|
|
429
|
+
conversationId: labChat.conversationId,
|
|
430
|
+
senderId: lastMessage.senderId,
|
|
431
|
+
});
|
|
432
|
+
} catch (error) {
|
|
433
|
+
console.error("Failed to broadcast message deletion:", error);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
await prisma.message.update({
|
|
437
|
+
where: { id: teacherMessage.id },
|
|
438
|
+
data: { status: GenerationStatus.PENDING },
|
|
439
|
+
});
|
|
440
|
+
try {
|
|
441
|
+
await pusher.trigger(teacherChannel(labChat.classId), "lab-response-pending", {
|
|
442
|
+
labChatId,
|
|
443
|
+
messageId: teacherMessage.id,
|
|
444
|
+
conversationId: labChat.conversationId,
|
|
445
|
+
});
|
|
446
|
+
} catch (error) {
|
|
447
|
+
console.error("Failed to broadcast lab response pending:", error);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
generateAndSendLabResponse(
|
|
451
|
+
labChatId,
|
|
452
|
+
teacherMessage.content,
|
|
453
|
+
labChat.conversationId,
|
|
454
|
+
{ classId: labChat.classId, messageId: teacherMessage.id }
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
return { success: true };
|
|
458
|
+
}
|
package/src/services/message.ts
CHANGED
|
@@ -357,6 +357,99 @@ export async function deleteMessage(userId: string, messageId: string) {
|
|
|
357
357
|
return { success: true, messageId };
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
+
const CREATED_INDICES_KEY: Record<"assignment" | "worksheet" | "section", string> = {
|
|
361
|
+
assignment: "assignments",
|
|
362
|
+
worksheet: "worksheets",
|
|
363
|
+
section: "sections",
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
/** Mark an AI-suggested item as created. Stores in message meta for fast reads. */
|
|
367
|
+
export async function markSuggestionCreated(
|
|
368
|
+
userId: string,
|
|
369
|
+
input: {
|
|
370
|
+
messageId: string;
|
|
371
|
+
type: "assignment" | "worksheet" | "section";
|
|
372
|
+
index: number;
|
|
373
|
+
}
|
|
374
|
+
) {
|
|
375
|
+
const { messageId, type, index } = input;
|
|
376
|
+
|
|
377
|
+
const existingMessage = await findMessageByIdMinimal(messageId);
|
|
378
|
+
if (!existingMessage) {
|
|
379
|
+
throw new TRPCError({
|
|
380
|
+
code: "NOT_FOUND",
|
|
381
|
+
message: "Message not found",
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const membership = await findConversationMembership(
|
|
386
|
+
existingMessage.conversationId,
|
|
387
|
+
userId
|
|
388
|
+
);
|
|
389
|
+
if (!membership) {
|
|
390
|
+
throw new TRPCError({
|
|
391
|
+
code: "FORBIDDEN",
|
|
392
|
+
message: "Not a member of this conversation",
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const currentMeta = (existingMessage.meta as Record<string, unknown>) ?? {};
|
|
397
|
+
const createdIndices = (currentMeta.createdIndices as Record<string, number[]>) ?? {};
|
|
398
|
+
const typeIndices = createdIndices[CREATED_INDICES_KEY[type]] ?? [];
|
|
399
|
+
if (typeIndices.includes(index)) return { success: true };
|
|
400
|
+
|
|
401
|
+
const newTypeIndices = [...typeIndices, index].sort((a, b) => a - b);
|
|
402
|
+
const newMeta = {
|
|
403
|
+
...currentMeta,
|
|
404
|
+
createdIndices: {
|
|
405
|
+
...createdIndices,
|
|
406
|
+
[CREATED_INDICES_KEY[type]]: newTypeIndices,
|
|
407
|
+
},
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const updated = await prisma.message.update({
|
|
411
|
+
where: { id: messageId },
|
|
412
|
+
data: { meta: newMeta as object },
|
|
413
|
+
include: {
|
|
414
|
+
sender: {
|
|
415
|
+
select: {
|
|
416
|
+
id: true,
|
|
417
|
+
username: true,
|
|
418
|
+
profile: {
|
|
419
|
+
select: { displayName: true, profilePicture: true },
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
attachments: { select: { id: true, name: true, type: true } },
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
await pusher.trigger(
|
|
429
|
+
chatChannel(existingMessage.conversationId),
|
|
430
|
+
"message-updated",
|
|
431
|
+
{
|
|
432
|
+
id: updated.id,
|
|
433
|
+
content: updated.content,
|
|
434
|
+
senderId: updated.senderId,
|
|
435
|
+
conversationId: updated.conversationId,
|
|
436
|
+
createdAt: updated.createdAt,
|
|
437
|
+
sender: updated.sender,
|
|
438
|
+
attachments: updated.attachments ?? [],
|
|
439
|
+
meta: newMeta,
|
|
440
|
+
mentionedUserIds: [] as string[],
|
|
441
|
+
}
|
|
442
|
+
);
|
|
443
|
+
} catch (error) {
|
|
444
|
+
logger.error("Failed to broadcast suggestion status via Pusher", {
|
|
445
|
+
error,
|
|
446
|
+
messageId,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return { success: true };
|
|
451
|
+
}
|
|
452
|
+
|
|
360
453
|
export async function markAsRead(userId: string, conversationId: string) {
|
|
361
454
|
const membership = await findConversationMembership(conversationId, userId);
|
|
362
455
|
if (!membership) {
|