License Server
Analytics
Licenses
Components
Edit Component
Update the component metadata, code, and availability.
View Component
Back
Component Name
Component name cannot be changed after creation.
Display Name
*
Version
*
Active
Description
Component: ML Training
Controller Code (Optional)
<?php namespace App\Http\Controllers; use App\Services\MLCommandResolver; use App\Services\TrainingDataLoader; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; class MLTrainingController extends Controller { /** * Show the training interface */ public function index() { $resolver = new MLCommandResolver(); $stats = $resolver->getStats(); return view('components.ml-training.index', [ 'stats' => $stats, ]); } /** * Get current model statistics */ public function stats() { $resolver = new MLCommandResolver(); $stats = $resolver->getStats(); return response()->json($stats); } /** * Train the model from the dataset file */ public function train(Request $request) { $force = $request->input('force', false); try { $trainingFile = app_path('Services/trainingdataset.txt'); if (!file_exists($trainingFile)) { return response()->json([ 'success' => false, 'message' => "Training dataset file not found: {$trainingFile}" ], 404); } $examples = TrainingDataLoader::loadTrainingExamples($trainingFile); if (empty($examples)) { return response()->json([ 'success' => false, 'message' => 'No training examples found in the dataset file.' ], 400); } $resolver = new MLCommandResolver(); // Check if model already exists $stats = $resolver->getStats(); if ($stats['total_examples'] > 0 && !$force) { return response()->json([ 'success' => false, 'message' => "Model already exists with {$stats['total_examples']} examples. Use force=true to retrain.", 'stats' => $stats ], 409); } if ($stats['total_examples'] > 0 && $force) { $resolver->clear(); } // Train in chunks $chunkSize = 500; $chunks = array_chunk($examples, $chunkSize); $totalChunks = count($chunks); $processed = 0; foreach ($chunks as $chunk) { $resolver->batchTrain($chunk, $chunkSize); $processed += count($chunk); } $newStats = $resolver->getStats(); return response()->json([ 'success' => true, 'message' => "Model trained successfully with {$processed} examples!", 'stats' => $newStats ]); } catch (\Exception $e) { Log::error('ML Training Error: ' . $e->getMessage(), [ 'trace' => $e->getTraceAsString() ]); return response()->json([ 'success' => false, 'message' => 'Training failed: ' . $e->getMessage() ], 500); } } /** * Add a single training example */ public function addExample(Request $request) { $request->validate([ 'input' => 'required|string|max:500', 'command' => 'required|string|max:200', 'weight' => 'nullable|numeric|min:0|max:10', ]); try { // Normalize command - remove "php console" or "./console" prefix and clean up $command = $request->input('command'); $command = preg_replace('/^(php\s+console|\.\/console|console)\s+/i', '', $command); $command = trim($command); // Remove extra spaces in parameters (e.g., "--force-idsites=5, 7" -> "--force-idsites=5,7") $command = preg_replace('/,\s+/', ',', $command); $input = $request->input('input'); $weight = $request->input('weight', 1.0); // Train the model $resolver = new MLCommandResolver(); $resolver->train($input, $command, $weight); $resolver->saveModel(); // Also append to training dataset file so it persists after retraining $trainingFile = app_path('Services/trainingdataset.txt'); $appendSuccess = false; if (file_exists($trainingFile)) { if (is_writable($trainingFile)) { try { // Append as JSON line format (same format as the end of the file) $jsonLine = json_encode([ 'utterance' => $input, 'command' => './console ' . $command ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); // Append to end of file with newline $result = @file_put_contents($trainingFile, "\n" . $jsonLine, FILE_APPEND | LOCK_EX); $appendSuccess = $result !== false; } catch (\Exception $e) { Log::warning('Failed to append to training dataset file', [ 'file' => $trainingFile, 'error' => $e->getMessage() ]); } } else { // Try to make it writable (if we have permission) @chmod($trainingFile, 0664); if (is_writable($trainingFile)) { try { $jsonLine = json_encode([ 'utterance' => $input, 'command' => './console ' . $command ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); $result = @file_put_contents($trainingFile, "\n" . $jsonLine, FILE_APPEND | LOCK_EX); $appendSuccess = $result !== false; } catch (\Exception $e) { Log::warning('Failed to append to training dataset file after chmod', [ 'file' => $trainingFile, 'error' => $e->getMessage() ]); } } else { Log::warning('Training dataset file is not writable', [ 'file' => $trainingFile, 'permissions' => substr(sprintf('%o', fileperms($trainingFile)), -4), 'owner' => posix_getpwuid(fileowner($trainingFile))['name'] ?? 'unknown' ]); } } } if (!$appendSuccess) { // Still return success for the model training, but log the warning Log::warning('Could not append new example to training dataset file. Example was added to model but will be lost on retrain.', [ 'file' => $trainingFile, 'suggestion' => 'Run: chmod 664 ' . $trainingFile . ' && chown beonline:beonline ' . $trainingFile ]); } return response()->json([ 'success' => true, 'message' => 'Training example added successfully!', 'stats' => $resolver->getStats() ]); } catch (\Exception $e) { Log::error('Add Training Example Error: ' . $e->getMessage(), [ 'trace' => $e->getTraceAsString() ]); // Check if it's a permission error if (strpos($e->getMessage(), 'Permission denied') !== false || strpos($e->getMessage(), 'Failed to open stream') !== false) { return response()->json([ 'success' => false, 'message' => 'Permission denied. Please ensure the storage/app directory is writable by the web server user.' ], 500); } return response()->json([ 'success' => false, 'message' => 'Failed to add example: ' . $e->getMessage() ], 500); } } /** * Test a prediction */ public function test(Request $request) { // Start output buffering to catch any unexpected output ob_start(); // Register shutdown function to catch fatal errors register_shutdown_function(function() { $error = error_get_last(); if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) { ob_clean(); http_response_code(500); header('Content-Type: application/json'); echo json_encode([ 'success' => false, 'message' => 'Fatal error: ' . $error['message'], 'error_type' => 'FatalError', 'error_details' => config('app.debug') ? [ 'file' => $error['file'], 'line' => $error['line'] ] : null ]); exit; } }); try { $request->validate([ 'input' => 'required|string|max:500', ]); } catch (\Illuminate\Validation\ValidationException $e) { ob_end_clean(); return response()->json([ 'success' => false, 'message' => 'Validation failed: ' . $e->getMessage(), 'errors' => $e->errors() ], 422); } catch (\Exception $e) { ob_end_clean(); return response()->json([ 'success' => false, 'message' => 'Request validation error: ' . $e->getMessage() ], 400); } try { // Set execution time limit set_time_limit(60); $resolver = new MLCommandResolver(); $stats = $resolver->getStats(); // Check if model has training data if ($stats['total_examples'] == 0) { ob_end_clean(); return response()->json([ 'success' => false, 'message' => 'Model has no training data. Please train the model first.' ], 400); } $userInput = $request->input('input'); $siteIds = $this->extractSiteIds($userInput); // If site IDs are detected and input contains "archive", force core:archive // This handles cases where ML doesn't match well but the intent is clear $forceArchive = !empty($siteIds) && stripos($userInput, 'archive') !== false; // Lower threshold for testing (0.2 instead of 0.3 to catch more matches) $prediction = $resolver->predict($userInput, 0.2); // If we should force archive and prediction is not core:archive, create one if ($forceArchive && (!$prediction || strpos($prediction->command, 'core:archive') !== 0)) { // Check if core:archive exists in alternatives $foundInAlternatives = false; if ($prediction && isset($prediction->alternatives)) { foreach ($prediction->alternatives as $alt) { $altCommand = is_array($alt) ? ($alt['command'] ?? null) : $alt; if ($altCommand && strpos($altCommand, 'core:archive') === 0) { // Use the alternative $altScore = is_array($alt) ? ($alt['score'] ?? 0.5) : 0.5; $prediction = new \App\Services\CommandPrediction( $altCommand, max(0.5, $altScore), // Minimum 50% confidence for forced matches [] ); $foundInAlternatives = true; break; } } } // If not found in alternatives, create a new prediction for core:archive if (!$foundInAlternatives) { $prediction = new \App\Services\CommandPrediction( 'core:archive', 0.5, // 50% confidence for pattern-based match [] ); } } ob_end_clean(); if ($prediction) { // Post-process: Extract site IDs from input and add to command if it's an archive command $command = $prediction->command; // If we found site IDs and the command is core:archive, add them if (!empty($siteIds) && strpos($command, 'core:archive') === 0) { // Check if command already has force-idsites if (strpos($command, '--force-idsites') === false) { $siteIdsStr = implode(',', $siteIds); $command .= ' --force-idsites=' . $siteIdsStr; } } return response()->json([ 'success' => true, 'prediction' => [ 'command' => $command, 'confidence' => round($prediction->confidence * 100, 2), 'score' => $prediction->confidence, // Use confidence as score 'alternatives' => array_map(function($alt) use ($userInput) { $altCommand = $alt['command'] ?? $alt; // Also add site IDs to alternatives if applicable if (is_array($alt) && isset($alt['command']) && strpos($alt['command'], 'core:archive') === 0) { $siteIds = $this->extractSiteIds($userInput); if (!empty($siteIds) && strpos($altCommand, '--force-idsites') === false) { $siteIdsStr = implode(',', $siteIds); $altCommand .= ' --force-idsites=' . $siteIdsStr; } } return [ 'command' => $altCommand, 'score' => $alt['score'] ?? 0 ]; }, $prediction->alternatives ?? []) ] ]); } else { return response()->json([ 'success' => false, 'message' => 'No prediction found (confidence too low or no matching training examples)' ]); } } catch (\Error $e) { ob_end_clean(); Log::error('Prediction Test Fatal Error: ' . $e->getMessage(), [ 'file' => $e->getFile(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString(), 'input' => $request->input('input') ]); return response()->json([ 'success' => false, 'message' => 'Prediction failed: ' . $e->getMessage(), 'error_type' => get_class($e), 'error_details' => config('app.debug') ? [ 'file' => $e->getFile(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString() ] : null ], 500); } catch (\Exception $e) { ob_end_clean(); Log::error('Prediction Test Error: ' . $e->getMessage(), [ 'trace' => $e->getTraceAsString(), 'input' => $request->input('input') ]); return response()->json([ 'success' => false, 'message' => 'Prediction failed: ' . $e->getMessage(), 'error_type' => get_class($e), 'error_details' => config('app.debug') ? $e->getTraceAsString() : null ], 500); } } /** * Extract site IDs from user input */ private function extractSiteIds(string $input): array { $siteIds = []; // Pattern 1: "for sites 5 and 7" or "for site 5 and 7 and 8" - handles "for" prefix and multiple "and" conjunctions if (preg_match('/for\s+(?:\w+\s+)?sites?\s+((?:\d+\s+and\s+)+\d+)/i', $input, $matches)) { // Extract all numbers from the matched string preg_match_all('/\d+/', $matches[1], $numbers); $siteIds = array_map('intval', $numbers[0]); } // Pattern 2: "sites 5 and 7" or "site 5 and 7 and 8" - handles multiple "and" conjunctions (without "for") // Also handles mixed formats like "site 3 and 12 and 1 and 5, 9" if (empty($siteIds) && preg_match('/sites?\s+([\d\s,and]+)/i', $input, $matches)) { // Extract all numbers from the matched string (handles both "and" and comma-separated) preg_match_all('/\d+/', $matches[1], $numbers); $siteIds = array_map('intval', $numbers[0]); } // Pattern 3: "sites 2, 3, 4" or "sites 2,3,4" - comma-separated (fallback if pattern 2 didn't match) if (empty($siteIds) && preg_match('/sites?\s+(\d+(?:\s*,\s*\d+)+)/i', $input, $matches)) { preg_match_all('/\d+/', $matches[1], $numbers); $siteIds = array_map('intval', $numbers[0]); } // Pattern 4: "idsite=5,6" or "--idsite=5,6" if (empty($siteIds) && preg_match('/idsite[=:](\d+(?:[,\s]+\d+)*)/i', $input, $matches)) { preg_match_all('/\d+/', $matches[1], $numbers); $siteIds = array_map('intval', $numbers[0]); } // Pattern 5: Single "site 5" or "site5" (fallback) if (empty($siteIds)) { if (preg_match('/sites?[^\d]*(\d+)/i', $input, $matches)) { $siteIds = [(int) $matches[1]]; } elseif (preg_match('/site\s*id\s*(\d+)/i', $input, $matches)) { $siteIds = [(int) $matches[1]]; } } return array_values(array_unique($siteIds)); } /** * Clear the model */ public function clear() { try { $resolver = new MLCommandResolver(); $resolver->clear(); return response()->json([ 'success' => true, 'message' => 'Model cleared successfully!', 'stats' => $resolver->getStats() ]); } catch (\Exception $e) { Log::error('Clear Model Error: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'Failed to clear model: ' . $e->getMessage() ], 500); } } }
PHP code for the component controller.
Routes Code (Optional)
<?php use Illuminate\Support\Facades\Route; use App\Http\Controllers\MLTrainingController; Route::middleware(['auth', 'component.visible'])->group(function () { Route::get('/ml-training', [MLTrainingController::class, 'index'])->name('dashboard.ml-training'); Route::get('/ml-training/stats', [MLTrainingController::class, 'stats'])->name('ml-training.stats'); Route::post('/ml-training/train', [MLTrainingController::class, 'train'])->name('ml-training.train'); Route::post('/ml-training/add-example', [MLTrainingController::class, 'addExample'])->name('ml-training.add-example'); Route::post('/ml-training/test', [MLTrainingController::class, 'test'])->name('ml-training.test'); Route::post('/ml-training/clear', [MLTrainingController::class, 'clear'])->name('ml-training.clear'); });
Route definitions for the component.
Views (Optional - JSON)
{ "index.blade.php": "@extends('layouts.app')\n\n@section('title', 'ML Model Training - Matomo Tools')\n\n@section('content')\n<div class=\"container-fluid py-4\">\n <div class=\"row mb-4\">\n <div class=\"col-12\">\n <div class=\"card\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <h4 class=\"mb-0\">\n <i class=\"fas fa-brain me-2\"><\/i>\n ML Command Resolver Training\n <\/h4>\n <button class=\"btn btn-sm btn-outline-secondary\" onclick=\"refreshStats()\">\n <i class=\"fas fa-sync-alt me-1\"><\/i> Refresh Stats\n <\/button>\n <\/div>\n <div class=\"card-body\">\n <!-- Model Statistics -->\n <div class=\"row mb-4\" id=\"stats-container\">\n <div class=\"col-md-3\">\n <div class=\"card bg-primary text-white\">\n <div class=\"card-body\">\n <h6 class=\"text-white-50 mb-2\">Total Examples<\/h6>\n <h3 class=\"mb-0\" id=\"stat-examples\">{{ number_format($stats['total_examples'] ?? 0) }}<\/h3>\n <\/div>\n <\/div>\n <\/div>\n <div class=\"col-md-3\">\n <div class=\"card bg-info text-white\">\n <div class=\"card-body\">\n <h6 class=\"text-white-50 mb-2\">Vocabulary Size<\/h6>\n <h3 class=\"mb-0\" id=\"stat-vocab\">{{ number_format($stats['vocabulary_size'] ?? 0) }}<\/h3>\n <\/div>\n <\/div>\n <\/div>\n <div class=\"col-md-3\">\n <div class=\"card bg-success text-white\">\n <div class=\"card-body\">\n <h6 class=\"text-white-50 mb-2\">Unique Commands<\/h6>\n <h3 class=\"mb-0\" id=\"stat-commands\">{{ number_format($stats['unique_commands'] ?? 0) }}<\/h3>\n <\/div>\n <\/div>\n <\/div>\n <div class=\"col-md-3\">\n <div class=\"card bg-{{ ($stats['file_exists'] ?? false) ? 'success' : 'warning' }} text-white\">\n <div class=\"card-body\">\n <h6 class=\"text-white-50 mb-2\">Model Status<\/h6>\n <h3 class=\"mb-0\">\n <i class=\"fas fa-{{ ($stats['file_exists'] ?? false) ? 'check' : 'exclamation-triangle' }}\"><\/i>\n {{ ($stats['file_exists'] ?? false) ? 'Loaded' : 'Not Found' }}\n <\/h3>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n\n <!-- Training Actions -->\n <div class=\"row mb-4\">\n <div class=\"col-12\">\n <div class=\"card\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">Training Actions<\/h5>\n <\/div>\n <div class=\"card-body\">\n <div class=\"btn-group\" role=\"group\">\n <button type=\"button\" class=\"btn btn-primary\" onclick=\"trainModel(false)\">\n <i class=\"fas fa-graduation-cap me-1\"><\/i> Train Model\n <\/button>\n <button type=\"button\" class=\"btn btn-warning\" onclick=\"trainModel(true)\">\n <i class=\"fas fa-redo me-1\"><\/i> Retrain (Force)\n <\/button>\n <button type=\"button\" class=\"btn btn-danger\" onclick=\"clearModel()\">\n <i class=\"fas fa-trash me-1\"><\/i> Clear Model\n <\/button>\n <\/div>\n <div class=\"mt-3\">\n <div id=\"training-progress\" class=\"progress d-none\" style=\"height: 25px;\">\n <div class=\"progress-bar progress-bar-striped progress-bar-animated\" \n role=\"progressbar\" style=\"width: 0%\"><\/div>\n <\/div>\n <div id=\"training-message\" class=\"mt-2\"><\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n\n <!-- Add Training Example -->\n <div class=\"row mb-4\">\n <div class=\"col-12\">\n <div class=\"card\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">Add Training Example<\/h5>\n <\/div>\n <div class=\"card-body\">\n <form id=\"add-example-form\">\n <div class=\"row\">\n <div class=\"col-md-5\">\n <label class=\"form-label\">User Input (Natural Language)<\/label>\n <input type=\"text\" class=\"form-control\" id=\"example-input\" \n placeholder=\"e.g., activate plugin UsersFlow\" required>\n <\/div>\n <div class=\"col-md-5\">\n <label class=\"form-label\">Command<\/label>\n <input type=\"text\" class=\"form-control\" id=\"example-command\" \n placeholder=\"e.g., core:archive --force-idsites=5,7\" required>\n <small class=\"form-text text-muted\">Command only (e.g., \"core:archive --force-idsites=5,7\"). Prefixes like \"php console\" will be removed automatically.<\/small>\n <\/div>\n <div class=\"col-md-2\">\n <label class=\"form-label\">Weight<\/label>\n <input type=\"number\" class=\"form-control\" id=\"example-weight\" \n value=\"1.0\" step=\"0.1\" min=\"0\" max=\"10\">\n <\/div>\n <\/div>\n <div class=\"mt-3\">\n <button type=\"submit\" class=\"btn btn-success\">\n <i class=\"fas fa-plus me-1\"><\/i> Add Example\n <\/button>\n <\/div>\n <\/form>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n\n <!-- Test Prediction -->\n <div class=\"row\">\n <div class=\"col-12\">\n <div class=\"card\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">Test Prediction<\/h5>\n <\/div>\n <div class=\"card-body\">\n <form id=\"test-form\">\n <div class=\"row\">\n <div class=\"col-md-10\">\n <label class=\"form-label\">Test Input<\/label>\n <input type=\"text\" class=\"form-control\" id=\"test-input\" \n placeholder=\"e.g., remove plugin Goals\" required>\n <\/div>\n <div class=\"col-md-2\">\n <label class=\"form-label\"> <\/label>\n <button type=\"submit\" class=\"btn btn-info w-100\">\n <i class=\"fas fa-search me-1\"><\/i> Test\n <\/button>\n <\/div>\n <\/div>\n <\/form>\n <div id=\"test-result\" class=\"mt-3\"><\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n<\/div>\n\n<script>\nconst csrfToken = document.querySelector('meta[name=\"csrf-token\"]').content;\n\nfunction refreshStats() {\n fetch('\/ml-training\/stats', {\n headers: {\n 'X-CSRF-TOKEN': csrfToken,\n 'Accept': 'application\/json'\n }\n })\n .then(response => response.json())\n .then(data => {\n document.getElementById('stat-examples').textContent = new Intl.NumberFormat().format(data.total_examples || 0);\n document.getElementById('stat-vocab').textContent = new Intl.NumberFormat().format(data.vocabulary_size || 0);\n document.getElementById('stat-commands').textContent = new Intl.NumberFormat().format(data.unique_commands || 0);\n \n const statusCard = document.querySelector('#stats-container .col-md-3:last-child .card');\n if (data.file_exists) {\n statusCard.className = 'card bg-success text-white';\n statusCard.querySelector('h3').innerHTML = '<i class=\"fas fa-check\"><\/i> Loaded';\n } else {\n statusCard.className = 'card bg-warning text-white';\n statusCard.querySelector('h3').innerHTML = '<i class=\"fas fa-exclamation-triangle\"><\/i> Not Found';\n }\n })\n .catch(error => {\n console.error('Error refreshing stats:', error);\n showAlert('Error refreshing statistics', 'danger');\n });\n}\n\nfunction trainModel(force = false) {\n const progressBar = document.getElementById('training-progress');\n const progressFill = progressBar.querySelector('.progress-bar');\n const messageDiv = document.getElementById('training-message');\n \n progressBar.classList.remove('d-none');\n progressFill.style.width = '0%';\n messageDiv.innerHTML = '<div class=\"alert alert-info\">Training model... This may take a while.<\/div>';\n \n fetch('\/ml-training\/train', {\n method: 'POST',\n headers: {\n 'X-CSRF-TOKEN': csrfToken,\n 'Content-Type': 'application\/json',\n 'Accept': 'application\/json'\n },\n body: JSON.stringify({ force: force })\n })\n .then(response => {\n const status = response.status;\n return response.json().then(data => ({ status, data }));\n })\n .then(({ status, data }) => {\n progressFill.style.width = '100%';\n \n if (data.success) {\n messageDiv.innerHTML = `<div class=\"alert alert-success\">${data.message}<\/div>`;\n setTimeout(() => {\n progressBar.classList.add('d-none');\n refreshStats();\n }, 2000);\n } else {\n \/\/ 409 Conflict means model already exists - show warning, not error\n const alertType = status === 409 ? 'warning' : 'danger';\n messageDiv.innerHTML = `<div class=\"alert alert-${alertType}\">${data.message}<\/div>`;\n progressBar.classList.add('d-none');\n \n \/\/ If it's a 409 and user didn't force, suggest using force\n if (status === 409 && !force) {\n messageDiv.innerHTML += '<div class=\"alert alert-info mt-2\">Click \"Retrain with Force\" to overwrite the existing model.<\/div>';\n }\n }\n })\n .catch(error => {\n console.error('Error training model:', error);\n messageDiv.innerHTML = '<div class=\"alert alert-danger\">Error training model. Please check the console.<\/div>';\n progressBar.classList.add('d-none');\n });\n}\n\nfunction clearModel() {\n if (!confirm('Are you sure you want to clear the model? This action cannot be undone.')) {\n return;\n }\n \n fetch('\/ml-training\/clear', {\n method: 'POST',\n headers: {\n 'X-CSRF-TOKEN': csrfToken,\n 'Accept': 'application\/json'\n }\n })\n .then(response => response.json())\n .then(data => {\n if (data.success) {\n showAlert(data.message, 'success');\n refreshStats();\n } else {\n showAlert(data.message, 'danger');\n }\n })\n .catch(error => {\n console.error('Error clearing model:', error);\n showAlert('Error clearing model', 'danger');\n });\n}\n\ndocument.getElementById('add-example-form').addEventListener('submit', function(e) {\n e.preventDefault();\n \n const input = document.getElementById('example-input').value;\n const command = document.getElementById('example-command').value;\n const weight = parseFloat(document.getElementById('example-weight').value) || 1.0;\n \n fetch('\/ml-training\/add-example', {\n method: 'POST',\n headers: {\n 'X-CSRF-TOKEN': csrfToken,\n 'Content-Type': 'application\/json',\n 'Accept': 'application\/json'\n },\n body: JSON.stringify({ input, command, weight })\n })\n .then(response => response.json())\n .then(data => {\n if (data.success) {\n showAlert(data.message, 'success');\n document.getElementById('add-example-form').reset();\n document.getElementById('example-weight').value = '1.0';\n refreshStats();\n } else {\n showAlert(data.message, 'danger');\n }\n })\n .catch(error => {\n console.error('Error adding example:', error);\n showAlert('Error adding training example', 'danger');\n });\n});\n\ndocument.getElementById('test-form').addEventListener('submit', function(e) {\n e.preventDefault();\n \n const input = document.getElementById('test-input').value;\n const resultDiv = document.getElementById('test-result');\n \n resultDiv.innerHTML = '<div class=\"alert alert-info\">Testing prediction...<\/div>';\n \n fetch('\/ml-training\/test', {\n method: 'POST',\n headers: {\n 'X-CSRF-TOKEN': csrfToken,\n 'Content-Type': 'application\/json',\n 'Accept': 'application\/json'\n },\n body: JSON.stringify({ input })\n })\n .then(async response => {\n \/\/ Check if response is OK\n if (!response.ok) {\n const text = await response.text();\n let errorMessage = `Server error (${response.status})`;\n try {\n const errorData = JSON.parse(text);\n errorMessage = errorData.message || errorMessage;\n } catch (e) {\n errorMessage = text || errorMessage;\n }\n throw new Error(errorMessage);\n }\n \n \/\/ Get response text first to check if it's valid JSON\n const text = await response.text();\n if (!text || text.trim() === '') {\n throw new Error('Empty response from server. The model file may be too large to load.');\n }\n \n \/\/ Try to parse JSON\n let data;\n try {\n data = JSON.parse(text);\n } catch (e) {\n console.error('Invalid JSON response:', text.substring(0, 500));\n throw new Error('Invalid response from server. The model may have failed to load due to memory\/timeout issues.');\n }\n \n return data;\n })\n .then(data => {\n if (data.success && data.prediction) {\n let alternativesHtml = '';\n if (data.prediction.alternatives && data.prediction.alternatives.length > 0) {\n alternativesHtml = '<br><strong>Alternatives:<\/strong><ul class=\"mb-0\">';\n data.prediction.alternatives.forEach(alt => {\n alternativesHtml += `<li>${alt.command} (score: ${alt.score.toFixed(4)})<\/li>`;\n });\n alternativesHtml += '<\/ul>';\n }\n \n resultDiv.innerHTML = `\n <div class=\"alert alert-success\">\n <strong>Prediction:<\/strong> <code>${data.prediction.command}<\/code><br>\n <strong>Confidence:<\/strong> ${data.prediction.confidence}%<br>\n <strong>Score:<\/strong> ${data.prediction.score.toFixed(4)}\n ${alternativesHtml}\n <\/div>\n `;\n } else {\n resultDiv.innerHTML = `<div class=\"alert alert-warning\">${data.message || 'No prediction found'}<\/div>`;\n }\n })\n .catch(error => {\n console.error('Error testing prediction:', error);\n let errorMsg = error.message || 'Unknown error occurred';\n resultDiv.innerHTML = `<div class=\"alert alert-danger\">\n <strong>Error:<\/strong> ${errorMsg}<br>\n <small>This may be due to the model file being too large (49MB). Consider optimizing the model or increasing PHP memory limits.<\/small>\n <\/div>`;\n });\n});\n\nfunction showAlert(message, type) {\n const alertDiv = document.createElement('div');\n alertDiv.className = `alert alert-${type} alert-dismissible fade show`;\n alertDiv.innerHTML = `\n ${message}\n <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\"><\/button>\n `;\n \n const container = document.querySelector('.card-body');\n container.insertBefore(alertDiv, container.firstChild);\n \n setTimeout(() => {\n alertDiv.remove();\n }, 5000);\n}\n\n\/\/ Auto-refresh stats every 30 seconds\nsetInterval(refreshStats, 30000);\n<\/script>\n@endsection\n\n" }
JSON object with view file paths or inline content.
Assets (Optional - JSON)
[]
JSON array of asset file paths.
Update Component
Delete Component