memoryd: recall's local items on one indented line, as Honcho's
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use bxcore::sanitize::neutralise;
|
||||
use bxcore::{DataClass, RecallArgs, Timestamp};
|
||||
use rusqlite::params;
|
||||
|
||||
@@ -20,6 +21,38 @@ pub struct Recalled {
|
||||
pub untrusted: bool,
|
||||
}
|
||||
|
||||
/// The local index's hits, each rendered as an item, numbered from 1, and the label they give
|
||||
/// the answer: `Private` and trusted when there are none.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Local {
|
||||
pub items: Vec<String>,
|
||||
pub class: DataClass,
|
||||
pub untrusted: bool,
|
||||
}
|
||||
|
||||
/// A hit's text as an item holds it, from the local index or from Honcho: neutralised, every
|
||||
/// control character (a line break among them) and every Unicode line or paragraph separator a
|
||||
/// space, then cut to its first `SNIPPET` characters, with no mark.
|
||||
pub fn snippet(text: &str) -> String {
|
||||
neutralise(text)
|
||||
.chars()
|
||||
.map(|c| {
|
||||
if c.is_control() || c == '\u{2028}' || c == '\u{2029}' {
|
||||
' '
|
||||
} else {
|
||||
c
|
||||
}
|
||||
})
|
||||
.take(SNIPPET)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// An item as the model reads it: its header, then its text on one line indented by two spaces,
|
||||
/// so that no text can begin a line the way a header does (M5c spec, section 8; tip T37).
|
||||
pub fn item(header: &str, text: &str) -> String {
|
||||
format!("{header}\n {text}")
|
||||
}
|
||||
|
||||
/// The FTS5 query for `query`: every run of letters and digits, double-quoted, joined by `OR`.
|
||||
/// `None` when there is no such run.
|
||||
pub fn terms(query: &str) -> Option<String> {
|
||||
@@ -140,18 +173,39 @@ pub fn recall(
|
||||
session: &str,
|
||||
now: Timestamp,
|
||||
) -> Result<Recalled, String> {
|
||||
let found = local(index, store, args, session, now)?;
|
||||
if found.items.is_empty() {
|
||||
return Ok(Recalled {
|
||||
text: format!("(no matches for \"{}\")", args.query),
|
||||
class: DataClass::Private,
|
||||
untrusted: false,
|
||||
});
|
||||
}
|
||||
Ok(Recalled {
|
||||
text: found.items.join("\n"),
|
||||
class: found.class,
|
||||
untrusted: found.untrusted,
|
||||
})
|
||||
}
|
||||
|
||||
/// Search the local index for `args` on behalf of `session`, log the recall, and give its hits
|
||||
/// as items with their label. `recall` answers with them alone; with Honcho, its hits follow.
|
||||
pub fn local(
|
||||
index: &Index,
|
||||
store: &Store,
|
||||
args: &RecallArgs,
|
||||
session: &str,
|
||||
now: Timestamp,
|
||||
) -> Result<Local, String> {
|
||||
// 1. `args.check()?`.
|
||||
// 2. hits: `terms(&args.query)` None -> none; Some(fts) -> `search(index, &fts, args.k)?`.
|
||||
// 3. Log it: a Logged { time: now.to_rfc3339(), session, query, hits: a LoggedHit (kind,
|
||||
// session, epoch, record) per hit } as one JSON line appended to
|
||||
// format!("recalls/{month}.jsonl") (month: the first 7 characters of now.to_rfc3339()),
|
||||
// after `create_dir_all(store.path("recalls"))`.
|
||||
// 4. No hits: Ok(Recalled { text: format!("(no matches for \"{}\")", args.query), class
|
||||
// Private, untrusted false }).
|
||||
// 5. Otherwise, for each hit with i from 0: class = class.max(its class) starting at Private;
|
||||
// untrusted |= its tag is "memory-tainted"; its text cut to its first SNIPPET characters;
|
||||
// the item is format!("{}\n{text}", hit.header(i + 1)). Ok(Recalled { the items joined by
|
||||
// newlines, class, untrusted }).
|
||||
// 4. For each hit with i from 0: class = class.max(its class) starting at Private;
|
||||
// untrusted |= its tag is "memory-tainted"; the item is `item(&hit.header(i + 1),
|
||||
// &snippet(&hit.text))`. Ok(Local { items, class, untrusted }).
|
||||
args.check()?;
|
||||
let hits: Vec<Hit> = match terms(&args.query) {
|
||||
None => Vec::new(),
|
||||
@@ -176,24 +230,16 @@ pub fn recall(
|
||||
};
|
||||
let line = serde_json::to_string(&logged).map_err(|e| format!("recall: {e}"))?;
|
||||
store.append(&format!("recalls/{month}.jsonl"), &format!("{line}\n"))?;
|
||||
if hits.is_empty() {
|
||||
return Ok(Recalled {
|
||||
text: format!("(no matches for \"{}\")", args.query),
|
||||
class: DataClass::Private,
|
||||
untrusted: false,
|
||||
});
|
||||
}
|
||||
let mut class = DataClass::Private;
|
||||
let mut untrusted = false;
|
||||
let mut items: Vec<String> = Vec::with_capacity(hits.len());
|
||||
for (i, hit) in hits.iter().enumerate() {
|
||||
class = class.max(hit.class);
|
||||
untrusted |= hit.tag() == "memory-tainted";
|
||||
let text: String = hit.text.chars().take(SNIPPET).collect();
|
||||
items.push(format!("{}\n{text}", hit.header(i + 1)));
|
||||
items.push(item(&hit.header(i + 1), &snippet(&hit.text)));
|
||||
}
|
||||
Ok(Recalled {
|
||||
text: items.join("\n"),
|
||||
Ok(Local {
|
||||
items,
|
||||
class,
|
||||
untrusted,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user