Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 200 additions & 0 deletions SMOKE-CHECKS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# Smoke-Checks: Web-Viewer und Desktop-GUI

Dieses Dokument beschreibt die Smoke-Checks für die beiden interaktiven
Frontends von KnowledgeDigest: den Web-Viewer und die Desktop-GUI.
Die Core-Unit-Tests (chunker, schema, config, utils) sind in `tests/test_core.py`
abgedeckt und laufen via `python -m pytest tests -q`.

---

## 1. Web-Viewer-Smoke-Check

### Zweck

Sicherstellen, dass der Web-Viewer korrekt startet, einen HTTP-Server auf
dem konfigurierten Port bindet und eine gültige HTML-Seite (Dashboard)
ausliefert — ausschließlich mit Python-Stdlib, ohne externe Abhängigkeiten.

- Modul: `web_viewer.py`
- Einstiegspunkt: `launch_web()` / `python -m KnowledgeDigest --web`
- Abhängigkeiten: **Python-Stdlib only** (kein Flask, kein Django)

### Voraussetzungen

```bash
python --version # Python 3.10+ erforderlich
pip install -e . # Paket installieren (kein weiteres pip-install nötig)
```

### Manueller Smoke-Check (interaktiv)

```bash
# Windows (PowerShell oder Git Bash):
PYTHONIOENCODING=utf-8 python -m KnowledgeDigest --web --no-browser
```

Option `--no-browser` verhindert das automatische Öffnen des Browsers.
Alternativer Port (falls 8787 belegt):

```bash
PYTHONIOENCODING=utf-8 python -m KnowledgeDigest --web --port 9000 --no-browser
```

Eigene Datenbankdatei angeben:

```bash
PYTHONIOENCODING=utf-8 python -m KnowledgeDigest --web --db pfad/zur/knowledge.db --no-browser
```

### Erwartete Konsolenausgabe

```
KnowledgeDigest Web-Viewer: http://127.0.0.1:8787
DB: data/knowledge.db
```

Der Prozess blockiert danach (HTTP-Server lauscht). Beenden mit **Ctrl+C**:

```
Viewer beendet.
```

### Erwartetes HTTP-Ergebnis

`GET http://127.0.0.1:8787/` liefert HTTP 200 mit HTML-Dashboard.

Prüfung via PowerShell (Windows, während der Server läuft):

```powershell
(Invoke-WebRequest -Uri http://127.0.0.1:8787/ -UseBasicParsing).StatusCode
# Erwartet: 200
```

Prüfung via Git Bash / curl (falls verfügbar):

```bash
curl -s -w "%{http_code}" http://127.0.0.1:8787/ -o nul
# Erwartet: 200
```

### Headless-Import-Check (CI-geeignet, ohne laufenden Server)

Prüft nur, ob das Modul fehlerfrei importierbar ist — startet keinen Server:

```bash
PYTHONIOENCODING=utf-8 python -c "from KnowledgeDigest.web_viewer import launch_web; print('OK')"
# Erwartet: OK
```

Dieser Check ist bereits indirekt im CI enthalten (`python -m compileall -q .`
und `python -m KnowledgeDigest --help`), testet aber keinen laufenden Server.

### Typische Fehler

| Fehler | Ursache | Lösung |
|--------|---------|--------|
| `OSError: [Errno 10048] / Address already in use` | Port 8787 belegt | `--port 9000` (oder freien Port wählen) |
| `sqlite3.OperationalError: no such table` | DB-Schema fehlt | `python -m KnowledgeDigest status` ausführen (initialisiert Schema) |
| `ModuleNotFoundError: No module named 'KnowledgeDigest'` | Paket nicht installiert | `pip install -e .` ausführen |
| `FileNotFoundError` (DB-Pfad) | Falsche DB-Konfiguration | `--db pfad/knowledge.db` explizit angeben |
| Leere Dokumentenliste im Browser | Normale Erstnutzung | `python -m KnowledgeDigest scan <verzeichnis>` ausführen |

---

## 2. Desktop-GUI-Smoke-Check

### Zweck

Sicherstellen, dass die PySide6-Anwendung korrekt startet, das Hauptfenster
öffnet und die 3-Panel-Ansicht (Verzeichnisse | Dokumente | Vorschau) mit
Dark-Theme und Toolbar anzeigt.

- Modul: `gui/app.py` (Einstieg: `launch_gui()`)
- Einstiegspunkt: `python -m KnowledgeDigest --gui`
- Abhängigkeit: **PySide6** (nicht Stdlib — muss installiert sein)
- Voraussetzung: **Grafisches Display** muss verfügbar sein (kein reines Headless-CI)

### Voraussetzungen

```bash
python --version # Python 3.10+ erforderlich
pip install -e . # Paket + Kern-Abhängigkeiten installieren
pip install PySide6 # GUI-Abhängigkeit (LGPL)
```

### Manueller Smoke-Check (interaktiv)

```bash
# Windows (PowerShell oder Git Bash):
PYTHONIOENCODING=utf-8 python -m KnowledgeDigest --gui
```

