@goscribe/server 1.3.4 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (383) hide show
  1. package/.env.example +12 -0
  2. package/.vscode/settings.json +3 -0
  3. package/REFACTOR_NOTES.md +60 -0
  4. package/TESTING_PROMPT.md +225 -0
  5. package/dist/context.d.ts +14 -1
  6. package/dist/context.js +23 -2
  7. package/dist/controllers/admin.controller.d.ts +715 -0
  8. package/dist/controllers/admin.controller.js +9 -0
  9. package/dist/controllers/annotations.controller.d.ts +439 -0
  10. package/dist/controllers/annotations.controller.js +9 -0
  11. package/dist/controllers/app-router.controller.d.ts +3011 -0
  12. package/dist/controllers/app-router.controller.js +38 -0
  13. package/dist/controllers/app-router.controller.test.d.ts +1 -0
  14. package/dist/controllers/app-router.controller.test.js +36 -0
  15. package/dist/controllers/auth.controller.d.ts +323 -0
  16. package/dist/controllers/auth.controller.js +9 -0
  17. package/dist/controllers/base.controller.d.ts +4 -0
  18. package/dist/controllers/base.controller.js +5 -0
  19. package/dist/controllers/chat.controller.d.ts +341 -0
  20. package/dist/controllers/chat.controller.js +9 -0
  21. package/dist/controllers/copilot.controller.d.ts +397 -0
  22. package/dist/controllers/copilot.controller.js +9 -0
  23. package/dist/controllers/flashcards.controller.d.ts +651 -0
  24. package/dist/controllers/flashcards.controller.js +9 -0
  25. package/dist/controllers/members.controller.d.ts +339 -0
  26. package/dist/controllers/members.controller.js +9 -0
  27. package/dist/controllers/notifications.controller.d.ts +199 -0
  28. package/dist/controllers/notifications.controller.js +9 -0
  29. package/dist/controllers/payment.controller.d.ts +181 -0
  30. package/dist/controllers/payment.controller.js +9 -0
  31. package/dist/controllers/podcast.controller.d.ts +575 -0
  32. package/dist/controllers/podcast.controller.js +9 -0
  33. package/dist/controllers/router-module.controller.d.ts +5 -0
  34. package/dist/controllers/router-module.controller.js +6 -0
  35. package/dist/controllers/studyguide.controller.d.ts +73 -0
  36. package/dist/controllers/studyguide.controller.js +9 -0
  37. package/dist/controllers/worksheets.controller.d.ts +829 -0
  38. package/dist/controllers/worksheets.controller.js +9 -0
  39. package/dist/controllers/workspace.controller.d.ts +1207 -0
  40. package/dist/controllers/workspace.controller.js +9 -0
  41. package/dist/lib/activity_human_description.test.js +16 -15
  42. package/dist/lib/activity_log_service.test.js +28 -23
  43. package/dist/lib/ai/config.d.ts +20 -0
  44. package/dist/lib/ai/config.js +31 -0
  45. package/dist/lib/ai/embedding-client.d.ts +8 -0
  46. package/dist/lib/ai/embedding-client.js +30 -0
  47. package/dist/lib/ai/index.d.ts +48 -0
  48. package/dist/lib/ai/index.js +29 -0
  49. package/dist/lib/ai/inference-backend/client.d.ts +28 -0
  50. package/dist/lib/ai/inference-backend/client.js +301 -0
  51. package/dist/lib/ai/inference-backend/mocks.d.ts +12 -0
  52. package/dist/lib/ai/inference-backend/mocks.js +133 -0
  53. package/dist/lib/ai/inference-backend/types.d.ts +44 -0
  54. package/dist/lib/ai/inference-backend/types.js +1 -0
  55. package/dist/lib/ai/json-parse.d.ts +2 -0
  56. package/dist/lib/ai/json-parse.js +34 -0
  57. package/dist/lib/ai/llm-client.d.ts +7 -0
  58. package/dist/lib/ai/llm-client.js +36 -0
  59. package/dist/lib/ai/mock.d.ts +2 -0
  60. package/dist/lib/ai/mock.js +10 -0
  61. package/dist/lib/ai/types.d.ts +9 -0
  62. package/dist/lib/ai/types.js +1 -0
  63. package/dist/lib/chunking.d.ts +19 -0
  64. package/dist/lib/chunking.js +47 -0
  65. package/dist/lib/curated-kb-seed.d.ts +12 -0
  66. package/dist/lib/curated-kb-seed.js +155 -0
  67. package/dist/lib/email.js +67 -108
  68. package/dist/lib/embeddings.d.ts +2 -0
  69. package/dist/lib/embeddings.js +1 -0
  70. package/dist/lib/ensure-curated-kb-catalog.d.ts +6 -0
  71. package/dist/lib/ensure-curated-kb-catalog.js +53 -0
  72. package/dist/lib/env.d.ts +1 -5
  73. package/dist/lib/env.js +2 -7
  74. package/dist/lib/inference.d.ts +1 -8
  75. package/dist/lib/inference.js +1 -19
  76. package/dist/lib/kb-meta.d.ts +8 -0
  77. package/dist/lib/kb-meta.js +77 -0
  78. package/dist/lib/note-text.d.ts +1 -0
  79. package/dist/lib/note-text.js +47 -0
  80. package/dist/lib/notification-service.test.js +37 -36
  81. package/dist/lib/pdf.d.ts +11 -0
  82. package/dist/lib/pdf.js +11 -0
  83. package/dist/lib/usage_service.d.ts +2 -1
  84. package/dist/lib/usage_service.js +30 -12
  85. package/dist/lib/worksheet-generation.js +4 -4
  86. package/dist/lib/worksheet-generation.test.js +32 -17
  87. package/dist/lib/workspace-kb.d.ts +5 -0
  88. package/dist/lib/workspace-kb.js +7 -0
  89. package/dist/models/controller-context.model.d.ts +8 -0
  90. package/dist/models/controller-context.model.js +1 -0
  91. package/dist/repositories/artifact.repository.d.ts +60 -0
  92. package/dist/repositories/artifact.repository.js +40 -0
  93. package/dist/repositories/base.repository.d.ts +14 -0
  94. package/dist/repositories/base.repository.js +14 -0
  95. package/dist/repositories/invitation.repository.d.ts +94 -0
  96. package/dist/repositories/invitation.repository.js +44 -0
  97. package/dist/repositories/notification.repository.d.ts +72 -0
  98. package/dist/repositories/notification.repository.js +44 -0
  99. package/dist/repositories/router-module.repository.d.ts +10 -0
  100. package/dist/repositories/router-module.repository.js +14 -0
  101. package/dist/repositories/user.repository.d.ts +74 -0
  102. package/dist/repositories/user.repository.js +37 -0
  103. package/dist/repositories/workspace-member.repository.d.ts +31 -0
  104. package/dist/repositories/workspace-member.repository.js +31 -0
  105. package/dist/repositories/workspace.repository.d.ts +97 -0
  106. package/dist/repositories/workspace.repository.js +79 -0
  107. package/dist/routers/_app.d.ts +566 -111
  108. package/dist/routers/_app.js +4 -0
  109. package/dist/routers/admin.d.ts +0 -4
  110. package/dist/routers/admin.js +21 -549
  111. package/dist/routers/annotations.js +12 -170
  112. package/dist/routers/artifactVersions.d.ts +65 -0
  113. package/dist/routers/artifactVersions.js +14 -0
  114. package/dist/routers/auth.d.ts +0 -6
  115. package/dist/routers/auth.js +37 -422
  116. package/dist/routers/chat.js +15 -229
  117. package/dist/routers/copilot.d.ts +14 -13
  118. package/dist/routers/copilot.js +13 -532
  119. package/dist/routers/flashcards.d.ts +17 -6
  120. package/dist/routers/flashcards.js +23 -349
  121. package/dist/routers/knowledgeBase.d.ts +421 -0
  122. package/dist/routers/knowledgeBase.js +118 -0
  123. package/dist/routers/members.d.ts +0 -41
  124. package/dist/routers/members.js +22 -710
  125. package/dist/routers/notes.d.ts +94 -0
  126. package/dist/routers/notes.js +37 -0
  127. package/dist/routers/notifications.js +7 -109
  128. package/dist/routers/payment.d.ts +2 -12
  129. package/dist/routers/payment.js +11 -393
  130. package/dist/routers/podcast.d.ts +1 -1
  131. package/dist/routers/podcast.js +11 -784
  132. package/dist/routers/studyguide.js +3 -129
  133. package/dist/routers/worksheets.d.ts +29 -14
  134. package/dist/routers/worksheets.js +49 -628
  135. package/dist/routers/workspace.d.ts +27 -71
  136. package/dist/routers/workspace.js +29 -922
  137. package/dist/scripts/purge-deleted-users.js +2 -2
  138. package/dist/server.js +10 -3
  139. package/dist/services/activity/activity-human-description.service.d.ts +13 -0
  140. package/dist/services/activity/activity-human-description.service.js +221 -0
  141. package/dist/services/activity/activity-human-description.service.test.d.ts +1 -0
  142. package/dist/services/activity/activity-human-description.service.test.js +16 -0
  143. package/dist/services/activity/activity-log.service.d.ts +87 -0
  144. package/dist/services/activity/activity-log.service.js +276 -0
  145. package/dist/services/activity/activity-log.service.test.d.ts +1 -0
  146. package/dist/services/activity/activity-log.service.test.js +27 -0
  147. package/dist/services/activity-human-description.service.d.ts +13 -0
  148. package/dist/services/activity-human-description.service.js +221 -0
  149. package/dist/services/activity-human-description.service.test.d.ts +1 -0
  150. package/dist/services/activity-human-description.service.test.js +16 -0
  151. package/dist/services/activity-log.service.d.ts +87 -0
  152. package/dist/services/activity-log.service.js +276 -0
  153. package/dist/services/activity-log.service.test.d.ts +1 -0
  154. package/dist/services/activity-log.service.test.js +27 -0
  155. package/dist/services/admin/admin.service.d.ts +270 -0
  156. package/dist/services/admin/admin.service.js +476 -0
  157. package/dist/services/admin.service.d.ts +270 -0
  158. package/dist/services/admin.service.js +476 -0
  159. package/dist/services/ai/ai-session.service.d.ts +5 -0
  160. package/dist/services/ai/ai-session.service.js +4 -0
  161. package/dist/services/ai-session.service.d.ts +60 -0
  162. package/dist/services/ai-session.service.js +561 -0
  163. package/dist/services/annotation.service.d.ts +177 -0
  164. package/dist/services/annotation.service.js +154 -0
  165. package/dist/services/artifact-notification.service.d.ts +14 -0
  166. package/dist/services/artifact-notification.service.js +20 -0
  167. package/dist/services/artifact-version.service.d.ts +38 -0
  168. package/dist/services/artifact-version.service.js +129 -0
  169. package/dist/services/artifacts/annotation.service.d.ts +177 -0
  170. package/dist/services/artifacts/annotation.service.js +154 -0
  171. package/dist/services/artifacts/artifact-version.service.d.ts +38 -0
  172. package/dist/services/artifacts/artifact-version.service.js +129 -0
  173. package/dist/services/artifacts/chat.service.d.ts +127 -0
  174. package/dist/services/artifacts/chat.service.js +182 -0
  175. package/dist/services/artifacts/study-guide.service.d.ts +18 -0
  176. package/dist/services/artifacts/study-guide.service.js +65 -0
  177. package/dist/services/auth/auth.service.d.ts +94 -0
  178. package/dist/services/auth/auth.service.js +368 -0
  179. package/dist/services/auth.service.d.ts +94 -0
  180. package/dist/services/auth.service.js +368 -0
  181. package/dist/services/base.service.d.ts +14 -0
  182. package/dist/services/base.service.js +14 -0
  183. package/dist/services/billing/payment.service.d.ts +44 -0
  184. package/dist/services/billing/payment.service.js +365 -0
  185. package/dist/services/billing/subscription.service.d.ts +37 -0
  186. package/dist/services/billing/subscription.service.js +654 -0
  187. package/dist/services/billing/usage.service.d.ts +47 -0
  188. package/dist/services/billing/usage.service.js +149 -0
  189. package/dist/services/chat.service.d.ts +127 -0
  190. package/dist/services/chat.service.js +182 -0
  191. package/dist/services/content/copilot.service.d.ts +113 -0
  192. package/dist/services/content/copilot.service.js +439 -0
  193. package/dist/services/content/flashcard-progress.service.d.ts +159 -0
  194. package/dist/services/content/flashcard-progress.service.js +432 -0
  195. package/dist/services/content/flashcard.service.d.ts +184 -0
  196. package/dist/services/content/flashcard.service.js +339 -0
  197. package/dist/services/content/media-analysis.service.d.ts +23 -0
  198. package/dist/services/content/media-analysis.service.js +404 -0
  199. package/dist/services/content/podcast.service.d.ts +267 -0
  200. package/dist/services/content/podcast.service.js +653 -0
  201. package/dist/services/content/worksheet-content.service.d.ts +37 -0
  202. package/dist/services/content/worksheet-content.service.js +84 -0
  203. package/dist/services/content/worksheet-content.service.test.d.ts +1 -0
  204. package/dist/services/content/worksheet-content.service.test.js +69 -0
  205. package/dist/services/content/worksheet-generation.service.d.ts +91 -0
  206. package/dist/services/content/worksheet-generation.service.js +95 -0
  207. package/dist/services/content/worksheet-generation.service.test.d.ts +1 -0
  208. package/dist/services/content/worksheet-generation.service.test.js +20 -0
  209. package/dist/services/content/worksheet.service.d.ts +347 -0
  210. package/dist/services/content/worksheet.service.js +599 -0
  211. package/dist/services/copilot.service.d.ts +116 -0
  212. package/dist/services/copilot.service.js +447 -0
  213. package/dist/services/flashcard-progress.service.d.ts +2 -2
  214. package/dist/services/flashcard-progress.service.js +3 -2
  215. package/dist/services/flashcard.service.d.ts +140 -0
  216. package/dist/services/flashcard.service.js +325 -0
  217. package/dist/services/invitation.service.d.ts +66 -0
  218. package/dist/services/invitation.service.js +348 -0
  219. package/dist/services/knowledge/knowledge-base.service.d.ts +316 -0
  220. package/dist/services/knowledge/knowledge-base.service.js +544 -0
  221. package/dist/services/knowledge-base.service.d.ts +316 -0
  222. package/dist/services/knowledge-base.service.js +536 -0
  223. package/dist/services/media-analysis.service.d.ts +23 -0
  224. package/dist/services/media-analysis.service.js +384 -0
  225. package/dist/services/member.service.d.ts +36 -0
  226. package/dist/services/member.service.js +193 -0
  227. package/dist/services/members/invitation.service.d.ts +66 -0
  228. package/dist/services/members/invitation.service.js +348 -0
  229. package/dist/services/members/member.service.d.ts +36 -0
  230. package/dist/services/members/member.service.js +193 -0
  231. package/dist/services/note.service.d.ts +55 -0
  232. package/dist/services/note.service.js +111 -0
  233. package/dist/services/notification.service.d.ts +214 -0
  234. package/dist/services/notification.service.js +550 -0
  235. package/dist/services/notification.service.test.d.ts +1 -0
  236. package/dist/services/notification.service.test.js +87 -0
  237. package/dist/services/notifications/notification.service.d.ts +214 -0
  238. package/dist/services/notifications/notification.service.js +550 -0
  239. package/dist/services/notifications/notification.service.test.d.ts +1 -0
  240. package/dist/services/notifications/notification.service.test.js +87 -0
  241. package/dist/services/payment.service.d.ts +55 -0
  242. package/dist/services/payment.service.js +368 -0
  243. package/dist/services/podcast.service.d.ts +267 -0
  244. package/dist/services/podcast.service.js +654 -0
  245. package/dist/services/router-module.service.d.ts +7 -0
  246. package/dist/services/router-module.service.js +10 -0
  247. package/dist/services/study-guide.service.d.ts +18 -0
  248. package/dist/services/study-guide.service.js +65 -0
  249. package/dist/services/subscription.service.d.ts +37 -0
  250. package/dist/services/subscription.service.js +654 -0
  251. package/dist/services/usage-limit-policy.service.d.ts +12 -0
  252. package/dist/services/usage-limit-policy.service.js +22 -0
  253. package/dist/services/usage-limit-policy.service.test.d.ts +1 -0
  254. package/dist/services/usage-limit-policy.service.test.js +46 -0
  255. package/dist/services/usage.service.d.ts +27 -0
  256. package/dist/services/usage.service.js +77 -0
  257. package/dist/services/worksheet-content.service.d.ts +42 -0
  258. package/dist/services/worksheet-content.service.js +84 -0
  259. package/dist/services/worksheet-content.service.test.d.ts +1 -0
  260. package/dist/services/worksheet-content.service.test.js +69 -0
  261. package/dist/services/worksheet-generation.service.d.ts +91 -0
  262. package/dist/services/worksheet-generation.service.js +95 -0
  263. package/dist/services/worksheet-generation.service.test.d.ts +1 -0
  264. package/dist/services/worksheet-generation.service.test.js +20 -0
  265. package/dist/services/worksheet.service.d.ts +385 -0
  266. package/dist/services/worksheet.service.js +596 -0
  267. package/dist/services/workspace/workspace-analytics.service.d.ts +24 -0
  268. package/dist/services/workspace/workspace-analytics.service.js +95 -0
  269. package/dist/services/workspace/workspace-kb.service.d.ts +40 -0
  270. package/dist/services/workspace/workspace-kb.service.js +184 -0
  271. package/dist/services/workspace/workspace.service.d.ts +263 -0
  272. package/dist/services/workspace/workspace.service.js +401 -0
  273. package/dist/services/workspace-analytics.service.d.ts +24 -0
  274. package/dist/services/workspace-analytics.service.js +95 -0
  275. package/dist/services/workspace-kb.service.d.ts +40 -0
  276. package/dist/services/workspace-kb.service.js +184 -0
  277. package/dist/services/workspace-progress.service.d.ts +27 -0
  278. package/dist/services/workspace-progress.service.js +56 -0
  279. package/dist/services/workspace-progress.service.test.d.ts +1 -0
  280. package/dist/services/workspace-progress.service.test.js +49 -0
  281. package/dist/services/workspace.service.d.ts +307 -0
  282. package/dist/services/workspace.service.js +390 -0
  283. package/dist/trpc.d.ts +12 -4
  284. package/dist/trpc.js +7 -13
  285. package/package.json +5 -6
  286. package/prisma/migrations/20260509000001_add_knowledge_base/migration.sql +99 -0
  287. package/prisma/migrations/20260509000002_curate_knowledge_base/migration.sql +52 -0
  288. package/prisma/migrations/20260522000000_add_notes/migration.sql +27 -0
  289. package/prisma/migrations/20260524000000_remove_notes/migration.sql +3 -0
  290. package/prisma/schema.prisma +150 -48
  291. package/prisma/seed.mjs +67 -0
  292. package/scripts/debug/README.md +4 -0
  293. package/src/README.md +63 -0
  294. package/src/context.ts +33 -3
  295. package/src/lib/ai/config.ts +34 -0
  296. package/src/lib/ai/embedding-client.ts +47 -0
  297. package/src/lib/ai/index.ts +65 -0
  298. package/src/lib/ai/inference-backend/client.ts +479 -0
  299. package/src/lib/ai/inference-backend/mocks.ts +171 -0
  300. package/src/lib/ai/inference-backend/types.ts +50 -0
  301. package/src/lib/ai/json-parse.ts +35 -0
  302. package/src/lib/ai/llm-client.ts +54 -0
  303. package/src/lib/ai/mock.ts +12 -0
  304. package/src/lib/ai/types.ts +11 -0
  305. package/src/lib/chunking.ts +81 -0
  306. package/src/lib/curated-kb-seed.ts +164 -0
  307. package/src/lib/email.ts +77 -115
  308. package/src/lib/embeddings.ts +9 -0
  309. package/src/lib/ensure-curated-kb-catalog.ts +60 -0
  310. package/src/lib/env.ts +2 -7
  311. package/src/lib/inference.ts +1 -21
  312. package/src/lib/kb-meta.ts +81 -0
  313. package/src/lib/pdf.ts +23 -0
  314. package/src/lib/workspace-kb.ts +7 -0
  315. package/src/repositories/artifact.repository.ts +55 -0
  316. package/src/repositories/base.repository.ts +19 -0
  317. package/src/repositories/invitation.repository.ts +53 -0
  318. package/src/repositories/notification.repository.ts +53 -0
  319. package/src/repositories/user.repository.ts +44 -0
  320. package/src/repositories/workspace-member.repository.ts +38 -0
  321. package/src/repositories/workspace.repository.ts +89 -0
  322. package/src/routers/_app.ts +4 -0
  323. package/src/routers/admin.ts +124 -692
  324. package/src/routers/annotations.ts +25 -203
  325. package/src/routers/artifactVersions.ts +32 -0
  326. package/src/routers/auth.ts +82 -520
  327. package/src/routers/chat.ts +42 -245
  328. package/src/routers/copilot.ts +41 -666
  329. package/src/routers/flashcards.ts +108 -404
  330. package/src/routers/knowledgeBase.ts +216 -0
  331. package/src/routers/members.ts +60 -782
  332. package/src/routers/notifications.ts +15 -117
  333. package/src/routers/payment.ts +37 -446
  334. package/src/routers/podcast.ts +36 -898
  335. package/src/routers/studyguide.ts +5 -144
  336. package/src/routers/worksheets.ts +171 -735
  337. package/src/routers/workspace.ts +141 -1108
  338. package/src/scripts/purge-deleted-users.ts +2 -2
  339. package/src/server.ts +10 -3
  340. package/src/{lib/activity_human_description.test.ts → services/activity/activity-human-description.service.test.ts} +1 -1
  341. package/src/{lib/activity_log_service.test.ts → services/activity/activity-log.service.test.ts} +1 -1
  342. package/src/{lib/activity_log_service.ts → services/activity/activity-log.service.ts} +2 -2
  343. package/src/services/admin/admin.service.ts +612 -0
  344. package/src/services/ai/ai-session.service.ts +5 -0
  345. package/src/services/artifacts/annotation.service.ts +189 -0
  346. package/src/services/artifacts/artifact-version.service.ts +151 -0
  347. package/src/services/artifacts/chat.service.ts +197 -0
  348. package/src/services/artifacts/study-guide.service.ts +72 -0
  349. package/src/services/auth/auth.service.ts +473 -0
  350. package/src/services/base.service.ts +19 -0
  351. package/src/services/billing/payment.service.ts +433 -0
  352. package/src/{lib/subscription_service.ts → services/billing/subscription.service.ts} +5 -5
  353. package/src/services/billing/usage.service.ts +207 -0
  354. package/src/services/content/copilot.service.ts +587 -0
  355. package/src/services/{flashcard-progress.service.ts → content/flashcard-progress.service.ts} +18 -12
  356. package/src/services/content/flashcard.service.ts +417 -0
  357. package/src/services/content/media-analysis.service.ts +561 -0
  358. package/src/services/content/podcast.service.ts +777 -0
  359. package/src/services/content/worksheet-content.service.test.ts +83 -0
  360. package/src/services/content/worksheet-content.service.ts +117 -0
  361. package/src/{lib/worksheet-generation.test.ts → services/content/worksheet-generation.service.test.ts} +3 -3
  362. package/src/services/content/worksheet.service.ts +751 -0
  363. package/src/services/knowledge/knowledge-base.service.ts +705 -0
  364. package/src/services/members/invitation.service.ts +427 -0
  365. package/src/services/members/member.service.ts +241 -0
  366. package/src/{lib/notification-service.test.ts → services/notifications/notification.service.test.ts} +2 -2
  367. package/src/{lib/notification-service.ts → services/notifications/notification.service.ts} +102 -1
  368. package/src/services/workspace/workspace-analytics.service.ts +107 -0
  369. package/src/services/workspace/workspace-kb.service.ts +273 -0
  370. package/src/services/workspace/workspace.service.ts +488 -0
  371. package/src/trpc.ts +7 -15
  372. package/src/lib/ai-session.ts +0 -704
  373. package/src/lib/usage_service.ts +0 -74
  374. package/src/lib/workspace-access.ts +0 -13
  375. /package/{check-difficulty.cjs → scripts/debug/check-difficulty.cjs} +0 -0
  376. /package/{check-questions.cjs → scripts/debug/check-questions.cjs} +0 -0
  377. /package/{db-summary.cjs → scripts/debug/db-summary.cjs} +0 -0
  378. /package/{mcq-test.cjs → scripts/debug/mcq-test.cjs} +0 -0
  379. /package/{test-generate.js → scripts/debug/test-generate.js} +0 -0
  380. /package/{test-ratio.cjs → scripts/debug/test-ratio.cjs} +0 -0
  381. /package/{zod-test.cjs → scripts/debug/zod-test.cjs} +0 -0
  382. /package/src/{lib/activity_human_description.ts → services/activity/activity-human-description.service.ts} +0 -0
  383. /package/src/{lib/worksheet-generation.ts → services/content/worksheet-generation.service.ts} +0 -0
