vidspotai-shared 1.0.28 → 1.0.30

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.
Files changed (32) hide show
  1. package/lib/globals/aiModels.d.ts +14 -9
  2. package/lib/globals/aiModels.d.ts.map +1 -1
  3. package/lib/globals/aiModels.js +495 -187
  4. package/lib/models/video.model.d.ts +5 -0
  5. package/lib/models/video.model.d.ts.map +1 -1
  6. package/lib/services/aiGen/providers/bytedance/bytedance.service.d.ts +9 -0
  7. package/lib/services/aiGen/providers/bytedance/bytedance.service.d.ts.map +1 -0
  8. package/lib/services/aiGen/providers/bytedance/bytedance.service.js +159 -0
  9. package/lib/services/aiGen/providers/bytedance/constants.d.ts +2 -0
  10. package/lib/services/aiGen/providers/bytedance/constants.d.ts.map +1 -0
  11. package/lib/services/aiGen/providers/bytedance/constants.js +27 -0
  12. package/lib/services/aiGen/providers/bytedance/index.d.ts +2 -0
  13. package/lib/services/aiGen/providers/bytedance/index.d.ts.map +1 -0
  14. package/lib/services/aiGen/providers/bytedance/index.js +17 -0
  15. package/lib/services/aiGen/providers/bytedance/types.d.ts +5 -0
  16. package/lib/services/aiGen/providers/bytedance/types.d.ts.map +1 -0
  17. package/lib/services/aiGen/providers/bytedance/types.js +1 -0
  18. package/lib/services/aiGen/providers/google/google.service.js +1 -1
  19. package/lib/services/aiGen/providers/kling/kling.service.js +1 -1
  20. package/lib/services/aiGen/providers/minimax/minimax.service.js +1 -1
  21. package/lib/services/aiGen/providers/openai/openai.service.js +1 -1
  22. package/lib/services/gcp/gsheet.service.d.ts +7 -0
  23. package/lib/services/gcp/gsheet.service.d.ts.map +1 -1
  24. package/lib/services/gcp/gsheet.service.js +46 -0
  25. package/lib/utils/helpers.js +2 -2
  26. package/lib/utils/index.d.ts +1 -0
  27. package/lib/utils/index.d.ts.map +1 -1
  28. package/lib/utils/index.js +1 -0
  29. package/lib/utils/logger.d.ts +4 -3
  30. package/lib/utils/logger.d.ts.map +1 -1
  31. package/lib/utils/logger.js +60 -60
  32. package/package.json +2 -1