Alternativ über das mitgelieferte Windows-Startskript:

```bat
start.bat
```

(`start.bat` liegt im Elternordner des Projektverzeichnisses und ruft
`launcher.py` auf, der seinerseits die GUI startet.)

### Erwartetes Verhalten

Nach dem Start (keine Konsolenausgabe erwartet):

1. Anwendungsfenster öffnet sich (Mindestgröße 900 × 600 px).
2. Dark-Theme ist aktiv (Hintergrundfarbe `#0d1117`).
3. Toolbar sichtbar mit den Aktionen „+ Verzeichnis" und „Scannen", einem Suchfeld
(Platzhaltertext „Suche (FTS5)...") sowie den Aktionen „Web-Viewer" und „Einstellungen".
4. 3-Panel-Splitter: Links Verzeichnisliste, Mitte Dokumententabelle, Rechts Vorschau.
5. Statusleiste am unteren Rand zeigt eine Bereitschaftsmeldung.
6. Schließen des Fensters beendet die Anwendung sauber (Exit-Code 0).

### Headless-Import-Check (CI-geeignet, ohne Display)

Prüft den Import aller GUI-Module ohne einen QApplication-Start:

```bash
PYTHONIOENCODING=utf-8 python -c "from KnowledgeDigest.gui.app import launch_gui; print('OK')"
# Erwartet: OK (schlägt fehl, wenn PySide6 nicht installiert ist)
```

**Hinweis:** Der vollständige GUI-Start (`launch_gui()`) erfordert einen
aktiven Display-Server. In headless CI-Umgebungen (z. B. GitHub Actions
Ubuntu-Runner ohne Virtual Display) schlägt `python -m KnowledgeDigest --gui`
mit einem Qt-Fehler fehl — das ist kein Anwendungsfehler:

```
qt.qpa.xcb: could not connect to display
```

Der CI-Workflow (`tests.yml`) enthält deshalb keinen vollständigen GUI-Start.
Der Import-Check oben ist der höchste CI-taugliche Prüfpunkt ohne Xvfb-Setup.

### Typische Fehler

| Fehler | Ursache | Lösung |
|--------|---------|--------|
| `ModuleNotFoundError: No module named 'PySide6'` | PySide6 nicht installiert | `pip install PySide6` |
| `qt.qpa.xcb: could not connect to display` | Kein grafisches Display (Linux CI) | Nur im interaktiven Kontext ausführen; kein CI-Fehler |
| `qt.qpa.plugin: Could not load the Qt platform plugin "xcb"` | Fehlende Qt-Plattform-Bibliotheken (Linux) | PySide6 neu installieren oder `libxcb`-Pakete via apt prüfen |
| `FileNotFoundError: KnowledgeDigest.ico` | App-Icon nicht vorhanden | Kein funktionaler Fehler — GUI startet trotzdem ohne Icon |
| `ModuleNotFoundError: No module named 'KnowledgeDigest'` | Paket nicht installiert | `pip install -e .` ausführen |
| `ModuleNotFoundError: No module named 'fitz'` | PyMuPDF fehlt (PDF-Vorschau) | `pip install PyMuPDF` (optional, nur für PDF-Vorschau im Preview-Panel) |
| Leere Dokumentenliste nach Start | Normale Erstnutzung | Verzeichnis über Toolbar-Schaltfläche „+" hinzufügen und scannen |

---

## Abgrenzung: Was dieser Check NICHT testet

- **Core-Logik** (Chunker, Schema, Config, Utils): Abgedeckt durch `tests/test_core.py`
(`python -m pytest tests -q`).
- **LLM-Summarization** (Haiku/Flash): Erfordert API-Schlüssel — kein Teil des Smoke-Checks.
- **BACH-Integration**: Optionales Modul, deaktiviert per Default (`bach_enabled: false`).
- **Vollständige End-to-End-Tests** (Ingest → Suche → Ergebnis): Nicht enthalten;
diese würden eigene Integrationstests erfordern.

---

*Erstellt 2026-06-28. Basis: `web_viewer.py` (launch\_web), `gui/app.py` (launch\_gui),
`__main__.py`, `.github/workflows/tests.yml`.*
7 changes: 6 additions & 1 deletion __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@
License: MIT
"""

from .digest import KnowledgeDigest
try:
from .digest import KnowledgeDigest
except ImportError:
# Ohne Paket-Kontext (z.B. wenn pytest __init__.py direkt importiert):
# KnowledgeDigest bleibt undefiniert; normaler Package-Import ist nicht betroffen.
pass # type: ignore[assignment]

__version__ = "0.4.0"
__all__ = ["KnowledgeDigest"]
71 changes: 66 additions & 5 deletions indexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,21 @@
6. FTS5-Index wird automatisch via Trigger befuellt
"""

__all__ = ["SkillIndexer"]
__all__ = ["SkillIndexer", "import_bach_skills"]

import sqlite3
from pathlib import Path
from typing import Dict, Optional, Any
from typing import Dict, Optional, Any, Union

from .schema import ensure_schema
from .chunker import chunk_text, estimate_tokens
from .utils import sha256_hash as _sha256, extract_keywords as _extract_keywords
# Relative Imports (Paket-Kontext) mit Fallback auf absolute Imports (sys.path)
try:
from .schema import ensure_schema
from .chunker import chunk_text, estimate_tokens
from .utils import sha256_hash as _sha256, extract_keywords as _extract_keywords
except ImportError:
from schema import ensure_schema # type: ignore[no-redef]
from chunker import chunk_text, estimate_tokens # type: ignore[no-redef]
from utils import sha256_hash as _sha256, extract_keywords as _extract_keywords # type: ignore[no-redef]


class SkillIndexer:
Expand Down Expand Up @@ -261,3 +267,58 @@ def get_index_status(self) -> Dict[str, Any]:
'by_type': {r['skill_type']: r['cnt'] for r in by_type},
'by_category': {r['category']: r['cnt'] for r in by_category},
}


