better answer parsing

This commit is contained in:
filipriec
2025-06-16 11:14:04 +02:00
parent cccf029464
commit 8fcd28832d

View File

@@ -1,8 +1,6 @@
// src/components/common/autocomplete.rs // src/components/common/autocomplete.rs
use common::proto::multieko2::search::search_response::Hit; use common::proto::multieko2::search::search_response::Hit;
use serde::Deserialize;
// Keep all existing imports
use crate::config::colors::themes::Theme; use crate::config::colors::themes::Theme;
use ratatui::{ use ratatui::{
layout::Rect, layout::Rect,
@@ -10,14 +8,23 @@ use ratatui::{
widgets::{Block, List, ListItem, ListState}, widgets::{Block, List, ListItem, ListState},
Frame, Frame,
}; };
use std::collections::HashMap;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
// Helper struct for parsing the JSON inside a Hit /// Converts a serde_json::Value into a displayable String.
#[derive(Deserialize)] /// Handles String, Number, and Bool variants. Returns an empty string for Null and others.
struct SuggestionContent { fn json_value_to_string(value: &serde_json::Value) -> String {
name: String, match value {
serde_json::Value::String(s) => s.clone(),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
// Return an empty string for Null, Array, or Object so we can filter them out.
_ => String::new(),
} }
/// Renders an opaque dropdown list for autocomplete suggestions. }
/// Renders an opaque dropdown list for simple string-based suggestions.
/// This function remains unchanged.
pub fn render_autocomplete_dropdown( pub fn render_autocomplete_dropdown(
f: &mut Frame, f: &mut Frame,
input_rect: Rect, input_rect: Rect,
@@ -29,39 +36,32 @@ pub fn render_autocomplete_dropdown(
if suggestions.is_empty() { if suggestions.is_empty() {
return; return;
} }
// --- Calculate Dropdown Size & Position --- let max_suggestion_width =
let max_suggestion_width = suggestions.iter().map(|s| s.width()).max().unwrap_or(0) as u16; suggestions.iter().map(|s| s.width()).max().unwrap_or(0) as u16;
let horizontal_padding: u16 = 2; let horizontal_padding: u16 = 2;
let dropdown_width = (max_suggestion_width + horizontal_padding).max(10); let dropdown_width = (max_suggestion_width + horizontal_padding).max(10);
let dropdown_height = (suggestions.len() as u16).min(5); let dropdown_height = (suggestions.len() as u16).min(5);
let mut dropdown_area = Rect { let mut dropdown_area = Rect {
x: input_rect.x, // Align horizontally with input x: input_rect.x,
y: input_rect.y + 1, // Position directly below input y: input_rect.y + 1,
width: dropdown_width, width: dropdown_width,
height: dropdown_height, height: dropdown_height,
}; };
// --- Clamping Logic (prevent rendering off-screen) ---
// Clamp vertically (if it goes below the frame)
if dropdown_area.bottom() > frame_area.height { if dropdown_area.bottom() > frame_area.height {
dropdown_area.y = input_rect.y.saturating_sub(dropdown_height); // Try rendering above dropdown_area.y = input_rect.y.saturating_sub(dropdown_height);
} }
// Clamp horizontally (if it goes past the right edge)
if dropdown_area.right() > frame_area.width { if dropdown_area.right() > frame_area.width {
dropdown_area.x = frame_area.width.saturating_sub(dropdown_width); dropdown_area.x = frame_area.width.saturating_sub(dropdown_width);
} }
// Ensure x is not negative (if clamping pushes it left)
dropdown_area.x = dropdown_area.x.max(0); dropdown_area.x = dropdown_area.x.max(0);
// Ensure y is not negative (if clamping pushes it up)
dropdown_area.y = dropdown_area.y.max(0); dropdown_area.y = dropdown_area.y.max(0);
// --- End Clamping ---
// Render a solid background block first to ensure opacity let background_block =
let background_block = Block::default().style(Style::default().bg(Color::DarkGray)); Block::default().style(Style::default().bg(Color::DarkGray));
f.render_widget(background_block, dropdown_area); f.render_widget(background_block, dropdown_area);
// Create list items, ensuring each has a defined background
let items: Vec<ListItem> = suggestions let items: Vec<ListItem> = suggestions
.iter() .iter()
.enumerate() .enumerate()
@@ -69,54 +69,84 @@ pub fn render_autocomplete_dropdown(
let is_selected = selected_index == Some(i); let is_selected = selected_index == Some(i);
let s_width = s.width() as u16; let s_width = s.width() as u16;
let padding_needed = dropdown_width.saturating_sub(s_width); let padding_needed = dropdown_width.saturating_sub(s_width);
let padded_s = format!("{}{}", s, " ".repeat(padding_needed as usize)); let padded_s =
format!("{}{}", s, " ".repeat(padding_needed as usize));
ListItem::new(padded_s).style(if is_selected { ListItem::new(padded_s).style(if is_selected {
Style::default() Style::default()
.fg(theme.bg) // Text color on highlight .fg(theme.bg)
.bg(theme.highlight) // Highlight background .bg(theme.highlight)
.add_modifier(Modifier::BOLD) .add_modifier(Modifier::BOLD)
} else { } else {
// Style for non-selected items (matching background block) Style::default().fg(theme.fg).bg(Color::DarkGray)
Style::default()
.fg(theme.fg) // Text color on gray
.bg(Color::DarkGray) // Explicit gray background
}) })
}) })
.collect(); .collect();
// Create the list widget (without its own block)
let list = List::new(items); let list = List::new(items);
// State for managing selection highlight (still needed for logic)
let mut profile_list_state = ListState::default(); let mut profile_list_state = ListState::default();
profile_list_state.select(selected_index); profile_list_state.select(selected_index);
// Render the list statefully *over* the background block
f.render_stateful_widget(list, dropdown_area, &mut profile_list_state); f.render_stateful_widget(list, dropdown_area, &mut profile_list_state);
} }
// --- NEW FUNCTION FOR RICH SUGGESTIONS --- // --- MODIFIED FUNCTION FOR RICH SUGGESTIONS ---
/// Renders an opaque dropdown list for rich `Hit`-based suggestions. /// Renders an opaque dropdown list for rich `Hit`-based suggestions.
/// Displays the value of the first meaningful column, followed by the Hit ID.
pub fn render_rich_autocomplete_dropdown( pub fn render_rich_autocomplete_dropdown(
f: &mut Frame, f: &mut Frame,
input_rect: Rect, input_rect: Rect,
frame_area: Rect, frame_area: Rect,
theme: &Theme, theme: &Theme,
suggestions: &[Hit], // <-- Accepts &[Hit] suggestions: &[Hit],
selected_index: Option<usize>, selected_index: Option<usize>,
) { ) {
if suggestions.is_empty() { if suggestions.is_empty() {
return; return;
} }
// --- Get display names from Hits, with a fallback for parsing errors ---
let display_names: Vec<String> = suggestions let display_names: Vec<String> = suggestions
.iter() .iter()
.map(|hit| { .map(|hit| {
serde_json::from_str::<SuggestionContent>(&hit.content_json) // Use serde_json::Value to handle mixed types (string, null, etc.)
.map(|content| content.name) if let Ok(content_map) =
.unwrap_or_else(|_| format!("ID: {}", hit.id)) // Fallback display serde_json::from_str::<HashMap<String, serde_json::Value>>(
&hit.content_json,
)
{
// Define keys to ignore for a cleaner display
const IGNORED_KEYS: &[&str] = &["id", "deleted", "created_at"];
// Get keys, filter out ignored ones, and sort for consistency
let mut keys: Vec<_> = content_map
.keys()
.filter(|k| !IGNORED_KEYS.contains(&k.as_str()))
.cloned()
.collect();
keys.sort();
// Get only the first non-empty value from the sorted keys
let values: Vec<_> = keys
.iter()
.map(|key| {
content_map
.get(key)
.map(json_value_to_string)
.unwrap_or_default()
})
.filter(|s| !s.is_empty()) // Filter out null/empty values
.take(1) // Changed from take(2) to take(1)
.collect();
let display_part = values.first().cloned().unwrap_or_default(); // Get the first value
if display_part.is_empty() {
format!("ID: {}", hit.id)
} else {
format!("{} | ID: {}", display_part, hit.id) // ID at the end
}
} else {
format!("ID: {} (parse error)", hit.id)
}
}) })
.collect(); .collect();
@@ -134,7 +164,7 @@ pub fn render_rich_autocomplete_dropdown(
height: dropdown_height, height: dropdown_height,
}; };
// --- Clamping Logic (prevent rendering off-screen) --- // --- Clamping Logic ---
if dropdown_area.bottom() > frame_area.height { if dropdown_area.bottom() > frame_area.height {
dropdown_area.y = input_rect.y.saturating_sub(dropdown_height); dropdown_area.y = input_rect.y.saturating_sub(dropdown_height);
} }
@@ -176,4 +206,3 @@ pub fn render_rich_autocomplete_dropdown(
f.render_stateful_widget(list, dropdown_area, &mut list_state); f.render_stateful_widget(list, dropdown_area, &mut list_state);
} }