tauri fixes
This commit is contained in:
parent
599c2c3b53
commit
ea12aae7c8
File diff suppressed because one or more lines are too long
BIN
packages/kbot/dist/win-64/tauri-app.exe
vendored
BIN
packages/kbot/dist/win-64/tauri-app.exe
vendored
Binary file not shown.
@ -145,7 +145,7 @@ function App() {
|
|||||||
|
|
||||||
const saveAndClose = async () => {
|
const saveAndClose = async () => {
|
||||||
// Find the last generated image
|
// Find the last generated image
|
||||||
const generatedFiles = files.filter(file => file.path.startsWith('generated_'));
|
const generatedFiles = files.filter(file => file.isGenerated);
|
||||||
if (generatedFiles.length === 0) {
|
if (generatedFiles.length === 0) {
|
||||||
addDebugMessage('warn', 'No generated images to save');
|
addDebugMessage('warn', 'No generated images to save');
|
||||||
return;
|
return;
|
||||||
@ -158,7 +158,7 @@ function App() {
|
|||||||
// Send the final result back to images.ts for saving
|
// Send the final result back to images.ts for saving
|
||||||
const result = {
|
const result = {
|
||||||
prompt,
|
prompt,
|
||||||
files: files.filter(f => !f.path.startsWith('generated_')).map(f => f.path),
|
files: files.filter(f => !f.isGenerated).map(f => f.path),
|
||||||
dst,
|
dst,
|
||||||
generatedImage: {
|
generatedImage: {
|
||||||
src: lastGenerated.src,
|
src: lastGenerated.src,
|
||||||
|
|||||||
@ -20,10 +20,10 @@ export default function ImageGallery({
|
|||||||
const [lightboxOpen, setLightboxOpen] = useState(false);
|
const [lightboxOpen, setLightboxOpen] = useState(false);
|
||||||
const [lightboxLoaded, setLightboxLoaded] = useState(false);
|
const [lightboxLoaded, setLightboxLoaded] = useState(false);
|
||||||
|
|
||||||
// Reset current index if images change
|
// Reset current index if images change, preventing out-of-bounds errors
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentIndex >= images.length && images.length > 0) {
|
if (images.length > 0 && currentIndex >= images.length) {
|
||||||
setCurrentIndex(0);
|
setCurrentIndex(Math.max(0, images.length - 1));
|
||||||
}
|
}
|
||||||
}, [images.length, currentIndex]);
|
}, [images.length, currentIndex]);
|
||||||
|
|
||||||
@ -71,17 +71,14 @@ export default function ImageGallery({
|
|||||||
preloadImage(index);
|
preloadImage(index);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleThumbnailClick = (imagePath: string, index: number) => {
|
const handleThumbnailClick = (event: React.MouseEvent<HTMLButtonElement>, imagePath: string, index: number) => {
|
||||||
// Always change the main image first
|
if (event.ctrlKey || event.metaKey) {
|
||||||
setCurrentIndex(index);
|
if (showSelection && onImageSelect) {
|
||||||
|
onImageSelect(imagePath);
|
||||||
// If it's a generated image and selection is enabled, also toggle selection
|
}
|
||||||
/*
|
} else {
|
||||||
const isGenerated = imagePath.startsWith('generated_');
|
setCurrentIndex(index);
|
||||||
if (showSelection && onImageSelect) {
|
|
||||||
onImageSelect(imagePath);
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (images.length === 0) {
|
if (images.length === 0) {
|
||||||
@ -100,9 +97,24 @@ export default function ImageGallery({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentImage = images[currentIndex];
|
// Safeguard against rendering with an invalid index after a delete/remove operation
|
||||||
const isGenerated = currentImage?.path.startsWith('generated_');
|
const safeIndex = Math.max(0, Math.min(currentIndex, images.length - 1));
|
||||||
const isSelected = currentImage?.selected || false;
|
const currentImage = images.length > 0 ? images[safeIndex] : null;
|
||||||
|
|
||||||
|
if (!currentImage) {
|
||||||
|
// This should theoretically not be reached if the length check above is sound,
|
||||||
|
// but it's an extra layer of protection.
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center p-8 text-center">
|
||||||
|
<div className="w-64 h-64 bg-slate-100 dark:bg-slate-700 rounded-xl flex items-center justify-center">
|
||||||
|
<p>Loading...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isGenerated = !!currentImage.isGenerated;
|
||||||
|
const isSelected = currentImage.selected || false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
@ -119,7 +131,7 @@ export default function ImageGallery({
|
|||||||
? 'border-green-300'
|
? 'border-green-300'
|
||||||
: 'border-white/30'
|
: 'border-white/30'
|
||||||
}`}
|
}`}
|
||||||
onDoubleClick={() => openLightbox(currentIndex)}
|
onDoubleClick={() => openLightbox(safeIndex)}
|
||||||
title="Double-click for fullscreen"
|
title="Double-click for fullscreen"
|
||||||
/>
|
/>
|
||||||
<div className="absolute top-2 left-2 flex flex-col gap-2">
|
<div className="absolute top-2 left-2 flex flex-col gap-2">
|
||||||
@ -145,7 +157,7 @@ export default function ImageGallery({
|
|||||||
{/* Compact Image Info */}
|
{/* Compact Image Info */}
|
||||||
<div className="text-center mb-3">
|
<div className="text-center mb-3">
|
||||||
<p className="text-xs text-slate-600 dark:text-slate-400">
|
<p className="text-xs text-slate-600 dark:text-slate-400">
|
||||||
{currentImage.path.split(/[/\\]/).pop()} • {currentIndex + 1}/{images.length}
|
{currentImage.path.split(/[/\\]/).pop()} • {safeIndex + 1}/{images.length}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -153,15 +165,16 @@ export default function ImageGallery({
|
|||||||
<div className="grid grid-cols-4 md:grid-cols-6 lg:grid-cols-8 gap-2">
|
<div className="grid grid-cols-4 md:grid-cols-6 lg:grid-cols-8 gap-2">
|
||||||
{images.map((image, index) => {
|
{images.map((image, index) => {
|
||||||
const thumbIsGenerating = image.path.startsWith('generating_');
|
const thumbIsGenerating = image.path.startsWith('generating_');
|
||||||
const thumbIsGenerated = image.path.startsWith('generated_');
|
const thumbIsGenerated = !!image.isGenerated;
|
||||||
const thumbIsSelected = image.selected || false;
|
const thumbIsSelected = image.selected || false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
key={image.path}
|
key={image.path}
|
||||||
onClick={() => handleThumbnailClick(image.path, index)}
|
onClick={(e) => handleThumbnailClick(e, image.path, index)}
|
||||||
onDoubleClick={() => openLightbox(index)}
|
onDoubleClick={() => openLightbox(index)}
|
||||||
className={`relative aspect-square rounded-lg overflow-hidden transition-all duration-300 border-2 ${
|
className={`group relative aspect-square rounded-lg overflow-hidden transition-all duration-300 border-2 ${
|
||||||
currentIndex === index
|
currentIndex === index
|
||||||
? 'ring-2 ring-orange-500 border-orange-500'
|
? 'ring-2 ring-orange-500 border-orange-500'
|
||||||
: thumbIsSelected
|
: thumbIsSelected
|
||||||
@ -213,15 +226,17 @@ export default function ImageGallery({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Delete button (permanent) */}
|
{/* Delete button (permanent) */}
|
||||||
{!thumbIsGenerating && onImageDelete && (
|
{!thumbIsGenerating && onImageDelete && onImageRemove && (
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (window.confirm('Are you sure you want to permanently delete this file from your disk?')) {
|
if (window.confirm('Are you sure you want to permanently delete this file from your disk?')) {
|
||||||
onImageDelete(image.path);
|
onImageDelete(image.path);
|
||||||
|
onImageRemove(image.path);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="absolute bottom-1 right-1 bg-red-500/80 hover:bg-red-600 text-white rounded-full w-5 h-5 flex items-center justify-center text-xs opacity-0 group-hover:opacity-100 transition-all duration-200"
|
className="absolute bottom-1 right-1 bg-red-600/70 hover:bg-red-600 text-white rounded-full w-5 h-5 flex items-center justify-center text-xs transition-all duration-200"
|
||||||
title="Delete File Permanently"
|
title="Delete File Permanently"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3" viewBox="0 0 20 20" fill="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3" viewBox="0 0 20 20" fill="currentColor">
|
||||||
@ -243,8 +258,8 @@ export default function ImageGallery({
|
|||||||
<div className="relative w-full h-full flex items-center justify-center">
|
<div className="relative w-full h-full flex items-center justify-center">
|
||||||
{lightboxLoaded ? (
|
{lightboxLoaded ? (
|
||||||
<img
|
<img
|
||||||
src={images[currentIndex].src}
|
src={images[safeIndex].src}
|
||||||
alt={images[currentIndex].path}
|
alt={images[safeIndex].path}
|
||||||
className="max-w-full max-h-full object-contain"
|
className="max-w-full max-h-full object-contain"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
/>
|
/>
|
||||||
@ -267,14 +282,14 @@ export default function ImageGallery({
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Navigation Buttons */}
|
{/* Navigation Buttons */}
|
||||||
{currentIndex > 0 && (
|
{safeIndex > 0 && (
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setCurrentIndex(currentIndex - 1);
|
setCurrentIndex(safeIndex - 1);
|
||||||
setLightboxLoaded(false);
|
setLightboxLoaded(false);
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.src = images[currentIndex - 1].src;
|
img.src = images[safeIndex - 1].src;
|
||||||
img.onload = () => setLightboxLoaded(true);
|
img.onload = () => setLightboxLoaded(true);
|
||||||
}}
|
}}
|
||||||
className="absolute left-4 top-1/2 transform -translate-y-1/2 p-4 text-white text-3xl bg-black/75 rounded-lg hover:bg-black/90 transition-all duration-200"
|
className="absolute left-4 top-1/2 transform -translate-y-1/2 p-4 text-white text-3xl bg-black/75 rounded-lg hover:bg-black/90 transition-all duration-200"
|
||||||
@ -284,14 +299,14 @@ export default function ImageGallery({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{currentIndex < images.length - 1 && (
|
{safeIndex < images.length - 1 && (
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setCurrentIndex(currentIndex + 1);
|
setCurrentIndex(safeIndex + 1);
|
||||||
setLightboxLoaded(false);
|
setLightboxLoaded(false);
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.src = images[currentIndex + 1].src;
|
img.src = images[safeIndex + 1].src;
|
||||||
img.onload = () => setLightboxLoaded(true);
|
img.onload = () => setLightboxLoaded(true);
|
||||||
}}
|
}}
|
||||||
className="absolute right-4 top-1/2 transform -translate-y-1/2 p-4 text-white text-3xl bg-black/75 rounded-lg hover:bg-black/90 transition-all duration-200"
|
className="absolute right-4 top-1/2 transform -translate-y-1/2 p-4 text-white text-3xl bg-black/75 rounded-lg hover:bg-black/90 transition-all duration-200"
|
||||||
@ -304,7 +319,7 @@ export default function ImageGallery({
|
|||||||
{/* Info */}
|
{/* Info */}
|
||||||
{lightboxLoaded && (
|
{lightboxLoaded && (
|
||||||
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 bg-black/75 text-white px-4 py-2 rounded-lg text-sm">
|
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 bg-black/75 text-white px-4 py-2 rounded-lg text-sm">
|
||||||
{images[currentIndex].path.split(/[/\\]/).pop()} • {currentIndex + 1} of {images.length} • ESC to close
|
{images[safeIndex].path.split(/[/\\]/).pop()} • {safeIndex + 1} of {images.length} • ESC to close
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -70,13 +70,12 @@ export function useTauriListeners({
|
|||||||
|
|
||||||
if (imageData.base64 && imageData.mimeType && imageData.filename) {
|
if (imageData.base64 && imageData.mimeType && imageData.filename) {
|
||||||
const src = `data:${imageData.mimeType};base64,${imageData.base64}`;
|
const src = `data:${imageData.mimeType};base64,${imageData.base64}`;
|
||||||
const hasGeneratingPlaceholder = document.querySelector('[src^="data:image/svg+xml"]'); // A bit hacky, but avoids depending on files state
|
const isGeneratedImage = isGenerating || document.querySelector('[src^="data:image/svg+xml"]');
|
||||||
const isGeneratedImage = isGenerating || hasGeneratingPlaceholder || imageData.filename.includes('_out') || imageData.filename.includes('generated_');
|
|
||||||
|
|
||||||
if (isGeneratedImage) {
|
if (isGeneratedImage) {
|
||||||
const generatedImageFile: ImageFile = { path: `generated_${imageData.filename}`, src };
|
const generatedImageFile: ImageFile = { path: imageData.filename, src, isGenerated: true };
|
||||||
setFiles(prev => {
|
setFiles(prev => {
|
||||||
const withoutPlaceholder = prev.filter(file => !file.path.startsWith('generating_') && !file.path.endsWith(imageData.filename) && file.path !== `generated_${imageData.filename}`);
|
const withoutPlaceholder = prev.filter(file => !file.path.startsWith('generating_') && file.path !== imageData.filename);
|
||||||
return [...withoutPlaceholder, generatedImageFile];
|
return [...withoutPlaceholder, generatedImageFile];
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -87,7 +86,7 @@ export function useTauriListeners({
|
|||||||
setIsGenerating(false);
|
setIsGenerating(false);
|
||||||
addDebugMessage('info', '✅ Generated image added to files', { filename: imageData.filename, prompt });
|
addDebugMessage('info', '✅ Generated image added to files', { filename: imageData.filename, prompt });
|
||||||
} else {
|
} else {
|
||||||
const newImageFile = { path: imageData.filename, src };
|
const newImageFile: ImageFile = { path: imageData.filename, src, isGenerated: false };
|
||||||
setFiles(prevFiles => {
|
setFiles(prevFiles => {
|
||||||
const exists = prevFiles.some(f => f.path === imageData.filename);
|
const exists = prevFiles.some(f => f.path === imageData.filename);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ export interface ImageFile {
|
|||||||
path: string;
|
path: string;
|
||||||
src: string;
|
src: string;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
|
isGenerated?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GeneratedImage {
|
export interface GeneratedImage {
|
||||||
|
|||||||
@ -239,9 +239,15 @@ async function launchGuiAndGetPrompt(argv: any): Promise<string | null> {
|
|||||||
? path.basename(genFiles[0], path.extname(genFiles[0]))
|
? path.basename(genFiles[0], path.extname(genFiles[0]))
|
||||||
: 'generated';
|
: 'generated';
|
||||||
|
|
||||||
const newFileName = `${baseFileName}_gen_0.png`;
|
let i = 0;
|
||||||
|
let newFileName;
|
||||||
|
let finalDstPath;
|
||||||
|
do {
|
||||||
|
newFileName = `${baseFileName}_gen_${i}.png`;
|
||||||
|
finalDstPath = path.resolve(dstDir, newFileName);
|
||||||
|
i++;
|
||||||
|
} while (exists(finalDstPath));
|
||||||
|
|
||||||
const finalDstPath = path.resolve(dstDir, newFileName);
|
|
||||||
logger.info(`📝 Determined destination path for generated image: ${finalDstPath}`);
|
logger.info(`📝 Determined destination path for generated image: ${finalDstPath}`);
|
||||||
// --- End new logic ---
|
// --- End new logic ---
|
||||||
|
|
||||||
@ -262,8 +268,10 @@ async function launchGuiAndGetPrompt(argv: any): Promise<string | null> {
|
|||||||
} else {
|
} else {
|
||||||
// Image creation
|
// Image creation
|
||||||
logger.info(`Creating image with prompt: "${genPrompt}"`);
|
logger.info(`Creating image with prompt: "${genPrompt}"`);
|
||||||
|
const creationArgv = { ...argv };
|
||||||
|
delete creationArgv.include;
|
||||||
const parsedOptions = ImageOptionsSchema().parse({
|
const parsedOptions = ImageOptionsSchema().parse({
|
||||||
...argv,
|
...creationArgv,
|
||||||
prompt: genPrompt,
|
prompt: genPrompt,
|
||||||
dst: finalDstPath // Use the new path
|
dst: finalDstPath // Use the new path
|
||||||
});
|
});
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 194 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.8 MiB |
Loading…
Reference in New Issue
Block a user