Skip to content

Commit

Permalink
refactor(dictionary): extract dictionary trait
Browse files Browse the repository at this point in the history
#2

: placedholder
  • Loading branch information
0xRichardH committed Aug 4, 2024
1 parent 9702567 commit 5099d89
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 64 deletions.
17 changes: 9 additions & 8 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
[package]
name = "logseq-plugin-vocabulary-card"
version = "0.1.0"
version = "0.1.1"
authors = ["Richard Hao <[email protected]>"]
edition = "2021"

[lib]
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.
Expand Down
51 changes: 51 additions & 0 deletions lib/src/gemini_dictionary.rs
Original file line number Diff line number Diff line change
@@ -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<WordDefinition, String> {
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<WordDefinition, String> {
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::<WordDefinition>(dictionary_str)
.map_err(|e| format!("serde error: {}", e))?;
Ok(definition)
}
19 changes: 19 additions & 0 deletions lib/src/http_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use reqwest::StatusCode;

pub async fn make_request(url: &str, body: serde_json::Value) -> Result<serde_json::Value, String> {
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::<serde_json::Value>()
.await
.map_err(|e| format!("json error: {}", e))?;
Ok(json)
}
77 changes: 22 additions & 55 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,73 +1,40 @@
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,
examples: Vec<String>,
image: String,
}

#[wasm_bindgen]
pub async fn define_word(word: &str, api_key: &str) -> Result<JsValue, JsValue> {
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<WordDefinition, String>;
// fn get_llm_models(&self) -> Result<Vec<String>, 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<serde_json::Value, String> {
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::<serde_json::Value>()
#[wasm_bindgen]
pub async fn define_word(word: &str, api_key: &str) -> Result<JsValue, JsValue> {
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<Word, String> {
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::<Word>(dictionary_str).map_err(|e| format!("serde error: {}", e))?;
Ok(dictionary)
Ok(serde_wasm_bindgen::to_value(&word_defination)?)
}
2 changes: 1 addition & 1 deletion plugin/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down

0 comments on commit 5099d89

Please sign in to comment.