File size: 7,239 Bytes
af6cd33
83d8e9c
6451a6a
 
 
83d8e9c
6451a6a
 
 
a88f575
6fa91ec
1035089
 
6fa91ec
6451a6a
 
 
 
 
6fa91ec
6451a6a
 
 
 
6fa91ec
1035089
6fa91ec
 
a88f575
6451a6a
6fa91ec
 
83d8e9c
6451a6a
 
a88f575
6451a6a
 
 
6fa91ec
a88f575
 
6fa91ec
6451a6a
6fa91ec
6451a6a
6fa91ec
6451a6a
a88f575
 
 
6fa91ec
 
 
 
 
 
 
6451a6a
6fa91ec
 
 
 
 
a88f575
6fa91ec
 
6451a6a
 
1035089
a88f575
83d8e9c
a88f575
1035089
a88f575
f40de1a
6fa91ec
 
 
 
 
a88f575
6fa91ec
6451a6a
a88f575
f40de1a
6451a6a
 
f40de1a
e66e652
 
3417188
f40de1a
6fa91ec
6451a6a
3417188
6451a6a
3417188
 
 
 
 
 
 
e66e652
3417188
 
 
 
 
 
 
e66e652
 
6451a6a
e66e652
6451a6a
e66e652
 
 
6451a6a
6fa91ec
e66e652
 
 
 
 
 
 
3417188
 
e66e652
 
6fa91ec
 
3417188
 
 
 
6fa91ec
 
3417188
6451a6a
 
3417188
 
 
6451a6a
a88f575
f40de1a
6fa91ec
 
 
 
 
a88f575
6fa91ec
af6cd33
a88f575
f40de1a
 
 
d59a97d
 
 
a88f575
d59a97d
 
 
 
a88f575
f40de1a
 
a88f575
 
 
f40de1a
a88f575
 
 
f40de1a
 
a88f575
 
 
 
 
 
 
 
 
6451a6a
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import os
import sys
import uuid
import asyncio
import httpx
import traceback
from datetime import datetime, timezone
from typing import List, Optional

from fastapi import FastAPI, Request, APIRouter
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware

# ── CONFIGURATION & STATE ───────────────────────────────────────────────────

MONGO_URL = os.environ.get("MONGO_URL") or os.environ.get("MONGODB_URI")
_db = None
_inspections_col = None
_journal_col = None
_db_initialized = False

_mem_inspections = []
_mem_journal = []

# ── LAZY DB INITIALIZATION ──────────────────────────────────────────────────

async def get_db_collections():
    global _db, _inspections_col, _journal_col, _db_initialized
    if _db_initialized: return _inspections_col, _journal_col
    if not MONGO_URL:
        _db_initialized = True
        return None, None
    try:
        from motor.motor_asyncio import AsyncIOMotorClient
        import certifi
        client = AsyncIOMotorClient(MONGO_URL, serverSelectionTimeoutMS=2000, tlsCAFile=certifi.where(), tlsAllowInvalidCertificates=True)
        _db = client["forgesight"]
        _inspections_col = _db["inspections"]
        _journal_col = _db["journal"]
        _db_initialized = True
    except:
        _db_initialized = True
    return _inspections_col, _journal_col

# ── APP SETUP ───────────────────────────────────────────────────────────────

app = FastAPI(title="ForgeSight Backend")

# We handle routes with and without the /_/backend prefix to support all deployment styles
router = APIRouter()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

def get_agents():
    try:
        sys.path.append(os.path.dirname(__file__))
        import agents
        return agents
    except:
        import backend.agents as agents
        return agents

# ── API ENDPOINTS ───────────────────────────────────────────────────────────

@router.get("/health")
async def health():
    return {"status": "online", "db": "active" if _db_initialized else "initializing"}

@router.get("/inspections")
async def get_inspections(limit: int = 50):
    col, _ = await get_db_collections()
    if col is not None:
        try:
            cursor = col.find({}, {"_id": 0}).sort("timestamp", -1).limit(limit)
            return await cursor.to_list(length=limit)
        except: pass
    return sorted(_mem_inspections, key=lambda x: x.get("timestamp", ""), reverse=True)[:limit]

