@zodic/shared 0.0.350 → 0.0.351

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/app/api/index.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import OpenAI from 'openai';
2
- import { v4 } from 'uuid';
3
2
  import {
4
3
  BackendBindings,
5
4
  BatchInputItem,
@@ -12,7 +11,6 @@ import {
12
11
  NatalChartInterpretation,
13
12
  } from '../../types';
14
13
  import {
15
- AstrologyApiResponse,
16
14
  ChatGPTOptions,
17
15
  HouseCuspsReport,
18
16
  LeonardoGenerateImageParams,
@@ -21,6 +19,12 @@ import {
21
19
  NatalHouseCuspReport,
22
20
  TimezoneResponse,
23
21
  } from '../../types/scopes/legacy';
22
+ import {
23
+ resizeImage,
24
+ sendRequest,
25
+ uploadImageToStorage,
26
+ validateImageUrl,
27
+ } from '../../utils/faceSwapHelpers';
24
28
  import { makeAstrologyApiCall } from './astrology';
25
29
 
26
30
  const deepseek = (env: BackendBindings) =>
@@ -781,87 +785,10 @@ export const Api = (env: BackendBindings) => ({
781
785
  taskId: string;
782
786
  message: string;
783
787
  }> => {
784
- const endpoint = 'https://api.piapi.ai/api/v1/task';
785
- const headers = {
786
- 'x-api-key': `${env.PIAPI_API_KEY}`,
787
- 'Content-Type': 'application/json',
788
- };
789
-
790
- // Validate image URLs before making the request
791
- const validateImageUrl = async (
792
- url: string,
793
- label: string
794
- ): Promise<void> => {
795
- try {
796
- const response = await fetch(url, { method: 'HEAD' });
797
- if (!response.ok) {
798
- throw new Error(
799
- `${label} is inaccessible: ${response.status} ${response.statusText}`
800
- );
801
- }
802
- const contentType = response.headers.get('Content-Type');
803
- if (!contentType || !contentType.startsWith('image/')) {
804
- throw new Error(
805
- `${label} is not a valid image: Content-Type is ${contentType}`
806
- );
807
- }
808
- } catch (err: any) {
809
- console.error(`Error validating ${label}:`, err.message);
810
- throw new Error(`Invalid ${label}: ${err.message}`);
811
- }
812
- };
813
-
814
- // Helper function to resize an image by calling the Express server
815
- const resizeImage = async (
816
- url: string,
817
- label: string
818
- ): Promise<ArrayBuffer> => {
819
- const resizeEndpoint = `https://zodiako-image-mounter.onrender.com/resize-image?url=${encodeURIComponent(
820
- url
821
- )}`; // Replace with your Express server URL
822
- try {
823
- const response = await fetch(resizeEndpoint);
824
- if (!response.ok) {
825
- throw new Error(
826
- `${label} resize failed: ${response.status} ${response.statusText}`
827
- );
828
- }
829
- const contentType = response.headers.get('Content-Type');
830
- if (!contentType || !contentType.startsWith('image/')) {
831
- throw new Error(
832
- `${label} resize response is not an image: Content-Type is ${contentType}`
833
- );
834
- }
835
- return await response.arrayBuffer();
836
- } catch (err: any) {
837
- console.error(`Error resizing ${label}:`, err.message);
838
- throw new Error(`Failed to resize ${label}: ${err.message}`);
839
- }
840
- };
841
-
842
- const getKey = (label: string) => {
843
- const timestamp = Date.now();
844
- const key = `${label.toLowerCase().replace(/ /g, '_')}_${timestamp}.jpg`;
845
- return key;
846
- };
847
-
848
- // Helper function to upload an image to storage and get a public URL
849
- const uploadImageToStorage = async (
850
- imageData: ArrayBuffer,
851
- label: string
852
- ): Promise<string> => {
853
- const key = getKey(label);
854
- await env.PHOTOS_BUCKET.put(key, imageData);
855
- const imageUrl = `https://pub-bbc0cdb569734925ab1c39512bc4765d.r2.dev/${key}`;
856
- return imageUrl;
857
- };
858
-
859
788
  try {
860
- // Validate image URLs
861
789
  await validateImageUrl(sourceImageUrl, 'Source image URL');
862
790
  await validateImageUrl(targetImageUrl, 'Target image URL');
863
791
 
864
- // Resize images by calling the Express server
865
792
  const resizedSourceData = await resizeImage(
866
793
  sourceImageUrl,
867
794
  'Source image'
@@ -871,14 +798,15 @@ export const Api = (env: BackendBindings) => ({
871
798
  'Target image'
872
799
  );
873
800
 
874
- // Upload resized images to storage to get public URLs
875
801
  const resizedSourceUrl = await uploadImageToStorage(
876
802
  resizedSourceData,
877
- 'user_photo'
803
+ 'user_photo',
804
+ env
878
805
  );
879
806
  const resizedTargetUrl = await uploadImageToStorage(
880
807
  resizedTargetData,
881
- 'generated_image'
808
+ 'generated_image',
809
+ env
882
810
  );
883
811
 
884
812
  const body = JSON.stringify({
@@ -902,73 +830,13 @@ export const Api = (env: BackendBindings) => ({
902
830
  resizedTargetUrl,
903
831
  });
904
832
 
905
- const response = await fetch(endpoint, {
906
- method: 'POST',
907
- headers,
908
- body,
909
- });
910
-
911
- // Log the HTTP status and headers for debugging
912
- console.log('PiAPI FaceSwap Response Status:', response.status);
913
- console.log('PiAPI FaceSwap Response Headers:', [
914
- ...response.headers.entries(),
915
- ]);
916
-
917
- // Read the response body as text first to inspect it
918
- const responseText = await response.text();
919
- console.log('PiAPI FaceSwap Raw Response Body:', responseText);
920
-
921
- // Check for the expected 201 Created status
922
- if (response.status !== 201 && response.status !== 200) {
923
- let errorData;
924
- try {
925
- errorData = JSON.parse(responseText);
926
- } catch {
927
- errorData = { message: responseText || 'Unknown error' };
928
- }
929
- console.error('Error from PiAPI FaceSwap:', errorData);
930
- throw new Error(
931
- `PiAPI FaceSwap Error: ${response.status} - ${
932
- errorData.message || 'Unknown error'
933
- }`
934
- );
935
- }
936
-
937
- // Check if the response body is empty
938
- if (!responseText) {
939
- console.error('PiAPI FaceSwap response body is empty');
940
- throw new Error('PiAPI FaceSwap Error: Empty response body');
941
- }
833
+ const responseData = await sendRequest(body, 1, env);
942
834
 
943
- // Try to parse the response as JSON
944
- let data;
945
- try {
946
- data = JSON.parse(responseText) as {
947
- task_id: string;
948
- message: string;
949
- };
950
- } catch (err) {
951
- console.error(
952
- 'Failed to parse PiAPI FaceSwap response as JSON:',
953
- responseText
954
- );
955
- throw new Error('PiAPI FaceSwap Error: Invalid JSON response');
956
- }
957
-
958
- // Validate the response structure
959
- if (!data.task_id || !data.message) {
960
- console.error(
961
- 'PiAPI FaceSwap response missing required fields:',
962
- data
963
- );
964
- throw new Error('PiAPI FaceSwap Error: Invalid response structure');
965
- }
966
-
967
- console.log('FaceSwap task created successfully:', data);
835
+ console.log('FaceSwap task created successfully:', responseData);
968
836
 
969
837
  return {
970
- taskId: data.task_id,
971
- message: data.message,
838
+ taskId: responseData.data.task_id,
839
+ message: responseData.message,
972
840
  };
973
841
  } catch (err: any) {
974
842
  console.error('Error calling PiAPI FaceSwap:', err.message);
@@ -976,139 +844,4 @@ export const Api = (env: BackendBindings) => ({
976
844
  }
977
845
  },
978
846
  },
979
-
980
- mock: {
981
- callTimezone: async ({
982
- lat,
983
- lon,
984
- }: {
985
- lat: number;
986
- lon: number;
987
- }): Promise<TimezoneResponse> => {
988
- console.log('Mocked callTimezone invoked', lat, lon);
989
- await new Promise((resolve) => setTimeout(resolve, 3000)); // Simulate 3 seconds delay
990
- return {
991
- dstOffset: 3600, // Daylight saving time offset in seconds
992
- rawOffset: -10800, // Standard time offset in seconds (UTC-3)
993
- status: 'OK',
994
- timeZoneId: 'America/Sao_Paulo', // Timezone ID for São Paulo
995
- timeZoneName: 'Brasília Standard Time', // Official name for the timezone
996
- };
997
- },
998
-
999
- callAstrology: async (params: {
1000
- day: number;
1001
- month: number;
1002
- year: number;
1003
- hour: number;
1004
- min: number;
1005
- lat: number;
1006
- lon: number;
1007
- tzone: number;
1008
- }): Promise<AstrologyApiResponse> => {
1009
- console.log('Mocked callAstrology invoked', params);
1010
- await new Promise((resolve) => setTimeout(resolve, 3000)); // Simulate 3 seconds delay
1011
- return [
1012
- {
1013
- name: 'Sun',
1014
- fullDegree: 259.3692854715937,
1015
- normDegree: 19.369285471593685,
1016
- speed: 1.0163786145803477,
1017
- isRetro: 'false',
1018
- sign: 'Sagittarius',
1019
- house: 11,
1020
- },
1021
- {
1022
- name: 'Moon',
1023
- fullDegree: 113.01532109301493,
1024
- normDegree: 23.015321093014933,
1025
- speed: 13.422889565180862,
1026
- isRetro: 'false',
1027
- sign: 'Libra',
1028
- house: 7,
1029
- },
1030
- {
1031
- name: 'Mars',
1032
- fullDegree: 319.2803341423472,
1033
- normDegree: 19.280334142347215,
1034
- speed: 0.7621766783897992,
1035
- isRetro: 'false',
1036
- sign: 'Aquarius',
1037
- house: 1,
1038
- },
1039
- {
1040
- name: 'Mercury',
1041
- fullDegree: 267.157142291648,
1042
- normDegree: 27.157142291647972,
1043
- speed: -1.129353838866472,
1044
- isRetro: 'true',
1045
- sign: 'Sagittarius',
1046
- house: 12,
1047
- },
1048
- {
1049
- name: 'Jupiter',
1050
- fullDegree: 286.74844079893995,
1051
- normDegree: 16.748440798939953,
1052
- speed: 0.21989257845513263,
1053
- isRetro: 'false',
1054
- sign: 'Capricorn',
1055
- house: 12,
1056
- },
1057
- {
1058
- name: 'Venus',
1059
- fullDegree: 302.46109957231073,
1060
- normDegree: 2.4610995723107294,
1061
- speed: 1.1733155507134867,
1062
- isRetro: 'false',
1063
- sign: 'Aquarius',
1064
- house: 1,
1065
- },
1066
- {
1067
- name: 'Saturn',
1068
- fullDegree: 232.5728863112664,
1069
- normDegree: 22.572886311266387,
1070
- speed: 0.1112696563244877,
1071
- isRetro: 'false',
1072
- sign: 'Scorpio',
1073
- house: 10,
1074
- },
1075
- {
1076
- name: 'Uranus',
1077
- fullDegree: 254.1300701628793,
1078
- normDegree: 14.130070162879292,
1079
- speed: 0.06114507772452163,
1080
- isRetro: 'false',
1081
- sign: 'Sagittarius',
1082
- house: 11,
1083
- },
1084
- {
1085
- name: 'Neptune',
1086
- fullDegree: 270.70233881243786,
1087
- normDegree: 0.7023388124378585,
1088
- speed: 0.037269287688777034,
1089
- isRetro: 'false',
1090
- sign: 'Capricorn',
1091
- house: 12,
1092
- },
1093
- {
1094
- name: 'Pluto',
1095
- fullDegree: 213.81656661729093,
1096
- normDegree: 3.816566617290931,
1097
- speed: 0.030884363476755057,
1098
- isRetro: 'false',
1099
- sign: 'Scorpio',
1100
- house: 9,
1101
- },
1102
- {
1103
- name: 'Ascendant',
1104
- fullDegree: 288.3761917494198,
1105
- normDegree: 18.376191749419775,
1106
- speed: 0,
1107
- isRetro: 'false',
1108
- sign: 'Aquarius',
1109
- house: 1,
1110
- },
1111
- ];
1112
- },
1113
- },
1114
847
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zodic/shared",
3
- "version": "0.0.350",
3
+ "version": "0.0.351",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -598,4 +598,55 @@ export interface Composition {
598
598
  ascendant: ZodiacSignSlug;
599
599
  moon: ZodiacSignSlug;
600
600
  indexesToGenerate?: number[];
601
+ }
602
+
603
+ export interface ApidogModel {
604
+ code: number;
605
+ data: Data;
606
+ message: string;
607
+ [property: string]: any;
608
+ }
609
+
610
+ export interface Data {
611
+ detail: null;
612
+ error: Error | null;
613
+ input: { [key: string]: any };
614
+ logs: { [key: string]: any }[];
615
+ meta: Meta;
616
+ model: string;
617
+ output: { [key: string]: any };
618
+ status: Status;
619
+ task_id: string;
620
+ task_type: string;
621
+ [property: string]: any;
622
+ }
623
+
624
+ export interface Error {
625
+ code?: number;
626
+ message?: string;
627
+ [property: string]: any;
628
+ }
629
+
630
+ export interface Meta {
631
+ created_at?: string;
632
+ ended_at?: string;
633
+ is_using_private_pool: boolean;
634
+ started_at?: string;
635
+ usage: Usage;
636
+ [property: string]: any;
637
+ }
638
+
639
+ export interface Usage {
640
+ consume: number;
641
+ frozen: number;
642
+ type: string;
643
+ [property: string]: any;
644
+ }
645
+
646
+ export enum Status {
647
+ Completed = "Completed",
648
+ Failed = "Failed",
649
+ Pending = "Pending",
650
+ Processing = "Processing",
651
+ Staged = "Staged",
601
652
  }
@@ -0,0 +1,211 @@
1
+ import { ApidogModel, BackendBindings, Status } from '../types';
2
+
3
+ export const validateImageUrl = async (
4
+ url: string,
5
+ label: string
6
+ ): Promise<void> => {
7
+ try {
8
+ const response = await fetch(url, { method: 'HEAD' });
9
+ if (!response.ok) {
10
+ throw new Error(
11
+ `${label} is inaccessible: ${response.status} ${response.statusText}`
12
+ );
13
+ }
14
+ const contentType = response.headers.get('Content-Type');
15
+ if (!contentType || !contentType.startsWith('image/')) {
16
+ throw new Error(
17
+ `${label} is not a valid image: Content-Type is ${contentType}`
18
+ );
19
+ }
20
+ } catch (err: any) {
21
+ console.error(`Error validating ${label}:`, err.message);
22
+ throw new Error(`Invalid ${label}: ${err.message}`);
23
+ }
24
+ };
25
+
26
+ // Helper function to resize an image by calling the Express server
27
+ export const resizeImage = async (
28
+ url: string,
29
+ label: string
30
+ ): Promise<ArrayBuffer> => {
31
+ const resizeEndpoint = `https://zodiako-image-mounter.onrender.com/resize-image?url=${encodeURIComponent(
32
+ url
33
+ )}`;
34
+ try {
35
+ const response = await fetch(resizeEndpoint);
36
+ if (!response.ok) {
37
+ throw new Error(
38
+ `${label} resize failed: ${response.status} ${response.statusText}`
39
+ );
40
+ }
41
+ const contentType = response.headers.get('Content-Type');
42
+ if (!contentType || !contentType.startsWith('image/')) {
43
+ throw new Error(
44
+ `${label} resize response is not an image: Content-Type is ${contentType}`
45
+ );
46
+ }
47
+ return await response.arrayBuffer();
48
+ } catch (err: any) {
49
+ console.error(`Error resizing ${label}:`, err.message);
50
+ throw new Error(`Failed to resize ${label}: ${err.message}`);
51
+ }
52
+ };
53
+
54
+ export const getKey = (label: string) => {
55
+ const timestamp = Date.now();
56
+ const key = `${label.toLowerCase().replace(/ /g, '_')}_${timestamp}.jpg`;
57
+ return key;
58
+ };
59
+
60
+ // Helper function to upload an image to storage and get a public URL
61
+ export const uploadImageToStorage = async (
62
+ imageData: ArrayBuffer,
63
+ label: string,
64
+ env: BackendBindings
65
+ ): Promise<string> => {
66
+ const key = getKey(label);
67
+ await env.PHOTOS_BUCKET.put(key, imageData);
68
+ const imageUrl = `https://pub-bbc0cdb569734925ab1c39512bc4765d.r2.dev/${key}`;
69
+ return imageUrl;
70
+ };
71
+
72
+ export const sendRequest = async (
73
+ body: string,
74
+ attempt: number = 1,
75
+ env: BackendBindings
76
+ ): Promise<ApidogModel> => {
77
+ const maxRetries = 3;
78
+ const retryDelay = 1000; // 1 second delay between retries
79
+ const endpoint = 'https://api.piapi.ai/api/v1/task';
80
+ const headers = {
81
+ 'x-api-key': `${env.PIAPI_API_KEY}`,
82
+ 'Content-Type': 'application/json',
83
+ };
84
+
85
+ try {
86
+ const response = await fetch(endpoint, {
87
+ method: 'POST',
88
+ headers,
89
+ body,
90
+ });
91
+
92
+ console.log('PiAPI FaceSwap Response Status:', response.status);
93
+ console.log('PiAPI FaceSwap Response Headers:', [
94
+ ...response.headers.entries(),
95
+ ]);
96
+
97
+ const responseText = await response.text();
98
+ console.log('PiAPI FaceSwap Raw Response Body:', responseText);
99
+
100
+ // Check for expected 201 Created or 200 OK status
101
+ if (response.status !== 201 && response.status !== 200) {
102
+ // Handle retryable status codes (429, 503)
103
+ if (
104
+ (response.status === 429 || response.status === 503) &&
105
+ attempt <= maxRetries
106
+ ) {
107
+ console.log(
108
+ `Attempt ${attempt} failed with status ${response.status}. Retrying in ${retryDelay}ms...`
109
+ );
110
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
111
+ return sendRequest(body, attempt + 1, env);
112
+ }
113
+
114
+ let errorData: any;
115
+ try {
116
+ errorData = JSON.parse(responseText);
117
+ } catch {
118
+ errorData = { message: responseText || 'Unknown error' };
119
+ }
120
+ throw new Error(
121
+ `PiAPI FaceSwap Error: ${response.status} - ${
122
+ errorData.message || 'Unknown error'
123
+ }`
124
+ );
125
+ }
126
+
127
+ // Log a warning if we get a 200 instead of the expected 201
128
+ if (response.status === 200) {
129
+ console.warn(
130
+ 'PiAPI FaceSwap returned 200 OK instead of the expected 201 Created. This might indicate a documentation mismatch or unexpected API behavior.'
131
+ );
132
+ }
133
+
134
+ // Check if response body is empty
135
+ if (!responseText) {
136
+ throw new Error('PiAPI FaceSwap Error: Empty response body');
137
+ }
138
+
139
+ // Parse the response as JSON
140
+ let data: ApidogModel;
141
+ try {
142
+ data = JSON.parse(responseText) as ApidogModel;
143
+ } catch (err) {
144
+ console.error(
145
+ 'Failed to parse PiAPI FaceSwap response as JSON:',
146
+ responseText
147
+ );
148
+ throw new Error('PiAPI FaceSwap Error: Invalid JSON response');
149
+ }
150
+
151
+ // Validate the response structure
152
+ if (
153
+ !data ||
154
+ typeof data.code !== 'number' ||
155
+ !data.data ||
156
+ typeof data.message !== 'string'
157
+ ) {
158
+ console.error('PiAPI FaceSwap response missing required fields:', data);
159
+ throw new Error('PiAPI FaceSwap Error: Invalid response structure');
160
+ }
161
+
162
+ // Check the response code
163
+ if (data.code !== 0) {
164
+ const errorMsg = data.message || 'Unknown error';
165
+ const detailedError = data.data.error
166
+ ? ` - ${data.data.error.message || 'No detailed error message'}`
167
+ : '';
168
+ throw new Error(
169
+ `PiAPI FaceSwap Error: Code ${data.code} - ${errorMsg}${detailedError}`
170
+ );
171
+ }
172
+
173
+ // Validate the data structure
174
+ if (!data.data.task_id || !data.data.status) {
175
+ console.error('PiAPI FaceSwap data missing required fields:', data.data);
176
+ throw new Error('PiAPI FaceSwap Error: Invalid data structure');
177
+ }
178
+
179
+ // Check the task status
180
+ if (data.data.status === Status.Failed) {
181
+ const errorMsg =
182
+ data.data.error?.message || 'Task failed with unknown error';
183
+ throw new Error(`PiAPI FaceSwap Error: Task failed - ${errorMsg}`);
184
+ }
185
+
186
+ // Ensure the task is in a valid state for creation
187
+ if (
188
+ data.data.status !== Status.Staged &&
189
+ data.data.status !== Status.Pending &&
190
+ data.data.status !== Status.Processing
191
+ ) {
192
+ throw new Error(
193
+ `PiAPI FaceSwap Error: Unexpected task status - ${data.data.status}`
194
+ );
195
+ }
196
+
197
+ return data;
198
+ } catch (err: any) {
199
+ if (
200
+ attempt <= maxRetries &&
201
+ (err.message.includes('503') || err.message.includes('429'))
202
+ ) {
203
+ console.log(
204
+ `Attempt ${attempt} failed: ${err.message}. Retrying in ${retryDelay}ms...`
205
+ );
206
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
207
+ return sendRequest(body, attempt + 1, env);
208
+ }
209
+ throw err;
210
+ }
211
+ };