@rslsp1/fa-app-tools 2.0.17 → 2.0.19

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/index.d.mts CHANGED
@@ -583,6 +583,8 @@ interface HFStateResult {
583
583
  error: string | null;
584
584
  pendingBufferCount: number;
585
585
  eventCount: number;
586
+ localOnlyCount: number;
587
+ confirmedEventKeys: Set<string>;
586
588
  forks: Array<{
587
589
  parentTs: number;
588
590
  childTs: number[];
@@ -612,6 +614,6 @@ declare function findTips(dag: Dag): number[];
612
614
  declare function findForks(dag: Dag): DagFork[];
613
615
  declare function topoSort(events: HFEvent[]): HFEvent[];
614
616
 
615
- declare const LIB_VERSION = "2.0.17";
617
+ declare const LIB_VERSION = "2.0.19";
616
618
 
617
619
  export { AvatarArchitectApp, type AvatarArchitectAppProps, CollapsibleCard, CompactDropdown, type DagFork, type ExtractedCharacter, FaApp, type FaAppProps, FaToolsBadge, type FlowSdk, GLOBAL_STYLES, type Generation, type HFEvent, type HFEventVersion, type HFFileInfo$1 as HFFileInfo, type HFMetadataEntry, type HFStateMeta, type HFStateResult, type HFStateSnapshot, HistoryPanel, type ImageAddedPayload, InspectPanel, LIB_VERSION, LabBlend, LabCompare, type LabFrame, LabImagePicker, type LabItem, LabLoop, LabRemix, type LabServices, LabsTab, ListView, type MediaItem, MediaLibrary, type MetadataUpdatedPayload, PillButton, type ProjectMeta, type ProjectSettings, ProjectSyncTab, PromptTab, SectionLabel, type SelectedLabImage, type SelectedTag, SetupPanel, type SyncDiff, TagManagerPanel, type TagOption, type TagUpsertedPayload, type WorkspaceTags, applyEvent, applyEvents, autoLabel, buildBlendInstruction, buildCompareInstruction, buildDag, buildFallbackPrompt, buildGenerationPrompt, buildImageGenerationOptions, buildLoopInstruction, buildPromptTabPayload, buildReferenceImageMediaIds, buildRemixInstruction, buildScanInstruction, cleanAiResponse, createFlowServices, exportProjectToZip, findForks, findTips, formatTreeToMarkdown, frameToGeneration, getFormattedTimestamp, getHFToken, getSessionClientId, groupGenerationsToLabItems, hfBatchArchive, hfBootstrapFromLegacy, hfDeleteProject, hfDownloadProject, hfListDir, hfListProjects, hfLoadImageAsBase64, hfUploadImage, hfUploadProjectForm, hfUploadSmallFile, importProjectFromZip, injectXMPMetadata, interpretSdkError, loadHFState, loadPendingEvents, parsePromptFile, parsePromptResponse, setHFToken, topoSort, tsFromEventPath, useHFState, useKeyboardNavigation, useOnClickOutside, writeHFEvent };
package/dist/index.d.ts CHANGED
@@ -583,6 +583,8 @@ interface HFStateResult {
583
583
  error: string | null;
584
584
  pendingBufferCount: number;
585
585
  eventCount: number;
586
+ localOnlyCount: number;
587
+ confirmedEventKeys: Set<string>;
586
588
  forks: Array<{
587
589
  parentTs: number;
588
590
  childTs: number[];
@@ -612,6 +614,6 @@ declare function findTips(dag: Dag): number[];
612
614
  declare function findForks(dag: Dag): DagFork[];
613
615
  declare function topoSort(events: HFEvent[]): HFEvent[];
614
616
 
615
- declare const LIB_VERSION = "2.0.17";
617
+ declare const LIB_VERSION = "2.0.19";
616
618
 
617
619
  export { AvatarArchitectApp, type AvatarArchitectAppProps, CollapsibleCard, CompactDropdown, type DagFork, type ExtractedCharacter, FaApp, type FaAppProps, FaToolsBadge, type FlowSdk, GLOBAL_STYLES, type Generation, type HFEvent, type HFEventVersion, type HFFileInfo$1 as HFFileInfo, type HFMetadataEntry, type HFStateMeta, type HFStateResult, type HFStateSnapshot, HistoryPanel, type ImageAddedPayload, InspectPanel, LIB_VERSION, LabBlend, LabCompare, type LabFrame, LabImagePicker, type LabItem, LabLoop, LabRemix, type LabServices, LabsTab, ListView, type MediaItem, MediaLibrary, type MetadataUpdatedPayload, PillButton, type ProjectMeta, type ProjectSettings, ProjectSyncTab, PromptTab, SectionLabel, type SelectedLabImage, type SelectedTag, SetupPanel, type SyncDiff, TagManagerPanel, type TagOption, type TagUpsertedPayload, type WorkspaceTags, applyEvent, applyEvents, autoLabel, buildBlendInstruction, buildCompareInstruction, buildDag, buildFallbackPrompt, buildGenerationPrompt, buildImageGenerationOptions, buildLoopInstruction, buildPromptTabPayload, buildReferenceImageMediaIds, buildRemixInstruction, buildScanInstruction, cleanAiResponse, createFlowServices, exportProjectToZip, findForks, findTips, formatTreeToMarkdown, frameToGeneration, getFormattedTimestamp, getHFToken, getSessionClientId, groupGenerationsToLabItems, hfBatchArchive, hfBootstrapFromLegacy, hfDeleteProject, hfDownloadProject, hfListDir, hfListProjects, hfLoadImageAsBase64, hfUploadImage, hfUploadProjectForm, hfUploadSmallFile, importProjectFromZip, injectXMPMetadata, interpretSdkError, loadHFState, loadPendingEvents, parsePromptFile, parsePromptResponse, setHFToken, topoSort, tsFromEventPath, useHFState, useKeyboardNavigation, useOnClickOutside, writeHFEvent };
package/dist/index.js CHANGED
@@ -2610,6 +2610,7 @@ function useHFState(token, namespace) {
2610
2610
  const [isLoading, setIsLoading] = (0, import_react14.useState)(false);
2611
2611
  const [error, setError] = (0, import_react14.useState)(null);
2612
2612
  const [eventCount, setEventCount] = (0, import_react14.useState)(0);
2613
+ const [localOnlyCount, setLocalOnlyCount] = (0, import_react14.useState)(0);
2613
2614
  const [forks, setForks] = (0, import_react14.useState)([]);
2614
2615
  const [pendingBufferCount, setPendingBufferCount] = (0, import_react14.useState)(readOfflineBuffer().length);
2615
2616
  const [lastEventTs, setLastEventTs] = (0, import_react14.useState)(0);
@@ -2642,19 +2643,31 @@ function useHFState(token, namespace) {
2642
2643
  tags: { by_category: {}, all: [] },
2643
2644
  meta: { consolidatedAt: 0, version: 1 }
2644
2645
  };
2645
- const events = await loadPendingEvents(namespace, token, base.meta.consolidatedAt);
2646
- events.forEach((e) => knownEventPaths.current.add(`${e.ts}_${e.clientId}`));
2646
+ const hfEvents = await loadPendingEvents(namespace, token, base.meta.consolidatedAt);
2647
+ const hfEventKeys = new Set(hfEvents.map((e) => `${e.ts}_${e.clientId}`));
2648
+ const localOnlyEvents = allEventsRef.current.filter(
2649
+ (e) => !hfEventKeys.has(`${e.ts}_${e.clientId}`)
2650
+ );
2651
+ setLocalOnlyCount(localOnlyEvents.length);
2652
+ if (localOnlyEvents.length) {
2653
+ console.warn("[HF] loadFull: Behalte", localOnlyEvents.length, "lokale Events, die noch nicht auf HF sind");
2654
+ }
2655
+ hfEvents.forEach((e) => knownEventPaths.current.add(`${e.ts}_${e.clientId}`));
2647
2656
  allEventsRef.current = [];
2648
- const finalState = applyNewEvents(base, events);
2657
+ const finalState = applyNewEvents(base, [...hfEvents, ...localOnlyEvents]);
2649
2658
  setState(finalState);
2650
2659
  const buffer = readOfflineBuffer();
2651
2660
  if (buffer.length) {
2661
+ const failed = [];
2652
2662
  for (const evt of buffer) {
2653
- await writeHFEvent(namespace, token, evt.type, evt.payload, evt.prevTs).catch(() => {
2654
- });
2663
+ try {
2664
+ await writeHFEvent(namespace, token, evt.type, evt.payload, evt.prevTs);
2665
+ } catch {
2666
+ failed.push(evt);
2667
+ }
2655
2668
  }
2656
- writeOfflineBuffer([]);
2657
- setPendingBufferCount(0);
2669
+ writeOfflineBuffer(failed);
2670
+ setPendingBufferCount(failed.length);
2658
2671
  const freshEvents = await loadPendingEvents(namespace, token, base.meta.consolidatedAt);
2659
2672
  freshEvents.forEach((e) => knownEventPaths.current.add(`${e.ts}_${e.clientId}`));
2660
2673
  setState((prev) => prev ? applyNewEvents(base, freshEvents) : prev);
@@ -2672,6 +2685,9 @@ function useHFState(token, namespace) {
2672
2685
  const newEvents = events.filter((e) => !knownEventPaths.current.has(`${e.ts}_${e.clientId}`));
2673
2686
  if (!newEvents.length) return;
2674
2687
  newEvents.forEach((e) => knownEventPaths.current.add(`${e.ts}_${e.clientId}`));
2688
+ const confirmedKeys = new Set(events.map((e) => `${e.ts}_${e.clientId}`));
2689
+ const stillLocalOnly = allEventsRef.current.filter((e) => !confirmedKeys.has(`${e.ts}_${e.clientId}`));
2690
+ setLocalOnlyCount(stillLocalOnly.length);
2675
2691
  setState((prev) => prev ? applyNewEvents(prev, newEvents) : prev);
2676
2692
  } catch {
2677
2693
  }
@@ -2684,7 +2700,7 @@ function useHFState(token, namespace) {
2684
2700
  const id = setInterval(pollNew, POLL_INTERVAL_MS);
2685
2701
  return () => clearInterval(id);
2686
2702
  }, [token, namespace, pollNew]);
2687
- const writeEvent2 = (0, import_react14.useCallback)(async (type, payload) => {
2703
+ const writeEvent = (0, import_react14.useCallback)(async (type, payload) => {
2688
2704
  const prevTs = lastEventTs ? [lastEventTs] : [state?.meta.consolidatedAt ?? 0];
2689
2705
  console.log("[HF] writeEvent called:", { type, namespace, tokenOk: !!token, prevTs });
2690
2706
  await pollNew();
@@ -2693,6 +2709,9 @@ function useHFState(token, namespace) {
2693
2709
  const event = await writeHFEvent(namespace, token, type, payload, prevTs);
2694
2710
  console.log("[HF] writeHFEvent success:", event.ts);
2695
2711
  knownEventPaths.current.add(`${event.ts}_${event.clientId}`);
2712
+ const confirmedKey = `${event.ts}_${event.clientId}`;
2713
+ const stillLocalOnly = allEventsRef.current.filter((e) => `${e.ts}_${e.clientId}` !== confirmedKey && !knownEventPaths.current.has(`${e.ts}_${e.clientId}`));
2714
+ setLocalOnlyCount(stillLocalOnly.length);
2696
2715
  setState((prev) => prev ? applyNewEvents(prev, [event]) : prev);
2697
2716
  setLastEventTs(event.ts);
2698
2717
  await pollNew();
@@ -2710,6 +2729,7 @@ function useHFState(token, namespace) {
2710
2729
  writeOfflineBuffer([...buffer, offline]);
2711
2730
  setPendingBufferCount(buffer.length + 1);
2712
2731
  setState((prev) => prev ? applyNewEvents(prev, [offline]) : prev);
2732
+ setLocalOnlyCount((prev) => prev + 1);
2713
2733
  }
2714
2734
  }, [namespace, token, lastEventTs, state, pollNew, applyNewEvents]);
2715
2735
  return {
@@ -2717,9 +2737,11 @@ function useHFState(token, namespace) {
2717
2737
  isLoading,
2718
2738
  error,
2719
2739
  pendingBufferCount,
2740
+ localOnlyCount,
2741
+ confirmedEventKeys: knownEventPaths.current,
2720
2742
  eventCount,
2721
2743
  forks,
2722
- writeEvent: writeEvent2,
2744
+ writeEvent,
2723
2745
  refresh: loadFull,
2724
2746
  lastEventTs,
2725
2747
  allEvents: allEventsRef.current,
@@ -3974,7 +3996,7 @@ async function uploadViaCdnLib(token, path, bytes, mimeType) {
3974
3996
  }
3975
3997
  return [s];
3976
3998
  }
3977
- async function writeEvent(token, namespace) {
3999
+ async function writeTestEvent(token, namespace) {
3978
4000
  const ns = namespace.endsWith("/") ? namespace : namespace ? namespace + "/" : "";
3979
4001
  const ts = Date.now();
3980
4002
  const uuid = crypto.randomUUID().slice(0, 8);
@@ -4099,7 +4121,80 @@ function TestCard({
4099
4121
  ] })
4100
4122
  ] });
4101
4123
  }
4102
- function HFTestTab({ token, namespace, galleryItems }) {
4124
+ var EVENT_TYPE_COLORS = {
4125
+ image_added: "#60a5fa",
4126
+ tag_upserted: "#a78bfa",
4127
+ metadata_updated: "#34d399",
4128
+ probe: "#fbbf24"
4129
+ };
4130
+ function EventMonitor({ events, confirmedEventKeys, galleryItems, imageUploadStatus }) {
4131
+ if (!events.length) {
4132
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { padding: "12px 14px", fontSize: 12, color: "rgba(255,255,255,0.3)", fontStyle: "italic" }, children: "Noch keine Events geladen." });
4133
+ }
4134
+ const sorted = [...events].sort((a, b) => b.ts - a.ts).slice(0, 30);
4135
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { padding: "6px 8px 4px" }, children: [
4136
+ sorted.map((e, i) => {
4137
+ const eKey = `${e.ts}_${e.clientId}`;
4138
+ const isConfirmed = confirmedEventKeys.has(eKey);
4139
+ const typeColor = EVENT_TYPE_COLORS[e.type] || "rgba(255,255,255,0.5)";
4140
+ const date = new Date(e.ts);
4141
+ const timeStr = date.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit", second: "2-digit" });
4142
+ const isImageEvent = e.type === "image_added";
4143
+ const imgId = isImageEvent ? e.payload?.id : void 0;
4144
+ const galleryItem = imgId ? galleryItems.find((g) => g.id === imgId) : void 0;
4145
+ const uploadStatus = imgId ? imageUploadStatus.get(imgId) : void 0;
4146
+ const payloadStr = JSON.stringify(e.payload ?? {});
4147
+ const payloadPreview = payloadStr.length > 70 ? payloadStr.slice(0, 70) + "\u2026" : payloadStr;
4148
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { display: "flex", gap: 7, alignItems: "flex-start", padding: "6px 2px", borderBottom: "1px solid rgba(255,255,255,0.05)" }, children: [
4149
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { width: 36, height: 36, flexShrink: 0, borderRadius: 4, overflow: "hidden", background: "rgba(255,255,255,0.05)", display: "flex", alignItems: "center", justifyContent: "center" }, children: isImageEvent ? galleryItem?.base64 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("img", { src: galleryItem.base64, style: { width: "100%", height: "100%", objectFit: "cover" } }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "material-symbols-outlined", style: { fontSize: 18, color: "rgba(255,255,255,0.2)" }, children: "image" }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "material-symbols-outlined", style: { fontSize: 16, color: "rgba(255,255,255,0.15)" }, children: e.type === "tag_upserted" ? "label" : e.type === "metadata_updated" ? "edit_note" : "data_object" }) }),
4150
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
4151
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 3 }, children: [
4152
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { style: { fontSize: 11, fontWeight: 700, color: typeColor }, children: e.type }),
4153
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { style: { fontSize: 9, color: "rgba(255,255,255,0.25)", fontVariantNumeric: "tabular-nums" }, children: timeStr }),
4154
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { flex: 1 } }),
4155
+ isConfirmed ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { style: { fontSize: 9, fontWeight: 700, color: "#4ade80", display: "flex", alignItems: "center", gap: 2 }, children: [
4156
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "material-symbols-outlined", style: { fontSize: 11 }, children: "check_circle" }),
4157
+ "HF"
4158
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { style: { fontSize: 9, fontWeight: 700, color: "#fbbf24", display: "flex", alignItems: "center", gap: 2 }, children: [
4159
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "material-symbols-outlined", style: { fontSize: 11 }, children: "schedule" }),
4160
+ "lokal"
4161
+ ] })
4162
+ ] }),
4163
+ isImageEvent && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 3 }, children: [
4164
+ uploadStatus === "done" && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { style: { fontSize: 9, color: "#4ade80", display: "flex", alignItems: "center", gap: 2 }, children: [
4165
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "material-symbols-outlined", style: { fontSize: 11 }, children: "cloud_done" }),
4166
+ "Bild auf HF"
4167
+ ] }),
4168
+ uploadStatus === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { style: { fontSize: 9, color: "#60a5fa", display: "flex", alignItems: "center", gap: 2 }, children: [
4169
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "material-symbols-outlined", style: { fontSize: 11 }, children: "cloud_upload" }),
4170
+ "Bild l\xE4dt\u2026"
4171
+ ] }),
4172
+ uploadStatus === "failed" && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { style: { fontSize: 9, color: "#f87171", display: "flex", alignItems: "center", gap: 2 }, children: [
4173
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "material-symbols-outlined", style: { fontSize: 11 }, children: "cloud_off" }),
4174
+ "Bild-Upload fehlgeschlagen"
4175
+ ] }),
4176
+ !uploadStatus && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { style: { fontSize: 9, color: "rgba(255,255,255,0.2)" }, children: "Bild-Upload unbekannt (anderes Ger\xE4t?)" }),
4177
+ galleryItem?.base64 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { style: { fontSize: 9, color: "#4ade80", marginLeft: 6, display: "flex", alignItems: "center", gap: 2 }, children: [
4178
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "material-symbols-outlined", style: { fontSize: 11 }, children: "photo" }),
4179
+ "lokal vorhanden"
4180
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { style: { fontSize: 9, color: "#f87171", marginLeft: 6, display: "flex", alignItems: "center", gap: 2 }, children: [
4181
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "material-symbols-outlined", style: { fontSize: 11 }, children: "broken_image" }),
4182
+ "kein lokales Bild"
4183
+ ] })
4184
+ ] }),
4185
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { fontSize: 9, color: "rgba(255,255,255,0.25)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: payloadPreview })
4186
+ ] })
4187
+ ] }, `${eKey}_${i}`);
4188
+ }),
4189
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { padding: "6px 0 2px", fontSize: 9, color: "rgba(255,255,255,0.2)", textAlign: "right" }, children: [
4190
+ events.length,
4191
+ " Events gesamt \xB7 ",
4192
+ [...confirmedEventKeys].length,
4193
+ " auf HF best\xE4tigt"
4194
+ ] })
4195
+ ] });
4196
+ }
4197
+ function HFTestTab({ token, namespace, galleryItems, allEvents = [], confirmedEventKeys = /* @__PURE__ */ new Set(), imageUploadStatus = /* @__PURE__ */ new Map() }) {
4103
4198
  const [selected, setSelected] = (0, import_react22.useState)(null);
4104
4199
  const [results, setResults] = (0, import_react22.useState)({});
4105
4200
  const [expanded, setExpanded] = (0, import_react22.useState)({});
@@ -4135,7 +4230,7 @@ function HFTestTab({ token, namespace, galleryItems }) {
4135
4230
  break;
4136
4231
  }
4137
4232
  } else {
4138
- steps = await writeEvent(token, namespace);
4233
+ steps = await writeTestEvent(token, namespace);
4139
4234
  }
