@goscribe/server 1.3.4 → 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,705 @@
1
+ import type { PrismaClient } from '@prisma/client';
2
+ import { TRPCError } from '@trpc/server';
3
+ import { BaseService } from '../base.service.js';
4
+ import { workspaceAccessWhere } from '../../repositories/workspace.repository.js';
5
+ import { supabaseClient } from '../../lib/storage.js';
6
+ import {
7
+ DEFAULT_EMBEDDING_DIM,
8
+ DEFAULT_EMBEDDING_MODEL,
9
+ embedQuery,
10
+ embedTexts,
11
+ toVectorLiteral,
12
+ } from '../../lib/embeddings.js';
13
+ import { extractPdfText } from '../../lib/pdf.js';
14
+ import { chunkText } from '../../lib/chunking.js';
15
+ import { ai } from '../../lib/ai/index.js';
16
+ import { isWorkspaceManagedKb } from '../../lib/workspace-kb.js';
17
+ import { buildKbMetaFromName } from '../../lib/kb-meta.js';
18
+
19
+ const KB_BUCKET = process.env.SUPABASE_KB_BUCKET ?? 'media';
20
+ const MAX_PDF_BYTES = 50 * 1024 * 1024;
21
+
22
+ interface AdminCreateInput {
23
+ name: string;
24
+ description?: string;
25
+ isCurated?: boolean;
26
+ }
27
+
28
+ interface AdminUpdateInput {
29
+ id: string;
30
+ name?: string;
31
+ description?: string | null;
32
+ isCurated?: boolean;
33
+ }
34
+
35
+ interface RequestUploadUrlInput {
36
+ knowledgeBaseId: string;
37
+ filename: string;
38
+ contentType: string;
39
+ size: number;
40
+ }
41
+
42
+ interface ProcessDocumentInput {
43
+ knowledgeBaseId: string;
44
+ documentId: string;
45
+ }
46
+
47
+ interface QueryInput {
48
+ knowledgeBaseId: string;
49
+ query: string;
50
+ topK?: number;
51
+ generateAnswer?: boolean;
52
+ }
53
+
54
+ export class KnowledgeBaseService extends BaseService {
55
+ constructor(db: PrismaClient) {
56
+ super(db);
57
+ }
58
+
59
+ // ───────────────────────────────────────── access helpers
60
+
61
+ private async assertWorkspaceAccess(userId: string, workspaceId: string) {
62
+ const ws = await this.db.workspace.findFirst({
63
+ where: { id: workspaceId, ...workspaceAccessWhere(userId) },
64
+ select: { id: true },
65
+ });
66
+ if (!ws) {
67
+ throw new TRPCError({
68
+ code: 'NOT_FOUND',
69
+ message: 'Workspace not found or access denied',
70
+ });
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Loads a knowledge base only if it's reachable to the calling user, i.e.
76
+ * either curated (visible in catalog) or attached to one of their accessible
77
+ * workspaces.
78
+ */
79
+ private async loadKbForUser(userId: string, kbId: string) {
80
+ const kb = await this.db.knowledgeBase.findFirst({
81
+ where: {
82
+ id: kbId,
83
+ OR: [
84
+ { isCurated: true },
85
+ { workspaces: { some: { workspace: workspaceAccessWhere(userId) } } },
86
+ ],
87
+ },
88
+ });
89
+ if (!kb) {
90
+ throw new TRPCError({
91
+ code: 'NOT_FOUND',
92
+ message: 'Knowledge base not found',
93
+ });
94
+ }
95
+ return kb;
96
+ }
97
+
98
+ /** Admin-only: load by id without membership/curation checks. */
99
+ private async loadKbAsAdmin(kbId: string) {
100
+ const kb = await this.db.knowledgeBase.findUnique({ where: { id: kbId } });
101
+ if (!kb) {
102
+ throw new TRPCError({
103
+ code: 'NOT_FOUND',
104
+ message: 'Knowledge base not found',
105
+ });
106
+ }
107
+ return kb;
108
+ }
109
+
110
+ // ───────────────────────────────────────── user-facing reads
111
+
112
+ /**
113
+ * KBs attached to a given workspace (the user's "library" for that
114
+ * workspace).
115
+ */
116
+ async listForWorkspace(userId: string, workspaceId: string) {
117
+ await this.assertWorkspaceAccess(userId, workspaceId);
118
+ const rows = await this.db.workspaceKnowledgeBase.findMany({
119
+ where: { workspaceId },
120
+ orderBy: { createdAt: 'desc' },
121
+ include: {
122
+ knowledgeBase: {
123
+ include: { _count: { select: { documents: true } } },
124
+ },
125
+ },
126
+ });
127
+ return rows
128
+ .map((r) => ({
129
+ ...r.knowledgeBase,
130
+ attachmentId: r.id,
131
+ attachedAt: r.createdAt,
132
+ attachedById: r.addedById,
133
+ }))
134
+ .filter((kb) => !isWorkspaceManagedKb(kb));
135
+ }
136
+
137
+ /**
138
+ * Search the curated catalog. Optionally pass a workspaceId to also tag
139
+ * each result with whether the workspace already has it attached.
140
+ */
141
+ async searchCatalog(
142
+ userId: string,
143
+ input: { query?: string; workspaceId?: string; limit?: number },
144
+ ) {
145
+ const limit = Math.min(Math.max(input.limit ?? 50, 1), 100);
146
+ const q = (input.query ?? '').trim();
147
+
148
+ if (input.workspaceId) {
149
+ await this.assertWorkspaceAccess(userId, input.workspaceId);
150
+ }
151
+
152
+ const kbs = await this.db.knowledgeBase.findMany({
153
+ where: {
154
+ isCurated: true,
155
+ ...(q
156
+ ? {
157
+ OR: [
158
+ { name: { contains: q, mode: 'insensitive' as const } },
159
+ { description: { contains: q, mode: 'insensitive' as const } },
160
+ ],
161
+ }
162
+ : {}),
163
+ },
164
+ orderBy: { updatedAt: 'desc' },
165
+ take: limit,
166
+ include: { _count: { select: { documents: true } } },
167
+ });
168
+
169
+ if (!input.workspaceId) {
170
+ return kbs.map((kb) => ({ ...kb, attached: false }));
171
+ }
172
+
173
+ const attachments = await this.db.workspaceKnowledgeBase.findMany({
174
+ where: {
175
+ workspaceId: input.workspaceId,
176
+ knowledgeBaseId: { in: kbs.map((k) => k.id) },
177
+ },
178
+ select: { knowledgeBaseId: true },
179
+ });
180
+ const attachedSet = new Set(attachments.map((a) => a.knowledgeBaseId));
181
+ return kbs.map((kb) => ({ ...kb, attached: attachedSet.has(kb.id) }));
182
+ }
183
+
184
+ async get(userId: string, id: string) {
185
+ const kb = await this.loadKbForUser(userId, id);
186
+ const documents = await this.db.knowledgeBaseDocument.findMany({
187
+ where: { knowledgeBaseId: id },
188
+ orderBy: { createdAt: 'desc' },
189
+ });
190
+ return { ...kb, documents };
191
+ }
192
+
193
+ async listDocuments(userId: string, knowledgeBaseId: string) {
194
+ await this.loadKbForUser(userId, knowledgeBaseId);
195
+ return this.db.knowledgeBaseDocument.findMany({
196
+ where: { knowledgeBaseId },
197
+ orderBy: { createdAt: 'desc' },
198
+ });
199
+ }
200
+
201
+ // ───────────────────────────────────────── user-facing writes
202
+
203
+ /** Attach a curated KB to one of the user's workspaces. */
204
+ async addToWorkspace(
205
+ userId: string,
206
+ input: { workspaceId: string; knowledgeBaseId: string },
207
+ ) {
208
+ await this.assertWorkspaceAccess(userId, input.workspaceId);
209
+
210
+ const kb = await this.db.knowledgeBase.findUnique({
211
+ where: { id: input.knowledgeBaseId },
212
+ select: { id: true, isCurated: true },
213
+ });
214
+ if (!kb) throw new TRPCError({ code: 'NOT_FOUND' });
215
+ if (!kb.isCurated) {
216
+ throw new TRPCError({
217
+ code: 'FORBIDDEN',
218
+ message:
219
+ 'This knowledge base is private. Only an administrator can attach it to a workspace.',
220
+ });
221
+ }
222
+
223
+ const existing = await this.db.workspaceKnowledgeBase.findUnique({
224
+ where: {
225
+ workspaceId_knowledgeBaseId: {
226
+ workspaceId: input.workspaceId,
227
+ knowledgeBaseId: kb.id,
228
+ },
229
+ },
230
+ });
231
+ if (existing) return existing;
232
+
233
+ return this.db.workspaceKnowledgeBase.create({
234
+ data: {
235
+ workspaceId: input.workspaceId,
236
+ knowledgeBaseId: kb.id,
237
+ addedById: userId,
238
+ },
239
+ });
240
+ }
241
+
242
+ /** Detach a KB from one of the user's workspaces. */
243
+ async removeFromWorkspace(
244
+ userId: string,
245
+ input: { workspaceId: string; knowledgeBaseId: string },
246
+ ) {
247
+ await this.assertWorkspaceAccess(userId, input.workspaceId);
248
+
249
+ const kb = await this.db.knowledgeBase.findUnique({
250
+ where: { id: input.knowledgeBaseId },
251
+ select: { meta: true },
252
+ });
253
+ if (kb && isWorkspaceManagedKb(kb)) {
254
+ throw new TRPCError({
255
+ code: 'BAD_REQUEST',
256
+ message: 'The workspace knowledge base is managed automatically and cannot be removed.',
257
+ });
258
+ }
259
+
260
+ await this.db.workspaceKnowledgeBase.deleteMany({
261
+ where: {
262
+ workspaceId: input.workspaceId,
263
+ knowledgeBaseId: input.knowledgeBaseId,
264
+ },
265
+ });
266
+ return true;
267
+ }
268
+
269
+ // ───────────────────────────────────────── retrieval
270
+
271
+ async query(userId: string, input: QueryInput) {
272
+ const kb = await this.loadKbForUser(userId, input.knowledgeBaseId);
273
+ return this.queryForKb(kb, input);
274
+ }
275
+
276
+ private async queryForKb(
277
+ kb: { id: string; embeddingModel: string },
278
+ input: QueryInput,
279
+ ) {
280
+ const topK = Math.min(Math.max(input.topK ?? 5, 1), 20);
281
+
282
+ const queryVec = await embedQuery(input.query, { model: kb.embeddingModel });
283
+ const vecLiteral = toVectorLiteral(queryVec);
284
+
285
+ const rows = await this.db.$queryRawUnsafe<
286
+ Array<{
287
+ id: string;
288
+ documentId: string;
289
+ chunkIndex: number;
290
+ content: string;
291
+ distance: number;
292
+ documentName: string;
293
+ }>
294
+ >(
295
+ `SELECT c."id", c."documentId", c."chunkIndex", c."content",
296
+ (c."embedding" <=> $1::vector) AS "distance",
297
+ d."name" AS "documentName"
298
+ FROM "KnowledgeBaseChunk" c
299
+ JOIN "KnowledgeBaseDocument" d ON d."id" = c."documentId"
300
+ WHERE c."knowledgeBaseId" = $2
301
+ AND c."embedding" IS NOT NULL
302
+ ORDER BY c."embedding" <=> $1::vector
303
+ LIMIT ${topK}`,
304
+ vecLiteral,
305
+ kb.id,
306
+ );
307
+
308
+ const matches = rows.map((r) => ({
309
+ chunkId: r.id,
310
+ documentId: r.documentId,
311
+ documentName: r.documentName,
312
+ chunkIndex: r.chunkIndex,
313
+ content: r.content,
314
+ score: 1 - Number(r.distance),
315
+ }));
316
+
317
+ let answer: string | null = null;
318
+ if (input.generateAnswer && matches.length > 0) {
319
+ try {
320
+ const context = matches
321
+ .map(
322
+ (m, i) =>
323
+ `[${i + 1}] (${m.documentName}, chunk ${m.chunkIndex})\n${m.content}`,
324
+ )
325
+ .join('\n\n---\n\n');
326
+ const completion = await ai.llm.complete([
327
+ {
328
+ role: 'system',
329
+ content:
330
+ '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.',
331
+ },
332
+ {
333
+ role: 'user',
334
+ content: `Context:\n${context}\n\nQuestion: ${input.query}`,
335
+ },
336
+ ]);
337
+ answer = completion.choices?.[0]?.message?.content ?? null;
338
+ } catch (err) {
339
+ this.logger.error('KB answer generation failed', err);
340
+ }
341
+ }
342
+
343
+ return { matches, answer };
344
+ }
345
+
346
+ // ───────────────────────────────────────── document processing core
347
+
348
+ private async processDocumentForKb(
349
+ kb: { id: string; embeddingModel: string },
350
+ documentId: string,
351
+ ) {
352
+ const doc = await this.db.knowledgeBaseDocument.findFirst({
353
+ where: { id: documentId, knowledgeBaseId: kb.id },
354
+ });
355
+ if (!doc) throw new TRPCError({ code: 'NOT_FOUND' });
356
+ if (!doc.bucket || !doc.objectKey) {
357
+ throw new TRPCError({
358
+ code: 'BAD_REQUEST',
359
+ message: 'Document is missing storage location',
360
+ });
361
+ }
362
+
363
+ if (doc.status === 'PROCESSING' || doc.status === 'READY') {
364
+ return doc;
365
+ }
366
+
367
+ await this.db.knowledgeBaseDocument.update({
368
+ where: { id: doc.id },
369
+ data: { status: 'PROCESSING', errorMessage: null },
370
+ });
371
+ await this.db.knowledgeBase.update({
372
+ where: { id: kb.id },
373
+ data: { status: 'INDEXING' },
374
+ });
375
+
376
+ try {
377
+ const { data, error } = await supabaseClient.storage
378
+ .from(doc.bucket)
379
+ .download(doc.objectKey);
380
+
381
+ if (error || !data) {
382
+ throw new Error(error?.message ?? 'Failed to download document');
383
+ }
384
+
385
+ const buf = Buffer.from(await data.arrayBuffer());
386
+ let text: string;
387
+ let numPages: number | null = null;
388
+
389
+ if (doc.mimeType === 'application/pdf' || doc.name.toLowerCase().endsWith('.pdf')) {
390
+ const parsed = await extractPdfText(buf);
391
+ text = parsed.text;
392
+ numPages = parsed.numPages;
393
+ } else {
394
+ text = buf.toString('utf8');
395
+ }
396
+
397
+
398
+ if (!text.trim()) {
399
+ throw new Error('No text could be extracted from the document');
400
+ }
401
+
402
+ const chunks = chunkText(text, { targetTokens: 500, overlapTokens: 60 });
403
+
404
+ if (chunks.length === 0) {
405
+ throw new Error('Document produced no chunks');
406
+ }
407
+ console.log('chunks', chunks);
408
+ const embeddings = await embedTexts(
409
+ chunks.map((c) => c.content),
410
+ { model: kb.embeddingModel },
411
+ );
412
+
413
+ console.log('embeddings', embeddings);
414
+
415
+ await this.db.knowledgeBaseChunk.deleteMany({
416
+ where: { documentId: doc.id },
417
+ });
418
+
419
+ for (let i = 0; i < chunks.length; i += 1) {
420
+ const chunk = chunks[i];
421
+ const vec = toVectorLiteral(embeddings[i] ?? []);
422
+ await this.db.$executeRawUnsafe(
423
+ `INSERT INTO "KnowledgeBaseChunk"
424
+ ("id", "documentId", "knowledgeBaseId", "chunkIndex",
425
+ "pageNumber", "content", "tokenCount", "embedding", "createdAt")
426
+ VALUES (
427
+ gen_random_uuid()::text,
428
+ $1, $2, $3, $4, $5, $6, $7::vector, NOW()
429
+ )`,
430
+ doc.id,
431
+ kb.id,
432
+ chunk.index,
433
+ null,
434
+ chunk.content,
435
+ chunk.approxTokens,
436
+ vec,
437
+ );
438
+ }
439
+
440
+ await this.db.knowledgeBaseDocument.update({
441
+ where: { id: doc.id },
442
+ data: {
443
+ status: 'READY',
444
+ numChunks: chunks.length,
445
+ numPages: numPages ?? undefined,
446
+ },
447
+ });
448
+
449
+
450
+ const stillProcessing = await this.db.knowledgeBaseDocument.count({
451
+ where: { knowledgeBaseId: kb.id, status: 'PROCESSING' },
452
+ });
453
+ if (stillProcessing === 0) {
454
+ await this.db.knowledgeBase.update({
455
+ where: { id: kb.id },
456
+ data: { status: 'READY' },
457
+ });
458
+ }
459
+
460
+ return this.db.knowledgeBaseDocument.findUnique({
461
+ where: { id: doc.id },
462
+ });
463
+ } catch (err: any) {
464
+ this.logger.error(`KB processing failed for doc ${doc.id}`, err);
465
+ await this.db.knowledgeBaseDocument.update({
466
+ where: { id: doc.id },
467
+ data: {
468
+ status: 'FAILED',
469
+ errorMessage: err?.message?.slice(0, 500) ?? 'Unknown error',
470
+ },
471
+ });
472
+ await this.db.knowledgeBase.update({
473
+ where: { id: kb.id },
474
+ data: { status: 'ERROR' },
475
+ });
476
+ throw new TRPCError({
477
+ code: 'INTERNAL_SERVER_ERROR',
478
+ message: err?.message ?? 'Failed to process document',
479
+ });
480
+ }
481
+ }
482
+
483
+ // ───────────────────────────────────────── admin entrypoints
484
+
485
+ async adminListAll() {
486
+ return this.db.knowledgeBase.findMany({
487
+ orderBy: { updatedAt: 'desc' },
488
+ include: {
489
+ _count: {
490
+ select: { documents: true, workspaces: true },
491
+ },
492
+ },
493
+ });
494
+ }
495
+
496
+ async adminGet(id: string) {
497
+ const kb = await this.loadKbAsAdmin(id);
498
+ const [documents, workspaces] = await Promise.all([
499
+ this.db.knowledgeBaseDocument.findMany({
500
+ where: { knowledgeBaseId: id },
501
+ orderBy: { createdAt: 'desc' },
502
+ }),
503
+ this.db.workspaceKnowledgeBase.findMany({
504
+ where: { knowledgeBaseId: id },
505
+ orderBy: { createdAt: 'desc' },
506
+ include: {
507
+ workspace: { select: { id: true, title: true, icon: true } },
508
+ },
509
+ }),
510
+ ]);
511
+ return { ...kb, documents, workspaces };
512
+ }
513
+
514
+ async adminListDocuments(knowledgeBaseId: string) {
515
+ await this.loadKbAsAdmin(knowledgeBaseId);
516
+ return this.db.knowledgeBaseDocument.findMany({
517
+ where: { knowledgeBaseId },
518
+ orderBy: { createdAt: 'desc' },
519
+ });
520
+ }
521
+
522
+ async adminCreate(actorUserId: string, input: AdminCreateInput) {
523
+ const meta = buildKbMetaFromName(input.name);
524
+ return this.db.knowledgeBase.create({
525
+ data: {
526
+ createdById: actorUserId,
527
+ name: input.name,
528
+ description: input.description,
529
+ isCurated: input.isCurated ?? false,
530
+ embeddingModel: DEFAULT_EMBEDDING_MODEL,
531
+ embeddingDim: DEFAULT_EMBEDDING_DIM,
532
+ meta,
533
+ },
534
+ });
535
+ }
536
+
537
+ async adminUpdate(input: AdminUpdateInput) {
538
+ const existing = await this.loadKbAsAdmin(input.id);
539
+ const nextName = input.name ?? existing.name;
540
+ const meta =
541
+ input.name !== undefined
542
+ ? buildKbMetaFromName(nextName, (existing.meta as Record<string, unknown> | null) ?? null)
543
+ : undefined;
544
+
545
+ return this.db.knowledgeBase.update({
546
+ where: { id: input.id },
547
+ data: {
548
+ name: input.name ?? undefined,
549
+ description:
550
+ input.description === undefined ? undefined : input.description,
551
+ isCurated: input.isCurated ?? undefined,
552
+ ...(meta !== undefined ? { meta } : {}),
553
+ },
554
+ });
555
+ }
556
+
557
+ async adminDelete(id: string) {
558
+ const kb = await this.loadKbAsAdmin(id);
559
+ const docs = await this.db.knowledgeBaseDocument.findMany({
560
+ where: { knowledgeBaseId: kb.id },
561
+ select: { bucket: true, objectKey: true },
562
+ });
563
+ for (const d of docs) {
564
+ if (d.bucket && d.objectKey) {
565
+ supabaseClient.storage
566
+ .from(d.bucket)
567
+ .remove([d.objectKey])
568
+ .catch((err: unknown) => {
569
+ this.logger.error(
570
+ `Failed to remove KB doc ${d.objectKey} from ${d.bucket}`,
571
+ err,
572
+ );
573
+ });
574
+ }
575
+ }
576
+ await this.db.knowledgeBase.delete({ where: { id: kb.id } });
577
+ return true;
578
+ }
579
+
580
+ /** Admin-only: attach a KB to a workspace (curated or not). */
581
+ async adminAttachToWorkspace(
582
+ actorUserId: string,
583
+ input: { workspaceId: string; knowledgeBaseId: string },
584
+ ) {
585
+ const [ws, kb] = await Promise.all([
586
+ this.db.workspace.findUnique({
587
+ where: { id: input.workspaceId },
588
+ select: { id: true },
589
+ }),
590
+ this.db.knowledgeBase.findUnique({
591
+ where: { id: input.knowledgeBaseId },
592
+ select: { id: true },
593
+ }),
594
+ ]);
595
+ if (!ws) throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' });
596
+ if (!kb) throw new TRPCError({ code: 'NOT_FOUND', message: 'Knowledge base not found' });
597
+
598
+ const existing = await this.db.workspaceKnowledgeBase.findUnique({
599
+ where: {
600
+ workspaceId_knowledgeBaseId: {
601
+ workspaceId: ws.id,
602
+ knowledgeBaseId: kb.id,
603
+ },
604
+ },
605
+ });
606
+ if (existing) return existing;
607
+
608
+ return this.db.workspaceKnowledgeBase.create({
609
+ data: {
610
+ workspaceId: ws.id,
611
+ knowledgeBaseId: kb.id,
612
+ addedById: actorUserId,
613
+ },
614
+ });
615
+ }
616
+
617
+ async adminDetachFromWorkspace(input: {
618
+ workspaceId: string;
619
+ knowledgeBaseId: string;
620
+ }) {
621
+ await this.db.workspaceKnowledgeBase.deleteMany({
622
+ where: {
623
+ workspaceId: input.workspaceId,
624
+ knowledgeBaseId: input.knowledgeBaseId,
625
+ },
626
+ });
627
+ return true;
628
+ }
629
+
630
+ async adminRequestUploadUrl(input: RequestUploadUrlInput) {
631
+ if (input.size > MAX_PDF_BYTES) {
632
+ throw new TRPCError({
633
+ code: 'PAYLOAD_TOO_LARGE',
634
+ message: `Document exceeds the ${MAX_PDF_BYTES / (1024 * 1024)}MB limit`,
635
+ });
636
+ }
637
+ const kb = await this.loadKbAsAdmin(input.knowledgeBaseId);
638
+
639
+ const objectKey = `kb/${kb.id}/${Date.now()}-${input.filename.replace(
640
+ /[^a-zA-Z0-9._-]+/g,
641
+ '_',
642
+ )}`;
643
+
644
+ const doc = await this.db.knowledgeBaseDocument.create({
645
+ data: {
646
+ knowledgeBaseId: kb.id,
647
+ name: input.filename,
648
+ mimeType: input.contentType,
649
+ size: input.size,
650
+ bucket: KB_BUCKET,
651
+ objectKey,
652
+ status: 'PENDING',
653
+ },
654
+ });
655
+
656
+ const { data: signed, error } = await supabaseClient.storage
657
+ .from(KB_BUCKET)
658
+ .createSignedUploadUrl(objectKey);
659
+
660
+ if (error || !signed) {
661
+ this.logger.error('KB upload URL error', error);
662
+ await this.db.knowledgeBaseDocument.delete({ where: { id: doc.id } });
663
+ throw new TRPCError({
664
+ code: 'INTERNAL_SERVER_ERROR',
665
+ message: 'Failed to create upload URL',
666
+ });
667
+ }
668
+
669
+ return {
670
+ documentId: doc.id,
671
+ uploadUrl: signed.signedUrl,
672
+ bucket: KB_BUCKET,
673
+ objectKey,
674
+ };
675
+ }
676
+
677
+ async adminProcessDocument(input: ProcessDocumentInput) {
678
+ const kb = await this.loadKbAsAdmin(input.knowledgeBaseId);
679
+ return this.processDocumentForKb(kb, input.documentId);
680
+ }
681
+
682
+ async adminDeleteDocument(documentId: string) {
683
+ const doc = await this.db.knowledgeBaseDocument.findUnique({
684
+ where: { id: documentId },
685
+ });
686
+ if (!doc) throw new TRPCError({ code: 'NOT_FOUND' });
687
+
688
+ if (doc.bucket && doc.objectKey) {
689
+ supabaseClient.storage
690
+ .from(doc.bucket)
691
+ .remove([doc.objectKey])
692
+ .catch((err: unknown) => {
693
+ this.logger.error('Failed to remove KB doc object', err);
694
+ });
695
+ }
696
+
697
+ await this.db.knowledgeBaseDocument.delete({ where: { id: documentId } });
698
+ return true;
699
+ }
700
+
701
+ async adminQuery(input: QueryInput) {
702
+ const kb = await this.loadKbAsAdmin(input.knowledgeBaseId);
703
+ return this.queryForKb(kb, input);
704
+ }
705
+ }