recappi 0.1.4 → 0.1.5
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 +680 -443
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -268,17 +268,317 @@ var init_terminal = __esm({
|
|
|
268
268
|
}
|
|
269
269
|
});
|
|
270
270
|
|
|
271
|
-
// src/tui/
|
|
271
|
+
// src/tui/liveCaptions.ts
|
|
272
|
+
function initialLiveCaptionsState() {
|
|
273
|
+
return { status: "connecting", lines: [] };
|
|
274
|
+
}
|
|
275
|
+
function liveCaptionReducer(state, event) {
|
|
276
|
+
switch (event.kind) {
|
|
277
|
+
case "status": {
|
|
278
|
+
const next = { ...state, status: event.status };
|
|
279
|
+
if (event.status === "live" && state.startedAtMs == null) {
|
|
280
|
+
next.startedAtMs = event.atMs ?? state.startedAtMs;
|
|
281
|
+
}
|
|
282
|
+
if (event.status !== "error") next.error = void 0;
|
|
283
|
+
return next;
|
|
284
|
+
}
|
|
285
|
+
case "partial":
|
|
286
|
+
return { ...state, partial: event.text };
|
|
287
|
+
case "final": {
|
|
288
|
+
const lines = [...state.lines, event.line];
|
|
289
|
+
return { ...state, lines, partial: void 0 };
|
|
290
|
+
}
|
|
291
|
+
case "translationPartial":
|
|
292
|
+
return { ...state, translationPartial: event.text };
|
|
293
|
+
case "translationFinal": {
|
|
294
|
+
const lines = [...state.lines];
|
|
295
|
+
let idx = event.segmentId ? lines.findIndex((l) => l.id === event.segmentId) : -1;
|
|
296
|
+
if (idx < 0) idx = lines.length - 1;
|
|
297
|
+
if (idx >= 0) lines[idx] = { ...lines[idx], translation: event.text };
|
|
298
|
+
return { ...state, lines, translationPartial: void 0 };
|
|
299
|
+
}
|
|
300
|
+
case "error":
|
|
301
|
+
return { ...state, status: "error", error: event.message };
|
|
302
|
+
default:
|
|
303
|
+
return state;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function recordingStateToStatus(state) {
|
|
307
|
+
switch (state) {
|
|
308
|
+
case "idle":
|
|
309
|
+
case "starting":
|
|
310
|
+
return "connecting";
|
|
311
|
+
case "recording":
|
|
312
|
+
return "live";
|
|
313
|
+
case "stopping":
|
|
314
|
+
case "finalizing":
|
|
315
|
+
case "uploading":
|
|
316
|
+
case "completed":
|
|
317
|
+
case "cancelled":
|
|
318
|
+
return "stopped";
|
|
319
|
+
case "failed":
|
|
320
|
+
return "error";
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function sidecarToLiveCaptionEvent(event) {
|
|
324
|
+
switch (event.type) {
|
|
325
|
+
case "ready":
|
|
326
|
+
return { kind: "status", status: "connecting" };
|
|
327
|
+
case "recording.state":
|
|
328
|
+
if (event.state === "failed") {
|
|
329
|
+
return { kind: "error", message: event.message ?? "Recording failed" };
|
|
330
|
+
}
|
|
331
|
+
return { kind: "status", status: recordingStateToStatus(event.state) };
|
|
332
|
+
case "live_caption.delta": {
|
|
333
|
+
if (event.stream === "translation") {
|
|
334
|
+
return event.isFinal ? { kind: "translationFinal", segmentId: event.segmentId, text: event.text } : { kind: "translationPartial", text: event.text };
|
|
335
|
+
}
|
|
336
|
+
if (event.isFinal) {
|
|
337
|
+
return {
|
|
338
|
+
kind: "final",
|
|
339
|
+
line: {
|
|
340
|
+
id: event.segmentId ?? `${event.startMs ?? event.atMs ?? 0}`,
|
|
341
|
+
text: event.text,
|
|
342
|
+
speaker: event.speaker,
|
|
343
|
+
atMs: event.startMs ?? event.atMs
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
return { kind: "partial", text: event.text };
|
|
348
|
+
}
|
|
349
|
+
case "error":
|
|
350
|
+
return { kind: "error", message: event.message };
|
|
351
|
+
case "audio.level":
|
|
352
|
+
case "local_artifact.upserted":
|
|
353
|
+
return null;
|
|
354
|
+
default:
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function liveCaptionStatusLabel(status) {
|
|
359
|
+
switch (status) {
|
|
360
|
+
case "connecting":
|
|
361
|
+
return "Connecting\u2026";
|
|
362
|
+
case "live":
|
|
363
|
+
return "\u25CF LIVE";
|
|
364
|
+
case "reconnecting":
|
|
365
|
+
return "Reconnecting\u2026";
|
|
366
|
+
case "stopped":
|
|
367
|
+
return "Stopped";
|
|
368
|
+
case "error":
|
|
369
|
+
return "Error";
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
var init_liveCaptions = __esm({
|
|
373
|
+
"src/tui/liveCaptions.ts"() {
|
|
374
|
+
"use strict";
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// src/tui/LiveCaptionsView.tsx
|
|
379
|
+
import { useMemo, useState } from "react";
|
|
380
|
+
import { Box, Text, useInput } from "ink";
|
|
381
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
382
|
+
function LiveCaptionsView({
|
|
383
|
+
state,
|
|
384
|
+
nowMs
|
|
385
|
+
}) {
|
|
386
|
+
const size = useTerminalSize();
|
|
387
|
+
const [scrollUp, setScrollUp] = useState(0);
|
|
388
|
+
const innerWidth = Math.max(10, size.columns - 2);
|
|
389
|
+
const items = useMemo(() => {
|
|
390
|
+
const rows = [];
|
|
391
|
+
for (const l of state.lines) {
|
|
392
|
+
rows.push({ key: l.id, kind: "final", speaker: l.speaker, text: l.text });
|
|
393
|
+
if (l.translation) rows.push({ key: `${l.id}__t`, kind: "translation", text: l.translation });
|
|
394
|
+
}
|
|
395
|
+
if (state.partial && state.partial.length > 0) {
|
|
396
|
+
rows.push({ key: "__partial__", kind: "partial", text: state.partial });
|
|
397
|
+
}
|
|
398
|
+
if (state.translationPartial && state.translationPartial.length > 0) {
|
|
399
|
+
rows.push({ key: "__tpartial__", kind: "translation", text: state.translationPartial });
|
|
400
|
+
}
|
|
401
|
+
return rows;
|
|
402
|
+
}, [state.lines, state.partial, state.translationPartial]);
|
|
403
|
+
const heights = useMemo(
|
|
404
|
+
() => items.map((it) => {
|
|
405
|
+
const prefix = it.kind === "translation" ? "\u21B3 " : it.speaker ? `${it.speaker}: ` : "";
|
|
406
|
+
return Math.max(1, Math.ceil(displayWidth(prefix + it.text) / innerWidth));
|
|
407
|
+
}),
|
|
408
|
+
[items, innerWidth]
|
|
409
|
+
);
|
|
410
|
+
const budget = Math.max(3, size.rows - 3);
|
|
411
|
+
const maxScroll = windowByHeights(heights, Number.MAX_SAFE_INTEGER, budget).maxScroll;
|
|
412
|
+
const top = Math.max(0, maxScroll - scrollUp);
|
|
413
|
+
const win = windowByHeights(heights, top, budget);
|
|
414
|
+
const following = scrollUp === 0;
|
|
415
|
+
const page = Math.max(1, budget - 1);
|
|
416
|
+
useInput((input, key) => {
|
|
417
|
+
if (key.upArrow || input === "k") setScrollUp((s) => Math.min(maxScroll, s + 1));
|
|
418
|
+
else if (key.downArrow || input === "j") setScrollUp((s) => Math.max(0, s - 1));
|
|
419
|
+
else if (key.pageUp || input === "b") setScrollUp((s) => Math.min(maxScroll, s + page));
|
|
420
|
+
else if (key.pageDown || input === " ") setScrollUp((s) => Math.max(0, s - page));
|
|
421
|
+
else if (input === "G") setScrollUp(0);
|
|
422
|
+
else if (input === "g") setScrollUp(maxScroll);
|
|
423
|
+
});
|
|
424
|
+
const elapsed = state.startedAtMs != null ? formatClockMs(Math.max(0, nowMs - state.startedAtMs)) : null;
|
|
425
|
+
const statusColor = STATUS_COLOR[state.status] ?? "white";
|
|
426
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
427
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
428
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: statusColor, children: liveCaptionStatusLabel(state.status) }),
|
|
429
|
+
elapsed ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${elapsed}` }) : null,
|
|
430
|
+
!following ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u23F8 scrolled \u2014 G for live" }) : null
|
|
431
|
+
] }),
|
|
432
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: items.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: state.status === "error" ? state.error ? `Error: ${state.error}` : "Live captions error" : "Waiting for captions\u2026" }) : items.slice(win.start, win.end).map(
|
|
433
|
+
(it) => it.kind === "translation" ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: `\u21B3 ${it.text}` }, it.key) : /* @__PURE__ */ jsxs(Text, { dimColor: it.kind === "partial", italic: it.kind === "partial", children: [
|
|
434
|
+
it.speaker ? /* @__PURE__ */ jsx(Text, { color: "cyan", children: `${it.speaker}: ` }) : null,
|
|
435
|
+
it.text
|
|
436
|
+
] }, it.key)
|
|
437
|
+
) }),
|
|
438
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
439
|
+
maxScroll > 0 ? "\u2191\u2193 scroll \xB7 G live \xB7 " : "",
|
|
440
|
+
"q / esc / \u2190 back"
|
|
441
|
+
] }) })
|
|
442
|
+
] });
|
|
443
|
+
}
|
|
444
|
+
var STATUS_COLOR;
|
|
445
|
+
var init_LiveCaptionsView = __esm({
|
|
446
|
+
"src/tui/LiveCaptionsView.tsx"() {
|
|
447
|
+
"use strict";
|
|
448
|
+
init_format();
|
|
449
|
+
init_terminal();
|
|
450
|
+
init_liveCaptions();
|
|
451
|
+
STATUS_COLOR = {
|
|
452
|
+
connecting: "yellow",
|
|
453
|
+
live: "red",
|
|
454
|
+
reconnecting: "yellow",
|
|
455
|
+
stopped: "gray",
|
|
456
|
+
error: "red"
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// src/tui/LiveCaptionsScreen.tsx
|
|
462
|
+
import { useEffect, useState as useState2 } from "react";
|
|
463
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
464
|
+
function LiveCaptionsScreen({
|
|
465
|
+
source,
|
|
466
|
+
now = () => Date.now()
|
|
467
|
+
}) {
|
|
468
|
+
const [state, setState] = useState2(initialLiveCaptionsState);
|
|
469
|
+
const [tick, setTick] = useState2(() => now());
|
|
470
|
+
useEffect(() => {
|
|
471
|
+
const unsubscribe = source.onEvent((event) => {
|
|
472
|
+
const mapped = sidecarToLiveCaptionEvent(event);
|
|
473
|
+
if (mapped) setState((s) => liveCaptionReducer(s, mapped));
|
|
474
|
+
});
|
|
475
|
+
return unsubscribe;
|
|
476
|
+
}, [source]);
|
|
477
|
+
useEffect(() => {
|
|
478
|
+
const id = setInterval(() => setTick(now()), 1e3);
|
|
479
|
+
return () => clearInterval(id);
|
|
480
|
+
}, []);
|
|
481
|
+
return /* @__PURE__ */ jsx2(LiveCaptionsView, { state, nowMs: tick });
|
|
482
|
+
}
|
|
483
|
+
var init_LiveCaptionsScreen = __esm({
|
|
484
|
+
"src/tui/LiveCaptionsScreen.tsx"() {
|
|
485
|
+
"use strict";
|
|
486
|
+
init_LiveCaptionsView();
|
|
487
|
+
init_liveCaptions();
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
// src/tui/AccountView.tsx
|
|
272
492
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
273
|
-
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
493
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
494
|
+
function AccountView({ status }) {
|
|
495
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
496
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: "\u2039 Account" }),
|
|
497
|
+
status === "loading" || status === void 0 ? /* @__PURE__ */ jsx4(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text2, { dimColor: true, children: "Loading account\u2026" }) }) : status === "error" ? /* @__PURE__ */ jsx4(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text2, { color: "red", children: "Couldn't load account status" }) }) : !status.loggedIn ? /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
498
|
+
/* @__PURE__ */ jsx4(Text2, { color: "yellow", children: "Not signed in" }),
|
|
499
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: `origin ${status.origin}` }),
|
|
500
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: "Run `recappi auth login` to sign in." })
|
|
501
|
+
] }) : /* @__PURE__ */ jsx4(AccountBody, { status }),
|
|
502
|
+
/* @__PURE__ */ jsx4(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text2, { dimColor: true, children: "r refresh \xB7 esc back \xB7 q quit" }) })
|
|
503
|
+
] });
|
|
504
|
+
}
|
|
505
|
+
function AccountBody({ status }) {
|
|
506
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
507
|
+
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
508
|
+
/* @__PURE__ */ jsx4(Text2, { bold: true, color: "magenta", children: status.email ?? status.userId ?? "Signed in" }),
|
|
509
|
+
status.email && status.userId ? /* @__PURE__ */ jsx4(Text2, { dimColor: true, children: status.userId }) : null,
|
|
510
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: `origin ${status.origin}` })
|
|
511
|
+
] }),
|
|
512
|
+
status.billing ? /* @__PURE__ */ jsx4(Usage, { billing: status.billing }) : null,
|
|
513
|
+
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
514
|
+
/* @__PURE__ */ jsx4(Text2, { bold: true, children: "Local store" }),
|
|
515
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, wrap: "truncate-middle", children: status.localStore.path }),
|
|
516
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
517
|
+
`${status.localStore.accountScopedArtifacts} artifact${status.localStore.accountScopedArtifacts === 1 ? "" : "s"} for this account`,
|
|
518
|
+
status.localStore.unattributedArtifacts > 0 ? ` \xB7 ${status.localStore.unattributedArtifacts} unattributed` : ""
|
|
519
|
+
] })
|
|
520
|
+
] })
|
|
521
|
+
] });
|
|
522
|
+
}
|
|
523
|
+
function Usage({ billing }) {
|
|
524
|
+
const minutesCap = billing.minutesCap;
|
|
525
|
+
const minutesUsed = billing.minutesUsed;
|
|
526
|
+
const storageCap = billing.storageCapBytes;
|
|
527
|
+
return /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
528
|
+
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
529
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: "Plan " }),
|
|
530
|
+
/* @__PURE__ */ jsx4(Text2, { bold: true, children: billing.tier })
|
|
531
|
+
] }),
|
|
532
|
+
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
533
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: "Minutes " }),
|
|
534
|
+
minutesCap != null ? /* @__PURE__ */ jsx4(Text2, { color: billing.isOverMinutes ? "red" : "cyan", children: `${progressBar(minutesUsed / Math.max(1, minutesCap), 12)} ` }) : null,
|
|
535
|
+
/* @__PURE__ */ jsx4(Text2, { color: billing.isOverMinutes ? "red" : void 0, children: `${Math.round(minutesUsed)}` }),
|
|
536
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: ` / ${minutesCap != null ? Math.round(minutesCap) : "\u221E"} min` }),
|
|
537
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: ` (batch ${Math.round(billing.batchMinutesUsed)} \xB7 live ${Math.round(billing.realtimeMinutesUsed)})` })
|
|
538
|
+
] }),
|
|
539
|
+
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
540
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: "Storage " }),
|
|
541
|
+
storageCap != null ? /* @__PURE__ */ jsx4(Text2, { color: billing.isOverStorage ? "red" : "cyan", children: `${progressBar(billing.storageBytes / Math.max(1, storageCap), 12)} ` }) : null,
|
|
542
|
+
/* @__PURE__ */ jsx4(Text2, { color: billing.isOverStorage ? "red" : void 0, children: formatBytes2(billing.storageBytes) }),
|
|
543
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: ` / ${storageCap != null ? formatBytes2(storageCap) : "\u221E"}` })
|
|
544
|
+
] }),
|
|
545
|
+
billing.isOverMinutes || billing.isOverStorage ? /* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
|
|
546
|
+
billing.isOverMinutes ? "Over minutes limit. " : "",
|
|
547
|
+
billing.isOverStorage ? "Over storage limit." : ""
|
|
548
|
+
] }) : null,
|
|
549
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: `Period ${periodText(billing)}` })
|
|
550
|
+
] });
|
|
551
|
+
}
|
|
552
|
+
function periodText(billing) {
|
|
553
|
+
const remainingMs = epochToMs(billing.periodEnd) - Date.now();
|
|
554
|
+
if (!Number.isFinite(remainingMs) || remainingMs <= 0) return "\u2014";
|
|
555
|
+
const days = Math.floor(remainingMs / 864e5);
|
|
556
|
+
if (days >= 1) return `${days}d left`;
|
|
557
|
+
return `${formatClockMs(remainingMs)} left`;
|
|
558
|
+
}
|
|
559
|
+
function epochToMs(value) {
|
|
560
|
+
return value > 1e12 ? value : value * 1e3;
|
|
561
|
+
}
|
|
562
|
+
var init_AccountView = __esm({
|
|
563
|
+
"src/tui/AccountView.tsx"() {
|
|
564
|
+
"use strict";
|
|
565
|
+
init_format();
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
// src/tui/chrome.tsx
|
|
570
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
571
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
274
572
|
function Header({ active }) {
|
|
275
|
-
return /* @__PURE__ */
|
|
276
|
-
/* @__PURE__ */
|
|
573
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
574
|
+
/* @__PURE__ */ jsxs3(Text3, { bold: true, color: "magenta", children: [
|
|
277
575
|
"Recappi",
|
|
278
576
|
" "
|
|
279
577
|
] }),
|
|
280
|
-
/* @__PURE__ */
|
|
281
|
-
/* @__PURE__ */
|
|
578
|
+
/* @__PURE__ */ jsx5(Tab, { num: "1", label: "Overview", active: active === "overview" }),
|
|
579
|
+
/* @__PURE__ */ jsx5(Tab, { num: "2", label: "Jobs", active: active === "jobs" }),
|
|
580
|
+
/* @__PURE__ */ jsx5(Tab, { num: "3", label: "Account", active: active === "account" }),
|
|
581
|
+
/* @__PURE__ */ jsx5(Tab, { num: "4", label: "Record", active: active === "record" })
|
|
282
582
|
] });
|
|
283
583
|
}
|
|
284
584
|
function Tab({
|
|
@@ -288,12 +588,12 @@ function Tab({
|
|
|
288
588
|
}) {
|
|
289
589
|
const text = ` ${num} ${label} `;
|
|
290
590
|
if (active) {
|
|
291
|
-
return /* @__PURE__ */
|
|
591
|
+
return /* @__PURE__ */ jsx5(Text3, { bold: true, inverse: true, color: "cyan", children: text });
|
|
292
592
|
}
|
|
293
|
-
return /* @__PURE__ */
|
|
593
|
+
return /* @__PURE__ */ jsx5(Text3, { children: text });
|
|
294
594
|
}
|
|
295
595
|
function Footer({ keys }) {
|
|
296
|
-
return /* @__PURE__ */
|
|
596
|
+
return /* @__PURE__ */ jsx5(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: keys }) });
|
|
297
597
|
}
|
|
298
598
|
var init_chrome = __esm({
|
|
299
599
|
"src/tui/chrome.tsx"() {
|
|
@@ -302,8 +602,8 @@ var init_chrome = __esm({
|
|
|
302
602
|
});
|
|
303
603
|
|
|
304
604
|
// src/tui/JobRow.tsx
|
|
305
|
-
import { Box as
|
|
306
|
-
import { jsx as
|
|
605
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
606
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
307
607
|
function JobRow({
|
|
308
608
|
item,
|
|
309
609
|
selected,
|
|
@@ -312,11 +612,11 @@ function JobRow({
|
|
|
312
612
|
const style = statusStyle(item.status);
|
|
313
613
|
const glyph = statusGlyph(item.status, spinnerFrame);
|
|
314
614
|
const title = item.recording?.title ?? item.recordingId;
|
|
315
|
-
return /* @__PURE__ */
|
|
316
|
-
/* @__PURE__ */
|
|
317
|
-
/* @__PURE__ */
|
|
318
|
-
/* @__PURE__ */
|
|
319
|
-
/* @__PURE__ */
|
|
615
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
616
|
+
/* @__PURE__ */ jsx6(Text4, { color: "cyan", children: selected ? "\u25B8 " : " " }),
|
|
617
|
+
/* @__PURE__ */ jsx6(Text4, { color: style.color, children: `${glyph} ${padCell(style.label, 13)}` }),
|
|
618
|
+
/* @__PURE__ */ jsx6(Text4, { bold: selected, children: padCell(title, 24) }),
|
|
619
|
+
/* @__PURE__ */ jsx6(Text4, { dimColor: !selected, children: jobDetail(item) })
|
|
320
620
|
] });
|
|
321
621
|
}
|
|
322
622
|
var init_JobRow = __esm({
|
|
@@ -327,17 +627,17 @@ var init_JobRow = __esm({
|
|
|
327
627
|
});
|
|
328
628
|
|
|
329
629
|
// src/tui/JobsView.tsx
|
|
330
|
-
import { Box as
|
|
331
|
-
import { jsx as
|
|
630
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
631
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
332
632
|
function JobsView({
|
|
333
633
|
items,
|
|
334
634
|
selectedIndex,
|
|
335
635
|
spinnerFrame
|
|
336
636
|
}) {
|
|
337
637
|
if (items.length === 0) {
|
|
338
|
-
return /* @__PURE__ */
|
|
638
|
+
return /* @__PURE__ */ jsx7(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text5, { dimColor: true, children: "No transcription jobs yet \u2014 run: recappi upload <file> --transcribe" }) });
|
|
339
639
|
}
|
|
340
|
-
return /* @__PURE__ */
|
|
640
|
+
return /* @__PURE__ */ jsx7(Box5, { marginTop: 1, flexDirection: "column", children: items.map((item, index) => /* @__PURE__ */ jsx7(
|
|
341
641
|
JobRow,
|
|
342
642
|
{
|
|
343
643
|
item,
|
|
@@ -355,8 +655,8 @@ var init_JobsView = __esm({
|
|
|
355
655
|
});
|
|
356
656
|
|
|
357
657
|
// src/tui/RecordingRow.tsx
|
|
358
|
-
import { Box as
|
|
359
|
-
import { jsx as
|
|
658
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
659
|
+
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
360
660
|
function recordingTitle2(item) {
|
|
361
661
|
const named = (item.title || item.summaryTitle || "").trim();
|
|
362
662
|
if (named && !UUID_RE.test(named)) return named;
|
|
@@ -392,22 +692,22 @@ function RecordingRow({
|
|
|
392
692
|
const { title, showWhen } = recordingLayout(columns);
|
|
393
693
|
const { glyph, color } = recordingProcessingState(item, jobStatus, spinnerFrame);
|
|
394
694
|
const duration3 = item.durationMs ? formatClockMs(item.durationMs) : "\u2014";
|
|
395
|
-
return /* @__PURE__ */
|
|
396
|
-
/* @__PURE__ */
|
|
397
|
-
/* @__PURE__ */
|
|
398
|
-
/* @__PURE__ */
|
|
399
|
-
/* @__PURE__ */
|
|
400
|
-
showWhen ? /* @__PURE__ */
|
|
401
|
-
/* @__PURE__ */
|
|
695
|
+
return /* @__PURE__ */ jsxs5(Box6, { children: [
|
|
696
|
+
/* @__PURE__ */ jsx8(Text6, { color: "cyan", children: selected ? "\u25B8 " : " " }),
|
|
697
|
+
/* @__PURE__ */ jsx8(Text6, { color, children: `${glyph} ` }),
|
|
698
|
+
/* @__PURE__ */ jsx8(Text6, { bold: selected, children: padDisplay(recordingTitle2(item), title) }),
|
|
699
|
+
/* @__PURE__ */ jsx8(Text6, { dimColor: true, children: padDisplay(duration3, LENGTH_W) }),
|
|
700
|
+
showWhen ? /* @__PURE__ */ jsx8(Text6, { dimColor: true, children: padDisplay(formatAge(item.createdAt, nowMs), WHEN_W) }) : null,
|
|
701
|
+
/* @__PURE__ */ jsx8(Text6, { color: "green", children: downloaded ? " \u2913" : " " })
|
|
402
702
|
] });
|
|
403
703
|
}
|
|
404
704
|
function RecordingHeader({ columns }) {
|
|
405
705
|
const { title, showWhen } = recordingLayout(columns);
|
|
406
|
-
return /* @__PURE__ */
|
|
407
|
-
/* @__PURE__ */
|
|
408
|
-
/* @__PURE__ */
|
|
409
|
-
/* @__PURE__ */
|
|
410
|
-
showWhen ? /* @__PURE__ */
|
|
706
|
+
return /* @__PURE__ */ jsxs5(Box6, { children: [
|
|
707
|
+
/* @__PURE__ */ jsx8(Text6, { dimColor: true, children: padDisplay("", MARKER_W + GLYPH_W) }),
|
|
708
|
+
/* @__PURE__ */ jsx8(Text6, { dimColor: true, children: padDisplay("TITLE", title) }),
|
|
709
|
+
/* @__PURE__ */ jsx8(Text6, { dimColor: true, children: padDisplay("LENGTH", LENGTH_W) }),
|
|
710
|
+
showWhen ? /* @__PURE__ */ jsx8(Text6, { dimColor: true, children: "WHEN" }) : null
|
|
411
711
|
] });
|
|
412
712
|
}
|
|
413
713
|
var UUID_RE, MARKER_W, GLYPH_W, LENGTH_W, WHEN_W;
|
|
@@ -425,8 +725,8 @@ var init_RecordingRow = __esm({
|
|
|
425
725
|
|
|
426
726
|
// src/tui/RecordingsView.tsx
|
|
427
727
|
import React3 from "react";
|
|
428
|
-
import { Box as
|
|
429
|
-
import { jsx as
|
|
728
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
729
|
+
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
430
730
|
function RecordingsView({
|
|
431
731
|
items,
|
|
432
732
|
selectedIndex,
|
|
@@ -437,16 +737,16 @@ function RecordingsView({
|
|
|
437
737
|
spinnerFrame = 0
|
|
438
738
|
}) {
|
|
439
739
|
if (items.length === 0) {
|
|
440
|
-
return /* @__PURE__ */
|
|
740
|
+
return /* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: "No recordings yet \u2014 run: recappi upload <file>" }) });
|
|
441
741
|
}
|
|
442
|
-
return /* @__PURE__ */
|
|
443
|
-
/* @__PURE__ */
|
|
742
|
+
return /* @__PURE__ */ jsxs6(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
743
|
+
/* @__PURE__ */ jsx9(RecordingHeader, { columns }),
|
|
444
744
|
items.map((item, index) => {
|
|
445
745
|
const bucket = dateBucket(item.createdAt, nowMs);
|
|
446
746
|
const showHeader = index === 0 || bucket !== dateBucket(items[index - 1].createdAt, nowMs);
|
|
447
|
-
return /* @__PURE__ */
|
|
448
|
-
showHeader ? /* @__PURE__ */
|
|
449
|
-
/* @__PURE__ */
|
|
747
|
+
return /* @__PURE__ */ jsxs6(React3.Fragment, { children: [
|
|
748
|
+
showHeader ? /* @__PURE__ */ jsx9(Box7, { marginTop: index === 0 ? 0 : 1, children: /* @__PURE__ */ jsx9(Text7, { bold: true, color: "blue", children: bucket }) }) : null,
|
|
749
|
+
/* @__PURE__ */ jsx9(
|
|
450
750
|
RecordingRow,
|
|
451
751
|
{
|
|
452
752
|
item,
|
|
@@ -471,15 +771,15 @@ var init_RecordingsView = __esm({
|
|
|
471
771
|
});
|
|
472
772
|
|
|
473
773
|
// src/tui/RecordingPeek.tsx
|
|
474
|
-
import { Box as
|
|
475
|
-
import { Fragment, jsx as
|
|
774
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
775
|
+
import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
476
776
|
function RecordingPeek({
|
|
477
777
|
item,
|
|
478
778
|
summary,
|
|
479
779
|
nowMs,
|
|
480
780
|
width
|
|
481
781
|
}) {
|
|
482
|
-
return /* @__PURE__ */
|
|
782
|
+
return /* @__PURE__ */ jsx10(Box8, { width, borderStyle: "round", borderColor: "gray", paddingX: 1, flexDirection: "column", children: !item ? /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: "No selection" }) : /* @__PURE__ */ jsx10(PeekBody, { item, summary, nowMs }) });
|
|
483
783
|
}
|
|
484
784
|
function PeekBody({
|
|
485
785
|
item,
|
|
@@ -492,30 +792,30 @@ function PeekBody({
|
|
|
492
792
|
formatBytes2(item.sizeBytes) || null,
|
|
493
793
|
item.contentType || null
|
|
494
794
|
].filter(Boolean).join(" \xB7 ");
|
|
495
|
-
return /* @__PURE__ */
|
|
496
|
-
/* @__PURE__ */
|
|
497
|
-
/* @__PURE__ */
|
|
498
|
-
meta3 ? /* @__PURE__ */
|
|
499
|
-
/* @__PURE__ */
|
|
500
|
-
/* @__PURE__ */
|
|
501
|
-
/* @__PURE__ */
|
|
795
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
796
|
+
/* @__PURE__ */ jsx10(Text8, { bold: true, color: "magenta", wrap: "truncate-end", children: recordingTitle2(item) }),
|
|
797
|
+
/* @__PURE__ */ jsx10(Text8, { color: style.color, children: `${style.glyph} ${style.label}` }),
|
|
798
|
+
meta3 ? /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: meta3 }) : null,
|
|
799
|
+
/* @__PURE__ */ jsx10(Text8, { dimColor: true, children: formatAge(item.createdAt, nowMs) }),
|
|
800
|
+
/* @__PURE__ */ jsx10(Box8, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx10(SummarySection, { item, summary }) }),
|
|
801
|
+
/* @__PURE__ */ jsx10(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: "\u23CE open \xB7 t transcript \xB7 o web" }) })
|
|
502
802
|
] });
|
|
503
803
|
}
|
|
504
804
|
function SummarySection({
|
|
505
805
|
item,
|
|
506
806
|
summary
|
|
507
807
|
}) {
|
|
508
|
-
if (!item.activeTranscriptId) return /* @__PURE__ */
|
|
509
|
-
if (summary === "loading" || summary === void 0) return /* @__PURE__ */
|
|
510
|
-
if (summary === "error") return /* @__PURE__ */
|
|
808
|
+
if (!item.activeTranscriptId) return /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: "No transcript yet" });
|
|
809
|
+
if (summary === "loading" || summary === void 0) return /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: "Loading summary\u2026" });
|
|
810
|
+
if (summary === "error") return /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: "(summary unavailable)" });
|
|
511
811
|
if (summary.status !== "succeeded" || !summary.tldr) {
|
|
512
|
-
return /* @__PURE__ */
|
|
812
|
+
return /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: `Summary ${summary.status}` });
|
|
513
813
|
}
|
|
514
814
|
const points = (summary.keyPoints ?? []).slice(0, 3);
|
|
515
|
-
return /* @__PURE__ */
|
|
516
|
-
/* @__PURE__ */
|
|
517
|
-
/* @__PURE__ */
|
|
518
|
-
points.length > 0 ? /* @__PURE__ */
|
|
815
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
816
|
+
/* @__PURE__ */ jsx10(Text8, { bold: true, children: "Summary" }),
|
|
817
|
+
/* @__PURE__ */ jsx10(Text8, { children: summary.tldr }),
|
|
818
|
+
points.length > 0 ? /* @__PURE__ */ jsx10(Box8, { marginTop: 1, flexDirection: "column", children: points.map((point, i) => /* @__PURE__ */ jsx10(Text8, { dimColor: true, wrap: "truncate-end", children: `\u2022 ${point}` }, i)) }) : null
|
|
519
819
|
] });
|
|
520
820
|
}
|
|
521
821
|
var init_RecordingPeek = __esm({
|
|
@@ -527,8 +827,8 @@ var init_RecordingPeek = __esm({
|
|
|
527
827
|
});
|
|
528
828
|
|
|
529
829
|
// src/tui/OverviewView.tsx
|
|
530
|
-
import { Box as
|
|
531
|
-
import { jsx as
|
|
830
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
831
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
532
832
|
function OverviewView({
|
|
533
833
|
recordings,
|
|
534
834
|
jobs,
|
|
@@ -547,17 +847,17 @@ function OverviewView({
|
|
|
547
847
|
const jobCounts = countJobs(jobs);
|
|
548
848
|
const running = stats?.jobs.running ?? jobCounts.running;
|
|
549
849
|
const queued = stats?.jobs.queued ?? jobCounts.queued;
|
|
550
|
-
return /* @__PURE__ */
|
|
551
|
-
/* @__PURE__ */
|
|
552
|
-
/* @__PURE__ */
|
|
553
|
-
/* @__PURE__ */
|
|
554
|
-
stats?.recordings.ready != null ? /* @__PURE__ */
|
|
555
|
-
stats?.recordings.totalDurationMs != null ? /* @__PURE__ */
|
|
556
|
-
running > 0 ? /* @__PURE__ */
|
|
557
|
-
queued > 0 ? /* @__PURE__ */
|
|
850
|
+
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
|
|
851
|
+
/* @__PURE__ */ jsxs8(Box9, { children: [
|
|
852
|
+
/* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "Recordings " }),
|
|
853
|
+
/* @__PURE__ */ jsx11(Text9, { bold: true, children: stats?.recordings.total ?? recordings.length }),
|
|
854
|
+
stats?.recordings.ready != null ? /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: ` \xB7 ${stats.recordings.ready} ready` }) : null,
|
|
855
|
+
stats?.recordings.totalDurationMs != null ? /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: ` \xB7 ${formatClockMs(stats.recordings.totalDurationMs)} transcribed` }) : null,
|
|
856
|
+
running > 0 ? /* @__PURE__ */ jsx11(Text9, { color: "cyan", children: ` \xB7 ${running} transcribing` }) : null,
|
|
857
|
+
queued > 0 ? /* @__PURE__ */ jsx11(Text9, { color: "yellow", children: ` \xB7 ${queued} queued` }) : null
|
|
558
858
|
] }),
|
|
559
|
-
/* @__PURE__ */
|
|
560
|
-
/* @__PURE__ */
|
|
859
|
+
/* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", alignItems: "flex-start", children: [
|
|
860
|
+
/* @__PURE__ */ jsx11(Box9, { flexGrow: 1, flexDirection: "column", children: /* @__PURE__ */ jsx11(
|
|
561
861
|
RecordingsView,
|
|
562
862
|
{
|
|
563
863
|
items: recordings,
|
|
@@ -569,7 +869,7 @@ function OverviewView({
|
|
|
569
869
|
spinnerFrame
|
|
570
870
|
}
|
|
571
871
|
) }),
|
|
572
|
-
showPeek ? /* @__PURE__ */
|
|
872
|
+
showPeek ? /* @__PURE__ */ jsx11(Box9, { marginLeft: 1, marginTop: 1, children: /* @__PURE__ */ jsx11(RecordingPeek, { item: peekItem, summary: peekSummary, nowMs, width: peekWidth }) }) : null
|
|
573
873
|
] })
|
|
574
874
|
] });
|
|
575
875
|
}
|
|
@@ -583,8 +883,8 @@ var init_OverviewView = __esm({
|
|
|
583
883
|
});
|
|
584
884
|
|
|
585
885
|
// src/tui/JobDetailView.tsx
|
|
586
|
-
import { Box as
|
|
587
|
-
import { jsx as
|
|
886
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
887
|
+
import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
588
888
|
function JobDetailView({
|
|
589
889
|
item,
|
|
590
890
|
origin,
|
|
@@ -594,13 +894,13 @@ function JobDetailView({
|
|
|
594
894
|
const style = statusStyle(item.status);
|
|
595
895
|
const links = resolveJobLinks(item, origin);
|
|
596
896
|
const title = item.recording?.title ?? item.recordingId;
|
|
597
|
-
return /* @__PURE__ */
|
|
598
|
-
/* @__PURE__ */
|
|
897
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", paddingX: 1, children: [
|
|
898
|
+
/* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
|
|
599
899
|
"\u2039 Jobs / ",
|
|
600
900
|
title
|
|
601
901
|
] }),
|
|
602
|
-
/* @__PURE__ */
|
|
603
|
-
|
|
902
|
+
/* @__PURE__ */ jsxs9(
|
|
903
|
+
Box10,
|
|
604
904
|
{
|
|
605
905
|
marginTop: 1,
|
|
606
906
|
borderStyle: "round",
|
|
@@ -608,17 +908,17 @@ function JobDetailView({
|
|
|
608
908
|
paddingX: 1,
|
|
609
909
|
flexDirection: "column",
|
|
610
910
|
children: [
|
|
611
|
-
/* @__PURE__ */
|
|
911
|
+
/* @__PURE__ */ jsxs9(Text10, { color: style.color, bold: true, children: [
|
|
612
912
|
style.label,
|
|
613
|
-
item.provider ? /* @__PURE__ */
|
|
913
|
+
item.provider ? /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: ` ${item.provider}` }) : null
|
|
614
914
|
] }),
|
|
615
|
-
/* @__PURE__ */
|
|
915
|
+
/* @__PURE__ */ jsx12(StatusLine, { item, spinnerFrame, nowMs })
|
|
616
916
|
]
|
|
617
917
|
}
|
|
618
918
|
),
|
|
619
|
-
/* @__PURE__ */
|
|
620
|
-
/* @__PURE__ */
|
|
621
|
-
/* @__PURE__ */
|
|
919
|
+
/* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "column", children: [
|
|
920
|
+
/* @__PURE__ */ jsx12(Text10, { bold: true, children: "Timeline" }),
|
|
921
|
+
/* @__PURE__ */ jsx12(
|
|
622
922
|
TimelineRow,
|
|
623
923
|
{
|
|
624
924
|
label: "Enqueued",
|
|
@@ -627,7 +927,7 @@ function JobDetailView({
|
|
|
627
927
|
nowMs
|
|
628
928
|
}
|
|
629
929
|
),
|
|
630
|
-
/* @__PURE__ */
|
|
930
|
+
/* @__PURE__ */ jsx12(
|
|
631
931
|
TimelineRow,
|
|
632
932
|
{
|
|
633
933
|
label: "Started",
|
|
@@ -636,7 +936,7 @@ function JobDetailView({
|
|
|
636
936
|
nowMs
|
|
637
937
|
}
|
|
638
938
|
),
|
|
639
|
-
/* @__PURE__ */
|
|
939
|
+
/* @__PURE__ */ jsx12(
|
|
640
940
|
TimelineRow,
|
|
641
941
|
{
|
|
642
942
|
label: item.status === "failed" ? "Failed" : item.status === "running" ? "Transcribing" : "Finished",
|
|
@@ -648,21 +948,21 @@ function JobDetailView({
|
|
|
648
948
|
}
|
|
649
949
|
)
|
|
650
950
|
] }),
|
|
651
|
-
/* @__PURE__ */
|
|
652
|
-
/* @__PURE__ */
|
|
951
|
+
/* @__PURE__ */ jsx12(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
952
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: "Recording " }),
|
|
653
953
|
title,
|
|
654
|
-
item.recording?.durationMs ? /* @__PURE__ */
|
|
954
|
+
item.recording?.durationMs ? /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: ` \xB7 ${formatClockMs(item.recording.durationMs)}` }) : null
|
|
655
955
|
] }) }),
|
|
656
|
-
/* @__PURE__ */
|
|
657
|
-
/* @__PURE__ */
|
|
658
|
-
/* @__PURE__ */
|
|
659
|
-
/* @__PURE__ */
|
|
660
|
-
/* @__PURE__ */
|
|
661
|
-
/* @__PURE__ */
|
|
662
|
-
/* @__PURE__ */
|
|
663
|
-
/* @__PURE__ */
|
|
956
|
+
/* @__PURE__ */ jsx12(Box10, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
957
|
+
/* @__PURE__ */ jsx12(Text10, { color: links.webUrl ? "cyan" : "gray", children: "o open" }),
|
|
958
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " \xB7 " }),
|
|
959
|
+
/* @__PURE__ */ jsx12(Text10, { color: links.webUrl ? "cyan" : "gray", children: "w web" }),
|
|
960
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " \xB7 " }),
|
|
961
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: "m mac app (soon)" }),
|
|
962
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " \xB7 " }),
|
|
963
|
+
/* @__PURE__ */ jsx12(Text10, { color: links.webUrl ? "cyan" : "gray", children: "c copy" })
|
|
664
964
|
] }) }),
|
|
665
|
-
/* @__PURE__ */
|
|
965
|
+
/* @__PURE__ */ jsx12(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
|
|
666
966
|
"esc back \xB7 t transcript",
|
|
667
967
|
item.transcriptId ? "" : " (when ready)",
|
|
668
968
|
" \xB7 q quit"
|
|
@@ -679,24 +979,24 @@ function StatusLine({
|
|
|
679
979
|
const elapsed = item.startedAt ? ` \xB7 ${formatClockMs(nowMs - item.startedAt)} elapsed` : "";
|
|
680
980
|
if (fraction != null) {
|
|
681
981
|
const pct = Math.round(fraction * 100);
|
|
682
|
-
return /* @__PURE__ */
|
|
982
|
+
return /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
683
983
|
`${progressBar(fraction)} ${pct}% ${formatClockMs(item.processedDurationMs)} / ${formatClockMs(
|
|
684
984
|
item.recording?.durationMs
|
|
685
985
|
)}`,
|
|
686
|
-
/* @__PURE__ */
|
|
986
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: elapsed })
|
|
687
987
|
] });
|
|
688
988
|
}
|
|
689
989
|
const spinner = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"][spinnerFrame % 10];
|
|
690
|
-
return /* @__PURE__ */
|
|
990
|
+
return /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
691
991
|
`${spinner} transcribing\u2026`,
|
|
692
|
-
/* @__PURE__ */
|
|
992
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: elapsed })
|
|
693
993
|
] });
|
|
694
994
|
}
|
|
695
995
|
if (item.status === "succeeded")
|
|
696
|
-
return /* @__PURE__ */
|
|
697
|
-
if (item.status === "queued") return /* @__PURE__ */
|
|
698
|
-
if (item.status === "failed") return /* @__PURE__ */
|
|
699
|
-
return /* @__PURE__ */
|
|
996
|
+
return /* @__PURE__ */ jsx12(Text10, { children: item.transcriptId ? "transcript ready" : "done" });
|
|
997
|
+
if (item.status === "queued") return /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: "waiting to start\u2026" });
|
|
998
|
+
if (item.status === "failed") return /* @__PURE__ */ jsx12(Text10, { color: "red", children: "transcription failed" });
|
|
999
|
+
return /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: item.status });
|
|
700
1000
|
}
|
|
701
1001
|
function TimelineRow({
|
|
702
1002
|
label,
|
|
@@ -709,10 +1009,10 @@ function TimelineRow({
|
|
|
709
1009
|
const glyph = failed ? "\u2717" : done ? "\u2713" : running ? "\u280B" : "\u25CB";
|
|
710
1010
|
const color = failed ? "red" : done ? "green" : running ? "cyan" : "gray";
|
|
711
1011
|
const age = at ? formatAge(at, nowMs) : running ? "now" : "";
|
|
712
|
-
return /* @__PURE__ */
|
|
713
|
-
/* @__PURE__ */
|
|
714
|
-
/* @__PURE__ */
|
|
715
|
-
age ? /* @__PURE__ */
|
|
1012
|
+
return /* @__PURE__ */ jsxs9(Box10, { children: [
|
|
1013
|
+
/* @__PURE__ */ jsx12(Text10, { color, children: ` ${glyph} ` }),
|
|
1014
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: !done && !running, children: label }),
|
|
1015
|
+
age ? /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: ` ${age}` }) : null
|
|
716
1016
|
] });
|
|
717
1017
|
}
|
|
718
1018
|
var init_JobDetailView = __esm({
|
|
@@ -724,8 +1024,8 @@ var init_JobDetailView = __esm({
|
|
|
724
1024
|
|
|
725
1025
|
// src/tui/RecordingDetailView.tsx
|
|
726
1026
|
import React4, { useMemo as useMemo2, useState as useState3 } from "react";
|
|
727
|
-
import { Box as
|
|
728
|
-
import { Fragment as
|
|
1027
|
+
import { Box as Box11, Text as Text11, useInput as useInput3 } from "ink";
|
|
1028
|
+
import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
729
1029
|
function RecordingDetailView({
|
|
730
1030
|
item,
|
|
731
1031
|
nowMs,
|
|
@@ -788,20 +1088,20 @@ function RecordingDetailView({
|
|
|
788
1088
|
else if (input === "g") setScroll(0);
|
|
789
1089
|
else if (input === "G") setScroll(segWin.maxScroll);
|
|
790
1090
|
});
|
|
791
|
-
return /* @__PURE__ */
|
|
792
|
-
/* @__PURE__ */
|
|
793
|
-
/* @__PURE__ */
|
|
794
|
-
/* @__PURE__ */
|
|
795
|
-
/* @__PURE__ */
|
|
796
|
-
/* @__PURE__ */
|
|
1091
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingX: 1, children: [
|
|
1092
|
+
/* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "\u2039 Recordings" }),
|
|
1093
|
+
/* @__PURE__ */ jsx13(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text11, { bold: true, color: "magenta", children: title }) }),
|
|
1094
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
1095
|
+
/* @__PURE__ */ jsx13(Text11, { color: style.color, children: `${style.glyph} ${style.label}` }),
|
|
1096
|
+
/* @__PURE__ */ jsx13(Text11, { dimColor: true, children: ` ${formatAge(item.createdAt, nowMs) || "\u2014"}` })
|
|
797
1097
|
] }),
|
|
798
|
-
meta3 ? /* @__PURE__ */
|
|
799
|
-
/* @__PURE__ */
|
|
800
|
-
!item.activeTranscriptId ? /* @__PURE__ */
|
|
801
|
-
/* @__PURE__ */
|
|
802
|
-
/* @__PURE__ */
|
|
1098
|
+
meta3 ? /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: meta3 }) : null,
|
|
1099
|
+
/* @__PURE__ */ jsx13(AudioActionRow, { item, audio }),
|
|
1100
|
+
!item.activeTranscriptId ? /* @__PURE__ */ jsx13(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "Transcript not available yet" }) }) : transcript === "loading" || transcript === void 0 ? /* @__PURE__ */ jsx13(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "Loading\u2026" }) }) : transcript === "error" ? /* @__PURE__ */ jsx13(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "(transcript unavailable)" }) }) : /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
1101
|
+
/* @__PURE__ */ jsx13(TabBar, { active: tab }),
|
|
1102
|
+
/* @__PURE__ */ jsx13(Box11, { marginTop: 1, flexDirection: "column", children: tab === "summary" ? /* @__PURE__ */ jsx13(SummaryPane, { summary, budget: paneBudget }) : tab === "chapters" ? /* @__PURE__ */ jsx13(ChaptersPane, { chapters, win: chapWin, selectedIndex: chapterSel }) : /* @__PURE__ */ jsx13(TranscriptPane, { segments, win: segWin }) })
|
|
803
1103
|
] }),
|
|
804
|
-
/* @__PURE__ */
|
|
1104
|
+
/* @__PURE__ */ jsx13(Box11, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
|
|
805
1105
|
ready ? "tab switch" : "",
|
|
806
1106
|
ready && tab === "chapters" ? " \xB7 \u2191\u2193 select \xB7 \u23CE jump" : "",
|
|
807
1107
|
scrollable ? " \xB7 \u2191\u2193 scroll" : "",
|
|
@@ -814,9 +1114,9 @@ function RecordingDetailView({
|
|
|
814
1114
|
] });
|
|
815
1115
|
}
|
|
816
1116
|
function TabBar({ active }) {
|
|
817
|
-
return /* @__PURE__ */
|
|
818
|
-
i > 0 ? /* @__PURE__ */
|
|
819
|
-
tab === active ? /* @__PURE__ */
|
|
1117
|
+
return /* @__PURE__ */ jsx13(Box11, { marginTop: 1, children: TAB_ORDER.map((tab, i) => /* @__PURE__ */ jsxs10(React4.Fragment, { children: [
|
|
1118
|
+
i > 0 ? /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: " " }) : null,
|
|
1119
|
+
tab === active ? /* @__PURE__ */ jsx13(Text11, { inverse: true, bold: true, children: ` ${TAB_LABEL[tab]} ` }) : /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: ` ${TAB_LABEL[tab]} ` })
|
|
820
1120
|
] }, tab)) });
|
|
821
1121
|
}
|
|
822
1122
|
function AudioActionRow({
|
|
@@ -827,30 +1127,30 @@ function AudioActionRow({
|
|
|
827
1127
|
const status = audio?.status ?? "idle";
|
|
828
1128
|
let line;
|
|
829
1129
|
if (!ready) {
|
|
830
|
-
line = /* @__PURE__ */
|
|
1130
|
+
line = /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "Audio available once the recording is ready" });
|
|
831
1131
|
} else if (status === "downloading") {
|
|
832
|
-
line = /* @__PURE__ */
|
|
1132
|
+
line = /* @__PURE__ */ jsx13(Text11, { color: "cyan", children: "Downloading audio\u2026" });
|
|
833
1133
|
} else if (status === "opening") {
|
|
834
|
-
line = /* @__PURE__ */
|
|
1134
|
+
line = /* @__PURE__ */ jsx13(Text11, { color: "cyan", children: "Opening\u2026" });
|
|
835
1135
|
} else if (status === "error") {
|
|
836
|
-
line = /* @__PURE__ */
|
|
1136
|
+
line = /* @__PURE__ */ jsx13(Text11, { color: "red", children: audio?.error ? `Audio failed: ${audio.error}` : "Audio failed" });
|
|
837
1137
|
} else if (status === "ready" && audio?.localPath) {
|
|
838
|
-
line = /* @__PURE__ */
|
|
839
|
-
/* @__PURE__ */
|
|
840
|
-
/* @__PURE__ */
|
|
1138
|
+
line = /* @__PURE__ */ jsxs10(Text11, { children: [
|
|
1139
|
+
/* @__PURE__ */ jsx13(Text11, { color: "green", children: "\u2713 Downloaded " }),
|
|
1140
|
+
/* @__PURE__ */ jsx13(Text11, { dimColor: true, wrap: "truncate-middle", children: audio.localPath })
|
|
841
1141
|
] });
|
|
842
1142
|
} else {
|
|
843
|
-
line = /* @__PURE__ */
|
|
844
|
-
/* @__PURE__ */
|
|
845
|
-
/* @__PURE__ */
|
|
846
|
-
/* @__PURE__ */
|
|
847
|
-
/* @__PURE__ */
|
|
848
|
-
/* @__PURE__ */
|
|
849
|
-
/* @__PURE__ */
|
|
1143
|
+
line = /* @__PURE__ */ jsxs10(Text11, { children: [
|
|
1144
|
+
/* @__PURE__ */ jsx13(Text11, { color: "cyan", children: "o" }),
|
|
1145
|
+
/* @__PURE__ */ jsx13(Text11, { dimColor: true, children: " open in player \xB7 " }),
|
|
1146
|
+
/* @__PURE__ */ jsx13(Text11, { color: "cyan", children: "d" }),
|
|
1147
|
+
/* @__PURE__ */ jsx13(Text11, { dimColor: true, children: " download \xB7 " }),
|
|
1148
|
+
/* @__PURE__ */ jsx13(Text11, { color: "cyan", children: "f" }),
|
|
1149
|
+
/* @__PURE__ */ jsx13(Text11, { dimColor: true, children: " reveal in Finder" })
|
|
850
1150
|
] });
|
|
851
1151
|
}
|
|
852
|
-
return /* @__PURE__ */
|
|
853
|
-
/* @__PURE__ */
|
|
1152
|
+
return /* @__PURE__ */ jsxs10(Box11, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: [
|
|
1153
|
+
/* @__PURE__ */ jsx13(Text11, { color: ready ? "cyan" : "gray", children: "\u266A " }),
|
|
854
1154
|
line
|
|
855
1155
|
] });
|
|
856
1156
|
}
|
|
@@ -859,12 +1159,12 @@ function SummaryPane({
|
|
|
859
1159
|
budget
|
|
860
1160
|
}) {
|
|
861
1161
|
if (!summary || summary.status !== "succeeded" || !summary.tldr) {
|
|
862
|
-
return /* @__PURE__ */
|
|
1162
|
+
return /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: `Summary ${summary?.status ?? "unavailable"}` });
|
|
863
1163
|
}
|
|
864
1164
|
const points = (summary.keyPoints ?? []).slice(0, Math.max(1, budget - 4));
|
|
865
|
-
return /* @__PURE__ */
|
|
866
|
-
/* @__PURE__ */
|
|
867
|
-
points.length > 0 ? /* @__PURE__ */
|
|
1165
|
+
return /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
1166
|
+
/* @__PURE__ */ jsx13(Text11, { children: summary.tldr }),
|
|
1167
|
+
points.length > 0 ? /* @__PURE__ */ jsx13(Box11, { marginTop: 1, flexDirection: "column", children: points.map((point, i) => /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: `\u2022 ${point}` }, i)) }) : null
|
|
868
1168
|
] });
|
|
869
1169
|
}
|
|
870
1170
|
function ChaptersPane({
|
|
@@ -872,32 +1172,32 @@ function ChaptersPane({
|
|
|
872
1172
|
win,
|
|
873
1173
|
selectedIndex
|
|
874
1174
|
}) {
|
|
875
|
-
if (chapters.length === 0) return /* @__PURE__ */
|
|
876
|
-
return /* @__PURE__ */
|
|
1175
|
+
if (chapters.length === 0) return /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "No chapters" });
|
|
1176
|
+
return /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
877
1177
|
chapters.slice(win.start, win.end).map((chapter, i) => {
|
|
878
1178
|
const index = win.start + i;
|
|
879
1179
|
const selected = index === selectedIndex;
|
|
880
|
-
return /* @__PURE__ */
|
|
881
|
-
/* @__PURE__ */
|
|
882
|
-
/* @__PURE__ */
|
|
883
|
-
/* @__PURE__ */
|
|
1180
|
+
return /* @__PURE__ */ jsxs10(Text11, { wrap: "truncate-end", children: [
|
|
1181
|
+
/* @__PURE__ */ jsx13(Text11, { color: "cyan", children: selected ? "\u25B8 " : " " }),
|
|
1182
|
+
/* @__PURE__ */ jsx13(Text11, { color: "blue", children: `[${formatClockMs(chapter.startMs)}] ` }),
|
|
1183
|
+
/* @__PURE__ */ jsx13(Text11, { bold: selected, children: chapter.title })
|
|
884
1184
|
] }, index);
|
|
885
1185
|
}),
|
|
886
|
-
win.end < chapters.length || win.start > 0 ? /* @__PURE__ */
|
|
1186
|
+
win.end < chapters.length || win.start > 0 ? /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: ` ${selectedIndex + 1} / ${chapters.length}` }) : null
|
|
887
1187
|
] });
|
|
888
1188
|
}
|
|
889
1189
|
function TranscriptPane({
|
|
890
1190
|
segments,
|
|
891
1191
|
win
|
|
892
1192
|
}) {
|
|
893
|
-
if (segments.length === 0) return /* @__PURE__ */
|
|
894
|
-
return /* @__PURE__ */
|
|
895
|
-
segments.slice(win.start, win.end).map((seg, i) => /* @__PURE__ */
|
|
896
|
-
/* @__PURE__ */
|
|
897
|
-
seg.speaker ? /* @__PURE__ */
|
|
898
|
-
/* @__PURE__ */
|
|
1193
|
+
if (segments.length === 0) return /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "(no segments)" });
|
|
1194
|
+
return /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
1195
|
+
segments.slice(win.start, win.end).map((seg, i) => /* @__PURE__ */ jsxs10(Text11, { children: [
|
|
1196
|
+
/* @__PURE__ */ jsx13(Text11, { color: "blue", children: `[${formatClockMs(seg.startMs)}] ` }),
|
|
1197
|
+
seg.speaker ? /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: `${seg.speaker} ` }) : null,
|
|
1198
|
+
/* @__PURE__ */ jsx13(Text11, { children: seg.text })
|
|
899
1199
|
] }, win.start + i)),
|
|
900
|
-
win.maxScroll > 0 ? /* @__PURE__ */
|
|
1200
|
+
win.maxScroll > 0 ? /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: ` ${win.start + 1}\u2013${win.end} / ${segments.length}` }) : null
|
|
901
1201
|
] });
|
|
902
1202
|
}
|
|
903
1203
|
var TAB_ORDER, TAB_LABEL;
|
|
@@ -918,8 +1218,8 @@ var init_RecordingDetailView = __esm({
|
|
|
918
1218
|
|
|
919
1219
|
// src/tui/TranscriptView.tsx
|
|
920
1220
|
import { useMemo as useMemo3, useState as useState4 } from "react";
|
|
921
|
-
import { Box as
|
|
922
|
-
import { jsx as
|
|
1221
|
+
import { Box as Box12, Text as Text12, useInput as useInput4 } from "ink";
|
|
1222
|
+
import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
923
1223
|
function TranscriptView({ loading, data, error: error51 }) {
|
|
924
1224
|
const size = useTerminalSize();
|
|
925
1225
|
const [scroll, setScroll] = useState4(0);
|
|
@@ -944,42 +1244,42 @@ function TranscriptView({ loading, data, error: error51 }) {
|
|
|
944
1244
|
else if (input === "G") setScroll(win.maxScroll);
|
|
945
1245
|
});
|
|
946
1246
|
if (loading) {
|
|
947
|
-
return /* @__PURE__ */
|
|
1247
|
+
return /* @__PURE__ */ jsx14(Box12, { paddingX: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Loading transcript\u2026" }) });
|
|
948
1248
|
}
|
|
949
1249
|
if (error51) {
|
|
950
|
-
return /* @__PURE__ */
|
|
951
|
-
/* @__PURE__ */
|
|
1250
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingX: 1, children: [
|
|
1251
|
+
/* @__PURE__ */ jsxs11(Text12, { color: "red", children: [
|
|
952
1252
|
"! ",
|
|
953
1253
|
error51
|
|
954
1254
|
] }),
|
|
955
|
-
/* @__PURE__ */
|
|
1255
|
+
/* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "q / esc / \u2190 back" })
|
|
956
1256
|
] });
|
|
957
1257
|
}
|
|
958
1258
|
if (!data) {
|
|
959
|
-
return /* @__PURE__ */
|
|
1259
|
+
return /* @__PURE__ */ jsx14(Box12, { paddingX: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "No transcript." }) });
|
|
960
1260
|
}
|
|
961
1261
|
const title = data.summary?.title ?? "Transcript";
|
|
962
1262
|
const total = segments.length;
|
|
963
1263
|
const more = win.maxScroll > 0;
|
|
964
1264
|
const position = total === 0 ? "" : `${win.start + 1}\u2013${win.end} / ${total}`;
|
|
965
|
-
return /* @__PURE__ */
|
|
966
|
-
/* @__PURE__ */
|
|
1265
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingX: 1, children: [
|
|
1266
|
+
/* @__PURE__ */ jsxs11(Text12, { bold: true, color: "magenta", children: [
|
|
967
1267
|
title,
|
|
968
|
-
more ? /* @__PURE__ */
|
|
1268
|
+
more ? /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: ` ${position}` }) : null
|
|
969
1269
|
] }),
|
|
970
|
-
/* @__PURE__ */
|
|
971
|
-
/* @__PURE__ */
|
|
1270
|
+
/* @__PURE__ */ jsx14(Box12, { marginTop: 1, flexDirection: "column", children: total === 0 ? /* @__PURE__ */ jsx14(Text12, { children: data.text }) : segments.slice(win.start, win.end).map((segment, index) => /* @__PURE__ */ jsxs11(Text12, { children: [
|
|
1271
|
+
/* @__PURE__ */ jsxs11(Text12, { dimColor: true, children: [
|
|
972
1272
|
"[",
|
|
973
1273
|
formatClockMs(segment.startMs),
|
|
974
1274
|
"] "
|
|
975
1275
|
] }),
|
|
976
|
-
segment.speaker ? /* @__PURE__ */
|
|
1276
|
+
segment.speaker ? /* @__PURE__ */ jsxs11(Text12, { color: "cyan", children: [
|
|
977
1277
|
segment.speaker,
|
|
978
1278
|
": "
|
|
979
1279
|
] }) : null,
|
|
980
1280
|
segment.text
|
|
981
1281
|
] }, win.start + index)) }),
|
|
982
|
-
/* @__PURE__ */
|
|
1282
|
+
/* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsxs11(Text12, { dimColor: true, children: [
|
|
983
1283
|
more ? "\u2191\u2193 scroll \xB7 PgUp/PgDn \xB7 g/G top/bottom \xB7 " : "",
|
|
984
1284
|
"q / esc / \u2190 back"
|
|
985
1285
|
] }) })
|
|
@@ -995,15 +1295,17 @@ var init_TranscriptView = __esm({
|
|
|
995
1295
|
|
|
996
1296
|
// src/tui/AppShell.tsx
|
|
997
1297
|
import { useCallback, useEffect as useEffect2, useState as useState5 } from "react";
|
|
998
|
-
import { Box as
|
|
999
|
-
import { jsx as
|
|
1298
|
+
import { Box as Box13, Text as Text13, useApp, useInput as useInput5 } from "ink";
|
|
1299
|
+
import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1000
1300
|
function AppShell({
|
|
1001
1301
|
fetchJobs,
|
|
1002
1302
|
fetchTranscript,
|
|
1003
1303
|
fetchRecordings,
|
|
1004
1304
|
fetchDashboardStats,
|
|
1305
|
+
fetchAccountStatus,
|
|
1005
1306
|
recordingAudio,
|
|
1006
1307
|
listDownloadedRecordingIds,
|
|
1308
|
+
startLiveRecord,
|
|
1007
1309
|
initialView = "overview",
|
|
1008
1310
|
openUrl: openUrl2,
|
|
1009
1311
|
copyText: copyText2,
|
|
@@ -1018,6 +1320,7 @@ function AppShell({
|
|
|
1018
1320
|
const [recordingsNextCursor, setRecordingsNextCursor] = useState5(null);
|
|
1019
1321
|
const [recordingsTotalCount, setRecordingsTotalCount] = useState5(void 0);
|
|
1020
1322
|
const [stats, setStats] = useState5(void 0);
|
|
1323
|
+
const [accountStatus, setAccountStatus] = useState5("loading");
|
|
1021
1324
|
const [origin, setOrigin] = useState5("");
|
|
1022
1325
|
const [stack, setStack] = useState5([{ kind: initialView }]);
|
|
1023
1326
|
const [selected, setSelected] = useState5(0);
|
|
@@ -1031,6 +1334,7 @@ function AppShell({
|
|
|
1031
1334
|
);
|
|
1032
1335
|
const [audioCache, setAudioCache] = useState5(() => /* @__PURE__ */ new Map());
|
|
1033
1336
|
const [downloadedIds, setDownloadedIds] = useState5(() => /* @__PURE__ */ new Set());
|
|
1337
|
+
const [liveRecord, setLiveRecord] = useState5(void 0);
|
|
1034
1338
|
const refreshDownloadedIds = useCallback(async () => {
|
|
1035
1339
|
if (!listDownloadedRecordingIds) return;
|
|
1036
1340
|
try {
|
|
@@ -1042,6 +1346,42 @@ function AppShell({
|
|
|
1042
1346
|
void refreshDownloadedIds();
|
|
1043
1347
|
}, [refreshDownloadedIds]);
|
|
1044
1348
|
const screen = stack[stack.length - 1];
|
|
1349
|
+
const stopLiveRecord = useCallback(async () => {
|
|
1350
|
+
const current = liveRecord;
|
|
1351
|
+
if (current?.kind === "live") {
|
|
1352
|
+
setLiveRecord({ kind: "starting" });
|
|
1353
|
+
try {
|
|
1354
|
+
await current.session.stop();
|
|
1355
|
+
} catch (error51) {
|
|
1356
|
+
setNotice(error51 instanceof Error ? error51.message : String(error51));
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
setLiveRecord(void 0);
|
|
1360
|
+
setStack([{ kind: "overview" }]);
|
|
1361
|
+
}, [liveRecord]);
|
|
1362
|
+
useEffect2(() => {
|
|
1363
|
+
if (screen.kind !== "record") return;
|
|
1364
|
+
if (!startLiveRecord) {
|
|
1365
|
+
setLiveRecord({ kind: "error", message: "Live recording is not available" });
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
let cancelled = false;
|
|
1369
|
+
setLiveRecord({ kind: "starting" });
|
|
1370
|
+
startLiveRecord().then((session) => {
|
|
1371
|
+
if (cancelled) {
|
|
1372
|
+
void session.stop();
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
setLiveRecord({ kind: "live", session });
|
|
1376
|
+
}).catch((error51) => {
|
|
1377
|
+
if (!cancelled) {
|
|
1378
|
+
setLiveRecord({ kind: "error", message: error51 instanceof Error ? error51.message : String(error51) });
|
|
1379
|
+
}
|
|
1380
|
+
});
|
|
1381
|
+
return () => {
|
|
1382
|
+
cancelled = true;
|
|
1383
|
+
};
|
|
1384
|
+
}, [screen.kind, startLiveRecord]);
|
|
1045
1385
|
const selectedRecording = screen.kind === "overview" ? recordings[selected] : void 0;
|
|
1046
1386
|
const peekTranscriptId = selectedRecording?.activeTranscriptId ?? void 0;
|
|
1047
1387
|
useEffect2(() => {
|
|
@@ -1062,10 +1402,11 @@ function AppShell({
|
|
|
1062
1402
|
}, [peekTranscriptId, fetchTranscript]);
|
|
1063
1403
|
const peekSummary = peekTranscriptId ? summaryCache.get(peekTranscriptId) : void 0;
|
|
1064
1404
|
const refresh = useCallback(async ({ resetRecordings = false } = {}) => {
|
|
1065
|
-
const [jobsR, recR, statsR] = await Promise.allSettled([
|
|
1405
|
+
const [jobsR, recR, statsR, accountR] = await Promise.allSettled([
|
|
1066
1406
|
fetchJobs(),
|
|
1067
1407
|
resetRecordings && fetchRecordings ? fetchRecordings({ limit: RECORDINGS_PAGE_SIZE }) : Promise.resolve(void 0),
|
|
1068
|
-
fetchDashboardStats ? fetchDashboardStats() : Promise.resolve(void 0)
|
|
1408
|
+
fetchDashboardStats ? fetchDashboardStats() : Promise.resolve(void 0),
|
|
1409
|
+
fetchAccountStatus ? fetchAccountStatus() : Promise.resolve(void 0)
|
|
1069
1410
|
]);
|
|
1070
1411
|
if (jobsR.status === "fulfilled") {
|
|
1071
1412
|
setJobs(jobsR.value.items);
|
|
@@ -1080,7 +1421,12 @@ function AppShell({
|
|
|
1080
1421
|
setRecordingsTotalCount(recR.value.totalCount);
|
|
1081
1422
|
}
|
|
1082
1423
|
if (statsR.status === "fulfilled" && statsR.value) setStats(statsR.value);
|
|
1083
|
-
|
|
1424
|
+
if (accountR.status === "fulfilled") {
|
|
1425
|
+
setAccountStatus(accountR.value);
|
|
1426
|
+
} else {
|
|
1427
|
+
setAccountStatus("error");
|
|
1428
|
+
}
|
|
1429
|
+
}, [fetchJobs, fetchRecordings, fetchDashboardStats, fetchAccountStatus]);
|
|
1084
1430
|
const loadMoreRecordings = useCallback(async () => {
|
|
1085
1431
|
if (!fetchRecordings || !recordingsNextCursor || loadingMoreRecordings) return;
|
|
1086
1432
|
setLoadingMoreRecordings(true);
|
|
@@ -1125,7 +1471,7 @@ function AppShell({
|
|
|
1125
1471
|
jobStatusByRecording.set(job.recordingId, job.status);
|
|
1126
1472
|
}
|
|
1127
1473
|
}
|
|
1128
|
-
const listLength = screen.kind === "jobs" ? jobs.length : recordings.length;
|
|
1474
|
+
const listLength = screen.kind === "jobs" ? jobs.length : screen.kind === "overview" ? recordings.length : 0;
|
|
1129
1475
|
useEffect2(() => {
|
|
1130
1476
|
setSelected((i) => Math.max(0, Math.min(i, Math.max(0, listLength - 1))));
|
|
1131
1477
|
}, [listLength]);
|
|
@@ -1212,10 +1558,16 @@ function AppShell({
|
|
|
1212
1558
|
const back = () => setStack((st) => st.length > 1 ? st.slice(0, -1) : st);
|
|
1213
1559
|
useInput5((input, key) => {
|
|
1214
1560
|
setNotice(void 0);
|
|
1561
|
+
if (screen.kind === "record") {
|
|
1562
|
+
if (input === "q" || key.escape || key.leftArrow) void stopLiveRecord();
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1215
1565
|
if (input === "q") return exit();
|
|
1216
1566
|
if (key.escape || key.leftArrow) return back();
|
|
1217
1567
|
if (input === "1") return goTab("overview");
|
|
1218
1568
|
if (input === "2") return goTab("jobs");
|
|
1569
|
+
if (input === "3") return goTab("account");
|
|
1570
|
+
if (input === "4") return goTab("record");
|
|
1219
1571
|
if (input === "r") return void refresh({ resetRecordings: true });
|
|
1220
1572
|
if (screen.kind === "overview") {
|
|
1221
1573
|
if (key.upArrow || input === "k") setSelected((i) => Math.max(0, i - 1));
|
|
@@ -1262,18 +1614,18 @@ function AppShell({
|
|
|
1262
1614
|
}
|
|
1263
1615
|
});
|
|
1264
1616
|
if (screen.kind === "transcript") {
|
|
1265
|
-
return /* @__PURE__ */
|
|
1617
|
+
return /* @__PURE__ */ jsx15(TranscriptView, { loading: screen.loading, data: screen.data, error: screen.error });
|
|
1266
1618
|
}
|
|
1267
1619
|
if (screen.kind === "jobDetail") {
|
|
1268
1620
|
const job = jobs.find((j) => j.jobId === screen.jobId);
|
|
1269
|
-
if (!job) return /* @__PURE__ */
|
|
1270
|
-
return /* @__PURE__ */
|
|
1621
|
+
if (!job) return /* @__PURE__ */ jsx15(Missing, { label: "Job" });
|
|
1622
|
+
return /* @__PURE__ */ jsx15(Detail, { notice, children: /* @__PURE__ */ jsx15(JobDetailView, { item: job, origin, spinnerFrame, nowMs: now() }) });
|
|
1271
1623
|
}
|
|
1272
1624
|
if (screen.kind === "recordingDetail") {
|
|
1273
1625
|
const rec = recordings.find((r) => r.recordingId === screen.recordingId);
|
|
1274
|
-
if (!rec) return /* @__PURE__ */
|
|
1626
|
+
if (!rec) return /* @__PURE__ */ jsx15(Missing, { label: "Recording" });
|
|
1275
1627
|
const detailTranscript = rec.activeTranscriptId ? transcriptCache.get(rec.activeTranscriptId) : void 0;
|
|
1276
|
-
return /* @__PURE__ */
|
|
1628
|
+
return /* @__PURE__ */ jsx15(Detail, { notice, children: /* @__PURE__ */ jsx15(
|
|
1277
1629
|
RecordingDetailView,
|
|
1278
1630
|
{
|
|
1279
1631
|
item: rec,
|
|
@@ -1283,7 +1635,20 @@ function AppShell({
|
|
|
1283
1635
|
}
|
|
1284
1636
|
) });
|
|
1285
1637
|
}
|
|
1286
|
-
|
|
1638
|
+
if (screen.kind === "record") {
|
|
1639
|
+
if (liveRecord?.kind === "live") {
|
|
1640
|
+
return /* @__PURE__ */ jsx15(LiveCaptionsScreen, { source: liveRecord.session.source, now });
|
|
1641
|
+
}
|
|
1642
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
|
|
1643
|
+
/* @__PURE__ */ jsx15(Header, { active: "record" }),
|
|
1644
|
+
/* @__PURE__ */ jsx15(Box13, { flexGrow: 1, flexDirection: "column", paddingX: 1, paddingTop: 1, children: liveRecord?.kind === "error" ? /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
1645
|
+
/* @__PURE__ */ jsx15(Text13, { color: "red", children: "Couldn't start live recording" }),
|
|
1646
|
+
/* @__PURE__ */ jsx15(Text13, { dimColor: true, children: liveRecord.message })
|
|
1647
|
+
] }) : /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Starting live recording\u2026" }) }),
|
|
1648
|
+
/* @__PURE__ */ jsx15(Footer, { keys: "q / esc / \u2190 back" })
|
|
1649
|
+
] });
|
|
1650
|
+
}
|
|
1651
|
+
const tab = screen.kind === "jobs" ? "jobs" : screen.kind === "account" ? "account" : "overview";
|
|
1287
1652
|
let body;
|
|
1288
1653
|
let position = "";
|
|
1289
1654
|
if (screen.kind === "overview") {
|
|
@@ -1298,7 +1663,7 @@ function AppShell({
|
|
|
1298
1663
|
const showPeek = size.columns >= 100;
|
|
1299
1664
|
const peekWidth = showPeek ? 34 : 0;
|
|
1300
1665
|
const listColumns = showPeek ? Math.max(30, size.columns - peekWidth - 3) : size.columns;
|
|
1301
|
-
body = /* @__PURE__ */
|
|
1666
|
+
body = /* @__PURE__ */ jsx15(
|
|
1302
1667
|
OverviewView,
|
|
1303
1668
|
{
|
|
1304
1669
|
recordings: recordings.slice(win.start, win.end),
|
|
@@ -1316,10 +1681,13 @@ function AppShell({
|
|
|
1316
1681
|
peekWidth
|
|
1317
1682
|
}
|
|
1318
1683
|
);
|
|
1684
|
+
} else if (screen.kind === "account") {
|
|
1685
|
+
position = "";
|
|
1686
|
+
body = /* @__PURE__ */ jsx15(AccountView, { status: accountStatus });
|
|
1319
1687
|
} else {
|
|
1320
1688
|
const win = listWindow(selected, jobs.length, Math.max(3, size.rows - 4));
|
|
1321
1689
|
position = jobs.length ? `${selected + 1} / ${jobs.length}` : "0";
|
|
1322
|
-
body = /* @__PURE__ */
|
|
1690
|
+
body = /* @__PURE__ */ jsx15(
|
|
1323
1691
|
JobsView,
|
|
1324
1692
|
{
|
|
1325
1693
|
items: jobs.slice(win.start, win.end),
|
|
@@ -1328,47 +1696,49 @@ function AppShell({
|
|
|
1328
1696
|
}
|
|
1329
1697
|
);
|
|
1330
1698
|
}
|
|
1331
|
-
const footerKeys = screen.kind === "jobs" ? `${position} \xB7 \u2191\u2193 select \xB7 \u23CE job \xB7 t transcript \xB7 1 overview \xB7 r refresh \xB7 q quit` : `${position} \xB7 \u2191\u2193 scroll \xB7 \u23CE open \xB7 t transcript \xB7 2 jobs \xB7 r refresh \xB7 q quit`;
|
|
1332
|
-
return /* @__PURE__ */
|
|
1333
|
-
/* @__PURE__ */
|
|
1334
|
-
/* @__PURE__ */
|
|
1699
|
+
const footerKeys = screen.kind === "jobs" ? `${position} \xB7 \u2191\u2193 select \xB7 \u23CE job \xB7 t transcript \xB7 1 overview \xB7 3 account \xB7 4 record \xB7 r refresh \xB7 q quit` : screen.kind === "account" ? "3 account \xB7 1 overview \xB7 2 jobs \xB7 4 record \xB7 r refresh \xB7 q quit" : `${position} \xB7 \u2191\u2193 scroll \xB7 \u23CE open \xB7 t transcript \xB7 2 jobs \xB7 3 account \xB7 4 record \xB7 r refresh \xB7 q quit`;
|
|
1700
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
|
|
1701
|
+
/* @__PURE__ */ jsx15(Header, { active: tab }),
|
|
1702
|
+
/* @__PURE__ */ jsxs12(Box13, { flexGrow: 1, flexDirection: "column", children: [
|
|
1335
1703
|
body,
|
|
1336
|
-
loadError && jobs.length === 0 && recordings.length === 0 ? /* @__PURE__ */
|
|
1704
|
+
loadError && jobs.length === 0 && recordings.length === 0 ? /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { color: "red", children: [
|
|
1337
1705
|
"! ",
|
|
1338
1706
|
loadError
|
|
1339
1707
|
] }) }) : null
|
|
1340
1708
|
] }),
|
|
1341
|
-
/* @__PURE__ */
|
|
1709
|
+
/* @__PURE__ */ jsx15(Footer, { keys: footerKeys })
|
|
1342
1710
|
] });
|
|
1343
1711
|
}
|
|
1344
1712
|
function Detail({
|
|
1345
1713
|
notice,
|
|
1346
1714
|
children
|
|
1347
1715
|
}) {
|
|
1348
|
-
return /* @__PURE__ */
|
|
1716
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
|
|
1349
1717
|
children,
|
|
1350
|
-
notice ? /* @__PURE__ */
|
|
1718
|
+
notice ? /* @__PURE__ */ jsx15(Box13, { paddingX: 1, children: /* @__PURE__ */ jsx15(Text13, { color: "green", children: notice }) }) : null
|
|
1351
1719
|
] });
|
|
1352
1720
|
}
|
|
1353
1721
|
function Missing({ label }) {
|
|
1354
|
-
return /* @__PURE__ */
|
|
1355
|
-
/* @__PURE__ */
|
|
1722
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 1, children: [
|
|
1723
|
+
/* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
|
|
1356
1724
|
label,
|
|
1357
1725
|
" no longer in the list."
|
|
1358
1726
|
] }),
|
|
1359
|
-
/* @__PURE__ */
|
|
1727
|
+
/* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "esc back \xB7 q quit" })
|
|
1360
1728
|
] });
|
|
1361
1729
|
}
|
|
1362
1730
|
var RECORDINGS_PAGE_SIZE, RECORDINGS_PREFETCH_REMAINING;
|
|
1363
1731
|
var init_AppShell = __esm({
|
|
1364
1732
|
"src/tui/AppShell.tsx"() {
|
|
1365
1733
|
"use strict";
|
|
1734
|
+
init_AccountView();
|
|
1366
1735
|
init_chrome();
|
|
1367
1736
|
init_JobsView();
|
|
1368
1737
|
init_OverviewView();
|
|
1369
1738
|
init_JobDetailView();
|
|
1370
1739
|
init_RecordingDetailView();
|
|
1371
1740
|
init_TranscriptView();
|
|
1741
|
+
init_LiveCaptionsScreen();
|
|
1372
1742
|
init_format();
|
|
1373
1743
|
init_terminal();
|
|
1374
1744
|
RECORDINGS_PAGE_SIZE = 50;
|
|
@@ -1413,8 +1783,10 @@ async function runDashboard(deps) {
|
|
|
1413
1783
|
fetchTranscript: deps.fetchTranscript,
|
|
1414
1784
|
fetchRecordings: deps.fetchRecordings,
|
|
1415
1785
|
fetchDashboardStats: deps.fetchDashboardStats,
|
|
1786
|
+
fetchAccountStatus: deps.fetchAccountStatus,
|
|
1416
1787
|
recordingAudio: deps.recordingAudio,
|
|
1417
1788
|
listDownloadedRecordingIds: deps.listDownloadedRecordingIds,
|
|
1789
|
+
startLiveRecord: deps.startLiveRecord,
|
|
1418
1790
|
initialView: deps.initialView ?? "overview",
|
|
1419
1791
|
openUrl,
|
|
1420
1792
|
copyText
|
|
@@ -19145,227 +19517,64 @@ function defaultSidecarHandshakeParams(params) {
|
|
|
19145
19517
|
};
|
|
19146
19518
|
}
|
|
19147
19519
|
|
|
19148
|
-
// src/
|
|
19149
|
-
|
|
19150
|
-
|
|
19151
|
-
|
|
19152
|
-
|
|
19153
|
-
|
|
19154
|
-
|
|
19155
|
-
|
|
19156
|
-
|
|
19157
|
-
|
|
19158
|
-
|
|
19159
|
-
|
|
19160
|
-
|
|
19161
|
-
|
|
19162
|
-
|
|
19163
|
-
case "status": {
|
|
19164
|
-
const next = { ...state, status: event.status };
|
|
19165
|
-
if (event.status === "live" && state.startedAtMs == null) {
|
|
19166
|
-
next.startedAtMs = event.atMs ?? state.startedAtMs;
|
|
19167
|
-
}
|
|
19168
|
-
if (event.status !== "error") next.error = void 0;
|
|
19169
|
-
return next;
|
|
19170
|
-
}
|
|
19171
|
-
case "partial":
|
|
19172
|
-
return { ...state, partial: event.text };
|
|
19173
|
-
case "final": {
|
|
19174
|
-
const lines = [...state.lines, event.line];
|
|
19175
|
-
return { ...state, lines, partial: void 0 };
|
|
19520
|
+
// src/record.tsx
|
|
19521
|
+
init_LiveCaptionsScreen();
|
|
19522
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
19523
|
+
var SIDECAR_COMMAND_ENV = "RECAPPI_MINI_SIDECAR";
|
|
19524
|
+
async function recordViaSidecar(opts) {
|
|
19525
|
+
let liveRenderer;
|
|
19526
|
+
let session;
|
|
19527
|
+
try {
|
|
19528
|
+
session = await startRecordSession(opts);
|
|
19529
|
+
if (opts.renderLive) {
|
|
19530
|
+
liveRenderer = opts.runtime?.createLiveRenderer?.(session.source) ?? createInkLiveRenderer({
|
|
19531
|
+
source: session.source,
|
|
19532
|
+
renderApp: opts.runtime?.renderApp,
|
|
19533
|
+
now: opts.runtime?.now
|
|
19534
|
+
});
|
|
19176
19535
|
}
|
|
19177
|
-
|
|
19178
|
-
|
|
19179
|
-
|
|
19180
|
-
|
|
19181
|
-
let idx = event.segmentId ? lines.findIndex((l) => l.id === event.segmentId) : -1;
|
|
19182
|
-
if (idx < 0) idx = lines.length - 1;
|
|
19183
|
-
if (idx >= 0) lines[idx] = { ...lines[idx], translation: event.text };
|
|
19184
|
-
return { ...state, lines, translationPartial: void 0 };
|
|
19536
|
+
if (liveRenderer) {
|
|
19537
|
+
await liveRenderer.waitUntilStop();
|
|
19538
|
+
} else {
|
|
19539
|
+
await (opts.runtime?.waitForStop ?? waitForStopSignal)();
|
|
19185
19540
|
}
|
|
19186
|
-
|
|
19187
|
-
|
|
19188
|
-
|
|
19189
|
-
|
|
19190
|
-
|
|
19191
|
-
}
|
|
19192
|
-
function recordingStateToStatus(state) {
|
|
19193
|
-
switch (state) {
|
|
19194
|
-
case "idle":
|
|
19195
|
-
case "starting":
|
|
19196
|
-
return "connecting";
|
|
19197
|
-
case "recording":
|
|
19198
|
-
return "live";
|
|
19199
|
-
case "stopping":
|
|
19200
|
-
case "finalizing":
|
|
19201
|
-
case "uploading":
|
|
19202
|
-
case "completed":
|
|
19203
|
-
case "cancelled":
|
|
19204
|
-
return "stopped";
|
|
19205
|
-
case "failed":
|
|
19206
|
-
return "error";
|
|
19207
|
-
}
|
|
19208
|
-
}
|
|
19209
|
-
function sidecarToLiveCaptionEvent(event) {
|
|
19210
|
-
switch (event.type) {
|
|
19211
|
-
case "ready":
|
|
19212
|
-
return { kind: "status", status: "connecting" };
|
|
19213
|
-
case "recording.state":
|
|
19214
|
-
if (event.state === "failed") {
|
|
19215
|
-
return { kind: "error", message: event.message ?? "Recording failed" };
|
|
19216
|
-
}
|
|
19217
|
-
return { kind: "status", status: recordingStateToStatus(event.state) };
|
|
19218
|
-
case "live_caption.delta": {
|
|
19219
|
-
if (event.stream === "translation") {
|
|
19220
|
-
return event.isFinal ? { kind: "translationFinal", segmentId: event.segmentId, text: event.text } : { kind: "translationPartial", text: event.text };
|
|
19221
|
-
}
|
|
19222
|
-
if (event.isFinal) {
|
|
19223
|
-
return {
|
|
19224
|
-
kind: "final",
|
|
19225
|
-
line: {
|
|
19226
|
-
id: event.segmentId ?? `${event.startMs ?? event.atMs ?? 0}`,
|
|
19227
|
-
text: event.text,
|
|
19228
|
-
speaker: event.speaker,
|
|
19229
|
-
atMs: event.startMs ?? event.atMs
|
|
19230
|
-
}
|
|
19231
|
-
};
|
|
19541
|
+
return await session.stop();
|
|
19542
|
+
} catch (error51) {
|
|
19543
|
+
if (session) {
|
|
19544
|
+
try {
|
|
19545
|
+
await session.cancel();
|
|
19546
|
+
} catch {
|
|
19232
19547
|
}
|
|
19233
|
-
return { kind: "partial", text: event.text };
|
|
19234
19548
|
}
|
|
19235
|
-
|
|
19236
|
-
|
|
19237
|
-
|
|
19238
|
-
|
|
19239
|
-
return null;
|
|
19240
|
-
default:
|
|
19241
|
-
return null;
|
|
19242
|
-
}
|
|
19243
|
-
}
|
|
19244
|
-
function liveCaptionStatusLabel(status) {
|
|
19245
|
-
switch (status) {
|
|
19246
|
-
case "connecting":
|
|
19247
|
-
return "Connecting\u2026";
|
|
19248
|
-
case "live":
|
|
19249
|
-
return "\u25CF LIVE";
|
|
19250
|
-
case "reconnecting":
|
|
19251
|
-
return "Reconnecting\u2026";
|
|
19252
|
-
case "stopped":
|
|
19253
|
-
return "Stopped";
|
|
19254
|
-
case "error":
|
|
19255
|
-
return "Error";
|
|
19549
|
+
throw error51;
|
|
19550
|
+
} finally {
|
|
19551
|
+
liveRenderer?.close();
|
|
19552
|
+
session?.close();
|
|
19256
19553
|
}
|
|
19257
19554
|
}
|
|
19258
|
-
|
|
19259
|
-
|
|
19260
|
-
|
|
19261
|
-
|
|
19262
|
-
|
|
19263
|
-
|
|
19264
|
-
reconnecting: "yellow",
|
|
19265
|
-
stopped: "gray",
|
|
19266
|
-
error: "red"
|
|
19267
|
-
};
|
|
19268
|
-
function LiveCaptionsView({
|
|
19269
|
-
state,
|
|
19270
|
-
nowMs
|
|
19271
|
-
}) {
|
|
19272
|
-
const size = useTerminalSize();
|
|
19273
|
-
const [scrollUp, setScrollUp] = useState(0);
|
|
19274
|
-
const innerWidth = Math.max(10, size.columns - 2);
|
|
19275
|
-
const items = useMemo(() => {
|
|
19276
|
-
const rows = [];
|
|
19277
|
-
for (const l of state.lines) {
|
|
19278
|
-
rows.push({ key: l.id, kind: "final", speaker: l.speaker, text: l.text });
|
|
19279
|
-
if (l.translation) rows.push({ key: `${l.id}__t`, kind: "translation", text: l.translation });
|
|
19280
|
-
}
|
|
19281
|
-
if (state.partial && state.partial.length > 0) {
|
|
19282
|
-
rows.push({ key: "__partial__", kind: "partial", text: state.partial });
|
|
19283
|
-
}
|
|
19284
|
-
if (state.translationPartial && state.translationPartial.length > 0) {
|
|
19285
|
-
rows.push({ key: "__tpartial__", kind: "translation", text: state.translationPartial });
|
|
19555
|
+
async function startLiveRecordSession(opts) {
|
|
19556
|
+
const session = await startRecordSession({ ...opts, live: true });
|
|
19557
|
+
return {
|
|
19558
|
+
source: session.source,
|
|
19559
|
+
stop: async () => {
|
|
19560
|
+
await session.stop();
|
|
19286
19561
|
}
|
|
19287
|
-
|
|
19288
|
-
}, [state.lines, state.partial, state.translationPartial]);
|
|
19289
|
-
const heights = useMemo(
|
|
19290
|
-
() => items.map((it) => {
|
|
19291
|
-
const prefix = it.kind === "translation" ? "\u21B3 " : it.speaker ? `${it.speaker}: ` : "";
|
|
19292
|
-
return Math.max(1, Math.ceil(displayWidth(prefix + it.text) / innerWidth));
|
|
19293
|
-
}),
|
|
19294
|
-
[items, innerWidth]
|
|
19295
|
-
);
|
|
19296
|
-
const budget = Math.max(3, size.rows - 3);
|
|
19297
|
-
const maxScroll = windowByHeights(heights, Number.MAX_SAFE_INTEGER, budget).maxScroll;
|
|
19298
|
-
const top = Math.max(0, maxScroll - scrollUp);
|
|
19299
|
-
const win = windowByHeights(heights, top, budget);
|
|
19300
|
-
const following = scrollUp === 0;
|
|
19301
|
-
const page = Math.max(1, budget - 1);
|
|
19302
|
-
useInput((input, key) => {
|
|
19303
|
-
if (key.upArrow || input === "k") setScrollUp((s) => Math.min(maxScroll, s + 1));
|
|
19304
|
-
else if (key.downArrow || input === "j") setScrollUp((s) => Math.max(0, s - 1));
|
|
19305
|
-
else if (key.pageUp || input === "b") setScrollUp((s) => Math.min(maxScroll, s + page));
|
|
19306
|
-
else if (key.pageDown || input === " ") setScrollUp((s) => Math.max(0, s - page));
|
|
19307
|
-
else if (input === "G") setScrollUp(0);
|
|
19308
|
-
else if (input === "g") setScrollUp(maxScroll);
|
|
19309
|
-
});
|
|
19310
|
-
const elapsed = state.startedAtMs != null ? formatClockMs(Math.max(0, nowMs - state.startedAtMs)) : null;
|
|
19311
|
-
const statusColor = STATUS_COLOR[state.status] ?? "white";
|
|
19312
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
19313
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
19314
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: statusColor, children: liveCaptionStatusLabel(state.status) }),
|
|
19315
|
-
elapsed ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${elapsed}` }) : null,
|
|
19316
|
-
!following ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u23F8 scrolled \u2014 G for live" }) : null
|
|
19317
|
-
] }),
|
|
19318
|
-
/* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: items.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: state.status === "error" ? state.error ? `Error: ${state.error}` : "Live captions error" : "Waiting for captions\u2026" }) : items.slice(win.start, win.end).map(
|
|
19319
|
-
(it) => it.kind === "translation" ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: `\u21B3 ${it.text}` }, it.key) : /* @__PURE__ */ jsxs(Text, { dimColor: it.kind === "partial", italic: it.kind === "partial", children: [
|
|
19320
|
-
it.speaker ? /* @__PURE__ */ jsx(Text, { color: "cyan", children: `${it.speaker}: ` }) : null,
|
|
19321
|
-
it.text
|
|
19322
|
-
] }, it.key)
|
|
19323
|
-
) }),
|
|
19324
|
-
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
19325
|
-
maxScroll > 0 ? "\u2191\u2193 scroll \xB7 G live \xB7 " : "",
|
|
19326
|
-
"q / esc / \u2190 back"
|
|
19327
|
-
] }) })
|
|
19328
|
-
] });
|
|
19329
|
-
}
|
|
19330
|
-
|
|
19331
|
-
// src/tui/LiveCaptionsScreen.tsx
|
|
19332
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
19333
|
-
function LiveCaptionsScreen({
|
|
19334
|
-
source,
|
|
19335
|
-
now = () => Date.now()
|
|
19336
|
-
}) {
|
|
19337
|
-
const [state, setState] = useState2(initialLiveCaptionsState);
|
|
19338
|
-
const [tick, setTick] = useState2(() => now());
|
|
19339
|
-
useEffect(() => {
|
|
19340
|
-
const unsubscribe = source.onEvent((event) => {
|
|
19341
|
-
const mapped = sidecarToLiveCaptionEvent(event);
|
|
19342
|
-
if (mapped) setState((s) => liveCaptionReducer(s, mapped));
|
|
19343
|
-
});
|
|
19344
|
-
return unsubscribe;
|
|
19345
|
-
}, [source]);
|
|
19346
|
-
useEffect(() => {
|
|
19347
|
-
const id = setInterval(() => setTick(now()), 1e3);
|
|
19348
|
-
return () => clearInterval(id);
|
|
19349
|
-
}, []);
|
|
19350
|
-
return /* @__PURE__ */ jsx2(LiveCaptionsView, { state, nowMs: tick });
|
|
19562
|
+
};
|
|
19351
19563
|
}
|
|
19352
|
-
|
|
19353
|
-
// src/record.tsx
|
|
19354
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
19355
|
-
var SIDECAR_COMMAND_ENV = "RECAPPI_MINI_SIDECAR";
|
|
19356
|
-
async function recordViaSidecar(opts) {
|
|
19564
|
+
async function startRecordSession(opts) {
|
|
19357
19565
|
const command = resolveSidecarCommand(opts);
|
|
19358
19566
|
const sidecarArgs = opts.sidecarArgs ?? [];
|
|
19359
19567
|
const spawnSidecar = opts.runtime?.spawnSidecar ?? spawnMiniSidecar;
|
|
19360
19568
|
const sidecar = spawnSidecar({ command, args: sidecarArgs, env: opts.env });
|
|
19361
19569
|
const account = requireAccountPartition(opts.account);
|
|
19362
19570
|
const artifacts = [];
|
|
19363
|
-
let liveRenderer;
|
|
19364
19571
|
let handshake;
|
|
19365
19572
|
let sessionId;
|
|
19366
19573
|
let latestState;
|
|
19367
19574
|
let recordingId;
|
|
19368
19575
|
let localSessionRef;
|
|
19576
|
+
let stopPromise;
|
|
19577
|
+
let closed = false;
|
|
19369
19578
|
const unsubscribe = sidecar.client.onEvent((event) => {
|
|
19370
19579
|
if (event.type === "recording.state") {
|
|
19371
19580
|
latestState = event.state;
|
|
@@ -19376,14 +19585,22 @@ async function recordViaSidecar(opts) {
|
|
|
19376
19585
|
artifacts.push(event.artifact);
|
|
19377
19586
|
}
|
|
19378
19587
|
});
|
|
19379
|
-
|
|
19380
|
-
if (
|
|
19381
|
-
|
|
19382
|
-
|
|
19383
|
-
|
|
19384
|
-
|
|
19385
|
-
|
|
19588
|
+
const close = () => {
|
|
19589
|
+
if (closed) return;
|
|
19590
|
+
closed = true;
|
|
19591
|
+
unsubscribe();
|
|
19592
|
+
sidecar.kill();
|
|
19593
|
+
};
|
|
19594
|
+
const cancel = async () => {
|
|
19595
|
+
if (sessionId && latestState && latestState !== "completed" && latestState !== "cancelled") {
|
|
19596
|
+
try {
|
|
19597
|
+
await sidecar.client.cancelRecording({ sessionId });
|
|
19598
|
+
} catch {
|
|
19599
|
+
}
|
|
19386
19600
|
}
|
|
19601
|
+
close();
|
|
19602
|
+
};
|
|
19603
|
+
try {
|
|
19387
19604
|
handshake = await sidecar.client.handshake(
|
|
19388
19605
|
defaultSidecarHandshakeParams({
|
|
19389
19606
|
client: { name: "recappi-cli", version: opts.cliVersion },
|
|
@@ -19405,41 +19622,41 @@ async function recordViaSidecar(opts) {
|
|
|
19405
19622
|
sessionId = started.sessionId;
|
|
19406
19623
|
latestState = started.state;
|
|
19407
19624
|
localSessionRef = started.localSessionRef;
|
|
19408
|
-
|
|
19409
|
-
|
|
19410
|
-
|
|
19411
|
-
|
|
19412
|
-
|
|
19413
|
-
|
|
19414
|
-
|
|
19415
|
-
|
|
19416
|
-
|
|
19417
|
-
|
|
19418
|
-
|
|
19419
|
-
|
|
19420
|
-
|
|
19421
|
-
|
|
19422
|
-
|
|
19423
|
-
|
|
19424
|
-
|
|
19425
|
-
|
|
19426
|
-
|
|
19427
|
-
|
|
19428
|
-
|
|
19429
|
-
|
|
19430
|
-
|
|
19625
|
+
return {
|
|
19626
|
+
source: sidecar.client,
|
|
19627
|
+
stop: () => {
|
|
19628
|
+
stopPromise ??= (async () => {
|
|
19629
|
+
try {
|
|
19630
|
+
const stopped = await sidecar.client.stopRecording({ sessionId });
|
|
19631
|
+
latestState = stopped.state;
|
|
19632
|
+
recordingId = stopped.recordingId ?? recordingId;
|
|
19633
|
+
localSessionRef = stopped.localSessionRef ?? localSessionRef;
|
|
19634
|
+
artifacts.push(...stopped.artifacts ?? []);
|
|
19635
|
+
const uniqueArtifacts = dedupeArtifacts(artifacts);
|
|
19636
|
+
persistArtifacts(uniqueArtifacts, account, opts);
|
|
19637
|
+
return recordCommandDataSchema.parse({
|
|
19638
|
+
origin: account.backendOrigin,
|
|
19639
|
+
userId: account.userId,
|
|
19640
|
+
live: opts.live === true,
|
|
19641
|
+
sessionId: stopped.sessionId,
|
|
19642
|
+
state: stopped.state,
|
|
19643
|
+
...recordingId ? { recordingId } : {},
|
|
19644
|
+
...localSessionRef ? { localSessionRef } : {},
|
|
19645
|
+
...handshake?.sidecar ? { sidecar: handshake.sidecar } : {},
|
|
19646
|
+
artifacts: uniqueArtifacts
|
|
19647
|
+
});
|
|
19648
|
+
} finally {
|
|
19649
|
+
close();
|
|
19650
|
+
}
|
|
19651
|
+
})();
|
|
19652
|
+
return stopPromise;
|
|
19653
|
+
},
|
|
19654
|
+
cancel,
|
|
19655
|
+
close
|
|
19656
|
+
};
|
|
19431
19657
|
} catch (error51) {
|
|
19432
|
-
|
|
19433
|
-
try {
|
|
19434
|
-
await sidecar.client.cancelRecording({ sessionId });
|
|
19435
|
-
} catch {
|
|
19436
|
-
}
|
|
19437
|
-
}
|
|
19658
|
+
await cancel();
|
|
19438
19659
|
throw error51;
|
|
19439
|
-
} finally {
|
|
19440
|
-
unsubscribe();
|
|
19441
|
-
liveRenderer?.close();
|
|
19442
|
-
sidecar.kill();
|
|
19443
19660
|
}
|
|
19444
19661
|
}
|
|
19445
19662
|
function resolveSidecarCommand(opts) {
|
|
@@ -19578,10 +19795,30 @@ async function runCli(deps = {}) {
|
|
|
19578
19795
|
fetchJobs: () => client.listJobs({ status: "active", limit: 20 }),
|
|
19579
19796
|
fetchRecordings: ({ cursor, limit = DASHBOARD_RECORDINGS_PAGE_SIZE } = {}) => client.listRecordings({ limit, cursor }),
|
|
19580
19797
|
fetchDashboardStats: () => client.dashboardStats(),
|
|
19798
|
+
fetchAccountStatus: () => client.accountStatus(),
|
|
19581
19799
|
fetchTranscript: (transcriptId) => client.getTranscript(transcriptId),
|
|
19582
19800
|
recordingAudio,
|
|
19583
19801
|
listDownloadedRecordingIds: () => recordingAudio.listDownloadedRecordingIds(),
|
|
19584
19802
|
listDownloads: () => recordingAudio.listDownloads(),
|
|
19803
|
+
startLiveRecord: async () => {
|
|
19804
|
+
const liveStatus = await client.authStatus();
|
|
19805
|
+
if (!liveStatus.loggedIn || !liveStatus.userId) {
|
|
19806
|
+
throw cliError("auth.not_logged_in", "Sign in before starting a sidecar recording.", {
|
|
19807
|
+
hint: "Run recappi auth login, or import the Recappi Mini session with recappi auth import-macos."
|
|
19808
|
+
});
|
|
19809
|
+
}
|
|
19810
|
+
return startLiveRecordSession({
|
|
19811
|
+
account: {
|
|
19812
|
+
backendOrigin: auth.origin,
|
|
19813
|
+
userId: liveStatus.userId,
|
|
19814
|
+
...liveStatus.email ? { email: liveStatus.email } : {}
|
|
19815
|
+
},
|
|
19816
|
+
cliVersion: CLI_VERSION,
|
|
19817
|
+
env: deps.env,
|
|
19818
|
+
homeDir: deps.homeDir,
|
|
19819
|
+
runtime: deps.recordRuntime
|
|
19820
|
+
});
|
|
19821
|
+
},
|
|
19585
19822
|
initialView: parsed.initialView
|
|
19586
19823
|
});
|
|
19587
19824
|
return 0;
|