@@ -1,807 +1,34 @@
1
1
  import { z } from 'zod';
2
- import { TRPCError } from '@trpc/server';
3
2
  import { router, authedProcedure, limitedProcedure } from '../trpc.js';
4
- import { v4 as uuidv4 } from 'uuid';
5
- import inference from '../lib/inference.js';
6
- import { generateSignedUrl, deleteFromSupabase } from '../lib/storage.js';
7
- import PusherService from '../lib/pusher.js';
8
- import { aiSessionService } from '../lib/ai-session.js';
9
- import { ArtifactType } from '../lib/constants.js';
10
- import { workspaceAccessFilter } from '../lib/workspace-access.js';
11
- import { logger } from '../lib/logger.js';
12
- import { notifyArtifactFailed, notifyArtifactReady } from '../lib/notification-service.js';
13
- // Podcast segment schema
14
- const podcastSegmentSchema = z.object({
15
- id: z.string(),
16
- title: z.string(),
17
- content: z.string(),
18
- startTime: z.number(), // in seconds
19
- duration: z.number(), // in seconds
20
- keyPoints: z.array(z.string()),
21
- order: z.number().int(),
22
- audioUrl: z.string().optional(),
23
- objectKey: z.string().optional(), // Supabase Storage object key
24
- });
25
- // Speaker schema
26
- const speakerSchema = z.object({
27
- id: z.string(),
28
- role: z.enum(['host', 'guest', 'expert']),
29
- name: z.string().optional(),
30
- });
31
- // Podcast creation input schema
32
- const podcastInputSchema = z.object({
33
- title: z.string(),
34
- description: z.string().optional(),
35
- userPrompt: z.string(),
36
- speakers: z.array(speakerSchema).min(1).default([{ id: 'pNInz6obpgDQGcFmaJgB', role: 'host' }]),
37
- speed: z.number().min(0.25).max(4.0).default(1.0),
38
- generateIntro: z.boolean().default(true),
39
- generateOutro: z.boolean().default(true),
40
- segmentByTopics: z.boolean().default(true),
41
- });
42
- // Podcast metadata schema for version data (segments stored separately in database)
43
- const podcastMetadataSchema = z.object({
44
- title: z.string(),
45
- description: z.string().optional(),
46
- totalDuration: z.number(),
47
- speakers: z.array(speakerSchema),
48
- summary: z.object({
49
- executiveSummary: z.string(),
50
- learningObjectives: z.array(z.string()),
51
- keyConcepts: z.array(z.string()),
52
- followUpActions: z.array(z.string()),
53
- targetAudience: z.string(),
54
- prerequisites: z.array(z.string()),
55
- tags: z.array(z.string()),
56
- }),
57
- generatedAt: z.string(),
58
- });
3
+ import { PodcastService, podcastInputSchema } from '../services/content/podcast.service.js';
59
4
  export const podcast = router({
60
- // List all podcast episodes for a workspace
61
5
  listEpisodes: authedProcedure
62
6
  .input(z.object({ workspaceId: z.string() }))
63
- .query(async ({ ctx, input }) => {
64
- const workspace = await ctx.db.workspace.findFirst({
65
- where: { id: input.workspaceId, ownerId: ctx.session.user.id },
66
- });
67
- // Check if workspace exists
68
- if (!workspace)
69
- throw new TRPCError({ code: 'NOT_FOUND' });
70
- const artifacts = await ctx.db.artifact.findMany({
71
- where: {
72
- workspaceId: input.workspaceId,
73
- type: ArtifactType.PODCAST_EPISODE
74
- },
75
- include: {
76
- versions: {
77
- orderBy: { version: 'desc' },
78
- take: 1, // Get only the latest version
79
- },
80
- podcastSegments: {
81
- orderBy: { order: 'asc' },
82
- },
83
- },
84
- orderBy: { updatedAt: 'desc' },
85
- });
86
- logger.debug(`Found ${artifacts.length} podcast artifacts`);
87
- artifacts.forEach((artifact, i) => {
88
- logger.debug(` Podcast ${i + 1}: "${artifact.title}" - ${artifact.podcastSegments.length} segments`);
89
- });
90
- // Transform to include segments with fresh signed URLs
91
- const episodesWithUrls = await Promise.all(artifacts.map(async (artifact) => {
92
- const latestVersion = artifact.versions[0];
93
- let objectUrl = null;
94
- if (artifact.imageObjectKey) {
95
- objectUrl = await generateSignedUrl(artifact.imageObjectKey, 24);
96
- }
97
- // Generate fresh signed URLs for all segments
98
- const segmentsWithUrls = await Promise.all(artifact.podcastSegments.map(async (segment) => {
99
- if (segment.objectKey) {
100
- try {
101
- const signedUrl = await generateSignedUrl(segment.objectKey, 24); // 24 hours
102
- return {
103
- id: segment.id,
104
- title: segment.title,
105
- audioUrl: signedUrl,
106
- objectKey: segment.objectKey,
107
- startTime: segment.startTime,
108
- duration: segment.duration,
109
- order: segment.order,
110
- };
111
- }
112
- catch (error) {
113
- logger.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
114
- return {
115
- id: segment.id,
116
- title: segment.title,
117
- audioUrl: null,
118
- objectKey: segment.objectKey,
119
- startTime: segment.startTime,
120
- duration: segment.duration,
121
- order: segment.order,
122
- };
123
- }
124
- }
125
- return {
126
- id: segment.id,
127
- title: segment.title,
128
- audioUrl: null,
129
- objectKey: segment.objectKey,
130
- startTime: segment.startTime,
131
- duration: segment.duration,
132
- order: segment.order,
133
- };
134
- }));
135
- // Parse metadata from latest version if available
136
- let metadata = null;
137
- if (latestVersion) {
138
- try {
139
- logger.debug(JSON.stringify(latestVersion.data));
140
- metadata = podcastMetadataSchema.parse(latestVersion.data);
141
- }
142
- catch (error) {
143
- logger.error('Failed to parse podcast metadata:', error);
144
- }
145
- }
146
- return {
147
- id: artifact.id,
148
- title: metadata?.title || artifact.title || 'Untitled Episode',
149
- description: metadata?.description || artifact.description || null,
150
- metadata: metadata,
151
- imageUrl: objectUrl,
152
- segments: segmentsWithUrls,
153
- createdAt: artifact.createdAt,
154
- updatedAt: artifact.updatedAt,
155
- workspaceId: artifact.workspaceId,
156
- generating: artifact.generating,
157
- generatingMetadata: artifact.generatingMetadata,
158
- type: artifact.type,
159
- createdById: artifact.createdById,
160
- isArchived: artifact.isArchived,
161
- };
162
- }));
163
- return episodesWithUrls;
164
- }),
165
- // Get a specific podcast episode with segments and signed URLs
7
+ .query(({ ctx, input }) => new PodcastService(ctx.db).listEpisodes(ctx.session.user.id, input.workspaceId)),
166
8
  getEpisode: authedProcedure
167
9
  .input(z.object({ episodeId: z.string() }))
168
- .query(async ({ ctx, input }) => {
169
- const episode = await ctx.db.artifact.findFirst({
170
- where: {
171
- id: input.episodeId,
172
- type: ArtifactType.PODCAST_EPISODE,
173
- workspace: workspaceAccessFilter(ctx.session.user.id)
174
- },
175
- include: {
176
- versions: {
177
- orderBy: { version: 'desc' },
178
- take: 1,
179
- },
180
- podcastSegments: {
181
- orderBy: { order: 'asc' },
182
- },
183
- },
184
- });
185
- logger.debug(JSON.stringify(episode));
186
- if (!episode)
187
- throw new TRPCError({ code: 'NOT_FOUND' });
188
- const latestVersion = episode.versions[0];
189
- if (!latestVersion)
190
- throw new TRPCError({ code: 'NOT_FOUND', message: 'No version found' });
191
- logger.debug(JSON.stringify(latestVersion));
192
- try {
193
- const metadata = podcastMetadataSchema.parse(latestVersion.data);
194
- }
195
- catch (error) {
196
- logger.error('Failed to parse podcast metadata:', error);
197
- }
198
- const metadata = podcastMetadataSchema.parse(latestVersion.data);
199
- const imageUrl = episode.imageObjectKey ? await generateSignedUrl(episode.imageObjectKey, 24) : null;
200
- // Generate fresh signed URLs for all segments
201
- const segmentsWithUrls = await Promise.all(episode.podcastSegments.map(async (segment) => {
202
- if (segment.objectKey) {
203
- try {
204
- const signedUrl = await generateSignedUrl(segment.objectKey, 24); // 24 hours
205
- return {
206
- id: segment.id,
207
- title: segment.title,
208
- content: segment.content,
209
- audioUrl: signedUrl,
210
- objectKey: segment.objectKey,
211
- startTime: segment.startTime,
212
- duration: segment.duration,
213
- keyPoints: segment.keyPoints,
214
- order: segment.order,
215
- };
216
- }
217
- catch (error) {
218
- logger.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
219
- return {
220
- id: segment.id,
221
- title: segment.title,
222
- content: segment.content,
223
- audioUrl: null,
224
- objectKey: segment.objectKey,
225
- startTime: segment.startTime,
226
- duration: segment.duration,
227
- keyPoints: segment.keyPoints,
228
- order: segment.order,
229
- };
230
- }
231
- }
232
- return {
233
- id: segment.id,
234
- title: segment.title,
235
- content: segment.content,
236
- audioUrl: null,
237
- objectKey: segment.objectKey,
238
- startTime: segment.startTime,
239
- duration: segment.duration,
240
- keyPoints: segment.keyPoints,
241
- order: segment.order,
242
- };
243
- }));
244
- return {
245
- id: episode.id,
246
- title: metadata.title, // Use title from version metadata
247
- description: metadata.description, // Use description from version metadata
248
- metadata,
249
- imageUrl: imageUrl,
250
- segments: segmentsWithUrls,
251
- content: latestVersion.content, // transcript
252
- createdAt: episode.createdAt,
253
- updatedAt: episode.updatedAt,
254
- };
255
- }),
256
- // Generate podcast episode from text input
10
+ .query(({ ctx, input }) => new PodcastService(ctx.db).getEpisode(ctx.session.user.id, input.episodeId)),
257
11
  generateEpisode: limitedProcedure
258
- .input(z.object({
259
- workspaceId: z.string(),
260
- podcastData: podcastInputSchema,
261
- }))
262
- .mutation(async ({ ctx, input }) => {
263
- const workspace = await ctx.db.workspace.findFirst({
264
- where: { id: input.workspaceId, ownerId: ctx.session.user.id },
265
- });
266
- if (!workspace)
267
- throw new TRPCError({ code: 'NOT_FOUND' });
268
- // Emit podcast generation start notification
269
- await PusherService.emitTaskComplete(input.workspaceId, 'podcast_generation_start', {
270
- title: input.podcastData.title
271
- });
272
- const BEGIN_PODCAST_GENERATION_MESSAGE = 'Structuring podcast contents...';
273
- const newArtifact = await ctx.db.artifact.create({
274
- data: {
275
- title: '----',
276
- type: ArtifactType.PODCAST_EPISODE,
277
- generating: true,
278
- generatingMetadata: {
279
- message: BEGIN_PODCAST_GENERATION_MESSAGE,
280
- },
281
- workspace: {
282
- connect: {
283
- id: input.workspaceId,
284
- }
285
- }
286
- }
287
- });
288
- await PusherService.emitTaskComplete(input.workspaceId, 'podcast_info', {
289
- message: BEGIN_PODCAST_GENERATION_MESSAGE,
290
- });
291
- try {
292
- const structureResult = await aiSessionService.generatePodcastStructure(input.workspaceId, ctx.session.user.id, input.podcastData.title, input.podcastData.description || '', input.podcastData.userPrompt, input.podcastData.speakers);
293
- if (!structureResult.success || !structureResult.structure) {
294
- throw new TRPCError({
295
- code: 'INTERNAL_SERVER_ERROR',
296
- message: 'Failed to generate podcast structure'
297
- });
298
- }
299
- const structure = structureResult.structure;
300
- await ctx.db.artifact.update({
301
- where: {
302
- id: newArtifact.id,
303
- },
304
- data: {
305
- title: structure.episodeTitle,
306
- }
307
- });
308
- // Step 2: Generate audio for each segment
309
- const segments = [];
310
- const failedSegments = [];
311
- let totalDuration = 0;
312
- let fullTranscript = '';
313
- await ctx.db.artifact.update({
314
- where: {
315
- id: newArtifact.id,
316
- },
317
- data: {
318
- generatingMetadata: {
319
- message: `Generating podcast image...`,
320
- },
321
- }
322
- });
323
- await PusherService.emitTaskComplete(input.workspaceId, 'podcast_info', {
324
- message: `Generating podcast image...`,
325
- });
326
- const podcastImage = await aiSessionService.generatePodcastImage(input.workspaceId, ctx.session.user.id, structure.segments.map((segment) => segment.content).join('\n\n'));
327
- await ctx.db.artifact.update({
328
- where: {
329
- id: newArtifact.id,
330
- },
331
- data: {
332
- imageObjectKey: podcastImage,
333
- }
334
- });
335
- for (let i = 0; i < structure.segments.length; i++) {
336
- const segment = structure.segments[i];
337
- try {
338
- // Emit segment generation progress
339
- // await PusherService.emitTaskComplete(input.workspaceId, 'podcast_segment_progress', {
340
- // currentSegment: i + 1,
341
- // totalSegments: structure.segments.length,
342
- // segmentTitle: segment.title || `Segment ${i + 1}`,
343
- // successfulSegments: segments.length,
344
- // failedSegments: failedSegments.length,
345
- // });
346
- await ctx.db.artifact.update({
347
- where: {
348
- id: newArtifact.id,
349
- },
350
- data: {
351
- generatingMetadata: {
352
- message: `Generating audio for "${segment.title}" (${i + 1} of ${structure.segments.length})...`,
353
- },
354
- }
355
- });
356
- await PusherService.emitTaskComplete(input.workspaceId, 'podcast_info', {
357
- message: `Generating audio for segment ${i + 1} of ${structure.segments.length}...`,
358
- });
359
- // Generate audio using new API
360
- const audioResult = await aiSessionService.generatePodcastAudioFromText(input.workspaceId, ctx.session.user.id, newArtifact.id, i, segment.content, input.podcastData.speakers, segment.voiceId);
361
- if (!audioResult.success) {
362
- throw new Error('Failed to generate audio for segment');
363
- }
364
- segments.push({
365
- id: uuidv4(),
366
- title: segment.title,
367
- content: segment.content,
368
- objectKey: audioResult.objectKey,
369
- startTime: totalDuration,
370
- duration: audioResult.duration,
371
- keyPoints: segment.keyPoints || [],
372
- order: segment.order || i + 1,
373
- });
374
- totalDuration += audioResult.duration;
375
- fullTranscript += `\n\n## ${segment.title}\n\n${segment.content}`;
376
- }
377
- catch (audioError) {
378
- const errorMessage = audioError instanceof Error ? audioError.message : 'Unknown error';
379
- logger.error(`❌ Error generating audio for segment ${i + 1}:`, {
380
- title: segment.title,
381
- error: errorMessage,
382
- stack: audioError instanceof Error ? audioError.stack : undefined,
383
- });
384
- // Track failed segment
385
- failedSegments.push({
386
- index: i + 1,
387
- title: segment.title || `Segment ${i + 1}`,
388
- error: errorMessage,
389
- });
390
- await PusherService.emitTaskComplete(input.workspaceId, 'podcast_segment_error', {
391
- segmentIndex: i + 1,
392
- segmentTitle: segment.title || `Segment ${i + 1}`,
393
- error: errorMessage,
394
- successfulSegments: segments.length,
395
- failedSegments: failedSegments.length,
396
- });
397
- // Continue with other segments even if one fails
398
- }
399
- }
400
- // Check if any segments were successfully generated
401
- if (segments.length === 0) {
402
- logger.error('No segments were successfully generated');
403
- await PusherService.emitError(input.workspaceId, `Failed to generate any segments. ${failedSegments.length} segment(s) failed.`, 'podcast');
404
- // Cleanup the artifact
405
- await notifyArtifactFailed(ctx.db, {
406
- userId: ctx.session.user.id,
407
- workspaceId: input.workspaceId,
408
- artifactType: ArtifactType.PODCAST_EPISODE,
409
- artifactId: newArtifact.id,
410
- title: input.podcastData.title,
411
- message: `Failed to generate any audio segments. All ${failedSegments.length} attempts failed.`,
412
- }).catch(() => { });
413
- await ctx.db.artifact.delete({
414
- where: { id: newArtifact.id },
415
- });
416
- throw new TRPCError({
417
- code: 'INTERNAL_SERVER_ERROR',
418
- message: `Failed to generate any audio segments. All ${failedSegments.length} attempts failed.`
419
- });
420
- }
421
- await ctx.db.artifact.update({
422
- where: {
423
- id: newArtifact.id,
424
- },
425
- data: {
426
- generatingMetadata: {
427
- message: `Preparing podcast summary...`,
428
- },
429
- }
430
- });
431
- await PusherService.emitTaskComplete(input.workspaceId, 'podcast_info', {
432
- message: `Preparing podcast summary...`,
433
- });
434
- // Step 3: Generate episode summary using inference API
435
- const summaryPrompt = `Create a comprehensive podcast episode summary including:
436
- - Executive summary
437
- - Learning objectives
438
- - Key concepts covered
439
- - Recommended follow-up actions
440
- - Target audience
441
- - Prerequisites (if any)
442
-
443
- Format as JSON:
444
- {
445
- "executiveSummary": "Brief overview of the episode",
446
- "learningObjectives": ["objective1", "objective2"],
447
- "keyConcepts": ["concept1", "concept2"],
448
- "followUpActions": ["action1", "action2"],
449
- "targetAudience": "Description of target audience",
450
- "prerequisites": ["prerequisite1", "prerequisite2"],
451
- "tags": ["tag1", "tag2", "tag3"]
452
- }
453
-
454
- Podcast Title: ${structure.episodeTitle}
455
- Segments: ${JSON.stringify(segments.map(s => ({ title: s.title, keyPoints: s.keyPoints })))}`;
456
- const summaryResponse = await inference([{ role: "user", content: summaryPrompt }]);
457
- const summaryContent = summaryResponse.choices[0].message.content || '';
458
- let episodeSummary;
459
- try {
460
- // Extract JSON from the response
461
- const jsonMatch = summaryContent.match(/\{[\s\S]*\}/);
462
- if (!jsonMatch) {
463
- throw new Error('No JSON found in summary response');
464
- }
465
- episodeSummary = JSON.parse(jsonMatch[0]);
466
- }
467
- catch (parseError) {
468
- logger.error('Failed to parse summary response:', summaryContent);
469
- await PusherService.emitTaskComplete(input.workspaceId, 'podcast_summary_error', {
470
- error: 'Failed to parse summary response'
471
- });
472
- episodeSummary = {
473
- executiveSummary: 'AI-generated podcast episode',
474
- learningObjectives: [],
475
- keyConcepts: [],
476
- followUpActions: [],
477
- targetAudience: 'General audience',
478
- prerequisites: [],
479
- tags: [],
480
- };
481
- }
482
- // Emit summary generation completion notification
483
- await PusherService.emitTaskComplete(input.workspaceId, 'podcast_info', {
484
- message: `Podcast summary generated.`,
485
- });
486
- // Step 4: Create artifact and initial version
487
- const episodeTitle = structure.episodeTitle || input.podcastData.title;
488
- await ctx.db.artifact.update({
489
- where: {
490
- id: newArtifact.id,
491
- },
492
- data: {
493
- workspaceId: input.workspaceId,
494
- type: ArtifactType.PODCAST_EPISODE,
495
- title: episodeTitle, // Store basic title for listing/searching
496
- description: input.podcastData.description, // Store basic description for listing/searching
497
- createdById: ctx.session.user.id,
498
- },
499
- });
500
- const createdSegments = await ctx.db.podcastSegment.createMany({
501
- data: segments.map(segment => ({
502
- artifactId: newArtifact.id,
503
- title: segment.title,
504
- content: segment.content,
505
- startTime: segment.startTime,
506
- duration: segment.duration,
507
- order: segment.order,
508
- objectKey: segment.objectKey,
509
- keyPoints: segment.keyPoints,
510
- meta: {
511
- speed: input.podcastData.speed,
512
- speakers: input.podcastData.speakers,
513
- },
514
- })),
515
- });
516
- const metadata = {
517
- title: episodeTitle,
518
- description: input.podcastData.description,
519
- totalDuration: totalDuration,
520
- summary: episodeSummary,
521
- speakers: input.podcastData.speakers,
522
- generatedAt: new Date().toISOString(),
523
- };
524
- await ctx.db.artifactVersion.create({
525
- data: {
526
- artifactId: newArtifact.id,
527
- version: 1,
528
- content: fullTranscript.trim(), // Full transcript as markdown
529
- data: metadata,
530
- createdById: ctx.session.user.id,
531
- },
532
- });
533
- await ctx.db.artifact.update({
534
- where: {
535
- id: newArtifact.id,
536
- },
537
- data: {
538
- generating: false,
539
- },
540
- });
541
- // Emit podcast generation completion notification
542
- await PusherService.emitPodcastComplete(input.workspaceId, newArtifact);
543
- await notifyArtifactReady(ctx.db, {
544
- userId: ctx.session.user.id,
545
- workspaceId: input.workspaceId,
546
- artifactId: newArtifact.id,
547
- artifactType: ArtifactType.PODCAST_EPISODE,
548
- title: metadata.title,
549
- }).catch(() => { });
550
- return {
551
- id: newArtifact.id,
552
- title: metadata.title,
553
- description: metadata.description,
554
- metadata,
555
- content: fullTranscript.trim(),
556
- };
557
- }
558
- catch (error) {
559
- logger.error('Error generating podcast episode:', error);
560
- await notifyArtifactFailed(ctx.db, {
561
- userId: ctx.session.user.id,
562
- workspaceId: input.workspaceId,
563
- artifactType: ArtifactType.PODCAST_EPISODE,
564
- artifactId: newArtifact.id,
565
- title: input.podcastData.title,
566
- message: error instanceof Error
567
- ? error.message
568
- : 'Podcast generation failed.',
569
- }).catch(() => { });
570
- await ctx.db.artifact.delete({
571
- where: {
572
- id: newArtifact.id,
573
- },
574
- });
575
- await PusherService.emitError(input.workspaceId, `Failed to generate podcast episode: ${error instanceof Error ? error.message : 'Unknown error'}`, 'podcast');
576
- throw new TRPCError({
577
- code: 'INTERNAL_SERVER_ERROR',
578
- message: `Failed to generate podcast episode: ${error instanceof Error ? error.message : 'Unknown error'}`
579
- });
580
- }
581
- }),
12
+ .input(z.object({ workspaceId: z.string(), podcastData: podcastInputSchema }))
13
+ .mutation(({ ctx, input }) => new PodcastService(ctx.db).generateEpisode(ctx.session.user.id, input)),
582
14
  deleteSegment: authedProcedure
583
15
  .input(z.object({ segmentId: z.string() }))
584
- .mutation(async ({ ctx, input }) => {
585
- const segment = await ctx.db.podcastSegment.delete({ where: { id: input.segmentId } });
586
- return segment;
587
- }),
588
- // Get episode schema/structure for navigation
16
+ .mutation(({ ctx, input }) => new PodcastService(ctx.db).deleteSegment(input.segmentId)),
589
17
  getEpisodeSchema: authedProcedure
590
18
  .input(z.object({ episodeId: z.string() }))
591
- .query(async ({ ctx, input }) => {
592
- const episode = await ctx.db.artifact.findFirst({
593
- where: {
594
- id: input.episodeId,
595
- type: ArtifactType.PODCAST_EPISODE,
596
- workspace: workspaceAccessFilter(ctx.session.user.id)
597
- },
598
- include: {
599
- versions: {
600
- orderBy: { version: 'desc' },
601
- take: 1,
602
- },
603
- podcastSegments: {
604
- orderBy: { order: 'asc' },
605
- },
606
- },
607
- });
608
- if (!episode)
609
- throw new TRPCError({ code: 'NOT_FOUND' });
610
- const latestVersion = episode.versions[0];
611
- if (!latestVersion)
612
- throw new TRPCError({ code: 'NOT_FOUND', message: 'No version found' });
613
- const metadata = podcastMetadataSchema.parse(latestVersion.data);
614
- return {
615
- segments: episode.podcastSegments.map(s => ({
616
- id: s.id,
617
- title: s.title,
618
- startTime: s.startTime,
619
- duration: s.duration,
620
- keyPoints: s.keyPoints,
621
- order: s.order,
622
- })),
623
- summary: metadata.summary,
624
- metadata: {
625
- title: metadata.title,
626
- description: metadata.description,
627
- totalDuration: metadata.totalDuration,
628
- speakers: metadata.speakers,
629
- },
630
- };
631
- }),
632
- // Update episode metadata
19
+ .query(({ ctx, input }) => new PodcastService(ctx.db).getEpisodeSchema(ctx.session.user.id, input.episodeId)),
633
20
  updateEpisode: authedProcedure
634
21
  .input(z.object({
635
22
  episodeId: z.string(),
636
23
  title: z.string().optional(),
637
24
  description: z.string().optional(),
638
25
  }))
639
- .mutation(async ({ ctx, input }) => {
640
- const episode = await ctx.db.artifact.findFirst({
641
- where: {
642
- id: input.episodeId,
643
- type: ArtifactType.PODCAST_EPISODE,
644
- workspace: workspaceAccessFilter(ctx.session.user.id)
645
- },
646
- include: {
647
- versions: {
648
- orderBy: { version: 'desc' },
649
- take: 1,
650
- },
651
- },
652
- });
653
- if (!episode)
654
- throw new TRPCError({ code: 'NOT_FOUND' });
655
- const latestVersion = episode.versions[0];
656
- if (!latestVersion)
657
- throw new TRPCError({ code: 'NOT_FOUND', message: 'No version found' });
658
- const metadata = podcastMetadataSchema.parse(latestVersion.data);
659
- // Update metadata
660
- if (input.title)
661
- metadata.title = input.title;
662
- if (input.description)
663
- metadata.description = input.description;
664
- // Create new version with updated metadata
665
- const nextVersion = (latestVersion.version || 0) + 1;
666
- await ctx.db.artifactVersion.create({
667
- data: {
668
- artifactId: input.episodeId,
669
- version: nextVersion,
670
- content: latestVersion.content,
671
- data: metadata,
672
- createdById: ctx.session.user.id,
673
- },
674
- });
675
- // Update the artifact with basic info for listing/searching
676
- return ctx.db.artifact.update({
677
- where: { id: input.episodeId },
678
- data: {
679
- title: input.title ?? episode.title,
680
- description: input.description ?? episode.description,
681
- updatedAt: new Date(),
682
- },
683
- });
684
- }),
685
- // Delete episode and associated audio files
26
+ .mutation(({ ctx, input }) => new PodcastService(ctx.db).updateEpisode(ctx.session.user.id, input)),
686
27
  deleteEpisode: authedProcedure
687
28
  .input(z.object({ episodeId: z.string() }))
688
- .mutation(async ({ ctx, input }) => {
689
- const episode = await ctx.db.artifact.findFirst({
690
- where: {
691
- id: input.episodeId,
692
- type: ArtifactType.PODCAST_EPISODE,
693
- workspace: workspaceAccessFilter(ctx.session.user.id)
694
- },
695
- include: {
696
- versions: {
697
- orderBy: { version: 'desc' },
698
- take: 1,
699
- },
700
- },
701
- });
702
- if (!episode)
703
- throw new TRPCError({ code: 'NOT_FOUND' });
704
- try {
705
- // Emit episode deletion start notification
706
- await PusherService.emitTaskComplete(episode.workspaceId, 'podcast_deletion_start', {
707
- episodeId: input.episodeId,
708
- episodeTitle: episode.title || 'Untitled Episode'
709
- });
710
- // Get segments to delete audio files
711
- const segments = await ctx.db.podcastSegment.findMany({
712
- where: { artifactId: input.episodeId },
713
- });
714
- // Delete audio files from Supabase Storage
715
- for (const segment of segments) {
716
- if (segment.objectKey) {
717
- try {
718
- await deleteFromSupabase(segment.objectKey);
719
- }
720
- catch (error) {
721
- logger.error(`Failed to delete audio file ${segment.objectKey}:`, error);
722
- }
723
- }
724
- }
725
- // Delete associated segments
726
- await ctx.db.podcastSegment.deleteMany({
727
- where: { artifactId: input.episodeId },
728
- });
729
- // Delete associated versions
730
- await ctx.db.artifactVersion.deleteMany({
731
- where: { artifactId: input.episodeId },
732
- });
733
- // Delete the artifact
734
- await ctx.db.artifact.delete({
735
- where: { id: input.episodeId },
736
- });
737
- // Emit episode deletion completion notification
738
- await PusherService.emitTaskComplete(episode.workspaceId, 'podcast_deletion_complete', {
739
- episodeId: input.episodeId,
740
- episodeTitle: episode.title || 'Untitled Episode'
741
- });
742
- return true;
743
- }
744
- catch (error) {
745
- logger.error('Error deleting episode:', error);
746
- await PusherService.emitError(episode.workspaceId, `Failed to delete episode: ${error instanceof Error ? error.message : 'Unknown error'}`, 'podcast');
747
- throw new TRPCError({
748
- code: 'INTERNAL_SERVER_ERROR',
749
- message: 'Failed to delete episode'
750
- });
751
- }
752
- }),
753
- // Get a specific segment with signed URL
29
+ .mutation(({ ctx, input }) => new PodcastService(ctx.db).deleteEpisode(ctx.session.user.id, input.episodeId)),
754
30
  getSegment: authedProcedure
755
31
  .input(z.object({ segmentId: z.string() }))
756
- .query(async ({ ctx, input }) => {
757
- const segment = await ctx.db.podcastSegment.findFirst({
758
- where: {
759
- id: input.segmentId,
760
- artifact: {
761
- workspace: workspaceAccessFilter(ctx.session.user.id)
762
- }
763
- },
764
- include: {
765
- artifact: true,
766
- },
767
- });
768
- if (!segment)
769
- throw new TRPCError({ code: 'NOT_FOUND' });
770
- // Generate fresh signed URL
771
- let audioUrl = null;
772
- if (segment.objectKey) {
773
- try {
774
- audioUrl = await generateSignedUrl(segment.objectKey, 24); // 24 hours
775
- }
776
- catch (error) {
777
- logger.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
778
- }
779
- }
780
- return {
781
- id: segment.id,
782
- title: segment.title,
783
- content: segment.content,
784
- startTime: segment.startTime,
785
- duration: segment.duration,
786
- order: segment.order,
787
- keyPoints: segment.keyPoints,
788
- audioUrl,
789
- objectKey: segment.objectKey,
790
- meta: segment.meta,
791
- createdAt: segment.createdAt,
792
- updatedAt: segment.updatedAt,
793
- };
794
- }),
795
- // Get available voices for TTS
796
- getAvailableVoices: authedProcedure
797
- .query(async () => {
798
- return [
799
- { id: 'alloy', name: 'Alloy', description: 'Neutral, balanced voice' },
800
- { id: 'echo', name: 'Echo', description: 'Clear, professional voice' },
801
- { id: 'fable', name: 'Fable', description: 'Warm, storytelling voice' },
802
- { id: 'onyx', name: 'Onyx', description: 'Deep, authoritative voice' },
803
- { id: 'nova', name: 'Nova', description: 'Friendly, conversational voice' },
804
- { id: 'shimmer', name: 'Shimmer', description: 'Bright, energetic voice' },
805
- ];
806
- }),
32
+ .query(({ ctx, input }) => new PodcastService(ctx.db).getSegment(ctx.session.user.id, input.segmentId)),
33
+ getAvailableVoices: authedProcedure.query(({ ctx }) => new PodcastService(ctx.db).getAvailableVoices()),
807
34
  });