vidspotai-shared 1.0.82-dev.0 → 1.0.83
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/lib/globals/aiModels/enums.d.ts +11 -1
- package/lib/globals/aiModels/enums.d.ts.map +1 -1
- package/lib/globals/aiModels/enums.js +24 -1
- package/lib/globals/aiModels/index.d.ts.map +1 -1
- package/lib/globals/aiModels/index.js +2 -0
- package/lib/globals/aiModels/providers/anthropic.d.ts +12 -0
- package/lib/globals/aiModels/providers/anthropic.d.ts.map +1 -0
- package/lib/globals/aiModels/providers/anthropic.js +88 -0
- package/lib/globals/aiModels/providers/google.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/google.js +115 -10
- package/lib/globals/aiModels/providers/openai.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/openai.js +63 -4
- package/lib/globals/aiModels/tierHelpers.d.ts +12 -0
- package/lib/globals/aiModels/tierHelpers.d.ts.map +1 -1
- package/lib/globals/aiModels/tierHelpers.js +83 -0
- package/lib/globals/aiModels/types.d.ts +19 -0
- package/lib/globals/aiModels/types.d.ts.map +1 -1
- package/lib/globals/types.d.ts +120 -1
- package/lib/globals/types.d.ts.map +1 -1
- package/lib/globals/types.js +135 -1
- package/lib/models/agent.model.d.ts +41 -1
- package/lib/models/agent.model.d.ts.map +1 -1
- package/lib/models/index.d.ts +1 -0
- package/lib/models/index.d.ts.map +1 -1
- package/lib/models/index.js +1 -0
- package/lib/models/social.model.d.ts +180 -0
- package/lib/models/social.model.d.ts.map +1 -0
- package/lib/models/social.model.js +2 -0
- package/lib/models/user.model.d.ts +10 -0
- package/lib/models/user.model.d.ts.map +1 -1
- package/lib/models/video.model.d.ts +6 -0
- package/lib/models/video.model.d.ts.map +1 -1
- package/lib/schemas/brief.schema.d.ts +46 -0
- package/lib/schemas/brief.schema.d.ts.map +1 -1
- package/lib/schemas/brief.schema.js +72 -1
- package/lib/schemas/index.d.ts +1 -0
- package/lib/schemas/index.d.ts.map +1 -1
- package/lib/schemas/index.js +1 -0
- package/lib/schemas/project.schema.d.ts +70 -3
- package/lib/schemas/project.schema.d.ts.map +1 -1
- package/lib/schemas/project.schema.js +12 -0
- package/lib/schemas/social.schema.d.ts +91 -0
- package/lib/schemas/social.schema.d.ts.map +1 -0
- package/lib/schemas/social.schema.js +114 -0
- package/lib/schemas/videoPlan.schema.d.ts +117 -3
- package/lib/schemas/videoPlan.schema.d.ts.map +1 -1
- package/lib/schemas/videoPlan.schema.js +141 -1
- package/lib/services/agent/chatAgent.d.ts +25 -1
- package/lib/services/agent/chatAgent.d.ts.map +1 -1
- package/lib/services/agent/chatAgent.js +145 -9
- package/lib/services/agent/costPreflight.d.ts +11 -1
- package/lib/services/agent/costPreflight.d.ts.map +1 -1
- package/lib/services/agent/costPreflight.js +18 -1
- package/lib/services/agent/covers/coverPlanner.d.ts +41 -0
- package/lib/services/agent/covers/coverPlanner.d.ts.map +1 -0
- package/lib/services/agent/covers/coverPlanner.js +278 -0
- package/lib/services/agent/covers/covers.schema.d.ts +158 -0
- package/lib/services/agent/covers/covers.schema.d.ts.map +1 -0
- package/lib/services/agent/covers/covers.schema.js +166 -0
- package/lib/services/agent/covers/index.d.ts +3 -0
- package/lib/services/agent/covers/index.d.ts.map +1 -0
- package/lib/services/agent/covers/index.js +18 -0
- package/lib/services/agent/critic.d.ts +10 -0
- package/lib/services/agent/critic.d.ts.map +1 -1
- package/lib/services/agent/critic.js +37 -1
- package/lib/services/agent/editClassifier.d.ts +4 -4
- package/lib/services/agent/editClassifier.js +2 -2
- package/lib/services/agent/editExisting/editAssembler.d.ts +78 -0
- package/lib/services/agent/editExisting/editAssembler.d.ts.map +1 -0
- package/lib/services/agent/editExisting/editAssembler.js +172 -0
- package/lib/services/agent/editExisting/editExisting.schema.d.ts +119 -0
- package/lib/services/agent/editExisting/editExisting.schema.d.ts.map +1 -0
- package/lib/services/agent/editExisting/editExisting.schema.js +157 -0
- package/lib/services/agent/editExisting/highlightPicker.d.ts +48 -0
- package/lib/services/agent/editExisting/highlightPicker.d.ts.map +1 -0
- package/lib/services/agent/editExisting/highlightPicker.js +199 -0
- package/lib/services/agent/editExisting/index.d.ts +4 -0
- package/lib/services/agent/editExisting/index.d.ts.map +1 -0
- package/lib/services/agent/editExisting/index.js +19 -0
- package/lib/services/agent/eval/recorder.d.ts +13 -1
- package/lib/services/agent/eval/recorder.d.ts.map +1 -1
- package/lib/services/agent/eval/recorder.js +59 -0
- package/lib/services/agent/eval/seedBriefs.d.ts +4 -3
- package/lib/services/agent/eval/seedBriefs.d.ts.map +1 -1
- package/lib/services/agent/eval/seedBriefs.js +283 -3
- package/lib/services/agent/eval/types.d.ts +10 -0
- package/lib/services/agent/eval/types.d.ts.map +1 -1
- package/lib/services/agent/executor/core.d.ts +70 -0
- package/lib/services/agent/executor/core.d.ts.map +1 -0
- package/lib/services/agent/executor/core.js +250 -0
- package/lib/services/agent/executor/duration.d.ts +20 -0
- package/lib/services/agent/executor/duration.d.ts.map +1 -0
- package/lib/services/agent/executor/duration.js +46 -0
- package/lib/services/agent/executor/index.d.ts +15 -0
- package/lib/services/agent/executor/index.d.ts.map +1 -0
- package/lib/services/agent/executor/index.js +32 -0
- package/lib/services/agent/executor/types.d.ts +183 -0
- package/lib/services/agent/executor/types.d.ts.map +1 -0
- package/lib/services/agent/executor/types.js +29 -0
- package/lib/services/agent/executor/visual.d.ts +32 -0
- package/lib/services/agent/executor/visual.d.ts.map +1 -0
- package/lib/services/agent/executor/visual.js +400 -0
- package/lib/services/agent/executor/voice.d.ts +17 -0
- package/lib/services/agent/executor/voice.d.ts.map +1 -0
- package/lib/services/agent/executor/voice.js +119 -0
- package/lib/services/agent/extendChain.d.ts +101 -0
- package/lib/services/agent/extendChain.d.ts.map +1 -0
- package/lib/services/agent/extendChain.js +177 -0
- package/lib/services/agent/index.d.ts +11 -1
- package/lib/services/agent/index.d.ts.map +1 -1
- package/lib/services/agent/index.js +11 -1
- package/lib/services/agent/llmCaller.d.ts +7 -8
- package/lib/services/agent/llmCaller.d.ts.map +1 -1
- package/lib/services/agent/llmCallerAnthropic.d.ts +44 -31
- package/lib/services/agent/llmCallerAnthropic.d.ts.map +1 -1
- package/lib/services/agent/llmCallerAnthropic.js +135 -60
- package/lib/services/agent/llmCallerFactory.d.ts +34 -0
- package/lib/services/agent/llmCallerFactory.d.ts.map +1 -0
- package/lib/services/agent/llmCallerFactory.js +31 -0
- package/lib/services/agent/llmCallerGemini.d.ts +62 -0
- package/lib/services/agent/llmCallerGemini.d.ts.map +1 -0
- package/lib/services/agent/llmCallerGemini.js +235 -0
- package/lib/services/agent/llmCallerOpenai.d.ts +56 -0
- package/lib/services/agent/llmCallerOpenai.d.ts.map +1 -0
- package/lib/services/agent/llmCallerOpenai.js +230 -0
- package/lib/services/agent/llmCallerRegistry.d.ts.map +1 -1
- package/lib/services/agent/llmCallerRegistry.js +7 -7
- package/lib/services/agent/llmCallerRouting.d.ts +63 -0
- package/lib/services/agent/llmCallerRouting.d.ts.map +1 -0
- package/lib/services/agent/llmCallerRouting.js +124 -0
- package/lib/services/agent/llmModelRegistry.d.ts +59 -0
- package/lib/services/agent/llmModelRegistry.d.ts.map +1 -0
- package/lib/services/agent/llmModelRegistry.js +168 -0
- package/lib/services/agent/llmRetry.d.ts +57 -0
- package/lib/services/agent/llmRetry.d.ts.map +1 -0
- package/lib/services/agent/llmRetry.js +102 -0
- package/lib/services/agent/modelRouter.d.ts +3 -3
- package/lib/services/agent/modelRouter.d.ts.map +1 -1
- package/lib/services/agent/modelRouter.js +27 -13
- package/lib/services/agent/planMutations.d.ts +54 -1
- package/lib/services/agent/planMutations.d.ts.map +1 -1
- package/lib/services/agent/planMutations.js +78 -0
- package/lib/services/agent/planner/Planner.d.ts +0 -17
- package/lib/services/agent/planner/Planner.d.ts.map +1 -1
- package/lib/services/agent/planner/Planner.js +67 -303
- package/lib/services/agent/planner/overlayRegen.d.ts +38 -0
- package/lib/services/agent/planner/overlayRegen.d.ts.map +1 -0
- package/lib/services/agent/planner/overlayRegen.js +145 -0
- package/lib/services/agent/planner/plannerMessages.d.ts +34 -0
- package/lib/services/agent/planner/plannerMessages.d.ts.map +1 -0
- package/lib/services/agent/planner/plannerMessages.js +185 -0
- package/lib/services/agent/planner/promptSections.d.ts +12 -0
- package/lib/services/agent/planner/promptSections.d.ts.map +1 -1
- package/lib/services/agent/planner/promptSections.js +57 -0
- package/lib/services/agent/planner/scriptFirstPlanner.d.ts +35 -0
- package/lib/services/agent/planner/scriptFirstPlanner.d.ts.map +1 -0
- package/lib/services/agent/planner/scriptFirstPlanner.js +140 -0
- package/lib/services/agent/planner/structuralRules.d.ts +10 -0
- package/lib/services/agent/planner/structuralRules.d.ts.map +1 -1
- package/lib/services/agent/planner/structuralRules.js +92 -9
- package/lib/services/agent/planner/validators.d.ts +18 -0
- package/lib/services/agent/planner/validators.d.ts.map +1 -1
- package/lib/services/agent/planner/validators.js +97 -0
- package/lib/services/agent/planner.d.ts +2 -1
- package/lib/services/agent/planner.d.ts.map +1 -1
- package/lib/services/agent/planner.js +5 -1
- package/lib/services/agent/priorProject.d.ts +26 -0
- package/lib/services/agent/priorProject.d.ts.map +1 -0
- package/lib/services/agent/priorProject.js +51 -0
- package/lib/services/agent/providerFallback/chains.d.ts.map +1 -1
- package/lib/services/agent/providerFallback/chains.js +27 -15
- package/lib/services/agent/repurpose/index.d.ts +3 -0
- package/lib/services/agent/repurpose/index.d.ts.map +1 -0
- package/lib/services/agent/repurpose/index.js +18 -0
- package/lib/services/agent/repurpose/repurpose.schema.d.ts +132 -0
- package/lib/services/agent/repurpose/repurpose.schema.d.ts.map +1 -0
- package/lib/services/agent/repurpose/repurpose.schema.js +144 -0
- package/lib/services/agent/repurpose/shortsPicker.d.ts +25 -0
- package/lib/services/agent/repurpose/shortsPicker.d.ts.map +1 -0
- package/lib/services/agent/repurpose/shortsPicker.js +218 -0
- package/lib/services/agent/runHelpers.d.ts +21 -2
- package/lib/services/agent/runHelpers.d.ts.map +1 -1
- package/lib/services/agent/runHelpers.js +71 -2
- package/lib/services/agent/tools/animateImage.tool.d.ts +1 -0
- package/lib/services/agent/tools/animateImage.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/animateImage.tool.js +12 -0
- package/lib/services/agent/tools/chapterOutline.tool.d.ts +42 -0
- package/lib/services/agent/tools/chapterOutline.tool.d.ts.map +1 -0
- package/lib/services/agent/tools/chapterOutline.tool.js +115 -0
- package/lib/services/agent/tools/composeScene.tool.d.ts +65 -2
- package/lib/services/agent/tools/composeScene.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/estimateCost.tool.d.ts +28 -1
- package/lib/services/agent/tools/estimateCost.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/estimateCost.tool.js +55 -7
- package/lib/services/agent/tools/extendVideo.tool.d.ts +26 -0
- package/lib/services/agent/tools/extendVideo.tool.d.ts.map +1 -0
- package/lib/services/agent/tools/extendVideo.tool.js +149 -0
- package/lib/services/agent/tools/generateScript.tool.d.ts +184 -0
- package/lib/services/agent/tools/generateScript.tool.d.ts.map +1 -0
- package/lib/services/agent/tools/generateScript.tool.js +123 -0
- package/lib/services/agent/tools/generateVideo.tool.d.ts +1 -0
- package/lib/services/agent/tools/generateVideo.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/generateVideo.tool.js +20 -1
- package/lib/services/agent/tools/index.d.ts +4 -0
- package/lib/services/agent/tools/index.d.ts.map +1 -1
- package/lib/services/agent/tools/index.js +4 -0
- package/lib/services/agent/tools/matchBrollToScript.tool.d.ts +50 -0
- package/lib/services/agent/tools/matchBrollToScript.tool.d.ts.map +1 -0
- package/lib/services/agent/tools/matchBrollToScript.tool.js +139 -0
- package/lib/services/agent/tools/planVideo.tool.d.ts +57 -1
- package/lib/services/agent/tools/planVideo.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/planVideo.tool.js +3 -3
- package/lib/services/agent/tools/render.tool.d.ts +22 -1
- package/lib/services/agent/tools/render.tool.d.ts.map +1 -1
- package/lib/services/aiGen/aiGenFactory.service.d.ts.map +1 -1
- package/lib/services/aiGen/aiGenFactory.service.js +18 -3
- package/lib/services/aiGen/helpers.d.ts +8 -0
- package/lib/services/aiGen/helpers.d.ts.map +1 -1
- package/lib/services/aiGen/helpers.js +12 -0
- package/lib/services/aiGen/providers/anthropic/anthropic.service.d.ts +26 -0
- package/lib/services/aiGen/providers/anthropic/anthropic.service.d.ts.map +1 -0
- package/lib/services/aiGen/providers/anthropic/anthropic.service.js +95 -0
- package/lib/services/aiGen/providers/google/google.service.d.ts +25 -1
- package/lib/services/aiGen/providers/google/google.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/google/google.service.js +136 -237
- package/lib/services/aiGen/providers/google/googleApiKeys.d.ts +71 -0
- package/lib/services/aiGen/providers/google/googleApiKeys.d.ts.map +1 -0
- package/lib/services/aiGen/providers/google/googleApiKeys.js +137 -0
- package/lib/services/aiGen/providers/google/googleErrors.d.ts +13 -0
- package/lib/services/aiGen/providers/google/googleErrors.d.ts.map +1 -0
- package/lib/services/aiGen/providers/google/googleErrors.js +102 -0
- package/lib/services/aiGen/providers/google/googleFetch.d.ts +8 -0
- package/lib/services/aiGen/providers/google/googleFetch.d.ts.map +1 -0
- package/lib/services/aiGen/providers/google/googleFetch.js +96 -0
- package/lib/services/aiGen/providers/google/googleKeyPool.d.ts +52 -0
- package/lib/services/aiGen/providers/google/googleKeyPool.d.ts.map +1 -0
- package/lib/services/aiGen/providers/google/googleKeyPool.js +129 -0
- package/lib/services/aiGen/providers/google/googleMusic.d.ts +15 -0
- package/lib/services/aiGen/providers/google/googleMusic.d.ts.map +1 -0
- package/lib/services/aiGen/providers/google/googleMusic.js +77 -0
- package/lib/services/aiGen/providers/kling/kling.service.d.ts +7 -3
- package/lib/services/aiGen/providers/kling/kling.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/kling/kling.service.js +23 -367
- package/lib/services/aiGen/providers/kling/klingCredits.d.ts +9 -0
- package/lib/services/aiGen/providers/kling/klingCredits.d.ts.map +1 -0
- package/lib/services/aiGen/providers/kling/klingCredits.js +63 -0
- package/lib/services/aiGen/providers/kling/klingRequests.d.ts +32 -0
- package/lib/services/aiGen/providers/kling/klingRequests.d.ts.map +1 -0
- package/lib/services/aiGen/providers/kling/klingRequests.js +194 -0
- package/lib/services/aiGen/providers/kling/klingStatus.d.ts +16 -0
- package/lib/services/aiGen/providers/kling/klingStatus.d.ts.map +1 -0
- package/lib/services/aiGen/providers/kling/klingStatus.js +173 -0
- package/lib/services/aiGen/providers/pixverse/pixverse.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/pixverse/pixverse.service.js +7 -1
- package/lib/services/bullmq.service.d.ts +61 -0
- package/lib/services/bullmq.service.d.ts.map +1 -1
- package/lib/services/bullmq.service.js +124 -2
- package/lib/services/crypto/index.d.ts +2 -0
- package/lib/services/crypto/index.d.ts.map +1 -0
- package/lib/services/crypto/index.js +17 -0
- package/lib/services/crypto/tokenVault.d.ts +47 -0
- package/lib/services/crypto/tokenVault.d.ts.map +1 -0
- package/lib/services/crypto/tokenVault.js +179 -0
- package/lib/services/editor/captionStyleHint.d.ts +3 -0
- package/lib/services/editor/captionStyleHint.d.ts.map +1 -0
- package/lib/services/editor/captionStyleHint.js +112 -0
- package/lib/services/editor/planToProject.d.ts +7 -66
- package/lib/services/editor/planToProject.d.ts.map +1 -1
- package/lib/services/editor/planToProject.helpers.d.ts +40 -0
- package/lib/services/editor/planToProject.helpers.d.ts.map +1 -0
- package/lib/services/editor/planToProject.helpers.js +177 -0
- package/lib/services/editor/planToProject.js +197 -180
- package/lib/services/editor/planToProject.types.d.ts +94 -0
- package/lib/services/editor/planToProject.types.d.ts.map +1 -0
- package/lib/services/editor/planToProject.types.js +2 -0
- package/lib/services/firestore.service.d.ts +5 -0
- package/lib/services/firestore.service.d.ts.map +1 -1
- package/lib/services/firestore.service.js +13 -0
- package/lib/services/index.d.ts +13 -0
- package/lib/services/index.d.ts.map +1 -1
- package/lib/services/index.js +13 -0
- package/lib/services/promptEnhancer/index.d.ts +18 -0
- package/lib/services/promptEnhancer/index.d.ts.map +1 -0
- package/lib/services/promptEnhancer/index.js +33 -0
- package/lib/services/promptEnhancer/models.d.ts +54 -0
- package/lib/services/promptEnhancer/models.d.ts.map +1 -0
- package/lib/services/promptEnhancer/models.js +37 -0
- package/lib/services/promptEnhancer/profiles/agent.profile.d.ts +14 -0
- package/lib/services/promptEnhancer/profiles/agent.profile.d.ts.map +1 -0
- package/lib/services/promptEnhancer/profiles/agent.profile.js +40 -0
- package/lib/services/promptEnhancer/profiles/avatar.profile.d.ts +13 -0
- package/lib/services/promptEnhancer/profiles/avatar.profile.d.ts.map +1 -0
- package/lib/services/promptEnhancer/profiles/avatar.profile.js +40 -0
- package/lib/services/promptEnhancer/profiles/base.d.ts +28 -0
- package/lib/services/promptEnhancer/profiles/base.d.ts.map +1 -0
- package/lib/services/promptEnhancer/profiles/base.js +35 -0
- package/lib/services/promptEnhancer/profiles/image.profile.d.ts +11 -0
- package/lib/services/promptEnhancer/profiles/image.profile.d.ts.map +1 -0
- package/lib/services/promptEnhancer/profiles/image.profile.js +42 -0
- package/lib/services/promptEnhancer/profiles/index.d.ts +12 -0
- package/lib/services/promptEnhancer/profiles/index.d.ts.map +1 -0
- package/lib/services/promptEnhancer/profiles/index.js +33 -0
- package/lib/services/promptEnhancer/profiles/video.profile.d.ts +15 -0
- package/lib/services/promptEnhancer/profiles/video.profile.d.ts.map +1 -0
- package/lib/services/promptEnhancer/profiles/video.profile.js +81 -0
- package/lib/services/promptEnhancer/promptEnhancer.service.d.ts +45 -0
- package/lib/services/promptEnhancer/promptEnhancer.service.d.ts.map +1 -0
- package/lib/services/promptEnhancer/promptEnhancer.service.js +157 -0
- package/lib/services/promptEnhancer/schema.d.ts +19 -0
- package/lib/services/promptEnhancer/schema.d.ts.map +1 -0
- package/lib/services/promptEnhancer/schema.js +43 -0
- package/lib/services/promptEnhancer/types.d.ts +112 -0
- package/lib/services/promptEnhancer/types.d.ts.map +1 -0
- package/lib/services/promptEnhancer/types.js +2 -0
- package/lib/services/rateLimiter/distributedRateLimiter.service.d.ts +60 -5
- package/lib/services/rateLimiter/distributedRateLimiter.service.d.ts.map +1 -1
- package/lib/services/rateLimiter/distributedRateLimiter.service.js +184 -16
- package/lib/services/socialAI/captionGen.d.ts +81 -0
- package/lib/services/socialAI/captionGen.d.ts.map +1 -0
- package/lib/services/socialAI/captionGen.js +206 -0
- package/lib/services/socialAI/hookScore.d.ts +85 -0
- package/lib/services/socialAI/hookScore.d.ts.map +1 -0
- package/lib/services/socialAI/hookScore.js +170 -0
- package/lib/services/socialAI/index.d.ts +3 -0
- package/lib/services/socialAI/index.d.ts.map +1 -0
- package/lib/services/socialAI/index.js +18 -0
- package/lib/services/socialAccounts/index.d.ts +2 -0
- package/lib/services/socialAccounts/index.d.ts.map +1 -0
- package/lib/services/socialAccounts/index.js +17 -0
- package/lib/services/socialAccounts/socialAccountService.d.ts +25 -0
- package/lib/services/socialAccounts/socialAccountService.d.ts.map +1 -0
- package/lib/services/socialAccounts/socialAccountService.js +105 -0
- package/lib/services/socialEngage/factory.d.ts +7 -0
- package/lib/services/socialEngage/factory.d.ts.map +1 -0
- package/lib/services/socialEngage/factory.js +25 -0
- package/lib/services/socialEngage/index.d.ts +6 -0
- package/lib/services/socialEngage/index.d.ts.map +1 -0
- package/lib/services/socialEngage/index.js +21 -0
- package/lib/services/socialEngage/meta.engage.d.ts +17 -0
- package/lib/services/socialEngage/meta.engage.d.ts.map +1 -0
- package/lib/services/socialEngage/meta.engage.js +128 -0
- package/lib/services/socialEngage/metaWebhook.d.ts +50 -0
- package/lib/services/socialEngage/metaWebhook.d.ts.map +1 -0
- package/lib/services/socialEngage/metaWebhook.js +93 -0
- package/lib/services/socialEngage/types.d.ts +72 -0
- package/lib/services/socialEngage/types.d.ts.map +1 -0
- package/lib/services/socialEngage/types.js +10 -0
- package/lib/services/socialEngage/youtube.engage.d.ts +9 -0
- package/lib/services/socialEngage/youtube.engage.d.ts.map +1 -0
- package/lib/services/socialEngage/youtube.engage.js +87 -0
- package/lib/services/socialFormat/aspectGeometry.d.ts +74 -0
- package/lib/services/socialFormat/aspectGeometry.d.ts.map +1 -0
- package/lib/services/socialFormat/aspectGeometry.js +135 -0
- package/lib/services/socialFormat/index.d.ts +2 -0
- package/lib/services/socialFormat/index.d.ts.map +1 -0
- package/lib/services/socialFormat/index.js +19 -0
- package/lib/services/socialInsights/index.d.ts +3 -0
- package/lib/services/socialInsights/index.d.ts.map +1 -0
- package/lib/services/socialInsights/index.js +18 -0
- package/lib/services/socialInsights/recommendations.d.ts +131 -0
- package/lib/services/socialInsights/recommendations.d.ts.map +1 -0
- package/lib/services/socialInsights/recommendations.js +277 -0
- package/lib/services/socialInsights/timeBuckets.d.ts +35 -0
- package/lib/services/socialInsights/timeBuckets.d.ts.map +1 -0
- package/lib/services/socialInsights/timeBuckets.js +78 -0
- package/lib/services/socialMetrics/factory.d.ts +5 -0
- package/lib/services/socialMetrics/factory.d.ts.map +1 -0
- package/lib/services/socialMetrics/factory.js +24 -0
- package/lib/services/socialMetrics/index.d.ts +6 -0
- package/lib/services/socialMetrics/index.d.ts.map +1 -0
- package/lib/services/socialMetrics/index.js +21 -0
- package/lib/services/socialMetrics/meta.metrics.d.ts +22 -0
- package/lib/services/socialMetrics/meta.metrics.d.ts.map +1 -0
- package/lib/services/socialMetrics/meta.metrics.js +137 -0
- package/lib/services/socialMetrics/tiktok.metrics.d.ts +8 -0
- package/lib/services/socialMetrics/tiktok.metrics.d.ts.map +1 -0
- package/lib/services/socialMetrics/tiktok.metrics.js +43 -0
- package/lib/services/socialMetrics/types.d.ts +54 -0
- package/lib/services/socialMetrics/types.d.ts.map +1 -0
- package/lib/services/socialMetrics/types.js +2 -0
- package/lib/services/socialMetrics/youtube.metrics.d.ts +8 -0
- package/lib/services/socialMetrics/youtube.metrics.d.ts.map +1 -0
- package/lib/services/socialMetrics/youtube.metrics.js +43 -0
- package/lib/services/socialOAuth/factory.d.ts +7 -0
- package/lib/services/socialOAuth/factory.d.ts.map +1 -0
- package/lib/services/socialOAuth/factory.js +42 -0
- package/lib/services/socialOAuth/index.d.ts +11 -0
- package/lib/services/socialOAuth/index.d.ts.map +1 -0
- package/lib/services/socialOAuth/index.js +26 -0
- package/lib/services/socialOAuth/linkedin.oauth.d.ts +14 -0
- package/lib/services/socialOAuth/linkedin.oauth.d.ts.map +1 -0
- package/lib/services/socialOAuth/linkedin.oauth.js +127 -0
- package/lib/services/socialOAuth/meta.oauth.d.ts +31 -0
- package/lib/services/socialOAuth/meta.oauth.d.ts.map +1 -0
- package/lib/services/socialOAuth/meta.oauth.js +214 -0
- package/lib/services/socialOAuth/oauthState.d.ts +14 -0
- package/lib/services/socialOAuth/oauthState.d.ts.map +1 -0
- package/lib/services/socialOAuth/oauthState.js +66 -0
- package/lib/services/socialOAuth/pinterest.oauth.d.ts +15 -0
- package/lib/services/socialOAuth/pinterest.oauth.d.ts.map +1 -0
- package/lib/services/socialOAuth/pinterest.oauth.js +126 -0
- package/lib/services/socialOAuth/threads.oauth.d.ts +14 -0
- package/lib/services/socialOAuth/threads.oauth.d.ts.map +1 -0
- package/lib/services/socialOAuth/threads.oauth.js +129 -0
- package/lib/services/socialOAuth/tiktok.oauth.d.ts +15 -0
- package/lib/services/socialOAuth/tiktok.oauth.d.ts.map +1 -0
- package/lib/services/socialOAuth/tiktok.oauth.js +151 -0
- package/lib/services/socialOAuth/types.d.ts +67 -0
- package/lib/services/socialOAuth/types.d.ts.map +1 -0
- package/lib/services/socialOAuth/types.js +2 -0
- package/lib/services/socialOAuth/x.oauth.d.ts +17 -0
- package/lib/services/socialOAuth/x.oauth.d.ts.map +1 -0
- package/lib/services/socialOAuth/x.oauth.js +134 -0
- package/lib/services/socialOAuth/youtube.oauth.d.ts +15 -0
- package/lib/services/socialOAuth/youtube.oauth.d.ts.map +1 -0
- package/lib/services/socialOAuth/youtube.oauth.js +156 -0
- package/lib/services/socialPublish/factory.d.ts +5 -0
- package/lib/services/socialPublish/factory.d.ts.map +1 -0
- package/lib/services/socialPublish/factory.js +32 -0
- package/lib/services/socialPublish/index.d.ts +10 -0
- package/lib/services/socialPublish/index.d.ts.map +1 -0
- package/lib/services/socialPublish/index.js +25 -0
- package/lib/services/socialPublish/linkedin.publish.d.ts +9 -0
- package/lib/services/socialPublish/linkedin.publish.d.ts.map +1 -0
- package/lib/services/socialPublish/linkedin.publish.js +143 -0
- package/lib/services/socialPublish/meta.publish.d.ts +28 -0
- package/lib/services/socialPublish/meta.publish.d.ts.map +1 -0
- package/lib/services/socialPublish/meta.publish.js +149 -0
- package/lib/services/socialPublish/pinterest.publish.d.ts +13 -0
- package/lib/services/socialPublish/pinterest.publish.d.ts.map +1 -0
- package/lib/services/socialPublish/pinterest.publish.js +130 -0
- package/lib/services/socialPublish/threads.publish.d.ts +12 -0
- package/lib/services/socialPublish/threads.publish.d.ts.map +1 -0
- package/lib/services/socialPublish/threads.publish.js +96 -0
- package/lib/services/socialPublish/tiktok.publish.d.ts +13 -0
- package/lib/services/socialPublish/tiktok.publish.d.ts.map +1 -0
- package/lib/services/socialPublish/tiktok.publish.js +118 -0
- package/lib/services/socialPublish/types.d.ts +47 -0
- package/lib/services/socialPublish/types.d.ts.map +1 -0
- package/lib/services/socialPublish/types.js +2 -0
- package/lib/services/socialPublish/x.publish.d.ts +12 -0
- package/lib/services/socialPublish/x.publish.d.ts.map +1 -0
- package/lib/services/socialPublish/x.publish.js +147 -0
- package/lib/services/socialPublish/youtube.publish.d.ts +9 -0
- package/lib/services/socialPublish/youtube.publish.d.ts.map +1 -0
- package/lib/services/socialPublish/youtube.publish.js +107 -0
- package/lib/services/stock/index.d.ts +2 -0
- package/lib/services/stock/index.d.ts.map +1 -0
- package/lib/services/stock/index.js +17 -0
- package/lib/services/stock/realPersonSafety.d.ts +99 -0
- package/lib/services/stock/realPersonSafety.d.ts.map +1 -0
- package/lib/services/stock/realPersonSafety.js +248 -0
- package/lib/services/translation/index.d.ts +2 -0
- package/lib/services/translation/index.d.ts.map +1 -0
- package/lib/services/translation/index.js +9 -0
- package/lib/services/translation/translation.service.d.ts +50 -0
- package/lib/services/translation/translation.service.d.ts.map +1 -0
- package/lib/services/translation/translation.service.js +211 -0
- package/lib/utils/helpers.d.ts +2 -4
- package/lib/utils/helpers.d.ts.map +1 -1
- package/lib/utils/helpers.js +9 -63
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.d.ts.map +1 -1
- package/lib/utils/index.js +1 -0
- package/lib/utils/renderTier.d.ts +26 -0
- package/lib/utils/renderTier.d.ts.map +1 -0
- package/lib/utils/renderTier.js +34 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"distributedRateLimiter.service.d.ts","sourceRoot":"","sources":["../../../src/services/rateLimiter/distributedRateLimiter.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAErE,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"distributedRateLimiter.service.d.ts","sourceRoot":"","sources":["../../../src/services/rateLimiter/distributedRateLimiter.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAErE,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAM5D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,SAAU,CAAC;AAC9C,eAAO,MAAM,mBAAmB,SAAU,CAAC;AAQ3C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,UAAU,4BAA4B;IACpC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,eAAe,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,SAAS,EAAE,OAAO,CAAC;IACnB,yDAAyD;IACzD,cAAc,EAAE,OAAO,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AA4DD,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAErC,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,SAAS,CAAyB;gBAE9B,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B;IAOlE;;;;;;;;;;;;OAYG;IACG,kBAAkB,CAAC,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAUtE;;;;;;;;OAQG;IACG,4BAA4B,IAAI,OAAO,CAAC,IAAI,CAAC;IAInD;;;;;OAKG;IACG,eAAe,CACnB,mBAAmB,GAAE,MAA+B,GACnD,OAAO,CAAC,eAAe,CAAC;IAgF3B,gEAAgE;IAChE,WAAW,IAAI,IAAI;IAInB,IAAI,qBAAqB,IAAI,MAAM,CAElC;IAEK,QAAQ,IAAI,OAAO,CAAC,eAAe,CAAC;YAoC5B,iBAAiB;IAc/B,OAAO,CAAC,iBAAiB;YAQX,iBAAiB;CAoHhC;AAwBD,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,WAAW,GACpB,sBAAsB,CA0BxB;AAED,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,WAAW,GACpB,sBAAsB,CAexB;AAED;4DAC4D;AAC5D,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAM1E"}
|
|
@@ -3,15 +3,30 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.DistributedRateLimiter = void 0;
|
|
6
|
+
exports.DistributedRateLimiter = exports.DAILY_NEAR_RESET_MS = exports.MAX_ACCEPTABLE_WAIT_MS = void 0;
|
|
7
7
|
exports.getAiGenModelRateLimiter = getAiGenModelRateLimiter;
|
|
8
8
|
exports.getTtsProviderRateLimiter = getTtsProviderRateLimiter;
|
|
9
9
|
exports.snapshotAllRateLimiters = snapshotAllRateLimiters;
|
|
10
10
|
const crypto_1 = __importDefault(require("crypto"));
|
|
11
11
|
const aiModels_1 = require("../../globals/aiModels");
|
|
12
12
|
const ttsModels_1 = require("../../globals/ttsModels");
|
|
13
|
+
const googleApiKeys_1 = require("../aiGen/providers/google/googleApiKeys");
|
|
14
|
+
const errors_1 = require("../../utils/errors");
|
|
13
15
|
const logger_1 = require("../../utils/logger");
|
|
14
16
|
const redis_service_1 = require("../redis.service");
|
|
17
|
+
/**
|
|
18
|
+
* Capacity-planning thresholds (used by previewCapacity + the bounded gate).
|
|
19
|
+
*
|
|
20
|
+
* MAX_ACCEPTABLE_WAIT_MS — a model whose projected wait for a submit slot
|
|
21
|
+
* exceeds this is considered "not available" by previewCapacity, so the
|
|
22
|
+
* job-start selector spills to a less-loaded model in the same tier chain.
|
|
23
|
+
* DAILY_NEAR_RESET_MS — when a model's per-day cap is exhausted, we only wait
|
|
24
|
+
* it out if the UTC-midnight reset is within this window; otherwise the
|
|
25
|
+
* bounded gate fast-fails (VIDEO_PROVIDER_RATE_LIMITED) so the caller can
|
|
26
|
+
* fall back rather than block for hours.
|
|
27
|
+
*/
|
|
28
|
+
exports.MAX_ACCEPTABLE_WAIT_MS = 180000; // 3 min
|
|
29
|
+
exports.DAILY_NEAR_RESET_MS = 120000; // 2 min
|
|
15
30
|
// Module-level throttle so Redis outages don't fire a Slack error every
|
|
16
31
|
// request. First failure goes through; subsequent failures within the
|
|
17
32
|
// window log at warn level (Console + Loki only) until the window resets.
|
|
@@ -62,9 +77,14 @@ end
|
|
|
62
77
|
|
|
63
78
|
redis.call('ZADD', minKey, nowMs, uniqueId)
|
|
64
79
|
redis.call('PEXPIRE', minKey, 90000)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
80
|
+
-- Only touch the day counter when a day limit applies. With dayLimit <= 0
|
|
81
|
+
-- (skipDay poll path, or a model with no per-day cap) we must NOT increment —
|
|
82
|
+
-- otherwise polls would burn the daily SUBMIT budget they're meant to bypass.
|
|
83
|
+
if dayLimit > 0 then
|
|
84
|
+
local newDay = redis.call('INCR', dayKey)
|
|
85
|
+
if newDay == 1 then
|
|
86
|
+
redis.call('EXPIRE', dayKey, secsTilMidnight)
|
|
87
|
+
end
|
|
68
88
|
end
|
|
69
89
|
|
|
70
90
|
return { 1, 0, 'ok' }
|
|
@@ -83,11 +103,18 @@ class DistributedRateLimiter {
|
|
|
83
103
|
* a slot in each. Caller MUST call releaseSlot() when the in-flight task
|
|
84
104
|
* settles — per-min / per-day auto-expire but the concurrent counter does
|
|
85
105
|
* not. Mirrors the old AiGenModelRateLimiter API.
|
|
106
|
+
*
|
|
107
|
+
* `opts.maxWaitMs` bounds the rate-budget wait: if satisfying the per-day /
|
|
108
|
+
* per-minute gate would take longer than this, the call throws a
|
|
109
|
+
* `UserFacingError(VIDEO_PROVIDER_RATE_LIMITED)` instead of sleep-looping for
|
|
110
|
+
* hours. This is the backstop that stops jobs from piling up in-process when
|
|
111
|
+
* a daily cap is hit — the submit path passes it; legacy callers that omit
|
|
112
|
+
* it keep the original (unbounded) behavior.
|
|
86
113
|
*/
|
|
87
|
-
async waitUntilAvailable() {
|
|
114
|
+
async waitUntilAvailable(opts) {
|
|
88
115
|
await this.acquireConcurrent();
|
|
89
116
|
try {
|
|
90
|
-
await this.consumeRedisGates();
|
|
117
|
+
await this.consumeRedisGates({ maxWaitMs: opts?.maxWaitMs });
|
|
91
118
|
}
|
|
92
119
|
catch (err) {
|
|
93
120
|
this.releaseConcurrent();
|
|
@@ -95,13 +122,99 @@ class DistributedRateLimiter {
|
|
|
95
122
|
}
|
|
96
123
|
}
|
|
97
124
|
/**
|
|
98
|
-
* Bumps per-
|
|
99
|
-
* Use for short-lived calls that share a
|
|
100
|
-
*
|
|
101
|
-
*
|
|
125
|
+
* Bumps per-MINUTE only — does NOT count toward the concurrent cap and does
|
|
126
|
+
* NOT consume/await the per-day gate. Use for short-lived calls that share a
|
|
127
|
+
* provider's per-minute budget but must not be blocked by the daily SUBMIT
|
|
128
|
+
* cap: status polls of already-running tasks (failing a poll on daily quota
|
|
129
|
+
* would abandon a video that's already generating), plus sync image/TTS gens
|
|
130
|
+
* (which have no per-day limit configured anyway). No release needed — Redis
|
|
131
|
+
* auto-expires the counters.
|
|
102
132
|
*/
|
|
103
133
|
async waitUntilAvailableForOneShot() {
|
|
104
|
-
await this.consumeRedisGates();
|
|
134
|
+
await this.consumeRedisGates({ skipDay: true });
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Read-only capacity estimate — does NOT consume a slot. Reads current
|
|
138
|
+
* per-minute / per-day usage and concurrency pressure from Redis and
|
|
139
|
+
* projects how long a new submission would wait. Fails open (available:true,
|
|
140
|
+
* wait:0) on Redis errors / no client, matching the gate's fail-open policy.
|
|
141
|
+
*/
|
|
142
|
+
async previewCapacity(maxAcceptableWaitMs = exports.MAX_ACCEPTABLE_WAIT_MS) {
|
|
143
|
+
const secsUntilDayReset = secsUntilMidnight();
|
|
144
|
+
const base = {
|
|
145
|
+
modelKey: this.modelKey,
|
|
146
|
+
perMinLimit: this.perMinLimit,
|
|
147
|
+
perDayLimit: this.perDayLimit,
|
|
148
|
+
concurrentLimit: this.concurrentLimit,
|
|
149
|
+
concurrentActive: this.activeRequests,
|
|
150
|
+
concurrentQueueDepth: this.waitQueue.length,
|
|
151
|
+
secsUntilDayReset,
|
|
152
|
+
};
|
|
153
|
+
const failOpen = {
|
|
154
|
+
...base,
|
|
155
|
+
perMinUsed: 0,
|
|
156
|
+
perDayUsed: 0,
|
|
157
|
+
projectedWaitMs: 0,
|
|
158
|
+
available: true,
|
|
159
|
+
dailyExhausted: false,
|
|
160
|
+
};
|
|
161
|
+
const client = redis_service_1.redis.getClient();
|
|
162
|
+
if (!client)
|
|
163
|
+
return failOpen;
|
|
164
|
+
const minKey = `ratelimit:${this.modelKey}:min`;
|
|
165
|
+
const dayKey = `ratelimit:${this.modelKey}:day:${utcDateKey()}`;
|
|
166
|
+
let perMinUsed = 0;
|
|
167
|
+
let perDayUsed = 0;
|
|
168
|
+
try {
|
|
169
|
+
const now = Date.now();
|
|
170
|
+
const [minCount, dayRaw] = await Promise.all([
|
|
171
|
+
client.zcount(minKey, now - 60000, "+inf"),
|
|
172
|
+
client.get(dayKey),
|
|
173
|
+
]);
|
|
174
|
+
perMinUsed = minCount;
|
|
175
|
+
perDayUsed = dayRaw ? Number(dayRaw) : 0;
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
logger_1.logger.warn("distributedRateLimiter: previewCapacity read failed, assuming available", {
|
|
179
|
+
modelKey: this.modelKey,
|
|
180
|
+
err: err instanceof Error ? err.message : String(err),
|
|
181
|
+
});
|
|
182
|
+
return failOpen;
|
|
183
|
+
}
|
|
184
|
+
const dailyExhausted = this.perDayLimit > 0 && perDayUsed >= this.perDayLimit;
|
|
185
|
+
// Project the wait for a new submit slot.
|
|
186
|
+
let projectedWaitMs = 0;
|
|
187
|
+
if (dailyExhausted) {
|
|
188
|
+
projectedWaitMs = secsUntilDayReset * 1000;
|
|
189
|
+
}
|
|
190
|
+
else if (this.perMinLimit > 0) {
|
|
191
|
+
// Requests already queued on the concurrency gate are "ahead" of a new
|
|
192
|
+
// arrival competing for per-minute slots. Estimate how many 60s windows
|
|
193
|
+
// it takes to drain them past the per-minute throughput.
|
|
194
|
+
const pendingAhead = this.waitQueue.length;
|
|
195
|
+
const freeThisWindow = Math.max(0, this.perMinLimit - perMinUsed);
|
|
196
|
+
if (pendingAhead < freeThisWindow) {
|
|
197
|
+
projectedWaitMs = 0;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
const slotsNeeded = pendingAhead - freeThisWindow + 1;
|
|
201
|
+
const windowsNeeded = Math.ceil(slotsNeeded / this.perMinLimit);
|
|
202
|
+
projectedWaitMs = windowsNeeded * 60000;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// No per-minute gate (perMinLimit === 0): wait is concurrency-only; those
|
|
206
|
+
// models churn fast, so we treat backlog as drainable (projectedWaitMs 0).
|
|
207
|
+
const available = dailyExhausted
|
|
208
|
+
? secsUntilDayReset * 1000 <= exports.DAILY_NEAR_RESET_MS
|
|
209
|
+
: projectedWaitMs <= maxAcceptableWaitMs;
|
|
210
|
+
return {
|
|
211
|
+
...base,
|
|
212
|
+
perMinUsed,
|
|
213
|
+
perDayUsed,
|
|
214
|
+
projectedWaitMs,
|
|
215
|
+
available,
|
|
216
|
+
dailyExhausted,
|
|
217
|
+
};
|
|
105
218
|
}
|
|
106
219
|
/** Free a concurrent slot acquired via waitUntilAvailable(). */
|
|
107
220
|
releaseSlot() {
|
|
@@ -165,8 +278,11 @@ class DistributedRateLimiter {
|
|
|
165
278
|
if (next)
|
|
166
279
|
next();
|
|
167
280
|
}
|
|
168
|
-
async consumeRedisGates() {
|
|
169
|
-
|
|
281
|
+
async consumeRedisGates(opts) {
|
|
282
|
+
// skipDay ignores the per-day cap entirely (poll path) — see
|
|
283
|
+
// waitUntilAvailableForOneShot.
|
|
284
|
+
const effectiveDayLimit = opts?.skipDay ? 0 : this.perDayLimit;
|
|
285
|
+
if (!this.perMinLimit && !effectiveDayLimit)
|
|
170
286
|
return;
|
|
171
287
|
const client = redis_service_1.redis.getClient();
|
|
172
288
|
if (!client) {
|
|
@@ -179,6 +295,8 @@ class DistributedRateLimiter {
|
|
|
179
295
|
const minKey = `ratelimit:${this.modelKey}:min`;
|
|
180
296
|
const secsTilMidnight = secsUntilMidnight();
|
|
181
297
|
const uniqueId = `${Date.now()}-${crypto_1.default.randomBytes(4).toString("hex")}`;
|
|
298
|
+
const startedAt = Date.now();
|
|
299
|
+
const maxWaitMs = opts?.maxWaitMs;
|
|
182
300
|
while (true) {
|
|
183
301
|
const nowMs = Date.now();
|
|
184
302
|
// Day key resolved per-iteration so a retry across midnight UTC
|
|
@@ -186,7 +304,7 @@ class DistributedRateLimiter {
|
|
|
186
304
|
const dayKey = `ratelimit:${this.modelKey}:day:${utcDateKey()}`;
|
|
187
305
|
let result;
|
|
188
306
|
try {
|
|
189
|
-
result = (await client.eval(CONSUME_SCRIPT, 2, minKey, dayKey, String(nowMs), String(this.perMinLimit), String(
|
|
307
|
+
result = (await client.eval(CONSUME_SCRIPT, 2, minKey, dayKey, String(nowMs), String(this.perMinLimit), String(effectiveDayLimit), String(secsTilMidnight), uniqueId));
|
|
190
308
|
}
|
|
191
309
|
catch (err) {
|
|
192
310
|
// Redis blip — fail open rather than blocking the pipeline. Throttle
|
|
@@ -210,6 +328,43 @@ class DistributedRateLimiter {
|
|
|
210
328
|
}
|
|
211
329
|
if (result[0] === 1)
|
|
212
330
|
return;
|
|
331
|
+
// Bounded-wait backstop. Without it, a per-day-exhausted gate sleep-loops
|
|
332
|
+
// (60s cap per iteration) until UTC midnight — jobs pile up in-process,
|
|
333
|
+
// hold a worker + concurrency slot, and block graceful shutdown. When the
|
|
334
|
+
// caller passes maxWaitMs, fast-fail instead so the scene can fall back to
|
|
335
|
+
// another model (or surface a clean rate-limit error).
|
|
336
|
+
if (maxWaitMs != null) {
|
|
337
|
+
const reason = result[2];
|
|
338
|
+
// A 'day' block's TRUE wait is until midnight (result[1] is 60s-capped
|
|
339
|
+
// for sleep granularity, not the real ETA) — decide against the real ETA.
|
|
340
|
+
const trueWaitMs = reason === "day" ? secsTilMidnight * 1000 : Date.now() - startedAt + result[1];
|
|
341
|
+
if (trueWaitMs > maxWaitMs) {
|
|
342
|
+
// This is the terminal case: no fallback model had headroom either, so
|
|
343
|
+
// the scene fails with a clean rate-limit error. logger.error → Slack
|
|
344
|
+
// (error-only transport). Model + provider + cause live in the MESSAGE
|
|
345
|
+
// because the Slack dedup key is the message's first 80 chars — so each
|
|
346
|
+
// model fails its own alert (deduped to ~1/5min) and `day` vs `min`
|
|
347
|
+
// causes don't collapse into each other. This + the spill alert are
|
|
348
|
+
// the only signals Ammar gets that a provider is chronically over
|
|
349
|
+
// quota and needs more capacity. Provider-agnostic (every model + TTS).
|
|
350
|
+
const provider = this.modelKey.startsWith("tts:")
|
|
351
|
+
? this.modelKey.slice(4)
|
|
352
|
+
: this.modelKey.split("-")[0];
|
|
353
|
+
logger_1.logger.error(`distributedRateLimiter: ${this.modelKey} hard rate-limit ${reason === "day" ? "daily-cap" : "throughput"} fail (provider ${provider})`, {
|
|
354
|
+
modelKey: this.modelKey,
|
|
355
|
+
provider,
|
|
356
|
+
cause: reason,
|
|
357
|
+
trueWaitMs,
|
|
358
|
+
maxWaitMs,
|
|
359
|
+
perMinLimit: this.perMinLimit,
|
|
360
|
+
perDayLimit: this.perDayLimit,
|
|
361
|
+
secsUntilDayReset: secsTilMidnight,
|
|
362
|
+
});
|
|
363
|
+
throw new errors_1.UserFacingError(reason === "day"
|
|
364
|
+
? "This model has reached its daily generation limit."
|
|
365
|
+
: "This model is at capacity right now. Please try again shortly.", errors_1.USER_FACING_ERROR_CODES.VIDEO_PROVIDER_RATE_LIMITED);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
213
368
|
const waitMs = Math.max(50, Math.min(1000, result[1]));
|
|
214
369
|
await sleep(waitMs);
|
|
215
370
|
}
|
|
@@ -237,10 +392,23 @@ function getAiGenModelRateLimiter(modelKey) {
|
|
|
237
392
|
if (!config) {
|
|
238
393
|
throw new Error(`No model config for model key: ${modelKey}`);
|
|
239
394
|
}
|
|
395
|
+
// Google model configs hold the SINGLE-KEY Tier-2 baseline. With a
|
|
396
|
+
// multi-key pool (separate GCP projects/billing accounts, see
|
|
397
|
+
// googleKeyPool), the real aggregate budget is the SUM of each key's
|
|
398
|
+
// tier-scaled budget — so scale the model-level gate up to match, else it
|
|
399
|
+
// would throttle at one key's worth while the pool has more headroom.
|
|
400
|
+
// Single/legacy key → factor 1 (unchanged).
|
|
401
|
+
const isGoogle = modelKey.startsWith("google-");
|
|
402
|
+
const rpmFactor = isGoogle ? (0, googleApiKeys_1.googleAggregateFactor)("rpm") : 1;
|
|
403
|
+
const rpdFactor = isGoogle ? (0, googleApiKeys_1.googleAggregateFactor)("rpd") : 1;
|
|
240
404
|
aiModelLimiters[modelKey] = new DistributedRateLimiter(modelKey, {
|
|
241
405
|
concurrentRequests: config.concurrentRequests,
|
|
242
|
-
requestPerMin: config.requestPerMin
|
|
243
|
-
|
|
406
|
+
requestPerMin: config.requestPerMin
|
|
407
|
+
? Math.round(config.requestPerMin * rpmFactor)
|
|
408
|
+
: config.requestPerMin,
|
|
409
|
+
requestPerDay: config.requestPerDay
|
|
410
|
+
? Math.round(config.requestPerDay * rpdFactor)
|
|
411
|
+
: config.requestPerDay,
|
|
244
412
|
});
|
|
245
413
|
}
|
|
246
414
|
return aiModelLimiters[modelKey];
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ESocialProvider } from "../../globals/types";
|
|
2
|
+
/**
|
|
3
|
+
* X8 Social Suite — E1: AI caption / hashtag / title generation (Phase 6 AI
|
|
4
|
+
* growth layer). Pure, dependency-free core: builds the LLM prompt, parses +
|
|
5
|
+
* clamps the model's JSON, and exposes `generateSocialSuggestionsCore(llm, ctx)`
|
|
6
|
+
* with the LLM injected so it's unit-/eval-testable away from the OpenAI client
|
|
7
|
+
* (planner-style `*Core(llm,…)` convention — see L-3/L-5).
|
|
8
|
+
*
|
|
9
|
+
* One round-trip produces a platform-agnostic BASE (caption + hashtags + title)
|
|
10
|
+
* that fills the composer's main fields, PLUS per-platform VARIANTS tailored to
|
|
11
|
+
* each requested provider's norms (char limits, hashtag conventions, title use)
|
|
12
|
+
* that pre-seed the composer's per-platform overrides. The composer applies the
|
|
13
|
+
* base by default and reveals the variants progressively (pro depth, beginner
|
|
14
|
+
* surface — plan §B1/§E1).
|
|
15
|
+
*/
|
|
16
|
+
/** Input context for a suggestion request (no secrets — safe to log). */
|
|
17
|
+
export interface ISocialSuggestionContext {
|
|
18
|
+
/** Providers to tailor per-platform variants for (the composer's targets). */
|
|
19
|
+
providers: ESocialProvider[];
|
|
20
|
+
/** Title of the source video, if any. */
|
|
21
|
+
videoTitle?: string;
|
|
22
|
+
/** What the video is about — prompt / script summary that grounds the copy. */
|
|
23
|
+
videoSummary?: string;
|
|
24
|
+
/** ISO-639-1 / locale to WRITE the copy in (defaults to English). */
|
|
25
|
+
language?: string;
|
|
26
|
+
/** Optional brand-voice / tone steer (e.g. "playful, no emojis"). */
|
|
27
|
+
tone?: string;
|
|
28
|
+
}
|
|
29
|
+
/** A per-platform tailored caption + hashtag set. */
|
|
30
|
+
export interface ISocialSuggestionVariant {
|
|
31
|
+
provider: ESocialProvider;
|
|
32
|
+
caption: string;
|
|
33
|
+
hashtags: string[];
|
|
34
|
+
}
|
|
35
|
+
/** The full suggestion the composer renders. */
|
|
36
|
+
export interface ISocialSuggestion {
|
|
37
|
+
/** Platform-agnostic caption the composer fills into the main field. */
|
|
38
|
+
caption: string;
|
|
39
|
+
/** Platform-agnostic hashtag set (no leading '#', deduped). */
|
|
40
|
+
hashtags: string[];
|
|
41
|
+
/** Title (used by YouTube; harmless to ignore on caption-only platforms). */
|
|
42
|
+
title: string;
|
|
43
|
+
/** Per-platform variants for the requested providers (best-effort). */
|
|
44
|
+
variants: ISocialSuggestionVariant[];
|
|
45
|
+
}
|
|
46
|
+
/** A minimal LLM call: take chat messages, return the raw text completion. */
|
|
47
|
+
export type SuggestionLlm = (messages: Array<{
|
|
48
|
+
role: "system" | "user";
|
|
49
|
+
content: string;
|
|
50
|
+
}>) => Promise<string>;
|
|
51
|
+
/**
|
|
52
|
+
* Per-platform copy norms. `maxChars` clamps the caption; `maxHashtags` caps the
|
|
53
|
+
* tag list; `note` is fed to the model so each variant reads native to the
|
|
54
|
+
* platform. Hard limits (X = 280) are real product constraints, not style.
|
|
55
|
+
*/
|
|
56
|
+
interface PlatformCopyHint {
|
|
57
|
+
maxChars: number;
|
|
58
|
+
maxHashtags: number;
|
|
59
|
+
note: string;
|
|
60
|
+
}
|
|
61
|
+
export declare const PLATFORM_COPY_HINTS: Record<ESocialProvider, PlatformCopyHint>;
|
|
62
|
+
/** Build the chat prompt for a suggestion request. Pure — no I/O. */
|
|
63
|
+
export declare function buildSuggestionPrompt(ctx: ISocialSuggestionContext): Array<{
|
|
64
|
+
role: "system" | "user";
|
|
65
|
+
content: string;
|
|
66
|
+
}>;
|
|
67
|
+
/**
|
|
68
|
+
* Parse + validate + clamp the model's JSON into an `ISocialSuggestion`.
|
|
69
|
+
* Defensive: malformed JSON degrades to a plain caption (the raw text) with no
|
|
70
|
+
* hashtags/variants rather than throwing, so the composer always gets something
|
|
71
|
+
* usable. Variants are kept only for providers the caller actually requested.
|
|
72
|
+
*/
|
|
73
|
+
export declare function parseSocialSuggestion(raw: string, ctx: ISocialSuggestionContext): ISocialSuggestion;
|
|
74
|
+
/**
|
|
75
|
+
* Generate social suggestions with the LLM injected (testable). Builds the
|
|
76
|
+
* prompt, calls the model, parses + clamps the result. Never throws on a bad
|
|
77
|
+
* model response — it degrades to a usable caption (see `parseSocialSuggestion`).
|
|
78
|
+
*/
|
|
79
|
+
export declare function generateSocialSuggestionsCore(llm: SuggestionLlm, ctx: ISocialSuggestionContext): Promise<ISocialSuggestion>;
|
|
80
|
+
export {};
|
|
81
|
+
//# sourceMappingURL=captionGen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captionGen.d.ts","sourceRoot":"","sources":["../../../src/services/socialAI/captionGen.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD;;;;;;;;;;;;;GAaG;AAEH,yEAAyE;AACzE,MAAM,WAAW,wBAAwB;IACvC,8EAA8E;IAC9E,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qDAAqD;AACrD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,eAAe,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,gDAAgD;AAChD,MAAM,WAAW,iBAAiB;IAChC,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,QAAQ,EAAE,wBAAwB,EAAE,CAAC;CACtC;AAED,8EAA8E;AAC9E,MAAM,MAAM,aAAa,GAAG,CAC1B,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,KAC1D,OAAO,CAAC,MAAM,CAAC,CAAC;AAErB;;;;GAIG;AACH,UAAU,gBAAgB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,eAAe,EAAE,gBAAgB,CAyCzE,CAAC;AAqCF,qEAAqE;AACrE,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,wBAAwB,GAC5B,KAAK,CAAC;IAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAkDrD;AAYD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,wBAAwB,GAC5B,iBAAiB,CA2DnB;AAED;;;;GAIG;AACH,wBAAsB,6BAA6B,CACjD,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,wBAAwB,GAC5B,OAAO,CAAC,iBAAiB,CAAC,CAI5B"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PLATFORM_COPY_HINTS = void 0;
|
|
4
|
+
exports.buildSuggestionPrompt = buildSuggestionPrompt;
|
|
5
|
+
exports.parseSocialSuggestion = parseSocialSuggestion;
|
|
6
|
+
exports.generateSocialSuggestionsCore = generateSocialSuggestionsCore;
|
|
7
|
+
const types_1 = require("../../globals/types");
|
|
8
|
+
exports.PLATFORM_COPY_HINTS = {
|
|
9
|
+
[types_1.ESocialProvider.YOUTUBE]: {
|
|
10
|
+
maxChars: 4900,
|
|
11
|
+
maxHashtags: 5,
|
|
12
|
+
note: "YouTube: a strong, searchable title carries the post; description can be longer and front-load the hook; 3–5 relevant hashtags.",
|
|
13
|
+
},
|
|
14
|
+
[types_1.ESocialProvider.INSTAGRAM]: {
|
|
15
|
+
maxChars: 2100,
|
|
16
|
+
maxHashtags: 12,
|
|
17
|
+
note: "Instagram Reels: hook in the first line (it's the only part shown collapsed); conversational; 8–12 niche, non-spammy hashtags.",
|
|
18
|
+
},
|
|
19
|
+
[types_1.ESocialProvider.TIKTOK]: {
|
|
20
|
+
maxChars: 2100,
|
|
21
|
+
maxHashtags: 6,
|
|
22
|
+
note: "TikTok: punchy, native, trend-aware; only ~150 chars show before the fold; 3–6 hashtags mixing broad + niche.",
|
|
23
|
+
},
|
|
24
|
+
[types_1.ESocialProvider.FACEBOOK]: {
|
|
25
|
+
maxChars: 4900,
|
|
26
|
+
maxHashtags: 3,
|
|
27
|
+
note: "Facebook: friendly and shareable; hashtags are sparse here — at most 1–3.",
|
|
28
|
+
},
|
|
29
|
+
[types_1.ESocialProvider.LINKEDIN]: {
|
|
30
|
+
maxChars: 2900,
|
|
31
|
+
maxHashtags: 5,
|
|
32
|
+
note: "LinkedIn: professional, value-first, minimal emojis; 3–5 industry hashtags.",
|
|
33
|
+
},
|
|
34
|
+
[types_1.ESocialProvider.X]: {
|
|
35
|
+
maxChars: 270,
|
|
36
|
+
maxHashtags: 2,
|
|
37
|
+
note: "X (Twitter): HARD 280-char limit including hashtags — keep it tight; at most 1–2 hashtags.",
|
|
38
|
+
},
|
|
39
|
+
[types_1.ESocialProvider.PINTEREST]: {
|
|
40
|
+
maxChars: 480,
|
|
41
|
+
maxHashtags: 5,
|
|
42
|
+
note: "Pinterest: keyword-rich and descriptive for search; 2–5 descriptive hashtags.",
|
|
43
|
+
},
|
|
44
|
+
[types_1.ESocialProvider.THREADS]: {
|
|
45
|
+
maxChars: 480,
|
|
46
|
+
maxHashtags: 3,
|
|
47
|
+
note: "Threads: casual and conversational; very few hashtags (Threads supports one primary topic tag).",
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
const VALID_PROVIDERS = new Set(Object.values(types_1.ESocialProvider));
|
|
51
|
+
/** Normalize a raw hashtag list: strip '#', spaces, dedupe (case-insensitive). */
|
|
52
|
+
function normalizeHashtags(raw, cap) {
|
|
53
|
+
if (!Array.isArray(raw))
|
|
54
|
+
return [];
|
|
55
|
+
const seen = new Set();
|
|
56
|
+
const out = [];
|
|
57
|
+
for (const item of raw) {
|
|
58
|
+
if (typeof item !== "string")
|
|
59
|
+
continue;
|
|
60
|
+
// Strip leading '#'s and all whitespace; a model sometimes returns "#a b".
|
|
61
|
+
const tag = item.replace(/^#+/, "").replace(/\s+/g, "").trim();
|
|
62
|
+
if (!tag)
|
|
63
|
+
continue;
|
|
64
|
+
const key = tag.toLowerCase();
|
|
65
|
+
if (seen.has(key))
|
|
66
|
+
continue;
|
|
67
|
+
seen.add(key);
|
|
68
|
+
out.push(tag);
|
|
69
|
+
if (out.length >= cap)
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
/** Clamp to a max char length on a word boundary where possible. */
|
|
75
|
+
function clampText(text, maxChars) {
|
|
76
|
+
const t = (text ?? "").trim();
|
|
77
|
+
if (t.length <= maxChars)
|
|
78
|
+
return t;
|
|
79
|
+
const cut = t.slice(0, maxChars);
|
|
80
|
+
const lastSpace = cut.lastIndexOf(" ");
|
|
81
|
+
return (lastSpace > maxChars * 0.6 ? cut.slice(0, lastSpace) : cut).trim();
|
|
82
|
+
}
|
|
83
|
+
/** The loosest base limits (used for the platform-agnostic base caption). */
|
|
84
|
+
const BASE_CAPTION_MAX = 2200;
|
|
85
|
+
const BASE_HASHTAG_MAX = 15;
|
|
86
|
+
const TITLE_MAX = 100;
|
|
87
|
+
/** Build the chat prompt for a suggestion request. Pure — no I/O. */
|
|
88
|
+
function buildSuggestionPrompt(ctx) {
|
|
89
|
+
const providers = ctx.providers.filter((p) => VALID_PROVIDERS.has(p));
|
|
90
|
+
const lang = (ctx.language || "en").trim();
|
|
91
|
+
const toneLine = ctx.tone
|
|
92
|
+
? `Brand voice / tone: ${ctx.tone}.`
|
|
93
|
+
: "Brand voice: authentic, energetic, and human — never corporate or spammy.";
|
|
94
|
+
const platformNotes = providers
|
|
95
|
+
.map((p) => `- ${p}: ${exports.PLATFORM_COPY_HINTS[p].note}`)
|
|
96
|
+
.join("\n");
|
|
97
|
+
const system = "You are an expert social media copywriter for short-form video creators. " +
|
|
98
|
+
"You write scroll-stopping captions, relevant non-spammy hashtags, and " +
|
|
99
|
+
"compelling titles that maximize reach and engagement. You always write in " +
|
|
100
|
+
`the requested language (${lang}) and respect each platform's norms and ` +
|
|
101
|
+
"character limits. You reply with ONLY a single JSON object, no prose, no " +
|
|
102
|
+
"code fences.";
|
|
103
|
+
const aboutLines = [
|
|
104
|
+
ctx.videoTitle ? `Working title: ${ctx.videoTitle}` : null,
|
|
105
|
+
ctx.videoSummary ? `What the video is about: ${ctx.videoSummary}` : null,
|
|
106
|
+
].filter(Boolean);
|
|
107
|
+
const about = aboutLines.length
|
|
108
|
+
? aboutLines.join("\n")
|
|
109
|
+
: "A short-form video (no further details provided — infer a generic but appealing angle).";
|
|
110
|
+
const user = `Write social copy in language "${lang}" for this video.\n\n${about}\n\n` +
|
|
111
|
+
`${toneLine}\n\n` +
|
|
112
|
+
`Target platforms:\n${platformNotes || "- (generic)"}\n\n` +
|
|
113
|
+
"Produce a platform-agnostic BASE (a strong caption, a hashtag set, and a " +
|
|
114
|
+
"title) AND one tailored VARIANT per target platform that respects that " +
|
|
115
|
+
"platform's note above (length, hashtag count, style). Hashtags must NOT " +
|
|
116
|
+
"include the '#' symbol and must be single words (no spaces). Do not invent " +
|
|
117
|
+
"facts about the video.\n\n" +
|
|
118
|
+
"Reply with ONLY this JSON shape:\n" +
|
|
119
|
+
"{\n" +
|
|
120
|
+
' "title": "string (<= 100 chars, YouTube-style)",\n' +
|
|
121
|
+
' "caption": "string (the base caption)",\n' +
|
|
122
|
+
' "hashtags": ["tag1", "tag2"],\n' +
|
|
123
|
+
' "variants": [\n' +
|
|
124
|
+
' { "provider": "<one of the target platforms>", "caption": "string", "hashtags": ["tag1"] }\n' +
|
|
125
|
+
" ]\n" +
|
|
126
|
+
"}";
|
|
127
|
+
return [
|
|
128
|
+
{ role: "system", content: system },
|
|
129
|
+
{ role: "user", content: user },
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
/** Strip a ```json … ``` fence the model may wrap the object in. */
|
|
133
|
+
function stripCodeFence(raw) {
|
|
134
|
+
const t = raw.trim();
|
|
135
|
+
if (!t.startsWith("```"))
|
|
136
|
+
return t;
|
|
137
|
+
return t
|
|
138
|
+
.replace(/^```(?:json)?\s*/i, "")
|
|
139
|
+
.replace(/\s*```$/, "")
|
|
140
|
+
.trim();
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Parse + validate + clamp the model's JSON into an `ISocialSuggestion`.
|
|
144
|
+
* Defensive: malformed JSON degrades to a plain caption (the raw text) with no
|
|
145
|
+
* hashtags/variants rather than throwing, so the composer always gets something
|
|
146
|
+
* usable. Variants are kept only for providers the caller actually requested.
|
|
147
|
+
*/
|
|
148
|
+
function parseSocialSuggestion(raw, ctx) {
|
|
149
|
+
const requested = new Set(ctx.providers.filter((p) => VALID_PROVIDERS.has(p)));
|
|
150
|
+
let obj = null;
|
|
151
|
+
try {
|
|
152
|
+
const parsed = JSON.parse(stripCodeFence(raw));
|
|
153
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
154
|
+
obj = parsed;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
obj = null;
|
|
159
|
+
}
|
|
160
|
+
// Fallback: treat the whole response as the caption.
|
|
161
|
+
if (!obj) {
|
|
162
|
+
return {
|
|
163
|
+
caption: clampText(raw, BASE_CAPTION_MAX),
|
|
164
|
+
hashtags: [],
|
|
165
|
+
title: "",
|
|
166
|
+
variants: [],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const title = clampText(typeof obj.title === "string" ? obj.title : "", TITLE_MAX);
|
|
170
|
+
const caption = clampText(typeof obj.caption === "string" ? obj.caption : "", BASE_CAPTION_MAX);
|
|
171
|
+
const hashtags = normalizeHashtags(obj.hashtags, BASE_HASHTAG_MAX);
|
|
172
|
+
const variants = [];
|
|
173
|
+
const seenProviders = new Set();
|
|
174
|
+
if (Array.isArray(obj.variants)) {
|
|
175
|
+
for (const v of obj.variants) {
|
|
176
|
+
if (!v || typeof v !== "object")
|
|
177
|
+
continue;
|
|
178
|
+
const rec = v;
|
|
179
|
+
const provider = rec.provider;
|
|
180
|
+
if (typeof provider !== "string" || !requested.has(provider)) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const p = provider;
|
|
184
|
+
if (seenProviders.has(p))
|
|
185
|
+
continue;
|
|
186
|
+
seenProviders.add(p);
|
|
187
|
+
const hint = exports.PLATFORM_COPY_HINTS[p];
|
|
188
|
+
variants.push({
|
|
189
|
+
provider: p,
|
|
190
|
+
caption: clampText(typeof rec.caption === "string" ? rec.caption : caption, hint.maxChars),
|
|
191
|
+
hashtags: normalizeHashtags(rec.hashtags, hint.maxHashtags),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return { caption, hashtags, title, variants };
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Generate social suggestions with the LLM injected (testable). Builds the
|
|
199
|
+
* prompt, calls the model, parses + clamps the result. Never throws on a bad
|
|
200
|
+
* model response — it degrades to a usable caption (see `parseSocialSuggestion`).
|
|
201
|
+
*/
|
|
202
|
+
async function generateSocialSuggestionsCore(llm, ctx) {
|
|
203
|
+
const messages = buildSuggestionPrompt(ctx);
|
|
204
|
+
const raw = await llm(messages);
|
|
205
|
+
return parseSocialSuggestion(raw, ctx);
|
|
206
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { ESocialProvider } from "../../globals/types";
|
|
2
|
+
/**
|
|
3
|
+
* X8 Social Suite — Phase 6 E4: hook / caption score predictor.
|
|
4
|
+
*
|
|
5
|
+
* Score a hook (the caption's first line / the on-screen opener) for predicted
|
|
6
|
+
* engagement BEFORE posting, and hand back concrete rewrites. This is the same
|
|
7
|
+
* `*Core(llm,…)` convention as captionGen (E1) — pure prompt-build + defensive
|
|
8
|
+
* parse/clamp with the LLM injected, so it's unit-/eval-testable away from the
|
|
9
|
+
* OpenAI client. It is a *predictor / coach*, not a guarantee: the score is the
|
|
10
|
+
* model's judgement against well-known short-form hook heuristics (scroll-stop,
|
|
11
|
+
* clarity, curiosity gap, relevance, platform fit), surfaced so the user can
|
|
12
|
+
* iterate the copy. Review-before-publish — nothing posts here.
|
|
13
|
+
*
|
|
14
|
+
* Why a separate brain from E1: E1 *writes* copy from a video; E4 *grades*
|
|
15
|
+
* arbitrary copy the user already has (typed or AI-written) and proposes
|
|
16
|
+
* stronger variants. Different prompt, different output shape, shared norms.
|
|
17
|
+
*/
|
|
18
|
+
/** Input for a hook score request (no secrets — safe to log). */
|
|
19
|
+
export interface IHookScoreContext {
|
|
20
|
+
/** The hook / caption opener to grade (required). */
|
|
21
|
+
hook: string;
|
|
22
|
+
/** Platform the copy targets — tunes the rubric to that surface's norms. */
|
|
23
|
+
provider?: ESocialProvider;
|
|
24
|
+
/** What the video is about — lets the model judge relevance, not just craft. */
|
|
25
|
+
videoSummary?: string;
|
|
26
|
+
/** Language the copy is written in (defaults to English). */
|
|
27
|
+
language?: string;
|
|
28
|
+
}
|
|
29
|
+
/** The five engagement drivers we grade a hook on (each 0–100). */
|
|
30
|
+
export interface IHookScoreBreakdown {
|
|
31
|
+
/** Scroll-stopping power — does it arrest the thumb in the first beat? */
|
|
32
|
+
attention: number;
|
|
33
|
+
/** Is the promise/topic instantly legible (no decoding)? */
|
|
34
|
+
clarity: number;
|
|
35
|
+
/** Curiosity gap — does it open a loop the viewer must close? */
|
|
36
|
+
curiosity: number;
|
|
37
|
+
/** Does it match what the video actually delivers (no bait)? */
|
|
38
|
+
relevance: number;
|
|
39
|
+
/** Native fit for the target platform's length + tone conventions. */
|
|
40
|
+
platformFit: number;
|
|
41
|
+
}
|
|
42
|
+
/** Coarse band derived deterministically from the overall score. */
|
|
43
|
+
export type HookVerdict = "weak" | "okay" | "strong";
|
|
44
|
+
/** The full graded result the composer renders. */
|
|
45
|
+
export interface IHookScore {
|
|
46
|
+
/** Overall 0–100 predicted-engagement score. */
|
|
47
|
+
score: number;
|
|
48
|
+
/** Band derived from `score` (not trusted from the model). */
|
|
49
|
+
verdict: HookVerdict;
|
|
50
|
+
/** Per-driver sub-scores. */
|
|
51
|
+
breakdown: IHookScoreBreakdown;
|
|
52
|
+
/** What's working (short bullets). */
|
|
53
|
+
strengths: string[];
|
|
54
|
+
/** What's holding it back (short, actionable bullets). */
|
|
55
|
+
issues: string[];
|
|
56
|
+
/** 2–3 stronger rewrites the user can adopt with one tap. */
|
|
57
|
+
rewrites: string[];
|
|
58
|
+
}
|
|
59
|
+
/** A minimal LLM call: take chat messages, return the raw text completion. */
|
|
60
|
+
export type HookScoreLlm = (messages: Array<{
|
|
61
|
+
role: "system" | "user";
|
|
62
|
+
content: string;
|
|
63
|
+
}>) => Promise<string>;
|
|
64
|
+
/** Verdict bands: <40 weak, <70 okay, ≥70 strong. */
|
|
65
|
+
export declare function verdictForScore(score: number): HookVerdict;
|
|
66
|
+
/** Build the chat prompt for a hook-score request. Pure — no I/O. */
|
|
67
|
+
export declare function buildHookScorePrompt(ctx: IHookScoreContext): Array<{
|
|
68
|
+
role: "system" | "user";
|
|
69
|
+
content: string;
|
|
70
|
+
}>;
|
|
71
|
+
/**
|
|
72
|
+
* Parse + validate + clamp the model's JSON into an `IHookScore`. Defensive:
|
|
73
|
+
* malformed JSON degrades to a neutral 50 with empty coaching rather than
|
|
74
|
+
* throwing, so the composer always renders something. The verdict is ALWAYS
|
|
75
|
+
* recomputed from the clamped score (never trusted from the model) so the band
|
|
76
|
+
* and number can't disagree.
|
|
77
|
+
*/
|
|
78
|
+
export declare function parseHookScore(raw: string): IHookScore;
|
|
79
|
+
/**
|
|
80
|
+
* Score a hook with the LLM injected (testable). Builds the prompt, calls the
|
|
81
|
+
* model, parses + clamps. Never throws on a bad model response — degrades to a
|
|
82
|
+
* neutral score (see `parseHookScore`).
|
|
83
|
+
*/
|
|
84
|
+
export declare function scoreHookCore(llm: HookScoreLlm, ctx: IHookScoreContext): Promise<IHookScore>;
|
|
85
|
+
//# sourceMappingURL=hookScore.d.ts.map
|