@zodic/shared 0.0.349 → 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 +14 -281
- package/package.json +1 -1
- package/types/scopes/generic.ts +51 -0
- package/utils/faceSwapHelpers.ts +211 -0
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
|
|
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) {
|
|
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
|
-
|
|
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:
|
|
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
package/types/scopes/generic.ts
CHANGED
|
@@ -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
|
+
};
|