dboa9 commited on
Commit
c33dd22
·
1 Parent(s): 25c4c6d

Safe Moltbot v2.0 - brain only, no file access

Browse files
Files changed (2) hide show
  1. app.py +163 -43
  2. requirements.txt +4 -9
app.py CHANGED
@@ -1,57 +1,177 @@
 
 
 
 
 
1
  import os
 
2
  import json
3
- from fastapi import FastAPI, UploadFile, File, HTTPException, Header
 
 
4
 
5
- # 1. Initialize the App FIRST (Critical Fix)
6
- app = FastAPI(title="Moltbot Hybrid Engine")
 
 
 
 
7
 
8
- # 2. Set up API Key
9
  API_KEY = os.environ.get("MOLTBOT_API_KEY", "default_insecure_key")
10
  if API_KEY == "default_insecure_key":
11
- print("WARNING: MOLTBOT_API_KEY secret not set. Using insecure default key.")
12
 
13
- # 3. Health Check Endpoint (Keep server alive)
14
- @app.get("/")
15
- def read_root():
16
- return {"Status": "Running", "Message": "Moltbot Engine is Online"}
17
-
18
- # 4. Analysis Tool Endpoint
19
- @app.post("/tools/analyze_report")
20
- async def analyze_verification_report(
21
- report_file: UploadFile = File(...),
22
- x_api_key: str = Header(None)
23
- ):
24
- # Validation
25
- if not x_api_key or x_api_key != API_KEY:
26
- raise HTTPException(status_code=401, detail="Invalid or missing API Key")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
- if not report_file.filename.endswith('.json'):
29
- raise HTTPException(status_code=400, detail="Invalid file type. Please upload a JSON report.")
30
 
31
- try:
32
- content = await report_file.read()
33
- data = json.loads(content)
34
 
35
- # Logic: Extract issues
36
- missing_files = data.get("missing_total", 0)
37
- structure_issues = data.get("page_structure_analysis", {}).get("bundles_with_structure_issues", 0)
38
- suggestions = []
39
 
40
- if missing_files > 0:
41
- root_causes = data.get("root_cause_analysis", {}).get("failures_by_root_cause", {})
42
- for cause, details in root_causes.items():
43
- suggestions.append(f"Found {details.get('count', 0)} files with root cause '{cause}'. Fix: {details.get('fix_suggestion')}")
 
 
 
 
44
 
45
- if structure_issues > 0:
46
- suggestions.append(f"Found {structure_issues} bundles with structure problems.")
 
 
 
 
 
 
47
 
