@studious-lms/server 1.2.45 → 1.2.47

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.
Files changed (241) hide show
  1. package/.env.example +45 -0
  2. package/.env.test.example +37 -0
  3. package/README.md +34 -7
  4. package/coverage/base.css +224 -0
  5. package/coverage/block-navigation.js +87 -0
  6. package/coverage/clover.xml +12110 -0
  7. package/coverage/coverage-final.json +44 -0
  8. package/coverage/favicon.png +0 -0
  9. package/coverage/index.html +221 -0
  10. package/coverage/prettify.css +1 -0
  11. package/coverage/prettify.js +2 -0
  12. package/coverage/server/index.html +116 -0
  13. package/coverage/server/src/exportType.ts.html +109 -0
  14. package/coverage/server/src/index.html +161 -0
  15. package/coverage/server/src/index.ts.html +1702 -0
  16. package/coverage/server/src/instrument.ts.html +130 -0
  17. package/coverage/server/src/lib/config/env.ts.html +448 -0
  18. package/coverage/server/src/lib/config/index.html +116 -0
  19. package/coverage/server/src/lib/fileUpload.ts.html +1138 -0
  20. package/coverage/server/src/lib/googleCloudStorage.ts.html +334 -0
  21. package/coverage/server/src/lib/index.html +206 -0
  22. package/coverage/server/src/lib/jsonConversion.ts.html +2323 -0
  23. package/coverage/server/src/lib/jsonStyles.ts.html +193 -0
  24. package/coverage/server/src/lib/notificationHandler.ts.html +193 -0
  25. package/coverage/server/src/lib/pusher.ts.html +121 -0
  26. package/coverage/server/src/lib/thumbnailGenerator.ts.html +592 -0
  27. package/coverage/server/src/middleware/auth.ts.html +646 -0
  28. package/coverage/server/src/middleware/index.html +146 -0
  29. package/coverage/server/src/middleware/logging.ts.html +244 -0
  30. package/coverage/server/src/middleware/security.ts.html +271 -0
  31. package/coverage/server/src/routers/_app.ts.html +232 -0
  32. package/coverage/server/src/routers/agenda.ts.html +319 -0
  33. package/coverage/server/src/routers/announcement.ts.html +3481 -0
  34. package/coverage/server/src/routers/assignment.ts.html +7633 -0
  35. package/coverage/server/src/routers/attendance.ts.html +1030 -0
  36. package/coverage/server/src/routers/auth.ts.html +1081 -0
  37. package/coverage/server/src/routers/class.ts.html +3535 -0
  38. package/coverage/server/src/routers/comment.ts.html +991 -0
  39. package/coverage/server/src/routers/conversation.ts.html +982 -0
  40. package/coverage/server/src/routers/event.ts.html +1609 -0
  41. package/coverage/server/src/routers/file.ts.html +1144 -0
  42. package/coverage/server/src/routers/folder.ts.html +2797 -0
  43. package/coverage/server/src/routers/index.html +386 -0
  44. package/coverage/server/src/routers/labChat.ts.html +3073 -0
  45. package/coverage/server/src/routers/marketing.ts.html +340 -0
  46. package/coverage/server/src/routers/message.ts.html +1912 -0
  47. package/coverage/server/src/routers/notifications.ts.html +364 -0
  48. package/coverage/server/src/routers/section.ts.html +1120 -0
  49. package/coverage/server/src/routers/user.ts.html +862 -0
  50. package/coverage/server/src/routers/worksheet.ts.html +1729 -0
  51. package/coverage/server/src/trpc.ts.html +397 -0
  52. package/coverage/server/src/types/index.html +116 -0
  53. package/coverage/server/src/types/trpc.ts.html +127 -0
  54. package/coverage/server/src/utils/aiUser.ts.html +280 -0
  55. package/coverage/server/src/utils/email.ts.html +121 -0
  56. package/coverage/server/src/utils/generateInviteCode.ts.html +106 -0
  57. package/coverage/server/src/utils/index.html +206 -0
  58. package/coverage/server/src/utils/inference.ts.html +709 -0
  59. package/coverage/server/src/utils/logger.ts.html +664 -0
  60. package/coverage/server/src/utils/prismaErrorHandler.ts.html +907 -0
  61. package/coverage/server/src/utils/prismaWrapper.ts.html +355 -0
  62. package/coverage/server/vitest.config.ts.html +196 -0
  63. package/coverage/sort-arrow-sprite.png +0 -0
  64. package/coverage/sorter.js +210 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +83 -52
  67. package/dist/index.js.map +1 -1
  68. package/dist/instrument.js +15 -8
  69. package/dist/instrument.js.map +1 -1
  70. package/dist/lib/config/env.d.ts +169 -0
  71. package/dist/lib/config/env.d.ts.map +1 -0
  72. package/dist/lib/config/env.js +115 -0
  73. package/dist/lib/config/env.js.map +1 -0
  74. package/dist/lib/fileUpload.d.ts.map +1 -1
  75. package/dist/lib/fileUpload.js +5 -4
  76. package/dist/lib/fileUpload.js.map +1 -1
  77. package/dist/lib/googleCloudStorage.d.ts.map +1 -1
  78. package/dist/lib/googleCloudStorage.js +7 -8
  79. package/dist/lib/googleCloudStorage.js.map +1 -1
  80. package/dist/lib/jsonConversion.d.ts.map +1 -1
  81. package/dist/lib/jsonConversion.js +14 -16
  82. package/dist/lib/jsonConversion.js.map +1 -1
  83. package/dist/lib/notificationHandler.d.ts +2 -2
  84. package/dist/lib/prisma.d.ts +2 -2
  85. package/dist/lib/prisma.d.ts.map +1 -1
  86. package/dist/lib/prisma.js +22 -3
  87. package/dist/lib/prisma.js.map +1 -1
  88. package/dist/lib/pusher.d.ts.map +1 -1
  89. package/dist/lib/pusher.js +8 -7
  90. package/dist/lib/pusher.js.map +1 -1
  91. package/dist/middleware/auth.d.ts.map +1 -1
  92. package/dist/middleware/auth.js +7 -5
  93. package/dist/middleware/auth.js.map +1 -1
  94. package/dist/middleware/security.d.ts +5 -0
  95. package/dist/middleware/security.d.ts.map +1 -0
  96. package/dist/middleware/security.js +77 -0
  97. package/dist/middleware/security.js.map +1 -0
  98. package/dist/routers/_app.d.ts +368 -108
  99. package/dist/routers/_app.d.ts.map +1 -1
  100. package/dist/routers/_app.js +4 -2
  101. package/dist/routers/_app.js.map +1 -1
  102. package/dist/routers/agenda.d.ts.map +1 -1
  103. package/dist/routers/agenda.js +12 -9
  104. package/dist/routers/agenda.js.map +1 -1
  105. package/dist/routers/announcement.d.ts +8 -0
  106. package/dist/routers/announcement.d.ts.map +1 -1
  107. package/dist/routers/announcement.js +6 -4
  108. package/dist/routers/announcement.js.map +1 -1
  109. package/dist/routers/assignment.d.ts +17 -4
  110. package/dist/routers/assignment.d.ts.map +1 -1
  111. package/dist/routers/assignment.js +51 -19
  112. package/dist/routers/assignment.js.map +1 -1
  113. package/dist/routers/attendance.d.ts +1 -0
  114. package/dist/routers/attendance.d.ts.map +1 -1
  115. package/dist/routers/attendance.js +4 -4
  116. package/dist/routers/attendance.js.map +1 -1
  117. package/dist/routers/auth.d.ts +20 -0
  118. package/dist/routers/auth.d.ts.map +1 -1
  119. package/dist/routers/auth.js +132 -15
  120. package/dist/routers/auth.js.map +1 -1
  121. package/dist/routers/class.d.ts +10 -0
  122. package/dist/routers/class.d.ts.map +1 -1
  123. package/dist/routers/class.js +49 -5
  124. package/dist/routers/class.js.map +1 -1
  125. package/dist/routers/comment.d.ts +2 -0
  126. package/dist/routers/comment.d.ts.map +1 -1
  127. package/dist/routers/conversation.d.ts +2 -0
  128. package/dist/routers/conversation.d.ts.map +1 -1
  129. package/dist/routers/conversation.js +46 -31
  130. package/dist/routers/conversation.js.map +1 -1
  131. package/dist/routers/file.d.ts.map +1 -1
  132. package/dist/routers/file.js +30 -7
  133. package/dist/routers/file.js.map +1 -1
  134. package/dist/routers/labChat.d.ts +2 -0
  135. package/dist/routers/labChat.d.ts.map +1 -1
  136. package/dist/routers/labChat.js +5 -322
  137. package/dist/routers/labChat.js.map +1 -1
  138. package/dist/routers/marketing.d.ts +1 -1
  139. package/dist/routers/message.d.ts +1 -0
  140. package/dist/routers/message.d.ts.map +1 -1
  141. package/dist/routers/message.js +3 -2
  142. package/dist/routers/message.js.map +1 -1
  143. package/dist/routers/newtonChat.d.ts +55 -0
  144. package/dist/routers/newtonChat.d.ts.map +1 -0
  145. package/dist/routers/newtonChat.js +262 -0
  146. package/dist/routers/newtonChat.js.map +1 -0
  147. package/dist/routers/notifications.d.ts +4 -4
  148. package/dist/routers/section.d.ts +19 -4
  149. package/dist/routers/section.d.ts.map +1 -1
  150. package/dist/routers/section.js +26 -8
  151. package/dist/routers/section.js.map +1 -1
  152. package/dist/routers/user.d.ts.map +1 -1
  153. package/dist/routers/user.js +5 -4
  154. package/dist/routers/user.js.map +1 -1
  155. package/dist/routers/worksheet.d.ts +44 -41
  156. package/dist/routers/worksheet.d.ts.map +1 -1
  157. package/dist/routers/worksheet.js +25 -34
  158. package/dist/routers/worksheet.js.map +1 -1
  159. package/dist/seedDatabase.d.ts +1 -1
  160. package/dist/seedDatabase.js +275 -284
  161. package/dist/seedDatabase.js.map +1 -1
  162. package/dist/server/pipelines/aiLabChat.d.ts +21 -0
  163. package/dist/server/pipelines/aiLabChat.d.ts.map +1 -0
  164. package/dist/server/pipelines/aiLabChat.js +456 -0
  165. package/dist/server/pipelines/aiLabChat.js.map +1 -0
  166. package/dist/server/pipelines/aiNewtonChat.d.ts +30 -0
  167. package/dist/server/pipelines/aiNewtonChat.d.ts.map +1 -0
  168. package/dist/server/pipelines/aiNewtonChat.js +280 -0
  169. package/dist/server/pipelines/aiNewtonChat.js.map +1 -0
  170. package/dist/server/pipelines/gradeWorksheet.d.ts +15 -0
  171. package/dist/server/pipelines/gradeWorksheet.d.ts.map +1 -0
  172. package/dist/server/pipelines/gradeWorksheet.js +139 -0
  173. package/dist/server/pipelines/gradeWorksheet.js.map +1 -0
  174. package/dist/trpc.d.ts.map +1 -1
  175. package/dist/trpc.js +2 -2
  176. package/dist/trpc.js.map +1 -1
  177. package/dist/utils/email.d.ts +9 -1
  178. package/dist/utils/email.d.ts.map +1 -1
  179. package/dist/utils/email.js +20 -5
  180. package/dist/utils/email.js.map +1 -1
  181. package/dist/utils/inference.d.ts +5 -0
  182. package/dist/utils/inference.d.ts.map +1 -1
  183. package/dist/utils/inference.js +71 -7
  184. package/dist/utils/inference.js.map +1 -1
  185. package/dist/utils/logger.d.ts.map +1 -1
  186. package/dist/utils/logger.js +3 -3
  187. package/dist/utils/logger.js.map +1 -1
  188. package/docker-compose.yml +14 -0
  189. package/package.json +13 -4
  190. package/prisma/schema.prisma +34 -5
  191. package/scripts/test-pre-push.ts +14 -0
  192. package/src/index.ts +98 -54
  193. package/src/instrument.ts +13 -6
  194. package/src/lib/config/env.ts +126 -0
  195. package/src/lib/fileUpload.ts +3 -2
  196. package/src/lib/googleCloudStorage.ts +6 -6
  197. package/src/lib/jsonConversion.ts +12 -14
  198. package/src/lib/prisma.ts +23 -2
  199. package/src/lib/pusher.ts +6 -5
  200. package/src/middleware/auth.ts +5 -3
  201. package/src/middleware/security.ts +80 -0
  202. package/src/routers/_app.ts +2 -0
  203. package/src/routers/agenda.ts +10 -7
  204. package/src/routers/announcement.ts +4 -2
  205. package/src/routers/assignment.ts +74 -41
  206. package/src/routers/attendance.ts +2 -2
  207. package/src/routers/auth.ts +143 -14
  208. package/src/routers/class.ts +52 -3
  209. package/src/routers/conversation.ts +49 -29
  210. package/src/routers/file.ts +29 -5
  211. package/src/routers/labChat.ts +3 -367
  212. package/src/routers/message.ts +1 -1
  213. package/src/routers/newtonChat.ts +299 -0
  214. package/src/routers/section.ts +26 -6
  215. package/src/routers/user.ts +3 -2
  216. package/src/routers/worksheet.ts +26 -38
  217. package/src/seedDatabase.ts +290 -283
  218. package/src/server/pipelines/aiLabChat.ts +507 -0
  219. package/src/server/pipelines/aiNewtonChat.ts +338 -0
  220. package/src/server/pipelines/gradeWorksheet.ts +151 -0
  221. package/src/trpc.ts +2 -0
  222. package/src/utils/email.ts +30 -3
  223. package/src/utils/inference.ts +85 -5
  224. package/src/utils/logger.ts +2 -1
  225. package/tests/announcement.test.ts +164 -0
  226. package/tests/assignment.test.ts +296 -0
  227. package/tests/attendance.test.ts +168 -0
  228. package/tests/auth.test.ts +33 -10
  229. package/tests/class.test.ts +34 -9
  230. package/tests/event.test.ts +228 -0
  231. package/tests/section.test.ts +216 -0
  232. package/tests/setup.ts +70 -16
  233. package/tests/user.test.ts +158 -0
  234. package/vitest.config.ts +26 -0
  235. package/API_SPECIFICATION.md +0 -1597
  236. package/BASE64_REMOVAL_SUMMARY.md +0 -164
  237. package/CHAT_API_SPEC.md +0 -579
  238. package/LAB_CHAT_API_SPEC.md +0 -518
  239. package/dist/routers/school.d.ts +0 -208
  240. package/dist/routers/school.d.ts.map +0 -1
  241. package/dist/routers/school.js +0 -483
