recappi 0.1.49 → 0.1.51

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 CHANGED
@@ -593,17 +593,20 @@ var init_LiveCaptionsScreen = __esm({
593
593
 
594
594
  // src/tui/RecordingHeroScreen.tsx
595
595
  import { useEffect as useEffect2, useRef, useState as useState3 } from "react";
596
- import { Box as Box2, Text as Text2 } from "ink";
596
+ import { Box as Box2, Text as Text2, useInput as useInput2 } from "ink";
597
597
  import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
598
- function litCount(level) {
598
+ function waveRowsFor(terminalRows) {
599
+ return terminalRows >= 30 ? 5 : 3;
600
+ }
601
+ function litCount(level, rows) {
599
602
  const amp = Math.max(0, Math.min(1, level));
600
603
  if (amp <= 0.028) return 0;
601
- return Math.max(1, Math.min(WAVE_ROWS, Math.ceil(Math.pow(amp, 0.58) * WAVE_ROWS)));
604
+ return Math.max(1, Math.min(rows, Math.ceil(Math.pow(amp, 0.58) * rows)));
602
605
  }
603
- function litCounts(samples, width) {
606
+ function litCounts(samples, width, rows) {
604
607
  if (width <= 0) return [];
605
608
  const tail = samples.slice(-width);
606
- return [...Array(Math.max(0, width - tail.length)).fill(0), ...tail].map(litCount);
609
+ return [...Array(Math.max(0, width - tail.length)).fill(0), ...tail].map((v) => litCount(v, rows));
607
610
  }
608
611
  function levelDb(level) {
609
612
  if (level <= 0.03) return "silent";
@@ -614,18 +617,19 @@ function MeterRow({
614
617
  samples,
615
618
  level,
616
619
  paused,
617
- width
620
+ width,
621
+ rows
618
622
  }) {
619
623
  const silent = level <= 0.03;
620
- const cols = litCounts(samples, width);
624
+ const cols = litCounts(samples, width, rows);
621
625
  const litColor = paused ? "gray" : "cyan";
622
626
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
623
627
  /* @__PURE__ */ jsxs2(Box2, { width: width + 9, children: [
624
628
  /* @__PURE__ */ jsx3(Box2, { width: 9, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: label }) }),
625
629
  /* @__PURE__ */ jsx3(Box2, { flexGrow: 1, justifyContent: "flex-end", children: !paused && silent ? /* @__PURE__ */ jsx3(Text2, { color: "yellow", children: "silent" }) : /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: paused ? "paused" : levelDb(level) }) })
626
630
  ] }),
627
- Array.from({ length: WAVE_ROWS }, (_, r) => {
628
- const fromBottom = WAVE_ROWS - r;
631
+ Array.from({ length: rows }, (_, r) => {
632
+ const fromBottom = rows - r;
629
633
  return /* @__PURE__ */ jsx3(Text2, { children: cols.map(
630
634
  (c, i) => c >= fromBottom ? /* @__PURE__ */ jsx3(Text2, { color: litColor, children: c === fromBottom ? "\u2022" : "\u25CF" }, i) : /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "\xB7" }, i)
631
635
  ) }, r);
@@ -663,7 +667,13 @@ function RecordingHeroScreen({
663
667
  const [tick, setTick] = useState3(() => now());
664
668
  const [waveSys, setWaveSys] = useState3([]);
665
669
  const [waveMic, setWaveMic] = useState3([]);
670
+ const [captionMode, setCaptionMode] = useState3("both");
666
671
  const lastAppendRef = useRef(0);
672
+ useInput2((input) => {
673
+ if (input === "c") {
674
+ setCaptionMode((m) => m === "both" ? "source" : m === "source" ? "translation" : "both");
675
+ }
676
+ });
667
677
  useEffect2(() => {
668
678
  if (telemetry.level == null) return;
669
679
  const t = now();
@@ -720,7 +730,8 @@ function RecordingHeroScreen({
720
730
  const meterW = Math.max(10, Math.min(72, innerWidth - 20));
721
731
  const sizeStr = telemetry.sizeBytes ? formatBytes2(telemetry.sizeBytes) : "";
722
732
  const context = [telemetry.sourceLabel, telemetry.micEnabled ? "Microphone" : null, sizeStr || null].filter(Boolean).join(" \xB7 ");
723
- const meterBlockRows = (telemetry.micEnabled ? 2 : 1) * (WAVE_ROWS + 1) + (telemetry.micEnabled ? 1 : 0);
733
+ const waveRows = waveRowsFor(size.rows);
734
+ const meterBlockRows = (telemetry.micEnabled ? 2 : 1) * (waveRows + 1) + (telemetry.micEnabled ? 1 : 0);
724
735
  const fixedRows = 8 + meterBlockRows;
725
736
  const captionRows = Math.max(2, size.rows - fixedRows);
726
737
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
@@ -739,55 +750,83 @@ function RecordingHeroScreen({
739
750
  // reads as silence (the elapsed timer above proves it's live).
740
751
  /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: paused ? "Paused" : `Capturing audio${".".repeat(Math.floor(tick / 1e3) % 3 + 1)}` })
741
752
  ) : /* @__PURE__ */ jsxs2(Fragment, { children: [
742
- /* @__PURE__ */ jsx3(MeterRow, { label: "System", samples: waveSys, level: telemetry.level.system ?? 0, paused, width: meterW }),
743
- telemetry.micEnabled ? /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx3(MeterRow, { label: "Mic", samples: waveMic, level: telemetry.level.mic ?? 0, paused, width: meterW }) }) : null
753
+ /* @__PURE__ */ jsx3(MeterRow, { label: "System", samples: waveSys, level: telemetry.level.system ?? 0, paused, width: meterW, rows: waveRows }),
754
+ telemetry.micEnabled ? /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx3(MeterRow, { label: "Mic", samples: waveMic, level: telemetry.level.mic ?? 0, paused, width: meterW, rows: waveRows }) }) : null
744
755
  ] }) }),
745
756
  /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: context }) }),
