11use std:: fs;
2- use std:: path:: Path ;
2+ use std:: path:: { Path , PathBuf } ;
33use serde_json:: { Value , json} ;
44use clap:: Parser ;
55use 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}
0 commit comments