Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
588
vendor/ruvector/examples/edge-net/deploy/browser/README.md
vendored
Normal file
588
vendor/ruvector/examples/edge-net/deploy/browser/README.md
vendored
Normal file
@@ -0,0 +1,588 @@
|
||||
# Edge-Net Browser Deployment
|
||||
|
||||
Deploy edge-net directly in browsers without running your own infrastructure.
|
||||
Earn **rUv (Resource Utility Vouchers)** by contributing idle compute.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ BROWSER DEPLOYMENT OPTIONS │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Option A: CDN + Public Genesis Option B: Self-Hosted │
|
||||
│ ┌────────────────────────────┐ ┌────────────────────────┐ │
|
||||
│ │ Your Website │ │ Your Website │ │
|
||||
│ │ <script src="cdn/..."> │ │ <script src="local"> │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ ▼ │ │ ▼ │ │
|
||||
│ │ ┌────────────────┐ │ │ ┌────────────────┐ │ │
|
||||
│ │ │ Public Genesis │◄──┐ │ │ │ Your Genesis │ │ │
|
||||
│ │ │ Nodes │ │ │ │ │ Node │ │ │
|
||||
│ │ └────────────────┘ │ │ │ └────────────────┘ │ │
|
||||
│ │ │ │ │ │ │
|
||||
│ │ ┌────────────────┐ │ │ │ P2P via WebRTC │ │
|
||||
│ │ │ Public GUN │◄──┘ │ │ or WebSocket │ │
|
||||
│ │ │ Relays │ │ │ │ │
|
||||
│ │ └────────────────┘ │ └────────────────────────┘ │
|
||||
│ └────────────────────────────┘ │
|
||||
│ │
|
||||
│ Best for: Quick start, testing Best for: Control, privacy │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Option A: CDN Quick Start (Recommended)
|
||||
|
||||
### 1. Add Script Tag
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>My Site</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Your website content -->
|
||||
|
||||
<!-- Edge-Net: Contribute compute, earn credits -->
|
||||
<script type="module">
|
||||
import { EdgeNet } from 'https://cdn.jsdelivr.net/npm/@ruvector/edge-net/dist/edge-net.min.js';
|
||||
|
||||
const node = await EdgeNet.init({
|
||||
siteId: 'my-site-unique-id',
|
||||
contribution: 0.3, // 30% CPU when idle
|
||||
});
|
||||
|
||||
console.log(`Node ID: ${node.nodeId()}`);
|
||||
console.log(`Balance: ${node.creditBalance()} rUv`);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 2. NPM Installation (Alternative)
|
||||
|
||||
```bash
|
||||
npm install @ruvector/edge-net
|
||||
```
|
||||
|
||||
```javascript
|
||||
import { EdgeNet } from '@ruvector/edge-net';
|
||||
|
||||
const node = await EdgeNet.init({
|
||||
siteId: 'my-site',
|
||||
contribution: 0.3,
|
||||
});
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Basic Configuration
|
||||
|
||||
```javascript
|
||||
const node = await EdgeNet.init({
|
||||
// Required
|
||||
siteId: 'your-unique-site-id',
|
||||
|
||||
// Contribution settings
|
||||
contribution: {
|
||||
cpuLimit: 0.3, // 0.0 - 1.0 (30% max CPU)
|
||||
memoryLimit: 256_000_000, // 256MB max memory
|
||||
bandwidthLimit: 1_000_000, // 1MB/s max bandwidth
|
||||
tasks: ['vectors', 'embeddings', 'encryption'],
|
||||
},
|
||||
|
||||
// Idle detection
|
||||
idle: {
|
||||
minIdleTime: 5000, // Wait 5s of idle before working
|
||||
respectBattery: true, // Reduce when on battery
|
||||
respectDataSaver: true, // Respect data saver mode
|
||||
},
|
||||
|
||||
// UI integration
|
||||
ui: {
|
||||
showBadge: true, // Show contribution badge
|
||||
badgePosition: 'bottom-right',
|
||||
onEarn: (credits) => {
|
||||
// Custom notification on earning
|
||||
console.log(`Earned ${credits} QDAG!`);
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
```javascript
|
||||
const node = await EdgeNet.init({
|
||||
siteId: 'my-site',
|
||||
|
||||
// Network settings
|
||||
network: {
|
||||
// Use public genesis nodes (default)
|
||||
genesis: [
|
||||
'https://us-east1-edge-net.cloudfunctions.net/genesis',
|
||||
'https://europe-west1-edge-net.cloudfunctions.net/genesis',
|
||||
'https://asia-east1-edge-net.cloudfunctions.net/genesis',
|
||||
],
|
||||
|
||||
// P2P relay servers
|
||||
relays: [
|
||||
'https://gun-manhattan.herokuapp.com/gun',
|
||||
'https://gun-us.herokuapp.com/gun',
|
||||
],
|
||||
|
||||
// WebRTC configuration
|
||||
webrtc: {
|
||||
enabled: true,
|
||||
iceServers: [
|
||||
{ urls: 'stun:stun.l.google.com:19302' },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// Staking for higher priority
|
||||
stake: {
|
||||
amount: 100, // Stake 100 QDAG
|
||||
autoStake: true, // Auto-stake earnings
|
||||
},
|
||||
|
||||
// Callbacks
|
||||
onCredit: (earned, total) => console.log(`+${earned} QDAG`),
|
||||
onTask: (task) => console.log(`Processing: ${task.type}`),
|
||||
onError: (error) => console.error('Edge-Net error:', error),
|
||||
onConnect: (peers) => console.log(`Connected to ${peers} peers`),
|
||||
onDisconnect: () => console.log('Disconnected'),
|
||||
});
|
||||
```
|
||||
|
||||
## Widget Integration
|
||||
|
||||
### Contribution Badge
|
||||
|
||||
Show users their rUv contribution status:
|
||||
|
||||
```html
|
||||
<!-- Include the badge component -->
|
||||
<div id="edge-net-badge"></div>
|
||||
|
||||
<script type="module">
|
||||
import { EdgeNet, Badge } from '@ruvector/edge-net';
|
||||
|
||||
const node = await EdgeNet.init({ siteId: 'my-site' });
|
||||
|
||||
// Mount badge
|
||||
Badge.mount('#edge-net-badge', node, {
|
||||
theme: 'dark', // 'light' | 'dark' | 'auto'
|
||||
showRuv: true, // Show rUv balance
|
||||
showMultiplier: true,
|
||||
showUptime: true,
|
||||
minimizable: true,
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### Dashboard Widget
|
||||
|
||||
Full contribution dashboard:
|
||||
|
||||
```html
|
||||
<div id="edge-net-dashboard" style="width: 400px; height: 300px;"></div>
|
||||
|
||||
<script type="module">
|
||||
import { EdgeNet, Dashboard } from '@ruvector/edge-net';
|
||||
|
||||
const node = await EdgeNet.init({ siteId: 'my-site' });
|
||||
|
||||
Dashboard.mount('#edge-net-dashboard', node, {
|
||||
showStats: true,
|
||||
showHistory: true,
|
||||
showTasks: true,
|
||||
showPeers: true,
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
## User Consent Patterns
|
||||
|
||||
### Opt-In Modal
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import { EdgeNet, ConsentModal } from '@ruvector/edge-net';
|
||||
|
||||
// Show consent modal before initializing
|
||||
const consent = await ConsentModal.show({
|
||||
title: 'Help power our AI features',
|
||||
message: 'Contribute idle compute cycles to earn QDAG credits.',
|
||||
benefits: [
|
||||
'Earn credits while browsing',
|
||||
'Use credits for AI features',
|
||||
'Early adopter bonus: 5.2x multiplier',
|
||||
],
|
||||
learnMoreUrl: '/edge-net-info',
|
||||
});
|
||||
|
||||
if (consent.accepted) {
|
||||
const node = await EdgeNet.init({
|
||||
siteId: 'my-site',
|
||||
contribution: consent.cpuLimit, // User-selected limit
|
||||
});
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### Banner Opt-In
|
||||
|
||||
```html
|
||||
<div id="edge-net-banner"></div>
|
||||
|
||||
<script type="module">
|
||||
import { EdgeNet, ConsentBanner } from '@ruvector/edge-net';
|
||||
|
||||
ConsentBanner.show('#edge-net-banner', {
|
||||
onAccept: async (settings) => {
|
||||
const node = await EdgeNet.init({
|
||||
siteId: 'my-site',
|
||||
contribution: settings.cpuLimit,
|
||||
});
|
||||
},
|
||||
onDecline: () => {
|
||||
// User declined - respect their choice
|
||||
console.log('User declined edge-net participation');
|
||||
},
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
## Task Submission
|
||||
|
||||
Use earned credits for compute tasks:
|
||||
|
||||
```javascript
|
||||
// Check balance first
|
||||
if (node.creditBalance() >= 5) {
|
||||
// Submit vector search task
|
||||
const result = await node.submitTask('vector_search', {
|
||||
query: new Float32Array(128).fill(0.5),
|
||||
k: 10,
|
||||
}, {
|
||||
maxRuv: 5, // Max rUv to spend
|
||||
timeout: 30000, // 30s timeout
|
||||
priority: 'normal', // 'low' | 'normal' | 'high'
|
||||
});
|
||||
|
||||
console.log('Results:', result.results);
|
||||
console.log('Cost:', result.cost, 'rUv');
|
||||
}
|
||||
```
|
||||
|
||||
### Available Task Types
|
||||
|
||||
| Type | Description | Cost |
|
||||
|------|-------------|------|
|
||||
| `vector_search` | k-NN search in HNSW index | ~1 rUv / 1K vectors |
|
||||
| `vector_insert` | Add vectors to index | ~0.5 rUv / 100 vectors |
|
||||
| `embedding` | Generate text embeddings | ~5 rUv / 100 texts |
|
||||
| `semantic_match` | Task-to-agent routing | ~1 rUv / 10 queries |
|
||||
| `encryption` | AES encrypt/decrypt | ~0.1 rUv / MB |
|
||||
| `compression` | Adaptive quantization | ~0.2 rUv / MB |
|
||||
|
||||
## Framework Integration
|
||||
|
||||
### React
|
||||
|
||||
```jsx
|
||||
import { useEdgeNet, Badge } from '@ruvector/edge-net/react';
|
||||
|
||||
function App() {
|
||||
const { node, balance, multiplier, isConnected } = useEdgeNet({
|
||||
siteId: 'my-react-app',
|
||||
contribution: 0.3,
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>My App</h1>
|
||||
{isConnected && (
|
||||
<Badge balance={balance} multiplier={multiplier} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Vue 3
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<h1>My App</h1>
|
||||
<EdgeNetBadge v-if="isConnected" :node="node" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useEdgeNet, EdgeNetBadge } from '@ruvector/edge-net/vue';
|
||||
|
||||
const { node, isConnected } = useEdgeNet({
|
||||
siteId: 'my-vue-app',
|
||||
contribution: 0.3,
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### Next.js
|
||||
|
||||
```jsx
|
||||
// components/EdgeNetProvider.jsx
|
||||
'use client';
|
||||
|
||||
import { EdgeNetProvider } from '@ruvector/edge-net/react';
|
||||
|
||||
export default function Providers({ children }) {
|
||||
return (
|
||||
<EdgeNetProvider config={{ siteId: 'my-next-app', contribution: 0.3 }}>
|
||||
{children}
|
||||
</EdgeNetProvider>
|
||||
);
|
||||
}
|
||||
|
||||
// app/layout.jsx
|
||||
import Providers from '@/components/EdgeNetProvider';
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<Providers>{children}</Providers>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Self-Hosting the WASM Bundle
|
||||
|
||||
If you prefer to host the WASM files yourself:
|
||||
|
||||
### 1. Download the Package
|
||||
|
||||
```bash
|
||||
npm pack @ruvector/edge-net
|
||||
tar -xzf ruvector-edge-net-*.tgz
|
||||
cp -r package/dist/ ./public/edge-net/
|
||||
```
|
||||
|
||||
### 2. Configure Your Web Server
|
||||
|
||||
```nginx
|
||||
# nginx configuration
|
||||
location /edge-net/ {
|
||||
add_header Cross-Origin-Opener-Policy same-origin;
|
||||
add_header Cross-Origin-Embedder-Policy require-corp;
|
||||
|
||||
# WASM MIME type
|
||||
types {
|
||||
application/wasm wasm;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Use Local Path
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import { EdgeNet } from '/edge-net/edge-net.min.js';
|
||||
|
||||
const node = await EdgeNet.init({
|
||||
siteId: 'my-site',
|
||||
wasmPath: '/edge-net/', // Path to WASM files
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
## Option B: Self-Hosted Genesis Node
|
||||
|
||||
For full control, run your own genesis node:
|
||||
|
||||
### Using Docker
|
||||
|
||||
```bash
|
||||
# Pull the edge-net genesis image
|
||||
docker pull ruvector/edge-net-genesis:latest
|
||||
|
||||
# Run genesis node
|
||||
docker run -d \
|
||||
--name edge-net-genesis \
|
||||
-p 8080:8080 \
|
||||
-e NODE_ENV=production \
|
||||
-e GENESIS_KEYS_PATH=/keys/genesis.json \
|
||||
-v ./keys:/keys:ro \
|
||||
ruvector/edge-net-genesis:latest
|
||||
```
|
||||
|
||||
### Connect Browsers to Your Genesis
|
||||
|
||||
```javascript
|
||||
const node = await EdgeNet.init({
|
||||
siteId: 'my-site',
|
||||
network: {
|
||||
genesis: ['https://your-genesis.example.com'],
|
||||
relays: ['wss://your-relay.example.com'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
See [../gcloud/README.md](../gcloud/README.md) for Google Cloud Functions deployment.
|
||||
|
||||
## Privacy & Compliance
|
||||
|
||||
### GDPR Compliance
|
||||
|
||||
```javascript
|
||||
// Check for prior consent
|
||||
const hasConsent = localStorage.getItem('edge-net-consent') === 'true';
|
||||
|
||||
if (hasConsent) {
|
||||
const node = await EdgeNet.init({ siteId: 'my-site' });
|
||||
} else {
|
||||
// Show consent UI
|
||||
showConsentDialog();
|
||||
}
|
||||
|
||||
// Handle "forget me" requests
|
||||
async function handleForgetMe() {
|
||||
const node = await EdgeNet.getNode();
|
||||
if (node) {
|
||||
await node.deleteAllData();
|
||||
await node.disconnect();
|
||||
}
|
||||
localStorage.removeItem('edge-net-consent');
|
||||
}
|
||||
```
|
||||
|
||||
### Data Collected
|
||||
|
||||
| Data | Purpose | Retention |
|
||||
|------|---------|-----------|
|
||||
| Node ID | Identity | Until user clears |
|
||||
| Task results | Verification | 24 hours |
|
||||
| rUv balance | Economics | Permanent (on-chain) |
|
||||
| IP address | Rate limiting | Not stored |
|
||||
| Browser fingerprint | Sybil prevention | Hashed, 7 days |
|
||||
|
||||
### No Personal Data
|
||||
|
||||
Edge-net does NOT collect:
|
||||
- Names or emails
|
||||
- Browsing history
|
||||
- Cookie contents
|
||||
- Form inputs
|
||||
- Screen recordings
|
||||
|
||||
## Performance Impact
|
||||
|
||||
| Scenario | CPU Impact | Memory | Network |
|
||||
|----------|------------|--------|---------|
|
||||
| Idle (no tasks) | 0% | ~10MB | 0 |
|
||||
| Light tasks | 5-10% | ~50MB | ~1KB/s |
|
||||
| Active contribution | 10-30% | ~100MB | ~10KB/s |
|
||||
| Heavy workload | 30% (capped) | ~256MB | ~50KB/s |
|
||||
|
||||
### Optimization Tips
|
||||
|
||||
```javascript
|
||||
const node = await EdgeNet.init({
|
||||
siteId: 'my-site',
|
||||
|
||||
contribution: {
|
||||
cpuLimit: 0.2, // Lower CPU for sensitive sites
|
||||
memoryLimit: 128_000_000, // Lower memory footprint
|
||||
},
|
||||
|
||||
idle: {
|
||||
minIdleTime: 10000, // Wait longer before starting
|
||||
checkInterval: 5000, // Check less frequently
|
||||
},
|
||||
|
||||
// Pause during critical interactions
|
||||
pauseDuringInteraction: true,
|
||||
});
|
||||
|
||||
// Manually pause during important operations
|
||||
node.pause();
|
||||
await performCriticalOperation();
|
||||
node.resume();
|
||||
```
|
||||
|
||||
## Monitoring & Analytics
|
||||
|
||||
### Built-in Stats
|
||||
|
||||
```javascript
|
||||
const stats = node.getStats();
|
||||
console.log({
|
||||
uptime: stats.uptimeHours,
|
||||
tasksCompleted: stats.tasksCompleted,
|
||||
creditsEarned: stats.creditsEarned,
|
||||
reputation: stats.reputation,
|
||||
peers: stats.connectedPeers,
|
||||
});
|
||||
```
|
||||
|
||||
### Integration with Analytics
|
||||
|
||||
```javascript
|
||||
// Send to your analytics
|
||||
const node = await EdgeNet.init({
|
||||
siteId: 'my-site',
|
||||
onCredit: (earned, total) => {
|
||||
gtag('event', 'edge_net_credit', {
|
||||
earned,
|
||||
total,
|
||||
multiplier: node.getMultiplier(),
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**WASM fails to load**
|
||||
```
|
||||
Error: Failed to load WASM module
|
||||
```
|
||||
Solution: Ensure CORS headers allow WASM loading from CDN.
|
||||
|
||||
**SharedArrayBuffer not available**
|
||||
```
|
||||
Error: SharedArrayBuffer is not defined
|
||||
```
|
||||
Solution: Add required COOP/COEP headers:
|
||||
```
|
||||
Cross-Origin-Opener-Policy: same-origin
|
||||
Cross-Origin-Embedder-Policy: require-corp
|
||||
```
|
||||
|
||||
**WebWorkers blocked**
|
||||
```
|
||||
Error: Worker constructor blocked
|
||||
```
|
||||
Solution: Ensure your CSP allows worker-src.
|
||||
|
||||
### Debug Mode
|
||||
|
||||
```javascript
|
||||
const node = await EdgeNet.init({
|
||||
siteId: 'my-site',
|
||||
debug: true, // Enable verbose logging
|
||||
});
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
- Documentation: https://github.com/ruvnet/ruvector
|
||||
- Issues: https://github.com/ruvnet/ruvector/issues
|
||||
- Discord: https://discord.gg/ruvector
|
||||
324
vendor/ruvector/examples/edge-net/deploy/browser/embed-snippet.js
vendored
Normal file
324
vendor/ruvector/examples/edge-net/deploy/browser/embed-snippet.js
vendored
Normal file
@@ -0,0 +1,324 @@
|
||||
/**
|
||||
* Edge-Net Embed Snippet
|
||||
*
|
||||
* Minimal embed code for websites to include edge-net
|
||||
*
|
||||
* Usage:
|
||||
* <script src="https://cdn.jsdelivr.net/npm/@ruvector/edge-net/embed.min.js"
|
||||
* data-site-id="your-site-id"
|
||||
* data-cpu-limit="30"
|
||||
* data-show-badge="true">
|
||||
* </script>
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Get configuration from script tag
|
||||
const script = document.currentScript;
|
||||
const config = {
|
||||
siteId: script.getAttribute('data-site-id') || 'unknown',
|
||||
cpuLimit: parseFloat(script.getAttribute('data-cpu-limit') || '30') / 100,
|
||||
showBadge: script.getAttribute('data-show-badge') !== 'false',
|
||||
badgePosition: script.getAttribute('data-badge-position') || 'bottom-right',
|
||||
consentRequired: script.getAttribute('data-consent-required') !== 'false',
|
||||
debug: script.getAttribute('data-debug') === 'true',
|
||||
};
|
||||
|
||||
// CDN URLs
|
||||
const CDN_BASE = 'https://cdn.jsdelivr.net/npm/@ruvector/edge-net@latest';
|
||||
const WASM_URL = `${CDN_BASE}/dist/edge-net.wasm`;
|
||||
const JS_URL = `${CDN_BASE}/dist/edge-net.min.js`;
|
||||
|
||||
// Logger
|
||||
function log(...args) {
|
||||
if (config.debug) {
|
||||
console.log('[Edge-Net]', ...args);
|
||||
}
|
||||
}
|
||||
|
||||
// Storage keys
|
||||
const CONSENT_KEY = 'edge-net-consent';
|
||||
const NODE_KEY = 'edge-net-node';
|
||||
|
||||
// Check consent
|
||||
function hasConsent() {
|
||||
return localStorage.getItem(CONSENT_KEY) === 'true';
|
||||
}
|
||||
|
||||
// Show consent banner
|
||||
function showConsentBanner() {
|
||||
const banner = document.createElement('div');
|
||||
banner.id = 'edge-net-consent-banner';
|
||||
banner.innerHTML = `
|
||||
<style>
|
||||
#edge-net-consent-banner {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
color: white;
|
||||
padding: 1rem 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
z-index: 10000;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
#edge-net-consent-banner .content {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
#edge-net-consent-banner h4 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 1rem;
|
||||
color: #00d4ff;
|
||||
}
|
||||
#edge-net-consent-banner p {
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
color: #888;
|
||||
}
|
||||
#edge-net-consent-banner .buttons {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
#edge-net-consent-banner button {
|
||||
padding: 0.6rem 1.2rem;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
#edge-net-consent-banner button:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
#edge-net-consent-banner .accept {
|
||||
background: linear-gradient(90deg, #00d4ff, #7b2cbf);
|
||||
color: white;
|
||||
}
|
||||
#edge-net-consent-banner .decline {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: white;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
#edge-net-consent-banner .learn-more {
|
||||
color: #00d4ff;
|
||||
text-decoration: underline;
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
<div class="content">
|
||||
<h4>Help power AI features</h4>
|
||||
<p>Contribute idle compute to earn <strong>rUv</strong> (Resource Utility Vouchers). <button class="learn-more">Learn more</button></p>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<button class="decline">Not now</button>
|
||||
<button class="accept">Accept & Earn rUv</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(banner);
|
||||
|
||||
// Event handlers
|
||||
banner.querySelector('.accept').addEventListener('click', () => {
|
||||
localStorage.setItem(CONSENT_KEY, 'true');
|
||||
banner.remove();
|
||||
init();
|
||||
});
|
||||
|
||||
banner.querySelector('.decline').addEventListener('click', () => {
|
||||
localStorage.setItem(CONSENT_KEY, 'false');
|
||||
banner.remove();
|
||||
});
|
||||
|
||||
banner.querySelector('.learn-more').addEventListener('click', () => {
|
||||
window.open('https://github.com/ruvnet/ruvector/tree/main/examples/edge-net', '_blank');
|
||||
});
|
||||
}
|
||||
|
||||
// Create badge element
|
||||
function createBadge() {
|
||||
const badge = document.createElement('div');
|
||||
badge.id = 'edge-net-badge';
|
||||
|
||||
const positions = {
|
||||
'bottom-right': 'bottom: 20px; right: 20px;',
|
||||
'bottom-left': 'bottom: 20px; left: 20px;',
|
||||
'top-right': 'top: 20px; right: 20px;',
|
||||
'top-left': 'top: 20px; left: 20px;',
|
||||
};
|
||||
|
||||
badge.innerHTML = `
|
||||
<style>
|
||||
#edge-net-badge {
|
||||
position: fixed;
|
||||
${positions[config.badgePosition]}
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
color: white;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 12px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
font-size: 0.85rem;
|
||||
z-index: 9999;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
#edge-net-badge:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 24px rgba(0, 212, 255, 0.2);
|
||||
}
|
||||
#edge-net-badge.minimized {
|
||||
padding: 0.5rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
#edge-net-badge.minimized .details {
|
||||
display: none;
|
||||
}
|
||||
#edge-net-badge .status {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: #00ff88;
|
||||
animation: edge-net-pulse 2s infinite;
|
||||
}
|
||||
#edge-net-badge .status.paused {
|
||||
background: #ffaa00;
|
||||
animation: none;
|
||||
}
|
||||
#edge-net-badge .status.error {
|
||||
background: #ff6b6b;
|
||||
animation: none;
|
||||
}
|
||||
#edge-net-badge .balance {
|
||||
color: #00ff88;
|
||||
font-weight: bold;
|
||||
}
|
||||
#edge-net-badge .multiplier {
|
||||
color: #ff6b6b;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
@keyframes edge-net-pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
</style>
|
||||
<div class="status"></div>
|
||||
<div class="details">
|
||||
<span class="balance">0 rUv</span>
|
||||
<span class="multiplier">• 10.0x</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(badge);
|
||||
|
||||
// Toggle minimize on click
|
||||
badge.addEventListener('click', () => {
|
||||
badge.classList.toggle('minimized');
|
||||
});
|
||||
|
||||
return badge;
|
||||
}
|
||||
|
||||
// Update badge
|
||||
function updateBadge(badge, stats) {
|
||||
const balanceEl = badge.querySelector('.balance');
|
||||
const multiplierEl = badge.querySelector('.multiplier');
|
||||
const statusEl = badge.querySelector('.status');
|
||||
|
||||
if (balanceEl) balanceEl.textContent = `${stats.balance.toFixed(2)} rUv`;
|
||||
if (multiplierEl) multiplierEl.textContent = `• ${stats.multiplier.toFixed(1)}x`;
|
||||
|
||||
if (statusEl) {
|
||||
statusEl.classList.remove('paused', 'error');
|
||||
if (stats.paused) statusEl.classList.add('paused');
|
||||
if (stats.error) statusEl.classList.add('error');
|
||||
}
|
||||
}
|
||||
|
||||
// Load Edge-Net module
|
||||
async function loadModule() {
|
||||
log('Loading Edge-Net module...');
|
||||
|
||||
// Dynamic import from CDN
|
||||
const module = await import(JS_URL);
|
||||
return module.EdgeNet;
|
||||
}
|
||||
|
||||
// Initialize Edge-Net
|
||||
async function init() {
|
||||
try {
|
||||
log('Initializing with config:', config);
|
||||
|
||||
const EdgeNet = await loadModule();
|
||||
|
||||
const node = await EdgeNet.init({
|
||||
siteId: config.siteId,
|
||||
contribution: config.cpuLimit,
|
||||
wasmUrl: WASM_URL,
|
||||
onCredit: (earned, total) => {
|
||||
log(`Earned ${earned} QDAG, total: ${total}`);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('[Edge-Net] Error:', error);
|
||||
},
|
||||
});
|
||||
|
||||
// Create badge if enabled
|
||||
let badge = null;
|
||||
if (config.showBadge) {
|
||||
badge = createBadge();
|
||||
}
|
||||
|
||||
// Update loop
|
||||
setInterval(() => {
|
||||
const stats = node.getStats();
|
||||
if (badge) {
|
||||
updateBadge(badge, stats);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// Expose to window for debugging
|
||||
window.EdgeNetNode = node;
|
||||
|
||||
log('Edge-Net initialized successfully');
|
||||
|
||||
// Dispatch ready event
|
||||
window.dispatchEvent(new CustomEvent('edge-net-ready', { detail: { node } }));
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Edge-Net] Failed to initialize:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Entry point
|
||||
function main() {
|
||||
// Wait for DOM
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', main);
|
||||
return;
|
||||
}
|
||||
|
||||
log('Edge-Net embed script loaded');
|
||||
|
||||
// Check consent
|
||||
if (config.consentRequired && !hasConsent()) {
|
||||
showConsentBanner();
|
||||
} else if (hasConsent() || !config.consentRequired) {
|
||||
init();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
})();
|
||||
643
vendor/ruvector/examples/edge-net/deploy/browser/example.html
vendored
Normal file
643
vendor/ruvector/examples/edge-net/deploy/browser/example.html
vendored
Normal file
@@ -0,0 +1,643 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user