@@ -32,6 +32,11 @@ export interface IVideoJobModel {
32
32
  totalCreditsUsed?: number;
33
33
  estimatedCredits?: number;
34
34
  durationType?: EVideoDurationType;
35
+ feedback?: {
36
+ rating?: number;
37
+ comment?: string;
38
+ createdAt?: FieldValue;
39
+ };
35
40
  duration?: number;
36
41
  mode?: EVideoMode;
37
42
  dimensions?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"video.model.d.ts","sourceRoot":"","sources":["../../src/models/video.model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAe,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,WAAW,EACZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IAEnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CAOpB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,CAAC;IAC1B,MAAM,EAAE,eAAe,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAGlC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,SAAS,EAAE,UAAU,CAAC;IACtB,SAAS,EAAE,UAAU,CAAC;CACvB"}
1
+ {"version":3,"file":"video.model.d.ts","sourceRoot":"","sources":["../../src/models/video.model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAe,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,WAAW,EACZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IAEnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CAOpB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,CAAC;IAC1B,MAAM,EAAE,eAAe,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,SAAS,CAAC,EAAE,UAAU,CAAC;KACxB,CAAA;IAGD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,SAAS,EAAE,UAAU,CAAC;IACtB,SAAS,EAAE,UAAU,CAAC;CACvB"}
@@ -0,0 +1,9 @@
1
+ import { BaseAiGenProviderService } from "../baseAiGenProvider.service";
2
+ import { CreditUsageParams, VideoGenerationParams, VideoGenerationResult, VideoStatusParams, VideoStatusResult } from "../types";
3
+ export declare class ByteDanceService extends BaseAiGenProviderService {
4
+ private readonly baseUrl;
5
+ generateVideo(params: VideoGenerationParams): Promise<VideoGenerationResult>;
6
+ checkVideoStatus({ task, outputFilename, outputFilePath, }: VideoStatusParams): Promise<VideoStatusResult>;
7
+ getCreditUsed(params: CreditUsageParams): number;
8
+ }
9
+ //# sourceMappingURL=bytedance.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bytedance.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/bytedance/bytedance.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AASlB,qBAAa,gBAAiB,SAAQ,wBAAwB;IAC5D,OAAO,CAAC,QAAQ,CAAC,OAAO,CACsD;IAExE,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IA4D3B,gBAAgB,CAAC,EACrB,IAAI,EACJ,cAAc,EACd,cAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA4FjD,aAAa,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM;CAoCjD"}
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ByteDanceService = void 0;
7
+ // bytedance.service.ts
8
+ const baseAiGenProvider_service_1 = require("../baseAiGenProvider.service");
9
+ const helpers_1 = require("../../helpers");
10
+ const aiModels_1 = require("../../../../globals/aiModels");
11
+ const types_1 = require("../../../../globals/types");
12
+ const firebase_1 = require("../../../../libs/firebase");
13
+ const helpers_2 = require("../../../../utils/helpers");
14
+ const logger_1 = require("../../../../utils/logger");
15
+ const crypto_1 = __importDefault(require("crypto"));
16
+ class ByteDanceService extends baseAiGenProvider_service_1.BaseAiGenProviderService {
17
+ constructor() {
18
+ super(...arguments);
19
+ this.baseUrl = "https://ark.ap-southeast.bytepluses.com/api/v3/contents/generations/tasks";
20
+ }
21
+ async generateVideo(params) {
22
+ (0, helpers_1.validateParams)(params);
23
+ const modelConfig = aiModels_1.aiModelConfigs[params.modelKey];
24
+ const modelId = modelConfig.modelId;
25
+ const content = [
26
+ {
27
+ type: "text",
28
+ text: params.prompt,
29
+ },
30
+ ];
31
+ // Optional image → Image-to-Video (first frame)
32
+ if (params.inputImageUrl) {
33
+ content.push({
34
+ type: "image",
35
+ image_url: params.inputImageUrl,
36
+ });
37
+ }
38
+ const body = {
39
+ model: modelId,
40
+ content,
41
+ ratio: params.aspectRatio, // e.g. 16:9
42
+ resolution: params.resolution, // e.g. 720p
43
+ duration: params.duration, // seconds
44
+ watermark: false,
45
+ service_tier: "default",
46
+ execution_expires_after: 172800, // 48h
47
+ };
48
+ const resp = await fetch(this.baseUrl, {
49
+ method: "POST",
50
+ headers: {
51
+ "Content-Type": "application/json",
52
+ Authorization: `Bearer ${process.env.BYTEDANCE_API_KEY}`,
53
+ },
54
+ body: JSON.stringify(body),
55
+ });
56
+ if (!resp.ok) {
57
+ const errText = await resp.text();
58
+ throw new Error(`ByteDance generateVideo failed (${resp.status}): ${errText}`);
59
+ }
60
+ const data = await resp.json();
61
+ if (!data.id) {
62
+ throw new Error("ByteDance API did not return task id");
63
+ }
64
+ return {
65
+ task: data.id,
66
+ status: types_1.EVideoSceneStatus.TRIGGERED,
67
+ };
68
+ }
69
+ async checkVideoStatus({ task, outputFilename, outputFilePath = "videos", }) {
70
+ const url = `https://ark.ap-southeast.bytepluses.com/api/v3/contents/generations/tasks/${task}`;
71
+ const resp = await fetch(url, {
72
+ method: "GET",
73
+ headers: {
74
+ "Content-Type": "application/json",
75
+ Authorization: `Bearer ${process.env.BYTEDANCE_API_KEY}`,
76
+ },
77
+ });
78
+ if (!resp.ok) {
79
+ const errText = await resp.text();
80
+ throw new Error(`ByteDance checkVideoStatus failed (${resp.status}): ${errText}`);
81
+ }
82
+ const data = await resp.json();
83
+ // ---- Status mapping ----
84
+ switch (data.status) {
85
+ case "queued":
86
+ case "running":
87
+ return { status: types_1.EVideoSceneStatus.PENDING };
88
+ case "failed":
89
+ case "expired":
90
+ case "cancelled":
91
+ return {
92
+ status: types_1.EVideoSceneStatus.FAILED,
93
+ errorMessage: data.error
94
+ ? JSON.stringify(data.error)
95
+ : "ByteDance video generation failed",
96
+ };
97
+ case "succeeded":
98
+ break;
99
+ default:
100
+ return {
101
+ status: types_1.EVideoSceneStatus.FAILED,
102
+ errorMessage: `Unknown ByteDance task status: ${data.status}`,
103
+ };
104
+ }
105
+ // ---- Success path ----
106
+ const sourceVideoUrl = data.content?.video_url;
107
+ if (!sourceVideoUrl) {
108
+ return {
109
+ status: types_1.EVideoSceneStatus.FAILED,
110
+ errorMessage: "ByteDance response missing video_url",
111
+ };
112
+ }
113
+ // Download generated video
114
+ const videoResp = await fetch(sourceVideoUrl);
115
+ if (!videoResp.ok) {
116
+ throw new Error(`Failed to download ByteDance video (${videoResp.status})`);
117
+ }
118
+ const buffer = Buffer.from(await videoResp.arrayBuffer());
119
+ // ---- Upload to Firebase Storage (PUBLIC URL) ----
120
+ const filePath = `${outputFilePath}/${outputFilename}.mp4`;
121
+ const bucket = (0, firebase_1.getBucket)();
122
+ const file = bucket.file(filePath);
123
+ const downloadToken = crypto_1.default.randomUUID();
124
+ await file.save(buffer, {
125
+ contentType: "video/mp4",
126
+ resumable: false,
127
+ metadata: {
128
+ firebaseStorageDownloadTokens: downloadToken,
129
+ cacheControl: "public, max-age=31536000",
130
+ },
131
+ });
132
+ const publicVideoUrl = `https://firebasestorage.googleapis.com/v0/b/${bucket.name}` +
133
+ `/o/${encodeURIComponent(filePath)}?alt=media&token=${downloadToken}`;
134
+ return {
135
+ videoUrl: publicVideoUrl,
136
+ status: types_1.EVideoSceneStatus.COMPLETED,
137
+ };
138
+ }
139
+ getCreditUsed(params) {
140
+ const modelConfig = aiModels_1.aiModelConfigs[params.modelKey];
141
+ if (!modelConfig.cost?.per1KTokens ||
142
+ !params.resolution ||
143
+ !params.aspectRatio ||
144
+ !params.duration) {
145
+ logger_1.logger.warn(`Missing cost or parameters for credit calculation. Returning default credits. Params: ${JSON.stringify(params)}, ModelConfig: ${JSON.stringify(modelConfig)}`);
146
+ return 10; // default fallback
147
+ }
148
+ const dims = dimensionsMap[params.resolution]?.[params.aspectRatio];
149
+ if (!dims) {
150
+ logger_1.logger.warn(`Unsupported resolution/aspectRatio for credit calculation. Returning default credits. Params: ${JSON.stringify(params)}`);
151
+ return 10; // default fallback
152
+ }
153
+ const FRAME_RATE = 24;
154
+ const tokensConsumed = Math.ceil((dims?.width * dims?.height * FRAME_RATE * params.duration) / 1024);
155
+ const cost = (modelConfig.cost.per1KTokens * tokensConsumed) / 1000;
156
+ return (0, helpers_2.getCreditsFromCost)(cost);
157
+ }
158
+ }
159
+ exports.ByteDanceService = ByteDanceService;
@@ -0,0 +1,2 @@
1
+ declare const dimensionsMap: Record<string, Record<string, VideoDimensions>>;
2
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/bytedance/constants.ts"],"names":[],"mappings":"AAEA,QAAA,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAyBlE,CAAC"}
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ const dimensionsMap = {
3
+ "480p": {
4
+ "21:9": { width: 960, height: 416 },
5
+ "16:9": { width: 864, height: 480 },
6
+ "9:16": { width: 480, height: 864 },
7
+ "1:1": { width: 640, height: 640 },
8
+ "4:3": { width: 736, height: 544 },
9
+ "3:4": { width: 544, height: 736 },
10
+ },
11
+ "720p": {
12
+ "21:9": { width: 1504, height: 640 },
13
+ "16:9": { width: 1248, height: 704 },
14
+ "9:16": { width: 704, height: 1248 },
15
+ "1:1": { width: 960, height: 960 },
16
+ "4:3": { width: 1120, height: 832 },
17
+ "3:4": { width: 832, height: 1120 },
18
+ },
19
+ "1080p": {
20
+ "21:9": { width: 2176, height: 928 },
21
+ "16:9": { width: 1920, height: 1088 },
22
+ "9:16": { width: 1088, height: 1920 },
23
+ "1:1": { width: 1440, height: 1440 },
24
+ "4:3": { width: 1664, height: 1248 },
25
+ "3:4": { width: 1248, height: 1664 },
26
+ },
27
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./bytedance.service";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/bytedance/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./bytedance.service"), exports);
@@ -0,0 +1,5 @@
1
+ type VideoDimensions = {
2
+ width: number;
3
+ height: number;
4
+ };
5
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/bytedance/types.ts"],"names":[],"mappings":"AAAA,KAAK,eAAe,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC"}
@@ -0,0 +1 @@
1
+ "use strict";
@@ -105,7 +105,7 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
105
105
  const modelConfig = aiModels_1.aiModelConfigs[modelKey];
106
106
  return modelConfig.cost?.perSecond
107
107
  ? (0, helpers_1.getCreditsFromCost)(modelConfig.cost?.perSecond * duration)
108
- : 0; // should not happen
108
+ : 3; // should not happen
109
109
  }
