pub mod builder;
pub mod config;
pub mod converters;
pub mod error;
pub mod images_to_text;
pub mod model;
pub mod pipelines;
pub mod url_to_text;
#[cfg(feature = "uniffi")]
pub mod uniffi_bindings;
#[cfg(feature = "uniffi")]
pub use uniffi_bindings::*;
pub use builder::{ImportResult, LlmProvider, RecipeImporter, RecipeImporterBuilder};
pub use config::AiConfig;
pub use converters::{ConversionMetadata, ConversionResult, TokenUsage};
pub use error::ImportError;
pub use images_to_text::ImageSource;
pub use model::Recipe;
pub fn generate_frontmatter(metadata: &std::collections::HashMap<String, String>) -> String {
if metadata.is_empty() {
return String::new();
}
let mut frontmatter = String::from("---\n");
let mut keys: Vec<_> = metadata.keys().collect();
keys.sort();
for key in keys {
if let Some(value) = metadata.get(key) {
if value.contains('\n') || value.contains('"') || value.contains(':') {
frontmatter.push_str(&format!("{}: \"{}\"\n", key, value.replace('"', "\\\"")));
} else {
frontmatter.push_str(&format!("{key}: {value}\n"));
}
}
}
frontmatter.push_str("---\n\n");
frontmatter
}
pub async fn fetch_recipe(url: &str) -> Result<model::Recipe, ImportError> {
extract_recipe_from_url(url).await
}
pub async fn fetch_recipe_with_timeout(
url: &str,
timeout: Option<std::time::Duration>,
) -> Result<model::Recipe, ImportError> {
let mut builder = RecipeImporter::builder().url(url).extract_only();
if let Some(t) = timeout {
builder = builder.timeout(t);
}
match builder.build().await? {
builder::ImportResult::Recipe(r) => Ok(r),
builder::ImportResult::Cooklang { .. } => unreachable!("extract_only sets Recipe mode"),
}
}
pub async fn convert_recipe_with_config(
recipe: &model::Recipe,
provider_name: Option<&str>,
api_key: Option<String>,
model: Option<String>,
) -> Result<String, ImportError> {
let text = recipe.to_text_with_metadata();
let mut builder = RecipeImporter::builder().text(&text);
if let Some(name) = provider_name {
let provider = match name {
"openai" => LlmProvider::OpenAI,
"anthropic" => LlmProvider::Anthropic,
"google" => LlmProvider::Google,
"ollama" => LlmProvider::Ollama,
"azure_openai" => LlmProvider::AzureOpenAI,
_ => {
return Err(ImportError::ConversionError(format!(
"Unknown provider: {}",
name
)))
}
};
builder = builder.provider(provider);
}
if let Some(key) = api_key {
builder = builder.api_key(&key);
}
if let Some(m) = model {
builder = builder.model(&m);
}
match builder.build().await? {
builder::ImportResult::Cooklang { content, .. } => Ok(content),
builder::ImportResult::Recipe(_) => unreachable!("Default mode is Cooklang"),
}
}
pub async fn convert_recipe_with_provider(
recipe: &model::Recipe,
provider_name: Option<&str>,
) -> Result<String, ImportError> {
let text = recipe.to_text_with_metadata();
let mut builder = RecipeImporter::builder().text(&text);
if let Some(name) = provider_name {
let provider = match name {
"openai" => LlmProvider::OpenAI,
"anthropic" => LlmProvider::Anthropic,
"google" => LlmProvider::Google,
"ollama" => LlmProvider::Ollama,
"azure_openai" => LlmProvider::AzureOpenAI,
_ => {
return Err(ImportError::ConversionError(format!(
"Unknown provider: {}",
name
)))
}
};
builder = builder.provider(provider);
}
match builder.build().await? {
builder::ImportResult::Cooklang { content, .. } => Ok(content),
builder::ImportResult::Recipe(_) => unreachable!("Default mode is Cooklang"),
}
}
pub async fn import_from_url(url: &str) -> Result<String, ImportError> {
match RecipeImporter::builder().url(url).build().await? {
builder::ImportResult::Cooklang { content, .. } => Ok(content),
builder::ImportResult::Recipe(_) => unreachable!("Default mode is Cooklang"),
}
}
pub async fn extract_recipe_from_url(url: &str) -> Result<Recipe, ImportError> {
match RecipeImporter::builder()
.url(url)
.extract_only()
.build()
.await?
{
builder::ImportResult::Recipe(r) => Ok(r),
builder::ImportResult::Cooklang { .. } => unreachable!("extract_only sets Recipe mode"),
}
}
pub async fn convert_text_to_cooklang(text: &str) -> Result<String, ImportError> {
match RecipeImporter::builder().text(text).build().await? {
builder::ImportResult::Cooklang { content, .. } => Ok(content),
builder::ImportResult::Recipe(_) => unreachable!("Text + Cooklang mode"),
}
}