@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,413 +1,49 @@
1
- import { TRPCError } from "@trpc/server";
2
- import { z } from "zod";
3
- import { router, verifiedProcedure } from "../trpc.js";
4
- import inference from "../lib/inference.js";
5
- import { sanitizeString } from "../lib/validation.js";
6
- import { workspaceAccessFilter } from "../lib/workspace-access.js";
7
- import PusherService from "../lib/pusher.js";
8
- import { ArtifactType } from "../lib/constants.js";
9
- import type { Context } from "../context.js";
10
-
11
- const copilotArtifactType = z.enum([
12
- "study-guide",
13
- "worksheet",
14
- "flashcards",
15
- "notes",
16
- ]);
17
-
18
- const copilotContextSchema = z.object({
19
- workspaceId: z.string().min(1),
20
- artifactId: z.string().min(1),
21
- artifactType: copilotArtifactType,
22
- documentContent: z.string().min(1),
23
- selectedText: z.string().optional(),
24
- viewportText: z.string().optional(),
25
- cursorPosition: z
26
- .object({
27
- start: z.number().int().min(0),
28
- end: z.number().int().min(0),
29
- })
30
- .optional(),
31
- metadata: z
32
- .object({
33
- flashcardId: z.string().optional(),
34
- questionId: z.string().optional(),
35
- documentId: z.string().optional(),
36
- })
37
- .optional(),
38
- });
39
-
40
- const highlightInstructionSchema = z.object({
41
- start: z.number().int().min(0),
42
- end: z.number().int().min(0),
43
- label: z.string().optional(),
44
- });
45
-
46
- const flashcardSchema = z.object({
47
- front: z.string().min(1),
48
- back: z.string().min(1),
49
- });
50
-
51
- type CopilotContextInput = z.infer<typeof copilotContextSchema>;
52
-
53
- const RATE_LIMIT_WINDOW_MS = 60_000;
54
- const RATE_LIMIT_MAX_REQUESTS = 20;
55
- const requestBuckets = new Map<string, number[]>();
56
-
57
- function enforceRateLimit(userId: string, workspaceId: string) {
58
- const now = Date.now();
59
- const key = `${userId}:${workspaceId}`;
60
- const existing = requestBuckets.get(key) ?? [];
61
- const recent = existing.filter((timestamp) => now - timestamp < RATE_LIMIT_WINDOW_MS);
62
-
63
- if (recent.length >= RATE_LIMIT_MAX_REQUESTS) {
64
- throw new TRPCError({
65
- code: "TOO_MANY_REQUESTS",
66
- message: "Too many copilot requests. Please wait a moment.",
67
- });
68
- }
69
-
70
- recent.push(now);
71
- requestBuckets.set(key, recent);
72
- }
73
-
74
- function contextWindow(context: CopilotContextInput): string {
75
- // Pass the full document so the copilot has full context and does not make mistakes
76
- return sanitizeString(context.documentContent, 5_000_000);
77
- }
78
-
79
- function extractJson(content: string): Record<string, unknown> | null {
80
- const fencedMatch = content.match(/```json\s*([\s\S]*?)```/i);
81
- if (fencedMatch?.[1]) {
82
- try {
83
- return JSON.parse(fencedMatch[1]);
84
- } catch {
85
- // fall through
86
- }
87
- }
88
-
89
- const objectMatch = content.match(/\{[\s\S]*\}/);
90
- if (objectMatch?.[0]) {
91
- try {
92
- return JSON.parse(objectMatch[0]);
93
- } catch {
94
- return null;
95
- }
96
- }
97
-
98
- return null;
99
- }
100
-
101
- function getAnswerFromParsedJSON(parsed: Record<string, any> | null): string | null {
102
- if (!parsed) return null;
103
-
104
- const potentialKeys = [
105
- "answer",
106
- "summary",
107
- "explanation",
108
- "content",
109
- "explanationText",
110
- "response",
111
- "result",
112
- ];
113
-
114
- for (const key of potentialKeys) {
115
- if (typeof parsed[key] === "string" && parsed[key].length > 0) {
116
- return parsed[key];
117
- }
118
- }
119
-
120
- return null;
121
- }
122
-
123
- async function assertWorkspaceAccess(
124
- ctx: Context & { session: { user: { id: string } } },
125
- workspaceId: string
126
- ) {
127
- const workspace = await ctx.db.workspace.findFirst({
128
- where: {
129
- id: workspaceId,
130
- ...workspaceAccessFilter(ctx.session.user.id),
131
- },
132
- select: { id: true },
133
- });
134
-
135
- if (!workspace) {
136
- throw new TRPCError({
137
- code: "FORBIDDEN",
138
- message: "You do not have access to this workspace",
139
- });
140
- }
141
- }
142
-
143
- async function assertConversationAccess(params: {
144
- ctx: Context & { session: { user: { id: string } } };
145
- workspaceId: string;
146
- conversationId: string;
147
- }) {
148
- const conversation = await params.ctx.db.copilotConversation.findFirst({
149
- where: {
150
- id: params.conversationId,
151
- workspaceId: params.workspaceId,
152
- userId: params.ctx.session.user.id,
153
- },
154
- select: {
155
- id: true,
156
- },
157
- });
158
-
159
- if (!conversation) {
160
- throw new TRPCError({
161
- code: "NOT_FOUND",
162
- message: "Conversation not found",
163
- });
164
- }
165
- }
166
-
167
- async function persistConversationExchange(params: {
168
- ctx: Context & { session: { user: { id: string } } };
169
- workspaceId: string;
170
- conversationId?: string;
171
- userMessage: string;
172
- assistantMessage: string;
173
- }) {
174
- if (!params.conversationId) return;
175
-
176
- await assertConversationAccess({
177
- ctx: params.ctx,
178
- workspaceId: params.workspaceId,
179
- conversationId: params.conversationId,
180
- });
181
-
182
- const generatedTitle = sanitizeString(
183
- params.userMessage.replace(/\s+/g, " ").trim(),
184
- 80
185
- );
186
-
187
- const conversation = await params.ctx.db.copilotConversation.findUnique({
188
- where: { id: params.conversationId },
189
- select: { title: true },
190
- });
191
-
192
- const shouldUpdateTitle =
193
- !!conversation &&
194
- conversation.title.trim().toLowerCase() === "new chat" &&
195
- generatedTitle.length > 0;
196
-
197
- await params.ctx.db.copilotMessage.createMany({
198
- data: [
199
- {
200
- conversationId: params.conversationId,
201
- role: "USER",
202
- content: sanitizeString(params.userMessage, 8_000),
203
- },
204
- {
205
- conversationId: params.conversationId,
206
- role: "ASSISTANT",
207
- content: sanitizeString(params.assistantMessage, 20_000),
208
- },
209
- ],
210
- });
211
-
212
- await params.ctx.db.copilotConversation.update({
213
- where: { id: params.conversationId },
214
- data: {
215
- updatedAt: new Date(),
216
- ...(shouldUpdateTitle ? { title: generatedTitle } : {}),
217
- },
218
- });
219
- }
220
-
221
- async function callCopilotModel(params: {
222
- mode: "ask" | "explainSelection" | "suggestHighlights" | "generateFlashcards";
223
- context: CopilotContextInput;
224
- message: string;
225
- history?: { role: "user" | "assistant"; content: string }[];
226
- }) {
227
- const documentSnippet = contextWindow(params.context);
228
- const selectedText = params.context.selectedText
229
- ? sanitizeString(params.context.selectedText, 2_000)
230
- : "";
231
- const viewportText = params.context.viewportText
232
- ? sanitizeString(params.context.viewportText, 4_000)
233
- : "";
234
- const message = sanitizeString(params.message, 4_000);
235
-
236
- const systemPrompt = [
237
- "You are an AI study assistant for the Scribe learning platform.",
238
- "Be concise, correct, and safe. Do not claim edits were applied unless asked and confirmed by the user.",
239
- "Return valid JSON only.",
240
- "",
241
- "OUTPUT FORMAT:",
242
- "{",
243
- ' "answer": "markdown response for the user",',
244
- ' "highlights": [{ "start": 0, "end": 10, "label": "optional" }],',
245
- ' "flashcards": [{ "front": "question", "back": "answer" }]',
246
- "}",
247
- "Include only relevant keys for the task.",
248
- "",
249
- `MODE: ${params.mode}`,
250
- `ARTIFACT_TYPE: ${params.context.artifactType}`,
251
- ].join("\n");
252
-
253
- const contextPrompt = [
254
- "DOCUMENT:",
255
- documentSnippet,
256
- "",
257
- "SELECTION:",
258
- selectedText || "None",
259
- "",
260
- "VIEWPORT_TEXT:",
261
- viewportText || "None",
262
- ].join("\n");
263
-
264
- const messages: { role: "system" | "user" | "assistant"; content: string }[] = [
265
- { role: "system", content: systemPrompt },
266
- { role: "user", content: contextPrompt },
267
- ];
268
-
269
- if (params.history && params.history.length > 0) {
270
- params.history.forEach((msg) => {
271
- messages.push({
272
- role: msg.role === "user" ? "user" : "assistant",
273
- content: msg.content,
274
- });
275
- });
276
- }
277
-
278
- messages.push({ role: "user", content: message });
279
-
280
- const response = await inference(messages);
281
- const content = response.choices?.[0]?.message?.content ?? "";
282
- const parsed = extractJson(content);
283
-
284
- return {
285
- rawContent: content,
286
- parsed,
287
- };
288
- }
1
+ import { z } from 'zod';
2
+ import { router, verifiedProcedure } from '../trpc.js';
3
+ import { CopilotService, copilotContextSchema } from '../services/content/copilot.service.js';
289
4
 
