@rslsp1/fa-app-tools 2.0.16 → 2.0.18

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,7 @@ interface HFStateResult {
583
583
  error: string | null;
584
584
  pendingBufferCount: number;
585
585
  eventCount: number;
586
+ localOnlyCount: number;
586
587
  forks: Array<{
587
588
  parentTs: number;
588
589
  childTs: number[];
@@ -612,6 +613,6 @@ declare function findTips(dag: Dag): number[];
612
613
  declare function findForks(dag: Dag): DagFork[];
613
614
  declare function topoSort(events: HFEvent[]): HFEvent[];
614
615
 
615
- declare const LIB_VERSION = "2.0.16";
616
+ declare const LIB_VERSION = "2.0.18";
616
617
 
617
618
  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,7 @@ interface HFStateResult {
583
583
  error: string | null;
584
584
  pendingBufferCount: number;
585
585
  eventCount: number;
586
+ localOnlyCount: number;
586
587
  forks: Array<{
587
588
  parentTs: number;
588
589
  childTs: number[];
@@ -612,6 +613,6 @@ declare function findTips(dag: Dag): number[];
612
613
  declare function findForks(dag: Dag): DagFork[];
613
614
  declare function topoSort(events: HFEvent[]): HFEvent[];
614
615
 
615
- declare const LIB_VERSION = "2.0.16";
616
+ declare const LIB_VERSION = "2.0.18";
616
617
 
617
618
  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,10 @@ function useHFState(token, namespace) {
2717
2737
  isLoading,
2718
2738
  error,
2719
2739
  pendingBufferCount,
2740
+ localOnlyCount,
2720
2741
  eventCount,
2721
2742
  forks,
2722
- writeEvent: writeEvent2,
2743
+ writeEvent,
2723
2744
  refresh: loadFull,
2724
2745
  lastEventTs,
2725
2746
  allEvents: allEventsRef.current,
@@ -3974,7 +3995,7 @@ async function uploadViaCdnLib(token, path, bytes, mimeType) {
3974
3995
  }
3975
3996
  return [s];
3976
3997
  }
3977
- async function writeEvent(token, namespace) {
3998
+ async function writeTestEvent(token, namespace) {
3978
3999
  const ns = namespace.endsWith("/") ? namespace : namespace ? namespace + "/" : "";
3979
4000
  const ts = Date.now();
3980
4001
  const uuid = crypto.randomUUID().slice(0, 8);
@@ -4099,7 +4120,39 @@ function TestCard({
4099
4120
  ] })
4100
4121
  ] });
4101
4122
  }
4102
- function HFTestTab({ token, namespace, galleryItems }) {
4123
+ var EVENT_TYPE_COLORS = {
4124
+ image_added: "#60a5fa",
4125
+ tag_upserted: "#a78bfa",
4126
+ metadata_updated: "#34d399",
4127
+ probe: "#fbbf24"
4128
+ };
4129
+ function EventMonitor({ events }) {
4130
+ if (!events.length) {
4131
+ 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." });
4132
+ }
4133
+ const sorted = [...events].sort((a, b) => b.ts - a.ts).slice(0, 30);
4134
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { padding: "8px 10px 4px" }, children: [
4135
+ sorted.map((e, i) => {
4136
+ const color = EVENT_TYPE_COLORS[e.type] || "rgba(255,255,255,0.5)";
4137
+ const date = new Date(e.ts);
4138
+ const timeStr = date.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit", second: "2-digit" });
4139
+ const clientShort = e.clientId?.slice(0, 8) ?? "?";
4140
+ const payloadStr = JSON.stringify(e.payload ?? {});
4141
+ const payloadPreview = payloadStr.length > 80 ? payloadStr.slice(0, 80) + "\u2026" : payloadStr;
4142
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "flex-start", padding: "5px 0", borderBottom: "1px solid rgba(255,255,255,0.04)" }, children: [
4143
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { style: { fontSize: 9, color: "rgba(255,255,255,0.25)", minWidth: 60, paddingTop: 2, fontVariantNumeric: "tabular-nums" }, children: timeStr }),
4144
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { style: { fontSize: 11, fontWeight: 700, color, minWidth: 120, flexShrink: 0 }, children: e.type }),
4145
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { style: { fontSize: 9, color: "rgba(255,255,255,0.2)", minWidth: 65, flexShrink: 0, fontFamily: "monospace" }, children: clientShort }),
4146
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { style: { fontSize: 9, color: "rgba(255,255,255,0.35)", flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: payloadPreview })
4147
+ ] }, `${e.ts}_${e.clientId}_${i}`);
4148
+ }),
4149
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { padding: "6px 0 2px", fontSize: 9, color: "rgba(255,255,255,0.2)", textAlign: "right" }, children: [
4150
+ events.length,
4151
+ " Events gesamt"
4152
+ ] })
4153
+ ] });
4154
+ }
4155
+ function HFTestTab({ token, namespace, galleryItems, allEvents = [] }) {
4103
4156
  const [selected, setSelected] = (0, import_react22.useState)(null);
4104
4157
  const [results, setResults] = (0, import_react22.useState)({});
4105
4158
  const [expanded, setExpanded] = (0, import_react22.useState)({});
@@ -4135,7 +4188,7 @@ function HFTestTab({ token, namespace, galleryItems }) {
4135
4188
  break;
4136
4189
  }
4137
4190
  } else {
4138
- steps = await writeEvent(token, namespace);
4191
+ steps = await writeTestEvent(token, namespace);
4139
4192
  }
