@@ -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+ */
1417interface GenerateText2VideoTaskRequest {
1518 /**
1619 * 生成视频的画面纵横比,可选,枚举值:16:9, 9:16, 1:1
@@ -80,6 +83,9 @@ interface GenerateText2VideoTaskRequest {
8083 prompt : string ;
8184}
8285
86+ /**
87+ * 文生视频任务响应接口
88+ */
8389interface 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+ */
243257export 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
265296export 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