runapi-kling 0.2.12 → 0.2.13

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 60b66152ad919ac0d68333e322d2020f9b6b1494b1595d39cc40bdd3bdde4f16
4
- data.tar.gz: a599aa79714ccb068cb43ebedcb3092ed00435185941f4b20a853fdacf9a4524
3
+ metadata.gz: 3ee363d9aca47d12b9532fe37133181c95bc914e233c127ce86dc7ef9cde3000
4
+ data.tar.gz: 06e2eea2612c5d2a6eaba6264a7c050d2d61b9cb80c26cfea581139a17baed20
5
5
  SHA512:
6
- metadata.gz: 0e5605502283c0488cbfcf1a521b14a4cc172d389cbc452fa0c4169afd2ce8fd2e147a0fd5230cac020fd43a91393d1fe1bc578da1238ae3c1425b6a28bb63b6
7
- data.tar.gz: 7728476306ee7d0ab8d0877275093548347f60d6c08c831fab3e3f809643e5da95951b5561c0abbafa0ba4b8262ed6e38174f7dd838c845fde2fd52f22889afa
6
+ metadata.gz: 6bc711db4985a209433276045dfb1174b4295f7256075287da212e98f4f443e8b2670a99f3344410b6441d507cd10465f691b46b97b129b9b22066a7d5a1a085
7
+ data.tar.gz: b5e93c8807bacc42d3988ee2e1146011733ec0f0d5f9eda7d9fff97b7ad81ed72d6db39f7ee2ba1b2e334c69044026efde4c65fe2f7d250ad37762cb6d6552df
data/README.md CHANGED
@@ -22,6 +22,23 @@ task = client.text_to_video.create(
22
22
  status = client.text_to_video.get(task.id)
23
23
  ```
24
24
 
25
+ ## Kling O1 reference media
26
+
27
+ ```ruby
28
+ result = client.text_to_video.run(
29
+ model: "kling-o1",
30
+ prompt: "Keep <<<image_1>>> beside the performer from <<<video_1>>>",
31
+ reference_image_urls: ["https://cdn.runapi.ai/public/samples/portrait.jpg"],
32
+ reference_video_url: "https://cdn.runapi.ai/public/samples/video.mp4",
33
+ reference_video_type: "feature",
34
+ preserve_reference_video_audio: true,
35
+ mode: "pro",
36
+ duration_seconds: 5
37
+ )
38
+ ```
39
+
40
+ Number reference images in prompt order as `<<<image_1>>>`, `<<<image_2>>>`, and so on; the optional video is `<<<video_1>>>`. With a video, send at most four images. Do not combine `last_frame_image_url` with reference images or a reference video. A `feature` reference video may be used with the required first frame; `base` cannot be combined with frame inputs. O1 requests are five seconds and keep sound disabled. Pricing and limits: https://runapi.ai/models/kling/o1.
41
+
25
42
  Use `create` when you want to submit a task and return quickly, `get` when you need the latest task state, and `run` when a script should create and poll until completion. In web request handlers, prefer `create` plus webhook or later `get` polling so a worker is not held open.
26
43
 
27
44
  RunAPI-generated file URLs are temporary. Download and store generated images, videos, audio, or other files in your own durable storage within 7 days; do not treat returned URLs as long-term assets.
@@ -86,8 +86,39 @@ module RunApi
86
86
  }
87
87
  },
88
88
  "image-to-video" => {
89
- "models" => ["kling-v2.1-master-image-to-video", "kling-v2.1-pro", "kling-v2.1-standard", "kling-v2.5-turbo-image-to-video-pro", "kling-v2.6", "kling-v3-omni", "kling-v3-turbo-image-to-video"],
89
+ "models" => ["kling-o1", "kling-v2.1-master-image-to-video", "kling-v2.1-pro", "kling-v2.1-standard", "kling-v2.5-turbo-image-to-video-pro", "kling-v2.6", "kling-v3-omni", "kling-v3-turbo-image-to-video"],
90
90
  "fields_by_model" => {
91
+ "kling-o1" => {
92
+ "aspect_ratio" => {
93
+ "enum" => ["16:9", "9:16", "1:1"]
94
+ },
95
+ "duration_seconds" => {
96
+ "enum" => [5],
97
+ "type" => "integer"
98
+ },
99
+ "first_frame_image_url" => {
100
+ "required" => true
101
+ },
102
+ "mode" => {
103
+ "enum" => ["std", "pro"]
104
+ },
105
+ "model" => {
106
+ "required" => true
107
+ },
108
+ "prompt" => {
109
+ "required" => true,
110
+ "min" => 1,
111
+ "max" => 2500,
112
+ "length" => true
113
+ },
114
+ "reference_image_urls" => {
115
+ "min_items" => 1,
116
+ "max_items" => 7
117
+ },
118
+ "reference_video_type" => {
119
+ "enum" => ["base", "feature"]
120
+ }
121
+ },
91
122
  "kling-v2.1-master-image-to-video" => {
92
123
  "duration_seconds" => {
93
124
  "enum" => [5, 10],
@@ -219,40 +250,45 @@ module RunApi
219
250
  }
220
251
  },
221
252
  "rules" => [{
253
+ "when" => {
254
+ "model" => "kling-o1"
255
+ },
256
+ "forbidden" => ["output_resolution", "negative_prompt", "cfg_scale"]
257
+ }, {
222
258
  "when" => {
223
259
  "model" => "kling-v2.1-master-image-to-video"
224
260
  },
225
- "forbidden" => ["output_resolution", "enable_sound"]
261
+ "forbidden" => ["output_resolution", "enable_sound", "reference_image_urls", "reference_video_url", "reference_video_type", "preserve_reference_video_audio"]
226
262
  }, {
227
263
  "when" => {
228
264
  "model" => "kling-v2.1-pro"
229
265
  },
230
- "forbidden" => ["output_resolution", "enable_sound"]
266
+ "forbidden" => ["output_resolution", "enable_sound", "reference_image_urls", "reference_video_url", "reference_video_type", "preserve_reference_video_audio"]
231
267
  }, {
232
268
  "when" => {
233
269
  "model" => "kling-v2.1-standard"
234
270
  },
235
- "forbidden" => ["output_resolution", "enable_sound"]
271
+ "forbidden" => ["output_resolution", "enable_sound", "reference_image_urls", "reference_video_url", "reference_video_type", "preserve_reference_video_audio"]
236
272
  }, {
237
273
  "when" => {
238
274
  "model" => "kling-v2.5-turbo-image-to-video-pro"
239
275
  },
240
- "forbidden" => ["output_resolution", "enable_sound"]
276
+ "forbidden" => ["output_resolution", "enable_sound", "reference_image_urls", "reference_video_url", "reference_video_type", "preserve_reference_video_audio"]
241
277
  }, {
242
278
  "when" => {
243
279
  "model" => "kling-v2.6"
244
280
  },
245
- "forbidden" => ["output_resolution", "negative_prompt", "cfg_scale"]
281
+ "forbidden" => ["output_resolution", "negative_prompt", "cfg_scale", "reference_image_urls", "reference_video_url", "reference_video_type", "preserve_reference_video_audio"]
246
282
  }, {
247
283
  "when" => {
248
284
  "model" => "kling-v3-omni"
249
285
  },
250
- "forbidden" => ["negative_prompt", "cfg_scale"]
286
+ "forbidden" => ["negative_prompt", "cfg_scale", "reference_image_urls", "reference_video_url", "reference_video_type", "preserve_reference_video_audio"]
251
287
  }, {
252
288
  "when" => {
253
289
  "model" => "kling-v3-turbo-image-to-video"
254
290
  },
255
- "forbidden" => ["enable_sound", "aspect_ratio", "negative_prompt", "cfg_scale", "last_frame_image_url"]
291
+ "forbidden" => ["enable_sound", "aspect_ratio", "negative_prompt", "cfg_scale", "last_frame_image_url", "reference_image_urls", "reference_video_url", "reference_video_type", "preserve_reference_video_audio"]
256
292
  }]
257
293
  },
258
294
  "motion-control" => {
@@ -310,7 +346,7 @@ module RunApi
310
346
  }]
311
347
  },
312
348
  "text-to-video" => {
313
- "models" => ["kling-3.0", "kling-v2.1-master-text-to-video", "kling-v2.5-turbo-text-to-video-pro", "kling-v2.6", "kling-v3-omni", "kling-v3-turbo-text-to-video"],
349
+ "models" => ["kling-3.0", "kling-o1", "kling-v2.1-master-text-to-video", "kling-v2.5-turbo-text-to-video-pro", "kling-v2.6", "kling-v3-omni", "kling-v3-turbo-text-to-video"],
314
350
  "fields_by_model" => {
315
351
  "kling-3.0" => {
316
352
  "aspect_ratio" => {
@@ -327,6 +363,34 @@ module RunApi
327
363
  "enum" => ["720p", "1080p", "4k"]
328
364
  }
329
365
  },
366
+ "kling-o1" => {
367
+ "aspect_ratio" => {
368
+ "enum" => ["16:9", "9:16", "1:1"]
369
+ },
370
+ "duration_seconds" => {
371
+ "enum" => [5],
372
+ "type" => "integer"
373
+ },
374
+ "mode" => {
375
+ "enum" => ["std", "pro"]
376
+ },
377
+ "model" => {
378
+ "required" => true
379
+ },
380
+ "prompt" => {
381
+ "required" => true,
382
+ "min" => 1,
383
+ "max" => 2500,
384
+ "length" => true
385
+ },
386
+ "reference_image_urls" => {
387
+ "min_items" => 1,
388
+ "max_items" => 7
389
+ },
390
+ "reference_video_type" => {
391
+ "enum" => ["base", "feature"]
392
+ }
393
+ },
330
394
  "kling-v2.1-master-text-to-video" => {
331
395
  "aspect_ratio" => {
332
396
  "enum" => ["16:9", "9:16", "1:1"]
@@ -416,25 +480,40 @@ module RunApi
416
480
  }
417
481
  },
418
482
  "rules" => [{
483
+ "when" => {
484
+ "model" => "kling-3.0"
485
+ },
486
+ "forbidden" => ["reference_image_urls", "reference_video_url", "reference_video_type", "preserve_reference_video_audio"]
487
+ }, {
488
+ "when" => {
489
+ "model" => "kling-o1"
490
+ },
491
+ "forbidden" => ["output_resolution", "negative_prompt", "cfg_scale", "multi_shots", "multi_prompt", "first_frame_image_url", "last_frame_image_url", "kling_elements"]
492
+ }, {
493
+ "when" => {
494
+ "model" => "kling-v2.1-master-text-to-video"
495
+ },
496
+ "forbidden" => ["mode", "reference_image_urls", "reference_video_url", "reference_video_type", "preserve_reference_video_audio"]
497
+ }, {
419
498
  "when" => {
420
499
  "model" => "kling-v2.5-turbo-text-to-video-pro"
421
500
  },
422
- "forbidden" => ["mode"]
501
+ "forbidden" => ["mode", "reference_image_urls", "reference_video_url", "reference_video_type", "preserve_reference_video_audio"]
423
502
  }, {
424
503
  "when" => {
425
504
  "model" => "kling-v2.6"
426
505
  },
427
- "forbidden" => ["output_resolution", "negative_prompt", "cfg_scale", "multi_shots", "multi_prompt", "first_frame_image_url", "last_frame_image_url", "kling_elements"]
506
+ "forbidden" => ["output_resolution", "negative_prompt", "cfg_scale", "multi_shots", "multi_prompt", "first_frame_image_url", "last_frame_image_url", "kling_elements", "reference_image_urls", "reference_video_url", "reference_video_type", "preserve_reference_video_audio"]
428
507
  }, {
429
508
  "when" => {
430
509
  "model" => "kling-v3-omni"
431
510
  },
432
- "forbidden" => ["negative_prompt", "cfg_scale", "multi_shots", "multi_prompt", "first_frame_image_url", "last_frame_image_url", "kling_elements"]
511
+ "forbidden" => ["negative_prompt", "cfg_scale", "multi_shots", "multi_prompt", "first_frame_image_url", "last_frame_image_url", "kling_elements", "reference_image_urls", "reference_video_url", "reference_video_type", "preserve_reference_video_audio"]
433
512
  }, {
434
513
  "when" => {
435
514
  "model" => "kling-v3-turbo-text-to-video"
436
515
  },
437
- "forbidden" => ["enable_sound", "negative_prompt", "cfg_scale", "multi_shots", "multi_prompt", "first_frame_image_url", "last_frame_image_url", "kling_elements"]
516
+ "forbidden" => ["enable_sound", "negative_prompt", "cfg_scale", "multi_shots", "multi_prompt", "first_frame_image_url", "last_frame_image_url", "kling_elements", "reference_image_urls", "reference_video_url", "reference_video_type", "preserve_reference_video_audio"]
438
517
  }]
439
518
  }
440
519
  }.freeze
@@ -7,6 +7,7 @@ module RunApi
7
7
  # Generate videos from an input image.
8
8
  class ImageToVideo
9
9
  include RunApi::Core::ResourceHelpers
10
+ include O1ReferenceValidation
10
11
 
11
12
  ENDPOINT = "/api/v1/kling/image_to_video"
12
13
 
@@ -58,6 +59,7 @@ module RunApi
58
59
  def validate_params!(params)
59
60
  reject_unsupported_v3_turbo_fields!(params)
60
61
  validate_contract!(CONTRACT["image-to-video"], params)
62
+ validate_kling_o1_references!(params)
61
63
 
62
64
  # Bespoke last-frame rules that the generated contract cannot express.
63
65
  model = param(params, :model)
@@ -69,7 +71,7 @@ module RunApi
69
71
  return if duration_seconds.to_i == 5
70
72
 
71
73
  raise Core::ValidationError, "last_frame_image_url requires duration_seconds 5 for #{V3_OMNI_MODEL}"
72
- elsif last_frame_image_url && !%w[kling-v2.5-turbo-image-to-video-pro kling-v2.1-pro].include?(model)
74
+ elsif last_frame_image_url && !%w[kling-o1 kling-v2.5-turbo-image-to-video-pro kling-v2.1-pro].include?(model)
73
75
  raise Core::ValidationError, "last_frame_image_url is only supported by kling-v2.5-turbo-image-to-video-pro and kling-v2.1-pro"
74
76
  end
75
77
  end
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ipaddr"
4
+ require "uri"
5
+
6
+ module RunApi
7
+ module Kling
8
+ module Resources
9
+ module O1ReferenceValidation
10
+ O1_MODEL = "kling-o1"
11
+ O1_IMAGE_EXTENSIONS = %w[.jpg .jpeg .png].freeze
12
+ O1_VIDEO_EXTENSIONS = %w[.mp4 .mov].freeze
13
+ O1_NONCANONICAL_IPV4_LITERAL = /\A(?:0x[0-9a-f]+|\d+)(?:\.(?:0x[0-9a-f]+|\d+)){0,3}\z/i
14
+ O1_BLOCKED_IP_RANGES = %w[
15
+ 0.0.0.0/8
16
+ 10.0.0.0/8
17
+ 100.64.0.0/10
18
+ 127.0.0.0/8
19
+ 169.254.0.0/16
20
+ 172.16.0.0/12
21
+ 192.0.0.0/24
22
+ 192.0.2.0/24
23
+ 192.88.99.0/24
24
+ 192.168.0.0/16
25
+ 198.18.0.0/15
26
+ 198.51.100.0/24
27
+ 203.0.113.0/24
28
+ 224.0.0.0/4
29
+ 240.0.0.0/4
30
+ 255.255.255.255/32
31
+ ::/128
32
+ ::1/128
33
+ 64:ff9b::/96
34
+ 64:ff9b:1::/48
35
+ 100::/64
36
+ 2001::/32
37
+ 2001:2::/48
38
+ 2001:db8::/32
39
+ 2002::/16
40
+ 3fff::/20
41
+ fc00::/7
42
+ fe80::/10
43
+ ff00::/8
44
+ ].map { |cidr| IPAddr.new(cidr) }.freeze
45
+
46
+ private
47
+
48
+ def validate_kling_o1_references!(params)
49
+ return unless param(params, :model) == O1_MODEL
50
+
51
+ if param(params, :enable_sound) == true
52
+ raise Core::ValidationError, "enable_sound is not supported by #{O1_MODEL}"
53
+ end
54
+
55
+ validate_o1_frame_urls!(params)
56
+ prompt = param(params, :prompt)
57
+ raise Core::ValidationError, "prompt must be a string" unless prompt.is_a?(String)
58
+ reference_images = Array(param(params, :reference_image_urls))
59
+ reference_video_url = param(params, :reference_video_url)
60
+
61
+ if field_present?(params, :last_frame_image_url) &&
62
+ (reference_images.any? || field_present?(params, :reference_video_url))
63
+ raise Core::ValidationError,
64
+ "last_frame_image_url cannot be combined with reference_image_urls or reference_video_url"
65
+ end
66
+
67
+ if reference_video_url && reference_images.size > 4
68
+ raise Core::ValidationError, "reference_image_urls must contain at most 4 items when reference_video_url is present"
69
+ end
70
+
71
+ validate_o1_reference_images!(prompt, reference_images)
72
+ validate_o1_reference_video!(params, prompt, reference_video_url)
73
+ end
74
+
75
+ def validate_o1_frame_urls!(params)
76
+ %i[first_frame_image_url last_frame_image_url].each do |field|
77
+ value = param(params, field)
78
+ next unless value
79
+ validate_o1_media_url!(
80
+ value,
81
+ field: field,
82
+ extensions: O1_IMAGE_EXTENSIONS,
83
+ extension_error: "#{field} must use a JPG, JPEG, or PNG URL"
84
+ )
85
+ end
86
+ end
87
+
88
+ def validate_o1_reference_images!(prompt, reference_images)
89
+ reference_images.each_with_index do |url, index|
90
+ field = "reference_image_urls[#{index}]"
91
+ raise Core::ValidationError, "#{field} must be a string" unless url.is_a?(String)
92
+ validate_o1_media_url!(
93
+ url,
94
+ field: field,
95
+ extensions: O1_IMAGE_EXTENSIONS,
96
+ extension_error: "#{field} must use a JPG, JPEG, or PNG URL"
97
+ )
98
+
99
+ marker = "<<<image_#{index + 1}>>>"
100
+ unless prompt.include?(marker)
101
+ raise Core::ValidationError, "prompt must reference reference_image_urls[#{index}] as #{marker}"
102
+ end
103
+ end
104
+
105
+ prompt.scan(/<<<image_(\d+)>>>/).flatten.map(&:to_i).each do |index|
106
+ next if index.between?(1, reference_images.size)
107
+
108
+ raise Core::ValidationError, "prompt references missing image_#{index}"
109
+ end
110
+ end
111
+
112
+ def validate_o1_reference_video!(params, prompt, reference_video_url)
113
+ unless reference_video_url
114
+ raise Core::ValidationError, "reference_video_type requires reference_video_url" if o1_key?(params, :reference_video_type)
115
+ if o1_key?(params, :preserve_reference_video_audio)
116
+ raise Core::ValidationError, "preserve_reference_video_audio requires reference_video_url"
117
+ end
118
+ if (match = prompt.match(/<<<video_([^>]+)>>>/))
119
+ raise Core::ValidationError, "prompt references missing video_#{match[1]}"
120
+ end
121
+ return
122
+ end
123
+
124
+ validate_o1_media_url!(
125
+ reference_video_url,
126
+ field: :reference_video_url,
127
+ extensions: O1_VIDEO_EXTENSIONS,
128
+ extension_error: "reference_video_url must use an MP4 or MOV URL"
129
+ )
130
+ unless prompt.include?("<<<video_1>>>")
131
+ raise Core::ValidationError, "prompt must reference reference_video_url as <<<video_1>>>"
132
+ end
133
+ if prompt.scan(/<<<video_([^>]+)>>>/).flatten.any? { |index| index != "1" }
134
+ raise Core::ValidationError, "prompt may only reference video_1"
135
+ end
136
+
137
+ reference_video_type = param(params, :reference_video_type) || "base"
138
+ return unless reference_video_type == "base"
139
+ return unless field_present?(params, :first_frame_image_url) || field_present?(params, :last_frame_image_url)
140
+
141
+ raise Core::ValidationError,
142
+ "reference_video_type base cannot be combined with first_frame_image_url or last_frame_image_url"
143
+ end
144
+
145
+ def o1_key?(params, field)
146
+ params.key?(field) || params.key?(field.to_s)
147
+ end
148
+
149
+ def validate_o1_media_url!(value, field:, extensions:, extension_error:)
150
+ raise Core::ValidationError, "#{field} must be a string" unless value.is_a?(String)
151
+
152
+ uri = URI.parse(value.to_s)
153
+ host = uri.hostname
154
+ local_host = host&.casecmp?("localhost") || host&.downcase&.end_with?(".localhost")
155
+ valid_http_url = (uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)) &&
156
+ host && !uri.userinfo && !local_host && !blocked_o1_ip_literal?(host)
157
+ unless valid_http_url
158
+ raise Core::ValidationError, "#{field} must be a public HTTP or HTTPS URL"
159
+ end
160
+ return if extensions.include?(File.extname(uri.path).downcase)
161
+
162
+ raise Core::ValidationError, extension_error
163
+ rescue URI::InvalidURIError
164
+ raise Core::ValidationError, "#{field} must be a public HTTP or HTTPS URL"
165
+ end
166
+
167
+ def blocked_o1_ip_literal?(host)
168
+ ip = IPAddr.new(host).native
169
+ O1_BLOCKED_IP_RANGES.any? { |range| range.include?(ip) }
170
+ rescue IPAddr::InvalidAddressError
171
+ O1_NONCANONICAL_IPV4_LITERAL.match?(host)
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
@@ -7,6 +7,7 @@ module RunApi
7
7
  # Generate videos from text prompts.
8
8
  class TextToVideo
9
9
  include RunApi::Core::ResourceHelpers
10
+ include O1ReferenceValidation
10
11
 
11
12
  ENDPOINT = "/api/v1/kling/text_to_video"
12
13
 
@@ -62,6 +63,7 @@ module RunApi
62
63
  reject_unsupported_v3_turbo_fields!(params)
63
64
  validate_contract!(CONTRACT["text-to-video"], params)
64
65
  validate_v26_params!(params)
66
+ validate_kling_o1_references!(params)
65
67
 
66
68
  # Bespoke cross-field rules the contract cannot express.
67
69
  multi_shots = param(params, :multi_shots) == true
data/lib/runapi/kling.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require "runapi/core"
4
4
  require_relative "kling/types"
5
5
  require_relative "kling/contract_gen"
6
+ require_relative "kling/resources/o1_reference_validation"
6
7
  require_relative "kling/resources/text_to_video"
7
8
  require_relative "kling/resources/ai_avatar"
8
9
  require_relative "kling/resources/image_to_video"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: runapi-kling
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.12
4
+ version: 0.2.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - RunAPI
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.2.15
18
+ version: 0.3.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.2.15
25
+ version: 0.3.0
26
26
  description: The Kling Ruby SDK is the language-specific package for Kling on RunAPI.
27
27
  Use this package for video generation, animation, and video editing workflows when
28
28
  your application needs request bodies, task status lookup, and consistent RunAPI
@@ -44,6 +44,7 @@ files:
44
44
  - lib/runapi/kling/resources/extend_video.rb
45
45
  - lib/runapi/kling/resources/image_to_video.rb
46
46
  - lib/runapi/kling/resources/motion_control.rb
47
+ - lib/runapi/kling/resources/o1_reference_validation.rb
47
48
  - lib/runapi/kling/resources/text_to_video.rb
48
49
  - lib/runapi/kling/types.rb
49
50
  homepage: https://runapi.ai/models/kling