@studious-lms/server 1.19.0 → 1.21.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/Dockerfile +5 -2
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/pusher.d.ts +3 -1
- package/dist/lib/pusher.d.ts.map +1 -1
- package/dist/lib/pusher.js +5 -3
- package/dist/lib/pusher.js.map +1 -1
- package/dist/middleware/security.d.ts.map +1 -1
- package/dist/middleware/security.js +4 -4
- package/dist/middleware/security.js.map +1 -1
- package/dist/models/class.d.ts +1 -1
- package/dist/models/class.d.ts.map +1 -1
- package/dist/models/class.js +5 -4
- package/dist/models/class.js.map +1 -1
- package/dist/models/conversation.d.ts +17 -17
- package/dist/models/notification.d.ts +63 -11
- package/dist/models/notification.d.ts.map +1 -1
- package/dist/models/notification.js +24 -10
- package/dist/models/notification.js.map +1 -1
- package/dist/pipelines/aiLabChat.d.ts +1 -0
- package/dist/pipelines/aiLabChat.d.ts.map +1 -1
- package/dist/pipelines/aiLabChat.js +7 -2
- package/dist/pipelines/aiLabChat.js.map +1 -1
- package/dist/pipelines/aiNewtonChat.d.ts +2 -2
- package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
- package/dist/pipelines/aiNewtonChat.js +12 -4
- package/dist/pipelines/aiNewtonChat.js.map +1 -1
- package/dist/pipelines/gradeWorksheet.d.ts +3 -3
- package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
- package/dist/pipelines/gradeWorksheet.js +13 -9
- package/dist/pipelines/gradeWorksheet.js.map +1 -1
- package/dist/pipelines/pdfToWorksheet.d.ts +2 -0
- package/dist/pipelines/pdfToWorksheet.d.ts.map +1 -1
- package/dist/pipelines/pdfToWorksheet.js +3 -2
- package/dist/pipelines/pdfToWorksheet.js.map +1 -1
- package/dist/routers/_app.d.ts +322 -82
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/announcement.d.ts.map +1 -1
- package/dist/routers/announcement.js +2 -2
- package/dist/routers/announcement.js.map +1 -1
- package/dist/routers/assignment.d.ts +74 -0
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +190 -2
- package/dist/routers/assignment.js.map +1 -1
- package/dist/routers/notifications.d.ts +51 -41
- package/dist/routers/notifications.d.ts.map +1 -1
- package/dist/routers/notifications.js +22 -18
- package/dist/routers/notifications.js.map +1 -1
- package/dist/routers/school.d.ts +30 -0
- package/dist/routers/school.d.ts.map +1 -1
- package/dist/routers/school.js +11 -2
- package/dist/routers/school.js.map +1 -1
- package/dist/routers/user.d.ts +12 -0
- package/dist/routers/user.d.ts.map +1 -1
- package/dist/routers/user.js +11 -2
- package/dist/routers/user.js.map +1 -1
- package/dist/seedDatabase.d.ts +4 -0
- package/dist/seedDatabase.d.ts.map +1 -1
- package/dist/services/aiChatStream.d.ts +5 -0
- package/dist/services/aiChatStream.d.ts.map +1 -1
- package/dist/services/aiChatStream.js +32 -3
- package/dist/services/aiChatStream.js.map +1 -1
- package/dist/services/aiStream.d.ts +5 -0
- package/dist/services/aiStream.d.ts.map +1 -1
- package/dist/services/aiStream.js +29 -3
- package/dist/services/aiStream.js.map +1 -1
- package/dist/services/aiUsage.d.ts +72 -0
- package/dist/services/aiUsage.d.ts.map +1 -0
- package/dist/services/aiUsage.js +150 -0
- package/dist/services/aiUsage.js.map +1 -0
- package/dist/services/announcement.d.ts.map +1 -1
- package/dist/services/announcement.js +7 -2
- package/dist/services/announcement.js.map +1 -1
- package/dist/services/assignment.d.ts.map +1 -1
- package/dist/services/assignment.js +29 -4
- package/dist/services/assignment.js.map +1 -1
- package/dist/services/attendance.js +10 -2
- package/dist/services/attendance.js.map +1 -1
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/class.js +6 -2
- package/dist/services/class.js.map +1 -1
- package/dist/services/discussion.d.ts.map +1 -1
- package/dist/services/discussion.js +41 -12
- package/dist/services/discussion.js.map +1 -1
- package/dist/services/discussionSummary.d.ts.map +1 -1
- package/dist/services/discussionSummary.js +3 -2
- package/dist/services/discussionSummary.js.map +1 -1
- package/dist/services/notification.d.ts +79 -16
- package/dist/services/notification.d.ts.map +1 -1
- package/dist/services/notification.js +64 -16
- package/dist/services/notification.js.map +1 -1
- package/dist/services/studentProgress.d.ts.map +1 -1
- package/dist/services/studentProgress.js +6 -2
- package/dist/services/studentProgress.js.map +1 -1
- package/dist/services/userPrivacy.d.ts +4 -0
- package/dist/services/userPrivacy.d.ts.map +1 -1
- package/dist/services/userPrivacy.js +6 -2
- package/dist/services/userPrivacy.js.map +1 -1
- package/dist/services/worksheet.js +4 -4
- package/dist/services/worksheet.js.map +1 -1
- package/dist/utils/inference.d.ts +27 -0
- package/dist/utils/inference.d.ts.map +1 -1
- package/dist/utils/inference.js +23 -3
- package/dist/utils/inference.js.map +1 -1
- package/dist/workers/processors/aiLabChat.d.ts.map +1 -1
- package/dist/workers/processors/aiLabChat.js +4 -3
- package/dist/workers/processors/aiLabChat.js.map +1 -1
- package/dist/workers/processors/aiNewtonChat.d.ts.map +1 -1
- package/dist/workers/processors/aiNewtonChat.js +5 -5
- package/dist/workers/processors/aiNewtonChat.js.map +1 -1
- package/dist/workers/processors/gradeWorksheet.js +4 -4
- package/dist/workers/processors/gradeWorksheet.js.map +1 -1
- package/dist/workers/processors/pdfToWorksheet.d.ts.map +1 -1
- package/dist/workers/processors/pdfToWorksheet.js +7 -4
- package/dist/workers/processors/pdfToWorksheet.js.map +1 -1
- package/docker-entrypoint.sh +7 -0
- package/package.json +1 -1
- package/prisma/migrations/20260708011456_add_file_annotations/migration.sql +35 -0
- package/prisma/migrations/20260708011500_add_ai_usage_attribution/migration.sql +13 -0
- package/prisma/migrations/20260708013000_add_notification_type_and_entity/migration.sql +14 -0
- package/prisma/schema.prisma +99 -43
- package/src/index.ts +7 -0
- package/src/lib/pusher.ts +3 -1
- package/src/middleware/security.ts +2 -2
- package/src/models/class.ts +3 -2
- package/src/models/notification.ts +42 -13
- package/src/pipelines/aiLabChat.ts +6 -1
- package/src/pipelines/aiNewtonChat.ts +10 -0
- package/src/pipelines/gradeWorksheet.ts +25 -7
- package/src/pipelines/pdfToWorksheet.ts +3 -0
- package/src/routers/announcement.ts +0 -1
- package/src/routers/assignment.ts +214 -0
- package/src/routers/notifications.ts +26 -24
- package/src/routers/school.ts +12 -0
- package/src/routers/user.ts +12 -0
- package/src/services/aiChatStream.ts +35 -0
- package/src/services/aiStream.ts +34 -0
- package/src/services/aiUsage.ts +202 -0
- package/src/services/announcement.ts +5 -0
- package/src/services/assignment.ts +29 -2
- package/src/services/attendance.ts +12 -0
- package/src/services/class.ts +4 -0
- package/src/services/discussion.ts +50 -11
- package/src/services/discussionSummary.ts +1 -0
- package/src/services/notification.ts +83 -20
- package/src/services/studentProgress.ts +4 -0
- package/src/services/userPrivacy.ts +4 -0
- package/src/services/worksheet.ts +2 -2
- package/src/utils/inference.ts +40 -2
- package/src/workers/processors/aiLabChat.ts +2 -1
- package/src/workers/processors/aiNewtonChat.ts +3 -1
- package/src/workers/processors/gradeWorksheet.ts +2 -2
- package/src/workers/processors/pdfToWorksheet.ts +5 -2
- package/tests/integration/routers/notification.test.ts +68 -20
- package/tests/unit/services/aiUsage.test.ts +105 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!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]="
|
|
2
|
+
!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]="777a2189-86f3-5317-9904-cac13606cce2")}catch(e){}}();
|
|
3
3
|
import { gradeWorksheetPipeline } from '../../pipelines/gradeWorksheet.js';
|
|
4
4
|
import { createRunJobWorker } from './createRunJobWorker.js';
|
|
5
5
|
export const gradeWorksheetWorker = createRunJobWorker({
|
|
@@ -10,10 +10,10 @@ export const gradeWorksheetWorker = createRunJobWorker({
|
|
|
10
10
|
// throughput modest until we measure real-world usage.
|
|
11
11
|
limiter: { max: 5, duration: 60000 },
|
|
12
12
|
},
|
|
13
|
-
run: async ({ worksheetResponseId }) => {
|
|
14
|
-
await gradeWorksheetPipeline(worksheetResponseId);
|
|
13
|
+
run: async ({ worksheetResponseId }, ctx) => {
|
|
14
|
+
await gradeWorksheetPipeline(worksheetResponseId, ctx.userId);
|
|
15
15
|
return { worksheetResponseId };
|
|
16
16
|
},
|
|
17
17
|
});
|
|
18
18
|
//# sourceMappingURL=gradeWorksheet.js.map
|
|
19
|
-
//# debugId=
|
|
19
|
+
//# debugId=777a2189-86f3-5317-9904-cac13606cce2
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gradeWorksheet.js","sources":["workers/processors/gradeWorksheet.ts"],"sourceRoot":"/","sourcesContent":["import { gradeWorksheetPipeline } from '../../pipelines/gradeWorksheet.js';\nimport { createRunJobWorker } from './createRunJobWorker.js';\n\nexport const gradeWorksheetWorker = createRunJobWorker({\n type: 'GRADE_WORKSHEET',\n workerOptions: {\n concurrency: 2,\n // Each pipeline run can fan out to many per-question inferences; keep\n // throughput modest until we measure real-world usage.\n limiter: { max: 5, duration: 60_000 },\n },\n run: async ({ worksheetResponseId }) => {\n await gradeWorksheetPipeline(worksheetResponseId);\n return { worksheetResponseId };\n },\n});\n"],"names":[],"mappings":";;AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,CAAC,MAAM,oBAAoB,GAAG,kBAAkB,CAAC;IACrD,IAAI,EAAE,iBAAiB;IACvB,aAAa,EAAE;QACb,WAAW,EAAE,CAAC;QACd,sEAAsE;QACtE,uDAAuD;QACvD,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAM,EAAE;KACtC;IACD,GAAG,EAAE,KAAK,EAAE,EAAE,mBAAmB,EAAE,EAAE,EAAE;
|
|
1
|
+
{"version":3,"file":"gradeWorksheet.js","sources":["workers/processors/gradeWorksheet.ts"],"sourceRoot":"/","sourcesContent":["import { gradeWorksheetPipeline } from '../../pipelines/gradeWorksheet.js';\nimport { createRunJobWorker } from './createRunJobWorker.js';\n\nexport const gradeWorksheetWorker = createRunJobWorker({\n type: 'GRADE_WORKSHEET',\n workerOptions: {\n concurrency: 2,\n // Each pipeline run can fan out to many per-question inferences; keep\n // throughput modest until we measure real-world usage.\n limiter: { max: 5, duration: 60_000 },\n },\n run: async ({ worksheetResponseId }, ctx) => {\n await gradeWorksheetPipeline(worksheetResponseId, ctx.userId);\n return { worksheetResponseId };\n },\n});\n"],"names":[],"mappings":";;AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,CAAC,MAAM,oBAAoB,GAAG,kBAAkB,CAAC;IACrD,IAAI,EAAE,iBAAiB;IACvB,aAAa,EAAE;QACb,WAAW,EAAE,CAAC;QACd,sEAAsE;QACtE,uDAAuD;QACvD,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAM,EAAE;KACtC;IACD,GAAG,EAAE,KAAK,EAAE,EAAE,mBAAmB,EAAE,EAAE,GAAG,EAAE,EAAE;QAC1C,MAAM,sBAAsB,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;QAC9D,OAAO,EAAE,mBAAmB,EAAE,CAAC;IACjC,CAAC;CACF,CAAC,CAAC","debug_id":"777a2189-86f3-5317-9904-cac13606cce2"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pdfToWorksheet.d.ts","sourceRoot":"/","sources":["workers/processors/pdfToWorksheet.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,oBAAoB;;;;;;
|
|
1
|
+
{"version":3,"file":"pdfToWorksheet.d.ts","sourceRoot":"/","sources":["workers/processors/pdfToWorksheet.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,oBAAoB;;;;;;eAgB/B,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!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]="
|
|
2
|
+
!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]="83752754-be47-54a0-b136-1b4140250056")}catch(e){}}();
|
|
3
3
|
import { pdfToWorksheet } from '../../pipelines/pdfToWorksheet.js';
|
|
4
4
|
import { createRunJobWorker } from './createRunJobWorker.js';
|
|
5
5
|
export const pdfToWorksheetWorker = createRunJobWorker({
|
|
@@ -9,12 +9,15 @@ export const pdfToWorksheetWorker = createRunJobWorker({
|
|
|
9
9
|
// 5 jobs / minute, e.g. to stay under OpenAI rate limits.
|
|
10
10
|
limiter: { max: 5, duration: 60000 },
|
|
11
11
|
},
|
|
12
|
-
run: async ({ gcsUrl, classId, worksheetId }) => {
|
|
13
|
-
const worksheet = await pdfToWorksheet(gcsUrl, classId, {
|
|
12
|
+
run: async ({ gcsUrl, classId, worksheetId }, ctx) => {
|
|
13
|
+
const worksheet = await pdfToWorksheet(gcsUrl, classId, {
|
|
14
|
+
worksheetId,
|
|
15
|
+
userId: ctx.userId,
|
|
16
|
+
});
|
|
14
17
|
return worksheet
|
|
15
18
|
? { worksheetId: worksheet.id, name: worksheet.name }
|
|
16
19
|
: null;
|
|
17
20
|
},
|
|
18
21
|
});
|
|
19
22
|
//# sourceMappingURL=pdfToWorksheet.js.map
|
|
20
|
-
//# debugId=
|
|
23
|
+
//# debugId=83752754-be47-54a0-b136-1b4140250056
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pdfToWorksheet.js","sources":["workers/processors/pdfToWorksheet.ts"],"sourceRoot":"/","sourcesContent":["import { pdfToWorksheet } from '../../pipelines/pdfToWorksheet.js';\nimport { createRunJobWorker } from './createRunJobWorker.js';\n\nexport const pdfToWorksheetWorker = createRunJobWorker({\n type: 'PDF_TO_WORKSHEET',\n workerOptions: {\n concurrency: 2,\n // 5 jobs / minute, e.g. to stay under OpenAI rate limits.\n limiter: { max: 5, duration: 60_000 },\n },\n run: async ({ gcsUrl, classId, worksheetId }) => {\n const worksheet = await pdfToWorksheet(gcsUrl, classId, {
|
|
1
|
+
{"version":3,"file":"pdfToWorksheet.js","sources":["workers/processors/pdfToWorksheet.ts"],"sourceRoot":"/","sourcesContent":["import { pdfToWorksheet } from '../../pipelines/pdfToWorksheet.js';\nimport { createRunJobWorker } from './createRunJobWorker.js';\n\nexport const pdfToWorksheetWorker = createRunJobWorker({\n type: 'PDF_TO_WORKSHEET',\n workerOptions: {\n concurrency: 2,\n // 5 jobs / minute, e.g. to stay under OpenAI rate limits.\n limiter: { max: 5, duration: 60_000 },\n },\n run: async ({ gcsUrl, classId, worksheetId }, ctx) => {\n const worksheet = await pdfToWorksheet(gcsUrl, classId, {\n worksheetId,\n userId: ctx.userId,\n });\n return worksheet\n ? { worksheetId: worksheet.id, name: worksheet.name }\n : null;\n },\n});\n"],"names":[],"mappings":";;AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,CAAC,MAAM,oBAAoB,GAAG,kBAAkB,CAAC;IACrD,IAAI,EAAE,kBAAkB;IACxB,aAAa,EAAE;QACb,WAAW,EAAE,CAAC;QACd,0DAA0D;QAC1D,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAM,EAAE;KACtC;IACD,GAAG,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,EAAE;QACnD,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE;YACtD,WAAW;YACX,MAAM,EAAE,GAAG,CAAC,MAAM;SACnB,CAAC,CAAC;QACH,OAAO,SAAS;YACd,CAAC,CAAC,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE;YACrD,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;CACF,CAAC,CAAC","debug_id":"83752754-be47-54a0-b136-1b4140250056"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
-- CreateEnum
|
|
2
|
+
CREATE TYPE "FileAnnotationType" AS ENUM ('HIGHLIGHT', 'INK', 'COMMENT', 'TEXT');
|
|
3
|
+
|
|
4
|
+
-- CreateTable
|
|
5
|
+
CREATE TABLE "FileAnnotation" (
|
|
6
|
+
"id" TEXT NOT NULL,
|
|
7
|
+
"fileId" TEXT NOT NULL,
|
|
8
|
+
"submissionId" TEXT NOT NULL,
|
|
9
|
+
"authorId" TEXT NOT NULL,
|
|
10
|
+
"page" INTEGER NOT NULL,
|
|
11
|
+
"type" "FileAnnotationType" NOT NULL,
|
|
12
|
+
"geometry" JSONB NOT NULL,
|
|
13
|
+
"text" TEXT,
|
|
14
|
+
"color" TEXT,
|
|
15
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
16
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
17
|
+
|
|
18
|
+
CONSTRAINT "FileAnnotation_pkey" PRIMARY KEY ("id")
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
-- CreateIndex
|
|
22
|
+
CREATE INDEX "FileAnnotation_fileId_idx" ON "FileAnnotation"("fileId");
|
|
23
|
+
|
|
24
|
+
-- CreateIndex
|
|
25
|
+
CREATE INDEX "FileAnnotation_submissionId_idx" ON "FileAnnotation"("submissionId");
|
|
26
|
+
|
|
27
|
+
-- AddForeignKey
|
|
28
|
+
ALTER TABLE "FileAnnotation" ADD CONSTRAINT "FileAnnotation_fileId_fkey" FOREIGN KEY ("fileId") REFERENCES "File"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
29
|
+
|
|
30
|
+
-- AddForeignKey
|
|
31
|
+
ALTER TABLE "FileAnnotation" ADD CONSTRAINT "FileAnnotation_submissionId_fkey" FOREIGN KEY ("submissionId") REFERENCES "Submission"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
32
|
+
|
|
33
|
+
-- AddForeignKey
|
|
34
|
+
ALTER TABLE "FileAnnotation" ADD CONSTRAINT "FileAnnotation_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
35
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- AlterTable
|
|
2
|
+
ALTER TABLE "AiUsageLog" ADD COLUMN "userId" TEXT,
|
|
3
|
+
ADD COLUMN "schoolId" TEXT,
|
|
4
|
+
ADD COLUMN "classId" TEXT;
|
|
5
|
+
|
|
6
|
+
-- CreateIndex
|
|
7
|
+
CREATE INDEX "AiUsageLog_userId_createdAt_idx" ON "AiUsageLog"("userId", "createdAt");
|
|
8
|
+
|
|
9
|
+
-- CreateIndex
|
|
10
|
+
CREATE INDEX "AiUsageLog_schoolId_createdAt_idx" ON "AiUsageLog"("schoolId", "createdAt");
|
|
11
|
+
|
|
12
|
+
-- CreateIndex
|
|
13
|
+
CREATE INDEX "AiUsageLog_classId_createdAt_idx" ON "AiUsageLog"("classId", "createdAt");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- CreateEnum
|
|
2
|
+
CREATE TYPE "NotificationType" AS ENUM ('ASSIGNMENT_POSTED', 'ASSIGNMENT_RETURNED', 'COMMENT_REPLY', 'DISCUSSION_REPLY', 'DISCUSSION_MENTION', 'ANNOUNCEMENT', 'ATTENDANCE', 'SYSTEM');
|
|
3
|
+
|
|
4
|
+
-- AlterTable
|
|
5
|
+
ALTER TABLE "Notification" ADD COLUMN "type" "NotificationType" NOT NULL DEFAULT 'SYSTEM',
|
|
6
|
+
ADD COLUMN "entityType" TEXT,
|
|
7
|
+
ADD COLUMN "entityId" TEXT,
|
|
8
|
+
ADD COLUMN "classId" TEXT;
|
|
9
|
+
|
|
10
|
+
-- CreateIndex
|
|
11
|
+
CREATE INDEX "Notification_receiverId_createdAt_idx" ON "Notification"("receiverId", "createdAt");
|
|
12
|
+
|
|
13
|
+
-- CreateIndex
|
|
14
|
+
CREATE INDEX "Notification_receiverId_read_idx" ON "Notification"("receiverId", "read");
|
package/prisma/schema.prisma
CHANGED
|
@@ -128,14 +128,14 @@ model School {
|
|
|
128
128
|
createdAt DateTime @default(now())
|
|
129
129
|
updatedAt DateTime @updatedAt
|
|
130
130
|
|
|
131
|
-
users
|
|
132
|
-
memberships
|
|
133
|
-
invites
|
|
134
|
-
classes
|
|
135
|
-
departments
|
|
136
|
-
tags
|
|
137
|
-
storageRoot
|
|
138
|
-
permissions
|
|
131
|
+
users User[]
|
|
132
|
+
memberships SchoolMembership[]
|
|
133
|
+
invites SchoolInvite[]
|
|
134
|
+
classes Class[]
|
|
135
|
+
departments Department[]
|
|
136
|
+
tags Tag[]
|
|
137
|
+
storageRoot Folder? @relation("SchoolStorageRoot")
|
|
138
|
+
permissions Permission[] @relation("SchoolPermissions")
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
model Department {
|
|
@@ -175,13 +175,13 @@ model Tag {
|
|
|
175
175
|
createdAt DateTime @default(now())
|
|
176
176
|
updatedAt DateTime @updatedAt
|
|
177
177
|
|
|
178
|
-
school
|
|
179
|
-
department
|
|
180
|
-
users
|
|
181
|
-
classes
|
|
182
|
-
rubrics
|
|
183
|
-
boundaries
|
|
184
|
-
permissions Permission[]
|
|
178
|
+
school School @relation(fields: [schoolId], references: [id], onDelete: Cascade)
|
|
179
|
+
department Department? @relation(fields: [departmentId], references: [id], onDelete: SetNull)
|
|
180
|
+
users User[] @relation("UserTags")
|
|
181
|
+
classes Class[] @relation("ClassTags")
|
|
182
|
+
rubrics MarkScheme[] @relation("MarkSchemeTags")
|
|
183
|
+
boundaries GradingBoundary[] @relation("GradingBoundaryTags")
|
|
184
|
+
permissions Permission[] @relation("TagPermissions")
|
|
185
185
|
|
|
186
186
|
@@unique([schoolId, name])
|
|
187
187
|
@@index([schoolId])
|
|
@@ -197,7 +197,7 @@ model User {
|
|
|
197
197
|
verified Boolean @default(false)
|
|
198
198
|
|
|
199
199
|
termsAcceptedAt DateTime?
|
|
200
|
-
termsVersion
|
|
200
|
+
termsVersion String?
|
|
201
201
|
|
|
202
202
|
teacherIn Class[] @relation("UserTeacherToClass")
|
|
203
203
|
studentIn Class[] @relation("UserStudentToClass")
|
|
@@ -241,6 +241,7 @@ model User {
|
|
|
241
241
|
discussions Discussion[]
|
|
242
242
|
discussionReplies DiscussionReply[]
|
|
243
243
|
discussionReads DiscussionRead[]
|
|
244
|
+
fileAnnotations FileAnnotation[] @relation("AuthoredFileAnnotations")
|
|
244
245
|
}
|
|
245
246
|
|
|
246
247
|
model SchoolMembership {
|
|
@@ -330,9 +331,9 @@ model Class {
|
|
|
330
331
|
classTags ClassTag[]
|
|
331
332
|
embeddings Embedding[]
|
|
332
333
|
|
|
333
|
-
discussionsEnabled
|
|
334
|
-
studentsCanCreateDiscussions
|
|
335
|
-
studentsCanReply
|
|
334
|
+
discussionsEnabled Boolean @default(true)
|
|
335
|
+
studentsCanCreateDiscussions Boolean @default(true)
|
|
336
|
+
studentsCanReply Boolean @default(true)
|
|
336
337
|
|
|
337
338
|
@@index([schoolId])
|
|
338
339
|
@@index([departmentId])
|
|
@@ -454,6 +455,35 @@ model File {
|
|
|
454
455
|
|
|
455
456
|
schoolDevelopementProgram SchoolDevelopementProgram? @relation("SchoolDevelopementProgramSupportingDocumentation", fields: [schoolDevelopementProgramId], references: [id], onDelete: Cascade)
|
|
456
457
|
schoolDevelopementProgramId String?
|
|
458
|
+
|
|
459
|
+
fileAnnotations FileAnnotation[] @relation("FileAnnotations")
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
enum FileAnnotationType {
|
|
463
|
+
HIGHLIGHT
|
|
464
|
+
INK
|
|
465
|
+
COMMENT
|
|
466
|
+
TEXT
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
model FileAnnotation {
|
|
470
|
+
id String @id @default(uuid())
|
|
471
|
+
file File @relation("FileAnnotations", fields: [fileId], references: [id], onDelete: Cascade)
|
|
472
|
+
fileId String
|
|
473
|
+
submission Submission @relation("SubmissionFileAnnotations", fields: [submissionId], references: [id], onDelete: Cascade)
|
|
474
|
+
submissionId String
|
|
475
|
+
author User @relation("AuthoredFileAnnotations", fields: [authorId], references: [id], onDelete: Cascade)
|
|
476
|
+
authorId String
|
|
477
|
+
page Int
|
|
478
|
+
type FileAnnotationType
|
|
479
|
+
geometry Json
|
|
480
|
+
text String?
|
|
481
|
+
color String?
|
|
482
|
+
createdAt DateTime @default(now())
|
|
483
|
+
updatedAt DateTime @updatedAt
|
|
484
|
+
|
|
485
|
+
@@index([fileId])
|
|
486
|
+
@@index([submissionId])
|
|
457
487
|
}
|
|
458
488
|
|
|
459
489
|
model Assignment {
|
|
@@ -607,19 +637,19 @@ model DiscussionRead {
|
|
|
607
637
|
}
|
|
608
638
|
|
|
609
639
|
model Reaction {
|
|
610
|
-
id
|
|
611
|
-
type
|
|
612
|
-
user
|
|
613
|
-
userId
|
|
614
|
-
announcement
|
|
615
|
-
announcementId
|
|
616
|
-
comment
|
|
617
|
-
commentId
|
|
640
|
+
id String @id @default(uuid())
|
|
641
|
+
type ReactionType
|
|
642
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
643
|
+
userId String
|
|
644
|
+
announcement Announcement? @relation(fields: [announcementId], references: [id], onDelete: Cascade)
|
|
645
|
+
announcementId String?
|
|
646
|
+
comment Comment? @relation(fields: [commentId], references: [id], onDelete: Cascade)
|
|
647
|
+
commentId String?
|
|
618
648
|
discussion Discussion? @relation(fields: [discussionId], references: [id], onDelete: Cascade)
|
|
619
649
|
discussionId String?
|
|
620
650
|
discussionReply DiscussionReply? @relation(fields: [discussionReplyId], references: [id], onDelete: Cascade)
|
|
621
651
|
discussionReplyId String?
|
|
622
|
-
createdAt
|
|
652
|
+
createdAt DateTime @default(now())
|
|
623
653
|
|
|
624
654
|
@@unique([userId, announcementId])
|
|
625
655
|
@@unique([userId, commentId])
|
|
@@ -661,6 +691,8 @@ model Submission {
|
|
|
661
691
|
returned Boolean? @default(false)
|
|
662
692
|
newtonTutors NewtonChat[]
|
|
663
693
|
|
|
694
|
+
fileAnnotations FileAnnotation[] @relation("SubmissionFileAnnotations")
|
|
695
|
+
|
|
664
696
|
@@index([studentId, recommendationState])
|
|
665
697
|
}
|
|
666
698
|
|
|
@@ -734,16 +766,34 @@ model Attendance {
|
|
|
734
766
|
absent User[] @relation("AbsentAttendance")
|
|
735
767
|
}
|
|
736
768
|
|
|
769
|
+
enum NotificationType {
|
|
770
|
+
ASSIGNMENT_POSTED
|
|
771
|
+
ASSIGNMENT_RETURNED
|
|
772
|
+
COMMENT_REPLY
|
|
773
|
+
DISCUSSION_REPLY
|
|
774
|
+
DISCUSSION_MENTION
|
|
775
|
+
ANNOUNCEMENT
|
|
776
|
+
ATTENDANCE
|
|
777
|
+
SYSTEM
|
|
778
|
+
}
|
|
779
|
+
|
|
737
780
|
model Notification {
|
|
738
|
-
id String
|
|
781
|
+
id String @id @default(uuid())
|
|
739
782
|
title String
|
|
740
783
|
content String
|
|
741
|
-
createdAt DateTime
|
|
784
|
+
createdAt DateTime @default(now())
|
|
742
785
|
senderId String? // no ID means sys
|
|
743
786
|
receiverId String
|
|
744
|
-
read Boolean
|
|
745
|
-
|
|
746
|
-
|
|
787
|
+
read Boolean @default(false)
|
|
788
|
+
type NotificationType @default(SYSTEM)
|
|
789
|
+
entityType String? // e.g. ASSIGNMENT, ANNOUNCEMENT, DISCUSSION, SUBMISSION
|
|
790
|
+
entityId String?
|
|
791
|
+
classId String?
|
|
792
|
+
sender User? @relation("SentNotifications", fields: [senderId], references: [id])
|
|
793
|
+
receiver User @relation("ReceivedNotifications", fields: [receiverId], references: [id], onDelete: Cascade)
|
|
794
|
+
|
|
795
|
+
@@index([receiverId, createdAt])
|
|
796
|
+
@@index([receiverId, read])
|
|
747
797
|
}
|
|
748
798
|
|
|
749
799
|
enum ConversationType {
|
|
@@ -837,9 +887,15 @@ model AiUsageLog {
|
|
|
837
887
|
estimatedInputTokens Int?
|
|
838
888
|
metadata Json? @default("{}")
|
|
839
889
|
error String?
|
|
890
|
+
userId String?
|
|
891
|
+
schoolId String?
|
|
892
|
+
classId String?
|
|
840
893
|
createdAt DateTime @default(now())
|
|
841
894
|
|
|
842
895
|
@@index([reason, createdAt])
|
|
896
|
+
@@index([userId, createdAt])
|
|
897
|
+
@@index([schoolId, createdAt])
|
|
898
|
+
@@index([classId, createdAt])
|
|
843
899
|
}
|
|
844
900
|
|
|
845
901
|
model Mention {
|
|
@@ -866,11 +922,11 @@ model Worksheet {
|
|
|
866
922
|
questions WorksheetQuestion[]
|
|
867
923
|
studentWorksheetResponses StudentWorksheetResponse[]
|
|
868
924
|
|
|
869
|
-
runJobId
|
|
870
|
-
runJob
|
|
925
|
+
runJobId String?
|
|
926
|
+
runJob RunJob? @relation("WorksheetRunJob", fields: [runJobId], references: [id], onDelete: SetNull)
|
|
871
927
|
|
|
872
|
-
status
|
|
873
|
-
tags
|
|
928
|
+
status JobStatus? @default(NOT_STARTED)
|
|
929
|
+
tags ClassTag[] @relation("WorksheetToTag")
|
|
874
930
|
|
|
875
931
|
@@unique([classId, sourceUrl])
|
|
876
932
|
}
|
|
@@ -987,14 +1043,14 @@ enum EmbeddingSourceType {
|
|
|
987
1043
|
}
|
|
988
1044
|
|
|
989
1045
|
model Embedding {
|
|
990
|
-
id String
|
|
1046
|
+
id String @id @default(uuid())
|
|
991
1047
|
classId String
|
|
992
1048
|
sourceType EmbeddingSourceType
|
|
993
1049
|
sourceId String
|
|
994
1050
|
content String
|
|
995
1051
|
vector Unsupported("vector(1536)")
|
|
996
|
-
metadata Json?
|
|
997
|
-
createdAt DateTime
|
|
1052
|
+
metadata Json? @default("{}")
|
|
1053
|
+
createdAt DateTime @default(now())
|
|
998
1054
|
|
|
999
1055
|
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
|
|
1000
1056
|
|
|
@@ -1038,7 +1094,7 @@ model RunJob {
|
|
|
1038
1094
|
/// most jobs touch zero or one of these.
|
|
1039
1095
|
messages Message[] @relation("MessageRunJob")
|
|
1040
1096
|
studentQuestionProgresses StudentQuestionProgress[] @relation("StudentQuestionProgressRunJob")
|
|
1041
|
-
worksheet Worksheet[]
|
|
1097
|
+
worksheet Worksheet[] @relation("WorksheetRunJob")
|
|
1042
1098
|
|
|
1043
1099
|
@@index([userId, type, status])
|
|
1044
1100
|
@@index([classId, status])
|
|
@@ -1074,9 +1130,9 @@ enum PermissionLevel {
|
|
|
1074
1130
|
/// `schoolId` is set, matching `principalType`. CLASS, CLASS_TEACHERS,
|
|
1075
1131
|
/// CLASS_STUDENTS all share `classId`.
|
|
1076
1132
|
model Permission {
|
|
1077
|
-
id String
|
|
1133
|
+
id String @id @default(uuid())
|
|
1078
1134
|
folderId String
|
|
1079
|
-
folder Folder
|
|
1135
|
+
folder Folder @relation("FolderPermissions", fields: [folderId], references: [id], onDelete: Cascade)
|
|
1080
1136
|
|
|
1081
1137
|
principalType PermissionPrincipalType
|
|
1082
1138
|
userId String?
|
package/src/index.ts
CHANGED
|
@@ -272,6 +272,13 @@ app.post('/api/pusher/auth', async (req, res) => {
|
|
|
272
272
|
// }
|
|
273
273
|
// }
|
|
274
274
|
|
|
275
|
+
// Per-user notification channel: only the user themselves may subscribe
|
|
276
|
+
if (channel_name.startsWith('private-user-')) {
|
|
277
|
+
if (channel_name !== `private-user-${user.id}-notifications`) {
|
|
278
|
+
return res.status(403).json({ error: 'No access to this user channel' });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
275
282
|
if (channel_name.startsWith('presence-')) {
|
|
276
283
|
const authResponse = pusher.authorizeChannel(socket_id, channel_name, {
|
|
277
284
|
user_id: user.id,
|
package/src/lib/pusher.ts
CHANGED
|
@@ -19,5 +19,7 @@ const discussionChannel = (classId: string) => `private-class-${classId}-discuss
|
|
|
19
19
|
* worksheet-response channel so both can co-exist without auth collisions.
|
|
20
20
|
*/
|
|
21
21
|
const worksheetJobChannel = (worksheetId: string) => `private-worksheet-job-${worksheetId}`;
|
|
22
|
+
/** Per-user private channel for in-app notification pushes. */
|
|
23
|
+
const userNotificationChannel = (userId: string) => `private-user-${userId}-notifications`;
|
|
22
24
|
|
|
23
|
-
export { pusher, chatChannel, worksheetChannel, teacherChannel, discussionChannel, worksheetJobChannel };
|
|
25
|
+
export { pusher, chatChannel, worksheetChannel, teacherChannel, discussionChannel, worksheetJobChannel, userNotificationChannel };
|
|
@@ -20,7 +20,7 @@ const rateLimitHandler = (req: Request, res: Response) => {
|
|
|
20
20
|
// General API rate limiter - applies to all routes
|
|
21
21
|
export const generalLimiter = rateLimit({
|
|
22
22
|
windowMs: 10 * 60 * 1000, // 10 minutes
|
|
23
|
-
max:
|
|
23
|
+
max: 10000, // Limit each IP to 10000 requests per windowMs (schools often share one NAT IP)
|
|
24
24
|
message: 'Too many requests from this IP, please try again later.',
|
|
25
25
|
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
|
26
26
|
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
|
@@ -45,7 +45,7 @@ export const authLimiter = rateLimit({
|
|
|
45
45
|
// File upload rate limiter
|
|
46
46
|
export const uploadLimiter = rateLimit({
|
|
47
47
|
windowMs: 30 * 60 * 1000, // 30 minutes
|
|
48
|
-
max:
|
|
48
|
+
max: 500, // Limit each IP to 500 uploads per windowMs (a whole class may submit at once behind one IP)
|
|
49
49
|
message: 'Too many file uploads, please try again later.',
|
|
50
50
|
standardHeaders: true,
|
|
51
51
|
legacyHeaders: false,
|
package/src/models/class.ts
CHANGED
|
@@ -298,10 +298,11 @@ export function findSessionByClassCode(classCode: string) {
|
|
|
298
298
|
});
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
-
/** @returns
|
|
301
|
+
/** @returns Invite session for a class. */
|
|
302
302
|
export function findSessionByClassId(classId: string) {
|
|
303
303
|
return prisma.session.findFirst({
|
|
304
|
-
where: { classId },
|
|
304
|
+
where: { classId, type: SessionType.CLASS_INVITE },
|
|
305
|
+
orderBy: { expiresAt: "desc" },
|
|
305
306
|
});
|
|
306
307
|
}
|
|
307
308
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Notification model – in-app notifications, create, mark read.
|
|
3
3
|
*/
|
|
4
|
+
import type { NotificationType } from "@prisma/client";
|
|
4
5
|
import { prisma } from "../lib/prisma.js";
|
|
5
6
|
|
|
6
7
|
const notificationInclude = {
|
|
@@ -8,11 +9,28 @@ const notificationInclude = {
|
|
|
8
9
|
receiver: { select: { username: true } },
|
|
9
10
|
};
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
export type NotificationCreateData = {
|
|
13
|
+
receiverId: string;
|
|
14
|
+
title: string;
|
|
15
|
+
content: string;
|
|
16
|
+
senderId?: string | null;
|
|
17
|
+
type?: NotificationType;
|
|
18
|
+
entityType?: string | null;
|
|
19
|
+
entityId?: string | null;
|
|
20
|
+
classId?: string | null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** @returns A page of notifications for a receiver, newest first. */
|
|
24
|
+
export function findNotificationsByReceiverIdPaginated(
|
|
25
|
+
receiverId: string,
|
|
26
|
+
limit: number,
|
|
27
|
+
cursor?: string | null
|
|
28
|
+
) {
|
|
13
29
|
return prisma.notification.findMany({
|
|
14
30
|
where: { receiverId },
|
|
15
|
-
orderBy: { createdAt: "desc" },
|
|
31
|
+
orderBy: [{ createdAt: "desc" }, { id: "desc" }],
|
|
32
|
+
take: limit + 1,
|
|
33
|
+
...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
|
|
16
34
|
include: notificationInclude,
|
|
17
35
|
});
|
|
18
36
|
}
|
|
@@ -25,23 +43,26 @@ export function findNotificationByIdForReceiver(id: string, receiverId: string)
|
|
|
25
43
|
});
|
|
26
44
|
}
|
|
27
45
|
|
|
46
|
+
/** @returns Number of unread notifications for a receiver. */
|
|
47
|
+
export function countUnreadNotifications(receiverId: string) {
|
|
48
|
+
return prisma.notification.count({
|
|
49
|
+
where: { receiverId, read: false },
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
28
53
|
/** Create a notification for one receiver. */
|
|
29
|
-
export function createNotification(data: {
|
|
30
|
-
receiverId: string;
|
|
31
|
-
title: string;
|
|
32
|
-
content: string;
|
|
33
|
-
}) {
|
|
54
|
+
export function createNotification(data: NotificationCreateData) {
|
|
34
55
|
return prisma.notification.create({
|
|
35
56
|
data,
|
|
57
|
+
include: notificationInclude,
|
|
36
58
|
});
|
|
37
59
|
}
|
|
38
60
|
|
|
39
|
-
/** Create multiple notifications in one call. */
|
|
40
|
-
export function createManyNotifications(
|
|
41
|
-
|
|
42
|
-
) {
|
|
43
|
-
return prisma.notification.createMany({
|
|
61
|
+
/** Create multiple notifications in one call and return the created rows. */
|
|
62
|
+
export function createManyNotifications(data: NotificationCreateData[]) {
|
|
63
|
+
return prisma.notification.createManyAndReturn({
|
|
44
64
|
data,
|
|
65
|
+
include: notificationInclude,
|
|
45
66
|
});
|
|
46
67
|
}
|
|
47
68
|
|
|
@@ -58,3 +79,11 @@ export async function markNotificationAsReadForReceiver(id: string, receiverId:
|
|
|
58
79
|
|
|
59
80
|
return findNotificationByIdForReceiver(id, receiverId);
|
|
60
81
|
}
|
|
82
|
+
|
|
83
|
+
/** Mark all of a receiver's notifications as read. */
|
|
84
|
+
export function markAllNotificationsAsReadForReceiver(receiverId: string) {
|
|
85
|
+
return prisma.notification.updateMany({
|
|
86
|
+
where: { receiverId, read: false },
|
|
87
|
+
data: { read: true },
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -378,7 +378,7 @@ export const generateAndSendLabIntroduction = async (
|
|
|
378
378
|
export const generateAndSendLabResponse = async (
|
|
379
379
|
labChatId: string,
|
|
380
380
|
teacherMessage: string,
|
|
381
|
-
emitOptions: { classId: string; aiMessageId: string }
|
|
381
|
+
emitOptions: { classId: string; aiMessageId: string; userId?: string | null }
|
|
382
382
|
): Promise<void> => {
|
|
383
383
|
try {
|
|
384
384
|
// Get lab context from database
|
|
@@ -499,6 +499,11 @@ export const generateAndSendLabIntroduction = async (
|
|
|
499
499
|
model: AI_CHAT_MODEL,
|
|
500
500
|
system,
|
|
501
501
|
messages,
|
|
502
|
+
usageReason: 'lab_chat_response',
|
|
503
|
+
attribution: {
|
|
504
|
+
userId: emitOptions.userId ?? fullLabChat.createdById,
|
|
505
|
+
classId: emitOptions.classId,
|
|
506
|
+
},
|
|
502
507
|
tools,
|
|
503
508
|
temperature: 0.7,
|
|
504
509
|
maxOutputTokens: 4096,
|
|
@@ -99,6 +99,7 @@ export const generateAndSendNewtonIntroduction = async (
|
|
|
99
99
|
conversationId: string,
|
|
100
100
|
submissionId: string,
|
|
101
101
|
aiMessageId: string,
|
|
102
|
+
actingUserId?: string | null,
|
|
102
103
|
): Promise<void> => {
|
|
103
104
|
try {
|
|
104
105
|
// Get submission details for context
|
|
@@ -109,6 +110,7 @@ export const generateAndSendNewtonIntroduction = async (
|
|
|
109
110
|
select: {
|
|
110
111
|
title: true,
|
|
111
112
|
instructions: true,
|
|
113
|
+
classId: true,
|
|
112
114
|
class: {
|
|
113
115
|
select: {
|
|
114
116
|
subject: true,
|
|
@@ -160,6 +162,11 @@ export const generateAndSendNewtonIntroduction = async (
|
|
|
160
162
|
],
|
|
161
163
|
temperature: 0.8,
|
|
162
164
|
maxOutputTokens: 400,
|
|
165
|
+
usageReason: 'newton_introduction',
|
|
166
|
+
attribution: {
|
|
167
|
+
userId: actingUserId,
|
|
168
|
+
classId: submission.assignment.classId,
|
|
169
|
+
},
|
|
163
170
|
});
|
|
164
171
|
|
|
165
172
|
if (!response) {
|
|
@@ -237,6 +244,7 @@ export const generateAndSendNewtonIntroduction = async (
|
|
|
237
244
|
};
|
|
238
245
|
},
|
|
239
246
|
aiMessageId: string,
|
|
247
|
+
actingUserId?: string | null,
|
|
240
248
|
): Promise<void> => {
|
|
241
249
|
try {
|
|
242
250
|
// Get recent conversation history (excluding the empty AI placeholder
|
|
@@ -339,6 +347,8 @@ export const generateAndSendNewtonIntroduction = async (
|
|
|
339
347
|
...(newtonClassId ? { tools: aiTools(newtonClassId), maxSteps: 5 } : {}),
|
|
340
348
|
temperature: 0.7,
|
|
341
349
|
maxOutputTokens: 1200,
|
|
350
|
+
usageReason: 'newton_response',
|
|
351
|
+
attribution: { userId: actingUserId, classId: newtonClassId },
|
|
342
352
|
});
|
|
343
353
|
|
|
344
354
|
if (!response) {
|