@juspay/neurolink 8.35.2 → 8.37.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/CHANGELOG.md +12 -0
- package/dist/lib/types/generateTypes.d.ts +34 -2
- package/dist/lib/types/pptTypes.d.ts +69 -0
- package/dist/lib/types/pptTypes.js +2 -0
- package/dist/lib/utils/errorHandling.d.ts +60 -0
- package/dist/lib/utils/errorHandling.js +237 -0
- package/dist/lib/utils/imageProcessor.d.ts +1 -0
- package/dist/lib/utils/imageProcessor.js +4 -0
- package/dist/lib/utils/messageBuilder.js +4 -0
- package/dist/lib/utils/parameterValidation.d.ts +41 -0
- package/dist/lib/utils/parameterValidation.js +226 -0
- package/dist/lib/utils/rateLimiter.d.ts +89 -0
- package/dist/lib/utils/rateLimiter.js +201 -0
- package/dist/types/generateTypes.d.ts +34 -2
- package/dist/types/pptTypes.d.ts +69 -0
- package/dist/types/pptTypes.js +1 -0
- package/dist/utils/errorHandling.d.ts +60 -0
- package/dist/utils/errorHandling.js +237 -0
- package/dist/utils/imageProcessor.d.ts +1 -0
- package/dist/utils/imageProcessor.js +4 -0
- package/dist/utils/messageBuilder.js +4 -0
- package/dist/utils/parameterValidation.d.ts +41 -0
- package/dist/utils/parameterValidation.js +226 -0
- package/dist/utils/rateLimiter.d.ts +89 -0
- package/dist/utils/rateLimiter.js +200 -0
- package/package.json +1 -1
|
@@ -603,6 +603,232 @@ export function validateVideoGenerationInput(options) {
|
|
|
603
603
|
return { isValid: errors.length === 0, errors, warnings, suggestions };
|
|
604
604
|
}
|
|
605
605
|
// ============================================================================
|
|
606
|
+
// PPT VALIDATION (Presentation Generation)
|
|
607
|
+
// ============================================================================
|
|
608
|
+
/**
|
|
609
|
+
* Valid PPT generation options
|
|
610
|
+
*/
|
|
611
|
+
const VALID_PPT_THEMES = [
|
|
612
|
+
"modern",
|
|
613
|
+
"corporate",
|
|
614
|
+
"creative",
|
|
615
|
+
"minimal",
|
|
616
|
+
"dark",
|
|
617
|
+
];
|
|
618
|
+
const VALID_PPT_AUDIENCES = [
|
|
619
|
+
"business",
|
|
620
|
+
"students",
|
|
621
|
+
"technical",
|
|
622
|
+
"general",
|
|
623
|
+
];
|
|
624
|
+
const VALID_PPT_TONES = [
|
|
625
|
+
"professional",
|
|
626
|
+
"casual",
|
|
627
|
+
"educational",
|
|
628
|
+
"persuasive",
|
|
629
|
+
];
|
|
630
|
+
const VALID_PPT_ASPECT_RATIOS = ["16:9", "4:3"];
|
|
631
|
+
const VALID_PPT_FORMATS = ["pptx"];
|
|
632
|
+
export const MIN_PPT_PAGES = 5;
|
|
633
|
+
export const MAX_PPT_PAGES = 50;
|
|
634
|
+
export const MIN_PPT_PROMPT_LENGTH = 10;
|
|
635
|
+
export const MAX_PPT_PROMPT_LENGTH = 1000;
|
|
636
|
+
/**
|
|
637
|
+
* Validate PPT output options (pages, theme, audience, tone, etc.)
|
|
638
|
+
*
|
|
639
|
+
* @param options - PPTOutputOptions to validate
|
|
640
|
+
* @returns NeuroLinkError if invalid, null if valid
|
|
641
|
+
*
|
|
642
|
+
* @example
|
|
643
|
+
* ```typescript
|
|
644
|
+
* const error = validatePPTOutputOptions({ pages: 100, theme: "invalid" });
|
|
645
|
+
* // error.code === "INVALID_PPT_PAGES"
|
|
646
|
+
* ```
|
|
647
|
+
*/
|
|
648
|
+
export function validatePPTOutputOptions(options) {
|
|
649
|
+
// Validate pages (slide count) - REQUIRED FIELD
|
|
650
|
+
if (options.pages === undefined || options.pages === null) {
|
|
651
|
+
return ErrorFactory.missingPPTProperty("output.ppt.pages", [
|
|
652
|
+
"Provide the number of slides: output.ppt.pages = 10",
|
|
653
|
+
"Valid range: 5 to 50 slides",
|
|
654
|
+
"Recommended: 10 slides for most presentations",
|
|
655
|
+
]);
|
|
656
|
+
}
|
|
657
|
+
if (typeof options.pages !== "number") {
|
|
658
|
+
return ErrorFactory.invalidPPTPages(options.pages, "not a number");
|
|
659
|
+
}
|
|
660
|
+
if (!Number.isInteger(options.pages)) {
|
|
661
|
+
return ErrorFactory.invalidPPTPages(options.pages, "not an integer");
|
|
662
|
+
}
|
|
663
|
+
if (options.pages < MIN_PPT_PAGES || options.pages > MAX_PPT_PAGES) {
|
|
664
|
+
return ErrorFactory.invalidPPTPages(options.pages, `out of range (${MIN_PPT_PAGES}-${MAX_PPT_PAGES})`);
|
|
665
|
+
}
|
|
666
|
+
// Validate format
|
|
667
|
+
if (options.format !== undefined &&
|
|
668
|
+
!VALID_PPT_FORMATS.includes(options.format)) {
|
|
669
|
+
return ErrorFactory.invalidPPTFormat(options.format);
|
|
670
|
+
}
|
|
671
|
+
// Validate theme
|
|
672
|
+
if (options.theme !== undefined &&
|
|
673
|
+
!VALID_PPT_THEMES.includes(options.theme)) {
|
|
674
|
+
return ErrorFactory.invalidPPTOutputOptions("theme", options.theme, Array.from(VALID_PPT_THEMES));
|
|
675
|
+
}
|
|
676
|
+
// Validate audience
|
|
677
|
+
if (options.audience !== undefined &&
|
|
678
|
+
!VALID_PPT_AUDIENCES.includes(options.audience)) {
|
|
679
|
+
return ErrorFactory.invalidPPTOutputOptions("audience", options.audience, Array.from(VALID_PPT_AUDIENCES));
|
|
680
|
+
}
|
|
681
|
+
// Validate tone
|
|
682
|
+
if (options.tone !== undefined && !VALID_PPT_TONES.includes(options.tone)) {
|
|
683
|
+
return ErrorFactory.invalidPPTOutputOptions("tone", options.tone, Array.from(VALID_PPT_TONES));
|
|
684
|
+
}
|
|
685
|
+
// Validate aspectRatio
|
|
686
|
+
if (options.aspectRatio !== undefined &&
|
|
687
|
+
!VALID_PPT_ASPECT_RATIOS.includes(options.aspectRatio)) {
|
|
688
|
+
return ErrorFactory.invalidPPTOutputOptions("aspectRatio", options.aspectRatio, Array.from(VALID_PPT_ASPECT_RATIOS));
|
|
689
|
+
}
|
|
690
|
+
// Validate includeImages (must be boolean if provided)
|
|
691
|
+
if (options.includeImages !== undefined &&
|
|
692
|
+
typeof options.includeImages !== "boolean") {
|
|
693
|
+
return ErrorFactory.invalidPPTOutputOptions("includeImages", options.includeImages, ["true", "false"]);
|
|
694
|
+
}
|
|
695
|
+
// Validate logoPath (string path, Buffer, or ImageWithAltText)
|
|
696
|
+
if (options.logoPath !== undefined) {
|
|
697
|
+
if (typeof options.logoPath === "string") {
|
|
698
|
+
if (options.logoPath.trim().length === 0) {
|
|
699
|
+
return ErrorFactory.invalidPPTLogoPath(options.logoPath, "empty string");
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
else if (Buffer.isBuffer(options.logoPath)) {
|
|
703
|
+
// ok
|
|
704
|
+
}
|
|
705
|
+
else if (typeof options.logoPath === "object" &&
|
|
706
|
+
"data" in options.logoPath) {
|
|
707
|
+
const data = options.logoPath.data;
|
|
708
|
+
if (typeof data === "string") {
|
|
709
|
+
if (data.trim().length === 0) {
|
|
710
|
+
return ErrorFactory.invalidPPTLogoPath(options.logoPath, "empty string");
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
else if (!Buffer.isBuffer(data)) {
|
|
714
|
+
return ErrorFactory.invalidPPTLogoPath(options.logoPath, "invalid data type");
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
return ErrorFactory.invalidPPTLogoPath(options.logoPath, "invalid type");
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
// Validate outputPath (must be non-empty string if provided)
|
|
722
|
+
if (options.outputPath !== undefined) {
|
|
723
|
+
if (typeof options.outputPath !== "string") {
|
|
724
|
+
return ErrorFactory.invalidPPTOutputPath(options.outputPath, "not a string");
|
|
725
|
+
}
|
|
726
|
+
if (options.outputPath.trim().length === 0) {
|
|
727
|
+
return ErrorFactory.invalidPPTOutputPath(options.outputPath, "empty string");
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
function validatePPTProvider(provider) {
|
|
733
|
+
// PPT generation supported providers (subset of all AIProviderName values)
|
|
734
|
+
// Supports major LLM providers with structured output capabilities
|
|
735
|
+
const validProviders = [
|
|
736
|
+
"vertex",
|
|
737
|
+
"openai",
|
|
738
|
+
"azure",
|
|
739
|
+
"anthropic",
|
|
740
|
+
"google-ai",
|
|
741
|
+
"bedrock",
|
|
742
|
+
];
|
|
743
|
+
// Convert enum or string to lowercase string for comparison
|
|
744
|
+
const providerString = String(provider).toLowerCase();
|
|
745
|
+
if (!validProviders.includes(providerString)) {
|
|
746
|
+
return ErrorFactory.invalidPPTProvider(provider);
|
|
747
|
+
}
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Validate complete PPT generation input
|
|
752
|
+
*
|
|
753
|
+
* Validates all requirements for presentation generation:
|
|
754
|
+
* - output.mode must be "ppt"
|
|
755
|
+
* - Prompt must be within length limits
|
|
756
|
+
* - PPT output options must be valid
|
|
757
|
+
*
|
|
758
|
+
* @param options - GenerateOptions to validate for PPT generation
|
|
759
|
+
* @returns EnhancedValidationResult with errors, warnings, and suggestions
|
|
760
|
+
*
|
|
761
|
+
* @example
|
|
762
|
+
* ```typescript
|
|
763
|
+
* const validation = validatePPTGenerationInput({
|
|
764
|
+
* input: { text: "Introducing Our New Product" },
|
|
765
|
+
* output: { mode: "ppt", ppt: { pages: 10, theme: "modern" } }
|
|
766
|
+
* });
|
|
767
|
+
* if (!validation.isValid) {
|
|
768
|
+
* console.error(validation.errors);
|
|
769
|
+
* }
|
|
770
|
+
* ```
|
|
771
|
+
*/
|
|
772
|
+
export function validatePPTGenerationInput(options) {
|
|
773
|
+
const errors = [];
|
|
774
|
+
const warnings = [];
|
|
775
|
+
const suggestions = [];
|
|
776
|
+
// Validate prompt/text - trim once for consistency
|
|
777
|
+
const trimmedPrompt = options.input.text.trim();
|
|
778
|
+
if (trimmedPrompt === "") {
|
|
779
|
+
errors.push(toValidationError(ErrorFactory.invalidPPTPrompt("empty prompt")));
|
|
780
|
+
}
|
|
781
|
+
else if (trimmedPrompt.length < MIN_PPT_PROMPT_LENGTH) {
|
|
782
|
+
errors.push(toValidationError(ErrorFactory.invalidPPTPrompt(`prompt too short (${trimmedPrompt.length} characters, min ${MIN_PPT_PROMPT_LENGTH})`)));
|
|
783
|
+
}
|
|
784
|
+
else if (trimmedPrompt.length > MAX_PPT_PROMPT_LENGTH) {
|
|
785
|
+
errors.push(toValidationError(ErrorFactory.invalidPPTPrompt(`prompt too long (${trimmedPrompt.length} characters, max ${MAX_PPT_PROMPT_LENGTH})`)));
|
|
786
|
+
}
|
|
787
|
+
// image PPT options if provided
|
|
788
|
+
if (options.input.images && options.input.images.length > 0) {
|
|
789
|
+
warnings.push("Images can be unused in PPT generation due to fail in quality standards and can lead to longer generation times.");
|
|
790
|
+
suggestions.push("Only provide high-quality, relevant images for PPT generation.");
|
|
791
|
+
}
|
|
792
|
+
// Validate provider (optional - only validate if explicitly provided)
|
|
793
|
+
if (options.provider !== undefined) {
|
|
794
|
+
const providerError = validatePPTProvider(options.provider);
|
|
795
|
+
if (providerError) {
|
|
796
|
+
errors.push(toValidationError(providerError));
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
// Mode is optional, but if provided must be "ppt"
|
|
800
|
+
if (options.output?.mode !== undefined && options.output.mode !== "ppt") {
|
|
801
|
+
errors.push(toValidationError(ErrorFactory.invalidPPTMode()));
|
|
802
|
+
}
|
|
803
|
+
// Validate PPT output options
|
|
804
|
+
if (options.output?.ppt) {
|
|
805
|
+
const pptError = validatePPTOutputOptions(options.output.ppt);
|
|
806
|
+
if (pptError) {
|
|
807
|
+
errors.push(toValidationError(pptError));
|
|
808
|
+
}
|
|
809
|
+
// Add specific warnings
|
|
810
|
+
const pages = options.output.ppt.pages;
|
|
811
|
+
if (pages > 30) {
|
|
812
|
+
warnings.push(`Generating ${pages} slides may take significant time (estimated: ${Math.ceil(pages * 3)}-${Math.ceil(pages * 5)} seconds)`);
|
|
813
|
+
}
|
|
814
|
+
if (options.output.ppt.includeImages === undefined ||
|
|
815
|
+
options.output.ppt.includeImages === true) {
|
|
816
|
+
suggestions.push("Image generation is enabled. Each slide with images will take additional time (~2-5 seconds per image).");
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
else {
|
|
820
|
+
errors.push(toValidationError(ErrorFactory.missingPPTProperty("output.ppt", [
|
|
821
|
+
"Provide PPT generation options under output.ppt",
|
|
822
|
+
"Specify number of slides, theme, and other preferences",
|
|
823
|
+
])));
|
|
824
|
+
}
|
|
825
|
+
// Add helpful suggestions
|
|
826
|
+
if (errors.length === 0 && warnings.length === 0) {
|
|
827
|
+
suggestions.push("PPT generation typically takes 30-120 seconds depending on slide count and image generation.");
|
|
828
|
+
}
|
|
829
|
+
return { isValid: errors.length === 0, errors, warnings, suggestions };
|
|
830
|
+
}
|
|
831
|
+
// ============================================================================
|
|
606
832
|
// HELPER FUNCTIONS
|
|
607
833
|
// ============================================================================
|
|
608
834
|
/**
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Bucket Rate Limiter for URL Downloads
|
|
3
|
+
*
|
|
4
|
+
* Implements a token bucket algorithm to limit concurrent URL downloads.
|
|
5
|
+
* This prevents DoS attacks from rapid URL download requests.
|
|
6
|
+
*
|
|
7
|
+
* Default configuration: 10 downloads per second
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Configuration options for the rate limiter
|
|
11
|
+
*/
|
|
12
|
+
export interface RateLimiterConfig {
|
|
13
|
+
/** Maximum tokens (downloads) allowed per interval */
|
|
14
|
+
maxTokens: number;
|
|
15
|
+
/** Refill interval in milliseconds */
|
|
16
|
+
refillIntervalMs: number;
|
|
17
|
+
/** Number of tokens to add per refill interval */
|
|
18
|
+
tokensPerRefill: number;
|
|
19
|
+
/** Maximum queue size for pending requests */
|
|
20
|
+
maxQueueSize: number;
|
|
21
|
+
/** Timeout for queued requests in milliseconds */
|
|
22
|
+
queueTimeoutMs: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Token Bucket Rate Limiter
|
|
26
|
+
*
|
|
27
|
+
* Uses a token bucket algorithm where:
|
|
28
|
+
* - Tokens are consumed when a download is requested
|
|
29
|
+
* - Tokens are refilled at a fixed rate
|
|
30
|
+
* - Requests that exceed the limit are queued
|
|
31
|
+
*/
|
|
32
|
+
export declare class TokenBucketRateLimiter {
|
|
33
|
+
private tokens;
|
|
34
|
+
private config;
|
|
35
|
+
private queue;
|
|
36
|
+
private refillTimer;
|
|
37
|
+
private lastRefillTime;
|
|
38
|
+
constructor(config?: Partial<RateLimiterConfig>);
|
|
39
|
+
/**
|
|
40
|
+
* Start the token refill timer
|
|
41
|
+
*/
|
|
42
|
+
private startRefillTimer;
|
|
43
|
+
/**
|
|
44
|
+
* Refill tokens based on elapsed time
|
|
45
|
+
*/
|
|
46
|
+
private refillTokens;
|
|
47
|
+
/**
|
|
48
|
+
* Process queued requests when tokens become available
|
|
49
|
+
*/
|
|
50
|
+
private processQueue;
|
|
51
|
+
/**
|
|
52
|
+
* Acquire a token for a download
|
|
53
|
+
* Returns immediately if token is available, otherwise queues the request
|
|
54
|
+
*
|
|
55
|
+
* @throws NeuroLinkError if queue is full or request times out
|
|
56
|
+
*/
|
|
57
|
+
acquire(): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Get current rate limiter statistics
|
|
60
|
+
*/
|
|
61
|
+
getStats(): {
|
|
62
|
+
availableTokens: number;
|
|
63
|
+
queueLength: number;
|
|
64
|
+
maxTokens: number;
|
|
65
|
+
maxQueueSize: number;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Reset the rate limiter to initial state
|
|
69
|
+
*/
|
|
70
|
+
reset(): void;
|
|
71
|
+
/**
|
|
72
|
+
* Stop the rate limiter and clean up resources
|
|
73
|
+
*/
|
|
74
|
+
stop(): void;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Global rate limiter instance for URL downloads
|
|
78
|
+
* Default: 10 downloads per second
|
|
79
|
+
*/
|
|
80
|
+
export declare const urlDownloadRateLimiter: TokenBucketRateLimiter;
|
|
81
|
+
/**
|
|
82
|
+
* Rate-limited wrapper for async functions
|
|
83
|
+
* Ensures the function is rate-limited using the provided rate limiter
|
|
84
|
+
*
|
|
85
|
+
* @param fn - The async function to wrap
|
|
86
|
+
* @param rateLimiter - The rate limiter to use (defaults to urlDownloadRateLimiter)
|
|
87
|
+
* @returns A rate-limited version of the function
|
|
88
|
+
*/
|
|
89
|
+
export declare function withRateLimit<T extends unknown[], R>(fn: (...args: T) => Promise<R>, rateLimiter?: TokenBucketRateLimiter): (...args: T) => Promise<R>;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Bucket Rate Limiter for URL Downloads
|
|
3
|
+
*
|
|
4
|
+
* Implements a token bucket algorithm to limit concurrent URL downloads.
|
|
5
|
+
* This prevents DoS attacks from rapid URL download requests.
|
|
6
|
+
*
|
|
7
|
+
* Default configuration: 10 downloads per second
|
|
8
|
+
*/
|
|
9
|
+
import { logger } from "./logger.js";
|
|
10
|
+
import { ErrorFactory } from "./errorHandling.js";
|
|
11
|
+
/**
|
|
12
|
+
* Default configuration: 10 downloads per second
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_CONFIG = {
|
|
15
|
+
maxTokens: 10,
|
|
16
|
+
refillIntervalMs: 1000,
|
|
17
|
+
tokensPerRefill: 10,
|
|
18
|
+
maxQueueSize: 100,
|
|
19
|
+
queueTimeoutMs: 30000,
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Token Bucket Rate Limiter
|
|
23
|
+
*
|
|
24
|
+
* Uses a token bucket algorithm where:
|
|
25
|
+
* - Tokens are consumed when a download is requested
|
|
26
|
+
* - Tokens are refilled at a fixed rate
|
|
27
|
+
* - Requests that exceed the limit are queued
|
|
28
|
+
*/
|
|
29
|
+
export class TokenBucketRateLimiter {
|
|
30
|
+
tokens;
|
|
31
|
+
config;
|
|
32
|
+
queue = [];
|
|
33
|
+
refillTimer = null;
|
|
34
|
+
lastRefillTime;
|
|
35
|
+
constructor(config = {}) {
|
|
36
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
37
|
+
this.tokens = this.config.maxTokens;
|
|
38
|
+
this.lastRefillTime = Date.now();
|
|
39
|
+
this.startRefillTimer();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Start the token refill timer
|
|
43
|
+
*/
|
|
44
|
+
startRefillTimer() {
|
|
45
|
+
if (this.refillTimer) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
this.refillTimer = setInterval(() => {
|
|
49
|
+
this.refillTokens();
|
|
50
|
+
this.processQueue();
|
|
51
|
+
}, this.config.refillIntervalMs);
|
|
52
|
+
// Unref to prevent keeping the process alive
|
|
53
|
+
this.refillTimer.unref();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Refill tokens based on elapsed time
|
|
57
|
+
*/
|
|
58
|
+
refillTokens() {
|
|
59
|
+
const now = Date.now();
|
|
60
|
+
const elapsed = now - this.lastRefillTime;
|
|
61
|
+
const intervalsElapsed = Math.floor(elapsed / this.config.refillIntervalMs);
|
|
62
|
+
if (intervalsElapsed > 0) {
|
|
63
|
+
const tokensToAdd = intervalsElapsed * this.config.tokensPerRefill;
|
|
64
|
+
this.tokens = Math.min(this.config.maxTokens, this.tokens + tokensToAdd);
|
|
65
|
+
this.lastRefillTime = now;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Process queued requests when tokens become available
|
|
70
|
+
*/
|
|
71
|
+
processQueue() {
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
// Remove timed out requests
|
|
74
|
+
while (this.queue.length > 0) {
|
|
75
|
+
const request = this.queue[0];
|
|
76
|
+
if (now - request.timestamp > this.config.queueTimeoutMs) {
|
|
77
|
+
this.queue.shift();
|
|
78
|
+
if (request.timeoutTimer) {
|
|
79
|
+
clearTimeout(request.timeoutTimer);
|
|
80
|
+
}
|
|
81
|
+
request.reject(ErrorFactory.rateLimiterQueueTimeout(this.config.queueTimeoutMs));
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Process requests while we have tokens
|
|
88
|
+
while (this.tokens > 0 && this.queue.length > 0) {
|
|
89
|
+
const request = this.queue.shift();
|
|
90
|
+
if (request) {
|
|
91
|
+
if (request.timeoutTimer) {
|
|
92
|
+
clearTimeout(request.timeoutTimer);
|
|
93
|
+
}
|
|
94
|
+
this.tokens--;
|
|
95
|
+
request.resolve();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Acquire a token for a download
|
|
101
|
+
* Returns immediately if token is available, otherwise queues the request
|
|
102
|
+
*
|
|
103
|
+
* @throws NeuroLinkError if queue is full or request times out
|
|
104
|
+
*/
|
|
105
|
+
async acquire() {
|
|
106
|
+
// Refill tokens based on elapsed time
|
|
107
|
+
this.refillTokens();
|
|
108
|
+
// If token available, consume it immediately
|
|
109
|
+
if (this.tokens > 0) {
|
|
110
|
+
this.tokens--;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// Check queue size limit
|
|
114
|
+
if (this.queue.length >= this.config.maxQueueSize) {
|
|
115
|
+
logger.warn(`Rate limiter queue full (${this.config.maxQueueSize} requests pending)`);
|
|
116
|
+
throw ErrorFactory.rateLimiterQueueFull(this.config.maxQueueSize);
|
|
117
|
+
}
|
|
118
|
+
// Queue the request with per-request timeout handling
|
|
119
|
+
return new Promise((resolve, reject) => {
|
|
120
|
+
// Create per-request timeout timer for robust timeout handling
|
|
121
|
+
const timeoutTimer = setTimeout(() => {
|
|
122
|
+
// Remove from queue if still present
|
|
123
|
+
const index = this.queue.findIndex((req) => req.timeoutTimer === timeoutTimer);
|
|
124
|
+
if (index !== -1) {
|
|
125
|
+
this.queue.splice(index, 1);
|
|
126
|
+
reject(ErrorFactory.rateLimiterQueueTimeout(this.config.queueTimeoutMs));
|
|
127
|
+
}
|
|
128
|
+
}, this.config.queueTimeoutMs);
|
|
129
|
+
this.queue.push({
|
|
130
|
+
resolve,
|
|
131
|
+
reject,
|
|
132
|
+
timestamp: Date.now(),
|
|
133
|
+
timeoutTimer,
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get current rate limiter statistics
|
|
139
|
+
*/
|
|
140
|
+
getStats() {
|
|
141
|
+
return {
|
|
142
|
+
availableTokens: this.tokens,
|
|
143
|
+
queueLength: this.queue.length,
|
|
144
|
+
maxTokens: this.config.maxTokens,
|
|
145
|
+
maxQueueSize: this.config.maxQueueSize,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Reset the rate limiter to initial state
|
|
150
|
+
*/
|
|
151
|
+
reset() {
|
|
152
|
+
this.tokens = this.config.maxTokens;
|
|
153
|
+
this.lastRefillTime = Date.now();
|
|
154
|
+
// Reject all queued requests and clear timeout timers
|
|
155
|
+
while (this.queue.length > 0) {
|
|
156
|
+
const request = this.queue.shift();
|
|
157
|
+
if (request) {
|
|
158
|
+
if (request.timeoutTimer) {
|
|
159
|
+
clearTimeout(request.timeoutTimer);
|
|
160
|
+
}
|
|
161
|
+
request.reject(ErrorFactory.rateLimiterReset());
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Stop the rate limiter and clean up resources
|
|
167
|
+
*/
|
|
168
|
+
stop() {
|
|
169
|
+
if (this.refillTimer) {
|
|
170
|
+
clearInterval(this.refillTimer);
|
|
171
|
+
this.refillTimer = null;
|
|
172
|
+
}
|
|
173
|
+
this.reset();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Global rate limiter instance for URL downloads
|
|
178
|
+
* Default: 10 downloads per second
|
|
179
|
+
*/
|
|
180
|
+
export const urlDownloadRateLimiter = new TokenBucketRateLimiter({
|
|
181
|
+
maxTokens: 10,
|
|
182
|
+
refillIntervalMs: 1000,
|
|
183
|
+
tokensPerRefill: 10,
|
|
184
|
+
maxQueueSize: 100,
|
|
185
|
+
queueTimeoutMs: 30000,
|
|
186
|
+
});
|
|
187
|
+
/**
|
|
188
|
+
* Rate-limited wrapper for async functions
|
|
189
|
+
* Ensures the function is rate-limited using the provided rate limiter
|
|
190
|
+
*
|
|
191
|
+
* @param fn - The async function to wrap
|
|
192
|
+
* @param rateLimiter - The rate limiter to use (defaults to urlDownloadRateLimiter)
|
|
193
|
+
* @returns A rate-limited version of the function
|
|
194
|
+
*/
|
|
195
|
+
export function withRateLimit(fn, rateLimiter = urlDownloadRateLimiter) {
|
|
196
|
+
return async (...args) => {
|
|
197
|
+
await rateLimiter.acquire();
|
|
198
|
+
return fn(...args);
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=rateLimiter.js.map
|
|
@@ -8,6 +8,7 @@ import type { MiddlewareFactoryOptions } from "./middlewareTypes.js";
|
|
|
8
8
|
import type { JsonValue } from "./common.js";
|
|
9
9
|
import type { Content, ImageWithAltText } from "./content.js";
|
|
10
10
|
import type { TTSOptions, TTSResult } from "./ttsTypes.js";
|
|
11
|
+
import type { PPTOutputOptions, PPTGenerationResult } from "./pptTypes.js";
|
|
11
12
|
import type { VideoOutputOptions, VideoGenerationResult } from "./multimodal.js";
|
|
12
13
|
/**
|
|
13
14
|
* Generate function options type - Primary method for content generation
|
|
@@ -68,13 +69,19 @@ export type GenerateOptions = {
|
|
|
68
69
|
* Output mode - determines the type of content generated
|
|
69
70
|
* - "text": Standard text generation (default)
|
|
70
71
|
* - "video": Video generation using models like Veo 3.1
|
|
72
|
+
* - "ppt": PowerPoint presentation generation
|
|
71
73
|
*/
|
|
72
|
-
mode?: "text" | "video";
|
|
74
|
+
mode?: "text" | "video" | "ppt";
|
|
73
75
|
/**
|
|
74
76
|
* Video generation configuration (used when mode is "video")
|
|
75
77
|
* Requires an input image and text prompt
|
|
76
78
|
*/
|
|
77
79
|
video?: VideoOutputOptions;
|
|
80
|
+
/**
|
|
81
|
+
* PowerPoint generation configuration (used when mode is "ppt")
|
|
82
|
+
* Generates slides based on text prompt
|
|
83
|
+
*/
|
|
84
|
+
ppt?: PPTOutputOptions;
|
|
78
85
|
};
|
|
79
86
|
csvOptions?: {
|
|
80
87
|
maxRows?: number;
|
|
@@ -312,6 +319,24 @@ export type GenerateResult = {
|
|
|
312
319
|
* ```
|
|
313
320
|
*/
|
|
314
321
|
video?: VideoGenerationResult;
|
|
322
|
+
/**
|
|
323
|
+
* PowerPoint generation result (present when output.mode is "ppt")
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* ```typescript
|
|
327
|
+
* const result = await neurolink.generate({
|
|
328
|
+
* input: { text: "Introducing Our New Product" },
|
|
329
|
+
* model: "gemini-pro",
|
|
330
|
+
* output: { mode: "ppt", ppt: { pages: 10, theme: "modern" } }
|
|
331
|
+
* });
|
|
332
|
+
*
|
|
333
|
+
* if (result.ppt) {
|
|
334
|
+
* console.log(`Generated ${result.ppt.slides.length} slides`);
|
|
335
|
+
* console.log(`Title: ${result.ppt.slides[0].title}`);
|
|
336
|
+
* }
|
|
337
|
+
* ```
|
|
338
|
+
*/
|
|
339
|
+
ppt?: PPTGenerationResult;
|
|
315
340
|
imageOutput?: {
|
|
316
341
|
base64: string;
|
|
317
342
|
} | null;
|
|
@@ -434,12 +459,17 @@ export type TextGenerationOptions = {
|
|
|
434
459
|
* Output mode - determines the type of content generated
|
|
435
460
|
* - "text": Standard text generation (default)
|
|
436
461
|
* - "video": Video generation using models like Veo 3.1
|
|
462
|
+
* - "ppt": PowerPoint presentation generation
|
|
437
463
|
*/
|
|
438
|
-
mode?: "text" | "video";
|
|
464
|
+
mode?: "text" | "video" | "ppt";
|
|
439
465
|
/**
|
|
440
466
|
* Video generation configuration (used when mode is "video")
|
|
441
467
|
*/
|
|
442
468
|
video?: VideoOutputOptions;
|
|
469
|
+
/**
|
|
470
|
+
* PowerPoint generation configuration (used when mode is "ppt")
|
|
471
|
+
*/
|
|
472
|
+
ppt?: PPTOutputOptions;
|
|
443
473
|
};
|
|
444
474
|
tools?: Record<string, Tool>;
|
|
445
475
|
timeout?: number | string;
|
|
@@ -612,6 +642,8 @@ export type TextGenerationResult = {
|
|
|
612
642
|
audio?: TTSResult;
|
|
613
643
|
/** Video generation result */
|
|
614
644
|
video?: VideoGenerationResult;
|
|
645
|
+
/** PowerPoint generation result */
|
|
646
|
+
ppt?: PPTGenerationResult;
|
|
615
647
|
/** Image generation output */
|
|
616
648
|
imageOutput?: {
|
|
617
649
|
base64: string;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { ImageWithAltText } from "./content.js";
|
|
2
|
+
type ThemeOption = "modern" | "corporate" | "creative" | "minimal" | "dark";
|
|
3
|
+
type AudienceOption = "business" | "students" | "technical" | "general";
|
|
4
|
+
type ToneOption = "professional" | "casual" | "educational" | "persuasive";
|
|
5
|
+
type OutputFormatOption = "pptx";
|
|
6
|
+
type AspectRatioOption = "16:9" | "4:3";
|
|
7
|
+
export type PPTOutputOptions = {
|
|
8
|
+
/** Number of slides to generate (required, range: 5-50) */
|
|
9
|
+
pages: number;
|
|
10
|
+
/** Output format - only PPTX supported currently (default: "pptx") */
|
|
11
|
+
format?: OutputFormatOption;
|
|
12
|
+
/** Presentation theme/style (default: "modern") */
|
|
13
|
+
theme?: ThemeOption;
|
|
14
|
+
/** Target audience for content customization */
|
|
15
|
+
audience?: AudienceOption;
|
|
16
|
+
/** Presentation tone/style */
|
|
17
|
+
tone?: ToneOption;
|
|
18
|
+
/** Whether to generate AI images for slides (default: true) */
|
|
19
|
+
includeImages?: boolean;
|
|
20
|
+
/** Custom output file path (default: auto-generated in ./output/) */
|
|
21
|
+
outputPath?: string;
|
|
22
|
+
/** Aspect ratio for slides (default: "16:9") */
|
|
23
|
+
aspectRatio?: AspectRatioOption;
|
|
24
|
+
/** Path to logo image to include in slides */
|
|
25
|
+
logoPath?: Buffer | string | ImageWithAltText;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Result type for generated presentation content
|
|
29
|
+
*
|
|
30
|
+
* Returned in `GenerateResult.ppt` when presentation generation is successful.
|
|
31
|
+
* Contains the file path and metadata about the generated presentation.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const result = await neurolink.generate({
|
|
36
|
+
* input: { text: "Introducing Our New Product" },
|
|
37
|
+
* provider: "vertex",
|
|
38
|
+
* output: { mode: "ppt", ppt: { pages: 10, theme: "modern" } }
|
|
39
|
+
* });
|
|
40
|
+
*
|
|
41
|
+
* if (result.ppt) {
|
|
42
|
+
* console.log(`Presentation saved: ${result.ppt.filePath}`);
|
|
43
|
+
* console.log(`Total slides: ${result.ppt.totalSlides}`);
|
|
44
|
+
* console.log(`Theme: ${result.ppt.metadata?.theme}`);
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export type PPTGenerationResult = {
|
|
49
|
+
/** Path to the generated PPTX file */
|
|
50
|
+
filePath: string;
|
|
51
|
+
/** Total number of slides in the presentation */
|
|
52
|
+
totalSlides: number;
|
|
53
|
+
/** Output format (always "pptx" currently) */
|
|
54
|
+
format: OutputFormatOption;
|
|
55
|
+
/** Presentation metadata */
|
|
56
|
+
metadata?: {
|
|
57
|
+
/** Theme/style used */
|
|
58
|
+
theme?: string;
|
|
59
|
+
/** Target audience */
|
|
60
|
+
audience?: string;
|
|
61
|
+
/** Presentation tone */
|
|
62
|
+
tone?: string;
|
|
63
|
+
/** Model used for image generation */
|
|
64
|
+
imageModel?: string;
|
|
65
|
+
/** File size in bytes */
|
|
66
|
+
fileSize?: number;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|