recappi 0.1.68 → 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 +64 -19
- 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
|
}
|
|
@@ -2007,6 +2024,7 @@ function AppShell({
|
|
|
2007
2024
|
const [selected, setSelected] = useState8(0);
|
|
2008
2025
|
const [spinnerFrame, setSpinnerFrame] = useState8(0);
|
|
2009
2026
|
const [loadingMoreRecordings, setLoadingMoreRecordings] = useState8(false);
|
|
2027
|
+
const [loaded, setLoaded] = useState8(false);
|
|
2010
2028
|
const [loadError, setLoadError] = useState8(void 0);
|
|
2011
2029
|
const [notice, setNotice] = useState8(void 0);
|
|
2012
2030
|
const [summaryCache, setSummaryCache] = useState8(() => /* @__PURE__ */ new Map());
|
|
@@ -2246,6 +2264,7 @@ function AppShell({
|
|
|
2246
2264
|
} else {
|
|
2247
2265
|
setAccountStatus("error");
|
|
2248
2266
|
}
|
|
2267
|
+
setLoaded(true);
|
|
2249
2268
|
}, [fetchJobs, fetchRecordings, fetchDashboardStats, fetchAccountStatus]);
|
|
2250
2269
|
const transcribeStoppedRecording = useCallback(async () => {
|
|
2251
2270
|
const current = liveRecord;
|
|
@@ -2446,16 +2465,17 @@ function AppShell({
|
|
|
2446
2465
|
}, [refresh, pollMs]);
|
|
2447
2466
|
const hasRunning = jobs.some((item) => item.status === "running");
|
|
2448
2467
|
useEffect4(() => {
|
|
2449
|
-
if (!hasRunning) return;
|
|
2468
|
+
if (!hasRunning && loaded) return;
|
|
2450
2469
|
const id = setInterval(() => setSpinnerFrame((f) => f + 1), spinnerMs);
|
|
2451
2470
|
return () => clearInterval(id);
|
|
2452
|
-
}, [hasRunning, spinnerMs]);
|
|
2453
|
-
const jobRank = (s) => s === "running" ? 4 : s === "queued" ? 3 : s === "failed" ? 2 : s === "succeeded" ? 1 : 0;
|
|
2471
|
+
}, [hasRunning, loaded, spinnerMs]);
|
|
2472
|
+
const jobRank = (s) => s === "running" ? 5 : s === "stalled" ? 4 : s === "queued" ? 3 : s === "failed" ? 2 : s === "succeeded" ? 1 : 0;
|
|
2454
2473
|
const jobStatusByRecording = /* @__PURE__ */ new Map();
|
|
2455
2474
|
for (const job of jobs) {
|
|
2475
|
+
const status = effectiveJobStatus(job, now());
|
|
2456
2476
|
const prev = jobStatusByRecording.get(job.recordingId);
|
|
2457
|
-
if (!prev || jobRank(
|
|
2458
|
-
jobStatusByRecording.set(job.recordingId,
|
|
2477
|
+
if (!prev || jobRank(status) > jobRank(prev)) {
|
|
2478
|
+
jobStatusByRecording.set(job.recordingId, status);
|
|
2459
2479
|
}
|
|
2460
2480
|
}
|
|
2461
2481
|
useEffect4(() => {
|
|
@@ -2682,12 +2702,12 @@ function AppShell({
|
|
|
2682
2702
|
}
|
|
2683
2703
|
if (screen.kind === "jobDetail") {
|
|
2684
2704
|
const job = jobs.find((j) => j.jobId === screen.jobId);
|
|
2685
|
-
if (!job) return /* @__PURE__ */ jsx19(Missing, { label: "Job" });
|
|
2705
|
+
if (!job) return !loaded ? /* @__PURE__ */ jsx19(Loading, { label: "job" }) : /* @__PURE__ */ jsx19(Missing, { label: "Job" });
|
|
2686
2706
|
return /* @__PURE__ */ jsx19(Detail, { notice, children: /* @__PURE__ */ jsx19(JobDetailView, { item: job, origin, spinnerFrame, nowMs: now() }) });
|
|
2687
2707
|
}
|
|
2688
2708
|
if (screen.kind === "recordingDetail") {
|
|
2689
2709
|
const rec = recordings.find((r) => r.recordingId === screen.recordingId);
|
|
2690
|
-
if (!rec) return /* @__PURE__ */ jsx19(Missing, { label: "Recording" });
|
|
2710
|
+
if (!rec) return !loaded ? /* @__PURE__ */ jsx19(Loading, { label: "recording" }) : /* @__PURE__ */ jsx19(Missing, { label: "Recording" });
|
|
2691
2711
|
const detailTranscript = rec.activeTranscriptId ? transcriptCache.get(rec.activeTranscriptId) : void 0;
|
|
2692
2712
|
return /* @__PURE__ */ jsx19(Detail, { notice, children: /* @__PURE__ */ jsx19(
|
|
2693
2713
|
RecordingDetailView,
|
|
@@ -2754,7 +2774,14 @@ function AppShell({
|
|
|
2754
2774
|
const tab = screen.kind === "jobs" ? "jobs" : screen.kind === "account" ? "account" : "overview";
|
|
2755
2775
|
let body;
|
|
2756
2776
|
let position = "";
|
|
2757
|
-
if (
|
|
2777
|
+
if (!loaded) {
|
|
2778
|
+
position = "";
|
|
2779
|
+
const spin = "\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F"[spinnerFrame % 10];
|
|
2780
|
+
body = /* @__PURE__ */ jsx19(Box17, { marginTop: 1, children: /* @__PURE__ */ jsxs16(Text17, { color: "cyan", children: [
|
|
2781
|
+
spin,
|
|
2782
|
+
" Loading\u2026"
|
|
2783
|
+
] }) });
|
|
2784
|
+
} else if (screen.kind === "overview") {
|
|
2758
2785
|
const listBudget = Math.max(3, size.rows - 6);
|
|
2759
2786
|
const buckets = recordings.map((r) => dateBucket(r.createdAt, now()));
|
|
2760
2787
|
const win = groupedListWindow(buckets, selected, listBudget);
|
|
@@ -2795,7 +2822,8 @@ function AppShell({
|
|
|
2795
2822
|
{
|
|
2796
2823
|
items: jobs.slice(win.start, win.end),
|
|
2797
2824
|
selectedIndex: selected - win.start,
|
|
2798
|
-
spinnerFrame
|
|
2825
|
+
spinnerFrame,
|
|
2826
|
+
nowMs: now()
|
|
2799
2827
|
}
|
|
2800
2828
|
);
|
|
2801
2829
|
}
|
|
@@ -2830,6 +2858,13 @@ function Missing({ label }) {
|
|
|
2830
2858
|
/* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "esc back \xB7 q quit" })
|
|
2831
2859
|
] });
|
|
2832
2860
|
}
|
|
2861
|
+
function Loading({ label }) {
|
|
2862
|
+
return /* @__PURE__ */ jsx19(Box17, { paddingX: 1, children: /* @__PURE__ */ jsxs16(Text17, { color: "cyan", children: [
|
|
2863
|
+
"Loading ",
|
|
2864
|
+
label,
|
|
2865
|
+
"\u2026"
|
|
2866
|
+
] }) });
|
|
2867
|
+
}
|
|
2833
2868
|
var RECORDINGS_PAGE_SIZE, RECORDINGS_PREFETCH_REMAINING;
|
|
2834
2869
|
var init_AppShell = __esm({
|
|
2835
2870
|
"src/tui/AppShell.tsx"() {
|
|
@@ -17895,6 +17930,9 @@ var jobDataSchema = external_exports.object({
|
|
|
17895
17930
|
model: external_exports.string().optional(),
|
|
17896
17931
|
language: external_exports.string().nullable().optional(),
|
|
17897
17932
|
progressPercent: external_exports.number().min(0).max(100).nullable().optional(),
|
|
17933
|
+
claimExpiresAt: external_exports.number().int().nullable().optional(),
|
|
17934
|
+
lastHeartbeatAt: external_exports.number().int().nullable().optional(),
|
|
17935
|
+
heartbeatPhase: external_exports.string().nullable().optional(),
|
|
17898
17936
|
processedDurationMs: external_exports.number().int().nonnegative().nullable().optional(),
|
|
17899
17937
|
recording: external_exports.object({
|
|
17900
17938
|
title: external_exports.string().nullable().optional(),
|
|
@@ -17921,6 +17959,8 @@ var jobListItemSchema = external_exports.object({
|
|
|
17921
17959
|
enqueuedAt: external_exports.number().int().nullable().optional(),
|
|
17922
17960
|
startedAt: external_exports.number().int().nullable().optional(),
|
|
17923
17961
|
finishedAt: external_exports.number().int().nullable().optional(),
|
|
17962
|
+
claimExpiresAt: external_exports.number().int().nullable().optional(),
|
|
17963
|
+
lastHeartbeatAt: external_exports.number().int().nullable().optional(),
|
|
17924
17964
|
processedDurationMs: external_exports.number().int().nonnegative().nullable().optional(),
|
|
17925
17965
|
heartbeatPhase: external_exports.string().nullable().optional(),
|
|
17926
17966
|
recording: external_exports.object({
|
|
@@ -19560,6 +19600,9 @@ var RecappiApiClient = class {
|
|
|
19560
19600
|
...typeof parsed.model === "string" ? { model: parsed.model } : {},
|
|
19561
19601
|
...typeof parsed.language === "string" || parsed.language === null ? { language: parsed.language } : {},
|
|
19562
19602
|
...progressPercent !== void 0 ? { progressPercent } : {},
|
|
19603
|
+
...typeof parsed.claimExpiresAt === "number" || parsed.claimExpiresAt === null ? { claimExpiresAt: parsed.claimExpiresAt } : {},
|
|
19604
|
+
...typeof parsed.lastHeartbeatAt === "number" || parsed.lastHeartbeatAt === null ? { lastHeartbeatAt: parsed.lastHeartbeatAt } : {},
|
|
19605
|
+
...typeof parsed.heartbeatPhase === "string" || parsed.heartbeatPhase === null ? { heartbeatPhase: parsed.heartbeatPhase } : {},
|
|
19563
19606
|
...processedDurationMs !== void 0 ? { processedDurationMs } : {},
|
|
19564
19607
|
...recording ? {
|
|
19565
19608
|
recording: {
|
|
@@ -19824,6 +19867,8 @@ function mapJobListItem(row) {
|
|
|
19824
19867
|
...typeof row.enqueuedAt === "number" || row.enqueuedAt === null ? { enqueuedAt: row.enqueuedAt } : {},
|
|
19825
19868
|
...typeof row.startedAt === "number" || row.startedAt === null ? { startedAt: row.startedAt } : {},
|
|
19826
19869
|
...typeof row.finishedAt === "number" || row.finishedAt === null ? { finishedAt: row.finishedAt } : {},
|
|
19870
|
+
...typeof row.claimExpiresAt === "number" || row.claimExpiresAt === null ? { claimExpiresAt: row.claimExpiresAt } : {},
|
|
19871
|
+
...typeof row.lastHeartbeatAt === "number" || row.lastHeartbeatAt === null ? { lastHeartbeatAt: row.lastHeartbeatAt } : {},
|
|
19827
19872
|
...typeof row.processedDurationMs === "number" || row.processedDurationMs === null ? { processedDurationMs: row.processedDurationMs } : {},
|
|
19828
19873
|
...typeof row.heartbeatPhase === "string" || row.heartbeatPhase === null ? { heartbeatPhase: row.heartbeatPhase } : {},
|
|
19829
19874
|
recording: {
|