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