UNPKG

@piapi/sdk

Version:

SDK for PIAPI

530 lines (521 loc) 16.7 kB
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