# ---------------------------------------------------------------------------
# Standalone-API (kein Klassen-Overhead, für direkte Nutzung ohne Instanz)
# ---------------------------------------------------------------------------

def import_bach_skills(
bach_db_path: Union[str, Path, None],
knowledge_db_path: Union[str, Path],
*,
chunk_size: int = 350,
overlap: int = 0,
force: bool = False,
) -> Dict[str, Any]:
"""Importiert BACH-Skills aus bach.db in die knowledge.db.

Standalone-Funktion: läuft auch ohne echtes BACH graceful durch.

Gibt immer ein Dict zurück – kein Exception-Throw:
- ``{"available": False, "error": "..."}`` falls BACH nicht verfügbar
- ``{"available": True, "total_skills": N, ...}`` bei Erfolg

Args:
bach_db_path: Pfad zur BACH-Datenbank. ``None`` → sofortiges available=False.
knowledge_db_path: Pfad zur Ziel-knowledge.db (wird ggf. neu angelegt).
chunk_size: Wörter pro Chunk (Default: 350).
overlap: Overlap zwischen Chunks in Wörtern.
force: Wenn True, bestehenden Index komplett neu aufbauen.

Returns:
Dict mit ``"available"`` (bool) plus Statistiken oder Fehlerdetails.
"""
if bach_db_path is None:
return {"available": False, "error": "Kein bach_db_path angegeben."}

path = Path(bach_db_path)
if not path.exists():
return {"available": False, "error": f"bach.db nicht gefunden: {path}"}

indexer = SkillIndexer(Path(knowledge_db_path))
try:
result = indexer.index_from_bach(
path, chunk_size=chunk_size, overlap=overlap, force=force
)
# index_from_bach gibt bei fehlendem Pfad selbst ein error-Dict zurück –
# wird hier als available=False weitergereicht.
if "error" in result:
return {"available": False, **result}
result["available"] = True
return result
except sqlite3.Error as exc:
# Z.B. wenn bach.db existiert, aber die skills-Tabelle fehlt.
return {"available": False, "error": f"SQLite-Fehler beim BACH-Import: {exc}"}
finally:
indexer.close()
19 changes: 18 additions & 1 deletion launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,29 @@ def main():
# Struktur sicherstellen
ensure_structure(base)

# Sicherstellen dass das Paket importierbar ist
# Sicherstellen dass das Paket importierbar ist.
# Das Package liegt im Ordner ".db" (kein gueltiger Python-Name), wird aber
# als "KnowledgeDigest" importiert. Loesung: .db-Verzeichnis direkt importieren
# und unter dem Alias "KnowledgeDigest" in sys.modules registrieren.
if str(base.parent) not in sys.path:
sys.path.insert(0, str(base.parent))
if str(base) not in sys.path:
sys.path.insert(0, str(base))

# Alias: ".db"-Package als "KnowledgeDigest" im Import-System registrieren
import importlib.util
if "KnowledgeDigest" not in sys.modules:
spec = importlib.util.spec_from_file_location(
"KnowledgeDigest",
str(base / "__init__.py"),
submodule_search_locations=[str(base)],
)
pkg = importlib.util.module_from_spec(spec)
pkg.__path__ = [str(base)]
pkg.__package__ = "KnowledgeDigest"
sys.modules["KnowledgeDigest"] = pkg
spec.loader.exec_module(pkg)

# GUI starten
os.environ["PYTHONIOENCODING"] = "utf-8"

Expand Down
4 changes: 3 additions & 1 deletion start.bat
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@ if errorlevel 1 (
:: GUI starten
cd ..
echo Starte KnowledgeDigest GUI...
python -m KnowledgeDigest --gui
:: Ordnername .db ist kein gueltiger Python-Name - dort direkt ueber den
:: Launcher starten, im normal benannten Klon ueber das Paket.
if exist .db\launcher.py (python .db\launcher.py) else (python -m KnowledgeDigest --gui)
if errorlevel 1 pause
Loading