GLE / index.html
Shinhati2023's picture
Create index.html
72d6444 verified
Raw
History Blame Contribute Delete
19.2 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TEKDEV // AGENT</title>
<link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=VT323&display=swap" rel="stylesheet">
<style>
:root {
--green: #00ff41;
--green-dim: #00cc33;
--green-dark: #003b00;
--green-glow: rgba(0,255,65,0.15);
--bg: #0a0a0a;
--text-dim: #007a1f;
--yellow: #ffd700;
--red: #ff4444;
--blue: #4af;
}
- { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(–bg);
color: var(–green);
font-family: ‘Share Tech Mono’, monospace;
font-size: 13px;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
body::before {
content: ‘’;
position: fixed;
inset: 0;
background: repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,0,0,0.07) 2px, rgba(0,0,0,0.07) 4px);
pointer-events: none;
z-index: 9999;
}
/* HEADER */
header {
border-bottom: 1px solid var(–green-dark);
padding: 7px 14px;
display: flex;
align-items: center;
justify-content: space-between;
background: #060606;
flex-shrink: 0;
}
.logo { font-family: ‘VT323’, monospace; font-size: 20px; letter-spacing: 4px; text-shadow: 0 0 10px var(–green); }
.logo span { color: var(–text-dim); }
.header-right { display: flex; align-items: center; gap: 10px; }
.status-pill {
display: flex; align-items: center; gap: 6px;
font-size: 10px; color: var(–text-dim);
}
.dot { width: 6px; height: 6px; border-radius: 50%; background: var(–green); box-shadow: 0 0 6px var(–green); animation: pulse 2s infinite; }
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.3} }
select, .hdr-btn {
background: transparent; border: 1px solid var(–green-dark);
color: var(–text-dim); font-family: ‘Share Tech Mono’, monospace;
font-size: 10px; padding: 3px 8px; cursor: pointer; outline: none;
transition: all 0.2s;
}
select:hover, .hdr-btn:hover { border-color: var(–green); color: var(–green); }
option { background: #080808; }
/* TABS */
.tabs {
display: flex; border-bottom: 1px solid var(–green-dark);
background: #060606; flex-shrink: 0;
}
.tab {
padding: 6px 16px; font-size: 11px; cursor: pointer;
color: var(–text-dim); border-right: 1px solid var(–green-dark);
transition: all 0.2s;
}
.tab:hover { color: var(–green); background: var(–green-glow); }
.tab.active { color: var(–green); border-bottom: 1px solid var(–green); background: rgba(0,255,65,0.05); }
/* PANELS */
.panel { display: none; flex: 1; flex-direction: column; overflow: hidden; }
.panel.active { display: flex; }
/* TERMINAL */
#terminal {
flex: 1; overflow-y: auto; padding: 12px 14px;
display: flex; flex-direction: column; gap: 1px;
}
#terminal::-webkit-scrollbar { width: 3px; }
#terminal::-webkit-scrollbar-thumb { background: var(–green-dark); }
.line { line-height: 1.65; white-space: pre-wrap; word-break: break-word; }
.line.user-cmd { color: var(–green); }
.line.user-cmd::before { content: ’you › ’; color: var(–text-dim); }
.line.agent-text { color: #00cc33; }
.line.cmd-running { color: var(–yellow); }
.line.cmd-running::before { content: ’⚡ exec › ’; }
.line.cmd-out { color: #009920; padding-left: 8px; border-left: 2px solid var(–green-dark); }
.line.cmd-err { color: #cc4444; padding-left: 8px; border-left: 2px solid #cc2222; }
.line.sys { color: var(–text-dim); font-size: 11px; }
.line.err { color: var(–red); }
.line.blank { height: 5px; }
.line.streaming::after { content: ‘▋’; animation: blink 0.6s infinite; }
@keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} }
/* INPUT */
#input-area {
border-top: 1px solid var(–green-dark);
background: #060606; flex-shrink: 0;
}
#input-row {
display: flex; align-items: flex-end; gap: 8px;
padding: 8px 14px;
}
.prefix { color: var(–text-dim); white-space: nowrap; padding-bottom: 2px; }
#cmd-input {
flex: 1; background: transparent; border: none;
color: var(–green); font-family: ‘Share Tech Mono’, monospace;
font-size: 13px; outline: none; caret-color: var(–green);
resize: none; min-height: 20px; max-height: 120px;
overflow-y: auto; line-height: 1.5;
}
.run-btn {
background: transparent; border: 1px solid var(–green);
color: var(–green); font-family: ‘Share Tech Mono’, monospace;
font-size: 11px; padding: 5px 14px; cursor: pointer;
transition: all 0.2s; white-space: nowrap; align-self: flex-end;
}
.run-btn:hover { background: var(–green-glow); box-shadow: 0 0 10px var(–green-glow); }
.run-btn:disabled { opacity: 0.3; cursor: not-allowed; }
/* QUICK CMDS */
#quickbar {
padding: 5px 14px 7px; display: flex; gap: 6px; flex-wrap: wrap;
border-top: 1px solid #111;
}
.qbtn {
background: transparent; border: 1px solid #1a2a1a;
color: #005c10; font-family: ‘Share Tech Mono’, monospace;
font-size: 10px; padding: 2px 8px; cursor: pointer; transition: all 0.2s;
}
.qbtn:hover { border-color: var(–green-dim); color: var(–green-dim); background: var(–green-glow); }
/* SETTINGS PANEL */
#panel-settings {
padding: 20px; overflow-y: auto; gap: 16px;
}
.setting-group { margin-bottom: 20px; }
.setting-group h3 { color: var(–green); font-size: 12px; letter-spacing: 2px; margin-bottom: 10px; border-bottom: 1px solid var(–green-dark); padding-bottom: 6px; }
.field { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; }
.field label { font-size: 10px; color: var(–text-dim); }
.field input {
background: #0d0d0d; border: 1px solid var(–green-dark);
color: var(–green); font-family: ‘Share Tech Mono’, monospace;
font-size: 12px; padding: 6px 10px; outline: none; transition: border-color 0.2s;
}
.field input:focus { border-color: var(–green); box-shadow: 0 0 8px var(–green-glow); }
.save-btn {
background: transparent; border: 1px solid var(–green);
color: var(–green); font-family: ‘Share Tech Mono’, monospace;
font-size: 11px; padding: 6px 20px; cursor: pointer; transition: all 0.2s;
}
.save-btn:hover { background: var(–green-glow); }
.hint { font-size: 10px; color: var(–text-dim); margin-top: 4px; line-height: 1.5; }
/* FILES PANEL */
#panel-files { padding: 14px; overflow-y: auto; }
#file-tree { font-size: 12px; color: #009920; line-height: 1.8; }
.refresh-btn {
background: transparent; border: 1px solid var(–green-dark);
color: var(–text-dim); font-family: ‘Share Tech Mono’, monospace;
font-size: 10px; padding: 3px 10px; cursor: pointer; margin-bottom: 12px;
transition: all 0.2s;
}
.refresh-btn:hover { border-color: var(–green); color: var(–green); }
</style>
</head>
<body>
<header>
<div class="logo">TEKDEV<span>//</span>AGENT</div>
<div class="header-right">
<div class="status-pill">
<div class="dot" id="status-dot"></div>
<span id="status-text">READY</span>
</div>
<select id="model-select">
<option value="google/gemma-4-31b-it:free">gemma-4-31b</option>
<option value="google/gemma-4-26b-a4b-it:free">gemma-4-26b-moe</option>
<option value="qwen/qwen3-235b-a22b:free">qwen3-235b</option>
<option value="meta-llama/llama-4-maverick:free">llama-4-maverick</option>
<option value="nvidia/nemotron-3-super-120b-a12b:free">nemotron-120b</option>
</select>
<button class="hdr-btn" onclick="clearTerminal()">CLEAR</button>
</div>
</header>
<div class="tabs">
<div class="tab active" onclick="switchTab('terminal', this)">TERMINAL</div>
<div class="tab" onclick="switchTab('files', this)">WORKSPACE</div>
<div class="tab" onclick="switchTab('settings', this)">CONFIG</div>
</div>
<!-- TERMINAL PANEL -->
<div class="panel active" id="panel-terminal">
<div id="terminal"></div>
<div id="input-area">
<div id="input-row">
<span class="prefix"></span>
<textarea id="cmd-input" rows="1" placeholder="describe a task or type a command..." spellcheck="false"></textarea>
<button class="run-btn" id="run-btn" onclick="sendCommand()">RUN</button>
</div>
<div id="quickbar">
<button class="qbtn" onclick="q('clone my repo and show me the structure')">clone repo</button>
<button class="qbtn" onclick="q('show me all files in the workspace')">ls workspace</button>
<button class="qbtn" onclick="q('git status and show recent commits')">git status</button>
<button class="qbtn" onclick="q('stage all changes and commit with a smart message')">git commit</button>
<button class="qbtn" onclick="q('push to origin main')">git push</button>
<button class="qbtn" onclick="q('review the code and suggest improvements')">review code</button>
<button class="qbtn" onclick="q('install dependencies and run the project')">npm install & run</button>
<button class="qbtn" onclick="q('show git diff of latest changes')">git diff</button>
</div>
</div>
</div>
<!-- FILES PANEL -->
<div class="panel" id="panel-files">
<button class="refresh-btn" onclick="loadFiles()">⟳ REFRESH</button>
<div id="file-tree">// loading workspace...</div>
</div>
<!-- SETTINGS PANEL -->
<div class="panel" id="panel-settings">
<div class="setting-group">
<h3>// API KEY</h3>
<div class="field">
<label>OPENROUTER API KEY</label>
<input type="password" id="s-apikey" placeholder="sk-or-v1-..." />
</div>
<p class="hint">Get a free key at openrouter.ai — stored in localStorage only.</p>
</div>
<div class="setting-group">
<h3>// GIT IDENTITY</h3>
<div class="field">
<label>GIT NAME</label>
<input type="text" id="s-gitname" placeholder="Your Name" />
</div>
<div class="field">
<label>GIT EMAIL</label>
<input type="text" id="s-gitemail" placeholder="you@email.com" />
</div>
</div>
<div class="setting-group">
<h3>// GITHUB</h3>
<div class="field">
<label>GITHUB TOKEN (for push)</label>
<input type="password" id="s-ghtoken" placeholder="ghp_..." />
</div>
<p class="hint">Generate at github.com/settings/tokens — needs repo scope.<br>Used as: https://TOKEN@github.com/user/repo.git</p>
</div>
<button class="save-btn" onclick="saveConfig()">SAVE CONFIG</button>
</div>
<script>
const terminal = document.getElementById('terminal');
const cmdInput = document.getElementById('cmd-input');
const runBtn = document.getElementById('run-btn');
let isRunning = false;
let history = [];
// Auto-resize textarea
cmdInput.addEventListener('input', () => {
cmdInput.style.height = 'auto';
cmdInput.style.height = Math.min(cmdInput.scrollHeight, 120) + 'px';
});
cmdInput.addEventListener('keydown', e => {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendCommand(); }
});
// Init
window.onload = () => {
loadSavedConfig();
boot();
};
function boot() {
[
['╔════════════════════════════════════╗', 'sys'],
['║ TEKDEV AGENT // DOCKER ONLINE ║', 'sys'],
['╚════════════════════════════════════╝', 'sys'],
['', 'blank'],
['// Real shell access — Ubuntu container', 'sys'],
['// Workspace: /data/workspace (persistent)', 'sys'],
['// Set your keys in CONFIG tab first', 'sys'],
['// Then try: "clone my repo and show structure"', 'sys'],
['', 'blank'],
].forEach((l, i) => setTimeout(() => printLine(l[0], l[1]), i * 50));
}
function loadSavedConfig() {
const k = localStorage.getItem('or_key');
const gn = localStorage.getItem('git_name');
const ge = localStorage.getItem('git_email');
const gh = localStorage.getItem('gh_token');
if (k) document.getElementById('s-apikey').value = k;
if (gn) document.getElementById('s-gitname').value = gn;
if (ge) document.getElementById('s-gitemail').value = ge;
if (gh) document.getElementById('s-ghtoken').value = gh;
}
function saveConfig() {
const key = document.getElementById('s-apikey').value.trim();
const gn = document.getElementById('s-gitname').value.trim();
const ge = document.getElementById('s-gitemail').value.trim();
const gh = document.getElementById('s-ghtoken').value.trim();
if (key) localStorage.setItem('or_key', key);
if (gn) localStorage.setItem('git_name', gn);
if (ge) localStorage.setItem('git_email', ge);
if (gh) localStorage.setItem('gh_token', gh);
// Send to backend
fetch('/config', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ git_name: gn, git_email: ge, github_token: gh })
});
switchTab('terminal', document.querySelector('.tab'));
printLine('// Config saved. Agent configured.', 'sys');
}
function switchTab(name, el) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
el.classList.add('active');
document.getElementById(`panel-${name}`).classList.add('active');
if (name === 'files') loadFiles();
}
function printLine(text, cls = 'agent-text') {
const div = document.createElement('div');
div.className = `line ${cls}`;
div.textContent = text;
terminal.appendChild(div);
terminal.scrollTop = terminal.scrollHeight;
return div;
}
function clearTerminal() {
terminal.innerHTML = '';
history = [];
printLine('// Cleared. Session reset.', 'sys');
}
function q(text) {
cmdInput.value = text;
cmdInput.style.height = 'auto';
cmdInput.focus();
}
function setStatus(text, color = 'var(--green)') {
document.getElementById('status-text').textContent = text;
const dot = document.getElementById('status-dot');
dot.style.background = color;
dot.style.boxShadow = `0 0 6px ${color}`;
}
async function execCmd(cmd) {
printLine(cmd, 'cmd-running');
try {
const res = await fetch('/execute', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ cmd })
});
const data = await res.json();
if (data.output) {
data.output.trim().split('\n').forEach(l => printLine(l, 'cmd-out'));
}
if (data.error) {
data.error.trim().split('\n').forEach(l => printLine(l, 'cmd-err'));
}
return data;
} catch (e) {
printLine(`exec error: ${e.message}`, 'cmd-err');
return { error: e.message };
}
}
async function sendCommand() {
if (isRunning) return;
const input = cmdInput.value.trim();
if (!input) return;
const key = localStorage.getItem('or_key');
if (!key) {
printLine('// No API key — go to CONFIG tab and save your OpenRouter key.', 'err');
return;
}
cmdInput.value = '';
cmdInput.style.height = 'auto';
isRunning = true;
runBtn.disabled = true;
setStatus('THINKING...', '#ffd700');
printLine('', 'blank');
printLine(input, 'user-cmd');
printLine('', 'blank');
history.push({ role: 'user', content: input });
if (history.length > 20) history = history.slice(-20);
const model = document.getElementById('model-select').value;
let fullText = '';
const outputDiv = printLine('', 'agent-text streaming');
try {
const response = await fetch('/chat', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ messages: history, api_key: key, model })
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
setStatus('STREAMING...', '#ffd700');
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
for (const line of chunk.split('\n')) {
if (!line.startsWith('data: ')) continue;
const data = line.slice(6).trim();
if (data === '[DONE]') break;
try {
const parsed = JSON.parse(data);
if (parsed.error) throw new Error(parsed.error);
if (parsed.delta) {
fullText += parsed.delta;
outputDiv.textContent = fullText;
outputDiv.className = 'line agent-text streaming';
terminal.scrollTop = terminal.scrollHeight;
}
} catch(e) {
if (e.message && !e.message.includes('JSON')) throw e;
}
}
}
outputDiv.className = 'line agent-text';
history.push({ role: 'assistant', content: fullText });
// Extract and execute <CMD> blocks
const cmdRegex = /<CMD>([\s\S]*?)<\/CMD>/g;
let match;
const cmdsToRun = [];
while ((match = cmdRegex.exec(fullText)) !== null) {
cmdsToRun.push(match[1].trim());
}
if (cmdsToRun.length > 0) {
printLine('', 'blank');
setStatus('EXECUTING...', '#ff8c00');
const results = [];
for (const cmd of cmdsToRun) {
const result = await execCmd(cmd);
results.push({ cmd, ...result });
}
// Send results back to agent for interpretation
const resultSummary = results.map(r =>
`CMD: ${r.cmd}\nSTDOUT: ${r.output || '(empty)'}\nSTDERR: ${r.error || '(none)'}\nEXIT: ${r.returncode}`
).join('\n\n');
history.push({ role: 'user', content: `Command results:\n${resultSummary}\n\nPlease interpret these results briefly.` });
// Get agent's interpretation
printLine('', 'blank');
const interpDiv = printLine('', 'agent-text streaming');
let interpText = '';
const interpResp = await fetch('/chat', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ messages: history, api_key: key, model })
});
const reader2 = interpResp.body.getReader();
while (true) {
const { done, value } = await reader2.read();
if (done) break;
const chunk = decoder.decode(value);
for (const line of chunk.split('\n')) {
if (!line.startsWith('data: ')) continue;
const data = line.slice(6).trim();
if (data === '[DONE]') break;
try {
const parsed = JSON.parse(data);
if (parsed.delta) {
interpText += parsed.delta;
interpDiv.textContent = interpText;
terminal.scrollTop = terminal.scrollHeight;
}
} catch {}
}
}
interpDiv.className = 'line agent-text';
history.push({ role: 'assistant', content: interpText });
}
printLine('', 'blank');
setStatus('READY');
} catch (err) {
outputDiv.remove();
printLine(`// ERROR: ${err.message}`, 'err');
history.pop();
setStatus('ERROR', 'var(--red)');
setTimeout(() => setStatus('READY'), 3000);
}
isRunning = false;
runBtn.disabled = false;
cmdInput.focus();
}
async function loadFiles() {
const tree = document.getElementById('file-tree');
tree.textContent = '// scanning workspace...';
try {
const res = await fetch('/workspace');
const data = await res.json();
if (data.files && data.files.length > 0) {
tree.textContent = data.files.join('\n');
} else {
tree.textContent = '// workspace is empty\n// use "clone repo" to get started';
}
} catch {
tree.textContent = '// could not reach backend';
}
}
</script>
</body>
</html>