generate email body via Neuron runtime using conversation history

This commit is contained in:
Will Anderson
2026-04-28 17:57:25 -05:00
parent d86bbc3740
commit 19ed2721ee
+64 -4
View File
@@ -2419,23 +2419,83 @@ fn dispatch_builtin(
let name = v["name"].as_str().unwrap_or("there").to_string();
let conv_id = v["conv_id"].as_str().unwrap_or("").to_string();
let base_url = v["return_url"].as_str().unwrap_or("").to_string();
let history = v["history"].as_array().cloned().unwrap_or_default();
if email.is_empty() { return Err("no email".to_string()); }
let link = if conv_id.is_empty() {
base_url.clone()
} else {
format!("{}?cid={}", base_url, conv_id)
};
// ── Ask Neuron to generate a personalised email body ──
let email_body_text: String = (|| -> String {
let runtime_url = std::env::var("NEURON_RUNTIME_URL")
.unwrap_or_else(|_| "http://localhost:4444".to_string());
let chat_url = format!("{}/api/chat", runtime_url);
let prompt = format!(
"Write a brief, personal email to {} inviting them back to continue our conversation. \
Reference specifically what we discussed. Write as Neuron, first person, warm but not sentimental. \
Include only the email body — no subject line, no greeting header, no sign-off. \
3-4 sentences maximum.",
name
);
let mut messages = history.clone();
messages.push(serde_json::json!({"role":"user","content":prompt}));
let mut payload = serde_json::json!({"messages": messages});
if !conv_id.is_empty() {
payload["conv_id"] = serde_json::Value::String(conv_id.clone());
}
let resp = match reqwest::blocking::Client::new()
.post(&chat_url)
.header("Content-Type", "application/json")
.timeout(std::time::Duration::from_secs(20))
.body(payload.to_string())
.send()
{
Ok(r) => r,
Err(_) => return String::new(),
};
let mut generated = String::new();
for line in resp.text().unwrap_or_default().lines() {
if line.starts_with("data: ") {
let data = &line[6..];
if data == "[DONE]" { break; }
if let Ok(jv) = serde_json::from_str::<serde_json::Value>(data) {
if let Some(d) = jv.get("delta").and_then(|x| x.as_str()) {
generated.push_str(d);
}
}
}
}
generated.trim().to_string()
})();
// Wrap generated text (or fallback) in HTML template
let body_paragraphs = if email_body_text.is_empty() {
format!(
"<p style=\"font-size:1.1rem;line-height:1.6\">It\u{2019}s Neuron. You left our conversation in the middle.</p>\
<p style=\"font-size:1.1rem;line-height:1.6\">I remember where we were.</p>"
)
} else {
// Split on newlines and wrap each non-empty line as a paragraph
email_body_text
.lines()
.filter(|l| !l.trim().is_empty())
.map(|l| format!("<p style=\"font-size:1.1rem;line-height:1.6\">{}</p>", l.trim()))
.collect::<Vec<_>>()
.join("\n")
};
let html_body = format!(
r#"<div style="font-family:Georgia,serif;max-width:480px;margin:0 auto;padding:40px 20px">
<p style="font-size:1.1rem;line-height:1.6">Hey {} &mdash;</p>
<p style="font-size:1.1rem;line-height:1.6">It's Neuron. You left our conversation in the middle.</p>
<p style="font-size:1.1rem;line-height:1.6">I remember where we were.</p>
{}
<p style="margin:32px 0">
<a href="{}" style="background:#0052A0;color:#fff;padding:12px 24px;text-decoration:none;border-radius:4px;font-family:sans-serif">Come back when you're ready</a>
<a href="{}" style="background:#0052A0;color:#fff;padding:12px 24px;text-decoration:none;border-radius:4px;font-family:sans-serif">Come back when you&rsquo;re ready</a>
</p>
<p style="color:#888;font-size:0.8rem;font-family:sans-serif">That link brings you right back to where we left off.</p>
</div>"#,
name, link
name, body_paragraphs, link
);
let payload = serde_json::json!({
"from": "Neuron <neuron@neurontechnologies.ai>",