@@ -1 +1 @@
1
- {"version":3,"file":"file.js","sources":["routers/file.ts"],"sourceRoot":"/","sourcesContent":["import { z } from \"zod\";\nimport { createTRPCRouter, protectedProcedure, protectedTeacherProcedure } from \"../trpc.js\";\nimport { TRPCError } from \"@trpc/server\";\nimport { getSignedUrl, deleteFile } from \"../lib/googleCloudStorage.js\";\nimport type { User } from \"@prisma/client\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { logger } from \"../utils/logger.js\";\n\nexport const fileRouter = createTRPCRouter({\n getSignedUrl: protectedProcedure\n .input(z.object({\n fileId: z.string(),\n }))\n .mutation(async ({ ctx, input }) => {\n const { fileId } = input;\n const userId = ctx.user?.id;\n\n if (!userId) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"You must be logged in to access files\",\n });\n }\n\n // Get file metadata from database\n const file = await prisma.file.findUnique({\n where: { id: fileId },\n include: {\n assignment: {\n include: {\n class: {\n include: {\n students: true,\n teachers: true\n }\n }\n }\n },\n submission: {\n include: {\n student: true,\n assignment: {\n include: {\n class: {\n include: {\n teachers: true\n }\n }\n }\n }\n }\n },\n annotations: {\n include: {\n student: true,\n assignment: {\n include: {\n class: {\n include: {\n teachers: true,\n }\n }\n }\n }\n }\n },\n folder: {\n include: {\n class: {\n include: {\n students: true,\n teachers: true\n }\n }\n }\n }\n }\n });\n\n if (!file) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"File does not exist\",\n });\n }\n\n // Check if user has access to this file\n let hasAccess = false;\n\n let classId: string | null = null;\n\n // Check if user is a teacher of the class\n if (file.assignment?.class) {\n classId = file.assignment.class.id;\n hasAccess = file.assignment.class.teachers.some(teacher => teacher.id === userId) || false;\n }\n\n if (file.submission?.assignment?.classId) {\n classId = file.submission.assignment.classId;\n hasAccess = file.submission?.studentId === userId || false;\n if (!hasAccess) hasAccess = file.submission.assignment.class.teachers.some(teacher => teacher.id === userId) || false;\n }\n\n if (file.annotations?.assignment?.classId) {\n classId = file.annotations?.assignment.classId;\n hasAccess = file.annotations?.studentId === userId || false;\n if (!hasAccess) hasAccess = file.annotations.assignment.class.teachers.some(teacher => teacher.id === userId) || false;\n }\n\n // Check if user is the file owner\n if (file.userId === userId) {\n hasAccess = true;\n }\n\n // Check if file is in a folder and user has access to the class\n if (file.folder?.class) {\n hasAccess = hasAccess || file.folder.class.teachers.some(teacher => teacher.id === userId);\n hasAccess = hasAccess || file.folder.class.students.some(student => student.id === userId);\n }\n\n if (!hasAccess) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You do not have access to this file\",\n });\n }\n\n try {\n const signedUrl = await getSignedUrl(file.path);\n return { url: signedUrl };\n } catch (error) {\n logger.error('Error generating signed URL:', error as Record<string, any>);\n throw new TRPCError({\n code: \"INTERNAL_SERVER_ERROR\",\n message: \"Failed to generate download URL\",\n });\n }\n }),\n\n move: protectedTeacherProcedure\n .input(z.object({\n fileId: z.string(),\n targetFolderId: z.string(),\n classId: z.string(),\n }))\n .mutation(async ({ ctx, input }) => {\n const { fileId, targetFolderId } = input;\n\n // Get the file\n const file = await prisma.file.findUnique({\n where: { id: fileId },\n include: {\n folder: {\n include: {\n class: true,\n },\n },\n },\n });\n\n if (!file) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"File not found\",\n });\n }\n\n // Get the target folder\n const targetFolder = await prisma.folder.findUnique({\n where: { id: targetFolderId },\n include: {\n class: true,\n },\n });\n\n if (!targetFolder) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Target folder not found\",\n });\n }\n\n // Move the file\n const updatedFile = await prisma.file.update({\n where: { id: fileId },\n data: {\n folderId: targetFolderId,\n },\n include: {\n user: {\n select: {\n id: true,\n username: true,\n },\n },\n },\n });\n\n return updatedFile;\n }),\n\n rename: protectedTeacherProcedure\n .input(z.object({\n fileId: z.string(),\n newName: z.string(),\n classId: z.string(),\n }))\n .mutation(async ({ ctx, input }) => {\n const { fileId, newName, classId } = input;\n\n // Verify user is a teacher of the class\n const classData = await prisma.class.findFirst({\n where: {\n id: classId,\n teachers: {\n some: {\n id: ctx.user!.id,\n },\n },\n },\n });\n\n if (!classData) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You must be a teacher of this class to rename files\",\n });\n }\n\n // Get the file\n const file = await prisma.file.findUnique({\n where: { id: fileId },\n include: {\n folder: {\n include: {\n class: true,\n },\n },\n },\n });\n\n if (!file) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"File not found\",\n });\n }\n\n // Validate new name\n if (!newName.trim()) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"File name cannot be empty\",\n });\n }\n\n // Rename the file\n const updatedFile = await prisma.file.update({\n where: { id: fileId },\n data: {\n name: newName.trim(),\n },\n include: {\n user: {\n select: {\n id: true,\n username: true,\n },\n },\n },\n });\n\n return updatedFile;\n }),\n\n delete: protectedTeacherProcedure\n .input(z.object({\n fileId: z.string(),\n classId: z.string(),\n }))\n .mutation(async ({ ctx, input }) => {\n const { fileId, classId } = input;\n\n // Verify user is a teacher of the class\n const classData = await prisma.class.findFirst({\n where: {\n id: classId,\n teachers: {\n some: {\n id: ctx.user!.id,\n },\n },\n },\n });\n\n if (!classData) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You must be a teacher of this class to delete files\",\n });\n }\n\n // Get the file\n const file = await prisma.file.findUnique({\n where: { id: fileId },\n include: {\n folder: {\n include: {\n class: true,\n },\n },\n thumbnail: true,\n },\n });\n\n if (!file) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"File not found\",\n });\n }\n\n // Verify the file belongs to this class\n if (file.folder?.classId !== classId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"File does not belong to this class\",\n });\n }\n\n // Delete files from storage (only if they were actually uploaded)\n try {\n // Only delete from GCS if the file was successfully uploaded\n if (file.uploadStatus === 'COMPLETED') {\n // Delete the main file\n await deleteFile(file.path);\n \n // Delete thumbnail if it exists\n if (file.thumbnail) {\n await deleteFile(file.thumbnail.path);\n }\n }\n } catch (error) {\n logger.warn(`Failed to delete file ${file.path}:`, error as Record<string, any>);\n }\n\n // Delete the file record from database\n await prisma.file.delete({\n where: { id: fileId },\n });\n\n return { success: true };\n }),\n}); "],"names":[],"mappings":"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","debug_id":"62159ec7-261d-56bc-9984-ce30e5f341c1"}
1
+ {"version":3,"file":"file.js","sources":["routers/file.ts"],"sourceRoot":"/","sourcesContent":["import { z } from \"zod\";\nimport { createTRPCRouter, protectedProcedure, protectedTeacherProcedure } from \"../trpc.js\";\nimport { TRPCError } from \"@trpc/server\";\nimport { getSignedUrl, deleteFile } from \"../lib/googleCloudStorage.js\";\nimport type { User } from \"@prisma/client\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { logger } from \"../utils/logger.js\";\n\nexport const fileRouter = createTRPCRouter({\n getSignedUrl: protectedProcedure\n .input(z.object({\n fileId: z.string(),\n }))\n .mutation(async ({ ctx, input }) => {\n const { fileId } = input;\n const userId = ctx.user?.id;\n\n if (!userId) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"You must be logged in to access files\",\n });\n }\n\n // Get file metadata from database\n const file = await prisma.file.findUnique({\n where: { id: fileId },\n include: {\n assignment: {\n include: {\n class: {\n include: {\n students: true,\n teachers: true\n }\n }\n }\n },\n submission: {\n include: {\n student: true,\n assignment: {\n include: {\n class: {\n include: {\n teachers: true\n }\n }\n }\n }\n }\n },\n annotations: {\n include: {\n student: true,\n assignment: {\n include: {\n class: {\n include: {\n teachers: true,\n }\n }\n }\n }\n }\n },\n folder: {\n include: {\n class: {\n include: {\n students: true,\n teachers: true\n }\n }\n }\n },\n announcement: {\n include: {\n class: {\n include: {\n students: true,\n teachers: true\n }\n }\n }\n }\n }\n });\n\n if (!file) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"File does not exist\",\n });\n }\n\n // Check if user has access to this file\n let hasAccess = false;\n\n let classId: string | null = null;\n\n // Check if user is a teacher of the class\n if (file.assignment?.class) {\n classId = file.assignment.class.id;\n const isTeacher = file.assignment.class.teachers.some(teacher => teacher.id === userId);\n const isStudent = file.assignment.class.students.some(student => student.id === userId);\n logger.info(`Assignment file access check - userId: ${userId}, isTeacher: ${isTeacher}, isStudent: ${isStudent}`);\n hasAccess = isTeacher || isStudent;\n }\n\n // Check if user has access to announcement files (teachers or students in the class)\n if ((file as any).announcement?.class) {\n classId = (file as any).announcement.class.id;\n const isTeacher = (file as any).announcement.class.teachers.some((teacher: any) => teacher.id === userId);\n const isStudent = (file as any).announcement.class.students.some((student: any) => student.id === userId);\n logger.info(`Announcement file access check - userId: ${userId}, isTeacher: ${isTeacher}, isStudent: ${isStudent}`);\n hasAccess = hasAccess || isTeacher || isStudent;\n }\n\n if (file.submission?.assignment?.classId) {\n classId = file.submission.assignment.classId;\n hasAccess = hasAccess || file.submission?.studentId === userId || false;\n if (!hasAccess) hasAccess = file.submission.assignment.class.teachers.some(teacher => teacher.id === userId) || false;\n }\n\n if (file.annotations?.assignment?.classId) {\n classId = file.annotations?.assignment.classId;\n hasAccess = hasAccess || file.annotations?.studentId === userId || false;\n if (!hasAccess) hasAccess = file.annotations.assignment.class.teachers.some(teacher => teacher.id === userId) || false;\n }\n\n // Check if user is the file owner\n if (file.userId === userId) {\n hasAccess = true;\n }\n\n // Check if file is in a folder and user has access to the class\n if (file.folder?.class) {\n const isTeacher = file.folder.class.teachers.some(teacher => teacher.id === userId);\n const isStudent = file.folder.class.students.some(student => student.id === userId);\n hasAccess = hasAccess || isTeacher;\n hasAccess = hasAccess || isStudent;\n }\n\n if (!hasAccess) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You do not have access to this file\",\n });\n }\n\n try {\n const signedUrl = await getSignedUrl(file.path);\n return { url: signedUrl };\n } catch (error) {\n logger.error('Error generating signed URL:', error as Record<string, any>);\n throw new TRPCError({\n code: \"INTERNAL_SERVER_ERROR\",\n message: \"Failed to generate download URL\",\n });\n }\n }),\n\n move: protectedTeacherProcedure\n .input(z.object({\n fileId: z.string(),\n targetFolderId: z.string(),\n classId: z.string(),\n }))\n .mutation(async ({ ctx, input }) => {\n const { fileId, targetFolderId } = input;\n\n // Get the file\n const file = await prisma.file.findUnique({\n where: { id: fileId },\n include: {\n folder: {\n include: {\n class: true,\n },\n },\n },\n });\n\n if (!file) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"File not found\",\n });\n }\n\n // Get the target folder\n const targetFolder = await prisma.folder.findUnique({\n where: { id: targetFolderId },\n include: {\n class: true,\n },\n });\n\n if (!targetFolder) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Target folder not found\",\n });\n }\n\n // Move the file\n const updatedFile = await prisma.file.update({\n where: { id: fileId },\n data: {\n folderId: targetFolderId,\n },\n include: {\n user: {\n select: {\n id: true,\n username: true,\n },\n },\n },\n });\n\n return updatedFile;\n }),\n\n rename: protectedTeacherProcedure\n .input(z.object({\n fileId: z.string(),\n newName: z.string(),\n classId: z.string(),\n }))\n .mutation(async ({ ctx, input }) => {\n const { fileId, newName, classId } = input;\n\n // Verify user is a teacher of the class\n const classData = await prisma.class.findFirst({\n where: {\n id: classId,\n teachers: {\n some: {\n id: ctx.user!.id,\n },\n },\n },\n });\n\n if (!classData) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You must be a teacher of this class to rename files\",\n });\n }\n\n // Get the file\n const file = await prisma.file.findUnique({\n where: { id: fileId },\n include: {\n folder: {\n include: {\n class: true,\n },\n },\n },\n });\n\n if (!file) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"File not found\",\n });\n }\n\n // Validate new name\n if (!newName.trim()) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"File name cannot be empty\",\n });\n }\n\n // Rename the file\n const updatedFile = await prisma.file.update({\n where: { id: fileId },\n data: {\n name: newName.trim(),\n },\n include: {\n user: {\n select: {\n id: true,\n username: true,\n },\n },\n },\n });\n\n return updatedFile;\n }),\n\n delete: protectedTeacherProcedure\n .input(z.object({\n fileId: z.string(),\n classId: z.string(),\n }))\n .mutation(async ({ ctx, input }) => {\n const { fileId, classId } = input;\n\n // Verify user is a teacher of the class\n const classData = await prisma.class.findFirst({\n where: {\n id: classId,\n teachers: {\n some: {\n id: ctx.user!.id,\n },\n },\n },\n });\n\n if (!classData) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You must be a teacher of this class to delete files\",\n });\n }\n\n // Get the file\n const file = await prisma.file.findUnique({\n where: { id: fileId },\n include: {\n folder: {\n include: {\n class: true,\n },\n },\n thumbnail: true,\n },\n });\n\n if (!file) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"File not found\",\n });\n }\n\n // Verify the file belongs to this class\n if (file.folder?.classId !== classId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"File does not belong to this class\",\n });\n }\n\n // Delete files from storage (only if they were actually uploaded)\n try {\n // Only delete from GCS if the file was successfully uploaded\n if (file.uploadStatus === 'COMPLETED') {\n // Delete the main file\n await deleteFile(file.path);\n \n // Delete thumbnail if it exists\n if (file.thumbnail) {\n await deleteFile(file.thumbnail.path);\n }\n }\n } catch (error) {\n logger.warn(`Failed to delete file ${file.path}:`, error as Record<string, any>);\n }\n\n // Delete the file record from database\n await prisma.file.delete({\n where: { id: fileId },\n });\n\n return { success: true };\n }),\n}); "],"names":[],"mappings":"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","debug_id":"dcd6fb91-1e37-5e3a-a751-065b78566254"}
@@ -159,6 +159,8 @@ export declare const labChatRouter: import("@trpc/server").TRPCBuiltRouter<{
159
159
  } | null;
