@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
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { EmbeddingSourceType } from '@prisma/client';
|
|
2
|
+
export interface EmbeddingSearchResult {
|
|
3
|
+
id: string;
|
|
4
|
+
sourceType: EmbeddingSourceType;
|
|
5
|
+
sourceId: string;
|
|
6
|
+
content: string;
|
|
7
|
+
metadata: Record<string, unknown>;
|
|
8
|
+
similarity: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Split text into overlapping chunks for embedding.
|
|
12
|
+
*/
|
|
13
|
+
export declare function chunkText(text: string, chunkSize?: number, overlap?: number): string[];
|
|
14
|
+
/**
|
|
15
|
+
* Generate embedding vector for a text string.
|
|
16
|
+
*/
|
|
17
|
+
export declare function generateEmbedding(text: string): Promise<number[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Generate embeddings for multiple texts in a single batch.
|
|
20
|
+
*/
|
|
21
|
+
export declare function generateEmbeddings(texts: string[]): Promise<number[][]>;
|
|
22
|
+
/**
|
|
23
|
+
* Store embeddings for a source document. Replaces any existing embeddings for
|
|
24
|
+
* the same sourceType + sourceId combination.
|
|
25
|
+
*/
|
|
26
|
+
export declare function indexContent(params: {
|
|
27
|
+
classId: string;
|
|
28
|
+
sourceType: EmbeddingSourceType;
|
|
29
|
+
sourceId: string;
|
|
30
|
+
text: string;
|
|
31
|
+
metadata?: Record<string, unknown>;
|
|
32
|
+
}): Promise<number>;
|
|
33
|
+
/**
|
|
34
|
+
* Semantic search over class embeddings using cosine similarity.
|
|
35
|
+
*/
|
|
36
|
+
export declare function searchEmbeddings(params: {
|
|
37
|
+
classId: string;
|
|
38
|
+
query: string;
|
|
39
|
+
limit?: number;
|
|
40
|
+
sourceTypes?: EmbeddingSourceType[];
|
|
41
|
+
}): Promise<EmbeddingSearchResult[]>;
|
|
42
|
+
/**
|
|
43
|
+
* Remove all embeddings for a given source.
|
|
44
|
+
*/
|
|
45
|
+
export declare function removeEmbeddings(params: {
|
|
46
|
+
classId: string;
|
|
47
|
+
sourceType: EmbeddingSourceType;
|
|
48
|
+
sourceId: string;
|
|
49
|
+
}): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Index an assignment (title + instructions + attached file names) into the vector store.
|
|
52
|
+
*/
|
|
53
|
+
export declare function indexAssignment(classId: string, assignmentId: string): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Index a worksheet (questions + answers) into the vector store.
|
|
56
|
+
*/
|
|
57
|
+
export declare function indexWorksheet(classId: string, worksheetId: string): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Index an announcement into the vector store.
|
|
60
|
+
*/
|
|
61
|
+
export declare function indexAnnouncement(classId: string, announcementId: string): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Index a discussion (title + body + replies) into the vector store.
|
|
64
|
+
*/
|
|
65
|
+
export declare function indexDiscussion(classId: string, discussionId: string): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Index a submission (grade + feedback + file attachments + extended response) into the vector store.
|
|
68
|
+
*/
|
|
69
|
+
export declare function indexSubmission(classId: string, submissionId: string): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Index a class syllabus into the vector store.
|
|
72
|
+
* Uses classId as both classId and sourceId since syllabus is a field on the class.
|
|
73
|
+
*/
|
|
74
|
+
export declare function indexSyllabus(classId: string): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Index a file (name + type + path) into the vector store.
|
|
77
|
+
* For text-based content, PDF extraction would be added here in the future.
|
|
78
|
+
*/
|
|
79
|
+
export declare function indexFile(classId: string, fileId: string): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Fire-and-forget: enqueue a background job to embed content.
|
|
82
|
+
* Safe to call from service functions after a create/update.
|
|
83
|
+
*/
|
|
84
|
+
export declare function enqueueEmbedding(classId: string, sourceType: EmbeddingSourceType, sourceId: string): void;
|
|
85
|
+
//# sourceMappingURL=embedding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding.d.ts","sourceRoot":"/","sources":["services/embedding.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAQ1D,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,mBAAmB,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,SAAa,EAAE,OAAO,SAAgB,GAAG,MAAM,EAAE,CAoBjG;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAQvE;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAY7E;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,mBAAmB,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,GAAG,OAAO,CAAC,MAAM,CAAC,CAmClB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,mBAAmB,EAAE,CAAC;CACrC,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAwCnC;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,mBAAmB,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhB;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA+B1F;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBxF;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB9F;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyC1F;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA+C1F;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBlE;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB9E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,mBAAmB,EAC/B,QAAQ,EAAE,MAAM,GACf,IAAI,CAKN"}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedding service – pgvector-backed RAG for class materials.
|
|
3
|
+
* Handles text chunking, embedding generation, storage, and semantic search.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
!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]="81da6aeb-b86a-5af7-8dc6-34ecc6523f0d")}catch(e){}}();
|
|
7
|
+
import { prisma } from '../lib/prisma.js';
|
|
8
|
+
import { openAIClient } from '../utils/inference.js';
|
|
9
|
+
import { logger } from '../utils/logger.js';
|
|
10
|
+
import { enqueueRunJob } from '../workers/queues.js';
|
|
11
|
+
const EMBEDDING_MODEL = 'text-embedding-3-small';
|
|
12
|
+
const EMBEDDING_DIMENSIONS = 1536;
|
|
13
|
+
const CHUNK_SIZE = 512; // tokens (~2048 chars)
|
|
14
|
+
const CHUNK_OVERLAP = 100; // tokens (~400 chars)
|
|
15
|
+
const CHARS_PER_TOKEN = 4;
|
|
16
|
+
/**
|
|
17
|
+
* Split text into overlapping chunks for embedding.
|
|
18
|
+
*/
|
|
19
|
+
export function chunkText(text, chunkSize = CHUNK_SIZE, overlap = CHUNK_OVERLAP) {
|
|
20
|
+
const maxChars = chunkSize * CHARS_PER_TOKEN;
|
|
21
|
+
const overlapChars = overlap * CHARS_PER_TOKEN;
|
|
22
|
+
if (text.length <= maxChars) {
|
|
23
|
+
return [text];
|
|
24
|
+
}
|
|
25
|
+
const chunks = [];
|
|
26
|
+
let start = 0;
|
|
27
|
+
while (start < text.length) {
|
|
28
|
+
const end = Math.min(start + maxChars, text.length);
|
|
29
|
+
chunks.push(text.slice(start, end));
|
|
30
|
+
if (end >= text.length)
|
|
31
|
+
break;
|
|
32
|
+
start = end - overlapChars;
|
|
33
|
+
}
|
|
34
|
+
return chunks;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Generate embedding vector for a text string.
|
|
38
|
+
*/
|
|
39
|
+
export async function generateEmbedding(text) {
|
|
40
|
+
const response = await openAIClient.embeddings.create({
|
|
41
|
+
model: EMBEDDING_MODEL,
|
|
42
|
+
input: text,
|
|
43
|
+
dimensions: EMBEDDING_DIMENSIONS,
|
|
44
|
+
});
|
|
45
|
+
return response.data[0].embedding;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Generate embeddings for multiple texts in a single batch.
|
|
49
|
+
*/
|
|
50
|
+
export async function generateEmbeddings(texts) {
|
|
51
|
+
if (texts.length === 0)
|
|
52
|
+
return [];
|
|
53
|
+
const response = await openAIClient.embeddings.create({
|
|
54
|
+
model: EMBEDDING_MODEL,
|
|
55
|
+
input: texts,
|
|
56
|
+
dimensions: EMBEDDING_DIMENSIONS,
|
|
57
|
+
});
|
|
58
|
+
return response.data
|
|
59
|
+
.sort((a, b) => a.index - b.index)
|
|
60
|
+
.map((item) => item.embedding);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Store embeddings for a source document. Replaces any existing embeddings for
|
|
64
|
+
* the same sourceType + sourceId combination.
|
|
65
|
+
*/
|
|
66
|
+
export async function indexContent(params) {
|
|
67
|
+
const { classId, sourceType, sourceId, text, metadata = {} } = params;
|
|
68
|
+
// Remove existing embeddings for this source
|
|
69
|
+
await prisma.embedding.deleteMany({
|
|
70
|
+
where: { classId, sourceType, sourceId },
|
|
71
|
+
});
|
|
72
|
+
const chunks = chunkText(text);
|
|
73
|
+
if (chunks.length === 0)
|
|
74
|
+
return 0;
|
|
75
|
+
const embeddings = await generateEmbeddings(chunks);
|
|
76
|
+
// Insert using raw SQL for pgvector support
|
|
77
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
78
|
+
const vectorStr = `[${embeddings[i].join(',')}]`;
|
|
79
|
+
const chunkMetadata = { ...metadata, chunkIndex: i, totalChunks: chunks.length };
|
|
80
|
+
await prisma.$executeRaw `
|
|
81
|
+
INSERT INTO "Embedding" (id, "classId", "sourceType", "sourceId", content, vector, metadata, "createdAt")
|
|
82
|
+
VALUES (
|
|
83
|
+
gen_random_uuid(),
|
|
84
|
+
${classId},
|
|
85
|
+
${sourceType}::"EmbeddingSourceType",
|
|
86
|
+
${sourceId},
|
|
87
|
+
${chunks[i]},
|
|
88
|
+
${vectorStr}::vector,
|
|
89
|
+
${JSON.stringify(chunkMetadata)}::jsonb,
|
|
90
|
+
NOW()
|
|
91
|
+
)
|
|
92
|
+
`;
|
|
93
|
+
}
|
|
94
|
+
logger.info('Content indexed', { classId, sourceType, sourceId, chunks: chunks.length });
|
|
95
|
+
return chunks.length;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Semantic search over class embeddings using cosine similarity.
|
|
99
|
+
*/
|
|
100
|
+
export async function searchEmbeddings(params) {
|
|
101
|
+
const { classId, query, limit = 5, sourceTypes } = params;
|
|
102
|
+
const queryEmbedding = await generateEmbedding(query);
|
|
103
|
+
const vectorStr = `[${queryEmbedding.join(',')}]`;
|
|
104
|
+
let results;
|
|
105
|
+
if (sourceTypes && sourceTypes.length > 0) {
|
|
106
|
+
results = await prisma.$queryRaw `
|
|
107
|
+
SELECT
|
|
108
|
+
id,
|
|
109
|
+
"sourceType",
|
|
110
|
+
"sourceId",
|
|
111
|
+
content,
|
|
112
|
+
metadata,
|
|
113
|
+
1 - (vector <=> ${vectorStr}::vector) as similarity
|
|
114
|
+
FROM "Embedding"
|
|
115
|
+
WHERE "classId" = ${classId}
|
|
116
|
+
AND "sourceType" = ANY(${sourceTypes}::"EmbeddingSourceType"[])
|
|
117
|
+
ORDER BY vector <=> ${vectorStr}::vector
|
|
118
|
+
LIMIT ${limit}
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
results = await prisma.$queryRaw `
|
|
123
|
+
SELECT
|
|
124
|
+
id,
|
|
125
|
+
"sourceType",
|
|
126
|
+
"sourceId",
|
|
127
|
+
content,
|
|
128
|
+
metadata,
|
|
129
|
+
1 - (vector <=> ${vectorStr}::vector) as similarity
|
|
130
|
+
FROM "Embedding"
|
|
131
|
+
WHERE "classId" = ${classId}
|
|
132
|
+
ORDER BY vector <=> ${vectorStr}::vector
|
|
133
|
+
LIMIT ${limit}
|
|
134
|
+
`;
|
|
135
|
+
}
|
|
136
|
+
return results;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Remove all embeddings for a given source.
|
|
140
|
+
*/
|
|
141
|
+
export async function removeEmbeddings(params) {
|
|
142
|
+
await prisma.embedding.deleteMany({
|
|
143
|
+
where: {
|
|
144
|
+
classId: params.classId,
|
|
145
|
+
sourceType: params.sourceType,
|
|
146
|
+
sourceId: params.sourceId,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Index an assignment (title + instructions + attached file names) into the vector store.
|
|
152
|
+
*/
|
|
153
|
+
export async function indexAssignment(classId, assignmentId) {
|
|
154
|
+
const assignment = await prisma.assignment.findUnique({
|
|
155
|
+
where: { id: assignmentId },
|
|
156
|
+
select: {
|
|
157
|
+
title: true,
|
|
158
|
+
instructions: true,
|
|
159
|
+
type: true,
|
|
160
|
+
attachments: { select: { name: true, type: true } },
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
if (!assignment)
|
|
164
|
+
return;
|
|
165
|
+
const parts = [
|
|
166
|
+
`Title: ${assignment.title}`,
|
|
167
|
+
`Type: ${assignment.type}`,
|
|
168
|
+
assignment.instructions ? `Instructions: ${assignment.instructions}` : '',
|
|
169
|
+
];
|
|
170
|
+
if (assignment.attachments.length > 0) {
|
|
171
|
+
const fileList = assignment.attachments.map((f) => `${f.name} (${f.type})`).join(', ');
|
|
172
|
+
parts.push(`Attached files: ${fileList}`);
|
|
173
|
+
}
|
|
174
|
+
await indexContent({
|
|
175
|
+
classId,
|
|
176
|
+
sourceType: 'ASSIGNMENT',
|
|
177
|
+
sourceId: assignmentId,
|
|
178
|
+
text: parts.filter(Boolean).join('\n'),
|
|
179
|
+
metadata: { title: assignment.title, type: assignment.type, fileCount: assignment.attachments.length },
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Index a worksheet (questions + answers) into the vector store.
|
|
184
|
+
*/
|
|
185
|
+
export async function indexWorksheet(classId, worksheetId) {
|
|
186
|
+
const worksheet = await prisma.worksheet.findUnique({
|
|
187
|
+
where: { id: worksheetId },
|
|
188
|
+
include: { questions: true },
|
|
189
|
+
});
|
|
190
|
+
if (!worksheet)
|
|
191
|
+
return;
|
|
192
|
+
const questionTexts = worksheet.questions.map((q, i) => `Question ${i + 1} (${q.type}): ${q.question}\nAnswer: ${q.answer}`);
|
|
193
|
+
const text = [
|
|
194
|
+
`Worksheet: ${worksheet.name}`,
|
|
195
|
+
...questionTexts,
|
|
196
|
+
].join('\n\n');
|
|
197
|
+
await indexContent({
|
|
198
|
+
classId,
|
|
199
|
+
sourceType: 'WORKSHEET',
|
|
200
|
+
sourceId: worksheetId,
|
|
201
|
+
text,
|
|
202
|
+
metadata: { name: worksheet.name, questionCount: worksheet.questions.length },
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Index an announcement into the vector store.
|
|
207
|
+
*/
|
|
208
|
+
export async function indexAnnouncement(classId, announcementId) {
|
|
209
|
+
const announcement = await prisma.announcement.findUnique({
|
|
210
|
+
where: { id: announcementId },
|
|
211
|
+
select: { remarks: true, teacherId: true },
|
|
212
|
+
});
|
|
213
|
+
if (!announcement || !announcement.remarks)
|
|
214
|
+
return;
|
|
215
|
+
const text = `Announcement: ${announcement.remarks}`;
|
|
216
|
+
await indexContent({
|
|
217
|
+
classId,
|
|
218
|
+
sourceType: 'ANNOUNCEMENT',
|
|
219
|
+
sourceId: announcementId,
|
|
220
|
+
text,
|
|
221
|
+
metadata: { teacherId: announcement.teacherId },
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Index a discussion (title + body + replies) into the vector store.
|
|
226
|
+
*/
|
|
227
|
+
export async function indexDiscussion(classId, discussionId) {
|
|
228
|
+
const discussion = await prisma.discussion.findUnique({
|
|
229
|
+
where: { id: discussionId },
|
|
230
|
+
select: {
|
|
231
|
+
title: true,
|
|
232
|
+
body: true,
|
|
233
|
+
type: true,
|
|
234
|
+
author: { select: { username: true, profile: { select: { displayName: true } } } },
|
|
235
|
+
replies: {
|
|
236
|
+
select: { body: true, author: { select: { username: true, profile: { select: { displayName: true } } } } },
|
|
237
|
+
orderBy: { createdAt: 'asc' },
|
|
238
|
+
take: 20,
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
if (!discussion)
|
|
243
|
+
return;
|
|
244
|
+
const authorName = discussion.author.profile?.displayName || discussion.author.username;
|
|
245
|
+
const parts = [
|
|
246
|
+
`Discussion: ${discussion.title}`,
|
|
247
|
+
`Type: ${discussion.type}`,
|
|
248
|
+
`Author: ${authorName}`,
|
|
249
|
+
`Body: ${discussion.body}`,
|
|
250
|
+
];
|
|
251
|
+
if (discussion.replies.length > 0) {
|
|
252
|
+
parts.push('Replies:');
|
|
253
|
+
for (const reply of discussion.replies) {
|
|
254
|
+
const replyAuthor = reply.author.profile?.displayName || reply.author.username;
|
|
255
|
+
parts.push(` ${replyAuthor}: ${reply.body.slice(0, 300)}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
await indexContent({
|
|
259
|
+
classId,
|
|
260
|
+
sourceType: 'DISCUSSION',
|
|
261
|
+
sourceId: discussionId,
|
|
262
|
+
text: parts.join('\n'),
|
|
263
|
+
metadata: { title: discussion.title, type: discussion.type, replyCount: discussion.replies.length },
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Index a submission (grade + feedback + file attachments + extended response) into the vector store.
|
|
268
|
+
*/
|
|
269
|
+
export async function indexSubmission(classId, submissionId) {
|
|
270
|
+
const submission = await prisma.submission.findUnique({
|
|
271
|
+
where: { id: submissionId },
|
|
272
|
+
select: {
|
|
273
|
+
gradeReceived: true,
|
|
274
|
+
teacherComments: true,
|
|
275
|
+
extendedResponse: true,
|
|
276
|
+
returned: true,
|
|
277
|
+
student: { select: { username: true, profile: { select: { displayName: true } } } },
|
|
278
|
+
assignment: { select: { title: true, maxGrade: true, type: true } },
|
|
279
|
+
attachments: { select: { name: true, type: true } },
|
|
280
|
+
annotations: { select: { name: true, type: true } },
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
if (!submission)
|
|
284
|
+
return;
|
|
285
|
+
const studentName = submission.student.profile?.displayName || submission.student.username;
|
|
286
|
+
const parts = [
|
|
287
|
+
`Submission for: ${submission.assignment.title}`,
|
|
288
|
+
`Student: ${studentName}`,
|
|
289
|
+
`Assignment type: ${submission.assignment.type}`,
|
|
290
|
+
];
|
|
291
|
+
if (submission.gradeReceived != null) {
|
|
292
|
+
parts.push(`Grade: ${submission.gradeReceived}/${submission.assignment.maxGrade ?? '?'}`);
|
|
293
|
+
}
|
|
294
|
+
if (submission.teacherComments) {
|
|
295
|
+
parts.push(`Teacher feedback: ${submission.teacherComments}`);
|
|
296
|
+
}
|
|
297
|
+
if (submission.extendedResponse) {
|
|
298
|
+
parts.push(`Student response: ${submission.extendedResponse.slice(0, 500)}`);
|
|
299
|
+
}
|
|
300
|
+
if (submission.attachments.length > 0) {
|
|
301
|
+
parts.push(`Submitted files: ${submission.attachments.map((f) => f.name).join(', ')}`);
|
|
302
|
+
}
|
|
303
|
+
if (submission.annotations.length > 0) {
|
|
304
|
+
parts.push(`Teacher annotations: ${submission.annotations.map((f) => f.name).join(', ')}`);
|
|
305
|
+
}
|
|
306
|
+
await indexContent({
|
|
307
|
+
classId,
|
|
308
|
+
sourceType: 'SUBMISSION',
|
|
309
|
+
sourceId: submissionId,
|
|
310
|
+
text: parts.join('\n'),
|
|
311
|
+
metadata: { studentName, assignmentTitle: submission.assignment.title, grade: submission.gradeReceived },
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Index a class syllabus into the vector store.
|
|
316
|
+
* Uses classId as both classId and sourceId since syllabus is a field on the class.
|
|
317
|
+
*/
|
|
318
|
+
export async function indexSyllabus(classId) {
|
|
319
|
+
const cls = await prisma.class.findUnique({
|
|
320
|
+
where: { id: classId },
|
|
321
|
+
select: { name: true, subject: true, syllabus: true },
|
|
322
|
+
});
|
|
323
|
+
if (!cls?.syllabus)
|
|
324
|
+
return;
|
|
325
|
+
const text = [
|
|
326
|
+
`Syllabus for ${cls.name} (${cls.subject})`,
|
|
327
|
+
cls.syllabus,
|
|
328
|
+
].join('\n\n');
|
|
329
|
+
await indexContent({
|
|
330
|
+
classId,
|
|
331
|
+
sourceType: 'SYLLABUS',
|
|
332
|
+
sourceId: classId,
|
|
333
|
+
text,
|
|
334
|
+
metadata: { className: cls.name, subject: cls.subject },
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Index a file (name + type + path) into the vector store.
|
|
339
|
+
* For text-based content, PDF extraction would be added here in the future.
|
|
340
|
+
*/
|
|
341
|
+
export async function indexFile(classId, fileId) {
|
|
342
|
+
const file = await prisma.file.findUnique({
|
|
343
|
+
where: { id: fileId },
|
|
344
|
+
select: { name: true, path: true, type: true, size: true },
|
|
345
|
+
});
|
|
346
|
+
if (!file)
|
|
347
|
+
return;
|
|
348
|
+
const text = [
|
|
349
|
+
`File: ${file.name}`,
|
|
350
|
+
`Type: ${file.type}`,
|
|
351
|
+
`Path: ${file.path}`,
|
|
352
|
+
].join('\n');
|
|
353
|
+
await indexContent({
|
|
354
|
+
classId,
|
|
355
|
+
sourceType: 'FILE',
|
|
356
|
+
sourceId: fileId,
|
|
357
|
+
text,
|
|
358
|
+
metadata: { name: file.name, type: file.type, size: file.size },
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Fire-and-forget: enqueue a background job to embed content.
|
|
363
|
+
* Safe to call from service functions after a create/update.
|
|
364
|
+
*/
|
|
365
|
+
export function enqueueEmbedding(classId, sourceType, sourceId) {
|
|
366
|
+
enqueueRunJob('EMBED_CONTENT', { classId, sourceType: sourceType, sourceId }, { classId })
|
|
367
|
+
.catch((err) => {
|
|
368
|
+
logger.error('Failed to enqueue embedding job', { err, classId, sourceType, sourceId });
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
//# sourceMappingURL=embedding.js.map
|
|
372
|
+
//# debugId=81da6aeb-b86a-5af7-8dc6-34ecc6523f0d
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding.js","sources":["services/embedding.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Embedding service – pgvector-backed RAG for class materials.\n * Handles text chunking, embedding generation, storage, and semantic search.\n */\nimport { prisma } from '../lib/prisma.js';\nimport { openAIClient } from '../utils/inference.js';\nimport { logger } from '../utils/logger.js';\nimport { enqueueRunJob } from '../workers/queues.js';\nimport type { EmbeddingSourceType } from '@prisma/client';\n\nconst EMBEDDING_MODEL = 'text-embedding-3-small';\nconst EMBEDDING_DIMENSIONS = 1536;\nconst CHUNK_SIZE = 512; // tokens (~2048 chars)\nconst CHUNK_OVERLAP = 100; // tokens (~400 chars)\nconst CHARS_PER_TOKEN = 4;\n\nexport interface EmbeddingSearchResult {\n id: string;\n sourceType: EmbeddingSourceType;\n sourceId: string;\n content: string;\n metadata: Record<string, unknown>;\n similarity: number;\n}\n\n/**\n * Split text into overlapping chunks for embedding.\n */\nexport function chunkText(text: string, chunkSize = CHUNK_SIZE, overlap = CHUNK_OVERLAP): string[] {\n const maxChars = chunkSize * CHARS_PER_TOKEN;\n const overlapChars = overlap * CHARS_PER_TOKEN;\n\n if (text.length <= maxChars) {\n return [text];\n }\n\n const chunks: string[] = [];\n let start = 0;\n\n while (start < text.length) {\n const end = Math.min(start + maxChars, text.length);\n chunks.push(text.slice(start, end));\n\n if (end >= text.length) break;\n start = end - overlapChars;\n }\n\n return chunks;\n}\n\n/**\n * Generate embedding vector for a text string.\n */\nexport async function generateEmbedding(text: string): Promise<number[]> {\n const response = await openAIClient.embeddings.create({\n model: EMBEDDING_MODEL,\n input: text,\n dimensions: EMBEDDING_DIMENSIONS,\n });\n\n return response.data[0].embedding;\n}\n\n/**\n * Generate embeddings for multiple texts in a single batch.\n */\nexport async function generateEmbeddings(texts: string[]): Promise<number[][]> {\n if (texts.length === 0) return [];\n\n const response = await openAIClient.embeddings.create({\n model: EMBEDDING_MODEL,\n input: texts,\n dimensions: EMBEDDING_DIMENSIONS,\n });\n\n return response.data\n .sort((a, b) => a.index - b.index)\n .map((item) => item.embedding);\n}\n\n/**\n * Store embeddings for a source document. Replaces any existing embeddings for\n * the same sourceType + sourceId combination.\n */\nexport async function indexContent(params: {\n classId: string;\n sourceType: EmbeddingSourceType;\n sourceId: string;\n text: string;\n metadata?: Record<string, unknown>;\n}): Promise<number> {\n const { classId, sourceType, sourceId, text, metadata = {} } = params;\n\n // Remove existing embeddings for this source\n await prisma.embedding.deleteMany({\n where: { classId, sourceType, sourceId },\n });\n\n const chunks = chunkText(text);\n if (chunks.length === 0) return 0;\n\n const embeddings = await generateEmbeddings(chunks);\n\n // Insert using raw SQL for pgvector support\n for (let i = 0; i < chunks.length; i++) {\n const vectorStr = `[${embeddings[i].join(',')}]`;\n const chunkMetadata = { ...metadata, chunkIndex: i, totalChunks: chunks.length };\n\n await prisma.$executeRaw`\n INSERT INTO \"Embedding\" (id, \"classId\", \"sourceType\", \"sourceId\", content, vector, metadata, \"createdAt\")\n VALUES (\n gen_random_uuid(),\n ${classId},\n ${sourceType}::\"EmbeddingSourceType\",\n ${sourceId},\n ${chunks[i]},\n ${vectorStr}::vector,\n ${JSON.stringify(chunkMetadata)}::jsonb,\n NOW()\n )\n `;\n }\n\n logger.info('Content indexed', { classId, sourceType, sourceId, chunks: chunks.length });\n return chunks.length;\n}\n\n/**\n * Semantic search over class embeddings using cosine similarity.\n */\nexport async function searchEmbeddings(params: {\n classId: string;\n query: string;\n limit?: number;\n sourceTypes?: EmbeddingSourceType[];\n}): Promise<EmbeddingSearchResult[]> {\n const { classId, query, limit = 5, sourceTypes } = params;\n\n const queryEmbedding = await generateEmbedding(query);\n const vectorStr = `[${queryEmbedding.join(',')}]`;\n\n let results: EmbeddingSearchResult[];\n\n if (sourceTypes && sourceTypes.length > 0) {\n results = await prisma.$queryRaw<EmbeddingSearchResult[]>`\n SELECT\n id,\n \"sourceType\",\n \"sourceId\",\n content,\n metadata,\n 1 - (vector <=> ${vectorStr}::vector) as similarity\n FROM \"Embedding\"\n WHERE \"classId\" = ${classId}\n AND \"sourceType\" = ANY(${sourceTypes}::\"EmbeddingSourceType\"[])\n ORDER BY vector <=> ${vectorStr}::vector\n LIMIT ${limit}\n `;\n } else {\n results = await prisma.$queryRaw<EmbeddingSearchResult[]>`\n SELECT\n id,\n \"sourceType\",\n \"sourceId\",\n content,\n metadata,\n 1 - (vector <=> ${vectorStr}::vector) as similarity\n FROM \"Embedding\"\n WHERE \"classId\" = ${classId}\n ORDER BY vector <=> ${vectorStr}::vector\n LIMIT ${limit}\n `;\n }\n\n return results;\n}\n\n/**\n * Remove all embeddings for a given source.\n */\nexport async function removeEmbeddings(params: {\n classId: string;\n sourceType: EmbeddingSourceType;\n sourceId: string;\n}): Promise<void> {\n await prisma.embedding.deleteMany({\n where: {\n classId: params.classId,\n sourceType: params.sourceType,\n sourceId: params.sourceId,\n },\n });\n}\n\n/**\n * Index an assignment (title + instructions + attached file names) into the vector store.\n */\nexport async function indexAssignment(classId: string, assignmentId: string): Promise<void> {\n const assignment = await prisma.assignment.findUnique({\n where: { id: assignmentId },\n select: {\n title: true,\n instructions: true,\n type: true,\n attachments: { select: { name: true, type: true } },\n },\n });\n\n if (!assignment) return;\n\n const parts = [\n `Title: ${assignment.title}`,\n `Type: ${assignment.type}`,\n assignment.instructions ? `Instructions: ${assignment.instructions}` : '',\n ];\n\n if (assignment.attachments.length > 0) {\n const fileList = assignment.attachments.map((f) => `${f.name} (${f.type})`).join(', ');\n parts.push(`Attached files: ${fileList}`);\n }\n\n await indexContent({\n classId,\n sourceType: 'ASSIGNMENT',\n sourceId: assignmentId,\n text: parts.filter(Boolean).join('\\n'),\n metadata: { title: assignment.title, type: assignment.type, fileCount: assignment.attachments.length },\n });\n}\n\n/**\n * Index a worksheet (questions + answers) into the vector store.\n */\nexport async function indexWorksheet(classId: string, worksheetId: string): Promise<void> {\n const worksheet = await prisma.worksheet.findUnique({\n where: { id: worksheetId },\n include: { questions: true },\n });\n\n if (!worksheet) return;\n\n const questionTexts = worksheet.questions.map((q, i) =>\n `Question ${i + 1} (${q.type}): ${q.question}\\nAnswer: ${q.answer}`\n );\n\n const text = [\n `Worksheet: ${worksheet.name}`,\n ...questionTexts,\n ].join('\\n\\n');\n\n await indexContent({\n classId,\n sourceType: 'WORKSHEET',\n sourceId: worksheetId,\n text,\n metadata: { name: worksheet.name, questionCount: worksheet.questions.length },\n });\n}\n\n/**\n * Index an announcement into the vector store.\n */\nexport async function indexAnnouncement(classId: string, announcementId: string): Promise<void> {\n const announcement = await prisma.announcement.findUnique({\n where: { id: announcementId },\n select: { remarks: true, teacherId: true },\n });\n\n if (!announcement || !announcement.remarks) return;\n\n const text = `Announcement: ${announcement.remarks}`;\n\n await indexContent({\n classId,\n sourceType: 'ANNOUNCEMENT',\n sourceId: announcementId,\n text,\n metadata: { teacherId: announcement.teacherId },\n });\n}\n\n/**\n * Index a discussion (title + body + replies) into the vector store.\n */\nexport async function indexDiscussion(classId: string, discussionId: string): Promise<void> {\n const discussion = await prisma.discussion.findUnique({\n where: { id: discussionId },\n select: {\n title: true,\n body: true,\n type: true,\n author: { select: { username: true, profile: { select: { displayName: true } } } },\n replies: {\n select: { body: true, author: { select: { username: true, profile: { select: { displayName: true } } } } },\n orderBy: { createdAt: 'asc' },\n take: 20,\n },\n },\n });\n\n if (!discussion) return;\n\n const authorName = discussion.author.profile?.displayName || discussion.author.username;\n const parts = [\n `Discussion: ${discussion.title}`,\n `Type: ${discussion.type}`,\n `Author: ${authorName}`,\n `Body: ${discussion.body}`,\n ];\n\n if (discussion.replies.length > 0) {\n parts.push('Replies:');\n for (const reply of discussion.replies) {\n const replyAuthor = reply.author.profile?.displayName || reply.author.username;\n parts.push(` ${replyAuthor}: ${reply.body.slice(0, 300)}`);\n }\n }\n\n await indexContent({\n classId,\n sourceType: 'DISCUSSION',\n sourceId: discussionId,\n text: parts.join('\\n'),\n metadata: { title: discussion.title, type: discussion.type, replyCount: discussion.replies.length },\n });\n}\n\n/**\n * Index a submission (grade + feedback + file attachments + extended response) into the vector store.\n */\nexport async function indexSubmission(classId: string, submissionId: string): Promise<void> {\n const submission = await prisma.submission.findUnique({\n where: { id: submissionId },\n select: {\n gradeReceived: true,\n teacherComments: true,\n extendedResponse: true,\n returned: true,\n student: { select: { username: true, profile: { select: { displayName: true } } } },\n assignment: { select: { title: true, maxGrade: true, type: true } },\n attachments: { select: { name: true, type: true } },\n annotations: { select: { name: true, type: true } },\n },\n });\n\n if (!submission) return;\n\n const studentName = submission.student.profile?.displayName || submission.student.username;\n const parts = [\n `Submission for: ${submission.assignment.title}`,\n `Student: ${studentName}`,\n `Assignment type: ${submission.assignment.type}`,\n ];\n\n if (submission.gradeReceived != null) {\n parts.push(`Grade: ${submission.gradeReceived}/${submission.assignment.maxGrade ?? '?'}`);\n }\n if (submission.teacherComments) {\n parts.push(`Teacher feedback: ${submission.teacherComments}`);\n }\n if (submission.extendedResponse) {\n parts.push(`Student response: ${submission.extendedResponse.slice(0, 500)}`);\n }\n if (submission.attachments.length > 0) {\n parts.push(`Submitted files: ${submission.attachments.map((f) => f.name).join(', ')}`);\n }\n if (submission.annotations.length > 0) {\n parts.push(`Teacher annotations: ${submission.annotations.map((f) => f.name).join(', ')}`);\n }\n\n await indexContent({\n classId,\n sourceType: 'SUBMISSION',\n sourceId: submissionId,\n text: parts.join('\\n'),\n metadata: { studentName, assignmentTitle: submission.assignment.title, grade: submission.gradeReceived },\n });\n}\n\n/**\n * Index a class syllabus into the vector store.\n * Uses classId as both classId and sourceId since syllabus is a field on the class.\n */\nexport async function indexSyllabus(classId: string): Promise<void> {\n const cls = await prisma.class.findUnique({\n where: { id: classId },\n select: { name: true, subject: true, syllabus: true },\n });\n\n if (!cls?.syllabus) return;\n\n const text = [\n `Syllabus for ${cls.name} (${cls.subject})`,\n cls.syllabus,\n ].join('\\n\\n');\n\n await indexContent({\n classId,\n sourceType: 'SYLLABUS',\n sourceId: classId,\n text,\n metadata: { className: cls.name, subject: cls.subject },\n });\n}\n\n/**\n * Index a file (name + type + path) into the vector store.\n * For text-based content, PDF extraction would be added here in the future.\n */\nexport async function indexFile(classId: string, fileId: string): Promise<void> {\n const file = await prisma.file.findUnique({\n where: { id: fileId },\n select: { name: true, path: true, type: true, size: true },\n });\n\n if (!file) return;\n\n const text = [\n `File: ${file.name}`,\n `Type: ${file.type}`,\n `Path: ${file.path}`,\n ].join('\\n');\n\n await indexContent({\n classId,\n sourceType: 'FILE',\n sourceId: fileId,\n text,\n metadata: { name: file.name, type: file.type, size: file.size },\n });\n}\n\n/**\n * Fire-and-forget: enqueue a background job to embed content.\n * Safe to call from service functions after a create/update.\n */\nexport function enqueueEmbedding(\n classId: string,\n sourceType: EmbeddingSourceType,\n sourceId: string,\n): void {\n enqueueRunJob('EMBED_CONTENT', { classId, sourceType: sourceType as string, sourceId }, { classId })\n .catch((err) => {\n logger.error('Failed to enqueue embedding job', { err, classId, sourceType, sourceId });\n });\n}\n"],"names":[],"mappings":"AAAA;;;GAGG;;;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD,MAAM,eAAe,GAAG,wBAAwB,CAAC;AACjD,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAClC,MAAM,UAAU,GAAG,GAAG,CAAC,CAAC,uBAAuB;AAC/C,MAAM,aAAa,GAAG,GAAG,CAAC,CAAC,sBAAsB;AACjD,MAAM,eAAe,GAAG,CAAC,CAAC;AAW1B;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,SAAS,GAAG,UAAU,EAAE,OAAO,GAAG,aAAa;IACrF,MAAM,QAAQ,GAAG,SAAS,GAAG,eAAe,CAAC;IAC7C,MAAM,YAAY,GAAG,OAAO,GAAG,eAAe,CAAC;IAE/C,IAAI,IAAI,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC5B,OAAO,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC;IAED,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,OAAO,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACpD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;QAEpC,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM;YAAE,MAAM;QAC9B,KAAK,GAAG,GAAG,GAAG,YAAY,CAAC;IAC7B,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAY;IAClD,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC;QACpD,KAAK,EAAE,eAAe;QACtB,KAAK,EAAE,IAAI;QACX,UAAU,EAAE,oBAAoB;KACjC,CAAC,CAAC;IAEH,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,KAAe;IACtD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAElC,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC;QACpD,KAAK,EAAE,eAAe;QACtB,KAAK,EAAE,KAAK;QACZ,UAAU,EAAE,oBAAoB;KACjC,CAAC,CAAC;IAEH,OAAO,QAAQ,CAAC,IAAI;SACjB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;SACjC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAMlC;IACC,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,GAAG,EAAE,EAAE,GAAG,MAAM,CAAC;IAEtE,6CAA6C;IAC7C,MAAM,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;QAChC,KAAK,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE;KACzC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAElC,MAAM,UAAU,GAAG,MAAM,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAEpD,4CAA4C;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,SAAS,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;QACjD,MAAM,aAAa,GAAG,EAAE,GAAG,QAAQ,EAAE,UAAU,EAAE,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;QAEjF,MAAM,MAAM,CAAC,WAAW,CAAA;;;;UAIlB,OAAO;UACP,UAAU;UACV,QAAQ;UACR,MAAM,CAAC,CAAC,CAAC;UACT,SAAS;UACT,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC;;;KAGlC,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACzF,OAAO,MAAM,CAAC,MAAM,CAAC;AACvB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAKtC;IACC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;IAE1D,MAAM,cAAc,GAAG,MAAM,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,IAAI,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAElD,IAAI,OAAgC,CAAC;IAErC,IAAI,WAAW,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1C,OAAO,GAAG,MAAM,MAAM,CAAC,SAAS,CAAyB;;;;;;;0BAOnC,SAAS;;0BAET,OAAO;iCACA,WAAW;4BAChB,SAAS;cACvB,KAAK;KACd,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,MAAM,MAAM,CAAC,SAAS,CAAyB;;;;;;;0BAOnC,SAAS;;0BAET,OAAO;4BACL,SAAS;cACvB,KAAK;KACd,CAAC;IACJ,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAItC;IACC,MAAM,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;QAChC,KAAK,EAAE;YACL,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,QAAQ,EAAE,MAAM,CAAC,QAAQ;SAC1B;KACF,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAe,EAAE,YAAoB;IACzE,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC;QACpD,KAAK,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE;QAC3B,MAAM,EAAE;YACN,KAAK,EAAE,IAAI;YACX,YAAY,EAAE,IAAI;YAClB,IAAI,EAAE,IAAI;YACV,WAAW,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;SACpD;KACF,CAAC,CAAC;IAEH,IAAI,CAAC,UAAU;QAAE,OAAO;IAExB,MAAM,KAAK,GAAG;QACZ,UAAU,UAAU,CAAC,KAAK,EAAE;QAC5B,SAAS,UAAU,CAAC,IAAI,EAAE;QAC1B,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,iBAAiB,UAAU,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE;KAC1E,CAAC;IAEF,IAAI,UAAU,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtC,MAAM,QAAQ,GAAG,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvF,KAAK,CAAC,IAAI,CAAC,mBAAmB,QAAQ,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,MAAM,YAAY,CAAC;QACjB,OAAO;QACP,UAAU,EAAE,YAAY;QACxB,QAAQ,EAAE,YAAY;QACtB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QACtC,QAAQ,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,CAAC,WAAW,CAAC,MAAM,EAAE;KACvG,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAAe,EAAE,WAAmB;IACvE,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;QAClD,KAAK,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE;QAC1B,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE;KAC7B,CAAC,CAAC;IAEH,IAAI,CAAC,SAAS;QAAE,OAAO;IAEvB,MAAM,aAAa,GAAG,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACrD,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,QAAQ,aAAa,CAAC,CAAC,MAAM,EAAE,CACpE,CAAC;IAEF,MAAM,IAAI,GAAG;QACX,cAAc,SAAS,CAAC,IAAI,EAAE;QAC9B,GAAG,aAAa;KACjB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAEf,MAAM,YAAY,CAAC;QACjB,OAAO;QACP,UAAU,EAAE,WAAW;QACvB,QAAQ,EAAE,WAAW;QACrB,IAAI;QACJ,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,aAAa,EAAE,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE;KAC9E,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAAe,EAAE,cAAsB;IAC7E,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC;QACxD,KAAK,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE;QAC7B,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE;KAC3C,CAAC,CAAC;IAEH,IAAI,CAAC,YAAY,IAAI,CAAC,YAAY,CAAC,OAAO;QAAE,OAAO;IAEnD,MAAM,IAAI,GAAG,iBAAiB,YAAY,CAAC,OAAO,EAAE,CAAC;IAErD,MAAM,YAAY,CAAC;QACjB,OAAO;QACP,UAAU,EAAE,cAAc;QAC1B,QAAQ,EAAE,cAAc;QACxB,IAAI;QACJ,QAAQ,EAAE,EAAE,SAAS,EAAE,YAAY,CAAC,SAAS,EAAE;KAChD,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAe,EAAE,YAAoB;IACzE,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC;QACpD,KAAK,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE;QAC3B,MAAM,EAAE;YACN,KAAK,EAAE,IAAI;YACX,IAAI,EAAE,IAAI;YACV,IAAI,EAAE,IAAI;YACV,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE;YAClF,OAAO,EAAE;gBACP,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE;gBAC1G,OAAO,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;gBAC7B,IAAI,EAAE,EAAE;aACT;SACF;KACF,CAAC,CAAC;IAEH,IAAI,CAAC,UAAU;QAAE,OAAO;IAExB,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,WAAW,IAAI,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC;IACxF,MAAM,KAAK,GAAG;QACZ,eAAe,UAAU,CAAC,KAAK,EAAE;QACjC,SAAS,UAAU,CAAC,IAAI,EAAE;QAC1B,WAAW,UAAU,EAAE;QACvB,SAAS,UAAU,CAAC,IAAI,EAAE;KAC3B,CAAC;IAEF,IAAI,UAAU,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACvB,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;YACvC,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,WAAW,IAAI,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;YAC/E,KAAK,CAAC,IAAI,CAAC,KAAK,WAAW,KAAK,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IAED,MAAM,YAAY,CAAC;QACjB,OAAO;QACP,UAAU,EAAE,YAAY;QACxB,QAAQ,EAAE,YAAY;QACtB,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QACtB,QAAQ,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE;KACpG,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAe,EAAE,YAAoB;IACzE,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC;QACpD,KAAK,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE;QAC3B,MAAM,EAAE;YACN,aAAa,EAAE,IAAI;YACnB,eAAe,EAAE,IAAI;YACrB,gBAAgB,EAAE,IAAI;YACtB,QAAQ,EAAE,IAAI;YACd,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE;YACnF,UAAU,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;YACnE,WAAW,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;YACnD,WAAW,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;SACpD;KACF,CAAC,CAAC;IAEH,IAAI,CAAC,UAAU;QAAE,OAAO;IAExB,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC,OAAO,EAAE,WAAW,IAAI,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC;IAC3F,MAAM,KAAK,GAAG;QACZ,mBAAmB,UAAU,CAAC,UAAU,CAAC,KAAK,EAAE;QAChD,YAAY,WAAW,EAAE;QACzB,oBAAoB,UAAU,CAAC,UAAU,CAAC,IAAI,EAAE;KACjD,CAAC;IAEF,IAAI,UAAU,CAAC,aAAa,IAAI,IAAI,EAAE,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,UAAU,UAAU,CAAC,aAAa,IAAI,UAAU,CAAC,UAAU,CAAC,QAAQ,IAAI,GAAG,EAAE,CAAC,CAAC;IAC5F,CAAC;IACD,IAAI,UAAU,CAAC,eAAe,EAAE,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,qBAAqB,UAAU,CAAC,eAAe,EAAE,CAAC,CAAC;IAChE,CAAC;IACD,IAAI,UAAU,CAAC,gBAAgB,EAAE,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC,qBAAqB,UAAU,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,UAAU,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtC,KAAK,CAAC,IAAI,CAAC,oBAAoB,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACzF,CAAC;IACD,IAAI,UAAU,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtC,KAAK,CAAC,IAAI,CAAC,wBAAwB,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7F,CAAC;IAED,MAAM,YAAY,CAAC;QACjB,OAAO;QACP,UAAU,EAAE,YAAY;QACxB,QAAQ,EAAE,YAAY;QACtB,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QACtB,QAAQ,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE,UAAU,CAAC,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,aAAa,EAAE;KACzG,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAe;IACjD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC;QACxC,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE;QACtB,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE;KACtD,CAAC,CAAC;IAEH,IAAI,CAAC,GAAG,EAAE,QAAQ;QAAE,OAAO;IAE3B,MAAM,IAAI,GAAG;QACX,gBAAgB,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,OAAO,GAAG;QAC3C,GAAG,CAAC,QAAQ;KACb,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAEf,MAAM,YAAY,CAAC;QACjB,OAAO;QACP,UAAU,EAAE,UAAU;QACtB,QAAQ,EAAE,OAAO;QACjB,IAAI;QACJ,QAAQ,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE;KACxD,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAe,EAAE,MAAc;IAC7D,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;QACxC,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE;QACrB,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE;KAC3D,CAAC,CAAC;IAEH,IAAI,CAAC,IAAI;QAAE,OAAO;IAElB,MAAM,IAAI,GAAG;QACX,SAAS,IAAI,CAAC,IAAI,EAAE;QACpB,SAAS,IAAI,CAAC,IAAI,EAAE;QACpB,SAAS,IAAI,CAAC,IAAI,EAAE;KACrB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,MAAM,YAAY,CAAC;QACjB,OAAO;QACP,UAAU,EAAE,MAAM;QAClB,QAAQ,EAAE,MAAM;QAChB,IAAI;QACJ,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;KAChE,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAe,EACf,UAA+B,EAC/B,QAAgB;IAEhB,aAAa,CAAC,eAAe,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,UAAoB,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC;SACjG,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACb,MAAM,CAAC,KAAK,CAAC,iCAAiC,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;AACP,CAAC","debug_id":"81da6aeb-b86a-5af7-8dc6-34ecc6523f0d"}
|