#![allow(dead_code)]
use anyhow::Result;
use chrono::Utc;
use std::io::{BufRead, Write};
use crate::config::Config;
use crate::store::EpisodeStore;
pub async fn run(feedback_type: &str, episodes: Option<String>, _config: &Config) -> Result<()> {
let store = EpisodeStore::new()?;
let episode_ids = match episodes {
Some(ids) if ids.to_lowercase() == "last" => {
get_last_retrieved_ids()?
}
Some(ids) => {
ids.split(',').map(|s| s.trim().to_string()).collect()
}
None => {
println!("No episodes specified. Use --episodes <id1,id2,...> or --episodes last");
return Ok(());
}
};
if episode_ids.is_empty() {
println!("No episodes to provide feedback for.");
return Ok(());
}
let is_helpful = match feedback_type.to_lowercase().as_str() {
"helpful" | "yes" | "y" | "1" | "good" => Some(true),
"not-helpful" | "unhelpful" | "no" | "n" | "0" | "bad" => Some(false),
"mixed" | "partial" | "skip" => None,
_ => {
println!(
"Unknown feedback type: {}. Use 'helpful', 'not-helpful', or 'mixed'.",
feedback_type
);
return Ok(());
}
};
println!(
"📝 Recording feedback for {} episode(s)...",
episode_ids.len()
);
let mut updated = 0;
for id in &episode_ids {
match update_episode_feedback(&store, id, is_helpful) {
Ok(_) => {
updated += 1;
let feedback_str = match is_helpful {
Some(true) => "✅ helpful",
Some(false) => "❌ not helpful",
None => "➖ mixed/skipped",
};
println!(" {} -> {}", &id[..8.min(id.len())], feedback_str);
}
Err(e) => {
println!(" {} -> ⚠️ failed: {}", &id[..8.min(id.len())], e);
}
}
}
println!("\n✅ Updated {} episode(s)", updated);
log_feedback(&episode_ids, is_helpful)?;
Ok(())
}
fn update_episode_feedback(store: &EpisodeStore, id: &str, is_helpful: Option<bool>) -> Result<()> {
let mut episode = store.load(id)?;
if let Some(last_retrieval) = episode.retrieval_history.last_mut() {
last_retrieval.was_helpful = is_helpful;
}
if let Some(true) = is_helpful {
episode.utility.helpful_count += 1;
}
episode.utility.score = Some(episode.utility.calculate_score());
store.update(&episode)?;
Ok(())
}
fn get_last_retrieved_ids() -> Result<Vec<String>> {
let feedback_log = Config::feedback_log_path()?;
if !feedback_log.exists() {
return Ok(vec![]);
}
let file = std::fs::File::open(&feedback_log)?;
let reader = std::io::BufReader::new(file);
let mut last_ids = String::new();
for line in reader.lines().flatten() {
if line.contains("ids:") {
last_ids = line;
}
}
if let Some(ids_part) = last_ids.split("ids:").nth(1) {
let ids: Vec<String> = ids_part
.trim()
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
return Ok(ids);
}
Ok(vec![])
}
fn log_feedback(episode_ids: &[String], is_helpful: Option<bool>) -> Result<()> {
let feedback_log = Config::feedback_log_path()?;
let feedback_str = match is_helpful {
Some(true) => "helpful",
Some(false) => "not-helpful",
None => "mixed",
};
let log_entry = format!(
"{}\tfeedback:{}\tids:{}\n",
Utc::now().to_rfc3339(),
feedback_str,
episode_ids.join(",")
);
std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(feedback_log)?
.write_all(log_entry.as_bytes())?;
Ok(())
}
pub fn batch_feedback(
store: &EpisodeStore,
episode_ids: &[String],
is_helpful: bool,
) -> Result<usize> {
let mut updated = 0;
for id in episode_ids {
if update_episode_feedback(store, id, Some(is_helpful)).is_ok() {
updated += 1;
}
}
log_feedback(episode_ids, Some(is_helpful))?;
Ok(updated)
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn test_parse_feedback_type() {
let test_cases = [
("helpful", Some(true)),
("yes", Some(true)),
("not-helpful", Some(false)),
("no", Some(false)),
("mixed", None),
("skip", None),
];
for (input, expected) in test_cases {
let result = match input.to_lowercase().as_str() {
"helpful" | "yes" | "y" | "1" | "good" => Some(true),
"not-helpful" | "unhelpful" | "no" | "n" | "0" | "bad" => Some(false),
"mixed" | "partial" | "skip" => None,
_ => None,
};
assert_eq!(result, expected, "Failed for input: {}", input);
}
}
}