Files
astrololo/services/presentation/app/templates/_prompt_block.html
T
gitea 4ef90b30bc
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m38s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m43s
Testy / Build obrazu silnika B (swisseph) (pull_request) Failing after 24s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 24s
build / build (push) Successful in 59s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m47s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m53s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 29s
Testy / Kontrola składni wszystkich warstw (push) Successful in 18s
feat(logic): dostawcy LLM i pisanie horoskopu (LOG-31)
Domyslnie model LOKALNY — prompt niesie oryginalne opisy z baz, wiec
domyslnie NIC nie opuszcza sieci. Chmura wlaczana swiadomie (LOG-32).

- app/llm/: LLMProvider (jak EphemerisEngine z LOG-24) + dwie implementacje.
  Lokalny serwer modelu (Ollama/vLLM/llama.cpp) i OpenAI mowia TYM SAMYM
  protokolem /chat/completions, wiec obsluguje je jedna klasa; Anthropic ma
  wlasny /v1/messages. Napisane na samym httpx — bez SDK openai/anthropic:
  mniej zaleznosci i pelna kontrola nad tym, co wychodzi z sieci.
- Kazdy dostawca deklaruje `leaves_lan` — interfejs MUSI jawnie mowic, czy
  tresc baz opuszcza siec; UI na tej podstawie ostrzega.
- Ponawianie z backoffem (429/5xx), timeouty, czytelne bledy zamiast stacktrace.
- Klucz wylacznie z LLM_API_KEY (sekret), nigdy w repo ani w UI.
- POST /chart/horoscope + GET /llm/health.

WAZNE: prompt jest zwracany ZAWSZE — takze gdy model padnie lub brakuje
klucza. Dzieki temu awaria dostawcy nie blokuje pracy: prompt mozna
skopiowac i uzyc recznie.

Prezentacja: wybor modelu (lokalny/Anthropic/OpenAI), przycisk „Napisz
horoskop (AI)", wynik z informacja kto go napisal, czy dane opuscily siec,
ile wskazan weszlo, oraz zastrzezenie ze to nie porada medyczna (PRE-15).

Testy: 13 nowych (transport podstawiony — zaden prawdziwy model nie wolany),
calosc 131 passed / 1 skipped. Zweryfikowane e2e na atrapie serwera modelu:
horoskop napisany, leaves_lan=false, tokeny zliczone; a przy padnietym
modelu / braku klucza / zlym dostawcy — czytelny blad i zachowany prompt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 20:34:07 +02:00

87 lines
3.8 KiB
HTML

{# Wspólny blok: sterowanie generowaniem + prompt (LOG-29/30) + horoskop (LOG-31).
Używany na ekranach Interpretacje i Kalendarz. #}
<div class="opts">
<label>Budżet promptu
<select name="prompt_budget">
{% set pb = form.prompt_budget or 'medium' %}
<option value="concise" {{ 'selected' if pb == 'concise' else '' }}>zwięzły (~4 tys. znaków)</option>
<option value="medium" {{ 'selected' if pb == 'medium' else '' }}>średni (~12 tys.)</option>
<option value="extensive" {{ 'selected' if pb == 'extensive' else '' }}>obszerny (~30 tys.)</option>
</select>
</label>
<label>Model
<select name="llm_provider">
{% set lp = form.llm_provider or 'local' %}
<option value="local" {{ 'selected' if lp == 'local' else '' }}>lokalny — nic nie opuszcza sieci</option>
<option value="anthropic" {{ 'selected' if lp == 'anthropic' else '' }}>Anthropic — dane wychodzą</option>
<option value="openai" {{ 'selected' if lp == 'openai' else '' }}>OpenAI — dane wychodzą</option>
</select>
</label>
</div>
<p class="muted small">
Prompt zawiera <strong>oryginalne opisy z baz</strong> oraz dane urodzeniowe. Model lokalny
przetwarza je u nas; wybór dostawcy w chmurze oznacza, że ta treść <strong>opuszcza naszą
sieć</strong>. Możesz najpierw obejrzeć prompt, a dopiero potem wysłać.
</p>
{% if prompt_result %}
{% set st = prompt_result.stats %}
{# --- wynik: napisany horoskop (LOG-31) + transparentność (PRE-15) --- #}
{% if prompt_result.horoscope %}
<div class="meta">
Horoskop napisany przez: <strong>{{ prompt_result.provider }}</strong> ·
model: {{ prompt_result.model }}
{% if prompt_result.usage and prompt_result.usage.completion_tokens %}
· tokeny odpowiedzi: {{ prompt_result.usage.completion_tokens }}
{% endif %}
{% if prompt_result.leaves_lan %}
· <strong class="retro">dane opuściły sieć</strong>
{% else %}
· dane nie opuściły sieci
{% endif %}
</div>
<div class="actions">
<button type="button" class="ghost" data-copy="#horoscopeText">Kopiuj horoskop</button>
</div>
<textarea id="horoscopeText" class="prompt" rows="20" readonly>{{ prompt_result.horoscope }}</textarea>
<p class="muted small">
Treść wygenerował model językowy na podstawie {{ st.included }} wskazań z baz.
<strong>Nie stanowi porady medycznej, prawnej ani finansowej.</strong>
</p>
{% endif %}
{% if prompt_result.llm_error %}
<div class="error">
Nie udało się napisać horoskopu: {{ prompt_result.llm_error }}<br>
Prompt poniżej jest gotowy — możesz go skopiować i użyć ręcznie.
</div>
{% endif %}
{# --- prompt: zawsze dostępny do podglądu i skopiowania --- #}
<div class="meta">
Prompt · {{ st.chars }} znaków (~{{ st.est_tokens }} tokenów) ·
budżet: {{ st.budget }} ·
wskazań: <strong>{{ st.included }}</strong>
{% if st.omitted %}· pominięto: <strong>{{ st.omitted }}</strong>{% endif %}
{% if st.deduplicated %}· scalono powtórek: {{ st.deduplicated }}{% endif %}
</div>
{% if st.omitted %}
<p class="muted small">
Pominięto {{ st.omitted }} najsłabszych wskazań (próg wagi {{ st.min_score_included }}).
Chcesz komplet — wybierz obszerniejszy budżet i wygeneruj ponownie.
</p>
{% endif %}
{% if prompt_result.data_error %}
<div class="error">{{ prompt_result.data_error }} — prompt złożony z samych wyliczeń.</div>
{% endif %}
<div class="actions">
<button type="button" class="ghost" data-copy="#promptText">Kopiuj prompt</button>
<span class="muted small">Możesz też wkleić go samodzielnie do ChatGPT lub Claude.</span>
</div>
<textarea id="promptText" class="prompt" rows="14" readonly>{{ prompt_result.prompt }}</textarea>
{% endif %}