@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
@@ -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
+ }