recappi 0.1.54 → 0.1.55
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 +981 -719
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -59,18 +59,18 @@ function applyRecordingEventToTelemetry(telemetry, event) {
|
|
|
59
59
|
}
|
|
60
60
|
if (event.type === "audio.level") {
|
|
61
61
|
const level = levelFromRmsDb(event.rmsDb);
|
|
62
|
-
const
|
|
62
|
+
const levelDb4 = typeof event.rmsDb === "number" && Number.isFinite(event.rmsDb) ? event.rmsDb : void 0;
|
|
63
63
|
if (event.input === "microphone") {
|
|
64
64
|
return {
|
|
65
65
|
...telemetry,
|
|
66
66
|
level: { ...telemetry.level, mic: level },
|
|
67
|
-
levelDb: { ...telemetry.levelDb, ...
|
|
67
|
+
levelDb: { ...telemetry.levelDb, ...levelDb4 != null ? { mic: levelDb4 } : {} }
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
return {
|
|
71
71
|
...telemetry,
|
|
72
72
|
level: { ...telemetry.level, system: level },
|
|
73
|
-
levelDb: { ...telemetry.levelDb, ...
|
|
73
|
+
levelDb: { ...telemetry.levelDb, ...levelDb4 != null ? { system: levelDb4 } : {} }
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
76
|
if (event.type === "error") {
|
|
@@ -199,8 +199,8 @@ function jobDetail(item) {
|
|
|
199
199
|
if (item.status === "running") {
|
|
200
200
|
const fraction = transcribeFraction(item);
|
|
201
201
|
if (fraction != null) {
|
|
202
|
-
const
|
|
203
|
-
return `${progressBar(fraction)} ${String(
|
|
202
|
+
const pct2 = Math.round(fraction * 100);
|
|
203
|
+
return `${progressBar(fraction)} ${String(pct2).padStart(3)}% ${formatClockMs(
|
|
204
204
|
item.processedDurationMs
|
|
205
205
|
)} / ${formatClockMs(item.recording?.durationMs)}`;
|
|
206
206
|
}
|
|
@@ -591,704 +591,436 @@ var init_LiveCaptionsScreen = __esm({
|
|
|
591
591
|
}
|
|
592
592
|
});
|
|
593
593
|
|
|
594
|
-
// src/tui/
|
|
595
|
-
import {
|
|
596
|
-
import {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
594
|
+
// src/tui/AccountView.tsx
|
|
595
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
596
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
597
|
+
function AccountView({
|
|
598
|
+
status,
|
|
599
|
+
nowMs = Date.now()
|
|
600
|
+
}) {
|
|
601
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 1, children: [
|
|
602
|
+
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "\u2039 Account" }),
|
|
603
|
+
status === "loading" || status === void 0 ? /* @__PURE__ */ jsx5(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "Loading account\u2026" }) }) : status === "error" ? /* @__PURE__ */ jsx5(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text3, { color: "red", children: "Couldn't load account status" }) }) : !status.loggedIn ? /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
604
|
+
/* @__PURE__ */ jsx5(Text3, { color: "yellow", children: "Not signed in" }),
|
|
605
|
+
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: `origin ${status.origin}` }),
|
|
606
|
+
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "Run `recappi auth login` to sign in." })
|
|
607
|
+
] }) : /* @__PURE__ */ jsx5(AccountBody, { status, nowMs }),
|
|
608
|
+
/* @__PURE__ */ jsx5(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "r refresh \xB7 esc back \xB7 q quit" }) })
|
|
609
|
+
] });
|
|
600
610
|
}
|
|
601
|
-
function
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
611
|
+
function AccountBody({
|
|
612
|
+
status,
|
|
613
|
+
nowMs
|
|
614
|
+
}) {
|
|
615
|
+
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
616
|
+
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
617
|
+
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
618
|
+
/* @__PURE__ */ jsx5(Text3, { color: "green", children: "\u25CF " }),
|
|
619
|
+
/* @__PURE__ */ jsx5(Text3, { bold: true, children: status.email ?? status.userId ?? "Signed in" })
|
|
620
|
+
] }),
|
|
621
|
+
status.email && status.userId ? /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: status.userId }) : null,
|
|
622
|
+
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: `origin ${status.origin}` })
|
|
623
|
+
] }),
|
|
624
|
+
status.billing ? /* @__PURE__ */ jsx5(Usage, { billing: status.billing, nowMs }) : null,
|
|
625
|
+
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
626
|
+
/* @__PURE__ */ jsx5(Text3, { bold: true, dimColor: true, children: "LOCAL STORE" }),
|
|
627
|
+
/* @__PURE__ */ jsx5(Text3, { dimColor: true, wrap: "truncate-middle", children: status.localStore.path }),
|
|
628
|
+
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
629
|
+
`${status.localStore.accountScopedArtifacts} artifact${status.localStore.accountScopedArtifacts === 1 ? "" : "s"} for this account`,
|
|
630
|
+
status.localStore.unattributedArtifacts > 0 ? ` \xB7 ${status.localStore.unattributedArtifacts} unattributed` : ""
|
|
631
|
+
] })
|
|
632
|
+
] })
|
|
633
|
+
] });
|
|
605
634
|
}
|
|
606
|
-
function
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
635
|
+
function Usage({
|
|
636
|
+
billing,
|
|
637
|
+
nowMs
|
|
638
|
+
}) {
|
|
639
|
+
const minutesCap = billing.minutesCap;
|
|
640
|
+
const minutesUsed = billing.minutesUsed;
|
|
641
|
+
const storageCap = billing.storageCapBytes;
|
|
642
|
+
return /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
643
|
+
/* @__PURE__ */ jsx5(Text3, { bold: true, dimColor: true, children: "USAGE" }),
|
|
644
|
+
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
645
|
+
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "Plan " }),
|
|
646
|
+
/* @__PURE__ */ jsx5(Text3, { bold: true, children: billing.tier })
|
|
647
|
+
] }),
|
|
648
|
+
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
649
|
+
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "Minutes " }),
|
|
650
|
+
minutesCap != null ? /* @__PURE__ */ jsx5(Text3, { color: billing.isOverMinutes ? "red" : "cyan", children: `${progressBar(minutesUsed / Math.max(1, minutesCap), 12)} ` }) : null,
|
|
651
|
+
/* @__PURE__ */ jsx5(Text3, { color: billing.isOverMinutes ? "red" : void 0, children: `${Math.round(minutesUsed)}` }),
|
|
652
|
+
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: ` / ${minutesCap != null ? Math.round(minutesCap) : "\u221E"} min` }),
|
|
653
|
+
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: ` (batch ${Math.round(billing.batchMinutesUsed)} \xB7 live ${Math.round(billing.realtimeMinutesUsed)})` })
|
|
654
|
+
] }),
|
|
655
|
+
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
656
|
+
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "Storage " }),
|
|
657
|
+
storageCap != null ? /* @__PURE__ */ jsx5(Text3, { color: billing.isOverStorage ? "red" : "cyan", children: `${progressBar(billing.storageBytes / Math.max(1, storageCap), 12)} ` }) : null,
|
|
658
|
+
/* @__PURE__ */ jsx5(Text3, { color: billing.isOverStorage ? "red" : void 0, children: formatBytes2(billing.storageBytes) }),
|
|
659
|
+
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: ` / ${storageCap != null ? formatBytes2(storageCap) : "\u221E"}` })
|
|
660
|
+
] }),
|
|
661
|
+
billing.isOverMinutes || billing.isOverStorage ? /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
|
|
662
|
+
billing.isOverMinutes ? "Over minutes limit. " : "",
|
|
663
|
+
billing.isOverStorage ? "Over storage limit." : ""
|
|
664
|
+
] }) : null,
|
|
665
|
+
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: `Period ${periodText(billing, nowMs)}` })
|
|
666
|
+
] });
|
|
610
667
|
}
|
|
611
|
-
function
|
|
612
|
-
|
|
613
|
-
|
|
668
|
+
function periodText(billing, nowMs) {
|
|
669
|
+
const remainingMs = epochToMs(billing.periodEnd) - nowMs;
|
|
670
|
+
if (!Number.isFinite(remainingMs) || remainingMs <= 0) return "\u2014";
|
|
671
|
+
const days = Math.floor(remainingMs / 864e5);
|
|
672
|
+
if (days >= 1) return `${days}d left`;
|
|
673
|
+
return `${formatClockMs(remainingMs)} left`;
|
|
614
674
|
}
|
|
615
|
-
function
|
|
675
|
+
function epochToMs(value) {
|
|
676
|
+
return value > 1e12 ? value : value * 1e3;
|
|
677
|
+
}
|
|
678
|
+
var init_AccountView = __esm({
|
|
679
|
+
"src/tui/AccountView.tsx"() {
|
|
680
|
+
"use strict";
|
|
681
|
+
init_format();
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
// src/tui/chrome.tsx
|
|
686
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
687
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
688
|
+
function Header({ active }) {
|
|
689
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
690
|
+
/* @__PURE__ */ jsxs4(Text4, { bold: true, color: "green", children: [
|
|
691
|
+
"\u25CF Recappi",
|
|
692
|
+
" "
|
|
693
|
+
] }),
|
|
694
|
+
/* @__PURE__ */ jsx6(Tab, { num: "1", label: "Overview", active: active === "overview" }),
|
|
695
|
+
/* @__PURE__ */ jsx6(Tab, { num: "2", label: "Jobs", active: active === "jobs" }),
|
|
696
|
+
/* @__PURE__ */ jsx6(Tab, { num: "3", label: "Account", active: active === "account" })
|
|
697
|
+
] });
|
|
698
|
+
}
|
|
699
|
+
function Tab({
|
|
700
|
+
num,
|
|
616
701
|
label,
|
|
617
|
-
|
|
618
|
-
level,
|
|
619
|
-
paused,
|
|
620
|
-
width,
|
|
621
|
-
rows
|
|
702
|
+
active
|
|
622
703
|
}) {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
return /* @__PURE__ */
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
] }),
|
|
631
|
-
Array.from({ length: rows }, (_, r) => {
|
|
632
|
-
const fromBottom = rows - r;
|
|
633
|
-
return /* @__PURE__ */ jsx3(Text2, { children: cols.map(
|
|
634
|
-
(c, i) => c >= fromBottom ? /* @__PURE__ */ jsx3(Text2, { color: litColor, children: c === fromBottom ? "\u2022" : "\u25CF" }, i) : /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "\xB7" }, i)
|
|
635
|
-
) }, r);
|
|
636
|
-
})
|
|
704
|
+
if (active) {
|
|
705
|
+
return /* @__PURE__ */ jsx6(Text4, { bold: true, inverse: true, color: "cyan", children: ` ${num} ${label} ` });
|
|
706
|
+
}
|
|
707
|
+
return /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
708
|
+
" ",
|
|
709
|
+
/* @__PURE__ */ jsx6(Text4, { color: "cyan", children: num }),
|
|
710
|
+
` ${label} `
|
|
637
711
|
] });
|
|
638
712
|
}
|
|
639
|
-
function
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
713
|
+
function Footer({ keys }) {
|
|
714
|
+
const segments = keys.split(" \xB7 ");
|
|
715
|
+
return /* @__PURE__ */ jsx6(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text4, { children: segments.map((segment, i) => {
|
|
716
|
+
const space = segment.indexOf(" ");
|
|
717
|
+
const key = space === -1 ? segment : segment.slice(0, space);
|
|
718
|
+
const desc = space === -1 ? "" : segment.slice(space);
|
|
719
|
+
return /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
720
|
+
i > 0 ? /* @__PURE__ */ jsx6(Text4, { dimColor: true, children: " \xB7 " }) : null,
|
|
721
|
+
/* @__PURE__ */ jsx6(Text4, { color: "cyan", children: key }),
|
|
722
|
+
/* @__PURE__ */ jsx6(Text4, { dimColor: true, children: desc })
|
|
723
|
+
] }, `${segment}-${i}`);
|
|
724
|
+
}) }) });
|
|
725
|
+
}
|
|
726
|
+
var init_chrome = __esm({
|
|
727
|
+
"src/tui/chrome.tsx"() {
|
|
728
|
+
"use strict";
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
// src/tui/JobRow.tsx
|
|
733
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
734
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
735
|
+
function JobRow({
|
|
736
|
+
item,
|
|
737
|
+
selected,
|
|
738
|
+
spinnerFrame
|
|
739
|
+
}) {
|
|
740
|
+
const style = statusStyle(item.status);
|
|
741
|
+
const glyph = statusGlyph(item.status, spinnerFrame);
|
|
742
|
+
const title = item.recording?.title ?? item.recordingId;
|
|
743
|
+
return /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
744
|
+
/* @__PURE__ */ jsx7(Box5, { width: 3, children: /* @__PURE__ */ jsx7(Text5, { color: "cyan", children: selected ? "\u25B8" : "" }) }),
|
|
745
|
+
/* @__PURE__ */ jsx7(Box5, { width: 2, children: /* @__PURE__ */ jsx7(Text5, { color: style.color, children: glyph }) }),
|
|
746
|
+
/* @__PURE__ */ jsx7(Box5, { width: 13, children: /* @__PURE__ */ jsx7(Text5, { color: style.color, children: style.label }) }),
|
|
747
|
+
/* @__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) })
|
|
645
749
|
] });
|
|
646
750
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
751
|
+
var init_JobRow = __esm({
|
|
752
|
+
"src/tui/JobRow.tsx"() {
|
|
753
|
+
"use strict";
|
|
754
|
+
init_format();
|
|
651
755
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
// src/tui/JobsView.tsx
|
|
759
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
760
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
761
|
+
function JobsView({
|
|
762
|
+
items,
|
|
763
|
+
selectedIndex,
|
|
764
|
+
spinnerFrame
|
|
765
|
+
}) {
|
|
766
|
+
if (items.length === 0) {
|
|
767
|
+
return /* @__PURE__ */ jsx8(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text6, { dimColor: true, children: "No transcription jobs yet \u2014 run: recappi upload <file> --transcribe" }) });
|
|
655
768
|
}
|
|
656
|
-
return
|
|
769
|
+
return /* @__PURE__ */ jsx8(Box6, { marginTop: 1, flexDirection: "column", children: items.map((item, index) => /* @__PURE__ */ jsx8(
|
|
770
|
+
JobRow,
|
|
771
|
+
{
|
|
772
|
+
item,
|
|
773
|
+
selected: index === selectedIndex,
|
|
774
|
+
spinnerFrame
|
|
775
|
+
},
|
|
776
|
+
item.jobId
|
|
777
|
+
)) });
|
|
657
778
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
canTranscribe = false,
|
|
663
|
-
canPause = false,
|
|
664
|
-
now = () => Date.now()
|
|
665
|
-
}) {
|
|
666
|
-
const size = useTerminalSize();
|
|
667
|
-
const [tick, setTick] = useState3(() => now());
|
|
668
|
-
const [waveSys, setWaveSys] = useState3([]);
|
|
669
|
-
const [waveMic, setWaveMic] = useState3([]);
|
|
670
|
-
const [captionMode, setCaptionMode] = useState3("both");
|
|
671
|
-
const lastAppendRef = useRef(0);
|
|
672
|
-
useInput2((input) => {
|
|
673
|
-
if (input === "c") {
|
|
674
|
-
setCaptionMode((m) => m === "both" ? "source" : m === "source" ? "translation" : "both");
|
|
675
|
-
}
|
|
676
|
-
});
|
|
677
|
-
useEffect2(() => {
|
|
678
|
-
if (telemetry.level == null) return;
|
|
679
|
-
const t = now();
|
|
680
|
-
if (t - lastAppendRef.current < WAVE_THROTTLE_MS) return;
|
|
681
|
-
lastAppendRef.current = t;
|
|
682
|
-
setWaveSys((w) => [...w.slice(-512), telemetry.level.system ?? 0]);
|
|
683
|
-
setWaveMic((w) => [...w.slice(-512), telemetry.level.mic ?? 0]);
|
|
684
|
-
}, [telemetry.level]);
|
|
685
|
-
useEffect2(() => {
|
|
686
|
-
const id = setInterval(() => setTick(now()), 1e3);
|
|
687
|
-
return () => clearInterval(id);
|
|
688
|
-
}, []);
|
|
689
|
-
const elapsed = telemetry.startedAtMs != null ? formatClockMs(Math.max(0, tick - telemetry.startedAtMs)) : "00:00";
|
|
690
|
-
const innerWidth = Math.max(10, size.columns - 4);
|
|
691
|
-
if (telemetry.status === "stopped") {
|
|
692
|
-
const handoff = stoppedHandoffCopy(artifact, canTranscribe);
|
|
693
|
-
const phase = stoppedPhase(artifact);
|
|
694
|
-
const meta3 = [
|
|
695
|
-
telemetry.durationMs != null ? formatClockMs(telemetry.durationMs) : null,
|
|
696
|
-
formatBytes2(telemetry.sizeBytes) || null
|
|
697
|
-
].filter(Boolean).join(" \xB7 ");
|
|
698
|
-
const saved = artifact?.uploadStatus === "uploaded" ? "\u2713 Saved to Recappi Cloud" : "\u2713 Saved to your Mac";
|
|
699
|
-
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
700
|
-
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "recappi \xB7 Recording" }),
|
|
701
|
-
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
702
|
-
/* @__PURE__ */ jsx3(Text2, { color: "green", children: saved }),
|
|
703
|
-
meta3 ? /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: meta3 }) : null,
|
|
704
|
-
telemetry.savedPath ? /* @__PURE__ */ jsx3(Text2, { dimColor: true, wrap: "truncate-middle", children: telemetry.savedPath }) : null
|
|
705
|
-
] }),
|
|
706
|
-
phase ? /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
|
|
707
|
-
/* @__PURE__ */ jsx3(Text2, { color: "cyan", children: `\u25D0 ${phase.label}` }),
|
|
708
|
-
phase.fraction != null ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
709
|
-
/* @__PURE__ */ jsx3(Text2, { children: " " }),
|
|
710
|
-
/* @__PURE__ */ jsx3(ProgressBar, { fraction: phase.fraction }),
|
|
711
|
-
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: ` ${Math.round(phase.fraction * 100)}%` })
|
|
712
|
-
] }) : /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "\u2026" })
|
|
713
|
-
] }) : null,
|
|
714
|
-
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
715
|
-
/* @__PURE__ */ jsx3(Text2, { color: handoff.tone === "red" ? "red" : handoff.tone === "green" ? "green" : void 0, dimColor: handoff.tone === "dim", children: handoff.text }),
|
|
716
|
-
artifact?.error ? /* @__PURE__ */ jsx3(Text2, { color: "red", wrap: "truncate-end", children: artifact.error }) : null
|
|
717
|
-
] })
|
|
718
|
-
] });
|
|
719
|
-
}
|
|
720
|
-
if (telemetry.status === "error") {
|
|
721
|
-
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
722
|
-
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "recappi \xB7 Recording" }),
|
|
723
|
-
/* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text2, { color: "red", children: telemetry.error ? `Recording error: ${telemetry.error}` : "Recording error" }) }),
|
|
724
|
-
/* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "esc back" }) })
|
|
725
|
-
] });
|
|
779
|
+
var init_JobsView = __esm({
|
|
780
|
+
"src/tui/JobsView.tsx"() {
|
|
781
|
+
"use strict";
|
|
782
|
+
init_JobRow();
|
|
726
783
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
const captionRows = Math.max(2, size.rows - fixedRows);
|
|
737
|
-
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
738
|
-
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
739
|
-
/* @__PURE__ */ jsx3(Text2, { bold: true, color: "green", children: "recappi" }),
|
|
740
|
-
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: " \xB7 Recording" })
|
|
741
|
-
] }),
|
|
742
|
-
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, paddingX: 1, flexDirection: "column", children: [
|
|
743
|
-
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
744
|
-
/* @__PURE__ */ jsx3(Text2, { bold: true, color: paused ? "yellow" : "red", children: badge }),
|
|
745
|
-
/* @__PURE__ */ jsx3(Text2, { children: " " }),
|
|
746
|
-
/* @__PURE__ */ jsx3(Text2, { bold: true, children: elapsed })
|
|
747
|
-
] }),
|
|
748
|
-
/* @__PURE__ */ jsx3(Box2, { marginTop: 1, flexDirection: "column", children: telemetry.level == null ? (
|
|
749
|
-
// No level telemetry yet — honest activity, not a flat meter that
|
|
750
|
-
// reads as silence (the elapsed timer above proves it's live).
|
|
751
|
-
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: paused ? "Paused" : `Capturing audio${".".repeat(Math.floor(tick / 1e3) % 3 + 1)}` })
|
|
752
|
-
) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
753
|
-
/* @__PURE__ */ jsx3(MeterRow, { label: "System", samples: waveSys, level: telemetry.level.system ?? 0, paused, width: meterW, rows: waveRows }),
|
|
754
|
-
telemetry.micEnabled ? /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx3(MeterRow, { label: "Mic", samples: waveMic, level: telemetry.level.mic ?? 0, paused, width: meterW, rows: waveRows }) }) : null
|
|
755
|
-
] }) }),
|
|
756
|
-
/* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: context }) }),
|
|
757
|
-
captions ? /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
758
|
-
/* @__PURE__ */ jsx3(Text2, { bold: true, dimColor: true, children: "LIVE CAPTIONS" }),
|
|
759
|
-
/* @__PURE__ */ jsx3(HeroCaptions, { state: captions, maxRows: captionRows, width: innerWidth, mode: captionMode })
|
|
760
|
-
] }) : null
|
|
761
|
-
] }),
|
|
762
|
-
/* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
763
|
-
"q stop & save",
|
|
764
|
-
canPause ? ` \xB7 p ${paused ? "resume" : "pause"}` : "",
|
|
765
|
-
captions ? " \xB7 c captions" : ""
|
|
766
|
-
] }) })
|
|
767
|
-
] });
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
// src/tui/RecordingRow.tsx
|
|
787
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
788
|
+
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
789
|
+
function recordingTitle2(item) {
|
|
790
|
+
const named = (item.title || item.summaryTitle || "").trim();
|
|
791
|
+
if (named && !UUID_RE.test(named)) return named;
|
|
792
|
+
return "Untitled";
|
|
768
793
|
}
|
|
769
|
-
function
|
|
770
|
-
|
|
794
|
+
function recordingProcessingState(item, jobStatus, spinnerFrame) {
|
|
795
|
+
if (item.status === "uploading") return { glyph: "\u2191", color: "cyan" };
|
|
796
|
+
if (item.status === "failed" || jobStatus === "failed") return { glyph: "\u2717", color: "red" };
|
|
797
|
+
if (jobStatus === "running") return { glyph: spinnerChar(spinnerFrame), color: "cyan" };
|
|
798
|
+
if (jobStatus === "queued") return { glyph: "\u25CB", color: "yellow" };
|
|
799
|
+
if (item.status === "aborted") return { glyph: "\u2022", color: "gray" };
|
|
800
|
+
if (item.activeTranscriptId) return { glyph: "\u2713", color: "green" };
|
|
801
|
+
return { glyph: "\xB7", color: "gray" };
|
|
771
802
|
}
|
|
772
|
-
function
|
|
773
|
-
const
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
used += h;
|
|
781
|
-
}
|
|
782
|
-
return chosen.map((it) => /* @__PURE__ */ jsx3(Text2, { dimColor: dim, wrap: "wrap", children: it.text }, it.key));
|
|
803
|
+
function recordingLayout(columns) {
|
|
804
|
+
const usable = Math.max(20, columns - 2);
|
|
805
|
+
const showWhen = usable >= 54;
|
|
806
|
+
const title = Math.max(
|
|
807
|
+
10,
|
|
808
|
+
usable - MARKER_W - GLYPH_W - LENGTH_W - (showWhen ? WHEN_W : 0) - DL_W
|
|
809
|
+
);
|
|
810
|
+
return { title, showWhen };
|
|
783
811
|
}
|
|
784
|
-
function
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
812
|
+
function RecordingRow({
|
|
813
|
+
item,
|
|
814
|
+
selected,
|
|
815
|
+
nowMs,
|
|
816
|
+
columns,
|
|
817
|
+
jobStatus,
|
|
818
|
+
spinnerFrame = 0,
|
|
819
|
+
downloaded = false
|
|
789
820
|
}) {
|
|
790
|
-
const
|
|
791
|
-
const
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
const translationItems = state.lines.filter((l) => l.translation).map((l) => ({ key: `${l.id}-t`, text: trimLead(l.translation) }));
|
|
801
|
-
if (state.translationPartial) translationItems.push({ key: "tp", text: trimLead(state.translationPartial) });
|
|
802
|
-
const errLine = captionError ? /* @__PURE__ */ jsx3(Text2, { color: "yellow", wrap: "wrap", children: captionError }) : null;
|
|
803
|
-
const hasTranslation = translationItems.length > 0;
|
|
804
|
-
if (mode === "source" || !hasTranslation) {
|
|
805
|
-
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
806
|
-
captionColumn(sourceItems, maxRows, width, false),
|
|
807
|
-
errLine
|
|
808
|
-
] });
|
|
809
|
-
}
|
|
810
|
-
if (mode === "translation") {
|
|
811
|
-
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
812
|
-
captionColumn(translationItems, maxRows, width, false),
|
|
813
|
-
errLine
|
|
814
|
-
] });
|
|
815
|
-
}
|
|
816
|
-
const gap = 2;
|
|
817
|
-
const colW = Math.max(12, Math.floor((width - gap) / 2));
|
|
818
|
-
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
819
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", children: [
|
|
820
|
-
/* @__PURE__ */ jsxs2(Box2, { width: colW, flexDirection: "column", marginRight: gap, children: [
|
|
821
|
-
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "ORIGINAL" }),
|
|
822
|
-
captionColumn(sourceItems, Math.max(1, maxRows - 1), colW, false)
|
|
823
|
-
] }),
|
|
824
|
-
/* @__PURE__ */ jsxs2(Box2, { width: colW, flexDirection: "column", children: [
|
|
825
|
-
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "TRANSLATION" }),
|
|
826
|
-
captionColumn(translationItems, Math.max(1, maxRows - 1), colW, false)
|
|
827
|
-
] })
|
|
828
|
-
] }),
|
|
829
|
-
errLine
|
|
821
|
+
const { title, showWhen } = recordingLayout(columns);
|
|
822
|
+
const { glyph, color } = recordingProcessingState(item, jobStatus, spinnerFrame);
|
|
823
|
+
const duration3 = item.durationMs ? formatClockMs(item.durationMs) : "\u2014";
|
|
824
|
+
return /* @__PURE__ */ jsxs6(Box7, { children: [
|
|
825
|
+
/* @__PURE__ */ jsx9(Box7, { width: MARKER_W, children: /* @__PURE__ */ jsx9(Text7, { color: "cyan", children: selected ? "\u25B8" : "" }) }),
|
|
826
|
+
/* @__PURE__ */ jsx9(Box7, { width: GLYPH_W, children: /* @__PURE__ */ jsx9(Text7, { color, children: glyph }) }),
|
|
827
|
+
/* @__PURE__ */ jsx9(Box7, { width: title, children: /* @__PURE__ */ jsx9(Text7, { bold: selected, wrap: "truncate-end", children: recordingTitle2(item) }) }),
|
|
828
|
+
/* @__PURE__ */ jsx9(Box7, { width: LENGTH_W, justifyContent: "flex-end", children: /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: duration3 }) }),
|
|
829
|
+
showWhen ? /* @__PURE__ */ jsx9(Box7, { width: WHEN_W, justifyContent: "flex-end", children: /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: formatAge(item.createdAt, nowMs) }) }) : null,
|
|
830
|
+
/* @__PURE__ */ jsx9(Box7, { width: DL_W, justifyContent: "flex-end", children: /* @__PURE__ */ jsx9(Text7, { color: "green", children: downloaded ? "\u2913" : "" }) })
|
|
830
831
|
] });
|
|
831
832
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
return { text: "Transcription failed \xB7 \u23CE retry \xB7 n not now", tone: "red" };
|
|
833
|
+
var UUID_RE, MARKER_W, GLYPH_W, LENGTH_W, WHEN_W, DL_W;
|
|
834
|
+
var init_RecordingRow = __esm({
|
|
835
|
+
"src/tui/RecordingRow.tsx"() {
|
|
836
|
+
"use strict";
|
|
837
|
+
init_format();
|
|
838
|
+
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
839
|
+
MARKER_W = 3;
|
|
840
|
+
GLYPH_W = 2;
|
|
841
|
+
LENGTH_W = 8;
|
|
842
|
+
WHEN_W = 9;
|
|
843
|
+
DL_W = 3;
|
|
844
844
|
}
|
|
845
|
-
|
|
846
|
-
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
// src/tui/RecordingsView.tsx
|
|
848
|
+
import React5 from "react";
|
|
849
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
850
|
+
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
851
|
+
function RecordingsView({
|
|
852
|
+
items,
|
|
853
|
+
selectedIndex,
|
|
854
|
+
nowMs,
|
|
855
|
+
columns,
|
|
856
|
+
jobStatusByRecording,
|
|
857
|
+
downloadedRecordingIds,
|
|
858
|
+
spinnerFrame = 0
|
|
859
|
+
}) {
|
|
860
|
+
if (items.length === 0) {
|
|
861
|
+
return /* @__PURE__ */ jsx10(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: "No recordings yet \u2014 run: recappi upload <file>" }) });
|
|
847
862
|
}
|
|
848
|
-
return {
|
|
863
|
+
return /* @__PURE__ */ jsx10(Box8, { marginTop: 1, flexDirection: "column", children: items.map((item, index) => {
|
|
864
|
+
const bucket = dateBucket(item.createdAt, nowMs);
|
|
865
|
+
const showHeader = index === 0 || bucket !== dateBucket(items[index - 1].createdAt, nowMs);
|
|
866
|
+
return /* @__PURE__ */ jsxs7(React5.Fragment, { children: [
|
|
867
|
+
showHeader ? /* @__PURE__ */ jsx10(Box8, { marginTop: index === 0 ? 0 : 1, children: /* @__PURE__ */ jsx10(Text8, { bold: true, dimColor: true, children: bucket }) }) : null,
|
|
868
|
+
/* @__PURE__ */ jsx10(
|
|
869
|
+
RecordingRow,
|
|
870
|
+
{
|
|
871
|
+
item,
|
|
872
|
+
selected: index === selectedIndex,
|
|
873
|
+
nowMs,
|
|
874
|
+
columns,
|
|
875
|
+
jobStatus: jobStatusByRecording?.get(item.recordingId),
|
|
876
|
+
downloaded: downloadedRecordingIds?.has(item.recordingId) ?? false,
|
|
877
|
+
spinnerFrame
|
|
878
|
+
}
|
|
879
|
+
)
|
|
880
|
+
] }, item.recordingId);
|
|
881
|
+
}) });
|
|
849
882
|
}
|
|
850
|
-
var
|
|
851
|
-
|
|
852
|
-
"src/tui/RecordingHeroScreen.tsx"() {
|
|
883
|
+
var init_RecordingsView = __esm({
|
|
884
|
+
"src/tui/RecordingsView.tsx"() {
|
|
853
885
|
"use strict";
|
|
886
|
+
init_RecordingRow();
|
|
854
887
|
init_format();
|
|
855
|
-
init_liveCaptions();
|
|
856
|
-
init_terminal();
|
|
857
|
-
WAVE_THROTTLE_MS = 220;
|
|
858
|
-
trimLead = (s) => s.replace(/^\s+/, "");
|
|
859
888
|
}
|
|
860
889
|
});
|
|
861
890
|
|
|
862
|
-
// src/tui/
|
|
863
|
-
import { Box as
|
|
864
|
-
import { Fragment as
|
|
865
|
-
function
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
/* @__PURE__ */ jsx5(Text3, { color: "yellow", children: "Not signed in" }),
|
|
873
|
-
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: `origin ${status.origin}` }),
|
|
874
|
-
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "Run `recappi auth login` to sign in." })
|
|
875
|
-
] }) : /* @__PURE__ */ jsx5(AccountBody, { status, nowMs }),
|
|
876
|
-
/* @__PURE__ */ jsx5(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "r refresh \xB7 esc back \xB7 q quit" }) })
|
|
877
|
-
] });
|
|
891
|
+
// src/tui/RecordingPeek.tsx
|
|
892
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
893
|
+
import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
894
|
+
function RecordingPeek({
|
|
895
|
+
item,
|
|
896
|
+
summary,
|
|
897
|
+
nowMs,
|
|
898
|
+
width
|
|
899
|
+
}) {
|
|
900
|
+
return /* @__PURE__ */ jsx11(Box9, { width, borderStyle: "round", borderColor: "gray", paddingX: 1, flexDirection: "column", children: !item ? /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "No selection" }) : /* @__PURE__ */ jsx11(PeekBody, { item, summary, nowMs }) });
|
|
878
901
|
}
|
|
879
|
-
function
|
|
880
|
-
|
|
902
|
+
function PeekBody({
|
|
903
|
+
item,
|
|
904
|
+
summary,
|
|
881
905
|
nowMs
|
|
882
906
|
}) {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
907
|
+
const style = recordingStatusStyle(item.status);
|
|
908
|
+
const meta3 = [
|
|
909
|
+
item.durationMs ? formatClockMs(item.durationMs) : null,
|
|
910
|
+
formatBytes2(item.sizeBytes) || null,
|
|
911
|
+
formatAge(item.createdAt, nowMs)
|
|
912
|
+
].filter(Boolean).join(" \xB7 ");
|
|
913
|
+
return /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
914
|
+
/* @__PURE__ */ jsx11(Text9, { bold: true, wrap: "truncate-end", children: recordingTitle2(item) }),
|
|
915
|
+
/* @__PURE__ */ jsxs8(Box9, { children: [
|
|
916
|
+
/* @__PURE__ */ jsx11(Text9, { color: style.color, children: `${style.glyph} ${style.label}` }),
|
|
917
|
+
meta3 ? /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: ` ${meta3}` }) : null
|
|
891
918
|
] }),
|
|
892
|
-
|
|
893
|
-
/* @__PURE__ */
|
|
894
|
-
/* @__PURE__ */ jsx5(Text3, { bold: true, dimColor: true, children: "LOCAL STORE" }),
|
|
895
|
-
/* @__PURE__ */ jsx5(Text3, { dimColor: true, wrap: "truncate-middle", children: status.localStore.path }),
|
|
896
|
-
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
897
|
-
`${status.localStore.accountScopedArtifacts} artifact${status.localStore.accountScopedArtifacts === 1 ? "" : "s"} for this account`,
|
|
898
|
-
status.localStore.unattributedArtifacts > 0 ? ` \xB7 ${status.localStore.unattributedArtifacts} unattributed` : ""
|
|
899
|
-
] })
|
|
900
|
-
] })
|
|
919
|
+
/* @__PURE__ */ jsx11(Box9, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx11(SummarySection, { item, summary }) }),
|
|
920
|
+
/* @__PURE__ */ jsx11(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "\u23CE open \xB7 t transcript" }) })
|
|
901
921
|
] });
|
|
902
922
|
}
|
|
903
|
-
function
|
|
904
|
-
|
|
905
|
-
|
|
923
|
+
function SummarySection({
|
|
924
|
+
item,
|
|
925
|
+
summary
|
|
906
926
|
}) {
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
/* @__PURE__ */
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
/* @__PURE__ */
|
|
917
|
-
|
|
918
|
-
minutesCap != null ? /* @__PURE__ */ jsx5(Text3, { color: billing.isOverMinutes ? "red" : "cyan", children: `${progressBar(minutesUsed / Math.max(1, minutesCap), 12)} ` }) : null,
|
|
919
|
-
/* @__PURE__ */ jsx5(Text3, { color: billing.isOverMinutes ? "red" : void 0, children: `${Math.round(minutesUsed)}` }),
|
|
920
|
-
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: ` / ${minutesCap != null ? Math.round(minutesCap) : "\u221E"} min` }),
|
|
921
|
-
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: ` (batch ${Math.round(billing.batchMinutesUsed)} \xB7 live ${Math.round(billing.realtimeMinutesUsed)})` })
|
|
922
|
-
] }),
|
|
923
|
-
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
924
|
-
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "Storage " }),
|
|
925
|
-
storageCap != null ? /* @__PURE__ */ jsx5(Text3, { color: billing.isOverStorage ? "red" : "cyan", children: `${progressBar(billing.storageBytes / Math.max(1, storageCap), 12)} ` }) : null,
|
|
926
|
-
/* @__PURE__ */ jsx5(Text3, { color: billing.isOverStorage ? "red" : void 0, children: formatBytes2(billing.storageBytes) }),
|
|
927
|
-
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: ` / ${storageCap != null ? formatBytes2(storageCap) : "\u221E"}` })
|
|
928
|
-
] }),
|
|
929
|
-
billing.isOverMinutes || billing.isOverStorage ? /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
|
|
930
|
-
billing.isOverMinutes ? "Over minutes limit. " : "",
|
|
931
|
-
billing.isOverStorage ? "Over storage limit." : ""
|
|
932
|
-
] }) : null,
|
|
933
|
-
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: `Period ${periodText(billing, nowMs)}` })
|
|
927
|
+
if (!item.activeTranscriptId) return /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "No transcript yet" });
|
|
928
|
+
if (summary === "loading" || summary === void 0) return /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "Loading summary\u2026" });
|
|
929
|
+
if (summary === "error") return /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "(summary unavailable)" });
|
|
930
|
+
if (summary.status !== "succeeded" || !summary.tldr) {
|
|
931
|
+
return /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: `Summary ${summary.status}` });
|
|
932
|
+
}
|
|
933
|
+
const points = (summary.keyPoints ?? []).slice(0, 3);
|
|
934
|
+
return /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
935
|
+
/* @__PURE__ */ jsx11(Text9, { bold: true, dimColor: true, children: "SUMMARY" }),
|
|
936
|
+
/* @__PURE__ */ jsx11(Text9, { children: summary.tldr }),
|
|
937
|
+
points.length > 0 ? /* @__PURE__ */ jsx11(Box9, { marginTop: 1, flexDirection: "column", children: points.map((point, i) => /* @__PURE__ */ jsx11(Text9, { dimColor: true, wrap: "truncate-end", children: `\u2022 ${point}` }, i)) }) : null
|
|
934
938
|
] });
|
|
935
939
|
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
if (!Number.isFinite(remainingMs) || remainingMs <= 0) return "\u2014";
|
|
939
|
-
const days = Math.floor(remainingMs / 864e5);
|
|
940
|
-
if (days >= 1) return `${days}d left`;
|
|
941
|
-
return `${formatClockMs(remainingMs)} left`;
|
|
942
|
-
}
|
|
943
|
-
function epochToMs(value) {
|
|
944
|
-
return value > 1e12 ? value : value * 1e3;
|
|
945
|
-
}
|
|
946
|
-
var init_AccountView = __esm({
|
|
947
|
-
"src/tui/AccountView.tsx"() {
|
|
940
|
+
var init_RecordingPeek = __esm({
|
|
941
|
+
"src/tui/RecordingPeek.tsx"() {
|
|
948
942
|
"use strict";
|
|
949
943
|
init_format();
|
|
944
|
+
init_RecordingRow();
|
|
950
945
|
}
|
|
951
946
|
});
|
|
952
947
|
|
|
953
|
-
// src/tui/
|
|
954
|
-
import { Box as
|
|
955
|
-
import { jsx as
|
|
956
|
-
function
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
active
|
|
948
|
+
// src/tui/OverviewView.tsx
|
|
949
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
950
|
+
import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
951
|
+
function OverviewView({
|
|
952
|
+
recordings,
|
|
953
|
+
jobs,
|
|
954
|
+
stats,
|
|
955
|
+
selectedIndex,
|
|
956
|
+
spinnerFrame,
|
|
957
|
+
nowMs,
|
|
958
|
+
columns,
|
|
959
|
+
jobStatusByRecording,
|
|
960
|
+
downloadedRecordingIds,
|
|
961
|
+
peekItem,
|
|
962
|
+
peekSummary,
|
|
963
|
+
showPeek = false,
|
|
964
|
+
peekWidth = 0
|
|
971
965
|
}) {
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
return /* @__PURE__ */
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
966
|
+
const jobCounts = countJobs(jobs);
|
|
967
|
+
const running = stats?.jobs.running ?? jobCounts.running;
|
|
968
|
+
const queued = stats?.jobs.queued ?? jobCounts.queued;
|
|
969
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
|
|
970
|
+
/* @__PURE__ */ jsxs9(Box10, { children: [
|
|
971
|
+
/* @__PURE__ */ jsx12(Text10, { bold: true, children: stats?.recordings.total ?? recordings.length }),
|
|
972
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " recordings" }),
|
|
973
|
+
stats?.recordings.ready != null ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
974
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " \xB7 " }),
|
|
975
|
+
/* @__PURE__ */ jsx12(Text10, { color: "green", children: `${stats.recordings.ready} ready` })
|
|
976
|
+
] }) : null,
|
|
977
|
+
running > 0 ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
978
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " \xB7 " }),
|
|
979
|
+
/* @__PURE__ */ jsx12(Text10, { color: "cyan", children: `${running} transcribing` })
|
|
980
|
+
] }) : null,
|
|
981
|
+
queued > 0 ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
982
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " \xB7 " }),
|
|
983
|
+
/* @__PURE__ */ jsx12(Text10, { color: "yellow", children: `${queued} queued` })
|
|
984
|
+
] }) : null,
|
|
985
|
+
stats?.recordings.totalDurationMs != null ? /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: ` \xB7 ${formatClockMs(stats.recordings.totalDurationMs)} transcribed` }) : null
|
|
986
|
+
] }),
|
|
987
|
+
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", alignItems: "flex-start", children: [
|
|
988
|
+
/* @__PURE__ */ jsx12(Box10, { flexGrow: 1, flexDirection: "column", children: /* @__PURE__ */ jsx12(
|
|
989
|
+
RecordingsView,
|
|
990
|
+
{
|
|
991
|
+
items: recordings,
|
|
992
|
+
selectedIndex,
|
|
993
|
+
nowMs,
|
|
994
|
+
columns: showPeek ? Math.max(20, columns - peekWidth - 1) : columns,
|
|
995
|
+
jobStatusByRecording,
|
|
996
|
+
downloadedRecordingIds,
|
|
997
|
+
spinnerFrame
|
|
998
|
+
}
|
|
999
|
+
) }),
|
|
1000
|
+
showPeek ? /* @__PURE__ */ jsx12(Box10, { marginLeft: 1, marginTop: 1, children: /* @__PURE__ */ jsx12(RecordingPeek, { item: peekItem, summary: peekSummary, nowMs, width: peekWidth }) }) : null
|
|
1001
|
+
] })
|
|
979
1002
|
] });
|
|
980
1003
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
return /* @__PURE__ */ jsx6(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text4, { children: segments.map((segment, i) => {
|
|
984
|
-
const space = segment.indexOf(" ");
|
|
985
|
-
const key = space === -1 ? segment : segment.slice(0, space);
|
|
986
|
-
const desc = space === -1 ? "" : segment.slice(space);
|
|
987
|
-
return /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
988
|
-
i > 0 ? /* @__PURE__ */ jsx6(Text4, { dimColor: true, children: " \xB7 " }) : null,
|
|
989
|
-
/* @__PURE__ */ jsx6(Text4, { color: "cyan", children: key }),
|
|
990
|
-
/* @__PURE__ */ jsx6(Text4, { dimColor: true, children: desc })
|
|
991
|
-
] }, `${segment}-${i}`);
|
|
992
|
-
}) }) });
|
|
993
|
-
}
|
|
994
|
-
var init_chrome = __esm({
|
|
995
|
-
"src/tui/chrome.tsx"() {
|
|
1004
|
+
var init_OverviewView = __esm({
|
|
1005
|
+
"src/tui/OverviewView.tsx"() {
|
|
996
1006
|
"use strict";
|
|
1007
|
+
init_RecordingsView();
|
|
1008
|
+
init_RecordingPeek();
|
|
1009
|
+
init_format();
|
|
997
1010
|
}
|
|
998
1011
|
});
|
|
999
1012
|
|
|
1000
|
-
// src/tui/
|
|
1001
|
-
import { Box as
|
|
1002
|
-
import { jsx as
|
|
1003
|
-
function
|
|
1013
|
+
// src/tui/JobDetailView.tsx
|
|
1014
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
1015
|
+
import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1016
|
+
function JobDetailView({
|
|
1004
1017
|
item,
|
|
1005
|
-
|
|
1006
|
-
spinnerFrame
|
|
1018
|
+
origin,
|
|
1019
|
+
spinnerFrame,
|
|
1020
|
+
nowMs
|
|
1007
1021
|
}) {
|
|
1008
1022
|
const style = statusStyle(item.status);
|
|
1009
|
-
const
|
|
1010
|
-
const title = item.recording?.title ?? item.recordingId;
|
|
1011
|
-
return /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1012
|
-
/* @__PURE__ */ jsx7(Box5, { width: 3, children: /* @__PURE__ */ jsx7(Text5, { color: "cyan", children: selected ? "\u25B8" : "" }) }),
|
|
1013
|
-
/* @__PURE__ */ jsx7(Box5, { width: 2, children: /* @__PURE__ */ jsx7(Text5, { color: style.color, children: glyph }) }),
|
|
1014
|
-
/* @__PURE__ */ jsx7(Box5, { width: 13, children: /* @__PURE__ */ jsx7(Text5, { color: style.color, children: style.label }) }),
|
|
1015
|
-
/* @__PURE__ */ jsx7(Box5, { width: 26, children: /* @__PURE__ */ jsx7(Text5, { bold: selected, wrap: "truncate-end", children: title }) }),
|
|
1016
|
-
/* @__PURE__ */ jsx7(Text5, { dimColor: !selected, children: jobDetail(item) })
|
|
1017
|
-
] });
|
|
1018
|
-
}
|
|
1019
|
-
var init_JobRow = __esm({
|
|
1020
|
-
"src/tui/JobRow.tsx"() {
|
|
1021
|
-
"use strict";
|
|
1022
|
-
init_format();
|
|
1023
|
-
}
|
|
1024
|
-
});
|
|
1025
|
-
|
|
1026
|
-
// src/tui/JobsView.tsx
|
|
1027
|
-
import { Box as Box6, Text as Text6 } from "ink";
|
|
1028
|
-
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1029
|
-
function JobsView({
|
|
1030
|
-
items,
|
|
1031
|
-
selectedIndex,
|
|
1032
|
-
spinnerFrame
|
|
1033
|
-
}) {
|
|
1034
|
-
if (items.length === 0) {
|
|
1035
|
-
return /* @__PURE__ */ jsx8(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text6, { dimColor: true, children: "No transcription jobs yet \u2014 run: recappi upload <file> --transcribe" }) });
|
|
1036
|
-
}
|
|
1037
|
-
return /* @__PURE__ */ jsx8(Box6, { marginTop: 1, flexDirection: "column", children: items.map((item, index) => /* @__PURE__ */ jsx8(
|
|
1038
|
-
JobRow,
|
|
1039
|
-
{
|
|
1040
|
-
item,
|
|
1041
|
-
selected: index === selectedIndex,
|
|
1042
|
-
spinnerFrame
|
|
1043
|
-
},
|
|
1044
|
-
item.jobId
|
|
1045
|
-
)) });
|
|
1046
|
-
}
|
|
1047
|
-
var init_JobsView = __esm({
|
|
1048
|
-
"src/tui/JobsView.tsx"() {
|
|
1049
|
-
"use strict";
|
|
1050
|
-
init_JobRow();
|
|
1051
|
-
}
|
|
1052
|
-
});
|
|
1053
|
-
|
|
1054
|
-
// src/tui/RecordingRow.tsx
|
|
1055
|
-
import { Box as Box7, Text as Text7 } from "ink";
|
|
1056
|
-
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1057
|
-
function recordingTitle2(item) {
|
|
1058
|
-
const named = (item.title || item.summaryTitle || "").trim();
|
|
1059
|
-
if (named && !UUID_RE.test(named)) return named;
|
|
1060
|
-
return "Untitled";
|
|
1061
|
-
}
|
|
1062
|
-
function recordingProcessingState(item, jobStatus, spinnerFrame) {
|
|
1063
|
-
if (item.status === "uploading") return { glyph: "\u2191", color: "cyan" };
|
|
1064
|
-
if (item.status === "failed" || jobStatus === "failed") return { glyph: "\u2717", color: "red" };
|
|
1065
|
-
if (jobStatus === "running") return { glyph: spinnerChar(spinnerFrame), color: "cyan" };
|
|
1066
|
-
if (jobStatus === "queued") return { glyph: "\u25CB", color: "yellow" };
|
|
1067
|
-
if (item.status === "aborted") return { glyph: "\u2022", color: "gray" };
|
|
1068
|
-
if (item.activeTranscriptId) return { glyph: "\u2713", color: "green" };
|
|
1069
|
-
return { glyph: "\xB7", color: "gray" };
|
|
1070
|
-
}
|
|
1071
|
-
function recordingLayout(columns) {
|
|
1072
|
-
const usable = Math.max(20, columns - 2);
|
|
1073
|
-
const showWhen = usable >= 54;
|
|
1074
|
-
const title = Math.max(
|
|
1075
|
-
10,
|
|
1076
|
-
usable - MARKER_W - GLYPH_W - LENGTH_W - (showWhen ? WHEN_W : 0) - DL_W
|
|
1077
|
-
);
|
|
1078
|
-
return { title, showWhen };
|
|
1079
|
-
}
|
|
1080
|
-
function RecordingRow({
|
|
1081
|
-
item,
|
|
1082
|
-
selected,
|
|
1083
|
-
nowMs,
|
|
1084
|
-
columns,
|
|
1085
|
-
jobStatus,
|
|
1086
|
-
spinnerFrame = 0,
|
|
1087
|
-
downloaded = false
|
|
1088
|
-
}) {
|
|
1089
|
-
const { title, showWhen } = recordingLayout(columns);
|
|
1090
|
-
const { glyph, color } = recordingProcessingState(item, jobStatus, spinnerFrame);
|
|
1091
|
-
const duration3 = item.durationMs ? formatClockMs(item.durationMs) : "\u2014";
|
|
1092
|
-
return /* @__PURE__ */ jsxs6(Box7, { children: [
|
|
1093
|
-
/* @__PURE__ */ jsx9(Box7, { width: MARKER_W, children: /* @__PURE__ */ jsx9(Text7, { color: "cyan", children: selected ? "\u25B8" : "" }) }),
|
|
1094
|
-
/* @__PURE__ */ jsx9(Box7, { width: GLYPH_W, children: /* @__PURE__ */ jsx9(Text7, { color, children: glyph }) }),
|
|
1095
|
-
/* @__PURE__ */ jsx9(Box7, { width: title, children: /* @__PURE__ */ jsx9(Text7, { bold: selected, wrap: "truncate-end", children: recordingTitle2(item) }) }),
|
|
1096
|
-
/* @__PURE__ */ jsx9(Box7, { width: LENGTH_W, justifyContent: "flex-end", children: /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: duration3 }) }),
|
|
1097
|
-
showWhen ? /* @__PURE__ */ jsx9(Box7, { width: WHEN_W, justifyContent: "flex-end", children: /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: formatAge(item.createdAt, nowMs) }) }) : null,
|
|
1098
|
-
/* @__PURE__ */ jsx9(Box7, { width: DL_W, justifyContent: "flex-end", children: /* @__PURE__ */ jsx9(Text7, { color: "green", children: downloaded ? "\u2913" : "" }) })
|
|
1099
|
-
] });
|
|
1100
|
-
}
|
|
1101
|
-
var UUID_RE, MARKER_W, GLYPH_W, LENGTH_W, WHEN_W, DL_W;
|
|
1102
|
-
var init_RecordingRow = __esm({
|
|
1103
|
-
"src/tui/RecordingRow.tsx"() {
|
|
1104
|
-
"use strict";
|
|
1105
|
-
init_format();
|
|
1106
|
-
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1107
|
-
MARKER_W = 3;
|
|
1108
|
-
GLYPH_W = 2;
|
|
1109
|
-
LENGTH_W = 8;
|
|
1110
|
-
WHEN_W = 9;
|
|
1111
|
-
DL_W = 3;
|
|
1112
|
-
}
|
|
1113
|
-
});
|
|
1114
|
-
|
|
1115
|
-
// src/tui/RecordingsView.tsx
|
|
1116
|
-
import React5 from "react";
|
|
1117
|
-
import { Box as Box8, Text as Text8 } from "ink";
|
|
1118
|
-
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1119
|
-
function RecordingsView({
|
|
1120
|
-
items,
|
|
1121
|
-
selectedIndex,
|
|
1122
|
-
nowMs,
|
|
1123
|
-
columns,
|
|
1124
|
-
jobStatusByRecording,
|
|
1125
|
-
downloadedRecordingIds,
|
|
1126
|
-
spinnerFrame = 0
|
|
1127
|
-
}) {
|
|
1128
|
-
if (items.length === 0) {
|
|
1129
|
-
return /* @__PURE__ */ jsx10(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: "No recordings yet \u2014 run: recappi upload <file>" }) });
|
|
1130
|
-
}
|
|
1131
|
-
return /* @__PURE__ */ jsx10(Box8, { marginTop: 1, flexDirection: "column", children: items.map((item, index) => {
|
|
1132
|
-
const bucket = dateBucket(item.createdAt, nowMs);
|
|
1133
|
-
const showHeader = index === 0 || bucket !== dateBucket(items[index - 1].createdAt, nowMs);
|
|
1134
|
-
return /* @__PURE__ */ jsxs7(React5.Fragment, { children: [
|
|
1135
|
-
showHeader ? /* @__PURE__ */ jsx10(Box8, { marginTop: index === 0 ? 0 : 1, children: /* @__PURE__ */ jsx10(Text8, { bold: true, dimColor: true, children: bucket }) }) : null,
|
|
1136
|
-
/* @__PURE__ */ jsx10(
|
|
1137
|
-
RecordingRow,
|
|
1138
|
-
{
|
|
1139
|
-
item,
|
|
1140
|
-
selected: index === selectedIndex,
|
|
1141
|
-
nowMs,
|
|
1142
|
-
columns,
|
|
1143
|
-
jobStatus: jobStatusByRecording?.get(item.recordingId),
|
|
1144
|
-
downloaded: downloadedRecordingIds?.has(item.recordingId) ?? false,
|
|
1145
|
-
spinnerFrame
|
|
1146
|
-
}
|
|
1147
|
-
)
|
|
1148
|
-
] }, item.recordingId);
|
|
1149
|
-
}) });
|
|
1150
|
-
}
|
|
1151
|
-
var init_RecordingsView = __esm({
|
|
1152
|
-
"src/tui/RecordingsView.tsx"() {
|
|
1153
|
-
"use strict";
|
|
1154
|
-
init_RecordingRow();
|
|
1155
|
-
init_format();
|
|
1156
|
-
}
|
|
1157
|
-
});
|
|
1158
|
-
|
|
1159
|
-
// src/tui/RecordingPeek.tsx
|
|
1160
|
-
import { Box as Box9, Text as Text9 } from "ink";
|
|
1161
|
-
import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1162
|
-
function RecordingPeek({
|
|
1163
|
-
item,
|
|
1164
|
-
summary,
|
|
1165
|
-
nowMs,
|
|
1166
|
-
width
|
|
1167
|
-
}) {
|
|
1168
|
-
return /* @__PURE__ */ jsx11(Box9, { width, borderStyle: "round", borderColor: "gray", paddingX: 1, flexDirection: "column", children: !item ? /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "No selection" }) : /* @__PURE__ */ jsx11(PeekBody, { item, summary, nowMs }) });
|
|
1169
|
-
}
|
|
1170
|
-
function PeekBody({
|
|
1171
|
-
item,
|
|
1172
|
-
summary,
|
|
1173
|
-
nowMs
|
|
1174
|
-
}) {
|
|
1175
|
-
const style = recordingStatusStyle(item.status);
|
|
1176
|
-
const meta3 = [
|
|
1177
|
-
item.durationMs ? formatClockMs(item.durationMs) : null,
|
|
1178
|
-
formatBytes2(item.sizeBytes) || null,
|
|
1179
|
-
formatAge(item.createdAt, nowMs)
|
|
1180
|
-
].filter(Boolean).join(" \xB7 ");
|
|
1181
|
-
return /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
1182
|
-
/* @__PURE__ */ jsx11(Text9, { bold: true, wrap: "truncate-end", children: recordingTitle2(item) }),
|
|
1183
|
-
/* @__PURE__ */ jsxs8(Box9, { children: [
|
|
1184
|
-
/* @__PURE__ */ jsx11(Text9, { color: style.color, children: `${style.glyph} ${style.label}` }),
|
|
1185
|
-
meta3 ? /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: ` ${meta3}` }) : null
|
|
1186
|
-
] }),
|
|
1187
|
-
/* @__PURE__ */ jsx11(Box9, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx11(SummarySection, { item, summary }) }),
|
|
1188
|
-
/* @__PURE__ */ jsx11(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "\u23CE open \xB7 t transcript" }) })
|
|
1189
|
-
] });
|
|
1190
|
-
}
|
|
1191
|
-
function SummarySection({
|
|
1192
|
-
item,
|
|
1193
|
-
summary
|
|
1194
|
-
}) {
|
|
1195
|
-
if (!item.activeTranscriptId) return /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "No transcript yet" });
|
|
1196
|
-
if (summary === "loading" || summary === void 0) return /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "Loading summary\u2026" });
|
|
1197
|
-
if (summary === "error") return /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "(summary unavailable)" });
|
|
1198
|
-
if (summary.status !== "succeeded" || !summary.tldr) {
|
|
1199
|
-
return /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: `Summary ${summary.status}` });
|
|
1200
|
-
}
|
|
1201
|
-
const points = (summary.keyPoints ?? []).slice(0, 3);
|
|
1202
|
-
return /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
1203
|
-
/* @__PURE__ */ jsx11(Text9, { bold: true, dimColor: true, children: "SUMMARY" }),
|
|
1204
|
-
/* @__PURE__ */ jsx11(Text9, { children: summary.tldr }),
|
|
1205
|
-
points.length > 0 ? /* @__PURE__ */ jsx11(Box9, { marginTop: 1, flexDirection: "column", children: points.map((point, i) => /* @__PURE__ */ jsx11(Text9, { dimColor: true, wrap: "truncate-end", children: `\u2022 ${point}` }, i)) }) : null
|
|
1206
|
-
] });
|
|
1207
|
-
}
|
|
1208
|
-
var init_RecordingPeek = __esm({
|
|
1209
|
-
"src/tui/RecordingPeek.tsx"() {
|
|
1210
|
-
"use strict";
|
|
1211
|
-
init_format();
|
|
1212
|
-
init_RecordingRow();
|
|
1213
|
-
}
|
|
1214
|
-
});
|
|
1215
|
-
|
|
1216
|
-
// src/tui/OverviewView.tsx
|
|
1217
|
-
import { Box as Box10, Text as Text10 } from "ink";
|
|
1218
|
-
import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1219
|
-
function OverviewView({
|
|
1220
|
-
recordings,
|
|
1221
|
-
jobs,
|
|
1222
|
-
stats,
|
|
1223
|
-
selectedIndex,
|
|
1224
|
-
spinnerFrame,
|
|
1225
|
-
nowMs,
|
|
1226
|
-
columns,
|
|
1227
|
-
jobStatusByRecording,
|
|
1228
|
-
downloadedRecordingIds,
|
|
1229
|
-
peekItem,
|
|
1230
|
-
peekSummary,
|
|
1231
|
-
showPeek = false,
|
|
1232
|
-
peekWidth = 0
|
|
1233
|
-
}) {
|
|
1234
|
-
const jobCounts = countJobs(jobs);
|
|
1235
|
-
const running = stats?.jobs.running ?? jobCounts.running;
|
|
1236
|
-
const queued = stats?.jobs.queued ?? jobCounts.queued;
|
|
1237
|
-
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
|
|
1238
|
-
/* @__PURE__ */ jsxs9(Box10, { children: [
|
|
1239
|
-
/* @__PURE__ */ jsx12(Text10, { bold: true, children: stats?.recordings.total ?? recordings.length }),
|
|
1240
|
-
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " recordings" }),
|
|
1241
|
-
stats?.recordings.ready != null ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
1242
|
-
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " \xB7 " }),
|
|
1243
|
-
/* @__PURE__ */ jsx12(Text10, { color: "green", children: `${stats.recordings.ready} ready` })
|
|
1244
|
-
] }) : null,
|
|
1245
|
-
running > 0 ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
1246
|
-
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " \xB7 " }),
|
|
1247
|
-
/* @__PURE__ */ jsx12(Text10, { color: "cyan", children: `${running} transcribing` })
|
|
1248
|
-
] }) : null,
|
|
1249
|
-
queued > 0 ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
1250
|
-
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " \xB7 " }),
|
|
1251
|
-
/* @__PURE__ */ jsx12(Text10, { color: "yellow", children: `${queued} queued` })
|
|
1252
|
-
] }) : null,
|
|
1253
|
-
stats?.recordings.totalDurationMs != null ? /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: ` \xB7 ${formatClockMs(stats.recordings.totalDurationMs)} transcribed` }) : null
|
|
1254
|
-
] }),
|
|
1255
|
-
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", alignItems: "flex-start", children: [
|
|
1256
|
-
/* @__PURE__ */ jsx12(Box10, { flexGrow: 1, flexDirection: "column", children: /* @__PURE__ */ jsx12(
|
|
1257
|
-
RecordingsView,
|
|
1258
|
-
{
|
|
1259
|
-
items: recordings,
|
|
1260
|
-
selectedIndex,
|
|
1261
|
-
nowMs,
|
|
1262
|
-
columns: showPeek ? Math.max(20, columns - peekWidth - 1) : columns,
|
|
1263
|
-
jobStatusByRecording,
|
|
1264
|
-
downloadedRecordingIds,
|
|
1265
|
-
spinnerFrame
|
|
1266
|
-
}
|
|
1267
|
-
) }),
|
|
1268
|
-
showPeek ? /* @__PURE__ */ jsx12(Box10, { marginLeft: 1, marginTop: 1, children: /* @__PURE__ */ jsx12(RecordingPeek, { item: peekItem, summary: peekSummary, nowMs, width: peekWidth }) }) : null
|
|
1269
|
-
] })
|
|
1270
|
-
] });
|
|
1271
|
-
}
|
|
1272
|
-
var init_OverviewView = __esm({
|
|
1273
|
-
"src/tui/OverviewView.tsx"() {
|
|
1274
|
-
"use strict";
|
|
1275
|
-
init_RecordingsView();
|
|
1276
|
-
init_RecordingPeek();
|
|
1277
|
-
init_format();
|
|
1278
|
-
}
|
|
1279
|
-
});
|
|
1280
|
-
|
|
1281
|
-
// src/tui/JobDetailView.tsx
|
|
1282
|
-
import { Box as Box11, Text as Text11 } from "ink";
|
|
1283
|
-
import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1284
|
-
function JobDetailView({
|
|
1285
|
-
item,
|
|
1286
|
-
origin,
|
|
1287
|
-
spinnerFrame,
|
|
1288
|
-
nowMs
|
|
1289
|
-
}) {
|
|
1290
|
-
const style = statusStyle(item.status);
|
|
1291
|
-
const links = resolveJobLinks(item, origin);
|
|
1023
|
+
const links = resolveJobLinks(item, origin);
|
|
1292
1024
|
const title = item.recording?.title ?? item.recordingId;
|
|
1293
1025
|
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingX: 1, children: [
|
|
1294
1026
|
/* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
|
|
@@ -1374,9 +1106,9 @@ function StatusLine({
|
|
|
1374
1106
|
const fraction = transcribeFraction(item);
|
|
1375
1107
|
const elapsed = item.startedAt ? ` \xB7 ${formatClockMs(nowMs - item.startedAt)} elapsed` : "";
|
|
1376
1108
|
if (fraction != null) {
|
|
1377
|
-
const
|
|
1109
|
+
const pct2 = Math.round(fraction * 100);
|
|
1378
1110
|
return /* @__PURE__ */ jsxs10(Text11, { children: [
|
|
1379
|
-
`${progressBar(fraction)} ${
|
|
1111
|
+
`${progressBar(fraction)} ${pct2}% ${formatClockMs(item.processedDurationMs)} / ${formatClockMs(
|
|
1380
1112
|
item.recording?.durationMs
|
|
1381
1113
|
)}`,
|
|
1382
1114
|
/* @__PURE__ */ jsx13(Text11, { dimColor: true, children: elapsed })
|
|
@@ -1898,14 +1630,201 @@ var init_RecordSetupView = __esm({
|
|
|
1898
1630
|
}
|
|
1899
1631
|
});
|
|
1900
1632
|
|
|
1901
|
-
// src/tui/
|
|
1902
|
-
import {
|
|
1903
|
-
import { Box as Box16, Text as Text16,
|
|
1904
|
-
import {
|
|
1905
|
-
function
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1633
|
+
// src/tui/RecordFrame.tsx
|
|
1634
|
+
import { useState as useState7 } from "react";
|
|
1635
|
+
import { Box as Box16, Text as Text16, useInput as useInput7 } from "ink";
|
|
1636
|
+
import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1637
|
+
function levelDb3(level) {
|
|
1638
|
+
if (level <= 0.03) return "silent";
|
|
1639
|
+
return `${Math.round(level * 60 - 60)} dB`;
|
|
1640
|
+
}
|
|
1641
|
+
function CompactMeter({ label, level }) {
|
|
1642
|
+
const width = 12;
|
|
1643
|
+
const filled = Math.max(0, Math.min(width, Math.round(Math.max(0, Math.min(1, level)) * width)));
|
|
1644
|
+
const silent = level <= 0.03;
|
|
1645
|
+
return /* @__PURE__ */ jsxs15(Text16, { children: [
|
|
1646
|
+
/* @__PURE__ */ jsxs15(Text16, { dimColor: true, children: [
|
|
1647
|
+
label,
|
|
1648
|
+
" "
|
|
1649
|
+
] }),
|
|
1650
|
+
/* @__PURE__ */ jsx18(Text16, { color: silent ? "yellow" : "cyan", children: "\u25CF".repeat(filled) }),
|
|
1651
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "\xB7".repeat(width - filled) }),
|
|
1652
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: ` ${levelDb3(level)}` })
|
|
1653
|
+
] });
|
|
1654
|
+
}
|
|
1655
|
+
function CaptionColumn({
|
|
1656
|
+
lines,
|
|
1657
|
+
width,
|
|
1658
|
+
rows,
|
|
1659
|
+
dim,
|
|
1660
|
+
scrollBack = 0
|
|
1661
|
+
}) {
|
|
1662
|
+
const end = lines.length - Math.min(scrollBack, Math.max(0, lines.length - 1));
|
|
1663
|
+
const chosen = [];
|
|
1664
|
+
let used = 0;
|
|
1665
|
+
for (let i = end - 1; i >= 0; i--) {
|
|
1666
|
+
const h = wrappedRows2(lines[i], width);
|
|
1667
|
+
if (used + h > rows && chosen.length > 0) break;
|
|
1668
|
+
chosen.unshift(lines[i]);
|
|
1669
|
+
used += h;
|
|
1670
|
+
}
|
|
1671
|
+
return /* @__PURE__ */ jsx18(Box16, { width, flexDirection: "column", children: chosen.length === 0 ? /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Listening for speech\u2026" }) : chosen.map((l, i) => /* @__PURE__ */ jsx18(Text16, { dimColor: dim, wrap: "wrap", children: l }, i)) });
|
|
1672
|
+
}
|
|
1673
|
+
function outcomeLine(telemetry, artifact) {
|
|
1674
|
+
if (telemetry.status === "recording" || telemetry.status === "starting") {
|
|
1675
|
+
return "Recording\u2026 stop to auto-save + transcribe + summarize";
|
|
1676
|
+
}
|
|
1677
|
+
const up = artifact?.uploadStatus;
|
|
1678
|
+
const tr = artifact?.transcriptionStatus;
|
|
1679
|
+
if (up === "uploading") return `Uploading to Recappi Cloud\u2026 ${pct(artifact?.uploadProgress)}`;
|
|
1680
|
+
if (tr === "processing") return `Transcribing\u2026 ${pct(artifact?.transcriptionProgress)}`;
|
|
1681
|
+
if (tr === "ready") return "Transcript ready \xB7 \u23CE open \xB7 T re-transcribe";
|
|
1682
|
+
if (up === "failed" || tr === "failed") {
|
|
1683
|
+
return artifact?.error ? `Cloud handoff failed \xB7 ${artifact.error}` : "Cloud handoff failed \xB7 T retry";
|
|
1684
|
+
}
|
|
1685
|
+
return "Saved \xB7 \u23CE open";
|
|
1686
|
+
}
|
|
1687
|
+
function pct(f) {
|
|
1688
|
+
return f == null ? "" : `${Math.round(Math.max(0, Math.min(1, f)) * 100)}%`;
|
|
1689
|
+
}
|
|
1690
|
+
function RecordFrame({
|
|
1691
|
+
telemetry,
|
|
1692
|
+
captions,
|
|
1693
|
+
artifact,
|
|
1694
|
+
recordings = [],
|
|
1695
|
+
selectedIndex = 0,
|
|
1696
|
+
title = "New recording",
|
|
1697
|
+
recordingId,
|
|
1698
|
+
jobId,
|
|
1699
|
+
nowMs = Date.now(),
|
|
1700
|
+
spinnerFrame = 0
|
|
1701
|
+
}) {
|
|
1702
|
+
const size = useTerminalSize();
|
|
1703
|
+
const [captionMode, setCaptionMode] = useState7("both");
|
|
1704
|
+
const [scrollBack, setScrollBack] = useState7(0);
|
|
1705
|
+
const PAGE = 8;
|
|
1706
|
+
useInput7((input, key) => {
|
|
1707
|
+
if (input === "c") {
|
|
1708
|
+
setCaptionMode((m) => m === "both" ? "source" : m === "source" ? "translation" : "both");
|
|
1709
|
+
} else if (key.upArrow || input === "k") setScrollBack((s) => s + 1);
|
|
1710
|
+
else if (key.downArrow || input === "j") setScrollBack((s) => Math.max(0, s - 1));
|
|
1711
|
+
else if (key.pageUp || input === "b") setScrollBack((s) => s + PAGE);
|
|
1712
|
+
else if (key.pageDown || input === " ") setScrollBack((s) => Math.max(0, s - PAGE));
|
|
1713
|
+
else if (input === "g") setScrollBack(Number.MAX_SAFE_INTEGER);
|
|
1714
|
+
else if (input === "G") setScrollBack(0);
|
|
1715
|
+
});
|
|
1716
|
+
const elapsed = telemetry.startedAtMs != null ? formatClockMs(Math.max(0, nowMs - telemetry.startedAtMs)) : "00:00";
|
|
1717
|
+
const recording = telemetry.status === "recording" || telemetry.status === "starting";
|
|
1718
|
+
const stateLabel = recording ? "\u23FA REC" : telemetry.status === "paused" ? "\u23F8 PAUSED" : telemetry.status === "stopped" ? "\u25A0 STOPPED" : "\u2026";
|
|
1719
|
+
const ids = [recordingId, jobId].filter(Boolean).join(" \xB7 ");
|
|
1720
|
+
const innerWidth = Math.max(20, size.columns - 2);
|
|
1721
|
+
const listWidth = Math.min(20, Math.max(14, Math.floor(innerWidth * 0.22)));
|
|
1722
|
+
const rightWidth = Math.max(20, innerWidth - listWidth - 3);
|
|
1723
|
+
const captionRows = Math.max(3, size.rows - 10);
|
|
1724
|
+
const sourceLines = captions ? [
|
|
1725
|
+
...captions.lines.map((l) => `${l.speaker ? `${l.speaker}: ` : ""}${trimLead2(l.text)}`),
|
|
1726
|
+
...captions.partial ? [trimLead2(captions.partial)] : []
|
|
1727
|
+
] : [];
|
|
1728
|
+
const translationLines = captions ? [
|
|
1729
|
+
...captions.lines.filter((l) => l.translation).map((l) => trimLead2(l.translation)),
|
|
1730
|
+
...captions.translationPartial ? [trimLead2(captions.translationPartial)] : []
|
|
1731
|
+
] : [];
|
|
1732
|
+
const status = telemetry.sizeBytes ? formatBytes2(telemetry.sizeBytes) : "";
|
|
1733
|
+
const sourceLine = [telemetry.sourceLabel, telemetry.micEnabled ? "Microphone" : null, status || null].filter(Boolean).join(" \xB7 ");
|
|
1734
|
+
return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", paddingX: 1, height: size.rows, children: [
|
|
1735
|
+
/* @__PURE__ */ jsxs15(Box16, { justifyContent: "space-between", children: [
|
|
1736
|
+
/* @__PURE__ */ jsxs15(Text16, { children: [
|
|
1737
|
+
/* @__PURE__ */ jsx18(Text16, { bold: true, color: "green", children: "recappi" }),
|
|
1738
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " \xB7 Recording" })
|
|
1739
|
+
] }),
|
|
1740
|
+
/* @__PURE__ */ jsxs15(Text16, { children: [
|
|
1741
|
+
/* @__PURE__ */ jsx18(Text16, { bold: true, color: recording ? "red" : "gray", children: stateLabel }),
|
|
1742
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: ` ${elapsed}${ids ? ` \xB7 ${ids}` : ""}` })
|
|
1743
|
+
] })
|
|
1744
|
+
] }),
|
|
1745
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "\u2500".repeat(innerWidth) }),
|
|
1746
|
+
/* @__PURE__ */ jsxs15(Box16, { flexGrow: 1, children: [
|
|
1747
|
+
/* @__PURE__ */ jsxs15(Box16, { width: listWidth, flexDirection: "column", children: [
|
|
1748
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: `RECORDINGS \xB7 ${recordings.length}` }),
|
|
1749
|
+
/* @__PURE__ */ jsx18(Box16, { marginTop: 1, flexDirection: "column", children: recordings.slice(0, Math.max(1, size.rows - 8)).map((rec, i) => {
|
|
1750
|
+
const st = recordingProcessingState(rec, void 0, spinnerFrame);
|
|
1751
|
+
const sel = i === selectedIndex;
|
|
1752
|
+
return /* @__PURE__ */ jsxs15(Box16, { children: [
|
|
1753
|
+
/* @__PURE__ */ jsx18(Box16, { width: 2, children: /* @__PURE__ */ jsx18(Text16, { color: "cyan", children: sel ? "\u25B8" : "" }) }),
|
|
1754
|
+
/* @__PURE__ */ jsx18(Box16, { width: 2, children: /* @__PURE__ */ jsx18(Text16, { color: st.color, children: st.glyph }) }),
|
|
1755
|
+
/* @__PURE__ */ jsx18(Box16, { width: listWidth - 4, children: /* @__PURE__ */ jsx18(Text16, { bold: sel, wrap: "truncate-end", children: recordingTitle2(rec) }) })
|
|
1756
|
+
] }, rec.recordingId);
|
|
1757
|
+
}) })
|
|
1758
|
+
] }),
|
|
1759
|
+
/* @__PURE__ */ jsx18(Box16, { width: 3, flexDirection: "column", alignItems: "center", children: Array.from({ length: Math.max(1, size.rows - 6) }, (_, i) => /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "\u2502" }, i)) }),
|
|
1760
|
+
/* @__PURE__ */ jsxs15(Box16, { width: rightWidth, flexDirection: "column", children: [
|
|
1761
|
+
/* @__PURE__ */ jsx18(Text16, { bold: true, wrap: "truncate-end", children: title }),
|
|
1762
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, wrap: "truncate-end", children: sourceLine }),
|
|
1763
|
+
telemetry.level ? /* @__PURE__ */ jsxs15(Box16, { children: [
|
|
1764
|
+
/* @__PURE__ */ jsx18(CompactMeter, { label: "System", level: telemetry.level.system ?? 0 }),
|
|
1765
|
+
telemetry.micEnabled ? /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " " }) : null,
|
|
1766
|
+
telemetry.micEnabled ? /* @__PURE__ */ jsx18(CompactMeter, { label: "Mic", level: telemetry.level.mic ?? 0 }) : null
|
|
1767
|
+
] }) : /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Capturing audio\u2026" }),
|
|
1768
|
+
/* @__PURE__ */ jsxs15(Box16, { marginTop: 1, flexDirection: "column", flexGrow: 1, children: [
|
|
1769
|
+
/* @__PURE__ */ jsxs15(Box16, { children: [
|
|
1770
|
+
captionMode !== "translation" ? /* @__PURE__ */ jsx18(Box16, { width: captionMode === "both" ? Math.floor((rightWidth - 3) / 2) : rightWidth, children: /* @__PURE__ */ jsx18(Text16, { bold: true, dimColor: true, children: "ORIGINAL" }) }) : null,
|
|
1771
|
+
captionMode === "both" ? /* @__PURE__ */ jsx18(Box16, { width: 3 }) : null,
|
|
1772
|
+
captionMode !== "source" ? /* @__PURE__ */ jsx18(Text16, { bold: true, dimColor: true, children: "TRANSLATION" }) : null,
|
|
1773
|
+
scrollBack > 0 ? /* @__PURE__ */ jsx18(Text16, { color: "yellow", children: " \u23F8 scrolled \xB7 G live" }) : null
|
|
1774
|
+
] }),
|
|
1775
|
+
/* @__PURE__ */ jsxs15(Box16, { children: [
|
|
1776
|
+
captionMode !== "translation" ? /* @__PURE__ */ jsx18(
|
|
1777
|
+
CaptionColumn,
|
|
1778
|
+
{
|
|
1779
|
+
lines: sourceLines,
|
|
1780
|
+
width: captionMode === "both" ? Math.floor((rightWidth - 3) / 2) : rightWidth,
|
|
1781
|
+
rows: captionRows,
|
|
1782
|
+
scrollBack
|
|
1783
|
+
}
|
|
1784
|
+
) : null,
|
|
1785
|
+
captionMode === "both" ? /* @__PURE__ */ jsx18(Box16, { width: 3, flexDirection: "column", children: Array.from({ length: Math.min(captionRows, 12) }, (_, i) => /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "\u2502" }, i)) }) : null,
|
|
1786
|
+
captionMode !== "source" ? /* @__PURE__ */ jsx18(
|
|
1787
|
+
CaptionColumn,
|
|
1788
|
+
{
|
|
1789
|
+
lines: translationLines,
|
|
1790
|
+
width: captionMode === "both" ? Math.floor((rightWidth - 3) / 2) : rightWidth,
|
|
1791
|
+
rows: captionRows,
|
|
1792
|
+
dim: true,
|
|
1793
|
+
scrollBack
|
|
1794
|
+
}
|
|
1795
|
+
) : null
|
|
1796
|
+
] })
|
|
1797
|
+
] }),
|
|
1798
|
+
/* @__PURE__ */ jsxs15(Box16, { marginTop: 1, children: [
|
|
1799
|
+
/* @__PURE__ */ jsx18(Text16, { bold: true, dimColor: true, children: "OUTCOME " }),
|
|
1800
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: outcomeLine(telemetry, artifact) })
|
|
1801
|
+
] })
|
|
1802
|
+
] })
|
|
1803
|
+
] }),
|
|
1804
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "\u2500".repeat(innerWidth) }),
|
|
1805
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: `q stop & save \xB7 c captions (${captionMode}) \xB7 \u2191\u2193 scroll \xB7 G live \xB7 T re-transcribe \xB7 1 overview 2 jobs 3 account` })
|
|
1806
|
+
] });
|
|
1807
|
+
}
|
|
1808
|
+
var trimLead2, wrappedRows2;
|
|
1809
|
+
var init_RecordFrame = __esm({
|
|
1810
|
+
"src/tui/RecordFrame.tsx"() {
|
|
1811
|
+
"use strict";
|
|
1812
|
+
init_format();
|
|
1813
|
+
init_RecordingRow();
|
|
1814
|
+
init_terminal();
|
|
1815
|
+
trimLead2 = (s) => s.replace(/^\s+/, "");
|
|
1816
|
+
wrappedRows2 = (text, width) => Math.max(1, Math.ceil(displayWidth(text) / Math.max(1, width)));
|
|
1817
|
+
}
|
|
1818
|
+
});
|
|
1819
|
+
|
|
1820
|
+
// src/tui/AppShell.tsx
|
|
1821
|
+
import { useCallback, useEffect as useEffect4, useRef as useRef3, useState as useState8 } from "react";
|
|
1822
|
+
import { Box as Box17, Text as Text17, useApp, useInput as useInput8 } from "ink";
|
|
1823
|
+
import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1824
|
+
function recordErrorCopy(code, message) {
|
|
1825
|
+
switch (code) {
|
|
1826
|
+
case "record.helper_unavailable":
|
|
1827
|
+
return {
|
|
1909
1828
|
title: "This CLI install is missing its local recorder.",
|
|
1910
1829
|
detail: "Run npm install -g recappi@latest, or use npx -y recappi@latest.",
|
|
1911
1830
|
tone: "yellow"
|
|
@@ -2019,6 +1938,11 @@ function transcriptionStatusFromJob(status) {
|
|
|
2019
1938
|
return "not_started";
|
|
2020
1939
|
}
|
|
2021
1940
|
}
|
|
1941
|
+
function recordFrameTitle(recordings, recordingId) {
|
|
1942
|
+
if (!recordingId) return "New recording";
|
|
1943
|
+
const recording = recordings.find((item) => item.recordingId === recordingId);
|
|
1944
|
+
return recording ? recordingTitle2(recording) : "New recording";
|
|
1945
|
+
}
|
|
2022
1946
|
function permissionItemsFromRecordError(data) {
|
|
2023
1947
|
const sidecarError = isRecord8(data) ? data : void 0;
|
|
2024
1948
|
const sidecarData = isRecord8(sidecarError?.data) ? sidecarError.data : void 0;
|
|
@@ -2059,6 +1983,7 @@ function AppShell({
|
|
|
2059
1983
|
startLiveRecord,
|
|
2060
1984
|
startRecordSetupPreview,
|
|
2061
1985
|
transcribeRecordingArtifact,
|
|
1986
|
+
onRetranscribe,
|
|
2062
1987
|
initialView = "overview",
|
|
2063
1988
|
openUrl: openUrl2,
|
|
2064
1989
|
copyText: copyText2,
|
|
@@ -2068,34 +1993,34 @@ function AppShell({
|
|
|
2068
1993
|
}) {
|
|
2069
1994
|
const { exit } = useApp();
|
|
2070
1995
|
const size = useTerminalSize();
|
|
2071
|
-
const [jobs, setJobs] =
|
|
2072
|
-
const [recordings, setRecordings] =
|
|
2073
|
-
const [recordingsNextCursor, setRecordingsNextCursor] =
|
|
2074
|
-
const [recordingsTotalCount, setRecordingsTotalCount] =
|
|
2075
|
-
const [stats, setStats] =
|
|
2076
|
-
const [accountStatus, setAccountStatus] =
|
|
2077
|
-
const [origin, setOrigin] =
|
|
2078
|
-
const [stack, setStack] =
|
|
2079
|
-
const [selected, setSelected] =
|
|
2080
|
-
const [spinnerFrame, setSpinnerFrame] =
|
|
2081
|
-
const [loadingMoreRecordings, setLoadingMoreRecordings] =
|
|
2082
|
-
const [loadError, setLoadError] =
|
|
2083
|
-
const [notice, setNotice] =
|
|
2084
|
-
const [summaryCache, setSummaryCache] =
|
|
2085
|
-
const [transcriptCache, setTranscriptCache] =
|
|
1996
|
+
const [jobs, setJobs] = useState8([]);
|
|
1997
|
+
const [recordings, setRecordings] = useState8([]);
|
|
1998
|
+
const [recordingsNextCursor, setRecordingsNextCursor] = useState8(null);
|
|
1999
|
+
const [recordingsTotalCount, setRecordingsTotalCount] = useState8(void 0);
|
|
2000
|
+
const [stats, setStats] = useState8(void 0);
|
|
2001
|
+
const [accountStatus, setAccountStatus] = useState8("loading");
|
|
2002
|
+
const [origin, setOrigin] = useState8("");
|
|
2003
|
+
const [stack, setStack] = useState8([{ kind: initialView }]);
|
|
2004
|
+
const [selected, setSelected] = useState8(0);
|
|
2005
|
+
const [spinnerFrame, setSpinnerFrame] = useState8(0);
|
|
2006
|
+
const [loadingMoreRecordings, setLoadingMoreRecordings] = useState8(false);
|
|
2007
|
+
const [loadError, setLoadError] = useState8(void 0);
|
|
2008
|
+
const [notice, setNotice] = useState8(void 0);
|
|
2009
|
+
const [summaryCache, setSummaryCache] = useState8(() => /* @__PURE__ */ new Map());
|
|
2010
|
+
const [transcriptCache, setTranscriptCache] = useState8(
|
|
2086
2011
|
() => /* @__PURE__ */ new Map()
|
|
2087
2012
|
);
|
|
2088
|
-
const [audioCache, setAudioCache] =
|
|
2089
|
-
const [downloadedIds, setDownloadedIds] =
|
|
2090
|
-
const [liveRecord, setLiveRecord] =
|
|
2091
|
-
const [recordSetupInputs, setRecordSetupInputs] =
|
|
2013
|
+
const [audioCache, setAudioCache] = useState8(() => /* @__PURE__ */ new Map());
|
|
2014
|
+
const [downloadedIds, setDownloadedIds] = useState8(() => /* @__PURE__ */ new Set());
|
|
2015
|
+
const [liveRecord, setLiveRecord] = useState8(void 0);
|
|
2016
|
+
const [recordSetupInputs, setRecordSetupInputs] = useState8({
|
|
2092
2017
|
sources: DEFAULT_RECORDING_SOURCES,
|
|
2093
2018
|
microphones: []
|
|
2094
2019
|
});
|
|
2095
|
-
const [recordSetupSelection, setRecordSetupSelection] =
|
|
2020
|
+
const [recordSetupSelection, setRecordSetupSelection] = useState8(
|
|
2096
2021
|
DEFAULT_RECORDING_SELECTION
|
|
2097
2022
|
);
|
|
2098
|
-
const [recordSetupLevels, setRecordSetupLevels] =
|
|
2023
|
+
const [recordSetupLevels, setRecordSetupLevels] = useState8({
|
|
2099
2024
|
bySourceId: {},
|
|
2100
2025
|
byMicrophoneId: {}
|
|
2101
2026
|
});
|
|
@@ -2399,6 +2324,65 @@ function AppShell({
|
|
|
2399
2324
|
setNotice("Transcription failed. Press enter to retry.");
|
|
2400
2325
|
}
|
|
2401
2326
|
}, [liveRecord, refresh, transcribeRecordingArtifact]);
|
|
2327
|
+
const retranscribeStoppedRecording = useCallback(async () => {
|
|
2328
|
+
const current = liveRecord;
|
|
2329
|
+
if (current?.kind !== "stopped") return;
|
|
2330
|
+
const artifact = current.artifact;
|
|
2331
|
+
if (!artifact?.recordingId) {
|
|
2332
|
+
setNotice("This recording is not in Recappi Cloud yet.");
|
|
2333
|
+
return;
|
|
2334
|
+
}
|
|
2335
|
+
if (!onRetranscribe) {
|
|
2336
|
+
setNotice("Re-transcribe is not available in this CLI session.");
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
setLiveRecord({
|
|
2340
|
+
...current,
|
|
2341
|
+
artifact: {
|
|
2342
|
+
...artifact,
|
|
2343
|
+
uploadStatus: "uploaded",
|
|
2344
|
+
uploadProgress: 1,
|
|
2345
|
+
transcriptionStatus: "queued",
|
|
2346
|
+
transcriptionProgress: void 0,
|
|
2347
|
+
error: void 0
|
|
2348
|
+
}
|
|
2349
|
+
});
|
|
2350
|
+
try {
|
|
2351
|
+
const data = await onRetranscribe(artifact.recordingId);
|
|
2352
|
+
setLiveRecord((latest) => {
|
|
2353
|
+
const base = latest?.kind === "stopped" && latest.artifact?.recordingId === artifact.recordingId ? latest : current;
|
|
2354
|
+
return {
|
|
2355
|
+
...base,
|
|
2356
|
+
artifact: {
|
|
2357
|
+
...artifact,
|
|
2358
|
+
...base.artifact ?? {},
|
|
2359
|
+
recordingId: data.recordingId,
|
|
2360
|
+
jobId: data.jobId,
|
|
2361
|
+
...data.transcriptId ? { transcriptId: data.transcriptId } : {},
|
|
2362
|
+
uploadStatus: "uploaded",
|
|
2363
|
+
uploadProgress: 1,
|
|
2364
|
+
transcriptionStatus: transcriptionStatusFromJob(data.status),
|
|
2365
|
+
...data.status === "succeeded" ? { transcriptionProgress: 1 } : {}
|
|
2366
|
+
}
|
|
2367
|
+
};
|
|
2368
|
+
});
|
|
2369
|
+
setNotice(
|
|
2370
|
+
data.status === "succeeded" ? "Re-transcription ready." : data.status === "running" ? "Re-transcription running." : data.status === "failed" ? "Re-transcription failed. Press T to retry." : "Re-transcription queued."
|
|
2371
|
+
);
|
|
2372
|
+
await refresh({ resetRecordings: true });
|
|
2373
|
+
} catch {
|
|
2374
|
+
setLiveRecord({
|
|
2375
|
+
...current,
|
|
2376
|
+
artifact: {
|
|
2377
|
+
...artifact,
|
|
2378
|
+
uploadStatus: "uploaded",
|
|
2379
|
+
transcriptionStatus: "failed",
|
|
2380
|
+
error: "Could not start re-transcription. Please try again."
|
|
2381
|
+
}
|
|
2382
|
+
});
|
|
2383
|
+
setNotice("Re-transcription failed. Press T to retry.");
|
|
2384
|
+
}
|
|
2385
|
+
}, [liveRecord, onRetranscribe, refresh]);
|
|
2402
2386
|
useEffect4(() => {
|
|
2403
2387
|
if (liveRecord?.kind !== "stopped") return;
|
|
2404
2388
|
const artifact = liveRecord.artifact;
|
|
@@ -2556,7 +2540,7 @@ function AppShell({
|
|
|
2556
2540
|
setNotice(void 0);
|
|
2557
2541
|
};
|
|
2558
2542
|
const back = () => setStack((st) => st.length > 1 ? st.slice(0, -1) : st);
|
|
2559
|
-
|
|
2543
|
+
useInput8((input, key) => {
|
|
2560
2544
|
setNotice(void 0);
|
|
2561
2545
|
if (screen.kind === "recordSetup") {
|
|
2562
2546
|
if (input === "q" || key.leftArrow) back();
|
|
@@ -2575,6 +2559,10 @@ function AppShell({
|
|
|
2575
2559
|
void transcribeStoppedRecording();
|
|
2576
2560
|
return;
|
|
2577
2561
|
}
|
|
2562
|
+
if (liveRecord?.kind === "stopped" && input === "T") {
|
|
2563
|
+
void retranscribeStoppedRecording();
|
|
2564
|
+
return;
|
|
2565
|
+
}
|
|
2578
2566
|
if (liveRecord?.kind === "stopped" && input === "n") {
|
|
2579
2567
|
void stopLiveRecord();
|
|
2580
2568
|
return;
|
|
@@ -2651,18 +2639,18 @@ function AppShell({
|
|
|
2651
2639
|
}
|
|
2652
2640
|
});
|
|
2653
2641
|
if (screen.kind === "transcript") {
|
|
2654
|
-
return /* @__PURE__ */
|
|
2642
|
+
return /* @__PURE__ */ jsx19(TranscriptView, { loading: screen.loading, data: screen.data, error: screen.error });
|
|
2655
2643
|
}
|
|
2656
2644
|
if (screen.kind === "jobDetail") {
|
|
2657
2645
|
const job = jobs.find((j) => j.jobId === screen.jobId);
|
|
2658
|
-
if (!job) return /* @__PURE__ */
|
|
2659
|
-
return /* @__PURE__ */
|
|
2646
|
+
if (!job) return /* @__PURE__ */ jsx19(Missing, { label: "Job" });
|
|
2647
|
+
return /* @__PURE__ */ jsx19(Detail, { notice, children: /* @__PURE__ */ jsx19(JobDetailView, { item: job, origin, spinnerFrame, nowMs: now() }) });
|
|
2660
2648
|
}
|
|
2661
2649
|
if (screen.kind === "recordingDetail") {
|
|
2662
2650
|
const rec = recordings.find((r) => r.recordingId === screen.recordingId);
|
|
2663
|
-
if (!rec) return /* @__PURE__ */
|
|
2651
|
+
if (!rec) return /* @__PURE__ */ jsx19(Missing, { label: "Recording" });
|
|
2664
2652
|
const detailTranscript = rec.activeTranscriptId ? transcriptCache.get(rec.activeTranscriptId) : void 0;
|
|
2665
|
-
return /* @__PURE__ */
|
|
2653
|
+
return /* @__PURE__ */ jsx19(Detail, { notice, children: /* @__PURE__ */ jsx19(
|
|
2666
2654
|
RecordingDetailView,
|
|
2667
2655
|
{
|
|
2668
2656
|
item: rec,
|
|
@@ -2673,7 +2661,7 @@ function AppShell({
|
|
|
2673
2661
|
) });
|
|
2674
2662
|
}
|
|
2675
2663
|
if (screen.kind === "recordSetup") {
|
|
2676
|
-
return /* @__PURE__ */
|
|
2664
|
+
return /* @__PURE__ */ jsx19(Box17, { flexDirection: "column", height: size.rows, paddingX: 1, children: /* @__PURE__ */ jsx19(
|
|
2677
2665
|
RecordSetupView,
|
|
2678
2666
|
{
|
|
2679
2667
|
model: recordSetupModel,
|
|
@@ -2686,32 +2674,42 @@ function AppShell({
|
|
|
2686
2674
|
}
|
|
2687
2675
|
if (screen.kind === "record") {
|
|
2688
2676
|
if (liveRecord?.kind === "live" && liveRecord.session.mode === "live_captions") {
|
|
2689
|
-
return /* @__PURE__ */
|
|
2677
|
+
return /* @__PURE__ */ jsx19(LiveCaptionsScreen, { source: liveRecord.session.source, now });
|
|
2690
2678
|
}
|
|
2691
2679
|
if (liveRecord?.kind === "live" || liveRecord?.kind === "starting" || liveRecord?.kind === "stopping" || liveRecord?.kind === "stopped") {
|
|
2692
|
-
return /* @__PURE__ */
|
|
2693
|
-
|
|
2680
|
+
return /* @__PURE__ */ jsx19(Detail, { notice, children: /* @__PURE__ */ jsx19(
|
|
2681
|
+
RecordFrame,
|
|
2694
2682
|
{
|
|
2695
2683
|
telemetry: liveRecord.telemetry,
|
|
2696
2684
|
captions: liveRecord.kind === "live" || liveRecord.kind === "stopping" ? liveRecord.captions : void 0,
|
|
2697
2685
|
artifact: liveRecord.kind === "stopped" ? liveRecord.artifact : void 0,
|
|
2698
|
-
|
|
2699
|
-
|
|
2686
|
+
recordings,
|
|
2687
|
+
selectedIndex: liveRecord.kind === "stopped" && liveRecord.artifact?.recordingId ? Math.max(
|
|
2688
|
+
0,
|
|
2689
|
+
recordings.findIndex(
|
|
2690
|
+
(item) => item.recordingId === liveRecord.artifact?.recordingId
|
|
2691
|
+
)
|
|
2692
|
+
) : 0,
|
|
2693
|
+
title: liveRecord.kind === "stopped" && liveRecord.artifact?.recordingId ? recordFrameTitle(recordings, liveRecord.artifact.recordingId) : "New recording",
|
|
2694
|
+
recordingId: liveRecord.kind === "stopped" ? liveRecord.artifact?.recordingId : void 0,
|
|
2695
|
+
jobId: liveRecord.kind === "stopped" ? liveRecord.artifact?.jobId : void 0,
|
|
2696
|
+
nowMs: now(),
|
|
2697
|
+
spinnerFrame
|
|
2700
2698
|
}
|
|
2701
2699
|
) });
|
|
2702
2700
|
}
|
|
2703
|
-
return /* @__PURE__ */
|
|
2704
|
-
/* @__PURE__ */
|
|
2701
|
+
return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
|
|
2702
|
+
/* @__PURE__ */ jsx19(Box17, { flexGrow: 1, flexDirection: "column", paddingX: 1, paddingTop: 1, children: liveRecord?.kind === "error" ? (() => {
|
|
2705
2703
|
if (liveRecord.code === "record.permission_required") {
|
|
2706
|
-
return /* @__PURE__ */
|
|
2704
|
+
return /* @__PURE__ */ jsx19(PermissionPreflightView, { items: permissionItemsFromRecordError(liveRecord.data) });
|
|
2707
2705
|
}
|
|
2708
2706
|
const copy = recordErrorCopy(liveRecord.code, liveRecord.message);
|
|
2709
|
-
return /* @__PURE__ */
|
|
2710
|
-
/* @__PURE__ */
|
|
2711
|
-
copy.detail ? /* @__PURE__ */
|
|
2707
|
+
return /* @__PURE__ */ jsxs16(Fragment6, { children: [
|
|
2708
|
+
/* @__PURE__ */ jsx19(Text17, { color: copy.tone, children: copy.title }),
|
|
2709
|
+
copy.detail ? /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: copy.detail }) : null
|
|
2712
2710
|
] });
|
|
2713
|
-
})() : /* @__PURE__ */
|
|
2714
|
-
/* @__PURE__ */
|
|
2711
|
+
})() : /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "Starting recording\u2026" }) }),
|
|
2712
|
+
/* @__PURE__ */ jsx19(Footer, { keys: "r retry \xB7 o settings \xB7 q / esc / \u2190 back" })
|
|
2715
2713
|
] });
|
|
2716
2714
|
}
|
|
2717
2715
|
const tab = screen.kind === "jobs" ? "jobs" : screen.kind === "account" ? "account" : "overview";
|
|
@@ -2729,7 +2727,7 @@ function AppShell({
|
|
|
2729
2727
|
const showPeek = size.columns >= 100;
|
|
2730
2728
|
const peekWidth = showPeek ? 34 : 0;
|
|
2731
2729
|
const listColumns = showPeek ? Math.max(30, size.columns - peekWidth - 3) : size.columns;
|
|
2732
|
-
body = /* @__PURE__ */
|
|
2730
|
+
body = /* @__PURE__ */ jsx19(
|
|
2733
2731
|
OverviewView,
|
|
2734
2732
|
{
|
|
2735
2733
|
recordings: recordings.slice(win.start, win.end),
|
|
@@ -2749,11 +2747,11 @@ function AppShell({
|
|
|
2749
2747
|
);
|
|
2750
2748
|
} else if (screen.kind === "account") {
|
|
2751
2749
|
position = "";
|
|
2752
|
-
body = /* @__PURE__ */
|
|
2750
|
+
body = /* @__PURE__ */ jsx19(AccountView, { status: accountStatus, nowMs: now() });
|
|
2753
2751
|
} else {
|
|
2754
2752
|
const win = listWindow(selected, jobs.length, Math.max(3, size.rows - 4));
|
|
2755
2753
|
position = jobs.length ? `${selected + 1} / ${jobs.length}` : "0";
|
|
2756
|
-
body = /* @__PURE__ */
|
|
2754
|
+
body = /* @__PURE__ */ jsx19(
|
|
2757
2755
|
JobsView,
|
|
2758
2756
|
{
|
|
2759
2757
|
items: jobs.slice(win.start, win.end),
|
|
@@ -2763,34 +2761,34 @@ function AppShell({
|
|
|
2763
2761
|
);
|
|
2764
2762
|
}
|
|
2765
2763
|
const footerKeys = screen.kind === "jobs" ? `${position} \xB7 \u2191\u2193 select \xB7 \u23CE job \xB7 t transcript \xB7 n record \xB7 1 overview \xB7 3 account \xB7 r refresh \xB7 q quit` : screen.kind === "account" ? "Account \xB7 n record \xB7 1 overview \xB7 2 jobs \xB7 r refresh \xB7 q quit" : `${position} \xB7 \u2191\u2193 scroll \xB7 \u23CE open \xB7 t transcript \xB7 n record \xB7 2 jobs \xB7 3 account \xB7 r refresh \xB7 q quit`;
|
|
2766
|
-
return /* @__PURE__ */
|
|
2767
|
-
/* @__PURE__ */
|
|
2768
|
-
/* @__PURE__ */
|
|
2764
|
+
return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
|
|
2765
|
+
/* @__PURE__ */ jsx19(Header, { active: tab }),
|
|
2766
|
+
/* @__PURE__ */ jsxs16(Box17, { flexGrow: 1, flexDirection: "column", children: [
|
|
2769
2767
|
body,
|
|
2770
|
-
loadError && jobs.length === 0 && recordings.length === 0 ? /* @__PURE__ */
|
|
2768
|
+
loadError && jobs.length === 0 && recordings.length === 0 ? /* @__PURE__ */ jsx19(Box17, { marginTop: 1, children: /* @__PURE__ */ jsxs16(Text17, { color: "red", children: [
|
|
2771
2769
|
"! ",
|
|
2772
2770
|
loadError
|
|
2773
2771
|
] }) }) : null
|
|
2774
2772
|
] }),
|
|
2775
|
-
/* @__PURE__ */
|
|
2773
|
+
/* @__PURE__ */ jsx19(Footer, { keys: footerKeys })
|
|
2776
2774
|
] });
|
|
2777
2775
|
}
|
|
2778
2776
|
function Detail({
|
|
2779
2777
|
notice,
|
|
2780
2778
|
children
|
|
2781
2779
|
}) {
|
|
2782
|
-
return /* @__PURE__ */
|
|
2780
|
+
return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", children: [
|
|
2783
2781
|
children,
|
|
2784
|
-
notice ? /* @__PURE__ */
|
|
2782
|
+
notice ? /* @__PURE__ */ jsx19(Box17, { paddingX: 1, children: /* @__PURE__ */ jsx19(Text17, { color: "green", children: notice }) }) : null
|
|
2785
2783
|
] });
|
|
2786
2784
|
}
|
|
2787
2785
|
function Missing({ label }) {
|
|
2788
|
-
return /* @__PURE__ */
|
|
2789
|
-
/* @__PURE__ */
|
|
2786
|
+
return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", paddingX: 1, children: [
|
|
2787
|
+
/* @__PURE__ */ jsxs16(Text17, { dimColor: true, children: [
|
|
2790
2788
|
label,
|
|
2791
2789
|
" no longer in the list."
|
|
2792
2790
|
] }),
|
|
2793
|
-
/* @__PURE__ */
|
|
2791
|
+
/* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "esc back \xB7 q quit" })
|
|
2794
2792
|
] });
|
|
2795
2793
|
}
|
|
2796
2794
|
var RECORDINGS_PAGE_SIZE, RECORDINGS_PREFETCH_REMAINING;
|
|
@@ -2807,10 +2805,11 @@ var init_AppShell = __esm({
|
|
|
2807
2805
|
init_LiveCaptionsScreen();
|
|
2808
2806
|
init_PermissionPreflightView();
|
|
2809
2807
|
init_RecordSetupView();
|
|
2810
|
-
|
|
2808
|
+
init_RecordFrame();
|
|
2811
2809
|
init_liveCaptions();
|
|
2812
2810
|
init_recordingCore();
|
|
2813
2811
|
init_format();
|
|
2812
|
+
init_RecordingRow();
|
|
2814
2813
|
init_terminal();
|
|
2815
2814
|
RECORDINGS_PAGE_SIZE = 50;
|
|
2816
2815
|
RECORDINGS_PREFETCH_REMAINING = 8;
|
|
@@ -2828,7 +2827,7 @@ __export(tui_exports, {
|
|
|
2828
2827
|
runDashboard: () => runDashboard,
|
|
2829
2828
|
useTerminalSize: () => useTerminalSize
|
|
2830
2829
|
});
|
|
2831
|
-
import
|
|
2830
|
+
import React11 from "react";
|
|
2832
2831
|
import { render as render2 } from "ink";
|
|
2833
2832
|
import { spawn as spawn3 } from "child_process";
|
|
2834
2833
|
function openUrl(url2) {
|
|
@@ -2849,7 +2848,7 @@ function copyText(text) {
|
|
|
2849
2848
|
async function runDashboard(deps) {
|
|
2850
2849
|
const renderApp = deps.renderApp ?? render2;
|
|
2851
2850
|
const app = renderApp(
|
|
2852
|
-
|
|
2851
|
+
React11.createElement(AppShell, {
|
|
2853
2852
|
fetchJobs: deps.fetchJobs,
|
|
2854
2853
|
fetchTranscript: deps.fetchTranscript,
|
|
2855
2854
|
fetchRecordings: deps.fetchRecordings,
|
|
@@ -20972,7 +20971,270 @@ function createFifo(path6) {
|
|
|
20972
20971
|
|
|
20973
20972
|
// src/record.tsx
|
|
20974
20973
|
init_LiveCaptionsScreen();
|
|
20975
|
-
|
|
20974
|
+
|
|
20975
|
+
// src/tui/RecordingHeroScreen.tsx
|
|
20976
|
+
init_format();
|
|
20977
|
+
init_liveCaptions();
|
|
20978
|
+
init_terminal();
|
|
20979
|
+
import { useEffect as useEffect2, useRef, useState as useState3 } from "react";
|
|
20980
|
+
import { Box as Box2, Text as Text2, useInput as useInput2 } from "ink";
|
|
20981
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
20982
|
+
var WAVE_THROTTLE_MS = 220;
|
|
20983
|
+
function waveRowsFor(terminalRows) {
|
|
20984
|
+
return terminalRows >= 30 ? 5 : 3;
|
|
20985
|
+
}
|
|
20986
|
+
function litCount(level, rows) {
|
|
20987
|
+
const amp = Math.max(0, Math.min(1, level));
|
|
20988
|
+
if (amp <= 0.028) return 0;
|
|
20989
|
+
return Math.max(1, Math.min(rows, Math.ceil(Math.pow(amp, 0.58) * rows)));
|
|
20990
|
+
}
|
|
20991
|
+
function litCounts(samples, width, rows) {
|
|
20992
|
+
if (width <= 0) return [];
|
|
20993
|
+
const tail = samples.slice(-width);
|
|
20994
|
+
return [...Array(Math.max(0, width - tail.length)).fill(0), ...tail].map((v) => litCount(v, rows));
|
|
20995
|
+
}
|
|
20996
|
+
function levelDb(level) {
|
|
20997
|
+
if (level <= 0.03) return "silent";
|
|
20998
|
+
return `${Math.round(level * 60 - 60)} dB`;
|
|
20999
|
+
}
|
|
21000
|
+
function MeterRow({
|
|
21001
|
+
label,
|
|
21002
|
+
samples,
|
|
21003
|
+
level,
|
|
21004
|
+
paused,
|
|
21005
|
+
width,
|
|
21006
|
+
rows
|
|
21007
|
+
}) {
|
|
21008
|
+
const silent = level <= 0.03;
|
|
21009
|
+
const cols = litCounts(samples, width, rows);
|
|
21010
|
+
const litColor = paused ? "gray" : "cyan";
|
|
21011
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
21012
|
+
/* @__PURE__ */ jsxs2(Box2, { width: width + 9, children: [
|
|
21013
|
+
/* @__PURE__ */ jsx3(Box2, { width: 9, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: label }) }),
|
|
21014
|
+
/* @__PURE__ */ jsx3(Box2, { flexGrow: 1, justifyContent: "flex-end", children: !paused && silent ? /* @__PURE__ */ jsx3(Text2, { color: "yellow", children: "silent" }) : /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: paused ? "paused" : levelDb(level) }) })
|
|
21015
|
+
] }),
|
|
21016
|
+
Array.from({ length: rows }, (_, r) => {
|
|
21017
|
+
const fromBottom = rows - r;
|
|
21018
|
+
return /* @__PURE__ */ jsx3(Text2, { children: cols.map(
|
|
21019
|
+
(c, i) => c >= fromBottom ? /* @__PURE__ */ jsx3(Text2, { color: litColor, children: c === fromBottom ? "\u2022" : "\u25CF" }, i) : /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "\xB7" }, i)
|
|
21020
|
+
) }, r);
|
|
21021
|
+
})
|
|
21022
|
+
] });
|
|
21023
|
+
}
|
|
21024
|
+
function ProgressBar({ fraction, width = 12 }) {
|
|
21025
|
+
const f = Math.max(0, Math.min(1, fraction));
|
|
21026
|
+
const filled = Math.round(f * width);
|
|
21027
|
+
return /* @__PURE__ */ jsxs2(Text2, { color: "cyan", children: [
|
|
21028
|
+
"\u2593".repeat(filled),
|
|
21029
|
+
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "\u2591".repeat(Math.max(0, width - filled)) })
|
|
21030
|
+
] });
|
|
21031
|
+
}
|
|
21032
|
+
function stoppedPhase(artifact) {
|
|
21033
|
+
if (!artifact) return null;
|
|
21034
|
+
if (artifact.uploadStatus === "uploading") {
|
|
21035
|
+
return { label: "Uploading to Recappi Cloud", fraction: artifact.uploadProgress };
|
|
21036
|
+
}
|
|
21037
|
+
if (artifact.uploadStatus === "queued") return { label: "Queued to upload" };
|
|
21038
|
+
if (artifact.transcriptionStatus === "processing") {
|
|
21039
|
+
return { label: "Transcribing", fraction: artifact.transcriptionProgress };
|
|
21040
|
+
}
|
|
21041
|
+
return null;
|
|
21042
|
+
}
|
|
21043
|
+
function RecordingHeroScreen({
|
|
21044
|
+
telemetry,
|
|
21045
|
+
artifact,
|
|
21046
|
+
captions,
|
|
21047
|
+
canTranscribe = false,
|
|
21048
|
+
canPause = false,
|
|
21049
|
+
now = () => Date.now()
|
|
21050
|
+
}) {
|
|
21051
|
+
const size = useTerminalSize();
|
|
21052
|
+
const [tick, setTick] = useState3(() => now());
|
|
21053
|
+
const [waveSys, setWaveSys] = useState3([]);
|
|
21054
|
+
const [waveMic, setWaveMic] = useState3([]);
|
|
21055
|
+
const [captionMode, setCaptionMode] = useState3("both");
|
|
21056
|
+
const lastAppendRef = useRef(0);
|
|
21057
|
+
useInput2((input) => {
|
|
21058
|
+
if (input === "c") {
|
|
21059
|
+
setCaptionMode((m) => m === "both" ? "source" : m === "source" ? "translation" : "both");
|
|
21060
|
+
}
|
|
21061
|
+
});
|
|
21062
|
+
useEffect2(() => {
|
|
21063
|
+
if (telemetry.level == null) return;
|
|
21064
|
+
const t = now();
|
|
21065
|
+
if (t - lastAppendRef.current < WAVE_THROTTLE_MS) return;
|
|
21066
|
+
lastAppendRef.current = t;
|
|
21067
|
+
setWaveSys((w) => [...w.slice(-512), telemetry.level.system ?? 0]);
|
|
21068
|
+
setWaveMic((w) => [...w.slice(-512), telemetry.level.mic ?? 0]);
|
|
21069
|
+
}, [telemetry.level]);
|
|
21070
|
+
useEffect2(() => {
|
|
21071
|
+
const id = setInterval(() => setTick(now()), 1e3);
|
|
21072
|
+
return () => clearInterval(id);
|
|
21073
|
+
}, []);
|
|
21074
|
+
const elapsed = telemetry.startedAtMs != null ? formatClockMs(Math.max(0, tick - telemetry.startedAtMs)) : "00:00";
|
|
21075
|
+
const innerWidth = Math.max(10, size.columns - 4);
|
|
21076
|
+
if (telemetry.status === "stopped") {
|
|
21077
|
+
const handoff = stoppedHandoffCopy(artifact, canTranscribe);
|
|
21078
|
+
const phase = stoppedPhase(artifact);
|
|
21079
|
+
const meta3 = [
|
|
21080
|
+
telemetry.durationMs != null ? formatClockMs(telemetry.durationMs) : null,
|
|
21081
|
+
formatBytes2(telemetry.sizeBytes) || null
|
|
21082
|
+
].filter(Boolean).join(" \xB7 ");
|
|
21083
|
+
const saved = artifact?.uploadStatus === "uploaded" ? "\u2713 Saved to Recappi Cloud" : "\u2713 Saved to your Mac";
|
|
21084
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
21085
|
+
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "recappi \xB7 Recording" }),
|
|
21086
|
+
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
21087
|
+
/* @__PURE__ */ jsx3(Text2, { color: "green", children: saved }),
|
|
21088
|
+
meta3 ? /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: meta3 }) : null,
|
|
21089
|
+
telemetry.savedPath ? /* @__PURE__ */ jsx3(Text2, { dimColor: true, wrap: "truncate-middle", children: telemetry.savedPath }) : null
|
|
21090
|
+
] }),
|
|
21091
|
+
phase ? /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
|
|
21092
|
+
/* @__PURE__ */ jsx3(Text2, { color: "cyan", children: `\u25D0 ${phase.label}` }),
|
|
21093
|
+
phase.fraction != null ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
21094
|
+
/* @__PURE__ */ jsx3(Text2, { children: " " }),
|
|
21095
|
+
/* @__PURE__ */ jsx3(ProgressBar, { fraction: phase.fraction }),
|
|
21096
|
+
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: ` ${Math.round(phase.fraction * 100)}%` })
|
|
21097
|
+
] }) : /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "\u2026" })
|
|
21098
|
+
] }) : null,
|
|
21099
|
+
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
21100
|
+
/* @__PURE__ */ jsx3(Text2, { color: handoff.tone === "red" ? "red" : handoff.tone === "green" ? "green" : void 0, dimColor: handoff.tone === "dim", children: handoff.text }),
|
|
21101
|
+
artifact?.error ? /* @__PURE__ */ jsx3(Text2, { color: "red", wrap: "truncate-end", children: artifact.error }) : null
|
|
21102
|
+
] })
|
|
21103
|
+
] });
|
|
21104
|
+
}
|
|
21105
|
+
if (telemetry.status === "error") {
|
|
21106
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
21107
|
+
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "recappi \xB7 Recording" }),
|
|
21108
|
+
/* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text2, { color: "red", children: telemetry.error ? `Recording error: ${telemetry.error}` : "Recording error" }) }),
|
|
21109
|
+
/* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "esc back" }) })
|
|
21110
|
+
] });
|
|
21111
|
+
}
|
|
21112
|
+
const paused = telemetry.status === "paused";
|
|
21113
|
+
const starting = telemetry.status === "starting" || telemetry.status === "stopping";
|
|
21114
|
+
const badge = paused ? "\u23F8 PAUSED" : starting ? "\u2026" : "\u23FA REC";
|
|
21115
|
+
const meterW = Math.max(10, Math.min(72, innerWidth - 20));
|
|
21116
|
+
const sizeStr = telemetry.sizeBytes ? formatBytes2(telemetry.sizeBytes) : "";
|
|
21117
|
+
const context = [telemetry.sourceLabel, telemetry.micEnabled ? "Microphone" : null, sizeStr || null].filter(Boolean).join(" \xB7 ");
|
|
21118
|
+
const waveRows = waveRowsFor(size.rows);
|
|
21119
|
+
const meterBlockRows = (telemetry.micEnabled ? 2 : 1) * (waveRows + 1) + (telemetry.micEnabled ? 1 : 0);
|
|
21120
|
+
const fixedRows = 8 + meterBlockRows;
|
|
21121
|
+
const captionRows = Math.max(2, size.rows - fixedRows);
|
|
21122
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
21123
|
+
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
21124
|
+
/* @__PURE__ */ jsx3(Text2, { bold: true, color: "green", children: "recappi" }),
|
|
21125
|
+
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: " \xB7 Recording" })
|
|
21126
|
+
] }),
|
|
21127
|
+
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, paddingX: 1, flexDirection: "column", children: [
|
|
21128
|
+
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
21129
|
+
/* @__PURE__ */ jsx3(Text2, { bold: true, color: paused ? "yellow" : "red", children: badge }),
|
|
21130
|
+
/* @__PURE__ */ jsx3(Text2, { children: " " }),
|
|
21131
|
+
/* @__PURE__ */ jsx3(Text2, { bold: true, children: elapsed })
|
|
21132
|
+
] }),
|
|
21133
|
+
/* @__PURE__ */ jsx3(Box2, { marginTop: 1, flexDirection: "column", children: telemetry.level == null ? (
|
|
21134
|
+
// No level telemetry yet — honest activity, not a flat meter that
|
|
21135
|
+
// reads as silence (the elapsed timer above proves it's live).
|
|
21136
|
+
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: paused ? "Paused" : `Capturing audio${".".repeat(Math.floor(tick / 1e3) % 3 + 1)}` })
|
|
21137
|
+
) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
21138
|
+
/* @__PURE__ */ jsx3(MeterRow, { label: "System", samples: waveSys, level: telemetry.level.system ?? 0, paused, width: meterW, rows: waveRows }),
|
|
21139
|
+
telemetry.micEnabled ? /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx3(MeterRow, { label: "Mic", samples: waveMic, level: telemetry.level.mic ?? 0, paused, width: meterW, rows: waveRows }) }) : null
|
|
21140
|
+
] }) }),
|
|
21141
|
+
/* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: context }) }),
|
|
21142
|
+
captions ? /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
21143
|
+
/* @__PURE__ */ jsx3(Text2, { bold: true, dimColor: true, children: "LIVE CAPTIONS" }),
|
|
21144
|
+
/* @__PURE__ */ jsx3(HeroCaptions, { state: captions, maxRows: captionRows, width: innerWidth, mode: captionMode })
|
|
21145
|
+
] }) : null
|
|
21146
|
+
] }),
|
|
21147
|
+
/* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
21148
|
+
"q stop & save",
|
|
21149
|
+
canPause ? ` \xB7 p ${paused ? "resume" : "pause"}` : "",
|
|
21150
|
+
captions ? " \xB7 c captions" : ""
|
|
21151
|
+
] }) })
|
|
21152
|
+
] });
|
|
21153
|
+
}
|
|
21154
|
+
var trimLead = (s) => s.replace(/^\s+/, "");
|
|
21155
|
+
function wrappedRows(text, width) {
|
|
21156
|
+
return Math.max(1, Math.ceil(displayWidth(text) / Math.max(1, width)));
|
|
21157
|
+
}
|
|
21158
|
+
function captionColumn(items, maxRows, width, dim) {
|
|
21159
|
+
const budget = Math.max(1, maxRows);
|
|
21160
|
+
const chosen = [];
|
|
21161
|
+
let used = 0;
|
|
21162
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
21163
|
+
const h = wrappedRows(items[i].text, width);
|
|
21164
|
+
if (used + h > budget && chosen.length > 0) break;
|
|
21165
|
+
chosen.unshift(items[i]);
|
|
21166
|
+
used += h;
|
|
21167
|
+
}
|
|
21168
|
+
return chosen.map((it) => /* @__PURE__ */ jsx3(Text2, { dimColor: dim, wrap: "wrap", children: it.text }, it.key));
|
|
21169
|
+
}
|
|
21170
|
+
function HeroCaptions({
|
|
21171
|
+
state,
|
|
21172
|
+
maxRows,
|
|
21173
|
+
width,
|
|
21174
|
+
mode
|
|
21175
|
+
}) {
|
|
21176
|
+
const hasPartial = Boolean(state.partial && state.partial.length > 0);
|
|
21177
|
+
const captionError = state.status === "error" ? `Captions unavailable: ${state.error ?? "Live captions unavailable."}` : null;
|
|
21178
|
+
if (state.lines.length === 0 && !hasPartial) {
|
|
21179
|
+
return /* @__PURE__ */ jsx3(Text2, { color: captionError ? "yellow" : void 0, dimColor: !captionError, children: captionError ?? (state.status === "live" ? "Listening for speech\u2026" : liveCaptionStatusLabel(state.status)) });
|
|
21180
|
+
}
|
|
21181
|
+
const sourceItems = state.lines.map((l) => ({
|
|
21182
|
+
key: `${l.id}-s`,
|
|
21183
|
+
text: `${l.speaker ? `${l.speaker}: ` : ""}${trimLead(l.text)}`
|
|
21184
|
+
}));
|
|
21185
|
+
if (hasPartial) sourceItems.push({ key: "sp", text: trimLead(state.partial) });
|
|
21186
|
+
const translationItems = state.lines.filter((l) => l.translation).map((l) => ({ key: `${l.id}-t`, text: trimLead(l.translation) }));
|
|
21187
|
+
if (state.translationPartial) translationItems.push({ key: "tp", text: trimLead(state.translationPartial) });
|
|
21188
|
+
const errLine = captionError ? /* @__PURE__ */ jsx3(Text2, { color: "yellow", wrap: "wrap", children: captionError }) : null;
|
|
21189
|
+
const hasTranslation = translationItems.length > 0;
|
|
21190
|
+
if (mode === "source" || !hasTranslation) {
|
|
21191
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
21192
|
+
captionColumn(sourceItems, maxRows, width, false),
|
|
21193
|
+
errLine
|
|
21194
|
+
] });
|
|
21195
|
+
}
|
|
21196
|
+
if (mode === "translation") {
|
|
21197
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
21198
|
+
captionColumn(translationItems, maxRows, width, false),
|
|
21199
|
+
errLine
|
|
21200
|
+
] });
|
|
21201
|
+
}
|
|
21202
|
+
const gap = 2;
|
|
21203
|
+
const colW = Math.max(12, Math.floor((width - gap) / 2));
|
|
21204
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
21205
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", children: [
|
|
21206
|
+
/* @__PURE__ */ jsxs2(Box2, { width: colW, flexDirection: "column", marginRight: gap, children: [
|
|
21207
|
+
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "ORIGINAL" }),
|
|
21208
|
+
captionColumn(sourceItems, Math.max(1, maxRows - 1), colW, false)
|
|
21209
|
+
] }),
|
|
21210
|
+
/* @__PURE__ */ jsxs2(Box2, { width: colW, flexDirection: "column", children: [
|
|
21211
|
+
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "TRANSLATION" }),
|
|
21212
|
+
captionColumn(translationItems, Math.max(1, maxRows - 1), colW, false)
|
|
21213
|
+
] })
|
|
21214
|
+
] }),
|
|
21215
|
+
errLine
|
|
21216
|
+
] });
|
|
21217
|
+
}
|
|
21218
|
+
function stoppedHandoffCopy(artifact, canTranscribe) {
|
|
21219
|
+
if (artifact?.uploadStatus === "uploading" || artifact?.transcriptionStatus === "processing") {
|
|
21220
|
+
return { text: "esc run in background", tone: "dim" };
|
|
21221
|
+
}
|
|
21222
|
+
if (artifact?.transcriptionStatus === "queued") {
|
|
21223
|
+
return { text: "Transcription queued \xB7 \u23CE open recording \xB7 n not now", tone: "green" };
|
|
21224
|
+
}
|
|
21225
|
+
if (artifact?.transcriptionStatus === "ready") {
|
|
21226
|
+
return { text: "Transcription ready \xB7 \u23CE open recording \xB7 n not now", tone: "green" };
|
|
21227
|
+
}
|
|
21228
|
+
if (artifact?.uploadStatus === "failed" || artifact?.transcriptionStatus === "failed") {
|
|
21229
|
+
return { text: "Transcription failed \xB7 \u23CE retry \xB7 n not now", tone: "red" };
|
|
21230
|
+
}
|
|
21231
|
+
if (!canTranscribe || !artifact?.audioPath) {
|
|
21232
|
+
return { text: "Saved locally \xB7 n back", tone: "dim" };
|
|
21233
|
+
}
|
|
21234
|
+
return { text: "Starting transcription\u2026", tone: "normal" };
|
|
21235
|
+
}
|
|
21236
|
+
|
|
21237
|
+
// src/record.tsx
|
|
20976
21238
|
init_liveCaptions();
|
|
20977
21239
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
20978
21240
|
var SIDECAR_COMMAND_ENV = "RECAPPI_MINI_SIDECAR";
|