mono/packages/kbot/gui/tauri-app/src-tauri/src/lib.rs
2025-09-17 19:50:22 +02:00

400 lines
16 KiB
Rust

use tauri::{Manager, Emitter};
use serde::{Serialize, Deserialize};
struct Counter(std::sync::Mutex<u32>);
struct DebugMessages(std::sync::Mutex<Vec<DebugPayload>>);
#[derive(Serialize, Deserialize)]
struct Payload {
prompt: String,
files: Vec<String>,
dst: String,
}
#[derive(Serialize, Deserialize)]
struct IPCMessage {
#[serde(rename = "type")]
message_type: String,
data: serde_json::Value,
timestamp: Option<u64>,
id: Option<String>,
}
#[derive(Serialize, Deserialize)]
struct CounterPayload {
count: u32,
message: Option<String>,
}
#[derive(Serialize, Deserialize, Clone)]
struct DebugPayload {
level: String,
message: String,
data: Option<serde_json::Value>,
}
#[derive(Serialize, Deserialize)]
struct ImagePayload {
base64: String,
#[serde(rename = "mimeType")]
mime_type: String,
filename: Option<String>,
}
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]
fn submit_prompt(prompt: &str, files: Vec<String>, 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 increment_counter(state: tauri::State<'_, Counter>) -> Result<u32, String> {
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<u32, String> {
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<u32, String> {
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<serde_json::Value>, 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<Vec<DebugPayload>, 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::<u32>())),
};
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<String>, 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<String>, dst: Option<String>, api_key: Option<String>, files: Vec<String>, 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,
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::<serde_json::Value>(&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();
}
_ => {}
});
}