4140
4193
  } catch (e) {
4141
4194
  steps = [{ label: "Unexpected error", method: "-", url: "-", reqHeaders: {}, error: String(e?.message ?? e), ok: false }];
@@ -4152,96 +4205,115 @@ function HFTestTab({ token, namespace, galleryItems }) {
4152
4205
  ];
4153
4206
  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
4207
  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",
4180
- {
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 }
4183
- }
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
4208
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { marginBottom: 12 }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4209
+ CollapsibleCard,
4210
+ {
4211
+ title: "Event Monitor",
4212
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "material-symbols-outlined", style: { fontSize: 16 }, children: "bolt" }),
4213
+ defaultOpen: true,
4214
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(EventMonitor, { events: allEvents })
4215
+ }
4216
+ ) }),
4217
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4218
+ CollapsibleCard,
4219
+ {
4220
+ title: "Upload Tests",
4221
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "material-symbols-outlined", style: { fontSize: 16 }, children: "science" }),
4222
+ defaultOpen: false,
4223
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { padding: "10px 10px 4px" }, children: [
4224
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { marginBottom: 14 }, 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.08em", marginBottom: 8 }, children: [
4226
+ "Bild ausw\xE4hlen (",
4227
+ withResults.length,
4228
+ " verf\xFCgbar)"
4229
+ ] }),
4230
+ 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)(
4231
+ "button",
4232
+ {
4233
+ onClick: () => setSelected(g),
4234
+ style: { padding: 0, border: `2px solid ${selected?.id === g.id ? "#0284c7" : "transparent"}`, borderRadius: 6, cursor: "pointer", overflow: "hidden", background: "none", lineHeight: 0 },
4235
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4236
+ "img",
4237
+ {
4238
+ src: g.base64,
4239
+ alt: g.prompt || g.id,
4240
+ style: { width: "100%", aspectRatio: "1", objectFit: "cover", display: "block", borderRadius: 4 }
4241
+ }
4242
+ )
4243
+ },
4244
+ g.id
4245
+ )) }),
4246
+ selected && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { marginTop: 10, display: "flex", gap: 10, alignItems: "flex-start" }, children: [
4247
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4248
+ "img",
4249
+ {
4250
+ src: selected.base64,
4251
+ style: { width: 80, height: 80, objectFit: "cover", borderRadius: 8, border: "1px solid rgba(255,255,255,0.1)", flexShrink: 0 }
4252
+ }
4253
+ ),
4254
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
4255
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { fontSize: 11, fontWeight: 700, color: "rgba(255,255,255,0.7)", marginBottom: 2 }, children: "Ausgew\xE4hlt" }),
4256
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { fontSize: 10, color: "rgba(255,255,255,0.3)", wordBreak: "break-all" }, children: [
4257
+ "ID: ",
4258
+ selected.id
4259
+ ] }),
4260
+ /* @__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: [
4261
+ "Ziel: test/",
4262
+ selected.id,
4263
+ ".jpg"
4264
+ ] }),
4265
+ 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 })
4266
+ ] })
4267
+ ] })
4190
4268
  ] }),
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"
4269
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { marginBottom: 14 }, children: [
4270
+ /* @__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: [
4271
+ "Bild hochladen \u2192 test/",
4272
+ "{",
4273
+ "id",
4274
+ "}",
4275
+ ".jpg"
4276
+ ] }),
4277
+ imgTests.map((t) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4278
+ TestCard,
4279
+ {
4280
+ id: t.id,
4281
+ label: t.label,
4282
+ icon: t.icon,
4283
+ desc: t.desc,
4284
+ disabled: noToken || noImg || results[t.id]?.status === "running",
4285
+ state: results[t.id],
4286
+ onRun: () => run(t.id),
4287
+ expanded: !!expanded[t.id],
4288
+ onToggle: () => setExpanded((e) => ({ ...e, [t.id]: !e[t.id] }))
4289
+ },
4290
+ t.id
4291
+ ))
4195
4292
  ] }),
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 })
4293
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { marginBottom: 4 }, children: [
4294
+ /* @__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: [
4295
+ "Event schreiben \u2192 ",
4296
+ namespace || "(kein namespace)",
4297
+ "test/events/"
4298
+ ] }),
4299
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4300
+ TestCard,
4301
+ {
4302
+ id: "event",
4303
+ label: "Write Event",
4304
+ icon: "bolt",
4305
+ desc: "Kleines JSON-Event hochladen (direct commit)",
4306
+ disabled: noToken || results["event"]?.status === "running",
4307
+ state: results["event"],
4308
+ onRun: () => run("event"),
4309
+ expanded: !!expanded["event"],
4310
+ onToggle: () => setExpanded((e) => ({ ...e, event: !e.event }))
4311
+ }
4312
+ )
4313
+ ] })
4197
4314
  ] })
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
- ] })
4315
+ }
4316
+ )
4245
4317
  ] });
4246
4318
  }
4247
4319
 
@@ -4293,7 +4365,9 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4293
4365
  state: hfState,
4294
4366
  isLoading: isHfRefreshing,
4295
4367
  pendingBufferCount,
4368
+ localOnlyCount,
4296
4369
  eventCount,
4370
+ allEvents: hfAllEvents,
4297
4371
  writeEvent: hfWriteEvent,
4298
4372
  refresh: refreshHF,
4299
4373
  hasStateZip
@@ -4301,6 +4375,13 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4301
4375
  const [bootstrapLog, setBootstrapLog] = (0, import_react23.useState)([]);
4302
4376
  const [isBootstrapping, setIsBootstrapping] = (0, import_react23.useState)(false);
4303
4377
  const syncTopSlot = /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
4378
+ 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: [
4379
+ "\u26A0 ",
4380
+ localOnlyCount,
4381
+ " lokale Event",
4382
+ localOnlyCount > 1 ? "s" : "",
4383
+ " noch nicht auf HF best\xE4tigt"
4384
+ ] }),
4304
4385
  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
4386
  pendingBufferCount,
4306
4387
  " \xC4nderung",
@@ -4359,7 +4440,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4359
4440
  (0, import_react23.useEffect)(() => {
4360
4441
  galleryItemsRef.current = galleryItems;
4361
4442
  }, [galleryItems]);
4362
- const hfImageNotFoundRef = (0, import_react23.useRef)(/* @__PURE__ */ new Set());
4443
+ const hfImageNotFoundRef = (0, import_react23.useRef)(/* @__PURE__ */ new Map());
4363
4444
  (0, import_react23.useEffect)(() => {
4364
4445
  if (!hfState) return;
4365
4446
  if (hfState.tags?.by_category) setWorkspaceTags(hfState.tags);
@@ -4384,18 +4465,21 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4384
4465
  const merged = skeletons.map((s) => prev.find((g) => g.id === s.id) ?? s);
4385
4466
  return [...localOnly, ...merged];
4386
4467
  });
