feat: Enhance MailToTaskImporter with AI error handling and update OpenAI model to gpt-4o-mini
feat: Add Gantt task normalization and filter selection in main_view.php
This commit is contained in:
159
.vscode/ftp-kr.sync.cache.json
vendored
159
.vscode/ftp-kr.sync.cache.json
vendored
@@ -40,6 +40,92 @@
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"Users": {
|
||||
"UserRepository.php": {
|
||||
"type": "-",
|
||||
"size": 610,
|
||||
"lmtime": 1770653480229,
|
||||
"modified": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"factory": {
|
||||
"class.BackendSites.php": {
|
||||
"type": "-",
|
||||
"size": 2962,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"class.Crm.php": {
|
||||
"type": "-",
|
||||
"size": 1863,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"class.Cron.php": {
|
||||
"type": "-",
|
||||
"size": 26120,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"class.Finances.php": {
|
||||
"type": "-",
|
||||
"size": 16159,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"class.Projects.php": {
|
||||
"type": "-",
|
||||
"size": 27476,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"class.Tasks.php": {
|
||||
"type": "-",
|
||||
"size": 20921,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"class.Users.php": {
|
||||
"type": "-",
|
||||
"size": 2072,
|
||||
"lmtime": 1770654026945,
|
||||
"modified": false
|
||||
},
|
||||
"class.Wiki.php": {
|
||||
"type": "-",
|
||||
"size": 1911,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"Controllers": {
|
||||
"class.TasksController.php": {
|
||||
"type": "-",
|
||||
"size": 567,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"TasksController.php": {
|
||||
"type": "-",
|
||||
"size": 3009,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"UsersController.php": {
|
||||
"type": "-",
|
||||
"size": 3914,
|
||||
"lmtime": 1770653696575,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
"class.Users.php": {
|
||||
"type": "-",
|
||||
"size": 4242,
|
||||
"lmtime": 1770653518273,
|
||||
"modified": false
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -57,6 +143,12 @@
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"CODE_INDEX.md": {
|
||||
"type": "-",
|
||||
"size": 16884,
|
||||
"lmtime": 1770652965090,
|
||||
"modified": false
|
||||
},
|
||||
"config.php": {
|
||||
"type": "-",
|
||||
"size": 1249,
|
||||
@@ -140,9 +232,74 @@
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"login-form.php": {
|
||||
"type": "-",
|
||||
"size": 3336,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"main-view.php": {
|
||||
"type": "-",
|
||||
"size": 2012,
|
||||
"lmtime": 1770653623175,
|
||||
"modified": false
|
||||
},
|
||||
"settings.php": {
|
||||
"type": "-",
|
||||
"size": 3457,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"site": {
|
||||
"layout-cron.php": {
|
||||
"type": "-",
|
||||
"size": 6369,
|
||||
"lmtime": 1770653884637,
|
||||
"modified": false
|
||||
},
|
||||
"layout-logged.php": {
|
||||
"type": "-",
|
||||
"size": 6762,
|
||||
"lmtime": 1770653877600,
|
||||
"modified": false
|
||||
},
|
||||
"layout-unlogged.php": {
|
||||
"type": "-",
|
||||
"size": 986,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"tests": {
|
||||
"Controllers": {
|
||||
"UsersControllerTest.php": {
|
||||
"type": "-",
|
||||
"size": 1839,
|
||||
"lmtime": 1770653599232,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"Domain": {
|
||||
"Users": {
|
||||
"UserRepositoryTest.php": {
|
||||
"type": "-",
|
||||
"size": 1540,
|
||||
"lmtime": 1770653587539,
|
||||
"modified": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"run.php": {
|
||||
"type": "-",
|
||||
"size": 851,
|
||||
"lmtime": 1770653605021,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"tests": {},
|
||||
"tmp_debug_mail_import.php": {
|
||||
"type": "-",
|
||||
"size": 1238,
|
||||
|
||||
@@ -10,6 +10,7 @@ class MailToTaskImporter
|
||||
|
||||
private $mdb;
|
||||
private $attachments;
|
||||
private $last_ai_error;
|
||||
|
||||
public function __construct( $mdb = null )
|
||||
{
|
||||
@@ -22,6 +23,7 @@ class MailToTaskImporter
|
||||
}
|
||||
|
||||
$this -> attachments = new TaskAttachmentRepository( $this -> mdb );
|
||||
$this -> last_ai_error = '';
|
||||
}
|
||||
|
||||
public function importFromImap( array $config )
|
||||
@@ -88,24 +90,38 @@ class MailToTaskImporter
|
||||
try
|
||||
{
|
||||
$content = $this -> extractMessageContent( $imap, $message_no );
|
||||
$ai_used = false;
|
||||
$ai_error_for_log = null;
|
||||
|
||||
// Sprawdź czy użyć AI do parsowania
|
||||
global $settings;
|
||||
$use_ai = isset( $settings['openai_parse_emails'] ) && $settings['openai_parse_emails'] === true;
|
||||
$api_key = isset( $settings['openai_api_key'] ) ? trim( (string)$settings['openai_api_key'] ) : '';
|
||||
if ( $use_ai && $api_key === '' )
|
||||
{
|
||||
$ai_error_for_log = 'Brak klucza API OpenAI.';
|
||||
error_log( '[MailToTaskImporter] AI fallback: ' . $ai_error_for_log );
|
||||
}
|
||||
|
||||
if ( $use_ai && $api_key !== '' )
|
||||
{
|
||||
$model = isset( $settings['openai_model'] ) ? trim( (string)$settings['openai_model'] ) : 'gpt-3.5-turbo';
|
||||
$model = isset( $settings['openai_model'] ) ? trim( (string)$settings['openai_model'] ) : 'gpt-4o-mini';
|
||||
$ai_result = $this -> parseWithAI( $api_key, $model, $subject, $content['text'] );
|
||||
if ( $ai_result !== null )
|
||||
if ( is_array( $ai_result ) && isset( $ai_result['task_name'] ) && isset( $ai_result['task_text'] ) )
|
||||
{
|
||||
$task_name = $ai_result['task_name'];
|
||||
$task_text = $ai_result['task_text'];
|
||||
$ai_used = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback do normalnego parsowania jeśli AI nie zadziała
|
||||
if ( $this -> last_ai_error !== '' )
|
||||
{
|
||||
error_log( '[MailToTaskImporter] AI fallback: ' . $this -> last_ai_error );
|
||||
$ai_error_for_log = $this -> last_ai_error;
|
||||
}
|
||||
|
||||
$task_name = trim( $subject ) !== '' ? trim( $subject ) : '(bez tematu)';
|
||||
$task_text = $this -> prepareImportedTaskText( $content['text'] );
|
||||
}
|
||||
@@ -161,7 +177,17 @@ class MailToTaskImporter
|
||||
$this -> attachments -> uploadFromContent( $task_id, self::TASK_USER_ID, $attachment['name'], $attachment['content'] );
|
||||
}
|
||||
|
||||
$this -> saveImportLog( $message_key, $task_id, $sender, $subject, 'imported', null );
|
||||
$import_status = 'imported';
|
||||
$import_error = null;
|
||||
if ( $use_ai && $ai_used )
|
||||
$import_status = 'imported_ai';
|
||||
elseif ( $use_ai && !$ai_used )
|
||||
{
|
||||
$import_status = 'imported_fallback';
|
||||
$import_error = $ai_error_for_log;
|
||||
}
|
||||
|
||||
$this -> saveImportLog( $message_key, $task_id, $sender, $subject, $import_status, $import_error );
|
||||
$imported++;
|
||||
}
|
||||
catch ( \Throwable $e )
|
||||
@@ -173,7 +199,7 @@ class MailToTaskImporter
|
||||
}
|
||||
|
||||
$status_tmp = $this -> getImportStatus( $message_key );
|
||||
if ( in_array( $status_tmp, [ 'imported', 'skipped_sender' ], true ) )
|
||||
if ( in_array( $status_tmp, [ 'imported', 'imported_ai', 'imported_fallback', 'skipped_sender' ], true ) )
|
||||
@imap_delete( $imap, $message_no );
|
||||
}
|
||||
|
||||
@@ -318,7 +344,7 @@ class MailToTaskImporter
|
||||
private function isMessageFinalized( $message_key )
|
||||
{
|
||||
$status = $this -> getImportStatus( $message_key );
|
||||
return in_array( $status, [ 'imported', 'skipped_sender' ], true );
|
||||
return in_array( $status, [ 'imported', 'imported_ai', 'imported_fallback', 'skipped_sender' ], true );
|
||||
}
|
||||
|
||||
private function getImportStatus( $message_key )
|
||||
@@ -675,14 +701,34 @@ class MailToTaskImporter
|
||||
return strtolower( $value );
|
||||
}
|
||||
|
||||
private function parseWithAI( $api_key, $model, $subject, $raw_content )
|
||||
private function parseWithAI( $api_key, $model, $subject, $raw_content, $attempt = 1 )
|
||||
{
|
||||
$this -> last_ai_error = '';
|
||||
$api_key = trim( (string)$api_key );
|
||||
$subject = trim( (string)$subject );
|
||||
$raw_content = trim( (string)$raw_content );
|
||||
$model = trim( (string)$model );
|
||||
|
||||
if ( $api_key === '' )
|
||||
{
|
||||
$this -> last_ai_error = 'Brak klucza API OpenAI.';
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( $model === '' )
|
||||
$model = 'gpt-3.5-turbo';
|
||||
$model = 'gpt-4o-mini';
|
||||
|
||||
if ( $raw_content === '' )
|
||||
{
|
||||
$this -> last_ai_error = 'Pusta tresc emaila do analizy AI.';
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( !function_exists( 'curl_init' ) )
|
||||
{
|
||||
$this -> last_ai_error = 'Brak rozszerzenia cURL.';
|
||||
return null;
|
||||
}
|
||||
|
||||
// Przygotuj treść do analizy (usuń HTML jesli jest)
|
||||
if ( preg_match( '/<\s*[a-z][^>]*>/i', $raw_content ) )
|
||||
@@ -691,31 +737,54 @@ class MailToTaskImporter
|
||||
$raw_content = $this -> cleanBodyText( $raw_content );
|
||||
}
|
||||
|
||||
$content_limit = $attempt > 1 ? 1200 : 1800;
|
||||
$prompt = "Jesteś asystentem do przetwarzania emaili na zadania w systemie CRM. " .
|
||||
"Na podstawie poniższego tematu i treści emaila, wygeneruj:\n" .
|
||||
"1. Zwięzły, konkretny temat zadania (max 100 znaków)\n" .
|
||||
"2. Czytelny opis zadania zawierający najważniejsze informacje\n\n" .
|
||||
"Na podstawie poniższego tematu i treści emaila wygeneruj zadanie wdrożeniowe, NIE podsumowanie. " .
|
||||
"Opis ma być szczegółową listą rzeczy do wykonania dla zespołu.\n\n" .
|
||||
"Wymagania:\n" .
|
||||
"- Nie pomijaj żadnych wymagań, liczb, zakresów cen, CTA, pól formularza, kroków i terminów.\n" .
|
||||
"- Zamień treść emaila na checklistę implementacyjną.\n" .
|
||||
"- Zachowaj strukturę sekcji i podsekcji.\n" .
|
||||
"- Pisz konkretnie co zmienić, gdzie zmienić i jakie treści dodać.\n" .
|
||||
"- Nie używaj ogólników typu: \"zaktualizować stronę\".\n\n" .
|
||||
"Format pola task_text:\n" .
|
||||
"1) Cel zmiany\n" .
|
||||
"2) Zakres zmian (lista punktów)\n" .
|
||||
"3) Treści do wstawienia (dokładne teksty/liczby)\n" .
|
||||
"4) Zmiany formularza\n" .
|
||||
"5) Wersja sezonowa / warunki wyświetlania\n" .
|
||||
"6) CTA i bonusy\n" .
|
||||
"7) Kryteria akceptacji\n\n" .
|
||||
"Temat emaila: " . $subject . "\n\n" .
|
||||
"Treść emaila:\n" . mb_substr( $raw_content, 0, 2000 ) . "\n\n" .
|
||||
"Odpowiedz TYLKO w formacie JSON bez żadnych dodatkowych wyjaśnień:\n" .
|
||||
'{"task_name": "temat zadania", "task_text": "opis zadania"}';
|
||||
"Treść emaila:\n" . mb_substr( $raw_content, 0, $content_limit ) . "\n\n" .
|
||||
"Zwróć TYLKO poprawny JSON:\n" .
|
||||
'{"task_name": "krótki temat zadania", "task_text": "szczegółowa checklista wdrożeniowa"}';
|
||||
|
||||
$payload = [
|
||||
'model' => $model,
|
||||
'messages' => [
|
||||
[
|
||||
'role' => 'system',
|
||||
'content' => 'Jesteś asystentem do przetwarzania emaili. Odpowiadasz TYLKO w formacie JSON.'
|
||||
'content' => 'Jesteś asystentem do przetwarzania emaili. Odpowiadasz TYLKO w formacie JSON i tworzysz szczegółowe checklisty wdrożeniowe, nie skróty.'
|
||||
],
|
||||
[
|
||||
'role' => 'user',
|
||||
'content' => $prompt
|
||||
]
|
||||
],
|
||||
'temperature' => 0.3,
|
||||
'max_tokens' => 500
|
||||
]
|
||||
];
|
||||
|
||||
if ( stripos( $model, 'gpt-5' ) === 0 )
|
||||
$payload['response_format'] = [ 'type' => 'json_object' ];
|
||||
|
||||
if ( stripos( $model, 'gpt-5' ) === 0 )
|
||||
$payload['max_completion_tokens'] = $attempt > 1 ? 1600 : 1000;
|
||||
else
|
||||
{
|
||||
$payload['temperature'] = 0.3;
|
||||
$payload['max_tokens'] = 1200;
|
||||
}
|
||||
|
||||
$ch = curl_init( 'https://api.openai.com/v1/chat/completions' );
|
||||
curl_setopt_array( $ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
@@ -729,28 +798,87 @@ class MailToTaskImporter
|
||||
] );
|
||||
|
||||
$response = curl_exec( $ch );
|
||||
$curl_error = curl_error( $ch );
|
||||
$http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
|
||||
curl_close( $ch );
|
||||
|
||||
if ( $http_code !== 200 || !$response )
|
||||
if ( $response === false )
|
||||
{
|
||||
$this -> last_ai_error = 'Blad cURL: ' . $curl_error;
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( $http_code !== 200 )
|
||||
{
|
||||
$this -> last_ai_error = 'HTTP ' . $http_code . ' z OpenAI: ' . $this -> extractApiErrorMessage( $response );
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( !$response )
|
||||
{
|
||||
$this -> last_ai_error = 'Pusta odpowiedz z OpenAI.';
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode( $response, true );
|
||||
if ( !isset( $data['choices'][0]['message']['content'] ) )
|
||||
if ( !is_array( $data ) )
|
||||
{
|
||||
$this -> last_ai_error = 'Niepoprawny JSON z OpenAI: ' . json_last_error_msg();
|
||||
return null;
|
||||
}
|
||||
|
||||
$content = trim( $data['choices'][0]['message']['content'] );
|
||||
if ( isset( $data['error']['message'] ) )
|
||||
{
|
||||
$this -> last_ai_error = 'OpenAI error: ' . (string)$data['error']['message'];
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( !isset( $data['choices'][0] ) || !is_array( $data['choices'][0] ) )
|
||||
{
|
||||
$this -> last_ai_error = 'Brak choices[0] w odpowiedzi OpenAI. RAW: ' . $this -> clipForLog( $response );
|
||||
return null;
|
||||
}
|
||||
|
||||
$choice = $data['choices'][0];
|
||||
$content = $this -> extractModelMessageContent( $choice );
|
||||
if ( $content === '' )
|
||||
{
|
||||
$finish_reason = isset( $choice['finish_reason'] ) ? (string)$choice['finish_reason'] : '(brak)';
|
||||
$refusal = isset( $choice['message']['refusal'] ) ? (string)$choice['message']['refusal'] : '';
|
||||
|
||||
if ( $finish_reason === 'length' && (int)$attempt === 1 )
|
||||
{
|
||||
$retry_content = mb_substr( $raw_content, 0, 1200 );
|
||||
return $this -> parseWithAI( $api_key, $model, $subject, $retry_content, 2 );
|
||||
}
|
||||
|
||||
$error = 'Pusta tresc odpowiedzi modelu. finish_reason=' . $finish_reason;
|
||||
if ( $refusal !== '' )
|
||||
$error .= ', refusal=' . $this -> clipForLog( $refusal );
|
||||
|
||||
$this -> last_ai_error = $error . '. RAW: ' . $this -> clipForLog( $response );
|
||||
return null;
|
||||
}
|
||||
|
||||
// Usuń markdown code block jeśli jest
|
||||
$content = preg_replace( '/```json\s*|\s*```/', '', $content );
|
||||
$content = trim( $content );
|
||||
|
||||
// Jeśli model zwrócił JSON z komentarzem wokół, wyciągnij tylko obiekt.
|
||||
$json_start = strpos( $content, '{' );
|
||||
$json_end = strrpos( $content, '}' );
|
||||
if ( $json_start !== false && $json_end !== false && $json_end > $json_start )
|
||||
$content = substr( $content, $json_start, $json_end - $json_start + 1 );
|
||||
|
||||
$parsed = json_decode( $content, true );
|
||||
if ( !is_array( $parsed ) || !isset( $parsed['task_name'] ) || !isset( $parsed['task_text'] ) )
|
||||
{
|
||||
$this -> last_ai_error = 'Niepoprawny JSON z modelu. Odpowiedz: ' . $this -> clipForLog( $content );
|
||||
return null;
|
||||
}
|
||||
|
||||
$task_name = trim( (string)$parsed['task_name'] );
|
||||
$task_text = trim( (string)$parsed['task_text'] );
|
||||
$task_name = $this -> normalizeAiTextValue( $parsed['task_name'] );
|
||||
$task_text = $this -> normalizeAiTextValue( $parsed['task_text'] );
|
||||
|
||||
if ( $task_name === '' )
|
||||
$task_name = '(bez tematu)';
|
||||
@@ -766,4 +894,92 @@ class MailToTaskImporter
|
||||
'task_text' => $task_text
|
||||
];
|
||||
}
|
||||
|
||||
private function extractApiErrorMessage( $response )
|
||||
{
|
||||
$data = json_decode( (string)$response, true );
|
||||
if ( is_array( $data ) && isset( $data['error']['message'] ) )
|
||||
return (string)$data['error']['message'];
|
||||
|
||||
return $this -> clipForLog( $response );
|
||||
}
|
||||
|
||||
private function extractModelMessageContent( array $choice )
|
||||
{
|
||||
if ( isset( $choice['message']['content'] ) && is_string( $choice['message']['content'] ) )
|
||||
return trim( $choice['message']['content'] );
|
||||
|
||||
if ( isset( $choice['message']['content'] ) && is_array( $choice['message']['content'] ) )
|
||||
{
|
||||
$chunks = [];
|
||||
foreach ( $choice['message']['content'] as $part )
|
||||
{
|
||||
if ( is_string( $part ) )
|
||||
$chunks[] = $part;
|
||||
elseif ( is_array( $part ) )
|
||||
{
|
||||
if ( isset( $part['text'] ) && is_string( $part['text'] ) )
|
||||
$chunks[] = $part['text'];
|
||||
elseif ( isset( $part['text']['value'] ) && is_string( $part['text']['value'] ) )
|
||||
$chunks[] = $part['text']['value'];
|
||||
}
|
||||
}
|
||||
|
||||
return trim( implode( "\n", $chunks ) );
|
||||
}
|
||||
|
||||
if ( isset( $choice['message']['tool_calls'][0]['function']['arguments'] ) && is_string( $choice['message']['tool_calls'][0]['function']['arguments'] ) )
|
||||
return trim( $choice['message']['tool_calls'][0]['function']['arguments'] );
|
||||
|
||||
if ( isset( $choice['message']['function_call']['arguments'] ) && is_string( $choice['message']['function_call']['arguments'] ) )
|
||||
return trim( $choice['message']['function_call']['arguments'] );
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function clipForLog( $value, $limit = 600 )
|
||||
{
|
||||
$text = trim( (string)$value );
|
||||
if ( $text === '' )
|
||||
return '';
|
||||
|
||||
return mb_substr( $text, 0, (int)$limit );
|
||||
}
|
||||
|
||||
private function normalizeAiTextValue( $value )
|
||||
{
|
||||
if ( is_string( $value ) )
|
||||
return trim( $value );
|
||||
|
||||
if ( is_numeric( $value ) || is_bool( $value ) )
|
||||
return trim( (string)$value );
|
||||
|
||||
if ( is_array( $value ) )
|
||||
{
|
||||
$lines = [];
|
||||
foreach ( $value as $item )
|
||||
{
|
||||
$item_text = $this -> normalizeAiTextValue( $item );
|
||||
if ( $item_text !== '' )
|
||||
$lines[] = $item_text;
|
||||
}
|
||||
|
||||
return trim( implode( "\n", $lines ) );
|
||||
}
|
||||
|
||||
if ( is_object( $value ) )
|
||||
{
|
||||
$array_value = json_decode( json_encode( $value ), true );
|
||||
if ( is_array( $array_value ) )
|
||||
{
|
||||
$normalized = $this -> normalizeAiTextValue( $array_value );
|
||||
if ( $normalized !== '' )
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
return trim( json_encode( $value ) );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ class Tasks
|
||||
|
||||
static public function get_tasks_gantt( $user_id, $projects = null, $users = null ) {
|
||||
global $mdb;
|
||||
$data = [];
|
||||
|
||||
if ( $users ) {
|
||||
$sql = ' AND id IN (SELECT task_id FROM task_user WHERE user_id IN (' . implode( ',', $users ) . ')) ';
|
||||
@@ -95,6 +96,7 @@ class Tasks
|
||||
// custom class
|
||||
// if ( $task['date_start'] <= date( 'Y-m-d H:i:s' ) )
|
||||
// $task_json['custom_class'] = 'gantt-task-backlog';
|
||||
$task_json['custom_class'] = '';
|
||||
|
||||
if ( $task['parent_id'] )
|
||||
$task_json['dependencies'] = $task['parent_id'];
|
||||
|
||||
@@ -22,4 +22,4 @@ $settings['tasks_auto_start_timer'] = false;
|
||||
// OpenAI ChatGPT API configuration for email task parsing
|
||||
$settings['openai_api_key'] = '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'; // Wklej tutaj swój klucz API OpenAI
|
||||
$settings['openai_parse_emails'] = true; // true = użyj AI do parsowania emaili, false = normalne parsowanie
|
||||
$settings['openai_model'] = 'gpt-3.5-turbo'; // Model: gpt-3.5-turbo (najtańszy), gpt-4o-mini, gpt-4o, itp.
|
||||
$settings['openai_model'] = 'gpt-4o-mini'; // Model: gpt-4o-mini, gpt-4o, gpt-5-nano, itp.
|
||||
|
||||
@@ -156,11 +156,13 @@
|
||||
}
|
||||
?>
|
||||
];
|
||||
if ( tasks.length <= 0 ) {
|
||||
tasks = [
|
||||
|
||||
function getEmptyGanttTasks()
|
||||
{
|
||||
return [
|
||||
{
|
||||
start: new Date().toISOString().split('T')[0],
|
||||
end: new Date().toISOString().split('T')[0],
|
||||
start: new Date().toISOString().split( 'T' )[0],
|
||||
end: new Date().toISOString().split( 'T' )[0],
|
||||
name: "Brak zadań do wyświetlenia",
|
||||
id: "0",
|
||||
progress: 100,
|
||||
@@ -169,6 +171,29 @@
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function normalizeGanttTasks( tasksData )
|
||||
{
|
||||
if ( Array.isArray( tasksData ) && tasksData.length > 0 )
|
||||
return tasksData;
|
||||
|
||||
return getEmptyGanttTasks();
|
||||
}
|
||||
|
||||
function getSelectedTaskFilters()
|
||||
{
|
||||
return {
|
||||
projects: jQuery( 'input[name="projects"].g-checkbox:checked' ).map(function() {
|
||||
return this.value;
|
||||
}).get(),
|
||||
users: jQuery( 'input[name="users"].g-checkbox:checked' ).map(function() {
|
||||
return this.value;
|
||||
}).get()
|
||||
};
|
||||
}
|
||||
|
||||
tasks = normalizeGanttTasks( tasks );
|
||||
|
||||
var gantt_chart = new Gantt(".gantt-target", tasks, {
|
||||
on_click: function (task) {
|
||||
console.log(task);
|
||||
@@ -234,8 +259,7 @@
|
||||
$( '.tasks_container .tasks_suspended ul' ).empty().append( data.tasks_suspended );
|
||||
$( '.tasks_container .tasks_to_do ul' ).empty().append( data.tasks_to_do );
|
||||
$( '.tasks_container .tasks_fvat ul' ).empty().append( data.tasks_fvat );
|
||||
if ( data.tasks_gantt )
|
||||
gantt_chart.refresh( data.tasks_gantt );
|
||||
gantt_chart.refresh( normalizeGanttTasks( data.tasks_gantt ) );
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1156,10 +1180,8 @@
|
||||
var data = jQuery.parseJSON( response );
|
||||
if ( data.status == 'success' )
|
||||
{
|
||||
var checkedVals = jQuery( 'input.g-checkbox:checked' ).map(function() {
|
||||
return this.value;
|
||||
}).get();
|
||||
reload_tasks( checkedVals );
|
||||
var selectedFilters = getSelectedTaskFilters();
|
||||
reload_tasks( selectedFilters.projects, selectedFilters.users );
|
||||
close_task_popup();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user