746
757
  captions ? /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
747
758
  /* @__PURE__ */ jsx3(Text2, { bold: true, dimColor: true, children: "LIVE CAPTIONS" }),
748
- /* @__PURE__ */ jsx3(HeroCaptions, { state: captions, maxRows: captionRows })
759
+ /* @__PURE__ */ jsx3(HeroCaptions, { state: captions, maxRows: captionRows, width: innerWidth, mode: captionMode })
749
760
  ] }) : null
750
761
  ] }),
751
762
  /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
752
763
  "q stop & save",
753
- canPause ? ` \xB7 p ${paused ? "resume" : "pause"}` : ""
764
+ canPause ? ` \xB7 p ${paused ? "resume" : "pause"}` : "",
765
+ captions ? " \xB7 c captions" : ""
754
766
  ] }) })
755
767
  ] });
756
768
  }
757
- function HeroCaptions({ state, maxRows }) {
769
+ function wrappedRows(text, width) {
770
+ return Math.max(1, Math.ceil(displayWidth(text) / Math.max(1, width)));
771
+ }
772
+ function captionColumn(items, maxRows, width, dim) {
773
+ const budget = Math.max(1, maxRows);
774
+ const chosen = [];
775
+ let used = 0;
776
+ for (let i = items.length - 1; i >= 0; i--) {
777
+ const h = wrappedRows(items[i].text, width);
778
+ if (used + h > budget && chosen.length > 0) break;
779
+ chosen.unshift(items[i]);
780
+ used += h;
781
+ }
782
+ return chosen.map((it) => /* @__PURE__ */ jsx3(Text2, { dimColor: dim, wrap: "wrap", children: it.text }, it.key));
783
+ }
784
+ function HeroCaptions({
785
+ state,
786
+ maxRows,
787
+ width,
788
+ mode
789
+ }) {
758
790
  const hasPartial = Boolean(state.partial && state.partial.length > 0);
759
791
  const captionError = state.status === "error" ? `Captions unavailable: ${state.error ?? "Live captions unavailable."}` : null;
760
792
  if (state.lines.length === 0 && !hasPartial) {
761
- 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)) });
762
- }
763
- const rows = [];
764
- for (const line of state.lines) {
765
- rows.push(
766
- /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate-end", children: [
767
- line.speaker ? `${line.speaker}: ` : "",
768
- trimLead(line.text)
769
- ] }, `${line.id}-s`)
770
- );
771
- if (line.translation) {
772
- rows.push(
773
- /* @__PURE__ */ jsx3(Text2, { dimColor: true, wrap: "truncate-end", children: ` \u21B3 ${trimLead(line.translation)}` }, `${line.id}-t`)
774
- );
775
- }
793
+ return /* @__PURE__ */ jsx3(Text2, { color: captionError ? "yellow" : void 0, dimColor: !captionError, children: captionError ?? (state.status === "live" ? "Listening for speech\u2026" : liveCaptionStatusLabel(state.status)) });
776
794
  }
