@goscribe/server 1.3.4 → 1.6.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.
Files changed (383) hide show
  1. package/.env.example +12 -0
  2. package/.vscode/settings.json +3 -0
  3. package/REFACTOR_NOTES.md +60 -0
  4. package/TESTING_PROMPT.md +225 -0
  5. package/dist/context.d.ts +14 -1
  6. package/dist/context.js +23 -2
  7. package/dist/controllers/admin.controller.d.ts +715 -0
  8. package/dist/controllers/admin.controller.js +9 -0
  9. package/dist/controllers/annotations.controller.d.ts +439 -0
  10. package/dist/controllers/annotations.controller.js +9 -0
  11. package/dist/controllers/app-router.controller.d.ts +3011 -0
  12. package/dist/controllers/app-router.controller.js +38 -0
  13. package/dist/controllers/app-router.controller.test.d.ts +1 -0
  14. package/dist/controllers/app-router.controller.test.js +36 -0
  15. package/dist/controllers/auth.controller.d.ts +323 -0
  16. package/dist/controllers/auth.controller.js +9 -0
  17. package/dist/controllers/base.controller.d.ts +4 -0
  18. package/dist/controllers/base.controller.js +5 -0
  19. package/dist/controllers/chat.controller.d.ts +341 -0
  20. package/dist/controllers/chat.controller.js +9 -0
  21. package/dist/controllers/copilot.controller.d.ts +397 -0
  22. package/dist/controllers/copilot.controller.js +9 -0
  23. package/dist/controllers/flashcards.controller.d.ts +651 -0
  24. package/dist/controllers/flashcards.controller.js +9 -0
  25. package/dist/controllers/members.controller.d.ts +339 -0
  26. package/dist/controllers/members.controller.js +9 -0
  27. package/dist/controllers/notifications.controller.d.ts +199 -0
  28. package/dist/controllers/notifications.controller.js +9 -0
  29. package/dist/controllers/payment.controller.d.ts +181 -0
  30. package/dist/controllers/payment.controller.js +9 -0
  31. package/dist/controllers/podcast.controller.d.ts +575 -0
  32. package/dist/controllers/podcast.controller.js +9 -0
  33. package/dist/controllers/router-module.controller.d.ts +5 -0
  34. package/dist/controllers/router-module.controller.js +6 -0
  35. package/dist/controllers/studyguide.controller.d.ts +73 -0
  36. package/dist/controllers/studyguide.controller.js +9 -0
  37. package/dist/controllers/worksheets.controller.d.ts +829 -0
  38. package/dist/controllers/worksheets.controller.js +9 -0
  39. package/dist/controllers/workspace.controller.d.ts +1207 -0
  40. package/dist/controllers/workspace.controller.js +9 -0
  41. package/dist/lib/activity_human_description.test.js +16 -15
  42. package/dist/lib/activity_log_service.test.js +28 -23
  43. package/dist/lib/ai/config.d.ts +20 -0
  44. package/dist/lib/ai/config.js +31 -0
  45. package/dist/lib/ai/embedding-client.d.ts +8 -0
  46. package/dist/lib/ai/embedding-client.js +30 -0
  47. package/dist/lib/ai/index.d.ts +48 -0
  48. package/dist/lib/ai/index.js +29 -0
  49. package/dist/lib/ai/inference-backend/client.d.ts +28 -0
  50. package/dist/lib/ai/inference-backend/client.js +301 -0
  51. package/dist/lib/ai/inference-backend/mocks.d.ts +12 -0
  52. package/dist/lib/ai/inference-backend/mocks.js +133 -0
  53. package/dist/lib/ai/inference-backend/types.d.ts +44 -0
  54. package/dist/lib/ai/inference-backend/types.js +1 -0
  55. package/dist/lib/ai/json-parse.d.ts +2 -0
  56. package/dist/lib/ai/json-parse.js +34 -0
  57. package/dist/lib/ai/llm-client.d.ts +7 -0
  58. package/dist/lib/ai/llm-client.js +36 -0
  59. package/dist/lib/ai/mock.d.ts +2 -0
  60. package/dist/lib/ai/mock.js +10 -0
  61. package/dist/lib/ai/types.d.ts +9 -0
  62. package/dist/lib/ai/types.js +1 -0
  63. package/dist/lib/chunking.d.ts +19 -0
  64. package/dist/lib/chunking.js +47 -0
  65. package/dist/lib/curated-kb-seed.d.ts +12 -0
  66. package/dist/lib/curated-kb-seed.js +155 -0
  67. package/dist/lib/email.js +67 -108
  68. package/dist/lib/embeddings.d.ts +2 -0
  69. package/dist/lib/embeddings.js +1 -0
  70. package/dist/lib/ensure-curated-kb-catalog.d.ts +6 -0
  71. package/dist/lib/ensure-curated-kb-catalog.js +53 -0
  72. package/dist/lib/env.d.ts +1 -5
  73. package/dist/lib/env.js +2 -7
  74. package/dist/lib/inference.d.ts +1 -8
  75. package/dist/lib/inference.js +1 -19
  76. package/dist/lib/kb-meta.d.ts +8 -0
  77. package/dist/lib/kb-meta.js +77 -0
  78. package/dist/lib/note-text.d.ts +1 -0
  79. package/dist/lib/note-text.js +47 -0
  80. package/dist/lib/notification-service.test.js +37 -36
  81. package/dist/lib/pdf.d.ts +11 -0
  82. package/dist/lib/pdf.js +11 -0
  83. package/dist/lib/usage_service.d.ts +2 -1
  84. package/dist/lib/usage_service.js +30 -12
  85. package/dist/lib/worksheet-generation.js +4 -4
  86. package/dist/lib/worksheet-generation.test.js +32 -17
  87. package/dist/lib/workspace-kb.d.ts +5 -0
  88. package/dist/lib/workspace-kb.js +7 -0
  89. package/dist/models/controller-context.model.d.ts +8 -0
  90. package/dist/models/controller-context.model.js +1 -0
  91. package/dist/repositories/artifact.repository.d.ts +60 -0
  92. package/dist/repositories/artifact.repository.js +40 -0
  93. package/dist/repositories/base.repository.d.ts +14 -0
  94. package/dist/repositories/base.repository.js +14 -0
  95. package/dist/repositories/invitation.repository.d.ts +94 -0
  96. package/dist/repositories/invitation.repository.js +44 -0
  97. package/dist/repositories/notification.repository.d.ts +72 -0
  98. package/dist/repositories/notification.repository.js +44 -0
  99. package/dist/repositories/router-module.repository.d.ts +10 -0
  100. package/dist/repositories/router-module.repository.js +14 -0
  101. package/dist/repositories/user.repository.d.ts +74 -0
  102. package/dist/repositories/user.repository.js +37 -0
  103. package/dist/repositories/workspace-member.repository.d.ts +31 -0
  104. package/dist/repositories/workspace-member.repository.js +31 -0
  105. package/dist/repositories/workspace.repository.d.ts +97 -0
  106. package/dist/repositories/workspace.repository.js +79 -0
  107. package/dist/routers/_app.d.ts +566 -111
  108. package/dist/routers/_app.js +4 -0
  109. package/dist/routers/admin.d.ts +0 -4
  110. package/dist/routers/admin.js +21 -549
  111. package/dist/routers/annotations.js +12 -170
  112. package/dist/routers/artifactVersions.d.ts +65 -0
  113. package/dist/routers/artifactVersions.js +14 -0
  114. package/dist/routers/auth.d.ts +0 -6
  115. package/dist/routers/auth.js +37 -422
  116. package/dist/routers/chat.js +15 -229
  117. package/dist/routers/copilot.d.ts +14 -13
  118. package/dist/routers/copilot.js +13 -532
  119. package/dist/routers/flashcards.d.ts +17 -6
  120. package/dist/routers/flashcards.js +23 -349
  121. package/dist/routers/knowledgeBase.d.ts +421 -0
  122. package/dist/routers/knowledgeBase.js +118 -0
  123. package/dist/routers/members.d.ts +0 -41
  124. package/dist/routers/members.js +22 -710
  125. package/dist/routers/notes.d.ts +94 -0
  126. package/dist/routers/notes.js +37 -0
  127. package/dist/routers/notifications.js +7 -109
  128. package/dist/routers/payment.d.ts +2 -12
  129. package/dist/routers/payment.js +11 -393
  130. package/dist/routers/podcast.d.ts +1 -1
  131. package/dist/routers/podcast.js +11 -784
  132. package/dist/routers/studyguide.js +3 -129
  133. package/dist/routers/worksheets.d.ts +29 -14
  134. package/dist/routers/worksheets.js +49 -628
  135. package/dist/routers/workspace.d.ts +27 -71
  136. package/dist/routers/workspace.js +29 -922
  137. package/dist/scripts/purge-deleted-users.js +2 -2
  138. package/dist/server.js +10 -3
  139. package/dist/services/activity/activity-human-description.service.d.ts +13 -0
  140. package/dist/services/activity/activity-human-description.service.js +221 -0
  141. package/dist/services/activity/activity-human-description.service.test.d.ts +1 -0
  142. package/dist/services/activity/activity-human-description.service.test.js +16 -0
  143. package/dist/services/activity/activity-log.service.d.ts +87 -0
  144. package/dist/services/activity/activity-log.service.js +276 -0
  145. package/dist/services/activity/activity-log.service.test.d.ts +1 -0
  146. package/dist/services/activity/activity-log.service.test.js +27 -0
  147. package/dist/services/activity-human-description.service.d.ts +13 -0
  148. package/dist/services/activity-human-description.service.js +221 -0
  149. package/dist/services/activity-human-description.service.test.d.ts +1 -0
  150. package/dist/services/activity-human-description.service.test.js +16 -0
  151. package/dist/services/activity-log.service.d.ts +87 -0
  152. package/dist/services/activity-log.service.js +276 -0
  153. package/dist/services/activity-log.service.test.d.ts +1 -0
  154. package/dist/services/activity-log.service.test.js +27 -0
  155. package/dist/services/admin/admin.service.d.ts +270 -0
  156. package/dist/services/admin/admin.service.js +476 -0
  157. package/dist/services/admin.service.d.ts +270 -0
  158. package/dist/services/admin.service.js +476 -0
  159. package/dist/services/ai/ai-session.service.d.ts +5 -0
  160. package/dist/services/ai/ai-session.service.js +4 -0
  161. package/dist/services/ai-session.service.d.ts +60 -0
  162. package/dist/services/ai-session.service.js +561 -0
  163. package/dist/services/annotation.service.d.ts +177 -0
  164. package/dist/services/annotation.service.js +154 -0
  165. package/dist/services/artifact-notification.service.d.ts +14 -0
  166. package/dist/services/artifact-notification.service.js +20 -0
  167. package/dist/services/artifact-version.service.d.ts +38 -0
  168. package/dist/services/artifact-version.service.js +129 -0
  169. package/dist/services/artifacts/annotation.service.d.ts +177 -0
  170. package/dist/services/artifacts/annotation.service.js +154 -0
  171. package/dist/services/artifacts/artifact-version.service.d.ts +38 -0
  172. package/dist/services/artifacts/artifact-version.service.js +129 -0
  173. package/dist/services/artifacts/chat.service.d.ts +127 -0
  174. package/dist/services/artifacts/chat.service.js +182 -0
  175. package/dist/services/artifacts/study-guide.service.d.ts +18 -0
  176. package/dist/services/artifacts/study-guide.service.js +65 -0
  177. package/dist/services/auth/auth.service.d.ts +94 -0
  178. package/dist/services/auth/auth.service.js +368 -0
  179. package/dist/services/auth.service.d.ts +94 -0
  180. package/dist/services/auth.service.js +368 -0
  181. package/dist/services/base.service.d.ts +14 -0
  182. package/dist/services/base.service.js +14 -0
  183. package/dist/services/billing/payment.service.d.ts +44 -0
  184. package/dist/services/billing/payment.service.js +365 -0
  185. package/dist/services/billing/subscription.service.d.ts +37 -0
  186. package/dist/services/billing/subscription.service.js +654 -0
  187. package/dist/services/billing/usage.service.d.ts +47 -0
  188. package/dist/services/billing/usage.service.js +149 -0
  189. package/dist/services/chat.service.d.ts +127 -0
  190. package/dist/services/chat.service.js +182 -0
  191. package/dist/services/content/copilot.service.d.ts +113 -0
  192. package/dist/services/content/copilot.service.js +439 -0
  193. package/dist/services/content/flashcard-progress.service.d.ts +159 -0
  194. package/dist/services/content/flashcard-progress.service.js +432 -0
  195. package/dist/services/content/flashcard.service.d.ts +184 -0
  196. package/dist/services/content/flashcard.service.js +339 -0
  197. package/dist/services/content/media-analysis.service.d.ts +23 -0
  198. package/dist/services/content/media-analysis.service.js +404 -0
  199. package/dist/services/content/podcast.service.d.ts +267 -0
  200. package/dist/services/content/podcast.service.js +653 -0
  201. package/dist/services/content/worksheet-content.service.d.ts +37 -0
  202. package/dist/services/content/worksheet-content.service.js +84 -0
  203. package/dist/services/content/worksheet-content.service.test.d.ts +1 -0
  204. package/dist/services/content/worksheet-content.service.test.js +69 -0
  205. package/dist/services/content/worksheet-generation.service.d.ts +91 -0
  206. package/dist/services/content/worksheet-generation.service.js +95 -0
  207. package/dist/services/content/worksheet-generation.service.test.d.ts +1 -0
  208. package/dist/services/content/worksheet-generation.service.test.js +20 -0
  209. package/dist/services/content/worksheet.service.d.ts +347 -0
  210. package/dist/services/content/worksheet.service.js +599 -0
  211. package/dist/services/copilot.service.d.ts +116 -0
  212. package/dist/services/copilot.service.js +447 -0
  213. package/dist/services/flashcard-progress.service.d.ts +2 -2
  214. package/dist/services/flashcard-progress.service.js +3 -2
  215. package/dist/services/flashcard.service.d.ts +140 -0
  216. package/dist/services/flashcard.service.js +325 -0
  217. package/dist/services/invitation.service.d.ts +66 -0
  218. package/dist/services/invitation.service.js +348 -0
  219. package/dist/services/knowledge/knowledge-base.service.d.ts +316 -0
  220. package/dist/services/knowledge/knowledge-base.service.js +544 -0
  221. package/dist/services/knowledge-base.service.d.ts +316 -0
  222. package/dist/services/knowledge-base.service.js +536 -0
  223. package/dist/services/media-analysis.service.d.ts +23 -0
  224. package/dist/services/media-analysis.service.js +384 -0
  225. package/dist/services/member.service.d.ts +36 -0
  226. package/dist/services/member.service.js +193 -0
  227. package/dist/services/members/invitation.service.d.ts +66 -0
  228. package/dist/services/members/invitation.service.js +348 -0
  229. package/dist/services/members/member.service.d.ts +36 -0
  230. package/dist/services/members/member.service.js +193 -0
  231. package/dist/services/note.service.d.ts +55 -0
  232. package/dist/services/note.service.js +111 -0
  233. package/dist/services/notification.service.d.ts +214 -0
  234. package/dist/services/notification.service.js +550 -0
  235. package/dist/services/notification.service.test.d.ts +1 -0
  236. package/dist/services/notification.service.test.js +87 -0
  237. package/dist/services/notifications/notification.service.d.ts +214 -0
  238. package/dist/services/notifications/notification.service.js +550 -0
  239. package/dist/services/notifications/notification.service.test.d.ts +1 -0
  240. package/dist/services/notifications/notification.service.test.js +87 -0
  241. package/dist/services/payment.service.d.ts +55 -0
  242. package/dist/services/payment.service.js +368 -0
  243. package/dist/services/podcast.service.d.ts +267 -0
  244. package/dist/services/podcast.service.js +654 -0
  245. package/dist/services/router-module.service.d.ts +7 -0
  246. package/dist/services/router-module.service.js +10 -0
  247. package/dist/services/study-guide.service.d.ts +18 -0
  248. package/dist/services/study-guide.service.js +65 -0
  249. package/dist/services/subscription.service.d.ts +37 -0
  250. package/dist/services/subscription.service.js +654 -0
  251. package/dist/services/usage-limit-policy.service.d.ts +12 -0
  252. package/dist/services/usage-limit-policy.service.js +22 -0
  253. package/dist/services/usage-limit-policy.service.test.d.ts +1 -0
  254. package/dist/services/usage-limit-policy.service.test.js +46 -0
  255. package/dist/services/usage.service.d.ts +27 -0
  256. package/dist/services/usage.service.js +77 -0
  257. package/dist/services/worksheet-content.service.d.ts +42 -0
  258. package/dist/services/worksheet-content.service.js +84 -0
  259. package/dist/services/worksheet-content.service.test.d.ts +1 -0
  260. package/dist/services/worksheet-content.service.test.js +69 -0
  261. package/dist/services/worksheet-generation.service.d.ts +91 -0
  262. package/dist/services/worksheet-generation.service.js +95 -0
  263. package/dist/services/worksheet-generation.service.test.d.ts +1 -0
  264. package/dist/services/worksheet-generation.service.test.js +20 -0
  265. package/dist/services/worksheet.service.d.ts +385 -0
  266. package/dist/services/worksheet.service.js +596 -0
  267. package/dist/services/workspace/workspace-analytics.service.d.ts +24 -0
  268. package/dist/services/workspace/workspace-analytics.service.js +95 -0
  269. package/dist/services/workspace/workspace-kb.service.d.ts +40 -0
  270. package/dist/services/workspace/workspace-kb.service.js +184 -0
  271. package/dist/services/workspace/workspace.service.d.ts +263 -0
  272. package/dist/services/workspace/workspace.service.js +401 -0
  273. package/dist/services/workspace-analytics.service.d.ts +24 -0
  274. package/dist/services/workspace-analytics.service.js +95 -0
  275. package/dist/services/workspace-kb.service.d.ts +40 -0
  276. package/dist/services/workspace-kb.service.js +184 -0
  277. package/dist/services/workspace-progress.service.d.ts +27 -0
  278. package/dist/services/workspace-progress.service.js +56 -0
  279. package/dist/services/workspace-progress.service.test.d.ts +1 -0
  280. package/dist/services/workspace-progress.service.test.js +49 -0
  281. package/dist/services/workspace.service.d.ts +307 -0
  282. package/dist/services/workspace.service.js +390 -0
  283. package/dist/trpc.d.ts +12 -4
  284. package/dist/trpc.js +7 -13
  285. package/package.json +5 -6
  286. package/prisma/migrations/20260509000001_add_knowledge_base/migration.sql +99 -0
  287. package/prisma/migrations/20260509000002_curate_knowledge_base/migration.sql +52 -0
  288. package/prisma/migrations/20260522000000_add_notes/migration.sql +27 -0
  289. package/prisma/migrations/20260524000000_remove_notes/migration.sql +3 -0
  290. package/prisma/schema.prisma +150 -48
  291. package/prisma/seed.mjs +67 -0
  292. package/scripts/debug/README.md +4 -0
  293. package/src/README.md +63 -0
  294. package/src/context.ts +33 -3
  295. package/src/lib/ai/config.ts +34 -0
  296. package/src/lib/ai/embedding-client.ts +47 -0
  297. package/src/lib/ai/index.ts +65 -0
  298. package/src/lib/ai/inference-backend/client.ts +479 -0
  299. package/src/lib/ai/inference-backend/mocks.ts +171 -0
  300. package/src/lib/ai/inference-backend/types.ts +50 -0
  301. package/src/lib/ai/json-parse.ts +35 -0
  302. package/src/lib/ai/llm-client.ts +54 -0
  303. package/src/lib/ai/mock.ts +12 -0
  304. package/src/lib/ai/types.ts +11 -0
  305. package/src/lib/chunking.ts +81 -0
  306. package/src/lib/curated-kb-seed.ts +164 -0
  307. package/src/lib/email.ts +77 -115
  308. package/src/lib/embeddings.ts +9 -0
  309. package/src/lib/ensure-curated-kb-catalog.ts +60 -0
  310. package/src/lib/env.ts +2 -7
  311. package/src/lib/inference.ts +1 -21
  312. package/src/lib/kb-meta.ts +81 -0
  313. package/src/lib/pdf.ts +23 -0
  314. package/src/lib/workspace-kb.ts +7 -0
  315. package/src/repositories/artifact.repository.ts +55 -0
  316. package/src/repositories/base.repository.ts +19 -0
  317. package/src/repositories/invitation.repository.ts +53 -0
  318. package/src/repositories/notification.repository.ts +53 -0
  319. package/src/repositories/user.repository.ts +44 -0
  320. package/src/repositories/workspace-member.repository.ts +38 -0
  321. package/src/repositories/workspace.repository.ts +89 -0
  322. package/src/routers/_app.ts +4 -0
  323. package/src/routers/admin.ts +124 -692
  324. package/src/routers/annotations.ts +25 -203
  325. package/src/routers/artifactVersions.ts +32 -0
  326. package/src/routers/auth.ts +82 -520
  327. package/src/routers/chat.ts +42 -245
  328. package/src/routers/copilot.ts +41 -666
  329. package/src/routers/flashcards.ts +108 -404
  330. package/src/routers/knowledgeBase.ts +216 -0
  331. package/src/routers/members.ts +60 -782
  332. package/src/routers/notifications.ts +15 -117
  333. package/src/routers/payment.ts +37 -446
  334. package/src/routers/podcast.ts +36 -898
  335. package/src/routers/studyguide.ts +5 -144
  336. package/src/routers/worksheets.ts +171 -735
  337. package/src/routers/workspace.ts +141 -1108
  338. package/src/scripts/purge-deleted-users.ts +2 -2
  339. package/src/server.ts +10 -3
  340. package/src/{lib/activity_human_description.test.ts → services/activity/activity-human-description.service.test.ts} +1 -1
  341. package/src/{lib/activity_log_service.test.ts → services/activity/activity-log.service.test.ts} +1 -1
  342. package/src/{lib/activity_log_service.ts → services/activity/activity-log.service.ts} +2 -2
  343. package/src/services/admin/admin.service.ts +612 -0
  344. package/src/services/ai/ai-session.service.ts +5 -0
  345. package/src/services/artifacts/annotation.service.ts +189 -0
  346. package/src/services/artifacts/artifact-version.service.ts +151 -0
  347. package/src/services/artifacts/chat.service.ts +197 -0
  348. package/src/services/artifacts/study-guide.service.ts +72 -0
  349. package/src/services/auth/auth.service.ts +473 -0
  350. package/src/services/base.service.ts +19 -0
  351. package/src/services/billing/payment.service.ts +433 -0
  352. package/src/{lib/subscription_service.ts → services/billing/subscription.service.ts} +5 -5
  353. package/src/services/billing/usage.service.ts +207 -0
  354. package/src/services/content/copilot.service.ts +587 -0
  355. package/src/services/{flashcard-progress.service.ts → content/flashcard-progress.service.ts} +18 -12
  356. package/src/services/content/flashcard.service.ts +417 -0
  357. package/src/services/content/media-analysis.service.ts +561 -0
  358. package/src/services/content/podcast.service.ts +777 -0
  359. package/src/services/content/worksheet-content.service.test.ts +83 -0
  360. package/src/services/content/worksheet-content.service.ts +117 -0
  361. package/src/{lib/worksheet-generation.test.ts → services/content/worksheet-generation.service.test.ts} +3 -3
  362. package/src/services/content/worksheet.service.ts +751 -0
  363. package/src/services/knowledge/knowledge-base.service.ts +705 -0
  364. package/src/services/members/invitation.service.ts +427 -0
  365. package/src/services/members/member.service.ts +241 -0
  366. package/src/{lib/notification-service.test.ts → services/notifications/notification.service.test.ts} +2 -2
  367. package/src/{lib/notification-service.ts → services/notifications/notification.service.ts} +102 -1
  368. package/src/services/workspace/workspace-analytics.service.ts +107 -0
  369. package/src/services/workspace/workspace-kb.service.ts +273 -0
  370. package/src/services/workspace/workspace.service.ts +488 -0
  371. package/src/trpc.ts +7 -15
  372. package/src/lib/ai-session.ts +0 -704
  373. package/src/lib/usage_service.ts +0 -74
  374. package/src/lib/workspace-access.ts +0 -13
  375. /package/{check-difficulty.cjs → scripts/debug/check-difficulty.cjs} +0 -0
  376. /package/{check-questions.cjs → scripts/debug/check-questions.cjs} +0 -0
  377. /package/{db-summary.cjs → scripts/debug/db-summary.cjs} +0 -0
  378. /package/{mcq-test.cjs → scripts/debug/mcq-test.cjs} +0 -0
  379. /package/{test-generate.js → scripts/debug/test-generate.js} +0 -0
  380. /package/{test-ratio.cjs → scripts/debug/test-ratio.cjs} +0 -0
  381. /package/{zod-test.cjs → scripts/debug/zod-test.cjs} +0 -0
  382. /package/src/{lib/activity_human_description.ts → services/activity/activity-human-description.service.ts} +0 -0
  383. /package/src/{lib/worksheet-generation.ts → services/content/worksheet-generation.service.ts} +0 -0
