This commit is contained in:
2026-08-07 19:28:08 -04:00
parent 36a105edfc
commit 64a2c87346
4 changed files with 112 additions and 48 deletions
+1
View File
@@ -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 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")); const INPUT_GRAPH: GraphNameRef = GraphNameRef::NamedNode(NamedNodeRef::new_unchecked("https://graphofliberty.org/input"));
#[derive(Clone)]
pub struct InferenceEngine { pub struct InferenceEngine {
ontology: Store, ontology: Store,
} }
+100 -28
View File
@@ -3,7 +3,6 @@ use crate::rdf::ontology::{LabeledIri, Ontology};
use crate::rdf::term_helper::{TermHelper, TermHelperMut}; use crate::rdf::term_helper::{TermHelper, TermHelperMut};
use crate::widget::iri_input::iri_input; use crate::widget::iri_input::iri_input;
use crate::widget::navigation_area::navigation_area; use crate::widget::navigation_area::navigation_area;
use gl_graph::vocab::rdac;
use gl_search::language; use gl_search::language;
use gl_search::{Schema, SearchDocument, SearchIndex, Value}; use gl_search::{Schema, SearchDocument, SearchIndex, Value};
use http::StatusCode; use http::StatusCode;
@@ -22,9 +21,11 @@ use ldp::reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
use ldp::{RdfSource, RdfSourceUpdateResponse, ResourceRequestBuilder, SerializationOptions}; use ldp::{RdfSource, RdfSourceUpdateResponse, ResourceRequestBuilder, SerializationOptions};
use oxigraph::io::RdfFormat; use oxigraph::io::RdfFormat;
use oxigraph::model::vocab::{rdf, rdfs}; 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 tracing::{debug_span, error, trace};
use gl_graph::{vocab, CurieHelper}; use gl_graph::CurieHelper;
use gl_graph::inference::InferenceEngine;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) enum Message { pub(crate) enum Message {
@@ -38,7 +39,7 @@ pub(crate) enum Message {
ShowNewDocumentButtons, ShowNewDocumentButtons,
HideNewDocumentButtons, HideNewDocumentButtons,
ShowError(String), ShowError(String),
AddRow(Option<QuadKey>), AddRow(Option<(QuadKey, RowState)>),
DeleteRow(QuadKey), DeleteRow(QuadKey),
DeleteAllRows, DeleteAllRows,
OpenQueryWindow(SearchResultClickAction), OpenQueryWindow(SearchResultClickAction),
@@ -65,6 +66,10 @@ pub(crate) enum Message {
NavigateForward, NavigateForward,
ResetState, ResetState,
SetReadOnly(bool), SetReadOnly(bool),
SetInferredTypes(bool),
SetInferredProperties(bool),
RunInference,
SetInferredTriples(Dataset),
Event(Event), Event(Event),
} }
@@ -93,13 +98,17 @@ pub(crate) struct Publisher {
http_client: ClientWithMiddleware, http_client: ClientWithMiddleware,
curie_helper: CurieHelper, curie_helper: CurieHelper,
ontology: Ontology, ontology: Ontology,
inference_engine: InferenceEngine,
abbreviated_datatypes: Vec<String>, abbreviated_datatypes: Vec<String>,
window_id: window::Id, window_id: window::Id,
url_input: String, url_input: String,
url_input_valid: bool, url_input_valid: bool,
navigator: Navigator<Url>, navigator: Navigator<Url>,
document: RdfSource<KeyedDataset<RowState>>, document: RdfSource<KeyedDataset<RowState>>,
inferred_triples: Graph,
show_read_only: bool, show_read_only: bool,
show_inferred_types: bool,
show_inferred_properties: bool,
hovered_row: Option<QuadKey>, hovered_row: Option<QuadKey>,
search_state: Option<SearchState>, search_state: Option<SearchState>,
index: SearchIndex, index: SearchIndex,
@@ -120,6 +129,8 @@ impl Publisher {
.expect("Failed to build ontology") .expect("Failed to build ontology")
}); });
let inference_engine = InferenceEngine::new(ontology.store().clone());
let index = SearchIndex::builder() let index = SearchIndex::builder()
.with_path("/home/alex/.local/share/org.graphofliberty.desktop/index") .with_path("/home/alex/.local/share/org.graphofliberty.desktop/index")
.build() .build()
@@ -152,13 +163,17 @@ impl Publisher {
http_client, http_client,
curie_helper, curie_helper,
ontology, ontology,
inference_engine,
abbreviated_datatypes, abbreviated_datatypes,
window_id: id, window_id: id,
url_input: starting_url.to_string(), url_input: starting_url.to_string(),
url_input_valid: true, url_input_valid: true,
navigator, navigator,
document, document,
inferred_triples: Graph::new(),
show_read_only: false, show_read_only: false,
show_inferred_types: false,
show_inferred_properties: false,
hovered_row: None, hovered_row: None,
search_state: None, search_state: None,
index, index,
@@ -234,9 +249,21 @@ impl Publisher {
let messages = document let messages = document
.dataset() .dataset()
.quads .quads
.keys() .iter()
.map(|key| Message::AddRow(Some(key))); .map(|(key, quad)| {
task = Task::batch(messages.map(Task::done)).chain(Task::done(Message::ResetState)); 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; self.document = document;
} }
@@ -250,33 +277,21 @@ impl Publisher {
quad.object = Term::NamedNode(empty_node); quad.object = Term::NamedNode(empty_node);
let key = self.document.dataset_mut().quads.insert(quad); 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( let datatype_state = combo_box::State::new(
self.abbreviated_datatypes.clone(), self.abbreviated_datatypes.clone(),
); );
let state = RowState { let state = RowState {
read_only, read_only: false,
datatype_state, datatype_state,
}; };
task = Task::done(Message::AddRow(Some((key, state))));
}
Message::AddRow(Some((key, state))) => {
self.document self.document
.dataset_mut() .dataset_mut()
.associated_data .associated_data
.insert(key, state); .insert(key, state);
self.modified = true; self.modified = true;
} }
Message::DeleteRow(key) => { Message::DeleteRow(key) => {
@@ -472,7 +487,7 @@ impl Publisher {
let subject = NamedNode::new_unchecked(url.as_str()); let subject = NamedNode::new_unchecked(url.as_str());
self.document = RdfSource::new(url); self.document = RdfSource::new(url);
let template_quads = self /*let template_quads = self
.ontology .ontology
.template_triples(class.as_ref(), subject.as_ref()) .template_triples(class.as_ref(), subject.as_ref())
.map(|triples| { .map(|triples| {
@@ -490,7 +505,7 @@ impl Publisher {
let new_keys = self.document.dataset_mut().extend(template_quads.into_iter()); let new_keys = self.document.dataset_mut().extend(template_quads.into_iter());
let messages = new_keys.map(|triple| Message::AddRow(Some(triple))); 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 => { Message::ResetState => {
self.modified = false; self.modified = false;
@@ -526,6 +541,28 @@ impl Publisher {
Message::SetReadOnly(value) => { Message::SetReadOnly(value) => {
self.show_read_only = 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 { Message::Event(Event::KeyPressed {
key: keyboard::Key::Named(key::Named::Tab), key: keyboard::Key::Named(key::Named::Tab),
modifiers, modifiers,
@@ -823,6 +860,29 @@ impl Publisher {
column(rows).into() 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_base = button(text("\u{1f4be}"));
let save_button = if self.modified { let save_button = if self.modified {
save_button_base.on_press(Message::SaveGraph(false)) save_button_base.on_press(Message::SaveGraph(false))
@@ -830,10 +890,22 @@ impl Publisher {
save_button_base 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) let read_only_toggle = toggler(self.show_read_only)
.on_toggle(Message::SetReadOnly); .on_toggle(Message::SetReadOnly);
let footer = row![ let footer = row![
space::horizontal(),
text("Show Inferred Types"),
inferred_type_toggle,
space::horizontal(),
text("Show Inferred Properties"),
inferred_property_toggle,
space::horizontal(), space::horizontal(),
text("Show Read Only Triples"), text("Show Read Only Triples"),
read_only_toggle read_only_toggle
@@ -847,9 +919,9 @@ impl Publisher {
address_input, address_input,
save_button, save_button,
], ],
scrollable(body),
space::vertical(),
footer, footer,
scrollable(body),
scrollable(inference_table),
]) ])
.on_back_click(Message::NavigateBack) .on_back_click(Message::NavigateBack)
.on_forward_click(Message::NavigateForward); .on_forward_click(Message::NavigateForward);
+9 -7
View File
@@ -15,20 +15,19 @@ use ldp::reqwest::{Client, Url};
use ldp::reqwest_middleware::ClientBuilder; use ldp::reqwest_middleware::ClientBuilder;
use ldp::traverse::Traverse; use ldp::traverse::Traverse;
use oxigraph::io::{RdfFormat, RdfParser, RdfSerializer}; 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::{QueryResults, SparqlEvaluator};
use oxigraph::sparql::results::{QueryResultsFormat, QueryResultsSerializer}; use oxigraph::sparql::results::{QueryResultsFormat, QueryResultsSerializer};
use oxigraph::store::Store; use oxigraph::store::Store;
use tracing::{debug, debug_span, error, field}; use tracing::{debug_span, error, field};
use crate::app::Publisher; use crate::app::Publisher;
use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{EnvFilter, fmt}; use tracing_subscriber::{EnvFilter, fmt};
use tracing_subscriber::fmt::format::FmtSpan; use tracing_subscriber::fmt::format::FmtSpan;
use gl_graph::inference::InferenceEngine; use gl_graph::inference::InferenceEngine;
use gl_search::{Document, Schema, SearchIndex}; use gl_search::SearchIndex;
use crate::args::{AppArgs, Command}; use crate::args::{AppArgs, Command};
use crate::rdf::ontology::Ontology;
fn main() -> color_eyre::Result<()> { fn main() -> color_eyre::Result<()> {
let appender = tracing_appender::rolling::never("/tmp", "publisher-log"); let appender = tracing_appender::rolling::never("/tmp", "publisher-log");
@@ -38,9 +37,6 @@ fn main() -> color_eyre::Result<()> {
.init(); .init();
color_eyre::install()?; 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() let mut index = SearchIndex::builder()
.with_path("/home/alex/.local/share/org.graphofliberty.desktop/index") .with_path("/home/alex/.local/share/org.graphofliberty.desktop/index")
.build() .build()
@@ -49,6 +45,9 @@ fn main() -> color_eyre::Result<()> {
let args = AppArgs::parse(); let args = AppArgs::parse();
match args.command { match args.command {
Some(Command::Query(args)) => { 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 graph_name = GraphName::NamedNode(NamedNode::new_unchecked(format!("file://{}", args.dataset_path.to_string_lossy())));
let mut dataset = RdfParser::from_format(RdfFormat::Turtle) let mut dataset = RdfParser::from_format(RdfFormat::Turtle)
.with_default_graph(graph_name) .with_default_graph(graph_name)
@@ -92,6 +91,9 @@ fn main() -> color_eyre::Result<()> {
}*/ }*/
} }
Some(Command::Reindex) => { 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()?; let mut writer = index.writer()?;
debug_span!("Clear Index").in_scope(|| { debug_span!("Clear Index").in_scope(|| {
writer.delete_all_documents()?; writer.delete_all_documents()?;
+1 -12
View File
@@ -60,7 +60,6 @@ static PREFIXES: LazyLock<BTreeMap<String, String>> = LazyLock::new(|| {
pub struct OntologyBuilder { pub struct OntologyBuilder {
path: Option<PathBuf>, path: Option<PathBuf>,
materialize_inferences: bool,
} }
impl OntologyBuilder { impl OntologyBuilder {
@@ -70,18 +69,9 @@ impl OntologyBuilder {
self self
} }
pub fn materialize_inferences(mut self) -> Self {
self.materialize_inferences = true;
self
}
pub fn build(self) -> error::Result<Ontology> { pub fn build(self) -> error::Result<Ontology> {
let mut store = if let Some(path) = self.path { let store = if let Some(path) = self.path {
if self.materialize_inferences {
Store::open(path) Store::open(path)
} else {
Store::open_read_only(path)
}
} else { } else {
Store::new() Store::new()
}?; }?;
@@ -158,7 +148,6 @@ impl Ontology {
pub fn builder() -> OntologyBuilder { pub fn builder() -> OntologyBuilder {
OntologyBuilder { OntologyBuilder {
path: None, path: None,
materialize_inferences: false,
} }
} }