use std::{ io::{BufRead, BufReader}, sync::{Arc, Mutex}, }; use egui::{CollapsingResponse, Id, InnerResponse, Ui}; use serde::{Deserialize, Serialize}; use zxq5_egui::combobox::{self, text_combobox}; use crate::{ editors::settings_editor::ProjectSettings, services::llm::{LLMConfig, LLMHandle, LLMRequest, ReasoningEffort, ResponseToken}, }; // // #[derive(Clone)] // pub struct ContentAI { // pub open: bool, // // // model input // pub content: String, // pub instruction: String, // pub context_override: String, // pub system_prompt: String, // // // model settings // pub max_tokens: usize, // pub temperature: f32, // pub reasoning_effort: ReasoningEffort, // pub model_override: String, // // // model output // pub reasoning: Arc>, // pub result: Arc>, // pub ready: Arc>, // } #[derive(Debug)] pub struct AIChatUI { pub instruction: String, pub context: String, pub system_prompt: String, pub config: LLMConfig, pub request: Option, reasoning_out: String, content_out: String, } impl Clone for AIChatUI { fn clone(&self) -> Self { Self { instruction: self.instruction.clone(), context: self.context.clone(), system_prompt: self.system_prompt.clone(), config: self.config.clone(), request: None, reasoning_out: self.reasoning_out.clone(), content_out: self.content_out.clone(), } } } impl AIChatUI { pub fn new() -> Self { Self { instruction: String::new(), context: String::new(), system_prompt: String::new(), config: LLMConfig::default(), request: None, reasoning_out: String::new(), content_out: String::new(), } } pub fn update(&mut self) { if let Some(request) = &mut self.request { for token in request.stream_content() { match token { ResponseToken::Content(s) => self.content_out.push_str(&s), ResponseToken::Reasoning(s) => self.reasoning_out.push_str(&s), ResponseToken::End => { self.request = None; } } } } } pub fn ui(&mut self, ui: &mut egui::Ui, content: &mut String) { egui::Panel::right("ai_assistant").show_inside(ui, |ui| { collapsing_panel("Settings", ui, |ui| { egui::Grid::new("continue_grid") .num_columns(2) .striped(true) .show(ui, |ui| { ui.label("Max Tokens"); ui.add( egui::DragValue::new(&mut self.config.max_tokens) .range(128..=u32::MAX) .speed(128), ); ui.end_row(); ui.label("Temperature"); ui.add( egui::DragValue::new(&mut self.config.temperature) .range(0.0..=2.0) .speed(0.1), ); ui.label("Reasoning effort"); combobox::combobox( ui, &mut self.config.reasoning_effort, &[ ReasoningEffort::Minimal, ReasoningEffort::Low, ReasoningEffort::Medium, ReasoningEffort::High, ], ); ui.end_row(); ui.label("Model override"); combobox::text_combobox( ui, &mut self.config.model, &["openai/gpt-oss-20b", "google/gemma"], ); // ui.add(egui::TextEdit::singleline(&mut self.config.model)); ui.end_row(); }); }); collapsing_panel("Instructions", ui, |ui| { egui::ScrollArea::vertical() .auto_shrink([false, false]) .max_height(ui.available_height()) .show(ui, |ui| { ui.add( egui::TextEdit::multiline(&mut self.instruction) .desired_width(ui.available_width()) .hint_text("Writing Instructions"), ); }) }); collapsing_panel("Context", ui, |ui| { egui::ScrollArea::vertical() .auto_shrink([false, false]) .max_height(ui.available_height()) .show(ui, |ui| { ui.add( egui::TextEdit::multiline(&mut self.context) .desired_width(ui.available_width()) .hint_text("Any additional context"), ); }) }); collapsing_panel("System Prompt", ui, |ui| { egui::ScrollArea::vertical() .auto_shrink([false, false]) .max_height(ui.available_height()) .show(ui, |ui| { ui.add( egui::TextEdit::multiline(&mut self.system_prompt) .desired_width(ui.available_width()) .hint_text("System prompt"), ); }) }); ui.label("hello world"); }); } } fn collapsing_panel( id: &'static str, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R, ) -> CollapsingResponse { egui::CollapsingHeader::new(id) .default_open(true) .show(ui, |ui| { egui::Panel::top(id) .resizable(true) .show_inside(ui, add_contents) .inner }) } // // impl ContentAI { // pub fn new(content: String) -> Self { // Self { // // model input // content, // instruction: String::new(), // context_override: String::new(), // system_prompt: String::new(), // // // model settings // max_tokens: 2048, // reasoning_effort: ReasoningEffort::default(), // temperature: 0.7, // model_override: String::new(), // reasoning: Arc::new(Mutex::new(String::new())), // // // output // result: Arc::new(Mutex::new(String::new())), // ready: Arc::new(Mutex::new(ReadyState::Idle)), // // // ui // open: true, // } // } // // pub fn ui(&mut self, ui: &mut egui::Ui, project: &mut ProjectSettings) { // let is_open = self.open; // // if is_open { // egui::SidePanel::right("ai_assistant").show_inside(ui, |ui| { // Self::ui_main(self, ui, project); // }); // } // self.open = is_open; // } // // fn ui_output_box(&mut self, ui: &mut egui::Ui, project: &mut ProjectSettings) { // let mut ready_lock = self.ready.lock().unwrap(); // // ui.horizontal(|ui| { // if *ready_lock == ReadyState::Generating { // if ui.button("Cancel").clicked() { // *ready_lock = ReadyState::Halted; // } // if ui.button("Stop").clicked() { // *ready_lock = ReadyState::Idle; // } // ui.spinner(); // ui.label("Generating..."); // } // // if *ready_lock == ReadyState::Idle { // let continue_content = || { // let content = self.content.clone(); // let project = project.clone(); // let result = self.result.clone(); // let reasoning = self.reasoning.clone(); // let ready = self.ready.clone(); // // let options = AIOptions { // 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 // }, // }; // // let ai_input = AIInput { // system_prompt: self.system_prompt.clone(), // user_prompt: format!( // "{}\n\n{} {}", // self.instruction.clone(), // project.ai_context.clone(), // self.context_override.clone() // ), // previous_content: content.clone(), // structure: None, // }; // // result.lock().unwrap().clear(); // // std::thread::spawn(move || { // let result = crate::llm_integration::content_llm::continue_content( // ai_input, // options, // project, // result, // reasoning, // ready.clone(), // ); // if let Err(e) = result { // eprintln!("Error in content generation: {e}"); // } // }); // }; // // if ui.button("Generate ").clicked() { // continue_content(); // } // // ui.label("Idle"); // } // // // show regardless of state // if ui.button("Insert").clicked() { // *ready_lock = ReadyState::Ready; // } // // if ui.button("Clear").clicked() { // self.result.lock().unwrap().clear(); // self.reasoning.lock().unwrap().clear(); // } // }); // // ui.spacing(); // // ui.vertical(|ui| { // egui::TopBottomPanel::top("reasoning_output") // .resizable(true) // .show_inside(ui, |ui| { // egui::ScrollArea::both() // .auto_shrink([false, true]) // .id_salt("reasoning_output") // .max_width(ui.available_width()) // // .max_height(ui.available_height() / 3.0) // .show(ui, |ui| { // ui.add( // egui::TextEdit::multiline(&mut *self.reasoning.lock().unwrap()) // .font(egui::TextStyle::Monospace) // .interactive(false) // .desired_rows(5) // .frame(false) // .desired_width(ui.available_width()) // .lock_focus(true) // .hint_text("Reasoning will appear here..."), // ); // }); // }); // // egui::ScrollArea::both() // .auto_shrink([false, false]) // .id_salt("llm_output") // .max_width(ui.available_width()) // .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..."), // ); // }); // }); // } // // fn ui_main(&mut self, ui: &mut egui::Ui, project: &mut ProjectSettings) { // { // ui.weak("(The model will see current file content)"); // // egui::CollapsingHeader::new("Settings") // .default_open(true) // .show(ui, |ui| { // egui::Grid::new("continue_grid") // .num_columns(2) // .striped(true) // .show(ui, |ui| { // ui.label("Max Tokens"); // ui.add( // egui::DragValue::new(&mut self.max_tokens) // .range(128..=u32::MAX) // .speed(128), // ); // ui.end_row(); // // ui.label("Temperature"); // ui.add( // egui::DragValue::new(&mut self.temperature) // .range(0.0..=2.0) // .speed(0.1), // ); // // ui.label("Reasoning effort"); // // egui::ComboBox::from_id_salt("reasoning_effort") // .selected_text(self.reasoning_effort.to_string()) // .show_ui(ui, |ui| { // ui.selectable_value( // &mut self.reasoning_effort, // ReasoningEffort::Minimal, // "Minimal", // ); // ui.selectable_value( // &mut self.reasoning_effort, // ReasoningEffort::Low, // "Low", // ); // ui.selectable_value( // &mut self.reasoning_effort, // ReasoningEffort::Medium, // "Medium", // ); // ui.selectable_value( // &mut self.reasoning_effort, // ReasoningEffort::High, // "High", // ); // }); // // ui.end_row(); // // ui.label("Model override"); // ui.add(egui::TextEdit::singleline(&mut self.model_override)); // ui.end_row(); // }); // }); // // egui::TopBottomPanel::top("continue_instruction") // .resizable(true) // .show_inside(ui, |ui| { // egui::CollapsingHeader::new("Instructions") // .default_open(true) // .show(ui, |ui| { // egui::ScrollArea::vertical() // .auto_shrink([false, false]) // .max_height(ui.available_height()) // .show(ui, |ui| { // ui.add( // egui::TextEdit::multiline(&mut self.instruction) // .frame(false) // .desired_width(ui.available_width()) // .hint_text("Writing Instructions"), // ); // }); // }); // }); // // egui::TopBottomPanel::top("continue_context") // .resizable(true) // .show_inside(ui, |ui| { // egui::CollapsingHeader::new("Context") // .default_open(true) // .show(ui, |ui| { // egui::ScrollArea::vertical() // .auto_shrink([false, false]) // .max_height(ui.available_height()) // .show(ui, |ui| { // ui.add( // egui::TextEdit::multiline(&mut self.context_override) // .frame(false) // .desired_width(ui.available_width()) // .hint_text("Any additional context?"), // ); // }); // }); // }); // // egui::TopBottomPanel::top("continue_system_prompt") // .resizable(true) // .show_inside(ui, |ui| { // egui::CollapsingHeader::new("System prompt") // .default_open(true) // .show(ui, |ui| { // egui::ScrollArea::vertical() // .auto_shrink([false, false]) // .max_height(ui.available_height()) // .show(ui, |ui| { // ui.add( // egui::TextEdit::multiline(&mut self.system_prompt) // .frame(false) // .desired_width(ui.available_width()) // .hint_text("System prompt"), // ); // }); // }); // }); // // self.ui_output_box(ui, project); // } // } // }