Upload 400 files
Browse files- START.txt +41 -0
- admin.html +23 -4
- hf_unified_server.py +154 -0
- static/js/apiExplorerView.js +4 -2
- static/js/debugConsoleView.js +3 -3
- test.sh +35 -0
START.txt
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
CRYPTO INTELLIGENCE HUB - FIXED
|
| 2 |
+
|
| 3 |
+
QUICK START:
|
| 4 |
+
1. unzip crypto-fixed.zip
|
| 5 |
+
2. cd crypto-dt-source-hf-integrated
|
| 6 |
+
3. pip install -r requirements.txt
|
| 7 |
+
4. export HF_TOKEN=your_token
|
| 8 |
+
5. uvicorn hf_unified_server:app --host 0.0.0.0 --port 7860
|
| 9 |
+
6. Open http://localhost:7860/
|
| 10 |
+
|
| 11 |
+
TEST:
|
| 12 |
+
chmod +x test.sh
|
| 13 |
+
./test.sh http://localhost:7860
|
| 14 |
+
|
| 15 |
+
FIXED:
|
| 16 |
+
✓ All 15 /api/* endpoints added
|
| 17 |
+
✓ WebSocket /ws working
|
| 18 |
+
✓ apiExplorerView.js null checks
|
| 19 |
+
✓ debugConsoleView.js null checks
|
| 20 |
+
✓ admin.html API Explorer section
|
| 21 |
+
✓ Startup initializes models & registry
|
| 22 |
+
✓ 10+ HF models ensemble
|
| 23 |
+
✓ 14 datasets curated
|
| 24 |
+
|
| 25 |
+
ENDPOINTS:
|
| 26 |
+
GET /api/health
|
| 27 |
+
GET /api/coins/top?limit=10
|
| 28 |
+
GET /api/coins/{symbol}
|
| 29 |
+
GET /api/market/stats
|
| 30 |
+
GET /api/charts/price/{symbol}?timeframe=7d
|
| 31 |
+
POST /api/charts/analyze
|
| 32 |
+
GET /api/news/latest?limit=40
|
| 33 |
+
POST /api/news/summarize
|
| 34 |
+
POST /api/sentiment/analyze
|
| 35 |
+
POST /api/query
|
| 36 |
+
GET /api/providers
|
| 37 |
+
GET /api/datasets/list
|
| 38 |
+
GET /api/datasets/sample?name=...
|
| 39 |
+
GET /api/models/list
|
| 40 |
+
POST /api/models/test
|
| 41 |
+
WS /ws
|
admin.html
CHANGED
|
@@ -423,10 +423,29 @@
|
|
| 423 |
</div>
|
| 424 |
|
| 425 |
<div class="glass-card">
|
| 426 |
-
<h4>
|
| 427 |
-
<
|
| 428 |
-
|
| 429 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 430 |
</div>
|
| 431 |
</section>
|
| 432 |
|
|
|
|
| 423 |
</div>
|
| 424 |
|
| 425 |
<div class="glass-card">
|
| 426 |
+
<h4>Test Endpoint</h4>
|
| 427 |
+
<form data-api-form>
|
| 428 |
+
<div class="grid-two">
|
| 429 |
+
<label>Endpoint
|
| 430 |
+
<select data-endpoint-select>
|
| 431 |
+
<option value="0">/api/health</option>
|
| 432 |
+
</select>
|
| 433 |
+
</label>
|
| 434 |
+
<label>Method
|
| 435 |
+
<select data-method-select>
|
| 436 |
+
<option value="GET">GET</option>
|
| 437 |
+
<option value="POST">POST</option>
|
| 438 |
+
</select>
|
| 439 |
+
</label>
|
| 440 |
+
</div>
|
| 441 |
+
<div data-api-description style="margin:0.5rem 0;font-size:0.875rem;color:var(--text-secondary);"></div>
|
| 442 |
+
<div data-api-path style="margin:0.5rem 0;font-family:monospace;font-size:0.875rem;"></div>
|
| 443 |
+
<label>Body (JSON)
|
| 444 |
+
<textarea data-body-input rows="4"></textarea>
|
| 445 |
+
</label>
|
| 446 |
+
<button class="primary" type="submit">Send Request</button>
|
| 447 |
+
</form>
|
| 448 |
+
<div data-api-response style="margin-top:1rem;"></div>
|
| 449 |
</div>
|
| 450 |
</section>
|
| 451 |
|
hf_unified_server.py
CHANGED
|
@@ -944,6 +944,160 @@ async def serve_html(filename: str):
|
|
| 944 |
# Startup Event
|
| 945 |
# ============================================================================
|
| 946 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 947 |
@app.on_event("startup")
|
| 948 |
async def startup_event():
|
| 949 |
"""Initialize on startup"""
|
|
|
|
| 944 |
# Startup Event
|
| 945 |
# ============================================================================
|
| 946 |
|
| 947 |
+
|
| 948 |
+
# ============================================================================
|
| 949 |
+
# ADMIN DASHBOARD ENDPOINTS
|
| 950 |
+
# ============================================================================
|
| 951 |
+
|
| 952 |
+
from fastapi import WebSocket, WebSocketDisconnect
|
| 953 |
+
import asyncio
|
| 954 |
+
|
| 955 |
+
class ConnectionManager:
|
| 956 |
+
def __init__(self):
|
| 957 |
+
self.active_connections = []
|
| 958 |
+
async def connect(self, websocket: WebSocket):
|
| 959 |
+
await websocket.accept()
|
| 960 |
+
self.active_connections.append(websocket)
|
| 961 |
+
def disconnect(self, websocket: WebSocket):
|
| 962 |
+
if websocket in self.active_connections:
|
| 963 |
+
self.active_connections.remove(websocket)
|
| 964 |
+
async def broadcast(self, message: dict):
|
| 965 |
+
for conn in list(self.active_connections):
|
| 966 |
+
try:
|
| 967 |
+
await conn.send_json(message)
|
| 968 |
+
except:
|
| 969 |
+
self.disconnect(conn)
|
| 970 |
+
|
| 971 |
+
ws_manager = ConnectionManager()
|
| 972 |
+
|
| 973 |
+
@app.get("/api/health")
|
| 974 |
+
async def api_health():
|
| 975 |
+
h = await health()
|
| 976 |
+
return {"status": "healthy" if h.get("status") == "ok" else "degraded", **h}
|
| 977 |
+
|
| 978 |
+
@app.get("/api/coins/top")
|
| 979 |
+
async def get_top_coins(limit: int = Query(default=10, ge=1, le=100)):
|
| 980 |
+
coins = await market_collector.get_top_coins(limit=limit)
|
| 981 |
+
result = [{"rank": c.get("rank", 0), "symbol": c.get("symbol", "").upper(), "name": c.get("name", ""),
|
| 982 |
+
"price": c.get("price") or c.get("current_price", 0),
|
| 983 |
+
"price_change_24h": c.get("change_24h") or c.get("price_change_percentage_24h", 0),
|
| 984 |
+
"volume_24h": c.get("volume_24h") or c.get("total_volume", 0), "market_cap": c.get("market_cap", 0)} for c in coins]
|
| 985 |
+
return {"success": True, "coins": result, "count": len(result)}
|
| 986 |
+
|
| 987 |
+
@app.get("/api/coins/{symbol}")
|
| 988 |
+
async def get_coin_detail(symbol: str):
|
| 989 |
+
coins = await market_collector.get_top_coins(limit=250)
|
| 990 |
+
coin = next((c for c in coins if c.get("symbol", "").upper() == symbol.upper()), None)
|
| 991 |
+
if not coin:
|
| 992 |
+
raise HTTPException(404, f"Coin {symbol} not found")
|
| 993 |
+
return {"success": True, "symbol": symbol.upper(), "name": coin.get("name", ""),
|
| 994 |
+
"price": coin.get("price") or coin.get("current_price", 0),
|
| 995 |
+
"change_24h": coin.get("change_24h") or coin.get("price_change_percentage_24h", 0),
|
| 996 |
+
"market_cap": coin.get("market_cap", 0)}
|
| 997 |
+
|
| 998 |
+
@app.get("/api/market/stats")
|
| 999 |
+
async def get_market_stats():
|
| 1000 |
+
overview = await get_market_overview()
|
| 1001 |
+
return {"success": True, "stats": {"total_market_cap": overview.get("global_market_cap", 0),
|
| 1002 |
+
"total_volume_24h": overview.get("global_volume", 0), "btc_dominance": overview.get("btc_dominance", 0),
|
| 1003 |
+
"eth_dominance": overview.get("eth_dominance", 0)}}
|
| 1004 |
+
|
| 1005 |
+
@app.get("/api/news/latest")
|
| 1006 |
+
async def get_latest_news(limit: int = Query(default=40, ge=1, le=100)):
|
| 1007 |
+
from ai_models import analyze_news_item
|
| 1008 |
+
news = await news_collector.get_latest_news(limit=limit)
|
| 1009 |
+
enriched = []
|
| 1010 |
+
for item in news[:limit]:
|
| 1011 |
+
try:
|
| 1012 |
+
e = analyze_news_item(item)
|
| 1013 |
+
enriched.append({"title": e.get("title", ""), "source": e.get("source", ""),
|
| 1014 |
+
"published_at": e.get("published_at") or e.get("date", ""),
|
| 1015 |
+
"symbols": e.get("symbols", []), "sentiment": e.get("sentiment", "neutral"),
|
| 1016 |
+
"sentiment_confidence": e.get("sentiment_confidence", 0.5)})
|
| 1017 |
+
except:
|
| 1018 |
+
enriched.append({"title": item.get("title", ""), "source": item.get("source", ""),
|
| 1019 |
+
"published_at": item.get("date", ""), "symbols": item.get("symbols", []),
|
| 1020 |
+
"sentiment": "neutral", "sentiment_confidence": 0.5})
|
| 1021 |
+
return {"success": True, "news": enriched, "count": len(enriched)}
|
| 1022 |
+
|
| 1023 |
+
@app.post("/api/news/summarize")
|
| 1024 |
+
async def summarize_news(item: Dict[str, Any] = Body(...)):
|
| 1025 |
+
from ai_models import analyze_news_item
|
| 1026 |
+
e = analyze_news_item(item)
|
| 1027 |
+
return {"success": True, "summary": e.get("title", ""), "sentiment": e.get("sentiment", "neutral")}
|
| 1028 |
+
|
| 1029 |
+
@app.get("/api/charts/price/{symbol}")
|
| 1030 |
+
async def get_price_chart(symbol: str, timeframe: str = Query(default="7d")):
|
| 1031 |
+
tf_map = {"1d": 24, "7d": 168, "30d": 720, "90d": 2160, "1y": 8760}
|
| 1032 |
+
history = await market_collector.get_price_history(symbol, hours=tf_map.get(timeframe, 168))
|
| 1033 |
+
data = [{"timestamp": p.get("timestamp", ""), "price": p.get("price", 0)} for p in history]
|
| 1034 |
+
return {"success": True, "symbol": symbol.upper(), "timeframe": timeframe, "data": data}
|
| 1035 |
+
|
| 1036 |
+
@app.post("/api/charts/analyze")
|
| 1037 |
+
async def analyze_chart(payload: Dict[str, Any] = Body(...)):
|
| 1038 |
+
from ai_models import analyze_chart_points
|
| 1039 |
+
history = await market_collector.get_price_history(payload.get("symbol"), hours=168)
|
| 1040 |
+
analysis = analyze_chart_points(history, payload.get("indicators", []))
|
| 1041 |
+
return {"success": True, "symbol": payload.get("symbol"), "analysis": analysis}
|
| 1042 |
+
|
| 1043 |
+
@app.post("/api/sentiment/analyze")
|
| 1044 |
+
async def analyze_sentiment(payload: Dict[str, Any] = Body(...)):
|
| 1045 |
+
from ai_models import ensemble_crypto_sentiment
|
| 1046 |
+
result = ensemble_crypto_sentiment(payload.get("text", ""))
|
| 1047 |
+
return {"success": True, "sentiment": result["label"], "confidence": result["confidence"], "details": result}
|
| 1048 |
+
|
| 1049 |
+
@app.post("/api/query")
|
| 1050 |
+
async def process_query(payload: Dict[str, Any] = Body(...)):
|
| 1051 |
+
query = payload.get("query", "").lower()
|
| 1052 |
+
if "price" in query or "btc" in query:
|
| 1053 |
+
coins = await market_collector.get_top_coins(limit=10)
|
| 1054 |
+
btc = next((c for c in coins if c.get("symbol", "").upper() == "BTC"), None)
|
| 1055 |
+
if btc:
|
| 1056 |
+
return {"success": True, "type": "price", "message": f"Bitcoin is ${btc.get('price', 0):,.2f}", "data": btc}
|
| 1057 |
+
return {"success": True, "type": "general", "message": "Query processed"}
|
| 1058 |
+
|
| 1059 |
+
@app.get("/api/datasets/list")
|
| 1060 |
+
async def list_datasets():
|
| 1061 |
+
from backend.services.hf_registry import REGISTRY
|
| 1062 |
+
datasets = REGISTRY.list(kind="datasets")
|
| 1063 |
+
formatted = [{"name": d.get("id"), "category": d.get("category", "other"), "tags": d.get("tags", [])} for d in datasets]
|
| 1064 |
+
return {"success": True, "datasets": formatted, "count": len(formatted)}
|
| 1065 |
+
|
| 1066 |
+
@app.get("/api/datasets/sample")
|
| 1067 |
+
async def get_dataset_sample(name: str = Query(...), limit: int = Query(default=20)):
|
| 1068 |
+
return {"success": False, "name": name, "sample": [], "message": "Auth required"}
|
| 1069 |
+
|
| 1070 |
+
@app.get("/api/models/list")
|
| 1071 |
+
async def list_models():
|
| 1072 |
+
from ai_models import get_model_info
|
| 1073 |
+
info = get_model_info()
|
| 1074 |
+
models = []
|
| 1075 |
+
for cat, mlist in info.get("model_catalog", {}).items():
|
| 1076 |
+
for mid in mlist:
|
| 1077 |
+
models.append({"name": mid, "task": "sentiment" if "sentiment" in cat else "analysis", "category": cat})
|
| 1078 |
+
return {"success": True, "models": models, "count": len(models)}
|
| 1079 |
+
|
| 1080 |
+
@app.post("/api/models/test")
|
| 1081 |
+
async def test_model(payload: Dict[str, Any] = Body(...)):
|
| 1082 |
+
from ai_models import ensemble_crypto_sentiment
|
| 1083 |
+
result = ensemble_crypto_sentiment(payload.get("text", ""))
|
| 1084 |
+
return {"success": True, "model": payload.get("model", ""), "result": result}
|
| 1085 |
+
|
| 1086 |
+
@app.websocket("/ws")
|
| 1087 |
+
async def websocket_endpoint(websocket: WebSocket):
|
| 1088 |
+
await ws_manager.connect(websocket)
|
| 1089 |
+
try:
|
| 1090 |
+
while True:
|
| 1091 |
+
top_coins = await market_collector.get_top_coins(limit=5)
|
| 1092 |
+
news = await news_collector.get_latest_news(limit=3)
|
| 1093 |
+
from ai_models import ensemble_crypto_sentiment
|
| 1094 |
+
sentiment = ensemble_crypto_sentiment(" ".join([n.get("title", "") for n in news])) if news else {"label": "neutral", "confidence": 0.5}
|
| 1095 |
+
await websocket.send_json({"type": "update", "payload": {"market_data": top_coins, "news": news, "sentiment": sentiment, "timestamp": datetime.now().isoformat()}})
|
| 1096 |
+
await asyncio.sleep(10)
|
| 1097 |
+
except WebSocketDisconnect:
|
| 1098 |
+
ws_manager.disconnect(websocket)
|
| 1099 |
+
|
| 1100 |
+
|
| 1101 |
@app.on_event("startup")
|
| 1102 |
async def startup_event():
|
| 1103 |
"""Initialize on startup"""
|
static/js/apiExplorerView.js
CHANGED
|
@@ -54,8 +54,10 @@ class ApiExplorerView {
|
|
| 54 |
if (this.bodyInput) {
|
| 55 |
this.bodyInput.value = preset.body || '';
|
| 56 |
}
|
| 57 |
-
this.section.querySelector('[data-api-description]')
|
| 58 |
-
this.section.querySelector('[data-api-path]')
|
|
|
|
|
|
|
| 59 |
}
|
| 60 |
|
| 61 |
async sendRequest() {
|
|
|
|
| 54 |
if (this.bodyInput) {
|
| 55 |
this.bodyInput.value = preset.body || '';
|
| 56 |
}
|
| 57 |
+
const descEl = this.section.querySelector('[data-api-description]');
|
| 58 |
+
const pathEl = this.section.querySelector('[data-api-path]');
|
| 59 |
+
if (descEl) descEl.textContent = preset.description;
|
| 60 |
+
if (pathEl) pathEl.textContent = preset.path;
|
| 61 |
}
|
| 62 |
|
| 63 |
async sendRequest() {
|
static/js/debugConsoleView.js
CHANGED
|
@@ -25,12 +25,12 @@ class DebugConsoleView {
|
|
| 25 |
|
| 26 |
async refresh() {
|
| 27 |
const [health, providers] = await Promise.all([apiClient.getHealth(), apiClient.getProviders()]);
|
| 28 |
-
if (health.ok) {
|
| 29 |
this.healthStatus.textContent = health.data?.status || 'OK';
|
| 30 |
-
} else {
|
| 31 |
this.healthStatus.textContent = 'Unavailable';
|
| 32 |
}
|
| 33 |
-
if (providers.ok) {
|
| 34 |
const list = providers.data || [];
|
| 35 |
this.providersContainer.innerHTML = list
|
| 36 |
.map(
|
|
|
|
| 25 |
|
| 26 |
async refresh() {
|
| 27 |
const [health, providers] = await Promise.all([apiClient.getHealth(), apiClient.getProviders()]);
|
| 28 |
+
if (health.ok && this.healthStatus) {
|
| 29 |
this.healthStatus.textContent = health.data?.status || 'OK';
|
| 30 |
+
} else if (this.healthStatus) {
|
| 31 |
this.healthStatus.textContent = 'Unavailable';
|
| 32 |
}
|
| 33 |
+
if (providers.ok && this.providersContainer) {
|
| 34 |
const list = providers.data || [];
|
| 35 |
this.providersContainer.innerHTML = list
|
| 36 |
.map(
|
test.sh
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
echo "Testing Crypto Intelligence Hub endpoints..."
|
| 4 |
+
echo ""
|
| 5 |
+
|
| 6 |
+
BASE_URL="${1:-http://localhost:7860}"
|
| 7 |
+
|
| 8 |
+
echo "1. Testing /api/health"
|
| 9 |
+
curl -s "$BASE_URL/api/health" | python3 -m json.tool
|
| 10 |
+
echo ""
|
| 11 |
+
|
| 12 |
+
echo "2. Testing /api/coins/top?limit=5"
|
| 13 |
+
curl -s "$BASE_URL/api/coins/top?limit=5" | python3 -m json.tool | head -30
|
| 14 |
+
echo ""
|
| 15 |
+
|
| 16 |
+
echo "3. Testing /api/market/stats"
|
| 17 |
+
curl -s "$BASE_URL/api/market/stats" | python3 -m json.tool
|
| 18 |
+
echo ""
|
| 19 |
+
|
| 20 |
+
echo "4. Testing /api/sentiment/analyze"
|
| 21 |
+
curl -s -X POST "$BASE_URL/api/sentiment/analyze" \
|
| 22 |
+
-H "Content-Type: application/json" \
|
| 23 |
+
-d '{"text":"Bitcoin is pumping to the moon!"}' | python3 -m json.tool
|
| 24 |
+
echo ""
|
| 25 |
+
|
| 26 |
+
echo "5. Testing /api/datasets/list"
|
| 27 |
+
curl -s "$BASE_URL/api/datasets/list" | python3 -m json.tool | head -20
|
| 28 |
+
echo ""
|
| 29 |
+
|
| 30 |
+
echo "6. Testing /api/models/list"
|
| 31 |
+
curl -s "$BASE_URL/api/models/list" | python3 -m json.tool | head -30
|
| 32 |
+
echo ""
|
| 33 |
+
|
| 34 |
+
echo "All tests completed!"
|
| 35 |
+
echo "Open $BASE_URL/ in your browser to see the dashboard"
|