@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.
- package/.env.example +12 -0
- package/.vscode/settings.json +3 -0
- package/REFACTOR_NOTES.md +60 -0
- package/TESTING_PROMPT.md +225 -0
- package/dist/context.d.ts +14 -1
- package/dist/context.js +23 -2
- package/dist/controllers/admin.controller.d.ts +715 -0
- package/dist/controllers/admin.controller.js +9 -0
- package/dist/controllers/annotations.controller.d.ts +439 -0
- package/dist/controllers/annotations.controller.js +9 -0
- package/dist/controllers/app-router.controller.d.ts +3011 -0
- package/dist/controllers/app-router.controller.js +38 -0
- package/dist/controllers/app-router.controller.test.d.ts +1 -0
- package/dist/controllers/app-router.controller.test.js +36 -0
- package/dist/controllers/auth.controller.d.ts +323 -0
- package/dist/controllers/auth.controller.js +9 -0
- package/dist/controllers/base.controller.d.ts +4 -0
- package/dist/controllers/base.controller.js +5 -0
- package/dist/controllers/chat.controller.d.ts +341 -0
- package/dist/controllers/chat.controller.js +9 -0
- package/dist/controllers/copilot.controller.d.ts +397 -0
- package/dist/controllers/copilot.controller.js +9 -0
- package/dist/controllers/flashcards.controller.d.ts +651 -0
- package/dist/controllers/flashcards.controller.js +9 -0
- package/dist/controllers/members.controller.d.ts +339 -0
- package/dist/controllers/members.controller.js +9 -0
- package/dist/controllers/notifications.controller.d.ts +199 -0
- package/dist/controllers/notifications.controller.js +9 -0
- package/dist/controllers/payment.controller.d.ts +181 -0
- package/dist/controllers/payment.controller.js +9 -0
- package/dist/controllers/podcast.controller.d.ts +575 -0
- package/dist/controllers/podcast.controller.js +9 -0
- package/dist/controllers/router-module.controller.d.ts +5 -0
- package/dist/controllers/router-module.controller.js +6 -0
- package/dist/controllers/studyguide.controller.d.ts +73 -0
- package/dist/controllers/studyguide.controller.js +9 -0
- package/dist/controllers/worksheets.controller.d.ts +829 -0
- package/dist/controllers/worksheets.controller.js +9 -0
- package/dist/controllers/workspace.controller.d.ts +1207 -0
- package/dist/controllers/workspace.controller.js +9 -0
- package/dist/lib/activity_human_description.test.js +16 -15
- package/dist/lib/activity_log_service.test.js +28 -23
- package/dist/lib/ai/config.d.ts +20 -0
- package/dist/lib/ai/config.js +31 -0
- package/dist/lib/ai/embedding-client.d.ts +8 -0
- package/dist/lib/ai/embedding-client.js +30 -0
- package/dist/lib/ai/index.d.ts +48 -0
- package/dist/lib/ai/index.js +29 -0
- package/dist/lib/ai/inference-backend/client.d.ts +28 -0
- package/dist/lib/ai/inference-backend/client.js +301 -0
- package/dist/lib/ai/inference-backend/mocks.d.ts +12 -0
- package/dist/lib/ai/inference-backend/mocks.js +133 -0
- package/dist/lib/ai/inference-backend/types.d.ts +44 -0
- package/dist/lib/ai/inference-backend/types.js +1 -0
- package/dist/lib/ai/json-parse.d.ts +2 -0
- package/dist/lib/ai/json-parse.js +34 -0
- package/dist/lib/ai/llm-client.d.ts +7 -0
- package/dist/lib/ai/llm-client.js +36 -0
- package/dist/lib/ai/mock.d.ts +2 -0
- package/dist/lib/ai/mock.js +10 -0
- package/dist/lib/ai/types.d.ts +9 -0
- package/dist/lib/ai/types.js +1 -0
- package/dist/lib/chunking.d.ts +19 -0
- package/dist/lib/chunking.js +47 -0
- package/dist/lib/curated-kb-seed.d.ts +12 -0
- package/dist/lib/curated-kb-seed.js +155 -0
- package/dist/lib/email.js +67 -108
- package/dist/lib/embeddings.d.ts +2 -0
- package/dist/lib/embeddings.js +1 -0
- package/dist/lib/ensure-curated-kb-catalog.d.ts +6 -0
- package/dist/lib/ensure-curated-kb-catalog.js +53 -0
- package/dist/lib/env.d.ts +1 -5
- package/dist/lib/env.js +2 -7
- package/dist/lib/inference.d.ts +1 -8
- package/dist/lib/inference.js +1 -19
- package/dist/lib/kb-meta.d.ts +8 -0
- package/dist/lib/kb-meta.js +77 -0
- package/dist/lib/note-text.d.ts +1 -0
- package/dist/lib/note-text.js +47 -0
- package/dist/lib/notification-service.test.js +37 -36
- package/dist/lib/pdf.d.ts +11 -0
- package/dist/lib/pdf.js +11 -0
- package/dist/lib/usage_service.d.ts +2 -1
- package/dist/lib/usage_service.js +30 -12
- package/dist/lib/worksheet-generation.js +4 -4
- package/dist/lib/worksheet-generation.test.js +32 -17
- package/dist/lib/workspace-kb.d.ts +5 -0
- package/dist/lib/workspace-kb.js +7 -0
- package/dist/models/controller-context.model.d.ts +8 -0
- package/dist/models/controller-context.model.js +1 -0
- package/dist/repositories/artifact.repository.d.ts +60 -0
- package/dist/repositories/artifact.repository.js +40 -0
- package/dist/repositories/base.repository.d.ts +14 -0
- package/dist/repositories/base.repository.js +14 -0
- package/dist/repositories/invitation.repository.d.ts +94 -0
- package/dist/repositories/invitation.repository.js +44 -0
- package/dist/repositories/notification.repository.d.ts +72 -0
- package/dist/repositories/notification.repository.js +44 -0
- package/dist/repositories/router-module.repository.d.ts +10 -0
- package/dist/repositories/router-module.repository.js +14 -0
- package/dist/repositories/user.repository.d.ts +74 -0
- package/dist/repositories/user.repository.js +37 -0
- package/dist/repositories/workspace-member.repository.d.ts +31 -0
- package/dist/repositories/workspace-member.repository.js +31 -0
- package/dist/repositories/workspace.repository.d.ts +97 -0
- package/dist/repositories/workspace.repository.js +79 -0
- package/dist/routers/_app.d.ts +566 -111
- package/dist/routers/_app.js +4 -0
- package/dist/routers/admin.d.ts +0 -4
- package/dist/routers/admin.js +21 -549
- package/dist/routers/annotations.js +12 -170
- package/dist/routers/artifactVersions.d.ts +65 -0
- package/dist/routers/artifactVersions.js +14 -0
- package/dist/routers/auth.d.ts +0 -6
- package/dist/routers/auth.js +37 -422
- package/dist/routers/chat.js +15 -229
- package/dist/routers/copilot.d.ts +14 -13
- package/dist/routers/copilot.js +13 -532
- package/dist/routers/flashcards.d.ts +17 -6
- package/dist/routers/flashcards.js +23 -349
- package/dist/routers/knowledgeBase.d.ts +421 -0
- package/dist/routers/knowledgeBase.js +118 -0
- package/dist/routers/members.d.ts +0 -41
- package/dist/routers/members.js +22 -710
- package/dist/routers/notes.d.ts +94 -0
- package/dist/routers/notes.js +37 -0
- package/dist/routers/notifications.js +7 -109
- package/dist/routers/payment.d.ts +2 -12
- package/dist/routers/payment.js +11 -393
- package/dist/routers/podcast.d.ts +1 -1
- package/dist/routers/podcast.js +11 -784
- package/dist/routers/studyguide.js +3 -129
- package/dist/routers/worksheets.d.ts +29 -14
- package/dist/routers/worksheets.js +49 -628
- package/dist/routers/workspace.d.ts +27 -71
- package/dist/routers/workspace.js +29 -922
- package/dist/scripts/purge-deleted-users.js +2 -2
- package/dist/server.js +10 -3
- package/dist/services/activity/activity-human-description.service.d.ts +13 -0
- package/dist/services/activity/activity-human-description.service.js +221 -0
- package/dist/services/activity/activity-human-description.service.test.d.ts +1 -0
- package/dist/services/activity/activity-human-description.service.test.js +16 -0
- package/dist/services/activity/activity-log.service.d.ts +87 -0
- package/dist/services/activity/activity-log.service.js +276 -0
- package/dist/services/activity/activity-log.service.test.d.ts +1 -0
- package/dist/services/activity/activity-log.service.test.js +27 -0
- package/dist/services/activity-human-description.service.d.ts +13 -0
- package/dist/services/activity-human-description.service.js +221 -0
- package/dist/services/activity-human-description.service.test.d.ts +1 -0
- package/dist/services/activity-human-description.service.test.js +16 -0
- package/dist/services/activity-log.service.d.ts +87 -0
- package/dist/services/activity-log.service.js +276 -0
- package/dist/services/activity-log.service.test.d.ts +1 -0
- package/dist/services/activity-log.service.test.js +27 -0
- package/dist/services/admin/admin.service.d.ts +270 -0
- package/dist/services/admin/admin.service.js +476 -0
- package/dist/services/admin.service.d.ts +270 -0
- package/dist/services/admin.service.js +476 -0
- package/dist/services/ai/ai-session.service.d.ts +5 -0
- package/dist/services/ai/ai-session.service.js +4 -0
- package/dist/services/ai-session.service.d.ts +60 -0
- package/dist/services/ai-session.service.js +561 -0
- package/dist/services/annotation.service.d.ts +177 -0
- package/dist/services/annotation.service.js +154 -0
- package/dist/services/artifact-notification.service.d.ts +14 -0
- package/dist/services/artifact-notification.service.js +20 -0
- package/dist/services/artifact-version.service.d.ts +38 -0
- package/dist/services/artifact-version.service.js +129 -0
- package/dist/services/artifacts/annotation.service.d.ts +177 -0
- package/dist/services/artifacts/annotation.service.js +154 -0
- package/dist/services/artifacts/artifact-version.service.d.ts +38 -0
- package/dist/services/artifacts/artifact-version.service.js +129 -0
- package/dist/services/artifacts/chat.service.d.ts +127 -0
- package/dist/services/artifacts/chat.service.js +182 -0
- package/dist/services/artifacts/study-guide.service.d.ts +18 -0
- package/dist/services/artifacts/study-guide.service.js +65 -0
- package/dist/services/auth/auth.service.d.ts +94 -0
- package/dist/services/auth/auth.service.js +368 -0
- package/dist/services/auth.service.d.ts +94 -0
- package/dist/services/auth.service.js +368 -0
- package/dist/services/base.service.d.ts +14 -0
- package/dist/services/base.service.js +14 -0
- package/dist/services/billing/payment.service.d.ts +44 -0
- package/dist/services/billing/payment.service.js +365 -0
- package/dist/services/billing/subscription.service.d.ts +37 -0
- package/dist/services/billing/subscription.service.js +654 -0
- package/dist/services/billing/usage.service.d.ts +47 -0
- package/dist/services/billing/usage.service.js +149 -0
- package/dist/services/chat.service.d.ts +127 -0
- package/dist/services/chat.service.js +182 -0
- package/dist/services/content/copilot.service.d.ts +113 -0
- package/dist/services/content/copilot.service.js +439 -0
- package/dist/services/content/flashcard-progress.service.d.ts +159 -0
- package/dist/services/content/flashcard-progress.service.js +432 -0
- package/dist/services/content/flashcard.service.d.ts +184 -0
- package/dist/services/content/flashcard.service.js +339 -0
- package/dist/services/content/media-analysis.service.d.ts +23 -0
- package/dist/services/content/media-analysis.service.js +404 -0
- package/dist/services/content/podcast.service.d.ts +267 -0
- package/dist/services/content/podcast.service.js +653 -0
- package/dist/services/content/worksheet-content.service.d.ts +37 -0
- package/dist/services/content/worksheet-content.service.js +84 -0
- package/dist/services/content/worksheet-content.service.test.d.ts +1 -0
- package/dist/services/content/worksheet-content.service.test.js +69 -0
- package/dist/services/content/worksheet-generation.service.d.ts +91 -0
- package/dist/services/content/worksheet-generation.service.js +95 -0
- package/dist/services/content/worksheet-generation.service.test.d.ts +1 -0
- package/dist/services/content/worksheet-generation.service.test.js +20 -0
- package/dist/services/content/worksheet.service.d.ts +347 -0
- package/dist/services/content/worksheet.service.js +599 -0
- package/dist/services/copilot.service.d.ts +116 -0
- package/dist/services/copilot.service.js +447 -0
- package/dist/services/flashcard-progress.service.d.ts +2 -2
- package/dist/services/flashcard-progress.service.js +3 -2
- package/dist/services/flashcard.service.d.ts +140 -0
- package/dist/services/flashcard.service.js +325 -0
- package/dist/services/invitation.service.d.ts +66 -0
- package/dist/services/invitation.service.js +348 -0
- package/dist/services/knowledge/knowledge-base.service.d.ts +316 -0
- package/dist/services/knowledge/knowledge-base.service.js +544 -0
- package/dist/services/knowledge-base.service.d.ts +316 -0
- package/dist/services/knowledge-base.service.js +536 -0
- package/dist/services/media-analysis.service.d.ts +23 -0
- package/dist/services/media-analysis.service.js +384 -0
- package/dist/services/member.service.d.ts +36 -0
- package/dist/services/member.service.js +193 -0
- package/dist/services/members/invitation.service.d.ts +66 -0
- package/dist/services/members/invitation.service.js +348 -0
- package/dist/services/members/member.service.d.ts +36 -0
- package/dist/services/members/member.service.js +193 -0
- package/dist/services/note.service.d.ts +55 -0
- package/dist/services/note.service.js +111 -0
- package/dist/services/notification.service.d.ts +214 -0
- package/dist/services/notification.service.js +550 -0
- package/dist/services/notification.service.test.d.ts +1 -0
- package/dist/services/notification.service.test.js +87 -0
- package/dist/services/notifications/notification.service.d.ts +214 -0
- package/dist/services/notifications/notification.service.js +550 -0
- package/dist/services/notifications/notification.service.test.d.ts +1 -0
- package/dist/services/notifications/notification.service.test.js +87 -0
- package/dist/services/payment.service.d.ts +55 -0
- package/dist/services/payment.service.js +368 -0
- package/dist/services/podcast.service.d.ts +267 -0
- package/dist/services/podcast.service.js +654 -0
- package/dist/services/router-module.service.d.ts +7 -0
- package/dist/services/router-module.service.js +10 -0
- package/dist/services/study-guide.service.d.ts +18 -0
- package/dist/services/study-guide.service.js +65 -0
- package/dist/services/subscription.service.d.ts +37 -0
- package/dist/services/subscription.service.js +654 -0
- package/dist/services/usage-limit-policy.service.d.ts +12 -0
- package/dist/services/usage-limit-policy.service.js +22 -0
- package/dist/services/usage-limit-policy.service.test.d.ts +1 -0
- package/dist/services/usage-limit-policy.service.test.js +46 -0
- package/dist/services/usage.service.d.ts +27 -0
- package/dist/services/usage.service.js +77 -0
- package/dist/services/worksheet-content.service.d.ts +42 -0
- package/dist/services/worksheet-content.service.js +84 -0
- package/dist/services/worksheet-content.service.test.d.ts +1 -0
- package/dist/services/worksheet-content.service.test.js +69 -0
- package/dist/services/worksheet-generation.service.d.ts +91 -0
- package/dist/services/worksheet-generation.service.js +95 -0
- package/dist/services/worksheet-generation.service.test.d.ts +1 -0
- package/dist/services/worksheet-generation.service.test.js +20 -0
- package/dist/services/worksheet.service.d.ts +385 -0
- package/dist/services/worksheet.service.js +596 -0
- package/dist/services/workspace/workspace-analytics.service.d.ts +24 -0
- package/dist/services/workspace/workspace-analytics.service.js +95 -0
- package/dist/services/workspace/workspace-kb.service.d.ts +40 -0
- package/dist/services/workspace/workspace-kb.service.js +184 -0
- package/dist/services/workspace/workspace.service.d.ts +263 -0
- package/dist/services/workspace/workspace.service.js +401 -0
- package/dist/services/workspace-analytics.service.d.ts +24 -0
- package/dist/services/workspace-analytics.service.js +95 -0
- package/dist/services/workspace-kb.service.d.ts +40 -0
- package/dist/services/workspace-kb.service.js +184 -0
- package/dist/services/workspace-progress.service.d.ts +27 -0
- package/dist/services/workspace-progress.service.js +56 -0
- package/dist/services/workspace-progress.service.test.d.ts +1 -0
- package/dist/services/workspace-progress.service.test.js +49 -0
- package/dist/services/workspace.service.d.ts +307 -0
- package/dist/services/workspace.service.js +390 -0
- package/dist/trpc.d.ts +12 -4
- package/dist/trpc.js +7 -13
- package/package.json +5 -6
- package/prisma/migrations/20260509000001_add_knowledge_base/migration.sql +99 -0
- package/prisma/migrations/20260509000002_curate_knowledge_base/migration.sql +52 -0
- package/prisma/migrations/20260522000000_add_notes/migration.sql +27 -0
- package/prisma/migrations/20260524000000_remove_notes/migration.sql +3 -0
- package/prisma/schema.prisma +150 -48
- package/prisma/seed.mjs +67 -0
- package/scripts/debug/README.md +4 -0
- package/src/README.md +63 -0
- package/src/context.ts +33 -3
- package/src/lib/ai/config.ts +34 -0
- package/src/lib/ai/embedding-client.ts +47 -0
- package/src/lib/ai/index.ts +65 -0
- package/src/lib/ai/inference-backend/client.ts +479 -0
- package/src/lib/ai/inference-backend/mocks.ts +171 -0
- package/src/lib/ai/inference-backend/types.ts +50 -0
- package/src/lib/ai/json-parse.ts +35 -0
- package/src/lib/ai/llm-client.ts +54 -0
- package/src/lib/ai/mock.ts +12 -0
- package/src/lib/ai/types.ts +11 -0
- package/src/lib/chunking.ts +81 -0
- package/src/lib/curated-kb-seed.ts +164 -0
- package/src/lib/email.ts +77 -115
- package/src/lib/embeddings.ts +9 -0
- package/src/lib/ensure-curated-kb-catalog.ts +60 -0
- package/src/lib/env.ts +2 -7
- package/src/lib/inference.ts +1 -21
- package/src/lib/kb-meta.ts +81 -0
- package/src/lib/pdf.ts +23 -0
- package/src/lib/workspace-kb.ts +7 -0
- package/src/repositories/artifact.repository.ts +55 -0
- package/src/repositories/base.repository.ts +19 -0
- package/src/repositories/invitation.repository.ts +53 -0
- package/src/repositories/notification.repository.ts +53 -0
- package/src/repositories/user.repository.ts +44 -0
- package/src/repositories/workspace-member.repository.ts +38 -0
- package/src/repositories/workspace.repository.ts +89 -0
- package/src/routers/_app.ts +4 -0
- package/src/routers/admin.ts +124 -692
- package/src/routers/annotations.ts +25 -203
- package/src/routers/artifactVersions.ts +32 -0
- package/src/routers/auth.ts +82 -520
- package/src/routers/chat.ts +42 -245
- package/src/routers/copilot.ts +41 -666
- package/src/routers/flashcards.ts +108 -404
- package/src/routers/knowledgeBase.ts +216 -0
- package/src/routers/members.ts +60 -782
- package/src/routers/notifications.ts +15 -117
- package/src/routers/payment.ts +37 -446
- package/src/routers/podcast.ts +36 -898
- package/src/routers/studyguide.ts +5 -144
- package/src/routers/worksheets.ts +171 -735
- package/src/routers/workspace.ts +141 -1108
- package/src/scripts/purge-deleted-users.ts +2 -2
- package/src/server.ts +10 -3
- package/src/{lib/activity_human_description.test.ts → services/activity/activity-human-description.service.test.ts} +1 -1
- package/src/{lib/activity_log_service.test.ts → services/activity/activity-log.service.test.ts} +1 -1
- package/src/{lib/activity_log_service.ts → services/activity/activity-log.service.ts} +2 -2
- package/src/services/admin/admin.service.ts +612 -0
- package/src/services/ai/ai-session.service.ts +5 -0
- package/src/services/artifacts/annotation.service.ts +189 -0
- package/src/services/artifacts/artifact-version.service.ts +151 -0
- package/src/services/artifacts/chat.service.ts +197 -0
- package/src/services/artifacts/study-guide.service.ts +72 -0
- package/src/services/auth/auth.service.ts +473 -0
- package/src/services/base.service.ts +19 -0
- package/src/services/billing/payment.service.ts +433 -0
- package/src/{lib/subscription_service.ts → services/billing/subscription.service.ts} +5 -5
- package/src/services/billing/usage.service.ts +207 -0
- package/src/services/content/copilot.service.ts +587 -0
- package/src/services/{flashcard-progress.service.ts → content/flashcard-progress.service.ts} +18 -12
- package/src/services/content/flashcard.service.ts +417 -0
- package/src/services/content/media-analysis.service.ts +561 -0
- package/src/services/content/podcast.service.ts +777 -0
- package/src/services/content/worksheet-content.service.test.ts +83 -0
- package/src/services/content/worksheet-content.service.ts +117 -0
- package/src/{lib/worksheet-generation.test.ts → services/content/worksheet-generation.service.test.ts} +3 -3
- package/src/services/content/worksheet.service.ts +751 -0
- package/src/services/knowledge/knowledge-base.service.ts +705 -0
- package/src/services/members/invitation.service.ts +427 -0
- package/src/services/members/member.service.ts +241 -0
- package/src/{lib/notification-service.test.ts → services/notifications/notification.service.test.ts} +2 -2
- package/src/{lib/notification-service.ts → services/notifications/notification.service.ts} +102 -1
- package/src/services/workspace/workspace-analytics.service.ts +107 -0
- package/src/services/workspace/workspace-kb.service.ts +273 -0
- package/src/services/workspace/workspace.service.ts +488 -0
- package/src/trpc.ts +7 -15
- package/src/lib/ai-session.ts +0 -704
- package/src/lib/usage_service.ts +0 -74
- package/src/lib/workspace-access.ts +0 -13
- /package/{check-difficulty.cjs → scripts/debug/check-difficulty.cjs} +0 -0
- /package/{check-questions.cjs → scripts/debug/check-questions.cjs} +0 -0
- /package/{db-summary.cjs → scripts/debug/db-summary.cjs} +0 -0
- /package/{mcq-test.cjs → scripts/debug/mcq-test.cjs} +0 -0
- /package/{test-generate.js → scripts/debug/test-generate.js} +0 -0
- /package/{test-ratio.cjs → scripts/debug/test-ratio.cjs} +0 -0
- /package/{zod-test.cjs → scripts/debug/zod-test.cjs} +0 -0
- /package/src/{lib/activity_human_description.ts → services/activity/activity-human-description.service.ts} +0 -0
- /package/src/{lib/worksheet-generation.ts → services/content/worksheet-generation.service.ts} +0 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { prisma } from '../lib/prisma.js';
|
|
2
|
+
import { workspace } from '../routers/workspace.js';
|
|
3
|
+
import { RouterModuleRepository } from '../repositories/router-module.repository.js';
|
|
4
|
+
import { RouterModuleService } from '../services/router-module.service.js';
|
|
5
|
+
import { RouterModuleController } from './router-module.controller.js';
|
|
6
|
+
const workspaceRepository = new RouterModuleRepository(prisma, workspace);
|
|
7
|
+
const workspaceService = new RouterModuleService(prisma, workspaceRepository);
|
|
8
|
+
export const workspaceController = new RouterModuleController(workspaceService);
|
|
9
|
+
export const workspaceRouter = workspaceController.getModule();
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { getActivityHumanDescription, getTrpcPathsMatchingDescriptionSearch, } from './activity_human_description.js';
|
|
3
|
+
describe('activity_human_description', () => {
|
|
4
|
+
it('description search resolves paths by label substring', () => {
|
|
5
|
+
const paths = getTrpcPathsMatchingDescriptionSearch('study streak');
|
|
6
|
+
expect(paths).toContain('workspace.getStudyAnalytics');
|
|
7
|
+
});
|
|
8
|
+
it('known path returns curated label', () => {
|
|
9
|
+
expect(getActivityHumanDescription('workspace.getStudyAnalytics')).toBe('Loaded study streak and analytics');
|
|
10
|
+
expect(getActivityHumanDescription('payment.getUsageOverview')).toBe('Viewed plan usage and limits');
|
|
11
|
+
});
|
|
12
|
+
it('unknown path gets formatted fallback', () => {
|
|
13
|
+
const d = getActivityHumanDescription('someRouter.unknownProcedureName');
|
|
14
|
+
expect(d).toContain('Some Router');
|
|
15
|
+
expect(d).toContain('Unknown');
|
|
16
|
+
});
|
|
16
17
|
});
|
|
@@ -1,27 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ActivityLogCategory, ActivityLogStatus } from '@prisma/client';
|
|
3
|
+
import { buildActivityLogWhere, inferCategoryFromTrpcPath, redactSensitive, } from './activity_log_service.js';
|
|
4
|
+
describe('redactSensitive', () => {
|
|
5
|
+
it('redacts sensitive keys', () => {
|
|
6
|
+
const out = redactSensitive({
|
|
7
|
+
email: 'a@b.com',
|
|
8
|
+
password: 'secret',
|
|
9
|
+
nested: { authToken: 't', safe: 1 },
|
|
10
|
+
});
|
|
11
|
+
expect(out.password).toBe('[REDACTED]');
|
|
12
|
+
const nested = out.nested;
|
|
13
|
+
expect(nested.authToken).toBe('[REDACTED]');
|
|
14
|
+
expect(nested.safe).toBe(1);
|
|
10
15
|
});
|
|
11
|
-
assert.equal(out.password, "[REDACTED]");
|
|
12
|
-
const nested = out.nested;
|
|
13
|
-
assert.equal(nested.authToken, "[REDACTED]");
|
|
14
|
-
assert.equal(nested.safe, 1);
|
|
15
16
|
});
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
describe('inferCategoryFromTrpcPath', () => {
|
|
18
|
+
it('maps routers to categories', () => {
|
|
19
|
+
expect(inferCategoryFromTrpcPath('auth.login')).toBe(ActivityLogCategory.AUTH);
|
|
20
|
+
expect(inferCategoryFromTrpcPath('payment.checkout')).toBe(ActivityLogCategory.BILLING);
|
|
21
|
+
expect(inferCategoryFromTrpcPath('admin.listUsers')).toBe(ActivityLogCategory.ADMIN);
|
|
22
|
+
expect(inferCategoryFromTrpcPath('workspace.list')).toBe(ActivityLogCategory.WORKSPACE);
|
|
23
|
+
expect(inferCategoryFromTrpcPath('flashcards.list')).toBe(ActivityLogCategory.CONTENT);
|
|
24
|
+
});
|
|
22
25
|
});
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
describe('buildActivityLogWhere', () => {
|
|
27
|
+
it('forces actor for user scope', () => {
|
|
28
|
+
const w = buildActivityLogWhere({ status: ActivityLogStatus.SUCCESS }, { forceActorUserId: 'user-1' });
|
|
29
|
+
expect(w.actorUserId).toBe('user-1');
|
|
30
|
+
expect(w.status).toBe(ActivityLogStatus.SUCCESS);
|
|
31
|
+
});
|
|
27
32
|
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const aiConfig: {
|
|
2
|
+
readonly llm: {
|
|
3
|
+
readonly apiKey: string | undefined;
|
|
4
|
+
readonly baseUrl: string | undefined;
|
|
5
|
+
readonly model: string;
|
|
6
|
+
};
|
|
7
|
+
readonly embeddings: {
|
|
8
|
+
readonly apiKey: string | undefined;
|
|
9
|
+
readonly baseUrl: undefined;
|
|
10
|
+
readonly model: string;
|
|
11
|
+
readonly dim: number;
|
|
12
|
+
};
|
|
13
|
+
readonly inferenceBackend: {
|
|
14
|
+
readonly url: string | undefined;
|
|
15
|
+
readonly mockEnabled: boolean;
|
|
16
|
+
readonly mockDelayMs: 0 | 10000;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export declare function getInferenceBackendUploadUrl(): string;
|
|
20
|
+
export declare function tryGetInferenceBackendUploadUrl(): string | null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const aiConfig = {
|
|
2
|
+
llm: {
|
|
3
|
+
apiKey: process.env.INFERENCE_API_KEY,
|
|
4
|
+
baseUrl: process.env.INFERENCE_BASE_URL,
|
|
5
|
+
model: process.env.INFERENCE_MODEL ?? 'command-a-03-2025',
|
|
6
|
+
},
|
|
7
|
+
embeddings: {
|
|
8
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
9
|
+
baseUrl: undefined,
|
|
10
|
+
model: process.env.EMBEDDING_MODEL ?? 'text-embedding-3-small',
|
|
11
|
+
dim: Number(process.env.EMBEDDING_DIM ?? 1536),
|
|
12
|
+
},
|
|
13
|
+
inferenceBackend: {
|
|
14
|
+
url: process.env.INFERENCE_API_URL,
|
|
15
|
+
mockEnabled: process.env.DONT_TEST_INFERENCE === 'true',
|
|
16
|
+
mockDelayMs: process.env.DONT_TEST_INFERENCE === 'true' ? 10000 : 0,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
export function getInferenceBackendUploadUrl() {
|
|
20
|
+
const base = aiConfig.inferenceBackend.url;
|
|
21
|
+
if (!base) {
|
|
22
|
+
throw new Error('INFERENCE_API_URL is not configured');
|
|
23
|
+
}
|
|
24
|
+
return `${base.replace(/\/$/, '')}/upload`;
|
|
25
|
+
}
|
|
26
|
+
export function tryGetInferenceBackendUploadUrl() {
|
|
27
|
+
const base = aiConfig.inferenceBackend.url;
|
|
28
|
+
if (!base)
|
|
29
|
+
return null;
|
|
30
|
+
return `${base.replace(/\/$/, '')}/upload`;
|
|
31
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const DEFAULT_EMBEDDING_MODEL: string;
|
|
2
|
+
export declare const DEFAULT_EMBEDDING_DIM: number;
|
|
3
|
+
export interface EmbedOptions {
|
|
4
|
+
model?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function embedTexts(texts: string[], opts?: EmbedOptions): Promise<number[][]>;
|
|
7
|
+
export declare function embedQuery(text: string, opts?: EmbedOptions): Promise<number[]>;
|
|
8
|
+
export declare function toVectorLiteral(vec: number[]): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { aiConfig } from './config.js';
|
|
3
|
+
const client = new OpenAI({
|
|
4
|
+
apiKey: aiConfig.embeddings.apiKey,
|
|
5
|
+
baseURL: aiConfig.embeddings.baseUrl,
|
|
6
|
+
});
|
|
7
|
+
export const DEFAULT_EMBEDDING_MODEL = aiConfig.embeddings.model;
|
|
8
|
+
export const DEFAULT_EMBEDDING_DIM = aiConfig.embeddings.dim;
|
|
9
|
+
export async function embedTexts(texts, opts = {}) {
|
|
10
|
+
const model = opts.model ?? DEFAULT_EMBEDDING_MODEL;
|
|
11
|
+
if (texts.length === 0)
|
|
12
|
+
return [];
|
|
13
|
+
const BATCH = 96;
|
|
14
|
+
const out = new Array(texts.length);
|
|
15
|
+
for (let i = 0; i < texts.length; i += BATCH) {
|
|
16
|
+
const batch = texts.slice(i, i + BATCH);
|
|
17
|
+
const res = await client.embeddings.create({ model, input: batch });
|
|
18
|
+
res.data.forEach((row, j) => {
|
|
19
|
+
out[i + j] = row.embedding;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
export async function embedQuery(text, opts = {}) {
|
|
25
|
+
const [vec] = await embedTexts([text], opts);
|
|
26
|
+
return vec;
|
|
27
|
+
}
|
|
28
|
+
export function toVectorLiteral(vec) {
|
|
29
|
+
return `[${vec.map((n) => (Number.isFinite(n) ? n : 0)).join(',')}]`;
|
|
30
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { aiConfig } from './config.js';
|
|
2
|
+
import { complete, completeText, streamComplete } from './llm-client.js';
|
|
3
|
+
import { DEFAULT_EMBEDDING_DIM, DEFAULT_EMBEDDING_MODEL, embedQuery, embedTexts, toVectorLiteral } from './embedding-client.js';
|
|
4
|
+
import { extractJson, parseJsonField } from './json-parse.js';
|
|
5
|
+
import { isAiMockMode, mockDelay } from './mock.js';
|
|
6
|
+
import { inferenceBackend } from './inference-backend/client.js';
|
|
7
|
+
export declare const ai: {
|
|
8
|
+
config: {
|
|
9
|
+
readonly llm: {
|
|
10
|
+
readonly apiKey: string | undefined;
|
|
11
|
+
readonly baseUrl: string | undefined;
|
|
12
|
+
readonly model: string;
|
|
13
|
+
};
|
|
14
|
+
readonly embeddings: {
|
|
15
|
+
readonly apiKey: string | undefined;
|
|
16
|
+
readonly baseUrl: undefined;
|
|
17
|
+
readonly model: string;
|
|
18
|
+
readonly dim: number;
|
|
19
|
+
};
|
|
20
|
+
readonly inferenceBackend: {
|
|
21
|
+
readonly url: string | undefined;
|
|
22
|
+
readonly mockEnabled: boolean;
|
|
23
|
+
readonly mockDelayMs: 0 | 10000;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
isMockMode: typeof isAiMockMode;
|
|
27
|
+
mockDelay: typeof mockDelay;
|
|
28
|
+
llm: {
|
|
29
|
+
complete: typeof complete;
|
|
30
|
+
completeText: typeof completeText;
|
|
31
|
+
streamComplete: typeof streamComplete;
|
|
32
|
+
};
|
|
33
|
+
embed: {
|
|
34
|
+
texts: typeof embedTexts;
|
|
35
|
+
query: typeof embedQuery;
|
|
36
|
+
toVectorLiteral: typeof toVectorLiteral;
|
|
37
|
+
DEFAULT_MODEL: string;
|
|
38
|
+
DEFAULT_DIM: number;
|
|
39
|
+
};
|
|
40
|
+
backend: import("./inference-backend/client.js").InferenceBackendClient;
|
|
41
|
+
parse: {
|
|
42
|
+
extractJson: typeof extractJson;
|
|
43
|
+
parseJsonField: typeof parseJsonField;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
export type { ChatMessage, ChatRole, CompleteOptions } from './types.js';
|
|
47
|
+
export type { AISession, ProcessFileResult, PodcastSpeaker, StudyGuideSegment, WorksheetGenerationOptions, } from './inference-backend/types.js';
|
|
48
|
+
export { aiConfig, complete, completeText, streamComplete, DEFAULT_EMBEDDING_DIM, DEFAULT_EMBEDDING_MODEL, embedQuery, embedTexts, extractJson, inferenceBackend, isAiMockMode, mockDelay, parseJsonField, toVectorLiteral, };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { aiConfig } from './config.js';
|
|
2
|
+
import { complete, completeText, streamComplete, } from './llm-client.js';
|
|
3
|
+
import { DEFAULT_EMBEDDING_DIM, DEFAULT_EMBEDDING_MODEL, embedQuery, embedTexts, toVectorLiteral, } from './embedding-client.js';
|
|
4
|
+
import { extractJson, parseJsonField } from './json-parse.js';
|
|
5
|
+
import { isAiMockMode, mockDelay } from './mock.js';
|
|
6
|
+
import { inferenceBackend } from './inference-backend/client.js';
|
|
7
|
+
export const ai = {
|
|
8
|
+
config: aiConfig,
|
|
9
|
+
isMockMode: isAiMockMode,
|
|
10
|
+
mockDelay,
|
|
11
|
+
llm: {
|
|
12
|
+
complete,
|
|
13
|
+
completeText,
|
|
14
|
+
streamComplete,
|
|
15
|
+
},
|
|
16
|
+
embed: {
|
|
17
|
+
texts: embedTexts,
|
|
18
|
+
query: embedQuery,
|
|
19
|
+
toVectorLiteral,
|
|
20
|
+
DEFAULT_MODEL: DEFAULT_EMBEDDING_MODEL,
|
|
21
|
+
DEFAULT_DIM: DEFAULT_EMBEDDING_DIM,
|
|
22
|
+
},
|
|
23
|
+
backend: inferenceBackend,
|
|
24
|
+
parse: {
|
|
25
|
+
extractJson,
|
|
26
|
+
parseJsonField,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
export { aiConfig, complete, completeText, streamComplete, DEFAULT_EMBEDDING_DIM, DEFAULT_EMBEDDING_MODEL, embedQuery, embedTexts, extractJson, inferenceBackend, isAiMockMode, mockDelay, parseJsonField, toVectorLiteral, };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { MarkScheme, UserMarkScheme } from '../../../types/index.js';
|
|
2
|
+
import type { AISession, PodcastSpeaker, ProcessFileResult, StudyGuideSegment, WorksheetGenerationOptions, BackendGenerationOptions } from './types.js';
|
|
3
|
+
export declare class InferenceBackendClient {
|
|
4
|
+
private sessions;
|
|
5
|
+
private get uploadUrl();
|
|
6
|
+
private postCommand;
|
|
7
|
+
private buildForm;
|
|
8
|
+
private withRag;
|
|
9
|
+
initSession(workspaceId: string, user: string): Promise<AISession>;
|
|
10
|
+
processFile(sessionId: string, _user: string, fileUrl: string, fileType: 'image' | 'pdf', maxPages?: number): Promise<ProcessFileResult>;
|
|
11
|
+
generateStudyGuide(sessionId: string, user: string, opts?: BackendGenerationOptions): Promise<string>;
|
|
12
|
+
generateFlashcardQuestions(sessionId: string, user: string, numQuestions: number, difficulty: 'easy' | 'medium' | 'hard', prompt?: string, opts?: BackendGenerationOptions): Promise<string>;
|
|
13
|
+
generateWorksheetQuestions(sessionId: string, user: string, numQuestions: number, difficulty: 'EASY' | 'MEDIUM' | 'HARD', options?: WorksheetGenerationOptions): Promise<string>;
|
|
14
|
+
checkWorksheetQuestions(sessionId: string, user: string, question: string, answer: string, markScheme: MarkScheme): Promise<UserMarkScheme>;
|
|
15
|
+
generatePodcastStructure(sessionId: string, user: string, title: string, description: string, prompt: string, speakers: PodcastSpeaker[]): Promise<any>;
|
|
16
|
+
generatePodcastAudioFromText(sessionId: string, user: string, podcastId: string, segmentIndex: number, text: string, speakers: PodcastSpeaker[], voiceId?: string): Promise<any>;
|
|
17
|
+
generatePodcastImage(sessionId: string, user: string, summary: string): Promise<string>;
|
|
18
|
+
segmentStudyGuide(sessionId: string, user: string, studyGuide: string): Promise<StudyGuideSegment[]>;
|
|
19
|
+
validateSegmentSummary(sessionId: string, user: string, segmentContent: string, studentResponse: string, studyGuide: string): Promise<{
|
|
20
|
+
valid: boolean;
|
|
21
|
+
feedback: string;
|
|
22
|
+
}>;
|
|
23
|
+
getSession(sessionId: string): AISession | undefined;
|
|
24
|
+
getSessionsByUserAndWorkspace(_userId: string, workspaceId: string): AISession[];
|
|
25
|
+
deleteSession(sessionId: string): boolean;
|
|
26
|
+
checkHealth(): Promise<boolean>;
|
|
27
|
+
}
|
|
28
|
+
export declare const inferenceBackend: InferenceBackendClient;
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { TRPCError } from '@trpc/server';
|
|
2
|
+
import { getInferenceBackendUploadUrl, aiConfig, tryGetInferenceBackendUploadUrl } from '../config.js';
|
|
3
|
+
import { isAiMockMode, mockDelay } from '../mock.js';
|
|
4
|
+
import { parseJsonField } from '../json-parse.js';
|
|
5
|
+
import { withRetry } from '../../retry.js';
|
|
6
|
+
import { logger } from '../../logger.js';
|
|
7
|
+
import { mockFlashcards, mockPodcastAudio, mockPodcastStructure, mockProcessFile, mockSegmentSummaryValidation, mockStudyGuide, mockStudyGuideSegmentation, mockWorksheet, } from './mocks.js';
|
|
8
|
+
export class InferenceBackendClient {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.sessions = new Map();
|
|
11
|
+
}
|
|
12
|
+
get uploadUrl() {
|
|
13
|
+
return getInferenceBackendUploadUrl();
|
|
14
|
+
}
|
|
15
|
+
async postCommand(formData, options) {
|
|
16
|
+
const { label, timeoutMs = 300000, retries = 3 } = options;
|
|
17
|
+
return withRetry(async () => {
|
|
18
|
+
const controller = new AbortController();
|
|
19
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
20
|
+
try {
|
|
21
|
+
const response = await fetch(this.uploadUrl, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
body: formData,
|
|
24
|
+
signal: controller.signal,
|
|
25
|
+
});
|
|
26
|
+
clearTimeout(timeoutId);
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
const errorText = await response.text().catch(() => '');
|
|
29
|
+
throw new Error(`AI backend error (${response.status}): ${errorText || response.statusText}`);
|
|
30
|
+
}
|
|
31
|
+
return (await response.json());
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
clearTimeout(timeoutId);
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}, { maxRetries: retries, timeoutMs, label });
|
|
38
|
+
}
|
|
39
|
+
buildForm(command, fields = {}) {
|
|
40
|
+
const formData = new FormData();
|
|
41
|
+
formData.append('command', command);
|
|
42
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
43
|
+
formData.append(key, value);
|
|
44
|
+
}
|
|
45
|
+
return formData;
|
|
46
|
+
}
|
|
47
|
+
withRag(fields, opts) {
|
|
48
|
+
if (opts?.ragContext?.trim()) {
|
|
49
|
+
return { ...fields, rag_context: opts.ragContext.trim() };
|
|
50
|
+
}
|
|
51
|
+
return fields;
|
|
52
|
+
}
|
|
53
|
+
async initSession(workspaceId, user) {
|
|
54
|
+
await mockDelay();
|
|
55
|
+
const sessionId = workspaceId;
|
|
56
|
+
if (isAiMockMode()) {
|
|
57
|
+
logger.info(`[ai] mock initSession workspace=${workspaceId}`);
|
|
58
|
+
const session = {
|
|
59
|
+
id: sessionId,
|
|
60
|
+
workspaceId,
|
|
61
|
+
status: 'initialized',
|
|
62
|
+
files: [],
|
|
63
|
+
createdAt: new Date(),
|
|
64
|
+
updatedAt: new Date(),
|
|
65
|
+
};
|
|
66
|
+
this.sessions.set(sessionId, session);
|
|
67
|
+
return session;
|
|
68
|
+
}
|
|
69
|
+
const result = await this.postCommand(this.buildForm('init_session', { session: sessionId, user }), { label: 'initSession' });
|
|
70
|
+
if (!result.message) {
|
|
71
|
+
throw new TRPCError({
|
|
72
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
73
|
+
message: 'AI backend returned no session message',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const session = {
|
|
77
|
+
id: sessionId,
|
|
78
|
+
workspaceId,
|
|
79
|
+
status: 'initialized',
|
|
80
|
+
files: [],
|
|
81
|
+
createdAt: new Date(),
|
|
82
|
+
updatedAt: new Date(),
|
|
83
|
+
};
|
|
84
|
+
this.sessions.set(sessionId, session);
|
|
85
|
+
return session;
|
|
86
|
+
}
|
|
87
|
+
async processFile(sessionId, _user, fileUrl, fileType, maxPages) {
|
|
88
|
+
await mockDelay();
|
|
89
|
+
if (isAiMockMode()) {
|
|
90
|
+
logger.info(`[ai] mock processFile session=${sessionId} type=${fileType}`);
|
|
91
|
+
return mockProcessFile(fileType);
|
|
92
|
+
}
|
|
93
|
+
const fields = { fileUrl, fileType };
|
|
94
|
+
if (maxPages)
|
|
95
|
+
fields.maxPages = maxPages.toString();
|
|
96
|
+
try {
|
|
97
|
+
const result = await this.postCommand(this.buildForm('process_file', fields), { label: 'processFile' });
|
|
98
|
+
if (result.status === 'error') {
|
|
99
|
+
throw new Error(result.error || 'File processing failed');
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
105
|
+
logger.error(`[ai] processFile failed: ${message}`);
|
|
106
|
+
return {
|
|
107
|
+
status: 'error',
|
|
108
|
+
textContent: null,
|
|
109
|
+
imageDescriptions: [],
|
|
110
|
+
comprehensiveDescription: null,
|
|
111
|
+
pageCount: 0,
|
|
112
|
+
error: message,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async generateStudyGuide(sessionId, user, opts) {
|
|
117
|
+
await mockDelay();
|
|
118
|
+
if (isAiMockMode()) {
|
|
119
|
+
logger.info(`[ai] mock generateStudyGuide session=${sessionId}`);
|
|
120
|
+
return mockStudyGuide();
|
|
121
|
+
}
|
|
122
|
+
const result = await this.postCommand(this.buildForm('generate_study_guide', this.withRag({ session: sessionId, user }, opts)), { label: 'generateStudyGuide' });
|
|
123
|
+
if (!result.markdown) {
|
|
124
|
+
throw new Error('AI backend returned empty study guide');
|
|
125
|
+
}
|
|
126
|
+
return result.markdown;
|
|
127
|
+
}
|
|
128
|
+
async generateFlashcardQuestions(sessionId, user, numQuestions, difficulty, prompt, opts) {
|
|
129
|
+
await mockDelay();
|
|
130
|
+
if (isAiMockMode()) {
|
|
131
|
+
logger.info(`[ai] mock generateFlashcardQuestions session=${sessionId}`);
|
|
132
|
+
return JSON.stringify(mockFlashcards(numQuestions, difficulty));
|
|
133
|
+
}
|
|
134
|
+
const fields = {
|
|
135
|
+
session: sessionId,
|
|
136
|
+
user,
|
|
137
|
+
num_questions: numQuestions.toString(),
|
|
138
|
+
difficulty,
|
|
139
|
+
};
|
|
140
|
+
if (prompt)
|
|
141
|
+
fields.prompt = prompt;
|
|
142
|
+
const result = await this.postCommand(this.buildForm('generate_flashcard_questions', this.withRag(fields, opts)), { label: 'generateFlashcardQuestions' });
|
|
143
|
+
const parsed = JSON.parse(result.flashcards);
|
|
144
|
+
if (Array.isArray(parsed))
|
|
145
|
+
return JSON.stringify(parsed);
|
|
146
|
+
return JSON.stringify(parsed.flashcards ?? []);
|
|
147
|
+
}
|
|
148
|
+
async generateWorksheetQuestions(sessionId, user, numQuestions, difficulty, options) {
|
|
149
|
+
await mockDelay();
|
|
150
|
+
if (isAiMockMode()) {
|
|
151
|
+
logger.info(`[ai] mock generateWorksheetQuestions session=${sessionId}`);
|
|
152
|
+
return JSON.stringify(mockWorksheet(numQuestions, difficulty, options));
|
|
153
|
+
}
|
|
154
|
+
const fields = {
|
|
155
|
+
session: sessionId,
|
|
156
|
+
user,
|
|
157
|
+
num_questions: numQuestions.toString(),
|
|
158
|
+
difficulty,
|
|
159
|
+
mode: options?.mode ?? 'practice',
|
|
160
|
+
};
|
|
161
|
+
if (options?.mcqRatio !== undefined) {
|
|
162
|
+
fields.mcq_ratio = String(options.mcqRatio);
|
|
163
|
+
}
|
|
164
|
+
if (options?.questionTypes?.length) {
|
|
165
|
+
fields.question_types = JSON.stringify(options.questionTypes);
|
|
166
|
+
}
|
|
167
|
+
if (options?.prompt) {
|
|
168
|
+
fields.worksheet_prompt = options.prompt;
|
|
169
|
+
}
|
|
170
|
+
const result = await this.postCommand(this.buildForm('generate_worksheet_questions', this.withRag(fields, { ragContext: options?.ragContext })), { label: 'generateWorksheetQuestions' });
|
|
171
|
+
return result.worksheet;
|
|
172
|
+
}
|
|
173
|
+
async checkWorksheetQuestions(sessionId, user, question, answer, markScheme) {
|
|
174
|
+
const result = await this.postCommand(this.buildForm('mark_worksheet_questions', {
|
|
175
|
+
session: sessionId,
|
|
176
|
+
user,
|
|
177
|
+
question,
|
|
178
|
+
answer,
|
|
179
|
+
mark_scheme: JSON.stringify(markScheme),
|
|
180
|
+
}), { label: 'checkWorksheetQuestions', retries: 2 });
|
|
181
|
+
return parseJsonField(result.marking, 'marking');
|
|
182
|
+
}
|
|
183
|
+
async generatePodcastStructure(sessionId, user, title, description, prompt, speakers) {
|
|
184
|
+
await mockDelay();
|
|
185
|
+
if (isAiMockMode()) {
|
|
186
|
+
logger.info(`[ai] mock generatePodcastStructure session=${sessionId}`);
|
|
187
|
+
return mockPodcastStructure(title, speakers);
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
return await this.postCommand(this.buildForm('generate_podcast_structure', {
|
|
191
|
+
user,
|
|
192
|
+
session: sessionId,
|
|
193
|
+
title,
|
|
194
|
+
description,
|
|
195
|
+
prompt,
|
|
196
|
+
speakers: JSON.stringify(speakers),
|
|
197
|
+
}), { label: 'generatePodcastStructure' });
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
throw new TRPCError({
|
|
201
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
202
|
+
message: `Failed to generate podcast structure: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async generatePodcastAudioFromText(sessionId, user, podcastId, segmentIndex, text, speakers, voiceId) {
|
|
207
|
+
await mockDelay();
|
|
208
|
+
if (isAiMockMode()) {
|
|
209
|
+
logger.info(`[ai] mock generatePodcastAudio session=${sessionId}`);
|
|
210
|
+
return mockPodcastAudio(user, sessionId, podcastId, segmentIndex, text);
|
|
211
|
+
}
|
|
212
|
+
const fields = {
|
|
213
|
+
user,
|
|
214
|
+
session: sessionId,
|
|
215
|
+
podcast_id: podcastId,
|
|
216
|
+
segment_index: segmentIndex.toString(),
|
|
217
|
+
text,
|
|
218
|
+
speakers: JSON.stringify(speakers),
|
|
219
|
+
};
|
|
220
|
+
if (voiceId)
|
|
221
|
+
fields.voice_id = voiceId;
|
|
222
|
+
try {
|
|
223
|
+
return await this.postCommand(this.buildForm('generate_podcast_audio_from_text', fields), { label: 'generatePodcastAudioFromText' });
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
throw new TRPCError({
|
|
227
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
228
|
+
message: `Failed to generate podcast audio: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async generatePodcastImage(sessionId, user, summary) {
|
|
233
|
+
const result = await this.postCommand(this.buildForm('generate_podcast_image', {
|
|
234
|
+
session: sessionId,
|
|
235
|
+
user,
|
|
236
|
+
summary,
|
|
237
|
+
}), { label: 'generatePodcastImage' });
|
|
238
|
+
return result.image_key;
|
|
239
|
+
}
|
|
240
|
+
async segmentStudyGuide(sessionId, user, studyGuide) {
|
|
241
|
+
if (isAiMockMode()) {
|
|
242
|
+
return mockStudyGuideSegmentation();
|
|
243
|
+
}
|
|
244
|
+
const result = await this.postCommand(this.buildForm('generate_study_guide_segmentation', {
|
|
245
|
+
session: sessionId,
|
|
246
|
+
user,
|
|
247
|
+
study_guide: studyGuide,
|
|
248
|
+
}), { label: 'segmentStudyGuide' });
|
|
249
|
+
return result.segmentation;
|
|
250
|
+
}
|
|
251
|
+
async validateSegmentSummary(sessionId, user, segmentContent, studentResponse, studyGuide) {
|
|
252
|
+
if (isAiMockMode()) {
|
|
253
|
+
return mockSegmentSummaryValidation();
|
|
254
|
+
}
|
|
255
|
+
const result = await this.postCommand(this.buildForm('validate_segment_summary', {
|
|
256
|
+
session: sessionId,
|
|
257
|
+
user,
|
|
258
|
+
segment_content: segmentContent,
|
|
259
|
+
student_response: studentResponse,
|
|
260
|
+
study_guide: studyGuide,
|
|
261
|
+
}), { label: 'validateSegmentSummary' });
|
|
262
|
+
return result.feedback;
|
|
263
|
+
}
|
|
264
|
+
getSession(sessionId) {
|
|
265
|
+
return this.sessions.get(sessionId);
|
|
266
|
+
}
|
|
267
|
+
getSessionsByUserAndWorkspace(_userId, workspaceId) {
|
|
268
|
+
return Array.from(this.sessions.values()).filter((session) => session.workspaceId === workspaceId);
|
|
269
|
+
}
|
|
270
|
+
deleteSession(sessionId) {
|
|
271
|
+
return this.sessions.delete(sessionId);
|
|
272
|
+
}
|
|
273
|
+
async checkHealth() {
|
|
274
|
+
await mockDelay();
|
|
275
|
+
if (isAiMockMode()) {
|
|
276
|
+
logger.info('[ai] mock health check');
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
const uploadUrl = tryGetInferenceBackendUploadUrl();
|
|
280
|
+
if (!uploadUrl)
|
|
281
|
+
return false;
|
|
282
|
+
try {
|
|
283
|
+
const response = await fetch(uploadUrl, {
|
|
284
|
+
method: 'POST',
|
|
285
|
+
body: new FormData(),
|
|
286
|
+
});
|
|
287
|
+
return response.ok;
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
logger.error('[ai] health check failed', error);
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
export const inferenceBackend = new InferenceBackendClient();
|
|
296
|
+
if (aiConfig.inferenceBackend.url) {
|
|
297
|
+
logger.info(`[ai] inference backend: ${aiConfig.inferenceBackend.url}`);
|
|
298
|
+
}
|
|
299
|
+
if (isAiMockMode()) {
|
|
300
|
+
logger.info('[ai] mock mode enabled (DONT_TEST_INFERENCE=true)');
|
|
301
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ProcessFileResult, PodcastSpeaker, StudyGuideSegment, WorksheetGenerationOptions } from './types.js';
|
|
2
|
+
export declare function mockStudyGuide(): string;
|
|
3
|
+
export declare function mockFlashcards(numQuestions: number, difficulty: 'easy' | 'medium' | 'hard'): unknown[];
|
|
4
|
+
export declare function mockWorksheet(numQuestions: number, difficulty: 'EASY' | 'MEDIUM' | 'HARD', options?: WorksheetGenerationOptions): object;
|
|
5
|
+
export declare function mockProcessFile(fileType: 'image' | 'pdf'): ProcessFileResult;
|
|
6
|
+
export declare function mockPodcastStructure(title: string, speakers: PodcastSpeaker[]): object;
|
|
7
|
+
export declare function mockPodcastAudio(user: string, sessionId: string, podcastId: string, segmentIndex: number, text: string): object;
|
|
8
|
+
export declare function mockStudyGuideSegmentation(): StudyGuideSegment[];
|
|
9
|
+
export declare function mockSegmentSummaryValidation(): {
|
|
10
|
+
valid: boolean;
|
|
11
|
+
feedback: string;
|
|
12
|
+
};
|