recappi 0.1.69 → 0.1.70
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.js +33 -14
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -150,6 +150,12 @@ function transcribeFraction(item) {
|
|
|
150
150
|
if (!total || total <= 0 || done == null) return null;
|
|
151
151
|
return Math.max(0, Math.min(1, done / total));
|
|
152
152
|
}
|
|
153
|
+
function isJobStalled(item, nowMs) {
|
|
154
|
+
return item.status === "running" && typeof item.claimExpiresAt === "number" && item.claimExpiresAt < nowMs;
|
|
155
|
+
}
|
|
156
|
+
function effectiveJobStatus(item, nowMs) {
|
|
157
|
+
return isJobStalled(item, nowMs) ? "stalled" : item.status;
|
|
158
|
+
}
|
|
153
159
|
function statusStyle(status) {
|
|
154
160
|
switch (status) {
|
|
155
161
|
case "running":
|
|
@@ -160,6 +166,8 @@ function statusStyle(status) {
|
|
|
160
166
|
return { label: "Ready", color: "green" };
|
|
161
167
|
case "failed":
|
|
162
168
|
return { label: "Failed", color: "red" };
|
|
169
|
+
case "stalled":
|
|
170
|
+
return { label: "Stalled", color: "yellow" };
|
|
163
171
|
default:
|
|
164
172
|
return { label: status, color: "gray" };
|
|
165
173
|
}
|
|
@@ -191,11 +199,14 @@ function statusGlyph(status, spinnerFrame) {
|
|
|
191
199
|
return "\u2713";
|
|
192
200
|
case "failed":
|
|
193
201
|
return "\u2717";
|
|
202
|
+
case "stalled":
|
|
203
|
+
return "!";
|
|
194
204
|
default:
|
|
195
205
|
return "\u2022";
|
|
196
206
|
}
|
|
197
207
|
}
|
|
198
|
-
function jobDetail(item) {
|
|
208
|
+
function jobDetail(item, nowMs) {
|
|
209
|
+
if (nowMs != null && isJobStalled(item, nowMs)) return "stalled \u2014 worker lost \xB7 T retry";
|
|
199
210
|
if (item.status === "running") {
|
|
200
211
|
const fraction = transcribeFraction(item);
|
|
201
212
|
if (fraction != null) {
|
|
@@ -735,17 +746,19 @@ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
|
735
746
|
function JobRow({
|
|
736
747
|
item,
|
|
737
748
|
selected,
|
|
738
|
-
spinnerFrame
|
|
749
|
+
spinnerFrame,
|
|
750
|
+
nowMs
|
|
739
751
|
}) {
|
|
740
|
-
const
|
|
741
|
-
const
|
|
752
|
+
const status = nowMs != null ? effectiveJobStatus(item, nowMs) : item.status;
|
|
753
|
+
const style = statusStyle(status);
|
|
754
|
+
const glyph = statusGlyph(status, spinnerFrame);
|
|
742
755
|
const title = item.recording?.title ?? item.recordingId;
|
|
743
756
|
return /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
744
757
|
/* @__PURE__ */ jsx7(Box5, { width: 3, children: /* @__PURE__ */ jsx7(Text5, { color: "cyan", children: selected ? "\u25B8" : "" }) }),
|
|
745
758
|
/* @__PURE__ */ jsx7(Box5, { width: 2, children: /* @__PURE__ */ jsx7(Text5, { color: style.color, children: glyph }) }),
|
|
746
759
|
/* @__PURE__ */ jsx7(Box5, { width: 13, children: /* @__PURE__ */ jsx7(Text5, { color: style.color, children: style.label }) }),
|
|
747
760
|
/* @__PURE__ */ jsx7(Box5, { width: 26, children: /* @__PURE__ */ jsx7(Text5, { bold: selected, wrap: "truncate-end", children: title }) }),
|
|
748
|
-
/* @__PURE__ */ jsx7(Text5, { dimColor: !selected, children: jobDetail(item) })
|
|
761
|
+
/* @__PURE__ */ jsx7(Text5, { dimColor: !selected, children: jobDetail(item, nowMs) })
|
|
749
762
|
] });
|
|
750
763
|
}
|
|
751
764
|
var init_JobRow = __esm({
|
|
@@ -761,7 +774,8 @@ import { jsx as jsx8 } from "react/jsx-runtime";
|
|
|
761
774
|
function JobsView({
|
|
762
775
|
items,
|
|
763
776
|
selectedIndex,
|
|
764
|
-
spinnerFrame
|
|
777
|
+
spinnerFrame,
|
|
778
|
+
nowMs
|
|
765
779
|
}) {
|
|
766
780
|
if (items.length === 0) {
|
|
767
781
|
return /* @__PURE__ */ jsx8(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text6, { dimColor: true, children: "No transcription jobs yet \u2014 run: recappi upload <file> --transcribe" }) });
|
|
@@ -771,7 +785,8 @@ function JobsView({
|
|
|
771
785
|
{
|
|
772
786
|
item,
|
|
773
787
|
selected: index === selectedIndex,
|
|
774
|
-
spinnerFrame
|
|
788
|
+
spinnerFrame,
|
|
789
|
+
nowMs
|
|
775
790
|
},
|
|
776
791
|
item.jobId
|
|
777
792
|
)) });
|
|
@@ -794,6 +809,7 @@ function recordingTitle2(item) {
|
|
|
794
809
|
function recordingProcessingState(item, jobStatus, spinnerFrame) {
|
|
795
810
|
if (item.status === "uploading") return { glyph: "\u2191", color: "cyan" };
|
|
796
811
|
if (item.status === "failed" || jobStatus === "failed") return { glyph: "\u2717", color: "red" };
|
|
812
|
+
if (jobStatus === "stalled") return { glyph: "!", color: "yellow" };
|
|
797
813
|
if (jobStatus === "running") return { glyph: spinnerChar(spinnerFrame), color: "cyan" };
|
|
798
814
|
if (jobStatus === "queued") return { glyph: "\u25CB", color: "yellow" };
|
|
799
815
|
if (item.status === "aborted") return { glyph: "\u2022", color: "gray" };
|
|
@@ -1019,7 +1035,8 @@ function JobDetailView({
|
|
|
1019
1035
|
spinnerFrame,
|
|
1020
1036
|
nowMs
|
|
1021
1037
|
}) {
|
|
1022
|
-
const
|
|
1038
|
+
const status = effectiveJobStatus(item, nowMs);
|
|
1039
|
+
const style = statusStyle(status);
|
|
1023
1040
|
const links = resolveJobLinks(item, origin);
|
|
1024
1041
|
const title = item.recording?.title ?? item.recordingId;
|
|
1025
1042
|
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingX: 1, children: [
|
|
@@ -1067,10 +1084,10 @@ function JobDetailView({
|
|
|
1067
1084
|
/* @__PURE__ */ jsx13(
|
|
1068
1085
|
TimelineRow,
|
|
1069
1086
|
{
|
|
1070
|
-
label:
|
|
1087
|
+
label: status === "failed" ? "Failed" : status === "stalled" ? "Stalled \u2014 worker lost" : status === "running" ? "Transcribing" : "Finished",
|
|
1071
1088
|
done: item.finishedAt != null,
|
|
1072
1089
|
failed: item.status === "failed",
|
|
1073
|
-
running:
|
|
1090
|
+
running: status === "running",
|
|
1074
1091
|
at: item.finishedAt,
|
|
1075
1092
|
nowMs
|
|
1076
1093
|
}
|
|
@@ -2452,12 +2469,13 @@ function AppShell({
|
|
|
2452
2469
|
const id = setInterval(() => setSpinnerFrame((f) => f + 1), spinnerMs);
|
|
2453
2470
|
return () => clearInterval(id);
|
|
2454
2471
|
}, [hasRunning, loaded, spinnerMs]);
|
|
2455
|
-
const jobRank = (s) => s === "running" ? 4 : s === "queued" ? 3 : s === "failed" ? 2 : s === "succeeded" ? 1 : 0;
|
|
2472
|
+
const jobRank = (s) => s === "running" ? 5 : s === "stalled" ? 4 : s === "queued" ? 3 : s === "failed" ? 2 : s === "succeeded" ? 1 : 0;
|
|
2456
2473
|
const jobStatusByRecording = /* @__PURE__ */ new Map();
|
|
2457
2474
|
for (const job of jobs) {
|
|
2475
|
+
const status = effectiveJobStatus(job, now());
|
|
2458
2476
|
const prev = jobStatusByRecording.get(job.recordingId);
|
|
2459
|
-
if (!prev || jobRank(
|
|
2460
|
-
jobStatusByRecording.set(job.recordingId,
|
|
2477
|
+
if (!prev || jobRank(status) > jobRank(prev)) {
|
|
2478
|
+
jobStatusByRecording.set(job.recordingId, status);
|
|
2461
2479
|
}
|
|
2462
2480
|
}
|
|
2463
2481
|
useEffect4(() => {
|
|
@@ -2804,7 +2822,8 @@ function AppShell({
|
|
|
2804
2822
|
{
|
|
2805
2823
|
items: jobs.slice(win.start, win.end),
|
|
2806
2824
|
selectedIndex: selected - win.start,
|
|
2807
|
-
spinnerFrame
|
|
2825
|
+
spinnerFrame,
|
|
2826
|
+
nowMs: now()
|
|
2808
2827
|
}
|
|
2809
2828
|
);
|
|
2810
2829
|
}
|