Download index.html from MukulRay/Irminsul: direct link, hf CLI and curl.
- Browser
- Download file 24.3 kB
-
https://huggingface.co/spaces/MukulRay/Irminsul/resolve/main/index.html
- Command line
-
hf download hf://spaces/MukulRay/Irminsul/index.html
-
curl -L -o index.html https://huggingface.co/spaces/MukulRay/Irminsul/resolve/main/index.html
24.3 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Irminsul β Genshin Impact AI Assistant</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;500;600&family=EB+Garamond:ital,wght@0,400;0,500;1,400&family=JetBrains+Mono:wght@300;400&display=swap" rel="stylesheet"> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/9.1.6/marked.min.js"></script> | |
| <style> | |
| :root { | |
| --bg: #060d08; | |
| --bg2: #0a1410; | |
| --surface: rgba(8,18,12,0.88); | |
| --surface2: rgba(12,24,16,0.92); | |
| --border: rgba(80,180,100,0.12); | |
| --border2: rgba(80,180,100,0.22); | |
| --dendro: #7ecb6a; | |
| --dendro2: #a8e090; | |
| --dendro3: #c8f0b0; | |
| --parchment: #c8b888; | |
| --parchment2:#e0d0a8; | |
| --teal: #4ab890; | |
| --text: #d8e8d0; | |
| --text2: #7a9878; | |
| --text3: #3a5038; | |
| --green: #5dd68c; | |
| --red: #e87878; | |
| --amber: #d8a84a; | |
| --gold: #d4af37; | |
| --mono: 'JetBrains Mono', monospace; | |
| --serif: 'EB Garamond', serif; | |
| --display: 'Cinzel', serif; | |
| } | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| background: var(--bg); | |
| color: var(--text); | |
| font-family: var(--serif); | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| overflow-x: hidden; | |
| } | |
| canvas#bg { position: fixed; inset: 0; z-index: 0; pointer-events: none; } | |
| /* deep forest atmospheric glow */ | |
| .atmo { | |
| position: fixed; | |
| border-radius: 50%; | |
| pointer-events: none; | |
| z-index: 0; | |
| filter: blur(80px); | |
| } | |
| .atmo-1 { | |
| width: 700px; height: 500px; | |
| top: -100px; left: -100px; | |
| background: radial-gradient(ellipse, rgba(40,100,50,0.18) 0%, transparent 70%); | |
| } | |
| .atmo-2 { | |
| width: 500px; height: 400px; | |
| top: 30%; right: -80px; | |
| background: radial-gradient(ellipse, rgba(60,160,80,0.1) 0%, transparent 70%); | |
| } | |
| .atmo-3 { | |
| width: 600px; height: 300px; | |
| bottom: 0; left: 20%; | |
| background: radial-gradient(ellipse, rgba(30,80,40,0.14) 0%, transparent 70%); | |
| } | |
| header { | |
| position: relative; z-index: 10; | |
| padding: 20px 48px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| border-bottom: 1px solid var(--border); | |
| backdrop-filter: blur(20px); | |
| background: rgba(6,13,8,0.7); | |
| } | |
| .logo { display: flex; align-items: center; gap: 14px; } | |
| .logo-mark { | |
| width: 38px; height: 38px; | |
| border-radius: 50%; | |
| border: 1px solid rgba(126,203,106,0.3); | |
| background: radial-gradient(circle at 40% 35%, rgba(80,180,80,0.15), rgba(6,13,8,0.95)); | |
| display: flex; align-items: center; justify-content: center; | |
| font-size: 18px; | |
| box-shadow: 0 0 16px rgba(80,200,80,0.12), inset 0 0 12px rgba(80,200,80,0.06); | |
| animation: runeGlow 3s ease-in-out infinite; | |
| } | |
| @keyframes runeGlow { | |
| 0%,100% { box-shadow: 0 0 16px rgba(80,200,80,0.12), inset 0 0 12px rgba(80,200,80,0.06); } | |
| 50% { box-shadow: 0 0 28px rgba(80,200,80,0.25), inset 0 0 16px rgba(80,200,80,0.12); } | |
| } | |
| .logo-text { | |
| font-family: var(--display); | |
| font-size: 17px; font-weight: 500; | |
| color: var(--dendro2); | |
| letter-spacing: 0.08em; | |
| } | |
| .logo-sub { | |
| font-size: 10px; color: var(--text3); | |
| font-family: var(--mono); | |
| margin-top: 2px; letter-spacing: 0.04em; | |
| } | |
| .status-pill { | |
| display: flex; align-items: center; gap: 7px; | |
| padding: 5px 13px; border-radius: 20px; | |
| border: 1px solid var(--border); | |
| background: rgba(8,18,12,0.8); | |
| font-family: var(--mono); font-size: 11px; color: var(--text2); | |
| backdrop-filter: blur(8px); | |
| } | |
| .status-dot { | |
| width: 6px; height: 6px; border-radius: 50%; | |
| background: var(--text3); transition: background 0.3s; | |
| } | |
| .status-dot.online { background: var(--green); box-shadow: 0 0 7px var(--green); } | |
| .status-dot.error { background: var(--red); } | |
| .status-dot.loading { background: var(--amber); animation: pulse 1s ease-in-out infinite; } | |
| @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.3} } | |
| main { | |
| position: relative; z-index: 10; | |
| flex: 1; display: flex; flex-direction: column; | |
| max-width: 800px; width: 100%; | |
| margin: 0 auto; padding: 52px 40px 0; | |
| } | |
| /* parchment scroll hero */ | |
| .hero { | |
| margin-bottom: 44px; | |
| text-align: center; | |
| animation: fadeUp 0.7s ease both; | |
| } | |
| @keyframes fadeUp { | |
| from { opacity:0; transform: translateY(14px); } | |
| to { opacity:1; transform: translateY(0); } | |
| } | |
| .rune-row { | |
| display: flex; align-items: center; justify-content: center; | |
| gap: 12px; margin-bottom: 18px; | |
| font-size: 14px; color: var(--dendro); | |
| opacity: 0.6; letter-spacing: 0.3em; | |
| } | |
| .rune-row span { animation: runeFlicker 4s ease-in-out infinite; } | |
| .rune-row span:nth-child(2) { animation-delay: 0.8s; } | |
| .rune-row span:nth-child(3) { animation-delay: 1.6s; } | |
| @keyframes runeFlicker { | |
| 0%,100%{opacity:0.5} 50%{opacity:1;text-shadow:0 0 8px var(--dendro);} | |
| } | |
| .hero h1 { | |
| font-family: var(--display); | |
| font-size: 40px; font-weight: 500; | |
| color: var(--parchment2); | |
| letter-spacing: 0.06em; line-height: 1.2; | |
| margin-bottom: 8px; | |
| text-shadow: 0 0 60px rgba(100,200,80,0.15); | |
| } | |
| .hero h1 em { | |
| font-style: normal; | |
| color: var(--dendro2); | |
| text-shadow: 0 0 30px rgba(126,203,106,0.5); | |
| } | |
| .hero-sub { | |
| font-size: 16px; font-style: italic; | |
| color: var(--text2); line-height: 1.75; | |
| max-width: 480px; margin: 0 auto 20px; | |
| } | |
| .model-tag { | |
| display: inline-flex; align-items: center; gap: 8px; | |
| padding: 5px 16px; border-radius: 3px; | |
| border: 1px solid rgba(126,203,106,0.2); | |
| background: rgba(40,80,30,0.2); | |
| font-family: var(--mono); font-size: 11px; | |
| color: var(--dendro); letter-spacing: 0.04em; | |
| } | |
| /* ornamental divider */ | |
| .ornament { | |
| display: flex; align-items: center; | |
| gap: 12px; margin-bottom: 36px; opacity: 0.25; | |
| } | |
| .ornament-line { | |
| flex: 1; height: 1px; | |
| background: linear-gradient(90deg, transparent, var(--dendro), transparent); | |
| } | |
| .ornament-glyph { | |
| font-size: 12px; color: var(--dendro); | |
| letter-spacing: 4px; | |
| } | |
| /* query box β parchment card feel */ | |
| .query-box { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 4px; | |
| overflow: hidden; | |
| transition: border-color 0.3s, box-shadow 0.3s; | |
| animation: fadeUp 0.7s 0.1s ease both; | |
| backdrop-filter: blur(20px); | |
| position: relative; | |
| } | |
| /* corner rune accents */ | |
| .query-box::before, | |
| .query-box::after { | |
| content: 'β¦'; | |
| position: absolute; | |
| font-size: 10px; | |
| color: var(--dendro); | |
| opacity: 0.3; | |
| top: 8px; | |
| } | |
| .query-box::before { left: 10px; } | |
| .query-box::after { right: 10px; } | |
| .query-box:focus-within { | |
| border-color: rgba(126,203,106,0.35); | |
| box-shadow: 0 0 32px rgba(80,180,60,0.08); | |
| } | |
| .query-label { | |
| padding: 14px 18px 0; | |
| font-family: var(--display); | |
| font-size: 9px; color: var(--text3); | |
| letter-spacing: 0.18em; text-transform: uppercase; | |
| } | |
| textarea { | |
| width: 100%; background: transparent; | |
| border: none; outline: none; resize: none; | |
| padding: 10px 18px 16px; | |
| font-family: var(--serif); font-size: 15px; | |
| color: var(--text); line-height: 1.75; | |
| min-height: 100px; caret-color: var(--dendro); | |
| } | |
| textarea::placeholder { color: var(--text3); font-style: italic; } | |
| .query-footer { | |
| display: flex; align-items: center; | |
| justify-content: space-between; | |
| padding: 10px 16px; | |
| border-top: 1px solid var(--border); | |
| background: var(--surface2); | |
| } | |
| .top-k-wrap { | |
| display: flex; align-items: center; | |
| gap: 8px; font-family: var(--mono); | |
| font-size: 11px; color: var(--text2); | |
| } | |
| .top-k-wrap select { | |
| background: rgba(6,13,8,0.8); | |
| border: 1px solid var(--border2); | |
| border-radius: 3px; color: var(--text); | |
| font-family: var(--mono); font-size: 11px; | |
| padding: 3px 8px; cursor: pointer; outline: none; | |
| } | |
| .send-btn { | |
| display: flex; align-items: center; gap: 8px; | |
| padding: 9px 22px; | |
| background: linear-gradient(135deg, rgba(60,140,50,0.8), rgba(40,100,35,0.9)); | |
| border: 1px solid rgba(126,203,106,0.3); | |
| border-radius: 3px; | |
| color: var(--dendro3); | |
| font-family: var(--display); | |
| font-size: 11px; font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| letter-spacing: 0.1em; | |
| box-shadow: 0 2px 20px rgba(60,180,50,0.15); | |
| } | |
| .send-btn:hover { | |
| background: linear-gradient(135deg, rgba(80,160,60,0.9), rgba(55,120,45,0.95)); | |
| box-shadow: 0 4px 28px rgba(60,200,60,0.25); | |
| border-color: rgba(126,203,106,0.5); | |
| transform: translateY(-1px); | |
| } | |
| .send-btn:active { transform: scale(0.97); } | |
| .send-btn:disabled { | |
| background: rgba(20,35,20,0.7); | |
| color: var(--text3); cursor: not-allowed; | |
| transform: none; box-shadow: none; | |
| border-color: var(--border); | |
| } | |
| #response-area { | |
| margin-top: 28px; | |
| animation: fadeUp 0.5s ease both; | |
| display: none; | |
| } | |
| #response-area.visible { display: block; } | |
| .response-card { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 4px; overflow: hidden; | |
| backdrop-filter: blur(20px); | |
| } | |
| .response-header { | |
| display: flex; align-items: center; | |
| justify-content: space-between; | |
| padding: 11px 18px; | |
| border-bottom: 1px solid var(--border); | |
| background: var(--surface2); | |
| } | |
| .response-label { | |
| font-family: var(--display); font-size: 9px; | |
| color: var(--text3); text-transform: uppercase; | |
| letter-spacing: 0.14em; | |
| display: flex; align-items: center; gap: 8px; | |
| } | |
| .response-label .dot { | |
| width: 6px; height: 6px; border-radius: 50%; | |
| background: var(--dendro); | |
| box-shadow: 0 0 8px var(--dendro); | |
| animation: dendroFlicker 2s ease-in-out infinite; | |
| } | |
| @keyframes dendroFlicker { | |
| 0%,100%{opacity:1;box-shadow:0 0 8px var(--dendro);} | |
| 50%{opacity:0.6;box-shadow:0 0 4px var(--dendro);} | |
| } | |
| .latency-tag { | |
| font-family: var(--mono); font-size: 11px; | |
| color: var(--text3); padding: 2px 8px; | |
| border-radius: 2px; border: 1px solid var(--border); | |
| } | |
| .response-body { | |
| padding: 22px 24px; | |
| font-size: 15.5px; line-height: 1.9; | |
| color: var(--text); font-family: var(--serif); | |
| white-space: normal; word-break: break-word; | |
| } | |
| .response-body > *:first-child { margin-top: 0; } | |
| /* ββ Markdown answer styling βββββββββββββββββββββββββββ */ | |
| .response-body h1, | |
| .response-body h2 { | |
| font-family: var(--display); | |
| color: var(--gold); | |
| letter-spacing: 0.04em; | |
| margin: 1.4em 0 0.4em; | |
| font-weight: 500; | |
| } | |
| .response-body h2 { font-size: 1.15em; border-bottom: 1px solid rgba(212,175,55,0.25); padding-bottom: 0.3em; } | |
| .response-body h3 { font-family: var(--serif); color: var(--gold); opacity: 0.85; font-size: 1em; margin: 1.1em 0 0.25em; font-style: italic; } | |
| .response-body strong { color: var(--gold); font-weight: 600; } | |
| .response-body em { color: var(--text); opacity: 0.8; font-style: italic; } | |
| .response-body ul, .response-body ol { | |
| padding-left: 1.4em; | |
| margin: 0.5em 0 0.8em; | |
| } | |
| .response-body li { | |
| margin-bottom: 0.35em; | |
| line-height: 1.75; | |
| } | |
| .response-body li::marker { color: var(--gold); opacity: 0.7; } | |
| .response-body p { margin: 0 0 0.8em; } | |
| .response-body p:last-child { margin-bottom: 0; } | |
| .response-body hr { | |
| border: none; | |
| border-top: 1px solid rgba(212,175,55,0.2); | |
| margin: 1.2em 0; | |
| } | |
| .response-body code { | |
| font-family: var(--mono); | |
| font-size: 0.88em; | |
| background: rgba(212,175,55,0.08); | |
| border: 1px solid rgba(212,175,55,0.2); | |
| border-radius: 3px; | |
| padding: 0.1em 0.4em; | |
| color: var(--gold); | |
| } | |
| .thinking { | |
| display: flex; align-items: center; gap: 12px; | |
| padding: 22px 24px; | |
| font-family: var(--serif); font-size: 14px; | |
| color: var(--text3); font-style: italic; | |
| } | |
| .thinking-dots span { | |
| display: inline-block; | |
| width: 5px; height: 5px; border-radius: 50%; | |
| background: var(--dendro); margin: 0 2px; | |
| animation: dendroOrb 1.6s ease-in-out infinite; | |
| opacity: 0.4; | |
| } | |
| .thinking-dots span:nth-child(2) { animation-delay: 0.25s; } | |
| .thinking-dots span:nth-child(3) { animation-delay: 0.5s; } | |
| @keyframes dendroOrb { | |
| 0%,100%{opacity:0.3;transform:scale(0.9);} | |
| 50%{opacity:1;transform:scale(1.3);box-shadow:0 0 6px var(--dendro);} | |
| } | |
| .sources-section { | |
| border-top: 1px solid var(--border); | |
| padding: 12px 22px; | |
| background: var(--surface2); | |
| } | |
| .sources-label { | |
| font-family: var(--display); font-size: 9px; | |
| color: var(--text3); text-transform: uppercase; | |
| letter-spacing: 0.14em; margin-bottom: 8px; | |
| } | |
| .source-chip { | |
| display: inline-flex; align-items: center; gap: 5px; | |
| padding: 3px 10px; border-radius: 2px; | |
| border: 1px solid rgba(80,180,80,0.18); | |
| background: rgba(40,80,30,0.15); | |
| font-family: var(--mono); font-size: 11px; | |
| color: var(--dendro); margin: 3px 4px 3px 0; | |
| } | |
| .no-sources { | |
| font-family: var(--mono); font-size: 11px; | |
| color: var(--text3); font-style: italic; | |
| } | |
| .error-card { | |
| background: rgba(232,120,120,0.05); | |
| border: 1px solid rgba(232,120,120,0.18); | |
| border-radius: 4px; padding: 14px 18px; | |
| font-family: var(--mono); font-size: 12px; | |
| color: var(--red); margin-top: 16px; display: none; | |
| } | |
| .error-card.visible { display: block; } | |
| .history-section { | |
| margin-top: 32px; padding-bottom: 48px; | |
| animation: fadeUp 0.5s ease both; | |
| } | |
| .history-label { | |
| font-family: var(--display); font-size: 9px; | |
| color: var(--text3); text-transform: uppercase; | |
| letter-spacing: 0.14em; margin-bottom: 12px; | |
| } | |
| .history-item { | |
| background: var(--surface); border: 1px solid var(--border); | |
| border-radius: 3px; padding: 11px 16px; | |
| margin-bottom: 6px; cursor: pointer; | |
| transition: border-color 0.2s, background 0.2s; | |
| backdrop-filter: blur(8px); | |
| } | |
| .history-item:hover { | |
| border-color: rgba(80,180,80,0.28); | |
| background: rgba(30,60,25,0.3); | |
| } | |
| .history-q { | |
| font-size: 13.5px; color: var(--text2); | |
| font-family: var(--serif); | |
| white-space: nowrap; overflow: hidden; text-overflow: ellipsis; | |
| } | |
| .history-meta { | |
| font-size: 10px; color: var(--text3); | |
| font-family: var(--mono); margin-top: 4px; | |
| } | |
| footer { | |
| position: relative; z-index: 10; | |
| text-align: center; padding: 18px; | |
| font-family: var(--mono); font-size: 10px; | |
| color: var(--text3); letter-spacing: 0.06em; | |
| border-top: 1px solid var(--border); | |
| backdrop-filter: blur(8px); | |
| background: rgba(6,13,8,0.6); | |
| margin-top: auto; | |
| } | |
| footer span { color: var(--dendro); opacity: 0.6; } | |
| </style> | |
| </head> | |
| <body> | |
| <canvas id="bg"></canvas> | |
| <div class="atmo atmo-1"></div> | |
| <div class="atmo atmo-2"></div> | |
| <div class="atmo atmo-3"></div> | |
| <header> | |
| <div class="logo"> | |
| <div class="logo-mark">αͺ₯</div> | |
| <div> | |
| <div class="logo-text">Irminsul</div> | |
| <div class="logo-sub">Llama 3.1 Β· QLoRA Β· Pinecone RAG</div> | |
| </div> | |
| </div> | |
| <div class="status-pill"> | |
| <div class="status-dot" id="status-dot"></div> | |
| <span id="status-text">checking...</span> | |
| </div> | |
| </header> | |
| <main> | |
| <div class="hero"> | |
| <div class="rune-row"> | |
| <span>αͺ₯</span><span>β¦</span><span>αͺ₯</span> | |
| </div> | |
| <h1>The <em>Akasha</em> Speaks</h1> | |
| <p class="hero-sub">All knowledge flows through the tree. Ask of Teyvat's lore, its people, their battles, and the elements that bind this world.</p> | |
| <div class="model-tag"> | |
| β Dendro-augmented retrieval Β· Genshin Impact corpus | |
| </div> | |
| </div> | |
| <div class="ornament"> | |
| <div class="ornament-line"></div> | |
| <div class="ornament-glyph">β¦ αͺ₯ β¦</div> | |
| <div class="ornament-line"></div> | |
| </div> | |
| <div class="query-box"> | |
| <div class="query-label">Inscribe your query</div> | |
| <textarea | |
| id="query-input" | |
| placeholder="What does the Irminsul record of Nahida's imprisonment reveal..." | |
| rows="3" | |
| ></textarea> | |
| <div class="query-footer"> | |
| <div class="top-k-wrap"> | |
| <span>top_k</span> | |
| <select id="top-k"> | |
| <option value="1">1</option> | |
| <option value="2">2</option> | |
| <option value="3" selected>3</option> | |
| <option value="5">5</option> | |
| </select> | |
| <span style="color:var(--text3)">retrieved branches</span> | |
| </div> | |
| <button class="send-btn" id="send-btn" onclick="submitQuery()"> | |
| Query the Tree β | |
| </button> | |
| </div> | |
| </div> | |
| <div class="error-card" id="error-card"></div> | |
| <div id="response-area"> | |
| <div class="response-card"> | |
| <div class="response-header"> | |
| <div class="response-label"> | |
| <div class="dot"></div> | |
| irminsul responds | |
| </div> | |
| <div class="latency-tag" id="latency-tag">β</div> | |
| </div> | |
| <div id="response-body" class="response-body"></div> | |
| <div class="sources-section"> | |
| <div class="sources-label">branches consulted</div> | |
| <div id="sources-list"><span class="no-sources">no records ingested</span></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="history-section" id="history-section" style="display:none"> | |
| <div class="history-label">Recent queries</div> | |
| <div id="history-list"></div> | |
| </div> | |
| </main> | |
| <footer> | |
| Irminsul Β· <span>Genshin Impact AI Assistant</span> Β· exp2_lr2e-4_r16 + Groq | |
| </footer> | |
| <script> | |
| // ββ Floating spore particles + Dendro wisps ββββββββββββββββββββββββββββββββββββ | |
| (function() { | |
| const canvas = document.getElementById('bg'); | |
| const ctx = canvas.getContext('2d'); | |
| let W, H, particles = [], wisps = []; | |
| function resize() { | |
| W = canvas.width = window.innerWidth; | |
| H = canvas.height = window.innerHeight; | |
| } | |
| function rand(a, b) { return a + Math.random() * (b - a); } | |
| function initParticles() { | |
| particles = []; | |
| const n = Math.floor(W * H / 8000); | |
| for (let i = 0; i < n; i++) { | |
| particles.push({ | |
| x: rand(0, W), y: rand(0, H), | |
| r: rand(0.8, 2.5), | |
| vx: rand(-0.15, 0.15), | |
| vy: rand(-0.4, -0.1), | |
| alpha: rand(0.1, 0.5), | |
| flicker: rand(0.002, 0.006), | |
| phase: rand(0, Math.PI * 2), | |
| hue: rand(100, 150), | |
| }); | |
| } | |
| } | |
| function initWisps() { | |
| wisps = []; | |
| for (let i = 0; i < 6; i++) { | |
| wisps.push({ | |
| x: rand(0, W), y: rand(H * 0.3, H * 0.9), | |
| r: rand(60, 140), | |
| alpha: rand(0.02, 0.06), | |
| speed: rand(0.0003, 0.0008), | |
| phase: rand(0, Math.PI * 2), | |
| hue: rand(110, 145), | |
| }); | |
| } | |
| } | |
| function draw(t) { | |
| ctx.clearRect(0, 0, W, H); | |
| // Dendro wisps β soft glowing orbs drifting in the background | |
| wisps.forEach(w => { | |
| const a = w.alpha * (0.6 + 0.4 * Math.sin(t * w.speed * 1000 + w.phase)); | |
| const grd = ctx.createRadialGradient(w.x, w.y, 0, w.x, w.y, w.r); | |
| grd.addColorStop(0, `hsla(${w.hue},70%,55%,${a})`); | |
| grd.addColorStop(1, `hsla(${w.hue},70%,40%,0)`); | |
| ctx.beginPath(); | |
| ctx.arc(w.x, w.y, w.r, 0, Math.PI * 2); | |
| ctx.fillStyle = grd; | |
| ctx.fill(); | |
| w.x += Math.sin(t * 0.0003 + w.phase) * 0.3; | |
| w.y += Math.cos(t * 0.0002 + w.phase) * 0.2; | |
| }); | |
| // spore particles β tiny glowing motes floating upward | |
| particles.forEach(p => { | |
| const a = p.alpha * (0.5 + 0.5 * Math.sin(t * p.flicker * 1000 + p.phase)); | |
| ctx.beginPath(); | |
| ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2); | |
| ctx.fillStyle = `hsla(${p.hue},80%,70%,${a})`; | |
| ctx.fill(); | |
| // tiny glow ring on larger motes | |
| if (p.r > 1.8) { | |
| ctx.beginPath(); | |
| ctx.arc(p.x, p.y, p.r * 2.5, 0, Math.PI * 2); | |
| ctx.fillStyle = `hsla(${p.hue},80%,70%,${a * 0.15})`; | |
| ctx.fill(); | |
| } | |
| p.x += p.vx + Math.sin(t * 0.0005 + p.phase) * 0.2; | |
| p.y += p.vy; | |
| if (p.y < -10) { p.y = H + 10; p.x = rand(0, W); } | |
| if (p.x < -10) p.x = W + 10; | |
| if (p.x > W + 10) p.x = -10; | |
| }); | |
| requestAnimationFrame(draw); | |
| } | |
| window.addEventListener('resize', () => { resize(); initParticles(); initWisps(); }); | |
| resize(); initParticles(); initWisps(); | |
| requestAnimationFrame(draw); | |
| })(); | |
| // ββ API ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const API = window.location.origin;; | |
| const history = []; | |
| async function checkHealth() { | |
| const dot = document.getElementById('status-dot'); | |
| const txt = document.getElementById('status-text'); | |
| dot.className = 'status-dot loading'; | |
| txt.textContent = 'connecting...'; | |
| try { | |
| const r = await fetch(`${API}/health`); | |
| const d = await r.json(); | |
| if (d.model_loaded) { | |
| dot.className = 'status-dot online'; | |
| txt.textContent = 'akasha linked'; | |
| } else { | |
| dot.className = 'status-dot loading'; | |
| txt.textContent = 'loading...'; | |
| setTimeout(checkHealth, 3000); | |
| } | |
| } catch { | |
| dot.className = 'status-dot error'; | |
| txt.textContent = 'offline'; | |
| setTimeout(checkHealth, 5000); | |
| } | |
| } | |
| async function submitQuery() { | |
| const query = document.getElementById('query-input').value.trim(); | |
| if (!query) return; | |
| const top_k = parseInt(document.getElementById('top-k').value); | |
| const btn = document.getElementById('send-btn'); | |
| const respArea = document.getElementById('response-area'); | |
| const respBody = document.getElementById('response-body'); | |
| const errCard = document.getElementById('error-card'); | |
| const latTag = document.getElementById('latency-tag'); | |
| const srcList = document.getElementById('sources-list'); | |
| errCard.className = 'error-card'; | |
| respArea.style.display = 'block'; | |
| respArea.className = 'response-area visible'; | |
| latTag.textContent = 'β'; | |
| srcList.innerHTML = ''; | |
| btn.disabled = true; | |
| respBody.innerHTML = ` | |
| <div class="thinking"> | |
| <div class="thinking-dots"><span></span><span></span><span></span></div> | |
| searching the branches... | |
| </div>`; | |
| try { | |
| const res = await fetch(`${API}/generate`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ query, top_k }) | |
| }); | |
| if (!res.ok) { | |
| const err = await res.json(); | |
| throw new Error(JSON.stringify(err.detail || err)); | |
| } | |
| const data = await res.json(); | |
| respBody.innerHTML = marked.parse(data.answer); | |
| const ms = Math.round(data.latency_ms); | |
| latTag.textContent = ms >= 1000 ? `${(ms/1000).toFixed(1)}s` : `${ms}ms`; | |
| if (data.sources && data.sources.length > 0) { | |
| srcList.innerHTML = data.sources.map(s => { | |
| const name = s.split(/[\\/]/).pop(); | |
| return `<span class="source-chip">β ${name}</span>`; | |
| }).join(''); | |
| } else { | |
| srcList.innerHTML = '<span class="no-sources">no records found β run ingest.py</span>'; | |
| } | |
| addToHistory(query, ms); | |
| } catch (err) { | |
| respBody.innerHTML = ''; | |
| respArea.style.display = 'none'; | |
| errCard.className = 'error-card visible'; | |
| errCard.textContent = `Error: ${err.message}`; | |
| } | |
| btn.disabled = false; | |
| } | |
| function addToHistory(query, latency_ms) { | |
| history.unshift({ query, latency_ms, time: new Date().toLocaleTimeString() }); | |
| if (history.length > 5) history.pop(); | |
| document.getElementById('history-section').style.display = 'block'; | |
| document.getElementById('history-list').innerHTML = history.map((h, i) => ` | |
| <div class="history-item" onclick="rerun(${i})"> | |
| <div class="history-q">${h.query}</div> | |
| <div class="history-meta">${h.time} Β· ${(h.latency_ms / 1000).toFixed(1)}s</div> | |
| </div> | |
| `).join(''); | |
| } | |
| function rerun(i) { | |
| document.getElementById('query-input').value = history[i].query; | |
| submitQuery(); | |
| } | |
| document.getElementById('query-input').addEventListener('keydown', e => { | |
| if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { | |
| e.preventDefault(); | |
| submitQuery(); | |
| } | |
| }); | |
| checkHealth(); | |
| </script> | |
| </body> | |
| </html> |