@ng-native/expo 0.0.1 → 0.1.0
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/LICENSE +21 -0
- package/README.md +413 -1
- package/dist/app-info.d.ts +58 -0
- package/dist/app-info.js +71 -0
- package/dist/app-info.js.map +1 -0
- package/dist/apple-sign-in-button.d.ts +16 -0
- package/dist/apple-sign-in-button.js +55 -0
- package/dist/apple-sign-in-button.js.map +1 -0
- package/dist/apple-sign-in.d.ts +68 -0
- package/dist/apple-sign-in.js +120 -0
- package/dist/apple-sign-in.js.map +1 -0
- package/dist/assets.d.ts +24 -0
- package/dist/assets.js +47 -0
- package/dist/assets.js.map +1 -0
- package/dist/background-task.d.ts +66 -0
- package/dist/background-task.js +82 -0
- package/dist/background-task.js.map +1 -0
- package/dist/battery.d.ts +46 -0
- package/dist/battery.js +76 -0
- package/dist/battery.js.map +1 -0
- package/dist/biometrics.d.ts +45 -0
- package/dist/biometrics.js +53 -0
- package/dist/biometrics.js.map +1 -0
- package/dist/brightness.d.ts +38 -0
- package/dist/brightness.js +81 -0
- package/dist/brightness.js.map +1 -0
- package/dist/browser.d.ts +42 -0
- package/dist/browser.js +42 -0
- package/dist/browser.js.map +1 -0
- package/dist/camera.d.ts +48 -0
- package/dist/camera.js +59 -0
- package/dist/camera.js.map +1 -0
- package/dist/clipboard.d.ts +43 -0
- package/dist/clipboard.js +60 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/community-views.d.ts +14 -0
- package/dist/community-views.js +76 -0
- package/dist/community-views.js.map +1 -0
- package/dist/crypto.d.ts +56 -0
- package/dist/crypto.js +84 -0
- package/dist/crypto.js.map +1 -0
- package/dist/database.d.ts +47 -0
- package/dist/database.js +108 -0
- package/dist/database.js.map +1 -0
- package/dist/document-picker.d.ts +34 -0
- package/dist/document-picker.js +40 -0
- package/dist/document-picker.js.map +1 -0
- package/dist/dom-component.d.ts +87 -0
- package/dist/dom-component.js +139 -0
- package/dist/dom-component.js.map +1 -0
- package/dist/expo-image.d.ts +33 -0
- package/dist/expo-image.js +57 -0
- package/dist/expo-image.js.map +1 -0
- package/dist/expo-ui-components.d.ts +327 -0
- package/dist/expo-ui-components.js +707 -0
- package/dist/expo-ui-components.js.map +1 -0
- package/dist/expo-ui.d.ts +46 -0
- package/dist/expo-ui.js +128 -0
- package/dist/expo-ui.js.map +1 -0
- package/dist/file-system.d.ts +63 -0
- package/dist/file-system.js +67 -0
- package/dist/file-system.js.map +1 -0
- package/dist/fonts.d.ts +90 -0
- package/dist/fonts.js +118 -0
- package/dist/fonts.js.map +1 -0
- package/dist/glass.d.ts +33 -0
- package/dist/glass.js +72 -0
- package/dist/glass.js.map +1 -0
- package/dist/haptics.d.ts +49 -0
- package/dist/haptics.js +64 -0
- package/dist/haptics.js.map +1 -0
- package/dist/image-editor.d.ts +62 -0
- package/dist/image-editor.js +101 -0
- package/dist/image-editor.js.map +1 -0
- package/dist/image-picker.d.ts +49 -0
- package/dist/image-picker.js +58 -0
- package/dist/image-picker.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/keep-awake.d.ts +39 -0
- package/dist/keep-awake.js +80 -0
- package/dist/keep-awake.js.map +1 -0
- package/dist/language-model.d.ts +129 -0
- package/dist/language-model.js +225 -0
- package/dist/language-model.js.map +1 -0
- package/dist/locale.d.ts +51 -0
- package/dist/locale.js +78 -0
- package/dist/locale.js.map +1 -0
- package/dist/location.d.ts +68 -0
- package/dist/location.js +83 -0
- package/dist/location.js.map +1 -0
- package/dist/map-view.d.ts +203 -0
- package/dist/map-view.js +184 -0
- package/dist/map-view.js.map +1 -0
- package/dist/media-library.d.ts +103 -0
- package/dist/media-library.js +160 -0
- package/dist/media-library.js.map +1 -0
- package/dist/native-state.d.ts +22 -0
- package/dist/native-state.js +43 -0
- package/dist/native-state.js.map +1 -0
- package/dist/native.d.ts +42 -0
- package/dist/native.js +73 -0
- package/dist/native.js.map +1 -0
- package/dist/network.d.ts +35 -0
- package/dist/network.js +60 -0
- package/dist/network.js.map +1 -0
- package/dist/notifications.d.ts +175 -0
- package/dist/notifications.js +296 -0
- package/dist/notifications.js.map +1 -0
- package/dist/observed.d.ts +37 -0
- package/dist/observed.js +55 -0
- package/dist/observed.js.map +1 -0
- package/dist/orientation.d.ts +35 -0
- package/dist/orientation.js +78 -0
- package/dist/orientation.js.map +1 -0
- package/dist/permissions.d.ts +54 -0
- package/dist/permissions.js +72 -0
- package/dist/permissions.js.map +1 -0
- package/dist/player.d.ts +92 -0
- package/dist/player.js +141 -0
- package/dist/player.js.map +1 -0
- package/dist/register-expo-view.d.ts +55 -0
- package/dist/register-expo-view.js +110 -0
- package/dist/register-expo-view.js.map +1 -0
- package/dist/screen-capture.d.ts +58 -0
- package/dist/screen-capture.js +78 -0
- package/dist/screen-capture.js.map +1 -0
- package/dist/segmented-control.d.ts +32 -0
- package/dist/segmented-control.js +55 -0
- package/dist/segmented-control.js.map +1 -0
- package/dist/sensors.d.ts +85 -0
- package/dist/sensors.js +115 -0
- package/dist/sensors.js.map +1 -0
- package/dist/splash-screen.d.ts +73 -0
- package/dist/splash-screen.js +115 -0
- package/dist/splash-screen.js.map +1 -0
- package/dist/store-review.d.ts +43 -0
- package/dist/store-review.js +52 -0
- package/dist/store-review.js.map +1 -0
- package/dist/store.d.ts +108 -0
- package/dist/store.js +247 -0
- package/dist/store.js.map +1 -0
- package/dist/symbol.d.ts +36 -0
- package/dist/symbol.js +69 -0
- package/dist/symbol.js.map +1 -0
- package/dist/tracking.d.ts +45 -0
- package/dist/tracking.js +54 -0
- package/dist/tracking.js.map +1 -0
- package/dist/transforms.d.ts +2 -0
- package/dist/transforms.js +13 -0
- package/dist/transforms.js.map +1 -0
- package/dist/updates.d.ts +61 -0
- package/dist/updates.js +97 -0
- package/dist/updates.js.map +1 -0
- package/package.json +397 -3
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ImagePicker`, bound to `expo-image-picker`.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* private readonly picker = inject(ImagePicker);
|
|
6
|
+
* const [photo] = await this.picker.pick({ mediaTypes: ['images'] });
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* The options are Expo's own, passed through unchanged. What this adds is the two things every
|
|
10
|
+
* call site otherwise writes: a cancelled picker answers with no assets rather than a result to
|
|
11
|
+
* unwrap, and `capture()` asks for the camera before opening it, where Expo's would reject.
|
|
12
|
+
*/
|
|
13
|
+
import { InjectionToken, Service, inject } from '@angular/core';
|
|
14
|
+
import { expoModule } from "./native.js";
|
|
15
|
+
import { Permission } from "./permissions.js";
|
|
16
|
+
import * as i0 from "@angular/core";
|
|
17
|
+
/** What a permission is without the module: refused, and never to be asked about. */
|
|
18
|
+
const UNAVAILABLE = async () => ({
|
|
19
|
+
status: 'denied',
|
|
20
|
+
granted: false,
|
|
21
|
+
canAskAgain: false,
|
|
22
|
+
});
|
|
23
|
+
export class ImagePicker {
|
|
24
|
+
/** Overridden in a test to pick photos without a photo library. */
|
|
25
|
+
static SOURCE = new InjectionToken('angular-native.imagePickerSource', {
|
|
26
|
+
factory: () => expoModule('expo-image-picker', () => require('expo-image-picker')) ??
|
|
27
|
+
null,
|
|
28
|
+
});
|
|
29
|
+
native = inject(ImagePicker.SOURCE);
|
|
30
|
+
/**
|
|
31
|
+
* The photo library. The system picker needs no permission to pick from, so `pick()` never
|
|
32
|
+
* asks; this is for an app that reads the library itself.
|
|
33
|
+
*/
|
|
34
|
+
libraryPermission = Permission.of(this.native?.getMediaLibraryPermissionsAsync ?? UNAVAILABLE, this.native?.requestMediaLibraryPermissionsAsync ?? UNAVAILABLE);
|
|
35
|
+
/** The camera, which `capture()` asks for itself. */
|
|
36
|
+
cameraPermission = Permission.of(this.native?.getCameraPermissionsAsync ?? UNAVAILABLE, this.native?.requestCameraPermissionsAsync ?? UNAVAILABLE);
|
|
37
|
+
/** Pick from the photo library. Empty if they cancelled. */
|
|
38
|
+
async pick(options) {
|
|
39
|
+
if (!this.native)
|
|
40
|
+
return [];
|
|
41
|
+
return assetsOf(await this.native.launchImageLibraryAsync(options));
|
|
42
|
+
}
|
|
43
|
+
/** Take a photo or video with the system camera. Empty if they cancelled or refused it. */
|
|
44
|
+
async capture(options) {
|
|
45
|
+
if (!this.native || !(await this.cameraPermission.ensure()))
|
|
46
|
+
return [];
|
|
47
|
+
return assetsOf(await this.native.launchCameraAsync(options));
|
|
48
|
+
}
|
|
49
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: ImagePicker, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
50
|
+
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.2.0", ngImport: i0, type: ImagePicker });
|
|
51
|
+
}
|
|
52
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: ImagePicker, decorators: [{
|
|
53
|
+
type: Service
|
|
54
|
+
}] });
|
|
55
|
+
function assetsOf(result) {
|
|
56
|
+
return result.canceled ? [] : (result.assets ?? []);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=image-picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-picker.js","sourceRoot":"","sources":["../src/image-picker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAA2B,MAAM,kBAAkB,CAAC;;AAoBvE,qFAAqF;AACrF,MAAM,WAAW,GAAG,KAAK,IAAiC,EAAE,CAAC,CAAC;IAC5D,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,KAAK;IACd,WAAW,EAAE,KAAK;CACnB,CAAC,CAAC;AAGH,MAAM,OAAO,WAAW;IACtB,mEAAmE;IACnE,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,kCAAkC,EAClC;QACE,OAAO,EAAE,GAAG,EAAE,CACZ,UAAU,CAAC,mBAAmB,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,mBAAmB,CAAsB,CAAC;YACxF,IAAI;KACP,CACF,CAAC;IAEe,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAErD;;;OAGG;IACM,iBAAiB,GAAG,UAAU,CAAC,EAAE,CACxC,IAAI,CAAC,MAAM,EAAE,+BAA+B,IAAI,WAAW,EAC3D,IAAI,CAAC,MAAM,EAAE,mCAAmC,IAAI,WAAW,CAChE,CAAC;IAEF,qDAAqD;IAC5C,gBAAgB,GAAG,UAAU,CAAC,EAAE,CACvC,IAAI,CAAC,MAAM,EAAE,yBAAyB,IAAI,WAAW,EACrD,IAAI,CAAC,MAAM,EAAE,6BAA6B,IAAI,WAAW,CAC1D,CAAC;IAEF,4DAA4D;IAC5D,KAAK,CAAC,IAAI,CAAC,OAAuB;QAChC,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,CAAC;QAC5B,OAAO,QAAQ,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,uBAAuB,CAAC,OAAO,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,2FAA2F;IAC3F,KAAK,CAAC,OAAO,CAAC,OAAuB;QACnC,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE,CAAC;YAAE,OAAO,EAAE,CAAC;QACvE,OAAO,QAAQ,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC;IAChE,CAAC;uGAtCU,WAAW;wGAAX,WAAW;;2FAAX,WAAW;kBADvB,OAAO;;AA0CR,SAAS,QAAQ,CAAC,MAAoB;IACpC,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;AACtD,CAAC","sourcesContent":["/**\n * `ImagePicker`, bound to `expo-image-picker`.\n *\n * ```ts\n * private readonly picker = inject(ImagePicker);\n * const [photo] = await this.picker.pick({ mediaTypes: ['images'] });\n * ```\n *\n * The options are Expo's own, passed through unchanged. What this adds is the two things every\n * call site otherwise writes: a cancelled picker answers with no assets rather than a result to\n * unwrap, and `capture()` asks for the camera before opening it, where Expo's would reject.\n */\nimport { InjectionToken, Service, inject } from '@angular/core';\nimport { expoModule } from './native.ts';\nimport { Permission, type PermissionResponse } from './permissions.ts';\n\nexport type PickerOptions = import('expo-image-picker').ImagePickerOptions;\nexport type PickedAsset = import('expo-image-picker').ImagePickerAsset;\n\n/** The slice of `expo-image-picker` this needs. */\nexport interface NativeImagePicker {\n launchImageLibraryAsync(options?: PickerOptions): Promise<PickerResult>;\n launchCameraAsync(options?: PickerOptions): Promise<PickerResult>;\n getMediaLibraryPermissionsAsync(): Promise<PermissionResponse>;\n requestMediaLibraryPermissionsAsync(): Promise<PermissionResponse>;\n getCameraPermissionsAsync(): Promise<PermissionResponse>;\n requestCameraPermissionsAsync(): Promise<PermissionResponse>;\n}\n\ninterface PickerResult {\n readonly canceled: boolean;\n readonly assets: readonly PickedAsset[] | null;\n}\n\n/** What a permission is without the module: refused, and never to be asked about. */\nconst UNAVAILABLE = async (): Promise<PermissionResponse> => ({\n status: 'denied',\n granted: false,\n canAskAgain: false,\n});\n\n@Service()\nexport class ImagePicker {\n /** Overridden in a test to pick photos without a photo library. */\n static readonly SOURCE = new InjectionToken<NativeImagePicker | null>(\n 'angular-native.imagePickerSource',\n {\n factory: () =>\n expoModule('expo-image-picker', () => require('expo-image-picker') as NativeImagePicker) ??\n null,\n },\n );\n\n private readonly native = inject(ImagePicker.SOURCE);\n\n /**\n * The photo library. The system picker needs no permission to pick from, so `pick()` never\n * asks; this is for an app that reads the library itself.\n */\n readonly libraryPermission = Permission.of(\n this.native?.getMediaLibraryPermissionsAsync ?? UNAVAILABLE,\n this.native?.requestMediaLibraryPermissionsAsync ?? UNAVAILABLE,\n );\n\n /** The camera, which `capture()` asks for itself. */\n readonly cameraPermission = Permission.of(\n this.native?.getCameraPermissionsAsync ?? UNAVAILABLE,\n this.native?.requestCameraPermissionsAsync ?? UNAVAILABLE,\n );\n\n /** Pick from the photo library. Empty if they cancelled. */\n async pick(options?: PickerOptions): Promise<readonly PickedAsset[]> {\n if (!this.native) return [];\n return assetsOf(await this.native.launchImageLibraryAsync(options));\n }\n\n /** Take a photo or video with the system camera. Empty if they cancelled or refused it. */\n async capture(options?: PickerOptions): Promise<readonly PickedAsset[]> {\n if (!this.native || !(await this.cameraPermission.ensure())) return [];\n return assetsOf(await this.native.launchCameraAsync(options));\n }\n}\n\nfunction assetsOf(result: PickerResult): readonly PickedAsset[] {\n return result.canceled ? [] : (result.assets ?? []);\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the package exports without naming a module.
|
|
3
|
+
*
|
|
4
|
+
* Everything with an Expo module behind it has its own entry point - `@ng-native/expo/
|
|
5
|
+
* battery.ts` and friends - so that importing haptics does not make an app install the video
|
|
6
|
+
* player. What is left here is the shared machinery: the permission shape every module spells
|
|
7
|
+
* identically, the signal-from-a-listener those services are built out of, and the view names.
|
|
8
|
+
*/
|
|
9
|
+
export { observed, type Observed } from './observed.ts';
|
|
10
|
+
export { Permission, type PermissionApi, type PermissionResponse } from './permissions.ts';
|
|
11
|
+
export { MissingModuleError, expoModule, optional, type ModulePlatform } from './native.ts';
|
|
12
|
+
export { NATIVE_VIEWS, registerNativeViews } from './community-views.ts';
|
|
13
|
+
export { registerExpoUiViews } from './expo-ui.ts';
|
|
14
|
+
export { nativeState, type NativeState } from './native-state.ts';
|
|
15
|
+
export { EXPO_VIEWS, expoViewName, registerExpoView, registerExpoViews, type ExpoViewOptions, } from './register-expo-view.ts';
|
|
16
|
+
export { UiButton, UiDatePicker, UiDivider, UiHost, UiImage, UiMenu, UiPicker, UiColorPicker, type UiColorChangeEvent, UiForm, UiGauge, UiHStack, UiLabeledContent, UiList, UiProgress, UiSection, UiSpacer, UiStepper, type UiStepperChangeEvent, UiTextField, type UiTextFieldChangeEvent, UiToggle, type UiToggleChangeEvent, UiSlider, type UiSliderChangeEvent, UiSlot, UiSwipeActions, UiText, UiVStack, type UiDateChangeEvent, type UiPickerOption, type UiModifier, } from './expo-ui-components.ts';
|
|
17
|
+
export { ExpoImage, type ExpoImageContentFit, type ExpoImageSource } from './expo-image.ts';
|
|
18
|
+
export { ExpoGlass, ExpoGlassContainer, liquidGlassAvailable, type GlassEffectStyleConfig, type GlassStyle, } from './glass.ts';
|
|
19
|
+
export { ExpoSymbol, type SymbolAnimationSpec, type SymbolResizeMode, type SymbolScale, type SymbolType, type SymbolWeight, } from './symbol.ts';
|
|
20
|
+
export { SegmentedControl, type SegmentedControlChangeEvent, type SegmentedControlFont, } from './segmented-control.ts';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the package exports without naming a module.
|
|
3
|
+
*
|
|
4
|
+
* Everything with an Expo module behind it has its own entry point - `@ng-native/expo/
|
|
5
|
+
* battery.ts` and friends - so that importing haptics does not make an app install the video
|
|
6
|
+
* player. What is left here is the shared machinery: the permission shape every module spells
|
|
7
|
+
* identically, the signal-from-a-listener those services are built out of, and the view names.
|
|
8
|
+
*/
|
|
9
|
+
export { observed } from "./observed.js";
|
|
10
|
+
export { Permission } from "./permissions.js";
|
|
11
|
+
export { MissingModuleError, expoModule, optional } from "./native.js";
|
|
12
|
+
export { NATIVE_VIEWS, registerNativeViews } from "./community-views.js";
|
|
13
|
+
export { registerExpoUiViews } from "./expo-ui.js";
|
|
14
|
+
export { nativeState } from "./native-state.js";
|
|
15
|
+
export { EXPO_VIEWS, expoViewName, registerExpoView, registerExpoViews, } from "./register-expo-view.js";
|
|
16
|
+
export { UiButton, UiDatePicker, UiDivider, UiHost, UiImage, UiMenu, UiPicker, UiColorPicker, UiForm, UiGauge, UiHStack, UiLabeledContent, UiList, UiProgress, UiSection, UiSpacer, UiStepper, UiTextField, UiToggle, UiSlider, UiSlot, UiSwipeActions, UiText, UiVStack, } from "./expo-ui-components.js";
|
|
17
|
+
export { ExpoImage } from "./expo-image.js";
|
|
18
|
+
export { ExpoGlass, ExpoGlassContainer, liquidGlassAvailable, } from "./glass.js";
|
|
19
|
+
export { ExpoSymbol, } from "./symbol.js";
|
|
20
|
+
export { SegmentedControl, } from "./segmented-control.js";
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,QAAQ,EAAiB,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,UAAU,EAA+C,MAAM,kBAAkB,CAAC;AAC3F,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,QAAQ,EAAuB,MAAM,aAAa,CAAC;AAC5F,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,WAAW,EAAoB,MAAM,mBAAmB,CAAC;AAClE,OAAO,EACL,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,GAElB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,MAAM,EACN,OAAO,EACP,MAAM,EACN,QAAQ,EACR,aAAa,EAEb,MAAM,EACN,OAAO,EACP,QAAQ,EACR,gBAAgB,EAChB,MAAM,EACN,UAAU,EACV,SAAS,EACT,QAAQ,EACR,SAAS,EAET,WAAW,EAEX,QAAQ,EAER,QAAQ,EAER,MAAM,EACN,cAAc,EACd,MAAM,EACN,QAAQ,GAIT,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,SAAS,EAAkD,MAAM,iBAAiB,CAAC;AAC5F,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,oBAAoB,GAGrB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,UAAU,GAMX,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gBAAgB,GAGjB,MAAM,wBAAwB,CAAC","sourcesContent":["/**\n * What the package exports without naming a module.\n *\n * Everything with an Expo module behind it has its own entry point - `@ng-native/expo/\n * battery.ts` and friends - so that importing haptics does not make an app install the video\n * player. What is left here is the shared machinery: the permission shape every module spells\n * identically, the signal-from-a-listener those services are built out of, and the view names.\n */\nexport { observed, type Observed } from './observed.ts';\nexport { Permission, type PermissionApi, type PermissionResponse } from './permissions.ts';\nexport { MissingModuleError, expoModule, optional, type ModulePlatform } from './native.ts';\nexport { NATIVE_VIEWS, registerNativeViews } from './community-views.ts';\nexport { registerExpoUiViews } from './expo-ui.ts';\nexport { nativeState, type NativeState } from './native-state.ts';\nexport {\n EXPO_VIEWS,\n expoViewName,\n registerExpoView,\n registerExpoViews,\n type ExpoViewOptions,\n} from './register-expo-view.ts';\nexport {\n UiButton,\n UiDatePicker,\n UiDivider,\n UiHost,\n UiImage,\n UiMenu,\n UiPicker,\n UiColorPicker,\n type UiColorChangeEvent,\n UiForm,\n UiGauge,\n UiHStack,\n UiLabeledContent,\n UiList,\n UiProgress,\n UiSection,\n UiSpacer,\n UiStepper,\n type UiStepperChangeEvent,\n UiTextField,\n type UiTextFieldChangeEvent,\n UiToggle,\n type UiToggleChangeEvent,\n UiSlider,\n type UiSliderChangeEvent,\n UiSlot,\n UiSwipeActions,\n UiText,\n UiVStack,\n type UiDateChangeEvent,\n type UiPickerOption,\n type UiModifier,\n} from './expo-ui-components.ts';\nexport { ExpoImage, type ExpoImageContentFit, type ExpoImageSource } from './expo-image.ts';\nexport {\n ExpoGlass,\n ExpoGlassContainer,\n liquidGlassAvailable,\n type GlassEffectStyleConfig,\n type GlassStyle,\n} from './glass.ts';\nexport {\n ExpoSymbol,\n type SymbolAnimationSpec,\n type SymbolResizeMode,\n type SymbolScale,\n type SymbolType,\n type SymbolWeight,\n} from './symbol.ts';\nexport {\n SegmentedControl,\n type SegmentedControlChangeEvent,\n type SegmentedControlFont,\n} from './segmented-control.ts';\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `KeepAwake`, bound to `expo-keep-awake`.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* inject(DestroyRef).onDestroy(inject(KeepAwake).hold('recording'));
|
|
6
|
+
* ```
|
|
7
|
+
*
|
|
8
|
+
* Never a bare activate: a screen that holds the display on and never releases it is a phone that
|
|
9
|
+
* never sleeps, which the user experiences as a battery fault and never attributes to the app.
|
|
10
|
+
*
|
|
11
|
+
* Tags are how two screens can each hold it without either releasing the other's.
|
|
12
|
+
*/
|
|
13
|
+
import { InjectionToken, type Signal } from '@angular/core';
|
|
14
|
+
import * as i0 from "@angular/core";
|
|
15
|
+
export interface NativeKeepAwake {
|
|
16
|
+
activate(tag: string): Promise<void>;
|
|
17
|
+
deactivate(tag: string): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export declare class KeepAwake {
|
|
20
|
+
/** Overridden in a test to record a hold without keeping a screen on. */
|
|
21
|
+
static readonly SOURCE: InjectionToken<NativeKeepAwake | null>;
|
|
22
|
+
private readonly native;
|
|
23
|
+
private readonly held;
|
|
24
|
+
/** The tags currently holding the screen on, so a debug screen can say which. */
|
|
25
|
+
readonly holders: Signal<ReadonlySet<string>>;
|
|
26
|
+
/** Whether anything currently holds the screen on. */
|
|
27
|
+
readonly active: Signal<boolean>;
|
|
28
|
+
/**
|
|
29
|
+
* Hold the screen on. Returns the function that releases it, ready for `DestroyRef.onDestroy`.
|
|
30
|
+
*
|
|
31
|
+
* Never a bare `activate()`, because the release is the half that gets forgotten and the half
|
|
32
|
+
* that matters.
|
|
33
|
+
*/
|
|
34
|
+
hold(tag?: string): () => void;
|
|
35
|
+
/** How many holds each tag has that are not yet released. */
|
|
36
|
+
private readonly counts;
|
|
37
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<KeepAwake, never>;
|
|
38
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
39
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `KeepAwake`, bound to `expo-keep-awake`.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* inject(DestroyRef).onDestroy(inject(KeepAwake).hold('recording'));
|
|
6
|
+
* ```
|
|
7
|
+
*
|
|
8
|
+
* Never a bare activate: a screen that holds the display on and never releases it is a phone that
|
|
9
|
+
* never sleeps, which the user experiences as a battery fault and never attributes to the app.
|
|
10
|
+
*
|
|
11
|
+
* Tags are how two screens can each hold it without either releasing the other's.
|
|
12
|
+
*/
|
|
13
|
+
import { InjectionToken, Service, computed, inject, signal } from '@angular/core';
|
|
14
|
+
import { expoModule } from "./native.js";
|
|
15
|
+
import * as i0 from "@angular/core";
|
|
16
|
+
const DEFAULT_TAG = 'angular-native';
|
|
17
|
+
export class KeepAwake {
|
|
18
|
+
/** Overridden in a test to record a hold without keeping a screen on. */
|
|
19
|
+
static SOURCE = new InjectionToken('angular-native.keepAwakeSource', {
|
|
20
|
+
factory: () => {
|
|
21
|
+
const expo = expoModule('expo-keep-awake', () => require('expo-keep-awake'));
|
|
22
|
+
if (!expo)
|
|
23
|
+
return null;
|
|
24
|
+
return {
|
|
25
|
+
activate: (tag) => expo.activateKeepAwakeAsync(tag),
|
|
26
|
+
deactivate: (tag) => expo.deactivateKeepAwake(tag),
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
native = inject(KeepAwake.SOURCE);
|
|
31
|
+
held = signal(new Set(), /* @ts-ignore */
|
|
32
|
+
...(ngDevMode ? [{ debugName: "held" }] : /* istanbul ignore next */ []));
|
|
33
|
+
/** The tags currently holding the screen on, so a debug screen can say which. */
|
|
34
|
+
holders = this.held.asReadonly();
|
|
35
|
+
/** Whether anything currently holds the screen on. */
|
|
36
|
+
active = computed(() => this.held().size > 0, /* @ts-ignore */
|
|
37
|
+
...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
|
|
38
|
+
/**
|
|
39
|
+
* Hold the screen on. Returns the function that releases it, ready for `DestroyRef.onDestroy`.
|
|
40
|
+
*
|
|
41
|
+
* Never a bare `activate()`, because the release is the half that gets forgotten and the half
|
|
42
|
+
* that matters.
|
|
43
|
+
*/
|
|
44
|
+
hold(tag = DEFAULT_TAG) {
|
|
45
|
+
// Counted per tag: two screens of one kind hold the same one, and the native module keeps a
|
|
46
|
+
// set of tags, so the first to release would otherwise let go for both.
|
|
47
|
+
const count = this.counts.get(tag) ?? 0;
|
|
48
|
+
this.counts.set(tag, count + 1);
|
|
49
|
+
if (count === 0) {
|
|
50
|
+
this.held.update((tags) => new Set(tags).add(tag));
|
|
51
|
+
void this.native?.activate(tag).catch(() => { });
|
|
52
|
+
}
|
|
53
|
+
let released = false;
|
|
54
|
+
return () => {
|
|
55
|
+
if (released)
|
|
56
|
+
return;
|
|
57
|
+
released = true;
|
|
58
|
+
const left = (this.counts.get(tag) ?? 1) - 1;
|
|
59
|
+
if (left > 0) {
|
|
60
|
+
this.counts.set(tag, left);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
this.counts.delete(tag);
|
|
64
|
+
this.held.update((tags) => {
|
|
65
|
+
const next = new Set(tags);
|
|
66
|
+
next.delete(tag);
|
|
67
|
+
return next;
|
|
68
|
+
});
|
|
69
|
+
void this.native?.deactivate(tag).catch(() => { });
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** How many holds each tag has that are not yet released. */
|
|
73
|
+
counts = new Map();
|
|
74
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeepAwake, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
75
|
+
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.2.0", ngImport: i0, type: KeepAwake });
|
|
76
|
+
}
|
|
77
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeepAwake, decorators: [{
|
|
78
|
+
type: Service
|
|
79
|
+
}] });
|
|
80
|
+
//# sourceMappingURL=keep-awake.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keep-awake.js","sourceRoot":"","sources":["../src/keep-awake.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `KeepAwake`, bound to `expo-keep-awake`.\n *\n * ```ts\n * inject(DestroyRef).onDestroy(inject(KeepAwake).hold('recording'));\n * ```\n *\n * Never a bare activate: a screen that holds the display on and never releases it is a phone that\n * never sleeps, which the user experiences as a battery fault and never attributes to the app.\n *\n * Tags are how two screens can each hold it without either releasing the other's.\n */\nimport { InjectionToken, Service, computed, inject, signal, type Signal } from '@angular/core';\nimport { expoModule } from './native.ts';\n\nexport interface NativeKeepAwake {\n activate(tag: string): Promise<void>;\n deactivate(tag: string): Promise<void>;\n}\n\nconst DEFAULT_TAG = 'angular-native';\n\n@Service()\nexport class KeepAwake {\n /** Overridden in a test to record a hold without keeping a screen on. */\n static readonly SOURCE = new InjectionToken<NativeKeepAwake | null>(\n 'angular-native.keepAwakeSource',\n {\n factory: () => {\n const expo = expoModule(\n 'expo-keep-awake',\n () => require('expo-keep-awake') as typeof import('expo-keep-awake'),\n );\n if (!expo) return null;\n return {\n activate: (tag) => expo.activateKeepAwakeAsync(tag),\n deactivate: (tag) => expo.deactivateKeepAwake(tag),\n };\n },\n },\n );\n\n private readonly native = inject(KeepAwake.SOURCE);\n private readonly held = signal<ReadonlySet<string>>(new Set());\n\n /** The tags currently holding the screen on, so a debug screen can say which. */\n readonly holders: Signal<ReadonlySet<string>> = this.held.asReadonly();\n\n /** Whether anything currently holds the screen on. */\n readonly active: Signal<boolean> = computed(() => this.held().size > 0);\n\n /**\n * Hold the screen on. Returns the function that releases it, ready for `DestroyRef.onDestroy`.\n *\n * Never a bare `activate()`, because the release is the half that gets forgotten and the half\n * that matters.\n */\n hold(tag = DEFAULT_TAG): () => void {\n // Counted per tag: two screens of one kind hold the same one, and the native module keeps a\n // set of tags, so the first to release would otherwise let go for both.\n const count = this.counts.get(tag) ?? 0;\n this.counts.set(tag, count + 1);\n if (count === 0) {\n this.held.update((tags) => new Set(tags).add(tag));\n void this.native?.activate(tag).catch(() => {});\n }\n\n let released = false;\n return () => {\n if (released) return;\n released = true;\n const left = (this.counts.get(tag) ?? 1) - 1;\n if (left > 0) {\n this.counts.set(tag, left);\n return;\n }\n this.counts.delete(tag);\n this.held.update((tags) => {\n const next = new Set(tags);\n next.delete(tag);\n return next;\n });\n void this.native?.deactivate(tag).catch(() => {});\n };\n }\n\n /** How many holds each tag has that are not yet released. */\n private readonly counts = new Map<string, number>();\n}\n"]}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `LanguageModel`, bound to `expo-local-llm`.
|
|
3
|
+
*
|
|
4
|
+
* The model the operating system already has: Apple's Foundation Models on iOS 26 with Apple
|
|
5
|
+
* Intelligence, Gemini Nano on the Android phones that ship it. Nothing is bundled and nothing is
|
|
6
|
+
* sent anywhere; the prompt and the answer stay on the device.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* private readonly model = inject(LanguageModel);
|
|
10
|
+
* protected readonly answer = signal<LanguageModelStream | null>(null);
|
|
11
|
+
* ask(prompt: string) { this.answer.set(this.model.stream(prompt)); }
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Every call is a conversation of its own: a session is made, asked once and released, so one
|
|
15
|
+
* prompt never sees the last one. The module runs one stream at a time, so starting a stream
|
|
16
|
+
* cancels the one before it.
|
|
17
|
+
*/
|
|
18
|
+
import { InjectionToken, type Signal } from '@angular/core';
|
|
19
|
+
import * as i0 from "@angular/core";
|
|
20
|
+
/**
|
|
21
|
+
* What `expo-local-llm` reports, plus `notInstalled` for an app without the module or a build
|
|
22
|
+
* without its native code (Expo Go, or installed without a rebuild).
|
|
23
|
+
*
|
|
24
|
+
* - `available`: ready to answer.
|
|
25
|
+
* - `notEligible`: this device or OS version cannot run it (before iOS 26, older iPhones, most
|
|
26
|
+
* Android phones).
|
|
27
|
+
* - `notEnabled`: the device can, but Apple Intelligence is switched off in Settings.
|
|
28
|
+
* - `notReady`: the model is still being prepared by the system, usually downloading after
|
|
29
|
+
* Apple Intelligence was switched on.
|
|
30
|
+
* - `downloadRequired` / `downloading`: Android, where the app asks for Gemini Nano with
|
|
31
|
+
* `download()`.
|
|
32
|
+
* - `unknown`: the platform said something the module does not name.
|
|
33
|
+
*/
|
|
34
|
+
export type LanguageModelAvailability = 'available' | 'notEligible' | 'notEnabled' | 'notReady' | 'downloadRequired' | 'downloading' | 'unknown' | 'notInstalled';
|
|
35
|
+
export interface GenerateOptions {
|
|
36
|
+
/** System instructions: who the model is and how it answers. Kept apart from the prompt. */
|
|
37
|
+
readonly instructions?: string;
|
|
38
|
+
readonly temperature?: number;
|
|
39
|
+
/** Capped at 256 on Android. */
|
|
40
|
+
readonly maxTokens?: number;
|
|
41
|
+
readonly topK?: number;
|
|
42
|
+
}
|
|
43
|
+
/** A structured answer's shape: `expo-local-llm`'s own schema, a field per property. */
|
|
44
|
+
export type LanguageModelSchema = import('expo-local-llm').Schema;
|
|
45
|
+
type SessionConfig = import('expo-local-llm').SessionConfig;
|
|
46
|
+
/** The slice of an `expo-local-llm` session this needs, with its token event unwrapped. */
|
|
47
|
+
export interface NativeLanguageSession {
|
|
48
|
+
respond(prompt: string): Promise<string>;
|
|
49
|
+
/** Resolves with the whole text, or the text so far when cancelled; rejects on failure. */
|
|
50
|
+
stream(prompt: string, onText: (textSoFar: string) => void): Promise<string>;
|
|
51
|
+
cancel(): Promise<void>;
|
|
52
|
+
release(): void;
|
|
53
|
+
}
|
|
54
|
+
/** The slice of `expo-local-llm` this needs. */
|
|
55
|
+
export interface NativeLanguageModel {
|
|
56
|
+
availability(): string;
|
|
57
|
+
onAvailabilityChange(listener: (availability: string) => void): {
|
|
58
|
+
remove(): void;
|
|
59
|
+
};
|
|
60
|
+
onDownloadProgress(listener: (progress: number) => void): {
|
|
61
|
+
remove(): void;
|
|
62
|
+
};
|
|
63
|
+
download(): Promise<void>;
|
|
64
|
+
session(config: SessionConfig): NativeLanguageSession;
|
|
65
|
+
}
|
|
66
|
+
/** Why a call was refused before it reached the model. */
|
|
67
|
+
export declare class LanguageModelUnavailableError extends Error {
|
|
68
|
+
readonly reason: Exclude<LanguageModelAvailability, 'available'>;
|
|
69
|
+
constructor(reason: Exclude<LanguageModelAvailability, 'available'>);
|
|
70
|
+
}
|
|
71
|
+
export type LanguageModelStreamStatus = 'streaming' | 'done' | 'cancelled' | 'failed';
|
|
72
|
+
/** One streamed answer, as signals a template binds. */
|
|
73
|
+
export interface LanguageModelStream {
|
|
74
|
+
/** The text so far; the whole answer once `status` is `done`. */
|
|
75
|
+
readonly text: Signal<string>;
|
|
76
|
+
readonly status: Signal<LanguageModelStreamStatus>;
|
|
77
|
+
/** The failure's message while `status` is `failed`, otherwise null. */
|
|
78
|
+
readonly error: Signal<string | null>;
|
|
79
|
+
/** The final text (the partial text if cancelled). Rejects if the stream fails. */
|
|
80
|
+
readonly result: Promise<string>;
|
|
81
|
+
/** Stop generating. The text so far is kept. Does nothing once the stream has ended. */
|
|
82
|
+
cancel(): void;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The availability and download listeners follow the model for the life of the app, and are
|
|
86
|
+
* removed when the app is destroyed.
|
|
87
|
+
*/
|
|
88
|
+
export declare class LanguageModel {
|
|
89
|
+
/** Overridden in a test to answer without a model. */
|
|
90
|
+
static readonly SOURCE: InjectionToken<NativeLanguageModel | null>;
|
|
91
|
+
private readonly native;
|
|
92
|
+
private readonly state;
|
|
93
|
+
private readonly progress;
|
|
94
|
+
private streaming;
|
|
95
|
+
/** Whether the model can answer, and if not, the platform's reason. */
|
|
96
|
+
readonly availability: Signal<LanguageModelAvailability>;
|
|
97
|
+
readonly available: Signal<boolean>;
|
|
98
|
+
/** Android's download of Gemini Nano, 0 to 1. Null until a download reports; iOS never does. */
|
|
99
|
+
readonly downloadProgress: Signal<number | null>;
|
|
100
|
+
constructor();
|
|
101
|
+
/**
|
|
102
|
+
* Ask the platform again. iOS announces a change when the app comes back to the front, which
|
|
103
|
+
* covers Apple Intelligence being switched on in Settings; it does not announce the model
|
|
104
|
+
* finishing its preparation while the app is open.
|
|
105
|
+
*/
|
|
106
|
+
refresh(): LanguageModelAvailability;
|
|
107
|
+
/** Ask Android to fetch Gemini Nano. Does nothing on iOS, where the system manages the model. */
|
|
108
|
+
download(): Promise<void>;
|
|
109
|
+
/** The whole answer at once. Rejects with the platform's reason when the model is unavailable. */
|
|
110
|
+
generate(prompt: string, options?: GenerateOptions): Promise<string>;
|
|
111
|
+
/**
|
|
112
|
+
* An answer shaped by `schema`, parsed. iOS constrains the model's decoding to the schema, so
|
|
113
|
+
* the answer always fits it; Android only asks the model to follow it, so check what comes back.
|
|
114
|
+
*/
|
|
115
|
+
generateObject<T = unknown>(prompt: string, schema: LanguageModelSchema, options?: GenerateOptions): Promise<T>;
|
|
116
|
+
/**
|
|
117
|
+
* The answer as it is written, into a signal. Cancels any stream still running, because the
|
|
118
|
+
* module runs one at a time. Never throws: an unavailable model is a stream that has already
|
|
119
|
+
* failed, with the reason as its error.
|
|
120
|
+
*/
|
|
121
|
+
stream(prompt: string, options?: GenerateOptions): LanguageModelStream;
|
|
122
|
+
private once;
|
|
123
|
+
/** Re-reads rather than trusting the signal, which only moves when the platform announces it. */
|
|
124
|
+
private refuseUnlessAvailable;
|
|
125
|
+
private read;
|
|
126
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<LanguageModel, never>;
|
|
127
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
128
|
+
}
|
|
129
|
+
export {};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `LanguageModel`, bound to `expo-local-llm`.
|
|
3
|
+
*
|
|
4
|
+
* The model the operating system already has: Apple's Foundation Models on iOS 26 with Apple
|
|
5
|
+
* Intelligence, Gemini Nano on the Android phones that ship it. Nothing is bundled and nothing is
|
|
6
|
+
* sent anywhere; the prompt and the answer stay on the device.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* private readonly model = inject(LanguageModel);
|
|
10
|
+
* protected readonly answer = signal<LanguageModelStream | null>(null);
|
|
11
|
+
* ask(prompt: string) { this.answer.set(this.model.stream(prompt)); }
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Every call is a conversation of its own: a session is made, asked once and released, so one
|
|
15
|
+
* prompt never sees the last one. The module runs one stream at a time, so starting a stream
|
|
16
|
+
* cancels the one before it.
|
|
17
|
+
*/
|
|
18
|
+
import { DestroyRef, InjectionToken, Service, computed, inject, signal, } from '@angular/core';
|
|
19
|
+
import { expoModule } from "./native.js";
|
|
20
|
+
import * as i0 from "@angular/core";
|
|
21
|
+
const REPORTED = new Set([
|
|
22
|
+
'available',
|
|
23
|
+
'notEligible',
|
|
24
|
+
'notEnabled',
|
|
25
|
+
'notReady',
|
|
26
|
+
'downloadRequired',
|
|
27
|
+
'downloading',
|
|
28
|
+
'unknown',
|
|
29
|
+
]);
|
|
30
|
+
/** Why a call was refused before it reached the model. */
|
|
31
|
+
export class LanguageModelUnavailableError extends Error {
|
|
32
|
+
reason;
|
|
33
|
+
constructor(reason) {
|
|
34
|
+
super(`The on-device language model is unavailable: ${reason}`);
|
|
35
|
+
this.name = 'LanguageModelUnavailableError';
|
|
36
|
+
this.reason = reason;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The availability and download listeners follow the model for the life of the app, and are
|
|
41
|
+
* removed when the app is destroyed.
|
|
42
|
+
*/
|
|
43
|
+
export class LanguageModel {
|
|
44
|
+
/** Overridden in a test to answer without a model. */
|
|
45
|
+
static SOURCE = new InjectionToken('angular-native.languageModelSource', {
|
|
46
|
+
factory: () => {
|
|
47
|
+
// The package loads without its native half, so that half is what decides.
|
|
48
|
+
const llm = expoModule('expo-local-llm', () => {
|
|
49
|
+
const loaded = require('expo-local-llm');
|
|
50
|
+
return loaded.ExpoLocalLlmModule ? loaded : null;
|
|
51
|
+
}, ['ios']);
|
|
52
|
+
const module = llm?.ExpoLocalLlmModule;
|
|
53
|
+
if (!llm || !module)
|
|
54
|
+
return null;
|
|
55
|
+
return {
|
|
56
|
+
availability: () => module.getAvailability(),
|
|
57
|
+
onAvailabilityChange: (listener) => module.addListener('availabilityChange', (event) => listener(event.availability)),
|
|
58
|
+
onDownloadProgress: (listener) => module.addListener('downloadProgress', (event) => listener(event.progress)),
|
|
59
|
+
download: () => module.downloadModel(),
|
|
60
|
+
session: (config) => {
|
|
61
|
+
const session = llm.createLLMSession(config);
|
|
62
|
+
return {
|
|
63
|
+
respond: (prompt) => session.respond(prompt),
|
|
64
|
+
stream: async (prompt, onText) => {
|
|
65
|
+
const tokens = session.addListener('token', (event) => onText(event.accumulated));
|
|
66
|
+
try {
|
|
67
|
+
return await session.streamResponse(prompt);
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
tokens.remove();
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
cancel: () => session.cancelStream(),
|
|
74
|
+
release: () => session.release(),
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
native = inject(LanguageModel.SOURCE);
|
|
81
|
+
state = signal(this.read(), /* @ts-ignore */
|
|
82
|
+
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
83
|
+
progress = signal(null, /* @ts-ignore */
|
|
84
|
+
...(ngDevMode ? [{ debugName: "progress" }] : /* istanbul ignore next */ []));
|
|
85
|
+
streaming = null;
|
|
86
|
+
/** Whether the model can answer, and if not, the platform's reason. */
|
|
87
|
+
availability = this.state.asReadonly();
|
|
88
|
+
available = computed(() => this.state() === 'available', /* @ts-ignore */
|
|
89
|
+
...(ngDevMode ? [{ debugName: "available" }] : /* istanbul ignore next */ []));
|
|
90
|
+
/** Android's download of Gemini Nano, 0 to 1. Null until a download reports; iOS never does. */
|
|
91
|
+
downloadProgress = this.progress.asReadonly();
|
|
92
|
+
constructor() {
|
|
93
|
+
const native = this.native;
|
|
94
|
+
if (!native)
|
|
95
|
+
return;
|
|
96
|
+
const availability = native.onAvailabilityChange((value) => this.state.set(known(value)));
|
|
97
|
+
const progress = native.onDownloadProgress((value) => this.progress.set(value));
|
|
98
|
+
inject(DestroyRef).onDestroy(() => {
|
|
99
|
+
availability.remove();
|
|
100
|
+
progress.remove();
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Ask the platform again. iOS announces a change when the app comes back to the front, which
|
|
105
|
+
* covers Apple Intelligence being switched on in Settings; it does not announce the model
|
|
106
|
+
* finishing its preparation while the app is open.
|
|
107
|
+
*/
|
|
108
|
+
refresh() {
|
|
109
|
+
this.state.set(this.read());
|
|
110
|
+
return this.state();
|
|
111
|
+
}
|
|
112
|
+
/** Ask Android to fetch Gemini Nano. Does nothing on iOS, where the system manages the model. */
|
|
113
|
+
async download() {
|
|
114
|
+
await this.native?.download();
|
|
115
|
+
}
|
|
116
|
+
/** The whole answer at once. Rejects with the platform's reason when the model is unavailable. */
|
|
117
|
+
async generate(prompt, options = {}) {
|
|
118
|
+
return this.once(config(options), prompt);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* An answer shaped by `schema`, parsed. iOS constrains the model's decoding to the schema, so
|
|
122
|
+
* the answer always fits it; Android only asks the model to follow it, so check what comes back.
|
|
123
|
+
*/
|
|
124
|
+
async generateObject(prompt, schema, options = {}) {
|
|
125
|
+
const text = await this.once({ ...config(options), responseFormat: 'json', schema }, prompt);
|
|
126
|
+
return JSON.parse(text);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The answer as it is written, into a signal. Cancels any stream still running, because the
|
|
130
|
+
* module runs one at a time. Never throws: an unavailable model is a stream that has already
|
|
131
|
+
* failed, with the reason as its error.
|
|
132
|
+
*/
|
|
133
|
+
stream(prompt, options = {}) {
|
|
134
|
+
this.streaming?.cancel();
|
|
135
|
+
const text = signal('', /* @ts-ignore */
|
|
136
|
+
...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
137
|
+
const status = signal('streaming', /* @ts-ignore */
|
|
138
|
+
...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
139
|
+
const error = signal(null, /* @ts-ignore */
|
|
140
|
+
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
141
|
+
let session = null;
|
|
142
|
+
let cancelled = false;
|
|
143
|
+
const fail = (failure) => {
|
|
144
|
+
status.set('failed');
|
|
145
|
+
error.set(failure instanceof Error ? failure.message : String(failure));
|
|
146
|
+
throw failure;
|
|
147
|
+
};
|
|
148
|
+
let result;
|
|
149
|
+
try {
|
|
150
|
+
this.refuseUnlessAvailable();
|
|
151
|
+
const running = this.native.session(config(options));
|
|
152
|
+
session = running;
|
|
153
|
+
result = running
|
|
154
|
+
.stream(prompt, (soFar) => text.set(soFar))
|
|
155
|
+
.then((final) => {
|
|
156
|
+
text.set(final);
|
|
157
|
+
status.set(cancelled ? 'cancelled' : 'done');
|
|
158
|
+
return final;
|
|
159
|
+
}, fail)
|
|
160
|
+
.finally(() => running.release());
|
|
161
|
+
}
|
|
162
|
+
catch (refusal) {
|
|
163
|
+
result = Promise.reject(refusal);
|
|
164
|
+
try {
|
|
165
|
+
fail(refusal);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// Recorded on the signals at once, so a template shows the reason on its first render.
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Handled here so a stream whose result nobody awaits is not an unhandled rejection; anyone
|
|
172
|
+
// who does await it still sees the failure.
|
|
173
|
+
result.catch(() => { });
|
|
174
|
+
const stream = {
|
|
175
|
+
text: text.asReadonly(),
|
|
176
|
+
status: status.asReadonly(),
|
|
177
|
+
error: error.asReadonly(),
|
|
178
|
+
result,
|
|
179
|
+
cancel: () => {
|
|
180
|
+
if (status() !== 'streaming' || !session)
|
|
181
|
+
return;
|
|
182
|
+
cancelled = true;
|
|
183
|
+
void session.cancel();
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
this.streaming = session ? stream : null;
|
|
187
|
+
return stream;
|
|
188
|
+
}
|
|
189
|
+
async once(sessionConfig, prompt) {
|
|
190
|
+
this.refuseUnlessAvailable();
|
|
191
|
+
const session = this.native.session(sessionConfig);
|
|
192
|
+
try {
|
|
193
|
+
return await session.respond(prompt);
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
session.release();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** Re-reads rather than trusting the signal, which only moves when the platform announces it. */
|
|
200
|
+
refuseUnlessAvailable() {
|
|
201
|
+
const availability = this.refresh();
|
|
202
|
+
if (availability !== 'available')
|
|
203
|
+
throw new LanguageModelUnavailableError(availability);
|
|
204
|
+
}
|
|
205
|
+
read() {
|
|
206
|
+
return this.native ? known(this.native.availability()) : 'notInstalled';
|
|
207
|
+
}
|
|
208
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: LanguageModel, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
209
|
+
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.2.0", ngImport: i0, type: LanguageModel });
|
|
210
|
+
}
|
|
211
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: LanguageModel, decorators: [{
|
|
212
|
+
type: Service
|
|
213
|
+
}], ctorParameters: () => [] });
|
|
214
|
+
function known(value) {
|
|
215
|
+
return REPORTED.has(value) ? value : 'unknown';
|
|
216
|
+
}
|
|
217
|
+
/** Only the keys that were given, so the native side sees its own defaults for the rest. */
|
|
218
|
+
function config({ instructions, ...sampling }) {
|
|
219
|
+
const options = Object.fromEntries(Object.entries(sampling).filter(([, value]) => value !== undefined));
|
|
220
|
+
return {
|
|
221
|
+
...(instructions === undefined ? {} : { instructions }),
|
|
222
|
+
...(Object.keys(options).length === 0 ? {} : { options }),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=language-model.js.map
|