capacitor-apple-intelligence 1.0.1 → 1.0.2
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.
- package/CapacitorAppleIntelligence.podspec +1 -1
- package/README.md +42 -1
- package/dist/esm/definitions.d.ts +51 -0
- package/dist/esm/web.d.ts +1 -0
- package/dist/esm/web.js +10 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +10 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +10 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/AppleIntelligencePlugin/AppleIntelligence.swift +415 -71
- package/ios/Sources/AppleIntelligencePlugin/AppleIntelligencePlugin.swift +126 -116
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Pod::Spec.new do |s|
|
|
2
2
|
s.name = 'CapacitorAppleIntelligence'
|
|
3
|
-
s.version = '1.0.
|
|
3
|
+
s.version = '1.0.2'
|
|
4
4
|
s.summary = 'Capacitor plugin for Apple Intelligence with schema-constrained JSON generation'
|
|
5
5
|
s.license = 'MIT'
|
|
6
6
|
s.homepage = 'https://github.com/farabiabdelwahe/capacitor-apple-intelligence'
|
package/README.md
CHANGED
|
@@ -42,6 +42,18 @@ npx cap sync
|
|
|
42
42
|
|
|
43
43
|
### Basic Example
|
|
44
44
|
|
|
45
|
+
First, check if Apple Intelligence is available:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
const availability = await AppleIntelligence.checkAvailability();
|
|
49
|
+
if (!availability.available) {
|
|
50
|
+
console.error("Apple Intelligence not available");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Then generate JSON:
|
|
56
|
+
|
|
45
57
|
```typescript
|
|
46
58
|
import { AppleIntelligence } from 'capacitor-apple-intelligence';
|
|
47
59
|
|
|
@@ -177,12 +189,40 @@ const result = await AppleIntelligence.generateTextWithLanguage({
|
|
|
177
189
|
language: "es" // or "Spanish"
|
|
178
190
|
});
|
|
179
191
|
|
|
192
|
+
if (result.success) {
|
|
193
|
+
console.log(result.content);
|
|
194
|
+
// "El atardecer pinta el cielo con tonos dorados y rosados..."
|
|
195
|
+
}
|
|
180
196
|
if (result.success) {
|
|
181
197
|
console.log(result.content);
|
|
182
198
|
// "El atardecer pinta el cielo con tonos dorados y rosados..."
|
|
183
199
|
}
|
|
184
200
|
```
|
|
185
201
|
|
|
202
|
+
### Image Generation
|
|
203
|
+
|
|
204
|
+
Generate images from text prompts:
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
const result = await AppleIntelligence.generateImage({
|
|
208
|
+
prompt: "A futuristic city with flying cars",
|
|
209
|
+
style: "animation", // Optional: "animation" (default), "illustration", "sketch"
|
|
210
|
+
count: 1, // Optional: Defaults to 1
|
|
211
|
+
sourceImage: base64String // Optional: Required for face-based generation
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
if (result.success && result.images) {
|
|
215
|
+
result.images.forEach(base64Image => {
|
|
216
|
+
// Display image
|
|
217
|
+
const img = document.createElement('img');
|
|
218
|
+
img.src = `data:image/png;base64,${base64Image}`;
|
|
219
|
+
document.body.appendChild(img);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Note**: When your prompt involves generating images of people, you **must** provide a `sourceImage` containing a clearly visible face. This is a requirement of Apple's Image Playground API. The source image should be passed as a base64-encoded string.
|
|
225
|
+
|
|
186
226
|
Supported language codes: `en`, `es`, `fr`, `de`, `ja`, `zh`, `it`, `pt`, `ru`, `ar`, `ko`
|
|
187
227
|
|
|
188
228
|
## API
|
|
@@ -211,7 +251,8 @@ if (result.available) {
|
|
|
211
251
|
}
|
|
212
252
|
```
|
|
213
253
|
|
|
214
|
-
|
|
254
|
+
|
|
255
|
+
|
|
215
256
|
|
|
216
257
|
### `generate(request)`
|
|
217
258
|
|
|
@@ -167,6 +167,13 @@ export interface AppleIntelligencePlugin {
|
|
|
167
167
|
* @returns Promise resolving to availability status
|
|
168
168
|
*/
|
|
169
169
|
checkAvailability(): Promise<AvailabilityResponse>;
|
|
170
|
+
/**
|
|
171
|
+
* Generate an image based on the provided prompt using Apple Intelligence.
|
|
172
|
+
*
|
|
173
|
+
* @param request - The generation request containing prompt and options
|
|
174
|
+
* @returns Promise resolving to an image response with success/error and base64 image data
|
|
175
|
+
*/
|
|
176
|
+
generateImage(request: GenerateImageRequest): Promise<GenerateImageResponse>;
|
|
170
177
|
}
|
|
171
178
|
/**
|
|
172
179
|
* Request payload for text generation.
|
|
@@ -221,3 +228,47 @@ export interface AvailabilityResponse {
|
|
|
221
228
|
*/
|
|
222
229
|
error?: GenerateError;
|
|
223
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Request payload for image generation.
|
|
233
|
+
*/
|
|
234
|
+
export interface GenerateImageRequest {
|
|
235
|
+
/**
|
|
236
|
+
* The prompt describing the image to generate.
|
|
237
|
+
*/
|
|
238
|
+
prompt: string;
|
|
239
|
+
/**
|
|
240
|
+
* Optional style guide for generation.
|
|
241
|
+
* Supported values: "animation" (default), "illustration", "sketch".
|
|
242
|
+
* Note: "photorealistic" is NOT supported by Apple's on-device Image Playground.
|
|
243
|
+
*/
|
|
244
|
+
style?: string;
|
|
245
|
+
/**
|
|
246
|
+
* Optional number of images to generate. Defaults to 1. Maximum 10.
|
|
247
|
+
*/
|
|
248
|
+
count?: number;
|
|
249
|
+
/**
|
|
250
|
+
* Optional base64-encoded source image for face-based generation.
|
|
251
|
+
* Required when the prompt involves generating images of people/faces.
|
|
252
|
+
* The image should contain a clearly visible person's face.
|
|
253
|
+
*/
|
|
254
|
+
sourceImage?: string;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Response for image generation.
|
|
258
|
+
*/
|
|
259
|
+
export interface GenerateImageResponse {
|
|
260
|
+
/**
|
|
261
|
+
* Whether the generation was successful.
|
|
262
|
+
*/
|
|
263
|
+
success: boolean;
|
|
264
|
+
/**
|
|
265
|
+
* Array of base64 encoded images strings.
|
|
266
|
+
* Only present when success is true.
|
|
267
|
+
*/
|
|
268
|
+
images?: string[];
|
|
269
|
+
/**
|
|
270
|
+
* Error details on failure.
|
|
271
|
+
* Only present when success is false.
|
|
272
|
+
*/
|
|
273
|
+
error?: GenerateError;
|
|
274
|
+
}
|
package/dist/esm/web.d.ts
CHANGED
|
@@ -9,4 +9,5 @@ export declare class AppleIntelligenceWeb extends WebPlugin implements AppleInte
|
|
|
9
9
|
generateText(_request: unknown): Promise<any>;
|
|
10
10
|
generateTextWithLanguage(_request: unknown): Promise<any>;
|
|
11
11
|
checkAvailability(): Promise<any>;
|
|
12
|
+
generateImage(_request: unknown): Promise<any>;
|
|
12
13
|
}
|
package/dist/esm/web.js
CHANGED
|
@@ -43,5 +43,15 @@ export class AppleIntelligenceWeb extends WebPlugin {
|
|
|
43
43
|
},
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
47
|
+
async generateImage(_request) {
|
|
48
|
+
return {
|
|
49
|
+
success: false,
|
|
50
|
+
error: {
|
|
51
|
+
code: 'UNAVAILABLE',
|
|
52
|
+
message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
46
56
|
}
|
|
47
57
|
//# sourceMappingURL=web.js.map
|
package/dist/esm/web.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C;;;GAGG;AACH,MAAM,OAAO,oBAAqB,SAAQ,SAAS;IACjD,6DAA6D;IAC7D,KAAK,CAAC,QAAQ,CAAC,QAAyB;QACtC,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE;gBACL,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,0FAA0F;aACpG;SACF,CAAC;IACJ,CAAC;IAED,6DAA6D;IAC7D,KAAK,CAAC,YAAY,CAAC,QAAiB;QAClC,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE;gBACL,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,0FAA0F;aACpG;SACF,CAAC;IACJ,CAAC;IAED,6DAA6D;IAC7D,KAAK,CAAC,wBAAwB,CAAC,QAAiB;QAC9C,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE;gBACL,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,0FAA0F;aACpG;SACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,iBAAiB;QACrB,OAAO;YACL,SAAS,EAAE,KAAK;YAChB,KAAK,EAAE;gBACL,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,0FAA0F;aACpG;SACF,CAAC;IACJ,CAAC;CACF"}
|
|
1
|
+
{"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C;;;GAGG;AACH,MAAM,OAAO,oBAAqB,SAAQ,SAAS;IACjD,6DAA6D;IAC7D,KAAK,CAAC,QAAQ,CAAC,QAAyB;QACtC,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE;gBACL,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,0FAA0F;aACpG;SACF,CAAC;IACJ,CAAC;IAED,6DAA6D;IAC7D,KAAK,CAAC,YAAY,CAAC,QAAiB;QAClC,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE;gBACL,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,0FAA0F;aACpG;SACF,CAAC;IACJ,CAAC;IAED,6DAA6D;IAC7D,KAAK,CAAC,wBAAwB,CAAC,QAAiB;QAC9C,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE;gBACL,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,0FAA0F;aACpG;SACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,iBAAiB;QACrB,OAAO;YACL,SAAS,EAAE,KAAK;YAChB,KAAK,EAAE;gBACL,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,0FAA0F;aACpG;SACF,CAAC;IACJ,CAAC;IAED,6DAA6D;IAC7D,KAAK,CAAC,aAAa,CAAC,QAAiB;QACnC,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE;gBACL,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,0FAA0F;aACpG;SACF,CAAC;IACJ,CAAC;CACF"}
|
package/dist/plugin.cjs.js
CHANGED
|
@@ -79,6 +79,16 @@ class AppleIntelligenceWeb extends core.WebPlugin {
|
|
|
79
79
|
},
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
83
|
+
async generateImage(_request) {
|
|
84
|
+
return {
|
|
85
|
+
success: false,
|
|
86
|
+
error: {
|
|
87
|
+
code: 'UNAVAILABLE',
|
|
88
|
+
message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
var web = /*#__PURE__*/Object.freeze({
|
package/dist/plugin.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\n/**\n * Apple Intelligence Plugin instance.\n *\n * Provides schema-constrained JSON generation using Apple's on-device\n * Foundation Models framework.\n *\n * @example\n * ```typescript\n * import { AppleIntelligence } from 'capacitor-apple-intelligence';\n *\n * const result = await AppleIntelligence.generate({\n * messages: [{ role: \"user\", content: \"Suggest 3 books\" }],\n * response_format: {\n * type: \"json_schema\",\n * schema: {\n * type: \"array\",\n * items: {\n * type: \"object\",\n * properties: {\n * title: { type: \"string\" },\n * author: { type: \"string\" }\n * },\n * required: [\"title\", \"author\"]\n * }\n * }\n * }\n * });\n * ```\n */\nconst AppleIntelligence = registerPlugin('AppleIntelligence', {\n web: () => import('./web').then((m) => new m.AppleIntelligenceWeb()),\n});\nexport * from './definitions';\nexport { AppleIntelligence };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\n/**\n * Web implementation stub for Apple Intelligence.\n * Always returns UNAVAILABLE since Apple Intelligence is iOS-only.\n */\nexport class AppleIntelligenceWeb extends WebPlugin {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async generate(_request) {\n return {\n success: false,\n error: {\n code: 'UNAVAILABLE',\n message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',\n },\n };\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async generateText(_request) {\n return {\n success: false,\n error: {\n code: 'UNAVAILABLE',\n message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',\n },\n };\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async generateTextWithLanguage(_request) {\n return {\n success: false,\n error: {\n code: 'UNAVAILABLE',\n message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',\n },\n };\n }\n async checkAvailability() {\n return {\n available: false,\n error: {\n code: 'UNAVAILABLE',\n message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',\n },\n };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACK,MAAC,iBAAiB,GAAGA,mBAAc,CAAC,mBAAmB,EAAE;AAC9D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,oBAAoB,EAAE,CAAC;AACxE,CAAC;;AC/BD;AACA;AACA;AACA;AACO,MAAM,oBAAoB,SAASC,cAAS,CAAC;AACpD;AACA,IAAI,MAAM,QAAQ,CAAC,QAAQ,EAAE;AAC7B,QAAQ,OAAO;AACf,YAAY,OAAO,EAAE,KAAK;AAC1B,YAAY,KAAK,EAAE;AACnB,gBAAgB,IAAI,EAAE,aAAa;AACnC,gBAAgB,OAAO,EAAE,0FAA0F;AACnH,aAAa;AACb,SAAS;AACT,IAAI;AACJ;AACA,IAAI,MAAM,YAAY,CAAC,QAAQ,EAAE;AACjC,QAAQ,OAAO;AACf,YAAY,OAAO,EAAE,KAAK;AAC1B,YAAY,KAAK,EAAE;AACnB,gBAAgB,IAAI,EAAE,aAAa;AACnC,gBAAgB,OAAO,EAAE,0FAA0F;AACnH,aAAa;AACb,SAAS;AACT,IAAI;AACJ;AACA,IAAI,MAAM,wBAAwB,CAAC,QAAQ,EAAE;AAC7C,QAAQ,OAAO;AACf,YAAY,OAAO,EAAE,KAAK;AAC1B,YAAY,KAAK,EAAE;AACnB,gBAAgB,IAAI,EAAE,aAAa;AACnC,gBAAgB,OAAO,EAAE,0FAA0F;AACnH,aAAa;AACb,SAAS;AACT,IAAI;AACJ,IAAI,MAAM,iBAAiB,GAAG;AAC9B,QAAQ,OAAO;AACf,YAAY,SAAS,EAAE,KAAK;AAC5B,YAAY,KAAK,EAAE;AACnB,gBAAgB,IAAI,EAAE,aAAa;AACnC,gBAAgB,OAAO,EAAE,0FAA0F;AACnH,aAAa;AACb,SAAS;AACT,IAAI;AACJ;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\n/**\n * Apple Intelligence Plugin instance.\n *\n * Provides schema-constrained JSON generation using Apple's on-device\n * Foundation Models framework.\n *\n * @example\n * ```typescript\n * import { AppleIntelligence } from 'capacitor-apple-intelligence';\n *\n * const result = await AppleIntelligence.generate({\n * messages: [{ role: \"user\", content: \"Suggest 3 books\" }],\n * response_format: {\n * type: \"json_schema\",\n * schema: {\n * type: \"array\",\n * items: {\n * type: \"object\",\n * properties: {\n * title: { type: \"string\" },\n * author: { type: \"string\" }\n * },\n * required: [\"title\", \"author\"]\n * }\n * }\n * }\n * });\n * ```\n */\nconst AppleIntelligence = registerPlugin('AppleIntelligence', {\n web: () => import('./web').then((m) => new m.AppleIntelligenceWeb()),\n});\nexport * from './definitions';\nexport { AppleIntelligence };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\n/**\n * Web implementation stub for Apple Intelligence.\n * Always returns UNAVAILABLE since Apple Intelligence is iOS-only.\n */\nexport class AppleIntelligenceWeb extends WebPlugin {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async generate(_request) {\n return {\n success: false,\n error: {\n code: 'UNAVAILABLE',\n message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',\n },\n };\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async generateText(_request) {\n return {\n success: false,\n error: {\n code: 'UNAVAILABLE',\n message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',\n },\n };\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async generateTextWithLanguage(_request) {\n return {\n success: false,\n error: {\n code: 'UNAVAILABLE',\n message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',\n },\n };\n }\n async checkAvailability() {\n return {\n available: false,\n error: {\n code: 'UNAVAILABLE',\n message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',\n },\n };\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async generateImage(_request) {\n return {\n success: false,\n error: {\n code: 'UNAVAILABLE',\n message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',\n },\n };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACK,MAAC,iBAAiB,GAAGA,mBAAc,CAAC,mBAAmB,EAAE;AAC9D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,oBAAoB,EAAE,CAAC;AACxE,CAAC;;AC/BD;AACA;AACA;AACA;AACO,MAAM,oBAAoB,SAASC,cAAS,CAAC;AACpD;AACA,IAAI,MAAM,QAAQ,CAAC,QAAQ,EAAE;AAC7B,QAAQ,OAAO;AACf,YAAY,OAAO,EAAE,KAAK;AAC1B,YAAY,KAAK,EAAE;AACnB,gBAAgB,IAAI,EAAE,aAAa;AACnC,gBAAgB,OAAO,EAAE,0FAA0F;AACnH,aAAa;AACb,SAAS;AACT,IAAI;AACJ;AACA,IAAI,MAAM,YAAY,CAAC,QAAQ,EAAE;AACjC,QAAQ,OAAO;AACf,YAAY,OAAO,EAAE,KAAK;AAC1B,YAAY,KAAK,EAAE;AACnB,gBAAgB,IAAI,EAAE,aAAa;AACnC,gBAAgB,OAAO,EAAE,0FAA0F;AACnH,aAAa;AACb,SAAS;AACT,IAAI;AACJ;AACA,IAAI,MAAM,wBAAwB,CAAC,QAAQ,EAAE;AAC7C,QAAQ,OAAO;AACf,YAAY,OAAO,EAAE,KAAK;AAC1B,YAAY,KAAK,EAAE;AACnB,gBAAgB,IAAI,EAAE,aAAa;AACnC,gBAAgB,OAAO,EAAE,0FAA0F;AACnH,aAAa;AACb,SAAS;AACT,IAAI;AACJ,IAAI,MAAM,iBAAiB,GAAG;AAC9B,QAAQ,OAAO;AACf,YAAY,SAAS,EAAE,KAAK;AAC5B,YAAY,KAAK,EAAE;AACnB,gBAAgB,IAAI,EAAE,aAAa;AACnC,gBAAgB,OAAO,EAAE,0FAA0F;AACnH,aAAa;AACb,SAAS;AACT,IAAI;AACJ;AACA,IAAI,MAAM,aAAa,CAAC,QAAQ,EAAE;AAClC,QAAQ,OAAO;AACf,YAAY,OAAO,EAAE,KAAK;AAC1B,YAAY,KAAK,EAAE;AACnB,gBAAgB,IAAI,EAAE,aAAa;AACnC,gBAAgB,OAAO,EAAE,0FAA0F;AACnH,aAAa;AACb,SAAS;AACT,IAAI;AACJ;;;;;;;;;"}
|
package/dist/plugin.js
CHANGED
|
@@ -78,6 +78,16 @@ var capacitorAppleIntelligence = (function (exports, core) {
|
|
|
78
78
|
},
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
82
|
+
async generateImage(_request) {
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
error: {
|
|
86
|
+
code: 'UNAVAILABLE',
|
|
87
|
+
message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
81
91
|
}
|
|
82
92
|
|
|
83
93
|
var web = /*#__PURE__*/Object.freeze({
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\n/**\n * Apple Intelligence Plugin instance.\n *\n * Provides schema-constrained JSON generation using Apple's on-device\n * Foundation Models framework.\n *\n * @example\n * ```typescript\n * import { AppleIntelligence } from 'capacitor-apple-intelligence';\n *\n * const result = await AppleIntelligence.generate({\n * messages: [{ role: \"user\", content: \"Suggest 3 books\" }],\n * response_format: {\n * type: \"json_schema\",\n * schema: {\n * type: \"array\",\n * items: {\n * type: \"object\",\n * properties: {\n * title: { type: \"string\" },\n * author: { type: \"string\" }\n * },\n * required: [\"title\", \"author\"]\n * }\n * }\n * }\n * });\n * ```\n */\nconst AppleIntelligence = registerPlugin('AppleIntelligence', {\n web: () => import('./web').then((m) => new m.AppleIntelligenceWeb()),\n});\nexport * from './definitions';\nexport { AppleIntelligence };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\n/**\n * Web implementation stub for Apple Intelligence.\n * Always returns UNAVAILABLE since Apple Intelligence is iOS-only.\n */\nexport class AppleIntelligenceWeb extends WebPlugin {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async generate(_request) {\n return {\n success: false,\n error: {\n code: 'UNAVAILABLE',\n message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',\n },\n };\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async generateText(_request) {\n return {\n success: false,\n error: {\n code: 'UNAVAILABLE',\n message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',\n },\n };\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async generateTextWithLanguage(_request) {\n return {\n success: false,\n error: {\n code: 'UNAVAILABLE',\n message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',\n },\n };\n }\n async checkAvailability() {\n return {\n available: false,\n error: {\n code: 'UNAVAILABLE',\n message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',\n },\n };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;AACK,UAAC,iBAAiB,GAAGA,mBAAc,CAAC,mBAAmB,EAAE;IAC9D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,oBAAoB,EAAE,CAAC;IACxE,CAAC;;IC/BD;IACA;IACA;IACA;IACO,MAAM,oBAAoB,SAASC,cAAS,CAAC;IACpD;IACA,IAAI,MAAM,QAAQ,CAAC,QAAQ,EAAE;IAC7B,QAAQ,OAAO;IACf,YAAY,OAAO,EAAE,KAAK;IAC1B,YAAY,KAAK,EAAE;IACnB,gBAAgB,IAAI,EAAE,aAAa;IACnC,gBAAgB,OAAO,EAAE,0FAA0F;IACnH,aAAa;IACb,SAAS;IACT,IAAI;IACJ;IACA,IAAI,MAAM,YAAY,CAAC,QAAQ,EAAE;IACjC,QAAQ,OAAO;IACf,YAAY,OAAO,EAAE,KAAK;IAC1B,YAAY,KAAK,EAAE;IACnB,gBAAgB,IAAI,EAAE,aAAa;IACnC,gBAAgB,OAAO,EAAE,0FAA0F;IACnH,aAAa;IACb,SAAS;IACT,IAAI;IACJ;IACA,IAAI,MAAM,wBAAwB,CAAC,QAAQ,EAAE;IAC7C,QAAQ,OAAO;IACf,YAAY,OAAO,EAAE,KAAK;IAC1B,YAAY,KAAK,EAAE;IACnB,gBAAgB,IAAI,EAAE,aAAa;IACnC,gBAAgB,OAAO,EAAE,0FAA0F;IACnH,aAAa;IACb,SAAS;IACT,IAAI;IACJ,IAAI,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,OAAO;IACf,YAAY,SAAS,EAAE,KAAK;IAC5B,YAAY,KAAK,EAAE;IACnB,gBAAgB,IAAI,EAAE,aAAa;IACnC,gBAAgB,OAAO,EAAE,0FAA0F;IACnH,aAAa;IACb,SAAS;IACT,IAAI;IACJ;;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\n/**\n * Apple Intelligence Plugin instance.\n *\n * Provides schema-constrained JSON generation using Apple's on-device\n * Foundation Models framework.\n *\n * @example\n * ```typescript\n * import { AppleIntelligence } from 'capacitor-apple-intelligence';\n *\n * const result = await AppleIntelligence.generate({\n * messages: [{ role: \"user\", content: \"Suggest 3 books\" }],\n * response_format: {\n * type: \"json_schema\",\n * schema: {\n * type: \"array\",\n * items: {\n * type: \"object\",\n * properties: {\n * title: { type: \"string\" },\n * author: { type: \"string\" }\n * },\n * required: [\"title\", \"author\"]\n * }\n * }\n * }\n * });\n * ```\n */\nconst AppleIntelligence = registerPlugin('AppleIntelligence', {\n web: () => import('./web').then((m) => new m.AppleIntelligenceWeb()),\n});\nexport * from './definitions';\nexport { AppleIntelligence };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\n/**\n * Web implementation stub for Apple Intelligence.\n * Always returns UNAVAILABLE since Apple Intelligence is iOS-only.\n */\nexport class AppleIntelligenceWeb extends WebPlugin {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async generate(_request) {\n return {\n success: false,\n error: {\n code: 'UNAVAILABLE',\n message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',\n },\n };\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async generateText(_request) {\n return {\n success: false,\n error: {\n code: 'UNAVAILABLE',\n message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',\n },\n };\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async generateTextWithLanguage(_request) {\n return {\n success: false,\n error: {\n code: 'UNAVAILABLE',\n message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',\n },\n };\n }\n async checkAvailability() {\n return {\n available: false,\n error: {\n code: 'UNAVAILABLE',\n message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',\n },\n };\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async generateImage(_request) {\n return {\n success: false,\n error: {\n code: 'UNAVAILABLE',\n message: 'Apple Intelligence is only available on iOS 26+ devices with Apple Intelligence enabled.',\n },\n };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;AACK,UAAC,iBAAiB,GAAGA,mBAAc,CAAC,mBAAmB,EAAE;IAC9D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,oBAAoB,EAAE,CAAC;IACxE,CAAC;;IC/BD;IACA;IACA;IACA;IACO,MAAM,oBAAoB,SAASC,cAAS,CAAC;IACpD;IACA,IAAI,MAAM,QAAQ,CAAC,QAAQ,EAAE;IAC7B,QAAQ,OAAO;IACf,YAAY,OAAO,EAAE,KAAK;IAC1B,YAAY,KAAK,EAAE;IACnB,gBAAgB,IAAI,EAAE,aAAa;IACnC,gBAAgB,OAAO,EAAE,0FAA0F;IACnH,aAAa;IACb,SAAS;IACT,IAAI;IACJ;IACA,IAAI,MAAM,YAAY,CAAC,QAAQ,EAAE;IACjC,QAAQ,OAAO;IACf,YAAY,OAAO,EAAE,KAAK;IAC1B,YAAY,KAAK,EAAE;IACnB,gBAAgB,IAAI,EAAE,aAAa;IACnC,gBAAgB,OAAO,EAAE,0FAA0F;IACnH,aAAa;IACb,SAAS;IACT,IAAI;IACJ;IACA,IAAI,MAAM,wBAAwB,CAAC,QAAQ,EAAE;IAC7C,QAAQ,OAAO;IACf,YAAY,OAAO,EAAE,KAAK;IAC1B,YAAY,KAAK,EAAE;IACnB,gBAAgB,IAAI,EAAE,aAAa;IACnC,gBAAgB,OAAO,EAAE,0FAA0F;IACnH,aAAa;IACb,SAAS;IACT,IAAI;IACJ,IAAI,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,OAAO;IACf,YAAY,SAAS,EAAE,KAAK;IAC5B,YAAY,KAAK,EAAE;IACnB,gBAAgB,IAAI,EAAE,aAAa;IACnC,gBAAgB,OAAO,EAAE,0FAA0F;IACnH,aAAa;IACb,SAAS;IACT,IAAI;IACJ;IACA,IAAI,MAAM,aAAa,CAAC,QAAQ,EAAE;IAClC,QAAQ,OAAO;IACf,YAAY,OAAO,EAAE,KAAK;IAC1B,YAAY,KAAK,EAAE;IACnB,gBAAgB,IAAI,EAAE,aAAa;IACnC,gBAAgB,OAAO,EAAE,0FAA0F;IACnH,aAAa;IACb,SAAS;IACT,IAAI;IACJ;;;;;;;;;;;;;;;"}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import Foundation
|
|
2
|
+
import UIKit
|
|
2
3
|
|
|
3
4
|
#if canImport(FoundationModels)
|
|
4
5
|
import FoundationModels
|
|
5
6
|
#endif
|
|
6
7
|
|
|
8
|
+
#if canImport(ImagePlayground)
|
|
9
|
+
import ImagePlayground
|
|
10
|
+
#endif
|
|
11
|
+
|
|
7
12
|
// MARK: - Error Types
|
|
8
13
|
|
|
9
14
|
/// Error codes for Apple Intelligence plugin
|
|
@@ -51,6 +56,35 @@ public struct Message {
|
|
|
51
56
|
}
|
|
52
57
|
}
|
|
53
58
|
|
|
59
|
+
// MARK: - Image Generation Options
|
|
60
|
+
|
|
61
|
+
/// Configuration options for image generation
|
|
62
|
+
public struct ImageGenerationOptions {
|
|
63
|
+
/// JPEG compression quality (0.0 to 1.0)
|
|
64
|
+
public let compressionQuality: CGFloat
|
|
65
|
+
/// Maximum dimension for the output image
|
|
66
|
+
public let maxDimension: CGFloat?
|
|
67
|
+
/// Output format
|
|
68
|
+
public let format: ImageFormat
|
|
69
|
+
|
|
70
|
+
public enum ImageFormat {
|
|
71
|
+
case png
|
|
72
|
+
case jpeg
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public static let `default` = ImageGenerationOptions(
|
|
76
|
+
compressionQuality: 0.8,
|
|
77
|
+
maxDimension: 1024,
|
|
78
|
+
format: .jpeg
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
public init(compressionQuality: CGFloat = 0.8, maxDimension: CGFloat? = 1024, format: ImageFormat = .jpeg) {
|
|
82
|
+
self.compressionQuality = min(1.0, max(0.0, compressionQuality))
|
|
83
|
+
self.maxDimension = maxDimension
|
|
84
|
+
self.format = format
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
54
88
|
// MARK: - Main Implementation
|
|
55
89
|
|
|
56
90
|
/// Apple Intelligence implementation class
|
|
@@ -60,6 +94,121 @@ public struct Message {
|
|
|
60
94
|
// MARK: - Constants
|
|
61
95
|
|
|
62
96
|
private let maxRetries = 1
|
|
97
|
+
private let maxImageCount = 10
|
|
98
|
+
private let minImageCount = 1
|
|
99
|
+
|
|
100
|
+
// MARK: - Task Management
|
|
101
|
+
|
|
102
|
+
/// Currently running generation tasks for cancellation support
|
|
103
|
+
private var activeTasks: [UUID: Task<Void, Never>] = [:]
|
|
104
|
+
private let taskLock = NSLock()
|
|
105
|
+
|
|
106
|
+
/// Cancel all active generation tasks
|
|
107
|
+
public func cancelAllTasks() {
|
|
108
|
+
taskLock.lock()
|
|
109
|
+
defer { taskLock.unlock() }
|
|
110
|
+
for (_, task) in activeTasks {
|
|
111
|
+
task.cancel()
|
|
112
|
+
}
|
|
113
|
+
activeTasks.removeAll()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// Register a task for tracking
|
|
117
|
+
private func registerTask(_ task: Task<Void, Never>) -> UUID {
|
|
118
|
+
let id = UUID()
|
|
119
|
+
taskLock.lock()
|
|
120
|
+
activeTasks[id] = task
|
|
121
|
+
taskLock.unlock()
|
|
122
|
+
return id
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// Unregister a completed task
|
|
126
|
+
private func unregisterTask(_ id: UUID) {
|
|
127
|
+
taskLock.lock()
|
|
128
|
+
activeTasks.removeValue(forKey: id)
|
|
129
|
+
taskLock.unlock()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// MARK: - Helper Methods
|
|
133
|
+
|
|
134
|
+
/// Convert schema dictionary to JSON string
|
|
135
|
+
private func schemaToString(_ schema: [String: Any]) -> String {
|
|
136
|
+
guard let data = try? JSONSerialization.data(withJSONObject: schema, options: [.prettyPrinted, .sortedKeys]),
|
|
137
|
+
let string = String(data: data, encoding: .utf8) else {
|
|
138
|
+
return "{}"
|
|
139
|
+
}
|
|
140
|
+
return string
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Parse message dictionaries into Message objects
|
|
144
|
+
public func parseMessages(_ messages: [[String: String]]) -> [Message]? {
|
|
145
|
+
let parsed = messages.compactMap { dict -> Message? in
|
|
146
|
+
guard let roleStr = dict["role"],
|
|
147
|
+
let content = dict["content"],
|
|
148
|
+
let role = MessageRole(rawValue: roleStr) else { return nil }
|
|
149
|
+
return Message(role: role, content: content)
|
|
150
|
+
}
|
|
151
|
+
return parsed.isEmpty ? nil : parsed
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// Safely escape string for JSON embedding
|
|
155
|
+
private func escapeForJson(_ string: String) -> String {
|
|
156
|
+
guard let data = try? JSONSerialization.data(withJSONObject: string, options: []),
|
|
157
|
+
let escaped = String(data: data, encoding: .utf8) else {
|
|
158
|
+
// Fallback: basic escaping
|
|
159
|
+
return string
|
|
160
|
+
.replacingOccurrences(of: "\\", with: "\\\\")
|
|
161
|
+
.replacingOccurrences(of: "\"", with: "\\\"")
|
|
162
|
+
.replacingOccurrences(of: "\n", with: "\\n")
|
|
163
|
+
.replacingOccurrences(of: "\r", with: "\\r")
|
|
164
|
+
.replacingOccurrences(of: "\t", with: "\\t")
|
|
165
|
+
}
|
|
166
|
+
// Remove surrounding quotes from JSON string serialization
|
|
167
|
+
return String(escaped.dropFirst().dropLast())
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/// Get full language name from locale code
|
|
171
|
+
private func fullLanguageName(from code: String) -> String {
|
|
172
|
+
// First try to get from system locale
|
|
173
|
+
if let name = Locale.current.localizedString(forLanguageCode: code.lowercased()) {
|
|
174
|
+
return name
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Fallback map for common codes
|
|
178
|
+
let fallbackMap: [String: String] = [
|
|
179
|
+
"en": "English",
|
|
180
|
+
"es": "Spanish",
|
|
181
|
+
"fr": "French",
|
|
182
|
+
"de": "German",
|
|
183
|
+
"ja": "Japanese",
|
|
184
|
+
"zh": "Chinese",
|
|
185
|
+
"it": "Italian",
|
|
186
|
+
"pt": "Portuguese",
|
|
187
|
+
"ru": "Russian",
|
|
188
|
+
"ar": "Arabic",
|
|
189
|
+
"ko": "Korean",
|
|
190
|
+
"nl": "Dutch",
|
|
191
|
+
"pl": "Polish",
|
|
192
|
+
"tr": "Turkish",
|
|
193
|
+
"vi": "Vietnamese",
|
|
194
|
+
"th": "Thai",
|
|
195
|
+
"hi": "Hindi",
|
|
196
|
+
"he": "Hebrew",
|
|
197
|
+
"id": "Indonesian",
|
|
198
|
+
"ms": "Malay",
|
|
199
|
+
"sv": "Swedish",
|
|
200
|
+
"da": "Danish",
|
|
201
|
+
"no": "Norwegian",
|
|
202
|
+
"fi": "Finnish",
|
|
203
|
+
"cs": "Czech",
|
|
204
|
+
"el": "Greek",
|
|
205
|
+
"hu": "Hungarian",
|
|
206
|
+
"ro": "Romanian",
|
|
207
|
+
"uk": "Ukrainian"
|
|
208
|
+
]
|
|
209
|
+
|
|
210
|
+
return fallbackMap[code.lowercased()] ?? code
|
|
211
|
+
}
|
|
63
212
|
|
|
64
213
|
// MARK: - JSON Schema Validation
|
|
65
214
|
|
|
@@ -77,10 +226,9 @@ public struct Message {
|
|
|
77
226
|
case "string":
|
|
78
227
|
return (json is String, json is String ? nil : "Expected string, got \(type(of: json))")
|
|
79
228
|
case "number", "integer":
|
|
80
|
-
return (json
|
|
81
|
-
(json is NSNumber && !(json is Bool)) ? nil : "Expected number, got \(type(of: json))")
|
|
229
|
+
return validateNumber(json)
|
|
82
230
|
case "boolean":
|
|
83
|
-
return (json
|
|
231
|
+
return validateBoolean(json)
|
|
84
232
|
case "null":
|
|
85
233
|
return (json is NSNull, json is NSNull ? nil : "Expected null, got \(type(of: json))")
|
|
86
234
|
default:
|
|
@@ -88,6 +236,40 @@ public struct Message {
|
|
|
88
236
|
}
|
|
89
237
|
}
|
|
90
238
|
|
|
239
|
+
/// Validate a number value (ensuring it's not actually a boolean)
|
|
240
|
+
private func validateNumber(_ json: Any) -> (valid: Bool, error: String?) {
|
|
241
|
+
guard let number = json as? NSNumber else {
|
|
242
|
+
return (false, "Expected number, got \(type(of: json))")
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Use CFGetTypeID to properly distinguish booleans from numbers
|
|
246
|
+
let boolID = CFBooleanGetTypeID()
|
|
247
|
+
let typeID = CFGetTypeID(number as CFTypeRef)
|
|
248
|
+
|
|
249
|
+
if typeID == boolID {
|
|
250
|
+
return (false, "Expected number, got boolean")
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return (true, nil)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/// Validate a boolean value
|
|
257
|
+
private func validateBoolean(_ json: Any) -> (valid: Bool, error: String?) {
|
|
258
|
+
guard let number = json as? NSNumber else {
|
|
259
|
+
return (false, "Expected boolean, got \(type(of: json))")
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Use CFGetTypeID to properly identify booleans
|
|
263
|
+
let boolID = CFBooleanGetTypeID()
|
|
264
|
+
let typeID = CFGetTypeID(number as CFTypeRef)
|
|
265
|
+
|
|
266
|
+
if typeID == boolID {
|
|
267
|
+
return (true, nil)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return (false, "Expected boolean, got number")
|
|
271
|
+
}
|
|
272
|
+
|
|
91
273
|
/// Validate an object against a schema
|
|
92
274
|
private func validateObject(_ json: Any, schema: [String: Any]) -> (valid: Bool, error: String?) {
|
|
93
275
|
guard let jsonObject = json as? [String: Any] else {
|
|
@@ -155,13 +337,7 @@ public struct Message {
|
|
|
155
337
|
|
|
156
338
|
/// Build the system prompt with JSON schema instructions
|
|
157
339
|
private func buildSystemPrompt(userSystemPrompt: String?, schema: [String: Any]) -> String {
|
|
158
|
-
let schemaJson
|
|
159
|
-
do {
|
|
160
|
-
let schemaData = try JSONSerialization.data(withJSONObject: schema, options: [.prettyPrinted, .sortedKeys])
|
|
161
|
-
schemaJson = String(data: schemaData, encoding: .utf8) ?? "{}"
|
|
162
|
-
} catch {
|
|
163
|
-
schemaJson = "{}"
|
|
164
|
-
}
|
|
340
|
+
let schemaJson = schemaToString(schema)
|
|
165
341
|
|
|
166
342
|
var prompt = """
|
|
167
343
|
You are a JSON generator. Your response must be ONLY valid JSON that matches the provided schema.
|
|
@@ -189,13 +365,7 @@ public struct Message {
|
|
|
189
365
|
|
|
190
366
|
/// Build corrective prompt for retry attempts
|
|
191
367
|
private func buildCorrectivePrompt(previousResponse: String, validationError: String, schema: [String: Any]) -> String {
|
|
192
|
-
let schemaJson
|
|
193
|
-
do {
|
|
194
|
-
let schemaData = try JSONSerialization.data(withJSONObject: schema, options: [.prettyPrinted, .sortedKeys])
|
|
195
|
-
schemaJson = String(data: schemaData, encoding: .utf8) ?? "{}"
|
|
196
|
-
} catch {
|
|
197
|
-
schemaJson = "{}"
|
|
198
|
-
}
|
|
368
|
+
let schemaJson = schemaToString(schema)
|
|
199
369
|
|
|
200
370
|
return """
|
|
201
371
|
The previous response was invalid JSON or did not match the required schema.
|
|
@@ -278,8 +448,13 @@ public struct Message {
|
|
|
278
448
|
messages: [Message],
|
|
279
449
|
schema: [String: Any]
|
|
280
450
|
) async -> Result<Any, AppleIntelligenceError> {
|
|
281
|
-
//
|
|
282
|
-
|
|
451
|
+
// Check for task cancellation
|
|
452
|
+
if Task.isCancelled {
|
|
453
|
+
return .failure(AppleIntelligenceError(
|
|
454
|
+
code: .nativeError,
|
|
455
|
+
message: "Generation was cancelled"
|
|
456
|
+
))
|
|
457
|
+
}
|
|
283
458
|
|
|
284
459
|
// Extract system and user messages
|
|
285
460
|
let systemMessages = messages.filter { $0.role == .system }.map { $0.content }
|
|
@@ -296,6 +471,14 @@ public struct Message {
|
|
|
296
471
|
var lastError = ""
|
|
297
472
|
|
|
298
473
|
for attempt in 0...maxRetries {
|
|
474
|
+
// Check for cancellation between attempts
|
|
475
|
+
if Task.isCancelled {
|
|
476
|
+
return .failure(AppleIntelligenceError(
|
|
477
|
+
code: .nativeError,
|
|
478
|
+
message: "Generation was cancelled"
|
|
479
|
+
))
|
|
480
|
+
}
|
|
481
|
+
|
|
299
482
|
do {
|
|
300
483
|
let response: String
|
|
301
484
|
|
|
@@ -380,14 +563,24 @@ public struct Message {
|
|
|
380
563
|
return response.content
|
|
381
564
|
|
|
382
565
|
#else
|
|
383
|
-
//
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
566
|
+
// Mock fallback for development/testing
|
|
567
|
+
if systemPrompt.contains("You are a JSON generator") {
|
|
568
|
+
// JSON mock for generate() calls - using proper JSON escaping
|
|
569
|
+
let escapedQuery = escapeForJson(userPrompt)
|
|
570
|
+
return """
|
|
571
|
+
{
|
|
572
|
+
"mock_response": "This is a mocked JSON response from Apple Intelligence (Runtime not available)",
|
|
573
|
+
"original_query": "\(escapedQuery)"
|
|
574
|
+
}
|
|
575
|
+
"""
|
|
576
|
+
} else {
|
|
577
|
+
// Return plain text for generateText() calls
|
|
578
|
+
return "This is a mocked response from Apple Intelligence (Runtime not available). User query was: \(userPrompt)"
|
|
579
|
+
}
|
|
388
580
|
#endif
|
|
389
581
|
}
|
|
390
582
|
|
|
583
|
+
// MARK: - Bridge Helpers
|
|
391
584
|
|
|
392
585
|
/// Generate method that returns a dictionary suitable for Capacitor bridge
|
|
393
586
|
@available(iOS 26, *)
|
|
@@ -395,17 +588,7 @@ public struct Message {
|
|
|
395
588
|
messages: [[String: String]],
|
|
396
589
|
schema: [String: Any]
|
|
397
590
|
) async -> [String: Any] {
|
|
398
|
-
|
|
399
|
-
let parsedMessages = messages.compactMap { dict -> Message? in
|
|
400
|
-
guard let roleStr = dict["role"],
|
|
401
|
-
let content = dict["content"],
|
|
402
|
-
let role = MessageRole(rawValue: roleStr) else {
|
|
403
|
-
return nil
|
|
404
|
-
}
|
|
405
|
-
return Message(role: role, content: content)
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if parsedMessages.isEmpty {
|
|
591
|
+
guard let parsedMessages = parseMessages(messages) else {
|
|
409
592
|
return [
|
|
410
593
|
"success": false,
|
|
411
594
|
"error": AppleIntelligenceError(
|
|
@@ -430,6 +613,7 @@ public struct Message {
|
|
|
430
613
|
]
|
|
431
614
|
}
|
|
432
615
|
}
|
|
616
|
+
|
|
433
617
|
/// Generate plain text output using Apple Intelligence
|
|
434
618
|
/// - Parameters:
|
|
435
619
|
/// - messages: Array of conversation messages
|
|
@@ -438,6 +622,14 @@ public struct Message {
|
|
|
438
622
|
public func generateText(
|
|
439
623
|
messages: [Message]
|
|
440
624
|
) async -> Result<String, AppleIntelligenceError> {
|
|
625
|
+
// Check for task cancellation
|
|
626
|
+
if Task.isCancelled {
|
|
627
|
+
return .failure(AppleIntelligenceError(
|
|
628
|
+
code: .nativeError,
|
|
629
|
+
message: "Generation was cancelled"
|
|
630
|
+
))
|
|
631
|
+
}
|
|
632
|
+
|
|
441
633
|
let systemMessages = messages.filter { $0.role == .system }.map { $0.content }
|
|
442
634
|
let userMessages = messages.filter { $0.role == .user }.map { $0.content }
|
|
443
635
|
|
|
@@ -468,25 +660,19 @@ public struct Message {
|
|
|
468
660
|
messages: [Message],
|
|
469
661
|
language: String
|
|
470
662
|
) async -> Result<String, AppleIntelligenceError> {
|
|
663
|
+
// Check for task cancellation
|
|
664
|
+
if Task.isCancelled {
|
|
665
|
+
return .failure(AppleIntelligenceError(
|
|
666
|
+
code: .nativeError,
|
|
667
|
+
message: "Generation was cancelled"
|
|
668
|
+
))
|
|
669
|
+
}
|
|
670
|
+
|
|
471
671
|
let systemMessages = messages.filter { $0.role == .system }.map { $0.content }
|
|
472
672
|
let userMessages = messages.filter { $0.role == .user }.map { $0.content }
|
|
473
673
|
|
|
474
|
-
//
|
|
475
|
-
let
|
|
476
|
-
"en": "English",
|
|
477
|
-
"es": "Spanish",
|
|
478
|
-
"fr": "French",
|
|
479
|
-
"de": "German",
|
|
480
|
-
"ja": "Japanese",
|
|
481
|
-
"zh": "Chinese",
|
|
482
|
-
"it": "Italian",
|
|
483
|
-
"pt": "Portuguese",
|
|
484
|
-
"ru": "Russian",
|
|
485
|
-
"ar": "Arabic",
|
|
486
|
-
"ko": "Korean"
|
|
487
|
-
]
|
|
488
|
-
|
|
489
|
-
let fullLanguageName = languageMap[language.lowercased()] ?? language
|
|
674
|
+
// Get full language name dynamically
|
|
675
|
+
let fullLanguageName = fullLanguageName(from: language)
|
|
490
676
|
|
|
491
677
|
var systemPrompt = systemMessages.joined(separator: "\n")
|
|
492
678
|
// Append language instruction
|
|
@@ -516,16 +702,7 @@ public struct Message {
|
|
|
516
702
|
public func generateTextForBridge(
|
|
517
703
|
messages: [[String: String]]
|
|
518
704
|
) async -> [String: Any] {
|
|
519
|
-
let parsedMessages = messages
|
|
520
|
-
guard let roleStr = dict["role"],
|
|
521
|
-
let content = dict["content"],
|
|
522
|
-
let role = MessageRole(rawValue: roleStr) else {
|
|
523
|
-
return nil
|
|
524
|
-
}
|
|
525
|
-
return Message(role: role, content: content)
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
if parsedMessages.isEmpty {
|
|
705
|
+
guard let parsedMessages = parseMessages(messages) else {
|
|
529
706
|
return [
|
|
530
707
|
"success": false,
|
|
531
708
|
"error": AppleIntelligenceError(
|
|
@@ -557,16 +734,7 @@ public struct Message {
|
|
|
557
734
|
messages: [[String: String]],
|
|
558
735
|
language: String
|
|
559
736
|
) async -> [String: Any] {
|
|
560
|
-
let parsedMessages = messages
|
|
561
|
-
guard let roleStr = dict["role"],
|
|
562
|
-
let content = dict["content"],
|
|
563
|
-
let role = MessageRole(rawValue: roleStr) else {
|
|
564
|
-
return nil
|
|
565
|
-
}
|
|
566
|
-
return Message(role: role, content: content)
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
if parsedMessages.isEmpty {
|
|
737
|
+
guard let parsedMessages = parseMessages(messages) else {
|
|
570
738
|
return [
|
|
571
739
|
"success": false,
|
|
572
740
|
"error": AppleIntelligenceError(
|
|
@@ -591,4 +759,180 @@ public struct Message {
|
|
|
591
759
|
]
|
|
592
760
|
}
|
|
593
761
|
}
|
|
762
|
+
|
|
763
|
+
// MARK: - Image Generation
|
|
764
|
+
|
|
765
|
+
/// Supported image styles for Apple's on-device Image Playground
|
|
766
|
+
/// Note: Photorealistic images are NOT supported by Apple's on-device API
|
|
767
|
+
public static let supportedImageStyles = ["animation", "illustration", "sketch"]
|
|
768
|
+
|
|
769
|
+
/// Generate images using Apple Intelligence
|
|
770
|
+
/// - Parameters:
|
|
771
|
+
/// - prompt: Description of the image
|
|
772
|
+
/// - style: Style for the image. Supported values: "animation" (default), "illustration", "sketch".
|
|
773
|
+
/// Note: "photorealistic" and other styles are NOT supported by Apple's on-device Image Playground API.
|
|
774
|
+
/// - count: Number of images (clamped to 1-10)
|
|
775
|
+
/// - sourceImage: Optional source image for face-based generation. Required when prompt involves generating images of people.
|
|
776
|
+
/// - options: Image generation options for compression and sizing
|
|
777
|
+
/// - Returns: Result containing array of base64 image strings or error
|
|
778
|
+
@available(iOS 26, *)
|
|
779
|
+
public func generateImage(
|
|
780
|
+
prompt: String,
|
|
781
|
+
style: String?,
|
|
782
|
+
count: Int,
|
|
783
|
+
sourceImage: UIImage? = nil,
|
|
784
|
+
options: ImageGenerationOptions = .default
|
|
785
|
+
) async -> Result<[String], AppleIntelligenceError> {
|
|
786
|
+
// Check for task cancellation
|
|
787
|
+
if Task.isCancelled {
|
|
788
|
+
return .failure(AppleIntelligenceError(
|
|
789
|
+
code: .nativeError,
|
|
790
|
+
message: "Image generation was cancelled"
|
|
791
|
+
))
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// Validate style parameter
|
|
795
|
+
if let styleStr = style?.lowercased(), !styleStr.isEmpty {
|
|
796
|
+
if !Self.supportedImageStyles.contains(styleStr) {
|
|
797
|
+
return .failure(AppleIntelligenceError(
|
|
798
|
+
code: .nativeError,
|
|
799
|
+
message: "Unsupported image style: '\(style ?? "")'. Apple's on-device Image Playground only supports: \(Self.supportedImageStyles.joined(separator: ", ")). Note: Photorealistic images are not supported by Apple's native API."
|
|
800
|
+
))
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Validate and clamp count
|
|
805
|
+
let validatedCount = max(minImageCount, min(count, maxImageCount))
|
|
806
|
+
|
|
807
|
+
#if canImport(ImagePlayground)
|
|
808
|
+
do {
|
|
809
|
+
let creator = try await ImageCreator()
|
|
810
|
+
|
|
811
|
+
// Build concepts array
|
|
812
|
+
var concepts: [ImagePlaygroundConcept] = [.text(prompt)]
|
|
813
|
+
|
|
814
|
+
// If source image provided, save to temp URL and add as image concept
|
|
815
|
+
if let source = sourceImage, let jpegData = source.jpegData(compressionQuality: 0.9) {
|
|
816
|
+
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("source_\(UUID().uuidString).jpg")
|
|
817
|
+
try jpegData.write(to: tempURL)
|
|
818
|
+
if let imageConcept = ImagePlaygroundConcept.image(tempURL) {
|
|
819
|
+
concepts.append(imageConcept)
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Map style string to ImagePlaygroundStyle enum
|
|
824
|
+
let imageStyle: ImagePlaygroundStyle
|
|
825
|
+
switch style?.lowercased() {
|
|
826
|
+
case "illustration": imageStyle = .illustration
|
|
827
|
+
case "sketch": imageStyle = .sketch
|
|
828
|
+
default: imageStyle = .animation // Default to animation
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
var base64Images: [String] = []
|
|
832
|
+
|
|
833
|
+
for try await createdImage in creator.images(for: concepts, style: imageStyle, limit: validatedCount) {
|
|
834
|
+
// Check for cancellation during image generation
|
|
835
|
+
if Task.isCancelled {
|
|
836
|
+
return .failure(AppleIntelligenceError(
|
|
837
|
+
code: .nativeError,
|
|
838
|
+
message: "Image generation was cancelled"
|
|
839
|
+
))
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
var uiImage = UIImage(cgImage: createdImage.cgImage)
|
|
843
|
+
|
|
844
|
+
// Resize if needed
|
|
845
|
+
if let maxDim = options.maxDimension {
|
|
846
|
+
uiImage = resizeImage(uiImage, maxDimension: maxDim)
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// Compress based on format
|
|
850
|
+
let data: Data?
|
|
851
|
+
switch options.format {
|
|
852
|
+
case .png:
|
|
853
|
+
data = uiImage.pngData()
|
|
854
|
+
case .jpeg:
|
|
855
|
+
data = uiImage.jpegData(compressionQuality: options.compressionQuality)
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if let imageData = data {
|
|
859
|
+
base64Images.append(imageData.base64EncodedString())
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if base64Images.isEmpty {
|
|
864
|
+
return .failure(AppleIntelligenceError(code: .nativeError, message: "No images were generated"))
|
|
865
|
+
}
|
|
866
|
+
return .success(base64Images)
|
|
867
|
+
} catch let error as NSError {
|
|
868
|
+
// Provide more helpful error message for face-related errors
|
|
869
|
+
let errorMessage = error.localizedDescription
|
|
870
|
+
if errorMessage.lowercased().contains("face") || errorMessage.lowercased().contains("person") || errorMessage.lowercased().contains("source image") {
|
|
871
|
+
return .failure(AppleIntelligenceError(
|
|
872
|
+
code: .nativeError,
|
|
873
|
+
message: "This prompt requires generating images of people. Please provide a source image containing a face using the 'sourceImage' parameter, or modify your prompt to avoid generating human faces."
|
|
874
|
+
))
|
|
875
|
+
}
|
|
876
|
+
return .failure(AppleIntelligenceError(code: .nativeError, message: "Image generation failed: \(errorMessage)"))
|
|
877
|
+
}
|
|
878
|
+
#else
|
|
879
|
+
return .failure(AppleIntelligenceError(code: .unavailable, message: "ImagePlayground not available"))
|
|
880
|
+
#endif
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/// Resize image to fit within maximum dimension while maintaining aspect ratio
|
|
884
|
+
private func resizeImage(_ image: UIImage, maxDimension: CGFloat) -> UIImage {
|
|
885
|
+
let size = image.size
|
|
886
|
+
|
|
887
|
+
// Check if resizing is needed
|
|
888
|
+
guard size.width > maxDimension || size.height > maxDimension else {
|
|
889
|
+
return image
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
let ratio = min(maxDimension / size.width, maxDimension / size.height)
|
|
893
|
+
let newSize = CGSize(width: size.width * ratio, height: size.height * ratio)
|
|
894
|
+
|
|
895
|
+
let renderer = UIGraphicsImageRenderer(size: newSize)
|
|
896
|
+
return renderer.image { _ in
|
|
897
|
+
image.draw(in: CGRect(origin: .zero, size: newSize))
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/// Generate image bridge helper
|
|
902
|
+
@available(iOS 26, *)
|
|
903
|
+
public func generateImageForBridge(
|
|
904
|
+
prompt: String,
|
|
905
|
+
style: String?,
|
|
906
|
+
count: Int,
|
|
907
|
+
sourceImageBase64: String? = nil,
|
|
908
|
+
compressionQuality: CGFloat? = nil
|
|
909
|
+
) async -> [String: Any] {
|
|
910
|
+
let options = ImageGenerationOptions(
|
|
911
|
+
compressionQuality: compressionQuality ?? 0.8,
|
|
912
|
+
maxDimension: 1024,
|
|
913
|
+
format: .jpeg
|
|
914
|
+
)
|
|
915
|
+
|
|
916
|
+
// Decode source image from base64 if provided
|
|
917
|
+
var sourceImage: UIImage? = nil
|
|
918
|
+
if let base64String = sourceImageBase64,
|
|
919
|
+
let imageData = Data(base64Encoded: base64String) {
|
|
920
|
+
sourceImage = UIImage(data: imageData)
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
let result = await generateImage(prompt: prompt, style: style, count: count, sourceImage: sourceImage, options: options)
|
|
924
|
+
|
|
925
|
+
switch result {
|
|
926
|
+
case .success(let images):
|
|
927
|
+
return [
|
|
928
|
+
"success": true,
|
|
929
|
+
"images": images
|
|
930
|
+
]
|
|
931
|
+
case .failure(let error):
|
|
932
|
+
return [
|
|
933
|
+
"success": false,
|
|
934
|
+
"error": error.asDictionary
|
|
935
|
+
]
|
|
936
|
+
}
|
|
937
|
+
}
|
|
594
938
|
}
|
|
@@ -3,6 +3,7 @@ import Capacitor
|
|
|
3
3
|
|
|
4
4
|
/// Capacitor plugin class for Apple Intelligence
|
|
5
5
|
/// Bridges JavaScript calls to the native AppleIntelligence implementation
|
|
6
|
+
@MainActor
|
|
6
7
|
@objc(AppleIntelligencePlugin)
|
|
7
8
|
public class AppleIntelligencePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
8
9
|
|
|
@@ -14,13 +15,39 @@ public class AppleIntelligencePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
14
15
|
CAPPluginMethod(name: "generate", returnType: CAPPluginReturnPromise),
|
|
15
16
|
CAPPluginMethod(name: "generateText", returnType: CAPPluginReturnPromise),
|
|
16
17
|
CAPPluginMethod(name: "generateTextWithLanguage", returnType: CAPPluginReturnPromise),
|
|
17
|
-
CAPPluginMethod(name: "
|
|
18
|
+
CAPPluginMethod(name: "generateImage", returnType: CAPPluginReturnPromise),
|
|
19
|
+
CAPPluginMethod(name: "checkAvailability", returnType: CAPPluginReturnPromise),
|
|
20
|
+
CAPPluginMethod(name: "cancelAllTasks", returnType: CAPPluginReturnPromise)
|
|
18
21
|
]
|
|
19
22
|
|
|
20
23
|
// MARK: - Implementation
|
|
21
24
|
|
|
22
25
|
private let implementation = AppleIntelligence()
|
|
23
26
|
|
|
27
|
+
// MARK: - Helper Methods
|
|
28
|
+
|
|
29
|
+
/// Create a standardized error response dictionary
|
|
30
|
+
private func errorResponse(code: AppleIntelligenceErrorCode, message: String) -> [String: Any] {
|
|
31
|
+
return [
|
|
32
|
+
"success": false,
|
|
33
|
+
"error": [
|
|
34
|
+
"code": code.rawValue,
|
|
35
|
+
"message": message
|
|
36
|
+
]
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Create an unavailable error response from the availability check
|
|
41
|
+
private func unavailableResponse(from availability: (available: Bool, error: AppleIntelligenceError?)) -> [String: Any] {
|
|
42
|
+
return [
|
|
43
|
+
"success": false,
|
|
44
|
+
"error": availability.error?.asDictionary ?? [
|
|
45
|
+
"code": AppleIntelligenceErrorCode.unavailable.rawValue,
|
|
46
|
+
"message": "Apple Intelligence is not available on this device."
|
|
47
|
+
]
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
|
|
24
51
|
// MARK: - Plugin Methods
|
|
25
52
|
|
|
26
53
|
/// Generate structured JSON output using Apple Intelligence
|
|
@@ -28,185 +55,160 @@ public class AppleIntelligencePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
28
55
|
@objc func generate(_ call: CAPPluginCall) {
|
|
29
56
|
// Validate input
|
|
30
57
|
guard let messagesArray = call.getArray("messages") as? [[String: String]] else {
|
|
31
|
-
call.resolve(
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
"message": "Invalid or missing 'messages' array. Expected array of { role: string, content: string }."
|
|
36
|
-
]
|
|
37
|
-
])
|
|
58
|
+
call.resolve(errorResponse(
|
|
59
|
+
code: .nativeError,
|
|
60
|
+
message: "Invalid or missing 'messages' array. Expected array of { role: string, content: string }."
|
|
61
|
+
))
|
|
38
62
|
return
|
|
39
63
|
}
|
|
40
64
|
|
|
41
65
|
guard let responseFormat = call.getObject("response_format") else {
|
|
42
|
-
call.resolve(
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
"message": "Missing 'response_format' object."
|
|
47
|
-
]
|
|
48
|
-
])
|
|
66
|
+
call.resolve(errorResponse(
|
|
67
|
+
code: .nativeError,
|
|
68
|
+
message: "Missing 'response_format' object."
|
|
69
|
+
))
|
|
49
70
|
return
|
|
50
71
|
}
|
|
51
72
|
|
|
52
73
|
guard let formatType = responseFormat["type"] as? String, formatType == "json_schema" else {
|
|
53
|
-
call.resolve(
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
"message": "response_format.type must be 'json_schema'."
|
|
58
|
-
]
|
|
59
|
-
])
|
|
74
|
+
call.resolve(errorResponse(
|
|
75
|
+
code: .nativeError,
|
|
76
|
+
message: "response_format.type must be 'json_schema'."
|
|
77
|
+
))
|
|
60
78
|
return
|
|
61
79
|
}
|
|
62
80
|
|
|
63
81
|
guard let schema = responseFormat["schema"] as? [String: Any] else {
|
|
64
|
-
call.resolve(
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
|
|
68
|
-
"message": "Missing or invalid 'schema' in response_format."
|
|
69
|
-
]
|
|
70
|
-
])
|
|
82
|
+
call.resolve(errorResponse(
|
|
83
|
+
code: .nativeError,
|
|
84
|
+
message: "Missing or invalid 'schema' in response_format."
|
|
85
|
+
))
|
|
71
86
|
return
|
|
72
87
|
}
|
|
73
88
|
|
|
74
|
-
// Check availability
|
|
89
|
+
// Check availability (handles both iOS version and Apple Intelligence feature availability)
|
|
75
90
|
let availability = implementation.checkAvailability()
|
|
76
|
-
|
|
77
|
-
call.resolve(
|
|
78
|
-
"success": false,
|
|
79
|
-
"error": availability.error?.asDictionary ?? [
|
|
80
|
-
"code": "UNAVAILABLE",
|
|
81
|
-
"message": "Apple Intelligence is not available on this device."
|
|
82
|
-
]
|
|
83
|
-
])
|
|
91
|
+
guard availability.available else {
|
|
92
|
+
call.resolve(unavailableResponse(from: availability))
|
|
84
93
|
return
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
// Execute generation asynchronously
|
|
88
97
|
if #available(iOS 26, *) {
|
|
89
|
-
Task {
|
|
90
|
-
let
|
|
98
|
+
Task { [weak self] in
|
|
99
|
+
guard let self = self else { return }
|
|
100
|
+
|
|
101
|
+
let result = await self.implementation.generateForBridge(
|
|
91
102
|
messages: messagesArray,
|
|
92
103
|
schema: schema
|
|
93
104
|
)
|
|
94
105
|
|
|
95
|
-
|
|
96
|
-
await MainActor.run {
|
|
97
|
-
call.resolve(result)
|
|
98
|
-
}
|
|
106
|
+
call.resolve(result)
|
|
99
107
|
}
|
|
100
|
-
} else {
|
|
101
|
-
call.resolve([
|
|
102
|
-
"success": false,
|
|
103
|
-
"error": [
|
|
104
|
-
"code": "UNAVAILABLE",
|
|
105
|
-
"message": "Apple Intelligence requires iOS 26 or later."
|
|
106
|
-
]
|
|
107
|
-
])
|
|
108
108
|
}
|
|
109
|
+
// Note: No else needed - checkAvailability already handles iOS version check
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
/// Generate plain text output using Apple Intelligence
|
|
112
113
|
@objc func generateText(_ call: CAPPluginCall) {
|
|
113
114
|
guard let messagesArray = call.getArray("messages") as? [[String: String]] else {
|
|
114
|
-
call.resolve(
|
|
115
|
-
|
|
116
|
-
"
|
|
117
|
-
|
|
118
|
-
"message": "Invalid or missing 'messages' array."
|
|
119
|
-
]
|
|
120
|
-
])
|
|
115
|
+
call.resolve(errorResponse(
|
|
116
|
+
code: .nativeError,
|
|
117
|
+
message: "Invalid or missing 'messages' array."
|
|
118
|
+
))
|
|
121
119
|
return
|
|
122
120
|
}
|
|
123
121
|
|
|
124
122
|
// Check availability
|
|
125
123
|
let availability = implementation.checkAvailability()
|
|
126
|
-
|
|
127
|
-
call.resolve(
|
|
128
|
-
"success": false,
|
|
129
|
-
"error": availability.error?.asDictionary ?? [
|
|
130
|
-
"code": "UNAVAILABLE",
|
|
131
|
-
"message": "Apple Intelligence is not available on this device."
|
|
132
|
-
]
|
|
133
|
-
])
|
|
124
|
+
guard availability.available else {
|
|
125
|
+
call.resolve(unavailableResponse(from: availability))
|
|
134
126
|
return
|
|
135
127
|
}
|
|
136
128
|
|
|
137
129
|
if #available(iOS 26, *) {
|
|
138
|
-
Task {
|
|
139
|
-
let
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
130
|
+
Task { [weak self] in
|
|
131
|
+
guard let self = self else { return }
|
|
132
|
+
|
|
133
|
+
let result = await self.implementation.generateTextForBridge(messages: messagesArray)
|
|
134
|
+
call.resolve(result)
|
|
143
135
|
}
|
|
144
|
-
} else {
|
|
145
|
-
call.resolve([
|
|
146
|
-
"success": false,
|
|
147
|
-
"error": [
|
|
148
|
-
"code": "UNAVAILABLE",
|
|
149
|
-
"message": "Apple Intelligence requires iOS 26 or later."
|
|
150
|
-
]
|
|
151
|
-
])
|
|
152
136
|
}
|
|
153
137
|
}
|
|
154
138
|
|
|
155
139
|
/// Generate plain text output with specific language
|
|
156
140
|
@objc func generateTextWithLanguage(_ call: CAPPluginCall) {
|
|
157
141
|
guard let messagesArray = call.getArray("messages") as? [[String: String]] else {
|
|
158
|
-
call.resolve(
|
|
159
|
-
|
|
160
|
-
"
|
|
161
|
-
|
|
162
|
-
"message": "Invalid or missing 'messages' array."
|
|
163
|
-
]
|
|
164
|
-
])
|
|
142
|
+
call.resolve(errorResponse(
|
|
143
|
+
code: .nativeError,
|
|
144
|
+
message: "Invalid or missing 'messages' array."
|
|
145
|
+
))
|
|
165
146
|
return
|
|
166
147
|
}
|
|
167
148
|
|
|
168
149
|
guard let language = call.getString("language") else {
|
|
169
|
-
call.resolve(
|
|
170
|
-
|
|
171
|
-
"
|
|
172
|
-
|
|
173
|
-
"message": "Missing 'language' string."
|
|
174
|
-
]
|
|
175
|
-
])
|
|
150
|
+
call.resolve(errorResponse(
|
|
151
|
+
code: .nativeError,
|
|
152
|
+
message: "Missing 'language' string."
|
|
153
|
+
))
|
|
176
154
|
return
|
|
177
155
|
}
|
|
178
156
|
|
|
179
157
|
// Check availability
|
|
180
158
|
let availability = implementation.checkAvailability()
|
|
181
|
-
|
|
182
|
-
call.resolve(
|
|
183
|
-
"success": false,
|
|
184
|
-
"error": availability.error?.asDictionary ?? [
|
|
185
|
-
"code": "UNAVAILABLE",
|
|
186
|
-
"message": "Apple Intelligence is not available on this device."
|
|
187
|
-
]
|
|
188
|
-
])
|
|
159
|
+
guard availability.available else {
|
|
160
|
+
call.resolve(unavailableResponse(from: availability))
|
|
189
161
|
return
|
|
190
162
|
}
|
|
191
163
|
|
|
192
164
|
if #available(iOS 26, *) {
|
|
193
|
-
Task {
|
|
194
|
-
let
|
|
165
|
+
Task { [weak self] in
|
|
166
|
+
guard let self = self else { return }
|
|
167
|
+
|
|
168
|
+
let result = await self.implementation.generateTextWithLanguageForBridge(
|
|
195
169
|
messages: messagesArray,
|
|
196
170
|
language: language
|
|
197
171
|
)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
172
|
+
call.resolve(result)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// Generate image using Apple Intelligence
|
|
178
|
+
@objc func generateImage(_ call: CAPPluginCall) {
|
|
179
|
+
guard let prompt = call.getString("prompt") else {
|
|
180
|
+
call.resolve(errorResponse(
|
|
181
|
+
code: .nativeError,
|
|
182
|
+
message: "Missing 'prompt' string."
|
|
183
|
+
))
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let style = call.getString("style")
|
|
188
|
+
let count = call.getInt("count") ?? 1
|
|
189
|
+
let compressionQuality = call.getDouble("compressionQuality")
|
|
190
|
+
let sourceImage = call.getString("sourceImage")
|
|
191
|
+
|
|
192
|
+
// Check availability
|
|
193
|
+
let availability = implementation.checkAvailability()
|
|
194
|
+
guard availability.available else {
|
|
195
|
+
call.resolve(unavailableResponse(from: availability))
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if #available(iOS 26, *) {
|
|
200
|
+
Task { [weak self] in
|
|
201
|
+
guard let self = self else { return }
|
|
202
|
+
|
|
203
|
+
let result = await self.implementation.generateImageForBridge(
|
|
204
|
+
prompt: prompt,
|
|
205
|
+
style: style,
|
|
206
|
+
count: count,
|
|
207
|
+
sourceImageBase64: sourceImage,
|
|
208
|
+
compressionQuality: compressionQuality.map { CGFloat($0) }
|
|
209
|
+
)
|
|
210
|
+
call.resolve(result)
|
|
201
211
|
}
|
|
202
|
-
} else {
|
|
203
|
-
call.resolve([
|
|
204
|
-
"success": false,
|
|
205
|
-
"error": [
|
|
206
|
-
"code": "UNAVAILABLE",
|
|
207
|
-
"message": "Apple Intelligence requires iOS 26 or later."
|
|
208
|
-
]
|
|
209
|
-
])
|
|
210
212
|
}
|
|
211
213
|
}
|
|
212
214
|
|
|
@@ -222,10 +224,18 @@ public class AppleIntelligencePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
222
224
|
call.resolve([
|
|
223
225
|
"available": false,
|
|
224
226
|
"error": availability.error?.asDictionary ?? [
|
|
225
|
-
"code":
|
|
227
|
+
"code": AppleIntelligenceErrorCode.unavailable.rawValue,
|
|
226
228
|
"message": "Apple Intelligence is not available on this device."
|
|
227
229
|
]
|
|
228
230
|
])
|
|
229
231
|
}
|
|
230
232
|
}
|
|
233
|
+
|
|
234
|
+
/// Cancel all active generation tasks
|
|
235
|
+
@objc func cancelAllTasks(_ call: CAPPluginCall) {
|
|
236
|
+
implementation.cancelAllTasks()
|
|
237
|
+
call.resolve([
|
|
238
|
+
"success": true
|
|
239
|
+
])
|
|
240
|
+
}
|
|
231
241
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "capacitor-apple-intelligence",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Capacitor v8 plugin for Apple Intelligence with schema-constrained JSON generation for structured AI responses",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -77,4 +77,4 @@
|
|
|
77
77
|
"src": "ios"
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
-
}
|
|
80
|
+
}
|