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. + + + +Po wykonaniu utworz `.paul/phases/06-task-title-ai/06-01-SUMMARY.md`. + 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( '/]*>.*?<\/script>/is', ' ', $task_text ); + $task_text = preg_replace( '/]*>.*?<\/style>/is', ' ', $task_text ); + $task_text = strip_tags( $task_text ); + $task_text = preg_replace( '/\s+/', ' ', $task_text ); + $task_text = trim( $task_text ); + + if ( $task_text === '' ) + return ''; + + return mb_substr( $task_text, 0, self::CONTENT_LIMIT ); + } + + private function extractApiErrorMessage( $response_body ) + { + $data = json_decode( (string)$response_body, true ); + if ( is_array( $data ) && isset( $data['error']['message'] ) ) + return (string)$data['error']['message']; + + return mb_substr( trim( (string)$response_body ), 0, 300 ); + } + + private function error( $message ) + { + return [ + 'status' => 'error', + 'msg' => (string)$message + ]; + } +} diff --git a/autoload/controls/class.Tasks.php b/autoload/controls/class.Tasks.php index 4afeb52..f223408 100644 --- a/autoload/controls/class.Tasks.php +++ b/autoload/controls/class.Tasks.php @@ -372,6 +372,51 @@ class Tasks exit; } + static public function task_generate_title() + { + global $user, $settings; + + $response = [ 'status' => 'error', 'msg' => 'Nie udaÅ‚o siÄ™ wygenerować tytuÅ‚u zadania.' ]; + + if ( !$user or !isset( $user['email'] ) or strtolower( trim( (string)$user['email'] ) ) !== 'biuro@project-pro.pl' ) + { + echo json_encode( [ 'status' => 'error', 'msg' => 'Brak uprawnieÅ„ do generowania tytuÅ‚u.' ] ); + exit; + } + + $task_id = (int)\S::get( 'task_id' ); + if ( !$task_id ) + { + echo json_encode( [ 'status' => 'error', 'msg' => 'NieprawidÅ‚owe zadanie.' ] ); + exit; + } + + $task = \factory\Tasks::task_details( $task_id, (int)$user['id'] ); + if ( !is_array( $task ) or !isset( $task['id'] ) ) + { + echo json_encode( [ 'status' => 'error', 'msg' => 'Nie znaleziono zadania.' ] ); + exit; + } + + $task_text = isset( $task['text'] ) ? trim( (string)$task['text'] ) : ''; + if ( $task_text === '' ) + { + echo json_encode( [ 'status' => 'error', 'msg' => 'Zadanie nie ma treÅ›ci do wygenerowania tytuÅ‚u.' ] ); + exit; + } + + $api_key = isset( $settings['openai_api_key'] ) ? trim( (string)$settings['openai_api_key'] ) : ''; + $model = isset( $settings['openai_task_title_model'] ) ? trim( (string)$settings['openai_task_title_model'] ) : 'gpt-5-nano'; + $generator = new \Domain\Tasks\TaskTitleGenerator(); + $result = $generator -> generate( $api_key, $model, $task_text ); + + if ( is_array( $result ) and isset( $result['status'] ) ) + $response = $result; + + echo json_encode( $response ); + exit; + } + static public function task_change_text() { global $mdb; diff --git a/config.php b/config.php index ada2e8d..55c2c5e 100644 --- a/config.php +++ b/config.php @@ -23,3 +23,5 @@ $imap_tasks['password'] = 'ProjectPro2025!'; $settings['openai_api_key'] = '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'; // Wklej tutaj swój klucz API OpenAI $settings['openai_parse_emails'] = false; // true = użyj AI do parsowania emaili, false = normalne parsowanie $settings['openai_model'] = 'gpt-4o-mini'; // Model: gpt-4o-mini, gpt-4o, gpt-5-nano, itp. +$settings['openai_task_title_model'] = 'gpt-5-nano'; // Tani i szybki model do generowania tytulow zadan + diff --git a/templates/tasks/task_popup.php b/templates/tasks/task_popup.php index a153d46..1d274a8 100644 --- a/templates/tasks/task_popup.php +++ b/templates/tasks/task_popup.php @@ -1,3 +1,6 @@ + user['email'] ) && strtolower( trim( (string)$this -> user['email'] ) ) === 'biuro@project-pro.pl'; +?>
@@ -12,8 +15,11 @@ # task['id'];?> - task['name'];?> + task['name'] );?> + + +