mirror of
https://github.com/mileswolfallen2/miles-web.git
synced 2026-09-08 11:23:17 +00:00
266 lines
15 KiB
HTML
266 lines
15 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Building Nova AI | Miles Wolf Allen</title>
|
|
<meta name="description" content="How I built Nova AI — a local AI assistant with desktop GUI, web interface, and CLI. No cloud, no subscriptions, no data leaving your machine.">
|
|
<link rel="canonical" href="https://milesallen.site/blog/nova-devlog.html">
|
|
<meta property="og:title" content="Building Nova AI | Miles Wolf Allen">
|
|
<meta property="og:description" content="How I built Nova AI — a local AI assistant with desktop GUI, web interface, and CLI. No cloud, no subscriptions, no data leaving your machine.">
|
|
<meta property="og:image" content="https://milesallen.site/og.png">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Space+Mono:wght@400;700&family=Inter:wght@400;500;600;800&display=swap" rel="stylesheet">
|
|
<link rel="icon" type="image/svg+xml" href="../favicon.svg">
|
|
<link rel="stylesheet" href="../projects/style.css">
|
|
<style>
|
|
.article-header{padding:100px 0 40px;}
|
|
.article-eyebrow{font-family:var(--font-pixel);font-size:10px;color:var(--tape);margin-bottom:12px;text-transform:uppercase;letter-spacing:.1em;}
|
|
.article-date{font-family:var(--font-mono);font-size:12px;color:var(--muted);margin-bottom:8px;}
|
|
.article-header h1{font-family:var(--font-pixel);font-size:clamp(24px,5vw,44px);line-height:1.2;margin-bottom:16px;transform:rotate(-1deg);transform-origin:left center;text-shadow:3px 3px 0 var(--accent),-2px -2px 0 var(--tape);animation:glitch 5s ease-in-out infinite;}
|
|
@keyframes glitch{0%,90%,100%{text-shadow:3px 3px 0 var(--accent),-2px -2px 0 var(--tape);}92%{text-shadow:3px 3px 0 var(--red),-2px -2px 0 var(--accent);transform:rotate(-1deg) translateX(2px);}94%{text-shadow:3px 3px 0 var(--accent),-2px -2px 0 var(--red);transform:rotate(-1deg) translateX(-2px);}}
|
|
.article-tagline{font-size:16px;color:var(--muted);max-width:600px;line-height:1.7;font-family:var(--font-mono);margin-bottom:24px;}
|
|
.article-tags{display:flex;gap:8px;flex-wrap:wrap;}
|
|
.article-tag{font-family:var(--font-pixel);font-size:8px;padding:6px 12px;background:var(--accent);color:#000;text-transform:uppercase;}
|
|
.article-body{max-width:720px;}
|
|
.article-body h2{font-family:var(--font-pixel);font-size:16px;color:var(--accent);margin:40px 0 16px;text-transform:uppercase;}
|
|
.article-body h3{font-family:var(--font-mono);font-weight:700;font-size:16px;margin:32px 0 12px;color:var(--text);}
|
|
.article-body p{font-size:14px;color:#ccc;line-height:1.9;font-family:var(--font-mono);margin-bottom:16px;}
|
|
.article-body code{background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.12);padding:2px 6px;font-size:12px;font-family:var(--font-mono);}
|
|
.article-body blockquote{border-left:3px solid var(--accent);padding:12px 20px;margin:24px 0;background:rgba(255,255,255,0.02);font-style:italic;color:var(--muted);font-family:var(--font-mono);font-size:13px;}
|
|
.article-body ul{margin:12px 0 16px 20px;font-family:var(--font-mono);font-size:13px;color:#ccc;line-height:1.8;}
|
|
.article-body ul li{margin-bottom:6px;}
|
|
.article-body ul li::marker{color:var(--accent);}
|
|
.back-link{display:inline-flex;align-items:center;gap:8px;font-family:var(--font-mono);font-size:12px;color:var(--muted);margin-top:40px;text-decoration:none;transition:color .2s;}
|
|
.back-link:hover{color:var(--accent);}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="cursor-dot" id="cursorDot"></div>
|
|
<div class="cursor-ring" id="cursorRing"></div>
|
|
<div class="progress-bar" id="progressBar"></div>
|
|
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"></button>
|
|
<div class="shortcut-modal" id="shortcutModal">
|
|
<div class="shortcut-card">
|
|
<h3>Keyboard Shortcuts</h3>
|
|
<div class="shortcut-row"><span>Go Home</span><kbd>H</kbd></div>
|
|
<div class="shortcut-row"><span>Go to GitHub</span><kbd>G</kbd></div>
|
|
<div class="shortcut-row"><span>Toggle theme</span><kbd>T</kbd></div>
|
|
<div class="shortcut-row"><span>Show shortcuts</span><kbd>?</kbd></div>
|
|
<div class="close-hint">Press Esc or ? to close</div>
|
|
</div>
|
|
</div>
|
|
<div class="flash" id="flash"></div>
|
|
<div class="toast-stack" id="toastStack"></div>
|
|
<nav class="mobile-nav">
|
|
<a href="../index.html">Home</a>
|
|
<a href="fedl.html">FEDL</a>
|
|
<a href="omniemu.html">OmniEmu</a>
|
|
<a href="command-move.html">CommandMove</a>
|
|
<a href="https://github.com/mileswolfallen2" target="_blank">GitHub</a>
|
|
</nav>
|
|
|
|
<div class="container">
|
|
<nav>
|
|
<a href="../index.html" class="nav-logo"><span class="logo-dot"></span> milesallen</a>
|
|
<div class="nav-links">
|
|
<a href="../index.html#about">About</a>
|
|
<a href="../index.html#skills">Skills</a>
|
|
<a href="../index.html#projects">Projects</a>
|
|
<a href="../index.html#contact">Contact</a>
|
|
</div>
|
|
</nav>
|
|
|
|
<section class="article-header">
|
|
<div class="breadcrumb">
|
|
<a href="../index.html">Home</a>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
|
<a href="../index.html#blog">Devlog</a>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
|
<span>Building Nova AI</span>
|
|
</div>
|
|
<div class="article-eyebrow">// DEVLOG 004</div>
|
|
<div class="article-date">2025</div>
|
|
<h1>Building Nova AI</h1>
|
|
<p class="article-tagline">Local AI that doesn't phone home. Desktop GUI, web interface, CLI — no subscriptions, no cloud, no nonsense.</p>
|
|
<div class="article-tags">
|
|
<span class="article-tag">Python</span>
|
|
<span class="article-tag">AI</span>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="reveal">
|
|
<div class="article-body">
|
|
<h2>The Problem with AI Right Now</h2>
|
|
<p>Every AI assistant today wants your data. ChatGPT, Claude, Gemini — they all send your conversations to their servers, store your prompts, and use your data to train their models. Some of them charge $20/month for the privilege.</p>
|
|
<p>I wanted an AI that runs entirely on my machine. No cloud. No subscriptions. No data leaving my laptop. Just a local model that I can talk to, ask questions, and get answers from — without worrying about where my data goes.</p>
|
|
|
|
<h2>What Nova AI Is</h2>
|
|
<p>The vision was always Jarvis. Not in a sci-fi movie way, but in a practical way — an AI that lives on your machine, knows your setup, and is always ready to help. You say something, it responds. No loading screens, no API rate limits, no "I'm sorry, I can't do that." Just a fast, local assistant that's always there.</p>
|
|
<p>Nova AI is a local AI assistant with three interfaces:</p>
|
|
<ul>
|
|
<li><strong>Desktop GUI:</strong> A native desktop app for daily use</li>
|
|
<li><strong>Web Interface:</strong> Access it from any browser on your network</li>
|
|
<li><strong>CLI:</strong> For power users who live in the terminal</li>
|
|
</ul>
|
|
<p>All three interfaces connect to the same local backend. Your conversations stay on your machine. The model runs on your hardware. No API keys, no rate limits, no terms of service changes that break your workflow.</p>
|
|
|
|
<h2>Why Three Interfaces</h2>
|
|
<p>Different situations call for different tools. When I'm at my desk, I use the desktop app. It's clean, fast, and has a nice chat interface. When I'm on my phone or another device on the same network, I open the web interface. And when I'm in the terminal working on code, I use the CLI to ask quick questions without context-switching.</p>
|
|
<p>The web interface was especially important to me. I wanted to be able to access Nova from my phone while I'm away from my desk, but without setting up any cloud infrastructure. Just a local web server that's accessible on my LAN.</p>
|
|
|
|
<h2>The Tech Stack</h2>
|
|
<ul>
|
|
<li>Backend: Python with FastAPI</li>
|
|
<li>AI Engine: Local model inference (no external API calls)</li>
|
|
<li>Desktop: Native GUI framework</li>
|
|
<li>Web: Vanilla frontend, WebSocket for streaming responses</li>
|
|
<li>CLI: Python with Rich for pretty terminal output</li>
|
|
</ul>
|
|
|
|
<h2>Privacy by Design</h2>
|
|
<p>The biggest selling point of Nova isn't the features — it's the privacy. Every conversation, every prompt, every response stays on your machine. There's no telemetry, no analytics, no phone-home calls. The app doesn't even check for updates automatically.</p>
|
|
<p>In a world where every app wants to collect your data, Nova is refreshingly different. It's an AI assistant that respects your privacy because it was designed that way from the start.</p>
|
|
|
|
<h2>Lessons Learned</h2>
|
|
<ul>
|
|
<li>Local AI is getting more practical every month</li>
|
|
<li>The hardest part isn't the AI — it's building good interfaces for it</li>
|
|
<li>Users care more about privacy than most companies think</li>
|
|
<li>Multiple interfaces for the same backend is more work but worth it</li>
|
|
<li>The best AI is the one that's actually available when you need it</li>
|
|
</ul>
|
|
|
|
<blockquote>The best AI is the one that doesn't need an internet connection.</blockquote>
|
|
|
|
<a href="../index.html#blog" class="back-link">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><polyline points="15 18 9 12 15 6"/></svg>
|
|
Back to Devlog
|
|
</a>
|
|
</div>
|
|
</section>
|
|
|
|
<footer>
|
|
Built by Miles · 2026 · Powered by too much coffee and the Konami code
|
|
</footer>
|
|
</div>
|
|
<script>
|
|
fetch('https://api.github.com/repos/mileswolfallen2/')
|
|
.then(r => r.json())
|
|
.then(d => {})
|
|
.catch(() => {});
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('visible'); });
|
|
}, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' });
|
|
document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
|
|
</script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/[email protected]/gsap.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/[email protected]/ScrollTrigger.min.js"></script>
|
|
<script>
|
|
gsap.registerPlugin(ScrollTrigger);
|
|
const heroTl = gsap.timeline({ defaults: { ease: 'power3.out' } });
|
|
heroTl
|
|
.from('.breadcrumb', { x: -30, opacity: 0, duration: 0.6 })
|
|
.from('.article-header h1', { y: 50, opacity: 0, duration: 0.8 }, '-=0.3')
|
|
.from('.article-tagline', { y: 30, opacity: 0, duration: 0.7 }, '-=0.4')
|
|
.from('.article-tag', { y: 20, opacity: 0, scale: 0.8, duration: 0.4, stagger: 0.06 }, '-=0.3');
|
|
gsap.from('nav', { y: -20, opacity: 0, duration: 0.6, ease: 'power2.out' });
|
|
document.querySelectorAll('.article-body h2').forEach(title => {
|
|
gsap.from(title, { scrollTrigger: { trigger: title, start: 'top 85%' }, y: 30, opacity: 0, duration: 0.7, ease: 'power3.out' });
|
|
});
|
|
document.querySelectorAll('.article-body p, .article-body ul, .article-body blockquote').forEach(el => {
|
|
gsap.from(el, { scrollTrigger: { trigger: el, start: 'top 88%' }, y: 20, opacity: 0, duration: 0.6, ease: 'power3.out' });
|
|
});
|
|
gsap.from('footer', { scrollTrigger: { trigger: 'footer', start: 'top 95%' }, opacity: 0, duration: 0.8 });
|
|
window.addEventListener('load', () => ScrollTrigger.refresh());
|
|
</script>
|
|
<script>
|
|
var toastQueue=document.getElementById('toastStack');var shownToasts={};
|
|
function showToast(id,title,body,icon){
|
|
if(shownToasts[id])return;shownToasts[id]=true;
|
|
var t=document.createElement('div');t.className='toast';
|
|
t.innerHTML='<span class="icon">'+(icon||'!')+'</span><span><span class="title">'+title+'</span>'+body+'</span>';
|
|
toastQueue.appendChild(t);
|
|
gsap.to(t,{x:0,duration:.6,ease:'power3.out'});
|
|
setTimeout(function(){gsap.to(t,{x:'-150%',opacity:0,duration:.4,ease:'power2.in',onComplete:function(){t.remove();}});},3000);
|
|
}
|
|
(function(){
|
|
var seq=['ArrowUp','ArrowUp','ArrowDown','ArrowDown','ArrowLeft','ArrowRight','ArrowLeft','ArrowRight','b','a'];
|
|
var pos=0;
|
|
window.addEventListener('keydown',function(e){
|
|
var key=e.key.length===1?e.key.toLowerCase():e.key;
|
|
if(key===seq[pos]){pos++;if(pos===seq.length){pos=0;triggerKonami();}}
|
|
else{pos=(key===seq[0])?1:0;}
|
|
});
|
|
function triggerKonami(){
|
|
showToast('konami','SECRET FOUND','Konami Code Activated','*');
|
|
var flash=document.getElementById('flash');
|
|
gsap.fromTo(flash,{opacity:.5},{opacity:0,duration:.5});
|
|
gsap.to('body',{x:'random(-5,5)',y:'random(-5,5)',duration:.05,repeat:10,ease:'sine.inOut',onComplete:function(){gsap.set('body',{x:0,y:0});}});
|
|
document.body.style.cursor='auto';
|
|
var style=document.createElement('style');style.id='konami-cursor';
|
|
style.textContent='*{cursor:auto !important;}';
|
|
document.head.appendChild(style);
|
|
}
|
|
})();
|
|
</script>
|
|
<script>
|
|
(function(){
|
|
var dot=document.getElementById('cursorDot');
|
|
var ring=document.getElementById('cursorRing');
|
|
if(!dot||!ring)return;
|
|
var mx=0,my=0,rx=0,ry=0;
|
|
document.addEventListener('mousemove',function(e){mx=e.clientX;my=e.clientY;dot.style.left=mx+'px';dot.style.top=my+'px';});
|
|
(function loop(){
|
|
rx+=(mx-rx)*0.15;ry+=(my-ry)*0.15;
|
|
ring.style.left=rx+'px';ring.style.top=ry+'px';
|
|
requestAnimationFrame(loop);
|
|
})();
|
|
document.querySelectorAll('a,button,.meta-chip,.tech-badge,.feature-card').forEach(function(el){
|
|
el.addEventListener('mouseenter',function(){dot.classList.add('hover');ring.classList.add('hover');});
|
|
el.addEventListener('mouseleave',function(){dot.classList.remove('hover');ring.classList.remove('hover');});
|
|
});
|
|
})();
|
|
</script>
|
|
<script>
|
|
(function(){
|
|
var bar=document.getElementById('progressBar');
|
|
window.addEventListener('scroll',function(){
|
|
var pct=window.scrollY/(document.body.scrollHeight-window.innerHeight)*100;
|
|
bar.style.width=pct+'%';
|
|
},{passive:true});
|
|
})();
|
|
(function(){
|
|
var btn=document.getElementById('themeToggle');
|
|
var saved=localStorage.getItem('theme');
|
|
if(saved==='light')document.body.classList.add('light');
|
|
function updateIcon(){btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';}
|
|
updateIcon();
|
|
btn.addEventListener('click',function(){
|
|
document.body.classList.toggle('light');
|
|
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
|
updateIcon();
|
|
});
|
|
})();
|
|
(function(){
|
|
var modal=document.getElementById('shortcutModal');
|
|
function toggleModal(){modal.classList.toggle('open');}
|
|
window.addEventListener('keydown',function(e){
|
|
if(e.key==='?'){e.preventDefault();toggleModal();}
|
|
if(e.key==='Escape')modal.classList.remove('open');
|
|
if(e.key==='t'||e.key==='T'){
|
|
if(document.activeElement&&document.activeElement.tagName==='INPUT')return;
|
|
document.body.classList.toggle('light');
|
|
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
|
var btn=document.getElementById('themeToggle');
|
|
btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';
|
|
}
|
|
if(e.key==='h'||e.key==='H')window.location.href='../index.html';
|
|
if(e.key==='g'||e.key==='G')window.location.href='https://github.com/mileswolfallen2';
|
|
});
|
|
modal.addEventListener('click',function(e){if(e.target===modal)modal.classList.remove('open');});
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|