4140
4235
  } catch (e) {
4141
4236
  steps = [{ label: "Unexpected error", method: "-", url: "-", reqHeaders: {}, error: String(e?.message ?? e), ok: false }];
@@ -4152,96 +4247,123 @@ function HFTestTab({ token, namespace, galleryItems }) {
4152
4247
  ];
4153
4248
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { display: "flex", flexDirection: "column", height: "100%", overflowY: "auto", padding: "12px 10px 80px", boxSizing: "border-box", fontFamily: "inherit" }, children: [
4154
4249
  noToken && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { marginBottom: 10, padding: "8px 12px", background: "rgba(248,113,113,0.08)", borderRadius: 8, border: "1px solid rgba(248,113,113,0.2)", fontSize: 12, color: "#f87171" }, children: "Kein HF-Token geladen \u2014 bitte zuerst Token im Sync-Tab eingeben." }),
4155
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { marginBottom: 14 }, children: [
4156
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { fontSize: 10, fontWeight: 700, color: "rgba(255,255,255,0.3)", textTransform: "uppercase", letterSpacing: "0.08em", marginBottom: 8 }, children: [
4157
- "Bild ausw\xE4hlen (",
4158
- withResults.length,
4159
- " verf\xFCgbar)"
4160
- ] }),
4161
- withResults.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { fontSize: 12, color: "rgba(255,255,255,0.3)", fontStyle: "italic" }, children: "Noch keine Bilder in der Galerie. Generiere zuerst ein Bild oder lade von HF." }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 6 }, children: withResults.slice(0, 12).map((g) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4162
- "button",
4163
- {
4164
- onClick: () => setSelected(g),
4165
- style: { padding: 0, border: `2px solid ${selected?.id === g.id ? "#0284c7" : "transparent"}`, borderRadius: 6, cursor: "pointer", overflow: "hidden", background: "none", lineHeight: 0 },
4166
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4167
- "img",
4168
- {
4169
- src: g.base64,
4170
- alt: g.prompt || g.id,
4171
- style: { width: "100%", aspectRatio: "1", objectFit: "cover", display: "block", borderRadius: 4 }
4172
- }
4173
- )
4174
- },
4175
- g.id
4176
- )) }),
4177
- selected && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { marginTop: 10, display: "flex", gap: 10, alignItems: "flex-start" }, children: [
4178
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4179
- "img",
4250
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { marginBottom: 12 }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4251
+ CollapsibleCard,
4252
+ {
4253
+ title: "Event Monitor",
4254
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "material-symbols-outlined", style: { fontSize: 16 }, children: "bolt" }),
4255
+ defaultOpen: true,
4256
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4257
+ EventMonitor,
4180
4258
  {
4181
- src: selected.base64,
4182
- style: { width: 80, height: 80, objectFit: "cover", borderRadius: 8, border: "1px solid rgba(255,255,255,0.1)", flexShrink: 0 }
4259
+ events: allEvents,
4260
+ confirmedEventKeys,
4261
+ galleryItems,
4262
+ imageUploadStatus
4183
4263
  }
