@umituz/web-ai 1.0.2 → 1.0.4
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/dist/{AIErrors-B2ogd1PB.d.ts → AIErrors-CLjXRty-.d.ts} +99 -92
- package/dist/{AIErrors-C5EasV_c.d.cts → AIErrors-DjZXUiLT.d.cts} +99 -92
- package/dist/PromptBuilder-B5Lm1znm.d.ts +215 -0
- package/dist/PromptBuilder-DIUZlfV6.d.cts +215 -0
- package/dist/{text-generation.service-BQO3Tvz2.d.cts → cancellation-error-Cj7fJyCg.d.cts} +66 -3
- package/dist/{text-generation.service-CcYQM6Ch.d.ts → cancellation-error-CkMn5Kft.d.ts} +66 -3
- package/dist/chat/index.cjs.map +1 -1
- package/dist/chat/index.d.cts +3 -3
- package/dist/chat/index.d.ts +3 -3
- package/dist/chat/index.js.map +1 -1
- package/dist/{chat.service-CL1qtYxe.d.cts → chat.service-9M-KpLo-.d.cts} +1 -1
- package/dist/{chat.service-q05GnhEm.d.ts → chat.service-B4FLFxR1.d.ts} +1 -1
- package/dist/content/index.cjs +1582 -452
- package/dist/content/index.cjs.map +1 -1
- package/dist/content/index.d.cts +165 -219
- package/dist/content/index.d.ts +165 -219
- package/dist/content/index.js +1525 -453
- package/dist/content/index.js.map +1 -1
- package/dist/{create-ai-9PW752z8.d.ts → create-ai-0eZFFqTw.d.ts} +1 -1
- package/dist/{create-ai-CXYKPK6G.d.cts → create-ai-BrKR_5fQ.d.cts} +1 -1
- package/dist/hooks/index.cjs +924 -31
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.cts +44 -8
- package/dist/hooks/index.d.ts +44 -8
- package/dist/hooks/index.js +919 -32
- package/dist/hooks/index.js.map +1 -1
- package/dist/index-BUVULo3h.d.ts +177 -0
- package/dist/index-BulSkB2L.d.cts +177 -0
- package/dist/index.cjs +3057 -563
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -5
- package/dist/index.d.ts +8 -5
- package/dist/index.js +3011 -564
- package/dist/index.js.map +1 -1
- package/dist/{provider.factory-DfDfk0dK.d.ts → provider.factory-BwPnqSEU.d.ts} +3 -1
- package/dist/{provider.factory-BxsfK4WW.d.cts → provider.factory-DyfJtR8M.d.cts} +3 -1
- package/dist/providers/index.cjs +818 -27
- package/dist/providers/index.cjs.map +1 -1
- package/dist/providers/index.d.cts +50 -8
- package/dist/providers/index.d.ts +50 -8
- package/dist/providers/index.js +806 -28
- package/dist/providers/index.js.map +1 -1
- package/dist/wizard/index.d.cts +2 -86
- package/dist/wizard/index.d.ts +2 -86
- package/package.json +21 -1
- package/dist/ContentGeneration-B6iK2B33.d.cts +0 -93
- package/dist/ContentGeneration-B6iK2B33.d.ts +0 -93
package/dist/index.cjs
CHANGED
|
@@ -1084,6 +1084,26 @@ function isMediaProvider(provider) {
|
|
|
1084
1084
|
return provider.capabilities.includes("image") || provider.capabilities.includes("video");
|
|
1085
1085
|
}
|
|
1086
1086
|
|
|
1087
|
+
// src/content/domain/errors/AIErrors.ts
|
|
1088
|
+
var ValidationError = class extends AIError {
|
|
1089
|
+
constructor(message) {
|
|
1090
|
+
super(message, "VALIDATION_ERROR");
|
|
1091
|
+
this.name = "ValidationError";
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
var ProviderUnavailableError = class extends AIError {
|
|
1095
|
+
constructor(providerId) {
|
|
1096
|
+
super(`Provider ${providerId} is not available`, "PROVIDER_UNAVAILABLE");
|
|
1097
|
+
this.name = "ProviderUnavailableError";
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
var ProviderQuotaExhaustedError = class extends AIError {
|
|
1101
|
+
constructor(providerId) {
|
|
1102
|
+
super(`Provider ${providerId} quota exhausted`, "PROVIDER_QUOTA_EXHAUSTED");
|
|
1103
|
+
this.name = "ProviderQuotaExhaustedError";
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
|
|
1087
1107
|
// src/content/domain/limits/ProviderTimingConfig.ts
|
|
1088
1108
|
var ProviderTimingConfig = {
|
|
1089
1109
|
DEFAULT_TIMEOUT_MS: 3e4,
|
|
@@ -1426,6 +1446,9 @@ var ProviderFactory = class {
|
|
|
1426
1446
|
);
|
|
1427
1447
|
}
|
|
1428
1448
|
};
|
|
1449
|
+
function createProviderFactory(config) {
|
|
1450
|
+
return new ProviderFactory(config);
|
|
1451
|
+
}
|
|
1429
1452
|
|
|
1430
1453
|
// src/providers/anthropic/anthropic.constants.ts
|
|
1431
1454
|
var ANTHROPIC_MODELS = {
|
|
@@ -2455,6 +2478,9 @@ function toGroqMessages(messages, systemPrompt) {
|
|
|
2455
2478
|
}
|
|
2456
2479
|
return wire;
|
|
2457
2480
|
}
|
|
2481
|
+
function createGroqProvider(config) {
|
|
2482
|
+
return new GroqProvider(config);
|
|
2483
|
+
}
|
|
2458
2484
|
function findLastIndex2(items, predicate) {
|
|
2459
2485
|
for (let i = items.length - 1; i >= 0; i--) {
|
|
2460
2486
|
if (predicate(items[i])) return i;
|
|
@@ -2721,6 +2747,128 @@ function createGeminiProvider(config) {
|
|
|
2721
2747
|
return new GeminiProvider(config);
|
|
2722
2748
|
}
|
|
2723
2749
|
|
|
2750
|
+
// src/content/domain/predicates/KeywordProminenceClassifier.ts
|
|
2751
|
+
var HIGH_DENSITY_THRESHOLD = 2;
|
|
2752
|
+
var MEDIUM_DENSITY_THRESHOLD = 1;
|
|
2753
|
+
function classifyKeywordProminence(densityPercent) {
|
|
2754
|
+
if (densityPercent > HIGH_DENSITY_THRESHOLD) return "high";
|
|
2755
|
+
if (densityPercent > MEDIUM_DENSITY_THRESHOLD) return "medium";
|
|
2756
|
+
return "low";
|
|
2757
|
+
}
|
|
2758
|
+
function isUnderutilizedKeyword(densityPercent) {
|
|
2759
|
+
return densityPercent < 1;
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
// src/content/domain/types/index.ts
|
|
2763
|
+
var PLATFORM_SPECS = {
|
|
2764
|
+
twitter: {
|
|
2765
|
+
maxLength: 280,
|
|
2766
|
+
style: "concise, engaging, thread-friendly",
|
|
2767
|
+
emojiSupport: true,
|
|
2768
|
+
hashtagSupport: true,
|
|
2769
|
+
imageSupport: true,
|
|
2770
|
+
videoSupport: true,
|
|
2771
|
+
maxHashtags: 5
|
|
2772
|
+
},
|
|
2773
|
+
linkedin: {
|
|
2774
|
+
maxLength: 3e3,
|
|
2775
|
+
style: "professional, insightful, industry-focused",
|
|
2776
|
+
emojiSupport: true,
|
|
2777
|
+
hashtagSupport: true,
|
|
2778
|
+
imageSupport: true,
|
|
2779
|
+
videoSupport: true,
|
|
2780
|
+
maxHashtags: 5
|
|
2781
|
+
},
|
|
2782
|
+
instagram: {
|
|
2783
|
+
maxLength: 2200,
|
|
2784
|
+
style: "visual, engaging, emoji-rich",
|
|
2785
|
+
emojiSupport: true,
|
|
2786
|
+
hashtagSupport: true,
|
|
2787
|
+
imageSupport: true,
|
|
2788
|
+
videoSupport: true,
|
|
2789
|
+
maxHashtags: 30
|
|
2790
|
+
},
|
|
2791
|
+
threads: {
|
|
2792
|
+
maxLength: 500,
|
|
2793
|
+
style: "conversational, community-driven",
|
|
2794
|
+
emojiSupport: true,
|
|
2795
|
+
hashtagSupport: true,
|
|
2796
|
+
imageSupport: true,
|
|
2797
|
+
videoSupport: true,
|
|
2798
|
+
maxHashtags: 5
|
|
2799
|
+
},
|
|
2800
|
+
tiktok: {
|
|
2801
|
+
maxLength: 150,
|
|
2802
|
+
style: "catchy, trending, short-form",
|
|
2803
|
+
emojiSupport: true,
|
|
2804
|
+
hashtagSupport: true,
|
|
2805
|
+
imageSupport: false,
|
|
2806
|
+
videoSupport: true,
|
|
2807
|
+
maxHashtags: 5
|
|
2808
|
+
},
|
|
2809
|
+
facebook: {
|
|
2810
|
+
maxLength: 63206,
|
|
2811
|
+
style: "conversational, engaging, shareable",
|
|
2812
|
+
emojiSupport: true,
|
|
2813
|
+
hashtagSupport: true,
|
|
2814
|
+
imageSupport: true,
|
|
2815
|
+
videoSupport: true,
|
|
2816
|
+
maxHashtags: 5
|
|
2817
|
+
}
|
|
2818
|
+
};
|
|
2819
|
+
|
|
2820
|
+
// src/content/domain/predicates/HashtagPriorityRanker.ts
|
|
2821
|
+
var DEFAULT_HASHTAG_BUDGET = Number.MAX_SAFE_INTEGER;
|
|
2822
|
+
function resolveHashtagBudget(platform) {
|
|
2823
|
+
return PLATFORM_SPECS[platform]?.maxHashtags ?? DEFAULT_HASHTAG_BUDGET;
|
|
2824
|
+
}
|
|
2825
|
+
function rankAndTrimHashtags(hashtags, budget) {
|
|
2826
|
+
if (hashtags.length <= budget) return [...hashtags];
|
|
2827
|
+
return [...hashtags].sort((a, b) => b.length - a.length).slice(0, budget);
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
// src/content/domain/predicates/QualityRecommenderByRatio.ts
|
|
2831
|
+
var WIDE_RATIOS = /* @__PURE__ */ new Set(["16:9", "4:3"]);
|
|
2832
|
+
function recommendQualityForAspectRatio(ratio) {
|
|
2833
|
+
return WIDE_RATIOS.has(ratio) ? "hd" : "standard";
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
// src/content/domain/predicates/PlatformSpecResolver.ts
|
|
2837
|
+
var FALLBACK_SPEC = {
|
|
2838
|
+
maxLength: 280,
|
|
2839
|
+
style: "concise, engaging, audience-friendly",
|
|
2840
|
+
emojiSupport: true,
|
|
2841
|
+
hashtagSupport: true,
|
|
2842
|
+
imageSupport: true,
|
|
2843
|
+
videoSupport: true,
|
|
2844
|
+
maxHashtags: 5
|
|
2845
|
+
};
|
|
2846
|
+
function resolvePlatformSpec(platform) {
|
|
2847
|
+
return PLATFORM_SPECS[platform] ?? FALLBACK_SPEC;
|
|
2848
|
+
}
|
|
2849
|
+
function resolveMaxContentLength(platform, override) {
|
|
2850
|
+
return override ?? resolvePlatformSpec(platform).maxLength;
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
// src/content/domain/predicates/ScoreRangeGuard.ts
|
|
2854
|
+
var SCORE_MIN = 0;
|
|
2855
|
+
var SCORE_MAX = 100;
|
|
2856
|
+
var SCORE_FALLBACK = 50;
|
|
2857
|
+
var CONFIDENCE_MIN = 0;
|
|
2858
|
+
var CONFIDENCE_MAX = 1;
|
|
2859
|
+
var CONFIDENCE_FALLBACK = 0.5;
|
|
2860
|
+
function clampScoreToValidRange(score, fallback = SCORE_FALLBACK) {
|
|
2861
|
+
if (!Number.isFinite(score)) return fallback;
|
|
2862
|
+
return Math.max(SCORE_MIN, Math.min(SCORE_MAX, score));
|
|
2863
|
+
}
|
|
2864
|
+
function clampConfidenceToValidRange(confidence, fallback = CONFIDENCE_FALLBACK) {
|
|
2865
|
+
if (!Number.isFinite(confidence)) return fallback;
|
|
2866
|
+
return Math.max(CONFIDENCE_MIN, Math.min(CONFIDENCE_MAX, confidence));
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2869
|
+
// src/content/domain/predicates/AbTestConfidence.ts
|
|
2870
|
+
var AB_TEST_DEFAULT_CONFIDENCE = 0.7;
|
|
2871
|
+
|
|
2724
2872
|
// src/content/domain/predicates/FalImageRenderingOptions.ts
|
|
2725
2873
|
var FAL_FORMAT_TO_IMAGE_SIZE = {
|
|
2726
2874
|
square: "square_hd",
|
|
@@ -2917,674 +3065,2975 @@ function createFalProvider(config) {
|
|
|
2917
3065
|
return new FalProvider(config);
|
|
2918
3066
|
}
|
|
2919
3067
|
|
|
2920
|
-
// src/providers/pruna/
|
|
2921
|
-
var
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
3068
|
+
// src/providers/pruna/client/core/constants/limits.ts
|
|
3069
|
+
var DEFAULT_ASPECT_RATIO = "16:9";
|
|
3070
|
+
var P_VIDEO_DEFAULTS = {
|
|
3071
|
+
duration: 5,
|
|
3072
|
+
resolution: "720p",
|
|
3073
|
+
fps: 24,
|
|
3074
|
+
draft: false,
|
|
3075
|
+
promptUpsampling: true
|
|
3076
|
+
};
|
|
3077
|
+
var POLL_DEFAULTS = {
|
|
3078
|
+
intervalMs: 3e3,
|
|
3079
|
+
maxAttempts: 120,
|
|
3080
|
+
maxIntervalMs: 1e4,
|
|
3081
|
+
backoffBase: 1.5,
|
|
3082
|
+
jitterPercent: 0.1
|
|
3083
|
+
};
|
|
3084
|
+
var DEFAULT_DEDUPLICATOR_TTL_MS = 3e4;
|
|
3085
|
+
var DEDUPLICATOR_CLEANUP_GRACE_MS = 100;
|
|
3086
|
+
|
|
3087
|
+
// src/providers/pruna/client/core/constants/network.ts
|
|
3088
|
+
var HTTPS_PREFIX = "https://";
|
|
3089
|
+
var HTTP_PREFIX = "http://";
|
|
3090
|
+
var BASE64_MARKER = "base64,";
|
|
3091
|
+
var PRUNA_BASE_URL = "https://api.pruna.ai";
|
|
3092
|
+
var PRUNA_PREDICTIONS_URL = `${PRUNA_BASE_URL}/v1/predictions`;
|
|
3093
|
+
var PRUNA_FILES_URL = `${PRUNA_BASE_URL}/v1/files`;
|
|
3094
|
+
var AUTH_FAILURE_STATUSES = [401, 403];
|
|
3095
|
+
var MIN_API_KEY_LENGTH = 1;
|
|
3096
|
+
var MAX_API_KEY_LENGTH = 256;
|
|
3097
|
+
|
|
3098
|
+
// src/providers/pruna/client/core/constants/mime-types.ts
|
|
3099
|
+
var MIME_PNG = "image/png";
|
|
3100
|
+
var MIME_JPEG = "image/jpeg";
|
|
3101
|
+
var MIME_WEBP = "image/webp";
|
|
3102
|
+
var DEFAULT_IMAGE_MIME = MIME_PNG;
|
|
3103
|
+
var MIME_FILE_EXTENSION = {
|
|
3104
|
+
[MIME_PNG]: "png",
|
|
3105
|
+
[MIME_JPEG]: "jpg",
|
|
3106
|
+
[MIME_WEBP]: "webp"
|
|
3107
|
+
};
|
|
3108
|
+
var IMAGE_SIGNATURES = {
|
|
3109
|
+
PNG: [137, 80],
|
|
3110
|
+
JPEG: [255, 216],
|
|
3111
|
+
WEBP_RIFF: [82, 73]
|
|
3112
|
+
};
|
|
3113
|
+
|
|
3114
|
+
// src/providers/pruna/client/core/errors/cancellation-error.ts
|
|
3115
|
+
var CANCELLATION_MESSAGE = "Request cancelled by user";
|
|
3116
|
+
var CancellationError = class extends Error {
|
|
3117
|
+
constructor(message = CANCELLATION_MESSAGE) {
|
|
3118
|
+
super(message);
|
|
3119
|
+
this.name = "CancellationError";
|
|
3120
|
+
}
|
|
3121
|
+
};
|
|
3122
|
+
|
|
3123
|
+
// src/providers/pruna/client/core/errors/authentication-error.ts
|
|
3124
|
+
var AUTHENTICATION_MESSAGE = "Authentication failed. Please check your API key.";
|
|
3125
|
+
var AuthenticationError = class extends Error {
|
|
3126
|
+
constructor(message = AUTHENTICATION_MESSAGE) {
|
|
3127
|
+
super(message);
|
|
3128
|
+
this.name = "AuthenticationError";
|
|
3129
|
+
}
|
|
3130
|
+
};
|
|
3131
|
+
|
|
3132
|
+
// src/providers/pruna/client/core/errors/timeout-error.ts
|
|
3133
|
+
var TIMEOUT_MESSAGE = "Generation timed out. Maximum polling attempts reached.";
|
|
3134
|
+
var TimeoutError = class extends Error {
|
|
3135
|
+
constructor(message = TIMEOUT_MESSAGE) {
|
|
3136
|
+
super(message);
|
|
3137
|
+
this.name = "TimeoutError";
|
|
3138
|
+
}
|
|
3139
|
+
};
|
|
3140
|
+
|
|
3141
|
+
// src/providers/pruna/client/core/errors/validation-error.ts
|
|
3142
|
+
var ValidationError2 = class extends Error {
|
|
3143
|
+
field;
|
|
3144
|
+
reason;
|
|
3145
|
+
receivedValue;
|
|
3146
|
+
constructor(context) {
|
|
3147
|
+
super(`Invalid ${context.field}: ${context.reason}`);
|
|
3148
|
+
this.name = "ValidationError";
|
|
3149
|
+
this.field = context.field;
|
|
3150
|
+
this.reason = context.reason;
|
|
3151
|
+
this.receivedValue = context.receivedValue;
|
|
3152
|
+
}
|
|
3153
|
+
};
|
|
3154
|
+
|
|
3155
|
+
// src/providers/pruna/client/core/utils/base64.ts
|
|
3156
|
+
var globalScope = globalThis;
|
|
3157
|
+
var hasBuffer = typeof globalScope.Buffer?.from === "function";
|
|
3158
|
+
function base64ToBytes(raw) {
|
|
3159
|
+
if (hasBuffer) {
|
|
3160
|
+
const bufferFactory = globalScope.Buffer;
|
|
3161
|
+
if (!bufferFactory) {
|
|
3162
|
+
throw new Error("Buffer unavailable despite detection flag");
|
|
3163
|
+
}
|
|
3164
|
+
const buf = bufferFactory.from(raw, "base64");
|
|
3165
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
2926
3166
|
}
|
|
2927
|
-
|
|
3167
|
+
const decoded = atob(raw);
|
|
3168
|
+
return Uint8Array.from(decoded, (char) => char.charCodeAt(0));
|
|
2928
3169
|
}
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
if (
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
3170
|
+
|
|
3171
|
+
// src/providers/pruna/client/core/utils/mime-detector.ts
|
|
3172
|
+
var MIN_SIGNATURE_BYTES = 2;
|
|
3173
|
+
function matchesSignature(bytes, signature) {
|
|
3174
|
+
if (bytes.length < signature.length) return false;
|
|
3175
|
+
return signature.every((value, index) => bytes[index] === value);
|
|
3176
|
+
}
|
|
3177
|
+
function detectImageMime(bytes) {
|
|
3178
|
+
if (bytes.length < MIN_SIGNATURE_BYTES) return DEFAULT_IMAGE_MIME;
|
|
3179
|
+
if (matchesSignature(bytes, IMAGE_SIGNATURES.PNG)) return MIME_PNG;
|
|
3180
|
+
if (matchesSignature(bytes, IMAGE_SIGNATURES.JPEG)) return MIME_JPEG;
|
|
3181
|
+
if (matchesSignature(bytes, IMAGE_SIGNATURES.WEBP_RIFF)) return MIME_WEBP;
|
|
3182
|
+
return DEFAULT_IMAGE_MIME;
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
// src/providers/pruna/client/core/utils/api-error-parser.ts
|
|
3186
|
+
async function parseApiError(response, fallback) {
|
|
3187
|
+
const payload = await response.json().catch(() => ({ message: response.statusText }));
|
|
3188
|
+
const message = payload.message ?? `${fallback}: ${response.status}`;
|
|
3189
|
+
const error = new Error(message);
|
|
3190
|
+
error.statusCode = response.status;
|
|
3191
|
+
return error;
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
// src/providers/pruna/client/core/protocol/http-status.ts
|
|
3195
|
+
function isAuthFailureStatus(status) {
|
|
3196
|
+
return AUTH_FAILURE_STATUSES.includes(status);
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
// src/providers/pruna/client/core/protocol/job-status.ts
|
|
3200
|
+
var TERMINAL_SUCCESS_STATUSES = ["succeeded", "completed"];
|
|
3201
|
+
var TERMINAL_FAILURE_STATUSES = ["failed"];
|
|
3202
|
+
function isTerminalSuccessStatus(status) {
|
|
3203
|
+
if (!status) return false;
|
|
3204
|
+
return TERMINAL_SUCCESS_STATUSES.includes(status);
|
|
3205
|
+
}
|
|
3206
|
+
function isTerminalFailureStatus(status) {
|
|
3207
|
+
if (!status) return false;
|
|
3208
|
+
return TERMINAL_FAILURE_STATUSES.includes(status);
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
// src/providers/pruna/client/core/protocol/abort-signal-classifier.ts
|
|
3212
|
+
function isAbortError(err) {
|
|
3213
|
+
if (err instanceof DOMException && err.code === DOMException.ABORT_ERR) return true;
|
|
3214
|
+
if (err instanceof Error && err.name === "AbortError") return true;
|
|
3215
|
+
return false;
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
// src/providers/pruna/client/core/protocol/pruna-url-builder.ts
|
|
3219
|
+
function isAbsoluteHttpUrl(value) {
|
|
3220
|
+
return value.startsWith(HTTPS_PREFIX) || value.startsWith(HTTP_PREFIX);
|
|
3221
|
+
}
|
|
3222
|
+
function isHttpsUrl(value) {
|
|
3223
|
+
return value.startsWith(HTTPS_PREFIX);
|
|
3224
|
+
}
|
|
3225
|
+
function buildPrunaApiUrl(pathOrUrl) {
|
|
3226
|
+
if (isAbsoluteHttpUrl(pathOrUrl)) return pathOrUrl;
|
|
3227
|
+
return `${PRUNA_BASE_URL}${pathOrUrl}`;
|
|
3228
|
+
}
|
|
3229
|
+
function buildFileUrlById(fileId) {
|
|
3230
|
+
return `${PRUNA_FILES_URL}/${fileId}`;
|
|
3231
|
+
}
|
|
3232
|
+
|
|
3233
|
+
// src/providers/pruna/client/core/math/exponential-backoff.ts
|
|
3234
|
+
function calculateExponentialBackoff2(inputs) {
|
|
3235
|
+
const {
|
|
3236
|
+
attempt,
|
|
3237
|
+
baseIntervalMs,
|
|
3238
|
+
maxIntervalMs = POLL_DEFAULTS.maxIntervalMs,
|
|
3239
|
+
jitterPercent = POLL_DEFAULTS.jitterPercent,
|
|
3240
|
+
growthBase = POLL_DEFAULTS.backoffBase
|
|
3241
|
+
} = inputs;
|
|
3242
|
+
const safeAttempt = Math.max(0, attempt);
|
|
3243
|
+
const safeBase = Math.max(0, baseIntervalMs);
|
|
3244
|
+
const exponentialDelay = Math.min(safeBase * Math.pow(growthBase, safeAttempt), maxIntervalMs);
|
|
3245
|
+
const jitter = exponentialDelay * jitterPercent * Math.random();
|
|
3246
|
+
return exponentialDelay + jitter;
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
// src/providers/pruna/client/core/utils/uri-resolver.ts
|
|
3250
|
+
var uriCache = /* @__PURE__ */ new WeakMap();
|
|
3251
|
+
function stripBase64Prefix(image) {
|
|
3252
|
+
if (isHttpsUrl(image)) return image;
|
|
3253
|
+
const base64Index = image.indexOf(BASE64_MARKER);
|
|
3254
|
+
return base64Index !== -1 ? image.slice(base64Index + BASE64_MARKER.length) : image;
|
|
3255
|
+
}
|
|
3256
|
+
function readOutput(output) {
|
|
3257
|
+
if (typeof output === "string") return output;
|
|
3258
|
+
if (Array.isArray(output) && output.length > 0) return output[0] ?? null;
|
|
3259
|
+
if (output !== null && typeof output === "object" && "url" in output) {
|
|
3260
|
+
return output.url;
|
|
2946
3261
|
}
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
3262
|
+
return null;
|
|
3263
|
+
}
|
|
3264
|
+
function extractUri(data) {
|
|
3265
|
+
const cached = uriCache.get(data);
|
|
3266
|
+
if (cached !== void 0) return cached;
|
|
3267
|
+
if (!isTerminalSuccessStatus(data.status)) {
|
|
3268
|
+
uriCache.set(data, null);
|
|
3269
|
+
return null;
|
|
2950
3270
|
}
|
|
2951
|
-
const
|
|
2952
|
-
|
|
3271
|
+
const result = data.generation_url ?? readOutput(data.output) ?? data.video_url ?? data.data ?? null;
|
|
3272
|
+
uriCache.set(data, result);
|
|
3273
|
+
return result;
|
|
3274
|
+
}
|
|
3275
|
+
function resolveUri(uri) {
|
|
3276
|
+
return uri.startsWith("/") ? `${PRUNA_BASE_URL}${uri}` : uri;
|
|
2953
3277
|
}
|
|
2954
3278
|
|
|
2955
|
-
// src/providers/pruna/pruna.
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
3279
|
+
// src/providers/pruna/client/core/services/pruna-client.service.ts
|
|
3280
|
+
function buildFileExtension(mime) {
|
|
3281
|
+
return MIME_FILE_EXTENSION[mime] ?? "bin";
|
|
3282
|
+
}
|
|
3283
|
+
async function uploadImage(base64OrUrl, apiKey, onProgress) {
|
|
3284
|
+
if (isHttpsUrl(base64OrUrl)) return base64OrUrl;
|
|
3285
|
+
if (base64OrUrl.startsWith(HTTP_PREFIX)) {
|
|
3286
|
+
throw new Error("Only HTTPS URLs are supported for security.");
|
|
3287
|
+
}
|
|
3288
|
+
onProgress?.("uploading");
|
|
3289
|
+
const raw = stripBase64Prefix(base64OrUrl);
|
|
3290
|
+
let bytes;
|
|
3291
|
+
try {
|
|
3292
|
+
bytes = base64ToBytes(raw);
|
|
3293
|
+
} catch {
|
|
3294
|
+
throw new Error("Invalid image format. Provide base64 or a valid HTTPS URL.");
|
|
2959
3295
|
}
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
3296
|
+
const mime = detectImageMime(bytes);
|
|
3297
|
+
const buffer = bytes.buffer.slice(
|
|
3298
|
+
bytes.byteOffset,
|
|
3299
|
+
bytes.byteOffset + bytes.byteLength
|
|
3300
|
+
);
|
|
3301
|
+
const blob = new Blob([buffer], { type: mime });
|
|
3302
|
+
const formData = new FormData();
|
|
3303
|
+
formData.append("content", blob, `upload.${buildFileExtension(mime)}`);
|
|
3304
|
+
const res = await fetch(PRUNA_FILES_URL, {
|
|
3305
|
+
method: "POST",
|
|
3306
|
+
headers: { apikey: apiKey },
|
|
3307
|
+
body: formData
|
|
3308
|
+
});
|
|
3309
|
+
if (!res.ok) {
|
|
3310
|
+
throw await parseApiError(res, "File upload error");
|
|
2966
3311
|
}
|
|
2967
|
-
|
|
2968
|
-
|
|
3312
|
+
const data = await res.json();
|
|
3313
|
+
return data.urls?.get ?? buildFileUrlById(data.id ?? "");
|
|
3314
|
+
}
|
|
3315
|
+
async function submitPrediction(model, input, apiKey, signal, onProgress) {
|
|
3316
|
+
onProgress?.("predicting");
|
|
3317
|
+
const res = await fetch(PRUNA_PREDICTIONS_URL, {
|
|
3318
|
+
method: "POST",
|
|
3319
|
+
headers: {
|
|
3320
|
+
apikey: apiKey,
|
|
3321
|
+
Model: model,
|
|
3322
|
+
"Try-Sync": "true",
|
|
3323
|
+
"Content-Type": "application/json"
|
|
3324
|
+
},
|
|
3325
|
+
body: JSON.stringify({ input }),
|
|
3326
|
+
signal
|
|
3327
|
+
});
|
|
3328
|
+
if (!res.ok) {
|
|
3329
|
+
throw await parseApiError(res, "API error");
|
|
2969
3330
|
}
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
)) {
|
|
2977
|
-
if (stage.error) throw stage.error;
|
|
2978
|
-
if (stage.result?.url) finalUrl = stage.result.url;
|
|
2979
|
-
}
|
|
2980
|
-
if (!finalUrl) {
|
|
2981
|
-
throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No image generated by Pruna AI");
|
|
2982
|
-
}
|
|
2983
|
-
return {
|
|
2984
|
-
id: Date.now().toString(),
|
|
2985
|
-
type: "image",
|
|
2986
|
-
url: finalUrl,
|
|
2987
|
-
metadata: { provider: this.id, model: model ?? "p-image" },
|
|
2988
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2989
|
-
};
|
|
3331
|
+
return res.json();
|
|
3332
|
+
}
|
|
3333
|
+
var GenerationFailedError = class extends Error {
|
|
3334
|
+
constructor(message) {
|
|
3335
|
+
super(message);
|
|
3336
|
+
this.name = "GenerationFailedError";
|
|
2990
3337
|
}
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
))
|
|
2998
|
-
|
|
2999
|
-
if (stage.result?.url) finalUrl = stage.result.url;
|
|
3000
|
-
}
|
|
3001
|
-
if (!finalUrl) {
|
|
3002
|
-
throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated by Pruna AI");
|
|
3003
|
-
}
|
|
3004
|
-
return {
|
|
3005
|
-
id: Date.now().toString(),
|
|
3006
|
-
type: "video",
|
|
3007
|
-
url: finalUrl,
|
|
3008
|
-
metadata: { provider: this.id, model: model ?? "p-video" },
|
|
3009
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3010
|
-
};
|
|
3338
|
+
};
|
|
3339
|
+
async function attemptPoll(fullUrl, apiKey, signal) {
|
|
3340
|
+
let res;
|
|
3341
|
+
try {
|
|
3342
|
+
res = await fetch(fullUrl, { headers: { apikey: apiKey }, signal });
|
|
3343
|
+
} catch (err) {
|
|
3344
|
+
if (isAbortError(err)) throw new CancellationError();
|
|
3345
|
+
return { outcome: "transient-failure" };
|
|
3011
3346
|
}
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
}
|
|
3021
|
-
|
|
3022
|
-
|
|
3347
|
+
if (!res.ok) {
|
|
3348
|
+
if (isAuthFailureStatus(res.status)) throw new AuthenticationError();
|
|
3349
|
+
return { outcome: "transient-failure" };
|
|
3350
|
+
}
|
|
3351
|
+
let data;
|
|
3352
|
+
try {
|
|
3353
|
+
data = await res.json();
|
|
3354
|
+
} catch {
|
|
3355
|
+
return { outcome: "transient-failure" };
|
|
3356
|
+
}
|
|
3357
|
+
if (isTerminalSuccessStatus(data.status)) {
|
|
3358
|
+
const uri = extractUri(data);
|
|
3359
|
+
if (uri) return { outcome: "succeeded", uri: resolveUri(uri) };
|
|
3360
|
+
return { outcome: "transient-failure" };
|
|
3361
|
+
}
|
|
3362
|
+
if (isTerminalFailureStatus(data.status)) {
|
|
3363
|
+
throw new GenerationFailedError(data.error ?? "Generation failed during processing.");
|
|
3364
|
+
}
|
|
3365
|
+
return { outcome: "continue" };
|
|
3366
|
+
}
|
|
3367
|
+
async function pollForResult(pollUrl, apiKey, maxAttempts = POLL_DEFAULTS.maxAttempts, intervalMs = POLL_DEFAULTS.intervalMs, signal, onProgress) {
|
|
3368
|
+
const fullUrl = isAbsoluteHttpUrl(pollUrl) ? pollUrl : buildPrunaApiUrl(pollUrl);
|
|
3369
|
+
const controller = new AbortController();
|
|
3370
|
+
const onExternalAbort = () => controller.abort();
|
|
3371
|
+
signal?.addEventListener("abort", onExternalAbort);
|
|
3372
|
+
try {
|
|
3373
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
3374
|
+
if (signal?.aborted) throw new CancellationError();
|
|
3375
|
+
const delay = calculateExponentialBackoff2({ attempt: i, baseIntervalMs: intervalMs });
|
|
3376
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
3377
|
+
if (signal?.aborted) throw new CancellationError();
|
|
3378
|
+
onProgress?.("polling", i + 1);
|
|
3379
|
+
const result = await attemptPoll(fullUrl, apiKey, controller.signal);
|
|
3380
|
+
if (result.outcome === "succeeded" && result.uri) {
|
|
3381
|
+
return result.uri;
|
|
3382
|
+
}
|
|
3023
3383
|
}
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
metadata: { provider: this.id, model: "p-image-to-video" },
|
|
3029
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3030
|
-
};
|
|
3384
|
+
throw new TimeoutError();
|
|
3385
|
+
} finally {
|
|
3386
|
+
signal?.removeEventListener("abort", onExternalAbort);
|
|
3387
|
+
controller.abort();
|
|
3031
3388
|
}
|
|
3032
|
-
|
|
3033
|
-
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
// src/providers/pruna/client/core/math/comparison-hash.ts
|
|
3392
|
+
function sortKeysDeep(value) {
|
|
3393
|
+
if (Array.isArray(value)) {
|
|
3394
|
+
return value.map(sortKeysDeep);
|
|
3395
|
+
}
|
|
3396
|
+
if (value !== null && typeof value === "object") {
|
|
3397
|
+
const record = value;
|
|
3398
|
+
return Object.keys(record).sort().reduce((acc, key) => {
|
|
3399
|
+
acc[key] = sortKeysDeep(record[key]);
|
|
3400
|
+
return acc;
|
|
3401
|
+
}, {});
|
|
3034
3402
|
}
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3403
|
+
return value;
|
|
3404
|
+
}
|
|
3405
|
+
function hashValue(value) {
|
|
3406
|
+
if (value === null || typeof value !== "object") {
|
|
3407
|
+
return String(value);
|
|
3408
|
+
}
|
|
3409
|
+
try {
|
|
3410
|
+
return JSON.stringify(sortKeysDeep(value));
|
|
3411
|
+
} catch {
|
|
3412
|
+
return String(value);
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
function compositeHash(...values) {
|
|
3416
|
+
return values.map(hashValue).join("|");
|
|
3038
3417
|
}
|
|
3039
3418
|
|
|
3040
|
-
// src/providers/
|
|
3041
|
-
function
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
if (
|
|
3048
|
-
|
|
3419
|
+
// src/providers/pruna/client/core/math/cache-key-derivation.ts
|
|
3420
|
+
function deriveGenerationCacheKey(apiKey, input) {
|
|
3421
|
+
return `${apiKey}:${compositeHash(input)}`;
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3424
|
+
// src/providers/pruna/client/core/validation/api-key-validator.ts
|
|
3425
|
+
function assertValidApiKey(apiKey) {
|
|
3426
|
+
if (typeof apiKey !== "string") {
|
|
3427
|
+
throw new ValidationError2({
|
|
3428
|
+
field: "apiKey",
|
|
3429
|
+
reason: "must be a string",
|
|
3430
|
+
receivedValue: typeof apiKey
|
|
3431
|
+
});
|
|
3049
3432
|
}
|
|
3050
|
-
if (
|
|
3051
|
-
|
|
3433
|
+
if (apiKey.length < MIN_API_KEY_LENGTH) {
|
|
3434
|
+
throw new ValidationError2({
|
|
3435
|
+
field: "apiKey",
|
|
3436
|
+
reason: "must not be empty",
|
|
3437
|
+
receivedValue: apiKey
|
|
3438
|
+
});
|
|
3052
3439
|
}
|
|
3053
|
-
if (
|
|
3054
|
-
|
|
3440
|
+
if (apiKey.length > MAX_API_KEY_LENGTH) {
|
|
3441
|
+
throw new ValidationError2({
|
|
3442
|
+
field: "apiKey",
|
|
3443
|
+
reason: `exceeds maximum length of ${MAX_API_KEY_LENGTH}`,
|
|
3444
|
+
receivedValue: apiKey.length
|
|
3445
|
+
});
|
|
3055
3446
|
}
|
|
3056
|
-
|
|
3057
|
-
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
// src/providers/pruna/client/core/validation/image-source-validator.ts
|
|
3450
|
+
function classifyImageSource(source) {
|
|
3451
|
+
if (source.startsWith(HTTPS_PREFIX)) return "https-url";
|
|
3452
|
+
if (source.startsWith(HTTP_PREFIX)) return "http-url";
|
|
3453
|
+
if (source.includes(BASE64_MARKER) || /^[A-Za-z0-9+/=]+$/.test(source)) return "base64";
|
|
3454
|
+
return "unknown";
|
|
3455
|
+
}
|
|
3456
|
+
function assertSecureImageSource(image, fieldName = "image") {
|
|
3457
|
+
if (typeof image !== "string") {
|
|
3458
|
+
throw new ValidationError2({
|
|
3459
|
+
field: fieldName,
|
|
3460
|
+
reason: "must be a string",
|
|
3461
|
+
receivedValue: typeof image
|
|
3462
|
+
});
|
|
3058
3463
|
}
|
|
3059
|
-
|
|
3060
|
-
|
|
3464
|
+
const kind = classifyImageSource(image);
|
|
3465
|
+
if (kind === "http-url") {
|
|
3466
|
+
throw new ValidationError2({
|
|
3467
|
+
field: fieldName,
|
|
3468
|
+
reason: "HTTP URLs are not supported; use HTTPS or a base64 data URI",
|
|
3469
|
+
receivedValue: image
|
|
3470
|
+
});
|
|
3471
|
+
}
|
|
3472
|
+
if (kind === "unknown") {
|
|
3473
|
+
throw new ValidationError2({
|
|
3474
|
+
field: fieldName,
|
|
3475
|
+
reason: "must be an HTTPS URL or a base64 data URI",
|
|
3476
|
+
receivedValue: typeof image
|
|
3477
|
+
});
|
|
3061
3478
|
}
|
|
3062
|
-
return factory;
|
|
3063
3479
|
}
|
|
3064
3480
|
|
|
3065
|
-
// src/providers/
|
|
3066
|
-
var
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
var createSystemMessage = (content) => ({
|
|
3075
|
-
role: "system",
|
|
3076
|
-
content
|
|
3077
|
-
});
|
|
3078
|
-
var createMessage = (role, content) => ({
|
|
3079
|
-
role,
|
|
3080
|
-
content
|
|
3081
|
-
});
|
|
3082
|
-
var promptToMessages = (prompt) => [
|
|
3083
|
-
createUserMessage(prompt)
|
|
3481
|
+
// src/providers/pruna/client/core/validation/aspect-ratio-validator.ts
|
|
3482
|
+
var SUPPORTED_ASPECT_RATIOS = [
|
|
3483
|
+
"16:9",
|
|
3484
|
+
"9:16",
|
|
3485
|
+
"1:1",
|
|
3486
|
+
"4:3",
|
|
3487
|
+
"3:4",
|
|
3488
|
+
"3:2",
|
|
3489
|
+
"2:3"
|
|
3084
3490
|
];
|
|
3085
|
-
|
|
3086
|
-
|
|
3491
|
+
function isSupportedAspectRatio(value) {
|
|
3492
|
+
return typeof value === "string" && SUPPORTED_ASPECT_RATIOS.includes(value);
|
|
3493
|
+
}
|
|
3494
|
+
function assertValidAspectRatio(value, fieldName = "aspect_ratio") {
|
|
3495
|
+
if (value === void 0 || value === null) return;
|
|
3496
|
+
if (!isSupportedAspectRatio(value)) {
|
|
3497
|
+
throw new ValidationError2({
|
|
3498
|
+
field: fieldName,
|
|
3499
|
+
reason: `must be one of ${SUPPORTED_ASPECT_RATIOS.join(", ")}`,
|
|
3500
|
+
receivedValue: value
|
|
3501
|
+
});
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3087
3504
|
|
|
3088
|
-
// src/providers/
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
try {
|
|
3102
|
-
return JSON.parse(candidate);
|
|
3103
|
-
} catch (err) {
|
|
3104
|
-
throw new GroqError(
|
|
3105
|
-
"UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
3106
|
-
"model response contained no parsable json object",
|
|
3107
|
-
err instanceof Error ? err : void 0
|
|
3108
|
-
);
|
|
3505
|
+
// src/providers/pruna/client/core/validation/resolution-validator.ts
|
|
3506
|
+
var SUPPORTED_RESOLUTIONS = ["720p", "1080p"];
|
|
3507
|
+
function isSupportedResolution(value) {
|
|
3508
|
+
return typeof value === "string" && SUPPORTED_RESOLUTIONS.includes(value);
|
|
3509
|
+
}
|
|
3510
|
+
function assertValidResolution(value, fieldName = "resolution") {
|
|
3511
|
+
if (value === void 0 || value === null) return;
|
|
3512
|
+
if (!isSupportedResolution(value)) {
|
|
3513
|
+
throw new ValidationError2({
|
|
3514
|
+
field: fieldName,
|
|
3515
|
+
reason: `must be one of ${SUPPORTED_RESOLUTIONS.join(", ")}`,
|
|
3516
|
+
receivedValue: value
|
|
3517
|
+
});
|
|
3109
3518
|
}
|
|
3110
3519
|
}
|
|
3111
3520
|
|
|
3112
|
-
// src/providers/
|
|
3113
|
-
var
|
|
3114
|
-
var
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3521
|
+
// src/providers/pruna/client/core/validation/duration-validator.ts
|
|
3522
|
+
var MIN_VIDEO_DURATION_SECONDS = 1;
|
|
3523
|
+
var MAX_VIDEO_DURATION_SECONDS = 30;
|
|
3524
|
+
function isValidDuration(value) {
|
|
3525
|
+
return typeof value === "number" && Number.isFinite(value) && value >= MIN_VIDEO_DURATION_SECONDS && value <= MAX_VIDEO_DURATION_SECONDS && Number.isInteger(value);
|
|
3526
|
+
}
|
|
3527
|
+
function assertValidDuration(value, fieldName = "duration") {
|
|
3528
|
+
if (value === void 0 || value === null) return;
|
|
3529
|
+
if (!isValidDuration(value)) {
|
|
3530
|
+
throw new ValidationError2({
|
|
3531
|
+
field: fieldName,
|
|
3532
|
+
reason: `must be an integer between ${MIN_VIDEO_DURATION_SECONDS} and ${MAX_VIDEO_DURATION_SECONDS} seconds`,
|
|
3533
|
+
receivedValue: value
|
|
3534
|
+
});
|
|
3119
3535
|
}
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3536
|
+
}
|
|
3537
|
+
|
|
3538
|
+
// src/providers/pruna/client/core/validation/prompt-validator.ts
|
|
3539
|
+
var MIN_PROMPT_LENGTH = 1;
|
|
3540
|
+
var MAX_PROMPT_LENGTH = 4e3;
|
|
3541
|
+
function assertValidPrompt(value, fieldName = "prompt") {
|
|
3542
|
+
if (typeof value !== "string") {
|
|
3543
|
+
throw new ValidationError2({
|
|
3544
|
+
field: fieldName,
|
|
3545
|
+
reason: "must be a string",
|
|
3546
|
+
receivedValue: typeof value
|
|
3547
|
+
});
|
|
3548
|
+
}
|
|
3549
|
+
if (value.trim().length < MIN_PROMPT_LENGTH) {
|
|
3550
|
+
throw new ValidationError2({
|
|
3551
|
+
field: fieldName,
|
|
3552
|
+
reason: "must not be empty or whitespace-only",
|
|
3553
|
+
receivedValue: value
|
|
3554
|
+
});
|
|
3555
|
+
}
|
|
3556
|
+
if (value.length > MAX_PROMPT_LENGTH) {
|
|
3557
|
+
throw new ValidationError2({
|
|
3558
|
+
field: fieldName,
|
|
3559
|
+
reason: `exceeds maximum length of ${MAX_PROMPT_LENGTH} characters`,
|
|
3560
|
+
receivedValue: value.length
|
|
3561
|
+
});
|
|
3562
|
+
}
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3565
|
+
// src/providers/pruna/client/core/validation/seed-validator.ts
|
|
3566
|
+
var MIN_SEED = -2147483648;
|
|
3567
|
+
var MAX_SEED = 2147483647;
|
|
3568
|
+
function isValidSeed(value) {
|
|
3569
|
+
return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= MIN_SEED && value <= MAX_SEED;
|
|
3570
|
+
}
|
|
3571
|
+
function assertValidSeed(value, fieldName = "seed") {
|
|
3572
|
+
if (value === void 0 || value === null) return;
|
|
3573
|
+
if (!isValidSeed(value)) {
|
|
3574
|
+
throw new ValidationError2({
|
|
3575
|
+
field: fieldName,
|
|
3576
|
+
reason: `must be an integer in [${MIN_SEED}, ${MAX_SEED}]`,
|
|
3577
|
+
receivedValue: value
|
|
3578
|
+
});
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
|
|
3582
|
+
// src/providers/pruna/client/core/protocol/input-type-guard.ts
|
|
3583
|
+
function isTextToImageInput(input) {
|
|
3584
|
+
return input.model === "p-image";
|
|
3585
|
+
}
|
|
3586
|
+
function isImageToImageInput(input) {
|
|
3587
|
+
return input.model === "p-image-edit";
|
|
3588
|
+
}
|
|
3589
|
+
function isImageToVideoInput(input) {
|
|
3590
|
+
return input.model === "p-video";
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3593
|
+
// src/providers/pruna/client/core/utils/request-deduplicator.ts
|
|
3594
|
+
var RequestDeduplicator2 = class {
|
|
3595
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
3596
|
+
ttl;
|
|
3597
|
+
constructor(ttl = DEFAULT_DEDUPLICATOR_TTL_MS) {
|
|
3598
|
+
this.ttl = ttl;
|
|
3599
|
+
}
|
|
3600
|
+
execute(key, fn) {
|
|
3601
|
+
const existing = this.pendingRequests.get(key);
|
|
3602
|
+
if (existing) {
|
|
3603
|
+
if (Date.now() - existing.timestamp < this.ttl) {
|
|
3604
|
+
return existing.promise;
|
|
3140
3605
|
}
|
|
3141
|
-
|
|
3606
|
+
this.pendingRequests.delete(key);
|
|
3607
|
+
}
|
|
3608
|
+
const promise = fn().finally(() => {
|
|
3609
|
+
setTimeout(() => {
|
|
3610
|
+
this.pendingRequests.delete(key);
|
|
3611
|
+
}, DEDUPLICATOR_CLEANUP_GRACE_MS);
|
|
3612
|
+
});
|
|
3613
|
+
this.pendingRequests.set(key, { promise, timestamp: Date.now() });
|
|
3614
|
+
return promise;
|
|
3142
3615
|
}
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
return this.extractContent(response.choices[0]?.message?.content);
|
|
3616
|
+
clear() {
|
|
3617
|
+
this.pendingRequests.clear();
|
|
3618
|
+
}
|
|
3619
|
+
size() {
|
|
3620
|
+
this.purgeExpired();
|
|
3621
|
+
return this.pendingRequests.size;
|
|
3622
|
+
}
|
|
3623
|
+
purgeExpired() {
|
|
3624
|
+
const now = Date.now();
|
|
3625
|
+
for (const [key, value] of this.pendingRequests) {
|
|
3626
|
+
if (now - value.timestamp >= this.ttl) {
|
|
3627
|
+
this.pendingRequests.delete(key);
|
|
3156
3628
|
}
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
};
|
|
3632
|
+
var globalDeduplicator = new RequestDeduplicator2();
|
|
3633
|
+
|
|
3634
|
+
// src/providers/pruna/client/generation/builders/text-to-image.builder.ts
|
|
3635
|
+
var TextToImageInputBuilder = class {
|
|
3636
|
+
model = "p-image";
|
|
3637
|
+
build(input, _context) {
|
|
3638
|
+
const payload = {
|
|
3639
|
+
prompt: input.prompt,
|
|
3640
|
+
aspect_ratio: input.aspect_ratio ?? DEFAULT_ASPECT_RATIO
|
|
3641
|
+
};
|
|
3642
|
+
if (input.seed !== void 0) {
|
|
3643
|
+
payload.seed = input.seed;
|
|
3644
|
+
}
|
|
3645
|
+
return Promise.resolve(payload);
|
|
3646
|
+
}
|
|
3647
|
+
};
|
|
3648
|
+
|
|
3649
|
+
// src/providers/pruna/client/generation/builders/image-to-image.builder.ts
|
|
3650
|
+
var ImageToImageInputBuilder = class {
|
|
3651
|
+
model = "p-image-edit";
|
|
3652
|
+
build(input, _context) {
|
|
3653
|
+
const payload = {
|
|
3654
|
+
images: [stripBase64Prefix(input.image)],
|
|
3655
|
+
prompt: input.prompt,
|
|
3656
|
+
aspect_ratio: input.aspect_ratio ?? DEFAULT_ASPECT_RATIO
|
|
3657
|
+
};
|
|
3658
|
+
if (input.seed !== void 0) {
|
|
3659
|
+
payload.seed = input.seed;
|
|
3660
|
+
}
|
|
3661
|
+
return Promise.resolve(payload);
|
|
3662
|
+
}
|
|
3663
|
+
};
|
|
3664
|
+
|
|
3665
|
+
// src/providers/pruna/client/generation/builders/image-to-video.builder.ts
|
|
3666
|
+
var ImageToVideoInputBuilder = class {
|
|
3667
|
+
model = "p-video";
|
|
3668
|
+
async build(input, context) {
|
|
3669
|
+
if (context.signal?.aborted) {
|
|
3670
|
+
throw new CancellationError();
|
|
3671
|
+
}
|
|
3672
|
+
const fileUrl = await uploadImage(
|
|
3673
|
+
input.image,
|
|
3674
|
+
context.apiKey,
|
|
3675
|
+
context.onProgress
|
|
3157
3676
|
);
|
|
3677
|
+
return {
|
|
3678
|
+
image: fileUrl,
|
|
3679
|
+
prompt: input.prompt,
|
|
3680
|
+
duration: input.duration ?? P_VIDEO_DEFAULTS.duration,
|
|
3681
|
+
resolution: input.resolution ?? P_VIDEO_DEFAULTS.resolution,
|
|
3682
|
+
fps: P_VIDEO_DEFAULTS.fps,
|
|
3683
|
+
draft: input.draft ?? P_VIDEO_DEFAULTS.draft,
|
|
3684
|
+
aspect_ratio: input.aspect_ratio ?? DEFAULT_ASPECT_RATIO,
|
|
3685
|
+
prompt_upsampling: P_VIDEO_DEFAULTS.promptUpsampling
|
|
3686
|
+
};
|
|
3158
3687
|
}
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3688
|
+
};
|
|
3689
|
+
|
|
3690
|
+
// src/providers/pruna/client/generation/builders/model-input-builder.ts
|
|
3691
|
+
var ModelInputBuilderRegistry = class {
|
|
3692
|
+
builders = /* @__PURE__ */ new Map();
|
|
3693
|
+
register(builder) {
|
|
3694
|
+
this.builders.set(builder.model, builder);
|
|
3695
|
+
return this;
|
|
3696
|
+
}
|
|
3697
|
+
resolve(model) {
|
|
3698
|
+
const builder = this.builders.get(model);
|
|
3699
|
+
if (!builder) {
|
|
3700
|
+
throw new Error(`No input builder registered for model: ${model}`);
|
|
3701
|
+
}
|
|
3702
|
+
return builder;
|
|
3703
|
+
}
|
|
3704
|
+
};
|
|
3705
|
+
|
|
3706
|
+
// src/providers/pruna/client/generation/builders/builder-factory.ts
|
|
3707
|
+
function createDefaultBuilderRegistry() {
|
|
3708
|
+
return new ModelInputBuilderRegistry().register(new TextToImageInputBuilder()).register(new ImageToImageInputBuilder()).register(new ImageToVideoInputBuilder());
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
// src/providers/pruna/client/generation/services/generation.service.ts
|
|
3712
|
+
var builderRegistry = createDefaultBuilderRegistry();
|
|
3713
|
+
function validateGenerateInput(input) {
|
|
3714
|
+
assertValidPrompt(input.prompt, `${input.model}.prompt`);
|
|
3715
|
+
assertValidAspectRatio(input.aspect_ratio, `${input.model}.aspect_ratio`);
|
|
3716
|
+
if (isTextToImageInput(input)) {
|
|
3717
|
+
assertValidSeed(input.seed, `${input.model}.seed`);
|
|
3718
|
+
} else if (isImageToImageInput(input)) {
|
|
3719
|
+
assertSecureImageSource(input.image, `${input.model}.image`);
|
|
3720
|
+
assertValidSeed(input.seed, `${input.model}.seed`);
|
|
3721
|
+
} else if (isImageToVideoInput(input)) {
|
|
3722
|
+
assertSecureImageSource(input.image, `${input.model}.image`);
|
|
3723
|
+
assertValidDuration(input.duration, `${input.model}.duration`);
|
|
3724
|
+
assertValidResolution(input.resolution, `${input.model}.resolution`);
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
async function generate(apiKey, input, options = {}) {
|
|
3728
|
+
assertValidApiKey(apiKey);
|
|
3729
|
+
validateGenerateInput(input);
|
|
3730
|
+
const { signal, onProgress, useCache = true } = options;
|
|
3731
|
+
if (useCache) {
|
|
3732
|
+
const cacheKey = deriveGenerationCacheKey(apiKey, input);
|
|
3733
|
+
return globalDeduplicator.execute(
|
|
3734
|
+
cacheKey,
|
|
3735
|
+
() => generateInternal(apiKey, input, { signal, onProgress })
|
|
3736
|
+
);
|
|
3737
|
+
}
|
|
3738
|
+
return generateInternal(apiKey, input, { signal, onProgress });
|
|
3739
|
+
}
|
|
3740
|
+
async function generateInternal(apiKey, input, options) {
|
|
3741
|
+
const { signal, onProgress } = options;
|
|
3742
|
+
const builder = builderRegistry.resolve(input.model);
|
|
3743
|
+
const modelInput = await builder.build(input, { apiKey, signal, onProgress });
|
|
3744
|
+
const response = await submitPrediction(input.model, modelInput, apiKey, signal, onProgress);
|
|
3745
|
+
const syncUri = extractUri(response);
|
|
3746
|
+
if (syncUri) {
|
|
3747
|
+
return { url: resolveUri(syncUri), model: input.model };
|
|
3748
|
+
}
|
|
3749
|
+
const pollUrl = response.get_url ?? response.status_url;
|
|
3750
|
+
if (!pollUrl) {
|
|
3751
|
+
throw new Error("Pruna API returned no result and no polling URL.");
|
|
3752
|
+
}
|
|
3753
|
+
const url = await pollForResult(
|
|
3754
|
+
pollUrl,
|
|
3755
|
+
apiKey,
|
|
3756
|
+
POLL_DEFAULTS.maxAttempts,
|
|
3757
|
+
POLL_DEFAULTS.intervalMs,
|
|
3758
|
+
signal,
|
|
3759
|
+
onProgress
|
|
3760
|
+
);
|
|
3761
|
+
return { url, model: input.model };
|
|
3762
|
+
}
|
|
3763
|
+
async function generateImageThenVideo(apiKey, input, options = {}) {
|
|
3764
|
+
assertValidApiKey(apiKey);
|
|
3765
|
+
assertValidPrompt(input.prompt, "text-to-video.prompt");
|
|
3766
|
+
assertValidAspectRatio(input.aspect_ratio, "text-to-video.aspect_ratio");
|
|
3767
|
+
assertValidDuration(input.duration, "text-to-video.duration");
|
|
3768
|
+
assertValidResolution(input.resolution, "text-to-video.resolution");
|
|
3769
|
+
const { signal, onProgress } = options;
|
|
3770
|
+
const aspectRatio = input.aspect_ratio ?? DEFAULT_ASPECT_RATIO;
|
|
3771
|
+
onProgress?.("predicting", 1);
|
|
3772
|
+
const imageResult = await generate(
|
|
3773
|
+
apiKey,
|
|
3774
|
+
{ model: "p-image", prompt: input.prompt, aspect_ratio: aspectRatio },
|
|
3775
|
+
{ signal }
|
|
3776
|
+
);
|
|
3777
|
+
onProgress?.("predicting", 2);
|
|
3778
|
+
return generate(
|
|
3779
|
+
apiKey,
|
|
3780
|
+
{
|
|
3781
|
+
model: "p-video",
|
|
3782
|
+
prompt: input.prompt,
|
|
3783
|
+
image: imageResult.url,
|
|
3784
|
+
duration: input.duration ?? P_VIDEO_DEFAULTS.duration,
|
|
3785
|
+
resolution: input.resolution ?? P_VIDEO_DEFAULTS.resolution,
|
|
3786
|
+
aspect_ratio: aspectRatio,
|
|
3787
|
+
draft: input.draft ?? P_VIDEO_DEFAULTS.draft
|
|
3788
|
+
},
|
|
3789
|
+
{ signal, onProgress }
|
|
3790
|
+
);
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
// src/providers/pruna/pruna-generate.ts
|
|
3794
|
+
async function* generateWithPruna(request, apiKey, signal, onProgress) {
|
|
3795
|
+
const stages = [];
|
|
3796
|
+
let notify = null;
|
|
3797
|
+
let done = false;
|
|
3798
|
+
const wake = () => {
|
|
3799
|
+
const fn = notify;
|
|
3800
|
+
if (fn) {
|
|
3801
|
+
notify = null;
|
|
3802
|
+
fn();
|
|
3803
|
+
}
|
|
3804
|
+
};
|
|
3805
|
+
const handleProgress = (stage) => {
|
|
3806
|
+
stages.push({ stage });
|
|
3807
|
+
onProgress?.(stage);
|
|
3808
|
+
wake();
|
|
3809
|
+
};
|
|
3810
|
+
const promise = (async () => {
|
|
3811
|
+
try {
|
|
3812
|
+
const model = request.model ?? (request.image ? "p-image-edit" : "p-image");
|
|
3813
|
+
if (model === "p-video" && !request.image && request.prompt) {
|
|
3814
|
+
const videoRes = await generateImageThenVideo(
|
|
3815
|
+
apiKey,
|
|
3816
|
+
{
|
|
3817
|
+
prompt: request.prompt,
|
|
3818
|
+
duration: request.duration ?? 5,
|
|
3819
|
+
aspect_ratio: request.aspectRatio ?? request.aspect_ratio ?? "16:9"
|
|
3820
|
+
},
|
|
3821
|
+
{ signal, onProgress: handleProgress }
|
|
3822
|
+
);
|
|
3823
|
+
stages.push({
|
|
3824
|
+
stage: "polling",
|
|
3825
|
+
result: { url: videoRes.url, model: "p-video" }
|
|
3826
|
+
});
|
|
3827
|
+
return;
|
|
3828
|
+
}
|
|
3829
|
+
const input = {
|
|
3830
|
+
model,
|
|
3831
|
+
prompt: request.prompt ?? "",
|
|
3832
|
+
aspect_ratio: request.aspectRatio ?? request.aspect_ratio ?? "1:1",
|
|
3833
|
+
...request.image ? { image: request.image } : {},
|
|
3834
|
+
...request.duration ? { duration: request.duration } : {},
|
|
3835
|
+
...request.resolution ? { resolution: request.resolution } : {},
|
|
3836
|
+
...request.seed !== void 0 ? { seed: request.seed } : {}
|
|
3837
|
+
};
|
|
3838
|
+
const res = await generate(apiKey, input, {
|
|
3839
|
+
signal,
|
|
3840
|
+
onProgress: handleProgress
|
|
3841
|
+
});
|
|
3842
|
+
stages.push({
|
|
3843
|
+
stage: "polling",
|
|
3844
|
+
result: { url: res.url, model: res.model }
|
|
3845
|
+
});
|
|
3846
|
+
} catch (err) {
|
|
3847
|
+
stages.push({
|
|
3848
|
+
stage: "processing",
|
|
3849
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
3170
3850
|
});
|
|
3851
|
+
} finally {
|
|
3852
|
+
done = true;
|
|
3853
|
+
wake();
|
|
3171
3854
|
}
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3855
|
+
})();
|
|
3856
|
+
while (!done || stages.length > 0) {
|
|
3857
|
+
if (stages.length === 0 && !done) {
|
|
3858
|
+
await new Promise((resolve) => {
|
|
3859
|
+
notify = resolve;
|
|
3860
|
+
});
|
|
3861
|
+
notify = null;
|
|
3178
3862
|
}
|
|
3179
|
-
|
|
3180
|
-
|
|
3863
|
+
while (stages.length > 0) {
|
|
3864
|
+
const nextStage = stages.shift();
|
|
3865
|
+
if (!nextStage) continue;
|
|
3866
|
+
yield nextStage;
|
|
3867
|
+
if (nextStage.error) {
|
|
3868
|
+
throw nextStage.error;
|
|
3869
|
+
}
|
|
3181
3870
|
}
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3871
|
+
}
|
|
3872
|
+
await promise;
|
|
3873
|
+
}
|
|
3874
|
+
|
|
3875
|
+
// src/providers/pruna/pruna.provider.ts
|
|
3876
|
+
var PrunaProvider = class {
|
|
3877
|
+
constructor(config) {
|
|
3878
|
+
this.config = config;
|
|
3879
|
+
}
|
|
3880
|
+
config;
|
|
3881
|
+
id = "pruna";
|
|
3882
|
+
name = "Pruna AI";
|
|
3883
|
+
capabilities = ["image", "video"];
|
|
3884
|
+
async healthCheck() {
|
|
3885
|
+
return this.config.apiKey ? "healthy" : "unavailable";
|
|
3886
|
+
}
|
|
3887
|
+
async estimateCost(_request) {
|
|
3888
|
+
return 1;
|
|
3889
|
+
}
|
|
3890
|
+
async generateImage(request) {
|
|
3891
|
+
let finalUrl;
|
|
3892
|
+
const model = this.config.models?.textToImage;
|
|
3893
|
+
for await (const stage of generateWithPruna(
|
|
3894
|
+
{ prompt: request.prompt, model, quality: request.quality },
|
|
3895
|
+
this.config.apiKey
|
|
3896
|
+
)) {
|
|
3897
|
+
if (stage.error) throw stage.error;
|
|
3898
|
+
if (stage.result?.url) finalUrl = stage.result.url;
|
|
3186
3899
|
}
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3900
|
+
if (!finalUrl) {
|
|
3901
|
+
throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No image generated by Pruna AI");
|
|
3902
|
+
}
|
|
3903
|
+
return {
|
|
3904
|
+
id: Date.now().toString(),
|
|
3905
|
+
type: "image",
|
|
3906
|
+
url: finalUrl,
|
|
3907
|
+
metadata: { provider: this.id, model: model ?? "p-image" },
|
|
3908
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3909
|
+
};
|
|
3910
|
+
}
|
|
3911
|
+
async generateVideo(request) {
|
|
3912
|
+
let finalUrl;
|
|
3913
|
+
const model = this.config.models?.textToVideo;
|
|
3914
|
+
for await (const stage of generateWithPruna(
|
|
3915
|
+
{ prompt: request.prompt, model, duration: request.duration, aspectRatio: request.aspectRatio },
|
|
3916
|
+
this.config.apiKey
|
|
3917
|
+
)) {
|
|
3918
|
+
if (stage.error) throw stage.error;
|
|
3919
|
+
if (stage.result?.url) finalUrl = stage.result.url;
|
|
3920
|
+
}
|
|
3921
|
+
if (!finalUrl) {
|
|
3922
|
+
throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated by Pruna AI");
|
|
3923
|
+
}
|
|
3924
|
+
return {
|
|
3925
|
+
id: Date.now().toString(),
|
|
3926
|
+
type: "video",
|
|
3927
|
+
url: finalUrl,
|
|
3928
|
+
metadata: { provider: this.id, model: model ?? "p-video" },
|
|
3929
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3930
|
+
};
|
|
3931
|
+
}
|
|
3932
|
+
async imageToVideo(request) {
|
|
3933
|
+
let finalUrl;
|
|
3934
|
+
for await (const stage of generateWithPruna(
|
|
3935
|
+
{ image: request.imageUrl, prompt: request.prompt, duration: request.duration, motion: request.motion },
|
|
3936
|
+
this.config.apiKey
|
|
3937
|
+
)) {
|
|
3938
|
+
if (stage.error) throw stage.error;
|
|
3939
|
+
if (stage.result?.url) finalUrl = stage.result.url;
|
|
3940
|
+
}
|
|
3941
|
+
if (!finalUrl) {
|
|
3942
|
+
throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated from image by Pruna AI");
|
|
3943
|
+
}
|
|
3944
|
+
return {
|
|
3945
|
+
id: Date.now().toString(),
|
|
3946
|
+
type: "video",
|
|
3947
|
+
url: finalUrl,
|
|
3948
|
+
metadata: { provider: this.id, model: "p-image-to-video" },
|
|
3949
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3950
|
+
};
|
|
3951
|
+
}
|
|
3952
|
+
async videoToVideo(_request) {
|
|
3953
|
+
throw new AIError("CLIENT_ERROR" /* CLIENT_ERROR */, "Pruna AI provider does not support video-to-video conversion");
|
|
3954
|
+
}
|
|
3955
|
+
};
|
|
3956
|
+
function createPrunaProvider(config) {
|
|
3957
|
+
return new PrunaProvider(config);
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3960
|
+
// src/providers/factory/create-ai.ts
|
|
3961
|
+
function createAI(config = {}) {
|
|
3962
|
+
const factory = new ProviderFactory({
|
|
3963
|
+
priority: config.priority,
|
|
3964
|
+
fallbackEnabled: config.fallbackEnabled,
|
|
3965
|
+
defaultTier: config.defaultTier
|
|
3966
|
+
});
|
|
3967
|
+
if (config.anthropic?.apiKey) {
|
|
3968
|
+
factory.register(new AnthropicProvider(config.anthropic));
|
|
3969
|
+
}
|
|
3970
|
+
if (config.groq?.apiKey) {
|
|
3971
|
+
factory.register(new GroqProvider(config.groq));
|
|
3972
|
+
}
|
|
3973
|
+
if (config.gemini?.apiKey) {
|
|
3974
|
+
factory.register(new GeminiProvider({ ...config.gemini, enabled: true }));
|
|
3975
|
+
}
|
|
3976
|
+
if (config.fal?.apiKey) {
|
|
3977
|
+
factory.register(new FalProvider({ ...config.fal, enabled: true }));
|
|
3978
|
+
}
|
|
3979
|
+
if (config.pruna?.apiKey) {
|
|
3980
|
+
factory.register(new PrunaProvider({ ...config.pruna, enabled: true }));
|
|
3981
|
+
}
|
|
3982
|
+
return factory;
|
|
3983
|
+
}
|
|
3984
|
+
|
|
3985
|
+
// src/providers/groq/groq-messages.ts
|
|
3986
|
+
var createUserMessage = (content) => ({
|
|
3987
|
+
role: "user",
|
|
3988
|
+
content
|
|
3989
|
+
});
|
|
3990
|
+
var createAssistantMessage = (content) => ({
|
|
3991
|
+
role: "assistant",
|
|
3992
|
+
content
|
|
3993
|
+
});
|
|
3994
|
+
var createSystemMessage = (content) => ({
|
|
3995
|
+
role: "system",
|
|
3996
|
+
content
|
|
3997
|
+
});
|
|
3998
|
+
var createMessage = (role, content) => ({
|
|
3999
|
+
role,
|
|
4000
|
+
content
|
|
4001
|
+
});
|
|
4002
|
+
var promptToMessages = (prompt) => [
|
|
4003
|
+
createUserMessage(prompt)
|
|
4004
|
+
];
|
|
4005
|
+
var extractTextFromMessages = (messages) => messages.map((m) => m.content).join("\n\n");
|
|
4006
|
+
var formatMessagesForDisplay = (messages) => messages.map((m) => `[${m.role.toUpperCase()}]: ${m.content}`).join("\n\n---\n\n");
|
|
4007
|
+
|
|
4008
|
+
// src/providers/groq/groq-structured-json.ts
|
|
4009
|
+
async function requestStructuredJson(client, request) {
|
|
4010
|
+
const chatRequest = {
|
|
4011
|
+
model: request.model,
|
|
4012
|
+
messages: [createUserMessage(request.instructions)],
|
|
4013
|
+
temperature: request.temperature,
|
|
4014
|
+
max_tokens: request.maxTokens,
|
|
4015
|
+
response_format: { type: "json_object" },
|
|
4016
|
+
...request.reasoningEffort !== void 0 ? { reasoning_effort: request.reasoningEffort } : {}
|
|
4017
|
+
};
|
|
4018
|
+
const response = await client.postChatCompletion(chatRequest);
|
|
4019
|
+
const content = response.choices[0]?.message?.content ?? "";
|
|
4020
|
+
const candidate = extractJsonCandidate(content);
|
|
4021
|
+
try {
|
|
4022
|
+
return JSON.parse(candidate);
|
|
4023
|
+
} catch (err) {
|
|
4024
|
+
throw new GroqError(
|
|
4025
|
+
"UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
4026
|
+
"model response contained no parsable json object",
|
|
4027
|
+
err instanceof Error ? err : void 0
|
|
4028
|
+
);
|
|
4029
|
+
}
|
|
4030
|
+
}
|
|
4031
|
+
|
|
4032
|
+
// src/providers/groq/text-generation.service.ts
|
|
4033
|
+
var STRUCTURED_OUTPUT_TEMPERATURE = 0.1;
|
|
4034
|
+
var TextGenerationService = class {
|
|
4035
|
+
constructor(httpClient, cache = new CacheManager(), defaultModel = DEFAULT_MODELS.TEXT) {
|
|
4036
|
+
this.httpClient = httpClient;
|
|
4037
|
+
this.cache = cache;
|
|
4038
|
+
this.defaultModel = defaultModel;
|
|
4039
|
+
}
|
|
4040
|
+
httpClient;
|
|
4041
|
+
cache;
|
|
4042
|
+
defaultModel;
|
|
4043
|
+
id = "groq-text-generation";
|
|
4044
|
+
name = "Groq Text Generation";
|
|
4045
|
+
capabilities = ["text"];
|
|
4046
|
+
jsonParser = new JsonResponseParser();
|
|
4047
|
+
generateCompletion(prompt, options = {}) {
|
|
4048
|
+
const model = options.model ?? this.defaultModel;
|
|
4049
|
+
return this.executeWithCache(
|
|
4050
|
+
{
|
|
4051
|
+
type: CACHE_KEY_TYPE_COMPLETION,
|
|
4052
|
+
model,
|
|
4053
|
+
prompt,
|
|
4054
|
+
config: options.generationConfig
|
|
4055
|
+
},
|
|
4056
|
+
async () => {
|
|
4057
|
+
const request = GroqChatRequestBuilder.for(model).withMessages([createUserMessage(prompt)]).withConfig(options.generationConfig).build();
|
|
4058
|
+
const response = await this.httpClient.postChatCompletion(request);
|
|
4059
|
+
return this.extractContent(response.choices[0]?.message?.content);
|
|
4060
|
+
}
|
|
4061
|
+
);
|
|
4062
|
+
}
|
|
4063
|
+
generateChatCompletion(messages, options = {}) {
|
|
4064
|
+
const model = options.model ?? this.defaultModel;
|
|
4065
|
+
return this.executeWithCache(
|
|
4066
|
+
{
|
|
4067
|
+
type: CACHE_KEY_TYPE_CHAT,
|
|
4068
|
+
model,
|
|
4069
|
+
messages: [...messages],
|
|
4070
|
+
config: options.generationConfig
|
|
4071
|
+
},
|
|
4072
|
+
async () => {
|
|
4073
|
+
const request = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig(options.generationConfig).build();
|
|
4074
|
+
const response = await this.httpClient.postChatCompletion(request);
|
|
4075
|
+
return this.extractContent(response.choices[0]?.message?.content);
|
|
4076
|
+
}
|
|
4077
|
+
);
|
|
4078
|
+
}
|
|
4079
|
+
generateStructured(promptOrRequest, options) {
|
|
4080
|
+
if (typeof promptOrRequest === "string") {
|
|
4081
|
+
const model2 = options?.model ?? this.defaultModel;
|
|
4082
|
+
const jsonPrompt = StructuredPromptBuilder.for(promptOrRequest).withSchema(options?.schema).build();
|
|
4083
|
+
const request = GroqChatRequestBuilder.for(model2).withMessages([createUserMessage(jsonPrompt)]).withConfig({
|
|
4084
|
+
...options?.generationConfig,
|
|
4085
|
+
temperature: STRUCTURED_OUTPUT_TEMPERATURE
|
|
4086
|
+
}).build();
|
|
4087
|
+
return this.httpClient.postChatCompletion(request).then((response) => {
|
|
4088
|
+
const content = this.extractContent(response.choices[0]?.message?.content);
|
|
4089
|
+
return this.jsonParser.parse(content);
|
|
4090
|
+
});
|
|
4091
|
+
}
|
|
4092
|
+
const req = promptOrRequest;
|
|
4093
|
+
const model = req.model ?? (req.tier ? this.resolveModel(req.tier) : this.defaultModel);
|
|
4094
|
+
const schemaObj = req.schema;
|
|
4095
|
+
const messages = [];
|
|
4096
|
+
if (req.systemPrompt) {
|
|
4097
|
+
messages.push({ role: "system", content: req.systemPrompt });
|
|
4098
|
+
}
|
|
4099
|
+
for (const m of req.messages) {
|
|
4100
|
+
messages.push({ role: m.role, content: m.content });
|
|
4101
|
+
}
|
|
4102
|
+
const lastMsg = messages[messages.length - 1];
|
|
4103
|
+
if (lastMsg) {
|
|
4104
|
+
const structuredPrompt = StructuredPromptBuilder.for(lastMsg.content).withSchema(schemaObj).build();
|
|
4105
|
+
messages[messages.length - 1] = { ...lastMsg, content: structuredPrompt };
|
|
4106
|
+
}
|
|
4107
|
+
const groqReq = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig({
|
|
4108
|
+
temperature: req.temperature ?? STRUCTURED_OUTPUT_TEMPERATURE,
|
|
4109
|
+
maxTokens: req.maxTokens
|
|
4110
|
+
}).build();
|
|
4111
|
+
return this.httpClient.postChatCompletion(groqReq).then((response) => {
|
|
4112
|
+
const content = this.extractContent(response.choices[0]?.message?.content);
|
|
4113
|
+
const parsed = this.jsonParser.parse(content);
|
|
4114
|
+
const genResult = {
|
|
4115
|
+
text: content,
|
|
4116
|
+
model: response.model,
|
|
4117
|
+
providerId: this.id,
|
|
4118
|
+
finishReason: response.choices[0]?.finish_reason ?? "stop",
|
|
4119
|
+
usage: response.usage ? {
|
|
4120
|
+
inputTokens: response.usage.prompt_tokens,
|
|
4121
|
+
outputTokens: response.usage.completion_tokens,
|
|
4122
|
+
totalTokens: response.usage.total_tokens
|
|
4123
|
+
} : void 0
|
|
4124
|
+
};
|
|
4125
|
+
return { value: parsed, result: genResult };
|
|
4126
|
+
});
|
|
4127
|
+
}
|
|
4128
|
+
streamCompletion(prompt, callbacks, options = {}) {
|
|
4129
|
+
return this.streamChatCompletion([createUserMessage(prompt)], callbacks, options);
|
|
4130
|
+
}
|
|
4131
|
+
async streamChatCompletion(messages, callbacks, options = {}) {
|
|
4132
|
+
const model = options.model ?? this.defaultModel;
|
|
4133
|
+
const request = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig(options.generationConfig).withStream(true).build();
|
|
4134
|
+
const parser = new StreamingResponseParser(
|
|
4135
|
+
{
|
|
4136
|
+
onChunk: (chunk) => callbacks.onChunk?.(chunk)
|
|
4137
|
+
},
|
|
4138
|
+
extractContentFromSsePayload
|
|
4139
|
+
);
|
|
4140
|
+
try {
|
|
4141
|
+
const stream = await this.httpClient.streamChatCompletion(request, {
|
|
4142
|
+
signal: options.signal
|
|
4143
|
+
});
|
|
4144
|
+
const fullText = await parser.parse(stream);
|
|
4145
|
+
callbacks.onComplete?.(fullText);
|
|
4146
|
+
return fullText;
|
|
4147
|
+
} catch (error) {
|
|
4148
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
4149
|
+
callbacks.onError?.(err);
|
|
4150
|
+
throw err;
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
clearCache() {
|
|
4154
|
+
this.cache.clear();
|
|
4155
|
+
}
|
|
4156
|
+
// --- ITextProvider implementation ---
|
|
4157
|
+
resolveModel(tier) {
|
|
4158
|
+
return GROQ_TIERS[tier] ?? this.defaultModel;
|
|
4159
|
+
}
|
|
4160
|
+
async generate(request) {
|
|
4161
|
+
const messages = [];
|
|
4162
|
+
if (request.systemPrompt) {
|
|
4163
|
+
messages.push({ role: "system", content: request.systemPrompt });
|
|
4164
|
+
}
|
|
4165
|
+
for (const m of request.messages) {
|
|
4166
|
+
messages.push({ role: m.role, content: m.content });
|
|
4167
|
+
}
|
|
4168
|
+
const model = request.model ?? (request.tier ? this.resolveModel(request.tier) : this.defaultModel);
|
|
4169
|
+
const groqReq = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig({
|
|
4170
|
+
temperature: request.temperature,
|
|
4171
|
+
maxTokens: request.maxTokens
|
|
4172
|
+
}).build();
|
|
4173
|
+
const resp = await this.httpClient.postChatCompletion(groqReq);
|
|
4174
|
+
const content = resp.choices[0]?.message?.content ?? "";
|
|
4175
|
+
return {
|
|
4176
|
+
text: content,
|
|
4177
|
+
finishReason: resp.choices[0]?.finish_reason ?? "stop",
|
|
4178
|
+
model: resp.model,
|
|
4179
|
+
providerId: this.id,
|
|
4180
|
+
usage: resp.usage ? {
|
|
4181
|
+
inputTokens: resp.usage.prompt_tokens,
|
|
4182
|
+
outputTokens: resp.usage.completion_tokens,
|
|
4183
|
+
totalTokens: resp.usage.total_tokens
|
|
4184
|
+
} : void 0
|
|
4185
|
+
};
|
|
4186
|
+
}
|
|
4187
|
+
async stream(request, callbacks) {
|
|
4188
|
+
const messages = [];
|
|
4189
|
+
if (request.systemPrompt) {
|
|
4190
|
+
messages.push({ role: "system", content: request.systemPrompt });
|
|
4191
|
+
}
|
|
4192
|
+
for (const m of request.messages) {
|
|
4193
|
+
messages.push({ role: m.role, content: m.content });
|
|
4194
|
+
}
|
|
4195
|
+
const model = request.model ?? (request.tier ? this.resolveModel(request.tier) : this.defaultModel);
|
|
4196
|
+
const text = await this.streamChatCompletion(
|
|
4197
|
+
messages,
|
|
4198
|
+
{
|
|
4199
|
+
onChunk: callbacks.onChunk,
|
|
4200
|
+
onError: callbacks.onError
|
|
4201
|
+
},
|
|
4202
|
+
{
|
|
4203
|
+
model,
|
|
4204
|
+
signal: request.signal,
|
|
4205
|
+
generationConfig: {
|
|
4206
|
+
temperature: request.temperature,
|
|
4207
|
+
maxTokens: request.maxTokens
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
);
|
|
4211
|
+
const result = {
|
|
4212
|
+
text,
|
|
4213
|
+
finishReason: "stop",
|
|
4214
|
+
model,
|
|
4215
|
+
providerId: this.id
|
|
4216
|
+
};
|
|
4217
|
+
callbacks.onComplete?.(result);
|
|
4218
|
+
return result;
|
|
4219
|
+
}
|
|
4220
|
+
async countTokens(text) {
|
|
4221
|
+
return Math.max(1, Math.ceil(text.length / 4));
|
|
4222
|
+
}
|
|
4223
|
+
async estimateCost(_request) {
|
|
4224
|
+
return 0;
|
|
4225
|
+
}
|
|
4226
|
+
async healthCheck() {
|
|
4227
|
+
try {
|
|
4228
|
+
await this.generateCompletion("ping", {
|
|
4229
|
+
generationConfig: { maxTokens: 1 }
|
|
4230
|
+
});
|
|
4231
|
+
return "healthy";
|
|
4232
|
+
} catch {
|
|
4233
|
+
return "unavailable";
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
async executeWithCache(params, factory) {
|
|
4237
|
+
const key = this.cache.generateKey(params);
|
|
4238
|
+
const cached = this.cache.get(key);
|
|
4239
|
+
if (cached !== null) {
|
|
4240
|
+
return cached;
|
|
4241
|
+
}
|
|
4242
|
+
const result = await factory();
|
|
4243
|
+
this.cache.set(key, result);
|
|
4244
|
+
return result;
|
|
4245
|
+
}
|
|
4246
|
+
extractContent(content) {
|
|
4247
|
+
if (!content) {
|
|
4248
|
+
throw new GroqError(
|
|
4249
|
+
"UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
4250
|
+
"No content generated from Groq API"
|
|
4251
|
+
);
|
|
4252
|
+
}
|
|
4253
|
+
return content;
|
|
4254
|
+
}
|
|
4255
|
+
};
|
|
4256
|
+
|
|
4257
|
+
// src/core/calculations/random-base36-suffix.ts
|
|
4258
|
+
var BASE_36_RADIX = 36;
|
|
4259
|
+
var DECIMAL_OFFSET = 2;
|
|
4260
|
+
function generateRandomBase36Suffix(length, random = Math.random) {
|
|
4261
|
+
if (length <= 0) return "";
|
|
4262
|
+
const raw = random().toString(BASE_36_RADIX).slice(DECIMAL_OFFSET);
|
|
4263
|
+
if (raw.length >= length) return raw.slice(0, length);
|
|
4264
|
+
let result = raw;
|
|
4265
|
+
while (result.length < length) {
|
|
4266
|
+
result += random().toString(BASE_36_RADIX).slice(DECIMAL_OFFSET);
|
|
4267
|
+
}
|
|
4268
|
+
return result.slice(0, length);
|
|
4269
|
+
}
|
|
4270
|
+
|
|
4271
|
+
// src/chat/utils/chat-id-generator.ts
|
|
4272
|
+
var ID_PART_DELIMITER = "-";
|
|
4273
|
+
var ChatIdGenerator = class {
|
|
4274
|
+
counter = 0;
|
|
4275
|
+
randomLength;
|
|
4276
|
+
constructor(randomLength = CHAT_MESSAGE_ID_SUFFIX_LENGTH) {
|
|
4277
|
+
this.randomLength = randomLength;
|
|
4278
|
+
}
|
|
4279
|
+
next(sender) {
|
|
4280
|
+
this.counter += 1;
|
|
4281
|
+
const random = generateRandomBase36Suffix(this.randomLength);
|
|
4282
|
+
return [
|
|
4283
|
+
MESSAGE_ID_PREFIX,
|
|
4284
|
+
Date.now(),
|
|
4285
|
+
sender,
|
|
4286
|
+
this.counter,
|
|
4287
|
+
random
|
|
4288
|
+
].join(ID_PART_DELIMITER);
|
|
4289
|
+
}
|
|
4290
|
+
};
|
|
4291
|
+
var chatIdGenerator = new ChatIdGenerator();
|
|
4292
|
+
|
|
4293
|
+
// src/chat/utils/message-formatter.ts
|
|
4294
|
+
var MessageFormatter = class {
|
|
4295
|
+
constructor(idGenerator = chatIdGenerator) {
|
|
4296
|
+
this.idGenerator = idGenerator;
|
|
4297
|
+
}
|
|
4298
|
+
idGenerator;
|
|
4299
|
+
/** Maps chat-domain history to neutral core messages; system senders are
|
|
4300
|
+
* dropped — the system prompt travels on the request's own slot. */
|
|
4301
|
+
toCoreMessages(messages) {
|
|
4302
|
+
const coreMessages = [];
|
|
4303
|
+
for (const message of messages) {
|
|
4304
|
+
if (message.sender === "system") continue;
|
|
4305
|
+
coreMessages.push({
|
|
4306
|
+
role: message.sender === "user" ? USER_ROLE : ASSISTANT_ROLE,
|
|
4307
|
+
content: message.content
|
|
4308
|
+
});
|
|
4309
|
+
}
|
|
4310
|
+
return coreMessages;
|
|
4311
|
+
}
|
|
4312
|
+
toChatMessage(content, sender) {
|
|
4313
|
+
return {
|
|
4314
|
+
id: this.idGenerator.next(sender),
|
|
4315
|
+
sender,
|
|
4316
|
+
content: content.trim(),
|
|
4317
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4318
|
+
type: "text"
|
|
4319
|
+
};
|
|
4320
|
+
}
|
|
4321
|
+
};
|
|
4322
|
+
var messageFormatter = new MessageFormatter();
|
|
4323
|
+
|
|
4324
|
+
// src/chat/utils/prompt-strategy.ts
|
|
4325
|
+
var DEFAULT_PROMPT_STRATEGY = {
|
|
4326
|
+
resolve: (config) => {
|
|
4327
|
+
if (config.systemPrompt && config.systemPrompt.length > 0) {
|
|
4328
|
+
return config.systemPrompt;
|
|
4329
|
+
}
|
|
4330
|
+
return void 0;
|
|
4331
|
+
}
|
|
4332
|
+
};
|
|
4333
|
+
|
|
4334
|
+
// src/chat/services/chat.service.ts
|
|
4335
|
+
var DEFAULT_CONFIG = {
|
|
4336
|
+
temperature: 0.8,
|
|
4337
|
+
maxTokens: 500
|
|
4338
|
+
};
|
|
4339
|
+
var ChatService = class {
|
|
4340
|
+
constructor(engine) {
|
|
4341
|
+
this.engine = engine;
|
|
4342
|
+
}
|
|
4343
|
+
engine;
|
|
4344
|
+
config = DEFAULT_CONFIG;
|
|
4345
|
+
promptStrategy = DEFAULT_PROMPT_STRATEGY;
|
|
4346
|
+
storage = null;
|
|
4347
|
+
initialize(config) {
|
|
4348
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
4349
|
+
}
|
|
4350
|
+
updateConfig(config) {
|
|
4351
|
+
this.config = { ...this.config, ...config };
|
|
4352
|
+
}
|
|
4353
|
+
setPromptStrategy(strategy) {
|
|
4354
|
+
this.promptStrategy = strategy;
|
|
4355
|
+
}
|
|
4356
|
+
setStorage(storage) {
|
|
4357
|
+
this.storage = storage;
|
|
4358
|
+
}
|
|
4359
|
+
async getConversation(conversationId) {
|
|
4360
|
+
const storage = this.requireStorage();
|
|
4361
|
+
const messages = await storage.getMessages(conversationId);
|
|
4362
|
+
if (messages.length === 0) return null;
|
|
4363
|
+
const last = messages[messages.length - 1];
|
|
4364
|
+
const first = messages[0];
|
|
4365
|
+
const companionId = String(first.metadata?.["companionId"] ?? "unknown");
|
|
4366
|
+
const companionName = String(first.metadata?.["companionName"] ?? "Unknown");
|
|
4367
|
+
return {
|
|
4368
|
+
id: conversationId,
|
|
4369
|
+
companionId,
|
|
4370
|
+
companionName,
|
|
4371
|
+
lastMessage: last,
|
|
4372
|
+
messageCount: messages.length,
|
|
4373
|
+
updatedAt: last.timestamp
|
|
4374
|
+
};
|
|
4375
|
+
}
|
|
4376
|
+
async getConversations() {
|
|
4377
|
+
if (this.storage && "getConversations" in this.storage && typeof this.storage.getConversations === "function") {
|
|
4378
|
+
return this.storage.getConversations();
|
|
4379
|
+
}
|
|
4380
|
+
return [];
|
|
4381
|
+
}
|
|
4382
|
+
async sendMessage(input, signal) {
|
|
4383
|
+
const userMessage = {
|
|
4384
|
+
...messageFormatter.toChatMessage(input.text, "user"),
|
|
4385
|
+
type: input.type ?? "text",
|
|
4386
|
+
...input.imageUrl !== void 0 ? { imageUrl: input.imageUrl } : {}
|
|
4387
|
+
};
|
|
4388
|
+
const aiResponse = await this.generateAIResponse(
|
|
4389
|
+
input.companionId,
|
|
4390
|
+
input.text,
|
|
4391
|
+
[],
|
|
4392
|
+
signal
|
|
4393
|
+
);
|
|
4394
|
+
return { userMessage, aiResponse };
|
|
4395
|
+
}
|
|
4396
|
+
async generateAIResponse(companionId, userMessage, context = [], signal) {
|
|
4397
|
+
const result = await this.run({
|
|
4398
|
+
...this.buildRequest(userMessage, context, signal)
|
|
4399
|
+
});
|
|
4400
|
+
return {
|
|
4401
|
+
...messageFormatter.toChatMessage(result.text, "assistant"),
|
|
4402
|
+
metadata: {
|
|
4403
|
+
companionId,
|
|
4404
|
+
model: result.model,
|
|
4405
|
+
usage: {
|
|
4406
|
+
input_tokens: result.usage?.inputTokens,
|
|
4407
|
+
output_tokens: result.usage?.outputTokens,
|
|
4408
|
+
total_tokens: result.usage?.totalTokens
|
|
4409
|
+
},
|
|
4410
|
+
finishReason: result.finishReason
|
|
4411
|
+
}
|
|
4412
|
+
};
|
|
4413
|
+
}
|
|
4414
|
+
async generateStreamingResponse(userMessage, context, onChunk, signal) {
|
|
4415
|
+
const request = this.buildRequest(userMessage, context, signal);
|
|
4416
|
+
const result = await this.runStream(request, { onChunk });
|
|
4417
|
+
return result.text;
|
|
4418
|
+
}
|
|
4419
|
+
buildRequest(userMessage, context, signal) {
|
|
4420
|
+
const messages = messageFormatter.toCoreMessages(context);
|
|
4421
|
+
messages.push({ role: "user", content: userMessage });
|
|
4422
|
+
return {
|
|
4423
|
+
messages,
|
|
4424
|
+
systemPrompt: this.resolveSystemPrompt(),
|
|
4425
|
+
model: this.config.model,
|
|
4426
|
+
tier: this.config.tier,
|
|
4427
|
+
temperature: this.config.temperature,
|
|
4428
|
+
maxTokens: this.config.maxTokens,
|
|
4429
|
+
...signal !== void 0 ? { signal } : {}
|
|
4430
|
+
};
|
|
4431
|
+
}
|
|
4432
|
+
resolveSystemPrompt() {
|
|
4433
|
+
const prompt = this.promptStrategy.resolve(this.config);
|
|
4434
|
+
return prompt && prompt.length > 0 ? prompt : void 0;
|
|
4435
|
+
}
|
|
4436
|
+
async run(request) {
|
|
4437
|
+
if (this.engine instanceof ProviderFactory) {
|
|
4438
|
+
return this.engine.chat(request);
|
|
4439
|
+
}
|
|
4440
|
+
return this.engine.generate(request);
|
|
4441
|
+
}
|
|
4442
|
+
async runStream(request, callbacks) {
|
|
4443
|
+
if (this.engine instanceof ProviderFactory) {
|
|
4444
|
+
return this.engine.chatStream(request, callbacks);
|
|
4445
|
+
}
|
|
4446
|
+
return this.engine.stream(request, callbacks);
|
|
4447
|
+
}
|
|
4448
|
+
requireStorage() {
|
|
4449
|
+
if (!this.storage) {
|
|
4450
|
+
throw new AIError(
|
|
4451
|
+
"MISSING_CONFIG" /* MISSING_CONFIG */,
|
|
4452
|
+
"ChatService.getConversation requires setStorage() to be called first."
|
|
4453
|
+
);
|
|
4454
|
+
}
|
|
4455
|
+
return this.storage;
|
|
4456
|
+
}
|
|
4457
|
+
};
|
|
4458
|
+
function extractTokenUsage(message) {
|
|
4459
|
+
const meta = message.metadata;
|
|
4460
|
+
if (!meta) return null;
|
|
4461
|
+
const usage = meta["usage"];
|
|
4462
|
+
if (!usage || typeof usage !== "object") return null;
|
|
4463
|
+
const u = usage;
|
|
4464
|
+
const inputTokens = readNumber(u, ["input_tokens", "prompt_tokens"]);
|
|
4465
|
+
const outputTokens = readNumber(u, ["output_tokens", "completion_tokens"]);
|
|
4466
|
+
const totalTokens = readNumber(u, ["total_tokens"]);
|
|
4467
|
+
if (inputTokens === null || outputTokens === null || totalTokens === null) {
|
|
4468
|
+
return null;
|
|
4469
|
+
}
|
|
4470
|
+
return {
|
|
4471
|
+
promptTokens: inputTokens,
|
|
4472
|
+
completionTokens: outputTokens,
|
|
4473
|
+
totalTokens
|
|
4474
|
+
};
|
|
4475
|
+
}
|
|
4476
|
+
function readNumber(source, keys) {
|
|
4477
|
+
for (const key of keys) {
|
|
4478
|
+
const value = source[key];
|
|
4479
|
+
if (typeof value === "number") return value;
|
|
4480
|
+
}
|
|
4481
|
+
return null;
|
|
4482
|
+
}
|
|
4483
|
+
|
|
4484
|
+
// src/providers/groq/groq-container.ts
|
|
4485
|
+
var container = null;
|
|
4486
|
+
function configureProvider(config) {
|
|
4487
|
+
const configStore = new GroqConfigStore();
|
|
4488
|
+
const transport = new GroqHttpTransport(configStore);
|
|
4489
|
+
const httpClient = new GroqHttpClient({ configStore, transport });
|
|
4490
|
+
httpClient.initialize(config);
|
|
4491
|
+
const textGeneration = new TextGenerationService(
|
|
4492
|
+
httpClient,
|
|
4493
|
+
new CacheManager(),
|
|
4494
|
+
config.textModel
|
|
4495
|
+
);
|
|
4496
|
+
const chat = new ChatService(textGeneration);
|
|
4497
|
+
container = { groqHttpClient: httpClient, textGeneration, chat };
|
|
4498
|
+
return container;
|
|
4499
|
+
}
|
|
4500
|
+
function getProvider() {
|
|
4501
|
+
return container;
|
|
4502
|
+
}
|
|
4503
|
+
function resetProvider() {
|
|
4504
|
+
container?.groqHttpClient.reset();
|
|
4505
|
+
container = null;
|
|
4506
|
+
}
|
|
4507
|
+
|
|
4508
|
+
// src/content/domain/calculations/ContentCreditCost.ts
|
|
4509
|
+
var CREDIT_COSTS = {
|
|
4510
|
+
"text-to-image": { standard: 1, hd: 2, "4k": 4 },
|
|
4511
|
+
"image-to-image": { standard: 1, hd: 2, "4k": 4 },
|
|
4512
|
+
"image-to-video": { standard: 3, hd: 5, "4k": 8 },
|
|
4513
|
+
"text-to-video": { standard: 5, hd: 8, "4k": 12 }
|
|
4514
|
+
};
|
|
4515
|
+
var DEFAULT_CREDIT_COST = 0;
|
|
4516
|
+
function toQualityTier(quality) {
|
|
4517
|
+
if (quality === "4k") return "4k";
|
|
4518
|
+
if (quality === "hd" || quality === "1080p") return "hd";
|
|
4519
|
+
return "standard";
|
|
4520
|
+
}
|
|
4521
|
+
function calculateContentCreditCost(type, quality = "standard") {
|
|
4522
|
+
const tierCosts = CREDIT_COSTS[type];
|
|
4523
|
+
if (!tierCosts) return DEFAULT_CREDIT_COST;
|
|
4524
|
+
return tierCosts[toQualityTier(quality)] ?? tierCosts.standard;
|
|
4525
|
+
}
|
|
4526
|
+
|
|
4527
|
+
// src/content/domain/limits/ImageFormatOptions.ts
|
|
4528
|
+
var ASPECT_RATIO_OPTIONS = [
|
|
4529
|
+
{ value: "1:1", label: "Square", width: 1024, height: 1024 },
|
|
4530
|
+
{ value: "16:9", label: "Landscape", width: 1920, height: 1080 },
|
|
4531
|
+
{ value: "9:16", label: "Portrait", width: 1080, height: 1920 },
|
|
4532
|
+
{ value: "4:3", label: "Standard", width: 1440, height: 1080 },
|
|
4533
|
+
{ value: "3:4", label: "Tall", width: 1080, height: 1440 }
|
|
4534
|
+
];
|
|
4535
|
+
var IMAGE_QUALITY_OPTIONS = [
|
|
4536
|
+
{ value: "standard", label: "Standard", description: "1024x1024, good quality", resolution: "1024x1024" },
|
|
4537
|
+
{ value: "hd", label: "HD", description: "1920x1080, high quality", resolution: "1920x1080" },
|
|
4538
|
+
{ value: "4k", label: "4K", description: "3840x2160, ultra quality", resolution: "3840x2160" }
|
|
4539
|
+
];
|
|
4540
|
+
var VIDEO_QUALITY_OPTIONS = [
|
|
4541
|
+
{ value: "720p", label: "720p HD", resolution: "1280x720" },
|
|
4542
|
+
{ value: "1080p", label: "1080p Full HD", resolution: "1920x1080" },
|
|
4543
|
+
{ value: "4k", label: "4K Ultra HD", resolution: "3840x2160" }
|
|
4544
|
+
];
|
|
4545
|
+
var VIDEO_DURATION_OPTIONS_SECONDS = [2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
4546
|
+
var MOTION_OPTIONS = [
|
|
4547
|
+
{ value: "slow", label: "Slow", description: "Gentle movement" },
|
|
4548
|
+
{ value: "medium", label: "Medium", description: "Balanced motion" },
|
|
4549
|
+
{ value: "fast", label: "Fast", description: "Dynamic movement" }
|
|
4550
|
+
];
|
|
4551
|
+
var STYLE_PRESETS = [
|
|
4552
|
+
{ value: "realistic", label: "Realistic", promptFragment: "realistic, photograph, detailed" },
|
|
4553
|
+
{ value: "artistic", label: "Artistic", promptFragment: "artistic, painting, creative" },
|
|
4554
|
+
{ value: "anime", label: "Anime", promptFragment: "anime style, manga, vibrant colors" },
|
|
4555
|
+
{ value: "cinematic", label: "Cinematic", promptFragment: "cinematic, movie scene, dramatic lighting" },
|
|
4556
|
+
{ value: "vintage", label: "Vintage", promptFragment: "vintage, retro, film grain" },
|
|
4557
|
+
{ value: "fantasy", label: "Fantasy", promptFragment: "fantasy, magical, ethereal" },
|
|
4558
|
+
{ value: "minimalist", label: "Minimalist", promptFragment: "minimalist, clean, simple" },
|
|
4559
|
+
{ value: "abstract", label: "Abstract", promptFragment: "abstract, geometric, artistic" }
|
|
4560
|
+
];
|
|
4561
|
+
|
|
4562
|
+
// src/content/domain/calculations/AspectRatioDimensions.ts
|
|
4563
|
+
var FALLBACK_DIMENSIONS = { width: 1024, height: 1024 };
|
|
4564
|
+
function resolveDimensionsForAspectRatio(ratio) {
|
|
4565
|
+
const match = ASPECT_RATIO_OPTIONS.find((option) => option.value === ratio);
|
|
4566
|
+
return match ? { width: match.width, height: match.height } : FALLBACK_DIMENSIONS;
|
|
4567
|
+
}
|
|
4568
|
+
|
|
4569
|
+
// src/content/domain/constants/GenerationConstants.ts
|
|
4570
|
+
var ASPECT_RATIO_VALUES = ASPECT_RATIO_OPTIONS.map((o) => o.value);
|
|
4571
|
+
var IMAGE_QUALITY_VALUES = IMAGE_QUALITY_OPTIONS.map((o) => o.value);
|
|
4572
|
+
var VIDEO_QUALITY_VALUES = VIDEO_QUALITY_OPTIONS.map((o) => o.value);
|
|
4573
|
+
var VIDEO_DURATION_OPTIONS = VIDEO_DURATION_OPTIONS_SECONDS;
|
|
4574
|
+
var getDimensionsForAspectRatio = resolveDimensionsForAspectRatio;
|
|
4575
|
+
var getRecommendedQuality = recommendQualityForAspectRatio;
|
|
4576
|
+
|
|
4577
|
+
// src/content/domain/limits/ValidationLimits.ts
|
|
4578
|
+
var ValidationLimits = {
|
|
4579
|
+
TOPIC: {
|
|
4580
|
+
MIN: 1,
|
|
4581
|
+
MAX: 500
|
|
4582
|
+
},
|
|
4583
|
+
CONTENT: {
|
|
4584
|
+
MIN: 1,
|
|
4585
|
+
MAX: 1e4
|
|
4586
|
+
},
|
|
4587
|
+
KEYWORD: {
|
|
4588
|
+
MAX_COUNT: 20,
|
|
4589
|
+
MAX_LENGTH: 50
|
|
4590
|
+
},
|
|
4591
|
+
DURATION_SECONDS: {
|
|
4592
|
+
MIN: 10,
|
|
4593
|
+
MAX: 600
|
|
4594
|
+
},
|
|
4595
|
+
CALENDAR_DAYS: {
|
|
4596
|
+
MIN: 1,
|
|
4597
|
+
MAX: 90
|
|
4598
|
+
},
|
|
4599
|
+
HASHTAG_COUNT: {
|
|
4600
|
+
MIN: 1,
|
|
4601
|
+
MAX: 50
|
|
4602
|
+
},
|
|
4603
|
+
SEO_CONTENT_PREVIEW: 500,
|
|
4604
|
+
BLOG_PROMPT_PREVIEW: 1e3,
|
|
4605
|
+
AB_VARIANT_CONTENT_PREVIEW: 200
|
|
4606
|
+
};
|
|
4607
|
+
|
|
4608
|
+
// src/content/domain/limits/SpeechPaceConfig.ts
|
|
4609
|
+
var SpeechPaceConfig = {
|
|
4610
|
+
WORDS_PER_SECOND: 2.5,
|
|
4611
|
+
TOKENS_PER_SECOND: 10,
|
|
4612
|
+
JSON_TOKEN_OVERHEAD: 500,
|
|
4613
|
+
TOKENS_PER_CALENDAR_DAY: 150
|
|
4614
|
+
};
|
|
4615
|
+
|
|
4616
|
+
// src/content/domain/calculations/SpeechPaceEstimate.ts
|
|
4617
|
+
function estimateWordCountForDuration(durationSeconds) {
|
|
4618
|
+
return Math.floor(durationSeconds * SpeechPaceConfig.WORDS_PER_SECOND);
|
|
4619
|
+
}
|
|
4620
|
+
function estimateTokenBudgetForDuration(durationSeconds) {
|
|
4621
|
+
return durationSeconds * SpeechPaceConfig.TOKENS_PER_SECOND + SpeechPaceConfig.JSON_TOKEN_OVERHEAD;
|
|
4622
|
+
}
|
|
4623
|
+
function estimateTokenBudgetForCalendarDays(days) {
|
|
4624
|
+
return days * SpeechPaceConfig.TOKENS_PER_CALENDAR_DAY + SpeechPaceConfig.JSON_TOKEN_OVERHEAD;
|
|
4625
|
+
}
|
|
4626
|
+
|
|
4627
|
+
// src/content/domain/calculations/SeoKeywordDensity.ts
|
|
4628
|
+
function countKeywordOccurrences(content, keyword) {
|
|
4629
|
+
if (!keyword) return 0;
|
|
4630
|
+
const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4631
|
+
const matches = content.match(new RegExp(escaped, "gi"));
|
|
4632
|
+
return matches ? matches.length : 0;
|
|
4633
|
+
}
|
|
4634
|
+
function countWordsInContent(content) {
|
|
4635
|
+
const tokens = content.split(/\s+/).filter(Boolean);
|
|
4636
|
+
return tokens.length || 1;
|
|
4637
|
+
}
|
|
4638
|
+
function calculateKeywordDensity(content, keyword) {
|
|
4639
|
+
const totalWords = countWordsInContent(content);
|
|
4640
|
+
const occurrences = countKeywordOccurrences(content, keyword);
|
|
4641
|
+
return occurrences / totalWords * 100;
|
|
4642
|
+
}
|
|
4643
|
+
|
|
4644
|
+
// src/content/domain/calculations/AbTestImprovementRatio.ts
|
|
4645
|
+
function calculateImprovementPercentage(scoreA, scoreB) {
|
|
4646
|
+
const higher = Math.max(scoreA, scoreB);
|
|
4647
|
+
const lower = Math.min(scoreA, scoreB);
|
|
4648
|
+
if (lower <= 0) {
|
|
4649
|
+
return "100";
|
|
4650
|
+
}
|
|
4651
|
+
const delta = (higher / lower - 1) * 100;
|
|
4652
|
+
return Math.abs(delta).toFixed(0);
|
|
4653
|
+
}
|
|
4654
|
+
function selectHigherScoreVariantId(scoreA, scoreB, idA = "A", idB = "B") {
|
|
4655
|
+
return scoreA > scoreB ? idA : idB;
|
|
4656
|
+
}
|
|
4657
|
+
|
|
4658
|
+
// src/content/domain/calculations/ContentCalendarDateOffset.ts
|
|
4659
|
+
var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
|
|
4660
|
+
function resolveCalendarEntryDate(dayIndex, baseTimestamp = Date.now()) {
|
|
4661
|
+
return new Date(baseTimestamp + (dayIndex - 1) * ONE_DAY_MS);
|
|
4662
|
+
}
|
|
4663
|
+
|
|
4664
|
+
// src/content/domain/validation/TopicTextValidator.ts
|
|
4665
|
+
function validateTopicText(topic) {
|
|
4666
|
+
if (typeof topic !== "string" || topic.trim().length < ValidationLimits.TOPIC.MIN) {
|
|
4667
|
+
throw new ValidationError("Topic cannot be empty");
|
|
4668
|
+
}
|
|
4669
|
+
const trimmed = topic.trim();
|
|
4670
|
+
if (trimmed.length > ValidationLimits.TOPIC.MAX) {
|
|
4671
|
+
throw new ValidationError(`Topic exceeds maximum length of ${ValidationLimits.TOPIC.MAX} characters`);
|
|
4672
|
+
}
|
|
4673
|
+
return trimmed;
|
|
4674
|
+
}
|
|
4675
|
+
|
|
4676
|
+
// src/content/domain/validation/ContentBodyValidator.ts
|
|
4677
|
+
function validateContentBody(content) {
|
|
4678
|
+
if (typeof content !== "string" || content.trim().length < ValidationLimits.CONTENT.MIN) {
|
|
4679
|
+
throw new ValidationError("Content cannot be empty");
|
|
4680
|
+
}
|
|
4681
|
+
const trimmed = content.trim();
|
|
4682
|
+
if (trimmed.length > ValidationLimits.CONTENT.MAX) {
|
|
4683
|
+
throw new ValidationError(`Content exceeds maximum length of ${ValidationLimits.CONTENT.MAX} characters`);
|
|
4684
|
+
}
|
|
4685
|
+
return trimmed;
|
|
4686
|
+
}
|
|
4687
|
+
function truncateForSeoPreview(content) {
|
|
4688
|
+
return content.substring(0, ValidationLimits.SEO_CONTENT_PREVIEW);
|
|
4689
|
+
}
|
|
4690
|
+
function truncateForBlogPromptPreview(content) {
|
|
4691
|
+
return content.substring(0, ValidationLimits.BLOG_PROMPT_PREVIEW);
|
|
4692
|
+
}
|
|
4693
|
+
function truncateForAbVariantPreview(content) {
|
|
4694
|
+
return content.substring(0, ValidationLimits.AB_VARIANT_CONTENT_PREVIEW);
|
|
4695
|
+
}
|
|
4696
|
+
|
|
4697
|
+
// src/content/domain/validation/KeywordListValidator.ts
|
|
4698
|
+
function validateKeywordList(keywords) {
|
|
4699
|
+
if (!Array.isArray(keywords)) {
|
|
4700
|
+
throw new ValidationError("Keywords must be an array");
|
|
4701
|
+
}
|
|
4702
|
+
if (keywords.length > ValidationLimits.KEYWORD.MAX_COUNT) {
|
|
4703
|
+
throw new ValidationError(`Cannot process more than ${ValidationLimits.KEYWORD.MAX_COUNT} keywords`);
|
|
4704
|
+
}
|
|
4705
|
+
return keywords.filter((k) => typeof k === "string" && k.trim().length > 0).map((keyword) => {
|
|
4706
|
+
const trimmed = keyword.trim();
|
|
4707
|
+
if (trimmed.length > ValidationLimits.KEYWORD.MAX_LENGTH) {
|
|
4708
|
+
const preview = trimmed.substring(0, 20);
|
|
4709
|
+
throw new ValidationError(
|
|
4710
|
+
`Keyword "${preview}..." exceeds maximum length of ${ValidationLimits.KEYWORD.MAX_LENGTH} characters`
|
|
4711
|
+
);
|
|
4712
|
+
}
|
|
4713
|
+
return trimmed;
|
|
4714
|
+
});
|
|
4715
|
+
}
|
|
4716
|
+
|
|
4717
|
+
// src/content/domain/validation/DurationSecondsValidator.ts
|
|
4718
|
+
function validateDurationSeconds(duration) {
|
|
4719
|
+
if (!Number.isFinite(duration)) {
|
|
4720
|
+
throw new ValidationError(
|
|
4721
|
+
`Duration must be between ${ValidationLimits.DURATION_SECONDS.MIN} and ${ValidationLimits.DURATION_SECONDS.MAX} seconds`
|
|
4722
|
+
);
|
|
4723
|
+
}
|
|
4724
|
+
if (duration < ValidationLimits.DURATION_SECONDS.MIN || duration > ValidationLimits.DURATION_SECONDS.MAX) {
|
|
4725
|
+
throw new ValidationError(
|
|
4726
|
+
`Duration must be between ${ValidationLimits.DURATION_SECONDS.MIN} and ${ValidationLimits.DURATION_SECONDS.MAX} seconds`
|
|
4727
|
+
);
|
|
4728
|
+
}
|
|
4729
|
+
return duration;
|
|
4730
|
+
}
|
|
4731
|
+
|
|
4732
|
+
// src/content/domain/validation/CalendarDaysValidator.ts
|
|
4733
|
+
function validateCalendarDays(days) {
|
|
4734
|
+
if (!Number.isFinite(days)) {
|
|
4735
|
+
throw new ValidationError(
|
|
4736
|
+
`Days must be between ${ValidationLimits.CALENDAR_DAYS.MIN} and ${ValidationLimits.CALENDAR_DAYS.MAX}`
|
|
4737
|
+
);
|
|
4738
|
+
}
|
|
4739
|
+
if (days < ValidationLimits.CALENDAR_DAYS.MIN || days > ValidationLimits.CALENDAR_DAYS.MAX) {
|
|
4740
|
+
throw new ValidationError(
|
|
4741
|
+
`Days must be between ${ValidationLimits.CALENDAR_DAYS.MIN} and ${ValidationLimits.CALENDAR_DAYS.MAX}`
|
|
4742
|
+
);
|
|
4743
|
+
}
|
|
4744
|
+
return days;
|
|
4745
|
+
}
|
|
4746
|
+
|
|
4747
|
+
// src/content/domain/validation/HashtagCountValidator.ts
|
|
4748
|
+
function validateHashtagCount(count) {
|
|
4749
|
+
if (!Number.isFinite(count)) {
|
|
4750
|
+
throw new ValidationError(
|
|
4751
|
+
`Hashtag count must be between ${ValidationLimits.HASHTAG_COUNT.MIN} and ${ValidationLimits.HASHTAG_COUNT.MAX}`
|
|
4752
|
+
);
|
|
4753
|
+
}
|
|
4754
|
+
if (count < ValidationLimits.HASHTAG_COUNT.MIN || count > ValidationLimits.HASHTAG_COUNT.MAX) {
|
|
4755
|
+
throw new ValidationError(
|
|
4756
|
+
`Hashtag count must be between ${ValidationLimits.HASHTAG_COUNT.MIN} and ${ValidationLimits.HASHTAG_COUNT.MAX}`
|
|
4757
|
+
);
|
|
4758
|
+
}
|
|
4759
|
+
return Math.floor(count);
|
|
4760
|
+
}
|
|
4761
|
+
|
|
4762
|
+
// src/content/domain/utils/IdGenerator.ts
|
|
4763
|
+
function generateId(prefix) {
|
|
4764
|
+
const id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
|
|
4765
|
+
return prefix ? `${prefix}-${id}` : id;
|
|
4766
|
+
}
|
|
4767
|
+
|
|
4768
|
+
// src/content/application/prompts/PromptBuilder.ts
|
|
4769
|
+
var PromptBuilder = class {
|
|
4770
|
+
static blogPost(input) {
|
|
4771
|
+
return `
|
|
4772
|
+
As an expert SEO Content Strategist and Copywriter, generate a high-quality, comprehensive blog post.
|
|
4773
|
+
|
|
4774
|
+
TOPIC: ${input.topic}
|
|
4775
|
+
TYPE: ${input.blogType}
|
|
4776
|
+
KEYWORDS: ${input.keywords.join(", ")}
|
|
4777
|
+
TONE: ${input.tone}
|
|
4778
|
+
TARGET AUDIENCE: ${input.targetAudience}
|
|
4779
|
+
WORD COUNT: ${input.wordCount}
|
|
4780
|
+
LANGUAGE: ${input.language || "English"}
|
|
4781
|
+
|
|
4782
|
+
OPTIMIZATION REQUIREMENTS:
|
|
4783
|
+
- SEO Optimization: ${input.seoOptimization ? "ON (Include semantic keywords, LSI, and proper H1-H3 structure)" : "OFF"}
|
|
4784
|
+
- Include Schema Markup: ${input.includeSchema ? "ON (Generate JSON-LD for this blog type)" : "OFF"}
|
|
4785
|
+
- Image Suggestions: ${input.includeImages ? "ON (Provide DALL-E/Midjourney style prompts for contextually relevant images)" : "OFF"}
|
|
4786
|
+
|
|
4787
|
+
RESPONSE FORMAT (STRICT JSON ONLY):
|
|
4788
|
+
{
|
|
4789
|
+
"title": "Compelling, Click-worthy Title",
|
|
4790
|
+
"content": "Full markdown-formatted blog content with headers",
|
|
4791
|
+
"metaDescription": "SEO-optimized description (150-160 chars)",
|
|
4792
|
+
"seoScore": 0-100,
|
|
4793
|
+
"readabilityScore": 0-100,
|
|
4794
|
+
"keywords": ["list", "of", "optimized", "keywords"],
|
|
4795
|
+
"performance": {
|
|
4796
|
+
"readingTime": minutes (number),
|
|
4797
|
+
"shareability": 0-10 (number),
|
|
4798
|
+
"seoRank": 0-10 (number)
|
|
4799
|
+
},
|
|
4800
|
+
"schemaMarkup": "JSON-LD string (if requested)",
|
|
4801
|
+
"imagePrompts": ["Prompt 1", "Prompt 2"] (if requested)
|
|
4802
|
+
}
|
|
4803
|
+
`;
|
|
4804
|
+
}
|
|
4805
|
+
static socialPost(input) {
|
|
4806
|
+
return `
|
|
4807
|
+
Write a ${input.tone} social media post about: ${input.topic}
|
|
4808
|
+
|
|
4809
|
+
Platform: ${input.platform}
|
|
4810
|
+
Style: ${input.style}
|
|
4811
|
+
Maximum length: ${input.maxLength} characters
|
|
4812
|
+
${input.includeHashtags ? "Include relevant hashtags" : "No hashtags"}
|
|
4813
|
+
${input.includeCallToAction ? "Include a call-to-action" : ""}
|
|
4814
|
+
|
|
4815
|
+
Requirements:
|
|
4816
|
+
- Platform-optimized formatting
|
|
4817
|
+
- Engaging hook in first sentence
|
|
4818
|
+
- Natural, conversational tone
|
|
4819
|
+
- Platform-appropriate emoji usage
|
|
4820
|
+
|
|
4821
|
+
Generate the post in JSON format:
|
|
4822
|
+
{
|
|
4823
|
+
"content": "The post content",
|
|
4824
|
+
"hashtags": ["hashtag1", "hashtag2"],
|
|
4825
|
+
"emojis": ["emoji1", "emoji2"],
|
|
4826
|
+
"estimatedEngagement": 0-100
|
|
4827
|
+
}
|
|
4828
|
+
`;
|
|
4829
|
+
}
|
|
4830
|
+
static videoScript(input) {
|
|
4831
|
+
return `
|
|
4832
|
+
Create a ${input.tone} video script about: ${input.topic}
|
|
4833
|
+
|
|
4834
|
+
Duration: ${input.duration} seconds
|
|
4835
|
+
Target Audience: ${input.targetAudience}
|
|
4836
|
+
Include Visual Cues: ${input.includeVisuals ? "Yes" : "No"}
|
|
4837
|
+
Include CTA: ${input.includeCallToAction ? "Yes" : "No"}
|
|
4838
|
+
|
|
4839
|
+
Approximate word count: ${estimateWordCountForDuration(input.duration)} words
|
|
4840
|
+
|
|
4841
|
+
Generate in JSON format:
|
|
4842
|
+
{
|
|
4843
|
+
"script": "Full script with dialogue and narration",
|
|
4844
|
+
"visualCues": ["Visual cue 1", "Visual cue 2"],
|
|
4845
|
+
"callToAction": "Call to action text (if requested)",
|
|
4846
|
+
"estimatedEngagement": 0-100
|
|
4847
|
+
}
|
|
4848
|
+
`;
|
|
4849
|
+
}
|
|
4850
|
+
static voiceScript(input) {
|
|
4851
|
+
return `
|
|
4852
|
+
Create a ${input.emotion} voice script about: ${input.topic}
|
|
4853
|
+
|
|
4854
|
+
Duration: ${input.duration} seconds
|
|
4855
|
+
Emotion: ${input.emotion}
|
|
4856
|
+
|
|
4857
|
+
The script should:
|
|
4858
|
+
- Evoke the specified emotion
|
|
4859
|
+
- Be suitable for voice recording with emotion control
|
|
4860
|
+
- Include natural speech patterns
|
|
4861
|
+
- Have appropriate pacing for the emotion
|
|
4862
|
+
- Include emotional cues in brackets where needed
|
|
4863
|
+
|
|
4864
|
+
Approximate word count: ${estimateWordCountForDuration(input.duration)} words
|
|
4865
|
+
|
|
4866
|
+
Generate in JSON format:
|
|
4867
|
+
{
|
|
4868
|
+
"script": "Full script with emotional cues",
|
|
4869
|
+
"visualCues": ["Visual cue 1", "Visual cue 2"],
|
|
4870
|
+
"estimatedEngagement": 0-100
|
|
4871
|
+
}
|
|
4872
|
+
`;
|
|
4873
|
+
}
|
|
4874
|
+
static contentCalendar(niche, days) {
|
|
4875
|
+
return `
|
|
4876
|
+
Generate a ${days}-day content calendar for: ${niche}
|
|
4877
|
+
|
|
4878
|
+
For each day, provide:
|
|
4879
|
+
- Topic
|
|
4880
|
+
- Content type (blog, video, social, email, caption, script)
|
|
4881
|
+
- Best platform for this content
|
|
4882
|
+
- Brief description
|
|
4883
|
+
- Estimated engagement (0-100)
|
|
4884
|
+
- Priority level (high, medium, low)
|
|
4885
|
+
|
|
4886
|
+
Format as JSON array:
|
|
4887
|
+
[
|
|
4888
|
+
{
|
|
4889
|
+
"day": 1,
|
|
4890
|
+
"topic": "...",
|
|
4891
|
+
"contentType": "blog|video|social|email|caption|script",
|
|
4892
|
+
"platform": "twitter|linkedin|instagram|threads|tiktok|facebook",
|
|
4893
|
+
"description": "...",
|
|
4894
|
+
"estimatedEngagement": 0-100,
|
|
4895
|
+
"priority": "high|medium|low"
|
|
4896
|
+
}
|
|
4897
|
+
]
|
|
4898
|
+
|
|
4899
|
+
Requirements:
|
|
4900
|
+
- Mix of content types across platforms
|
|
4901
|
+
- Platform-appropriate suggestions
|
|
4902
|
+
- Engaging, varied topics
|
|
4903
|
+
- Realistic posting schedule
|
|
4904
|
+
`;
|
|
4905
|
+
}
|
|
4906
|
+
static sentiment(content) {
|
|
4907
|
+
return `
|
|
4908
|
+
Analyze the sentiment of this social media post:
|
|
4909
|
+
|
|
4910
|
+
"${content}"
|
|
4911
|
+
|
|
4912
|
+
Provide analysis in JSON format:
|
|
4913
|
+
{
|
|
4914
|
+
"sentiment": "positive|neutral|negative",
|
|
4915
|
+
"confidence": 0-1,
|
|
4916
|
+
"emotions": [
|
|
4917
|
+
{"emotion": "happy|excited|calm|sad|angry|surprised|fearful|disgusted|neutral", "score": 0-1}
|
|
4918
|
+
]
|
|
4919
|
+
}
|
|
4920
|
+
|
|
4921
|
+
Return only the JSON:
|
|
4922
|
+
`;
|
|
4923
|
+
}
|
|
4924
|
+
static contentAnalysis(content) {
|
|
4925
|
+
return `
|
|
4926
|
+
Analyze this content:
|
|
4927
|
+
|
|
4928
|
+
"${content}"
|
|
4929
|
+
|
|
4930
|
+
Provide analysis in JSON format:
|
|
4931
|
+
{
|
|
4932
|
+
"keywords": ["keyword1", "keyword2", ...],
|
|
4933
|
+
"entities": ["entity1", "entity2", ...],
|
|
4934
|
+
"suggestedImprovements": ["suggestion1", ...],
|
|
4935
|
+
"readabilityScore": 0-100,
|
|
4936
|
+
"estimatedEngagement": 0-100
|
|
4937
|
+
}
|
|
4938
|
+
|
|
4939
|
+
Return only the JSON:
|
|
4940
|
+
`;
|
|
4941
|
+
}
|
|
4942
|
+
static seoOptimization(content, keywords) {
|
|
4943
|
+
return `
|
|
4944
|
+
Optimize this content for SEO:
|
|
4945
|
+
|
|
4946
|
+
Content: "${content}"
|
|
4947
|
+
Keywords: ${keywords.join(", ")}
|
|
4948
|
+
|
|
4949
|
+
Tasks:
|
|
4950
|
+
1. Incorporate keywords naturally
|
|
4951
|
+
2. Improve engagement potential
|
|
4952
|
+
3. Add relevant call-to-action if appropriate
|
|
4953
|
+
4. Optimize for readability
|
|
4954
|
+
5. Enhance heading structure
|
|
4955
|
+
|
|
4956
|
+
Return JSON:
|
|
4957
|
+
{
|
|
4958
|
+
"optimized": "...",
|
|
4959
|
+
"score": 0-100,
|
|
4960
|
+
"suggestions": ["suggestion1", ...],
|
|
4961
|
+
"addedKeywords": ["keyword1", ...],
|
|
4962
|
+
"removedFillerWords": number,
|
|
4963
|
+
"readabilityImprovements": ["improvement1", ...],
|
|
4964
|
+
"metaDescription": "SEO-optimized meta description",
|
|
4965
|
+
"titleSuggestions": ["title1", ...]
|
|
4966
|
+
}
|
|
4967
|
+
|
|
4968
|
+
Return only the JSON:
|
|
4969
|
+
`;
|
|
4970
|
+
}
|
|
4971
|
+
static seoScore(content, keywords) {
|
|
4972
|
+
return `
|
|
4973
|
+
Calculate SEO score for this content:
|
|
4974
|
+
|
|
4975
|
+
Content: "${content}"
|
|
4976
|
+
Keywords: ${keywords.join(", ")}
|
|
4977
|
+
|
|
4978
|
+
Provide breakdown in JSON:
|
|
4979
|
+
{
|
|
4980
|
+
"keywordDensity": 0-100,
|
|
4981
|
+
"readabilityScore": 0-100,
|
|
4982
|
+
"titleOptimization": 0-100,
|
|
4983
|
+
"metaDescription": 0-100,
|
|
4984
|
+
"headingStructure": 0-100,
|
|
4985
|
+
"internalLinking": 0-100,
|
|
4986
|
+
"overall": 0-100
|
|
4987
|
+
}
|
|
4988
|
+
|
|
4989
|
+
Return only the JSON:
|
|
4990
|
+
`;
|
|
4991
|
+
}
|
|
4992
|
+
static abTestVariant(input) {
|
|
4993
|
+
return `
|
|
4994
|
+
Predict performance for this content variant:
|
|
4995
|
+
|
|
4996
|
+
Content: "${input.content}"
|
|
4997
|
+
Content Type: ${input.contentType}
|
|
4998
|
+
Tone: ${input.tone}
|
|
4999
|
+
Target Audience: ${input.targetAudience}
|
|
5000
|
+
Platform: ${input.platform}
|
|
5001
|
+
Goals: ${input.goals.join(", ")}
|
|
5002
|
+
|
|
5003
|
+
Provide prediction in JSON:
|
|
5004
|
+
{
|
|
5005
|
+
"predictedEngagement": 0-100,
|
|
5006
|
+
"predictedCTR": 0-100,
|
|
5007
|
+
"predictedConversions": 0-100,
|
|
5008
|
+
"confidence": 0-1,
|
|
5009
|
+
"reasoning": "...",
|
|
5010
|
+
"strengths": ["strength1", ...],
|
|
5011
|
+
"weaknesses": ["weakness1", ...],
|
|
5012
|
+
"suggestions": ["suggestion1", ...]
|
|
5013
|
+
}
|
|
5014
|
+
|
|
5015
|
+
Return only the JSON:
|
|
5016
|
+
`;
|
|
5017
|
+
}
|
|
5018
|
+
static hashtags(content, count) {
|
|
5019
|
+
return `
|
|
5020
|
+
Generate ${count} relevant hashtags for this content:
|
|
5021
|
+
|
|
5022
|
+
"${content}"
|
|
5023
|
+
|
|
5024
|
+
Requirements:
|
|
5025
|
+
- Mix of popular and niche hashtags
|
|
5026
|
+
- Industry-specific when possible
|
|
5027
|
+
- Trending if relevant
|
|
5028
|
+
- No duplicated words
|
|
5029
|
+
- Format: #hashtag (one per line)
|
|
5030
|
+
|
|
5031
|
+
Return only the hashtags:
|
|
5032
|
+
`;
|
|
5033
|
+
}
|
|
5034
|
+
static imagePrompt(description, style) {
|
|
5035
|
+
return `
|
|
5036
|
+
Generate a detailed AI image generation prompt for:
|
|
5037
|
+
|
|
5038
|
+
Description: ${description}
|
|
5039
|
+
Style: ${style || "realistic"}
|
|
5040
|
+
|
|
5041
|
+
Create a detailed, descriptive prompt that includes:
|
|
5042
|
+
- Subject details
|
|
5043
|
+
- Mood and atmosphere
|
|
5044
|
+
- Lighting
|
|
5045
|
+
- Composition
|
|
5046
|
+
- Art style reference
|
|
5047
|
+
- Technical specifications (aspect ratio, quality)
|
|
5048
|
+
|
|
5049
|
+
The prompt should be optimized for models like Midjourney, DALL-E, or Stable Diffusion.
|
|
5050
|
+
|
|
5051
|
+
Generate the prompt:
|
|
5052
|
+
`;
|
|
5053
|
+
}
|
|
5054
|
+
static blogImagePrompts(blogContent) {
|
|
5055
|
+
return `
|
|
5056
|
+
Analyze this blog content and generate 3-5 detailed image prompts for illustrative images:
|
|
5057
|
+
|
|
5058
|
+
Blog Content: "${blogContent}"
|
|
5059
|
+
|
|
5060
|
+
For each image prompt, provide:
|
|
5061
|
+
- Subject description
|
|
5062
|
+
- Mood and atmosphere
|
|
5063
|
+
- Style reference
|
|
5064
|
+
- Composition details
|
|
5065
|
+
- Technical specs
|
|
5066
|
+
|
|
5067
|
+
Return as JSON array:
|
|
5068
|
+
["prompt1", "prompt2", "prompt3", ...]
|
|
5069
|
+
|
|
5070
|
+
Return only the JSON:
|
|
5071
|
+
`;
|
|
5072
|
+
}
|
|
5073
|
+
static maxTokensForDuration(durationSeconds) {
|
|
5074
|
+
return estimateTokenBudgetForDuration(durationSeconds);
|
|
5075
|
+
}
|
|
5076
|
+
};
|
|
5077
|
+
|
|
5078
|
+
// src/content/application/services/BlogService.ts
|
|
5079
|
+
var BlogService = class {
|
|
5080
|
+
constructor(executor) {
|
|
5081
|
+
this.executor = executor;
|
|
5082
|
+
}
|
|
5083
|
+
executor;
|
|
5084
|
+
async generateBlogPost(request) {
|
|
5085
|
+
const topic = validateTopicText(request.topic);
|
|
5086
|
+
const keywords = validateKeywordList(request.targetKeywords);
|
|
5087
|
+
const prompt = PromptBuilder.blogPost({
|
|
5088
|
+
topic,
|
|
5089
|
+
blogType: request.blogType,
|
|
5090
|
+
keywords,
|
|
5091
|
+
tone: request.tone,
|
|
5092
|
+
targetAudience: request.targetAudience,
|
|
5093
|
+
wordCount: request.wordCount,
|
|
5094
|
+
language: request.language,
|
|
5095
|
+
seoOptimization: request.seoOptimization,
|
|
5096
|
+
includeSchema: request.includeSchema,
|
|
5097
|
+
includeImages: request.includeImages
|
|
3206
5098
|
});
|
|
5099
|
+
const parsed = await this.executor.runJson(
|
|
5100
|
+
prompt,
|
|
5101
|
+
{},
|
|
5102
|
+
{ maxTokens: request.wordCount * 2, temperature: 0.7 }
|
|
5103
|
+
);
|
|
5104
|
+
return {
|
|
5105
|
+
id: generateId("blog"),
|
|
5106
|
+
...parsed,
|
|
5107
|
+
blogType: request.blogType,
|
|
5108
|
+
tone: request.tone,
|
|
5109
|
+
wordCount: request.wordCount,
|
|
5110
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5111
|
+
};
|
|
3207
5112
|
}
|
|
3208
|
-
|
|
3209
|
-
|
|
5113
|
+
};
|
|
5114
|
+
async function batchRequest(requests, options = {}) {
|
|
5115
|
+
const {
|
|
5116
|
+
concurrency = 3,
|
|
5117
|
+
stopOnError = false
|
|
5118
|
+
} = options;
|
|
5119
|
+
const results = [];
|
|
5120
|
+
const errors = [];
|
|
5121
|
+
for (let i = 0; i < requests.length; i += concurrency) {
|
|
5122
|
+
const batch = requests.slice(i, i + concurrency);
|
|
5123
|
+
const batchResults = await Promise.allSettled(
|
|
5124
|
+
batch.map((fn) => fn())
|
|
5125
|
+
);
|
|
5126
|
+
for (const result of batchResults) {
|
|
5127
|
+
if (result.status === "fulfilled") {
|
|
5128
|
+
results.push(result.value);
|
|
5129
|
+
} else {
|
|
5130
|
+
errors.push(result.reason);
|
|
5131
|
+
if (stopOnError) {
|
|
5132
|
+
throw new Error(
|
|
5133
|
+
`Batch request failed: ${errors[0].message}`
|
|
5134
|
+
);
|
|
5135
|
+
}
|
|
5136
|
+
}
|
|
5137
|
+
}
|
|
3210
5138
|
}
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
5139
|
+
if (errors.length > 0 && !stopOnError) {
|
|
5140
|
+
console.warn(`${errors.length} requests failed in batch`);
|
|
5141
|
+
}
|
|
5142
|
+
return results;
|
|
5143
|
+
}
|
|
5144
|
+
|
|
5145
|
+
// src/content/application/services/SocialService.ts
|
|
5146
|
+
var MULTI_PLATFORMS = [
|
|
5147
|
+
"twitter",
|
|
5148
|
+
"linkedin",
|
|
5149
|
+
"instagram",
|
|
5150
|
+
"threads",
|
|
5151
|
+
"tiktok"
|
|
5152
|
+
];
|
|
5153
|
+
var DEFAULT_SOCIAL_RESULT = {
|
|
5154
|
+
content: "",
|
|
5155
|
+
platform: "twitter",
|
|
5156
|
+
hashtags: [],
|
|
5157
|
+
emojis: [],
|
|
5158
|
+
characterCount: 0,
|
|
5159
|
+
estimatedEngagement: 0
|
|
5160
|
+
};
|
|
5161
|
+
var SocialService = class {
|
|
5162
|
+
constructor(executor) {
|
|
5163
|
+
this.executor = executor;
|
|
5164
|
+
}
|
|
5165
|
+
executor;
|
|
5166
|
+
async generateSocialContent(request) {
|
|
5167
|
+
const topic = validateTopicText(request.topic);
|
|
5168
|
+
const spec = resolvePlatformSpec(request.platform);
|
|
5169
|
+
const maxLength = resolveMaxContentLength(request.platform, request.maxLength);
|
|
5170
|
+
const prompt = PromptBuilder.socialPost({
|
|
5171
|
+
topic,
|
|
5172
|
+
platform: request.platform,
|
|
5173
|
+
tone: request.tone,
|
|
5174
|
+
style: spec.style,
|
|
5175
|
+
maxLength,
|
|
5176
|
+
includeHashtags: request.hashtags !== false,
|
|
5177
|
+
includeCallToAction: Boolean(request.includeCallToAction)
|
|
5178
|
+
});
|
|
5179
|
+
const parsed = await this.executor.runJson(
|
|
5180
|
+
prompt,
|
|
5181
|
+
{ ...DEFAULT_SOCIAL_RESULT, platform: request.platform },
|
|
5182
|
+
{ maxTokens: 500, temperature: 0.8 }
|
|
5183
|
+
);
|
|
5184
|
+
return {
|
|
5185
|
+
...parsed,
|
|
5186
|
+
platform: request.platform,
|
|
5187
|
+
characterCount: parsed.content?.length ?? 0
|
|
5188
|
+
};
|
|
5189
|
+
}
|
|
5190
|
+
async generateForAllPlatforms(topic, tone) {
|
|
5191
|
+
const requests = MULTI_PLATFORMS.map(
|
|
5192
|
+
(platform) => () => this.generateSocialContent({ topic, platform, tone })
|
|
3219
5193
|
);
|
|
3220
5194
|
try {
|
|
3221
|
-
|
|
3222
|
-
signal: options.signal
|
|
3223
|
-
});
|
|
3224
|
-
const fullText = await parser.parse(stream);
|
|
3225
|
-
callbacks.onComplete?.(fullText);
|
|
3226
|
-
return fullText;
|
|
5195
|
+
return await batchRequest(requests, { concurrency: 2, stopOnError: false });
|
|
3227
5196
|
} catch (error) {
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
5197
|
+
console.warn("Batch execution failed, falling back to sequential:", error);
|
|
5198
|
+
const results = [];
|
|
5199
|
+
for (const platform of MULTI_PLATFORMS) {
|
|
5200
|
+
try {
|
|
5201
|
+
results.push(await this.generateSocialContent({ topic, platform, tone }));
|
|
5202
|
+
} catch (err) {
|
|
5203
|
+
console.error(`Failed to generate for ${platform}:`, err);
|
|
5204
|
+
}
|
|
5205
|
+
}
|
|
5206
|
+
return results;
|
|
3231
5207
|
}
|
|
3232
5208
|
}
|
|
3233
|
-
|
|
3234
|
-
|
|
5209
|
+
async generateHashtags(content, count) {
|
|
5210
|
+
const validated = validateHashtagCount(count);
|
|
5211
|
+
const prompt = PromptBuilder.hashtags(content, validated);
|
|
5212
|
+
const response = await this.executor.runText(prompt, { maxTokens: 200, temperature: 0.7 });
|
|
5213
|
+
const hashtags = response.match(/#[\w-]+/g) || [];
|
|
5214
|
+
return hashtags.slice(0, validated);
|
|
3235
5215
|
}
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
return
|
|
5216
|
+
optimizeHashtags(hashtags, platform) {
|
|
5217
|
+
const budget = resolveHashtagBudget(platform);
|
|
5218
|
+
return rankAndTrimHashtags(hashtags, budget);
|
|
3239
5219
|
}
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
5220
|
+
};
|
|
5221
|
+
|
|
5222
|
+
// src/content/application/services/VideoScriptService.ts
|
|
5223
|
+
var buildScriptResult = (parsed, duration) => {
|
|
5224
|
+
const script = parsed.script ?? "";
|
|
5225
|
+
if (!script) {
|
|
5226
|
+
throw new Error("AI response missing script field");
|
|
5227
|
+
}
|
|
5228
|
+
const wordCount = script.trim().length === 0 ? 0 : script.trim().split(/\s+/).length;
|
|
5229
|
+
return {
|
|
5230
|
+
...parsed,
|
|
5231
|
+
script,
|
|
5232
|
+
duration,
|
|
5233
|
+
wordCount
|
|
5234
|
+
};
|
|
5235
|
+
};
|
|
5236
|
+
var VideoScriptService = class {
|
|
5237
|
+
constructor(executor) {
|
|
5238
|
+
this.executor = executor;
|
|
5239
|
+
}
|
|
5240
|
+
executor;
|
|
5241
|
+
async generateVideoScript(request) {
|
|
5242
|
+
const topic = validateTopicText(request.topic);
|
|
5243
|
+
const duration = validateDurationSeconds(request.duration);
|
|
5244
|
+
const prompt = PromptBuilder.videoScript({
|
|
5245
|
+
topic,
|
|
5246
|
+
tone: request.tone,
|
|
5247
|
+
duration,
|
|
5248
|
+
targetAudience: request.targetAudience,
|
|
5249
|
+
includeVisuals: request.includeVisuals,
|
|
5250
|
+
includeCallToAction: request.includeCallToAction
|
|
5251
|
+
});
|
|
5252
|
+
const parsed = await this.executor.runJson(
|
|
5253
|
+
prompt,
|
|
5254
|
+
{ script: "", visualCues: [], duration, wordCount: 0, estimatedEngagement: 0 },
|
|
5255
|
+
{ maxTokens: estimateTokenBudgetForDuration(duration), temperature: 0.8 }
|
|
5256
|
+
);
|
|
5257
|
+
return buildScriptResult(parsed, duration);
|
|
5258
|
+
}
|
|
5259
|
+
async generateVoiceScript(topic, emotion, duration) {
|
|
5260
|
+
const validatedTopic = validateTopicText(topic);
|
|
5261
|
+
const validatedDuration = validateDurationSeconds(duration);
|
|
5262
|
+
const prompt = PromptBuilder.voiceScript({
|
|
5263
|
+
topic: validatedTopic,
|
|
5264
|
+
emotion,
|
|
5265
|
+
duration: validatedDuration
|
|
5266
|
+
});
|
|
5267
|
+
const parsed = await this.executor.runJson(
|
|
5268
|
+
prompt,
|
|
5269
|
+
{ script: "", visualCues: [], duration: validatedDuration, wordCount: 0, estimatedEngagement: 0 },
|
|
5270
|
+
{ maxTokens: estimateTokenBudgetForDuration(validatedDuration), temperature: 0.8 }
|
|
5271
|
+
);
|
|
5272
|
+
return buildScriptResult(parsed, validatedDuration);
|
|
5273
|
+
}
|
|
5274
|
+
};
|
|
5275
|
+
|
|
5276
|
+
// src/content/application/services/AnalysisService.ts
|
|
5277
|
+
var DEFAULT_SENTIMENT = {
|
|
5278
|
+
sentiment: "neutral",
|
|
5279
|
+
confidence: 0.5,
|
|
5280
|
+
emotions: []
|
|
5281
|
+
};
|
|
5282
|
+
var DEFAULT_ANALYSIS = {
|
|
5283
|
+
sentiment: DEFAULT_SENTIMENT,
|
|
5284
|
+
keywords: [],
|
|
5285
|
+
entities: [],
|
|
5286
|
+
suggestedImprovements: [],
|
|
5287
|
+
readabilityScore: 50,
|
|
5288
|
+
estimatedEngagement: 50
|
|
5289
|
+
};
|
|
5290
|
+
var AnalysisService = class {
|
|
5291
|
+
constructor(executor) {
|
|
5292
|
+
this.executor = executor;
|
|
5293
|
+
}
|
|
5294
|
+
executor;
|
|
5295
|
+
async analyzeSentiment(content) {
|
|
5296
|
+
const validated = validateContentBody(content);
|
|
5297
|
+
const prompt = PromptBuilder.sentiment(validated);
|
|
5298
|
+
const result = await this.executor.runJson(
|
|
5299
|
+
prompt,
|
|
5300
|
+
DEFAULT_SENTIMENT,
|
|
5301
|
+
{ maxTokens: 200, temperature: 0.3 }
|
|
5302
|
+
);
|
|
3255
5303
|
return {
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
model: resp.model,
|
|
3259
|
-
providerId: this.id,
|
|
3260
|
-
usage: resp.usage ? {
|
|
3261
|
-
inputTokens: resp.usage.prompt_tokens,
|
|
3262
|
-
outputTokens: resp.usage.completion_tokens,
|
|
3263
|
-
totalTokens: resp.usage.total_tokens
|
|
3264
|
-
} : void 0
|
|
5304
|
+
...result,
|
|
5305
|
+
confidence: clampConfidenceToValidRange(result.confidence)
|
|
3265
5306
|
};
|
|
3266
5307
|
}
|
|
3267
|
-
async
|
|
3268
|
-
const
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
5308
|
+
async analyzeContent(request) {
|
|
5309
|
+
const validated = validateContentBody(request.content);
|
|
5310
|
+
const sentimentResult = await this.analyzeSentiment(validated);
|
|
5311
|
+
const prompt = PromptBuilder.contentAnalysis(validated);
|
|
5312
|
+
const parsed = await this.executor.runJson(
|
|
5313
|
+
prompt,
|
|
5314
|
+
{ ...DEFAULT_ANALYSIS, sentiment: sentimentResult },
|
|
5315
|
+
{ maxTokens: 300, temperature: 0.5 }
|
|
5316
|
+
);
|
|
5317
|
+
return {
|
|
5318
|
+
sentiment: sentimentResult,
|
|
5319
|
+
keywords: parsed.keywords ?? [],
|
|
5320
|
+
entities: parsed.entities ?? [],
|
|
5321
|
+
suggestedImprovements: parsed.suggestedImprovements ?? [],
|
|
5322
|
+
readabilityScore: clampScoreToValidRange(parsed.readabilityScore),
|
|
5323
|
+
estimatedEngagement: clampScoreToValidRange(parsed.estimatedEngagement)
|
|
5324
|
+
};
|
|
5325
|
+
}
|
|
5326
|
+
};
|
|
5327
|
+
|
|
5328
|
+
// src/content/application/services/SeoService.ts
|
|
5329
|
+
var DEFAULT_SEO_SCORE = {
|
|
5330
|
+
keywordDensity: 50,
|
|
5331
|
+
readabilityScore: 50,
|
|
5332
|
+
titleOptimization: 50,
|
|
5333
|
+
metaDescription: 50,
|
|
5334
|
+
headingStructure: 50,
|
|
5335
|
+
internalLinking: 50,
|
|
5336
|
+
overall: 50
|
|
5337
|
+
};
|
|
5338
|
+
var UNDERUTILIZED_SUGGESTION = "Consider using this keyword more frequently";
|
|
5339
|
+
var SeoService = class {
|
|
5340
|
+
constructor(executor) {
|
|
5341
|
+
this.executor = executor;
|
|
5342
|
+
}
|
|
5343
|
+
executor;
|
|
5344
|
+
async optimizeSEO(request) {
|
|
5345
|
+
const content = validateContentBody(request.content);
|
|
5346
|
+
const keywords = validateKeywordList(request.keywords);
|
|
5347
|
+
const prompt = PromptBuilder.seoOptimization(content, keywords);
|
|
5348
|
+
const result = await this.executor.runJson(
|
|
5349
|
+
prompt,
|
|
3278
5350
|
{
|
|
3279
|
-
|
|
3280
|
-
|
|
5351
|
+
optimized: content,
|
|
5352
|
+
score: 50,
|
|
5353
|
+
suggestions: [],
|
|
5354
|
+
addedKeywords: [],
|
|
5355
|
+
removedFillerWords: 0,
|
|
5356
|
+
readabilityImprovements: []
|
|
3281
5357
|
},
|
|
3282
|
-
{
|
|
3283
|
-
model,
|
|
3284
|
-
signal: request.signal,
|
|
3285
|
-
generationConfig: {
|
|
3286
|
-
temperature: request.temperature,
|
|
3287
|
-
maxTokens: request.maxTokens
|
|
3288
|
-
}
|
|
3289
|
-
}
|
|
5358
|
+
{ maxTokens: 1e3, temperature: 0.7 }
|
|
3290
5359
|
);
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
model,
|
|
3295
|
-
providerId: this.id
|
|
5360
|
+
return {
|
|
5361
|
+
...result,
|
|
5362
|
+
score: clampScoreToValidRange(result.score)
|
|
3296
5363
|
};
|
|
3297
|
-
callbacks.onComplete?.(result);
|
|
3298
|
-
return result;
|
|
3299
5364
|
}
|
|
3300
|
-
async
|
|
3301
|
-
|
|
5365
|
+
async calculateSEOScore(content, keywords) {
|
|
5366
|
+
const validatedContent = validateContentBody(content);
|
|
5367
|
+
const validatedKeywords = validateKeywordList(keywords);
|
|
5368
|
+
const prompt = PromptBuilder.seoOptimization(
|
|
5369
|
+
truncateForSeoPreview(validatedContent),
|
|
5370
|
+
validatedKeywords
|
|
5371
|
+
);
|
|
5372
|
+
const result = await this.executor.runJson(
|
|
5373
|
+
prompt,
|
|
5374
|
+
DEFAULT_SEO_SCORE,
|
|
5375
|
+
{ maxTokens: 200, temperature: 0.3 }
|
|
5376
|
+
);
|
|
5377
|
+
return {
|
|
5378
|
+
keywordDensity: clampScoreToValidRange(result.keywordDensity),
|
|
5379
|
+
readabilityScore: clampScoreToValidRange(result.readabilityScore),
|
|
5380
|
+
titleOptimization: clampScoreToValidRange(result.titleOptimization),
|
|
5381
|
+
metaDescription: clampScoreToValidRange(result.metaDescription),
|
|
5382
|
+
headingStructure: clampScoreToValidRange(result.headingStructure),
|
|
5383
|
+
internalLinking: clampScoreToValidRange(result.internalLinking),
|
|
5384
|
+
overall: clampScoreToValidRange(result.overall)
|
|
5385
|
+
};
|
|
3302
5386
|
}
|
|
3303
|
-
|
|
3304
|
-
|
|
5387
|
+
analyzeKeywords(content, keywords) {
|
|
5388
|
+
const validatedContent = validateContentBody(content);
|
|
5389
|
+
const validatedKeywords = validateKeywordList(keywords);
|
|
5390
|
+
return validatedKeywords.map((keyword) => {
|
|
5391
|
+
const count = countKeywordOccurrences(validatedContent, keyword);
|
|
5392
|
+
const density = calculateKeywordDensity(validatedContent, keyword);
|
|
5393
|
+
return {
|
|
5394
|
+
keyword,
|
|
5395
|
+
count,
|
|
5396
|
+
density,
|
|
5397
|
+
prominence: classifyKeywordProminence(density),
|
|
5398
|
+
suggestions: isUnderutilizedKeyword(density) ? [UNDERUTILIZED_SUGGESTION] : []
|
|
5399
|
+
};
|
|
5400
|
+
});
|
|
3305
5401
|
}
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
5402
|
+
};
|
|
5403
|
+
|
|
5404
|
+
// src/content/application/services/ABTestService.ts
|
|
5405
|
+
var DEFAULT_PREDICTION = {
|
|
5406
|
+
variantId: "",
|
|
5407
|
+
predictedEngagement: 0,
|
|
5408
|
+
predictedCTR: 0,
|
|
5409
|
+
predictedConversions: 0,
|
|
5410
|
+
confidence: 0,
|
|
5411
|
+
reasoning: "",
|
|
5412
|
+
strengths: [],
|
|
5413
|
+
weaknesses: [],
|
|
5414
|
+
suggestions: []
|
|
5415
|
+
};
|
|
5416
|
+
var VARIANT_A_ID = "A";
|
|
5417
|
+
var VARIANT_B_ID = "B";
|
|
5418
|
+
var FALLBACK_ENGAGEMENT = 50;
|
|
5419
|
+
var DEFAULT_RECOMMENDATIONS = [
|
|
5420
|
+
"Test with larger audience",
|
|
5421
|
+
"Monitor click-through rates"
|
|
5422
|
+
];
|
|
5423
|
+
var ABTestService = class {
|
|
5424
|
+
constructor(executor) {
|
|
5425
|
+
this.executor = executor;
|
|
5426
|
+
}
|
|
5427
|
+
executor;
|
|
5428
|
+
async predictABTest(request) {
|
|
5429
|
+
const predictions = [];
|
|
5430
|
+
for (const variant of request.variants) {
|
|
5431
|
+
const prompt = PromptBuilder.abTestVariant({
|
|
5432
|
+
content: truncateForAbVariantPreview(variant.content),
|
|
5433
|
+
contentType: variant.contentType,
|
|
5434
|
+
tone: variant.tone,
|
|
5435
|
+
targetAudience: request.targetAudience,
|
|
5436
|
+
platform: request.platform,
|
|
5437
|
+
goals: request.goals
|
|
5438
|
+
});
|
|
5439
|
+
const parsed = await this.executor.runJson(
|
|
5440
|
+
prompt,
|
|
5441
|
+
{ ...DEFAULT_PREDICTION, variantId: variant.id },
|
|
5442
|
+
{ maxTokens: 300, temperature: 0.5 }
|
|
5443
|
+
);
|
|
5444
|
+
predictions.push({
|
|
5445
|
+
variantId: variant.id,
|
|
5446
|
+
predictedEngagement: clampScoreToValidRange(parsed.predictedEngagement),
|
|
5447
|
+
predictedCTR: clampScoreToValidRange(parsed.predictedCTR),
|
|
5448
|
+
predictedConversions: clampScoreToValidRange(parsed.predictedConversions),
|
|
5449
|
+
confidence: clampConfidenceToValidRange(parsed.confidence),
|
|
5450
|
+
reasoning: parsed.reasoning ?? "",
|
|
5451
|
+
strengths: parsed.strengths ?? [],
|
|
5452
|
+
weaknesses: parsed.weaknesses ?? [],
|
|
5453
|
+
suggestions: parsed.suggestions ?? []
|
|
3310
5454
|
});
|
|
3311
|
-
return "healthy";
|
|
3312
|
-
} catch {
|
|
3313
|
-
return "unavailable";
|
|
3314
5455
|
}
|
|
5456
|
+
return predictions;
|
|
5457
|
+
}
|
|
5458
|
+
async compareVariants(variantA, variantB) {
|
|
5459
|
+
const predictions = await this.predictABTest({
|
|
5460
|
+
variants: [
|
|
5461
|
+
{ id: VARIANT_A_ID, content: variantA, contentType: "social", tone: "casual" },
|
|
5462
|
+
{ id: VARIANT_B_ID, content: variantB, contentType: "social", tone: "casual" }
|
|
5463
|
+
],
|
|
5464
|
+
targetAudience: "general",
|
|
5465
|
+
platform: "twitter",
|
|
5466
|
+
goals: ["engagement", "clicks"]
|
|
5467
|
+
});
|
|
5468
|
+
const scoreA = predictions[0]?.predictedEngagement ?? FALLBACK_ENGAGEMENT;
|
|
5469
|
+
const scoreB = predictions[1]?.predictedEngagement ?? FALLBACK_ENGAGEMENT;
|
|
5470
|
+
const isTie = scoreA === scoreB;
|
|
5471
|
+
const winner = selectHigherScoreVariantId(scoreA, scoreB, VARIANT_A_ID, VARIANT_B_ID);
|
|
5472
|
+
const improvement = calculateImprovementPercentage(scoreA, scoreB);
|
|
5473
|
+
return {
|
|
5474
|
+
winner,
|
|
5475
|
+
confidence: AB_TEST_DEFAULT_CONFIDENCE,
|
|
5476
|
+
improvement: isTie ? "0%" : `+${improvement}% engagement`,
|
|
5477
|
+
reasoning: isTie ? "Both variants have equal predicted engagement" : winner === VARIANT_A_ID ? "Variant A has more engaging content" : "Variant B has more engaging content",
|
|
5478
|
+
recommendations: [...DEFAULT_RECOMMENDATIONS]
|
|
5479
|
+
};
|
|
3315
5480
|
}
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
const result = await factory();
|
|
3323
|
-
this.cache.set(key, result);
|
|
3324
|
-
return result;
|
|
5481
|
+
};
|
|
5482
|
+
|
|
5483
|
+
// src/content/application/services/ImagePromptService.ts
|
|
5484
|
+
var ImagePromptService = class {
|
|
5485
|
+
constructor(executor) {
|
|
5486
|
+
this.executor = executor;
|
|
3325
5487
|
}
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
5488
|
+
executor;
|
|
5489
|
+
async generateImagePrompt(description, style) {
|
|
5490
|
+
const validated = validateTopicText(description);
|
|
5491
|
+
const prompt = PromptBuilder.imagePrompt(validated, style);
|
|
5492
|
+
return this.executor.runText(prompt, { maxTokens: 300, temperature: 0.8 });
|
|
5493
|
+
}
|
|
5494
|
+
async generateImagePromptsForBlog(blogContent) {
|
|
5495
|
+
const truncated = truncateForBlogPromptPreview(validateContentBody(blogContent));
|
|
5496
|
+
const prompt = PromptBuilder.blogImagePrompts(truncated);
|
|
5497
|
+
const prompts = await this.executor.runJson(prompt, [], {
|
|
5498
|
+
maxTokens: 500,
|
|
5499
|
+
temperature: 0.8
|
|
5500
|
+
});
|
|
5501
|
+
return Array.isArray(prompts) ? prompts : [];
|
|
3334
5502
|
}
|
|
3335
5503
|
};
|
|
3336
5504
|
|
|
3337
|
-
// src/
|
|
3338
|
-
var
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
5505
|
+
// src/content/application/services/ContentCalendarService.ts
|
|
5506
|
+
var toCalendarEntry = (item) => ({
|
|
5507
|
+
date: resolveCalendarEntryDate(item.day),
|
|
5508
|
+
topic: item.topic,
|
|
5509
|
+
contentType: item.contentType,
|
|
5510
|
+
platform: item.platform,
|
|
5511
|
+
description: item.description,
|
|
5512
|
+
estimatedEngagement: item.estimatedEngagement,
|
|
5513
|
+
priority: item.priority
|
|
5514
|
+
});
|
|
5515
|
+
var ContentCalendarService = class {
|
|
5516
|
+
constructor(executor) {
|
|
5517
|
+
this.executor = executor;
|
|
3347
5518
|
}
|
|
3348
|
-
|
|
3349
|
-
|
|
5519
|
+
executor;
|
|
5520
|
+
async generateContentCalendar(niche, days) {
|
|
5521
|
+
const validatedNiche = validateTopicText(niche);
|
|
5522
|
+
const validatedDays = validateCalendarDays(days);
|
|
5523
|
+
const prompt = PromptBuilder.contentCalendar(validatedNiche, validatedDays);
|
|
5524
|
+
const data = await this.executor.runJson(
|
|
5525
|
+
prompt,
|
|
5526
|
+
[],
|
|
5527
|
+
{
|
|
5528
|
+
maxTokens: estimateTokenBudgetForCalendarDays(validatedDays),
|
|
5529
|
+
temperature: 0.8
|
|
5530
|
+
}
|
|
5531
|
+
);
|
|
5532
|
+
const items = Array.isArray(data) ? data : Array.isArray(data?.calendar) ? data.calendar : Array.isArray(data?.entries) ? data.entries : [];
|
|
5533
|
+
return items.map(toCalendarEntry);
|
|
5534
|
+
}
|
|
5535
|
+
};
|
|
3350
5536
|
|
|
3351
|
-
// src/
|
|
3352
|
-
var
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
5537
|
+
// src/content/application/services/ProviderServices.ts
|
|
5538
|
+
var wrapProviderError = (operation, error) => {
|
|
5539
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
5540
|
+
return new AIError(`${operation} failed: ${message}`, "PROVIDER_ERROR", error);
|
|
5541
|
+
};
|
|
5542
|
+
var ensureFactory = (factory, operation) => {
|
|
5543
|
+
if (factory.getAllProviders().length === 0) {
|
|
5544
|
+
throw new AIError(
|
|
5545
|
+
`${operation} failed: No providers are registered. Provide a ProviderConfig with at least one enabled provider when constructing AIContentService.`,
|
|
5546
|
+
"PROVIDER_UNAVAILABLE"
|
|
5547
|
+
);
|
|
3358
5548
|
}
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
5549
|
+
};
|
|
5550
|
+
var MediaGenerationService = class {
|
|
5551
|
+
constructor(providerFactory) {
|
|
5552
|
+
this.providerFactory = providerFactory;
|
|
5553
|
+
}
|
|
5554
|
+
providerFactory;
|
|
5555
|
+
generateImage(request) {
|
|
5556
|
+
ensureFactory(this.providerFactory, "Image generation");
|
|
5557
|
+
return this.providerFactory.generateImage(request).catch((err) => {
|
|
5558
|
+
throw wrapProviderError("Image generation", err);
|
|
5559
|
+
});
|
|
5560
|
+
}
|
|
5561
|
+
generateVideo(request) {
|
|
5562
|
+
ensureFactory(this.providerFactory, "Video generation");
|
|
5563
|
+
return this.providerFactory.generateVideo(request).catch((err) => {
|
|
5564
|
+
throw wrapProviderError("Video generation", err);
|
|
5565
|
+
});
|
|
5566
|
+
}
|
|
5567
|
+
convertImageToVideo(request) {
|
|
5568
|
+
ensureFactory(this.providerFactory, "Image-to-video conversion");
|
|
5569
|
+
return this.providerFactory.imageToVideo(request).catch((err) => {
|
|
5570
|
+
throw wrapProviderError("Image-to-video conversion", err);
|
|
5571
|
+
});
|
|
5572
|
+
}
|
|
5573
|
+
transformVideo(request) {
|
|
5574
|
+
ensureFactory(this.providerFactory, "Video-to-video transformation");
|
|
5575
|
+
return this.providerFactory.videoToVideo(request).catch((err) => {
|
|
5576
|
+
throw wrapProviderError("Video-to-video transformation", err);
|
|
5577
|
+
});
|
|
3369
5578
|
}
|
|
3370
5579
|
};
|
|
3371
|
-
var chatIdGenerator = new ChatIdGenerator();
|
|
3372
5580
|
|
|
3373
|
-
// src/
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
5581
|
+
// src/content/infrastructure/utils/jsonParser.ts
|
|
5582
|
+
function repairCommonJsonDefects2(text) {
|
|
5583
|
+
return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
|
|
5584
|
+
}
|
|
5585
|
+
function safeJSONParse(text, fallback, onError) {
|
|
5586
|
+
try {
|
|
5587
|
+
return JSON.parse(text);
|
|
5588
|
+
} catch (error) {
|
|
5589
|
+
try {
|
|
5590
|
+
return JSON.parse(repairCommonJsonDefects2(text));
|
|
5591
|
+
} catch {
|
|
5592
|
+
}
|
|
5593
|
+
if (error instanceof SyntaxError) {
|
|
5594
|
+
const positionMatch = error.message.match(/position (\d+)/);
|
|
5595
|
+
const position = positionMatch ? parseInt(positionMatch[1], 10) : -1;
|
|
5596
|
+
if (position > 0) {
|
|
5597
|
+
text.substring(Math.max(0, position - 50), position + 50);
|
|
5598
|
+
}
|
|
5599
|
+
}
|
|
5600
|
+
return fallback;
|
|
3377
5601
|
}
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
const
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
coreMessages.push({
|
|
3386
|
-
role: message.sender === "user" ? USER_ROLE : ASSISTANT_ROLE,
|
|
3387
|
-
content: message.content
|
|
3388
|
-
});
|
|
5602
|
+
}
|
|
5603
|
+
function extractJSON(text) {
|
|
5604
|
+
const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
5605
|
+
if (codeBlockMatch && codeBlockMatch[1]) {
|
|
5606
|
+
const candidate = codeBlockMatch[1].trim();
|
|
5607
|
+
if (candidate.startsWith("{") || candidate.startsWith("[")) {
|
|
5608
|
+
return candidate;
|
|
3389
5609
|
}
|
|
3390
|
-
return coreMessages;
|
|
3391
5610
|
}
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
sender,
|
|
3396
|
-
content: content.trim(),
|
|
3397
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3398
|
-
type: "text"
|
|
3399
|
-
};
|
|
5611
|
+
const objectMatch = text.match(/\{[\s\S]*\}/);
|
|
5612
|
+
if (objectMatch) {
|
|
5613
|
+
return objectMatch[0];
|
|
3400
5614
|
}
|
|
3401
|
-
|
|
3402
|
-
|
|
5615
|
+
const arrayMatch = text.match(/\[[\s\S]*\]/);
|
|
5616
|
+
if (arrayMatch) {
|
|
5617
|
+
return arrayMatch[0];
|
|
5618
|
+
}
|
|
5619
|
+
return null;
|
|
5620
|
+
}
|
|
5621
|
+
function parseAIResponse(response, fallback, onError) {
|
|
5622
|
+
const json = extractJSON(response);
|
|
5623
|
+
if (!json) {
|
|
5624
|
+
return fallback;
|
|
5625
|
+
}
|
|
5626
|
+
return safeJSONParse(json, fallback);
|
|
5627
|
+
}
|
|
3403
5628
|
|
|
3404
|
-
// src/
|
|
3405
|
-
var
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
5629
|
+
// src/content/application/services/GenerationExecutor.ts
|
|
5630
|
+
var GenerationExecutor = class {
|
|
5631
|
+
constructor(generator) {
|
|
5632
|
+
this.generator = generator;
|
|
5633
|
+
}
|
|
5634
|
+
generator;
|
|
5635
|
+
async runJson(prompt, fallback, generationOptions = {}) {
|
|
5636
|
+
const response = await this.generator.generateText(prompt, generationOptions);
|
|
5637
|
+
return parseAIResponse(response, fallback);
|
|
5638
|
+
}
|
|
5639
|
+
runText(prompt, options = {}) {
|
|
5640
|
+
return this.generator.generateText(prompt, options);
|
|
5641
|
+
}
|
|
5642
|
+
/** Usage-complete pass-through; requires a ProviderFactory engine. */
|
|
5643
|
+
runChat(request) {
|
|
5644
|
+
if (!(this.generator instanceof ProviderFactory)) {
|
|
5645
|
+
throw new AIError(
|
|
5646
|
+
"runChat requires a ProviderFactory engine \u2014 construct AIContentService with a provider config, not a custom ITextGenerator",
|
|
5647
|
+
"PROVIDER_UNAVAILABLE"
|
|
5648
|
+
);
|
|
3409
5649
|
}
|
|
3410
|
-
return
|
|
5650
|
+
return this.generator.chat(request);
|
|
3411
5651
|
}
|
|
3412
5652
|
};
|
|
3413
5653
|
|
|
3414
|
-
// src/
|
|
3415
|
-
var
|
|
3416
|
-
|
|
3417
|
-
|
|
5654
|
+
// src/content/application/services/AIContentService.ts
|
|
5655
|
+
var isTextGenerator = (value) => typeof value === "object" && value !== null && typeof value.generateText === "function";
|
|
5656
|
+
function toAIProviderConfig(config) {
|
|
5657
|
+
const legacy = config;
|
|
5658
|
+
const modern = config;
|
|
5659
|
+
const anthropic = modern.anthropic ?? (legacy.anthropic?.enabled ? {
|
|
5660
|
+
apiKey: legacy.anthropic.apiKey,
|
|
5661
|
+
baseUrl: legacy.anthropic.baseUrl,
|
|
5662
|
+
timeoutMs: legacy.anthropic.timeout,
|
|
5663
|
+
models: legacy.anthropic.models,
|
|
5664
|
+
effort: legacy.anthropic.effort
|
|
5665
|
+
} : void 0);
|
|
5666
|
+
const groq = modern.groq ?? (legacy.groq?.enabled ? {
|
|
5667
|
+
apiKey: legacy.groq.apiKey,
|
|
5668
|
+
baseUrl: legacy.groq.baseUrl,
|
|
5669
|
+
timeoutMs: legacy.groq.timeout,
|
|
5670
|
+
// legacy {text, chat} model pins map onto the neutral tier slots
|
|
5671
|
+
...legacy.groq.models?.text ? { models: { balanced: legacy.groq.models.text } } : {}
|
|
5672
|
+
} : void 0);
|
|
5673
|
+
const gemini = modern.gemini ?? (legacy.gemini?.enabled ? { ...legacy.gemini } : void 0);
|
|
5674
|
+
const fal = modern.fal ?? (legacy.fal?.enabled ? { ...legacy.fal } : void 0);
|
|
5675
|
+
const pruna = modern.pruna ?? (legacy.pruna?.enabled ? { ...legacy.pruna } : void 0);
|
|
5676
|
+
return {
|
|
5677
|
+
priority: modern.priority,
|
|
5678
|
+
fallbackEnabled: modern.fallbackEnabled,
|
|
5679
|
+
defaultTier: modern.defaultTier,
|
|
5680
|
+
...anthropic ? { anthropic } : {},
|
|
5681
|
+
...groq ? { groq } : {},
|
|
5682
|
+
...gemini ? { gemini } : {},
|
|
5683
|
+
...fal ? { fal } : {},
|
|
5684
|
+
...pruna ? { pruna } : {}
|
|
5685
|
+
};
|
|
5686
|
+
}
|
|
5687
|
+
var buildServices = (configOrGenerator) => {
|
|
5688
|
+
if (isTextGenerator(configOrGenerator)) {
|
|
5689
|
+
const executor2 = new GenerationExecutor(configOrGenerator);
|
|
5690
|
+
const emptyFactory = new ProviderFactory({
|
|
5691
|
+
priority: [],
|
|
5692
|
+
fallbackEnabled: false
|
|
5693
|
+
});
|
|
5694
|
+
return {
|
|
5695
|
+
providerFactory: emptyFactory,
|
|
5696
|
+
blogService: new BlogService(executor2),
|
|
5697
|
+
socialService: new SocialService(executor2),
|
|
5698
|
+
videoScriptService: new VideoScriptService(executor2),
|
|
5699
|
+
analysisService: new AnalysisService(executor2),
|
|
5700
|
+
seoService: new SeoService(executor2),
|
|
5701
|
+
abTestService: new ABTestService(executor2),
|
|
5702
|
+
imagePromptService: new ImagePromptService(executor2),
|
|
5703
|
+
contentCalendarService: new ContentCalendarService(executor2),
|
|
5704
|
+
mediaGenerationService: new MediaGenerationService(emptyFactory)
|
|
5705
|
+
};
|
|
5706
|
+
}
|
|
5707
|
+
const factory = createAI(toAIProviderConfig(configOrGenerator));
|
|
5708
|
+
const executor = new GenerationExecutor(factory);
|
|
5709
|
+
return {
|
|
5710
|
+
providerFactory: factory,
|
|
5711
|
+
blogService: new BlogService(executor),
|
|
5712
|
+
socialService: new SocialService(executor),
|
|
5713
|
+
videoScriptService: new VideoScriptService(executor),
|
|
5714
|
+
analysisService: new AnalysisService(executor),
|
|
5715
|
+
seoService: new SeoService(executor),
|
|
5716
|
+
abTestService: new ABTestService(executor),
|
|
5717
|
+
imagePromptService: new ImagePromptService(executor),
|
|
5718
|
+
contentCalendarService: new ContentCalendarService(executor),
|
|
5719
|
+
mediaGenerationService: new MediaGenerationService(factory)
|
|
5720
|
+
};
|
|
3418
5721
|
};
|
|
3419
|
-
var
|
|
3420
|
-
|
|
3421
|
-
|
|
5722
|
+
var AIContentService = class {
|
|
5723
|
+
providerFactory;
|
|
5724
|
+
blogService;
|
|
5725
|
+
socialService;
|
|
5726
|
+
videoScriptService;
|
|
5727
|
+
analysisService;
|
|
5728
|
+
seoService;
|
|
5729
|
+
abTestService;
|
|
5730
|
+
imagePromptService;
|
|
5731
|
+
contentCalendarService;
|
|
5732
|
+
mediaGenerationService;
|
|
5733
|
+
constructor(configOrGenerator) {
|
|
5734
|
+
const bundle = buildServices(configOrGenerator);
|
|
5735
|
+
this.providerFactory = bundle.providerFactory;
|
|
5736
|
+
this.blogService = bundle.blogService;
|
|
5737
|
+
this.socialService = bundle.socialService;
|
|
5738
|
+
this.videoScriptService = bundle.videoScriptService;
|
|
5739
|
+
this.analysisService = bundle.analysisService;
|
|
5740
|
+
this.seoService = bundle.seoService;
|
|
5741
|
+
this.abTestService = bundle.abTestService;
|
|
5742
|
+
this.imagePromptService = bundle.imagePromptService;
|
|
5743
|
+
this.contentCalendarService = bundle.contentCalendarService;
|
|
5744
|
+
this.mediaGenerationService = bundle.mediaGenerationService;
|
|
3422
5745
|
}
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
storage = null;
|
|
3427
|
-
initialize(config) {
|
|
3428
|
-
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
5746
|
+
/** The composed factory — chat()/chatStream() with usage, health, fallback. */
|
|
5747
|
+
get providers() {
|
|
5748
|
+
return this.providerFactory;
|
|
3429
5749
|
}
|
|
3430
|
-
|
|
3431
|
-
|
|
5750
|
+
generateBlogPost(request) {
|
|
5751
|
+
return this.blogService.generateBlogPost(request);
|
|
3432
5752
|
}
|
|
3433
|
-
|
|
3434
|
-
this.
|
|
5753
|
+
generateSocialContent(request) {
|
|
5754
|
+
return this.socialService.generateSocialContent(request);
|
|
3435
5755
|
}
|
|
3436
|
-
|
|
3437
|
-
this.
|
|
5756
|
+
generateForAllPlatforms(topic, tone) {
|
|
5757
|
+
return this.socialService.generateForAllPlatforms(topic, tone);
|
|
3438
5758
|
}
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
const messages = await storage.getMessages(conversationId);
|
|
3442
|
-
if (messages.length === 0) return null;
|
|
3443
|
-
const last = messages[messages.length - 1];
|
|
3444
|
-
const first = messages[0];
|
|
3445
|
-
const companionId = String(first.metadata?.["companionId"] ?? "unknown");
|
|
3446
|
-
const companionName = String(first.metadata?.["companionName"] ?? "Unknown");
|
|
3447
|
-
return {
|
|
3448
|
-
id: conversationId,
|
|
3449
|
-
companionId,
|
|
3450
|
-
companionName,
|
|
3451
|
-
lastMessage: last,
|
|
3452
|
-
messageCount: messages.length,
|
|
3453
|
-
updatedAt: last.timestamp
|
|
3454
|
-
};
|
|
5759
|
+
generateHashtags(content, count) {
|
|
5760
|
+
return this.socialService.generateHashtags(content, count);
|
|
3455
5761
|
}
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
return this.storage.getConversations();
|
|
3459
|
-
}
|
|
3460
|
-
return [];
|
|
5762
|
+
optimizeHashtags(hashtags, platform) {
|
|
5763
|
+
return Promise.resolve(this.socialService.optimizeHashtags(hashtags, platform));
|
|
3461
5764
|
}
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
...messageFormatter.toChatMessage(input.text, "user"),
|
|
3465
|
-
type: input.type ?? "text",
|
|
3466
|
-
...input.imageUrl !== void 0 ? { imageUrl: input.imageUrl } : {}
|
|
3467
|
-
};
|
|
3468
|
-
const aiResponse = await this.generateAIResponse(
|
|
3469
|
-
input.companionId,
|
|
3470
|
-
input.text,
|
|
3471
|
-
[],
|
|
3472
|
-
signal
|
|
3473
|
-
);
|
|
3474
|
-
return { userMessage, aiResponse };
|
|
5765
|
+
generateVideoScript(request) {
|
|
5766
|
+
return this.videoScriptService.generateVideoScript(request);
|
|
3475
5767
|
}
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
...this.buildRequest(userMessage, context, signal)
|
|
3479
|
-
});
|
|
3480
|
-
return {
|
|
3481
|
-
...messageFormatter.toChatMessage(result.text, "assistant"),
|
|
3482
|
-
metadata: {
|
|
3483
|
-
companionId,
|
|
3484
|
-
model: result.model,
|
|
3485
|
-
usage: {
|
|
3486
|
-
input_tokens: result.usage?.inputTokens,
|
|
3487
|
-
output_tokens: result.usage?.outputTokens,
|
|
3488
|
-
total_tokens: result.usage?.totalTokens
|
|
3489
|
-
},
|
|
3490
|
-
finishReason: result.finishReason
|
|
3491
|
-
}
|
|
3492
|
-
};
|
|
5768
|
+
generateVoiceScript(topic, emotion, duration) {
|
|
5769
|
+
return this.videoScriptService.generateVoiceScript(topic, emotion, duration);
|
|
3493
5770
|
}
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
const result = await this.runStream(request, { onChunk });
|
|
3497
|
-
return result.text;
|
|
5771
|
+
generateContentCalendar(niche, days) {
|
|
5772
|
+
return this.contentCalendarService.generateContentCalendar(niche, days);
|
|
3498
5773
|
}
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
messages.push({ role: "user", content: userMessage });
|
|
3502
|
-
return {
|
|
3503
|
-
messages,
|
|
3504
|
-
systemPrompt: this.resolveSystemPrompt(),
|
|
3505
|
-
model: this.config.model,
|
|
3506
|
-
tier: this.config.tier,
|
|
3507
|
-
temperature: this.config.temperature,
|
|
3508
|
-
maxTokens: this.config.maxTokens,
|
|
3509
|
-
...signal !== void 0 ? { signal } : {}
|
|
3510
|
-
};
|
|
5774
|
+
analyzeSentiment(content) {
|
|
5775
|
+
return this.analysisService.analyzeSentiment(content);
|
|
3511
5776
|
}
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
return prompt && prompt.length > 0 ? prompt : void 0;
|
|
5777
|
+
analyzeContent(request) {
|
|
5778
|
+
return this.analysisService.analyzeContent(request);
|
|
3515
5779
|
}
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
return this.engine.chat(request);
|
|
3519
|
-
}
|
|
3520
|
-
return this.engine.generate(request);
|
|
5780
|
+
optimizeSEO(request) {
|
|
5781
|
+
return this.seoService.optimizeSEO(request);
|
|
3521
5782
|
}
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
return this.engine.chatStream(request, callbacks);
|
|
3525
|
-
}
|
|
3526
|
-
return this.engine.stream(request, callbacks);
|
|
5783
|
+
calculateSEOScore(content, keywords) {
|
|
5784
|
+
return this.seoService.calculateSEOScore(content, keywords);
|
|
3527
5785
|
}
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
return this.
|
|
5786
|
+
analyzeKeywords(content, keywords) {
|
|
5787
|
+
return Promise.resolve(this.seoService.analyzeKeywords(content, keywords));
|
|
5788
|
+
}
|
|
5789
|
+
predictABTest(request) {
|
|
5790
|
+
return this.abTestService.predictABTest(request);
|
|
5791
|
+
}
|
|
5792
|
+
compareVariants(variantA, variantB) {
|
|
5793
|
+
return this.abTestService.compareVariants(variantA, variantB);
|
|
5794
|
+
}
|
|
5795
|
+
generateImagePrompt(description, style) {
|
|
5796
|
+
return this.imagePromptService.generateImagePrompt(description, style);
|
|
5797
|
+
}
|
|
5798
|
+
generateImagePromptsForBlog(blogContent) {
|
|
5799
|
+
return this.imagePromptService.generateImagePromptsForBlog(blogContent);
|
|
5800
|
+
}
|
|
5801
|
+
generateImage(request) {
|
|
5802
|
+
return this.mediaGenerationService.generateImage(request);
|
|
5803
|
+
}
|
|
5804
|
+
generateVideo(request) {
|
|
5805
|
+
return this.mediaGenerationService.generateVideo(request);
|
|
5806
|
+
}
|
|
5807
|
+
convertImageToVideo(request) {
|
|
5808
|
+
return this.mediaGenerationService.convertImageToVideo(request);
|
|
5809
|
+
}
|
|
5810
|
+
transformVideo(request) {
|
|
5811
|
+
return this.mediaGenerationService.transformVideo(request);
|
|
3536
5812
|
}
|
|
3537
5813
|
};
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
5814
|
+
|
|
5815
|
+
// src/wizard/content.wizard.ts
|
|
5816
|
+
var CONTENT_WIZARD_STEPS = [
|
|
5817
|
+
{
|
|
5818
|
+
id: "select-type",
|
|
5819
|
+
label: "Select Type",
|
|
5820
|
+
icon: "Layers",
|
|
5821
|
+
description: "Choose the type of content you want to generate",
|
|
5822
|
+
validate: (data) => Boolean(data.contentType)
|
|
5823
|
+
},
|
|
5824
|
+
{
|
|
5825
|
+
id: "input-topic",
|
|
5826
|
+
label: "Topic",
|
|
5827
|
+
icon: "Lightbulb",
|
|
5828
|
+
description: "Enter your topic or idea",
|
|
5829
|
+
validate: (data) => {
|
|
5830
|
+
const topic = data.topic;
|
|
5831
|
+
return typeof topic === "string" && topic.length > 0;
|
|
5832
|
+
}
|
|
5833
|
+
},
|
|
5834
|
+
{
|
|
5835
|
+
id: "configure",
|
|
5836
|
+
label: "Configure",
|
|
5837
|
+
icon: "Settings",
|
|
5838
|
+
description: "Set tone, audience, and platform",
|
|
5839
|
+
validate: (data) => {
|
|
5840
|
+
const d = data;
|
|
5841
|
+
return Boolean(d.tone && d.targetAudience);
|
|
5842
|
+
}
|
|
5843
|
+
},
|
|
5844
|
+
{
|
|
5845
|
+
id: "advanced",
|
|
5846
|
+
label: "Advanced",
|
|
5847
|
+
icon: "Sliders",
|
|
5848
|
+
description: "Additional options and optimizations",
|
|
5849
|
+
optional: true
|
|
5850
|
+
},
|
|
5851
|
+
{
|
|
5852
|
+
id: "preview",
|
|
5853
|
+
label: "Preview",
|
|
5854
|
+
icon: "Eye",
|
|
5855
|
+
description: "Review your settings before generation"
|
|
5856
|
+
},
|
|
5857
|
+
{
|
|
5858
|
+
id: "generating",
|
|
5859
|
+
label: "Generating",
|
|
5860
|
+
icon: "Sparkles",
|
|
5861
|
+
description: "AI is generating your content"
|
|
5862
|
+
},
|
|
5863
|
+
{
|
|
5864
|
+
id: "results",
|
|
5865
|
+
label: "Results",
|
|
5866
|
+
icon: "CheckCircle",
|
|
5867
|
+
description: "Your generated content is ready"
|
|
3549
5868
|
}
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
5869
|
+
];
|
|
5870
|
+
function buildContentRequest(data) {
|
|
5871
|
+
switch (data.contentType) {
|
|
5872
|
+
case "blog":
|
|
5873
|
+
return {
|
|
5874
|
+
topic: data.topic ?? "",
|
|
5875
|
+
blogType: "tutorial",
|
|
5876
|
+
targetKeywords: data.keywords ?? [],
|
|
5877
|
+
tone: data.tone ?? "professional",
|
|
5878
|
+
wordCount: data.wordCount ?? 1e3,
|
|
5879
|
+
targetAudience: data.targetAudience ?? "",
|
|
5880
|
+
seoOptimization: data.seoOptimization ?? false,
|
|
5881
|
+
includeImages: data.includeImages ?? false,
|
|
5882
|
+
includeSchema: false
|
|
5883
|
+
};
|
|
5884
|
+
case "social":
|
|
5885
|
+
return {
|
|
5886
|
+
topic: data.topic ?? "",
|
|
5887
|
+
platform: data.platform ?? "twitter",
|
|
5888
|
+
tone: data.tone ?? "casual",
|
|
5889
|
+
hashtags: true,
|
|
5890
|
+
targetAudience: data.targetAudience,
|
|
5891
|
+
includeCallToAction: data.includeCallToAction
|
|
5892
|
+
};
|
|
5893
|
+
case "script":
|
|
5894
|
+
return {
|
|
5895
|
+
topic: data.topic ?? "",
|
|
5896
|
+
tone: data.tone ?? "professional",
|
|
5897
|
+
duration: data.duration ?? 60,
|
|
5898
|
+
targetAudience: data.targetAudience ?? "",
|
|
5899
|
+
includeVisuals: true,
|
|
5900
|
+
includeCallToAction: data.includeCallToAction ?? false
|
|
5901
|
+
};
|
|
5902
|
+
default:
|
|
5903
|
+
throw new Error(`Unsupported content type: ${data.contentType}`);
|
|
3560
5904
|
}
|
|
3561
|
-
return null;
|
|
3562
5905
|
}
|
|
3563
5906
|
|
|
3564
|
-
// src/
|
|
3565
|
-
var
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
5907
|
+
// src/wizard/image.wizard.ts
|
|
5908
|
+
var IMAGE_WIZARD_STEPS = [
|
|
5909
|
+
{
|
|
5910
|
+
id: "input-prompt",
|
|
5911
|
+
label: "Describe Image",
|
|
5912
|
+
icon: "Image",
|
|
5913
|
+
description: "Describe the image you want to create",
|
|
5914
|
+
validate: (data) => {
|
|
5915
|
+
const prompt = data.prompt;
|
|
5916
|
+
return typeof prompt === "string" && prompt.length > 0;
|
|
5917
|
+
}
|
|
5918
|
+
},
|
|
5919
|
+
{
|
|
5920
|
+
id: "select-style",
|
|
5921
|
+
label: "Style",
|
|
5922
|
+
icon: "Palette",
|
|
5923
|
+
description: "Choose the artistic style",
|
|
5924
|
+
validate: (data) => Boolean(data.style)
|
|
5925
|
+
},
|
|
5926
|
+
{
|
|
5927
|
+
id: "configure",
|
|
5928
|
+
label: "Configure",
|
|
5929
|
+
icon: "Settings",
|
|
5930
|
+
description: "Set format, quality, and quantity",
|
|
5931
|
+
validate: (data) => {
|
|
5932
|
+
const d = data;
|
|
5933
|
+
return Boolean(d.format && d.quality);
|
|
5934
|
+
}
|
|
5935
|
+
},
|
|
5936
|
+
{
|
|
5937
|
+
id: "advanced",
|
|
5938
|
+
label: "Advanced",
|
|
5939
|
+
icon: "Sliders",
|
|
5940
|
+
description: "Color palette and platform optimization",
|
|
5941
|
+
optional: true
|
|
5942
|
+
},
|
|
5943
|
+
{ id: "preview", label: "Preview", icon: "Eye", description: "Review your settings" },
|
|
5944
|
+
{ id: "generating", label: "Generating", icon: "Sparkles", description: "AI is creating your image" },
|
|
5945
|
+
{ id: "results", label: "Results", icon: "Image", description: "Your generated image is ready" }
|
|
5946
|
+
];
|
|
5947
|
+
function buildImageRequest(data) {
|
|
5948
|
+
return {
|
|
5949
|
+
type: "image",
|
|
5950
|
+
prompt: data.prompt ?? "",
|
|
5951
|
+
style: data.style,
|
|
5952
|
+
format: data.format,
|
|
5953
|
+
quality: data.quality,
|
|
5954
|
+
quantity: data.quantity ?? 1
|
|
5955
|
+
};
|
|
3582
5956
|
}
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
5957
|
+
|
|
5958
|
+
// src/wizard/video.wizard.ts
|
|
5959
|
+
var validateSource = (data) => {
|
|
5960
|
+
const d = data;
|
|
5961
|
+
if (d.generationType === "text-to-video") {
|
|
5962
|
+
return typeof d.prompt === "string" && d.prompt.length > 0;
|
|
5963
|
+
}
|
|
5964
|
+
if (d.generationType === "image-to-video") {
|
|
5965
|
+
return typeof d.imageUrl === "string" && d.imageUrl.length > 0;
|
|
5966
|
+
}
|
|
5967
|
+
if (d.generationType === "video-to-video") {
|
|
5968
|
+
return typeof d.sourceVideoUrl === "string" && d.sourceVideoUrl.length > 0 && typeof d.prompt === "string" && d.prompt.length > 0;
|
|
5969
|
+
}
|
|
5970
|
+
return false;
|
|
5971
|
+
};
|
|
5972
|
+
var VIDEO_WIZARD_STEPS = [
|
|
5973
|
+
{
|
|
5974
|
+
id: "select-type",
|
|
5975
|
+
label: "Select Type",
|
|
5976
|
+
icon: "Video",
|
|
5977
|
+
description: "Choose video generation type",
|
|
5978
|
+
validate: (data) => Boolean(data.generationType)
|
|
5979
|
+
},
|
|
5980
|
+
{
|
|
5981
|
+
id: "input-source",
|
|
5982
|
+
label: "Source",
|
|
5983
|
+
icon: "FileInput",
|
|
5984
|
+
description: "Provide your source (prompt, image, or video)",
|
|
5985
|
+
validate: validateSource
|
|
5986
|
+
},
|
|
5987
|
+
{
|
|
5988
|
+
id: "configure",
|
|
5989
|
+
label: "Configure",
|
|
5990
|
+
icon: "Settings",
|
|
5991
|
+
description: "Set duration, aspect ratio, and style",
|
|
5992
|
+
validate: (data) => {
|
|
5993
|
+
const d = data;
|
|
5994
|
+
return Boolean(d.duration && d.aspectRatio);
|
|
5995
|
+
}
|
|
5996
|
+
},
|
|
5997
|
+
{ id: "advanced", label: "Advanced", icon: "Sliders", description: "Additional video options", optional: true },
|
|
5998
|
+
{ id: "preview", label: "Preview", icon: "Eye", description: "Review your settings" },
|
|
5999
|
+
{ id: "generating", label: "Generating", icon: "Sparkles", description: "AI is creating your video" },
|
|
6000
|
+
{ id: "results", label: "Results", icon: "Video", description: "Your generated video is ready" }
|
|
6001
|
+
];
|
|
6002
|
+
function buildVideoRequest(data) {
|
|
6003
|
+
switch (data.generationType) {
|
|
6004
|
+
case "text-to-video":
|
|
6005
|
+
return {
|
|
6006
|
+
type: "video",
|
|
6007
|
+
prompt: data.prompt ?? "",
|
|
6008
|
+
duration: data.duration ?? 5,
|
|
6009
|
+
aspectRatio: data.aspectRatio ?? "16:9",
|
|
6010
|
+
style: data.style,
|
|
6011
|
+
quality: data.quality
|
|
6012
|
+
};
|
|
6013
|
+
case "image-to-video":
|
|
6014
|
+
return {
|
|
6015
|
+
type: "video",
|
|
6016
|
+
imageUrl: data.imageUrl ?? "",
|
|
6017
|
+
prompt: data.prompt,
|
|
6018
|
+
duration: data.duration ?? 5,
|
|
6019
|
+
motion: data.motion ?? "medium",
|
|
6020
|
+
camera: data.camera ?? "static"
|
|
6021
|
+
};
|
|
6022
|
+
case "video-to-video":
|
|
6023
|
+
return {
|
|
6024
|
+
type: "video",
|
|
6025
|
+
sourceVideoUrl: data.sourceVideoUrl ?? "",
|
|
6026
|
+
prompt: data.prompt ?? "",
|
|
6027
|
+
styleTransfer: data.styleTransfer,
|
|
6028
|
+
enhanceQuality: data.enhanceQuality
|
|
6029
|
+
};
|
|
6030
|
+
default:
|
|
6031
|
+
throw new Error(`Unsupported generation type: ${data.generationType}`);
|
|
6032
|
+
}
|
|
3586
6033
|
}
|
|
3587
6034
|
|
|
6035
|
+
exports.ABTestService = ABTestService;
|
|
6036
|
+
exports.AIContentService = AIContentService;
|
|
3588
6037
|
exports.AIError = AIError;
|
|
3589
6038
|
exports.AIErrorType = AIErrorType;
|
|
3590
6039
|
exports.ANTHROPIC_API_VERSION = ANTHROPIC_API_VERSION;
|
|
@@ -3592,14 +6041,23 @@ exports.ANTHROPIC_DEFAULT_MAX_TOKENS = ANTHROPIC_DEFAULT_MAX_TOKENS;
|
|
|
3592
6041
|
exports.ANTHROPIC_MODELS = ANTHROPIC_MODELS;
|
|
3593
6042
|
exports.ANTHROPIC_TIERS = ANTHROPIC_TIERS;
|
|
3594
6043
|
exports.API_ENDPOINTS = API_ENDPOINTS;
|
|
6044
|
+
exports.ASPECT_RATIO_OPTIONS = ASPECT_RATIO_OPTIONS;
|
|
6045
|
+
exports.ASPECT_RATIO_VALUES = ASPECT_RATIO_VALUES;
|
|
6046
|
+
exports.AnalysisService = AnalysisService;
|
|
3595
6047
|
exports.AnthropicHttpTransport = AnthropicHttpTransport;
|
|
3596
6048
|
exports.AnthropicProvider = AnthropicProvider;
|
|
3597
6049
|
exports.AnthropicRequestBuilder = AnthropicRequestBuilder;
|
|
6050
|
+
exports.AuthenticationError = AuthenticationError;
|
|
6051
|
+
exports.BaseAIProvider = BaseProvider;
|
|
3598
6052
|
exports.BaseProvider = BaseProvider;
|
|
6053
|
+
exports.BlogService = BlogService;
|
|
6054
|
+
exports.CONTENT_WIZARD_STEPS = CONTENT_WIZARD_STEPS;
|
|
3599
6055
|
exports.CacheManager = CacheManager;
|
|
6056
|
+
exports.CancellationError = CancellationError;
|
|
3600
6057
|
exports.ChatIdGenerator = ChatIdGenerator;
|
|
3601
6058
|
exports.ChatService = ChatService;
|
|
3602
6059
|
exports.CompositeExecutor = CompositeExecutor;
|
|
6060
|
+
exports.ContentCalendarService = ContentCalendarService;
|
|
3603
6061
|
exports.DEFAULT_API_KEY_RULE = DEFAULT_API_KEY_RULE;
|
|
3604
6062
|
exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
|
|
3605
6063
|
exports.DEFAULT_ERROR_CLASSIFICATION_RULE = DEFAULT_ERROR_CLASSIFICATION_RULE;
|
|
@@ -3616,9 +6074,12 @@ exports.DebounceManager = DebounceManager;
|
|
|
3616
6074
|
exports.DedupeExecutor = DedupeExecutor;
|
|
3617
6075
|
exports.FalProvider = FalProvider;
|
|
3618
6076
|
exports.GEMINI_TIERS = GEMINI_TIERS;
|
|
6077
|
+
exports.GENERATION_COSTS = CREDIT_COSTS;
|
|
3619
6078
|
exports.GROQ_MODELS = GROQ_MODELS;
|
|
3620
6079
|
exports.GROQ_TIERS = GROQ_TIERS;
|
|
3621
6080
|
exports.GeminiProvider = GeminiProvider;
|
|
6081
|
+
exports.GenerationExecutor = GenerationExecutor;
|
|
6082
|
+
exports.GenerationFailedError = GenerationFailedError;
|
|
3622
6083
|
exports.GroqChatRequestBuilder = GroqChatRequestBuilder;
|
|
3623
6084
|
exports.GroqConfigStore = GroqConfigStore;
|
|
3624
6085
|
exports.GroqError = GroqError;
|
|
@@ -3626,11 +6087,20 @@ exports.GroqErrorType = AIErrorType;
|
|
|
3626
6087
|
exports.GroqHttpClient = GroqHttpClient;
|
|
3627
6088
|
exports.GroqHttpTransport = GroqHttpTransport;
|
|
3628
6089
|
exports.GroqProvider = GroqProvider;
|
|
6090
|
+
exports.IMAGE_QUALITY_OPTIONS = IMAGE_QUALITY_OPTIONS;
|
|
6091
|
+
exports.IMAGE_QUALITY_VALUES = IMAGE_QUALITY_VALUES;
|
|
6092
|
+
exports.IMAGE_WIZARD_STEPS = IMAGE_WIZARD_STEPS;
|
|
6093
|
+
exports.ImagePromptService = ImagePromptService;
|
|
3629
6094
|
exports.JSON_CODE_BLOCK_PATTERN = JSON_CODE_BLOCK_PATTERN;
|
|
3630
6095
|
exports.JSON_OBJECT_PATTERN = JSON_OBJECT_PATTERN;
|
|
3631
6096
|
exports.JsonResponseParser = JsonResponseParser;
|
|
6097
|
+
exports.MOTION_OPTIONS = MOTION_OPTIONS;
|
|
6098
|
+
exports.MediaGenerationService = MediaGenerationService;
|
|
6099
|
+
exports.PromptBuilder = PromptBuilder;
|
|
3632
6100
|
exports.ProviderError = ProviderError;
|
|
3633
6101
|
exports.ProviderFactory = ProviderFactory;
|
|
6102
|
+
exports.ProviderQuotaExhaustedError = ProviderQuotaExhaustedError;
|
|
6103
|
+
exports.ProviderUnavailableError = ProviderUnavailableError;
|
|
3634
6104
|
exports.PrunaProvider = PrunaProvider;
|
|
3635
6105
|
exports.QueueExecutor = QueueExecutor;
|
|
3636
6106
|
exports.RequestDeduplicator = RequestDeduplicator;
|
|
@@ -3640,20 +6110,35 @@ exports.RetryManager = RetryManager;
|
|
|
3640
6110
|
exports.SSE_BRIDGE_EVENTS = SSE_BRIDGE_EVENTS;
|
|
3641
6111
|
exports.SSE_DONE_MARKER = SSE_DONE_MARKER;
|
|
3642
6112
|
exports.SSE_PREFIX = SSE_PREFIX;
|
|
6113
|
+
exports.STYLE_PRESETS = STYLE_PRESETS;
|
|
6114
|
+
exports.SeoService = SeoService;
|
|
6115
|
+
exports.SocialService = SocialService;
|
|
3643
6116
|
exports.StreamingResponseParser = StreamingResponseParser;
|
|
3644
6117
|
exports.StructuredPromptBuilder = StructuredPromptBuilder;
|
|
3645
6118
|
exports.TEXT_TIERS = TEXT_TIERS;
|
|
3646
6119
|
exports.TIMEOUTS = TIMEOUTS;
|
|
3647
6120
|
exports.TextGenerationService = TextGenerationService;
|
|
3648
6121
|
exports.ThrottleManager = ThrottleManager;
|
|
6122
|
+
exports.TimeoutError = TimeoutError;
|
|
3649
6123
|
exports.USER_FRIENDLY_ERROR_MESSAGES = USER_FRIENDLY_ERROR_MESSAGES;
|
|
6124
|
+
exports.VIDEO_DURATION_OPTIONS = VIDEO_DURATION_OPTIONS;
|
|
6125
|
+
exports.VIDEO_DURATION_OPTIONS_SECONDS = VIDEO_DURATION_OPTIONS_SECONDS;
|
|
6126
|
+
exports.VIDEO_QUALITY_OPTIONS = VIDEO_QUALITY_OPTIONS;
|
|
6127
|
+
exports.VIDEO_QUALITY_VALUES = VIDEO_QUALITY_VALUES;
|
|
6128
|
+
exports.VIDEO_WIZARD_STEPS = VIDEO_WIZARD_STEPS;
|
|
6129
|
+
exports.ValidationError = ValidationError2;
|
|
6130
|
+
exports.VideoScriptService = VideoScriptService;
|
|
3650
6131
|
exports.asObjectArray = asObjectArray;
|
|
3651
6132
|
exports.asRecord = asRecord;
|
|
3652
6133
|
exports.asStringArray = asStringArray;
|
|
3653
6134
|
exports.asTrimmedOptionalString = asTrimmedOptionalString;
|
|
3654
6135
|
exports.buildCacheKey = buildCacheKey;
|
|
6136
|
+
exports.buildContentRequest = buildContentRequest;
|
|
3655
6137
|
exports.buildHttpStatusFallback = buildHttpStatusFallback;
|
|
6138
|
+
exports.buildImageRequest = buildImageRequest;
|
|
6139
|
+
exports.buildVideoRequest = buildVideoRequest;
|
|
3656
6140
|
exports.cacheManager = cacheManager;
|
|
6141
|
+
exports.calculateCreditCost = calculateContentCreditCost;
|
|
3657
6142
|
exports.chatIdGenerator = chatIdGenerator;
|
|
3658
6143
|
exports.clampScore = clampScore;
|
|
3659
6144
|
exports.classifyHttpStatus = classifyHttpStatus;
|
|
@@ -3664,7 +6149,9 @@ exports.createAI = createAI;
|
|
|
3664
6149
|
exports.createAssistantMessage = createAssistantMessage;
|
|
3665
6150
|
exports.createFalProvider = createFalProvider;
|
|
3666
6151
|
exports.createGeminiProvider = createGeminiProvider;
|
|
6152
|
+
exports.createGroqProvider = createGroqProvider;
|
|
3667
6153
|
exports.createMessage = createMessage;
|
|
6154
|
+
exports.createProviderFactory = createProviderFactory;
|
|
3668
6155
|
exports.createPrunaProvider = createPrunaProvider;
|
|
3669
6156
|
exports.createSseResponse = createSseResponse;
|
|
3670
6157
|
exports.createSystemMessage = createSystemMessage;
|
|
@@ -3685,8 +6172,12 @@ exports.extractTextFromMessages = extractTextFromMessages;
|
|
|
3685
6172
|
exports.extractTokenUsage = extractTokenUsage;
|
|
3686
6173
|
exports.formatErrorForLogging = formatErrorForLogging;
|
|
3687
6174
|
exports.formatMessagesForDisplay = formatMessagesForDisplay;
|
|
6175
|
+
exports.generateImageThenVideo = generateImageThenVideo;
|
|
6176
|
+
exports.generatePruna = generate;
|
|
3688
6177
|
exports.generateWithPruna = generateWithPruna;
|
|
6178
|
+
exports.getDimensionsForAspectRatio = getDimensionsForAspectRatio;
|
|
3689
6179
|
exports.getProvider = getProvider;
|
|
6180
|
+
exports.getRecommendedQuality = getRecommendedQuality;
|
|
3690
6181
|
exports.isAuthErrorType = isAuthErrorType;
|
|
3691
6182
|
exports.isMediaProvider = isMediaProvider;
|
|
3692
6183
|
exports.isRetryableErrorType = isRetryableErrorType;
|
|
@@ -3700,6 +6191,7 @@ exports.mergeTierOverrides = mergeTierOverrides;
|
|
|
3700
6191
|
exports.messageFormatter = messageFormatter;
|
|
3701
6192
|
exports.normalizeSseChunk = normalizeSseChunk;
|
|
3702
6193
|
exports.oneOfCatalog = oneOfCatalog;
|
|
6194
|
+
exports.pollForResult = pollForResult;
|
|
3703
6195
|
exports.promptToMessages = promptToMessages;
|
|
3704
6196
|
exports.repairCommonJsonDefects = repairCommonJsonDefects;
|
|
3705
6197
|
exports.requestDeduplicator = requestDeduplicator;
|
|
@@ -3711,9 +6203,11 @@ exports.resolveUserFriendlyErrorMessage = resolveUserFriendlyErrorMessage;
|
|
|
3711
6203
|
exports.resolveUserFriendlyMessage = resolveUserFriendlyMessage;
|
|
3712
6204
|
exports.retryManager = retryManager;
|
|
3713
6205
|
exports.splitSseLines = splitSseLines;
|
|
6206
|
+
exports.submitPrediction = submitPrediction;
|
|
3714
6207
|
exports.throttle = throttle;
|
|
3715
6208
|
exports.throttleManager = throttleManager;
|
|
3716
6209
|
exports.toGroqMessages = toGroqMessages;
|
|
6210
|
+
exports.uploadImage = uploadImage;
|
|
3717
6211
|
exports.validateApiKey = validateApiKey;
|
|
3718
6212
|
exports.withRetry = withRetry;
|
|
3719
6213
|
//# sourceMappingURL=index.cjs.map
|