4468
+ const COOLDOWN_MS = 5 * 60 * 1e3;
4387
4469
  for (const entry of hfState.metadata) {
4388
- if (hfImageNotFoundRef.current.has(entry.id)) continue;
4470
+ if (galleryItemsRef.current.find((g) => g.id === entry.id)?.base64) continue;
4471
+ const failedAt = hfImageNotFoundRef.current.get(entry.id);
4472
+ if (failedAt && Date.now() - failedAt < COOLDOWN_MS) continue;
4389
4473
  hfLoadImageAsBase64(entry.id, hfToken).then((b64) => {
4390
4474
  if (!b64) {
4391
- hfImageNotFoundRef.current.add(entry.id);
4475
+ hfImageNotFoundRef.current.set(entry.id, Date.now());
4392
4476
  return;
4393
4477
  }
4394
4478
  const prefix = `data:${entry.mimeType || "image/jpeg"};base64,`;
4395
4479
  setGalleryItems((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
4396
4480
  setHistory((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
4397
4481
  }).catch(() => {
4398
- hfImageNotFoundRef.current.add(entry.id);
4482
+ hfImageNotFoundRef.current.set(entry.id, Date.now());
4399
4483
  });
4400
4484
  }
4401
4485
  }, [hfState]);
@@ -5105,7 +5189,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5105
5189
  }
5106
5190
  )
