fixed migrations, changed settings to global settings, changed image list to have a "delete all" button instead of "create", fixed printing, added imagick for printing.

This commit is contained in:
2025-08-26 10:39:18 +02:00
parent 44dd0f2867
commit 9b1f6a479f
69 changed files with 17232 additions and 1263 deletions

View File

@@ -13,7 +13,7 @@
</svg>
Schließen
</button>
<button @click="$emit('print', image)" class="context-menu-button">
<button v-if="settings.show_print_button === '1'" @click="$emit('print', image)" class="context-menu-button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" class="w-8 h-8 inline-block align-middle mr-2"><!--!Font Awesome Free v6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M128 0C92.7 0 64 28.7 64 64l0 96 64 0 0-96 226.7 0L384 93.3l0 66.7 64 0 0-66.7c0-17-6.7-33.3-18.7-45.3L400 18.7C388 6.7 371.7 0 354.7 0L128 0zM384 352l0 32 0 64-256 0 0-64 0-16 0-16 256 0zm64 32l32 0c17.7 0 32-14.3 32-32l0-96c0-35.3-28.7-64-64-64L64 192c-35.3 0-64 28.7-64 64l0 96c0 17.7 14.3 32 32 32l32 0 0 64c0 35.3 28.7 64 64 64l256 0c35.3 0 64-28.7 64-64l0-64zM432 248a24 24 0 1 1 0 48 24 24 0 1 1 0-48z"/></svg>
Drucken
</button>
@@ -22,12 +22,12 @@
Stil ändern
</button>
</div>
<StyleSelector
<StyleSelector
v-else
:image_id="image.image_id"
@styleSelected="(style, imageId) => $emit('styleSelected', style, imageId)"
@back="showStyleSelectorView = false"
@close="$emit('close')"
@styleSelected="(style, imageId) => $emit('styleSelected', style, imageId)"
@back="showStyleSelectorView = false"
@close="$emit('close')"
/>
</div>
</div>
@@ -36,8 +36,12 @@
<script setup>
import { ref } from 'vue';
import { usePage } from '@inertiajs/vue3';
import StyleSelector from './StyleSelector.vue';
const page = usePage();
const settings = page.props.settings;
const props = defineProps({
position: {
type: Object,
@@ -115,4 +119,4 @@ const showStyleSelectorView = ref(false);
.context-menu-button:last-child {
border-bottom: none;
}
</style>
</style>

View File

@@ -19,14 +19,22 @@
</div>
</div>
<ImageContextMenu
v-if="currentOverlayComponent === 'contextMenu'"
:position="contextMenuPosition"
:image="selectedImage"
@close="currentOverlayComponent = null; selectedImage = null"
@print="printImage"
@changeStyle="showStyleSelector"
@styleSelected="applyStyle"
<ImageContextMenu
v-if="currentOverlayComponent === 'contextMenu'"
:position="contextMenuPosition"
:image="selectedImage"
@close="currentOverlayComponent = null; selectedImage = null"
@print="printImage"
@changeStyle="showStyleSelector"
@styleSelected="applyStyle"
/>
<StyleSelector
v-if="currentOverlayComponent === 'styleSelector'"
:image_id="selectedImage.id"
@styleSelected="applyStyle"
@back="goBackToContextMenu"
@close="currentOverlayComponent = null; selectedImage = null"
/>
<div v-if="errorMessage" class="fixed bottom-4 right-4 bg-red-500 text-white p-4 rounded-lg shadow-lg z-50">
@@ -87,7 +95,6 @@ const currentOverlayComponent = ref(null); // null, 'contextMenu', 'styleSelecto
const contextMenuPosition = ref({ x: 0, y: 0 });
const selectedImage = ref(null);
const styledImage = ref(null); // To store the newly styled image
const processingImageUuid = ref(null); // To store the UUID of the image being processed
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
@@ -168,91 +175,124 @@ const applyStyle = (style, imageId) => {
console.log('Applying style:', style.title, 'to image:', imageId);
currentOverlayComponent.value = null; // Close style selector immediately
isLoading.value = true; // Show loading spinner
processingImageUuid.value = selectedImage.value.uuid; // Set the UUID of the image being processed
processingProgress.value = 0; // Reset progress
axios.get('/api/comfyui-url')
.then(response => {
const comfyUiBaseUrl = response.data.comfyui_url;
const wsUrl = `ws://${new URL(comfyUiBaseUrl).host}/ws`;
const ws = new WebSocket(wsUrl);
// Send style change request to backend first
axios.post('/api/images/style-change', {
image_id: imageId,
style_id: style.id,
})
.then(response => {
console.log('Style change request sent:', response.data);
// Store the prompt_id and plugin from the backend response
const promptId = response.data.prompt_id;
const plugin = response.data.plugin;
ws.onopen = () => {
console.log('WebSocket connected to ComfyUI.');
// Send prompt to ComfyUI via HTTP, then listen for progress via WS
axios.post('/api/images/style-change', {
image_id: imageId,
style_id: style.id,
})
.then(response => {
console.log('Style change request sent:', response.data);
// Store the prompt_id from the backend response
const promptId = response.data.prompt_id;
// Handle different plugins differently
if (plugin === 'ComfyUi') {
// For ComfyUI, use WebSocket for progress monitoring
axios.get(`/api/comfyui-url?style_id=${style.id}`)
.then(comfyResponse => {
const comfyUiBaseUrl = comfyResponse.data.comfyui_url;
const wsUrl = `ws://${new URL(comfyUiBaseUrl).host}/ws`;
const ws = new WebSocket(wsUrl);
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'progress') {
console.log('ComfyUI Progress Message:', message);
const { value, max } = message.data;
const progress = (max > 0) ? (value / max) * 100 : 0;
if (message.data.prompt_id === promptId) {
processingProgress.value = progress;
ws.onopen = () => {
console.log('WebSocket connected to ComfyUI.');
if (processingProgress.value >= 100) {
console.log('Frontend: Progress reached 100%. Attempting to fetch final image.', { promptId: promptId });
// Fetch the final styled image from the backend
axios.get(`/api/images/fetch-styled/${promptId}`)
.then(imageResponse => {
console.log('Frontend: Successfully fetched styled image.', imageResponse.data);
styledImage.value = imageResponse.data.styled_image;
currentOverlayComponent.value = 'styledImageDisplay';
fetchImages(); // Refresh gallery
})
.catch(imageError => {
console.error('Frontend: Error fetching styled image:', imageError.response?.data?.error || imageError.message);
showError(imageError.response?.data?.error || 'Failed to fetch styled image.');
})
.finally(() => {
console.log('Frontend: Final fetch process completed.');
isLoading.value = false;
processingImageUuid.value = null;
processingProgress.value = 0;
ws.close();
});
}
}
} else {
console.warn('Received unexpected WebSocket message type:', message.type, message);
}
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'progress') {
console.log('ComfyUI Progress Message:', message);
const { value, max } = message.data;
const progress = (max > 0) ? (value / max) * 100 : 0;
if (message.data.prompt_id === promptId) {
processingProgress.value = progress;
if (processingProgress.value >= 100) {
console.log('Frontend: Progress reached 100%. Attempting to fetch final image.', { promptId: promptId });
// Fetch the final styled image from the backend
axios.get(`/api/images/fetch-styled/${promptId}`)
.then(imageResponse => {
console.log('Frontend: Successfully fetched styled image.', imageResponse.data);
styledImage.value = imageResponse.data.styled_image;
currentOverlayComponent.value = 'styledImageDisplay';
fetchImages(); // Refresh gallery
})
.catch(imageError => {
console.error('Frontend: Error fetching styled image:', imageError.response?.data?.error || imageError.message);
showError(imageError.response?.data?.error || 'Failed to fetch styled image.');
})
.finally(() => {
console.log('Frontend: Final fetch process completed.');
isLoading.value = false;
processingProgress.value = 0;
ws.close();
});
}
}
} else {
console.warn('Received unexpected WebSocket message type:', message.type, message);
}
};
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
showError('WebSocket connection error.');
isLoading.value = false;
processingProgress.value = 0;
};
ws.onclose = () => {
console.log('WebSocket closed.');
};
})
.catch(error => {
console.error('Error applying style:', error);
showError(error.response?.data?.error || 'Failed to apply style.');
console.error('Error fetching ComfyUI URL:', error);
showError(error.response?.data?.error || 'Failed to get ComfyUI URL.');
isLoading.value = false;
processingImageUuid.value = null;
processingProgress.value = 0;
ws.close();
});
} else {
// For other plugins, use polling approach
const pollForStyledImage = () => {
axios.get(`/api/images/fetch-styled/${promptId}`)
.then(imageResponse => {
console.log('Frontend: Successfully fetched styled image.', imageResponse.data);
styledImage.value = imageResponse.data.styled_image;
currentOverlayComponent.value = 'styledImageDisplay';
fetchImages(); // Refresh gallery
isLoading.value = false;
processingProgress.value = 0;
})
.catch(imageError => {
console.error('Frontend: Error fetching styled image:', imageError.response?.data?.error || imageError.message);
// If the image is not ready yet, continue polling
if (imageError.response?.status === 404) {
// Update progress if available
if (imageError.response?.data?.progress !== undefined) {
processingProgress.value = imageError.response.data.progress;
}
// Continue polling
setTimeout(pollForStyledImage, 2000); // Poll every 2 seconds
} else {
showError(imageError.response?.data?.error || 'Failed to fetch styled image.');
isLoading.value = false;
processingProgress.value = 0;
}
});
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
showError('WebSocket connection error.');
isLoading.value = false;
processingImageUuid.value = null;
processingProgress.value = 0;
};
ws.onclose = () => {
console.log('WebSocket closed.');
};
})
.catch(error => {
console.error('Error fetching ComfyUI URL:', error);
showError(error.response?.data?.error || 'Failed to get ComfyUI URL.');
isLoading.value = false;
});
// Start polling for the styled image
pollForStyledImage();
}
})
.catch(error => {
console.error('Error applying style:', error);
showError(error.response?.data?.error || 'Failed to apply style.');
isLoading.value = false;
processingProgress.value = 0;
});
};
const keepStyledImage = (imageToKeep) => {

View File

@@ -103,6 +103,11 @@ return [
'image_refresh_interval' => 'Bildaktualisierungsintervall (Sekunden)',
'new_image_timespan_minutes' => 'Neue Bilder Zeitspanne (Minuten)',
'gallery_heading' => 'Galerie Überschrift',
'max_number_of_copies' => 'Maximale Anzahl an Kopien',
'show_print_button' => 'Button \'Drucken\' beim Bild anzeigen',
'printer' => 'Drucker',
'custom_printer' => 'Eigener Drucker/Adresse',
'custom_printer_address' => 'Druckername oder -adresse',
],
],
'plugin' => [
@@ -155,6 +160,6 @@ return [
'delete_button' => 'Löschen',
],
'loading_spinner' => [
'processing_image' => 'Bild wird verarbeitet...',
'processing_image' => 'Bild wird verarbeitet...',
],
];
];

View File

@@ -102,6 +102,8 @@ return [
'image_refresh_interval' => 'Image Refresh Interval (seconds)',
'new_image_timespan_minutes' => 'New Image Timespan (minutes)',
'gallery_heading' => 'Gallery Heading',
"max_number_of_copies" => "Max Number of Copies",
"show_print_button" => "Show 'Print' button on image",
],
],
'user' => [

View File

@@ -0,0 +1,9 @@
<x-filament-panels::page>
<form wire:submit="save">
{{ $this->form }}
<x-filament-panels::form.actions
:actions="$this->getFormActions()"
/>
</form>
</x-filament-panels::page>

View File

@@ -22,37 +22,37 @@
</tr>
</thead>
<tbody>
@foreach ($this->getTableRecords() as $record)
@foreach ($plugins as $plugin)
<x-filament-tables::row>
<x-filament-tables::cell>
{{ $record->name }}
{{ $plugin->name }}
</x-filament-tables::cell>
<x-filament-tables::cell>
{{ $record->identifier }}
{{ $plugin->identifier }}
</x-filament-tables::cell>
<x-filament-tables::cell>
@if ($record->enabled)
@if ($plugin->enabled)
<x-filament::icon
icon="heroicon-o-check-circle"
class="text-success-500"
class="text-success-500 w-5 h-5"
/>
@else
<x-filament::icon
icon="heroicon-o-x-circle"
class="text-danger-500"
class="text-danger-500 w-5 h-5"
/>
@endif
</x-filament-tables::cell>
<x-filament-tables::cell>
@if ($record->configured)
@if ($plugin->configured)
<x-filament::icon
icon="heroicon-o-check-circle"
class="text-success-500"
class="text-success-500 w-5 h-5"
/>
@else
<x-filament::icon
icon="heroicon-o-x-circle"
class="text-danger-500"
class="text-danger-500 w-5 h-5"
/>
@endif
</x-filament-tables::cell>