diff --git a/packages/kbot/cat_gen_0.png b/packages/kbot/cat_gen_0.png deleted file mode 100644 index 8370f589..00000000 Binary files a/packages/kbot/cat_gen_0.png and /dev/null differ diff --git a/packages/kbot/cat_gen_1.png b/packages/kbot/cat_gen_1.png deleted file mode 100644 index ff331f83..00000000 Binary files a/packages/kbot/cat_gen_1.png and /dev/null differ diff --git a/packages/kbot/cat_gen_2.png b/packages/kbot/cat_gen_2.png deleted file mode 100644 index ffe690da..00000000 Binary files a/packages/kbot/cat_gen_2.png and /dev/null differ diff --git a/packages/kbot/dist/win-64/tauri-app.exe b/packages/kbot/dist/win-64/tauri-app.exe index 223e583d..8656a450 100644 Binary files a/packages/kbot/dist/win-64/tauri-app.exe and b/packages/kbot/dist/win-64/tauri-app.exe differ diff --git a/packages/kbot/generated_gen_0.png b/packages/kbot/generated_gen_0.png index bf351ac9..f9ef6b2f 100644 Binary files a/packages/kbot/generated_gen_0.png and b/packages/kbot/generated_gen_0.png differ diff --git a/packages/kbot/generated_gen_1.png b/packages/kbot/generated_gen_1.png index aaabd310..ed220a1b 100644 Binary files a/packages/kbot/generated_gen_1.png and b/packages/kbot/generated_gen_1.png differ diff --git a/packages/kbot/generated_gen_10.png b/packages/kbot/generated_gen_10.png index c7388101..e99dd065 100644 Binary files a/packages/kbot/generated_gen_10.png and b/packages/kbot/generated_gen_10.png differ diff --git a/packages/kbot/generated_gen_11.png b/packages/kbot/generated_gen_11.png new file mode 100644 index 00000000..f2396bdf Binary files /dev/null and b/packages/kbot/generated_gen_11.png differ diff --git a/packages/kbot/generated_gen_12.png b/packages/kbot/generated_gen_12.png new file mode 100644 index 00000000..f9fed2cc Binary files /dev/null and b/packages/kbot/generated_gen_12.png differ diff --git a/packages/kbot/generated_gen_13.png b/packages/kbot/generated_gen_13.png new file mode 100644 index 00000000..70f87dc9 Binary files /dev/null and b/packages/kbot/generated_gen_13.png differ diff --git a/packages/kbot/generated_gen_14.png b/packages/kbot/generated_gen_14.png new file mode 100644 index 00000000..fb569779 Binary files /dev/null and b/packages/kbot/generated_gen_14.png differ diff --git a/packages/kbot/generated_gen_2.png b/packages/kbot/generated_gen_2.png index 616495f1..de906f44 100644 Binary files a/packages/kbot/generated_gen_2.png and b/packages/kbot/generated_gen_2.png differ diff --git a/packages/kbot/generated_gen_3.png b/packages/kbot/generated_gen_3.png index 41002fd3..de605b39 100644 Binary files a/packages/kbot/generated_gen_3.png and b/packages/kbot/generated_gen_3.png differ diff --git a/packages/kbot/generated_gen_4.png b/packages/kbot/generated_gen_4.png index 1db554dd..ffd50402 100644 Binary files a/packages/kbot/generated_gen_4.png and b/packages/kbot/generated_gen_4.png differ diff --git a/packages/kbot/generated_gen_5.png b/packages/kbot/generated_gen_5.png index 5a57af82..991d203e 100644 Binary files a/packages/kbot/generated_gen_5.png and b/packages/kbot/generated_gen_5.png differ diff --git a/packages/kbot/generated_gen_6.png b/packages/kbot/generated_gen_6.png index 81e46a11..339123ed 100644 Binary files a/packages/kbot/generated_gen_6.png and b/packages/kbot/generated_gen_6.png differ diff --git a/packages/kbot/generated_gen_7.png b/packages/kbot/generated_gen_7.png index 65e0ba85..7bc7240d 100644 Binary files a/packages/kbot/generated_gen_7.png and b/packages/kbot/generated_gen_7.png differ diff --git a/packages/kbot/generated_gen_8.png b/packages/kbot/generated_gen_8.png index 3806f2e5..ddcc6319 100644 Binary files a/packages/kbot/generated_gen_8.png and b/packages/kbot/generated_gen_8.png differ diff --git a/packages/kbot/generated_gen_9.png b/packages/kbot/generated_gen_9.png index c61e4b8c..0b4b137d 100644 Binary files a/packages/kbot/generated_gen_9.png and b/packages/kbot/generated_gen_9.png differ diff --git a/packages/kbot/gui/tauri-app/package-lock.json b/packages/kbot/gui/tauri-app/package-lock.json index 4fc3e0db..bbbeadfd 100644 --- a/packages/kbot/gui/tauri-app/package-lock.json +++ b/packages/kbot/gui/tauri-app/package-lock.json @@ -31,6 +31,7 @@ "@tauri-apps/plugin-store": "^2.4.0", "@tauri-apps/plugin-updater": "^2.9.0", "@tauri-apps/plugin-upload": "^2.3.0", + "lucide-react": "^0.544.0", "mime-types": "^2.1.35", "react": "^19.1.0", "react-dom": "^19.1.0", @@ -2473,6 +2474,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.544.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.544.0.tgz", + "integrity": "sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.19", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", diff --git a/packages/kbot/gui/tauri-app/package.json b/packages/kbot/gui/tauri-app/package.json index 8f439cc3..0562408b 100644 --- a/packages/kbot/gui/tauri-app/package.json +++ b/packages/kbot/gui/tauri-app/package.json @@ -34,6 +34,7 @@ "@tauri-apps/plugin-store": "^2.4.0", "@tauri-apps/plugin-updater": "^2.9.0", "@tauri-apps/plugin-upload": "^2.3.0", + "lucide-react": "^0.544.0", "mime-types": "^2.1.35", "react": "^19.1.0", "react-dom": "^19.1.0", diff --git a/packages/kbot/gui/tauri-app/src-tauri/src/stdin_processor.rs b/packages/kbot/gui/tauri-app/src-tauri/src/stdin_processor.rs index 0642f496..2283b442 100644 --- a/packages/kbot/gui/tauri-app/src-tauri/src/stdin_processor.rs +++ b/packages/kbot/gui/tauri-app/src-tauri/src/stdin_processor.rs @@ -18,9 +18,6 @@ pub fn start_stdin_listener(app_handle: tauri::AppHandle) { // Parse command from images.ts if let Ok(command) = serde_json::from_str::(&line_content) { if let Some(cmd) = command.get("cmd").and_then(|v| v.as_str()) { - log_json("info", "Processing command", Some(serde_json::json!({ - "command": cmd - }))); match cmd { "forward_config_to_frontend" => { @@ -132,12 +129,6 @@ fn handle_image_forward(command: &serde_json::Value, app_handle: &tauri::AppHand command.get("base64").and_then(|v| v.as_str()), command.get("mimeType").and_then(|v| v.as_str()) ) { - log_json("info", "Forwarding image to frontend", Some(serde_json::json!({ - "filename": filename, - "mime_type": mime_type, - "base64_size": base64.len() - }))); - let image_data = serde_json::json!({ "base64": base64, "mimeType": mime_type, @@ -150,9 +141,7 @@ fn handle_image_forward(command: &serde_json::Value, app_handle: &tauri::AppHand "filename": filename }))); } else { - log_json("info", "Image emitted successfully", Some(serde_json::json!({ - "filename": filename - }))); + } } } diff --git a/packages/kbot/gui/tauri-app/src/App.tsx b/packages/kbot/gui/tauri-app/src/App.tsx index 530e2f7c..e5eb985d 100644 --- a/packages/kbot/gui/tauri-app/src/App.tsx +++ b/packages/kbot/gui/tauri-app/src/App.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from "react"; import { ImageFile, PromptTemplate } from "./types"; import { useTauriListeners } from "./hooks/useTauriListeners"; import { tauriApi } from "./lib/tauriApi"; -import { saveStore } from "./lib/init"; +import { saveToStore } from "./lib/init"; import { QUICK_STYLES, QUICK_ACTIONS } from "./constants"; import log from "./lib/log"; import Header from "./components/Header"; @@ -46,6 +46,14 @@ function App() { const [promptHistory, setPromptHistory] = useState([]); const [historyIndex, setHistoryIndex] = useState(-1); const [fileHistory, setFileHistory] = useState([]); + + // Debug wrapper for setFileHistory + const setFileHistoryWithLogging = (history: string[]) => { + log.debug(`🔧 setFileHistory called with ${history.length} files`, { + files: history.map(f => f.split(/[/\\]/).pop()) + }); + setFileHistory(history); + }; const [showFileHistory, setShowFileHistory] = useState(false); @@ -80,7 +88,7 @@ function App() { log.info(`🎨 Style replaced with: ${style}`); }; - const executeQuickAction = async (action: { name: string; prompt: string; icon: string }) => { + const executeQuickAction = async (action: { name: string; prompt: string; iconName: string }) => { const selectedImages = getSelectedImages(); if (selectedImages.length === 0) { @@ -108,7 +116,7 @@ function App() { // Auto-save to store try { - await saveStore(prompts, newHistory); + await saveToStore({ history: newHistory }); log.info('💾 History saved to store'); } catch (error) { log.error('Failed to save history', { error: (error as Error).message }); @@ -138,6 +146,74 @@ function App() { } }; + const addToFileHistory = async (filePath: string) => { + if (!filePath) return; + + // Use functional update to get current state + setFileHistory(currentHistory => { + // Remove if already exists, then add to front, then limit to 8 + const filteredHistory = currentHistory.filter(path => path !== filePath); + const newFileHistory = [filePath, ...filteredHistory].slice(0, 8); + + log.info(`📁 Adding to file history: ${filePath.split(/[/\\]/).pop()}`, { + currentHistoryLength: currentHistory.length, + newHistoryLength: newFileHistory.length, + fullPath: filePath, + maxEntries: 8 + }); + + // Auto-save to store + saveToStore({ fileHistory: newFileHistory }).then(() => { + log.info('💾 File history saved to store', { + savedCount: newFileHistory.length, + savedFiles: newFileHistory.map(f => f.split(/[/\\]/).pop()) + }); + }).catch(error => { + log.error('Failed to save file history', { error: error.message }); + }); + + return newFileHistory; + }); + }; + + const openFileFromHistory = async (filePath: string) => { + try { + if (await tauriApi.fs.readFile(filePath)) { + await addFiles([filePath]); + setShowFileHistory(false); + log.info(`📂 Reopened from history: ${filePath.split(/[/\\]/).pop()}`); + } + } catch (error) { + log.error(`File no longer exists: ${filePath.split(/[/\\]/).pop()}`); + // Remove from history if file doesn't exist + const updatedHistory = fileHistory.filter(f => f !== filePath); + setFileHistoryWithLogging(updatedHistory); + await saveToStore({ fileHistory: updatedHistory }); + } + }; + + const onFileHistoryCleanup = async (validFiles: string[]) => { + setFileHistoryWithLogging(validFiles); + await saveToStore({ fileHistory: validFiles }); + }; + + const handleLightboxPromptSubmit = async (promptText: string, imagePath: string) => { + // Set the prompt and select the image for editing + setPrompt(promptText); + + // Find and select the image + setFiles(prev => prev.map(file => ({ + ...file, + selected: file.path === imagePath + }))); + + // Add to history and generate + await addToHistory(promptText); + await generateImage(promptText, [{ path: imagePath, src: '', isGenerated: false }]); + + log.info(`🎨 Lightbox edit: "${promptText}" on ${imagePath.split(/[/\\]/).pop()}`); + }; + const importPrompts = async () => { try { const selected = await tauriApi.dialog.open({ @@ -270,6 +346,8 @@ function App() { prompt, setCurrentIndex, setPromptHistory, + setFileHistory: setFileHistoryWithLogging, + addToFileHistory, }); const addFiles = async (newPaths: string[]) => { @@ -552,7 +630,7 @@ function App() { const savePrompts = async (promptsToSave: PromptTemplate[]) => { try { - await saveStore(promptsToSave, promptHistory); + await saveToStore({ prompts: promptsToSave }); } catch (error) { log.error('Failed to save prompts', { error: (error as Error).message @@ -690,6 +768,12 @@ function App() { promptHistory={promptHistory} historyIndex={historyIndex} navigateHistory={navigateHistory} + fileHistory={fileHistory} + showFileHistory={showFileHistory} + setShowFileHistory={setShowFileHistory} + openFileFromHistory={openFileFromHistory} + onFileHistoryCleanup={onFileHistoryCleanup} + onLightboxPromptSubmit={handleLightboxPromptSubmit} /> {/* Debug Panel */} diff --git a/packages/kbot/gui/tauri-app/src/components/ImageGallery.tsx b/packages/kbot/gui/tauri-app/src/components/ImageGallery.tsx index 1a27f56d..7b49d9a2 100644 --- a/packages/kbot/gui/tauri-app/src/components/ImageGallery.tsx +++ b/packages/kbot/gui/tauri-app/src/components/ImageGallery.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useRef } from 'react'; import { ImageFile } from '../types'; import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch'; +import { ArrowUp } from 'lucide-react'; interface ImageGalleryProps { images: ImageFile[]; @@ -11,6 +12,12 @@ interface ImageGalleryProps { showSelection?: boolean; currentIndex: number; setCurrentIndex: (index: number) => void; + onDoubleClick?: (imagePath: string) => void; + onLightboxPromptSubmit?: (prompt: string, imagePath: string) => void; + promptHistory?: string[]; + historyIndex?: number; + navigateHistory?: (direction: 'up' | 'down') => void; + isGenerating?: boolean; } export default function ImageGallery({ @@ -21,13 +28,27 @@ export default function ImageGallery({ onImageSaveAs, showSelection = false, currentIndex, - setCurrentIndex + setCurrentIndex, + onDoubleClick, + onLightboxPromptSubmit, + promptHistory = [], + historyIndex = -1, + navigateHistory, + isGenerating = false }: ImageGalleryProps) { const [lightboxOpen, setLightboxOpen] = useState(false); const [lightboxLoaded, setLightboxLoaded] = useState(false); const [isPanning, setIsPanning] = useState(false); + const [lightboxPrompt, setLightboxPrompt] = useState(''); const panStartRef = useRef<{ x: number; y: number } | null>(null); + // Sync lightbox prompt with history navigation + useEffect(() => { + if (lightboxOpen && historyIndex >= 0 && historyIndex < promptHistory.length) { + setLightboxPrompt(promptHistory[historyIndex]); + } + }, [historyIndex, promptHistory, lightboxOpen]); + // Reset current index if images change, preventing out-of-bounds errors useEffect(() => { if (images.length > 0 && currentIndex >= images.length) { @@ -188,7 +209,13 @@ export default function ImageGallery({ type="button" key={image.path} onClick={(e) => handleThumbnailClick(e, image.path, index)} - onDoubleClick={() => openLightbox(index)} + onDoubleClick={() => { + if (onDoubleClick) { + onDoubleClick(image.path); + } else { + openLightbox(index); + } + }} 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' @@ -373,10 +400,105 @@ export default function ImageGallery({ )} + {/* Lightbox Prompt Field */} + {lightboxLoaded && onLightboxPromptSubmit && ( +
+
+
+
+