use crate::command::chat::app::types::{AskQuestion, AskRequest};
use crate::command::chat::oneshot::confirm::*;
use crate::command::chat::oneshot::display::box_width;
use colored::Colorize;
use crossterm::event::{self, Event, KeyCode};
use crossterm::terminal;
use std::io;
use std::sync::mpsc;
pub(crate) fn spawn_ask_handler(ask_rx: mpsc::Receiver<AskRequest>) {
std::thread::spawn(move || {
while let Ok(req) = ask_rx.recv() {
handle_ask_request(&req);
}
});
}
fn handle_ask_request(req: &AskRequest) {
let mut answers = serde_json::Map::new();
for q in &req.questions {
let bw = box_width();
let title = if q.header.is_empty() {
"选择".to_string()
} else {
q.header.clone()
};
let question_lines = wrap_text(&q.question, bw.saturating_sub(6).max(1));
if q.multi_select {
let answer = handle_multi_select(q, &title, &question_lines, bw);
println!(" → {}", answer.green());
answers.insert(q.header.clone(), serde_json::Value::String(answer));
} else {
let answer = handle_single_select(q, &title, &question_lines, bw);
println!(" → {}", answer.green());
answers.insert(q.header.clone(), serde_json::Value::String(answer));
}
}
let response =
serde_json::to_string(&serde_json::json!({ "answers": answers })).unwrap_or_default();
let _ = req.response_tx.send(response);
}
fn handle_multi_select(
q: &AskQuestion,
title: &str,
question_lines: &[String],
bw: usize,
) -> String {
let mut selected = vec![false; q.options.len()];
let mut cursor_pos: usize = 0;
let total_lines = (1 + question_lines.len() + 1 + q.options.len() * 2 + 1 + 1 + 1) as u16;
let draw_multi = |stdout: &mut io::Stdout,
cursor_pos: usize,
selected: &[bool],
first: bool|
-> io::Result<()> {
if !first {
clear_drawn_lines(stdout, total_lines)?;
}
draw_top_border(stdout, bw, title)?;
for line in question_lines {
draw_content_line(stdout, line)?;
}
draw_empty_line(stdout)?;
for (i, opt) in q.options.iter().enumerate() {
if cursor_pos == i {
draw_multi_selected_option(stdout, selected[i], &opt.label)?;
} else {
draw_multi_unselected_option(stdout, selected[i], &opt.label)?;
}
draw_option_description(stdout, &opt.description)?;
}
draw_empty_line(stdout)?;
draw_hint_line(stdout, "• ↑↓ 移动 Space 切换 Enter 确认")?;
draw_bottom_border(stdout, bw)?;
Ok(())
};
let _ = terminal::enable_raw_mode();
let mut stdout = io::stdout();
let _ = draw_multi(&mut stdout, cursor_pos, &selected, true);
loop {
if let Ok(Event::Key(key)) = event::read() {
match key.code {
KeyCode::Up | KeyCode::Char('k') => {
cursor_pos = cursor_pos.saturating_sub(1);
}
KeyCode::Down | KeyCode::Char('j') => {
if cursor_pos + 1 < q.options.len() {
cursor_pos += 1;
}
}
KeyCode::Char(' ') => {
selected[cursor_pos] = !selected[cursor_pos];
}
KeyCode::Enter | KeyCode::Esc => break,
_ => continue,
}
let _ = draw_multi(&mut stdout, cursor_pos, &selected, false);
}
}
let _ = terminal::disable_raw_mode();
{
let _ = clear_drawn_lines(&mut stdout, total_lines);
}
let result: Vec<String> = q
.options
.iter()
.zip(selected.iter())
.filter(|(_, s)| **s)
.map(|(o, _)| o.label.clone())
.collect();
if result.is_empty() {
"(无选择)".to_string()
} else {
result.join(", ")
}
}
fn handle_single_select(
q: &AskQuestion,
title: &str,
question_lines: &[String],
bw: usize,
) -> String {
let mut cursor_pos: usize = 0;
let total_lines = (1 + question_lines.len() + 1 + q.options.len() * 2 + 1 + 1 + 1) as u16;
let draw_single = |stdout: &mut io::Stdout, cursor_pos: usize, first: bool| -> io::Result<()> {
if !first {
clear_drawn_lines(stdout, total_lines)?;
}
draw_top_border(stdout, bw, title)?;
for line in question_lines {
draw_content_line(stdout, line)?;
}
draw_empty_line(stdout)?;
for (i, opt) in q.options.iter().enumerate() {
if cursor_pos == i {
draw_selected_option(stdout, &opt.label)?;
} else {
draw_unselected_option(stdout, &opt.label)?;
}
draw_option_description(stdout, &opt.description)?;
}
draw_empty_line(stdout)?;
draw_hint_line(stdout, "• ↑↓ 移动 Enter 确认")?;
draw_bottom_border(stdout, bw)?;
Ok(())
};
let _ = terminal::enable_raw_mode();
let mut stdout = io::stdout();
let _ = draw_single(&mut stdout, cursor_pos, true);
loop {
if let Ok(Event::Key(key)) = event::read() {
match key.code {
KeyCode::Up | KeyCode::Char('k') => {
cursor_pos = cursor_pos.saturating_sub(1);
}
KeyCode::Down | KeyCode::Char('j') => {
if cursor_pos + 1 < q.options.len() {
cursor_pos += 1;
}
}
KeyCode::Enter | KeyCode::Esc => break,
_ => continue,
}
let _ = draw_single(&mut stdout, cursor_pos, false);
}
}
let _ = terminal::disable_raw_mode();
{
let _ = clear_drawn_lines(&mut stdout, total_lines);
}
q.options
.get(cursor_pos)
.map(|o| o.label.clone())
.unwrap_or_default()
}
fn wrap_text(text: &str, max_chars: usize) -> Vec<String> {
if text.is_empty() || max_chars == 0 {
return vec![];
}
let chars: Vec<char> = text.chars().collect();
let mut lines = Vec::with_capacity((chars.len() / max_chars).max(1) + 1);
let mut start = 0;
while start < chars.len() {
let end = (start + max_chars).min(chars.len());
let line: String = chars[start..end].iter().collect();
lines.push(line);
start = end;
}
lines
}