4184
- ),
4185
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
4186
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { fontSize: 11, fontWeight: 700, color: "rgba(255,255,255,0.7)", marginBottom: 2 }, children: "Ausgew\xE4hlt" }),
4187
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { fontSize: 10, color: "rgba(255,255,255,0.3)", wordBreak: "break-all" }, children: [
4188
- "ID: ",
4189
- selected.id
4264
+ )
4265
+ }
4266
+ ) }),
4267
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4268
+ CollapsibleCard,
4269
+ {
4270
+ title: "Upload Tests",
4271
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "material-symbols-outlined", style: { fontSize: 16 }, children: "science" }),
4272
+ defaultOpen: false,
4273
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { padding: "10px 10px 4px" }, children: [
4274
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { marginBottom: 14 }, children: [
4275
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { fontSize: 10, fontWeight: 700, color: "rgba(255,255,255,0.3)", textTransform: "uppercase", letterSpacing: "0.08em", marginBottom: 8 }, children: [
4276
+ "Bild ausw\xE4hlen (",
4277
+ withResults.length,
4278
+ " verf\xFCgbar)"
4279
+ ] }),
4280
+ withResults.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { fontSize: 12, color: "rgba(255,255,255,0.3)", fontStyle: "italic" }, children: "Noch keine Bilder in der Galerie. Generiere zuerst ein Bild oder lade von HF." }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 6 }, children: withResults.slice(0, 12).map((g) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4281
+ "button",
4282
+ {
4283
+ onClick: () => setSelected(g),
4284
+ style: { padding: 0, border: `2px solid ${selected?.id === g.id ? "#0284c7" : "transparent"}`, borderRadius: 6, cursor: "pointer", overflow: "hidden", background: "none", lineHeight: 0 },
4285
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4286
+ "img",
4287
+ {
4288
+ src: g.base64,
4289
+ alt: g.prompt || g.id,
4290
+ style: { width: "100%", aspectRatio: "1", objectFit: "cover", display: "block", borderRadius: 4 }
4291
+ }
4292
+ )
4293
+ },
4294
+ g.id
4295
+ )) }),
4296
+ selected && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { marginTop: 10, display: "flex", gap: 10, alignItems: "flex-start" }, children: [
4297
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4298
+ "img",
4299
+ {
4300
+ src: selected.base64,
4301
+ style: { width: 80, height: 80, objectFit: "cover", borderRadius: 8, border: "1px solid rgba(255,255,255,0.1)", flexShrink: 0 }
4302
+ }
4303
+ ),
4304
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
4305
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { fontSize: 11, fontWeight: 700, color: "rgba(255,255,255,0.7)", marginBottom: 2 }, children: "Ausgew\xE4hlt" }),
4306
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { fontSize: 10, color: "rgba(255,255,255,0.3)", wordBreak: "break-all" }, children: [
4307
+ "ID: ",
4308
+ selected.id
4309
+ ] }),
4310
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { fontSize: 10, color: "rgba(255,255,255,0.3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", marginTop: 2 }, children: [
4311
+ "Ziel: test/",
4312
+ selected.id,
4313
+ ".jpg"
4314
+ ] }),
4315
+ selected.prompt && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { fontSize: 10, color: "rgba(255,255,255,0.25)", marginTop: 2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: selected.prompt })
4316
+ ] })
4317
+ ] })
4190
4318
  ] }),
4191
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { fontSize: 10, color: "rgba(255,255,255,0.3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", marginTop: 2 }, children: [
4192
- "Ziel: test/",
4193
- selected.id,
4194
- ".jpg"
4319
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { marginBottom: 14 }, children: [
4320
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { fontSize: 10, fontWeight: 700, color: "rgba(255,255,255,0.3)", textTransform: "uppercase", letterSpacing: "0.1em", marginBottom: 8, borderBottom: "1px solid rgba(255,255,255,0.06)", paddingBottom: 4 }, children: [
4321
+ "Bild hochladen \u2192 test/",
4322
+ "{",
4323
+ "id",
4324
+ "}",
4325
+ ".jpg"
4326
+ ] }),
4327
+ imgTests.map((t) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4328
+ TestCard,
4329
+ {
4330
+ id: t.id,
4331
+ label: t.label,
4332
+ icon: t.icon,
4333
+ desc: t.desc,
4334
+ disabled: noToken || noImg || results[t.id]?.status === "running",
4335
+ state: results[t.id],
4336
+ onRun: () => run(t.id),
4337
+ expanded: !!expanded[t.id],
4338
+ onToggle: () => setExpanded((e) => ({ ...e, [t.id]: !e[t.id] }))
4339
+ },
4340
+ t.id
4341
+ ))
4195
4342
  ] }),
