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