优化Rust桌面应用UI:3卡片布局、中文字体、控制台隐藏
This commit is contained in:
@@ -22,3 +22,6 @@ lto = true
|
|||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
strip = true
|
strip = true
|
||||||
|
|
||||||
|
[target.x86_64-pc-windows-gnu]
|
||||||
|
rustflags = ["-C", "link-args=-Wl,--subsystem,windows"]
|
||||||
|
|||||||
@@ -31,14 +31,15 @@ pub struct FlomoAiApp {
|
|||||||
input_text: String,
|
input_text: String,
|
||||||
selected_prompt_index: usize,
|
selected_prompt_index: usize,
|
||||||
char_count: usize,
|
char_count: usize,
|
||||||
|
|
||||||
model_displays: Vec<ModelDisplay>,
|
model_displays: Vec<ModelDisplay>,
|
||||||
pending_results: Option<std::thread::JoinHandle<Vec<ModelResult>>>,
|
pending_results: Option<std::thread::JoinHandle<Vec<ModelResult>>>,
|
||||||
|
pending_tests: Vec<(usize, std::thread::JoinHandle<Result<String, String>>)>,
|
||||||
|
|
||||||
settings_selected_theme: ThemeMode,
|
settings_selected_theme: ThemeMode,
|
||||||
new_prompt_title: String,
|
new_prompt_title: String,
|
||||||
new_prompt_content: String,
|
new_prompt_content: String,
|
||||||
|
|
||||||
current_page: Page,
|
current_page: Page,
|
||||||
theme_dirty: bool,
|
theme_dirty: bool,
|
||||||
}
|
}
|
||||||
@@ -65,6 +66,7 @@ impl FlomoAiApp {
|
|||||||
char_count: 0,
|
char_count: 0,
|
||||||
model_displays,
|
model_displays,
|
||||||
pending_results: None,
|
pending_results: None,
|
||||||
|
pending_tests: Vec::new(),
|
||||||
settings_selected_theme: ThemeMode::Light,
|
settings_selected_theme: ThemeMode::Light,
|
||||||
new_prompt_title: String::new(),
|
new_prompt_title: String::new(),
|
||||||
new_prompt_content: String::new(),
|
new_prompt_content: String::new(),
|
||||||
@@ -73,6 +75,28 @@ impl FlomoAiApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn poll_test_results(&mut self, ctx: &egui::Context) {
|
||||||
|
for (index, handle) in self.pending_tests.drain(..) {
|
||||||
|
match handle.join() {
|
||||||
|
Ok(Ok(response)) => {
|
||||||
|
let msg = if response.len() > 30 {
|
||||||
|
format!("成功: {}...", &response[..30])
|
||||||
|
} else {
|
||||||
|
format!("成功: {}", response)
|
||||||
|
};
|
||||||
|
self.model_displays[index].test_status = msg;
|
||||||
|
}
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
self.model_displays[index].test_status = format!("失败: {}", e);
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
self.model_displays[index].test_status = "测试线程错误".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.request_repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn poll_results(&mut self, ctx: &egui::Context) {
|
fn poll_results(&mut self, ctx: &egui::Context) {
|
||||||
if let Some(handle) = self.pending_results.take() {
|
if let Some(handle) = self.pending_results.take() {
|
||||||
if handle.is_finished() {
|
if handle.is_finished() {
|
||||||
@@ -184,6 +208,7 @@ impl FlomoAiApp {
|
|||||||
impl eframe::App for FlomoAiApp {
|
impl eframe::App for FlomoAiApp {
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
self.poll_results(ctx);
|
self.poll_results(ctx);
|
||||||
|
self.poll_test_results(ctx);
|
||||||
|
|
||||||
if self.theme_dirty {
|
if self.theme_dirty {
|
||||||
let theme = AppTheme::from_mode(self.settings.theme_config.mode);
|
let theme = AppTheme::from_mode(self.settings.theme_config.mode);
|
||||||
@@ -287,79 +312,77 @@ impl FlomoAiApp {
|
|||||||
ui.label(egui::RichText::new("各模型返回结果").size(11.0).color(egui::Color32::GRAY));
|
ui.label(egui::RichText::new("各模型返回结果").size(11.0).color(egui::Color32::GRAY));
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let available_width = ui.available_width();
|
let total_width = ui.available_width();
|
||||||
let column_width = (available_width - 16.0) / 3.0;
|
let spacing = 8.0;
|
||||||
|
let column_width = (total_width - spacing * 2.0) / 3.0;
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal_wrapped(|ui| {
|
||||||
ui.set_height(200.0);
|
ui.set_width(total_width);
|
||||||
for (i, display) in self.model_displays.iter().enumerate() {
|
|
||||||
if i > 0 {
|
for d in self.model_displays.iter() {
|
||||||
ui.add_space(8.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.set_min_width(column_width);
|
|
||||||
|
|
||||||
egui::Frame::none()
|
egui::Frame::none()
|
||||||
.fill(ui.style().visuals.widgets.inactive.bg_fill)
|
.fill(if d.enabled {
|
||||||
|
ui.style().visuals.widgets.inactive.bg_fill
|
||||||
|
} else {
|
||||||
|
egui::Color32::from_rgb(235, 235, 240)
|
||||||
|
})
|
||||||
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(180, 180, 200)))
|
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(180, 180, 200)))
|
||||||
.rounding(8.0)
|
.rounding(8.0)
|
||||||
.inner_margin(egui::Margin::same(10.0))
|
.inner_margin(egui::Margin::same(8.0))
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.set_min_width(column_width - 20.0);
|
ui.set_min_width(column_width - 16.0);
|
||||||
|
|
||||||
let enabled_color = if display.enabled {
|
let enabled_color = if d.enabled {
|
||||||
egui::Color32::from_rgb(80, 80, 220)
|
egui::Color32::from_rgb(80, 80, 220)
|
||||||
} else {
|
} else {
|
||||||
egui::Color32::GRAY
|
egui::Color32::GRAY
|
||||||
};
|
};
|
||||||
|
|
||||||
let enabled_dot = if display.enabled { "●" } else { "○" };
|
let enabled_dot = if d.enabled { "●" } else { "○" };
|
||||||
ui.label(egui::RichText::new(format!("{} {}", enabled_dot, display.name))
|
ui.label(egui::RichText::new(format!("{} {}", enabled_dot, d.name))
|
||||||
.size(13.0).strong().color(enabled_color));
|
.size(13.0).strong().color(enabled_color));
|
||||||
|
|
||||||
ui.label(egui::RichText::new(&display.model).size(10.0).color(egui::Color32::GRAY));
|
ui.label(egui::RichText::new(&d.model).size(10.0).color(egui::Color32::GRAY));
|
||||||
|
|
||||||
ui.add_space(6.0);
|
ui.add_space(4.0);
|
||||||
ui.add_sized([ui.available_width(), 1.0], egui::Separator::default());
|
ui.add_sized([ui.available_width(), 1.0], egui::Separator::default());
|
||||||
|
|
||||||
ui.add_space(6.0);
|
let status_color = match &d.status {
|
||||||
|
|
||||||
let status_color = match &display.status {
|
|
||||||
ModelStatus::Waiting => egui::Color32::GRAY,
|
ModelStatus::Waiting => egui::Color32::GRAY,
|
||||||
ModelStatus::Loading => egui::Color32::from_rgb(255, 140, 0),
|
ModelStatus::Loading => egui::Color32::from_rgb(255, 140, 0),
|
||||||
ModelStatus::Completed => egui::Color32::from_rgb(0, 160, 0),
|
ModelStatus::Completed => egui::Color32::from_rgb(0, 160, 0),
|
||||||
ModelStatus::Error(_) => egui::Color32::RED,
|
ModelStatus::Error(_) => egui::Color32::RED,
|
||||||
};
|
};
|
||||||
|
|
||||||
ui.label(egui::RichText::new(match &display.status {
|
ui.label(egui::RichText::new(match &d.status {
|
||||||
ModelStatus::Waiting => "● 就绪",
|
ModelStatus::Waiting => "● 就绪",
|
||||||
ModelStatus::Loading => "◐ 生成中...",
|
ModelStatus::Loading => "◐ 生成中...",
|
||||||
ModelStatus::Completed => "✓ 完成",
|
ModelStatus::Completed => "✓ 完成",
|
||||||
ModelStatus::Error(_) => "✗ 错误",
|
ModelStatus::Error(_) => "✗ 错误",
|
||||||
}).size(10.0).color(status_color));
|
}).size(10.0).color(status_color));
|
||||||
|
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
|
|
||||||
if display.result.is_empty() {
|
if d.result.is_empty() {
|
||||||
ui.label(egui::RichText::new("等待结果...").size(11.0).color(egui::Color32::GRAY));
|
ui.label(egui::RichText::new("等待结果...").size(11.0).color(egui::Color32::GRAY));
|
||||||
} else {
|
} else {
|
||||||
let mut result_text = display.result.clone();
|
let mut result_text = d.result.clone();
|
||||||
ui.add_sized(
|
ui.add_sized(
|
||||||
[ui.available_width(), 120.0],
|
[ui.available_width(), 100.0],
|
||||||
egui::TextEdit::multiline(&mut result_text)
|
egui::TextEdit::multiline(&mut result_text)
|
||||||
.desired_rows(5)
|
.desired_rows(4)
|
||||||
.frame(false),
|
.frame(false),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.add_space(6.0);
|
ui.add_space(4.0);
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
if ui.small_button("复制").clicked() && !d.result.is_empty() {
|
||||||
if ui.small_button("复制").clicked() && !display.result.is_empty() {
|
self.copy_to_clipboard(&d.result);
|
||||||
self.copy_to_clipboard(&display.result);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ui.add_space(spacing);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -380,75 +403,77 @@ impl FlomoAiApp {
|
|||||||
|
|
||||||
ScrollArea::vertical().show(ui, |ui| {
|
ScrollArea::vertical().show(ui, |ui| {
|
||||||
for i in 0..self.model_displays.len() {
|
for i in 0..self.model_displays.len() {
|
||||||
let enabled = self.model_displays[i].enabled;
|
|
||||||
let name = self.model_displays[i].name.clone();
|
|
||||||
let model = self.model_displays[i].model.clone();
|
|
||||||
|
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
self.model_displays[i].enabled ^= ui.checkbox(&mut self.model_displays[i].enabled, "").changed();
|
ui.checkbox(&mut self.model_displays[i].enabled, "");
|
||||||
ui.label(egui::RichText::new(&format!("模型 {} 配置", i + 1)).size(14.0).strong());
|
let label = if self.model_displays[i].enabled {
|
||||||
|
format!("{} ({})", self.model_displays[i].name, self.model_displays[i].model)
|
||||||
|
} else {
|
||||||
|
format!("{} (已禁用)", self.model_displays[i].name)
|
||||||
|
};
|
||||||
|
ui.label(egui::RichText::new(&label).size(14.0).strong());
|
||||||
});
|
});
|
||||||
|
|
||||||
if !enabled {
|
ui.add_space(4.0);
|
||||||
ui.label(egui::RichText::new("已禁用").size(11.0).color(egui::Color32::GRAY));
|
|
||||||
} else {
|
ui.label("名称");
|
||||||
ui.label("名称");
|
ui.text_edit_singleline(&mut self.model_displays[i].name);
|
||||||
ui.text_edit_singleline(&mut self.model_displays[i].name);
|
|
||||||
|
ui.add_space(4.0);
|
||||||
ui.add_space(4.0);
|
|
||||||
|
let mut base_url = self.settings.llm_configs.models.get(i)
|
||||||
let mut base_url = self.settings.llm_configs.models.get(i)
|
.map(|m| m.base_url.clone())
|
||||||
.map(|m| m.base_url.clone())
|
.unwrap_or_default();
|
||||||
.unwrap_or_default();
|
ui.label("API Base URL");
|
||||||
ui.label("API Base URL");
|
ui.text_edit_singleline(&mut base_url);
|
||||||
ui.text_edit_singleline(&mut base_url);
|
if i < self.settings.llm_configs.models.len() {
|
||||||
if i < self.settings.llm_configs.models.len() {
|
self.settings.llm_configs.models[i].base_url = base_url.clone();
|
||||||
self.settings.llm_configs.models[i].base_url = base_url.clone();
|
}
|
||||||
}
|
|
||||||
|
ui.add_space(4.0);
|
||||||
ui.add_space(4.0);
|
|
||||||
|
let mut api_key = self.settings.llm_configs.models.get(i)
|
||||||
let mut api_key = self.settings.llm_configs.models.get(i)
|
.map(|m| m.api_key.clone())
|
||||||
.map(|m| m.api_key.clone())
|
.unwrap_or_default();
|
||||||
.unwrap_or_default();
|
ui.label("API Key");
|
||||||
ui.label("API Key");
|
ui.text_edit_singleline(&mut api_key);
|
||||||
ui.text_edit_singleline(&mut api_key);
|
if i < self.settings.llm_configs.models.len() {
|
||||||
if i < self.settings.llm_configs.models.len() {
|
self.settings.llm_configs.models[i].api_key = api_key.clone();
|
||||||
self.settings.llm_configs.models[i].api_key = api_key.clone();
|
}
|
||||||
}
|
|
||||||
|
ui.add_space(4.0);
|
||||||
ui.add_space(4.0);
|
|
||||||
|
ui.label("Model");
|
||||||
ui.label("Model");
|
ui.text_edit_singleline(&mut self.model_displays[i].model);
|
||||||
ui.text_edit_singleline(&mut self.model_displays[i].model);
|
if i < self.settings.llm_configs.models.len() {
|
||||||
|
self.settings.llm_configs.models[i].model = self.model_displays[i].model.clone();
|
||||||
ui.add_space(6.0);
|
}
|
||||||
|
|
||||||
let test_status = self.model_displays[i].test_status.clone();
|
ui.add_space(6.0);
|
||||||
let test_btn = egui::Button::new("测试")
|
|
||||||
.fill(egui::Color32::from_rgb(100, 100, 255))
|
let test_status = self.model_displays[i].test_status.clone();
|
||||||
.rounding(4.0)
|
let test_btn = egui::Button::new("测试")
|
||||||
.min_size(egui::vec2(60.0, 28.0));
|
.fill(egui::Color32::from_rgb(100, 100, 255))
|
||||||
|
.rounding(4.0)
|
||||||
|
.min_size(egui::vec2(60.0, 28.0));
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
if ui.add(test_btn).clicked() {
|
if ui.add(test_btn).clicked() {
|
||||||
if let Some(config) = self.settings.llm_configs.models.get(i).cloned() {
|
if let Some(config) = self.settings.llm_configs.models.get(i).cloned() {
|
||||||
self.model_displays[i].test_status = "测试中...".to_string();
|
self.model_displays[i].test_status = "测试中...".to_string();
|
||||||
let headers = self.settings.header_configs.clone();
|
let headers = self.settings.header_configs.clone();
|
||||||
let ctx_clone = ctx.clone();
|
|
||||||
let display_index = i;
|
let handle = std::thread::spawn(move || {
|
||||||
|
test_single_llm(&config, &headers)
|
||||||
std::thread::spawn(move || {
|
|
||||||
let result = test_single_llm(&config, &headers);
|
|
||||||
ctx_clone.request_repaint();
|
|
||||||
});
|
});
|
||||||
|
self.pending_tests.push((i, handle));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.label(egui::RichText::new(&test_status).size(11.0).color(egui::Color32::GRAY));
|
ui.label(egui::RichText::new(&test_status).size(11.0).color(egui::Color32::GRAY));
|
||||||
}
|
});
|
||||||
|
|
||||||
if i < self.model_displays.len() - 1 {
|
if i < self.model_displays.len() - 1 {
|
||||||
ui.separator();
|
ui.separator();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user