diff --git a/graph/src/inference.rs b/graph/src/inference.rs index 6abf4ac..dbfb32e 100644 --- a/graph/src/inference.rs +++ b/graph/src/inference.rs @@ -9,6 +9,7 @@ const GL_PREFIX: &str = "https://graphofliberty.org/"; const INFERENCE_GRAPH: GraphNameRef = GraphNameRef::NamedNode(NamedNodeRef::new_unchecked("https://graphofliberty.org/inference")); const INPUT_GRAPH: GraphNameRef = GraphNameRef::NamedNode(NamedNodeRef::new_unchecked("https://graphofliberty.org/input")); +#[derive(Clone)] pub struct InferenceEngine { ontology: Store, } diff --git a/publish/src/app.rs b/publish/src/app.rs index 3dfae43..1995db2 100644 --- a/publish/src/app.rs +++ b/publish/src/app.rs @@ -3,7 +3,6 @@ use crate::rdf::ontology::{LabeledIri, Ontology}; use crate::rdf::term_helper::{TermHelper, TermHelperMut}; use crate::widget::iri_input::iri_input; use crate::widget::navigation_area::navigation_area; -use gl_graph::vocab::rdac; use gl_search::language; use gl_search::{Schema, SearchDocument, SearchIndex, Value}; use http::StatusCode; @@ -22,9 +21,11 @@ use ldp::reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; use ldp::{RdfSource, RdfSourceUpdateResponse, ResourceRequestBuilder, SerializationOptions}; use oxigraph::io::RdfFormat; use oxigraph::model::vocab::{rdf, rdfs}; -use oxigraph::model::{BaseDirection, Dataset, NamedNode, NamedOrBlankNode, Quad, Term}; +use oxigraph::model::{BaseDirection, Dataset, Graph, NamedNode, NamedOrBlankNode, NamedOrBlankNodeRef, Quad, Term, TripleRef}; +use oxigraph::store::Store; use tracing::{debug_span, error, trace}; -use gl_graph::{vocab, CurieHelper}; +use gl_graph::CurieHelper; +use gl_graph::inference::InferenceEngine; #[derive(Clone, Debug)] pub(crate) enum Message { @@ -38,7 +39,7 @@ pub(crate) enum Message { ShowNewDocumentButtons, HideNewDocumentButtons, ShowError(String), - AddRow(Option), + AddRow(Option<(QuadKey, RowState)>), DeleteRow(QuadKey), DeleteAllRows, OpenQueryWindow(SearchResultClickAction), @@ -65,6 +66,10 @@ pub(crate) enum Message { NavigateForward, ResetState, SetReadOnly(bool), + SetInferredTypes(bool), + SetInferredProperties(bool), + RunInference, + SetInferredTriples(Dataset), Event(Event), } @@ -93,13 +98,17 @@ pub(crate) struct Publisher { http_client: ClientWithMiddleware, curie_helper: CurieHelper, ontology: Ontology, + inference_engine: InferenceEngine, abbreviated_datatypes: Vec, window_id: window::Id, url_input: String, url_input_valid: bool, navigator: Navigator, document: RdfSource>, + inferred_triples: Graph, show_read_only: bool, + show_inferred_types: bool, + show_inferred_properties: bool, hovered_row: Option, search_state: Option, index: SearchIndex, @@ -120,6 +129,8 @@ impl Publisher { .expect("Failed to build ontology") }); + let inference_engine = InferenceEngine::new(ontology.store().clone()); + let index = SearchIndex::builder() .with_path("/home/alex/.local/share/org.graphofliberty.desktop/index") .build() @@ -152,13 +163,17 @@ impl Publisher { http_client, curie_helper, ontology, + inference_engine, abbreviated_datatypes, window_id: id, url_input: starting_url.to_string(), url_input_valid: true, navigator, document, + inferred_triples: Graph::new(), show_read_only: false, + show_inferred_types: false, + show_inferred_properties: false, hovered_row: None, search_state: None, index, @@ -234,9 +249,21 @@ impl Publisher { let messages = document .dataset() .quads - .keys() - .map(|key| Message::AddRow(Some(key))); - task = Task::batch(messages.map(Task::done)).chain(Task::done(Message::ResetState)); + .iter() + .map(|(key, quad)| { + let datatype_state = combo_box::State::new( + self.abbreviated_datatypes.clone(), + ); + + let state = RowState { + read_only: self.ontology.is_read_only(quad.as_ref()), + datatype_state, + }; + Message::AddRow(Some((key, state))) + }); + task = Task::batch(messages.map(Task::done)) + .chain(Task::done(Message::RunInference)) + .chain(Task::done(Message::ResetState)); self.document = document; } @@ -250,33 +277,21 @@ impl Publisher { quad.object = Term::NamedNode(empty_node); let key = self.document.dataset_mut().quads.insert(quad); - task = Task::done(Message::AddRow(Some(key))); - } - Message::AddRow(Some(key)) => { - let quad = self - .document - .dataset() - .quads - .get(key) - .expect("Failed to get quad from document"); - let read_only = self.ontology.is_read_only(quad.as_ref()); - let term = TermHelper::new(&quad.object); - let datatype = term - .datatype() - .map(|datatype| self.curie_helper.abbreviate(None, datatype.as_str()) - .unwrap_or(datatype.as_str().to_string())); - let datatype_state = combo_box::State::new( self.abbreviated_datatypes.clone(), ); let state = RowState { - read_only, + read_only: false, datatype_state, }; + task = Task::done(Message::AddRow(Some((key, state)))); + } + Message::AddRow(Some((key, state))) => { self.document .dataset_mut() .associated_data .insert(key, state); + self.modified = true; } Message::DeleteRow(key) => { @@ -472,7 +487,7 @@ impl Publisher { let subject = NamedNode::new_unchecked(url.as_str()); self.document = RdfSource::new(url); - let template_quads = self + /*let template_quads = self .ontology .template_triples(class.as_ref(), subject.as_ref()) .map(|triples| { @@ -490,7 +505,7 @@ impl Publisher { let new_keys = self.document.dataset_mut().extend(template_quads.into_iter()); let messages = new_keys.map(|triple| Message::AddRow(Some(triple))); - task = task.chain(Task::batch(messages.map(Task::done))); + task = task.chain(Task::batch(messages.map(Task::done)));*/ } Message::ResetState => { self.modified = false; @@ -526,6 +541,28 @@ impl Publisher { Message::SetReadOnly(value) => { self.show_read_only = value; } + Message::SetInferredTypes(value) => { + self.show_inferred_types = value; + } + Message::SetInferredProperties(value) => { + self.show_inferred_properties = value; + } + Message::RunInference => { + let dataset = self.document + .dataset() + .quads + .iter() + .map(|(_, quad)| quad) + .collect(); + + let engine = self.inference_engine.clone(); + task = Task::perform(tokio::task::spawn_blocking(move || { + engine.run(&dataset).unwrap() + }), |dataset| Message::SetInferredTriples(dataset.unwrap())); + } + Message::SetInferredTriples(dataset) => { + self.inferred_triples = Graph::from_iter(&dataset); + } Message::Event(Event::KeyPressed { key: keyboard::Key::Named(key::Named::Tab), modifiers, @@ -823,6 +860,29 @@ impl Publisher { column(rows).into() }; + let inference_table = if self.show_inferred_properties { + let subject_column = table::column("Subject", |triple: TripleRef| { + let curie = if let NamedOrBlankNodeRef::NamedNode(subject) = triple.subject { + self.curie_helper.abbreviate(None, subject.as_str()) + } else { None }; + + curie.map(text) + }); + + let predicate_column = table::column("Predicate", |triple: TripleRef| { + let predicate_info = self.ontology.info(&triple.predicate.into_owned(), &*language::ENGLISH_OR_UNTAGGED); + let label = if predicate_info.label.is_empty() { + self.curie_helper + .abbreviate(None, triple.predicate.as_str()) + .unwrap_or(triple.predicate.as_str().to_string()) + } else { predicate_info.label }; + text(label) + }); + + let object_column = table::column("Object", |triple: TripleRef| text(triple.object.to_string())); + Some(table([subject_column, predicate_column, object_column], self.inferred_triples.iter())) + } else { None }; + let save_button_base = button(text("\u{1f4be}")); let save_button = if self.modified { save_button_base.on_press(Message::SaveGraph(false)) @@ -830,10 +890,22 @@ impl Publisher { save_button_base }; + let inferred_type_toggle = toggler(self.show_inferred_types) + .on_toggle(Message::SetInferredTypes); + + let inferred_property_toggle = toggler(self.show_inferred_properties) + .on_toggle(Message::SetInferredProperties); + let read_only_toggle = toggler(self.show_read_only) .on_toggle(Message::SetReadOnly); let footer = row![ + space::horizontal(), + text("Show Inferred Types"), + inferred_type_toggle, + space::horizontal(), + text("Show Inferred Properties"), + inferred_property_toggle, space::horizontal(), text("Show Read Only Triples"), read_only_toggle @@ -847,9 +919,9 @@ impl Publisher { address_input, save_button, ], - scrollable(body), - space::vertical(), footer, + scrollable(body), + scrollable(inference_table), ]) .on_back_click(Message::NavigateBack) .on_forward_click(Message::NavigateForward); diff --git a/publish/src/main.rs b/publish/src/main.rs index 634e329..73fed60 100644 --- a/publish/src/main.rs +++ b/publish/src/main.rs @@ -15,20 +15,19 @@ use ldp::reqwest::{Client, Url}; use ldp::reqwest_middleware::ClientBuilder; use ldp::traverse::Traverse; use oxigraph::io::{RdfFormat, RdfParser, RdfSerializer}; -use oxigraph::model::{Dataset, Graph, GraphName, GraphNameRef, NamedNode, Quad, Triple}; +use oxigraph::model::{Dataset, GraphName, NamedNode, Quad, Triple}; use oxigraph::sparql::{QueryResults, SparqlEvaluator}; use oxigraph::sparql::results::{QueryResultsFormat, QueryResultsSerializer}; use oxigraph::store::Store; -use tracing::{debug, debug_span, error, field}; +use tracing::{debug_span, error, field}; use crate::app::Publisher; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::{EnvFilter, fmt}; use tracing_subscriber::fmt::format::FmtSpan; use gl_graph::inference::InferenceEngine; -use gl_search::{Document, Schema, SearchIndex}; +use gl_search::SearchIndex; use crate::args::{AppArgs, Command}; -use crate::rdf::ontology::Ontology; fn main() -> color_eyre::Result<()> { let appender = tracing_appender::rolling::never("/tmp", "publisher-log"); @@ -38,9 +37,6 @@ fn main() -> color_eyre::Result<()> { .init(); color_eyre::install()?; - let store = Store::open("/home/alex/.local/share/org.graphofliberty.desktop/ontology")?; - let inference_engine = InferenceEngine::new(store.clone()); - let mut index = SearchIndex::builder() .with_path("/home/alex/.local/share/org.graphofliberty.desktop/index") .build() @@ -49,6 +45,9 @@ fn main() -> color_eyre::Result<()> { let args = AppArgs::parse(); match args.command { Some(Command::Query(args)) => { + let store = Store::open("/home/alex/.local/share/org.graphofliberty.desktop/ontology")?; + let inference_engine = InferenceEngine::new(store.clone()); + let graph_name = GraphName::NamedNode(NamedNode::new_unchecked(format!("file://{}", args.dataset_path.to_string_lossy()))); let mut dataset = RdfParser::from_format(RdfFormat::Turtle) .with_default_graph(graph_name) @@ -92,6 +91,9 @@ fn main() -> color_eyre::Result<()> { }*/ } Some(Command::Reindex) => { + let store = Store::open("/home/alex/.local/share/org.graphofliberty.desktop/ontology")?; + let inference_engine = InferenceEngine::new(store.clone()); + let mut writer = index.writer()?; debug_span!("Clear Index").in_scope(|| { writer.delete_all_documents()?; diff --git a/publish/src/rdf/ontology.rs b/publish/src/rdf/ontology.rs index afe577a..df3f287 100644 --- a/publish/src/rdf/ontology.rs +++ b/publish/src/rdf/ontology.rs @@ -60,7 +60,6 @@ static PREFIXES: LazyLock> = LazyLock::new(|| { pub struct OntologyBuilder { path: Option, - materialize_inferences: bool, } impl OntologyBuilder { @@ -70,18 +69,9 @@ impl OntologyBuilder { self } - pub fn materialize_inferences(mut self) -> Self { - self.materialize_inferences = true; - self - } - pub fn build(self) -> error::Result { - let mut store = if let Some(path) = self.path { - if self.materialize_inferences { - Store::open(path) - } else { - Store::open_read_only(path) - } + let store = if let Some(path) = self.path { + Store::open(path) } else { Store::new() }?; @@ -158,7 +148,6 @@ impl Ontology { pub fn builder() -> OntologyBuilder { OntologyBuilder { path: None, - materialize_inferences: false, } }