@rslsp1/fa-app-tools 5.1.0 → 7.0.1

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/szenen.mjs CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  CHAIN_EDGES,
5
5
  CHAIN_LINK_FIELDS,
6
6
  CapabilityAnchorError,
7
+ DEFAULT_SEED_VORLAGE,
7
8
  DERIVABLE_TYPES,
8
9
  ELEMENT_VERSION_SCHEMA_FILE,
9
10
  FLOW_EXECUTION_PATH,
@@ -22,6 +23,7 @@ import {
22
23
  SZENEN_SCHEMAS,
23
24
  SzenenView,
24
25
  UNCHECKED_PUBLISH_RULES,
26
+ UPLOAD_MAX_BYTES,
25
27
  UnsupportedSchemaError,
26
28
  acceptProposalTitle,
27
29
  areasFor,
@@ -99,8 +101,10 @@ import {
99
101
  findSceneDerivationRecovery,
100
102
  firstError,
101
103
  formatSchemaErrors,
104
+ formatSeedListe,
102
105
  freezeDraftChain,
103
106
  freezeSceneWorkingState,
107
+ gesamtHerkunft,
104
108
  getSzenenSchemas,
105
109
  gruppiereZuItems,
106
110
  hasPlacementCapability,
@@ -108,16 +112,21 @@ import {
108
112
  isOk,
109
113
  isProjectionCurrent,
110
114
  isSzenenItemId,
115
+ istZipDatei,
111
116
  itemIdForRun,
112
117
  ladeVorschauen,
118
+ leseBilderAusZip,
113
119
  linkFor,
114
120
  locatorTeile,
115
121
  logRevisionOf,
116
122
  mergeSzenenBatchOrigin,
123
+ mitDefaultVorlage,
117
124
  noticeFor,
118
125
  ordersForPromptRun,
126
+ ordneSeedsZu,
119
127
  parseDerivationResponse,
120
128
  parseRunResponse,
129
+ parseSeedListe,
121
130
  preferredHeadRevisionOf,
122
131
  promptDiagnosticsOf,
123
132
  publishedHeadsOf,
@@ -137,6 +146,7 @@ import {
137
146
  runSceneDerivation,
138
147
  sceneOnlyCapabilities,
139
148
  scopeOfPath,
149
+ seedFehlerText,
140
150
  selectableTargetModels,
141
151
  showChain,
142
152
  simulatedAnswerFor,
@@ -148,7 +158,7 @@ import {
148
158
  validateElementVersion,
149
159
  validatePayload,
150
160
  withReference
151
- } from "./chunk-3MUI4ZV7.mjs";
161
+ } from "./chunk-B2YV4TNI.mjs";
152
162
  import {
153
163
  ContractViolation,
154
164
  STYLE_PAYLOAD,
@@ -162,7 +172,6 @@ import {
162
172
  preferredVersionId,
163
173
  resolvePreferredHead
164
174
  } from "./chunk-FUY2OCVC.mjs";
165
- import "./chunk-NBUS7AWR.mjs";
166
175
 
167
176
  // src/lib/szenen/providerCheckStore.ts
168
177
  function collectProviderChecks(events) {
@@ -824,11 +833,16 @@ function createMemoryViewPort(options = {}) {
824
833
  const neueBilder = input.images.map((bild, offset) => ({
825
834
  media_ref: `${input.generationBatchId}-${startPosition + offset}`,
826
835
  position: startPosition + offset,
836
+ generation_batch_id: input.generationBatchId,
827
837
  model: bild.model ?? null,
828
838
  seed: bild.seed ?? null,
829
839
  width: bild.width ?? null,
830
840
  height: bild.height ?? null
831
841
  }));
842
+ const importOrigins = [
843
+ ...vorhandenesItem?.origins ?? [],
844
+ { generation_batch_id: input.generationBatchId, source_kind: "external_import" }
845
+ ];
832
846
  const neu = {
833
847
  item_id: vorhandenesItem?.item_id ?? `szn-mem-batch-${input.generationBatchId}`,
834
848
  prompt_run_id: input.runId,
@@ -836,7 +850,8 @@ function createMemoryViewPort(options = {}) {
836
850
  ...vorhandenesItem?.generation_batch_ids ?? [],
837
851
  input.generationBatchId
838
852
  ],
839
- source_kind: vorhandenesItem?.source_kind ?? "external_import",
853
+ source_kind: gesamtHerkunft(importOrigins),
854
+ origins: importOrigins,
840
855
  title: vorhandenesItem?.title ?? input.title ?? `Batch aus Lauf ${input.runId}`,
841
856
  images: [...vorhandenesItem?.images ?? [], ...neueBilder]
842
857
  };
@@ -890,11 +905,16 @@ function createMemoryViewPort(options = {}) {
890
905
  const neueBilder = input.images.map((bild, offset) => ({
891
906
  media_ref: `${input.generationBatchId}-${startPosition + offset}`,
892
907
  position: startPosition + offset,
908
+ generation_batch_id: input.generationBatchId,
893
909
  model: bild.model ?? null,
894
910
  seed: bild.seed ?? null,
895
911
  width: bild.width ?? null,
896
912
  height: bild.height ?? null
897
913
  }));
914
+ const genOrigins = [
915
+ ...vorhandenesItem?.origins ?? [],
916
+ { generation_batch_id: input.generationBatchId, source_kind: "generated_in_app" }
917
+ ];
898
918
  const neu = {
899
919
  item_id: vorhandenesItem?.item_id ?? `szn-mem-gen-${input.generationBatchId}`,
900
920
  prompt_run_id: input.runId,
@@ -902,7 +922,8 @@ function createMemoryViewPort(options = {}) {
902
922
  ...vorhandenesItem?.generation_batch_ids ?? [],
903
923
  input.generationBatchId
904
924
  ],
905
- source_kind: "generated_in_app",
925
+ source_kind: gesamtHerkunft(genOrigins),
926
+ origins: genOrigins,
906
927
  title: vorhandenesItem?.title ?? `Batch aus Lauf ${input.runId}`,
907
928
  images: [...vorhandenesItem?.images ?? [], ...neueBilder]
908
929
  };
@@ -933,6 +954,31 @@ function createMemoryViewPort(options = {}) {
933
954
  }
934
955
  }
935
956
  });
957
+ const seedVorlagen = [...options.seedTemplates ?? []];
958
+ let seedVorlagenZaehler = 0;
959
+ const seedTemplatePort = {
960
+ async listSeedTemplates() {
961
+ if (options.seedTemplatesFehler) {
962
+ return { kind: "error", message: "Vorlagenbestand nicht lesbar (Simulation).", retryable: true };
963
+ }
964
+ return ok(seedVorlagen);
965
+ },
966
+ async saveSeedTemplate(input) {
967
+ const vorlage = {
968
+ id: `seedtpl-${seedVorlagenZaehler += 1}`,
969
+ name: input.name,
970
+ seeds: input.seeds
971
+ };
972
+ seedVorlagen.push(vorlage);
973
+ return done(vorlage);
974
+ },
975
+ async deleteSeedTemplate(id) {
976
+ const index = seedVorlagen.findIndex((v) => String(v.id) === String(id));
977
+ if (index < 0) return { kind: "error", message: `Seed-Vorlage nicht gefunden: ${id}`, retryable: false };
978
+ seedVorlagen.splice(index, 1);
979
+ return done(null);
980
+ }
981
+ };
936
982
  const port = {
937
983
  elements,
938
984
  context: {
@@ -950,6 +996,9 @@ function createMemoryViewPort(options = {}) {
950
996
  if (options.derivation !== false) port.derivation = derivationPort;
951
997
  if (options.imageBatchHistory !== false) port.imageBatchHistory = imageBatchHistoryPort;
952
998
  if (options.imageBatchWrite !== false) port.imageBatchWrite = imageBatchWritePort;
999
+ if (options.seedTemplates !== void 0 || options.seedTemplatesFehler) {
1000
+ port.seedTemplates = seedTemplatePort;
1001
+ }
953
1002
  if (options.imageGeneration !== false) port.imageGeneration = imageGenerationPort;
954
1003
  return port;
955
1004
  }
@@ -1598,6 +1647,7 @@ export {
1598
1647
  CHAIN_LINK_FIELDS,
1599
1648
  CapabilityAnchorError,
1600
1649
  ContractViolation,
1650
+ DEFAULT_SEED_VORLAGE,
1601
1651
  DERIVABLE_TYPES,
1602
1652
  ELEMENT_VERSION_SCHEMA_FILE,
1603
1653
  FLOW_EXECUTION_PATH,
@@ -1617,6 +1667,7 @@ export {
1617
1667
  SZENEN_SCHEMAS,
1618
1668
  SzenenView,
1619
1669
  UNCHECKED_PUBLISH_RULES,
1670
+ UPLOAD_MAX_BYTES,
1620
1671
  UnsupportedSchemaError,
1621
1672
  VersionDagError,
1622
1673
  areasFor,
@@ -1705,8 +1756,10 @@ export {
1705
1756
  findSceneDerivationRecovery,
1706
1757
  firstError,
1707
1758
  formatSchemaErrors,
1759
+ formatSeedListe,
1708
1760
  freezeDraftChain,
1709
1761
  freezeSceneWorkingState,
1762
+ gesamtHerkunft,
1710
1763
  getSzenenSchemas,
1711
1764
  gruppiereZuItems,
1712
1765
  hasPlacementCapability,
@@ -1714,16 +1767,21 @@ export {
1714
1767
  isOk,
1715
1768
  isProjectionCurrent,
1716
1769
  isSzenenItemId,
1770
+ istZipDatei,
1717
1771
  itemIdForRun,
1718
1772
  ladeVorschauen,
1773
+ leseBilderAusZip,
1719
1774
  linkFor,
1720
1775
  locatorTeile,
1721
1776
  logRevisionOf,
1722
1777
  mergeSzenenBatchOrigin,
1778
+ mitDefaultVorlage,
1723
1779
  noticeFor,
1724
1780
  ordersForPromptRun,
1781
+ ordneSeedsZu,
1725
1782
  parseDerivationResponse,
1726
1783
  parseRunResponse,
1784
+ parseSeedListe,
1727
1785
  preferredHeadRevisionOf,
1728
1786
  preferredVersionId,
1729
1787
  promptDiagnosticsOf,
@@ -1747,6 +1805,7 @@ export {
1747
1805
  runStoreDifferential,
1748
1806
  sceneOnlyCapabilities,
1749
1807
  scopeOfPath,
1808
+ seedFehlerText,
1750
1809
  selectableTargetModels,
1751
1810
  showChain,
1752
1811
  simulatedAnswerFor,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rslsp1/fa-app-tools",
3
- "version": "5.1.0",
3
+ "version": "7.0.1",
4
4
  "description": "Shared tools and hooks for Fine Art flow apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -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
- };