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 () => {
|
||||
// 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) {
|
||||
addDebugMessage('warn', 'No generated images to save');
|
||||
return;
|
||||
@ -158,7 +158,7 @@ function App() {
|
||||
// Send the final result back to images.ts for saving
|
||||
const result = {
|
||||
prompt,
|
||||
files: files.filter(f => !f.path.startsWith('generated_')).map(f => f.path),
|
||||
files: files.filter(f => !f.isGenerated).map(f => f.path),
|
||||
dst,
|
||||
generatedImage: {
|
||||
src: lastGenerated.src,
|
||||
|
||||
@ -20,10 +20,10 @@ export default function ImageGallery({
|
||||
const [lightboxOpen, setLightboxOpen] = 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(() => {
|
||||
if (currentIndex >= images.length && images.length > 0) {
|
||||
setCurrentIndex(0);
|
||||
if (images.length > 0 && currentIndex >= images.length) {
|
||||
setCurrentIndex(Math.max(0, images.length - 1));
|
||||
}
|
||||
}, [images.length, currentIndex]);
|
||||
|
||||
@ -71,17 +71,14 @@ export default function ImageGallery({
|
||||
preloadImage(index);
|
||||
};
|
||||
|
||||
const handleThumbnailClick = (imagePath: string, index: number) => {
|
||||
// Always change the main image first
|
||||
setCurrentIndex(index);
|
||||
|
||||
// If it's a generated image and selection is enabled, also toggle selection
|
||||
/*
|
||||
const isGenerated = imagePath.startsWith('generated_');
|
||||
if (showSelection && onImageSelect) {
|
||||
onImageSelect(imagePath);
|
||||
const handleThumbnailClick = (event: React.MouseEvent<HTMLButtonElement>, imagePath: string, index: number) => {
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
if (showSelection && onImageSelect) {
|
||||
onImageSelect(imagePath);
|
||||
}
|
||||
} else {
|
||||
setCurrentIndex(index);
|
||||
}
|
||||
*/
|
||||
};
|
||||
|
||||
if (images.length === 0) {
|
||||
@ -100,9 +97,24 @@ export default function ImageGallery({
|
||||
);
|
||||
}
|
||||
|
||||
const currentImage = images[currentIndex];
|
||||
const isGenerated = currentImage?.path.startsWith('generated_');
|
||||
const isSelected = currentImage?.selected || false;
|
||||
// Safeguard against rendering with an invalid index after a delete/remove operation
|
||||
const safeIndex = Math.max(0, Math.min(currentIndex, images.length - 1));
|
||||
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 (
|
||||
<div className="flex flex-col">
|
||||
@ -119,7 +131,7 @@ export default function ImageGallery({
|
||||
? 'border-green-300'
|
||||
: 'border-white/30'
|
||||
}`}
|
||||
onDoubleClick={() => openLightbox(currentIndex)}
|
||||
onDoubleClick={() => openLightbox(safeIndex)}
|
||||
title="Double-click for fullscreen"
|
||||
/>
|
||||
<div className="absolute top-2 left-2 flex flex-col gap-2">
|
||||
@ -145,7 +157,7 @@ export default function ImageGallery({
|
||||
{/* Compact Image Info */}
|
||||
<div className="text-center mb-3">
|
||||
<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>
|
||||
</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">
|
||||
{images.map((image, index) => {
|
||||
const thumbIsGenerating = image.path.startsWith('generating_');
|
||||
const thumbIsGenerated = image.path.startsWith('generated_');
|
||||
const thumbIsGenerated = !!image.isGenerated;
|
||||
const thumbIsSelected = image.selected || false;
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
key={image.path}
|
||||
onClick={() => handleThumbnailClick(image.path, index)}
|
||||
onClick={(e) => handleThumbnailClick(e, image.path, 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
|
||||
? 'ring-2 ring-orange-500 border-orange-500'
|
||||
: thumbIsSelected
|
||||
@ -213,15 +226,17 @@ export default function ImageGallery({
|
||||
)}
|
||||
|
||||
{/* Delete button (permanent) */}
|
||||
{!thumbIsGenerating && onImageDelete && (
|
||||
{!thumbIsGenerating && onImageDelete && onImageRemove && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (window.confirm('Are you sure you want to permanently delete this file from your disk?')) {
|
||||
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"
|
||||
>
|
||||
<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">
|
||||
{lightboxLoaded ? (
|
||||
<img
|
||||
src={images[currentIndex].src}
|
||||
alt={images[currentIndex].path}
|
||||
src={images[safeIndex].src}
|
||||
alt={images[safeIndex].path}
|
||||
className="max-w-full max-h-full object-contain"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
@ -267,14 +282,14 @@ export default function ImageGallery({
|
||||
</button>
|
||||
|
||||
{/* Navigation Buttons */}
|
||||
{currentIndex > 0 && (
|
||||
{safeIndex > 0 && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setCurrentIndex(currentIndex - 1);
|
||||
setCurrentIndex(safeIndex - 1);
|
||||
setLightboxLoaded(false);
|
||||
const img = new Image();
|
||||
img.src = images[currentIndex - 1].src;
|
||||
img.src = images[safeIndex - 1].src;
|
||||
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"
|
||||
@ -284,14 +299,14 @@ export default function ImageGallery({
|
||||
</button>
|
||||
)}
|
||||
|
||||
{currentIndex < images.length - 1 && (
|
||||
{safeIndex < images.length - 1 && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setCurrentIndex(currentIndex + 1);
|
||||
setCurrentIndex(safeIndex + 1);
|
||||
setLightboxLoaded(false);
|
||||
const img = new Image();
|
||||
img.src = images[currentIndex + 1].src;
|
||||
img.src = images[safeIndex + 1].src;
|
||||
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"
|
||||
@ -304,7 +319,7 @@ export default function ImageGallery({
|
||||
{/* Info */}
|
||||
{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">
|
||||
{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>
|
||||
|
||||
@ -70,13 +70,12 @@ export function useTauriListeners({
|
||||
|
||||
if (imageData.base64 && imageData.mimeType && imageData.filename) {
|
||||
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 || hasGeneratingPlaceholder || imageData.filename.includes('_out') || imageData.filename.includes('generated_');
|
||||
const isGeneratedImage = isGenerating || document.querySelector('[src^="data:image/svg+xml"]');
|
||||
|
||||
if (isGeneratedImage) {
|
||||
const generatedImageFile: ImageFile = { path: `generated_${imageData.filename}`, src };
|
||||
const generatedImageFile: ImageFile = { path: imageData.filename, src, isGenerated: true };
|
||||
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];
|
||||
});
|
||||
|
||||
@ -87,7 +86,7 @@ export function useTauriListeners({
|
||||
setIsGenerating(false);
|
||||
addDebugMessage('info', '✅ Generated image added to files', { filename: imageData.filename, prompt });
|
||||
} else {
|
||||
const newImageFile = { path: imageData.filename, src };
|
||||
const newImageFile: ImageFile = { path: imageData.filename, src, isGenerated: false };
|
||||
setFiles(prevFiles => {
|
||||
const exists = prevFiles.some(f => f.path === imageData.filename);
|
||||
if (!exists) {
|
||||
|
||||
@ -2,6 +2,7 @@ export interface ImageFile {
|
||||
path: string;
|
||||
src: string;
|
||||
selected?: boolean;
|
||||
isGenerated?: boolean;
|
||||
}
|
||||
|
||||
export interface GeneratedImage {
|
||||
|
||||
@ -239,9 +239,15 @@ async function launchGuiAndGetPrompt(argv: any): Promise<string | null> {
|
||||
? path.basename(genFiles[0], path.extname(genFiles[0]))
|
||||
: '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}`);
|
||||
// --- End new logic ---
|
||||
|
||||
@ -262,8 +268,10 @@ async function launchGuiAndGetPrompt(argv: any): Promise<string | null> {
|
||||
} else {
|
||||
// Image creation
|
||||
logger.info(`Creating image with prompt: "${genPrompt}"`);
|
||||
const creationArgv = { ...argv };
|
||||
delete creationArgv.include;
|
||||
const parsedOptions = ImageOptionsSchema().parse({
|
||||
...argv,
|
||||
...creationArgv,
|
||||
prompt: genPrompt,
|
||||
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