|
| 1 | +<!DOCTYPE html> |
| 2 | +<html lang="en"> |
| 3 | +<head> |
| 4 | + <meta charset="UTF-8"> |
| 5 | + <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 6 | + <title>Vial MCP Terminal</title> |
| 7 | + <link rel="stylesheet" href="/vial/mcp/frontend/static/css/vial-terminal.css"> |
| 8 | + <style> |
| 9 | + body { background: #000; color: #0f0; font-family: 'Courier New', monospace; margin: 0; padding: 0; height: 100vh; display: flex; flex-direction: column; align-items: center; } |
| 10 | + #terminal { width: 90%; max-width: 900px; height: 60vh; background: rgba(0, 255, 0, 0.1); border: 1px solid #0f0; padding: 10px; overflow-y: auto; font-size: 14px; } |
| 11 | + #terminal .error { color: #ff0000; } |
| 12 | + #input { width: 90%; max-width: 900px; background: #000; border: 1px solid #0f0; color: #0f0; padding: 8px; font-size: 14px; } |
| 13 | + .button-group { display: flex; gap: 10px; margin: 10px 0; } |
| 14 | + button { background: #0f0; color: #000; border: none; padding: 8px 16px; cursor: pointer; font-size: 14px; } |
| 15 | + button:hover { background: #0c0; } |
| 16 | + #status { width: 90%; max-width: 900px; margin: 10px 0; } |
| 17 | + .status-bar { display: flex; align-items: center; margin: 5px 0; } |
| 18 | + .status-bar div { flex: 1; height: 10px; background: #0f0; } |
| 19 | + </style> |
| 20 | +</head> |
| 21 | +<body> |
| 22 | + <h1>Vial MCP Testing Terminal</h1> |
| 23 | + <div id="terminal"></div> |
| 24 | + <input id="input" placeholder="Enter git commands (e.g., /prompt vial1 train dataset)"> |
| 25 | + <div class="button-group"> |
| 26 | + <button onclick="authenticate()">Authenticate</button> |
| 27 | + <button onclick="voidVials()">Void</button> |
| 28 | + <button onclick="troubleshoot()">Troubleshoot</button> |
| 29 | + <button onclick="quantumLink()">Quantum Link</button> |
| 30 | + <button onclick="exportVials()">Export</button> |
| 31 | + <button onclick="importVials()">Import</button> |
| 32 | + <button onclick="showApiCredentials()">API Credentials</button> |
| 33 | + </div> |
| 34 | + <div id="status"></div> |
| 35 | + <input type="file" id="file-input" style="display: none;" accept=".md"> |
| 36 | + <script> |
| 37 | + const terminal = document.getElementById('terminal'); |
| 38 | + const input = document.getElementById('input'); |
| 39 | + let logs = []; |
| 40 | + let isAuthenticated = false; |
| 41 | + let accessToken = null; |
| 42 | + let userId = null; |
| 43 | + let vials = ['vial1', 'vial2', 'vial3', 'vial4'].map(id => ({ id, status: 'stopped', tasks: [] })); |
| 44 | + |
| 45 | + function log(message, isError = false) { |
| 46 | + logs.push(`<div${isError ? ' class="error"' : ''}>${new Date().toISOString()} - ${message}</div>`); |
| 47 | + if (logs.length > 100) logs.shift(); |
| 48 | + terminal.innerHTML = logs.join(''); |
| 49 | + terminal.scrollTop = terminal.scrollHeight; |
| 50 | + } |
| 51 | + |
| 52 | + function updateStatus() { |
| 53 | + document.getElementById('status').innerHTML = vials.map(vial => |
| 54 | + `<div class="status-bar">${vial.id}: ${vial.status} <div style="width: ${vial.status === 'running' ? '100%' : '0%'};"></div></div>` |
| 55 | + ).join(''); |
| 56 | + } |
| 57 | + |
| 58 | + async function handleCommand(command) { |
| 59 | + if (!isAuthenticated && command !== '/help') { |
| 60 | + log('Error: Not authenticated. Please authenticate first.', true); |
| 61 | + return; |
| 62 | + } |
| 63 | + command = command.trim(); |
| 64 | + if (command === '/help') { |
| 65 | + log(`Available commands: |
| 66 | +- /prompt <vial> <text> - Send prompt to vial |
| 67 | +- /task <vial> <task> - Assign task to vial |
| 68 | +- /config <vial> <key> <value> - Set vial config |
| 69 | +- /status - Show vial statuses`, false); |
| 70 | + return; |
| 71 | + } |
| 72 | + |
| 73 | + try { |
| 74 | + const [cmd, vialId, ...args] = command.split(' '); |
| 75 | + const response = await fetch('/vial/mcp/api/endpoints', { |
| 76 | + method: 'POST', |
| 77 | + headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}` }, |
| 78 | + body: JSON.stringify({ command: cmd.slice(1), vialId, args, userId }) |
| 79 | + }); |
| 80 | + const result = await response.json(); |
| 81 | + if (result.error) throw new Error(result.error); |
| 82 | + log(`Command ${command} executed: ${JSON.stringify(result)}`); |
| 83 | + if (cmd === '/status') { |
| 84 | + vials = result.vials; |
| 85 | + updateStatus(); |
| 86 | + } else if (vialId) { |
| 87 | + const vial = vials.find(v => v.id === vialId); |
| 88 | + if (vial) vial.status = 'running'; |
| 89 | + updateStatus(); |
| 90 | + } |
| 91 | + } catch (error) { |
| 92 | + log(`Command ${command} failed: ${error.message}`, true); |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + async function authenticate() { |
| 97 | + try { |
| 98 | + const response = await fetch('/vial/mcp/api/auth', { |
| 99 | + method: 'POST', |
| 100 | + headers: { 'Content-Type': 'application/json' }, |
| 101 | + body: JSON.stringify({ method: 'oauth_login' }) |
| 102 | + }); |
| 103 | + const result = await response.json(); |
| 104 | + if (result.error) throw new Error(result.error); |
| 105 | + accessToken = result.access_token; |
| 106 | + userId = result.user_id; |
| 107 | + isAuthenticated = true; |
| 108 | + log('Authentication successful'); |
| 109 | + } catch (error) { |
| 110 | + log(`Authentication failed: ${error.message}`, true); |
| 111 | + } |
| 112 | + } |
| 113 | + |
| 114 | + function voidVials() { |
| 115 | + vials.forEach(vial => vial.status = 'stopped'); |
| 116 | + isAuthenticated = false; |
| 117 | + accessToken = null; |
| 118 | + userId = null; |
| 119 | + log('All vials voided'); |
| 120 | + updateStatus(); |
| 121 | + } |
| 122 | + |
| 123 | + async function troubleshoot() { |
| 124 | + try { |
| 125 | + const response = await fetch('/vial/mcp/api/health', { |
| 126 | + method: 'GET', |
| 127 | + headers: { 'Authorization': `Bearer ${accessToken}` } |
| 128 | + }); |
| 129 | + const result = await response.json(); |
| 130 | + log(`Health check: ${JSON.stringify(result)}`); |
| 131 | + } catch (error) { |
| 132 | + log(`Troubleshoot failed: ${error.message}`, true); |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | + async function quantumLink() { |
| 137 | + try { |
| 138 | + const response = await fetch('/vial/mcp/api/vials', { |
| 139 | + method: 'POST', |
| 140 | + headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}` }, |
| 141 | + body: JSON.stringify({ method: 'createVial', userId }) |
| 142 | + }); |
| 143 | + const result = await response.json(); |
| 144 | + if (result.error) throw new Error(result.error); |
| 145 | + log('Quantum link established'); |
| 146 | + vials.forEach(vial => vial.status = 'running'); |
| 147 | + updateStatus(); |
| 148 | + } catch (error) { |
| 149 | + log(`Quantum link failed: ${error.message}`, true); |
| 150 | + } |
| 151 | + } |
| 152 | + |
| 153 | + async function exportVials() { |
| 154 | + try { |
| 155 | + const response = await fetch('/vial/mcp/api/wallet', { |
| 156 | + method: 'POST', |
| 157 | + headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}` }, |
| 158 | + body: JSON.stringify({ method: 'export', userId }) |
| 159 | + }); |
| 160 | + const result = await response.json(); |
| 161 | + if (result.error) throw new Error(result.error); |
| 162 | + const blob = new Blob([result.markdown], { type: 'text/markdown' }); |
| 163 | + const url = URL.createObjectURL(blob); |
| 164 | + const a = document.createElement('a'); |
| 165 | + a.href = url; |
| 166 | + a.download = `vial_export_${new Date().toISOString().replace(/[:.]/g, '-')}.md`; |
| 167 | + a.click(); |
| 168 | + URL.revokeObjectURL(url); |
| 169 | + log('Vials exported successfully'); |
| 170 | + } catch (error) { |
| 171 | + log(`Export failed: ${error.message}`, true); |
| 172 | + } |
| 173 | + } |
| 174 | + |
| 175 | + async function importVials() { |
| 176 | + const fileInput = document.getElementById('file-input'); |
| 177 | + if (!fileInput.files.length) { |
| 178 | + log('No file selected', true); |
| 179 | + return; |
| 180 | + } |
| 181 | + try { |
| 182 | + const file = fileInput.files[0]; |
| 183 | + const text = await file.text(); |
| 184 | + const response = await fetch('/vial/mcp/api/wallet', { |
| 185 | + method: 'POST', |
| 186 | + headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}` }, |
| 187 | + body: JSON.stringify({ method: 'import', userId, markdown: text }) |
| 188 | + }); |
| 189 | + const result = await response.json(); |
| 190 | + if (result.error) throw new Error(result.error); |
| 191 | + vials = result.vials; |
| 192 | + log('Vials imported successfully'); |
| 193 | + updateStatus(); |
| 194 | + } catch (error) { |
| 195 | + log(`Import failed: ${error.message}`, true); |
| 196 | + } |
| 197 | + } |
| 198 | + |
| 199 | + async function showApiCredentials() { |
| 200 | + try { |
| 201 | + const response = await fetch('/vial/mcp/api/auth', { |
| 202 | + method: 'POST', |
| 203 | + headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}` }, |
| 204 | + body: JSON.stringify({ method: 'generateAPICredentials', userId }) |
| 205 | + }); |
| 206 | + const result = await response.json(); |
| 207 | + if (result.error) throw new Error(result.error); |
| 208 | + log(`API Credentials: Key=${result.key}, Secret=${result.secret}`); |
| 209 | + } catch (error) { |
| 210 | + log(`API Credentials failed: ${error.message}`, true); |
| 211 | + } |
| 212 | + } |
| 213 | + |
| 214 | + input.addEventListener('keydown', e => { |
| 215 | + if (e.key === 'Enter') { |
| 216 | + handleCommand(input.value); |
| 217 | + input.value = ''; |
| 218 | + } |
| 219 | + }); |
| 220 | + |
| 221 | + document.getElementById('file-input').addEventListener('change', importVials); |
| 222 | + log('Vial MCP Terminal initialized. Use /help for commands.'); |
| 223 | + updateStatus(); |
| 224 | + </script> |
| 225 | +</body> |
| 226 | +</html> |
0 commit comments