@piapi/sdk
Version:
SDK for PIAPI
530 lines (521 loc) • 16.7 kB
JavaScript
import { defu } from 'defu';
// src/core/logger.ts
var ConsoleLogger = class {
constructor(enableDebug = false) {
this.enableDebug = enableDebug;
}
debug(message, ...args) {
if (this.enableDebug) {
console.debug(`[PIAPI Debug] ${message}`, ...args);
}
}
info(message, ...args) {
console.info(`[PIAPI Info] ${message}`, ...args);
}
warn(message, ...args) {
console.warn(`[PIAPI Warn] ${message}`, ...args);
}
error(message, ...args) {
console.error(`[PIAPI Error] ${message}`, ...args);
}
};
// src/core/base.ts
var PIAPI = class {
constructor(apiKey, baseUrl = "https://api.piapi.ai/api/v1") {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
this.logger = new ConsoleLogger(false);
}
// 添加启用/关闭调试日志的方法
enableDebug(enable = true) {
this.logger = new ConsoleLogger(enable);
}
async request(endpoint, options = {}) {
this.logger.debug(`Making request to ${endpoint}`, options);
const response = await fetch(`${this.baseUrl}${endpoint}`, {
...options,
headers: {
"Content-Type": "application/json",
"X-API-Key": this.apiKey,
...options.headers
}
});
const data = await response.json();
this.logger.debug(`Response received:`, data);
if (!response.ok) {
this.logger.error(`X-API Error: ${response.statusText}`, data);
}
return data;
}
async createTask(request) {
this.logger.info(`Creating new task`, { type: request.task_type, model: request.model });
this.logger.debug("Request payload:", {
task_type: request.task_type,
model: request.model,
input: request.input,
config: request.config
});
return this.request("/task", {
method: "POST",
body: JSON.stringify(request)
});
}
async getTask(taskId) {
this.logger.debug(`Fetching task status`, { taskId });
return this.request(`/task/${taskId}`);
}
async waitForTask(taskId, options = {}) {
const { maxAttempts = 60, interval = 2e3 } = options;
let attempts = 0;
this.logger.info(`Starting to poll task`, { taskId, maxAttempts, interval });
while (attempts < maxAttempts) {
try {
const response = await this.getTask(taskId);
if (response.data.status === "completed") {
this.logger.info(`Task completed successfully`, { taskId });
return response;
}
if (response.data.status === "failed") {
const errorMessage = `Task failed: ${response.data.error?.message || "Unknown error"}`;
this.logger.error(errorMessage, { taskId, error: response.data.error });
throw new Error(errorMessage);
}
this.logger.debug(`Task still processing`, { taskId, attempt: attempts + 1, status: response.data.status });
await new Promise((resolve) => setTimeout(resolve, interval));
attempts++;
} catch (error) {
this.logger.error(`Error polling task`, { taskId, error });
if (error instanceof Error) {
throw new Error(`Failed to poll task: ${error.message}`);
}
throw error;
}
}
const timeoutError = `Task timeout after ${maxAttempts} attempts`;
this.logger.error(timeoutError, { taskId });
throw new Error(timeoutError);
}
async createTaskAndWait(request, options) {
const response = await this.createTask(request);
return this.waitForTask(response.data.task_id, options);
}
};
var _KlingAPI = class _KlingAPI extends PIAPI {
constructor(apiKey, config = {}, baseUrl) {
super(apiKey, baseUrl);
this.config = config;
}
createKlingRequest(params) {
this.logger.debug("Creating Kling request with params:", params);
const finalInput = defu(params, _KlingAPI.DEFAULT_PARAMS);
if (![5, 10].includes(finalInput.duration)) {
const error = "Kling duration must be either 5 or 10 seconds";
this.logger.error(error, { duration: finalInput.duration });
throw new Error(error);
}
const request = {
model: "kling",
task_type: "video_generation",
input: finalInput
};
if (this.config.webhookUrl && this.config.webhookSecret) {
request.config = {
webhook_config: {
endpoint: this.config.webhookUrl,
secret: this.config.webhookSecret
}
};
}
return request;
}
async textToVideo(params) {
this.logger.info("Starting text-to-video generation", { prompt: params.prompt });
const request = this.createKlingRequest(params);
return this.createTask(request);
}
async imageToVideo(params) {
const request = this.createKlingRequest(params);
return this.createTask(request);
}
async elementsToVideo(params) {
if (!params.elements || !Array.isArray(params.elements) || params.elements.length === 0) {
throw new Error("Elements array is required and must not be empty");
}
if (params.elements.length > 4) {
throw new Error("Maximum 4 elements are allowed");
}
const request = this.createKlingRequest(params);
return this.createTask(request);
}
// 分别为不同类型的任务提供状态查询方法
async getTextToVideoTask(taskId) {
return this.getTask(taskId);
}
async getImageToVideoTask(taskId) {
return this.getTask(taskId);
}
async getElementsToVideoTask(taskId) {
return this.getTask(taskId);
}
};
_KlingAPI.DEFAULT_PARAMS = {
duration: 5,
aspect_ratio: "16:9",
mode: "std",
version: "1.0",
cfg_scale: 0.5
};
_KlingAPI.DEFAULT_CAMERA = {
type: "default",
config: {
horizontal: 0,
pan: 0,
roll: 0,
tilt: 0,
vertical: 0,
zoom: 0
}
};
var KlingAPI = _KlingAPI;
// src/services/kling/tryon.ts
var _KlingTryonAPI = class _KlingTryonAPI extends PIAPI {
constructor(apiKey, config = {}, baseUrl) {
super(apiKey, baseUrl);
this.config = config;
}
createTryonRequest(params) {
if (!params.model_input) {
throw new Error("model_input is required");
}
if (params.dress_input && (params.upper_input || params.lower_input)) {
throw new Error("Cannot use dress_input together with upper_input or lower_input");
}
if (!params.dress_input && !params.upper_input && !params.lower_input) {
throw new Error("Must provide either dress_input or upper_input/lower_input");
}
const request = {
model: "kling",
task_type: "ai_try_on",
input: {
..._KlingTryonAPI.DEFAULT_PARAMS,
...params
}
};
if (this.config.webhookUrl && this.config.webhookSecret) {
request.config = {
webhook_config: {
endpoint: this.config.webhookUrl,
secret: this.config.webhookSecret
}
};
}
return request;
}
async tryOn(params) {
const request = this.createTryonRequest(params);
return this.createTask(request);
}
async tryOnAndWait(params, options) {
const request = this.createTryonRequest(params);
return this.createTaskAndWait(request, options);
}
};
_KlingTryonAPI.DEFAULT_PARAMS = {
batch_size: 1
};
var KlingTryonAPI = _KlingTryonAPI;
// src/services/kling/effects.ts
var _KlingEffectsAPI = class _KlingEffectsAPI extends PIAPI {
constructor(apiKey, config = {}, baseUrl) {
super(apiKey, baseUrl);
this.config = config;
}
createEffectsRequest(params) {
if (!params.image_url) {
throw new Error("image_url is required");
}
if (!params.effect) {
throw new Error("effect is required");
}
const effectConfig = _KlingEffectsAPI.EFFECTS_CONFIG[params.effect];
if (!effectConfig) {
throw new Error(`Invalid effect: ${params.effect}`);
}
const isProfessionalMode = params.professional_mode || false;
if (isProfessionalMode && !effectConfig.proMode) {
throw new Error(`Effect "${params.effect}" is not available in professional mode`);
}
if (!isProfessionalMode && !effectConfig.stdMode) {
throw new Error(`Effect "${params.effect}" is only available in professional mode`);
}
if (effectConfig.requiresPrompt && !params.prompt) {
throw new Error(`Effect "${params.effect}" requires a prompt`);
}
const request = {
model: "kling",
task_type: "effects",
input: {
image_url: params.image_url,
effect: params.effect,
...params.prompt && { prompt: params.prompt },
...params.professional_mode && { professional_mode: params.professional_mode }
}
};
if (this.config.webhookUrl && this.config.webhookSecret) {
request.config = {
webhook_config: {
endpoint: this.config.webhookUrl,
secret: this.config.webhookSecret
}
};
}
return request;
}
async applyEffect(params) {
const request = this.createEffectsRequest(params);
return this.createTask(request);
}
async applyEffectAndWait(params, options) {
const request = this.createEffectsRequest(params);
return this.createTaskAndWait(request, options);
}
// 获取特定 effect 任务状态
async getEffectTask(taskId) {
return this.getTask(taskId);
}
// 静态方法:获取指定 effect 的配置信息
static getEffectConfig(effect) {
return _KlingEffectsAPI.EFFECTS_CONFIG[effect];
}
// 静态方法:获取所有可用的 effects
static getAvailableEffects() {
return Object.keys(_KlingEffectsAPI.EFFECTS_CONFIG);
}
// 静态方法:根据模式筛选可用的 effects
static getEffectsByMode(professionalMode = false) {
return Object.entries(_KlingEffectsAPI.EFFECTS_CONFIG).filter(([_, config]) => professionalMode ? config.proMode : config.stdMode).map(([effect]) => effect);
}
};
// Effects 配置信息 - 定义每个 effect 的可用模式和是否需要 prompt
_KlingEffectsAPI.EFFECTS_CONFIG = {
squish: { stdMode: true, proMode: false, requiresPrompt: false },
expansion: { stdMode: true, proMode: false, requiresPrompt: false },
jellycat_oversea: { stdMode: true, proMode: false, requiresPrompt: false },
spinoff: { stdMode: false, proMode: true, requiresPrompt: false },
rocket: { stdMode: true, proMode: true, requiresPrompt: true },
hearting: { stdMode: true, proMode: true, requiresPrompt: true },
fighting: { stdMode: true, proMode: true, requiresPrompt: true },
kissing: { stdMode: true, proMode: true, requiresPrompt: true },
hugging: { stdMode: true, proMode: true, requiresPrompt: true },
figure: { stdMode: true, proMode: false, requiresPrompt: false },
vstack: { stdMode: true, proMode: true, requiresPrompt: false },
surfing: { stdMode: true, proMode: true, requiresPrompt: false },
birthday: { stdMode: true, proMode: true, requiresPrompt: true },
water: { stdMode: true, proMode: true, requiresPrompt: true }
};
var KlingEffectsAPI = _KlingEffectsAPI;
var _LumaAPI = class _LumaAPI extends PIAPI {
constructor(apiKey, config = {}, baseUrl) {
super(apiKey, baseUrl);
this.config = config;
}
createLumaRequest(params) {
const finalInput = defu(params, _LumaAPI.DEFAULT_PARAMS);
const request = {
model: "luma",
task_type: "video_generation",
input: finalInput
};
if (this.config.webhookUrl && this.config.webhookSecret) {
request.config = {
webhook_config: {
endpoint: this.config.webhookUrl,
secret: this.config.webhookSecret
}
};
}
return request;
}
async createVideo(params) {
const request = this.createLumaRequest(params);
return this.createTask(request);
}
};
_LumaAPI.DEFAULT_PARAMS = {
expand_prompt: true,
loop: true
};
var LumaAPI = _LumaAPI;
// src/services/faceswap/api.ts
var FaceSwapAPI = class extends PIAPI {
constructor(apiKey, config = {}, baseUrl) {
super(apiKey, baseUrl);
this.config = config;
}
createFaceSwapRequest(params) {
if (!params.target_image) {
throw new Error("target_image is required");
}
if (!params.swap_image) {
throw new Error("swap_image is required");
}
const request = {
model: "Qubico/image-toolkit",
task_type: "face-swap",
input: params
};
return request;
}
async faceSwap(params) {
const request = this.createFaceSwapRequest(params);
return this.createTask(request);
}
async faceSwapAndWait(params, options) {
const request = this.createFaceSwapRequest(params);
return this.createTaskAndWait(request, options);
}
async getFaceSwapTask(taskId) {
return this.getTask(taskId);
}
};
// src/services/hailuo/api.ts
var HailuoAPI = class extends PIAPI {
constructor(apiKey, config = {}, baseUrl) {
super(apiKey, baseUrl);
this.config = config;
}
validateImageUrl(imageUrl) {
if (!imageUrl) return;
const url = new URL(imageUrl);
const extension = url.pathname.split(".").pop()?.toLowerCase();
if (!extension || !["jpg", "jpeg", "png"].includes(extension)) {
throw new Error("Image URL must point to a JPG or PNG file");
}
}
createHailuoRequest(params) {
if (params.prompt && params.prompt.length > 2e3) {
throw new Error("Prompt must not exceed 2000 characters");
}
this.validateImageUrl(params.image_url);
const request = {
model: "hailuo",
task_type: "video_generation",
input: {
...params
},
config: {
service_mode: "public"
}
};
if (this.config.webhookUrl && this.config.webhookSecret) {
request.config = {
...request.config,
webhook_config: {
endpoint: this.config.webhookUrl,
secret: this.config.webhookSecret
}
};
}
return request;
}
async generateVideo(params) {
const request = this.createHailuoRequest(params);
return this.createTask(request);
}
async getVideoTask(taskId) {
return this.getTask(taskId);
}
};
// src/services/aihug/api.ts
var AIHugAPI = class extends PIAPI {
constructor(apiKey, config = {}, baseUrl) {
super(apiKey, baseUrl);
this.config = config;
}
validateImageUrl(imageUrl) {
try {
new URL(imageUrl);
} catch (error) {
throw new Error("Invalid image URL format");
}
}
createAIHugRequest(params) {
this.validateImageUrl(params.image_url);
const request = {
model: "Qubico/hug-video",
task_type: "image_to_video",
input: {
image_url: params.image_url,
duration: params.duration?.toString() ?? "5"
},
config: {
service_mode: "public"
}
};
if (this.config.webhookUrl && this.config.webhookSecret) {
request.config = {
...request.config,
webhook_config: {
endpoint: this.config.webhookUrl,
secret: this.config.webhookSecret
}
};
}
return request;
}
async generateVideo(params) {
const request = this.createAIHugRequest(params);
return this.createTask(request);
}
async getVideoTask(taskId) {
return this.getTask(taskId);
}
};
// src/services/wanx/api.ts
var _WanxAPI = class _WanxAPI extends PIAPI {
constructor(apiKey, config = {}, baseUrl) {
super(apiKey, baseUrl);
this.config = config;
}
createWanxRequest(taskType, params) {
this.logger.debug("Creating Wanx request with params:", params);
const request = {
model: _WanxAPI.MODEL_NAME,
task_type: taskType,
input: {
...params
}
};
if (this.config.webhookUrl && this.config.webhookSecret) {
request.config = {
webhook_config: {
endpoint: this.config.webhookUrl,
secret: this.config.webhookSecret
}
};
}
return request;
}
async textToVideo(params, modelSize = "14b") {
this.logger.info("Starting Wanx text-to-video generation", { prompt: params.prompt });
const taskType = modelSize === "1.3b" ? "txt2video-1.3b" : "txt2video-14b";
const request = this.createWanxRequest(taskType, params);
return this.createTask(request);
}
async imageToVideo(params) {
this.logger.info("Starting Wanx image-to-video generation", { prompt: params.prompt, image: params.image });
if (!params.image) {
throw new Error("Image URL is required for image-to-video generation");
}
const request = this.createWanxRequest("img2video-14b", params);
return this.createTask(request);
}
async getVideoTask(taskId) {
return this.getTask(taskId);
}
};
_WanxAPI.MODEL_NAME = "Qubico/wanx";
var WanxAPI = _WanxAPI;
export { AIHugAPI, FaceSwapAPI, HailuoAPI, KlingAPI, KlingEffectsAPI, KlingTryonAPI, LumaAPI, PIAPI, WanxAPI };
//# sourceMappingURL=index.mjs.map
//# sourceMappingURL=index.mjs.map