use std::sync::Mutex;
static WARNINGS: Mutex<Vec<String>> = Mutex::new(Vec::new());
const SUPPORTED_IMAGE_FORMATS: [&str; 10] = [
"jpg", "jpeg", "png", "tga", "bmp", "psd", " gif", "hdr", "pic", "ppm",
];
#[cxx::bridge]
mod ffi {
extern "Rust" {
fn log_warning(message: String);
}
unsafe extern "C++" {
include!("cliprs/clip.h");
include!("cliprs/rust_interface.h");
type clip_ctx;
fn init(path: String) -> *mut clip_ctx;
unsafe fn embed_text(ctx: *const clip_ctx, text: String) -> Result<Vec<f32>>;
unsafe fn embed_image(ctx: *const clip_ctx, path: String) -> Result<Vec<f32>>;
unsafe fn embed_compare(ctx: *const clip_ctx, p1: &Vec<f32>, p2: &Vec<f32>) -> f32;
unsafe fn end(ctx: *mut clip_ctx);
}
}
pub fn log_warning(message: String) {
if let Ok(mut warnings) = WARNINGS.lock() {
warnings.push(message);
}
}
pub struct ClipModel {
ctx: *mut ffi::clip_ctx,
}
unsafe impl Send for ClipModel {}
unsafe impl Sync for ClipModel {}
impl ClipModel {
pub fn new(model_path: impl Into<String>) -> Self {
Self {
ctx: ffi::init(model_path.into()),
}
}
pub fn embed_compare(&self, p1: &Vec<f32>, p2: &Vec<f32>) -> f32 {
unsafe { ffi::embed_compare(self.ctx, p1, p2) }
}
pub fn embed_text(&self, text: impl Into<String>) -> Result<Vec<f32>, String> {
match unsafe { ffi::embed_text(self.ctx, text.into()) } {
Ok(embed) if embed.is_empty() => Err("Text embedding is empty".to_string()),
Ok(embed) => Ok(embed),
Err(e) => Err(e.to_string()),
}
}
pub fn embed_image(&self, path: impl Into<String>) -> Result<Vec<f32>, String> {
let path: String = path.into();
if !SUPPORTED_IMAGE_FORMATS
.iter()
.any(|suffix| path.ends_with(suffix.trim()))
{
return Err(format!(
"Unsupported image format: {}",
path.split('.').last().unwrap_or_default()
));
}
match unsafe { ffi::embed_image(self.ctx, path.clone()) } {
Ok(embed) if embed.is_empty() => Err(format!("Embedding for {} is empty", path)),
Ok(embed) => Ok(embed),
Err(e) => Err(e.to_string()),
}
}
}
impl Drop for ClipModel {
fn drop(&mut self) {
unsafe {
ffi::end(self.ctx);
}
}
}
pub fn poll_warnings() -> Vec<String> {
WARNINGS
.lock()
.map(|mut w| w.drain(..).collect())
.unwrap_or_default()
}