diff --git a/app/Api/Plugins/RunwareAi.php b/app/Api/Plugins/RunwareAi.php index 4031ca0..d5711b3 100644 --- a/app/Api/Plugins/RunwareAi.php +++ b/app/Api/Plugins/RunwareAi.php @@ -103,17 +103,34 @@ class RunwareAi implements ApiPluginInterface try { $response = Http::withHeaders([ - 'Authorization' => 'Bearer ' . $token, + 'Content-Type' => 'application/json', 'Accept' => 'application/json', ])->timeout(5)->post($apiUrl, [ [ - 'taskType' => 'ping', + 'taskType' => 'authentication', + 'apiKey' => $token, ] ]); - return $response->successful(); + $responseData = $response->json(); + + if ($response->successful() && isset($responseData['data']) && !isset($responseData['error'])) { + $this->logInfo('RunwareAI connection test successful: Authentication successful.', [ + 'status' => $response->status(), + 'response' => $responseData, + ]); + return true; + } else { + $errorMessage = $responseData['error'] ?? 'Unknown error'; + $this->logError('RunwareAI connection test failed: Authentication failed.', [ + 'status' => $response->status(), + 'response' => $responseData, + 'error_message' => $errorMessage, + ]); + return false; + } } catch (\Exception $e) { - $this->logError('RunwareAI connection test failed.', ['error' => $e->getMessage()]); + $this->logError('RunwareAI connection test failed: Exception caught.', ['error' => $e->getMessage()]); return false; } } diff --git a/app/Filament/Resources/ApiProviderResource.php b/app/Filament/Resources/ApiProviderResource.php index 65774f1..15024ca 100644 --- a/app/Filament/Resources/ApiProviderResource.php +++ b/app/Filament/Resources/ApiProviderResource.php @@ -26,6 +26,7 @@ use Filament\Notifications\Notification; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Log; +use App\Api\Plugins\PluginLoader; class ApiProviderResource extends Resource { @@ -90,67 +91,59 @@ class ApiProviderResource extends Resource ->action(function (array $data, Forms\Components\Component $component, \Livewire\Component $livewire) { $formData = $component->getLivewire()->form->getState(); $apiUrl = str_replace('127.0.0.1', 'localhost', $formData['api_url'] ?? null); - $plugin = $formData['plugin'] ?? null; - $username = $formData['username'] ?? null; - $password = $formData['password'] ?? null; + $pluginName = $formData['plugin'] ?? null; $token = $formData['token'] ?? null; + if (!$pluginName) { + Notification::make() + ->title(__('filament.resource.api_provider.notification.connection_failed')) + ->body('Please select a plugin first.') + ->danger() + ->send(); + $component->getLivewire()->dispatch('testConnectionFinished', result: 'failed'); + return; + } + try { - $http = Http::timeout(25); + // Create a dummy ApiProvider model for the test + $dummyApiProvider = new \App\Models\ApiProvider(); + $dummyApiProvider->api_url = $apiUrl; + $dummyApiProvider->token = $token; - if ($username && $password) { - $http->withBasicAuth($username, $password); - } elseif ($token) { - $http->withToken($token); - } + // Load the specific plugin using the PluginLoader + $pluginInstance = PluginLoader::getPlugin($pluginName, $dummyApiProvider); - $response = $http->get($apiUrl); + // Call the testConnection method of the plugin + $testResult = $pluginInstance->testConnection([ + 'api_url' => $apiUrl, + 'token' => $token, + 'username' => $formData['username'] ?? null, + 'password' => $formData['password'] ?? null, + ]); - if ($response->successful()) { - Log::info('External API connection successful.', [ - 'api_url' => $apiUrl, - 'plugin' => $plugin, - ]); + if ($testResult) { Notification::make() ->title(__('filament.resource.api_provider.notification.connection_successful')) ->success() ->send(); $component->getLivewire()->dispatch('testConnectionFinished', result: 'success'); } else { - Log::warning('External API connection failed: Non-successful response.', [ - 'api_url' => $apiUrl, - 'plugin' => $plugin, - 'status' => $response->status(), - 'response_body' => $response->body(), - ]); Notification::make() ->title(__('filament.resource.api_provider.notification.connection_failed')) - ->body($response->json('message', 'An unknown error occurred.')) + ->body('Plugin reported connection failed. Check logs for details.') ->danger() ->send(); $component->getLivewire()->dispatch('testConnectionFinished', result: 'failed'); } - } catch (\Illuminate\Http\Client\RequestException $e) { - Log::error('External API connection failed: Timeout or network error.', [ - 'api_url' => $apiUrl, - 'plugin' => $plugin, - 'error_message' => $e->getMessage(), - ]); - Notification::make() - ->title(__('filament.resource.api_provider.notification.connection_failed')) - ->body('Timeout or network error: ' . $e->getMessage()) - ->danger() - ->send(); - $component->getLivewire()->dispatch('testConnectionFinished', result: 'failed'); } catch (\Exception $e) { - Log::error('External API connection failed: An unexpected error occurred.', [ + Log::error('Plugin test connection failed: An unexpected error occurred.', [ 'api_url' => $apiUrl, - 'plugin' => $plugin, + 'plugin' => $pluginName, 'error_message' => $e->getMessage(), ]); Notification::make() ->title(__('filament.resource.api_provider.notification.connection_failed')) - ->body('An unexpected error occurred: ' . $e->getMessage()) + ->body('An unexpected error occurred during plugin test: ' . $e->getMessage()) ->danger() ->send(); $component->getLivewire()->dispatch('testConnectionFinished', result: 'failed'); diff --git a/app/Http/Controllers/Api/StyleController.php b/app/Http/Controllers/Api/StyleController.php index c038e1f..3b6fe71 100644 --- a/app/Http/Controllers/Api/StyleController.php +++ b/app/Http/Controllers/Api/StyleController.php @@ -35,4 +35,11 @@ class StyleController extends Controller return response()->json(['interval' => $interval ? (int)$interval->value / 1000 : 5]); } + + public function getMaxNumberOfCopies() + { + $maxCopies = Setting::where('key', 'max_number_of_copies')->first(); + + return response()->json(['max_copies' => $maxCopies ? (int)$maxCopies->value : 10]); // Default to 10 if not set + } } \ No newline at end of file diff --git a/app/Http/Controllers/PrintController.php b/app/Http/Controllers/PrintController.php new file mode 100644 index 0000000..e62b963 --- /dev/null +++ b/app/Http/Controllers/PrintController.php @@ -0,0 +1,53 @@ +validate([ + 'image_path' => 'required|string', + 'quantity' => 'required|integer|min:1', + ]); + + $imagePath = public_path(str_replace(url('/'), '', $request->input('image_path'))); + $quantity = $request->input('quantity'); + + if (!file_exists($imagePath)) { + Log::error("PrintController: Image file not found at {$imagePath}"); + return response()->json(['error' => 'Image file not found.'], 404); + } + + // IMPORTANT: Replace this command with one that works in your environment. + // Examples: + // Linux/macOS: $command = ['lpr', '-#', $quantity, $imagePath]; + // Windows (assuming a shared printer named 'MyNetworkPrinter'): + // $command = ['print', '/d:\\MyNetworkPrinter', $imagePath]; + // You might need to install additional software or configure your system + // to enable command-line printing. + // For a more robust solution, consider a dedicated print server application + // or a commercial print API. + $command = ['echo', "Simulating print of {$quantity} copies of {$imagePath}"]; // Placeholder + + try { + $process = new Process($command); + $process->run(); + + if (!$process->isSuccessful()) { + throw new ProcessFailedException($process); + } + + Log::info("PrintController: Successfully sent print command for {$imagePath} (x{$quantity})"); + return response()->json(['message' => 'Print command sent successfully.']); + } catch (ProcessFailedException $exception) { + Log::error("PrintController: Print command failed. Error: " . $exception->getMessage()); + return response()->json(['error' => 'Failed to send print command.', 'details' => $exception->getMessage()], 500); + } + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index a7bb293..490f2e2 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -14,7 +14,8 @@ class DatabaseSeeder extends Seeder { $this->call([ RoleSeeder::class, - UserSeeder::class + UserSeeder::class, + MaxCopiesSettingSeeder::class, ]); } } diff --git a/database/seeders/MaxCopiesSettingSeeder.php b/database/seeders/MaxCopiesSettingSeeder.php new file mode 100644 index 0000000..4db30c3 --- /dev/null +++ b/database/seeders/MaxCopiesSettingSeeder.php @@ -0,0 +1,20 @@ + 'max_number_of_copies'], + ['value' => 10] // Default value + ); + } +} diff --git a/resources/js/Components/ImageContextMenu.vue b/resources/js/Components/ImageContextMenu.vue index c76b731..1a515a6 100644 --- a/resources/js/Components/ImageContextMenu.vue +++ b/resources/js/Components/ImageContextMenu.vue @@ -5,28 +5,23 @@
- + + + +
\ No newline at end of file diff --git a/resources/js/Components/PrintQuantityModal.vue b/resources/js/Components/PrintQuantityModal.vue new file mode 100644 index 0000000..024752d --- /dev/null +++ b/resources/js/Components/PrintQuantityModal.vue @@ -0,0 +1,95 @@ + + + + + \ No newline at end of file diff --git a/resources/js/Pages/Home.vue b/resources/js/Pages/Home.vue index d2c0232..c74dc26 100644 --- a/resources/js/Pages/Home.vue +++ b/resources/js/Pages/Home.vue @@ -39,6 +39,12 @@ /> + @@ -70,6 +76,7 @@ import ImageContextMenu from '../Components/ImageContextMenu.vue'; import StyleSelector from '../Components/StyleSelector.vue'; import StyledImageDisplay from '../Components/StyledImageDisplay.vue'; // Import the new component import LoadingSpinner from '../Components/LoadingSpinner.vue'; // Import the new component +import PrintQuantityModal from '../Components/PrintQuantityModal.vue'; // Import the new component import axios from 'axios'; import { ref, computed, onMounted, onUnmounted } from 'vue'; @@ -84,6 +91,7 @@ const processingProgress = ref(0); // To store the progress percentage const errorMessage = ref(null); // New ref for error messages const isLoading = ref(false); // New ref for loading state const currentTheme = ref('light'); // New ref for current theme +const maxCopiesSetting = ref(10); // Default to 10, will be fetched from backend let touchStartX = 0; let touchEndX = 0; @@ -124,8 +132,27 @@ const showContextMenu = (image, event) => { }; const printImage = () => { - console.log('Printing image:', selectedImage.value); - currentOverlayComponent.value = null; + console.log('Showing print quantity modal for image:', selectedImage.value); + currentOverlayComponent.value = 'printQuantityModal'; +}; + +const handlePrintConfirmed = (quantity) => { + console.log(`Printing ${quantity} copies of image:`, selectedImage.value); + currentOverlayComponent.value = null; // Close the modal + + axios.post('/api/print-image', { + image_id: selectedImage.value.id, + image_path: selectedImage.value.path, + quantity: quantity, + }) + .then(response => { + console.log('Print request sent successfully:', response.data); + showError('Print request sent successfully!'); + }) + .catch(error => { + console.error('Error sending print request:', error); + showError(error.response?.data?.error || 'Failed to send print request.'); + }); }; const showStyleSelector = () => { @@ -294,6 +321,15 @@ onMounted(() => { console.error('Error fetching image refresh interval:', error); fetchInterval = setInterval(fetchImages, 5000); // Fallback to 5 seconds }); + + // Fetch max number of copies setting from API + axios.get('/api/max-copies-setting') + .then(response => { + maxCopiesSetting.value = response.data.max_copies; + }) + .catch(error => { + console.error('Error fetching max copies setting:', error); + }); }); onUnmounted(() => { diff --git a/resources/lang/de/api.php b/resources/lang/de/api.php index b6cb119..1c871b0 100644 --- a/resources/lang/de/api.php +++ b/resources/lang/de/api.php @@ -18,5 +18,10 @@ return [ 'styled_image_display.title' => 'Neu gestyltes Bild', 'styled_image_display.keep_button' => 'Behalten', 'styled_image_display.delete_button' => 'Löschen', - + 'print_command_sent_successfully' => 'Druckbefehl erfolgreich gesendet.', + 'failed_to_send_print_command' => 'Druckbefehl konnte nicht gesendet werden.', + 'api.print_dialog.title' => 'Bild drucken', + 'api.print_dialog.quantity_prompt' => 'Wie viele Kopien möchten Sie drucken?', + 'api.print_dialog.cancel_button' => 'Abbrechen', + 'api.print_dialog.print_button' => 'Drucken', ]; diff --git a/resources/lang/en/api.php b/resources/lang/en/api.php index 62f9340..b3455b0 100644 --- a/resources/lang/en/api.php +++ b/resources/lang/en/api.php @@ -18,4 +18,10 @@ return [ 'styled_image_display.title' => 'Newly Styled Image', 'styled_image_display.keep_button' => 'Keep', 'styled_image_display.delete_button' => 'Delete', + 'print_command_sent_successfully' => 'Print command sent successfully.', + 'failed_to_send_print_command' => 'Failed to send print command.', + 'print_dialog.title' => 'Print Image', + 'print_dialog.quantity_prompt' => 'How many copies would you like to print?', + 'print_dialog.cancel_button' => 'Cancel', + 'print_dialog.print_button' => 'Print', ]; diff --git a/routes/api.php b/routes/api.php index 2f368f6..9c6b70d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -28,6 +28,7 @@ Route::post('/admin/navigation-state', [NavigationStateController::class, 'store Route::get('/images', [ImageController::class, 'index']); Route::get('/styles', [StyleController::class, 'index']); Route::get('/image-refresh-interval', [StyleController::class, 'getImageRefreshInterval']); +Route::get('/max-copies-setting', [StyleController::class, 'getMaxNumberOfCopies']); Route::post('/images/style-change', [ImageController::class, 'styleChangeRequest']); Route::get('/comfyui-url', [ImageController::class, 'getComfyUiUrl']); @@ -39,3 +40,6 @@ Route::middleware('auth:sanctum')->group(function () { }); Route::get('/images/fetch-styled/{prompt_id}', [ImageController::class, 'fetchStyledImage']); + +use App\Http\Controllers\PrintController; +Route::post('/print-image', [PrintController::class, 'printImage']);