Files
vibepod/web/components/WaveformPreview.tsx
T
LyAhn 13085166fb feat(phase-1): persistent generation library
- Save every completed generation to SQLite (generation_store.py) with
  WAV and waveform peaks written to data/generations/<id>/
- Deferred DB write until success — cancelled/errored generations never
  touch the DB and never appear in the library
- Fixed cancel+regenerate IndexError: _reset_scheduler_caches() now
  directly zeros scheduler._step_index and running state in addition to
  clearing VibePod cache dicts; same explicit resets added in the fresh
  path of prepare_noise_scheduler as belt-and-suspenders
- Added /library page with GenerationCard, WaveformPreview, waveform
  fetch, play/pause, download, delete, pagination, empty + error states
- Added generation API routes (list, single, audio stream, waveform,
  delete) proxying to Python server
- Added Library nav link to Header with active state
- Persist script/speaker/CFG to localStorage so generate page state
  survives navigation
- Updated build plan: Phase 0+1 ticked off, better-sqlite3 moved to
  Phase 2, architectural note on Python owning all persistence
2026-05-02 23:05:11 +01:00

58 lines
1.3 KiB
TypeScript

"use client";
import { useEffect, useRef } from "react";
import type { WaveformPeaks } from "@/lib/types/generation";
interface WaveformPreviewProps {
peaks: WaveformPeaks;
color?: string;
height?: number;
className?: string;
}
export default function WaveformPreview({
peaks,
color = "#2dd4bf",
height = 48,
className = "",
}: WaveformPreviewProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const { width } = canvas;
const midY = height / 2;
const { min, max } = peaks.data;
const len = peaks.length;
ctx.clearRect(0, 0, width, height);
ctx.strokeStyle = color;
ctx.lineWidth = 1;
for (let x = 0; x < width; x++) {
const peakIndex = Math.floor((x / width) * len);
const minY = midY - min[peakIndex] * midY;
const maxY = midY - max[peakIndex] * midY;
ctx.beginPath();
ctx.moveTo(x + 0.5, Math.min(minY, maxY));
ctx.lineTo(x + 0.5, Math.max(minY, maxY));
ctx.stroke();
}
}, [peaks, color, height]);
return (
<canvas
ref={canvasRef}
width={400}
height={height}
className={`w-full ${className}`}
style={{ height }}
/>
);
}