Skip to main content

fur_cli/helpers/
conversation.rs

1use std::fs;
2use std::path::{Path};
3use serde_json::{Value, json};
4use std::io::{self, Write};
5use crate::commands::conversation::ThreadArgs;
6use colored::*;
7
8pub fn resolve_target_thread_id(
9    index: &Value,
10    args: &ThreadArgs,
11) -> Option<String> {
12    let empty_vec: Vec<Value> = Vec::new();
13    let threads: Vec<String> = index["threads"]
14        .as_array()
15        .unwrap_or(&empty_vec)
16        .iter()
17        .filter_map(|t| t.as_str().map(|s| s.to_string()))
18        .collect();
19
20    // If ID prefix provided
21    if let Some(prefix) = &args.id {
22        let matches: Vec<&String> = threads
23            .iter()
24            .filter(|tid| tid.starts_with(prefix))
25            .collect();
26
27        return match matches.as_slice() {
28            [] => {
29                eprintln!("❌ No conversation matches '{}'", prefix);
30                None
31            }
32            [single] => Some((*single).clone()),
33            _ => {
34                eprintln!("❌ Ambiguous prefix '{}': {:?}", prefix, matches);
35                None
36            }
37        };
38    }
39
40    // Otherwise use active thread
41    let active = index["active_thread"].as_str().unwrap_or("").to_string();
42    if active.is_empty() {
43        eprintln!("❌ No active conversation to delete.");
44        return None;
45    }
46
47    Some(active)
48}
49
50pub fn confirm_delete_primary() -> bool {
51
52    println!("Are you sure you want to delete this conversation? (y/N)");
53    print!("> ");
54    io::stdout().flush().unwrap();
55
56    let mut input = String::new();
57    io::stdin().read_line(&mut input).unwrap();
58
59    input.trim().to_lowercase() == "y"
60}
61
62pub fn confirm_delete_destructive() -> bool {
63
64    println!();
65    println!(
66        "{}",
67        "⚠️  Reminder: deleting a conversation is a destructive action.\n\
68         It cannot be reversed unless the project is version-controlled (git)."
69            .color(Color::BrightRed)
70            .bold()
71    );
72    println!();
73    println!("Type DELETE to confirm:");
74    print!("> ");
75    io::stdout().flush().unwrap();
76
77    let mut input = String::new();
78    io::stdin().read_line(&mut input).unwrap();
79
80    input.trim() == "DELETE"
81}
82
83pub fn perform_conversation_deletion(
84    index: &mut Value,
85    fur_dir: &Path,
86    target_tid: &str,
87    threads: &[String],
88) {
89    let convo_path = fur_dir.join("threads").join(format!("{}.json", target_tid));
90
91    // Load convo to extract message IDs + title
92    let convo_content = fs::read_to_string(&convo_path)
93        .expect("Failed to load conversation JSON.");
94    let convo: Value = serde_json::from_str(&convo_content).unwrap();
95
96    let title = convo["title"].as_str().unwrap_or("Untitled");
97    let msg_ids: Vec<String> = convo["messages"]
98        .as_array()
99        .unwrap_or(&vec![])
100        .iter()
101        .filter_map(|v| v.as_str().map(|s| s.to_string()))
102        .collect();
103
104    println!(
105        "🗑️  Deleting conversation {} \"{}\"...",
106        &target_tid[..8],
107        title
108    );
109
110    // 1. Delete conversation JSON
111    let _ = fs::remove_file(&convo_path);
112
113    // 2. Delete message files and markdown attachments
114    for mid in msg_ids {
115        let msg_path = fur_dir.join("messages").join(format!("{}.json", mid));
116
117        if let Ok(content) = fs::read_to_string(&msg_path) {
118            if let Ok(msg_json) = serde_json::from_str::<Value>(&content) {
119                if let Some(md_raw) = msg_json["markdown"].as_str() {
120                    let md_path = Path::new(md_raw);
121                    if md_path.is_absolute() {
122                        let _ = fs::remove_file(md_path);
123                    } else {
124                        let _ = fs::remove_file(Path::new(".").join(md_raw));
125                    }
126                }
127            }
128        }
129
130        let _ = fs::remove_file(&msg_path);
131    }
132
133    // 3. Update index.json (remove thread)
134    let new_threads: Vec<String> = threads
135        .iter()
136        .filter(|tid| tid.as_str() != target_tid)
137        .cloned()
138        .collect();
139
140    index["threads"] = json!(new_threads);
141
142    // 4. Clear active thread if it matches deleted
143    if index["active_thread"].as_str() == Some(target_tid) {
144        index["active_thread"] = Value::Null;
145        index["current_message"] = Value::Null;
146    }
147
148    // 5. Save index.json
149    let index_path = fur_dir.join("index.json");
150    fs::write(&index_path, serde_json::to_string_pretty(&index).unwrap()).unwrap();
151
152    println!("✔️ Conversation deleted successfully.");
153}