recappi 0.1.53 → 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 +843 -453
- 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,274 +591,6 @@ var init_LiveCaptionsScreen = __esm({
|
|
|
591
591
|
}
|
|
592
592
|
});
|
|
593
593
|
|
|
594
|
-
// src/tui/RecordingHeroScreen.tsx
|
|
595
|
-
import { useEffect as useEffect2, useRef, useState as useState3 } from "react";
|
|
596
|
-
import { Box as Box2, Text as Text2, useInput as useInput2 } from "ink";
|
|
597
|
-
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
598
|
-
function waveRowsFor(terminalRows) {
|
|
599
|
-
return terminalRows >= 30 ? 5 : 3;
|
|
600
|
-
}
|
|
601
|
-
function litCount(level, rows) {
|
|
602
|
-
const amp = Math.max(0, Math.min(1, level));
|
|
603
|
-
if (amp <= 0.028) return 0;
|
|
604
|
-
return Math.max(1, Math.min(rows, Math.ceil(Math.pow(amp, 0.58) * rows)));
|
|
605
|
-
}
|
|
606
|
-
function litCounts(samples, width, rows) {
|
|
607
|
-
if (width <= 0) return [];
|
|
608
|
-
const tail = samples.slice(-width);
|
|
609
|
-
return [...Array(Math.max(0, width - tail.length)).fill(0), ...tail].map((v) => litCount(v, rows));
|
|
610
|
-
}
|
|
611
|
-
function levelDb(level) {
|
|
612
|
-
if (level <= 0.03) return "silent";
|
|
613
|
-
return `${Math.round(level * 60 - 60)} dB`;
|
|
614
|
-
}
|
|
615
|
-
function MeterRow({
|
|
616
|
-
label,
|
|
617
|
-
samples,
|
|
618
|
-
level,
|
|
619
|
-
paused,
|
|
620
|
-
width,
|
|
621
|
-
rows
|
|
622
|
-
}) {
|
|
623
|
-
const silent = level <= 0.03;
|
|
624
|
-
const cols = litCounts(samples, width, rows);
|
|
625
|
-
const litColor = paused ? "gray" : "cyan";
|
|
626
|
-
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
627
|
-
/* @__PURE__ */ jsxs2(Box2, { width: width + 9, children: [
|
|
628
|
-
/* @__PURE__ */ jsx3(Box2, { width: 9, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: label }) }),
|
|
629
|
-
/* @__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) }) })
|
|
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
|
-
})
|
|
637
|
-
] });
|
|
638
|
-
}
|
|
639
|
-
function ProgressBar({ fraction, width = 12 }) {
|
|
640
|
-
const f = Math.max(0, Math.min(1, fraction));
|
|
641
|
-
const filled = Math.round(f * width);
|
|
642
|
-
return /* @__PURE__ */ jsxs2(Text2, { color: "cyan", children: [
|
|
643
|
-
"\u2593".repeat(filled),
|
|
644
|
-
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "\u2591".repeat(Math.max(0, width - filled)) })
|
|
645
|
-
] });
|
|
646
|
-
}
|
|
647
|
-
function stoppedPhase(artifact) {
|
|
648
|
-
if (!artifact) return null;
|
|
649
|
-
if (artifact.uploadStatus === "uploading") {
|
|
650
|
-
return { label: "Uploading to Recappi Cloud", fraction: artifact.uploadProgress };
|
|
651
|
-
}
|
|
652
|
-
if (artifact.uploadStatus === "queued") return { label: "Queued to upload" };
|
|
653
|
-
if (artifact.transcriptionStatus === "processing") {
|
|
654
|
-
return { label: "Transcribing", fraction: artifact.transcriptionProgress };
|
|
655
|
-
}
|
|
656
|
-
return null;
|
|
657
|
-
}
|
|
658
|
-
function RecordingHeroScreen({
|
|
659
|
-
telemetry,
|
|
660
|
-
artifact,
|
|
661
|
-
captions,
|
|
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
|
-
] });
|
|
726
|
-
}
|
|
727
|
-
const paused = telemetry.status === "paused";
|
|
728
|
-
const starting = telemetry.status === "starting" || telemetry.status === "stopping";
|
|
729
|
-
const badge = paused ? "\u23F8 PAUSED" : starting ? "\u2026" : "\u23FA REC";
|
|
730
|
-
const meterW = Math.max(10, Math.min(72, innerWidth - 20));
|
|
731
|
-
const sizeStr = telemetry.sizeBytes ? formatBytes2(telemetry.sizeBytes) : "";
|
|
732
|
-
const context = [telemetry.sourceLabel, telemetry.micEnabled ? "Microphone" : null, sizeStr || null].filter(Boolean).join(" \xB7 ");
|
|
733
|
-
const waveRows = waveRowsFor(size.rows);
|
|
734
|
-
const meterBlockRows = (telemetry.micEnabled ? 2 : 1) * (waveRows + 1) + (telemetry.micEnabled ? 1 : 0);
|
|
735
|
-
const fixedRows = 8 + meterBlockRows;
|
|
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
|
-
] });
|
|
768
|
-
}
|
|
769
|
-
function wrappedRows(text, width) {
|
|
770
|
-
return Math.max(1, Math.ceil(displayWidth(text) / Math.max(1, width)));
|
|
771
|
-
}
|
|
772
|
-
function captionColumn(items, maxRows, width, dim) {
|
|
773
|
-
const budget = Math.max(1, maxRows);
|
|
774
|
-
const chosen = [];
|
|
775
|
-
let used = 0;
|
|
776
|
-
for (let i = items.length - 1; i >= 0; i--) {
|
|
777
|
-
const h = wrappedRows(items[i].text, width);
|
|
778
|
-
if (used + h > budget && chosen.length > 0) break;
|
|
779
|
-
chosen.unshift(items[i]);
|
|
780
|
-
used += h;
|
|
781
|
-
}
|
|
782
|
-
return chosen.map((it) => /* @__PURE__ */ jsx3(Text2, { dimColor: dim, wrap: "wrap", children: it.text }, it.key));
|
|
783
|
-
}
|
|
784
|
-
function HeroCaptions({
|
|
785
|
-
state,
|
|
786
|
-
maxRows,
|
|
787
|
-
width,
|
|
788
|
-
mode
|
|
789
|
-
}) {
|
|
790
|
-
const hasPartial = Boolean(state.partial && state.partial.length > 0);
|
|
791
|
-
const captionError = state.status === "error" ? `Captions unavailable: ${state.error ?? "Live captions unavailable."}` : null;
|
|
792
|
-
if (state.lines.length === 0 && !hasPartial) {
|
|
793
|
-
return /* @__PURE__ */ jsx3(Text2, { color: captionError ? "yellow" : void 0, dimColor: !captionError, children: captionError ?? (state.status === "live" ? "Listening for speech\u2026" : liveCaptionStatusLabel(state.status)) });
|
|
794
|
-
}
|
|
795
|
-
const sourceItems = state.lines.map((l) => ({
|
|
796
|
-
key: `${l.id}-s`,
|
|
797
|
-
text: `${l.speaker ? `${l.speaker}: ` : ""}${trimLead(l.text)}`
|
|
798
|
-
}));
|
|
799
|
-
if (hasPartial) sourceItems.push({ key: "sp", text: trimLead(state.partial) });
|
|
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
|
|
830
|
-
] });
|
|
831
|
-
}
|
|
832
|
-
function stoppedHandoffCopy(artifact, canTranscribe) {
|
|
833
|
-
if (artifact?.uploadStatus === "uploading" || artifact?.transcriptionStatus === "processing") {
|
|
834
|
-
return { text: "esc run in background", tone: "dim" };
|
|
835
|
-
}
|
|
836
|
-
if (artifact?.transcriptionStatus === "queued") {
|
|
837
|
-
return { text: "Transcription queued \xB7 \u23CE open recording \xB7 n not now", tone: "green" };
|
|
838
|
-
}
|
|
839
|
-
if (artifact?.transcriptionStatus === "ready") {
|
|
840
|
-
return { text: "Transcription ready \xB7 \u23CE open recording \xB7 n not now", tone: "green" };
|
|
841
|
-
}
|
|
842
|
-
if (artifact?.uploadStatus === "failed" || artifact?.transcriptionStatus === "failed") {
|
|
843
|
-
return { text: "Transcription failed \xB7 \u23CE retry \xB7 n not now", tone: "red" };
|
|
844
|
-
}
|
|
845
|
-
if (!canTranscribe || !artifact?.audioPath) {
|
|
846
|
-
return { text: "Saved locally \xB7 n back", tone: "dim" };
|
|
847
|
-
}
|
|
848
|
-
return { text: "Starting transcription\u2026", tone: "normal" };
|
|
849
|
-
}
|
|
850
|
-
var WAVE_THROTTLE_MS, trimLead;
|
|
851
|
-
var init_RecordingHeroScreen = __esm({
|
|
852
|
-
"src/tui/RecordingHeroScreen.tsx"() {
|
|
853
|
-
"use strict";
|
|
854
|
-
init_format();
|
|
855
|
-
init_liveCaptions();
|
|
856
|
-
init_terminal();
|
|
857
|
-
WAVE_THROTTLE_MS = 220;
|
|
858
|
-
trimLead = (s) => s.replace(/^\s+/, "");
|
|
859
|
-
}
|
|
860
|
-
});
|
|
861
|
-
|
|
862
594
|
// src/tui/AccountView.tsx
|
|
863
595
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
864
596
|
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -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,10 +1630,197 @@ var init_RecordSetupView = __esm({
|
|
|
1898
1630
|
}
|
|
1899
1631
|
});
|
|
1900
1632
|
|
|
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
|
+
|
|
1901
1820
|
// src/tui/AppShell.tsx
|
|
1902
|
-
import { useCallback, useEffect as useEffect4, useRef as useRef3, useState as
|
|
1903
|
-
import { Box as
|
|
1904
|
-
import { Fragment as Fragment6, jsx as
|
|
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";
|
|
1905
1824
|
function recordErrorCopy(code, message) {
|
|
1906
1825
|
switch (code) {
|
|
1907
1826
|
case "record.helper_unavailable":
|
|
@@ -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,
|
|
@@ -2861,6 +2860,7 @@ async function runDashboard(deps) {
|
|
|
2861
2860
|
startLiveRecord: deps.startLiveRecord,
|
|
2862
2861
|
startRecordSetupPreview: deps.startRecordSetupPreview,
|
|
2863
2862
|
transcribeRecordingArtifact: deps.transcribeRecordingArtifact,
|
|
2863
|
+
onRetranscribe: deps.retranscribeRecording,
|
|
2864
2864
|
initialView: deps.initialView ?? "overview",
|
|
2865
2865
|
openUrl,
|
|
2866
2866
|
copyText
|
|
@@ -17838,6 +17838,13 @@ var uploadBatchDataSchema = external_exports.object({
|
|
|
17838
17838
|
totalCount: external_exports.number().int().nonnegative(),
|
|
17839
17839
|
attemptedCount: external_exports.number().int().nonnegative()
|
|
17840
17840
|
});
|
|
17841
|
+
var recordingTranscribeDataSchema = external_exports.object({
|
|
17842
|
+
origin: external_exports.string(),
|
|
17843
|
+
recordingId: external_exports.string(),
|
|
17844
|
+
jobId: external_exports.string(),
|
|
17845
|
+
status: transcriptionJobStatusSchema,
|
|
17846
|
+
transcriptId: external_exports.string().nullable().optional()
|
|
17847
|
+
});
|
|
17841
17848
|
var jobDataSchema = external_exports.object({
|
|
17842
17849
|
jobId: external_exports.string(),
|
|
17843
17850
|
recordingId: external_exports.string().optional(),
|
|
@@ -19088,6 +19095,62 @@ var RecappiApiClient = class {
|
|
|
19088
19095
|
);
|
|
19089
19096
|
return mapRecording(parsed, this.auth.origin);
|
|
19090
19097
|
}
|
|
19098
|
+
async transcribeRecording(opts) {
|
|
19099
|
+
if (opts.scene && opts.scene !== "default") {
|
|
19100
|
+
throw cliError("usage.invalid_argument", `Unknown transcription scene '${opts.scene}'.`, {
|
|
19101
|
+
hint: "Only the default scene is available today; use --prompt for custom context."
|
|
19102
|
+
});
|
|
19103
|
+
}
|
|
19104
|
+
opts.onEvent?.({
|
|
19105
|
+
type: "started",
|
|
19106
|
+
command: "recordings retranscribe",
|
|
19107
|
+
recordingId: opts.recordingId,
|
|
19108
|
+
message: "Starting transcription"
|
|
19109
|
+
});
|
|
19110
|
+
const hasPrompt = Boolean(opts.prompt?.trim());
|
|
19111
|
+
const parsed = await this.postJson(
|
|
19112
|
+
`/api/recordings/${encodeURIComponent(opts.recordingId)}/transcribe`,
|
|
19113
|
+
{
|
|
19114
|
+
...opts.language ? { language: opts.language } : {},
|
|
19115
|
+
...opts.provider ? { provider: opts.provider } : {},
|
|
19116
|
+
...opts.model ? { model: opts.model } : {},
|
|
19117
|
+
...hasPrompt ? { prompt: opts.prompt } : { force: true }
|
|
19118
|
+
}
|
|
19119
|
+
);
|
|
19120
|
+
let result = recordingTranscribeDataSchema.parse({
|
|
19121
|
+
origin: this.auth.origin,
|
|
19122
|
+
recordingId: opts.recordingId,
|
|
19123
|
+
jobId: parsed.jobId,
|
|
19124
|
+
status: parsed.status,
|
|
19125
|
+
...typeof parsed.transcriptId === "string" || parsed.transcriptId === null ? { transcriptId: parsed.transcriptId } : {}
|
|
19126
|
+
});
|
|
19127
|
+
opts.onEvent?.({
|
|
19128
|
+
type: "progress",
|
|
19129
|
+
command: "recordings retranscribe",
|
|
19130
|
+
recordingId: opts.recordingId,
|
|
19131
|
+
jobId: result.jobId,
|
|
19132
|
+
status: result.status,
|
|
19133
|
+
...result.transcriptId ? { transcriptId: result.transcriptId } : {},
|
|
19134
|
+
message: result.status === "succeeded" ? "Transcription already ready" : "Transcription queued"
|
|
19135
|
+
});
|
|
19136
|
+
if (opts.wait && result.status !== "succeeded") {
|
|
19137
|
+
const waited = await this.waitForJob(result.jobId, {
|
|
19138
|
+
onEvent: (event) => opts.onEvent?.({
|
|
19139
|
+
...event,
|
|
19140
|
+
command: "recordings retranscribe",
|
|
19141
|
+
recordingId: opts.recordingId
|
|
19142
|
+
})
|
|
19143
|
+
});
|
|
19144
|
+
result = recordingTranscribeDataSchema.parse({
|
|
19145
|
+
origin: this.auth.origin,
|
|
19146
|
+
recordingId: opts.recordingId,
|
|
19147
|
+
jobId: waited.jobId,
|
|
19148
|
+
status: waited.status,
|
|
19149
|
+
...waited.transcriptId !== void 0 ? { transcriptId: waited.transcriptId } : {}
|
|
19150
|
+
});
|
|
19151
|
+
}
|
|
19152
|
+
return result;
|
|
19153
|
+
}
|
|
19091
19154
|
async downloadRecordingAudio(recordingId, opts = {}) {
|
|
19092
19155
|
const response = await this.request(
|
|
19093
19156
|
"GET",
|
|
@@ -20039,12 +20102,35 @@ Next cursor: ${data.nextCursor}
|
|
|
20039
20102
|
`);
|
|
20040
20103
|
if (typeof data.sizeBytes === "number") opts.stdout(` size: ${formatBytes(data.sizeBytes)}
|
|
20041
20104
|
`);
|
|
20042
|
-
if (typeof data.activeTranscriptId === "string") {
|
|
20043
|
-
opts.stdout(` activeTranscriptId: ${data.activeTranscriptId}
|
|
20105
|
+
if (typeof data.activeTranscriptId === "string") {
|
|
20106
|
+
opts.stdout(` activeTranscriptId: ${data.activeTranscriptId}
|
|
20107
|
+
`);
|
|
20108
|
+
opts.stdout(`
|
|
20109
|
+
Next:
|
|
20110
|
+
recappi transcript get ${data.activeTranscriptId}
|
|
20111
|
+
`);
|
|
20112
|
+
}
|
|
20113
|
+
return;
|
|
20114
|
+
}
|
|
20115
|
+
if (command === "recordings retranscribe" && isRecord4(data)) {
|
|
20116
|
+
opts.stdout("Transcription started\n");
|
|
20117
|
+
if (typeof data.recordingId === "string") opts.stdout(` recordingId: ${data.recordingId}
|
|
20118
|
+
`);
|
|
20119
|
+
if (typeof data.jobId === "string") opts.stdout(` jobId: ${data.jobId}
|
|
20120
|
+
`);
|
|
20121
|
+
if (typeof data.status === "string") opts.stdout(` status: ${data.status}
|
|
20122
|
+
`);
|
|
20123
|
+
if (typeof data.transcriptId === "string") {
|
|
20124
|
+
opts.stdout(` transcriptId: ${data.transcriptId}
|
|
20125
|
+
`);
|
|
20126
|
+
opts.stdout(`
|
|
20127
|
+
Next:
|
|
20128
|
+
recappi transcript get ${data.transcriptId}
|
|
20044
20129
|
`);
|
|
20130
|
+
} else if (typeof data.jobId === "string") {
|
|
20045
20131
|
opts.stdout(`
|
|
20046
20132
|
Next:
|
|
20047
|
-
recappi
|
|
20133
|
+
recappi jobs wait ${data.jobId}
|
|
20048
20134
|
`);
|
|
20049
20135
|
}
|
|
20050
20136
|
return;
|
|
@@ -20441,6 +20527,7 @@ var COMMAND_DATA_SCHEMAS = {
|
|
|
20441
20527
|
record: recordCommandDataSchema,
|
|
20442
20528
|
"recordings get": recordingDataSchema,
|
|
20443
20529
|
"recordings list": recordingListDataSchema,
|
|
20530
|
+
"recordings retranscribe": recordingTranscribeDataSchema,
|
|
20444
20531
|
"jobs list": jobListDataSchema,
|
|
20445
20532
|
"jobs wait": jobDataSchema,
|
|
20446
20533
|
"transcript get": transcriptDataSchema
|
|
@@ -20760,131 +20847,394 @@ function sidecarErrorToCliError(error51) {
|
|
|
20760
20847
|
retryable
|
|
20761
20848
|
});
|
|
20762
20849
|
}
|
|
20763
|
-
return cliError("internal.unexpected", error51.message, {
|
|
20764
|
-
data: error51,
|
|
20765
|
-
hint,
|
|
20766
|
-
retryable
|
|
20767
|
-
});
|
|
20768
|
-
}
|
|
20769
|
-
function isRecord6(value) {
|
|
20770
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20771
|
-
}
|
|
20772
|
-
function spawnMiniSidecar(opts) {
|
|
20773
|
-
if (isLaunchServicesAppCommand(opts.command)) {
|
|
20774
|
-
return spawnLaunchServicesSidecar(opts);
|
|
20850
|
+
return cliError("internal.unexpected", error51.message, {
|
|
20851
|
+
data: error51,
|
|
20852
|
+
hint,
|
|
20853
|
+
retryable
|
|
20854
|
+
});
|
|
20855
|
+
}
|
|
20856
|
+
function isRecord6(value) {
|
|
20857
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20858
|
+
}
|
|
20859
|
+
function spawnMiniSidecar(opts) {
|
|
20860
|
+
if (isLaunchServicesAppCommand(opts.command)) {
|
|
20861
|
+
return spawnLaunchServicesSidecar(opts);
|
|
20862
|
+
}
|
|
20863
|
+
const spawnProcess = opts.spawnProcess ?? spawn2;
|
|
20864
|
+
const child = spawnProcess(opts.command, opts.args ?? [], {
|
|
20865
|
+
env: opts.env,
|
|
20866
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
20867
|
+
});
|
|
20868
|
+
const client = new MiniSidecarClient({
|
|
20869
|
+
input: child.stdin,
|
|
20870
|
+
output: child.stdout,
|
|
20871
|
+
requestTimeoutMs: opts.requestTimeoutMs
|
|
20872
|
+
});
|
|
20873
|
+
return {
|
|
20874
|
+
client,
|
|
20875
|
+
kill: () => {
|
|
20876
|
+
client.close();
|
|
20877
|
+
child.kill();
|
|
20878
|
+
}
|
|
20879
|
+
};
|
|
20880
|
+
}
|
|
20881
|
+
function defaultSidecarHandshakeParams(params) {
|
|
20882
|
+
return {
|
|
20883
|
+
protocolVersion: SIDECAR_PROTOCOL_VERSION,
|
|
20884
|
+
...params
|
|
20885
|
+
};
|
|
20886
|
+
}
|
|
20887
|
+
function isLaunchServicesAppCommand(command, platform = process.platform) {
|
|
20888
|
+
return platform === "darwin" && command.endsWith(".app");
|
|
20889
|
+
}
|
|
20890
|
+
function launchServicesOpenArgs(appPath, pipes, sidecarArgs = []) {
|
|
20891
|
+
return [
|
|
20892
|
+
"-W",
|
|
20893
|
+
"-n",
|
|
20894
|
+
"-g",
|
|
20895
|
+
"--stdin",
|
|
20896
|
+
pipes.stdin,
|
|
20897
|
+
"--stdout",
|
|
20898
|
+
pipes.stdout,
|
|
20899
|
+
"--stderr",
|
|
20900
|
+
pipes.stderr,
|
|
20901
|
+
appPath,
|
|
20902
|
+
"--args",
|
|
20903
|
+
...sidecarArgs
|
|
20904
|
+
];
|
|
20905
|
+
}
|
|
20906
|
+
function spawnLaunchServicesSidecar(opts) {
|
|
20907
|
+
const spawnProcess = opts.spawnProcess ?? spawn2;
|
|
20908
|
+
const tempDir = mkdtempSync(join(tmpdir(), "recappi-sidecar-"));
|
|
20909
|
+
const pipes = {
|
|
20910
|
+
stdin: join(tempDir, "stdin.fifo"),
|
|
20911
|
+
stdout: join(tempDir, "stdout.fifo"),
|
|
20912
|
+
stderr: join(tempDir, "stderr.log")
|
|
20913
|
+
};
|
|
20914
|
+
createFifo(pipes.stdin);
|
|
20915
|
+
createFifo(pipes.stdout);
|
|
20916
|
+
const output = createReadStream(pipes.stdout);
|
|
20917
|
+
const input = createWriteStream2(pipes.stdin);
|
|
20918
|
+
const child = spawnProcess("open", launchServicesOpenArgs(opts.command, pipes, opts.args ?? []), {
|
|
20919
|
+
env: opts.env,
|
|
20920
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
20921
|
+
});
|
|
20922
|
+
const client = new MiniSidecarClient({
|
|
20923
|
+
input,
|
|
20924
|
+
output,
|
|
20925
|
+
requestTimeoutMs: opts.requestTimeoutMs
|
|
20926
|
+
});
|
|
20927
|
+
let cleaned = false;
|
|
20928
|
+
const cleanup = () => {
|
|
20929
|
+
if (cleaned) return;
|
|
20930
|
+
cleaned = true;
|
|
20931
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
20932
|
+
};
|
|
20933
|
+
child.once("exit", cleanup);
|
|
20934
|
+
child.once("error", cleanup);
|
|
20935
|
+
return {
|
|
20936
|
+
client,
|
|
20937
|
+
kill: () => {
|
|
20938
|
+
requestLaunchServicesSidecarShutdown(input);
|
|
20939
|
+
client.close();
|
|
20940
|
+
input.end();
|
|
20941
|
+
output.destroy();
|
|
20942
|
+
const killTimer = setTimeout(() => child.kill(), 2e3);
|
|
20943
|
+
killTimer.unref?.();
|
|
20944
|
+
child.once("exit", () => clearTimeout(killTimer));
|
|
20945
|
+
cleanup();
|
|
20946
|
+
}
|
|
20947
|
+
};
|
|
20948
|
+
}
|
|
20949
|
+
function requestLaunchServicesSidecarShutdown(input) {
|
|
20950
|
+
try {
|
|
20951
|
+
input.write(
|
|
20952
|
+
`${JSON.stringify({
|
|
20953
|
+
jsonrpc: "2.0",
|
|
20954
|
+
id: "shutdown",
|
|
20955
|
+
method: "recappi.shutdown",
|
|
20956
|
+
params: {}
|
|
20957
|
+
})}
|
|
20958
|
+
`
|
|
20959
|
+
);
|
|
20960
|
+
} catch {
|
|
20961
|
+
}
|
|
20962
|
+
}
|
|
20963
|
+
function createFifo(path6) {
|
|
20964
|
+
const result = spawnSync("mkfifo", [path6], { encoding: "utf8" });
|
|
20965
|
+
if (result.status !== 0) {
|
|
20966
|
+
throw cliError("record.helper_unavailable", "Recappi recording helper could not start.", {
|
|
20967
|
+
hint: result.stderr || "Could not create the local recorder pipes. Try again."
|
|
20968
|
+
});
|
|
20969
|
+
}
|
|
20970
|
+
}
|
|
20971
|
+
|
|
20972
|
+
// src/record.tsx
|
|
20973
|
+
init_LiveCaptionsScreen();
|
|
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
|
+
] });
|
|
20775
21111
|
}
|
|
20776
|
-
const
|
|
20777
|
-
const
|
|
20778
|
-
|
|
20779
|
-
|
|
20780
|
-
|
|
20781
|
-
const
|
|
20782
|
-
|
|
20783
|
-
|
|
20784
|
-
|
|
20785
|
-
|
|
20786
|
-
return {
|
|
20787
|
-
|
|
20788
|
-
|
|
20789
|
-
|
|
20790
|
-
|
|
20791
|
-
|
|
20792
|
-
|
|
20793
|
-
}
|
|
20794
|
-
|
|
20795
|
-
|
|
20796
|
-
|
|
20797
|
-
|
|
20798
|
-
|
|
20799
|
-
|
|
20800
|
-
|
|
20801
|
-
|
|
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
|
+
] });
|
|
20802
21153
|
}
|
|
20803
|
-
|
|
20804
|
-
|
|
20805
|
-
|
|
20806
|
-
"-n",
|
|
20807
|
-
"-g",
|
|
20808
|
-
"--stdin",
|
|
20809
|
-
pipes.stdin,
|
|
20810
|
-
"--stdout",
|
|
20811
|
-
pipes.stdout,
|
|
20812
|
-
"--stderr",
|
|
20813
|
-
pipes.stderr,
|
|
20814
|
-
appPath,
|
|
20815
|
-
"--args",
|
|
20816
|
-
...sidecarArgs
|
|
20817
|
-
];
|
|
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)));
|
|
20818
21157
|
}
|
|
20819
|
-
function
|
|
20820
|
-
const
|
|
20821
|
-
const
|
|
20822
|
-
|
|
20823
|
-
|
|
20824
|
-
|
|
20825
|
-
|
|
20826
|
-
|
|
20827
|
-
|
|
20828
|
-
|
|
20829
|
-
|
|
20830
|
-
const input = createWriteStream2(pipes.stdin);
|
|
20831
|
-
const child = spawnProcess("open", launchServicesOpenArgs(opts.command, pipes, opts.args ?? []), {
|
|
20832
|
-
env: opts.env,
|
|
20833
|
-
stdio: ["ignore", "ignore", "pipe"]
|
|
20834
|
-
});
|
|
20835
|
-
const client = new MiniSidecarClient({
|
|
20836
|
-
input,
|
|
20837
|
-
output,
|
|
20838
|
-
requestTimeoutMs: opts.requestTimeoutMs
|
|
20839
|
-
});
|
|
20840
|
-
let cleaned = false;
|
|
20841
|
-
const cleanup = () => {
|
|
20842
|
-
if (cleaned) return;
|
|
20843
|
-
cleaned = true;
|
|
20844
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
20845
|
-
};
|
|
20846
|
-
child.once("exit", cleanup);
|
|
20847
|
-
child.once("error", cleanup);
|
|
20848
|
-
return {
|
|
20849
|
-
client,
|
|
20850
|
-
kill: () => {
|
|
20851
|
-
requestLaunchServicesSidecarShutdown(input);
|
|
20852
|
-
client.close();
|
|
20853
|
-
input.end();
|
|
20854
|
-
output.destroy();
|
|
20855
|
-
const killTimer = setTimeout(() => child.kill(), 2e3);
|
|
20856
|
-
killTimer.unref?.();
|
|
20857
|
-
child.once("exit", () => clearTimeout(killTimer));
|
|
20858
|
-
cleanup();
|
|
20859
|
-
}
|
|
20860
|
-
};
|
|
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));
|
|
20861
21169
|
}
|
|
20862
|
-
function
|
|
20863
|
-
|
|
20864
|
-
|
|
20865
|
-
|
|
20866
|
-
|
|
20867
|
-
|
|
20868
|
-
|
|
20869
|
-
|
|
20870
|
-
|
|
20871
|
-
|
|
20872
|
-
|
|
20873
|
-
|
|
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
|
+
] });
|
|
20874
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
|
+
] });
|
|
20875
21217
|
}
|
|
20876
|
-
function
|
|
20877
|
-
|
|
20878
|
-
|
|
20879
|
-
|
|
20880
|
-
|
|
20881
|
-
}
|
|
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" };
|
|
20882
21233
|
}
|
|
21234
|
+
return { text: "Starting transcription\u2026", tone: "normal" };
|
|
20883
21235
|
}
|
|
20884
21236
|
|
|
20885
21237
|
// src/record.tsx
|
|
20886
|
-
init_LiveCaptionsScreen();
|
|
20887
|
-
init_RecordingHeroScreen();
|
|
20888
21238
|
init_liveCaptions();
|
|
20889
21239
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
20890
21240
|
var SIDECAR_COMMAND_ENV = "RECAPPI_MINI_SIDECAR";
|
|
@@ -21648,6 +21998,7 @@ async function runCli(deps = {}) {
|
|
|
21648
21998
|
}
|
|
21649
21999
|
return success2;
|
|
21650
22000
|
},
|
|
22001
|
+
retranscribeRecording: (recordingId, options = {}) => client.transcribeRecording({ recordingId, ...options }),
|
|
21651
22002
|
initialView: parsed.initialView
|
|
21652
22003
|
});
|
|
21653
22004
|
return 0;
|
|
@@ -21838,6 +22189,24 @@ async function runCli(deps = {}) {
|
|
|
21838
22189
|
renderSuccess("recordings get", data, render3);
|
|
21839
22190
|
return 0;
|
|
21840
22191
|
}
|
|
22192
|
+
if (parsed.kind === "recordings-retranscribe") {
|
|
22193
|
+
const eventMode = parsed.options.mode === "jsonl" ? "jsonl" : "human";
|
|
22194
|
+
const data = await client.transcribeRecording({
|
|
22195
|
+
recordingId: parsed.recordingId,
|
|
22196
|
+
language: parsed.language,
|
|
22197
|
+
provider: parsed.provider,
|
|
22198
|
+
model: parsed.model,
|
|
22199
|
+
prompt: parsed.prompt,
|
|
22200
|
+
scene: parsed.scene,
|
|
22201
|
+
wait: parsed.wait,
|
|
22202
|
+
onEvent: parsed.wait || mode === "jsonl" ? (event) => renderEvent(event, {
|
|
22203
|
+
...render3,
|
|
22204
|
+
mode: eventMode
|
|
22205
|
+
}) : void 0
|
|
22206
|
+
});
|
|
22207
|
+
renderSuccess("recordings retranscribe", data, render3);
|
|
22208
|
+
return 0;
|
|
22209
|
+
}
|
|
21841
22210
|
if (parsed.kind === "dashboard-stats") {
|
|
21842
22211
|
const data = await client.dashboardStats();
|
|
21843
22212
|
renderSuccess("dashboard stats", data, render3);
|
|
@@ -22162,6 +22531,25 @@ Agent mode:
|
|
|
22162
22531
|
});
|
|
22163
22532
|
}
|
|
22164
22533
|
);
|
|
22534
|
+
const recordingsRetranscribe = recordings.command("retranscribe <recordingId>").description("Start a fresh transcription job for an existing recording").option("--language <lang>", "transcription language hint", parseStringOption("--language")).option("--provider <name>", "transcription provider", parseStringOption("--provider")).option("--model <name>", "transcription model", parseStringOption("--model")).option("--prompt <text>", "custom transcription prompt/context", parseStringOption("--prompt")).option("--scene <id>", "transcription scene preset", parseStringOption("--scene")).option("--wait", "wait for the transcription job to reach a terminal state");
|
|
22535
|
+
addCommonOptions(recordingsRetranscribe);
|
|
22536
|
+
recordingsRetranscribe.action(
|
|
22537
|
+
(recordingId, _options, command) => {
|
|
22538
|
+
const opts = command.opts();
|
|
22539
|
+
onSelect({
|
|
22540
|
+
kind: "recordings-retranscribe",
|
|
22541
|
+
options: collectGlobalOptions(command),
|
|
22542
|
+
commandName: "recordings retranscribe",
|
|
22543
|
+
recordingId,
|
|
22544
|
+
...typeof opts.language === "string" ? { language: opts.language } : {},
|
|
22545
|
+
...typeof opts.provider === "string" ? { provider: opts.provider } : {},
|
|
22546
|
+
...typeof opts.model === "string" ? { model: opts.model } : {},
|
|
22547
|
+
...typeof opts.prompt === "string" ? { prompt: opts.prompt } : {},
|
|
22548
|
+
...typeof opts.scene === "string" ? { scene: opts.scene } : {},
|
|
22549
|
+
...opts.wait === true ? { wait: true } : {}
|
|
22550
|
+
});
|
|
22551
|
+
}
|
|
22552
|
+
);
|
|
22165
22553
|
const transcript = program.command("transcript").description("Transcript commands");
|
|
22166
22554
|
addCommonOptions(transcript);
|
|
22167
22555
|
const transcriptGet = transcript.command("get <transcriptId>").description("Fetch a transcript by transcript id");
|
|
@@ -22285,7 +22673,9 @@ var VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
|
22285
22673
|
"--title",
|
|
22286
22674
|
"--language",
|
|
22287
22675
|
"--provider",
|
|
22676
|
+
"--model",
|
|
22288
22677
|
"--prompt",
|
|
22678
|
+
"--scene",
|
|
22289
22679
|
"--translation-language",
|
|
22290
22680
|
"--transcription-language",
|
|
22291
22681
|
"--sidecar-command",
|