290
5
  export const copilot = router({
291
6
  listConversations: verifiedProcedure
292
- .input(
293
- z.object({
294
- workspaceId: z.string().min(1),
295
- })
296
- )
297
- .query(async ({ ctx, input }) => {
298
- await assertWorkspaceAccess(ctx, input.workspaceId);
299
-
300
- const conversations = await ctx.db.copilotConversation.findMany({
301
- where: {
302
- workspaceId: input.workspaceId,
303
- userId: ctx.session.user.id,
304
- },
305
- include: {
306
- messages: {
307
- orderBy: { createdAt: "desc" },
308
- take: 1,
309
- },
310
- },
311
- orderBy: { updatedAt: "desc" },
312
- });
313
-
314
- return conversations.map((conversation) => ({
315
- id: conversation.id,
316
- title: conversation.title,
317
- updatedAt: conversation.updatedAt,
318
- preview: conversation.messages[0]?.content ?? "",
319
- }));
320
- }),
7
+ .input(z.object({ workspaceId: z.string().min(1) }))
8
+ .query(({ ctx, input }) =>
9
+ new CopilotService(ctx.db).listConversations(ctx.session.user.id, input.workspaceId),
10
+ ),
321
11
 
322
12
  getConversation: verifiedProcedure
323
13
  .input(
324
14
  z.object({
325
15
  workspaceId: z.string().min(1),
326
16
  conversationId: z.string().min(1),
327
- })
17
+ }),
328
18
  )
