#![allow(clippy::pedantic)]
#![allow(clippy::missing_inline_in_public_items)]
use std::collections::HashMap;
use std::io::{self, Write};
use std::time::Instant;
use serde::{Deserialize, Serialize};
use api_huggingface::*;
use api_huggingface::components::input::InferenceParameters;
use api_huggingface::environment::HuggingFaceEnvironmentImpl;
use api_huggingface::secret::Secret;
#[ derive( Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize ) ]
pub enum SentimentCategory
{
VeryPositive,
Positive,
Neutral,
Negative,
VeryNegative,
}
impl SentimentCategory
{
pub fn name(&self) -> &'static str
{
match self
{
SentimentCategory::VeryPositive => "Very Positive",
SentimentCategory::Positive => "Positive",
SentimentCategory::Neutral => "Neutral",
SentimentCategory::Negative => "Negative",
SentimentCategory::VeryNegative => "Very Negative",
}
}
pub fn score_range(&self) -> (f32, f32)
{
match self
{
SentimentCategory::VeryPositive => (0.8, 1.0),
SentimentCategory::Positive => (0.6, 0.8),
SentimentCategory::Neutral => (0.4, 0.6),
SentimentCategory::Negative => (0.2, 0.4),
SentimentCategory::VeryNegative => (0.0, 0.2),
}
}
pub fn polarity(&self) -> f32
{
match self
{
SentimentCategory::VeryPositive => 1.0,
SentimentCategory::Positive => 0.5,
SentimentCategory::Neutral => 0.0,
SentimentCategory::Negative => -0.5,
SentimentCategory::VeryNegative => -1.0,
}
}
pub fn preferred_model() -> &'static str
{
"cardiffnlp/twitter-roberta-base-sentiment-latest"
}
pub fn from_score(score : f32) -> Self
{
if score >= 0.8
{
SentimentCategory::VeryPositive
} else if score >= 0.6
{
SentimentCategory::Positive
} else if score >= 0.4
{
SentimentCategory::Neutral
} else if score >= 0.2
{
SentimentCategory::Negative
} else {
SentimentCategory::VeryNegative
}
}
pub fn display_with_range(&self) -> String
{
let (min, max) = self.score_range();
format!("{} ({:.1}-{:.1})", self.name(), min, max)
}
}
#[ derive( Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize ) ]
pub enum EmotionalTone
{
Joy,
Sadness,
Anger,
Fear,
Surprise,
Disgust,
Trust,
Anticipation,
}
impl EmotionalTone
{
pub fn name(&self) -> &'static str
{
match self
{
EmotionalTone::Joy => "Joy",
EmotionalTone::Sadness => "Sadness",
EmotionalTone::Anger => "Anger",
EmotionalTone::Fear => "Fear",
EmotionalTone::Surprise => "Surprise",
EmotionalTone::Disgust => "Disgust",
EmotionalTone::Trust => "Trust",
EmotionalTone::Anticipation => "Anticipation",
}
}
pub fn sentiment_bias(&self) -> SentimentCategory
{
match self
{
EmotionalTone::Joy => SentimentCategory::VeryPositive,
EmotionalTone::Trust => SentimentCategory::Positive,
EmotionalTone::Anticipation => SentimentCategory::Positive,
EmotionalTone::Surprise => SentimentCategory::Neutral,
EmotionalTone::Sadness => SentimentCategory::Negative,
EmotionalTone::Fear => SentimentCategory::Negative,
EmotionalTone::Anger => SentimentCategory::VeryNegative,
EmotionalTone::Disgust => SentimentCategory::VeryNegative,
}
}
pub fn all_tones() -> Vec< EmotionalTone >
{
vec![
EmotionalTone::Joy,
EmotionalTone::Sadness,
EmotionalTone::Anger,
EmotionalTone::Fear,
EmotionalTone::Surprise,
EmotionalTone::Disgust,
EmotionalTone::Trust,
EmotionalTone::Anticipation,
]
}
pub fn icon(&self) -> &'static str
{
match self
{
EmotionalTone::Joy => "😊",
EmotionalTone::Sadness => "😢",
EmotionalTone::Anger => "😠",
EmotionalTone::Fear => "😨",
EmotionalTone::Surprise => "😮",
EmotionalTone::Disgust => "🤢",
EmotionalTone::Trust => "🤝",
EmotionalTone::Anticipation => "🤗",
}
}
}
#[ derive( Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize ) ]
pub enum ContentCategory
{
Safe,
Questionable,
Harmful,
Spam,
HateSpeech,
Violence,
}
impl ContentCategory
{
pub fn name(&self) -> &'static str
{
match self
{
ContentCategory::Safe => "Safe",
ContentCategory::Questionable => "Questionable",
ContentCategory::Harmful => "Harmful",
ContentCategory::Spam => "Spam",
ContentCategory::HateSpeech => "Hate Speech",
ContentCategory::Violence => "Violence",
}
}
pub fn severity_level(&self) -> u8
{
match self
{
ContentCategory::Safe => 1,
ContentCategory::Questionable => 2,
ContentCategory::Spam => 3,
ContentCategory::Harmful => 4,
ContentCategory::HateSpeech => 5,
ContentCategory::Violence => 5,
}
}
pub fn should_block(&self) -> bool
{
matches!(
self,
ContentCategory::Harmful | ContentCategory::HateSpeech | ContentCategory::Violence
)
}
pub fn preferred_model() -> &'static str
{
"unitary/toxic-bert"
}
pub fn color(&self) -> &'static str
{
match self
{
ContentCategory::Safe => "🟢",
ContentCategory::Questionable => "🟡",
ContentCategory::Spam => "🟠",
ContentCategory::Harmful => "🔴",
ContentCategory::HateSpeech => "🚫",
ContentCategory::Violence => "⛔",
}
}
}
#[ derive( Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize ) ]
pub enum ModerationAction
{
Allow,
Review,
Block,
Flag,
}
impl ModerationAction
{
pub fn name(&self) -> &'static str
{
match self
{
ModerationAction::Allow => "Allow",
ModerationAction::Review => "Review",
ModerationAction::Block => "Block",
ModerationAction::Flag => "Flag",
}
}
pub fn severity(&self) -> u8
{
match self
{
ModerationAction::Allow => 1,
ModerationAction::Review => 2,
ModerationAction::Flag => 3,
ModerationAction::Block => 4,
}
}
pub fn icon(&self) -> &'static str
{
match self
{
ModerationAction::Allow => "✅",
ModerationAction::Review => "⚠️",
ModerationAction::Flag => "🚩",
ModerationAction::Block => "🚫",
}
}
}
#[ derive( Debug, Clone ) ]
pub struct SentimentResult
{
pub text : String,
pub sentiment : SentimentCategory,
pub confidence : f32,
pub sentiment_score : f32,
pub emotional_tones : Vec< (EmotionalTone, f32) >,
pub content_assessment : ContentModerationResult,
pub processing_time_ms : u64,
}
#[ derive( Debug, Clone ) ]
pub struct ContentModerationResult
{
pub category : ContentCategory,
pub confidence : f32,
pub toxicity_score : f32,
pub flags : Vec< String >,
pub recommendation : ModerationAction,
}
#[ derive( Debug, Clone ) ]
pub struct BatchSentimentRequest
{
pub texts : Vec< String >,
pub include_emotional_analysis : bool,
pub include_moderation : bool,
pub batch_options : BatchOptions,
}
#[ derive( Debug, Clone ) ]
pub struct BatchOptions
{
pub max_batch_size : usize,
pub parallel_processing : bool,
pub progress_interval : Option< usize >,
pub confidence_threshold : f32,
}
impl Default for BatchOptions
{
fn default() -> Self
{
Self {
max_batch_size : 20,
parallel_processing : true,
progress_interval : Some(10),
confidence_threshold : 0.5,
}
}
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct SentimentStatistics
{
pub total_count : usize,
pub sentiment_distribution : HashMap< SentimentCategory, usize >,
pub average_sentiment_score : f32,
pub sentiment_score_std_dev : f32,
pub top_emotional_tones : Vec< (EmotionalTone, f32) >,
pub moderation_summary : ModerationStatistics,
pub performance_metrics : PerformanceMetrics,
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct ModerationStatistics
{
pub category_distribution : HashMap< ContentCategory, usize >,
pub average_toxicity_score : f32,
pub blocked_count : usize,
pub flagged_count : usize,
pub common_flags : Vec< (String, usize) >,
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct PerformanceMetrics
{
pub average_processing_time : f64,
pub total_processing_time : u64,
pub throughput : f64,
pub memory_usage_mb : f32,
}
#[ derive( Debug, Clone ) ]
pub struct PlatformConfig
{
pub sentiment_model : String,
pub moderation_model : String,
pub confidence_threshold : f32,
pub enable_emotional_analysis : bool,
pub enable_content_moderation : bool,
pub max_text_length : usize,
}
impl Default for PlatformConfig
{
fn default() -> Self
{
Self {
sentiment_model : SentimentCategory::preferred_model().to_string(),
moderation_model : ContentCategory::preferred_model().to_string(),
confidence_threshold : 0.6,
enable_emotional_analysis : true,
enable_content_moderation : true,
max_text_length : 512,
}
}
}
#[ derive( Debug ) ]
pub struct SentimentAnalysisPlatform
{
client : Client< HuggingFaceEnvironmentImpl >,
config : PlatformConfig,
analysis_history : Vec< SentimentResult >,
performance_stats : PerformanceMetrics,
}
impl SentimentAnalysisPlatform
{
pub fn new(client : Client< HuggingFaceEnvironmentImpl >) -> Self
{
Self {
client,
config : PlatformConfig::default(),
analysis_history : Vec::new(),
performance_stats : PerformanceMetrics {
average_processing_time : 0.0,
total_processing_time : 0,
throughput : 0.0,
memory_usage_mb : 0.0,
},
}
}
pub fn with_config(client : Client< HuggingFaceEnvironmentImpl >, config : PlatformConfig) -> Self
{
Self {
client,
config,
analysis_history : Vec::new(),
performance_stats : PerformanceMetrics {
average_processing_time : 0.0,
total_processing_time : 0,
throughput : 0.0,
memory_usage_mb : 0.0,
},
}
}
pub async fn analyze_sentiment(&mut self, text : &str) -> Result< SentimentResult, Box< dyn std::error::Error > >
{
let start_time = Instant::now();
if text.len() > self.config.max_text_length
{
return Err(format!(
"Text length {} exceeds maximum {}",
text.len(),
self.config.max_text_length
)
.into());
}
let sentiment_prompt = self.build_sentiment_prompt(text)?;
let params = InferenceParameters::new()
.with_max_new_tokens(50)
.with_temperature(0.1) .with_top_p(0.8);
let response = self
.client
.inference()
.create_with_parameters(&sentiment_prompt, &self.config.sentiment_model, params)
.await?;
let sentiment_text = response.extract_text_or_default( "neutral" );
let processing_time = start_time.elapsed().as_millis() as u64;
let (sentiment, confidence, sentiment_score) = self.parse_sentiment_response(&sentiment_text)?;
let emotional_tones = if self.config.enable_emotional_analysis
{
self.analyze_emotional_tones(text).await?
} else {
Vec::new()
};
let content_assessment = if self.config.enable_content_moderation
{
self.moderate_content(text).await?
} else {
ContentModerationResult {
category : ContentCategory::Safe,
confidence : 1.0,
toxicity_score : 0.0,
flags : Vec::new(),
recommendation : ModerationAction::Allow,
}
};
let result = SentimentResult {
text : text.to_string(),
sentiment,
confidence,
sentiment_score,
emotional_tones,
content_assessment,
processing_time_ms : processing_time,
};
self.update_performance_stats(processing_time);
self.analysis_history.push(result.clone());
Ok(result)
}
pub async fn analyze_batch(&mut self, request : &BatchSentimentRequest) -> Result< Vec< Result< SentimentResult, Box< dyn std::error::Error > > >, Box< dyn std::error::Error > >
{
let mut results = Vec::new();
let batch_size = request.batch_options.max_batch_size.min(request.texts.len());
for (chunk_idx, chunk) in request.texts.chunks(batch_size).enumerate()
{
let mut chunk_results = Vec::new();
if request.batch_options.parallel_processing
{
for text in chunk
{
let result = self.analyze_sentiment(text).await;
chunk_results.push(result);
}
} else {
for text in chunk
{
let result = self.analyze_sentiment(text).await;
chunk_results.push(result);
}
}
if let Some(interval) = request.batch_options.progress_interval
{
if (chunk_idx + 1) % interval == 0
{
println!(
"Processed {} batches of {} texts",
chunk_idx + 1,
batch_size
);
}
}
results.extend(chunk_results);
}
Ok(results)
}
pub fn generate_statistics(&self) -> SentimentStatistics
{
let mut sentiment_distribution = HashMap::new();
let mut total_sentiment_score = 0.0;
let mut total_toxicity_score = 0.0;
let mut emotional_tone_counts : HashMap< EmotionalTone, f32 > = HashMap::new();
let mut category_distribution = HashMap::new();
let mut flag_counts : HashMap< String, usize > = HashMap::new();
let mut blocked_count = 0;
let mut flagged_count = 0;
for result in &self.analysis_history
{
*sentiment_distribution.entry(result.sentiment).or_insert(0) += 1;
total_sentiment_score += result.sentiment_score;
for (tone, intensity) in &result.emotional_tones
{
*emotional_tone_counts.entry(*tone).or_insert(0.0) += intensity;
}
*category_distribution
.entry(result.content_assessment.category)
.or_insert(0) += 1;
total_toxicity_score += result.content_assessment.toxicity_score;
for flag in &result.content_assessment.flags
{
*flag_counts.entry(flag.clone()).or_insert(0) += 1;
}
if result.content_assessment.recommendation == ModerationAction::Block
{
blocked_count += 1;
}
if result.content_assessment.recommendation == ModerationAction::Flag
{
flagged_count += 1;
}
}
let total_count = self.analysis_history.len();
let average_sentiment_score = if total_count > 0
{
total_sentiment_score / total_count as f32
} else {
0.0
};
let average_toxicity_score = if total_count > 0
{
total_toxicity_score / total_count as f32
} else {
0.0
};
let sentiment_score_std_dev = if total_count > 1
{
let variance = self
.analysis_history
.iter()
.map(|result| (result.sentiment_score - average_sentiment_score).powi(2))
.sum::< f32 >()
/ (total_count - 1) as f32;
variance.sqrt()
} else {
0.0
};
let mut top_emotional_tones : Vec< (EmotionalTone, f32) > = emotional_tone_counts.into_iter().collect();
top_emotional_tones.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(core::cmp::Ordering::Equal));
top_emotional_tones.truncate(5);
let mut common_flags : Vec< (String, usize) > = flag_counts.into_iter().collect();
common_flags.sort_by_key( |( _, count )| core::cmp::Reverse( *count ) );
common_flags.truncate(5);
SentimentStatistics {
total_count,
sentiment_distribution,
average_sentiment_score,
sentiment_score_std_dev,
top_emotional_tones,
moderation_summary : ModerationStatistics {
category_distribution,
average_toxicity_score,
blocked_count,
flagged_count,
common_flags,
},
performance_metrics : self.performance_stats.clone(),
}
}
pub fn clear_history(&mut self)
{
self.analysis_history.clear();
self.performance_stats = PerformanceMetrics {
average_processing_time : 0.0,
total_processing_time : 0,
throughput : 0.0,
memory_usage_mb : 0.0,
};
}
pub fn get_history(&self) -> &Vec< SentimentResult >
{
&self.analysis_history
}
fn build_sentiment_prompt(&self, text : &str) -> Result< String, Box< dyn std::error::Error > >
{
let prompt = format!(
"Analyze the sentiment of the following text and classify it as very positive, positive, neutral, negative, or very negative:\n\nText : {}\n\nSentiment:",
text
);
Ok(prompt)
}
fn parse_sentiment_response(&self, response : &str) -> Result< (SentimentCategory, f32, f32), Box< dyn std::error::Error > >
{
let response_lower = response.trim().to_lowercase();
let (sentiment, base_confidence) = if response_lower.contains("very positive") || response_lower.contains("excellent") || response_lower.contains("amazing")
{
(SentimentCategory::VeryPositive, 0.9)
} else if response_lower.contains("positive") || response_lower.contains("good") || response_lower.contains("nice")
{
(SentimentCategory::Positive, 0.8)
} else if response_lower.contains("neutral") || response_lower.contains("okay")
{
(SentimentCategory::Neutral, 0.7)
} else if response_lower.contains("negative") || response_lower.contains("bad")
{
(SentimentCategory::Negative, 0.8)
} else if response_lower.contains("very negative") || response_lower.contains("terrible") || response_lower.contains("awful")
{
(SentimentCategory::VeryNegative, 0.9)
} else {
(SentimentCategory::Neutral, 0.5)
};
let confidence = (base_confidence * (0.8 + (response.len() as f32 / 100.0).min(0.2))).min(1.0);
let sentiment_score = sentiment.polarity() * 0.5 + 0.5;
Ok((sentiment, confidence, sentiment_score))
}
async fn analyze_emotional_tones(&self, text : &str) -> Result< Vec< (EmotionalTone, f32) >, Box< dyn std::error::Error > >
{
let mut tones = Vec::new();
let text_lower = text.to_lowercase();
if text_lower.contains("happy") || text_lower.contains("joy") || text_lower.contains("excited") || text_lower.contains("wonderful")
{
tones.push((EmotionalTone::Joy, 0.8));
}
if text_lower.contains("sad") || text_lower.contains("depressed") || text_lower.contains("disappointed")
{
tones.push((EmotionalTone::Sadness, 0.7));
}
if text_lower.contains("angry") || text_lower.contains("furious") || text_lower.contains("hate")
{
tones.push((EmotionalTone::Anger, 0.8));
}
if text_lower.contains("scared") || text_lower.contains("afraid") || text_lower.contains("worried")
{
tones.push((EmotionalTone::Fear, 0.7));
}
if tones.is_empty()
{
tones.push((EmotionalTone::Trust, 0.3));
}
Ok(tones)
}
async fn moderate_content(&self, text : &str) -> Result< ContentModerationResult, Box< dyn std::error::Error > >
{
let text_lower = text.to_lowercase();
let mut flags = Vec::new();
let mut toxicity_score : f32 = 0.0;
if text_lower.contains("hate") || text_lower.contains("stupid") || text_lower.contains("idiot")
{
flags.push("potential_hate_speech".to_string());
toxicity_score += 0.3;
}
if text_lower.contains("kill") || text_lower.contains("violence") || text_lower.contains("hurt")
{
flags.push("violence_threat".to_string());
toxicity_score += 0.4;
}
if text_lower.contains("spam") || text_lower.contains("click here") || text_lower.contains("buy now")
{
flags.push("promotional_content".to_string());
toxicity_score += 0.2;
}
let (category, recommendation) = if toxicity_score >= 0.7
{
if flags.iter().any(|f| f.contains("violence"))
{
(ContentCategory::Violence, ModerationAction::Block)
} else if flags.iter().any(|f| f.contains("hate"))
{
(ContentCategory::HateSpeech, ModerationAction::Block)
} else {
(ContentCategory::Harmful, ModerationAction::Flag)
}
} else if toxicity_score >= 0.4
{
(ContentCategory::Questionable, ModerationAction::Review)
} else if toxicity_score >= 0.2
{
if flags.iter().any(|f| f.contains("promotional"))
{
(ContentCategory::Spam, ModerationAction::Review)
} else {
(ContentCategory::Questionable, ModerationAction::Allow)
}
} else {
(ContentCategory::Safe, ModerationAction::Allow)
};
let confidence = if flags.is_empty()
{
0.9
} else {
0.7 + (flags.len() as f32 * 0.1).min(0.2)
};
Ok(ContentModerationResult {
category,
confidence,
toxicity_score : toxicity_score.min(1.0),
flags,
recommendation,
})
}
fn update_performance_stats(&mut self, processing_time : u64)
{
let history_count = self.analysis_history.len() as u64;
self.performance_stats.total_processing_time += processing_time;
self.performance_stats.average_processing_time =
self.performance_stats.total_processing_time as f64 / (history_count + 1) as f64;
if self.performance_stats.total_processing_time > 0
{
self.performance_stats.throughput = (history_count + 1) as f64 * 1000.0
/ self.performance_stats.total_processing_time as f64;
}
self.performance_stats.memory_usage_mb = (history_count + 1) as f32 * 0.1; }
}
#[ derive( Debug ) ]
pub struct SentimentSystemPlatform
{
sentiment_platform : SentimentAnalysisPlatform,
stats : SystemStats,
sample_texts : Vec< String >,
}
#[ derive( Debug, Default, Serialize, Deserialize ) ]
pub struct SystemStats
{
analyses_completed : usize,
batch_analyses_completed : usize,
total_response_time_ms : u64,
emotions_detected : usize,
content_blocked : usize,
}
impl SentimentSystemPlatform
{
pub fn new(client : Client< HuggingFaceEnvironmentImpl >) -> Self
{
let mut platform = Self {
sentiment_platform : SentimentAnalysisPlatform::new(client),
stats : SystemStats::default(),
sample_texts : Vec::new(),
};
platform.load_sample_data();
platform
}
fn load_sample_data(&mut self)
{
self.sample_texts = vec![
"I absolutely love this product! It's amazing and works perfectly.".to_string(),
"This is okay, nothing special but not bad either.".to_string(),
"I hate this so much, it's terrible and doesn't work at all.".to_string(),
"The weather is nice today.".to_string(),
"I'm so excited about the upcoming vacation!".to_string(),
"I'm really worried about the exam tomorrow.".to_string(),
"This movie was incredibly boring and disappointing.".to_string(),
"Thank you so much for your help, I really appreciate it.".to_string(),
"Click here to buy now! Amazing deal, don't miss out!".to_string(),
"I'm feeling sad and depressed lately.".to_string(),
"This is the best day ever, I'm so happy!".to_string(),
"I'm scared about what might happen next.".to_string(),
];
}
pub async fn run(&mut self) -> Result< (), Box< dyn std::error::Error > >
{
println!("🎭 Sentiment Analysis & Content Moderation System");
println!("================================================");
println!();
self.show_help();
loop
{
print!("\n > ");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let input = input.trim();
if input.is_empty()
{
continue;
}
match input
{
"/help" | "/h" => self.show_help(),
"/quit" | "/q" => {
println!("Thanks for using the sentiment analysis system!");
break;
}
"/analyze" => self.analyze_interactive().await?,
"/batch" => self.batch_analyze_interactive().await?,
"/samples" => self.analyze_samples().await?,
"/emotions" => self.show_emotion_guide(),
"/moderation" => self.show_moderation_guide(),
"/stats" => self.show_statistics(),
"/export" => self.export_results()?,
"/clear" => self.clear_history(),
cmd if cmd.starts_with('/') =>
{
println!("❌ Unknown command : {}. Type /help for available commands.", cmd);
}
text => {
self.quick_analyze(text).await?;
}
}
}
Ok(())
}
fn show_help(&self)
{
println!("Available commands:");
println!(" /analyze - Interactive sentiment analysis with full options");
println!(" /batch - Batch analysis of multiple texts");
println!(" /samples - Analyze pre-loaded sample texts");
println!(" /emotions - Show emotional tone reference guide");
println!(" /moderation - Show content moderation categories");
println!(" /stats - Show comprehensive system statistics");
println!(" /export - Export analysis results and statistics");
println!(" /clear - Clear analysis history");
println!(" /help - Show this help");
println!(" /quit - Exit the system");
println!();
println!("You can also type text directly for quick sentiment analysis.");
println!("Example : I love this product!");
}
async fn quick_analyze(&mut self, text : &str) -> Result< (), Box< dyn std::error::Error > >
{
println!("\n🔍 Analyzing : \"{}\"", text);
let start_time = std::time::Instant::now();
match self.sentiment_platform.analyze_sentiment(text).await
{
Ok(result) => {
self.display_analysis_result(&result);
self.update_stats(&result, start_time.elapsed().as_millis() as u64);
}
Err(e) => {
println!("❌ Analysis failed : {}", e);
}
}
Ok(())
}
async fn analyze_interactive(&mut self) -> Result< (), Box< dyn std::error::Error > >
{
println!("\n📝 Interactive Sentiment Analysis");
println!("=================================");
print!("Enter text to analyze : ");
io::stdout().flush()?;
let mut text = String::new();
io::stdin().read_line(&mut text)?;
let text = text.trim();
if text.is_empty()
{
println!("❌ Text cannot be empty.");
return Ok(());
}
if text.len() > self.sentiment_platform.config.max_text_length
{
println!(
"⚠️ Text length ({}) exceeds maximum ({}). Truncating...",
text.len(),
self.sentiment_platform.config.max_text_length
);
}
println!("\n🔍 Analyzing sentiment and content...");
let start_time = std::time::Instant::now();
match self.sentiment_platform.analyze_sentiment(text).await
{
Ok(result) => {
self.display_detailed_analysis(&result);
self.update_stats(&result, start_time.elapsed().as_millis() as u64);
}
Err(e) => {
println!("❌ Analysis failed : {}", e);
}
}
Ok(())
}
async fn batch_analyze_interactive(&mut self) -> Result< (), Box< dyn std::error::Error > >
{
println!("\n📦 Batch Sentiment Analysis");
println!("===========================");
println!("Enter texts to analyze (one per line, empty line to finish):");
let mut texts = Vec::new();
loop
{
print!("{}: ", texts.len() + 1);
io::stdout().flush()?;
let mut text = String::new();
io::stdin().read_line(&mut text)?;
let text = text.trim();
if text.is_empty()
{
break;
}
texts.push(text.to_string());
}
if texts.is_empty()
{
println!("❌ No texts provided.");
return Ok(());
}
let batch_request = BatchSentimentRequest {
texts,
include_emotional_analysis : true,
include_moderation : true,
batch_options : BatchOptions::default(),
};
println!("\n🔄 Processing {} texts...", batch_request.texts.len());
let start_time = std::time::Instant::now();
match self.sentiment_platform.analyze_batch(&batch_request).await
{
Ok(results) => {
println!("\n📊 Batch Analysis Results:");
println!("=========================");
for (i, result) in results.iter().enumerate()
{
println!("\n{}. \"{}\"", i + 1, batch_request.texts[i]);
match result
{
Ok(analysis) => {
println!(" Sentiment : {} {}", analysis.sentiment.name(), self.get_sentiment_icon(&analysis.sentiment));
println!(" Confidence : {:.1}%", analysis.confidence * 100.0);
if !analysis.emotional_tones.is_empty()
{
let emotions : Vec< String > = analysis.emotional_tones.iter()
.map(|(tone, intensity)| format!("{} {:.1}", tone.icon(), intensity))
.collect();
println!(" Emotions : {}", emotions.join(" "));
}
println!(" Moderation : {} {}", analysis.content_assessment.category.color(), analysis.content_assessment.category.name());
}
Err(e) => {
println!(" ❌ Failed : {}", e);
}
}
}
self.stats.batch_analyses_completed += 1;
self.stats.total_response_time_ms += start_time.elapsed().as_millis() as u64;
}
Err(e) => {
println!("❌ Batch analysis failed : {}", e);
}
}
Ok(())
}
async fn analyze_samples(&mut self) -> Result< (), Box< dyn std::error::Error > >
{
println!("\n📚 Sample Text Analysis");
println!("=======================");
for (i, text) in self.sample_texts.iter().enumerate()
{
println!("{}. \"{}\"", i + 1, text);
}
print!("\nSelect sample (1-{}) or 'all' for all samples : ", self.sample_texts.len());
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let input = input.trim();
if input.eq_ignore_ascii_case("all")
{
println!("\n🔄 Analyzing all {} samples...", self.sample_texts.len());
let sample_texts = self.sample_texts.clone();
for (i, text) in sample_texts.iter().enumerate()
{
println!("\n--- Sample {} ---", i + 1);
match self.sentiment_platform.analyze_sentiment(text).await
{
Ok(result) => {
self.display_analysis_result(&result);
self.update_stats(&result, result.processing_time_ms);
}
Err(e) => {
println!("❌ Analysis failed for sample {}: {}", i + 1, e);
}
}
}
} else if let Ok(index) = input.parse::< usize >()
{
if index > 0 && index <= self.sample_texts.len()
{
let text = &self.sample_texts[index - 1];
println!("\n🔍 Analyzing sample {}: \"{}\"", index, text);
let start_time = std::time::Instant::now();
match self.sentiment_platform.analyze_sentiment(text).await
{
Ok(result) => {
self.display_detailed_analysis(&result);
self.update_stats(&result, start_time.elapsed().as_millis() as u64);
}
Err(e) => {
println!("❌ Analysis failed : {}", e);
}
}
} else {
println!("❌ Invalid sample number.");
}
}
Ok(())
}
fn show_emotion_guide(&self)
{
println!("\n😊 Emotional Tone Reference Guide");
println!("=================================");
for tone in EmotionalTone::all_tones()
{
println!("{} {} - Associated with {}", tone.icon(), tone.name(), tone.sentiment_bias().name());
}
println!("\nEmotional tones are detected based on keywords and context.");
println!("Multiple tones can be present in a single text with varying intensities.");
}
fn show_moderation_guide(&self)
{
println!("\n🛡️ Content Moderation Categories");
println!("=================================");
let categories = [
ContentCategory::Safe,
ContentCategory::Questionable,
ContentCategory::Spam,
ContentCategory::Harmful,
ContentCategory::HateSpeech,
ContentCategory::Violence,
];
for category in categories
{
println!("{} {} - Severity Level : {}, Should Block : {}",
category.color(),
category.name(),
category.severity_level(),
if category.should_block() { "Yes" } else { "No" });
}
println!("\nModeration Actions:");
let actions = [
ModerationAction::Allow,
ModerationAction::Review,
ModerationAction::Flag,
ModerationAction::Block,
];
for action in actions
{
println!("{} {} - Severity : {}", action.icon(), action.name(), action.severity());
}
}
fn show_statistics(&self)
{
let platform_stats = self.sentiment_platform.generate_statistics();
println!("\n📊 System Statistics");
println!("===================");
println!("Individual Analyses : {}", self.stats.analyses_completed);
println!("Batch Analyses : {}", self.stats.batch_analyses_completed);
println!("Total Platform Analyses : {}", platform_stats.total_count);
println!("Emotions Detected : {}", self.stats.emotions_detected);
println!("Content Blocked : {}", self.stats.content_blocked);
println!("\n📈 Performance Metrics");
println!("======================");
println!("Average Processing Time : {:.2}ms", platform_stats.performance_metrics.average_processing_time);
println!("Total Processing Time : {}ms", platform_stats.performance_metrics.total_processing_time);
println!("Throughput : {:.2} texts/second", platform_stats.performance_metrics.throughput);
println!("Memory Usage : {:.1}MB", platform_stats.performance_metrics.memory_usage_mb);
if !platform_stats.sentiment_distribution.is_empty()
{
println!("\n💭 Sentiment Distribution");
println!("=========================");
for (sentiment, count) in platform_stats.sentiment_distribution
{
println!("{} {}: {}",
self.get_sentiment_icon(&sentiment),
sentiment.name(),
count);
}
println!("Average Sentiment Score : {:.2}", platform_stats.average_sentiment_score);
println!("Standard Deviation : {:.2}", platform_stats.sentiment_score_std_dev);
}
if !platform_stats.top_emotional_tones.is_empty()
{
println!("\n😊 Top Emotional Tones");
println!("======================");
for (tone, intensity) in platform_stats.top_emotional_tones
{
println!("{} {}: {:.2}", tone.icon(), tone.name(), intensity);
}
}
if !platform_stats.moderation_summary.category_distribution.is_empty()
{
println!("\n🛡️ Content Moderation Summary");
println!("=============================");
for (category, count) in platform_stats.moderation_summary.category_distribution
{
println!("{} {}: {}", category.color(), category.name(), count);
}
println!("Average Toxicity Score : {:.2}", platform_stats.moderation_summary.average_toxicity_score);
println!("Blocked : {}, Flagged : {}",
platform_stats.moderation_summary.blocked_count,
platform_stats.moderation_summary.flagged_count);
}
}
fn export_results(&self) -> Result< (), Box< dyn std::error::Error > >
{
println!("\n💾 Export Analysis Results");
println!("==========================");
print!("Export filename (press Enter for default): ");
io::stdout().flush()?;
let mut filename = String::new();
io::stdin().read_line(&mut filename)?;
let filename = filename.trim();
let filename = if filename.is_empty()
{
"sentiment_analysis_results.json".to_string()
} else {
filename.to_string()
};
let stats = self.sentiment_platform.generate_statistics();
let export_data = serde_json::json!({
"system_stats": self.stats,
"platform_statistics": stats,
"total_analyses": self.sentiment_platform.analysis_history.len(),
"export_timestamp": chrono::Utc::now().to_rfc3339()
});
std::fs::write(&filename, serde_json::to_string_pretty(&export_data)?)?;
println!("✅ Results exported to : {}", filename);
Ok(())
}
fn clear_history(&mut self)
{
self.sentiment_platform.clear_history();
self.stats = SystemStats::default();
println!("✅ Analysis history cleared.");
}
fn display_analysis_result(&self, result : &SentimentResult)
{
println!("📊 Sentiment : {} {} (Confidence : {:.1}%)",
self.get_sentiment_icon(&result.sentiment),
result.sentiment.name(),
result.confidence * 100.0);
if !result.emotional_tones.is_empty()
{
let emotions : Vec< String > = result.emotional_tones.iter()
.map(|(tone, intensity)| format!("{} {} ({:.1})", tone.icon(), tone.name(), intensity))
.collect();
println!("😊 Emotions : {}", emotions.join(", "));
}
println!("🛡️ Moderation : {} {} (Toxicity : {:.1}%)",
result.content_assessment.category.color(),
result.content_assessment.category.name(),
result.content_assessment.toxicity_score * 100.0);
if !result.content_assessment.flags.is_empty()
{
println!("🚩 Flags : {}", result.content_assessment.flags.join(", "));
}
println!("⏱️ Processing Time : {}ms", result.processing_time_ms);
}
fn display_detailed_analysis(&self, result : &SentimentResult)
{
println!("\n🎭 Detailed Analysis Results");
println!("=============================");
println!("Text : \"{}\"", result.text);
println!();
println!("📊 Sentiment Analysis:");
println!(" Category : {} {}", self.get_sentiment_icon(&result.sentiment), result.sentiment.name());
println!(" Score : {:.2} (Range : {:.1}-{:.1})",
result.sentiment_score,
result.sentiment.score_range().0,
result.sentiment.score_range().1);
println!(" Polarity : {:.2}", result.sentiment.polarity());
println!(" Confidence : {:.1}%", result.confidence * 100.0);
if !result.emotional_tones.is_empty()
{
println!("\n😊 Emotional Tones:");
for (tone, intensity) in &result.emotional_tones
{
println!(" {} {} - Intensity : {:.2} (Bias : {})",
tone.icon(),
tone.name(),
intensity,
tone.sentiment_bias().name());
}
}
println!("\n🛡️ Content Moderation:");
println!(" Category : {} {}", result.content_assessment.category.color(), result.content_assessment.category.name());
println!(" Severity Level : {}", result.content_assessment.category.severity_level());
println!(" Should Block : {}", if result.content_assessment.category.should_block() { "Yes" } else { "No" });
println!(" Toxicity Score : {:.1}%", result.content_assessment.toxicity_score * 100.0);
println!(" Confidence : {:.1}%", result.content_assessment.confidence * 100.0);
println!(" Recommendation : {} {}", result.content_assessment.recommendation.icon(), result.content_assessment.recommendation.name());
if !result.content_assessment.flags.is_empty()
{
println!(" Flags : {}", result.content_assessment.flags.join(", "));
}
println!("\n⏱️ Performance:");
println!(" Processing Time : {}ms", result.processing_time_ms);
}
fn get_sentiment_icon(&self, sentiment : &SentimentCategory) -> &'static str
{
match sentiment
{
SentimentCategory::VeryPositive => "😍",
SentimentCategory::Positive => "😊",
SentimentCategory::Neutral => "😐",
SentimentCategory::Negative => "😞",
SentimentCategory::VeryNegative => "😡",
}
}
fn update_stats(&mut self, result : &SentimentResult, response_time : u64)
{
self.stats.analyses_completed += 1;
self.stats.total_response_time_ms += response_time;
self.stats.emotions_detected += result.emotional_tones.len();
if result.content_assessment.recommendation == ModerationAction::Block
{
self.stats.content_blocked += 1;
}
}
}
#[ tokio::main ]
async fn main() -> Result< (), Box< dyn std::error::Error > >
{
let api_key = std::env::var("HUGGINGFACE_API_KEY")
.or_else(|_| {
use workspace_tools as workspace;
let workspace = workspace::workspace()
.map_err(|_| std::env::VarError::NotPresent)?; let secrets = workspace.load_secrets_from_file("-secrets.sh")
.map_err(|_| std::env::VarError::NotPresent)?; secrets.get("HUGGINGFACE_API_KEY")
.cloned()
.ok_or(std::env::VarError::NotPresent)
})?;
let secret = Secret::new(api_key);
let env = HuggingFaceEnvironmentImpl::build(secret, None)?;
let client = Client::build(env)?;
let mut platform = SentimentSystemPlatform::new(client);
platform.run().await?;
Ok(())
}