@studious-lms/server 1.13.0 → 1.15.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/.dockerignore +28 -0
- package/.env.example +5 -0
- package/.github/workflows/deploy.yml +123 -0
- package/Dockerfile +54 -0
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/instrument.js +3 -3
- package/dist/instrument.js.map +1 -1
- package/dist/lib/config/cors.d.ts.map +1 -1
- package/dist/lib/config/cors.js +31 -11
- package/dist/lib/config/cors.js.map +1 -1
- package/dist/lib/config/env.d.ts +17 -0
- package/dist/lib/config/env.d.ts.map +1 -1
- package/dist/lib/config/env.js +8 -2
- package/dist/lib/config/env.js.map +1 -1
- package/dist/lib/googleCloudStorage.d.ts +10 -0
- package/dist/lib/googleCloudStorage.d.ts.map +1 -1
- package/dist/lib/googleCloudStorage.js +22 -2
- package/dist/lib/googleCloudStorage.js.map +1 -1
- package/dist/lib/inferDocumentFormat.d.ts +10 -0
- package/dist/lib/inferDocumentFormat.d.ts.map +1 -0
- package/dist/lib/inferDocumentFormat.js +48 -0
- package/dist/lib/inferDocumentFormat.js.map +1 -0
- package/dist/lib/legal.d.ts +3 -0
- package/dist/lib/legal.d.ts.map +1 -0
- package/dist/lib/legal.js +6 -0
- package/dist/lib/legal.js.map +1 -0
- package/dist/lib/prisma.d.ts +104 -5
- package/dist/lib/prisma.d.ts.map +1 -1
- package/dist/lib/prisma.js +33 -2
- package/dist/lib/prisma.js.map +1 -1
- package/dist/models/agenda.d.ts +20 -20
- package/dist/models/announcement.d.ts +211 -101
- package/dist/models/announcement.d.ts.map +1 -1
- package/dist/models/assignment.d.ts +560 -362
- package/dist/models/assignment.d.ts.map +1 -1
- package/dist/models/attendance.d.ts +197 -71
- package/dist/models/attendance.d.ts.map +1 -1
- package/dist/models/auth.d.ts +356 -114
- package/dist/models/auth.d.ts.map +1 -1
- package/dist/models/auth.js +4 -2
- package/dist/models/auth.js.map +1 -1
- package/dist/models/class.d.ts +327 -201
- package/dist/models/class.d.ts.map +1 -1
- package/dist/models/class.js +2 -2
- package/dist/models/class.js.map +1 -1
- package/dist/models/cloudStorage.d.ts +146 -56
- package/dist/models/cloudStorage.d.ts.map +1 -1
- package/dist/models/comment.d.ts +262 -110
- package/dist/models/comment.d.ts.map +1 -1
- package/dist/models/conversation.d.ts +206 -80
- package/dist/models/conversation.d.ts.map +1 -1
- package/dist/models/department.d.ts +196 -70
- package/dist/models/department.d.ts.map +1 -1
- package/dist/models/event.d.ts +305 -141
- package/dist/models/event.d.ts.map +1 -1
- package/dist/models/file.d.ts +422 -272
- package/dist/models/file.d.ts.map +1 -1
- package/dist/models/folder.d.ts +433 -181
- package/dist/models/folder.d.ts.map +1 -1
- package/dist/models/labChat.d.ts +205 -79
- package/dist/models/labChat.d.ts.map +1 -1
- package/dist/models/marketing.d.ts +79 -38
- package/dist/models/marketing.d.ts.map +1 -1
- package/dist/models/marketing.js +12 -4
- package/dist/models/marketing.js.map +1 -1
- package/dist/models/message.d.ts +97 -43
- package/dist/models/message.d.ts.map +1 -1
- package/dist/models/newtonChat.d.ts +84 -30
- package/dist/models/newtonChat.d.ts.map +1 -1
- package/dist/models/notification.d.ts +57 -21
- package/dist/models/notification.d.ts.map +1 -1
- package/dist/models/school.d.ts +349 -133
- package/dist/models/school.d.ts.map +1 -1
- package/dist/models/section.d.ts +216 -72
- package/dist/models/section.d.ts.map +1 -1
- package/dist/models/tag.d.ts +156 -48
- package/dist/models/tag.d.ts.map +1 -1
- package/dist/models/user.d.ts +82 -28
- package/dist/models/user.d.ts.map +1 -1
- package/dist/models/worksheet.d.ts +1032 -423
- package/dist/models/worksheet.d.ts.map +1 -1
- package/dist/models/worksheet.js +8 -2
- package/dist/models/worksheet.js.map +1 -1
- package/dist/pipelines/aiLabChat.d.ts +7 -9
- package/dist/pipelines/aiLabChat.d.ts.map +1 -1
- package/dist/pipelines/aiLabChat.js +194 -125
- package/dist/pipelines/aiLabChat.js.map +1 -1
- package/dist/pipelines/aiLabChatContract.d.ts +233 -389
- package/dist/pipelines/aiLabChatContract.d.ts.map +1 -1
- package/dist/pipelines/aiLabChatContract.js +54 -70
- package/dist/pipelines/aiLabChatContract.js.map +1 -1
- package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
- package/dist/pipelines/aiNewtonChat.js +41 -41
- package/dist/pipelines/aiNewtonChat.js.map +1 -1
- package/dist/pipelines/gradeWorksheet.d.ts +8 -8
- package/dist/pipelines/labChatPrompt.d.ts +18 -11
- package/dist/pipelines/labChatPrompt.d.ts.map +1 -1
- package/dist/pipelines/labChatPrompt.js +93 -75
- package/dist/pipelines/labChatPrompt.js.map +1 -1
- package/dist/pipelines/pdfToWorksheet.d.ts +1 -1
- package/dist/routers/_app.d.ts +2601 -2151
- 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 +13 -13
- package/dist/routers/agenda.d.ts +18 -18
- package/dist/routers/announcement.d.ts +60 -60
- package/dist/routers/assignment.d.ts +393 -383
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/attendance.d.ts +10 -10
- package/dist/routers/auth.d.ts +4 -2
- package/dist/routers/auth.d.ts.map +1 -1
- package/dist/routers/auth.js +15 -2
- package/dist/routers/auth.js.map +1 -1
- package/dist/routers/class.d.ts +157 -153
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/comment.d.ts +18 -18
- package/dist/routers/conversation.d.ts +11 -11
- package/dist/routers/department.d.ts +34 -34
- package/dist/routers/discussion.d.ts +68 -56
- package/dist/routers/discussion.d.ts.map +1 -1
- package/dist/routers/discussion.js +6 -2
- package/dist/routers/discussion.js.map +1 -1
- package/dist/routers/embedding.d.ts +73 -0
- package/dist/routers/embedding.d.ts.map +1 -0
- package/dist/routers/embedding.js +151 -0
- package/dist/routers/embedding.js.map +1 -0
- package/dist/routers/event.d.ts +57 -55
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/file.d.ts +14 -14
- package/dist/routers/folder.d.ts +38 -38
- package/dist/routers/labChat.d.ts +16 -16
- package/dist/routers/marketing.d.ts +1 -14
- package/dist/routers/marketing.d.ts.map +1 -1
- package/dist/routers/message.d.ts +13 -13
- package/dist/routers/newtonChat.d.ts +4 -4
- package/dist/routers/notifications.d.ts +5 -5
- package/dist/routers/permissions.d.ts +14 -14
- package/dist/routers/school.d.ts +59 -55
- package/dist/routers/school.d.ts.map +1 -1
- package/dist/routers/search.d.ts +22 -1
- package/dist/routers/search.d.ts.map +1 -1
- package/dist/routers/search.js +9 -3
- package/dist/routers/search.js.map +1 -1
- package/dist/routers/section.d.ts +4 -4
- package/dist/routers/studentProgress.d.ts +1 -1
- package/dist/routers/tag.d.ts +7 -7
- package/dist/routers/user.d.ts +99 -10
- package/dist/routers/user.d.ts.map +1 -1
- package/dist/routers/user.js +19 -2
- package/dist/routers/user.js.map +1 -1
- package/dist/routers/worksheet.d.ts +93 -55
- package/dist/routers/worksheet.d.ts.map +1 -1
- package/dist/routers/worksheet.js +26 -2
- package/dist/routers/worksheet.js.map +1 -1
- package/dist/routes/aiStream.d.ts +2 -0
- package/dist/routes/aiStream.d.ts.map +1 -0
- package/dist/routes/aiStream.js +726 -0
- package/dist/routes/aiStream.js.map +1 -0
- package/dist/seedDatabase.d.ts +3 -1
- package/dist/seedDatabase.d.ts.map +1 -1
- package/dist/seedDatabase.js +6 -6
- package/dist/seedDatabase.js.map +1 -1
- package/dist/services/adminInstitution.d.ts +8 -8
- package/dist/services/adminRunJob.d.ts +4 -4
- package/dist/services/agenda.d.ts +18 -18
- package/dist/services/aiChatStream.d.ts +88 -0
- package/dist/services/aiChatStream.d.ts.map +1 -0
- package/dist/services/aiChatStream.js +254 -0
- package/dist/services/aiChatStream.js.map +1 -0
- package/dist/services/aiModifierTools.d.ts +16 -0
- package/dist/services/aiModifierTools.d.ts.map +1 -0
- package/dist/services/aiModifierTools.js +274 -0
- package/dist/services/aiModifierTools.js.map +1 -0
- package/dist/services/aiProposalTools.d.ts +32 -0
- package/dist/services/aiProposalTools.d.ts.map +1 -0
- package/dist/services/aiProposalTools.js +207 -0
- package/dist/services/aiProposalTools.js.map +1 -0
- package/dist/services/aiStream.d.ts +22 -0
- package/dist/services/aiStream.d.ts.map +1 -0
- package/dist/services/aiStream.js +142 -0
- package/dist/services/aiStream.js.map +1 -0
- package/dist/services/aiTools.d.ts +16 -0
- package/dist/services/aiTools.d.ts.map +1 -0
- package/dist/services/aiTools.js +271 -0
- package/dist/services/aiTools.js.map +1 -0
- package/dist/services/announcement.d.ts +42 -42
- 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 +355 -345
- package/dist/services/assignment.d.ts.map +1 -1
- package/dist/services/assignment.js +30 -9
- package/dist/services/assignment.js.map +1 -1
- package/dist/services/attendance.d.ts +10 -10
- package/dist/services/auth.d.ts +4 -2
- package/dist/services/auth.d.ts.map +1 -1
- package/dist/services/auth.js +13 -4
- package/dist/services/auth.js.map +1 -1
- package/dist/services/class.d.ts +146 -142
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/class.js +24 -4
- package/dist/services/class.js.map +1 -1
- package/dist/services/comment.d.ts +18 -18
- package/dist/services/conversation.d.ts +11 -11
- package/dist/services/department.d.ts +33 -33
- package/dist/services/discussion.d.ts +58 -58
- package/dist/services/discussion.d.ts.map +1 -1
- package/dist/services/discussion.js +15 -8
- package/dist/services/discussion.js.map +1 -1
- package/dist/services/discussionSummary.d.ts +21 -0
- package/dist/services/discussionSummary.d.ts.map +1 -0
- package/dist/services/discussionSummary.js +179 -0
- package/dist/services/discussionSummary.js.map +1 -0
- package/dist/services/embedding.d.ts +85 -0
- package/dist/services/embedding.d.ts.map +1 -0
- package/dist/services/embedding.js +372 -0
- package/dist/services/embedding.js.map +1 -0
- package/dist/services/event.d.ts +55 -53
- package/dist/services/event.d.ts.map +1 -1
- package/dist/services/file.d.ts +28 -28
- package/dist/services/folder.d.ts +36 -36
- package/dist/services/folder.d.ts.map +1 -1
- package/dist/services/folder.js +32 -2
- package/dist/services/folder.js.map +1 -1
- package/dist/services/labChat.d.ts +16 -16
- package/dist/services/marketing.d.ts +2 -15
- package/dist/services/marketing.d.ts.map +1 -1
- package/dist/services/marketing.js +5 -5
- package/dist/services/marketing.js.map +1 -1
- package/dist/services/message.d.ts +11 -11
- package/dist/services/message.d.ts.map +1 -1
- package/dist/services/message.js +2 -2
- package/dist/services/message.js.map +1 -1
- package/dist/services/newtonChat.d.ts +4 -4
- package/dist/services/notification.d.ts +5 -5
- package/dist/services/notification.d.ts.map +1 -1
- package/dist/services/openaiDocumentGen.d.ts +37 -0
- package/dist/services/openaiDocumentGen.d.ts.map +1 -0
- package/dist/services/openaiDocumentGen.js +262 -0
- package/dist/services/openaiDocumentGen.js.map +1 -0
- package/dist/services/permissions.d.ts +14 -14
- package/dist/services/school.d.ts +54 -50
- package/dist/services/school.d.ts.map +1 -1
- package/dist/services/search.d.ts +13 -1
- package/dist/services/search.d.ts.map +1 -1
- package/dist/services/search.js +145 -2
- package/dist/services/search.js.map +1 -1
- package/dist/services/section.d.ts +3 -3
- package/dist/services/tag.d.ts +4 -4
- package/dist/services/user.d.ts +4 -4
- package/dist/services/user.d.ts.map +1 -1
- package/dist/services/user.js +10 -15
- package/dist/services/user.js.map +1 -1
- package/dist/services/userPrivacy.d.ts +86 -0
- package/dist/services/userPrivacy.d.ts.map +1 -0
- package/dist/services/userPrivacy.js +184 -0
- package/dist/services/userPrivacy.js.map +1 -0
- package/dist/services/worksheet.d.ts +86 -57
- package/dist/services/worksheet.d.ts.map +1 -1
- package/dist/services/worksheet.js +57 -5
- package/dist/services/worksheet.js.map +1 -1
- package/dist/utils/email.d.ts.map +1 -1
- package/dist/utils/email.js +7 -3
- package/dist/utils/email.js.map +1 -1
- package/dist/utils/streamBroadcast.d.ts +11 -0
- package/dist/utils/streamBroadcast.d.ts.map +1 -0
- package/dist/utils/streamBroadcast.js +32 -0
- package/dist/utils/streamBroadcast.js.map +1 -0
- package/dist/workers/connection.d.ts +15 -0
- package/dist/workers/connection.d.ts.map +1 -1
- package/dist/workers/connection.js +20 -2
- package/dist/workers/connection.js.map +1 -1
- package/dist/workers/index.js +4 -2
- package/dist/workers/index.js.map +1 -1
- package/dist/workers/processors/createRunJobWorker.d.ts.map +1 -1
- package/dist/workers/processors/createRunJobWorker.js +31 -8
- package/dist/workers/processors/createRunJobWorker.js.map +1 -1
- package/dist/workers/processors/embedContent.d.ts +8 -0
- package/dist/workers/processors/embedContent.d.ts.map +1 -0
- package/dist/workers/processors/embedContent.js +67 -0
- package/dist/workers/processors/embedContent.js.map +1 -0
- package/dist/workers/queues.d.ts +6 -0
- package/dist/workers/queues.d.ts.map +1 -1
- package/dist/workers/queues.js +5 -3
- package/dist/workers/queues.js.map +1 -1
- package/package.json +5 -1
- package/prisma/migrations/20260609215135_init/migration.sql +1412 -0
- package/prisma/migrations/20260610130000_add_worksheet_tags/migration.sql +13 -0
- package/prisma/migrations/20260610140000_expand_embedding_source_types/migration.sql +4 -0
- package/prisma/migrations/20260611120000_add_user_terms_acceptance/migration.sql +3 -0
- package/prisma/schema.prisma +44 -7
- package/src/index.ts +4 -0
- package/src/instrument.ts +1 -1
- package/src/lib/config/cors.ts +32 -9
- package/src/lib/config/env.ts +6 -0
- package/src/lib/googleCloudStorage.ts +21 -0
- package/src/lib/inferDocumentFormat.ts +58 -0
- package/src/lib/legal.ts +2 -0
- package/src/lib/prisma.ts +40 -1
- package/src/models/auth.ts +4 -0
- package/src/models/class.ts +3 -2
- package/src/models/marketing.ts +10 -2
- package/src/models/worksheet.ts +5 -2
- package/src/pipelines/aiLabChat.ts +245 -146
- package/src/pipelines/aiLabChatContract.ts +59 -69
- package/src/pipelines/aiNewtonChat.ts +46 -43
- package/src/pipelines/labChatPrompt.ts +115 -90
- package/src/routers/_app.ts +2 -0
- package/src/routers/auth.ts +13 -0
- package/src/routers/discussion.ts +7 -0
- package/src/routers/embedding.ts +164 -0
- package/src/routers/search.ts +10 -1
- package/src/routers/user.ts +26 -0
- package/src/routers/worksheet.ts +27 -0
- package/src/routes/aiStream.ts +827 -0
- package/src/seedDatabase.ts +4 -4
- package/src/services/aiChatStream.ts +341 -0
- package/src/services/aiModifierTools.ts +341 -0
- package/src/services/aiProposalTools.ts +274 -0
- package/src/services/aiStream.ts +175 -0
- package/src/services/aiTools.ts +290 -0
- package/src/services/announcement.ts +7 -0
- package/src/services/assignment.ts +31 -7
- package/src/services/auth.ts +14 -2
- package/src/services/class.ts +27 -2
- package/src/services/discussion.ts +22 -8
- package/src/services/discussionSummary.ts +226 -0
- package/src/services/embedding.ts +446 -0
- package/src/services/folder.ts +31 -0
- package/src/services/marketing.ts +3 -3
- package/src/services/message.ts +2 -2
- package/src/services/openaiDocumentGen.ts +319 -0
- package/src/services/search.ts +163 -1
- package/src/services/user.ts +9 -17
- package/src/services/userPrivacy.ts +196 -0
- package/src/services/worksheet.ts +77 -3
- package/src/utils/email.ts +5 -1
- package/src/utils/streamBroadcast.ts +48 -0
- package/src/workers/connection.ts +19 -0
- package/src/workers/index.ts +2 -0
- package/src/workers/processors/createRunJobWorker.ts +32 -6
- package/src/workers/processors/embedContent.ts +86 -0
- package/src/workers/queues.ts +5 -1
- package/tests/helpers/registerInput.ts +20 -0
- package/tests/integration/globalSetup.ts +10 -18
- package/tests/integration/routers/auth.test.ts +13 -18
- package/tests/unit/lib/inferDocumentFormat.test.ts +33 -0
- package/tests/unit/pipelines/aiLabChatContract.test.ts +38 -21
- package/tests/unit/pipelines/labChatPrompt.test.ts +23 -36
- package/tests/unit/services/aiChatStream.test.ts +92 -0
- package/tests/unit/services/openaiDocumentGen.test.ts +65 -0
- package/prisma/migrations/20250807062924_init/migration.sql +0 -436
- package/prisma/migrations/20250919063704_init2/migration.sql +0 -24
- package/prisma/migrations/20250920143543_add_profile_fields/migration.sql +0 -15
- package/prisma/migrations/20250922165147_add_chat_models/migration.sql +0 -68
- package/prisma/migrations/20250922165314_add_chat_roles/migration.sql +0 -5
- package/prisma/migrations/20250922171242_refactor_notification_system/migration.sql +0 -20
- package/prisma/migrations/20250922172208_add_mentions_system/migration.sql +0 -21
- package/prisma/migrations/20250925072732_add_lab_chat_system/migration.sql +0 -25
- package/prisma/migrations/20251020151505_add_upload_tracking_fields/migration.sql +0 -57
- package/prisma/migrations/20251109122857_annuoncements_comments/migration.sql +0 -30
- package/prisma/migrations/20251109135555_reactions_announcements_comments/migration.sql +0 -35
- package/prisma/migrations/20260410124000_add_submission_recommendation_state/migration.sql +0 -14
- package/prisma/migrations/20260415103500_add_session_type/migration.sql +0 -33
- package/prisma/migrations/20260415133000_add_ai_usage_logs/migration.sql +0 -19
- package/prisma/migrations/20260417083000_add_conversation_member_hidden_at/migration.sql +0 -2
- package/prisma/migrations/20260502230500_assignment_submission_comments/migration.sql +0 -60
- package/prisma/migrations/20260504071500_school_memberships/migration.sql +0 -81
- package/prisma/migrations/20260506162500_consolidate_run_job_and_unify_job_status/migration.sql +0 -166
- package/prisma/migrations/20260509220000_institution_overhaul/migration.sql +0 -184
- package/prisma/migrations/20260510170000_add_folder_permissions/migration.sql +0 -93
- package/prisma/migrations/20260510183000_add_discussions/migration.sql +0 -151
- /package/.github/workflows/{npm.yml → npm-publish.yml} +0 -0
|
@@ -1,23 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AI lab chat pipeline – generates lab introductions and responses.
|
|
3
|
-
* Can create worksheets, sections, assignments, and
|
|
3
|
+
* Can create worksheets, sections, assignments, and generated files from AI output.
|
|
4
4
|
*/
|
|
5
5
|
import { isAIUser } from "../utils/aiUser.js";
|
|
6
6
|
import { prisma } from "../lib/prisma.js";
|
|
7
|
-
import { pusher, teacherChannel } from "../lib/pusher.js";
|
|
7
|
+
import { pusher, chatChannel, teacherChannel } from "../lib/pusher.js";
|
|
8
8
|
import type { Assignment, Class, File, Section, User } from "@prisma/client";
|
|
9
9
|
import {
|
|
10
|
-
inference,
|
|
11
10
|
completePendingAIMessage,
|
|
12
11
|
failPendingAIMessage,
|
|
13
12
|
} from "../utils/inference.js";
|
|
14
13
|
import { logger } from "../utils/logger.js";
|
|
15
|
-
import { createPdf } from "../lib/jsonConversion.js";
|
|
16
14
|
import { v4 } from "uuid";
|
|
17
|
-
import { bucket } from "../lib/googleCloudStorage.js";
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
import { bucket, getSignedUrl } from "../lib/googleCloudStorage.js";
|
|
16
|
+
import {
|
|
17
|
+
generateDocumentWithOpenAI,
|
|
18
|
+
normalizeDocumentFormat,
|
|
19
|
+
} from "../services/openaiDocumentGen.js";
|
|
20
|
+
import { inferDocumentFormat } from "../lib/inferDocumentFormat.js";
|
|
21
|
+
import { broadcastTextChunks, broadcastStreamComplete } from "../utils/streamBroadcast.js";
|
|
22
|
+
import { buildLabChatAgentMessages } from "./labChatPrompt.js";
|
|
23
|
+
import type { ConversationMessage } from "./labChatPrompt.js";
|
|
24
|
+
import {
|
|
25
|
+
applyResolvedFormatsToStreamParts,
|
|
26
|
+
enrichStreamPartsWithGeneratedFiles,
|
|
27
|
+
finalizeDeferredToolParts,
|
|
28
|
+
streamChatResponse,
|
|
29
|
+
type GeneratedDocFileLink,
|
|
30
|
+
type StreamPart,
|
|
31
|
+
} from "../services/aiChatStream.js";
|
|
32
|
+
import { aiTools } from "../services/aiTools.js";
|
|
33
|
+
import { labProposalTools, createLabProposals } from "../services/aiProposalTools.js";
|
|
21
34
|
|
|
22
35
|
/** Cap model wait time; leave headroom after inference for PDF/GCS/DB within Vercel maxDuration (500s). */
|
|
23
36
|
const LAB_CHAT_RESPONSE_TIMEOUT_MS = 400_000;
|
|
@@ -72,6 +85,12 @@ type RecentLabChatMessage = {
|
|
|
72
85
|
displayName: string | null;
|
|
73
86
|
} | null;
|
|
74
87
|
} | null;
|
|
88
|
+
attachments: {
|
|
89
|
+
id: string;
|
|
90
|
+
name: string;
|
|
91
|
+
type: string;
|
|
92
|
+
path: string;
|
|
93
|
+
}[];
|
|
75
94
|
};
|
|
76
95
|
|
|
77
96
|
/**
|
|
@@ -116,6 +135,14 @@ const loadRecentLabChatMessages = async (
|
|
|
116
135
|
},
|
|
117
136
|
},
|
|
118
137
|
},
|
|
138
|
+
attachments: {
|
|
139
|
+
select: {
|
|
140
|
+
id: true,
|
|
141
|
+
name: true,
|
|
142
|
+
type: true,
|
|
143
|
+
path: true,
|
|
144
|
+
},
|
|
145
|
+
},
|
|
119
146
|
};
|
|
120
147
|
|
|
121
148
|
const newestMessages = await prisma.message.findMany({
|
|
@@ -186,6 +213,42 @@ const loadRecentLabChatMessages = async (
|
|
|
186
213
|
.slice(0, limit);
|
|
187
214
|
};
|
|
188
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Resolves signed URLs for image attachments in messages so the LLM can see them.
|
|
218
|
+
* Non-image files are passed as text descriptions only.
|
|
219
|
+
*/
|
|
220
|
+
async function resolveAttachmentUrls(
|
|
221
|
+
messages: RecentLabChatMessage[],
|
|
222
|
+
): Promise<ConversationMessage[]> {
|
|
223
|
+
const resolved: ConversationMessage[] = [];
|
|
224
|
+
|
|
225
|
+
for (const msg of messages) {
|
|
226
|
+
const attachments = msg.attachments ?? [];
|
|
227
|
+
if (attachments.length === 0) {
|
|
228
|
+
resolved.push({ ...msg, attachments: [] });
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const withUrls = await Promise.all(
|
|
233
|
+
attachments.map(async (a) => {
|
|
234
|
+
if (a.type.startsWith('image/')) {
|
|
235
|
+
try {
|
|
236
|
+
const url = await getSignedUrl(a.path);
|
|
237
|
+
return { id: a.id, name: a.name, type: a.type, url };
|
|
238
|
+
} catch {
|
|
239
|
+
return { id: a.id, name: a.name, type: a.type };
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return { id: a.id, name: a.name, type: a.type };
|
|
243
|
+
}),
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
resolved.push({ ...msg, attachments: withUrls });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return resolved;
|
|
250
|
+
}
|
|
251
|
+
|
|
189
252
|
/**
|
|
190
253
|
* Builds schema-aware context for the AI from class data.
|
|
191
254
|
* Formats entities with IDs so the model can reference them when creating assignments.
|
|
@@ -303,33 +366,6 @@ export const getBaseSystemPrompt = (
|
|
|
303
366
|
|
|
304
367
|
|
|
305
368
|
|
|
306
|
-
/**
|
|
307
|
-
* Generate labchat responses
|
|
308
|
-
* Allow for the generation of the following:
|
|
309
|
-
* - Assignment(s) either individual or bulk as an lesson / course plan.
|
|
310
|
-
* - Worksheet(s) either individual or bulk as an lesson / course plan.
|
|
311
|
-
* - Files (PDFs)
|
|
312
|
-
* @param labChatId
|
|
313
|
-
*/
|
|
314
|
-
// export const sendAiLabChatResponsePipeline = async (labChatId: string) => {
|
|
315
|
-
// const message = await prisma?.message.create({
|
|
316
|
-
// data: {
|
|
317
|
-
// content: "GENERATING_CONTENT",
|
|
318
|
-
// senderId: getAIUserId(),
|
|
319
|
-
// conversationId: labChatId,
|
|
320
|
-
// status: JobStatus.PENDING,
|
|
321
|
-
// },
|
|
322
|
-
// });
|
|
323
|
-
|
|
324
|
-
// try {
|
|
325
|
-
|
|
326
|
-
// inference(`
|
|
327
|
-
// `)
|
|
328
|
-
// }
|
|
329
|
-
|
|
330
|
-
// };
|
|
331
|
-
|
|
332
|
-
|
|
333
369
|
/**
|
|
334
370
|
* Fill the AI introduction placeholder for a brand-new lab chat.
|
|
335
371
|
*
|
|
@@ -345,9 +381,10 @@ export const generateAndSendLabIntroduction = async (
|
|
|
345
381
|
aiMessageId: string,
|
|
346
382
|
): Promise<void> => {
|
|
347
383
|
const introMessage =
|
|
348
|
-
"Hello teaching team! I'm your AI assistant for course material development. I
|
|
384
|
+
"Hello teaching team! I'm your AI assistant for course material development. I can create worksheets, assignments, and downloadable files — PDFs, Word docs, PowerPoints, spreadsheets, and more. When you have a clear request, I'll produce it directly. I only ask questions when I need to clarify the topic or scope. What would you like to work on?";
|
|
349
385
|
|
|
350
386
|
try {
|
|
387
|
+
await broadcastTextChunks(conversationId, aiMessageId, introMessage);
|
|
351
388
|
await completePendingAIMessage(aiMessageId, introMessage);
|
|
352
389
|
logger.info('AI Introduction sent', { labChatId, conversationId, aiMessageId });
|
|
353
390
|
} catch (error) {
|
|
@@ -376,7 +413,7 @@ export const generateAndSendLabIntroduction = async (
|
|
|
376
413
|
*/
|
|
377
414
|
export const generateAndSendLabResponse = async (
|
|
378
415
|
labChatId: string,
|
|
379
|
-
|
|
416
|
+
teacherMessage: string,
|
|
380
417
|
emitOptions: { classId: string; aiMessageId: string }
|
|
381
418
|
): Promise<void> => {
|
|
382
419
|
try {
|
|
@@ -474,133 +511,195 @@ export const generateAndSendLabIntroduction = async (
|
|
|
474
511
|
assignments: classData.assignments,
|
|
475
512
|
});
|
|
476
513
|
|
|
477
|
-
|
|
514
|
+
// Resolve signed URLs for image attachments so the LLM can see them
|
|
515
|
+
const messagesWithAttachments = await resolveAttachmentUrls(recentMessages.reverse());
|
|
516
|
+
|
|
517
|
+
const { system, messages } = buildLabChatAgentMessages({
|
|
478
518
|
context: fullLabChat.context,
|
|
479
519
|
classContext,
|
|
480
|
-
recentMessages:
|
|
520
|
+
recentMessages: messagesWithAttachments,
|
|
481
521
|
isAIUser,
|
|
482
522
|
});
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
labChatId,
|
|
523
|
+
|
|
524
|
+
// Genuine agentic generation: the model streams prose token-by-token and
|
|
525
|
+
// calls real tools inline — read-only RAG lookups plus "propose_*" tools
|
|
526
|
+
// that record create-actions into `proposals`. `streamChatResponse`
|
|
527
|
+
// broadcasts the real text chunks and tool-call events as they happen.
|
|
528
|
+
const proposals = createLabProposals();
|
|
529
|
+
const tools = {
|
|
530
|
+
...aiTools(fullLabChat.classId),
|
|
531
|
+
...labProposalTools(proposals),
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
const { text: streamedText, parts: rawStreamParts } = await withTimeout(
|
|
535
|
+
streamChatResponse({
|
|
497
536
|
conversationId,
|
|
498
|
-
classId: fullLabChat.classId,
|
|
499
537
|
aiMessageId: emitOptions.aiMessageId,
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
538
|
+
system,
|
|
539
|
+
messages,
|
|
540
|
+
tools,
|
|
541
|
+
temperature: 0.7,
|
|
542
|
+
maxOutputTokens: 4096,
|
|
543
|
+
maxSteps: 8,
|
|
544
|
+
}),
|
|
545
|
+
LAB_CHAT_RESPONSE_TIMEOUT_MS,
|
|
546
|
+
"Lab chat response generation",
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
// Real text + tool-call events were already broadcast inline (in order)
|
|
550
|
+
// during the stream by `streamChatResponse`. Just close the stream.
|
|
551
|
+
await broadcastStreamComplete(conversationId, emitOptions.aiMessageId);
|
|
552
|
+
|
|
553
|
+
// `generate_document` is an inline tool that only QUEUES into `proposals.docs`
|
|
554
|
+
// during the stream. OpenAI Code Interpreter renders files here, after the
|
|
555
|
+
// stream ends, then we link them to this message.
|
|
510
556
|
try {
|
|
511
|
-
const jsonData = response;
|
|
512
|
-
|
|
513
|
-
|
|
514
557
|
const attachmentIds: string[] = [];
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
const file = await prisma.file.create({
|
|
552
|
-
data: {
|
|
553
|
-
name: filename,
|
|
554
|
-
path: filePath,
|
|
555
|
-
type: 'application/pdf',
|
|
556
|
-
size: pdfBytes.length,
|
|
557
|
-
userId: fullLabChat.createdById,
|
|
558
|
-
uploadStatus: 'COMPLETED',
|
|
559
|
-
uploadedAt: new Date(),
|
|
560
|
-
},
|
|
561
|
-
});
|
|
562
|
-
attachmentIds.push(file.id);
|
|
563
|
-
} else {
|
|
564
|
-
logger.error(`PDF ${i + 1} creation returned undefined/null`, { labChatId, title: doc.title });
|
|
565
|
-
}
|
|
566
|
-
} catch (pdfError) {
|
|
567
|
-
logger.error(`PDF creation threw an error for document ${i + 1}:`, {
|
|
568
|
-
error: pdfError instanceof Error ? {
|
|
569
|
-
message: pdfError.message,
|
|
570
|
-
stack: pdfError.stack,
|
|
571
|
-
name: pdfError.name
|
|
572
|
-
} : pdfError,
|
|
558
|
+
const generatedDocFiles: GeneratedDocFileLink[] = [];
|
|
559
|
+
const resolvedDocs: Array<{ title: string; format: string }> = [];
|
|
560
|
+
|
|
561
|
+
for (let i = 0; i < proposals.docs.length; i++) {
|
|
562
|
+
const doc = proposals.docs[i];
|
|
563
|
+
if (!doc.title || !doc.content?.trim()) {
|
|
564
|
+
logger.error(`Document ${i + 1} is missing title or content`, { labChatId, doc });
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const format = inferDocumentFormat(
|
|
569
|
+
doc.title,
|
|
570
|
+
doc.content,
|
|
571
|
+
doc.format,
|
|
572
|
+
teacherMessage,
|
|
573
|
+
);
|
|
574
|
+
resolvedDocs.push({ title: doc.title, format });
|
|
575
|
+
|
|
576
|
+
if (format !== normalizeDocumentFormat(doc.format)) {
|
|
577
|
+
logger.info('Document format overridden from model default', {
|
|
578
|
+
labChatId,
|
|
579
|
+
title: doc.title,
|
|
580
|
+
modelFormat: doc.format,
|
|
581
|
+
resolvedFormat: format,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
try {
|
|
586
|
+
const generated = await generateDocumentWithOpenAI({
|
|
587
|
+
title: doc.title,
|
|
588
|
+
content: doc.content,
|
|
589
|
+
format,
|
|
590
|
+
});
|
|
591
|
+
if (!generated) {
|
|
592
|
+
logger.error(`Document ${i + 1} generation returned null`, {
|
|
573
593
|
labChatId,
|
|
574
|
-
title: doc.title
|
|
594
|
+
title: doc.title,
|
|
595
|
+
format,
|
|
575
596
|
});
|
|
597
|
+
continue;
|
|
576
598
|
}
|
|
599
|
+
|
|
600
|
+
const sanitizedTitle = doc.title
|
|
601
|
+
.replace(/[^a-zA-Z0-9\s\-_]/g, '')
|
|
602
|
+
.replace(/\s+/g, '_')
|
|
603
|
+
.substring(0, 50);
|
|
604
|
+
const filename = `${sanitizedTitle}_${v4().substring(0, 8)}.${generated.format}`;
|
|
605
|
+
const filePath = `class/generated/${fullLabChat.classId}/${filename}`;
|
|
606
|
+
|
|
607
|
+
logger.info(`Document ${i + 1} generated successfully`, {
|
|
608
|
+
labChatId,
|
|
609
|
+
title: doc.title,
|
|
610
|
+
format: generated.format,
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
await bucket.file(filePath).save(generated.bytes, {
|
|
614
|
+
metadata: { contentType: generated.mimeType },
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
logger.info(`Document ${i + 1} uploaded successfully`, { labChatId, filename });
|
|
618
|
+
|
|
619
|
+
const file = await prisma.file.create({
|
|
620
|
+
data: {
|
|
621
|
+
name: filename,
|
|
622
|
+
path: filePath,
|
|
623
|
+
type: generated.mimeType,
|
|
624
|
+
size: generated.bytes.length,
|
|
625
|
+
userId: fullLabChat.createdById,
|
|
626
|
+
conversationId,
|
|
627
|
+
messageId: emitOptions.aiMessageId,
|
|
628
|
+
uploadStatus: 'COMPLETED',
|
|
629
|
+
uploadedAt: new Date(),
|
|
630
|
+
},
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
attachmentIds.push(file.id);
|
|
634
|
+
generatedDocFiles.push({
|
|
635
|
+
title: doc.title,
|
|
636
|
+
fileId: file.id,
|
|
637
|
+
fileName: filename,
|
|
638
|
+
format: generated.format,
|
|
639
|
+
});
|
|
640
|
+
} catch (docError) {
|
|
641
|
+
logger.error(`Document generation failed for item ${i + 1}:`, {
|
|
642
|
+
error: docError instanceof Error ? {
|
|
643
|
+
message: docError.message,
|
|
644
|
+
stack: docError.stack,
|
|
645
|
+
name: docError.name,
|
|
646
|
+
} : docError,
|
|
647
|
+
labChatId,
|
|
648
|
+
title: doc.title,
|
|
649
|
+
format: doc.format,
|
|
650
|
+
});
|
|
577
651
|
}
|
|
578
652
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
653
|
+
|
|
654
|
+
const streamParts: StreamPart[] = finalizeDeferredToolParts(
|
|
655
|
+
enrichStreamPartsWithGeneratedFiles(
|
|
656
|
+
applyResolvedFormatsToStreamParts(rawStreamParts, resolvedDocs),
|
|
657
|
+
generatedDocFiles,
|
|
658
|
+
),
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
// While the message is still PENDING, resolve deferred tool indicators
|
|
662
|
+
// (spinner → attachment or error) before the final persist event.
|
|
663
|
+
for (const part of streamParts) {
|
|
664
|
+
if (part.type !== 'tool' || part.toolName !== 'generate_document') {
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
if (part.status === 'executing') continue;
|
|
668
|
+
await pusher
|
|
669
|
+
.trigger(chatChannel(conversationId), 'message-tool-call', {
|
|
670
|
+
messageId: emitOptions.aiMessageId,
|
|
671
|
+
toolCallId: part.toolCallId,
|
|
672
|
+
toolName: part.toolName,
|
|
673
|
+
...(part.title ? { title: part.title } : {}),
|
|
674
|
+
...(part.format ? { format: part.format } : {}),
|
|
675
|
+
...(part.fileId ? { fileId: part.fileId, fileName: part.fileName } : {}),
|
|
676
|
+
status: part.status,
|
|
677
|
+
})
|
|
678
|
+
.catch(() => {});
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
await completePendingAIMessage(emitOptions.aiMessageId, streamedText, {
|
|
682
|
+
...(attachmentIds.length > 0 && {
|
|
683
|
+
attachments: { connect: attachmentIds.map((id) => ({ id })) },
|
|
684
|
+
}),
|
|
584
685
|
meta: {
|
|
585
|
-
assignmentsToCreate:
|
|
586
|
-
...assignment,
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
id: v4(),
|
|
596
|
-
})) || null,
|
|
686
|
+
assignmentsToCreate: proposals.assignments.length > 0
|
|
687
|
+
? proposals.assignments.map(assignment => ({ ...assignment, id: v4() }))
|
|
688
|
+
: null,
|
|
689
|
+
worksheetsToCreate: proposals.worksheets.length > 0
|
|
690
|
+
? proposals.worksheets.map(worksheet => ({ ...worksheet, id: v4() }))
|
|
691
|
+
: null,
|
|
692
|
+
sectionsToCreate: proposals.sections.length > 0
|
|
693
|
+
? proposals.sections.map(section => ({ ...section, id: v4() }))
|
|
694
|
+
: null,
|
|
695
|
+
streamParts: streamParts.length > 0 ? streamParts : null,
|
|
597
696
|
},
|
|
598
697
|
});
|
|
599
|
-
} catch (
|
|
600
|
-
logger.error('Failed to
|
|
698
|
+
} catch (persistError) {
|
|
699
|
+
logger.error('Failed to persist AI response or generate PDF:', { error: persistError, labChatId });
|
|
601
700
|
// Fallback: complete the placeholder with the raw text so the
|
|
602
701
|
// teacher still sees something useful instead of FAILED.
|
|
603
|
-
await completePendingAIMessage(emitOptions.aiMessageId,
|
|
702
|
+
await completePendingAIMessage(emitOptions.aiMessageId, streamedText);
|
|
604
703
|
}
|
|
605
704
|
|
|
606
705
|
try {
|
|
@@ -1,75 +1,65 @@
|
|
|
1
1
|
import z from "zod";
|
|
2
2
|
|
|
3
|
-
export const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
description: z.string(),
|
|
20
|
-
})).optional().default([]),
|
|
21
|
-
points: z.number().optional().default(0),
|
|
22
|
-
order: z.number(),
|
|
23
|
-
})),
|
|
24
|
-
})).default([]),
|
|
25
|
-
sectionsToCreate: z.array(z.object({
|
|
26
|
-
name: z.string(),
|
|
27
|
-
color: z.string().regex(/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/).nullable().optional(),
|
|
28
|
-
})).default([]),
|
|
29
|
-
assignmentsToCreate: z.array(z.object({
|
|
30
|
-
title: z.string(),
|
|
31
|
-
instructions: z.string(),
|
|
32
|
-
dueDate: z.string().datetime(),
|
|
33
|
-
acceptFiles: z.boolean(),
|
|
34
|
-
acceptExtendedResponse: z.boolean(),
|
|
35
|
-
acceptWorksheet: z.boolean(),
|
|
36
|
-
maxGrade: z.number(),
|
|
37
|
-
gradingBoundaryId: z.string().nullable().optional(),
|
|
38
|
-
markSchemeId: z.string().nullable().optional(),
|
|
39
|
-
worksheetIds: z.array(z.string()),
|
|
40
|
-
studentIds: z.array(z.string()),
|
|
41
|
-
sectionId: z.string().nullable().optional(),
|
|
42
|
-
type: z.enum(["HOMEWORK", "QUIZ", "TEST", "PROJECT", "ESSAY", "DISCUSSION", "PRESENTATION", "LAB", "OTHER"]),
|
|
43
|
-
attachments: z.array(z.object({
|
|
44
|
-
id: z.string(),
|
|
45
|
-
})),
|
|
46
|
-
})).nullable().optional(),
|
|
47
|
-
docs: z.array(z.object({
|
|
48
|
-
title: z.string(),
|
|
49
|
-
blocks: z.array(z.object({
|
|
50
|
-
format: z.number().int().min(0).max(12),
|
|
51
|
-
content: z.union([z.string(), z.array(z.string())]),
|
|
52
|
-
metadata: z.object({
|
|
53
|
-
fontSize: z.number().min(6).nullable().optional(),
|
|
54
|
-
lineHeight: z.number().min(0.6).nullable().optional(),
|
|
55
|
-
paragraphSpacing: z.number().min(0).nullable().optional(),
|
|
56
|
-
indentWidth: z.number().min(0).nullable().optional(),
|
|
57
|
-
paddingX: z.number().min(0).nullable().optional(),
|
|
58
|
-
paddingY: z.number().min(0).nullable().optional(),
|
|
59
|
-
font: z.number().int().min(0).max(5).nullable().optional(),
|
|
60
|
-
color: z.string().regex(/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/).nullable().optional(),
|
|
61
|
-
background: z.string().regex(/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/).nullable().optional(),
|
|
62
|
-
align: z.enum(["left", "center", "right"]).nullable().optional(),
|
|
63
|
-
}).nullable().optional(),
|
|
64
|
-
})),
|
|
65
|
-
})).nullable().optional(),
|
|
3
|
+
export const worksheetQuestionSchema = z.object({
|
|
4
|
+
type: z.enum(["MULTIPLE_CHOICE", "TRUE_FALSE", "SHORT_ANSWER", "LONG_ANSWER", "MATH_EXPRESSION", "ESSAY"]),
|
|
5
|
+
question: z.string(),
|
|
6
|
+
answer: z.string(),
|
|
7
|
+
options: z.array(z.object({
|
|
8
|
+
id: z.string(),
|
|
9
|
+
text: z.string(),
|
|
10
|
+
isCorrect: z.boolean(),
|
|
11
|
+
})).optional().default([]),
|
|
12
|
+
markScheme: z.array(z.object({
|
|
13
|
+
id: z.string(),
|
|
14
|
+
points: z.number(),
|
|
15
|
+
description: z.string(),
|
|
16
|
+
})).optional().default([]),
|
|
17
|
+
points: z.number().optional().default(0),
|
|
18
|
+
order: z.number(),
|
|
66
19
|
});
|
|
67
20
|
|
|
68
|
-
export
|
|
21
|
+
export const worksheetToCreateSchema = z.object({
|
|
22
|
+
title: z.string(),
|
|
23
|
+
questions: z.array(worksheetQuestionSchema),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const sectionToCreateSchema = z.object({
|
|
27
|
+
name: z.string(),
|
|
28
|
+
color: z.string().regex(/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/).nullable().optional(),
|
|
29
|
+
});
|
|
69
30
|
|
|
70
|
-
export const
|
|
31
|
+
export const assignmentToCreateSchema = z.object({
|
|
32
|
+
title: z.string(),
|
|
33
|
+
instructions: z.string(),
|
|
34
|
+
dueDate: z.string().datetime(),
|
|
35
|
+
acceptFiles: z.boolean(),
|
|
36
|
+
acceptExtendedResponse: z.boolean(),
|
|
37
|
+
acceptWorksheet: z.boolean(),
|
|
38
|
+
maxGrade: z.number(),
|
|
39
|
+
gradingBoundaryId: z.string().nullable().optional(),
|
|
40
|
+
markSchemeId: z.string().nullable().optional(),
|
|
41
|
+
worksheetIds: z.array(z.string()),
|
|
42
|
+
studentIds: z.array(z.string()),
|
|
43
|
+
sectionId: z.string().nullable().optional(),
|
|
44
|
+
type: z.enum(["HOMEWORK", "QUIZ", "TEST", "PROJECT", "ESSAY", "DISCUSSION", "PRESENTATION", "LAB", "OTHER"]),
|
|
45
|
+
attachments: z.array(z.object({
|
|
46
|
+
id: z.string(),
|
|
47
|
+
})),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export const documentFormatSchema = z.enum([
|
|
51
|
+
'pdf', 'docx', 'doc', 'xlsx', 'pptx', 'csv', 'md', 'html', 'txt', 'json',
|
|
52
|
+
'xml', 'tex', 'png', 'jpeg', 'jpg', 'gif', 'zip', 'tar', 'py', 'js', 'ts',
|
|
53
|
+
'css', 'sh',
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
export const docToCreateSchema = z.object({
|
|
57
|
+
title: z.string(),
|
|
58
|
+
content: z.string().min(1),
|
|
59
|
+
format: documentFormatSchema.optional().default('pdf'),
|
|
60
|
+
});
|
|
71
61
|
|
|
72
|
-
export
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
62
|
+
export type WorksheetToCreate = z.infer<typeof worksheetToCreateSchema>;
|
|
63
|
+
export type SectionToCreate = z.infer<typeof sectionToCreateSchema>;
|
|
64
|
+
export type AssignmentToCreate = z.infer<typeof assignmentToCreateSchema>;
|
|
65
|
+
export type DocToCreate = z.infer<typeof docToCreateSchema>;
|