tauri cleanup
This commit is contained in:
parent
005a909534
commit
3a59b33a91
File diff suppressed because one or more lines are too long
BIN
packages/kbot/dist/win-64/tauri-app.exe
vendored
BIN
packages/kbot/dist/win-64/tauri-app.exe
vendored
Binary file not shown.
222
packages/kbot/gui/tauri-app/src-tauri/src/handlers.rs
Normal file
222
packages/kbot/gui/tauri-app/src-tauri/src/handlers.rs
Normal file
@ -0,0 +1,222 @@
|
||||
use tauri::{Manager, Emitter};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use dirs;
|
||||
|
||||
use crate::{log_json, Counter, DebugMessages, DebugPayload};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Payload {
|
||||
pub prompt: String,
|
||||
pub files: Vec<String>,
|
||||
pub dst: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct IPCMessage {
|
||||
#[serde(rename = "type")]
|
||||
pub message_type: String,
|
||||
pub data: serde_json::Value,
|
||||
pub timestamp: Option<u64>,
|
||||
pub id: Option<String>,
|
||||
}
|
||||
|
||||
// Core command handlers
|
||||
#[tauri::command]
|
||||
pub fn submit_prompt(prompt: &str, files: Vec<String>, dst: &str, window: tauri::Window) {
|
||||
log_json("info", "submit_prompt command called", Some(serde_json::json!({
|
||||
"prompt": prompt,
|
||||
"files": files,
|
||||
"dst": dst
|
||||
})));
|
||||
|
||||
let payload = Payload {
|
||||
prompt: prompt.to_string(),
|
||||
files,
|
||||
dst: dst.to_string(),
|
||||
};
|
||||
let json_payload = serde_json::to_string(&payload).unwrap();
|
||||
|
||||
log_json("info", "Sending JSON payload to stdout", Some(serde_json::json!({
|
||||
"payload_length": json_payload.len()
|
||||
})));
|
||||
println!("{}", json_payload);
|
||||
let _ = window.app_handle().exit(0);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn log_error_to_console(error: &str) {
|
||||
eprintln!("[WebView ERROR forwarded]: {}", error);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn resolve_path_relative_to_home(absolute_path: String) -> Result<String, String> {
|
||||
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())?;
|
||||
Ok(relative_path.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
// Debug message handlers
|
||||
#[tauri::command]
|
||||
pub fn add_debug_message(message: String, level: String, data: Option<serde_json::Value>, state: tauri::State<'_, DebugMessages>) -> Result<(), String> {
|
||||
log_json(&level, &format!("Frontend: {}", message), data.clone());
|
||||
|
||||
let debug_payload = DebugPayload {
|
||||
level,
|
||||
message,
|
||||
data,
|
||||
};
|
||||
|
||||
let mut messages = state.0.lock().unwrap();
|
||||
messages.push(debug_payload);
|
||||
|
||||
if messages.len() > 100 {
|
||||
let len = messages.len();
|
||||
messages.drain(0..len - 100);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_debug_messages(state: tauri::State<'_, DebugMessages>) -> Result<Vec<DebugPayload>, String> {
|
||||
let messages = state.0.lock().unwrap();
|
||||
Ok(messages.clone())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn clear_debug_messages(state: tauri::State<'_, DebugMessages>) -> Result<(), String> {
|
||||
let mut messages = state.0.lock().unwrap();
|
||||
messages.clear();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Counter handlers (legacy)
|
||||
#[tauri::command]
|
||||
pub fn increment_counter(state: tauri::State<'_, Counter>) -> Result<u32, String> {
|
||||
let mut counter = state.0.lock().unwrap();
|
||||
*counter += 1;
|
||||
Ok(*counter)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_counter(state: tauri::State<'_, Counter>) -> Result<u32, String> {
|
||||
let counter = state.0.lock().unwrap();
|
||||
Ok(*counter)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn reset_counter(state: tauri::State<'_, Counter>) -> Result<u32, String> {
|
||||
let mut counter = state.0.lock().unwrap();
|
||||
*counter = 0;
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
// IPC communication handlers
|
||||
#[tauri::command]
|
||||
pub fn send_ipc_message(message_type: String, data: serde_json::Value, _window: tauri::Window) -> Result<(), String> {
|
||||
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();
|
||||
println!("{}", json_message);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn send_message_to_stdout(message: String) -> Result<(), String> {
|
||||
println!("{}", message);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Image generation handlers
|
||||
#[tauri::command]
|
||||
pub fn generate_image_via_backend(prompt: String, files: Vec<String>, dst: String) -> Result<(), String> {
|
||||
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());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn request_config_from_images(_app: tauri::AppHandle) -> Result<(), String> {
|
||||
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());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn request_file_deletion(path: String) -> Result<(), String> {
|
||||
let request = serde_json::json!({
|
||||
"type": "delete_request",
|
||||
"path": path,
|
||||
});
|
||||
|
||||
println!("{}", serde_json::to_string(&request).unwrap());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Legacy direct handlers (not used via stdin)
|
||||
#[tauri::command]
|
||||
pub fn forward_config_to_frontend(prompt: Option<String>, dst: Option<String>, api_key: Option<String>, files: Vec<String>, app: tauri::AppHandle) -> Result<(), String> {
|
||||
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) {
|
||||
return Err(format!("Failed to emit config: {}", e));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn forward_image_to_frontend(base64: String, mime_type: String, filename: String, app: tauri::AppHandle) -> Result<(), String> {
|
||||
let image_data = serde_json::json!({
|
||||
"base64": base64,
|
||||
"mimeType": mime_type,
|
||||
"filename": filename
|
||||
});
|
||||
|
||||
if let Err(e) = app.emit("image-received", &image_data) {
|
||||
return Err(format!("Failed to emit image: {}", e));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -1,6 +1,10 @@
|
||||
use tauri::{Manager, Emitter};
|
||||
use tauri::Manager;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use dirs;
|
||||
|
||||
mod handlers;
|
||||
mod stdin_processor;
|
||||
|
||||
pub use handlers::*;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct LogMessage {
|
||||
@ -11,7 +15,7 @@ struct LogMessage {
|
||||
timestamp: u64,
|
||||
}
|
||||
|
||||
fn log_json(level: &str, message: &str, data: Option<serde_json::Value>) {
|
||||
pub fn log_json(level: &str, message: &str, data: Option<serde_json::Value>) {
|
||||
let log_msg = LogMessage {
|
||||
level: level.to_string(),
|
||||
message: message.to_string(),
|
||||
@ -24,298 +28,17 @@ fn log_json(level: &str, message: &str, data: Option<serde_json::Value>) {
|
||||
eprintln!("{}", serde_json::to_string(&log_msg).unwrap_or_else(|_| format!("{{\"level\":\"error\",\"message\":\"Failed to serialize log message\"}}")));
|
||||
}
|
||||
|
||||
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>,
|
||||
}
|
||||
// App state structures
|
||||
pub struct Counter(pub std::sync::Mutex<u32>);
|
||||
pub struct DebugMessages(pub std::sync::Mutex<Vec<DebugPayload>>);
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
struct DebugPayload {
|
||||
level: String,
|
||||
message: String,
|
||||
data: Option<serde_json::Value>,
|
||||
pub struct DebugPayload {
|
||||
pub level: String,
|
||||
pub message: String,
|
||||
pub 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) {
|
||||
log_json("info", "submit_prompt command called", Some(serde_json::json!({
|
||||
"prompt": prompt,
|
||||
"files": files,
|
||||
"dst": dst
|
||||
})));
|
||||
|
||||
let payload = Payload {
|
||||
prompt: prompt.to_string(),
|
||||
files,
|
||||
dst: dst.to_string(),
|
||||
};
|
||||
let json_payload = serde_json::to_string(&payload).unwrap();
|
||||
|
||||
log_json("info", "Sending JSON payload to stdout", Some(serde_json::json!({
|
||||
"payload_length": json_payload.len()
|
||||
})));
|
||||
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]: {}", error);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn resolve_path_relative_to_home(absolute_path: String) -> Result<String, String> {
|
||||
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<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> {
|
||||
// Forward frontend debug messages to CLI via structured logging
|
||||
log_json(&level, &format!("Frontend: {}", message), data.clone());
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn request_file_deletion(path: String) -> Result<(), String> {
|
||||
log_json("info", "request_file_deletion command called", Some(serde_json::json!({
|
||||
"path": path
|
||||
})));
|
||||
|
||||
let request = serde_json::json!({
|
||||
"type": "delete_request",
|
||||
"path": path,
|
||||
});
|
||||
|
||||
println!("{}", serde_json::to_string(&request).unwrap());
|
||||
log_json("info", "Deletion request sent to images.ts", None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
@ -352,153 +75,9 @@ pub fn run() {
|
||||
}
|
||||
|
||||
let app_handle = app.handle().clone();
|
||||
|
||||
// Test our new JSON logging
|
||||
log_json("info", "Tauri app starting with improved logging", Some(serde_json::json!({
|
||||
"test": true,
|
||||
"message": "This is a test of the new structured logging system"
|
||||
})));
|
||||
|
||||
// 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);
|
||||
|
||||
log_json("info", "Stdin listener thread started", None);
|
||||
|
||||
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
|
||||
if line_content.contains("\"base64\"") {
|
||||
log_json("debug", "Received stdin command with base64 data", Some(serde_json::json!({
|
||||
"content_length": line_content.len()
|
||||
})));
|
||||
} else {
|
||||
log_json("debug", "Received stdin command", Some(serde_json::json!({
|
||||
"content": line_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()) {
|
||||
log_json("info", "Processing command", Some(serde_json::json!({
|
||||
"command": cmd
|
||||
})));
|
||||
|
||||
match cmd {
|
||||
"forward_config_to_frontend" => {
|
||||
log_json("info", "Forwarding config to frontend", Some(serde_json::json!({
|
||||
"has_prompt": command.get("prompt").is_some(),
|
||||
"has_dst": command.get("dst").is_some(),
|
||||
"has_api_key": command.get("apiKey").is_some(),
|
||||
"file_count": command.get("files").and_then(|f| f.as_array()).map(|a| a.len()).unwrap_or(0)
|
||||
})));
|
||||
|
||||
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) {
|
||||
log_json("error", "Failed to emit config-received", Some(serde_json::json!({
|
||||
"error": e.to_string()
|
||||
})));
|
||||
} else {
|
||||
log_json("info", "Config emitted successfully to frontend", None);
|
||||
}
|
||||
}
|
||||
"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())
|
||||
) {
|
||||
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,
|
||||
"filename": filename
|
||||
});
|
||||
|
||||
if let Err(e) = app_handle.emit("image-received", &image_data) {
|
||||
log_json("error", "Failed to emit image-received", Some(serde_json::json!({
|
||||
"error": e.to_string(),
|
||||
"filename": filename
|
||||
})));
|
||||
} else {
|
||||
log_json("info", "Image emitted successfully", Some(serde_json::json!({
|
||||
"filename": 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");
|
||||
}
|
||||
}
|
||||
"file_deleted_successfully" => {
|
||||
if let Some(path) = command.get("path").and_then(|v| v.as_str()) {
|
||||
eprintln!("[RUST LOG]: Received confirmation of file deletion: {}", path);
|
||||
if let Err(e) = app_handle.emit("file-deleted-successfully", &serde_json::json!({ "path": path })) {
|
||||
eprintln!("[RUST LOG]: Failed to emit file-deleted-successfully: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
"file_deletion_error" => {
|
||||
if let (Some(path), Some(error)) = (
|
||||
command.get("path").and_then(|v| v.as_str()),
|
||||
command.get("error").and_then(|v| v.as_str())
|
||||
) {
|
||||
eprintln!("[RUST LOG]: Received file deletion error for {}: {}", path, error);
|
||||
if let Err(e) = app_handle.emit("file-deletion-error", &serde_json::json!({ "path": path, "error": error })) {
|
||||
eprintln!("[RUST LOG]: Failed to emit file-deletion-error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
eprintln!("[RUST LOG]: Unknown command: {}", cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!("[RUST LOG]: Failed to parse command as JSON");
|
||||
}
|
||||
}
|
||||
}
|
||||
eprintln!("[RUST LOG]: Stdin listener thread ended");
|
||||
});
|
||||
|
||||
// Start stdin listener in separate module
|
||||
stdin_processor::start_stdin_listener(app_handle);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
|
||||
158
packages/kbot/gui/tauri-app/src-tauri/src/stdin_processor.rs
Normal file
158
packages/kbot/gui/tauri-app/src-tauri/src/stdin_processor.rs
Normal file
@ -0,0 +1,158 @@
|
||||
use tauri::Emitter;
|
||||
use std::io::{BufRead, BufReader};
|
||||
|
||||
use crate::log_json;
|
||||
|
||||
pub fn start_stdin_listener(app_handle: tauri::AppHandle) {
|
||||
std::thread::spawn(move || {
|
||||
let stdin = std::io::stdin();
|
||||
let reader = BufReader::new(stdin);
|
||||
|
||||
|
||||
for line in reader.lines() {
|
||||
if let Ok(line_content) = line {
|
||||
if line_content.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
log_json("info", "Processing command", Some(serde_json::json!({
|
||||
"command": cmd
|
||||
})));
|
||||
|
||||
match cmd {
|
||||
"forward_config_to_frontend" => {
|
||||
handle_config_forward(&command, &app_handle);
|
||||
}
|
||||
"forward_image_to_frontend" => {
|
||||
handle_image_forward(&command, &app_handle);
|
||||
}
|
||||
"generation_result" => {
|
||||
if let Err(e) = app_handle.emit("generation-result", &command) {
|
||||
log_json("error", "Failed to emit generation-result", Some(serde_json::json!({
|
||||
"error": e.to_string()
|
||||
})));
|
||||
}
|
||||
}
|
||||
"generation_error" => {
|
||||
if let Err(e) = app_handle.emit("generation-error", &command) {
|
||||
log_json("error", "Failed to emit generation-error", Some(serde_json::json!({
|
||||
"error": e.to_string()
|
||||
})));
|
||||
}
|
||||
}
|
||||
"generation_complete" => {
|
||||
if let Err(e) = app_handle.emit("generation-complete", &command) {
|
||||
log_json("error", "Failed to emit generation-complete", Some(serde_json::json!({
|
||||
"error": e.to_string()
|
||||
})));
|
||||
}
|
||||
}
|
||||
"file_deleted_successfully" => {
|
||||
if let Some(path) = command.get("path").and_then(|v| v.as_str()) {
|
||||
if let Err(e) = app_handle.emit("file-deleted-successfully", &serde_json::json!({ "path": path })) {
|
||||
log_json("error", "Failed to emit file-deleted-successfully", Some(serde_json::json!({
|
||||
"error": e.to_string()
|
||||
})));
|
||||
}
|
||||
}
|
||||
}
|
||||
"file_deletion_error" => {
|
||||
if let (Some(path), Some(error)) = (
|
||||
command.get("path").and_then(|v| v.as_str()),
|
||||
command.get("error").and_then(|v| v.as_str())
|
||||
) {
|
||||
if let Err(e) = app_handle.emit("file-deletion-error", &serde_json::json!({ "path": path, "error": error })) {
|
||||
log_json("error", "Failed to emit file-deletion-error", Some(serde_json::json!({
|
||||
"error": e.to_string()
|
||||
})));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
log_json("warn", "Unknown command received", Some(serde_json::json!({
|
||||
"command": cmd
|
||||
})));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log_json("warn", "Failed to parse stdin as JSON", Some(serde_json::json!({
|
||||
"content_length": line_content.len()
|
||||
})));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log_json("info", "Stdin listener thread ended", None);
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_config_forward(command: &serde_json::Value, app_handle: &tauri::AppHandle) {
|
||||
log_json("info", "Forwarding config to frontend", Some(serde_json::json!({
|
||||
"has_prompt": command.get("prompt").is_some(),
|
||||
"has_dst": command.get("dst").is_some(),
|
||||
"has_api_key": command.get("apiKey").is_some(),
|
||||
"file_count": command.get("files").and_then(|f| f.as_array()).map(|a| a.len()).unwrap_or(0)
|
||||
})));
|
||||
|
||||
// Extract values, handling both null and undefined
|
||||
let prompt = command.get("prompt")
|
||||
.and_then(|v| if v.is_null() { None } else { v.as_str().map(|s| s.to_string()) });
|
||||
let dst = command.get("dst")
|
||||
.and_then(|v| if v.is_null() { None } else { v.as_str().map(|s| s.to_string()) });
|
||||
let api_key = command.get("apiKey")
|
||||
.and_then(|v| if v.is_null() { None } else { v.as_str().map(|s| s.to_string()) });
|
||||
let files = command.get("files")
|
||||
.and_then(|v| v.as_array())
|
||||
.map(|arr| arr.iter().filter_map(|f| f.as_str().map(|s| s.to_string())).collect::<Vec<String>>())
|
||||
.unwrap_or_else(Vec::new);
|
||||
|
||||
let config_data = serde_json::json!({
|
||||
"prompt": prompt,
|
||||
"dst": dst,
|
||||
"apiKey": api_key,
|
||||
"files": files
|
||||
});
|
||||
|
||||
if let Err(e) = app_handle.emit("config-received", &config_data) {
|
||||
log_json("error", "Failed to emit config-received", Some(serde_json::json!({
|
||||
"error": e.to_string()
|
||||
})));
|
||||
} else {
|
||||
log_json("info", "Config emitted successfully to frontend", None);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_image_forward(command: &serde_json::Value, app_handle: &tauri::AppHandle) {
|
||||
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())
|
||||
) {
|
||||
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,
|
||||
"filename": filename
|
||||
});
|
||||
|
||||
if let Err(e) = app_handle.emit("image-received", &image_data) {
|
||||
log_json("error", "Failed to emit image-received", Some(serde_json::json!({
|
||||
"error": e.to_string(),
|
||||
"filename": filename
|
||||
})));
|
||||
} else {
|
||||
log_json("info", "Image emitted successfully", Some(serde_json::json!({
|
||||
"filename": filename
|
||||
})));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -26,7 +26,7 @@ function App() {
|
||||
const [apiKey, setApiKey] = useState("");
|
||||
const [isDarkMode, setIsDarkMode] = useState(false);
|
||||
const [debugMessages, setDebugMessages] = useState<any[]>([]);
|
||||
const [showDebugPanel, setShowDebugPanel] = useState(true); // Default open for debugging
|
||||
const [showDebugPanel, setShowDebugPanel] = useState(false); // Hidden in production
|
||||
|
||||
// Initialize logging system and connect to UI
|
||||
useEffect(() => {
|
||||
@ -140,10 +140,6 @@ function App() {
|
||||
}
|
||||
};
|
||||
|
||||
// Legacy function for compatibility - just use log directly now
|
||||
const addDebugMessage = (level: 'info' | 'warn' | 'error' | 'debug', message: string, data?: any) => {
|
||||
log[level](message, data);
|
||||
};
|
||||
|
||||
const addImageFromUrl = async (url: string) => {
|
||||
try {
|
||||
@ -416,7 +412,7 @@ function App() {
|
||||
await generateImage(prompt, imagesToUse);
|
||||
// Don't clear prompt - let user iterate
|
||||
} else {
|
||||
addDebugMessage('error', 'API key required for image generation');
|
||||
log.error('API key required for image generation');
|
||||
}
|
||||
}
|
||||
|
||||
@ -591,7 +587,6 @@ function App() {
|
||||
{showDebugPanel && (
|
||||
<DebugPanel
|
||||
debugMessages={debugMessages}
|
||||
addDebugMessage={addDebugMessage}
|
||||
sendIPCMessage={sendIPCMessage}
|
||||
clearDebugMessages={clearDebugMessages}
|
||||
ipcInitialized={ipcInitialized}
|
||||
|
||||
@ -1,9 +1,64 @@
|
||||
import React from 'react';
|
||||
import { tauriApi } from '../lib/tauriApi';
|
||||
import log from '../lib/log';
|
||||
|
||||
// Safe JSON stringify to prevent circular reference crashes
|
||||
function safeStringify(obj: any, maxDepth = 3): string {
|
||||
const seen = new WeakSet();
|
||||
|
||||
function serialize(value: any, depth: number): any {
|
||||
if (depth > maxDepth) {
|
||||
return '[Max depth reached]';
|
||||
}
|
||||
|
||||
if (value === null || value === undefined) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value !== 'object') {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (seen.has(value)) {
|
||||
return '[Circular Reference]';
|
||||
}
|
||||
|
||||
seen.add(value);
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.slice(0, 10).map((item, index) => {
|
||||
if (index >= 10) return '[Truncated]';
|
||||
return serialize(item, depth + 1);
|
||||
});
|
||||
}
|
||||
|
||||
const result: any = {};
|
||||
const keys = Object.keys(value).slice(0, 20); // Limit keys
|
||||
|
||||
for (const key of keys) {
|
||||
try {
|
||||
result[key] = serialize(value[key], depth + 1);
|
||||
} catch (e) {
|
||||
result[key] = '[Serialization Error]';
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(value).length > 20) {
|
||||
result['[truncated]'] = `${Object.keys(value).length - 20} more keys...`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.stringify(serialize(obj, 0), null, 2);
|
||||
} catch (e) {
|
||||
return `[Serialization failed: ${e instanceof Error ? e.message : 'Unknown error'}]`;
|
||||
}
|
||||
}
|
||||
|
||||
interface DebugPanelProps {
|
||||
debugMessages: any[];
|
||||
addDebugMessage: (level: 'info' | 'warn' | 'error' | 'debug', message: string, data?: any) => void;
|
||||
sendIPCMessage: (messageType: string, data: any) => void;
|
||||
clearDebugMessages: () => void;
|
||||
ipcInitialized: boolean;
|
||||
@ -14,7 +69,6 @@ interface DebugPanelProps {
|
||||
|
||||
const DebugPanel: React.FC<DebugPanelProps> = ({
|
||||
debugMessages,
|
||||
addDebugMessage,
|
||||
sendIPCMessage,
|
||||
clearDebugMessages,
|
||||
ipcInitialized,
|
||||
@ -39,7 +93,7 @@ const DebugPanel: React.FC<DebugPanelProps> = ({
|
||||
? await tauriApi.path.join(dataDir, '.kbot-gui.json')
|
||||
: 'N/A';
|
||||
|
||||
addDebugMessage('info', 'System Info & Store Paths', {
|
||||
log.info('System Info & Store Paths', {
|
||||
platform: navigator.platform,
|
||||
userAgent: navigator.userAgent,
|
||||
isTauri: tauriApi.isTauri(),
|
||||
@ -50,7 +104,7 @@ const DebugPanel: React.FC<DebugPanelProps> = ({
|
||||
windowLocation: window.location.href
|
||||
});
|
||||
} catch (error) {
|
||||
addDebugMessage('error', 'Failed to get system info', { error: (error as Error).message });
|
||||
log.error('Failed to get system info', { error: (error as Error).message });
|
||||
}
|
||||
}}
|
||||
className="glass-button text-sm px-4 py-2 rounded-lg"
|
||||
@ -58,7 +112,7 @@ const DebugPanel: React.FC<DebugPanelProps> = ({
|
||||
Test Info
|
||||
</button>
|
||||
<button
|
||||
onClick={() => addDebugMessage('error', 'Test error message')}
|
||||
onClick={() => log.error('Test error message')}
|
||||
className="glass-button text-sm px-4 py-2 rounded-lg border-red-400/50 text-red-600 hover:bg-red-500/20"
|
||||
>
|
||||
Test Error
|
||||
@ -67,7 +121,7 @@ const DebugPanel: React.FC<DebugPanelProps> = ({
|
||||
onClick={() => sendIPCMessage('test-message', { content: 'Hello from GUI', timestamp: Date.now() })}
|
||||
className="glass-button text-sm px-4 py-2 rounded-lg border-blue-400/50 text-blue-600 hover:bg-blue-500/20"
|
||||
>
|
||||
Send IPC - DEPRECATED
|
||||
Send IPC
|
||||
</button>
|
||||
<button
|
||||
onClick={clearDebugMessages}
|
||||
@ -81,7 +135,7 @@ const DebugPanel: React.FC<DebugPanelProps> = ({
|
||||
<div className="glass-card max-h-96 overflow-y-auto">
|
||||
{debugMessages.length === 0 ? (
|
||||
<div className="p-4 text-center text-slate-500 dark:text-slate-400">
|
||||
No debug messages yet. Click the test buttons above to generate some.
|
||||
No debug messages yet.
|
||||
</div>
|
||||
) : (
|
||||
<div className="divide-y divide-slate-200/50 dark:divide-slate-700/50">
|
||||
@ -103,7 +157,7 @@ const DebugPanel: React.FC<DebugPanelProps> = ({
|
||||
<div className="text-sm text-slate-700 dark:text-slate-300 mb-1">{msg.message}</div>
|
||||
{msg.data && (
|
||||
<div className="text-xs text-slate-500 dark:text-slate-400 bg-slate-100/50 dark:bg-slate-800/50 p-2 rounded font-mono max-h-40 overflow-y-auto">
|
||||
{JSON.stringify(msg.data, null, 2)}
|
||||
{safeStringify(msg.data)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -36,31 +36,35 @@ export function useTauriListeners({
|
||||
let unlistenDeleteError: (() => void) | undefined;
|
||||
|
||||
const setupListeners = async () => {
|
||||
// Initialize the app using the centralized init system
|
||||
const initCallbacks: InitCallbacks = {
|
||||
setPrompt,
|
||||
setDst,
|
||||
setApiKey,
|
||||
setPrompts,
|
||||
setIpcInitialized
|
||||
};
|
||||
// Initialize APIs first
|
||||
const { isTauri: isTauriEnv } = await tauriApi.ensureTauriApi();
|
||||
|
||||
const initState = await initializeApp(initCallbacks);
|
||||
|
||||
if (!initState.isTauriEnv) {
|
||||
if (!isTauriEnv) {
|
||||
log.warn('Tauri APIs not available, running in browser mode.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up event listeners FIRST, before sending any requests
|
||||
const listeners = await Promise.all([
|
||||
tauriApi.listen(TauriEvent.CONFIG_RECEIVED, async (event: any) => {
|
||||
const data = event.payload;
|
||||
|
||||
// Complete initialization using the centralized system
|
||||
log.info('📨 Config received from backend');
|
||||
|
||||
// Process config and update state
|
||||
const initCallbacks: InitCallbacks = {
|
||||
setPrompt,
|
||||
setDst,
|
||||
setApiKey,
|
||||
setPrompts,
|
||||
setIpcInitialized
|
||||
};
|
||||
|
||||
try {
|
||||
await completeInitialization(data, initCallbacks);
|
||||
log.info('✅ App initialization completed');
|
||||
} catch (error) {
|
||||
log.error('Failed to complete initialization after config received', {
|
||||
log.error('Failed to complete initialization', {
|
||||
error: (error as Error).message
|
||||
});
|
||||
}
|
||||
@ -138,6 +142,16 @@ export function useTauriListeners({
|
||||
]);
|
||||
|
||||
[unlistenConfig, unlistenImage, unlistenError, unlistenComplete, unlistenDeleted, unlistenDeleteError] = listeners;
|
||||
|
||||
// Send config request after listeners are ready
|
||||
try {
|
||||
await tauriApi.requestConfigFromImages();
|
||||
log.info('📤 Config request sent');
|
||||
} catch (error) {
|
||||
log.error('Failed to request config', {
|
||||
error: (error as Error).message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
setupListeners();
|
||||
|
||||
@ -58,12 +58,72 @@ export function getLogState() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely serialize data to prevent circular references
|
||||
*/
|
||||
function safeStringify(obj: any, maxDepth = 3): any {
|
||||
const seen = new WeakSet();
|
||||
|
||||
function serialize(value: any, depth: number): any {
|
||||
if (depth > maxDepth) {
|
||||
return '[Max depth reached]';
|
||||
}
|
||||
|
||||
if (value === null || value === undefined) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value !== 'object') {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (seen.has(value)) {
|
||||
return '[Circular Reference]';
|
||||
}
|
||||
|
||||
seen.add(value);
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.slice(0, 10).map((item, index) => {
|
||||
if (index >= 10) return '[Truncated]';
|
||||
return serialize(item, depth + 1);
|
||||
});
|
||||
}
|
||||
|
||||
const result: any = {};
|
||||
const keys = Object.keys(value).slice(0, 20); // Limit keys
|
||||
|
||||
for (const key of keys) {
|
||||
try {
|
||||
result[key] = serialize(value[key], depth + 1);
|
||||
} catch (e) {
|
||||
result[key] = '[Serialization Error]';
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(value).length > 20) {
|
||||
result['[truncated]'] = `${Object.keys(value).length - 20} more keys...`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
return serialize(obj, 0);
|
||||
} catch (e) {
|
||||
return { error: 'Serialization failed', type: typeof obj };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to backend via IPC
|
||||
*/
|
||||
async function sendToBackend(message: LogMessage) {
|
||||
try {
|
||||
await tauriApi.addDebugMessage(message.message, message.level, message.data);
|
||||
// Safely serialize data to prevent circular reference issues
|
||||
const safeData = message.data ? safeStringify(message.data) : message.data;
|
||||
|
||||
await tauriApi.addDebugMessage(message.message, message.level, safeData);
|
||||
} catch (error) {
|
||||
console.warn('Failed to send log message to backend:', error);
|
||||
// Don't create infinite loop by logging this error
|
||||
@ -89,7 +149,17 @@ function log(level: LogLevel, message: string, data?: any) {
|
||||
debug: '🔍'
|
||||
}[level];
|
||||
|
||||
console[consoleMethod](`${prefix} [${timestamp}] ${message}`, data || '');
|
||||
// Safely log to console
|
||||
if (data) {
|
||||
try {
|
||||
const safeData = safeStringify(data);
|
||||
console[consoleMethod](`${prefix} [${timestamp}] ${message}`, safeData);
|
||||
} catch (e) {
|
||||
console[consoleMethod](`${prefix} [${timestamp}] ${message} [Data serialization failed]`);
|
||||
}
|
||||
} else {
|
||||
console[consoleMethod](`${prefix} [${timestamp}] ${message}`);
|
||||
}
|
||||
|
||||
// Store locally for UI
|
||||
logState.localMessages.push(logMessage);
|
||||
|
||||
@ -32,55 +32,53 @@ const apiInitializationPromise = (async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if we're in Tauri environment first
|
||||
if (typeof window !== 'undefined' && (window as any).__TAURI__) {
|
||||
console.log('🔍 Tauri environment detected, loading APIs...');
|
||||
|
||||
// Load all Tauri APIs in parallel for better performance
|
||||
const [
|
||||
windowApi,
|
||||
webviewApi,
|
||||
coreApi,
|
||||
eventApi,
|
||||
dialogApi,
|
||||
fsApi,
|
||||
httpApi,
|
||||
pathApi
|
||||
] = await Promise.all([
|
||||
import('@tauri-apps/api/window'),
|
||||
import('@tauri-apps/api/webview'),
|
||||
import('@tauri-apps/api/core'),
|
||||
import('@tauri-apps/api/event'),
|
||||
import('@tauri-apps/plugin-dialog'),
|
||||
import('@tauri-apps/plugin-fs'),
|
||||
import('@tauri-apps/plugin-http'),
|
||||
import('@tauri-apps/api/path')
|
||||
]);
|
||||
// Try to load Tauri APIs - if they fail, we're in browser mode
|
||||
console.log('🔍 Attempting to load Tauri APIs...');
|
||||
|
||||
// Load all Tauri APIs in parallel for better performance
|
||||
const [
|
||||
windowApi,
|
||||
webviewApi,
|
||||
coreApi,
|
||||
eventApi,
|
||||
dialogApi,
|
||||
fsApi,
|
||||
httpApi,
|
||||
pathApi
|
||||
] = await Promise.all([
|
||||
import('@tauri-apps/api/window'),
|
||||
import('@tauri-apps/api/webview'),
|
||||
import('@tauri-apps/api/core'),
|
||||
import('@tauri-apps/api/event'),
|
||||
import('@tauri-apps/plugin-dialog'),
|
||||
import('@tauri-apps/plugin-fs'),
|
||||
import('@tauri-apps/plugin-http'),
|
||||
import('@tauri-apps/api/path')
|
||||
]);
|
||||
|
||||
// Assign all APIs
|
||||
getCurrentWindow = windowApi.getCurrentWindow;
|
||||
getCurrentWebview = webviewApi.getCurrentWebview;
|
||||
invoke = coreApi.invoke;
|
||||
listen = eventApi.listen;
|
||||
open = dialogApi.open;
|
||||
save = dialogApi.save;
|
||||
readFile = fsApi.readFile;
|
||||
writeFile = fsApi.writeFile;
|
||||
readTextFile = fsApi.readTextFile;
|
||||
writeTextFile = fsApi.writeTextFile;
|
||||
BaseDirectory = fsApi.BaseDirectory;
|
||||
fetch = httpApi.fetch;
|
||||
appConfigDir = pathApi.appConfigDir;
|
||||
appDataDir = pathApi.appDataDir;
|
||||
join = pathApi.join;
|
||||
// Test if we can actually use the APIs (this will throw if not in Tauri)
|
||||
await windowApi.getCurrentWindow();
|
||||
|
||||
// Assign all APIs
|
||||
getCurrentWindow = windowApi.getCurrentWindow;
|
||||
getCurrentWebview = webviewApi.getCurrentWebview;
|
||||
invoke = coreApi.invoke;
|
||||
listen = eventApi.listen;
|
||||
open = dialogApi.open;
|
||||
save = dialogApi.save;
|
||||
readFile = fsApi.readFile;
|
||||
writeFile = fsApi.writeFile;
|
||||
readTextFile = fsApi.readTextFile;
|
||||
writeTextFile = fsApi.writeTextFile;
|
||||
BaseDirectory = fsApi.BaseDirectory;
|
||||
fetch = httpApi.fetch;
|
||||
appConfigDir = pathApi.appConfigDir;
|
||||
appDataDir = pathApi.appDataDir;
|
||||
join = pathApi.join;
|
||||
|
||||
isTauri = true;
|
||||
apiInitialized = true;
|
||||
console.log('✅ All Tauri APIs loaded successfully');
|
||||
} else {
|
||||
console.log('🌐 No Tauri environment detected, running in browser mode');
|
||||
isTauri = false;
|
||||
}
|
||||
isTauri = true;
|
||||
apiInitialized = true;
|
||||
console.log('✅ All Tauri APIs loaded successfully');
|
||||
} catch (e) {
|
||||
console.warn('❌ Failed to load Tauri APIs, falling back to browser mode:', e);
|
||||
isTauri = false;
|
||||
|
||||
@ -6,6 +6,15 @@ import App from "./App";
|
||||
// Check if we're running in Tauri environment
|
||||
const isTauri = typeof window !== 'undefined' && '__TAURI__' in window;
|
||||
|
||||
// Safe stringify for error handling
|
||||
function safeStringifyError(obj: any): string {
|
||||
try {
|
||||
return JSON.stringify(obj, null, 2);
|
||||
} catch (e) {
|
||||
return `[Serialization failed: ${e instanceof Error ? e.message : 'Unknown error'}] - Object type: ${typeof obj}`;
|
||||
}
|
||||
}
|
||||
|
||||
let invoke: any;
|
||||
|
||||
// Safe invoke function that works in both Tauri and browser environments
|
||||
@ -33,7 +42,7 @@ window.addEventListener('error', event => {
|
||||
colno: event.colno,
|
||||
error: event.error ? event.error.stack : 'No stack available',
|
||||
};
|
||||
safeInvoke('log_error_to_console', { error: `[JavaScript Error] ${JSON.stringify(errorPayload, null, 2)}` });
|
||||
safeInvoke('log_error_to_console', { error: `[JavaScript Error] ${safeStringifyError(errorPayload)}` });
|
||||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', event => {
|
||||
@ -43,7 +52,7 @@ window.addEventListener('unhandledrejection', event => {
|
||||
stack: reason.stack || 'No stack available',
|
||||
reason: String(reason)
|
||||
};
|
||||
safeInvoke('log_error_to_console', { error: `[Unhandled Promise Rejection] ${JSON.stringify(errorPayload, null, 2)}` });
|
||||
safeInvoke('log_error_to_console', { error: `[Unhandled Promise Rejection] ${safeStringifyError(errorPayload)}` });
|
||||
});
|
||||
|
||||
// Hijack console.log to send messages to the Rust backend (only in Tauri mode)
|
||||
@ -57,7 +66,7 @@ if (isTauri) {
|
||||
originalConsoleLog(...args);
|
||||
|
||||
// Format the arguments into a single string
|
||||
const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)).join(' ');
|
||||
const message = args.map(arg => typeof arg === 'object' ? safeStringifyError(arg) : String(arg)).join(' ');
|
||||
|
||||
// Send the message to the Rust backend (with delay to ensure invoke is loaded)
|
||||
setTimeout(() => {
|
||||
@ -70,7 +79,7 @@ if (isTauri) {
|
||||
originalConsoleError(...args);
|
||||
|
||||
// Format the arguments into a single string
|
||||
const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)).join(' ');
|
||||
const message = args.map(arg => typeof arg === 'object' ? safeStringifyError(arg) : String(arg)).join(' ');
|
||||
|
||||
// Send the message to the Rust backend (with delay to ensure invoke is loaded)
|
||||
setTimeout(() => {
|
||||
@ -83,7 +92,7 @@ if (isTauri) {
|
||||
originalConsoleWarn(...args);
|
||||
|
||||
// Format the arguments into a single string
|
||||
const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)).join(' ');
|
||||
const message = args.map(arg => typeof arg === 'object' ? safeStringifyError(arg) : String(arg)).join(' ');
|
||||
|
||||
// Send the message to the Rust backend (with delay to ensure invoke is loaded)
|
||||
setTimeout(() => {
|
||||
|
||||
@ -188,8 +188,10 @@ async function launchGuiAndGetPrompt(argv: any): Promise<string | null> {
|
||||
files: absoluteIncludes
|
||||
};
|
||||
|
||||
tauriProcess.stdin?.write(JSON.stringify(configResponse) + '\n');
|
||||
logger.info('📤 Sent config response to GUI');
|
||||
const jsonString = JSON.stringify(configResponse);
|
||||
logger.info('📤 About to write to stdin:', jsonString);
|
||||
tauriProcess.stdin?.write(jsonString + '\n');
|
||||
logger.info('📤 Sent config response to GUI', configResponse);
|
||||
|
||||
// Send image data
|
||||
for (const imagePath of absoluteIncludes) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user