recappi 0.1.44 → 0.1.46
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 +483 -169
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -59,10 +59,19 @@ function applyRecordingEventToTelemetry(telemetry, event) {
|
|
|
59
59
|
}
|
|
60
60
|
if (event.type === "audio.level") {
|
|
61
61
|
const level = levelFromRmsDb(event.rmsDb);
|
|
62
|
+
const levelDb3 = typeof event.rmsDb === "number" && Number.isFinite(event.rmsDb) ? event.rmsDb : void 0;
|
|
62
63
|
if (event.input === "microphone") {
|
|
63
|
-
return {
|
|
64
|
+
return {
|
|
65
|
+
...telemetry,
|
|
66
|
+
level: { ...telemetry.level, mic: level },
|
|
67
|
+
levelDb: { ...telemetry.levelDb, ...levelDb3 != null ? { mic: levelDb3 } : {} }
|
|
68
|
+
};
|
|
64
69
|
}
|
|
65
|
-
return {
|
|
70
|
+
return {
|
|
71
|
+
...telemetry,
|
|
72
|
+
level: { ...telemetry.level, system: level },
|
|
73
|
+
levelDb: { ...telemetry.levelDb, ...levelDb3 != null ? { system: levelDb3 } : {} }
|
|
74
|
+
};
|
|
66
75
|
}
|
|
67
76
|
if (event.type === "error") {
|
|
68
77
|
if (isLiveCaptionSidecarError(event.code)) return telemetry;
|
|
@@ -202,10 +211,6 @@ function jobDetail(item) {
|
|
|
202
211
|
if (item.status === "failed") return "failed";
|
|
203
212
|
return "";
|
|
204
213
|
}
|
|
205
|
-
function padCell(text, width) {
|
|
206
|
-
if (text.length > width) return `${text.slice(0, Math.max(0, width - 1))}\u2026`;
|
|
207
|
-
return text.padEnd(width);
|
|
208
|
-
}
|
|
209
214
|
function charWidth(code) {
|
|
210
215
|
if (code >= 4352 && code <= 4447 || // Hangul Jamo
|
|
211
216
|
code === 9001 || code === 9002 || code >= 11904 && code <= 12350 || // CJK radicals … Kangxi
|
|
@@ -228,23 +233,6 @@ function displayWidth(text) {
|
|
|
228
233
|
for (const ch of text) width += charWidth(ch.codePointAt(0) ?? 0);
|
|
229
234
|
return width;
|
|
230
235
|
}
|
|
231
|
-
function padDisplay(text, width) {
|
|
232
|
-
const w = displayWidth(text);
|
|
233
|
-
if (w === width) return text;
|
|
234
|
-
if (w < width) return text + " ".repeat(width - w);
|
|
235
|
-
let out = "";
|
|
236
|
-
let acc = 0;
|
|
237
|
-
for (const ch of text) {
|
|
238
|
-
const cw = charWidth(ch.codePointAt(0) ?? 0);
|
|
239
|
-
if (acc + cw > width - 1) break;
|
|
240
|
-
out += ch;
|
|
241
|
-
acc += cw;
|
|
242
|
-
}
|
|
243
|
-
out += "\u2026";
|
|
244
|
-
acc += 1;
|
|
245
|
-
if (acc < width) out += " ".repeat(width - acc);
|
|
246
|
-
return out;
|
|
247
|
-
}
|
|
248
236
|
function countJobs(items) {
|
|
249
237
|
const counts = {
|
|
250
238
|
total: items.length,
|
|
@@ -456,6 +444,11 @@ function sidecarToLiveCaptionEvent(event) {
|
|
|
456
444
|
}
|
|
457
445
|
return { kind: "partial", text: event.text };
|
|
458
446
|
}
|
|
447
|
+
case "live_caption.status":
|
|
448
|
+
if (event.status === "error") {
|
|
449
|
+
return { kind: "error", message: event.message ?? "Live captions error" };
|
|
450
|
+
}
|
|
451
|
+
return { kind: "status", status: event.status };
|
|
459
452
|
case "error":
|
|
460
453
|
return event.code.startsWith("live_caption.") ? { kind: "error", message: event.message } : null;
|
|
461
454
|
case "audio.level":
|
|
@@ -612,6 +605,43 @@ function waveform(samples, width) {
|
|
|
612
605
|
});
|
|
613
606
|
return "\u2581".repeat(Math.max(0, pad)) + cells.join("");
|
|
614
607
|
}
|
|
608
|
+
function levelDb(level) {
|
|
609
|
+
if (level <= 0.03) return "silent";
|
|
610
|
+
return `${Math.round(level * 60 - 60)} dB`;
|
|
611
|
+
}
|
|
612
|
+
function MeterRow({
|
|
613
|
+
label,
|
|
614
|
+
samples,
|
|
615
|
+
level,
|
|
616
|
+
paused,
|
|
617
|
+
width
|
|
618
|
+
}) {
|
|
619
|
+
const silent = level <= 0.03;
|
|
620
|
+
return /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
621
|
+
/* @__PURE__ */ jsx3(Box2, { width: 9, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: label }) }),
|
|
622
|
+
/* @__PURE__ */ jsx3(Box2, { width, children: /* @__PURE__ */ jsx3(Text2, { color: paused ? "gray" : silent ? "yellow" : "red", children: waveform(samples, width) }) }),
|
|
623
|
+
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: ` ${paused ? "paused" : levelDb(level)}` })
|
|
624
|
+
] });
|
|
625
|
+
}
|
|
626
|
+
function ProgressBar({ fraction, width = 12 }) {
|
|
627
|
+
const f = Math.max(0, Math.min(1, fraction));
|
|
628
|
+
const filled = Math.round(f * width);
|
|
629
|
+
return /* @__PURE__ */ jsxs2(Text2, { color: "cyan", children: [
|
|
630
|
+
"\u2593".repeat(filled),
|
|
631
|
+
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "\u2591".repeat(Math.max(0, width - filled)) })
|
|
632
|
+
] });
|
|
633
|
+
}
|
|
634
|
+
function stoppedPhase(artifact) {
|
|
635
|
+
if (!artifact) return null;
|
|
636
|
+
if (artifact.uploadStatus === "uploading") {
|
|
637
|
+
return { label: "Uploading to Recappi Cloud", fraction: artifact.uploadProgress };
|
|
638
|
+
}
|
|
639
|
+
if (artifact.uploadStatus === "queued") return { label: "Queued to upload" };
|
|
640
|
+
if (artifact.transcriptionStatus === "processing") {
|
|
641
|
+
return { label: "Transcribing", fraction: artifact.transcriptionProgress };
|
|
642
|
+
}
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
615
645
|
function RecordingHeroScreen({
|
|
616
646
|
telemetry,
|
|
617
647
|
artifact,
|
|
@@ -622,11 +652,12 @@ function RecordingHeroScreen({
|
|
|
622
652
|
}) {
|
|
623
653
|
const size = useTerminalSize();
|
|
624
654
|
const [tick, setTick] = useState3(() => now());
|
|
625
|
-
const [
|
|
655
|
+
const [waveSys, setWaveSys] = useState3([]);
|
|
656
|
+
const [waveMic, setWaveMic] = useState3([]);
|
|
626
657
|
useEffect2(() => {
|
|
627
658
|
if (telemetry.level == null) return;
|
|
628
|
-
|
|
629
|
-
|
|
659
|
+
setWaveSys((w) => [...w.slice(-256), telemetry.level.system ?? 0]);
|
|
660
|
+
setWaveMic((w) => [...w.slice(-256), telemetry.level.mic ?? 0]);
|
|
630
661
|
}, [telemetry.level]);
|
|
631
662
|
useEffect2(() => {
|
|
632
663
|
const id = setInterval(() => setTick(now()), 1e3);
|
|
@@ -636,17 +667,27 @@ function RecordingHeroScreen({
|
|
|
636
667
|
const innerWidth = Math.max(10, size.columns - 4);
|
|
637
668
|
if (telemetry.status === "stopped") {
|
|
638
669
|
const handoff = stoppedHandoffCopy(artifact, canTranscribe);
|
|
670
|
+
const phase = stoppedPhase(artifact);
|
|
639
671
|
const meta3 = [
|
|
640
672
|
telemetry.durationMs != null ? formatClockMs(telemetry.durationMs) : null,
|
|
641
673
|
formatBytes2(telemetry.sizeBytes) || null
|
|
642
674
|
].filter(Boolean).join(" \xB7 ");
|
|
675
|
+
const saved = artifact?.uploadStatus === "uploaded" ? "\u2713 Saved to Recappi Cloud" : "\u2713 Saved to your Mac";
|
|
643
676
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
644
677
|
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "recappi \xB7 Recording" }),
|
|
645
678
|
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
646
|
-
/* @__PURE__ */ jsx3(Text2, { color: "green", children:
|
|
679
|
+
/* @__PURE__ */ jsx3(Text2, { color: "green", children: saved }),
|
|
647
680
|
meta3 ? /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: meta3 }) : null,
|
|
648
681
|
telemetry.savedPath ? /* @__PURE__ */ jsx3(Text2, { dimColor: true, wrap: "truncate-middle", children: telemetry.savedPath }) : null
|
|
649
682
|
] }),
|
|
683
|
+
phase ? /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
|
|
684
|
+
/* @__PURE__ */ jsx3(Text2, { color: "cyan", children: `\u25D0 ${phase.label}` }),
|
|
685
|
+
phase.fraction != null ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
686
|
+
/* @__PURE__ */ jsx3(Text2, { children: " " }),
|
|
687
|
+
/* @__PURE__ */ jsx3(ProgressBar, { fraction: phase.fraction }),
|
|
688
|
+
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: ` ${Math.round(phase.fraction * 100)}%` })
|
|
689
|
+
] }) : /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "\u2026" })
|
|
690
|
+
] }) : null,
|
|
650
691
|
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
651
692
|
/* @__PURE__ */ jsx3(Text2, { color: handoff.tone === "red" ? "red" : handoff.tone === "green" ? "green" : void 0, dimColor: handoff.tone === "dim", children: handoff.text }),
|
|
652
693
|
artifact?.error ? /* @__PURE__ */ jsx3(Text2, { color: "red", wrap: "truncate-end", children: artifact.error }) : null
|
|
@@ -663,24 +704,33 @@ function RecordingHeroScreen({
|
|
|
663
704
|
const paused = telemetry.status === "paused";
|
|
664
705
|
const starting = telemetry.status === "starting" || telemetry.status === "stopping";
|
|
665
706
|
const badge = paused ? "\u23F8 PAUSED" : starting ? "\u2026" : "\u23FA REC";
|
|
707
|
+
const meterW = Math.max(10, Math.min(48, innerWidth - 22));
|
|
708
|
+
const sizeStr = telemetry.sizeBytes ? formatBytes2(telemetry.sizeBytes) : "";
|
|
709
|
+
const context = [telemetry.sourceLabel, telemetry.micEnabled ? "Microphone" : null, sizeStr || null].filter(Boolean).join(" \xB7 ");
|
|
666
710
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, height: size.rows, children: [
|
|
667
711
|
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
668
712
|
/* @__PURE__ */ jsx3(Text2, { bold: true, color: "green", children: "recappi" }),
|
|
669
713
|
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: " \xB7 Recording" })
|
|
670
714
|
] }),
|
|
671
|
-
/* @__PURE__ */ jsxs2(Box2, {
|
|
672
|
-
/* @__PURE__ */
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
715
|
+
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, paddingX: 1, flexGrow: 1, flexDirection: "column", children: [
|
|
716
|
+
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
717
|
+
/* @__PURE__ */ jsx3(Text2, { bold: true, color: paused ? "yellow" : "red", children: badge }),
|
|
718
|
+
/* @__PURE__ */ jsx3(Text2, { children: " " }),
|
|
719
|
+
/* @__PURE__ */ jsx3(Text2, { bold: true, children: elapsed })
|
|
720
|
+
] }),
|
|
721
|
+
/* @__PURE__ */ jsx3(Box2, { marginTop: 1, flexDirection: "column", children: telemetry.level == null ? (
|
|
722
|
+
// No level telemetry yet — honest activity, not a flat meter that
|
|
723
|
+
// reads as silence (the elapsed timer above proves it's live).
|
|
677
724
|
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: paused ? "Paused" : `Capturing audio${".".repeat(Math.floor(tick / 1e3) % 3 + 1)}` })
|
|
678
|
-
) : /* @__PURE__ */
|
|
679
|
-
|
|
680
|
-
telemetry.
|
|
681
|
-
telemetry.micEnabled ? " + Microphone" : ""
|
|
725
|
+
) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
726
|
+
/* @__PURE__ */ jsx3(MeterRow, { label: "System", samples: waveSys, level: telemetry.level.system ?? 0, paused, width: meterW }),
|
|
727
|
+
telemetry.micEnabled ? /* @__PURE__ */ jsx3(MeterRow, { label: "Mic", samples: waveMic, level: telemetry.level.mic ?? 0, paused, width: meterW }) : null
|
|
682
728
|
] }) }),
|
|
683
|
-
|
|
729
|
+
/* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: context }) }),
|
|
730
|
+
captions ? /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
731
|
+
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "LIVE CAPTIONS" }),
|
|
732
|
+
/* @__PURE__ */ jsx3(HeroCaptions, { state: captions })
|
|
733
|
+
] }) : null
|
|
684
734
|
] }),
|
|
685
735
|
/* @__PURE__ */ jsx3(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
686
736
|
"q stop & save",
|
|
@@ -697,7 +747,7 @@ function HeroCaptions({ state }) {
|
|
|
697
747
|
return /* @__PURE__ */ jsx3(Text2, { color: captionError ? "yellow" : void 0, dimColor: !captionError, wrap: "truncate-end", children: captionError ?? (state.status === "live" ? "Listening for speech\u2026" : liveCaptionStatusLabel(state.status)) });
|
|
698
748
|
}
|
|
699
749
|
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
700
|
-
recent.map((line) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column",
|
|
750
|
+
recent.map((line) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
701
751
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate-end", children: [
|
|
702
752
|
line.speaker ? `${line.speaker}: ` : "",
|
|
703
753
|
line.text
|
|
@@ -710,11 +760,8 @@ function HeroCaptions({ state }) {
|
|
|
710
760
|
] });
|
|
711
761
|
}
|
|
712
762
|
function stoppedHandoffCopy(artifact, canTranscribe) {
|
|
713
|
-
if (artifact?.uploadStatus === "uploading") {
|
|
714
|
-
return { text: "
|
|
715
|
-
}
|
|
716
|
-
if (artifact?.transcriptionStatus === "processing") {
|
|
717
|
-
return { text: "Transcribing\u2026", tone: "normal" };
|
|
763
|
+
if (artifact?.uploadStatus === "uploading" || artifact?.transcriptionStatus === "processing") {
|
|
764
|
+
return { text: "esc run in background", tone: "dim" };
|
|
718
765
|
}
|
|
719
766
|
if (artifact?.transcriptionStatus === "queued") {
|
|
720
767
|
return { text: "Transcription queued \xB7 \u23CE open recording \xB7 n not now", tone: "green" };
|
|
@@ -758,13 +805,16 @@ function AccountView({ status }) {
|
|
|
758
805
|
function AccountBody({ status }) {
|
|
759
806
|
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
760
807
|
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
761
|
-
/* @__PURE__ */
|
|
808
|
+
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
809
|
+
/* @__PURE__ */ jsx5(Text3, { color: "green", children: "\u25CF " }),
|
|
810
|
+
/* @__PURE__ */ jsx5(Text3, { bold: true, children: status.email ?? status.userId ?? "Signed in" })
|
|
811
|
+
] }),
|
|
762
812
|
status.email && status.userId ? /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: status.userId }) : null,
|
|
763
813
|
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: `origin ${status.origin}` })
|
|
764
814
|
] }),
|
|
765
815
|
status.billing ? /* @__PURE__ */ jsx5(Usage, { billing: status.billing }) : null,
|
|
766
816
|
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
767
|
-
/* @__PURE__ */ jsx5(Text3, {
|
|
817
|
+
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "LOCAL STORE" }),
|
|
768
818
|
/* @__PURE__ */ jsx5(Text3, { dimColor: true, wrap: "truncate-middle", children: status.localStore.path }),
|
|
769
819
|
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
770
820
|
`${status.localStore.accountScopedArtifacts} artifact${status.localStore.accountScopedArtifacts === 1 ? "" : "s"} for this account`,
|
|
@@ -778,6 +828,7 @@ function Usage({ billing }) {
|
|
|
778
828
|
const minutesUsed = billing.minutesUsed;
|
|
779
829
|
const storageCap = billing.storageCapBytes;
|
|
780
830
|
return /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
831
|
+
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "USAGE" }),
|
|
781
832
|
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
782
833
|
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "Plan " }),
|
|
783
834
|
/* @__PURE__ */ jsx5(Text3, { bold: true, children: billing.tier })
|
|
@@ -878,9 +929,10 @@ function JobRow({
|
|
|
878
929
|
const glyph = statusGlyph(item.status, spinnerFrame);
|
|
879
930
|
const title = item.recording?.title ?? item.recordingId;
|
|
880
931
|
return /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
881
|
-
/* @__PURE__ */ jsx7(Text5, { color: "cyan", children: selected ? "\u25B8
|
|
882
|
-
/* @__PURE__ */ jsx7(Text5, { color: style.color, children:
|
|
883
|
-
/* @__PURE__ */ jsx7(
|
|
932
|
+
/* @__PURE__ */ jsx7(Box5, { width: 3, children: /* @__PURE__ */ jsx7(Text5, { color: "cyan", children: selected ? "\u25B8" : "" }) }),
|
|
933
|
+
/* @__PURE__ */ jsx7(Box5, { width: 2, children: /* @__PURE__ */ jsx7(Text5, { color: style.color, children: glyph }) }),
|
|
934
|
+
/* @__PURE__ */ jsx7(Box5, { width: 13, children: /* @__PURE__ */ jsx7(Text5, { color: style.color, children: style.label }) }),
|
|
935
|
+
/* @__PURE__ */ jsx7(Box5, { width: 26, children: /* @__PURE__ */ jsx7(Text5, { bold: selected, wrap: "truncate-end", children: title }) }),
|
|
884
936
|
/* @__PURE__ */ jsx7(Text5, { dimColor: !selected, children: jobDetail(item) })
|
|
885
937
|
] });
|
|
886
938
|
}
|
|
@@ -941,7 +993,7 @@ function recordingLayout(columns) {
|
|
|
941
993
|
const showWhen = usable >= 54;
|
|
942
994
|
const title = Math.max(
|
|
943
995
|
10,
|
|
944
|
-
usable - MARKER_W - GLYPH_W - LENGTH_W - (showWhen ? WHEN_W : 0)
|
|
996
|
+
usable - MARKER_W - GLYPH_W - LENGTH_W - (showWhen ? WHEN_W : 0) - DL_W
|
|
945
997
|
);
|
|
946
998
|
return { title, showWhen };
|
|
947
999
|
}
|
|
@@ -958,33 +1010,25 @@ function RecordingRow({
|
|
|
958
1010
|
const { glyph, color } = recordingProcessingState(item, jobStatus, spinnerFrame);
|
|
959
1011
|
const duration3 = item.durationMs ? formatClockMs(item.durationMs) : "\u2014";
|
|
960
1012
|
return /* @__PURE__ */ jsxs6(Box7, { children: [
|
|
961
|
-
/* @__PURE__ */ jsx9(Text7, { color: "cyan", children: selected ? "\u25B8
|
|
962
|
-
/* @__PURE__ */ jsx9(Text7, { color, children:
|
|
963
|
-
/* @__PURE__ */ jsx9(Text7, { bold: selected, children:
|
|
964
|
-
/* @__PURE__ */ jsx9(Text7, { dimColor: true, children:
|
|
965
|
-
showWhen ? /* @__PURE__ */ jsx9(Text7, { dimColor: true, children:
|
|
966
|
-
/* @__PURE__ */ jsx9(Text7, { color: "green", children: downloaded ? "
|
|
1013
|
+
/* @__PURE__ */ jsx9(Box7, { width: MARKER_W, children: /* @__PURE__ */ jsx9(Text7, { color: "cyan", children: selected ? "\u25B8" : "" }) }),
|
|
1014
|
+
/* @__PURE__ */ jsx9(Box7, { width: GLYPH_W, children: /* @__PURE__ */ jsx9(Text7, { color, children: glyph }) }),
|
|
1015
|
+
/* @__PURE__ */ jsx9(Box7, { width: title, children: /* @__PURE__ */ jsx9(Text7, { bold: selected, wrap: "truncate-end", children: recordingTitle2(item) }) }),
|
|
1016
|
+
/* @__PURE__ */ jsx9(Box7, { width: LENGTH_W, justifyContent: "flex-end", children: /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: duration3 }) }),
|
|
1017
|
+
showWhen ? /* @__PURE__ */ jsx9(Box7, { width: WHEN_W, justifyContent: "flex-end", children: /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: formatAge(item.createdAt, nowMs) }) }) : null,
|
|
1018
|
+
/* @__PURE__ */ jsx9(Box7, { width: DL_W, justifyContent: "flex-end", children: /* @__PURE__ */ jsx9(Text7, { color: "green", children: downloaded ? "\u2913" : "" }) })
|
|
967
1019
|
] });
|
|
968
1020
|
}
|
|
969
|
-
|
|
970
|
-
const { title, showWhen } = recordingLayout(columns);
|
|
971
|
-
return /* @__PURE__ */ jsxs6(Box7, { children: [
|
|
972
|
-
/* @__PURE__ */ jsx9(Text7, { dimColor: true, children: padDisplay("", MARKER_W + GLYPH_W) }),
|
|
973
|
-
/* @__PURE__ */ jsx9(Text7, { dimColor: true, children: padDisplay("TITLE", title) }),
|
|
974
|
-
/* @__PURE__ */ jsx9(Text7, { dimColor: true, children: padDisplay("LENGTH", LENGTH_W) }),
|
|
975
|
-
showWhen ? /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: "WHEN" }) : null
|
|
976
|
-
] });
|
|
977
|
-
}
|
|
978
|
-
var UUID_RE, MARKER_W, GLYPH_W, LENGTH_W, WHEN_W;
|
|
1021
|
+
var UUID_RE, MARKER_W, GLYPH_W, LENGTH_W, WHEN_W, DL_W;
|
|
979
1022
|
var init_RecordingRow = __esm({
|
|
980
1023
|
"src/tui/RecordingRow.tsx"() {
|
|
981
1024
|
"use strict";
|
|
982
1025
|
init_format();
|
|
983
1026
|
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
984
|
-
MARKER_W =
|
|
1027
|
+
MARKER_W = 3;
|
|
985
1028
|
GLYPH_W = 2;
|
|
986
|
-
LENGTH_W =
|
|
1029
|
+
LENGTH_W = 8;
|
|
987
1030
|
WHEN_W = 9;
|
|
1031
|
+
DL_W = 3;
|
|
988
1032
|
}
|
|
989
1033
|
});
|
|
990
1034
|
|
|
@@ -1004,28 +1048,25 @@ function RecordingsView({
|
|
|
1004
1048
|
if (items.length === 0) {
|
|
1005
1049
|
return /* @__PURE__ */ jsx10(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: "No recordings yet \u2014 run: recappi upload <file>" }) });
|
|
1006
1050
|
}
|
|
1007
|
-
return /* @__PURE__ */
|
|
1008
|
-
|
|
1009
|
-
items.
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
] }, item.recordingId);
|
|
1027
|
-
})
|
|
1028
|
-
] });
|
|
1051
|
+
return /* @__PURE__ */ jsx10(Box8, { marginTop: 1, flexDirection: "column", children: items.map((item, index) => {
|
|
1052
|
+
const bucket = dateBucket(item.createdAt, nowMs);
|
|
1053
|
+
const showHeader = index === 0 || bucket !== dateBucket(items[index - 1].createdAt, nowMs);
|
|
1054
|
+
return /* @__PURE__ */ jsxs7(React5.Fragment, { children: [
|
|
1055
|
+
showHeader ? /* @__PURE__ */ jsx10(Box8, { marginTop: index === 0 ? 0 : 1, children: /* @__PURE__ */ jsx10(Text8, { bold: true, dimColor: true, children: bucket }) }) : null,
|
|
1056
|
+
/* @__PURE__ */ jsx10(
|
|
1057
|
+
RecordingRow,
|
|
1058
|
+
{
|
|
1059
|
+
item,
|
|
1060
|
+
selected: index === selectedIndex,
|
|
1061
|
+
nowMs,
|
|
1062
|
+
columns,
|
|
1063
|
+
jobStatus: jobStatusByRecording?.get(item.recordingId),
|
|
1064
|
+
downloaded: downloadedRecordingIds?.has(item.recordingId) ?? false,
|
|
1065
|
+
spinnerFrame
|
|
1066
|
+
}
|
|
1067
|
+
)
|
|
1068
|
+
] }, item.recordingId);
|
|
1069
|
+
}) });
|
|
1029
1070
|
}
|
|
1030
1071
|
var init_RecordingsView = __esm({
|
|
1031
1072
|
"src/tui/RecordingsView.tsx"() {
|
|
@@ -1055,13 +1096,14 @@ function PeekBody({
|
|
|
1055
1096
|
const meta3 = [
|
|
1056
1097
|
item.durationMs ? formatClockMs(item.durationMs) : null,
|
|
1057
1098
|
formatBytes2(item.sizeBytes) || null,
|
|
1058
|
-
item.
|
|
1059
|
-
].filter(Boolean).join("
|
|
1099
|
+
formatAge(item.createdAt, nowMs)
|
|
1100
|
+
].filter(Boolean).join(" \xB7 ");
|
|
1060
1101
|
return /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
1061
|
-
/* @__PURE__ */ jsx11(Text9, { bold: true,
|
|
1062
|
-
/* @__PURE__ */
|
|
1063
|
-
|
|
1064
|
-
|
|
1102
|
+
/* @__PURE__ */ jsx11(Text9, { bold: true, wrap: "truncate-end", children: recordingTitle2(item) }),
|
|
1103
|
+
/* @__PURE__ */ jsxs8(Box9, { children: [
|
|
1104
|
+
/* @__PURE__ */ jsx11(Text9, { color: style.color, children: `${style.glyph} ${style.label}` }),
|
|
1105
|
+
meta3 ? /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: ` ${meta3}` }) : null
|
|
1106
|
+
] }),
|
|
1065
1107
|
/* @__PURE__ */ jsx11(Box9, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx11(SummarySection, { item, summary }) }),
|
|
1066
1108
|
/* @__PURE__ */ jsx11(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "\u23CE open \xB7 t transcript \xB7 o web" }) })
|
|
1067
1109
|
] });
|
|
@@ -1078,7 +1120,7 @@ function SummarySection({
|
|
|
1078
1120
|
}
|
|
1079
1121
|
const points = (summary.keyPoints ?? []).slice(0, 3);
|
|
1080
1122
|
return /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
1081
|
-
/* @__PURE__ */ jsx11(Text9, {
|
|
1123
|
+
/* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "SUMMARY" }),
|
|
1082
1124
|
/* @__PURE__ */ jsx11(Text9, { children: summary.tldr }),
|
|
1083
1125
|
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
|
|
1084
1126
|
] });
|
|
@@ -1093,7 +1135,7 @@ var init_RecordingPeek = __esm({
|
|
|
1093
1135
|
|
|
1094
1136
|
// src/tui/OverviewView.tsx
|
|
1095
1137
|
import { Box as Box10, Text as Text10 } from "ink";
|
|
1096
|
-
import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1138
|
+
import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1097
1139
|
function OverviewView({
|
|
1098
1140
|
recordings,
|
|
1099
1141
|
jobs,
|
|
@@ -1114,12 +1156,21 @@ function OverviewView({
|
|
|
1114
1156
|
const queued = stats?.jobs.queued ?? jobCounts.queued;
|
|
1115
1157
|
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
|
|
1116
1158
|
/* @__PURE__ */ jsxs9(Box10, { children: [
|
|
1117
|
-
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: "Recordings " }),
|
|
1118
1159
|
/* @__PURE__ */ jsx12(Text10, { bold: true, children: stats?.recordings.total ?? recordings.length }),
|
|
1119
|
-
|
|
1120
|
-
stats?.recordings.
|
|
1121
|
-
|
|
1122
|
-
|
|
1160
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " recordings" }),
|
|
1161
|
+
stats?.recordings.ready != null ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
1162
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " \xB7 " }),
|
|
1163
|
+
/* @__PURE__ */ jsx12(Text10, { color: "green", children: `${stats.recordings.ready} ready` })
|
|
1164
|
+
] }) : null,
|
|
1165
|
+
running > 0 ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
1166
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " \xB7 " }),
|
|
1167
|
+
/* @__PURE__ */ jsx12(Text10, { color: "cyan", children: `${running} transcribing` })
|
|
1168
|
+
] }) : null,
|
|
1169
|
+
queued > 0 ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
1170
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " \xB7 " }),
|
|
1171
|
+
/* @__PURE__ */ jsx12(Text10, { color: "yellow", children: `${queued} queued` })
|
|
1172
|
+
] }) : null,
|
|
1173
|
+
stats?.recordings.totalDurationMs != null ? /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: ` \xB7 ${formatClockMs(stats.recordings.totalDurationMs)} transcribed` }) : null
|
|
1123
1174
|
] }),
|
|
1124
1175
|
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", alignItems: "flex-start", children: [
|
|
1125
1176
|
/* @__PURE__ */ jsx12(Box10, { flexGrow: 1, flexDirection: "column", children: /* @__PURE__ */ jsx12(
|
|
@@ -1128,7 +1179,7 @@ function OverviewView({
|
|
|
1128
1179
|
items: recordings,
|
|
1129
1180
|
selectedIndex,
|
|
1130
1181
|
nowMs,
|
|
1131
|
-
columns,
|
|
1182
|
+
columns: showPeek ? Math.max(20, columns - peekWidth - 1) : columns,
|
|
1132
1183
|
jobStatusByRecording,
|
|
1133
1184
|
downloadedRecordingIds,
|
|
1134
1185
|
spinnerFrame
|
|
@@ -1290,7 +1341,7 @@ var init_JobDetailView = __esm({
|
|
|
1290
1341
|
// src/tui/RecordingDetailView.tsx
|
|
1291
1342
|
import React6, { useMemo as useMemo2, useState as useState4 } from "react";
|
|
1292
1343
|
import { Box as Box12, Text as Text12, useInput as useInput3 } from "ink";
|
|
1293
|
-
import { Fragment as
|
|
1344
|
+
import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1294
1345
|
function RecordingDetailView({
|
|
1295
1346
|
item,
|
|
1296
1347
|
nowMs,
|
|
@@ -1355,14 +1406,14 @@ function RecordingDetailView({
|
|
|
1355
1406
|
});
|
|
1356
1407
|
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingX: 1, children: [
|
|
1357
1408
|
/* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "\u2039 Recordings" }),
|
|
1358
|
-
/* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { bold: true,
|
|
1409
|
+
/* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { bold: true, children: title }) }),
|
|
1359
1410
|
/* @__PURE__ */ jsxs11(Text12, { children: [
|
|
1360
1411
|
/* @__PURE__ */ jsx14(Text12, { color: style.color, children: `${style.glyph} ${style.label}` }),
|
|
1361
1412
|
/* @__PURE__ */ jsx14(Text12, { dimColor: true, children: ` ${formatAge(item.createdAt, nowMs) || "\u2014"}` })
|
|
1362
1413
|
] }),
|
|
1363
1414
|
meta3 ? /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: meta3 }) : null,
|
|
1364
1415
|
/* @__PURE__ */ jsx14(AudioActionRow, { item, audio }),
|
|
1365
|
-
!item.activeTranscriptId ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Transcript not available yet" }) }) : transcript === "loading" || transcript === void 0 ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Loading\u2026" }) }) : transcript === "error" ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "(transcript unavailable)" }) }) : /* @__PURE__ */ jsxs11(
|
|
1416
|
+
!item.activeTranscriptId ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Transcript not available yet" }) }) : transcript === "loading" || transcript === void 0 ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Loading\u2026" }) }) : transcript === "error" ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "(transcript unavailable)" }) }) : /* @__PURE__ */ jsxs11(Fragment5, { children: [
|
|
1366
1417
|
/* @__PURE__ */ jsx14(TabBar, { active: tab }),
|
|
1367
1418
|
/* @__PURE__ */ jsx14(Box12, { marginTop: 1, flexDirection: "column", children: tab === "summary" ? /* @__PURE__ */ jsx14(SummaryPane, { summary, budget: paneBudget }) : tab === "chapters" ? /* @__PURE__ */ jsx14(ChaptersPane, { chapters, win: chapWin, selectedIndex: chapterSel }) : /* @__PURE__ */ jsx14(TranscriptPane, { segments, win: segWin }) })
|
|
1368
1419
|
] }),
|
|
@@ -1381,7 +1432,7 @@ function RecordingDetailView({
|
|
|
1381
1432
|
function TabBar({ active }) {
|
|
1382
1433
|
return /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: TAB_ORDER.map((tab, i) => /* @__PURE__ */ jsxs11(React6.Fragment, { children: [
|
|
1383
1434
|
i > 0 ? /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: " " }) : null,
|
|
1384
|
-
tab === active ? /* @__PURE__ */ jsx14(Text12, { inverse: true, bold: true, children: ` ${TAB_LABEL[tab]} ` }) : /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: ` ${TAB_LABEL[tab]} ` })
|
|
1435
|
+
tab === active ? /* @__PURE__ */ jsx14(Text12, { inverse: true, bold: true, color: "cyan", children: ` ${TAB_LABEL[tab]} ` }) : /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: ` ${TAB_LABEL[tab]} ` })
|
|
1385
1436
|
] }, tab)) });
|
|
1386
1437
|
}
|
|
1387
1438
|
function AudioActionRow({
|
|
@@ -1427,7 +1478,7 @@ function SummaryPane({
|
|
|
1427
1478
|
return /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `Summary ${summary?.status ?? "unavailable"}` });
|
|
1428
1479
|
}
|
|
1429
1480
|
const points = (summary.keyPoints ?? []).slice(0, Math.max(1, budget - 4));
|
|
1430
|
-
return /* @__PURE__ */ jsxs11(
|
|
1481
|
+
return /* @__PURE__ */ jsxs11(Fragment5, { children: [
|
|
1431
1482
|
/* @__PURE__ */ jsx14(Text12, { children: summary.tldr }),
|
|
1432
1483
|
points.length > 0 ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, flexDirection: "column", children: points.map((point, i) => /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `\u2022 ${point}` }, i)) }) : null
|
|
1433
1484
|
] });
|
|
@@ -1438,13 +1489,13 @@ function ChaptersPane({
|
|
|
1438
1489
|
selectedIndex
|
|
1439
1490
|
}) {
|
|
1440
1491
|
if (chapters.length === 0) return /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "No chapters" });
|
|
1441
|
-
return /* @__PURE__ */ jsxs11(
|
|
1492
|
+
return /* @__PURE__ */ jsxs11(Fragment5, { children: [
|
|
1442
1493
|
chapters.slice(win.start, win.end).map((chapter, i) => {
|
|
1443
1494
|
const index = win.start + i;
|
|
1444
1495
|
const selected = index === selectedIndex;
|
|
1445
1496
|
return /* @__PURE__ */ jsxs11(Text12, { wrap: "truncate-end", children: [
|
|
1446
1497
|
/* @__PURE__ */ jsx14(Text12, { color: "cyan", children: selected ? "\u25B8 " : " " }),
|
|
1447
|
-
/* @__PURE__ */ jsx14(Text12, {
|
|
1498
|
+
/* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `[${formatClockMs(chapter.startMs)}] ` }),
|
|
1448
1499
|
/* @__PURE__ */ jsx14(Text12, { bold: selected, children: chapter.title })
|
|
1449
1500
|
] }, index);
|
|
1450
1501
|
}),
|
|
@@ -1456,10 +1507,10 @@ function TranscriptPane({
|
|
|
1456
1507
|
win
|
|
1457
1508
|
}) {
|
|
1458
1509
|
if (segments.length === 0) return /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "(no segments)" });
|
|
1459
|
-
return /* @__PURE__ */ jsxs11(
|
|
1510
|
+
return /* @__PURE__ */ jsxs11(Fragment5, { children: [
|
|
1460
1511
|
segments.slice(win.start, win.end).map((seg, i) => /* @__PURE__ */ jsxs11(Text12, { children: [
|
|
1461
|
-
/* @__PURE__ */ jsx14(Text12, {
|
|
1462
|
-
seg.speaker ? /* @__PURE__ */ jsx14(Text12, {
|
|
1512
|
+
/* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `[${formatClockMs(seg.startMs)}] ` }),
|
|
1513
|
+
seg.speaker ? /* @__PURE__ */ jsx14(Text12, { color: "cyan", children: `${seg.speaker} ` }) : null,
|
|
1463
1514
|
/* @__PURE__ */ jsx14(Text12, { children: seg.text })
|
|
1464
1515
|
] }, win.start + i)),
|
|
1465
1516
|
win.maxScroll > 0 ? /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: ` ${win.start + 1}\u2013${win.end} / ${segments.length}` }) : null
|
|
@@ -1626,15 +1677,33 @@ var init_PermissionPreflightView = __esm({
|
|
|
1626
1677
|
});
|
|
1627
1678
|
|
|
1628
1679
|
// src/tui/RecordSetupView.tsx
|
|
1629
|
-
import { useState as useState6 } from "react";
|
|
1680
|
+
import { useEffect as useEffect3, useState as useState6 } from "react";
|
|
1630
1681
|
import { Box as Box15, Text as Text15, useInput as useInput5 } from "ink";
|
|
1631
1682
|
import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1683
|
+
function levelDb2(level) {
|
|
1684
|
+
if (level <= 0.03) return "silent";
|
|
1685
|
+
return `${Math.round(level * 60 - 60)} dB`;
|
|
1686
|
+
}
|
|
1687
|
+
function meterBar(level, width) {
|
|
1688
|
+
const f = Math.max(0, Math.min(1, level));
|
|
1689
|
+
const filled = Math.round(f * width);
|
|
1690
|
+
return "\u2587".repeat(filled) + "\u2591".repeat(Math.max(0, width - filled));
|
|
1691
|
+
}
|
|
1692
|
+
function InputMeter({ level }) {
|
|
1693
|
+
if (level == null) return /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "\u2014" });
|
|
1694
|
+
const silent = level <= 0.03;
|
|
1695
|
+
return /* @__PURE__ */ jsxs14(Text15, { children: [
|
|
1696
|
+
/* @__PURE__ */ jsx17(Text15, { color: silent ? "yellow" : "green", children: meterBar(level, METER_W) }),
|
|
1697
|
+
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: ` ${levelDb2(level)}` })
|
|
1698
|
+
] });
|
|
1699
|
+
}
|
|
1632
1700
|
function RecordSetupView({
|
|
1633
1701
|
model,
|
|
1702
|
+
levels,
|
|
1634
1703
|
onStart,
|
|
1635
|
-
onCancel
|
|
1704
|
+
onCancel,
|
|
1705
|
+
onSelectionChange
|
|
1636
1706
|
}) {
|
|
1637
|
-
const size = useTerminalSize();
|
|
1638
1707
|
const [srcIdx, setSrcIdx] = useState6(0);
|
|
1639
1708
|
const [includeMic, setIncludeMic] = useState6(true);
|
|
1640
1709
|
const [micIdx, setMicIdx] = useState6(
|
|
@@ -1645,10 +1714,18 @@ function RecordSetupView({
|
|
|
1645
1714
|
const microphones = model.microphones ?? [];
|
|
1646
1715
|
const selected = sources[Math.min(srcIdx, Math.max(0, sources.length - 1))];
|
|
1647
1716
|
const selectedMic = microphones[Math.min(micIdx, Math.max(0, microphones.length - 1))];
|
|
1648
|
-
const wide = size.columns >= 100;
|
|
1649
1717
|
const hasAppSource = sources.some((source) => source.kind === "app");
|
|
1650
1718
|
const hasMultipleSources = sources.length > 1;
|
|
1651
1719
|
const hasMultipleMicrophones = microphones.length > 1;
|
|
1720
|
+
const selection = {
|
|
1721
|
+
sourceId: selected?.id ?? "system",
|
|
1722
|
+
includeMicrophone: includeMic,
|
|
1723
|
+
...includeMic && selectedMic ? { microphoneDeviceId: selectedMic.id } : {},
|
|
1724
|
+
sceneId: model.scenes[sceneIdx]?.id
|
|
1725
|
+
};
|
|
1726
|
+
useEffect3(() => {
|
|
1727
|
+
onSelectionChange?.(selection);
|
|
1728
|
+
}, [srcIdx, includeMic, micIdx]);
|
|
1652
1729
|
useInput5((input, key) => {
|
|
1653
1730
|
if (key.upArrow || input === "k") setSrcIdx((i) => Math.max(0, i - 1));
|
|
1654
1731
|
else if (key.downArrow || input === "j") setSrcIdx((i) => Math.min(sources.length - 1, i + 1));
|
|
@@ -1656,30 +1733,29 @@ function RecordSetupView({
|
|
|
1656
1733
|
else if (input === "m" && includeMic && hasMultipleMicrophones)
|
|
1657
1734
|
setMicIdx((i) => (i + 1) % microphones.length);
|
|
1658
1735
|
else if (input === "s" && model.scenes.length > 1) setSceneIdx((i) => (i + 1) % model.scenes.length);
|
|
1659
|
-
else if (key.return && selected)
|
|
1660
|
-
|
|
1661
|
-
sourceId: selected.id,
|
|
1662
|
-
includeMicrophone: includeMic,
|
|
1663
|
-
...includeMic && selectedMic ? { microphoneDeviceId: selectedMic.id } : {},
|
|
1664
|
-
sceneId: model.scenes[sceneIdx]?.id
|
|
1665
|
-
});
|
|
1666
|
-
} else if (key.escape) onCancel();
|
|
1736
|
+
else if (key.return && selected) onStart(selection);
|
|
1737
|
+
else if (key.escape) onCancel();
|
|
1667
1738
|
});
|
|
1668
1739
|
const sourceList = /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
|
|
1669
|
-
/* @__PURE__ */
|
|
1740
|
+
/* @__PURE__ */ jsxs14(Box15, { children: [
|
|
1741
|
+
/* @__PURE__ */ jsx17(Box15, { width: 36, children: /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "SOURCE" }) }),
|
|
1742
|
+
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "INPUT" })
|
|
1743
|
+
] }),
|
|
1670
1744
|
sources.map((s, i) => {
|
|
1671
1745
|
const on = i === srcIdx;
|
|
1672
|
-
return /* @__PURE__ */ jsxs14(
|
|
1673
|
-
on ? "
|
|
1674
|
-
|
|
1746
|
+
return /* @__PURE__ */ jsxs14(Box15, { children: [
|
|
1747
|
+
/* @__PURE__ */ jsx17(Box15, { width: 36, children: /* @__PURE__ */ jsxs14(Text15, { color: on ? "cyan" : void 0, wrap: "truncate-end", children: [
|
|
1748
|
+
on ? "\u25B8 \u25CF " : " \u25CB ",
|
|
1749
|
+
s.label
|
|
1750
|
+
] }) }),
|
|
1751
|
+
/* @__PURE__ */ jsx17(InputMeter, { level: levels?.bySourceId?.[s.id] })
|
|
1675
1752
|
] }, s.id);
|
|
1676
1753
|
}),
|
|
1677
1754
|
!hasAppSource ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "No app-specific sources available right now" }) : null
|
|
1678
1755
|
] });
|
|
1679
|
-
const capturePlan = /* @__PURE__ */ jsxs14(
|
|
1680
|
-
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "
|
|
1681
|
-
/* @__PURE__ */ jsx17(Text15, { children: includeMic ? `${selected?.label ?? "System audio"} + microphone` : `${selected?.label ?? "System audio"} only` })
|
|
1682
|
-
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: includeMic ? "Mic is mixed into the recording" : "Mic stays muted" })
|
|
1756
|
+
const capturePlan = /* @__PURE__ */ jsxs14(Text15, { children: [
|
|
1757
|
+
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Capture " }),
|
|
1758
|
+
/* @__PURE__ */ jsx17(Text15, { children: includeMic ? `${selected?.label ?? "System audio"} + microphone` : `${selected?.label ?? "System audio"} only` })
|
|
1683
1759
|
] });
|
|
1684
1760
|
const shortcuts = [
|
|
1685
1761
|
hasMultipleSources ? "\u2191\u2193 source" : void 0,
|
|
@@ -1691,9 +1767,9 @@ function RecordSetupView({
|
|
|
1691
1767
|
].filter(Boolean).join(" \xB7 ");
|
|
1692
1768
|
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 1, children: [
|
|
1693
1769
|
/* @__PURE__ */ jsx17(Text15, { bold: true, color: "green", children: "New recording" }),
|
|
1694
|
-
/* @__PURE__ */ jsxs14(Box15, { marginTop: 1, flexDirection:
|
|
1695
|
-
|
|
1696
|
-
/* @__PURE__ */ jsx17(Box15, {
|
|
1770
|
+
/* @__PURE__ */ jsxs14(Box15, { marginTop: 1, flexDirection: "column", children: [
|
|
1771
|
+
sourceList,
|
|
1772
|
+
/* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: capturePlan })
|
|
1697
1773
|
] }),
|
|
1698
1774
|
/* @__PURE__ */ jsxs14(Box15, { marginTop: 1, flexDirection: "column", children: [
|
|
1699
1775
|
/* @__PURE__ */ jsxs14(Text15, { children: [
|
|
@@ -1701,12 +1777,14 @@ function RecordSetupView({
|
|
|
1701
1777
|
/* @__PURE__ */ jsx17(Text15, { color: includeMic ? "green" : "gray", children: includeMic ? "[x] include mic" : "[ ] include mic" }),
|
|
1702
1778
|
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " (space)" })
|
|
1703
1779
|
] }),
|
|
1704
|
-
selectedMic ? /* @__PURE__ */ jsxs14(
|
|
1705
|
-
/* @__PURE__ */ jsx17(Text15, { dimColor:
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1780
|
+
selectedMic ? /* @__PURE__ */ jsxs14(Box15, { children: [
|
|
1781
|
+
/* @__PURE__ */ jsx17(Box15, { width: 36, children: /* @__PURE__ */ jsxs14(Text15, { dimColor: !includeMic, wrap: "truncate-end", children: [
|
|
1782
|
+
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Mic device " }),
|
|
1783
|
+
selectedMic.label
|
|
1784
|
+
] }) }),
|
|
1785
|
+
includeMic ? /* @__PURE__ */ jsx17(InputMeter, { level: levels?.byMicrophoneId?.[selectedMic.id] }) : null
|
|
1709
1786
|
] }) : null,
|
|
1787
|
+
includeMic && hasMultipleMicrophones ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " (m to change device)" }) : null,
|
|
1710
1788
|
model.scenes.length > 0 ? /* @__PURE__ */ jsxs14(Text15, { children: [
|
|
1711
1789
|
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Scene " }),
|
|
1712
1790
|
/* @__PURE__ */ jsx17(Text15, { children: model.scenes[sceneIdx]?.label ?? "Default" }),
|
|
@@ -1716,17 +1794,18 @@ function RecordSetupView({
|
|
|
1716
1794
|
/* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: shortcuts }) })
|
|
1717
1795
|
] });
|
|
1718
1796
|
}
|
|
1797
|
+
var METER_W;
|
|
1719
1798
|
var init_RecordSetupView = __esm({
|
|
1720
1799
|
"src/tui/RecordSetupView.tsx"() {
|
|
1721
1800
|
"use strict";
|
|
1722
|
-
|
|
1801
|
+
METER_W = 12;
|
|
1723
1802
|
}
|
|
1724
1803
|
});
|
|
1725
1804
|
|
|
1726
1805
|
// src/tui/AppShell.tsx
|
|
1727
|
-
import { useCallback, useEffect as
|
|
1806
|
+
import { useCallback, useEffect as useEffect4, useState as useState7 } from "react";
|
|
1728
1807
|
import { Box as Box16, Text as Text16, useApp, useInput as useInput6 } from "ink";
|
|
1729
|
-
import { Fragment as
|
|
1808
|
+
import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1730
1809
|
function recordErrorCopy(code, message) {
|
|
1731
1810
|
switch (code) {
|
|
1732
1811
|
case "record.helper_unavailable":
|
|
@@ -1807,6 +1886,43 @@ function transcribeHandoffErrorCopy(error51) {
|
|
|
1807
1886
|
return "Could not start transcription. Please try again.";
|
|
1808
1887
|
}
|
|
1809
1888
|
}
|
|
1889
|
+
function artifactProgressPatchFromOperationEvent(event) {
|
|
1890
|
+
if (event.command !== "upload") return void 0;
|
|
1891
|
+
if (event.status === "uploading" && typeof event.percent === "number") {
|
|
1892
|
+
return {
|
|
1893
|
+
uploadStatus: "uploading",
|
|
1894
|
+
uploadProgress: Math.max(0, Math.min(1, event.percent / 100))
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
if (event.status === "finishing_upload") {
|
|
1898
|
+
return {
|
|
1899
|
+
uploadStatus: "uploading",
|
|
1900
|
+
uploadProgress: 1
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
if (event.status === "starting_transcription") {
|
|
1904
|
+
return {
|
|
1905
|
+
uploadStatus: "uploaded",
|
|
1906
|
+
uploadProgress: 1,
|
|
1907
|
+
transcriptionStatus: "queued"
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
return void 0;
|
|
1911
|
+
}
|
|
1912
|
+
function transcriptionStatusFromJob(status) {
|
|
1913
|
+
switch (status) {
|
|
1914
|
+
case "queued":
|
|
1915
|
+
return "queued";
|
|
1916
|
+
case "running":
|
|
1917
|
+
return "processing";
|
|
1918
|
+
case "succeeded":
|
|
1919
|
+
return "ready";
|
|
1920
|
+
case "failed":
|
|
1921
|
+
return "failed";
|
|
1922
|
+
default:
|
|
1923
|
+
return "not_started";
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1810
1926
|
function permissionItemsFromRecordError(data) {
|
|
1811
1927
|
const sidecarError = isRecord8(data) ? data : void 0;
|
|
1812
1928
|
const sidecarData = isRecord8(sidecarError?.data) ? sidecarError.data : void 0;
|
|
@@ -1845,6 +1961,7 @@ function AppShell({
|
|
|
1845
1961
|
listDownloadedRecordingIds,
|
|
1846
1962
|
fetchRecordSetup,
|
|
1847
1963
|
startLiveRecord,
|
|
1964
|
+
startRecordSetupPreview,
|
|
1848
1965
|
transcribeRecordingArtifact,
|
|
1849
1966
|
initialView = "overview",
|
|
1850
1967
|
openUrl: openUrl2,
|
|
@@ -1879,6 +1996,13 @@ function AppShell({
|
|
|
1879
1996
|
sources: DEFAULT_RECORDING_SOURCES,
|
|
1880
1997
|
microphones: []
|
|
1881
1998
|
});
|
|
1999
|
+
const [recordSetupSelection, setRecordSetupSelection] = useState7(
|
|
2000
|
+
DEFAULT_RECORDING_SELECTION
|
|
2001
|
+
);
|
|
2002
|
+
const [recordSetupLevels, setRecordSetupLevels] = useState7({
|
|
2003
|
+
bySourceId: {},
|
|
2004
|
+
byMicrophoneId: {}
|
|
2005
|
+
});
|
|
1882
2006
|
const recordSetupModel = {
|
|
1883
2007
|
sources: recordSetupInputs.sources.length > 0 ? recordSetupInputs.sources : DEFAULT_RECORDING_SOURCES,
|
|
1884
2008
|
microphones: recordSetupInputs.microphones ?? [],
|
|
@@ -1891,10 +2015,56 @@ function AppShell({
|
|
|
1891
2015
|
} catch {
|
|
1892
2016
|
}
|
|
1893
2017
|
}, [listDownloadedRecordingIds]);
|
|
1894
|
-
|
|
2018
|
+
useEffect4(() => {
|
|
1895
2019
|
void refreshDownloadedIds();
|
|
1896
2020
|
}, [refreshDownloadedIds]);
|
|
1897
2021
|
const screen = stack[stack.length - 1];
|
|
2022
|
+
useEffect4(() => {
|
|
2023
|
+
if (screen.kind !== "recordSetup" || !startRecordSetupPreview) return;
|
|
2024
|
+
let cancelled = false;
|
|
2025
|
+
let preview;
|
|
2026
|
+
let unsubscribe;
|
|
2027
|
+
const selection = recordSetupSelection;
|
|
2028
|
+
setRecordSetupLevels({ bySourceId: {}, byMicrophoneId: {} });
|
|
2029
|
+
startRecordSetupPreview(selection, recordSetupModel.sources).then((session) => {
|
|
2030
|
+
if (cancelled) {
|
|
2031
|
+
void session.stop();
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
preview = session;
|
|
2035
|
+
unsubscribe = session.source.onEvent((event) => {
|
|
2036
|
+
if (event.type !== "audio.level") return;
|
|
2037
|
+
const level = levelFromRmsDb(event.rmsDb);
|
|
2038
|
+
if (event.input === "microphone") {
|
|
2039
|
+
const microphoneId = event.microphoneDeviceId ?? selection.microphoneDeviceId;
|
|
2040
|
+
if (!microphoneId) return;
|
|
2041
|
+
setRecordSetupLevels((current) => ({
|
|
2042
|
+
...current,
|
|
2043
|
+
byMicrophoneId: { ...current.byMicrophoneId ?? {}, [microphoneId]: level }
|
|
2044
|
+
}));
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
2047
|
+
const sourceId = event.sourceId ?? selection.sourceId;
|
|
2048
|
+
setRecordSetupLevels((current) => ({
|
|
2049
|
+
...current,
|
|
2050
|
+
bySourceId: { ...current.bySourceId, [sourceId]: level }
|
|
2051
|
+
}));
|
|
2052
|
+
});
|
|
2053
|
+
}).catch(() => {
|
|
2054
|
+
});
|
|
2055
|
+
return () => {
|
|
2056
|
+
cancelled = true;
|
|
2057
|
+
unsubscribe?.();
|
|
2058
|
+
if (preview) void preview.stop();
|
|
2059
|
+
};
|
|
2060
|
+
}, [
|
|
2061
|
+
recordSetupSelection.includeMicrophone,
|
|
2062
|
+
recordSetupSelection.microphoneDeviceId,
|
|
2063
|
+
recordSetupSelection.sourceId,
|
|
2064
|
+
recordSetupModel.sources,
|
|
2065
|
+
screen.kind,
|
|
2066
|
+
startRecordSetupPreview
|
|
2067
|
+
]);
|
|
1898
2068
|
const beginLiveRecord = useCallback(
|
|
1899
2069
|
(selection = DEFAULT_RECORDING_SELECTION) => {
|
|
1900
2070
|
const capture = recordingCaptureMappingFromSelection(selection, recordSetupModel.sources);
|
|
@@ -1974,7 +2144,7 @@ function AppShell({
|
|
|
1974
2144
|
setStack([{ kind: "overview" }]);
|
|
1975
2145
|
}, [liveRecord, now, refreshDownloadedIds]);
|
|
1976
2146
|
const liveSession = liveRecord?.kind === "live" ? liveRecord.session : void 0;
|
|
1977
|
-
|
|
2147
|
+
useEffect4(() => {
|
|
1978
2148
|
if (!liveSession) return;
|
|
1979
2149
|
const session = liveSession;
|
|
1980
2150
|
const unsubscribe = session.source.onEvent((event) => {
|
|
@@ -1997,7 +2167,7 @@ function AppShell({
|
|
|
1997
2167
|
}, [liveSession]);
|
|
1998
2168
|
const selectedRecording = screen.kind === "overview" ? recordings[selected] : void 0;
|
|
1999
2169
|
const peekTranscriptId = selectedRecording?.activeTranscriptId ?? void 0;
|
|
2000
|
-
|
|
2170
|
+
useEffect4(() => {
|
|
2001
2171
|
if (!peekTranscriptId || summaryCache.has(peekTranscriptId)) return;
|
|
2002
2172
|
let cancelled = false;
|
|
2003
2173
|
const timer = setTimeout(() => {
|
|
@@ -2062,23 +2232,46 @@ function AppShell({
|
|
|
2062
2232
|
artifact: {
|
|
2063
2233
|
...artifact,
|
|
2064
2234
|
uploadStatus: "uploading",
|
|
2235
|
+
uploadProgress: 0,
|
|
2065
2236
|
transcriptionStatus: "not_started",
|
|
2237
|
+
transcriptionProgress: void 0,
|
|
2066
2238
|
error: void 0
|
|
2067
2239
|
}
|
|
2068
2240
|
});
|
|
2069
2241
|
try {
|
|
2070
|
-
const uploaded = await transcribeRecordingArtifact(artifact)
|
|
2242
|
+
const uploaded = await transcribeRecordingArtifact(artifact, (event) => {
|
|
2243
|
+
const patch = artifactProgressPatchFromOperationEvent(event);
|
|
2244
|
+
if (!patch) return;
|
|
2245
|
+
setLiveRecord((latest) => {
|
|
2246
|
+
if (latest?.kind !== "stopped" || latest.artifact?.sessionId !== artifact.sessionId) {
|
|
2247
|
+
return latest;
|
|
2248
|
+
}
|
|
2249
|
+
return {
|
|
2250
|
+
...latest,
|
|
2251
|
+
artifact: {
|
|
2252
|
+
...latest.artifact,
|
|
2253
|
+
...patch
|
|
2254
|
+
}
|
|
2255
|
+
};
|
|
2256
|
+
});
|
|
2257
|
+
});
|
|
2071
2258
|
const transcriptionStatus = uploaded.transcriptId != null || uploaded.status === "succeeded" || uploaded.status === "ready" ? "ready" : uploaded.status === "running" ? "processing" : uploaded.jobId ? "queued" : "not_started";
|
|
2072
|
-
setLiveRecord({
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
...
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2259
|
+
setLiveRecord((latest) => {
|
|
2260
|
+
const base = latest?.kind === "stopped" && latest.artifact?.sessionId === artifact.sessionId ? latest : current;
|
|
2261
|
+
return {
|
|
2262
|
+
...base,
|
|
2263
|
+
artifact: {
|
|
2264
|
+
...artifact,
|
|
2265
|
+
...base.artifact ?? {},
|
|
2266
|
+
recordingId: uploaded.recordingId,
|
|
2267
|
+
...uploaded.jobId ? { jobId: uploaded.jobId } : {},
|
|
2268
|
+
...uploaded.transcriptId ? { transcriptId: uploaded.transcriptId } : {},
|
|
2269
|
+
uploadStatus: "uploaded",
|
|
2270
|
+
uploadProgress: 1,
|
|
2271
|
+
transcriptionStatus,
|
|
2272
|
+
...transcriptionStatus === "ready" ? { transcriptionProgress: 1 } : {}
|
|
2273
|
+
}
|
|
2274
|
+
};
|
|
2082
2275
|
});
|
|
2083
2276
|
setNotice(
|
|
2084
2277
|
transcriptionStatus === "ready" ? "Transcription ready." : uploaded.jobId ? "Transcription queued." : "Uploaded to Recappi Cloud."
|
|
@@ -2122,13 +2315,13 @@ function AppShell({
|
|
|
2122
2315
|
setLoadingMoreRecordings(false);
|
|
2123
2316
|
}
|
|
2124
2317
|
}, [fetchRecordings, loadingMoreRecordings, recordingsNextCursor]);
|
|
2125
|
-
|
|
2318
|
+
useEffect4(() => {
|
|
2126
2319
|
void refresh({ resetRecordings: true });
|
|
2127
2320
|
const id = setInterval(() => void refresh(), pollMs);
|
|
2128
2321
|
return () => clearInterval(id);
|
|
2129
2322
|
}, [refresh, pollMs]);
|
|
2130
2323
|
const hasRunning = jobs.some((item) => item.status === "running");
|
|
2131
|
-
|
|
2324
|
+
useEffect4(() => {
|
|
2132
2325
|
if (!hasRunning) return;
|
|
2133
2326
|
const id = setInterval(() => setSpinnerFrame((f) => f + 1), spinnerMs);
|
|
2134
2327
|
return () => clearInterval(id);
|
|
@@ -2141,12 +2334,29 @@ function AppShell({
|
|
|
2141
2334
|
jobStatusByRecording.set(job.recordingId, job.status);
|
|
2142
2335
|
}
|
|
2143
2336
|
}
|
|
2337
|
+
useEffect4(() => {
|
|
2338
|
+
setLiveRecord((current) => {
|
|
2339
|
+
if (current?.kind !== "stopped" || !current.artifact?.jobId) return current;
|
|
2340
|
+
const job = jobs.find((item) => item.jobId === current.artifact?.jobId);
|
|
2341
|
+
if (!job) return current;
|
|
2342
|
+
const fraction = transcribeFraction(job);
|
|
2343
|
+
return {
|
|
2344
|
+
...current,
|
|
2345
|
+
artifact: {
|
|
2346
|
+
...current.artifact,
|
|
2347
|
+
transcriptionStatus: transcriptionStatusFromJob(job.status),
|
|
2348
|
+
...job.transcriptId ? { transcriptId: job.transcriptId } : {},
|
|
2349
|
+
...job.status === "succeeded" ? { transcriptionProgress: 1 } : fraction != null ? { transcriptionProgress: fraction } : {}
|
|
2350
|
+
}
|
|
2351
|
+
};
|
|
2352
|
+
});
|
|
2353
|
+
}, [jobs]);
|
|
2144
2354
|
const listLength = screen.kind === "jobs" ? jobs.length : screen.kind === "overview" ? recordings.length : 0;
|
|
2145
|
-
|
|
2355
|
+
useEffect4(() => {
|
|
2146
2356
|
setSelected((i) => Math.max(0, Math.min(i, Math.max(0, listLength - 1))));
|
|
2147
2357
|
}, [listLength]);
|
|
2148
2358
|
const visibleRecordingRows = Math.max(3, size.rows - 6);
|
|
2149
|
-
|
|
2359
|
+
useEffect4(() => {
|
|
2150
2360
|
if (screen.kind !== "overview" || !recordingsNextCursor) return;
|
|
2151
2361
|
const nearLoadedEnd = recordings.length - selected <= RECORDINGS_PREFETCH_REMAINING;
|
|
2152
2362
|
const underfilledViewport = recordings.length < visibleRecordingRows;
|
|
@@ -2179,7 +2389,7 @@ function AppShell({
|
|
|
2179
2389
|
[fetchTranscript]
|
|
2180
2390
|
);
|
|
2181
2391
|
const detailTranscriptId = screen.kind === "recordingDetail" ? recordings.find((r) => r.recordingId === screen.recordingId)?.activeTranscriptId : void 0;
|
|
2182
|
-
|
|
2392
|
+
useEffect4(() => {
|
|
2183
2393
|
if (!detailTranscriptId || transcriptCache.has(detailTranscriptId)) return;
|
|
2184
2394
|
let cancelled = false;
|
|
2185
2395
|
setTranscriptCache((m) => new Map(m).set(detailTranscriptId, "loading"));
|
|
@@ -2339,8 +2549,10 @@ function AppShell({
|
|
|
2339
2549
|
RecordSetupView,
|
|
2340
2550
|
{
|
|
2341
2551
|
model: recordSetupModel,
|
|
2552
|
+
levels: recordSetupLevels,
|
|
2342
2553
|
onStart: beginLiveRecord,
|
|
2343
|
-
onCancel: () => setStack((st) => st.length > 1 ? st.slice(0, -1) : [{ kind: "overview" }])
|
|
2554
|
+
onCancel: () => setStack((st) => st.length > 1 ? st.slice(0, -1) : [{ kind: "overview" }]),
|
|
2555
|
+
onSelectionChange: setRecordSetupSelection
|
|
2344
2556
|
}
|
|
2345
2557
|
) });
|
|
2346
2558
|
}
|
|
@@ -2366,7 +2578,7 @@ function AppShell({
|
|
|
2366
2578
|
return /* @__PURE__ */ jsx18(PermissionPreflightView, { items: permissionItemsFromRecordError(liveRecord.data) });
|
|
2367
2579
|
}
|
|
2368
2580
|
const copy = recordErrorCopy(liveRecord.code, liveRecord.message);
|
|
2369
|
-
return /* @__PURE__ */ jsxs15(
|
|
2581
|
+
return /* @__PURE__ */ jsxs15(Fragment6, { children: [
|
|
2370
2582
|
/* @__PURE__ */ jsx18(Text16, { color: copy.tone, children: copy.title }),
|
|
2371
2583
|
copy.detail ? /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: copy.detail }) : null
|
|
2372
2584
|
] });
|
|
@@ -2519,6 +2731,7 @@ async function runDashboard(deps) {
|
|
|
2519
2731
|
listDownloadedRecordingIds: deps.listDownloadedRecordingIds,
|
|
2520
2732
|
fetchRecordSetup: deps.fetchRecordSetup,
|
|
2521
2733
|
startLiveRecord: deps.startLiveRecord,
|
|
2734
|
+
startRecordSetupPreview: deps.startRecordSetupPreview,
|
|
2522
2735
|
transcribeRecordingArtifact: deps.transcribeRecordingArtifact,
|
|
2523
2736
|
initialView: deps.initialView ?? "overview",
|
|
2524
2737
|
openUrl,
|
|
@@ -17279,6 +17492,19 @@ var sidecarRecordingStartResultSchema = external_exports.object({
|
|
|
17279
17492
|
state: sidecarRecordingStateSchema,
|
|
17280
17493
|
localSessionRef: external_exports.string().optional()
|
|
17281
17494
|
});
|
|
17495
|
+
var sidecarLevelPreviewStartParamsSchema = external_exports.object({
|
|
17496
|
+
options: sidecarRecordingOptionsSchema
|
|
17497
|
+
});
|
|
17498
|
+
var sidecarLevelPreviewStartResultSchema = external_exports.object({
|
|
17499
|
+
previewId: external_exports.string()
|
|
17500
|
+
});
|
|
17501
|
+
var sidecarLevelPreviewStopParamsSchema = external_exports.object({
|
|
17502
|
+
previewId: external_exports.string()
|
|
17503
|
+
});
|
|
17504
|
+
var sidecarLevelPreviewStopResultSchema = external_exports.object({
|
|
17505
|
+
previewId: external_exports.string(),
|
|
17506
|
+
state: external_exports.literal("stopped")
|
|
17507
|
+
});
|
|
17282
17508
|
var sidecarSessionParamsSchema = external_exports.object({
|
|
17283
17509
|
sessionId: external_exports.string()
|
|
17284
17510
|
});
|
|
@@ -17320,6 +17546,18 @@ var sidecarRequestSchema = external_exports.discriminatedUnion("method", [
|
|
|
17320
17546
|
method: external_exports.literal("recappi.recording.start"),
|
|
17321
17547
|
params: sidecarRecordingStartParamsSchema
|
|
17322
17548
|
}),
|
|
17549
|
+
external_exports.object({
|
|
17550
|
+
jsonrpc: external_exports.literal("2.0"),
|
|
17551
|
+
id: sidecarJsonRpcIdSchema,
|
|
17552
|
+
method: external_exports.literal("recappi.recording.level_preview.start"),
|
|
17553
|
+
params: sidecarLevelPreviewStartParamsSchema
|
|
17554
|
+
}),
|
|
17555
|
+
external_exports.object({
|
|
17556
|
+
jsonrpc: external_exports.literal("2.0"),
|
|
17557
|
+
id: sidecarJsonRpcIdSchema,
|
|
17558
|
+
method: external_exports.literal("recappi.recording.level_preview.stop"),
|
|
17559
|
+
params: sidecarLevelPreviewStopParamsSchema
|
|
17560
|
+
}),
|
|
17323
17561
|
external_exports.object({
|
|
17324
17562
|
jsonrpc: external_exports.literal("2.0"),
|
|
17325
17563
|
id: sidecarJsonRpcIdSchema,
|
|
@@ -17378,8 +17616,11 @@ var sidecarEventSchema = external_exports.discriminatedUnion("type", [
|
|
|
17378
17616
|
}),
|
|
17379
17617
|
external_exports.object({
|
|
17380
17618
|
type: external_exports.literal("audio.level"),
|
|
17381
|
-
sessionId: external_exports.string(),
|
|
17619
|
+
sessionId: external_exports.string().optional(),
|
|
17620
|
+
previewId: external_exports.string().optional(),
|
|
17382
17621
|
input: external_exports.enum(["system", "microphone"]),
|
|
17622
|
+
sourceId: external_exports.string().optional(),
|
|
17623
|
+
microphoneDeviceId: external_exports.string().optional(),
|
|
17383
17624
|
rmsDb: external_exports.number().optional(),
|
|
17384
17625
|
peakDb: external_exports.number().optional(),
|
|
17385
17626
|
at: external_exports.number().int().optional(),
|
|
@@ -20219,6 +20460,20 @@ var MiniSidecarClient = class {
|
|
|
20219
20460
|
sidecarRecordingStartResultSchema
|
|
20220
20461
|
);
|
|
20221
20462
|
}
|
|
20463
|
+
startLevelPreview(params) {
|
|
20464
|
+
return this.request(
|
|
20465
|
+
"recappi.recording.level_preview.start",
|
|
20466
|
+
sidecarLevelPreviewStartParamsSchema.parse(params),
|
|
20467
|
+
sidecarLevelPreviewStartResultSchema
|
|
20468
|
+
);
|
|
20469
|
+
}
|
|
20470
|
+
stopLevelPreview(params) {
|
|
20471
|
+
return this.request(
|
|
20472
|
+
"recappi.recording.level_preview.stop",
|
|
20473
|
+
sidecarLevelPreviewStopParamsSchema.parse(params),
|
|
20474
|
+
sidecarLevelPreviewStopResultSchema
|
|
20475
|
+
);
|
|
20476
|
+
}
|
|
20222
20477
|
getPermissionStatus(params) {
|
|
20223
20478
|
return this.request(
|
|
20224
20479
|
"recappi.permissions.status",
|
|
@@ -20568,6 +20823,55 @@ async function listRecordInputs(opts) {
|
|
|
20568
20823
|
sidecar.kill();
|
|
20569
20824
|
}
|
|
20570
20825
|
}
|
|
20826
|
+
async function startRecordSetupLevelPreview(opts, selection = {
|
|
20827
|
+
sourceId: DEFAULT_RECORDING_SOURCES[0].id,
|
|
20828
|
+
includeMicrophone: true
|
|
20829
|
+
}, sources = DEFAULT_RECORDING_SOURCES) {
|
|
20830
|
+
const capture = recordingCaptureMappingFromSelection(selection, sources);
|
|
20831
|
+
const command = resolveSidecarCommand(opts);
|
|
20832
|
+
const sidecarArgs = opts.sidecarArgs ?? [];
|
|
20833
|
+
const spawnSidecar = opts.runtime?.spawnSidecar ?? spawnMiniSidecar;
|
|
20834
|
+
const sidecar = spawnSidecar({ command, args: sidecarArgs, env: opts.env });
|
|
20835
|
+
let previewId;
|
|
20836
|
+
let closed = false;
|
|
20837
|
+
const close = () => {
|
|
20838
|
+
if (closed) return;
|
|
20839
|
+
closed = true;
|
|
20840
|
+
sidecar.kill();
|
|
20841
|
+
};
|
|
20842
|
+
try {
|
|
20843
|
+
await sidecar.client.handshake(
|
|
20844
|
+
defaultSidecarHandshakeParams({
|
|
20845
|
+
client: { name: "recappi-cli", version: opts.cliVersion },
|
|
20846
|
+
capabilities: ["recording.capture"]
|
|
20847
|
+
})
|
|
20848
|
+
);
|
|
20849
|
+
const started = await sidecar.client.startLevelPreview({
|
|
20850
|
+
options: {
|
|
20851
|
+
includeSystemAudio: capture.includeSystemAudio,
|
|
20852
|
+
includeMicrophone: capture.includeMicrophone,
|
|
20853
|
+
liveCaptions: false,
|
|
20854
|
+
...capture.targetBundleId ? { targetBundleId: capture.targetBundleId } : {},
|
|
20855
|
+
...capture.microphoneDeviceId ? { microphoneDeviceId: capture.microphoneDeviceId } : {}
|
|
20856
|
+
}
|
|
20857
|
+
});
|
|
20858
|
+
previewId = started.previewId;
|
|
20859
|
+
return {
|
|
20860
|
+
source: sidecar.client,
|
|
20861
|
+
stop: async () => {
|
|
20862
|
+
try {
|
|
20863
|
+
if (previewId) await sidecar.client.stopLevelPreview({ previewId });
|
|
20864
|
+
} catch {
|
|
20865
|
+
} finally {
|
|
20866
|
+
close();
|
|
20867
|
+
}
|
|
20868
|
+
}
|
|
20869
|
+
};
|
|
20870
|
+
} catch (error51) {
|
|
20871
|
+
close();
|
|
20872
|
+
throw error51;
|
|
20873
|
+
}
|
|
20874
|
+
}
|
|
20571
20875
|
async function startRecordSession(opts) {
|
|
20572
20876
|
let retriedAfterMicrophoneGrant = false;
|
|
20573
20877
|
while (true) {
|
|
@@ -21115,14 +21419,24 @@ async function runCli(deps = {}) {
|
|
|
21115
21419
|
sources
|
|
21116
21420
|
);
|
|
21117
21421
|
},
|
|
21118
|
-
|
|
21422
|
+
startRecordSetupPreview: async (selection, sources) => startRecordSetupLevelPreview(
|
|
21423
|
+
{
|
|
21424
|
+
cliVersion: CLI_VERSION,
|
|
21425
|
+
env: deps.env,
|
|
21426
|
+
runtime: deps.recordRuntime
|
|
21427
|
+
},
|
|
21428
|
+
selection,
|
|
21429
|
+
sources
|
|
21430
|
+
),
|
|
21431
|
+
transcribeRecordingArtifact: async (artifact, onEvent) => {
|
|
21119
21432
|
if (!artifact.audioPath) {
|
|
21120
21433
|
throw cliError("input.not_found", "No local audio file is available to transcribe.");
|
|
21121
21434
|
}
|
|
21122
21435
|
const data = await client.uploadPathBatch({
|
|
21123
21436
|
inputPath: artifact.audioPath,
|
|
21124
21437
|
transcribe: true,
|
|
21125
|
-
wait: false
|
|
21438
|
+
wait: false,
|
|
21439
|
+
onEvent
|
|
21126
21440
|
});
|
|
21127
21441
|
if (data.failures.length > 0) {
|
|
21128
21442
|
const failure = data.failures[0];
|