329
- .query(async ({ ctx, input }) => {
330
- await assertWorkspaceAccess(ctx, input.workspaceId);
331
- await assertConversationAccess({
332
- ctx,
333
- workspaceId: input.workspaceId,
334
- conversationId: input.conversationId,
335
- });
336
-
337
- const conversation = await ctx.db.copilotConversation.findUnique({
338
- where: { id: input.conversationId },
339
- include: {
340
- messages: {
341
- orderBy: { createdAt: "asc" },
342
- },
343
- },
344
- });
345
-
346
- if (!conversation) {
347
- throw new TRPCError({
348
- code: "NOT_FOUND",
349
- message: "Conversation not found",
350
- });
351
- }
352
-
353
- return {
354
- id: conversation.id,
355
- title: conversation.title,
356
- messages: conversation.messages.map((message) => ({
357
- id: message.id,
358
- role: message.role === "USER" ? "user" : "assistant",
359
- content: message.content,
360
- createdAt: message.createdAt,
361
- })),
362
- };
363
- }),
19
+ .query(({ ctx, input }) =>
20
+ new CopilotService(ctx.db).getConversation(
21
+ ctx.session.user.id,
22
+ input.workspaceId,
23
+ input.conversationId,
24
+ ),
25
+ ),
364
26
 
365
27
  createConversation: verifiedProcedure
