use crate::types::MAX_PHRASES_PER_LANGUAGE;
use crate::*;
use crate::{FxHashMap, FxHashSet};
impl Resolver {
pub fn add_intent(&mut self, id: &str, seeds: impl Into<IntentSeeds>) -> Result<usize, Error> {
let seeds_by_lang: FxHashMap<String, Vec<String>> = match seeds.into() {
IntentSeeds::Mono(phrases) => {
let mut m = FxHashMap::default();
m.insert("en".to_string(), phrases);
m
}
IntentSeeds::Multi(m) => m.into_iter().collect(),
};
let truncated: FxHashMap<String, Vec<String>> = seeds_by_lang
.into_iter()
.map(|(lang, seeds)| {
let limited: Vec<String> =
seeds.into_iter().take(MAX_PHRASES_PER_LANGUAGE).collect();
(lang, limited)
})
.collect();
let mut total_phrases = 0usize;
for phrases in truncated.values() {
for phrase in phrases {
self.index_phrase_no_rebuild(id, phrase);
total_phrases += 1;
}
}
self.training
.insert(id.to_string(), truncated.into_iter().collect());
self.rebuild_l0();
self.version += 1;
Ok(total_phrases)
}
pub fn remove_phrase(&mut self, intent_id: &str, seed: &str) -> bool {
let training = match self.training.get_mut(intent_id) {
Some(t) => t,
None => return false,
};
let mut found = false;
for phrases in training.values_mut() {
if let Some(pos) = phrases.iter().position(|s| s == seed) {
phrases.remove(pos);
found = true;
break;
}
}
if !found {
return false;
}
training.retain(|_, phrases| !phrases.is_empty());
self.rebuild_l2();
self.version += 1;
true
}
pub fn remove_intent(&mut self, id: &str) {
self.training.remove(id);
self.intent_types.remove(id);
self.descriptions.remove(id);
self.instructions.remove(id);
self.persona.remove(id);
self.sources.remove(id);
self.targets.remove(id);
self.schemas.remove(id);
self.guardrails.remove(id);
self.rebuild_l2();
self.version += 1;
}
pub fn check_phrase(&self, intent_id: &str, seed: &str) -> PhraseCheckResult {
if seed.trim().is_empty() {
return PhraseCheckResult {
added: false,
redundant: false,
warning: Some("Phrase is empty".to_string()),
};
}
let is_duplicate = self
.training
.get(intent_id)
.map(|m| m.values().any(|phrases| phrases.iter().any(|p| p == seed)))
.unwrap_or(false);
PhraseCheckResult {
added: false,
redundant: is_duplicate,
warning: if is_duplicate {
Some("Phrase already exists in this intent".to_string())
} else {
None
},
}
}
pub fn add_phrase_checked(
&mut self,
intent_id: &str,
seed: &str,
lang: &str,
) -> PhraseCheckResult {
let mut result = self.check_phrase(intent_id, seed);
if result.warning.as_deref() == Some("Phrase is empty") {
return result;
}
if result.redundant {
return result;
}
if let Some(lang_map) = self.training.get(intent_id) {
if let Some(seeds) = lang_map.get(lang) {
if seeds.len() >= MAX_PHRASES_PER_LANGUAGE {
result.warning = Some(format!(
"Language '{}' has reached max {} phrases",
lang, MAX_PHRASES_PER_LANGUAGE
));
return result;
}
}
}
result.added = self.add_phrase(intent_id, seed, lang);
result
}
pub(crate) fn add_phrase(&mut self, intent_id: &str, seed: &str, lang: &str) -> bool {
let lang_map = match self.training.get_mut(intent_id) {
Some(m) => m,
None => return false,
};
let seeds = lang_map.entry(lang.to_string()).or_default();
if seeds.len() >= MAX_PHRASES_PER_LANGUAGE {
return false;
}
if seeds.iter().any(|s| s == seed) {
return true;
}
seeds.push(seed.to_string());
self.index_phrase(intent_id, seed);
self.version += 1;
true
}
pub fn intent_namespace(id: &str) -> Option<&str> {
let colon = id.find(':')?;
Some(&id[..colon])
}
pub fn list_namespaces(&self) -> Vec<String> {
let mut set = FxHashSet::default();
for id in self.training.keys() {
if let Some(ns) = Self::intent_namespace(id) {
set.insert(ns.to_string());
}
}
let mut v: Vec<String> = set.into_iter().collect();
v.sort();
v
}
pub fn intents_in_namespace(&self, ns: &str) -> Vec<String> {
let prefix = format!("{}:", ns);
let mut ids: Vec<String> = self
.training
.keys()
.filter(|id| id.starts_with(&prefix))
.cloned()
.collect();
ids.sort();
ids
}
}