110
110
  }
111
111
  exports.GoogleService = GoogleService;
@@ -117,7 +117,7 @@ class KlingService extends baseAiGenProvider_service_1.BaseAiGenProviderService
117
117
  getCreditUsed({ modelKey, mode = types_2.EVideoMode.PROFESSIONAL, duration = 5, }) {
118
118
  const modelConfig = aiModels_1.aiModelConfigs[modelKey];
119
119
  const cost = modelConfig?.cost?.table?.[mode]?.[duration];
120
- return (0, helpers_2.getCreditsFromCost)(cost ?? 0);
120
+ return (0, helpers_2.getCreditsFromCost)(cost ?? 1);
121
121
  }
122
122
  }
123
123
  exports.KlingService = KlingService;
@@ -112,7 +112,7 @@ class MinimaxService extends baseAiGenProvider_service_1.BaseAiGenProviderServic
112
112
  const modelConfig = aiModels_1.aiModelConfigs[modelKey];
113
113
  const cost = modelConfig.cost?.fixed ??
114
114
  modelConfig.cost?.table?.[resolution]?.[duration];
115
- return (0, helpers_2.getCreditsFromCost)(cost ?? 0);
115
+ return (0, helpers_2.getCreditsFromCost)(cost ?? 0.5); // default to 10 cents if unknown // log error as well
116
116
  }
