feat(tray): Modell-Lade-Wartebalken plattformübergreifend anzeigen
Entfernt den Windows-only-Guard in App.__init__, damit der Dialog mit indeterminatem ttk.Progressbar auch unter Linux erscheint, wenn das Laden länger als 500 ms dauert. Ersetzt das literale \u2026 im Label durch das Zeichen … und passt Spec/Plan an den tatsächlichen Umsetzungsstand an (Timeout-basierter Wartebalken statt tqdm-Monkey-Patch, da die Xet-Engine Python-tqdm bypasst). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,235 +1,67 @@
|
||||
# Model-Download-Fortschrittsdialog — Implementierungsplan
|
||||
# Modell-Lade-Wartebalken — Implementierungsplan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
**Status:** Umgesetzt (Stand 2026-04-16)
|
||||
**Zuletzt geändert:** 2026-04-16
|
||||
|
||||
**Goal:** Beim ersten Start zeigt die App einen tkinter-Dialog mit Progressbar, der den echten Byte-Fortschritt des Whisper-Modell-Downloads anzeigt. Bei gecachtem Modell erscheint kein Dialog.
|
||||
**Goal:** Beim ersten Start zeigt die App einen tkinter-Dialog mit indeterminatem Wartebalken, der erscheint, wenn das Laden des Whisper-Modells länger als 500 ms dauert. Bei schnell bereitem Modell erscheint kein Dialog.
|
||||
|
||||
**Architecture:** `tqdm.tqdm` wird vor dem `WhisperModel()`-Aufruf durch eine eigene Klasse (`TkProgressTqdm`) ersetzt, die Fortschrittsdaten thread-safe via `queue.Queue` an den Hauptthread weiterleitet. Der Hauptthread betreibt einen tkinter-Dialog, der sich erst bei der ersten Download-Meldung zeigt. Nach Abschluss kehrt `load_model_with_progress()` mit dem fertigen `WhisperModel` zurück.
|
||||
**Architecture:** Ein Daemon-Thread lädt `WhisperModel`. Der Hauptthread wartet 500 ms via `threading.Event`; erst bei Timeout öffnet er einen tkinter-Dialog mit indeterminatem `ttk.Progressbar`, der via `root.after(100, poll)` auf `done_event.is_set()` pollt. Ein früher geplanter tqdm-Monkey-Patch (`TkProgressTqdm`) wurde verworfen, weil `huggingface_hub` für `model.bin` die Xet-Rust-Engine nutzt, die Python-tqdm bypasst.
|
||||
|
||||
**Tech Stack:** Python 3.13+, tkinter, `tqdm`, `faster_whisper`, `threading`, `queue`
|
||||
**Tech Stack:** Python 3.13+, tkinter, `faster_whisper`, `threading`, `queue` (historisch), `tqdm` (historisch)
|
||||
|
||||
---
|
||||
|
||||
## Dateien
|
||||
|
||||
| Datei | Änderungstyp | Verantwortlichkeit |
|
||||
|-------|-------------|-------------------|
|
||||
| `whisper_local/tray/_download_progress.py` | Neu | `TkProgressTqdm`, `DownloadProgressDialog`-Logik, `load_model_with_progress()` |
|
||||
| `whisper_local/transcriber.py` | Geändert | Optionaler `model`-Parameter in `Transcriber.__init__` |
|
||||
| `whisper_local/__main__.py` | Geändert | Plattform-Guard + Aufruf von `load_model_with_progress` |
|
||||
| `tests/test_download_progress.py` | Neu | Unit-Tests für `TkProgressTqdm` |
|
||||
| `tests/test_transcriber.py` | Geändert | Test für neuen `model`-Parameter + Signatur-Korrektur |
|
||||
| Datei | Status | Verantwortlichkeit |
|
||||
|-------|--------|-------------------|
|
||||
| `whisper_local/tray/_download_progress.py` | Umgesetzt | `TkProgressTqdm` (Fallback, ungenutzt), `load_model_with_progress()` |
|
||||
| `whisper_local/transcriber.py` | Umgesetzt | Optionaler `model`-Parameter in `Transcriber.__init__` |
|
||||
| `whisper_local/__main__.py` | Umgesetzt | Aufruf von `load_model_with_progress` (plattformübergreifend) |
|
||||
| `tests/test_download_progress.py` | Umgesetzt | Unit-Tests für `TkProgressTqdm` |
|
||||
| `tests/test_transcriber.py` | Umgesetzt | Test für neuen `model`-Parameter + Signatur-Korrektur |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: `Transcriber` um optionalen `model`-Parameter erweitern
|
||||
## Historie der Umsetzung
|
||||
|
||||
**Files:**
|
||||
- Modify: `whisper_local/transcriber.py`
|
||||
- Modify: `tests/test_transcriber.py`
|
||||
Die Implementierung erfolgte in zwei Phasen:
|
||||
|
||||
- [ ] **Schritt 1: Failing-Test für neuen `model`-Parameter schreiben**
|
||||
1. **Initialer Plan (April 2026):** tqdm-Monkey-Patch mit determinater Progressbar, Dateiname- und Prozentanzeige (Commits `e92f5f5`, `3067499`, `44c8d8e`, `c26dfa3`, `3d9f95b`, `e31230f`).
|
||||
2. **Korrektur (Commit `753dbc5`):** Monkey-Patch verworfen, da Xet-Engine tqdm bypasst. Ersetzt durch Timeout-basierten indeterminaten Wartebalken.
|
||||
|
||||
In `tests/test_transcriber.py` folgenden Test ergänzen:
|
||||
|
||||
```python
|
||||
def test_init_with_preloaded_model():
|
||||
mock_model = MagicMock()
|
||||
t = Transcriber(language="de", model=mock_model)
|
||||
assert t.model is mock_model
|
||||
assert t.language == "de"
|
||||
```
|
||||
|
||||
- [ ] **Schritt 2: Test ausführen — erwartet FAIL**
|
||||
|
||||
```
|
||||
uv run pytest tests/test_transcriber.py::TestTranscriber::test_init_with_preloaded_model -v
|
||||
```
|
||||
|
||||
Erwartete Ausgabe: `FAILED` — `TypeError: __init__() got an unexpected keyword argument 'model'`
|
||||
|
||||
- [ ] **Schritt 3: Bestehenden `test_init_loads_model`-Test korrigieren**
|
||||
|
||||
Der Test prüft derzeit `assert_called_once_with("small", compute_type="int8")`, übergeht aber `download_root=None`. Ersetzen durch:
|
||||
|
||||
```python
|
||||
@patch("whisper_local.transcriber.WhisperModel")
|
||||
def test_init_loads_model(self, mock_model_class):
|
||||
t = Transcriber(model_name="small", compute_type="int8", language="de")
|
||||
mock_model_class.assert_called_once_with("small", compute_type="int8", download_root=None)
|
||||
assert t.language == "de"
|
||||
```
|
||||
|
||||
- [ ] **Schritt 4: `Transcriber.__init__` um optionalen `model`-Parameter erweitern**
|
||||
|
||||
`whisper_local/transcriber.py` — `__init__`-Signatur und Body ersetzen:
|
||||
|
||||
```python
|
||||
from faster_whisper import WhisperModel
|
||||
|
||||
class Transcriber:
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str = "small",
|
||||
compute_type: str = "int8",
|
||||
language: str = "de",
|
||||
model: WhisperModel | None = None,
|
||||
):
|
||||
self.language = language
|
||||
if model is not None:
|
||||
self.model = model
|
||||
else:
|
||||
logger.info("Lade Whisper-Modell '%s' (compute_type=%s)...", model_name, compute_type)
|
||||
self.model = WhisperModel(
|
||||
model_name, compute_type=compute_type, download_root=_model_cache_dir()
|
||||
)
|
||||
logger.info("Modell geladen")
|
||||
```
|
||||
|
||||
- [ ] **Schritt 5: Alle Transcriber-Tests ausführen — erwartet PASS**
|
||||
|
||||
```
|
||||
uv run pytest tests/test_transcriber.py -v
|
||||
```
|
||||
|
||||
Erwartete Ausgabe: alle Tests `PASSED`
|
||||
|
||||
- [ ] **Schritt 6: Commit**
|
||||
|
||||
```bash
|
||||
git add whisper_local/transcriber.py tests/test_transcriber.py
|
||||
git commit -m "feat: Transcriber akzeptiert optionales vorgeladenes WhisperModel"
|
||||
```
|
||||
Die Tasks unten beschreiben den umgesetzten Endzustand.
|
||||
|
||||
---
|
||||
|
||||
## Task 2: `TkProgressTqdm` implementieren und testen
|
||||
## Task 1: `Transcriber` um optionalen `model`-Parameter erweitern *(erledigt)*
|
||||
|
||||
**Files:**
|
||||
- Create: `whisper_local/tray/_download_progress.py`
|
||||
- Create: `tests/test_download_progress.py`
|
||||
- `whisper_local/transcriber.py`
|
||||
- `tests/test_transcriber.py`
|
||||
|
||||
- [ ] **Schritt 1: Test-Datei mit Failing-Tests anlegen**
|
||||
|
||||
`tests/test_download_progress.py`:
|
||||
|
||||
```python
|
||||
import queue
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import tqdm as tqdm_module
|
||||
|
||||
from whisper_local.tray._download_progress import TkProgressTqdm
|
||||
|
||||
|
||||
class TestTkProgressTqdm:
|
||||
def test_update_puts_message_in_queue(self):
|
||||
q = queue.Queue()
|
||||
TkProgressTqdm._queue = q
|
||||
|
||||
bar = TkProgressTqdm(total=1000, desc="model.bin", disable=True)
|
||||
bar.update(300)
|
||||
bar.close()
|
||||
|
||||
TkProgressTqdm._queue = None
|
||||
|
||||
msg = q.get_nowait()
|
||||
assert msg["file"] == "model.bin"
|
||||
assert msg["n"] == 300
|
||||
assert msg["total"] == 1000
|
||||
|
||||
def test_update_without_queue_does_not_raise(self):
|
||||
TkProgressTqdm._queue = None
|
||||
bar = TkProgressTqdm(total=100, desc="test.bin", disable=True)
|
||||
bar.update(50) # darf nicht crashen
|
||||
bar.close()
|
||||
|
||||
def test_multiple_updates_accumulate(self):
|
||||
q = queue.Queue()
|
||||
TkProgressTqdm._queue = q
|
||||
|
||||
bar = TkProgressTqdm(total=1000, desc="file.bin", disable=True)
|
||||
bar.update(200)
|
||||
bar.update(300)
|
||||
bar.close()
|
||||
|
||||
TkProgressTqdm._queue = None
|
||||
|
||||
msgs = []
|
||||
while not q.empty():
|
||||
msgs.append(q.get_nowait())
|
||||
|
||||
assert len(msgs) == 2
|
||||
assert msgs[0]["n"] == 200
|
||||
assert msgs[1]["n"] == 500 # tqdm akkumuliert: 200+300
|
||||
```
|
||||
|
||||
- [ ] **Schritt 2: Tests ausführen — erwartet FAIL**
|
||||
|
||||
```
|
||||
uv run pytest tests/test_download_progress.py -v
|
||||
```
|
||||
|
||||
Erwartete Ausgabe: `ERROR` / `ImportError` — Modul existiert noch nicht
|
||||
|
||||
- [ ] **Schritt 3: `_download_progress.py` mit `TkProgressTqdm` anlegen**
|
||||
|
||||
`whisper_local/tray/_download_progress.py`:
|
||||
|
||||
```python
|
||||
"""Download-Fortschrittsdialog für den ersten Whisper-Modell-Download (Windows)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import queue
|
||||
import sys
|
||||
import threading
|
||||
from typing import Any
|
||||
|
||||
import tqdm as tqdm_module
|
||||
|
||||
|
||||
class TkProgressTqdm(tqdm_module.tqdm):
|
||||
"""tqdm-Ersatz, der Fortschritts-Updates thread-safe in eine Queue schreibt."""
|
||||
|
||||
_queue: queue.Queue | None = None
|
||||
|
||||
def update(self, n: int = 1) -> bool | None:
|
||||
result = super().update(n)
|
||||
if self._queue is not None:
|
||||
self._queue.put({
|
||||
"file": self.desc or "",
|
||||
"n": self.n,
|
||||
"total": self.total or 0,
|
||||
})
|
||||
return result
|
||||
```
|
||||
|
||||
- [ ] **Schritt 4: Tests ausführen — erwartet PASS**
|
||||
|
||||
```
|
||||
uv run pytest tests/test_download_progress.py -v
|
||||
```
|
||||
|
||||
Erwartete Ausgabe: alle 3 Tests `PASSED`
|
||||
|
||||
- [ ] **Schritt 5: Commit**
|
||||
|
||||
```bash
|
||||
git add whisper_local/tray/_download_progress.py tests/test_download_progress.py
|
||||
git commit -m "feat: TkProgressTqdm leitet tqdm-Fortschritt an Queue weiter"
|
||||
```
|
||||
`Transcriber.__init__` akzeptiert `model: WhisperModel | None = None`. Wenn gesetzt, wird `WhisperModel` nicht selbst instanziiert. Test `test_init_with_preloaded_model` prüft diesen Pfad; `test_init_loads_model` wurde an die neue Signatur mit `download_root=None` angepasst.
|
||||
|
||||
---
|
||||
|
||||
## Task 3: `load_model_with_progress` implementieren
|
||||
## Task 2: `TkProgressTqdm` implementieren und testen *(erledigt, aber ungenutzt)*
|
||||
|
||||
**Files:**
|
||||
- Modify: `whisper_local/tray/_download_progress.py`
|
||||
- `whisper_local/tray/_download_progress.py`
|
||||
- `tests/test_download_progress.py`
|
||||
|
||||
- [ ] **Schritt 1: `load_model_with_progress` an `_download_progress.py` anhängen**
|
||||
Klasse `TkProgressTqdm` erbt von `tqdm.tqdm`, akkumuliert Fortschritt in `_accumulated_n` und schreibt `{"file", "n", "total"}`-Dicts in `TkProgressTqdm._queue`, falls gesetzt. Unit-Tests decken Queue-Weiterleitung, Null-Queue-Fall und Akkumulation ab.
|
||||
|
||||
Folgenden Code nach der `TkProgressTqdm`-Klasse einfügen:
|
||||
**Hinweis:** `load_model_with_progress` nutzt die Klasse nicht mehr (Xet-Bypass, siehe Historie). Die Klasse + Tests bleiben als dokumentierter Fallback erhalten — wird `faster_whisper` irgendwann wieder durch Python-tqdm laufen, kann sie reaktiviert werden.
|
||||
|
||||
---
|
||||
|
||||
## Task 3: `load_model_with_progress` implementieren *(erledigt, Ansatz geändert)*
|
||||
|
||||
**Files:**
|
||||
- `whisper_local/tray/_download_progress.py`
|
||||
|
||||
Die finale Version nutzt **kein** tqdm-Patching. Stattdessen:
|
||||
|
||||
```python
|
||||
def load_model_with_progress(
|
||||
@@ -237,146 +69,92 @@ def load_model_with_progress(
|
||||
compute_type: str,
|
||||
download_root: str | None,
|
||||
) -> Any:
|
||||
"""Lädt WhisperModel — zeigt bei Bedarf einen Download-Fortschrittsdialog.
|
||||
|
||||
Wenn das Modell bereits gecacht ist (kein tqdm-Update kommt), erscheint
|
||||
kein Dialog. Auf Download-Fehler wird ein Fehlerdialog gezeigt und sys.exit(1)
|
||||
aufgerufen.
|
||||
"""
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox, ttk
|
||||
|
||||
from faster_whisper import WhisperModel
|
||||
|
||||
from whisper_local.tray._theme import apply_system_theme
|
||||
|
||||
q: queue.Queue[dict[str, Any] | None] = queue.Queue()
|
||||
result: list[WhisperModel | None] = [None]
|
||||
result: list[Any] = [None]
|
||||
error: list[BaseException | None] = [None]
|
||||
original_tqdm = tqdm_module.tqdm
|
||||
done_event = threading.Event()
|
||||
|
||||
def worker() -> None:
|
||||
TkProgressTqdm._queue = q
|
||||
tqdm_module.tqdm = TkProgressTqdm
|
||||
try:
|
||||
result[0] = WhisperModel(
|
||||
model_name,
|
||||
compute_type=compute_type,
|
||||
download_root=download_root,
|
||||
model_name, compute_type=compute_type, download_root=download_root
|
||||
)
|
||||
except Exception as exc:
|
||||
error[0] = exc
|
||||
finally:
|
||||
tqdm_module.tqdm = original_tqdm
|
||||
TkProgressTqdm._queue = None
|
||||
q.put(None) # Sentinel: signalisiert Fertigstellung
|
||||
done_event.set()
|
||||
|
||||
thread = threading.Thread(target=worker, daemon=True)
|
||||
thread.start()
|
||||
|
||||
# --- tkinter-Dialog (lazy: erscheint nur bei echtem Download) ---
|
||||
# Kurz warten – schneller Cache-Hit überspringt Dialog
|
||||
if done_event.wait(timeout=0.5):
|
||||
if error[0] is not None:
|
||||
root = tk.Tk()
|
||||
root.withdraw()
|
||||
messagebox.showerror("Fehler beim Modell-Laden", str(error[0]))
|
||||
root.destroy()
|
||||
sys.exit(1)
|
||||
return result[0]
|
||||
|
||||
# Dialog mit indeterminatem Wartebalken
|
||||
root = tk.Tk()
|
||||
root.withdraw() # zunächst versteckt
|
||||
root.title("whisper-local – Modell wird geladen")
|
||||
root.resizable(False, False)
|
||||
apply_system_theme(root)
|
||||
|
||||
frame = ttk.Frame(root, padding=16)
|
||||
frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
ttk.Label(frame, text=f"Lade Whisper-Modell '{model_name}'...").pack(anchor=tk.W)
|
||||
ttk.Label(frame, text="Bitte warten\u2026", foreground="gray").pack(
|
||||
anchor=tk.W, pady=(4, 8)
|
||||
)
|
||||
|
||||
file_var = tk.StringVar(value="")
|
||||
ttk.Label(frame, textvariable=file_var, foreground="gray").pack(anchor=tk.W, pady=(4, 8))
|
||||
|
||||
progress_var = tk.DoubleVar(value=0.0)
|
||||
ttk.Progressbar(
|
||||
frame, variable=progress_var, maximum=100, length=320, mode="determinate"
|
||||
).pack(fill=tk.X)
|
||||
|
||||
pct_var = tk.StringVar(value="0 %")
|
||||
ttk.Label(frame, textvariable=pct_var).pack(anchor=tk.E, pady=(2, 0))
|
||||
pb = ttk.Progressbar(frame, length=320, mode="indeterminate")
|
||||
pb.pack(fill=tk.X)
|
||||
pb.start(10)
|
||||
|
||||
def poll() -> None:
|
||||
try:
|
||||
while True:
|
||||
msg = q.get_nowait()
|
||||
if msg is None: # Sentinel → fertig
|
||||
root.quit()
|
||||
return
|
||||
# Erste echte Meldung → Dialog anzeigen
|
||||
if not root.winfo_viewable():
|
||||
root.deiconify()
|
||||
# UI aktualisieren
|
||||
file_var.set(msg["file"])
|
||||
if msg["total"] > 0:
|
||||
pct = 100.0 * msg["n"] / msg["total"]
|
||||
progress_var.set(pct)
|
||||
pct_var.set(f"{pct:.0f} %")
|
||||
except queue.Empty:
|
||||
pass
|
||||
root.after(50, poll)
|
||||
if done_event.is_set():
|
||||
root.quit()
|
||||
return
|
||||
root.after(100, poll)
|
||||
|
||||
root.after(50, poll)
|
||||
root.after(100, poll)
|
||||
root.mainloop()
|
||||
root.destroy()
|
||||
|
||||
if error[0] is not None:
|
||||
messagebox.showerror("Fehler beim Modell-Download", str(error[0]))
|
||||
root.withdraw()
|
||||
messagebox.showerror("Fehler beim Modell-Laden", str(error[0]))
|
||||
root.destroy()
|
||||
sys.exit(1)
|
||||
|
||||
root.destroy()
|
||||
return result[0]
|
||||
```
|
||||
|
||||
- [ ] **Schritt 2: Vorhandene Tests noch laufen lassen**
|
||||
|
||||
```
|
||||
uv run pytest tests/test_download_progress.py tests/test_transcriber.py -v
|
||||
```
|
||||
|
||||
Erwartete Ausgabe: alle Tests `PASSED`
|
||||
|
||||
- [ ] **Schritt 3: Commit**
|
||||
|
||||
```bash
|
||||
git add whisper_local/tray/_download_progress.py
|
||||
git commit -m "feat: load_model_with_progress mit tkinter-Fortschrittsdialog"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: `App.__init__` in `__main__.py` anpassen
|
||||
## Task 4: `App.__init__` in `__main__.py` anpassen *(erledigt)*
|
||||
|
||||
**Files:**
|
||||
- Modify: `whisper_local/__main__.py`
|
||||
- `whisper_local/__main__.py`
|
||||
|
||||
- [ ] **Schritt 1: Plattform-Guard und Modell-Vorladen einfügen**
|
||||
|
||||
In `whisper_local/__main__.py` die `App.__init__`-Methode anpassen.
|
||||
|
||||
Vorher (Zeile 33–37):
|
||||
Plattformübergreifend — der anfängliche `sys.platform == "win32"`-Guard wurde entfernt, weil der Dialog auch auf Linux gewünscht ist und tkinter zur Standardbibliothek gehört.
|
||||
|
||||
```python
|
||||
self.transcriber = Transcriber(
|
||||
from whisper_local.tray._download_progress import load_model_with_progress
|
||||
from whisper_local.transcriber import _model_cache_dir
|
||||
_preloaded_model = load_model_with_progress(
|
||||
model_name=config.whisper_model,
|
||||
compute_type=config.compute_type,
|
||||
language=config.language,
|
||||
download_root=_model_cache_dir(),
|
||||
)
|
||||
```
|
||||
|
||||
Nachher — vollständiger Ersatz des Blocks:
|
||||
|
||||
```python
|
||||
if sys.platform == "win32":
|
||||
from whisper_local.tray._download_progress import load_model_with_progress
|
||||
from whisper_local.transcriber import _model_cache_dir
|
||||
_preloaded_model = load_model_with_progress(
|
||||
model_name=config.whisper_model,
|
||||
compute_type=config.compute_type,
|
||||
download_root=_model_cache_dir(),
|
||||
)
|
||||
else:
|
||||
_preloaded_model = None
|
||||
|
||||
self.transcriber = Transcriber(
|
||||
model_name=config.whisper_model,
|
||||
@@ -386,37 +164,18 @@ self.transcriber = Transcriber(
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Schritt 2: Alle Tests ausführen**
|
||||
|
||||
```
|
||||
uv run pytest -v
|
||||
```
|
||||
|
||||
Erwartete Ausgabe: alle Tests `PASSED`
|
||||
|
||||
- [ ] **Schritt 3: Commit**
|
||||
|
||||
```bash
|
||||
git add whisper_local/__main__.py
|
||||
git commit -m "feat: App lädt Whisper-Modell auf Windows mit Fortschrittsdialog"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Manueller Test
|
||||
|
||||
*Dieser Schritt kann nicht automatisiert werden, da er eine laufende GUI erfordert.*
|
||||
## Task 5: Manueller Test *(empfohlen nach Regressionen)*
|
||||
|
||||
- [ ] **Schritt 1: Modell-Cache temporär umbenennen (Download erzwingen)**
|
||||
|
||||
Finde den Cache-Pfad (Standard: `%APPDATA%\whisper-local\models\` oder HuggingFace-Cache):
|
||||
|
||||
```bash
|
||||
# Zeigt den Cache-Pfad
|
||||
uv run python -c "from whisper_local.transcriber import _model_cache_dir; print(_model_cache_dir())"
|
||||
```
|
||||
|
||||
Benenne den Modell-Ordner um (z.B. `models` → `models_bak`), damit beim Start ein Download ausgelöst wird.
|
||||
Modell-Ordner umbenennen (`models` → `models_bak`).
|
||||
|
||||
- [ ] **Schritt 2: App starten und Dialog beobachten**
|
||||
|
||||
@@ -425,10 +184,9 @@ uv run whisper-local
|
||||
```
|
||||
|
||||
Erwartet:
|
||||
- Kleines Fenster erscheint mit Label `"Lade Whisper-Modell 'small'..."`
|
||||
- Dateiname wechselt während des Downloads (z.B. `model.bin`, `tokenizer.json`)
|
||||
- Progressbar füllt sich von 0 % auf 100 %
|
||||
- Dialog schließt sich automatisch, Tray-Icon erscheint
|
||||
- Fenster mit Label `"Lade Whisper-Modell 'small'..."` + `"Bitte warten…"` erscheint nach ca. 500 ms
|
||||
- Indeterminater Wartebalken animiert durchlaufend
|
||||
- Dialog schließt automatisch nach Abschluss des Downloads, Tray-Icon erscheint
|
||||
|
||||
- [ ] **Schritt 3: Neustart mit gecachtem Modell**
|
||||
|
||||
@@ -436,8 +194,8 @@ Erwartet:
|
||||
uv run whisper-local
|
||||
```
|
||||
|
||||
Erwartet: **kein Dialog** erscheint — App startet direkt mit Tray-Icon.
|
||||
Erwartet: **kein Dialog** erscheint — App startet direkt mit Tray-Icon (Modell < 500 ms bereit).
|
||||
|
||||
- [ ] **Schritt 4: Cache wiederherstellen**
|
||||
|
||||
Benenne `models_bak` zurück zu `models`.
|
||||
`models_bak` zurück zu `models` benennen.
|
||||
|
||||
Reference in New Issue
Block a user