From 8cf54f3346369191dd071343f6de53e4b45741dd Mon Sep 17 00:00:00 2001 From: zxq5 Date: Sun, 10 Aug 2025 00:55:02 +0100 Subject: [PATCH] moved ai assistant to editor side panel --- .gitignore | 1 + src/editors/content_editor.rs | 59 +++----- src/llm_integration/content_llm.rs | 209 +++++++++++------------------ 3 files changed, 94 insertions(+), 175 deletions(-) diff --git a/.gitignore b/.gitignore index 44fa581..8b82735 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ Cargo.lock *.pkg.tar.zst /pkg +/.config diff --git a/src/editors/content_editor.rs b/src/editors/content_editor.rs index 3c0bdfe..edc0f88 100644 --- a/src/editors/content_editor.rs +++ b/src/editors/content_editor.rs @@ -124,37 +124,6 @@ impl MainEditor { } pub fn render_ui(&mut self, project: &mut ProjectSettings, ui: &mut egui::Ui) { - if let Some(dialog) = &mut self.dialog { - dialog.ui(ui, project); - - match dialog { - ContentAI::Summarise { ready, result, .. } => { - if *ready.lock().unwrap() == ReadyState::Ready { - self.content.content.push_str(&result.lock().unwrap()); - self.content.saved = false; - *ready.lock().unwrap() = ReadyState::Idle; - } else if *ready.lock().unwrap() == ReadyState::Halted { - *ready.lock().unwrap() = ReadyState::Idle; - } - } - ContentAI::Continue { - ready, - result, - content, - .. - } => { - *content = self.content.content.clone(); - if *ready.lock().unwrap() == ReadyState::Ready { - self.content.content.push_str(&result.lock().unwrap()); - self.content.saved = false; - *ready.lock().unwrap() = ReadyState::Idle; - } else if *ready.lock().unwrap() == ReadyState::Halted { - *ready.lock().unwrap() = ReadyState::Idle; - } - } - } - } - ui.vertical(|ui| { // check for Ctrl+S to save if ui.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) { @@ -253,6 +222,21 @@ impl MainEditor { ui.separator(); + if let Some(dialog) = &mut self.dialog { + dialog.ui(ui, project); + + dialog.content = self.content.content.clone(); + if *dialog.ready.lock().unwrap() == ReadyState::Ready { + self.content + .content + .push_str(&dialog.result.lock().unwrap()); + self.content.saved = false; + *dialog.ready.lock().unwrap() = ReadyState::Idle; + } else if *dialog.ready.lock().unwrap() == ReadyState::Halted { + *dialog.ready.lock().unwrap() = ReadyState::Idle; + } + } + if self.show_preview { self.preview_ui(ui); } @@ -356,17 +340,8 @@ impl MainEditor { ui.menu_button("AI Actions", |ui| { ui.add_enabled_ui(project.ai_enabled(), |ui| { - if ui.button("Summarise").clicked() { - self.dialog = Some(ContentAI::Summarise { - result: Arc::new(Mutex::new(String::new())), - content: self.content.content.clone(), - open: true, - ready: Arc::new(Mutex::new(ReadyState::Idle)), - }); - } - - if ui.button("Continue").clicked() { - self.dialog = Some(ContentAI::Continue { + if ui.button("AI Assistant").clicked() { + self.dialog = Some(ContentAI { content: self.content.content.clone(), instruction: String::new(), max_tokens: 1024, diff --git a/src/llm_integration/content_llm.rs b/src/llm_integration/content_llm.rs index 70c9d00..4fcc680 100644 --- a/src/llm_integration/content_llm.rs +++ b/src/llm_integration/content_llm.rs @@ -8,100 +8,32 @@ use serde::{Deserialize, Serialize}; use crate::editors::settings_editor::ProjectSettings; #[derive(Clone)] -pub enum ContentAI { - Summarise { - open: bool, - content: String, - result: Arc>, - ready: Arc>, - }, - Continue { - open: bool, - content: String, - instruction: String, - max_tokens: usize, - context_override: String, - result: Arc>, - ready: Arc>, - temperature: f32, - reasoning_effort: ReasoningEffort, - model_override: String, - }, +pub struct ContentAI { + pub open: bool, + pub content: String, + pub instruction: String, + pub max_tokens: usize, + pub context_override: String, + pub result: Arc>, + pub ready: Arc>, + pub temperature: f32, + pub reasoning_effort: ReasoningEffort, + pub model_override: String, } impl ContentAI { pub fn ui(&mut self, ui: &mut egui::Ui, project: &mut ProjectSettings) { - let mut is_open = *match self { - ContentAI::Summarise { open, .. } => open, - ContentAI::Continue { open, .. } => open, - }; + let is_open = self.open; if is_open { - egui::Window::new("AI Assistant") - .open(&mut is_open) - .show(ui.ctx(), |ui| match self { - ContentAI::Summarise { .. } => { - Self::ui_summarise(self, ui, project); - } - ContentAI::Continue { .. } => { - Self::ui_continue(self, ui, project); - } - }); - } - - match self { - ContentAI::Summarise { open, .. } => *open = is_open, - ContentAI::Continue { open, .. } => *open = is_open, - }; - } - - fn ui_summarise(&mut self, ui: &mut egui::Ui, _project: &mut ProjectSettings) { - if let ContentAI::Summarise { - content, - result, - ready, - .. - } = self - { - egui::ScrollArea::vertical() - .id_salt("summarise") - .auto_shrink([false, false]) - .max_width(ui.available_width()) - .show(ui, |ui| { - ui.add( - egui::TextEdit::multiline(content) - .frame(false) - .interactive(false), - ); - }); - ui.add( - egui::TextEdit::multiline(&mut *result.lock().unwrap()) - .font(egui::TextStyle::Monospace) - .interactive(false) - .frame(false) - .lock_focus(true) - .hint_text("Summary will appear here..."), - ); - if ui.button("Summarise").clicked() { - // Self::summarise(content, result.clone()); - *ready.lock().unwrap() = ReadyState::Generating; - } + egui::SidePanel::right("ai_assistant").show_inside(ui, |ui| { + Self::ui_continue(self, ui, project); + }); } + self.open = is_open; } fn ui_continue(&mut self, ui: &mut egui::Ui, project: &mut ProjectSettings) { - if let ContentAI::Continue { - content, - instruction, - max_tokens, - context_override, - result, - ready, - temperature, - model_override, - reasoning_effort, - .. - } = self { ui.weak("(The model will see current file content)"); ui.separator(); @@ -114,7 +46,7 @@ impl ContentAI { .max_width(ui.available_width()) .show(ui, |ui| { ui.add( - egui::TextEdit::multiline(instruction) + egui::TextEdit::multiline(&mut self.instruction) .frame(false) .desired_width(ui.available_width()) .hint_text("Writing Instructions"), @@ -130,7 +62,7 @@ impl ContentAI { .max_width(ui.available_width()) .show(ui, |ui| { ui.add( - egui::TextEdit::multiline(context_override) + egui::TextEdit::multiline(&mut self.context_override) .frame(false) .desired_width(ui.available_width()) .hint_text("Any additional context?"), @@ -144,7 +76,7 @@ impl ContentAI { .show(ui, |ui| { ui.label("Max Tokens"); ui.add( - egui::DragValue::new(max_tokens) + egui::DragValue::new(&mut self.max_tokens) .range(128..=u32::MAX) .speed(128), ); @@ -152,7 +84,7 @@ impl ContentAI { ui.label("Temperature"); ui.add( - egui::DragValue::new(temperature) + egui::DragValue::new(&mut self.temperature) .range(0.0..=2.0) .speed(0.1), ); @@ -160,51 +92,58 @@ impl ContentAI { ui.label("Reasoning effort"); egui::ComboBox::from_id_salt("reasoning_effort") - .selected_text(reasoning_effort.to_string()) + .selected_text(self.reasoning_effort.to_string()) .show_ui(ui, |ui| { ui.selectable_value( - reasoning_effort, + &mut self.reasoning_effort, ReasoningEffort::Minimal, "Minimal", ); - ui.selectable_value(reasoning_effort, ReasoningEffort::Low, "Low"); ui.selectable_value( - reasoning_effort, + &mut self.reasoning_effort, + ReasoningEffort::Low, + "Low", + ); + ui.selectable_value( + &mut self.reasoning_effort, ReasoningEffort::Medium, "Medium", ); - ui.selectable_value(reasoning_effort, ReasoningEffort::High, "High"); + ui.selectable_value( + &mut self.reasoning_effort, + ReasoningEffort::High, + "High", + ); }); ui.end_row(); ui.label("Model override"); - ui.add(egui::TextEdit::singleline(model_override)); + ui.add(egui::TextEdit::singleline(&mut self.model_override)); ui.end_row(); }); ui.separator(); - let mut ready_lock = ready.lock().unwrap(); + let mut ready_lock = self.ready.lock().unwrap(); match *ready_lock { ReadyState::Idle => { let continue_content = || { - let context_override = context_override.clone(); - let content = content.clone(); - let instruction = instruction.clone(); + let context_override = self.context_override.clone(); + let content = self.content.clone(); + let instruction = self.instruction.clone(); let project = project.clone(); let ai_context = project.ai_context.clone(); - let result = result.clone(); - let ready = ready.clone(); - let reasoning_effort = reasoning_effort; + let result = self.result.clone(); + let ready = self.ready.clone(); let options = AIOptions { - max_completion_tokens: *max_tokens, - reasoning_effort: *reasoning_effort, - temperature: *temperature, - model_override: if !model_override.is_empty() { - Some(model_override.clone()) + max_completion_tokens: self.max_tokens, + reasoning_effort: self.reasoning_effort, + temperature: self.temperature, + model_override: if !self.model_override.is_empty() { + Some(self.model_override.clone()) } else { None }, @@ -254,34 +193,38 @@ impl ContentAI { ReadyState::Ready => {} } - egui::ScrollArea::both() - .auto_shrink([true, true]) - .id_salt("llm_output") - .max_width(ui.available_width()) - .max_height(ui.available_height() / 4.0) - .show(ui, |ui| { - ui.add( - egui::TextEdit::multiline(&mut *result.lock().unwrap()) - .font(egui::TextStyle::Monospace) - .interactive(false) - .desired_rows(0) - .frame(false) - .desired_width(ui.available_width()) - .lock_focus(true) - .hint_text("Content will appear here..."), - ); + egui::TopBottomPanel::bottom("llm_output") + .resizable(true) + .show_inside(ui, |ui| { + egui::ScrollArea::both() + .auto_shrink([false, false]) + .id_salt("llm_output") + .max_width(ui.available_width()) + // .max_height(ui.available_height() / 3.0) + .show(ui, |ui| { + ui.add( + egui::TextEdit::multiline(&mut *self.result.lock().unwrap()) + .font(egui::TextStyle::Monospace) + .interactive(false) + .desired_rows(0) + .frame(false) + .desired_width(ui.available_width()) + .lock_focus(true) + .hint_text("Content will appear here..."), + ); + }); + + ui.separator(); + ui.horizontal(|ui| { + if ui.button("Insert").clicked() { + *ready_lock = ReadyState::Ready; + } + + if ui.button("Clear").clicked() { + self.result.lock().unwrap().clear(); + } + }); }); - - ui.separator(); - ui.horizontal(|ui| { - if ui.button("Insert").clicked() { - *ready_lock = ReadyState::Ready; - } - - if ui.button("Clear").clicked() { - result.lock().unwrap().clear(); - } - }); } } }