CoolFace
Apppublic

weazel/houseai-modularizer

sourceHugging Faceupdated 11mo agoView on Hugging Face
0likes
index.html241 linesDownload Raw Back to root
1 2<!doctype html>3<html lang="en">4<head>5  <meta charset="utf-8" />6  <meta name="viewport" content="width=device-width, initial-scale=1" />7  <title>HouseAI — ultra-minimal</title>8 9  <!-- iOS standalone web app -->10  <meta name="apple-mobile-web-app-capable" content="yes">11  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">12  <meta name="apple-mobile-web-app-title" content="HouseAI">13  <link rel="apple-touch-icon" href="/icons/apple-touch-icon-180.png">14 15  <!-- External Dependencies -->16  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js" defer></script>17  <!-- Module Imports -->18  <script type="module" src="./config.js"></script>19  <script type="module" src="./utils.js"></script>20  <script type="module" src="./api.js"></script>21  <script type="module" src="./parser.js"></script>22  <script type="module" src="./renderer.js"></script>23<style>24    :root { --w: 720px; }25 26    /* Base layout */27    html, body { height: 100%; }28    body { margin: 0; font: 16px/1.45 system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Arial, sans-serif; background:#f7f7f7; padding-bottom: env(safe-area-inset-bottom); }29    textarea, input, button { font-size: 16px; } /* prevent iOS zoom */30    .app { max-width: var(--w); margin: 0 auto; padding: 12px; min-height: 100dvh; display:flex; flex-direction:column; gap: 10px; }31 32    h1 { font-size: 18px; margin: 8px 0 6px; font-weight: 600; }33    .top { display:flex; align-items:center; justify-content:space-between; gap:12px; }34    .status { font-size:12px; color:#555; display:flex; align-items:center; gap:8px; }35    .dot { width:8px; height:8px; border-radius:50%; background:#bbb; display:inline-block; }36    .ok .dot { background:#22c55e; }37    .down .dot { background:#ef4444; }38 39    .card { background:#fff; border:1px solid #e5e5e5; border-radius: 10px; box-shadow: 0 1px 2px rgba(0,0,0,.04); }40 41    /* Messages area flexes */42    .messages { flex:1; min-height: 0; overflow:auto; padding: 10px; display: flex; flex-direction: column; gap: 8px; overscroll-behavior-y: contain; -webkit-overflow-scrolling: touch; }43    .msg { max-width: 85%; padding: 8px 10px; border:1px solid #e6e6e6; border-radius: 10px; background:#fff; }44    .me { align-self: flex-end; background:#e9f3ff; border-color:#d6e9ff; }45    .ai { align-self: flex-start; background:#fcfcfc; }46    .meta { font-size: 11px; color:#888; margin-bottom: 2px; }47 48    /* Sticky composer with safe-area padding */49    form#composer { position: sticky; bottom: 0; padding: 8px; border-top:1px solid #eee; padding-bottom: calc(8px + env(safe-area-inset-bottom)); background:#fff; }50    textarea {51      width: 100%; min-height: 42px; resize: none; /* auto-grow handles height */52      padding: 10px 12px; border:1px solid #ddd; border-radius: 8px; box-sizing: border-box;53      overflow: hidden; max-height: 40dvh; line-height: 1.45;54    }55    .hint { font-size: 12px; color:#777; padding: 0 2px 8px; }56 57    /* Markdown niceties */58    .msg h1,.msg h2,.msg h3{margin:.2em 0 .4em}59    .msg ul{margin:.3em 0 .6em .8em}60    .msg code{font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.95em; padding: 0 .25em; border:1px solid #eee; border-radius:4px; background:#fafafa;}61    .msg pre{overflow:auto; padding:.6em; border:1px solid #eee; border-radius:8px; background:#fafafa;}62    details.tool { margin-top:.4em; }63    details.tool summary { cursor:pointer; user-select:none; }64 65    /* Debug nested items (tiny touch) */66    details.sub { margin: .25em 0 .25em 1em; }67 68    /* Sub-detail content smaller + wrapping */69    details.sub pre {70      font-size: 12.5px;          /* smaller text */71      line-height: 1.4;72      white-space: pre-wrap;       /* preserve newlines, wrap long lines */73      word-break: break-word;      /* break long tokens if needed */74      overflow-wrap: anywhere;     /* extra safety for long JSON keys */75      max-height: 320px;           /* keep it neat */76      overflow-y: auto;            /* vertical scroll only */77      overflow-x: hidden;          /* no horizontal scroll */78    }79    80    details.sub summary { font-size: 14px; }81 82    /* Mobile ergonomics */83    @media (max-width: 600px) {84      body { font-size: 17px; }85      h1 { font-size: 18px; }86      .msg { padding: 8px 10px; }87      textarea { padding: 12px 14px; }88    }89    90  </style>91</head>92<body>93 94  <!-- <div id="chat-header" style="display:flex;justify-content:flex-end;padding:4px 8px;">95    <button onclick="startNewChat()" style="font-size:0.8em;opacity:0.6;border:none;background:none;cursor:pointer;">96      New chat97    </button>98  </div> -->99 100  <div class="app">101  <!-- subtle top-right new chat -->102  <div style="display:flex;justify-content:flex-end;padding:4px 8px;position:sticky;top:0;background:#f7f7f7;z-index:1;">103    <button onclick="startNewChat()" style="font-size:0.8em;opacity:0.6;border:none;background:none;cursor:pointer;">104      New chat105    </button>106  </div>107 108  <div class="top">109    <h1>Wonka, De HouseAI V1.4</h1>110    <div id="status" class="status" title="Back-end health">111      <span class="dot" aria-hidden="true"></span>112      <span><strong>API:</strong> checking…</span>113    </div>114  </div>115 116    <div id="messages" class="card messages" aria-live="polite"></div>117 118    <form id="composer" class="card" autocomplete="off">119      <div class="hint">Enter to send • Shift+Enter for newline</div>120      <div style="padding:0 8px 8px;">121        <textarea id="input" placeholder="Ask or instruct HouseAI…" rows="1" enterkeyhint="send" autocapitalize="sentences" spellcheck="true"></textarea>122      </div>123    </form>124  </div>125 126  <script type="module">127    import { CONFIG } from './config.js';128    import { newCid, getCid, autoGrow, timeStr } from './utils.js';129    import { sendMessageToAPI, fetchHealthStatus, fetchChangelog } from './api.js';130    import { render } from './renderer.js';131 132    const $ = sel => document.querySelector(sel);133 134    const messagesEl = $('#messages');135    const inputEl = $('#input');136 137    let CONVERSATION_ID = getCid();138    console.debug("Conversation ID initialised:", CONVERSATION_ID);139 140    let convo = [];141 142    function push(role, content) {143      convo.push({ role, content, t: Date.now() });144      render(convo, messagesEl);145    }146 147    async function sendMessage(text) {148      // Client-side command interception149      if (text.trim().toLowerCase() === "!changelog") {150        try {151          const { changelog } = await fetchChangelog();152          push("assistant", "📜 Backend changelog:\n\n" + changelog);153        } catch (err) {154          push("assistant", "⚠️ Could not fetch changelog: " + (err?.message || err));155        } finally {156          inputEl.focus({ preventScroll: true });157        }158        return;159      }160 161      // Normal chat flow162      push("user", text);163      try {164        const data = await sendMessageToAPI(text, CONVERSATION_ID);165        166        if (data.conversation_id && data.conversation_id !== CONVERSATION_ID) {167          CONVERSATION_ID = data.conversation_id;168          localStorage.setItem("wonka.cid", CONVERSATION_ID);169          const url = new URL(window.location.href);170          url.searchParams.set("cid", CONVERSATION_ID);171          history.replaceState({}, "", url.toString());172        }173 174        const reply = (data.result ?? data.reply ?? data.message ?? data.content ?? JSON.stringify(data));175        push("assistant", String(reply));176      } catch (err) {177        push("assistant", "⚠️ Error: " + (err && err.message ? err.message : err));178      } finally {179        inputEl.focus({ preventScroll: true });180      }181    }182 183    function startNewChat() {184      const cid = newCid();185      localStorage.setItem('wonka.cid', cid);186      const url = new URL(window.location.href);187      url.searchParams.set('cid', cid);188      history.pushState({}, '', url.toString());189      CONVERSATION_ID = cid;190 191      // Reset UI and in-memory convo192      convo = [];193      messagesEl.innerHTML = '';194      inputEl.value = '';195      inputEl.focus({ preventScroll: true });196    }197    // Submit handling198    $('#composer').addEventListener('submit', (e) => {199      e.preventDefault();200      const text = (inputEl.value || '').trim();201      if (!text) return;202      inputEl.value = '';203      autoGrow(inputEl);204      sendMessage(text);205    });206 207    inputEl.addEventListener('keydown', (e) => {208      if (e.isComposing) return;209      if (e.key === 'Enter' && !e.shiftKey) {210        e.preventDefault();211        $('#composer').dispatchEvent(new Event('submit'));212      }213    });214// Textarea auto-grow215    ['input', 'change'].forEach(ev => inputEl.addEventListener(ev, () => autoGrow(inputEl)));216    window.addEventListener('resize', () => autoGrow(inputEl));217    autoGrow(inputEl);218    // Health check219    const statusEl = $('#status');220    async function refreshHealth() {221      try {222        const { status, version } = await fetchHealthStatus();223        const ok = String(status).toLowerCase() === 'ok';224        statusEl.classList.toggle('ok', ok);225        statusEl.classList.toggle('down', !ok);226        statusEl.lastElementChild.innerHTML = `<strong>API:</strong> ${version || 'unknown'}`;227        statusEl.title = `Back-end status: ${status}${version ? ' • ' + version : ''}`;228      } catch (error) {229        console.error('Health check failed:', error);230        statusEl.classList.remove('ok');231        statusEl.classList.add('down');232        statusEl.lastElementChild.innerHTML = `<strong>API:</strong> offline`;233        statusEl.title = 'Back-end unreachable';234      }235    }236    refreshHealth();237    setInterval(refreshHealth, CONFIG.HEALTH_CHECK_INTERVAL);238</script>239</body>240</html>241