use egui::{Ui, Color32, RichText, TextEdit, Button, ScrollArea}; use crate::config::AppSettings; use crate::api::call_llm; pub enum OutputStatus { Waiting, Connecting, Completed, Error(String), Stopped, } pub struct MainPage { pub input_text: String, pub output_text: String, pub status: OutputStatus, pub selected_prompt_index: usize, pub is_loading: bool, } impl Default for MainPage { fn default() -> Self { Self { input_text: String::new(), output_text: String::new(), status: OutputStatus::Waiting, selected_prompt_index: 0, is_loading: false, } } } impl MainPage { pub fn new() -> Self { Self::default() } pub fn ui(&mut self, ui: &mut Ui, settings: &AppSettings, ctx: &egui::Context) { ScrollArea::vertical().show(ui, |ui| { self.render_content(ui, settings, ctx); }); } fn render_content(&mut self, ui: &mut Ui, settings: &AppSettings, ctx: &egui::Context) { ui.add_space(16.0); ui.label(RichText::new("快速操作").size(11.0).color(Color32::GRAY)); ui.add_space(6.0); ui.horizontal(|ui| { let buttons = vec![ ("🔍", "检查错别字"), ("📋", "总结"), ("🌐", "翻译"), ("✨", "润色"), ]; for (emoji, name) in buttons { let btn = Button::new(RichText::new(emoji).size(16.0)) .fill(Color32::from_rgb(245, 245, 247)) .stroke(egui::Stroke::new(1.0, Color32::from_rgb(210, 210, 220))) .rounding(6.0) .min_size(egui::vec2(36.0, 36.0)); if ui.add(btn).on_hover_text(name).clicked() { self.select_prompt_by_name(name, settings, ctx); } } }); ui.add_space(14.0); ui.label(RichText::new("提示词").size(11.0).color(Color32::GRAY)); ui.add_space(6.0); let selected_text = if self.selected_prompt_index == 0 { "无系统提示词".to_string() } else if self.selected_prompt_index <= settings.prompt_configs.len() { settings.prompt_configs[self.selected_prompt_index - 1].title.clone() } else { "无系统提示词".to_string() }; egui::ComboBox::from_id_salt("prompt_selector") .selected_text(&selected_text) .show_ui(ui, |ui| { ui.selectable_value(&mut self.selected_prompt_index, 0, "无系统提示词"); for (i, prompt) in settings.prompt_configs.iter().enumerate() { ui.selectable_value(&mut self.selected_prompt_index, i + 1, &prompt.title); } }); ui.separator(); let prompt_content = if self.selected_prompt_index == 0 { "无特殊指令".to_string() } else if self.selected_prompt_index <= settings.prompt_configs.len() { settings.prompt_configs[self.selected_prompt_index - 1].content.clone() } else { "无特殊指令".to_string() }; ui.label(RichText::new(&prompt_content).size(11.0).color(Color32::GRAY)); ui.add_space(14.0); ui.label(RichText::new("优化结果").size(11.0).color(Color32::GRAY)); ui.add_space(6.0); let status_text = match &self.status { OutputStatus::Waiting => "等待发送".to_string(), OutputStatus::Connecting => "连接中…".to_string(), OutputStatus::Completed => "已完成".to_string(), OutputStatus::Error(_) => "发生错误".to_string(), OutputStatus::Stopped => "已停止".to_string(), }; let status_color = match &self.status { OutputStatus::Waiting => Color32::from_rgb(100, 100, 255), OutputStatus::Connecting => Color32::from_rgb(255, 165, 0), OutputStatus::Completed => Color32::from_rgb(0, 180, 0), OutputStatus::Error(_) => Color32::RED, OutputStatus::Stopped => Color32::GRAY, }; ui.label(RichText::new(&status_text).size(11.0).color(status_color)); let card_frame = egui::Frame::none() .fill(ui.style().visuals.widgets.inactive.bg_fill) .stroke(egui::Stroke::new(1.0, Color32::from_rgb(200, 200, 210))) .inner_margin(egui::Margin::same(12.0)) .rounding(6.0); card_frame.show(ui, |ui: &mut Ui| { ui.add_sized([ui.available_width(), 2.0], egui::Separator::default()); ui.add_space(8.0); if self.output_text.is_empty() { ui.label(RichText::new("发送消息后结果将在此显示").size(13.0).color(Color32::GRAY)); } else { ui.add_sized( [ui.available_width(), 150.0], TextEdit::multiline(&mut self.output_text.clone()) .desired_width(f32::INFINITY) .desired_rows(6), ); } ui.add_space(8.0); ui.horizontal(|ui: &mut Ui| { let copy_btn = Button::new("复制结果") .fill(Color32::TRANSPARENT) .stroke(egui::Stroke::new(1.0, Color32::from_rgb(100, 100, 255))) .rounding(4.0); if ui.add(copy_btn).clicked() && !self.output_text.is_empty() { self.copy_to_clipboard(); } }); }); ui.add_space(16.0); } pub fn send_request(&mut self, settings: &AppSettings, ctx: egui::Context) { if self.input_text.trim().is_empty() || self.is_loading { return; } self.status = OutputStatus::Connecting; self.output_text = "正在生成...".to_string(); self.is_loading = true; let prompt = if self.selected_prompt_index == 0 { None } else if self.selected_prompt_index <= settings.prompt_configs.len() { Some(settings.prompt_configs[self.selected_prompt_index - 1].content.clone()) } else { None }; let input = self.input_text.clone(); let settings = settings.clone(); let handle = std::thread::spawn(move || { let result = call_llm(&settings, input, prompt); ctx.request_repaint(); result }); std::thread::spawn(move || { let _ = handle.join(); }); } fn select_prompt_by_name(&mut self, name: &str, settings: &AppSettings, ctx: &egui::Context) { let mut found = false; for (i, prompt) in settings.prompt_configs.iter().enumerate() { if prompt.title == name { self.selected_prompt_index = i + 1; found = true; break; } } if !self.input_text.is_empty() && found { self.send_request(settings, ctx.clone()); } } fn copy_to_clipboard(&self) { if let Ok(mut clipboard) = arboard::Clipboard::new() { let _ = clipboard.set_text(&self.output_text); } } }