use maud::html;
use crate::backends::gemini::decode_transcript_bytes;
use crate::filesystem::Filesystem;
use crate::types::TranscriptRole;
use super::dom;
use super::templates;
use super::APP;
const HISTORY_FILE: &str = ".lh_history.json";
pub(crate) async fn load_into_pending() {
let fs = super::shared_opfs();
let bytes = match fs.read(HISTORY_FILE).await {
Ok(b) if !b.is_empty() => b,
_ => return,
};
let bytes = super::encryption::open(&bytes).await.unwrap_or(bytes);
match decode_transcript_bytes(&bytes) {
Ok(entries) if !entries.is_empty() => {
paint_entries(&entries);
dom::scroll_to_bottom_soon("transcript");
}
Ok(_) => {
}
Err(err) => {
web_sys::console::warn_1(&wasm_bindgen::JsValue::from_str(&format!(
"history decode: {err}"
)));
}
}
APP.with(|cell| cell.borrow_mut().pending_history = Some(bytes));
}
pub(crate) async fn save_from_agent() {
let bytes = APP.with(|cell| {
cell.borrow()
.agent
.as_ref()
.and_then(|a| a.history_bytes().ok().flatten())
});
let Some(bytes) = bytes else { return };
let fs = super::shared_opfs();
let data = super::encryption::seal(&bytes).await.unwrap_or(bytes);
if let Err(err) = fs.write_atomic(HISTORY_FILE, &data).await {
web_sys::console::warn_1(&wasm_bindgen::JsValue::from_str(&format!(
"history save: {err}"
)));
}
}
pub(crate) fn take_pending() -> Option<Vec<u8>> {
APP.with(|cell| cell.borrow_mut().pending_history.take())
}
pub(crate) fn paint_entries(entries: &[crate::types::TranscriptEntry]) {
for entry in entries {
for tc in &entry.tool_calls {
let seg_id = APP.with(|cell| cell.borrow_mut().alloc_id());
let call = crate::types::ToolCall {
name: tc.name.clone(),
id: None,
args: tc.args.clone(),
canonical_path: None,
};
let mut block = templates::tool_call_block(seg_id, &call).into_string();
if tc.result.is_some() || tc.error.is_some() {
let result = crate::types::ToolResult {
name: tc.name.clone(),
id: None,
result: tc.result.clone(),
error: tc.error.clone(),
};
let result_html = templates::tool_call_result(&result).into_string();
let result_slot = format!("id=\"tool-{seg_id}-result\"");
block = block.replace(
&format!("{result_slot}></div>"),
&format!("{result_slot}>{result_html}</div>"),
);
let final_class = if tc.error.is_none() {
"tc-status ok"
} else {
"tc-status err"
};
block = block.replace("tc-status running", final_class);
} else {
block = block.replace("tc-status running", "tc-status err");
}
dom::append_html("transcript", &block);
}
if !entry.text.is_empty() {
let turn_id = APP.with(|cell| cell.borrow_mut().alloc_id());
let role = entry.role.as_str();
let body = match entry.role {
TranscriptRole::User => html! { (entry.text) },
TranscriptRole::Assistant => templates::rendered_markdown(&entry.text),
};
let html_str = templates::turn(turn_id, role, body, false).into_string();
dom::append_html("transcript", &html_str);
}
}
}
pub(crate) async fn clear_persisted() {
let fs = super::shared_opfs();
if let Err(err) = fs.write_atomic(HISTORY_FILE, &[]).await {
web_sys::console::warn_1(&wasm_bindgen::JsValue::from_str(&format!(
"history clear: {err}"
)));
}
}