If this is useful, a ❤️ helps others find it.
All tests run on an 8-year-old MacBook Air.
The same error appears twice. Most AI tools diagnose it twice — two API calls, same answer.
HiyokoHelper remembers. When the same error appears again, it responds instantly from cache: "💡 先日も同じケースが発生し、〇〇で解決しました"
Here's how the history cache works.
The data structure
#[derive(Serialize, Deserialize, Clone)]
pub struct HistoryEntry {
pub error_hash: String,
pub error_preview: String,
pub diagnosis: String,
pub resolved: bool,
pub created_at: u64,
pub hit_count: u32,
}
Stored in history.json via tauri-plugin-store. Local only, never leaves the machine.
Normalizing before hashing
Same error, different timestamps → same hash:
pub fn normalize_error(text: &str) -> String {
let mut result = text.to_string();
let timestamp_re = Regex::new(r"\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}").unwrap();
result = timestamp_re.replace_all(&result, "[TIMESTAMP]").to_string();
let line_re = Regex::new(r"line \d+").unwrap();
result = line_re.replace_all(&result, "line [N]").to_string();
let pid_re = Regex::new(r"\bpid[: ]\d+").unwrap();
result = pid_re.replace_all(&result, "pid [PID]").to_string();
result.split_whitespace().collect::>().join(" ")
}
The lookup flow
pub async fn diagnose_with_history(
input: &str,
api_key: &str,
history: &mut HistoryCache,
) -> DiagnosisResult {
let hash = error_hash(input);
if let Some(entry) = history.get(&hash) {
entry.hit_count += 1;
let msg = if entry.resolved {
format!("💡 先日も同じエラーが発生し、解決済みです。\n\n{}", entry.diagnosis)
} else {
format!("⚠️ このエラーは以前も発生しています({}回目)。\n\n{}", entry.hit_count, entry.diagnosis)
};
return DiagnosisResult::FromHistory { diagnosis: entry.diagnosis.clone(), message: msg };
}
let diagnosis = call_gemini(input, api_key).await?;
history.insert(hash, HistoryEntry {
error_hash: hash.clone(),
error_preview: input.chars().take(100).collect(),
diagnosis: diagnosis.clone(),
resolved: false,
created_at: unix_now(),
hit_count: 1,
});
DiagnosisResult::Fresh { diagnosis }
}
The "resolved" button
pub fn mark_resolved(history: &mut HistoryCache, hash: &str) {
if let Some(entry) = history.get_mut(hash) {
entry.resolved = true;
}
history.save();
}
Next time: "You had this issue and resolved it. Here's what worked."
Cache eviction
Unresolved entries older than 30 days evicted. Resolved entries kept forever.
pub fn evict_old_entries(history: &mut HistoryCache) {
let cutoff = unix_now() - (30 * 24 * 60 * 60);
history.entries.retain(|_, entry| {
entry.created_at > cutoff || entry.resolved
});
}
HiyokoHelper (OSS) → github.com/hiyoyok/HiyokoHelper
X → @hiyoyok
Top comments (0)