chore: refactor logging module, update genesis timestamp, remove model relationships, and reorganize routers - Rename logging.py to logger.py and update import paths in poa.py and main.py - Update devnet genesis timestamp to 1766828620 - Remove SQLModel Relationship declarations from Block, Transaction, and Receipt models - Add SessionDep type alias and get_session dependency in coordinator-api deps - Reorganize coordinator-api routers: replace explorer/registry with exchange, users, marketplace
492 lines
23 KiB
HTML
492 lines
23 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>AITBC Marketplace - GPU Compute Trading</title>
|
|
<base href="/Marketplace/">
|
|
<link rel="stylesheet" href="/assets/css/aitbc.css">
|
|
<script src="/assets/js/axios.min.js"></script>
|
|
<script src="/assets/js/lucide.js"></script>
|
|
<style>
|
|
.gradient-bg {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
}
|
|
.card-hover {
|
|
transition: all 0.3s ease;
|
|
}
|
|
.card-hover:hover {
|
|
transform: translateY(-4px);
|
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="bg-gray-50 dark:bg-gray-900 transition-colors duration-300">
|
|
<!-- Header -->
|
|
<header class="gradient-bg text-white shadow-lg">
|
|
<div class="container mx-auto px-4 py-6">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center space-x-3">
|
|
<i data-lucide="cpu" class="w-8 h-8"></i>
|
|
<h1 class="text-2xl font-bold">AITBC Marketplace</h1>
|
|
</div>
|
|
<nav class="flex items-center space-x-6">
|
|
<button onclick="showSection('marketplace')" class="hover:text-purple-200 transition">Marketplace</button>
|
|
<button onclick="showSection('register')" class="hover:text-purple-200 transition">Register GPU</button>
|
|
<button onclick="showSection('my-bids')" class="hover:text-purple-200 transition">My Listings</button>
|
|
<button onclick="toggleDarkMode()" class="hover:text-purple-200 transition" title="Toggle dark mode">
|
|
<i data-lucide="moon" class="w-5 h-5" id="darkModeIcon"></i>
|
|
</button>
|
|
<button onclick="connectWallet()" class="bg-white text-purple-600 px-4 py-2 rounded-lg hover:bg-purple-100 transition">
|
|
<i data-lucide="wallet" class="w-4 h-4 inline mr-2"></i>Connect Wallet
|
|
</button>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Main Content -->
|
|
<main class="container mx-auto px-4 py-8">
|
|
<!-- Stats Section -->
|
|
<section class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-gray-500 dark:text-gray-400 text-sm">Active Bids</p>
|
|
<p class="text-2xl font-bold text-gray-900 dark:text-white" id="activeBids">0</p>
|
|
</div>
|
|
<i data-lucide="trending-up" class="w-8 h-8 text-purple-500"></i>
|
|
</div>
|
|
</div>
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-gray-500 text-sm">Total Capacity</p>
|
|
<p class="text-2xl font-bold" id="totalCapacity">0 GPUs</p>
|
|
</div>
|
|
<i data-lucide="server" class="w-8 h-8 text-blue-500"></i>
|
|
</div>
|
|
</div>
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-gray-500 text-sm">Avg Price</p>
|
|
<p class="text-2xl font-bold" id="avgPrice">$0.00</p>
|
|
</div>
|
|
<i data-lucide="dollar-sign" class="w-8 h-8 text-green-500"></i>
|
|
</div>
|
|
</div>
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-gray-500 text-sm">Your Balance</p>
|
|
<p class="text-2xl font-bold" id="walletBalance">0 AITBC</p>
|
|
</div>
|
|
<i data-lucide="coins" class="w-8 h-8 text-yellow-500"></i>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Marketplace Section -->
|
|
<section id="marketplaceSection" class="section">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h2 class="text-2xl font-bold">Available GPU Compute</h2>
|
|
<div class="flex space-x-4">
|
|
<select class="border rounded-lg px-4 py-2" id="sortSelect">
|
|
<option value="price">Sort by Price</option>
|
|
<option value="capacity">Sort by Capacity</option>
|
|
<option value="memory">Sort by Memory</option>
|
|
</select>
|
|
<button onclick="refreshMarketplace()" class="bg-purple-600 text-white px-4 py-2 rounded-lg hover:bg-purple-700 transition">
|
|
<i data-lucide="refresh-cw" class="w-4 h-4 inline mr-2"></i>Refresh
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="marketplaceList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<!-- GPU cards will be inserted here -->
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Register GPU Section -->
|
|
<section id="registerSection" class="section hidden">
|
|
<div class="max-w-2xl mx-auto">
|
|
<h2 class="text-2xl font-bold mb-6">Register Your GPU</h2>
|
|
<div class="bg-white rounded-lg shadow-lg p-8">
|
|
<form id="gpuRegisterForm" class="space-y-6">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">GPU Model</label>
|
|
<input type="text" id="gpuModel" class="w-full border rounded-lg px-4 py-2" placeholder="e.g., NVIDIA RTX 4060 Ti" required>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Memory (GB)</label>
|
|
<input type="number" id="gpuMemory" class="w-full border rounded-lg px-4 py-2" placeholder="16" required>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Price per Hour ($)</label>
|
|
<input type="number" id="gpuPrice" step="0.01" class="w-full border rounded-lg px-4 py-2" placeholder="0.50" required>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">CUDA Version</label>
|
|
<select id="cudaVersion" class="w-full border rounded-lg px-4 py-2">
|
|
<option value="11.8">CUDA 11.8</option>
|
|
<option value="12.0">CUDA 12.0</option>
|
|
<option value="12.1">CUDA 12.1</option>
|
|
<option value="12.2">CUDA 12.2</option>
|
|
<option value="12.3">CUDA 12.3</option>
|
|
<option value="12.4" selected>CUDA 12.4</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Supported Models</label>
|
|
<div class="space-y-2">
|
|
<label class="flex items-center">
|
|
<input type="checkbox" value="stable-diffusion" class="mr-2" checked>
|
|
<span>Stable Diffusion</span>
|
|
</label>
|
|
<label class="flex items-center">
|
|
<input type="checkbox" value="llama2-7b" class="mr-2" checked>
|
|
<span>LLaMA-2 7B</span>
|
|
</label>
|
|
<label class="flex items-center">
|
|
<input type="checkbox" value="llama2-13b" class="mr-2">
|
|
<span>LLaMA-2 13B</span>
|
|
</label>
|
|
<label class="flex items-center">
|
|
<input type="checkbox" value="whisper" class="mr-2" checked>
|
|
<span>Whisper</span>
|
|
</label>
|
|
<label class="flex items-center">
|
|
<input type="checkbox" value="clip" class="mr-2" checked>
|
|
<span>CLIP</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Additional Notes</label>
|
|
<textarea id="gpuNotes" rows="3" class="w-full border rounded-lg px-4 py-2" placeholder="Any additional information about your GPU setup..."></textarea>
|
|
</div>
|
|
|
|
<button type="submit" class="w-full bg-purple-600 text-white py-3 rounded-lg hover:bg-purple-700 transition font-semibold">
|
|
Register GPU
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- My Bids Section -->
|
|
<section id="myBidsSection" class="section hidden">
|
|
<h2 class="text-2xl font-bold mb-6">My GPU Listings</h2>
|
|
<div id="myBidsList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<!-- Your listings will appear here -->
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<!-- Toast Notification -->
|
|
<div id="toast" class="fixed bottom-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg transform translate-y-full transition-transform duration-300">
|
|
<span id="toastMessage"></span>
|
|
</div>
|
|
|
|
<script>
|
|
// API Configuration
|
|
const API_BASE = window.location.origin + '/api';
|
|
const BLOCKCHAIN_API = window.location.origin + '/rpc';
|
|
let walletAddress = null;
|
|
let connectedWallet = null;
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
lucide.createIcons();
|
|
loadMarketplaceStats();
|
|
loadMarketplaceBids();
|
|
|
|
// Form submission
|
|
document.getElementById('gpuRegisterForm').addEventListener('submit', registerGPU);
|
|
|
|
// Check for saved dark mode preference
|
|
if (localStorage.getItem('darkMode') === 'true' ||
|
|
(!localStorage.getItem('darkMode') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
|
document.documentElement.classList.add('dark');
|
|
updateDarkModeIcon(true);
|
|
}
|
|
});
|
|
|
|
// Dark mode toggle
|
|
function toggleDarkMode() {
|
|
const isDark = document.documentElement.classList.toggle('dark');
|
|
localStorage.setItem('darkMode', isDark);
|
|
updateDarkModeIcon(isDark);
|
|
}
|
|
|
|
function updateDarkModeIcon(isDark) {
|
|
const icon = document.getElementById('darkModeIcon');
|
|
if (isDark) {
|
|
icon.setAttribute('data-lucide', 'sun');
|
|
} else {
|
|
icon.setAttribute('data-lucide', 'moon');
|
|
}
|
|
lucide.createIcons();
|
|
}
|
|
|
|
// Section Navigation
|
|
function showSection(section) {
|
|
document.querySelectorAll('.section').forEach(s => s.classList.add('hidden'));
|
|
document.getElementById(section + 'Section').classList.remove('hidden');
|
|
|
|
if (section === 'my-bids') {
|
|
loadMyBids();
|
|
}
|
|
}
|
|
|
|
// Connect Wallet
|
|
async function connectWallet() {
|
|
// For demo, create a new wallet
|
|
const walletId = 'wallet-' + Math.random().toString(36).substr(2, 9);
|
|
const address = 'aitbc1' + walletId + 'x'.repeat(40 - walletId.length);
|
|
|
|
connectedWallet = {
|
|
id: walletId,
|
|
address: address,
|
|
publicKey: '0x' + Array(64).fill(0).map(() => Math.floor(Math.random() * 16).toString(16)).join('')
|
|
};
|
|
|
|
walletAddress = address;
|
|
showToast('Wallet connected: ' + address.substring(0, 20) + '...');
|
|
updateWalletBalance();
|
|
}
|
|
|
|
// Load Marketplace Stats
|
|
async function loadMarketplaceStats() {
|
|
try {
|
|
const response = await axios.get(`${API_BASE}/marketplace/stats`);
|
|
const stats = response.data;
|
|
document.getElementById('activeBids').textContent = stats.activeBids;
|
|
document.getElementById('totalCapacity').textContent = stats.openCapacity + ' GPUs';
|
|
document.getElementById('avgPrice').textContent = '$' + stats.averagePrice.toFixed(2);
|
|
} catch (error) {
|
|
console.error('Failed to load stats:', error);
|
|
}
|
|
}
|
|
|
|
// Load Marketplace Bids
|
|
async function loadMarketplaceBids() {
|
|
try {
|
|
const response = await axios.get(`${API_BASE}/marketplace/offers`);
|
|
const bids = response.data;
|
|
displayMarketplaceBids(bids);
|
|
} catch (error) {
|
|
console.error('Failed to load bids:', error);
|
|
// Display demo data if API fails
|
|
displayDemoBids();
|
|
}
|
|
}
|
|
|
|
// Display Marketplace Bids
|
|
function displayMarketplaceBids(bids) {
|
|
const container = document.getElementById('marketplaceList');
|
|
|
|
if (bids.length === 0) {
|
|
container.innerHTML = '<div class="col-span-full text-center py-12 text-gray-500">No GPU offers available at the moment.</div>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = bids.map(bid => `
|
|
<div class="bg-white rounded-lg shadow-lg p-6 card-hover">
|
|
<div class="flex justify-between items-start mb-4">
|
|
<h3 class="text-lg font-semibold">${bid.provider}</h3>
|
|
<span class="bg-green-100 text-green-800 px-2 py-1 rounded text-sm">Available</span>
|
|
</div>
|
|
<div class="space-y-2 text-sm text-gray-600 mb-4">
|
|
<p><i data-lucide="monitor" class="w-4 h-4 inline mr-1"></i>GPU: ${bid.gpu_model || 'Not specified'}</p>
|
|
<p><i data-lucide="hard-drive" class="w-4 h-4 inline mr-1"></i>Memory: ${bid.gpu_memory_gb || 'N/A'} GB</p>
|
|
<p><i data-lucide="clock" class="w-4 h-4 inline mr-1"></i>Capacity: ${bid.capacity || 1} GPU(s)</p>
|
|
</div>
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-2xl font-bold text-purple-600">$${bid.price || '0.50'}/hr</span>
|
|
<button onclick="purchaseGPU('${bid.id}')" class="bg-purple-600 text-white px-4 py-2 rounded hover:bg-purple-700 transition">
|
|
Purchase
|
|
</button>
|
|
</div>
|
|
${bid.notes ? `<p class="mt-4 text-sm text-gray-500">${bid.notes}</p>` : ''}
|
|
</div>
|
|
`).join('');
|
|
|
|
lucide.createIcons();
|
|
}
|
|
|
|
// Display Demo Bids (for testing)
|
|
function displayDemoBids() {
|
|
const demoBids = [
|
|
{
|
|
id: 'demo1',
|
|
provider: 'miner_dev_key_1',
|
|
gpu_model: 'NVIDIA RTX 4060 Ti',
|
|
gpu_memory_gb: 16,
|
|
capacity: 1,
|
|
price: 0.50,
|
|
notes: 'NVIDIA RTX 4060 Ti 16GB - Available for AI workloads'
|
|
}
|
|
];
|
|
displayMarketplaceBids(demoBids);
|
|
}
|
|
|
|
// Register GPU
|
|
async function registerGPU(e) {
|
|
e.preventDefault();
|
|
|
|
const gpuModel = document.getElementById('gpuModel').value;
|
|
const gpuMemory = document.getElementById('gpuMemory').value;
|
|
const gpuPrice = document.getElementById('gpuPrice').value;
|
|
const cudaVersion = document.getElementById('cudaVersion').value;
|
|
const gpuNotes = document.getElementById('gpuNotes').value;
|
|
|
|
const supportedModels = [];
|
|
document.querySelectorAll('input[type="checkbox"]:checked').forEach(cb => {
|
|
supportedModels.push(cb.value);
|
|
});
|
|
|
|
try {
|
|
// First register as miner
|
|
const minerResponse = await axios.post(`${API_BASE}/miners/register`, {
|
|
capabilities: {
|
|
gpu: gpuModel,
|
|
gpu_memory_gb: parseInt(gpuMemory),
|
|
cuda_version: cudaVersion,
|
|
supported_models: supportedModels,
|
|
region: 'local',
|
|
pricing_per_hour: parseFloat(gpuPrice)
|
|
}
|
|
}, {
|
|
headers: { 'X-Api-Key': 'miner_dev_key_1' }
|
|
});
|
|
|
|
// Then create marketplace bid
|
|
const bidResponse = await axios.post(`${API_BASE}/marketplace/bids`, {
|
|
provider: 'miner_dev_key_1',
|
|
capacity: 1,
|
|
price: parseFloat(gpuPrice),
|
|
notes: `${gpuModel} ${gpuMemory}GB - ${supportedModels.join(', ')}${gpuNotes ? '. ' + gpuNotes : ''}`
|
|
}, {
|
|
headers: { 'X-Api-Key': 'client_dev_key_1' }
|
|
});
|
|
|
|
showToast('GPU registered successfully!');
|
|
document.getElementById('gpuRegisterForm').reset();
|
|
loadMarketplaceStats();
|
|
loadMarketplaceBids();
|
|
|
|
} catch (error) {
|
|
console.error('Registration failed:', error);
|
|
showToast('Registration failed. Please try again.', 'error');
|
|
}
|
|
}
|
|
|
|
// Purchase GPU
|
|
async function purchaseGPU(bidId) {
|
|
if (!walletAddress) {
|
|
showToast('Please connect your wallet first', 'error');
|
|
return;
|
|
}
|
|
|
|
// Create job for GPU purchase
|
|
try {
|
|
const response = await axios.post(`${API_BASE}/jobs`, {
|
|
job_type: 'inference',
|
|
model: 'stable-diffusion',
|
|
requirements: {
|
|
gpu_memory_min_gb: 8,
|
|
cuda_version_min: '11.0'
|
|
},
|
|
pricing: {
|
|
max_price_per_hour: 1.0,
|
|
duration_hours: 1
|
|
}
|
|
}, {
|
|
headers: { 'X-Api-Key': 'client_dev_key_1' }
|
|
});
|
|
|
|
showToast('GPU time purchased successfully!');
|
|
updateWalletBalance();
|
|
|
|
} catch (error) {
|
|
console.error('Purchase failed:', error);
|
|
showToast('Purchase failed. Please try again.', 'error');
|
|
}
|
|
}
|
|
|
|
// Load My Bids
|
|
function loadMyBids() {
|
|
const myBidsList = document.getElementById('myBidsList');
|
|
|
|
// For demo, show the registered GPU
|
|
myBidsList.innerHTML = `
|
|
<div class="bg-white rounded-lg shadow-lg p-6">
|
|
<div class="flex justify-between items-start mb-4">
|
|
<h3 class="text-lg font-semibold">NVIDIA RTX 4060 Ti</h3>
|
|
<span class="bg-green-100 text-green-800 px-2 py-1 rounded text-sm">Active</span>
|
|
</div>
|
|
<div class="space-y-2 text-sm text-gray-600 mb-4">
|
|
<p><i data-lucide="monitor" class="w-4 h-4 inline mr-1"></i>Memory: 16 GB</p>
|
|
<p><i data-lucide="clock" class="w-4 h-4 inline mr-1"></i>Price: $0.50/hr</p>
|
|
<p><i data-lucide="activity" class="w-4 h-4 inline mr-1"></i>Status: Available</p>
|
|
</div>
|
|
<div class="flex space-x-2">
|
|
<button class="flex-1 bg-blue-600 text-white px-3 py-2 rounded hover:bg-blue-700 transition text-sm">
|
|
Edit
|
|
</button>
|
|
<button class="flex-1 bg-red-600 text-white px-3 py-2 rounded hover:bg-red-700 transition text-sm">
|
|
Remove
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
lucide.createIcons();
|
|
}
|
|
|
|
// Update Wallet Balance
|
|
async function updateWalletBalance() {
|
|
if (!walletAddress) return;
|
|
|
|
try {
|
|
const response = await axios.get(`${BLOCKCHAIN_API}/getBalance/${walletAddress}`);
|
|
document.getElementById('walletBalance').textContent = response.data.balance + ' AITBC';
|
|
} catch (error) {
|
|
document.getElementById('walletBalance').textContent = '1000 AITBC'; // Demo balance
|
|
}
|
|
}
|
|
|
|
// Refresh Marketplace
|
|
function refreshMarketplace() {
|
|
loadMarketplaceStats();
|
|
loadMarketplaceBids();
|
|
showToast('Marketplace refreshed');
|
|
}
|
|
|
|
// Toast Notification
|
|
function showToast(message, type = 'success') {
|
|
const toast = document.getElementById('toast');
|
|
const toastMessage = document.getElementById('toastMessage');
|
|
|
|
toastMessage.textContent = message;
|
|
toast.className = `fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg transform transition-transform duration-300 ${
|
|
type === 'error' ? 'bg-red-500' : 'bg-green-500'
|
|
} text-white`;
|
|
|
|
toast.style.transform = 'translateY(0)';
|
|
|
|
setTimeout(() => {
|
|
toast.style.transform = 'translateY(100%)';
|
|
}, 3000);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|