From 7b5e73cfa8cd281b9da6d0ef7a692fb484431948 Mon Sep 17 00:00:00 2001 From: Jamie Pine Date: Thu, 29 Jan 2026 18:56:10 -0800 Subject: [PATCH] Add .npmrc for bun usage and update dependencies - Created a new .npmrc file to enforce bun usage. - Bumped version numbers for multiple packages to 0.1.9 in bun.lock. - Added react-sound-visualizer dependency to enhance audio visualization features. - Introduced convert:assets script in package.json for asset optimization. - Updated CONTRIBUTING.md with instructions for converting assets to web formats. - Added documentation files for API endpoints and developer guidelines in the docs directory. --- .npmrc | 2 + CONTRIBUTING.md | 20 + app/package.json | 1 + .../VoiceProfiles/AudioSampleRecording.tsx | 83 +- .../VoiceProfiles/AudioSampleSystem.tsx | 3 +- .../VoiceProfiles/AudioSampleUpload.tsx | 3 +- .../components/VoiceProfiles/ProfileForm.tsx | 142 +- backend/main.py | 6 +- bun.lock | 13 +- docs/.gitignore | 3 + docs/README.md | 64 + docs/api/authentication.mdx | 55 + docs/api/generation.mdx | 119 ++ docs/api/overview.mdx | 219 ++ docs/api/recordings.mdx | 95 + docs/api/voice-profiles.mdx | 149 ++ docs/bun.lock | 1831 +++++++++++++++++ docs/custom.css | 15 + docs/developer/architecture.mdx | 202 ++ docs/developer/audio-channels.mdx | 310 +++ docs/developer/autoupdater.mdx | 84 + docs/developer/building.mdx | 263 +++ docs/developer/contributing.mdx | 326 +++ docs/developer/history.mdx | 260 +++ docs/developer/model-management.mdx | 341 +++ docs/developer/setup.mdx | 242 +++ docs/developer/stories.mdx | 320 +++ docs/developer/transcription.mdx | 299 +++ docs/developer/tts-generation.mdx | 283 +++ docs/developer/voice-profiles.mdx | 202 ++ docs/images/app-screenshot-1.webp | Bin 0 -> 136754 bytes docs/images/app-screenshot-2.webp | Bin 0 -> 131774 bytes docs/images/app-screenshot-3.webp | Bin 0 -> 110446 bytes docs/logo/icon-dark.png | Bin 0 -> 10562 bytes docs/logo/icon-light.png | Bin 0 -> 10562 bytes docs/mint.json | 102 + docs/overview/building-stories.mdx | 37 + docs/overview/creating-voice-profiles.mdx | 296 +++ docs/overview/generating-speech.mdx | 65 + docs/overview/generation-history.mdx | 88 + docs/overview/installation.mdx | 119 ++ docs/overview/introduction.mdx | 58 + docs/overview/quick-start.mdx | 154 ++ docs/overview/recording-transcription.mdx | 64 + docs/overview/remote-mode.mdx | 138 ++ docs/overview/stories-editor.mdx | 64 + docs/overview/troubleshooting.mdx | 477 +++++ docs/overview/voice-cloning.mdx | 75 + docs/package.json | 15 + docs/plans/OPENAI_SUPPORT.md | 235 +++ docs/public/app-screenshot-1.webp | Bin 0 -> 136754 bytes docs/public/app-screenshot-2.webp | Bin 0 -> 131774 bytes docs/public/app-screenshot-3.webp | Bin 0 -> 110446 bytes package.json | 7 +- scripts/convert-assets.sh | 154 ++ tauri/src-tauri/Cargo.lock | 2 +- tauri/src-tauri/gen/Assets.car | Bin 3847048 -> 3847048 bytes tauri/src-tauri/src/main.rs | 15 +- 58 files changed, 8014 insertions(+), 106 deletions(-) create mode 100644 .npmrc create mode 100644 docs/.gitignore create mode 100644 docs/README.md create mode 100644 docs/api/authentication.mdx create mode 100644 docs/api/generation.mdx create mode 100644 docs/api/overview.mdx create mode 100644 docs/api/recordings.mdx create mode 100644 docs/api/voice-profiles.mdx create mode 100644 docs/bun.lock create mode 100644 docs/custom.css create mode 100644 docs/developer/architecture.mdx create mode 100644 docs/developer/audio-channels.mdx create mode 100644 docs/developer/autoupdater.mdx create mode 100644 docs/developer/building.mdx create mode 100644 docs/developer/contributing.mdx create mode 100644 docs/developer/history.mdx create mode 100644 docs/developer/model-management.mdx create mode 100644 docs/developer/setup.mdx create mode 100644 docs/developer/stories.mdx create mode 100644 docs/developer/transcription.mdx create mode 100644 docs/developer/tts-generation.mdx create mode 100644 docs/developer/voice-profiles.mdx create mode 100644 docs/images/app-screenshot-1.webp create mode 100644 docs/images/app-screenshot-2.webp create mode 100644 docs/images/app-screenshot-3.webp create mode 100644 docs/logo/icon-dark.png create mode 100644 docs/logo/icon-light.png create mode 100644 docs/mint.json create mode 100644 docs/overview/building-stories.mdx create mode 100644 docs/overview/creating-voice-profiles.mdx create mode 100644 docs/overview/generating-speech.mdx create mode 100644 docs/overview/generation-history.mdx create mode 100644 docs/overview/installation.mdx create mode 100644 docs/overview/introduction.mdx create mode 100644 docs/overview/quick-start.mdx create mode 100644 docs/overview/recording-transcription.mdx create mode 100644 docs/overview/remote-mode.mdx create mode 100644 docs/overview/stories-editor.mdx create mode 100644 docs/overview/troubleshooting.mdx create mode 100644 docs/overview/voice-cloning.mdx create mode 100644 docs/package.json create mode 100644 docs/plans/OPENAI_SUPPORT.md create mode 100644 docs/public/app-screenshot-1.webp create mode 100644 docs/public/app-screenshot-2.webp create mode 100644 docs/public/app-screenshot-3.webp create mode 100755 scripts/convert-assets.sh diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..f3ecbbce --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +# Force bun usage +engine-strict=true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 69a83802..a7d06561 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -159,6 +159,26 @@ After starting the backend server: ``` This downloads the OpenAPI schema and generates the TypeScript client in `app/src/lib/api/` +### Convert Assets to Web Formats + +To optimize images and videos for the web, run: +```bash +bun run convert:assets +``` + +This script: +- Converts PNG → WebP (better compression, same quality) +- Converts MOV → WebM (VP9 codec, smaller file size) +- Processes files in `landing/public/` and `docs/public/` +- **Deletes original files** after successful conversion + +**Requirements:** Install `webp` and `ffmpeg`: +```bash +brew install webp ffmpeg +``` + +> **Note:** Run this before committing new images or videos to keep the repository size small. + ## Development Workflow ### 1. Create a Branch diff --git a/app/package.json b/app/package.json index a67d4527..edae6c50 100644 --- a/app/package.json +++ b/app/package.json @@ -48,6 +48,7 @@ "react": "^18.3.0", "react-dom": "^18.3.0", "react-hook-form": "^7.53.0", + "react-sound-visualizer": "^1.4.0", "tailwind-merge": "^2.5.4", "wavesurfer.js": "^7.0.0", "zod": "^3.23.8", diff --git a/app/src/components/VoiceProfiles/AudioSampleRecording.tsx b/app/src/components/VoiceProfiles/AudioSampleRecording.tsx index 9c7c9b62..4f2db4e3 100644 --- a/app/src/components/VoiceProfiles/AudioSampleRecording.tsx +++ b/app/src/components/VoiceProfiles/AudioSampleRecording.tsx @@ -1,8 +1,31 @@ import { Mic, Pause, Play, Square } from 'lucide-react'; +import { memo, useEffect, useState } from 'react'; +import { Visualizer } from 'react-sound-visualizer'; import { Button } from '@/components/ui/button'; -import { FormControl, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; +import { FormControl, FormItem, FormMessage } from '@/components/ui/form'; import { formatAudioDuration } from '@/lib/utils/audio'; +const MemoizedWaveform = memo(function MemoizedWaveform({ + audioStream, +}: { + audioStream: MediaStream; +}) { + return ( +
+ + {({ canvasRef }) => ( + + )} + +
+ ); +}); + interface AudioSampleRecordingProps { file: File | null | undefined; isRecording: boolean; @@ -14,6 +37,7 @@ interface AudioSampleRecordingProps { onPlayPause: () => void; isPlaying: boolean; isTranscribing?: boolean; + showWaveform?: boolean; } export function AudioSampleRecording({ @@ -27,29 +51,67 @@ export function AudioSampleRecording({ onPlayPause, isPlaying, isTranscribing = false, + showWaveform = true, }: AudioSampleRecordingProps) { + const [audioStream, setAudioStream] = useState(null); + + // Request microphone access when component mounts + useEffect(() => { + if (!showWaveform) return; + + let stream: MediaStream | null = null; + + navigator.mediaDevices + .getUserMedia({ audio: true, video: false }) + .then((s) => { + stream = s; + setAudioStream(s); + }) + .catch((err) => { + console.warn('Could not access microphone for visualization:', err); + }); + + return () => { + if (stream) { + stream.getTracks().forEach((track) => { + track.stop(); + }); + } + }; + }, [showWaveform]); + return ( - Record Audio
{!isRecording && !file && ( -
- -

+

Click to start recording. Maximum duration: 30 seconds.

)} {isRecording && ( -
-
+
+ {showWaveform && audioStream && ( + + )} +
-
+
{formatAudioDuration(duration)} @@ -58,13 +120,12 @@ export function AudioSampleRecording({ -

+

{formatAudioDuration(30 - duration)} remaining

diff --git a/app/src/components/VoiceProfiles/AudioSampleSystem.tsx b/app/src/components/VoiceProfiles/AudioSampleSystem.tsx index 1f4d7b95..0cc892b2 100644 --- a/app/src/components/VoiceProfiles/AudioSampleSystem.tsx +++ b/app/src/components/VoiceProfiles/AudioSampleSystem.tsx @@ -1,6 +1,6 @@ import { Mic, Monitor, Pause, Play, Square } from 'lucide-react'; import { Button } from '@/components/ui/button'; -import { FormControl, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; +import { FormControl, FormItem, FormMessage } from '@/components/ui/form'; import { formatAudioDuration } from '@/lib/utils/audio'; interface AudioSampleSystemProps { @@ -30,7 +30,6 @@ export function AudioSampleSystem({ }: AudioSampleSystemProps) { return ( - Capture System Audio
{!isRecording && !file && ( diff --git a/app/src/components/VoiceProfiles/AudioSampleUpload.tsx b/app/src/components/VoiceProfiles/AudioSampleUpload.tsx index f3c4c111..c66ded14 100644 --- a/app/src/components/VoiceProfiles/AudioSampleUpload.tsx +++ b/app/src/components/VoiceProfiles/AudioSampleUpload.tsx @@ -1,7 +1,7 @@ import { Mic, Pause, Play, Upload } from 'lucide-react'; import { useRef, useState } from 'react'; import { Button } from '@/components/ui/button'; -import { FormControl, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; +import { FormControl, FormItem, FormMessage } from '@/components/ui/form'; interface AudioSampleUploadProps { file: File | null | undefined; @@ -31,7 +31,6 @@ export function AudioSampleUpload({ return ( - Audio File
('upload'); + const [sampleMode, setSampleMode] = useState<'upload' | 'record' | 'system'>('record'); const [audioDuration, setAudioDuration] = useState(null); const [isValidatingAudio, setIsValidatingAudio] = useState(false); const { isPlaying, playPause, cleanup: cleanupAudio } = useAudioPlayer(); @@ -282,7 +282,7 @@ export function ProfileForm() { sampleFile: undefined, referenceText: undefined, }); - setSampleMode('upload'); + setSampleMode('record'); } }, [editingProfile, profileFormDraft, open, form]); @@ -488,9 +488,10 @@ export function ProfileForm() { return ( - - - {editingProfileId ? 'Edit Voice' : 'Create Voice Profile'} + +
+ + {editingProfileId ? 'Edit Voice' : 'Clone voice'} {editingProfileId ? 'Update your voice profile details and manage samples.' @@ -513,7 +514,7 @@ export function ProfileForm() { sampleFile: undefined, referenceText: '', }); - setSampleMode('upload'); + setSampleMode('record'); }} > @@ -524,76 +525,14 @@ export function ProfileForm() {
- -
- {/* Left column: Profile info */} -
- ( - - Name - - - - - - )} - /> - - ( - - Description (Optional) - -