4196
- selected.prompt && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { fontSize: 10, color: "rgba(255,255,255,0.25)", marginTop: 2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: selected.prompt })
4343
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { marginBottom: 4 }, children: [
4344
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { fontSize: 10, fontWeight: 700, color: "rgba(255,255,255,0.3)", textTransform: "uppercase", letterSpacing: "0.1em", marginBottom: 8, borderBottom: "1px solid rgba(255,255,255,0.06)", paddingBottom: 4 }, children: [
4345
+ "Event schreiben \u2192 ",
4346
+ namespace || "(kein namespace)",
4347
+ "test/events/"
4348
+ ] }),
4349
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4350
+ TestCard,
4351
+ {
4352
+ id: "event",
4353
+ label: "Write Event",
4354
+ icon: "bolt",
4355
+ desc: "Kleines JSON-Event hochladen (direct commit)",
4356
+ disabled: noToken || results["event"]?.status === "running",
4357
+ state: results["event"],
4358
+ onRun: () => run("event"),
4359
+ expanded: !!expanded["event"],
4360
+ onToggle: () => setExpanded((e) => ({ ...e, event: !e.event }))
4361
+ }
4362
+ )
4363
+ ] })
4197
4364
  ] })
4198
- ] })
4199
- ] }),
4200
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { marginBottom: 18 }, children: [
4201
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { fontSize: 10, fontWeight: 700, color: "rgba(255,255,255,0.3)", textTransform: "uppercase", letterSpacing: "0.1em", marginBottom: 8, borderBottom: "1px solid rgba(255,255,255,0.06)", paddingBottom: 4 }, children: [
4202
- "Bild hochladen \u2192 test/",
4203
- "{",
4204
- "id",
4205
- "}",
4206
- ".jpg"
4207
- ] }),
4208
- imgTests.map((t) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4209
- TestCard,
4210
- {
4211
- id: t.id,
4212
- label: t.label,
4213
- icon: t.icon,
4214
- desc: t.desc,
4215
- disabled: noToken || noImg || results[t.id]?.status === "running",
4216
- state: results[t.id],
4217
- onRun: () => run(t.id),
4218
- expanded: !!expanded[t.id],
4219
- onToggle: () => setExpanded((e) => ({ ...e, [t.id]: !e[t.id] }))
4220
- },
4221
- t.id
4222
- ))
4223
- ] }),
4224
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { children: [
4225
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { fontSize: 10, fontWeight: 700, color: "rgba(255,255,255,0.3)", textTransform: "uppercase", letterSpacing: "0.1em", marginBottom: 8, borderBottom: "1px solid rgba(255,255,255,0.06)", paddingBottom: 4 }, children: [
4226
- "Event schreiben \u2192 ",
4227
- namespace || "(kein namespace)",
4228
- "test/events/"
4229
- ] }),
4230
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4231
- TestCard,
4232
- {
4233
- id: "event",
4234
- label: "Write Event",
4235
- icon: "bolt",
4236
- desc: "Kleines JSON-Event hochladen (direct commit)",
4237
- disabled: noToken || results["event"]?.status === "running",
4238
- state: results["event"],
4239
- onRun: () => run("event"),
4240
- expanded: !!expanded["event"],
4241
- onToggle: () => setExpanded((e) => ({ ...e, event: !e.event }))
4242
- }
4243
- )
4244
- ] })
4365
+ }
4366
+ )
4245
4367
  ] });
4246
4368
  }
4247
4369
 
@@ -4293,14 +4415,25 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4293
4415
  state: hfState,
4294
4416
  isLoading: isHfRefreshing,
4295
4417
  pendingBufferCount,
4418
+ localOnlyCount,
4296
4419
  eventCount,
4420
+ allEvents: hfAllEvents,
4421
+ confirmedEventKeys: hfConfirmedKeys,
4297
4422
  writeEvent: hfWriteEvent,
4298
4423
  refresh: refreshHF,
4299
4424
  hasStateZip
4300
4425
  } = useHFState(hfToken, effectiveNamespace);
4426
+ const [imageUploadStatus, setImageUploadStatus] = (0, import_react23.useState)(/* @__PURE__ */ new Map());
4301
4427
  const [bootstrapLog, setBootstrapLog] = (0, import_react23.useState)([]);
4302
4428
  const [isBootstrapping, setIsBootstrapping] = (0, import_react23.useState)(false);
4303
4429
  const syncTopSlot = /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
4430
+ localOnlyCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: { background: "rgba(234,179,8,0.15)", border: "1px solid rgba(234,179,8,0.3)", padding: "4px 10px", fontSize: 11, color: "#fbbf24", borderRadius: 4, marginBottom: 4 }, children: [
4431
+ "\u26A0 ",
4432
+ localOnlyCount,
4433
+ " lokale Event",
4434
+ localOnlyCount > 1 ? "s" : "",
4435
+ " noch nicht auf HF best\xE4tigt"
4436
+ ] }),
4304
4437
  pendingBufferCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: { background: "linear-gradient(90deg,#f59e0b,#ef4444)", padding: "4px 10px", fontSize: 11, color: "#fff", borderRadius: 4, marginBottom: 4 }, children: [
4305
4438
  pendingBufferCount,
4306
4439
  " \xC4nderung",
@@ -4359,7 +4492,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4359
4492
  (0, import_react23.useEffect)(() => {
4360
4493
  galleryItemsRef.current = galleryItems;
4361
4494
  }, [galleryItems]);
4362
- const hfImageNotFoundRef = (0, import_react23.useRef)(/* @__PURE__ */ new Set());
4495
+ const hfImageNotFoundRef = (0, import_react23.useRef)(/* @__PURE__ */ new Map());
4363
4496
  (0, import_react23.useEffect)(() => {
4364
4497
  if (!hfState) return;
4365
4498
  if (hfState.tags?.by_category) setWorkspaceTags(hfState.tags);
@@ -4384,18 +4517,21 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4384
4517
  const merged = skeletons.map((s) => prev.find((g) => g.id === s.id) ?? s);
4385
4518
  return [...localOnly, ...merged];
4386
4519
  });
4520
+ const COOLDOWN_MS = 5 * 60 * 1e3;
4387
4521
  for (const entry of hfState.metadata) {
4388
- if (hfImageNotFoundRef.current.has(entry.id)) continue;
4522
+ if (galleryItemsRef.current.find((g) => g.id === entry.id)?.base64) continue;
4523
+ const failedAt = hfImageNotFoundRef.current.get(entry.id);
4524
+ if (failedAt && Date.now() - failedAt < COOLDOWN_MS) continue;
4389
4525
  hfLoadImageAsBase64(entry.id, hfToken).then((b64) => {
4390
4526
  if (!b64) {
4391
- hfImageNotFoundRef.current.add(entry.id);
4527
+ hfImageNotFoundRef.current.set(entry.id, Date.now());
4392
4528
  return;
4393
4529
  }
4394
4530
  const prefix = `data:${entry.mimeType || "image/jpeg"};base64,`;
4395
4531
  setGalleryItems((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
4396
4532
  setHistory((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
4397
4533
  }).catch(() => {
4398
- hfImageNotFoundRef.current.add(entry.id);
4534
+ hfImageNotFoundRef.current.set(entry.id, Date.now());
4399
4535
  });
4400
4536
  }
4401
4537
  }, [hfState]);
@@ -4649,8 +4785,10 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4649
4785
  });
4650
4786
  console.log("[HF] handleGenerateImage \u2014 condition check:", { hfToken: !!hfToken, base64: !!base64, effectiveNamespace });
4651
4787
  if (hfToken && base64 && effectiveNamespace) {
4652
- hfUploadImage(base64, genId, hfToken).catch((e) => {
4788
+ setImageUploadStatus((m) => new Map(m).set(genId, "uploading"));
4789
+ hfUploadImage(base64, genId, hfToken).then(() => setImageUploadStatus((m) => new Map(m).set(genId, "done"))).catch((e) => {
4653
4790
  console.error("[HF] hfUploadImage failed:", e);
4791
+ setImageUploadStatus((m) => new Map(m).set(genId, "failed"));
4654
4792
  });
4655
4793
  const entry = {
4656
4794
  id: genId,
@@ -5477,7 +5615,17 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5477
5615
  onTagMove: handleTagMove
5478
5616
  }
5479
5617
  ),
5480
- activeTab === "hftest" && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "absolute inset-0", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(HFTestTab, { token: hfToken, namespace: effectiveNamespace, galleryItems }) })
5618
+ activeTab === "hftest" && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "absolute inset-0", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
5619
+ HFTestTab,
5620
+ {
5621
+ token: hfToken,
5622
+ namespace: effectiveNamespace,
5623
+ galleryItems,
5624
+ allEvents: hfAllEvents,
5625
+ confirmedEventKeys: hfConfirmedKeys,
5626
+ imageUploadStatus
5627
+ }
5628
+ ) })
5481
5629
  ] })
