File size: 2,878 Bytes
b2d4d0e
 
 
 
 
 
8122b04
b2d4d0e
 
65949d0
 
 
 
8122b04
b2d4d0e
 
 
 
 
 
65949d0
 
 
 
 
b2d4d0e
 
 
65949d0
 
b2d4d0e
8122b04
 
 
 
b2d4d0e
65949d0
 
 
 
 
8122b04
b2d4d0e
65949d0
 
8122b04
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b2d4d0e
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import { useEffect, useState } from "react";
import { listHistory, type HistoryRecord } from "@/lib/idb";

type Props = {
  refreshKey?: number;
  onRegenerate: (h: HistoryRecord) => void;
  onReuseSeed?: (seed: number) => void;
};

function fmtTime(ts: number): string {
  return new Date(ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
}

export default function HistoryList({ refreshKey, onRegenerate, onReuseSeed }: Props) {
  const [items, setItems] = useState<HistoryRecord[]>([]);
  useEffect(() => {
    listHistory().then(setItems);
  }, [refreshKey]);

  if (items.length === 0) {
    return (
      <p className="text-sm text-muted-foreground italic">
        Generations will be archived here.
      </p>
    );
  }

  return (
    <ul className="space-y-3">
      {items.map((h, i) => {
        const url = URL.createObjectURL(h.audioBlob);
        const kindLabel =
          h.kind === "dialog"
            ? `dialog · ${(h.speakers ?? []).length} spk · ${h.modelId.replace("chatterbox-", "")}`
            : `${h.modelId.replace("chatterbox-", "")} · ${h.language ?? "—"}`;
        return (
          <li key={h.id} className="card-paper p-3 space-y-2.5">
            <div className="flex items-baseline justify-between gap-3">
              <span className="marker-num">
                {String(items.length - i).padStart(2, "0")}
              </span>
              <span className="label-mono">{kindLabel} · {fmtTime(h.createdAt)}</span>
            </div>
            <p className="text-[13px] leading-snug line-clamp-3">{h.text}</p>
            <audio controls src={url} className="w-full h-9" />
            <div className="flex items-center justify-between">
              {h.seedUsed != null ? (
                <button
                  type="button"
                  onClick={() => onReuseSeed?.(h.seedUsed!)}
                  className="label-mono hover:text-[hsl(var(--ember))] transition-colors"
                  title="Copy this seed into the active params"
                >
                  seed {h.seedUsed} · ↻
                </button>
              ) : (
                <span className="label-mono text-muted-foreground/60">no seed</span>
              )}
              <div className="flex gap-3">
                <a
                  href={url}
                  download={`${h.id}.wav`}
                  className="label-mono hover:text-foreground transition-colors"
                >
                  ↓ download
                </a>
                <button
                  type="button"
                  className="label-mono hover:text-[hsl(var(--ember))] transition-colors"
                  onClick={() => onRegenerate(h)}
                >
                  ↻ regenerate
                </button>
              </div>
            </div>
          </li>
        );
      })}
    </ul>
  );
}