218 lines
7.2 KiB
Rust
218 lines
7.2 KiB
Rust
|
|
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);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|