160
160
  };
161
161
  } & {
162
+ status: import(".prisma/client").$Enums.GenerationStatus | null;
163
+ meta: import("@prisma/client/runtime/library.js").JsonValue | null;
162
164
  id: string;
163
165
  content: string;
164
166
  createdAt: Date;
@@ -1 +1 @@
1
- {"version":3,"file":"labChat.d.ts","sourceRoot":"/","sources":["routers/labChat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAiBxB,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAonBxB,CAAC"}
1
+ {"version":3,"file":"labChat.d.ts","sourceRoot":"/","sources":["routers/labChat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+mBxB,CAAC"}
@@ -1,16 +1,12 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="7dd48cd3-b996-5dc5-bd2d-c8e1531645d1")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="933db03e-fc38-599b-93a4-890502ba8281")}catch(e){}}();
3
3
  import { z } from 'zod';
4
4
  import { createTRPCRouter, protectedProcedure } from '../trpc.js';
5
5
  import { prisma } from '../lib/prisma.js';
6
6
  import { pusher } from '../lib/pusher.js';
7
7
  import { TRPCError } from '@trpc/server';
8
- import { inferenceClient, sendAIMessage } from '../utils/inference.js';
9
- import { logger } from '../utils/logger.js';
10
8
  import { isAIUser } from '../utils/aiUser.js';
