mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-16 13:20:39 -07:00
Update global styles and enhance landing page layout
- Added overflow-x hidden to prevent horizontal scrolling on html and body elements. - Centered the heading and paragraph text on the landing page for improved aesthetics. - Introduced a mobile-friendly centered screenshot above download buttons. - Updated comments for clarity regarding screenshot positioning on different devices.
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getLatestRelease } from '@/lib/releases';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
export const revalidate = 600; // Revalidate every 10 minutes
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const releaseInfo = await getLatestRelease();
|
||||
return NextResponse.json(releaseInfo);
|
||||
} catch (error) {
|
||||
console.error('Error fetching release info:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch release information' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -54,8 +54,12 @@
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
html {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground antialiased;
|
||||
overflow-x: hidden;
|
||||
background-image:
|
||||
radial-gradient(at 0% 0%, rgba(255, 255, 255, 0.03) 0px, transparent 50%),
|
||||
radial-gradient(at 100% 100%, rgba(255, 255, 255, 0.02) 0px, transparent 50%);
|
||||
|
||||
@@ -2,13 +2,36 @@
|
||||
|
||||
import { Cloud, Code, Cpu, Github, Shield, Zap } from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { AppleIcon, LinuxIcon, WindowsIcon } from '@/components/PlatformIcons';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Section, SectionTitle } from '@/components/ui/section';
|
||||
import { DOWNLOAD_LINKS, GITHUB_REPO } from '@/lib/constants';
|
||||
import type { DownloadLinks } from '@/lib/releases';
|
||||
import { FeatureCard } from '../components/ui/feature-card';
|
||||
|
||||
export default function Home() {
|
||||
const [downloadLinks, setDownloadLinks] = useState<DownloadLinks>(DOWNLOAD_LINKS);
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch latest release info
|
||||
fetch('/api/releases')
|
||||
.then((res) => {
|
||||
if (!res.ok) {
|
||||
throw new Error('Failed to fetch releases');
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.downloadLinks) {
|
||||
setDownloadLinks(data.downloadLinks);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to fetch release info:', error);
|
||||
// Keep fallback links (releases page) on error
|
||||
});
|
||||
}, []);
|
||||
const features = [
|
||||
{
|
||||
title: 'Near-Perfect Voice Cloning',
|
||||
@@ -64,20 +87,34 @@ export default function Home() {
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<h1 className="text-5xl sm:text-6xl md:text-7xl lg:text-8xl font-bold leading-tight text-left">
|
||||
<h1 className="text-5xl sm:text-6xl md:text-7xl lg:text-8xl font-bold leading-tight text-center lg:text-left">
|
||||
Voicebox
|
||||
</h1>
|
||||
<p className="text-lg sm:text-xl md:text-2xl text-foreground/70 max-w-xl text-left">
|
||||
<p className="text-lg sm:text-xl md:text-2xl text-foreground/70 max-w-xl text-center lg:text-left mx-auto lg:mx-0">
|
||||
Open source voice cloning powered by Qwen3-TTS. Create natural-sounding speech from
|
||||
text with near-perfect voice replication.
|
||||
</p>
|
||||
|
||||
{/* Mobile: centered screenshot above download buttons */}
|
||||
<div className="flex justify-center lg:hidden my-8">
|
||||
<div className="w-full max-w-2xl">
|
||||
<Image
|
||||
src="/VoiceBoxAppScreenshot.webp"
|
||||
alt="Voicebox Application Screenshot"
|
||||
width={1920}
|
||||
height={1080}
|
||||
className="w-full h-auto rounded-lg shadow-lg"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download buttons under left content */}
|
||||
<div className="space-y-4 pt-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 w-full max-w-2xl">
|
||||
<Button asChild size="lg" className="w-full px-0">
|
||||
<a
|
||||
href={DOWNLOAD_LINKS.macArm}
|
||||
href={downloadLinks.macArm}
|
||||
download
|
||||
className="flex items-center w-full relative"
|
||||
>
|
||||
@@ -90,7 +127,7 @@ export default function Home() {
|
||||
</Button>
|
||||
<Button asChild size="lg" className="w-full px-0">
|
||||
<a
|
||||
href={DOWNLOAD_LINKS.macIntel}
|
||||
href={downloadLinks.macIntel}
|
||||
download
|
||||
className="flex items-center w-full relative"
|
||||
>
|
||||
@@ -103,7 +140,7 @@ export default function Home() {
|
||||
</Button>
|
||||
<Button asChild size="lg" className="w-full px-0">
|
||||
<a
|
||||
href={DOWNLOAD_LINKS.windows}
|
||||
href={downloadLinks.windows}
|
||||
download
|
||||
className="flex items-center w-full relative"
|
||||
>
|
||||
@@ -116,7 +153,7 @@ export default function Home() {
|
||||
</Button>
|
||||
<Button asChild size="lg" className="w-full px-0">
|
||||
<a
|
||||
href={DOWNLOAD_LINKS.linux}
|
||||
href={downloadLinks.linux}
|
||||
download
|
||||
className="flex items-center w-full relative"
|
||||
>
|
||||
@@ -137,7 +174,7 @@ export default function Home() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right side - Large screenshot positioned off-screen */}
|
||||
{/* Desktop: Large screenshot positioned off-screen */}
|
||||
<div className="hidden lg:block relative">
|
||||
<div className="absolute right-0 top-0 -mt-10 w-[200%] -mr-[100%]">
|
||||
<Image
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// Download links for voicebox releases
|
||||
// Update these when new releases are published
|
||||
// These are fallback values - link to releases page if API fails
|
||||
export const LATEST_VERSION = 'v0.1.0';
|
||||
|
||||
export const GITHUB_REPO = 'https://github.com/jamiepine/voicebox';
|
||||
export const GITHUB_RELEASES_PAGE = `${GITHUB_REPO}/releases`;
|
||||
|
||||
export const DOWNLOAD_LINKS = {
|
||||
macArm:
|
||||
'https://github.com/jamiepine/voicebox/releases/download/v0.1.0/voicebox_0.1.0_aarch64.dmg',
|
||||
macIntel:
|
||||
'https://github.com/jamiepine/voicebox/releases/download/v0.1.0/voicebox_0.1.0_x64.dmg',
|
||||
windows:
|
||||
'https://github.com/jamiepine/voicebox/releases/download/v0.1.0/voicebox_0.1.0_x64_en-US.msi',
|
||||
linux:
|
||||
'https://github.com/jamiepine/voicebox/releases/download/v0.1.0/voicebox_x86_64-unknown-linux-gnu.AppImage',
|
||||
macArm: GITHUB_RELEASES_PAGE,
|
||||
macIntel: GITHUB_RELEASES_PAGE,
|
||||
windows: GITHUB_RELEASES_PAGE,
|
||||
linux: GITHUB_RELEASES_PAGE,
|
||||
} as const;
|
||||
|
||||
export const GITHUB_REPO = 'https://github.com/jamiepine/voicebox';
|
||||
// Export function to get dynamic download links
|
||||
export { getLatestRelease } from './releases';
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// Fetch latest release information from GitHub
|
||||
export interface DownloadLinks {
|
||||
macArm: string;
|
||||
macIntel: string;
|
||||
windows: string;
|
||||
linux: string;
|
||||
}
|
||||
|
||||
export interface ReleaseInfo {
|
||||
version: string;
|
||||
downloadLinks: DownloadLinks;
|
||||
}
|
||||
|
||||
const GITHUB_REPO = 'jamiepine/voicebox';
|
||||
const GITHUB_API_BASE = 'https://api.github.com';
|
||||
|
||||
// Cache for release info (in-memory cache, resets on server restart)
|
||||
let cachedReleaseInfo: ReleaseInfo | null = null;
|
||||
let cacheTimestamp: number = 0;
|
||||
const CACHE_DURATION = 1000 * 60 * 10; // 10 minutes
|
||||
|
||||
/**
|
||||
* Fetches the latest release from GitHub and extracts download links
|
||||
*/
|
||||
export async function getLatestRelease(): Promise<ReleaseInfo> {
|
||||
// Return cached data if still valid
|
||||
const now = Date.now();
|
||||
if (cachedReleaseInfo && now - cacheTimestamp < CACHE_DURATION) {
|
||||
return cachedReleaseInfo;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${GITHUB_API_BASE}/repos/${GITHUB_REPO}/releases/latest`, {
|
||||
next: { revalidate: 600 }, // Revalidate every 10 minutes
|
||||
headers: {
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`GitHub API error: ${response.status}`);
|
||||
}
|
||||
|
||||
const release = await response.json();
|
||||
const version = release.tag_name;
|
||||
const assets = release.assets || [];
|
||||
|
||||
// Extract download links based on file patterns
|
||||
const downloadLinks: Partial<DownloadLinks> = {};
|
||||
|
||||
for (const asset of assets) {
|
||||
const name = asset.name.toLowerCase();
|
||||
const url = asset.browser_download_url;
|
||||
|
||||
if (name.includes('aarch64') || name.includes('arm64')) {
|
||||
downloadLinks.macArm = url;
|
||||
} else if (name.includes('x64') && name.includes('.dmg')) {
|
||||
downloadLinks.macIntel = url;
|
||||
} else if (name.includes('.msi')) {
|
||||
downloadLinks.windows = url;
|
||||
} else if (name.includes('.appimage') || name.includes('.deb')) {
|
||||
downloadLinks.linux = url;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: construct URLs if not found in assets
|
||||
const baseUrl = `https://github.com/${GITHUB_REPO}/releases/download/${version}`;
|
||||
|
||||
const releaseInfo: ReleaseInfo = {
|
||||
version,
|
||||
downloadLinks: {
|
||||
macArm: downloadLinks.macArm || `${baseUrl}/voicebox_${version.replace('v', '')}_aarch64.dmg`,
|
||||
macIntel: downloadLinks.macIntel || `${baseUrl}/voicebox_${version.replace('v', '')}_x64.dmg`,
|
||||
windows: downloadLinks.windows || `${baseUrl}/voicebox_${version.replace('v', '')}_x64_en-US.msi`,
|
||||
linux: downloadLinks.linux || `${baseUrl}/voicebox_x86_64-unknown-linux-gnu.AppImage`,
|
||||
},
|
||||
};
|
||||
|
||||
// Update cache
|
||||
cachedReleaseInfo = releaseInfo;
|
||||
cacheTimestamp = now;
|
||||
|
||||
return releaseInfo;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch latest release:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user