tauri fixes

This commit is contained in:
babayaga 2025-09-18 13:31:36 +02:00
parent 45c0ba14aa
commit 599c2c3b53
22 changed files with 369 additions and 157 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

View File

@ -278,6 +278,22 @@ fn forward_image_to_frontend(base64: String, mime_type: String, filename: String
Ok(())
}
#[tauri::command]
fn request_file_deletion(path: String) -> Result<(), String> {
eprintln!("[RUST LOG]: request_file_deletion command called.");
eprintln!("[RUST LOG]: - Path: {}", path);
let request = serde_json::json!({
"type": "delete_request",
"path": path,
});
println!("{}", serde_json::to_string(&request).unwrap());
eprintln!("[RUST LOG]: Deletion request sent to images.ts");
Ok(())
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let app = tauri::Builder::default()
@ -302,7 +318,8 @@ pub fn run() {
request_config_from_images,
forward_config_to_frontend,
forward_image_to_frontend,
generate_image_via_backend
generate_image_via_backend,
request_file_deletion
])
.setup(|app| {
#[cfg(debug_assertions)] // only include this code on debug builds
@ -405,6 +422,25 @@ pub fn run() {
eprintln!("[RUST LOG]: Generation complete emitted successfully");
}
}
"file_deleted_successfully" => {
if let Some(path) = command.get("path").and_then(|v| v.as_str()) {
eprintln!("[RUST LOG]: Received confirmation of file deletion: {}", path);
if let Err(e) = app_handle.emit("file-deleted-successfully", &serde_json::json!({ "path": path })) {
eprintln!("[RUST LOG]: Failed to emit file-deleted-successfully: {}", e);
}
}
}
"file_deletion_error" => {
if let (Some(path), Some(error)) = (
command.get("path").and_then(|v| v.as_str()),
command.get("error").and_then(|v| v.as_str())
) {
eprintln!("[RUST LOG]: Received file deletion error for {}: {}", path, error);
if let Err(e) = app_handle.emit("file-deletion-error", &serde_json::json!({ "path": path, "error": error })) {
eprintln!("[RUST LOG]: Failed to emit file-deletion-error: {}", e);
}
}
}
_ => {
eprintln!("[RUST LOG]: Unknown command: {}", cmd);
}

View File

@ -29,6 +29,12 @@ function App() {
const [messageToSend, setMessageToSend] = useState("");
const [generationTimeoutId, setGenerationTimeoutId] = useState<NodeJS.Timeout | null>(null);
const deleteFilePermanently = async (pathToDelete: string) => {
addDebugMessage('info', `Requesting deletion of file: ${pathToDelete}`);
// This will be the new tauri command
await tauriApi.requestFileDeletion({ path: pathToDelete });
};
const generateDefaultDst = (fileCount: number, firstFilePath?: string) => {
if (fileCount === 1 && firstFilePath) {
const parsedPath = firstFilePath.split(/[/\\]/).pop() || 'image';
@ -91,23 +97,17 @@ function App() {
const addFiles = async (newPaths: string[]) => {
const uniqueNewPaths = newPaths.filter(newPath => !files.some(f => f.path === newPath));
const newImageFiles: ImageFile[] = [];
for (const path of uniqueNewPaths) {
try {
const relativePath = await tauriApi.resolvePathRelativeToHome(path);
if (!relativePath) {
console.warn(`Could not resolve relative path for: ${path}`);
continue;
}
const buffer = await tauriApi.fs.readFile(relativePath, { baseDir: tauriApi.fs.BaseDirectory().Home });
const buffer = await tauriApi.fs.readFile(path);
const base64 = arrayBufferToBase64(Array.from(buffer));
const mimeType = path.toLowerCase().endsWith('.png') ? 'image/png' :
path.toLowerCase().endsWith('.jpg') || path.toLowerCase().endsWith('.jpeg') ? 'image/jpeg' :
const mimeType = path.toLowerCase().endsWith('.png') ? 'image/png' :
path.toLowerCase().endsWith('.jpg') || path.toLowerCase().endsWith('.jpeg') ? 'image/jpeg' :
'image/png';
const src = `data:${mimeType};base64,${base64}`;
newImageFiles.push({ path, src });
} catch (e) {
const errorMessage = e instanceof Error ? e.message : JSON.stringify(e);
@ -115,7 +115,7 @@ function App() {
tauriApi.logErrorToConsole(`[Frontend Error] Failed to read file ${path}: ${errorMessage}`);
}
}
setFiles(prevFiles => [...prevFiles, ...newImageFiles]);
};
@ -128,9 +128,9 @@ function App() {
};
const toggleImageSelection = (imagePath: string) => {
setFiles(prev =>
prev.map(file =>
file.path === imagePath
setFiles(prev =>
prev.map(file =>
file.path === imagePath
? { ...file, selected: !file.selected }
: file
)
@ -183,7 +183,7 @@ function App() {
setIsGenerating(true);
addDebugMessage('info', `🎨 Starting image generation via backend: "${promptText}"`);
// Add placeholder image with spinner to the files grid
const placeholderFile: ImageFile = {
path: `generating_${Date.now()}`,
@ -198,43 +198,43 @@ function App() {
</svg>
`)
};
setFiles(prev => [...prev, placeholderFile]);
try {
// Use the images.ts backend instead of direct API calls
const filePaths = includeImages.map(img => img.path);
const genDst = dst || `generated_${Date.now()}.png`;
addDebugMessage('info', 'Sending generation request to images.ts backend', {
prompt: promptText,
files: filePaths,
dst: genDst
});
// Send generation request via Tauri command
await tauriApi.generateImageViaBackend({
prompt: promptText,
prompt: promptText,
files: filePaths,
dst: genDst
});
addDebugMessage('info', '📤 Generation request sent to backend');
// Clear any existing timeout
if (generationTimeoutId) {
clearTimeout(generationTimeoutId);
}
const timeoutId = setTimeout(() => {
addDebugMessage('warn', '⏰ Generation timeout - resetting state');
setIsGenerating(false);
setFiles(prev => prev.filter(file => !file.path.startsWith('generating_')));
setGenerationTimeoutId(null);
}, 30000);
setGenerationTimeoutId(timeoutId);
} catch (error) {
addDebugMessage('error', 'Failed to send generation request', {
error: error instanceof Error ? error.message : JSON.stringify(error)
@ -267,7 +267,7 @@ function App() {
const toggleTheme = () => {
const newDarkMode = !isDarkMode;
setIsDarkMode(newDarkMode);
if (newDarkMode) {
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
@ -292,13 +292,13 @@ function App() {
}, []);
async function submit() {
if (apiKey) {
// Generate image via backend (always chat mode now)
// Use selected images if any, otherwise use all files
const selectedImages = getSelectedImages();
const imagesToUse = selectedImages.length > 0 ? selectedImages : files.filter(f => !f.path.startsWith('generating_'));
await generateImage(prompt, imagesToUse);
// Don't clear prompt - let user iterate
} else {
@ -309,17 +309,17 @@ function App() {
const clearDebugMessages = async () => {
setDebugMessages([]);
await tauriApi.clearDebugMessages();
addDebugMessage('info', 'Debug messages cleared');
addDebugMessage('info', 'Debug messages cleared');
};
const sendIPCMessage = async (messageType: string, data: any) => {
await tauriApi.sendIPCMessage(messageType, data);
addDebugMessage('info', `IPC message sent: ${messageType}`, data);
addDebugMessage('info', `IPC message sent: ${messageType}`, data);
};
const sendMessageToImages = async () => {
if (!messageToSend.trim()) return;
const message = {
message: messageToSend,
timestamp: Date.now(),
@ -333,7 +333,7 @@ function App() {
const errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
addDebugMessage('error', `Failed to send message: ${errorMessage}`);
}
// Clear the input
setMessageToSend('');
};
@ -351,7 +351,7 @@ function App() {
const fileArray = Array.from(target.files);
const newImageFiles: ImageFile[] = [];
let loadedCount = 0;
fileArray.forEach(file => {
const reader = new FileReader();
reader.onload = (e) => {
@ -373,13 +373,13 @@ function App() {
input.click();
return;
}
try {
if (!tauriApi.dialog.open) {
console.error('Open function not available');
return;
}
const selected = await tauriApi.dialog.open({
multiple: true,
filters: [{
@ -391,7 +391,7 @@ function App() {
addFiles(selected);
}
} catch (e) {
console.error('File picker error:', e);
console.error('File picker error:', e);
tauriApi.logErrorToConsole(`[Frontend Error] File picker error: ${JSON.stringify(e)}`);
}
}
@ -400,7 +400,7 @@ function App() {
try {
// Extract current filename from dst for default, or use smart default
const currentFilename = dst.split(/[/\\]/).pop() || generateDefaultDst(files.length, files[0]?.path);
const selected = await tauriApi.dialog.save({
defaultPath: currentFilename,
filters: [{
@ -408,12 +408,12 @@ function App() {
extensions: ['png', 'jpg']
}]
});
if (selected) {
setDst(selected);
}
} catch (e) {
console.error('Save dialog error:', e);
console.error('Save dialog error:', e);
tauriApi.logErrorToConsole(`[Frontend Error] Save dialog error: ${JSON.stringify(e)}`);
}
}
@ -425,7 +425,7 @@ function App() {
<div className="absolute -top-40 -right-40 w-80 h-80 bg-gradient-to-br from-indigo-200/30 to-purple-200/30 dark:from-indigo-500/20 dark:to-purple-500/20 rounded-full blur-3xl"></div>
<div className="absolute -bottom-40 -left-40 w-80 h-80 bg-gradient-to-tr from-cyan-200/30 to-blue-200/30 dark:from-cyan-500/20 dark:to-blue-500/20 rounded-full blur-3xl"></div>
</div>
<div className="w-full max-w-4xl relative z-10 mt-8">
<Header
showDebugPanel={showDebugPanel}
@ -450,6 +450,7 @@ function App() {
saveAndClose={saveAndClose}
submit={submit}
addImageFromUrl={addImageFromUrl}
onImageDelete={deleteFilePermanently}
/>
{/* Debug Panel */}

View File

@ -5,6 +5,7 @@ interface ImageGalleryProps {
images: ImageFile[];
onImageSelect?: (imagePath: string) => void;
onImageRemove?: (imagePath: string) => void;
onImageDelete?: (imagePath: string) => void;
showSelection?: boolean;
}
@ -12,6 +13,7 @@ export default function ImageGallery({
images,
onImageSelect,
onImageRemove,
onImageDelete,
showSelection = false
}: ImageGalleryProps) {
const [currentIndex, setCurrentIndex] = useState(0);
@ -74,10 +76,12 @@ export default function ImageGallery({
setCurrentIndex(index);
// If it's a generated image and selection is enabled, also toggle selection
/*
const isGenerated = imagePath.startsWith('generated_');
if (showSelection && isGenerated && onImageSelect) {
if (showSelection && onImageSelect) {
onImageSelect(imagePath);
}
*/
};
if (images.length === 0) {
@ -202,11 +206,29 @@ export default function ImageGallery({
onImageRemove(image.path);
}}
className="absolute top-1 right-1 bg-red-500/90 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"
title="Remove"
title="Remove from view"
>
×
</button>
)}
{/* Delete button (permanent) */}
{!thumbIsGenerating && onImageDelete && (
<button
onClick={(e) => {
e.stopPropagation();
if (window.confirm('Are you sure you want to permanently delete this file from your disk?')) {
onImageDelete(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"
title="Delete File Permanently"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm4 0a1 1 0 012 0v6a1 1 0 11-2 0V8z" clipRule="evenodd" />
</svg>
</button>
)}
</button>
);
})}

View File

@ -18,6 +18,7 @@ interface PromptFormProps {
saveAndClose: () => void;
submit: () => void;
addImageFromUrl: (url: string) => void;
onImageDelete?: (path: string) => void;
}
const PromptForm: React.FC<PromptFormProps> = ({
@ -36,7 +37,10 @@ const PromptForm: React.FC<PromptFormProps> = ({
saveAndClose,
submit,
addImageFromUrl,
onImageDelete
}) => {
const selectedCount = getSelectedImages().length;
return (
<form
className="flex flex-col items-center glass-card p-8 glass-shimmer shadow-2xl"
@ -115,9 +119,9 @@ const PromptForm: React.FC<PromptFormProps> = ({
<div className="flex justify-between items-center mb-4">
<h3 className="text-sm font-semibold text-slate-700 dark:text-slate-300">
Images ({files.length})
{getSelectedImages().length > 0 && (
{selectedCount > 0 && (
<span className="ml-2 text-blue-600 dark:text-blue-400">
{getSelectedImages().length} selected
{selectedCount} selected
</span>
)}
</h3>
@ -137,6 +141,7 @@ const PromptForm: React.FC<PromptFormProps> = ({
onImageSelect={toggleImageSelection}
onImageRemove={removeFile}
showSelection={true}
onImageDelete={onImageDelete}
/>
</div>
</div>

View File

@ -11,6 +11,7 @@ export enum TauriCommand {
CLEAR_DEBUG_MESSAGES = 'clear_debug_messages',
SEND_IPC_MESSAGE = 'send_ipc_message',
SEND_MESSAGE_TO_STDOUT = 'send_message_to_stdout',
REQUEST_FILE_DELETION = 'request_file_deletion',
}
export enum TauriEvent {
@ -18,4 +19,6 @@ export enum TauriEvent {
IMAGE_RECEIVED = 'image-received',
GENERATION_ERROR = 'generation-error',
GENERATION_COMPLETE = 'generation-complete',
FILE_DELETED_SUCCESSFULLY = 'file-deleted-successfully',
FILE_DELETION_ERROR = 'file-deletion-error',
}

View File

@ -31,114 +31,126 @@ export function useTauriListeners({
prompt
}: TauriListenersProps) {
useEffect(() => {
const initializeApp = async () => {
let unlistenConfig: (() => void) | undefined;
let unlistenImage: (() => void) | undefined;
let unlistenError: (() => void) | undefined;
let unlistenComplete: (() => void) | undefined;
let unlistenDeleted: (() => void) | undefined;
let unlistenDeleteError: (() => void) | undefined;
const setupListeners = async () => {
await tauriApi.ensureTauriApi();
if (tauriApi.isTauri()) {
addDebugMessage('info', 'IPC system initialized successfully');
}
const setupTauriEventListeners = async () => {
if (tauriApi.isTauri()) {
try {
await tauriApi.listen(TauriEvent.CONFIG_RECEIVED, (event: any) => {
const data = event.payload;
if (data.prompt) setPrompt(data.prompt);
if (data.dst) setDst(data.dst);
if (data.apiKey) setApiKey(data.apiKey);
setIpcInitialized(true);
addDebugMessage('info', '📨 Config received from images.ts', {
hasPrompt: !!data.prompt,
hasDst: !!data.dst,
hasApiKey: !!data.apiKey,
fileCount: data.files?.length || 0,
});
});
const listeners = await Promise.all([
tauriApi.listen(TauriEvent.CONFIG_RECEIVED, (event: any) => {
const data = event.payload;
if (data.prompt) setPrompt(data.prompt);
if (data.dst) setDst(data.dst);
if (data.apiKey) setApiKey(data.apiKey);
setIpcInitialized(true);
addDebugMessage('info', '📨 Config received from images.ts', {
hasPrompt: !!data.prompt,
hasDst: !!data.dst,
hasApiKey: !!data.apiKey,
fileCount: data.files?.length || 0,
});
}),
tauriApi.listen(TauriEvent.IMAGE_RECEIVED, (event: any) => {
const imageData = event.payload;
addDebugMessage('debug', '🖼️ Processing image data', {
filename: imageData.filename,
mimeType: imageData.mimeType,
base64Length: imageData.base64?.length,
hasValidData: !!(imageData.base64 && imageData.mimeType && imageData.filename),
});
await tauriApi.listen(TauriEvent.IMAGE_RECEIVED, (event: any) => {
const imageData = event.payload;
addDebugMessage('debug', '🖼️ Processing image data', {
filename: imageData.filename,
mimeType: imageData.mimeType,
base64Length: imageData.base64?.length,
hasValidData: !!(imageData.base64 && imageData.mimeType && imageData.filename),
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_');
if (isGeneratedImage) {
const generatedImageFile: ImageFile = { path: `generated_${imageData.filename}`, src };
setFiles(prev => {
const withoutPlaceholder = prev.filter(file => !file.path.startsWith('generating_') && !file.path.endsWith(imageData.filename) && file.path !== `generated_${imageData.filename}`);
return [...withoutPlaceholder, generatedImageFile];
});
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_');
if (isGeneratedImage) {
const generatedImageFile: ImageFile = { path: `generated_${imageData.filename}`, src };
setFiles(prev => {
const withoutPlaceholder = prev.filter(file => !file.path.startsWith('generating_') && !file.path.endsWith(imageData.filename) && file.path !== `generated_${imageData.filename}`);
return [...withoutPlaceholder, generatedImageFile];
});
if (generationTimeoutId) {
clearTimeout(generationTimeoutId);
setGenerationTimeoutId(null);
}
setIsGenerating(false);
addDebugMessage('info', '✅ Generated image added to files', { filename: imageData.filename, prompt });
} else {
const newImageFile = { path: imageData.filename, src };
setFiles(prevFiles => {
const exists = prevFiles.some(f => f.path === imageData.filename);
if (!exists) {
addDebugMessage('info', `📁 Adding input image: ${imageData.filename}`);
return [...prevFiles, newImageFile];
}
addDebugMessage('warn', `🔄 Image already exists: ${imageData.filename}`);
return prevFiles;
});
}
} else {
addDebugMessage('error', '❌ Invalid image data received', {
hasBase64: !!imageData.base64,
hasMimeType: !!imageData.mimeType,
hasFilename: !!imageData.filename,
});
if (generationTimeoutId) {
clearTimeout(generationTimeoutId);
setGenerationTimeoutId(null);
}
});
await tauriApi.listen(TauriEvent.GENERATION_ERROR, (event: any) => {
const errorData = event.payload;
addDebugMessage('error', '❌ Generation failed', errorData);
setIsGenerating(false);
setFiles(prev => prev.filter(file => !file.path.startsWith('generating_')));
});
await tauriApi.listen(TauriEvent.GENERATION_COMPLETE, (event: any) => {
const completionData = event.payload;
addDebugMessage('info', '✅ Simple mode: Image generation completed', {
dst: completionData.dst,
prompt: completionData.prompt
});
setIsGenerating(false);
setFiles(prev => prev.filter(file => !file.path.startsWith('generating_')));
});
addDebugMessage('info', 'Tauri event listeners set up');
try {
await tauriApi.requestConfigFromImages();
addDebugMessage('info', 'Config request sent to images.ts');
} catch (e) {
addDebugMessage('error', `Failed to request config: ${e}`);
addDebugMessage('info', '✅ Generated image added to files', { filename: imageData.filename, prompt });
} else {
const newImageFile = { path: imageData.filename, src };
setFiles(prevFiles => {
const exists = prevFiles.some(f => f.path === imageData.filename);
if (!exists) {
addDebugMessage('info', `📁 Adding input image: ${imageData.filename}`);
return [...prevFiles, newImageFile];
}
addDebugMessage('warn', `🔄 Image already exists: ${imageData.filename}`);
return prevFiles;
});
}
} catch (error) {
addDebugMessage('error', `Failed to set up event listeners: ${error}`);
} else {
addDebugMessage('error', '❌ Invalid image data received', {
hasBase64: !!imageData.base64,
hasMimeType: !!imageData.mimeType,
hasFilename: !!imageData.filename,
});
}
} else {
addDebugMessage('warn', 'Tauri event listeners not available - running in browser mode');
}
};
}),
tauriApi.listen(TauriEvent.GENERATION_ERROR, (event: any) => {
const errorData = event.payload;
addDebugMessage('error', '❌ Generation failed', errorData);
setIsGenerating(false);
setFiles(prev => prev.filter(file => !file.path.startsWith('generating_')));
}),
tauriApi.listen(TauriEvent.GENERATION_COMPLETE, (event: any) => {
const completionData = event.payload;
addDebugMessage('info', '✅ Simple mode: Image generation completed', {
dst: completionData.dst,
prompt: completionData.prompt
});
setIsGenerating(false);
setFiles(prev => prev.filter(file => !file.path.startsWith('generating_')));
}),
tauriApi.listen(TauriEvent.FILE_DELETED_SUCCESSFULLY, (event: any) => {
const deletedPath = event.payload.path;
addDebugMessage('info', `✅ File deleted successfully: ${deletedPath}`);
setFiles(prevFiles => prevFiles.filter(file => file.path !== deletedPath));
}),
tauriApi.listen(TauriEvent.FILE_DELETION_ERROR, (event: any) => {
const { path, error } = event.payload;
addDebugMessage('error', `Failed to delete file: ${path}`, { error });
})
]);
setTimeout(setupTauriEventListeners, 500);
[unlistenConfig, unlistenImage, unlistenError, unlistenComplete, unlistenDeleted, unlistenDeleteError] = listeners;
try {
await tauriApi.requestConfigFromImages();
addDebugMessage('info', 'Config request sent to images.ts');
} catch (e) {
addDebugMessage('error', `Failed to request config: ${e}`);
}
};
setTimeout(initializeApp, 200);
setupListeners();
return () => {
unlistenConfig?.();
unlistenImage?.();
unlistenError?.();
unlistenComplete?.();
unlistenDeleted?.();
unlistenDeleteError?.();
};
}, []); // Empty dependency array to run only once on mount
}

View File

@ -141,4 +141,6 @@ export const tauriApi = {
sendIPCMessage: (messageType: string, data: any) =>
safeInvoke(TauriCommand.SEND_IPC_MESSAGE, { messageType, data }),
requestFileDeletion: (data: { path: string }) =>
safeInvoke(TauriCommand.REQUEST_FILE_DELETION, data),
};

View File

@ -16,8 +16,8 @@
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],

View File

@ -2,7 +2,11 @@ import { z } from 'zod';
import * as path from 'node:path';
import { sync as write } from '@polymech/fs/write';
import { sync as exists } from '@polymech/fs/exists';
import { readFileSync } from 'node:fs';
import {
readFileSync,
statSync,
unlinkSync
} from 'node:fs';
import { variables } from '../variables.js';
import { resolve } from '@polymech/commons';
@ -162,7 +166,7 @@ async function launchGuiAndGetPrompt(argv: any): Promise<string | null> {
cmd: 'forward_image_to_frontend',
base64,
mimeType,
filename
filename: imagePath
};
tauriProcess.stdin?.write(JSON.stringify(imageResponse) + '\n');
@ -172,6 +176,40 @@ async function launchGuiAndGetPrompt(argv: any): Promise<string | null> {
logger.error(`Failed to send image: ${imagePath}`, error.message);
}
}
} else if (message.type === 'delete_request') {
logger.info('📨 Received delete request from GUI');
const pathToDelete = message.path;
if (pathToDelete && isString(pathToDelete)) {
try {
if (exists(pathToDelete)) {
unlinkSync(pathToDelete);
logger.info(`✅ File deleted successfully: ${pathToDelete}`);
const successResponse = {
cmd: 'file_deleted_successfully',
path: pathToDelete
};
tauriProcess.stdin?.write(JSON.stringify(successResponse) + '\n');
} else {
logger.warn(`⚠️ File not found for deletion: ${pathToDelete}`);
const errorResponse = {
cmd: 'file_deletion_error',
path: pathToDelete,
error: 'File not found on server.'
};
tauriProcess.stdin?.write(JSON.stringify(errorResponse) + '\n');
}
} catch (error) {
logger.error(`❌ Failed to delete file: ${pathToDelete}`, error.message);
const errorResponse = {
cmd: 'file_deletion_error',
path: pathToDelete,
error: error.message
};
tauriProcess.stdin?.write(JSON.stringify(errorResponse) + '\n');
}
} else {
logger.error('Invalid delete request from GUI, path is missing.');
}
} else if (message.type === 'generate_request') {
logger.info('📨 Received generation request from GUI');
@ -179,7 +217,33 @@ async function launchGuiAndGetPrompt(argv: any): Promise<string | null> {
try {
const genPrompt = message.prompt;
const genFiles = message.files || [];
const genDst = message.dst;
// --- New logic for destination path ---
let dstDir: string;
if (argv.dst) {
const absoluteDst = path.resolve(argv.dst);
const dstStat = exists(absoluteDst) ? statSync(absoluteDst) : null;
if (dstStat && dstStat.isDirectory()) {
dstDir = absoluteDst;
} else {
dstDir = path.dirname(absoluteDst);
}
} else if (genFiles.length > 0) {
dstDir = path.dirname(genFiles[0]);
} else {
dstDir = process.cwd(); // fallback to current working dir
}
const baseFileName = genFiles.length > 0
? path.basename(genFiles[0], path.extname(genFiles[0]))
: 'generated';
const newFileName = `${baseFileName}_gen_0.png`;
const finalDstPath = path.resolve(dstDir, newFileName);
logger.info(`📝 Determined destination path for generated image: ${finalDstPath}`);
// --- End new logic ---
logger.info(`🎨 Starting image generation: "${genPrompt}"`);
@ -192,7 +256,7 @@ async function launchGuiAndGetPrompt(argv: any): Promise<string | null> {
...argv,
prompt: genPrompt,
include: genFiles,
dst: genDst
dst: finalDstPath // Use the new path
});
imageBuffer = await editImage(genPrompt, genFiles, parsedOptions);
} else {
@ -201,12 +265,15 @@ async function launchGuiAndGetPrompt(argv: any): Promise<string | null> {
const parsedOptions = ImageOptionsSchema().parse({
...argv,
prompt: genPrompt,
dst: genDst
dst: finalDstPath // Use the new path
});
imageBuffer = await createImage(genPrompt, parsedOptions);
}
if (imageBuffer) {
write(finalDstPath, imageBuffer);
logger.info(`✅ Image saved to: ${finalDstPath}`);
// Send the generated image back to the GUI (chat mode)
const base64Result = imageBuffer.toString('base64');
@ -214,11 +281,11 @@ async function launchGuiAndGetPrompt(argv: any): Promise<string | null> {
cmd: 'forward_image_to_frontend',
base64: base64Result,
mimeType: 'image/png',
filename: path.basename(genDst)
filename: finalDstPath
};
tauriProcess.stdin?.write(JSON.stringify(imageResponse) + '\n');
logger.info(`✅ Generated image sent to GUI: ${genDst}`);
logger.info(`✅ Generated image sent to GUI: ${path.basename(finalDstPath)}`);
} else {
logger.error('❌ Failed to generate image');

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB