use std::collections::HashSet;
use serde::{Deserialize, Serialize};
use oxyde_core::{OxydeError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum IntentType {
Question,
Greeting,
Command,
Chat,
Proximity,
Friendly,
Hostile,
Threat,
Request,
Demand,
Query,
Custom,
}
impl IntentType {
pub fn from_str(s: &str) -> Self {
match s.to_lowercase().as_str() {
"question" | "query" => Self::Question,
"greeting" => Self::Greeting,
"command" => Self::Command,
"chat" => Self::Chat,
"proximity" => Self::Proximity,
"friendly" => Self::Friendly,
"hostile" => Self::Hostile,
"threat" => Self::Threat,
"request" => Self::Request,
"demand" => Self::Demand,
_ => Self::Custom,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Question => "question",
Self::Greeting => "greeting",
Self::Command => "command",
Self::Chat => "chat",
Self::Proximity => "proximity",
Self::Friendly => "friendly",
Self::Hostile => "hostile",
Self::Threat => "threat",
Self::Request => "request",
Self::Demand => "demand",
Self::Query => "query",
Self::Custom => "custom",
}
}
}
impl std::fmt::Display for IntentType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Intent {
pub intent_type: IntentType,
pub confidence: f64,
pub raw_input: String,
pub keywords: Vec<String>,
}
impl Intent {
pub fn new(
intent_type: IntentType,
confidence: f64,
raw_input: &str,
keywords: Vec<String>,
) -> Self {
Self {
intent_type,
confidence: confidence.clamp(0.0, 1.0),
raw_input: raw_input.to_string(),
keywords,
}
}
pub fn proximity(distance: f32) -> Self {
Self::new(
IntentType::Proximity,
1.0,
"",
vec![format!("distance:{}", distance)],
)
}
pub fn from_chat(text: &str) -> Self {
let keywords = Self::extract_keywords(text);
let intent_type = if text.ends_with("?") {
IntentType::Question
} else if Self::is_greeting(text) {
IntentType::Greeting
} else if Self::is_command(text) {
IntentType::Command
} else {
IntentType::Chat
};
Self::new(
intent_type,
0.8, text,
keywords,
)
}
pub async fn analyze(input: &str) -> Result<Self> {
if input.is_empty() {
return Err(OxydeError::IntentError("Empty input".to_string()));
}
Ok(Self::from_chat(input))
}
pub fn extract_keywords(text: &str) -> Vec<String> {
let mut keywords = Vec::new();
let stopwords: HashSet<&str> = [
"the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for",
"with", "by", "about", "against", "between", "into", "through",
"is", "are", "was", "were", "be", "been", "being",
"i", "you", "he", "she", "it", "we", "they",
"my", "your", "his", "her", "its", "our", "their",
].iter().cloned().collect();
for word in text.split_whitespace() {
let clean_word = word.trim_matches(|c: char| !c.is_alphanumeric()).to_lowercase();
if clean_word.len() > 2 && !stopwords.contains(clean_word.as_str()) {
keywords.push(clean_word);
}
}
keywords
}
fn is_greeting(text: &str) -> bool {
let greetings = [
"hello", "hi", "hey", "greetings", "good morning",
"good afternoon", "good evening", "howdy", "sup",
"what's up", "hiya",
];
let text_lower = text.to_lowercase();
greetings.iter().any(|g| {
text_lower.starts_with(g) ||
text_lower.split_whitespace().any(|word| word == *g)
})
}
fn is_command(text: &str) -> bool {
let command_prefixes = [
"follow", "go", "attack", "defend", "run", "wait",
"stop", "help", "give", "take", "use", "open",
"close", "find", "look", "examine", "talk",
];
let text_lower = text.to_lowercase();
command_prefixes.iter().any(|c| text_lower.starts_with(c))
}
pub fn has_keyword(&self, keyword: &str) -> bool {
self.keywords.iter().any(|k| k == keyword)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_intent_from_chat() {
let greeting = Intent::from_chat("Hello there!");
assert_eq!(greeting.intent_type, IntentType::Greeting);
let question = Intent::from_chat("What is your name?");
assert_eq!(question.intent_type, IntentType::Question);
let command = Intent::from_chat("follow me");
assert_eq!(command.intent_type, IntentType::Command);
let chat = Intent::from_chat("I like this village.");
assert_eq!(chat.intent_type, IntentType::Chat);
}
#[test]
fn test_keyword_extraction() {
let keywords = Intent::extract_keywords("What is the capital of France?");
assert!(keywords.contains(&"capital".to_string()));
assert!(keywords.contains(&"france".to_string()));
assert!(!keywords.contains(&"is".to_string())); }
}