5107
5191
  ] }),
5108
- allowDevNamespace && !hfNamespace && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-3 w-full", children: [
5192
+ !hfNamespace && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-3 w-full", children: [
5109
5193
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-white/25 text-[10px] uppercase tracking-widest font-bold flex-shrink-0", children: "State:" }),
5110
5194
  ["app.art-by-rolands.de/", "dev-app.art-by-rolands.de/"].map((ns, i) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
5111
5195
  "button",
@@ -5477,7 +5561,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5477
5561
  onTagMove: handleTagMove
5478
5562
  }
5479
5563
  ),
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 }) })
5564
+ 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, allEvents: hfAllEvents }) })
5481
5565
  ] })
5482
5566
  ] }),
5483
5567
  /* @__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 +5943,7 @@ function FaApp({
5859
5943
  // src/index.ts
5860
5944
  init_hfStateService();
5861
5945
  init_hfStateService();
5862
- var LIB_VERSION = "2.0.16";
5946
+ var LIB_VERSION = "2.0.18";
5863
5947
  // Annotate the CommonJS export names for ESM import in node:
5864
5948
  0 && (module.exports = {
5865
5949
  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,10 @@ function useHFState(token, namespace) {
2049
2069
  isLoading,
2050
2070
  error,
2051
2071
  pendingBufferCount,
2072
+ localOnlyCount,
2052
2073
  eventCount,
2053
2074
  forks,
2054
- writeEvent: writeEvent2,
2075
+ writeEvent,
2055
2076
  refresh: loadFull,
2056
2077
  lastEventTs,
2057
2078
  allEvents: allEventsRef.current,
@@ -3306,7 +3327,7 @@ async function uploadViaCdnLib(token, path, bytes, mimeType) {
3306
3327
  }
3307
3328
  return [s];
3308
3329
  }
3309
- async function writeEvent(token, namespace) {
3330
+ async function writeTestEvent(token, namespace) {
3310
3331
  const ns = namespace.endsWith("/") ? namespace : namespace ? namespace + "/" : "";
3311
3332
  const ts = Date.now();
3312
3333
  const uuid = crypto.randomUUID().slice(0, 8);
@@ -3431,7 +3452,39 @@ function TestCard({
3431
3452
  ] })
3432
3453
  ] });
3433
3454
  }
3434
- function HFTestTab({ token, namespace, galleryItems }) {
3455
+ var EVENT_TYPE_COLORS = {
3456
+ image_added: "#60a5fa",
3457
+ tag_upserted: "#a78bfa",
3458
+ metadata_updated: "#34d399",
3459
+ probe: "#fbbf24"
3460
+ };
3461
+ function EventMonitor({ events }) {
3462
+ if (!events.length) {
3463
+ return /* @__PURE__ */ jsx20("div", { style: { padding: "12px 14px", fontSize: 12, color: "rgba(255,255,255,0.3)", fontStyle: "italic" }, children: "Noch keine Events geladen." });
3464
+ }
3465
+ const sorted = [...events].sort((a, b) => b.ts - a.ts).slice(0, 30);
3466
+ return /* @__PURE__ */ jsxs18("div", { style: { padding: "8px 10px 4px" }, children: [
3467
+ sorted.map((e, i) => {
3468
+ const color = EVENT_TYPE_COLORS[e.type] || "rgba(255,255,255,0.5)";
3469
+ const date = new Date(e.ts);
3470
+ const timeStr = date.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit", second: "2-digit" });
3471
+ const clientShort = e.clientId?.slice(0, 8) ?? "?";
3472
+ const payloadStr = JSON.stringify(e.payload ?? {});
3473
+ const payloadPreview = payloadStr.length > 80 ? payloadStr.slice(0, 80) + "\u2026" : payloadStr;
3474
+ return /* @__PURE__ */ jsxs18("div", { style: { display: "flex", gap: 8, alignItems: "flex-start", padding: "5px 0", borderBottom: "1px solid rgba(255,255,255,0.04)" }, children: [
3475
+ /* @__PURE__ */ jsx20("span", { style: { fontSize: 9, color: "rgba(255,255,255,0.25)", minWidth: 60, paddingTop: 2, fontVariantNumeric: "tabular-nums" }, children: timeStr }),
3476
+ /* @__PURE__ */ jsx20("span", { style: { fontSize: 11, fontWeight: 700, color, minWidth: 120, flexShrink: 0 }, children: e.type }),
3477
+ /* @__PURE__ */ jsx20("span", { style: { fontSize: 9, color: "rgba(255,255,255,0.2)", minWidth: 65, flexShrink: 0, fontFamily: "monospace" }, children: clientShort }),
3478
+ /* @__PURE__ */ jsx20("span", { style: { fontSize: 9, color: "rgba(255,255,255,0.35)", flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: payloadPreview })
3479
+ ] }, `${e.ts}_${e.clientId}_${i}`);
3480
+ }),
3481
+ /* @__PURE__ */ jsxs18("div", { style: { padding: "6px 0 2px", fontSize: 9, color: "rgba(255,255,255,0.2)", textAlign: "right" }, children: [
3482
+ events.length,
3483
+ " Events gesamt"
3484
+ ] })
3485
+ ] });
3486
+ }
3487
+ function HFTestTab({ token, namespace, galleryItems, allEvents = [] }) {
3435
3488
  const [selected, setSelected] = useState15(null);
3436
3489
  const [results, setResults] = useState15({});
3437
3490
  const [expanded, setExpanded] = useState15({});
@@ -3467,7 +3520,7 @@ function HFTestTab({ token, namespace, galleryItems }) {
3467
3520
  break;
3468
3521
  }
3469
3522
  } else {
3470
- steps = await writeEvent(token, namespace);
3523
+ steps = await writeTestEvent(token, namespace);
3471
3524
  }
