Skip to content

Commit aa4223f

Browse files
committed
convo_path across & add size col for fur convo
1 parent 612a449 commit aa4223f

File tree

11 files changed

+132
-49
lines changed

11 files changed

+132
-49
lines changed

src/commands/conversation.rs

Lines changed: 104 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::fs;
2-
use std::path::Path;
2+
use std::path::{Path, PathBuf};
33
use serde_json::{Value, json};
44
use clap::Parser;
55
use chrono::{DateTime, Local, Utc};
@@ -44,31 +44,46 @@ pub fn run_conversation(args: ThreadArgs) {
4444
let mut conversation_info = Vec::new();
4545
for tid in threads {
4646
if let Some(tid_str) = tid.as_str() {
47-
let conversation_path = fur_dir.join("threads").join(format!("{}.json", tid_str));
48-
if let Ok(content) = fs::read_to_string(conversation_path) {
47+
let convo_path = fur_dir.join("threads").join(format!("{}.json", tid_str));
48+
if let Ok(content) = fs::read_to_string(&convo_path) {
4949
if let Ok(conversation_json) = serde_json::from_str::<Value>(&content) {
50-
let title = conversation_json["title"].as_str().unwrap_or("Untitled").to_string();
51-
let created_raw = conversation_json["created_at"].as_str().unwrap_or("");
52-
let msg_count = conversation_json["messages"]
50+
let title = conversation_json["title"]
51+
.as_str()
52+
.unwrap_or("Untitled")
53+
.to_string();
54+
55+
let created_raw =
56+
conversation_json["created_at"].as_str().unwrap_or("");
57+
let msg_ids = conversation_json["messages"]
5358
.as_array()
54-
.map(|a| a.len())
55-
.unwrap_or(0);
59+
.map(|a| a.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect::<Vec<_>>())
60+
.unwrap_or_default();
61+
62+
let msg_count = msg_ids.len();
5663

5764
// Parse created_at safely
58-
let parsed_time = DateTime::parse_from_rfc3339(created_raw)
59-
.map(|dt| dt.with_timezone(&Utc))
60-
.unwrap_or_else(|_| Utc::now());
61-
let local_time: DateTime<Local> = DateTime::from(parsed_time);
65+
let parsed_time =
66+
DateTime::parse_from_rfc3339(created_raw)
67+
.map(|dt| dt.with_timezone(&Utc))
68+
.unwrap_or_else(|_| Utc::now());
69+
let local_time: DateTime<Local> =
70+
DateTime::from(parsed_time);
6271
let date_str = local_time.format("%Y-%m-%d").to_string();
6372
let time_str = local_time.format("%H:%M").to_string();
6473

74+
// Compute total footprint (JSON + markdown attachments)
75+
let size_bytes = compute_conversation_size(fur_dir, tid_str, &msg_ids);
76+
let size_mb =
77+
(size_bytes as f64 / (1024.0 * 1024.0)).min(9999.0);
78+
6579
conversation_info.push((
6680
tid_str.to_string(),
6781
title,
6882
date_str,
6983
time_str,
7084
msg_count,
7185
parsed_time,
86+
size_mb,
7287
));
7388
}
7489
}
@@ -79,20 +94,31 @@ pub fn run_conversation(args: ThreadArgs) {
7994
conversation_info.sort_by(|a, b| b.5.cmp(&a.5));
8095

8196
// Build rows and track active index
82-
for (i, (tid, title, date, time, msg_count, _)) in conversation_info.iter().enumerate() {
97+
for (i, (tid, title, date, time, msg_count, _, size_mb)) in
98+
conversation_info.iter().enumerate()
99+
{
83100
let short_id = &tid[..8];
101+
84102
rows.push(vec![
85103
short_id.to_string(),
86104
title.to_string(),
87105
format!("{} | {}", date, time),
88106
msg_count.to_string(),
107+
format!("{:.2} MB", size_mb),
89108
]);
109+
90110
if tid == active {
91111
active_idx = Some(i);
92112
}
93113
}
94114

95-
render_table("Threads", &["ID", "Title", "Created", "#Msgs"], rows, active_idx);
115+
render_table(
116+
"Threads",
117+
&["ID", "Title", "Created", "#Msgs", "Size"],
118+
rows,
119+
active_idx,
120+
);
121+
96122
return;
97123
}
98124

@@ -110,7 +136,8 @@ pub fn run_conversation(args: ThreadArgs) {
110136

111137
let mut found = threads.iter().find(|&s| s == &tid);
112138
if found.is_none() {
113-
let matches: Vec<&String> = threads.iter().filter(|s| s.starts_with(&tid)).collect();
139+
let matches: Vec<&String> =
140+
threads.iter().filter(|s| s.starts_with(&tid)).collect();
114141
if matches.len() == 1 {
115142
found = Some(matches[0]);
116143
} else if matches.len() > 1 {
@@ -129,13 +156,69 @@ pub fn run_conversation(args: ThreadArgs) {
129156

130157
index["active_thread"] = json!(tid_full);
131158
index["current_message"] = serde_json::Value::Null;
132-
fs::write(&index_path, serde_json::to_string_pretty(&index).unwrap()).unwrap();
159+
fs::write(
160+
&index_path,
161+
serde_json::to_string_pretty(&index).unwrap(),
162+
)
163+
.unwrap();
164+
165+
let convo_path =
166+
fur_dir.join("threads").join(format!("{}.json", tid_full));
167+
let content = fs::read_to_string(convo_path).unwrap();
168+
let conversation_json: Value =
169+
serde_json::from_str(&content).unwrap();
170+
let title =
171+
conversation_json["title"].as_str().unwrap_or("Untitled");
172+
173+
println!(
174+
"✔️ Switched active conversation to {} \"{}\"",
175+
&tid_full[..8],
176+
title
177+
);
178+
}
179+
}
180+
181+
182+
/// Computes total storage: conversation.json + all message JSONs + all markdown attachments.
183+
fn compute_conversation_size(
184+
fur_dir: &Path,
185+
tid: &str,
186+
msg_ids: &[String],
187+
) -> u64 {
188+
let mut total: u64 = 0;
189+
190+
// Add main conversation JSON
191+
let convo_path = fur_dir.join("threads").join(format!("{}.json", tid));
192+
total += file_size(&convo_path);
133193

134-
let conversation_path = fur_dir.join("threads").join(format!("{}.json", tid_full));
135-
let content = fs::read_to_string(conversation_path).unwrap();
136-
let conversation_json: Value = serde_json::from_str(&content).unwrap();
137-
let title = conversation_json["title"].as_str().unwrap_or("Untitled");
194+
// Add all messages JSON
195+
total += get_message_file_sizes(fur_dir, msg_ids);
138196

139-
println!("✔️ Switched active conversation to {} \"{}\"", &tid_full[..8], title);
197+
total
198+
}
199+
200+
fn get_message_file_sizes(fur_dir: &Path, msg_ids: &[String]) -> u64 {
201+
let mut total = 0;
202+
203+
for mid in msg_ids {
204+
// message JSON
205+
let msg_path = fur_dir.join("messages").join(format!("{}.json", mid));
206+
total += file_size(&msg_path);
207+
208+
// check for markdown pointer inside JSON
209+
if let Ok(content) = fs::read_to_string(&msg_path) {
210+
if let Ok(json) = serde_json::from_str::<Value>(&content) {
211+
if let Some(markdown_rel) = json["markdown"].as_str() {
212+
let md_path = fur_dir.join(markdown_rel);
213+
total += file_size(&md_path);
214+
}
215+
}
216+
}
140217
}
218+
219+
total
220+
}
221+
222+
fn file_size(path: &PathBuf) -> u64 {
223+
fs::metadata(path).map(|m| m.len()).unwrap_or(0)
141224
}

src/commands/jot/core.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,18 +87,18 @@ pub fn update_conversation(ctx: &FurContext, msg_id: &str, parent: Option<&str>)
8787
return;
8888
}
8989

90-
let conversation_path = ctx
90+
let convo_path = ctx
9191
.fur_dir
9292
.join("threads")
9393
.join(format!("{}.json", ctx.conversation_id));
9494

95-
let mut conversation = read_json(&conversation_path);
95+
let mut conversation = read_json(&convo_path);
9696

9797
if let Some(arr) = conversation["messages"].as_array_mut() {
9898
arr.push(json!(msg_id));
9999
}
100100

101-
write_json(&conversation_path, &conversation);
101+
write_json(&convo_path, &conversation);
102102
}
103103

104104
fn attach_to_parent(fur_dir: &Path, parent_id: &str, message_id: &str) {

src/commands/jump.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ pub fn run_jump(args: JumpArgs) -> Result<(), Box<dyn std::error::Error>> {
2222
let mut index: Value = serde_json::from_str(&index_data).unwrap();
2323

2424
let conversation_id = index["active_thread"].as_str().unwrap();
25-
let conversation_path = Path::new(".fur/threads").join(format!("{}.json", conversation_id));
26-
let conversation_data = fs::read_to_string(conversation_path).expect("❌ Couldn't read conversation file");
25+
let convo_path = Path::new(".fur/threads").join(format!("{}.json", conversation_id));
26+
let conversation_data = fs::read_to_string(convo_path).expect("❌ Couldn't read conversation file");
2727
let conversation: Value = serde_json::from_str(&conversation_data).unwrap();
2828

2929
let current_id = index["current_message"].as_str().unwrap_or_default();

src/commands/new.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,8 @@ fn run_new_internal(
124124
let conversation_meta = make_conversation_metadata(&name, &conversation_id);
125125

126126
// --- Write conversation ---
127-
let conversation_path = fur_dir.join("threads").join(format!("{}.json", conversation_id));
128-
fs::write(&conversation_path, serde_json::to_string_pretty(&conversation_meta).unwrap())
127+
let convo_path = fur_dir.join("threads").join(format!("{}.json", conversation_id));
128+
fs::write(&convo_path, serde_json::to_string_pretty(&conversation_meta).unwrap())
129129
.expect("Could not write conversation file");
130130

131131
// --- Update index ---

src/commands/printed.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ pub fn run_printed(out: Option<String>, verbose: bool) {
2323
}
2424

2525
// --- Load active conversation metadata ---
26-
let conversation_path = fur_dir.join("threads").join(format!("{}.json", active_id));
26+
let convo_path = fur_dir.join("threads").join(format!("{}.json", active_id));
2727
let conversation_json: Value =
28-
serde_json::from_str(&fs::read_to_string(&conversation_path).unwrap()).unwrap();
28+
serde_json::from_str(&fs::read_to_string(&convo_path).unwrap()).unwrap();
2929

3030
let title = conversation_json["title"].as_str().unwrap_or("untitled");
3131
let id = conversation_json["id"].as_str().unwrap_or("unknown");

src/commands/save.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ pub fn run_save(args: SaveArgs) {
3333
}
3434
};
3535

36-
let conversation_path = fur_dir.join("threads").join(format!("{}.json", conversation_id));
36+
let convo_path = fur_dir.join("threads").join(format!("{}.json", conversation_id));
3737
let conversation: Value =
38-
serde_json::from_str(&fs::read_to_string(&conversation_path).expect("❌ Cannot read conversation"))
38+
serde_json::from_str(&fs::read_to_string(&convo_path).expect("❌ Cannot read conversation"))
3939
.unwrap();
4040

4141
let title = conversation["title"].as_str().unwrap_or("Untitled");

src/commands/status/core.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ pub fn load_index_and_conversation(fur_dir: &Path)
1212
let conversation_id = index["active_thread"].as_str().unwrap_or("");
1313
let current = index["current_message"].as_str().unwrap_or("").to_string();
1414

15-
let conversation_path = fur_dir.join("threads").join(format!("{}.json", conversation_id));
16-
let conversation: Value = read_json(&conversation_path);
15+
let convo_path = fur_dir.join("threads").join(format!("{}.json", conversation_id));
16+
let conversation: Value = read_json(&convo_path);
1717

1818
(index, conversation, current)
1919
}

src/commands/status/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ pub fn run_status(args: StatusArgs) {
3838
load_index_and_conversation(&fur_dir);
3939

4040
if let Some(ref tid) = args.conversation_override {
41-
let conversation_path = fur_dir.join("tmp").join(format!("{}.json", tid));
42-
if let Ok(content) = fs::read_to_string(&conversation_path) {
41+
let convo_path = fur_dir.join("tmp").join(format!("{}.json", tid));
42+
if let Ok(content) = fs::read_to_string(&convo_path) {
4343
if let Ok(tmp_conversation) = serde_json::from_str::<Value>(&content) {
4444
conversation = tmp_conversation;
4545
}

src/commands/timeline.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ pub fn run_timeline(args: TimelineArgs) {
4242
index["active_thread"].as_str().unwrap_or("")
4343
};
4444

45-
let conversation_path = fur_dir.join("threads").join(format!("{}.json", conversation_id));
46-
let conversation_json: Value = serde_json::from_str(&fs::read_to_string(&conversation_path).unwrap()).unwrap();
45+
let convo_path = fur_dir.join("threads").join(format!("{}.json", conversation_id));
46+
let conversation_json: Value = serde_json::from_str(&fs::read_to_string(&convo_path).unwrap()).unwrap();
4747

4848
let conversation_title = conversation_json["title"].as_str().unwrap_or("Untitled");
4949

src/commands/tree.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ pub fn run_tree(args: TreeArgs) {
3131
} else {
3232
index_data["active_thread"].as_str().unwrap_or("")
3333
};
34-
let conversation_path = fur_dir.join("threads").join(format!("{}.json", conversation_id));
34+
let convo_path = fur_dir.join("threads").join(format!("{}.json", conversation_id));
3535
let conversation_data: Value =
36-
serde_json::from_str(&fs::read_to_string(&conversation_path).expect("❌ Cannot read conversation"))
36+
serde_json::from_str(&fs::read_to_string(&convo_path).expect("❌ Cannot read conversation"))
3737
.unwrap();
3838

3939
// Load avatars.json once

0 commit comments

Comments
 (0)