dsh-comfyui 0.5.1 → 0.5.2

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/client/client.js CHANGED
@@ -45,6 +45,8 @@ window.__ModuleLoader__.load({
45
45
  cardFailed: "执行失败",
46
46
  cardInterrupted: "已中断",
47
47
  cardCollect: "使用 job_output 收集后台任务结果",
48
+ cardRecovered: "已从资产索引恢复(ComfyUI 历史已失效)",
49
+ cardPollStalled: "后台任务状态收集失败(已重试 {n} 次):{message}",
48
50
  cardLoadFailed: "加载失败,点下方下载",
49
51
  cardDownload: "下载",
50
52
  cardWorkflow: "工作流",
@@ -308,6 +310,8 @@ window.__ModuleLoader__.load({
308
310
  cardFailed: "Execution failed",
309
311
  cardInterrupted: "Interrupted",
310
312
  cardCollect: "Collect the background job result with job_output",
313
+ cardRecovered: "Recovered from the asset index (ComfyUI history was evicted)",
314
+ cardPollStalled: "Failed to collect the background job status after {n} retries: {message}",
311
315
  cardLoadFailed: "failed to load, use the download link below",
312
316
  cardDownload: "Download",
313
317
  cardWorkflow: "Workflow",
@@ -733,17 +737,39 @@ window.__ModuleLoader__.load({
733
737
  onIndex: setLightbox
734
738
  }) : null);
735
739
  }
740
+ /** Transient-failure retry budget for the jobs/media poll (backoff steps). */
741
+ const POLL_MAX_FAILURES = 20;
742
+ /** 'unknown' grace polls (~5 min at 3 s) before treating history as evicted.
743
+ * The route answers 'queued' while ComfyUI still holds the prompt, so this
744
+ * only counts polls where the job is in neither history nor the queue. */
745
+ const POLL_UNKNOWN_GRACE = 100;
736
746
  function BackgroundCard({ label, promptId, t }) {
737
747
  const [result, setResult] = (0, react.useState)(null);
738
748
  const [lightbox, setLightbox] = (0, react.useState)(null);
739
749
  (0, react.useEffect)(() => {
740
750
  let stopped = false;
741
751
  let timer;
752
+ let failures = 0;
753
+ let unknowns = 0;
754
+ const schedule = (delay, fn) => {
755
+ timer = window.setTimeout(fn, delay);
756
+ };
757
+ const recoverFromAssets = async () => {
758
+ try {
759
+ const data = await (await fetch("/comfyui/assets", { headers: { accept: "application/json" } })).json();
760
+ if (stopped || data.ok !== true || !Array.isArray(data.assets)) return null;
761
+ const media = data.assets.find((entry) => entry.promptId === promptId)?.media ?? [];
762
+ return media.length > 0 ? media : null;
763
+ } catch {
764
+ return null;
765
+ }
766
+ };
742
767
  const poll = async () => {
743
768
  try {
744
769
  const data = await (await fetch(`/comfyui/jobs/media?promptId=${encodeURIComponent(promptId)}`, { headers: { accept: "application/json" } })).json();
745
770
  if (stopped) return;
746
- if (data.ok !== true || data.status === void 0) return;
771
+ if (data.ok !== true || data.status === void 0) throw new Error(data.error ?? "invalid jobs/media response");
772
+ failures = 0;
747
773
  if (data.status === "completed" || data.status === "failed") {
748
774
  setResult({
749
775
  status: data.status,
@@ -752,13 +778,51 @@ window.__ModuleLoader__.load({
752
778
  });
753
779
  return;
754
780
  }
755
- timer = window.setTimeout(() => {
781
+ if (data.status === "unknown") {
782
+ unknowns += 1;
783
+ const recovered = await recoverFromAssets();
784
+ if (stopped) return;
785
+ if (recovered !== null) {
786
+ setResult({
787
+ status: "completed",
788
+ media: recovered,
789
+ recovered: true
790
+ });
791
+ return;
792
+ }
793
+ if (unknowns > POLL_UNKNOWN_GRACE) {
794
+ setResult({
795
+ status: "completed",
796
+ media: []
797
+ });
798
+ return;
799
+ }
800
+ schedule(3e3, () => {
801
+ poll();
802
+ });
803
+ return;
804
+ }
805
+ unknowns = 0;
806
+ schedule(3e3, () => {
756
807
  poll();
757
- }, 3e3);
758
- } catch {
759
- timer = window.setTimeout(() => {
808
+ });
809
+ } catch (error) {
810
+ if (stopped) return;
811
+ failures += 1;
812
+ if (failures > POLL_MAX_FAILURES) {
813
+ const message = error instanceof Error ? error.message : String(error);
814
+ setResult({
815
+ status: "failed",
816
+ error: t("cardPollStalled", {
817
+ n: POLL_MAX_FAILURES,
818
+ message
819
+ })
820
+ });
821
+ return;
822
+ }
823
+ schedule(Math.min(3e3 * failures, 3e4), () => {
760
824
  poll();
761
- }, 5e3);
825
+ });
762
826
  }
763
827
  };
764
828
  poll();
@@ -766,12 +830,12 @@ window.__ModuleLoader__.load({
766
830
  stopped = true;
767
831
  if (timer !== void 0) window.clearTimeout(timer);
768
832
  };
769
- }, [promptId]);
833
+ }, [promptId, t]);
770
834
  if (result !== null && (result.status === "completed" || result.status === "failed")) {
771
835
  const media = result.media ?? [];
772
836
  const urls = media.map((item) => item.url);
773
837
  const kinds = media.map((item) => item.kind);
774
- return (0, react.createElement)("div", { className: "dsc-card" }, (0, react.createElement)("div", { className: "dsc-card-head" }, (0, react.createElement)("span", { className: result.status === "failed" ? "dsc-badge dsc-badge--err" : "dsc-badge dsc-badge--ok" }, result.status === "failed" ? t("cardFailed") : t("cardBackgroundDone")), (0, react.createElement)("span", { className: "dsc-meta" }, `${label} · ${promptId}`)), result.status === "failed" ? (0, react.createElement)("div", { className: "dsc-meta dsc-job-error" }, result.error ?? t("cardFailed")) : media.length > 0 ? (0, react.createElement)("div", { className: "dsc-grid" }, media.map((item, index) => (0, react.createElement)(MediaItem, {
838
+ return (0, react.createElement)("div", { className: "dsc-card" }, (0, react.createElement)("div", { className: "dsc-card-head" }, (0, react.createElement)("span", { className: result.status === "failed" ? "dsc-badge dsc-badge--err" : "dsc-badge dsc-badge--ok" }, result.status === "failed" ? t("cardFailed") : t("cardBackgroundDone")), (0, react.createElement)("span", { className: "dsc-meta" }, `${label} · ${promptId}`)), result.status !== "failed" && result.recovered === true ? (0, react.createElement)("div", { className: "dsc-meta" }, t("cardRecovered")) : null, result.status === "failed" ? (0, react.createElement)("div", { className: "dsc-meta dsc-job-error" }, result.error ?? t("cardFailed")) : media.length > 0 ? (0, react.createElement)("div", { className: "dsc-grid" }, media.map((item, index) => (0, react.createElement)(MediaItem, {
775
839
  key: `${item.node}-${item.index}`,
776
840
  item,
777
841
  t,
@@ -843,6 +907,19 @@ window.__ModuleLoader__.load({
843
907
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
844
908
  return await response.json();
845
909
  }
910
+ /**
911
+ * GET a list endpoint and return the array under `key`. Routes report failure
912
+ * as HTTP 200 `{ ok: false, error }`, so a plain getJson hands the caller
913
+ * `undefined` for the list — which crashed the whole panel on `.length` inside
914
+ * the slot error boundary, i.e. "click does nothing" (Issue #5). Throwing the
915
+ * route's error instead lets the caller show it next to the section.
916
+ */
917
+ async function getList(url, key) {
918
+ const data = await getJson(url);
919
+ const list = data[key];
920
+ if (Array.isArray(list)) return list;
921
+ throw new Error(typeof data.error === "string" ? data.error : `unexpected response from ${url}`);
922
+ }
846
923
  async function postJson(url, body) {
847
924
  const response = await fetch(url, {
848
925
  method: "POST",
@@ -2250,13 +2327,16 @@ window.__ModuleLoader__.load({
2250
2327
  active: tab === "queue",
2251
2328
  label: t("tabQueue"),
2252
2329
  onClick: () => panelStore.setTab("queue")
2253
- })), (0, react.createElement)("div", { className: "dsc-panel-body" }, tab === "workflows" ? (0, react.createElement)(WorkflowsTab, { t }) : tab === "assets" ? (0, react.createElement)(AssetsTab, {
2330
+ })), (0, react.createElement)("div", { className: "dsc-panel-body" }, (0, react.createElement)(TabBoundary, {
2331
+ key: tab,
2332
+ t
2333
+ }, tab === "workflows" ? (0, react.createElement)(WorkflowsTab, { t }) : tab === "assets" ? (0, react.createElement)(AssetsTab, {
2254
2334
  t,
2255
2335
  onPreview: openPreview
2256
2336
  }) : (0, react.createElement)(QueueTab, {
2257
2337
  t,
2258
2338
  onPreview: openPreview
2259
- })), lightbox !== null ? (0, react.createElement)(Lightbox, {
2339
+ }))), lightbox !== null ? (0, react.createElement)(Lightbox, {
2260
2340
  t,
2261
2341
  images: lightbox.images,
2262
2342
  kinds: lightbox.kinds,
@@ -2284,6 +2364,27 @@ window.__ModuleLoader__.load({
2284
2364
  function ErrorNote({ t, message }) {
2285
2365
  return (0, react.createElement)("div", { className: "dsc-err" }, `${t("error")}: ${message}`);
2286
2366
  }
2367
+ /**
2368
+ * Catches a tab's render error and shows it in place. Without this the host's
2369
+ * slot error boundary swallows the whole overlay silently, so a tab crash
2370
+ * reads as "clicking the button does nothing" with a clean console (Issue #5).
2371
+ */
2372
+ var TabBoundary = class extends react.Component {
2373
+ state = { error: null };
2374
+ static getDerivedStateFromError(error) {
2375
+ return { error: error instanceof Error ? error : new Error(String(error)) };
2376
+ }
2377
+ componentDidCatch(error) {
2378
+ console.error("[dsh-comfyui] panel tab render error", error);
2379
+ }
2380
+ render() {
2381
+ if (this.state.error !== null) return (0, react.createElement)(ErrorNote, {
2382
+ t: this.props.t,
2383
+ message: this.state.error.message
2384
+ });
2385
+ return this.props.children;
2386
+ }
2387
+ };
2287
2388
  /** Load area at the bottom of the workflow library, modeled on the ComfyUI
2288
2389
  * LoadImage node — a list of **slots** rather than a single image.
2289
2390
  *
@@ -3169,15 +3270,13 @@ window.__ModuleLoader__.load({
3169
3270
  const [deleting, setDeleting] = (0, react.useState)(null);
3170
3271
  const load = async () => {
3171
3272
  try {
3172
- const data = await getJson("/comfyui/workflows");
3173
- setList(data.workflows);
3273
+ setList(await getList("/comfyui/workflows", "workflows"));
3174
3274
  setError(null);
3175
3275
  } catch (cause) {
3176
3276
  setError(cause instanceof Error ? cause.message : String(cause));
3177
3277
  }
3178
3278
  try {
3179
- const data = await getJson("/comfyui/comfy-workflows");
3180
- setComfyui(data.workflows);
3279
+ setComfyui(await getList("/comfyui/comfy-workflows", "workflows"));
3181
3280
  setComfyuiError(null);
3182
3281
  } catch (cause) {
3183
3282
  setComfyuiError(cause instanceof Error ? cause.message : String(cause));
@@ -3185,13 +3284,13 @@ window.__ModuleLoader__.load({
3185
3284
  };
3186
3285
  (0, react.useEffect)(() => {
3187
3286
  let cancelled = false;
3188
- getJson("/comfyui/workflows").then((data) => {
3189
- if (!cancelled) setList(data.workflows);
3287
+ getList("/comfyui/workflows", "workflows").then((workflows) => {
3288
+ if (!cancelled) setList(workflows);
3190
3289
  }).catch((cause) => {
3191
3290
  if (!cancelled) setError(cause instanceof Error ? cause.message : String(cause));
3192
3291
  });
3193
- getJson("/comfyui/comfy-workflows").then((data) => {
3194
- if (!cancelled) setComfyui(data.workflows);
3292
+ getList("/comfyui/comfy-workflows", "workflows").then((workflows) => {
3293
+ if (!cancelled) setComfyui(workflows);
3195
3294
  }).catch((cause) => {
3196
3295
  if (!cancelled) setComfyuiError(cause instanceof Error ? cause.message : String(cause));
3197
3296
  });
@@ -4059,8 +4158,7 @@ window.__ModuleLoader__.load({
4059
4158
  const [notice, setNotice] = (0, react.useState)(null);
4060
4159
  const load = async () => {
4061
4160
  try {
4062
- const data = await getJson("/comfyui/assets");
4063
- setAssets(data.assets);
4161
+ setAssets(await getList("/comfyui/assets", "assets"));
4064
4162
  setError(null);
4065
4163
  } catch (cause) {
4066
4164
  setError(cause instanceof Error ? cause.message : String(cause));
@@ -4068,8 +4166,8 @@ window.__ModuleLoader__.load({
4068
4166
  };
4069
4167
  (0, react.useEffect)(() => {
4070
4168
  let cancelled = false;
4071
- getJson("/comfyui/assets").then((data) => {
4072
- if (!cancelled) setAssets(data.assets);
4169
+ getList("/comfyui/assets", "assets").then((items) => {
4170
+ if (!cancelled) setAssets(items);
4073
4171
  }).catch((cause) => {
4074
4172
  if (!cancelled) setError(cause instanceof Error ? cause.message : String(cause));
4075
4173
  });