3472
3525
  } catch (e) {
3473
3526
  steps = [{ label: "Unexpected error", method: "-", url: "-", reqHeaders: {}, error: String(e?.message ?? e), ok: false }];
@@ -3484,96 +3537,115 @@ function HFTestTab({ token, namespace, galleryItems }) {
3484
3537
  ];
3485
3538
  return /* @__PURE__ */ jsxs18("div", { style: { display: "flex", flexDirection: "column", height: "100%", overflowY: "auto", padding: "12px 10px 80px", boxSizing: "border-box", fontFamily: "inherit" }, children: [
3486
3539
  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",
3512
- {
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 }
3515
- }
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
3540
+ /* @__PURE__ */ jsx20("div", { style: { marginBottom: 12 }, children: /* @__PURE__ */ jsx20(
3541
+ CollapsibleCard,
3542
+ {
3543
+ title: "Event Monitor",
3544
+ icon: /* @__PURE__ */ jsx20("span", { className: "material-symbols-outlined", style: { fontSize: 16 }, children: "bolt" }),
3545
+ defaultOpen: true,
3546
+ children: /* @__PURE__ */ jsx20(EventMonitor, { events: allEvents })
3547
+ }
3548
+ ) }),
3549
+ /* @__PURE__ */ jsx20(
3550
+ CollapsibleCard,
3551
+ {
3552
+ title: "Upload Tests",
3553
+ icon: /* @__PURE__ */ jsx20("span", { className: "material-symbols-outlined", style: { fontSize: 16 }, children: "science" }),
3554
+ defaultOpen: false,
3555
+ children: /* @__PURE__ */ jsxs18("div", { style: { padding: "10px 10px 4px" }, children: [
3556
+ /* @__PURE__ */ jsxs18("div", { style: { marginBottom: 14 }, children: [
3557
+ /* @__PURE__ */ jsxs18("div", { style: { fontSize: 10, fontWeight: 700, color: "rgba(255,255,255,0.3)", textTransform: "uppercase", letterSpacing: "0.08em", marginBottom: 8 }, children: [
3558
+ "Bild ausw\xE4hlen (",
3559
+ withResults.length,
3560
+ " verf\xFCgbar)"
3561
+ ] }),
3562
+ 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(
3563
+ "button",
3564
+ {
3565
+ onClick: () => setSelected(g),
3566
+ style: { padding: 0, border: `2px solid ${selected?.id === g.id ? "#0284c7" : "transparent"}`, borderRadius: 6, cursor: "pointer", overflow: "hidden", background: "none", lineHeight: 0 },
3567
+ children: /* @__PURE__ */ jsx20(
3568
+ "img",
3569
+ {
3570
+ src: g.base64,
3571
+ alt: g.prompt || g.id,
3572
+ style: { width: "100%", aspectRatio: "1", objectFit: "cover", display: "block", borderRadius: 4 }
3573
+ }
3574
+ )
3575
+ },
3576
+ g.id
3577
+ )) }),
3578
+ selected && /* @__PURE__ */ jsxs18("div", { style: { marginTop: 10, display: "flex", gap: 10, alignItems: "flex-start" }, children: [
3579
+ /* @__PURE__ */ jsx20(
3580
+ "img",
3581
+ {
3582
+ src: selected.base64,
3583
+ style: { width: 80, height: 80, objectFit: "cover", borderRadius: 8, border: "1px solid rgba(255,255,255,0.1)", flexShrink: 0 }
3584
+ }
3585
+ ),
3586
+ /* @__PURE__ */ jsxs18("div", { style: { flex: 1, minWidth: 0 }, children: [
3587
+ /* @__PURE__ */ jsx20("div", { style: { fontSize: 11, fontWeight: 700, color: "rgba(255,255,255,0.7)", marginBottom: 2 }, children: "Ausgew\xE4hlt" }),
3588
+ /* @__PURE__ */ jsxs18("div", { style: { fontSize: 10, color: "rgba(255,255,255,0.3)", wordBreak: "break-all" }, children: [
3589
+ "ID: ",
3590
+ selected.id
3591
+ ] }),
3592
+ /* @__PURE__ */ jsxs18("div", { style: { fontSize: 10, color: "rgba(255,255,255,0.3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", marginTop: 2 }, children: [
3593
+ "Ziel: test/",
3594
+ selected.id,
3595
+ ".jpg"
3596
+ ] }),
3597
+ 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 })
3598
+ ] })
3599
+ ] })
3522
3600
  ] }),
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"
3601
+ /* @__PURE__ */ jsxs18("div", { style: { marginBottom: 14 }, children: [
3602
+ /* @__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: [
3603
+ "Bild hochladen \u2192 test/",
3604
+ "{",
3605
+ "id",
3606
+ "}",
3607
+ ".jpg"
3608
+ ] }),
3609
+ imgTests.map((t) => /* @__PURE__ */ jsx20(
3610
+ TestCard,
3611
+ {
3612
+ id: t.id,
3613
+ label: t.label,
3614
+ icon: t.icon,
3615
+ desc: t.desc,
3616
+ disabled: noToken || noImg || results[t.id]?.status === "running",
3617
+ state: results[t.id],
3618
+ onRun: () => run(t.id),
3619
+ expanded: !!expanded[t.id],
3620
+ onToggle: () => setExpanded((e) => ({ ...e, [t.id]: !e[t.id] }))
3621
+ },
3622
+ t.id
3623
+ ))
3527
3624
  ] }),
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 })
3625
+ /* @__PURE__ */ jsxs18("div", { style: { marginBottom: 4 }, children: [
3626
+ /* @__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: [
3627
+ "Event schreiben \u2192 ",
3628
+ namespace || "(kein namespace)",
3629
+ "test/events/"
3630
+ ] }),
3631
+ /* @__PURE__ */ jsx20(
3632
+ TestCard,
3633
+ {
3634
+ id: "event",
3635
+ label: "Write Event",
3636
+ icon: "bolt",
3637
+ desc: "Kleines JSON-Event hochladen (direct commit)",
3638
+ disabled: noToken || results["event"]?.status === "running",
3639
+ state: results["event"],
3640
+ onRun: () => run("event"),
3641
+ expanded: !!expanded["event"],
3642
+ onToggle: () => setExpanded((e) => ({ ...e, event: !e.event }))
3643
+ }
3644
+ )
3645
+ ] })
3529
3646
  ] })
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
- ] })
3647
+ }
3648
+ )
3577
3649
  ] });
