@rslsp1/fa-app-tools 6.1.0 → 7.2.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/dist/{chunk-GHKN2VX3.mjs → chunk-5QA6DTJ6.mjs} +428 -15
- package/dist/{hfEventTypes-Dkfrcd_t.d.mts → hfEventTypes-BUztJySs.d.mts} +1 -11
- package/dist/{hfEventTypes-C_PX6RFR.d.ts → hfEventTypes-fAdA-0A9.d.ts} +1 -11
- package/dist/index.d.mts +18 -49
- package/dist/index.d.ts +18 -49
- package/dist/index.js +728 -1522
- package/dist/index.mjs +230 -723
- package/dist/szenen.d.mts +331 -221
- package/dist/szenen.d.ts +331 -221
- package/dist/szenen.js +121 -4
- package/dist/szenen.mjs +21 -4
- package/package.json +1 -1
- package/dist/chunk-NBUS7AWR.mjs +0 -418
- package/dist/chunk-UFXDXENC.mjs +0 -218
- package/dist/hfStateService-W7KO2FOH.mjs +0 -56
- package/dist/project-O4ORKXY5.mjs +0 -8
|
@@ -4,15 +4,396 @@ import {
|
|
|
4
4
|
preferredVersionId,
|
|
5
5
|
resolvePreferredHead
|
|
6
6
|
} from "./chunk-FUY2OCVC.mjs";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
7
|
+
|
|
8
|
+
// src/lib/szenen/szenenSnapshot.ts
|
|
9
|
+
var SZENEN_SNAPSHOT_SCHEMA_VERSION = 1;
|
|
10
|
+
var SZENEN_SNAPSHOT_PREFIX = "szenen-state-";
|
|
11
|
+
var SZENEN_SNAPSHOT_FILE = "szenen-state.json";
|
|
12
|
+
function szenenSnapshotFilename(maxTs) {
|
|
13
|
+
const stamp = new Date(maxTs).toISOString().replace(/:/g, "-").replace(".", "-");
|
|
14
|
+
return `${SZENEN_SNAPSHOT_PREFIX}${stamp}.json`;
|
|
15
|
+
}
|
|
16
|
+
function istSzenenSnapshotDatei(name) {
|
|
17
|
+
return name.startsWith(SZENEN_SNAPSHOT_PREFIX) && name.endsWith(".json");
|
|
18
|
+
}
|
|
19
|
+
var maxTsOf = (events) => {
|
|
20
|
+
let max = 0;
|
|
21
|
+
for (const event of events) if (event.ts > max) max = event.ts;
|
|
22
|
+
return max;
|
|
23
|
+
};
|
|
24
|
+
function buildSzenenSnapshot(base, neue, neuePfade = []) {
|
|
25
|
+
const events = [...base?.events ?? [], ...neue];
|
|
26
|
+
const paths = [...base?.consumed.paths ?? [], ...neuePfade.map(basisname)];
|
|
27
|
+
return {
|
|
28
|
+
schema_version: SZENEN_SNAPSHOT_SCHEMA_VERSION,
|
|
29
|
+
consumed: { count: events.length, max_ts: maxTsOf(events), paths },
|
|
30
|
+
events
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function basisname(pfad) {
|
|
34
|
+
return pfad.split("/").pop() ?? pfad;
|
|
35
|
+
}
|
|
36
|
+
function aufgenommeneDateien(snapshot) {
|
|
37
|
+
return new Set(snapshot ? snapshot.consumed.paths : []);
|
|
38
|
+
}
|
|
39
|
+
function parseSzenenSnapshot(raw, herkunft) {
|
|
40
|
+
if (!raw || typeof raw !== "object") {
|
|
41
|
+
throw new Error(`Szenen-Snapshot ${herkunft}: kein Objekt, sondern ${typeof raw}.`);
|
|
42
|
+
}
|
|
43
|
+
const obj = raw;
|
|
44
|
+
if (obj.schema_version !== SZENEN_SNAPSHOT_SCHEMA_VERSION) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Szenen-Snapshot ${herkunft}: schema_version ${String(obj.schema_version)} wird nicht gelesen, erwartet ${SZENEN_SNAPSHOT_SCHEMA_VERSION}.`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
if (!Array.isArray(obj.events)) {
|
|
50
|
+
throw new Error(`Szenen-Snapshot ${herkunft}: "events" fehlt oder ist kein Array.`);
|
|
51
|
+
}
|
|
52
|
+
const events = obj.events;
|
|
53
|
+
for (const [i, event] of events.entries()) {
|
|
54
|
+
if (!event || typeof event.type !== "string" || typeof event.ts !== "number") {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Szenen-Snapshot ${herkunft}: Ereignis ${i} hat keinen Typ oder keinen Zeitstempel.`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const consumed = obj.consumed;
|
|
61
|
+
if (!consumed || typeof consumed.count !== "number" || typeof consumed.max_ts !== "number") {
|
|
62
|
+
throw new Error(`Szenen-Snapshot ${herkunft}: "consumed" fehlt oder ist unvollstaendig.`);
|
|
63
|
+
}
|
|
64
|
+
if (!Array.isArray(consumed.paths)) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Szenen-Snapshot ${herkunft}: "consumed.paths" fehlt. Ohne die aufgenommenen Dateinamen laesst sich nicht entscheiden, welche Datei in events/ noch aussteht.`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if (consumed.count !== events.length) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Szenen-Snapshot ${herkunft}: consumed.count ${consumed.count} passt nicht zu ${events.length} Ereignis(sen) in der Datei \u2014 die Datei ist unvollstaendig.`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
const echt = maxTsOf(events);
|
|
75
|
+
if (consumed.max_ts !== echt) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Szenen-Snapshot ${herkunft}: consumed.max_ts ${consumed.max_ts} passt nicht zum groessten Zeitstempel ${echt} in der Datei.`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return { schema_version: SZENEN_SNAPSHOT_SCHEMA_VERSION, consumed, events };
|
|
81
|
+
}
|
|
82
|
+
function mergeSnapshotAndPending(snapshot, pending, bereitsImSnapshot = 0) {
|
|
83
|
+
if (!snapshot) return { events: pending, bereitsImSnapshot };
|
|
84
|
+
return { events: [...snapshot.events, ...pending], bereitsImSnapshot };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/lib/hfEventTypes.ts
|
|
88
|
+
var HF_BASE = "https://huggingface.co";
|
|
89
|
+
var HF_REPO = "RolandSch/fa-app-state";
|
|
90
|
+
var CURRENT_EVENT_VERSION = { major: 1, minor: 0 };
|
|
91
|
+
var SPEICHERBARE_BILDTYPEN = ["image/png", "image/jpeg", "image/webp"];
|
|
92
|
+
function bildEndungFuer(mimeType) {
|
|
93
|
+
if (mimeType === "image/png") return "png";
|
|
94
|
+
if (mimeType === "image/webp") return "webp";
|
|
95
|
+
return "jpg";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/lib/hfEventLog.ts
|
|
99
|
+
function treeUrl(namespace, subdir = "") {
|
|
100
|
+
const parts = [namespace.replace(/\/$/, ""), subdir].filter(Boolean).join("/");
|
|
101
|
+
return `${HF_BASE}/api/datasets/${HF_REPO}/tree/main${parts ? "/" + parts : ""}`;
|
|
102
|
+
}
|
|
103
|
+
var DROSSEL_VERSUCHE = 4;
|
|
104
|
+
var DROSSEL_BASIS_MS = 400;
|
|
105
|
+
var schlafen = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
106
|
+
var HF_LESE_ZEITLIMIT_MS = 2e4;
|
|
107
|
+
async function holeMitZeitlimit(url, init) {
|
|
108
|
+
const abbruch = new AbortController();
|
|
109
|
+
const timer = setTimeout(() => abbruch.abort(), HF_LESE_ZEITLIMIT_MS);
|
|
110
|
+
try {
|
|
111
|
+
return await fetch(url, { ...init, signal: abbruch.signal });
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (abbruch.signal.aborted) {
|
|
114
|
+
throw new Error(`HF-Anfrage ohne Antwort nach ${HF_LESE_ZEITLIMIT_MS}ms abgebrochen: ${url}`);
|
|
115
|
+
}
|
|
116
|
+
throw error;
|
|
117
|
+
} finally {
|
|
118
|
+
clearTimeout(timer);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function mitGeduld(anfrage) {
|
|
122
|
+
let letzte = null;
|
|
123
|
+
for (let versuch = 0; versuch < DROSSEL_VERSUCHE; versuch += 1) {
|
|
124
|
+
const res = await anfrage();
|
|
125
|
+
if (res.status !== 429 && res.status < 500) return res;
|
|
126
|
+
letzte = res;
|
|
127
|
+
if (versuch === DROSSEL_VERSUCHE - 1) break;
|
|
128
|
+
const angesagt = Number(res.headers.get("retry-after"));
|
|
129
|
+
const warten2 = Number.isFinite(angesagt) && angesagt > 0 ? angesagt * 1e3 : DROSSEL_BASIS_MS * 2 ** versuch;
|
|
130
|
+
await schlafen(warten2);
|
|
131
|
+
}
|
|
132
|
+
return letzte;
|
|
133
|
+
}
|
|
134
|
+
async function hfListDir(namespace, subdir, token) {
|
|
135
|
+
const res = await mitGeduld(() => holeMitZeitlimit(treeUrl(namespace, subdir), {
|
|
136
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
137
|
+
}));
|
|
138
|
+
if (res.status === 404) return [];
|
|
139
|
+
if (!res.ok) throw new Error(`HF list failed: ${res.status} ${res.statusText}`);
|
|
140
|
+
return res.json();
|
|
141
|
+
}
|
|
142
|
+
async function hfDownloadJsonByPath(repoPath, token) {
|
|
143
|
+
const res = await mitGeduld(() => holeMitZeitlimit(
|
|
144
|
+
`${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
|
|
145
|
+
{ headers: { Authorization: `Bearer ${token}` } }
|
|
146
|
+
));
|
|
147
|
+
if (!res.ok) throw new Error(`HF download failed: ${res.status}`);
|
|
148
|
+
return res.json();
|
|
149
|
+
}
|
|
150
|
+
async function hfDownloadBinaryByPath(repoPath, token) {
|
|
151
|
+
const res = await mitGeduld(() => holeMitZeitlimit(
|
|
152
|
+
`${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
|
|
153
|
+
{ headers: { Authorization: `Bearer ${token}` } }
|
|
154
|
+
));
|
|
155
|
+
if (!res.ok) throw new Error(`HF download binary failed: ${res.status}`);
|
|
156
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
157
|
+
}
|
|
158
|
+
function toBase64(text) {
|
|
159
|
+
const bytes = new TextEncoder().encode(text);
|
|
160
|
+
let binary = "";
|
|
161
|
+
bytes.forEach((b) => binary += String.fromCharCode(b));
|
|
162
|
+
return btoa(binary);
|
|
163
|
+
}
|
|
164
|
+
async function hfUploadSmallFile(repoPath, content, token, summary = `Update ${repoPath}`) {
|
|
165
|
+
const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
|
|
166
|
+
method: "POST",
|
|
167
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/x-ndjson" },
|
|
168
|
+
body: [
|
|
169
|
+
JSON.stringify({ key: "header", value: { summary, description: "" } }),
|
|
170
|
+
JSON.stringify({ key: "file", value: { path: repoPath, encoding: "base64", content: toBase64(content) } })
|
|
171
|
+
].join("\n")
|
|
172
|
+
});
|
|
173
|
+
if (!res.ok) {
|
|
174
|
+
const err = await res.text().catch(() => "");
|
|
175
|
+
throw new Error(`HF upload failed: ${res.status} \u2014 ${err.slice(0, 200)}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function hfBatchArchive(moves, token, summary) {
|
|
179
|
+
const lines = [
|
|
180
|
+
JSON.stringify({ key: "header", value: { summary, description: "" } }),
|
|
181
|
+
...moves.flatMap(({ from, to, content }) => [
|
|
182
|
+
JSON.stringify({ key: "file", value: { path: to, encoding: "base64", content: toBase64(content) } }),
|
|
183
|
+
JSON.stringify({ key: "deletedFile", value: { path: from } })
|
|
184
|
+
])
|
|
185
|
+
];
|
|
186
|
+
const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
|
|
187
|
+
method: "POST",
|
|
188
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/x-ndjson" },
|
|
189
|
+
body: lines.join("\n")
|
|
190
|
+
});
|
|
191
|
+
if (!res.ok) {
|
|
192
|
+
const err = await res.text().catch(() => "");
|
|
193
|
+
throw new Error(`HF batch archive failed: ${res.status} \u2014 ${err.slice(0, 200)}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function tsFromEventPath(repoPath) {
|
|
197
|
+
const filename = repoPath.split("/").pop() || "";
|
|
198
|
+
const base = filename.replace(/_[^_]+\.json$/, "");
|
|
199
|
+
const iso = base.replace(/T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z$/, "T$1:$2:$3.$4Z");
|
|
200
|
+
const ts = Date.parse(iso);
|
|
201
|
+
return isNaN(ts) ? 0 : ts;
|
|
202
|
+
}
|
|
203
|
+
var EVENTDOWNLOAD_PARALLELITAET = 3;
|
|
204
|
+
async function mitBegrenzterParallelitaet(count, limit, task) {
|
|
205
|
+
let cursor = 0;
|
|
206
|
+
let ersterFehler;
|
|
207
|
+
let fehlerAufgetreten = false;
|
|
208
|
+
const worker = async () => {
|
|
209
|
+
while (cursor < count) {
|
|
210
|
+
const index = cursor++;
|
|
211
|
+
try {
|
|
212
|
+
await task(index);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
if (!fehlerAufgetreten) {
|
|
215
|
+
fehlerAufgetreten = true;
|
|
216
|
+
ersterFehler = error;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
await Promise.all(Array.from({ length: Math.min(limit, count) }, worker));
|
|
222
|
+
if (fehlerAufgetreten) throw ersterFehler;
|
|
223
|
+
}
|
|
224
|
+
async function loadPendingEvents(namespace, token, sinceTs, knownPaths) {
|
|
225
|
+
const files = await hfListDir(namespace, "events", token);
|
|
226
|
+
const pending = files.filter((f) => f.type === "file" && tsFromEventPath(f.path) > sinceTs && !knownPaths?.has(f.path)).sort((a, b) => a.path.localeCompare(b.path));
|
|
227
|
+
if (!pending.length) return [];
|
|
228
|
+
const raw = new Array(pending.length);
|
|
229
|
+
await mitBegrenzterParallelitaet(pending.length, EVENTDOWNLOAD_PARALLELITAET, async (index) => {
|
|
230
|
+
raw[index] = await hfDownloadJsonByPath(pending[index].path, token);
|
|
231
|
+
});
|
|
232
|
+
for (const f of pending) knownPaths?.add(f.path);
|
|
233
|
+
return raw.flatMap((e) => Array.isArray(e) ? e : [e]);
|
|
234
|
+
}
|
|
235
|
+
async function findLatestSzenenSnapshotPath(namespace, token) {
|
|
236
|
+
const files = await hfListDir(namespace, "", token);
|
|
237
|
+
const treffer = files.filter((f) => f.type === "file" && istSzenenSnapshotDatei(f.path.split("/").pop() || "")).map((f) => f.path).sort((a, b) => b.localeCompare(a));
|
|
238
|
+
return treffer[0] ?? null;
|
|
239
|
+
}
|
|
240
|
+
async function loadLatestSzenenSnapshot(namespace, token) {
|
|
241
|
+
const pfad = await findLatestSzenenSnapshotPath(namespace, token);
|
|
242
|
+
if (!pfad) return null;
|
|
243
|
+
return parseSzenenSnapshot(await hfDownloadJsonByPath(pfad, token), pfad);
|
|
244
|
+
}
|
|
245
|
+
var SESSION_CLIENT_ID = `client-${crypto.randomUUID().slice(0, 8)}`;
|
|
246
|
+
function getSessionClientId() {
|
|
247
|
+
return SESSION_CLIENT_ID;
|
|
248
|
+
}
|
|
249
|
+
function tsToFilename(ts) {
|
|
250
|
+
return new Date(ts).toISOString().replace(/:/g, "-").replace(".", "-");
|
|
251
|
+
}
|
|
252
|
+
var writeSeq = 0;
|
|
253
|
+
var lastOrderedTs = 0;
|
|
254
|
+
async function writeHFEvent(namespace, token, type, payload, prevTs, options) {
|
|
255
|
+
const wallTs = Date.now();
|
|
256
|
+
const ts = options?.ordered ? Math.max(wallTs, lastOrderedTs) : wallTs;
|
|
257
|
+
if (options?.ordered) lastOrderedTs = ts;
|
|
258
|
+
const uuid = crypto.randomUUID().slice(0, 8);
|
|
259
|
+
const event = {
|
|
260
|
+
v: CURRENT_EVENT_VERSION,
|
|
261
|
+
type,
|
|
262
|
+
ts,
|
|
263
|
+
prevTs,
|
|
264
|
+
clientId: SESSION_CLIENT_ID,
|
|
265
|
+
payload
|
|
266
|
+
};
|
|
267
|
+
const suffix = options?.ordered ? `${String(++writeSeq % 1e6).padStart(6, "0")}${uuid}` : uuid;
|
|
268
|
+
const filename = `${tsToFilename(ts)}_${suffix}.json`;
|
|
269
|
+
const repoPath = `${namespace}events/${filename}`;
|
|
270
|
+
await hfUploadSmallFile(repoPath, JSON.stringify(event, null, 2), token, `Event: ${type}`);
|
|
271
|
+
return event;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/lib/hfStateService.ts
|
|
275
|
+
import JSZip from "jszip";
|
|
276
|
+
import { uploadFile } from "@huggingface/hub";
|
|
277
|
+
var HF_TOKEN_KEY = "hf-token";
|
|
278
|
+
function getHFToken() {
|
|
279
|
+
try {
|
|
280
|
+
return localStorage.getItem(HF_TOKEN_KEY);
|
|
281
|
+
} catch {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function setHFToken(token) {
|
|
286
|
+
try {
|
|
287
|
+
localStorage.setItem(HF_TOKEN_KEY, token);
|
|
288
|
+
} catch {
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async function loadHFState(namespace, token) {
|
|
292
|
+
const files = await hfListDir(namespace, "", token);
|
|
293
|
+
const stateFiles = files.filter((f) => f.type === "file" && /^state-[\dT\-]+Z\.zip$/.test(f.path.split("/").pop() || "")).sort((a, b) => b.path.localeCompare(a.path));
|
|
294
|
+
if (!stateFiles.length) return null;
|
|
295
|
+
const zipBytes = await hfDownloadBinaryByPath(stateFiles[0].path, token);
|
|
296
|
+
const zip = await JSZip.loadAsync(zipBytes);
|
|
297
|
+
const [metadataStr, tagsStr, metaStr, openTasksStr] = await Promise.all([
|
|
298
|
+
zip.file("metadata.json")?.async("string"),
|
|
299
|
+
zip.file("tags.json")?.async("string"),
|
|
300
|
+
zip.file("state_meta.json")?.async("string"),
|
|
301
|
+
zip.file("open_tasks.json")?.async("string")
|
|
302
|
+
// optional — ältere Snapshots haben es nicht
|
|
303
|
+
]);
|
|
304
|
+
if (!metadataStr || !tagsStr || !metaStr) throw new Error("state.zip: fehlende Pflicht-Dateien");
|
|
305
|
+
return {
|
|
306
|
+
metadata: JSON.parse(metadataStr),
|
|
307
|
+
tags: JSON.parse(tagsStr),
|
|
308
|
+
meta: JSON.parse(metaStr),
|
|
309
|
+
openTasks: openTasksStr ? JSON.parse(openTasksStr) : {}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
async function hfBootstrapFromLegacy(namespace, token, onProgress) {
|
|
313
|
+
const log = (msg) => {
|
|
314
|
+
if (onProgress) onProgress(msg);
|
|
315
|
+
};
|
|
316
|
+
log("Lese tags.json \u2026");
|
|
317
|
+
const tags = await fetch(
|
|
318
|
+
`${HF_BASE}/datasets/${HF_REPO}/resolve/main/tags.json?download=true`,
|
|
319
|
+
{ headers: { Authorization: `Bearer ${token}` } }
|
|
320
|
+
).then((r) => r.ok ? r.json() : null).catch(() => null);
|
|
321
|
+
log("Lese metadata.json \u2026");
|
|
322
|
+
const metadata = await fetch(
|
|
323
|
+
`${HF_BASE}/datasets/${HF_REPO}/resolve/main/metadata.json?download=true`,
|
|
324
|
+
{ headers: { Authorization: `Bearer ${token}` } }
|
|
325
|
+
).then((r) => r.ok ? r.json() : []).catch(() => []);
|
|
326
|
+
const snapshot = {
|
|
327
|
+
metadata: Array.isArray(metadata) ? metadata : [],
|
|
328
|
+
tags: tags?.by_category ? tags : { by_category: {}, all: [] },
|
|
329
|
+
meta: { consolidatedAt: Date.now(), version: 1 }
|
|
330
|
+
};
|
|
331
|
+
log(`Erstelle state.zip (${snapshot.metadata.length} Bilder, ${snapshot.tags.all?.length ?? 0} Tags) \u2026`);
|
|
332
|
+
const zip = new JSZip();
|
|
333
|
+
zip.file("metadata.json", JSON.stringify(snapshot.metadata, null, 2));
|
|
334
|
+
zip.file("tags.json", JSON.stringify(snapshot.tags, null, 2));
|
|
335
|
+
zip.file("state_meta.json", JSON.stringify(snapshot.meta, null, 2));
|
|
336
|
+
const zipBytes = await zip.generateAsync({ type: "uint8array", compression: "DEFLATE" });
|
|
337
|
+
const ts = snapshot.meta.consolidatedAt;
|
|
338
|
+
const stateFilename = `state-${new Date(ts).toISOString().replace(/:/g, "-").replace(".", "-")}.zip`;
|
|
339
|
+
const repoPath = `${namespace}${stateFilename}`;
|
|
340
|
+
log("Lade state.zip hoch \u2026");
|
|
341
|
+
await uploadFile({
|
|
342
|
+
repo: { type: "dataset", name: HF_REPO },
|
|
343
|
+
credentials: { accessToken: token },
|
|
344
|
+
file: { path: repoPath, content: new Blob([zipBytes.buffer], { type: "application/zip" }) },
|
|
345
|
+
branch: "main",
|
|
346
|
+
commitTitle: `Bootstrap: initialer State aus Legacy-Daten`
|
|
347
|
+
});
|
|
348
|
+
log(`Fertig \u2014 ${stateFilename}`);
|
|
349
|
+
}
|
|
350
|
+
async function hfUploadImage(base64, id, token, mimeType = "image/jpeg") {
|
|
351
|
+
const ext = bildEndungFuer(mimeType);
|
|
352
|
+
const binary = atob(base64);
|
|
353
|
+
const bytes = new Uint8Array(binary.length);
|
|
354
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
355
|
+
await uploadFile({
|
|
356
|
+
repo: { type: "dataset", name: HF_REPO },
|
|
357
|
+
credentials: { accessToken: token },
|
|
358
|
+
file: { path: `images/${id}.${ext}`, content: new Blob([bytes], { type: mimeType }) },
|
|
359
|
+
branch: "main",
|
|
360
|
+
commitTitle: `Add image ${id}`
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
async function hfLoadImageAsBase64(id, token, namespace, filename, on404, mimeType, hasThumb) {
|
|
364
|
+
const fetchBase64 = async (repoPath) => {
|
|
365
|
+
try {
|
|
366
|
+
const res = await fetch(
|
|
367
|
+
`${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
|
|
368
|
+
{ headers: { Authorization: `Bearer ${token}` } }
|
|
369
|
+
);
|
|
370
|
+
if (!res.ok) {
|
|
371
|
+
if (on404 && on404()) return "ABORT";
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
const blob = await res.blob();
|
|
375
|
+
return new Promise((resolve, reject) => {
|
|
376
|
+
const reader = new FileReader();
|
|
377
|
+
reader.onload = () => resolve(reader.result.split(",")[1]);
|
|
378
|
+
reader.onerror = reject;
|
|
379
|
+
reader.readAsDataURL(blob);
|
|
380
|
+
});
|
|
381
|
+
} catch {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
if (filename) {
|
|
386
|
+
if (hasThumb) {
|
|
387
|
+
const thumb = await fetchBase64(`thumbs/${filename}`);
|
|
388
|
+
if (thumb && thumb !== "ABORT") return thumb;
|
|
389
|
+
}
|
|
390
|
+
const result2 = await fetchBase64(`images/${filename}`);
|
|
391
|
+
return result2 === "ABORT" ? null : result2;
|
|
392
|
+
}
|
|
393
|
+
const ext = bildEndungFuer(mimeType);
|
|
394
|
+
const result = await fetchBase64(`images/${id}.${ext}`);
|
|
395
|
+
return result === "ABORT" ? null : result;
|
|
396
|
+
}
|
|
16
397
|
|
|
17
398
|
// src/lib/szenen/viewPort.ts
|
|
18
399
|
function capabilitiesOf(port) {
|
|
@@ -5318,8 +5699,8 @@ async function leseBilderAusZip(datei) {
|
|
|
5318
5699
|
`${name} ist ${mb(datei.size)} MB gro\xDF \u2014 die Grenze liegt bei ${UPLOAD_MAX_BYTES / 1024 / 1024} MB.`
|
|
5319
5700
|
);
|
|
5320
5701
|
}
|
|
5321
|
-
const { default:
|
|
5322
|
-
const archiv = await
|
|
5702
|
+
const { default: JSZip2 } = await import("jszip");
|
|
5703
|
+
const archiv = await JSZip2.loadAsync(datei).catch((fehler4) => {
|
|
5323
5704
|
throw new Error(
|
|
5324
5705
|
`${name} konnte nicht als ZIP gelesen werden: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
|
|
5325
5706
|
);
|
|
@@ -13409,11 +13790,20 @@ function createHfElementStore(options) {
|
|
|
13409
13790
|
|
|
13410
13791
|
// src/lib/szenen/hfEventChannel.ts
|
|
13411
13792
|
function createHfEventChannel(options) {
|
|
13412
|
-
const { namespace, token, sinceTs = 0 } = options;
|
|
13793
|
+
const { namespace, token, sinceTs = 0, ohneSnapshot = false } = options;
|
|
13413
13794
|
const knownPaths = /* @__PURE__ */ new Set();
|
|
13414
13795
|
let cachedRawEvents = [];
|
|
13415
13796
|
let syncInFlight = null;
|
|
13797
|
+
let snapshot = null;
|
|
13798
|
+
let snapshotGeladen = ohneSnapshot;
|
|
13416
13799
|
const refresh = async () => {
|
|
13800
|
+
if (!snapshotGeladen) {
|
|
13801
|
+
snapshot = await loadLatestSzenenSnapshot(namespace, token);
|
|
13802
|
+
snapshotGeladen = true;
|
|
13803
|
+
for (const name of aufgenommeneDateien(snapshot)) {
|
|
13804
|
+
knownPaths.add(`${namespace}events/${name}`);
|
|
13805
|
+
}
|
|
13806
|
+
}
|
|
13417
13807
|
const neue = await loadPendingEvents(namespace, token, sinceTs, knownPaths);
|
|
13418
13808
|
if (neue.length) cachedRawEvents = [...cachedRawEvents, ...neue];
|
|
13419
13809
|
};
|
|
@@ -13428,12 +13818,13 @@ function createHfEventChannel(options) {
|
|
|
13428
13818
|
return {
|
|
13429
13819
|
async readEvents() {
|
|
13430
13820
|
await sync();
|
|
13431
|
-
|
|
13821
|
+
const pending = cachedRawEvents.filter((event) => typeof event.type === "string" && event.type.startsWith(SZENEN_EVENT_PREFIX)).map((event) => ({
|
|
13432
13822
|
type: event.type,
|
|
13433
13823
|
payload: event.payload,
|
|
13434
13824
|
clientId: event.clientId,
|
|
13435
13825
|
ts: event.ts
|
|
13436
13826
|
}));
|
|
13827
|
+
return mergeSnapshotAndPending(snapshot, pending).events;
|
|
13437
13828
|
},
|
|
13438
13829
|
async appendEvent(type, payload) {
|
|
13439
13830
|
if (!type.startsWith(SZENEN_EVENT_PREFIX)) {
|
|
@@ -15099,7 +15490,7 @@ function createHfImageStore(options) {
|
|
|
15099
15490
|
const now = options.now ?? (() => Date.now());
|
|
15100
15491
|
const versuche = options.leseVersuche ?? 4;
|
|
15101
15492
|
const basisMs = options.leseBasisMs ?? 400;
|
|
15102
|
-
const
|
|
15493
|
+
const schlafen2 = options.schlafen ?? warten;
|
|
15103
15494
|
const abgelegt = /* @__PURE__ */ new Map();
|
|
15104
15495
|
const vorschauen = /* @__PURE__ */ new Map();
|
|
15105
15496
|
const bekanntePfade = /* @__PURE__ */ new Set();
|
|
@@ -15196,7 +15587,7 @@ function createHfImageStore(options) {
|
|
|
15196
15587
|
}
|
|
15197
15588
|
const pfadTyp = typ ?? "image/png";
|
|
15198
15589
|
for (let versuch = 0; versuch < versuche; versuch += 1) {
|
|
15199
|
-
if (versuch > 0) await
|
|
15590
|
+
if (versuch > 0) await schlafen2(basisMs * 2 ** (versuch - 1));
|
|
15200
15591
|
const base64 = await hfLoadImageAsBase64(
|
|
15201
15592
|
id,
|
|
15202
15593
|
token,
|
|
@@ -16021,6 +16412,28 @@ function createFlowSzenenViewPort(options) {
|
|
|
16021
16412
|
}
|
|
16022
16413
|
|
|
16023
16414
|
export {
|
|
16415
|
+
SZENEN_SNAPSHOT_SCHEMA_VERSION,
|
|
16416
|
+
SZENEN_SNAPSHOT_PREFIX,
|
|
16417
|
+
SZENEN_SNAPSHOT_FILE,
|
|
16418
|
+
szenenSnapshotFilename,
|
|
16419
|
+
istSzenenSnapshotDatei,
|
|
16420
|
+
buildSzenenSnapshot,
|
|
16421
|
+
parseSzenenSnapshot,
|
|
16422
|
+
mergeSnapshotAndPending,
|
|
16423
|
+
hfListDir,
|
|
16424
|
+
hfDownloadBinaryByPath,
|
|
16425
|
+
hfUploadSmallFile,
|
|
16426
|
+
hfBatchArchive,
|
|
16427
|
+
tsFromEventPath,
|
|
16428
|
+
loadPendingEvents,
|
|
16429
|
+
getSessionClientId,
|
|
16430
|
+
writeHFEvent,
|
|
16431
|
+
getHFToken,
|
|
16432
|
+
setHFToken,
|
|
16433
|
+
loadHFState,
|
|
16434
|
+
hfBootstrapFromLegacy,
|
|
16435
|
+
hfUploadImage,
|
|
16436
|
+
hfLoadImageAsBase64,
|
|
16024
16437
|
applyEvents,
|
|
16025
16438
|
capabilitiesOf,
|
|
16026
16439
|
unsupportedReason,
|
|
@@ -43,16 +43,6 @@ interface ProjectSettings {
|
|
|
43
43
|
seed: number;
|
|
44
44
|
seedMode: 'random' | 'fixed';
|
|
45
45
|
}
|
|
46
|
-
interface ProjectMeta {
|
|
47
|
-
id: string;
|
|
48
|
-
name: string;
|
|
49
|
-
saved_at: string;
|
|
50
|
-
size: number;
|
|
51
|
-
}
|
|
52
|
-
interface SyncDiff {
|
|
53
|
-
localOnly: Generation[];
|
|
54
|
-
serverOnly: Generation[];
|
|
55
|
-
}
|
|
56
46
|
interface LabFrame {
|
|
57
47
|
id: string;
|
|
58
48
|
base64: string;
|
|
@@ -238,4 +228,4 @@ interface TaskRequestedPayload {
|
|
|
238
228
|
expiresAt?: number;
|
|
239
229
|
}
|
|
240
230
|
|
|
241
|
-
export { type CreateFlowTextGenerationPortOptions as C, type FlowGenerateText as F, type Generation as G, type HFEvent as H, type ImageAddedPayload as I, type LabFrame as L, type MetadataUpdatedPayload as M, type
|
|
231
|
+
export { type CreateFlowTextGenerationPortOptions as C, type FlowGenerateText as F, type Generation as G, type HFEvent as H, type ImageAddedPayload as I, type LabFrame as L, type MetadataUpdatedPayload as M, type ProjectSettings as P, type SelectedLabImage as S, type TagOption as T, type WorkspaceTags as W, type FlowSdk as a, type FlowTextGenerationOptions as b, type HFEventVersion as c, type HFFileInfo as d, type HFMetadataEntry as e, type HFStateMeta as f, type HFStateSnapshot as g, type LabItem as h, type LabServices as i, type SelectedTag as j, type TagUpsertedPayload as k, buildFallbackPrompt as l, buildGenerationPrompt as m, buildImageGenerationOptions as n, buildPromptTabPayload as o, cleanAiResponse as p, createFlowServices as q, createFlowTextGenerationPort as r, interpretSdkError as s, parsePromptResponse as t };
|
|
@@ -43,16 +43,6 @@ interface ProjectSettings {
|
|
|
43
43
|
seed: number;
|
|
44
44
|
seedMode: 'random' | 'fixed';
|
|
45
45
|
}
|
|
46
|
-
interface ProjectMeta {
|
|
47
|
-
id: string;
|
|
48
|
-
name: string;
|
|
49
|
-
saved_at: string;
|
|
50
|
-
size: number;
|
|
51
|
-
}
|
|
52
|
-
interface SyncDiff {
|
|
53
|
-
localOnly: Generation[];
|
|
54
|
-
serverOnly: Generation[];
|
|
55
|
-
}
|
|
56
46
|
interface LabFrame {
|
|
57
47
|
id: string;
|
|
58
48
|
base64: string;
|
|
@@ -238,4 +228,4 @@ interface TaskRequestedPayload {
|
|
|
238
228
|
expiresAt?: number;
|
|
239
229
|
}
|
|
240
230
|
|
|
241
|
-
export { type CreateFlowTextGenerationPortOptions as C, type FlowGenerateText as F, type Generation as G, type HFEvent as H, type ImageAddedPayload as I, type LabFrame as L, type MetadataUpdatedPayload as M, type
|
|
231
|
+
export { type CreateFlowTextGenerationPortOptions as C, type FlowGenerateText as F, type Generation as G, type HFEvent as H, type ImageAddedPayload as I, type LabFrame as L, type MetadataUpdatedPayload as M, type ProjectSettings as P, type SelectedLabImage as S, type TagOption as T, type WorkspaceTags as W, type FlowSdk as a, type FlowTextGenerationOptions as b, type HFEventVersion as c, type HFFileInfo as d, type HFMetadataEntry as e, type HFStateMeta as f, type HFStateSnapshot as g, type LabItem as h, type LabServices as i, type SelectedTag as j, type TagUpsertedPayload as k, buildFallbackPrompt as l, buildGenerationPrompt as m, buildImageGenerationOptions as n, buildPromptTabPayload as o, cleanAiResponse as p, createFlowServices as q, createFlowTextGenerationPort as r, interpretSdkError as s, parsePromptResponse as t };
|