48
- return {
49
- "status": "success",
50
- "analysis": {
51
- "critical_issues": missing_files + structure_issues,
52
- "suggestions": suggestions
53
- }
54
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
- except Exception as e:
57
- raise HTTPException(status_code=500, detail=f"Failed to process report: {str(e)}")
 
 
1
+ """
2
+ Moltbot Hybrid Engine - SAFE DEPLOYMENT
3
+ Brain only - NO file access, NO local filesystem access
4
+ Runs on Hugging Face Spaces in isolated container
5
+ """
6
  import os
7
+ import re
8
  import json
9
+ from fastapi import FastAPI, HTTPException, Header
10
+ from pydantic import BaseModel
11
+ from typing import List, Optional, Dict, Any
12
 
13
+ # Initialize App
14
+ app = FastAPI(
15
+ title="Moltbot Hybrid Engine",
16
+ description="Safe AI agent for file matching - brain only, no file access",
17
+ version="2.0.0"
18
+ )
19
 
20
+ # API Key for authentication
21
  API_KEY = os.environ.get("MOLTBOT_API_KEY", "default_insecure_key")
22
  if API_KEY == "default_insecure_key":
23
+ print("WARNING: MOLTBOT_API_KEY not set. Using insecure default.")
24
 
25
+
26
+ # ============================================================
27
+ # DATA MODELS
28
+ # ============================================================
29
+
30
+ class FileSearchRequest(BaseModel):
31
+ missing_filename: str
32
+ available_files: List[str]
33
+ context: Optional[str] = None
34
+
35
+ class FileSearchResponse(BaseModel):
36
+ status: str
37
+ missing_filename: str
38
+ suggestions: List[Dict[str, Any]]
39
+ confidence: float
40
+ reasoning: str
41
+
42
+ class AnalysisRequest(BaseModel):
43
+ report_data: Dict[str, Any]
44
+
45
+ class AnalysisResponse(BaseModel):
46
+ status: str
47
+ critical_issues: int
48
+ suggestions: List[str]
49
+
50
+
51
+ # ============================================================
52
+ # HELPER FUNCTIONS (Pure logic, no file access)
53
+ # ============================================================
54
+
55
+ def tokenize(text: str) -> set:
56
+ clean = re.sub(r'[_\-\.\(\)\[\]]', ' ', text.lower())
57
+ tokens = set(clean.split())
58
+ junk = {'pdf', 'mp4', 'jpg', 'jpeg', 'png', 'gif', 'doc', 'docx',
59
+ 'the', 'and', 'to', 'of', 'in', 'a', 'for', 'with', 'on'}
60
+ return tokens - junk
61
+
62
+ def calculate_match_score(wanted: set, found: set) -> float:
63
+ if not wanted:
64
+ return 0.0
65
+ common = wanted.intersection(found)
66
+ return len(common) / len(wanted)
67
+
68
+ def find_best_matches(missing_filename: str, available_files: List[str], max_results: int = 5) -> List[Dict[str, Any]]:
69
+ wanted_tokens = tokenize(missing_filename)
70
+ if not wanted_tokens:
71
+ return []
72
+
73
+ matches = []
74
+ for filename in available_files:
75
+ if filename == missing_filename:
76
+ matches.append({"filename": filename, "score": 1.0, "match_type": "exact"})
77
+ continue
78
+ if filename.lower() == missing_filename.lower():
79
+ matches.append({"filename": filename, "score": 0.99, "match_type": "case_insensitive"})
80
+ continue
81
+ found_tokens = tokenize(filename)
82
+ score = calculate_match_score(wanted_tokens, found_tokens)
83
+ if score >= 0.5:
84
+ matches.append({"filename": filename, "score": round(score, 3), "match_type": "token_match"})
85
 
86
+ matches.sort(key=lambda x: x["score"], reverse=True)
87
+ return matches[:max_results]
88
 
 
 
 
89
 
90
+ # ============================================================
91
+ # API ENDPOINTS
92
+ # ============================================================
 
93
 
94
+ @app.get("/")
95
+ def health_check():
96
+ return {
97
+ "status": "running",
98
+ "service": "Moltbot Hybrid Engine",
99
+ "version": "2.0.0",
100
+ "mode": "SAFE - Brain only, no file access"
101
+ }
102
 
103
+ @app.get("/security")
104
+ def security_info():
105
+ return {
106
+ "file_access": False,
107
+ "network_access": "API only",
108
+ "isolation": "Hugging Face container",
109
+ "cannot_do": ["Read files", "Write files", "Delete files", "Access filesystem", "Execute commands"]
110
+ }
111
 
112
+ @app.post("/api/search", response_model=FileSearchResponse)
113
+ async def search_file(request: FileSearchRequest, x_api_key: str = Header(None)):
114
+ if not x_api_key or x_api_key != API_KEY:
115
+ raise HTTPException(status_code=401, detail="Invalid or missing API Key")
116
+
117
+ if len(request.missing_filename) > 200:
118
+ return FileSearchResponse(
119
+ status="error", missing_filename=request.missing_filename[:50] + "...",
120
+ suggestions=[], confidence=0.0,
121
+ reasoning="Filename too long - likely concatenated filenames"
122
+ )
123
+
124
+ matches = find_best_matches(request.missing_filename, request.available_files)
125
+ confidence = matches[0]["score"] if matches else 0.0
126
+
127
+ if not matches:
128
+ reasoning = f"No matches found in {len(request.available_files)} files"
129
+ elif matches[0]["match_type"] == "exact":
130
+ reasoning = f"Exact match: {matches[0]['filename']}"
131
+ else:
132
+ reasoning = f"Token match with {int(confidence * 100)}% similarity"
133
+
134
+ return FileSearchResponse(
135
+ status="success", missing_filename=request.missing_filename,
136
+ suggestions=matches, confidence=confidence, reasoning=reasoning
137
+ )
138
+
139
+ @app.post("/api/analyze", response_model=AnalysisResponse)
140
+ async def analyze_report(request: AnalysisRequest, x_api_key: str = Header(None)):
141
+ if not x_api_key or x_api_key != API_KEY:
142
+ raise HTTPException(status_code=401, detail="Invalid or missing API Key")
143
+
144
+ data = request.report_data
145
+ suggestions = []
146
+ missing_files = data.get("missing_total", 0)
147
+ structure_issues = data.get("page_structure_analysis", {}).get("bundles_with_structure_issues", 0)
148
+
149
+ if missing_files > 0:
150
+ suggestions.append(f"{missing_files} files missing - check paths")
151
+ if structure_issues > 0:
152
+ suggestions.append(f"{structure_issues} bundles have structure problems")
153
+
154
+ return AnalysisResponse(
155
+ status="success", critical_issues=missing_files + structure_issues, suggestions=suggestions
156
+ )
157
+
158
+ @app.post("/api/extract_date")
159
+ async def extract_date(filename: str, x_api_key: str = Header(None)):
160
+ if not x_api_key or x_api_key != API_KEY:
161
+ raise HTTPException(status_code=401, detail="Invalid or missing API Key")
162
+
163
+ patterns = [
164
+ (r'(\d{4})-(\d{2})-(\d{2})', 'ISO'),
165
+ (r'(\d{4})_(\d{2})_(\d{2})', 'underscore'),
166
+ (r'(\d{4})(\d{2})(\d{2})', 'compact'),
167
+ ]
168
+ for pattern, fmt in patterns:
169
+ match = re.search(pattern, filename)
170
+ if match:
171
+ year, month, day = match.groups()
172
+ return {"status": "found", "date": f"{year}-{int(month):02d}-{int(day):02d}"}
173
+ return {"status": "not_found", "date": None}
174
 
175
+ if __name__ == "__main__":
176
+ import uvicorn
177
+ uvicorn.run(app, host="0.0.0.0", port=7860)
requirements.txt CHANGED
@@ -1,9 +1,4 @@
1
- fastapi
2
- uvicorn
3
- python-multipart
4
- requests
5
- jira
6
- atlassian-python-api
7
- google-generativeai
8
- gitpython
9
- twilio
 
1
+ # Moltbot Hybrid Engine - Safe Deployment
2
+ fastapi>=0.104.0
3
+ uvicorn>=0.24.0
4
+ pydantic>=2.0.0