fur_cli/commands/status/
render.rs1use crate::frs::avatars::resolve_avatar;
2use colored::*;
3use serde_json::Value;
4use std::collections::HashMap;
5
6type Map = HashMap<String, Value>;
7
8pub fn print_active_conversation(index: &Value, conversation: &Value) {
10 let fallback_title = conversation["title"].as_str().unwrap_or("Untitled");
11
12 let title = index["title"].as_str().unwrap_or(fallback_title);
13
14 println!(
15 "{} {} {}",
16 "Active conversation:".bright_cyan().bold(),
17 title.bright_green().bold(),
18 format!("({})", index["active_thread"].as_str().unwrap_or("?")).bright_black()
19 );
20}
21
22pub fn print_current_message(current_msg_id: &str) {
24 println!(
25 "{} {}",
26 "Current message:".bright_cyan().bold(),
27 current_msg_id.bright_black()
28 );
29}
30
31pub fn print_lineage(map: &HashMap<String, Value>, current: &str, avatars: &Value) {
33 let mut chain = vec![];
34 let mut cur = current.to_string();
35
36 while let Some(msg) = map.get(&cur) {
37 chain.push(cur.clone());
38 match msg["parent"].as_str() {
39 Some(pid) => cur = pid.to_string(),
40 None => break,
41 }
42 }
43
44 chain.reverse();
45
46 for mid in chain {
47 if let Some(msg) = map.get(&mid) {
48 let avatar_key = msg["avatar"].as_str().unwrap_or("???");
49 let (name, emoji) = resolve_avatar(avatars, avatar_key);
50
51 let preview = crate::renderer::utils::preview_of(msg, 40);
52
53 let marker = if mid == current {
54 "(current)".cyan().bold()
55 } else {
56 "✅".green()
57 };
58
59 let branch_label = compute_branch_label(&mid, map);
60
61 println!(
62 "{} {} {} {} {} {}",
63 preview.white(),
64 emoji,
65 format!("[{}]", name).bright_yellow().bold(),
66 &mid[..8].bright_black(),
67 branch_label.bright_green(),
68 marker
69 );
70 }
71 }
72}
73
74pub fn print_next_messages(map: &Map, conversation: &Value, current: &str, avatars: &Value) {
75 let curr_msg = match map.get(current) {
76 Some(v) => v,
77 None => return println!("{}", "(No current message found.)".red()),
78 };
79
80 let next = get_children(curr_msg)
81 .or_else(|| get_sibling_branch(map, curr_msg, current))
82 .or_else(|| get_top_level_siblings(conversation, current))
83 .unwrap_or_default();
84
85 if next.is_empty() {
86 println!("{}", "(No further messages in this branch.)".bright_black());
87 return;
88 }
89
90 for cid in next {
91 if let Some(msg) = map.get(&cid) {
92 render_preview(msg, avatars, &cid, map);
93 }
94 }
95}
96
97fn get_children(curr_msg: &Value) -> Option<Vec<String>> {
98 let arr = curr_msg["children"].as_array()?;
99 let v = arr
100 .iter()
101 .filter_map(|c| c.as_str().map(|s| s.to_string()))
102 .collect::<Vec<_>>();
103
104 if v.is_empty() {
105 None
106 } else {
107 Some(v)
108 }
109}
110
111fn get_sibling_branch(map: &Map, curr_msg: &Value, current: &str) -> Option<Vec<String>> {
112 let parent_id = curr_msg["parent"].as_str()?;
113 let parent = map.get(parent_id)?;
114
115 let blocks = parent["branches"].as_array()?;
116 for block in blocks {
117 if let Some(arr) = block.as_array() {
118 if let Some(pos) = arr.iter().position(|v| v.as_str() == Some(current)) {
119 let sibs = arr
120 .iter()
121 .skip(pos + 1)
122 .filter_map(|v| v.as_str().map(|s| s.to_string()))
123 .collect::<Vec<_>>();
124 if !sibs.is_empty() {
125 return Some(sibs);
126 }
127 }
128 }
129 }
130
131 None
132}
133
134fn get_top_level_siblings(conversation: &Value, current: &str) -> Option<Vec<String>> {
135 let arr = conversation["messages"].as_array()?;
136
137 let pos = arr.iter().position(|v| v.as_str() == Some(current))?;
138 let v = arr
139 .iter()
140 .skip(pos + 1)
141 .filter_map(|v| v.as_str().map(|s| s.to_string()))
142 .collect::<Vec<_>>();
143
144 if v.is_empty() {
145 None
146 } else {
147 Some(v)
148 }
149}
150
151fn render_preview(msg: &Value, avatars: &Value, cid: &str, map: &Map) {
152 let avatar_key = msg["avatar"].as_str().unwrap_or("???");
153 let (name, emoji) = resolve_avatar(avatars, avatar_key);
154
155 let preview = crate::renderer::utils::preview_of(msg, 40);
156
157 let branch_label = compute_branch_label(cid, map);
158
159 println!(
160 "🔹 {} {} {} {} {}",
161 preview.white(),
162 emoji,
163 format!("[{}]", name).bright_yellow().bold(),
164 &cid[..8].bright_black(),
165 branch_label.bright_green(),
166 );
167}
168
169pub fn compute_branch_label(msg_id: &str, map: &HashMap<String, Value>) -> String {
171 let mut labels = vec![];
172 let mut cur = msg_id;
173
174 while let Some(msg) = map.get(cur) {
175 if let Some(pid) = msg["parent"].as_str() {
176 if let Some(parent) = map.get(pid) {
177 if let Some(blocks) = parent["branches"].as_array() {
178 for (i, block) in blocks.iter().enumerate() {
179 if let Some(arr) = block.as_array() {
180 if arr.iter().any(|v| v.as_str() == Some(cur)) {
181 labels.push(format!("{}", i + 1));
182 }
183 }
184 }
185 }
186 cur = pid;
187 continue;
188 }
189 }
190 break;
191 }
192
193 labels.reverse();
194 if labels.is_empty() {
195 "[Root]".to_string()
196 } else {
197 format!("[Branch {}]", labels.join("."))
198 }
199}