@@ -1,314 +1,89 @@
1
1
  import { z } from 'zod';
2
- import { TRPCError } from '@trpc/server';
3
2
  import { router, authedProcedure, limitedProcedure } from '../trpc.js';
4
- import { aiSessionService } from '../lib/ai-session.js';
5
- import PusherService from '../lib/pusher.js';
6
- import { notifyArtifactFailed, notifyArtifactReady } from '../lib/notification-service.js';
7
- import { logger } from '../lib/logger.js';
8
- import { ArtifactType } from '../lib/constants.js';
9
- import { workspaceAccessFilter } from '../lib/workspace-access.js';
10
- import { mergeWorksheetGenerationConfig, normalizeWorksheetProblemForDb, parsePresetConfig, worksheetModeSchema, worksheetPresetConfigPartialSchema, } from '../lib/worksheet-generation.js';
11
- const Difficulty = {
12
- EASY: 'EASY',
13
- MEDIUM: 'MEDIUM',
14
- HARD: 'HARD',
15
- };
16
- const QuestionType = {
17
- MULTIPLE_CHOICE: 'MULTIPLE_CHOICE',
18
- TEXT: 'TEXT',
19
- NUMERIC: 'NUMERIC',
20
- TRUE_FALSE: 'TRUE_FALSE',
21
- MATCHING: 'MATCHING',
22
- FILL_IN_THE_BLANK: 'FILL_IN_THE_BLANK',
23
- };
3
+ import { WorksheetService } from '../services/content/worksheet.service.js';
4
+ import { worksheetModeSchema, worksheetPresetConfigPartialSchema, } from '../services/content/worksheet-generation.service.js';
5
+ const questionTypeEnum = z.enum([
6
+ 'MULTIPLE_CHOICE',
7
+ 'TEXT',
8
+ 'NUMERIC',
9
+ 'TRUE_FALSE',
10
+ 'MATCHING',
11
+ 'FILL_IN_THE_BLANK',
12
+ ]);
13
+ const difficultyEnum = z.enum(['EASY', 'MEDIUM', 'HARD']);
24
14
  export const worksheets = router({
25
- // List all worksheet artifacts for a workspace
26
15
  list: authedProcedure
27
16
  .input(z.object({ workspaceId: z.string() }))
28
- .query(async ({ ctx, input }) => {
29
- const worksheets = await ctx.db.artifact.findMany({
30
- where: { workspaceId: input.workspaceId, type: ArtifactType.WORKSHEET },
31
- include: {
32
- versions: {
33
- orderBy: { version: 'desc' },
34
- take: 1, // Get only the latest version
35
- },
36
- questions: true,
37
- },
38
- orderBy: { updatedAt: 'desc' },
39
- });
40
- if (!worksheets)
41
- throw new TRPCError({ code: 'NOT_FOUND' });
42
- // Merge per-user progress into question.meta for compatibility with UI
43
- const allQuestionIds = worksheets.flatMap(w => w.questions.map(q => q.id));
44
- if (allQuestionIds.length === 0)
45
- return worksheets;
46
- const progress = await ctx.db.worksheetQuestionProgress.findMany({
47
- where: { userId: ctx.session.user.id, worksheetQuestionId: { in: allQuestionIds } },
48
- });
49
- const progressByQuestionId = new Map(progress.map(p => [p.worksheetQuestionId, p]));
50
- const merged = worksheets.map(w => ({
51
- ...w,
52
- questions: w.questions.map(q => {
53
- const p = progressByQuestionId.get(q.id);
54
- if (!p)
55
- return q;
56
- const existingMeta = q.meta ? (typeof q.meta === 'object' ? q.meta : JSON.parse(q.meta)) : {};
57
- const progressMeta = p.meta ? (typeof p.meta === 'object' ? p.meta : JSON.parse(p.meta)) : {};
58
- return {
59
- ...q,
60
- meta: {
61
- ...existingMeta,
62
- completed: p.modified,
63
- userAnswer: p.userAnswer,
64
- completedAt: p.completedAt,
65
- userMarkScheme: progressMeta.userMarkScheme,
66
- },
67
- };
68
- }),
69
- }));
70
- return merged;
71
- }),
17
+ .query(({ ctx, input }) => new WorksheetService(ctx.db).list(ctx.session.user.id, input.workspaceId)),
72
18
  listPresets: authedProcedure
73
19
  .input(z.object({ workspaceId: z.string() }))
74
- .query(async ({ ctx, input }) => {
75
- const ws = await ctx.db.workspace.findFirst({
76
- where: { id: input.workspaceId, ...workspaceAccessFilter(ctx.session.user.id) },
77
- });
78
- if (!ws)
79
- throw new TRPCError({ code: 'NOT_FOUND' });
80
- return ctx.db.worksheetPreset.findMany({
81
- where: {
82
- OR: [
83
- { isSystem: true },
84
- {
85
- userId: ctx.session.user.id,
86
- OR: [{ workspaceId: input.workspaceId }, { workspaceId: null }],
87
- },
88
- ],
89
- },
90
- orderBy: [{ isSystem: 'desc' }, { name: 'asc' }],
91
- });
92
- }),
20
+ .query(({ ctx, input }) => new WorksheetService(ctx.db).listPresets(ctx.session.user.id, input.workspaceId)),
93
21
  createPreset: authedProcedure
94
22
  .input(z.object({
95
23
  workspaceId: z.string().optional(),
96
24
  name: z.string().min(1).max(120),
97
25
  config: z.record(z.string(), z.unknown()),
98
26
  }))
99
- .mutation(async ({ ctx, input }) => {
100
- if (input.workspaceId) {
101
- const ws = await ctx.db.workspace.findFirst({
102
- where: { id: input.workspaceId, ...workspaceAccessFilter(ctx.session.user.id) },
103
- });
104
- if (!ws)
105
- throw new TRPCError({ code: 'NOT_FOUND' });
106
- }
107
- const config = parsePresetConfig(input.config);
108
- return ctx.db.worksheetPreset.create({
109
- data: {
110
- userId: ctx.session.user.id,
111
- workspaceId: input.workspaceId ?? null,
112
- name: input.name,
113
- isSystem: false,
114
- config: config,
115
- },
116
- });
117
- }),
27
+ .mutation(({ ctx, input }) => new WorksheetService(ctx.db).createPreset(ctx.session.user.id, input)),
118
28
  updatePreset: authedProcedure
119
- .input(z.object({
29
+ .input(z
30
+ .object({
120
31
  id: z.string(),
121
32
  name: z.string().min(1).max(120).optional(),
122
33
  config: z.record(z.string(), z.unknown()).optional(),
123
- }).refine(d => d.name !== undefined || d.config !== undefined, { message: 'Provide name and/or config' }))
124
- .mutation(async ({ ctx, input }) => {
125
- const existing = await ctx.db.worksheetPreset.findFirst({
126
- where: { id: input.id, userId: ctx.session.user.id, isSystem: false },
127
- });
128
- if (!existing)
129
- throw new TRPCError({ code: 'NOT_FOUND', message: 'Preset not found or read-only' });
130
- const data = {};
131
- if (input.name !== undefined)
132
- data.name = input.name;
133
- if (input.config !== undefined)
134
- data.config = parsePresetConfig(input.config);
135
- return ctx.db.worksheetPreset.update({
136
- where: { id: input.id },
137
- data,
138
- });
139
- }),
34
+ })
35
+ .refine((d) => d.name !== undefined || d.config !== undefined, {
36
+ message: 'Provide name and/or config',
37
+ }))
38
+ .mutation(({ ctx, input }) => new WorksheetService(ctx.db).updatePreset(ctx.session.user.id, input)),
140
39
  deletePreset: authedProcedure
141
40
  .input(z.object({ id: z.string() }))
142
- .mutation(async ({ ctx, input }) => {
143
- const existing = await ctx.db.worksheetPreset.findFirst({
144
- where: { id: input.id, userId: ctx.session.user.id, isSystem: false },
145
- });
146
- if (!existing)
147
- throw new TRPCError({ code: 'NOT_FOUND', message: 'Preset not found or read-only' });
148
- await ctx.db.worksheetPreset.delete({ where: { id: input.id } });
149
- return true;
150
- }),
151
- // Create a worksheet set
41
+ .mutation(({ ctx, input }) => new WorksheetService(ctx.db).deletePreset(ctx.session.user.id, input.id)),
152
42
  create: limitedProcedure
153
43
  .input(z.object({
154
44
  workspaceId: z.string(),
155
45
  title: z.string().min(1).max(120),
156
46
  description: z.string().optional(),
157
- difficulty: z.enum(['EASY', 'MEDIUM', 'HARD']).optional(),
47
+ difficulty: difficultyEnum.optional(),
158
48
  estimatedTime: z.string().optional(),
159
- problems: z.array(z.object({
49
+ problems: z
50
+ .array(z.object({
160
51
  question: z.string().min(1),
161
52
  answer: z.string().min(1),
162
- type: z.enum(['MULTIPLE_CHOICE', 'TEXT', 'NUMERIC', 'TRUE_FALSE', 'MATCHING', 'FILL_IN_THE_BLANK']).default('TEXT'),
53
+ type: questionTypeEnum.default('TEXT'),
163
54
  options: z.array(z.string()).optional(),
164
- })).optional(),
55
+ }))
56
+ .optional(),
165
57
  }))
166
- .mutation(async ({ ctx, input }) => {
167
- const workspace = await ctx.db.workspace.findFirst({
168
- where: { id: input.workspaceId, ownerId: ctx.session.user.id },
169
- });
170
- if (!workspace)
171
- throw new TRPCError({ code: 'NOT_FOUND' });
172
- const { problems, ...worksheetData } = input;
173
- return ctx.db.artifact.create({
174
- data: {
175
- workspaceId: input.workspaceId,
176
- type: ArtifactType.WORKSHEET,
177
- title: input.title,
178
- difficulty: input.difficulty,
179
- estimatedTime: input.estimatedTime,
180
- createdById: ctx.session.user.id,
181
- questions: problems ? {
182
- create: problems.map((problem, index) => ({
183
- prompt: problem.question,
184
- answer: problem.answer,
185
- type: problem.type,
186
- order: index,
187
- meta: problem.options ? { options: problem.options } : undefined,
188
- })),
189
- } : undefined,
190
- },
191
- include: {
192
- questions: true,
193
- },
194
- });
195
- }),
196
- // Get a worksheet with its questions
58
+ .mutation(({ ctx, input }) => new WorksheetService(ctx.db).create(ctx.session.user.id, input)),
197
59
  get: authedProcedure
198
60
  .input(z.object({ worksheetId: z.string() }))
199
- .query(async ({ ctx, input }) => {
200
- const worksheet = await ctx.db.artifact.findFirst({
201
- where: {
202
- id: input.worksheetId,
203
- type: ArtifactType.WORKSHEET,
204
- workspace: workspaceAccessFilter(ctx.session.user.id),
205
- },
206
- include: { questions: true },
207
- orderBy: { updatedAt: 'desc' },
208
- });
209
- if (!worksheet)
210
- throw new TRPCError({ code: 'NOT_FOUND' });
211
- // Merge per-user progress into question.meta for compatibility with UI
212
- const questionIds = worksheet.questions.map(q => q.id);
213
- if (questionIds.length === 0)
214
- return worksheet;
215
- const progress = await ctx.db.worksheetQuestionProgress.findMany({
216
- where: { userId: ctx.session.user.id, worksheetQuestionId: { in: questionIds } },
217
- });
218
- const progressByQuestionId = new Map(progress.map(p => [p.worksheetQuestionId, p]));
219
- const merged = {
220
- ...worksheet,
221
- questions: worksheet.questions.map(q => {
222
- const p = progressByQuestionId.get(q.id);
223
- if (!p)
224
- return q;
225
- const existingMeta = q.meta ? (typeof q.meta === 'object' ? q.meta : JSON.parse(q.meta)) : {};
226
- const progressMeta = p.meta ? (typeof p.meta === 'object' ? p.meta : JSON.parse(p.meta)) : {};
227
- return {
228
- ...q,
229
- meta: {
230
- ...existingMeta,
231
- completed: p.modified,
232
- userAnswer: p.userAnswer,
233
- completedAt: p.completedAt,
234
- userMarkScheme: progressMeta.userMarkScheme,
235
- },
236
- };
237
- }),
238
- };
239
- return merged;
240
- }),
241
- // Add a question to a worksheet
61
+ .query(({ ctx, input }) => new WorksheetService(ctx.db).get(ctx.session.user.id, input.worksheetId)),
242
62
  createWorksheetQuestion: limitedProcedure
243
63
  .input(z.object({
244
64
  worksheetId: z.string(),
245
65
  prompt: z.string().min(1),
246
66
  answer: z.string().optional(),
247
- type: z.enum(['MULTIPLE_CHOICE', 'TEXT', 'NUMERIC', 'TRUE_FALSE', 'MATCHING', 'FILL_IN_THE_BLANK']).optional(),
248
- difficulty: z.enum(['EASY', 'MEDIUM', 'HARD']).optional(),
67
+ type: questionTypeEnum.optional(),
68
+ difficulty: difficultyEnum.optional(),
249
69
  order: z.number().int().optional(),
250
70
  meta: z.record(z.string(), z.unknown()).optional(),
251
71
  }))
252
- .mutation(async ({ ctx, input }) => {
253
- const worksheet = await ctx.db.artifact.findFirst({
254
- where: { id: input.worksheetId, type: ArtifactType.WORKSHEET, workspace: workspaceAccessFilter(ctx.session.user.id) },
255
- });
256
- if (!worksheet)
257
- throw new TRPCError({ code: 'NOT_FOUND' });
258
- return ctx.db.worksheetQuestion.create({
259
- data: {
260
- artifactId: input.worksheetId,
261
- prompt: input.prompt,
262
- answer: input.answer,
263
- type: (input.type ?? QuestionType.TEXT),
264
- difficulty: (input.difficulty ?? Difficulty.MEDIUM),
265
- order: input.order ?? 0,
266
- meta: input.meta,
267
- },
268
- });
269
- }),
270
- // Update a question
72
+ .mutation(({ ctx, input }) => new WorksheetService(ctx.db).createWorksheetQuestion(ctx.session.user.id, input)),
271
73
  updateWorksheetQuestion: authedProcedure
272
74
  .input(z.object({
273
75
  worksheetQuestionId: z.string(),
274
76
  prompt: z.string().optional(),
275
77
  answer: z.string().optional(),
276
- type: z.enum(['MULTIPLE_CHOICE', 'TEXT', 'NUMERIC', 'TRUE_FALSE', 'MATCHING', 'FILL_IN_THE_BLANK']).optional(),
277
- difficulty: z.enum(['EASY', 'MEDIUM', 'HARD']).optional(),
78
+ type: questionTypeEnum.optional(),
79
+ difficulty: difficultyEnum.optional(),
278
80
  order: z.number().int().optional(),
279
81
  meta: z.record(z.string(), z.unknown()).optional(),
280
82
  }))
281
- .mutation(async ({ ctx, input }) => {
282
- const q = await ctx.db.worksheetQuestion.findFirst({
283
- where: { id: input.worksheetQuestionId, artifact: { type: ArtifactType.WORKSHEET, workspace: workspaceAccessFilter(ctx.session.user.id) } },
284
- });
285
- if (!q)
286
- throw new TRPCError({ code: 'NOT_FOUND' });
287
- return ctx.db.worksheetQuestion.update({
288
- where: { id: input.worksheetQuestionId },
289
- data: {
290
- prompt: input.prompt ?? q.prompt,
291
- answer: input.answer ?? q.answer,
292
- type: (input.type ?? q.type),
293
- difficulty: (input.difficulty ?? q.difficulty),
294
- order: input.order ?? q.order,
295
- meta: (input.meta ?? q.meta),
296
- },
297
- });
298
- }),
299
- // Delete a question
83
+ .mutation(({ ctx, input }) => new WorksheetService(ctx.db).updateWorksheetQuestion(ctx.session.user.id, input)),
300
84
  deleteWorksheetQuestion: authedProcedure
301
85
  .input(z.object({ worksheetQuestionId: z.string() }))
302
- .mutation(async ({ ctx, input }) => {
303
- const q = await ctx.db.worksheetQuestion.findFirst({
304
- where: { id: input.worksheetQuestionId, artifact: { workspace: workspaceAccessFilter(ctx.session.user.id) } },
305
- });
306
- if (!q)
307
- throw new TRPCError({ code: 'NOT_FOUND' });
308
- await ctx.db.worksheetQuestion.delete({ where: { id: input.worksheetQuestionId } });
309
- return true;
310
- }),
311
- // Update problem completion status
86
+ .mutation(({ ctx, input }) => new WorksheetService(ctx.db).deleteWorksheetQuestion(ctx.session.user.id, input.worksheetQuestionId)),
312
87
  updateProblemStatus: authedProcedure
313
88
  .input(z.object({
314
89
  problemId: z.string(),
@@ -316,145 +91,31 @@ export const worksheets = router({
316
91
  answer: z.string().optional(),
317
92
  correct: z.boolean().optional(),
318
93
  }))
319
- .mutation(async ({ ctx, input }) => {
320
- // Verify question ownership through worksheet
321
- const question = await ctx.db.worksheetQuestion.findFirst({
322
- where: {
323
- id: input.problemId,
324
- artifact: {
325
- type: ArtifactType.WORKSHEET,
326
- workspace: workspaceAccessFilter(ctx.session.user.id),
327
- },
328
- },
329
- });
330
- if (!question)
331
- throw new TRPCError({ code: 'NOT_FOUND' });
332
- // Upsert per-user progress row
333
- const progress = await ctx.db.worksheetQuestionProgress.upsert({
334
- where: {
335
- worksheetQuestionId_userId: {
336
- worksheetQuestionId: input.problemId,
337
- userId: ctx.session.user.id,
338
- },
339
- },
340
- create: {
341
- worksheetQuestionId: input.problemId,
342
- userId: ctx.session.user.id,
343
- modified: input.completed,
344
- userAnswer: input.answer,
345
- correct: input.correct,
346
- completedAt: input.completed ? new Date() : null,
347
- attempts: 1,
348
- },
349
- update: {
350
- modified: input.completed,
351
- userAnswer: input.answer,
352
- completedAt: input.completed ? new Date() : null,
353
- attempts: { increment: 1 },
354
- correct: input.correct,
355
- },
356
- });
357
- return progress;
358
- }),
359
- // Get current user's progress for all questions in a worksheet
94
+ .mutation(({ ctx, input }) => new WorksheetService(ctx.db).updateProblemStatus(ctx.session.user.id, input)),
360
95
  getProgress: authedProcedure
361
96
  .input(z.object({ worksheetId: z.string() }))
362
- .query(async ({ ctx, input }) => {
363
- // Verify worksheet ownership
364
- const worksheet = await ctx.db.artifact.findFirst({
365
- where: {
366
- id: input.worksheetId,
367
- type: ArtifactType.WORKSHEET,
368
- workspace: workspaceAccessFilter(ctx.session.user.id),
369
- },
370
- });
371
- if (!worksheet)
372
- throw new TRPCError({ code: 'NOT_FOUND' });
373
- const questions = await ctx.db.worksheetQuestion.findMany({
374
- where: { artifactId: input.worksheetId },
375
- select: { id: true },
376
- });
377
- const questionIds = questions.map(q => q.id);
378
- const progress = await ctx.db.worksheetQuestionProgress.findMany({
379
- where: {
380
- userId: ctx.session.user.id,
381
- worksheetQuestionId: { in: questionIds },
382
- },
383
- });
384
- return progress;
385
- }),
386
- // Update a worksheet
97
+ .query(({ ctx, input }) => new WorksheetService(ctx.db).getProgress(ctx.session.user.id, input.worksheetId)),
387
98
  update: authedProcedure
388
99
  .input(z.object({
389
100
  id: z.string(),
390
101
  title: z.string().min(1).max(120).optional(),
391
102
  description: z.string().optional(),
392
- difficulty: z.enum(['EASY', 'MEDIUM', 'HARD']).optional(),
103
+ difficulty: difficultyEnum.optional(),
393
104
  estimatedTime: z.string().optional(),
394
- problems: z.array(z.object({
105
+ problems: z
106
+ .array(z.object({
395
107
  id: z.string().optional(),
396
108
  question: z.string().min(1),
397
109
  answer: z.string().min(1),
398
- type: z.enum(['MULTIPLE_CHOICE', 'TEXT', 'NUMERIC', 'TRUE_FALSE', 'MATCHING', 'FILL_IN_THE_BLANK']).default('TEXT'),
110
+ type: questionTypeEnum.default('TEXT'),
399
111
  options: z.array(z.string()).optional(),
400
- })).optional(),
112
+ }))
113
+ .optional(),
401
114
  }))
402
- .mutation(async ({ ctx, input }) => {
403
- const { id, problems, ...updateData } = input;
404
- // Verify worksheet ownership
405
- const existingWorksheet = await ctx.db.artifact.findFirst({
406
- where: {
407
- id,
408
- type: ArtifactType.WORKSHEET,
409
- workspace: workspaceAccessFilter(ctx.session.user.id),
410
- },
411
- });
412
- if (!existingWorksheet)
413
- throw new TRPCError({ code: 'NOT_FOUND' });
414
- // Handle questions update if provided
415
- if (problems) {
416
- // Delete existing questions and create new ones
417
- await ctx.db.worksheetQuestion.deleteMany({
418
- where: { artifactId: id },
419
- });
420
- await ctx.db.worksheetQuestion.createMany({
421
- data: problems.map((problem, index) => ({
422
- artifactId: id,
423
- prompt: problem.question,
424
- answer: problem.answer,
425
- type: problem.type,
426
- order: index,
427
- meta: problem.options ? { options: problem.options } : undefined,
428
- })),
429
- });
430
- }
431
- // Process update data
432
- const processedUpdateData = {
433
- ...updateData,
434
- difficulty: updateData.difficulty,
435
- };
436
- return ctx.db.artifact.update({
437
- where: { id },
438
- data: processedUpdateData,
439
- include: {
440
- questions: {
441
- orderBy: { order: 'asc' },
442
- },
443
- },
444
- });
445
- }),
446
- // Delete a worksheet set and its questions
115
+ .mutation(({ ctx, input }) => new WorksheetService(ctx.db).update(ctx.session.user.id, input)),
447
116
  delete: authedProcedure
448
117
  .input(z.object({ id: z.string() }))
449
- .mutation(async ({ ctx, input }) => {
450
- const deleted = await ctx.db.artifact.deleteMany({
451
- where: { id: input.id, type: ArtifactType.WORKSHEET, workspace: workspaceAccessFilter(ctx.session.user.id) },
452
- });
453
- if (deleted.count === 0)
454
- throw new TRPCError({ code: 'NOT_FOUND' });
455
- return true;
456
- }),
457
- // Generate a worksheet from a user prompt
118
+ .mutation(({ ctx, input }) => new WorksheetService(ctx.db).delete(ctx.session.user.id, input.id)),
458
119
  generateFromPrompt: limitedProcedure
459
120
  .input(z.object({
460
121
  workspaceId: z.string(),
@@ -467,252 +128,12 @@ export const worksheets = router({
467
128
  title: z.string().optional(),
468
129
  estimatedTime: z.string().optional(),
469
130
  }))
470
- .mutation(async ({ ctx, input }) => {
471
- const workspace = await ctx.db.workspace.findFirst({
472
- where: { id: input.workspaceId, ...workspaceAccessFilter(ctx.session.user.id) },
473
- });
474
- if (!workspace)
475
- throw new TRPCError({ code: 'NOT_FOUND' });
476
- let presetRow = null;
477
- if (input.presetId) {
478
- presetRow = await ctx.db.worksheetPreset.findFirst({
479
- where: {
480
- id: input.presetId,
481
- OR: [
482
- { isSystem: true },
483
- {
484
- userId: ctx.session.user.id,
485
- OR: [{ workspaceId: input.workspaceId }, { workspaceId: null }],
486
- },
487
- ],
488
- },
489
- });
490
- if (!presetRow)
491
- throw new TRPCError({ code: 'NOT_FOUND', message: 'Preset not found' });
492
- }
493
- const presetConfig = presetRow?.config
494
- ? parsePresetConfig(presetRow.config)
495
- : undefined;
496
- const resolved = mergeWorksheetGenerationConfig(presetConfig, input.configOverride ?? undefined, {
497
- numQuestions: input.numQuestions,
498
- difficulty: input.difficulty,
499
- mode: input.mode,
500
- });
501
- const difficultyUpper = resolved.difficulty.toUpperCase();
502
- console.log("[DEBUG TRPC PAYLOAD] input =", input);
503
- console.log("[DEBUG TRPC PAYLOAD] presetConfig =", presetConfig);
504
- console.log("[DEBUG TRPC PAYLOAD] legacy merged legacy values:", { numQuestions: input.numQuestions, difficulty: input.difficulty, mode: input.mode });
505
- console.log("[DEBUG TRPC PAYLOAD] RESOLVED =", resolved);
506
- const worksheetConfigSnapshot = {
507
- mode: resolved.mode,
508
- presetId: input.presetId ?? null,
509
- presetName: presetRow?.name ?? null,
510
- numQuestions: resolved.numQuestions,
511
- difficulty: resolved.difficulty,
512
- mcqRatio: resolved.mcqRatio ?? null,
513
- questionTypes: resolved.questionTypes ?? null,
514
- };
515
- await PusherService.emitWorksheetGenerationStart(input.workspaceId);
516
- const artifact = await ctx.db.artifact.create({
517
- data: {
518
- workspaceId: input.workspaceId,
519
- type: ArtifactType.WORKSHEET,
520
- title: input.title || (resolved.mode === 'quiz' ? `Quiz - ${new Date().toLocaleString()}` : `Worksheet - ${new Date().toLocaleString()}`),
521
- createdById: ctx.session.user.id,
522
- difficulty: difficultyUpper,
523
- estimatedTime: input.estimatedTime,
524
- generating: true,
525
- generatingMetadata: {
526
- quantity: resolved.numQuestions,
527
- difficulty: resolved.difficulty,
528
- mode: resolved.mode,
529
- presetId: input.presetId ?? null,
530
- },
531
- worksheetConfig: worksheetConfigSnapshot,
532
- },
533
- });
534
- await PusherService.emitTaskComplete(input.workspaceId, 'worksheet_info', { contentLength: resolved.numQuestions });
535
- await PusherService.emitWorksheetNew(input.workspaceId, artifact);
536
- const userId = ctx.session.user.id;
537
- const workspaceId = input.workspaceId;
538
- const promptText = input.prompt;
539
- const estTime = input.estimatedTime;
540
- // Launch generation in the background to free up the connection pool immediately
541
- (async () => {
542
- try {
543
- const content = await aiSessionService.generateWorksheetQuestions(workspaceId, userId, resolved.numQuestions, difficultyUpper, {
544
- mode: resolved.mode,
545
- mcqRatio: resolved.mcqRatio,
546
- questionTypes: resolved.questionTypes,
547
- prompt: promptText,
548
- });
549
- let problems = [];
550
- try {
551
- const worksheetData = JSON.parse(content);
552
- let actualWorksheetData = worksheetData;
553
- if (worksheetData.last_response) {
554
- try {
555
- actualWorksheetData = JSON.parse(worksheetData.last_response);
556
- }
557
- catch { /* noop */ }
558
- }
559
- problems = actualWorksheetData.problems || actualWorksheetData.questions || actualWorksheetData || [];
560
- if (!Array.isArray(problems))
561
- problems = [];
562
- }
563
- catch (parseError) {
564
- logger.error('Failed to parse worksheet JSON', parseError);
565
- throw new Error('Failed to parse worksheet JSON');
566
- }
567
- const forceMcq = resolved.mode === 'quiz';
568
- const questionsToCreate = [];
569
- for (let i = 0; i < Math.min(problems.length, resolved.numQuestions); i++) {
570
- const problem = problems[i] && typeof problems[i] === 'object' ? problems[i] : {};
571
- const row = normalizeWorksheetProblemForDb(problem, i, difficultyUpper, forceMcq);
572
- const metaPayload = {};
573
- if (row.meta.options?.length)
574
- metaPayload.options = row.meta.options;
575
- if (row.meta.markScheme != null)
576
- metaPayload.markScheme = row.meta.markScheme;
577
- questionsToCreate.push({
578
- artifactId: artifact.id,
579
- prompt: row.prompt,
580
- answer: row.answer ?? '',
581
- difficulty: row.difficulty,
582
- order: row.order,
583
- type: row.type,
584
- meta: Object.keys(metaPayload).length ? metaPayload : undefined,
585
- });
586
- }
587
- if (questionsToCreate.length > 0) {
588
- await ctx.db.worksheetQuestion.createMany({
589
- data: questionsToCreate,
590
- });
591
- }
592
- let parsedTitle;
593
- let parsedDescription;
594
- let parsedEstimated;
595
- try {
596
- const worksheetData = JSON.parse(content);
597
- let actualWorksheetData = worksheetData;
598
- if (worksheetData.last_response) {
599
- try {
600
- actualWorksheetData = JSON.parse(worksheetData.last_response);
601
- }
602
- catch { /* noop */ }
603
- }
604
- parsedTitle = typeof actualWorksheetData.title === 'string' ? actualWorksheetData.title : undefined;
605
- parsedDescription = typeof actualWorksheetData.description === 'string' ? actualWorksheetData.description : undefined;
606
- parsedEstimated = typeof actualWorksheetData.estimatedTime === 'string' ? actualWorksheetData.estimatedTime : undefined;
607
- }
608
- catch { /* noop */ }
609
- await ctx.db.artifact.update({
610
- where: { id: artifact.id },
611
- data: {
612
- generating: false,
613
- title: parsedTitle || artifact.title,
614
- description: parsedDescription ?? undefined,
615
- estimatedTime: estTime || parsedEstimated || undefined,
616
- worksheetConfig: worksheetConfigSnapshot,
617
- },
618
- });
619
- const updatedWorksheet = await ctx.db.artifact.findFirst({
620
- where: { id: artifact.id },
621
- include: { questions: true }
622
- });
623
- await PusherService.emitWorksheetGenerationComplete(workspaceId, updatedWorksheet);
624
- await PusherService.emitWorksheetComplete(workspaceId, artifact);
625
- await notifyArtifactReady(ctx.db, {
626
- userId,
627
- workspaceId,
628
- artifactId: artifact.id,
629
- artifactType: ArtifactType.WORKSHEET,
630
- title: updatedWorksheet?.title ?? artifact.title,
631
- }).catch(() => { });
632
- }
633
- catch (error) {
634
- logger.error(`Background generation failed for artifact ${artifact.id}:`, error);
635
- await notifyArtifactFailed(ctx.db, {
636
- userId,
637
- workspaceId,
638
- artifactId: artifact.id,
639
- artifactType: ArtifactType.WORKSHEET,
640
- title: artifact.title,
641
- message: `Failed to generate worksheet: ${error instanceof Error ? error.message : 'Unknown error'}`,
642
- }).catch(() => { });
643
- await ctx.db.artifact.deleteMany({
644
- where: { id: artifact.id },
645
- }).catch(() => { }); // Ignore delete errors if already gone
646
- await PusherService.emitError(workspaceId, `Failed to generate worksheet: ${error instanceof Error ? error.message : 'Unknown error'}`, 'worksheet_generation');
647
- }
648
- })();
649
- return { artifact };
650
- }),
131
+ .mutation(({ ctx, input }) => new WorksheetService(ctx.db).generateFromPrompt(ctx.session.user.id, input)),
651
132
  checkAnswer: authedProcedure
652
133
  .input(z.object({
653
134
  worksheetId: z.string(),
654
135
  questionId: z.string(),
655
136
  answer: z.string().min(1),
656
137
  }))
657
- .mutation(async ({ ctx, input }) => {
658
- const worksheet = await ctx.db.artifact.findFirst({ where: { id: input.worksheetId, type: ArtifactType.WORKSHEET, workspace: workspaceAccessFilter(ctx.session.user.id) }, include: { workspace: true } });
659
- if (!worksheet)
660
- throw new TRPCError({ code: 'NOT_FOUND' });
661
- const question = await ctx.db.worksheetQuestion.findFirst({ where: { id: input.questionId, artifactId: input.worksheetId } });
662
- if (!question)
663
- throw new TRPCError({ code: 'NOT_FOUND' });
664
- // Parse question meta to get mark_scheme
665
- const questionMeta = question.meta ? (typeof question.meta === 'object' ? question.meta : JSON.parse(question.meta)) : {};
666
- const markScheme = questionMeta.markScheme;
667
- let isCorrect = false;
668
- let userMarkScheme = null;
669
- // If mark scheme exists, use AI marking
670
- if (markScheme && markScheme.points && markScheme.points.length > 0) {
671
- try {
672
- userMarkScheme = await aiSessionService.checkWorksheetQuestions(worksheet.workspace.id, ctx.session.user.id, question.prompt, input.answer, markScheme);
673
- // Determine if correct by comparing achieved points vs total points
674
- const achievedTotal = userMarkScheme.points.reduce((sum, p) => sum + (p.achievedPoints || 0), 0);
675
- isCorrect = achievedTotal === markScheme.totalPoints;
676
- }
677
- catch (error) {
678
- logger.error('Failed to check answer with AI', error instanceof Error ? error.message : 'Unknown error');
679
- // Fallback to simple string comparison
680
- isCorrect = question.answer === input.answer;
681
- }
682
- }
683
- else {
684
- // Simple string comparison if no mark scheme
685
- throw new TRPCError({ code: 'BAD_REQUEST', message: 'No mark scheme found for question' });
686
- }
687
- // @todo: figure out this wierd fix
688
- const progress = await ctx.db.worksheetQuestionProgress.upsert({
689
- where: {
690
- worksheetQuestionId_userId: {
691
- worksheetQuestionId: input.questionId,
692
- userId: ctx.session.user.id,
693
- },
694
- },
695
- create: {
696
- worksheetQuestionId: input.questionId,
697
- userId: ctx.session.user.id,
698
- modified: true,
699
- userAnswer: input.answer,
700
- correct: isCorrect,
701
- completedAt: new Date(),
702
- attempts: 1,
703
- meta: userMarkScheme ? { userMarkScheme: JSON.parse(JSON.stringify(userMarkScheme)) } : { userMarkScheme: null },
704
- },
705
- update: {
706
- modified: true,
707
- userAnswer: input.answer,
708
- correct: isCorrect,
709
- completedAt: new Date(),
710
- attempts: { increment: 1 },
711
- meta: userMarkScheme
712
- ? { userMarkScheme: JSON.parse(JSON.stringify(userMarkScheme)) }
713
- : { userMarkScheme: null },
714
- },
715
- });
716
- return { isCorrect, userMarkScheme, progress };
717
- }),
138
+ .mutation(({ ctx, input }) => new WorksheetService(ctx.db).checkAnswer(ctx.session.user.id, input)),
718
139
  });