Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
753 changes: 753 additions & 0 deletions public/app/axiolotl-inference.js

Large diffs are not rendered by default.

121 changes: 90 additions & 31 deletions public/lib/axiolotl-query.js → public/app/axiolotl-query.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,23 +56,59 @@ const defaultActivePrefixes = ['rdfs', 'owl', 'skos'];
* window.__lastOverlayGraph exists
* element with id="rdf-preview" exists
* getSelectedOutputMime() function exists
* $rdf.serialize function exists
* @returns
*/
function updatePreviewFromOverlay() {
async function updatePreviewFromOverlay() {
const g = window.__lastOverlayGraph;
const box = document.getElementById('rdf-preview');
if (!g || !box) return;

try {
const mime = getSelectedOutputMime(); // turtle, n-triples, etc.
const text = $rdf.serialize(null, g, 'http://example.org/', mime);
const mime = getSelectedOutputMime();
const text = await serializeStore(g, mime);
box.value = text;
} catch (e) {
if (debuggingConsoleEnabled) {console.error('[updatePreviewFromOverlay] serialize error:', e);}
if (debuggingConsoleEnabled) {
console.error('[updatePreviewFromOverlay] serialize error:', e);
}
box.value = `Serialization error: ${e && (e.message || e)}`;
}
}

async function serializeStore(store, mime = 'text/turtle') {
if (!store || typeof store.getQuads !== 'function') {
throw new Error('serializeStore expected an N3.Store or compatible RDF/JS source.');
}

const supported = new Set([
'text/turtle',
'application/n-triples',
'application/n-quads'
]);

const format = supported.has(mime) ? mime : 'text/turtle';

return await new Promise((resolve, reject) => {
const writer = new N3.Writer({ format });
writer.addQuads(store.getQuads(null, null, null, null));
writer.end((error, result) => {
if (error) reject(error);
else resolve(result || '');
});
});
}

async function serializeStoreToNTriples(store) {
return await new Promise((resolve, reject) => {
const writer = new N3.Writer({ format: 'N-Triples' });
writer.addQuads(store.getQuads(null, null, null, null));
writer.end((error, result) => {
if (error) reject(error);
else resolve(result || '');
});
});
}

/**
* Get/set active prefixes from localStorage
* Assumes:
Expand Down Expand Up @@ -203,17 +239,20 @@ async function handleRunInference() {

const { overlayGraph, metrics } = await inferUntilStable(selectedRules, baseIRI, overlayIRI);

// Serialize and display preview
const previewText = await serializeTurtle(overlayGraph);
window.__lastOverlayGraph = overlayGraph;

const previewText = await serializeStore(overlayGraph, getSelectedOutputMime());
document.getElementById('rdf-preview').value = previewText;

// Save overlay graph
await stashGraphToIndexedDB(overlayGraph, 'named', overlayIRI);

// Log metrics
if (debuggingConsoleEnabled) {console.info('[handleRunInference] Inference metrics:', metrics);}
if (debuggingConsoleEnabled) {
console.info('[handleRunInference] Inference metrics:', metrics);
}
} catch (error) {
if (debuggingConsoleEnabled) {console.error('[handleRunInference] Failed:', error);}
if (debuggingConsoleEnabled) {
console.error('[handleRunInference] Failed:', error);
}
}
}

Expand Down Expand Up @@ -248,23 +287,33 @@ function getSaveTarget() {

// Save inferred overlay graph to IndexedDB
async function runInference() {
try {
try {
clearInferenceConsole?.();
setInferenceBusy(true);

const rules = getSelectedRulesFromCheckboxes();
const { overlayGraph, metrics } = await inferUntilStable(rules);

window.__lastOverlayGraph = overlayGraph;
updatePreviewFromOverlay();
await updatePreviewFromOverlay();

const n = overlayGraph.getQuads(null, null, null, null).length;

const n = overlayGraph.statements.length;
showToast(
n ? `Inference finished — ${n} triple${n === 1 ? '' : 's'} materialized.`
n
? `Inference finished — ${n} triple${n === 1 ? '' : 's'} materialized.`
: 'Inference finished — no new triples.',
n ? 'success' : 'info'
);
} catch (err) {
if (debuggingConsoleEnabled) {console.error('[run-inference] failed:', err);}
if (debuggingConsoleEnabled) {
console.error('[run-inference] failed:', err);
}
showToast(`Inference error: ${err.message || err}`, 'error');
} finally {
setInferenceBusy(false);
}
};
}

// Insert overlay graph into SPARQL endpoint
async function saveOverlayToIndexedDB(overlayGraph, { mode, graphIRI }) {
Expand Down Expand Up @@ -306,25 +355,29 @@ async function insertInferredTriplesIntoEndpoint() {
};

// Export inferred overlay graph as a file in chosen format
function exportInferredOverlay() {
async function exportInferredOverlay() {
try {
const g = window.__lastOverlayGraph;
if (!g) throw new Error('Nothing to export. Run inference first.');

const mime = getSelectedOutputMime();
const text = serializeGraph(g, mime);
const text = await serializeStore(g, mime);

const ext = ({
'text/turtle': 'ttl',
'application/n-triples': 'nt',
'application/ld+json': 'jsonld',
'application/rdf+xml': 'rdf'
'application/n-quads': 'nq'
})[mime] || 'ttl';

downloadText(`inferred-${timestampUTC()}.${ext}`, text, mime);
showToast('Download started.', 'success');
} catch (e) {
if (debuggingConsoleEnabled) {console.error(e);}
if (debuggingConsoleEnabled) {
console.error(e);
}
showToast(e.message || String(e), 'error');
}
};
}

// Dynamically add file + IRI input rows
function createFileInputRow(index) {
Expand Down Expand Up @@ -378,8 +431,12 @@ toggleReasonerButtons();
document.getElementById('run-inference')?.addEventListener('click', runInference);
document.getElementById('save-inferred-to-db')?.addEventListener('click', saveInferredTriplesToDB);
document.getElementById('insert-inferred-to-endpoint')?.addEventListener('click', insertInferredTriplesIntoEndpoint);
document.getElementById('export-inferred')?.addEventListener('click', exportInferredOverlay);
document.getElementById('output-format')?.addEventListener('change', updatePreviewFromOverlay);
document.getElementById('export-inferred')?.addEventListener('click', async () => {
await exportInferredOverlay();
});
document.getElementById('output-format')?.addEventListener('change', async () => {
await updatePreviewFromOverlay();
});

// Event handler for adding new rows
document.getElementById('add-file-row').addEventListener('click', addNewFileRow);
Expand Down Expand Up @@ -542,6 +599,7 @@ function initTabs() {

// UI event bindings
window.addEventListener('DOMContentLoaded', () => {
setInferenceBusy(false);
initTabs();
document.getElementById('file-upload')?.addEventListener('change', async (e) => {
for (const file of e.target.files) {
Expand Down Expand Up @@ -751,12 +809,13 @@ const commitUpdateByMaterialization = async (updateStr, targetMode='default') =>
if (insQs.length) {
for (const q of insQs) {
const ttl = await runConstructPreview(q, 'text/turtle');
const overlay = $rdf.graph();
await new Promise((resolve, reject) => {
$rdf.parse(ttl, overlay, 'http://example.org/', 'text/turtle', err => err ? reject(err) : resolve());
});

const parser = new N3.Parser({ format: 'text/turtle', baseIRI: 'http://example.org/' });
const quads = parser.parse(ttl);
const overlay = new N3.Store(quads);

await stashGraphToIndexedDB(overlay, targetMode, graphIRI);
inserted += overlay.statements.length;
inserted += quads.length;
}
}

Expand Down Expand Up @@ -820,7 +879,7 @@ async function renderOntologyList() {

const warnMissing = !fileName || !dataPath;
const li = document.createElement('li');
li.style.marginLeft = '0.4em';
li.style.marginLeft = '1.5em';
li.style.marginBottom = '0.4em';
li.innerHTML = `
<label ${warnMissing ? 'style="color:red;" title="Missing file name"' : ''}>
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ const applyUpdateWithComunica = async (updateQuery, graph) => {
// Prefer the CONSTRUCT path for inference.
if (debuggingConsoleEnabled) {console.warn('[applyUpdateWithComunica] UPDATE against stringSource is a no-op; prefer CONSTRUCT.')};
const comunica = engine;
const datasetText = $rdf.serialize(null, graph, 'http://example.org/', 'text/turtle');
const source = { type: 'stringSource', value: datasetText, mediaType: 'text/turtle' };
const text = await serializeStore(g, mime);
const source = { type: 'stringSource', value: text, mediaType: 'text/turtle' };
await comunica.queryVoid(updateQuery, {
sources: [source],
baseIRI: 'http://example.org/',
Expand Down Expand Up @@ -777,58 +777,28 @@ function makeNamedGraphIRI(base='urn:graph:auto') {
* @param {boolean} [opts.replace=false] - if true and mode==='named', clear target graph before append
* @returns {Promise<{count:number, graphIRI:string}>}
*/
async function stashGraphToIndexedDB(
graph,
mode = 'default',
graphIRI = null,
autoBase = 'urn:graph:auto',
opts = {}
) {
const { dedupe = true, replace = false } = opts;
if (!graph) throw new Error('No graph provided');

async function stashGraphToIndexedDB(graph, mode='default', graphIRI=null, autoBase='urn:graph:auto', opts={}) {
const iri = (mode === 'named') ? (graphIRI || makeNamedGraphIRI(autoBase)) : null;
const graphSym = iri ? $rdf.namedNode(iri) : undefined;

// Prepare statements for the target graph (assign .graph = graphSym for named)
let prepared = graph.statements.map(st =>
new $rdf.Statement(st.subject, st.predicate, st.object, graphSym)
);

// Optional: batch de-dup to avoid inserting exact duplicates
if (dedupe) {
const seen = new Set();
// rdflib Statement#toNT() yields N-Triples; with .graph set, it’s effectively N-Quads
prepared = prepared.filter(st => {
const key = st.toNT();
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}

// Optional: targeted "replace" for named graph (no full DB load)
if (replace && iri) {
// implement (or call) a clearNamedGraph(iri) helper that deletes rows where row.graph === iri
// await clearNamedGraph(iri);
// If you don't have it yet, you can add a filtered delete in indexeddb-triplestore.js
}

// Append to IDB (no read/merge step)
await storeTriplesInNamedGraph(prepared);

// Notify UI so buttons update immediately
try {
window?.dispatchEvent(new CustomEvent('triples-changed', {
detail: { db: 'inferenceDB', store: 'triples', type: 'put' , graphIRI: graphIRI || '(default)'}
const graphValue = iri || '';

let prepared;
if (typeof graph.getQuads === 'function') {
prepared = graph.getQuads(null, null, null, null).map(q => ({
subject: q.subject.value,
subjectType: q.subject.termType,
predicate: q.predicate.value,
predicateType: q.predicate.termType,
object: q.object.value,
objectType: q.object.termType,
objectLang: q.object.language || null,
objectDatatype: q.object.datatype?.value || null,
graph: graphValue || (q.graph.termType === 'DefaultGraph' ? '' : q.graph.value)
}));
} catch {}

const count = prepared.length;
if (debuggingConsoleEnabled) {
console.info(`[stashGraphToIndexedDB] Saved ${count} triple(s) into ${iri || '(default graph)'}${replace ? ' (replace)' : ''}`);
await storeTriplesInNamedGraph(prepared);
return { count: prepared.length, graphIRI: iri || '(default graph)' };
}
return { count, graphIRI: iri || '(default graph)' };

throw new Error('stashGraphToIndexedDB expected an N3.Store or compatible RDF/JS source');
}


Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
12 changes: 6 additions & 6 deletions public/graph-analytics.html
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,12 @@ <h1>IRI Path Finder</h1>
</div>
</div>
</div>
<script src="lib/n3.min.js"></script>
<script src="lib/rdflib.min.js"></script>
<script src="lib/comunica-browser.js"></script>
<script src="lib/idb.min.js"></script>
<script src="lib/comunica-indexeddb-bridge.js"></script>
<script src="indexeddb-triplestore.js"></script>
<script src="./app/n3.min.js"></script>
<script src="./app/rdflib.min.js"></script>
<script src="./app/comunica-browser.js"></script>
<script src="./app/idb.min.js"></script>
<script src="./app/comunica-indexeddb-bridge.js"></script>
<script src="./app/indexeddb-triplestore.js"></script>

<script>
(function(){
Expand Down
Loading
Loading