use std::ffi::CString;
use std::ffi::c_void;
use std::path::PathBuf;
use ringo_fm_sys as sys;
use crate::error::{Error, Result};
#[derive(Debug, Clone, Default)]
pub struct Prompt {
components: Vec<Component>,
}
#[derive(Debug, Clone)]
enum Component {
Text(String),
Image(ImageAttachment),
Attachment(Attachment),
}
#[derive(Debug, Clone)]
pub struct ImageAttachment {
pub path: PathBuf,
pub identifier: Option<String>,
}
impl ImageAttachment {
pub fn new(path: impl Into<PathBuf>) -> Self {
Self { path: path.into(), identifier: None }
}
pub fn identified(path: impl Into<PathBuf>, identifier: impl Into<String>) -> Self {
Self { path: path.into(), identifier: Some(identifier.into()) }
}
}
#[derive(Debug, Clone)]
pub struct Attachment {
pub path: PathBuf,
pub label: Option<String>,
}
impl Attachment {
pub fn new(path: impl Into<PathBuf>) -> Self {
Self { path: path.into(), label: None }
}
pub fn labeled(path: impl Into<PathBuf>, label: impl Into<String>) -> Self {
Self { path: path.into(), label: Some(label.into()) }
}
}
impl Prompt {
pub fn builder() -> Self {
Self::default()
}
pub fn text(text: impl Into<String>) -> Self {
Self { components: vec![Component::Text(text.into())] }
}
pub fn add_text(mut self, text: impl Into<String>) -> Self {
self.components.push(Component::Text(text.into()));
self
}
pub fn add_image(mut self, image: ImageAttachment) -> Self {
self.components.push(Component::Image(image));
self
}
pub fn add_attachment(mut self, attachment: Attachment) -> Self {
self.components.push(Component::Attachment(attachment));
self
}
pub(crate) fn into_composed(self) -> Result<ComposedPrompt> {
let ptr = unsafe { sys::FMComposedPromptInitialize() };
let composed = ComposedPrompt { ptr };
for component in self.components {
match component {
Component::Text(t) => {
let cstr = CString::new(t).map_err(|e| Error::Native(e.to_string()))?;
unsafe { sys::FMComposedPromptAddText(composed.ptr, cstr.as_ptr()) };
}
Component::Image(img) => {
if img.identifier.is_some() {
return Err(Error::IdentifiedImageUnsupported);
}
let path = path_to_cstring(&img.path)?;
let mut err: sys::FMComposedPromptAddImageError = 0;
let ok = unsafe {
sys::FMComposedPromptAddAttachment(
composed.ptr,
path.as_ptr(),
std::ptr::null(),
&mut err,
)
};
if !ok {
return Err(map_image_error(err));
}
}
Component::Attachment(att) => {
let path = path_to_cstring(&att.path)?;
let label_c = match att.label {
Some(l) => Some(CString::new(l).map_err(|e| Error::Native(e.to_string()))?),
None => None,
};
let label_ptr = label_c.as_ref().map_or(std::ptr::null(), |c| c.as_ptr());
let mut err: sys::FMComposedPromptAddImageError = 0;
let ok = unsafe {
sys::FMComposedPromptAddAttachment(composed.ptr, path.as_ptr(), label_ptr, &mut err)
};
if !ok {
return Err(map_image_error(err));
}
}
}
}
Ok(composed)
}
}
impl From<&str> for Prompt {
fn from(s: &str) -> Self {
Prompt::text(s)
}
}
impl From<String> for Prompt {
fn from(s: String) -> Self {
Prompt::text(s)
}
}
pub(crate) struct ComposedPrompt {
ptr: *const c_void,
}
impl ComposedPrompt {
pub(crate) fn into_raw(self) -> *const c_void {
self.ptr
}
}
fn path_to_cstring(path: &std::path::Path) -> Result<CString> {
let s = path
.to_str()
.ok_or_else(|| Error::Native(format!("non-utf8 path: {}", path.display())))?;
CString::new(s).map_err(|e| Error::Native(e.to_string()))
}
fn map_image_error(err: sys::FMComposedPromptAddImageError) -> Error {
match err {
x if x == sys::FMComposedPromptAddImageError_FMComposedPromptAddImageErrorUnsupported => {
Error::AttachmentUnsupported
}
_ => Error::Native("failed to add image to prompt".into()),
}
}