11
- import { bucket } from '../lib/googleCloudStorage.js';
12
- import { createPdf } from "../lib/jsonConversion.js";
13
- import { v4 as uuidv4 } from "uuid";
9
+ import { generateAndSendLabIntroduction, generateAndSendLabResponse } from '../server/pipelines/aiLabChat.js';
14
10
  export const labChatRouter = createTRPCRouter({
15
11
  create: protectedProcedure
16
12
  .input(z.object({
@@ -150,9 +146,7 @@ export const labChatRouter = createTRPCRouter({
150
146
  return labChat;
151
147
  });
152
148
  // Generate AI introduction message in parallel (don't await - fire and forget)
153
- generateAndSendLabIntroduction(result.id, result.conversationId, context, classWithTeachers.subject || 'Lab').catch(error => {
154
- logger.error('Failed to generate AI introduction:', { error, labChatId: result.id });
155
- });
149
+ generateAndSendLabIntroduction(result.id, result.conversationId, context, classWithTeachers.subject || 'Lab');
156
150
  // Broadcast lab chat creation to class members
157
151
  try {
158
152
  await pusher.trigger(`class-${classId}`, 'lab-chat-created', {
@@ -518,9 +512,7 @@ export const labChatRouter = createTRPCRouter({
518
512
  // Generate AI response in parallel (don't await - fire and forget)
519
513
  if (!isAIUser(userId)) {
520
514
  // Run AI response generation in background
521
- generateAndSendLabResponse(labChatId, content, labChat.conversationId).catch(error => {
522
- logger.error('Failed to generate AI response:', { error });
523
- });
515
+ generateAndSendLabResponse(labChatId, content, labChat.conversationId);
524
516
  }
525
517
  return {
526
518
  id: result.id,
@@ -595,314 +587,5 @@ export const labChatRouter = createTRPCRouter({
595
587
  return { success: true };
596
588
  }),
597
589
  });
598
- /**
599
- * Generate and send AI introduction for lab chat
600
- * Uses the stored context directly from database
601
- */
602
- async function generateAndSendLabIntroduction(labChatId, conversationId, contextString, subject) {
603
- try {
604
- // Enhance the stored context with clarifying question instructions
605
- const enhancedSystemPrompt = `${contextString}
606
-
607
- IMPORTANT INSTRUCTIONS:
608
- - You are helping teachers create course materials
609
- - Use the context information provided above (subject, topic, difficulty, objectives, etc.) as your foundation
610
- - Only ask clarifying questions about details NOT already specified in the context
611
- - Focus your questions on format preferences, specific requirements, or missing details needed to create the content
612
- - Only output final course materials when you have sufficient details beyond what's in the context
613
- - Do not use markdown formatting in your responses - use plain text only
614
- - When creating content, make it clear and well-structured without markdown`;
615
- const completion = await inferenceClient.chat.completions.create({
616
- model: 'command-a-03-2025',
617
- messages: [
618
- { role: 'system', content: enhancedSystemPrompt },
619
- {
620
- role: 'user',
621
- content: 'Please introduce yourself to the teaching team. Explain that you will help create course materials by first asking clarifying questions based on the context provided, and only output final content when you have enough information.'
622
- },
623
- ],
624
- max_tokens: 300,
625
- temperature: 0.8,
626
- });
627
- const response = completion.choices[0]?.message?.content;
628
- if (!response) {
629
- throw new Error('No response generated from inference API');
630
- }
631
- // Send AI introduction using centralized sender
632
- await sendAIMessage(response, conversationId, {
633
- subject,
634
- });
635
- logger.info('AI Introduction sent', { labChatId, conversationId });
636
- }
637
- catch (error) {
638
- logger.error('Failed to generate AI introduction:', { error, labChatId });
639
- // Send fallback introduction
640
- try {
641
- const fallbackIntro = `Hello teaching team! I'm your AI assistant for course material development. I will help you create educational content by first asking clarifying questions based on the provided context, then outputting final materials when I have sufficient information. I won't use markdown formatting in my responses. What would you like to work on?`;
642
- await sendAIMessage(fallbackIntro, conversationId, {
643
- subject,
644
- });
645
- logger.info('Fallback AI introduction sent', { labChatId });
646
- }
647
- catch (fallbackError) {
648
- logger.error('Failed to send fallback AI introduction:', { error: fallbackError, labChatId });
649
- }
650
- }
651
- }
652
- /**
653
- * Generate and send AI response to teacher message
654
- * Uses the stored context directly from database
655
- */
656
- async function generateAndSendLabResponse(labChatId, teacherMessage, conversationId) {
657
- try {
658
- // Get lab context from database
659
- const fullLabChat = await prisma.labChat.findUnique({
660
- where: { id: labChatId },
661
- include: {
662
- class: {
663
- select: {
664
- name: true,
665
- subject: true,
666
- },
667
- },
668
- },
669
- });
670
- if (!fullLabChat) {
671
- throw new Error('Lab chat not found');
672
- }
673
- // Get recent conversation history
674
- const recentMessages = await prisma.message.findMany({
675
- where: {
676
- conversationId,
677
- },
678
- include: {
679
- sender: {
680
- select: {
681
- id: true,
682
- username: true,
683
- profile: {
684
- select: {
685
- displayName: true,
686
- },
687
- },
688
- },
689
- },
690
- },
691
- orderBy: {
692
- createdAt: 'desc',
693
- },
694
- take: 10, // Last 10 messages for context
695
- });
696
- // Build conversation history as proper message objects
697
- // Enhance the stored context with clarifying question instructions
698
- const enhancedSystemPrompt = `${fullLabChat.context}
699
-
700
- IMPORTANT INSTRUCTIONS:
701
- - Use the context information provided above (subject, topic, difficulty, objectives, etc.) as your foundation
702
- - Based on the teacher's input and existing context, only ask clarifying questions about details NOT already specified
703
- - Focus questions on format preferences, specific requirements, quantity, or missing implementation details
704
- - Only output final course materials when you have sufficient details beyond what's in the context
705
- - Do not use markdown formatting in your responses - use plain text only
706
- - When you do create content, make it clear and well-structured without markdown
707
- - If the request is vague, ask 1-2 specific clarifying questions about missing details only
708
- - You are primarily a chatbot - only provide files when it is necessary
709
-
710
- RESPONSE FORMAT:
711
- - Always respond with JSON in this format: { "text": string, "docs": null | array }
712
- - "text": Your conversational response (questions, explanations, etc.) - use plain text, no markdown
713
- - "docs": null for regular conversation, or array of PDF document objects when creating course materials
714
-
715
- WHEN CREATING COURSE MATERIALS (docs field):
716
- - docs: [ { "title": string, "blocks": [ { "format": <int 0-12>, "content": string | string[], "metadata"?: { fontSize?: number, lineHeight?: number, paragraphSpacing?: number, indentWidth?: number, paddingX?: number, paddingY?: number, font?: 0|1|2|3|4|5, color?: "#RGB"|"#RRGGBB", background?: "#RGB"|"#RRGGBB", align?: "left"|"center"|"right" } } ] } ]
717
- - Each document in the array should have a "title" (used for filename) and "blocks" (content)
718
- - You can create multiple documents when it makes sense (e.g., separate worksheets, answer keys, different topics)
719
- - Use descriptive titles like "Biology_Cell_Structure_Worksheet" or "Chemistry_Lab_Instructions"
720
- - Format enum (integers): 0=HEADER_1, 1=HEADER_2, 2=HEADER_3, 3=HEADER_4, 4=HEADER_5, 5=HEADER_6, 6=PARAGRAPH, 7=BULLET, 8=NUMBERED, 9=TABLE, 10=IMAGE, 11=CODE_BLOCK, 12=QUOTE
721
- - Fonts enum: 0=TIMES_ROMAN, 1=COURIER, 2=HELVETICA, 3=HELVETICA_BOLD, 4=HELVETICA_ITALIC, 5=HELVETICA_BOLD_ITALIC
722
- - Colors must be hex strings: "#RGB" or "#RRGGBB".
723
- - Headings (0-5): content is a single string; you may set metadata.align.
724
- - Paragraphs (6) and Quotes (12): content is a single string.
725
- - Bullets (7) and Numbered (8): content is an array of strings (one item per list entry). DO NOT include bullet symbols (*) or numbers (1. 2. 3.) in the content - the format will automatically add these.
726
- - Code blocks (11): prefer content as an array of lines; preserve indentation via leading tabs/spaces. If using a single string, include \n between lines.
727
- - Table (9) and Image (10) are not supported by the renderer now; do not emit them.
728
- - Use metadata sparingly; omit fields you don't need. For code blocks you may set metadata.paddingX, paddingY, background, and font (1 for Courier).
729
- - Wrap text naturally; do not insert manual line breaks except where semantically required (lists, code).
730
- - The JSON must be valid and ready for PDF rendering by the server.`;
731
- const messages = [
732
- { role: 'system', content: enhancedSystemPrompt },
733
- ];
734
- // Add recent conversation history
735
- recentMessages.reverse().forEach(msg => {
736
- const role = isAIUser(msg.senderId) ? 'assistant' : 'user';
737
- const senderName = msg.sender?.profile?.displayName || msg.sender?.username || 'Teacher';
738
- const content = isAIUser(msg.senderId) ? msg.content : `${senderName}: ${msg.content}`;
739
- messages.push({
740
- role: role,
741
- content,
742
- });
743
- });
744
- // Add the new teacher message
745
- const senderName = 'Teacher'; // We could get this from the actual sender if needed
746
- messages.push({
747
- role: 'user',
748
- content: `${senderName}: ${teacherMessage}`,
749
- });
750
- const completion = await inferenceClient.chat.completions.create({
751
- model: 'command-a-03-2025',
752
- messages,
753
- temperature: 0.7,
754
- response_format: {
755
- type: "json_object",
756
- // @ts-expect-error
757
- schema: {
758
- type: "object",
759
- properties: {
760
- text: { type: "string" },
761
- docs: {
762
- type: "array",
763
- items: {
764
- type: "object",
765
- properties: {
766
- title: { type: "string" },
767
- blocks: {
768
- type: "array",
769
- items: {
770
- type: "object",
771
- properties: {
772
- format: { type: "integer", minimum: 0, maximum: 12 },
773
- content: {
774
- oneOf: [
775
- { type: "string" },
776
- { type: "array", items: { type: "string" } }
777
- ]
778
- },
779
- metadata: {
780
- type: "object",
781
- properties: {
782
- fontSize: { type: "number", minimum: 6 },
783
- lineHeight: { type: "number", minimum: 0.6 },
784
- paragraphSpacing: { type: "number", minimum: 0 },
785
- indentWidth: { type: "number", minimum: 0 },
786
- paddingX: { type: "number", minimum: 0 },
787
- paddingY: { type: "number", minimum: 0 },
788
- font: { type: "integer", minimum: 0, maximum: 5 },
789
- color: { type: "string", pattern: "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" },
790
- background: { type: "string", pattern: "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" },
791
- align: { type: "string", enum: ["left", "center", "right"] }
792
- },
793
- additionalProperties: false
794
- }
795
- },
796
- required: ["format", "content"],
797
- additionalProperties: false
798
- }
799
- }
800
- },
801
- required: ["title", "blocks"],
802
- additionalProperties: false
803
- }
804
- }
805
- },
806
- required: ["text"],
807
- additionalProperties: false
808
- }
809
- },
810
- });
811
- const response = completion.choices[0]?.message?.content;
812
- if (!response) {
813
- throw new Error('No response generated from inference API');
814
- }
815
- // Parse the JSON response and generate PDF if docs are provided
816
- try {
817
- const jsonData = JSON.parse(response);
818
- const attachmentIds = [];
819
- // Generate PDFs if docs are provided
820
- if (jsonData.docs && Array.isArray(jsonData.docs)) {
821
- console.log('Generating PDFs', { labChatId, docs: JSON.stringify(jsonData.docs, null, 2) });
822
- for (let i = 0; i < jsonData.docs.length; i++) {
823
- const doc = jsonData.docs[i];
824
- if (!doc.title || !doc.blocks || !Array.isArray(doc.blocks)) {
825
- logger.error(`Document ${i + 1} is missing title or blocks`);
826
- continue;
827
- }
828
- try {
829
- let pdfBytes = await createPdf(doc.blocks);
830
- if (pdfBytes) {
831
- // Sanitize filename - remove special characters and limit length
832
- const sanitizedTitle = doc.title
833
- .replace(/[^a-zA-Z0-9\s\-_]/g, '')
834
- .replace(/\s+/g, '_')
835
- .substring(0, 50);
836
- const filename = `${sanitizedTitle}_${uuidv4().substring(0, 8)}.pdf`;
837
- const filePath = `class/generated/${fullLabChat.classId}/${filename}`;
838
- logger.info(`PDF ${i + 1} generated successfully`, { labChatId, title: doc.title });
839
- // Upload directly to Google Cloud Storage
840
- const gcsFile = bucket.file(filePath);
841
- await gcsFile.save(Buffer.from(pdfBytes), {
842
- metadata: {
843
- contentType: 'application/pdf',
844
- }
845
- });
846
- logger.info(`PDF ${i + 1} uploaded successfully`, { labChatId, filename });
847
- const file = await prisma.file.create({
848
- data: {
849
- name: filename,
850
- path: filePath,
851
- type: 'application/pdf',
852
- size: pdfBytes.length,
853
- userId: fullLabChat.createdById,
854
- uploadStatus: 'COMPLETED',
855
- uploadedAt: new Date(),
856
- },
857
- });
858
- attachmentIds.push(file.id);
859
- }
860
- else {
861
- logger.error(`PDF ${i + 1} creation returned undefined/null`, { labChatId, title: doc.title });
862
- }
863
- }
864
- catch (pdfError) {
865
- logger.error(`PDF creation threw an error for document ${i + 1}:`, {
866
- error: pdfError instanceof Error ? {
867
- message: pdfError.message,
868
- stack: pdfError.stack,
869
- name: pdfError.name
870
- } : pdfError,
871
- labChatId,
872
- title: doc.title
873
- });
874
- }
875
- }
876
- }
877
- // Send the text response to the conversation
878
- await sendAIMessage(jsonData.text || response, conversationId, {
879
- attachments: {
880
- connect: attachmentIds.map(id => ({ id })),
881
- },
882
- subject: fullLabChat.class?.subject || 'Lab',
883
- });
884
- }
885
- catch (parseError) {
886
- logger.error('Failed to parse AI response or generate PDF:', { error: parseError, labChatId });
887
- // Fallback: send the raw response if parsing fails
888
- await sendAIMessage(response, conversationId, {
889
- subject: fullLabChat.class?.subject || 'Lab',
890
- });
891
- }
892
- logger.info('AI response sent', { labChatId, conversationId });
893
- }
894
- catch (error) {
895
- console.error('Full error object:', error);
896
- logger.error('Failed to generate AI response:', {
897
- error: error instanceof Error ? {
898
- message: error.message,
899
- stack: error.stack,
900
- name: error.name
901
- } : error,
902
- labChatId
903
- });
904
- throw error; // Re-throw to see the full error in the calling function
905
- }
906
- }
907
590
  //# sourceMappingURL=labChat.js.map
908
- //# debugId=7dd48cd3-b996-5dc5-bd2d-c8e1531645d1
591
+ //# debugId=933db03e-fc38-599b-93a4-890502ba8281