Spaces:
Running
feat: Implement comprehensive cache management and codebase optimization
Browse files- Add dynamic service worker versioning with HF Spaces detection
- Implement automated cache busting for all static files
- Add version management API endpoints (/api/version, /api/version/update)
- Create HF Spaces optimized cache manager (hf_cache_manager.py)
- Add comprehensive project documentation (llm.txt)
- Remove all emojis from codebase for production readiness
- Optimize Python imports across all modules
- Add proper .gitignore for Python/FastAPI projects
- Update HTML files with timestamp-based cache busting
- Enhance service worker with development mode detection
- Create cache management utilities and reference documentation
This resolves caching issues permanently by:
1. Detecting HF Spaces environment automatically
2. Using network-first strategy in development
3. Implementing proper cache invalidation
4. Providing multiple cache clearing methods
All files are now production-ready and emoji-free.
- .gitignore +74 -0
- app.py +77 -1
- clear_cache.bat +17 -0
- clear_cache.py +213 -0
- config.py +3 -2
- hf_cache_manager.py +158 -0
- llm.txt +339 -0
- static/app.js +12 -12
- static/index.html +2 -2
- static/map.js +2 -2
- static/sw.js +47 -9
- update_version.py +86 -0
- version.json +9 -0
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Python
|
2 |
+
__pycache__/
|
3 |
+
*.pyc
|
4 |
+
*.pyo
|
5 |
+
*.pyd
|
6 |
+
.Python
|
7 |
+
*.so
|
8 |
+
*.egg
|
9 |
+
*.egg-info/
|
10 |
+
dist/
|
11 |
+
build/
|
12 |
+
.venv/
|
13 |
+
venv/
|
14 |
+
env/
|
15 |
+
|
16 |
+
# FastAPI/Uvicorn
|
17 |
+
*.log
|
18 |
+
app.log
|
19 |
+
|
20 |
+
# Database
|
21 |
+
data/
|
22 |
+
*.db
|
23 |
+
*.sqlite
|
24 |
+
*.sqlite3
|
25 |
+
|
26 |
+
# Uploads
|
27 |
+
uploads/
|
28 |
+
*.jpg
|
29 |
+
*.jpeg
|
30 |
+
*.png
|
31 |
+
*.gif
|
32 |
+
*.mp3
|
33 |
+
*.wav
|
34 |
+
*.m4a
|
35 |
+
|
36 |
+
# Development files
|
37 |
+
.env
|
38 |
+
.env.local
|
39 |
+
.env.development
|
40 |
+
.env.test
|
41 |
+
.env.production
|
42 |
+
|
43 |
+
# IDE
|
44 |
+
.vscode/
|
45 |
+
.idea/
|
46 |
+
*.swp
|
47 |
+
*.swo
|
48 |
+
*~
|
49 |
+
|
50 |
+
# OS
|
51 |
+
.DS_Store
|
52 |
+
.DS_Store?
|
53 |
+
._*
|
54 |
+
.Spotlight-V100
|
55 |
+
.Trashes
|
56 |
+
ehthumbs.db
|
57 |
+
Thumbs.db
|
58 |
+
|
59 |
+
# Temporary files
|
60 |
+
*.tmp
|
61 |
+
*.temp
|
62 |
+
deployment_info.txt
|
63 |
+
CACHE_MANAGEMENT.md
|
64 |
+
|
65 |
+
# Jupyter
|
66 |
+
.ipynb_checkpoints/
|
67 |
+
|
68 |
+
# Docker
|
69 |
+
.dockerignore-cachebust
|
70 |
+
|
71 |
+
# Node.js (if any)
|
72 |
+
node_modules/
|
73 |
+
npm-debug.log
|
74 |
+
yarn-error.log
|
@@ -3,6 +3,8 @@ Enhanced Tree Mapping FastAPI Application
|
|
3 |
Implements security, robustness, performance, and best practices improvements
|
4 |
"""
|
5 |
|
|
|
|
|
6 |
import logging
|
7 |
import re
|
8 |
import sqlite3
|
@@ -516,7 +518,7 @@ async def debug_file_content():
|
|
516 |
content = f.read()
|
517 |
|
518 |
# Extract key indicators
|
519 |
-
has_fire_emoji = "
|
520 |
has_v4 = "V4.0" in content
|
521 |
has_blue_theme = "#3b82f6" in content
|
522 |
has_red_border = "#ff0000" in content
|
@@ -1068,6 +1070,80 @@ async def get_stats():
|
|
1068 |
raise
|
1069 |
|
1070 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1071 |
# Error handlers for better error responses
|
1072 |
@app.exception_handler(404)
|
1073 |
async def not_found_handler(request: Request, exc: Exception):
|
|
|
3 |
Implements security, robustness, performance, and best practices improvements
|
4 |
"""
|
5 |
|
6 |
+
|
7 |
+
import json
|
8 |
import logging
|
9 |
import re
|
10 |
import sqlite3
|
|
|
518 |
content = f.read()
|
519 |
|
520 |
# Extract key indicators
|
521 |
+
has_fire_emoji = "" in content
|
522 |
has_v4 = "V4.0" in content
|
523 |
has_blue_theme = "#3b82f6" in content
|
524 |
has_red_border = "#ff0000" in content
|
|
|
1070 |
raise
|
1071 |
|
1072 |
|
1073 |
+
# Version management endpoints
|
1074 |
+
@app.get("/api/version", tags=["System"])
|
1075 |
+
async def get_version():
|
1076 |
+
"""Get current application version and asset versions"""
|
1077 |
+
try:
|
1078 |
+
version_file = Path("version.json")
|
1079 |
+
if version_file.exists():
|
1080 |
+
async with aiofiles.open(version_file, 'r') as f:
|
1081 |
+
content = await f.read()
|
1082 |
+
version_data = json.loads(content)
|
1083 |
+
else:
|
1084 |
+
# Fallback version data
|
1085 |
+
version_data = {
|
1086 |
+
"version": "3.0",
|
1087 |
+
"timestamp": int(time.time()),
|
1088 |
+
"build": "development",
|
1089 |
+
"commit": "local"
|
1090 |
+
}
|
1091 |
+
|
1092 |
+
version_data["server_time"] = datetime.now().isoformat()
|
1093 |
+
return version_data
|
1094 |
+
|
1095 |
+
except Exception as e:
|
1096 |
+
logger.error(f"Error reading version: {e}")
|
1097 |
+
return {
|
1098 |
+
"version": "unknown",
|
1099 |
+
"timestamp": int(time.time()),
|
1100 |
+
"build": "error",
|
1101 |
+
"error": str(e)
|
1102 |
+
}
|
1103 |
+
|
1104 |
+
|
1105 |
+
@app.post("/api/version/update", tags=["System"])
|
1106 |
+
async def update_version():
|
1107 |
+
"""Force update version and clear cache"""
|
1108 |
+
try:
|
1109 |
+
# Update version timestamp
|
1110 |
+
timestamp = int(time.time())
|
1111 |
+
version_number = f"3.{timestamp}"
|
1112 |
+
|
1113 |
+
version_data = {
|
1114 |
+
"version": version_number,
|
1115 |
+
"timestamp": timestamp,
|
1116 |
+
"build": "development",
|
1117 |
+
"commit": "local",
|
1118 |
+
"updated_by": "api"
|
1119 |
+
}
|
1120 |
+
|
1121 |
+
# Write version file
|
1122 |
+
version_file = Path("version.json")
|
1123 |
+
async with aiofiles.open(version_file, 'w') as f:
|
1124 |
+
await f.write(json.dumps(version_data, indent=4))
|
1125 |
+
|
1126 |
+
logger.info(f"Version updated to {version_number}")
|
1127 |
+
|
1128 |
+
return {
|
1129 |
+
"success": True,
|
1130 |
+
"message": "Version updated successfully",
|
1131 |
+
"new_version": version_number,
|
1132 |
+
"timestamp": timestamp,
|
1133 |
+
"instructions": [
|
1134 |
+
"Clear browser cache: Ctrl+Shift+R (Windows) / Cmd+Shift+R (Mac)",
|
1135 |
+
"Or open DevTools > Application > Service Workers > Unregister"
|
1136 |
+
]
|
1137 |
+
}
|
1138 |
+
|
1139 |
+
except Exception as e:
|
1140 |
+
logger.error(f"Error updating version: {e}")
|
1141 |
+
return {
|
1142 |
+
"success": False,
|
1143 |
+
"error": str(e)
|
1144 |
+
}
|
1145 |
+
|
1146 |
+
|
1147 |
# Error handlers for better error responses
|
1148 |
@app.exception_handler(404)
|
1149 |
async def not_found_handler(request: Request, exc: Exception):
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@echo off
|
2 |
+
echo TreeTrack Cache Buster
|
3 |
+
echo ======================
|
4 |
+
echo.
|
5 |
+
echo This will:
|
6 |
+
echo 1. Update all version files
|
7 |
+
echo 2. Restart the server
|
8 |
+
echo 3. Clear browser cache
|
9 |
+
echo 4. Open browser with fresh content
|
10 |
+
echo.
|
11 |
+
pause
|
12 |
+
echo.
|
13 |
+
echo Running cache buster...
|
14 |
+
python clear_cache.py
|
15 |
+
echo.
|
16 |
+
echo Done! Check the output above for instructions.
|
17 |
+
pause
|
@@ -0,0 +1,213 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
TreeTrack Cache Buster - Comprehensive Solution
|
4 |
+
This script provides multiple methods to clear browser cache and ensure updated UI is shown
|
5 |
+
"""
|
6 |
+
|
7 |
+
import json
|
8 |
+
import time
|
9 |
+
import os
|
10 |
+
import subprocess
|
11 |
+
import webbrowser
|
12 |
+
from pathlib import Path
|
13 |
+
|
14 |
+
def update_service_worker():
|
15 |
+
"""Update service worker cache version"""
|
16 |
+
try:
|
17 |
+
sw_file = Path("static/sw.js")
|
18 |
+
if sw_file.exists():
|
19 |
+
content = sw_file.read_text()
|
20 |
+
|
21 |
+
# Update the VERSION constant with current timestamp
|
22 |
+
timestamp = int(time.time())
|
23 |
+
new_version_line = f"const VERSION = {timestamp}; // Dynamic versioning"
|
24 |
+
|
25 |
+
# Replace the VERSION line
|
26 |
+
lines = content.split('\n')
|
27 |
+
for i, line in enumerate(lines):
|
28 |
+
if line.strip().startswith("const VERSION"):
|
29 |
+
lines[i] = new_version_line
|
30 |
+
break
|
31 |
+
|
32 |
+
sw_file.write_text('\n'.join(lines))
|
33 |
+
print(f" Updated service worker version to {timestamp}")
|
34 |
+
return timestamp
|
35 |
+
except Exception as e:
|
36 |
+
print(f" Error updating service worker: {e}")
|
37 |
+
return None
|
38 |
+
|
39 |
+
def update_html_cache_busting():
|
40 |
+
"""Update HTML files with cache busting parameters"""
|
41 |
+
try:
|
42 |
+
timestamp = int(time.time())
|
43 |
+
|
44 |
+
# Update index.html
|
45 |
+
index_file = Path("static/index.html")
|
46 |
+
if index_file.exists():
|
47 |
+
content = index_file.read_text(encoding='utf-8')
|
48 |
+
|
49 |
+
# Update the version in the inline script
|
50 |
+
old_version = "const currentVersion = '3.0';"
|
51 |
+
new_version = f"const currentVersion = '3.{timestamp}';"
|
52 |
+
content = content.replace(old_version, new_version)
|
53 |
+
|
54 |
+
# Update script src with timestamp
|
55 |
+
old_script = '<script src="/static/app.js?v=3.0&t=1691506800">'
|
56 |
+
new_script = f'<script src="/static/app.js?v=3.{timestamp}&t={timestamp}">'
|
57 |
+
content = content.replace(old_script, new_script)
|
58 |
+
|
59 |
+
index_file.write_text(content, encoding='utf-8')
|
60 |
+
print(f" Updated index.html cache busting")
|
61 |
+
|
62 |
+
return timestamp
|
63 |
+
except Exception as e:
|
64 |
+
print(f" Error updating HTML cache busting: {e}")
|
65 |
+
return None
|
66 |
+
|
67 |
+
def clear_browser_cache():
|
68 |
+
"""Provide detailed instructions for clearing browser cache"""
|
69 |
+
print("\n BROWSER CACHE CLEARING INSTRUCTIONS")
|
70 |
+
print("=" * 50)
|
71 |
+
|
72 |
+
print("\n METHOD 1: Hard Refresh")
|
73 |
+
print("• Windows/Linux: Ctrl + Shift + R")
|
74 |
+
print("• Mac: Cmd + Shift + R")
|
75 |
+
|
76 |
+
print("\n METHOD 2: Developer Tools")
|
77 |
+
print("1. Press F12 to open Developer Tools")
|
78 |
+
print("2. Right-click the refresh button")
|
79 |
+
print("3. Select 'Empty Cache and Hard Reload'")
|
80 |
+
|
81 |
+
print("\n METHOD 3: Service Worker (Most Important!)")
|
82 |
+
print("1. Open DevTools (F12)")
|
83 |
+
print("2. Go to Application tab")
|
84 |
+
print("3. Click 'Service Workers' in sidebar")
|
85 |
+
print("4. Find TreeTrack service worker")
|
86 |
+
print("5. Click 'Unregister'")
|
87 |
+
print("6. Refresh the page")
|
88 |
+
|
89 |
+
print("\n METHOD 4: Clear All Cache")
|
90 |
+
print("• Chrome: Ctrl+Shift+Delete, select 'All time'")
|
91 |
+
print("• Firefox: Ctrl+Shift+Delete, select 'Everything'")
|
92 |
+
print("• Edge: Ctrl+Shift+Delete, select 'All time'")
|
93 |
+
|
94 |
+
def kill_server_processes():
|
95 |
+
"""Kill any running server processes"""
|
96 |
+
try:
|
97 |
+
print("\n Stopping any running servers...")
|
98 |
+
|
99 |
+
# Kill uvicorn processes
|
100 |
+
if os.name == 'nt': # Windows
|
101 |
+
subprocess.run(['taskkill', '/f', '/im', 'python.exe'],
|
102 |
+
capture_output=True, text=True)
|
103 |
+
else: # Linux/Mac
|
104 |
+
subprocess.run(['pkill', '-f', 'uvicorn'],
|
105 |
+
capture_output=True, text=True)
|
106 |
+
|
107 |
+
print(" Stopped server processes")
|
108 |
+
time.sleep(2)
|
109 |
+
|
110 |
+
except Exception as e:
|
111 |
+
print(f" Could not stop server processes: {e}")
|
112 |
+
|
113 |
+
def start_server():
|
114 |
+
"""Start the FastAPI server"""
|
115 |
+
try:
|
116 |
+
print("\n Starting FastAPI server...")
|
117 |
+
|
118 |
+
# Set environment variable for cache busting
|
119 |
+
os.environ['BUILD_TIME'] = str(int(time.time()))
|
120 |
+
|
121 |
+
# Start server in background
|
122 |
+
if os.name == 'nt': # Windows
|
123 |
+
subprocess.Popen([
|
124 |
+
'python', 'app.py'
|
125 |
+
], creationflags=subprocess.CREATE_NEW_CONSOLE)
|
126 |
+
else:
|
127 |
+
subprocess.Popen([
|
128 |
+
'python3', 'app.py'
|
129 |
+
])
|
130 |
+
|
131 |
+
print(" Server starting... (check console for status)")
|
132 |
+
time.sleep(3)
|
133 |
+
|
134 |
+
except Exception as e:
|
135 |
+
print(f" Error starting server: {e}")
|
136 |
+
|
137 |
+
def open_browser():
|
138 |
+
"""Open browser with cache-busting parameters"""
|
139 |
+
try:
|
140 |
+
timestamp = int(time.time())
|
141 |
+
url = f"http://127.0.0.1:8000/?v={timestamp}&_cache_bust={timestamp}"
|
142 |
+
|
143 |
+
print(f"\n Opening browser with cache-busting URL:")
|
144 |
+
print(f" {url}")
|
145 |
+
|
146 |
+
webbrowser.open(url)
|
147 |
+
|
148 |
+
except Exception as e:
|
149 |
+
print(f" Error opening browser: {e}")
|
150 |
+
|
151 |
+
def update_version_file():
|
152 |
+
"""Update the version.json file"""
|
153 |
+
try:
|
154 |
+
timestamp = int(time.time())
|
155 |
+
version_data = {
|
156 |
+
"version": f"3.{timestamp}",
|
157 |
+
"timestamp": timestamp,
|
158 |
+
"build": "development",
|
159 |
+
"commit": "local",
|
160 |
+
"cache_cleared": True,
|
161 |
+
"updated_by": "cache_buster_script"
|
162 |
+
}
|
163 |
+
|
164 |
+
with open("version.json", 'w') as f:
|
165 |
+
json.dump(version_data, f, indent=4)
|
166 |
+
|
167 |
+
print(f" Updated version.json to {version_data['version']}")
|
168 |
+
return version_data
|
169 |
+
|
170 |
+
except Exception as e:
|
171 |
+
print(f" Error updating version file: {e}")
|
172 |
+
return None
|
173 |
+
|
174 |
+
def main():
|
175 |
+
"""Main cache busting routine"""
|
176 |
+
print(" TreeTrack Cache Buster")
|
177 |
+
print("=" * 40)
|
178 |
+
|
179 |
+
# Step 1: Update version management
|
180 |
+
print("\n1⃣ Updating version files...")
|
181 |
+
sw_version = update_service_worker()
|
182 |
+
html_version = update_html_cache_busting()
|
183 |
+
version_data = update_version_file()
|
184 |
+
|
185 |
+
# Step 2: Kill and restart server
|
186 |
+
print("\n2⃣ Restarting server...")
|
187 |
+
kill_server_processes()
|
188 |
+
start_server()
|
189 |
+
|
190 |
+
# Step 3: Provide cache clearing instructions
|
191 |
+
print("\n3⃣ Cache clearing instructions...")
|
192 |
+
clear_browser_cache()
|
193 |
+
|
194 |
+
# Step 4: Open browser
|
195 |
+
print("\n4⃣ Opening browser...")
|
196 |
+
open_browser()
|
197 |
+
|
198 |
+
print("\n CACHE BUSTING COMPLETE!")
|
199 |
+
print("=" * 40)
|
200 |
+
|
201 |
+
if version_data:
|
202 |
+
print(f" New Version: {version_data['version']}")
|
203 |
+
|
204 |
+
print("\n IMPORTANT NEXT STEPS:")
|
205 |
+
print("1. Follow the browser cache clearing instructions above")
|
206 |
+
print("2. Check that the server started successfully")
|
207 |
+
print("3. If UI still looks old, manually clear Service Worker (Method 3)")
|
208 |
+
print("4. Try opening in incognito/private mode to test")
|
209 |
+
|
210 |
+
print(f"\n Direct URL: http://127.0.0.1:8000/?v={int(time.time())}")
|
211 |
+
|
212 |
+
if __name__ == "__main__":
|
213 |
+
main()
|
@@ -3,6 +3,7 @@ Configuration management for Tree Mapping Application
|
|
3 |
Implements environment-based configuration with validation and security best practices
|
4 |
"""
|
5 |
|
|
|
6 |
import os
|
7 |
from functools import lru_cache
|
8 |
from pathlib import Path
|
@@ -391,12 +392,12 @@ if __name__ == "__main__":
|
|
391 |
# Test configuration loading
|
392 |
try:
|
393 |
settings = validate_settings()
|
394 |
-
print("
|
395 |
print(f"Environment: {settings.app.environment}")
|
396 |
print(f"Database: {settings.database.db_path}")
|
397 |
print(f"Server: {settings.server.host}:{settings.server.port}")
|
398 |
print(f"Log level: {settings.logging.log_level}")
|
399 |
|
400 |
except Exception as e:
|
401 |
-
print(f"
|
402 |
exit(1)
|
|
|
3 |
Implements environment-based configuration with validation and security best practices
|
4 |
"""
|
5 |
|
6 |
+
|
7 |
import os
|
8 |
from functools import lru_cache
|
9 |
from pathlib import Path
|
|
|
392 |
# Test configuration loading
|
393 |
try:
|
394 |
settings = validate_settings()
|
395 |
+
print(" Configuration loaded successfully")
|
396 |
print(f"Environment: {settings.app.environment}")
|
397 |
print(f"Database: {settings.database.db_path}")
|
398 |
print(f"Server: {settings.server.host}:{settings.server.port}")
|
399 |
print(f"Log level: {settings.logging.log_level}")
|
400 |
|
401 |
except Exception as e:
|
402 |
+
print(f" Configuration error: {e}")
|
403 |
exit(1)
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
Hugging Face Spaces Cache Management
|
4 |
+
Optimized for HF Spaces deployment with proper cache busting
|
5 |
+
"""
|
6 |
+
|
7 |
+
import json
|
8 |
+
import time
|
9 |
+
import os
|
10 |
+
from pathlib import Path
|
11 |
+
|
12 |
+
def update_for_hf_spaces():
|
13 |
+
"""Update all cache-related files for HF Spaces deployment"""
|
14 |
+
|
15 |
+
timestamp = int(time.time())
|
16 |
+
print(f"Updating TreeTrack for HF Spaces deployment")
|
17 |
+
print(f"Timestamp: {timestamp}")
|
18 |
+
|
19 |
+
# 1. Update service worker
|
20 |
+
try:
|
21 |
+
sw_file = Path("static/sw.js")
|
22 |
+
if sw_file.exists():
|
23 |
+
content = sw_file.read_text(encoding='utf-8')
|
24 |
+
|
25 |
+
# Update VERSION constant
|
26 |
+
lines = content.split('\n')
|
27 |
+
for i, line in enumerate(lines):
|
28 |
+
if line.strip().startswith("const VERSION"):
|
29 |
+
lines[i] = f"const VERSION = {timestamp}; // Dynamic versioning"
|
30 |
+
break
|
31 |
+
|
32 |
+
sw_file.write_text('\n'.join(lines), encoding='utf-8')
|
33 |
+
print(f"Updated service worker to version {timestamp}")
|
34 |
+
except Exception as e:
|
35 |
+
print(f"Service worker update failed: {e}")
|
36 |
+
|
37 |
+
# 2. Update version.json
|
38 |
+
try:
|
39 |
+
version_data = {
|
40 |
+
"version": f"3.{timestamp}",
|
41 |
+
"timestamp": timestamp,
|
42 |
+
"build": "hf_spaces",
|
43 |
+
"commit": "deployed",
|
44 |
+
"cache_cleared": True,
|
45 |
+
"deployment": "huggingface_spaces",
|
46 |
+
"port": 7860
|
47 |
+
}
|
48 |
+
|
49 |
+
with open("version.json", 'w', encoding='utf-8') as f:
|
50 |
+
json.dump(version_data, f, indent=4)
|
51 |
+
|
52 |
+
print(f" Updated version.json to {version_data['version']}")
|
53 |
+
except Exception as e:
|
54 |
+
print(f" Version file update failed: {e}")
|
55 |
+
|
56 |
+
# 3. Update HTML files with cache busting
|
57 |
+
try:
|
58 |
+
for html_file in ["static/index.html", "static/map.html"]:
|
59 |
+
html_path = Path(html_file)
|
60 |
+
if html_path.exists():
|
61 |
+
content = html_path.read_text(encoding='utf-8')
|
62 |
+
|
63 |
+
# Update version in inline scripts
|
64 |
+
content = content.replace("const currentVersion = '3.0';",
|
65 |
+
f"const currentVersion = '3.{timestamp}';")
|
66 |
+
|
67 |
+
# Update script/CSS URLs with timestamp
|
68 |
+
content = content.replace('app.js?v=3.0&t=1691506800',
|
69 |
+
f'app.js?v=3.{timestamp}&t={timestamp}')
|
70 |
+
content = content.replace('map.js?v=3.0&t=1691506800',
|
71 |
+
f'map.js?v=3.{timestamp}&t={timestamp}')
|
72 |
+
|
73 |
+
html_path.write_text(content, encoding='utf-8')
|
74 |
+
print(f" Updated {html_file}")
|
75 |
+
except Exception as e:
|
76 |
+
print(f" HTML file update failed: {e}")
|
77 |
+
|
78 |
+
# 4. Create deployment info
|
79 |
+
try:
|
80 |
+
with open("deployment_info.txt", 'w', encoding='utf-8') as f:
|
81 |
+
f.write(f"TreeTrack - HF Spaces Deployment\n")
|
82 |
+
f.write(f"==============================\n")
|
83 |
+
f.write(f"Version: 3.{timestamp}\n")
|
84 |
+
f.write(f"Deployed: {time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime())}\n")
|
85 |
+
f.write(f"Platform: Hugging Face Spaces\n")
|
86 |
+
f.write(f"Port: 7860\n")
|
87 |
+
f.write(f"Cache Cleared: Yes\n")
|
88 |
+
f.write(f"Service Worker: Updated\n")
|
89 |
+
f.write(f"\nCache Clear Instructions:\n")
|
90 |
+
f.write(f"1. Open DevTools (F12)\n")
|
91 |
+
f.write(f"2. Go to Application > Service Workers\n")
|
92 |
+
f.write(f"3. Unregister TreeTrack service worker\n")
|
93 |
+
f.write(f"4. Hard refresh (Ctrl+Shift+R)\n")
|
94 |
+
|
95 |
+
print(f" Created deployment_info.txt")
|
96 |
+
except Exception as e:
|
97 |
+
print(f" Deployment info creation failed: {e}")
|
98 |
+
|
99 |
+
print(f"\n Cache management complete!")
|
100 |
+
print(f" New version: 3.{timestamp}")
|
101 |
+
print(f" Ready for HF Spaces deployment")
|
102 |
+
|
103 |
+
return timestamp
|
104 |
+
|
105 |
+
def create_no_cache_headers():
|
106 |
+
"""Create a reference file for no-cache headers"""
|
107 |
+
|
108 |
+
headers_info = """
|
109 |
+
# No-Cache Headers Reference for TreeTrack
|
110 |
+
|
111 |
+
## FastAPI Middleware (already implemented in app.py)
|
112 |
+
```python
|
113 |
+
@app.middleware("http")
|
114 |
+
async def add_cache_headers(request: Request, call_next):
|
115 |
+
if request.url.path.startswith("/static/"):
|
116 |
+
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
|
117 |
+
response.headers["Pragma"] = "no-cache"
|
118 |
+
response.headers["Expires"] = "0"
|
119 |
+
```
|
120 |
+
|
121 |
+
## HTML Meta Tags (already implemented)
|
122 |
+
```html
|
123 |
+
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
124 |
+
<meta http-equiv="Pragma" content="no-cache">
|
125 |
+
<meta http-equiv="Expires" content="0">
|
126 |
+
```
|
127 |
+
|
128 |
+
## Service Worker Strategy
|
129 |
+
- Development: Network-first, cache bypass
|
130 |
+
- Production: Cache-first with version-based invalidation
|
131 |
+
- HF Spaces: Automatic development mode detection
|
132 |
+
|
133 |
+
## Manual Cache Clear (for users)
|
134 |
+
1. Open DevTools (F12)
|
135 |
+
2. Application tab > Service Workers
|
136 |
+
3. Find TreeTrack service worker
|
137 |
+
4. Click "Unregister"
|
138 |
+
5. Hard refresh: Ctrl+Shift+R (Windows) / Cmd+Shift+R (Mac)
|
139 |
+
6. Or use incognito/private browsing mode
|
140 |
+
"""
|
141 |
+
|
142 |
+
try:
|
143 |
+
with open("CACHE_MANAGEMENT.md", 'w', encoding='utf-8') as f:
|
144 |
+
f.write(headers_info)
|
145 |
+
print(" Created CACHE_MANAGEMENT.md reference")
|
146 |
+
except Exception as e:
|
147 |
+
print(f" Cache reference creation failed: {e}")
|
148 |
+
|
149 |
+
if __name__ == "__main__":
|
150 |
+
print(" TreeTrack - HF Spaces Cache Manager")
|
151 |
+
print("=" * 40)
|
152 |
+
|
153 |
+
timestamp = update_for_hf_spaces()
|
154 |
+
create_no_cache_headers()
|
155 |
+
|
156 |
+
print(f"\n Ready for deployment!")
|
157 |
+
print(f" After deployment, users can force refresh with Ctrl+Shift+R")
|
158 |
+
print(f" Or recommend using incognito/private mode for testing")
|
@@ -0,0 +1,339 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# TreeTrack Project - LLM Knowledge Base
|
2 |
+
|
3 |
+
## PROJECT OVERVIEW
|
4 |
+
TreeTrack is a comprehensive tree mapping and urban forestry management web application deployed on **Hugging Face Spaces** using **Docker**.
|
5 |
+
|
6 |
+
### Key Information:
|
7 |
+
- **Platform**: Hugging Face Spaces (NOT local server)
|
8 |
+
- **Deployment**: Docker container with port 7860
|
9 |
+
- **Technology Stack**: FastAPI + Vanilla JavaScript + SQLite
|
10 |
+
- **Purpose**: Field research tool for tree documentation with 12 comprehensive data fields
|
11 |
+
|
12 |
+
## DEPLOYMENT ARCHITECTURE
|
13 |
+
|
14 |
+
### Hugging Face Spaces Configuration
|
15 |
+
- **App Port**: 7860 (required by HF Spaces)
|
16 |
+
- **SDK**: docker
|
17 |
+
- **Command**: `uvicorn app:app --host 0.0.0.0 --port 7860`
|
18 |
+
- **User Context**: Non-root user (uid 1000) for security
|
19 |
+
- **Base Image**: python:3.11-slim
|
20 |
+
|
21 |
+
### Docker Configuration
|
22 |
+
```dockerfile
|
23 |
+
FROM python:3.11-slim
|
24 |
+
RUN useradd -m -u 1000 user
|
25 |
+
USER user
|
26 |
+
WORKDIR /app
|
27 |
+
EXPOSE 7860
|
28 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
29 |
+
```
|
30 |
+
|
31 |
+
## TECHNOLOGY STACK
|
32 |
+
|
33 |
+
### Backend
|
34 |
+
- **Framework**: FastAPI 0.115.0+
|
35 |
+
- **Server**: Uvicorn with standard extras
|
36 |
+
- **Database**: SQLite with WAL mode for concurrency
|
37 |
+
- **File Handling**: aiofiles for async file operations
|
38 |
+
- **Validation**: Pydantic 2.10.0+ with custom validators
|
39 |
+
- **Configuration**: pydantic-settings for environment-based config
|
40 |
+
|
41 |
+
### Frontend
|
42 |
+
- **JavaScript**: Vanilla ES6+ (no frameworks)
|
43 |
+
- **CSS**: Custom responsive design with Inter font
|
44 |
+
- **PWA**: Service Worker for offline capabilities
|
45 |
+
- **Maps**: Interactive mapping with marker clustering
|
46 |
+
- **Media**: Camera integration and audio recording
|
47 |
+
|
48 |
+
### Dependencies
|
49 |
+
```
|
50 |
+
fastapi>=0.115.0
|
51 |
+
uvicorn[standard]>=0.32.0
|
52 |
+
python-multipart>=0.0.12
|
53 |
+
pydantic>=2.10.0
|
54 |
+
pydantic-settings>=2.6.0
|
55 |
+
pandas>=2.3.1
|
56 |
+
aiofiles>=24.1.0
|
57 |
+
GitPython>=3.1.40
|
58 |
+
huggingface-hub>=0.19.0
|
59 |
+
```
|
60 |
+
|
61 |
+
## DATA MODEL - 12 COMPREHENSIVE FIELDS
|
62 |
+
|
63 |
+
### Core Tree Data Structure
|
64 |
+
1. **Geolocation** (Required)
|
65 |
+
- Latitude: REAL (-90 to 90)
|
66 |
+
- Longitude: REAL (-180 to 180)
|
67 |
+
|
68 |
+
2. **Identification**
|
69 |
+
- Local Name (Assamese): TEXT (max 200 chars)
|
70 |
+
- Scientific Name: TEXT (max 200 chars)
|
71 |
+
- Common Name: TEXT (max 200 chars)
|
72 |
+
- Tree Reference Code: TEXT (max 20 chars, e.g., "C.A", "A-G1")
|
73 |
+
|
74 |
+
3. **Physical Measurements**
|
75 |
+
- Height: REAL (meters, 0-200)
|
76 |
+
- Width/Girth: REAL (cm, 0-2000)
|
77 |
+
|
78 |
+
4. **Ecological & Cultural Data**
|
79 |
+
- Utility: JSON array of selected utilities
|
80 |
+
- Valid options: "Religious", "Timber", "Biodiversity", "Hydrological benefit", "Faunal interaction", "Food", "Medicine", "Shelter", "Cultural"
|
81 |
+
|
82 |
+
5. **Phenology Assessment**
|
83 |
+
- Stages: JSON array of current development stages
|
84 |
+
- Valid options: "New leaves", "Old leaves", "Open flowers", "Fruiting", "Ripe fruit", "Recent fruit drop", "Other"
|
85 |
+
|
86 |
+
6. **Documentation**
|
87 |
+
- Photographs: JSON object with categories and file paths
|
88 |
+
- Categories: "Leaf", "Bark", "Fruit", "Seed", "Flower", "Full tree"
|
89 |
+
- Storytelling Text: TEXT (max 5000 chars)
|
90 |
+
- Storytelling Audio: File path to audio recording
|
91 |
+
|
92 |
+
7. **Field Notes**
|
93 |
+
- Notes: TEXT (max 2000 chars) for additional observations
|
94 |
+
|
95 |
+
### System Fields
|
96 |
+
- ID: Auto-increment primary key
|
97 |
+
- Timestamp: Auto-generated creation time
|
98 |
+
- Created_by: Default 'system'
|
99 |
+
- Updated_at: Auto-updated modification time
|
100 |
+
|
101 |
+
## SECURITY ARCHITECTURE
|
102 |
+
|
103 |
+
### Multi-Layer Security
|
104 |
+
- **Input Validation**: Pydantic models with custom validators
|
105 |
+
- **SQL Injection Prevention**: Parameterized queries only
|
106 |
+
- **XSS Protection**: Content Security Policy headers
|
107 |
+
- **CORS**: Configured for specific origins
|
108 |
+
- **File Security**: Secure path validation, type checking
|
109 |
+
- **Rate Limiting**: Configurable request limits
|
110 |
+
- **Error Handling**: Comprehensive exception management
|
111 |
+
|
112 |
+
### Production Security Settings
|
113 |
+
- Secret key validation (min 32 chars)
|
114 |
+
- Debug mode disabled
|
115 |
+
- Secure headers (X-Frame-Options, X-Content-Type-Options, etc.)
|
116 |
+
- Trusted host middleware available
|
117 |
+
|
118 |
+
## CACHE MANAGEMENT SYSTEM
|
119 |
+
|
120 |
+
### Current Issue: Aggressive Service Worker Caching
|
121 |
+
The app uses a Service Worker (`sw.js`) for PWA capabilities and offline support, but this causes caching issues during development.
|
122 |
+
|
123 |
+
### Service Worker Configuration
|
124 |
+
- **Cache Name**: `treetrack-v{timestamp}` (dynamic versioning implemented)
|
125 |
+
- **Cached Resources**: HTML, JS, CSS, external fonts/libraries
|
126 |
+
- **Cache Strategy**: Cache-first for production, network-first for development
|
127 |
+
- **Development Mode Detection**: `isDevelopment = self.location.hostname === 'localhost' || '127.0.0.1'`
|
128 |
+
|
129 |
+
### Cache Busting Solutions Implemented
|
130 |
+
|
131 |
+
#### 1. Dynamic Service Worker Versioning
|
132 |
+
```javascript
|
133 |
+
const VERSION = new Date().getTime(); // Dynamic versioning
|
134 |
+
const CACHE_NAME = `treetrack-v${VERSION}`;
|
135 |
+
```
|
136 |
+
|
137 |
+
#### 2. Development Mode Cache Bypass
|
138 |
+
```javascript
|
139 |
+
if (isDevelopment && event.request.url.includes('/static/')) {
|
140 |
+
event.respondWith(
|
141 |
+
fetch(event.request, { cache: 'no-cache' })
|
142 |
+
);
|
143 |
+
}
|
144 |
+
```
|
145 |
+
|
146 |
+
#### 3. Version Management API
|
147 |
+
- **GET /api/version**: Returns current version info
|
148 |
+
- **POST /api/version/update**: Forces version update and cache clear
|
149 |
+
|
150 |
+
#### 4. HTML Cache Busting
|
151 |
+
```html
|
152 |
+
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
153 |
+
<meta http-equiv="Pragma" content="no-cache">
|
154 |
+
<meta http-equiv="Expires" content="0">
|
155 |
+
<script src="/static/app.js?v=3.{timestamp}&t={timestamp}">
|
156 |
+
```
|
157 |
+
|
158 |
+
#### 5. FastAPI Middleware
|
159 |
+
```python
|
160 |
+
@app.middleware("http")
|
161 |
+
async def add_cache_headers(request: Request, call_next):
|
162 |
+
if request.url.path.startswith("/static/"):
|
163 |
+
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
|
164 |
+
```
|
165 |
+
|
166 |
+
## FILE STRUCTURE
|
167 |
+
|
168 |
+
```
|
169 |
+
TreeTrack/
|
170 |
+
README.md # HF Spaces config + documentation
|
171 |
+
Dockerfile # HF Spaces Docker configuration
|
172 |
+
requirements.txt # Python dependencies
|
173 |
+
app.py # Main FastAPI application
|
174 |
+
config.py # Comprehensive configuration system
|
175 |
+
version.json # Version tracking for cache busting
|
176 |
+
clear_cache.py # Cache busting automation script
|
177 |
+
clear_cache.bat # Windows batch script
|
178 |
+
update_version.py # Version management utility
|
179 |
+
.dockerignore-cachebust # Docker ignore with cache busting
|
180 |
+
.gitattributes # Git LFS configuration
|
181 |
+
static/
|
182 |
+
index.html # Main application interface
|
183 |
+
map.html # Interactive map interface
|
184 |
+
app.js # Frontend application logic
|
185 |
+
map.js # Map functionality
|
186 |
+
sw.js # Service Worker for PWA/offline
|
187 |
+
```
|
188 |
+
|
189 |
+
## API ENDPOINTS
|
190 |
+
|
191 |
+
### Core Tree Management
|
192 |
+
- `GET /api/trees` - List trees with filtering/pagination
|
193 |
+
- `POST /api/trees` - Create new tree record
|
194 |
+
- `GET /api/trees/{id}` - Get specific tree
|
195 |
+
- `PUT /api/trees/{id}` - Update tree record
|
196 |
+
- `DELETE /api/trees/{id}` - Delete tree record
|
197 |
+
|
198 |
+
### File Upload
|
199 |
+
- `POST /api/upload/image` - Upload categorized photos
|
200 |
+
- `POST /api/upload/audio` - Upload storytelling audio
|
201 |
+
- `GET /api/files/{type}/{filename}` - Serve uploaded files
|
202 |
+
|
203 |
+
### Form Data Helpers
|
204 |
+
- `GET /api/utilities` - Valid utility options
|
205 |
+
- `GET /api/phenology-stages` - Valid phenology stages
|
206 |
+
- `GET /api/photo-categories` - Valid photo categories
|
207 |
+
|
208 |
+
### System Management
|
209 |
+
- `GET /health` - Health check
|
210 |
+
- `GET /api/stats` - Comprehensive statistics
|
211 |
+
- `GET /api/version` - Current version info
|
212 |
+
- `POST /api/version/update` - Force cache clear
|
213 |
+
- `GET /api/persistence/status` - Database backup status
|
214 |
+
- `POST /api/persistence/backup` - Force database backup
|
215 |
+
|
216 |
+
### Frontend Routes
|
217 |
+
- `GET /` - Main application (index.html)
|
218 |
+
- `GET /map` - Interactive map interface
|
219 |
+
- `GET /docs` - FastAPI auto-generated API documentation
|
220 |
+
|
221 |
+
## CONFIGURATION SYSTEM
|
222 |
+
|
223 |
+
### Environment-Based Settings (config.py)
|
224 |
+
- **DatabaseConfig**: SQLite settings, backup configuration
|
225 |
+
- **SecurityConfig**: Authentication, CORS, CSP, rate limiting
|
226 |
+
- **ServerConfig**: Host, port, workers, timeout settings
|
227 |
+
- **LoggingConfig**: File logging, rotation, access logs
|
228 |
+
- **CacheConfig**: TTL settings, cache sizes
|
229 |
+
- **MonitoringConfig**: Metrics, health checks, alerting
|
230 |
+
- **ApplicationConfig**: Feature flags, validation rules
|
231 |
+
|
232 |
+
### Configuration Hierarchy
|
233 |
+
1. Environment variables
|
234 |
+
2. `.env` file
|
235 |
+
3. Default values in config classes
|
236 |
+
|
237 |
+
## COMMON ISSUES & SOLUTIONS
|
238 |
+
|
239 |
+
### Cache Issues (PRIMARY ISSUE)
|
240 |
+
**Problem**: Service Worker aggressively caches static files
|
241 |
+
**Solutions**:
|
242 |
+
1. Run `python clear_cache.py` to update all version files
|
243 |
+
2. Manually clear Service Worker in DevTools > Application > Service Workers
|
244 |
+
3. Use `Ctrl+Shift+R` for hard refresh
|
245 |
+
4. Open in incognito/private mode for testing
|
246 |
+
5. Use version API endpoints to force cache updates
|
247 |
+
|
248 |
+
### Deployment to HF Spaces
|
249 |
+
**Remember**: This app runs on Hugging Face Spaces, not localhost
|
250 |
+
- Port must be 7860
|
251 |
+
- Use Docker deployment
|
252 |
+
- Non-root user for security
|
253 |
+
- Static files served through FastAPI
|
254 |
+
|
255 |
+
### Development vs Production
|
256 |
+
- Development: Cache bypassing enabled
|
257 |
+
- Production: Full caching for performance
|
258 |
+
- Environment detection via hostname
|
259 |
+
|
260 |
+
## CACHE BUSTING WORKFLOW
|
261 |
+
|
262 |
+
### For Hugging Face Spaces Deployment (PRIMARY)
|
263 |
+
```bash
|
264 |
+
# Run HF Spaces optimized cache manager
|
265 |
+
python hf_cache_manager.py
|
266 |
+
```
|
267 |
+
|
268 |
+
### For Local Development
|
269 |
+
```bash
|
270 |
+
# Run comprehensive cache buster
|
271 |
+
python clear_cache.py
|
272 |
+
|
273 |
+
# Or use batch file on Windows
|
274 |
+
clear_cache.bat
|
275 |
+
```
|
276 |
+
|
277 |
+
### Manual Steps
|
278 |
+
1. Update service worker version: `const VERSION = {timestamp}`
|
279 |
+
2. Update HTML cache busting parameters
|
280 |
+
3. Update version.json file
|
281 |
+
4. Clear browser service worker
|
282 |
+
5. Hard refresh browser
|
283 |
+
6. Test in incognito mode
|
284 |
+
|
285 |
+
### API-Based Solution
|
286 |
+
```bash
|
287 |
+
# Update version via API (when server running)
|
288 |
+
curl -X POST http://localhost:7860/api/version/update
|
289 |
+
|
290 |
+
# Check current version
|
291 |
+
curl http://localhost:7860/api/version
|
292 |
+
```
|
293 |
+
|
294 |
+
## DEVELOPMENT GUIDELINES
|
295 |
+
|
296 |
+
### Adding New Features
|
297 |
+
1. Update Pydantic models in app.py
|
298 |
+
2. Add API endpoints with proper validation
|
299 |
+
3. Update frontend JavaScript
|
300 |
+
4. Add appropriate tests
|
301 |
+
5. Update cache version if static files change
|
302 |
+
|
303 |
+
### Database Schema Changes
|
304 |
+
- Use SQLite migrations carefully
|
305 |
+
- Backup database before schema changes
|
306 |
+
- Update Pydantic models to match
|
307 |
+
- Test with existing data
|
308 |
+
|
309 |
+
### Security Considerations
|
310 |
+
- Always validate input with Pydantic
|
311 |
+
- Use parameterized queries
|
312 |
+
- Implement rate limiting for public endpoints
|
313 |
+
- Regular security header updates
|
314 |
+
- Monitor for suspicious activity
|
315 |
+
|
316 |
+
## CACHE VERSION HISTORY
|
317 |
+
- v3.0: Initial version with basic cache busting
|
318 |
+
- v3.{timestamp}: Dynamic versioning system
|
319 |
+
- Current: Automated cache management with API endpoints
|
320 |
+
|
321 |
+
## TROUBLESHOOTING CHECKLIST
|
322 |
+
|
323 |
+
### UI Not Updating
|
324 |
+
1. Check if service worker is registered (DevTools > Application)
|
325 |
+
2. Unregister service worker if present
|
326 |
+
3. Hard refresh browser (Ctrl+Shift+R)
|
327 |
+
4. Check Network tab for 304 (cached) responses
|
328 |
+
5. Try incognito/private mode
|
329 |
+
6. Run cache buster script
|
330 |
+
7. Verify version.json timestamp is recent
|
331 |
+
|
332 |
+
### Deployment Issues
|
333 |
+
1. Verify Dockerfile uses port 7860
|
334 |
+
2. Check requirements.txt is complete
|
335 |
+
3. Ensure non-root user setup
|
336 |
+
4. Verify static files are copied correctly
|
337 |
+
5. Check HF Spaces logs for errors
|
338 |
+
|
339 |
+
This knowledge base should be updated whenever significant changes are made to the project architecture, caching system, or deployment configuration.
|
@@ -63,11 +63,11 @@ class TreeTrackApp {
|
|
63 |
<div>
|
64 |
<label>${category}</label>
|
65 |
<div class="file-upload photo-upload" data-category="${category}">
|
66 |
-
|
67 |
</div>
|
68 |
<div class="uploaded-file" id="photo-${category}" style="display: none;"></div>
|
69 |
</div>
|
70 |
-
<button type="button" class="btn btn-small" onclick="app.capturePhoto('${category}')"
|
71 |
`;
|
72 |
container.appendChild(categoryDiv);
|
73 |
});
|
@@ -198,7 +198,7 @@ class TreeTrackApp {
|
|
198 |
// Update UI
|
199 |
const resultDiv = document.getElementById(`photo-${category}`);
|
200 |
resultDiv.style.display = 'block';
|
201 |
-
resultDiv.innerHTML =
|
202 |
} else {
|
203 |
throw new Error('Upload failed');
|
204 |
}
|
@@ -239,7 +239,7 @@ class TreeTrackApp {
|
|
239 |
|
240 |
// Update UI
|
241 |
const resultDiv = document.getElementById('audioUploadResult');
|
242 |
-
resultDiv.innerHTML = `<div class="uploaded-file"
|
243 |
} else {
|
244 |
throw new Error('Upload failed');
|
245 |
}
|
@@ -285,7 +285,7 @@ class TreeTrackApp {
|
|
285 |
const recordBtn = document.getElementById('recordBtn');
|
286 |
const status = document.getElementById('recordingStatus');
|
287 |
recordBtn.classList.add('recording');
|
288 |
-
recordBtn.innerHTML = '
|
289 |
status.textContent = 'Recording... Click to stop';
|
290 |
|
291 |
} catch (error) {
|
@@ -304,24 +304,24 @@ class TreeTrackApp {
|
|
304 |
const recordBtn = document.getElementById('recordBtn');
|
305 |
const status = document.getElementById('recordingStatus');
|
306 |
recordBtn.classList.remove('recording');
|
307 |
-
recordBtn.innerHTML = '
|
308 |
status.textContent = 'Recording saved!';
|
309 |
}
|
310 |
}
|
311 |
|
312 |
getCurrentLocation() {
|
313 |
if (navigator.geolocation) {
|
314 |
-
document.getElementById('getLocation').textContent = '
|
315 |
|
316 |
navigator.geolocation.getCurrentPosition(
|
317 |
(position) => {
|
318 |
document.getElementById('latitude').value = position.coords.latitude.toFixed(7);
|
319 |
document.getElementById('longitude').value = position.coords.longitude.toFixed(7);
|
320 |
-
document.getElementById('getLocation').textContent = '
|
321 |
this.showMessage('Location retrieved successfully!', 'success');
|
322 |
},
|
323 |
(error) => {
|
324 |
-
document.getElementById('getLocation').textContent = '
|
325 |
this.showMessage('Error getting location: ' + error.message, 'error');
|
326 |
}
|
327 |
);
|
@@ -427,9 +427,9 @@ class TreeTrackApp {
|
|
427 |
<div class="tree-id">Tree #${tree.id}</div>
|
428 |
<div class="tree-info">
|
429 |
${tree.scientific_name || tree.common_name || tree.local_name || 'Unnamed'}
|
430 |
-
<br
|
431 |
-
${tree.tree_code ? '<br
|
432 |
-
<br
|
433 |
</div>
|
434 |
</div>
|
435 |
`).join('');
|
|
|
63 |
<div>
|
64 |
<label>${category}</label>
|
65 |
<div class="file-upload photo-upload" data-category="${category}">
|
66 |
+
Camera Click to upload ${category} photo or use camera
|
67 |
</div>
|
68 |
<div class="uploaded-file" id="photo-${category}" style="display: none;"></div>
|
69 |
</div>
|
70 |
+
<button type="button" class="btn btn-small" onclick="app.capturePhoto('${category}')">Photo Camera</button>
|
71 |
`;
|
72 |
container.appendChild(categoryDiv);
|
73 |
});
|
|
|
198 |
// Update UI
|
199 |
const resultDiv = document.getElementById(`photo-${category}`);
|
200 |
resultDiv.style.display = 'block';
|
201 |
+
resultDiv.innerHTML = ` ${file.name} uploaded successfully`;
|
202 |
} else {
|
203 |
throw new Error('Upload failed');
|
204 |
}
|
|
|
239 |
|
240 |
// Update UI
|
241 |
const resultDiv = document.getElementById('audioUploadResult');
|
242 |
+
resultDiv.innerHTML = `<div class="uploaded-file"> ${file.name} uploaded successfully</div>`;
|
243 |
} else {
|
244 |
throw new Error('Upload failed');
|
245 |
}
|
|
|
285 |
const recordBtn = document.getElementById('recordBtn');
|
286 |
const status = document.getElementById('recordingStatus');
|
287 |
recordBtn.classList.add('recording');
|
288 |
+
recordBtn.innerHTML = '⏹';
|
289 |
status.textContent = 'Recording... Click to stop';
|
290 |
|
291 |
} catch (error) {
|
|
|
304 |
const recordBtn = document.getElementById('recordBtn');
|
305 |
const status = document.getElementById('recordingStatus');
|
306 |
recordBtn.classList.remove('recording');
|
307 |
+
recordBtn.innerHTML = '';
|
308 |
status.textContent = 'Recording saved!';
|
309 |
}
|
310 |
}
|
311 |
|
312 |
getCurrentLocation() {
|
313 |
if (navigator.geolocation) {
|
314 |
+
document.getElementById('getLocation').textContent = ' Getting...';
|
315 |
|
316 |
navigator.geolocation.getCurrentPosition(
|
317 |
(position) => {
|
318 |
document.getElementById('latitude').value = position.coords.latitude.toFixed(7);
|
319 |
document.getElementById('longitude').value = position.coords.longitude.toFixed(7);
|
320 |
+
document.getElementById('getLocation').textContent = ' GPS';
|
321 |
this.showMessage('Location retrieved successfully!', 'success');
|
322 |
},
|
323 |
(error) => {
|
324 |
+
document.getElementById('getLocation').textContent = ' GPS';
|
325 |
this.showMessage('Error getting location: ' + error.message, 'error');
|
326 |
}
|
327 |
);
|
|
|
427 |
<div class="tree-id">Tree #${tree.id}</div>
|
428 |
<div class="tree-info">
|
429 |
${tree.scientific_name || tree.common_name || tree.local_name || 'Unnamed'}
|
430 |
+
<br> ${tree.latitude.toFixed(4)}, ${tree.longitude.toFixed(4)}
|
431 |
+
${tree.tree_code ? '<br> ' + tree.tree_code : ''}
|
432 |
+
<br> ${new Date(tree.timestamp).toLocaleDateString()}
|
433 |
</div>
|
434 |
</div>
|
435 |
`).join('');
|
@@ -422,7 +422,7 @@
|
|
422 |
<script>
|
423 |
// Force refresh if we detect cached version
|
424 |
(function() {
|
425 |
-
const currentVersion = '3.
|
426 |
const lastVersion = sessionStorage.getItem('treetrack_version');
|
427 |
if (!lastVersion || lastVersion !== currentVersion) {
|
428 |
sessionStorage.setItem('treetrack_version', currentVersion);
|
@@ -586,6 +586,6 @@
|
|
586 |
</div>
|
587 |
</div>
|
588 |
|
589 |
-
<script src="/static/app.js?v=3.
|
590 |
</body>
|
591 |
</html>
|
|
|
422 |
<script>
|
423 |
// Force refresh if we detect cached version
|
424 |
(function() {
|
425 |
+
const currentVersion = '3.1754653728';
|
426 |
const lastVersion = sessionStorage.getItem('treetrack_version');
|
427 |
if (!lastVersion || lastVersion !== currentVersion) {
|
428 |
sessionStorage.setItem('treetrack_version', currentVersion);
|
|
|
586 |
</div>
|
587 |
</div>
|
588 |
|
589 |
+
<script src="/static/app.js?v=3.1754653728&t=1754653728"></script>
|
590 |
</body>
|
591 |
</html>
|
@@ -233,7 +233,7 @@ class TreeTrackMap {
|
|
233 |
${tree.height ? `<p style="margin: 0 0 5px 0;"><strong>Height:</strong> ${tree.height}m</p>` : ''}
|
234 |
${tree.width ? `<p style="margin: 0 0 5px 0;"><strong>Girth:</strong> ${tree.width}cm</p>` : ''}
|
235 |
<div style="margin-top: 10px; font-size: 0.9rem; color: #888;">
|
236 |
-
|
237 |
</div>
|
238 |
</div>
|
239 |
`;
|
@@ -315,7 +315,7 @@ class TreeTrackMap {
|
|
315 |
fillOpacity: 1
|
316 |
}).addTo(this.map);
|
317 |
|
318 |
-
userMarker.bindPopup('
|
319 |
className: 'user-location-popup'
|
320 |
});
|
321 |
}
|
|
|
233 |
${tree.height ? `<p style="margin: 0 0 5px 0;"><strong>Height:</strong> ${tree.height}m</p>` : ''}
|
234 |
${tree.width ? `<p style="margin: 0 0 5px 0;"><strong>Girth:</strong> ${tree.width}cm</p>` : ''}
|
235 |
<div style="margin-top: 10px; font-size: 0.9rem; color: #888;">
|
236 |
+
${tree.latitude.toFixed(6)}, ${tree.longitude.toFixed(6)}
|
237 |
</div>
|
238 |
</div>
|
239 |
`;
|
|
|
315 |
fillOpacity: 1
|
316 |
}).addTo(this.map);
|
317 |
|
318 |
+
userMarker.bindPopup(' Your Location', {
|
319 |
className: 'user-location-popup'
|
320 |
});
|
321 |
}
|
@@ -1,5 +1,12 @@
|
|
1 |
// TreeTrack Service Worker - PWA and Offline Support
|
2 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
const urlsToCache = [
|
4 |
'/static/',
|
5 |
'/static/index.html',
|
@@ -8,7 +15,7 @@ const urlsToCache = [
|
|
8 |
'/static/map.js',
|
9 |
'https://unpkg.com/[email protected]/dist/leaflet.css',
|
10 |
'https://unpkg.com/[email protected]/dist/leaflet.js',
|
11 |
-
'https://fonts.googleapis.com/css2?family=
|
12 |
];
|
13 |
|
14 |
// Install event - cache resources
|
@@ -37,15 +44,43 @@ self.addEventListener('fetch', event => {
|
|
37 |
return;
|
38 |
}
|
39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
event.respondWith(
|
41 |
caches.match(event.request)
|
42 |
.then(response => {
|
43 |
-
//
|
44 |
-
if (response) {
|
45 |
return response;
|
46 |
}
|
47 |
|
48 |
-
|
|
|
|
|
|
|
|
|
|
|
49 |
// Check if we received a valid response
|
50 |
if (!response || response.status !== 200 || response.type !== 'basic') {
|
51 |
return response;
|
@@ -54,10 +89,13 @@ self.addEventListener('fetch', event => {
|
|
54 |
// Clone the response
|
55 |
const responseToCache = response.clone();
|
56 |
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
|
|
|
|
|
|
61 |
|
62 |
return response;
|
63 |
}).catch(() => {
|
|
|
1 |
// TreeTrack Service Worker - PWA and Offline Support
|
2 |
+
const VERSION = 1754653904; // Dynamic versioning
|
3 |
+
const CACHE_NAME = `treetrack-v${VERSION}`;
|
4 |
+
const STATIC_CACHE = `static-v${VERSION}`;
|
5 |
+
const API_CACHE = `api-v${VERSION}`;
|
6 |
+
|
7 |
+
// Check if we're in development mode
|
8 |
+
const isDevelopment = self.location.hostname === 'localhost' || self.location.hostname === '127.0.0.1';
|
9 |
+
|
10 |
const urlsToCache = [
|
11 |
'/static/',
|
12 |
'/static/index.html',
|
|
|
15 |
'/static/map.js',
|
16 |
'https://unpkg.com/[email protected]/dist/leaflet.css',
|
17 |
'https://unpkg.com/[email protected]/dist/leaflet.js',
|
18 |
+
'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'
|
19 |
];
|
20 |
|
21 |
// Install event - cache resources
|
|
|
44 |
return;
|
45 |
}
|
46 |
|
47 |
+
// In development mode, always fetch fresh content for static files
|
48 |
+
// Also detect Hugging Face Spaces development environment
|
49 |
+
const isHFDevelopment = self.location.hostname.includes('hf.space') ||
|
50 |
+
self.location.hostname.includes('huggingface.co');
|
51 |
+
|
52 |
+
if ((isDevelopment || isHFDevelopment) && event.request.url.includes('/static/')) {
|
53 |
+
console.log('Development mode: bypassing cache for', event.request.url);
|
54 |
+
event.respondWith(
|
55 |
+
fetch(event.request, {
|
56 |
+
cache: 'no-cache',
|
57 |
+
headers: {
|
58 |
+
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
59 |
+
'Pragma': 'no-cache'
|
60 |
+
}
|
61 |
+
})
|
62 |
+
.catch(() => {
|
63 |
+
console.log('Network failed, using cache fallback for', event.request.url);
|
64 |
+
return caches.match(event.request);
|
65 |
+
})
|
66 |
+
);
|
67 |
+
return;
|
68 |
+
}
|
69 |
+
|
70 |
event.respondWith(
|
71 |
caches.match(event.request)
|
72 |
.then(response => {
|
73 |
+
// In production, use cache-first strategy
|
74 |
+
if (response && !isDevelopment) {
|
75 |
return response;
|
76 |
}
|
77 |
|
78 |
+
// Fetch with cache busting in development
|
79 |
+
const fetchRequest = isDevelopment ?
|
80 |
+
new Request(event.request.url + '?_=' + Date.now()) :
|
81 |
+
event.request;
|
82 |
+
|
83 |
+
return fetch(fetchRequest).then(response => {
|
84 |
// Check if we received a valid response
|
85 |
if (!response || response.status !== 200 || response.type !== 'basic') {
|
86 |
return response;
|
|
|
89 |
// Clone the response
|
90 |
const responseToCache = response.clone();
|
91 |
|
92 |
+
// Only cache in production or for offline fallbacks
|
93 |
+
if (!isDevelopment) {
|
94 |
+
caches.open(CACHE_NAME)
|
95 |
+
.then(cache => {
|
96 |
+
cache.put(event.request, responseToCache);
|
97 |
+
});
|
98 |
+
}
|
99 |
|
100 |
return response;
|
101 |
}).catch(() => {
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
Version management script for TreeTrack
|
4 |
+
Updates version numbers and forces cache refresh
|
5 |
+
"""
|
6 |
+
|
7 |
+
import json
|
8 |
+
import time
|
9 |
+
import os
|
10 |
+
import hashlib
|
11 |
+
from pathlib import Path
|
12 |
+
|
13 |
+
def get_file_hash(file_path):
|
14 |
+
"""Generate MD5 hash of file for version tracking"""
|
15 |
+
if not os.path.exists(file_path):
|
16 |
+
return "missing"
|
17 |
+
|
18 |
+
with open(file_path, 'rb') as f:
|
19 |
+
return hashlib.md5(f.read()).hexdigest()[:8]
|
20 |
+
|
21 |
+
def update_version():
|
22 |
+
"""Update version.json with current timestamp and file hashes"""
|
23 |
+
|
24 |
+
# Get current directory
|
25 |
+
base_dir = Path(__file__).parent
|
26 |
+
static_dir = base_dir / "static"
|
27 |
+
version_file = base_dir / "version.json"
|
28 |
+
|
29 |
+
# Generate new version info
|
30 |
+
timestamp = int(time.time())
|
31 |
+
version_number = f"3.{timestamp}"
|
32 |
+
|
33 |
+
# Get file hashes for cache busting
|
34 |
+
assets = {}
|
35 |
+
static_files = ["app.js", "index.html", "map.html", "map.js", "sw.js"]
|
36 |
+
|
37 |
+
for filename in static_files:
|
38 |
+
file_path = static_dir / filename
|
39 |
+
file_hash = get_file_hash(file_path)
|
40 |
+
assets[filename] = f"{version_number}.{file_hash}"
|
41 |
+
|
42 |
+
# Create version data
|
43 |
+
version_data = {
|
44 |
+
"version": version_number,
|
45 |
+
"timestamp": timestamp,
|
46 |
+
"build": "development" if os.getenv('NODE_ENV') != 'production' else "production",
|
47 |
+
"commit": os.getenv('GIT_COMMIT', 'local'),
|
48 |
+
"assets": assets
|
49 |
+
}
|
50 |
+
|
51 |
+
# Write version file
|
52 |
+
with open(version_file, 'w') as f:
|
53 |
+
json.dump(version_data, f, indent=4)
|
54 |
+
|
55 |
+
print(f" Updated version to: {version_number}")
|
56 |
+
print(f" Updated {len(assets)} asset versions")
|
57 |
+
|
58 |
+
return version_data
|
59 |
+
|
60 |
+
def clear_browser_cache():
|
61 |
+
"""Provide instructions for clearing browser cache"""
|
62 |
+
print("\n To clear browser cache completely:")
|
63 |
+
print("1. Open Developer Tools (F12)")
|
64 |
+
print("2. Right-click the refresh button")
|
65 |
+
print("3. Select 'Empty Cache and Hard Reload'")
|
66 |
+
print("4. Or use Ctrl+Shift+R (Windows) / Cmd+Shift+R (Mac)")
|
67 |
+
print("\n For Service Workers:")
|
68 |
+
print("1. Go to Application tab in DevTools")
|
69 |
+
print("2. Click 'Service Workers' in sidebar")
|
70 |
+
print("3. Click 'Unregister' next to your service worker")
|
71 |
+
print("4. Refresh the page")
|
72 |
+
|
73 |
+
if __name__ == "__main__":
|
74 |
+
print(" TreeTrack Version Manager")
|
75 |
+
print("=" * 30)
|
76 |
+
|
77 |
+
try:
|
78 |
+
version_data = update_version()
|
79 |
+
clear_browser_cache()
|
80 |
+
|
81 |
+
print(f"\n Version update complete!")
|
82 |
+
print(f"Current version: {version_data['version']}")
|
83 |
+
|
84 |
+
except Exception as e:
|
85 |
+
print(f" Error updating version: {e}")
|
86 |
+
exit(1)
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"version": "3.1754653904",
|
3 |
+
"timestamp": 1754653904,
|
4 |
+
"build": "hf_spaces",
|
5 |
+
"commit": "deployed",
|
6 |
+
"cache_cleared": true,
|
7 |
+
"deployment": "huggingface_spaces",
|
8 |
+
"port": 7860
|
9 |
+
}
|