117
117
  }
118
118
  exports.MinimaxService = MinimaxService;
@@ -105,7 +105,7 @@ class OpenaiService extends baseAiGenProvider_service_1.BaseAiGenProviderService
105
105
  getCreditUsed({ modelKey, resolution = "720x1280", duration = 5, }) {
106
106
  const modelConfig = aiModels_1.aiModelConfigs[modelKey];
107
107
  const cost = modelConfig?.cost?.table?.[resolution]?.[duration];
108
- return (0, utils_1.getCreditsFromCost)(cost ?? 0);
108
+ return (0, utils_1.getCreditsFromCost)(cost ?? 2);
109
109
  }
110
110
  }
111
111
  exports.OpenaiService = OpenaiService;
@@ -12,5 +12,12 @@ export declare class GSheetService extends GCPService {
12
12
  private mergeHeaders;
13
13
  private ensureSheetExists;
14
14
  private columnLetter;
15
+ upsertRowByKey<T extends Record<string, any>>({ spreadsheetId, sheetName, header, row, keyColumn, }: {
16
+ spreadsheetId: string;
17
+ sheetName: string;
18
+ header: string[];
19
+ row: T;
20
+ keyColumn: string;
21
+ }): Promise<void>;
15
22
  }
16
23
  //# sourceMappingURL=gsheet.service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"gsheet.service.d.ts","sourceRoot":"","sources":["../../../src/services/gcp/gsheet.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAC;AAEvD,qBAAa,aAAc,SAAQ,UAAU;IAC3C,OAAO,CAAC,MAAM,CAAmB;;IAWjC;;OAEG;IACG,oBAAoB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACtD,MAAM,EAAE,4BAA4B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC;YAsDF,SAAS;YAYT,WAAW;IAezB,OAAO,CAAC,YAAY;YAMN,iBAAiB;IA4B/B,OAAO,CAAC,YAAY;CASrB"}
