use super::super::edit_history::EditHistory;
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ChatOverlayMessageRole {
User,
Nika,
System,
Tool,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ChatOverlayMessage {
pub role: ChatOverlayMessageRole,
pub content: String,
}
impl ChatOverlayMessage {
pub fn new(role: ChatOverlayMessageRole, content: impl Into<String>) -> Self {
Self {
role,
content: content.into(),
}
}
}
#[derive(Debug, Clone)]
pub struct ChatOverlayState {
pub messages: Vec<ChatOverlayMessage>,
pub input: String,
pub cursor: usize,
pub scroll: usize,
pub history: Vec<String>,
pub history_index: Option<usize>,
pub is_streaming: bool,
pub partial_response: String,
pub current_model: String,
pub edit_history: EditHistory,
}
impl Default for ChatOverlayState {
fn default() -> Self {
Self::new()
}
}
impl ChatOverlayState {
pub fn new() -> Self {
let initial_model = if std::env::var("ANTHROPIC_API_KEY").is_ok() {
"claude-sonnet-4".to_string()
} else if std::env::var("OPENAI_API_KEY").is_ok() {
"gpt-4o".to_string()
} else {
"No API Key".to_string()
};
let mut edit_history = EditHistory::default();
edit_history.init("", 0);
Self {
messages: vec![ChatOverlayMessage::new(
ChatOverlayMessageRole::System,
"Chat overlay active. Ask for help with the current view.",
)],
input: String::new(),
cursor: 0,
scroll: 0,
history: Vec::new(),
history_index: None,
is_streaming: false,
partial_response: String::new(),
current_model: initial_model,
edit_history,
}
}
pub fn start_streaming(&mut self) {
self.is_streaming = true;
self.partial_response.clear();
}
pub fn append_streaming(&mut self, chunk: &str) {
self.partial_response.push_str(chunk);
}
pub fn finish_streaming(&mut self) -> String {
self.is_streaming = false;
std::mem::take(&mut self.partial_response)
}
pub fn set_model(&mut self, model: impl Into<String>) {
self.current_model = model.into();
}
pub fn add_tool_message(&mut self, content: impl Into<String>) {
self.messages.push(ChatOverlayMessage::new(
ChatOverlayMessageRole::Tool,
content,
));
}
pub fn insert_char(&mut self, c: char) {
self.input.insert(self.cursor, c);
self.cursor += 1;
self.edit_history.push(&self.input, self.cursor);
}
pub fn backspace(&mut self) {
if self.cursor > 0 {
self.cursor -= 1;
self.input.remove(self.cursor);
self.edit_history.push(&self.input, self.cursor);
}
}
pub fn delete(&mut self) {
if self.cursor < self.input.len() {
self.input.remove(self.cursor);
self.edit_history.push(&self.input, self.cursor);
}
}
pub fn undo(&mut self) -> bool {
if let Some((text, cursor)) = self.edit_history.undo() {
self.input = text;
self.cursor = cursor;
true
} else {
false
}
}
pub fn redo(&mut self) -> bool {
if let Some((text, cursor)) = self.edit_history.redo() {
self.input = text;
self.cursor = cursor;
true
} else {
false
}
}
pub fn can_undo(&self) -> bool {
self.edit_history.can_undo()
}
pub fn can_redo(&self) -> bool {
self.edit_history.can_redo()
}
pub fn cursor_left(&mut self) {
if self.cursor > 0 {
self.cursor -= 1;
}
}
pub fn cursor_right(&mut self) {
if self.cursor < self.input.len() {
self.cursor += 1;
}
}
pub fn cursor_home(&mut self) {
self.cursor = 0;
}
pub fn cursor_end(&mut self) {
self.cursor = self.input.len();
}
pub fn add_user_message(&mut self) -> Option<String> {
let trimmed = self.input.trim();
if trimmed.is_empty() {
return None;
}
let message = std::mem::take(&mut self.input);
self.history.push(message.clone());
self.history_index = None;
self.messages.push(ChatOverlayMessage::new(
ChatOverlayMessageRole::User,
&message,
));
self.cursor = 0;
self.edit_history.init("", 0);
Some(message)
}
pub fn add_nika_message(&mut self, content: impl Into<String>) {
self.messages.push(ChatOverlayMessage::new(
ChatOverlayMessageRole::Nika,
content,
));
}
pub fn history_up(&mut self) {
if self.history.is_empty() {
return;
}
match self.history_index {
None => {
self.history_index = Some(self.history.len() - 1);
}
Some(i) if i > 0 => {
self.history_index = Some(i - 1);
}
_ => {}
}
if let Some(i) = self.history_index {
if let Some(entry) = self.history.get(i) {
self.input = entry.clone();
self.cursor = self.input.len();
}
}
}
pub fn history_down(&mut self) {
let history_len = self.history.len();
match self.history_index {
Some(i) if history_len > 0 && i + 1 < history_len => {
self.history_index = Some(i + 1);
if let Some(entry) = self.history.get(i + 1) {
self.input = entry.clone();
self.cursor = self.input.len();
}
}
Some(_) => {
self.history_index = None;
self.input.clear();
self.cursor = 0;
}
None => {}
}
}
pub fn clear(&mut self) {
self.messages = vec![ChatOverlayMessage::new(
ChatOverlayMessageRole::System,
"Chat cleared.",
)];
self.scroll = 0;
}
pub fn scroll_up(&mut self) {
self.scroll = self.scroll.saturating_add(1);
}
pub fn scroll_down(&mut self) {
self.scroll = self.scroll.saturating_sub(1);
}
}