777
- if (hasPartial) {
778
- rows.push(
779
- /* @__PURE__ */ jsx3(Text2, { dimColor: true, wrap: "truncate-end", children: trimLead(state.partial) }, "partial")
780
- );
795
+ const sourceItems = state.lines.map((l) => ({
796
+ key: `${l.id}-s`,
797
+ text: `${l.speaker ? `${l.speaker}: ` : ""}${trimLead(l.text)}`
798
+ }));
799
+ if (hasPartial) sourceItems.push({ key: "sp", text: trimLead(state.partial) });
800
+ const translationItems = state.lines.filter((l) => l.translation).map((l) => ({ key: `${l.id}-t`, text: trimLead(l.translation) }));
801
+ if (state.translationPartial) translationItems.push({ key: "tp", text: trimLead(state.translationPartial) });
802
+ const errLine = captionError ? /* @__PURE__ */ jsx3(Text2, { color: "yellow", wrap: "wrap", children: captionError }) : null;
803
+ const hasTranslation = translationItems.length > 0;
804
+ if (mode === "source" || !hasTranslation) {
805
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
806
+ captionColumn(sourceItems, maxRows, width, false),
807
+ errLine
808
+ ] });
781
809
  }
782
- if (state.translationPartial) {
783
- rows.push(
784
- /* @__PURE__ */ jsx3(Text2, { dimColor: true, wrap: "truncate-end", children: ` \u21B3 ${trimLead(state.translationPartial)}` }, "tpartial")
785
- );
810
+ if (mode === "translation") {
811
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
812
+ captionColumn(translationItems, maxRows, width, false),
813
+ errLine
814
+ ] });
786
815
  }
787
- const visible = rows.slice(-Math.max(1, maxRows));
816
+ const gap = 2;
817
+ const colW = Math.max(12, Math.floor((width - gap) / 2));
788
818
  return /* @__PURE__ */ jsxs2(Fragment, { children: [
789
- visible,
790
- captionError ? /* @__PURE__ */ jsx3(Text2, { color: "yellow", wrap: "truncate-end", children: captionError }) : null
819
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", children: [
820
+ /* @__PURE__ */ jsxs2(Box2, { width: colW, flexDirection: "column", marginRight: gap, children: [
821
+ /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "ORIGINAL" }),
822
+ captionColumn(sourceItems, Math.max(1, maxRows - 1), colW, false)
823
+ ] }),
824
+ /* @__PURE__ */ jsxs2(Box2, { width: colW, flexDirection: "column", children: [
825
+ /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "TRANSLATION" }),
826
+ captionColumn(translationItems, Math.max(1, maxRows - 1), colW, false)
827
+ ] })
828
+ ] }),
829
+ errLine
791
830
  ] });
792
831
  }
793
832
  function stoppedHandoffCopy(artifact, canTranscribe) {
@@ -808,14 +847,13 @@ function stoppedHandoffCopy(artifact, canTranscribe) {
808
847
  }
809
848
  return { text: "Transcribe now? \u23CE yes \xB7 n not now", tone: "normal" };
810
849
  }