3578
3650
  }
3579
3651
 
@@ -3625,7 +3697,9 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3625
3697
  state: hfState,
3626
3698
  isLoading: isHfRefreshing,
3627
3699
  pendingBufferCount,
3700
+ localOnlyCount,
3628
3701
  eventCount,
3702
+ allEvents: hfAllEvents,
3629
3703
  writeEvent: hfWriteEvent,
3630
3704
  refresh: refreshHF,
3631
3705
  hasStateZip
@@ -3633,6 +3707,13 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3633
3707
  const [bootstrapLog, setBootstrapLog] = useState16([]);
3634
3708
  const [isBootstrapping, setIsBootstrapping] = useState16(false);
3635
3709
  const syncTopSlot = /* @__PURE__ */ jsxs19(Fragment9, { children: [
3710
+ 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: [
3711
+ "\u26A0 ",
3712
+ localOnlyCount,
3713
+ " lokale Event",
3714
+ localOnlyCount > 1 ? "s" : "",
3715
+ " noch nicht auf HF best\xE4tigt"
3716
+ ] }),
3636
3717
  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
3718
  pendingBufferCount,
3638
3719
  " \xC4nderung",
@@ -3691,7 +3772,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3691
3772
  useEffect6(() => {
3692
3773
  galleryItemsRef.current = galleryItems;
3693
3774
  }, [galleryItems]);
3694
- const hfImageNotFoundRef = useRef7(/* @__PURE__ */ new Set());
3775
+ const hfImageNotFoundRef = useRef7(/* @__PURE__ */ new Map());
3695
3776
  useEffect6(() => {
3696
3777
  if (!hfState) return;
3697
3778
  if (hfState.tags?.by_category) setWorkspaceTags(hfState.tags);
@@ -3716,18 +3797,21 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3716
3797
  const merged = skeletons.map((s) => prev.find((g) => g.id === s.id) ?? s);
3717
3798
  return [...localOnly, ...merged];
3718
3799
  });
3800
+ const COOLDOWN_MS = 5 * 60 * 1e3;
3719
3801
  for (const entry of hfState.metadata) {
3720
- if (hfImageNotFoundRef.current.has(entry.id)) continue;
3802
+ if (galleryItemsRef.current.find((g) => g.id === entry.id)?.base64) continue;
3803
+ const failedAt = hfImageNotFoundRef.current.get(entry.id);
3804
+ if (failedAt && Date.now() - failedAt < COOLDOWN_MS) continue;
3721
3805
  hfLoadImageAsBase64(entry.id, hfToken).then((b64) => {
3722
3806
  if (!b64) {
3723
- hfImageNotFoundRef.current.add(entry.id);
3807
+ hfImageNotFoundRef.current.set(entry.id, Date.now());
3724
3808
  return;
3725
3809
  }
3726
3810
  const prefix = `data:${entry.mimeType || "image/jpeg"};base64,`;
3727
3811
  setGalleryItems((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
3728
3812
  setHistory((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
3729
3813
  }).catch(() => {
3730
- hfImageNotFoundRef.current.add(entry.id);
3814
+ hfImageNotFoundRef.current.set(entry.id, Date.now());
3731
3815
  });
3732
3816
  }
3733
3817
  }, [hfState]);
@@ -4437,7 +4521,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4437
4521
  }
4438
4522
  )
4439
4523
  ] }),
