| import React, { useEffect, useState } from 'react'; |
| import { Num, AssetRow, PipelineTrace, Sparkline } from '../components/ui/index'; |
|
|
| interface Props { balance: { sol: number; usdt: number }; publicKey: string | null; onRefresh: () => void; onNavigate: (p: any) => void; } |
|
|
| export default function Dashboard({ balance, publicKey, onRefresh, onNavigate }: Props) { |
| const [txs, setTxs] = useState<any[]>([]); |
| const [aiStatus, setAiStatus] = useState<any>(null); |
|
|
| useEffect(() => { |
| if (!window.solvox) return; |
| Promise.all([window.solvox.wallet.getHistory(5), window.solvox.ai.getStatus()]) |
| .then(([h, s]) => { if (h.success) setTxs(h.history || []); setAiStatus(s); }).catch(() => {}); |
| }, []); |
|
|
| const total = balance.sol * 170 + balance.usdt; |
| const mods = aiStatus ? [ |
| { k: 'llm', n: 'LLM', p: '@qvac/llm-llamacpp' }, { k: 'transcription', n: 'Speech-to-Text', p: '@qvac/transcription-whispercpp' }, |
| { k: 'tts', n: 'Text-to-Speech', p: '@qvac/tts-onnx' }, { k: 'embed', n: 'Embeddings', p: '@qvac/embed-llamacpp' }, |
| { k: 'translation', n: 'Translation', p: '@qvac/translation-nmtcpp' }, { k: 'ocr', n: 'OCR', p: '@qvac/ocr-onnx' }, |
| ] : []; |
|
|
| return ( |
| <div className="max-w-content mx-auto px-8 py-section"> |
| {/* โโ Hero Band โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */} |
| <section className="bg-surface-dark rounded-xl p-xl mb-12 relative overflow-hidden"> |
| <div className="relative z-10"> |
| <div className="text-on-dark-soft text-body-sm mb-1">Total portfolio value</div> |
| <div className="text-on-dark display-text text-display-lg mb-2"><Num value={total} decimals={2} prefix="$" /></div> |
| <div className="flex items-center gap-3 mt-4"> |
| <button onClick={() => onNavigate('send')} className="btn-primary">Send</button> |
| <button onClick={() => onNavigate('voice')} className="btn-secondary-dark">Voice AI</button> |
| <button onClick={onRefresh} className="btn-outline-dark">Refresh</button> |
| </div> |
| </div> |
| {/* Floating product-UI card โ Coinbase signature */} |
| <div className="absolute right-8 top-6 w-[260px]"> |
| <div className="card-dark p-4 mb-2 shadow-soft"> |
| <div className="text-on-dark-soft text-caption mb-1">SOL Balance</div> |
| <div className="text-on-dark number-mono text-number-display"><Num value={balance.sol} decimals={4} /></div> |
| <div className="text-on-dark-soft text-caption mt-1">โ $<Num value={balance.sol * 170} decimals={2} /></div> |
| </div> |
| <div className="card-dark p-4 shadow-soft ml-4"> |
| <div className="text-on-dark-soft text-caption mb-1">USDT Balance</div> |
| <div className="text-on-dark number-mono text-number-display"><Num value={balance.usdt} decimals={2} /></div> |
| </div> |
| </div> |
| </section> |
| |
| {/* โโ Assets โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */} |
| <section className="mb-12"> |
| <div className="text-title-lg display-text text-ink mb-6">Assets</div> |
| <div className="card"> |
| <div className="asset-row px-4"> |
| <div className="asset-icon mr-3 text-sm font-bold text-ink">โ</div> |
| <div className="flex-1"><div className="text-title-sm text-ink">Solana</div><div className="text-caption text-muted">SOL</div></div> |
| <Sparkline data={[145, 152, 148, 160, 155, 170, 165, 172, 168, 170]} width={80} height={28} /> |
| <div className="text-right ml-4"> |
| <div className="number-mono text-number-display text-ink">${(balance.sol * 170).toLocaleString(undefined, { minimumFractionDigits: 2 })}</div> |
| <div className="number-mono text-caption text-semantic-up">+2.34%</div> |
| </div> |
| </div> |
| <div className="asset-row px-4"> |
| <div className="asset-icon mr-3 text-sm font-bold text-ink">โฎ</div> |
| <div className="flex-1"><div className="text-title-sm text-ink">Tether</div><div className="text-caption text-muted">USDT</div></div> |
| <Sparkline data={[1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00]} width={80} height={28} /> |
| <div className="text-right ml-4"> |
| <div className="number-mono text-number-display text-ink">${balance.usdt.toLocaleString(undefined, { minimumFractionDigits: 2 })}</div> |
| <div className="number-mono text-caption text-semantic-up">+0.01%</div> |
| </div> |
| </div> |
| </div> |
| </section> |
| |
| {/* โโ AI Engine + Transactions โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */} |
| <div className="grid grid-cols-5 gap-6"> |
| <section className="col-span-3"> |
| <div className="text-title-lg display-text text-ink mb-6">QVAC AI Engine</div> |
| <div className="card"> |
| {mods.map(m => ( |
| <div key={m.k} className="flex items-center justify-between py-3 border-b border-hairline-soft last:border-0"> |
| <div> |
| <div className="text-title-sm text-ink">{m.n}</div> |
| <div className="text-caption text-muted font-mono">{m.p}</div> |
| </div> |
| <span className={`badge-pill text-[10px] ${(aiStatus as any)?.[m.k] ? 'badge-pill-green' : ''}`}> |
| {(aiStatus as any)?.[m.k] ? 'ACTIVE' : 'LOADING'} |
| </span> |
| </div> |
| ))} |
| <div className="pt-3 mt-1 text-caption text-muted flex items-center gap-1.5"> |
| <div className="w-1.5 h-1.5 rounded-full bg-semantic-up" /> |
| All inference runs 100% locally via QVAC Fabric (Vulkan GPU) |
| </div> |
| </div> |
| </section> |
| |
| <section className="col-span-2"> |
| <div className="text-title-lg display-text text-ink mb-6">Recent activity</div> |
| <div className="card"> |
| {txs.length === 0 ? ( |
| <div className="text-center py-8"> |
| <div className="text-body text-muted">No transactions yet</div> |
| <div className="text-caption text-muted-soft mt-1">Try "Send 1 SOL toโฆ"</div> |
| </div> |
| ) : txs.map((tx, i) => ( |
| <div key={i} className="flex items-center gap-3 py-3 border-b border-hairline-soft last:border-0"> |
| <div className={`w-7 h-7 rounded-full flex items-center justify-center text-caption-strong ${tx.status === 'success' ? 'bg-semantic-up/10 text-semantic-up' : 'bg-semantic-down/10 text-semantic-down'}`}> |
| {tx.status === 'success' ? 'โ' : 'โ'} |
| </div> |
| <div className="flex-1 min-w-0"> |
| <div className="text-caption font-mono text-muted truncate">{tx.signature?.slice(0, 16)}โฆ</div> |
| <div className="text-caption text-muted-soft">{tx.timestamp ? new Date(tx.timestamp).toLocaleDateString() : 'โ'}</div> |
| </div> |
| <span className={`badge-pill text-[10px] ${tx.status === 'success' ? 'badge-pill-green' : 'badge-pill-red'}`}>{tx.status}</span> |
| </div> |
| ))} |
| </div> |
| </section> |
| </div> |
| |
| {/* โโ Wallet Address โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */} |
| <section className="mt-12"> |
| <div className="bg-surface-soft rounded-xl p-5 flex items-center justify-between"> |
| <div> |
| <div className="text-caption-strong text-muted uppercase tracking-wider">Wallet address</div> |
| <div className="text-body-sm font-mono text-muted mt-1">{publicKey || 'โ'}</div> |
| </div> |
| <button onClick={() => publicKey && navigator.clipboard.writeText(publicKey)} className="btn-secondary text-body-sm py-2 px-4">Copy</button> |
| </div> |
| </section> |
| </div> |
| ); |
| } |
|
|