recappi 0.1.1 → 0.1.2
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 +622 -164
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ function Header({ active }) {
|
|
|
20
20
|
] }),
|
|
21
21
|
/* @__PURE__ */ jsx(Tab, { num: "1", label: "Overview", active: active === "overview", enabled: true }),
|
|
22
22
|
/* @__PURE__ */ jsx(Tab, { num: "2", label: "Jobs", active: active === "jobs", enabled: true }),
|
|
23
|
-
/* @__PURE__ */ jsx(Tab, { num: "3", label: "Recordings", active: active === "recordings", enabled:
|
|
23
|
+
/* @__PURE__ */ jsx(Tab, { num: "3", label: "Recordings", active: active === "recordings", enabled: true })
|
|
24
24
|
] });
|
|
25
25
|
}
|
|
26
26
|
function Tab({
|
|
@@ -151,6 +151,36 @@ function resolveJobLinks(item, origin) {
|
|
|
151
151
|
}
|
|
152
152
|
return {};
|
|
153
153
|
}
|
|
154
|
+
function resolveRecordingLinks(recordingId, origin) {
|
|
155
|
+
if (!recordingId) return {};
|
|
156
|
+
return { webUrl: `${origin}/recordings/${recordingId}` };
|
|
157
|
+
}
|
|
158
|
+
function recordingStatusStyle(status) {
|
|
159
|
+
switch (status) {
|
|
160
|
+
case "ready":
|
|
161
|
+
return { label: "Ready", color: "green", glyph: "\u2713" };
|
|
162
|
+
case "uploading":
|
|
163
|
+
return { label: "Uploading", color: "cyan", glyph: "\u2191" };
|
|
164
|
+
case "failed":
|
|
165
|
+
return { label: "Failed", color: "red", glyph: "\u2717" };
|
|
166
|
+
case "aborted":
|
|
167
|
+
return { label: "Aborted", color: "gray", glyph: "\u2022" };
|
|
168
|
+
default:
|
|
169
|
+
return { label: status, color: "white", glyph: "\u2022" };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function formatBytes2(bytes) {
|
|
173
|
+
if (bytes == null || !Number.isFinite(bytes) || bytes < 0) return "";
|
|
174
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
175
|
+
let value = bytes;
|
|
176
|
+
let unit = 0;
|
|
177
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
178
|
+
value /= 1024;
|
|
179
|
+
unit += 1;
|
|
180
|
+
}
|
|
181
|
+
const rounded = value < 10 && unit > 0 ? value.toFixed(1) : String(Math.round(value));
|
|
182
|
+
return `${rounded}${units[unit]}`;
|
|
183
|
+
}
|
|
154
184
|
var SPINNER_FRAMES;
|
|
155
185
|
var init_format = __esm({
|
|
156
186
|
"src/tui/format.ts"() {
|
|
@@ -212,83 +242,149 @@ var init_JobsView = __esm({
|
|
|
212
242
|
}
|
|
213
243
|
});
|
|
214
244
|
|
|
215
|
-
// src/tui/
|
|
245
|
+
// src/tui/RecordingRow.tsx
|
|
216
246
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
217
247
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
218
|
-
function
|
|
219
|
-
return
|
|
248
|
+
function recordingTitle2(item) {
|
|
249
|
+
return item.title || item.summaryTitle || item.recordingId;
|
|
250
|
+
}
|
|
251
|
+
function RecordingRow({
|
|
252
|
+
item,
|
|
253
|
+
selected,
|
|
254
|
+
nowMs
|
|
255
|
+
}) {
|
|
256
|
+
const style = recordingStatusStyle(item.status);
|
|
257
|
+
const detail = [
|
|
258
|
+
item.durationMs ? formatClockMs(item.durationMs) : void 0,
|
|
259
|
+
formatAge(item.createdAt, nowMs) || void 0,
|
|
260
|
+
item.activeTranscriptId ? "transcript ready" : void 0
|
|
261
|
+
].filter(Boolean).join(" \xB7 ");
|
|
262
|
+
return /* @__PURE__ */ jsxs3(Box4, { children: [
|
|
263
|
+
/* @__PURE__ */ jsx4(Text4, { color: "cyan", children: selected ? "\u25B8 " : " " }),
|
|
264
|
+
/* @__PURE__ */ jsx4(Text4, { color: style.color, children: `${style.glyph} ${padCell(style.label, 10)}` }),
|
|
265
|
+
/* @__PURE__ */ jsx4(Text4, { bold: selected, children: padCell(recordingTitle2(item), 26) }),
|
|
266
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: !selected, children: detail })
|
|
267
|
+
] });
|
|
268
|
+
}
|
|
269
|
+
var init_RecordingRow = __esm({
|
|
270
|
+
"src/tui/RecordingRow.tsx"() {
|
|
271
|
+
"use strict";
|
|
272
|
+
init_format();
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// src/tui/OverviewView.tsx
|
|
277
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
278
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
279
|
+
function overviewRecentRecordings(recordings) {
|
|
280
|
+
return recordings.slice(0, RECENT_LIMIT);
|
|
220
281
|
}
|
|
221
282
|
function OverviewView({
|
|
222
|
-
|
|
283
|
+
recordings,
|
|
284
|
+
jobs,
|
|
285
|
+
stats,
|
|
223
286
|
selectedIndex,
|
|
224
287
|
spinnerFrame,
|
|
225
288
|
nowMs
|
|
226
289
|
}) {
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
290
|
+
const recent = overviewRecentRecordings(recordings);
|
|
291
|
+
const activeJobs = jobs.filter((j) => j.status === "running" || j.status === "queued");
|
|
292
|
+
const jobCounts = countJobs(jobs);
|
|
293
|
+
const recTotal = stats?.recordings.total ?? recordings.length;
|
|
294
|
+
const recReady = stats?.recordings.ready;
|
|
295
|
+
const transcribed = stats?.recordings.totalDurationMs;
|
|
296
|
+
return /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
297
|
+
/* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
|
|
298
|
+
/* @__PURE__ */ jsxs4(Text5, { children: [
|
|
299
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Recordings " }),
|
|
300
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: recTotal }),
|
|
301
|
+
recReady != null ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: ` \xB7 ${recReady} ready` }) : null,
|
|
302
|
+
transcribed != null ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: ` \xB7 ${formatClockMs(transcribed)} transcribed` }) : null
|
|
236
303
|
] }),
|
|
237
|
-
/* @__PURE__ */
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
304
|
+
/* @__PURE__ */ jsxs4(Text5, { children: [
|
|
305
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Jobs " }),
|
|
306
|
+
/* @__PURE__ */ jsxs4(Text5, { color: "cyan", children: [
|
|
307
|
+
stats?.jobs.running ?? jobCounts.running,
|
|
308
|
+
" running"
|
|
309
|
+
] }),
|
|
310
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " \xB7 " }),
|
|
311
|
+
/* @__PURE__ */ jsxs4(Text5, { color: "yellow", children: [
|
|
312
|
+
stats?.jobs.queued ?? jobCounts.queued,
|
|
313
|
+
" queued"
|
|
314
|
+
] }),
|
|
315
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " \xB7 " }),
|
|
316
|
+
/* @__PURE__ */ jsxs4(Text5, { color: "green", children: [
|
|
317
|
+
stats?.jobs.succeeded ?? jobCounts.succeeded,
|
|
318
|
+
" done"
|
|
319
|
+
] })
|
|
251
320
|
] })
|
|
252
321
|
] }),
|
|
253
|
-
/* @__PURE__ */
|
|
254
|
-
/* @__PURE__ */
|
|
255
|
-
|
|
256
|
-
|
|
322
|
+
/* @__PURE__ */ jsxs4(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
323
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Recent recordings" }),
|
|
324
|
+
recent.length === 0 ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " No recordings yet \u2014 run: recappi upload <file>" }) : recent.map((item, index) => /* @__PURE__ */ jsx5(
|
|
325
|
+
RecordingRow,
|
|
257
326
|
{
|
|
258
327
|
item,
|
|
259
328
|
selected: index === selectedIndex,
|
|
260
|
-
|
|
329
|
+
nowMs
|
|
261
330
|
},
|
|
262
|
-
item.
|
|
331
|
+
item.recordingId
|
|
263
332
|
))
|
|
264
333
|
] }),
|
|
265
|
-
|
|
266
|
-
/* @__PURE__ */
|
|
267
|
-
|
|
268
|
-
const style = statusStyle(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
/* @__PURE__ */
|
|
273
|
-
|
|
274
|
-
/* @__PURE__ */ jsx4(Text4, { children: title }),
|
|
275
|
-
age ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: ` ${age}` }) : null
|
|
276
|
-
] }, item.jobId);
|
|
334
|
+
activeJobs.length > 0 ? /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
335
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Transcribing now" }),
|
|
336
|
+
activeJobs.slice(0, 3).map((job) => {
|
|
337
|
+
const style = statusStyle(job.status);
|
|
338
|
+
return /* @__PURE__ */ jsxs4(Box5, { children: [
|
|
339
|
+
/* @__PURE__ */ jsx5(Text5, { children: " " }),
|
|
340
|
+
/* @__PURE__ */ jsx5(Text5, { color: style.color, children: `${statusGlyph(job.status, spinnerFrame)} ` }),
|
|
341
|
+
/* @__PURE__ */ jsx5(Text5, { children: job.recording?.title ?? job.recordingId })
|
|
342
|
+
] }, job.jobId);
|
|
277
343
|
})
|
|
278
344
|
] }) : null
|
|
279
345
|
] });
|
|
280
346
|
}
|
|
347
|
+
var RECENT_LIMIT;
|
|
281
348
|
var init_OverviewView = __esm({
|
|
282
349
|
"src/tui/OverviewView.tsx"() {
|
|
283
350
|
"use strict";
|
|
284
|
-
|
|
351
|
+
init_RecordingRow();
|
|
285
352
|
init_format();
|
|
353
|
+
RECENT_LIMIT = 6;
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// src/tui/RecordingsView.tsx
|
|
358
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
359
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
360
|
+
function RecordingsView({
|
|
361
|
+
items,
|
|
362
|
+
selectedIndex,
|
|
363
|
+
nowMs
|
|
364
|
+
}) {
|
|
365
|
+
if (items.length === 0) {
|
|
366
|
+
return /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "No recordings yet \u2014 run: recappi upload <file>" }) });
|
|
367
|
+
}
|
|
368
|
+
return /* @__PURE__ */ jsx6(Box6, { marginTop: 1, flexDirection: "column", children: items.map((item, index) => /* @__PURE__ */ jsx6(
|
|
369
|
+
RecordingRow,
|
|
370
|
+
{
|
|
371
|
+
item,
|
|
372
|
+
selected: index === selectedIndex,
|
|
373
|
+
nowMs
|
|
374
|
+
},
|
|
375
|
+
item.recordingId
|
|
376
|
+
)) });
|
|
377
|
+
}
|
|
378
|
+
var init_RecordingsView = __esm({
|
|
379
|
+
"src/tui/RecordingsView.tsx"() {
|
|
380
|
+
"use strict";
|
|
381
|
+
init_RecordingRow();
|
|
286
382
|
}
|
|
287
383
|
});
|
|
288
384
|
|
|
289
385
|
// src/tui/JobDetailView.tsx
|
|
290
|
-
import { Box as
|
|
291
|
-
import { jsx as
|
|
386
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
387
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
292
388
|
function JobDetailView({
|
|
293
389
|
item,
|
|
294
390
|
origin,
|
|
@@ -298,13 +394,13 @@ function JobDetailView({
|
|
|
298
394
|
const style = statusStyle(item.status);
|
|
299
395
|
const links = resolveJobLinks(item, origin);
|
|
300
396
|
const title = item.recording?.title ?? item.recordingId;
|
|
301
|
-
return /* @__PURE__ */
|
|
302
|
-
/* @__PURE__ */
|
|
397
|
+
return /* @__PURE__ */ jsxs5(Box7, { flexDirection: "column", paddingX: 1, children: [
|
|
398
|
+
/* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
|
|
303
399
|
"\u2039 Jobs / ",
|
|
304
400
|
title
|
|
305
401
|
] }),
|
|
306
|
-
/* @__PURE__ */
|
|
307
|
-
|
|
402
|
+
/* @__PURE__ */ jsxs5(
|
|
403
|
+
Box7,
|
|
308
404
|
{
|
|
309
405
|
marginTop: 1,
|
|
310
406
|
borderStyle: "round",
|
|
@@ -312,17 +408,17 @@ function JobDetailView({
|
|
|
312
408
|
paddingX: 1,
|
|
313
409
|
flexDirection: "column",
|
|
314
410
|
children: [
|
|
315
|
-
/* @__PURE__ */
|
|
411
|
+
/* @__PURE__ */ jsxs5(Text7, { color: style.color, bold: true, children: [
|
|
316
412
|
style.label,
|
|
317
|
-
item.provider ? /* @__PURE__ */
|
|
413
|
+
item.provider ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: ` ${item.provider}` }) : null
|
|
318
414
|
] }),
|
|
319
|
-
/* @__PURE__ */
|
|
415
|
+
/* @__PURE__ */ jsx7(StatusLine, { item, spinnerFrame, nowMs })
|
|
320
416
|
]
|
|
321
417
|
}
|
|
322
418
|
),
|
|
323
|
-
/* @__PURE__ */
|
|
324
|
-
/* @__PURE__ */
|
|
325
|
-
/* @__PURE__ */
|
|
419
|
+
/* @__PURE__ */ jsxs5(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
420
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, children: "Timeline" }),
|
|
421
|
+
/* @__PURE__ */ jsx7(
|
|
326
422
|
TimelineRow,
|
|
327
423
|
{
|
|
328
424
|
label: "Enqueued",
|
|
@@ -331,7 +427,7 @@ function JobDetailView({
|
|
|
331
427
|
nowMs
|
|
332
428
|
}
|
|
333
429
|
),
|
|
334
|
-
/* @__PURE__ */
|
|
430
|
+
/* @__PURE__ */ jsx7(
|
|
335
431
|
TimelineRow,
|
|
336
432
|
{
|
|
337
433
|
label: "Started",
|
|
@@ -340,7 +436,7 @@ function JobDetailView({
|
|
|
340
436
|
nowMs
|
|
341
437
|
}
|
|
342
438
|
),
|
|
343
|
-
/* @__PURE__ */
|
|
439
|
+
/* @__PURE__ */ jsx7(
|
|
344
440
|
TimelineRow,
|
|
345
441
|
{
|
|
346
442
|
label: item.status === "failed" ? "Failed" : item.status === "running" ? "Transcribing" : "Finished",
|
|
@@ -352,21 +448,21 @@ function JobDetailView({
|
|
|
352
448
|
}
|
|
353
449
|
)
|
|
354
450
|
] }),
|
|
355
|
-
/* @__PURE__ */
|
|
356
|
-
/* @__PURE__ */
|
|
451
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text7, { children: [
|
|
452
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Recording " }),
|
|
357
453
|
title,
|
|
358
|
-
item.recording?.durationMs ? /* @__PURE__ */
|
|
454
|
+
item.recording?.durationMs ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: ` \xB7 ${formatClockMs(item.recording.durationMs)}` }) : null
|
|
359
455
|
] }) }),
|
|
360
|
-
/* @__PURE__ */
|
|
361
|
-
/* @__PURE__ */
|
|
362
|
-
/* @__PURE__ */
|
|
363
|
-
/* @__PURE__ */
|
|
364
|
-
/* @__PURE__ */
|
|
365
|
-
/* @__PURE__ */
|
|
366
|
-
/* @__PURE__ */
|
|
367
|
-
/* @__PURE__ */
|
|
456
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxs5(Text7, { children: [
|
|
457
|
+
/* @__PURE__ */ jsx7(Text7, { color: links.webUrl ? "cyan" : "gray", children: "o open" }),
|
|
458
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " \xB7 " }),
|
|
459
|
+
/* @__PURE__ */ jsx7(Text7, { color: links.webUrl ? "cyan" : "gray", children: "w web" }),
|
|
460
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " \xB7 " }),
|
|
461
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "m mac app (soon)" }),
|
|
462
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " \xB7 " }),
|
|
463
|
+
/* @__PURE__ */ jsx7(Text7, { color: links.webUrl ? "cyan" : "gray", children: "c copy" })
|
|
368
464
|
] }) }),
|
|
369
|
-
/* @__PURE__ */
|
|
465
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
|
|
370
466
|
"esc back \xB7 t transcript",
|
|
371
467
|
item.transcriptId ? "" : " (when ready)",
|
|
372
468
|
" \xB7 q quit"
|
|
@@ -383,24 +479,24 @@ function StatusLine({
|
|
|
383
479
|
const elapsed = item.startedAt ? ` \xB7 ${formatClockMs(nowMs - item.startedAt)} elapsed` : "";
|
|
384
480
|
if (fraction != null) {
|
|
385
481
|
const pct = Math.round(fraction * 100);
|
|
386
|
-
return /* @__PURE__ */
|
|
482
|
+
return /* @__PURE__ */ jsxs5(Text7, { children: [
|
|
387
483
|
`${progressBar(fraction)} ${pct}% ${formatClockMs(item.processedDurationMs)} / ${formatClockMs(
|
|
388
484
|
item.recording?.durationMs
|
|
389
485
|
)}`,
|
|
390
|
-
/* @__PURE__ */
|
|
486
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: elapsed })
|
|
391
487
|
] });
|
|
392
488
|
}
|
|
393
489
|
const spinner = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"][spinnerFrame % 10];
|
|
394
|
-
return /* @__PURE__ */
|
|
490
|
+
return /* @__PURE__ */ jsxs5(Text7, { children: [
|
|
395
491
|
`${spinner} transcribing\u2026`,
|
|
396
|
-
/* @__PURE__ */
|
|
492
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: elapsed })
|
|
397
493
|
] });
|
|
398
494
|
}
|
|
399
495
|
if (item.status === "succeeded")
|
|
400
|
-
return /* @__PURE__ */
|
|
401
|
-
if (item.status === "queued") return /* @__PURE__ */
|
|
402
|
-
if (item.status === "failed") return /* @__PURE__ */
|
|
403
|
-
return /* @__PURE__ */
|
|
496
|
+
return /* @__PURE__ */ jsx7(Text7, { children: item.transcriptId ? "transcript ready" : "done" });
|
|
497
|
+
if (item.status === "queued") return /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "waiting to start\u2026" });
|
|
498
|
+
if (item.status === "failed") return /* @__PURE__ */ jsx7(Text7, { color: "red", children: "transcription failed" });
|
|
499
|
+
return /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: item.status });
|
|
404
500
|
}
|
|
405
501
|
function TimelineRow({
|
|
406
502
|
label,
|
|
@@ -413,10 +509,10 @@ function TimelineRow({
|
|
|
413
509
|
const glyph = failed ? "\u2717" : done ? "\u2713" : running ? "\u280B" : "\u25CB";
|
|
414
510
|
const color = failed ? "red" : done ? "green" : running ? "cyan" : "gray";
|
|
415
511
|
const age = at ? formatAge(at, nowMs) : running ? "now" : "";
|
|
416
|
-
return /* @__PURE__ */
|
|
417
|
-
/* @__PURE__ */
|
|
418
|
-
/* @__PURE__ */
|
|
419
|
-
age ? /* @__PURE__ */
|
|
512
|
+
return /* @__PURE__ */ jsxs5(Box7, { children: [
|
|
513
|
+
/* @__PURE__ */ jsx7(Text7, { color, children: ` ${glyph} ` }),
|
|
514
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: !done && !running, children: label }),
|
|
515
|
+
age ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: ` ${age}` }) : null
|
|
420
516
|
] });
|
|
421
517
|
}
|
|
422
518
|
var init_JobDetailView = __esm({
|
|
@@ -426,46 +522,111 @@ var init_JobDetailView = __esm({
|
|
|
426
522
|
}
|
|
427
523
|
});
|
|
428
524
|
|
|
525
|
+
// src/tui/RecordingDetailView.tsx
|
|
526
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
527
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
528
|
+
function RecordingDetailView({
|
|
529
|
+
item,
|
|
530
|
+
nowMs
|
|
531
|
+
}) {
|
|
532
|
+
const style = recordingStatusStyle(item.status);
|
|
533
|
+
const links = resolveRecordingLinks(item.recordingId, item.origin);
|
|
534
|
+
const title = recordingTitle2(item);
|
|
535
|
+
const meta3 = [
|
|
536
|
+
item.durationMs ? formatClockMs(item.durationMs) : void 0,
|
|
537
|
+
formatBytes2(item.sizeBytes) || void 0,
|
|
538
|
+
item.contentType || void 0
|
|
539
|
+
].filter(Boolean).join(" \xB7 ");
|
|
540
|
+
return /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", paddingX: 1, children: [
|
|
541
|
+
/* @__PURE__ */ jsxs6(Text8, { dimColor: true, children: [
|
|
542
|
+
"\u2039 Recordings / ",
|
|
543
|
+
title
|
|
544
|
+
] }),
|
|
545
|
+
/* @__PURE__ */ jsxs6(
|
|
546
|
+
Box8,
|
|
547
|
+
{
|
|
548
|
+
marginTop: 1,
|
|
549
|
+
borderStyle: "round",
|
|
550
|
+
borderColor: style.color,
|
|
551
|
+
paddingX: 1,
|
|
552
|
+
flexDirection: "column",
|
|
553
|
+
children: [
|
|
554
|
+
/* @__PURE__ */ jsxs6(Text8, { color: style.color, bold: true, children: [
|
|
555
|
+
style.label,
|
|
556
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: ` created ${formatAge(item.createdAt, nowMs) || "\u2014"}` })
|
|
557
|
+
] }),
|
|
558
|
+
meta3 ? /* @__PURE__ */ jsx8(Text8, { children: meta3 }) : null
|
|
559
|
+
]
|
|
560
|
+
}
|
|
561
|
+
),
|
|
562
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text8, { children: [
|
|
563
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Transcript " }),
|
|
564
|
+
item.activeTranscriptId ? /* @__PURE__ */ jsx8(Text8, { color: "green", children: "ready \u2014 press t to view" }) : /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "not available yet" })
|
|
565
|
+
] }) }),
|
|
566
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxs6(Text8, { children: [
|
|
567
|
+
/* @__PURE__ */ jsx8(Text8, { color: links.webUrl ? "cyan" : "gray", children: "o open" }),
|
|
568
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " \xB7 " }),
|
|
569
|
+
/* @__PURE__ */ jsx8(Text8, { color: links.webUrl ? "cyan" : "gray", children: "w web" }),
|
|
570
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " \xB7 " }),
|
|
571
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "m mac app (soon)" }),
|
|
572
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " \xB7 " }),
|
|
573
|
+
/* @__PURE__ */ jsx8(Text8, { color: links.webUrl ? "cyan" : "gray", children: "c copy" })
|
|
574
|
+
] }) }),
|
|
575
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text8, { dimColor: true, children: [
|
|
576
|
+
"esc back \xB7 ",
|
|
577
|
+
item.activeTranscriptId ? "t transcript \xB7 " : "",
|
|
578
|
+
"q quit"
|
|
579
|
+
] }) })
|
|
580
|
+
] });
|
|
581
|
+
}
|
|
582
|
+
var init_RecordingDetailView = __esm({
|
|
583
|
+
"src/tui/RecordingDetailView.tsx"() {
|
|
584
|
+
"use strict";
|
|
585
|
+
init_format();
|
|
586
|
+
init_RecordingRow();
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
|
|
429
590
|
// src/tui/TranscriptView.tsx
|
|
430
|
-
import { Box as
|
|
431
|
-
import { jsx as
|
|
591
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
592
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
432
593
|
function TranscriptView({ loading, data, error: error51 }) {
|
|
433
594
|
if (loading) {
|
|
434
|
-
return /* @__PURE__ */
|
|
595
|
+
return /* @__PURE__ */ jsx9(Box9, { paddingX: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Loading transcript\u2026" }) });
|
|
435
596
|
}
|
|
436
597
|
if (error51) {
|
|
437
|
-
return /* @__PURE__ */
|
|
438
|
-
/* @__PURE__ */
|
|
598
|
+
return /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", paddingX: 1, children: [
|
|
599
|
+
/* @__PURE__ */ jsxs7(Text9, { color: "red", children: [
|
|
439
600
|
"! ",
|
|
440
601
|
error51
|
|
441
602
|
] }),
|
|
442
|
-
/* @__PURE__ */
|
|
603
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "q / esc / \u2190 back" })
|
|
443
604
|
] });
|
|
444
605
|
}
|
|
445
606
|
if (!data) {
|
|
446
|
-
return /* @__PURE__ */
|
|
607
|
+
return /* @__PURE__ */ jsx9(Box9, { paddingX: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "No transcript." }) });
|
|
447
608
|
}
|
|
448
609
|
const summary = data.summary;
|
|
449
610
|
const showSummary = summary?.status === "succeeded";
|
|
450
|
-
return /* @__PURE__ */
|
|
451
|
-
/* @__PURE__ */
|
|
452
|
-
/* @__PURE__ */
|
|
453
|
-
/* @__PURE__ */
|
|
611
|
+
return /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", paddingX: 1, children: [
|
|
612
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: "magenta", children: summary?.title ?? "Transcript" }),
|
|
613
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: data.segments.length === 0 ? /* @__PURE__ */ jsx9(Text9, { children: data.text }) : data.segments.slice(0, 200).map((segment, index) => /* @__PURE__ */ jsxs7(Text9, { children: [
|
|
614
|
+
/* @__PURE__ */ jsxs7(Text9, { dimColor: true, children: [
|
|
454
615
|
"[",
|
|
455
616
|
formatClockMs(segment.startMs),
|
|
456
617
|
"] "
|
|
457
618
|
] }),
|
|
458
|
-
segment.speaker ? /* @__PURE__ */
|
|
619
|
+
segment.speaker ? /* @__PURE__ */ jsxs7(Text9, { color: "cyan", children: [
|
|
459
620
|
segment.speaker,
|
|
460
621
|
": "
|
|
461
622
|
] }) : null,
|
|
462
623
|
segment.text
|
|
463
624
|
] }, index)) }),
|
|
464
|
-
showSummary && summary?.tldr ? /* @__PURE__ */
|
|
465
|
-
/* @__PURE__ */
|
|
466
|
-
/* @__PURE__ */
|
|
625
|
+
showSummary && summary?.tldr ? /* @__PURE__ */ jsxs7(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
626
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: "Summary" }),
|
|
627
|
+
/* @__PURE__ */ jsx9(Text9, { children: summary.tldr })
|
|
467
628
|
] }) : null,
|
|
468
|
-
/* @__PURE__ */
|
|
629
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "q / esc / \u2190 back" }) })
|
|
469
630
|
] });
|
|
470
631
|
}
|
|
471
632
|
var init_TranscriptView = __esm({
|
|
@@ -477,11 +638,13 @@ var init_TranscriptView = __esm({
|
|
|
477
638
|
|
|
478
639
|
// src/tui/AppShell.tsx
|
|
479
640
|
import { useCallback, useEffect, useState } from "react";
|
|
480
|
-
import { Box as
|
|
481
|
-
import { jsx as
|
|
641
|
+
import { Box as Box10, Text as Text10, useApp, useInput } from "ink";
|
|
642
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
482
643
|
function AppShell({
|
|
483
644
|
fetchJobs,
|
|
484
645
|
fetchTranscript,
|
|
646
|
+
fetchRecordings,
|
|
647
|
+
fetchDashboardStats,
|
|
485
648
|
initialView = "overview",
|
|
486
649
|
openUrl: openUrl2,
|
|
487
650
|
copyText: copyText2,
|
|
@@ -490,41 +653,48 @@ function AppShell({
|
|
|
490
653
|
spinnerMs = 80
|
|
491
654
|
}) {
|
|
492
655
|
const { exit } = useApp();
|
|
493
|
-
const [
|
|
656
|
+
const [jobs, setJobs] = useState([]);
|
|
657
|
+
const [recordings, setRecordings] = useState([]);
|
|
658
|
+
const [stats, setStats] = useState(void 0);
|
|
494
659
|
const [origin, setOrigin] = useState("");
|
|
495
|
-
const [stack, setStack] = useState([
|
|
496
|
-
{ kind: initialView === "jobs" ? "jobs" : "overview" }
|
|
497
|
-
]);
|
|
660
|
+
const [stack, setStack] = useState([{ kind: initialView }]);
|
|
498
661
|
const [selected, setSelected] = useState(0);
|
|
499
662
|
const [spinnerFrame, setSpinnerFrame] = useState(0);
|
|
500
663
|
const [loadError, setLoadError] = useState(void 0);
|
|
501
664
|
const [notice, setNotice] = useState(void 0);
|
|
502
665
|
const screen = stack[stack.length - 1];
|
|
503
666
|
const refresh = useCallback(async () => {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
667
|
+
const [jobsR, recR, statsR] = await Promise.allSettled([
|
|
668
|
+
fetchJobs(),
|
|
669
|
+
fetchRecordings ? fetchRecordings() : Promise.resolve(void 0),
|
|
670
|
+
fetchDashboardStats ? fetchDashboardStats() : Promise.resolve(void 0)
|
|
671
|
+
]);
|
|
672
|
+
if (jobsR.status === "fulfilled") {
|
|
673
|
+
setJobs(jobsR.value.items);
|
|
674
|
+
setOrigin(jobsR.value.origin);
|
|
508
675
|
setLoadError(void 0);
|
|
509
|
-
}
|
|
510
|
-
setLoadError(
|
|
676
|
+
} else {
|
|
677
|
+
setLoadError(jobsR.reason instanceof Error ? jobsR.reason.message : String(jobsR.reason));
|
|
511
678
|
}
|
|
512
|
-
|
|
679
|
+
if (recR.status === "fulfilled" && recR.value) setRecordings(recR.value.items);
|
|
680
|
+
if (statsR.status === "fulfilled" && statsR.value) setStats(statsR.value);
|
|
681
|
+
}, [fetchJobs, fetchRecordings, fetchDashboardStats]);
|
|
513
682
|
useEffect(() => {
|
|
514
683
|
void refresh();
|
|
515
684
|
const id = setInterval(() => void refresh(), pollMs);
|
|
516
685
|
return () => clearInterval(id);
|
|
517
686
|
}, [refresh, pollMs]);
|
|
518
|
-
const hasRunning =
|
|
687
|
+
const hasRunning = jobs.some((item) => item.status === "running");
|
|
519
688
|
useEffect(() => {
|
|
520
689
|
if (!hasRunning) return;
|
|
521
690
|
const id = setInterval(() => setSpinnerFrame((f) => f + 1), spinnerMs);
|
|
522
691
|
return () => clearInterval(id);
|
|
523
692
|
}, [hasRunning, spinnerMs]);
|
|
524
|
-
const
|
|
693
|
+
const recordingList = screen.kind === "overview" ? overviewRecentRecordings(recordings) : recordings;
|
|
694
|
+
const listLength = screen.kind === "jobs" ? jobs.length : recordingList.length;
|
|
525
695
|
useEffect(() => {
|
|
526
|
-
setSelected((i) => Math.max(0, Math.min(i, Math.max(0,
|
|
527
|
-
}, [
|
|
696
|
+
setSelected((i) => Math.max(0, Math.min(i, Math.max(0, listLength - 1))));
|
|
697
|
+
}, [listLength]);
|
|
528
698
|
const openTranscript = useCallback(
|
|
529
699
|
async (transcriptId) => {
|
|
530
700
|
setStack((st) => [...st, { kind: "transcript", loading: true }]);
|
|
@@ -552,33 +722,46 @@ function AppShell({
|
|
|
552
722
|
const back = () => setStack((st) => st.length > 1 ? st.slice(0, -1) : st);
|
|
553
723
|
useInput((input, key) => {
|
|
554
724
|
setNotice(void 0);
|
|
555
|
-
if (input === "q")
|
|
556
|
-
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
if (key.escape || key.leftArrow) {
|
|
560
|
-
back();
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
725
|
+
if (input === "q") return exit();
|
|
726
|
+
if (key.escape || key.leftArrow) return back();
|
|
563
727
|
if (input === "1") return goTab("overview");
|
|
564
728
|
if (input === "2") return goTab("jobs");
|
|
565
|
-
if (input === "
|
|
566
|
-
|
|
729
|
+
if (input === "3") return goTab("recordings");
|
|
730
|
+
if (input === "r") return void refresh();
|
|
731
|
+
if (screen.kind === "overview" || screen.kind === "recordings") {
|
|
732
|
+
if (key.upArrow || input === "k") setSelected((i) => Math.max(0, i - 1));
|
|
733
|
+
if (key.downArrow || input === "j")
|
|
734
|
+
setSelected((i) => Math.min(recordingList.length - 1, i + 1));
|
|
735
|
+
const rec = recordingList[selected];
|
|
736
|
+
if (key.return && rec)
|
|
737
|
+
setStack((st) => [...st, { kind: "recordingDetail", recordingId: rec.recordingId }]);
|
|
738
|
+
if (input === "t" && rec?.activeTranscriptId) void openTranscript(rec.activeTranscriptId);
|
|
567
739
|
return;
|
|
568
740
|
}
|
|
569
|
-
if (screen.kind === "
|
|
741
|
+
if (screen.kind === "jobs") {
|
|
570
742
|
if (key.upArrow || input === "k") setSelected((i) => Math.max(0, i - 1));
|
|
571
|
-
if (key.downArrow || input === "j")
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
if (
|
|
575
|
-
if (input === "t" && item?.transcriptId) void openTranscript(item.transcriptId);
|
|
743
|
+
if (key.downArrow || input === "j") setSelected((i) => Math.min(jobs.length - 1, i + 1));
|
|
744
|
+
const job = jobs[selected];
|
|
745
|
+
if (key.return && job) setStack((st) => [...st, { kind: "jobDetail", jobId: job.jobId }]);
|
|
746
|
+
if (input === "t" && job?.transcriptId) void openTranscript(job.transcriptId);
|
|
576
747
|
return;
|
|
577
748
|
}
|
|
578
749
|
if (screen.kind === "jobDetail") {
|
|
579
|
-
const
|
|
580
|
-
const links =
|
|
581
|
-
if (input === "t" &&
|
|
750
|
+
const job = jobs.find((j) => j.jobId === screen.jobId);
|
|
751
|
+
const links = job ? resolveJobLinks(job, origin) : {};
|
|
752
|
+
if (input === "t" && job?.transcriptId) void openTranscript(job.transcriptId);
|
|
753
|
+
else if ((input === "o" || input === "w") && links.webUrl) openUrl2?.(links.webUrl);
|
|
754
|
+
else if (input === "m") setNotice("Mac app deeplink not available yet");
|
|
755
|
+
else if (input === "c" && links.webUrl) {
|
|
756
|
+
copyText2?.(links.webUrl);
|
|
757
|
+
setNotice("Link copied");
|
|
758
|
+
}
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
if (screen.kind === "recordingDetail") {
|
|
762
|
+
const rec = recordings.find((r) => r.recordingId === screen.recordingId);
|
|
763
|
+
const links = rec ? resolveRecordingLinks(rec.recordingId, rec.origin) : {};
|
|
764
|
+
if (input === "t" && rec?.activeTranscriptId) void openTranscript(rec.activeTranscriptId);
|
|
582
765
|
else if ((input === "o" || input === "w") && links.webUrl) openUrl2?.(links.webUrl);
|
|
583
766
|
else if (input === "m") setNotice("Mac app deeplink not available yet");
|
|
584
767
|
else if (input === "c" && links.webUrl) {
|
|
@@ -589,43 +772,56 @@ function AppShell({
|
|
|
589
772
|
}
|
|
590
773
|
});
|
|
591
774
|
if (screen.kind === "transcript") {
|
|
592
|
-
return /* @__PURE__ */
|
|
775
|
+
return /* @__PURE__ */ jsx10(TranscriptView, { loading: screen.loading, data: screen.data, error: screen.error });
|
|
593
776
|
}
|
|
594
777
|
if (screen.kind === "jobDetail") {
|
|
595
|
-
const
|
|
596
|
-
if (!
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
}
|
|
602
|
-
return /* @__PURE__ */
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
/* @__PURE__ */ jsx7(Header, { active: tab }),
|
|
610
|
-
screen.kind === "overview" ? /* @__PURE__ */ jsx7(
|
|
778
|
+
const job = jobs.find((j) => j.jobId === screen.jobId);
|
|
779
|
+
if (!job) return /* @__PURE__ */ jsx10(Missing, { label: "Job" });
|
|
780
|
+
return /* @__PURE__ */ jsx10(Detail, { notice, children: /* @__PURE__ */ jsx10(JobDetailView, { item: job, origin, spinnerFrame, nowMs: now() }) });
|
|
781
|
+
}
|
|
782
|
+
if (screen.kind === "recordingDetail") {
|
|
783
|
+
const rec = recordings.find((r) => r.recordingId === screen.recordingId);
|
|
784
|
+
if (!rec) return /* @__PURE__ */ jsx10(Missing, { label: "Recording" });
|
|
785
|
+
return /* @__PURE__ */ jsx10(Detail, { notice, children: /* @__PURE__ */ jsx10(RecordingDetailView, { item: rec, nowMs: now() }) });
|
|
786
|
+
}
|
|
787
|
+
const tab = screen.kind === "jobs" ? "jobs" : screen.kind === "recordings" ? "recordings" : "overview";
|
|
788
|
+
const footerKeys = screen.kind === "jobs" ? "1/2/3 tabs \xB7 \u2191\u2193 select \xB7 \u23CE job \xB7 t transcript \xB7 r refresh \xB7 q quit" : "1/2/3 tabs \xB7 \u2191\u2193 select \xB7 \u23CE recording \xB7 t transcript \xB7 r refresh \xB7 q quit";
|
|
789
|
+
return /* @__PURE__ */ jsxs8(Box10, { flexDirection: "column", paddingX: 1, children: [
|
|
790
|
+
/* @__PURE__ */ jsx10(Header, { active: tab }),
|
|
791
|
+
screen.kind === "overview" ? /* @__PURE__ */ jsx10(
|
|
611
792
|
OverviewView,
|
|
612
793
|
{
|
|
613
|
-
|
|
794
|
+
recordings,
|
|
795
|
+
jobs,
|
|
796
|
+
stats,
|
|
614
797
|
selectedIndex: selected,
|
|
615
798
|
spinnerFrame,
|
|
616
799
|
nowMs: now()
|
|
617
800
|
}
|
|
618
|
-
) : /* @__PURE__ */
|
|
619
|
-
loadError &&
|
|
801
|
+
) : screen.kind === "recordings" ? /* @__PURE__ */ jsx10(RecordingsView, { items: recordings, selectedIndex: selected, nowMs: now() }) : /* @__PURE__ */ jsx10(JobsView, { items: jobs, selectedIndex: selected, spinnerFrame }),
|
|
802
|
+
loadError && jobs.length === 0 && recordings.length === 0 ? /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text10, { color: "red", children: [
|
|
620
803
|
"! ",
|
|
621
804
|
loadError
|
|
622
805
|
] }) }) : null,
|
|
623
|
-
/* @__PURE__ */
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
806
|
+
/* @__PURE__ */ jsx10(Footer, { keys: footerKeys })
|
|
807
|
+
] });
|
|
808
|
+
}
|
|
809
|
+
function Detail({
|
|
810
|
+
notice,
|
|
811
|
+
children
|
|
812
|
+
}) {
|
|
813
|
+
return /* @__PURE__ */ jsxs8(Box10, { flexDirection: "column", children: [
|
|
814
|
+
children,
|
|
815
|
+
notice ? /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text10, { color: "green", children: notice }) }) : null
|
|
816
|
+
] });
|
|
817
|
+
}
|
|
818
|
+
function Missing({ label }) {
|
|
819
|
+
return /* @__PURE__ */ jsxs8(Box10, { flexDirection: "column", paddingX: 1, children: [
|
|
820
|
+
/* @__PURE__ */ jsxs8(Text10, { dimColor: true, children: [
|
|
821
|
+
label,
|
|
822
|
+
" no longer in the list."
|
|
823
|
+
] }),
|
|
824
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "esc back \xB7 q quit" })
|
|
629
825
|
] });
|
|
630
826
|
}
|
|
631
827
|
var init_AppShell = __esm({
|
|
@@ -634,7 +830,9 @@ var init_AppShell = __esm({
|
|
|
634
830
|
init_chrome();
|
|
635
831
|
init_JobsView();
|
|
636
832
|
init_OverviewView();
|
|
833
|
+
init_RecordingsView();
|
|
637
834
|
init_JobDetailView();
|
|
835
|
+
init_RecordingDetailView();
|
|
638
836
|
init_TranscriptView();
|
|
639
837
|
init_format();
|
|
640
838
|
}
|
|
@@ -672,6 +870,8 @@ async function runDashboard(deps) {
|
|
|
672
870
|
React2.createElement(AppShell, {
|
|
673
871
|
fetchJobs: deps.fetchJobs,
|
|
674
872
|
fetchTranscript: deps.fetchTranscript,
|
|
873
|
+
fetchRecordings: deps.fetchRecordings,
|
|
874
|
+
fetchDashboardStats: deps.fetchDashboardStats,
|
|
675
875
|
initialView: deps.initialView ?? "overview",
|
|
676
876
|
openUrl,
|
|
677
877
|
copyText
|
|
@@ -15340,6 +15540,45 @@ var jobListDataSchema = external_exports.object({
|
|
|
15340
15540
|
limit: external_exports.number().int().positive(),
|
|
15341
15541
|
origin: external_exports.string()
|
|
15342
15542
|
});
|
|
15543
|
+
var recordingDataSchema = external_exports.object({
|
|
15544
|
+
recordingId: external_exports.string(),
|
|
15545
|
+
title: external_exports.string().nullable().optional(),
|
|
15546
|
+
summaryTitle: external_exports.string().nullable().optional(),
|
|
15547
|
+
status: recordingStatusSchema,
|
|
15548
|
+
durationMs: external_exports.number().int().nonnegative().nullable().optional(),
|
|
15549
|
+
sizeBytes: external_exports.number().int().nonnegative().nullable().optional(),
|
|
15550
|
+
contentType: external_exports.string().optional(),
|
|
15551
|
+
activeTranscriptId: external_exports.string().nullable().optional(),
|
|
15552
|
+
createdAt: external_exports.number().int(),
|
|
15553
|
+
updatedAt: external_exports.number().int(),
|
|
15554
|
+
origin: external_exports.string()
|
|
15555
|
+
});
|
|
15556
|
+
var recordingListDataSchema = external_exports.object({
|
|
15557
|
+
items: external_exports.array(recordingDataSchema),
|
|
15558
|
+
limit: external_exports.number().int().positive(),
|
|
15559
|
+
nextCursor: external_exports.string().nullable().optional(),
|
|
15560
|
+
totalCount: external_exports.number().int().nonnegative().optional(),
|
|
15561
|
+
origin: external_exports.string()
|
|
15562
|
+
});
|
|
15563
|
+
var dashboardStatsDataSchema = external_exports.object({
|
|
15564
|
+
origin: external_exports.string(),
|
|
15565
|
+
recordings: external_exports.object({
|
|
15566
|
+
total: external_exports.number().int().nonnegative(),
|
|
15567
|
+
ready: external_exports.number().int().nonnegative(),
|
|
15568
|
+
uploading: external_exports.number().int().nonnegative(),
|
|
15569
|
+
failed: external_exports.number().int().nonnegative(),
|
|
15570
|
+
aborted: external_exports.number().int().nonnegative(),
|
|
15571
|
+
totalDurationMs: external_exports.number().int().nonnegative(),
|
|
15572
|
+
totalSizeBytes: external_exports.number().int().nonnegative()
|
|
15573
|
+
}),
|
|
15574
|
+
jobs: external_exports.object({
|
|
15575
|
+
active: external_exports.number().int().nonnegative(),
|
|
15576
|
+
queued: external_exports.number().int().nonnegative(),
|
|
15577
|
+
running: external_exports.number().int().nonnegative(),
|
|
15578
|
+
succeeded: external_exports.number().int().nonnegative(),
|
|
15579
|
+
failed: external_exports.number().int().nonnegative()
|
|
15580
|
+
})
|
|
15581
|
+
});
|
|
15343
15582
|
var transcriptSegmentSchema = external_exports.object({
|
|
15344
15583
|
startMs: external_exports.number().nonnegative(),
|
|
15345
15584
|
endMs: external_exports.number().nonnegative(),
|
|
@@ -16130,6 +16369,30 @@ var RecappiApiClient = class {
|
|
|
16130
16369
|
origin: this.auth.origin
|
|
16131
16370
|
});
|
|
16132
16371
|
}
|
|
16372
|
+
async listRecordings(opts) {
|
|
16373
|
+
const params = new URLSearchParams({ limit: String(opts.limit) });
|
|
16374
|
+
if (opts.cursor) params.set("cursor", opts.cursor);
|
|
16375
|
+
if (opts.search) params.set("search", opts.search);
|
|
16376
|
+
const parsed = await this.getJson(`/api/recordings?${params}`);
|
|
16377
|
+
const items = Array.isArray(parsed.items) ? parsed.items.filter(isRecord2).map((row) => mapRecording(row, this.auth.origin)) : [];
|
|
16378
|
+
return recordingListDataSchema.parse({
|
|
16379
|
+
items,
|
|
16380
|
+
limit: opts.limit,
|
|
16381
|
+
...typeof parsed.nextCursor === "string" || parsed.nextCursor === null ? { nextCursor: parsed.nextCursor } : {},
|
|
16382
|
+
...typeof parsed.totalCount === "number" ? { totalCount: parsed.totalCount } : {},
|
|
16383
|
+
origin: this.auth.origin
|
|
16384
|
+
});
|
|
16385
|
+
}
|
|
16386
|
+
async getRecording(recordingId) {
|
|
16387
|
+
const parsed = await this.getJson(
|
|
16388
|
+
`/api/recordings/${encodeURIComponent(recordingId)}`
|
|
16389
|
+
);
|
|
16390
|
+
return mapRecording(parsed, this.auth.origin);
|
|
16391
|
+
}
|
|
16392
|
+
async dashboardStats() {
|
|
16393
|
+
const parsed = await this.getJson("/api/dashboard/stats");
|
|
16394
|
+
return mapDashboardStats(parsed, this.auth.origin);
|
|
16395
|
+
}
|
|
16133
16396
|
async uploadPathBatch(opts) {
|
|
16134
16397
|
const files = await collectAudioFiles(opts.inputPath);
|
|
16135
16398
|
if (files.length === 0) {
|
|
@@ -16510,9 +16773,59 @@ function mapJobListItem(row) {
|
|
|
16510
16773
|
}
|
|
16511
16774
|
};
|
|
16512
16775
|
}
|
|
16776
|
+
function mapRecording(row, origin) {
|
|
16777
|
+
const recordingId = stringValue(row.id) ?? stringValue(row.recordingId);
|
|
16778
|
+
const status = stringValue(row.status);
|
|
16779
|
+
const createdAt = numberValue(row.createdAt);
|
|
16780
|
+
const updatedAt = numberValue(row.updatedAt);
|
|
16781
|
+
if (!recordingId) {
|
|
16782
|
+
throw cliError("cloud.invalid_response", "Recording response was missing id.");
|
|
16783
|
+
}
|
|
16784
|
+
if (!status) {
|
|
16785
|
+
throw cliError("cloud.invalid_response", "Recording response was missing status.");
|
|
16786
|
+
}
|
|
16787
|
+
if (createdAt === void 0 || updatedAt === void 0) {
|
|
16788
|
+
throw cliError("cloud.invalid_response", "Recording response was missing timestamps.");
|
|
16789
|
+
}
|
|
16790
|
+
return recordingDataSchema.parse({
|
|
16791
|
+
recordingId,
|
|
16792
|
+
...typeof row.title === "string" || row.title === null ? { title: row.title } : {},
|
|
16793
|
+
...typeof row.summaryTitle === "string" || row.summaryTitle === null ? { summaryTitle: row.summaryTitle } : {},
|
|
16794
|
+
status,
|
|
16795
|
+
...typeof row.durationMs === "number" || row.durationMs === null ? { durationMs: row.durationMs } : {},
|
|
16796
|
+
...typeof row.sizeBytes === "number" || row.sizeBytes === null ? { sizeBytes: row.sizeBytes } : {},
|
|
16797
|
+
...typeof row.contentType === "string" ? { contentType: row.contentType } : {},
|
|
16798
|
+
...typeof row.activeTranscriptId === "string" || row.activeTranscriptId === null ? { activeTranscriptId: row.activeTranscriptId } : {},
|
|
16799
|
+
createdAt,
|
|
16800
|
+
updatedAt,
|
|
16801
|
+
origin
|
|
16802
|
+
});
|
|
16803
|
+
}
|
|
16804
|
+
function mapDashboardStats(row, origin) {
|
|
16805
|
+
return dashboardStatsDataSchema.parse({
|
|
16806
|
+
origin,
|
|
16807
|
+
recordings: mapCountObject(row.recordings, [
|
|
16808
|
+
"total",
|
|
16809
|
+
"ready",
|
|
16810
|
+
"uploading",
|
|
16811
|
+
"failed",
|
|
16812
|
+
"aborted",
|
|
16813
|
+
"totalDurationMs",
|
|
16814
|
+
"totalSizeBytes"
|
|
16815
|
+
]),
|
|
16816
|
+
jobs: mapCountObject(row.jobs, ["active", "queued", "running", "succeeded", "failed"])
|
|
16817
|
+
});
|
|
16818
|
+
}
|
|
16819
|
+
function mapCountObject(value, keys) {
|
|
16820
|
+
const source = isRecord2(value) ? value : {};
|
|
16821
|
+
return Object.fromEntries(keys.map((key) => [key, numberValue(source[key]) ?? 0]));
|
|
16822
|
+
}
|
|
16513
16823
|
function stringValue(value) {
|
|
16514
16824
|
return typeof value === "string" ? value : void 0;
|
|
16515
16825
|
}
|
|
16826
|
+
function numberValue(value) {
|
|
16827
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
16828
|
+
}
|
|
16516
16829
|
function parseSummaryStatus(value) {
|
|
16517
16830
|
const allowed = /* @__PURE__ */ new Set([
|
|
16518
16831
|
"pending",
|
|
@@ -16672,6 +16985,56 @@ function renderHumanSuccess(command, data, opts) {
|
|
|
16672
16985
|
renderTranscriptHuman(data, opts);
|
|
16673
16986
|
return;
|
|
16674
16987
|
}
|
|
16988
|
+
if (command === "recordings list" && isRecord3(data) && Array.isArray(data.items)) {
|
|
16989
|
+
opts.stdout("Recordings:\n");
|
|
16990
|
+
for (const item of data.items) {
|
|
16991
|
+
if (!isRecord3(item)) continue;
|
|
16992
|
+
opts.stdout(` ${recordingLabel(item)}
|
|
16993
|
+
`);
|
|
16994
|
+
}
|
|
16995
|
+
if (data.items.length === 0) opts.stdout(" No recordings found.\n");
|
|
16996
|
+
if (typeof data.nextCursor === "string") {
|
|
16997
|
+
opts.stdout(`
|
|
16998
|
+
Next cursor: ${data.nextCursor}
|
|
16999
|
+
`);
|
|
17000
|
+
}
|
|
17001
|
+
return;
|
|
17002
|
+
}
|
|
17003
|
+
if (command === "recordings get" && isRecord3(data)) {
|
|
17004
|
+
opts.stdout(`${recordingTitle(data)}
|
|
17005
|
+
`);
|
|
17006
|
+
opts.stdout(` recordingId: ${String(data.recordingId)}
|
|
17007
|
+
`);
|
|
17008
|
+
if (typeof data.status === "string") opts.stdout(` status: ${data.status}
|
|
17009
|
+
`);
|
|
17010
|
+
if (typeof data.durationMs === "number")
|
|
17011
|
+
opts.stdout(` duration: ${formatDurationMs(data.durationMs)}
|
|
17012
|
+
`);
|
|
17013
|
+
if (typeof data.sizeBytes === "number") opts.stdout(` size: ${formatBytes(data.sizeBytes)}
|
|
17014
|
+
`);
|
|
17015
|
+
if (typeof data.activeTranscriptId === "string") {
|
|
17016
|
+
opts.stdout(` activeTranscriptId: ${data.activeTranscriptId}
|
|
17017
|
+
`);
|
|
17018
|
+
opts.stdout(`
|
|
17019
|
+
Next:
|
|
17020
|
+
recappi transcript get ${data.activeTranscriptId}
|
|
17021
|
+
`);
|
|
17022
|
+
}
|
|
17023
|
+
return;
|
|
17024
|
+
}
|
|
17025
|
+
if (command === "dashboard stats" && isRecord3(data)) {
|
|
17026
|
+
const recordings = isRecord3(data.recordings) ? data.recordings : {};
|
|
17027
|
+
const jobs = isRecord3(data.jobs) ? data.jobs : {};
|
|
17028
|
+
opts.stdout(
|
|
17029
|
+
`Recordings: ${numberText(recordings.total)} total, ${numberText(recordings.ready)} ready
|
|
17030
|
+
`
|
|
17031
|
+
);
|
|
17032
|
+
opts.stdout(
|
|
17033
|
+
`Jobs: ${numberText(jobs.active)} active (${numberText(jobs.queued)} queued, ${numberText(jobs.running)} running)
|
|
17034
|
+
`
|
|
17035
|
+
);
|
|
17036
|
+
return;
|
|
17037
|
+
}
|
|
16675
17038
|
if (command === "upload" && isUploadBatch(data)) {
|
|
16676
17039
|
if (data.successes.length > 0) {
|
|
16677
17040
|
opts.stdout(data.successes.length === 1 ? "Upload complete\n" : "Uploads complete\n");
|
|
@@ -16852,6 +17215,37 @@ Summary:
|
|
|
16852
17215
|
}
|
|
16853
17216
|
}
|
|
16854
17217
|
}
|
|
17218
|
+
function recordingLabel(item) {
|
|
17219
|
+
const id = typeof item.recordingId === "string" ? item.recordingId : "unknown";
|
|
17220
|
+
const status = typeof item.status === "string" ? item.status : "unknown";
|
|
17221
|
+
const duration3 = typeof item.durationMs === "number" ? ` \xB7 ${formatDurationMs(item.durationMs)}` : "";
|
|
17222
|
+
return `${recordingTitle(item)} (${status}, ${id}${duration3})`;
|
|
17223
|
+
}
|
|
17224
|
+
function recordingTitle(item) {
|
|
17225
|
+
for (const key of ["title", "summaryTitle"]) {
|
|
17226
|
+
const value = item[key];
|
|
17227
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
17228
|
+
}
|
|
17229
|
+
return "Untitled recording";
|
|
17230
|
+
}
|
|
17231
|
+
function numberText(value) {
|
|
17232
|
+
return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : "0";
|
|
17233
|
+
}
|
|
17234
|
+
function formatDurationMs(ms) {
|
|
17235
|
+
return formatClock(ms / 1e3);
|
|
17236
|
+
}
|
|
17237
|
+
function formatBytes(bytes) {
|
|
17238
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
17239
|
+
const units = ["KB", "MB", "GB", "TB"];
|
|
17240
|
+
let value = bytes / 1024;
|
|
17241
|
+
let unit = units[0];
|
|
17242
|
+
for (const next of units.slice(1)) {
|
|
17243
|
+
if (value < 1024) break;
|
|
17244
|
+
value /= 1024;
|
|
17245
|
+
unit = next;
|
|
17246
|
+
}
|
|
17247
|
+
return `${value >= 10 ? value.toFixed(0) : value.toFixed(1)} ${unit}`;
|
|
17248
|
+
}
|
|
16855
17249
|
function formatClock(seconds) {
|
|
16856
17250
|
const total = Math.max(0, Math.floor(seconds));
|
|
16857
17251
|
const hours = Math.floor(total / 3600);
|
|
@@ -16942,7 +17336,10 @@ var COMMAND_DATA_SCHEMAS = {
|
|
|
16942
17336
|
"auth import-macos": authImportDataSchema,
|
|
16943
17337
|
"auth status": authStatusDataSchema,
|
|
16944
17338
|
doctor: doctorDataSchema,
|
|
17339
|
+
"dashboard stats": dashboardStatsDataSchema,
|
|
16945
17340
|
upload: uploadBatchDataSchema,
|
|
17341
|
+
"recordings get": recordingDataSchema,
|
|
17342
|
+
"recordings list": recordingListDataSchema,
|
|
16946
17343
|
"jobs list": jobListDataSchema,
|
|
16947
17344
|
"jobs wait": jobDataSchema,
|
|
16948
17345
|
"transcript get": transcriptDataSchema
|
|
@@ -17084,6 +17481,8 @@ async function runCli(deps = {}) {
|
|
|
17084
17481
|
const runDashboard2 = deps.runDashboard ?? (await Promise.resolve().then(() => (init_tui(), tui_exports))).runDashboard;
|
|
17085
17482
|
await runDashboard2({
|
|
17086
17483
|
fetchJobs: () => client.listJobs({ status: "active", limit: 20 }),
|
|
17484
|
+
fetchRecordings: () => client.listRecordings({ limit: 20 }),
|
|
17485
|
+
fetchDashboardStats: () => client.dashboardStats(),
|
|
17087
17486
|
fetchTranscript: (transcriptId) => client.getTranscript(transcriptId),
|
|
17088
17487
|
initialView: parsed.initialView
|
|
17089
17488
|
});
|
|
@@ -17181,6 +17580,25 @@ async function runCli(deps = {}) {
|
|
|
17181
17580
|
renderSuccess("jobs list", data, render2);
|
|
17182
17581
|
return 0;
|
|
17183
17582
|
}
|
|
17583
|
+
if (parsed.kind === "recordings-list") {
|
|
17584
|
+
const data = await client.listRecordings({
|
|
17585
|
+
limit: parsed.limit,
|
|
17586
|
+
cursor: parsed.cursor,
|
|
17587
|
+
search: parsed.search
|
|
17588
|
+
});
|
|
17589
|
+
renderSuccess("recordings list", data, render2);
|
|
17590
|
+
return 0;
|
|
17591
|
+
}
|
|
17592
|
+
if (parsed.kind === "recordings-get") {
|
|
17593
|
+
const data = await client.getRecording(parsed.recordingId);
|
|
17594
|
+
renderSuccess("recordings get", data, render2);
|
|
17595
|
+
return 0;
|
|
17596
|
+
}
|
|
17597
|
+
if (parsed.kind === "dashboard-stats") {
|
|
17598
|
+
const data = await client.dashboardStats();
|
|
17599
|
+
renderSuccess("dashboard stats", data, render2);
|
|
17600
|
+
return 0;
|
|
17601
|
+
}
|
|
17184
17602
|
if (parsed.kind === "transcript-get") {
|
|
17185
17603
|
const data = await client.getTranscript(parsed.transcriptId);
|
|
17186
17604
|
renderSuccess("transcript get", data, render2);
|
|
@@ -17402,6 +17820,44 @@ Agent mode:
|
|
|
17402
17820
|
document: buildSchemaDocument(program)
|
|
17403
17821
|
});
|
|
17404
17822
|
});
|
|
17823
|
+
const dashboard = program.command("dashboard").description("Dashboard data commands");
|
|
17824
|
+
addCommonOptions(dashboard);
|
|
17825
|
+
const dashboardStats = dashboard.command("stats").description("Fetch dashboard counters");
|
|
17826
|
+
addCommonOptions(dashboardStats);
|
|
17827
|
+
dashboardStats.action((_options, command) => {
|
|
17828
|
+
onSelect({
|
|
17829
|
+
kind: "dashboard-stats",
|
|
17830
|
+
options: collectGlobalOptions(command),
|
|
17831
|
+
commandName: "dashboard stats"
|
|
17832
|
+
});
|
|
17833
|
+
});
|
|
17834
|
+
const recordings = program.command("recordings").description("Recording commands");
|
|
17835
|
+
addCommonOptions(recordings);
|
|
17836
|
+
const recordingsList = recordings.command("list").description("List recent recordings").option("--limit <n>", "number of recordings to show", parseLimitOption("--limit", 1, 100), 20).option("--cursor <cursor>", "pagination cursor", parseStringOption("--cursor")).option("--search <query>", "search recordings and transcripts", parseStringOption("--search"));
|
|
17837
|
+
addCommonOptions(recordingsList);
|
|
17838
|
+
recordingsList.action((_options, command) => {
|
|
17839
|
+
const opts = command.opts();
|
|
17840
|
+
onSelect({
|
|
17841
|
+
kind: "recordings-list",
|
|
17842
|
+
options: collectGlobalOptions(command),
|
|
17843
|
+
commandName: "recordings list",
|
|
17844
|
+
limit: opts.limit ?? 20,
|
|
17845
|
+
...typeof opts.cursor === "string" ? { cursor: opts.cursor } : {},
|
|
17846
|
+
...typeof opts.search === "string" ? { search: opts.search } : {}
|
|
17847
|
+
});
|
|
17848
|
+
});
|
|
17849
|
+
const recordingsGet = recordings.command("get <recordingId>").description("Fetch a recording by recording id");
|
|
17850
|
+
addCommonOptions(recordingsGet);
|
|
17851
|
+
recordingsGet.action(
|
|
17852
|
+
(recordingId, _options, command) => {
|
|
17853
|
+
onSelect({
|
|
17854
|
+
kind: "recordings-get",
|
|
17855
|
+
options: collectGlobalOptions(command),
|
|
17856
|
+
commandName: "recordings get",
|
|
17857
|
+
recordingId
|
|
17858
|
+
});
|
|
17859
|
+
}
|
|
17860
|
+
);
|
|
17405
17861
|
const transcript = program.command("transcript").description("Transcript commands");
|
|
17406
17862
|
addCommonOptions(transcript);
|
|
17407
17863
|
const transcriptGet = transcript.command("get <transcriptId>").description("Fetch a transcript by transcript id");
|
|
@@ -17516,7 +17972,9 @@ var VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
|
17516
17972
|
"--provider",
|
|
17517
17973
|
"--prompt",
|
|
17518
17974
|
"--status",
|
|
17519
|
-
"--limit"
|
|
17975
|
+
"--limit",
|
|
17976
|
+
"--cursor",
|
|
17977
|
+
"--search"
|
|
17520
17978
|
]);
|
|
17521
17979
|
function hasCommandToken(argv) {
|
|
17522
17980
|
return commandTokens(argv).length > 0;
|