All checks were successful
CI never ran clippy on the agent crate (the build-server clippy job is Linux-only and can't compile the Windows agent; build-agent only runs cargo build), so 77 clippy -D-warnings errors had accumulated. Behavior-preserving cleanup, code-reviewed APPROVED, locally verified (cargo clippy --workspace --all-targets --all-features -- -D warnings exits 0; cargo test --workspace = 57 passed). - let _ = on Win32 resource-teardown BOOL returns (gdi.rs); fallible BitBlt/GetDIBits stay error-handled - removed unused imports/vars; idiom fixes (div_ceil, is_null, transmute annotations, match collapsing, useless_conversion) - #[allow(dead_code)] + comment on genuine Task-6/7 scaffolding (vk consts, SpecialKey emission, SAS mgmt API, modifier tracking, GDI frame-diff fields) - Cargo.lock: cargo pruned ~147 stale transitive entries (no version changes) Follow-up: add cargo clippy -D warnings to the build-agent CI job so the agent crate stays clippy-clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
173 lines
4.7 KiB
Rust
173 lines
4.7 KiB
Rust
//! Chat window for the agent
|
|
//!
|
|
//! Provides a simple chat interface for communication between
|
|
//! the technician and the end user.
|
|
|
|
use std::sync::mpsc::{self, Receiver, Sender};
|
|
use std::sync::{Arc, Mutex};
|
|
use std::thread;
|
|
use tracing::info;
|
|
#[cfg(not(windows))]
|
|
use tracing::warn;
|
|
|
|
#[cfg(windows)]
|
|
use windows::core::PCWSTR;
|
|
#[cfg(windows)]
|
|
use windows::Win32::UI::WindowsAndMessaging::*;
|
|
|
|
/// A chat message
|
|
#[derive(Debug, Clone)]
|
|
pub struct ChatMessage {
|
|
pub id: String,
|
|
pub sender: String,
|
|
pub content: String,
|
|
pub timestamp: i64,
|
|
}
|
|
|
|
/// Commands that can be sent to the chat window
|
|
// Show/Hide/Close are part of the chat control API but not yet driven by the session loop.
|
|
#[derive(Debug)]
|
|
pub enum ChatCommand {
|
|
#[allow(dead_code)]
|
|
Show,
|
|
#[allow(dead_code)]
|
|
Hide,
|
|
AddMessage(ChatMessage),
|
|
#[allow(dead_code)]
|
|
Close,
|
|
}
|
|
|
|
/// Controller for the chat window
|
|
pub struct ChatController {
|
|
command_tx: Sender<ChatCommand>,
|
|
message_rx: Arc<Mutex<Receiver<ChatMessage>>>,
|
|
_handle: thread::JoinHandle<()>,
|
|
}
|
|
|
|
impl ChatController {
|
|
/// Create a new chat controller (spawns chat window thread)
|
|
#[cfg(windows)]
|
|
pub fn new() -> Option<Self> {
|
|
let (command_tx, command_rx) = mpsc::channel::<ChatCommand>();
|
|
let (message_tx, message_rx) = mpsc::channel::<ChatMessage>();
|
|
|
|
let handle = thread::spawn(move || {
|
|
run_chat_window(command_rx, message_tx);
|
|
});
|
|
|
|
Some(Self {
|
|
command_tx,
|
|
message_rx: Arc::new(Mutex::new(message_rx)),
|
|
_handle: handle,
|
|
})
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
pub fn new() -> Option<Self> {
|
|
warn!("Chat window not supported on this platform");
|
|
None
|
|
}
|
|
|
|
/// Show the chat window
|
|
#[allow(dead_code)]
|
|
pub fn show(&self) {
|
|
let _ = self.command_tx.send(ChatCommand::Show);
|
|
}
|
|
|
|
/// Hide the chat window
|
|
#[allow(dead_code)]
|
|
pub fn hide(&self) {
|
|
let _ = self.command_tx.send(ChatCommand::Hide);
|
|
}
|
|
|
|
/// Add a message to the chat window
|
|
pub fn add_message(&self, msg: ChatMessage) {
|
|
let _ = self.command_tx.send(ChatCommand::AddMessage(msg));
|
|
}
|
|
|
|
/// Check for outgoing messages from the user
|
|
pub fn poll_outgoing(&self) -> Option<ChatMessage> {
|
|
if let Ok(rx) = self.message_rx.lock() {
|
|
rx.try_recv().ok()
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Close the chat window
|
|
#[allow(dead_code)]
|
|
pub fn close(&self) {
|
|
let _ = self.command_tx.send(ChatCommand::Close);
|
|
}
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
fn run_chat_window(command_rx: Receiver<ChatCommand>, _message_tx: Sender<ChatMessage>) {
|
|
info!("Starting chat window thread");
|
|
|
|
// For now, we'll use a simple message box approach
|
|
// A full implementation would create a proper window with a text input
|
|
|
|
// Process commands
|
|
loop {
|
|
match command_rx.recv() {
|
|
Ok(ChatCommand::Show) => {
|
|
info!("Chat window: Show requested");
|
|
// Show a simple notification that chat is available
|
|
}
|
|
Ok(ChatCommand::Hide) => {
|
|
info!("Chat window: Hide requested");
|
|
}
|
|
Ok(ChatCommand::AddMessage(msg)) => {
|
|
info!("Chat message received: {} - {}", msg.sender, msg.content);
|
|
|
|
// Show the message to the user via a message box (simple implementation)
|
|
let title = format!("Message from {}", msg.sender);
|
|
let content = msg.content.clone();
|
|
|
|
// Spawn a thread to show the message box (non-blocking)
|
|
thread::spawn(move || {
|
|
show_message_box_internal(&title, &content);
|
|
});
|
|
}
|
|
Ok(ChatCommand::Close) => {
|
|
info!("Chat window: Close requested");
|
|
break;
|
|
}
|
|
Err(_) => {
|
|
// Channel closed
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
fn show_message_box_internal(title: &str, message: &str) {
|
|
use std::ffi::OsStr;
|
|
use std::os::windows::ffi::OsStrExt;
|
|
|
|
let title_wide: Vec<u16> = OsStr::new(title)
|
|
.encode_wide()
|
|
.chain(std::iter::once(0))
|
|
.collect();
|
|
let message_wide: Vec<u16> = OsStr::new(message)
|
|
.encode_wide()
|
|
.chain(std::iter::once(0))
|
|
.collect();
|
|
|
|
unsafe {
|
|
MessageBoxW(
|
|
None,
|
|
PCWSTR(message_wide.as_ptr()),
|
|
PCWSTR(title_wide.as_ptr()),
|
|
MB_OK | MB_ICONINFORMATION | MB_TOPMOST | MB_SETFOREGROUND,
|
|
);
|
|
}
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
fn run_chat_window(_command_rx: Receiver<ChatCommand>, _message_tx: Sender<ChatMessage>) {
|
|
// No-op on non-Windows
|
|
}
|