use crate::memory::alloc_dynamic;
use anyhow::Result;
use base64::{Engine as _, engine::general_purpose};
use dynamic::{Dynamic, Type};
use std::future::Future;
extern "C" fn llm_complete(openai: *const Dynamic, value: *const Dynamic) -> *const Dynamic {
let openai = unsafe { (&*openai).clone() };
let value = unsafe { (&*value).clone() };
let result = root::sync_await!(llm::complete(openai, value, None)).unwrap_or(Dynamic::Null);
alloc_dynamic(result)
}
extern "C" fn llm_audio(openai: *const Dynamic, value: *const Dynamic) -> *const Dynamic {
let openai = unsafe { (&*openai).clone() };
let value = unsafe { (&*value).clone() };
let text = root::sync_await!(llm::audio_recognize(openai, value)).ok().unwrap_or(Dynamic::Null);
alloc_dynamic(text)
}
extern "C" fn llm_tts(openai: *const Dynamic, value: *const Dynamic) -> *const Dynamic {
let openai = unsafe { (&*openai).clone() };
let value = unsafe { (&*value).clone() };
let audio = root::sync_await!(llm::tts(openai, value)).ok().unwrap_or(Dynamic::Null);
alloc_dynamic(audio)
}
fn task_value(id: &str, status: &str, info: Dynamic) -> Dynamic {
dynamic::map!("id"=> id, "status"=> status, "info"=> info)
}
fn start_llm_task<F, Fut>(info: Dynamic, f: F) -> Dynamic
where
F: FnOnce() -> Fut + 'static + Send,
Fut: Future<Output = Result<Dynamic>> + 'static + Send,
{
let id = uuid::Uuid::new_v4().to_string();
let path = format!("local/tasks/{}", id);
let running = task_value(&id, "running", info.deep_clone());
let done_id = id.clone();
let done_path = path.clone();
let done_info = info.deep_clone();
let runner = async move {
match f().await {
Ok(result) => {
let _ = root::add_value(&done_path, dynamic::map!("id"=> done_id, "status"=> "done", "info"=> done_info, "result"=> result));
Ok(())
}
Err(err) => {
let _ = root::add_value(&done_path, dynamic::map!("id"=> done_id, "status"=> "error", "info"=> done_info, "error"=> err.to_string()));
Err(err)
}
}
};
let object = if tokio::runtime::Handle::try_current().is_ok() {
root::Object::Task(tokio::task::spawn(runner), running)
} else {
root::Object::ThreadTask(
std::thread::spawn(move || {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(runner)
}),
running,
)
};
let _ = root::add(&path, object);
id.into()
}
extern "C" fn llm_deep(openai: *const Dynamic, value: *const Dynamic, notifier: *const Dynamic) -> *const Dynamic {
let openai = unsafe { (&*openai).clone() };
let value = unsafe { (&*value).clone() };
let notifier = unsafe { (&*notifier).clone() };
let id = start_llm_task(value.clone(), || async move {
let r = llm::complete(openai, value, Some(notifier.clone())).await?;
llm::notify(¬ifier, r.clone())?;
Ok(r)
});
alloc_dynamic(id.into())
}
fn image_url(value: &Dynamic) -> Option<Dynamic> {
value.get_dynamic("url").or_else(|| value.get_dynamic("image_url")).or_else(|| value.get_dynamic("image"))
}
fn image_b64(value: &Dynamic) -> Option<Dynamic> {
value.get_dynamic("b64_json").or_else(|| value.get_dynamic("base64")).or_else(|| value.get_dynamic("image_base64"))
}
async fn store_generated_image(result: &Dynamic, image_name: &str) -> Option<String> {
if let Some(b64) = image_b64(result) {
if let Ok(data) = general_purpose::STANDARD.decode(b64.as_str()) {
let _ = root::insert("redis/images", image_name, Dynamic::Bytes(data));
return Some(format!("/images/{}", image_name));
}
}
if let Some(url) = image_url(result) {
if let Ok(resp) = reqwest::get(url.as_str()).await {
if let Ok(data) = resp.bytes().await {
let _ = root::insert("redis/images", image_name, Dynamic::Bytes(data.to_vec()));
return Some(format!("/images/{}", image_name));
}
}
return Some(url.as_str().to_string());
}
None
}
extern "C" fn llm_image(openai: *const Dynamic, value: *const Dynamic, notifier: *const Dynamic) -> *const Dynamic {
let openai = unsafe { (&*openai).clone() };
let value = unsafe { (&*value).clone() };
let notifier = unsafe { (&*notifier).clone() };
let id = start_llm_task(value.clone(), || async move {
let r = llm::image(openai, value, Some(notifier.clone())).await?;
if let (Some(world_id), Some(image_id)) = (notifier.get_dynamic("world_id"), notifier.get_dynamic("image_id")) {
let image_name = format!("world-{}-{}", world_id.as_str(), image_id.as_str());
let local_url = store_generated_image(&r, &image_name).await.unwrap_or_default();
let image_key = format!("redis/worlds/{}/images/{}", world_id.as_str(), image_id.as_str());
let image_info = dynamic::map!();
image_info.insert("id", image_id.clone());
image_info.insert("kind", "world_intro");
image_info.insert("status", "ready");
if !local_url.is_empty() {
image_info.insert("url", local_url.clone());
}
if let Some(prompt) = notifier.get_dynamic("prompt") {
image_info.insert("prompt", prompt.clone());
}
let _ = root::add_value(&image_key, image_info.clone());
let ws_msg = dynamic::map!("type" => "world_image_ready", "world_id" => world_id.clone(), "image_id" => image_id.clone(), "image" => image_info);
let _ = llm::notify(¬ifier, ws_msg);
} else {
let _ = llm::notify(¬ifier, r.clone());
}
Ok(r)
});
alloc_dynamic(id.into())
}
pub const LLM_NATIVE: [(&str, &[Type], Type, *const u8); 5] = [
("complete", &[Type::Any, Type::Any], Type::Any, llm_complete as *const u8),
("image", &[Type::Any, Type::Any, Type::Any], Type::Any, llm_image as *const u8),
("audio", &[Type::Any, Type::Any], Type::Any, llm_audio as *const u8),
("tts", &[Type::Any, Type::Any], Type::Any, llm_tts as *const u8),
("deep", &[Type::Any, Type::Any, Type::Any], Type::Any, llm_deep as *const u8),
];