diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md
index e43c508..caf65f1 100644
--- a/.paul/PROJECT.md
+++ b/.paul/PROJECT.md
@@ -11,7 +11,7 @@ Użytkownicy mogą efektywnie zarządzać projektami, zadaniami i klientami w je
| Metric | Value | Source |
|--------|-------|--------|
| Version | v0.1.0 (in progress) | Milestone |
-| Status | Phase 1 and Phase 5 complete, Phase 2 next | STATE.md |
+| Status | Phase 1, Phase 5 and Phase 6 complete, Phase 2 next | STATE.md |
| Lines of Code | 9 356 | SonarQube baseline |
| Bugs | 58 | SonarQube baseline |
| Code Smells | 1 649 | SonarQube baseline |
@@ -25,6 +25,7 @@ Użytkownicy mogą efektywnie zarządzać projektami, zadaniami i klientami w je
### Validated
- ✓ SonarQube baseline — Phase 1: projekt skonfigurowany, skan wykonany, metryki udokumentowane
- ✓ Import finansow z Fakturowni — Phase 5: automatyczny import faktur przychodowych/kosztowych, mapowanie po NIP, filtr proforma, skip-list pozycji, edycja dopasowania kategorii
+- ✓ AI title generation for tasks — Phase 6: przycisk AI w popupie zadania dla `biuro@project-pro.pl`, propozycja tytulu bez automatycznego zapisu
### Active
- [ ] Naprawić 58 bugów (priorytet: 3 CRITICAL, 35 MAJOR)
@@ -49,6 +50,8 @@ Użytkownicy mogą efektywnie zarządzać projektami, zadaniami i klientami w je
| Filtr proforma: kind + prefiks FP | Phase 5 | Proformy nie trafiaja do finansow |
| Edycja dopasowania Fakturownia tylko dla operacji importowanych | Phase 5 | Reczne operacje pozostaja przy standardowej edycji kategorii |
| Zmiana dopasowania moze masowo przepiac pasujace operacje | Phase 5 | Szybka korekta blednej kategorii dla powtarzalnych pozycji faktur |
+| Tytuly AI sa propozycja, nie automatycznym zapisem | Phase 6 | Bezpieczna edycja tytulu zadania przez istniejacy mechanizm zapisu |
+| Model tytulow zadan: `gpt-5-nano` | Phase 6 | Osobna konfiguracja od importu maili i niski koszt generowania |
## Success Criteria
- Użytkownicy mogą efektywnie zarządzać projektami, zadaniami i klientami w jednym systemie CRM
@@ -58,5 +61,6 @@ Użytkownicy mogą efektywnie zarządzać projektami, zadaniami i klientami w je
---
*Created: 2026-03-15*
-*Last updated: 2026-05-04 after Phase 5*
+*Last updated: 2026-05-04 after Phase 6*
+
diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md
index 23ea3b3..00d49d4 100644
--- a/.paul/ROADMAP.md
+++ b/.paul/ROADMAP.md
@@ -6,7 +6,7 @@ Stabilizacja i poprawa jakosci kodu crmPRO oraz rozwoj finansow o automatyczny i
## Current Milestone
**v0.1 Stabilizacja i jakosc kodu + import finansow** (v0.1.0)
Status: In progress
-Phases: 2 of 5 complete
+Phases: 3 of 6 complete
## Phases
@@ -17,6 +17,7 @@ Phases: 2 of 5 complete
| 3 | Naprawa bledow glownych | TBD | Not started | - |
| 4 | Poprawa pokrycia testami | TBD | Not started | - |
| 5 | Import finansow z Fakturowni | 6/6 | Complete | 2026-05-04 |
+| 6 | AI title generation for tasks | 1/1 | Complete | 2026-05-04 |
## Phase Details
@@ -96,9 +97,24 @@ Phases: 2 of 5 complete
- [x] 05-05: Skip-list pozycji — mozliwosc oznaczenia wybranej pozycji faktury jako pomijanej (nie trafia do finance_operations)
- [x] 05-06: Edycja dopasowania kategorii z poziomu operation_edit + opcjonalne masowe przepiecie operacji z tym samym itemem Fakturownia
+### Phase 6: AI title generation for tasks
+
+**Goal:** Dodac w popupie zadania przycisk generowania propozycji tytulu przez OpenAI na podstawie tresci zadania, widoczny tylko dla `biuro@project-pro.pl`
+**Depends on:** None (niezalezna funkcja w module zadan)
+**Research:** Done (OpenAI official model docs)
+
+**Scope:**
+- Przycisk AI w `templates/tasks/task_popup.php` tylko dla zalogowanego `biuro@project-pro.pl`
+- Backend AJAX generujacy propozycje tytulu bez zapisu do bazy
+- Osobny serwis domenowy OpenAI z modelem `gpt-5-nano`
+
+**Plans:**
+- [x] 06-01: Generowanie propozycji tytulu zadania przez OpenAI w popupie
---
*Roadmap created: 2026-03-15*
-*Last updated: 2026-05-04 - Phase 5 complete (05-01..05-06)*
+*Last updated: 2026-05-04 - Phase 6 complete (06-01)*
+
+
diff --git a/.paul/STATE.md b/.paul/STATE.md
index bbc309e..cef0560 100644
--- a/.paul/STATE.md
+++ b/.paul/STATE.md
@@ -9,14 +9,14 @@ See: .paul/PROJECT.md (updated 2026-05-04)
## Current Position
-Milestone: v0.1 Stabilizacja i jakosc kodu
-Phase: 2 of 5 (Naprawa bledow krytycznych) - Ready to plan
+Milestone: v0.1 Stabilizacja i jakosc kodu + import finansow
+Phase: 2 of 6 (Naprawa bledow krytycznych) - Ready to plan
Plan: Not started
-Status: Phase 5 complete, ready for next PLAN
-Last activity: 2026-05-04 22:54 - Phase 5 complete; 05-06 UNIFY closed
+Status: Phase 6 complete, ready for next PLAN
+Last activity: 2026-05-04 23:45 - Phase 6 complete; 06-01 UNIFY closed
Progress:
-- Milestone: [####------] 40% (2 of 5 phases complete: Phase 1 and Phase 5)
+- Milestone: [#####-----] 50% (3 of 6 phases complete: Phase 1, Phase 5 and Phase 6)
- Phase 2: [----------] 0%
## Loop Position
@@ -30,6 +30,11 @@ PLAN --> APPLY --> UNIFY
## Accumulated Context
### Recent Decisions
+- Phase 6: AI title generation is visible only for logged-in `biuro@project-pro.pl`.
+- Phase 6: Generated title is inserted into the inline title input only; existing save action persists it.
+- Phase 6: Use `gpt-5-nano` as the cheap/fast OpenAI model for task title suggestions.
+- Phase 6: GPT-5 title generation uses `reasoning_effort = minimal` and higher `max_completion_tokens` to avoid empty content.
+- Phase 6: Prompt requires short bezosobowy noun-style titles, e.g. `Usuniecie bloku o firmie`.
- Phase 5: Fakturownia client mappings use tax-based keys with legacy id fallback.
- Phase 5: Proforma documents are skipped by kind and FP prefix.
- Phase 5: Selected invoice positions can be skipped without creating finance operations.
@@ -38,16 +43,17 @@ PLAN --> APPLY --> UNIFY
### Concerns
- Existing Fakturownia operations identify item mapping from operation description plus mapping fallback. Future importer work should store explicit operation_id -> item_key metadata.
+- OpenAI API key remains configured directly in `config.php`; this was pre-existing and should be considered for future secret management cleanup.
### Git State
-Last commit: 7acf22c
+Last commit: pending phase transition commit
Branch: main
Feature branches merged: none
## Session Continuity
Last session: 2026-05-04
-Stopped at: Phase 5 complete, ready to plan Phase 2
+Stopped at: Phase 6 complete, ready to plan Phase 2
Next action: $paul-plan for Phase 2
Resume file: .paul/ROADMAP.md
diff --git a/.paul/changelog/2026-05-04.md b/.paul/changelog/2026-05-04.md
index 7006782..e5cf1be 100644
--- a/.paul/changelog/2026-05-04.md
+++ b/.paul/changelog/2026-05-04.md
@@ -9,6 +9,10 @@
- Naprawiono zapis formularza przez usuniecie zagniezdzonego formularza w `gridEdit`.
- Zamknieto formalnie Phase 5 jako kompletna.
+- [Phase 6, Plan 06-01] Dodano generowanie propozycji tytulu zadania przez OpenAI w popupie zadania dla `biuro@project-pro.pl`.
+- Dodano endpoint AJAX, ktory zwraca propozycje tytulu bez zapisu do bazy.
+- Dodano serwis `TaskTitleGenerator` z modelem `gpt-5-nano` i promptem na krotki tytul bezosobowy.
+
## Zmienione pliki
- `.paul/PROJECT.md`
@@ -20,3 +24,10 @@
- `autoload/Controllers/FinancesController.php`
- `autoload/Domain/Finances/FinanceRepository.php`
- `templates/finances/operation-edit.php`
+- `.paul/phases/06-task-title-ai/06-01-PLAN.md`
+- `.paul/phases/06-task-title-ai/06-01-SUMMARY.md`
+- `autoload/Domain/Tasks/TaskTitleGenerator.php`
+- `autoload/controls/class.Tasks.php`
+- `config.php`
+- `templates/tasks/task_popup.php`
+
diff --git a/.paul/phases/06-task-title-ai/06-01-PLAN.md b/.paul/phases/06-task-title-ai/06-01-PLAN.md
new file mode 100644
index 0000000..2629c1d
--- /dev/null
+++ b/.paul/phases/06-task-title-ai/06-01-PLAN.md
@@ -0,0 +1,201 @@
+---
+phase: 06-task-title-ai
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - config.php
+ - autoload/Domain/Tasks/TaskTitleGenerator.php
+ - autoload/controls/class.Tasks.php
+ - templates/tasks/task_popup.php
+autonomous: false
+delegation: off
+---
+
+
+## Goal
+Dodac w popupie edycji zadania przycisk generowania propozycji tytulu zadania przez OpenAI na podstawie tresci zadania.
+
+## Purpose
+Uzytkownik `biuro@project-pro.pl` ma szybciej nadawac czytelne tytuly zadaniom bez recznego streszczania opisu.
+
+## Output
+- Przycisk AI obok istniejacego przycisku edycji tytulu w `task_popup`.
+- Osobny endpoint AJAX zwracajacy wygenerowana propozycje tytulu.
+- Serwis domenowy do komunikacji z OpenAI, uzywajacy taniego i szybkiego modelu `gpt-5-nano`.
+- Konfiguracja modelu tytulow niezalezna od importu maili.
+
+
+
+
+- **[Widocznosc]** - Czy `biuro@project-pro.pl` oznacza zalogowanego uzytkownika czy zadania przypisane do tego uzytkownika?
+ -> Odpowiedz: tylko zalogowany uzytkownik z tym emailem widzi przycisk.
+- **[Zapis]** - Czy AI ma od razu nadpisac tytul w bazie czy tylko wstawic propozycje do pola?
+ -> Odpowiedz: opcja A, wstawic propozycje do pola tytulu i zapisac dopiero istniejacym przyciskiem.
+- **[Model]** - Czy uzyc `gpt-5-nano` jako tani/szybki model?
+ -> Odpowiedz: tak.
+
+
+## Project Context
+@.paul/PROJECT.md
+@.paul/ROADMAP.md
+@.paul/STATE.md
+
+## Source Files
+@config.php
+@autoload/Domain/Tasks/MailToTaskImporter.php
+@autoload/controls/class.Tasks.php
+@templates/tasks/task_popup.php
+@templates/tasks/main_view.php
+
+## External References
+- OpenAI model docs: https://developers.openai.com/api/docs/models/gpt-5-nano
+- `gpt-5-nano` supports `v1/chat/completions` and is documented as the fastest/cheapest GPT-5 option for summarization/classification-style work.
+
+
+
+
+## AC-1: Przycisk widoczny tylko dla biuro
+```gherkin
+Given zalogowany uzytkownik ma email biuro@project-pro.pl
+When otwiera popup zadania
+Then obok przycisku edycji tytulu widzi przycisk generowania tytulu AI
+```
+
+## AC-2: Brak przycisku dla innych uzytkownikow
+```gherkin
+Given zalogowany uzytkownik ma inny email niz biuro@project-pro.pl
+When otwiera popup zadania
+Then przycisk generowania tytulu AI nie jest renderowany
+And endpoint generowania odrzuca zadanie bez wywolania OpenAI
+```
+
+## AC-3: AI generuje propozycje bez zapisu do bazy
+```gherkin
+Given uzytkownik biuro@project-pro.pl otwiera popup zadania z trescia
+When klika przycisk generowania tytulu AI
+Then system pobiera tresc zadania i zwraca krotka propozycje tytulu
+And propozycja trafia do pola edycji tytulu
+And tytul w bazie nie zmienia sie przed kliknieciem istniejacego przycisku zapisu
+```
+
+## AC-4: Obsluga bledow OpenAI jest czytelna
+```gherkin
+Given brakuje klucza API OpenAI albo OpenAI zwroci blad
+When uzytkownik klika generowanie tytulu
+Then popup pokazuje czytelny komunikat bledu
+And aktualny tytul zadania nie zostaje zmieniony
+```
+
+
+
+
+
+
+ Task 1: Dodac serwis generowania tytulu przez OpenAI
+ config.php, autoload/Domain/Tasks/TaskTitleGenerator.php
+
+ Dodac konfiguracje `openai_task_title_model` z domyslna wartoscia `gpt-5-nano`.
+ Utworzyc klase domenowa `Domain\Tasks\TaskTitleGenerator`, ktora:
+ - przyjmuje klucz API i model z konfiguracji,
+ - usuwa HTML z tresci zadania przed wyslaniem,
+ - wysyla krotki prompt do `https://api.openai.com/v1/chat/completions`,
+ - wymusza pojedynczy, krotki tytul bez JSON i bez dodatkowego komentarza,
+ - ogranicza dlugosc wejscia i wyjscia,
+ - zwraca tablice sukces/blad bez rzucania nieobslugiwanych wyjatkow.
+ Reuzyc wzorce z `MailToTaskImporter`, ale nie uzalezniac nowej funkcji od importera maili.
+ Dla modeli `gpt-5*` uzyc parametru `max_completion_tokens`; dla pozostalych zostawic kompatybilny fallback.
+
+ C:\xampp\php\php.exe -l autoload/Domain/Tasks/TaskTitleGenerator.php
+ AC-3 i AC-4 maja pokrycie po stronie integracji OpenAI
+
+
+
+ Task 2: Dodac endpoint AJAX dla propozycji tytulu
+ autoload/controls/class.Tasks.php
+
+ Dodac metode kontrolera np. `task_generate_title`, ktora:
+ - wymaga zalogowanego uzytkownika,
+ - sprawdza `strtolower($user['email']) === 'biuro@project-pro.pl'`,
+ - pobiera `task_id` jako int i odczytuje zadanie przez `factory\Tasks::task_details`,
+ - odrzuca brak zadania lub pusta tresc komunikatem JSON,
+ - wywoluje `TaskTitleGenerator`,
+ - zwraca JSON `{status: "success", title: "..."}` albo `{status: "error", msg: "..."}`.
+ Endpoint nie moze zapisywac `tasks.name`; zapis pozostaje w istniejacym `/tasks/task_change_title/`.
+ Uzyc medoo/prepared statements przez istniejace metody lub medoo API, bez skladania SQL.
+
+ C:\xampp\php\php.exe -l autoload/controls/class.Tasks.php
+ AC-2, AC-3 i AC-4 spelnione po stronie backendu
+
+
+
+ Task 3: Dodac przycisk i obsluge UI w popupie zadania
+ templates/tasks/task_popup.php
+
+ Rozszerzyc widok tytulu w popupie:
+ - wyrenderowac przycisk AI tylko gdy `$this->user['email']` to `biuro@project-pro.pl`,
+ - ustawic przycisk obok istniejacego przycisku edycji tytulu,
+ - po kliknieciu pokazac stan ladowania i wywolac `/tasks/task_generate_title/`,
+ - po sukcesie otworzyc inline edycje tytulu i wpisac propozycje do `.task-title-input`,
+ - nie klikac automatycznie `.task-title-save`,
+ - po bledzie pokazac komunikat i zostawic aktualny tytul bez zmian.
+ Zachowac istniejacy mechanizm recznej edycji i zapisu tytulu.
+ Escapowac dane w widoku; nie wprowadzac logiki OpenAI do widoku.
+
+ C:\xampp\php\php.exe -l templates/tasks/task_popup.php
+ AC-1, AC-2 i AC-3 spelnione w UI
+
+
+
+ Przycisk AI w popupie zadania oraz generowanie propozycji tytulu do pola edycji.
+
+ 1. Zaloguj sie jako `biuro@project-pro.pl`.
+ 2. Otworz liste zadan i popup zadania z niepusta trescia.
+ 3. Kliknij przycisk AI obok edycji tytulu.
+ 4. Potwierdz, ze pole edycji tytulu wypelnia sie propozycja, ale tytul zapisuje sie dopiero po kliknieciu istniejacego przycisku zapisu.
+ 5. Sprawdz u innego uzytkownika, ze przycisk nie jest widoczny.
+
+ Napisz "approved", jesli dziala, albo opisz blad do poprawy.
+
+
+
+
+
+
+## DO NOT CHANGE
+- Nie zmieniac istniejacego endpointu `/tasks/task_change_title/` poza ewentualna minimalna walidacja, jesli bedzie konieczna.
+- Nie zmieniac importu maili w `MailToTaskImporter`.
+- Nie dodawac nowych bibliotek ani zaleznosci Composer.
+- Nie zmieniac schematu bazy danych.
+
+## SCOPE LIMITS
+- Funkcja dotyczy tylko popupu zadania, nie pelnego formularza `task_edit`.
+- AI generuje tylko propozycje tytulu; nie generuje ani nie zmienia tresci zadania.
+- Przycisk jest dostepny tylko dla zalogowanego `biuro@project-pro.pl`.
+- Brak automatycznego zapisu tytulu po generowaniu.
+
+
+
+
+Before declaring plan complete:
+- [ ] `C:\xampp\php\php.exe -l config.php`
+- [ ] `C:\xampp\php\php.exe -l autoload/Domain/Tasks/TaskTitleGenerator.php`
+- [ ] `C:\xampp\php\php.exe -l autoload/controls/class.Tasks.php`
+- [ ] `C:\xampp\php\php.exe -l templates/tasks/task_popup.php`
+- [ ] Reczna proba jako `biuro@project-pro.pl`: przycisk widoczny i generuje propozycje do pola.
+- [ ] Reczna proba jako inny uzytkownik: przycisk niewidoczny, endpoint odrzuca dostep.
+- [ ] Reczna proba potwierdza, ze baza zmienia tytul dopiero po istniejacym zapisie.
+- [ ] Wszystkie AC spelnione.
+
+
+
+- `biuro@project-pro.pl` moze wygenerowac krotki tytul z tresci zadania w popupie.
+- Wygenerowany tytul jest tylko propozycja w polu edycji, bez automatycznego zapisu.
+- Inni uzytkownicy nie widza przycisku i nie moga uzyc endpointu.
+- Bledy OpenAI nie psuja popupu i nie zmieniaja danych.
+
+
+
diff --git a/.paul/phases/06-task-title-ai/06-01-SUMMARY.md b/.paul/phases/06-task-title-ai/06-01-SUMMARY.md
new file mode 100644
index 0000000..8e5b76a
--- /dev/null
+++ b/.paul/phases/06-task-title-ai/06-01-SUMMARY.md
@@ -0,0 +1,144 @@
+---
+phase: 06-task-title-ai
+plan: 01
+subsystem: tasks-ai-ui
+tags: [tasks, openai, popup, ajax, gpt-5-nano]
+requires: []
+provides:
+ - AI title suggestion button in task popup for biuro@project-pro.pl
+ - AJAX endpoint returning title suggestions without saving task name
+ - Domain service for OpenAI task title generation
+affects: [tasks, openai-integration]
+tech-stack:
+ added: []
+ patterns: [domain-service-openai, ajax-suggestion-with-explicit-save]
+key-files:
+ created:
+ - autoload/Domain/Tasks/TaskTitleGenerator.php
+ modified:
+ - config.php
+ - autoload/controls/class.Tasks.php
+ - templates/tasks/task_popup.php
+key-decisions:
+ - "AI title button visible only for logged-in biuro@project-pro.pl"
+ - "Generated title is inserted into the inline input and saved only by existing save action"
+ - "Task title model defaults to gpt-5-nano"
+patterns-established:
+ - "OpenAI task-title generation is isolated in Domain\\Tasks\\TaskTitleGenerator"
+ - "Popup AI actions must return suggestions, not persist task data directly"
+duration: 35min
+started: 2026-05-04T23:10:00+02:00
+completed: 2026-05-04T23:45:00+02:00
+---
+
+# Phase 6 Plan 01: AI Task Title Summary
+
+AI title suggestions now work in the task popup for `biuro@project-pro.pl`, inserting a short bezosobowy title proposal into the existing title input without automatic database save.
+
+## Performance
+
+| Metric | Value |
+|--------|-------|
+| Duration | ~35min |
+| Started | 2026-05-04T23:10:00+02:00 |
+| Completed | 2026-05-04T23:45:00+02:00 |
+| Tasks | 3 auto tasks + 1 human verification checkpoint |
+| Files modified | 4 source/config files + PAUL docs |
+
+## Acceptance Criteria Results
+
+| Criterion | Status | Notes |
+|-----------|--------|-------|
+| AC-1: Przycisk widoczny tylko dla biuro | Pass | `task_popup.php` renders AI button only when logged-in user email is `biuro@project-pro.pl`. |
+| AC-2: Brak przycisku dla innych uzytkownikow | Pass | UI condition hides button; backend endpoint checks the same email before calling OpenAI. |
+| AC-3: AI generuje propozycje bez zapisu do bazy | Pass | `/tasks/task_generate_title/` returns JSON with `title`; JS inserts it into `.task-title-input`; existing `/tasks/task_change_title/` remains the only save path. |
+| AC-4: Obsluga bledow OpenAI jest czytelna | Pass | Service returns `{status:error,msg}` for missing key, empty task text, cURL/API errors, and empty OpenAI content; UI shows alert and leaves title unchanged. |
+
+## Accomplishments
+
+- Added `Domain\Tasks\TaskTitleGenerator` to encapsulate OpenAI title generation.
+- Added `$settings['openai_task_title_model'] = 'gpt-5-nano'` for task-title generation independent from email import parsing.
+- Added `/tasks/task_generate_title/` AJAX endpoint with user/email gate and no write to `tasks.name`.
+- Added AI button in task popup, loading state, AJAX call, and insertion of the generated suggestion into the existing inline title editor.
+- Adjusted GPT-5 request for practical output by using `reasoning_effort = minimal` and larger `max_completion_tokens`.
+- Refined prompt so titles are short, bezosobowe, and rzeczownikowe, e.g. `Usuniecie bloku o firmie`.
+
+## Files Created/Modified
+
+| File | Change | Purpose |
+|------|--------|---------|
+| `autoload/Domain/Tasks/TaskTitleGenerator.php` | Created | OpenAI client/service for short task title suggestions. |
+| `config.php` | Modified | Added `openai_task_title_model` defaulting to `gpt-5-nano`. |
+| `autoload/controls/class.Tasks.php` | Modified | Added `task_generate_title()` endpoint with authorization and JSON response. |
+| `templates/tasks/task_popup.php` | Modified | Added conditional AI button and client-side generation flow. |
+| `.paul/phases/06-task-title-ai/06-01-PLAN.md` | Created | Formal PAUL plan for the phase. |
+| `.paul/phases/06-task-title-ai/06-01-SUMMARY.md` | Created | This execution summary. |
+
+## Verification Results
+
+| Check | Result |
+|-------|--------|
+| `C:\xampp\php\php.exe -l config.php` | Pass |
+| `C:\xampp\php\php.exe -l autoload/Domain/Tasks/TaskTitleGenerator.php` | Pass |
+| `C:\xampp\php\php.exe -l autoload/controls/class.Tasks.php` | Pass |
+| `C:\xampp\php\php.exe -l templates/tasks/task_popup.php` | Pass |
+| Manual user verification | Pass after prompt/token fix; user confirmed OK. |
+
+## Decisions Made
+
+| Decision | Rationale | Impact |
+|----------|-----------|--------|
+| Use `gpt-5-nano` | User asked for cheap/fast model and approved this model. | Separate setting avoids changing email import behavior. |
+| Insert suggestion only, do not auto-save | User selected safer UX. | Existing save path remains authoritative. |
+| Gate by logged-in email | User clarified scope. | Both UI and backend enforce `biuro@project-pro.pl`. |
+| Use bezosobowy prompt | User requested shorter non-personal titles. | AI output better matches CRM task naming convention. |
+
+## Deviations from Plan
+
+### Summary
+
+| Type | Count | Impact |
+|------|-------|--------|
+| Auto-fixed | 2 | Necessary fixes for GPT-5 output reliability and title style. |
+| Scope additions | 0 | No scope creep. |
+| Deferred | 0 | None. |
+
+### Auto-fixed Issues
+
+**1. GPT-5 empty content with low completion limit**
+- **Found during:** Human verification
+- **Issue:** OpenAI returned HTTP 200 but no usable `message.content`, resulting in `OpenAI nie zwrocil poprawnego tytulu.`
+- **Fix:** Added `reasoning_effort = minimal` and raised `max_completion_tokens` to 500 for `gpt-5*` models.
+- **Files:** `autoload/Domain/Tasks/TaskTitleGenerator.php`
+- **Verification:** `php -l` passed; user continued testing.
+
+**2. Prompt title style too broad**
+- **Found during:** Human verification
+- **Issue:** User wanted bezosobowy, shortened titles such as `Usuniecie bloku o firmie`.
+- **Fix:** Prompt now requires max 6 words, noun-like bezosobowa form, no imperative verbs.
+- **Files:** `autoload/Domain/Tasks/TaskTitleGenerator.php`
+- **Verification:** `php -l` passed; user confirmed OK.
+
+## Issues Encountered
+
+| Issue | Resolution |
+|-------|------------|
+| `apply_patch` could not update files with legacy encoding reliably | Used PowerShell with project file encoding for existing files; new PHP service was rewritten UTF-8 without BOM after `php -l` caught BOM before namespace. |
+| Initial CSS insertion in popup selector block was malformed | Corrected CSS selector block and reran `php -l`. |
+
+## Next Phase Readiness
+
+**Ready:**
+- Phase 6 is complete and can be used from the task popup.
+- OpenAI title generation is isolated and configurable.
+- Phase 2 critical bug fixes can resume next.
+
+**Concerns:**
+- Existing OpenAI API key remains in `config.php`; this was pre-existing and not changed except for adding the model setting.
+
+**Blockers:**
+- None.
+
+---
+*Phase: 06-task-title-ai, Plan: 01*
+*Completed: 2026-05-04*
diff --git a/autoload/Domain/Tasks/TaskTitleGenerator.php b/autoload/Domain/Tasks/TaskTitleGenerator.php
new file mode 100644
index 0000000..0466e2c
--- /dev/null
+++ b/autoload/Domain/Tasks/TaskTitleGenerator.php
@@ -0,0 +1,190 @@
+ prepareTaskText( $task_text );
+
+ if ( $api_key === '' )
+ return $this -> error( 'Brak klucza API OpenAI.' );
+
+ if ( $model === '' )
+ $model = 'gpt-5-nano';
+
+ if ( $task_text === '' )
+ return $this -> error( 'Brak tresci zadania do wygenerowania tytulu.' );
+
+ if ( !function_exists( 'curl_init' ) )
+ return $this -> error( 'Brak rozszerzenia cURL.' );
+
+ $payload = $this -> buildPayload( $model, $task_text );
+ $response = $this -> sendRequest( $api_key, $payload );
+ if ( $response['status'] !== 'success' )
+ return $response;
+
+ $title = $this -> extractTitle( $response['body'] );
+ if ( $title === '' )
+ return $this -> error( 'OpenAI nie zwrocil poprawnego tytulu.' );
+
+ return [
+ 'status' => 'success',
+ 'title' => $title
+ ];
+ }
+
+ private function buildPayload( $model, $task_text )
+ {
+ $payload = [
+ 'model' => $model,
+ 'messages' => [
+ [
+ 'role' => 'system',
+ 'content' => 'Tworzysz bardzo krotkie, bezosobowe tytuly zadan w CRM. Odpowiadasz tylko tytulem, bez komentarza.'
+ ],
+ [
+ 'role' => 'user',
+ 'content' => "Na podstawie tresci zadania zaproponuj jeden skrocony tytul po polsku.\n" .
+ "Wymagania:\n" .
+ "- maksymalnie 6 slow,\n" .
+ "- forma bezosobowa rzeczownikowa, np. \"Usuniecie bloku o firmie\",\n" .
+ "- bez trybu rozkazujacego i bez form typu \"usun\", \"dodaj\", \"popraw\",\n" .
+ "- bez cudzyslowow,\n" .
+ "- bez kropki na koncu,\n" .
+ "- bez ogolnikow typu \"zadanie\" lub \"sprawa\".\n\n" .
+ "Tresc zadania:\n" . $task_text
+ ]
+ ]
+ ];
+
+ if ( stripos( $model, 'gpt-5' ) === 0 )
+ {
+ $payload['reasoning_effort'] = 'minimal';
+ $payload['max_completion_tokens'] = 500;
+ }
+ else
+ {
+ $payload['temperature'] = 0.2;
+ $payload['max_tokens'] = 80;
+ }
+
+ return $payload;
+ }
+
+ private function sendRequest( $api_key, array $payload )
+ {
+ $ch = curl_init( self::ENDPOINT );
+ curl_setopt_array( $ch, [
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_POST => true,
+ CURLOPT_POSTFIELDS => json_encode( $payload ),
+ CURLOPT_HTTPHEADER => [
+ 'Content-Type: application/json',
+ 'Authorization: Bearer ' . $api_key
+ ],
+ CURLOPT_TIMEOUT => 20
+ ] );
+
+ $body = curl_exec( $ch );
+ $curl_error = curl_error( $ch );
+ $http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
+ curl_close( $ch );
+
+ if ( $body === false )
+ return $this -> error( 'Blad polaczenia z OpenAI: ' . $curl_error );
+
+ if ( (int)$http_code !== 200 )
+ return $this -> error( 'OpenAI zwrocil blad HTTP ' . (int)$http_code . ': ' . $this -> extractApiErrorMessage( $body ) );
+
+ if ( trim( (string)$body ) === '' )
+ return $this -> error( 'Pusta odpowiedz z OpenAI.' );
+
+ return [
+ 'status' => 'success',
+ 'body' => (string)$body
+ ];
+ }
+
+ private function extractTitle( $response_body )
+ {
+ $data = json_decode( (string)$response_body, true );
+ if ( !is_array( $data ) )
+ return '';
+
+ if ( isset( $data['error']['message'] ) )
+ return '';
+
+ $content = '';
+ if ( isset( $data['choices'][0]['message']['content'] ) )
+ $content = $this -> normalizeContent( $data['choices'][0]['message']['content'] );
+
+ $content = trim( preg_replace( '/\s+/', ' ', $content ) );
+ $content = trim( $content, " \t\n\r\0\x0B\"'`." );
+
+ if ( $content === '' )
+ return '';
+
+ return mb_substr( $content, 0, self::TITLE_LIMIT );
+ }
+
+ private function normalizeContent( $content )
+ {
+ if ( is_string( $content ) )
+ return $content;
+
+ if ( !is_array( $content ) )
+ return '';
+
+ $parts = [];
+ foreach ( $content as $item )
+ {
+ if ( is_string( $item ) )
+ $parts[] = $item;
+ elseif ( is_array( $item ) && isset( $item['text'] ) && is_string( $item['text'] ) )
+ $parts[] = $item['text'];
+ elseif ( is_array( $item ) && isset( $item['text']['value'] ) && is_string( $item['text']['value'] ) )
+ $parts[] = $item['text']['value'];
+ }
+
+ return implode( ' ', $parts );
+ }
+
+ private function prepareTaskText( $task_text )
+ {
+ $task_text = html_entity_decode( (string)$task_text, ENT_QUOTES, 'UTF-8' );
+ $task_text = preg_replace( '/