366
- .input(
367
- z.object({
368
- workspaceId: z.string().min(1),
369
- title: z.string().optional(),
370
- })
371
- )
372
- .mutation(async ({ ctx, input }) => {
373
- await assertWorkspaceAccess(ctx, input.workspaceId);
374
-
375
- const conversation = await ctx.db.copilotConversation.create({
376
- data: {
377
- workspaceId: input.workspaceId,
378
- userId: ctx.session.user.id,
379
- title: sanitizeString(input.title || "New Chat", 120),
380
- },
381
- });
382
-
383
- return {
384
- id: conversation.id,
385
- title: conversation.title,
386
- updatedAt: conversation.updatedAt,
387
- };
388
- }),
28
+ .input(z.object({ workspaceId: z.string().min(1), title: z.string().optional() }))
29
+ .mutation(({ ctx, input }) =>
30
+ new CopilotService(ctx.db).createConversation(ctx.session.user.id, input),
31
+ ),
389
32
 
390
33
  deleteConversation: verifiedProcedure
391
34
  .input(
392
35
  z.object({
393
36
  workspaceId: z.string().min(1),
394
37
  conversationId: z.string().min(1),
395
- })
38
+ }),
396
39
  )
397
- .mutation(async ({ ctx, input }) => {
398
- await assertWorkspaceAccess(ctx, input.workspaceId);
399
- await assertConversationAccess({
400
- ctx,
401
- workspaceId: input.workspaceId,
402
- conversationId: input.conversationId,
403
- });
404
-
405
- await ctx.db.copilotConversation.delete({
406
- where: { id: input.conversationId },
407
- });
408
-
409
- return { success: true };
410
- }),
40
+ .mutation(({ ctx, input }) =>
41
+ new CopilotService(ctx.db).deleteConversation(
42
+ ctx.session.user.id,
43
+ input.workspaceId,
44
+ input.conversationId,
45
+ ),
46
+ ),
411
47
 
412
48
  ask: verifiedProcedure
413
49
  .input(
@@ -415,67 +51,9 @@ export const copilot = router({
415
51
  context: copilotContextSchema,
416
52
  message: z.string().min(1),
417
53
  conversationId: z.string().optional(),
418
- })
54
+ }),
419
55
  )
420
- .mutation(async ({ ctx, input }) => {
421
- enforceRateLimit(ctx.session.user.id, input.context.workspaceId);
422
- await assertWorkspaceAccess(ctx, input.context.workspaceId);
423
-
424
- await PusherService.emitTaskComplete(input.context.workspaceId, "copilot:thinking", {
425
- status: "started",
426
- });
427
-
428
- let history: { role: "user" | "assistant"; content: string }[] = [];
429
- if (input.conversationId) {
430
- const messages = await ctx.db.copilotMessage.findMany({
431
- where: { conversationId: input.conversationId },
432
- orderBy: { createdAt: "asc" },
433
- take: 50,
434
- });
435
- history = messages.map((m) => ({
436
- role: m.role.toLowerCase() as "user" | "assistant",
437
- content: m.content,
438
- }));
439
- }
440
-
441
- const model = await callCopilotModel({
442
- mode: "ask",
443
- context: input.context,
444
- message: input.message,
445
- history,
446
- });
447
-
448
- const answer =
449
- getAnswerFromParsedJSON(model.parsed) ||
450
- model.rawContent ||
451
- "I could not generate a response.";
452
-
453
- const highlights = z.array(highlightInstructionSchema).safeParse(model.parsed?.highlights);
454
- const safeHighlights = highlights.success
455
- ? highlights.data.filter(
456
- (item) =>
457
- item.start < item.end &&
458
- item.end <= Math.max(input.context.documentContent.length, 0)
459
- )
460
- : [];
461
-
462
- await PusherService.emitTaskComplete(input.context.workspaceId, "copilot:response", {
463
- status: "completed",
464
- });
465
-
466
- await persistConversationExchange({
467
- ctx,
468
- workspaceId: input.context.workspaceId,
469
- conversationId: input.conversationId,
470
- userMessage: input.message,
471
- assistantMessage: answer,
472
- });
473
-
474
- return {
475
- answer,
476
- highlights: safeHighlights,
477
- };
478
- }),
56
+ .mutation(({ ctx, input }) => new CopilotService(ctx.db).ask(ctx.session.user.id, input)),
479
57
 
480
58
  explainSelection: verifiedProcedure
