@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
package/dist/chunk-NBUS7AWR.mjs
DELETED
|
@@ -1,418 +0,0 @@
|
|
|
1
|
-
// src/lib/hfStateService.ts
|
|
2
|
-
import JSZip from "jszip";
|
|
3
|
-
import { uploadFile } from "@huggingface/hub";
|
|
4
|
-
|
|
5
|
-
// src/lib/hfEventTypes.ts
|
|
6
|
-
var HF_BASE = "https://huggingface.co";
|
|
7
|
-
var HF_REPO = "RolandSch/fa-app-state";
|
|
8
|
-
var CURRENT_EVENT_VERSION = { major: 1, minor: 0 };
|
|
9
|
-
var SPEICHERBARE_BILDTYPEN = ["image/png", "image/jpeg", "image/webp"];
|
|
10
|
-
function bildEndungFuer(mimeType) {
|
|
11
|
-
if (mimeType === "image/png") return "png";
|
|
12
|
-
if (mimeType === "image/webp") return "webp";
|
|
13
|
-
return "jpg";
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// src/lib/hfEventLog.ts
|
|
17
|
-
function treeUrl(namespace, subdir = "") {
|
|
18
|
-
const parts = [namespace.replace(/\/$/, ""), subdir].filter(Boolean).join("/");
|
|
19
|
-
return `${HF_BASE}/api/datasets/${HF_REPO}/tree/main${parts ? "/" + parts : ""}`;
|
|
20
|
-
}
|
|
21
|
-
var DROSSEL_VERSUCHE = 4;
|
|
22
|
-
var DROSSEL_BASIS_MS = 400;
|
|
23
|
-
var schlafen = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
24
|
-
var HF_LESE_ZEITLIMIT_MS = 2e4;
|
|
25
|
-
async function holeMitZeitlimit(url, init) {
|
|
26
|
-
const abbruch = new AbortController();
|
|
27
|
-
const timer = setTimeout(() => abbruch.abort(), HF_LESE_ZEITLIMIT_MS);
|
|
28
|
-
try {
|
|
29
|
-
return await fetch(url, { ...init, signal: abbruch.signal });
|
|
30
|
-
} catch (error) {
|
|
31
|
-
if (abbruch.signal.aborted) {
|
|
32
|
-
throw new Error(`HF-Anfrage ohne Antwort nach ${HF_LESE_ZEITLIMIT_MS}ms abgebrochen: ${url}`);
|
|
33
|
-
}
|
|
34
|
-
throw error;
|
|
35
|
-
} finally {
|
|
36
|
-
clearTimeout(timer);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
async function mitGeduld(anfrage) {
|
|
40
|
-
let letzte = null;
|
|
41
|
-
for (let versuch = 0; versuch < DROSSEL_VERSUCHE; versuch += 1) {
|
|
42
|
-
const res = await anfrage();
|
|
43
|
-
if (res.status !== 429 && res.status < 500) return res;
|
|
44
|
-
letzte = res;
|
|
45
|
-
if (versuch === DROSSEL_VERSUCHE - 1) break;
|
|
46
|
-
const angesagt = Number(res.headers.get("retry-after"));
|
|
47
|
-
const warten = Number.isFinite(angesagt) && angesagt > 0 ? angesagt * 1e3 : DROSSEL_BASIS_MS * 2 ** versuch;
|
|
48
|
-
await schlafen(warten);
|
|
49
|
-
}
|
|
50
|
-
return letzte;
|
|
51
|
-
}
|
|
52
|
-
async function hfListDir(namespace, subdir, token) {
|
|
53
|
-
const res = await mitGeduld(() => holeMitZeitlimit(treeUrl(namespace, subdir), {
|
|
54
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
55
|
-
}));
|
|
56
|
-
if (res.status === 404) return [];
|
|
57
|
-
if (!res.ok) throw new Error(`HF list failed: ${res.status} ${res.statusText}`);
|
|
58
|
-
return res.json();
|
|
59
|
-
}
|
|
60
|
-
async function hfDownloadJsonByPath(repoPath, token) {
|
|
61
|
-
const res = await mitGeduld(() => holeMitZeitlimit(
|
|
62
|
-
`${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
|
|
63
|
-
{ headers: { Authorization: `Bearer ${token}` } }
|
|
64
|
-
));
|
|
65
|
-
if (!res.ok) throw new Error(`HF download failed: ${res.status}`);
|
|
66
|
-
return res.json();
|
|
67
|
-
}
|
|
68
|
-
async function hfDownloadBinaryByPath(repoPath, token) {
|
|
69
|
-
const res = await mitGeduld(() => holeMitZeitlimit(
|
|
70
|
-
`${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
|
|
71
|
-
{ headers: { Authorization: `Bearer ${token}` } }
|
|
72
|
-
));
|
|
73
|
-
if (!res.ok) throw new Error(`HF download binary failed: ${res.status}`);
|
|
74
|
-
return new Uint8Array(await res.arrayBuffer());
|
|
75
|
-
}
|
|
76
|
-
function toBase64(text) {
|
|
77
|
-
const bytes = new TextEncoder().encode(text);
|
|
78
|
-
let binary = "";
|
|
79
|
-
bytes.forEach((b) => binary += String.fromCharCode(b));
|
|
80
|
-
return btoa(binary);
|
|
81
|
-
}
|
|
82
|
-
async function hfUploadSmallFile(repoPath, content, token, summary = `Update ${repoPath}`) {
|
|
83
|
-
const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
|
|
84
|
-
method: "POST",
|
|
85
|
-
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/x-ndjson" },
|
|
86
|
-
body: [
|
|
87
|
-
JSON.stringify({ key: "header", value: { summary, description: "" } }),
|
|
88
|
-
JSON.stringify({ key: "file", value: { path: repoPath, encoding: "base64", content: toBase64(content) } })
|
|
89
|
-
].join("\n")
|
|
90
|
-
});
|
|
91
|
-
if (!res.ok) {
|
|
92
|
-
const err = await res.text().catch(() => "");
|
|
93
|
-
throw new Error(`HF upload failed: ${res.status} \u2014 ${err.slice(0, 200)}`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
async function hfBatchArchive(moves, token, summary) {
|
|
97
|
-
const lines = [
|
|
98
|
-
JSON.stringify({ key: "header", value: { summary, description: "" } }),
|
|
99
|
-
...moves.flatMap(({ from, to, content }) => [
|
|
100
|
-
JSON.stringify({ key: "file", value: { path: to, encoding: "base64", content: toBase64(content) } }),
|
|
101
|
-
JSON.stringify({ key: "deletedFile", value: { path: from } })
|
|
102
|
-
])
|
|
103
|
-
];
|
|
104
|
-
const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
|
|
105
|
-
method: "POST",
|
|
106
|
-
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/x-ndjson" },
|
|
107
|
-
body: lines.join("\n")
|
|
108
|
-
});
|
|
109
|
-
if (!res.ok) {
|
|
110
|
-
const err = await res.text().catch(() => "");
|
|
111
|
-
throw new Error(`HF batch archive failed: ${res.status} \u2014 ${err.slice(0, 200)}`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
function tsFromEventPath(repoPath) {
|
|
115
|
-
const filename = repoPath.split("/").pop() || "";
|
|
116
|
-
const base = filename.replace(/_[^_]+\.json$/, "");
|
|
117
|
-
const iso = base.replace(/T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z$/, "T$1:$2:$3.$4Z");
|
|
118
|
-
const ts = Date.parse(iso);
|
|
119
|
-
return isNaN(ts) ? 0 : ts;
|
|
120
|
-
}
|
|
121
|
-
var EVENTDOWNLOAD_PARALLELITAET = 3;
|
|
122
|
-
async function mitBegrenzterParallelitaet(count, limit, task) {
|
|
123
|
-
let cursor = 0;
|
|
124
|
-
let ersterFehler;
|
|
125
|
-
let fehlerAufgetreten = false;
|
|
126
|
-
const worker = async () => {
|
|
127
|
-
while (cursor < count) {
|
|
128
|
-
const index = cursor++;
|
|
129
|
-
try {
|
|
130
|
-
await task(index);
|
|
131
|
-
} catch (error) {
|
|
132
|
-
if (!fehlerAufgetreten) {
|
|
133
|
-
fehlerAufgetreten = true;
|
|
134
|
-
ersterFehler = error;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
await Promise.all(Array.from({ length: Math.min(limit, count) }, worker));
|
|
140
|
-
if (fehlerAufgetreten) throw ersterFehler;
|
|
141
|
-
}
|
|
142
|
-
async function loadPendingEvents(namespace, token, sinceTs, knownPaths) {
|
|
143
|
-
const files = await hfListDir(namespace, "events", token);
|
|
144
|
-
const pending = files.filter((f) => f.type === "file" && tsFromEventPath(f.path) > sinceTs && !knownPaths?.has(f.path)).sort((a, b) => a.path.localeCompare(b.path));
|
|
145
|
-
if (!pending.length) return [];
|
|
146
|
-
const raw = new Array(pending.length);
|
|
147
|
-
await mitBegrenzterParallelitaet(pending.length, EVENTDOWNLOAD_PARALLELITAET, async (index) => {
|
|
148
|
-
raw[index] = await hfDownloadJsonByPath(pending[index].path, token);
|
|
149
|
-
});
|
|
150
|
-
for (const f of pending) knownPaths?.add(f.path);
|
|
151
|
-
return raw.flatMap((e) => Array.isArray(e) ? e : [e]);
|
|
152
|
-
}
|
|
153
|
-
async function listEventPaths(namespace, token) {
|
|
154
|
-
const files = await hfListDir(namespace, "events", token);
|
|
155
|
-
return files.filter((f) => f.type === "file").map((f) => f.path).sort((a, b) => a.localeCompare(b));
|
|
156
|
-
}
|
|
157
|
-
var SESSION_CLIENT_ID = `client-${crypto.randomUUID().slice(0, 8)}`;
|
|
158
|
-
function getSessionClientId() {
|
|
159
|
-
return SESSION_CLIENT_ID;
|
|
160
|
-
}
|
|
161
|
-
function tsToFilename(ts) {
|
|
162
|
-
return new Date(ts).toISOString().replace(/:/g, "-").replace(".", "-");
|
|
163
|
-
}
|
|
164
|
-
var writeSeq = 0;
|
|
165
|
-
var lastOrderedTs = 0;
|
|
166
|
-
async function writeHFEvent(namespace, token, type, payload, prevTs, options) {
|
|
167
|
-
const wallTs = Date.now();
|
|
168
|
-
const ts = options?.ordered ? Math.max(wallTs, lastOrderedTs) : wallTs;
|
|
169
|
-
if (options?.ordered) lastOrderedTs = ts;
|
|
170
|
-
const uuid = crypto.randomUUID().slice(0, 8);
|
|
171
|
-
const event = {
|
|
172
|
-
v: CURRENT_EVENT_VERSION,
|
|
173
|
-
type,
|
|
174
|
-
ts,
|
|
175
|
-
prevTs,
|
|
176
|
-
clientId: SESSION_CLIENT_ID,
|
|
177
|
-
payload
|
|
178
|
-
};
|
|
179
|
-
const suffix = options?.ordered ? `${String(++writeSeq % 1e6).padStart(6, "0")}${uuid}` : uuid;
|
|
180
|
-
const filename = `${tsToFilename(ts)}_${suffix}.json`;
|
|
181
|
-
const repoPath = `${namespace}events/${filename}`;
|
|
182
|
-
await hfUploadSmallFile(repoPath, JSON.stringify(event, null, 2), token, `Event: ${type}`);
|
|
183
|
-
return event;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// src/lib/hfStateService.ts
|
|
187
|
-
var HF_TOKEN_KEY = "hf-token";
|
|
188
|
-
function getHFToken() {
|
|
189
|
-
try {
|
|
190
|
-
return localStorage.getItem(HF_TOKEN_KEY);
|
|
191
|
-
} catch {
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
function setHFToken(token) {
|
|
196
|
-
try {
|
|
197
|
-
localStorage.setItem(HF_TOKEN_KEY, token);
|
|
198
|
-
} catch {
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
async function loadHFState(namespace, token) {
|
|
202
|
-
const files = await hfListDir(namespace, "", token);
|
|
203
|
-
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));
|
|
204
|
-
if (!stateFiles.length) return null;
|
|
205
|
-
const zipBytes = await hfDownloadBinaryByPath(stateFiles[0].path, token);
|
|
206
|
-
const zip = await JSZip.loadAsync(zipBytes);
|
|
207
|
-
const [metadataStr, tagsStr, metaStr, openTasksStr] = await Promise.all([
|
|
208
|
-
zip.file("metadata.json")?.async("string"),
|
|
209
|
-
zip.file("tags.json")?.async("string"),
|
|
210
|
-
zip.file("state_meta.json")?.async("string"),
|
|
211
|
-
zip.file("open_tasks.json")?.async("string")
|
|
212
|
-
// optional — ältere Snapshots haben es nicht
|
|
213
|
-
]);
|
|
214
|
-
if (!metadataStr || !tagsStr || !metaStr) throw new Error("state.zip: fehlende Pflicht-Dateien");
|
|
215
|
-
return {
|
|
216
|
-
metadata: JSON.parse(metadataStr),
|
|
217
|
-
tags: JSON.parse(tagsStr),
|
|
218
|
-
meta: JSON.parse(metaStr),
|
|
219
|
-
openTasks: openTasksStr ? JSON.parse(openTasksStr) : {}
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
async function hfBootstrapFromLegacy(namespace, token, onProgress) {
|
|
223
|
-
const log = (msg) => {
|
|
224
|
-
if (onProgress) onProgress(msg);
|
|
225
|
-
};
|
|
226
|
-
log("Lese tags.json \u2026");
|
|
227
|
-
const tags = await fetch(
|
|
228
|
-
`${HF_BASE}/datasets/${HF_REPO}/resolve/main/tags.json?download=true`,
|
|
229
|
-
{ headers: { Authorization: `Bearer ${token}` } }
|
|
230
|
-
).then((r) => r.ok ? r.json() : null).catch(() => null);
|
|
231
|
-
log("Lese metadata.json \u2026");
|
|
232
|
-
const metadata = await fetch(
|
|
233
|
-
`${HF_BASE}/datasets/${HF_REPO}/resolve/main/metadata.json?download=true`,
|
|
234
|
-
{ headers: { Authorization: `Bearer ${token}` } }
|
|
235
|
-
).then((r) => r.ok ? r.json() : []).catch(() => []);
|
|
236
|
-
const snapshot = {
|
|
237
|
-
metadata: Array.isArray(metadata) ? metadata : [],
|
|
238
|
-
tags: tags?.by_category ? tags : { by_category: {}, all: [] },
|
|
239
|
-
meta: { consolidatedAt: Date.now(), version: 1 }
|
|
240
|
-
};
|
|
241
|
-
log(`Erstelle state.zip (${snapshot.metadata.length} Bilder, ${snapshot.tags.all?.length ?? 0} Tags) \u2026`);
|
|
242
|
-
const zip = new JSZip();
|
|
243
|
-
zip.file("metadata.json", JSON.stringify(snapshot.metadata, null, 2));
|
|
244
|
-
zip.file("tags.json", JSON.stringify(snapshot.tags, null, 2));
|
|
245
|
-
zip.file("state_meta.json", JSON.stringify(snapshot.meta, null, 2));
|
|
246
|
-
const zipBytes = await zip.generateAsync({ type: "uint8array", compression: "DEFLATE" });
|
|
247
|
-
const ts = snapshot.meta.consolidatedAt;
|
|
248
|
-
const stateFilename = `state-${new Date(ts).toISOString().replace(/:/g, "-").replace(".", "-")}.zip`;
|
|
249
|
-
const repoPath = `${namespace}${stateFilename}`;
|
|
250
|
-
log("Lade state.zip hoch \u2026");
|
|
251
|
-
await uploadFile({
|
|
252
|
-
repo: { type: "dataset", name: HF_REPO },
|
|
253
|
-
credentials: { accessToken: token },
|
|
254
|
-
file: { path: repoPath, content: new Blob([zipBytes.buffer], { type: "application/zip" }) },
|
|
255
|
-
branch: "main",
|
|
256
|
-
commitTitle: `Bootstrap: initialer State aus Legacy-Daten`
|
|
257
|
-
});
|
|
258
|
-
log(`Fertig \u2014 ${stateFilename}`);
|
|
259
|
-
}
|
|
260
|
-
async function hfListProjects(token) {
|
|
261
|
-
const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/tree/main`, {
|
|
262
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
263
|
-
});
|
|
264
|
-
if (!res.ok) throw new Error(`HF list failed: ${res.status} ${res.statusText}`);
|
|
265
|
-
const files = await res.json();
|
|
266
|
-
return files.filter((f) => f.type === "file" && f.path.endsWith(".zip")).map((f) => ({
|
|
267
|
-
id: f.path.replace(/\.zip$/, ""),
|
|
268
|
-
name: f.path.replace(/\.zip$/, ""),
|
|
269
|
-
path: f.path,
|
|
270
|
-
size: f.size,
|
|
271
|
-
isLfs: !!f.lfs
|
|
272
|
-
}));
|
|
273
|
-
}
|
|
274
|
-
async function hfDownloadProject(path, token) {
|
|
275
|
-
const res = await fetch(
|
|
276
|
-
`${HF_BASE}/datasets/${HF_REPO}/resolve/main/${path}?download=true`,
|
|
277
|
-
{ headers: { Authorization: `Bearer ${token}` } }
|
|
278
|
-
);
|
|
279
|
-
if (!res.ok) throw new Error(`HF download failed: ${res.status} ${res.statusText}`);
|
|
280
|
-
const blob = await res.blob();
|
|
281
|
-
return new File([blob], path, { type: "application/zip" });
|
|
282
|
-
}
|
|
283
|
-
async function hfUploadProject(zipBase64, name, token) {
|
|
284
|
-
const filename = name.endsWith(".zip") ? name : `${name}.zip`;
|
|
285
|
-
const binary = atob(zipBase64);
|
|
286
|
-
const bytes = new Uint8Array(binary.length);
|
|
287
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
288
|
-
await uploadFile({
|
|
289
|
-
repo: { type: "dataset", name: HF_REPO },
|
|
290
|
-
credentials: { accessToken: token },
|
|
291
|
-
file: { path: filename, content: new Blob([bytes], { type: "application/zip" }) },
|
|
292
|
-
branch: "main",
|
|
293
|
-
commitTitle: `Upload ${filename}`
|
|
294
|
-
});
|
|
295
|
-
return { id: filename.replace(/\.zip$/, ""), name: filename.replace(/\.zip$/, ""), path: filename, size: bytes.length, isLfs: true };
|
|
296
|
-
}
|
|
297
|
-
async function hfUploadProjectForm(_zipBase64, _name, _token) {
|
|
298
|
-
throw new Error("hfUploadProjectForm is deprecated. Use hfUploadProject instead.");
|
|
299
|
-
}
|
|
300
|
-
async function hfDeleteProject(path, token) {
|
|
301
|
-
const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
|
|
302
|
-
method: "POST",
|
|
303
|
-
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/x-ndjson" },
|
|
304
|
-
body: [
|
|
305
|
-
JSON.stringify({ key: "header", value: { summary: `Delete ${path}`, description: "" } }),
|
|
306
|
-
JSON.stringify({ key: "deletedFile", value: { path } })
|
|
307
|
-
].join("\n")
|
|
308
|
-
});
|
|
309
|
-
if (!res.ok) throw new Error(`HF delete failed: ${res.status} ${res.statusText}`);
|
|
310
|
-
}
|
|
311
|
-
async function hfSaveTags(_workspaceTags, _token) {
|
|
312
|
-
throw new Error("hfSaveTags is deprecated. Use writeHFEvent instead.");
|
|
313
|
-
}
|
|
314
|
-
async function hfLoadTags(token) {
|
|
315
|
-
try {
|
|
316
|
-
const res = await fetch(
|
|
317
|
-
`${HF_BASE}/datasets/${HF_REPO}/resolve/main/tags.json?download=true`,
|
|
318
|
-
{ headers: { Authorization: `Bearer ${token}` } }
|
|
319
|
-
);
|
|
320
|
-
if (!res.ok) return null;
|
|
321
|
-
return res.json();
|
|
322
|
-
} catch {
|
|
323
|
-
return null;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
async function hfSaveMetadata(_entries, _token) {
|
|
327
|
-
throw new Error("hfSaveMetadata is deprecated. Use writeHFEvent instead.");
|
|
328
|
-
}
|
|
329
|
-
async function hfLoadMetadata(token) {
|
|
330
|
-
try {
|
|
331
|
-
const res = await fetch(
|
|
332
|
-
`${HF_BASE}/datasets/${HF_REPO}/resolve/main/metadata.json?download=true`,
|
|
333
|
-
{ headers: { Authorization: `Bearer ${token}` } }
|
|
334
|
-
);
|
|
335
|
-
if (!res.ok) return [];
|
|
336
|
-
return res.json();
|
|
337
|
-
} catch {
|
|
338
|
-
return [];
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
async function hfUploadImage(base64, id, token, mimeType = "image/jpeg") {
|
|
342
|
-
const ext = bildEndungFuer(mimeType);
|
|
343
|
-
const binary = atob(base64);
|
|
344
|
-
const bytes = new Uint8Array(binary.length);
|
|
345
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
346
|
-
await uploadFile({
|
|
347
|
-
repo: { type: "dataset", name: HF_REPO },
|
|
348
|
-
credentials: { accessToken: token },
|
|
349
|
-
file: { path: `images/${id}.${ext}`, content: new Blob([bytes], { type: mimeType }) },
|
|
350
|
-
branch: "main",
|
|
351
|
-
commitTitle: `Add image ${id}`
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
async function hfLoadImageAsBase64(id, token, namespace, filename, on404, mimeType, hasThumb) {
|
|
355
|
-
const fetchBase64 = async (repoPath) => {
|
|
356
|
-
try {
|
|
357
|
-
const res = await fetch(
|
|
358
|
-
`${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
|
|
359
|
-
{ headers: { Authorization: `Bearer ${token}` } }
|
|
360
|
-
);
|
|
361
|
-
if (!res.ok) {
|
|
362
|
-
if (on404 && on404()) return "ABORT";
|
|
363
|
-
return null;
|
|
364
|
-
}
|
|
365
|
-
const blob = await res.blob();
|
|
366
|
-
return new Promise((resolve, reject) => {
|
|
367
|
-
const reader = new FileReader();
|
|
368
|
-
reader.onload = () => resolve(reader.result.split(",")[1]);
|
|
369
|
-
reader.onerror = reject;
|
|
370
|
-
reader.readAsDataURL(blob);
|
|
371
|
-
});
|
|
372
|
-
} catch {
|
|
373
|
-
return null;
|
|
374
|
-
}
|
|
375
|
-
};
|
|
376
|
-
if (filename) {
|
|
377
|
-
if (hasThumb) {
|
|
378
|
-
const thumb = await fetchBase64(`thumbs/${filename}`);
|
|
379
|
-
if (thumb && thumb !== "ABORT") return thumb;
|
|
380
|
-
}
|
|
381
|
-
const result2 = await fetchBase64(`images/${filename}`);
|
|
382
|
-
return result2 === "ABORT" ? null : result2;
|
|
383
|
-
}
|
|
384
|
-
const ext = bildEndungFuer(mimeType);
|
|
385
|
-
const result = await fetchBase64(`images/${id}.${ext}`);
|
|
386
|
-
return result === "ABORT" ? null : result;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
export {
|
|
390
|
-
SPEICHERBARE_BILDTYPEN,
|
|
391
|
-
bildEndungFuer,
|
|
392
|
-
hfListDir,
|
|
393
|
-
hfDownloadJsonByPath,
|
|
394
|
-
hfDownloadBinaryByPath,
|
|
395
|
-
hfUploadSmallFile,
|
|
396
|
-
hfBatchArchive,
|
|
397
|
-
tsFromEventPath,
|
|
398
|
-
loadPendingEvents,
|
|
399
|
-
listEventPaths,
|
|
400
|
-
getSessionClientId,
|
|
401
|
-
writeHFEvent,
|
|
402
|
-
HF_TOKEN_KEY,
|
|
403
|
-
getHFToken,
|
|
404
|
-
setHFToken,
|
|
405
|
-
loadHFState,
|
|
406
|
-
hfBootstrapFromLegacy,
|
|
407
|
-
hfListProjects,
|
|
408
|
-
hfDownloadProject,
|
|
409
|
-
hfUploadProject,
|
|
410
|
-
hfUploadProjectForm,
|
|
411
|
-
hfDeleteProject,
|
|
412
|
-
hfSaveTags,
|
|
413
|
-
hfLoadTags,
|
|
414
|
-
hfSaveMetadata,
|
|
415
|
-
hfLoadMetadata,
|
|
416
|
-
hfUploadImage,
|
|
417
|
-
hfLoadImageAsBase64
|
|
418
|
-
};
|
package/dist/chunk-UFXDXENC.mjs
DELETED
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
// src/lib/project.ts
|
|
2
|
-
import JSZip from "jszip";
|
|
3
|
-
|
|
4
|
-
// src/lib/metadata.ts
|
|
5
|
-
var crcTable = (() => {
|
|
6
|
-
let c;
|
|
7
|
-
const table = new Uint32Array(256);
|
|
8
|
-
for (let n = 0; n < 256; n++) {
|
|
9
|
-
c = n;
|
|
10
|
-
for (let k = 0; k < 8; k++) {
|
|
11
|
-
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
12
|
-
}
|
|
13
|
-
table[n] = c;
|
|
14
|
-
}
|
|
15
|
-
return table;
|
|
16
|
-
})();
|
|
17
|
-
function calculateCRC(bytes) {
|
|
18
|
-
let crc = 4294967295;
|
|
19
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
20
|
-
crc = crcTable[(crc ^ bytes[i]) & 255] ^ crc >>> 8;
|
|
21
|
-
}
|
|
22
|
-
return (crc ^ 4294967295) >>> 0;
|
|
23
|
-
}
|
|
24
|
-
function indexOfBytes(buffer, search, start = 0) {
|
|
25
|
-
for (let i = start; i <= buffer.length - search.length; i++) {
|
|
26
|
-
let found = true;
|
|
27
|
-
for (let j = 0; j < search.length; j++) {
|
|
28
|
-
if (buffer[i + j] !== search[j]) {
|
|
29
|
-
found = false;
|
|
30
|
-
break;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
if (found) return i;
|
|
34
|
-
}
|
|
35
|
-
return -1;
|
|
36
|
-
}
|
|
37
|
-
function createXMPPacket(prompt, seed, model, id, tags = [], hierarchy) {
|
|
38
|
-
const sanitize = (str) => (str || "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
39
|
-
const escPrompt = sanitize(prompt);
|
|
40
|
-
const escModel = sanitize(model || "AI Model");
|
|
41
|
-
const escHierarchy = sanitize(hierarchy || "");
|
|
42
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
43
|
-
const allTags = [.../* @__PURE__ */ new Set([...tags, "Avatar Architect", escModel])];
|
|
44
|
-
const rdfTags = allTags.map((t) => `<rdf:li>${sanitize(t)}</rdf:li>`).join("");
|
|
45
|
-
const BOM = "\uFEFF";
|
|
46
|
-
return `<?xpacket begin="${BOM}" id="W5M0MpCehiHzreSzNTczkc9d"?>
|
|
47
|
-
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c140">
|
|
48
|
-
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
|
49
|
-
<rdf:Description rdf:about=""
|
|
50
|
-
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
|
|
51
|
-
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
52
|
-
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
|
|
53
|
-
xmlns:ai="http://ns.flow.creative/ai/1.0/">
|
|
54
|
-
<dc:format>image/png</dc:format>
|
|
55
|
-
<dc:description><rdf:Alt><rdf:li xml:lang="x-default">${escPrompt}</rdf:li></rdf:Alt></dc:description>
|
|
56
|
-
<dc:subject><rdf:Bag>${rdfTags}</rdf:Bag></dc:subject>
|
|
57
|
-
<photoshop:Instructions>Seed: ${seed} | Model: ${escModel}</photoshop:Instructions>
|
|
58
|
-
<xmp:CreateDate>${timestamp}</xmp:CreateDate>
|
|
59
|
-
<ai:prompt>${escPrompt}</ai:prompt>
|
|
60
|
-
<ai:seed>${seed ?? "0"}</ai:seed>
|
|
61
|
-
<ai:model>${escModel}</ai:model>
|
|
62
|
-
<ai:hierarchy>${escHierarchy}</ai:hierarchy>
|
|
63
|
-
</rdf:Description>
|
|
64
|
-
</rdf:RDF>
|
|
65
|
-
</x:xmpmeta>${" ".repeat(1024)}<?xpacket end="w"?>`;
|
|
66
|
-
}
|
|
67
|
-
function bytesToBase64(bytes) {
|
|
68
|
-
let binary = "";
|
|
69
|
-
const len = bytes.byteLength;
|
|
70
|
-
for (let i = 0; i < len; i++) binary += String.fromCharCode(bytes[i]);
|
|
71
|
-
return btoa(binary);
|
|
72
|
-
}
|
|
73
|
-
function base64ToBytes(base64) {
|
|
74
|
-
const clean = base64.includes(",") ? base64.split(",")[1] : base64.trim();
|
|
75
|
-
try {
|
|
76
|
-
const binary = atob(clean);
|
|
77
|
-
const bytes = new Uint8Array(binary.length);
|
|
78
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
79
|
-
return bytes;
|
|
80
|
-
} catch (e) {
|
|
81
|
-
return new Uint8Array(0);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
function injectXMPMetadata(base64, prompt, seed, model, id, tags = [], hierarchy) {
|
|
85
|
-
try {
|
|
86
|
-
const bytes = base64ToBytes(base64);
|
|
87
|
-
if (bytes.length < 8 || bytes[0] !== 137 || bytes[1] !== 80) {
|
|
88
|
-
console.warn("Metadaten-Injektion \xFCbersprungen: Datei ist kein PNG.");
|
|
89
|
-
return base64;
|
|
90
|
-
}
|
|
91
|
-
const xmpData = createXMPPacket(prompt, seed, model, id, tags, hierarchy);
|
|
92
|
-
const encoder = new TextEncoder();
|
|
93
|
-
const keyword = encoder.encode("XML:com.adobe.xmp");
|
|
94
|
-
const textBytes = encoder.encode(xmpData);
|
|
95
|
-
const chunkData = new Uint8Array(keyword.length + 5 + textBytes.length);
|
|
96
|
-
chunkData.set(keyword, 0);
|
|
97
|
-
chunkData.set(textBytes, keyword.length + 5);
|
|
98
|
-
const type = encoder.encode("iTXt");
|
|
99
|
-
const crcInput = new Uint8Array(type.length + chunkData.length);
|
|
100
|
-
crcInput.set(type, 0);
|
|
101
|
-
crcInput.set(chunkData, type.length);
|
|
102
|
-
const crc = calculateCRC(crcInput);
|
|
103
|
-
const fullChunk = new Uint8Array(4 + 4 + chunkData.length + 4);
|
|
104
|
-
const view = new DataView(fullChunk.buffer);
|
|
105
|
-
view.setUint32(0, chunkData.length);
|
|
106
|
-
fullChunk.set(type, 4);
|
|
107
|
-
fullChunk.set(chunkData, 8);
|
|
108
|
-
view.setUint32(8 + chunkData.length, crc);
|
|
109
|
-
const IDAT = encoder.encode("IDAT");
|
|
110
|
-
const idatPos = indexOfBytes(bytes, IDAT);
|
|
111
|
-
const insertPos = idatPos !== -1 ? idatPos - 4 : 8;
|
|
112
|
-
const result = new Uint8Array(bytes.length + fullChunk.length);
|
|
113
|
-
result.set(bytes.slice(0, insertPos), 0);
|
|
114
|
-
result.set(fullChunk, insertPos);
|
|
115
|
-
result.set(bytes.slice(insertPos), insertPos + fullChunk.length);
|
|
116
|
-
return bytesToBase64(result);
|
|
117
|
-
} catch (e) {
|
|
118
|
-
console.error("Fehler beim Einbetten der Metadaten:", e);
|
|
119
|
-
return base64;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// src/lib/project.ts
|
|
124
|
-
function generateMarkdownReport(nodes, history) {
|
|
125
|
-
let md = "# Avatar Architect - Projekt Report\n\n";
|
|
126
|
-
md += `Exportiert: ${(/* @__PURE__ */ new Date()).toLocaleString()}
|
|
127
|
-
|
|
128
|
-
`;
|
|
129
|
-
md += "## Hierarchie\n";
|
|
130
|
-
nodes.forEach((n) => {
|
|
131
|
-
md += `- **${n.data?.label || "Unbenannt"}**
|
|
132
|
-
`;
|
|
133
|
-
});
|
|
134
|
-
md += "\n## Historie der Generationen\n";
|
|
135
|
-
history.forEach((h, i) => {
|
|
136
|
-
md += `### ${i + 1}. Generation (${h.model || "AI"})
|
|
137
|
-
`;
|
|
138
|
-
md += `- **Zeitstempel**: ${new Date(h.timestamp).toLocaleString()}
|
|
139
|
-
`;
|
|
140
|
-
md += `- **Seed**: ${h.seed}
|
|
141
|
-
`;
|
|
142
|
-
md += `- **Prompt**:
|
|
143
|
-
> ${h.prompt}
|
|
144
|
-
|
|
145
|
-
`;
|
|
146
|
-
});
|
|
147
|
-
return md;
|
|
148
|
-
}
|
|
149
|
-
async function exportProjectToZip(nodes, edges, history, galleryItems, settings, workspaceTags, recentLabItemIds) {
|
|
150
|
-
const zip = new JSZip();
|
|
151
|
-
const projectData = {
|
|
152
|
-
nodes,
|
|
153
|
-
edges,
|
|
154
|
-
history: history.map((h) => ({ ...h, base64: void 0 })),
|
|
155
|
-
galleryItems: galleryItems.map((g) => ({ ...g, base64: void 0 })),
|
|
156
|
-
settings,
|
|
157
|
-
workspaceTags: workspaceTags || null,
|
|
158
|
-
recentLabItemIds: recentLabItemIds || [],
|
|
159
|
-
version: "1.3.0"
|
|
160
|
-
};
|
|
161
|
-
zip.file("project.json", JSON.stringify(projectData, null, 2));
|
|
162
|
-
zip.file("projekt_bericht.md", generateMarkdownReport(nodes, history));
|
|
163
|
-
const imgFolder = zip.folder("images");
|
|
164
|
-
if (imgFolder) {
|
|
165
|
-
const allItems = [...history, ...galleryItems];
|
|
166
|
-
for (const item of allItems) {
|
|
167
|
-
if (item.base64) {
|
|
168
|
-
try {
|
|
169
|
-
const cleanB64 = item.base64.includes(",") ? item.base64.split(",")[1] : item.base64;
|
|
170
|
-
const withMeta = injectXMPMetadata(cleanB64, item.prompt || "", item.seed, item.model, item.id, item.tags || []);
|
|
171
|
-
imgFolder.file(`${item.id}.png`, withMeta, { base64: true });
|
|
172
|
-
} catch (e) {
|
|
173
|
-
console.error("Fehler beim Packen eines Bildes:", e);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
const blob = await zip.generateAsync({ type: "blob", compression: "STORE" });
|
|
179
|
-
return blobToBase64(blob);
|
|
180
|
-
}
|
|
181
|
-
async function blobToBase64(blob) {
|
|
182
|
-
return new Promise((resolve, reject) => {
|
|
183
|
-
const reader = new FileReader();
|
|
184
|
-
reader.onloadend = () => {
|
|
185
|
-
const result = reader.result;
|
|
186
|
-
resolve({ base64: result.split(",")[1] });
|
|
187
|
-
};
|
|
188
|
-
reader.onerror = reject;
|
|
189
|
-
reader.readAsDataURL(blob);
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
async function importProjectFromZip(file) {
|
|
193
|
-
const zip = await JSZip.loadAsync(file);
|
|
194
|
-
const jsonStr = await zip.file("project.json")?.async("string");
|
|
195
|
-
if (!jsonStr) throw new Error("Keine project.json gefunden.");
|
|
196
|
-
const data = JSON.parse(jsonStr);
|
|
197
|
-
const imgFolder = zip.folder("images");
|
|
198
|
-
if (imgFolder) {
|
|
199
|
-
const loadImgs = async (items) => {
|
|
200
|
-
if (!items) return [];
|
|
201
|
-
return Promise.all(items.map(async (h) => {
|
|
202
|
-
const imgFile = zip.file(`images/${h.id}.png`);
|
|
203
|
-
if (!imgFile) return h;
|
|
204
|
-
const b64 = await imgFile.async("base64");
|
|
205
|
-
return { ...h, base64: `data:image/png;base64,${b64}` };
|
|
206
|
-
}));
|
|
207
|
-
};
|
|
208
|
-
data.history = await loadImgs(data.history || []);
|
|
209
|
-
data.galleryItems = await loadImgs(data.galleryItems || []);
|
|
210
|
-
}
|
|
211
|
-
return data;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export {
|
|
215
|
-
injectXMPMetadata,
|
|
216
|
-
exportProjectToZip,
|
|
217
|
-
importProjectFromZip
|
|
218
|
-
};
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
HF_TOKEN_KEY,
|
|
3
|
-
getHFToken,
|
|
4
|
-
getSessionClientId,
|
|
5
|
-
hfBatchArchive,
|
|
6
|
-
hfBootstrapFromLegacy,
|
|
7
|
-
hfDeleteProject,
|
|
8
|
-
hfDownloadBinaryByPath,
|
|
9
|
-
hfDownloadJsonByPath,
|
|
10
|
-
hfDownloadProject,
|
|
11
|
-
hfListDir,
|
|
12
|
-
hfListProjects,
|
|
13
|
-
hfLoadImageAsBase64,
|
|
14
|
-
hfLoadMetadata,
|
|
15
|
-
hfLoadTags,
|
|
16
|
-
hfSaveMetadata,
|
|
17
|
-
hfSaveTags,
|
|
18
|
-
hfUploadImage,
|
|
19
|
-
hfUploadProject,
|
|
20
|
-
hfUploadProjectForm,
|
|
21
|
-
hfUploadSmallFile,
|
|
22
|
-
listEventPaths,
|
|
23
|
-
loadHFState,
|
|
24
|
-
loadPendingEvents,
|
|
25
|
-
setHFToken,
|
|
26
|
-
tsFromEventPath,
|
|
27
|
-
writeHFEvent
|
|
28
|
-
} from "./chunk-NBUS7AWR.mjs";
|
|
29
|
-
export {
|
|
30
|
-
HF_TOKEN_KEY,
|
|
31
|
-
getHFToken,
|
|
32
|
-
getSessionClientId,
|
|
33
|
-
hfBatchArchive,
|
|
34
|
-
hfBootstrapFromLegacy,
|
|
35
|
-
hfDeleteProject,
|
|
36
|
-
hfDownloadBinaryByPath,
|
|
37
|
-
hfDownloadJsonByPath,
|
|
38
|
-
hfDownloadProject,
|
|
39
|
-
hfListDir,
|
|
40
|
-
hfListProjects,
|
|
41
|
-
hfLoadImageAsBase64,
|
|
42
|
-
hfLoadMetadata,
|
|
43
|
-
hfLoadTags,
|
|
44
|
-
hfSaveMetadata,
|
|
45
|
-
hfSaveTags,
|
|
46
|
-
hfUploadImage,
|
|
47
|
-
hfUploadProject,
|
|
48
|
-
hfUploadProjectForm,
|
|
49
|
-
hfUploadSmallFile,
|
|
50
|
-
listEventPaths,
|
|
51
|
-
loadHFState,
|
|
52
|
-
loadPendingEvents,
|
|
53
|
-
setHFToken,
|
|
54
|
-
tsFromEventPath,
|
|
55
|
-
writeHFEvent
|
|
56
|
-
};
|