use phantomdev_core::{CodeBlock, DetectionResult, Detector, Pattern, PatternType, StealthScore};
use std::sync::Arc;
pub struct PhantomDetector {
#[allow(dead_code)]
config: DetectorConfig,
local_model: Option<Arc<dyn LocalModel>>,
cloud_client: Option<Arc<dyn CloudClient>>,
}
#[derive(Clone)]
pub struct DetectorConfig {
pub use_local: bool,
pub use_cloud_fallback: bool,
pub threshold: f32,
}
impl Default for DetectorConfig {
fn default() -> Self {
Self {
use_local: true,
use_cloud_fallback: true,
threshold: 0.15,
}
}
}
impl PhantomDetector {
pub fn new() -> anyhow::Result<Self> {
Self::with_config(DetectorConfig::default())
}
pub fn with_config(config: DetectorConfig) -> anyhow::Result<Self> {
let local_model: Option<Arc<dyn LocalModel>> = if config.use_local {
Some(Arc::new(RobertaDetector::new()?))
} else {
None
};
let cloud_client: Option<Arc<dyn CloudClient>> = if config.use_cloud_fallback {
Some(Arc::new(CloudDetector::new()?))
} else {
None
};
Ok(Self {
config,
local_model,
cloud_client,
})
}
fn detect_local(&self, code: &CodeBlock) -> anyhow::Result<Option<DetectionResult>> {
if let Some(model) = &self.local_model {
Ok(Some(model.detect(code)?))
} else {
Ok(None)
}
}
async fn detect_cloud(&self, code: &CodeBlock) -> anyhow::Result<Option<DetectionResult>> {
if let Some(client) = &self.cloud_client {
Ok(Some(client.detect(code).await?))
} else {
Ok(None)
}
}
fn detect_patterns(&self, code: &CodeBlock) -> Vec<Pattern> {
let mut patterns = Vec::new();
let emoji_count = code.content.chars().filter(|c| is_emoji(*c)).count();
if emoji_count > 3 {
patterns.push(Pattern {
pattern_type: PatternType::ExcessiveEmojis,
confidence: (emoji_count as f32 / 10.0).min(1.0),
location: None,
});
}
if code.content.contains("TODO") || code.content.contains("FIXME") {
patterns.push(Pattern {
pattern_type: PatternType::Watermark,
confidence: 0.3,
location: None,
});
}
let comment_lines: Vec<_> = code.content
.lines()
.filter(|l| l.trim().starts_with("//") || l.trim().starts_with("#"))
.collect();
if comment_lines.len() > 5 {
patterns.push(Pattern {
pattern_type: PatternType::UniformComments,
confidence: 0.4,
location: None,
});
}
patterns
}
}
impl Detector for PhantomDetector {
fn detect(&self, code: &CodeBlock) -> anyhow::Result<DetectionResult> {
let mut result = if let Some(local_result) = self.detect_local(code)? {
local_result
} else {
let runtime = tokio::runtime::Runtime::new()?;
if let Some(cloud_result) = runtime.block_on(self.detect_cloud(code))? {
cloud_result
} else {
DetectionResult {
score: StealthScore::new(0.5, 0.5, 0.5),
patterns: Vec::new(),
sections: Vec::new(),
}
}
};
let patterns = self.detect_patterns(code);
result.patterns.extend(patterns);
Ok(result)
}
}
pub trait LocalModel: Send + Sync {
fn detect(&self, code: &CodeBlock) -> anyhow::Result<DetectionResult>;
}
#[async_trait::async_trait]
pub trait CloudClient: Send + Sync {
async fn detect(&self, code: &CodeBlock) -> anyhow::Result<DetectionResult>;
}
pub struct RobertaDetector {
}
impl RobertaDetector {
pub fn new() -> anyhow::Result<Self> {
Ok(Self {})
}
}
impl LocalModel for RobertaDetector {
fn detect(&self, _code: &CodeBlock) -> anyhow::Result<DetectionResult> {
Ok(DetectionResult {
score: StealthScore::new(0.5, 0.5, 0.5),
patterns: Vec::new(),
sections: Vec::new(),
})
}
}
pub struct CloudDetector {
}
impl CloudDetector {
pub fn new() -> anyhow::Result<Self> {
Ok(Self {})
}
}
#[async_trait::async_trait]
impl CloudClient for CloudDetector {
async fn detect(&self, _code: &CodeBlock) -> anyhow::Result<DetectionResult> {
Ok(DetectionResult {
score: StealthScore::new(0.5, 0.5, 0.5),
patterns: Vec::new(),
sections: Vec::new(),
})
}
}
fn is_emoji(c: char) -> bool {
let as_u32 = c as u32;
(0x1F600..=0x1F64F).contains(&as_u32) || (0x1F300..=0x1F5FF).contains(&as_u32) || (0x1F680..=0x1F6FF).contains(&as_u32) || (0x1F1E0..=0x1F1FF).contains(&as_u32) || (0x2600..=0x26FF).contains(&as_u32) || (0x2700..=0x27BF).contains(&as_u32) }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detector_creation() {
let detector = PhantomDetector::new().unwrap();
assert!(detector.local_model.is_some());
}
#[test]
fn test_pattern_detection() {
let detector = PhantomDetector::new().unwrap();
let code = CodeBlock {
path: "test.rs".into(),
language: phantomdev_core::Language::Rust,
content: "// TODO: fix this 🎉\n// Another comment 🚀\n// Yet another 🎯".to_string(),
line_range: (1, 3),
};
let patterns = detector.detect_patterns(&code);
assert!(!patterns.is_empty());
}
}