644 lines
17 KiB
HTML
644 lines
17 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Edge-Net Demo</title>
|
|
<style>
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
color: #fff;
|
|
min-height: 100vh;
|
|
padding: 2rem;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
header {
|
|
text-align: center;
|
|
margin-bottom: 3rem;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 2.5rem;
|
|
margin-bottom: 0.5rem;
|
|
background: linear-gradient(90deg, #00d4ff, #7b2cbf);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
}
|
|
|
|
.subtitle {
|
|
color: #888;
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
.dashboard {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
gap: 1.5rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.card {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border-radius: 16px;
|
|
padding: 1.5rem;
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.card h3 {
|
|
font-size: 0.9rem;
|
|
color: #888;
|
|
text-transform: uppercase;
|
|
letter-spacing: 1px;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.stat {
|
|
font-size: 2.5rem;
|
|
font-weight: bold;
|
|
color: #00d4ff;
|
|
}
|
|
|
|
.stat.credits {
|
|
color: #00ff88;
|
|
}
|
|
|
|
.stat.multiplier {
|
|
color: #ff6b6b;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 0.85rem;
|
|
color: #666;
|
|
}
|
|
|
|
.status-indicator {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.5rem 1rem;
|
|
background: rgba(0, 255, 136, 0.1);
|
|
border-radius: 20px;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.status-dot {
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
background: #00ff88;
|
|
animation: pulse 2s infinite;
|
|
}
|
|
|
|
.status-dot.disconnected {
|
|
background: #ff6b6b;
|
|
animation: none;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.5; }
|
|
}
|
|
|
|
.controls {
|
|
display: flex;
|
|
gap: 1rem;
|
|
flex-wrap: wrap;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
button {
|
|
padding: 0.75rem 1.5rem;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-size: 1rem;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
button.primary {
|
|
background: linear-gradient(90deg, #00d4ff, #7b2cbf);
|
|
color: white;
|
|
}
|
|
|
|
button.secondary {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
color: white;
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
button:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(0, 212, 255, 0.3);
|
|
}
|
|
|
|
button:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
}
|
|
|
|
.log {
|
|
background: rgba(0, 0, 0, 0.3);
|
|
border-radius: 12px;
|
|
padding: 1rem;
|
|
font-family: 'Monaco', 'Menlo', monospace;
|
|
font-size: 0.85rem;
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.log-entry {
|
|
padding: 0.25rem 0;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
.log-entry.info { color: #00d4ff; }
|
|
.log-entry.success { color: #00ff88; }
|
|
.log-entry.error { color: #ff6b6b; }
|
|
.log-entry.warning { color: #ffaa00; }
|
|
|
|
.timestamp {
|
|
color: #666;
|
|
margin-right: 0.5rem;
|
|
}
|
|
|
|
.consent-modal {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.8);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.consent-modal.hidden {
|
|
display: none;
|
|
}
|
|
|
|
.consent-content {
|
|
background: #1a1a2e;
|
|
border-radius: 16px;
|
|
padding: 2rem;
|
|
max-width: 500px;
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
.consent-content h2 {
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.consent-content p {
|
|
color: #888;
|
|
margin-bottom: 1.5rem;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.consent-content ul {
|
|
margin-bottom: 1.5rem;
|
|
padding-left: 1.5rem;
|
|
}
|
|
|
|
.consent-content li {
|
|
color: #00ff88;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.consent-buttons {
|
|
display: flex;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.slider-container {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.slider-container label {
|
|
display: block;
|
|
margin-bottom: 0.5rem;
|
|
color: #888;
|
|
}
|
|
|
|
input[type="range"] {
|
|
width: 100%;
|
|
height: 8px;
|
|
border-radius: 4px;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
appearance: none;
|
|
}
|
|
|
|
input[type="range"]::-webkit-slider-thumb {
|
|
appearance: none;
|
|
width: 20px;
|
|
height: 20px;
|
|
border-radius: 50%;
|
|
background: #00d4ff;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.cpu-value {
|
|
text-align: center;
|
|
font-size: 1.5rem;
|
|
color: #00d4ff;
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
.task-form {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border-radius: 12px;
|
|
padding: 1.5rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.task-form h3 {
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.form-group label {
|
|
display: block;
|
|
margin-bottom: 0.5rem;
|
|
color: #888;
|
|
}
|
|
|
|
select, input[type="text"] {
|
|
width: 100%;
|
|
padding: 0.75rem;
|
|
border-radius: 8px;
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
background: rgba(0, 0, 0, 0.3);
|
|
color: white;
|
|
font-size: 1rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- Consent Modal -->
|
|
<div id="consent-modal" class="consent-modal">
|
|
<div class="consent-content">
|
|
<h2>Join the Edge-Net Compute Network</h2>
|
|
<p>
|
|
Contribute idle CPU cycles while browsing to earn <strong>rUv</strong> (Resource Utility Vouchers).
|
|
Use your vouchers to access distributed AI compute power.
|
|
</p>
|
|
<ul>
|
|
<li>Earn rUv while you browse</li>
|
|
<li>Early adopter bonus: <span id="multiplier-preview">10x</span> multiplier</li>
|
|
<li>Zero cost - use what you earn</li>
|
|
<li>Full privacy - no personal data collected</li>
|
|
</ul>
|
|
|
|
<div class="slider-container">
|
|
<label>CPU Contribution Limit</label>
|
|
<input type="range" id="cpu-slider" min="10" max="50" value="30">
|
|
<div class="cpu-value"><span id="cpu-value">30</span>% max CPU</div>
|
|
</div>
|
|
|
|
<div class="consent-buttons">
|
|
<button class="primary" id="accept-btn">Start Contributing</button>
|
|
<button class="secondary" id="decline-btn">Not Now</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="container">
|
|
<header>
|
|
<h1>Edge-Net Demo</h1>
|
|
<p class="subtitle">Distributed Compute Intelligence Network</p>
|
|
</header>
|
|
|
|
<div class="dashboard">
|
|
<div class="card">
|
|
<h3>Status</h3>
|
|
<div class="status-indicator">
|
|
<span class="status-dot disconnected" id="status-dot"></span>
|
|
<span id="status-text">Disconnected</span>
|
|
</div>
|
|
<div style="margin-top: 1rem;">
|
|
<span class="stat-label">Node ID:</span>
|
|
<div id="node-id" style="font-family: monospace; font-size: 0.8rem; color: #666;">-</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Balance</h3>
|
|
<div class="stat credits" id="balance">0</div>
|
|
<div class="stat-label">rUv (Resource Utility Vouchers)</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Multiplier</h3>
|
|
<div class="stat multiplier" id="multiplier">1.0x</div>
|
|
<div class="stat-label">Early Adopter Bonus</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Tasks Completed</h3>
|
|
<div class="stat" id="tasks">0</div>
|
|
<div class="stat-label">Total Tasks</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Uptime</h3>
|
|
<div class="stat" id="uptime">0:00</div>
|
|
<div class="stat-label">Hours Contributing</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Connected Peers</h3>
|
|
<div class="stat" id="peers">0</div>
|
|
<div class="stat-label">Network Nodes</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="controls">
|
|
<button class="primary" id="start-btn" disabled>Start Contributing</button>
|
|
<button class="secondary" id="pause-btn" disabled>Pause</button>
|
|
<button class="secondary" id="stake-btn" disabled>Stake 100 rUv</button>
|
|
<button class="secondary" id="disconnect-btn" disabled>Disconnect</button>
|
|
</div>
|
|
|
|
<!-- Task Submission -->
|
|
<div class="task-form">
|
|
<h3>Submit a Task (spend rUv)</h3>
|
|
<div class="form-group">
|
|
<label>Task Type</label>
|
|
<select id="task-type">
|
|
<option value="vector_search">Vector Search (1 rUv)</option>
|
|
<option value="embedding">Generate Embedding (5 rUv)</option>
|
|
<option value="encryption">Encryption (0.1 rUv)</option>
|
|
</select>
|
|
</div>
|
|
<button class="primary" id="submit-task-btn" disabled>Submit Task</button>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Activity Log</h3>
|
|
<div class="log" id="log">
|
|
<div class="log-entry info">
|
|
<span class="timestamp">[--:--:--]</span>
|
|
Waiting for initialization...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="module">
|
|
// Mock EdgeNet for demo (replace with actual import in production)
|
|
// import { EdgeNet } from '@ruvector/edge-net';
|
|
|
|
// DOM Elements
|
|
const consentModal = document.getElementById('consent-modal');
|
|
const cpuSlider = document.getElementById('cpu-slider');
|
|
const cpuValue = document.getElementById('cpu-value');
|
|
const acceptBtn = document.getElementById('accept-btn');
|
|
const declineBtn = document.getElementById('decline-btn');
|
|
const startBtn = document.getElementById('start-btn');
|
|
const pauseBtn = document.getElementById('pause-btn');
|
|
const stakeBtn = document.getElementById('stake-btn');
|
|
const disconnectBtn = document.getElementById('disconnect-btn');
|
|
const submitTaskBtn = document.getElementById('submit-task-btn');
|
|
const logContainer = document.getElementById('log');
|
|
|
|
// State
|
|
let node = null;
|
|
let isConnected = false;
|
|
let isPaused = false;
|
|
let startTime = null;
|
|
let updateInterval = null;
|
|
|
|
// Check for prior consent
|
|
const hasConsent = localStorage.getItem('edge-net-consent') === 'true';
|
|
if (hasConsent) {
|
|
consentModal.classList.add('hidden');
|
|
initEdgeNet(parseInt(localStorage.getItem('edge-net-cpu') || '30'));
|
|
}
|
|
|
|
// CPU slider
|
|
cpuSlider.addEventListener('input', () => {
|
|
cpuValue.textContent = cpuSlider.value;
|
|
});
|
|
|
|
// Accept consent
|
|
acceptBtn.addEventListener('click', async () => {
|
|
const cpuLimit = parseInt(cpuSlider.value);
|
|
localStorage.setItem('edge-net-consent', 'true');
|
|
localStorage.setItem('edge-net-cpu', cpuLimit.toString());
|
|
consentModal.classList.add('hidden');
|
|
await initEdgeNet(cpuLimit);
|
|
});
|
|
|
|
// Decline consent
|
|
declineBtn.addEventListener('click', () => {
|
|
consentModal.classList.add('hidden');
|
|
log('User declined edge-net participation', 'warning');
|
|
});
|
|
|
|
// Initialize EdgeNet
|
|
async function initEdgeNet(cpuLimit) {
|
|
log('Initializing Edge-Net...', 'info');
|
|
|
|
try {
|
|
// Mock initialization for demo
|
|
// In production: node = await EdgeNet.init({ siteId: 'demo', contribution: cpuLimit / 100 });
|
|
|
|
await simulateInit();
|
|
|
|
node = createMockNode();
|
|
isConnected = true;
|
|
startTime = Date.now();
|
|
|
|
updateUI();
|
|
startBtn.disabled = true;
|
|
pauseBtn.disabled = false;
|
|
stakeBtn.disabled = false;
|
|
disconnectBtn.disabled = false;
|
|
submitTaskBtn.disabled = false;
|
|
|
|
log(`Connected! Node ID: ${node.nodeId}`, 'success');
|
|
log(`Current multiplier: ${node.multiplier.toFixed(1)}x`, 'info');
|
|
|
|
// Start update loop
|
|
updateInterval = setInterval(updateStats, 1000);
|
|
|
|
// Simulate earning credits
|
|
simulateEarnings();
|
|
|
|
} catch (error) {
|
|
log(`Failed to initialize: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
// Button handlers
|
|
pauseBtn.addEventListener('click', () => {
|
|
if (isPaused) {
|
|
isPaused = false;
|
|
pauseBtn.textContent = 'Pause';
|
|
log('Resumed contribution', 'info');
|
|
} else {
|
|
isPaused = true;
|
|
pauseBtn.textContent = 'Resume';
|
|
log('Paused contribution', 'warning');
|
|
}
|
|
});
|
|
|
|
stakeBtn.addEventListener('click', async () => {
|
|
if (node && node.balance >= 100) {
|
|
node.staked += 100;
|
|
node.balance -= 100;
|
|
updateUI();
|
|
log('Staked 100 rUv for priority task assignment', 'success');
|
|
} else {
|
|
log('Insufficient balance for staking', 'error');
|
|
}
|
|
});
|
|
|
|
disconnectBtn.addEventListener('click', () => {
|
|
if (updateInterval) {
|
|
clearInterval(updateInterval);
|
|
}
|
|
isConnected = false;
|
|
isPaused = false;
|
|
node = null;
|
|
updateUI();
|
|
log('Disconnected from Edge-Net', 'warning');
|
|
|
|
startBtn.disabled = false;
|
|
pauseBtn.disabled = true;
|
|
stakeBtn.disabled = true;
|
|
disconnectBtn.disabled = true;
|
|
submitTaskBtn.disabled = true;
|
|
});
|
|
|
|
startBtn.addEventListener('click', () => {
|
|
const cpuLimit = parseInt(localStorage.getItem('edge-net-cpu') || '30');
|
|
initEdgeNet(cpuLimit);
|
|
});
|
|
|
|
submitTaskBtn.addEventListener('click', async () => {
|
|
const taskType = document.getElementById('task-type').value;
|
|
const costs = { vector_search: 1, embedding: 5, encryption: 0.1 };
|
|
const cost = costs[taskType];
|
|
|
|
if (node && node.balance >= cost) {
|
|
node.balance -= cost;
|
|
log(`Submitted ${taskType} task (-${cost} rUv)`, 'info');
|
|
|
|
// Simulate task completion
|
|
await new Promise(r => setTimeout(r, 1500));
|
|
log(`Task completed: ${taskType}`, 'success');
|
|
updateUI();
|
|
} else {
|
|
log('Insufficient balance for task', 'error');
|
|
}
|
|
});
|
|
|
|
// Helper functions
|
|
function log(message, type = 'info') {
|
|
const time = new Date().toLocaleTimeString();
|
|
const entry = document.createElement('div');
|
|
entry.className = `log-entry ${type}`;
|
|
entry.innerHTML = `<span class="timestamp">[${time}]</span> ${message}`;
|
|
logContainer.appendChild(entry);
|
|
logContainer.scrollTop = logContainer.scrollHeight;
|
|
}
|
|
|
|
function updateUI() {
|
|
const statusDot = document.getElementById('status-dot');
|
|
const statusText = document.getElementById('status-text');
|
|
|
|
if (isConnected) {
|
|
statusDot.classList.remove('disconnected');
|
|
statusText.textContent = isPaused ? 'Paused' : 'Contributing';
|
|
document.getElementById('node-id').textContent = node?.nodeId || '-';
|
|
document.getElementById('balance').textContent = node?.balance.toFixed(2) + ' rUv' || '0 rUv';
|
|
document.getElementById('multiplier').textContent = `${node?.multiplier.toFixed(1)}x` || '1.0x';
|
|
document.getElementById('tasks').textContent = node?.tasksCompleted || '0';
|
|
document.getElementById('peers').textContent = node?.peers || '0';
|
|
} else {
|
|
statusDot.classList.add('disconnected');
|
|
statusText.textContent = 'Disconnected';
|
|
document.getElementById('node-id').textContent = '-';
|
|
document.getElementById('balance').textContent = '0 rUv';
|
|
document.getElementById('multiplier').textContent = '1.0x';
|
|
document.getElementById('tasks').textContent = '0';
|
|
document.getElementById('uptime').textContent = '0:00';
|
|
document.getElementById('peers').textContent = '0';
|
|
}
|
|
}
|
|
|
|
function updateStats() {
|
|
if (!isConnected || !startTime) return;
|
|
|
|
// Update uptime
|
|
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
const hours = Math.floor(elapsed / 3600);
|
|
const minutes = Math.floor((elapsed % 3600) / 60);
|
|
document.getElementById('uptime').textContent = `${hours}:${minutes.toString().padStart(2, '0')}`;
|
|
|
|
updateUI();
|
|
}
|
|
|
|
// Mock functions for demo
|
|
async function simulateInit() {
|
|
await new Promise(r => setTimeout(r, 1000));
|
|
}
|
|
|
|
function createMockNode() {
|
|
return {
|
|
nodeId: 'node-' + Math.random().toString(36).substr(2, 8),
|
|
balance: 0,
|
|
staked: 0,
|
|
multiplier: 9.1, // Early adopter bonus
|
|
tasksCompleted: 0,
|
|
peers: Math.floor(Math.random() * 50) + 10,
|
|
};
|
|
}
|
|
|
|
function simulateEarnings() {
|
|
if (!isConnected) return;
|
|
|
|
setInterval(() => {
|
|
if (!isConnected || isPaused || !node) return;
|
|
|
|
// Simulate task completion
|
|
if (Math.random() > 0.7) {
|
|
const baseReward = 0.1 + Math.random() * 0.4;
|
|
const reward = baseReward * node.multiplier;
|
|
node.balance += reward;
|
|
node.tasksCompleted++;
|
|
|
|
log(`Task completed! +${reward.toFixed(2)} rUv (${node.multiplier.toFixed(1)}x bonus)`, 'success');
|
|
|
|
// Randomly add peers
|
|
if (Math.random() > 0.8) {
|
|
node.peers += Math.floor(Math.random() * 3) + 1;
|
|
}
|
|
|
|
updateUI();
|
|
}
|
|
}, 3000);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|