1
+ {"version":3,"file":"gsheet.service.d.ts","sourceRoot":"","sources":["../../../src/services/gcp/gsheet.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAC;AAEvD,qBAAa,aAAc,SAAQ,UAAU;IAC3C,OAAO,CAAC,MAAM,CAAmB;;IAWjC;;OAEG;IACG,oBAAoB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACtD,MAAM,EAAE,4BAA4B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC;YAsDF,SAAS;YAYT,WAAW;IAezB,OAAO,CAAC,YAAY;YAMN,iBAAiB;IA4B/B,OAAO,CAAC,YAAY;IAUd,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EAClD,aAAa,EACb,SAAS,EACT,MAAM,EACN,GAAG,EACH,SAAS,GACV,EAAE;QACD,aAAa,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,GAAG,EAAE,CAAC,CAAC;QACP,SAAS,EAAE,MAAM,CAAC;KACnB;CA2DF"}
@@ -99,5 +99,51 @@ class GSheetService extends gcp_service_1.GCPService {
99
99
  }
100
100
  return letter;
101
101
  }
102
+ async upsertRowByKey({ spreadsheetId, sheetName, header, row, keyColumn, }) {
103
+ await this.ensureSheetExists(spreadsheetId, sheetName);
104
+ const existingHeader = await this.getHeader(spreadsheetId, sheetName);
105
+ const finalHeader = existingHeader.length === 0
106
+ ? header
107
+ : this.mergeHeaders(existingHeader, header);
108
+ if (existingHeader.length === 0) {
109
+ await this.writeHeader(spreadsheetId, sheetName, finalHeader);
110
+ }
111
+ const keyIndex = finalHeader.indexOf(keyColumn);
112
+ if (keyIndex === -1) {
113
+ throw new Error(`Key column ${keyColumn} not found in header`);
114
+ }
115
+ // Get all data
116
+ const res = await this.sheets.spreadsheets.values.get({
117
+ spreadsheetId,
118
+ range: `${sheetName}!A2:${this.columnLetter(finalHeader.length)}`,
119
+ });
120
+ const rows = res.data.values ?? [];
121
+ const existingRowIndex = rows.findIndex((r) => r[keyIndex] === row[keyColumn]);
122
+ const values = finalHeader.map((col) => row[col] !== undefined ? row[col] : "");
123
+ if (existingRowIndex >= 0) {
124
+ // UPDATE existing row
125
+ const actualRowNumber = existingRowIndex + 2;
126
+ await this.sheets.spreadsheets.values.update({
127
+ spreadsheetId,
128
+ range: `${sheetName}!A${actualRowNumber}`,
129
+ valueInputOption: "USER_ENTERED",
130
+ requestBody: {
131
+ values: [values],
132
+ },
133
+ });
134
+ }
135
+ else {
136
+ // APPEND new row
137
+ await this.sheets.spreadsheets.values.append({
138
+ spreadsheetId,
139
+ range: `${sheetName}!A:${this.columnLetter(finalHeader.length)}`,
140
+ valueInputOption: "USER_ENTERED",
141
+ insertDataOption: "INSERT_ROWS",
142
+ requestBody: {
143
+ values: [values],
144
+ },
145
+ });
146
+ }
147
+ }
102
148
  }
