bleam 0.0.9 → 0.0.11
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/dist/ai.cjs +608 -59
- package/dist/ai.d.cts +217 -89
- package/dist/ai.d.ts +217 -89
- package/dist/ai.js +601 -59
- package/dist/cli.cjs +142 -94
- package/dist/cli.d.cts +11 -27
- package/dist/cli.d.ts +11 -27
- package/dist/cli.js +143 -95
- package/dist/files.d.cts +1 -1
- package/dist/files.d.ts +1 -1
- package/dist/platform.cjs +1 -13
- package/dist/platform.d.cts +1 -26
- package/dist/platform.d.ts +1 -26
- package/dist/platform.js +1 -10
- package/dist/schema.d.cts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/state.d.cts +1 -1
- package/dist/state.d.ts +1 -1
- package/dist/{ui-Bg11tvlc.d.ts → ui-CHc4xEs_.d.ts} +7 -7
- package/dist/ui.d.ts +1 -1
- package/dist/window.d.ts +1 -1
- package/package.json +2 -1
- package/templates/image-generation/app/index.tsx +3 -1
- package/templates/native/ios/Bleam/AI/Flux2/Configuration/ModelRegistry.swift +1 -1
- package/templates/native/ios/Bleam/AI/Flux2/Pipeline/FluxImageRunner.swift +7 -2
- package/templates/native/ios/Bleam/AI/TextGeneration/Bonsai/BonsaiTextGenerator.swift +46 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Bonsai/BonsaiTextModel.swift +107 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Bonsai/BonsaiTextSnapshotValidator.swift +201 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Core/GenerationTypes.swift +85 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Core/ModelCapabilities.swift +21 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Core/TextKVCache.swift +23 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Core/TokenSampler.swift +79 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3CheckpointLoader.swift +161 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3Configuration.swift +119 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3InferenceSession.swift +84 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3Layers.swift +197 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3Model.swift +130 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Checkpoint.swift +102 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Configuration.swift +140 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Model.swift +112 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Session.swift +121 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridCache.swift +42 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridFullAttention.swift +57 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridLayers.swift +58 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridLinearAttention.swift +111 -0
- package/templates/native/ios/Bleam/AppDelegate.swift +0 -97
- package/templates/native/ios/Bleam.xcodeproj/project.pbxproj +128 -121
- package/templates/native/ios/GenerationService/BonsaiTextGenerationRunner.swift +103 -0
- package/templates/native/ios/GenerationService/ImageGenerationRunner.swift +19 -7
- package/templates/native/ios/GenerationService/main.swift +174 -48
- package/templates/native/ios/Shared/Generation/GenerationServiceProtocol.swift +3 -0
- package/templates/native/ios/Shared/Generation/GenerationWorkerProtocol.swift +44 -0
- package/templates/native/modules/bleam-runtime/ios/AIModule.swift +225 -5
- package/templates/native/modules/bleam-runtime/ios/GenerationContracts.swift +47 -0
- package/templates/native/modules/bleam-runtime/ios/PlatformModule.swift +0 -175
- package/templates/text-generation/app/index.tsx +93 -0
- package/templates/text-generation/app.config.ts +5 -0
- package/templates/updates/README.md +116 -0
- package/templates/updates/src/index.ts +292 -0
- package/templates/updates/src/schema.ts +230 -0
- package/templates/updates/tsconfig.json +12 -0
- package/templates/updates/wrangler.jsonc +19 -0
- package/templates/native/ios/PlatformHelper/Info.plist +0 -29
- package/templates/native/ios/PlatformHelper/main.swift +0 -335
- /package/dist/{files-DwA7pzr3.d.cts → files-4ZEoAWiv.d.ts} +0 -0
- /package/dist/{files-VrkQlKIT.d.ts → files-Dt5mbzLq.d.cts} +0 -0
- /package/dist/{schema-DOOjfXvs.d.ts → schema-CYh6n8GS.d.ts} +0 -0
- /package/dist/{schema-ENSMX_1t.d.cts → schema-oeOrd3i1.d.cts} +0 -0
package/dist/ai.cjs
CHANGED
|
@@ -11,6 +11,53 @@ react = require_chunk.__toESM(react);
|
|
|
11
11
|
let react_native = require("react-native");
|
|
12
12
|
react_native = require_chunk.__toESM(react_native);
|
|
13
13
|
|
|
14
|
+
//#region src/ai/bonsai.ts
|
|
15
|
+
const internalTextModelCatalog = [
|
|
16
|
+
{
|
|
17
|
+
id: "ternary-bonsai-1.7b",
|
|
18
|
+
repo: "prism-ml/Ternary-Bonsai-1.7B-mlx-2bit",
|
|
19
|
+
revision: "5f3e306330f636cfc6c6241b4850fae6711c5985",
|
|
20
|
+
estimatedDownloadBytes: 495528947,
|
|
21
|
+
contextLength: 32768,
|
|
22
|
+
architecture: "qwen3"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "ternary-bonsai-4b",
|
|
26
|
+
repo: "prism-ml/Ternary-Bonsai-4B-mlx-2bit",
|
|
27
|
+
revision: "e1374ad6bf9b1b56afd743936b8faa33c409a75f",
|
|
28
|
+
estimatedDownloadBytes: 1143060044,
|
|
29
|
+
contextLength: 32768,
|
|
30
|
+
architecture: "qwen3"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "ternary-bonsai-8b",
|
|
34
|
+
repo: "prism-ml/Ternary-Bonsai-8B-mlx-2bit",
|
|
35
|
+
revision: "9260b24298e4211e804663e9f519962cf59f34be",
|
|
36
|
+
estimatedDownloadBytes: 2315155948,
|
|
37
|
+
contextLength: 65536,
|
|
38
|
+
architecture: "qwen3"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "ternary-bonsai-27b",
|
|
42
|
+
repo: "prism-ml/Ternary-Bonsai-27B-mlx-2bit",
|
|
43
|
+
revision: "70f75f3ad081ab840a42f3304c02c27e7f89bfb7",
|
|
44
|
+
estimatedDownloadBytes: 8510972679,
|
|
45
|
+
contextLength: 262144,
|
|
46
|
+
architecture: "qwen3.5"
|
|
47
|
+
}
|
|
48
|
+
];
|
|
49
|
+
const bonsaiTextModelCatalog = Object.freeze(internalTextModelCatalog.map(({ architecture: _,...metadata }) => Object.freeze(metadata)));
|
|
50
|
+
function bonsai(id) {
|
|
51
|
+
const text = internalTextModelCatalog.some((model) => model.id === id);
|
|
52
|
+
if (!text && id !== "ternary-bonsai-image-4b") throw new TypeError(`Unsupported Bonsai model: ${id}`);
|
|
53
|
+
return Object.freeze({
|
|
54
|
+
provider: "bonsai",
|
|
55
|
+
kind: text ? "text" : "image",
|
|
56
|
+
id
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
14
61
|
//#region src/ai/errors.ts
|
|
15
62
|
var AIError = class extends Error {
|
|
16
63
|
constructor(code, message, cause) {
|
|
@@ -50,6 +97,217 @@ function mapNativeError(error) {
|
|
|
50
97
|
return new AIError("generation_failed", message, error);
|
|
51
98
|
}
|
|
52
99
|
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/ai/bonsai-text.ts
|
|
102
|
+
const bonsaiTextModelFiles = [
|
|
103
|
+
"config.json",
|
|
104
|
+
"tokenizer.json",
|
|
105
|
+
"tokenizer_config.json",
|
|
106
|
+
"chat_template.jinja",
|
|
107
|
+
"model.safetensors",
|
|
108
|
+
"model.safetensors.index.json"
|
|
109
|
+
];
|
|
110
|
+
function modelId(model) {
|
|
111
|
+
return typeof model === "string" ? model : model.id;
|
|
112
|
+
}
|
|
113
|
+
function metadataFor(model) {
|
|
114
|
+
const id = modelId(model);
|
|
115
|
+
const metadata = bonsaiTextModelCatalog.find((candidate) => candidate.id === id);
|
|
116
|
+
if (!metadata) throw new AIError("invalid_model", `Unsupported Bonsai text model: ${id}`);
|
|
117
|
+
return metadata;
|
|
118
|
+
}
|
|
119
|
+
function bonsaiTextModelDirectory(model) {
|
|
120
|
+
return require_files.files.directory(require_files.files.paths.models, "bonsai", "text", modelId(model));
|
|
121
|
+
}
|
|
122
|
+
function modelDirectory(model, override) {
|
|
123
|
+
return override ? require_files.files.directory(override) : bonsaiTextModelDirectory(model);
|
|
124
|
+
}
|
|
125
|
+
function modelDirectoryIn(model, directory) {
|
|
126
|
+
return directory ? require_files.files.directory(directory, modelId(model)) : bonsaiTextModelDirectory(model);
|
|
127
|
+
}
|
|
128
|
+
function resolveUrl(repo, revision, file) {
|
|
129
|
+
return `https://huggingface.co/${repo.split("/").map(encodeURIComponent).join("/")}/resolve/${encodeURIComponent(revision)}/${encodeURIComponent(file)}`;
|
|
130
|
+
}
|
|
131
|
+
function throwIfAborted$1(signal) {
|
|
132
|
+
if (signal?.aborted) throw new AIError("generation_canceled", "Bonsai model preparation canceled");
|
|
133
|
+
}
|
|
134
|
+
const fileSizes = {
|
|
135
|
+
"ternary-bonsai-1.7b": {
|
|
136
|
+
"config.json": 2939,
|
|
137
|
+
"tokenizer.json": 11422650,
|
|
138
|
+
"tokenizer_config.json": 348,
|
|
139
|
+
"chat_template.jinja": 4063,
|
|
140
|
+
"model.safetensors": 484049216,
|
|
141
|
+
"model.safetensors.index.json": 49731
|
|
142
|
+
},
|
|
143
|
+
"ternary-bonsai-4b": {
|
|
144
|
+
"config.json": 3115,
|
|
145
|
+
"tokenizer.json": 11422650,
|
|
146
|
+
"tokenizer_config.json": 348,
|
|
147
|
+
"chat_template.jinja": 4063,
|
|
148
|
+
"model.safetensors": 1131565944,
|
|
149
|
+
"model.safetensors.index.json": 63924
|
|
150
|
+
},
|
|
151
|
+
"ternary-bonsai-8b": {
|
|
152
|
+
"config.json": 3118,
|
|
153
|
+
"tokenizer.json": 11422650,
|
|
154
|
+
"tokenizer_config.json": 348,
|
|
155
|
+
"chat_template.jinja": 4063,
|
|
156
|
+
"model.safetensors": 2303661704,
|
|
157
|
+
"model.safetensors.index.json": 64065
|
|
158
|
+
},
|
|
159
|
+
"ternary-bonsai-27b": {
|
|
160
|
+
"config.json": 3790,
|
|
161
|
+
"tokenizer.json": 19989343,
|
|
162
|
+
"tokenizer_config.json": 1098,
|
|
163
|
+
"chat_template.jinja": 7764,
|
|
164
|
+
"model.safetensors": 8490785104,
|
|
165
|
+
"model.safetensors.index.json": 185580
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
function validFile(model, file, size) {
|
|
169
|
+
return size === fileSizes[model][file];
|
|
170
|
+
}
|
|
171
|
+
async function stateFor(model, rootOverride) {
|
|
172
|
+
const metadata = metadataFor(model);
|
|
173
|
+
const root = modelDirectory(model, rootOverride);
|
|
174
|
+
const missingFiles = [];
|
|
175
|
+
const invalidFiles = [];
|
|
176
|
+
let bytesPresent = 0;
|
|
177
|
+
for (const file of bonsaiTextModelFiles) {
|
|
178
|
+
const stat = await root.file(file).stat();
|
|
179
|
+
if (stat.type !== "file") missingFiles.push(file);
|
|
180
|
+
else {
|
|
181
|
+
bytesPresent += stat.size ?? 0;
|
|
182
|
+
if (!validFile(metadata.id, file, stat.size)) invalidFiles.push(file);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const expectedRevision = metadata.revision;
|
|
186
|
+
const manifestFile = root.file("manifest.json");
|
|
187
|
+
if ((await manifestFile.stat()).type !== "file") missingFiles.push("manifest.json");
|
|
188
|
+
else try {
|
|
189
|
+
const manifest = await manifestFile.json();
|
|
190
|
+
if (!isRecord(manifest) || manifest.formatVersion !== 1 || manifest.model !== metadata.id || manifest.revision !== expectedRevision) invalidFiles.push("manifest.json");
|
|
191
|
+
} catch {
|
|
192
|
+
invalidFiles.push("manifest.json");
|
|
193
|
+
}
|
|
194
|
+
if (!missingFiles.includes("config.json")) try {
|
|
195
|
+
const config = await root.file("config.json").json();
|
|
196
|
+
if (!validConfig(metadata.id, config)) invalidFiles.push("config.json");
|
|
197
|
+
} catch {
|
|
198
|
+
invalidFiles.push("config.json");
|
|
199
|
+
}
|
|
200
|
+
const installed = missingFiles.length === 0;
|
|
201
|
+
return {
|
|
202
|
+
model: bonsai(metadata.id),
|
|
203
|
+
metadata,
|
|
204
|
+
path: root.path,
|
|
205
|
+
installed,
|
|
206
|
+
verified: installed && invalidFiles.length === 0,
|
|
207
|
+
bytesPresent,
|
|
208
|
+
estimatedTotalBytes: metadata.estimatedDownloadBytes,
|
|
209
|
+
missingFiles,
|
|
210
|
+
invalidFiles
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function validConfig(model, value) {
|
|
214
|
+
if (!isRecord(value)) return false;
|
|
215
|
+
const expected = {
|
|
216
|
+
"ternary-bonsai-1.7b": [
|
|
217
|
+
2048,
|
|
218
|
+
28,
|
|
219
|
+
32768
|
|
220
|
+
],
|
|
221
|
+
"ternary-bonsai-4b": [
|
|
222
|
+
2560,
|
|
223
|
+
36,
|
|
224
|
+
32768
|
|
225
|
+
],
|
|
226
|
+
"ternary-bonsai-8b": [
|
|
227
|
+
4096,
|
|
228
|
+
36,
|
|
229
|
+
65536
|
|
230
|
+
],
|
|
231
|
+
"ternary-bonsai-27b": [
|
|
232
|
+
5120,
|
|
233
|
+
64,
|
|
234
|
+
262144
|
|
235
|
+
]
|
|
236
|
+
}[model];
|
|
237
|
+
const config = model === "ternary-bonsai-27b" ? value.text_config : value;
|
|
238
|
+
if (!isRecord(config)) return false;
|
|
239
|
+
const quantization = config.quantization;
|
|
240
|
+
return isRecord(quantization) && quantization.bits === 2 && quantization.group_size === 128 && config.hidden_size === expected[0] && config.num_hidden_layers === expected[1] && config.max_position_embeddings === expected[2];
|
|
241
|
+
}
|
|
242
|
+
async function prepareBonsaiTextModel(model, options = {}) {
|
|
243
|
+
const metadata = metadataFor(model);
|
|
244
|
+
const root = modelDirectoryIn(model, options.directory);
|
|
245
|
+
throwIfAborted$1(options.signal);
|
|
246
|
+
await root.create({ recursive: true });
|
|
247
|
+
const headers = options.accessToken ? { Authorization: `Bearer ${options.accessToken}` } : void 0;
|
|
248
|
+
const completedBytes = /* @__PURE__ */ new Map();
|
|
249
|
+
const emit = (file, index, progress) => {
|
|
250
|
+
completedBytes.set(file, progress.bytesWritten);
|
|
251
|
+
const modelBytesWritten = Array.from(completedBytes.values()).reduce((total, bytes) => total + bytes, 0);
|
|
252
|
+
options.onProgress?.({
|
|
253
|
+
model: metadata.id,
|
|
254
|
+
file,
|
|
255
|
+
index,
|
|
256
|
+
total: bonsaiTextModelFiles.length,
|
|
257
|
+
...progress,
|
|
258
|
+
modelBytesWritten,
|
|
259
|
+
estimatedModelBytes: metadata.estimatedDownloadBytes,
|
|
260
|
+
modelFraction: Math.min(1, modelBytesWritten / metadata.estimatedDownloadBytes)
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
for (const [fileIndex, file] of bonsaiTextModelFiles.entries()) {
|
|
264
|
+
throwIfAborted$1(options.signal);
|
|
265
|
+
const index = fileIndex + 1;
|
|
266
|
+
const destination = root.file(file);
|
|
267
|
+
const expectedBytes = fileSizes[metadata.id][file];
|
|
268
|
+
const stat = await destination.stat();
|
|
269
|
+
if (stat.type === "file" && validFile(metadata.id, file, stat.size)) {
|
|
270
|
+
emit(file, index, {
|
|
271
|
+
bytesWritten: stat.size ?? 0,
|
|
272
|
+
totalBytes: stat.size,
|
|
273
|
+
fraction: 1
|
|
274
|
+
});
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
await destination.download(resolveUrl(metadata.repo, metadata.revision, file), {
|
|
278
|
+
headers,
|
|
279
|
+
expectedBytes,
|
|
280
|
+
atomic: true,
|
|
281
|
+
resume: true,
|
|
282
|
+
signal: options.signal,
|
|
283
|
+
onProgress(progress) {
|
|
284
|
+
emit(file, index, progress);
|
|
285
|
+
}
|
|
286
|
+
}).result;
|
|
287
|
+
throwIfAborted$1(options.signal);
|
|
288
|
+
}
|
|
289
|
+
await root.file("manifest.json").write(JSON.stringify({
|
|
290
|
+
formatVersion: 1,
|
|
291
|
+
model: metadata.id,
|
|
292
|
+
revision: metadata.revision
|
|
293
|
+
}));
|
|
294
|
+
const state = await stateFor(metadata.id, root.path);
|
|
295
|
+
if (!state.verified) throw new AIError("invalid_model", `Downloaded Bonsai model is incomplete: ${[...state.missingFiles, ...state.invalidFiles].join(", ")}`);
|
|
296
|
+
return {
|
|
297
|
+
model: state.model,
|
|
298
|
+
path: root.path
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function verifyBonsaiTextModel(model, options = {}) {
|
|
302
|
+
return stateFor(model, options.path);
|
|
303
|
+
}
|
|
304
|
+
function listBonsaiTextModels(options = {}) {
|
|
305
|
+
return Promise.all(bonsaiTextModelCatalog.map((metadata) => stateFor(metadata.id, options.directory ? require_files.files.directory(options.directory, metadata.id).path : void 0)));
|
|
306
|
+
}
|
|
307
|
+
async function deleteBonsaiTextModel(model, options = {}) {
|
|
308
|
+
await modelDirectory(model, options.path).delete({ recursive: true });
|
|
309
|
+
}
|
|
310
|
+
|
|
53
311
|
//#endregion
|
|
54
312
|
//#region src/ai/image-batch.ts
|
|
55
313
|
function batchImageSeeds(count, seed) {
|
|
@@ -247,7 +505,51 @@ const flux2Klein4B8BitAbliterated = {
|
|
|
247
505
|
}
|
|
248
506
|
}
|
|
249
507
|
};
|
|
250
|
-
const
|
|
508
|
+
const ternaryBonsaiImage4B = {
|
|
509
|
+
formatVersion: 1,
|
|
510
|
+
model: "ternary-bonsai-image-4b",
|
|
511
|
+
directory: "ternary-bonsai-image-4b",
|
|
512
|
+
metadata: {
|
|
513
|
+
displayName: "Ternary Bonsai Image 4B",
|
|
514
|
+
family: "bonsai-image",
|
|
515
|
+
precision: "2-bit",
|
|
516
|
+
estimatedMemoryGB: 8,
|
|
517
|
+
quality: "high",
|
|
518
|
+
speed: "fast",
|
|
519
|
+
capabilities: ["text-to-image"]
|
|
520
|
+
},
|
|
521
|
+
components: {
|
|
522
|
+
textEncoder: {
|
|
523
|
+
repo: "prism-ml/bonsai-image-ternary-4B-mlx-2bit",
|
|
524
|
+
revision: "2c24c81b934a658ba5590cf39088ba929985b4a8",
|
|
525
|
+
path: "text-encoder",
|
|
526
|
+
files: [
|
|
527
|
+
"text_encoder-mlx-4bit/config.json",
|
|
528
|
+
"text_encoder-mlx-4bit/tokenizer.json",
|
|
529
|
+
"text_encoder-mlx-4bit/tokenizer_config.json",
|
|
530
|
+
"text_encoder-mlx-4bit/model.safetensors",
|
|
531
|
+
"text_encoder-mlx-4bit/model.safetensors.index.json"
|
|
532
|
+
]
|
|
533
|
+
},
|
|
534
|
+
transformer: {
|
|
535
|
+
repo: "prism-ml/bonsai-image-ternary-4B-mlx-2bit",
|
|
536
|
+
revision: "2c24c81b934a658ba5590cf39088ba929985b4a8",
|
|
537
|
+
path: "transformer",
|
|
538
|
+
files: [
|
|
539
|
+
"transformer-packed-mflux/config.json",
|
|
540
|
+
"transformer-packed-mflux/diffusion_pytorch_model.safetensors",
|
|
541
|
+
"transformer-packed-mflux/quantization_config.json"
|
|
542
|
+
]
|
|
543
|
+
},
|
|
544
|
+
vae: {
|
|
545
|
+
repo: "prism-ml/bonsai-image-ternary-4B-mlx-2bit",
|
|
546
|
+
revision: "2c24c81b934a658ba5590cf39088ba929985b4a8",
|
|
547
|
+
path: "vae",
|
|
548
|
+
files: ["vae/config.json", "vae/diffusion_pytorch_model.safetensors"]
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
const imageModelManifests = [flux2Klein4B8BitAbliterated, ternaryBonsaiImage4B];
|
|
251
553
|
function imageModelManifest(model) {
|
|
252
554
|
return imageModelManifests.find((manifest) => manifest.model === model);
|
|
253
555
|
}
|
|
@@ -317,15 +619,90 @@ const flux2KleinModelFiles = [
|
|
|
317
619
|
}
|
|
318
620
|
];
|
|
319
621
|
const flux2KleinTotalBytes = flux2KleinModelFiles.reduce((total, file) => total + file.bytes, 0);
|
|
622
|
+
const bonsaiRepo = ternaryBonsaiImage4B.components.textEncoder.repo;
|
|
623
|
+
const bonsaiRevision = ternaryBonsaiImage4B.components.textEncoder.revision;
|
|
624
|
+
const ternaryBonsaiImageModelFiles = [
|
|
625
|
+
{
|
|
626
|
+
repo: bonsaiRepo,
|
|
627
|
+
revision: bonsaiRevision,
|
|
628
|
+
source: "text_encoder-mlx-4bit/config.json",
|
|
629
|
+
destination: "text-encoder/config.json",
|
|
630
|
+
bytes: 937
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
repo: bonsaiRepo,
|
|
634
|
+
revision: bonsaiRevision,
|
|
635
|
+
source: "text_encoder-mlx-4bit/tokenizer.json",
|
|
636
|
+
destination: "text-encoder/tokenizer.json",
|
|
637
|
+
bytes: 11422654
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
repo: bonsaiRepo,
|
|
641
|
+
revision: bonsaiRevision,
|
|
642
|
+
source: "text_encoder-mlx-4bit/tokenizer_config.json",
|
|
643
|
+
destination: "text-encoder/tokenizer_config.json",
|
|
644
|
+
bytes: 9706
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
repo: bonsaiRepo,
|
|
648
|
+
revision: bonsaiRevision,
|
|
649
|
+
source: "text_encoder-mlx-4bit/model.safetensors",
|
|
650
|
+
destination: "text-encoder/model.safetensors",
|
|
651
|
+
bytes: 2263022529
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
repo: bonsaiRepo,
|
|
655
|
+
revision: bonsaiRevision,
|
|
656
|
+
source: "text_encoder-mlx-4bit/model.safetensors.index.json",
|
|
657
|
+
destination: "text-encoder/model.safetensors.index.json",
|
|
658
|
+
bytes: 63924
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
repo: bonsaiRepo,
|
|
662
|
+
revision: bonsaiRevision,
|
|
663
|
+
source: "transformer-packed-mflux/config.json",
|
|
664
|
+
destination: "transformer/config.json",
|
|
665
|
+
bytes: 619
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
repo: bonsaiRepo,
|
|
669
|
+
revision: bonsaiRevision,
|
|
670
|
+
source: "transformer-packed-mflux/diffusion_pytorch_model.safetensors",
|
|
671
|
+
destination: "transformer/diffusion_pytorch_model.safetensors",
|
|
672
|
+
bytes: 1425271472
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
repo: bonsaiRepo,
|
|
676
|
+
revision: bonsaiRevision,
|
|
677
|
+
source: "transformer-packed-mflux/quantization_config.json",
|
|
678
|
+
destination: "transformer/quantization_config.json",
|
|
679
|
+
bytes: 5054
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
repo: bonsaiRepo,
|
|
683
|
+
revision: bonsaiRevision,
|
|
684
|
+
source: "vae/config.json",
|
|
685
|
+
destination: "vae/config.json",
|
|
686
|
+
bytes: 821
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
repo: bonsaiRepo,
|
|
690
|
+
revision: bonsaiRevision,
|
|
691
|
+
source: "vae/diffusion_pytorch_model.safetensors",
|
|
692
|
+
destination: "vae/diffusion_pytorch_model.safetensors",
|
|
693
|
+
bytes: 168120878
|
|
694
|
+
}
|
|
695
|
+
];
|
|
696
|
+
const ternaryBonsaiImageTotalBytes = ternaryBonsaiImageModelFiles.reduce((total, file) => total + file.bytes, 0);
|
|
320
697
|
function joinModelPath(root, component) {
|
|
321
698
|
return `${root.replace(/\/+$/, "")}/${component.replace(/^\/+/, "")}`;
|
|
322
699
|
}
|
|
323
|
-
function fluxImageModelPaths(
|
|
700
|
+
function fluxImageModelPaths(path, manifest = flux2Klein4B8BitAbliterated) {
|
|
324
701
|
return {
|
|
325
|
-
|
|
326
|
-
textEncoderPath: joinModelPath(
|
|
327
|
-
transformerPath: joinModelPath(
|
|
328
|
-
vaePath: joinModelPath(
|
|
702
|
+
path,
|
|
703
|
+
textEncoderPath: joinModelPath(path, manifest.components.textEncoder.path),
|
|
704
|
+
transformerPath: joinModelPath(path, manifest.components.transformer.path),
|
|
705
|
+
vaePath: joinModelPath(path, manifest.components.vae.path)
|
|
329
706
|
};
|
|
330
707
|
}
|
|
331
708
|
|
|
@@ -2424,14 +2801,31 @@ var Tokenizer_default = Tokenizer;
|
|
|
2424
2801
|
|
|
2425
2802
|
//#endregion
|
|
2426
2803
|
//#region src/ai/image.ts
|
|
2427
|
-
function
|
|
2804
|
+
function imageModelId(model) {
|
|
2805
|
+
return typeof model === "string" ? model : model.id;
|
|
2806
|
+
}
|
|
2807
|
+
function imageModelDefinition(model) {
|
|
2808
|
+
const id = imageModelId(model);
|
|
2809
|
+
if (id === flux2Klein4B8BitAbliterated.model) return {
|
|
2810
|
+
manifest: flux2Klein4B8BitAbliterated,
|
|
2811
|
+
files: flux2KleinModelFiles,
|
|
2812
|
+
totalBytes: flux2KleinTotalBytes
|
|
2813
|
+
};
|
|
2814
|
+
if (id === ternaryBonsaiImage4B.model) return {
|
|
2815
|
+
manifest: ternaryBonsaiImage4B,
|
|
2816
|
+
files: ternaryBonsaiImageModelFiles,
|
|
2817
|
+
totalBytes: ternaryBonsaiImageTotalBytes
|
|
2818
|
+
};
|
|
2819
|
+
throw new AIError("invalid_model", `Unsupported image model: ${id}`);
|
|
2820
|
+
}
|
|
2821
|
+
function modelCacheRoot(manifest) {
|
|
2428
2822
|
return require_files.files.directory(require_files.files.paths.models, manifest.directory);
|
|
2429
2823
|
}
|
|
2430
2824
|
function assertImageModel(model) {
|
|
2431
|
-
|
|
2825
|
+
imageModelDefinition(model);
|
|
2432
2826
|
}
|
|
2433
|
-
function
|
|
2434
|
-
return
|
|
2827
|
+
function modelPathFromOptions(definition, path) {
|
|
2828
|
+
return path ?? modelCacheRoot(definition.manifest).path;
|
|
2435
2829
|
}
|
|
2436
2830
|
function downloadConcurrency(value) {
|
|
2437
2831
|
if (value === void 0) return 2;
|
|
@@ -2598,11 +2992,11 @@ function cachedLoRAFile(lora) {
|
|
|
2598
2992
|
return imageLoRARoot().directory(id).file(safeCacheName(sourceFileName(lora), "lora filename"));
|
|
2599
2993
|
}
|
|
2600
2994
|
async function ensureModelFile(input) {
|
|
2601
|
-
const destination = require_files.files.file(input.
|
|
2995
|
+
const destination = require_files.files.file(input.path, input.file.destination);
|
|
2602
2996
|
const stat = await destination.stat();
|
|
2603
2997
|
if (stat.type === "file" && stat.size === input.file.bytes) {
|
|
2604
2998
|
input.onProgress?.({
|
|
2605
|
-
model:
|
|
2999
|
+
model: input.model,
|
|
2606
3000
|
file: input.file.destination,
|
|
2607
3001
|
index: input.index,
|
|
2608
3002
|
total: input.total,
|
|
@@ -2619,7 +3013,7 @@ async function ensureModelFile(input) {
|
|
|
2619
3013
|
resume: true,
|
|
2620
3014
|
onProgress(progress) {
|
|
2621
3015
|
input.onProgress?.({
|
|
2622
|
-
model:
|
|
3016
|
+
model: input.model,
|
|
2623
3017
|
file: input.file.destination,
|
|
2624
3018
|
index: input.index,
|
|
2625
3019
|
total: input.total,
|
|
@@ -2628,16 +3022,16 @@ async function ensureModelFile(input) {
|
|
|
2628
3022
|
}
|
|
2629
3023
|
}).result;
|
|
2630
3024
|
}
|
|
2631
|
-
async function writePreparedManifest(
|
|
2632
|
-
await require_files.files.file(
|
|
3025
|
+
async function writePreparedManifest(path, manifest) {
|
|
3026
|
+
await require_files.files.file(path, "manifest.json").write(JSON.stringify(manifest, null, 2));
|
|
2633
3027
|
}
|
|
2634
|
-
async function imageModelState(model,
|
|
2635
|
-
|
|
3028
|
+
async function imageModelState(model, path) {
|
|
3029
|
+
const definition = imageModelDefinition(model);
|
|
2636
3030
|
let bytesPresent = 0;
|
|
2637
3031
|
const missingFiles = [];
|
|
2638
3032
|
const invalidFiles = [];
|
|
2639
|
-
for (const file of
|
|
2640
|
-
const stat = await require_files.files.file(
|
|
3033
|
+
for (const file of definition.files) {
|
|
3034
|
+
const stat = await require_files.files.file(path, file.destination).stat();
|
|
2641
3035
|
if (stat.type !== "file") {
|
|
2642
3036
|
missingFiles.push(file.destination);
|
|
2643
3037
|
continue;
|
|
@@ -2645,17 +3039,17 @@ async function imageModelState(model, modelRoot) {
|
|
|
2645
3039
|
bytesPresent += Math.min(stat.size ?? 0, file.bytes);
|
|
2646
3040
|
if (stat.size !== file.bytes) invalidFiles.push(file.destination);
|
|
2647
3041
|
}
|
|
2648
|
-
const
|
|
2649
|
-
const manifestStat = await require_files.files.file(modelRoot, "manifest.json").stat();
|
|
3042
|
+
const manifestStat = await require_files.files.file(path, "manifest.json").stat();
|
|
2650
3043
|
if (manifestStat.type !== "file") missingFiles.push("manifest.json");
|
|
3044
|
+
const installed = missingFiles.length === 0;
|
|
2651
3045
|
return {
|
|
2652
3046
|
model,
|
|
2653
|
-
|
|
2654
|
-
paths: fluxImageModelPaths(
|
|
3047
|
+
path,
|
|
3048
|
+
paths: fluxImageModelPaths(path, definition.manifest),
|
|
2655
3049
|
installed,
|
|
2656
3050
|
verified: installed && invalidFiles.length === 0 && manifestStat.type === "file",
|
|
2657
3051
|
bytesPresent,
|
|
2658
|
-
totalBytes:
|
|
3052
|
+
totalBytes: definition.totalBytes,
|
|
2659
3053
|
missingFiles,
|
|
2660
3054
|
invalidFiles
|
|
2661
3055
|
};
|
|
@@ -2670,8 +3064,8 @@ function tokenArray(value, name) {
|
|
|
2670
3064
|
return number;
|
|
2671
3065
|
});
|
|
2672
3066
|
}
|
|
2673
|
-
async function createFluxTokenizer(
|
|
2674
|
-
const paths = fluxImageModelPaths(
|
|
3067
|
+
async function createFluxTokenizer(path, manifest) {
|
|
3068
|
+
const paths = fluxImageModelPaths(path, manifest);
|
|
2675
3069
|
const tokenizer = new Tokenizer_default(await require_files.files.file(paths.textEncoderPath, "tokenizer.json").json(), await require_files.files.file(paths.textEncoderPath, "tokenizer_config.json").json());
|
|
2676
3070
|
const padTokenId = 151643;
|
|
2677
3071
|
return ({ prompt, maxLength }) => {
|
|
@@ -2718,18 +3112,18 @@ function assertTokenArray(value, name) {
|
|
|
2718
3112
|
if (value.length === 0) throw new AIError("invalid_message", `${name} must not be empty`);
|
|
2719
3113
|
for (const token of value) if (!Number.isInteger(token) || token < 0) throw new AIError("invalid_message", `${name} must contain token ids`);
|
|
2720
3114
|
}
|
|
2721
|
-
function imageResult(value, fallbackMetadata) {
|
|
2722
|
-
if (!isRecord(value) || typeof value.uri !== "string" || typeof value.width !== "number" || typeof value.height !== "number" || value.model !==
|
|
3115
|
+
function imageResult(value, model, fallbackMetadata) {
|
|
3116
|
+
if (!isRecord(value) || typeof value.uri !== "string" || typeof value.width !== "number" || typeof value.height !== "number" || value.model !== model) throw new AIError("image_generation_failed", "Expected generated image URI");
|
|
2723
3117
|
const result = value;
|
|
2724
3118
|
const metadata = isRecord(result.metadata) ? result.metadata : {};
|
|
2725
3119
|
return {
|
|
2726
3120
|
uri: value.uri,
|
|
2727
3121
|
width: value.width,
|
|
2728
3122
|
height: value.height,
|
|
2729
|
-
model
|
|
3123
|
+
model,
|
|
2730
3124
|
metadata: {
|
|
2731
3125
|
...fallbackMetadata,
|
|
2732
|
-
model: metadata.model ===
|
|
3126
|
+
model: metadata.model === model ? model : fallbackMetadata.model,
|
|
2733
3127
|
width: typeof metadata.width === "number" ? metadata.width : value.width,
|
|
2734
3128
|
height: typeof metadata.height === "number" ? metadata.height : value.height,
|
|
2735
3129
|
steps: typeof metadata.steps === "number" ? metadata.steps : fallbackMetadata.steps,
|
|
@@ -2778,15 +3172,18 @@ function nativeErrorMessage(error) {
|
|
|
2778
3172
|
return (error instanceof Error ? error.message : String(error)).replace(/^\[(download|tokenize|encode|denoise|decode|write)]\s*/, "");
|
|
2779
3173
|
}
|
|
2780
3174
|
async function prepareImageModel(options = {}) {
|
|
2781
|
-
const
|
|
2782
|
-
|
|
2783
|
-
|
|
3175
|
+
const definition = imageModelDefinition(options.model ?? flux2Klein4B8BitAbliterated.model);
|
|
3176
|
+
return prepareImageModelAtPath(definition, options, options.directory ? require_files.files.directory(options.directory, definition.manifest.directory).path : modelCacheRoot(definition.manifest).path);
|
|
3177
|
+
}
|
|
3178
|
+
async function prepareImageModelAtPath(definition, options, path) {
|
|
3179
|
+
const model = definition.manifest.model;
|
|
3180
|
+
const root = require_files.files.directory(path);
|
|
2784
3181
|
await root.create({ recursive: true });
|
|
2785
3182
|
const headers = options.accessToken ? { Authorization: `Bearer ${options.accessToken}` } : void 0;
|
|
2786
|
-
const total =
|
|
3183
|
+
const total = definition.files.length;
|
|
2787
3184
|
const bytesByFile = /* @__PURE__ */ new Map();
|
|
2788
3185
|
const emitProgress = (progress) => {
|
|
2789
|
-
const file =
|
|
3186
|
+
const file = definition.files.find((candidate) => candidate.destination === progress.file);
|
|
2790
3187
|
const bytesWritten = file ? Math.min(progress.bytesWritten, file.bytes) : progress.bytesWritten;
|
|
2791
3188
|
bytesByFile.set(progress.file, bytesWritten);
|
|
2792
3189
|
const modelBytesWritten = Array.from(bytesByFile.values()).reduce((sum, bytes) => sum + bytes, 0);
|
|
@@ -2794,12 +3191,12 @@ async function prepareImageModel(options = {}) {
|
|
|
2794
3191
|
...progress,
|
|
2795
3192
|
bytesWritten,
|
|
2796
3193
|
modelBytesWritten,
|
|
2797
|
-
totalModelBytes:
|
|
2798
|
-
modelFraction: modelBytesWritten /
|
|
3194
|
+
totalModelBytes: definition.totalBytes,
|
|
3195
|
+
modelFraction: modelBytesWritten / definition.totalBytes
|
|
2799
3196
|
});
|
|
2800
3197
|
};
|
|
2801
3198
|
const missingFiles = [];
|
|
2802
|
-
for (const [index, file] of
|
|
3199
|
+
for (const [index, file] of definition.files.entries()) {
|
|
2803
3200
|
const stat = await require_files.files.file(root.path, file.destination).stat();
|
|
2804
3201
|
if (stat.type === "file" && stat.size === file.bytes) emitProgress({
|
|
2805
3202
|
model,
|
|
@@ -2817,7 +3214,8 @@ async function prepareImageModel(options = {}) {
|
|
|
2817
3214
|
}
|
|
2818
3215
|
await runConcurrent(missingFiles, downloadConcurrency(options.concurrency), async ({ file, index }) => {
|
|
2819
3216
|
await ensureModelFile({
|
|
2820
|
-
|
|
3217
|
+
model,
|
|
3218
|
+
path: root.path,
|
|
2821
3219
|
file,
|
|
2822
3220
|
headers,
|
|
2823
3221
|
index,
|
|
@@ -2825,22 +3223,26 @@ async function prepareImageModel(options = {}) {
|
|
|
2825
3223
|
onProgress: emitProgress
|
|
2826
3224
|
});
|
|
2827
3225
|
});
|
|
2828
|
-
await writePreparedManifest(root.path);
|
|
3226
|
+
await writePreparedManifest(root.path, definition.manifest);
|
|
2829
3227
|
return {
|
|
2830
3228
|
model,
|
|
2831
|
-
...fluxImageModelPaths(root.path),
|
|
2832
|
-
tokenizer: await createFluxTokenizer(root.path)
|
|
3229
|
+
...fluxImageModelPaths(root.path, definition.manifest),
|
|
3230
|
+
tokenizer: await createFluxTokenizer(root.path, definition.manifest)
|
|
2833
3231
|
};
|
|
2834
3232
|
}
|
|
2835
3233
|
async function verifyImageModel(options = {}) {
|
|
2836
|
-
|
|
3234
|
+
const definition = imageModelDefinition(options.model ?? flux2Klein4B8BitAbliterated.model);
|
|
3235
|
+
return imageModelState(definition.manifest.model, modelPathFromOptions(definition, options.path));
|
|
2837
3236
|
}
|
|
2838
3237
|
async function listImageModels(options = {}) {
|
|
2839
|
-
return [await
|
|
3238
|
+
return [...await Promise.all([flux2Klein4B8BitAbliterated.model, ternaryBonsaiImage4B.model].map((model) => {
|
|
3239
|
+
const definition = imageModelDefinition(model);
|
|
3240
|
+
return imageModelState(model, options.directory ? require_files.files.directory(options.directory, definition.manifest.directory).path : modelPathFromOptions(definition, void 0));
|
|
3241
|
+
}))];
|
|
2840
3242
|
}
|
|
2841
3243
|
async function deleteImageModel(options = {}) {
|
|
2842
|
-
|
|
2843
|
-
await require_files.files.directory(
|
|
3244
|
+
const definition = imageModelDefinition(options.model ?? flux2Klein4B8BitAbliterated.model);
|
|
3245
|
+
await require_files.files.directory(modelPathFromOptions(definition, options.path)).delete({ recursive: true });
|
|
2844
3246
|
}
|
|
2845
3247
|
async function imageMemoryTelemetry() {
|
|
2846
3248
|
const value = await bridge().imageMemoryTelemetry?.();
|
|
@@ -2978,8 +3380,8 @@ async function prepareSelectedImageLoRAs(loras, model, options = {}) {
|
|
|
2978
3380
|
}
|
|
2979
3381
|
async function generateImage(options) {
|
|
2980
3382
|
throwIfAborted(options.signal);
|
|
2981
|
-
const
|
|
2982
|
-
|
|
3383
|
+
const definition = imageModelDefinition(options.model ?? flux2Klein4B8BitAbliterated.model);
|
|
3384
|
+
const model = definition.manifest.model;
|
|
2983
3385
|
const prompt = options.prompt.trim();
|
|
2984
3386
|
if (!prompt) throw new AIError("invalid_message", "Expected prompt");
|
|
2985
3387
|
const presetSize = options.aspectRatio ? imageSizeForPreset(options.aspectRatio) : void 0;
|
|
@@ -3038,16 +3440,16 @@ async function generateImage(options) {
|
|
|
3038
3440
|
const sigmaSchedule = imageSigmaSchedule(selectedLoRAs.map((lora) => lora.metadata?.sigmaSchedule));
|
|
3039
3441
|
if (sigmaSchedule && options.steps !== void 0 && options.steps !== sigmaSchedule.length) throw new AIError("invalid_model", `steps must equal the LoRA sigma schedule length (${sigmaSchedule.length})`);
|
|
3040
3442
|
const generationSteps = sigmaSchedule?.length ?? steps;
|
|
3041
|
-
const prepared = options.
|
|
3443
|
+
const prepared = options.path && options.tokenizer ? void 0 : await withImageStage("download", () => prepareImageModelAtPath(definition, {
|
|
3042
3444
|
model,
|
|
3043
3445
|
accessToken: options.accessToken,
|
|
3044
3446
|
concurrency: options.concurrency,
|
|
3045
3447
|
onProgress: options.onModelProgress
|
|
3046
|
-
}));
|
|
3448
|
+
}, options.path ?? modelCacheRoot(definition.manifest).path));
|
|
3047
3449
|
throwIfAborted(options.signal);
|
|
3048
3450
|
const tokenizer = options.tokenizer ?? prepared?.tokenizer;
|
|
3049
|
-
const
|
|
3050
|
-
if (!tokenizer || !
|
|
3451
|
+
const modelPath = options.path ?? prepared?.path;
|
|
3452
|
+
if (!tokenizer || !modelPath) throw new AIError("invalid_model", "Expected image model path and tokenizer");
|
|
3051
3453
|
const tokenized = await withImageStage("tokenize", () => tokenizer({
|
|
3052
3454
|
prompt,
|
|
3053
3455
|
maxLength: 512
|
|
@@ -3058,7 +3460,7 @@ async function generateImage(options) {
|
|
|
3058
3460
|
assertTokenArray(tokenized.attentionMask, "attentionMask");
|
|
3059
3461
|
if (tokenized.inputIds.length !== tokenized.attentionMask.length) throw new AIError("invalid_message", "inputIds and attentionMask must have the same length");
|
|
3060
3462
|
});
|
|
3061
|
-
const paths = fluxImageModelPaths(
|
|
3463
|
+
const paths = fluxImageModelPaths(modelPath, definition.manifest);
|
|
3062
3464
|
const request = {
|
|
3063
3465
|
model,
|
|
3064
3466
|
prompt,
|
|
@@ -3088,7 +3490,10 @@ async function generateImage(options) {
|
|
|
3088
3490
|
seed,
|
|
3089
3491
|
inputIds: tokenized.inputIds,
|
|
3090
3492
|
attentionMask: tokenized.attentionMask,
|
|
3091
|
-
|
|
3493
|
+
modelRoot: paths.path,
|
|
3494
|
+
textEncoderPath: paths.textEncoderPath,
|
|
3495
|
+
transformerPath: paths.transformerPath,
|
|
3496
|
+
vaePath: paths.vaePath
|
|
3092
3497
|
};
|
|
3093
3498
|
const fallbackMetadata = {
|
|
3094
3499
|
model,
|
|
@@ -3119,7 +3524,7 @@ async function generateImage(options) {
|
|
|
3119
3524
|
throwIfAborted(options.signal);
|
|
3120
3525
|
const value = await native.generateImage?.(request);
|
|
3121
3526
|
throwIfAborted(options.signal);
|
|
3122
|
-
return imageResult(value, fallbackMetadata);
|
|
3527
|
+
return imageResult(value, model, fallbackMetadata);
|
|
3123
3528
|
} finally {
|
|
3124
3529
|
subscription?.remove();
|
|
3125
3530
|
options.signal?.removeEventListener("abort", cancelNative);
|
|
@@ -3887,6 +4292,59 @@ function stableStringify(value) {
|
|
|
3887
4292
|
return JSON.stringify(value) ?? "undefined";
|
|
3888
4293
|
}
|
|
3889
4294
|
|
|
4295
|
+
//#endregion
|
|
4296
|
+
//#region src/ai/bonsai-generation.ts
|
|
4297
|
+
async function prepareBonsaiGeneration(input) {
|
|
4298
|
+
const state = await verifyBonsaiTextModel(input.model, { path: input.path });
|
|
4299
|
+
if (!state.verified) throw new AIError("model_unavailable", `${input.model.id} is not prepared. Call prepareTextModel() before generation.`);
|
|
4300
|
+
const tokenizer = new Tokenizer_default(await require_files.files.file(state.path, "tokenizer.json").json(), await require_files.files.file(state.path, "tokenizer_config.json").json());
|
|
4301
|
+
const inputIds = tokenizer.encode(formatMessages(messagesFromInput(input))).ids.map(Number);
|
|
4302
|
+
if (!inputIds.length || inputIds.some((token) => !Number.isInteger(token))) throw new AIError("invalid_message", "Tokenizer returned invalid token ids");
|
|
4303
|
+
return {
|
|
4304
|
+
request: {
|
|
4305
|
+
id: input.id,
|
|
4306
|
+
model: input.model.id,
|
|
4307
|
+
modelPath: state.path,
|
|
4308
|
+
inputIds,
|
|
4309
|
+
endTokenIds: endTokenIds(input.model.id),
|
|
4310
|
+
maxTokens: input.maxTokens,
|
|
4311
|
+
temperature: input.temperature,
|
|
4312
|
+
seed: input.seed
|
|
4313
|
+
},
|
|
4314
|
+
decode(tokenIds) {
|
|
4315
|
+
return tokenIds.length ? tokenizer.decode(tokenIds, { skip_special_tokens: true }) : "";
|
|
4316
|
+
}
|
|
4317
|
+
};
|
|
4318
|
+
}
|
|
4319
|
+
function messagesFromInput(input) {
|
|
4320
|
+
if (input.messages) {
|
|
4321
|
+
if (input.system !== void 0 || input.prompt !== void 0) throw new AIError("invalid_message", "messages cannot be combined with system or prompt");
|
|
4322
|
+
if (!input.messages.length) throw new AIError("invalid_message", "Expected at least one message");
|
|
4323
|
+
return input.messages;
|
|
4324
|
+
}
|
|
4325
|
+
if (!input.prompt?.trim()) throw new AIError("invalid_message", "Expected prompt");
|
|
4326
|
+
return [...input.system ? [{
|
|
4327
|
+
role: "system",
|
|
4328
|
+
content: input.system
|
|
4329
|
+
}] : [], {
|
|
4330
|
+
role: "user",
|
|
4331
|
+
content: input.prompt
|
|
4332
|
+
}];
|
|
4333
|
+
}
|
|
4334
|
+
function formatMessages(messages) {
|
|
4335
|
+
if (messages[0]?.role === "assistant") throw new AIError("invalid_message", "Conversation cannot begin with assistant");
|
|
4336
|
+
const parts = messages.map((message, index) => {
|
|
4337
|
+
if (!message.content.trim()) throw new AIError("invalid_message", `Message ${index + 1} is empty`);
|
|
4338
|
+
if (message.role === "system" && index !== 0) throw new AIError("invalid_message", "System message must be first");
|
|
4339
|
+
return `<|im_start|>${message.role}\n${message.content}<|im_end|>\n`;
|
|
4340
|
+
});
|
|
4341
|
+
parts.push("<|im_start|>assistant\n<think>\n\n</think>\n\n");
|
|
4342
|
+
return parts.join("");
|
|
4343
|
+
}
|
|
4344
|
+
function endTokenIds(model) {
|
|
4345
|
+
return model === "ternary-bonsai-27b" ? [248044, 248046] : [151643, 151645];
|
|
4346
|
+
}
|
|
4347
|
+
|
|
3890
4348
|
//#endregion
|
|
3891
4349
|
//#region src/ai/text.ts
|
|
3892
4350
|
function promptFromInput(options) {
|
|
@@ -3897,14 +4355,27 @@ function promptFromInput(options) {
|
|
|
3897
4355
|
if (parts.length === 0) throw new AIError("invalid_message", "Expected prompt or input");
|
|
3898
4356
|
return parts.join("\n\n");
|
|
3899
4357
|
}
|
|
3900
|
-
async function isAvailable() {
|
|
4358
|
+
async function isAvailable(model, options = {}) {
|
|
4359
|
+
if (model) return (await availability(model, options)).available;
|
|
3901
4360
|
try {
|
|
3902
4361
|
return await bridge().isAvailable?.() ?? false;
|
|
3903
4362
|
} catch {
|
|
3904
4363
|
return false;
|
|
3905
4364
|
}
|
|
3906
4365
|
}
|
|
3907
|
-
async function availability() {
|
|
4366
|
+
async function availability(model, options = {}) {
|
|
4367
|
+
if (model) {
|
|
4368
|
+
const state = await verifyBonsaiTextModel(model, options);
|
|
4369
|
+
if (!state.installed) return {
|
|
4370
|
+
available: false,
|
|
4371
|
+
reason: "not_installed"
|
|
4372
|
+
};
|
|
4373
|
+
if (!state.verified) return {
|
|
4374
|
+
available: false,
|
|
4375
|
+
reason: "invalid_model"
|
|
4376
|
+
};
|
|
4377
|
+
return { available: true };
|
|
4378
|
+
}
|
|
3908
4379
|
try {
|
|
3909
4380
|
return await bridge().availability?.() ?? {
|
|
3910
4381
|
available: false,
|
|
@@ -3974,11 +4445,28 @@ async function generate(options) {
|
|
|
3974
4445
|
const output = options.output;
|
|
3975
4446
|
const id = generationId();
|
|
3976
4447
|
const native = bridge();
|
|
3977
|
-
const toolSubscription = listenForTools(id, options.tools);
|
|
4448
|
+
const toolSubscription = listenForTools(id, "tools" in options ? options.tools : void 0);
|
|
3978
4449
|
const abort = () => void native.cancelTextGeneration?.(id);
|
|
3979
4450
|
options.signal?.addEventListener("abort", abort, { once: true });
|
|
3980
4451
|
try {
|
|
3981
4452
|
if (options.signal?.aborted) throw new AIError("generation_canceled", "Generation canceled");
|
|
4453
|
+
if (options.model) {
|
|
4454
|
+
const prepared = await prepareBonsaiGeneration({
|
|
4455
|
+
id,
|
|
4456
|
+
model: options.model,
|
|
4457
|
+
path: options.path,
|
|
4458
|
+
system: options.system,
|
|
4459
|
+
prompt: options.prompt,
|
|
4460
|
+
messages: options.messages,
|
|
4461
|
+
temperature: options.temperature,
|
|
4462
|
+
maxTokens: options.maxTokens,
|
|
4463
|
+
seed: options.seed
|
|
4464
|
+
});
|
|
4465
|
+
if (options.signal?.aborted) throw new AIError("generation_canceled", "Generation canceled");
|
|
4466
|
+
const tokenIds = await native.generateLocalText?.(prepared.request);
|
|
4467
|
+
if (!tokenIds) throw new AIError("unsupported_runtime", "Local text generation is unavailable in this runtime");
|
|
4468
|
+
return prepared.decode(tokenIds);
|
|
4469
|
+
}
|
|
3982
4470
|
const value = await native.generate?.(nativeRequest(options, id));
|
|
3983
4471
|
if (output) {
|
|
3984
4472
|
const result = output["~standard"].validate(value);
|
|
@@ -3996,6 +4484,10 @@ async function generate(options) {
|
|
|
3996
4484
|
}
|
|
3997
4485
|
}
|
|
3998
4486
|
async function* stream(options) {
|
|
4487
|
+
if (options.model) {
|
|
4488
|
+
yield* streamBonsai(options);
|
|
4489
|
+
return;
|
|
4490
|
+
}
|
|
3999
4491
|
const id = generationId();
|
|
4000
4492
|
const native = bridge();
|
|
4001
4493
|
const updates = [];
|
|
@@ -4033,6 +4525,56 @@ async function* stream(options) {
|
|
|
4033
4525
|
toolSubscription?.remove();
|
|
4034
4526
|
}
|
|
4035
4527
|
}
|
|
4528
|
+
async function* streamBonsai(options) {
|
|
4529
|
+
const id = generationId();
|
|
4530
|
+
const native = bridge();
|
|
4531
|
+
if (options.signal?.aborted) throw new AIError("generation_canceled", "Generation canceled");
|
|
4532
|
+
const prepared = await prepareBonsaiGeneration({
|
|
4533
|
+
id,
|
|
4534
|
+
model: options.model,
|
|
4535
|
+
path: options.path,
|
|
4536
|
+
system: options.system,
|
|
4537
|
+
prompt: options.prompt,
|
|
4538
|
+
messages: options.messages,
|
|
4539
|
+
temperature: options.temperature,
|
|
4540
|
+
maxTokens: options.maxTokens,
|
|
4541
|
+
seed: options.seed
|
|
4542
|
+
});
|
|
4543
|
+
if (options.signal?.aborted) throw new AIError("generation_canceled", "Generation canceled");
|
|
4544
|
+
const updates = [];
|
|
4545
|
+
let wake;
|
|
4546
|
+
let settled = false;
|
|
4547
|
+
let failure;
|
|
4548
|
+
const updateSubscription = native.addListener?.("localTextGenerationUpdate", (event) => {
|
|
4549
|
+
if (event.id !== id) return;
|
|
4550
|
+
updates.push(event.tokenIds);
|
|
4551
|
+
wake?.();
|
|
4552
|
+
wake = void 0;
|
|
4553
|
+
});
|
|
4554
|
+
const abort = () => void native.cancelTextGeneration?.(id);
|
|
4555
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
4556
|
+
const completion = native.streamLocalText?.(prepared.request).catch((error) => {
|
|
4557
|
+
failure = error;
|
|
4558
|
+
return [];
|
|
4559
|
+
}).finally(() => {
|
|
4560
|
+
settled = true;
|
|
4561
|
+
wake?.();
|
|
4562
|
+
wake = void 0;
|
|
4563
|
+
});
|
|
4564
|
+
try {
|
|
4565
|
+
if (!completion) throw new AIError("unsupported_runtime", "Local text streaming is unavailable in this runtime");
|
|
4566
|
+
while (!settled || updates.length > 0) if (updates.length > 0) yield prepared.decode(updates.shift());
|
|
4567
|
+
else await new Promise((resolve) => {
|
|
4568
|
+
wake = resolve;
|
|
4569
|
+
});
|
|
4570
|
+
await completion;
|
|
4571
|
+
if (failure) throw mapNativeError(failure);
|
|
4572
|
+
} finally {
|
|
4573
|
+
if (!settled) await native.cancelTextGeneration?.(id);
|
|
4574
|
+
options.signal?.removeEventListener("abort", abort);
|
|
4575
|
+
updateSubscription?.remove();
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4036
4578
|
|
|
4037
4579
|
//#endregion
|
|
4038
4580
|
//#region src/ai/text-hook.ts
|
|
@@ -4106,11 +4648,14 @@ function useTextGeneration() {
|
|
|
4106
4648
|
exports.AIError = AIError;
|
|
4107
4649
|
exports.ImageGenerationError = ImageGenerationError;
|
|
4108
4650
|
exports.availability = availability;
|
|
4651
|
+
exports.bonsai = bonsai;
|
|
4652
|
+
exports.bonsaiTextModelCatalog = bonsaiTextModelCatalog;
|
|
4109
4653
|
exports.buildImageGenerationRequest = buildImageGenerationRequest;
|
|
4110
4654
|
exports.clampOutpaintRect = clampOutpaintRect;
|
|
4111
4655
|
exports.defaultImageLongSide = defaultImageLongSide;
|
|
4112
4656
|
exports.deleteImageLoRA = deleteImageLoRA;
|
|
4113
4657
|
exports.deleteImageModel = deleteImageModel;
|
|
4658
|
+
exports.deleteTextModel = deleteBonsaiTextModel;
|
|
4114
4659
|
exports.flux2Klein4B8BitAbliterated = flux2Klein4B8BitAbliterated;
|
|
4115
4660
|
exports.fluxImageModelPaths = fluxImageModelPaths;
|
|
4116
4661
|
exports.generate = generate;
|
|
@@ -4127,15 +4672,18 @@ exports.isAvailable = isAvailable;
|
|
|
4127
4672
|
exports.listImageGenerationJobs = listImageGenerationJobs;
|
|
4128
4673
|
exports.listImageLoRAs = listImageLoRAs;
|
|
4129
4674
|
exports.listImageModels = listImageModels;
|
|
4675
|
+
exports.listTextModels = listBonsaiTextModels;
|
|
4130
4676
|
exports.normalizeImageSize = normalizeImageSize;
|
|
4131
4677
|
exports.prepareImageLoRA = prepareImageLoRA;
|
|
4132
4678
|
exports.prepareImageModel = prepareImageModel;
|
|
4679
|
+
exports.prepareTextModel = prepareBonsaiTextModel;
|
|
4133
4680
|
exports.rectForSourceScale = rectForSourceScale;
|
|
4134
4681
|
exports.roundImageDimension = roundImageDimension;
|
|
4135
4682
|
exports.roundedImageSize = roundedImageSize;
|
|
4136
4683
|
exports.scheduleImageGeneration = scheduleImageGeneration;
|
|
4137
4684
|
exports.shape = shape;
|
|
4138
4685
|
exports.stream = stream;
|
|
4686
|
+
exports.ternaryBonsaiImage4B = ternaryBonsaiImage4B;
|
|
4139
4687
|
exports.unloadImageModel = unloadImageModel;
|
|
4140
4688
|
exports.upscaleImage = upscaleImage;
|
|
4141
4689
|
exports.useImageComposer = useImageComposer;
|
|
@@ -4143,4 +4691,5 @@ exports.useImageGeneration = useImageGeneration;
|
|
|
4143
4691
|
exports.useImageModel = useImageModel;
|
|
4144
4692
|
exports.useTextGeneration = useTextGeneration;
|
|
4145
4693
|
exports.verifyImageLoRA = verifyImageLoRA;
|
|
4146
|
-
exports.verifyImageModel = verifyImageModel;
|
|
4694
|
+
exports.verifyImageModel = verifyImageModel;
|
|
4695
|
+
exports.verifyTextModel = verifyBonsaiTextModel;
|