use vil_server::prelude::*;
use std::sync::Arc;
use crate::analyzer::ImageAnalyzer;
use crate::config::VisionConfig;
#[derive(Debug, Deserialize)]
pub struct AnalyzeRequest {
pub image_base64: String,
#[serde(default = "default_ocr")]
pub ocr_enabled: bool,
}
fn default_ocr() -> bool {
true
}
#[derive(Debug, Serialize)]
pub struct AnalyzeResponseBody {
pub description: String,
pub objects: Vec<DetectedObjectSummary>,
pub text_content: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct DetectedObjectSummary {
pub label: String,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize)]
pub struct VisionStatsBody {
pub backend: String,
pub config: VisionConfigSummary,
pub version: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct VisionConfigSummary {
pub max_dimension: u32,
pub ocr_enabled: bool,
pub object_detection: bool,
pub min_confidence: f32,
pub model: String,
}
#[derive(Clone)]
pub struct VisionAnalyzer(pub Arc<dyn ImageAnalyzer>);
pub async fn analyze_handler(
ctx: ServiceCtx,
body: ShmSlice,
) -> HandlerResult<VilResponse<AnalyzeResponseBody>> {
let analyzer = &ctx.state::<VisionAnalyzer>().expect("VisionAnalyzer").0;
let req: AnalyzeRequest = body.json().expect("invalid JSON");
if req.image_base64.trim().is_empty() {
return Err(VilError::bad_request("image_base64 must not be empty"));
}
let image_bytes = req.image_base64.as_bytes();
match analyzer.analyze(image_bytes).await {
Ok(analysis) => {
let objects: Vec<DetectedObjectSummary> = analysis
.objects
.iter()
.map(|o| DetectedObjectSummary {
label: o.label.clone(),
confidence: o.confidence,
})
.collect();
Ok(VilResponse::ok(AnalyzeResponseBody {
description: analysis.description,
objects,
text_content: analysis.text_content,
}))
}
Err(e) => Err(VilError::internal(format!("analysis failed: {}", e))),
}
}
pub async fn stats_handler(ctx: ServiceCtx) -> VilResponse<VisionStatsBody> {
let analyzer = &ctx.state::<VisionAnalyzer>().expect("VisionAnalyzer").0;
let config = ctx.state::<Arc<VisionConfig>>().expect("VisionConfig");
VilResponse::ok(VisionStatsBody {
backend: analyzer.name().to_string(),
config: VisionConfigSummary {
max_dimension: config.max_dimension,
ocr_enabled: config.ocr_enabled,
object_detection: config.object_detection,
min_confidence: config.min_confidence,
model: config.model.clone(),
},
version: env!("CARGO_PKG_VERSION").into(),
})
}