Spaces:
Runtime error
Runtime error
Update app.py
#1
by
TNTFLO
- opened
app.py
CHANGED
|
@@ -11,8 +11,11 @@ import hashlib
|
|
| 11 |
from pathlib import Path
|
| 12 |
import requests
|
| 13 |
from io import BytesIO
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
-
class
|
| 16 |
def __init__(self):
|
| 17 |
# Lade Secrets aus Space Environment
|
| 18 |
self.hf_token = os.environ.get("HF_TOKEN", "")
|
|
@@ -20,29 +23,24 @@ class HFSecretsFileStorageMCP:
|
|
| 20 |
self.org_name = os.environ.get("ORG_NAME", "mcp-filestorage")
|
| 21 |
self.db_path = "user_management.db"
|
| 22 |
|
| 23 |
-
# Fallback auf lokale Variablen falls Secrets nicht verfügbar
|
| 24 |
-
if not self.hf_token:
|
| 25 |
-
print("⚠️ HF_TOKEN nicht in Secrets gefunden, verwende leeren Token")
|
| 26 |
-
if not self.dataset_name or self.dataset_name == "mcp-filestorage/default-files":
|
| 27 |
-
print("⚠️ DATASET_NAME nicht in Secrets gefunden, erstelle lokalen Speicher")
|
| 28 |
-
|
| 29 |
self.api = HfApi(token=self.hf_token if self.hf_token else None)
|
| 30 |
self.init_database()
|
| 31 |
self.init_storage()
|
| 32 |
-
|
|
|
|
| 33 |
def init_database(self):
|
| 34 |
"""Initialisiert SQLite-Datenbank für Benutzerverwaltung"""
|
| 35 |
conn = sqlite3.connect(self.db_path)
|
| 36 |
cursor = conn.cursor()
|
| 37 |
|
| 38 |
-
# Benutzer-Tabelle
|
| 39 |
cursor.execute('''
|
| 40 |
CREATE TABLE IF NOT EXISTS users (
|
| 41 |
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 42 |
user_uuid TEXT UNIQUE NOT NULL,
|
|
|
|
| 43 |
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 44 |
last_access DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 45 |
-
folder_count INTEGER DEFAULT 0,
|
| 46 |
file_count INTEGER DEFAULT 0,
|
| 47 |
is_active BOOLEAN DEFAULT 1,
|
| 48 |
storage_type TEXT DEFAULT 'hf_dataset'
|
|
@@ -76,12 +74,10 @@ class HFSecretsFileStorageMCP:
|
|
| 76 |
"""Initialisiert Speicher (Dataset oder lokal)"""
|
| 77 |
try:
|
| 78 |
if self.dataset_name and self.dataset_name != "mcp-filestorage/default-files":
|
| 79 |
-
# Versuche Dataset zu laden
|
| 80 |
self.dataset = load_dataset(self.dataset_name, split='train', token=self.hf_token)
|
| 81 |
print(f"✅ Dataset geladen: {self.dataset_name}")
|
| 82 |
self.storage_mode = 'dataset'
|
| 83 |
else:
|
| 84 |
-
# Lokaler Speicher als Fallback
|
| 85 |
self.storage_mode = 'local'
|
| 86 |
self.local_storage_path = Path("user_storage")
|
| 87 |
self.local_storage_path.mkdir(exist_ok=True)
|
|
@@ -93,24 +89,38 @@ class HFSecretsFileStorageMCP:
|
|
| 93 |
self.local_storage_path.mkdir(exist_ok=True)
|
| 94 |
|
| 95 |
def register_user(self) -> Dict[str, Any]:
|
| 96 |
-
"""Registriert neuen Benutzer mit UUID"""
|
| 97 |
user_uuid = str(uuid.uuid4())
|
|
|
|
| 98 |
|
| 99 |
conn = sqlite3.connect(self.db_path)
|
| 100 |
cursor = conn.cursor()
|
| 101 |
|
| 102 |
try:
|
| 103 |
-
cursor.execute('
|
|
|
|
|
|
|
|
|
|
| 104 |
conn.commit()
|
| 105 |
|
| 106 |
# Erstelle Benutzerordner
|
| 107 |
self._create_user_folder(user_uuid)
|
| 108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
return {
|
| 110 |
'success': True,
|
| 111 |
'user_uuid': user_uuid,
|
|
|
|
|
|
|
| 112 |
'message': f'Benutzer {user_uuid} registriert'
|
| 113 |
}
|
|
|
|
| 114 |
except Exception as e:
|
| 115 |
return {
|
| 116 |
'success': False,
|
|
@@ -118,12 +128,35 @@ class HFSecretsFileStorageMCP:
|
|
| 118 |
}
|
| 119 |
finally:
|
| 120 |
conn.close()
|
| 121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
def _create_user_folder(self, user_uuid: str):
|
| 123 |
"""Erstellt Benutzerordner je nach Speicher-Modus"""
|
| 124 |
if self.storage_mode == 'dataset':
|
| 125 |
try:
|
| 126 |
-
# Erstelle Ordner-Struktur im Dataset
|
| 127 |
folder_path = f"users/{user_uuid}"
|
| 128 |
readme_content = f"# User Folder: {user_uuid}\nCreated: {datetime.now().isoformat()}\n"
|
| 129 |
|
|
@@ -144,6 +177,11 @@ class HFSecretsFileStorageMCP:
|
|
| 144 |
def create_file(self, user_uuid: str, file_path: str, content: bytes,
|
| 145 |
metadata: Optional[Dict] = None) -> Dict[str, Any]:
|
| 146 |
"""Erstellt neue Datei für Benutzer"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
file_uuid = str(uuid.uuid4())
|
| 148 |
file_hash = hashlib.sha256(content).hexdigest()
|
| 149 |
|
|
@@ -151,9 +189,9 @@ class HFSecretsFileStorageMCP:
|
|
| 151 |
cursor = conn.cursor()
|
| 152 |
|
| 153 |
try:
|
| 154 |
-
# Speichere Datei je nach Modus
|
| 155 |
storage_path = f"users/{user_uuid}/{file_path}"
|
| 156 |
|
|
|
|
| 157 |
if self.storage_mode == 'dataset':
|
| 158 |
try:
|
| 159 |
self.api.upload_file(
|
|
@@ -186,24 +224,29 @@ class HFSecretsFileStorageMCP:
|
|
| 186 |
|
| 187 |
conn.commit()
|
| 188 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
return {
|
| 190 |
'success': True,
|
| 191 |
'file_uuid': file_uuid,
|
| 192 |
'file_path': file_path,
|
| 193 |
-
'
|
| 194 |
-
'storage_mode': self.storage_mode
|
| 195 |
}
|
| 196 |
|
| 197 |
except Exception as e:
|
| 198 |
-
return {
|
| 199 |
-
'success': False,
|
| 200 |
-
'error': str(e)
|
| 201 |
-
}
|
| 202 |
finally:
|
| 203 |
conn.close()
|
| 204 |
-
|
| 205 |
def read_file(self, user_uuid: str, file_path: str) -> Dict[str, Any]:
|
| 206 |
"""Liest Datei eines Benutzers"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
conn = sqlite3.connect(self.db_path)
|
| 208 |
cursor = conn.cursor()
|
| 209 |
|
|
@@ -219,11 +262,11 @@ class HFSecretsFileStorageMCP:
|
|
| 219 |
|
| 220 |
file_uuid, filename, storage_path = result
|
| 221 |
|
| 222 |
-
# Erstelle Download-URL
|
| 223 |
if self.storage_mode == 'dataset':
|
| 224 |
file_url = f"https://huggingface.co/datasets/{self.dataset_name}/resolve/main/{storage_path}"
|
| 225 |
else:
|
| 226 |
-
# Lokaler Speicher
|
| 227 |
local_file_path = self.local_storage_path / user_uuid / file_path
|
| 228 |
if local_file_path.exists():
|
| 229 |
import base64
|
|
@@ -232,10 +275,9 @@ class HFSecretsFileStorageMCP:
|
|
| 232 |
else:
|
| 233 |
return {'success': False, 'error': 'Datei nicht im lokalen Speicher gefunden'}
|
| 234 |
|
| 235 |
-
# Aktualisiere
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
conn.commit()
|
| 239 |
|
| 240 |
return {
|
| 241 |
'success': True,
|
|
@@ -243,16 +285,20 @@ class HFSecretsFileStorageMCP:
|
|
| 243 |
'file_path': file_path,
|
| 244 |
'file_url': file_url,
|
| 245 |
'filename': filename,
|
| 246 |
-
'
|
| 247 |
}
|
| 248 |
|
| 249 |
except Exception as e:
|
| 250 |
return {'success': False, 'error': str(e)}
|
| 251 |
finally:
|
| 252 |
conn.close()
|
| 253 |
-
|
| 254 |
def update_file(self, user_uuid: str, file_path: str, content: bytes) -> Dict[str, Any]:
|
| 255 |
"""Aktualisiert vorhandene Datei"""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
conn = sqlite3.connect(self.db_path)
|
| 257 |
cursor = conn.cursor()
|
| 258 |
|
|
@@ -298,20 +344,29 @@ class HFSecretsFileStorageMCP:
|
|
| 298 |
|
| 299 |
conn.commit()
|
| 300 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
return {
|
| 302 |
'success': True,
|
| 303 |
'file_uuid': file_uuid,
|
| 304 |
'version': current_version + 1,
|
| 305 |
-
'file_path': file_path
|
|
|
|
| 306 |
}
|
| 307 |
|
| 308 |
except Exception as e:
|
| 309 |
return {'success': False, 'error': str(e)}
|
| 310 |
finally:
|
| 311 |
conn.close()
|
| 312 |
-
|
| 313 |
def delete_file(self, user_uuid: str, file_path: str) -> Dict[str, Any]:
|
| 314 |
"""Löscht Datei (Soft Delete)"""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 315 |
conn = sqlite3.connect(self.db_path)
|
| 316 |
cursor = conn.cursor()
|
| 317 |
|
|
@@ -333,19 +388,28 @@ class HFSecretsFileStorageMCP:
|
|
| 333 |
|
| 334 |
conn.commit()
|
| 335 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 336 |
return {
|
| 337 |
'success': True,
|
| 338 |
'message': f'Datei {file_path} gelöscht',
|
| 339 |
-
'file_path': file_path
|
|
|
|
| 340 |
}
|
| 341 |
|
| 342 |
except Exception as e:
|
| 343 |
return {'success': False, 'error': str(e)}
|
| 344 |
finally:
|
| 345 |
conn.close()
|
| 346 |
-
|
| 347 |
def list_files(self, user_uuid: str, folder_path: str = "") -> List[Dict[str, Any]]:
|
| 348 |
"""Listet alle Dateien eines Benutzers auf"""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
conn = sqlite3.connect(self.db_path)
|
| 350 |
cursor = conn.cursor()
|
| 351 |
|
|
@@ -379,70 +443,51 @@ class HFSecretsFileStorageMCP:
|
|
| 379 |
'file_size': row[6]
|
| 380 |
})
|
| 381 |
|
| 382 |
-
|
|
|
|
|
|
|
| 383 |
|
| 384 |
-
|
| 385 |
-
conn.close()
|
| 386 |
-
|
| 387 |
-
def get_user_stats(self, user_uuid: str) -> Dict[str, Any]:
|
| 388 |
-
"""Gibt Statistiken für einen Benutzer zurück"""
|
| 389 |
-
conn = sqlite3.connect(self.db_path)
|
| 390 |
-
cursor = conn.cursor()
|
| 391 |
-
|
| 392 |
-
try:
|
| 393 |
-
cursor.execute('''
|
| 394 |
-
SELECT created_at, file_count, last_access
|
| 395 |
-
FROM users WHERE user_uuid = ?
|
| 396 |
-
''', (user_uuid,))
|
| 397 |
-
|
| 398 |
-
result = cursor.fetchone()
|
| 399 |
-
if not result:
|
| 400 |
-
return {'error': 'Benutzer nicht gefunden'}
|
| 401 |
-
|
| 402 |
-
cursor.execute('''
|
| 403 |
-
SELECT COUNT(*) FROM file_metadata WHERE user_uuid = ? AND is_deleted = 0
|
| 404 |
-
''', (user_uuid,))
|
| 405 |
-
|
| 406 |
-
active_files = cursor.fetchone()[0]
|
| 407 |
-
|
| 408 |
-
return {
|
| 409 |
-
'user_uuid': user_uuid,
|
| 410 |
-
'created_at': result[0],
|
| 411 |
-
'total_files': active_files,
|
| 412 |
-
'last_access': result[2],
|
| 413 |
-
'storage_mode': self.storage_mode
|
| 414 |
-
}
|
| 415 |
|
| 416 |
finally:
|
| 417 |
conn.close()
|
| 418 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 419 |
# Globale Instanz
|
| 420 |
-
|
| 421 |
|
| 422 |
-
# ===== MCP TOOLS =====
|
| 423 |
|
| 424 |
@gr.mcp.tool()
|
| 425 |
-
def
|
| 426 |
"""
|
| 427 |
-
Registriert einen neuen Benutzer und
|
| 428 |
|
| 429 |
Returns:
|
| 430 |
-
|
| 431 |
"""
|
| 432 |
-
result =
|
| 433 |
|
| 434 |
if result['success']:
|
| 435 |
return f"""✅ Benutzer erfolgreich registriert!
|
|
|
|
| 436 |
🆔 Ihre UUID: {result['user_uuid']}
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
|
|
|
| 440 |
else:
|
| 441 |
return f"❌ Fehler: {result.get('error', 'Unbekannter Fehler')}"
|
| 442 |
|
| 443 |
@gr.mcp.tool()
|
| 444 |
-
def
|
| 445 |
-
|
| 446 |
"""
|
| 447 |
Erstellt eine neue Datei für einen Benutzer.
|
| 448 |
|
|
@@ -458,19 +503,19 @@ def create_user_file_secret(user_uuid: str, file_path: str, content: str,
|
|
| 458 |
content_bytes = content.encode('utf-8')
|
| 459 |
meta_dict = json.loads(metadata) if metadata else None
|
| 460 |
|
| 461 |
-
result =
|
| 462 |
|
| 463 |
if result['success']:
|
| 464 |
return f"""✅ Datei erfolgreich erstellt!
|
| 465 |
📁 Datei-UUID: {result['file_uuid']}
|
| 466 |
📄 Pfad: {result['file_path']}
|
| 467 |
-
|
| 468 |
-
|
| 469 |
else:
|
| 470 |
return f"❌ Fehler: {result.get('error', 'Unbekannter Fehler')}"
|
| 471 |
|
| 472 |
@gr.mcp.tool()
|
| 473 |
-
def
|
| 474 |
"""
|
| 475 |
Liest eine Datei eines Benutzers.
|
| 476 |
|
|
@@ -481,20 +526,20 @@ def read_user_file_secret(user_uuid: str, file_path: str) -> str:
|
|
| 481 |
Returns:
|
| 482 |
Datei-Details und Zugriffs-URL
|
| 483 |
"""
|
| 484 |
-
result =
|
| 485 |
|
| 486 |
if result['success']:
|
| 487 |
return f"""📄 Datei erfolgreich gelesen!
|
| 488 |
🆔 Datei-UUID: {result['file_uuid']}
|
| 489 |
🔗 Direkt-Link: {result['file_url']}
|
| 490 |
📁 Dateiname: {result['filename']}
|
| 491 |
-
|
| 492 |
-
|
| 493 |
else:
|
| 494 |
return f"❌ Fehler: {result.get('error', 'Datei nicht gefunden')}"
|
| 495 |
|
| 496 |
@gr.mcp.tool()
|
| 497 |
-
def
|
| 498 |
"""
|
| 499 |
Aktualisiert eine vorhandene Datei.
|
| 500 |
|
|
@@ -507,19 +552,19 @@ def update_user_file_secret(user_uuid: str, file_path: str, content: str) -> str
|
|
| 507 |
Erfolgsmeldung mit Versions-Info
|
| 508 |
"""
|
| 509 |
content_bytes = content.encode('utf-8')
|
| 510 |
-
result =
|
| 511 |
|
| 512 |
if result['success']:
|
| 513 |
return f"""✅ Datei erfolgreich aktualisiert!
|
| 514 |
🆔 Datei-UUID: {result['file_uuid']}
|
| 515 |
🔢 Neue Version: {result['version']}
|
| 516 |
📁 Pfad: {result['file_path']}
|
| 517 |
-
|
| 518 |
else:
|
| 519 |
return f"❌ Fehler: {result.get('error', 'Unbekannter Fehler')}"
|
| 520 |
|
| 521 |
@gr.mcp.tool()
|
| 522 |
-
def
|
| 523 |
"""
|
| 524 |
Löscht eine Datei eines Benutzers.
|
| 525 |
|
|
@@ -530,18 +575,18 @@ def delete_user_file_secret(user_uuid: str, file_path: str) -> str:
|
|
| 530 |
Returns:
|
| 531 |
Erfolgsmeldung
|
| 532 |
"""
|
| 533 |
-
result =
|
| 534 |
|
| 535 |
if result['success']:
|
| 536 |
return f"""🗑️ Datei erfolgreich gelöscht!
|
| 537 |
📁 Pfad: {result['file_path']}
|
| 538 |
-
|
| 539 |
-
|
| 540 |
else:
|
| 541 |
return f"❌ Fehler: {result.get('error', 'Unbekannter Fehler')}"
|
| 542 |
|
| 543 |
@gr.mcp.tool()
|
| 544 |
-
def
|
| 545 |
"""
|
| 546 |
Listet alle Dateien eines Benutzers auf.
|
| 547 |
|
|
@@ -552,13 +597,13 @@ def list_user_files_secret(user_uuid: str, folder_path: str = "") -> str:
|
|
| 552 |
Returns:
|
| 553 |
Liste aller Dateien des Benutzers
|
| 554 |
"""
|
| 555 |
-
files =
|
| 556 |
|
| 557 |
if not files:
|
| 558 |
return "📁 Keine Dateien gefunden!"
|
| 559 |
|
| 560 |
output = f"📊 Gefunden: {len(files)} Dateien für Benutzer {user_uuid}\n"
|
| 561 |
-
output += f"
|
| 562 |
|
| 563 |
for file in files:
|
| 564 |
output += f"""📄 {file['filename']} (Version {file['version']})
|
|
@@ -572,99 +617,60 @@ def list_user_files_secret(user_uuid: str, folder_path: str = "") -> str:
|
|
| 572 |
|
| 573 |
return output
|
| 574 |
|
| 575 |
-
|
| 576 |
-
def get_user_stats_secret(user_uuid: str) -> str:
|
| 577 |
-
"""
|
| 578 |
-
Gibt Statistiken für einen Benutzer zurück.
|
| 579 |
-
|
| 580 |
-
Args:
|
| 581 |
-
user_uuid: Die UUID des Benutzers
|
| 582 |
-
|
| 583 |
-
Returns:
|
| 584 |
-
Benutzer-Statistiken
|
| 585 |
-
"""
|
| 586 |
-
stats = hf_secrets_storage.get_user_stats(user_uuid)
|
| 587 |
-
|
| 588 |
-
if 'error' in stats:
|
| 589 |
-
return f"❌ Fehler: {stats['error']}"
|
| 590 |
-
|
| 591 |
-
return f"""📊 Benutzer-Statistiken für {user_uuid}:
|
| 592 |
-
📅 Registriert: {stats['created_at']}
|
| 593 |
-
📁 Aktive Dateien: {stats['total_files']}
|
| 594 |
-
🔄 Letzter Zugriff: {stats['last_access']}
|
| 595 |
-
☁️ Speicher-Modus: {stats['storage_mode']}
|
| 596 |
-
🗃️ Dataset: {hf_secrets_storage.dataset_name}"""
|
| 597 |
-
|
| 598 |
-
# ===== GRADIO INTERFACE =====
|
| 599 |
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 606 |
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
📁 Ihr Ordner: users/{result['user_uuid']}/
|
| 610 |
-
☁️ Speicher-Modus: {hf_secrets_storage.storage_mode}"""
|
| 611 |
-
else:
|
| 612 |
-
return f"❌ Fehler: {result.get('error', 'Unbekannter Fehler')}"
|
| 613 |
-
|
| 614 |
-
def create_file_ui(user_uuid, file_path, content, metadata):
|
| 615 |
-
"""UI-Funktion zum Erstellen von Dateien"""
|
| 616 |
-
if not user_uuid or not file_path or not content:
|
| 617 |
-
return "❌ Bitte füllen Sie UUID, Dateipfad und Inhalt aus!"
|
| 618 |
-
|
| 619 |
-
content_bytes = content.encode('utf-8')
|
| 620 |
-
meta_dict = json.loads(metadata) if metadata else None
|
| 621 |
-
|
| 622 |
-
result = hf_secrets_storage.create_file(user_uuid, file_path, content_bytes, meta_dict)
|
| 623 |
-
|
| 624 |
-
if result['success']:
|
| 625 |
-
return f"""✅ Datei erstellt!
|
| 626 |
-
|
| 627 |
-
📁 Pfad: {result['file_path']}
|
| 628 |
-
🆔 UUID: {result['file_uuid']}
|
| 629 |
-
☁️ Modus: {result['storage_mode']}"""
|
| 630 |
-
else:
|
| 631 |
-
return f"❌ Fehler: {result.get('error', 'Unbekannter Fehler')}"
|
| 632 |
-
|
| 633 |
-
def list_files_ui(user_uuid, folder_path):
|
| 634 |
-
"""UI-Funktion zum Auflisten von Dateien"""
|
| 635 |
-
if not user_uuid:
|
| 636 |
-
return "❌ Bitte geben Sie Ihre UUID ein!"
|
| 637 |
-
|
| 638 |
-
files = hf_secrets_storage.list_files(user_uuid, folder_path)
|
| 639 |
-
|
| 640 |
-
if not files:
|
| 641 |
-
return "📁 Keine Dateien gefunden!"
|
| 642 |
-
|
| 643 |
-
output = f"📊 {len(files)} Dateien gefunden:\n\n"
|
| 644 |
-
for file in files:
|
| 645 |
-
output += f"📄 {file['filename']} ({file['file_path']}) - Version {file['version']}\n"
|
| 646 |
-
|
| 647 |
-
return output
|
| 648 |
-
|
| 649 |
-
# Gradio Interface mit Space-Anpassungen
|
| 650 |
-
with gr.Blocks(title="🤗 HF Secrets Filestorage MCP", theme=gr.themes.Soft()) as demo:
|
| 651 |
gr.Markdown("""
|
| 652 |
# 🗃️ Hugging Face Dataset Filestorage MCP Server
|
| 653 |
-
###
|
| 654 |
|
| 655 |
-
Jeder Benutzer bekommt einen eigenen
|
| 656 |
-
Alle Dateien werden sicher in Hugging Face Datasets gespeichert - konfiguriert über Space Secrets!
|
| 657 |
""")
|
| 658 |
|
| 659 |
with gr.Tab("📝 Registrieren"):
|
| 660 |
with gr.Row():
|
| 661 |
with gr.Column():
|
| 662 |
-
gr.Markdown("### 🎯 Neuen Benutzer erstellen")
|
| 663 |
register_btn = gr.Button("🎯 Neuen Benutzer erstellen", variant="primary", size="lg")
|
| 664 |
with gr.Column():
|
| 665 |
-
register_output = gr.Textbox(label="Registrierungsergebnis", lines=
|
| 666 |
|
| 667 |
-
register_btn.click(
|
| 668 |
|
| 669 |
with gr.Tab("📤 Datei erstellen"):
|
| 670 |
with gr.Row():
|
|
@@ -680,96 +686,11 @@ with gr.Blocks(title="🤗 HF Secrets Filestorage MCP", theme=gr.themes.Soft())
|
|
| 680 |
create_output = gr.Textbox(label="Ergebnis", lines=8, max_lines=10)
|
| 681 |
|
| 682 |
create_btn.click(
|
| 683 |
-
|
| 684 |
inputs=[create_user_uuid, create_file_path, create_content, create_metadata],
|
| 685 |
outputs=create_output
|
| 686 |
)
|
| 687 |
|
| 688 |
-
with gr.Tab("📖 Datei lesen"):
|
| 689 |
-
with gr.Row():
|
| 690 |
-
with gr.Column():
|
| 691 |
-
gr.Markdown("### 📖 Datei aus dem Speicher lesen")
|
| 692 |
-
read_user_uuid = gr.Textbox(label="🆔 Ihre UUID", placeholder="Ihre UUID eingeben")
|
| 693 |
-
read_file_path = gr.Textbox(label="📁 Dateipfad", placeholder="z.B. meine-dokumente/test.txt")
|
| 694 |
-
read_btn = gr.Button("📖 Datei lesen", variant="secondary")
|
| 695 |
-
|
| 696 |
-
with gr.Column():
|
| 697 |
-
read_output = gr.Textbox(label="Datei-Details", lines=8, max_lines=10)
|
| 698 |
-
|
| 699 |
-
def read_file_wrapper(user_uuid, file_path):
|
| 700 |
-
if not user_uuid or not file_path:
|
| 701 |
-
return "❌ Bitte geben Sie UUID und Dateipfad ein!"
|
| 702 |
-
|
| 703 |
-
result = hf_secrets_storage.read_file(user_uuid, file_path)
|
| 704 |
-
if result['success']:
|
| 705 |
-
return f"""📄 Datei gefunden!
|
| 706 |
-
|
| 707 |
-
📁 Name: {result['filename']}
|
| 708 |
-
🔗 URL: {result['file_url']}
|
| 709 |
-
🆔 UUID: {result['file_uuid']}
|
| 710 |
-
☁️ Modus: {result['storage_mode']}"""
|
| 711 |
-
else:
|
| 712 |
-
return f"❌ Fehler: {result.get('error', 'Datei nicht gefunden')}"
|
| 713 |
-
|
| 714 |
-
read_btn.click(read_file_wrapper, inputs=[read_user_uuid, read_file_path], outputs=read_output)
|
| 715 |
-
|
| 716 |
-
with gr.Tab("✏️ Datei aktualisieren"):
|
| 717 |
-
with gr.Row():
|
| 718 |
-
with gr.Column():
|
| 719 |
-
gr.Markdown("### ✏️ Bestehende Datei bearbeiten")
|
| 720 |
-
update_user_uuid = gr.Textbox(label="🆔 Ihre UUID", placeholder="Ihre UUID eingeben")
|
| 721 |
-
update_file_path = gr.Textbox(label="📁 Dateipfad", placeholder="z.B. meine-dokumente/test.txt")
|
| 722 |
-
update_content = gr.TextArea(label="📝 Neuer Inhalt", placeholder="Neuer Inhalt hier eingeben...", lines=6)
|
| 723 |
-
update_btn = gr.Button("✏️ Datei aktualisieren", variant="secondary")
|
| 724 |
-
|
| 725 |
-
with gr.Column():
|
| 726 |
-
update_output = gr.Textbox(label="Update-Ergebnis", lines=6)
|
| 727 |
-
|
| 728 |
-
def update_file_wrapper(user_uuid, file_path, content):
|
| 729 |
-
if not user_uuid or not file_path or not content:
|
| 730 |
-
return "❌ Bitte füllen Sie alle Felder aus!"
|
| 731 |
-
|
| 732 |
-
content_bytes = content.encode('utf-8')
|
| 733 |
-
result = hf_secrets_storage.update_file(user_uuid, file_path, content_bytes)
|
| 734 |
-
|
| 735 |
-
if result['success']:
|
| 736 |
-
return f"""✅ Datei aktualisiert!
|
| 737 |
-
|
| 738 |
-
📁 Pfad: {result['file_path']}
|
| 739 |
-
🔄 Version: {result['version']}
|
| 740 |
-
🆔 UUID: {result['file_uuid']}"""
|
| 741 |
-
else:
|
| 742 |
-
return f"❌ Fehler: {result.get('error', 'Unbekannter Fehler')}"
|
| 743 |
-
|
| 744 |
-
update_btn.click(update_file_wrapper, inputs=[update_user_uuid, update_file_path, update_content], outputs=update_output)
|
| 745 |
-
|
| 746 |
-
with gr.Tab("🗑️ Datei löschen"):
|
| 747 |
-
with gr.Row():
|
| 748 |
-
with gr.Column():
|
| 749 |
-
gr.Markdown("### 🗑️ Datei endgültig löschen")
|
| 750 |
-
delete_user_uuid = gr.Textbox(label="🆔 Ihre UUID", placeholder="Ihre UUID eingeben")
|
| 751 |
-
delete_file_path = gr.Textbox(label="📁 Dateipfad", placeholder="z.B. meine-dokumente/test.txt")
|
| 752 |
-
delete_btn = gr.Button("🗑️ Datei löschen", variant="stop")
|
| 753 |
-
|
| 754 |
-
with gr.Column():
|
| 755 |
-
delete_output = gr.Textbox(label="Lösch-Ergebnis", lines=4)
|
| 756 |
-
|
| 757 |
-
def delete_file_wrapper(user_uuid, file_path):
|
| 758 |
-
if not user_uuid or not file_path:
|
| 759 |
-
return "❌ Bitte geben Sie UUID und Dateipfad ein!"
|
| 760 |
-
|
| 761 |
-
result = hf_secrets_storage.delete_file(user_uuid, file_path)
|
| 762 |
-
|
| 763 |
-
if result['success']:
|
| 764 |
-
return f"""🗑️ Datei gelöscht!
|
| 765 |
-
|
| 766 |
-
📁 Pfad: {result['file_path']}
|
| 767 |
-
☁️ Aus dem {hf_secrets_storage.storage_mode} entfernt"""
|
| 768 |
-
else:
|
| 769 |
-
return f"❌ Fehler: {result.get('error', 'Unbekannter Fehler')}"
|
| 770 |
-
|
| 771 |
-
delete_btn.click(delete_file_wrapper, inputs=[delete_user_uuid, delete_file_path], outputs=delete_output)
|
| 772 |
-
|
| 773 |
with gr.Tab("📋 Meine Dateien"):
|
| 774 |
with gr.Row():
|
| 775 |
with gr.Column():
|
|
@@ -781,78 +702,100 @@ with gr.Blocks(title="🤗 HF Secrets Filestorage MCP", theme=gr.themes.Soft())
|
|
| 781 |
with gr.Column():
|
| 782 |
list_output = gr.Textbox(label="Datei-Liste", lines=15, max_lines=20)
|
| 783 |
|
| 784 |
-
list_btn.click(
|
| 785 |
|
| 786 |
-
with gr.Tab("ℹ️
|
| 787 |
with gr.Row():
|
| 788 |
with gr.Column():
|
| 789 |
-
gr.Markdown("###
|
| 790 |
-
|
|
|
|
| 791 |
|
| 792 |
with gr.Column():
|
| 793 |
-
|
| 794 |
|
| 795 |
-
def
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
|
|
|
|
|
|
|
|
|
| 800 |
|
| 801 |
-
|
| 802 |
-
|
| 803 |
-
☁️ Speicher-Modus: {hf_secrets_storage.storage_mode}
|
| 804 |
-
🚀 MCP Endpoint: /gradio_api/mcp/sse
|
| 805 |
-
🛡️ Space Secrets: Für sichere Konfiguration
|
| 806 |
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 812 |
|
| 813 |
-
|
| 814 |
|
| 815 |
gr.Markdown("""
|
| 816 |
---
|
| 817 |
-
### 🚀 MCP Server Integration
|
| 818 |
|
| 819 |
-
**
|
| 820 |
|
| 821 |
**Verfügbare MCP Tools:**
|
| 822 |
-
- 📝 `
|
| 823 |
-
- 📤 `
|
| 824 |
-
- 📖 `
|
| 825 |
-
- ✏️ `
|
| 826 |
-
- 🗑️ `
|
| 827 |
-
- 📋 `
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
**Konfiguration für MCP Clients:**
|
| 831 |
```json
|
| 832 |
{
|
| 833 |
"mcpServers": {
|
| 834 |
-
"
|
| 835 |
-
"url": "https://dein-space-name.hf.space/gradio_api/mcp/sse"
|
| 836 |
}
|
| 837 |
}
|
| 838 |
}
|
| 839 |
```
|
| 840 |
|
| 841 |
-
|
| 842 |
-
|
| 843 |
-
Um diesen Space vollständig zu nutzen, konfiguriere diese Secrets:
|
| 844 |
-
- `HF_TOKEN`: Dein Hugging Face Token für Dataset-Zugriff
|
| 845 |
-
- `DATASET_NAME`: Name des Zieldatasets (Format: "organisation/dataset-name")
|
| 846 |
-
- `ORG_NAME`: (Optional) Organisation name
|
| 847 |
-
|
| 848 |
-
Ohne Secrets funktioniert der Space im lokalen Speicher-Modus.
|
| 849 |
""")
|
| 850 |
|
| 851 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 852 |
if __name__ == "__main__":
|
|
|
|
| 853 |
demo.launch(
|
| 854 |
mcp_server=True,
|
| 855 |
server_name="0.0.0.0",
|
| 856 |
server_port=7860,
|
| 857 |
-
share=False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 858 |
)
|
|
|
|
| 11 |
from pathlib import Path
|
| 12 |
import requests
|
| 13 |
from io import BytesIO
|
| 14 |
+
from flask import Flask, request, jsonify
|
| 15 |
+
from flask_cors import CORS
|
| 16 |
+
import threading
|
| 17 |
|
| 18 |
+
class HFUUIDSSEFileStorageMCP:
|
| 19 |
def __init__(self):
|
| 20 |
# Lade Secrets aus Space Environment
|
| 21 |
self.hf_token = os.environ.get("HF_TOKEN", "")
|
|
|
|
| 23 |
self.org_name = os.environ.get("ORG_NAME", "mcp-filestorage")
|
| 24 |
self.db_path = "user_management.db"
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
self.api = HfApi(token=self.hf_token if self.hf_token else None)
|
| 27 |
self.init_database()
|
| 28 |
self.init_storage()
|
| 29 |
+
self.user_sessions = {} # Speichert aktive Benutzer-Sessions
|
| 30 |
+
|
| 31 |
def init_database(self):
|
| 32 |
"""Initialisiert SQLite-Datenbank für Benutzerverwaltung"""
|
| 33 |
conn = sqlite3.connect(self.db_path)
|
| 34 |
cursor = conn.cursor()
|
| 35 |
|
| 36 |
+
# Benutzer-Tabelle mit SSE-Endpunkt
|
| 37 |
cursor.execute('''
|
| 38 |
CREATE TABLE IF NOT EXISTS users (
|
| 39 |
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 40 |
user_uuid TEXT UNIQUE NOT NULL,
|
| 41 |
+
sse_endpoint TEXT UNIQUE,
|
| 42 |
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 43 |
last_access DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
| 44 |
file_count INTEGER DEFAULT 0,
|
| 45 |
is_active BOOLEAN DEFAULT 1,
|
| 46 |
storage_type TEXT DEFAULT 'hf_dataset'
|
|
|
|
| 74 |
"""Initialisiert Speicher (Dataset oder lokal)"""
|
| 75 |
try:
|
| 76 |
if self.dataset_name and self.dataset_name != "mcp-filestorage/default-files":
|
|
|
|
| 77 |
self.dataset = load_dataset(self.dataset_name, split='train', token=self.hf_token)
|
| 78 |
print(f"✅ Dataset geladen: {self.dataset_name}")
|
| 79 |
self.storage_mode = 'dataset'
|
| 80 |
else:
|
|
|
|
| 81 |
self.storage_mode = 'local'
|
| 82 |
self.local_storage_path = Path("user_storage")
|
| 83 |
self.local_storage_path.mkdir(exist_ok=True)
|
|
|
|
| 89 |
self.local_storage_path.mkdir(exist_ok=True)
|
| 90 |
|
| 91 |
def register_user(self) -> Dict[str, Any]:
|
| 92 |
+
"""Registriert neuen Benutzer mit UUID und erstellt SSE-Endpunkt"""
|
| 93 |
user_uuid = str(uuid.uuid4())
|
| 94 |
+
sse_endpoint = f"user-{user_uuid[:8]}" # Kurze UUID für Endpoint
|
| 95 |
|
| 96 |
conn = sqlite3.connect(self.db_path)
|
| 97 |
cursor = conn.cursor()
|
| 98 |
|
| 99 |
try:
|
| 100 |
+
cursor.execute('''
|
| 101 |
+
INSERT INTO users (user_uuid, sse_endpoint) VALUES (?, ?)
|
| 102 |
+
''', (user_uuid, sse_endpoint))
|
| 103 |
+
|
| 104 |
conn.commit()
|
| 105 |
|
| 106 |
# Erstelle Benutzerordner
|
| 107 |
self._create_user_folder(user_uuid)
|
| 108 |
|
| 109 |
+
# Registriere Benutzer-Session
|
| 110 |
+
self.user_sessions[user_uuid] = {
|
| 111 |
+
'sse_endpoint': sse_endpoint,
|
| 112 |
+
'created_at': datetime.now(),
|
| 113 |
+
'last_access': datetime.now()
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
return {
|
| 117 |
'success': True,
|
| 118 |
'user_uuid': user_uuid,
|
| 119 |
+
'sse_endpoint': sse_endpoint,
|
| 120 |
+
'full_sse_url': f"/gradio_api/mcp/user/{user_uuid}/sse",
|
| 121 |
'message': f'Benutzer {user_uuid} registriert'
|
| 122 |
}
|
| 123 |
+
|
| 124 |
except Exception as e:
|
| 125 |
return {
|
| 126 |
'success': False,
|
|
|
|
| 128 |
}
|
| 129 |
finally:
|
| 130 |
conn.close()
|
| 131 |
+
|
| 132 |
+
def get_user_by_uuid(self, user_uuid: str) -> Optional[Dict[str, Any]]:
|
| 133 |
+
"""Holt Benutzer-Informationen anhand der UUID"""
|
| 134 |
+
conn = sqlite3.connect(self.db_path)
|
| 135 |
+
cursor = conn.cursor()
|
| 136 |
+
|
| 137 |
+
try:
|
| 138 |
+
cursor.execute('''
|
| 139 |
+
SELECT user_uuid, sse_endpoint, created_at, file_count
|
| 140 |
+
FROM users WHERE user_uuid = ? AND is_active = 1
|
| 141 |
+
''', (user_uuid,))
|
| 142 |
+
|
| 143 |
+
result = cursor.fetchone()
|
| 144 |
+
if result:
|
| 145 |
+
return {
|
| 146 |
+
'user_uuid': result[0],
|
| 147 |
+
'sse_endpoint': result[1],
|
| 148 |
+
'created_at': result[2],
|
| 149 |
+
'file_count': result[3]
|
| 150 |
+
}
|
| 151 |
+
return None
|
| 152 |
+
|
| 153 |
+
finally:
|
| 154 |
+
conn.close()
|
| 155 |
+
|
| 156 |
def _create_user_folder(self, user_uuid: str):
|
| 157 |
"""Erstellt Benutzerordner je nach Speicher-Modus"""
|
| 158 |
if self.storage_mode == 'dataset':
|
| 159 |
try:
|
|
|
|
| 160 |
folder_path = f"users/{user_uuid}"
|
| 161 |
readme_content = f"# User Folder: {user_uuid}\nCreated: {datetime.now().isoformat()}\n"
|
| 162 |
|
|
|
|
| 177 |
def create_file(self, user_uuid: str, file_path: str, content: bytes,
|
| 178 |
metadata: Optional[Dict] = None) -> Dict[str, Any]:
|
| 179 |
"""Erstellt neue Datei für Benutzer"""
|
| 180 |
+
# Verifiziere Benutzer
|
| 181 |
+
user = self.get_user_by_uuid(user_uuid)
|
| 182 |
+
if not user:
|
| 183 |
+
return {'success': False, 'error': 'Ungültige Benutzer-UUID'}
|
| 184 |
+
|
| 185 |
file_uuid = str(uuid.uuid4())
|
| 186 |
file_hash = hashlib.sha256(content).hexdigest()
|
| 187 |
|
|
|
|
| 189 |
cursor = conn.cursor()
|
| 190 |
|
| 191 |
try:
|
|
|
|
| 192 |
storage_path = f"users/{user_uuid}/{file_path}"
|
| 193 |
|
| 194 |
+
# Speichere Datei
|
| 195 |
if self.storage_mode == 'dataset':
|
| 196 |
try:
|
| 197 |
self.api.upload_file(
|
|
|
|
| 224 |
|
| 225 |
conn.commit()
|
| 226 |
|
| 227 |
+
# Aktualisiere Session
|
| 228 |
+
if user_uuid in self.user_sessions:
|
| 229 |
+
self.user_sessions[user_uuid]['last_access'] = datetime.now()
|
| 230 |
+
|
| 231 |
return {
|
| 232 |
'success': True,
|
| 233 |
'file_uuid': file_uuid,
|
| 234 |
'file_path': file_path,
|
| 235 |
+
'user_uuid': user_uuid
|
|
|
|
| 236 |
}
|
| 237 |
|
| 238 |
except Exception as e:
|
| 239 |
+
return {'success': False, 'error': str(e)}
|
|
|
|
|
|
|
|
|
|
| 240 |
finally:
|
| 241 |
conn.close()
|
| 242 |
+
|
| 243 |
def read_file(self, user_uuid: str, file_path: str) -> Dict[str, Any]:
|
| 244 |
"""Liest Datei eines Benutzers"""
|
| 245 |
+
# Verifiziere Benutzer
|
| 246 |
+
user = self.get_user_by_uuid(user_uuid)
|
| 247 |
+
if not user:
|
| 248 |
+
return {'success': False, 'error': 'Ungültige Benutzer-UUID'}
|
| 249 |
+
|
| 250 |
conn = sqlite3.connect(self.db_path)
|
| 251 |
cursor = conn.cursor()
|
| 252 |
|
|
|
|
| 262 |
|
| 263 |
file_uuid, filename, storage_path = result
|
| 264 |
|
| 265 |
+
# Erstelle Download-URL
|
| 266 |
if self.storage_mode == 'dataset':
|
| 267 |
file_url = f"https://huggingface.co/datasets/{self.dataset_name}/resolve/main/{storage_path}"
|
| 268 |
else:
|
| 269 |
+
# Lokaler Speicher
|
| 270 |
local_file_path = self.local_storage_path / user_uuid / file_path
|
| 271 |
if local_file_path.exists():
|
| 272 |
import base64
|
|
|
|
| 275 |
else:
|
| 276 |
return {'success': False, 'error': 'Datei nicht im lokalen Speicher gefunden'}
|
| 277 |
|
| 278 |
+
# Aktualisiere Session
|
| 279 |
+
if user_uuid in self.user_sessions:
|
| 280 |
+
self.user_sessions[user_uuid]['last_access'] = datetime.now()
|
|
|
|
| 281 |
|
| 282 |
return {
|
| 283 |
'success': True,
|
|
|
|
| 285 |
'file_path': file_path,
|
| 286 |
'file_url': file_url,
|
| 287 |
'filename': filename,
|
| 288 |
+
'user_uuid': user_uuid
|
| 289 |
}
|
| 290 |
|
| 291 |
except Exception as e:
|
| 292 |
return {'success': False, 'error': str(e)}
|
| 293 |
finally:
|
| 294 |
conn.close()
|
| 295 |
+
|
| 296 |
def update_file(self, user_uuid: str, file_path: str, content: bytes) -> Dict[str, Any]:
|
| 297 |
"""Aktualisiert vorhandene Datei"""
|
| 298 |
+
user = self.get_user_by_uuid(user_uuid)
|
| 299 |
+
if not user:
|
| 300 |
+
return {'success': False, 'error': 'Ungültige Benutzer-UUID'}
|
| 301 |
+
|
| 302 |
conn = sqlite3.connect(self.db_path)
|
| 303 |
cursor = conn.cursor()
|
| 304 |
|
|
|
|
| 344 |
|
| 345 |
conn.commit()
|
| 346 |
|
| 347 |
+
# Aktualisiere Session
|
| 348 |
+
if user_uuid in self.user_sessions:
|
| 349 |
+
self.user_sessions[user_uuid]['last_access'] = datetime.now()
|
| 350 |
+
|
| 351 |
return {
|
| 352 |
'success': True,
|
| 353 |
'file_uuid': file_uuid,
|
| 354 |
'version': current_version + 1,
|
| 355 |
+
'file_path': file_path,
|
| 356 |
+
'user_uuid': user_uuid
|
| 357 |
}
|
| 358 |
|
| 359 |
except Exception as e:
|
| 360 |
return {'success': False, 'error': str(e)}
|
| 361 |
finally:
|
| 362 |
conn.close()
|
| 363 |
+
|
| 364 |
def delete_file(self, user_uuid: str, file_path: str) -> Dict[str, Any]:
|
| 365 |
"""Löscht Datei (Soft Delete)"""
|
| 366 |
+
user = self.get_user_by_uuid(user_uuid)
|
| 367 |
+
if not user:
|
| 368 |
+
return {'success': False, 'error': 'Ungültige Benutzer-UUID'}
|
| 369 |
+
|
| 370 |
conn = sqlite3.connect(self.db_path)
|
| 371 |
cursor = conn.cursor()
|
| 372 |
|
|
|
|
| 388 |
|
| 389 |
conn.commit()
|
| 390 |
|
| 391 |
+
# Aktualisiere Session
|
| 392 |
+
if user_uuid in self.user_sessions:
|
| 393 |
+
self.user_sessions[user_uuid]['last_access'] = datetime.now()
|
| 394 |
+
|
| 395 |
return {
|
| 396 |
'success': True,
|
| 397 |
'message': f'Datei {file_path} gelöscht',
|
| 398 |
+
'file_path': file_path,
|
| 399 |
+
'user_uuid': user_uuid
|
| 400 |
}
|
| 401 |
|
| 402 |
except Exception as e:
|
| 403 |
return {'success': False, 'error': str(e)}
|
| 404 |
finally:
|
| 405 |
conn.close()
|
| 406 |
+
|
| 407 |
def list_files(self, user_uuid: str, folder_path: str = "") -> List[Dict[str, Any]]:
|
| 408 |
"""Listet alle Dateien eines Benutzers auf"""
|
| 409 |
+
user = self.get_user_by_uuid(user_uuid)
|
| 410 |
+
if not user:
|
| 411 |
+
return []
|
| 412 |
+
|
| 413 |
conn = sqlite3.connect(self.db_path)
|
| 414 |
cursor = conn.cursor()
|
| 415 |
|
|
|
|
| 443 |
'file_size': row[6]
|
| 444 |
})
|
| 445 |
|
| 446 |
+
# Aktualisiere Session
|
| 447 |
+
if user_uuid in self.user_sessions:
|
| 448 |
+
self.user_sessions[user_uuid]['last_access'] = datetime.now()
|
| 449 |
|
| 450 |
+
return files
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 451 |
|
| 452 |
finally:
|
| 453 |
conn.close()
|
| 454 |
|
| 455 |
+
def get_user_sse_endpoint(self, user_uuid: str) -> Optional[str]:
|
| 456 |
+
"""Gibt den SSE-Endpunkt für einen Benutzer zurück"""
|
| 457 |
+
user = self.get_user_by_uuid(user_uuid)
|
| 458 |
+
if user:
|
| 459 |
+
return f"/gradio_api/mcp/user/{user_uuid}/sse"
|
| 460 |
+
return None
|
| 461 |
+
|
| 462 |
# Globale Instanz
|
| 463 |
+
hf_uuid_sse_storage = HFUUIDSSEFileStorageMCP()
|
| 464 |
|
| 465 |
+
# ===== BENUTZERSPEZIFISCHE MCP TOOLS =====
|
| 466 |
|
| 467 |
@gr.mcp.tool()
|
| 468 |
+
def register_user_uuid_sse() -> str:
|
| 469 |
"""
|
| 470 |
+
Registriert einen neuen Benutzer und erstellt einen persönlichen SSE-Endpunkt.
|
| 471 |
|
| 472 |
Returns:
|
| 473 |
+
Benutzer-UUID und persönlicher SSE-Endpunkt
|
| 474 |
"""
|
| 475 |
+
result = hf_uuid_sse_storage.register_user()
|
| 476 |
|
| 477 |
if result['success']:
|
| 478 |
return f"""✅ Benutzer erfolgreich registriert!
|
| 479 |
+
|
| 480 |
🆔 Ihre UUID: {result['user_uuid']}
|
| 481 |
+
🌐 Ihr persönlicher SSE-Endpunkt: {result['full_sse_url']}
|
| 482 |
+
📁 Ihr Ordner: users/{result['user_uuid']}/
|
| 483 |
+
🔐 Speichern Sie diese UUID! Sie ist Ihr persönlicher Zugangsschlüssel!
|
| 484 |
+
🚀 Verwenden Sie Ihren SSE-Endpunkt für MCP-Integration!"""
|
| 485 |
else:
|
| 486 |
return f"❌ Fehler: {result.get('error', 'Unbekannter Fehler')}"
|
| 487 |
|
| 488 |
@gr.mcp.tool()
|
| 489 |
+
def create_user_file_uuid(user_uuid: str, file_path: str, content: str,
|
| 490 |
+
metadata: Optional[str] = None) -> str:
|
| 491 |
"""
|
| 492 |
Erstellt eine neue Datei für einen Benutzer.
|
| 493 |
|
|
|
|
| 503 |
content_bytes = content.encode('utf-8')
|
| 504 |
meta_dict = json.loads(metadata) if metadata else None
|
| 505 |
|
| 506 |
+
result = hf_uuid_sse_storage.create_file(user_uuid, file_path, content_bytes, meta_dict)
|
| 507 |
|
| 508 |
if result['success']:
|
| 509 |
return f"""✅ Datei erfolgreich erstellt!
|
| 510 |
📁 Datei-UUID: {result['file_uuid']}
|
| 511 |
📄 Pfad: {result['file_path']}
|
| 512 |
+
👤 Benutzer: {result['user_uuid']}
|
| 513 |
+
🌐 Ihr SSE-Endpunkt: /gradio_api/mcp/user/{user_uuid}/sse"""
|
| 514 |
else:
|
| 515 |
return f"❌ Fehler: {result.get('error', 'Unbekannter Fehler')}"
|
| 516 |
|
| 517 |
@gr.mcp.tool()
|
| 518 |
+
def read_user_file_uuid(user_uuid: str, file_path: str) -> str:
|
| 519 |
"""
|
| 520 |
Liest eine Datei eines Benutzers.
|
| 521 |
|
|
|
|
| 526 |
Returns:
|
| 527 |
Datei-Details und Zugriffs-URL
|
| 528 |
"""
|
| 529 |
+
result = hf_uuid_sse_storage.read_file(user_uuid, file_path)
|
| 530 |
|
| 531 |
if result['success']:
|
| 532 |
return f"""📄 Datei erfolgreich gelesen!
|
| 533 |
🆔 Datei-UUID: {result['file_uuid']}
|
| 534 |
🔗 Direkt-Link: {result['file_url']}
|
| 535 |
📁 Dateiname: {result['filename']}
|
| 536 |
+
👤 Benutzer: {result['user_uuid']}
|
| 537 |
+
🌐 Ihr SSE-Endpunkt: /gradio_api/mcp/user/{user_uuid}/sse"""
|
| 538 |
else:
|
| 539 |
return f"❌ Fehler: {result.get('error', 'Datei nicht gefunden')}"
|
| 540 |
|
| 541 |
@gr.mcp.tool()
|
| 542 |
+
def update_user_file_uuid(user_uuid: str, file_path: str, content: str) -> str:
|
| 543 |
"""
|
| 544 |
Aktualisiert eine vorhandene Datei.
|
| 545 |
|
|
|
|
| 552 |
Erfolgsmeldung mit Versions-Info
|
| 553 |
"""
|
| 554 |
content_bytes = content.encode('utf-8')
|
| 555 |
+
result = hf_uuid_sse_storage.update_file(user_uuid, file_path, content_bytes)
|
| 556 |
|
| 557 |
if result['success']:
|
| 558 |
return f"""✅ Datei erfolgreich aktualisiert!
|
| 559 |
🆔 Datei-UUID: {result['file_uuid']}
|
| 560 |
🔢 Neue Version: {result['version']}
|
| 561 |
📁 Pfad: {result['file_path']}
|
| 562 |
+
👤 Benutzer: {result['user_uuid']}"""
|
| 563 |
else:
|
| 564 |
return f"❌ Fehler: {result.get('error', 'Unbekannter Fehler')}"
|
| 565 |
|
| 566 |
@gr.mcp.tool()
|
| 567 |
+
def delete_user_file_uuid(user_uuid: str, file_path: str) -> str:
|
| 568 |
"""
|
| 569 |
Löscht eine Datei eines Benutzers.
|
| 570 |
|
|
|
|
| 575 |
Returns:
|
| 576 |
Erfolgsmeldung
|
| 577 |
"""
|
| 578 |
+
result = hf_uuid_sse_storage.delete_file(user_uuid, file_path)
|
| 579 |
|
| 580 |
if result['success']:
|
| 581 |
return f"""🗑️ Datei erfolgreich gelöscht!
|
| 582 |
📁 Pfad: {result['file_path']}
|
| 583 |
+
👤 Benutzer: {result['user_uuid']}
|
| 584 |
+
🌐 Ihr SSE-Endpunkt bleibt: /gradio_api/mcp/user/{user_uuid}/sse"""
|
| 585 |
else:
|
| 586 |
return f"❌ Fehler: {result.get('error', 'Unbekannter Fehler')}"
|
| 587 |
|
| 588 |
@gr.mcp.tool()
|
| 589 |
+
def list_user_files_uuid(user_uuid: str, folder_path: str = "") -> str:
|
| 590 |
"""
|
| 591 |
Listet alle Dateien eines Benutzers auf.
|
| 592 |
|
|
|
|
| 597 |
Returns:
|
| 598 |
Liste aller Dateien des Benutzers
|
| 599 |
"""
|
| 600 |
+
files = hf_uuid_sse_storage.list_files(user_uuid, folder_path)
|
| 601 |
|
| 602 |
if not files:
|
| 603 |
return "📁 Keine Dateien gefunden!"
|
| 604 |
|
| 605 |
output = f"📊 Gefunden: {len(files)} Dateien für Benutzer {user_uuid}\n"
|
| 606 |
+
output += f"🌐 Ihr SSE-Endpunkt: /gradio_api/mcp/user/{user_uuid}/sse\n\n"
|
| 607 |
|
| 608 |
for file in files:
|
| 609 |
output += f"""📄 {file['filename']} (Version {file['version']})
|
|
|
|
| 617 |
|
| 618 |
return output
|
| 619 |
|
| 620 |
+
# ===== GRADIO INTERFACE MIT UUID-SSE INTEGRATION =====
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 621 |
|
| 622 |
+
# Benutzerdefinierte MCP-Routen erstellen
|
| 623 |
+
def create_user_mcp_server(user_uuid: str):
|
| 624 |
+
"""Erstellt einen benutzerspezifischen MCP-Server"""
|
| 625 |
+
|
| 626 |
+
@gr.mcp.tool()
|
| 627 |
+
def user_create_file(file_path: str, content: str, metadata: Optional[str] = None) -> str:
|
| 628 |
+
"""Erstelle Datei für aktuellen Benutzer"""
|
| 629 |
+
return create_user_file_uuid(user_uuid, file_path, content, metadata)
|
| 630 |
+
|
| 631 |
+
@gr.mcp.tool()
|
| 632 |
+
def user_read_file(file_path: str) -> str:
|
| 633 |
+
"""Lese Datei für aktuellen Benutzer"""
|
| 634 |
+
return read_user_file_uuid(user_uuid, file_path)
|
| 635 |
+
|
| 636 |
+
@gr.mcp.tool()
|
| 637 |
+
def user_update_file(file_path: str, content: str) -> str:
|
| 638 |
+
"""Aktualisiere Datei für aktuellen Benutzer"""
|
| 639 |
+
return update_user_file_uuid(user_uuid, file_path, content)
|
| 640 |
+
|
| 641 |
+
@gr.mcp.tool()
|
| 642 |
+
def user_delete_file(file_path: str) -> str:
|
| 643 |
+
"""Lösche Datei für aktuellen Benutzer"""
|
| 644 |
+
return delete_user_file_uuid(user_uuid, file_path)
|
| 645 |
+
|
| 646 |
+
@gr.mcp.tool()
|
| 647 |
+
def user_list_files(folder_path: str = "") -> str:
|
| 648 |
+
"""Liste Dateien für aktuellen Benutzer auf"""
|
| 649 |
+
return list_user_files_uuid(user_uuid, folder_path)
|
| 650 |
+
|
| 651 |
+
@gr.mcp.tool()
|
| 652 |
+
def user_get_stats() -> str:
|
| 653 |
+
"""Hole Statistiken für aktuellen Benutzer"""
|
| 654 |
+
return get_user_stats_uuid(user_uuid)
|
| 655 |
|
| 656 |
+
# Haupt-Gradio-Interface
|
| 657 |
+
with gr.Blocks(title="🤗 HF UUID-SSE Filestorage MCP", theme=gr.themes.Soft()) as demo:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 658 |
gr.Markdown("""
|
| 659 |
# 🗃️ Hugging Face Dataset Filestorage MCP Server
|
| 660 |
+
### 🔗 UUID-basierte persönliche SSE-Endpunkte
|
| 661 |
|
| 662 |
+
Jeder Benutzer bekommt einen eigenen SSE-Endpunkt mit seiner UUID - z.B. `/gradio_api/mcp/user/123e4567-e89b-12d3-a456-426614174000/sse`
|
|
|
|
| 663 |
""")
|
| 664 |
|
| 665 |
with gr.Tab("📝 Registrieren"):
|
| 666 |
with gr.Row():
|
| 667 |
with gr.Column():
|
| 668 |
+
gr.Markdown("### 🎯 Neuen Benutzer mit persönlichem SSE-Endpunkt erstellen")
|
| 669 |
register_btn = gr.Button("🎯 Neuen Benutzer erstellen", variant="primary", size="lg")
|
| 670 |
with gr.Column():
|
| 671 |
+
register_output = gr.Textbox(label="Registrierungsergebnis", lines=8, max_lines=10)
|
| 672 |
|
| 673 |
+
register_btn.click(register_user_uuid_sse, outputs=register_output)
|
| 674 |
|
| 675 |
with gr.Tab("📤 Datei erstellen"):
|
| 676 |
with gr.Row():
|
|
|
|
| 686 |
create_output = gr.Textbox(label="Ergebnis", lines=8, max_lines=10)
|
| 687 |
|
| 688 |
create_btn.click(
|
| 689 |
+
create_user_file_uuid,
|
| 690 |
inputs=[create_user_uuid, create_file_path, create_content, create_metadata],
|
| 691 |
outputs=create_output
|
| 692 |
)
|
| 693 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 694 |
with gr.Tab("📋 Meine Dateien"):
|
| 695 |
with gr.Row():
|
| 696 |
with gr.Column():
|
|
|
|
| 702 |
with gr.Column():
|
| 703 |
list_output = gr.Textbox(label="Datei-Liste", lines=15, max_lines=20)
|
| 704 |
|
| 705 |
+
list_btn.click(list_user_files_uuid, inputs=[list_user_uuid, list_folder], outputs=list_output)
|
| 706 |
|
| 707 |
+
with gr.Tab("ℹ️ Mein SSE-Endpunkt"):
|
| 708 |
with gr.Row():
|
| 709 |
with gr.Column():
|
| 710 |
+
gr.Markdown("### 🔗 Meinen persönlichen SSE-Endpunkt finden")
|
| 711 |
+
sse_user_uuid = gr.Textbox(label="🆔 Ihre UUID", placeholder="Ihre UUID eingeben")
|
| 712 |
+
sse_btn = gr.Button("🔗 SSE-Endpunkt anzeigen", variant="secondary")
|
| 713 |
|
| 714 |
with gr.Column():
|
| 715 |
+
sse_output = gr.Textbox(label="SSE-Endpunkt-Details", lines=8)
|
| 716 |
|
| 717 |
+
def get_sse_endpoint(user_uuid):
|
| 718 |
+
if not user_uuid:
|
| 719 |
+
return "❌ Bitte geben Sie Ihre UUID ein!"
|
| 720 |
+
|
| 721 |
+
endpoint = hf_uuid_sse_storage.get_user_sse_endpoint(user_uuid)
|
| 722 |
+
if endpoint:
|
| 723 |
+
full_url = f"https://dein-space-name.hf.space{endpoint}"
|
| 724 |
+
return f"""🔗 Ihr persönlicher SSE-Endpunkt:
|
| 725 |
|
| 726 |
+
🌐 Komplett: {full_url}
|
| 727 |
+
📍 Kurz: {endpoint}
|
|
|
|
|
|
|
|
|
|
| 728 |
|
| 729 |
+
📝 MCP-Konfiguration:
|
| 730 |
+
```json
|
| 731 |
+
{{
|
| 732 |
+
"mcpServers": {{
|
| 733 |
+
"mein-filestorage-{user_uuid[:8]}": {{
|
| 734 |
+
"url": "{full_url}"
|
| 735 |
+
}}
|
| 736 |
+
}}
|
| 737 |
+
}}
|
| 738 |
+
```"""
|
| 739 |
+
else:
|
| 740 |
+
return "❌ Kein SSE-Endpunkt für diese UUID gefunden. Bitte registrieren Sie sich zuerst."
|
| 741 |
|
| 742 |
+
sse_btn.click(get_sse_endpoint, inputs=[sse_user_uuid], outputs=sse_output)
|
| 743 |
|
| 744 |
gr.Markdown("""
|
| 745 |
---
|
| 746 |
+
### 🚀 UUID-basierte MCP Server Integration
|
| 747 |
|
| 748 |
+
**Persönliche SSE-Endpunkte:** `/gradio_api/mcp/user/{Ihre-UUID}/sse`
|
| 749 |
|
| 750 |
**Verfügbare MCP Tools:**
|
| 751 |
+
- 📝 `register_user_uuid_sse` - Neuen Benutzer mit SSE-Endpunkt registrieren
|
| 752 |
+
- 📤 `create_user_file_uuid` - Datei für Benutzer erstellen
|
| 753 |
+
- 📖 `read_user_file_uuid` - Datei lesen und Download-Link erhalten
|
| 754 |
+
- ✏️ `update_user_file_uuid` - Datei-Inhalt aktualisieren
|
| 755 |
+
- 🗑️ `delete_user_file_uuid` - Datei löschen
|
| 756 |
+
- 📋 `list_user_files_uuid` - Alle Dateien eines Benutzers auflisten
|
| 757 |
+
|
| 758 |
+
**Beispiel MCP Konfiguration für Benutzer:**
|
|
|
|
| 759 |
```json
|
| 760 |
{
|
| 761 |
"mcpServers": {
|
| 762 |
+
"mein-filestorage-123e4567": {
|
| 763 |
+
"url": "https://dein-space-name.hf.space/gradio_api/mcp/user/123e4567-e89b-12d3-a456-426614174000/sse"
|
| 764 |
}
|
| 765 |
}
|
| 766 |
}
|
| 767 |
```
|
| 768 |
|
| 769 |
+
🔐 **Jeder Benutzer hat seinen eigenen MCP-Server-Endpunkt!**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 770 |
""")
|
| 771 |
|
| 772 |
+
# Benutzerdefinierte SSE-Routen handler
|
| 773 |
+
def user_sse_handler(user_uuid: str):
|
| 774 |
+
"""Handler für benutzerspezifische SSE-Endpunkte"""
|
| 775 |
+
user = hf_uuid_sse_storage.get_user_by_uuid(user_uuid)
|
| 776 |
+
if not user:
|
| 777 |
+
return {"error": "Ungültige Benutzer-UUID"}, 404
|
| 778 |
+
|
| 779 |
+
# Erstelle benutzerspezifischen MCP-Server
|
| 780 |
+
create_user_mcp_server(user_uuid)
|
| 781 |
+
|
| 782 |
+
# Hier würde die SSE-Verbindung etabliert
|
| 783 |
+
return {"status": "connected", "user_uuid": user_uuid, "endpoint": f"/user/{user_uuid}/sse"}
|
| 784 |
+
|
| 785 |
+
# MCP Server mit benutzerdefinierten Routen
|
| 786 |
if __name__ == "__main__":
|
| 787 |
+
# Standard-MCP-Server für allgemeine Funktionen
|
| 788 |
demo.launch(
|
| 789 |
mcp_server=True,
|
| 790 |
server_name="0.0.0.0",
|
| 791 |
server_port=7860,
|
| 792 |
+
share=False,
|
| 793 |
+
# Benutzerdefinierte Routen werden über Gradio's MCP-Integration gehandhabt
|
| 794 |
+
routes=[
|
| 795 |
+
{
|
| 796 |
+
"path": "/gradio_api/mcp/user/{user_uuid}/sse",
|
| 797 |
+
"method": "GET",
|
| 798 |
+
"handler": user_sse_handler
|
| 799 |
+
}
|
| 800 |
+
]
|
| 801 |
)
|