feat(ui): okno postepu z logiem podczas pisania horoskopu
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m57s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m52s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 38s
Testy / Kontrola składni wszystkich warstw (push) Successful in 24s
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 11m21s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m50s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 34s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 21s

Generowanie trwa minutami, a zwykly POST nie dawal zadnego sygnalu — aplikacja
wygladala na zawieszona. Teraz w trakcie pracy pojawia sie okno z logiem,
zegarem i spinnerem.

Log pokazuje RZECZYWISTE zdarzenia z serwera, nie udawany pasek postepu:
- app/progress.py — strumien NDJSON; praca leci w watku roboczym, generator
  odpompowuje kolejke, wiec zdarzenia docz w TRAKCIE pracy, nie na koncu;
  heartbeat co 10s, zeby proxy nie uznalo polaczenia za martwe,
- providers.generate(..., on_event) — raportuje kazda ture (start, czas trwania,
  liczba znakow, czy urwana), bo to tura trwa,
- POST /chart/horoscope/stream w logice + proxy /horoscope/stream w prezentacji.

Wynik: ostatnie zdarzenie niesie GOTOWY HTML wyrenderowany z tego samego
szablonu, ktory renderuje przeladowanie strony (_prompt_result.html wydzielony
z _prompt_block.html). Jedno zrodlo prawdy dla wygladu wyniku — okno wstawia go
bez przeladowania.

Degradacja: bez strumieniowania w przegladarce formularz idzie klasycznie
i wszystko dziala jak wczesniej, tylko bez okna. Blad polaczenia konczy sie
komunikatem w logu, nie cisza.

BLAD ZNALEZIONY PRZY TESCIE NA ZYWO: petla kontynuacji odejmowala od budzetu
ZAMOWIONY limit tury zamiast tokenow faktycznie wyprodukowanych — pierwsza tura
zjadala caly budzet, wiec urwana odpowiedz nigdy nie doczekala sie dokonczenia
i wracala do uzytkownika jako calosc. Naprawione i pokryte testem regresyjnym.

Testy: 176 passed / 1 skipped (logika) + 17 (prezentacja). Zweryfikowane na zywo
z wolna atrapa modelu: zdarzenia z poprawnymi czasami, okno z 11 liniami logu,
wynik wstawiony bez przeladowania strony.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 22:58:56 +02:00
parent 163ace4283
commit 114b7eebdf
12 changed files with 508 additions and 80 deletions
+30
View File
@@ -405,3 +405,33 @@ def test_max_budget_differs_between_models(monkeypatch):
haiku = prompt_token_budget(*factory.resolve_model("anthropic", "claude-haiku-4-5"))
local = prompt_token_budget(*factory.resolve_model("local", "llama3.1:8b"))
assert opus > haiku > local > 0
def test_turn_budget_counts_produced_not_requested(monkeypatch):
"""Regresja: odejmowanie ZAMOWIONEGO limitu tury zamiast wyprodukowanych
tokenow konczylo petle po jednej turze — urwany fragment wracal jako calosc."""
seq = [_chat("Fragment 1. ", "length"), _chat("Fragment 2. ", "length"),
_chat("Zakonczenie. KONIEC", "stop")]
def handler(request):
return httpx.Response(200, json=seq.pop(0) if seq else seq[-1])
monkeypatch.setattr(httpx, "Client", _mock_client(handler))
# budzet 8000 < TURN_TOKENS_CAP: przy starej logice byla dokladnie jedna tura
out = ChatCompletionsProvider("local", "http://x/v1", "m").generate("p", 8000)
assert out.usage["turns"] == 3, "urwana odpowiedz musi byc kontynuowana"
assert "Fragment 1." in out.text and "Zakonczenie." in out.text
def test_generate_reports_progress_events(monkeypatch):
"""Log w UI ma pokazywac RZECZYWISTE tury, nie udawany pasek postepu."""
seq = [_chat("Czesc. ", "length"), _chat("Reszta. KONIEC", "stop")]
monkeypatch.setattr(httpx, "Client", _mock_client(
lambda r: httpx.Response(200, json=seq.pop(0) if seq else seq[-1])))
events = []
ChatCompletionsProvider("local", "http://x/v1", "m").generate(
"p", 8000, on_event=events.append)
kinds = [e["type"] for e in events]
assert kinds.count("turn_start") == 2 and kinds.count("turn_end") == 2
assert kinds[-1] == "generated"
assert any("urwana" in e["message"] for e in events if e["type"] == "turn_end")