diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 9fd4ecb..cae8280 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "logseq-plugin-vocabulary-card" -version = "0.1.0" +version = "0.1.1" authors = ["Richard Hao "] edition = "2021" @@ -8,15 +8,16 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] -wasm-bindgen = "0.2.89" -wasm-bindgen-futures = "0.4.39" -serde-wasm-bindgen = "0.6.3" -serde = { version = "1.0.194", features = ["derive"] } -serde_json = "1.0.110" -reqwest = { version = "0.11.23", features = ["blocking", "json"] } +wasm-bindgen = "0.2.92" +wasm-bindgen-futures = "0.4.42" +serde-wasm-bindgen = "0.6.5" +serde = { version = "1.0.204", features = ["derive"] } +serde_json = "1.0.122" +reqwest = { version = "0.12.5", features = ["blocking", "json"] } +async-trait = "0.1.81" [dev-dependencies] -wasm-bindgen-test = "0.3.39" +wasm-bindgen-test = "0.3.42" [profile.release] # Tell `rustc` to optimize for small code size. diff --git a/lib/src/gemini_dictionary.rs b/lib/src/gemini_dictionary.rs new file mode 100644 index 0000000..fdd42e5 --- /dev/null +++ b/lib/src/gemini_dictionary.rs @@ -0,0 +1,51 @@ +use async_trait::async_trait; + +use crate::{http_request, Dictionary, WordDefinition}; + +pub struct GeminiDictionary { + api_key: String, +} + +impl GeminiDictionary { + pub fn new(api_key: &str) -> Self { + Self { + api_key: api_key.trim().to_string(), + } + } +} + +#[async_trait(?Send)] +impl Dictionary for GeminiDictionary { + async fn define(&self, word: &str) -> Result { + let url = format!( + "https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key={}", + self.api_key + ); + let req_body = serde_json::json!({ + "contents": [{ + "parts": [{ + "text": self.default_prompt(word) + }] + }] + }); + + let res_body = http_request::make_request(&url, req_body).await?; + let dictionary = extract_dictionary(res_body)?; + Ok(dictionary) + } +} + +fn extract_dictionary(res_body: serde_json::Value) -> Result { + let Some(dictionary_str) = res_body["candidates"][0]["content"]["parts"][0]["text"].as_str() + else { + return Err("json error: dictionary not found".to_string()); + }; + let dictionary_str = dictionary_str + .trim_start_matches("```json") + .trim_start_matches("```") + .trim_end_matches("```") + .trim(); + let definition = serde_json::from_str::(dictionary_str) + .map_err(|e| format!("serde error: {}", e))?; + Ok(definition) +} diff --git a/lib/src/http_request.rs b/lib/src/http_request.rs new file mode 100644 index 0000000..d8f92c0 --- /dev/null +++ b/lib/src/http_request.rs @@ -0,0 +1,19 @@ +use reqwest::StatusCode; + +pub async fn make_request(url: &str, body: serde_json::Value) -> Result { + let req_client = reqwest::Client::new(); + let res = req_client + .post(url) + .json(&body) + .send() + .await + .map_err(|e| format!("request error: {}", e))?; + if res.status() != StatusCode::OK { + return Err(format!("request error: {}", res.status())); + } + let json = res + .json::() + .await + .map_err(|e| format!("json error: {}", e))?; + Ok(json) +} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index b1514e8..275c6c4 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,10 +1,14 @@ -use reqwest::StatusCode; +mod gemini_dictionary; +mod http_request; + +use async_trait::async_trait; +use gemini_dictionary::GeminiDictionary; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; #[wasm_bindgen] #[derive(Serialize, Deserialize)] -pub struct Word { +pub struct WordDefinition { word: String, pronunciation: String, definition: String, @@ -12,62 +16,25 @@ pub struct Word { image: String, } -#[wasm_bindgen] -pub async fn define_word(word: &str, api_key: &str) -> Result { - let url = format!( - "https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key={}", - api_key.trim() - ); - let prompt = format!( - "please act as a dictionary, including the pronunciation, explanation, two examples of sentences, and one image. And the word is {}. please output the result in json format, the json keys are `word`, `pronunciation`, `definition`, `examples`, and `image`.", - word.trim() - ); - let req_body = serde_json::json!({ - "contents": [{ - "parts": [{ - "text": prompt - }] - }] - - }); - - let res_body = make_request(&url, req_body) - .await - .map_err(|e| JsValue::from_str(&e))?; - let dictionary = extract_dictionary(res_body).map_err(|e| JsValue::from_str(&e))?; +const PROMPT_TEMPLATE: &str = "please act as a dictionary, including the pronunciation, explanation, two examples of sentences, and one image. And the word is `{:word}`. please output the result in json format, the json keys are `word`, `pronunciation`, `definition`, `examples`, and `image`."; - Ok(serde_wasm_bindgen::to_value(&dictionary)?) +#[async_trait(?Send)] +pub trait Dictionary { + // fn set_llm_model(&self, model: &str) -> Result<&Self, String>; + async fn define(&self, word: &str) -> Result; + // fn get_llm_models(&self) -> Result, String>; + fn default_prompt(&self, word: &str) -> String { + PROMPT_TEMPLATE.replace("{:word}", word.trim()) + } } -async fn make_request(url: &str, body: serde_json::Value) -> Result { - let req_client = reqwest::Client::new(); - let res = req_client - .post(url) - .json(&body) - .send() - .await - .map_err(|e| format!("request error: {}", e))?; - if res.status() != StatusCode::OK { - return Err(format!("request error: {}", res.status())); - } - let json = res - .json::() +#[wasm_bindgen] +pub async fn define_word(word: &str, api_key: &str) -> Result { + let dictionary = GeminiDictionary::new(api_key); + let word_defination = dictionary + .define(word) .await - .map_err(|e| format!("json error: {}", e))?; - Ok(json) -} + .map_err(|e| JsValue::from_str(&e))?; -fn extract_dictionary(res_body: serde_json::Value) -> Result { - let Some(dictionary_str) = res_body["candidates"][0]["content"]["parts"][0]["text"].as_str() - else { - return Err("json error: dictionary not found".to_string()); - }; - let dictionary_str = dictionary_str - .trim_start_matches("```json") - .trim_start_matches("```") - .trim_end_matches("```") - .trim(); - let dictionary = - serde_json::from_str::(dictionary_str).map_err(|e| format!("serde error: {}", e))?; - Ok(dictionary) + Ok(serde_wasm_bindgen::to_value(&word_defination)?) } diff --git a/plugin/package.json b/plugin/package.json index b837943..b6080de 100644 --- a/plugin/package.json +++ b/plugin/package.json @@ -1,6 +1,6 @@ { "name": "logseq-plugin-vocabulary-card", - "version": "0.1.0", + "version": "0.1.1", "description": "A logseq plugin for creating vocabulary card", "main": "dist/index.html", "scripts": {