feat(items): integrate Wikidata API for external data enrichment
- Added functionality to fetch additional attributes for selected items from Wikidata. - Defined structures for handling Wikidata API responses. - Incorporated async data fetching using futures and `gloo_net::http::Request`. - Enhanced the UI with a button to fetch external data for comparison. - Updated the comparison table to include external descriptions retrieved from Wikidata.
This commit is contained in:
parent
da720a48d3
commit
2733fc958b
|
@ -721,6 +721,8 @@ dependencies = [
|
||||||
"actix-files",
|
"actix-files",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
|
"futures",
|
||||||
|
"gloo-net 0.5.0",
|
||||||
"http 1.2.0",
|
"http 1.2.0",
|
||||||
"leptos",
|
"leptos",
|
||||||
"leptos_actix",
|
"leptos_actix",
|
||||||
|
@ -731,6 +733,7 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"uuid",
|
"uuid",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1123,6 +1126,27 @@ version = "0.31.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gloo-net"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43aaa242d1239a8822c15c645f02166398da4f8b5c4bae795c1f5b44e9eee173"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"gloo-utils",
|
||||||
|
"http 0.2.12",
|
||||||
|
"js-sys",
|
||||||
|
"pin-project",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gloo-net"
|
name = "gloo-net"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
@ -1735,7 +1759,7 @@ checksum = "8d71dea7d42c0d29c40842750232d3425ed1cf10e313a1f898076d20871dad32"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cached",
|
"cached",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"gloo-net",
|
"gloo-net 0.6.0",
|
||||||
"itertools",
|
"itertools",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
@ -2662,7 +2686,7 @@ dependencies = [
|
||||||
"const_format",
|
"const_format",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"futures",
|
"futures",
|
||||||
"gloo-net",
|
"gloo-net 0.6.0",
|
||||||
"http 1.2.0",
|
"http 1.2.0",
|
||||||
"inventory",
|
"inventory",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
|
|
@ -21,6 +21,9 @@ uuid = { version = "1.0", features = ["v4"] }
|
||||||
web-sys = { version = "0.3", features = ["Event"] }
|
web-sys = { version = "0.3", features = ["Event"] }
|
||||||
nostr-sdk = "0.37"
|
nostr-sdk = "0.37"
|
||||||
tokio = "1"
|
tokio = "1"
|
||||||
|
gloo-net = "0.5"
|
||||||
|
futures = "0.3"
|
||||||
|
wasm-bindgen-futures = "0.4"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
|
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
|
||||||
|
|
|
@ -39,6 +39,7 @@ pub fn App() -> impl IntoView {
|
||||||
description,
|
description,
|
||||||
tags,
|
tags,
|
||||||
reviews: vec![ReviewWithRating { content: review.clone(), rating }],
|
reviews: vec![ReviewWithRating { content: review.clone(), rating }],
|
||||||
|
wikidata_id: None,
|
||||||
};
|
};
|
||||||
items.push(item);
|
items.push(item);
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,11 +2,74 @@
|
||||||
/// Iterates through the items and renders their name, description, tags, and reviews.
|
/// Iterates through the items and renders their name, description, tags, and reviews.
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use crate::models::item::Item;
|
use crate::models::item::Item;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use futures::future;
|
||||||
|
use gloo_net::http::Request;
|
||||||
|
use wasm_bindgen_futures::spawn_local;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
// Define the structure for Wikidata API response
|
||||||
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
|
struct WikidataResponse {
|
||||||
|
entities: std::collections::HashMap<String, WikidataEntity>,
|
||||||
|
}
|
||||||
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
|
struct WikidataEntity {
|
||||||
|
labels: Option<std::collections::HashMap<String, WikidataLabel>>,
|
||||||
|
descriptions: Option<std::collections::HashMap<String, WikidataLabel>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
|
struct WikidataLabel {
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ItemsList(items: ReadSignal<Vec<Item>>) -> impl IntoView {
|
pub fn ItemsList(items: ReadSignal<Vec<Item>>) -> impl IntoView {
|
||||||
// Create a signal for selected items
|
// Create a signal for selected items
|
||||||
let (selected_items_signal, set_selected_items) = create_signal(Vec::<usize>::new());
|
let (selected_items_signal, set_selected_items) = create_signal(Vec::<usize>::new());
|
||||||
|
let (wikidata_data, set_wikidata_data) = create_signal(Vec::<Option<WikidataEntity>>::new());
|
||||||
|
|
||||||
|
// Fetch additional data from Wikidata for selected items
|
||||||
|
let fetch_wikidata = move || {
|
||||||
|
let selected_indices = selected_items_signal.get();
|
||||||
|
let selected_items: Vec<Item> = selected_indices
|
||||||
|
.iter()
|
||||||
|
.map(|&i| items.get()[i].clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
|
||||||
|
// Wrap `selected_items` in an `Arc` so it can be cloned
|
||||||
|
let selected_items = Arc::new(selected_items);
|
||||||
|
|
||||||
|
// Clone selected_items before the async block to avoid borrowing issues
|
||||||
|
let selected_items_clone: Arc<Vec<Item>> = Arc::clone(&selected_items);
|
||||||
|
|
||||||
|
// For each selected item, fetch Wikidata attributes
|
||||||
|
let futures = selected_items_clone.iter().map(move |item| {
|
||||||
|
let wikidata_id = item.wikidata_id.clone();
|
||||||
|
async move {
|
||||||
|
if let Some(id) = wikidata_id {
|
||||||
|
let url = format!("https://www.wikidata.org/wiki/Special:EntityData/{}.json", id);
|
||||||
|
match Request::get(&url).send().await {
|
||||||
|
Ok(response) => match response.json::<WikidataResponse>().await {
|
||||||
|
Ok(parsed_data) => parsed_data.entities.get(&id).cloned(),
|
||||||
|
Err(_) => None,
|
||||||
|
},
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
|
||||||
|
spawn_local(async move {
|
||||||
|
let results = future::join_all(futures).await;
|
||||||
|
set_wikidata_data.set(results);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Function to toggle selection of an item
|
// Function to toggle selection of an item
|
||||||
let toggle_selection = move |i: usize| {
|
let toggle_selection = move |i: usize| {
|
||||||
|
@ -52,6 +115,9 @@ pub fn ItemsList(items: ReadSignal<Vec<Item>>) -> impl IntoView {
|
||||||
}).collect::<Vec<_>>() }
|
}).collect::<Vec<_>>() }
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
// Button to fetch Wikidata attributes
|
||||||
|
<button on:click=move |_| fetch_wikidata()>{ "Fetch External Data" }</button>
|
||||||
|
|
||||||
// Comparison Table
|
// Comparison Table
|
||||||
<h2>{ "Comparison Table" }</h2>
|
<h2>{ "Comparison Table" }</h2>
|
||||||
<table>
|
<table>
|
||||||
|
@ -61,13 +127,15 @@ pub fn ItemsList(items: ReadSignal<Vec<Item>>) -> impl IntoView {
|
||||||
<th>{ "Description" }</th>
|
<th>{ "Description" }</th>
|
||||||
<th>{ "Tags" }</th>
|
<th>{ "Tags" }</th>
|
||||||
<th>{ "Reviews" }</th>
|
<th>{ "Reviews" }</th>
|
||||||
|
<th>{ "External Description (Wikidata)" }</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{move || {
|
{move || {
|
||||||
let selected_indices = selected_items_signal.get();
|
let selected_indices = selected_items_signal.get();
|
||||||
selected_indices.iter().map(|&i| {
|
selected_indices.iter().enumerate().map(|(idx, &i)| {
|
||||||
let item = &items.get()[i];
|
let item = &items.get()[i];
|
||||||
|
let wikidata_entity = wikidata_data.get().get(idx).cloned().flatten();
|
||||||
view! {
|
view! {
|
||||||
<tr key={i.to_string()}>
|
<tr key={i.to_string()}>
|
||||||
<td>{ &item.name }</td>
|
<td>{ &item.name }</td>
|
||||||
|
@ -82,6 +150,20 @@ pub fn ItemsList(items: ReadSignal<Vec<Item>>) -> impl IntoView {
|
||||||
<span>{ format!("Rating: {}/5 ", review.rating) }</span>
|
<span>{ format!("Rating: {}/5 ", review.rating) }</span>
|
||||||
}).collect::<Vec<_>>()}
|
}).collect::<Vec<_>>()}
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
{move || {
|
||||||
|
let entity = wikidata_entity.clone(); // Clone the value
|
||||||
|
match entity {
|
||||||
|
Some(entity) => entity
|
||||||
|
.descriptions
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|descriptions| descriptions.get("en"))
|
||||||
|
.map(|label| label.value.clone())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
None => String::from("No description"),
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
}).collect::<Vec<_>>()
|
}).collect::<Vec<_>>()
|
||||||
|
|
|
@ -9,6 +9,7 @@ pub struct Item {
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub tags: Vec<(String, String)>,
|
pub tags: Vec<(String, String)>,
|
||||||
pub reviews: Vec<ReviewWithRating>,
|
pub reviews: Vec<ReviewWithRating>,
|
||||||
|
pub wikidata_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
|
Loading…
Reference in New Issue