Files
oib ff5486fe08 ```
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
2025-12-28 21:05:53 +01:00

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>