@goscribe/server 1.3.3 → 1.5.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 (378) 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/controllers/admin.controller.d.ts +715 -0
  6. package/dist/controllers/admin.controller.js +9 -0
  7. package/dist/controllers/annotations.controller.d.ts +439 -0
  8. package/dist/controllers/annotations.controller.js +9 -0
  9. package/dist/controllers/app-router.controller.d.ts +3011 -0
  10. package/dist/controllers/app-router.controller.js +38 -0
  11. package/dist/controllers/app-router.controller.test.d.ts +1 -0
  12. package/dist/controllers/app-router.controller.test.js +36 -0
  13. package/dist/controllers/auth.controller.d.ts +323 -0
  14. package/dist/controllers/auth.controller.js +9 -0
  15. package/dist/controllers/base.controller.d.ts +4 -0
  16. package/dist/controllers/base.controller.js +5 -0
  17. package/dist/controllers/chat.controller.d.ts +341 -0
  18. package/dist/controllers/chat.controller.js +9 -0
  19. package/dist/controllers/copilot.controller.d.ts +397 -0
  20. package/dist/controllers/copilot.controller.js +9 -0
  21. package/dist/controllers/flashcards.controller.d.ts +651 -0
  22. package/dist/controllers/flashcards.controller.js +9 -0
  23. package/dist/controllers/members.controller.d.ts +339 -0
  24. package/dist/controllers/members.controller.js +9 -0
  25. package/dist/controllers/notifications.controller.d.ts +199 -0
  26. package/dist/controllers/notifications.controller.js +9 -0
  27. package/dist/controllers/payment.controller.d.ts +181 -0
  28. package/dist/controllers/payment.controller.js +9 -0
  29. package/dist/controllers/podcast.controller.d.ts +575 -0
  30. package/dist/controllers/podcast.controller.js +9 -0
  31. package/dist/controllers/router-module.controller.d.ts +5 -0
  32. package/dist/controllers/router-module.controller.js +6 -0
  33. package/dist/controllers/studyguide.controller.d.ts +73 -0
  34. package/dist/controllers/studyguide.controller.js +9 -0
  35. package/dist/controllers/worksheets.controller.d.ts +829 -0
  36. package/dist/controllers/worksheets.controller.js +9 -0
  37. package/dist/controllers/workspace.controller.d.ts +1207 -0
  38. package/dist/controllers/workspace.controller.js +9 -0
  39. package/dist/lib/activity_human_description.test.js +16 -15
  40. package/dist/lib/activity_log_service.test.js +28 -23
  41. package/dist/lib/ai/config.d.ts +20 -0
  42. package/dist/lib/ai/config.js +31 -0
  43. package/dist/lib/ai/embedding-client.d.ts +8 -0
  44. package/dist/lib/ai/embedding-client.js +30 -0
  45. package/dist/lib/ai/index.d.ts +47 -0
  46. package/dist/lib/ai/index.js +28 -0
  47. package/dist/lib/ai/inference-backend/client.d.ts +28 -0
  48. package/dist/lib/ai/inference-backend/client.js +301 -0
  49. package/dist/lib/ai/inference-backend/mocks.d.ts +12 -0
  50. package/dist/lib/ai/inference-backend/mocks.js +133 -0
  51. package/dist/lib/ai/inference-backend/types.d.ts +44 -0
  52. package/dist/lib/ai/inference-backend/types.js +1 -0
  53. package/dist/lib/ai/json-parse.d.ts +2 -0
  54. package/dist/lib/ai/json-parse.js +34 -0
  55. package/dist/lib/ai/llm-client.d.ts +6 -0
  56. package/dist/lib/ai/llm-client.js +19 -0
  57. package/dist/lib/ai/mock.d.ts +2 -0
  58. package/dist/lib/ai/mock.js +10 -0
  59. package/dist/lib/ai/types.d.ts +9 -0
  60. package/dist/lib/ai/types.js +1 -0
  61. package/dist/lib/chunking.d.ts +19 -0
  62. package/dist/lib/chunking.js +47 -0
  63. package/dist/lib/curated-kb-seed.d.ts +12 -0
  64. package/dist/lib/curated-kb-seed.js +155 -0
  65. package/dist/lib/email.js +67 -108
  66. package/dist/lib/embeddings.d.ts +2 -0
  67. package/dist/lib/embeddings.js +1 -0
  68. package/dist/lib/ensure-curated-kb-catalog.d.ts +6 -0
  69. package/dist/lib/ensure-curated-kb-catalog.js +53 -0
  70. package/dist/lib/env.d.ts +1 -5
  71. package/dist/lib/env.js +2 -7
  72. package/dist/lib/inference.d.ts +1 -8
  73. package/dist/lib/inference.js +1 -19
  74. package/dist/lib/kb-meta.d.ts +8 -0
  75. package/dist/lib/kb-meta.js +77 -0
  76. package/dist/lib/note-text.d.ts +1 -0
  77. package/dist/lib/note-text.js +47 -0
  78. package/dist/lib/notification-service.test.js +37 -36
  79. package/dist/lib/pdf.d.ts +11 -0
  80. package/dist/lib/pdf.js +11 -0
  81. package/dist/lib/usage_service.d.ts +2 -1
  82. package/dist/lib/usage_service.js +30 -12
  83. package/dist/lib/worksheet-generation.js +4 -4
  84. package/dist/lib/worksheet-generation.test.js +32 -17
  85. package/dist/lib/workspace-kb.d.ts +5 -0
  86. package/dist/lib/workspace-kb.js +7 -0
  87. package/dist/models/controller-context.model.d.ts +8 -0
  88. package/dist/models/controller-context.model.js +1 -0
  89. package/dist/repositories/artifact.repository.d.ts +60 -0
  90. package/dist/repositories/artifact.repository.js +40 -0
  91. package/dist/repositories/base.repository.d.ts +14 -0
  92. package/dist/repositories/base.repository.js +14 -0
  93. package/dist/repositories/invitation.repository.d.ts +94 -0
  94. package/dist/repositories/invitation.repository.js +44 -0
  95. package/dist/repositories/notification.repository.d.ts +72 -0
  96. package/dist/repositories/notification.repository.js +44 -0
  97. package/dist/repositories/router-module.repository.d.ts +10 -0
  98. package/dist/repositories/router-module.repository.js +14 -0
  99. package/dist/repositories/user.repository.d.ts +74 -0
  100. package/dist/repositories/user.repository.js +37 -0
  101. package/dist/repositories/workspace-member.repository.d.ts +31 -0
  102. package/dist/repositories/workspace-member.repository.js +31 -0
  103. package/dist/repositories/workspace.repository.d.ts +97 -0
  104. package/dist/repositories/workspace.repository.js +79 -0
  105. package/dist/routers/_app.d.ts +528 -33
  106. package/dist/routers/_app.js +4 -0
  107. package/dist/routers/admin.d.ts +0 -4
  108. package/dist/routers/admin.js +21 -549
  109. package/dist/routers/annotations.js +12 -170
  110. package/dist/routers/artifactVersions.d.ts +65 -0
  111. package/dist/routers/artifactVersions.js +14 -0
  112. package/dist/routers/auth.d.ts +0 -6
  113. package/dist/routers/auth.js +36 -421
  114. package/dist/routers/chat.js +15 -229
  115. package/dist/routers/copilot.d.ts +14 -13
  116. package/dist/routers/copilot.js +13 -532
  117. package/dist/routers/flashcards.d.ts +5 -5
  118. package/dist/routers/flashcards.js +23 -349
  119. package/dist/routers/knowledgeBase.d.ts +421 -0
  120. package/dist/routers/knowledgeBase.js +118 -0
  121. package/dist/routers/members.d.ts +0 -41
  122. package/dist/routers/members.js +22 -710
  123. package/dist/routers/notes.d.ts +94 -0
  124. package/dist/routers/notes.js +37 -0
  125. package/dist/routers/notifications.js +7 -109
  126. package/dist/routers/payment.d.ts +3 -2
  127. package/dist/routers/payment.js +11 -393
  128. package/dist/routers/podcast.d.ts +1 -1
  129. package/dist/routers/podcast.js +11 -784
  130. package/dist/routers/studyguide.js +3 -129
  131. package/dist/routers/worksheets.d.ts +29 -14
  132. package/dist/routers/worksheets.js +49 -628
  133. package/dist/routers/workspace.d.ts +0 -4
  134. package/dist/routers/workspace.js +28 -922
  135. package/dist/scripts/purge-deleted-users.js +2 -2
  136. package/dist/server.js +10 -3
  137. package/dist/services/activity/activity-human-description.service.d.ts +13 -0
  138. package/dist/services/activity/activity-human-description.service.js +221 -0
  139. package/dist/services/activity/activity-human-description.service.test.d.ts +1 -0
  140. package/dist/services/activity/activity-human-description.service.test.js +16 -0
  141. package/dist/services/activity/activity-log.service.d.ts +87 -0
  142. package/dist/services/activity/activity-log.service.js +276 -0
  143. package/dist/services/activity/activity-log.service.test.d.ts +1 -0
  144. package/dist/services/activity/activity-log.service.test.js +27 -0
  145. package/dist/services/activity-human-description.service.d.ts +13 -0
  146. package/dist/services/activity-human-description.service.js +221 -0
  147. package/dist/services/activity-human-description.service.test.d.ts +1 -0
  148. package/dist/services/activity-human-description.service.test.js +16 -0
  149. package/dist/services/activity-log.service.d.ts +87 -0
  150. package/dist/services/activity-log.service.js +276 -0
  151. package/dist/services/activity-log.service.test.d.ts +1 -0
  152. package/dist/services/activity-log.service.test.js +27 -0
  153. package/dist/services/admin/admin.service.d.ts +270 -0
  154. package/dist/services/admin/admin.service.js +476 -0
  155. package/dist/services/admin.service.d.ts +270 -0
  156. package/dist/services/admin.service.js +476 -0
  157. package/dist/services/ai/ai-session.service.d.ts +5 -0
  158. package/dist/services/ai/ai-session.service.js +4 -0
  159. package/dist/services/ai-session.service.d.ts +60 -0
  160. package/dist/services/ai-session.service.js +561 -0
  161. package/dist/services/annotation.service.d.ts +177 -0
  162. package/dist/services/annotation.service.js +154 -0
  163. package/dist/services/artifact-notification.service.d.ts +14 -0
  164. package/dist/services/artifact-notification.service.js +20 -0
  165. package/dist/services/artifact-version.service.d.ts +38 -0
  166. package/dist/services/artifact-version.service.js +129 -0
  167. package/dist/services/artifacts/annotation.service.d.ts +177 -0
  168. package/dist/services/artifacts/annotation.service.js +154 -0
  169. package/dist/services/artifacts/artifact-version.service.d.ts +38 -0
  170. package/dist/services/artifacts/artifact-version.service.js +129 -0
  171. package/dist/services/artifacts/chat.service.d.ts +127 -0
  172. package/dist/services/artifacts/chat.service.js +182 -0
  173. package/dist/services/artifacts/study-guide.service.d.ts +18 -0
  174. package/dist/services/artifacts/study-guide.service.js +65 -0
  175. package/dist/services/auth/auth.service.d.ts +94 -0
  176. package/dist/services/auth/auth.service.js +368 -0
  177. package/dist/services/auth.service.d.ts +94 -0
  178. package/dist/services/auth.service.js +368 -0
  179. package/dist/services/base.service.d.ts +14 -0
  180. package/dist/services/base.service.js +14 -0
  181. package/dist/services/billing/payment.service.d.ts +55 -0
  182. package/dist/services/billing/payment.service.js +368 -0
  183. package/dist/services/billing/subscription.service.d.ts +37 -0
  184. package/dist/services/billing/subscription.service.js +654 -0
  185. package/dist/services/billing/usage.service.d.ts +27 -0
  186. package/dist/services/billing/usage.service.js +77 -0
  187. package/dist/services/chat.service.d.ts +127 -0
  188. package/dist/services/chat.service.js +182 -0
  189. package/dist/services/content/copilot.service.d.ts +113 -0
  190. package/dist/services/content/copilot.service.js +453 -0
  191. package/dist/services/content/flashcard-progress.service.d.ts +159 -0
  192. package/dist/services/content/flashcard-progress.service.js +432 -0
  193. package/dist/services/content/flashcard.service.d.ts +140 -0
  194. package/dist/services/content/flashcard.service.js +326 -0
  195. package/dist/services/content/media-analysis.service.d.ts +23 -0
  196. package/dist/services/content/media-analysis.service.js +404 -0
  197. package/dist/services/content/podcast.service.d.ts +267 -0
  198. package/dist/services/content/podcast.service.js +653 -0
  199. package/dist/services/content/worksheet-content.service.d.ts +37 -0
  200. package/dist/services/content/worksheet-content.service.js +84 -0
  201. package/dist/services/content/worksheet-content.service.test.d.ts +1 -0
  202. package/dist/services/content/worksheet-content.service.test.js +69 -0
  203. package/dist/services/content/worksheet-generation.service.d.ts +91 -0
  204. package/dist/services/content/worksheet-generation.service.js +95 -0
  205. package/dist/services/content/worksheet-generation.service.test.d.ts +1 -0
  206. package/dist/services/content/worksheet-generation.service.test.js +20 -0
  207. package/dist/services/content/worksheet.service.d.ts +347 -0
  208. package/dist/services/content/worksheet.service.js +599 -0
  209. package/dist/services/copilot.service.d.ts +116 -0
  210. package/dist/services/copilot.service.js +447 -0
  211. package/dist/services/flashcard-progress.service.d.ts +2 -2
  212. package/dist/services/flashcard-progress.service.js +3 -2
  213. package/dist/services/flashcard.service.d.ts +140 -0
  214. package/dist/services/flashcard.service.js +325 -0
  215. package/dist/services/invitation.service.d.ts +66 -0
  216. package/dist/services/invitation.service.js +348 -0
  217. package/dist/services/knowledge/knowledge-base.service.d.ts +316 -0
  218. package/dist/services/knowledge/knowledge-base.service.js +544 -0
  219. package/dist/services/knowledge-base.service.d.ts +316 -0
  220. package/dist/services/knowledge-base.service.js +536 -0
  221. package/dist/services/media-analysis.service.d.ts +23 -0
  222. package/dist/services/media-analysis.service.js +384 -0
  223. package/dist/services/member.service.d.ts +36 -0
  224. package/dist/services/member.service.js +193 -0
  225. package/dist/services/members/invitation.service.d.ts +66 -0
  226. package/dist/services/members/invitation.service.js +348 -0
  227. package/dist/services/members/member.service.d.ts +36 -0
  228. package/dist/services/members/member.service.js +193 -0
  229. package/dist/services/note.service.d.ts +55 -0
  230. package/dist/services/note.service.js +111 -0
  231. package/dist/services/notification.service.d.ts +214 -0
  232. package/dist/services/notification.service.js +550 -0
  233. package/dist/services/notification.service.test.d.ts +1 -0
  234. package/dist/services/notification.service.test.js +87 -0
  235. package/dist/services/notifications/notification.service.d.ts +214 -0
  236. package/dist/services/notifications/notification.service.js +550 -0
  237. package/dist/services/notifications/notification.service.test.d.ts +1 -0
  238. package/dist/services/notifications/notification.service.test.js +87 -0
  239. package/dist/services/payment.service.d.ts +55 -0
  240. package/dist/services/payment.service.js +368 -0
  241. package/dist/services/podcast.service.d.ts +267 -0
  242. package/dist/services/podcast.service.js +654 -0
  243. package/dist/services/router-module.service.d.ts +7 -0
  244. package/dist/services/router-module.service.js +10 -0
  245. package/dist/services/study-guide.service.d.ts +18 -0
  246. package/dist/services/study-guide.service.js +65 -0
  247. package/dist/services/subscription.service.d.ts +37 -0
  248. package/dist/services/subscription.service.js +654 -0
  249. package/dist/services/usage-limit-policy.service.d.ts +12 -0
  250. package/dist/services/usage-limit-policy.service.js +22 -0
  251. package/dist/services/usage-limit-policy.service.test.d.ts +1 -0
  252. package/dist/services/usage-limit-policy.service.test.js +46 -0
  253. package/dist/services/usage.service.d.ts +27 -0
  254. package/dist/services/usage.service.js +77 -0
  255. package/dist/services/worksheet-content.service.d.ts +42 -0
  256. package/dist/services/worksheet-content.service.js +84 -0
  257. package/dist/services/worksheet-content.service.test.d.ts +1 -0
  258. package/dist/services/worksheet-content.service.test.js +69 -0
  259. package/dist/services/worksheet-generation.service.d.ts +91 -0
  260. package/dist/services/worksheet-generation.service.js +95 -0
  261. package/dist/services/worksheet-generation.service.test.d.ts +1 -0
  262. package/dist/services/worksheet-generation.service.test.js +20 -0
  263. package/dist/services/worksheet.service.d.ts +385 -0
  264. package/dist/services/worksheet.service.js +596 -0
  265. package/dist/services/workspace/workspace-analytics.service.d.ts +24 -0
  266. package/dist/services/workspace/workspace-analytics.service.js +95 -0
  267. package/dist/services/workspace/workspace-kb.service.d.ts +40 -0
  268. package/dist/services/workspace/workspace-kb.service.js +184 -0
  269. package/dist/services/workspace/workspace.service.d.ts +307 -0
  270. package/dist/services/workspace/workspace.service.js +394 -0
  271. package/dist/services/workspace-analytics.service.d.ts +24 -0
  272. package/dist/services/workspace-analytics.service.js +95 -0
  273. package/dist/services/workspace-kb.service.d.ts +40 -0
  274. package/dist/services/workspace-kb.service.js +184 -0
  275. package/dist/services/workspace-progress.service.d.ts +27 -0
  276. package/dist/services/workspace-progress.service.js +56 -0
  277. package/dist/services/workspace-progress.service.test.d.ts +1 -0
  278. package/dist/services/workspace-progress.service.test.js +49 -0
  279. package/dist/services/workspace.service.d.ts +307 -0
  280. package/dist/services/workspace.service.js +390 -0
  281. package/dist/trpc.js +2 -2
  282. package/package.json +5 -6
  283. package/prisma/migrations/20260509000001_add_knowledge_base/migration.sql +99 -0
  284. package/prisma/migrations/20260509000002_curate_knowledge_base/migration.sql +52 -0
  285. package/prisma/migrations/20260522000000_add_notes/migration.sql +27 -0
  286. package/prisma/migrations/20260524000000_remove_notes/migration.sql +3 -0
  287. package/prisma/schema.prisma +150 -48
  288. package/prisma/seed.mjs +67 -0
  289. package/scripts/debug/README.md +4 -0
  290. package/src/README.md +63 -0
  291. package/src/lib/ai/config.ts +34 -0
  292. package/src/lib/ai/embedding-client.ts +47 -0
  293. package/src/lib/ai/index.ts +62 -0
  294. package/src/lib/ai/inference-backend/client.ts +479 -0
  295. package/src/lib/ai/inference-backend/mocks.ts +171 -0
  296. package/src/lib/ai/inference-backend/types.ts +50 -0
  297. package/src/lib/ai/json-parse.ts +35 -0
  298. package/src/lib/ai/llm-client.ts +31 -0
  299. package/src/lib/ai/mock.ts +12 -0
  300. package/src/lib/ai/types.ts +11 -0
  301. package/src/lib/chunking.ts +81 -0
  302. package/src/lib/curated-kb-seed.ts +164 -0
  303. package/src/lib/email.ts +77 -115
  304. package/src/lib/embeddings.ts +9 -0
  305. package/src/lib/ensure-curated-kb-catalog.ts +60 -0
  306. package/src/lib/env.ts +2 -7
  307. package/src/lib/inference.ts +1 -21
  308. package/src/lib/kb-meta.ts +81 -0
  309. package/src/lib/pdf.ts +23 -0
  310. package/src/lib/workspace-kb.ts +7 -0
  311. package/src/repositories/artifact.repository.ts +55 -0
  312. package/src/repositories/base.repository.ts +19 -0
  313. package/src/repositories/invitation.repository.ts +53 -0
  314. package/src/repositories/notification.repository.ts +53 -0
  315. package/src/repositories/user.repository.ts +44 -0
  316. package/src/repositories/workspace-member.repository.ts +38 -0
  317. package/src/repositories/workspace.repository.ts +89 -0
  318. package/src/routers/_app.ts +4 -0
  319. package/src/routers/admin.ts +124 -692
  320. package/src/routers/annotations.ts +25 -203
  321. package/src/routers/artifactVersions.ts +32 -0
  322. package/src/routers/auth.ts +81 -519
  323. package/src/routers/chat.ts +42 -245
  324. package/src/routers/copilot.ts +41 -666
  325. package/src/routers/flashcards.ts +108 -404
  326. package/src/routers/knowledgeBase.ts +216 -0
  327. package/src/routers/members.ts +60 -782
  328. package/src/routers/notifications.ts +15 -117
  329. package/src/routers/payment.ts +37 -446
  330. package/src/routers/podcast.ts +36 -898
  331. package/src/routers/studyguide.ts +5 -144
  332. package/src/routers/worksheets.ts +171 -735
  333. package/src/routers/workspace.ts +138 -1109
  334. package/src/scripts/purge-deleted-users.ts +2 -2
  335. package/src/server.ts +10 -3
  336. package/src/{lib/activity_human_description.test.ts → services/activity/activity-human-description.service.test.ts} +1 -1
  337. package/src/{lib/activity_log_service.test.ts → services/activity/activity-log.service.test.ts} +1 -1
  338. package/src/{lib/activity_log_service.ts → services/activity/activity-log.service.ts} +2 -2
  339. package/src/services/admin/admin.service.ts +612 -0
  340. package/src/services/ai/ai-session.service.ts +5 -0
  341. package/src/services/artifacts/annotation.service.ts +189 -0
  342. package/src/services/artifacts/artifact-version.service.ts +151 -0
  343. package/src/services/artifacts/chat.service.ts +197 -0
  344. package/src/services/artifacts/study-guide.service.ts +72 -0
  345. package/src/services/auth/auth.service.ts +473 -0
  346. package/src/services/base.service.ts +19 -0
  347. package/src/services/billing/payment.service.ts +436 -0
  348. package/src/{lib/subscription_service.ts → services/billing/subscription.service.ts} +5 -5
  349. package/src/{lib/usage_service.ts → services/billing/usage.service.ts} +32 -12
  350. package/src/services/content/copilot.service.ts +596 -0
  351. package/src/services/{flashcard-progress.service.ts → content/flashcard-progress.service.ts} +6 -3
  352. package/src/services/content/flashcard.service.ts +394 -0
  353. package/src/services/content/media-analysis.service.ts +556 -0
  354. package/src/services/content/podcast.service.ts +777 -0
  355. package/src/services/content/worksheet-content.service.test.ts +83 -0
  356. package/src/services/content/worksheet-content.service.ts +117 -0
  357. package/src/{lib/worksheet-generation.test.ts → services/content/worksheet-generation.service.test.ts} +1 -1
  358. package/src/services/content/worksheet.service.ts +751 -0
  359. package/src/services/knowledge/knowledge-base.service.ts +705 -0
  360. package/src/services/members/invitation.service.ts +427 -0
  361. package/src/services/members/member.service.ts +241 -0
  362. package/src/{lib/notification-service.test.ts → services/notifications/notification.service.test.ts} +2 -2
  363. package/src/{lib/notification-service.ts → services/notifications/notification.service.ts} +102 -1
  364. package/src/services/workspace/workspace-analytics.service.ts +107 -0
  365. package/src/services/workspace/workspace-kb.service.ts +273 -0
  366. package/src/services/workspace/workspace.service.ts +481 -0
  367. package/src/trpc.ts +2 -2
  368. package/src/lib/ai-session.ts +0 -704
  369. package/src/lib/workspace-access.ts +0 -13
  370. /package/{check-difficulty.cjs → scripts/debug/check-difficulty.cjs} +0 -0
  371. /package/{check-questions.cjs → scripts/debug/check-questions.cjs} +0 -0
  372. /package/{db-summary.cjs → scripts/debug/db-summary.cjs} +0 -0
  373. /package/{mcq-test.cjs → scripts/debug/mcq-test.cjs} +0 -0
  374. /package/{test-generate.js → scripts/debug/test-generate.js} +0 -0
  375. /package/{test-ratio.cjs → scripts/debug/test-ratio.cjs} +0 -0
  376. /package/{zod-test.cjs → scripts/debug/zod-test.cjs} +0 -0
  377. /package/src/{lib/activity_human_description.ts → services/activity/activity-human-description.service.ts} +0 -0
  378. /package/src/{lib/worksheet-generation.ts → services/content/worksheet-generation.service.ts} +0 -0