481
59
  .input(
@@ -483,60 +61,11 @@ export const copilot = router({
483
61
  context: copilotContextSchema,
484
62
  message: z.string().optional(),
485
63
  conversationId: z.string().optional(),
486
- })
64
+ }),
487
65
  )
488
- .mutation(async ({ ctx, input }) => {
489
- enforceRateLimit(ctx.session.user.id, input.context.workspaceId);
490
- await assertWorkspaceAccess(ctx, input.context.workspaceId);
491
-
492
- await PusherService.emitTaskComplete(input.context.workspaceId, "copilot:thinking", {
493
- status: "started",
494
- });
495
-
496
- const question =
497
- input.message && input.message.trim().length > 0
498
- ? input.message
499
- : "Explain the selected text in simple study-friendly terms and include one practical example.";
500
-
501
- let history: { role: "user" | "assistant"; content: string }[] = [];
502
- if (input.conversationId) {
503
- const messages = await ctx.db.copilotMessage.findMany({
504
- where: { conversationId: input.conversationId },
505
- orderBy: { createdAt: "asc" },
506
- take: 50,
507
- });
508
- history = messages.map((m) => ({
509
- role: m.role.toLowerCase() as "user" | "assistant",
510
- content: m.content,
511
- }));
512
- }
513
-
514
- const model = await callCopilotModel({
515
- mode: "explainSelection",
516
- context: input.context,
517
- message: question,
518
- history,
519
- });
520
-
521
- const answer =
522
- getAnswerFromParsedJSON(model.parsed) ||
523
- model.rawContent ||
524
- "I could not explain this selection.";
525
-
526
- await PusherService.emitTaskComplete(input.context.workspaceId, "copilot:response", {
527
- status: "completed",
528
- });
529
-
530
- await persistConversationExchange({
531
- ctx,
532
- workspaceId: input.context.workspaceId,
533
- conversationId: input.conversationId,
534
- userMessage: question,
535
- assistantMessage: answer,
536
- });
537
-
538
- return { answer };
539
- }),
66
+ .mutation(({ ctx, input }) =>
67
+ new CopilotService(ctx.db).explainSelection(ctx.session.user.id, input),
68
+ ),
540
69
 
541
70
  suggestHighlights: verifiedProcedure
542
71
  .input(
@@ -544,78 +73,11 @@ export const copilot = router({
544
73
  context: copilotContextSchema,
545
74
  message: z.string().optional(),
546
75
  conversationId: z.string().optional(),
547
- })
76
+ }),
548
77
  )
549
- .mutation(async ({ ctx, input }) => {
550
- enforceRateLimit(ctx.session.user.id, input.context.workspaceId);
551
- await assertWorkspaceAccess(ctx, input.context.workspaceId);
552
-
553
- await PusherService.emitTaskComplete(input.context.workspaceId, "copilot:thinking", {
554
- status: "started",
555
- });
556
-
557
- const instruction =
558
- input.message && input.message.trim().length > 0
559
- ? input.message
560
- : "Suggest key highlights for the provided section.";
561
-
562
- let history: { role: "user" | "assistant"; content: string }[] = [];
563
- if (input.conversationId) {
564
- const messages = await ctx.db.copilotMessage.findMany({
565
- where: { conversationId: input.conversationId },
566
- orderBy: { createdAt: "asc" },
567
- take: 50,
568
- });
569
- history = messages.map((m) => ({
570
- role: m.role.toLowerCase() as "user" | "assistant",
571
- content: m.content,
572
- }));
573
- }
574
-
575
- const model = await callCopilotModel({
576
- mode: "suggestHighlights",
577
- context: input.context,
578
- message: instruction,
579
- history,
580
- });
581
-
582
- const parsedHighlights = z.array(highlightInstructionSchema).safeParse(
583
- model.parsed?.highlights
584
- );
585
-
586
- const safeHighlights = parsedHighlights.success
587
- ? parsedHighlights.data.filter(
588
- (item) =>
589
- item.start < item.end &&
590
- item.end <= Math.max(input.context.documentContent.length, 0)
591
- )
592
- : [];
593
-
594
- await PusherService.emitTaskComplete(
595
- input.context.workspaceId,
596
- "copilot:highlight_suggestions",
597
- {
598
- highlights: safeHighlights,
599
- }
600
- );
601
-
602
- const answer =
603
- getAnswerFromParsedJSON(model.parsed) ||
604
- "Here are suggested highlights.";
605
-
606
- await persistConversationExchange({
607
- ctx,
608
- workspaceId: input.context.workspaceId,
609
- conversationId: input.conversationId,
610
- userMessage: instruction,
611
- assistantMessage: answer,
612
- });
613
-
614
- return {
615
- answer,
616
- highlights: safeHighlights,
617
- };
618
- }),
78
+ .mutation(({ ctx, input }) =>
79
+ new CopilotService(ctx.db).suggestHighlights(ctx.session.user.id, input),
80
+ ),
619
81
 
