use serde::{Deserialize, Serialize};
use serde_json::json;
use thiserror::Error;
use super::{ThinkToolContext, ThinkToolModule, ThinkToolModuleConfig, ThinkToolOutput};
use crate::error::{Error, Result};
#[derive(Error, Debug, Clone)]
pub enum ImageGenerationError {
#[error("Visual concept too ambiguous: {concept}")]
AmbiguousConcept { concept: String },
#[error("Invalid style specified: {style}")]
InvalidStyle { style: String },
#[error("Insufficient composition elements: {description}")]
InsufficientComposition { description: String },
#[error("Invalid technical parameters: {parameters}")]
InvalidParameters { parameters: String },
#[error("Quality criteria incomplete: {criteria}")]
IncompleteCriteria { criteria: String },
}
impl From<ImageGenerationError> for Error {
fn from(err: ImageGenerationError) -> Self {
Error::ThinkToolExecutionError(err.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImageGenerationConfig {
pub base: ThinkToolModuleConfig,
pub min_perspectives: usize,
pub max_styles: usize,
pub include_technical_specs: bool,
pub include_quality_criteria: bool,
pub default_style_bias: Option<String>,
}
impl ImageGenerationConfig {
pub fn new(
name: impl Into<String>,
version: impl Into<String>,
description: impl Into<String>,
) -> Self {
Self {
base: ThinkToolModuleConfig::new(name, version, description),
min_perspectives: 8,
max_styles: 5,
include_technical_specs: true,
include_quality_criteria: true,
default_style_bias: None,
}
}
pub fn with_min_perspectives(mut self, count: usize) -> Self {
self.min_perspectives = count;
self
}
pub fn with_max_styles(mut self, count: usize) -> Self {
self.max_styles = count;
self
}
pub fn with_style_bias(mut self, style: impl Into<String>) -> Self {
self.default_style_bias = Some(style.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VisualPerspective {
pub name: String,
pub description: String,
pub observations: Vec<String>,
pub confidence: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StyleRecommendation {
pub name: String,
pub rationale: String,
pub characteristics: Vec<String>,
pub confidence: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompositionElement {
pub element_type: String,
pub description: String,
pub suggestions: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TechnicalParameters {
pub resolution: Option<(u32, u32)>,
pub color_palette: Vec<String>,
pub lighting: Vec<String>,
pub detail_level: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityCriteria {
pub aesthetic: Vec<String>,
pub technical: Vec<String>,
pub composition: Vec<String>,
pub style_consistency: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImageGenerationResult {
pub perspectives: Vec<VisualPerspective>,
pub style_recommendations: Vec<StyleRecommendation>,
pub composition_elements: Vec<CompositionElement>,
pub technical_parameters: Option<TechnicalParameters>,
pub quality_criteria: Option<QualityCriteria>,
pub overall_confidence: f64,
pub warnings: Vec<String>,
}
pub struct ImageGeneration {
config: ImageGenerationConfig,
}
impl Default for ImageGeneration {
fn default() -> Self {
Self::new()
}
}
impl ImageGeneration {
pub fn new() -> Self {
Self {
config: ImageGenerationConfig::new(
"ImageGeneration",
"1.0.0",
"Structured reasoning for visual concepts and AI image generation planning",
),
}
}
pub fn builder() -> ImageGenerationBuilder {
ImageGenerationBuilder::new()
}
fn analyze_prompt(&self, prompt: &str) -> Result<ImageGenerationResult> {
if prompt.trim().len() < 10 {
return Err(ImageGenerationError::AmbiguousConcept {
concept: prompt.to_string(),
}
.into());
}
let perspectives = self.generate_perspectives(prompt)?;
let style_recommendations = self.generate_style_recommendations(prompt)?;
let composition_elements = self.generate_composition_elements(prompt)?;
let technical_parameters = if self.config.include_technical_specs {
Some(self.generate_technical_parameters(prompt)?)
} else {
None
};
let quality_criteria = if self.config.include_quality_criteria {
Some(self.generate_quality_criteria(prompt)?)
} else {
None
};
let overall_confidence = self.calculate_confidence(&perspectives, &style_recommendations);
Ok(ImageGenerationResult {
perspectives,
style_recommendations,
composition_elements,
technical_parameters,
quality_criteria,
overall_confidence,
warnings: vec!["This is a structured analysis, not generation".to_string()],
})
}
fn generate_perspectives(&self, prompt: &str) -> Result<Vec<VisualPerspective>> {
let mut perspectives = Vec::new();
let core_perspectives = [
("Technical", "Technical requirements and specifications"),
("Aesthetic", "Visual appeal, beauty, artistic merit"),
("Emotional", "Emotional impact, mood, feeling"),
("Narrative", "Storytelling, context, backstory"),
("Symbolic", "Symbolism, metaphors, deeper meaning"),
("Practical", "Practical usability, effectiveness"),
("Cultural", "Cultural context, references, appropriateness"),
("Innovative", "Creativity, uniqueness, originality"),
];
for (name, desc) in core_perspectives.iter().take(self.config.min_perspectives) {
perspectives.push(VisualPerspective {
name: name.to_string(),
description: desc.to_string(),
observations: vec![
format!("Consider {} aspects of {}", name.to_lowercase(), prompt),
"Analyze visual composition requirements".to_string(),
"Identify key visual elements".to_string(),
],
confidence: 0.7 + (rand::random::<f64>() * 0.2), });
}
Ok(perspectives)
}
fn generate_style_recommendations(&self, _prompt: &str) -> Result<Vec<StyleRecommendation>> {
let mut styles = Vec::new();
let common_styles = [
("Photorealistic", "Realistic photography style"),
("Digital Art", "Digital painting with visible brush strokes"),
("Cyberpunk", "Neon-lit futuristic dystopian"),
("Minimalist", "Simple, clean, essential elements only"),
("Vaporwave", "80s/90s aesthetic with pastel colors"),
("Surreal", "Dreamlike, unexpected combinations"),
("Concept Art", "Professional concept development style"),
];
for (name, rationale) in common_styles.iter().take(self.config.max_styles) {
styles.push(StyleRecommendation {
name: name.to_string(),
rationale: rationale.to_string(),
characteristics: vec![
"Strong color palette".to_string(),
"Distinct lighting style".to_string(),
"Characteristic composition".to_string(),
],
confidence: 0.6 + (rand::random::<f64>() * 0.3), });
}
Ok(styles)
}
fn generate_composition_elements(&self, _prompt: &str) -> Result<Vec<CompositionElement>> {
Ok(vec![
CompositionElement {
element_type: "Focal Point".to_string(),
description: "Primary subject or area of interest".to_string(),
suggestions: vec![
"Place according to rule of thirds".to_string(),
"Ensure clear visual hierarchy".to_string(),
],
},
CompositionElement {
element_type: "Rule of Thirds".to_string(),
description: "Divide frame into thirds for balanced composition".to_string(),
suggestions: vec![
"Align key elements with intersection points".to_string(),
"Use vertical/horizontal thirds lines".to_string(),
],
},
CompositionElement {
element_type: "Leading Lines".to_string(),
description: "Use lines to guide viewer's eye".to_string(),
suggestions: vec![
"Natural lines in environment".to_string(),
"Architectural elements as guides".to_string(),
],
},
])
}
fn generate_technical_parameters(&self, _prompt: &str) -> Result<TechnicalParameters> {
Ok(TechnicalParameters {
resolution: Some((1024, 1024)),
color_palette: vec![
"Vibrant neons".to_string(),
"Deep shadows".to_string(),
"High contrast".to_string(),
],
lighting: vec![
"Directional key light".to_string(),
"Fill light for detail".to_string(),
"Rim light for separation".to_string(),
],
detail_level: "high".to_string(),
})
}
fn generate_quality_criteria(&self, _prompt: &str) -> Result<QualityCriteria> {
Ok(QualityCriteria {
aesthetic: vec![
"Visual harmony and balance".to_string(),
"Emotional resonance".to_string(),
"Style consistency".to_string(),
],
technical: vec![
"Proper lighting and shadow".to_string(),
"Accurate perspective".to_string(),
"Texture detail".to_string(),
],
composition: vec![
"Strong focal point".to_string(),
"Balanced composition".to_string(),
"Effective use of space".to_string(),
],
style_consistency: vec![
"Coherent style throughout".to_string(),
"Appropriate level of detail".to_string(),
"Consistent color palette".to_string(),
],
})
}
fn calculate_confidence(
&self,
perspectives: &[VisualPerspective],
styles: &[StyleRecommendation],
) -> f64 {
let perspective_conf: f64 =
perspectives.iter().map(|p| p.confidence).sum::<f64>() / perspectives.len() as f64;
let style_conf: f64 =
styles.iter().map(|s| s.confidence).sum::<f64>() / styles.len() as f64;
(perspective_conf * 0.6) + (style_conf * 0.4)
}
}
impl ThinkToolModule for ImageGeneration {
fn config(&self) -> &ThinkToolModuleConfig {
&self.config.base
}
fn execute(&self, context: &ThinkToolContext) -> Result<ThinkToolOutput> {
let result = self.analyze_prompt(&context.query)?;
let output_json = json!({
"analysis": result,
"module": self.config().name,
"version": self.config().version,
});
Ok(ThinkToolOutput::new(
self.config().name.clone(),
result.overall_confidence,
output_json,
))
}
}
pub struct ImageGenerationBuilder {
config: ImageGenerationConfig,
}
impl Default for ImageGenerationBuilder {
fn default() -> Self {
Self::new()
}
}
impl ImageGenerationBuilder {
pub fn new() -> Self {
Self {
config: ImageGenerationConfig::new(
"ImageGeneration",
"1.0.0",
"Structured reasoning for visual concepts and AI image generation planning",
),
}
}
pub fn min_perspectives(mut self, count: usize) -> Self {
self.config.min_perspectives = count;
self
}
pub fn max_styles(mut self, count: usize) -> Self {
self.config.max_styles = count;
self
}
pub fn include_technical_specs(mut self, include: bool) -> Self {
self.config.include_technical_specs = include;
self
}
pub fn include_quality_criteria(mut self, include: bool) -> Self {
self.config.include_quality_criteria = include;
self
}
pub fn style_bias(mut self, style: impl Into<String>) -> Self {
self.config.default_style_bias = Some(style.into());
self
}
pub fn build(self) -> ImageGeneration {
ImageGeneration {
config: self.config,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_image_generation_new() {
let module = ImageGeneration::new();
assert_eq!(module.config().name, "ImageGeneration");
assert_eq!(module.config().version, "1.0.0");
}
#[test]
fn test_image_generation_builder() {
let module = ImageGeneration::builder()
.min_perspectives(12)
.max_styles(8)
.include_technical_specs(true)
.include_quality_criteria(true)
.style_bias("Cyberpunk")
.build();
assert_eq!(module.config.min_perspectives, 12);
assert_eq!(module.config.max_styles, 8);
assert!(module.config.include_technical_specs);
assert!(module.config.include_quality_criteria);
assert_eq!(
module.config.default_style_bias,
Some("Cyberpunk".to_string())
);
}
#[test]
fn test_image_generation_execution() {
let module = ImageGeneration::new();
let context =
ThinkToolContext::new("A cyberpunk cityscape with neon signs and rainy streets");
let result = module.execute(&context).unwrap();
assert_eq!(result.module, "ImageGeneration");
assert!(result.confidence > 0.0);
assert!(result.confidence <= 1.0);
let analysis = result.get("analysis").unwrap();
assert!(analysis.get("perspectives").is_some());
assert!(analysis.get("style_recommendations").is_some());
}
}