811
- var WAVE_ROWS, WAVE_THROTTLE_MS, trimLead;
850
+ var WAVE_THROTTLE_MS, trimLead;
812
851
  var init_RecordingHeroScreen = __esm({
813
852
  "src/tui/RecordingHeroScreen.tsx"() {
814
853
  "use strict";
815
854
  init_format();
816
855
  init_liveCaptions();
817
856
  init_terminal();
818
- WAVE_ROWS = 5;
819
857
  WAVE_THROTTLE_MS = 220;
820
858
  trimLead = (s) => s.replace(/^\s+/, "");
821
859
  }
@@ -1382,7 +1420,7 @@ var init_JobDetailView = __esm({
1382
1420
 
1383
1421
  // src/tui/RecordingDetailView.tsx
1384
1422
  import React6, { useMemo as useMemo2, useState as useState4 } from "react";
1385
- import { Box as Box12, Text as Text12, useInput as useInput3 } from "ink";
1423
+ import { Box as Box12, Text as Text12, useInput as useInput4 } from "ink";
1386
1424
  import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
1387
1425
  function RecordingDetailView({
1388
1426
  item,
@@ -1426,7 +1464,7 @@ function RecordingDetailView({
1426
1464
  setScroll(found < 0 ? Math.max(0, segments.length - 1) : found);
1427
1465
  setTab("transcript");
1428
1466
  };
1429
- useInput3((input, key) => {
1467
+ useInput4((input, key) => {
1430
1468
  if (!item.activeTranscriptId || !ready) return;
1431
1469
  if (key.tab) {
1432
1470
  setTab((t) => TAB_ORDER[(TAB_ORDER.indexOf(t) + (key.shift ? TAB_ORDER.length - 1 : 1)) % TAB_ORDER.length]);
@@ -1576,7 +1614,7 @@ var init_RecordingDetailView = __esm({
1576
1614
 
1577
1615
  // src/tui/TranscriptView.tsx
1578
1616
  import { useMemo as useMemo3, useState as useState5 } from "react";
1579
- import { Box as Box13, Text as Text13, useInput as useInput4 } from "ink";
1617
+ import { Box as Box13, Text as Text13, useInput as useInput5 } from "ink";
1580
1618
  import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
1581
1619
  function TranscriptView({ loading, data, error: error51 }) {
1582
1620
  const size = useTerminalSize();
@@ -1593,7 +1631,7 @@ function TranscriptView({ loading, data, error: error51 }) {
1593
1631
  const budget = Math.max(3, size.rows - 3);
1594
1632
  const win = windowByHeights(heights, scroll, budget);
1595
1633
  const page = Math.max(1, budget - 1);
1596
- useInput4((input, key) => {
1634
+ useInput5((input, key) => {
1597
1635
  if (key.downArrow || input === "j") setScroll((s) => Math.min(win.maxScroll, s + 1));
1598
1636
  else if (key.upArrow || input === "k") setScroll((s) => Math.max(0, s - 1));
1599
1637
  else if (key.pageDown || input === " ") setScroll((s) => Math.min(win.maxScroll, s + page));
@@ -1720,7 +1758,7 @@ var init_PermissionPreflightView = __esm({
1720
1758
 
1721
1759
  // src/tui/RecordSetupView.tsx
1722
1760
  import { useEffect as useEffect3, useRef as useRef2, useState as useState6 } from "react";
1723
- import { Box as Box15, Text as Text15, useInput as useInput5 } from "ink";
1761
+ import { Box as Box15, Text as Text15, useInput as useInput6 } from "ink";
1724
1762
  import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
1725
1763
  function levelDb2(level) {
1726
1764
  if (level <= 0.03) return "silent";
@@ -1783,7 +1821,7 @@ function RecordSetupView({
1783
1821
  const di = microphones.findIndex((device) => device.isDefault);
1784
1822
  if (di > 0) setMicIdx(di);
1785
1823
  }, [microphones.length]);
1786
- useInput5((input, key) => {
1824
+ useInput6((input, key) => {
1787
1825
  if (key.upArrow || input === "k") setSrcIdx((i) => Math.max(0, i - 1));
1788
1826
  else if (key.downArrow || input === "j") setSrcIdx((i) => Math.min(sources.length - 1, i + 1));
1789
1827
  else if (input === " ") setIncludeMic((m) => !m);
@@ -1862,7 +1900,7 @@ var init_RecordSetupView = __esm({
1862
1900
 
1863
1901
  // src/tui/AppShell.tsx
1864
1902
  import { useCallback, useEffect as useEffect4, useState as useState7 } from "react";
1865
- import { Box as Box16, Text as Text16, useApp, useInput as useInput6 } from "ink";
1903
+ import { Box as Box16, Text as Text16, useApp, useInput as useInput7 } from "ink";
1866
1904
  import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
1867
1905
  function recordErrorCopy(code, message) {
1868
1906
  switch (code) {
@@ -2506,7 +2544,7 @@ function AppShell({
2506
2544
  setNotice(void 0);
2507
2545
  };
2508
2546
  const back = () => setStack((st) => st.length > 1 ? st.slice(0, -1) : st);
2509
- useInput6((input, key) => {
2547
+ useInput7((input, key) => {
2510
2548
  setNotice(void 0);
2511
2549
  if (screen.kind === "recordSetup") {
2512
2550
  if (input === "q" || key.leftArrow) back();
@@ -20480,7 +20518,7 @@ import { createRequire as createRequire2 } from "module";
20480
20518
  import { homedir } from "os";
20481
20519
  import { dirname, join as join2 } from "path";
20482
20520
  import { fileURLToPath } from "url";
20483
- import { render, useInput as useInput2 } from "ink";
20521
+ import { render, useInput as useInput3 } from "ink";
20484
20522
  init_recordingCore();
20485
20523
 
20486
20524
  // src/sidecar.ts
@@ -21336,7 +21374,7 @@ function RecordLiveScreen({
21336
21374
  onStop,
21337
21375
  now
21338
21376
  }) {
21339
- useInput2((input, key) => {
21377
+ useInput3((input, key) => {
21340
21378
  if (input === "q" || key.escape || key.leftArrow) onStop();
21341
21379
  });
21342
21380
  return /* @__PURE__ */ jsx4(LiveCaptionsScreen, { source, now });
@@ -21395,7 +21433,7 @@ function RecordingHeroLive({
21395
21433
  if (mapped) setCaptions((prev) => liveCaptionReducer(prev, mapped));
21396
21434
  });
21397
21435
  }, [captionStreamEnabled, source]);
21398
- useInput2((input, key) => {
21436
+ useInput3((input, key) => {
21399
21437
  if (input === "q" || key.escape) onStop();
21400
21438
  });
21401
21439
  return /* @__PURE__ */ jsx4(