620
82
  generateFlashcards: verifiedProcedure
621
83
  .input(
@@ -624,96 +86,9 @@ export const copilot = router({
624
86
  message: z.string().optional(),
625
87
  numCards: z.number().int().min(1).max(30).default(10),
626
88
  conversationId: z.string().optional(),
627
- })
89
+ }),
628
90
  )
629
- .mutation(async ({ ctx, input }) => {
630
- enforceRateLimit(ctx.session.user.id, input.context.workspaceId);
631
- await assertWorkspaceAccess(ctx, input.context.workspaceId);
632
-
633
- await PusherService.emitTaskComplete(input.context.workspaceId, "copilot:thinking", {
634
- status: "started",
635
- });
636
-
637
- const instruction =
638
- input.message && input.message.trim().length > 0
639
- ? input.message
640
- : `Generate ${input.numCards} concise study flashcards from this context.`;
641
-
642
- let history: { role: "user" | "assistant"; content: string }[] = [];
643
- if (input.conversationId) {
644
- const messages = await ctx.db.copilotMessage.findMany({
645
- where: { conversationId: input.conversationId },
646
- orderBy: { createdAt: "asc" },
647
- take: 50,
648
- });
649
- history = messages.map((m) => ({
650
- role: m.role.toLowerCase() as "user" | "assistant",
651
- content: m.content,
652
- }));
653
- }
654
-
655
- const model = await callCopilotModel({
656
- mode: "generateFlashcards",
657
- context: input.context,
658
- message: instruction,
659
- history,
660
- });
661
-
662
- const parsedFlashcards = z.array(flashcardSchema).safeParse(model.parsed?.flashcards);
663
- const cards = (parsedFlashcards.success ? parsedFlashcards.data : []).slice(
664
- 0,
665
- input.numCards
666
- );
667
-
668
- if (cards.length === 0) {
669
- throw new TRPCError({
670
- code: "BAD_REQUEST",
671
- message: "Copilot did not return valid flashcards.",
672
- });
673
- }
674
-
675
- const artifact = await ctx.db.artifact.create({
676
- data: {
677
- workspaceId: input.context.workspaceId,
678
- type: ArtifactType.FLASHCARD_SET,
679
- title: `Copilot Flashcards - ${new Date().toLocaleString()}`,
680
- createdById: ctx.session.user.id,
681
- generating: false,
682
- flashcards: {
683
- create: cards.map((card, index) => ({
684
- front: sanitizeString(card.front, 200),
685
- back: sanitizeString(card.back, 500),
686
- order: index,
687
- tags: ["copilot-generated"],
688
- })),
689
- },
690
- },
691
- include: {
692
- flashcards: true,
693
- },
694
- });
695
-
696
- await PusherService.emitTaskComplete(input.context.workspaceId, "copilot:response", {
697
- status: "completed",
698
- flashcardSetId: artifact.id,
699
- });
700
-
701
- const answer =
702
- getAnswerFromParsedJSON(model.parsed) ||
703
- `Generated ${artifact.flashcards.length} flashcards.`;
704
-
705
- await persistConversationExchange({
706
- ctx,
707
- workspaceId: input.context.workspaceId,
708
- conversationId: input.conversationId,
709
- userMessage: instruction,
710
- assistantMessage: answer,
711
- });
712
-
713
- return {
714
- answer,
715
- artifactId: artifact.id,
716
- flashcards: artifact.flashcards,
717
- };
718
- }),
91
+ .mutation(({ ctx, input }) =>
92
+ new CopilotService(ctx.db).generateFlashcards(ctx.session.user.id, input),
93
+ ),
719
94
  });