From 2b9b62b0aadb54e6a33893b886b9dca8a1b0c923 Mon Sep 17 00:00:00 2001 From: igor Date: Sun, 14 Jun 2026 12:42:41 +0200 Subject: [PATCH] implemented step 12 by Gemini - Task actions --- public/ajax.php | 9 +++++ public/css/wizard.css | 27 +++++++++++++++ public/index.html | 8 +++-- public/js/wizard.js | 67 +++++++++++++++++++++++++++++++++++++ src/Actions/TaskActions.php | 66 ++++++++++++++++++++++++++++++++++++ 5 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 src/Actions/TaskActions.php diff --git a/public/ajax.php b/public/ajax.php index 49ae8cc..0af3aaf 100644 --- a/public/ajax.php +++ b/public/ajax.php @@ -64,6 +64,7 @@ try { // Router $projectActions = new \App\Actions\ProjectActions(); + $taskActions = new \App\Actions\TaskActions(); $consentService = new \App\Services\ConsentService(); switch ($action) { @@ -130,6 +131,14 @@ try { sendResponse(true, $result); break; + case 'generateWebsite': + $projectId = $data['project_id'] ?? null; + if (!$projectId) { + sendResponse(false, ['code' => 'MISSING_PROJECT_ID', 'message' => 'Project ID is required.'], 400); + } + sendResponse(true, $taskActions->generateWebsite($userId, $projectId)); + break; + default: sendResponse(false, ['code' => 'UNKNOWN_ACTION', 'message' => "Action '$action' is not defined."], 404); break; diff --git a/public/css/wizard.css b/public/css/wizard.css index 2ee7cb8..b5b1276 100644 --- a/public/css/wizard.css +++ b/public/css/wizard.css @@ -482,6 +482,33 @@ textarea:focus, input[type="text"]:focus, input[type="email"]:focus, input[type= padding: 0; } +/* Generation Loader */ +.generation-loader { + display: flex; + flex-direction: column; + align-items: center; + gap: 1.5rem; + padding: 3rem 0; +} + +.spinner { + width: 3rem; + height: 3rem; + border: 4px solid var(--border-color); + border-top-color: var(--primary-color); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +#generation-status { + font-weight: 500; + color: var(--primary-color); +} + button:disabled { opacity: 0.5; cursor: not-allowed; diff --git a/public/index.html b/public/index.html index 1299a5f..1601de3 100644 --- a/public/index.html +++ b/public/index.html @@ -307,9 +307,11 @@

Generovanie obsahu

-

Naša AI teraz pripravuje váš textový obsah.

-
-

Obsah pre krok 6...

+

Naša AI teraz pripravuje váš textový obsah a štruktúru webu. Môže to trvať niekoľko desiatok sekúnd.

+ +
+
+

Čakám na zaradenie do fronty...

diff --git a/public/js/wizard.js b/public/js/wizard.js index fd9c74b..bfde63b 100644 --- a/public/js/wizard.js +++ b/public/js/wizard.js @@ -50,6 +50,13 @@ const App = { this.state.currentStep = this.state.project.current_step || 1; console.log('Loaded existing project:', this.state.project.project_id); this.syncSelectionWithProject(); + + // Resume polling if generating + const pollingStatuses = ['queued', 'generating', 'rendering']; + if (pollingStatuses.includes(this.state.project.status)) { + this.state.currentStep = 6; + this.startPolling(); + } } } else { await this.createProject(); @@ -708,9 +715,66 @@ const App = { if (this.state.currentStep < this.state.totalSteps) { this.showStep(this.state.currentStep + 1); + + // Trigger generation if entering step 6 + if (this.state.currentStep === 6) { + this.startWebsiteGeneration(); + } } }, + async startWebsiteGeneration() { + try { + const result = await this.apiCall('generateWebsite'); + if (result.success) { + console.log('Generation started:', result.data.task_id); + this.startPolling(); + } + } catch (error) { + console.error('Failed to start generation:', error); + document.getElementById('generation-status').textContent = 'Chyba: ' + error.message; + } + }, + + startPolling() { + if (this.pollingInterval) clearInterval(this.pollingInterval); + + this.pollingInterval = setInterval(async () => { + try { + const response = await this.apiCall('getProjectStatus'); + if (response.success) { + const project = response.data; + this.state.project = project; + this.updateGenerationStatus(project.status); + + if (project.status === 'ready' || project.status === 'failed') { + clearInterval(this.pollingInterval); + if (project.status === 'ready') { + // Move to preview step + this.showStep(7); + } else { + document.getElementById('generation-status').textContent = 'Generovanie zlyhalo. Skúste to neskôr.'; + } + } + } + } catch (error) { + console.error('Polling error:', error); + } + }, 3000); + }, + + updateGenerationStatus(status) { + const statusEl = document.getElementById('generation-status'); + const messages = { + 'queued': 'Zaradené do fronty, čakám na AI...', + 'generating': 'AI práve píše texty pre váš web...', + 'rendering': 'Skladáme výslednú stránku...', + 'ready': 'Hotovo! Pripravujem náhľad...', + 'failed': 'Nastal problém pri generovaní.' + }; + statusEl.textContent = messages[status] || 'Spracovávam...'; + }, + prevStep() { if (this.state.currentStep > 1) { this.showStep(this.state.currentStep - 1); @@ -749,6 +813,9 @@ const App = { btnNext.disabled = nextDisabled; + // Hide footer in generating step + document.querySelector('.wizard-footer').classList.toggle('hidden', this.state.currentStep === 6); + if (this.state.currentStep === this.state.totalSteps) { btnNext.textContent = 'Dokončiť'; } else { diff --git a/src/Actions/TaskActions.php b/src/Actions/TaskActions.php new file mode 100644 index 0000000..afbef3d --- /dev/null +++ b/src/Actions/TaskActions.php @@ -0,0 +1,66 @@ +storage = new FileStorage(); + $this->projectActions = new ProjectActions(); + } + + /** + * Creates a new AI generation task for a project. + */ + public function generateWebsite(string $userId, string $projectId): array + { + // 1. Verify project ownership and status + $projectData = $this->projectActions->getProjectStatus($userId, $projectId); + + // 2. Prevent duplicate queuing + if ($projectData['status'] === 'queued' || $projectData['status'] === 'generating') { + return ['message' => 'Generation already in progress.', 'status' => $projectData['status']]; + } + + // 3. Verify GDPR consent + $consentService = new ConsentService(); + if (!$consentService->hasConsent($projectId)) { + throw new Exception("GDPR consent is required for AI generation.", 403); + } + + // 4. Create Task + $taskId = 't_' . bin2hex(random_bytes(8)); + $taskData = [ + 'task_id' => $taskId, + 'project_id' => $projectId, + 'status' => 'queued', + 'attempt_count' => 0, + 'max_attempts' => 3, + 'created_at' => date('c'), + 'wizard_data' => $projectData['wizard_data'] + ]; + + $this->storage->put("llm/{$taskId}.json", $taskData); + + // 5. Update Project Status + $projectData['status'] = 'queued'; + $projectData['updated_at'] = date('c'); + $this->storage->put("projects/{$projectId}.json", $projectData); + + return [ + 'task_id' => $taskId, + 'status' => 'queued', + 'message' => 'Project successfully queued for generation.' + ]; + } +}