@@ -0,0 +1,544 @@
1
+ import { TRPCError } from '@trpc/server';
2
+ import { BaseService } from '../base.service.js';
3
+ import { workspaceAccessWhere } from '../../repositories/workspace.repository.js';
4
+ import { supabaseClient } from '../../lib/storage.js';
5
+ import { DEFAULT_EMBEDDING_DIM, DEFAULT_EMBEDDING_MODEL, embedQuery, embedTexts, toVectorLiteral, } from '../../lib/embeddings.js';
6
+ import { extractPdfText } from '../../lib/pdf.js';
7
+ import { chunkText } from '../../lib/chunking.js';
8
+ import { ai } from '../../lib/ai/index.js';
9
+ import { isWorkspaceManagedKb } from '../../lib/workspace-kb.js';
10
+ import { buildKbMetaFromName } from '../../lib/kb-meta.js';
11
+ const KB_BUCKET = process.env.SUPABASE_KB_BUCKET ?? 'media';
12
+ const MAX_PDF_BYTES = 50 * 1024 * 1024;
13
+ export class KnowledgeBaseService extends BaseService {
14
+ constructor(db) {
15
+ super(db);
16
+ }
17
+ // ───────────────────────────────────────── access helpers
18
+ async assertWorkspaceAccess(userId, workspaceId) {
19
+ const ws = await this.db.workspace.findFirst({
20
+ where: { id: workspaceId, ...workspaceAccessWhere(userId) },
21
+ select: { id: true },
22
+ });
23
+ if (!ws) {
24
+ throw new TRPCError({
25
+ code: 'NOT_FOUND',
26
+ message: 'Workspace not found or access denied',
27
+ });
28
+ }
29
+ }
30
+ /**
31
+ * Loads a knowledge base only if it's reachable to the calling user, i.e.
32
+ * either curated (visible in catalog) or attached to one of their accessible
33
+ * workspaces.
34
+ */
35
+ async loadKbForUser(userId, kbId) {
36
+ const kb = await this.db.knowledgeBase.findFirst({
37
+ where: {
38
+ id: kbId,
39
+ OR: [
40
+ { isCurated: true },
41
+ { workspaces: { some: { workspace: workspaceAccessWhere(userId) } } },
42
+ ],
43
+ },
44
+ });
45
+ if (!kb) {
46
+ throw new TRPCError({
47
+ code: 'NOT_FOUND',
48
+ message: 'Knowledge base not found',
49
+ });
50
+ }
51
+ return kb;
52
+ }
53
+ /** Admin-only: load by id without membership/curation checks. */
54
+ async loadKbAsAdmin(kbId) {
55
+ const kb = await this.db.knowledgeBase.findUnique({ where: { id: kbId } });
56
+ if (!kb) {
57
+ throw new TRPCError({
58
+ code: 'NOT_FOUND',
59
+ message: 'Knowledge base not found',
60
+ });
61
+ }
62
+ return kb;
63
+ }
64
+ // ───────────────────────────────────────── user-facing reads
65
+ /**
66
+ * KBs attached to a given workspace (the user's "library" for that
67
+ * workspace).
68
+ */
69
+ async listForWorkspace(userId, workspaceId) {
70
+ await this.assertWorkspaceAccess(userId, workspaceId);
71
+ const rows = await this.db.workspaceKnowledgeBase.findMany({
72
+ where: { workspaceId },
73
+ orderBy: { createdAt: 'desc' },
74
+ include: {
75
+ knowledgeBase: {
76
+ include: { _count: { select: { documents: true } } },
77
+ },
78
+ },
79
+ });
80
+ return rows
81
+ .map((r) => ({
82
+ ...r.knowledgeBase,
83
+ attachmentId: r.id,
84
+ attachedAt: r.createdAt,
85
+ attachedById: r.addedById,
86
+ }))
87
+ .filter((kb) => !isWorkspaceManagedKb(kb));
88
+ }
89
+ /**
90
+ * Search the curated catalog. Optionally pass a workspaceId to also tag
91
+ * each result with whether the workspace already has it attached.
92
+ */
93
+ async searchCatalog(userId, input) {
94
+ const limit = Math.min(Math.max(input.limit ?? 50, 1), 100);
95
+ const q = (input.query ?? '').trim();
96
+ if (input.workspaceId) {
97
+ await this.assertWorkspaceAccess(userId, input.workspaceId);
98
+ }
99
+ const kbs = await this.db.knowledgeBase.findMany({
100
+ where: {
101
+ isCurated: true,
102
+ ...(q
103
+ ? {
104
+ OR: [
105
+ { name: { contains: q, mode: 'insensitive' } },
106
+ { description: { contains: q, mode: 'insensitive' } },
107
+ ],
108
+ }
109
+ : {}),
110
+ },
111
+ orderBy: { updatedAt: 'desc' },
112
+ take: limit,
113
+ include: { _count: { select: { documents: true } } },
114
+ });
115
+ if (!input.workspaceId) {
116
+ return kbs.map((kb) => ({ ...kb, attached: false }));
117
+ }
118
+ const attachments = await this.db.workspaceKnowledgeBase.findMany({
119
+ where: {
120
+ workspaceId: input.workspaceId,
121
+ knowledgeBaseId: { in: kbs.map((k) => k.id) },
122
+ },
123
+ select: { knowledgeBaseId: true },
124
+ });
125
+ const attachedSet = new Set(attachments.map((a) => a.knowledgeBaseId));
126
+ return kbs.map((kb) => ({ ...kb, attached: attachedSet.has(kb.id) }));
127
+ }
128
+ async get(userId, id) {
129
+ const kb = await this.loadKbForUser(userId, id);
130
+ const documents = await this.db.knowledgeBaseDocument.findMany({
131
+ where: { knowledgeBaseId: id },
132
+ orderBy: { createdAt: 'desc' },
133
+ });
134
+ return { ...kb, documents };
135
+ }
136
+ async listDocuments(userId, knowledgeBaseId) {
137
+ await this.loadKbForUser(userId, knowledgeBaseId);
138
+ return this.db.knowledgeBaseDocument.findMany({
139
+ where: { knowledgeBaseId },
140
+ orderBy: { createdAt: 'desc' },
141
+ });
142
+ }
143
+ // ───────────────────────────────────────── user-facing writes
144
+ /** Attach a curated KB to one of the user's workspaces. */
145
+ async addToWorkspace(userId, input) {
146
+ await this.assertWorkspaceAccess(userId, input.workspaceId);
147
+ const kb = await this.db.knowledgeBase.findUnique({
148
+ where: { id: input.knowledgeBaseId },
149
+ select: { id: true, isCurated: true },
150
+ });
151
+ if (!kb)
152
+ throw new TRPCError({ code: 'NOT_FOUND' });
153
+ if (!kb.isCurated) {
154
+ throw new TRPCError({
155
+ code: 'FORBIDDEN',
156
+ message: 'This knowledge base is private. Only an administrator can attach it to a workspace.',
157
+ });
158
+ }
159
+ const existing = await this.db.workspaceKnowledgeBase.findUnique({
160
+ where: {
161
+ workspaceId_knowledgeBaseId: {
162
+ workspaceId: input.workspaceId,
163
+ knowledgeBaseId: kb.id,
164
+ },
165
+ },
166
+ });
167
+ if (existing)
168
+ return existing;
169
+ return this.db.workspaceKnowledgeBase.create({
170
+ data: {
171
+ workspaceId: input.workspaceId,
172
+ knowledgeBaseId: kb.id,
173
+ addedById: userId,
174
+ },
175
+ });
176
+ }
177
+ /** Detach a KB from one of the user's workspaces. */
178
+ async removeFromWorkspace(userId, input) {
179
+ await this.assertWorkspaceAccess(userId, input.workspaceId);
180
+ const kb = await this.db.knowledgeBase.findUnique({
181
+ where: { id: input.knowledgeBaseId },
182
+ select: { meta: true },
183
+ });
184
+ if (kb && isWorkspaceManagedKb(kb)) {
185
+ throw new TRPCError({
186
+ code: 'BAD_REQUEST',
187
+ message: 'The workspace knowledge base is managed automatically and cannot be removed.',
188
+ });
189
+ }
190
+ await this.db.workspaceKnowledgeBase.deleteMany({
191
+ where: {
192
+ workspaceId: input.workspaceId,
193
+ knowledgeBaseId: input.knowledgeBaseId,
194
+ },
195
+ });
196
+ return true;
197
+ }
198
+ // ───────────────────────────────────────── retrieval
199
+ async query(userId, input) {
200
+ const kb = await this.loadKbForUser(userId, input.knowledgeBaseId);
201
+ return this.queryForKb(kb, input);
202
+ }
203
+ async queryForKb(kb, input) {
204
+ const topK = Math.min(Math.max(input.topK ?? 5, 1), 20);
205
+ const queryVec = await embedQuery(input.query, { model: kb.embeddingModel });
206
+ const vecLiteral = toVectorLiteral(queryVec);
207
+ const rows = await this.db.$queryRawUnsafe(`SELECT c."id", c."documentId", c."chunkIndex", c."content",
208
+ (c."embedding" <=> $1::vector) AS "distance",
209
+ d."name" AS "documentName"
210
+ FROM "KnowledgeBaseChunk" c
211
+ JOIN "KnowledgeBaseDocument" d ON d."id" = c."documentId"
212
+ WHERE c."knowledgeBaseId" = $2
213
+ AND c."embedding" IS NOT NULL
214
+ ORDER BY c."embedding" <=> $1::vector
215
+ LIMIT ${topK}`, vecLiteral, kb.id);
216
+ const matches = rows.map((r) => ({
217
+ chunkId: r.id,
218
+ documentId: r.documentId,
219
+ documentName: r.documentName,
220
+ chunkIndex: r.chunkIndex,
221
+ content: r.content,
222
+ score: 1 - Number(r.distance),
223
+ }));
224
+ let answer = null;
225
+ if (input.generateAnswer && matches.length > 0) {
226
+ try {
227
+ const context = matches
228
+ .map((m, i) => `[${i + 1}] (${m.documentName}, chunk ${m.chunkIndex})\n${m.content}`)
229
+ .join('\n\n---\n\n');
230
+ const completion = await ai.llm.complete([
231
+ {
232
+ role: 'system',
233
+ content: 'You are a helpful assistant. Answer the user\'s question using ONLY the provided context. Cite sources as [1], [2], etc. If the answer is not in the context, say so.',
234
+ },
235
+ {
236
+ role: 'user',
237
+ content: `Context:\n${context}\n\nQuestion: ${input.query}`,
238
+ },
239
+ ]);
240
+ answer = completion.choices?.[0]?.message?.content ?? null;
241
+ }
242
+ catch (err) {
243
+ this.logger.error('KB answer generation failed', err);
244
+ }
245
+ }
246
+ return { matches, answer };
247
+ }
248
+ // ───────────────────────────────────────── document processing core
249
+ async processDocumentForKb(kb, documentId) {
250
+ const doc = await this.db.knowledgeBaseDocument.findFirst({
251
+ where: { id: documentId, knowledgeBaseId: kb.id },
252
+ });
253
+ if (!doc)
254
+ throw new TRPCError({ code: 'NOT_FOUND' });
255
+ if (!doc.bucket || !doc.objectKey) {
256
+ throw new TRPCError({
257
+ code: 'BAD_REQUEST',
258
+ message: 'Document is missing storage location',
259
+ });
260
+ }
261
+ if (doc.status === 'PROCESSING' || doc.status === 'READY') {
262
+ return doc;
263
+ }
264
+ await this.db.knowledgeBaseDocument.update({
265
+ where: { id: doc.id },
266
+ data: { status: 'PROCESSING', errorMessage: null },
267
+ });
268
+ await this.db.knowledgeBase.update({
269
+ where: { id: kb.id },
270
+ data: { status: 'INDEXING' },
271
+ });
272
+ try {
273
+ const { data, error } = await supabaseClient.storage
274
+ .from(doc.bucket)
275
+ .download(doc.objectKey);
276
+ if (error || !data) {
277
+ throw new Error(error?.message ?? 'Failed to download document');
278
+ }
279
+ const buf = Buffer.from(await data.arrayBuffer());
280
+ let text;
281
+ let numPages = null;
282
+ if (doc.mimeType === 'application/pdf' || doc.name.toLowerCase().endsWith('.pdf')) {
283
+ const parsed = await extractPdfText(buf);
284
+ text = parsed.text;
285
+ numPages = parsed.numPages;
286
+ }
287
+ else {
288
+ text = buf.toString('utf8');
289
+ }
290
+ if (!text.trim()) {
291
+ throw new Error('No text could be extracted from the document');
292
+ }
293
+ const chunks = chunkText(text, { targetTokens: 500, overlapTokens: 60 });
294
+ if (chunks.length === 0) {
295
+ throw new Error('Document produced no chunks');
296
+ }
297
+ console.log('chunks', chunks);
298
+ const embeddings = await embedTexts(chunks.map((c) => c.content), { model: kb.embeddingModel });
299
+ console.log('embeddings', embeddings);
300
+ await this.db.knowledgeBaseChunk.deleteMany({
301
+ where: { documentId: doc.id },
302
+ });
303
+ for (let i = 0; i < chunks.length; i += 1) {
304
+ const chunk = chunks[i];
305
+ const vec = toVectorLiteral(embeddings[i] ?? []);
306
+ await this.db.$executeRawUnsafe(`INSERT INTO "KnowledgeBaseChunk"
307
+ ("id", "documentId", "knowledgeBaseId", "chunkIndex",
308
+ "pageNumber", "content", "tokenCount", "embedding", "createdAt")
309
+ VALUES (
310
+ gen_random_uuid()::text,
311
+ $1, $2, $3, $4, $5, $6, $7::vector, NOW()
312
+ )`, doc.id, kb.id, chunk.index, null, chunk.content, chunk.approxTokens, vec);
313
+ }
314
+ await this.db.knowledgeBaseDocument.update({
315
+ where: { id: doc.id },
316
+ data: {
317
+ status: 'READY',
318
+ numChunks: chunks.length,
319
+ numPages: numPages ?? undefined,
320
+ },
321
+ });
322
+ const stillProcessing = await this.db.knowledgeBaseDocument.count({
323
+ where: { knowledgeBaseId: kb.id, status: 'PROCESSING' },
324
+ });
325
+ if (stillProcessing === 0) {
326
+ await this.db.knowledgeBase.update({
327
+ where: { id: kb.id },
328
+ data: { status: 'READY' },
329
+ });
330
+ }
331
+ return this.db.knowledgeBaseDocument.findUnique({
332
+ where: { id: doc.id },
333
+ });
334
+ }
335
+ catch (err) {
336
+ this.logger.error(`KB processing failed for doc ${doc.id}`, err);
337
+ await this.db.knowledgeBaseDocument.update({
338
+ where: { id: doc.id },
339
+ data: {
340
+ status: 'FAILED',
341
+ errorMessage: err?.message?.slice(0, 500) ?? 'Unknown error',
342
+ },
343
+ });
344
+ await this.db.knowledgeBase.update({
345
+ where: { id: kb.id },
346
+ data: { status: 'ERROR' },
347
+ });
348
+ throw new TRPCError({
349
+ code: 'INTERNAL_SERVER_ERROR',
350
+ message: err?.message ?? 'Failed to process document',
351
+ });
352
+ }
353
+ }
354
+ // ───────────────────────────────────────── admin entrypoints
355
+ async adminListAll() {
356
+ return this.db.knowledgeBase.findMany({
357
+ orderBy: { updatedAt: 'desc' },
358
+ include: {
359
+ _count: {
360
+ select: { documents: true, workspaces: true },
361
+ },
362
+ },
363
+ });
364
+ }
365
+ async adminGet(id) {
366
+ const kb = await this.loadKbAsAdmin(id);
367
+ const [documents, workspaces] = await Promise.all([
368
+ this.db.knowledgeBaseDocument.findMany({
369
+ where: { knowledgeBaseId: id },
370
+ orderBy: { createdAt: 'desc' },
371
+ }),
372
+ this.db.workspaceKnowledgeBase.findMany({
373
+ where: { knowledgeBaseId: id },
374
+ orderBy: { createdAt: 'desc' },
375
+ include: {
376
+ workspace: { select: { id: true, title: true, icon: true } },
377
+ },
378
+ }),
379
+ ]);
380
+ return { ...kb, documents, workspaces };
381
+ }
382
+ async adminListDocuments(knowledgeBaseId) {
383
+ await this.loadKbAsAdmin(knowledgeBaseId);
384
+ return this.db.knowledgeBaseDocument.findMany({
385
+ where: { knowledgeBaseId },
386
+ orderBy: { createdAt: 'desc' },
387
+ });
388
+ }
389
+ async adminCreate(actorUserId, input) {
390
+ const meta = buildKbMetaFromName(input.name);
391
+ return this.db.knowledgeBase.create({
392
+ data: {
393
+ createdById: actorUserId,
394
+ name: input.name,
395
+ description: input.description,
396
+ isCurated: input.isCurated ?? false,
397
+ embeddingModel: DEFAULT_EMBEDDING_MODEL,
398
+ embeddingDim: DEFAULT_EMBEDDING_DIM,
399
+ meta,
400
+ },
401
+ });
402
+ }
403
+ async adminUpdate(input) {
404
+ const existing = await this.loadKbAsAdmin(input.id);
405
+ const nextName = input.name ?? existing.name;
406
+ const meta = input.name !== undefined
407
+ ? buildKbMetaFromName(nextName, existing.meta ?? null)
408
+ : undefined;
409
+ return this.db.knowledgeBase.update({
410
+ where: { id: input.id },
411
+ data: {
412
+ name: input.name ?? undefined,
413
+ description: input.description === undefined ? undefined : input.description,
414
+ isCurated: input.isCurated ?? undefined,
415
+ ...(meta !== undefined ? { meta } : {}),
416
+ },
417
+ });
418
+ }
419
+ async adminDelete(id) {
420
+ const kb = await this.loadKbAsAdmin(id);
421
+ const docs = await this.db.knowledgeBaseDocument.findMany({
422
+ where: { knowledgeBaseId: kb.id },
423
+ select: { bucket: true, objectKey: true },
424
+ });
425
+ for (const d of docs) {
426
+ if (d.bucket && d.objectKey) {
427
+ supabaseClient.storage
428
+ .from(d.bucket)
429
+ .remove([d.objectKey])
430
+ .catch((err) => {
431
+ this.logger.error(`Failed to remove KB doc ${d.objectKey} from ${d.bucket}`, err);
432
+ });
433
+ }
434
+ }
435
+ await this.db.knowledgeBase.delete({ where: { id: kb.id } });
436
+ return true;
437
+ }
438
+ /** Admin-only: attach a KB to a workspace (curated or not). */
439
+ async adminAttachToWorkspace(actorUserId, input) {
440
+ const [ws, kb] = await Promise.all([
441
+ this.db.workspace.findUnique({
442
+ where: { id: input.workspaceId },
443
+ select: { id: true },
444
+ }),
445
+ this.db.knowledgeBase.findUnique({
446
+ where: { id: input.knowledgeBaseId },
447
+ select: { id: true },
448
+ }),
449
+ ]);
450
+ if (!ws)
451
+ throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' });
452
+ if (!kb)
453
+ throw new TRPCError({ code: 'NOT_FOUND', message: 'Knowledge base not found' });
454
+ const existing = await this.db.workspaceKnowledgeBase.findUnique({
455
+ where: {
456
+ workspaceId_knowledgeBaseId: {
457
+ workspaceId: ws.id,
458
+ knowledgeBaseId: kb.id,
459
+ },
460
+ },
461
+ });
462
+ if (existing)
463
+ return existing;
464
+ return this.db.workspaceKnowledgeBase.create({
465
+ data: {
466
+ workspaceId: ws.id,
467
+ knowledgeBaseId: kb.id,
468
+ addedById: actorUserId,
469
+ },
470
+ });
471
+ }
472
+ async adminDetachFromWorkspace(input) {
473
+ await this.db.workspaceKnowledgeBase.deleteMany({
474
+ where: {
475
+ workspaceId: input.workspaceId,
476
+ knowledgeBaseId: input.knowledgeBaseId,
477
+ },
478
+ });
479
+ return true;
480
+ }
481
+ async adminRequestUploadUrl(input) {
482
+ if (input.size > MAX_PDF_BYTES) {
483
+ throw new TRPCError({
484
+ code: 'PAYLOAD_TOO_LARGE',
485
+ message: `Document exceeds the ${MAX_PDF_BYTES / (1024 * 1024)}MB limit`,
486
+ });
487
+ }
488
+ const kb = await this.loadKbAsAdmin(input.knowledgeBaseId);
489
+ const objectKey = `kb/${kb.id}/${Date.now()}-${input.filename.replace(/[^a-zA-Z0-9._-]+/g, '_')}`;
490
+ const doc = await this.db.knowledgeBaseDocument.create({
491
+ data: {
492
+ knowledgeBaseId: kb.id,
493
+ name: input.filename,
494
+ mimeType: input.contentType,
495
+ size: input.size,
496
+ bucket: KB_BUCKET,
497
+ objectKey,
498
+ status: 'PENDING',
499
+ },
500
+ });
501
+ const { data: signed, error } = await supabaseClient.storage
502
+ .from(KB_BUCKET)
503
+ .createSignedUploadUrl(objectKey);
504
+ if (error || !signed) {
505
+ this.logger.error('KB upload URL error', error);
506
+ await this.db.knowledgeBaseDocument.delete({ where: { id: doc.id } });
507
+ throw new TRPCError({
508
+ code: 'INTERNAL_SERVER_ERROR',
509
+ message: 'Failed to create upload URL',
510
+ });
511
+ }
512
+ return {
513
+ documentId: doc.id,
514
+ uploadUrl: signed.signedUrl,
515
+ bucket: KB_BUCKET,
516
+ objectKey,
517
+ };
518
+ }
519
+ async adminProcessDocument(input) {
520
+ const kb = await this.loadKbAsAdmin(input.knowledgeBaseId);
521
+ return this.processDocumentForKb(kb, input.documentId);
522
+ }
523
+ async adminDeleteDocument(documentId) {
524
+ const doc = await this.db.knowledgeBaseDocument.findUnique({
525
+ where: { id: documentId },
526
+ });
527
+ if (!doc)
528
+ throw new TRPCError({ code: 'NOT_FOUND' });
529
+ if (doc.bucket && doc.objectKey) {
530
+ supabaseClient.storage
531
+ .from(doc.bucket)
532
+ .remove([doc.objectKey])
533
+ .catch((err) => {
534
+ this.logger.error('Failed to remove KB doc object', err);
535
+ });
536
+ }
537
+ await this.db.knowledgeBaseDocument.delete({ where: { id: documentId } });
538
+ return true;
539
+ }
540
+ async adminQuery(input) {
541
+ const kb = await this.loadKbAsAdmin(input.knowledgeBaseId);
542
+ return this.queryForKb(kb, input);
543
+ }
544
+ }