HomePilot Deploy Bot
chore: sync installer from monorepo
cd7f665
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<title>HomePilot — Deploy Your Private AI</title>
<style>
/* ── Reset + Base ── */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#09090b; --surface:#111113; --card:#161618; --elevated:#1c1c1f;
--border:rgba(255,255,255,0.06); --border-h:rgba(255,255,255,0.12);
--text:#e4e4e7; --muted:#a1a1aa; --dim:#71717a; --faint:#3f3f46;
--cyan:#06b6d4; --blue:#3b82f6; --purple:#8b5cf6; --pink:#ec4899; --green:#22c55e;
--grad:linear-gradient(135deg,#06b6d4,#3b82f6,#8b5cf6);
--glow:0 0 40px rgba(59,130,246,0.12);
--r:12px;
--font:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
}
html{background:var(--bg);color:var(--text);font-family:var(--font);-webkit-text-size-adjust:100%}
body{min-height:100dvh;display:flex;flex-direction:column;align-items:center;
padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)}
a{color:var(--blue);text-decoration:none}
a:hover{color:var(--cyan)}
/* ── Layout ── */
.wrap{width:100%;max-width:580px;padding:0 20px}
/* ── Hero ── */
.hero{text-align:center;padding:56px 20px 8px;position:relative}
.hero::before{content:'';position:absolute;top:-50%;left:50%;transform:translateX(-50%);
width:140%;height:120%;background:radial-gradient(ellipse 50% 40% at 50% 0%,rgba(59,130,246,0.1),transparent);pointer-events:none}
.hero-logo{width:clamp(140px,30vw,200px);height:auto;position:relative;filter:drop-shadow(0 0 24px rgba(59,130,246,.25))}
.hero h1{font-size:clamp(1.7rem,4.5vw,2.4rem);font-weight:800;letter-spacing:-0.03em;
line-height:1.15;margin-top:16px;position:relative}
.hero h1 span{background:var(--grad);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
.hero p{color:var(--dim);font-size:14px;line-height:1.6;margin:10px auto 0;max-width:420px;position:relative}
.alt-link{margin-top:16px;font-size:12px;color:var(--faint);position:relative;display:flex;align-items:center;justify-content:center;gap:6px}
.alt-link a{color:var(--muted);font-weight:600;transition:color .2s}
.alt-link a:hover{color:var(--cyan)}
/* ── Trust bar ── */
.trust{display:flex;justify-content:center;gap:20px;padding:24px 0 36px;flex-wrap:wrap}
.trust-item{display:flex;align-items:center;gap:5px;font-size:12px;font-weight:500;color:var(--muted)}
/* ── Steps ── */
.step{margin-bottom:20px}
.step-head{display:flex;align-items:center;gap:10px;margin-bottom:10px}
.step-num{width:28px;height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;
font-size:12px;font-weight:800;color:white;flex-shrink:0}
.step-num.s1{background:linear-gradient(135deg,var(--cyan),var(--blue))}
.step-num.s2{background:linear-gradient(135deg,var(--blue),var(--purple))}
.step-num.s3{background:linear-gradient(135deg,var(--purple),var(--pink))}
.step-label{font-size:15px;font-weight:700;color:var(--text)}
.step-desc{font-size:12px;color:var(--dim);margin-top:1px}
/* ── Card ── */
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);
padding:20px;transition:border-color .2s}
.card:hover{border-color:var(--border-h)}
/* ── Inputs ── */
.field{margin-bottom:14px}
.field:last-child{margin-bottom:0}
.field label{display:block;font-size:11px;font-weight:600;color:var(--dim);
text-transform:uppercase;letter-spacing:.04em;margin-bottom:6px}
.field input,.field select{width:100%;height:44px;padding:0 14px;font-size:15px;font-family:var(--font);
color:var(--text);background:var(--bg);border:1px solid var(--border);border-radius:10px;
outline:none;transition:border-color .2s}
.field input:focus,.field select:focus{border-color:var(--blue);box-shadow:0 0 0 3px rgba(59,130,246,.1)}
.field input::placeholder{color:var(--faint)}
.field select{appearance:none;cursor:pointer;
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2371717a' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
background-repeat:no-repeat;background-position:right 14px center}
.field select option{background:var(--card);color:var(--text)}
.row{display:flex;gap:12px}
.row>.field{flex:1}
/* ── Toggle ── */
.toggle{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;color:var(--muted);
user-select:none;height:44px}
.toggle input{display:none}
.toggle .track{width:36px;height:20px;border-radius:10px;background:var(--faint);position:relative;transition:.2s}
.toggle input:checked+.track{background:var(--blue)}
.toggle .track::after{content:'';position:absolute;top:2px;left:2px;width:16px;height:16px;
border-radius:50%;background:white;transition:.2s}
.toggle input:checked+.track::after{left:18px}
/* ── Buttons ── */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;height:44px;
padding:0 24px;border:none;border-radius:10px;font-size:14px;font-weight:700;
font-family:var(--font);cursor:pointer;transition:all .2s;letter-spacing:-.01em}
.btn-primary{background:var(--grad);color:white;box-shadow:var(--glow);width:100%}
.btn-primary:hover{transform:translateY(-1px);box-shadow:0 0 48px rgba(59,130,246,.2)}
.btn-primary:disabled{opacity:.5;cursor:not-allowed;transform:none}
.btn-sm{height:36px;padding:0 16px;font-size:13px;border-radius:8px;background:var(--blue);color:white}
.btn-sm:hover{background:var(--cyan)}
.btn-sm:disabled{opacity:.5;cursor:not-allowed}
.btn-outline{height:40px;padding:0 20px;font-size:13px;border-radius:8px;
background:transparent;border:1px solid var(--border);color:var(--muted)}
.btn-outline:hover{border-color:var(--border-h);color:var(--text)}
/* ── Status ── */
.status{font-size:13px;margin-top:8px;min-height:20px;font-weight:500}
.status.ok{color:var(--green)} .status.err{color:#ef4444}
/* ── Accordion ── */
.accordion-btn{width:100%;display:flex;align-items:center;justify-content:space-between;
padding:14px 16px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r);
color:var(--muted);font-size:13px;font-weight:600;cursor:pointer;transition:all .2s;
font-family:var(--font)}
.accordion-btn:hover{border-color:var(--border-h);color:var(--text)}
.accordion-btn .arrow{transition:transform .2s}
.accordion-btn.open .arrow{transform:rotate(180deg)}
.accordion-body{overflow:hidden;max-height:0;transition:max-height .3s ease}
.accordion-body.open{max-height:500px}
.accordion-inner{padding:16px;background:var(--surface);border:1px solid var(--border);
border-top:none;border-radius:0 0 var(--r) var(--r)}
/* ── Persona chips ── */
.pack-label{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;
color:var(--dim);margin:12px 0 6px}
.pack-label:first-child{margin-top:0}
.chips{display:flex;flex-wrap:wrap;gap:6px}
.chip{padding:5px 10px;border-radius:8px;font-size:12px;font-weight:500;color:var(--text);
background:var(--card);border:1px solid var(--border);transition:all .15s;white-space:nowrap}
.chip:hover{border-color:var(--border-h);transform:translateY(-1px)}
/* ── Log ── */
.log{background:var(--bg);border:1px solid var(--border);border-radius:var(--r);
padding:16px;margin-top:12px;font-family:'SF Mono','Fira Code',monospace;font-size:13px;
line-height:1.8;color:var(--muted);min-height:0;display:none;white-space:pre-wrap}
.log.visible{display:block}
.log .ok{color:var(--green)} .log .info{color:var(--blue)} .log .err{color:#ef4444}
/* ── Success ── */
.success{text-align:center;padding:32px 20px;display:none}
.success.visible{display:block}
.success h2{font-size:22px;font-weight:800;margin:12px 0 8px}
.success p{color:var(--dim);font-size:14px;margin-bottom:20px}
.success .links{display:flex;gap:10px;justify-content:center;flex-wrap:wrap}
/* ── Footer ── */
.footer{padding:40px 0 24px;text-align:center;border-top:1px solid var(--border);
margin-top:auto;width:100%;max-width:580px}
.footer p{font-size:12px;color:var(--faint)}
/* ── Lang picker ── */
.lang-btn{background:transparent;border:1px solid var(--border);color:var(--dim);
padding:4px 10px;border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;
font-family:var(--font);transition:all .15s}
.lang-btn:hover{border-color:var(--border-h);color:var(--text)}
.lang-btn.active{background:var(--blue);border-color:var(--blue);color:white}
/* ── Responsive ── */
@media(max-width:640px){
.hero{padding:36px 16px 8px}
.hero h1{font-size:1.6rem}
.hero p{font-size:13px}
.trust{gap:10px 16px;padding:16px 0 24px}
.trust-item{font-size:11px}
.wrap{padding:0 12px}
.row{flex-direction:column;gap:8px}
.card{padding:16px}
.field input,.field select{height:48px;font-size:16px}
.btn{height:48px;font-size:15px}
.btn-sm{height:44px;font-size:14px}
.step-head{gap:8px}
.alt-link{flex-direction:column;gap:2px}
.chips{gap:4px}
.chip{padding:4px 8px;font-size:11px}
.lang-btn{padding:5px 12px;font-size:12px}
.footer{padding:24px 12px 16px}
}
@media(max-width:360px){
.hero-logo{width:120px}
.hero h1{font-size:1.35rem}
.trust{flex-direction:column;align-items:center;gap:6px}
}
/* ── Animations ── */
@keyframes fadeUp{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.step{animation:fadeUp .4s ease both}
.step:nth-child(2){animation-delay:.1s}
.step:nth-child(3){animation-delay:.2s}
@keyframes spin{to{transform:rotate(360deg)}}
.spinner{display:inline-block;width:14px;height:14px;border:2px solid rgba(255,255,255,.3);
border-top-color:white;border-radius:50%;animation:spin .6s linear infinite}
</style>
</head>
<body>
<!-- ── HERO ── -->
<div class="hero">
<img src="https://raw.githubusercontent.com/ruslanmv/HomePilot/master/assets/homepilot-logo.svg"
alt="HomePilot" class="hero-logo">
<h1><span>Your private AI in 2 minutes</span></h1>
<p>Deploy HomePilot with Ollama and 14 AI personas on your own Hugging Face Space. No code. Private by default.</p>
<div class="alt-link">
<span>Prefer to run locally?</span>
<a href="https://ruslanmv.com/HomePilot/getting-started.html" target="_blank" id="desktop-link">Desktop version →</a>
</div>
</div>
<!-- Language picker -->
<div style="display:flex;justify-content:center;gap:4px;padding:0 0 8px">
<button class="lang-btn active" data-lang="en" onclick="setLang('en')">EN</button>
<button class="lang-btn" data-lang="es" onclick="setLang('es')">ES</button>
<button class="lang-btn" data-lang="pt" onclick="setLang('pt')">PT</button>
<button class="lang-btn" data-lang="fr" onclick="setLang('fr')">FR</button>
<button class="lang-btn" data-lang="de" onclick="setLang('de')">DE</button>
</div>
<div class="trust">
<div class="trust-item"><span style="color:var(--green)">🔒</span> Privado</div>
<div class="trust-item"><span style="color:var(--blue)">🧠</span> Ollama</div>
<div class="trust-item"><span style="color:var(--purple)"></span> GPU ready</div>
<div class="trust-item"><span style="color:var(--pink)">🎭</span> 14 personas</div>
</div>
<div class="wrap" id="main-flow">
<!-- ── STEP 1 ── -->
<div class="step">
<div class="step-head">
<div class="step-num s1">1</div>
<div>
<div class="step-label" id="s1-label">Connect your account</div>
<div class="step-desc" id="s1-desc">A <a href="https://huggingface.co/settings/tokens" target="_blank">HF token</a> with write permission. We don't store credentials.</div>
</div>
</div>
<div class="card">
<div class="row">
<div class="field" style="flex:3">
<label id="lbl-token">Token</label>
<input type="password" id="token" placeholder="hf_..." autocomplete="off">
</div>
<div style="flex:1;display:flex;align-items:flex-end">
<button class="btn-sm" id="verify-btn" style="width:100%">Conectar</button>
</div>
</div>
<div class="status" id="auth-status"></div>
</div>
</div>
<!-- ── STEP 2 ── -->
<div class="step">
<div class="step-head">
<div class="step-num s2">2</div>
<div>
<div class="step-label" id="s2-label">Configure</div>
<div class="step-desc" id="s2-desc">Everything has defaults — only change if you want.</div>
</div>
</div>
<div class="card">
<div class="row">
<div class="field">
<label id="lbl-space">Space name</label>
<input type="text" id="space-name" value="HomePilot">
</div>
<div style="display:flex;align-items:flex-end;padding-bottom:2px">
<label class="toggle" id="lbl-private">
<input type="checkbox" id="private" checked>
<div class="track"></div>
Private
</label>
</div>
</div>
<div class="field">
<label id="lbl-model">LLM Model</label>
<select id="model">
<option value="qwen2.5:1.5b" selected>qwen2.5 1.5b — fast, ideal to start</option>
<option value="qwen2.5:3b">qwen2.5 3b — better quality</option>
<option value="llama3:8b">llama3 8b — powerful (needs GPU)</option>
<option value="gemma:2b">gemma 2b — balanced</option>
</select>
</div>
<div class="field">
<label class="toggle" id="lbl-personas">
<input type="checkbox" id="include-personas" checked>
<span id="txt-personas">Include Chata personas (14 Starter + Retro pack) — uncheck for a clean HomePilot</span>
</label>
</div>
</div>
</div>
<!-- ── PERSONAS ── -->
<div style="margin-bottom:20px">
<button class="accordion-btn" id="acc-btn">
🎭 14 AI personas included
<svg class="arrow" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg>
</button>
<div class="accordion-body" id="acc-body">
<div class="accordion-inner">
<p style="color:var(--dim);font-size:12px;margin-bottom:12px" id="acc-sub">Auto-imported on first start. Names shown here match what you'll see in HomePilot.</p>
<div class="pack-label">Starter Pack</div>
<div class="chips">
<div class="chip">🌙 LunaLite</div>
<div class="chip">😎 ChillBro</div>
<div class="chip">🔍 Curiosa</div>
<div class="chip">⚡ HypeKid</div>
</div>
<div class="pack-label">Retro Pack</div>
<div class="chips">
<div class="chip">🔋 Volt</div>
<div class="chip">⚔️ Ronin</div>
<div class="chip">🦖 Kaiju</div>
<div class="chip">💾 Glitch</div>
<div class="chip">🗺️ Quest</div>
<div class="chip">🧠 Sigma</div>
<div class="chip">🃏 Loki</div>
<div class="chip">🌳 OldRoot</div>
<div class="chip">🔮 Morphling</div>
<div class="chip">🌌 Nova</div>
</div>
</div>
</div>
</div>
<!-- ── STEP 3 ── -->
<div class="step">
<div class="step-head">
<div class="step-num s3">3</div>
<div>
<div class="step-label" id="s3-label">Deploy</div>
<div class="step-desc" id="s3-desc">One click. Your HomePilot will be ready in ~3 minutes.</div>
</div>
</div>
<button class="btn btn-primary" id="install-btn" disabled>
🚀 Deploy HomePilot
</button>
<div class="log" id="log"></div>
</div>
<!-- ── SUCCESS ── -->
<div class="success" id="success">
<div style="font-size:48px">🎉</div>
<h2>Your HomePilot is running!</h2>
<p id="success-msg">Wait ~3 minutes for the first build.</p>
<div class="links">
<a class="btn-outline" id="open-space" href="#" target="_blank">Open Space →</a>
<a class="btn-outline" id="go-chata" href="https://huggingface.co/spaces/ruslanmv/Chata" target="_blank">Go to Chata</a>
</div>
</div>
</div>
<!-- ── FOOTER ── -->
<div class="footer">
<p>
<a href="https://ruslanmv.com/HomePilot/">HomePilot</a> ·
<a href="https://huggingface.co/spaces/ruslanmv/Chata">Chata</a> ·
<a href="https://github.com/ruslanmv/HomePilot">GitHub</a>
</p>
</div>
<script>
const $ = s => document.querySelector(s);
let username = '';
// ── i18n ──
const T = {
en: {
hero: 'Your private AI in 2 minutes',
heroSub: 'Deploy HomePilot with Ollama and 14 AI personas on your own Hugging Face Space. No code. Private by default.',
trust: ['Private', 'Ollama', 'GPU ready', '14 personas'],
s1: 'Connect your account', s1d: 'with write permission. We don\'t store credentials.',
s1token: 'A <a href="https://huggingface.co/settings/tokens" target="_blank">HF token</a>',
connect: 'Connect', connected: 'Connected as',
s2: 'Configure', s2d: 'Everything has defaults — only change if you want.',
spaceName: 'Space name', private: 'Private', model: 'LLM Model',
m1: 'fast, ideal to start', m2: 'better quality', m3: 'powerful (needs GPU)', m4: 'balanced',
personas: '14 AI personas included', personaSub: 'Auto-imported on first start.',
s3: 'Deploy', s3d: 'One click. Your HomePilot will be ready in ~3 minutes.',
deployBtn: '🚀 Deploy HomePilot', deploying: 'Deploying...', retry: '🚀 Retry',
starting: 'Starting installation...', deployed: 'Deployed successfully!',
successH: 'Your HomePilot is running!', successP: 'Wait ~3 minutes for the first build.',
openSpace: 'Open Space →', goChata: 'Go to Chata',
tokenEmpty: 'Empty or invalid token', verifyFirst: 'Connect your account first (Step 1)',
altPref: 'Prefer to run locally?', altLink: 'Desktop version →',
},
es: {
hero: 'Tu IA privada en 2 minutos',
heroSub: 'Despliega HomePilot con Ollama y 14 personas AI en tu propio Hugging Face Space. Sin código. Privado por defecto.',
trust: ['Privado', 'Ollama', 'GPU ready', '14 personas'],
s1: 'Conecta tu cuenta', s1d: 'con permiso write. No almacenamos credenciales.',
s1token: 'Un <a href="https://huggingface.co/settings/tokens" target="_blank">token de HF</a>',
connect: 'Conectar', connected: 'Conectado como',
s2: 'Configura', s2d: 'Todo tiene valores por defecto — solo cambia si quieres.',
spaceName: 'Nombre del Space', private: 'Privado', model: 'Modelo LLM',
m1: 'rápido, ideal para empezar', m2: 'mejor calidad', m3: 'poderoso (necesita GPU)', m4: 'equilibrado',
personas: '14 personas AI incluidas', personaSub: 'Se importan automáticamente al iniciar.',
s3: 'Despliega', s3d: 'Un clic. Tu HomePilot estará listo en ~3 minutos.',
deployBtn: '🚀 Desplegar HomePilot', deploying: 'Desplegando...', retry: '🚀 Reintentar',
starting: 'Iniciando instalación...', deployed: '¡Desplegado exitosamente!',
successH: '¡Tu HomePilot está en marcha!', successP: 'Espera ~3 minutos para el primer build.',
openSpace: 'Abrir Space →', goChata: 'Ir a Chata',
tokenEmpty: 'Token vacío o inválido', verifyFirst: 'Conecta tu cuenta primero (Paso 1)',
altPref: '¿Prefieres ejecutar localmente?', altLink: 'Versión de escritorio →',
},
pt: {
hero: 'Sua IA privada em 2 minutos',
heroSub: 'Implante o HomePilot com Ollama e 14 personas AI no seu próprio Hugging Face Space. Sem código. Privado por padrão.',
trust: ['Privado', 'Ollama', 'GPU ready', '14 personas'],
s1: 'Conecte sua conta', s1d: 'com permissão write. Não armazenamos credenciais.',
s1token: 'Um <a href="https://huggingface.co/settings/tokens" target="_blank">token do HF</a>',
connect: 'Conectar', connected: 'Conectado como',
s2: 'Configure', s2d: 'Tudo tem valores padrão — mude apenas se quiser.',
spaceName: 'Nome do Space', private: 'Privado', model: 'Modelo LLM',
m1: 'rápido, ideal para começar', m2: 'melhor qualidade', m3: 'poderoso (precisa GPU)', m4: 'equilibrado',
personas: '14 personas AI incluídas', personaSub: 'Importadas automaticamente ao iniciar.',
s3: 'Implante', s3d: 'Um clique. Seu HomePilot estará pronto em ~3 minutos.',
deployBtn: '🚀 Implantar HomePilot', deploying: 'Implantando...', retry: '🚀 Tentar novamente',
starting: 'Iniciando instalação...', deployed: 'Implantado com sucesso!',
successH: 'Seu HomePilot está rodando!', successP: 'Aguarde ~3 minutos para o primeiro build.',
openSpace: 'Abrir Space →', goChata: 'Ir para Chata',
tokenEmpty: 'Token vazio ou inválido', verifyFirst: 'Conecte sua conta primeiro (Passo 1)',
altPref: 'Prefere executar localmente?', altLink: 'Versão desktop →',
},
fr: {
hero: 'Votre IA privée en 2 minutes',
heroSub: 'Déployez HomePilot avec Ollama et 14 personas AI sur votre propre Hugging Face Space. Sans code. Privé par défaut.',
trust: ['Privé', 'Ollama', 'GPU ready', '14 personas'],
s1: 'Connectez votre compte', s1d: 'avec permission write. Nous ne stockons pas vos identifiants.',
s1token: 'Un <a href="https://huggingface.co/settings/tokens" target="_blank">token HF</a>',
connect: 'Connecter', connected: 'Connecté en tant que',
s2: 'Configurez', s2d: 'Tout a des valeurs par défaut — changez seulement si vous voulez.',
spaceName: 'Nom du Space', private: 'Privé', model: 'Modèle LLM',
m1: 'rapide, idéal pour commencer', m2: 'meilleure qualité', m3: 'puissant (nécessite GPU)', m4: 'équilibré',
personas: '14 personas AI incluses', personaSub: 'Importées automatiquement au démarrage.',
s3: 'Déployez', s3d: 'Un clic. Votre HomePilot sera prêt en ~3 minutes.',
deployBtn: '🚀 Déployer HomePilot', deploying: 'Déploiement...', retry: '🚀 Réessayer',
starting: 'Début de l\'installation...', deployed: 'Déployé avec succès !',
successH: 'Votre HomePilot est en marche !', successP: 'Attendez ~3 minutes pour le premier build.',
openSpace: 'Ouvrir Space →', goChata: 'Aller sur Chata',
tokenEmpty: 'Token vide ou invalide', verifyFirst: 'Connectez votre compte d\'abord (Étape 1)',
altPref: 'Préférez-vous exécuter localement ?', altLink: 'Version bureau →',
},
de: {
hero: 'Ihre private KI in 2 Minuten',
heroSub: 'Deployen Sie HomePilot mit Ollama und 14 AI-Personas auf Ihrem eigenen Hugging Face Space. Ohne Code. Standardmäßig privat.',
trust: ['Privat', 'Ollama', 'GPU ready', '14 Personas'],
s1: 'Konto verbinden', s1d: 'mit Schreibberechtigung. Wir speichern keine Zugangsdaten.',
s1token: 'Ein <a href="https://huggingface.co/settings/tokens" target="_blank">HF-Token</a>',
connect: 'Verbinden', connected: 'Verbunden als',
s2: 'Konfigurieren', s2d: 'Alles hat Standardwerte — ändern Sie nur, was Sie möchten.',
spaceName: 'Space-Name', private: 'Privat', model: 'LLM-Modell',
m1: 'schnell, ideal zum Starten', m2: 'bessere Qualität', m3: 'leistungsstark (GPU nötig)', m4: 'ausgewogen',
personas: '14 AI-Personas enthalten', personaSub: 'Werden beim ersten Start automatisch importiert.',
s3: 'Deployen', s3d: 'Ein Klick. Ihr HomePilot ist in ~3 Minuten bereit.',
deployBtn: '🚀 HomePilot deployen', deploying: 'Wird deployed...', retry: '🚀 Erneut versuchen',
starting: 'Installation wird gestartet...', deployed: 'Erfolgreich deployed!',
successH: 'Ihr HomePilot läuft!', successP: 'Warten Sie ~3 Minuten auf den ersten Build.',
openSpace: 'Space öffnen →', goChata: 'Zu Chata',
tokenEmpty: 'Token leer oder ungültig', verifyFirst: 'Verbinden Sie zuerst Ihr Konto (Schritt 1)',
altPref: 'Lieber lokal ausführen?', altLink: 'Desktop-Version →',
},
};
let lang = 'en';
function detectLang() {
const nav = (navigator.language || '').slice(0,2).toLowerCase();
return T[nav] ? nav : 'en';
}
function setLang(l) {
lang = l;
const t = T[l];
// Hero
document.querySelector('.hero h1 span').textContent = t.hero;
document.querySelector('.hero p').textContent = t.heroSub;
// Trust
const ti = document.querySelectorAll('.trust-item');
t.trust.forEach((v,i) => { if(ti[i]) ti[i].lastChild.textContent = ' '+v; });
// Steps
document.querySelector('#s1-label').textContent = t.s1;
document.querySelector('#s1-desc').innerHTML = t.s1token + ' ' + t.s1d;
document.querySelector('#s2-label').textContent = t.s2;
document.querySelector('#s2-desc').textContent = t.s2d;
document.querySelector('#s3-label').textContent = t.s3;
document.querySelector('#s3-desc').textContent = t.s3d;
// Labels
document.querySelector('#lbl-token').textContent = 'Token';
document.querySelector('#lbl-space').textContent = t.spaceName;
document.querySelector('#lbl-private').lastChild.textContent = ' '+t.private;
document.querySelector('#lbl-model').textContent = t.model;
// Buttons
document.querySelector('#verify-btn').textContent = t.connect;
document.querySelector('#install-btn').innerHTML = t.deployBtn;
// Accordion
document.querySelector('#acc-btn').firstChild.textContent = '🎭 ' + t.personas + ' ';
document.querySelector('#acc-sub').textContent = t.personaSub;
// Models
const opts = document.querySelectorAll('#model option');
const ms = [t.m1, t.m2, t.m3, t.m4];
opts.forEach((o,i) => { if(ms[i]) o.textContent = o.value.split(':').join(' ') + ' — ' + ms[i]; });
// Success
document.querySelector('#success h2').textContent = t.successH;
document.querySelector('#open-space').textContent = t.openSpace;
document.querySelector('#go-chata').textContent = t.goChata;
// Alt link
const altLink = document.querySelector('.alt-link');
if (altLink) {
altLink.querySelector('span').textContent = t.altPref;
altLink.querySelector('a').textContent = t.altLink;
}
// Lang picker
document.querySelectorAll('.lang-btn').forEach(b => {
b.classList.toggle('active', b.dataset.lang === l);
});
}
// Accordion
$('#acc-btn').onclick = () => {
$('#acc-btn').classList.toggle('open');
$('#acc-body').classList.toggle('open');
};
// Verify
$('#verify-btn').onclick = async () => {
const btn = $('#verify-btn');
const token = $('#token').value.trim();
if (!token) return;
btn.disabled = true;
btn.textContent = '...';
try {
const r = await fetch('/api/verify', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({token})
});
const d = await r.json();
const st = $('#auth-status');
if (d.ok) {
username = d.username;
st.textContent = '✅ Conectado como ' + d.username;
st.className = 'status ok';
$('#install-btn').disabled = false;
} else {
st.textContent = '❌ ' + (d.error || 'Error');
st.className = 'status err';
}
} catch(e) {
$('#auth-status').textContent = '❌ ' + e.message;
$('#auth-status').className = 'status err';
}
btn.disabled = false;
btn.textContent = 'Conectar';
};
// Install
$('#install-btn').onclick = async () => {
const btn = $('#install-btn');
btn.disabled = true;
btn.innerHTML = '<span class="spinner"></span> Desplegando...';
const log = $('#log');
log.classList.add('visible');
log.innerHTML = '<span class="info">▸ Iniciando instalación...</span>\n';
try {
const r = await fetch('/api/install', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
token: $('#token').value.trim(),
username,
space_name: $('#space-name').value.trim() || 'HomePilot',
private: $('#private').checked,
model: $('#model').value,
include_personas: $('#include-personas') ? $('#include-personas').checked : true
})
});
const d = await r.json();
if (d.steps) {
log.innerHTML = d.steps.map(s => `<span class="info">✓ ${s}</span>`).join('\n') + '\n';
}
if (d.ok) {
log.innerHTML += '<span class="ok">✅ ¡Desplegado exitosamente!</span>';
// Show success
$('#success').classList.add('visible');
$('#open-space').href = d.url;
$('#success-msg').textContent = d.repo_id + ' — espera ~3 min para el build.';
btn.style.display = 'none';
} else {
log.innerHTML += `<span class="err">❌ ${d.error || 'Error desconocido'}</span>`;
btn.disabled = false;
btn.innerHTML = '🚀 Reintentar';
}
} catch(e) {
log.innerHTML += `<span class="err">❌ ${e.message}</span>`;
btn.disabled = false;
btn.innerHTML = '🚀 Reintentar';
}
};
// Enter key on token
$('#token').onkeydown = e => { if (e.key === 'Enter') $('#verify-btn').click(); };
// Auto-detect language on load
setLang(detectLang());
</script>
</body>
</html>