5
5
namespace WordPress \AiClient \Results \DTO ;
6
6
7
7
use WordPress \AiClient \Files \Contracts \FileInterface ;
8
+ use WordPress \AiClient \Files \Utilities \MimeTypeUtil ;
8
9
use WordPress \AiClient \Messages \DTO \Message ;
9
10
use WordPress \AiClient \Messages \Enums \MessagePartTypeEnum ;
10
11
use WordPress \AiClient \Results \Contracts \ResultInterface ;
@@ -48,9 +49,14 @@ class GenerativeAiResult implements ResultInterface
48
49
* @param Candidate[] $candidates The generated candidates.
49
50
* @param TokenUsage $tokenUsage Token usage statistics.
50
51
* @param array<string, mixed> $providerMetadata Provider-specific metadata.
52
+ * @throws \InvalidArgumentException If no candidates provided.
51
53
*/
52
54
public function __construct (string $ id , array $ candidates , TokenUsage $ tokenUsage , array $ providerMetadata = [])
53
55
{
56
+ if (empty ($ candidates )) {
57
+ throw new \InvalidArgumentException ('At least one candidate must be provided ' );
58
+ }
59
+
54
60
$ this ->id = $ id ;
55
61
$ this ->candidates = $ candidates ;
56
62
$ this ->tokenUsage = $ tokenUsage ;
@@ -99,28 +105,75 @@ public function getProviderMetadata(): array
99
105
return $ this ->providerMetadata ;
100
106
}
101
107
108
+ /**
109
+ * Gets the total number of candidates.
110
+ *
111
+ * @since n.e.x.t
112
+ *
113
+ * @return int The total number of candidates.
114
+ */
115
+ public function getTotalCandidates (): int
116
+ {
117
+ return count ($ this ->candidates );
118
+ }
119
+
120
+ /**
121
+ * Checks if the result has multiple candidates.
122
+ *
123
+ * @since n.e.x.t
124
+ *
125
+ * @return bool True if there are multiple candidates, false otherwise.
126
+ */
127
+ public function hasMultipleCandidates (): bool
128
+ {
129
+ return count ($ this ->candidates ) > 1 ;
130
+ }
131
+
102
132
/**
103
133
* Converts the first candidate to text.
104
134
*
105
135
* @since n.e.x.t
106
136
*
107
137
* @return string The text content.
108
- * @throws \RuntimeException If no candidates or no text content.
138
+ * @throws \RuntimeException If no text content.
109
139
*/
110
140
public function toText (): string
111
141
{
112
- if (empty ($ this ->candidates )) {
113
- throw new \RuntimeException ('No candidates available ' );
142
+ $ message = $ this ->candidates [0 ]->getMessage ();
143
+ foreach ($ message ->getParts () as $ part ) {
144
+ $ text = $ part ->getText ();
145
+ if ($ text !== null ) {
146
+ return $ text ;
147
+ }
114
148
}
115
149
150
+ throw new \RuntimeException ('No text content found in first candidate ' );
151
+ }
152
+
153
+ /**
154
+ * Converts the first candidate to a file.
155
+ *
156
+ * @since n.e.x.t
157
+ *
158
+ * @return FileInterface The file.
159
+ * @throws \RuntimeException If no file content.
160
+ */
161
+ public function toFile (): FileInterface
162
+ {
116
163
$ message = $ this ->candidates [0 ]->getMessage ();
117
164
foreach ($ message ->getParts () as $ part ) {
118
- if ($ part ->getType ()->equals (MessagePartTypeEnum::text ()) && $ part ->getText () !== null ) {
119
- return $ part ->getText ();
165
+ $ inlineFile = $ part ->getInlineFile ();
166
+ if ($ inlineFile !== null ) {
167
+ return $ inlineFile ;
168
+ }
169
+
170
+ $ remoteFile = $ part ->getRemoteFile ();
171
+ if ($ remoteFile !== null ) {
172
+ return $ remoteFile ;
120
173
}
121
174
}
122
175
123
- throw new \RuntimeException ('No text content found in first candidate ' );
176
+ throw new \RuntimeException ('No file content found in first candidate ' );
124
177
}
125
178
126
179
/**
@@ -129,25 +182,19 @@ public function toText(): string
129
182
* @since n.e.x.t
130
183
*
131
184
* @return FileInterface The image file.
132
- * @throws \RuntimeException If no candidates or no image content.
185
+ * @throws \RuntimeException If no image content.
133
186
*/
134
187
public function toImageFile (): FileInterface
135
188
{
136
- if (empty ($ this ->candidates )) {
137
- throw new \RuntimeException ('No candidates available ' );
138
- }
189
+ $ file = $ this ->toFile ();
139
190
140
- $ message = $ this ->candidates [0 ]->getMessage ();
141
- foreach ($ message ->getParts () as $ part ) {
142
- if ($ part ->getType ()->equals (MessagePartTypeEnum::inlineFile ()) && $ part ->getInlineFile () !== null ) {
143
- return $ part ->getInlineFile ();
144
- }
145
- if ($ part ->getType ()->equals (MessagePartTypeEnum::remoteFile ()) && $ part ->getRemoteFile () !== null ) {
146
- return $ part ->getRemoteFile ();
147
- }
191
+ if (!MimeTypeUtil::isImageType ($ file ->getMimeType ())) {
192
+ throw new \RuntimeException (
193
+ sprintf ('File is not an image. MIME type: %s ' , $ file ->getMimeType ())
194
+ );
148
195
}
149
196
150
- throw new \ RuntimeException ( ' No image content found in first candidate ' ) ;
197
+ return $ file ;
151
198
}
152
199
153
200
/**
@@ -156,12 +203,19 @@ public function toImageFile(): FileInterface
156
203
* @since n.e.x.t
157
204
*
158
205
* @return FileInterface The audio file.
159
- * @throws \RuntimeException If no candidates or no audio content.
206
+ * @throws \RuntimeException If no audio content.
160
207
*/
161
208
public function toAudioFile (): FileInterface
162
209
{
163
- // Similar implementation to toImageFile, but checking for audio MIME types
164
- return $ this ->toImageFile (); // Simplified for now
210
+ $ file = $ this ->toFile ();
211
+
212
+ if (!MimeTypeUtil::isAudioType ($ file ->getMimeType ())) {
213
+ throw new \RuntimeException (
214
+ sprintf ('File is not an audio file. MIME type: %s ' , $ file ->getMimeType ())
215
+ );
216
+ }
217
+
218
+ return $ file ;
165
219
}
166
220
167
221
/**
@@ -170,12 +224,19 @@ public function toAudioFile(): FileInterface
170
224
* @since n.e.x.t
171
225
*
172
226
* @return FileInterface The video file.
173
- * @throws \RuntimeException If no candidates or no video content.
227
+ * @throws \RuntimeException If no video content.
174
228
*/
175
229
public function toVideoFile (): FileInterface
176
230
{
177
- // Similar implementation to toImageFile, but checking for video MIME types
178
- return $ this ->toImageFile (); // Simplified for now
231
+ $ file = $ this ->toFile ();
232
+
233
+ if (!MimeTypeUtil::isVideoType ($ file ->getMimeType ())) {
234
+ throw new \RuntimeException (
235
+ sprintf ('File is not a video file. MIME type: %s ' , $ file ->getMimeType ())
236
+ );
237
+ }
238
+
239
+ return $ file ;
179
240
}
180
241
181
242
/**
@@ -184,14 +245,9 @@ public function toVideoFile(): FileInterface
184
245
* @since n.e.x.t
185
246
*
186
247
* @return Message The message.
187
- * @throws \RuntimeException If no candidates available.
188
248
*/
189
249
public function toMessage (): Message
190
250
{
191
- if (empty ($ this ->candidates )) {
192
- throw new \RuntimeException ('No candidates available ' );
193
- }
194
-
195
251
return $ this ->candidates [0 ]->getMessage ();
196
252
}
197
253
@@ -208,8 +264,9 @@ public function toTexts(): array
208
264
foreach ($ this ->candidates as $ candidate ) {
209
265
$ message = $ candidate ->getMessage ();
210
266
foreach ($ message ->getParts () as $ part ) {
211
- if ($ part ->getType ()->equals (MessagePartTypeEnum::text ()) && $ part ->getText () !== null ) {
212
- $ texts [] = $ part ->getText ();
267
+ $ text = $ part ->getText ();
268
+ if ($ text !== null ) {
269
+ $ texts [] = $ text ;
213
270
break ;
214
271
}
215
272
}
@@ -230,12 +287,15 @@ public function toImageFiles(): array
230
287
foreach ($ this ->candidates as $ candidate ) {
231
288
$ message = $ candidate ->getMessage ();
232
289
foreach ($ message ->getParts () as $ part ) {
233
- if ($ part ->getType ()->equals (MessagePartTypeEnum::inlineFile ()) && $ part ->getInlineFile () !== null ) {
234
- $ files [] = $ part ->getInlineFile ();
290
+ $ inlineFile = $ part ->getInlineFile ();
291
+ if ($ inlineFile !== null && MimeTypeUtil::isImageType ($ inlineFile ->getMimeType ())) {
292
+ $ files [] = $ inlineFile ;
235
293
break ;
236
294
}
237
- if ($ part ->getType ()->equals (MessagePartTypeEnum::remoteFile ()) && $ part ->getRemoteFile () !== null ) {
238
- $ files [] = $ part ->getRemoteFile ();
295
+
296
+ $ remoteFile = $ part ->getRemoteFile ();
297
+ if ($ remoteFile !== null && MimeTypeUtil::isImageType ($ remoteFile ->getMimeType ())) {
298
+ $ files [] = $ remoteFile ;
239
299
break ;
240
300
}
241
301
}
@@ -252,8 +312,24 @@ public function toImageFiles(): array
252
312
*/
253
313
public function toAudioFiles (): array
254
314
{
255
- // Similar implementation to toImageFiles, but checking for audio MIME types
256
- return $ this ->toImageFiles (); // Simplified for now
315
+ $ files = [];
316
+ foreach ($ this ->candidates as $ candidate ) {
317
+ $ message = $ candidate ->getMessage ();
318
+ foreach ($ message ->getParts () as $ part ) {
319
+ $ inlineFile = $ part ->getInlineFile ();
320
+ if ($ inlineFile !== null && MimeTypeUtil::isAudioType ($ inlineFile ->getMimeType ())) {
321
+ $ files [] = $ inlineFile ;
322
+ break ;
323
+ }
324
+
325
+ $ remoteFile = $ part ->getRemoteFile ();
326
+ if ($ remoteFile !== null && MimeTypeUtil::isAudioType ($ remoteFile ->getMimeType ())) {
327
+ $ files [] = $ remoteFile ;
328
+ break ;
329
+ }
330
+ }
331
+ }
332
+ return $ files ;
257
333
}
258
334
259
335
/**
@@ -265,8 +341,24 @@ public function toAudioFiles(): array
265
341
*/
266
342
public function toVideoFiles (): array
267
343
{
268
- // Similar implementation to toImageFiles, but checking for video MIME types
269
- return $ this ->toImageFiles (); // Simplified for now
344
+ $ files = [];
345
+ foreach ($ this ->candidates as $ candidate ) {
346
+ $ message = $ candidate ->getMessage ();
347
+ foreach ($ message ->getParts () as $ part ) {
348
+ $ inlineFile = $ part ->getInlineFile ();
349
+ if ($ inlineFile !== null && MimeTypeUtil::isVideoType ($ inlineFile ->getMimeType ())) {
350
+ $ files [] = $ inlineFile ;
351
+ break ;
352
+ }
353
+
354
+ $ remoteFile = $ part ->getRemoteFile ();
355
+ if ($ remoteFile !== null && MimeTypeUtil::isVideoType ($ remoteFile ->getMimeType ())) {
356
+ $ files [] = $ remoteFile ;
357
+ break ;
358
+ }
359
+ }
360
+ }
361
+ return $ files ;
270
362
}
271
363
272
364
/**
@@ -298,6 +390,7 @@ public static function getJsonSchema(): array
298
390
'candidates ' => [
299
391
'type ' => 'array ' ,
300
392
'items ' => Candidate::getJsonSchema (),
393
+ 'minItems ' => 1 ,
301
394
'description ' => 'The generated candidates. ' ,
302
395
],
303
396
'tokenUsage ' => TokenUsage::getJsonSchema (),
0 commit comments