4440
- allowDevNamespace && !hfNamespace && /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-3 w-full", children: [
4524
+ !hfNamespace && /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-3 w-full", children: [
4441
4525
  /* @__PURE__ */ jsx21("span", { className: "text-white/25 text-[10px] uppercase tracking-widest font-bold flex-shrink-0", children: "State:" }),
4442
4526
  ["app.art-by-rolands.de/", "dev-app.art-by-rolands.de/"].map((ns, i) => /* @__PURE__ */ jsx21(
4443
4527
  "button",
@@ -4809,7 +4893,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4809
4893
  onTagMove: handleTagMove
4810
4894
  }
4811
4895
  ),
4812
- activeTab === "hftest" && /* @__PURE__ */ jsx21("div", { className: "absolute inset-0", children: /* @__PURE__ */ jsx21(HFTestTab, { token: hfToken, namespace: effectiveNamespace, galleryItems }) })
4896
+ activeTab === "hftest" && /* @__PURE__ */ jsx21("div", { className: "absolute inset-0", children: /* @__PURE__ */ jsx21(HFTestTab, { token: hfToken, namespace: effectiveNamespace, galleryItems, allEvents: hfAllEvents }) })
4813
4897
  ] })
4814
4898
  ] }),
4815
4899
  /* @__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 +5273,7 @@ function FaApp({
5189
5273
  }
5190
5274
 
5191
5275
  // src/index.ts
5192
- var LIB_VERSION = "2.0.16";
5276
+ var LIB_VERSION = "2.0.18";
5193
5277
  export {
5194
5278
  AvatarArchitectApp,
5195
5279
  CollapsibleCard,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rslsp1/fa-app-tools",
3
- "version": "2.0.16",
3
+ "version": "2.0.18",
4
4
  "description": "Shared tools and hooks for Fine Art flow apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",