Skip to content

Commit 21c2b2a

Browse files
committed
tui fix
Updated 'serve-ui' to resolve app.py path relative to the project root and provide clearer error messages if not found. Replaced print debugging in engine.py with logging. Enhanced TUI to support multiple Textual versions for ScrollView, added a status message widget, improved error handling, and switched background analysis to use threading for broader compatibility.
1 parent c8aadf0 commit 21c2b2a

3 files changed

Lines changed: 63 additions & 26 deletions

File tree

patternlab/cli.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,9 +380,16 @@ def serve_ui(host, port, reload):
380380
raise click.ClickException(
381381
"streamlit is required to run 'serve-ui'.\n"
382382
"Install it with: pip install streamlit\n"
383-
"Or run the server manually: streamlit run app.py --server.address {host} --server.port {port}".format(host=host, port=port)
383+
"Or run the server manually: streamlit run <path-to-app.py> --server.address {host} --server.port {port}".format(host=host, port=port)
384384
)
385-
cmd = ['streamlit', 'run', 'app.py', '--server.address', host, '--server.port', str(port)]
385+
# Resolve app.py path relative to the project root (one level above the package)
386+
app_py_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'app.py'))
387+
if not os.path.exists(app_py_path):
388+
# Provide a clear error showing which path was checked
389+
raise click.ClickException(f"Invalid value: File does not exist: {app_py_path}\n"
390+
"Ensure app.py exists at the project root or run streamlit manually with the correct path.")
391+
click.echo(f"Starting streamlit with app: {app_py_path}")
392+
cmd = ['streamlit', 'run', app_py_path, '--server.address', host, '--server.port', str(port)]
386393
subprocess.run(cmd, check=True)
387394
except click.ClickException:
388395
raise

