index.html•6.81 kB
<!DOCTYPE html>
<html data-theme="dark">
<head>
  <meta charset="UTF-8" />
  <title>FGD Stack</title>
  <style>
    :root { --bg: #0d1117; --card: #161b22; --text: #f0f6fc; --accent: #58a6ff; --border: #30363d; --log-bg: #010409; }
    [data-theme="light"] { --bg: #f7f9fc; --card: #ffffff; --text: #24292f; --accent: #0969da; --border: #d0d7de; --log-bg: #f6f8fa; }
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { background: var(--bg); color: var(--text); font-family: -apple-system, sans-serif; padding: 2rem; }
    .container { max-width: 800px; margin: 0 auto; }
    .card { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; }
    button, input, select { padding: 0.65rem; margin: 0.4rem 0; border-radius: 8px; width: 100%; font-size: 1rem; }
    button { background: var(--accent); color: white; border: none; cursor: pointer; }
    .suggestion { background: rgba(88,166,255,0.1); padding: 0.6rem; border-radius: 6px; margin: 0.3rem 0; cursor: pointer; }
    #logViewer { background: var(--log-bg); height: 300px; overflow-y: auto; padding: 0.75rem; font-family: monospace; font-size: 0.85rem; white-space: pre-wrap; }
    .theme-toggle { position: fixed; top: 1rem; right: 1rem; width: 40px; height: 40px; background: var(--card); border: 1px solid var(--border); border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; }
  </style>
</head>
<body>
  <div class="theme-toggle" id="themeToggle">Dark Mode</div>
  <div class="container">
    <h1>FGD Stack</h1>
    <div class="card">
      <h3>1. Select Directory</h3>
      <input type="file" id="dirPicker" webkitdirectory directory style="display:none">
      <button onclick="document.getElementById('dirPicker').click()">Browse...</button>
      <div id="selectedPath"></div>
    </div>
    <div class="card">
      <h3>2. Git Repos</h3>
      <div id="suggestions"></div>
    </div>
    <div class="card">
      <h3>3. LLM</h3>
      <select id="provider"><option value="grok">Grok</option><option value="openai">ChatGPT</option></select>
    </div>
    <button onclick="startServer()" id="startBtn">Launch Server</button>
    <div id="status"></div>
    <div class="card" id="logCard" style="display:none;">
      <h3>Live Logs 
        <span style="float:right;font-size:0.8rem;">
          <select id="levelFilter" style="padding:0.3rem;"><option value="">All</option><option>INFO</option><option>ERROR</option></select>
          <input id="searchFilter" placeholder="Search..." style="width:120px;padding:0.3rem;margin-left:0.5rem;">
          <button onclick="clearFilters()" style="padding:0.3rem 0.5rem;margin-left:0.3rem;">Clear</button>
        </span>
      </h3>
      <div id="logViewer"></div>
    </div>
  </div>
  <script>
    let allLogLines = [], logInterval;
    const themeToggle = document.getElementById('themeToggle');
    themeToggle.onclick = () => {
      const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
      document.documentElement.setAttribute('data-theme', isDark ? 'light' : 'dark');
      themeToggle.textContent = isDark ? 'Light Mode' : 'Dark Mode';
    };
    document.getElementById('dirPicker').onchange = () => {
      const entry = document.getElementById('dirPicker').webkitEntries?.[0];
      if (entry) window.selectedDir = entry.fullPath || entry.name;
      document.getElementById('selectedPath').innerHTML = `<strong>Selected:</strong> ${window.selectedDir || 'None'}`;
    };
    async function loadSuggestions() {
      const res = await fetch('/api/suggest');
      const paths = await res.json();
      document.getElementById('suggestions').innerHTML = paths.map(p => 
        `<div class="suggestion" onclick="window.selectedDir='${p}'; document.getElementById('selectedPath').innerHTML='<strong>Selected:</strong> ${p}'">${p}</div>`
      ).join('');
    }
    async function startServer() {
      const dir = window.selectedDir || document.getElementById('selectedPath').innerText.replace(/.*:\s*/, '');
      const provider = document.getElementById('provider').value;
      if (!dir || dir === 'None') return alert("Select a directory");
      const res = await fetch('/api/start', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({watch_dir: dir, default_provider: provider}) });
      const result = await res.json();
      document.getElementById('status').innerHTML = result.success ? 
        `<div style="background:#e8f5e9;color:#2e7d32;padding:1rem;border-radius:8px;">
          Server running!<br>Memory: ${result.memory_file}<br>
          <a href="#" onclick="showLogs('${result.log_file}'); return false;">View Live Logs</a>
        </div>` : 
        `<div style="background:#ffebee;color:#c62828;padding:1rem;border-radius:8px;">Error: ${result.error}</div>`;
    }
    function showLogs(file) {
      const card = document.getElementById('logCard');
      const viewer = document.getElementById('logViewer');
      card.style.display = 'block';
      viewer.innerHTML = 'Loading...\n';
      allLogLines = [];
      if (logInterval) clearInterval(logInterval);
      logInterval = setInterval(async () => {
        const res = await fetch(`/api/logs?file=${encodeURIComponent(file)}&t=${Date.now()}`);
        const text = await res.text();
        const lines = text.trim().split('\n');
        const newLines = lines.slice(allLogLines.length);
        allLogLines.push(...newLines);
        applyFilters();
      }, 1500);
    }
    function applyFilters() {
      const level = document.getElementById('levelFilter').value;
      const search = document.getElementById('searchFilter').value.toLowerCase();
      const viewer = document.getElementById('logViewer');
      const filtered = allLogLines.filter(l => {
        if (level && !l.includes(level)) return false;
        if (search && !l.toLowerCase().includes(search)) return false;
        return true;
      });
      viewer.innerHTML = filtered.map(l => search && l.toLowerCase().includes(search) ? 
        `<span style="background:#fffbe6;padding:0 2px;">${l.replace(/</g, '<')}</span>` : 
        l.replace(/</g, '<')).join('\n');
      viewer.scrollTop = viewer.scrollHeight;
    }
    function clearFilters() {
      document.getElementById('levelFilter').value = '';
      document.getElementById('searchFilter').value = '';
      applyFilters();
    }
    document.getElementById('levelFilter').onchange = applyFilters;
    document.getElementById('searchFilter').oninput = () => setTimeout(applyFilters, 300);
    loadSuggestions();
  </script>
</body>
</html>