diff --git a/packages/kbot/dev-kbot.code-workspace b/packages/kbot/dev-kbot.code-workspace new file mode 100644 index 00000000..41855cda --- /dev/null +++ b/packages/kbot/dev-kbot.code-workspace @@ -0,0 +1,14 @@ +{ + "folders": [ + { + "path": "." + }, + { + "path": "../../../site2" + }, + { + "path": "../../../polymech-astro/packages/polymech" + } + ], + "settings": {} +} \ No newline at end of file diff --git a/packages/kbot/dist-in/commands/images.js b/packages/kbot/dist-in/commands/images.js index 5a9ed16a..edc6d0dd 100644 --- a/packages/kbot/dist-in/commands/images.js +++ b/packages/kbot/dist-in/commands/images.js @@ -3,13 +3,13 @@ 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 { variables } from '../variables.js'; +import { resolve } from '@polymech/commons'; import { isArray, isString } from '@polymech/core/primitives'; import { OptionsSchema } from '../zod_schema.js'; import { createImage, editImage } from '../lib/images-google.js'; import { getLogger } from '../index.js'; import { prompt as resolvePrompt } from '../prompt.js'; -import { variables } from '../variables.js'; -import { resolve } from '@polymech/commons'; import { spawn } from 'node:child_process'; import { loadConfig } from '../config.js'; function getGuiAppPath() { @@ -61,7 +61,7 @@ export const ImageOptionsSchema = () => { }; async function launchGuiAndGetPrompt(argv) { const logger = getLogger(argv); - return new Promise((resolve, reject) => { + return new Promise((_resolve, reject) => { const guiAppPath = getGuiAppPath(); console.log('guiAppPath', guiAppPath); if (!exists(guiAppPath)) { @@ -177,15 +177,15 @@ async function launchGuiAndGetPrompt(argv) { imageBuffer = await createImage(genPrompt, parsedOptions); } if (imageBuffer) { + // Send the generated image back to the GUI (chat mode) const base64Result = imageBuffer.toString('base64'); - // Send the generated image back to the GUI - const resultResponse = { + const imageResponse = { cmd: 'forward_image_to_frontend', base64: base64Result, mimeType: 'image/png', filename: path.basename(genDst) }; - tauriProcess.stdin?.write(JSON.stringify(resultResponse) + '\n'); + tauriProcess.stdin?.write(JSON.stringify(imageResponse) + '\n'); logger.info(`βœ… Generated image sent to GUI: ${genDst}`); } else { @@ -228,7 +228,7 @@ async function launchGuiAndGetPrompt(argv) { if (code === 0) { const trimmedOutput = output.trim(); console.log('Attempting to parse JSON:', JSON.stringify(trimmedOutput)); - resolve(trimmedOutput || null); + _resolve(trimmedOutput || null); } else { reject(new Error(`Tauri app exited with code ${code}. stderr: ${errorOutput}`)); @@ -315,4 +315,4 @@ export const imageCommand = async (argv) => { logger.error('Failed to parse options or generate image:', error.message, error.issues, error.stack); } }; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1hZ2VzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvbW1hbmRzL2ltYWdlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsQ0FBQyxFQUFFLE1BQU0sS0FBSyxDQUFDO0FBQ3hCLE9BQU8sS0FBSyxJQUFJLE1BQU0sV0FBVyxDQUFDO0FBQ2xDLE9BQU8sRUFBRSxJQUFJLElBQUksS0FBSyxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDbkQsT0FBTyxFQUFFLElBQUksSUFBSSxNQUFNLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUNyRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBRXZDLE9BQU8sRUFBRSxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFFOUQsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQ2pELE9BQU8sRUFBRSxXQUFXLEVBQUUsU0FBUyxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDakUsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUN4QyxPQUFPLEVBQUUsTUFBTSxJQUFJLGFBQWEsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUN2RCxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDNUMsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBQzVDLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUMzQyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBRTFDLFNBQVMsYUFBYTtJQUVsQixzRUFBc0U7SUFDdEUsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEdBQUcsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ2xFLG9GQUFvRjtJQUNw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 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1hZ2VzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvbW1hbmRzL2ltYWdlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsQ0FBQyxFQUFFLE1BQU0sS0FBSyxDQUFDO0FBQ3hCLE9BQU8sS0FBSyxJQUFJLE1BQU0sV0FBVyxDQUFDO0FBQ2xDLE9BQU8sRUFBRSxJQUFJLElBQUksS0FBSyxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDbkQsT0FBTyxFQUFFLElBQUksSUFBSSxNQUFNLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUNyRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBQ3ZDLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUM1QyxPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFFNUMsT0FBTyxFQUFFLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUU5RCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFDakQsT0FBTyxFQUFFLFdBQVcsRUFBRSxTQUFTLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUNqRSxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxNQUFNLElBQUksYUFBYSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ3ZELE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUMzQyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBRTFDLFNBQVMsYUFBYTtJQUVsQixzRUFBc0U7SUFDdEUsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEdBQUcsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ2xFLG9GQUFvRjtJQUNw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 \ No newline at end of file diff --git a/packages/kbot/dist/win-64/tauri-app.exe b/packages/kbot/dist/win-64/tauri-app.exe index 8fc4e2b1..8473c4eb 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/docs/images-gui.md b/packages/kbot/docs/images-gui.md new file mode 100644 index 00000000..2170cf77 --- /dev/null +++ b/packages/kbot/docs/images-gui.md @@ -0,0 +1,89 @@ +# Image Command GUI (`--gui`) + +## Overview + +The `images` command includes a powerful Graphical User Interface (GUI) for interactive image generation and editing. By adding the `--gui` flag to your command, you launch a desktop application that provides a rich, user-friendly environment for working with images. + +This mode is designed for an iterative workflow, allowing you to refine prompts, swap source images, and see results in real-time, which we refer to as "Chat Mode". + +To launch the GUI, simply add the `--gui` flag: +```bash +kbot image --dst "my_artwork.png" --gui +``` + +## Features + +- **Interactive Prompting**: A large text area to write and refine your image descriptions. +- **Source Image Management**: Easily add, view, and remove source images for editing tasks. +- **Image Gallery**: A comprehensive gallery that displays both your source images and all generated images from the current session. You can select any image to view it larger or use it as a source for the next generation. +- **Interactive Chat Mode**: Continuously generate images without restarting the command. Each generated image is added to the gallery, allowing you to build upon your ideas. +- **Output Configuration**: Specify the destination file path for the final image. +- **Debug Panel**: An advanced panel to inspect the Inter-Process Communication (IPC) messages between the GUI and the command-line tool. + +## Workflow: Interactive Chat Mode + +The GUI operates in a persistent "Chat Mode", which facilitates an iterative creation process. Here’s a typical workflow: + +1. **Launch**: Start the GUI with initial parameters. For example, to start with a specific prompt and some source images: + ```bash + kbot image --prompt "a cat wearing a wizard hat" --include "cat.jpg" --dst "wizard_cat.png" --gui + ``` +2. **Generate**: The GUI will open, pre-filled with your prompt and images. Click the **✨ Generate Image** button. +3. **Review**: The newly generated image appears in the image gallery. You can click on its thumbnail to view it in the main display. +4. **Iterate**: Now, you can: + * Modify the prompt (e.g., "a cat wearing a blue wizard hat with stars"). + * Select the newly generated image from the gallery to use it as a source for the next edit. + * Add or remove other source images. +5. **Re-generate**: Click **✨ Generate Image** again. A new image will be generated and added to the gallery. +6. **Repeat**: Continue this cycle of refining and generating until you are satisfied with the result. + +This loop of generating, reviewing, and refining is the core of the Chat Mode experience. + +```mermaid +sequenceDiagram + participant User + participant GUI as React Frontend + participant CLI as images.ts + + User->>GUI: Modifies prompt / selects images + User->>GUI: Clicks "Generate Image" + GUI->>CLI: Sends 'generate_request' (prompt, files, dst) + CLI->>CLI: Calls Image Generation API + CLI-->>GUI: Sends generated image back ('image-received' event) + GUI->>GUI: Adds new image to gallery + GUI->>User: Displays new image + User->>GUI: Continues iteration... +``` + +## Parameters & Configuration + +The GUI can be pre-configured using arguments from the command line. + +- `--prompt `: Sets the initial text in the prompt box. +- `--include `: Populates the image gallery with one or more source images. +- `--dst `: Sets the initial value for the output file path. +- `--api_key `: Provides the necessary API key for image generation. If not provided, it will be loaded from your config file. + +When the GUI starts, it sends a request to the `images.ts` process, which then provides this initial configuration data. + +## Finalizing and Saving + +Once you have a generated image you're happy with, you have two options: + +1. **Simple Mode (Generation only)**: If you are only generating images and don't need to return a specific file to the calling process, you can save images directly from the gallery and close the GUI when you're done. *Note: Direct saving from the GUI is not fully implemented yet.* + +2. **Submitting a Final Result**: To complete the `images` command and save the final output, click the **πŸ’Ύ Save Last Generated Image and Close** button. This action: + * Identifies the most recently generated image. + * Sends a final payload containing the prompt, source files, and destination path back to the `images.ts` process. + * Closes the GUI application. + +The `images.ts` command then saves the final image to the specified `--dst` path and exits cleanly. + +## Communication Protocol (IPC) + +The GUI and the `images.ts` CLI process communicate using an Inter-Process Communication (IPC) system that sends JSON messages over the standard input/output streams (`stdin`/`stdout`). + +- **CLI β†’ GUI**: The CLI sends initial configuration, source images, and newly generated images to the GUI. +- **GUI β†’ CLI**: The GUI sends requests to generate images and, finally, sends the chosen prompt and settings when the user clicks "Save and Close". + +For a detailed breakdown of the IPC message formats and communication flow, please see the [IPC Communication Documentation](./ipc.md). diff --git a/packages/kbot/fat_cat.png b/packages/kbot/fat_cat.png new file mode 100644 index 00000000..ab07da1b Binary files /dev/null and b/packages/kbot/fat_cat.png differ diff --git a/packages/kbot/gui/tauri-app/src-tauri/src/lib.rs b/packages/kbot/gui/tauri-app/src-tauri/src/lib.rs index 6fe8d16c..09595efa 100644 --- a/packages/kbot/gui/tauri-app/src-tauri/src/lib.rs +++ b/packages/kbot/gui/tauri-app/src-tauri/src/lib.rs @@ -1,418 +1,426 @@ -use tauri::{Manager, Emitter}; -use serde::{Serialize, Deserialize}; -use dirs; - -struct Counter(std::sync::Mutex); -struct DebugMessages(std::sync::Mutex>); - -#[derive(Serialize, Deserialize)] -struct Payload { - prompt: String, - files: Vec, - dst: String, -} - -#[derive(Serialize, Deserialize)] -struct IPCMessage { - #[serde(rename = "type")] - message_type: String, - data: serde_json::Value, - timestamp: Option, - id: Option, -} - -#[derive(Serialize, Deserialize)] -struct CounterPayload { - count: u32, - message: Option, -} - -#[derive(Serialize, Deserialize, Clone)] -struct DebugPayload { - level: String, - message: String, - data: Option, -} - -#[derive(Serialize, Deserialize)] -struct ImagePayload { - base64: String, - #[serde(rename = "mimeType")] - mime_type: String, - filename: Option, -} - -// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ -#[tauri::command] -fn submit_prompt(prompt: &str, files: Vec, dst: &str, window: tauri::Window) { - // Use eprintln! for debug logs so they go to stderr, not stdout - eprintln!("[RUST LOG]: submit_prompt command called."); - eprintln!("[RUST LOG]: - Prompt: {}", prompt); - eprintln!("[RUST LOG]: - Files: {:?}", files); - eprintln!("[RUST LOG]: - Dst: {}", dst); - - let payload = Payload { - prompt: prompt.to_string(), - files, - dst: dst.to_string(), - }; - let json_payload = serde_json::to_string(&payload).unwrap(); - - eprintln!("[RUST LOG]: - Sending JSON payload to stdout: {}", json_payload); - println!("{}", json_payload); // The actual payload - ONLY this should go to stdout - let _ = window.app_handle().exit(0); -} - -#[tauri::command] -fn log_error_to_console(error: &str) { - eprintln!("[WebView ERROR forwarded from JS]: {}", error); -} - -#[tauri::command] -fn resolve_path_relative_to_home(absolute_path: String) -> Result { - eprintln!("[RUST LOG]: resolve_path_relative_to_home command called."); - eprintln!("[RUST LOG]: - Received absolute path: {}", absolute_path); - - let home_dir = dirs::home_dir().ok_or_else(|| "Could not find home directory".to_string())?; - - let path_to_resolve = std::path::Path::new(&absolute_path); - - let relative_path = pathdiff::diff_paths(path_to_resolve, home_dir) - .ok_or_else(|| "Failed to calculate relative path from home directory".to_string())?; - - let result = relative_path.to_string_lossy().to_string(); - eprintln!("[RUST LOG]: - Resolved to path relative to home: {}", result); - Ok(result) -} - -#[tauri::command] -fn increment_counter(state: tauri::State<'_, Counter>) -> Result { - eprintln!("[RUST LOG]: increment_counter command called."); - let mut counter = state.0.lock().unwrap(); - *counter += 1; - let current_value = *counter; - eprintln!("[RUST LOG]: - Counter incremented to: {}", current_value); - Ok(current_value) -} - -#[tauri::command] -fn get_counter(state: tauri::State<'_, Counter>) -> Result { - eprintln!("[RUST LOG]: get_counter command called."); - let counter = state.0.lock().unwrap(); - let current_value = *counter; - eprintln!("[RUST LOG]: - Current counter value: {}", current_value); - Ok(current_value) -} - -#[tauri::command] -fn reset_counter(state: tauri::State<'_, Counter>) -> Result { - eprintln!("[RUST LOG]: reset_counter command called."); - let mut counter = state.0.lock().unwrap(); - *counter = 0; - eprintln!("[RUST LOG]: - Counter reset to: 0"); - Ok(0) -} - -#[tauri::command] -fn add_debug_message(message: String, level: String, data: Option, state: tauri::State<'_, DebugMessages>) -> Result<(), String> { - eprintln!("[RUST LOG]: add_debug_message command called."); - eprintln!("[RUST LOG]: - Level: {}", level); - eprintln!("[RUST LOG]: - Message: {}", message); - - let debug_payload = DebugPayload { - level, - message, - data, - }; - - let mut messages = state.0.lock().unwrap(); - messages.push(debug_payload); - - // Keep only the last 100 messages to prevent memory issues - if messages.len() > 100 { - let len = messages.len(); - messages.drain(0..len - 100); - } - - eprintln!("[RUST LOG]: - Debug message added. Total messages: {}", messages.len()); - Ok(()) -} - -#[tauri::command] -fn get_debug_messages(state: tauri::State<'_, DebugMessages>) -> Result, String> { - eprintln!("[RUST LOG]: get_debug_messages command called."); - let messages = state.0.lock().unwrap(); - let result = messages.clone(); - eprintln!("[RUST LOG]: - Returning {} debug messages", result.len()); - Ok(result) -} - -#[tauri::command] -fn clear_debug_messages(state: tauri::State<'_, DebugMessages>) -> Result<(), String> { - eprintln!("[RUST LOG]: clear_debug_messages command called."); - let mut messages = state.0.lock().unwrap(); - messages.clear(); - eprintln!("[RUST LOG]: - Debug messages cleared"); - Ok(()) -} - -#[tauri::command] -fn send_ipc_message(message_type: String, data: serde_json::Value, _window: tauri::Window) -> Result<(), String> { - eprintln!("[RUST LOG]: send_ipc_message command called."); - eprintln!("[RUST LOG]: - Type: {}", message_type); - eprintln!("[RUST LOG]: - Data: {}", data); - - let ipc_message = IPCMessage { - message_type, - data, - timestamp: Some(std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_millis() as u64), - id: Some(format!("msg_{}_{}", - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_millis(), - rand::random::())), - }; - - let json_message = serde_json::to_string(&ipc_message).unwrap(); - eprintln!("[RUST LOG]: - Sending IPC message to stdout: {}", json_message); - println!("{}", json_message); - - Ok(()) -} - -#[tauri::command] -fn send_message_to_stdout(message: String) -> Result<(), String> { - eprintln!("[RUST LOG]: send_message_to_stdout command called."); - eprintln!("[RUST LOG]: - Message: {}", message); - - // Send directly to stdout (this will be captured by images.ts) - println!("{}", message); - - Ok(()) -} - -#[tauri::command] -fn generate_image_via_backend(prompt: String, files: Vec, dst: String) -> Result<(), String> { - eprintln!("[RUST LOG]: generate_image_via_backend called"); - eprintln!("[RUST LOG]: - Prompt: {}", prompt); - eprintln!("[RUST LOG]: - Files: {:?}", files); - eprintln!("[RUST LOG]: - Dst: {}", dst); - - // Send generation request to images.ts via stdout - let request = serde_json::json!({ - "type": "generate_request", - "prompt": prompt, - "files": files, - "dst": dst, - "timestamp": std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_millis() - }); - - println!("{}", serde_json::to_string(&request).unwrap()); - eprintln!("[RUST LOG]: Generation request sent to images.ts"); - - Ok(()) -} - -#[tauri::command] -fn request_config_from_images(_app: tauri::AppHandle) -> Result<(), String> { - eprintln!("[RUST LOG]: request_config_from_images called"); - - // Send request to images.ts via stdout - let request = serde_json::json!({ - "type": "config_request", - "timestamp": std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_millis() - }); - - println!("{}", serde_json::to_string(&request).unwrap()); - eprintln!("[RUST LOG]: Config request sent to images.ts"); - - Ok(()) -} - -#[tauri::command] -fn forward_config_to_frontend(prompt: Option, dst: Option, api_key: Option, files: Vec, app: tauri::AppHandle) -> Result<(), String> { - eprintln!("[RUST LOG]: forward_config_to_frontend called"); - - let config_data = serde_json::json!({ - "prompt": prompt, - "dst": dst, - "apiKey": api_key, - "files": files - }); - - if let Err(e) = app.emit("config-received", &config_data) { - eprintln!("[RUST LOG]: Failed to emit config-received: {}", e); - return Err(format!("Failed to emit config: {}", e)); - } - - eprintln!("[RUST LOG]: Config forwarded to frontend successfully"); - Ok(()) -} - -#[tauri::command] -fn forward_image_to_frontend(base64: String, mime_type: String, filename: String, app: tauri::AppHandle) -> Result<(), String> { - eprintln!("[RUST LOG]: forward_image_to_frontend called for {}", filename); - - let image_data = serde_json::json!({ - "base64": base64, - "mimeType": mime_type, - "filename": filename - }); - - if let Err(e) = app.emit("image-received", &image_data) { - eprintln!("[RUST LOG]: Failed to emit image-received: {}", e); - return Err(format!("Failed to emit image: {}", e)); - } - - eprintln!("[RUST LOG]: Image forwarded to frontend successfully"); - Ok(()) -} - -#[cfg_attr(mobile, tauri::mobile_entry_point)] -pub fn run() { - let app = tauri::Builder::default() - .manage(Counter(std::sync::Mutex::new(0))) - .manage(DebugMessages(std::sync::Mutex::new(Vec::new()))) - .plugin(tauri_plugin_opener::init()) - .plugin(tauri_plugin_dialog::init()) - .plugin(tauri_plugin_fs::init()) - .plugin(tauri_plugin_http::init()) - .invoke_handler(tauri::generate_handler![ - submit_prompt, - log_error_to_console, - resolve_path_relative_to_home, - increment_counter, - get_counter, - reset_counter, - add_debug_message, - get_debug_messages, - clear_debug_messages, - send_message_to_stdout, - send_ipc_message, - request_config_from_images, - forward_config_to_frontend, - forward_image_to_frontend, - generate_image_via_backend - ]) - .setup(|app| { - let app_handle = app.handle().clone(); - - // Listen for stdin commands from images.ts - std::thread::spawn(move || { - use std::io::{self, BufRead, BufReader}; - let stdin = io::stdin(); - let reader = BufReader::new(stdin); - - eprintln!("[RUST LOG]: Stdin listener thread started"); - - for line in reader.lines() { - if let Ok(line_content) = line { - if line_content.trim().is_empty() { - continue; - } - - // Log stdin command but hide binary data - let log_content = if line_content.contains("\"base64\"") { - format!("[COMMAND WITH BASE64 DATA - {} chars]", line_content.len()) - } else { - line_content.clone() - }; - eprintln!("[RUST LOG]: Received stdin command: {}", log_content); - - // 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()) { - eprintln!("[RUST LOG]: Processing command: {}", cmd); - - match cmd { - "forward_config_to_frontend" => { - eprintln!("[RUST LOG]: Forwarding config to frontend"); - eprintln!("[RUST LOG]: - prompt: {:?}", command.get("prompt")); - eprintln!("[RUST LOG]: - dst: {:?}", command.get("dst")); - eprintln!("[RUST LOG]: - apiKey: {:?}", command.get("apiKey").map(|_| "[REDACTED]")); - eprintln!("[RUST LOG]: - files: {:?}", command.get("files")); - - let config_data = serde_json::json!({ - "prompt": command.get("prompt"), - "dst": command.get("dst"), - "apiKey": command.get("apiKey"), - "files": command.get("files") - }); - - if let Err(e) = app_handle.emit("config-received", &config_data) { - eprintln!("[RUST LOG]: Failed to emit config-received: {}", e); - } else { - eprintln!("[RUST LOG]: Config emitted successfully to frontend"); - } - } - "forward_image_to_frontend" => { - if let (Some(filename), Some(base64), Some(mime_type)) = ( - command.get("filename").and_then(|v| v.as_str()), - command.get("base64").and_then(|v| v.as_str()), - command.get("mimeType").and_then(|v| v.as_str()) - ) { - eprintln!("[RUST LOG]: Forwarding image to frontend: {}", filename); - let image_data = serde_json::json!({ - "base64": base64, - "mimeType": mime_type, - "filename": filename - }); - - if let Err(e) = app_handle.emit("image-received", &image_data) { - eprintln!("[RUST LOG]: Failed to emit image-received: {}", e); - } else { - eprintln!("[RUST LOG]: Image emitted successfully: {}", filename); - } - } - } - "generation_result" => { - eprintln!("[RUST LOG]: Forwarding generation result to frontend"); - if let Err(e) = app_handle.emit("generation-result", &command) { - eprintln!("[RUST LOG]: Failed to emit generation-result: {}", e); - } else { - eprintln!("[RUST LOG]: Generation result emitted successfully"); - } - } - "generation_error" => { - eprintln!("[RUST LOG]: Forwarding generation error to frontend"); - if let Err(e) = app_handle.emit("generation-error", &command) { - eprintln!("[RUST LOG]: Failed to emit generation-error: {}", e); - } else { - eprintln!("[RUST LOG]: Generation error emitted successfully"); - } - } - _ => { - eprintln!("[RUST LOG]: Unknown command: {}", cmd); - } - } - } - } else { - eprintln!("[RUST LOG]: Failed to parse command as JSON"); - } - } - } - eprintln!("[RUST LOG]: Stdin listener thread ended"); - }); - - Ok(()) - }) - .build(tauri::generate_context!()) - .expect("error while building tauri application"); - - app.run(|_app_handle, event| match event { - tauri::RunEvent::ExitRequested { api, .. } => { - api.prevent_exit(); - } - _ => {} - }); -} +use tauri::{Manager, Emitter}; +use serde::{Serialize, Deserialize}; +use dirs; + +struct Counter(std::sync::Mutex); +struct DebugMessages(std::sync::Mutex>); + +#[derive(Serialize, Deserialize)] +struct Payload { + prompt: String, + files: Vec, + dst: String, +} + +#[derive(Serialize, Deserialize)] +struct IPCMessage { + #[serde(rename = "type")] + message_type: String, + data: serde_json::Value, + timestamp: Option, + id: Option, +} + +#[derive(Serialize, Deserialize)] +struct CounterPayload { + count: u32, + message: Option, +} + +#[derive(Serialize, Deserialize, Clone)] +struct DebugPayload { + level: String, + message: String, + data: Option, +} + +#[derive(Serialize, Deserialize)] +struct ImagePayload { + base64: String, + #[serde(rename = "mimeType")] + mime_type: String, + filename: Option, +} + +// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ +#[tauri::command] +fn submit_prompt(prompt: &str, files: Vec, dst: &str, window: tauri::Window) { + // Use eprintln! for debug logs so they go to stderr, not stdout + eprintln!("[RUST LOG]: submit_prompt command called."); + eprintln!("[RUST LOG]: - Prompt: {}", prompt); + eprintln!("[RUST LOG]: - Files: {:?}", files); + eprintln!("[RUST LOG]: - Dst: {}", dst); + + let payload = Payload { + prompt: prompt.to_string(), + files, + dst: dst.to_string(), + }; + let json_payload = serde_json::to_string(&payload).unwrap(); + + eprintln!("[RUST LOG]: - Sending JSON payload to stdout: {}", json_payload); + println!("{}", json_payload); // The actual payload - ONLY this should go to stdout + let _ = window.app_handle().exit(0); +} + +#[tauri::command] +fn log_error_to_console(error: &str) { + eprintln!("[WebView ERROR forwarded from JS]: {}", error); +} + +#[tauri::command] +fn resolve_path_relative_to_home(absolute_path: String) -> Result { + eprintln!("[RUST LOG]: resolve_path_relative_to_home command called."); + eprintln!("[RUST LOG]: - Received absolute path: {}", absolute_path); + + let home_dir = dirs::home_dir().ok_or_else(|| "Could not find home directory".to_string())?; + + let path_to_resolve = std::path::Path::new(&absolute_path); + + let relative_path = pathdiff::diff_paths(path_to_resolve, home_dir) + .ok_or_else(|| "Failed to calculate relative path from home directory".to_string())?; + + let result = relative_path.to_string_lossy().to_string(); + eprintln!("[RUST LOG]: - Resolved to path relative to home: {}", result); + Ok(result) +} + +#[tauri::command] +fn increment_counter(state: tauri::State<'_, Counter>) -> Result { + eprintln!("[RUST LOG]: increment_counter command called."); + let mut counter = state.0.lock().unwrap(); + *counter += 1; + let current_value = *counter; + eprintln!("[RUST LOG]: - Counter incremented to: {}", current_value); + Ok(current_value) +} + +#[tauri::command] +fn get_counter(state: tauri::State<'_, Counter>) -> Result { + eprintln!("[RUST LOG]: get_counter command called."); + let counter = state.0.lock().unwrap(); + let current_value = *counter; + eprintln!("[RUST LOG]: - Current counter value: {}", current_value); + Ok(current_value) +} + +#[tauri::command] +fn reset_counter(state: tauri::State<'_, Counter>) -> Result { + eprintln!("[RUST LOG]: reset_counter command called."); + let mut counter = state.0.lock().unwrap(); + *counter = 0; + eprintln!("[RUST LOG]: - Counter reset to: 0"); + Ok(0) +} + +#[tauri::command] +fn add_debug_message(message: String, level: String, data: Option, state: tauri::State<'_, DebugMessages>) -> Result<(), String> { + eprintln!("[RUST LOG]: add_debug_message command called."); + eprintln!("[RUST LOG]: - Level: {}", level); + eprintln!("[RUST LOG]: - Message: {}", message); + + let debug_payload = DebugPayload { + level, + message, + data, + }; + + let mut messages = state.0.lock().unwrap(); + messages.push(debug_payload); + + // Keep only the last 100 messages to prevent memory issues + if messages.len() > 100 { + let len = messages.len(); + messages.drain(0..len - 100); + } + + eprintln!("[RUST LOG]: - Debug message added. Total messages: {}", messages.len()); + Ok(()) +} + +#[tauri::command] +fn get_debug_messages(state: tauri::State<'_, DebugMessages>) -> Result, String> { + eprintln!("[RUST LOG]: get_debug_messages command called."); + let messages = state.0.lock().unwrap(); + let result = messages.clone(); + eprintln!("[RUST LOG]: - Returning {} debug messages", result.len()); + Ok(result) +} + +#[tauri::command] +fn clear_debug_messages(state: tauri::State<'_, DebugMessages>) -> Result<(), String> { + eprintln!("[RUST LOG]: clear_debug_messages command called."); + let mut messages = state.0.lock().unwrap(); + messages.clear(); + eprintln!("[RUST LOG]: - Debug messages cleared"); + Ok(()) +} + +#[tauri::command] +fn send_ipc_message(message_type: String, data: serde_json::Value, _window: tauri::Window) -> Result<(), String> { + eprintln!("[RUST LOG]: send_ipc_message command called."); + eprintln!("[RUST LOG]: - Type: {}", message_type); + eprintln!("[RUST LOG]: - Data: {}", data); + + let ipc_message = IPCMessage { + message_type, + data, + timestamp: Some(std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() as u64), + id: Some(format!("msg_{}_{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis(), + rand::random::())), + }; + + let json_message = serde_json::to_string(&ipc_message).unwrap(); + eprintln!("[RUST LOG]: - Sending IPC message to stdout: {}", json_message); + println!("{}", json_message); + + Ok(()) +} + +#[tauri::command] +fn send_message_to_stdout(message: String) -> Result<(), String> { + eprintln!("[RUST LOG]: send_message_to_stdout command called."); + eprintln!("[RUST LOG]: - Message: {}", message); + + // Send directly to stdout (this will be captured by images.ts) + println!("{}", message); + + Ok(()) +} + +#[tauri::command] +fn generate_image_via_backend(prompt: String, files: Vec, dst: String) -> Result<(), String> { + eprintln!("[RUST LOG]: generate_image_via_backend called"); + eprintln!("[RUST LOG]: - Prompt: {}", prompt); + eprintln!("[RUST LOG]: - Files: {:?}", files); + eprintln!("[RUST LOG]: - Dst: {}", dst); + + // Send generation request to images.ts via stdout + let request = serde_json::json!({ + "type": "generate_request", + "prompt": prompt, + "files": files, + "dst": dst, + "timestamp": std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() + }); + + println!("{}", serde_json::to_string(&request).unwrap()); + eprintln!("[RUST LOG]: Generation request sent to images.ts"); + + Ok(()) +} + +#[tauri::command] +fn request_config_from_images(_app: tauri::AppHandle) -> Result<(), String> { + eprintln!("[RUST LOG]: request_config_from_images called"); + + // Send request to images.ts via stdout + let request = serde_json::json!({ + "type": "config_request", + "timestamp": std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() + }); + + println!("{}", serde_json::to_string(&request).unwrap()); + eprintln!("[RUST LOG]: Config request sent to images.ts"); + + Ok(()) +} + +#[tauri::command] +fn forward_config_to_frontend(prompt: Option, dst: Option, api_key: Option, files: Vec, app: tauri::AppHandle) -> Result<(), String> { + eprintln!("[RUST LOG]: forward_config_to_frontend called"); + + let config_data = serde_json::json!({ + "prompt": prompt, + "dst": dst, + "apiKey": api_key, + "files": files + }); + + if let Err(e) = app.emit("config-received", &config_data) { + eprintln!("[RUST LOG]: Failed to emit config-received: {}", e); + return Err(format!("Failed to emit config: {}", e)); + } + + eprintln!("[RUST LOG]: Config forwarded to frontend successfully"); + Ok(()) +} + +#[tauri::command] +fn forward_image_to_frontend(base64: String, mime_type: String, filename: String, app: tauri::AppHandle) -> Result<(), String> { + eprintln!("[RUST LOG]: forward_image_to_frontend called for {}", filename); + + let image_data = serde_json::json!({ + "base64": base64, + "mimeType": mime_type, + "filename": filename + }); + + if let Err(e) = app.emit("image-received", &image_data) { + eprintln!("[RUST LOG]: Failed to emit image-received: {}", e); + return Err(format!("Failed to emit image: {}", e)); + } + + eprintln!("[RUST LOG]: Image forwarded to frontend successfully"); + Ok(()) +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + let app = tauri::Builder::default() + .manage(Counter(std::sync::Mutex::new(0))) + .manage(DebugMessages(std::sync::Mutex::new(Vec::new()))) + .plugin(tauri_plugin_opener::init()) + .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_fs::init()) + .plugin(tauri_plugin_http::init()) + .invoke_handler(tauri::generate_handler![ + submit_prompt, + log_error_to_console, + resolve_path_relative_to_home, + increment_counter, + get_counter, + reset_counter, + add_debug_message, + get_debug_messages, + clear_debug_messages, + send_message_to_stdout, + send_ipc_message, + request_config_from_images, + forward_config_to_frontend, + forward_image_to_frontend, + generate_image_via_backend + ]) + .setup(|app| { + let app_handle = app.handle().clone(); + + // Listen for stdin commands from images.ts + std::thread::spawn(move || { + use std::io::{self, BufRead, BufReader}; + let stdin = io::stdin(); + let reader = BufReader::new(stdin); + + eprintln!("[RUST LOG]: Stdin listener thread started"); + + for line in reader.lines() { + if let Ok(line_content) = line { + if line_content.trim().is_empty() { + continue; + } + + // Log stdin command but hide binary data + let log_content = if line_content.contains("\"base64\"") { + format!("[COMMAND WITH BASE64 DATA - {} chars]", line_content.len()) + } else { + line_content.clone() + }; + eprintln!("[RUST LOG]: Received stdin command: {}", log_content); + + // 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()) { + eprintln!("[RUST LOG]: Processing command: {}", cmd); + + match cmd { + "forward_config_to_frontend" => { + eprintln!("[RUST LOG]: Forwarding config to frontend"); + eprintln!("[RUST LOG]: - prompt: {:?}", command.get("prompt")); + eprintln!("[RUST LOG]: - dst: {:?}", command.get("dst")); + eprintln!("[RUST LOG]: - apiKey: {:?}", command.get("apiKey").map(|_| "[REDACTED]")); + eprintln!("[RUST LOG]: - files: {:?}", command.get("files")); + + let config_data = serde_json::json!({ + "prompt": command.get("prompt"), + "dst": command.get("dst"), + "apiKey": command.get("apiKey"), + "files": command.get("files") + }); + + if let Err(e) = app_handle.emit("config-received", &config_data) { + eprintln!("[RUST LOG]: Failed to emit config-received: {}", e); + } else { + eprintln!("[RUST LOG]: Config emitted successfully to frontend"); + } + } + "forward_image_to_frontend" => { + if let (Some(filename), Some(base64), Some(mime_type)) = ( + command.get("filename").and_then(|v| v.as_str()), + command.get("base64").and_then(|v| v.as_str()), + command.get("mimeType").and_then(|v| v.as_str()) + ) { + eprintln!("[RUST LOG]: Forwarding image to frontend: {}", filename); + let image_data = serde_json::json!({ + "base64": base64, + "mimeType": mime_type, + "filename": filename + }); + + if let Err(e) = app_handle.emit("image-received", &image_data) { + eprintln!("[RUST LOG]: Failed to emit image-received: {}", e); + } else { + eprintln!("[RUST LOG]: Image emitted successfully: {}", filename); + } + } + } + "generation_result" => { + eprintln!("[RUST LOG]: Forwarding generation result to frontend"); + if let Err(e) = app_handle.emit("generation-result", &command) { + eprintln!("[RUST LOG]: Failed to emit generation-result: {}", e); + } else { + eprintln!("[RUST LOG]: Generation result emitted successfully"); + } + } + "generation_error" => { + eprintln!("[RUST LOG]: Forwarding generation error to frontend"); + if let Err(e) = app_handle.emit("generation-error", &command) { + eprintln!("[RUST LOG]: Failed to emit generation-error: {}", e); + } else { + eprintln!("[RUST LOG]: Generation error emitted successfully"); + } + } + "generation_complete" => { + eprintln!("[RUST LOG]: Generation completed successfully"); + if let Err(e) = app_handle.emit("generation-complete", &command) { + eprintln!("[RUST LOG]: Failed to emit generation-complete: {}", e); + } else { + eprintln!("[RUST LOG]: Generation complete emitted successfully"); + } + } + _ => { + eprintln!("[RUST LOG]: Unknown command: {}", cmd); + } + } + } + } else { + eprintln!("[RUST LOG]: Failed to parse command as JSON"); + } + } + } + eprintln!("[RUST LOG]: Stdin listener thread ended"); + }); + + Ok(()) + }) + .build(tauri::generate_context!()) + .expect("error while building tauri application"); + + app.run(|_app_handle, event| match event { + tauri::RunEvent::ExitRequested { api, .. } => { + api.prevent_exit(); + } + _ => {} + }); +} diff --git a/packages/kbot/gui/tauri-app/src/App.tsx b/packages/kbot/gui/tauri-app/src/App.tsx index 46dcce19..f39e0d0d 100644 --- a/packages/kbot/gui/tauri-app/src/App.tsx +++ b/packages/kbot/gui/tauri-app/src/App.tsx @@ -1,95 +1,10 @@ import { useState, useEffect } from "react"; - -let invoke: any, open: any, save: any, readFile: any, writeFile: any, BaseDirectory: any, fetch: any, listen: any, getCurrentWindow: any; -let isTauri = false; - -// Detect Tauri environment by trying to import getCurrentWindow -import("@tauri-apps/api/window").then(module => { - getCurrentWindow = module.getCurrentWindow; - isTauri = true; - console.log('βœ… Tauri window API loaded - running in Tauri environment'); -}).catch(() => { - console.log('🌐 Tauri window API not available - running in browser mode'); - isTauri = false; -}); - -// Load other Tauri APIs -import("@tauri-apps/api/core").then(module => { - invoke = module.invoke; - console.log('βœ… Tauri core API loaded'); -}).catch(() => { - console.log('❌ Tauri core API not available'); -}); - -import("@tauri-apps/api/event").then(module => { - listen = module.listen; - console.log('βœ… Tauri event API loaded'); -}).catch(() => { - console.log('❌ Tauri event API not available'); -}); - -import("@tauri-apps/plugin-dialog").then(module => { - open = module.open; - save = module.save; - console.log('βœ… Tauri dialog plugin loaded'); -}).catch(() => { - console.log('❌ Tauri dialog plugin not available'); -}); - -import("@tauri-apps/plugin-fs").then(module => { - readFile = module.readFile; - writeFile = module.writeFile; - BaseDirectory = module.BaseDirectory; - console.log('βœ… Tauri fs plugin loaded'); -}).catch(() => { - console.log('❌ Tauri fs plugin not available'); -}); - -import("@tauri-apps/plugin-http").then(module => { - fetch = module.fetch; - console.log('βœ… Tauri http plugin loaded'); -}).catch(() => { - console.log('❌ Tauri http plugin not available'); - fetch = window.fetch; // Browser fallback -}); - -// Browser fallback -if (typeof window !== 'undefined' && !fetch) { - fetch = window.fetch; -} - -// Safe invoke function that works in both Tauri and browser environments -const safeInvoke = (command: string, args?: any) => { - if (invoke) { - return invoke(command, args); - } else { - console.log(`[Browser Mode] Would invoke: ${command}`, args); - return Promise.resolve(null); - } -}; -// Path imports commented out since they're not currently used -// import { -// homeDir, audioDir, cacheDir, configDir, dataDir, localDataDir, desktopDir, -// documentDir, downloadDir, executableDir, fontDir, pictureDir, publicDir, -// resourceDir, runtimeDir, templateDir, videoDir, appConfigDir, appDataDir, -// appLocalDataDir, appCacheDir, appLogDir, tempDir -// } from '@tauri-apps/api/path'; -// import { lookup } from 'mime-types'; - -interface ImageFile { - path: string; - src: string; -} - -interface GeneratedImage { - id: string; - src: string; - prompt: string; - timestamp: number; - saved?: boolean; - selectedForNext?: boolean; - timeoutId?: NodeJS.Timeout; -} +import { ImageFile } from "./types"; +import { useTauriListeners } from "./hooks/useTauriListeners"; +import { tauriApi } from "./lib/tauriApi"; +import Header from "./components/Header"; +import PromptForm from "./components/PromptForm"; +import DebugPanel from "./components/DebugPanel"; function arrayBufferToBase64(buffer: number[]) { let binary = ''; @@ -105,17 +20,14 @@ function App() { const [prompt, setPrompt] = useState(""); const [files, setFiles] = useState([]); const [dst, setDst] = useState(""); - const [chatMode, setChatMode] = useState(false); - const [generatedImages, setGeneratedImages] = useState([]); const [isGenerating, setIsGenerating] = useState(false); const [apiKey, setApiKey] = useState(""); const [isDarkMode, setIsDarkMode] = useState(false); - const [counter, setCounter] = useState(0); const [debugMessages, setDebugMessages] = useState([]); const [showDebugPanel, setShowDebugPanel] = useState(true); // Default open for debugging const [ipcInitialized, setIpcInitialized] = useState(false); const [messageToSend, setMessageToSend] = useState(""); - const [fullscreenImage, setFullscreenImage] = useState(null); + const [generationTimeoutId, setGenerationTimeoutId] = useState(null); const generateDefaultDst = (fileCount: number, firstFilePath?: string) => { if (fileCount === 1 && firstFilePath) { @@ -130,137 +42,116 @@ function App() { } }; - const addFiles = async (newPaths: string[]) => { - if (!isTauri) { - console.log('[Browser Mode] File operations not available'); - return; - } - - // Wait for Tauri APIs to be loaded - let attempts = 0; - while ((!readFile || !BaseDirectory) && attempts < 50) { - await new Promise(resolve => setTimeout(resolve, 100)); - attempts++; - } - - if (!readFile || !BaseDirectory) { - console.error('Tauri APIs not loaded after 5 seconds, cannot process files'); - return; - } - - const uniqueNewPaths = newPaths.filter(newPath => !files.some(f => f.path === newPath)); + const addDebugMessage = async (level: 'info' | 'warn' | 'error' | 'debug', message: string, data?: any) => { + const timestamp = new Date().toLocaleTimeString(); + const debugMsg = { level, message, data, timestamp }; + // Add to local state + setDebugMessages(prev => [...prev.slice(-99), debugMsg]); // Keep last 100 messages + + await tauriApi.addDebugMessage(message, level, data); + }; + + useTauriListeners({ + setPrompt, + setDst, + setApiKey, + setIpcInitialized, + addDebugMessage, + setFiles, + isGenerating, + generationTimeoutId, + setGenerationTimeoutId, + setIsGenerating, + prompt + }); + + const addFiles = async (newPaths: string[]) => { + const uniqueNewPaths = newPaths.filter(newPath => !files.some(f => f.path === newPath)); const newImageFiles: ImageFile[] = []; - console.log('Processing uniqueNewPaths:', uniqueNewPaths); - + for (const path of uniqueNewPaths) { try { - console.log('Processing path:', path); - - // Ask Rust to make the path relative to the HOME directory - const relativePath = await safeInvoke('resolve_path_relative_to_home', { absolutePath: path }) as string; - console.log('Relative path resolved:', relativePath); - + const relativePath = await tauriApi.resolvePathRelativeToHome(path); if (!relativePath) { console.warn(`Could not resolve relative path for: ${path}`); continue; } - - const buffer = await readFile(relativePath, { baseDir: BaseDirectory.Home }); - console.log('File read successfully, buffer size:', buffer.length); - + + const buffer = await tauriApi.fs.readFile(relativePath, { baseDir: tauriApi.fs.BaseDirectory().Home }); + const base64 = arrayBufferToBase64(Array.from(buffer)); - const mimeType = path.toLowerCase().endsWith('.png') ? 'image/png' : - path.toLowerCase().endsWith('.jpg') || path.toLowerCase().endsWith('.jpeg') ? 'image/jpeg' : - 'image/png'; // default + 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 }); - console.log('Successfully processed file:', path); } catch (e) { const errorMessage = e instanceof Error ? e.message : JSON.stringify(e); console.error(`Failed to read file: ${path}`, e); - safeInvoke('log_error_to_console', { error: `[Frontend Error] Failed to read file ${path}: ${errorMessage}` }); + tauriApi.logErrorToConsole(`[Frontend Error] Failed to read file ${path}: ${errorMessage}`); } } - - console.log('Adding files to state:', newImageFiles.length); + setFiles(prevFiles => [...prevFiles, ...newImageFiles]); }; const removeFile = (pathToRemove: string) => { setFiles(prevFiles => prevFiles.filter(file => file.path !== pathToRemove)); - console.log('Removed file:', pathToRemove); }; const clearAllFiles = () => { setFiles([]); - console.log('Cleared all files'); }; - const toggleGeneratedImageSelection = (imageId: string) => { - setGeneratedImages(prev => - prev.map(img => - img.id === imageId - ? { ...img, selectedForNext: !img.selectedForNext } - : img + const toggleImageSelection = (imagePath: string) => { + setFiles(prev => + prev.map(file => + file.path === imagePath + ? { ...file, selected: !file.selected } + : file ) ); }; - const useSelectedImagesForNext = () => { - const selectedImages = generatedImages.filter(img => img.selectedForNext); - - if (selectedImages.length === 0) { - addDebugMessage('warn', 'No generated images selected'); + const getSelectedImages = () => { + return files.filter(file => file.selected); + }; + + + + const saveAndClose = async () => { + // Find the last generated image + const generatedFiles = files.filter(file => file.path.startsWith('generated_')); + if (generatedFiles.length === 0) { + addDebugMessage('warn', 'No generated images to save'); return; } - // Convert generated images to ImageFile format - const newImageFiles: ImageFile[] = selectedImages.map(img => ({ - path: `generated_${img.id}.png`, - src: img.src - })); + const lastGenerated = generatedFiles[generatedFiles.length - 1]; + addDebugMessage('info', `πŸ’Ύ Saving and closing with: ${lastGenerated.path}`); - // Add to current files - setFiles(prevFiles => [...prevFiles, ...newImageFiles]); - - // Clear selections - setGeneratedImages(prev => - prev.map(img => ({ ...img, selectedForNext: false })) - ); + try { + // 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), + dst, + generatedImage: { + src: lastGenerated.src, + filename: lastGenerated.path + } + }; - addDebugMessage('info', `πŸ“ Added ${selectedImages.length} generated images as input`, { - images: selectedImages.map(img => `generated_${img.id}.png`) - }); - }; + await tauriApi.submitPrompt(result); - const clearGeneratedImages = () => { - setGeneratedImages([]); - addDebugMessage('info', 'πŸ—‘οΈ Cleared generated images gallery'); - }; - - const openFullscreen = (imageSrc: string) => { - setFullscreenImage(imageSrc); - }; - - const closeFullscreen = () => { - setFullscreenImage(null); - }; - - // ESC key handler for fullscreen - useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === 'Escape' && fullscreenImage) { - closeFullscreen(); - } - }; - - if (fullscreenImage) { - document.addEventListener('keydown', handleKeyDown); - return () => document.removeEventListener('keydown', handleKeyDown); + addDebugMessage('info', 'βœ… Final result sent, closing app'); + } catch (error) { + addDebugMessage('error', 'Failed to save and close', { error: (error as Error).message }); } - }, [fullscreenImage]); + }; + const generateImage = async (promptText: string, includeImages: ImageFile[] = []) => { if (!apiKey) { @@ -270,56 +161,64 @@ function App() { setIsGenerating(true); addDebugMessage('info', `🎨 Starting image generation via backend: "${promptText}"`); - - // Add placeholder image with spinner - const placeholderImage: GeneratedImage = { - id: `placeholder_${Date.now()}`, - src: '', // Empty src for placeholder - prompt: promptText, - timestamp: Date.now(), - saved: false, - selectedForNext: false + + // Add placeholder image with spinner to the files grid + const placeholderFile: ImageFile = { + path: `generating_${Date.now()}`, + src: 'data:image/svg+xml;base64,' + btoa(` + + + + + + + Generating... + + `) }; - - setGeneratedImages(prev => [...prev, placeholderImage]); - + + 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 safeInvoke('generate_image_via_backend', { + await tauriApi.generateImageViaBackend({ prompt: promptText, files: filePaths, dst: genDst }); - + addDebugMessage('info', 'πŸ“€ Generation request sent to backend'); - - // Safety timeout to reset generating state (in case something goes wrong) + + // Clear any existing timeout + if (generationTimeoutId) { + clearTimeout(generationTimeoutId); + } + const timeoutId = setTimeout(() => { addDebugMessage('warn', '⏰ Generation timeout - resetting state'); setIsGenerating(false); - setGeneratedImages(prev => prev.filter(img => !img.id.startsWith('placeholder_'))); - }, 30000); // 30 second timeout - - // Store timeout ID so we can clear it when generation completes - placeholderImage.timeoutId = timeoutId; - + 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) }); setIsGenerating(false); - // Remove placeholder on error - setGeneratedImages(prev => prev.filter(img => img.id !== placeholderImage.id)); + setFiles(prev => prev.filter(file => !file.path.startsWith('generating_'))); } }; @@ -346,7 +245,7 @@ function App() { const toggleTheme = () => { const newDarkMode = !isDarkMode; setIsDarkMode(newDarkMode); - + if (newDarkMode) { document.documentElement.classList.add('dark'); localStorage.setItem('theme', 'dark'); @@ -356,222 +255,6 @@ function App() { } }; - useEffect(() => { - const initializeApp = async () => { - console.log('Initializing app...'); - console.log('isTauri:', isTauri); - - // Wait for Tauri APIs to be loaded first (check if any loaded) - let attempts = 0; - while (!invoke && attempts < 50) { - await new Promise(resolve => setTimeout(resolve, 100)); - attempts++; - } - - // Update isTauri based on whether APIs actually loaded - const actuallyInTauri = !!invoke; - console.log('Environment detection result:', { - initialDetection: isTauri, - actuallyInTauri, - invokeLoaded: !!invoke, - listenLoaded: !!listen - }); - - if (actuallyInTauri) { - - // Initialize counter from backend - try { - await getCounter(); - addDebugMessage('info', 'IPC system initialized successfully'); - } catch (e) { - console.log("Failed to initialize counter:", e); - addDebugMessage('warn', 'Counter initialization failed - running in browser mode'); - } - - } - - // Set up Tauri event listeners for config and images - const setupTauriEventListeners = async () => { - if (actuallyInTauri && listen) { - try { - console.log('Setting up Tauri event listeners'); - - // Listen for config data - await listen('config-received', (event: any) => { - console.log('Received config event:', event); - const data = event.payload; - - console.log('Config data details:', { - prompt: data.prompt, - dst: data.dst, - apiKey: data.apiKey ? '[REDACTED]' : null, - files: data.files - }); - - if (data.prompt) { - console.log('Setting prompt from config:', data.prompt); - setPrompt(data.prompt); - } else { - console.log('No prompt in config data'); - } - - if (data.dst) { - console.log('Setting dst from config:', data.dst); - setDst(data.dst); - } else { - console.log('No dst in config data'); - } - - if (data.apiKey) { - console.log('Setting API key from config'); - setApiKey(data.apiKey); - setChatMode(true); - } else { - console.log('No API key in config data'); - } - - setIpcInitialized(true); - addDebugMessage('info', 'πŸ“¨ Config received from images.ts', { - hasPrompt: !!data.prompt, - hasDst: !!data.dst, - hasApiKey: !!data.apiKey, - fileCount: data.files?.length || 0 - }); - }); - - // Listen for image data - await listen('image-received', (event: any) => { - const imageData = event.payload; - - addDebugMessage('debug', 'πŸ–ΌοΈ Processing image data', { - filename: imageData.filename, - mimeType: imageData.mimeType, - base64Length: imageData.base64?.length, - base64Sample: imageData.base64?.substring(0, 50) + '...', - hasValidData: !!(imageData.base64 && imageData.mimeType && imageData.filename) - }); - - if (imageData.base64 && imageData.mimeType && imageData.filename) { - const src = `data:${imageData.mimeType};base64,${imageData.base64}`; - - // Test if the base64 is valid by trying to create an image - const testImg = new Image(); - testImg.onload = () => { - addDebugMessage('info', `βœ… Image loaded successfully: ${imageData.filename}`); - }; - testImg.onerror = () => { - addDebugMessage('error', `❌ Failed to load image: ${imageData.filename}`, { - mimeType: imageData.mimeType, - base64Start: imageData.base64.substring(0, 100) - }); - }; - testImg.src = src; - - // Check if this is a generated image (output file) or input image - const isGeneratedImage = imageData.filename.includes('_out') || - imageData.filename.includes('generated_') || - isGenerating; - - if (isGeneratedImage) { - // This is a generated image - replace placeholder or add new - const generatedImage: GeneratedImage = { - id: Date.now().toString(), - src, - prompt: prompt || 'Generated image', - timestamp: Date.now(), - saved: false, - selectedForNext: false - }; - - setGeneratedImages(prev => { - // Find and clear timeout for any placeholder - const placeholder = prev.find(img => img.id.startsWith('placeholder_')); - if (placeholder?.timeoutId) { - clearTimeout(placeholder.timeoutId); - } - - // Remove any placeholder and add the real image - const withoutPlaceholder = prev.filter(img => !img.id.startsWith('placeholder_')); - return [...withoutPlaceholder, generatedImage]; - }); - - setIsGenerating(false); // Complete the generation - addDebugMessage('info', 'βœ… Generated image added to gallery', { - filename: imageData.filename, - prompt: prompt - }); - } else { - // This is an input image - add to files - 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; - }); - - addDebugMessage('info', 'πŸ“¨ Input image received from images.ts', { - filename: imageData.filename, - mimeType: imageData.mimeType, - size: `${Math.round(imageData.base64.length/1024)}KB` - }); - } - } else { - addDebugMessage('error', '❌ Invalid image data received', { - hasBase64: !!imageData.base64, - hasMimeType: !!imageData.mimeType, - hasFilename: !!imageData.filename - }); - } - }); - - // Listen for generation results (generated images come as regular image-received events) - // We'll detect generation completion by checking if it's a generated image - - // For now, we'll rely on the image-received event and detect if it's a generated result - // The generated image will be added to the generated images gallery - - // Listen for generation errors - await listen('generation-error', (event: any) => { - const errorData = event.payload; - addDebugMessage('error', '❌ Generation failed', errorData); - setIsGenerating(false); - // Remove any placeholder images - setGeneratedImages(prev => prev.filter(img => !img.id.startsWith('placeholder_'))); - }); - - addDebugMessage('info', 'Tauri event listeners set up'); - - // NOW request config from images.ts (after listeners are ready) - try { - console.log('Requesting config from images.ts...'); - await safeInvoke('request_config_from_images'); - addDebugMessage('info', 'Config request sent to images.ts'); - } catch (e) { - console.error('Failed to request config:', e); - addDebugMessage('error', `Failed to request config: ${e}`); - } - - } catch (error) { - console.error('Failed to set up Tauri event listeners:', error); - addDebugMessage('error', `Failed to set up event listeners: ${error}`); - } - } else { - addDebugMessage('warn', 'Tauri event listeners not available - running in browser mode'); - } - }; - - // Set up event listeners with delay to ensure listen function is loaded - setTimeout(setupTauriEventListeners, 500); - }; - - // Delay the initialization slightly to ensure everything is loaded - setTimeout(initializeApp, 200); - }, []); - // Auto-generate default destination file when files change useEffect(() => { if (files.length > 0 && !dst) { @@ -582,156 +265,39 @@ function App() { useEffect(() => { const logBaseDirectories = async () => { - console.log('--- Resolving BaseDirectory Paths ---'); - /* - try { console.log('Home:', await homeDir()); } catch (e) { console.error('homeDir:', e); } - try { console.log('Audio:', await audioDir()); } catch (e) { console.error('audioDir:', e); } - try { console.log('Cache:', await cacheDir()); } catch (e) { console.error('cacheDir:', e); } - try { console.log('Config:', await configDir()); } catch (e) { console.error('configDir:', e); } - try { console.log('Data:', await dataDir()); } catch (e) { console.error('dataDir:', e); } - try { console.log('LocalData:', await localDataDir()); } catch (e) { console.error('localDataDir:', e); } - try { console.log('Desktop:', await desktopDir()); } catch (e) { console.error('desktopDir:', e); } - try { console.log('Document:', await documentDir()); } catch (e) { console.error('documentDir:', e); } - try { console.log('Download:', await downloadDir()); } catch (e) { console.error('downloadDir:', e); } - try { console.log('Executable:', await executableDir()); } catch (e) { console.error('executableDir:', e); } - try { console.log('Font:', await fontDir()); } catch (e) { console.error('fontDir:', e); } - try { console.log('Picture:', await pictureDir()); } catch (e) { console.error('pictureDir:', e); } - try { console.log('Public:', await publicDir()); } catch (e) { console.error('publicDir:', e); } - try { console.log('Resource:', await resourceDir()); } catch (e) { console.error('resourceDir:', e); } - try { console.log('Runtime:', await runtimeDir()); } catch (e) { console.error('runtimeDir:', e); } - try { console.log('Template:', await templateDir()); } catch (e) { console.error('templateDir:', e); } - try { console.log('Video:', await videoDir()); } catch (e) { console.error('videoDir:', e); } - try { console.log('Temp:', await tempDir()); } catch(e) { console.error('tempDir:', e); } - console.log('--- Resolving App-Specific BaseDirectory Paths ---'); - try { console.log('AppConfig:', await appConfigDir()); } catch (e) { console.error('appConfigDir:', e); } - try { console.log('AppData:', await appDataDir()); } catch (e) { console.error('appDataDir:', e); } - try { console.log('AppLocalData:', await appLocalDataDir()); } catch (e) { console.error('appLocalDataDir:', e); } - try { console.log('AppCache:', await appCacheDir()); } catch (e) { console.error('appCacheDir:', e); } - try { console.log('AppLog:', await appLogDir()); } catch (e) { console.error('appLogDir:', e); } - */ console.log('------------------------------------'); }; - logBaseDirectories(); }, []); async function submit() { - console.log('=== SUBMIT DEBUG ==='); - console.log('chatMode:', chatMode); - console.log('prompt:', prompt); - console.log('files:', files.map(f => f.path)); - console.log('dst:', dst); - console.log('=================='); - - if (chatMode && apiKey) { - // Chat mode: generate image via backend - await generateImage(prompt, files); - // Don't clear prompt immediately - let user decide if they want to keep it + + 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 { - // Simple mode: send to CLI - try { - const result = await safeInvoke("submit_prompt", { prompt, files: files.map(f => f.path), dst }); - console.log('Submit result:', result); - } catch (error) { - console.error('Submit error:', error); - safeInvoke('log_error_to_console', { error: `[Frontend Error] Submit failed: ${JSON.stringify(error)}` }); - } + addDebugMessage('error', 'API key required for image generation'); } } - // Counter functions - const incrementCounter = async () => { - if (!isTauri) { - setCounter(prev => prev + 1); - return; - } - - try { - const newCount = await safeInvoke('increment_counter') as number; - setCounter(newCount); - addDebugMessage('info', `Counter incremented to ${newCount}`); - } catch (error) { - console.error('Failed to increment counter:', error); - addDebugMessage('error', `Failed to increment counter: ${error}`); - } - }; - - const resetCounter = async () => { - if (!isTauri) { - setCounter(0); - return; - } - - try { - await safeInvoke('reset_counter'); - setCounter(0); - addDebugMessage('info', 'Counter reset to 0'); - } catch (error) { - console.error('Failed to reset counter:', error); - addDebugMessage('error', `Failed to reset counter: ${error}`); - } - }; - - const getCounter = async () => { - if (!isTauri) return; - - try { - const currentCount = await safeInvoke('get_counter') as number; - setCounter(currentCount); - addDebugMessage('info', `Current counter value: ${currentCount}`); - } catch (error) { - console.error('Failed to get counter:', error); - addDebugMessage('error', `Failed to get counter: ${error}`); - } - }; - - // Debug message functions - const addDebugMessage = async (level: 'info' | 'warn' | 'error' | 'debug', message: string, data?: any) => { - const timestamp = new Date().toLocaleTimeString(); - const debugMsg = { level, message, data, timestamp }; - - // Add to local state - setDebugMessages(prev => [...prev.slice(-99), debugMsg]); // Keep last 100 messages - - if (!isTauri) return; - - try { - await safeInvoke('add_debug_message', { message, level, data }); - } catch (error) { - console.error('Failed to add debug message:', error); - } - }; - const clearDebugMessages = async () => { setDebugMessages([]); - - if (!isTauri) return; - - try { - await safeInvoke('clear_debug_messages'); - addDebugMessage('info', 'Debug messages cleared'); - } catch (error) { - console.error('Failed to clear debug messages:', error); - } + await tauriApi.clearDebugMessages(); + addDebugMessage('info', 'Debug messages cleared'); }; const sendIPCMessage = async (messageType: string, data: any) => { - if (!isTauri) { - console.log(`[Browser Mode] Would send IPC message: ${messageType}`, data); - return; - } - - try { - await safeInvoke('send_ipc_message', { messageType, data }); - addDebugMessage('info', `IPC message sent: ${messageType}`, data); - } catch (error) { - console.error('Failed to send IPC message:', error); - addDebugMessage('error', `Failed to send IPC message: ${error}`); - } + await tauriApi.sendIPCMessage(messageType, data); + addDebugMessage('info', `IPC message sent: ${messageType}`, data); }; const sendMessageToImages = async () => { if (!messageToSend.trim()) return; - + const message = { message: messageToSend, timestamp: Date.now(), @@ -739,89 +305,19 @@ function App() { }; try { - // Send via Tauri command to stdout (bypasses console hijacking) - await safeInvoke('send_message_to_stdout', { message: JSON.stringify(message) }); - - // Also add to our debug panel + await tauriApi.sendMessageToStdout(JSON.stringify(message)); addDebugMessage('info', `πŸ“€ Sent to images.ts: ${messageToSend}`, message); } catch (error) { - console.error('Failed to send message to images.ts:', error); - addDebugMessage('error', `Failed to send message: ${error}`); + const errorMessage = error instanceof Error ? error.message : JSON.stringify(error); + addDebugMessage('error', `Failed to send message: ${errorMessage}`); } - + // Clear the input setMessageToSend(''); }; - - const saveGeneratedImage = async (generatedImage: GeneratedImage) => { - if (!isTauri) { - console.log('[Browser Mode] File save not available - downloading instead'); - // Browser fallback: trigger download - const base64Match = generatedImage.src.match(/^data:([^;]+);base64,(.+)$/); - if (base64Match) { - const link = document.createElement('a'); - link.download = `generated_${generatedImage.id}.png`; - link.href = generatedImage.src; - link.click(); - - // Mark as saved - setGeneratedImages(prev => - prev.map(img => - img.id === generatedImage.id - ? { ...img, saved: true } - : img - ) - ); - } - return; - } - - try { - if (!save || !writeFile) { - console.error('Save functions not available'); - return; - } - - const defaultFilename = `generated_${generatedImage.id}.png`; - const filePath = await save({ - defaultPath: defaultFilename, - filters: [{ - name: 'Images', - extensions: ['png', 'jpg', 'jpeg'] - }] - }); - - if (filePath) { - // Convert base64 to binary data - const base64Match = generatedImage.src.match(/^data:([^;]+);base64,(.+)$/); - if (base64Match) { - const base64Data = base64Match[2]; - const binaryData = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0)); - - await writeFile(filePath, binaryData); - - // Mark as saved - setGeneratedImages(prev => - prev.map(img => - img.id === generatedImage.id - ? { ...img, saved: true } - : img - ) - ); - - console.log('Image saved to:', filePath); - } - } - } catch (error) { - console.error('Failed to save image:', error); - safeInvoke('log_error_to_console', { error: `[Frontend Error] Failed to save image: ${JSON.stringify(error)}` }); - } - }; - async function openFilePicker() { - if (!isTauri) { - console.log('[Browser Mode] File picker not available'); + if (!tauriApi.isTauri()) { // Browser fallback: create file input const input = document.createElement('input'); input.type = 'file'; @@ -833,7 +329,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) => { @@ -855,14 +351,14 @@ function App() { input.click(); return; } - + try { - if (!open) { + if (!tauriApi.dialog.open) { console.error('Open function not available'); return; } - - const selected = await open({ + + const selected = await tauriApi.dialog.open({ multiple: true, filters: [{ name: 'Images', @@ -873,8 +369,8 @@ function App() { addFiles(selected); } } catch (e) { - console.error('File picker error:', e); - safeInvoke('log_error_to_console', { error: `[Frontend Error] File picker error: ${JSON.stringify(e)}` }); + console.error('File picker error:', e); + tauriApi.logErrorToConsole(`[Frontend Error] File picker error: ${JSON.stringify(e)}`); } } @@ -882,21 +378,21 @@ 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 save({ + + const selected = await tauriApi.dialog.save({ defaultPath: currentFilename, filters: [{ name: 'Images', extensions: ['png', 'jpg'] }] }); - + if (selected) { setDst(selected); } } catch (e) { - console.error('Save dialog error:', e); - safeInvoke('log_error_to_console', { error: `[Frontend Error] Save dialog error: ${JSON.stringify(e)}` }); + console.error('Save dialog error:', e); + tauriApi.logErrorToConsole(`[Frontend Error] Save dialog error: ${JSON.stringify(e)}`); } } @@ -907,589 +403,47 @@ function App() {
- +
-
-

Image Prompt

-
- {/* Debug Panel Toggle */} - - - {/* Counter Display */} -
- Counter: {counter} -
- - -
-
- - {/* Theme Toggle */} - - - {/* Chat Mode Toggle */} -
- Simple - - Chat - {!apiKey && (API key required)} -
-
-
-
{ - e.preventDefault(); - submit(); - }} - > -
-
- -