5482
5630
  ] }),
5483
5631
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex border-t border-white/10 bg-black shrink-0", style: { height: 56, paddingBottom: "env(safe-area-inset-bottom, 0px)" }, children: [
@@ -5859,7 +6007,7 @@ function FaApp({
5859
6007
  // src/index.ts
5860
6008
  init_hfStateService();
5861
6009
  init_hfStateService();
5862
- var LIB_VERSION = "2.0.17";
6010
+ var LIB_VERSION = "2.0.19";
5863
6011
  // Annotate the CommonJS export names for ESM import in node:
5864
6012
  0 && (module.exports = {
5865
6013
  AvatarArchitectApp,
package/dist/index.mjs CHANGED
@@ -1942,6 +1942,7 @@ function useHFState(token, namespace) {
1942
1942
  const [isLoading, setIsLoading] = useState7(false);
1943
1943
  const [error, setError] = useState7(null);
1944
1944
  const [eventCount, setEventCount] = useState7(0);
1945
+ const [localOnlyCount, setLocalOnlyCount] = useState7(0);
1945
1946
  const [forks, setForks] = useState7([]);
1946
1947
  const [pendingBufferCount, setPendingBufferCount] = useState7(readOfflineBuffer().length);
1947
1948
  const [lastEventTs, setLastEventTs] = useState7(0);
@@ -1974,19 +1975,31 @@ function useHFState(token, namespace) {
1974
1975
  tags: { by_category: {}, all: [] },
1975
1976
  meta: { consolidatedAt: 0, version: 1 }
1976
1977
  };
1977
- const events = await loadPendingEvents(namespace, token, base.meta.consolidatedAt);
1978
- events.forEach((e) => knownEventPaths.current.add(`${e.ts}_${e.clientId}`));
1978
+ const hfEvents = await loadPendingEvents(namespace, token, base.meta.consolidatedAt);
1979
+ const hfEventKeys = new Set(hfEvents.map((e) => `${e.ts}_${e.clientId}`));
1980
+ const localOnlyEvents = allEventsRef.current.filter(
1981
+ (e) => !hfEventKeys.has(`${e.ts}_${e.clientId}`)
1982
+ );
1983
+ setLocalOnlyCount(localOnlyEvents.length);
1984
+ if (localOnlyEvents.length) {
1985
+ console.warn("[HF] loadFull: Behalte", localOnlyEvents.length, "lokale Events, die noch nicht auf HF sind");
1986
+ }
1987
+ hfEvents.forEach((e) => knownEventPaths.current.add(`${e.ts}_${e.clientId}`));
1979
1988
  allEventsRef.current = [];
1980
- const finalState = applyNewEvents(base, events);
1989
+ const finalState = applyNewEvents(base, [...hfEvents, ...localOnlyEvents]);
1981
1990
  setState(finalState);
1982
1991
  const buffer = readOfflineBuffer();
1983
1992
  if (buffer.length) {
1993
+ const failed = [];
1984
1994
  for (const evt of buffer) {
1985
- await writeHFEvent(namespace, token, evt.type, evt.payload, evt.prevTs).catch(() => {
1986
- });
1995
+ try {
1996
+ await writeHFEvent(namespace, token, evt.type, evt.payload, evt.prevTs);
1997
+ } catch {
1998
+ failed.push(evt);
1999
+ }
1987
2000
  }
1988
- writeOfflineBuffer([]);
1989
- setPendingBufferCount(0);
2001
+ writeOfflineBuffer(failed);
2002
+ setPendingBufferCount(failed.length);
1990
2003
  const freshEvents = await loadPendingEvents(namespace, token, base.meta.consolidatedAt);
1991
2004
  freshEvents.forEach((e) => knownEventPaths.current.add(`${e.ts}_${e.clientId}`));
1992
2005
  setState((prev) => prev ? applyNewEvents(base, freshEvents) : prev);
@@ -2004,6 +2017,9 @@ function useHFState(token, namespace) {
2004
2017
  const newEvents = events.filter((e) => !knownEventPaths.current.has(`${e.ts}_${e.clientId}`));
2005
2018
  if (!newEvents.length) return;
2006
2019
  newEvents.forEach((e) => knownEventPaths.current.add(`${e.ts}_${e.clientId}`));
2020
+ const confirmedKeys = new Set(events.map((e) => `${e.ts}_${e.clientId}`));
2021
+ const stillLocalOnly = allEventsRef.current.filter((e) => !confirmedKeys.has(`${e.ts}_${e.clientId}`));
2022
+ setLocalOnlyCount(stillLocalOnly.length);
2007
2023
  setState((prev) => prev ? applyNewEvents(prev, newEvents) : prev);
2008
2024
  } catch {
2009
2025
  }
@@ -2016,7 +2032,7 @@ function useHFState(token, namespace) {
2016
2032
  const id = setInterval(pollNew, POLL_INTERVAL_MS);
2017
2033
  return () => clearInterval(id);
2018
2034
  }, [token, namespace, pollNew]);
2019
- const writeEvent2 = useCallback(async (type, payload) => {
2035
+ const writeEvent = useCallback(async (type, payload) => {
2020
2036
  const prevTs = lastEventTs ? [lastEventTs] : [state?.meta.consolidatedAt ?? 0];
2021
2037
  console.log("[HF] writeEvent called:", { type, namespace, tokenOk: !!token, prevTs });
2022
2038
  await pollNew();
@@ -2025,6 +2041,9 @@ function useHFState(token, namespace) {
2025
2041
  const event = await writeHFEvent(namespace, token, type, payload, prevTs);
2026
2042
  console.log("[HF] writeHFEvent success:", event.ts);
2027
2043
  knownEventPaths.current.add(`${event.ts}_${event.clientId}`);
2044
+ const confirmedKey = `${event.ts}_${event.clientId}`;
2045
+ const stillLocalOnly = allEventsRef.current.filter((e) => `${e.ts}_${e.clientId}` !== confirmedKey && !knownEventPaths.current.has(`${e.ts}_${e.clientId}`));
2046
+ setLocalOnlyCount(stillLocalOnly.length);
2028
2047
  setState((prev) => prev ? applyNewEvents(prev, [event]) : prev);
2029
2048
  setLastEventTs(event.ts);
2030
2049
  await pollNew();
@@ -2042,6 +2061,7 @@ function useHFState(token, namespace) {
2042
2061
  writeOfflineBuffer([...buffer, offline]);
2043
2062
  setPendingBufferCount(buffer.length + 1);
2044
2063
  setState((prev) => prev ? applyNewEvents(prev, [offline]) : prev);
2064
+ setLocalOnlyCount((prev) => prev + 1);
2045
2065
  }
2046
2066
  }, [namespace, token, lastEventTs, state, pollNew, applyNewEvents]);
2047
2067
  return {
@@ -2049,9 +2069,11 @@ function useHFState(token, namespace) {
2049
2069
  isLoading,
2050
2070
  error,
2051
2071
  pendingBufferCount,
2072
+ localOnlyCount,
2073
+ confirmedEventKeys: knownEventPaths.current,
2052
2074
  eventCount,
2053
2075
  forks,
2054
- writeEvent: writeEvent2,
2076
+ writeEvent,
2055
2077
  refresh: loadFull,
2056
2078
  lastEventTs,
2057
2079
  allEvents: allEventsRef.current,
@@ -3306,7 +3328,7 @@ async function uploadViaCdnLib(token, path, bytes, mimeType) {
3306
3328
  }
3307
3329
  return [s];
3308
3330
  }
3309
- async function writeEvent(token, namespace) {
3331
+ async function writeTestEvent(token, namespace) {
3310
3332
  const ns = namespace.endsWith("/") ? namespace : namespace ? namespace + "/" : "";
3311
3333
  const ts = Date.now();
3312
3334
  const uuid = crypto.randomUUID().slice(0, 8);
@@ -3431,7 +3453,80 @@ function TestCard({
3431
3453
  ] })
3432
3454
  ] });
3433
3455
  }
3434
- function HFTestTab({ token, namespace, galleryItems }) {
3456
+ var EVENT_TYPE_COLORS = {
3457
+ image_added: "#60a5fa",
3458
+ tag_upserted: "#a78bfa",
3459
+ metadata_updated: "#34d399",
3460
+ probe: "#fbbf24"
3461
+ };
3462
+ function EventMonitor({ events, confirmedEventKeys, galleryItems, imageUploadStatus }) {
3463
+ if (!events.length) {
3464
+ return /* @__PURE__ */ jsx20("div", { style: { padding: "12px 14px", fontSize: 12, color: "rgba(255,255,255,0.3)", fontStyle: "italic" }, children: "Noch keine Events geladen." });
3465
+ }
3466
+ const sorted = [...events].sort((a, b) => b.ts - a.ts).slice(0, 30);
3467
+ return /* @__PURE__ */ jsxs18("div", { style: { padding: "6px 8px 4px" }, children: [
3468
+ sorted.map((e, i) => {
3469
+ const eKey = `${e.ts}_${e.clientId}`;
3470
+ const isConfirmed = confirmedEventKeys.has(eKey);
3471
+ const typeColor = EVENT_TYPE_COLORS[e.type] || "rgba(255,255,255,0.5)";
3472
+ const date = new Date(e.ts);
3473
+ const timeStr = date.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit", second: "2-digit" });
3474
+ const isImageEvent = e.type === "image_added";
3475
+ const imgId = isImageEvent ? e.payload?.id : void 0;
3476
+ const galleryItem = imgId ? galleryItems.find((g) => g.id === imgId) : void 0;
3477
+ const uploadStatus = imgId ? imageUploadStatus.get(imgId) : void 0;
3478
+ const payloadStr = JSON.stringify(e.payload ?? {});
3479
+ const payloadPreview = payloadStr.length > 70 ? payloadStr.slice(0, 70) + "\u2026" : payloadStr;
3480
+ return /* @__PURE__ */ jsxs18("div", { style: { display: "flex", gap: 7, alignItems: "flex-start", padding: "6px 2px", borderBottom: "1px solid rgba(255,255,255,0.05)" }, children: [
3481
+ /* @__PURE__ */ jsx20("div", { style: { width: 36, height: 36, flexShrink: 0, borderRadius: 4, overflow: "hidden", background: "rgba(255,255,255,0.05)", display: "flex", alignItems: "center", justifyContent: "center" }, children: isImageEvent ? galleryItem?.base64 ? /* @__PURE__ */ jsx20("img", { src: galleryItem.base64, style: { width: "100%", height: "100%", objectFit: "cover" } }) : /* @__PURE__ */ jsx20("span", { className: "material-symbols-outlined", style: { fontSize: 18, color: "rgba(255,255,255,0.2)" }, children: "image" }) : /* @__PURE__ */ jsx20("span", { className: "material-symbols-outlined", style: { fontSize: 16, color: "rgba(255,255,255,0.15)" }, children: e.type === "tag_upserted" ? "label" : e.type === "metadata_updated" ? "edit_note" : "data_object" }) }),
3482
+ /* @__PURE__ */ jsxs18("div", { style: { flex: 1, minWidth: 0 }, children: [
3483
+ /* @__PURE__ */ jsxs18("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 3 }, children: [
3484
+ /* @__PURE__ */ jsx20("span", { style: { fontSize: 11, fontWeight: 700, color: typeColor }, children: e.type }),
3485
+ /* @__PURE__ */ jsx20("span", { style: { fontSize: 9, color: "rgba(255,255,255,0.25)", fontVariantNumeric: "tabular-nums" }, children: timeStr }),
3486
+ /* @__PURE__ */ jsx20("div", { style: { flex: 1 } }),
3487
+ isConfirmed ? /* @__PURE__ */ jsxs18("span", { style: { fontSize: 9, fontWeight: 700, color: "#4ade80", display: "flex", alignItems: "center", gap: 2 }, children: [
3488
+ /* @__PURE__ */ jsx20("span", { className: "material-symbols-outlined", style: { fontSize: 11 }, children: "check_circle" }),
3489
+ "HF"
3490
+ ] }) : /* @__PURE__ */ jsxs18("span", { style: { fontSize: 9, fontWeight: 700, color: "#fbbf24", display: "flex", alignItems: "center", gap: 2 }, children: [
3491
+ /* @__PURE__ */ jsx20("span", { className: "material-symbols-outlined", style: { fontSize: 11 }, children: "schedule" }),
3492
+ "lokal"
3493
+ ] })
3494
+ ] }),
3495
+ isImageEvent && /* @__PURE__ */ jsxs18("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 3 }, children: [
3496
+ uploadStatus === "done" && /* @__PURE__ */ jsxs18("span", { style: { fontSize: 9, color: "#4ade80", display: "flex", alignItems: "center", gap: 2 }, children: [
3497
+ /* @__PURE__ */ jsx20("span", { className: "material-symbols-outlined", style: { fontSize: 11 }, children: "cloud_done" }),
3498
+ "Bild auf HF"
3499
+ ] }),
3500
+ uploadStatus === "uploading" && /* @__PURE__ */ jsxs18("span", { style: { fontSize: 9, color: "#60a5fa", display: "flex", alignItems: "center", gap: 2 }, children: [
3501
+ /* @__PURE__ */ jsx20("span", { className: "material-symbols-outlined", style: { fontSize: 11 }, children: "cloud_upload" }),
3502
+ "Bild l\xE4dt\u2026"
3503
+ ] }),
3504
+ uploadStatus === "failed" && /* @__PURE__ */ jsxs18("span", { style: { fontSize: 9, color: "#f87171", display: "flex", alignItems: "center", gap: 2 }, children: [
3505
+ /* @__PURE__ */ jsx20("span", { className: "material-symbols-outlined", style: { fontSize: 11 }, children: "cloud_off" }),
3506
+ "Bild-Upload fehlgeschlagen"
3507
+ ] }),
3508
+ !uploadStatus && /* @__PURE__ */ jsx20("span", { style: { fontSize: 9, color: "rgba(255,255,255,0.2)" }, children: "Bild-Upload unbekannt (anderes Ger\xE4t?)" }),
3509
+ galleryItem?.base64 ? /* @__PURE__ */ jsxs18("span", { style: { fontSize: 9, color: "#4ade80", marginLeft: 6, display: "flex", alignItems: "center", gap: 2 }, children: [
3510
+ /* @__PURE__ */ jsx20("span", { className: "material-symbols-outlined", style: { fontSize: 11 }, children: "photo" }),
3511
+ "lokal vorhanden"
3512
+ ] }) : /* @__PURE__ */ jsxs18("span", { style: { fontSize: 9, color: "#f87171", marginLeft: 6, display: "flex", alignItems: "center", gap: 2 }, children: [
3513
+ /* @__PURE__ */ jsx20("span", { className: "material-symbols-outlined", style: { fontSize: 11 }, children: "broken_image" }),
3514
+ "kein lokales Bild"
3515
+ ] })
3516
+ ] }),
3517
+ /* @__PURE__ */ jsx20("div", { style: { fontSize: 9, color: "rgba(255,255,255,0.25)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: payloadPreview })
3518
+ ] })
3519
+ ] }, `${eKey}_${i}`);
3520
+ }),
3521
+ /* @__PURE__ */ jsxs18("div", { style: { padding: "6px 0 2px", fontSize: 9, color: "rgba(255,255,255,0.2)", textAlign: "right" }, children: [
3522
+ events.length,
3523
+ " Events gesamt \xB7 ",
3524
+ [...confirmedEventKeys].length,
3525
+ " auf HF best\xE4tigt"
3526
+ ] })
3527
+ ] });
3528
+ }
3529
+ function HFTestTab({ token, namespace, galleryItems, allEvents = [], confirmedEventKeys = /* @__PURE__ */ new Set(), imageUploadStatus = /* @__PURE__ */ new Map() }) {
3435
3530
  const [selected, setSelected] = useState15(null);
3436
3531
  const [results, setResults] = useState15({});
3437
3532
  const [expanded, setExpanded] = useState15({});
@@ -3467,7 +3562,7 @@ function HFTestTab({ token, namespace, galleryItems }) {
3467
3562
  break;
3468
3563
  }
3469
3564
  } else {
3470
- steps = await writeEvent(token, namespace);
3565
+ steps = await writeTestEvent(token, namespace);
3471
3566
  }
3472
3567
  } catch (e) {
3473
3568
  steps = [{ label: "Unexpected error", method: "-", url: "-", reqHeaders: {}, error: String(e?.message ?? e), ok: false }];
@@ -3484,96 +3579,123 @@ function HFTestTab({ token, namespace, galleryItems }) {
3484
3579
  ];
3485
3580
  return /* @__PURE__ */ jsxs18("div", { style: { display: "flex", flexDirection: "column", height: "100%", overflowY: "auto", padding: "12px 10px 80px", boxSizing: "border-box", fontFamily: "inherit" }, children: [
3486
3581
  noToken && /* @__PURE__ */ jsx20("div", { style: { marginBottom: 10, padding: "8px 12px", background: "rgba(248,113,113,0.08)", borderRadius: 8, border: "1px solid rgba(248,113,113,0.2)", fontSize: 12, color: "#f87171" }, children: "Kein HF-Token geladen \u2014 bitte zuerst Token im Sync-Tab eingeben." }),
3487
- /* @__PURE__ */ jsxs18("div", { style: { marginBottom: 14 }, children: [
3488
- /* @__PURE__ */ jsxs18("div", { style: { fontSize: 10, fontWeight: 700, color: "rgba(255,255,255,0.3)", textTransform: "uppercase", letterSpacing: "0.08em", marginBottom: 8 }, children: [
3489
- "Bild ausw\xE4hlen (",
3490
- withResults.length,
3491
- " verf\xFCgbar)"
3492
- ] }),
3493
- withResults.length === 0 ? /* @__PURE__ */ jsx20("div", { style: { fontSize: 12, color: "rgba(255,255,255,0.3)", fontStyle: "italic" }, children: "Noch keine Bilder in der Galerie. Generiere zuerst ein Bild oder lade von HF." }) : /* @__PURE__ */ jsx20("div", { style: { display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 6 }, children: withResults.slice(0, 12).map((g) => /* @__PURE__ */ jsx20(
3494
- "button",
3495
- {
3496
- onClick: () => setSelected(g),
3497
- style: { padding: 0, border: `2px solid ${selected?.id === g.id ? "#0284c7" : "transparent"}`, borderRadius: 6, cursor: "pointer", overflow: "hidden", background: "none", lineHeight: 0 },
3498
- children: /* @__PURE__ */ jsx20(
3499
- "img",
3500
- {
3501
- src: g.base64,
3502
- alt: g.prompt || g.id,
3503
- style: { width: "100%", aspectRatio: "1", objectFit: "cover", display: "block", borderRadius: 4 }
3504
- }
3505
- )
3506
- },
3507
- g.id
3508
- )) }),
3509
- selected && /* @__PURE__ */ jsxs18("div", { style: { marginTop: 10, display: "flex", gap: 10, alignItems: "flex-start" }, children: [
3510
- /* @__PURE__ */ jsx20(
3511
- "img",
3582
+ /* @__PURE__ */ jsx20("div", { style: { marginBottom: 12 }, children: /* @__PURE__ */ jsx20(
3583
+ CollapsibleCard,
3584
+ {
3585
+ title: "Event Monitor",
3586
+ icon: /* @__PURE__ */ jsx20("span", { className: "material-symbols-outlined", style: { fontSize: 16 }, children: "bolt" }),
3587
+ defaultOpen: true,
3588
+ children: /* @__PURE__ */ jsx20(
3589
+ EventMonitor,
3512
3590
  {
3513
- src: selected.base64,
3514
- style: { width: 80, height: 80, objectFit: "cover", borderRadius: 8, border: "1px solid rgba(255,255,255,0.1)", flexShrink: 0 }
3591
+ events: allEvents,
3592
+ confirmedEventKeys,
3593
+ galleryItems,
3594
+ imageUploadStatus
3515
3595
  }
3516
- ),
3517
- /* @__PURE__ */ jsxs18("div", { style: { flex: 1, minWidth: 0 }, children: [
3518
- /* @__PURE__ */ jsx20("div", { style: { fontSize: 11, fontWeight: 700, color: "rgba(255,255,255,0.7)", marginBottom: 2 }, children: "Ausgew\xE4hlt" }),
3519
- /* @__PURE__ */ jsxs18("div", { style: { fontSize: 10, color: "rgba(255,255,255,0.3)", wordBreak: "break-all" }, children: [
3520
- "ID: ",
3521
- selected.id
3596
+ )
3597
+ }
3598
+ ) }),
3599
+ /* @__PURE__ */ jsx20(
3600
+ CollapsibleCard,
3601
+ {
3602
+ title: "Upload Tests",
3603
+ icon: /* @__PURE__ */ jsx20("span", { className: "material-symbols-outlined", style: { fontSize: 16 }, children: "science" }),
3604
+ defaultOpen: false,
3605
+ children: /* @__PURE__ */ jsxs18("div", { style: { padding: "10px 10px 4px" }, children: [
3606
+ /* @__PURE__ */ jsxs18("div", { style: { marginBottom: 14 }, children: [
3607
+ /* @__PURE__ */ jsxs18("div", { style: { fontSize: 10, fontWeight: 700, color: "rgba(255,255,255,0.3)", textTransform: "uppercase", letterSpacing: "0.08em", marginBottom: 8 }, children: [
3608
+ "Bild ausw\xE4hlen (",
3609
+ withResults.length,
3610
+ " verf\xFCgbar)"
3611
+ ] }),
3612
+ withResults.length === 0 ? /* @__PURE__ */ jsx20("div", { style: { fontSize: 12, color: "rgba(255,255,255,0.3)", fontStyle: "italic" }, children: "Noch keine Bilder in der Galerie. Generiere zuerst ein Bild oder lade von HF." }) : /* @__PURE__ */ jsx20("div", { style: { display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 6 }, children: withResults.slice(0, 12).map((g) => /* @__PURE__ */ jsx20(
3613
+ "button",
3614
+ {
3615
+ onClick: () => setSelected(g),
3616
+ style: { padding: 0, border: `2px solid ${selected?.id === g.id ? "#0284c7" : "transparent"}`, borderRadius: 6, cursor: "pointer", overflow: "hidden", background: "none", lineHeight: 0 },
3617
+ children: /* @__PURE__ */ jsx20(
3618
+ "img",
3619
+ {
3620
+ src: g.base64,
3621
+ alt: g.prompt || g.id,
3622
+ style: { width: "100%", aspectRatio: "1", objectFit: "cover", display: "block", borderRadius: 4 }
3623
+ }
3624
+ )
3625
+ },
3626
+ g.id
3627
+ )) }),
3628
+ selected && /* @__PURE__ */ jsxs18("div", { style: { marginTop: 10, display: "flex", gap: 10, alignItems: "flex-start" }, children: [
3629
+ /* @__PURE__ */ jsx20(
3630
+ "img",
3631
+ {
3632
+ src: selected.base64,
3633
+ style: { width: 80, height: 80, objectFit: "cover", borderRadius: 8, border: "1px solid rgba(255,255,255,0.1)", flexShrink: 0 }
3634
+ }
3635
+ ),
3636
+ /* @__PURE__ */ jsxs18("div", { style: { flex: 1, minWidth: 0 }, children: [
3637
+ /* @__PURE__ */ jsx20("div", { style: { fontSize: 11, fontWeight: 700, color: "rgba(255,255,255,0.7)", marginBottom: 2 }, children: "Ausgew\xE4hlt" }),
3638
+ /* @__PURE__ */ jsxs18("div", { style: { fontSize: 10, color: "rgba(255,255,255,0.3)", wordBreak: "break-all" }, children: [
3639
+ "ID: ",
3640
+ selected.id
3641
+ ] }),
3642
+ /* @__PURE__ */ jsxs18("div", { style: { fontSize: 10, color: "rgba(255,255,255,0.3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", marginTop: 2 }, children: [
3643
+ "Ziel: test/",
3644
+ selected.id,
3645
+ ".jpg"
3646
+ ] }),
3647
+ selected.prompt && /* @__PURE__ */ jsx20("div", { style: { fontSize: 10, color: "rgba(255,255,255,0.25)", marginTop: 2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: selected.prompt })
3648
+ ] })
3649
+ ] })
3522
3650
  ] }),
3523
- /* @__PURE__ */ jsxs18("div", { style: { fontSize: 10, color: "rgba(255,255,255,0.3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", marginTop: 2 }, children: [
3524
- "Ziel: test/",
3525
- selected.id,
3526
- ".jpg"
3651
+ /* @__PURE__ */ jsxs18("div", { style: { marginBottom: 14 }, children: [
3652
+ /* @__PURE__ */ jsxs18("div", { style: { fontSize: 10, fontWeight: 700, color: "rgba(255,255,255,0.3)", textTransform: "uppercase", letterSpacing: "0.1em", marginBottom: 8, borderBottom: "1px solid rgba(255,255,255,0.06)", paddingBottom: 4 }, children: [
3653
+ "Bild hochladen \u2192 test/",
3654
+ "{",
3655
+ "id",
3656
+ "}",
3657
+ ".jpg"
3658
+ ] }),
3659
+ imgTests.map((t) => /* @__PURE__ */ jsx20(
3660
+ TestCard,
3661
+ {
3662
+ id: t.id,
3663
+ label: t.label,
3664
+ icon: t.icon,
3665
+ desc: t.desc,
3666
+ disabled: noToken || noImg || results[t.id]?.status === "running",
3667
+ state: results[t.id],
3668
+ onRun: () => run(t.id),
3669
+ expanded: !!expanded[t.id],
3670
+ onToggle: () => setExpanded((e) => ({ ...e, [t.id]: !e[t.id] }))
3671
+ },
3672
+ t.id
3673
+ ))
3527
3674
  ] }),
3528
- selected.prompt && /* @__PURE__ */ jsx20("div", { style: { fontSize: 10, color: "rgba(255,255,255,0.25)", marginTop: 2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: selected.prompt })
3675
+ /* @__PURE__ */ jsxs18("div", { style: { marginBottom: 4 }, children: [
3676
+ /* @__PURE__ */ jsxs18("div", { style: { fontSize: 10, fontWeight: 700, color: "rgba(255,255,255,0.3)", textTransform: "uppercase", letterSpacing: "0.1em", marginBottom: 8, borderBottom: "1px solid rgba(255,255,255,0.06)", paddingBottom: 4 }, children: [
3677
+ "Event schreiben \u2192 ",
3678
+ namespace || "(kein namespace)",
3679
+ "test/events/"
3680
+ ] }),
3681
+ /* @__PURE__ */ jsx20(
3682
+ TestCard,
3683
+ {
3684
+ id: "event",
3685
+ label: "Write Event",
3686
+ icon: "bolt",
3687
+ desc: "Kleines JSON-Event hochladen (direct commit)",
3688
+ disabled: noToken || results["event"]?.status === "running",
3689
+ state: results["event"],
3690
+ onRun: () => run("event"),
3691
+ expanded: !!expanded["event"],
3692
+ onToggle: () => setExpanded((e) => ({ ...e, event: !e.event }))
3693
+ }
3694
+ )
3695
+ ] })
3529
3696
  ] })
3530
- ] })
3531
- ] }),
3532
- /* @__PURE__ */ jsxs18("div", { style: { marginBottom: 18 }, children: [
3533
- /* @__PURE__ */ jsxs18("div", { style: { fontSize: 10, fontWeight: 700, color: "rgba(255,255,255,0.3)", textTransform: "uppercase", letterSpacing: "0.1em", marginBottom: 8, borderBottom: "1px solid rgba(255,255,255,0.06)", paddingBottom: 4 }, children: [
3534
- "Bild hochladen \u2192 test/",
3535
- "{",
3536
- "id",
3537
- "}",
3538
- ".jpg"
3539
- ] }),
3540
- imgTests.map((t) => /* @__PURE__ */ jsx20(
3541
- TestCard,
3542
- {
3543
- id: t.id,
3544
- label: t.label,
3545
- icon: t.icon,
3546
- desc: t.desc,
3547
- disabled: noToken || noImg || results[t.id]?.status === "running",
3548
- state: results[t.id],
3549
- onRun: () => run(t.id),
3550
- expanded: !!expanded[t.id],
3551
- onToggle: () => setExpanded((e) => ({ ...e, [t.id]: !e[t.id] }))
3552
- },
3553
- t.id
3554
- ))
3555
- ] }),
3556
- /* @__PURE__ */ jsxs18("div", { children: [
3557
- /* @__PURE__ */ jsxs18("div", { style: { fontSize: 10, fontWeight: 700, color: "rgba(255,255,255,0.3)", textTransform: "uppercase", letterSpacing: "0.1em", marginBottom: 8, borderBottom: "1px solid rgba(255,255,255,0.06)", paddingBottom: 4 }, children: [
3558
- "Event schreiben \u2192 ",
3559
- namespace || "(kein namespace)",
3560
- "test/events/"
3561
- ] }),
3562
- /* @__PURE__ */ jsx20(
3563
- TestCard,
3564
- {
3565
- id: "event",
3566
- label: "Write Event",
3567
- icon: "bolt",
3568
- desc: "Kleines JSON-Event hochladen (direct commit)",
3569
- disabled: noToken || results["event"]?.status === "running",
3570
- state: results["event"],
3571
- onRun: () => run("event"),
3572
- expanded: !!expanded["event"],
3573
- onToggle: () => setExpanded((e) => ({ ...e, event: !e.event }))
3574
- }
3575
- )
3576
- ] })
3697
+ }
3698
+ )
3577
3699
  ] });
3578
3700
  }
3579
3701
 
@@ -3625,14 +3747,25 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3625
3747
  state: hfState,
3626
3748
  isLoading: isHfRefreshing,
3627
3749
  pendingBufferCount,
3750
+ localOnlyCount,
3628
3751
  eventCount,
3752
+ allEvents: hfAllEvents,
3753
+ confirmedEventKeys: hfConfirmedKeys,
3629
3754
  writeEvent: hfWriteEvent,
3630
3755
  refresh: refreshHF,
3631
3756
  hasStateZip
3632
3757
  } = useHFState(hfToken, effectiveNamespace);
3758
+ const [imageUploadStatus, setImageUploadStatus] = useState16(/* @__PURE__ */ new Map());
3633
3759
  const [bootstrapLog, setBootstrapLog] = useState16([]);
3634
3760
  const [isBootstrapping, setIsBootstrapping] = useState16(false);
3635
3761
  const syncTopSlot = /* @__PURE__ */ jsxs19(Fragment9, { children: [
3762
+ localOnlyCount > 0 && /* @__PURE__ */ jsxs19("div", { style: { background: "rgba(234,179,8,0.15)", border: "1px solid rgba(234,179,8,0.3)", padding: "4px 10px", fontSize: 11, color: "#fbbf24", borderRadius: 4, marginBottom: 4 }, children: [
3763
+ "\u26A0 ",
3764
+ localOnlyCount,
3765
+ " lokale Event",
3766
+ localOnlyCount > 1 ? "s" : "",
3767
+ " noch nicht auf HF best\xE4tigt"
3768
+ ] }),
3636
3769
  pendingBufferCount > 0 && /* @__PURE__ */ jsxs19("div", { style: { background: "linear-gradient(90deg,#f59e0b,#ef4444)", padding: "4px 10px", fontSize: 11, color: "#fff", borderRadius: 4, marginBottom: 4 }, children: [
3637
3770
  pendingBufferCount,
3638
3771
  " \xC4nderung",
@@ -3691,7 +3824,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3691
3824
  useEffect6(() => {
3692
3825
  galleryItemsRef.current = galleryItems;
3693
3826
  }, [galleryItems]);
3694
- const hfImageNotFoundRef = useRef7(/* @__PURE__ */ new Set());
3827
+ const hfImageNotFoundRef = useRef7(/* @__PURE__ */ new Map());
3695
3828
  useEffect6(() => {
3696
3829
  if (!hfState) return;
3697
3830
  if (hfState.tags?.by_category) setWorkspaceTags(hfState.tags);
@@ -3716,18 +3849,21 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3716
3849
  const merged = skeletons.map((s) => prev.find((g) => g.id === s.id) ?? s);
3717
3850
  return [...localOnly, ...merged];
3718
3851
  });
3852
+ const COOLDOWN_MS = 5 * 60 * 1e3;
3719
3853
  for (const entry of hfState.metadata) {
3720
- if (hfImageNotFoundRef.current.has(entry.id)) continue;
3854
+ if (galleryItemsRef.current.find((g) => g.id === entry.id)?.base64) continue;
3855
+ const failedAt = hfImageNotFoundRef.current.get(entry.id);
3856
+ if (failedAt && Date.now() - failedAt < COOLDOWN_MS) continue;
3721
3857
  hfLoadImageAsBase64(entry.id, hfToken).then((b64) => {
3722
3858
  if (!b64) {
3723
- hfImageNotFoundRef.current.add(entry.id);
3859
+ hfImageNotFoundRef.current.set(entry.id, Date.now());
3724
3860
  return;
3725
3861
  }
3726
3862
  const prefix = `data:${entry.mimeType || "image/jpeg"};base64,`;
3727
3863
  setGalleryItems((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
3728
3864
  setHistory((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
3729
3865
  }).catch(() => {
3730
- hfImageNotFoundRef.current.add(entry.id);
3866
+ hfImageNotFoundRef.current.set(entry.id, Date.now());
3731
3867
  });
3732
3868
  }
3733
3869
  }, [hfState]);
@@ -3981,8 +4117,10 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3981
4117
  });
3982
4118
  console.log("[HF] handleGenerateImage \u2014 condition check:", { hfToken: !!hfToken, base64: !!base64, effectiveNamespace });
3983
4119
  if (hfToken && base64 && effectiveNamespace) {
3984
- hfUploadImage(base64, genId, hfToken).catch((e) => {
4120
+ setImageUploadStatus((m) => new Map(m).set(genId, "uploading"));
4121
+ hfUploadImage(base64, genId, hfToken).then(() => setImageUploadStatus((m) => new Map(m).set(genId, "done"))).catch((e) => {
3985
4122
  console.error("[HF] hfUploadImage failed:", e);
4123
+ setImageUploadStatus((m) => new Map(m).set(genId, "failed"));
3986
4124
  });
3987
4125
  const entry = {
3988
4126
  id: genId,
@@ -4809,7 +4947,17 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4809
4947
  onTagMove: handleTagMove
4810
4948
  }
4811
4949
  ),
4812
- activeTab === "hftest" && /* @__PURE__ */ jsx21("div", { className: "absolute inset-0", children: /* @__PURE__ */ jsx21(HFTestTab, { token: hfToken, namespace: effectiveNamespace, galleryItems }) })
4950
+ activeTab === "hftest" && /* @__PURE__ */ jsx21("div", { className: "absolute inset-0", children: /* @__PURE__ */ jsx21(
4951
+ HFTestTab,
4952
+ {
4953
+ token: hfToken,
4954
+ namespace: effectiveNamespace,
4955
+ galleryItems,
4956
+ allEvents: hfAllEvents,
4957
+ confirmedEventKeys: hfConfirmedKeys,
4958
+ imageUploadStatus
4959
+ }
4960
+ ) })
4813
4961
  ] })
4814
4962
  ] }),
4815
4963
  /* @__PURE__ */ jsx21("div", { className: "flex border-t border-white/10 bg-black shrink-0", style: { height: 56, paddingBottom: "env(safe-area-inset-bottom, 0px)" }, children: [
@@ -5189,7 +5337,7 @@ function FaApp({
5189
5337
  }
5190
5338
 
5191
5339
  // src/index.ts
5192
- var LIB_VERSION = "2.0.17";
5340
+ var LIB_VERSION = "2.0.19";
5193
5341
  export {
5194
5342
  AvatarArchitectApp,
5195
5343
  CollapsibleCard,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rslsp1/fa-app-tools",
3
- "version": "2.0.17",
3
+ "version": "2.0.19",
4
4
  "description": "Shared tools and hooks for Fine Art flow apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",