chore: remove configuration files and enhance blockchain explorer with advanced search, analytics, and export features
- Delete .aitbc.yaml.example CLI configuration template - Delete .lycheeignore link checker exclusion rules - Delete .nvmrc Node.js version specification - Add advanced search panel with filters for address, amount range, transaction type, time range, and validator - Add analytics dashboard with transaction volume, active addresses, and block time metrics - Add Chart.js integration
This commit is contained in:
328
cli/examples/client_enhanced.py
Normal file
328
cli/examples/client_enhanced.py
Normal file
@@ -0,0 +1,328 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
AITBC Client CLI Tool - Enhanced version with output formatting
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import httpx
|
||||
import json
|
||||
import sys
|
||||
import yaml
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, Any
|
||||
from tabulate import tabulate
|
||||
|
||||
# Configuration
|
||||
DEFAULT_COORDINATOR = "http://127.0.0.1:18000"
|
||||
DEFAULT_API_KEY = "${CLIENT_API_KEY}"
|
||||
|
||||
|
||||
class OutputFormatter:
|
||||
"""Handle different output formats"""
|
||||
|
||||
@staticmethod
|
||||
def format(data: Any, format_type: str = "table") -> str:
|
||||
"""Format data according to specified type"""
|
||||
if format_type == "json":
|
||||
return json.dumps(data, indent=2, default=str)
|
||||
elif format_type == "yaml":
|
||||
return yaml.dump(data, default_flow_style=False, sort_keys=False)
|
||||
elif format_type == "table":
|
||||
return OutputFormatter._format_table(data)
|
||||
else:
|
||||
return str(data)
|
||||
|
||||
@staticmethod
|
||||
def _format_table(data: Any) -> str:
|
||||
"""Format data as table"""
|
||||
if isinstance(data, dict):
|
||||
# Simple key-value table
|
||||
rows = [[k, v] for k, v in data.items()]
|
||||
return tabulate(rows, headers=["Key", "Value"], tablefmt="grid")
|
||||
elif isinstance(data, list) and data:
|
||||
if all(isinstance(item, dict) for item in data):
|
||||
# Table from list of dicts
|
||||
headers = list(data[0].keys())
|
||||
rows = [[item.get(h, "") for h in headers] for item in data]
|
||||
return tabulate(rows, headers=headers, tablefmt="grid")
|
||||
else:
|
||||
# Simple list
|
||||
return "\n".join(f"• {item}" for item in data)
|
||||
else:
|
||||
return str(data)
|
||||
|
||||
|
||||
class AITBCClient:
|
||||
def __init__(self, coordinator_url: str, api_key: str):
|
||||
self.coordinator_url = coordinator_url
|
||||
self.api_key = api_key
|
||||
self.client = httpx.Client()
|
||||
|
||||
def submit_job(self, job_type: str, task_data: dict, ttl: int = 900) -> Optional[str]:
|
||||
"""Submit a job to the coordinator"""
|
||||
job_payload = {
|
||||
"payload": {
|
||||
"type": job_type,
|
||||
**task_data
|
||||
},
|
||||
"ttl_seconds": ttl
|
||||
}
|
||||
|
||||
try:
|
||||
response = self.client.post(
|
||||
f"{self.coordinator_url}/v1/jobs",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-Api-Key": self.api_key
|
||||
},
|
||||
json=job_payload
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
job = response.json()
|
||||
return job['job_id']
|
||||
else:
|
||||
print(f"❌ Error submitting job: {response.status_code}")
|
||||
print(f" Response: {response.text}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return None
|
||||
|
||||
def get_job_status(self, job_id: str) -> Optional[Dict]:
|
||||
"""Get job status"""
|
||||
try:
|
||||
response = self.client.get(
|
||||
f"{self.coordinator_url}/v1/jobs/{job_id}",
|
||||
headers={"X-Api-Key": self.api_key}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
print(f"❌ Error getting status: {response.status_code}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return None
|
||||
|
||||
def list_blocks(self, limit: int = 10) -> Optional[list]:
|
||||
"""List recent blocks"""
|
||||
try:
|
||||
response = self.client.get(
|
||||
f"{self.coordinator_url}/v1/explorer/blocks",
|
||||
params={"limit": limit},
|
||||
headers={"X-Api-Key": self.api_key}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
print(f"❌ Error getting blocks: {response.status_code}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return None
|
||||
|
||||
def list_transactions(self, limit: int = 10) -> Optional[list]:
|
||||
"""List recent transactions"""
|
||||
try:
|
||||
response = self.client.get(
|
||||
f"{self.coordinator_url}/v1/explorer/transactions",
|
||||
params={"limit": limit},
|
||||
headers={"X-Api-Key": self.api_key}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
print(f"❌ Error getting transactions: {response.status_code}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return None
|
||||
|
||||
def list_receipts(self, limit: int = 10, job_id: str = None) -> Optional[list]:
|
||||
"""List job receipts"""
|
||||
try:
|
||||
params = {"limit": limit}
|
||||
if job_id:
|
||||
params["job_id"] = job_id
|
||||
|
||||
response = self.client.get(
|
||||
f"{self.coordinator_url}/v1/explorer/receipts",
|
||||
params=params,
|
||||
headers={"X-Api-Key": self.api_key}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
print(f"❌ Error getting receipts: {response.status_code}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return None
|
||||
|
||||
def cancel_job(self, job_id: str) -> bool:
|
||||
"""Cancel a job"""
|
||||
try:
|
||||
response = self.client.post(
|
||||
f"{self.coordinator_url}/v1/jobs/{job_id}/cancel",
|
||||
headers={"X-Api-Key": self.api_key}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return True
|
||||
else:
|
||||
print(f"❌ Error cancelling job: {response.status_code}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="AITBC Client CLI Tool")
|
||||
parser.add_argument("--url", default=DEFAULT_COORDINATOR, help="Coordinator URL")
|
||||
parser.add_argument("--api-key", default=DEFAULT_API_KEY, help="API key")
|
||||
parser.add_argument("--output", choices=["table", "json", "yaml"],
|
||||
default="table", help="Output format")
|
||||
|
||||
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
||||
|
||||
# Submit command
|
||||
submit_parser = subparsers.add_parser("submit", help="Submit a job")
|
||||
submit_parser.add_argument("type", help="Job type (e.g., inference, training)")
|
||||
submit_parser.add_argument("--prompt", help="Prompt for inference jobs")
|
||||
submit_parser.add_argument("--model", help="Model name")
|
||||
submit_parser.add_argument("--ttl", type=int, default=900, help="Time to live (seconds)")
|
||||
submit_parser.add_argument("--file", type=argparse.FileType('r'),
|
||||
help="Submit job from JSON file")
|
||||
|
||||
# Status command
|
||||
status_parser = subparsers.add_parser("status", help="Check job status")
|
||||
status_parser.add_argument("job_id", help="Job ID")
|
||||
|
||||
# Blocks command
|
||||
blocks_parser = subparsers.add_parser("blocks", help="List recent blocks")
|
||||
blocks_parser.add_argument("--limit", type=int, default=10, help="Number of blocks")
|
||||
|
||||
# Browser command
|
||||
browser_parser = subparsers.add_parser("browser", help="Browse blockchain")
|
||||
browser_parser.add_argument("--block-limit", type=int, default=5, help="Block limit")
|
||||
browser_parser.add_argument("--tx-limit", type=int, default=10, help="Transaction limit")
|
||||
browser_parser.add_argument("--receipt-limit", type=int, default=10, help="Receipt limit")
|
||||
browser_parser.add_argument("--job-id", help="Filter by job ID")
|
||||
|
||||
# Cancel command
|
||||
cancel_parser = subparsers.add_parser("cancel", help="Cancel a job")
|
||||
cancel_parser.add_argument("job_id", help="Job ID")
|
||||
|
||||
# Receipts command
|
||||
receipts_parser = subparsers.add_parser("receipts", help="List receipts")
|
||||
receipts_parser.add_argument("--limit", type=int, default=10, help="Number of receipts")
|
||||
receipts_parser.add_argument("--job-id", help="Filter by job ID")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.command:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
# Create client
|
||||
client = AITBCClient(args.url, args.api_key)
|
||||
|
||||
# Execute command
|
||||
if args.command == "submit":
|
||||
# Build job data
|
||||
if args.file:
|
||||
try:
|
||||
task_data = json.load(args.file)
|
||||
except Exception as e:
|
||||
print(f"❌ Error reading job file: {e}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
task_data = {"type": args.type}
|
||||
if args.prompt:
|
||||
task_data["prompt"] = args.prompt
|
||||
if args.model:
|
||||
task_data["model"] = args.model
|
||||
|
||||
# Submit job
|
||||
job_id = client.submit_job(args.type, task_data, args.ttl)
|
||||
|
||||
if job_id:
|
||||
result = {
|
||||
"status": "success",
|
||||
"job_id": job_id,
|
||||
"message": "Job submitted successfully",
|
||||
"track_command": f"python3 cli/client_enhanced.py status {job_id}"
|
||||
}
|
||||
print(OutputFormatter.format(result, args.output))
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
elif args.command == "status":
|
||||
status = client.get_job_status(args.job_id)
|
||||
|
||||
if status:
|
||||
print(OutputFormatter.format(status, args.output))
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
elif args.command == "blocks":
|
||||
blocks = client.list_blocks(args.limit)
|
||||
|
||||
if blocks:
|
||||
print(OutputFormatter.format(blocks, args.output))
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
elif args.command == "browser":
|
||||
blocks = client.list_blocks(args.block_limit) or []
|
||||
transactions = client.list_transactions(args.tx_limit) or []
|
||||
receipts = client.list_receipts(args.receipt_limit, job_id=args.job_id) or []
|
||||
|
||||
result = {
|
||||
"latest_block": blocks[0] if blocks else None,
|
||||
"recent_transactions": transactions,
|
||||
"recent_receipts": receipts
|
||||
}
|
||||
|
||||
print(OutputFormatter.format(result, args.output))
|
||||
sys.exit(0)
|
||||
|
||||
elif args.command == "cancel":
|
||||
if client.cancel_job(args.job_id):
|
||||
result = {
|
||||
"status": "success",
|
||||
"job_id": args.job_id,
|
||||
"message": "Job cancelled successfully"
|
||||
}
|
||||
print(OutputFormatter.format(result, args.output))
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
elif args.command == "receipts":
|
||||
receipts = client.list_receipts(args.limit, args.job_id)
|
||||
|
||||
if receipts:
|
||||
print(OutputFormatter.format(receipts, args.output))
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user