@router.post("/inspections")
async def create_inspection(request: Request):
    try:
        body = await request.json()
        image_base64 = body.get("image_base64")
        notes = body.get("notes", "")
        product_spec = body.get("product_spec", "")
        
        if not image_base64:
            return JSONResponse({"error": "image_base64 required"}, status_code=400)
        
        print(f"DEBUG: Processing inspection. Image length: {len(image_base64)}")
        
        try:
            agents = get_agents()
            print(f"DEBUG: Agents module loaded: {agents.__name__}")
        except Exception as e:
            print(f"DEBUG: Failed to load agents: {str(e)}")
            return JSONResponse({"error": f"Agent load failed: {str(e)}"}, status_code=500)

        # Run pipeline
        try:
            result = await agents.run_pipeline(image_base64, notes=notes, product_spec=product_spec)
            print(f"DEBUG: Pipeline completed. ID: {result.get('id')}")
        except Exception as e:
            tb = traceback.format_exc()
            print(f"DEBUG: Pipeline error:\n{tb}")
            return JSONResponse({"error": f"Pipeline execution failed: {str(e)}", "traceback": tb}, status_code=500)
        
        # Save to DB - ensure we include everything the frontend expects
        inspection_data = {
            **result,
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "image_url": f"data:image/jpeg;base64,{image_base64}" if "," not in image_base64 else image_base64,
            "notes": notes,
            "product_spec": product_spec
        }
        
        # Generate social post (using the reporter summary as the body)
        try:
            social = await agents.generate_social_post(
                inspection_data.get("headline", "New Inspection"),
                inspection_data.get("summary", "Complete analysis of project infrastructure.")
            )
            inspection_data["social"] = social
        except Exception as e:
            print(f"DEBUG: Social post generation failed: {str(e)}")
            inspection_data["social"] = {"x_post": "", "linkedin_post": ""}

        col, _ = await get_db_collections()
        if col is not None:
            try:
                await col.insert_one(inspection_data.copy())
            except Exception as e:
                print(f"DEBUG: MongoDB insert failed: {str(e)}")
        else:
            _mem_inspections.append(inspection_data)
            
        return inspection_data
    except Exception as e:
        tb = traceback.format_exc()
        print(f"DEBUG: Global inspection error:\n{tb}")
        return JSONResponse({"error": str(e), "traceback": tb}, status_code=500)

@router.get("/journal")
async def get_journal():
    _, j_col = await get_db_collections()
    if j_col is not None:
        try:
            cursor = j_col.find({}, {"_id": 0}).sort("created_at", -1).limit(50)
            return await cursor.to_list(length=50)
        except: pass
    return sorted(_mem_journal, key=lambda x: x.get("created_at", ""), reverse=True)[:50]

@router.get("/telemetry")
async def get_telemetry():
    import random
    return {
        "status": "Connected",
        "gpu_util_pct": random.randint(30, 95),
        "vram_used_gb": random.randint(110, 160),
        "vram_total_gb": 192,
        "temp_c": random.randint(45, 72),
        "tokens_per_sec": random.randint(1200, 3800),
        "power_watts": random.randint(250, 680),
        "device": "AMD Instinct MI300X",
        "persistence": "Active"
    }

@router.get("/metrics")
async def get_metrics():
    # Simple metrics for dashboard
    return {
        "avg_score": 88.5,
        "total_inspections": len(_mem_inspections),
        "status_distribution": {"PASS": 85, "FAIL": 15}
    }

@router.get("/blueprint")
async def get_blueprint():
    return {"architecture": "Agentic", "provider": "AMD"}

# Include router with multiple prefixes to handle Vercel's various routing modes
app.include_router(router, prefix="/api")
app.include_router(router, prefix="/_/backend/api")
app.include_router(router, prefix="")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=7860)