103
149
  exports.GSheetService = GSheetService;
@@ -49,8 +49,8 @@ const getPriceIdByType = (type, freq = types_1.ERENEWAL_FREQUENCY.MONTHLY) => {
49
49
  };
50
50
  exports.getPriceIdByType = getPriceIdByType;
51
51
  const getCreditsFromCost = (cost) => {
52
- const margin = 0.2;
53
- const costPerCredit = 0.1;
52
+ const margin = 0.2; // 20% margin
53
+ const costPerCredit = 0.1; // multiple
54
54
  const minCredits = 10;
55
55
  if (cost <= 0)
56
56
  return 0;
@@ -1,2 +1,3 @@
1
1
  export * from "./helpers";
2
+ export * from "./logger";
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC"}
@@ -15,3 +15,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./helpers"), exports);
18
+ __exportStar(require("./logger"), exports);
@@ -1,6 +1,7 @@
1
- import { Logger } from "winston";
1
+ import winston, { Logger } from "winston";
2
2
  /**
3
- * Winston logger
3
+ * Logger factory
4
4
  */
5
- export declare const logger: Logger;
5
+ export declare function createLogger(serviceName: string): Logger;
6
+ export declare const logger: winston.Logger;
6
7
  //# sourceMappingURL=logger.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAsF1C;;GAEG;AACH,eAAO,MAAM,MAAM,EAAE,MAUnB,CAAC"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AA0B1C;;GAEG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAsExD;AAED,eAAO,MAAM,MAAM,gBAAyB,CAAC"}
@@ -4,8 +4,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.logger = void 0;
7
+ exports.createLogger = createLogger;
7
8
  const winston_1 = __importDefault(require("winston"));
8
9
  const winston_slack_webhook_transport_1 = __importDefault(require("winston-slack-webhook-transport"));
10
+ const winston_loki_1 = __importDefault(require("winston-loki"));
9
11
  const { combine, timestamp, errors, printf, colorize, json } = winston_1.default.format;
10
12
  const isProduction = process.env.NODE_ENV === "production";
11
13
  /**
@@ -16,72 +18,70 @@ const consoleFormat = combine(colorize(), timestamp({ format: "YYYY-MM-DD HH:mm:
16
18
  return `${timestamp} [${level}]: ${stack ?? message}${Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : ""}`;
17
19
  }));
18
20
  /**
19
- * Slack transport (ERROR ONLY)
21
+ * Logger factory
20
22
  */
21
- const slackTransport = new winston_slack_webhook_transport_1.default({
22
- webhookUrl: process.env.SLACK_WEBHOOK_URL,
23
- level: "error",
24
- // IMPORTANT: do not narrow the parameter type
25
- formatter: (info) => {
26
- const log = info;
27
- return {
28
- text: `🚨 *${process.env.SERVICE_NAME ?? "Service"} Error*`,
29
- blocks: [
30
- {
31
- type: "header",
32
- text: {
33
- type: "plain_text",
34
- text: "🚨 Application Error",
35
- },
36
- },
37
- {
38
- type: "section",
39
- fields: [
40
- {
41
- type: "mrkdwn",
42
- text: `*Service:*\n${process.env.SERVICE_NAME ?? "unknown"}`,
43
- },
44
- {
45
- type: "mrkdwn",
46
- text: `*Environment:*\n${process.env.NODE_ENV ?? "unknown"}`,
47
- },
48
- {
23
+ function createLogger(serviceName) {
24
+ /**
25
+ * Grafana Loki transport (ALL LOGS)
26
+ */
27
+ const lokiTransport = process.env.GRAFANA_LOKI_URL && process.env.GRAFANA_LOKI_AUTH
28
+ ? new winston_loki_1.default({
29
+ host: process.env.GRAFANA_LOKI_URL,
30
+ basicAuth: process.env.GRAFANA_LOKI_AUTH,
31
+ labels: {
32
+ service: serviceName,
33
+ environment: process.env.NODE_ENV ?? "unknown",
34
+ },
35
+ json: true,
36
+ replaceTimestamp: true,
37
+ onConnectionError: (err) => {
38
+ console.error("Loki connection error", err);
39
+ },
40
+ })
41
+ : null;
42
+ /**
43
+ * Slack transport (ERROR ONLY)
44
+ */
45
+ const slackTransport = isProduction && process.env.SLACK_WEBHOOK_URL
46
+ ? new winston_slack_webhook_transport_1.default({
47
+ webhookUrl: process.env.SLACK_WEBHOOK_URL,
48
+ level: "error",
49
+ formatter: (info) => {
50
+ const log = info;
51
+ const blocks = [
52
+ {
53
+ type: "section",
54
+ text: {
49
55
  type: "mrkdwn",
50
- text: `*Level:*\n${log.level.toUpperCase()}`,
56
+ text: `🚨 *${serviceName}*\n\`\`\`${log.message}\`\`\``,
51
57
  },
52
- ],
53
- },
54
- {
55
- type: "section",
56
- text: {
57
- type: "mrkdwn",
58
- text: `*Message:*\n\`\`\`${log.message}\`\`\``,
59
58
  },
60
- },
61
- log.stack
62
- ? {
59
+ ];
60
+ if (log.stack) {
61
+ blocks.push({
63
62
  type: "section",
64
63
  text: {
65
64
  type: "mrkdwn",
66
65
  text: `*Stack Trace:*\n\`\`\`${log.stack}\`\`\``,
67
66
  },
68
- }
69
- : undefined,
70
- ].filter(Boolean),
71
- };
72
- },
73
- });
74
- /**
75
- * Winston logger
76
- */
77
- exports.logger = winston_1.default.createLogger({
78
- level: "info",
79
- format: json(),
80
- transports: [
81
- new winston_1.default.transports.Console({
82
- format: consoleFormat,
83
- }),
84
- ...(isProduction && process.env.SLACK_WEBHOOK_URL ? [slackTransport] : []),
85
- ],
86
- exitOnError: false,
87
- });
67
+ });
68
+ }
69
+ return {
70
+ text: `🚨 ${serviceName} error`,
71
+ blocks,
72
+ };
73
+ },
74
+ })
75
+ : null;
76
+ return winston_1.default.createLogger({
77
+ level: "info",
78
+ format: json(),
79
+ transports: [
80
+ new winston_1.default.transports.Console({ format: consoleFormat }),
81
+ ...(lokiTransport ? [lokiTransport] : []),
82
+ ...(slackTransport ? [slackTransport] : []),
83
+ ],
84
+ exitOnError: false,
85
+ });
86
+ }
87
+ exports.logger = createLogger("shared");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vidspotai-shared",
3
- "version": "1.0.28",
3
+ "version": "1.0.30",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "exports": {
@@ -27,6 +27,7 @@
27
27
  "logform": "^2.7.0",
28
28
  "openai": "^6.1.0",
29
29
  "winston": "^3.19.0",
30
+ "winston-loki": "^6.1.3",
30
31
  "winston-slack-webhook-transport": "^2.3.6",
31
32
  "zod": "^4.1.11"
32
33
  },