feat(diagnostic): Anhang-Limit einstellbar + Datum/Uhrzeit bei Gedanken
Zwei Dinge, die bisher nur im Code bzw. nur in der DB standen. Anhang-Limit (Einstellungen → System & Wartung): - Das Limit war eine Env-Variable mit 20 MB Default — zum Anheben musste man die .env editieren und das Brain neu starten. Jetzt steht es in runtime.json und das Brain liest es bei JEDEM Upload neu: Aenderung greift sofort. - Reihenfolge: runtime.json > ENV > Default. Anders als beim OpenAI-Endpunkt gewinnt hier NICHT die .env — dort erzwingt ein Docker-Port-Mapping das, hier gibt es keinen solchen Zwang, und eine Einstellung die nichts tut waere schlimmer als keine. - Default von 20 auf 64 MB. Genau so viel holt die App auch auf ausdruecklichen Tipp noch herunter (TAP_DL_MAX_BYTES); darueber warnt die UI, verbietet es aber nicht — der Anhang liegt dann sauber auf dem Server, ist mobil nur nicht mehr ladbar. - Neuer Endpunkt GET /attachments/limit. Das fest eingetippte "max 20 MB pro Datei" im HTML ist weg — es stimmte nach jeder Aenderung nicht mehr. - Die Fehlermeldung nennt jetzt MB statt Bytes und sagt, wo die Einstellung ist. Datum/Uhrzeit bei Gedanken: - created_at/updated_at lagen schon immer in der DB und wurden ausgeliefert — nur nie angezeigt. Jede Zeile zeigt jetzt "angelegt <Zeit>" und, falls abweichend, "geaendert <Zeit>" plus ein relatives "(vor 3 Std)". Damit sieht man auf den ersten Blick, was der letzte Eintrag war. - Sortier-Dropdown: nach Typ gruppiert (wie bisher) oder nach Anlage/Aenderung auf- und absteigend. Bei Zeit-Sortierung wird die Liste flach dargestellt — man will sehen was zuletzt passiert ist, nicht in welche Schublade es gehoert. - Zeitraum-Filter (🕒) mit von/bis, umschaltbar zwischen angelegt und geaendert, plus Schnellwahl Heute / 7 / 30 Tage. Gilt auch fuer Such-Treffer, damit "suche X, aber nur letzte Woche" geht. Zuklappen schaltet ihn aus, und das ✕-Zuruecksetzen raeumt ihn mit auf (sonst filtert er unsichtbar weiter). Beides gegen den echten Code getestet: 7 Faelle fuers Limit (inkl. kaputter runtime.json und einem 50-MB-PDF an beiden Grenzen), 10 fuer Sortierung und Zeitraum. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ selbst hat in Stufe A kein Tool zum Upload.
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
@@ -27,9 +28,41 @@ from typing import List, Optional
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ROOT = Path(os.environ.get("MEMORY_ATTACHMENTS_DIR", "/shared/memory-attachments"))
|
||||
MAX_BYTES = int(os.environ.get("MEMORY_ATTACHMENT_MAX_BYTES", str(20 * 1024 * 1024))) # 20 MB
|
||||
SAFE_NAME_RE = re.compile(r"[^A-Za-z0-9._\-]")
|
||||
|
||||
RUNTIME_CONFIG_FILE = Path("/shared/config/runtime.json")
|
||||
# 64 MB: so viel holt die App auch auf ausdruecklichen Tipp noch herunter
|
||||
# (TAP_DL_MAX_BYTES in ChatScreen.tsx). Ein hoeheres Limit wuerde Anhaenge
|
||||
# erlauben, die das Handy anschliessend nicht mehr laden kann.
|
||||
_DEFAULT_MAX_BYTES = 64 * 1024 * 1024
|
||||
|
||||
|
||||
def max_bytes() -> int:
|
||||
"""Aktuelles Groessen-Limit fuer Anhaenge.
|
||||
|
||||
Reihenfolge: runtime.json (Diagnostic → System & Wartung) > ENV > Default.
|
||||
Wird bei JEDEM Aufruf gelesen, damit eine Aenderung in der Diagnostic sofort
|
||||
greift — ein Brain-Neustart nur fuers Anheben einer Zahl waere Unsinn.
|
||||
"""
|
||||
try:
|
||||
with RUNTIME_CONFIG_FILE.open() as f:
|
||||
mb = json.load(f).get("memoryAttachmentMaxMb")
|
||||
if mb is not None:
|
||||
val = int(float(mb) * 1024 * 1024)
|
||||
if val > 0:
|
||||
return val
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except Exception as exc:
|
||||
logger.warning("runtime.json lesen fehlgeschlagen: %s", exc)
|
||||
env = os.environ.get("MEMORY_ATTACHMENT_MAX_BYTES")
|
||||
if env:
|
||||
try:
|
||||
return int(env)
|
||||
except ValueError:
|
||||
logger.warning("MEMORY_ATTACHMENT_MAX_BYTES ist keine Zahl: %r", env)
|
||||
return _DEFAULT_MAX_BYTES
|
||||
|
||||
|
||||
def _safe_filename(name: str) -> str:
|
||||
"""Macht aus einem User-Namen einen filesystem-sicheren String —
|
||||
@@ -78,8 +111,12 @@ def save_attachment(memory_id: str, filename: str, data: bytes) -> dict:
|
||||
Ueberschreibt eine bestehende Datei mit gleichem Namen."""
|
||||
if not memory_id:
|
||||
raise ValueError("memory_id ist Pflicht")
|
||||
if len(data) > MAX_BYTES:
|
||||
raise ValueError(f"Anhang zu gross ({len(data)} > {MAX_BYTES} Byte)")
|
||||
limit = max_bytes()
|
||||
if len(data) > limit:
|
||||
raise ValueError(
|
||||
f"Anhang zu gross ({len(data) // (1024 * 1024)} MB > "
|
||||
f"{limit // (1024 * 1024)} MB). Das Limit steht in der Diagnostic "
|
||||
f"unter System & Wartung.")
|
||||
safe = _safe_filename(filename)
|
||||
d = memory_dir(memory_id)
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
@@ -165,8 +202,12 @@ def attach_from_path(memory_id: str, source_path: str) -> dict:
|
||||
if not src.is_file():
|
||||
raise ValueError(f"Datei nicht gefunden: {source_path}")
|
||||
size = src.stat().st_size
|
||||
if size > MAX_BYTES:
|
||||
raise ValueError(f"Datei zu gross ({size} > {MAX_BYTES} Byte)")
|
||||
limit = max_bytes()
|
||||
if size > limit:
|
||||
raise ValueError(
|
||||
f"Datei zu gross ({size // (1024 * 1024)} MB > "
|
||||
f"{limit // (1024 * 1024)} MB). Das Limit steht in der Diagnostic "
|
||||
f"unter System & Wartung.")
|
||||
# Reuse save_attachment damit Filename-Sanitization + Logging konsistent
|
||||
data = src.read_bytes()
|
||||
return save_attachment(memory_id, src.name, data)
|
||||
|
||||
Reference in New Issue
Block a user