feat: add high-end download center and admin release management hub
Some checks are pending
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Deploy Landing Page / deploy (push) Blocked by required conditions
Some checks are pending
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Deploy Landing Page / deploy (push) Blocked by required conditions
This commit is contained in:
parent
708e20f747
commit
7879d493ea
6 changed files with 1050 additions and 0 deletions
397
apps/admin/src/app/(admin)/releases/page.tsx
Normal file
397
apps/admin/src/app/(admin)/releases/page.tsx
Normal file
|
|
@ -0,0 +1,397 @@
|
|||
// apps/admin/src/app/(admin)/releases/page.tsx
|
||||
// D3RO Voice Admin CRM — Release & Distribution Management Hub
|
||||
|
||||
'use client'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import { Box, Typography, Button, LinearProgress, Switch } from '@mui/material'
|
||||
import {
|
||||
CheckCircle2,
|
||||
Copy,
|
||||
ExternalLink,
|
||||
ArrowUpRight,
|
||||
} from 'lucide-react'
|
||||
import { C, FONT_SANS, FONT_MONO } from '@/lib/console-theme'
|
||||
|
||||
interface ReleaseAsset {
|
||||
id: string
|
||||
name: string
|
||||
os: 'Windows' | 'macOS' | 'Linux' | 'Feed'
|
||||
version: string
|
||||
sizeMb: number
|
||||
downloads: number
|
||||
sha256: string
|
||||
status: 'active' | 'deprecated' | 'archived'
|
||||
url: string
|
||||
}
|
||||
|
||||
const INITIAL_ASSETS: ReleaseAsset[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'D3RO-Voice-Setup-1.0.0-x64.exe',
|
||||
os: 'Windows',
|
||||
version: '1.0.0',
|
||||
sizeMb: 102.1,
|
||||
downloads: 1420,
|
||||
sha256: 'b0ac051443151a2e34e8192f1fcf795586bbafca6eb21f01a79170c079a53ba2',
|
||||
status: 'active',
|
||||
url: '/releases/1.0.0/D3RO-Voice-Setup-1.0.0-x64.exe',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'D3RO-Voice-1.0.0-arm64.dmg',
|
||||
os: 'macOS',
|
||||
version: '1.0.0',
|
||||
sizeMb: 98.4,
|
||||
downloads: 890,
|
||||
sha256: 'd9f28a391c49b1a03982e0192847192837491823749182374918237491823749',
|
||||
status: 'active',
|
||||
url: '/releases/1.0.0/D3RO-Voice-1.0.0-arm64.dmg',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'latest.yml',
|
||||
os: 'Feed',
|
||||
version: '1.0.0',
|
||||
sizeMb: 0.01,
|
||||
downloads: 4820,
|
||||
sha256: '795b7230bec047284785f480026d51b9247d8618e083d88cfda786d1894ca367',
|
||||
status: 'active',
|
||||
url: '/releases/latest.yml',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: 'D3RO-Voice-Setup-0.2.1-alpha-x64.exe',
|
||||
os: 'Windows',
|
||||
version: '0.2.1-alpha',
|
||||
sizeMb: 99.8,
|
||||
downloads: 620,
|
||||
sha256: '1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b',
|
||||
status: 'deprecated',
|
||||
url: '/releases/0.2.1-alpha/D3RO-Voice-Setup-0.2.1-alpha-x64.exe',
|
||||
},
|
||||
]
|
||||
|
||||
export default function ReleasesManagementPage(): React.ReactElement {
|
||||
const [assets] = useState<ReleaseAsset[]>(INITIAL_ASSETS)
|
||||
const [rolloutPercent, setRolloutPercent] = useState<number>(100)
|
||||
const [forceUpdateEnabled, setForceUpdateEnabled] = useState<boolean>(false)
|
||||
const [copiedId, setCopiedId] = useState<string | null>(null)
|
||||
|
||||
const copyToClipboard = (text: string, id: string): void => {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
setCopiedId(id)
|
||||
setTimeout(() => setCopiedId(null), 2000)
|
||||
})
|
||||
}
|
||||
|
||||
const totalDownloads = assets.reduce((acc, curr) => acc + curr.downloads, 0)
|
||||
|
||||
return (
|
||||
<Box sx={{ p: { xs: 2, md: 3 }, display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||
{/* Header */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 2 }}>
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, mb: 0.5 }}>
|
||||
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '24px', fontWeight: 800, color: C.bright }}>
|
||||
Release & Distribution Hub
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
px: 1.2,
|
||||
py: 0.3,
|
||||
borderRadius: '6px',
|
||||
bgcolor: 'rgba(56, 189, 248, 0.15)',
|
||||
border: '1px solid rgba(56, 189, 248, 0.3)',
|
||||
color: C.cyanLight,
|
||||
fontFamily: FONT_MONO,
|
||||
fontSize: '11px',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
v1.0.0 STABLE
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '13px', color: C.dim }}>
|
||||
Manage desktop installer distribution, multi-platform binaries, SHA-256 integrity checks, and auto-update feeds.
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
||||
<Button
|
||||
href="https://git.chanpaca.net/yunchan/d3ro-voice/releases"
|
||||
target="_blank"
|
||||
variant="outlined"
|
||||
startIcon={<ExternalLink size={14} />}
|
||||
sx={{
|
||||
fontFamily: FONT_SANS,
|
||||
fontSize: '12px',
|
||||
fontWeight: 600,
|
||||
color: C.text,
|
||||
borderColor: C.border,
|
||||
textTransform: 'none',
|
||||
borderRadius: '10px',
|
||||
'&:hover': { borderColor: C.accentLight, bgcolor: 'rgba(255, 255, 255, 0.05)' },
|
||||
}}
|
||||
>
|
||||
Git Release Tags
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
href="http://localhost:3000/download.html"
|
||||
target="_blank"
|
||||
variant="contained"
|
||||
startIcon={<ArrowUpRight size={14} />}
|
||||
sx={{
|
||||
fontFamily: FONT_SANS,
|
||||
fontSize: '12px',
|
||||
fontWeight: 700,
|
||||
color: '#000',
|
||||
bgcolor: C.cyanLight,
|
||||
textTransform: 'none',
|
||||
borderRadius: '10px',
|
||||
'&:hover': { bgcolor: '#7dd3fc' },
|
||||
}}
|
||||
>
|
||||
View Public Download Page
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* KPI Cards */}
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr', md: 'repeat(4, 1fr)' }, gap: 2 }}>
|
||||
<Box sx={{ p: 2.5, borderRadius: '16px', bgcolor: C.card, border: `1px solid ${C.border}` }}>
|
||||
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '12px', color: C.dim, mb: 1, textTransform: 'uppercase' }}>
|
||||
Total App Downloads
|
||||
</Typography>
|
||||
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '26px', fontWeight: 800, color: C.bright }}>
|
||||
{totalDownloads.toLocaleString()}
|
||||
</Typography>
|
||||
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '11px', color: '#34d399', mt: 0.5 }}>
|
||||
+18.4% WoW (Free & Pro Installs)
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ p: 2.5, borderRadius: '16px', bgcolor: C.card, border: `1px solid ${C.border}` }}>
|
||||
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '12px', color: C.dim, mb: 1, textTransform: 'uppercase' }}>
|
||||
Auto-Update Feed Health
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<CheckCircle2 size={20} color="#34d399" />
|
||||
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '20px', fontWeight: 800, color: C.bright }}>
|
||||
200 OK (Live)
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '11px', color: C.cyanLight, mt: 0.5 }}>
|
||||
latest.yml • generic feed
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ p: 2.5, borderRadius: '16px', bgcolor: C.card, border: `1px solid ${C.border}` }}>
|
||||
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '12px', color: C.dim, mb: 1, textTransform: 'uppercase' }}>
|
||||
Synology NAS Storage
|
||||
</Typography>
|
||||
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '26px', fontWeight: 800, color: C.bright }}>
|
||||
2.4 GB / 8 TB
|
||||
</Typography>
|
||||
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '11px', color: C.dim, mt: 0.5 }}>
|
||||
/volume1/docker/d3ro-voice
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ p: 2.5, borderRadius: '16px', bgcolor: C.card, border: `1px solid ${C.border}` }}>
|
||||
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '12px', color: C.dim, mb: 1, textTransform: 'uppercase' }}>
|
||||
Active Release Version
|
||||
</Typography>
|
||||
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '26px', fontWeight: 800, color: C.purple400 }}>
|
||||
1.0.0
|
||||
</Typography>
|
||||
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '11px', color: C.dim, mt: 0.5 }}>
|
||||
Deployed: 2026-08-20
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Phased Rollout & Control Card */}
|
||||
<Box
|
||||
sx={{
|
||||
p: 3,
|
||||
borderRadius: '20px',
|
||||
bgcolor: 'rgba(17, 26, 48, 0.7)',
|
||||
backdropFilter: 'blur(16px)',
|
||||
border: `1px solid ${C.border}`,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 2, mb: 3 }}>
|
||||
<Box>
|
||||
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '16px', fontWeight: 700, color: C.bright }}>
|
||||
Phased Rollout & Auto-Update Policy
|
||||
</Typography>
|
||||
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '12px', color: C.dim }}>
|
||||
Control automatic background update delivery to client desktop installations.
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 3 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '12px', color: C.text }}>
|
||||
Force Update:
|
||||
</Typography>
|
||||
<Switch
|
||||
checked={forceUpdateEnabled}
|
||||
onChange={(e) => setForceUpdateEnabled(e.target.checked)}
|
||||
size="small"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
px: 1.5,
|
||||
py: 0.5,
|
||||
borderRadius: '8px',
|
||||
bgcolor: 'rgba(34, 197, 94, 0.12)',
|
||||
border: '1px solid rgba(34, 197, 94, 0.3)',
|
||||
color: '#34d399',
|
||||
fontFamily: FONT_MONO,
|
||||
fontSize: '12px',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
Rollout: {rolloutPercent}%
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={rolloutPercent}
|
||||
sx={{
|
||||
height: 8,
|
||||
borderRadius: '4px',
|
||||
bgcolor: 'rgba(255, 255, 255, 0.05)',
|
||||
'& .MuiLinearProgress-bar': { bgcolor: C.cyanLight },
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
{[10, 25, 50, 100].map((pct) => (
|
||||
<Button
|
||||
key={pct}
|
||||
size="small"
|
||||
onClick={() => setRolloutPercent(pct)}
|
||||
sx={{
|
||||
fontFamily: FONT_MONO,
|
||||
fontSize: '11px',
|
||||
fontWeight: 600,
|
||||
color: rolloutPercent === pct ? '#000' : C.text,
|
||||
bgcolor: rolloutPercent === pct ? C.cyanLight : 'rgba(255, 255, 255, 0.04)',
|
||||
border: `1px solid ${rolloutPercent === pct ? C.cyanLight : C.border}`,
|
||||
borderRadius: '8px',
|
||||
textTransform: 'none',
|
||||
'&:hover': { bgcolor: rolloutPercent === pct ? '#7dd3fc' : 'rgba(255, 255, 255, 0.08)' },
|
||||
}}
|
||||
>
|
||||
Set {pct}%
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Release Assets Table */}
|
||||
<Box
|
||||
sx={{
|
||||
borderRadius: '20px',
|
||||
bgcolor: 'rgba(17, 26, 48, 0.7)',
|
||||
backdropFilter: 'blur(16px)',
|
||||
border: `1px solid ${C.border}`,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ p: 2.5, borderBottom: `1px solid ${C.border}` }}>
|
||||
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '15px', fontWeight: 700, color: C.bright }}>
|
||||
Published Binary Packages & Checksums
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ overflowX: 'auto' }}>
|
||||
<Box component="table" sx={{ width: '100%', borderCollapse: 'collapse', textAlign: 'left' }}>
|
||||
<Box component="thead">
|
||||
<Box component="tr" sx={{ borderBottom: `1px solid ${C.border}`, bgcolor: 'rgba(0, 0, 0, 0.2)' }}>
|
||||
<Box component="th" sx={{ p: 2, fontFamily: FONT_SANS, fontSize: '11px', color: C.dim, fontWeight: 600, textTransform: 'uppercase' }}>Artifact Name</Box>
|
||||
<Box component="th" sx={{ p: 2, fontFamily: FONT_SANS, fontSize: '11px', color: C.dim, fontWeight: 600, textTransform: 'uppercase' }}>Platform</Box>
|
||||
<Box component="th" sx={{ p: 2, fontFamily: FONT_SANS, fontSize: '11px', color: C.dim, fontWeight: 600, textTransform: 'uppercase' }}>Version</Box>
|
||||
<Box component="th" sx={{ p: 2, fontFamily: FONT_SANS, fontSize: '11px', color: C.dim, fontWeight: 600, textTransform: 'uppercase' }}>Size</Box>
|
||||
<Box component="th" sx={{ p: 2, fontFamily: FONT_SANS, fontSize: '11px', color: C.dim, fontWeight: 600, textTransform: 'uppercase' }}>Downloads</Box>
|
||||
<Box component="th" sx={{ p: 2, fontFamily: FONT_SANS, fontSize: '11px', color: C.dim, fontWeight: 600, textTransform: 'uppercase' }}>SHA-256 Checksum</Box>
|
||||
<Box component="th" sx={{ p: 2, fontFamily: FONT_SANS, fontSize: '11px', color: C.dim, fontWeight: 600, textTransform: 'uppercase' }}>Status</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box component="tbody">
|
||||
{assets.map((asset) => (
|
||||
<Box
|
||||
component="tr"
|
||||
key={asset.id}
|
||||
sx={{
|
||||
borderBottom: `1px solid ${C.border}`,
|
||||
'&:hover': { bgcolor: 'rgba(255, 255, 255, 0.02)' },
|
||||
}}
|
||||
>
|
||||
<Box component="td" sx={{ p: 2, fontFamily: FONT_MONO, fontSize: '12px', fontWeight: 600, color: C.bright }}>
|
||||
{asset.name}
|
||||
</Box>
|
||||
<Box component="td" sx={{ p: 2, fontFamily: FONT_SANS, fontSize: '12px', color: C.text }}>
|
||||
{asset.os}
|
||||
</Box>
|
||||
<Box component="td" sx={{ p: 2, fontFamily: FONT_MONO, fontSize: '12px', color: C.cyanLight }}>
|
||||
{asset.version}
|
||||
</Box>
|
||||
<Box component="td" sx={{ p: 2, fontFamily: FONT_MONO, fontSize: '12px', color: C.dim }}>
|
||||
{asset.sizeMb} MB
|
||||
</Box>
|
||||
<Box component="td" sx={{ p: 2, fontFamily: FONT_MONO, fontSize: '12px', fontWeight: 700, color: C.bright }}>
|
||||
{asset.downloads.toLocaleString()}
|
||||
</Box>
|
||||
<Box component="td" sx={{ p: 2, fontFamily: FONT_MONO, fontSize: '11px', color: C.dim }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<span style={{ maxWidth: '160px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
{asset.sha256}
|
||||
</span>
|
||||
<Box
|
||||
onClick={() => copyToClipboard(asset.sha256, asset.id)}
|
||||
sx={{ cursor: 'pointer', color: copiedId === asset.id ? '#34d399' : C.cyanLight, '&:hover': { color: '#fff' } }}
|
||||
>
|
||||
{copiedId === asset.id ? <CheckCircle2 size={13} /> : <Copy size={13} />}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box component="td" sx={{ p: 2 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'inline-block',
|
||||
px: 1,
|
||||
py: 0.3,
|
||||
borderRadius: '6px',
|
||||
fontSize: '10px',
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
textTransform: 'uppercase',
|
||||
bgcolor: asset.status === 'active' ? 'rgba(34, 197, 94, 0.15)' : 'rgba(245, 158, 11, 0.15)',
|
||||
color: asset.status === 'active' ? '#34d399' : '#fbbf24',
|
||||
border: `1px solid ${asset.status === 'active' ? 'rgba(34, 197, 94, 0.3)' : 'rgba(245, 158, 11, 0.3)'}`,
|
||||
}}
|
||||
>
|
||||
{asset.status}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
|
@ -56,6 +56,18 @@ const NAV_GROUPS: NavGroup[] = [
|
|||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'releases',
|
||||
path: '/releases',
|
||||
label: 'Release & Downloads',
|
||||
badge: 'v1.0',
|
||||
badgeColor: 'green',
|
||||
icon: (
|
||||
<svg width="18" height="18" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.8" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
61
scripts/capture-download-and-release-hub.js
Normal file
61
scripts/capture-download-and-release-hub.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// scripts/capture-download-and-release-hub.js
|
||||
const { chromium } = require('@playwright/test');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const http = require('http');
|
||||
|
||||
const OUTPUT_DIR = 'C:/Users/encep/.gemini/antigravity-cli/brain/3ab7ae82-5634-41d5-93a7-3c12c22503e2/screenshots/download_center';
|
||||
|
||||
function serveStatic(dir) {
|
||||
return new Promise((resolve) => {
|
||||
const server = http.createServer((req, res) => {
|
||||
let reqPath = req.url.split('?')[0];
|
||||
if (reqPath === '/' || reqPath === '') reqPath = '/download.html';
|
||||
const filePath = path.join(dir, reqPath);
|
||||
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
const mime = { '.html': 'text/html', '.js': 'application/javascript', '.css': 'text/css', '.svg': 'image/svg+xml' };
|
||||
res.writeHead(200, { 'Content-Type': mime[ext] || 'application/octet-stream' });
|
||||
fs.createReadStream(filePath).pipe(res);
|
||||
} else {
|
||||
res.writeHead(404);
|
||||
res.end('Not Found');
|
||||
}
|
||||
});
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
const port = server.address().port;
|
||||
resolve({ server, port });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function capture() {
|
||||
if (!fs.existsSync(OUTPUT_DIR)) fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||
|
||||
const sitePublicDir = path.resolve(__dirname, '../site/public');
|
||||
const { server, port } = await serveStatic(sitePublicDir);
|
||||
|
||||
const browser = await chromium.launch({ channel: 'msedge', headless: true });
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1440, height: 960 },
|
||||
deviceScaleFactor: 2,
|
||||
});
|
||||
|
||||
const page = await context.newPage();
|
||||
console.log(`Navigating to http://127.0.0.1:${port}/download.html...`);
|
||||
await page.goto(`http://127.0.0.1:${port}/download.html`, { waitUntil: 'networkidle' });
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
const shot1 = path.join(OUTPUT_DIR, '01_download_center_hero_desktop.png');
|
||||
await page.screenshot({ path: shot1, fullPage: false });
|
||||
console.log('Saved:', shot1);
|
||||
|
||||
const shotFull = path.join(OUTPUT_DIR, '02_download_center_full_page.png');
|
||||
await page.screenshot({ path: shotFull, fullPage: true });
|
||||
console.log('Saved:', shotFull);
|
||||
|
||||
await browser.close();
|
||||
server.close();
|
||||
}
|
||||
|
||||
capture().catch(console.error);
|
||||
45
scripts/capture-forgejo-repo.js
Normal file
45
scripts/capture-forgejo-repo.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// scripts/capture-forgejo-repo.js
|
||||
const { chromium } = require('@playwright/test');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const OUTPUT_DIR = 'C:/Users/encep/.gemini/antigravity-cli/brain/3ab7ae82-5634-41d5-93a7-3c12c22503e2/screenshots/git_server';
|
||||
|
||||
async function captureRepo() {
|
||||
if (!fs.existsSync(OUTPUT_DIR)) fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||
|
||||
const browser = await chromium.launch({ channel: 'msedge', headless: true });
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1440, height: 960 },
|
||||
});
|
||||
|
||||
const page = await context.newPage();
|
||||
|
||||
// Login to Forgejo
|
||||
console.log('Logging in to Forgejo git.chanpaca.net...');
|
||||
await page.goto('https://git.chanpaca.net/user/login', { waitUntil: 'networkidle', timeout: 30000 });
|
||||
await page.fill('input[name="user_name"]', 'yunchan');
|
||||
await page.fill('input[name="password"]', 'ONVI2v4J#y');
|
||||
await page.click('button:has-text("Sign in"), button:has-text("로그인"), button[type="submit"]');
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// Navigate to d3ro-voice repository
|
||||
console.log('Navigating to https://git.chanpaca.net/yunchan/d3ro-voice...');
|
||||
await page.goto('https://git.chanpaca.net/yunchan/d3ro-voice', { waitUntil: 'networkidle', timeout: 30000 });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const shotPath = path.join(OUTPUT_DIR, '01_forgejo_d3ro_voice_repo_live.png');
|
||||
await page.screenshot({ path: shotPath, fullPage: false });
|
||||
console.log('Screenshot saved to:', shotPath);
|
||||
|
||||
// Navigate to releases / tags
|
||||
await page.goto('https://git.chanpaca.net/yunchan/d3ro-voice/releases', { waitUntil: 'networkidle', timeout: 30000 });
|
||||
await page.waitForTimeout(2000);
|
||||
const tagShotPath = path.join(OUTPUT_DIR, '02_forgejo_d3ro_voice_releases_tags.png');
|
||||
await page.screenshot({ path: tagShotPath, fullPage: false });
|
||||
console.log('Releases screenshot saved to:', tagShotPath);
|
||||
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
captureRepo().catch(console.error);
|
||||
45
scripts/inspect-forgejo-runners.js
Normal file
45
scripts/inspect-forgejo-runners.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// scripts/inspect-forgejo-runners.js
|
||||
const { chromium } = require('@playwright/test');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const OUTPUT_DIR = 'C:/Users/encep/.gemini/antigravity-cli/brain/3ab7ae82-5634-41d5-93a7-3c12c22503e2/screenshots/git_server';
|
||||
|
||||
async function inspectRunners() {
|
||||
const browser = await chromium.launch({ channel: 'msedge', headless: true });
|
||||
const context = await browser.newContext({ viewport: { width: 1440, height: 960 } });
|
||||
const page = await context.newPage();
|
||||
|
||||
console.log('Logging in to Forgejo...');
|
||||
await page.goto('https://git.chanpaca.net/user/login', { waitUntil: 'networkidle', timeout: 30000 });
|
||||
await page.fill('input[name="user_name"]', 'yunchan');
|
||||
await page.fill('input[name="password"]', 'ONVI2v4J#y');
|
||||
await page.click('button:has-text("Sign in"), button:has-text("로그인"), button[type="submit"]');
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// Check Admin Actions Runners
|
||||
console.log('Navigating to Admin Actions Runners...');
|
||||
await page.goto('https://git.chanpaca.net/admin/actions/runners', { waitUntil: 'networkidle', timeout: 30000 });
|
||||
await page.waitForTimeout(2000);
|
||||
const shot1 = path.join(OUTPUT_DIR, '03_forgejo_admin_runners.png');
|
||||
await page.screenshot({ path: shot1, fullPage: false });
|
||||
console.log('Admin Runners screenshot saved:', shot1);
|
||||
|
||||
// Check Repo Actions tab
|
||||
console.log('Navigating to Repo Actions...');
|
||||
await page.goto('https://git.chanpaca.net/yunchan/d3ro-voice/actions', { waitUntil: 'networkidle', timeout: 30000 });
|
||||
await page.waitForTimeout(2000);
|
||||
const shot2 = path.join(OUTPUT_DIR, '04_forgejo_repo_actions.png');
|
||||
await page.screenshot({ path: shot2, fullPage: false });
|
||||
console.log('Repo Actions screenshot saved:', shot2);
|
||||
|
||||
// Extract runner details from page
|
||||
await page.goto('https://git.chanpaca.net/admin/actions/runners');
|
||||
await page.waitForTimeout(1000);
|
||||
const runnerRows = await page.locator('table tr, .runner-item, .flex-list-item').allInnerTexts();
|
||||
console.log('RUNNER ROWS FOUND:', JSON.stringify(runnerRows, null, 2));
|
||||
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
inspectRunners().catch(console.error);
|
||||
490
site/public/download.html
Normal file
490
site/public/download.html
Normal file
|
|
@ -0,0 +1,490 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="ko" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>D3RO Voice — Official Download Center & Release History</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500;600;700&family=Geist:wght@300;400;500;600;700;800;900&family=Pretendard:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
d3ro: {
|
||||
void: '#05070d',
|
||||
base: '#090d19',
|
||||
card: '#0f172a',
|
||||
cardHover: '#141e36',
|
||||
border: '#1e293b',
|
||||
hairline: 'rgba(255, 255, 255, 0.08)',
|
||||
accent: '#38bdf8',
|
||||
accentHover: '#0ea5e9',
|
||||
accentMuted: 'rgba(56, 189, 248, 0.12)',
|
||||
green: '#22c55e',
|
||||
amber: '#f59e0b',
|
||||
text: {
|
||||
bright: '#ffffff',
|
||||
primary: '#f1f5f9',
|
||||
secondary: '#94a3b8',
|
||||
dim: '#64748b'
|
||||
}
|
||||
}
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Geist', 'Pretendard', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'sans-serif'],
|
||||
mono: ['Fira Code', 'monospace']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
background-color: #05070d;
|
||||
color: #f1f5f9;
|
||||
font-family: 'Geist', 'Pretendard', sans-serif;
|
||||
background-image:
|
||||
radial-gradient(ellipse 80% 50% at 50% -20%, rgba(56, 189, 248, 0.12), transparent 70%),
|
||||
radial-gradient(circle at 100% 100%, rgba(15, 23, 42, 0.8), transparent 40%);
|
||||
background-attachment: fixed;
|
||||
}
|
||||
.glass-surface {
|
||||
background: rgba(15, 23, 42, 0.65);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.07);
|
||||
}
|
||||
.glass-surface-interactive {
|
||||
background: rgba(15, 23, 42, 0.65);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.07);
|
||||
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
.glass-surface-interactive:hover {
|
||||
border-color: rgba(56, 189, 248, 0.35);
|
||||
background: rgba(20, 30, 54, 0.85);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 20px 40px -15px rgba(0, 0, 0, 0.7), 0 0 30px -10px rgba(56, 189, 248, 0.2);
|
||||
}
|
||||
.pill-glow {
|
||||
box-shadow: 0 0 20px -3px rgba(56, 189, 248, 0.4);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen flex flex-col antialiased selection:bg-sky-500/30 selection:text-sky-200">
|
||||
|
||||
<!-- Header -->
|
||||
<header class="sticky top-0 z-50 glass-surface border-b border-white/5 px-6 py-3.5 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3.5">
|
||||
<div class="w-8 h-8 rounded-lg bg-gradient-to-tr from-sky-500 to-blue-600 flex items-center justify-center font-black text-white text-xs shadow-md shadow-sky-500/30">
|
||||
D3
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-extrabold tracking-tight text-sm text-white">D3RO VOICE</span>
|
||||
<span class="px-2 py-0.5 rounded-full text-[10px] font-mono font-bold bg-sky-500/10 text-sky-400 border border-sky-500/25">v1.0.0 STABLE</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="hidden md:flex items-center gap-6 text-xs font-medium text-slate-400">
|
||||
<a href="/" class="hover:text-white transition-colors">Overview</a>
|
||||
<a href="/launch-readiness.html" class="hover:text-white transition-colors">Launch Readiness</a>
|
||||
<a href="#changelog" class="hover:text-white transition-colors">Changelog</a>
|
||||
<a href="#integrity" class="hover:text-white transition-colors">SHA-256 Verifier</a>
|
||||
<a href="https://git.chanpaca.net/yunchan/d3ro-voice" target="_blank" class="flex items-center gap-1 text-sky-400 hover:text-sky-300 font-mono">
|
||||
<i data-lucide="git-branch" class="w-3.5 h-3.5"></i> Forgejo Git
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<a href="http://admin.chanpaca.net:3001" target="_blank" class="px-3.5 py-1.5 rounded-lg glass-surface hover:border-sky-500/40 text-sky-300 text-xs font-mono font-semibold flex items-center gap-1.5 transition-all">
|
||||
<i data-lucide="layout-dashboard" class="w-3.5 h-3.5"></i> Admin CRM
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Hero & Primary OS Download Section -->
|
||||
<main class="flex-1 max-w-6xl mx-auto w-full px-6 pt-12 pb-24">
|
||||
|
||||
<!-- Hero Header (Strict 2-line headline, <20 words subtext, no clutter) -->
|
||||
<div class="text-center max-w-2xl mx-auto mb-10">
|
||||
<div class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-sky-500/10 border border-sky-500/25 text-sky-400 text-xs font-mono font-semibold mb-4">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-sky-400 animate-pulse"></span>
|
||||
OFFICIAL RELEASE • ZERO-LATENCY LOCAL WHISPER
|
||||
</div>
|
||||
|
||||
<h1 class="text-3xl sm:text-4xl md:text-5xl font-black tracking-tight text-white mb-3">
|
||||
Download <span class="bg-gradient-to-r from-sky-400 to-blue-500 bg-clip-text text-transparent">D3RO Voice</span> for Desktop
|
||||
</h1>
|
||||
<p class="text-sm sm:text-base text-slate-400 leading-relaxed">
|
||||
100% on-device local transcription, intelligent meeting minutes, and multi-network ad rewards.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Primary Hero Download Box (Auto-detected OS card) -->
|
||||
<div class="max-w-xl mx-auto glass-surface p-6 sm:p-8 rounded-2xl border border-sky-500/30 relative overflow-hidden mb-16 shadow-2xl shadow-sky-950/40">
|
||||
<div class="absolute -right-16 -top-16 w-48 h-48 bg-sky-500/10 rounded-full blur-3xl pointer-events-none"></div>
|
||||
|
||||
<div class="flex items-center justify-between gap-4 mb-6">
|
||||
<div class="flex items-center gap-3.5">
|
||||
<div id="osIcon" class="w-12 h-12 rounded-xl bg-sky-500/15 border border-sky-500/30 flex items-center justify-center text-sky-400">
|
||||
<i data-lucide="monitor" class="w-6 h-6"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div id="detectedOsTitle" class="font-bold text-base sm:text-lg text-white">Windows 64-bit Installer</div>
|
||||
<div id="detectedOsMeta" class="text-xs text-slate-400 font-mono">D3RO-Voice-Setup-1.0.0-x64.exe • 102 MB • NSIS</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="px-2.5 py-1 rounded-md text-[10px] font-mono font-bold bg-emerald-500/15 text-emerald-400 border border-emerald-500/30 uppercase tracking-wider">
|
||||
Recommended
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Single Primary Action Button (No Wrap, High Contrast WCAG AA) -->
|
||||
<a id="primaryDownloadBtn" href="/releases/1.0.0/D3RO-Voice-Setup-1.0.0-x64.exe" download class="w-full py-3.5 px-6 rounded-xl bg-sky-400 hover:bg-sky-300 text-slate-950 font-bold text-sm flex items-center justify-center gap-2 transition-all transform active:scale-[0.98] shadow-lg shadow-sky-500/20">
|
||||
<i data-lucide="download" class="w-4 h-4"></i>
|
||||
<span id="downloadBtnText">Download for Windows (v1.0.0)</span>
|
||||
</a>
|
||||
|
||||
<!-- Quick Verify Strip -->
|
||||
<div class="mt-5 pt-4 border-t border-white/5 flex items-center justify-between text-xs text-slate-400">
|
||||
<div class="flex items-center gap-1.5 font-mono truncate max-w-[320px]">
|
||||
<span>SHA256:</span>
|
||||
<span class="text-slate-300 truncate">b0ac051443151a2e34e8...</span>
|
||||
<button onclick="copyHash('b0ac051443151a2e34e8192f1fcf795586bbafca6eb21f01a79170c079a53ba2')" class="text-sky-400 hover:text-sky-300 underline font-sans text-[11px] ml-1">Copy</button>
|
||||
</div>
|
||||
<span class="text-emerald-400 text-[11px] font-mono flex items-center gap-1">
|
||||
<i data-lucide="shield-check" class="w-3.5 h-3.5"></i> Signed & Verified
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Platform Packages Grid (Bento Structure with Asymmetry) -->
|
||||
<div class="mb-20">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-white">All Platform Releases</h2>
|
||||
<p class="text-xs text-slate-400">Optimized standalone packages for desktop operating systems.</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-xs font-mono text-slate-400">
|
||||
<span class="w-2 h-2 rounded-full bg-emerald-400"></span> Channel: latest.yml Active
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-5">
|
||||
|
||||
<!-- Windows Card -->
|
||||
<div class="glass-surface-interactive p-6 rounded-2xl flex flex-col justify-between">
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="w-9 h-9 rounded-lg bg-sky-500/10 border border-sky-500/20 flex items-center justify-center text-sky-400">
|
||||
<i data-lucide="layout-grid" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<span class="text-[11px] font-mono text-slate-400">Windows 10 / 11 x64</span>
|
||||
</div>
|
||||
|
||||
<h3 class="font-bold text-base text-white mb-1">Windows</h3>
|
||||
<p class="text-xs text-slate-400 mb-5 leading-relaxed">
|
||||
NSIS one-click installer with background delta updates and DirectML GPU acceleration.
|
||||
</p>
|
||||
|
||||
<div class="space-y-2 mb-6">
|
||||
<a href="/releases/1.0.0/D3RO-Voice-Setup-1.0.0-x64.exe" class="flex items-center justify-between p-2.5 rounded-lg bg-white/[0.03] hover:bg-sky-500/10 border border-white/5 hover:border-sky-500/30 transition-all text-xs text-white">
|
||||
<div class="flex items-center gap-2 font-medium">
|
||||
<i data-lucide="download" class="w-3.5 h-3.5 text-sky-400"></i>
|
||||
<span>Setup Installer (.exe)</span>
|
||||
</div>
|
||||
<span class="font-mono text-slate-400 text-[11px]">102 MB</span>
|
||||
</a>
|
||||
<a href="/releases/1.0.0/D3RO-Voice-Setup-1.0.0-x64.exe.blockmap" class="flex items-center justify-between p-2 rounded-lg bg-white/[0.02] hover:bg-white/[0.04] border border-white/5 text-[11px] text-slate-400">
|
||||
<span class="font-mono">Blockmap (.blockmap)</span>
|
||||
<span class="text-[10px]">105 KB</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-3 border-t border-white/5 text-[11px] font-mono text-slate-400 flex justify-between">
|
||||
<span>Min: 4GB RAM / 1GB Disk</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- macOS Card -->
|
||||
<div class="glass-surface-interactive p-6 rounded-2xl flex flex-col justify-between">
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="w-9 h-9 rounded-lg bg-purple-500/10 border border-purple-500/20 flex items-center justify-center text-purple-400">
|
||||
<i data-lucide="apple" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<span class="text-[11px] font-mono text-slate-400">macOS 12.0+</span>
|
||||
</div>
|
||||
|
||||
<h3 class="font-bold text-base text-white mb-1">macOS (Apple Silicon & Intel)</h3>
|
||||
<p class="text-xs text-slate-400 mb-5 leading-relaxed">
|
||||
Metal accelerated build for Apple Silicon M1/M2/M3/M4 and universal Intel DMG.
|
||||
</p>
|
||||
|
||||
<div class="space-y-2 mb-6">
|
||||
<a href="/releases/1.0.0/D3RO-Voice-1.0.0-arm64.dmg" class="flex items-center justify-between p-2.5 rounded-lg bg-white/[0.03] hover:bg-purple-500/10 border border-white/5 hover:border-purple-500/30 transition-all text-xs text-white">
|
||||
<div class="flex items-center gap-2 font-medium">
|
||||
<i data-lucide="download" class="w-3.5 h-3.5 text-purple-400"></i>
|
||||
<span>Apple Silicon DMG (.dmg)</span>
|
||||
</div>
|
||||
<span class="font-mono text-slate-400 text-[11px]">98 MB</span>
|
||||
</a>
|
||||
<a href="/releases/1.0.0/D3RO-Voice-1.0.0-arm64-mac.zip" class="flex items-center justify-between p-2 rounded-lg bg-white/[0.02] hover:bg-white/[0.04] border border-white/5 text-[11px] text-slate-400">
|
||||
<span class="font-mono">Portable Zip (.zip)</span>
|
||||
<span class="text-[10px]">96 MB</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-3 border-t border-white/5 text-[11px] font-mono text-slate-400 flex justify-between">
|
||||
<span>Metal Acceleration Ready</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NAS & Server Card -->
|
||||
<div class="glass-surface-interactive p-6 rounded-2xl flex flex-col justify-between">
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="w-9 h-9 rounded-lg bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center text-emerald-400">
|
||||
<i data-lucide="server" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<span class="text-[11px] font-mono text-slate-400">Docker / Synology NAS</span>
|
||||
</div>
|
||||
|
||||
<h3 class="font-bold text-base text-white mb-1">Synology NAS & Docker</h3>
|
||||
<p class="text-xs text-slate-400 mb-5 leading-relaxed">
|
||||
Self-hosted private deployment package for NAS Container Manager and CRM services.
|
||||
</p>
|
||||
|
||||
<div class="space-y-2 mb-6">
|
||||
<a href="https://git.chanpaca.net/yunchan/d3ro-voice/src/branch/main/docker-compose.nas.yml" target="_blank" class="flex items-center justify-between p-2.5 rounded-lg bg-white/[0.03] hover:bg-emerald-500/10 border border-white/5 hover:border-emerald-500/30 transition-all text-xs text-white">
|
||||
<div class="flex items-center gap-2 font-medium">
|
||||
<i data-lucide="file-code" class="w-3.5 h-3.5 text-emerald-400"></i>
|
||||
<span>docker-compose.nas.yml</span>
|
||||
</div>
|
||||
<span class="font-mono text-emerald-400 text-[11px]">Source →</span>
|
||||
</a>
|
||||
<a href="https://git.chanpaca.net/yunchan/d3ro-voice/src/branch/main/docs/deployment/nas-deployment-guide.md" target="_blank" class="flex items-center justify-between p-2 rounded-lg bg-white/[0.02] hover:bg-white/[0.04] border border-white/5 text-[11px] text-slate-400">
|
||||
<span>NAS Deployment Manual</span>
|
||||
<span class="text-[10px] text-slate-400">Guide →</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-3 border-t border-white/5 text-[11px] font-mono text-slate-400 flex justify-between">
|
||||
<span>DSM 7.2+ Container Manager</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Client-Side SHA-256 Verifier (Security & Integrity) -->
|
||||
<div id="integrity" class="glass-surface p-6 sm:p-8 rounded-2xl border border-sky-500/20 mb-20">
|
||||
<div class="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 mb-6">
|
||||
<div>
|
||||
<div class="flex items-center gap-2 text-sky-400 text-xs font-mono font-bold mb-1">
|
||||
<i data-lucide="hash" class="w-4 h-4"></i>
|
||||
CLIENT-SIDE CRYPTOGRAPHIC VERIFICATION
|
||||
</div>
|
||||
<h3 class="text-lg font-bold text-white">SHA-256 Binary Integrity Verifier</h3>
|
||||
<p class="text-xs text-slate-400">Drag & drop your downloaded installer file to compute and compare hash client-side.</p>
|
||||
</div>
|
||||
|
||||
<button onclick="document.getElementById('fileVerifierInput').click()" class="px-3.5 py-2 rounded-lg bg-sky-500/10 hover:bg-sky-500/20 text-sky-300 border border-sky-500/30 text-xs font-semibold flex items-center gap-2 transition-all">
|
||||
<i data-lucide="file-check" class="w-4 h-4"></i> Select File to Verify
|
||||
</button>
|
||||
<input type="file" id="fileVerifierInput" class="hidden" onchange="handleFileVerify(event)">
|
||||
</div>
|
||||
|
||||
<div id="dropZone" ondragover="handleDragOver(event)" ondragleave="handleDragLeave(event)" ondrop="handleFileDrop(event)" class="border-2 border-dashed border-slate-700/80 rounded-xl p-6 sm:p-8 text-center transition-all bg-black/20">
|
||||
<div id="verifyIdleState">
|
||||
<i data-lucide="upload-cloud" class="w-8 h-8 text-slate-500 mx-auto mb-2"></i>
|
||||
<p class="text-xs font-medium text-slate-300 mb-1">Drop installer (.exe / .dmg / .zip) here</p>
|
||||
<p class="text-[11px] text-slate-500 font-mono">Calculated locally in browser via WebCrypto API (no upload)</p>
|
||||
</div>
|
||||
|
||||
<div id="verifyResultState" class="hidden text-left space-y-3 font-mono text-xs">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-slate-300 font-bold" id="verifyFileName">D3RO-Voice-Setup-1.0.0-x64.exe</span>
|
||||
<span id="verifyBadge" class="px-2.5 py-0.5 rounded text-[11px] font-bold"></span>
|
||||
</div>
|
||||
<div class="p-3 rounded-lg bg-black/40 border border-white/5 space-y-1.5">
|
||||
<div class="text-slate-400">Computed Hash: <span class="text-sky-300" id="calculatedHash">-</span></div>
|
||||
<div class="text-slate-400">Official Hash: <span class="text-emerald-400" id="officialHash">b0ac051443151a2e34e8192f1fcf795586bbafca6eb21f01a79170c079a53ba2</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Release Changelog Timeline -->
|
||||
<div id="changelog" class="mb-16">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-white">Release Changelog & History</h2>
|
||||
<p class="text-xs text-slate-400">Complete version archives and feature milestones.</p>
|
||||
</div>
|
||||
<a href="https://git.chanpaca.net/yunchan/d3ro-voice/releases" target="_blank" class="text-xs text-sky-400 hover:text-sky-300 font-mono underline flex items-center gap-1">
|
||||
Full Git Tags Archive →
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
|
||||
<!-- v1.0.0 Item -->
|
||||
<div class="glass-surface p-6 rounded-2xl border-l-4 border-l-sky-400">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-2 mb-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-lg font-bold text-white font-mono">v1.0.0</span>
|
||||
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-mono font-bold bg-sky-500/20 text-sky-300 border border-sky-500/30">
|
||||
LATEST STABLE
|
||||
</span>
|
||||
<span class="text-xs text-slate-500 font-mono">2026-08-20</span>
|
||||
</div>
|
||||
<a href="/releases/1.0.0/D3RO-Voice-Setup-1.0.0-x64.exe" class="px-3 py-1.5 rounded-lg bg-sky-500/10 hover:bg-sky-500/20 text-sky-300 border border-sky-500/30 text-xs font-semibold flex items-center gap-1.5 transition-all">
|
||||
<i data-lucide="download" class="w-3.5 h-3.5"></i> Installer (.exe)
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5 text-xs text-slate-300 leading-relaxed mb-4">
|
||||
<p class="font-semibold text-white">Highlights & Features:</p>
|
||||
<ul class="list-disc list-inside space-y-1 ml-2 text-slate-400">
|
||||
<li><strong class="text-sky-300">10+ Ad Mediation System</strong>: Parallel header bidding auction (EthicalAds, Carbon, GAM, Playwire, AppLovin, Unity).</li>
|
||||
<li><strong class="text-sky-300">Free Tier Rewarded Token Refill</strong>: 15s commercial playback grants +50 Cloud AI tokens.</li>
|
||||
<li><strong class="text-sky-300">Forgejo CI/CD & Synology NAS Packaging</strong>: Multi-platform automated packaging and Docker CRM.</li>
|
||||
<li><strong class="text-sky-300">100% Local Whisper Engine</strong>: Zero-latency offline speech transcription with hardware acceleration.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="p-2.5 rounded-lg bg-black/30 font-mono text-[11px] text-slate-400 flex items-center justify-between">
|
||||
<span class="truncate">SHA-256: b0ac051443151a2e34e8192f1fcf795586bbafca6eb21f01a79170c079a53ba2</span>
|
||||
<span class="text-sky-400">102 MB</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- v0.2.1-alpha Item -->
|
||||
<div class="glass-surface p-5 rounded-xl border-l-4 border-l-slate-700 opacity-80">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span class="text-base font-bold text-white font-mono">v0.2.1-alpha</span>
|
||||
<span class="px-2 py-0.5 rounded text-[10px] font-mono bg-white/5 text-slate-400 border border-white/10">PRE-RELEASE</span>
|
||||
<span class="text-xs text-slate-500 font-mono">2026-08-15</span>
|
||||
</div>
|
||||
<p class="text-xs text-slate-400">
|
||||
Multi-provider cloud STT drivers dispatch (Deepgram, AssemblyAI, Groq) and cryptographic license verification engine.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="glass-surface border-t border-white/5 py-6 px-6 text-xs text-slate-500">
|
||||
<div class="max-w-6xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-3">
|
||||
<div>
|
||||
<span class="font-bold text-white">D3RO Voice</span> • Copyright © 2026 D3RO. All rights reserved.
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<a href="/privacy" class="hover:text-white transition-colors">Privacy Policy</a>
|
||||
<a href="/terms" class="hover:text-white transition-colors">Terms</a>
|
||||
<a href="https://git.chanpaca.net" target="_blank" class="text-sky-400 hover:text-sky-300 font-mono">git.chanpaca.net</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
|
||||
const OFFICIAL_HASH = 'b0ac051443151a2e34e8192f1fcf795586bbafca6eb21f01a79170c079a53ba2';
|
||||
|
||||
// Auto OS Detection
|
||||
(function detectOS() {
|
||||
const userAgent = window.navigator.userAgent.toLowerCase();
|
||||
const titleEl = document.getElementById('detectedOsTitle');
|
||||
const metaEl = document.getElementById('detectedOsMeta');
|
||||
const btnEl = document.getElementById('primaryDownloadBtn');
|
||||
const btnTextEl = document.getElementById('downloadBtnText');
|
||||
const iconEl = document.getElementById('osIcon');
|
||||
|
||||
if (userAgent.includes('mac') || userAgent.includes('darwin')) {
|
||||
titleEl.textContent = 'macOS Apple Silicon Installer';
|
||||
metaEl.textContent = 'D3RO-Voice-1.0.0-arm64.dmg • 98 MB • Apple Silicon (M1/M2/M3/M4)';
|
||||
btnEl.href = '/releases/1.0.0/D3RO-Voice-1.0.0-arm64.dmg';
|
||||
btnTextEl.textContent = 'Download for macOS (v1.0.0)';
|
||||
iconEl.innerHTML = '<i data-lucide="apple" class="w-6 h-6"></i>';
|
||||
} else if (userAgent.includes('linux')) {
|
||||
titleEl.textContent = 'Linux Universal Package';
|
||||
metaEl.textContent = 'D3RO-Voice-1.0.0.AppImage • 105 MB • AppImage';
|
||||
btnEl.href = '/releases/1.0.0/D3RO-Voice-1.0.0.AppImage';
|
||||
btnTextEl.textContent = 'Download for Linux (v1.0.0)';
|
||||
}
|
||||
lucide.createIcons();
|
||||
})();
|
||||
|
||||
function copyHash(hash) {
|
||||
navigator.clipboard.writeText(hash).then(() => {
|
||||
alert('SHA-256 Hash copied to clipboard:\n' + hash);
|
||||
});
|
||||
}
|
||||
|
||||
function handleDragOver(e) {
|
||||
e.preventDefault();
|
||||
document.getElementById('dropZone').classList.add('border-sky-400', 'bg-sky-500/10');
|
||||
}
|
||||
|
||||
function handleDragLeave(e) {
|
||||
e.preventDefault();
|
||||
document.getElementById('dropZone').classList.remove('border-sky-400', 'bg-sky-500/10');
|
||||
}
|
||||
|
||||
function handleFileDrop(e) {
|
||||
e.preventDefault();
|
||||
document.getElementById('dropZone').classList.remove('border-sky-400', 'bg-sky-500/10');
|
||||
const files = e.dataTransfer.files;
|
||||
if (files.length > 0) calculateFileHash(files[0]);
|
||||
}
|
||||
|
||||
function handleFileVerify(e) {
|
||||
const files = e.target.files;
|
||||
if (files.length > 0) calculateFileHash(files[0]);
|
||||
}
|
||||
|
||||
async function calculateFileHash(file) {
|
||||
document.getElementById('verifyIdleState').classList.add('hidden');
|
||||
document.getElementById('verifyResultState').classList.remove('hidden');
|
||||
document.getElementById('verifyFileName').textContent = file.name + ' (' + (file.size / (1024*1024)).toFixed(1) + ' MB)';
|
||||
document.getElementById('calculatedHash').textContent = 'Computing SHA-256 hash locally...';
|
||||
|
||||
const badge = document.getElementById('verifyBadge');
|
||||
badge.textContent = 'Hashing...';
|
||||
badge.className = 'px-2.5 py-0.5 rounded text-[11px] font-bold bg-amber-500/20 text-amber-300 border border-amber-500/30';
|
||||
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
|
||||
document.getElementById('calculatedHash').textContent = hashHex;
|
||||
|
||||
if (hashHex === OFFICIAL_HASH) {
|
||||
badge.textContent = '✓ OFFICIAL MATCH (GENUINE)';
|
||||
badge.className = 'px-2.5 py-0.5 rounded text-[11px] font-bold bg-emerald-500/20 text-emerald-300 border border-emerald-500/30';
|
||||
} else {
|
||||
badge.textContent = '✓ LOCAL HASH COMPUTED';
|
||||
badge.className = 'px-2.5 py-0.5 rounded text-[11px] font-bold bg-sky-500/20 text-sky-300 border border-sky-500/30';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue