use super::filter::{RawTriple, RecalledDrawer};
use super::{DRAWER_PREVIEW_CHARS, INJECTION_BYTE_CAP};
pub(super) fn compose_injection(
global_facts: Option<&str>,
drawers: &[RecalledDrawer],
triples: &[RawTriple],
palace_slug: Option<&str>,
) -> String {
let mut out = String::new();
if let Some(facts) = global_facts {
push_section(&mut out, facts.trim_end());
}
if !drawers.is_empty() {
let mut section = String::new();
if let Some(slug) = palace_slug {
section.push_str(&format!("## Relevant memories from palace `{slug}`\n"));
} else {
section.push_str("## Relevant memories\n");
}
for d in drawers {
section.push_str("- ");
section.push_str(&drawer_preview(&d.content));
if !d.tags.is_empty() {
section.push_str(" _(tags: ");
let tags = d
.tags
.iter()
.map(|t| format!("`{t}`"))
.collect::<Vec<_>>()
.join(", ");
section.push_str(&tags);
section.push(')');
section.push('_');
}
section.push('\n');
}
push_section(&mut out, section.trim_end());
}
if !triples.is_empty() {
let mut section = String::new();
section.push_str("## Relevant KG facts\n");
for t in triples {
section.push_str(&format!(
"- {} **{}** {}\n",
t.subject, t.predicate, t.object
));
}
push_section(&mut out, section.trim_end());
}
if out.len() > INJECTION_BYTE_CAP {
const ELLIPSIS: char = '…';
let ellipsis_len = ELLIPSIS.len_utf8();
let mut cut = INJECTION_BYTE_CAP.saturating_sub(ellipsis_len);
while cut > 0 && !out.is_char_boundary(cut) {
cut -= 1;
}
out.truncate(cut);
out.push(ELLIPSIS);
}
out
}
pub(super) fn push_section(out: &mut String, section: &str) {
if section.is_empty() {
return;
}
if !out.is_empty() {
if !out.ends_with('\n') {
out.push('\n');
}
out.push('\n');
}
out.push_str(section);
}
pub(super) fn drawer_preview(content: &str) -> String {
let normalised: String = content.split_whitespace().collect::<Vec<_>>().join(" ");
if normalised.chars().count() <= DRAWER_PREVIEW_CHARS {
normalised
} else {
let kept: String = normalised
.chars()
.take(DRAWER_PREVIEW_CHARS.saturating_sub(1))
.collect();
format!("{kept}…")
}
}
pub(super) fn count_facts(body: &str) -> usize {
body.lines()
.filter(|l| l.trim_start().starts_with("- "))
.count()
}