Files
worldcoder/src/components/llm.rs
T

488 lines
19 KiB
Rust

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<Mutex<String>>,
// pub result: Arc<Mutex<String>>,
// pub ready: Arc<Mutex<ReadyState>>,
// }
#[derive(Debug)]
pub struct AIChatUI {
pub instruction: String,
pub context: String,
pub system_prompt: String,
pub config: LLMConfig,
pub request: Option<LLMRequest>,
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<R>(
id: &'static str,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> CollapsingResponse<R> {
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);
// }
// }
// }