recappi 0.1.45 → 0.1.47
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 +503 -176
- 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;
|
|
@@ -152,7 +161,7 @@ function statusStyle(status) {
|
|
|
152
161
|
case "failed":
|
|
153
162
|
return { label: "Failed", color: "red" };
|
|
154
163
|
default:
|
|
155
|
-
return { label: status, color: "
|
|
164
|
+
return { label: status, color: "gray" };
|
|
156
165
|
}
|
|
157
166
|
}
|
|
158
167
|
function spinnerChar(frame) {
|
|
@@ -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,
|
|
@@ -297,7 +285,7 @@ function recordingStatusStyle(status) {
|
|
|
297
285
|
case "aborted":
|
|
298
286
|
return { label: "Aborted", color: "gray", glyph: "\u2022" };
|
|
299
287
|
default:
|
|
300
|
-
return { label: status, color: "
|
|
288
|
+
return { label: status, color: "gray", glyph: "\u2022" };
|
|
301
289
|
}
|
|
302
290
|
}
|
|
303
291
|
function listWindow(selected, total, size) {
|
|
@@ -565,7 +553,7 @@ var init_LiveCaptionsView = __esm({
|
|
|
565
553
|
init_liveCaptions();
|
|
566
554
|
STATUS_COLOR = {
|
|
567
555
|
connecting: "yellow",
|
|
568
|
-
live: "
|
|
556
|
+
live: "cyan",
|
|
569
557
|
reconnecting: "yellow",
|
|
570
558
|
stopped: "gray",
|
|
571
559
|
error: "red"
|
|
@@ -617,6 +605,43 @@ function waveform(samples, width) {
|
|
|
617
605
|
});
|
|
618
606
|
return "\u2581".repeat(Math.max(0, pad)) + cells.join("");
|
|
619
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" : "cyan", 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
|
+
}
|
|
620
645
|
function RecordingHeroScreen({
|
|
621
646
|
telemetry,
|
|
622
647
|
artifact,
|
|
@@ -627,11 +652,12 @@ function RecordingHeroScreen({
|
|
|
627
652
|
}) {
|
|
628
653
|
const size = useTerminalSize();
|
|
629
654
|
const [tick, setTick] = useState3(() => now());
|
|
630
|
-
const [
|
|
655
|
+
const [waveSys, setWaveSys] = useState3([]);
|
|
656
|
+
const [waveMic, setWaveMic] = useState3([]);
|
|
631
657
|
useEffect2(() => {
|
|
632
658
|
if (telemetry.level == null) return;
|
|
633
|
-
|
|
634
|
-
|
|
659
|
+
setWaveSys((w) => [...w.slice(-256), telemetry.level.system ?? 0]);
|
|
660
|
+
setWaveMic((w) => [...w.slice(-256), telemetry.level.mic ?? 0]);
|
|
635
661
|
}, [telemetry.level]);
|
|
636
662
|
useEffect2(() => {
|
|
637
663
|
const id = setInterval(() => setTick(now()), 1e3);
|
|
@@ -641,17 +667,27 @@ function RecordingHeroScreen({
|
|
|
641
667
|
const innerWidth = Math.max(10, size.columns - 4);
|
|
642
668
|
if (telemetry.status === "stopped") {
|
|
643
669
|
const handoff = stoppedHandoffCopy(artifact, canTranscribe);
|
|
670
|
+
const phase = stoppedPhase(artifact);
|
|
644
671
|
const meta3 = [
|
|
645
672
|
telemetry.durationMs != null ? formatClockMs(telemetry.durationMs) : null,
|
|
646
673
|
formatBytes2(telemetry.sizeBytes) || null
|
|
647
674
|
].filter(Boolean).join(" \xB7 ");
|
|
675
|
+
const saved = artifact?.uploadStatus === "uploaded" ? "\u2713 Saved to Recappi Cloud" : "\u2713 Saved to your Mac";
|
|
648
676
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
649
677
|
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "recappi \xB7 Recording" }),
|
|
650
678
|
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
651
|
-
/* @__PURE__ */ jsx3(Text2, { color: "green", children:
|
|
679
|
+
/* @__PURE__ */ jsx3(Text2, { color: "green", children: saved }),
|
|
652
680
|
meta3 ? /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: meta3 }) : null,
|
|
653
681
|
telemetry.savedPath ? /* @__PURE__ */ jsx3(Text2, { dimColor: true, wrap: "truncate-middle", children: telemetry.savedPath }) : null
|
|
654
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,
|
|
655
691
|
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
656
692
|
/* @__PURE__ */ jsx3(Text2, { color: handoff.tone === "red" ? "red" : handoff.tone === "green" ? "green" : void 0, dimColor: handoff.tone === "dim", children: handoff.text }),
|
|
657
693
|
artifact?.error ? /* @__PURE__ */ jsx3(Text2, { color: "red", wrap: "truncate-end", children: artifact.error }) : null
|
|
@@ -668,24 +704,33 @@ function RecordingHeroScreen({
|
|
|
668
704
|
const paused = telemetry.status === "paused";
|
|
669
705
|
const starting = telemetry.status === "starting" || telemetry.status === "stopping";
|
|
670
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 ");
|
|
671
710
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, height: size.rows, children: [
|
|
672
711
|
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
673
712
|
/* @__PURE__ */ jsx3(Text2, { bold: true, color: "green", children: "recappi" }),
|
|
674
713
|
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: " \xB7 Recording" })
|
|
675
714
|
] }),
|
|
676
|
-
/* @__PURE__ */ jsxs2(Box2, {
|
|
677
|
-
/* @__PURE__ */
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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).
|
|
682
724
|
/* @__PURE__ */ jsx3(Text2, { dimColor: true, children: paused ? "Paused" : `Capturing audio${".".repeat(Math.floor(tick / 1e3) % 3 + 1)}` })
|
|
683
|
-
) : /* @__PURE__ */
|
|
684
|
-
|
|
685
|
-
telemetry.
|
|
686
|
-
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
|
|
687
728
|
] }) }),
|
|
688
|
-
|
|
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, { bold: true, dimColor: true, children: "LIVE CAPTIONS" }),
|
|
732
|
+
/* @__PURE__ */ jsx3(HeroCaptions, { state: captions })
|
|
733
|
+
] }) : null
|
|
689
734
|
] }),
|
|
690
735
|
/* @__PURE__ */ jsx3(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
691
736
|
"q stop & save",
|
|
@@ -702,7 +747,7 @@ function HeroCaptions({ state }) {
|
|
|
702
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)) });
|
|
703
748
|
}
|
|
704
749
|
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
705
|
-
recent.map((line) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column",
|
|
750
|
+
recent.map((line) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
706
751
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate-end", children: [
|
|
707
752
|
line.speaker ? `${line.speaker}: ` : "",
|
|
708
753
|
line.text
|
|
@@ -715,11 +760,8 @@ function HeroCaptions({ state }) {
|
|
|
715
760
|
] });
|
|
716
761
|
}
|
|
717
762
|
function stoppedHandoffCopy(artifact, canTranscribe) {
|
|
718
|
-
if (artifact?.uploadStatus === "uploading") {
|
|
719
|
-
return { text: "
|
|
720
|
-
}
|
|
721
|
-
if (artifact?.transcriptionStatus === "processing") {
|
|
722
|
-
return { text: "Transcribing\u2026", tone: "normal" };
|
|
763
|
+
if (artifact?.uploadStatus === "uploading" || artifact?.transcriptionStatus === "processing") {
|
|
764
|
+
return { text: "esc run in background", tone: "dim" };
|
|
723
765
|
}
|
|
724
766
|
if (artifact?.transcriptionStatus === "queued") {
|
|
725
767
|
return { text: "Transcription queued \xB7 \u23CE open recording \xB7 n not now", tone: "green" };
|
|
@@ -763,13 +805,16 @@ function AccountView({ status }) {
|
|
|
763
805
|
function AccountBody({ status }) {
|
|
764
806
|
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
765
807
|
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
766
|
-
/* @__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
|
+
] }),
|
|
767
812
|
status.email && status.userId ? /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: status.userId }) : null,
|
|
768
813
|
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: `origin ${status.origin}` })
|
|
769
814
|
] }),
|
|
770
815
|
status.billing ? /* @__PURE__ */ jsx5(Usage, { billing: status.billing }) : null,
|
|
771
816
|
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
772
|
-
/* @__PURE__ */ jsx5(Text3, { bold: true, children: "
|
|
817
|
+
/* @__PURE__ */ jsx5(Text3, { bold: true, dimColor: true, children: "LOCAL STORE" }),
|
|
773
818
|
/* @__PURE__ */ jsx5(Text3, { dimColor: true, wrap: "truncate-middle", children: status.localStore.path }),
|
|
774
819
|
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
775
820
|
`${status.localStore.accountScopedArtifacts} artifact${status.localStore.accountScopedArtifacts === 1 ? "" : "s"} for this account`,
|
|
@@ -783,6 +828,7 @@ function Usage({ billing }) {
|
|
|
783
828
|
const minutesUsed = billing.minutesUsed;
|
|
784
829
|
const storageCap = billing.storageCapBytes;
|
|
785
830
|
return /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
831
|
+
/* @__PURE__ */ jsx5(Text3, { bold: true, dimColor: true, children: "USAGE" }),
|
|
786
832
|
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
787
833
|
/* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "Plan " }),
|
|
788
834
|
/* @__PURE__ */ jsx5(Text3, { bold: true, children: billing.tier })
|
|
@@ -883,9 +929,10 @@ function JobRow({
|
|
|
883
929
|
const glyph = statusGlyph(item.status, spinnerFrame);
|
|
884
930
|
const title = item.recording?.title ?? item.recordingId;
|
|
885
931
|
return /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
886
|
-
/* @__PURE__ */ jsx7(Text5, { color: "cyan", children: selected ? "\u25B8
|
|
887
|
-
/* @__PURE__ */ jsx7(Text5, { color: style.color, children:
|
|
888
|
-
/* @__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 }) }),
|
|
889
936
|
/* @__PURE__ */ jsx7(Text5, { dimColor: !selected, children: jobDetail(item) })
|
|
890
937
|
] });
|
|
891
938
|
}
|
|
@@ -946,7 +993,7 @@ function recordingLayout(columns) {
|
|
|
946
993
|
const showWhen = usable >= 54;
|
|
947
994
|
const title = Math.max(
|
|
948
995
|
10,
|
|
949
|
-
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
|
|
950
997
|
);
|
|
951
998
|
return { title, showWhen };
|
|
952
999
|
}
|
|
@@ -963,33 +1010,25 @@ function RecordingRow({
|
|
|
963
1010
|
const { glyph, color } = recordingProcessingState(item, jobStatus, spinnerFrame);
|
|
964
1011
|
const duration3 = item.durationMs ? formatClockMs(item.durationMs) : "\u2014";
|
|
965
1012
|
return /* @__PURE__ */ jsxs6(Box7, { children: [
|
|
966
|
-
/* @__PURE__ */ jsx9(Text7, { color: "cyan", children: selected ? "\u25B8
|
|
967
|
-
/* @__PURE__ */ jsx9(Text7, { color, children:
|
|
968
|
-
/* @__PURE__ */ jsx9(Text7, { bold: selected, children:
|
|
969
|
-
/* @__PURE__ */ jsx9(Text7, { dimColor: true, children:
|
|
970
|
-
showWhen ? /* @__PURE__ */ jsx9(Text7, { dimColor: true, children:
|
|
971
|
-
/* @__PURE__ */ jsx9(Text7, { color: "green", children: downloaded ? "
|
|
972
|
-
] });
|
|
973
|
-
}
|
|
974
|
-
function RecordingHeader({ columns }) {
|
|
975
|
-
const { title, showWhen } = recordingLayout(columns);
|
|
976
|
-
return /* @__PURE__ */ jsxs6(Box7, { children: [
|
|
977
|
-
/* @__PURE__ */ jsx9(Text7, { dimColor: true, children: padDisplay("", MARKER_W + GLYPH_W) }),
|
|
978
|
-
/* @__PURE__ */ jsx9(Text7, { dimColor: true, children: padDisplay("TITLE", title) }),
|
|
979
|
-
/* @__PURE__ */ jsx9(Text7, { dimColor: true, children: padDisplay("LENGTH", LENGTH_W) }),
|
|
980
|
-
showWhen ? /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: "WHEN" }) : null
|
|
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" : "" }) })
|
|
981
1019
|
] });
|
|
982
1020
|
}
|
|
983
|
-
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;
|
|
984
1022
|
var init_RecordingRow = __esm({
|
|
985
1023
|
"src/tui/RecordingRow.tsx"() {
|
|
986
1024
|
"use strict";
|
|
987
1025
|
init_format();
|
|
988
1026
|
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
989
|
-
MARKER_W =
|
|
1027
|
+
MARKER_W = 3;
|
|
990
1028
|
GLYPH_W = 2;
|
|
991
|
-
LENGTH_W =
|
|
1029
|
+
LENGTH_W = 8;
|
|
992
1030
|
WHEN_W = 9;
|
|
1031
|
+
DL_W = 3;
|
|
993
1032
|
}
|
|
994
1033
|
});
|
|
995
1034
|
|
|
@@ -1009,28 +1048,25 @@ function RecordingsView({
|
|
|
1009
1048
|
if (items.length === 0) {
|
|
1010
1049
|
return /* @__PURE__ */ jsx10(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: "No recordings yet \u2014 run: recappi upload <file>" }) });
|
|
1011
1050
|
}
|
|
1012
|
-
return /* @__PURE__ */
|
|
1013
|
-
|
|
1014
|
-
items.
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
] }, item.recordingId);
|
|
1032
|
-
})
|
|
1033
|
-
] });
|
|
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
|
+
}) });
|
|
1034
1070
|
}
|
|
1035
1071
|
var init_RecordingsView = __esm({
|
|
1036
1072
|
"src/tui/RecordingsView.tsx"() {
|
|
@@ -1060,15 +1096,16 @@ function PeekBody({
|
|
|
1060
1096
|
const meta3 = [
|
|
1061
1097
|
item.durationMs ? formatClockMs(item.durationMs) : null,
|
|
1062
1098
|
formatBytes2(item.sizeBytes) || null,
|
|
1063
|
-
item.
|
|
1064
|
-
].filter(Boolean).join("
|
|
1099
|
+
formatAge(item.createdAt, nowMs)
|
|
1100
|
+
].filter(Boolean).join(" \xB7 ");
|
|
1065
1101
|
return /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
1066
|
-
/* @__PURE__ */ jsx11(Text9, { bold: true,
|
|
1067
|
-
/* @__PURE__ */
|
|
1068
|
-
|
|
1069
|
-
|
|
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
|
+
] }),
|
|
1070
1107
|
/* @__PURE__ */ jsx11(Box9, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx11(SummarySection, { item, summary }) }),
|
|
1071
|
-
/* @__PURE__ */ jsx11(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "\u23CE open \xB7 t transcript
|
|
1108
|
+
/* @__PURE__ */ jsx11(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "\u23CE open \xB7 t transcript" }) })
|
|
1072
1109
|
] });
|
|
1073
1110
|
}
|
|
1074
1111
|
function SummarySection({
|
|
@@ -1083,7 +1120,7 @@ function SummarySection({
|
|
|
1083
1120
|
}
|
|
1084
1121
|
const points = (summary.keyPoints ?? []).slice(0, 3);
|
|
1085
1122
|
return /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
1086
|
-
/* @__PURE__ */ jsx11(Text9, { bold: true, children: "
|
|
1123
|
+
/* @__PURE__ */ jsx11(Text9, { bold: true, dimColor: true, children: "SUMMARY" }),
|
|
1087
1124
|
/* @__PURE__ */ jsx11(Text9, { children: summary.tldr }),
|
|
1088
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
|
|
1089
1126
|
] });
|
|
@@ -1098,7 +1135,7 @@ var init_RecordingPeek = __esm({
|
|
|
1098
1135
|
|
|
1099
1136
|
// src/tui/OverviewView.tsx
|
|
1100
1137
|
import { Box as Box10, Text as Text10 } from "ink";
|
|
1101
|
-
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";
|
|
1102
1139
|
function OverviewView({
|
|
1103
1140
|
recordings,
|
|
1104
1141
|
jobs,
|
|
@@ -1119,12 +1156,21 @@ function OverviewView({
|
|
|
1119
1156
|
const queued = stats?.jobs.queued ?? jobCounts.queued;
|
|
1120
1157
|
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
|
|
1121
1158
|
/* @__PURE__ */ jsxs9(Box10, { children: [
|
|
1122
|
-
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: "Recordings " }),
|
|
1123
1159
|
/* @__PURE__ */ jsx12(Text10, { bold: true, children: stats?.recordings.total ?? recordings.length }),
|
|
1124
|
-
|
|
1125
|
-
stats?.recordings.
|
|
1126
|
-
|
|
1127
|
-
|
|
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
|
|
1128
1174
|
] }),
|
|
1129
1175
|
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", alignItems: "flex-start", children: [
|
|
1130
1176
|
/* @__PURE__ */ jsx12(Box10, { flexGrow: 1, flexDirection: "column", children: /* @__PURE__ */ jsx12(
|
|
@@ -1133,7 +1179,7 @@ function OverviewView({
|
|
|
1133
1179
|
items: recordings,
|
|
1134
1180
|
selectedIndex,
|
|
1135
1181
|
nowMs,
|
|
1136
|
-
columns,
|
|
1182
|
+
columns: showPeek ? Math.max(20, columns - peekWidth - 1) : columns,
|
|
1137
1183
|
jobStatusByRecording,
|
|
1138
1184
|
downloadedRecordingIds,
|
|
1139
1185
|
spinnerFrame
|
|
@@ -1295,7 +1341,7 @@ var init_JobDetailView = __esm({
|
|
|
1295
1341
|
// src/tui/RecordingDetailView.tsx
|
|
1296
1342
|
import React6, { useMemo as useMemo2, useState as useState4 } from "react";
|
|
1297
1343
|
import { Box as Box12, Text as Text12, useInput as useInput3 } from "ink";
|
|
1298
|
-
import { Fragment as
|
|
1344
|
+
import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1299
1345
|
function RecordingDetailView({
|
|
1300
1346
|
item,
|
|
1301
1347
|
nowMs,
|
|
@@ -1360,14 +1406,14 @@ function RecordingDetailView({
|
|
|
1360
1406
|
});
|
|
1361
1407
|
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingX: 1, children: [
|
|
1362
1408
|
/* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "\u2039 Recordings" }),
|
|
1363
|
-
/* @__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 }) }),
|
|
1364
1410
|
/* @__PURE__ */ jsxs11(Text12, { children: [
|
|
1365
1411
|
/* @__PURE__ */ jsx14(Text12, { color: style.color, children: `${style.glyph} ${style.label}` }),
|
|
1366
1412
|
/* @__PURE__ */ jsx14(Text12, { dimColor: true, children: ` ${formatAge(item.createdAt, nowMs) || "\u2014"}` })
|
|
1367
1413
|
] }),
|
|
1368
1414
|
meta3 ? /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: meta3 }) : null,
|
|
1369
1415
|
/* @__PURE__ */ jsx14(AudioActionRow, { item, audio }),
|
|
1370
|
-
!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: [
|
|
1371
1417
|
/* @__PURE__ */ jsx14(TabBar, { active: tab }),
|
|
1372
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 }) })
|
|
1373
1419
|
] }),
|
|
@@ -1386,7 +1432,7 @@ function RecordingDetailView({
|
|
|
1386
1432
|
function TabBar({ active }) {
|
|
1387
1433
|
return /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: TAB_ORDER.map((tab, i) => /* @__PURE__ */ jsxs11(React6.Fragment, { children: [
|
|
1388
1434
|
i > 0 ? /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: " " }) : null,
|
|
1389
|
-
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]} ` })
|
|
1390
1436
|
] }, tab)) });
|
|
1391
1437
|
}
|
|
1392
1438
|
function AudioActionRow({
|
|
@@ -1432,7 +1478,7 @@ function SummaryPane({
|
|
|
1432
1478
|
return /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `Summary ${summary?.status ?? "unavailable"}` });
|
|
1433
1479
|
}
|
|
1434
1480
|
const points = (summary.keyPoints ?? []).slice(0, Math.max(1, budget - 4));
|
|
1435
|
-
return /* @__PURE__ */ jsxs11(
|
|
1481
|
+
return /* @__PURE__ */ jsxs11(Fragment5, { children: [
|
|
1436
1482
|
/* @__PURE__ */ jsx14(Text12, { children: summary.tldr }),
|
|
1437
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
|
|
1438
1484
|
] });
|
|
@@ -1443,13 +1489,13 @@ function ChaptersPane({
|
|
|
1443
1489
|
selectedIndex
|
|
1444
1490
|
}) {
|
|
1445
1491
|
if (chapters.length === 0) return /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "No chapters" });
|
|
1446
|
-
return /* @__PURE__ */ jsxs11(
|
|
1492
|
+
return /* @__PURE__ */ jsxs11(Fragment5, { children: [
|
|
1447
1493
|
chapters.slice(win.start, win.end).map((chapter, i) => {
|
|
1448
1494
|
const index = win.start + i;
|
|
1449
1495
|
const selected = index === selectedIndex;
|
|
1450
1496
|
return /* @__PURE__ */ jsxs11(Text12, { wrap: "truncate-end", children: [
|
|
1451
1497
|
/* @__PURE__ */ jsx14(Text12, { color: "cyan", children: selected ? "\u25B8 " : " " }),
|
|
1452
|
-
/* @__PURE__ */ jsx14(Text12, {
|
|
1498
|
+
/* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `[${formatClockMs(chapter.startMs)}] ` }),
|
|
1453
1499
|
/* @__PURE__ */ jsx14(Text12, { bold: selected, children: chapter.title })
|
|
1454
1500
|
] }, index);
|
|
1455
1501
|
}),
|
|
@@ -1461,10 +1507,10 @@ function TranscriptPane({
|
|
|
1461
1507
|
win
|
|
1462
1508
|
}) {
|
|
1463
1509
|
if (segments.length === 0) return /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "(no segments)" });
|
|
1464
|
-
return /* @__PURE__ */ jsxs11(
|
|
1510
|
+
return /* @__PURE__ */ jsxs11(Fragment5, { children: [
|
|
1465
1511
|
segments.slice(win.start, win.end).map((seg, i) => /* @__PURE__ */ jsxs11(Text12, { children: [
|
|
1466
|
-
/* @__PURE__ */ jsx14(Text12, {
|
|
1467
|
-
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,
|
|
1468
1514
|
/* @__PURE__ */ jsx14(Text12, { children: seg.text })
|
|
1469
1515
|
] }, win.start + i)),
|
|
1470
1516
|
win.maxScroll > 0 ? /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: ` ${win.start + 1}\u2013${win.end} / ${segments.length}` }) : null
|
|
@@ -1584,7 +1630,7 @@ function PermissionPreflightView({
|
|
|
1584
1630
|
return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, children: [
|
|
1585
1631
|
/* @__PURE__ */ jsxs13(Text14, { children: [
|
|
1586
1632
|
/* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "\u2039 " }),
|
|
1587
|
-
/* @__PURE__ */ jsx16(Text14, { bold: true,
|
|
1633
|
+
/* @__PURE__ */ jsx16(Text14, { bold: true, children: "Recording permissions" })
|
|
1588
1634
|
] }),
|
|
1589
1635
|
/* @__PURE__ */ jsx16(Box14, { marginTop: 1, flexDirection: "column", children: items.length === 0 ? /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "Checking permissions\u2026" }) : items.map((item) => {
|
|
1590
1636
|
const status = statusGlyph2(item.status);
|
|
@@ -1631,60 +1677,101 @@ var init_PermissionPreflightView = __esm({
|
|
|
1631
1677
|
});
|
|
1632
1678
|
|
|
1633
1679
|
// src/tui/RecordSetupView.tsx
|
|
1634
|
-
import { useState as useState6 } from "react";
|
|
1680
|
+
import { useEffect as useEffect3, useRef, useState as useState6 } from "react";
|
|
1635
1681
|
import { Box as Box15, Text as Text15, useInput as useInput5 } from "ink";
|
|
1636
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" : "cyan", children: meterBar(level, METER_W) }),
|
|
1697
|
+
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: ` ${levelDb2(level)}` })
|
|
1698
|
+
] });
|
|
1699
|
+
}
|
|
1637
1700
|
function RecordSetupView({
|
|
1638
1701
|
model,
|
|
1702
|
+
levels,
|
|
1639
1703
|
onStart,
|
|
1640
|
-
onCancel
|
|
1704
|
+
onCancel,
|
|
1705
|
+
onSelectionChange
|
|
1641
1706
|
}) {
|
|
1642
|
-
const size = useTerminalSize();
|
|
1643
1707
|
const [srcIdx, setSrcIdx] = useState6(0);
|
|
1644
1708
|
const [includeMic, setIncludeMic] = useState6(true);
|
|
1645
1709
|
const [micIdx, setMicIdx] = useState6(
|
|
1646
1710
|
() => Math.max(0, model.microphones?.findIndex((device) => device.isDefault) ?? 0)
|
|
1647
1711
|
);
|
|
1648
1712
|
const [sceneIdx, setSceneIdx] = useState6(0);
|
|
1713
|
+
const userPickedMic = useRef(false);
|
|
1649
1714
|
const sources = model.sources;
|
|
1650
1715
|
const microphones = model.microphones ?? [];
|
|
1651
1716
|
const selected = sources[Math.min(srcIdx, Math.max(0, sources.length - 1))];
|
|
1652
1717
|
const selectedMic = microphones[Math.min(micIdx, Math.max(0, microphones.length - 1))];
|
|
1653
|
-
const wide = size.columns >= 100;
|
|
1654
1718
|
const hasAppSource = sources.some((source) => source.kind === "app");
|
|
1655
1719
|
const hasMultipleSources = sources.length > 1;
|
|
1656
1720
|
const hasMultipleMicrophones = microphones.length > 1;
|
|
1721
|
+
const selection = {
|
|
1722
|
+
sourceId: selected?.id ?? "system",
|
|
1723
|
+
includeMicrophone: includeMic,
|
|
1724
|
+
...includeMic && selectedMic ? { microphoneDeviceId: selectedMic.id } : {},
|
|
1725
|
+
sceneId: model.scenes[sceneIdx]?.id
|
|
1726
|
+
};
|
|
1727
|
+
useEffect3(() => {
|
|
1728
|
+
onSelectionChange?.(selection);
|
|
1729
|
+
}, [
|
|
1730
|
+
includeMic,
|
|
1731
|
+
onSelectionChange,
|
|
1732
|
+
selection.includeMicrophone,
|
|
1733
|
+
selection.microphoneDeviceId,
|
|
1734
|
+
selection.sceneId,
|
|
1735
|
+
selection.sourceId,
|
|
1736
|
+
srcIdx,
|
|
1737
|
+
micIdx
|
|
1738
|
+
]);
|
|
1739
|
+
useEffect3(() => {
|
|
1740
|
+
if (userPickedMic.current || microphones.length === 0) return;
|
|
1741
|
+
const di = microphones.findIndex((device) => device.isDefault);
|
|
1742
|
+
if (di > 0) setMicIdx(di);
|
|
1743
|
+
}, [microphones.length]);
|
|
1657
1744
|
useInput5((input, key) => {
|
|
1658
1745
|
if (key.upArrow || input === "k") setSrcIdx((i) => Math.max(0, i - 1));
|
|
1659
1746
|
else if (key.downArrow || input === "j") setSrcIdx((i) => Math.min(sources.length - 1, i + 1));
|
|
1660
1747
|
else if (input === " ") setIncludeMic((m) => !m);
|
|
1661
|
-
else if (input === "m" && includeMic && hasMultipleMicrophones)
|
|
1748
|
+
else if (input === "m" && includeMic && hasMultipleMicrophones) {
|
|
1749
|
+
userPickedMic.current = true;
|
|
1662
1750
|
setMicIdx((i) => (i + 1) % microphones.length);
|
|
1663
|
-
else if (input === "s" && model.scenes.length > 1) setSceneIdx((i) => (i + 1) % model.scenes.length);
|
|
1664
|
-
else if (key.return && selected)
|
|
1665
|
-
|
|
1666
|
-
sourceId: selected.id,
|
|
1667
|
-
includeMicrophone: includeMic,
|
|
1668
|
-
...includeMic && selectedMic ? { microphoneDeviceId: selectedMic.id } : {},
|
|
1669
|
-
sceneId: model.scenes[sceneIdx]?.id
|
|
1670
|
-
});
|
|
1671
|
-
} else if (key.escape) onCancel();
|
|
1751
|
+
} else if (input === "s" && model.scenes.length > 1) setSceneIdx((i) => (i + 1) % model.scenes.length);
|
|
1752
|
+
else if (key.return && selected) onStart(selection);
|
|
1753
|
+
else if (key.escape) onCancel();
|
|
1672
1754
|
});
|
|
1673
1755
|
const sourceList = /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
|
|
1674
|
-
/* @__PURE__ */
|
|
1756
|
+
/* @__PURE__ */ jsxs14(Box15, { children: [
|
|
1757
|
+
/* @__PURE__ */ jsx17(Box15, { width: 36, children: /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "SOURCE" }) }),
|
|
1758
|
+
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "INPUT" })
|
|
1759
|
+
] }),
|
|
1675
1760
|
sources.map((s, i) => {
|
|
1676
1761
|
const on = i === srcIdx;
|
|
1677
|
-
return /* @__PURE__ */ jsxs14(
|
|
1678
|
-
on ? "
|
|
1679
|
-
|
|
1762
|
+
return /* @__PURE__ */ jsxs14(Box15, { children: [
|
|
1763
|
+
/* @__PURE__ */ jsx17(Box15, { width: 36, children: /* @__PURE__ */ jsxs14(Text15, { color: on ? "cyan" : void 0, wrap: "truncate-end", children: [
|
|
1764
|
+
on ? "\u25B8 \u25CF " : " \u25CB ",
|
|
1765
|
+
s.label
|
|
1766
|
+
] }) }),
|
|
1767
|
+
/* @__PURE__ */ jsx17(InputMeter, { level: levels?.bySourceId?.[s.id] })
|
|
1680
1768
|
] }, s.id);
|
|
1681
1769
|
}),
|
|
1682
1770
|
!hasAppSource ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "No app-specific sources available right now" }) : null
|
|
1683
1771
|
] });
|
|
1684
|
-
const capturePlan = /* @__PURE__ */ jsxs14(
|
|
1685
|
-
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "
|
|
1686
|
-
/* @__PURE__ */ jsx17(Text15, { children: includeMic ? `${selected?.label ?? "System audio"} + microphone` : `${selected?.label ?? "System audio"} only` })
|
|
1687
|
-
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: includeMic ? "Mic is mixed into the recording" : "Mic stays muted" })
|
|
1772
|
+
const capturePlan = /* @__PURE__ */ jsxs14(Text15, { children: [
|
|
1773
|
+
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Capture " }),
|
|
1774
|
+
/* @__PURE__ */ jsx17(Text15, { children: includeMic ? `${selected?.label ?? "System audio"} + microphone` : `${selected?.label ?? "System audio"} only` })
|
|
1688
1775
|
] });
|
|
1689
1776
|
const shortcuts = [
|
|
1690
1777
|
hasMultipleSources ? "\u2191\u2193 source" : void 0,
|
|
@@ -1696,9 +1783,9 @@ function RecordSetupView({
|
|
|
1696
1783
|
].filter(Boolean).join(" \xB7 ");
|
|
1697
1784
|
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 1, children: [
|
|
1698
1785
|
/* @__PURE__ */ jsx17(Text15, { bold: true, color: "green", children: "New recording" }),
|
|
1699
|
-
/* @__PURE__ */ jsxs14(Box15, { marginTop: 1, flexDirection:
|
|
1700
|
-
|
|
1701
|
-
/* @__PURE__ */ jsx17(Box15, {
|
|
1786
|
+
/* @__PURE__ */ jsxs14(Box15, { marginTop: 1, flexDirection: "column", children: [
|
|
1787
|
+
sourceList,
|
|
1788
|
+
/* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: capturePlan })
|
|
1702
1789
|
] }),
|
|
1703
1790
|
/* @__PURE__ */ jsxs14(Box15, { marginTop: 1, flexDirection: "column", children: [
|
|
1704
1791
|
/* @__PURE__ */ jsxs14(Text15, { children: [
|
|
@@ -1706,12 +1793,14 @@ function RecordSetupView({
|
|
|
1706
1793
|
/* @__PURE__ */ jsx17(Text15, { color: includeMic ? "green" : "gray", children: includeMic ? "[x] include mic" : "[ ] include mic" }),
|
|
1707
1794
|
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " (space)" })
|
|
1708
1795
|
] }),
|
|
1709
|
-
selectedMic ? /* @__PURE__ */ jsxs14(
|
|
1710
|
-
/* @__PURE__ */ jsx17(Text15, { dimColor:
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1796
|
+
selectedMic ? /* @__PURE__ */ jsxs14(Box15, { children: [
|
|
1797
|
+
/* @__PURE__ */ jsx17(Box15, { width: 36, children: /* @__PURE__ */ jsxs14(Text15, { dimColor: !includeMic, wrap: "truncate-end", children: [
|
|
1798
|
+
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Mic device " }),
|
|
1799
|
+
selectedMic.label
|
|
1800
|
+
] }) }),
|
|
1801
|
+
includeMic ? /* @__PURE__ */ jsx17(InputMeter, { level: levels?.byMicrophoneId?.[selectedMic.id] }) : null
|
|
1714
1802
|
] }) : null,
|
|
1803
|
+
includeMic && hasMultipleMicrophones ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " (m to change device)" }) : null,
|
|
1715
1804
|
model.scenes.length > 0 ? /* @__PURE__ */ jsxs14(Text15, { children: [
|
|
1716
1805
|
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Scene " }),
|
|
1717
1806
|
/* @__PURE__ */ jsx17(Text15, { children: model.scenes[sceneIdx]?.label ?? "Default" }),
|
|
@@ -1721,17 +1810,18 @@ function RecordSetupView({
|
|
|
1721
1810
|
/* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: shortcuts }) })
|
|
1722
1811
|
] });
|
|
1723
1812
|
}
|
|
1813
|
+
var METER_W;
|
|
1724
1814
|
var init_RecordSetupView = __esm({
|
|
1725
1815
|
"src/tui/RecordSetupView.tsx"() {
|
|
1726
1816
|
"use strict";
|
|
1727
|
-
|
|
1817
|
+
METER_W = 12;
|
|
1728
1818
|
}
|
|
1729
1819
|
});
|
|
1730
1820
|
|
|
1731
1821
|
// src/tui/AppShell.tsx
|
|
1732
|
-
import { useCallback, useEffect as
|
|
1822
|
+
import { useCallback, useEffect as useEffect4, useState as useState7 } from "react";
|
|
1733
1823
|
import { Box as Box16, Text as Text16, useApp, useInput as useInput6 } from "ink";
|
|
1734
|
-
import { Fragment as
|
|
1824
|
+
import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1735
1825
|
function recordErrorCopy(code, message) {
|
|
1736
1826
|
switch (code) {
|
|
1737
1827
|
case "record.helper_unavailable":
|
|
@@ -1812,6 +1902,43 @@ function transcribeHandoffErrorCopy(error51) {
|
|
|
1812
1902
|
return "Could not start transcription. Please try again.";
|
|
1813
1903
|
}
|
|
1814
1904
|
}
|
|
1905
|
+
function artifactProgressPatchFromOperationEvent(event) {
|
|
1906
|
+
if (event.command !== "upload") return void 0;
|
|
1907
|
+
if (event.status === "uploading" && typeof event.percent === "number") {
|
|
1908
|
+
return {
|
|
1909
|
+
uploadStatus: "uploading",
|
|
1910
|
+
uploadProgress: Math.max(0, Math.min(1, event.percent / 100))
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
if (event.status === "finishing_upload") {
|
|
1914
|
+
return {
|
|
1915
|
+
uploadStatus: "uploading",
|
|
1916
|
+
uploadProgress: 1
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
if (event.status === "starting_transcription") {
|
|
1920
|
+
return {
|
|
1921
|
+
uploadStatus: "uploaded",
|
|
1922
|
+
uploadProgress: 1,
|
|
1923
|
+
transcriptionStatus: "queued"
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
return void 0;
|
|
1927
|
+
}
|
|
1928
|
+
function transcriptionStatusFromJob(status) {
|
|
1929
|
+
switch (status) {
|
|
1930
|
+
case "queued":
|
|
1931
|
+
return "queued";
|
|
1932
|
+
case "running":
|
|
1933
|
+
return "processing";
|
|
1934
|
+
case "succeeded":
|
|
1935
|
+
return "ready";
|
|
1936
|
+
case "failed":
|
|
1937
|
+
return "failed";
|
|
1938
|
+
default:
|
|
1939
|
+
return "not_started";
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1815
1942
|
function permissionItemsFromRecordError(data) {
|
|
1816
1943
|
const sidecarError = isRecord8(data) ? data : void 0;
|
|
1817
1944
|
const sidecarData = isRecord8(sidecarError?.data) ? sidecarError.data : void 0;
|
|
@@ -1850,6 +1977,7 @@ function AppShell({
|
|
|
1850
1977
|
listDownloadedRecordingIds,
|
|
1851
1978
|
fetchRecordSetup,
|
|
1852
1979
|
startLiveRecord,
|
|
1980
|
+
startRecordSetupPreview,
|
|
1853
1981
|
transcribeRecordingArtifact,
|
|
1854
1982
|
initialView = "overview",
|
|
1855
1983
|
openUrl: openUrl2,
|
|
@@ -1884,6 +2012,13 @@ function AppShell({
|
|
|
1884
2012
|
sources: DEFAULT_RECORDING_SOURCES,
|
|
1885
2013
|
microphones: []
|
|
1886
2014
|
});
|
|
2015
|
+
const [recordSetupSelection, setRecordSetupSelection] = useState7(
|
|
2016
|
+
DEFAULT_RECORDING_SELECTION
|
|
2017
|
+
);
|
|
2018
|
+
const [recordSetupLevels, setRecordSetupLevels] = useState7({
|
|
2019
|
+
bySourceId: {},
|
|
2020
|
+
byMicrophoneId: {}
|
|
2021
|
+
});
|
|
1887
2022
|
const recordSetupModel = {
|
|
1888
2023
|
sources: recordSetupInputs.sources.length > 0 ? recordSetupInputs.sources : DEFAULT_RECORDING_SOURCES,
|
|
1889
2024
|
microphones: recordSetupInputs.microphones ?? [],
|
|
@@ -1896,10 +2031,58 @@ function AppShell({
|
|
|
1896
2031
|
} catch {
|
|
1897
2032
|
}
|
|
1898
2033
|
}, [listDownloadedRecordingIds]);
|
|
1899
|
-
|
|
2034
|
+
useEffect4(() => {
|
|
1900
2035
|
void refreshDownloadedIds();
|
|
1901
2036
|
}, [refreshDownloadedIds]);
|
|
1902
2037
|
const screen = stack[stack.length - 1];
|
|
2038
|
+
useEffect4(() => {
|
|
2039
|
+
if (screen.kind !== "recordSetup" || !startRecordSetupPreview) return;
|
|
2040
|
+
let cancelled = false;
|
|
2041
|
+
let preview;
|
|
2042
|
+
let unsubscribe;
|
|
2043
|
+
const selection = recordSetupSelection;
|
|
2044
|
+
setRecordSetupLevels({ bySourceId: {}, byMicrophoneId: {} });
|
|
2045
|
+
startRecordSetupPreview(selection, recordSetupModel.sources).then((session) => {
|
|
2046
|
+
if (cancelled) {
|
|
2047
|
+
void session.stop();
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
preview = session;
|
|
2051
|
+
unsubscribe = session.source.onEvent((event) => {
|
|
2052
|
+
if (event.type !== "audio.level") return;
|
|
2053
|
+
const level = levelFromRmsDb(event.rmsDb);
|
|
2054
|
+
if (event.input === "microphone") {
|
|
2055
|
+
const microphones = recordSetupModel.microphones ?? [];
|
|
2056
|
+
const microphoneId = event.microphoneDeviceId ?? selection.microphoneDeviceId ?? microphones.find((device) => device.isDefault)?.id ?? microphones[0]?.id;
|
|
2057
|
+
if (!microphoneId) return;
|
|
2058
|
+
setRecordSetupLevels((current) => ({
|
|
2059
|
+
...current,
|
|
2060
|
+
byMicrophoneId: { ...current.byMicrophoneId ?? {}, [microphoneId]: level }
|
|
2061
|
+
}));
|
|
2062
|
+
return;
|
|
2063
|
+
}
|
|
2064
|
+
const sourceId = event.sourceId ?? selection.sourceId;
|
|
2065
|
+
setRecordSetupLevels((current) => ({
|
|
2066
|
+
...current,
|
|
2067
|
+
bySourceId: { ...current.bySourceId, [sourceId]: level }
|
|
2068
|
+
}));
|
|
2069
|
+
});
|
|
2070
|
+
}).catch(() => {
|
|
2071
|
+
});
|
|
2072
|
+
return () => {
|
|
2073
|
+
cancelled = true;
|
|
2074
|
+
unsubscribe?.();
|
|
2075
|
+
if (preview) void preview.stop();
|
|
2076
|
+
};
|
|
2077
|
+
}, [
|
|
2078
|
+
recordSetupSelection.includeMicrophone,
|
|
2079
|
+
recordSetupSelection.microphoneDeviceId,
|
|
2080
|
+
recordSetupSelection.sourceId,
|
|
2081
|
+
recordSetupModel.microphones,
|
|
2082
|
+
recordSetupModel.sources,
|
|
2083
|
+
screen.kind,
|
|
2084
|
+
startRecordSetupPreview
|
|
2085
|
+
]);
|
|
1903
2086
|
const beginLiveRecord = useCallback(
|
|
1904
2087
|
(selection = DEFAULT_RECORDING_SELECTION) => {
|
|
1905
2088
|
const capture = recordingCaptureMappingFromSelection(selection, recordSetupModel.sources);
|
|
@@ -1979,7 +2162,7 @@ function AppShell({
|
|
|
1979
2162
|
setStack([{ kind: "overview" }]);
|
|
1980
2163
|
}, [liveRecord, now, refreshDownloadedIds]);
|
|
1981
2164
|
const liveSession = liveRecord?.kind === "live" ? liveRecord.session : void 0;
|
|
1982
|
-
|
|
2165
|
+
useEffect4(() => {
|
|
1983
2166
|
if (!liveSession) return;
|
|
1984
2167
|
const session = liveSession;
|
|
1985
2168
|
const unsubscribe = session.source.onEvent((event) => {
|
|
@@ -2002,7 +2185,7 @@ function AppShell({
|
|
|
2002
2185
|
}, [liveSession]);
|
|
2003
2186
|
const selectedRecording = screen.kind === "overview" ? recordings[selected] : void 0;
|
|
2004
2187
|
const peekTranscriptId = selectedRecording?.activeTranscriptId ?? void 0;
|
|
2005
|
-
|
|
2188
|
+
useEffect4(() => {
|
|
2006
2189
|
if (!peekTranscriptId || summaryCache.has(peekTranscriptId)) return;
|
|
2007
2190
|
let cancelled = false;
|
|
2008
2191
|
const timer = setTimeout(() => {
|
|
@@ -2067,23 +2250,46 @@ function AppShell({
|
|
|
2067
2250
|
artifact: {
|
|
2068
2251
|
...artifact,
|
|
2069
2252
|
uploadStatus: "uploading",
|
|
2253
|
+
uploadProgress: 0,
|
|
2070
2254
|
transcriptionStatus: "not_started",
|
|
2255
|
+
transcriptionProgress: void 0,
|
|
2071
2256
|
error: void 0
|
|
2072
2257
|
}
|
|
2073
2258
|
});
|
|
2074
2259
|
try {
|
|
2075
|
-
const uploaded = await transcribeRecordingArtifact(artifact)
|
|
2260
|
+
const uploaded = await transcribeRecordingArtifact(artifact, (event) => {
|
|
2261
|
+
const patch = artifactProgressPatchFromOperationEvent(event);
|
|
2262
|
+
if (!patch) return;
|
|
2263
|
+
setLiveRecord((latest) => {
|
|
2264
|
+
if (latest?.kind !== "stopped" || latest.artifact?.sessionId !== artifact.sessionId) {
|
|
2265
|
+
return latest;
|
|
2266
|
+
}
|
|
2267
|
+
return {
|
|
2268
|
+
...latest,
|
|
2269
|
+
artifact: {
|
|
2270
|
+
...latest.artifact,
|
|
2271
|
+
...patch
|
|
2272
|
+
}
|
|
2273
|
+
};
|
|
2274
|
+
});
|
|
2275
|
+
});
|
|
2076
2276
|
const transcriptionStatus = uploaded.transcriptId != null || uploaded.status === "succeeded" || uploaded.status === "ready" ? "ready" : uploaded.status === "running" ? "processing" : uploaded.jobId ? "queued" : "not_started";
|
|
2077
|
-
setLiveRecord({
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
...
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2277
|
+
setLiveRecord((latest) => {
|
|
2278
|
+
const base = latest?.kind === "stopped" && latest.artifact?.sessionId === artifact.sessionId ? latest : current;
|
|
2279
|
+
return {
|
|
2280
|
+
...base,
|
|
2281
|
+
artifact: {
|
|
2282
|
+
...artifact,
|
|
2283
|
+
...base.artifact ?? {},
|
|
2284
|
+
recordingId: uploaded.recordingId,
|
|
2285
|
+
...uploaded.jobId ? { jobId: uploaded.jobId } : {},
|
|
2286
|
+
...uploaded.transcriptId ? { transcriptId: uploaded.transcriptId } : {},
|
|
2287
|
+
uploadStatus: "uploaded",
|
|
2288
|
+
uploadProgress: 1,
|
|
2289
|
+
transcriptionStatus,
|
|
2290
|
+
...transcriptionStatus === "ready" ? { transcriptionProgress: 1 } : {}
|
|
2291
|
+
}
|
|
2292
|
+
};
|
|
2087
2293
|
});
|
|
2088
2294
|
setNotice(
|
|
2089
2295
|
transcriptionStatus === "ready" ? "Transcription ready." : uploaded.jobId ? "Transcription queued." : "Uploaded to Recappi Cloud."
|
|
@@ -2127,13 +2333,13 @@ function AppShell({
|
|
|
2127
2333
|
setLoadingMoreRecordings(false);
|
|
2128
2334
|
}
|
|
2129
2335
|
}, [fetchRecordings, loadingMoreRecordings, recordingsNextCursor]);
|
|
2130
|
-
|
|
2336
|
+
useEffect4(() => {
|
|
2131
2337
|
void refresh({ resetRecordings: true });
|
|
2132
2338
|
const id = setInterval(() => void refresh(), pollMs);
|
|
2133
2339
|
return () => clearInterval(id);
|
|
2134
2340
|
}, [refresh, pollMs]);
|
|
2135
2341
|
const hasRunning = jobs.some((item) => item.status === "running");
|
|
2136
|
-
|
|
2342
|
+
useEffect4(() => {
|
|
2137
2343
|
if (!hasRunning) return;
|
|
2138
2344
|
const id = setInterval(() => setSpinnerFrame((f) => f + 1), spinnerMs);
|
|
2139
2345
|
return () => clearInterval(id);
|
|
@@ -2146,12 +2352,29 @@ function AppShell({
|
|
|
2146
2352
|
jobStatusByRecording.set(job.recordingId, job.status);
|
|
2147
2353
|
}
|
|
2148
2354
|
}
|
|
2355
|
+
useEffect4(() => {
|
|
2356
|
+
setLiveRecord((current) => {
|
|
2357
|
+
if (current?.kind !== "stopped" || !current.artifact?.jobId) return current;
|
|
2358
|
+
const job = jobs.find((item) => item.jobId === current.artifact?.jobId);
|
|
2359
|
+
if (!job) return current;
|
|
2360
|
+
const fraction = transcribeFraction(job);
|
|
2361
|
+
return {
|
|
2362
|
+
...current,
|
|
2363
|
+
artifact: {
|
|
2364
|
+
...current.artifact,
|
|
2365
|
+
transcriptionStatus: transcriptionStatusFromJob(job.status),
|
|
2366
|
+
...job.transcriptId ? { transcriptId: job.transcriptId } : {},
|
|
2367
|
+
...job.status === "succeeded" ? { transcriptionProgress: 1 } : fraction != null ? { transcriptionProgress: fraction } : {}
|
|
2368
|
+
}
|
|
2369
|
+
};
|
|
2370
|
+
});
|
|
2371
|
+
}, [jobs]);
|
|
2149
2372
|
const listLength = screen.kind === "jobs" ? jobs.length : screen.kind === "overview" ? recordings.length : 0;
|
|
2150
|
-
|
|
2373
|
+
useEffect4(() => {
|
|
2151
2374
|
setSelected((i) => Math.max(0, Math.min(i, Math.max(0, listLength - 1))));
|
|
2152
2375
|
}, [listLength]);
|
|
2153
2376
|
const visibleRecordingRows = Math.max(3, size.rows - 6);
|
|
2154
|
-
|
|
2377
|
+
useEffect4(() => {
|
|
2155
2378
|
if (screen.kind !== "overview" || !recordingsNextCursor) return;
|
|
2156
2379
|
const nearLoadedEnd = recordings.length - selected <= RECORDINGS_PREFETCH_REMAINING;
|
|
2157
2380
|
const underfilledViewport = recordings.length < visibleRecordingRows;
|
|
@@ -2184,7 +2407,7 @@ function AppShell({
|
|
|
2184
2407
|
[fetchTranscript]
|
|
2185
2408
|
);
|
|
2186
2409
|
const detailTranscriptId = screen.kind === "recordingDetail" ? recordings.find((r) => r.recordingId === screen.recordingId)?.activeTranscriptId : void 0;
|
|
2187
|
-
|
|
2410
|
+
useEffect4(() => {
|
|
2188
2411
|
if (!detailTranscriptId || transcriptCache.has(detailTranscriptId)) return;
|
|
2189
2412
|
let cancelled = false;
|
|
2190
2413
|
setTranscriptCache((m) => new Map(m).set(detailTranscriptId, "loading"));
|
|
@@ -2344,8 +2567,10 @@ function AppShell({
|
|
|
2344
2567
|
RecordSetupView,
|
|
2345
2568
|
{
|
|
2346
2569
|
model: recordSetupModel,
|
|
2570
|
+
levels: recordSetupLevels,
|
|
2347
2571
|
onStart: beginLiveRecord,
|
|
2348
|
-
onCancel: () => setStack((st) => st.length > 1 ? st.slice(0, -1) : [{ kind: "overview" }])
|
|
2572
|
+
onCancel: () => setStack((st) => st.length > 1 ? st.slice(0, -1) : [{ kind: "overview" }]),
|
|
2573
|
+
onSelectionChange: setRecordSetupSelection
|
|
2349
2574
|
}
|
|
2350
2575
|
) });
|
|
2351
2576
|
}
|
|
@@ -2371,7 +2596,7 @@ function AppShell({
|
|
|
2371
2596
|
return /* @__PURE__ */ jsx18(PermissionPreflightView, { items: permissionItemsFromRecordError(liveRecord.data) });
|
|
2372
2597
|
}
|
|
2373
2598
|
const copy = recordErrorCopy(liveRecord.code, liveRecord.message);
|
|
2374
|
-
return /* @__PURE__ */ jsxs15(
|
|
2599
|
+
return /* @__PURE__ */ jsxs15(Fragment6, { children: [
|
|
2375
2600
|
/* @__PURE__ */ jsx18(Text16, { color: copy.tone, children: copy.title }),
|
|
2376
2601
|
copy.detail ? /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: copy.detail }) : null
|
|
2377
2602
|
] });
|
|
@@ -2524,6 +2749,7 @@ async function runDashboard(deps) {
|
|
|
2524
2749
|
listDownloadedRecordingIds: deps.listDownloadedRecordingIds,
|
|
2525
2750
|
fetchRecordSetup: deps.fetchRecordSetup,
|
|
2526
2751
|
startLiveRecord: deps.startLiveRecord,
|
|
2752
|
+
startRecordSetupPreview: deps.startRecordSetupPreview,
|
|
2527
2753
|
transcribeRecordingArtifact: deps.transcribeRecordingArtifact,
|
|
2528
2754
|
initialView: deps.initialView ?? "overview",
|
|
2529
2755
|
openUrl,
|
|
@@ -17284,6 +17510,19 @@ var sidecarRecordingStartResultSchema = external_exports.object({
|
|
|
17284
17510
|
state: sidecarRecordingStateSchema,
|
|
17285
17511
|
localSessionRef: external_exports.string().optional()
|
|
17286
17512
|
});
|
|
17513
|
+
var sidecarLevelPreviewStartParamsSchema = external_exports.object({
|
|
17514
|
+
options: sidecarRecordingOptionsSchema
|
|
17515
|
+
});
|
|
17516
|
+
var sidecarLevelPreviewStartResultSchema = external_exports.object({
|
|
17517
|
+
previewId: external_exports.string()
|
|
17518
|
+
});
|
|
17519
|
+
var sidecarLevelPreviewStopParamsSchema = external_exports.object({
|
|
17520
|
+
previewId: external_exports.string()
|
|
17521
|
+
});
|
|
17522
|
+
var sidecarLevelPreviewStopResultSchema = external_exports.object({
|
|
17523
|
+
previewId: external_exports.string(),
|
|
17524
|
+
state: external_exports.literal("stopped")
|
|
17525
|
+
});
|
|
17287
17526
|
var sidecarSessionParamsSchema = external_exports.object({
|
|
17288
17527
|
sessionId: external_exports.string()
|
|
17289
17528
|
});
|
|
@@ -17325,6 +17564,18 @@ var sidecarRequestSchema = external_exports.discriminatedUnion("method", [
|
|
|
17325
17564
|
method: external_exports.literal("recappi.recording.start"),
|
|
17326
17565
|
params: sidecarRecordingStartParamsSchema
|
|
17327
17566
|
}),
|
|
17567
|
+
external_exports.object({
|
|
17568
|
+
jsonrpc: external_exports.literal("2.0"),
|
|
17569
|
+
id: sidecarJsonRpcIdSchema,
|
|
17570
|
+
method: external_exports.literal("recappi.recording.level_preview.start"),
|
|
17571
|
+
params: sidecarLevelPreviewStartParamsSchema
|
|
17572
|
+
}),
|
|
17573
|
+
external_exports.object({
|
|
17574
|
+
jsonrpc: external_exports.literal("2.0"),
|
|
17575
|
+
id: sidecarJsonRpcIdSchema,
|
|
17576
|
+
method: external_exports.literal("recappi.recording.level_preview.stop"),
|
|
17577
|
+
params: sidecarLevelPreviewStopParamsSchema
|
|
17578
|
+
}),
|
|
17328
17579
|
external_exports.object({
|
|
17329
17580
|
jsonrpc: external_exports.literal("2.0"),
|
|
17330
17581
|
id: sidecarJsonRpcIdSchema,
|
|
@@ -17383,8 +17634,11 @@ var sidecarEventSchema = external_exports.discriminatedUnion("type", [
|
|
|
17383
17634
|
}),
|
|
17384
17635
|
external_exports.object({
|
|
17385
17636
|
type: external_exports.literal("audio.level"),
|
|
17386
|
-
sessionId: external_exports.string(),
|
|
17637
|
+
sessionId: external_exports.string().optional(),
|
|
17638
|
+
previewId: external_exports.string().optional(),
|
|
17387
17639
|
input: external_exports.enum(["system", "microphone"]),
|
|
17640
|
+
sourceId: external_exports.string().optional(),
|
|
17641
|
+
microphoneDeviceId: external_exports.string().optional(),
|
|
17388
17642
|
rmsDb: external_exports.number().optional(),
|
|
17389
17643
|
peakDb: external_exports.number().optional(),
|
|
17390
17644
|
at: external_exports.number().int().optional(),
|
|
@@ -20224,6 +20478,20 @@ var MiniSidecarClient = class {
|
|
|
20224
20478
|
sidecarRecordingStartResultSchema
|
|
20225
20479
|
);
|
|
20226
20480
|
}
|
|
20481
|
+
startLevelPreview(params) {
|
|
20482
|
+
return this.request(
|
|
20483
|
+
"recappi.recording.level_preview.start",
|
|
20484
|
+
sidecarLevelPreviewStartParamsSchema.parse(params),
|
|
20485
|
+
sidecarLevelPreviewStartResultSchema
|
|
20486
|
+
);
|
|
20487
|
+
}
|
|
20488
|
+
stopLevelPreview(params) {
|
|
20489
|
+
return this.request(
|
|
20490
|
+
"recappi.recording.level_preview.stop",
|
|
20491
|
+
sidecarLevelPreviewStopParamsSchema.parse(params),
|
|
20492
|
+
sidecarLevelPreviewStopResultSchema
|
|
20493
|
+
);
|
|
20494
|
+
}
|
|
20227
20495
|
getPermissionStatus(params) {
|
|
20228
20496
|
return this.request(
|
|
20229
20497
|
"recappi.permissions.status",
|
|
@@ -20573,6 +20841,55 @@ async function listRecordInputs(opts) {
|
|
|
20573
20841
|
sidecar.kill();
|
|
20574
20842
|
}
|
|
20575
20843
|
}
|
|
20844
|
+
async function startRecordSetupLevelPreview(opts, selection = {
|
|
20845
|
+
sourceId: DEFAULT_RECORDING_SOURCES[0].id,
|
|
20846
|
+
includeMicrophone: true
|
|
20847
|
+
}, sources = DEFAULT_RECORDING_SOURCES) {
|
|
20848
|
+
const capture = recordingCaptureMappingFromSelection(selection, sources);
|
|
20849
|
+
const command = resolveSidecarCommand(opts);
|
|
20850
|
+
const sidecarArgs = opts.sidecarArgs ?? [];
|
|
20851
|
+
const spawnSidecar = opts.runtime?.spawnSidecar ?? spawnMiniSidecar;
|
|
20852
|
+
const sidecar = spawnSidecar({ command, args: sidecarArgs, env: opts.env });
|
|
20853
|
+
let previewId;
|
|
20854
|
+
let closed = false;
|
|
20855
|
+
const close = () => {
|
|
20856
|
+
if (closed) return;
|
|
20857
|
+
closed = true;
|
|
20858
|
+
sidecar.kill();
|
|
20859
|
+
};
|
|
20860
|
+
try {
|
|
20861
|
+
await sidecar.client.handshake(
|
|
20862
|
+
defaultSidecarHandshakeParams({
|
|
20863
|
+
client: { name: "recappi-cli", version: opts.cliVersion },
|
|
20864
|
+
capabilities: ["recording.capture"]
|
|
20865
|
+
})
|
|
20866
|
+
);
|
|
20867
|
+
const started = await sidecar.client.startLevelPreview({
|
|
20868
|
+
options: {
|
|
20869
|
+
includeSystemAudio: capture.includeSystemAudio,
|
|
20870
|
+
includeMicrophone: capture.includeMicrophone,
|
|
20871
|
+
liveCaptions: false,
|
|
20872
|
+
...capture.targetBundleId ? { targetBundleId: capture.targetBundleId } : {},
|
|
20873
|
+
...capture.microphoneDeviceId ? { microphoneDeviceId: capture.microphoneDeviceId } : {}
|
|
20874
|
+
}
|
|
20875
|
+
});
|
|
20876
|
+
previewId = started.previewId;
|
|
20877
|
+
return {
|
|
20878
|
+
source: sidecar.client,
|
|
20879
|
+
stop: async () => {
|
|
20880
|
+
try {
|
|
20881
|
+
if (previewId) await sidecar.client.stopLevelPreview({ previewId });
|
|
20882
|
+
} catch {
|
|
20883
|
+
} finally {
|
|
20884
|
+
close();
|
|
20885
|
+
}
|
|
20886
|
+
}
|
|
20887
|
+
};
|
|
20888
|
+
} catch (error51) {
|
|
20889
|
+
close();
|
|
20890
|
+
throw error51;
|
|
20891
|
+
}
|
|
20892
|
+
}
|
|
20576
20893
|
async function startRecordSession(opts) {
|
|
20577
20894
|
let retriedAfterMicrophoneGrant = false;
|
|
20578
20895
|
while (true) {
|
|
@@ -21120,14 +21437,24 @@ async function runCli(deps = {}) {
|
|
|
21120
21437
|
sources
|
|
21121
21438
|
);
|
|
21122
21439
|
},
|
|
21123
|
-
|
|
21440
|
+
startRecordSetupPreview: async (selection, sources) => startRecordSetupLevelPreview(
|
|
21441
|
+
{
|
|
21442
|
+
cliVersion: CLI_VERSION,
|
|
21443
|
+
env: deps.env,
|
|
21444
|
+
runtime: deps.recordRuntime
|
|
21445
|
+
},
|
|
21446
|
+
selection,
|
|
21447
|
+
sources
|
|
21448
|
+
),
|
|
21449
|
+
transcribeRecordingArtifact: async (artifact, onEvent) => {
|
|
21124
21450
|
if (!artifact.audioPath) {
|
|
21125
21451
|
throw cliError("input.not_found", "No local audio file is available to transcribe.");
|
|
21126
21452
|
}
|
|
21127
21453
|
const data = await client.uploadPathBatch({
|
|
21128
21454
|
inputPath: artifact.audioPath,
|
|
21129
21455
|
transcribe: true,
|
|
21130
|
-
wait: false
|
|
21456
|
+
wait: false,
|
|
21457
|
+
onEvent
|
|
21131
21458
|
});
|
|
21132
21459
|
if (data.failures.length > 0) {
|
|
21133
21460
|
const failure = data.failures[0];
|