diff --git a/.gitignore b/.gitignore index e427ff3..5147d85 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ */node_modules/ */build/ +/.env diff --git a/api.php b/api.php new file mode 100644 index 0000000..4c3d1a2 --- /dev/null +++ b/api.php @@ -0,0 +1,177 @@ + 'OpenAI API key not configured.']; + } + + $prompt = "I have two CSV files. I want to convert the input file to the output file format.\n\n"; + $prompt .= "Here is a sample of the input CSV file:\n---\n" . $inputSample . "\n---\n\n"; + $prompt .= "Here is a sample of the output CSV file:\n---\n" . $outputSample . "\n---\n\n"; + $prompt .= "Please generate a PHP script that contains a single function `convert_csv($input_file_path, $output_file_path)`.\n"; + $prompt .= "This function should read the data from the `$input_file_path`, transform it into the format of the output sample, and write the result to `$output_file_path`.\n"; + $prompt .= "The script should handle CSV headers correctly. Do not include any explanations, just the raw PHP code starting with ` 'gpt-5', + 'messages' => [ + [ + 'role' => 'user', + 'content' => $prompt + ] + ] + ]; + + $ch = curl_init('https://api.openai.com/v1/chat/completions'); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'Authorization: Bearer ' . $apiKey + ]); + + $response = curl_exec($ch); + $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($httpcode != 200) { + return ['error' => 'Failed to connect to OpenAI API. Response: ' . $response]; + } + + $result = json_decode($response, true); + + if (isset($result['choices'][0]['message']['content'])) { + $php_code = $result['choices'][0]['message']['content']; + // Clean up the response to get only the code + if (strpos($php_code, '```php') !== false) { + $php_code = substr($php_code, strpos($php_code, '```php') + 5); + $php_code = substr($php_code, 0, strrpos($php_code, '```')); + } + if (strpos($php_code, ' true, 'code' => $php_code]; + } + + return ['error' => 'Could not extract PHP code from OpenAI response.', 'details' => $result]; +} + +load_env(__DIR__ . '/.env'); +$action = $_POST['action'] ?? $_GET['action'] ?? ''; + +if ($action === 'build_mapping') { + if (!isset($_FILES['inputSample']) || !isset($_FILES['outputSample'])) { + echo json_encode(['success' => false, 'error' => 'Input and output sample files are required.']); + exit; + } + + $inputTmpPath = $_FILES['inputSample']['tmp_name']; + $outputTmpPath = $_FILES['outputSample']['tmp_name']; + + $inputSampleContent = get_csv_sample($inputTmpPath); + $outputSampleContent = get_csv_sample($outputTmpPath); + + if ($inputSampleContent === null || $outputSampleContent === null) { + echo json_encode(['success' => false, 'error' => 'Failed to read sample files.']); + exit; + } + + $result = call_openai_api($inputSampleContent, $outputSampleContent); + + if (isset($result['error'])) { + echo json_encode(['success' => false, 'error' => $result['error'], 'details' => $result['details'] ?? null]); + exit; + } + + $mappingDir = __DIR__ . '/mappings'; + if (!is_dir($mappingDir)) { + mkdir($mappingDir, 0775, true); + } + $mappingFile = 'mappings/mapping_' . time() . '.php'; + file_put_contents($mappingFile, $result['code']); + + echo json_encode(['success' => true, 'mapping_file' => $mappingFile]); + exit; +} + +if ($action === 'convert') { + if (!isset($_FILES['fullInputFile']) || !isset($_POST['mappingFile'])) { + echo json_encode(['success' => false, 'error' => 'Input file and mapping file are required.']); + exit; + } + + $mappingFile = $_POST['mappingFile']; + if (!file_exists($mappingFile)) { + echo json_encode(['success' => false, 'error' => 'Mapping file not found.']); + exit; + } + + require_once $mappingFile; + + if (!function_exists('convert_csv')) { + echo json_encode(['success' => false, 'error' => 'Invalid mapping file: convert_csv function not found.']); + exit; + } + + $inputTmpPath = $_FILES['fullInputFile']['tmp_name']; + $outputDir = __DIR__ . '/outputs'; + if (!is_dir($outputDir)) { + mkdir($outputDir, 0775, true); + } + $outputFile = $outputDir . '/converted_' . time() . '.csv'; + + try { + convert_csv($inputTmpPath, $outputFile); + echo json_encode(['success' => true, 'download_url' => basename($outputFile)]); + } catch (Exception $e) { + echo json_encode(['success' => false, 'error' => 'Error during conversion: ' . $e->getMessage()]); + } + exit; +} + + +echo json_encode(['success' => false, 'error' => 'No action specified.']); \ No newline at end of file diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..19816b6 --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,53 @@ +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + background-color: #f8f9fa; + color: #333; +} + +.container { + max-width: 960px; +} + +.card { + border: none; + border-radius: 0.75rem; + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1); + transition: transform 0.2s ease-in-out; +} + +.card:hover { + transform: translateY(-5px); +} + +.card-title { + font-weight: 600; +} + +.btn { + border-radius: 0.5rem; + padding: 0.75rem 1.25rem; + font-weight: 600; + transition: all 0.2s ease-in-out; +} + +.btn-primary { + background-color: #0d6efd; + border-color: #0d6efd; +} + +.btn-primary:hover { + background-color: #0b5ed7; + border-color: #0a58ca; +} + +.form-control { + border-radius: 0.5rem; +} + +header { + border-bottom: 1px solid #e9ecef; +} + +footer { + border-top: 1px solid #e9ecef; +} diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..ad989d5 --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,94 @@ +document.addEventListener('DOMContentLoaded', function () { + const mappingForm = document.getElementById('mappingForm'); + const convertForm = document.getElementById('convertForm'); + + const inputSample = document.getElementById('inputSample'); + const outputSample = document.getElementById('outputSample'); + const buildMappingBtn = document.getElementById('buildMappingBtn'); + + const fullInputFile = document.getElementById('fullInputFile'); + const convertBtn = document.getElementById('convertBtn'); + + const convertSection = document.getElementById('convertSection'); + const convertResult = document.getElementById('convertResult'); + const mappingFileInput = document.getElementById('mappingFile'); + + function checkMappingFiles() { + buildMappingBtn.disabled = !(inputSample.files.length > 0 && outputSample.files.length > 0); + } + + function checkConvertFile() { + convertBtn.disabled = !(fullInputFile.files.length > 0); + } + + inputSample.addEventListener('change', checkMappingFiles); + outputSample.addEventListener('change', checkMappingFiles); + fullInputFile.addEventListener('change', checkConvertFile); + + mappingForm.addEventListener('submit', function (e) { + e.preventDefault(); + buildMappingBtn.disabled = true; + buildMappingBtn.innerHTML = ' Building...'; + + const formData = new FormData(this); + formData.append('action', 'build_mapping'); + + fetch('api.php', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + alert('Mapping built successfully!'); + mappingFileInput.value = data.mapping_file; + convertSection.classList.remove('d-none'); + } else { + let errorMsg = 'Error building mapping: ' + data.error; + if(data.details) { + errorMsg += '\nDetails: ' + JSON.stringify(data.details); + } + alert(errorMsg); + } + }) + .catch(error => { + console.error('Error:', error); + alert('An error occurred while building the mapping.'); + }) + .finally(() => { + buildMappingBtn.disabled = false; + buildMappingBtn.innerHTML = 'Build Mapping'; + }); + }); + + convertForm.addEventListener('submit', function (e) { + e.preventDefault(); + convertBtn.disabled = true; + convertBtn.innerHTML = ' Converting...'; + convertResult.innerHTML = ''; + + const formData = new FormData(this); + formData.append('action', 'convert'); + + fetch('api.php', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + convertResult.innerHTML = `
Conversion successful! Download Converted File
`; + } else { + convertResult.innerHTML = `
Error: ${data.error}
`; + } + }) + .catch(error => { + console.error('Error:', error); + convertResult.innerHTML = `
An unexpected error occurred.
`; + }) + .finally(() => { + convertBtn.disabled = false; + convertBtn.innerHTML = 'Convert'; + }); + }); +}); \ No newline at end of file diff --git a/converter.py b/converter.py new file mode 100644 index 0000000..9630dc0 --- /dev/null +++ b/converter.py @@ -0,0 +1 @@ +# This is a placeholder for the Python converter script. \ No newline at end of file diff --git a/index.php b/index.php index 7205f3d..97524a7 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,95 @@ - - + - - - New Style - - - - - - - - - - - - - - - - - - - + + + Data Mapping Tool + + + + + + + + + + -
-
-

Analyzing your requirements and generating your website…

-
- Loading… -
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

-
-
- +
+
+ +

Data Mapper

+
+ +
+
+ +
+
+
+
+
+

1Build Mapping

+

Provide input and output sample files (CSV or XLSX) to generate a mapping configuration using AI.

+
+
+ + +
+
+ + +
+ +
+
+
+
+
+
+
+

2Convert Data

+

Use the generated mapping to convert your full input file.

+
+ +
+ + +
+ +
+
+
+
+
+
+
+ + + + + - + \ No newline at end of file diff --git a/mappings/mapping_1760650510.php b/mappings/mapping_1760650510.php new file mode 100644 index 0000000..6b3b310 --- /dev/null +++ b/mappings/mapping_1760650510.php @@ -0,0 +1,156 @@ + $name) { + $norm = trim($name); + $idx[$norm] = $i; + } + + // Helper to fetch a cell by header name, with fallbacks + $get = function(array $row, array $candidates) use ($idx) { + foreach ($candidates as $cand) { + if (isset($idx[$cand])) { + $val = isset($row[$idx[$cand]]) ? $row[$idx[$cand]] : ''; + $val = is_string($val) ? trim($val) : $val; + if ($val !== '' && $val !== null) { + return $val; + } + } + } + return ''; + }; + + // Helper to normalize date to YYYY-MM-DD + $normDate = function($s) { + $s = trim($s); + if ($s === '') { + return ''; + } + // Try to extract date part + // Accept formats: YYYY-MM-DD, YYYY-MM-DDTHH:MM:SSZ, etc. + if (preg_match('/^\d{4}-\d{2}-\d{2}/', $s, $m)) { + return $m[0]; + } + // Try strtotime + $ts = strtotime($s); + if ($ts !== false) { + return date('Y-m-d', $ts); + } + return $s; + }; + + // Helper to normalize amount (use dot as decimal separator, keep sign) + $normAmount = function($s) { + $s = trim((string)$s); + if ($s === '') return '0'; + // Replace comma decimal to dot, remove spaces + $s = str_replace([' ', ','], ['', '.'], $s); + // If it's a plain numeric string, keep as-is + if (is_numeric($s)) { + // Keep as minimally formatted string + // Avoid scientific notation + $f = (float)$s; + // Preserve sign and decimals without trailing zeros if possible + // Format with high precision then trim + $str = rtrim(rtrim(number_format($f, 8, '.', ''), '0'), '.'); + return $str === '' ? '0' : $str; + } + return $s; + }; + + // Write output header (semicolon-separated) + $outputHeader = [ + 'Data księgowania', + 'Nadawca / Odbiorca', + 'Adres nadawcy / odbiorcy', + 'Rachunek źródłowy', + 'Rachunek docelowy', + 'Tytuł', + 'Kwota', + ]; + // Use fputcsv with semicolon delimiter + fputcsv($out, $outputHeader, ';'); + + while (($row = fgetcsv($in)) !== false) { + // Skip entirely empty rows + if (count($row) === 1 && trim((string)$row[0]) === '') { + continue; + } + + // Extract fields using common header names + $date = $get($row, ['Date completed (UTC)', 'Date started (UTC)', 'Date', 'Completed Date']); + $date = $normDate($date); + + $description = $get($row, ['Description', 'Narrative', 'Details']); + $payer = $get($row, ['Payer', 'Counterparty', 'Merchant']); + $account = $get($row, ['Account', 'From', 'Source account']); + + $benefIban = $get($row, ['Beneficiary IBAN', 'Beneficiary account IBAN', 'IBAN']); + $benefAccNo = $get($row, ['Beneficiary account number', 'Beneficiary account', 'Account number']); + $benefRouting = $get($row, ['Beneficiary sort code or routing number', 'Routing number', 'Sort code']); + + $amount = $get($row, ['Amount', 'Total amount', 'Payment amount', 'Paid amount', 'Value']); + if ($amount === '') { + // Fallback to original amount if needed + $amount = $get($row, ['Orig amount', 'Original amount']); + } + $amount = $normAmount($amount); + + $counterparty = $description !== '' ? $description : ($payer !== '' ? $payer : ''); + $title = $description !== '' ? $description : ($counterparty !== '' ? $counterparty : ''); + + $destAccount = ''; + if ($benefIban !== '') { + $destAccount = $benefIban; + } elseif ($benefAccNo !== '') { + $destAccount = $benefAccNo; + } elseif ($benefRouting !== '') { + $destAccount = $benefRouting; + } + + $outRow = [ + $date, + $counterparty, + '', // Adres nadawcy / odbiorcy (not provided in input) + $account, + $destAccount, + $title, + $amount, + ]; + + fputcsv($out, $outRow, ';'); + } + + fclose($in); + fclose($out); +} +?> \ No newline at end of file diff --git a/outputs/converted_1760650527.csv b/outputs/converted_1760650527.csv new file mode 100644 index 0000000..c282d42 --- /dev/null +++ b/outputs/converted_1760650527.csv @@ -0,0 +1,47 @@ +"Data księgowania";"Nadawca / Odbiorca";"Adres nadawcy / odbiorcy";"Rachunek źródłowy";"Rachunek docelowy";Tytuł;Kwota +2025-08-23;"Web Innovative Softwar";;"EUR Card EUR";;"Web Innovative Softwar";-18.48 +2025-08-23;"Web Innovative Softwar";;"EUR Card EUR";;"Web Innovative Softwar";-0.24 +2025-08-23;"Web Innovative Softwar";;"EUR Card EUR";;"Web Innovative Softwar";-34.35 +2025-08-23;"Web Innovative Softwar";;"EUR Card EUR";;"Web Innovative Softwar";-0.24 +2025-08-23;"Discord* 12xserverboos";;"EUR Card EUR";;"Discord* 12xserverboos";-44.54 +2025-08-22;"To Flatlogic Poland";;"EUR Main";PL51109025900000000161061759;"To Flatlogic Poland";-50 +2025-08-22;Cloudflare;;"EUR Card EUR";;Cloudflare;-8.03 +2025-08-20;Cloudflare;;"EUR Card EUR";;Cloudflare;-25.71 +2025-08-20;"Openai *chatgpt Subscr";;"EUR Card EUR";;"Openai *chatgpt Subscr";-186.18 +2025-08-20;Calendly;;"EUR Card EUR";;Calendly;-42.24 +2025-08-20;"Facebk *whhzuwgcy2";;"EUR Card EUR";;"Facebk *whhzuwgcy2";-25.74 +2025-08-19;"Main · EUR → Main · USD";;"EUR Main";;"Main · EUR → Main · USD";-4115.97 +2025-08-19;"Card EUR · EUR → Card USD · USD";;"EUR Card EUR";;"Card EUR · EUR → Card USD · USD";-250.15 +2025-08-19;"To Card EUR";;"EUR Main";;"To Card EUR";-1300 +2025-08-19;"From Main";;"EUR Card EUR";;"From Main";1300 +2025-08-14;"Main · EUR → Main · PLN";;"EUR Main";;"Main · EUR → Main · PLN";-41.07 +2025-08-14;"Money added from MOGANADEN MOORGHEN";;"EUR Main";;"Money added from MOGANADEN MOORGHEN";6468.75 +2025-08-12;Ing*meeting15.com;;"EUR Card EUR";;Ing*meeting15.com;-59.48 +2025-08-07;"Google*cloud Qzxlb5";;"EUR Card EUR";;"Google*cloud Qzxlb5";-417.23 +2025-08-07;"Google Cloud Sv93dn";;"EUR Card EUR";;"Google Cloud Sv93dn";-500 +2025-08-06;"Card USD · USD → Card EUR · EUR";;"EUR Card EUR";;"Card USD · USD → Card EUR · EUR";1000 +2025-08-04;"Main · EUR → Card USD · USD";;"EUR Main";;"Main · EUR → Card USD · USD";-6007.69 +2025-08-04;"Main · EUR → Card USD · USD";;"EUR Main";;"Main · EUR → Card USD · USD";-3460.84 +2025-08-04;"Money added from FLATLOGIC POLAND SPOLKA Z OGRANICZONA ODPOWIEDZIALNOSCIA";;"EUR Main";;"Money added from FLATLOGIC POLAND SPOLKA Z OGRANICZONA ODPOWIEDZIALNOSCIA";8800 +2025-08-04;"Openai *chatgpt Subscr";;"EUR Card EUR";;"Openai *chatgpt Subscr";-17.41 +2025-07-29;"Github, Inc.";;"EUR Card EUR";;"Github, Inc.";-81.96 +2025-07-29;Dropbox*sgvfd1d5tz31;;"EUR Card EUR";;Dropbox*sgvfd1d5tz31;-11.99 +2025-07-28;"To Flatlogic EU";;"EUR Main";;"To Flatlogic EU";-400 +2025-07-28;Figma;;"EUR Card EUR";;Figma;-102.39 +2025-07-27;"Trello.com* Atlassian";;"EUR Card EUR";;"Trello.com* Atlassian";-5.12 +2025-07-26;Paypro-charge.com;;"EUR Card EUR";;Paypro-charge.com;-11.62 +2025-07-23;"Discord* 12xserverboos";;"EUR Card EUR";;"Discord* 12xserverboos";-44.27 +2025-07-21;"To Flatlogic Poland";;"EUR Main";PL51109025900000000161061759;"To Flatlogic Poland";-450 +2025-07-20;Cloudflare;;"EUR Card EUR";;Cloudflare;-25.83 +2025-07-20;Calendly;;"EUR Card EUR";;Calendly;-42.36 +2025-07-18;"Main · EUR → Main · USD";;"EUR Main";;"Main · EUR → Main · USD";-972.24 +2025-07-18;"To Flatlogic Poland";;"EUR Main";PL51109025900000000161061759;"To Flatlogic Poland";-250 +2025-07-17;"Openai *chatgpt Subscr";;"EUR Card EUR";;"Openai *chatgpt Subscr";-186.18 +2025-07-16;"To Card EUR";;"EUR Main";;"To Card EUR";-500 +2025-07-16;"From Main";;"EUR Card EUR";;"From Main";500 +2025-07-16;"Veed.io Basic";;"EUR Card EUR";;"Veed.io Basic";-44 +2025-07-14;"Main · EUR → Main · PLN";;"EUR Main";;"Main · EUR → Main · PLN";-8.49 +2025-07-14;"Main · EUR → Main · USD";;"EUR Main";;"Main · EUR → Main · USD";-17.12 +2025-07-14;"Main · EUR → Main · USD";;"EUR Main";;"Main · EUR → Main · USD";-2121 +2025-07-14;"Main · EUR → Main · USD";;"EUR Main";;"Main · EUR → Main · USD";-1711.65 +2025-07-14;"Money added from MOGANADEN MOORGHEN";;"EUR Main";;"Money added from MOGANADEN MOORGHEN";1631.25