@dvai-bridge/capacitor-llama 4.0.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/DVAICapacitorLlama.podspec +21 -0
- package/LICENSE +51 -0
- package/README.md +199 -0
- package/android/build.gradle +91 -0
- package/android/gradle.properties +4 -0
- package/android/settings.gradle +4 -0
- package/android/src/androidTest/assets/audio/m4a-1s.m4a +0 -0
- package/android/src/androidTest/assets/audio/pcm16-1s-16khz-mono.bin +0 -0
- package/android/src/androidTest/assets/audio/wav-1s-16khz-mono.wav +0 -0
- package/android/src/androidTest/assets/images/tiny-test.png +0 -0
- package/android/src/androidTest/java/co/deepvoiceai/bridge/llama/RealModelSmokeTest.kt +238 -0
- package/android/src/main/AndroidManifest.xml +7 -0
- package/android/src/main/java/co/deepvoiceai/bridge/llama/Plugin.kt +177 -0
- package/android/src/main/res/xml/dvai_network_security_config.xml +7 -0
- package/android/src/test/java/co/deepvoiceai/bridge/llama/SmokeTest.kt +11 -0
- package/dist/index.cjs +34 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/ios/Package.swift +57 -0
- package/ios/Sources/DVAICapacitorLlama/Plugin.swift +154 -0
- package/ios/Sources/DVAICapacitorLlama/PluginProxy.m +15 -0
- package/ios/Tests/DVAICapacitorLlamaTests/RealModelSmokeTest.swift +412 -0
- package/ios/Tests/DVAICapacitorLlamaTests/SmokeTest.swift +10 -0
- package/package.json +70 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
package co.deepvoiceai.bridge.llama
|
|
2
|
+
|
|
3
|
+
import com.getcapacitor.JSArray
|
|
4
|
+
import com.getcapacitor.JSObject
|
|
5
|
+
import com.getcapacitor.Plugin
|
|
6
|
+
import com.getcapacitor.PluginCall
|
|
7
|
+
import com.getcapacitor.PluginMethod
|
|
8
|
+
import com.getcapacitor.annotation.CapacitorPlugin
|
|
9
|
+
import co.deepvoiceai.bridge.llama.core.ModelDownloader
|
|
10
|
+
import co.deepvoiceai.bridge.llama.core.PluginState
|
|
11
|
+
import kotlinx.coroutines.CoroutineScope
|
|
12
|
+
import kotlinx.coroutines.Dispatchers
|
|
13
|
+
import kotlinx.coroutines.SupervisorJob
|
|
14
|
+
import kotlinx.coroutines.cancel
|
|
15
|
+
import kotlinx.coroutines.launch
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// JSObject ↔ Map translation helpers — needed because PluginState's API is
|
|
19
|
+
// Capacitor-neutral (Map<String, Any?>) while the JS-bridge layer works with
|
|
20
|
+
// Capacitor's JSObject (which extends JSONObject and has no .toMap()).
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
private fun JSObject.toAnyMap(): Map<String, Any?> {
|
|
24
|
+
val out = mutableMapOf<String, Any?>()
|
|
25
|
+
val it = keys()
|
|
26
|
+
while (it.hasNext()) {
|
|
27
|
+
val k = it.next()
|
|
28
|
+
out[k] = this.opt(k) // returns Any (or JSONObject.NULL); fine for our usage
|
|
29
|
+
}
|
|
30
|
+
return out
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private fun Map<String, Any?>.toJSObject(): JSObject {
|
|
34
|
+
val obj = JSObject()
|
|
35
|
+
for ((k, v) in this) {
|
|
36
|
+
if (v != null) obj.put(k, v)
|
|
37
|
+
}
|
|
38
|
+
return obj
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@CapacitorPlugin(name = "DVAIBridgeLlama")
|
|
42
|
+
class DVAIBridgeLlamaPlugin : Plugin() {
|
|
43
|
+
private val state = PluginState()
|
|
44
|
+
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
|
45
|
+
private val downloader: ModelDownloader by lazy { ModelDownloader(context) }
|
|
46
|
+
|
|
47
|
+
override fun handleOnDestroy() {
|
|
48
|
+
super.handleOnDestroy()
|
|
49
|
+
scope.cancel()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@PluginMethod
|
|
53
|
+
fun start(call: PluginCall) {
|
|
54
|
+
scope.launch {
|
|
55
|
+
notifyListeners("progress", JSObject().apply { put("phase", "load") })
|
|
56
|
+
try {
|
|
57
|
+
val resultMap = state.start((call.data ?: JSObject()).toAnyMap())
|
|
58
|
+
notifyListeners("progress", JSObject().apply { put("phase", "ready") })
|
|
59
|
+
call.resolve(resultMap.toJSObject())
|
|
60
|
+
} catch (e: Exception) {
|
|
61
|
+
notifyListeners("progress", JSObject().apply {
|
|
62
|
+
put("phase", "error")
|
|
63
|
+
put("message", e.message ?: "Start failed")
|
|
64
|
+
})
|
|
65
|
+
call.reject(e.message ?: "Start failed", e)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@PluginMethod
|
|
71
|
+
fun stop(call: PluginCall) {
|
|
72
|
+
scope.launch {
|
|
73
|
+
try {
|
|
74
|
+
state.stop()
|
|
75
|
+
call.resolve()
|
|
76
|
+
} catch (e: Exception) {
|
|
77
|
+
call.reject(e.message ?: "Stop failed", e)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@PluginMethod
|
|
83
|
+
fun status(call: PluginCall) {
|
|
84
|
+
scope.launch {
|
|
85
|
+
call.resolve(state.statusInfo().toJSObject())
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@PluginMethod
|
|
90
|
+
fun downloadModel(call: PluginCall) {
|
|
91
|
+
val url = call.getString("url") ?: return call.reject("url is required")
|
|
92
|
+
val sha = call.getString("sha256")?.takeIf { it.isNotEmpty() }?.lowercase()
|
|
93
|
+
?: return call.reject("sha256 is required")
|
|
94
|
+
val destFilename = call.getString("destFilename") ?: url.substringAfterLast('/')
|
|
95
|
+
val headers: Map<String, String> = call.getObject("headers")?.let { obj ->
|
|
96
|
+
val map = mutableMapOf<String, String>()
|
|
97
|
+
obj.keys().forEachRemaining { k -> obj.getString(k)?.let { map[k] = it } }
|
|
98
|
+
map
|
|
99
|
+
} ?: emptyMap()
|
|
100
|
+
|
|
101
|
+
scope.launch {
|
|
102
|
+
try {
|
|
103
|
+
val (path, cached) = downloader.downloadModel(
|
|
104
|
+
url = url,
|
|
105
|
+
expectedSha256 = sha,
|
|
106
|
+
destFilename = destFilename,
|
|
107
|
+
headers = headers,
|
|
108
|
+
) { bytesDone, bytesTotal ->
|
|
109
|
+
val payload = JSObject().apply {
|
|
110
|
+
put("phase", "download")
|
|
111
|
+
put("bytesReceived", bytesDone)
|
|
112
|
+
if (bytesTotal != null) {
|
|
113
|
+
put("bytesTotal", bytesTotal)
|
|
114
|
+
if (bytesTotal > 0) {
|
|
115
|
+
put("percent", bytesDone.toDouble() / bytesTotal.toDouble() * 100.0)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
notifyListeners("progress", payload)
|
|
120
|
+
}
|
|
121
|
+
call.resolve(JSObject().apply {
|
|
122
|
+
put("path", path)
|
|
123
|
+
put("cached", cached)
|
|
124
|
+
})
|
|
125
|
+
} catch (e: Exception) {
|
|
126
|
+
call.reject(e.message ?: "Download failed", e)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@PluginMethod
|
|
132
|
+
fun listCachedModels(call: PluginCall) {
|
|
133
|
+
scope.launch {
|
|
134
|
+
try {
|
|
135
|
+
val infos = downloader.listCached()
|
|
136
|
+
val arr = JSArray()
|
|
137
|
+
infos.forEach { info ->
|
|
138
|
+
arr.put(JSObject().apply {
|
|
139
|
+
put("filename", info.filename)
|
|
140
|
+
put("path", info.path)
|
|
141
|
+
put("bytes", info.bytes)
|
|
142
|
+
put("sha256", info.sha256)
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
call.resolve(JSObject().apply { put("models", arr) })
|
|
146
|
+
} catch (e: Exception) {
|
|
147
|
+
call.reject(e.message ?: "List failed", e)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@PluginMethod
|
|
153
|
+
fun deleteCachedModel(call: PluginCall) {
|
|
154
|
+
val filename = call.getString("filename")?.takeIf { it.isNotEmpty() }
|
|
155
|
+
?: return call.reject("filename is required")
|
|
156
|
+
scope.launch {
|
|
157
|
+
try {
|
|
158
|
+
downloader.deleteCached(filename)
|
|
159
|
+
call.resolve()
|
|
160
|
+
} catch (e: Exception) {
|
|
161
|
+
call.reject(e.message ?: "Delete failed", e)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@PluginMethod
|
|
167
|
+
fun cacheDir(call: PluginCall) {
|
|
168
|
+
scope.launch {
|
|
169
|
+
try {
|
|
170
|
+
val path = downloader.cacheDir().absolutePath
|
|
171
|
+
call.resolve(JSObject().apply { put("path", path) })
|
|
172
|
+
} catch (e: Exception) {
|
|
173
|
+
call.reject(e.message ?: "cacheDir failed", e)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<network-security-config>
|
|
3
|
+
<domain-config cleartextTrafficPermitted="true">
|
|
4
|
+
<domain includeSubdomains="false">localhost</domain>
|
|
5
|
+
<domain includeSubdomains="false">127.0.0.1</domain>
|
|
6
|
+
</domain-config>
|
|
7
|
+
</network-security-config>
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DVAIBridgeLlama: () => DVAIBridgeLlama,
|
|
24
|
+
default: () => index_default
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
var import_core = require("@capacitor/core");
|
|
28
|
+
var DVAIBridgeLlama = (0, import_core.registerPlugin)("DVAIBridgeLlama");
|
|
29
|
+
var index_default = DVAIBridgeLlama;
|
|
30
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
31
|
+
0 && (module.exports = {
|
|
32
|
+
DVAIBridgeLlama
|
|
33
|
+
});
|
|
34
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { registerPlugin } from \"@capacitor/core\";\r\nimport type { NativePluginInterface } from \"@dvai-bridge/capacitor\";\r\n\r\nconst DVAIBridgeLlama = registerPlugin<NativePluginInterface>(\"DVAIBridgeLlama\");\r\n\r\nexport default DVAIBridgeLlama;\r\nexport { DVAIBridgeLlama };\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAA+B;AAG/B,IAAM,sBAAkB,4BAAsC,iBAAiB;AAE/E,IAAO,gBAAQ;","names":[]}
|
package/dist/index.d.cts
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { registerPlugin } from \"@capacitor/core\";\r\nimport type { NativePluginInterface } from \"@dvai-bridge/capacitor\";\r\n\r\nconst DVAIBridgeLlama = registerPlugin<NativePluginInterface>(\"DVAIBridgeLlama\");\r\n\r\nexport default DVAIBridgeLlama;\r\nexport { DVAIBridgeLlama };\r\n"],"mappings":";AAAA,SAAS,sBAAsB;AAG/B,IAAM,kBAAkB,eAAsC,iBAAiB;AAE/E,IAAO,gBAAQ;","names":[]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// swift-tools-version: 5.9
|
|
2
|
+
import PackageDescription
|
|
3
|
+
|
|
4
|
+
let package = Package(
|
|
5
|
+
name: "DVAICapacitorLlama",
|
|
6
|
+
platforms: [.iOS(.v14), .macOS(.v12)],
|
|
7
|
+
products: [
|
|
8
|
+
.library(name: "DVAICapacitorLlama", targets: ["DVAICapacitorLlama"]),
|
|
9
|
+
],
|
|
10
|
+
dependencies: [
|
|
11
|
+
// Capacitor SPM artifact (existing)
|
|
12
|
+
.package(url: "https://github.com/ionic-team/capacitor-swift-pm", branch: "main"),
|
|
13
|
+
// Core package — relative path during dev, replaced with version-pin
|
|
14
|
+
// or git URL at publish time. The path is relative to *this* Package.swift's
|
|
15
|
+
// location (`packages/dvai-bridge-capacitor-llama/ios/`), so two `..` get
|
|
16
|
+
// us to the `packages/` parent and `dvai-bridge-ios-llama-core/ios` is the
|
|
17
|
+
// sibling package's SPM root.
|
|
18
|
+
//
|
|
19
|
+
// The core's Package.swift lives at the PACKAGE ROOT (not under `ios/`)
|
|
20
|
+
// so that SPM derives identity "dvai-bridge-ios-llama-core" rather than
|
|
21
|
+
// "ios" for this dependency. With both Package.swifts at `ios/`, SPM's
|
|
22
|
+
// path-dep identity-from-last-dir-name rule aliased them and triggered
|
|
23
|
+
// a false `cyclic dependency between packages DVAICapacitorLlama ->
|
|
24
|
+
// DVAICapacitorLlama` resolution error. The bare product reference
|
|
25
|
+
// ("DVAILlamaCore") in the target dependency list below works because
|
|
26
|
+
// SPM auto-resolves unambiguous product names across the dep graph.
|
|
27
|
+
.package(path: "../../dvai-bridge-ios-llama-core"),
|
|
28
|
+
],
|
|
29
|
+
targets: [
|
|
30
|
+
.target(
|
|
31
|
+
name: "DVAICapacitorLlama",
|
|
32
|
+
dependencies: [
|
|
33
|
+
.product(name: "Capacitor", package: "capacitor-swift-pm"),
|
|
34
|
+
.product(name: "Cordova", package: "capacitor-swift-pm"),
|
|
35
|
+
// The package's identity is derived from the last dir of the
|
|
36
|
+
// path dep (`dvai-bridge-ios-llama-core`), not from the
|
|
37
|
+
// manifest's `name:` field. Bare-name product references don't
|
|
38
|
+
// cross package boundaries reliably; explicit form is required.
|
|
39
|
+
.product(name: "DVAILlamaCore", package: "dvai-bridge-ios-llama-core"),
|
|
40
|
+
],
|
|
41
|
+
path: "Sources/DVAICapacitorLlama",
|
|
42
|
+
exclude: ["PluginProxy.m"]
|
|
43
|
+
),
|
|
44
|
+
.testTarget(
|
|
45
|
+
name: "DVAICapacitorLlamaTests",
|
|
46
|
+
dependencies: [
|
|
47
|
+
"DVAICapacitorLlama",
|
|
48
|
+
// RealModelSmokeTest reaches the core directly (LlamaCppBridge,
|
|
49
|
+
// ModelDownloader, MTMD_MEDIA_MARKER) — declare both core products
|
|
50
|
+
// explicitly so Xcode resolves them at link time.
|
|
51
|
+
.product(name: "DVAILlamaCore", package: "dvai-bridge-ios-llama-core"),
|
|
52
|
+
.product(name: "DVAILlamaCoreObjC", package: "dvai-bridge-ios-llama-core"),
|
|
53
|
+
],
|
|
54
|
+
path: "Tests/DVAICapacitorLlamaTests"
|
|
55
|
+
),
|
|
56
|
+
]
|
|
57
|
+
)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import DVAILlamaCore
|
|
3
|
+
|
|
4
|
+
#if canImport(Capacitor)
|
|
5
|
+
import Capacitor
|
|
6
|
+
|
|
7
|
+
@objc(DVAIBridgeLlamaPlugin)
|
|
8
|
+
public class DVAIBridgeLlamaPlugin: CAPPlugin {
|
|
9
|
+
private let state = PluginState()
|
|
10
|
+
private let downloader = ModelDownloader()
|
|
11
|
+
|
|
12
|
+
public override func load() {
|
|
13
|
+
super.load()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@objc func start(_ call: CAPPluginCall) {
|
|
17
|
+
let opts: [String: Any] = (call.options as? [String: Any]) ?? [:]
|
|
18
|
+
Task { [weak self] in
|
|
19
|
+
guard let self else { return }
|
|
20
|
+
self.notifyListeners("progress", data: ["phase": "load"])
|
|
21
|
+
do {
|
|
22
|
+
let result = try await self.state.start(opts: opts)
|
|
23
|
+
self.notifyListeners("progress", data: ["phase": "ready"])
|
|
24
|
+
call.resolve(result)
|
|
25
|
+
} catch {
|
|
26
|
+
self.notifyListeners("progress", data: [
|
|
27
|
+
"phase": "error",
|
|
28
|
+
"message": error.localizedDescription,
|
|
29
|
+
])
|
|
30
|
+
call.reject(error.localizedDescription)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@objc func stop(_ call: CAPPluginCall) {
|
|
36
|
+
Task {
|
|
37
|
+
do {
|
|
38
|
+
try await state.stop()
|
|
39
|
+
call.resolve()
|
|
40
|
+
} catch {
|
|
41
|
+
call.reject(error.localizedDescription)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@objc func status(_ call: CAPPluginCall) {
|
|
47
|
+
Task {
|
|
48
|
+
let info = await state.statusInfo()
|
|
49
|
+
call.resolve(info)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@objc func downloadModel(_ call: CAPPluginCall) {
|
|
54
|
+
guard let urlStr = call.getString("url"), let url = URL(string: urlStr) else {
|
|
55
|
+
call.reject("url is required")
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
guard let sha = call.getString("sha256"), !sha.isEmpty else {
|
|
59
|
+
call.reject("sha256 is required")
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
let destFilename = call.getString("destFilename") ?? url.lastPathComponent
|
|
63
|
+
// Capacitor's JSObject is `[String: Any]`, so an `as? [String: String]`
|
|
64
|
+
// cast returns nil whenever any value isn't already typed as String,
|
|
65
|
+
// silently dropping ALL headers. Mirror Android's per-key extraction.
|
|
66
|
+
var headers: [String: String] = [:]
|
|
67
|
+
if let raw = call.getObject("headers") {
|
|
68
|
+
for (k, v) in raw {
|
|
69
|
+
if let s = v as? String { headers[k] = s }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
Task { [weak self] in
|
|
74
|
+
guard let self else { return }
|
|
75
|
+
do {
|
|
76
|
+
let result = try await self.downloader.downloadModel(
|
|
77
|
+
url: url,
|
|
78
|
+
expectedSha256: sha.lowercased(),
|
|
79
|
+
destFilename: destFilename,
|
|
80
|
+
headers: headers,
|
|
81
|
+
onProgress: { [weak self] bytesDone, bytesTotal in
|
|
82
|
+
guard let self else { return }
|
|
83
|
+
var payload: [String: Any] = [
|
|
84
|
+
"phase": "download",
|
|
85
|
+
"bytesReceived": bytesDone,
|
|
86
|
+
]
|
|
87
|
+
if let total = bytesTotal {
|
|
88
|
+
payload["bytesTotal"] = total
|
|
89
|
+
if total > 0 {
|
|
90
|
+
payload["percent"] = Double(bytesDone) / Double(total) * 100.0
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
self.notifyListeners("progress", data: payload)
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
call.resolve([
|
|
97
|
+
"path": result.path,
|
|
98
|
+
"cached": result.cached,
|
|
99
|
+
])
|
|
100
|
+
} catch {
|
|
101
|
+
call.reject(error.localizedDescription)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@objc func listCachedModels(_ call: CAPPluginCall) {
|
|
107
|
+
Task { [weak self] in
|
|
108
|
+
guard let self else { return }
|
|
109
|
+
do {
|
|
110
|
+
let infos = try await self.downloader.listCachedModels()
|
|
111
|
+
let models: [[String: Any]] = infos.map {
|
|
112
|
+
[
|
|
113
|
+
"filename": $0.filename,
|
|
114
|
+
"path": $0.path,
|
|
115
|
+
"bytes": $0.bytes,
|
|
116
|
+
"sha256": $0.sha256,
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
call.resolve(["models": models])
|
|
120
|
+
} catch {
|
|
121
|
+
call.reject(error.localizedDescription)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@objc func deleteCachedModel(_ call: CAPPluginCall) {
|
|
127
|
+
guard let filename = call.getString("filename"), !filename.isEmpty else {
|
|
128
|
+
call.reject("filename is required")
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
Task { [weak self] in
|
|
132
|
+
guard let self else { return }
|
|
133
|
+
do {
|
|
134
|
+
try await self.downloader.deleteCachedModel(filename: filename)
|
|
135
|
+
call.resolve()
|
|
136
|
+
} catch {
|
|
137
|
+
call.reject(error.localizedDescription)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@objc func cacheDir(_ call: CAPPluginCall) {
|
|
143
|
+
Task { [weak self] in
|
|
144
|
+
guard let self else { return }
|
|
145
|
+
do {
|
|
146
|
+
let path = try await self.downloader.cacheDirPath()
|
|
147
|
+
call.resolve(["path": path])
|
|
148
|
+
} catch {
|
|
149
|
+
call.reject(error.localizedDescription)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
#endif
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
|
|
3
|
+
#if __has_include(<Capacitor/Capacitor.h>)
|
|
4
|
+
#import <Capacitor/Capacitor.h>
|
|
5
|
+
|
|
6
|
+
CAP_PLUGIN(DVAIBridgeLlamaPlugin, "DVAIBridgeLlama",
|
|
7
|
+
CAP_PLUGIN_METHOD(start, CAPPluginReturnPromise);
|
|
8
|
+
CAP_PLUGIN_METHOD(stop, CAPPluginReturnPromise);
|
|
9
|
+
CAP_PLUGIN_METHOD(status, CAPPluginReturnPromise);
|
|
10
|
+
CAP_PLUGIN_METHOD(downloadModel, CAPPluginReturnPromise);
|
|
11
|
+
CAP_PLUGIN_METHOD(listCachedModels, CAPPluginReturnPromise);
|
|
12
|
+
CAP_PLUGIN_METHOD(deleteCachedModel, CAPPluginReturnPromise);
|
|
13
|
+
CAP_PLUGIN_METHOD(cacheDir, CAPPluginReturnPromise);
|
|
14
|
+
)
|
|
15
|
+
#endif
|