(value: &Value)
| 419 | } |
| 420 | |
| 421 | fn visible_text_from_content(value: &Value) -> Option<String> { |
| 422 | match value { |
| 423 | Value::String(text) => Some(text.clone()), |
| 424 | Value::Array(items) => { |
| 425 | let parts = items |
| 426 | .iter() |
| 427 | .filter_map(visible_text_from_content) |
| 428 | .filter(|text| !text.trim().is_empty()) |
| 429 | .collect::<Vec<_>>(); |
| 430 | (!parts.is_empty()).then(|| parts.join("\n\n")) |
| 431 | } |
| 432 | Value::Object(map) => { |
| 433 | for key in ["text", "content", "message"] { |
| 434 | if let Some(text) = map.get(key).and_then(Value::as_str) { |
| 435 | return Some(text.to_string()); |
| 436 | } |
| 437 | } |
| 438 | None |
| 439 | } |
| 440 | _ => None, |
| 441 | } |
| 442 | } |
| 443 | |
| 444 | /// Build a session title from the first user message, if any. |
| 445 | pub(crate) fn title_from_messages(messages: &[SessionMessageRecord]) -> Option<String> { |
no test coverage detected