recappi 0.1.78 → 0.1.80
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 +817 -227
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -9,6 +9,117 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/tui/askInline.ts
|
|
13
|
+
function formatAskTimecode(ms) {
|
|
14
|
+
const total = Math.max(0, Math.floor(ms / 1e3));
|
|
15
|
+
const h = Math.floor(total / 3600);
|
|
16
|
+
const m = Math.floor(total % 3600 / 60);
|
|
17
|
+
const s = total % 60;
|
|
18
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
19
|
+
return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${m}:${pad(s)}`;
|
|
20
|
+
}
|
|
21
|
+
function askCitationInlineLabel(citation) {
|
|
22
|
+
if (typeof citation.startMs === "number") return formatAskTimecode(citation.startMs);
|
|
23
|
+
const label = citation.label?.trim();
|
|
24
|
+
if (label) {
|
|
25
|
+
const firstRangePart = label.split(/[-–—]/)[0]?.trim();
|
|
26
|
+
if (firstRangePart) return firstRangePart;
|
|
27
|
+
}
|
|
28
|
+
if (typeof citation.index === "number") return `#${citation.index + 1}`;
|
|
29
|
+
return "Source";
|
|
30
|
+
}
|
|
31
|
+
function hasCitationMarkers(content) {
|
|
32
|
+
MARKER.lastIndex = 0;
|
|
33
|
+
return MARKER.test(content);
|
|
34
|
+
}
|
|
35
|
+
function strippedAskAnswer(content) {
|
|
36
|
+
return content.replace(LOOSE_MARKER, "");
|
|
37
|
+
}
|
|
38
|
+
function renderAskInline(content, citations) {
|
|
39
|
+
const bySegmentId = /* @__PURE__ */ new Map();
|
|
40
|
+
for (const citation of citations) {
|
|
41
|
+
if (citation.segmentId && !bySegmentId.has(citation.segmentId)) {
|
|
42
|
+
bySegmentId.set(citation.segmentId, citation);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (!hasCitationMarkers(content) || bySegmentId.size === 0) {
|
|
46
|
+
const text = strippedAskAnswer(content);
|
|
47
|
+
return text ? [{ kind: "text", text }] : [];
|
|
48
|
+
}
|
|
49
|
+
const segments = [];
|
|
50
|
+
let lastVisibleChar;
|
|
51
|
+
const pushText = (text) => {
|
|
52
|
+
if (!text) return;
|
|
53
|
+
const prev = segments[segments.length - 1];
|
|
54
|
+
if (prev && prev.kind === "text") prev.text += text;
|
|
55
|
+
else segments.push({ kind: "text", text });
|
|
56
|
+
lastVisibleChar = text[text.length - 1];
|
|
57
|
+
};
|
|
58
|
+
const pushCitation = (citation) => {
|
|
59
|
+
if (lastVisibleChar !== void 0 && !/\s/.test(lastVisibleChar)) pushText(" ");
|
|
60
|
+
const label = askCitationInlineLabel(citation);
|
|
61
|
+
segments.push({ kind: "citation", label, citation });
|
|
62
|
+
lastVisibleChar = label[label.length - 1];
|
|
63
|
+
};
|
|
64
|
+
MARKER.lastIndex = 0;
|
|
65
|
+
let lastIndex = 0;
|
|
66
|
+
let match;
|
|
67
|
+
while ((match = MARKER.exec(content)) !== null) {
|
|
68
|
+
const textBefore = content.slice(lastIndex, match.index);
|
|
69
|
+
const citation = bySegmentId.get(match[1]);
|
|
70
|
+
if (citation) {
|
|
71
|
+
pushText(textBefore);
|
|
72
|
+
pushCitation(citation);
|
|
73
|
+
} else {
|
|
74
|
+
const nextChar = content[match.index + match[0].length];
|
|
75
|
+
pushText(cleanupBeforeRemovedMarker(textBefore, nextChar));
|
|
76
|
+
}
|
|
77
|
+
lastIndex = match.index + match[0].length;
|
|
78
|
+
}
|
|
79
|
+
pushText(content.slice(lastIndex));
|
|
80
|
+
return segments;
|
|
81
|
+
}
|
|
82
|
+
function cleanupBeforeRemovedMarker(text, nextChar) {
|
|
83
|
+
if (!nextChar || !TRAILING_PUNCTUATION.includes(nextChar)) return text;
|
|
84
|
+
return text.replace(/[ \t]+$/, "");
|
|
85
|
+
}
|
|
86
|
+
function truncateSnippet(snippet, max = 100) {
|
|
87
|
+
const clean = snippet.replace(/\s+/g, " ").trim();
|
|
88
|
+
return clean.length > max ? `${clean.slice(0, max - 1)}\u2026` : clean;
|
|
89
|
+
}
|
|
90
|
+
function formatAskAnswerPlain(content, citations) {
|
|
91
|
+
const segments = renderAskInline(content, citations);
|
|
92
|
+
const body = segments.map((segment) => segment.kind === "text" ? segment.text : `\u27E8${segment.label}\u27E9`).join("").trim();
|
|
93
|
+
const seen = /* @__PURE__ */ new Set();
|
|
94
|
+
const cited = [];
|
|
95
|
+
for (const segment of segments) {
|
|
96
|
+
if (segment.kind !== "citation") continue;
|
|
97
|
+
const key = segment.citation.segmentId ?? segment.label;
|
|
98
|
+
if (seen.has(key)) continue;
|
|
99
|
+
seen.add(key);
|
|
100
|
+
cited.push(segment.citation);
|
|
101
|
+
}
|
|
102
|
+
if (cited.length === 0) return body;
|
|
103
|
+
const sources = cited.map((citation) => {
|
|
104
|
+
const label = askCitationInlineLabel(citation);
|
|
105
|
+
const meta3 = [citation.speaker?.trim(), citation.snippet ? truncateSnippet(citation.snippet) : ""].filter(Boolean).join(" \xB7 ");
|
|
106
|
+
return ` \u27E8${label}\u27E9${meta3 ? ` ${meta3}` : ""}`;
|
|
107
|
+
});
|
|
108
|
+
return `${body}
|
|
109
|
+
|
|
110
|
+
Sources:
|
|
111
|
+
${sources.join("\n")}`;
|
|
112
|
+
}
|
|
113
|
+
var MARKER, LOOSE_MARKER, TRAILING_PUNCTUATION;
|
|
114
|
+
var init_askInline = __esm({
|
|
115
|
+
"src/tui/askInline.ts"() {
|
|
116
|
+
"use strict";
|
|
117
|
+
MARKER = /\[\[(seg-[^\]]+)\]\]/g;
|
|
118
|
+
LOOSE_MARKER = /\s*\[?\[seg-[^\]]+\]\]?/g;
|
|
119
|
+
TRAILING_PUNCTUATION = ".\u3002!\uFF01?\uFF1F,\uFF0C;\uFF1B:\uFF1A";
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
12
123
|
// src/recordingCore.ts
|
|
13
124
|
function recordingCaptureMappingFromSelection(selection = DEFAULT_RECORDING_SELECTION, sources = DEFAULT_RECORDING_SOURCES) {
|
|
14
125
|
const source = sources.find((candidate) => candidate.id === selection.sourceId) ?? sources[0];
|
|
@@ -1177,7 +1288,8 @@ function RecordingDetailView({
|
|
|
1177
1288
|
item,
|
|
1178
1289
|
nowMs,
|
|
1179
1290
|
transcript,
|
|
1180
|
-
audio
|
|
1291
|
+
audio,
|
|
1292
|
+
localDir
|
|
1181
1293
|
}) {
|
|
1182
1294
|
const size = useTerminalSize();
|
|
1183
1295
|
const [tab, setTab] = useState4("summary");
|
|
@@ -1244,6 +1356,10 @@ function RecordingDetailView({
|
|
|
1244
1356
|
] }),
|
|
1245
1357
|
meta3 ? /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: meta3 }) : null,
|
|
1246
1358
|
/* @__PURE__ */ jsx14(AudioActionRow, { item, audio }),
|
|
1359
|
+
localDir ? /* @__PURE__ */ jsxs11(Text12, { children: [
|
|
1360
|
+
/* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "\u2302 Local \xB7 " }),
|
|
1361
|
+
/* @__PURE__ */ jsx14(Text12, { dimColor: true, wrap: "truncate-middle", children: localDir })
|
|
1362
|
+
] }) : null,
|
|
1247
1363
|
!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: [
|
|
1248
1364
|
/* @__PURE__ */ jsx14(TabBar, { active: tab }),
|
|
1249
1365
|
/* @__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 }) })
|
|
@@ -1254,7 +1370,8 @@ function RecordingDetailView({
|
|
|
1254
1370
|
scrollable ? " \xB7 \u2191\u2193 scroll" : "",
|
|
1255
1371
|
ready ? " \xB7 " : "",
|
|
1256
1372
|
`o open \xB7 d download \xB7 f finder`,
|
|
1257
|
-
|
|
1373
|
+
localDir ? " \xB7 l folder" : "",
|
|
1374
|
+
" \xB7 T re-transcribe \xB7 s re-summarize \xB7 a ask \xB7 e export",
|
|
1258
1375
|
item.activeTranscriptId ? " \xB7 t full" : "",
|
|
1259
1376
|
links.webUrl ? " \xB7 w web" : "",
|
|
1260
1377
|
" \xB7 r refresh \xB7 esc back"
|
|
@@ -1364,13 +1481,177 @@ var init_RecordingDetailView = __esm({
|
|
|
1364
1481
|
}
|
|
1365
1482
|
});
|
|
1366
1483
|
|
|
1367
|
-
// src/tui/
|
|
1368
|
-
import {
|
|
1484
|
+
// src/tui/AskScreen.tsx
|
|
1485
|
+
import { useCallback, useRef as useRef2, useState as useState5 } from "react";
|
|
1369
1486
|
import { Box as Box13, Text as Text13, useInput as useInput5 } from "ink";
|
|
1370
1487
|
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1488
|
+
function AskScreen({
|
|
1489
|
+
recordingId,
|
|
1490
|
+
title,
|
|
1491
|
+
askRecording,
|
|
1492
|
+
onBack,
|
|
1493
|
+
onOpenTranscript,
|
|
1494
|
+
spinnerFrame
|
|
1495
|
+
}) {
|
|
1496
|
+
const [phase, setPhase] = useState5("input");
|
|
1497
|
+
const [question, setQuestion] = useState5("");
|
|
1498
|
+
const [asked, setAsked] = useState5("");
|
|
1499
|
+
const [content, setContent] = useState5("");
|
|
1500
|
+
const [citations, setCitations] = useState5([]);
|
|
1501
|
+
const [error51, setError] = useState5(void 0);
|
|
1502
|
+
const runIdRef = useRef2(0);
|
|
1503
|
+
const submit = useCallback(
|
|
1504
|
+
(raw) => {
|
|
1505
|
+
const query = raw.trim();
|
|
1506
|
+
if (!query) return;
|
|
1507
|
+
const runId = ++runIdRef.current;
|
|
1508
|
+
setAsked(query);
|
|
1509
|
+
setPhase("asking");
|
|
1510
|
+
setContent("");
|
|
1511
|
+
setCitations([]);
|
|
1512
|
+
setError(void 0);
|
|
1513
|
+
void (async () => {
|
|
1514
|
+
try {
|
|
1515
|
+
let text = "";
|
|
1516
|
+
let cites = [];
|
|
1517
|
+
for await (const event of askRecording({ recordingId, question: query })) {
|
|
1518
|
+
if (runIdRef.current !== runId) return;
|
|
1519
|
+
if (event.type === "answer_delta") {
|
|
1520
|
+
text += event.delta;
|
|
1521
|
+
setContent(text);
|
|
1522
|
+
} else if (event.type === "citation") {
|
|
1523
|
+
cites = [...cites, event.citation];
|
|
1524
|
+
setCitations(cites);
|
|
1525
|
+
} else if (event.type === "done") {
|
|
1526
|
+
if (typeof event.content === "string" && event.content) {
|
|
1527
|
+
text = event.content;
|
|
1528
|
+
setContent(text);
|
|
1529
|
+
}
|
|
1530
|
+
if (event.citations.length) {
|
|
1531
|
+
cites = event.citations;
|
|
1532
|
+
setCitations(cites);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
if (runIdRef.current === runId) setPhase("done");
|
|
1537
|
+
} catch (err) {
|
|
1538
|
+
if (runIdRef.current === runId) {
|
|
1539
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
1540
|
+
setPhase("error");
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
})();
|
|
1544
|
+
},
|
|
1545
|
+
[askRecording, recordingId]
|
|
1546
|
+
);
|
|
1547
|
+
const reset = () => {
|
|
1548
|
+
runIdRef.current++;
|
|
1549
|
+
setPhase("input");
|
|
1550
|
+
setQuestion("");
|
|
1551
|
+
setAsked("");
|
|
1552
|
+
setContent("");
|
|
1553
|
+
setCitations([]);
|
|
1554
|
+
setError(void 0);
|
|
1555
|
+
};
|
|
1556
|
+
useInput5((input, key) => {
|
|
1557
|
+
if (key.escape) {
|
|
1558
|
+
runIdRef.current++;
|
|
1559
|
+
onBack();
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
if (phase === "input") {
|
|
1563
|
+
if (key.return) return submit(question);
|
|
1564
|
+
if (key.backspace || key.delete) return setQuestion((q) => q.slice(0, -1));
|
|
1565
|
+
if (input && !key.ctrl && !key.meta) setQuestion((q) => q + input);
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
if (phase === "done" || phase === "error") {
|
|
1569
|
+
if (input === "a") return reset();
|
|
1570
|
+
if (input === "t" && onOpenTranscript) return onOpenTranscript();
|
|
1571
|
+
}
|
|
1572
|
+
});
|
|
1573
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 1, children: [
|
|
1574
|
+
/* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
|
|
1575
|
+
"\u2039 Ask",
|
|
1576
|
+
title ? ` \xB7 ${title}` : ""
|
|
1577
|
+
] }),
|
|
1578
|
+
phase === "input" ? /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "column", children: [
|
|
1579
|
+
/* @__PURE__ */ jsxs12(Text13, { children: [
|
|
1580
|
+
/* @__PURE__ */ jsx15(Text13, { color: "cyan", children: "? " }),
|
|
1581
|
+
/* @__PURE__ */ jsx15(Text13, { children: question }),
|
|
1582
|
+
/* @__PURE__ */ jsx15(Text13, { color: "cyan", children: "\u258F" })
|
|
1583
|
+
] }),
|
|
1584
|
+
/* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Type a question \xB7 \u23CE ask \xB7 esc back" }) })
|
|
1585
|
+
] }) : /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "column", children: [
|
|
1586
|
+
/* @__PURE__ */ jsx15(Text13, { dimColor: true, wrap: "truncate-end", children: `? ${asked}` }),
|
|
1587
|
+
/* @__PURE__ */ jsx15(Box13, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx15(AnswerBody, { phase, content, citations, error: error51, spinnerFrame }) }),
|
|
1588
|
+
phase === "done" ? /* @__PURE__ */ jsx15(Sources, { citations }) : null,
|
|
1589
|
+
/* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
|
|
1590
|
+
phase === "asking" ? "esc cancel" : "a ask again",
|
|
1591
|
+
(phase === "done" || phase === "error") && onOpenTranscript ? " \xB7 t transcript" : "",
|
|
1592
|
+
phase !== "asking" ? " \xB7 esc back" : ""
|
|
1593
|
+
] }) })
|
|
1594
|
+
] })
|
|
1595
|
+
] });
|
|
1596
|
+
}
|
|
1597
|
+
function AnswerBody({
|
|
1598
|
+
phase,
|
|
1599
|
+
content,
|
|
1600
|
+
citations,
|
|
1601
|
+
error: error51,
|
|
1602
|
+
spinnerFrame
|
|
1603
|
+
}) {
|
|
1604
|
+
if (phase === "error") {
|
|
1605
|
+
return /* @__PURE__ */ jsx15(Text13, { color: "red", children: error51 ? `Ask failed: ${error51}` : "Ask failed" });
|
|
1606
|
+
}
|
|
1607
|
+
if (phase === "asking" && !content) {
|
|
1608
|
+
return /* @__PURE__ */ jsx15(Text13, { color: "cyan", children: `${SPINNER2[spinnerFrame % SPINNER2.length]} Thinking\u2026` });
|
|
1609
|
+
}
|
|
1610
|
+
if (phase === "asking") {
|
|
1611
|
+
return /* @__PURE__ */ jsx15(Text13, { children: strippedAskAnswer(content) });
|
|
1612
|
+
}
|
|
1613
|
+
const segments = renderAskInline(content, citations);
|
|
1614
|
+
return /* @__PURE__ */ jsx15(Text13, { children: segments.map(
|
|
1615
|
+
(segment, i) => segment.kind === "text" ? /* @__PURE__ */ jsx15(Text13, { children: segment.text }, i) : /* @__PURE__ */ jsx15(Text13, { color: "cyan", children: `\u27E8${segment.label}\u27E9` }, i)
|
|
1616
|
+
) });
|
|
1617
|
+
}
|
|
1618
|
+
function Sources({ citations }) {
|
|
1619
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1620
|
+
const unique = [];
|
|
1621
|
+
for (const citation of citations) {
|
|
1622
|
+
const key = citation.segmentId ?? askCitationInlineLabel(citation);
|
|
1623
|
+
if (seen.has(key)) continue;
|
|
1624
|
+
seen.add(key);
|
|
1625
|
+
unique.push(citation);
|
|
1626
|
+
}
|
|
1627
|
+
if (unique.length === 0) return null;
|
|
1628
|
+
return /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "column", children: [
|
|
1629
|
+
/* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Sources" }),
|
|
1630
|
+
unique.map((citation, i) => {
|
|
1631
|
+
const meta3 = [citation.speaker?.trim(), citation.snippet?.trim()].filter(Boolean).join(" \xB7 ");
|
|
1632
|
+
return /* @__PURE__ */ jsxs12(Text13, { wrap: "truncate-end", children: [
|
|
1633
|
+
/* @__PURE__ */ jsx15(Text13, { color: "cyan", children: `\u27E8${askCitationInlineLabel(citation)}\u27E9` }),
|
|
1634
|
+
meta3 ? /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: ` ${meta3}` }) : null
|
|
1635
|
+
] }, i);
|
|
1636
|
+
})
|
|
1637
|
+
] });
|
|
1638
|
+
}
|
|
1639
|
+
var SPINNER2;
|
|
1640
|
+
var init_AskScreen = __esm({
|
|
1641
|
+
"src/tui/AskScreen.tsx"() {
|
|
1642
|
+
"use strict";
|
|
1643
|
+
init_askInline();
|
|
1644
|
+
SPINNER2 = "\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F";
|
|
1645
|
+
}
|
|
1646
|
+
});
|
|
1647
|
+
|
|
1648
|
+
// src/tui/TranscriptView.tsx
|
|
1649
|
+
import { useMemo as useMemo3, useState as useState6 } from "react";
|
|
1650
|
+
import { Box as Box14, Text as Text14, useInput as useInput6 } from "ink";
|
|
1651
|
+
import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1371
1652
|
function TranscriptView({ loading, data, error: error51 }) {
|
|
1372
1653
|
const size = useTerminalSize();
|
|
1373
|
-
const [scroll, setScroll] =
|
|
1654
|
+
const [scroll, setScroll] = useState6(0);
|
|
1374
1655
|
const segments = data?.segments ?? [];
|
|
1375
1656
|
const innerWidth = Math.max(10, size.columns - 2);
|
|
1376
1657
|
const heights = useMemo3(
|
|
@@ -1383,7 +1664,7 @@ function TranscriptView({ loading, data, error: error51 }) {
|
|
|
1383
1664
|
const budget = Math.max(3, size.rows - 3);
|
|
1384
1665
|
const win = windowByHeights(heights, scroll, budget);
|
|
1385
1666
|
const page = Math.max(1, budget - 1);
|
|
1386
|
-
|
|
1667
|
+
useInput6((input, key) => {
|
|
1387
1668
|
if (key.downArrow || input === "j") setScroll((s) => Math.min(win.maxScroll, s + 1));
|
|
1388
1669
|
else if (key.upArrow || input === "k") setScroll((s) => Math.max(0, s - 1));
|
|
1389
1670
|
else if (key.pageDown || input === " ") setScroll((s) => Math.min(win.maxScroll, s + page));
|
|
@@ -1392,42 +1673,42 @@ function TranscriptView({ loading, data, error: error51 }) {
|
|
|
1392
1673
|
else if (input === "G") setScroll(win.maxScroll);
|
|
1393
1674
|
});
|
|
1394
1675
|
if (loading) {
|
|
1395
|
-
return /* @__PURE__ */
|
|
1676
|
+
return /* @__PURE__ */ jsx16(Box14, { paddingX: 1, children: /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "Loading transcript\u2026" }) });
|
|
1396
1677
|
}
|
|
1397
1678
|
if (error51) {
|
|
1398
|
-
return /* @__PURE__ */
|
|
1399
|
-
/* @__PURE__ */
|
|
1679
|
+
return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, children: [
|
|
1680
|
+
/* @__PURE__ */ jsxs13(Text14, { color: "red", children: [
|
|
1400
1681
|
"! ",
|
|
1401
1682
|
error51
|
|
1402
1683
|
] }),
|
|
1403
|
-
/* @__PURE__ */
|
|
1684
|
+
/* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "q / esc / \u2190 back" })
|
|
1404
1685
|
] });
|
|
1405
1686
|
}
|
|
1406
1687
|
if (!data) {
|
|
1407
|
-
return /* @__PURE__ */
|
|
1688
|
+
return /* @__PURE__ */ jsx16(Box14, { paddingX: 1, children: /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "No transcript." }) });
|
|
1408
1689
|
}
|
|
1409
1690
|
const title = data.summary?.title ?? "Transcript";
|
|
1410
1691
|
const total = segments.length;
|
|
1411
1692
|
const more = win.maxScroll > 0;
|
|
1412
1693
|
const position = total === 0 ? "" : `${win.start + 1}\u2013${win.end} / ${total}`;
|
|
1413
|
-
return /* @__PURE__ */
|
|
1414
|
-
/* @__PURE__ */
|
|
1694
|
+
return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, children: [
|
|
1695
|
+
/* @__PURE__ */ jsxs13(Text14, { bold: true, color: "green", children: [
|
|
1415
1696
|
title,
|
|
1416
|
-
more ? /* @__PURE__ */
|
|
1697
|
+
more ? /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: ` ${position}` }) : null
|
|
1417
1698
|
] }),
|
|
1418
|
-
/* @__PURE__ */
|
|
1419
|
-
/* @__PURE__ */
|
|
1699
|
+
/* @__PURE__ */ jsx16(Box14, { marginTop: 1, flexDirection: "column", children: total === 0 ? /* @__PURE__ */ jsx16(Text14, { children: data.text }) : segments.slice(win.start, win.end).map((segment, index) => /* @__PURE__ */ jsxs13(Text14, { children: [
|
|
1700
|
+
/* @__PURE__ */ jsxs13(Text14, { dimColor: true, children: [
|
|
1420
1701
|
"[",
|
|
1421
1702
|
formatClockMs(segment.startMs),
|
|
1422
1703
|
"] "
|
|
1423
1704
|
] }),
|
|
1424
|
-
segment.speaker ? /* @__PURE__ */
|
|
1705
|
+
segment.speaker ? /* @__PURE__ */ jsxs13(Text14, { color: "cyan", children: [
|
|
1425
1706
|
segment.speaker,
|
|
1426
1707
|
": "
|
|
1427
1708
|
] }) : null,
|
|
1428
1709
|
segment.text
|
|
1429
1710
|
] }, win.start + index)) }),
|
|
1430
|
-
/* @__PURE__ */
|
|
1711
|
+
/* @__PURE__ */ jsx16(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { dimColor: true, children: [
|
|
1431
1712
|
more ? "\u2191\u2193 scroll \xB7 PgUp/PgDn \xB7 g/G top/bottom \xB7 " : "",
|
|
1432
1713
|
"q / esc / \u2190 back"
|
|
1433
1714
|
] }) })
|
|
@@ -1442,8 +1723,8 @@ var init_TranscriptView = __esm({
|
|
|
1442
1723
|
});
|
|
1443
1724
|
|
|
1444
1725
|
// src/tui/PermissionPreflightView.tsx
|
|
1445
|
-
import { Box as
|
|
1446
|
-
import { jsx as
|
|
1726
|
+
import { Box as Box15, Text as Text15 } from "ink";
|
|
1727
|
+
import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1447
1728
|
function statusGlyph2(status) {
|
|
1448
1729
|
switch (status) {
|
|
1449
1730
|
case "granted":
|
|
@@ -1459,41 +1740,41 @@ function PermissionPreflightView({
|
|
|
1459
1740
|
}) {
|
|
1460
1741
|
const allGranted = items.length > 0 && items.every((item) => item.status === "granted" && !item.requiresProcessRestart);
|
|
1461
1742
|
const hasRestartRequired = items.some((item) => item.requiresProcessRestart);
|
|
1462
|
-
return /* @__PURE__ */
|
|
1463
|
-
/* @__PURE__ */
|
|
1464
|
-
/* @__PURE__ */
|
|
1465
|
-
/* @__PURE__ */
|
|
1743
|
+
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 1, children: [
|
|
1744
|
+
/* @__PURE__ */ jsxs14(Text15, { children: [
|
|
1745
|
+
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "\u2039 " }),
|
|
1746
|
+
/* @__PURE__ */ jsx17(Text15, { bold: true, children: "Recording permissions" })
|
|
1466
1747
|
] }),
|
|
1467
|
-
/* @__PURE__ */
|
|
1748
|
+
/* @__PURE__ */ jsx17(Box15, { marginTop: 1, flexDirection: "column", children: items.length === 0 ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Checking permissions\u2026" }) : items.map((item) => {
|
|
1468
1749
|
const status = statusGlyph2(item.status);
|
|
1469
1750
|
const restart = item.requiresProcessRestart === true;
|
|
1470
1751
|
const color = restart ? "yellow" : status.color;
|
|
1471
1752
|
const hint = restart ? item.hint ?? `${item.name} enabled. Run recappi record again to start.` : item.status === "granted" ? void 0 : item.hint ?? DEFAULT_HINTS[item.name];
|
|
1472
|
-
return /* @__PURE__ */
|
|
1473
|
-
/* @__PURE__ */
|
|
1474
|
-
/* @__PURE__ */
|
|
1475
|
-
/* @__PURE__ */
|
|
1476
|
-
/* @__PURE__ */
|
|
1753
|
+
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
|
|
1754
|
+
/* @__PURE__ */ jsxs14(Text15, { children: [
|
|
1755
|
+
/* @__PURE__ */ jsx17(Text15, { bold: true, color, children: status.glyph }),
|
|
1756
|
+
/* @__PURE__ */ jsx17(Text15, { bold: true, children: ` ${item.name}` }),
|
|
1757
|
+
/* @__PURE__ */ jsx17(Text15, { color, children: ` ${status.label}` })
|
|
1477
1758
|
] }),
|
|
1478
|
-
hint ? /* @__PURE__ */
|
|
1759
|
+
hint ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: ` ${hint}` }) : null
|
|
1479
1760
|
] }, item.name);
|
|
1480
1761
|
}) }),
|
|
1481
|
-
/* @__PURE__ */
|
|
1482
|
-
/* @__PURE__ */
|
|
1483
|
-
/* @__PURE__ */
|
|
1484
|
-
/* @__PURE__ */
|
|
1485
|
-
] }) : /* @__PURE__ */
|
|
1486
|
-
/* @__PURE__ */
|
|
1487
|
-
/* @__PURE__ */
|
|
1488
|
-
/* @__PURE__ */
|
|
1762
|
+
/* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: allGranted ? /* @__PURE__ */ jsx17(Text15, { bold: true, color: "green", children: "\u2713 All set \u2014 ready to record." }) : hasRestartRequired ? /* @__PURE__ */ jsxs14(Text15, { children: [
|
|
1763
|
+
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Run recappi record again to start, or press " }),
|
|
1764
|
+
/* @__PURE__ */ jsx17(Text15, { bold: true, color: "cyan", children: "r" }),
|
|
1765
|
+
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " to retry." })
|
|
1766
|
+
] }) : /* @__PURE__ */ jsxs14(Text15, { children: [
|
|
1767
|
+
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Grant the permissions above, then press " }),
|
|
1768
|
+
/* @__PURE__ */ jsx17(Text15, { bold: true, color: "cyan", children: "r" }),
|
|
1769
|
+
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " to recheck." })
|
|
1489
1770
|
] }) }),
|
|
1490
|
-
/* @__PURE__ */
|
|
1491
|
-
/* @__PURE__ */
|
|
1492
|
-
/* @__PURE__ */
|
|
1493
|
-
/* @__PURE__ */
|
|
1494
|
-
/* @__PURE__ */
|
|
1495
|
-
/* @__PURE__ */
|
|
1496
|
-
/* @__PURE__ */
|
|
1771
|
+
/* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text15, { children: [
|
|
1772
|
+
/* @__PURE__ */ jsx17(Text15, { color: "cyan", children: "r" }),
|
|
1773
|
+
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " recheck \xB7 " }),
|
|
1774
|
+
/* @__PURE__ */ jsx17(Text15, { color: "cyan", children: "o" }),
|
|
1775
|
+
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " open System Settings \xB7 " }),
|
|
1776
|
+
/* @__PURE__ */ jsx17(Text15, { color: "cyan", children: "esc" }),
|
|
1777
|
+
/* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " back" })
|
|
1497
1778
|
] }) })
|
|
1498
1779
|
] });
|
|
1499
1780
|
}
|
|
@@ -1509,9 +1790,9 @@ var init_PermissionPreflightView = __esm({
|
|
|
1509
1790
|
});
|
|
1510
1791
|
|
|
1511
1792
|
// src/tui/RecordSetupView.tsx
|
|
1512
|
-
import { useEffect as useEffect3, useRef as
|
|
1513
|
-
import { Box as
|
|
1514
|
-
import { jsx as
|
|
1793
|
+
import { useEffect as useEffect3, useRef as useRef3, useState as useState7 } from "react";
|
|
1794
|
+
import { Box as Box16, Text as Text16, useInput as useInput7 } from "ink";
|
|
1795
|
+
import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1515
1796
|
function levelDb2(level) {
|
|
1516
1797
|
if (level <= 0.03) return "silent";
|
|
1517
1798
|
return `${Math.round(level * 60 - 60)} dB`;
|
|
@@ -1522,11 +1803,11 @@ function meterBar(level, width) {
|
|
|
1522
1803
|
return "\u2587".repeat(filled) + "\u2591".repeat(Math.max(0, width - filled));
|
|
1523
1804
|
}
|
|
1524
1805
|
function InputMeter({ level }) {
|
|
1525
|
-
if (level == null) return /* @__PURE__ */
|
|
1806
|
+
if (level == null) return /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "\u2014" });
|
|
1526
1807
|
const silent = level <= 0.03;
|
|
1527
|
-
return /* @__PURE__ */
|
|
1528
|
-
/* @__PURE__ */
|
|
1529
|
-
/* @__PURE__ */
|
|
1808
|
+
return /* @__PURE__ */ jsxs15(Text16, { children: [
|
|
1809
|
+
/* @__PURE__ */ jsx18(Text16, { color: silent ? "yellow" : "cyan", children: meterBar(level, METER_W) }),
|
|
1810
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: ` ${levelDb2(level)}` })
|
|
1530
1811
|
] });
|
|
1531
1812
|
}
|
|
1532
1813
|
function RecordSetupView({
|
|
@@ -1536,13 +1817,13 @@ function RecordSetupView({
|
|
|
1536
1817
|
onCancel,
|
|
1537
1818
|
onSelectionChange
|
|
1538
1819
|
}) {
|
|
1539
|
-
const [srcIdx, setSrcIdx] =
|
|
1540
|
-
const [includeMic, setIncludeMic] =
|
|
1541
|
-
const [micIdx, setMicIdx] =
|
|
1820
|
+
const [srcIdx, setSrcIdx] = useState7(0);
|
|
1821
|
+
const [includeMic, setIncludeMic] = useState7(true);
|
|
1822
|
+
const [micIdx, setMicIdx] = useState7(
|
|
1542
1823
|
() => Math.max(0, model.microphones?.findIndex((device) => device.isDefault) ?? 0)
|
|
1543
1824
|
);
|
|
1544
|
-
const [sceneIdx, setSceneIdx] =
|
|
1545
|
-
const userPickedMic =
|
|
1825
|
+
const [sceneIdx, setSceneIdx] = useState7(0);
|
|
1826
|
+
const userPickedMic = useRef3(false);
|
|
1546
1827
|
const sources = model.sources;
|
|
1547
1828
|
const microphones = model.microphones ?? [];
|
|
1548
1829
|
const selected = sources[Math.min(srcIdx, Math.max(0, sources.length - 1))];
|
|
@@ -1573,7 +1854,7 @@ function RecordSetupView({
|
|
|
1573
1854
|
const di = microphones.findIndex((device) => device.isDefault);
|
|
1574
1855
|
if (di > 0) setMicIdx(di);
|
|
1575
1856
|
}, [microphones.length]);
|
|
1576
|
-
|
|
1857
|
+
useInput7((input, key) => {
|
|
1577
1858
|
if (key.upArrow || input === "k") setSrcIdx((i) => Math.max(0, i - 1));
|
|
1578
1859
|
else if (key.downArrow || input === "j") setSrcIdx((i) => Math.min(sources.length - 1, i + 1));
|
|
1579
1860
|
else if (input === " ") setIncludeMic((m) => !m);
|
|
@@ -1584,26 +1865,26 @@ function RecordSetupView({
|
|
|
1584
1865
|
else if (key.return && selected) onStart(selection);
|
|
1585
1866
|
else if (key.escape) onCancel();
|
|
1586
1867
|
});
|
|
1587
|
-
const sourceList = /* @__PURE__ */
|
|
1588
|
-
/* @__PURE__ */
|
|
1589
|
-
/* @__PURE__ */
|
|
1590
|
-
/* @__PURE__ */
|
|
1868
|
+
const sourceList = /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", children: [
|
|
1869
|
+
/* @__PURE__ */ jsxs15(Box16, { children: [
|
|
1870
|
+
/* @__PURE__ */ jsx18(Box16, { width: 36, children: /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "SOURCE" }) }),
|
|
1871
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "INPUT" })
|
|
1591
1872
|
] }),
|
|
1592
1873
|
sources.map((s, i) => {
|
|
1593
1874
|
const on = i === srcIdx;
|
|
1594
|
-
return /* @__PURE__ */
|
|
1595
|
-
/* @__PURE__ */
|
|
1875
|
+
return /* @__PURE__ */ jsxs15(Box16, { children: [
|
|
1876
|
+
/* @__PURE__ */ jsx18(Box16, { width: 36, children: /* @__PURE__ */ jsxs15(Text16, { color: on ? "cyan" : void 0, wrap: "truncate-end", children: [
|
|
1596
1877
|
on ? "\u25B8 \u25CF " : " \u25CB ",
|
|
1597
1878
|
s.label
|
|
1598
1879
|
] }) }),
|
|
1599
|
-
/* @__PURE__ */
|
|
1880
|
+
/* @__PURE__ */ jsx18(InputMeter, { level: levels?.bySourceId?.[s.id] })
|
|
1600
1881
|
] }, s.id);
|
|
1601
1882
|
}),
|
|
1602
|
-
!hasAppSource ? /* @__PURE__ */
|
|
1883
|
+
!hasAppSource ? /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "No app-specific sources available right now" }) : null
|
|
1603
1884
|
] });
|
|
1604
|
-
const capturePlan = /* @__PURE__ */
|
|
1605
|
-
/* @__PURE__ */
|
|
1606
|
-
/* @__PURE__ */
|
|
1885
|
+
const capturePlan = /* @__PURE__ */ jsxs15(Text16, { children: [
|
|
1886
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Capture " }),
|
|
1887
|
+
/* @__PURE__ */ jsx18(Text16, { children: includeMic ? `${selected?.label ?? "System audio"} + microphone` : `${selected?.label ?? "System audio"} only` })
|
|
1607
1888
|
] });
|
|
1608
1889
|
const shortcuts = [
|
|
1609
1890
|
hasMultipleSources ? "\u2191\u2193 source" : void 0,
|
|
@@ -1613,33 +1894,33 @@ function RecordSetupView({
|
|
|
1613
1894
|
"\u23CE start recording",
|
|
1614
1895
|
"esc cancel"
|
|
1615
1896
|
].filter(Boolean).join(" \xB7 ");
|
|
1616
|
-
return /* @__PURE__ */
|
|
1617
|
-
/* @__PURE__ */
|
|
1618
|
-
/* @__PURE__ */
|
|
1897
|
+
return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", paddingX: 1, children: [
|
|
1898
|
+
/* @__PURE__ */ jsx18(Text16, { bold: true, color: "green", children: "New recording" }),
|
|
1899
|
+
/* @__PURE__ */ jsxs15(Box16, { marginTop: 1, flexDirection: "column", children: [
|
|
1619
1900
|
sourceList,
|
|
1620
|
-
/* @__PURE__ */
|
|
1901
|
+
/* @__PURE__ */ jsx18(Box16, { marginTop: 1, children: capturePlan })
|
|
1621
1902
|
] }),
|
|
1622
|
-
/* @__PURE__ */
|
|
1623
|
-
/* @__PURE__ */
|
|
1624
|
-
/* @__PURE__ */
|
|
1625
|
-
/* @__PURE__ */
|
|
1626
|
-
/* @__PURE__ */
|
|
1903
|
+
/* @__PURE__ */ jsxs15(Box16, { marginTop: 1, flexDirection: "column", children: [
|
|
1904
|
+
/* @__PURE__ */ jsxs15(Text16, { children: [
|
|
1905
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Microphone " }),
|
|
1906
|
+
/* @__PURE__ */ jsx18(Text16, { color: includeMic ? "green" : "gray", children: includeMic ? "[x] include mic" : "[ ] include mic" }),
|
|
1907
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " (space)" })
|
|
1627
1908
|
] }),
|
|
1628
|
-
selectedMic ? /* @__PURE__ */
|
|
1629
|
-
/* @__PURE__ */
|
|
1630
|
-
/* @__PURE__ */
|
|
1909
|
+
selectedMic ? /* @__PURE__ */ jsxs15(Box16, { children: [
|
|
1910
|
+
/* @__PURE__ */ jsx18(Box16, { width: 36, children: /* @__PURE__ */ jsxs15(Text16, { dimColor: !includeMic, wrap: "truncate-end", children: [
|
|
1911
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Mic device " }),
|
|
1631
1912
|
selectedMic.label
|
|
1632
1913
|
] }) }),
|
|
1633
|
-
includeMic ? /* @__PURE__ */
|
|
1914
|
+
includeMic ? /* @__PURE__ */ jsx18(InputMeter, { level: levels?.byMicrophoneId?.[selectedMic.id] }) : null
|
|
1634
1915
|
] }) : null,
|
|
1635
|
-
includeMic && hasMultipleMicrophones ? /* @__PURE__ */
|
|
1636
|
-
model.scenes.length > 0 ? /* @__PURE__ */
|
|
1637
|
-
/* @__PURE__ */
|
|
1638
|
-
/* @__PURE__ */
|
|
1639
|
-
model.scenes.length > 1 ? /* @__PURE__ */
|
|
1916
|
+
includeMic && hasMultipleMicrophones ? /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " (m to change device)" }) : null,
|
|
1917
|
+
model.scenes.length > 0 ? /* @__PURE__ */ jsxs15(Text16, { children: [
|
|
1918
|
+
/* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Scene " }),
|
|
1919
|
+
/* @__PURE__ */ jsx18(Text16, { children: model.scenes[sceneIdx]?.label ?? "Default" }),
|
|
1920
|
+
model.scenes.length > 1 ? /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " (s to change)" }) : null
|
|
1640
1921
|
] }) : null
|
|
1641
1922
|
] }),
|
|
1642
|
-
/* @__PURE__ */
|
|
1923
|
+
/* @__PURE__ */ jsx18(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: shortcuts }) })
|
|
1643
1924
|
] });
|
|
1644
1925
|
}
|
|
1645
1926
|
var METER_W;
|
|
@@ -1651,9 +1932,9 @@ var init_RecordSetupView = __esm({
|
|
|
1651
1932
|
});
|
|
1652
1933
|
|
|
1653
1934
|
// src/tui/RecordFrame.tsx
|
|
1654
|
-
import { useState as
|
|
1655
|
-
import { Box as
|
|
1656
|
-
import { jsx as
|
|
1935
|
+
import { useState as useState8 } from "react";
|
|
1936
|
+
import { Box as Box17, Text as Text17, useInput as useInput8 } from "ink";
|
|
1937
|
+
import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1657
1938
|
function levelDb3(level) {
|
|
1658
1939
|
if (level <= 0.03) return "silent";
|
|
1659
1940
|
return `${Math.round(level * 60 - 60)} dB`;
|
|
@@ -1662,14 +1943,14 @@ function CompactMeter({ label, level }) {
|
|
|
1662
1943
|
const width = 12;
|
|
1663
1944
|
const filled = Math.max(0, Math.min(width, Math.round(Math.max(0, Math.min(1, level)) * width)));
|
|
1664
1945
|
const silent = level <= 0.03;
|
|
1665
|
-
return /* @__PURE__ */
|
|
1666
|
-
/* @__PURE__ */
|
|
1946
|
+
return /* @__PURE__ */ jsxs16(Text17, { children: [
|
|
1947
|
+
/* @__PURE__ */ jsxs16(Text17, { dimColor: true, children: [
|
|
1667
1948
|
label,
|
|
1668
1949
|
" "
|
|
1669
1950
|
] }),
|
|
1670
|
-
/* @__PURE__ */
|
|
1671
|
-
/* @__PURE__ */
|
|
1672
|
-
/* @__PURE__ */
|
|
1951
|
+
/* @__PURE__ */ jsx19(Text17, { color: silent ? "yellow" : "cyan", children: "\u25CF".repeat(filled) }),
|
|
1952
|
+
/* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "\xB7".repeat(width - filled) }),
|
|
1953
|
+
/* @__PURE__ */ jsx19(Text17, { dimColor: true, children: ` ${levelDb3(level)}` })
|
|
1673
1954
|
] });
|
|
1674
1955
|
}
|
|
1675
1956
|
function CaptionColumn({
|
|
@@ -1688,7 +1969,7 @@ function CaptionColumn({
|
|
|
1688
1969
|
chosen.unshift(lines[i]);
|
|
1689
1970
|
used += h;
|
|
1690
1971
|
}
|
|
1691
|
-
return /* @__PURE__ */
|
|
1972
|
+
return /* @__PURE__ */ jsx19(Box17, { width, flexDirection: "column", children: chosen.length === 0 ? /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "Listening for speech\u2026" }) : chosen.map((l, i) => /* @__PURE__ */ jsx19(Text17, { dimColor: dim, wrap: "wrap", children: l }, i)) });
|
|
1692
1973
|
}
|
|
1693
1974
|
function outcomeLine(telemetry, artifact) {
|
|
1694
1975
|
if (telemetry.status === "recording" || telemetry.status === "starting") {
|
|
@@ -1720,10 +2001,10 @@ function RecordFrame({
|
|
|
1720
2001
|
spinnerFrame = 0
|
|
1721
2002
|
}) {
|
|
1722
2003
|
const size = useTerminalSize();
|
|
1723
|
-
const [captionMode, setCaptionMode] =
|
|
1724
|
-
const [scrollBack, setScrollBack] =
|
|
2004
|
+
const [captionMode, setCaptionMode] = useState8("both");
|
|
2005
|
+
const [scrollBack, setScrollBack] = useState8(0);
|
|
1725
2006
|
const PAGE = 8;
|
|
1726
|
-
|
|
2007
|
+
useInput8((input, key) => {
|
|
1727
2008
|
if (input === "c") {
|
|
1728
2009
|
setCaptionMode((m) => m === "both" ? "source" : m === "source" ? "translation" : "both");
|
|
1729
2010
|
} else if (key.upArrow || input === "k") setScrollBack((s) => s + 1);
|
|
@@ -1751,49 +2032,49 @@ function RecordFrame({
|
|
|
1751
2032
|
] : [];
|
|
1752
2033
|
const status = telemetry.sizeBytes ? formatBytes3(telemetry.sizeBytes) : "";
|
|
1753
2034
|
const sourceLine = [telemetry.sourceLabel, telemetry.micEnabled ? "Microphone" : null, status || null].filter(Boolean).join(" \xB7 ");
|
|
1754
|
-
return /* @__PURE__ */
|
|
1755
|
-
/* @__PURE__ */
|
|
1756
|
-
/* @__PURE__ */
|
|
1757
|
-
/* @__PURE__ */
|
|
1758
|
-
/* @__PURE__ */
|
|
2035
|
+
return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", paddingX: 1, height: size.rows, children: [
|
|
2036
|
+
/* @__PURE__ */ jsxs16(Box17, { justifyContent: "space-between", children: [
|
|
2037
|
+
/* @__PURE__ */ jsxs16(Text17, { children: [
|
|
2038
|
+
/* @__PURE__ */ jsx19(Text17, { bold: true, color: "green", children: "recappi" }),
|
|
2039
|
+
/* @__PURE__ */ jsx19(Text17, { dimColor: true, children: " \xB7 Recording" })
|
|
1759
2040
|
] }),
|
|
1760
|
-
/* @__PURE__ */
|
|
1761
|
-
/* @__PURE__ */
|
|
1762
|
-
/* @__PURE__ */
|
|
2041
|
+
/* @__PURE__ */ jsxs16(Text17, { children: [
|
|
2042
|
+
/* @__PURE__ */ jsx19(Text17, { bold: true, color: recording ? "red" : "gray", children: stateLabel }),
|
|
2043
|
+
/* @__PURE__ */ jsx19(Text17, { dimColor: true, children: ` ${elapsed}${ids ? ` \xB7 ${ids}` : ""}` })
|
|
1763
2044
|
] })
|
|
1764
2045
|
] }),
|
|
1765
|
-
/* @__PURE__ */
|
|
1766
|
-
/* @__PURE__ */
|
|
1767
|
-
/* @__PURE__ */
|
|
1768
|
-
/* @__PURE__ */
|
|
1769
|
-
/* @__PURE__ */
|
|
2046
|
+
/* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "\u2500".repeat(innerWidth) }),
|
|
2047
|
+
/* @__PURE__ */ jsxs16(Box17, { flexGrow: 1, children: [
|
|
2048
|
+
/* @__PURE__ */ jsxs16(Box17, { width: listWidth, flexDirection: "column", children: [
|
|
2049
|
+
/* @__PURE__ */ jsx19(Text17, { dimColor: true, children: `RECORDINGS \xB7 ${recordings.length}` }),
|
|
2050
|
+
/* @__PURE__ */ jsx19(Box17, { marginTop: 1, flexDirection: "column", children: recordings.slice(0, Math.max(1, size.rows - 8)).map((rec, i) => {
|
|
1770
2051
|
const st = recordingProcessingState(rec, void 0, spinnerFrame);
|
|
1771
2052
|
const sel = i === selectedIndex;
|
|
1772
|
-
return /* @__PURE__ */
|
|
1773
|
-
/* @__PURE__ */
|
|
1774
|
-
/* @__PURE__ */
|
|
1775
|
-
/* @__PURE__ */
|
|
2053
|
+
return /* @__PURE__ */ jsxs16(Box17, { children: [
|
|
2054
|
+
/* @__PURE__ */ jsx19(Box17, { width: 2, children: /* @__PURE__ */ jsx19(Text17, { color: "cyan", children: sel ? "\u25B8" : "" }) }),
|
|
2055
|
+
/* @__PURE__ */ jsx19(Box17, { width: 2, children: /* @__PURE__ */ jsx19(Text17, { color: st.color, children: st.glyph }) }),
|
|
2056
|
+
/* @__PURE__ */ jsx19(Box17, { width: listWidth - 4, children: /* @__PURE__ */ jsx19(Text17, { bold: sel, wrap: "truncate-end", children: recordingTitle2(rec) }) })
|
|
1776
2057
|
] }, rec.recordingId);
|
|
1777
2058
|
}) })
|
|
1778
2059
|
] }),
|
|
1779
|
-
/* @__PURE__ */
|
|
1780
|
-
/* @__PURE__ */
|
|
1781
|
-
/* @__PURE__ */
|
|
1782
|
-
/* @__PURE__ */
|
|
1783
|
-
telemetry.level ? /* @__PURE__ */
|
|
1784
|
-
/* @__PURE__ */
|
|
1785
|
-
telemetry.micEnabled ? /* @__PURE__ */
|
|
1786
|
-
telemetry.micEnabled ? /* @__PURE__ */
|
|
1787
|
-
] }) : /* @__PURE__ */
|
|
1788
|
-
/* @__PURE__ */
|
|
1789
|
-
/* @__PURE__ */
|
|
1790
|
-
captionMode !== "translation" ? /* @__PURE__ */
|
|
1791
|
-
captionMode === "both" ? /* @__PURE__ */
|
|
1792
|
-
captionMode !== "source" ? /* @__PURE__ */
|
|
1793
|
-
scrollBack > 0 ? /* @__PURE__ */
|
|
2060
|
+
/* @__PURE__ */ jsx19(Box17, { width: 3, flexDirection: "column", alignItems: "center", children: Array.from({ length: Math.max(1, size.rows - 6) }, (_, i) => /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "\u2502" }, i)) }),
|
|
2061
|
+
/* @__PURE__ */ jsxs16(Box17, { width: rightWidth, flexDirection: "column", children: [
|
|
2062
|
+
/* @__PURE__ */ jsx19(Text17, { bold: true, wrap: "truncate-end", children: title }),
|
|
2063
|
+
/* @__PURE__ */ jsx19(Text17, { dimColor: true, wrap: "truncate-end", children: sourceLine }),
|
|
2064
|
+
telemetry.level ? /* @__PURE__ */ jsxs16(Box17, { children: [
|
|
2065
|
+
/* @__PURE__ */ jsx19(CompactMeter, { label: "System", level: telemetry.level.system ?? 0 }),
|
|
2066
|
+
telemetry.micEnabled ? /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: " " }) : null,
|
|
2067
|
+
telemetry.micEnabled ? /* @__PURE__ */ jsx19(CompactMeter, { label: "Mic", level: telemetry.level.mic ?? 0 }) : null
|
|
2068
|
+
] }) : /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "Capturing audio\u2026" }),
|
|
2069
|
+
/* @__PURE__ */ jsxs16(Box17, { marginTop: 1, flexDirection: "column", flexGrow: 1, children: [
|
|
2070
|
+
/* @__PURE__ */ jsxs16(Box17, { children: [
|
|
2071
|
+
captionMode !== "translation" ? /* @__PURE__ */ jsx19(Box17, { width: captionMode === "both" ? Math.floor((rightWidth - 3) / 2) : rightWidth, children: /* @__PURE__ */ jsx19(Text17, { bold: true, dimColor: true, children: "ORIGINAL" }) }) : null,
|
|
2072
|
+
captionMode === "both" ? /* @__PURE__ */ jsx19(Box17, { width: 3 }) : null,
|
|
2073
|
+
captionMode !== "source" ? /* @__PURE__ */ jsx19(Text17, { bold: true, dimColor: true, children: "TRANSLATION" }) : null,
|
|
2074
|
+
scrollBack > 0 ? /* @__PURE__ */ jsx19(Text17, { color: "yellow", children: " \u23F8 scrolled \xB7 G live" }) : null
|
|
1794
2075
|
] }),
|
|
1795
|
-
/* @__PURE__ */
|
|
1796
|
-
captionMode !== "translation" ? /* @__PURE__ */
|
|
2076
|
+
/* @__PURE__ */ jsxs16(Box17, { children: [
|
|
2077
|
+
captionMode !== "translation" ? /* @__PURE__ */ jsx19(
|
|
1797
2078
|
CaptionColumn,
|
|
1798
2079
|
{
|
|
1799
2080
|
lines: sourceLines,
|
|
@@ -1802,8 +2083,8 @@ function RecordFrame({
|
|
|
1802
2083
|
scrollBack
|
|
1803
2084
|
}
|
|
1804
2085
|
) : null,
|
|
1805
|
-
captionMode === "both" ? /* @__PURE__ */
|
|
1806
|
-
captionMode !== "source" ? /* @__PURE__ */
|
|
2086
|
+
captionMode === "both" ? /* @__PURE__ */ jsx19(Box17, { width: 3, flexDirection: "column", children: Array.from({ length: Math.min(captionRows, 12) }, (_, i) => /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "\u2502" }, i)) }) : null,
|
|
2087
|
+
captionMode !== "source" ? /* @__PURE__ */ jsx19(
|
|
1807
2088
|
CaptionColumn,
|
|
1808
2089
|
{
|
|
1809
2090
|
lines: translationLines,
|
|
@@ -1815,14 +2096,14 @@ function RecordFrame({
|
|
|
1815
2096
|
) : null
|
|
1816
2097
|
] })
|
|
1817
2098
|
] }),
|
|
1818
|
-
/* @__PURE__ */
|
|
1819
|
-
/* @__PURE__ */
|
|
1820
|
-
/* @__PURE__ */
|
|
2099
|
+
/* @__PURE__ */ jsxs16(Box17, { marginTop: 1, children: [
|
|
2100
|
+
/* @__PURE__ */ jsx19(Text17, { bold: true, dimColor: true, children: "OUTCOME " }),
|
|
2101
|
+
/* @__PURE__ */ jsx19(Text17, { dimColor: true, children: outcomeLine(telemetry, artifact) })
|
|
1821
2102
|
] })
|
|
1822
2103
|
] })
|
|
1823
2104
|
] }),
|
|
1824
|
-
/* @__PURE__ */
|
|
1825
|
-
/* @__PURE__ */
|
|
2105
|
+
/* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "\u2500".repeat(innerWidth) }),
|
|
2106
|
+
/* @__PURE__ */ jsx19(Text17, { dimColor: true, children: recording ? `q stop & save \xB7 c captions (${captionMode}) \xB7 \u2191\u2193 scroll \xB7 G live` : `\u23CE open \xB7 T re-transcribe \xB7 c captions (${captionMode}) \xB7 \u2191\u2193 scroll \xB7 1 overview 2 jobs 3 account` })
|
|
1826
2107
|
] });
|
|
1827
2108
|
}
|
|
1828
2109
|
var trimLead2, wrappedRows2;
|
|
@@ -1838,9 +2119,9 @@ var init_RecordFrame = __esm({
|
|
|
1838
2119
|
});
|
|
1839
2120
|
|
|
1840
2121
|
// src/tui/AppShell.tsx
|
|
1841
|
-
import { useCallback, useEffect as useEffect4, useRef as
|
|
1842
|
-
import { Box as
|
|
1843
|
-
import { Fragment as Fragment6, jsx as
|
|
2122
|
+
import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef4, useState as useState9 } from "react";
|
|
2123
|
+
import { Box as Box18, Text as Text18, useApp, useInput as useInput9 } from "ink";
|
|
2124
|
+
import { Fragment as Fragment6, jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1844
2125
|
function recordErrorCopy(code, message) {
|
|
1845
2126
|
switch (code) {
|
|
1846
2127
|
case "record.helper_unavailable":
|
|
@@ -2011,6 +2292,7 @@ function AppShell({
|
|
|
2011
2292
|
onSyncRecordingText,
|
|
2012
2293
|
onSyncRecordingAudio,
|
|
2013
2294
|
onExportRecording,
|
|
2295
|
+
askRecording,
|
|
2014
2296
|
initialView = "overview",
|
|
2015
2297
|
openUrl: openUrl2,
|
|
2016
2298
|
copyText: copyText2,
|
|
@@ -2020,47 +2302,47 @@ function AppShell({
|
|
|
2020
2302
|
}) {
|
|
2021
2303
|
const { exit } = useApp();
|
|
2022
2304
|
const size = useTerminalSize();
|
|
2023
|
-
const [jobs, setJobs] =
|
|
2024
|
-
const [recordings, setRecordings] =
|
|
2025
|
-
const [recordingsNextCursor, setRecordingsNextCursor] =
|
|
2026
|
-
const [recordingsTotalCount, setRecordingsTotalCount] =
|
|
2027
|
-
const [stats, setStats] =
|
|
2028
|
-
const [accountStatus, setAccountStatus] =
|
|
2029
|
-
const [origin, setOrigin] =
|
|
2030
|
-
const [stack, setStack] =
|
|
2031
|
-
const [selected, setSelected] =
|
|
2032
|
-
const [spinnerFrame, setSpinnerFrame] =
|
|
2033
|
-
const [loadingMoreRecordings, setLoadingMoreRecordings] =
|
|
2034
|
-
const [revalidatingRecordings, setRevalidatingRecordings] =
|
|
2035
|
-
const [loaded, setLoaded] =
|
|
2036
|
-
const [loadError, setLoadError] =
|
|
2037
|
-
const [notice, setNotice] =
|
|
2038
|
-
const [summaryCache, setSummaryCache] =
|
|
2039
|
-
const [transcriptCache, setTranscriptCache] =
|
|
2305
|
+
const [jobs, setJobs] = useState9([]);
|
|
2306
|
+
const [recordings, setRecordings] = useState9([]);
|
|
2307
|
+
const [recordingsNextCursor, setRecordingsNextCursor] = useState9(null);
|
|
2308
|
+
const [recordingsTotalCount, setRecordingsTotalCount] = useState9(void 0);
|
|
2309
|
+
const [stats, setStats] = useState9(void 0);
|
|
2310
|
+
const [accountStatus, setAccountStatus] = useState9("loading");
|
|
2311
|
+
const [origin, setOrigin] = useState9("");
|
|
2312
|
+
const [stack, setStack] = useState9([{ kind: initialView }]);
|
|
2313
|
+
const [selected, setSelected] = useState9(0);
|
|
2314
|
+
const [spinnerFrame, setSpinnerFrame] = useState9(0);
|
|
2315
|
+
const [loadingMoreRecordings, setLoadingMoreRecordings] = useState9(false);
|
|
2316
|
+
const [revalidatingRecordings, setRevalidatingRecordings] = useState9(false);
|
|
2317
|
+
const [loaded, setLoaded] = useState9(false);
|
|
2318
|
+
const [loadError, setLoadError] = useState9(void 0);
|
|
2319
|
+
const [notice, setNotice] = useState9(void 0);
|
|
2320
|
+
const [summaryCache, setSummaryCache] = useState9(() => /* @__PURE__ */ new Map());
|
|
2321
|
+
const [transcriptCache, setTranscriptCache] = useState9(
|
|
2040
2322
|
() => /* @__PURE__ */ new Map()
|
|
2041
2323
|
);
|
|
2042
|
-
const [audioCache, setAudioCache] =
|
|
2043
|
-
const [downloadedIds, setDownloadedIds] =
|
|
2044
|
-
const summaryRefreshRef =
|
|
2045
|
-
const [liveRecord, setLiveRecord] =
|
|
2046
|
-
const [recordSetupInputs, setRecordSetupInputs] =
|
|
2324
|
+
const [audioCache, setAudioCache] = useState9(() => /* @__PURE__ */ new Map());
|
|
2325
|
+
const [downloadedIds, setDownloadedIds] = useState9(() => /* @__PURE__ */ new Set());
|
|
2326
|
+
const summaryRefreshRef = useRef4(null);
|
|
2327
|
+
const [liveRecord, setLiveRecord] = useState9(void 0);
|
|
2328
|
+
const [recordSetupInputs, setRecordSetupInputs] = useState9({
|
|
2047
2329
|
sources: DEFAULT_RECORDING_SOURCES,
|
|
2048
2330
|
microphones: []
|
|
2049
2331
|
});
|
|
2050
|
-
const [recordSetupSelection, setRecordSetupSelection] =
|
|
2332
|
+
const [recordSetupSelection, setRecordSetupSelection] = useState9(
|
|
2051
2333
|
DEFAULT_RECORDING_SELECTION
|
|
2052
2334
|
);
|
|
2053
|
-
const [recordSetupLevels, setRecordSetupLevels] =
|
|
2335
|
+
const [recordSetupLevels, setRecordSetupLevels] = useState9({
|
|
2054
2336
|
bySourceId: {},
|
|
2055
2337
|
byMicrophoneId: {}
|
|
2056
2338
|
});
|
|
2057
|
-
const autoTranscribeStartedSessionIds =
|
|
2339
|
+
const autoTranscribeStartedSessionIds = useRef4(/* @__PURE__ */ new Set());
|
|
2058
2340
|
const recordSetupModel = {
|
|
2059
2341
|
sources: recordSetupInputs.sources.length > 0 ? recordSetupInputs.sources : DEFAULT_RECORDING_SOURCES,
|
|
2060
2342
|
microphones: recordSetupInputs.microphones ?? [],
|
|
2061
2343
|
scenes: DEFAULT_RECORDING_SCENES
|
|
2062
2344
|
};
|
|
2063
|
-
const refreshDownloadedIds =
|
|
2345
|
+
const refreshDownloadedIds = useCallback2(async () => {
|
|
2064
2346
|
if (!listDownloadedRecordingIds) return;
|
|
2065
2347
|
try {
|
|
2066
2348
|
setDownloadedIds(await listDownloadedRecordingIds());
|
|
@@ -2071,11 +2353,11 @@ function AppShell({
|
|
|
2071
2353
|
void refreshDownloadedIds();
|
|
2072
2354
|
}, [refreshDownloadedIds]);
|
|
2073
2355
|
const screen = stack[stack.length - 1];
|
|
2074
|
-
const recordingsRef =
|
|
2356
|
+
const recordingsRef = useRef4(recordings);
|
|
2075
2357
|
recordingsRef.current = recordings;
|
|
2076
|
-
const selectedRef =
|
|
2358
|
+
const selectedRef = useRef4(selected);
|
|
2077
2359
|
selectedRef.current = selected;
|
|
2078
|
-
const screenRef =
|
|
2360
|
+
const screenRef = useRef4(screen);
|
|
2079
2361
|
screenRef.current = screen;
|
|
2080
2362
|
useEffect4(() => {
|
|
2081
2363
|
if (screen.kind !== "recordSetup" || !startRecordSetupPreview) return;
|
|
@@ -2125,7 +2407,7 @@ function AppShell({
|
|
|
2125
2407
|
screen.kind,
|
|
2126
2408
|
startRecordSetupPreview
|
|
2127
2409
|
]);
|
|
2128
|
-
const beginLiveRecord =
|
|
2410
|
+
const beginLiveRecord = useCallback2(
|
|
2129
2411
|
(selection = DEFAULT_RECORDING_SELECTION) => {
|
|
2130
2412
|
const capture = recordingCaptureMappingFromSelection(selection, recordSetupModel.sources);
|
|
2131
2413
|
const telemetry = {
|
|
@@ -2167,7 +2449,7 @@ function AppShell({
|
|
|
2167
2449
|
},
|
|
2168
2450
|
[now, recordSetupModel.sources, startLiveRecord]
|
|
2169
2451
|
);
|
|
2170
|
-
const stopLiveRecord =
|
|
2452
|
+
const stopLiveRecord = useCallback2(async () => {
|
|
2171
2453
|
const current = liveRecord;
|
|
2172
2454
|
if (current?.kind === "live") {
|
|
2173
2455
|
const stoppingTelemetry = { ...current.telemetry, status: "stopping" };
|
|
@@ -2254,7 +2536,7 @@ function AppShell({
|
|
|
2254
2536
|
};
|
|
2255
2537
|
}, [peekTranscriptId, fetchTranscript]);
|
|
2256
2538
|
const peekSummary = peekTranscriptId ? summaryCache.get(peekTranscriptId) : void 0;
|
|
2257
|
-
const refresh =
|
|
2539
|
+
const refresh = useCallback2(async ({ resetRecordings = false } = {}) => {
|
|
2258
2540
|
let showingCachedRecordings = false;
|
|
2259
2541
|
if (resetRecordings && fetchCachedRecordings) {
|
|
2260
2542
|
try {
|
|
@@ -2303,7 +2585,7 @@ function AppShell({
|
|
|
2303
2585
|
setLoaded(true);
|
|
2304
2586
|
if (showingCachedRecordings) setRevalidatingRecordings(false);
|
|
2305
2587
|
}, [fetchJobs, fetchRecordings, fetchCachedRecordings, fetchDashboardStats, fetchAccountStatus]);
|
|
2306
|
-
const transcribeStoppedRecording =
|
|
2588
|
+
const transcribeStoppedRecording = useCallback2(async () => {
|
|
2307
2589
|
const current = liveRecord;
|
|
2308
2590
|
if (current?.kind !== "stopped") return;
|
|
2309
2591
|
const artifact = current.artifact;
|
|
@@ -2383,7 +2665,7 @@ function AppShell({
|
|
|
2383
2665
|
setNotice("Transcription failed. Press enter to retry.");
|
|
2384
2666
|
}
|
|
2385
2667
|
}, [liveRecord, refresh, transcribeRecordingArtifact]);
|
|
2386
|
-
const retranscribeStoppedRecording =
|
|
2668
|
+
const retranscribeStoppedRecording = useCallback2(async () => {
|
|
2387
2669
|
const current = liveRecord;
|
|
2388
2670
|
if (current?.kind !== "stopped") return;
|
|
2389
2671
|
const artifact = current.artifact;
|
|
@@ -2442,7 +2724,7 @@ function AppShell({
|
|
|
2442
2724
|
setNotice("Re-transcription failed. Press T to retry.");
|
|
2443
2725
|
}
|
|
2444
2726
|
}, [liveRecord, onRetranscribe, refresh]);
|
|
2445
|
-
const retranscribeExistingRecording =
|
|
2727
|
+
const retranscribeExistingRecording = useCallback2(
|
|
2446
2728
|
async (recordingId) => {
|
|
2447
2729
|
if (!onRetranscribe) {
|
|
2448
2730
|
setNotice("Re-transcribe is not available in this CLI session.");
|
|
@@ -2459,7 +2741,7 @@ function AppShell({
|
|
|
2459
2741
|
},
|
|
2460
2742
|
[onRetranscribe, refresh]
|
|
2461
2743
|
);
|
|
2462
|
-
const refetchTranscript =
|
|
2744
|
+
const refetchTranscript = useCallback2(
|
|
2463
2745
|
(transcriptId) => {
|
|
2464
2746
|
setTranscriptCache((m) => new Map(m).set(transcriptId, "loading"));
|
|
2465
2747
|
setSummaryCache((m) => {
|
|
@@ -2471,7 +2753,7 @@ function AppShell({
|
|
|
2471
2753
|
},
|
|
2472
2754
|
[fetchTranscript]
|
|
2473
2755
|
);
|
|
2474
|
-
const resummarizeExistingRecording =
|
|
2756
|
+
const resummarizeExistingRecording = useCallback2(
|
|
2475
2757
|
async (recordingId) => {
|
|
2476
2758
|
if (!onResummarize) {
|
|
2477
2759
|
setNotice("Re-summarize is not available in this CLI session.");
|
|
@@ -2493,22 +2775,30 @@ function AppShell({
|
|
|
2493
2775
|
},
|
|
2494
2776
|
[onResummarize, refresh, recordings, now, refetchTranscript]
|
|
2495
2777
|
);
|
|
2496
|
-
const
|
|
2497
|
-
|
|
2778
|
+
const [sessionDirByRecording, setSessionDirByRecording] = useState9(
|
|
2779
|
+
() => /* @__PURE__ */ new Map()
|
|
2780
|
+
);
|
|
2781
|
+
const rememberSessionDir = useCallback2((recordingId, dir) => {
|
|
2782
|
+
if (!dir) return;
|
|
2783
|
+
setSessionDirByRecording((m) => m.get(recordingId) === dir ? m : new Map(m).set(recordingId, dir));
|
|
2784
|
+
}, []);
|
|
2785
|
+
const syncedTextRef = useRef4(/* @__PURE__ */ new Set());
|
|
2786
|
+
const syncRecordingText2 = useCallback2(
|
|
2498
2787
|
async (recordingId, opts = {}) => {
|
|
2499
2788
|
if (!onSyncRecordingText) return;
|
|
2500
2789
|
if (opts.manual) setNotice("Syncing text locally\u2026");
|
|
2501
2790
|
try {
|
|
2502
2791
|
const data = await onSyncRecordingText(recordingId);
|
|
2503
2792
|
syncedTextRef.current.add(recordingId);
|
|
2793
|
+
rememberSessionDir(recordingId, data.sessionDir);
|
|
2504
2794
|
if (opts.manual) setNotice(`Text synced \xB7 ${data.sessionDir}`);
|
|
2505
2795
|
} catch (error51) {
|
|
2506
2796
|
if (opts.manual) setNotice(transcribeHandoffErrorCopy(error51));
|
|
2507
2797
|
}
|
|
2508
2798
|
},
|
|
2509
|
-
[onSyncRecordingText]
|
|
2799
|
+
[onSyncRecordingText, rememberSessionDir]
|
|
2510
2800
|
);
|
|
2511
|
-
const syncRecordingAudio2 =
|
|
2801
|
+
const syncRecordingAudio2 = useCallback2(
|
|
2512
2802
|
async (recordingId) => {
|
|
2513
2803
|
if (!onSyncRecordingAudio) {
|
|
2514
2804
|
setNotice("Audio sync is not available in this CLI session.");
|
|
@@ -2517,14 +2807,35 @@ function AppShell({
|
|
|
2517
2807
|
setNotice("Downloading audio\u2026");
|
|
2518
2808
|
try {
|
|
2519
2809
|
const data = await onSyncRecordingAudio(recordingId);
|
|
2810
|
+
rememberSessionDir(recordingId, data.sessionDir);
|
|
2520
2811
|
setNotice(`Audio saved \xB7 ${data.audioPath}`);
|
|
2521
2812
|
} catch (error51) {
|
|
2522
2813
|
setNotice(transcribeHandoffErrorCopy(error51));
|
|
2523
2814
|
}
|
|
2524
2815
|
},
|
|
2525
|
-
[onSyncRecordingAudio]
|
|
2816
|
+
[onSyncRecordingAudio, rememberSessionDir]
|
|
2526
2817
|
);
|
|
2527
|
-
const
|
|
2818
|
+
const openLocalFolder = useCallback2(
|
|
2819
|
+
async (recordingId) => {
|
|
2820
|
+
const dir = sessionDirByRecording.get(recordingId);
|
|
2821
|
+
if (!dir) {
|
|
2822
|
+
setNotice("No local copy yet \u2014 it syncs when you open the recording.");
|
|
2823
|
+
return;
|
|
2824
|
+
}
|
|
2825
|
+
if (!recordingAudio) {
|
|
2826
|
+
setNotice("Opening the local folder isn't available in this CLI session.");
|
|
2827
|
+
return;
|
|
2828
|
+
}
|
|
2829
|
+
try {
|
|
2830
|
+
await recordingAudio.openPath(dir);
|
|
2831
|
+
setNotice(`Opened ${dir}`);
|
|
2832
|
+
} catch (error51) {
|
|
2833
|
+
setNotice(transcribeHandoffErrorCopy(error51));
|
|
2834
|
+
}
|
|
2835
|
+
},
|
|
2836
|
+
[sessionDirByRecording, recordingAudio]
|
|
2837
|
+
);
|
|
2838
|
+
const exportRecordingForAgent = useCallback2(
|
|
2528
2839
|
async (recordingId) => {
|
|
2529
2840
|
if (!onExportRecording) {
|
|
2530
2841
|
setNotice("Export is not available in this CLI session.");
|
|
@@ -2552,7 +2863,7 @@ function AppShell({
|
|
|
2552
2863
|
autoTranscribeStartedSessionIds.current.add(artifact.sessionId);
|
|
2553
2864
|
void transcribeStoppedRecording();
|
|
2554
2865
|
}, [liveRecord, transcribeRecordingArtifact, transcribeStoppedRecording]);
|
|
2555
|
-
const loadMoreRecordings =
|
|
2866
|
+
const loadMoreRecordings = useCallback2(async () => {
|
|
2556
2867
|
if (!fetchRecordings || !recordingsNextCursor || loadingMoreRecordings) return;
|
|
2557
2868
|
setLoadingMoreRecordings(true);
|
|
2558
2869
|
try {
|
|
@@ -2582,7 +2893,7 @@ function AppShell({
|
|
|
2582
2893
|
const id = setInterval(() => void refresh(), pollMs);
|
|
2583
2894
|
return () => clearInterval(id);
|
|
2584
2895
|
}, [refresh, pollMs]);
|
|
2585
|
-
const transcriptCacheRef =
|
|
2896
|
+
const transcriptCacheRef = useRef4(transcriptCache);
|
|
2586
2897
|
transcriptCacheRef.current = transcriptCache;
|
|
2587
2898
|
useEffect4(() => {
|
|
2588
2899
|
const id = setInterval(() => {
|
|
@@ -2652,7 +2963,7 @@ function AppShell({
|
|
|
2652
2963
|
selected,
|
|
2653
2964
|
visibleRecordingRows
|
|
2654
2965
|
]);
|
|
2655
|
-
const openTranscript =
|
|
2966
|
+
const openTranscript = useCallback2(
|
|
2656
2967
|
async (transcriptId) => {
|
|
2657
2968
|
setStack((st) => [...st, { kind: "transcript", loading: true }]);
|
|
2658
2969
|
try {
|
|
@@ -2691,7 +3002,7 @@ function AppShell({
|
|
|
2691
3002
|
void syncRecordingText2(detailRecordingId);
|
|
2692
3003
|
}, [detailRecordingId, syncRecordingText2]);
|
|
2693
3004
|
const setAudio = (recordingId, action) => setAudioCache((m) => new Map(m).set(recordingId, action));
|
|
2694
|
-
const runAudio =
|
|
3005
|
+
const runAudio = useCallback2(
|
|
2695
3006
|
async (recordingId, mode) => {
|
|
2696
3007
|
if (!recordingAudio) {
|
|
2697
3008
|
setNotice("Audio actions are not available");
|
|
@@ -2729,8 +3040,9 @@ function AppShell({
|
|
|
2729
3040
|
setStack((st) => st[st.length - 1]?.kind === "record" ? st : [...st, { kind: "record" }]);
|
|
2730
3041
|
setNotice(void 0);
|
|
2731
3042
|
};
|
|
2732
|
-
|
|
3043
|
+
useInput9((input, key) => {
|
|
2733
3044
|
setNotice(void 0);
|
|
3045
|
+
if (screen.kind === "ask") return;
|
|
2734
3046
|
if (screen.kind === "recordSetup") {
|
|
2735
3047
|
if (input === "q" || key.leftArrow) back();
|
|
2736
3048
|
return;
|
|
@@ -2839,6 +3151,8 @@ function AppShell({
|
|
|
2839
3151
|
const links = rec ? resolveRecordingLinks(rec.recordingId, rec.origin) : {};
|
|
2840
3152
|
if (input === "T" && rec) void retranscribeExistingRecording(rec.recordingId);
|
|
2841
3153
|
else if ((input === "s" || input === "S") && rec) void resummarizeExistingRecording(rec.recordingId);
|
|
3154
|
+
else if (input === "a" && rec && askRecording)
|
|
3155
|
+
setStack((st) => [...st, { kind: "ask", recordingId: rec.recordingId }]);
|
|
2842
3156
|
else if (input === "e" && rec) void exportRecordingForAgent(rec.recordingId);
|
|
2843
3157
|
else if (input === "t" && rec?.activeTranscriptId) void openTranscript(rec.activeTranscriptId);
|
|
2844
3158
|
else if (input === "o" && rec) void runAudio(rec.recordingId, "open");
|
|
@@ -2846,6 +3160,7 @@ function AppShell({
|
|
|
2846
3160
|
if (onSyncRecordingAudio) void syncRecordingAudio2(rec.recordingId);
|
|
2847
3161
|
else void runAudio(rec.recordingId, "download");
|
|
2848
3162
|
} else if (input === "f" && rec) void runAudio(rec.recordingId, "finder");
|
|
3163
|
+
else if (input === "l" && rec) void openLocalFolder(rec.recordingId);
|
|
2849
3164
|
else if (input === "w" && links.webUrl) openUrl2?.(links.webUrl);
|
|
2850
3165
|
else if (input === "c" && links.webUrl) {
|
|
2851
3166
|
copyText2?.(links.webUrl);
|
|
@@ -2854,30 +3169,46 @@ function AppShell({
|
|
|
2854
3169
|
return;
|
|
2855
3170
|
}
|
|
2856
3171
|
});
|
|
3172
|
+
if (screen.kind === "ask" && askRecording) {
|
|
3173
|
+
const rec = recordings.find((r) => r.recordingId === screen.recordingId);
|
|
3174
|
+
const activeTranscriptId = rec?.activeTranscriptId;
|
|
3175
|
+
return /* @__PURE__ */ jsx20(
|
|
3176
|
+
AskScreen,
|
|
3177
|
+
{
|
|
3178
|
+
recordingId: screen.recordingId,
|
|
3179
|
+
title: rec ? recordingTitle2(rec) : void 0,
|
|
3180
|
+
askRecording,
|
|
3181
|
+
spinnerFrame,
|
|
3182
|
+
onBack: back,
|
|
3183
|
+
onOpenTranscript: activeTranscriptId ? () => void openTranscript(activeTranscriptId) : void 0
|
|
3184
|
+
}
|
|
3185
|
+
);
|
|
3186
|
+
}
|
|
2857
3187
|
if (screen.kind === "transcript") {
|
|
2858
|
-
return /* @__PURE__ */
|
|
3188
|
+
return /* @__PURE__ */ jsx20(TranscriptView, { loading: screen.loading, data: screen.data, error: screen.error });
|
|
2859
3189
|
}
|
|
2860
3190
|
if (screen.kind === "jobDetail") {
|
|
2861
3191
|
const job = jobs.find((j) => j.jobId === screen.jobId);
|
|
2862
|
-
if (!job) return !loaded ? /* @__PURE__ */
|
|
2863
|
-
return /* @__PURE__ */
|
|
3192
|
+
if (!job) return !loaded ? /* @__PURE__ */ jsx20(Loading, { label: "job" }) : /* @__PURE__ */ jsx20(Missing, { label: "Job" });
|
|
3193
|
+
return /* @__PURE__ */ jsx20(Detail, { notice, children: /* @__PURE__ */ jsx20(JobDetailView, { item: job, origin, spinnerFrame, nowMs: now() }) });
|
|
2864
3194
|
}
|
|
2865
3195
|
if (screen.kind === "recordingDetail") {
|
|
2866
3196
|
const rec = recordings.find((r) => r.recordingId === screen.recordingId);
|
|
2867
|
-
if (!rec) return !loaded ? /* @__PURE__ */
|
|
3197
|
+
if (!rec) return !loaded ? /* @__PURE__ */ jsx20(Loading, { label: "recording" }) : /* @__PURE__ */ jsx20(Missing, { label: "Recording" });
|
|
2868
3198
|
const detailTranscript = rec.activeTranscriptId ? transcriptCache.get(rec.activeTranscriptId) : void 0;
|
|
2869
|
-
return /* @__PURE__ */
|
|
3199
|
+
return /* @__PURE__ */ jsx20(Detail, { notice, children: /* @__PURE__ */ jsx20(
|
|
2870
3200
|
RecordingDetailView,
|
|
2871
3201
|
{
|
|
2872
3202
|
item: rec,
|
|
2873
3203
|
nowMs: now(),
|
|
2874
3204
|
transcript: detailTranscript,
|
|
2875
|
-
audio: audioCache.get(rec.recordingId)
|
|
3205
|
+
audio: audioCache.get(rec.recordingId),
|
|
3206
|
+
localDir: sessionDirByRecording.get(rec.recordingId)
|
|
2876
3207
|
}
|
|
2877
3208
|
) });
|
|
2878
3209
|
}
|
|
2879
3210
|
if (screen.kind === "recordSetup") {
|
|
2880
|
-
return /* @__PURE__ */
|
|
3211
|
+
return /* @__PURE__ */ jsx20(Box18, { flexDirection: "column", height: size.rows, paddingX: 1, children: /* @__PURE__ */ jsx20(
|
|
2881
3212
|
RecordSetupView,
|
|
2882
3213
|
{
|
|
2883
3214
|
model: recordSetupModel,
|
|
@@ -2890,10 +3221,10 @@ function AppShell({
|
|
|
2890
3221
|
}
|
|
2891
3222
|
if (screen.kind === "record") {
|
|
2892
3223
|
if (liveRecord?.kind === "live" && liveRecord.session.mode === "live_captions") {
|
|
2893
|
-
return /* @__PURE__ */
|
|
3224
|
+
return /* @__PURE__ */ jsx20(LiveCaptionsScreen, { source: liveRecord.session.source, now });
|
|
2894
3225
|
}
|
|
2895
3226
|
if (liveRecord?.kind === "live" || liveRecord?.kind === "starting" || liveRecord?.kind === "stopping" || liveRecord?.kind === "stopped") {
|
|
2896
|
-
return /* @__PURE__ */
|
|
3227
|
+
return /* @__PURE__ */ jsx20(Detail, { notice, children: /* @__PURE__ */ jsx20(
|
|
2897
3228
|
RecordFrame,
|
|
2898
3229
|
{
|
|
2899
3230
|
telemetry: liveRecord.telemetry,
|
|
@@ -2914,18 +3245,18 @@ function AppShell({
|
|
|
2914
3245
|
}
|
|
2915
3246
|
) });
|
|
2916
3247
|
}
|
|
2917
|
-
return /* @__PURE__ */
|
|
2918
|
-
/* @__PURE__ */
|
|
3248
|
+
return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
|
|
3249
|
+
/* @__PURE__ */ jsx20(Box18, { flexGrow: 1, flexDirection: "column", paddingX: 1, paddingTop: 1, children: liveRecord?.kind === "error" ? (() => {
|
|
2919
3250
|
if (liveRecord.code === "record.permission_required") {
|
|
2920
|
-
return /* @__PURE__ */
|
|
3251
|
+
return /* @__PURE__ */ jsx20(PermissionPreflightView, { items: permissionItemsFromRecordError(liveRecord.data) });
|
|
2921
3252
|
}
|
|
2922
3253
|
const copy = recordErrorCopy(liveRecord.code, liveRecord.message);
|
|
2923
|
-
return /* @__PURE__ */
|
|
2924
|
-
/* @__PURE__ */
|
|
2925
|
-
copy.detail ? /* @__PURE__ */
|
|
3254
|
+
return /* @__PURE__ */ jsxs17(Fragment6, { children: [
|
|
3255
|
+
/* @__PURE__ */ jsx20(Text18, { color: copy.tone, children: copy.title }),
|
|
3256
|
+
copy.detail ? /* @__PURE__ */ jsx20(Text18, { dimColor: true, children: copy.detail }) : null
|
|
2926
3257
|
] });
|
|
2927
|
-
})() : /* @__PURE__ */
|
|
2928
|
-
/* @__PURE__ */
|
|
3258
|
+
})() : /* @__PURE__ */ jsx20(Text18, { dimColor: true, children: "Starting recording\u2026" }) }),
|
|
3259
|
+
/* @__PURE__ */ jsx20(Footer, { keys: "r retry \xB7 o settings \xB7 q / esc / \u2190 back" })
|
|
2929
3260
|
] });
|
|
2930
3261
|
}
|
|
2931
3262
|
const tab = screen.kind === "jobs" ? "jobs" : screen.kind === "account" ? "account" : "overview";
|
|
@@ -2934,7 +3265,7 @@ function AppShell({
|
|
|
2934
3265
|
if (!loaded) {
|
|
2935
3266
|
position = "";
|
|
2936
3267
|
const spin = "\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F"[spinnerFrame % 10];
|
|
2937
|
-
body = /* @__PURE__ */
|
|
3268
|
+
body = /* @__PURE__ */ jsx20(Box18, { marginTop: 1, children: /* @__PURE__ */ jsxs17(Text18, { color: "cyan", children: [
|
|
2938
3269
|
spin,
|
|
2939
3270
|
" Loading\u2026"
|
|
2940
3271
|
] }) });
|
|
@@ -2950,7 +3281,7 @@ function AppShell({
|
|
|
2950
3281
|
const showPeek = size.columns >= 100;
|
|
2951
3282
|
const peekWidth = showPeek ? 34 : 0;
|
|
2952
3283
|
const listColumns = showPeek ? Math.max(30, size.columns - peekWidth - 3) : size.columns;
|
|
2953
|
-
body = /* @__PURE__ */
|
|
3284
|
+
body = /* @__PURE__ */ jsx20(
|
|
2954
3285
|
OverviewView,
|
|
2955
3286
|
{
|
|
2956
3287
|
recordings: recordings.slice(win.start, win.end),
|
|
@@ -2971,11 +3302,11 @@ function AppShell({
|
|
|
2971
3302
|
);
|
|
2972
3303
|
} else if (screen.kind === "account") {
|
|
2973
3304
|
position = "";
|
|
2974
|
-
body = /* @__PURE__ */
|
|
3305
|
+
body = /* @__PURE__ */ jsx20(AccountView, { status: accountStatus, nowMs: now() });
|
|
2975
3306
|
} else {
|
|
2976
3307
|
const win = listWindow(selected, jobs.length, Math.max(3, size.rows - 4));
|
|
2977
3308
|
position = jobs.length ? `${selected + 1} / ${jobs.length}` : "0";
|
|
2978
|
-
body = /* @__PURE__ */
|
|
3309
|
+
body = /* @__PURE__ */ jsx20(
|
|
2979
3310
|
JobsView,
|
|
2980
3311
|
{
|
|
2981
3312
|
items: jobs.slice(win.start, win.end),
|
|
@@ -2986,38 +3317,38 @@ function AppShell({
|
|
|
2986
3317
|
);
|
|
2987
3318
|
}
|
|
2988
3319
|
const footerKeys = screen.kind === "jobs" ? `${position} \xB7 \u2191\u2193 select \xB7 \u23CE job \xB7 t transcript \xB7 n record \xB7 1 overview \xB7 3 account \xB7 r refresh \xB7 q quit` : screen.kind === "account" ? "Account \xB7 n record \xB7 1 overview \xB7 2 jobs \xB7 r refresh \xB7 q quit" : `${position} \xB7 \u2191\u2193 scroll \xB7 \u23CE open \xB7 t transcript \xB7 n record \xB7 2 jobs \xB7 3 account \xB7 r refresh \xB7 q quit`;
|
|
2989
|
-
return /* @__PURE__ */
|
|
2990
|
-
/* @__PURE__ */
|
|
2991
|
-
/* @__PURE__ */
|
|
3320
|
+
return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
|
|
3321
|
+
/* @__PURE__ */ jsx20(Header, { active: tab }),
|
|
3322
|
+
/* @__PURE__ */ jsxs17(Box18, { flexGrow: 1, flexDirection: "column", children: [
|
|
2992
3323
|
body,
|
|
2993
|
-
loadError && jobs.length === 0 && recordings.length === 0 ? /* @__PURE__ */
|
|
3324
|
+
loadError && jobs.length === 0 && recordings.length === 0 ? /* @__PURE__ */ jsx20(Box18, { marginTop: 1, children: /* @__PURE__ */ jsxs17(Text18, { color: "red", children: [
|
|
2994
3325
|
"! ",
|
|
2995
3326
|
loadError
|
|
2996
3327
|
] }) }) : null
|
|
2997
3328
|
] }),
|
|
2998
|
-
/* @__PURE__ */
|
|
3329
|
+
/* @__PURE__ */ jsx20(Footer, { keys: footerKeys })
|
|
2999
3330
|
] });
|
|
3000
3331
|
}
|
|
3001
3332
|
function Detail({
|
|
3002
3333
|
notice,
|
|
3003
3334
|
children
|
|
3004
3335
|
}) {
|
|
3005
|
-
return /* @__PURE__ */
|
|
3336
|
+
return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", children: [
|
|
3006
3337
|
children,
|
|
3007
|
-
notice ? /* @__PURE__ */
|
|
3338
|
+
notice ? /* @__PURE__ */ jsx20(Box18, { paddingX: 1, children: /* @__PURE__ */ jsx20(Text18, { color: "green", children: notice }) }) : null
|
|
3008
3339
|
] });
|
|
3009
3340
|
}
|
|
3010
3341
|
function Missing({ label }) {
|
|
3011
|
-
return /* @__PURE__ */
|
|
3012
|
-
/* @__PURE__ */
|
|
3342
|
+
return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", paddingX: 1, children: [
|
|
3343
|
+
/* @__PURE__ */ jsxs17(Text18, { dimColor: true, children: [
|
|
3013
3344
|
label,
|
|
3014
3345
|
" no longer in the list."
|
|
3015
3346
|
] }),
|
|
3016
|
-
/* @__PURE__ */
|
|
3347
|
+
/* @__PURE__ */ jsx20(Text18, { dimColor: true, children: "esc back \xB7 q quit" })
|
|
3017
3348
|
] });
|
|
3018
3349
|
}
|
|
3019
3350
|
function Loading({ label }) {
|
|
3020
|
-
return /* @__PURE__ */
|
|
3351
|
+
return /* @__PURE__ */ jsx20(Box18, { paddingX: 1, children: /* @__PURE__ */ jsxs17(Text18, { color: "cyan", children: [
|
|
3021
3352
|
"Loading ",
|
|
3022
3353
|
label,
|
|
3023
3354
|
"\u2026"
|
|
@@ -3033,6 +3364,7 @@ var init_AppShell = __esm({
|
|
|
3033
3364
|
init_OverviewView();
|
|
3034
3365
|
init_JobDetailView();
|
|
3035
3366
|
init_RecordingDetailView();
|
|
3367
|
+
init_AskScreen();
|
|
3036
3368
|
init_TranscriptView();
|
|
3037
3369
|
init_LiveCaptionsScreen();
|
|
3038
3370
|
init_PermissionPreflightView();
|
|
@@ -3059,7 +3391,7 @@ __export(tui_exports, {
|
|
|
3059
3391
|
runDashboard: () => runDashboard,
|
|
3060
3392
|
useTerminalSize: () => useTerminalSize
|
|
3061
3393
|
});
|
|
3062
|
-
import
|
|
3394
|
+
import React12 from "react";
|
|
3063
3395
|
import { render as render2 } from "ink";
|
|
3064
3396
|
import { spawn as spawn3 } from "child_process";
|
|
3065
3397
|
function openUrl(url2) {
|
|
@@ -3080,7 +3412,7 @@ function copyText(text) {
|
|
|
3080
3412
|
async function runDashboard(deps) {
|
|
3081
3413
|
const renderApp = deps.renderApp ?? render2;
|
|
3082
3414
|
const app = renderApp(
|
|
3083
|
-
|
|
3415
|
+
React12.createElement(AppShell, {
|
|
3084
3416
|
fetchJobs: deps.fetchJobs,
|
|
3085
3417
|
fetchTranscript: deps.fetchTranscript,
|
|
3086
3418
|
fetchRecordings: deps.fetchRecordings,
|
|
@@ -3098,6 +3430,9 @@ async function runDashboard(deps) {
|
|
|
3098
3430
|
onSyncRecordingText: deps.syncRecordingText,
|
|
3099
3431
|
onSyncRecordingAudio: deps.syncRecordingAudio,
|
|
3100
3432
|
onExportRecording: deps.exportRecording,
|
|
3433
|
+
fetchAskThread: deps.fetchAskThread,
|
|
3434
|
+
fetchAskSuggestions: deps.fetchAskSuggestions,
|
|
3435
|
+
askRecording: deps.askRecording,
|
|
3101
3436
|
initialView: deps.initialView ?? "overview",
|
|
3102
3437
|
openUrl,
|
|
3103
3438
|
copyText
|
|
@@ -19540,6 +19875,62 @@ var RecappiApiClient = class {
|
|
|
19540
19875
|
summaryStatus: parsed.summaryStatus
|
|
19541
19876
|
});
|
|
19542
19877
|
}
|
|
19878
|
+
async fetchAskThread(recordingId) {
|
|
19879
|
+
const parsed = await this.getJson(
|
|
19880
|
+
`/api/recordings/${encodeURIComponent(recordingId)}/ask-thread`
|
|
19881
|
+
);
|
|
19882
|
+
const thread = isRecord2(parsed.thread) ? {
|
|
19883
|
+
id: stringValue2(parsed.thread.id) ?? "",
|
|
19884
|
+
recordingId: stringValue2(parsed.thread.recordingId) ?? recordingId,
|
|
19885
|
+
...numberValue2(parsed.thread.createdAt) !== void 0 ? { createdAt: numberValue2(parsed.thread.createdAt) } : {},
|
|
19886
|
+
...numberValue2(parsed.thread.updatedAt) !== void 0 ? { updatedAt: numberValue2(parsed.thread.updatedAt) } : {}
|
|
19887
|
+
} : null;
|
|
19888
|
+
return {
|
|
19889
|
+
thread: thread && thread.id ? thread : null,
|
|
19890
|
+
messages: Array.isArray(parsed.messages) ? parsed.messages.filter(isRecord2).map(mapAskThreadMessage) : [],
|
|
19891
|
+
origin: this.auth.origin
|
|
19892
|
+
};
|
|
19893
|
+
}
|
|
19894
|
+
async fetchAskSuggestions(recordingId, opts = {}) {
|
|
19895
|
+
const params = new URLSearchParams();
|
|
19896
|
+
if (opts.language) params.set("language", opts.language);
|
|
19897
|
+
const suffix = params.size > 0 ? `?${params}` : "";
|
|
19898
|
+
const parsed = await this.getJson(
|
|
19899
|
+
`/api/recordings/${encodeURIComponent(recordingId)}/ask-suggestions${suffix}`
|
|
19900
|
+
);
|
|
19901
|
+
return {
|
|
19902
|
+
suggestions: Array.isArray(parsed.suggestions) ? parsed.suggestions.filter(isRecord2).flatMap(mapAskSuggestion) : [],
|
|
19903
|
+
...typeof parsed.transcriptId === "string" ? { transcriptId: parsed.transcriptId } : {},
|
|
19904
|
+
...typeof parsed.model === "string" ? { model: parsed.model } : {},
|
|
19905
|
+
...typeof parsed.language === "string" ? { language: parsed.language } : {},
|
|
19906
|
+
...typeof parsed.cached === "boolean" ? { cached: parsed.cached } : {},
|
|
19907
|
+
origin: this.auth.origin
|
|
19908
|
+
};
|
|
19909
|
+
}
|
|
19910
|
+
async *askRecordingStream(opts) {
|
|
19911
|
+
const response = await this.request(
|
|
19912
|
+
"POST",
|
|
19913
|
+
`/api/recordings/${encodeURIComponent(opts.recordingId)}/ask-thread/messages`,
|
|
19914
|
+
JSON.stringify({
|
|
19915
|
+
question: opts.question,
|
|
19916
|
+
webSearch: opts.webSearch === true,
|
|
19917
|
+
...opts.model ? { model: opts.model } : {}
|
|
19918
|
+
}),
|
|
19919
|
+
{
|
|
19920
|
+
headers: {
|
|
19921
|
+
accept: "text/event-stream",
|
|
19922
|
+
"content-type": "application/json"
|
|
19923
|
+
}
|
|
19924
|
+
}
|
|
19925
|
+
);
|
|
19926
|
+
if (!response.body) {
|
|
19927
|
+
throw cliError("cloud.invalid_response", "Ask stream response was empty.");
|
|
19928
|
+
}
|
|
19929
|
+
for await (const frame of parseSseFrames(response.body)) {
|
|
19930
|
+
const event = decodeAskStreamEvent(frame.event, frame.data);
|
|
19931
|
+
if (event) yield event;
|
|
19932
|
+
}
|
|
19933
|
+
}
|
|
19543
19934
|
async downloadRecordingAudio(recordingId, opts = {}) {
|
|
19544
19935
|
const response = await this.request(
|
|
19545
19936
|
"GET",
|
|
@@ -20194,6 +20585,139 @@ function stringValue2(value) {
|
|
|
20194
20585
|
function numberValue2(value) {
|
|
20195
20586
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
20196
20587
|
}
|
|
20588
|
+
function mapAskThreadMessage(row) {
|
|
20589
|
+
const id = stringValue2(row.id) ?? "";
|
|
20590
|
+
const role = row.role === "user" ? "user" : "assistant";
|
|
20591
|
+
return {
|
|
20592
|
+
id,
|
|
20593
|
+
role,
|
|
20594
|
+
...typeof row.status === "string" ? { status: row.status } : {},
|
|
20595
|
+
...numberValue2(row.sequence) !== void 0 ? { sequence: numberValue2(row.sequence) } : {},
|
|
20596
|
+
content: stringValue2(row.content) ?? "",
|
|
20597
|
+
...typeof row.model === "string" || row.model === null ? { model: row.model } : {},
|
|
20598
|
+
...typeof row.webSearch === "boolean" ? { webSearch: row.webSearch } : {},
|
|
20599
|
+
...typeof row.errorMessage === "string" || row.errorMessage === null ? { errorMessage: row.errorMessage } : {},
|
|
20600
|
+
...numberValue2(row.createdAt) !== void 0 ? { createdAt: numberValue2(row.createdAt) } : {},
|
|
20601
|
+
...numberValue2(row.updatedAt) !== void 0 ? { updatedAt: numberValue2(row.updatedAt) } : {},
|
|
20602
|
+
citations: Array.isArray(row.citations) ? row.citations.filter(isRecord2).map(mapAskCitation) : []
|
|
20603
|
+
};
|
|
20604
|
+
}
|
|
20605
|
+
function mapAskSuggestion(row) {
|
|
20606
|
+
const question = stringValue2(row.question)?.trim();
|
|
20607
|
+
if (!question) return [];
|
|
20608
|
+
return [
|
|
20609
|
+
{
|
|
20610
|
+
...typeof row.id === "string" ? { id: row.id } : {},
|
|
20611
|
+
question,
|
|
20612
|
+
...typeof row.reason === "string" && row.reason.trim() ? { reason: row.reason } : {}
|
|
20613
|
+
}
|
|
20614
|
+
];
|
|
20615
|
+
}
|
|
20616
|
+
function mapAskCitation(row) {
|
|
20617
|
+
return {
|
|
20618
|
+
...typeof row.id === "string" ? { id: row.id } : {},
|
|
20619
|
+
...typeof row.segmentId === "string" ? { segmentId: row.segmentId } : {},
|
|
20620
|
+
...numberValue2(row.index) !== void 0 ? { index: numberValue2(row.index) } : {},
|
|
20621
|
+
...typeof row.startMs === "number" || row.startMs === null ? { startMs: row.startMs } : {},
|
|
20622
|
+
...typeof row.endMs === "number" || row.endMs === null ? { endMs: row.endMs } : {},
|
|
20623
|
+
...typeof row.label === "string" ? { label: row.label } : {},
|
|
20624
|
+
...typeof row.speaker === "string" ? { speaker: row.speaker } : {},
|
|
20625
|
+
...typeof row.snippet === "string" ? { snippet: row.snippet } : {},
|
|
20626
|
+
...numberValue2(row.order) !== void 0 ? { order: numberValue2(row.order) } : {}
|
|
20627
|
+
};
|
|
20628
|
+
}
|
|
20629
|
+
async function* parseSseFrames(stream) {
|
|
20630
|
+
const reader = stream.getReader();
|
|
20631
|
+
const decoder = new TextDecoder();
|
|
20632
|
+
let buffer = "";
|
|
20633
|
+
try {
|
|
20634
|
+
for (; ; ) {
|
|
20635
|
+
const { value, done } = await reader.read();
|
|
20636
|
+
if (done) break;
|
|
20637
|
+
buffer += decoder.decode(value, { stream: true });
|
|
20638
|
+
let delimiter = findSseDelimiter(buffer);
|
|
20639
|
+
while (delimiter) {
|
|
20640
|
+
const frameText = buffer.slice(0, delimiter.index);
|
|
20641
|
+
buffer = buffer.slice(delimiter.index + delimiter.length);
|
|
20642
|
+
const frame = parseSseFrame(frameText);
|
|
20643
|
+
if (frame) yield frame;
|
|
20644
|
+
delimiter = findSseDelimiter(buffer);
|
|
20645
|
+
}
|
|
20646
|
+
}
|
|
20647
|
+
buffer += decoder.decode();
|
|
20648
|
+
if (buffer.trim()) {
|
|
20649
|
+
const frame = parseSseFrame(buffer);
|
|
20650
|
+
if (frame) yield frame;
|
|
20651
|
+
}
|
|
20652
|
+
} finally {
|
|
20653
|
+
reader.releaseLock();
|
|
20654
|
+
}
|
|
20655
|
+
}
|
|
20656
|
+
function findSseDelimiter(buffer) {
|
|
20657
|
+
const lf = buffer.indexOf("\n\n");
|
|
20658
|
+
const crlf = buffer.indexOf("\r\n\r\n");
|
|
20659
|
+
if (lf === -1 && crlf === -1) return void 0;
|
|
20660
|
+
if (lf === -1) return { index: crlf, length: 4 };
|
|
20661
|
+
if (crlf === -1) return { index: lf, length: 2 };
|
|
20662
|
+
return crlf < lf ? { index: crlf, length: 4 } : { index: lf, length: 2 };
|
|
20663
|
+
}
|
|
20664
|
+
function parseSseFrame(frameText) {
|
|
20665
|
+
let event = "message";
|
|
20666
|
+
const data = [];
|
|
20667
|
+
for (const rawLine of frameText.split(/\r?\n/)) {
|
|
20668
|
+
if (!rawLine || rawLine.startsWith(":")) continue;
|
|
20669
|
+
if (rawLine.startsWith("event:")) {
|
|
20670
|
+
event = rawLine.slice("event:".length).trim() || "message";
|
|
20671
|
+
} else if (rawLine.startsWith("data:")) {
|
|
20672
|
+
const value = rawLine.slice("data:".length);
|
|
20673
|
+
data.push(value.startsWith(" ") ? value.slice(1) : value);
|
|
20674
|
+
}
|
|
20675
|
+
}
|
|
20676
|
+
if (data.length === 0) return void 0;
|
|
20677
|
+
return { event, data: data.join("\n") };
|
|
20678
|
+
}
|
|
20679
|
+
function decodeAskStreamEvent(eventName, data) {
|
|
20680
|
+
const parsed = JSON.parse(data);
|
|
20681
|
+
if (!isRecord2(parsed)) return void 0;
|
|
20682
|
+
const type = stringValue2(parsed.type) ?? eventName;
|
|
20683
|
+
switch (eventName === "message" ? type : eventName) {
|
|
20684
|
+
case "metadata":
|
|
20685
|
+
return {
|
|
20686
|
+
type: "metadata",
|
|
20687
|
+
...typeof parsed.recordingId === "string" ? { recordingId: parsed.recordingId } : {},
|
|
20688
|
+
...typeof parsed.transcriptId === "string" ? { transcriptId: parsed.transcriptId } : {},
|
|
20689
|
+
...typeof parsed.threadId === "string" ? { threadId: parsed.threadId } : {},
|
|
20690
|
+
...typeof parsed.userMessageId === "string" ? { userMessageId: parsed.userMessageId } : {},
|
|
20691
|
+
...typeof parsed.assistantMessageId === "string" ? { assistantMessageId: parsed.assistantMessageId } : {},
|
|
20692
|
+
...typeof parsed.model === "string" ? { model: parsed.model } : {},
|
|
20693
|
+
...typeof parsed.webSearch === "boolean" ? { webSearch: parsed.webSearch } : {},
|
|
20694
|
+
...numberValue2(parsed.segmentCount) !== void 0 ? { segmentCount: numberValue2(parsed.segmentCount) } : {},
|
|
20695
|
+
...typeof parsed.citationMarker === "string" ? { citationMarker: parsed.citationMarker } : {}
|
|
20696
|
+
};
|
|
20697
|
+
case "answer_delta":
|
|
20698
|
+
return { type: "answer_delta", delta: stringValue2(parsed.delta) ?? "" };
|
|
20699
|
+
case "citation": {
|
|
20700
|
+
const citation = isRecord2(parsed.citation) ? parsed.citation : parsed;
|
|
20701
|
+
return { type: "citation", citation: mapAskCitation(citation) };
|
|
20702
|
+
}
|
|
20703
|
+
case "done":
|
|
20704
|
+
return {
|
|
20705
|
+
type: "done",
|
|
20706
|
+
...typeof parsed.threadId === "string" ? { threadId: parsed.threadId } : {},
|
|
20707
|
+
...typeof parsed.userMessageId === "string" ? { userMessageId: parsed.userMessageId } : {},
|
|
20708
|
+
...typeof parsed.assistantMessageId === "string" ? { assistantMessageId: parsed.assistantMessageId } : {},
|
|
20709
|
+
...typeof parsed.content === "string" ? { content: parsed.content } : {},
|
|
20710
|
+
citations: Array.isArray(parsed.citations) ? parsed.citations.filter(isRecord2).map(mapAskCitation) : []
|
|
20711
|
+
};
|
|
20712
|
+
case "error":
|
|
20713
|
+
throw cliError(
|
|
20714
|
+
"cloud.http_error",
|
|
20715
|
+
stringValue2(parsed.message) ?? stringValue2(parsed.error) ?? "The assistant ran into an error."
|
|
20716
|
+
);
|
|
20717
|
+
default:
|
|
20718
|
+
return void 0;
|
|
20719
|
+
}
|
|
20720
|
+
}
|
|
20197
20721
|
function recordingCloudUrl(origin, recordingId) {
|
|
20198
20722
|
return `${origin.replace(/\/+$/, "")}/recordings/${encodeURIComponent(recordingId)}`;
|
|
20199
20723
|
}
|
|
@@ -20469,6 +20993,7 @@ var COMMON_TASKS = [
|
|
|
20469
20993
|
{ label: "Export recording bundle", command: "recappi recordings export <recordingId> --dir ./bundle" },
|
|
20470
20994
|
{ label: "List / find recordings", command: "recappi recordings list" },
|
|
20471
20995
|
{ label: "Read a transcript", command: "recappi transcript get <transcriptId>" },
|
|
20996
|
+
{ label: "Ask about a recording", command: 'recappi ask <recordingId> "<question>"' },
|
|
20472
20997
|
{ label: "Download / open audio", command: "recappi audio <recordingId> --open" },
|
|
20473
20998
|
{ label: "Check a transcription job", command: "recappi jobs wait <jobId>" },
|
|
20474
20999
|
{ label: "Account \xB7 quota \xB7 usage", command: "recappi account status" },
|
|
@@ -20647,6 +21172,23 @@ var COMMAND_METADATA = {
|
|
|
20647
21172
|
{ description: "Wait for a transcription job", command: "recappi jobs wait <jobId>" }
|
|
20648
21173
|
],
|
|
20649
21174
|
relatedCommands: ["jobs list", "transcript get"]
|
|
21175
|
+
},
|
|
21176
|
+
ask: {
|
|
21177
|
+
capabilities: [
|
|
21178
|
+
"Ask a question about a recording and stream the answer",
|
|
21179
|
+
"Answers cite the transcript with inline \u27E8mm:ss\u27E9 references"
|
|
21180
|
+
],
|
|
21181
|
+
examples: [
|
|
21182
|
+
{
|
|
21183
|
+
description: "Ask about a recording",
|
|
21184
|
+
command: 'recappi ask <recordingId> "What were the decisions?"'
|
|
21185
|
+
},
|
|
21186
|
+
{
|
|
21187
|
+
description: "Get the raw answer + citations for an agent",
|
|
21188
|
+
command: 'recappi ask <recordingId> "Summarize the risks" --json'
|
|
21189
|
+
}
|
|
21190
|
+
],
|
|
21191
|
+
relatedCommands: ["recordings get", "transcript get", "recordings list"]
|
|
20650
21192
|
}
|
|
20651
21193
|
};
|
|
20652
21194
|
function commonTasksHelpText() {
|
|
@@ -21281,6 +21823,9 @@ function formatTimestamp(ms) {
|
|
|
21281
21823
|
return hours > 0 ? `${hours}:${mm}:${ss}` : `${mm}:${ss}`;
|
|
21282
21824
|
}
|
|
21283
21825
|
|
|
21826
|
+
// src/cli.ts
|
|
21827
|
+
init_askInline();
|
|
21828
|
+
|
|
21284
21829
|
// src/progressStepper.ts
|
|
21285
21830
|
var STEP_DEFS = [
|
|
21286
21831
|
{ key: "check", label: "Check" },
|
|
@@ -23830,6 +24375,9 @@ async function runCli(deps = {}) {
|
|
|
23830
24375
|
env: deps.env,
|
|
23831
24376
|
homeDir: deps.homeDir
|
|
23832
24377
|
}),
|
|
24378
|
+
fetchAskThread: (recordingId) => client.fetchAskThread(recordingId),
|
|
24379
|
+
fetchAskSuggestions: (recordingId, options) => client.fetchAskSuggestions(recordingId, options),
|
|
24380
|
+
askRecording: (options) => client.askRecordingStream(options),
|
|
23833
24381
|
initialView: parsed.initialView
|
|
23834
24382
|
});
|
|
23835
24383
|
return 0;
|
|
@@ -24070,6 +24618,35 @@ async function runCli(deps = {}) {
|
|
|
24070
24618
|
renderSuccess("recordings resummarize", data, render3);
|
|
24071
24619
|
return 0;
|
|
24072
24620
|
}
|
|
24621
|
+
if (parsed.kind === "ask") {
|
|
24622
|
+
if (render3.mode === "human") render3.stderr("Asking\u2026\n");
|
|
24623
|
+
let content = "";
|
|
24624
|
+
let citations = [];
|
|
24625
|
+
for await (const event of client.askRecordingStream({
|
|
24626
|
+
recordingId: parsed.recordingId,
|
|
24627
|
+
question: parsed.question,
|
|
24628
|
+
...parsed.webSearch ? { webSearch: true } : {},
|
|
24629
|
+
...parsed.model ? { model: parsed.model } : {}
|
|
24630
|
+
})) {
|
|
24631
|
+
if (event.type === "answer_delta") content += event.delta;
|
|
24632
|
+
else if (event.type === "citation") citations.push(event.citation);
|
|
24633
|
+
else if (event.type === "done") {
|
|
24634
|
+
if (typeof event.content === "string" && event.content) content = event.content;
|
|
24635
|
+
if (event.citations.length) citations = event.citations;
|
|
24636
|
+
}
|
|
24637
|
+
}
|
|
24638
|
+
if (render3.mode === "human") {
|
|
24639
|
+
render3.stdout(`${formatAskAnswerPlain(content, citations)}
|
|
24640
|
+
`);
|
|
24641
|
+
} else {
|
|
24642
|
+
renderSuccess(
|
|
24643
|
+
"ask",
|
|
24644
|
+
{ recordingId: parsed.recordingId, question: parsed.question, answer: content, citations },
|
|
24645
|
+
render3
|
|
24646
|
+
);
|
|
24647
|
+
}
|
|
24648
|
+
return 0;
|
|
24649
|
+
}
|
|
24073
24650
|
if (parsed.kind === "dashboard-stats") {
|
|
24074
24651
|
const data = await client.dashboardStats();
|
|
24075
24652
|
renderSuccess("dashboard stats", data, render3);
|
|
@@ -24301,6 +24878,19 @@ Agent mode:
|
|
|
24301
24878
|
...opts.force === true ? { force: true } : {}
|
|
24302
24879
|
});
|
|
24303
24880
|
});
|
|
24881
|
+
const ask = program.command("ask <recordingId> <question>").description("Ask a question about a recording; streams an answer with inline citations").option("--web-search", "allow web search for extra context").option("--model <name>", "Ask model", parseStringOption("--model")).addHelpText("after", commandMetadataHelpText("ask"));
|
|
24882
|
+
addCommonOptions(ask);
|
|
24883
|
+
ask.action((recordingId, question, opts, command) => {
|
|
24884
|
+
onSelect({
|
|
24885
|
+
kind: "ask",
|
|
24886
|
+
options: collectGlobalOptions(command),
|
|
24887
|
+
commandName: "ask",
|
|
24888
|
+
recordingId,
|
|
24889
|
+
question,
|
|
24890
|
+
...opts.webSearch === true ? { webSearch: true } : {},
|
|
24891
|
+
...typeof opts.model === "string" ? { model: opts.model } : {}
|
|
24892
|
+
});
|
|
24893
|
+
});
|
|
24304
24894
|
const record2 = program.command("record").description("Record system/mic audio via the Recappi Mini sidecar").option("--title <title>", "recording title", parseStringOption("--title")).option("--live", "show live captions while recording").option("--no-system-audio", "record microphone only").option("--no-microphone", "record system audio only").option(
|
|
24305
24895
|
"--translation-language <lang>",
|
|
24306
24896
|
"live caption translation language",
|