mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-17 22:00:40 -07:00
- Replace Qwen-specific copy with multi-engine messaging across hero, meta, and features - Add GitHub star count fetched server-side via /api/stars with Spacedrive-style navbar badge - Replace 'Why Voicebox exists' section with model cards for all 4 TTS engines - Enable Linux download card (was 'Coming soon') - Update GPU support copy to include ROCm, Intel Arc, DirectML - Add Voice Creator section with animated 3-tab UI (upload, mic, system audio) and waveform background - Make ControlUI responsive: horizontal scroll cards on mobile, stacked layout, scroll-to-active profile - Fix iOS Safari audio autoplay (unlock AudioContext on user gesture) - Fix hero logo square background with mix-blend-lighten - Remove generation length green coloring, use gray with accent highlights - Comment out grain overlay (visible tile seams) - Remove player close button, stack waveform above controls on mobile - Fixed-height profile cards (143px) with space between badges and buttons
134 lines
3.9 KiB
TypeScript
134 lines
3.9 KiB
TypeScript
// 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
|
|
|
|
// Cache for star count
|
|
let cachedStarCount: number | null = null;
|
|
let starCacheTimestamp: number = 0;
|
|
|
|
/**
|
|
* 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;
|
|
|
|
// Skip signature files and other non-downloadable files
|
|
if (name.endsWith('.sig') || name.endsWith('.json') || name.endsWith('.txt')) {
|
|
continue;
|
|
}
|
|
|
|
if ((name.includes('aarch64') || name.includes('arm64')) && name.endsWith('.app.tar.gz')) {
|
|
downloadLinks.macArm = url;
|
|
} else if (name.includes('x64') && name.endsWith('.app.tar.gz')) {
|
|
downloadLinks.macIntel = url;
|
|
} else if (name.endsWith('.msi')) {
|
|
downloadLinks.windows = url;
|
|
} else if (name.endsWith('.appimage') || name.endsWith('.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_aarch64.app.tar.gz`,
|
|
macIntel: downloadLinks.macIntel || `${baseUrl}/voicebox_x64.app.tar.gz`,
|
|
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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetches the star count for the repo from GitHub
|
|
*/
|
|
export async function getStarCount(): Promise<number> {
|
|
const now = Date.now();
|
|
if (cachedStarCount !== null && now - starCacheTimestamp < CACHE_DURATION) {
|
|
return cachedStarCount;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${GITHUB_API_BASE}/repos/${GITHUB_REPO}`, {
|
|
next: { revalidate: 600 },
|
|
headers: {
|
|
Accept: 'application/vnd.github.v3+json',
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`GitHub API error: ${response.status}`);
|
|
}
|
|
|
|
const repo = await response.json();
|
|
const count = repo.stargazers_count ?? 0;
|
|
|
|
cachedStarCount = count;
|
|
starCacheTimestamp = now;
|
|
|
|
return count;
|
|
} catch (error) {
|
|
console.error('Failed to fetch star count:', error);
|
|
if (cachedStarCount !== null) return cachedStarCount;
|
|
throw error;
|
|
}
|
|
}
|