Skip to content

Commit 74bfe58

Browse files
committed
kling
1 parent beae74b commit 74bfe58

8 files changed

Lines changed: 614 additions & 239 deletions

File tree

app/pages/Kling.tsx

Lines changed: 350 additions & 144 deletions
Large diffs are not rendered by default.

app/providers/interfaces.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -320,9 +320,9 @@ export interface CallApiFunction<ApiTypes extends CommonApiTypes = CommonApiType
320320
(ApiTypes[K]["endpoint_params"] extends never
321321
? { endpoint_params?: never }
322322
: { endpoint_params: ApiTypes[K]["endpoint_params"] }) & {
323-
signal?: AbortSignal;
323+
signal: AbortSignal | undefined;
324324
},
325-
): Promise<ApiTypes[K]["res"]>;
325+
): Promise<{ resData: ApiTypes[K]["res"]; metaData: any }>;
326326
}
327327

328328
/**

app/providers/kling/index.ts

Lines changed: 176 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ import {
99
ProviderName,
1010
ProviderWebsite,
1111
} from "@/app/providers/interfaces";
12-
import { ApiRequestConfig, makeApiRequest, replaceEndpointParams } from "@/app/utils/fetch";
12+
import { ApiRequestConfig, makeApiRequest, MakeApiRequestResult, replaceEndpointParams } from "@/app/utils/fetch";
1313

14+
/**
15+
* 文生视频任务请求接口
16+
*/
1417
interface GenerateText2VideoTaskRequest {
1518
/**
1619
* 生成视频的画面纵横比,可选,枚举值:16:9, 9:16, 1:1
@@ -80,6 +83,9 @@ interface GenerateText2VideoTaskRequest {
8083
prompt: string;
8184
}
8285

86+
/**
87+
* 文生视频任务响应接口
88+
*/
8389
interface GenerateText2VideoTaskResponse {
8490
/**
8591
* 错误码;具体定义错误码
@@ -114,63 +120,6 @@ interface GenerateText2VideoTaskResponse {
114120
request_id: string;
115121
}
116122

117-
interface QueryTaskResponse {
118-
/**
119-
* 错误码;具体定义见错误码
120-
*/
121-
code: number;
122-
/**
123-
* 错误信息
124-
*/
125-
message: string;
126-
/**
127-
* 请求ID,系统生成,用于跟踪请求、排查问题
128-
*/
129-
request_id: string;
130-
data: {
131-
/**
132-
* 任务ID,系统生成
133-
*/
134-
task_id: string;
135-
/**
136-
* 任务状态,枚举值:submitted(已提交)、processing(处理中)、succeed(成功)、failed(失败)
137-
*/
138-
task_status: "submitted" | "processing" | "succeed" | "failed";
139-
/**
140-
* 任务状态信息,当任务失败时展示失败原因(如触发平台的内容风控等)
141-
*/
142-
task_status_msg: string;
143-
/**
144-
* 任务创建时间,Unix时间戳、单位ms
145-
*/
146-
created_at: number;
147-
/**
148-
* 任务更新时间,Unix时间戳、单位ms
149-
*/
150-
updated_at: number;
151-
task_result: {
152-
videos: Array<{
153-
/**
154-
* 生成的视频ID;全局唯一
155-
*/
156-
id: string;
157-
/**
158-
* 生成视频的URL
159-
*/
160-
url: string;
161-
/**
162-
* 视频总时长,单位s
163-
*/
164-
duration: string;
165-
}>;
166-
/**
167-
* 官方文档没有说明,但是实际返回有这个字段
168-
*/
169-
images: any;
170-
};
171-
};
172-
}
173-
174123
/**
175124
* 图生视频任务请求接口
176125
*/
@@ -240,12 +189,82 @@ interface GenerateImage2VideoTaskRequest {
240189
callback_url?: string;
241190
}
242191

192+
/**
193+
* 图生视频任务响应接口
194+
*/
195+
type GenerateImage2VideoTaskResponse = GenerateText2VideoTaskResponse;
196+
197+
interface QueryTaskResponse {
198+
/**
199+
* 错误码;具体定义见错误码
200+
*/
201+
code: number;
202+
/**
203+
* 错误信息
204+
*/
205+
message: string;
206+
/**
207+
* 请求ID,系统生成,用于跟踪请求、排查问题
208+
*/
209+
request_id: string;
210+
data: {
211+
/**
212+
* 任务ID,系统生成
213+
*/
214+
task_id: string;
215+
/**
216+
* 任务状态,枚举值:submitted(已提交)、processing(处理中)、succeed(成功)、failed(失败)
217+
*/
218+
task_status: "submitted" | "processing" | "succeed" | "failed";
219+
/**
220+
* 任务状态信息,当任务失败时展示失败原因(如触发平台的内容风控等)
221+
*/
222+
task_status_msg: string;
223+
/**
224+
* 任务创建时间,Unix时间戳、单位ms
225+
*/
226+
created_at: number;
227+
/**
228+
* 任务更新时间,Unix时间戳、单位ms
229+
*/
230+
updated_at: number;
231+
task_result: {
232+
videos: Array<{
233+
/**
234+
* 生成的视频ID;全局唯一
235+
*/
236+
id: string;
237+
/**
238+
* 生成视频的URL
239+
*/
240+
url: string;
241+
/**
242+
* 视频总时长,单位s
243+
*/
244+
duration: string;
245+
}>;
246+
/**
247+
* 官方文档没有说明,但是实际返回有这个字段
248+
*/
249+
images: any;
250+
};
251+
};
252+
}
253+
254+
/**
255+
* 可灵 API 类型
256+
*/
243257
export interface KlingApiTypes extends CommonApiTypes {
244258
generateText2VideoTask: {
245259
req: GenerateText2VideoTaskRequest;
246260
res: GenerateText2VideoTaskResponse;
247261
endpoint_params: never;
248262
};
263+
generateImage2VideoTask: {
264+
req: GenerateImage2VideoTaskRequest;
265+
res: GenerateImage2VideoTaskResponse;
266+
endpoint_params: never;
267+
};
249268
queryTask: {
250269
req: never;
251270
res: QueryTaskResponse;
@@ -255,11 +274,23 @@ export interface KlingApiTypes extends CommonApiTypes {
255274
task_id: string;
256275
};
257276
};
258-
generateImage2VideoTask: {
259-
req: GenerateImage2VideoTaskRequest;
260-
res: any;
261-
endpoint_params: never;
262-
};
277+
}
278+
279+
/**
280+
* 本地储存的任务
281+
*/
282+
export interface KlingTask {
283+
// 本地任务唯一标识
284+
id: string;
285+
// 首次 fetch 信息
286+
original_fetch_info: MakeApiRequestResult["metaData"];
287+
// 最新 fetch 信息
288+
latest_fetch_info: MakeApiRequestResult<KlingApiTypes["queryTask"]["res"]>["metaData"];
289+
// 最新的任务信息
290+
latest_task_info:
291+
| KlingApiTypes["generateText2VideoTask"]["res"]["data"]
292+
| KlingApiTypes["generateImage2VideoTask"]["res"]["data"]
293+
| KlingApiTypes["queryTask"]["res"]["data"];
263294
}
264295

265296
export class Kling implements AIProvider {
@@ -382,11 +413,22 @@ export class Kling implements AIProvider {
382413
endpoint: "kling/v1/videos/image2video",
383414
req_example: {
384415
model: "kling-v1",
385-
image: "base64_encoded_image_or_url",
386-
prompt: "A cat driving a car through a busy city street",
387-
cfg_scale: 0.5,
388416
mode: "std",
389417
duration: "5",
418+
image: "", // base64
419+
prompt: "猫开车",
420+
cfg_scale: 0.5,
421+
},
422+
res_example: {
423+
code: 0,
424+
message: "SUCCEED",
425+
request_id: "ClogHGb1P5IAAAAAAAcRVA",
426+
data: {
427+
task_id: "ClogHGb1P5IAAAAAAAcRVA",
428+
task_status: "submitted",
429+
created_at: 1727362910941,
430+
updated_at: 1727362910941,
431+
},
390432
},
391433
},
392434
},
@@ -396,46 +438,98 @@ export class Kling implements AIProvider {
396438
this.api_config.authorization = apiKey;
397439
}
398440

399-
callApi: CallApiFunction<KlingApiTypes> = ({ callKey, params, endpoint_params, signal }) => {
441+
callApi: CallApiFunction<KlingApiTypes> = async ({ callKey, params, endpoint_params, signal }) => {
400442
const headers: HeadersInit = {
401443
Authorization: `Bearer ${this.api_config.authorization}`,
402444
};
403445

404446
switch (callKey) {
405447
case "generateText2VideoTask":
406-
const opt: ApiRequestConfig = {
448+
return await makeApiRequest({
407449
endpoint: this.api_config.call_map[callKey].endpoint,
408450
options: {
409451
method: this.api_config.call_map[callKey].method,
410452
headers,
411-
signal,
412453
body: params,
454+
signal,
413455
},
414-
};
415-
return makeApiRequest(opt);
416-
case "queryTask":
417-
const opt2: ApiRequestConfig = {
418-
endpoint: replaceEndpointParams(endpoint_params, this.api_config.call_map[callKey].endpoint),
456+
});
457+
case "generateImage2VideoTask":
458+
return await makeApiRequest({
459+
endpoint: this.api_config.call_map[callKey].endpoint,
419460
options: {
420461
method: this.api_config.call_map[callKey].method,
421462
headers,
463+
body: params,
422464
signal,
423465
},
424-
};
425-
return makeApiRequest(opt2);
426-
case "generateImage2VideoTask":
427-
const opt3: ApiRequestConfig = {
428-
endpoint: this.api_config.call_map[callKey].endpoint,
466+
});
467+
case "queryTask":
468+
return await makeApiRequest({
469+
endpoint: replaceEndpointParams(endpoint_params, this.api_config.call_map[callKey].endpoint),
429470
options: {
430471
method: this.api_config.call_map[callKey].method,
431472
headers,
432473
signal,
433-
body: params,
434474
},
435-
};
436-
return makeApiRequest(opt3);
475+
});
437476
default:
438477
throw new Error(`Unknown API call key: ${callKey}`);
439478
}
440479
};
480+
481+
/**
482+
* 更新任务
483+
* @param task 任务
484+
* @param originalTask 原始任务,如果传入,则更新任务信息,否则创建新任务
485+
*/
486+
updateTask(
487+
res: MakeApiRequestResult<
488+
| KlingApiTypes["generateText2VideoTask"]["res"]
489+
| KlingApiTypes["generateImage2VideoTask"]["res"]
490+
| KlingApiTypes["queryTask"]["res"]
491+
>,
492+
originalTask?: KlingTask,
493+
): KlingTask {
494+
return {
495+
id: originalTask?.id || crypto.randomUUID(),
496+
original_fetch_info: originalTask?.original_fetch_info || res?.metaData,
497+
latest_fetch_info: res?.metaData,
498+
latest_task_info: res?.resData?.data,
499+
};
500+
}
501+
502+
/**
503+
* 轮询所有未完成的任务
504+
* @param tasks 任务列表
505+
* @param onUpdate 更新任务的回调函数,传给组件渲染
506+
*/
507+
async pollTasks(tasks: KlingTask[], onUpdate: (task: KlingTask) => void) {
508+
const unfinishedTasks = tasks.filter(
509+
(t) => t?.latest_task_info?.task_status === "submitted" || t?.latest_task_info?.task_status === "processing",
510+
);
511+
console.log("[Polling Kling tasks]", `total: ${tasks?.length}`, `unfinished: ${unfinishedTasks?.length}`);
512+
unfinishedTasks?.forEach(async (task, index) => {
513+
console.log(`poll unfinished task ${index + 1}/${unfinishedTasks?.length}`);
514+
const taskId = task?.latest_task_info?.task_id;
515+
if (taskId) {
516+
const res = await this.callApi({
517+
callKey: "queryTask",
518+
endpoint_params: {
519+
action: "videos",
520+
action2: "text2video",
521+
task_id: taskId,
522+
},
523+
signal: undefined,
524+
});
525+
if (res.metaData.response.status === 200) {
526+
onUpdate(this.updateTask(res, task));
527+
} else {
528+
console.error("Failed to query task:", res.metaData.response.status, res.metaData.response.statusText);
529+
}
530+
} else {
531+
console.error("Task ID is undefined");
532+
}
533+
});
534+
}
441535
}

0 commit comments

Comments
 (0)