mirror of
https://github.com/JezzWTF/vibepod.git
synced 2026-06-13 03:58:07 +00:00
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
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
"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 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user