patternlab/engine.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ def _analyze_impl(self, input_bytes: bytes, config: Dict[str, Any]) -> Dict[str,
493493
"""
494494
# Normalize input
495495
data = BytesView(input_bytes)
496-
print("DEBUG: Engine.analyze start - registered tests:", list(self._tests.keys()))
496+
logging.getLogger(__name__).debug("Engine.analyze start - registered tests: %s", list(self._tests.keys()))
497497
# Configure logging (attach JSONL file handler when requested)
498498
try:
499499
self._configure_logging(config)
@@ -1630,7 +1630,7 @@ def analyze_stream(self, stream_iterable, config: Dict[str, Any]) -> Dict[str, A
16301630

16311631
output = {"results": serialized_results, "scorecard": scorecard, "meta": meta}
16321632
try:
1633-
print("DEBUG: Engine.analyze returning output - results count:", len(serialized_results))
1633+
logging.getLogger(__name__).debug("Engine.analyze returning output - results count: %d", len(serialized_results))
16341634
except Exception:
16351635
pass
16361636
return output

patternlab/tui.py

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,25 @@
22

33
from typing import Optional
44
from textual.app import App, ComposeResult
5-
from textual.widgets import Header, Footer, Static, DirectoryTree, Checkbox, Button, ScrollView, LoadingIndicator, DataTable
5+
# ScrollView API moved between textual versions; try importing from widgets first,
6+
# then from containers, otherwise provide a minimal fallback shim so the TUI still runs.
7+
try:
8+
from textual.widgets import Header, Footer, Static, DirectoryTree, Checkbox, Button, LoadingIndicator, DataTable
9+
from textual.containers import VerticalScroll as ScrollView
10+
_SCROLL_SRC = 'textual.containers'
11+
except Exception:
12+
try:
13+
# some textual versions expose ScrollView from widgets
14+
from textual.widgets import Header, Footer, Static, DirectoryTree, Checkbox, Button, LoadingIndicator, DataTable, ScrollView
15+
_SCROLL_SRC = 'textual.widgets'
16+
except Exception:
17+
# final fallback: emulate a simple ScrollView using Container so imports succeed and children are rendered
18+
from textual.widgets import Header, Footer, Static, DirectoryTree, Checkbox, Button, LoadingIndicator, DataTable
19+
from textual.containers import Container
20+
class ScrollView(Container): # type: ignore
21+
"""Fallback ScrollView used when textual does not provide one."""
22+
pass
23+
_SCROLL_SRC = 'fallback'
624
from textual.containers import Container, Horizontal, Vertical
725
from textual.screen import ModalScreen
826
from patternlab.engine import Engine
@@ -51,6 +69,7 @@ def compose(self) -> ComposeResult:
5169
DataTable(id="results_table"),
5270
Static("Tıklanabilir sonuç listesi", id="results_list_label"),
5371
ScrollView(id="results_scroll"),
72+
Static("", id="status", expand=False), # Status mesajları için
5473
Horizontal(
5574
Button("Başlat", id="start_btn", variant="success"),
5675
Button("Çıkış", id="exit_btn", variant="error"),
@@ -236,17 +255,17 @@ def on_button_pressed(self, event: Button.Pressed) -> None:
236255
file_path = str(val)
237256
break
238257

239-
footer = self.query_one(Footer)
240-
258+
status = self.query_one("#status", Static)
259+
241260
# Eğer analiz zaten çalışıyorsa yeni analiz başlatma
242261
if getattr(self, "_analysis_running", False):
243-
footer.update("Analiz zaten çalışıyor")
262+
status.update("Analiz zaten çalışıyor")
244263
return
245-
264+
246265
# Hazırlık: Loading göster, flag set
247266
self._analysis_running = True
248267
self._show_loading()
249-
footer.update(f"Analiz başlatıldı — seçili testler: {len(selected_tests)}")
268+
status.update(f"Analiz başlatıldı — seçili testler: {len(selected_tests)}")
250269

251270
# Worker fonksiyonu: Engine.analyze'ı arka planda çağırır
252271
def _worker():
@@ -267,28 +286,31 @@ def _worker():
267286

268287
# tamamlandığında çağrılacak callback
269288
def _on_done(result):
270-
# Analiz sona erdi — Loading'i gizle ve footer'ı güncelle
271289
self._analysis_running = False
272290
self._hide_loading()
273291
try:
274-
footer.update("Analiz tamamlandı — sonuçlar gösteriliyor")
275-
except Exception:
276-
pass
277-
# Sonuçları göster
278-
try:
279-
self._display_results(result or {})
280-
except Exception:
281-
pass
292+
if isinstance(result, dict) and "error" in result:
293+
status.update(f"Analiz hatası: {result['error']}")
294+
else:
295+
status.update("Analiz tamamlandı — sonuçlar gösteriliyor")
296+
self._display_results(result or {})
297+
except Exception as e:
298+
status.update(f"Görüntüleme hatası: {e}")
282299

283-
# run_worker ile arka plan çalıştır; callback ile tamamlandığında UI güncellenecek
300+
# Standart threading ile arka plan çalıştır
284301
try:
285-
# Textual'ın run_worker API'sini kullanıyoruz
286-
self.run_worker(_worker, callback=_on_done)
287-
except Exception:
288-
# Eğer run_worker erişilemezse, flag'i temizle ve Loading'i gizle
302+
import threading
303+
def _thread_worker():
304+
result = _worker()
305+
# Callback'i main thread'de çalıştır
306+
self.call_from_thread(_on_done, result)
307+
thread = threading.Thread(target=_thread_worker, daemon=True)
308+
thread.start()
309+
except Exception as e:
310+
# Hata durumunda temizle
289311
self._analysis_running = False
290312
self._hide_loading()
291-
footer.update("Analiz başlatılamadı (run_worker mevcut değil)")
313+
status.update(f"Analiz başlatılamadı: {e}")
292314
return
293315

294316
# Modal kapatma düğmesi
@@ -328,7 +350,15 @@ def __init__(self, test_name: str, metrics_text: str) -> None:
328350
def compose(self) -> ComposeResult:
329351
yield Vertical(
330352
Static(f"Metrikler — {self.test_name}", id="modal_title"),
331-
ScrollView(Static(self.metrics_text, id="modal_metrics")),
353+
(
354+
Static(self.metrics_text, id="modal_metrics")
355+
if _SCROLL_SRC == 'fallback'
356+
else
357+
ScrollView(self.metrics_text, id="modal_metrics")
358+
if _SCROLL_SRC == 'textual.widgets'
359+
else
360+
ScrollView(Static(self.metrics_text), id="modal_metrics")
361+
),
332362
Horizontal(
333363
Button("Kapat", id="modal_close", variant="primary"),
334364
),

0 commit comments

Comments
 (0)